[TableGen] Track reference locations of Records/RecordVals

This is extremely useful for language tooling as it allows
for providing go-to-def/find-references/etc. for many
more situations than what is currently possible.

Differential Revision: https://reviews.llvm.org/D134087
This commit is contained in:
River Riddle 2022-09-16 15:06:51 -07:00
parent 67bce07964
commit 50d96f59d0
18 changed files with 108 additions and 51 deletions

View File

@ -4,31 +4,31 @@ include "DiagnosticBase.inc"
def NamedGroup : DiagGroup<"a">;
def InNamedGroup1 : Warning<"">, InGroup<DiagGroup<"a">>;
def InNamedGroup2 : Warning<"">, InGroup < DiagGroup<"a"> >;
// CHECK: redefined-group.td:[[@LINE-3]]:1: error: group 'a' is defined more than once
// CHECK: redefined-group.td:[[@LINE-3]]:1: note: also implicitly defined here
// CHECK: redefined-group.td:[[@LINE-3]]:1: note: also implicitly defined here
// CHECK: redefined-group.td:[[@LINE-3]]:5: error: group 'a' is defined more than once
// CHECK: redefined-group.td:[[@LINE-3]]:5: note: also implicitly defined here
// CHECK: redefined-group.td:[[@LINE-3]]:5: note: also implicitly defined here
def : DiagGroup<"b">;
def InUnnamedGroup : Warning<"">, InGroup<DiagGroup<"b">>;
// CHECK: redefined-group.td:[[@LINE-2]]:1: error: group 'b' is defined more than once
// CHECK: redefined-group.td:[[@LINE-2]]:1: note: also implicitly defined here
// CHECK: redefined-group.td:[[@LINE-2]]:5: note: also implicitly defined here
def ImplicitGroup1 : Warning<"">, InGroup<DiagGroup<"c">>;
def ImplicitGroup2 : Warning<"">, InGroup<DiagGroup<"c">>;
def ImplicitGroup3 : Warning<"">,
InGroup<DiagGroup<"c">>;
// CHECK: redefined-group.td:[[@LINE-4]]:1: error: group 'c' is implicitly defined more than once
// CHECK: redefined-group.td:[[@LINE-4]]:1: note: also implicitly defined here
// CHECK: redefined-group.td:[[@LINE-4]]:1: note: also implicitly defined here
// CHECK: redefined-group.td:[[@LINE-4]]:5: error: group 'c' is implicitly defined more than once
// CHECK: redefined-group.td:[[@LINE-4]]:5: note: also implicitly defined here
// CHECK: redefined-group.td:[[@LINE-4]]:5: note: also implicitly defined here
def NamedAndUnnamed : DiagGroup<"d">;
def : DiagGroup<"d">;
// CHECK: redefined-group.td:[[@LINE-2]]:1: error: group 'd' is defined more than once
// CHECK: redefined-group.td:[[@LINE-2]]:5: error: group 'd' is defined more than once
// CHECK: redefined-group.td:[[@LINE-2]]:1: note: also defined here
def : DiagGroup<"e">;
def NamedAndUnnamed2 : DiagGroup<"e">;
// CHECK: redefined-group.td:[[@LINE-1]]:1: error: group 'e' is defined more than once
// CHECK: redefined-group.td:[[@LINE-1]]:5: error: group 'e' is defined more than once
// CHECK: redefined-group.td:[[@LINE-3]]:1: note: also defined here
def InGroupF1 : Warning<"">, InGroup<DiagGroup<"f">>;
@ -38,6 +38,6 @@ def GroupF : DiagGroup<"f">;
def InGroupF3 : Warning<"">, InGroup<GroupF>;
def InGroupF4 : Warning<"">, InGroup<DiagGroup<"f">>;
// CHECK: redefined-group.td:[[@LINE-5]]:1: error: group 'f' is defined more than once
// CHECK: redefined-group.td:[[@LINE-7]]:1: note: also implicitly defined here
// CHECK: redefined-group.td:[[@LINE-6]]:1: note: also implicitly defined here
// CHECK: redefined-group.td:[[@LINE-4]]:1: note: also implicitly defined here
// CHECK: redefined-group.td:[[@LINE-7]]:5: note: also implicitly defined here
// CHECK: redefined-group.td:[[@LINE-6]]:5: note: also implicitly defined here
// CHECK: redefined-group.td:[[@LINE-4]]:5: note: also implicitly defined here

