[TableGen] Add support for the 'assert' statement in multiclasses

This commit is contained in:
Paul C. Anagnostopoulos 2021-04-01 13:01:43 -04:00
parent 1206313f82
commit 3b9a15d910
6 changed files with 122 additions and 23 deletions

View File

@ -1282,7 +1282,9 @@ placement.
the subclasses and records that inherit from the class. The assertions are the subclasses and records that inherit from the class. The assertions are
then checked when the records are completely built. then checked when the records are completely built.
* In a multiclass definition, ... [this placement is not yet available] * In a multiclass definition, the assertions are saved with the other
components of the multiclass and then checked each time the multiclass
is instantiated with ``defm``.
Using assertions in TableGen files can simplify record checking in TableGen Using assertions in TableGen files can simplify record checking in TableGen
backends. Here is an example of an ``assert`` in two class definitions. backends. Here is an example of an ``assert`` in two class definitions.

View File

@ -1469,6 +1469,10 @@ inline raw_ostream &operator<<(raw_ostream &OS, const RecordVal &RV) {
} }
class Record { class Record {
public:
using AssertionTuple = std::tuple<SMLoc, Init *, Init *>;
private:
static unsigned LastID; static unsigned LastID;
Init *Name; Init *Name;
@ -1478,7 +1482,7 @@ class Record {
SmallVector<Init *, 0> TemplateArgs; SmallVector<Init *, 0> TemplateArgs;
SmallVector<RecordVal, 0> Values; SmallVector<RecordVal, 0> Values;
// Vector of [source location, condition Init, message Init]. // Vector of [source location, condition Init, message Init].
SmallVector<std::tuple<SMLoc, Init *, Init *>, 0> Assertions; SmallVector<AssertionTuple, 0> Assertions;
// All superclasses in the inheritance forest in post-order (yes, it // All superclasses in the inheritance forest in post-order (yes, it
// must be a forest; diamond-shaped inheritance is not allowed). // must be a forest; diamond-shaped inheritance is not allowed).
@ -1553,7 +1557,7 @@ public:
ArrayRef<RecordVal> getValues() const { return Values; } ArrayRef<RecordVal> getValues() const { return Values; }
ArrayRef<std::tuple<SMLoc, Init *, Init *>> getAssertions() const { ArrayRef<AssertionTuple> getAssertions() const {
return Assertions; return Assertions;
} }
@ -1620,7 +1624,7 @@ public:
Assertions.append(Rec->Assertions); Assertions.append(Rec->Assertions);
} }
void checkAssertions(); void checkRecordAssertions();
bool isSubClassOf(const Record *R) const { bool isSubClassOf(const Record *R) const {
for (const auto &SCPair : SuperClasses) for (const auto &SCPair : SuperClasses)

View File

@ -1832,7 +1832,7 @@ DefInit *VarDefInit::instantiate() {
Records.addDef(std::move(NewRecOwner)); Records.addDef(std::move(NewRecOwner));
// Check the assertions. // Check the assertions.
NewRec->checkAssertions(); NewRec->checkRecordAssertions();
Def = DefInit::get(NewRec); Def = DefInit::get(NewRec);
} }
@ -2615,7 +2615,7 @@ DagInit *Record::getValueAsDag(StringRef FieldName) const {
// and message, then call CheckAssert(). // and message, then call CheckAssert().
// Note: The condition and message are probably already resolved, // Note: The condition and message are probably already resolved,
// but resolving again allows calls before records are resolved. // but resolving again allows calls before records are resolved.
void Record::checkAssertions() { void Record::checkRecordAssertions() {
RecordResolver R(*this); RecordResolver R(*this);
R.setFinal(true); R.setFinal(true);

View File

@ -339,27 +339,38 @@ bool TGParser::AddSubMultiClass(MultiClass *CurMC,
return resolve(SMC->Entries, TemplateArgs, false, &CurMC->Entries); return resolve(SMC->Entries, TemplateArgs, false, &CurMC->Entries);
} }
/// Add a record or foreach loop to the current context (global record keeper, /// Add a record, foreach loop, or assertion to the current context.
/// current inner-most foreach loop, or multiclass).
bool TGParser::addEntry(RecordsEntry E) { bool TGParser::addEntry(RecordsEntry E) {
assert(!E.Rec || !E.Loop); assert((!!E.Rec + !!E.Loop + !!E.Assertion) == 1 &&
"RecordsEntry has invalid number of items");
// If we are parsing a loop, add it to the loop's entries.
if (!Loops.empty()) { if (!Loops.empty()) {
Loops.back()->Entries.push_back(std::move(E)); Loops.back()->Entries.push_back(std::move(E));
return false; return false;
} }
// If it is a loop, then resolve and perform the loop.
if (E.Loop) { if (E.Loop) {
SubstStack Stack; SubstStack Stack;
return resolve(*E.Loop, Stack, CurMultiClass == nullptr, return resolve(*E.Loop, Stack, CurMultiClass == nullptr,
CurMultiClass ? &CurMultiClass->Entries : nullptr); CurMultiClass ? &CurMultiClass->Entries : nullptr);
} }
// If we are parsing a multiclass, add it to the multiclass's entries.
if (CurMultiClass) { if (CurMultiClass) {
CurMultiClass->Entries.push_back(std::move(E)); CurMultiClass->Entries.push_back(std::move(E));
return false; return false;
} }
// If it is an assertion, then it's a top-level one, so check it.
if (E.Assertion) {
CheckAssert(std::get<0>(*E.Assertion), std::get<1>(*E.Assertion),
std::get<2>(*E.Assertion));
return false;
}
// It must be a record, so finish it off.
return addDefOne(std::move(E.Rec)); return addDefOne(std::move(E.Rec));
} }
@ -414,6 +425,24 @@ bool TGParser::resolve(const std::vector<RecordsEntry> &Source,
for (auto &E : Source) { for (auto &E : Source) {
if (E.Loop) { if (E.Loop) {
Error = resolve(*E.Loop, Substs, Final, Dest); Error = resolve(*E.Loop, Substs, Final, Dest);
} else if (E.Assertion) {
MapResolver R;
for (const auto &S : Substs)
R.set(S.first, S.second);
Init *Condition = std::get<1>(*E.Assertion)->resolveReferences(R);
Init *Message = std::get<2>(*E.Assertion)->resolveReferences(R);
if (Dest) {
std::unique_ptr<Record::AssertionTuple> tuple =
std::make_unique<Record::AssertionTuple>(std::get<0>(*E.Assertion),
std::move(Condition),
std::move(Message));
Dest->push_back(std::move(tuple));
} else {
CheckAssert(std::get<0>(*E.Assertion), Condition, Message);
}
} else { } else {
auto Rec = std::make_unique<Record>(*E.Rec); auto Rec = std::make_unique<Record>(*E.Rec);
if (Loc) if (Loc)
@ -459,7 +488,7 @@ bool TGParser::addDefOne(std::unique_ptr<Record> Rec) {
} }
// Check the assertions. // Check the assertions.
Rec->checkAssertions(); Rec->checkRecordAssertions();
// If ObjectBody has template arguments, it's an error. // If ObjectBody has template arguments, it's an error.
assert(Rec->getTemplateArgs().empty() && "How'd this get template args?"); assert(Rec->getTemplateArgs().empty() && "How'd this get template args?");
@ -2842,10 +2871,15 @@ bool TGParser::ApplyLetStack(Record *CurRec) {
return false; return false;
} }
/// Apply the current let bindings to the RecordsEntry.
bool TGParser::ApplyLetStack(RecordsEntry &Entry) { bool TGParser::ApplyLetStack(RecordsEntry &Entry) {
if (Entry.Rec) if (Entry.Rec)
return ApplyLetStack(Entry.Rec.get()); return ApplyLetStack(Entry.Rec.get());
// Let bindings are not applied to assertions.
if (Entry.Assertion)
return false;
for (auto &E : Entry.Loop->Entries) { for (auto &E : Entry.Loop->Entries) {
if (ApplyLetStack(E)) if (ApplyLetStack(E))
return true; return true;
@ -2889,8 +2923,8 @@ bool TGParser::ParseObjectBody(Record *CurRec) {
return ParseBody(CurRec); return ParseBody(CurRec);
} }
/// ParseDef - Parse and return a top level or multiclass def, return the record /// ParseDef - Parse and return a top level or multiclass record definition.
/// corresponding to it. This returns null on error. /// Return false if okay, true if error.
/// ///
/// DefInst ::= DEF ObjectName ObjectBody /// DefInst ::= DEF ObjectName ObjectBody
/// ///
@ -3184,12 +3218,12 @@ bool TGParser::ParseAssert(MultiClass *CurMultiClass, Record *CurRec) {
if (!consume(tgtok::semi)) if (!consume(tgtok::semi))
return TokError("expected ';'"); return TokError("expected ';'");
if (CurMultiClass) { if (CurRec) {
assert(false && "assert in multiclass not yet supported");
} else if (CurRec) {
CurRec->addAssertion(ConditionLoc, Condition, Message); CurRec->addAssertion(ConditionLoc, Condition, Message);
} else { // at top level } else {
CheckAssert(ConditionLoc, Condition, Message); std::unique_ptr<Record::AssertionTuple> tuple =
std::make_unique<Record::AssertionTuple>(ConditionLoc, Condition, Message);
addEntry(std::move(tuple));
} }
return false; return false;
@ -3328,10 +3362,13 @@ bool TGParser::ParseTopLevelLet(MultiClass *CurMultiClass) {
/// MultiClassInst ::= MULTICLASS ID TemplateArgList? /// MultiClassInst ::= MULTICLASS ID TemplateArgList?
/// ':' BaseMultiClassList '{' MultiClassObject+ '}' /// ':' BaseMultiClassList '{' MultiClassObject+ '}'
/// MultiClassObject ::= DefInst /// MultiClassObject ::= DefInst
/// MultiClassObject ::= MultiClassInst
/// MultiClassObject ::= DefMInst /// MultiClassObject ::= DefMInst
/// MultiClassObject ::= Defvar
/// MultiClassObject ::= Foreach
/// MultiClassObject ::= If
/// MultiClassObject ::= LETCommand '{' ObjectList '}' /// MultiClassObject ::= LETCommand '{' ObjectList '}'
/// MultiClassObject ::= LETCommand Object /// MultiClassObject ::= LETCommand Object
/// MultiClassObject ::= Assert
/// ///
bool TGParser::ParseMultiClass() { bool TGParser::ParseMultiClass() {
assert(Lex.getCode() == tgtok::MultiClass && "Unexpected token"); assert(Lex.getCode() == tgtok::MultiClass && "Unexpected token");
@ -3396,8 +3433,6 @@ bool TGParser::ParseMultiClass() {
default: default:
return TokError("expected 'assert', 'def', 'defm', 'defvar', " return TokError("expected 'assert', 'def', 'defm', 'defvar', "
"'foreach', 'if', or 'let' in multiclass body"); "'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::Def:
case tgtok::Defm: case tgtok::Defm:
@ -3405,6 +3440,7 @@ bool TGParser::ParseMultiClass() {
case tgtok::Foreach: case tgtok::Foreach:
case tgtok::If: case tgtok::If:
case tgtok::Let: case tgtok::Let:
case tgtok::Assert:
if (ParseObject(CurMultiClass)) if (ParseObject(CurMultiClass))
return true; return true;
break; break;
@ -3564,7 +3600,7 @@ bool TGParser::ParseObject(MultiClass *MC) {
default: default:
return TokError( return TokError(
"Expected assert, class, def, defm, defset, foreach, if, or let"); "Expected assert, class, def, defm, defset, foreach, if, or let");
case tgtok::Assert: return ParseAssert(MC, nullptr); case tgtok::Assert: return ParseAssert(MC);
case tgtok::Def: return ParseDef(MC); case tgtok::Def: return ParseDef(MC);
case tgtok::Defm: return ParseDefm(MC); case tgtok::Defm: return ParseDefm(MC);
case tgtok::Defvar: return ParseDefvar(); case tgtok::Defvar: return ParseDefvar();

View File

@ -36,10 +36,12 @@ namespace llvm {
} }
}; };
/// RecordsEntry - Can be either a record or a foreach loop. /// RecordsEntry - Holds exactly one of a Record, ForeachLoop, or
/// assertion tuple.
struct RecordsEntry { struct RecordsEntry {
std::unique_ptr<Record> Rec; std::unique_ptr<Record> Rec;
std::unique_ptr<ForeachLoop> Loop; std::unique_ptr<ForeachLoop> Loop;
std::unique_ptr<Record::AssertionTuple> Assertion;
void dump() const; void dump() const;
@ -47,6 +49,8 @@ namespace llvm {
RecordsEntry(std::unique_ptr<Record> Rec) : Rec(std::move(Rec)) {} RecordsEntry(std::unique_ptr<Record> Rec) : Rec(std::move(Rec)) {}
RecordsEntry(std::unique_ptr<ForeachLoop> Loop) RecordsEntry(std::unique_ptr<ForeachLoop> Loop)
: Loop(std::move(Loop)) {} : Loop(std::move(Loop)) {}
RecordsEntry(std::unique_ptr<Record::AssertionTuple> Assertion)
: Assertion(std::move(Assertion)) {}
}; };
/// ForeachLoop - Record the iteration state associated with a for loop. /// ForeachLoop - Record the iteration state associated with a for loop.
@ -222,7 +226,7 @@ private: // Parser methods.
bool ParseForeach(MultiClass *CurMultiClass); bool ParseForeach(MultiClass *CurMultiClass);
bool ParseIf(MultiClass *CurMultiClass); bool ParseIf(MultiClass *CurMultiClass);
bool ParseIfBody(MultiClass *CurMultiClass, StringRef Kind); bool ParseIfBody(MultiClass *CurMultiClass, StringRef Kind);
bool ParseAssert(MultiClass *CurMultiClass, Record *CurRec); bool ParseAssert(MultiClass *CurMultiClass, Record *CurRec = nullptr);
bool ParseTopLevelLet(MultiClass *CurMultiClass); bool ParseTopLevelLet(MultiClass *CurMultiClass);
void ParseLetList(SmallVectorImpl<LetRecord> &Result); void ParseLetList(SmallVectorImpl<LetRecord> &Result);

View File

@ -39,6 +39,15 @@ class Cube<int n> {
assert !eq(Cube<9>.result, 81), "cube of 9 should be 729"; assert !eq(Cube<9>.result, 81), "cube of 9 should be 729";
// CHECK: assertion failed
// CHECK: note: foreach i cannot be 2
// CHECK-NOT: note: foreach i cannot be 2
foreach i = 1...3 in {
assert !ne(i, 2), "foreach i cannot be 2";
def bar_ # i;
}
// Test the assert statement in a record definition. // Test the assert statement in a record definition.
// CHECK: assertion failed // CHECK: assertion failed
@ -136,3 +145,47 @@ def Rec32 {
// Test the assert statement in a multiclass. // Test the assert statement in a multiclass.
// CHECK: assertion failed
// CHECK: note: MC1 id string is too long
// CHECK: assertion failed
// CHECK: note: MC1 seq is too high
multiclass MC1<string id, int seq> {
assert !le(!size(id), 5), "MC1 id string is too long";
assert !le(seq, 999999), "MC1 seq is too high";
def _mc1 {
string ID = id;
int Seq = seq;
}
}
defm Rec40 : MC1<"ILISP", 999>;
defm Rec41 : MC1<"ILISPX", 999>;
defm Rec42 : MC1<"ILISP", 999999999>;
// CHECK: assertion failed
// CHECK: note: MC2 phrase must be secret: secrex code
multiclass MC2<string phr> {
assert !eq(!substr(phr, 0, 6), "secret"), "MC2 phrase must be secret: " # phr;
def _mc2 {
string phrase = phr;
}
}
multiclass MC3<string phr> {
defm _mc3 : MC2<phr>;
}
defm Rec43 : MC3<"secrex code">;
// CHECK: assertion failed
// CHECK: note: MC2 phrase must be secret: xecret code
multiclass MC4<string phr> : MC2<phr> {
def _def;
}
defm Rec44 : MC4<"xecret code">;