Skip to content

Commit

Permalink
[TableGen] Add support for the 'assert' statement in class definitions.
Browse files Browse the repository at this point in the history
Differential Revision: https://reviews.llvm.org/D99275
  • Loading branch information
Paul C. Anagnostopoulos committed Mar 29, 2021
1 parent 25fbe80 commit 5f473a0
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 44 deletions.
24 changes: 21 additions & 3 deletions llvm/docs/TableGen/ProgRef.rst
Expand Up @@ -1272,12 +1272,30 @@ placement.
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<string name> {
assert !le(!size(name), 32), "person name is too long: " # name;
string Name = name;
}
class Person<string name, int age> : PersonName<name> {
assert !and(!ge(age, 1), !le(age, 120)), "person age is invalid: " # age;
int Age = age;
}
def Rec20 : Person<"Donald Knuth", 60> {
...
}
Additional Details
==================
Expand Down
2 changes: 2 additions & 0 deletions llvm/include/llvm/TableGen/Error.h
Expand Up @@ -48,6 +48,8 @@ LLVM_ATTRIBUTE_NORETURN void PrintFatalError(const Record *Rec,
LLVM_ATTRIBUTE_NORETURN void PrintFatalError(const RecordVal *RecVal,
const Twine &Msg);

void CheckAssert(SMLoc Loc, Init *Condition, Init *Message);

extern SourceMgr SrcMgr;
extern unsigned ErrorsPrinted;

Expand Down
6 changes: 6 additions & 0 deletions llvm/include/llvm/TableGen/Record.h
Expand Up @@ -1616,6 +1616,12 @@ class Record {
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)
Expand Down
16 changes: 16 additions & 0 deletions llvm/lib/TableGen/Error.cpp
Expand Up @@ -154,4 +154,20 @@ void PrintFatalError(const RecordVal *RecVal, const Twine &Msg) {
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<IntInit>(
Condition->convertInitializerTo(IntRecTy::get()));
if (!CondValue)
PrintError(Loc, "assert condition must of type bit, bits, or int.");
else if (!CondValue->getValue()) {
PrintError(Loc, "assertion failed");
if (auto *MessageInit = dyn_cast<StringInit>(Message))
PrintNote(MessageInit->getValue());
else
PrintNote("(assert message is not a string)");
}
}

} // end namespace llvm
31 changes: 31 additions & 0 deletions llvm/lib/TableGen/Record.cpp
Expand Up @@ -1800,6 +1800,9 @@ DefInit *VarDefInit::instantiate() {
for (const RecordVal &Val : Class->getValues())
NewRec->addValue(Val);

// Copy assertions from class to instance.
NewRec->appendAssertions(Class);

// Substitute and resolve template arguments
ArrayRef<Init *> TArgs = Class->getTemplateArgs();
MapResolver R(NewRec);
Expand Down Expand Up @@ -1828,6 +1831,9 @@ DefInit *VarDefInit::instantiate() {
NewRec->resolveReferences();
Records.addDef(std::move(NewRecOwner));

// Check the assertions.
NewRec->checkAssertions();

Def = DefInit::get(NewRec);
}

Expand Down Expand Up @@ -2334,6 +2340,8 @@ void Record::resolveReferences(Resolver &R, const RecordVal *SkipVal) {
// 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;
Expand All @@ -2354,6 +2362,14 @@ void Record::resolveReferences(Resolver &R, const RecordVal *SkipVal) {
}
}
}

// 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) {
Expand Down Expand Up @@ -2595,6 +2611,21 @@ DagInit *Record::getValueAsDag(StringRef FieldName) const {
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
Expand Down
39 changes: 6 additions & 33 deletions llvm/lib/TableGen/TGParser.cpp
Expand Up @@ -258,6 +258,9 @@ bool TGParser::AddSubClass(Record *CurRec, SubClassReference &SubClass) {
") of parent class '" + SC->getNameInitAsString() + "'");
}

// Copy the subclass record's assertions to the new record.
CurRec->appendAssertions(SC);

Init *Name;
if (CurRec->isClass())
Name =
Expand Down Expand Up @@ -448,15 +451,16 @@ bool TGParser::addDefOne(std::unique_ptr<Record> Rec) {
Rec->resolveReferences(NewName);
checkConcrete(*Rec);

CheckRecordAsserts(*Rec);

if (!isa<StringInit>(Rec->getNameInit())) {
PrintError(Rec->getLoc(), Twine("record name '") +
Rec->getNameInit()->getAsString() +
"' could not be fully resolved");
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?");

Expand Down Expand Up @@ -3640,37 +3644,6 @@ bool TGParser::CheckTemplateArgValues(SmallVectorImpl<llvm::Init *> &Values,
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<IntInit>(
Condition->convertInitializerTo(IntRecTy::get()));
if (CondValue) {
if (!CondValue->getValue()) {
PrintError(Loc, "assertion failed");
if (auto *MessageInit = dyn_cast<StringInit>(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)
Expand Down
2 changes: 0 additions & 2 deletions llvm/lib/TableGen/TGParser.h
Expand Up @@ -268,8 +268,6 @@ class TGParser {
bool ApplyLetStack(RecordsEntry &Entry);
bool CheckTemplateArgValues(SmallVectorImpl<llvm::Init *> &Values,
SMLoc Loc, Record *ArgsRec);
void CheckAssert(SMLoc Loc, Init *Condition, Init *Message);
void CheckRecordAsserts(Record &Rec);
};

} // end namespace llvm
Expand Down
52 changes: 46 additions & 6 deletions llvm/test/TableGen/assert.td
Expand Up @@ -14,21 +14,21 @@ assert !le(!size(Name), 20), "primary name is too long: " # Name;
// 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
Expand Down Expand Up @@ -94,5 +94,45 @@ def Rec14 : Cube<3> {

// Test the assert statement in a class definition.

class PersonName<string name> {
assert !le(!size(name), 32), "person name is too long: " # name;
string Name = name;
}

class Person<string name, int age> : PersonName<name> {
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.

0 comments on commit 5f473a0

Please sign in to comment.