View File

@ -1478,6 +1478,9 @@ private:
Init *Value;
bool IsUsed = false;
/// Reference locations to this record value.
SmallVector<SMRange> ReferenceLocs;
public:
RecordVal(Init *N, RecTy *T, FieldKind K);
RecordVal(Init *N, SMLoc Loc, RecTy *T, FieldKind K);
@ -1524,6 +1527,12 @@ public:
/// Set the value and source location of the field.
bool setValue(Init *V, SMLoc NewLoc);
/// Add a reference to this record value.
void addReferenceLoc(SMRange Loc) { ReferenceLocs.push_back(Loc); }
/// Return the references of this record value.
ArrayRef<SMRange> getReferenceLocs() const { return ReferenceLocs; }
/// Whether this value is used. Useful for reporting warnings, for example
/// when a template argument is unused.
void setUsed(bool Used) { IsUsed = Used; }
@ -1556,9 +1565,11 @@ public:
private:
Init *Name;
// Location where record was instantiated, followed by the location of
// multiclass prototypes used.
// multiclass prototypes used, and finally by the locations of references to
// this record.
SmallVector<SMLoc, 4> Locs;
SmallVector<SMLoc, 0> ForwardDeclarationLocs;
SmallVector<SMRange, 0> ReferenceLocs;
SmallVector<Init *, 0> TemplateArgs;
SmallVector<RecordVal, 0> Values;
SmallVector<AssertionInfo, 0> Assertions;
@ -1628,6 +1639,12 @@ public:
return ForwardDeclarationLocs;
}
/// Add a reference to this record value.
void appendReferenceLoc(SMRange Loc) { ReferenceLocs.push_back(Loc); }
/// Return the references of this record value.
ArrayRef<SMRange> getReferenceLocs() const { return ReferenceLocs; }
// Update a class location when encountering a (re-)definition.
void updateClassLoc(SMLoc Loc);

View File

@ -63,6 +63,10 @@ SMLoc TGLexer::getLoc() const {
return SMLoc::getFromPointer(TokStart);
}
SMRange TGLexer::getLocRange() const {
return {getLoc(), SMLoc::getFromPointer(CurPtr)};
}
/// ReturnError - Set the error to the specified string at the specified
/// location. This is defined to always return tgtok::Error.
tgtok::TokKind TGLexer::ReturnError(SMLoc Loc, const Twine &Msg) {

View File

@ -131,6 +131,7 @@ public:
}
SMLoc getLoc() const;
SMRange getLocRange() const;
private:
/// LexToken - Read the next token and return its code.

View File

