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:
parent
a33e47faab
commit
d74e81058e
@ -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
|
||||
==================
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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
98
test/TableGen/assert.td
Normal 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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user