diff --git a/llvm/include/llvm/TableGen/Record.h b/llvm/include/llvm/TableGen/Record.h --- a/llvm/include/llvm/TableGen/Record.h +++ b/llvm/include/llvm/TableGen/Record.h @@ -708,6 +708,7 @@ /// Init *resolveReferences(Resolver &R) const override; + bool isComplete() const override; bool isConcrete() const override; std::string getAsString() const override; @@ -755,7 +756,7 @@ /// class UnOpInit : public OpInit, public FoldingSetNode { public: - enum UnaryOp : uint8_t { CAST, HEAD, TAIL, SIZE, EMPTY, GETOP }; + enum UnaryOp : uint8_t { CAST, HEAD, TAIL, SIZE, EMPTY, GETOP, ISCOMPLETE }; private: Init *LHS; diff --git a/llvm/lib/TableGen/Record.cpp b/llvm/lib/TableGen/Record.cpp --- a/llvm/lib/TableGen/Record.cpp +++ b/llvm/lib/TableGen/Record.cpp @@ -658,6 +658,14 @@ return const_cast(this); } +bool ListInit::isComplete() const { + for (Init *Element : *this) { + if (!Element->isComplete()) + return false; + } + return true; +} + bool ListInit::isConcrete() const { for (Init *Element : *this) { if (!Element->isConcrete()) @@ -803,6 +811,10 @@ } } break; + + case ISCOMPLETE: + if (LHS->isConcrete()) + return IntInit::get(LHS->isComplete()); } return const_cast(this); } @@ -825,6 +837,7 @@ case SIZE: Result = "!size"; break; case EMPTY: Result = "!empty"; break; case GETOP: Result = "!getop"; break; + case ISCOMPLETE: Result = "!iscomplete"; break; } return Result + "(" + LHS->getAsString() + ")"; } @@ -1772,7 +1785,7 @@ FieldName->getAsUnquotedString() + "' of '" + Rec->getAsString() + "' is a forbidden self-reference"); Init *FieldVal = Def->getValue(FieldName)->getValue(); - if (FieldVal->isComplete()) + if (FieldVal->isConcrete()) return FieldVal; } return const_cast(this); diff --git a/llvm/lib/TableGen/TGLexer.h b/llvm/lib/TableGen/TGLexer.h --- a/llvm/lib/TableGen/TGLexer.h +++ b/llvm/lib/TableGen/TGLexer.h @@ -53,6 +53,7 @@ XConcat, XADD, XMUL, XAND, XOR, XSRA, XSRL, XSHL, XListConcat, XListSplat, XStrConcat, XCast, XSubst, XForEach, XFoldl, XHead, XTail, XSize, XEmpty, XIf, XCond, XEq, XIsA, XDag, XNe, XLe, XLt, XGe, XGt, XSetOp, XGetOp, + XIsComplete, // Integer value. IntVal, diff --git a/llvm/lib/TableGen/TGLexer.cpp b/llvm/lib/TableGen/TGLexer.cpp --- a/llvm/lib/TableGen/TGLexer.cpp +++ b/llvm/lib/TableGen/TGLexer.cpp @@ -565,6 +565,7 @@ .Case("strconcat", tgtok::XStrConcat) .Case("setop", tgtok::XSetOp) .Case("getop", tgtok::XGetOp) + .Case("iscomplete", tgtok::XIsComplete) .Default(tgtok::Error); return Kind != tgtok::Error ? Kind : ReturnError(Start-1, "Unknown operator"); diff --git a/llvm/lib/TableGen/TGParser.cpp b/llvm/lib/TableGen/TGParser.cpp --- a/llvm/lib/TableGen/TGParser.cpp +++ b/llvm/lib/TableGen/TGParser.cpp @@ -914,7 +914,8 @@ case tgtok::XSize: case tgtok::XEmpty: case tgtok::XCast: - case tgtok::XGetOp: { // Value ::= !unop '(' Value ')' + case tgtok::XGetOp: + case tgtok::XIsComplete: { // Value ::= !unop '(' Value ')' UnOpInit::UnaryOp Code; RecTy *Type = nullptr; @@ -950,6 +951,11 @@ Code = UnOpInit::EMPTY; Type = IntRecTy::get(); break; + case tgtok::XIsComplete: + Lex.Lex(); // eat the operation + Code = UnOpInit::ISCOMPLETE; + Type = IntRecTy::get(); + break; case tgtok::XGetOp: Lex.Lex(); // eat the operation if (Lex.getCode() == tgtok::less) { @@ -2140,10 +2146,10 @@ case tgtok::XCond: case tgtok::XFoldl: case tgtok::XForEach: - case tgtok::XSubst: { // Value ::= !ternop '(' Value ',' Value ',' Value ')' + case tgtok::XSubst: // Value ::= !ternop '(' Value ',' Value ',' Value ')' + case tgtok::XIsComplete: return ParseOperation(CurRec, ItemType); } - } return R; } diff --git a/llvm/test/TableGen/field-access-initializers.td b/llvm/test/TableGen/field-access-initializers.td --- a/llvm/test/TableGen/field-access-initializers.td +++ b/llvm/test/TableGen/field-access-initializers.td @@ -8,7 +8,7 @@ // CHECK: } // CHECK: def B1 { -// CHECK: string value = A1.value; +// CHECK: string value = ?; // CHECK: } class A { diff --git a/llvm/test/TableGen/isa-consistency.td b/llvm/test/TableGen/isa-consistency.td new file mode 100644 --- /dev/null +++ b/llvm/test/TableGen/isa-consistency.td @@ -0,0 +1,70 @@ +// RUN: llvm-tblgen %s | FileCheck %s + +// This file is documenting some strange inconsistencies in the behaviour of +// isa<>() that were encountered while attempting to use it to distinguish +// `0` from `?`. In particular, Z4 and Z5 behave inconsistently compared to the +// others. +// XFAIL: * + +// CHECK: def Z1 { +// CHECK: int ret = 1; +// CHECK: } + +def Z1 { + bit A = ?; + int ret = !isa(A); +} + +// CHECK: def Z2 { +// CHECK: int ret = 1; +// CHECK: } + +def Z2 { + bits<1> A = {?}; + int ret = !isa(A{0}); +} + +// CHECK: def Z3 { +// CHECK: int ret = 1; +// CHECK: } + +def Z3 { + int A = ?; + int ret = !isa(A); +} + +// So far so good, but it doesn't work without the field assignment. +// CHECK: def Z4 { +// CHECK: int ret = 1; +// CHECK: } + +def Z4 { + int ret = !isa(!cast(?)); +} + +// CHECK: def Z5 { +// CHECK: int ret = 1; +// CHECK: } + +def Z5 { + int ret = !isa(!cast(?)); +} + +// Yet it does work on bits<>, even though it didn't work on bit. +// CHECK: def Z6 { +// CHECK: int ret = 1; +// CHECK: } + +def Z6 { + int ret = !isa({?, ?}); +} + +// Stranger still, if you extract an unset bit from a bits<> it still works even +// though bit didn't. +// CHECK: def Z7 { +// CHECK: int ret = 1; +// CHECK: } + +def Z7 { + int ret = !isa({?, ?}{0}); +} diff --git a/llvm/test/TableGen/iscomplete.td b/llvm/test/TableGen/iscomplete.td new file mode 100644 --- /dev/null +++ b/llvm/test/TableGen/iscomplete.td @@ -0,0 +1,164 @@ +// RUN: llvm-tblgen %s | FileCheck %s + +def op; + +class DefTy { + int A = a; +} + +def Inputs { + string StringA = ""; + string StringB = "foo"; + string StringC = ?; + code CodeA = [{}]; + code CodeB = [{foo}]; + code CodeC = ?; + bit BitA = 0; + bit BitB = 1; + bit BitC = ?; + int IntA = 0; + int IntB = 1; + int IntC = ?; + dag DagA = (op); + dag DagB = (op 1); + dag DagC = ?; + bits<2> Bit2A = {0, 0}; + bits<2> Bit2B = {0, 1}; + bits<2> Bit2C = ?; + bits<2> Bit2D = { 0, ? }; + list LstBitA = [0, 0]; + list LstBitB = [0, 1]; + list LstBitC = ?; + list LstBitD = [ ?, 0 ]; + DefTy DefA = DefTy<0>; + DefTy DefB = DefTy<1>; + DefTy DefC = ?; +} + +// CHECK-LABEL: def Z1Strings { +// CHECK-NEXT: bit A = 1; +// CHECK-NEXT: bit B = 1; +// CHECK-NEXT: bit C = 0; +// CHECK-NEXT: } + +def Z1Strings { + bit A = !iscomplete(Inputs.StringA); + bit B = !iscomplete(Inputs.StringB); + bit C = !iscomplete(Inputs.StringC); +} + +// CHECK-LABEL: def Z2Codes { +// CHECK-NEXT: bit A = 1; +// CHECK-NEXT: bit B = 1; +// CHECK-NEXT: bit C = 0; +// CHECK-NEXT: } + +def Z2Codes { + bit A = !iscomplete(Inputs.CodeA); + bit B = !iscomplete(Inputs.CodeB); + bit C = !iscomplete(Inputs.CodeC); +} + +// CHECK-LABEL: def Z3Bits { +// CHECK-NEXT: bit A = 1; +// CHECK-NEXT: bit B = 1; +// CHECK-NEXT: bit C = 0; +// CHECK-NEXT: } + +def Z3Bits { + bit A = !iscomplete(Inputs.BitA); + bit B = !iscomplete(Inputs.BitB); + bit C = !iscomplete(Inputs.BitC); +} + +// CHECK-LABEL: def Z4Ints { +// CHECK-NEXT: bit A = 1; +// CHECK-NEXT: bit B = 1; +// CHECK-NEXT: bit C = 0; +// CHECK-NEXT: } + +def Z4Ints { + bit A = !iscomplete(Inputs.IntA); + bit B = !iscomplete(Inputs.IntB); + bit C = !iscomplete(Inputs.IntC); +} + +// CHECK-LABEL: def Z5Dags { +// CHECK-NEXT: bit A = 1; +// CHECK-NEXT: bit B = 1; +// CHECK-NEXT: bit C = 0; +// CHECK-NEXT: } + +def Z5Dags { + bit A = !iscomplete(Inputs.DagA); + bit B = !iscomplete(Inputs.DagB); + bit C = !iscomplete(Inputs.DagC); +} + +// CHECK-LABEL: def Z6Bit2s { +// CHECK-NEXT: bit A = 1; +// CHECK-NEXT: bit B = 1; +// CHECK-NEXT: bit C = 0; +// CHECK-NEXT: bit D = 0; +// CHECK-NEXT: bit E = 1; +// CHECK-NEXT: } + +def Z6Bit2s { + bit A = !iscomplete(Inputs.Bit2A); + bit B = !iscomplete(Inputs.Bit2B); + bit C = !iscomplete(Inputs.Bit2C); + // Test these two explicitly because unset is not convertible to int but an + // unset bit extracted from a a bits<> is convertible to int. + bit D = !iscomplete(Inputs.Bit2D{0}); + bit E = !iscomplete(Inputs.Bit2D{1}); +} + +// CHECK-LABEL: def Z7LstBits { +// CHECK-NEXT: bit A = 1; +// CHECK-NEXT: bit B = 1; +// CHECK-NEXT: bit C = 0; +// CHECK-NEXT: bit D = 0; +// CHECK-NEXT: bit E = 1; +// CHECK-NEXT: } + +def Z7LstBits { + bit A = !iscomplete(Inputs.LstBitA); + bit B = !iscomplete(Inputs.LstBitB); + bit C = !iscomplete(Inputs.LstBitC); + // Test these two explicitly because unset is not convertible to int but an + // unset bit extracted from a a bits<> is convertible to int. + bit D = !iscomplete(Inputs.LstBitD[0]); + bit E = !iscomplete(Inputs.LstBitD[1]); +} + +// CHECK-LABEL: def Z8Defs { +// CHECK-NEXT: bit A = 1; +// CHECK-NEXT: bit B = 1; +// CHECK-NEXT: bit C = 0; +// CHECK-NEXT: } + +def Z8Defs { + bit A = !iscomplete(Inputs.DefA); + bit B = !iscomplete(Inputs.DefB); + bit C = !iscomplete(Inputs.DefC); +} + +// CHECK-LABEL: def Z9OriginalBug { +// CHECK-NEXT: bit A = 1; +// CHECK-NEXT: bit B = 1; +// CHECK-NEXT: bit C = 1; +// CHECK-NEXT: } + +def Z9OriginalBug { + // These are the expressions that caused the original problem. Because bits<> + // is convertible to int and squashes the `?`s to 0, these both evaluate to 1. + bit A = !if(!eq(Inputs.Bit2A{0}, 0), 1, 0); + bit B = !if(!eq(Inputs.Bit2A{1}, 0), 1, 0); + // This one also evaluates to 1 because BitA's value is a BitInit + bit C = !if(!eq(Inputs.BitA, 0), 1, 0); + // This one however, caused an assertion because BitC's value an UnsetInit + // and !eq() can only accept TypedInit's. This still doesn't work but it + // at least emits a proper error. + // bit D = !if(!eq(Inputs.BitC, 0), 1, 0); +} +