[Mlir-commits] [llvm] [mlir] [TableGen] Store template arguments separately from fields (PR #234933)
Jay Foad
llvmlistbot at llvm.org
Fri Oct 23 08:47:24 PDT 2026
https://github.com/jayfoad updated https://github.com/llvm/llvm-project/pull/234933
>From c12ad2ee261bfd29c3f57c9a1a2fe0d92f04c842 Mon Sep 17 00:00:00 2001
From: Jay Foad <jay.foad at amd.com>
Date: Fri, 23 Oct 2026 12:29:49 +0100
Subject: [PATCH 1/2] [TableGen] Store template arguments separately from
fields
Record used to store template arguments as RecordVals in Values, along
with the fields, and keep a separate list of their names. Store them in
their own vector of RecordVals instead. getValues() now only returns
fields, so the code that skipped template arguments when inheriting,
instantiating, printing or dumping fields goes away. getValue() looks up
both fields and template arguments, and the new getTemplateArg() looks
up only template arguments.
A template argument is added after its default value is parsed, so that
it is still not in scope in its own default value. Duplicate template
arguments are now diagnosed in ParseDeclaration, which also avoids a
misleading type error when the duplicate has a different type.
The only change in output is that --print-detailed-records prints
"Fields: (none)" for a class with template arguments but no fields.
Parser memory usage decreases by about 1% on AMDGPU.td because
VarDefInit::instantiate no longer copies template args from the class to
the def only to remove them again.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply at anthropic.com>
---
llvm/include/llvm/TableGen/Record.h | 37 +++-
llvm/lib/TableGen/DetailedRecordsBackend.cpp | 23 ++-
llvm/lib/TableGen/JSONBackend.cpp | 10 +-
llvm/lib/TableGen/Record.cpp | 41 ++---
llvm/lib/TableGen/TGParser.cpp | 162 +++++++-----------
llvm/lib/TableGen/TGParser.h | 2 +-
llvm/test/TableGen/detailed-records.td | 8 +
.../TableGen/template-arg-default-scope.td | 44 +++++
llvm/test/TableGen/template-arg-duplicate.td | 24 +++
.../tblgen-lsp-server/TableGenServer.cpp | 5 +-
10 files changed, 207 insertions(+), 149 deletions(-)
create mode 100644 llvm/test/TableGen/template-arg-default-scope.td
create mode 100644 llvm/test/TableGen/template-arg-duplicate.td
diff --git a/llvm/include/llvm/TableGen/Record.h b/llvm/include/llvm/TableGen/Record.h
index d36457e411d2c4..3d1d06551b86b5 100644
--- a/llvm/include/llvm/TableGen/Record.h
+++ b/llvm/include/llvm/TableGen/Record.h
@@ -1704,7 +1704,9 @@ class Record {
SmallVector<SMLoc, 0> ForwardDeclarationLocs;
mutable SmallVector<SMRange, 0> ReferenceLocs;
mutable DenseMap<const Init *, SmallVector<SMRange, 0>> ValueReferenceLocs;
- SmallVector<const Init *, 0> TemplateArgs;
+ // Template arguments, in order.
+ SmallVector<RecordVal, 0> TemplateArgs;
+ // Fields, including the ones inherited from superclasses.
SmallVector<RecordVal, 0> Values;
SmallVector<AssertionInfo, 0> Assertions;
SmallVector<DumpInfo, 0> Dumps;
@@ -1811,8 +1813,10 @@ class Record {
bool isAnonymous() const { return Kind == RK_AnonymousDef; }
- ArrayRef<const Init *> getTemplateArgs() const { return TemplateArgs; }
+ /// Return the template arguments of this class or multiclass.
+ ArrayRef<RecordVal> getTemplateArgs() const { return TemplateArgs; }
+ /// Return the fields of this record.
ArrayRef<RecordVal> getValues() const { return Values; }
ArrayRef<AssertionInfo> getAssertions() const { return Assertions; }
@@ -1843,14 +1847,25 @@ class Record {
return DirectSuperClasses;
}
- bool isTemplateArg(const Init *Name) const {
- return llvm::is_contained(TemplateArgs, Name);
+ /// Return the template argument with the given name, or null if there is
+ /// none.
+ const RecordVal *getTemplateArg(const Init *Name) const {
+ for (const RecordVal &Arg : TemplateArgs)
+ if (Arg.Name == Name)
+ return &Arg;
+ return nullptr;
}
+ RecordVal *getTemplateArg(const Init *Name) {
+ return const_cast<RecordVal *>(
+ static_cast<const Record *>(this)->getTemplateArg(Name));
+ }
+
+ /// Return the field or template argument with the given name.
const RecordVal *getValue(const Init *Name) const {
for (const RecordVal &Val : Values)
if (Val.Name == Name) return &Val;
- return nullptr;
+ return getTemplateArg(Name);
}
const RecordVal *getValue(StringRef Name) const {
@@ -1867,12 +1882,18 @@ class Record {
static_cast<const Record *>(this)->getValue(Name));
}
- void addTemplateArg(const Init *Name) {
- assert(!isTemplateArg(Name) && "Template arg already defined!");
- TemplateArgs.push_back(Name);
+ /// Add a template argument. Return false, without adding it, if there is
+ /// already a template argument with the same name.
+ bool addTemplateArg(const RecordVal &Arg) {
+ assert(Arg.isTemplateArg() && "Not a template argument!");
+ if (getTemplateArg(Arg.getNameInit()))
+ return false;
+ TemplateArgs.push_back(Arg);
+ return true;
}
void addValue(const RecordVal &RV) {
+ assert(!RV.isTemplateArg() && "Use addTemplateArg for template arguments");
assert(getValue(RV.getNameInit()) == nullptr && "Value already added!");
Values.push_back(RV);
}
diff --git a/llvm/lib/TableGen/DetailedRecordsBackend.cpp b/llvm/lib/TableGen/DetailedRecordsBackend.cpp
index fc79df45631fa7..5486db3746830c 100644
--- a/llvm/lib/TableGen/DetailedRecordsBackend.cpp
+++ b/llvm/lib/TableGen/DetailedRecordsBackend.cpp
@@ -130,20 +130,18 @@ void DetailedRecordsEmitter::printDefms(const Record &Rec, raw_ostream &OS) {
// Print the template arguments of a class.
void DetailedRecordsEmitter::printTemplateArgs(const Record &Rec,
raw_ostream &OS) {
- ArrayRef<const Init *> Args = Rec.getTemplateArgs();
+ ArrayRef<RecordVal> Args = Rec.getTemplateArgs();
if (Args.empty()) {
OS << " Template args: (none)\n";
return;
}
OS << " Template args:\n";
- for (const Init *ArgName : Args) {
- const RecordVal *Value = Rec.getValue(ArgName);
- assert(Value && "Template argument value not found.");
+ for (const RecordVal &Arg : Args) {
OS << " ";
- Value->print(OS, false);
+ Arg.print(OS, false);
OS << formatv(" |{0}|\n",
- SrcMgr.getFormattedLocationNoOffset(Value->getLoc()));
+ SrcMgr.getFormattedLocationNoOffset(Arg.getLoc()));
}
}
@@ -176,13 +174,12 @@ void DetailedRecordsEmitter::printFields(const Record &Rec, raw_ostream &OS) {
}
OS << " Fields:\n";
- for (const RecordVal &Value : ValueList)
- if (!Rec.isTemplateArg(Value.getNameInit())) {
- OS << " ";
- Value.print(OS, false);
- OS << formatv(" |{0}|\n",
- SrcMgr.getFormattedLocationNoOffset(Value.getLoc()));
- }
+ for (const RecordVal &Value : ValueList) {
+ OS << " ";
+ Value.print(OS, false);
+ OS << formatv(" |{0}|\n",
+ SrcMgr.getFormattedLocationNoOffset(Value.getLoc()));
+ }
}
// This function is called by TableGen after parsing the files.
diff --git a/llvm/lib/TableGen/JSONBackend.cpp b/llvm/lib/TableGen/JSONBackend.cpp
index d9f93a38963274..1e81218fbe0a1a 100644
--- a/llvm/lib/TableGen/JSONBackend.cpp
+++ b/llvm/lib/TableGen/JSONBackend.cpp
@@ -138,12 +138,10 @@ void JSONEmitter::run(raw_ostream &OS) {
json::Array Fields;
for (const RecordVal &RV : Def->getValues()) {
- if (!Def->isTemplateArg(RV.getNameInit())) {
- auto Name = RV.getNameInitAsString();
- if (RV.isNonconcreteOK())
- Fields.push_back(Name);
- Obj[Name] = translateInit(*RV.getValue());
- }
+ auto Name = RV.getNameInitAsString();
+ if (RV.isNonconcreteOK())
+ Fields.push_back(Name);
+ Obj[Name] = translateInit(*RV.getValue());
}
Obj["!fields"] = std::move(Fields);
diff --git a/llvm/lib/TableGen/Record.cpp b/llvm/lib/TableGen/Record.cpp
index 55360837b30aa6..cb368038732e86 100644
--- a/llvm/lib/TableGen/Record.cpp
+++ b/llvm/lib/TableGen/Record.cpp
@@ -2374,7 +2374,7 @@ const DefInit *VarDefInit::instantiate() {
Records.getNewAnonymousName(), Loc, Records, Record::RK_AnonymousDef);
Record *NewRec = NewRecOwner.get();
- // Copy values from class to instance
+ // Copy fields from class to instance.
for (const RecordVal &Val : Class->getValues()) {
NewRec->addValue(Val);
NewRec->appendValueReferenceLocs(*Class, Val.getNameInit());
@@ -2386,18 +2386,17 @@ const DefInit *VarDefInit::instantiate() {
// Copy dumps from class to instance.
NewRec->appendDumps(Class);
- // Substitute and resolve template arguments
- ArrayRef<const Init *> TArgs = Class->getTemplateArgs();
+ // Substitute and resolve template arguments, starting with their default
+ // values.
+ ArrayRef<RecordVal> TArgs = Class->getTemplateArgs();
MapResolver R(NewRec);
- for (const Init *Arg : TArgs) {
- R.set(Arg, NewRec->getValue(Arg)->getValue());
- NewRec->removeValue(Arg);
- }
+ for (const RecordVal &Arg : TArgs)
+ R.set(Arg.getNameInit(), Arg.getValue());
for (auto *Arg : args()) {
if (Arg->isPositional())
- R.set(TArgs[Arg->getIndex()], Arg->getValue());
+ R.set(TArgs[Arg->getIndex()].getNameInit(), Arg->getValue());
if (Arg->isNamed())
R.set(Arg->getName(), Arg->getValue());
}
@@ -2868,8 +2867,9 @@ void Record::resolveReferences(Resolver &R, const RecordVal *SkipVal) {
setName(NewName);
}
- // Resolve the field values.
- for (RecordVal &Value : Values) {
+ // Resolve the template argument and field values. Template arguments come
+ // first, as they are declared before any fields.
+ for (RecordVal &Value : concat<RecordVal>(TemplateArgs, Values)) {
if (SkipVal == &Value) // Skip resolve the same field as the given one
continue;
if (const Init *V = Value.getValue()) {
@@ -2918,15 +2918,13 @@ LLVM_DUMP_METHOD void Record::dump() const { errs() << *this; }
raw_ostream &llvm::operator<<(raw_ostream &OS, const Record &R) {
OS << R.getNameInitAsString();
- ArrayRef<const Init *> TArgs = R.getTemplateArgs();
+ ArrayRef<RecordVal> TArgs = R.getTemplateArgs();
if (!TArgs.empty()) {
OS << "<";
ListSeparator LS;
- for (const Init *TA : TArgs) {
- const RecordVal *RV = R.getValue(TA);
- assert(RV && "Template argument record not found??");
+ for (const RecordVal &TA : TArgs) {
OS << LS;
- RV->print(OS, false);
+ TA.print(OS, false);
}
OS << ">";
}
@@ -2941,10 +2939,10 @@ raw_ostream &llvm::operator<<(raw_ostream &OS, const Record &R) {
OS << "\n";
for (const RecordVal &Val : R.getValues())
- if (Val.isNonconcreteOK() && !R.isTemplateArg(Val.getNameInit()))
+ if (Val.isNonconcreteOK())
OS << Val;
for (const RecordVal &Val : R.getValues())
- if (!Val.isNonconcreteOK() && !R.isTemplateArg(Val.getNameInit()))
+ if (!Val.isNonconcreteOK())
OS << Val;
return OS << "}\n";
@@ -3145,11 +3143,10 @@ void Record::emitRecordDumps() {
// Report a warning if the record has unused template arguments.
void Record::checkUnusedTemplateArgs() {
- for (const Init *TA : getTemplateArgs()) {
- const RecordVal *Arg = getValue(TA);
- if (!Arg->isUsed())
- PrintWarning(Arg->getLoc(),
- "unused template argument: " + Twine(Arg->getName()));
+ for (const RecordVal &Arg : getTemplateArgs()) {
+ if (!Arg.isUsed())
+ PrintWarning(Arg.getLoc(),
+ "unused template argument: " + Twine(Arg.getName()));
}
}
diff --git a/llvm/lib/TableGen/TGParser.cpp b/llvm/lib/TableGen/TGParser.cpp
index 00e691a87d3938..4dfd7600fd5492 100644
--- a/llvm/lib/TableGen/TGParser.cpp
+++ b/llvm/lib/TableGen/TGParser.cpp
@@ -131,10 +131,6 @@ static const Init *QualifyName(const Record &CurRec, const Init *Name) {
return NewName;
}
-static const Init *QualifyName(MultiClass *MC, const Init *Name) {
- return QualifyName(MC->Rec, Name);
-}
-
/// Return the qualified version of the implicit 'NAME' template argument.
static const Init *QualifiedNameOfImplicitName(const Record &Rec) {
return QualifyName(Rec, StringInit::get(Rec.getRecords(), "NAME"));
@@ -158,9 +154,7 @@ const Init *TGVarScope::getVar(RecordKeeper &Records,
if (!Rec)
return nullptr;
const Init *ArgName = QualifyName(*Rec, Name);
- if (Rec->isTemplateArg(ArgName)) {
- RecordVal *RV = Rec->getValue(ArgName);
- assert(RV && "Template arg doesn't exist??");
+ if (RecordVal *RV = Rec->getTemplateArg(ArgName)) {
RV->setUsed(true);
if (TrackReferenceLocs)
Rec->appendValueReferenceLoc(ArgName, NameLoc);
@@ -353,12 +347,10 @@ bool TGParser::AddSubClass(Record *CurRec, SubClassReference &SubClass) {
const Record *SC = SubClass.Rec;
MapResolver R(CurRec);
- // Loop over all the subclass record's fields. Add regular fields to the new
- // record.
+ // Loop over all the subclass record's fields and add them to the new record.
for (const RecordVal &Field : SC->getValues())
- if (!Field.isTemplateArg())
- if (AddValue(CurRec, SubClass.RefRange.Start, Field, SC))
- return true;
+ if (AddValue(CurRec, SubClass.RefRange.Start, Field, SC))
+ return true;
if (resolveArgumentsOfClass(R, SC, SubClass.TemplateArgs,
SubClass.RefRange.Start))
@@ -635,17 +627,18 @@ bool TGParser::addDefOne(std::unique_ptr<Record> Rec) {
bool TGParser::resolveArguments(const Record *Rec,
ArrayRef<const ArgumentInit *> ArgValues,
SMLoc Loc, ArgValueHandler ArgValueHandler) {
- ArrayRef<const Init *> ArgNames = Rec->getTemplateArgs();
- assert(ArgValues.size() <= ArgNames.size() &&
+ ArrayRef<RecordVal> Args = Rec->getTemplateArgs();
+ assert(ArgValues.size() <= Args.size() &&
"Too many template arguments allowed");
// Loop over the template arguments and handle the (name, value) pair.
- SmallVector<const Init *, 2> UnsolvedArgNames(ArgNames);
+ SmallVector<const Init *, 2> UnsolvedArgNames(
+ map_range(Args, [](const RecordVal &Arg) { return Arg.getNameInit(); }));
for (auto *Arg : ArgValues) {
const Init *ArgName = nullptr;
const Init *ArgValue = Arg->getValue();
if (Arg->isPositional())
- ArgName = ArgNames[Arg->getIndex()];
+ ArgName = Args[Arg->getIndex()].getNameInit();
if (Arg->isNamed())
ArgName = Arg->getName();
@@ -3522,7 +3515,7 @@ bool TGParser::ParseTemplateArgValueList(
SmallVectorImpl<const ArgumentInit *> &Result,
SmallVectorImpl<SMLoc> &ArgLocs, Record *CurRec, const Record *ArgsRec) {
assert(Result.empty() && "Result vector is not empty");
- ArrayRef<const Init *> TArgs = ArgsRec->getTemplateArgs();
+ ArrayRef<RecordVal> TArgs = ArgsRec->getTemplateArgs();
if (consume(tgtok::greater)) // empty value list
return false;
@@ -3538,9 +3531,8 @@ bool TGParser::ParseTemplateArgValueList(
SMLoc ValueLoc = ArgLocs.emplace_back(Lex.getLoc());
// If we are parsing named argument, we don't need to know the argument name
// and argument type will be resolved after we know the name.
- const Init *Value = ParseValue(
- CurRec,
- HasNamedArg ? nullptr : ArgsRec->getValue(TArgs[ArgIndex])->getType());
+ const Init *Value =
+ ParseValue(CurRec, HasNamedArg ? nullptr : TArgs[ArgIndex].getType());
if (!Value)
return true;
@@ -3586,80 +3578,72 @@ bool TGParser::ParseTemplateArgValueList(
}
}
-/// ParseDeclaration - Read a declaration, returning the name of field ID, or an
-/// empty string on error. This can happen in a number of different contexts,
-/// including within a def or in the template args for a class (in which case
-/// CurRec will be non-null) and within the template args for a multiclass (in
-/// which case CurRec will be null, but CurMultiClass will be set). This can
-/// also happen within a def that is within a multiclass, which will set both
-/// CurRec and CurMultiClass.
+/// ParseDeclaration - Read a declaration of a field or template argument and
+/// add it to the record. Return true on error. This can happen in a number of
+/// different contexts, including within a def or in the template args for a
+/// class (in which case CurRec will be non-null) and within the template args
+/// for a multiclass (in which case CurRec will be null, but CurMultiClass will
+/// be set). This can also happen within a def that is within a multiclass,
+/// which will set both CurRec and CurMultiClass.
///
/// Declaration ::= FIELD? Type ID ('=' Value)?
///
-const Init *TGParser::ParseDeclaration(Record *CurRec,
- bool ParsingTemplateArgs) {
+bool TGParser::ParseDeclaration(Record *CurRec, bool ParsingTemplateArgs) {
+ SMLoc DeclLoc = Lex.getLoc();
+
// Read the field prefix if present.
bool HasField = !ParsingTemplateArgs && consume(tgtok::Field);
const RecTy *Type = ParseType();
if (!Type)
- return nullptr;
+ return true;
- if (Lex.getCode() != tgtok::Id) {
- TokError("Expected identifier in declaration");
- return nullptr;
- }
+ if (Lex.getCode() != tgtok::Id)
+ return TokError("Expected identifier in declaration");
std::string Str = Lex.getCurStrVal();
- if (Str == "NAME") {
- TokError("'" + Str + "' is a reserved variable name");
- return nullptr;
- }
+ if (Str == "NAME")
+ return TokError("'" + Str + "' is a reserved variable name");
- if (!ParsingTemplateArgs && CurScope->varAlreadyDefined(Str)) {
- TokError("local variable of this name already exists");
- return nullptr;
- }
+ if (!ParsingTemplateArgs && CurScope->varAlreadyDefined(Str))
+ return TokError("local variable of this name already exists");
SMLoc IdLoc = Lex.getLoc();
const Init *DeclName = StringInit::get(Records, Str);
Lex.Lex();
- bool BadField;
- if (!ParsingTemplateArgs) { // def, possibly in a multiclass
- BadField = AddValue(CurRec, IdLoc,
- RecordVal(DeclName, IdLoc, Type,
- HasField ? RecordVal::FK_NonconcreteOK
- : RecordVal::FK_Normal));
- } else if (CurRec) { // class template argument
- DeclName = QualifyName(*CurRec, DeclName);
- BadField =
- AddValue(CurRec, IdLoc,
- RecordVal(DeclName, IdLoc, Type, RecordVal::FK_TemplateArg));
- } else { // multiclass template argument
- assert(CurMultiClass && "invalid context for template argument");
- DeclName = QualifyName(CurMultiClass, DeclName);
- BadField =
- AddValue(CurRec, IdLoc,
- RecordVal(DeclName, IdLoc, Type, RecordVal::FK_TemplateArg));
- }
- if (BadField)
- return nullptr;
+ Record *Rec = CurRec ? CurRec : &CurMultiClass->Rec;
- // If a value is present, parse it and set new field's value.
+ // A field is added before its value is parsed, but a template argument is
+ // added after its default value is parsed, so that it is not in scope in its
+ // own default value.
+ if (!ParsingTemplateArgs &&
+ AddValue(Rec, IdLoc,
+ RecordVal(DeclName, IdLoc, Type,
+ HasField ? RecordVal::FK_NonconcreteOK
+ : RecordVal::FK_Normal)))
+ return true;
+
+ SMLoc ValLoc;
+ const Init *Val = nullptr;
if (consume(tgtok::equal)) {
- SMLoc ValLoc = Lex.getLoc();
- const Init *Val = ParseValue(CurRec, Type);
- if (!Val || SetValue(CurRec, ValLoc, DeclName, {}, Val,
- /*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;
- }
+ ValLoc = Lex.getLoc();
+ Val = ParseValue(CurRec, Type);
+ }
+
+ if (ParsingTemplateArgs) {
+ DeclName = QualifyName(*Rec, DeclName);
+ if (!Rec->addTemplateArg(
+ RecordVal(DeclName, IdLoc, Type, RecordVal::FK_TemplateArg)))
+ return Error(DeclLoc, "template argument with the same name has already "
+ "been defined");
}
- return DeclName;
+ // Errors in the value do not stop parsing, so that we can continue to make
+ // some progress.
+ if (Val)
+ SetValue(Rec, ValLoc, DeclName, {}, Val, /*OverrideDefLoc=*/false);
+ return false;
}
/// ParseForeachDeclaration - Read a foreach declaration, returning
@@ -3754,28 +3738,12 @@ bool TGParser::ParseTemplateArgList(Record *CurRec) {
assert(Lex.getCode() == tgtok::less && "Not a template arg list!");
Lex.Lex(); // eat the '<'
- Record *TheRecToAddTo = CurRec ? CurRec : &CurMultiClass->Rec;
-
- // Read the first declaration.
- const Init *TemplArg = ParseDeclaration(CurRec, true /*templateargs*/);
- if (!TemplArg)
- return true;
-
- TheRecToAddTo->addTemplateArg(TemplArg);
-
- while (consume(tgtok::comma)) {
- // Read the following declarations.
- SMLoc Loc = Lex.getLoc();
- TemplArg = ParseDeclaration(CurRec, true /*templateargs*/);
- if (!TemplArg)
+ // Read the declarations. ParseDeclaration adds each one to the class or
+ // multiclass.
+ do {
+ if (ParseDeclaration(CurRec, /*ParsingTemplateArgs=*/true))
return true;
-
- if (TheRecToAddTo->isTemplateArg(TemplArg))
- return Error(Loc, "template argument with the same name has already been "
- "defined");
-
- TheRecToAddTo->addTemplateArg(TemplArg);
- }
+ } while (consume(tgtok::comma));
if (!consume(tgtok::greater))
return TokError("expected '>' at end of template argument list");
@@ -3838,7 +3806,7 @@ bool TGParser::ParseBodyItem(Record *CurRec) {
return ParseDump(nullptr, CurRec);
if (Lex.getCode() != tgtok::Let) {
- if (!ParseDeclaration(CurRec, false))
+ if (ParseDeclaration(CurRec, /*ParsingTemplateArgs=*/false))
return true;
if (!consume(tgtok::semi))
@@ -4793,13 +4761,13 @@ bool TGParser::CheckTemplateArgValues(
assert(Values.size() == ValuesLocs.size() &&
"expected as many values as locations");
- ArrayRef<const Init *> TArgs = ArgsRec->getTemplateArgs();
+ ArrayRef<RecordVal> TArgs = ArgsRec->getTemplateArgs();
bool HasError = false;
for (auto [Value, Loc] : llvm::zip_equal(Values, ValuesLocs)) {
const Init *ArgName = nullptr;
if (Value->isPositional())
- ArgName = TArgs[Value->getIndex()];
+ ArgName = TArgs[Value->getIndex()].getNameInit();
if (Value->isNamed())
ArgName = Value->getName();
diff --git a/llvm/lib/TableGen/TGParser.h b/llvm/lib/TableGen/TGParser.h
index 862a38de5453ba..458d765c96cdad 100644
--- a/llvm/lib/TableGen/TGParser.h
+++ b/llvm/lib/TableGen/TGParser.h
@@ -296,7 +296,7 @@ class TGParser {
bool ParseBodyItem(Record *CurRec);
bool ParseTemplateArgList(Record *CurRec);
- const Init *ParseDeclaration(Record *CurRec, bool ParsingTemplateArgs);
+ bool ParseDeclaration(Record *CurRec, bool ParsingTemplateArgs);
const VarInit *ParseForeachDeclaration(const Init *&ForeachListValue);
SubClassReference ParseSubClassReference(Record *CurRec, bool isDefm);
diff --git a/llvm/test/TableGen/detailed-records.td b/llvm/test/TableGen/detailed-records.td
index 15b5f268f2c495..f4998388fa2ae2 100644
--- a/llvm/test/TableGen/detailed-records.td
+++ b/llvm/test/TableGen/detailed-records.td
@@ -10,6 +10,11 @@
// CHECK: ----- Classes
// CHECK: Class1
// CHECK: Class2
+// CHECK: Class3
+// CHECK-NEXT: Template args:
+// CHECK-NEXT: int Class3:Arg = ?
+// CHECK-NEXT: Superclasses: (none)
+// CHECK-NEXT: Fields: (none)
// CHECK: ----- Records
// CHECK: ""
@@ -26,6 +31,9 @@ class Class1 {
class Class2 {
}
+class Class3<int Arg> {
+}
+
def : Class1;
def "" : Class2;
diff --git a/llvm/test/TableGen/template-arg-default-scope.td b/llvm/test/TableGen/template-arg-default-scope.td
new file mode 100644
index 00000000000000..5e5633ebdc47a5
--- /dev/null
+++ b/llvm/test/TableGen/template-arg-default-scope.td
@@ -0,0 +1,44 @@
+// RUN: llvm-tblgen %s | FileCheck %s
+
+// Check that a template argument is not in scope in its own default value, so
+// the default value can refer to a def with the same name, and that earlier
+// template arguments are in scope in later default values.
+
+class Op;
+def store : Op;
+
+class StoreU<Op store = store> {
+ Op Store = store;
+}
+
+multiclass StoreM<Op store = store> {
+ def NAME {
+ Op Store = store;
+ }
+}
+
+class Two<int a, int b = !add(a, 1)> {
+ int B = b;
+}
+
+multiclass TwoM<int a, int b = !add(a, 1)> {
+ def NAME {
+ int B = b;
+ }
+}
+
+// CHECK-LABEL: def FromClass {
+// CHECK-NEXT: Op Store = store;
+def FromClass : StoreU;
+
+// CHECK-LABEL: def FromMultiClass {
+// CHECK-NEXT: Op Store = store;
+defm FromMultiClass : StoreM;
+
+// CHECK-LABEL: def TwoFromClass {
+// CHECK-NEXT: int B = 6;
+def TwoFromClass : Two<5>;
+
+// CHECK-LABEL: def TwoFromMultiClass {
+// CHECK-NEXT: int B = 8;
+defm TwoFromMultiClass : TwoM<7>;
diff --git a/llvm/test/TableGen/template-arg-duplicate.td b/llvm/test/TableGen/template-arg-duplicate.td
new file mode 100644
index 00000000000000..ca73ef00ca0506
--- /dev/null
+++ b/llvm/test/TableGen/template-arg-duplicate.td
@@ -0,0 +1,24 @@
+// RUN: not llvm-tblgen -DCLASS %s 2>&1 | FileCheck %s --check-prefix=CLASS
+// RUN: not llvm-tblgen -DMULTICLASS %s 2>&1 | FileCheck %s --check-prefix=MULTICLASS
+// RUN: not llvm-tblgen -DTYPE %s 2>&1 | FileCheck %s --check-prefix=TYPE --implicit-check-not=error:
+
+// Check that a duplicate template argument name is diagnosed at the start of
+// the second declaration.
+
+#ifdef CLASS
+// CLASS: [[@LINE+1]]:16: error: template argument with the same name has already been defined
+class C<int a, int a>;
+#endif
+
+#ifdef MULTICLASS
+// MULTICLASS: [[@LINE+1]]:21: error: template argument with the same name has already been defined
+multiclass M<int a, int a> {
+ def NAME;
+}
+#endif
+
+// A duplicate with a different type and a default value only gives one error.
+#ifdef TYPE
+// TYPE: [[@LINE+1]]:16: error: template argument with the same name has already been defined
+class D<int a, string a = "x">;
+#endif
diff --git a/mlir/lib/Tools/tblgen-lsp-server/TableGenServer.cpp b/mlir/lib/Tools/tblgen-lsp-server/TableGenServer.cpp
index 2dae2448fda306..9d6342974515b9 100644
--- a/mlir/lib/Tools/tblgen-lsp-server/TableGenServer.cpp
+++ b/mlir/lib/Tools/tblgen-lsp-server/TableGenServer.cpp
@@ -294,8 +294,9 @@ void TableGenIndex::initialize(const RecordKeeper &records) {
for (SMRange loc : def.getReferenceLocs())
insertRef(sym, loc);
- // Add definitions for any values.
- for (const RecordVal &value : def.getValues()) {
+ // Add definitions for any template arguments and values.
+ for (const RecordVal &value : llvm::concat<const RecordVal>(
+ def.getTemplateArgs(), def.getValues())) {
auto *sym = getOrInsertDef(&def, &value);
insertRef(sym, sym->defLoc, /*isDef=*/true);
for (SMRange refLoc : def.getValueReferenceLocs(value.getNameInit()))
>From 8c1e5fdb49829fa00719dc2127ef8e841f67c650 Mon Sep 17 00:00:00 2001
From: Jay Foad <jay.foad at amd.com>
Date: Fri, 23 Oct 2026 16:43:53 +0100
Subject: [PATCH 2/2] [TableGen] Look up template arguments with getTemplateArg
Now that template arguments are stored separately from fields, use
getTemplateArg() or the template argument list directly, instead of
getValue(), which searches all fields before the template arguments.
In resolveArguments, keep track of the unresolved template arguments
themselves rather than their names, so that their default values and
locations do not need to be looked up again. NFC.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply at anthropic.com>
---
llvm/lib/TableGen/TGParser.cpp | 34 ++++++++++++++++------------------
1 file changed, 16 insertions(+), 18 deletions(-)
diff --git a/llvm/lib/TableGen/TGParser.cpp b/llvm/lib/TableGen/TGParser.cpp
index 4dfd7600fd5492..b23428bf56e737 100644
--- a/llvm/lib/TableGen/TGParser.cpp
+++ b/llvm/lib/TableGen/TGParser.cpp
@@ -632,8 +632,7 @@ bool TGParser::resolveArguments(const Record *Rec,
"Too many template arguments allowed");
// Loop over the template arguments and handle the (name, value) pair.
- SmallVector<const Init *, 2> UnsolvedArgNames(
- map_range(Args, [](const RecordVal &Arg) { return Arg.getNameInit(); }));
+ SmallVector<const RecordVal *, 2> UnsolvedArgs(make_pointer_range(Args));
for (auto *Arg : ArgValues) {
const Init *ArgName = nullptr;
const Init *ArgValue = Arg->getValue();
@@ -643,25 +642,28 @@ bool TGParser::resolveArguments(const Record *Rec,
ArgName = Arg->getName();
// We can only specify the template argument once.
- if (!is_contained(UnsolvedArgNames, ArgName))
+ auto It = find_if(UnsolvedArgs, [ArgName](const RecordVal *UnsolvedArg) {
+ return UnsolvedArg->getNameInit() == ArgName;
+ });
+ if (It == UnsolvedArgs.end())
return Error(Loc, "We can only specify the template argument '" +
ArgName->getAsUnquotedString() + "' once");
ArgValueHandler(ArgName, ArgValue);
- llvm::erase(UnsolvedArgNames, ArgName);
+ UnsolvedArgs.erase(It);
}
// For unsolved arguments, if there is no default value, complain.
- for (auto *UnsolvedArgName : UnsolvedArgNames) {
- const Init *Default = Rec->getValue(UnsolvedArgName)->getValue();
+ for (const RecordVal *UnsolvedArg : UnsolvedArgs) {
+ const Init *Default = UnsolvedArg->getValue();
if (!Default->isComplete()) {
- std::string Name = UnsolvedArgName->getAsUnquotedString();
- Error(Loc, "value not specified for template argument '" + Name + "'");
- PrintNote(Rec->getFieldLoc(Name),
+ Error(Loc, "value not specified for template argument '" +
+ UnsolvedArg->getName() + "'");
+ PrintNote(UnsolvedArg->getLoc(),
"declared in '" + Rec->getNameInitAsString() + "'");
return true;
}
- ArgValueHandler(UnsolvedArgName, Default);
+ ArgValueHandler(UnsolvedArg->getNameInit(), Default);
}
return false;
@@ -3544,7 +3546,7 @@ bool TGParser::ParseTemplateArgValueList(
auto *Name = cast<StringInit>(Value);
const Init *QualifiedName = QualifyName(*ArgsRec, Name);
- auto *NamedArg = ArgsRec->getValue(QualifiedName);
+ const RecordVal *NamedArg = ArgsRec->getTemplateArg(QualifiedName);
if (!NamedArg)
return Error(ValueLoc,
"Argument " + Name->getAsString() + " doesn't exist");
@@ -4765,13 +4767,9 @@ bool TGParser::CheckTemplateArgValues(
bool HasError = false;
for (auto [Value, Loc] : llvm::zip_equal(Values, ValuesLocs)) {
- const Init *ArgName = nullptr;
- if (Value->isPositional())
- ArgName = TArgs[Value->getIndex()].getNameInit();
- if (Value->isNamed())
- ArgName = Value->getName();
-
- const RecordVal *Arg = ArgsRec->getValue(ArgName);
+ const RecordVal *Arg = Value->isPositional()
+ ? &TArgs[Value->getIndex()]
+ : ArgsRec->getTemplateArg(Value->getName());
const RecTy *ArgType = Arg->getType();
if (const auto *ArgValue = dyn_cast<TypedInit>(Value->getValue())) {
More information about the Mlir-commits
mailing list