@ -161,7 +161,7 @@ bool TGParser::AddValue(Record *CurRec, SMLoc Loc, const RecordVal &RV) {
/// Return true on error, false on success.
bool TGParser::SetValue(Record *CurRec, SMLoc Loc, Init *ValName,
ArrayRef<unsigned> BitList, Init *V,
bool AllowSelfAssignment) {
bool AllowSelfAssignment, bool OverrideDefLoc) {
if (!V) return false;
if (!CurRec) CurRec = &CurMultiClass->Rec;
@ -211,7 +211,7 @@ bool TGParser::SetValue(Record *CurRec, SMLoc Loc, Init *ValName,
V = BitsInit::get(Records, NewBits);
}
if (RV->setValue(V, Loc)) {
if (OverrideDefLoc ? RV->setValue(V, Loc) : RV->setValue(V)) {
std::string InitType;
if (BitsInit *BI = dyn_cast<BitsInit>(V))
InitType = (Twine("' of type bit initializer with length ") +
@ -586,6 +586,8 @@ Record *TGParser::ParseClassID() {
Lex.getCurStrVal() + "'");
else
TokError(Msg);
} else {
Result->appendReferenceLoc(Lex.getLocRange());
}
Lex.Lex();
@ -867,11 +869,13 @@ RecTy *TGParser::ParseType() {
}
/// ParseIDValue
Init *TGParser::ParseIDValue(Record *CurRec, StringInit *Name, SMLoc NameLoc,
Init *TGParser::ParseIDValue(Record *CurRec, StringInit *Name, SMRange NameLoc,
IDParseMode Mode) {
if (CurRec) {
if (const RecordVal *RV = CurRec->getValue(Name))
if (RecordVal *RV = CurRec->getValue(Name)) {
RV->addReferenceLoc(NameLoc);
return VarInit::get(Name, RV->getType());
}
}
if ((CurRec && CurRec->isClass()) || CurMultiClass) {
@ -887,6 +891,7 @@ Init *TGParser::ParseIDValue(Record *CurRec, StringInit *Name, SMLoc NameLoc,
RecordVal *RV = TemplateRec->getValue(TemplateArgName);
assert(RV && "Template arg doesn't exist??");
RV->setUsed(true);
RV->addReferenceLoc(NameLoc);
return VarInit::get(TemplateArgName, RV->getType());
} else if (Name->getValue() == "NAME") {
return VarInit::get(TemplateArgName, StringRecTy::get(Records));
@ -909,8 +914,12 @@ Init *TGParser::ParseIDValue(Record *CurRec, StringInit *Name, SMLoc NameLoc,
if (Mode == ParseNameMode)
return Name;
if (Init *I = Records.getGlobal(Name->getValue()))
if (Init *I = Records.getGlobal(Name->getValue())) {
// Add a reference to the global if it's a record.
if (auto *Def = dyn_cast<DefInit>(I))
Def->getDef()->appendReferenceLoc(NameLoc);
return I;
}
// Allow self-references of concrete defs, but delay the lookup so that we
// get the correct type.
@ -918,7 +927,7 @@ Init *TGParser::ParseIDValue(Record *CurRec, StringInit *Name, SMLoc NameLoc,
CurRec->getNameInit() == Name)
return UnOpInit::get(UnOpInit::CAST, Name, CurRec->getType());
Error(NameLoc, "Variable not defined: '" + Name->getValue() + "'");
Error(NameLoc.Start, "Variable not defined: '" + Name->getValue() + "'");
return nullptr;
}
@ -2184,7 +2193,7 @@ Init *TGParser::ParseSimpleValue(Record *CurRec, RecTy *ItemType,
Lex.Lex();
break;
case tgtok::Id: {
SMLoc NameLoc = Lex.getLoc();
SMRange NameLoc = Lex.getLocRange();
StringInit *Name = StringInit::get(Records, Lex.getCurStrVal());
if (Lex.Lex() != tgtok::less) // consume the Id.
return ParseIDValue(CurRec, Name, NameLoc, Mode); // Value ::= IDValue
@ -2194,7 +2203,8 @@ Init *TGParser::ParseSimpleValue(Record *CurRec, RecTy *ItemType,
// from the class with the template arguments, but no body.
Record *Class = Records.getClass(Name->getValue());
if (!Class) {
Error(NameLoc, "Expected a class name, got '" + Name->getValue() + "'");
Error(NameLoc.Start,
"Expected a class name, got '" + Name->getValue() + "'");
return nullptr;
}
@ -2203,7 +2213,7 @@ Init *TGParser::ParseSimpleValue(Record *CurRec, RecTy *ItemType,
if (ParseTemplateArgValueList(Args, CurRec, Class))
return nullptr; // Error parsing value list.
if (CheckTemplateArgValues(Args, NameLoc, Class))
if (CheckTemplateArgValues(Args, NameLoc.Start, Class))
return nullptr; // Error checking template argument values.
// Loop through the arguments that were not specified and make sure
@ -2211,14 +2221,15 @@ Init *TGParser::ParseSimpleValue(Record *CurRec, RecTy *ItemType,
ArrayRef<Init *> TArgs = Class->getTemplateArgs();
for (unsigned I = Args.size(), E = TArgs.size(); I < E; ++I) {
RecordVal *Arg = Class->getValue(TArgs[I]);
if (!Arg->getValue()->isComplete())
Error(NameLoc, "Value not specified for template argument '" +
TArgs[I]->getAsUnquotedString() + "' (#" + Twine(I) +
") of parent class '" +
Class->getNameInitAsString() + "'");
if (!Arg->getValue()->isComplete()) {
Error(NameLoc.Start, "Value not specified for template argument '" +
TArgs[I]->getAsUnquotedString() + "' (#" +
Twine(I) + ") of parent class '" +
Class->getNameInitAsString() + "'");
}
}
Class->appendReferenceLoc(NameLoc);
return VarDefInit::get(Class, Args)->Fold();
}
case tgtok::l_brace: { // Value ::= '{' ValueList '}'
@ -2510,12 +2521,25 @@ Init *TGParser::ParseValue(Record *CurRec, RecTy *ItemType, IDParseMode Mode) {
TokError("expected field identifier after '.'");
return nullptr;
}
SMRange FieldNameLoc = Lex.getLocRange();
StringInit *FieldName = StringInit::get(Records, Lex.getCurStrVal());
if (!Result->getFieldType(FieldName)) {
TokError("Cannot access field '" + Lex.getCurStrVal() + "' of value '" +
Result->getAsString() + "'");
return nullptr;
}
// Add a reference to this field if we know the record class.
if (auto *DI = dyn_cast<DefInit>(Result)) {
DI->getDef()->getValue(FieldName)->addReferenceLoc(FieldNameLoc);
} else if (auto *TI = dyn_cast<TypedInit>(Result)) {
if (auto *RecTy = dyn_cast<RecordRecTy>(TI->getType())) {
for (Record *R : RecTy->getClasses())
if (auto *RV = R->getValue(FieldName))
RV->addReferenceLoc(FieldNameLoc);
}
}
Result = FieldInit::get(Result, FieldName)->Fold(CurRec);
Lex.Lex(); // eat field name
break;
@ -2780,11 +2804,13 @@ Init *TGParser::ParseDeclaration(Record *CurRec,
SMLoc ValLoc = Lex.getLoc();
Init *Val = ParseValue(CurRec, Type);
if (!Val ||
SetValue(CurRec, ValLoc, DeclName, None, Val))
SetValue(CurRec, ValLoc, DeclName, None, Val,
/*AllowSelfAssignment=*/false, /*OverrideDefLoc=*/false)) {
// Return the name, even if an error is thrown. This is so that we can
// continue to make some progress, even without the value having been
// initialized.
return DeclName;
}
}
return DeclName;
@ -3078,17 +3104,24 @@ bool TGParser::ParseDef(MultiClass *CurMultiClass) {
assert(Lex.getCode() == tgtok::Def && "Unknown tok");
Lex.Lex(); // Eat the 'def' token.
// If the name of the def is an Id token, use that for the location.
// Otherwise, the name is more complex and we use the location of the 'def'
// token.
SMLoc NameLoc = Lex.getCode() == tgtok::Id ? Lex.getLoc() : DefLoc;
// Parse ObjectName and make a record for it.
std::unique_ptr<Record> CurRec;
Init *Name = ParseObjectName(CurMultiClass);
if (!Name)
return true;
if (isa<UnsetInit>(Name))
CurRec = std::make_unique<Record>(Records.getNewAnonymousName(), DefLoc, Records,
if (isa<UnsetInit>(Name)) {
CurRec =
std::make_unique<Record>(Records.getNewAnonymousName(), DefLoc, Records,
/*Anonymous=*/true);
else
CurRec = std::make_unique<Record>(Name, DefLoc, Records);
} else {
CurRec = std::make_unique<Record>(Name, NameLoc, Records);
}
if (ParseObjectBody(CurRec.get()))
return true;

View File

@ -198,9 +198,12 @@ public:
private: // Semantic analysis methods.
bool AddValue(Record *TheRec, SMLoc Loc, const RecordVal &RV);
/// Set the value of a RecordVal within the given record. If `OverrideDefLoc`
/// is set, the provided location overrides any existing location of the
/// RecordVal.
bool SetValue(Record *TheRec, SMLoc Loc, Init *ValName,
ArrayRef<unsigned> BitList, Init *V,
bool AllowSelfAssignment = false);
bool AllowSelfAssignment = false, bool OverrideDefLoc = true);
bool AddSubClass(Record *Rec, SubClassReference &SubClass);
bool AddSubClass(RecordsEntry &Entry, SubClassReference &SubClass);
bool AddSubMultiClass(MultiClass *CurMC,
@ -244,7 +247,7 @@ private: // Parser methods.
SubClassReference ParseSubClassReference(Record *CurRec, bool isDefm);
SubMultiClassReference ParseSubMultiClassReference(MultiClass *CurMC);
Init *ParseIDValue(Record *CurRec, StringInit *Name, SMLoc NameLoc,
Init *ParseIDValue(Record *CurRec, StringInit *Name, SMRange NameLoc,
IDParseMode Mode = ParseValueMode);
Init *ParseSimpleValue(Record *CurRec, RecTy *ItemType = nullptr,
IDParseMode Mode = ParseValueMode);

View File

@ -2,5 +2,5 @@
include "ConstraintChecking.inc"
// CHECK: [[FILE]]:[[@LINE+1]]:1: error: Unrecognized constraint '$dest1 ~ $src2' in 'Foo'
// CHECK: [[FILE]]:[[@LINE+1]]:5: error: Unrecognized constraint '$dest1 ~ $src2' in 'Foo'
def Foo : TestInstructionWithConstraints<"$dest1 ~ $src2">;

View File

@ -2,5 +2,5 @@
include "ConstraintChecking.inc"
// CHECK: [[FILE]]:[[@LINE+1]]:1: error: Illegal format for @earlyclobber constraint in 'Foo'
// CHECK: [[FILE]]:[[@LINE+1]]:5: error: Illegal format for @earlyclobber constraint in 'Foo'
def Foo : TestInstructionWithConstraints<"@earlyclobber ">;

View File

@ -4,5 +4,5 @@ include "ConstraintChecking.inc"
// (This is illegal because the '=' has to be surrounded by whitespace)
// CHECK: [[FILE]]:[[@LINE+1]]:1: error: Illegal format for tied-to constraint in 'Foo'
// CHECK: [[FILE]]:[[@LINE+1]]:5: error: Illegal format for tied-to constraint in 'Foo'
def Foo : TestInstructionWithConstraints<"$dest1=$dest2">;

View File

@ -2,5 +2,5 @@
include "ConstraintChecking.inc"
// CHECK: [[FILE]]:[[@LINE+1]]:1: error: Output operands '$dest1' and '$dest2' of 'Foo' cannot be tied!
// CHECK: [[FILE]]:[[@LINE+1]]:5: error: Output operands '$dest1' and '$dest2' of 'Foo' cannot be tied!
def Foo : TestInstructionWithConstraints<"$dest1 = $dest2">;

View File

@ -2,5 +2,5 @@
include "ConstraintChecking.inc"
// CHECK: [[FILE]]:[[@LINE+1]]:1: error: Input operands '$src1' and '$src2' of 'Foo' cannot be tied!
// CHECK: [[FILE]]:[[@LINE+1]]:5: error: Input operands '$src1' and '$src2' of 'Foo' cannot be tied!
def Foo : TestInstructionWithConstraints<"$src1 = $src2">;

View File

@ -2,5 +2,5 @@
include "ConstraintChecking.inc"
// CHECK: [[FILE]]:[[@LINE+1]]:1: error: Operand '$dest1' of 'Foo' cannot have multiple operands tied to it!
// CHECK: [[FILE]]:[[@LINE+1]]:5: error: Operand '$dest1' of 'Foo' cannot have multiple operands tied to it!
def Foo : TestInstructionWithConstraints<"$dest1 = $src1, $dest1 = $src2">;

View File

@ -2,5 +2,5 @@
include "ConstraintChecking.inc"
// CHECK: [[FILE]]:[[@LINE+1]]:1: error: Operand '$src1' of 'Foo' cannot have multiple constraints!
// CHECK: [[FILE]]:[[@LINE+1]]:5: error: Operand '$src1' of 'Foo' cannot have multiple constraints!
def Foo : TestInstructionWithConstraints<"$dest1 = $src1, $dest2 = $src1">;

View File

@ -19,6 +19,6 @@ def ICMPEQ : I<(outs GPR32:$dst), (ins GPR32Op:$src0, GPR32:$src1),
[(set GPR32:$dst, (i32 (setcc i32:$src0, i32:$src1, SETEQ)))]>;
// Check there is an error if not a CondCode operand.
// ERR: [[FILE]]:[[@LINE+1]]:1: warning: Skipped pattern: Unable to handle CondCode
// ERR: [[FILE]]:[[@LINE+1]]:5: warning: Skipped pattern: Unable to handle CondCode
def FCMP_NOTCC : I<(outs GPR32:$dst), (ins FPR32Op:$src0, FPR32:$src1),
[(set GPR32:$dst, (i32 (setcc f32:$src0, f32:$src1, i32)))]>;

View File

@ -4,4 +4,4 @@ include "llvm/Target/Target.td"
def MyTarget : Target;
def R0 : Register<"r0">;
def ClassA : RegisterClass<"MyTarget", [], 32, (add R0)>; // CHECK: [[@LINE]]:1: error: RegTypes list must not be empty!
def ClassA : RegisterClass<"MyTarget", [], 32, (add R0)>; // CHECK: [[@LINE]]:5: error: RegTypes list must not be empty!

View File

@ -4,7 +4,7 @@ include "llvm/Target/Target.td"
def TestTarget : Target;
// CHECK: [[FILE]]:[[@LINE+1]]:1: error: No schedule information for instruction 'TestInst' in SchedMachineModel 'TestSchedModel'
// CHECK: [[FILE]]:[[@LINE+1]]:5: error: No schedule information for instruction 'TestInst' in SchedMachineModel 'TestSchedModel'
def TestInst : Instruction {
let OutOperandList = (outs);
let InOperandList = (ins);

View File

@ -154,7 +154,7 @@ class DEntry<string str, int val1> {
}
def DFoo : DEntry<"foo", 1>;
// ERROR1: [[@LINE+1]]:1: error: Record 'DBar' for table 'DTable' is missing field 'Val1'
// ERROR1: [[@LINE+1]]:5: error: Record 'DBar' for table 'DTable' is missing field 'Val1'
def DBar : DEntry<"bar", ?>;
def DTable : GenericTable {

View File

@ -202,16 +202,15 @@ void TableGenIndex::initialize(const llvm::RecordKeeper &records) {
// Add references to the definition.
for (SMLoc loc : def.getLoc().drop_front())
insertRef(sym, lsp::convertTokenLocToRange(loc));
// Add references to any super classes.
for (auto &it : def.getSuperClasses())
insertRef(getOrInsertDef(it.first),
lsp::convertTokenLocToRange(it.second.Start));
for (SMRange loc : def.getReferenceLocs())
insertRef(sym, loc);
// Add definitions for any values.
for (const llvm::RecordVal &value : def.getValues()) {
auto *sym = getOrInsertDef(&value);
insertRef(sym, sym->defLoc, /*isDef=*/true);
for (SMRange refLoc : value.getReferenceLocs())
insertRef(sym, refLoc);
}
}
}