1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2025-01-31 12:41:49 +01:00

[TableGen] Add the assert statement, step 1

Differential Revision: https://reviews.llvm.org/D93911

This first step adds the assert statement and supports it at top level
and in record definitions. Later steps will support it in class
definitions and multiclasses.
This commit is contained in:
Paul C. Anagnostopoulos 2020-12-16 16:25:54 -05:00
parent a33e47faab
commit d74e81058e
7 changed files with 250 additions and 29 deletions

View File

@ -194,11 +194,11 @@ numeric literal rather than an identifier.
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 @@ files.
.. 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 @@ name of a multiclass.
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 @@ when the bodies are finished (see `Defvar in a Record Body`_ for more details).
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
==================

View File

@ -1445,6 +1445,8 @@ class Record {
SmallVector<SMLoc, 4> Locs;
SmallVector<Init *, 0> TemplateArgs;
SmallVector<RecordVal, 0> Values;
// Vector of [source location, condition Init, message Init].
SmallVector<std::tuple<SMLoc, Init *, Init *>, 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 @@ public:
ArrayRef<RecordVal> getValues() const { return Values; }
ArrayRef<std::tuple<SMLoc, Init *, Init *>> getAssertions() const {
return Assertions;
}
ArrayRef<std::pair<Record *, SMRange>> getSuperClasses() const {
return SuperClasses;
}
@ -1576,6 +1582,10 @@ public:
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)

View File

@ -365,6 +365,7 @@ tgtok::TokKind TGLexer::LexIdentifier() {
.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.

View File

@ -47,8 +47,8 @@ namespace tgtok {
// 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.

View File

@ -452,6 +452,8 @@ bool TGParser::addDefOne(std::unique_ptr<Record> Rec) {
Rec->resolveReferences();
checkConcrete(*Rec);
CheckRecordAsserts(*Rec);
if (!isa<StringInit>(Rec->getNameInit())) {
PrintError(Rec->getLoc(), Twine("record name '") +
Rec->getNameInit()->getAsString() +
@ -482,11 +484,12 @@ bool TGParser::addDefOne(std::unique_ptr<Record> Rec) {
// 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 @@ RecTy *TGParser::ParseType() {
}
}
/// 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 @@ Init *TGParser::ParseSimpleValue(Record *CurRec, RecTy *ItemType,
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 @@ bool TGParser::ParseTemplateArgList(Record *CurRec) {
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 @@ bool TGParser::ParseIfBody(MultiClass *CurMultiClass, StringRef Kind) {
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 @@ bool TGParser::ParseMultiClass() {
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 @@ bool TGParser::ParseDefm(MultiClass *CurMultiClass) {
/// 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 @@ bool TGParser::ParseFile() {
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<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)

View File

@ -222,6 +222,7 @@ private: // Parser methods.
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<LetRecord> &Result);
@ -263,6 +264,8 @@ private: // Parser methods.
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

98
test/TableGen/assert.td Normal file
View File

@ -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 n> {
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.