1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-10-18 18:42:46 +02:00

[TableGen] Introduce a defvar statement.

Summary:
This allows you to define a global or local variable to an arbitrary
value, and refer to it in subsequent definitions.

The main use I anticipate for this is if you have to compute some
difficult function of the parameters of a multiclass, and then use it
many times. For example:

  multiclass Foo<int i, string s> {
    defvar op = !cast<BaseClass>("whatnot_" # s # "_" # i);
    def myRecord {
      dag a = (op this, (op that, the other), (op x, y, z));
      int b = op.subfield;
    }
    def myOtherRecord<"template params including", op>;
  }

There are a couple of ways to do this already, but they're not really
satisfactory. You can replace `defvar x = y` with a loop over a
singleton list, `foreach x = [y] in { ... }` - but that's unintuitive
to someone who hasn't seen that workaround idiom before, and requires
an extra pair of braces that you often didn't really want. Or you can
define a nested pair of multiclasses, with the inner one taking `x` as
a template parameter, and the outer one instantiating it just once
with the desired value of `x` computed from its other parameters - but
that makes it awkward to sequentially compute each value based on the
previous ones. I think `defvar` makes things considerably easier.

You can also use `defvar` at the top level, where it inserts globals
into the same map used by `defset`. That allows you to define global
constants without having to make a dummy record for them to live in:

  defvar MAX_BUFSIZE = 512;

  // previously:
  // def Dummy { int MAX_BUFSIZE = 512; }
  // and then refer to Dummy.MAX_BUFSIZE everywhere

Reviewers: nhaehnle, hfinkel

Reviewed By: hfinkel

Subscribers: hiraditya, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D71407
This commit is contained in:
Simon Tatham 2020-01-14 09:10:18 +00:00
parent 682d92f2bb
commit 923de4e7fe
6 changed files with 301 additions and 10 deletions

View File

@ -124,8 +124,8 @@ TableGen's top-level production consists of "objects".
.. productionlist::
TableGenFile: `Object`*
Object: `Class` | `Def` | `Defm` | `Defset` | `Let` | `MultiClass` |
`Foreach`
Object: `Class` | `Def` | `Defm` | `Defset` | `Defvar` | `Let` |
`MultiClass` | `Foreach`
``class``\es
------------
@ -262,7 +262,7 @@ of:
foreach i = 0-5 in
def Foo#i;
* a variable defined by ``defset``
* a variable defined by ``defset`` or ``defvar``
* the implicit template argument ``NAME`` in a ``class`` or ``multiclass``
@ -348,6 +348,7 @@ It is after parsing the base class list that the "let stack" is applied.
BodyList: BodyItem*
BodyItem: `Declaration` ";"
:| "let" `TokIdentifier` [ "{" `RangeList` "}" ] "=" `Value` ";"
:| `Defvar`
The ``let`` form allows overriding the value of an inherited field.
@ -359,8 +360,8 @@ The ``let`` form allows overriding the value of an inherited field.
Defines a record whose name is given by the optional :token:`Value`. The value
is parsed in a special mode where global identifiers (records and variables
defined by ``defset``) are not recognized, and all unrecognized identifiers
are interpreted as strings.
defined by ``defset``, and variables defined at global scope by ``defvar``) are
not recognized, and all unrecognized identifiers are interpreted as strings.
If no name is given, the record is anonymous. The final name of anonymous
records is undefined, but globally unique.
@ -420,6 +421,36 @@ The given type must be ``list<A>``, where ``A`` is some class. It is an error
to define a record (via ``def`` or ``defm``) inside the braces which doesn't
derive from ``A``.
``defvar``
----------
.. productionlist::
Defvar: "defvar" `TokIdentifier` "=" `Value` ";"
The identifier on the left of the ``=`` is defined to be a global or local
variable, whose value is given by the expression on the right of the ``=``. The
type of the variable is automatically inferred.
A ``defvar`` statement at the top level of the file defines a global variable,
in the same scope used by ``defset``. If a ``defvar`` statement appears inside
any other construction, including classes, multiclasses and ``foreach``
statements, then the variable is scoped to the inside of that construction
only.
In contexts where the ``defvar`` statement will be encountered multiple times,
the definition is re-evaluated for each instance. For example, a ``defvar``
inside a ``foreach`` can construct a value based on the iteration variable,
which will be different every time round the loop; a ``defvar`` inside a
templated class or multiclass can have a definition depending on the template
parameters.
Variables local to a ``foreach`` go out of scope at the end of each loop
iteration, so their previous value is not accessible in the next iteration. (It
won't work to ``defvar i=!add(i,1)`` each time you go round the loop.)
In general, ``defvar`` variables are immutable once they are defined. It is an
error to define the same variable name twice in the same scope (but legal to
shadow the first definition temporarily in an inner scope).
``foreach``
-----------

View File

@ -350,6 +350,7 @@ tgtok::TokKind TGLexer::LexIdentifier() {
.Case("field", tgtok::Field)
.Case("let", tgtok::Let)
.Case("in", tgtok::In)
.Case("defvar", tgtok::Defvar)
.Default(tgtok::Id);
if (Kind == tgtok::Id)

View File

@ -46,7 +46,7 @@ namespace tgtok {
// Keywords.
Bit, Bits, Class, Code, Dag, Def, Foreach, Defm, Field, In, Int, Let, List,
MultiClass, String, Defset,
MultiClass, String, Defset, Defvar,
// !keywords.
XConcat, XADD, XMUL, XAND, XOR, XSRA, XSRL, XSHL, XListConcat, XListSplat,

View File

@ -482,7 +482,7 @@ bool TGParser::addDefOne(std::unique_ptr<Record> Rec) {
static bool isObjectStart(tgtok::TokKind K) {
return K == tgtok::Class || K == tgtok::Def || K == tgtok::Defm ||
K == tgtok::Let || K == tgtok::MultiClass || K == tgtok::Foreach ||
K == tgtok::Defset;
K == tgtok::Defset || K == tgtok::Defvar;
}
/// ParseObjectName - If a valid object name is specified, return it. If no
@ -869,6 +869,10 @@ Init *TGParser::ParseIDValue(Record *CurRec, StringInit *Name, SMLoc NameLoc,
}
}
if (CurLocalScope)
if (Init *I = CurLocalScope->getVar(Name->getValue()))
return I;
// If this is in a foreach loop, make sure it's not a loop iterator
for (const auto &L : Loops) {
VarInit *IterVar = dyn_cast<VarInit>(L->IterVar);
@ -2595,7 +2599,11 @@ bool TGParser::ParseTemplateArgList(Record *CurRec) {
///
/// BodyItem ::= Declaration ';'
/// BodyItem ::= LET ID OptionalBitList '=' Value ';'
/// BodyItem ::= Defvar
bool TGParser::ParseBodyItem(Record *CurRec) {
if (Lex.getCode() == tgtok::Defvar)
return ParseDefvar();
if (Lex.getCode() != tgtok::Let) {
if (!ParseDeclaration(CurRec, false))
return true;
@ -2658,10 +2666,15 @@ bool TGParser::ParseBody(Record *CurRec) {
// Eat the '{'.
Lex.Lex();
// An object body introduces a new scope for local variables.
TGLocalVarScope *BodyScope = PushLocalScope();
while (Lex.getCode() != tgtok::r_brace)
if (ParseBodyItem(CurRec))
return true;
PopLocalScope(BodyScope);
// Eat the '}'.
Lex.Lex();
return false;
@ -2800,6 +2813,45 @@ bool TGParser::ParseDefset() {
return false;
}
/// ParseDefvar - Parse a defvar statement.
///
/// Defvar ::= DEFVAR Id '=' Value ';'
///
bool TGParser::ParseDefvar() {
assert(Lex.getCode() == tgtok::Defvar);
Lex.Lex(); // Eat the 'defvar' token
if (Lex.getCode() != tgtok::Id)
return TokError("expected identifier");
StringInit *DeclName = StringInit::get(Lex.getCurStrVal());
if (CurLocalScope) {
if (CurLocalScope->varAlreadyDefined(DeclName->getValue()))
return TokError("local variable of this name already exists");
} else {
if (Records.getGlobal(DeclName->getValue()))
return TokError("def or global variable of this name already exists");
}
if (Lex.Lex() != tgtok::equal) // Eat the identifier
return TokError("expected '='");
Lex.Lex(); // Eat the '='
Init *Value = ParseValue(nullptr);
if (!Value)
return true;
if (Lex.getCode() != tgtok::semi)
return TokError("expected ';'");
Lex.Lex(); // Eat the ';'
if (CurLocalScope)
CurLocalScope->addVar(DeclName->getValue(), Value);
else
Records.addExtraGlobal(DeclName->getValue(), Value);
return false;
}
/// ParseForeach - Parse a for statement. Return the record corresponding
/// to it. This returns true on error.
///
@ -2825,6 +2877,9 @@ bool TGParser::ParseForeach(MultiClass *CurMultiClass) {
// Create a loop object and remember it.
Loops.push_back(std::make_unique<ForeachLoop>(Loc, IterName, ListValue));
// A foreach loop introduces a new scope for local variables.
TGLocalVarScope *ForeachScope = PushLocalScope();
if (Lex.getCode() != tgtok::l_brace) {
// FOREACH Declaration IN Object
if (ParseObject(CurMultiClass))
@ -2845,6 +2900,8 @@ bool TGParser::ParseForeach(MultiClass *CurMultiClass) {
Lex.Lex(); // Eat the }
}
PopLocalScope(ForeachScope);
// Resolve the loop or store it for later resolution.
std::unique_ptr<ForeachLoop> Loop = std::move(Loops.back());
Loops.pop_back();
@ -2957,6 +3014,8 @@ bool TGParser::ParseTopLevelLet(MultiClass *CurMultiClass) {
return TokError("expected 'in' at end of top-level 'let'");
Lex.Lex();
TGLocalVarScope *LetScope = PushLocalScope();
// If this is a scalar let, just handle it now
if (Lex.getCode() != tgtok::l_brace) {
// LET LetList IN Object
@ -2978,6 +3037,8 @@ bool TGParser::ParseTopLevelLet(MultiClass *CurMultiClass) {
Lex.Lex();
}
PopLocalScope(LetScope);
// Outside this let scope, this let block is not active.
LetStack.pop_back();
return false;
@ -3051,14 +3112,18 @@ bool TGParser::ParseMultiClass() {
if (Lex.Lex() == tgtok::r_brace) // eat the '{'.
return TokError("multiclass must contain at least one def");
// A multiclass body introduces a new scope for local variables.
TGLocalVarScope *MulticlassScope = PushLocalScope();
while (Lex.getCode() != tgtok::r_brace) {
switch (Lex.getCode()) {
default:
return TokError("expected 'let', 'def', 'defm' or 'foreach' in "
"multiclass body");
return TokError("expected 'let', 'def', 'defm', 'defvar' or 'foreach' "
"in multiclass body");
case tgtok::Let:
case tgtok::Def:
case tgtok::Defm:
case tgtok::Defvar:
case tgtok::Foreach:
if (ParseObject(CurMultiClass))
return true;
@ -3066,6 +3131,8 @@ bool TGParser::ParseMultiClass() {
}
}
Lex.Lex(); // eat the '}'.
PopLocalScope(MulticlassScope);
}
CurMultiClass = nullptr;
@ -3207,6 +3274,8 @@ bool TGParser::ParseDefm(MultiClass *CurMultiClass) {
/// Object ::= DefMInst
/// Object ::= LETCommand '{' ObjectList '}'
/// Object ::= LETCommand Object
/// Object ::= Defset
/// Object ::= Defvar
bool TGParser::ParseObject(MultiClass *MC) {
switch (Lex.getCode()) {
default:
@ -3220,6 +3289,8 @@ bool TGParser::ParseObject(MultiClass *MC) {
if (MC)
return TokError("defset is not allowed inside multiclass");
return ParseDefset();
case tgtok::Defvar:
return ParseDefvar();
case tgtok::Class:
if (MC)
return TokError("class is not allowed inside multiclass");

View File

@ -74,6 +74,46 @@ namespace llvm {
SmallVector<Init *, 16> Elements;
};
class TGLocalVarScope {
// A scope to hold local variable definitions from defvar.
std::map<std::string, Init *, std::less<>> vars;
std::unique_ptr<TGLocalVarScope> parent;
public:
TGLocalVarScope() = default;
TGLocalVarScope(std::unique_ptr<TGLocalVarScope> parent)
: parent(std::move(parent)) {}
std::unique_ptr<TGLocalVarScope> extractParent() {
// This is expected to be called just before we are destructed, so
// it doesn't much matter what state we leave 'parent' in.
return std::move(parent);
}
Init *getVar(StringRef Name) const {
auto It = vars.find(Name);
if (It != vars.end())
return It->second;
if (parent)
return parent->getVar(Name);
return nullptr;
}
bool varAlreadyDefined(StringRef Name) const {
// When we check whether a variable is already defined, for the purpose of
// reporting an error on redefinition, we don't look up to the parent
// scope, because it's all right to shadow an outer definition with an
// inner one.
return vars.find(Name) != vars.end();
}
void addVar(StringRef Name, Init *I) {
bool Ins = vars.insert(std::make_pair(Name, I)).second;
(void)Ins;
assert(Ins && "Local variable already exists");
}
};
struct MultiClass {
Record Rec; // Placeholder for template args and Name.
std::vector<RecordsEntry> Entries;
@ -99,6 +139,10 @@ class TGParser {
/// current value.
MultiClass *CurMultiClass;
/// CurLocalScope - Innermost of the current nested scopes for 'defvar' local
/// variables.
std::unique_ptr<TGLocalVarScope> CurLocalScope;
// Record tracker
RecordKeeper &Records;
@ -133,6 +177,19 @@ public:
return Lex.getDependencies();
}
TGLocalVarScope *PushLocalScope() {
CurLocalScope = std::make_unique<TGLocalVarScope>(std::move(CurLocalScope));
// Returns a pointer to the new scope, so that the caller can pass it back
// to PopLocalScope which will check by assertion that the pushes and pops
// match up properly.
return CurLocalScope.get();
}
void PopLocalScope(TGLocalVarScope *ExpectedStackTop) {
assert(ExpectedStackTop == CurLocalScope.get() &&
"Mismatched pushes and pops of local variable scopes");
CurLocalScope = CurLocalScope->extractParent();
}
private: // Semantic analysis methods.
bool AddValue(Record *TheRec, SMLoc Loc, const RecordVal &RV);
bool SetValue(Record *TheRec, SMLoc Loc, Init *ValName,
@ -161,6 +218,7 @@ private: // Parser methods.
bool ParseDefm(MultiClass *CurMultiClass);
bool ParseDef(MultiClass *CurMultiClass);
bool ParseDefset();
bool ParseDefvar();
bool ParseForeach(MultiClass *CurMultiClass);
bool ParseTopLevelLet(MultiClass *CurMultiClass);
void ParseLetList(SmallVectorImpl<LetRecord> &Result);

130
test/TableGen/defvar.td Normal file
View File

@ -0,0 +1,130 @@
// RUN: llvm-tblgen %s | FileCheck %s
// RUN: not llvm-tblgen -DERROR1 %s 2>&1 | FileCheck --check-prefix=ERROR1 %s
// RUN: not llvm-tblgen -DERROR2 %s 2>&1 | FileCheck --check-prefix=ERROR2 %s
// RUN: not llvm-tblgen -DERROR3 %s 2>&1 | FileCheck --check-prefix=ERROR3 %s
#ifdef ERROR1
// Refer to a variable we haven't defined *yet*, expecting an error.
// ERROR1: [[@LINE+1]]:22: error: Variable not defined: 'myvar'
def bad { dag x = (? myvar); }
#endif
// Define a global variable.
defvar myvar = "foo";
#ifdef ERROR2
// Demonstrate an error when a global variable is redefined.
// ERROR2: [[@LINE+1]]:8: error: def or global variable of this name already exists
defvar myvar = "another value";
#endif
multiclass Test<int x> {
// Refer to a global variable, while inside a local scope like a multiclass.
def _with_global_string { string s = myvar; }
// Define some variables local to this multiclass, and prove we can refer to
// those too.
defvar myvar = !add(x, 100);
defvar myvar2 = "string of " # myvar;
def _with_local_int { int i = myvar; string s = myvar2; }
#ifdef ERROR3
// Demonstrate an error when a local variable is redefined.
// ERROR3: [[@LINE+1]]:10: error: local variable of this name already exists
defvar myvar = "another value";
#endif
}
// Instantiate the above multiclass, and expect all the right outputs.
// CHECK: def aaa_with_global_string {
// CHECK-NEXT: string s = "foo";
// CHECK: def aaa_with_local_int {
// CHECK-NEXT: int i = 101;
// CHECK-NEXT: string s = "string of 101";
// CHECK: def bbb_with_global_string {
// CHECK-NEXT: string s = "foo";
// CHECK: def bbb_with_local_int {
// CHECK-NEXT: int i = 102;
// CHECK-NEXT: string s = "string of 102";
defm aaa: Test<1>;
defm bbb: Test<2>;
// Test that local variables can be defined inside a foreach block, and inside
// an object body.
//
// The scopes nest (you can refer to variables in an outer block from an inner
// one), and the variables go out of scope again at the end of the block (in
// particular, you don't get a redefinition error the next time round the
// loop).
// CHECK: def nest_f1_s3 {
// CHECK-NEXT: int member = 113;
// CHECK-NEXT: }
// CHECK: def nest_f1_s4 {
// CHECK-NEXT: int member = 114;
// CHECK-NEXT: }
// CHECK: def nest_f2_s3 {
// CHECK-NEXT: int member = 123;
// CHECK-NEXT: }
// CHECK: def nest_f2_s4 {
// CHECK-NEXT: int member = 124;
// CHECK-NEXT: }
foreach first = [ 1, 2 ] in {
defvar firstStr = "f" # first;
foreach second = [ 3, 4 ] in {
defvar secondStr = "s" # second;
def "nest_" # firstStr # "_" # secondStr {
defvar defLocalVariable = !add(!mul(first, 10), second);
int member = !add(100, defLocalVariable);
}
}
}
defvar firstStr = "now define this at the top level and still expect no error";
// Test that you can shadow an outer declaration with an inner one. Here, we
// expect all the shadowOuter records (both above and below the inner foreach)
// to get the value 1 from the outer definition of shadowedVariable, and the
// shadowInner ones to get 2 from the inner definition.
// CHECK: def shadowInner11 {
// CHECK-NEXT: int var = 2;
// CHECK: def shadowInner12 {
// CHECK-NEXT: int var = 2;
// CHECK: def shadowInner21 {
// CHECK-NEXT: int var = 2;
// CHECK: def shadowInner22 {
// CHECK-NEXT: int var = 2;
// CHECK: def shadowOuterAbove1 {
// CHECK-NEXT: int var = 1;
// CHECK: def shadowOuterAbove2 {
// CHECK-NEXT: int var = 1;
// CHECK: def shadowOuterBelowForeach1 {
// CHECK-NEXT: int var = 1;
// CHECK: def shadowOuterBelowForeach2 {
// CHECK-NEXT: int var = 1;
foreach first = [ 1, 2 ] in {
defvar shadowedVariable = 1;
def shadowOuterAbove # first { int var = shadowedVariable; }
// The foreach statement opens a new scope, in which a new variable of the
// same name can be defined without clashing with the outer one.
foreach second = [ 1, 2 ] in {
defvar shadowedVariable = 2;
def shadowInner # first # second { int var = shadowedVariable; }
}
// Now the outer variable is back in scope.
def shadowOuterBelowForeach # first { int var = shadowedVariable; }
}
// Test that a top-level let statement also makes a variable scope (on the
// general principle of consistency, because it defines a braced sub-block).
let someVariable = "some value" in {
defvar myvar = "override the definition from above and expect no error";
}
// CHECK: def topLevelLetTest {
// CHECK-NEXT: string val = "foo";
def topLevelLetTest { string val = myvar; }