diff --git a/llvm/docs/TableGen/ProgRef.rst b/llvm/docs/TableGen/ProgRef.rst index 2e66778b42ae..7c041ec530d0 100644 --- a/llvm/docs/TableGen/ProgRef.rst +++ b/llvm/docs/TableGen/ProgRef.rst @@ -686,9 +686,14 @@ arguments. .. productionlist:: Body: ";" | "{" `BodyItem`* "}" BodyItem: `Type` `TokIdentifier` ["=" `Value`] ";" - :| "let" `TokIdentifier` ["{" `RangeList` "}"] "=" `Value` ";" + :| "let" [`LetMode`] `TokIdentifier` ["{" `RangeList` "}"] "=" `Value` ";" :| "defvar" `TokIdentifier` "=" `Value` ";" :| `Assert` + LetMode: "append" | "prepend" + +Note that ``append`` and ``prepend`` are context-sensitive keywords: they are +only recognized as modifiers immediately after ``let``. In all other positions, +they remain valid identifiers (e.g., usable as field names). A field definition in the body specifies a field to be included in the class or record. If no initial value is specified, then the field's value is @@ -700,6 +705,34 @@ for fields defined directly in the body or fields inherited from parent classes. A :token:`RangeList` can be specified to reset certain bits in a ``bit`` field. +The ``let append`` and ``let prepend`` forms concatenate a value with the +field's current value instead of replacing it. For ``append``, the new value +is added after the current value; for ``prepend``, it is added before. The +supported types and concatenation operators are: + +* ``list``: uses ``!listconcat`` +* ``string`` / ``code``: uses ``!strconcat`` +* ``dag``: uses ``!con`` + +If the field is currently unset (``?``), ``let append`` and ``let prepend`` +simply set the value directly. This is useful for accumulating values across +a class hierarchy: + +.. code-block:: text + + class Base { + list items = [2, 3]; + } + class Middle : Base { + let append items = [4]; // items = [2, 3, 4] + } + def Concrete : Middle { + let prepend items = [1]; // items = [1, 2, 3, 4] + } + +A plain ``let`` (without ``append``/``prepend``) always replaces the current +value, which can be used to opt out of accumulated values. + The ``defvar`` form defines a variable whose value can be used in other value expressions within the body. The variable is not a field: it does not become a field of the class or record being defined. Variables are provided @@ -890,7 +923,7 @@ statements within the scope of the ``let``. Let: "let" `LetList` "in" "{" `Statement`* "}" :| "let" `LetList` "in" `Statement` LetList: `LetItem` ("," `LetItem`)* - LetItem: `TokIdentifier` ["<" `RangeList` ">"] "=" `Value` + LetItem: [`LetMode`] `TokIdentifier` ["<" `RangeList` ">"] "=" `Value` The ``let`` statement establishes a scope, which is a sequence of statements in braces or a single statement with no braces. The bindings in the @@ -927,6 +960,16 @@ statements can be nested. Note that a top-level ``let`` will not override fields defined in the classes or records themselves. +Top-level ``let`` also supports ``append`` and ``prepend`` modes, which +concatenate the value with the field's current value instead of replacing it. +See the :token:`BodyItem` production for the supported types and semantics. + +.. code-block:: text + + let append traits = [NewTrait] in { + def MyRecord : Base; + } + ``multiclass`` --- define multiple records ------------------------------------------ diff --git a/llvm/lib/TableGen/TGParser.cpp b/llvm/lib/TableGen/TGParser.cpp index 3d31d8e2717b..74a9c789c985 100644 --- a/llvm/lib/TableGen/TGParser.cpp +++ b/llvm/lib/TableGen/TGParser.cpp @@ -14,6 +14,7 @@ #include "TGLexer.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/Twine.h" #include "llvm/Config/llvm-config.h" #include "llvm/Support/Casting.h" @@ -238,7 +239,8 @@ bool TGParser::AddValue(Record *CurRec, SMLoc Loc, const RecordVal &RV) { /// Return true on error, false on success. bool TGParser::SetValue(Record *CurRec, SMLoc Loc, const Init *ValName, ArrayRef BitList, const Init *V, - bool AllowSelfAssignment, bool OverrideDefLoc) { + bool AllowSelfAssignment, bool OverrideDefLoc, + LetMode Mode) { if (!V) return false; @@ -250,6 +252,41 @@ bool TGParser::SetValue(Record *CurRec, SMLoc Loc, const Init *ValName, return Error(Loc, "Value '" + ValName->getAsUnquotedString() + "' unknown!"); + // Handle append/prepend by concatenating with the current value. + if (Mode != LetMode::Replace) { + assert(Mode == LetMode::Append || Mode == LetMode::Prepend); + + if (!BitList.empty()) + return Error(Loc, "Cannot use append/prepend with bit range"); + + const Init *CurrentValue = RV->getValue(); + const RecTy *FieldType = RV->getType(); + + // If the current value is unset, just assign the new value directly. + if (!isa(CurrentValue)) { + const bool IsAppendMode = Mode == LetMode::Append; + + const Init *LHS = IsAppendMode ? CurrentValue : V; + const Init *RHS = IsAppendMode ? V : CurrentValue; + + BinOpInit::BinaryOp ConcatOp; + if (isa(FieldType)) + ConcatOp = BinOpInit::LISTCONCAT; + else if (isa(FieldType)) + ConcatOp = BinOpInit::STRCONCAT; + else if (isa(FieldType)) + ConcatOp = BinOpInit::CONCAT; + else + return Error(Loc, Twine("Cannot ") + + (IsAppendMode ? "append to" : "prepend to") + + " field '" + ValName->getAsUnquotedString() + + "' of type '" + FieldType->getAsString() + + "' (expected list, string, code, or dag)"); + + V = BinOpInit::get(ConcatOp, LHS, RHS, FieldType)->Fold(CurRec); + } + } + // Do not allow assignments like 'X = X'. This will just cause infinite loops // in the resolution machinery. if (BitList.empty()) @@ -3610,10 +3647,47 @@ bool TGParser::ParseTemplateArgList(Record *CurRec) { return false; } +/// Parse an optional 'append'/'prepend' mode followed by a field name. +/// +/// The current token must be an identifier. If the identifier is 'append' or +/// 'prepend' and is followed by another identifier, it is interpreted as a +/// mode keyword and the following identifier is parsed as the field name. +/// Otherwise the identifier itself is treated as the field name. +/// +/// These keywords are contextual: a field may still be named 'append' or +/// 'prepend' (e.g. `let append = ...`). In that case the keyword is not +/// interpreted as a mode and the identifier is parsed as the field name. +LetModeAndName TGParser::ParseLetModeAndName() { + assert(Lex.getCode() == tgtok::Id && "expected identifier"); + + SMLoc Loc = Lex.getLoc(); + // Copy the identifier before Lex.Lex() invalidates the lexer buffer. + std::string CurStr = Lex.getCurStrVal(); + + LetMode Mode = llvm::StringSwitch(CurStr) + .Case("append", LetMode::Append) + .Case("prepend", LetMode::Prepend) + .Default(LetMode::Replace); + + // Consume the current identifier. + Lex.Lex(); + + if (Mode != LetMode::Replace && Lex.getCode() == tgtok::Id) { + // 'append'/'prepend' used as a contextual keyword. + LetModeAndName Result = {Mode, Lex.getLoc(), Lex.getCurStrVal()}; + Lex.Lex(); // Consume the field name. + return Result; + } + + // Otherwise the identifier itself is the field name (including the case + // where the field is literally named 'append' or 'prepend'). + return {LetMode::Replace, Loc, std::move(CurStr)}; +} + /// ParseBodyItem - Parse a single item within the body of a def or class. /// /// BodyItem ::= Declaration ';' -/// BodyItem ::= LET ID OptionalBitList '=' Value ';' +/// BodyItem ::= LET [append|prepend] ID OptionalBitList '=' Value ';' /// BodyItem ::= Defvar /// BodyItem ::= Dump /// BodyItem ::= Assert @@ -3637,13 +3711,14 @@ bool TGParser::ParseBodyItem(Record *CurRec) { return false; } - // LET ID OptionalRangeList '=' Value ';' - if (Lex.Lex() != tgtok::Id) + // LET [append|prepend] ID OptionalBitList '=' Value ';' + Lex.Lex(); // eat 'let'. + + if (Lex.getCode() != tgtok::Id) return TokError("expected field identifier after let"); - SMLoc IdLoc = Lex.getLoc(); - const StringInit *FieldName = StringInit::get(Records, Lex.getCurStrVal()); - Lex.Lex(); // eat the field name. + auto [Mode, IdLoc, FieldNameStr] = ParseLetModeAndName(); + const StringInit *FieldName = StringInit::get(Records, FieldNameStr); SmallVector BitList; if (ParseOptionalBitList(BitList)) @@ -3671,7 +3746,8 @@ bool TGParser::ParseBodyItem(Record *CurRec) { if (!consume(tgtok::semi)) return TokError("expected ';' after let expression"); - return SetValue(CurRec, IdLoc, FieldName, BitList, Val); + return SetValue(CurRec, IdLoc, FieldName, BitList, Val, + /*AllowSelfAssignment=*/false, /*OverrideDefLoc=*/true, Mode); } /// ParseBody - Read the body of a class or def. Return true on error, false on @@ -3711,7 +3787,9 @@ bool TGParser::ParseBody(Record *CurRec) { bool TGParser::ApplyLetStack(Record *CurRec) { for (SmallVectorImpl &LetInfo : LetStack) for (LetRecord &LR : LetInfo) - if (SetValue(CurRec, LR.Loc, LR.Name, LR.Bits, LR.Value)) + if (SetValue(CurRec, LR.Loc, LR.Name, LR.Bits, LR.Value, + /*AllowSelfAssignment=*/false, /*OverrideDefLoc=*/true, + LR.Mode)) return true; return false; } @@ -4187,7 +4265,7 @@ bool TGParser::ParseClass() { /// of LetRecords. /// /// LetList ::= LetItem (',' LetItem)* -/// LetItem ::= ID OptionalRangeList '=' Value +/// LetItem ::= [append|prepend] ID OptionalRangeList '=' Value /// void TGParser::ParseLetList(SmallVectorImpl &Result) { do { @@ -4197,9 +4275,8 @@ void TGParser::ParseLetList(SmallVectorImpl &Result) { return; } - const StringInit *Name = StringInit::get(Records, Lex.getCurStrVal()); - SMLoc NameLoc = Lex.getLoc(); - Lex.Lex(); // Eat the identifier. + auto [Mode, NameLoc, NameStr] = ParseLetModeAndName(); + const StringInit *Name = StringInit::get(Records, NameStr); // Check for an optional RangeList. SmallVector Bits; @@ -4222,7 +4299,7 @@ void TGParser::ParseLetList(SmallVectorImpl &Result) { } // Now that we have everything, add the record. - Result.emplace_back(Name, Bits, Val, NameLoc); + Result.emplace_back(Name, Bits, Val, NameLoc, Mode); } while (consume(tgtok::comma)); } diff --git a/llvm/lib/TableGen/TGParser.h b/llvm/lib/TableGen/TGParser.h index 09b7d5380695..9f0b89f080c9 100644 --- a/llvm/lib/TableGen/TGParser.h +++ b/llvm/lib/TableGen/TGParser.h @@ -26,13 +26,29 @@ struct MultiClass; struct SubClassReference; struct SubMultiClassReference; +/// Specifies how a 'let' assignment interacts with the existing field value. +/// - Replace: overwrite the field (default behavior). +/// - Append: concatenate the new value after the existing value. +/// - Prepend: concatenate the new value before the existing value. +enum class LetMode { Replace, Append, Prepend }; + +/// Parsed let mode keyword and field name (e.g. `let append x` yields +/// Mode=Append, Name="x"; plain `let x` yields Mode=Replace, Name="x"). +struct LetModeAndName { + LetMode Mode; + SMLoc Loc; // Source location of the field name. + std::string Name; // The field name being assigned. +}; + struct LetRecord { const StringInit *Name; std::vector Bits; const Init *Value; SMLoc Loc; - LetRecord(const StringInit *N, ArrayRef B, const Init *V, SMLoc L) - : Name(N), Bits(B), Value(V), Loc(L) {} + LetMode Mode; + LetRecord(const StringInit *N, ArrayRef B, const Init *V, SMLoc L, + LetMode M = LetMode::Replace) + : Name(N), Bits(B), Value(V), Loc(L), Mode(M) {} }; /// RecordsEntry - Holds exactly one of a Record, ForeachLoop, or @@ -223,10 +239,11 @@ 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. + /// RecordVal. An optional `Mode` specifies append/prepend concatenation. bool SetValue(Record *TheRec, SMLoc Loc, const Init *ValName, ArrayRef BitList, const Init *V, - bool AllowSelfAssignment = false, bool OverrideDefLoc = true); + bool AllowSelfAssignment = false, bool OverrideDefLoc = true, + LetMode Mode = LetMode::Replace); bool AddSubClass(Record *Rec, SubClassReference &SubClass); bool AddSubClass(RecordsEntry &Entry, SubClassReference &SubClass); bool AddSubMultiClass(MultiClass *CurMC, @@ -270,6 +287,7 @@ private: // Parser methods. bool ParseIfBody(MultiClass *CurMultiClass, StringRef Kind); bool ParseAssert(MultiClass *CurMultiClass, Record *CurRec = nullptr); bool ParseTopLevelLet(MultiClass *CurMultiClass); + LetModeAndName ParseLetModeAndName(); void ParseLetList(SmallVectorImpl &Result); bool ParseObjectBody(Record *CurRec); diff --git a/llvm/test/TableGen/let-append-bitrange-error.td b/llvm/test/TableGen/let-append-bitrange-error.td new file mode 100644 index 000000000000..b0b912d7b34b --- /dev/null +++ b/llvm/test/TableGen/let-append-bitrange-error.td @@ -0,0 +1,12 @@ +// RUN: not llvm-tblgen %s 2>&1 | FileCheck %s + +// Test that 'let append' with bit range produces an error. + +class Base { + bits<8> flags = 0; +} + +// CHECK: error: Cannot use append/prepend with bit range +def Bad : Base { + let append flags{0-3} = 0; +} diff --git a/llvm/test/TableGen/let-append-error.td b/llvm/test/TableGen/let-append-error.td new file mode 100644 index 000000000000..904b5abbdca4 --- /dev/null +++ b/llvm/test/TableGen/let-append-error.td @@ -0,0 +1,12 @@ +// RUN: not llvm-tblgen %s 2>&1 | FileCheck %s + +// Test that 'let append' on unsupported types produces an error. + +class Base { + int count = 0; +} + +// CHECK: error: Cannot append to field 'count' of type 'int' (expected list, string, code, or dag) +def Bad : Base { + let append count = 1; +} diff --git a/llvm/test/TableGen/let-append-toplevel.td b/llvm/test/TableGen/let-append-toplevel.td new file mode 100644 index 000000000000..4ecda52296aa --- /dev/null +++ b/llvm/test/TableGen/let-append-toplevel.td @@ -0,0 +1,63 @@ +// RUN: llvm-tblgen %s | FileCheck %s + +// Test 'let append' and 'let prepend' syntax in top-level let. + +class Base { + list items = [1, 2]; + string text = "hello"; +} + +// Test that 'append' and 'prepend' can be used as field names in top-level let. +class HasAppendField { + list append = [1, 2]; + list prepend = [3, 4]; + int other = 0; +} + +// Test top-level let with multiple items: no mode, append, and prepend. +// CHECK: def MultiItemTopLevel +// CHECK: list items = [0, 1, 2, 100]; +// CHECK: string text = "prefix hello suffix"; +let prepend items = [0], append items = [100], + prepend text = "prefix ", append text = " suffix" in { + def MultiItemTopLevel : Base; +} + +// Test nested top-level let append. +let append items = [100] in { + let append items = [200] in { + // CHECK: def NestedTopLevel + // CHECK: list items = [1, 2, 100, 200]; + def NestedTopLevel : Base; + } +} + +// Test top-level let with append. +let append items = [100] in { + // CHECK: def TopLevelAppend + // CHECK: list items = [1, 2, 100]; + def TopLevelAppend : Base; +} + +// Test top-level 'let append = ...' where 'append' is the field name. +let append = [10, 20] in { + // CHECK: def TopLevelFieldNamedAppend + // CHECK: list append = [10, 20]; + // CHECK: list prepend = [3, 4]; + def TopLevelFieldNamedAppend : HasAppendField; +} + +// Test top-level 'let prepend = ...' where 'prepend' is the field name. +let prepend = [30, 40] in { + // CHECK: def TopLevelFieldNamedPrepend + // CHECK: list append = [1, 2]; + // CHECK: list prepend = [30, 40]; + def TopLevelFieldNamedPrepend : HasAppendField; +} + +// Test top-level let with prepend. +let prepend items = [0] in { + // CHECK: def TopLevelPrepend + // CHECK: list items = [0, 1, 2]; + def TopLevelPrepend : Base; +} diff --git a/llvm/test/TableGen/let-append.td b/llvm/test/TableGen/let-append.td new file mode 100644 index 000000000000..1b811a7e91da --- /dev/null +++ b/llvm/test/TableGen/let-append.td @@ -0,0 +1,224 @@ +// RUN: llvm-tblgen %s | FileCheck %s + +// Test 'let append' and 'let prepend' syntax in body items. + +def op; + +class Base { + list items = [1, 2]; + string text = "hello"; + dag d = (op); +} + +class WithCode { + code body = [{ int x = 0; }]; +} + +class WithUnset { + list vals = ?; + string msg = ?; +} + +// Multi-level inheritance accumulation. +class Middle : Base { + let append items = [3]; + let append text = " world"; +} + +// Multiple inheritance classes. +class BaseA { + list items = [1, 2]; + string text = "a"; +} + +class BaseB { + list items = [3, 4]; + string text = "b"; +} + +// Diamond inheritance classes. +class DiamondBase { + list items = [1]; +} + +class Left : DiamondBase { + let append items = [2]; // items = [1, 2] +} + +class Right : DiamondBase { + let append items = [3]; // items = [1, 3] +} + +// Template argument class. +class Parameterized init> { + list items = init; +} + +// Multiclass with append/prepend in body. +multiclass MC extra> { + def _a : Base { + let append items = extra; + } + def _b : Base { + let prepend items = extra; + } +} + +// Test that 'append' and 'prepend' can be used as field names +// (contextual keywords, not reserved). +class HasAppendField { + list append = [1, 2]; + list prepend = [3, 4]; + int other = 0; +} + +// Test that scalar fields named 'append'/'prepend' work with plain let. +class HasScalarAppendField { + int append = 0; + int prepend = 0; +} + +// --- Definitions (CHECK lines in alphabetical order of def names) --- + +// CHECK: def AppendCode +// CHECK: code body = [{ int x = 0; int y = 1; }] +def AppendCode : WithCode { + let append body = [{ int y = 1; }]; +} + +// CHECK: def AppendDag +// CHECK: dag d = (op 3:$a); +def AppendDag : Base { + let append d = (op 3:$a); +} + +// CHECK: def AppendList +// CHECK: list items = [1, 2, 3, 4]; +def AppendList : Base { + let append items = [3, 4]; +} + +// CHECK: def AppendString +// CHECK: string text = "hello world"; +def AppendString : Base { + let append text = " world"; +} + +// CHECK: def AppendUnset +// CHECK: list vals = [1]; +// CHECK: string msg = "hi"; +def AppendUnset : WithUnset { + let append vals = [1]; + let append msg = "hi"; +} + +// Test 'let append = ...' on a scalar (int) field named 'append'. +// CHECK: def AssignScalarAppend +// CHECK: int append = 42; +// CHECK: int prepend = 99; +def AssignScalarAppend : HasScalarAppendField { + let append = 42; + let prepend = 99; +} + +// Test sequential append + prepend on the same field. +// CHECK: def Both +// CHECK: list items = [0, 1, 2, 3]; +def Both : Base { + let append items = [3]; + let prepend items = [0]; +} + +// Test 'let append append' where the second 'append' is the field name. +// CHECK: def ContextualKeyword +// CHECK: list append = [1, 2, 5]; +// CHECK: list prepend = [0, 3, 4]; +// CHECK: int other = 0; +def ContextualKeyword : HasAppendField { + let append append = [5]; + let prepend prepend = [0]; +} + +// Test diamond inheritance: Right is the last parent, so only Right's +// accumulated value survives. Left's append is lost. +// CHECK: def Diamond +// CHECK: list items = [1, 3, 4]; +def Diamond : Left, Right { + let append items = [4]; +} + +// Test 'let append = ...' where 'append' is the field name (no mode keyword). +// CHECK: def FieldNamedAppend +// CHECK: list append = [10, 20]; +// CHECK: list prepend = [30, 40]; +// CHECK: int other = 5; +def FieldNamedAppend : HasAppendField { + let append = [10, 20]; + let prepend = [30, 40]; + let other = 5; +} + +// Test let append on a field set by a template argument. +// CHECK: def FromTemplateArg +// CHECK: list items = [1, 2, 3]; +def FromTemplateArg : Parameterized<[1, 2]> { + let append items = [3]; +} + +// Test let append in multiclass body with defm. +// CHECK: def MCTest_a +// CHECK: list items = [1, 2, 10, 20]; +// CHECK: def MCTest_b +// CHECK: list items = [10, 20, 1, 2]; +defm MCTest : MC<[10, 20]>; + +// Test multiple inheritance: last parent class wins, then append applies. +// CHECK: def MultiInherit +// CHECK: list items = [3, 4, 5]; +// CHECK: string text = "b!"; +def MultiInherit : BaseA, BaseB { + let append items = [5]; + let append text = "!"; +} + +// CHECK: def MultiLevel +// CHECK: list items = [1, 2, 3, 4]; +// CHECK: string text = "hello world!"; +def MultiLevel : Middle { + let append items = [4]; + let append text = "!"; +} + +// CHECK: def OverrideAfterAppend +// CHECK: list items = [10]; +def OverrideAfterAppend : Base { + let append items = [3]; + let items = [10]; +} + +// CHECK: def PrependDag +// CHECK: dag d = (op 0:$z); +def PrependDag : Base { + let prepend d = (op 0:$z); +} + +// CHECK: def PrependList +// CHECK: list items = [0, 1, 2]; +def PrependList : Base { + let prepend items = [0]; +} + +// CHECK: def PrependString +// CHECK: string text = "say hello"; +def PrependString : Base { + let prepend text = "say "; +} + +// Test prepend on unset fields. +// CHECK: def PrependUnset +// CHECK: list vals = [1]; +// CHECK: string msg = "hi"; +def PrependUnset : WithUnset { + let prepend vals = [1]; + let prepend msg = "hi"; +} diff --git a/llvm/test/TableGen/let-prepend-error.td b/llvm/test/TableGen/let-prepend-error.td new file mode 100644 index 000000000000..3a0e96eb95c0 --- /dev/null +++ b/llvm/test/TableGen/let-prepend-error.td @@ -0,0 +1,12 @@ +// RUN: not llvm-tblgen %s 2>&1 | FileCheck %s + +// Test that 'let prepend' on unsupported types produces an error. + +class Base { + int count = 0; +} + +// CHECK: error: Cannot prepend to field 'count' of type 'int' (expected list, string, code, or dag) +def Bad : Base { + let prepend count = 1; +}