[llvm] 6e2b635 - [TableGen] Add the assert statement, step 1

Paul C. Anagnostopoulos via llvm-commits llvm-commits at lists.llvm.org
Fri Jan 8 06:48:26 PST 2021


Author: Paul C. Anagnostopoulos
Date: 2021-01-08T09:47:51-05:00
New Revision: 6e2b6351d2cb1feaa88e6c92ba844ab48b4758f9

URL: https://github.com/llvm/llvm-project/commit/6e2b6351d2cb1feaa88e6c92ba844ab48b4758f9
DIFF: https://github.com/llvm/llvm-project/commit/6e2b6351d2cb1feaa88e6c92ba844ab48b4758f9.diff

LOG: [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.

Added: 
    llvm/test/TableGen/assert.td

Modified: 
    llvm/docs/TableGen/ProgRef.rst
    llvm/include/llvm/TableGen/Record.h
    llvm/lib/TableGen/TGLexer.cpp
    llvm/lib/TableGen/TGLexer.h
    llvm/lib/TableGen/TGParser.cpp
    llvm/lib/TableGen/TGParser.h

Removed: 
    


################################################################################
diff  --git a/llvm/docs/TableGen/ProgRef.rst b/llvm/docs/TableGen/ProgRef.rst
index f2ee7a7e549a..f5a7760b2885 100644
--- a/llvm/docs/TableGen/ProgRef.rst
+++ b/llvm/docs/TableGen/ProgRef.rst
@@ -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
 ==================
 

diff  --git a/llvm/include/llvm/TableGen/Record.h b/llvm/include/llvm/TableGen/Record.h
index 8dea76fb64a2..2853a471b67f 100644
--- a/llvm/include/llvm/TableGen/Record.h
+++ b/llvm/include/llvm/TableGen/Record.h
@@ -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 @@ class Record {
 
   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 @@ class Record {
     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)

diff  --git a/llvm/lib/TableGen/TGLexer.cpp b/llvm/lib/TableGen/TGLexer.cpp
index a45ef6dc10c1..94c79102c7cd 100644
--- a/llvm/lib/TableGen/TGLexer.cpp
+++ b/llvm/lib/TableGen/TGLexer.cpp
@@ -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.

diff  --git a/llvm/lib/TableGen/TGLexer.h b/llvm/lib/TableGen/TGLexer.h
index ee568849ca88..2f322f705e0d 100644
--- a/llvm/lib/TableGen/TGLexer.h
+++ b/llvm/lib/TableGen/TGLexer.h
@@ -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.

diff  --git a/llvm/lib/TableGen/TGParser.cpp b/llvm/lib/TableGen/TGParser.cpp
index c217c0115bc3..9f3c69bab89e 100644
--- a/llvm/lib/TableGen/TGParser.cpp
+++ b/llvm/lib/TableGen/TGParser.cpp
@@ -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)

diff  --git a/llvm/lib/TableGen/TGParser.h b/llvm/lib/TableGen/TGParser.h
index 3ed78a23067f..578a56c9d01c 100644
--- a/llvm/lib/TableGen/TGParser.h
+++ b/llvm/lib/TableGen/TGParser.h
@@ -222,6 +222,7 @@ class TGParser {
   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 @@ class TGParser {
   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

diff  --git a/llvm/test/TableGen/assert.td b/llvm/test/TableGen/assert.td
new file mode 100644
index 000000000000..858ed9a9e92e
--- /dev/null
+++ b/llvm/test/TableGen/assert.td
@@ -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.
+


        


More information about the llvm-commits mailing list