Index: llvm/docs/TableGen/ProgRef.rst =================================================================== --- llvm/docs/TableGen/ProgRef.rst +++ llvm/docs/TableGen/ProgRef.rst @@ -1272,12 +1272,30 @@ 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] + the subclasses and records that inherit from the class. The assertions are + then checked when the records are completely built. * In a multiclass definition, ... [this placement is not yet available] +Using assertions in TableGen files can simplify record checking in TableGen +backends. Here is an example of an ``assert`` in two class definitions. + +.. code-block:: text + + class PersonName { + assert !le(!size(name), 32), "person name is too long: " # name; + string Name = name; + } + + class Person : PersonName { + assert !and(!ge(age, 1), !le(age, 120)), "person age is invalid: " # age; + int Age = age; + } + + def Rec20 : Person<"Donald Knuth", 60> { + ... + } + Additional Details ================== Index: llvm/include/llvm/TableGen/Error.h =================================================================== --- llvm/include/llvm/TableGen/Error.h +++ llvm/include/llvm/TableGen/Error.h @@ -48,6 +48,9 @@ LLVM_ATTRIBUTE_NORETURN void PrintFatalError(const RecordVal *RecVal, const Twine &Msg); +LLVM_ATTRIBUTE_NORETURN void CheckAssert(SMLoc Loc, Init *Condition, + Init *Message); + extern SourceMgr SrcMgr; extern unsigned ErrorsPrinted; Index: llvm/include/llvm/TableGen/Record.h =================================================================== --- llvm/include/llvm/TableGen/Record.h +++ llvm/include/llvm/TableGen/Record.h @@ -1616,6 +1616,12 @@ Assertions.push_back(std::make_tuple(Loc, Condition, Message)); } + void appendAssertions(const Record *Rec) { + Assertions.append(Rec->Assertions); + } + + void checkAssertions(); + bool isSubClassOf(const Record *R) const { for (const auto &SCPair : SuperClasses) if (SCPair.first == R) Index: llvm/lib/TableGen/Error.cpp =================================================================== --- llvm/lib/TableGen/Error.cpp +++ llvm/lib/TableGen/Error.cpp @@ -154,4 +154,22 @@ std::exit(1); } +// Check an assertion: Obtain the condition value and be sure it is true. +// If not, print a nonfatal error along with the message. +void 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."); + } +} + } // end namespace llvm Index: llvm/lib/TableGen/Record.cpp =================================================================== --- llvm/lib/TableGen/Record.cpp +++ llvm/lib/TableGen/Record.cpp @@ -1800,6 +1800,9 @@ for (const RecordVal &Val : Class->getValues()) NewRec->addValue(Val); + // Copy assertions from class to instance. + NewRec->appendAssertions(Class); + // Substitute and resolve template arguments ArrayRef TArgs = Class->getTemplateArgs(); MapResolver R(NewRec); @@ -1828,6 +1831,9 @@ NewRec->resolveReferences(); Records.addDef(std::move(NewRecOwner)); + // Check the assertions. + NewRec->checkAssertions(); + Def = DefInit::get(NewRec); } @@ -2334,6 +2340,8 @@ // Re-register with RecordKeeper. setName(NewName); } + + // Resolve the field values. for (RecordVal &Value : Values) { if (SkipVal == &Value) // Skip resolve the same field as the given one continue; @@ -2354,6 +2362,14 @@ } } } + + // Resolve the assertion expressions. + for (auto &Assertion : Assertions) { + Init *Value = std::get<1>(Assertion)->resolveReferences(R); + std::get<1>(Assertion) = Value; + Value = std::get<2>(Assertion)->resolveReferences(R); + std::get<2>(Assertion) = Value; + } } void Record::resolveReferences(Init *NewName) { @@ -2595,6 +2611,21 @@ FieldName + "' does not have a dag initializer!"); } +// Check all record assertions: For each one, resolve the condition +// and message, then call CheckAssert(). +// Note: The condition and message are probably already resolved, +// but resolving again allows calls before records are resolved. +void Record::checkAssertions() { + RecordResolver R(*this); + R.setFinal(true); + + for (auto Assertion : 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 RecordKeeper::dump() const { errs() << *this; } #endif Index: llvm/lib/TableGen/TGParser.h =================================================================== --- llvm/lib/TableGen/TGParser.h +++ llvm/lib/TableGen/TGParser.h @@ -268,8 +268,6 @@ bool ApplyLetStack(RecordsEntry &Entry); bool CheckTemplateArgValues(SmallVectorImpl &Values, SMLoc Loc, Record *ArgsRec); - void CheckAssert(SMLoc Loc, Init *Condition, Init *Message); - void CheckRecordAsserts(Record &Rec); }; } // end namespace llvm Index: llvm/lib/TableGen/TGParser.cpp =================================================================== --- llvm/lib/TableGen/TGParser.cpp +++ llvm/lib/TableGen/TGParser.cpp @@ -258,6 +258,9 @@ ") of parent class '" + SC->getNameInitAsString() + "'"); } + // Copy the subclass record's assertions to the new record. + CurRec->appendAssertions(SC); + Init *Name; if (CurRec->isClass()) Name = @@ -448,8 +451,6 @@ Rec->resolveReferences(NewName); checkConcrete(*Rec); - CheckRecordAsserts(*Rec); - if (!isa(Rec->getNameInit())) { PrintError(Rec->getLoc(), Twine("record name '") + Rec->getNameInit()->getAsString() + @@ -457,6 +458,9 @@ return true; } + // Check the assertions. + Rec->checkAssertions(); + // If ObjectBody has template arguments, it's an error. assert(Rec->getTemplateArgs().empty() && "How'd this get template args?"); @@ -3640,37 +3644,6 @@ return false; } -// 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) Index: llvm/test/TableGen/assert.td =================================================================== --- llvm/test/TableGen/assert.td +++ llvm/test/TableGen/assert.td @@ -14,21 +14,21 @@ // CHECK: assertion failed // CHECK: note: first name is incorrect -def Rec1 { +def Rec01 { string name = "Fred Smith"; } -assert !eq(!substr(Rec1.name, 0, 3), "Jane"), - !strconcat("first name is incorrect: ", Rec1.name); +assert !eq(!substr(Rec01.name, 0, 3), "Jane"), + !strconcat("first name is incorrect: ", Rec01.name); // CHECK: assertion failed -// CHECK: note: record Rec2 is broken +// CHECK: note: record Rec02 is broken -def Rec2 { +def Rec02 { bit broken = true; } -assert !not(Rec2.broken), "record Rec2 is broken"; +assert !not(Rec02.broken), "record Rec02 is broken"; // CHECK: assertion failed // CHECK: note: cube of 9 @@ -94,5 +94,45 @@ // Test the assert statement in a class definition. +class PersonName { + assert !le(!size(name), 32), "person name is too long: " # name; + string Name = name; +} + +class Person : PersonName { + assert !and(!ge(age, 1), !le(age, 120)), + "person age is invalid: " # age; + int Age = age; +} + +def Rec20 : Person<"Donald Knuth", 60>; + +// CHECK: assertion failed +// CHECK: note: person name is too long + +def Rec21 : Person<"Donald Uh Oh This Name Is Too Long Knuth", 50>; + +// CHECK: assertion failed +// CHECK: note: person age is invalid + +def Rec22 : Person<"Donald Knuth", 150>; + +// Test the assert statement in an anonymous class invocation. + +def Rec30 { + string Name = Person<"Margaret Heafield Hamilton", 25>.Name; + int Age = Person<"Margaret Heafield Hamilton", 25>.Age; +} + +def Rec31 { + string Name = Person<"Margaret Heafield And More Middle Names Hamilton", 25>.Name; + int Age = Person<"Margaret Heafield Hamilton", 25>.Age; +} + +def Rec32 { + string Name = Person<"Margaret Heafield Hamilton", 25>.Name; + int Age = Person<"Margaret Heafield Hamilton", 0>.Age; +} + // Test the assert statement in a multiclass.