diff --git a/llvm/docs/TableGen/ProgRef.rst b/llvm/docs/TableGen/ProgRef.rst --- a/llvm/docs/TableGen/ProgRef.rst +++ b/llvm/docs/TableGen/ProgRef.rst @@ -194,11 +194,11 @@ TableGen has the following reserved keywords, which cannot be used as identifiers:: - bit bits class code dag - def else false foreach defm - defset defvar field if in - include int let list multiclass - string then true + assert bit bits class code + dag def else false foreach + defm defset defvar field if + in include int let list + multiclass string then true .. warning:: The ``field`` reserved word is deprecated. @@ -536,8 +536,8 @@ .. productionlist:: TableGenFile: `Statement`* - Statement: `Class` | `Def` | `Defm` | `Defset` | `Defvar` | `Foreach` - :| `If` | `Let` | `MultiClass` + Statement: `Assert` | `Class` | `Def` | `Defm` | `Defset` | `Defvar` + :| `Foreach` | `If` | `Let` | `MultiClass` The following sections describe each of these top-level statements. @@ -616,6 +616,7 @@ BodyItem: (`Type` | "code") `TokIdentifier` ["=" `Value`] ";" :| "let" `TokIdentifier` ["{" `RangeList` "}"] "=" `Value` ";" :| "defvar" `TokIdentifier` "=" `Value` ";" + :| `Assert` 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 @@ -1247,6 +1248,34 @@ The ``if`` statement can also be used in a record :token:`Body`. +``assert`` --- check that a condition is true +--------------------------------------------- + +The ``assert`` statement checks a boolean condition to be sure that it is true +and prints an error message if it is not. + +.. productionlist:: + Assert: "assert" `condition` "," `message` ";" + +If the boolean condition is true, the statement does nothing. If the +condition is false, it prints a nonfatal error message. The **message**, which +can be an arbitrary string expression, is included in the error message as a +note. The exact behavior of the ``assert`` statement depends on its +placement. + +* At top level, the assertion is checked immediately. + +* In a record definition, the statement is saved and all assertions are + checked after the record is completely built. + +* In a class definition, the assertions are saved and inherited by all + the record definitions that inherit from the class. The assertions are + then checked when the records are completely built. [this placement is not + yet available] + +* In a multiclass definition, ... [this placement is not yet available] + + Additional Details ================== diff --git a/llvm/include/llvm/TableGen/Record.h b/llvm/include/llvm/TableGen/Record.h --- a/llvm/include/llvm/TableGen/Record.h +++ b/llvm/include/llvm/TableGen/Record.h @@ -1445,6 +1445,8 @@ SmallVector Locs; SmallVector TemplateArgs; SmallVector Values; + // Vector of [source location, condition Init, message Init]. + SmallVector, 0> Assertions; // All superclasses in the inheritance forest in post-order (yes, it // must be a forest; diamond-shaped inheritance is not allowed). @@ -1519,6 +1521,10 @@ ArrayRef getValues() const { return Values; } + ArrayRef> getAssertions() const { + return Assertions; + } + ArrayRef> getSuperClasses() const { return SuperClasses; } @@ -1576,6 +1582,10 @@ removeValue(StringInit::get(Name)); } + void addAssertion(SMLoc Loc, Init *Condition, Init *Message) { + Assertions.push_back(std::make_tuple(Loc, Condition, Message)); + } + bool isSubClassOf(const Record *R) const { for (const auto &SCPair : SuperClasses) if (SCPair.first == R) diff --git a/llvm/lib/TableGen/TGLexer.h b/llvm/lib/TableGen/TGLexer.h --- a/llvm/lib/TableGen/TGLexer.h +++ b/llvm/lib/TableGen/TGLexer.h @@ -47,8 +47,8 @@ // Reserved keywords. ('ElseKW' is named to distinguish it from the // existing 'Else' that means the preprocessor #else.) - Bit, Bits, Class, Code, Dag, Def, Defm, Defset, Defvar, ElseKW, FalseKW, - Field, Foreach, If, In, Include, Int, Let, List, MultiClass, + Assert, Bit, Bits, Class, Code, Dag, Def, Defm, Defset, Defvar, ElseKW, + FalseKW, Field, Foreach, If, In, Include, Int, Let, List, MultiClass, String, Then, TrueKW, // Bang operators. diff --git a/llvm/lib/TableGen/TGLexer.cpp b/llvm/lib/TableGen/TGLexer.cpp --- a/llvm/lib/TableGen/TGLexer.cpp +++ b/llvm/lib/TableGen/TGLexer.cpp @@ -365,6 +365,7 @@ .Case("if", tgtok::If) .Case("then", tgtok::Then) .Case("else", tgtok::ElseKW) + .Case("assert", tgtok::Assert) .Default(tgtok::Id); // A couple of tokens require special processing. diff --git a/llvm/lib/TableGen/TGParser.h b/llvm/lib/TableGen/TGParser.h --- a/llvm/lib/TableGen/TGParser.h +++ b/llvm/lib/TableGen/TGParser.h @@ -222,6 +222,7 @@ bool ParseForeach(MultiClass *CurMultiClass); bool ParseIf(MultiClass *CurMultiClass); bool ParseIfBody(MultiClass *CurMultiClass, StringRef Kind); + bool ParseAssert(MultiClass *CurMultiClass, Record *CurRec); bool ParseTopLevelLet(MultiClass *CurMultiClass); void ParseLetList(SmallVectorImpl &Result); @@ -263,6 +264,8 @@ MultiClass *ParseMultiClassID(); bool ApplyLetStack(Record *CurRec); bool ApplyLetStack(RecordsEntry &Entry); + void CheckAssert(SMLoc Loc, Init *Condition, Init *Message); + void CheckRecordAsserts(Record &Rec); }; } // end namespace llvm diff --git a/llvm/lib/TableGen/TGParser.cpp b/llvm/lib/TableGen/TGParser.cpp --- a/llvm/lib/TableGen/TGParser.cpp +++ b/llvm/lib/TableGen/TGParser.cpp @@ -452,6 +452,8 @@ Rec->resolveReferences(); checkConcrete(*Rec); + CheckRecordAsserts(*Rec); + if (!isa(Rec->getNameInit())) { PrintError(Rec->getLoc(), Twine("record name '") + Rec->getNameInit()->getAsString() + @@ -482,11 +484,12 @@ // Parser Code //===----------------------------------------------------------------------===// -/// isObjectStart - Return true if this is a valid first token for an Object. +/// isObjectStart - Return true if this is a valid first token for a statement. 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::Defvar || K == tgtok::If; + return K == tgtok::Assert || K == tgtok::Class || K == tgtok::Def || + K == tgtok::Defm || K == tgtok::Defset || K == tgtok::Defvar || + K == tgtok::Foreach || K == tgtok::If || K == tgtok::Let || + K == tgtok::MultiClass; } bool TGParser::consume(tgtok::TokKind K) { @@ -844,8 +847,7 @@ } } -/// ParseIDValue - This is just like ParseIDValue above, but it assumes the ID -/// has already been read. +/// ParseIDValue Init *TGParser::ParseIDValue(Record *CurRec, StringInit *Name, SMLoc NameLoc, IDParseMode Mode) { if (CurRec) { @@ -2308,7 +2310,7 @@ return R; } -/// ParseValue - Parse a tblgen value. This returns null on error. +/// ParseValue - Parse a TableGen value. This returns null on error. /// /// Value ::= SimpleValue ValueSuffix* /// ValueSuffix ::= '{' BitList '}' @@ -2763,12 +2765,16 @@ return false; } -/// ParseBodyItem - Parse a single item at within the body of a def or class. +/// ParseBodyItem - Parse a single item within the body of a def or class. /// /// BodyItem ::= Declaration ';' /// BodyItem ::= LET ID OptionalBitList '=' Value ';' /// BodyItem ::= Defvar +/// BodyItem ::= Assert bool TGParser::ParseBodyItem(Record *CurRec) { + if (Lex.getCode() == tgtok::Assert) + return ParseAssert(nullptr, CurRec); + if (Lex.getCode() == tgtok::Defvar) return ParseDefvar(); @@ -3174,6 +3180,45 @@ return false; } +/// ParseAssert - Parse an assert statement. +/// +/// Assert ::= ASSERT condition , message ; +bool TGParser::ParseAssert(MultiClass *CurMultiClass, Record *CurRec) { + SMLoc Loc = Lex.getLoc(); + assert(Lex.getCode() == tgtok::Assert && "Unknown tok"); + Lex.Lex(); // Eat the 'assert' token. + + SMLoc ConditionLoc = Lex.getLoc(); + Init *Condition = ParseValue(CurRec); + if (!Condition) + return true; + + if (!consume(tgtok::comma)) { + TokError("expected ',' in assert statement"); + return true; + } + + Init *Message = ParseValue(CurRec); + if (!Message) + return true; + + if (!consume(tgtok::semi)) + return TokError("expected ';'"); + + if (CurMultiClass) { + assert(false && "assert in multiclass not yet supported"); + } else if (CurRec) { + CurRec->addAssertion(ConditionLoc, Condition, Message); + } else { // at top level + RecordResolver R(*CurRec); + Init *Value = Condition->resolveReferences(R); + Init *Text = Message->resolveReferences(R); + CheckAssert(ConditionLoc, Value, Text); + } + + return false; +} + /// ParseClass - Parse a tblgen class definition. /// /// ClassInst ::= CLASS ID TemplateArgList? ObjectBody @@ -3373,14 +3418,17 @@ while (Lex.getCode() != tgtok::r_brace) { switch (Lex.getCode()) { default: - return TokError("expected 'let', 'def', 'defm', 'defvar', 'foreach' " - "or 'if' in multiclass body"); - case tgtok::Let: + return TokError("expected 'assert', 'def', 'defm', 'defvar', " + "'foreach', 'if', or 'let' in multiclass body"); + case tgtok::Assert: + return TokError("an assert statement in a multiclass is not yet supported"); + case tgtok::Def: case tgtok::Defm: case tgtok::Defvar: case tgtok::Foreach: case tgtok::If: + case tgtok::Let: if (ParseObject(CurMultiClass)) return true; break; @@ -3531,22 +3579,23 @@ /// Object ::= LETCommand Object /// Object ::= Defset /// Object ::= Defvar +/// Object ::= Assert bool TGParser::ParseObject(MultiClass *MC) { switch (Lex.getCode()) { default: - return TokError("Expected class, def, defm, defset, multiclass, let, " - "foreach or if"); - case tgtok::Let: return ParseTopLevelLet(MC); - case tgtok::Def: return ParseDef(MC); - case tgtok::Foreach: return ParseForeach(MC); - case tgtok::If: return ParseIf(MC); - case tgtok::Defm: return ParseDefm(MC); + return TokError( + "Expected assert, class, def, defm, defset, foreach, if, or let"); + case tgtok::Assert: return ParseAssert(MC, nullptr); + case tgtok::Def: return ParseDef(MC); + case tgtok::Defm: return ParseDefm(MC); + case tgtok::Defvar: return ParseDefvar(); + case tgtok::Foreach: return ParseForeach(MC); + case tgtok::If: return ParseIf(MC); + case tgtok::Let: return ParseTopLevelLet(MC); case tgtok::Defset: 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"); @@ -3581,6 +3630,37 @@ return TokError("Unexpected input at top level"); } +// Check an assertion: Obtain the condition value and be sure it is true. +// If not, print a nonfatal error along with the message. +void TGParser::CheckAssert(SMLoc Loc, Init *Condition, Init *Message) { + auto *CondValue = dyn_cast_or_null( + Condition->convertInitializerTo(IntRecTy::get())); + if (CondValue) { + if (!CondValue->getValue()) { + PrintError(Loc, "assertion failed"); + if (auto *MessageInit = dyn_cast(Message)) + PrintNote(MessageInit->getValue()); + else + PrintNote("(assert message is not a string)"); + } + } else { + PrintError(Loc, "assert condition must of type bit, bits, or int."); + } +} + +// Check all record assertions: For each one, resolve the condition +// and message, then call CheckAssert(). +void TGParser::CheckRecordAsserts(Record &Rec) { + RecordResolver R(Rec); + R.setFinal(true); + + for (auto Assertion : Rec.getAssertions()) { + Init *Condition = std::get<1>(Assertion)->resolveReferences(R); + Init *Message = std::get<2>(Assertion)->resolveReferences(R); + CheckAssert(std::get<0>(Assertion), Condition, Message); + } +} + #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) LLVM_DUMP_METHOD void RecordsEntry::dump() const { if (Loop) diff --git a/llvm/test/TableGen/assert.td b/llvm/test/TableGen/assert.td new file mode 100644 --- /dev/null +++ b/llvm/test/TableGen/assert.td @@ -0,0 +1,98 @@ +// RUN: not llvm-tblgen %s 2>&1 | FileCheck %s + +// Test the assert statement at top level. + +// CHECK: assertion failed +// CHECK-NOT: note: primary name is too short +// CHECK: note: primary name is too long + +defvar Name = "Grace Brewster Murray Hopper"; + +assert !ge(!size(Name), 20), "primary name is too short: " # Name; +assert !le(!size(Name), 20), "primary name is too long: " # Name; + +// CHECK: assertion failed +// CHECK: note: first name is incorrect + +def Rec1 { + string name = "Fred Smith"; +} + +assert !eq(!substr(Rec1.name, 0, 3), "Jane"), + !strconcat("first name is incorrect: ", Rec1.name); + +// CHECK: assertion failed +// CHECK: note: record Rec2 is broken + +def Rec2 { + bit broken = true; +} + +assert !not(Rec2.broken), "record Rec2 is broken"; + +// CHECK: assertion failed +// CHECK: note: cube of 9 + +class Cube { + int result = !mul(n, n, n); +} + +assert !eq(Cube<9>.result, 81), "cube of 9 should be 729"; + +// Test the assert statement in a record definition. + +// CHECK: assertion failed +// CHECK-NOT: primary first name is not "Grace" +// CHECK: primary first name is not "Grack" +// CHECK: assertion failed +// CHECK: foo field should be + +def Rec10 { + assert !eq(!substr(Name, 0, 5), "Grace"), "primary first name is not \"Grace\""; + assert !eq(!substr(Name, 0, 5), "Grack"), "primary first name is not \"Grack\""; + string foo = "Foo"; + assert !eq(foo, "foo"), "foo field should be \"Foo\""; +} + +// CHECK: assertion failed +// CHECK: note: magic field is incorrect: 42 + +def Rec11 { + int magic = 13; + assert !eq(magic, 13), "magic field is incorrect: " # magic; + let magic = 42; +} + +// CHECK: assertion failed +// CHECK: note: var field has wrong value + +def Rec12 { + defvar prefix = "foo_"; + string var = prefix # "snork"; + assert !eq(var, "foo_snorx"), "var field has wrong value: " # var; +} + +// CHECK: assertion failed +// CHECK: note: kind field has wrong value + +class Kind { + int kind = 7; +} + +def Rec13 : Kind { + let kind = 8; + assert !eq(kind, 7), "kind field has wrong value: " # kind; +} + +// CHECK: assertion failed +// CHECK: note: double_result should be + +def Rec14 : Cube<3> { + int double_result = !mul(result, 2); + assert !eq(double_result, 53), "double_result should be 54"; +} + +// Test the assert statement in a class definition. + +// Test the assert statement in a multiclass. +