[TableGen] Add let append/prepend syntax for field concatenation (#182382)
## Motivation
LLVM TableGen currently lacks a way to **accumulate** field values
across class hierarchies. When a derived class sets a field via `let`,
it completely replaces the parent's value. This forces users into
verbose workarounds like:
```tablegen
class Op { // This is generic MLIR Base
code extraClassDeclaration = ?;
}
// Some Generic shared base
class MyShared1OpClass : Op {
code shared1ExtraClassDeclaration = [{ some generic code 1 }];
}
class MyShared2OpClass : MyShared1OpClass {
code shared2ExtraClassDeclaration = [{ some generic code 2 }];
}
def MyOp : MyShared2OpClass {
// need to manually concatenate shared code
let extraClassDeclaration =
shared1ExtraClassDeclaration
# shared2ExtraClassDeclaration
# [{ additional specialized code }];
}
```
Instead I propose a more natural incremental solution without
unnecessery intermediate definitions:
```
class Op {
code extraClassDeclaration = ?;
}
class MyShared1OpClass : Op {
let append extraClassDeclaration = [{ some generic code 1 }];
}
class MyShared2OpClass : MyShared1OpClass {
let append extraClassDeclaration = [{ some generic code 2 }];
}
def MyOp : MyShared2OpClass {
let append extraClassDeclaration = [{ additional specialized code }];
}
```
This is especially painful in MLIR, where dialect authors want base
op/type/attribute classes to inject shared C++ declarations into all
derived definitions. I attempted to solve this in PR
https://github.com/llvm/llvm-project/pull/182265 with MLIR-specific
`inheritableExtraClassDeclaration`/`Definition` fields, but as
@joker-eph [pointed
out](https://github.com/llvm/llvm-project/pull/182265#discussion_r2098718600),
this is ad-hoc -- the same inheritance problem exists for `traits`,
`arguments`, `results`, and any other list/string/dag field. Rather than
adding `inheritable*` variants per field, we should solve this at the
language level.
## Design
This PR adds two new modifiers to the `let` statement: **`append`** and
**`prepend`**.
```tablegen
class Base {
list<int> items = [1, 2];
string text = "hello";
dag d = (op);
}
def Example : Base {
let append items = [3, 4]; // items = [1, 2, 3, 4]
let prepend items = [0]; // items = [0, 1, 2]
let append text = " world"; // text = "hello world"
let prepend text = "say "; // text = "say hello"
let append d = (op 3:$a); // d = (op 3:$a)
}
```
### Supported types
| Field type | Operation | Concat operator |
|---|---|---|
| `list<T>` | append/prepend | `!listconcat` |
| `string` / `code` | append/prepend | `!strconcat` |
| `dag` | append/prepend | `!con` |
| Other (`bit`, `int`, `bits`) | -- | Error |
### Semantics
- **`let append`** concatenates the new value **after** the current
value
- **`let prepend`** concatenates the new value **before** the current
value
- If the current value is **unset** (`?`), the new value is used
directly
- A plain **`let`** (without modifier) still replaces, allowing opt-out
from accumulated values
- Works in both **body-level** (`def Foo { let append ... }`) and
**top-level** (`let append ... in { }`) contexts
### Multi-level inheritance
Accumulation works naturally across inheritance chains:
```tablegen
class Base {
list<int> items = [1, 2];
}
class Middle : Base {
let append items = [3]; // items = [1, 2, 3]
}
def Leaf : Middle {
let append items = [4]; // items = [1, 2, 3, 4]
}
```
### Multiple inheritance
TableGen supports multiple inheritance (`def D : A, B { ... }`), where
parent classes are processed left to right and the **last parent class's
value wins** for any shared field. `let append`/`let prepend` operates
on whatever value the field has *after* inheritance resolution — it does
not accumulate across sibling parents:
```tablegen
class A { list<int> items = [1, 2]; }
class B { list<int> items = [3, 4]; }
def D : A, B {
let append items = [5]; // items = [3, 4, 5] (A's value is lost)
}
```
This also applies to diamond inheritance:
```tablegen
class Base { list<int> items = [1]; }
class Left : Base { let append items = [2]; } // [1, 2]
class Right : Base { let append items = [3]; } // [1, 3]
def D : Left, Right {
let append items = [4]; // items = [1, 3, 4] (Left's [2] is lost)
}
```
This is consistent with how plain `let` works with multiple inheritance
— it is the standard last-writer-wins rule. Users who need accumulation
from multiple parents should use a single-inheritance chain instead.
## Backward compatibility
This proposal is **fully backward compatible**. The keywords `append`
and `prepend` are implemented as **context-sensitive keywords** — they
are only recognized as modifiers when they appear immediately after
`let` (in both body-level and top-level contexts). In all other
positions, `append` and `prepend` remain valid identifiers and can be
used as field names, class names, def names, etc. This means:
- No existing `.td` files (in-tree or out-of-tree) will break
- Fields named `append` or `prepend` continue to work: `let append
append = [5];` is valid (the first `append` is the modifier, the second
is the field name)
- The parser checks for the identifier string value after `let`, not for
a reserved token
RFC:
https://discourse.llvm.org/t/rfc-tablegen-add-let-append-prepend-syntax-for-field-concatenation/89924/
This commit is contained in:
parent
f7a48fbefa
commit
89d150a797
@ -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<n>`` 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<T>``: 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<int> 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
|
||||
------------------------------------------
|
||||
|
||||
@ -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<unsigned> 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<UnsetInit>(CurrentValue)) {
|
||||
const bool IsAppendMode = Mode == LetMode::Append;
|
||||
|
||||
const Init *LHS = IsAppendMode ? CurrentValue : V;
|
||||
const Init *RHS = IsAppendMode ? V : CurrentValue;
|
||||
|
||||
BinOpInit::BinaryOp ConcatOp;
|
||||
if (isa<ListRecTy>(FieldType))
|
||||
ConcatOp = BinOpInit::LISTCONCAT;
|
||||
else if (isa<StringRecTy>(FieldType))
|
||||
ConcatOp = BinOpInit::STRCONCAT;
|
||||
else if (isa<DagRecTy>(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<LetMode>(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<unsigned, 16> 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<LetRecord> &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<LetRecord> &Result) {
|
||||
do {
|
||||
@ -4197,9 +4275,8 @@ void TGParser::ParseLetList(SmallVectorImpl<LetRecord> &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<unsigned, 16> Bits;
|
||||
@ -4222,7 +4299,7 @@ void TGParser::ParseLetList(SmallVectorImpl<LetRecord> &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));
|
||||
}
|
||||
|
||||
|
||||
@ -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<unsigned> Bits;
|
||||
const Init *Value;
|
||||
SMLoc Loc;
|
||||
LetRecord(const StringInit *N, ArrayRef<unsigned> B, const Init *V, SMLoc L)
|
||||
: Name(N), Bits(B), Value(V), Loc(L) {}
|
||||
LetMode Mode;
|
||||
LetRecord(const StringInit *N, ArrayRef<unsigned> 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<unsigned> 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<LetRecord> &Result);
|
||||
|
||||
bool ParseObjectBody(Record *CurRec);
|
||||
|
||||
12
llvm/test/TableGen/let-append-bitrange-error.td
Normal file
12
llvm/test/TableGen/let-append-bitrange-error.td
Normal file
@ -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;
|
||||
}
|
||||
12
llvm/test/TableGen/let-append-error.td
Normal file
12
llvm/test/TableGen/let-append-error.td
Normal file
@ -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;
|
||||
}
|
||||
63
llvm/test/TableGen/let-append-toplevel.td
Normal file
63
llvm/test/TableGen/let-append-toplevel.td
Normal file
@ -0,0 +1,63 @@
|
||||
// RUN: llvm-tblgen %s | FileCheck %s
|
||||
|
||||
// Test 'let append' and 'let prepend' syntax in top-level let.
|
||||
|
||||
class Base {
|
||||
list<int> items = [1, 2];
|
||||
string text = "hello";
|
||||
}
|
||||
|
||||
// Test that 'append' and 'prepend' can be used as field names in top-level let.
|
||||
class HasAppendField {
|
||||
list<int> append = [1, 2];
|
||||
list<int> prepend = [3, 4];
|
||||
int other = 0;
|
||||
}
|
||||
|
||||
// Test top-level let with multiple items: no mode, append, and prepend.
|
||||
// CHECK: def MultiItemTopLevel
|
||||
// CHECK: list<int> 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<int> items = [1, 2, 100, 200];
|
||||
def NestedTopLevel : Base;
|
||||
}
|
||||
}
|
||||
|
||||
// Test top-level let with append.
|
||||
let append items = [100] in {
|
||||
// CHECK: def TopLevelAppend
|
||||
// CHECK: list<int> 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<int> append = [10, 20];
|
||||
// CHECK: list<int> 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<int> append = [1, 2];
|
||||
// CHECK: list<int> prepend = [30, 40];
|
||||
def TopLevelFieldNamedPrepend : HasAppendField;
|
||||
}
|
||||
|
||||
// Test top-level let with prepend.
|
||||
let prepend items = [0] in {
|
||||
// CHECK: def TopLevelPrepend
|
||||
// CHECK: list<int> items = [0, 1, 2];
|
||||
def TopLevelPrepend : Base;
|
||||
}
|
||||
224
llvm/test/TableGen/let-append.td
Normal file
224
llvm/test/TableGen/let-append.td
Normal file
@ -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<int> items = [1, 2];
|
||||
string text = "hello";
|
||||
dag d = (op);
|
||||
}
|
||||
|
||||
class WithCode {
|
||||
code body = [{ int x = 0; }];
|
||||
}
|
||||
|
||||
class WithUnset {
|
||||
list<int> vals = ?;
|
||||
string msg = ?;
|
||||
}
|
||||
|
||||
// Multi-level inheritance accumulation.
|
||||
class Middle : Base {
|
||||
let append items = [3];
|
||||
let append text = " world";
|
||||
}
|
||||
|
||||
// Multiple inheritance classes.
|
||||
class BaseA {
|
||||
list<int> items = [1, 2];
|
||||
string text = "a";
|
||||
}
|
||||
|
||||
class BaseB {
|
||||
list<int> items = [3, 4];
|
||||
string text = "b";
|
||||
}
|
||||
|
||||
// Diamond inheritance classes.
|
||||
class DiamondBase {
|
||||
list<int> 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<list<int> init> {
|
||||
list<int> items = init;
|
||||
}
|
||||
|
||||
// Multiclass with append/prepend in body.
|
||||
multiclass MC<list<int> 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<int> append = [1, 2];
|
||||
list<int> 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<int> 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<int> 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<int> 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<int> append = [1, 2, 5];
|
||||
// CHECK: list<int> 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<int> 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<int> append = [10, 20];
|
||||
// CHECK: list<int> 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<int> 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<int> items = [1, 2, 10, 20];
|
||||
// CHECK: def MCTest_b
|
||||
// CHECK: list<int> 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<int> items = [3, 4, 5];
|
||||
// CHECK: string text = "b!";
|
||||
def MultiInherit : BaseA, BaseB {
|
||||
let append items = [5];
|
||||
let append text = "!";
|
||||
}
|
||||
|
||||
// CHECK: def MultiLevel
|
||||
// CHECK: list<int> 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<int> 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<int> 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<int> vals = [1];
|
||||
// CHECK: string msg = "hi";
|
||||
def PrependUnset : WithUnset {
|
||||
let prepend vals = [1];
|
||||
let prepend msg = "hi";
|
||||
}
|
||||
12
llvm/test/TableGen/let-prepend-error.td
Normal file
12
llvm/test/TableGen/let-prepend-error.td
Normal file
@ -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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user