diff --git a/llvm/docs/TableGen/ProgRef.rst b/llvm/docs/TableGen/ProgRef.rst --- a/llvm/docs/TableGen/ProgRef.rst +++ b/llvm/docs/TableGen/ProgRef.rst @@ -221,14 +221,14 @@ BangOperator: one of : !add !and !cast !con !dag : !div !empty !eq !exists !filter - : !find !foldl !foreach !ge !getdagarg - : !getdagname !getdagop !gt !head !if - : !interleave !isa !le !listconcat !listremove - : !listsplat !logtwo !lt !mul !ne - : !not !or !range !setdagarg !setdagname - : !setdagop !shl !size !sra !srl - : !strconcat !sub !subst !substr !tail - : !tolower !toupper !xor + : !find !foldl !foreach !format !ge + : !getdagarg !getdagname !getdagop !gt !head + : !if !interleave !isa !le !listconcat + : !listremove !listsplat !logtwo !lt !mul + : !ne !not !or !range !setdagarg + : !setdagname !setdagop !shl !size !sra + : !srl !strconcat !sub !subst !substr + : !tail !tolower !toupper !xor The ``!cond`` operator has a slightly different syntax compared to other bang operators, so it is defined separately: @@ -1728,6 +1728,12 @@ If you simply want to create a list of a certain length containing the same value repeated multiple times, see ``!listsplat``. +``!format(``\ *fmt*\ ``,`` *args*\ ``, ...)`` + Format ``args`` according to the format string ``fmt``, and return the + result as a string. ``fmt`` can be a code fragment. + The placeholder are like ``{i}``, which means it will be replaced by the + ``i``-th argument. + ``!ge(``\ *a*\ `,` *b*\ ``)`` This operator produces 1 if *a* is greater than or equal to *b*; 0 otherwise. The arguments must be ``bit``, ``bits``, ``int``, or ``string`` values. 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 @@ -312,6 +312,7 @@ IK_FoldOpInit, IK_IsAOpInit, IK_ExistsOpInit, + IK_FormatOpInit, IK_AnonymousNameInit, IK_StringInit, IK_VarInit, @@ -1232,6 +1233,51 @@ std::string getAsString() const override; }; +/// !format(fmt, args, ...) - Format `args` according to `fmt`. +class FormatOpInit final : public TypedInit, + public FoldingSetNode, + public TrailingObjects { +private: + Init *Fmt; + unsigned NumArgs; + + FormatOpInit(Init *Fmt, unsigned NumArgs) + : TypedInit(IK_FormatOpInit, StringRecTy::get(Fmt->getRecordKeeper())), + Fmt(Fmt), NumArgs(NumArgs) {} + +public: + FormatOpInit(const FormatOpInit &) = delete; + FormatOpInit &operator=(const FormatOpInit &) = delete; + + static bool classof(const Init *I) { return I->getKind() == IK_FormatOpInit; } + + static FormatOpInit *get(Init *Fmt, ArrayRef Args); + + void Profile(FoldingSetNodeID &ID) const; + Init *Fold(Record *CurRec) const; + + bool isComplete() const override; + bool isConcrete() const override; + Init *resolveReferences(Resolver &R) const override; + Init *getBit(unsigned Bit) const override; + std::string getAsString() const override; + + Init *getArg(unsigned I) const { + assert(I < NumArgs && "Argument index out of range!"); + return getTrailingObjects()[I]; + } + + using const_iterator = Init *const *; + + const_iterator args_begin() const { return getTrailingObjects(); } + const_iterator args_end() const { return args_begin() + NumArgs; } + + size_t args_size() const { return NumArgs; } + bool args_empty() const { return NumArgs == 0; } + + ArrayRef args() const { return ArrayRef(args_begin(), NumArgs); } +}; + /// 'Opcode' - Represent a reference to an entire variable object. class VarInit : public TypedInit { Init *VarName; 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 @@ -82,6 +82,7 @@ FoldingSet TheFoldOpInitPool; FoldingSet TheIsAOpInitPool; FoldingSet TheExistsOpInitPool; + FoldingSet TheFormatOpInitPool; DenseMap, VarInit *> TheVarInitPool; DenseMap, VarBitInit *> TheVarBitInitPool; FoldingSet TheVarDefInitPool; @@ -1611,6 +1612,21 @@ return nullptr; } +static std::string replaceAll(const std::string &Target, const std::string &Old, + const std::string &New) { + std::string Res = std::string(Target); + std::string::size_type Found; + std::string::size_type Index = 0; + while (true) { + Found = Res.find(Old, Index); + if (Found == std::string::npos) + break; + Res.replace(Found, Old.size(), New); + Index = Found + New.size(); + } + return Res; +} + Init *TernOpInit::Fold(Record *CurRec) const { RecordKeeper &RK = getRecordKeeper(); switch (getOpcode()) { @@ -1639,22 +1655,11 @@ Val = std::string(MHSv->getName()); return VarInit::get(Val, getType()); } - if (LHSs && MHSs && RHSs) { - std::string Val = std::string(RHSs->getValue()); - - std::string::size_type found; - std::string::size_type idx = 0; - while (true) { - found = Val.find(std::string(LHSs->getValue()), idx); - if (found == std::string::npos) - break; - Val.replace(found, LHSs->getValue().size(), - std::string(MHSs->getValue())); - idx = found + MHSs->getValue().size(); - } + if (LHSs && MHSs && RHSs) + return StringInit::get(RK, replaceAll(std::string(RHSs->getValue()), + std::string(LHSs->getValue()), + std::string(MHSs->getValue()))); - return StringInit::get(RK, Val); - } break; } @@ -2044,6 +2049,98 @@ .str(); } +static void ProfileFormatOpInit(FoldingSetNodeID &ID, Init *Fmt, + ArrayRef Args) { + ID.AddPointer(Fmt); + for (auto *Arg : Args) + ID.AddPointer(Arg); +} + +void FormatOpInit::Profile(FoldingSetNodeID &ID) const { + ProfileFormatOpInit(ID, Fmt, args()); +} + +FormatOpInit *FormatOpInit::get(Init *Fmt, ArrayRef Args) { + FoldingSetNodeID ID; + ProfileFormatOpInit(ID, Fmt, Args); + + detail::RecordKeeperImpl &RK = Fmt->getRecordKeeper().getImpl(); + void *IP = nullptr; + if (FormatOpInit *I = RK.TheFormatOpInitPool.FindNodeOrInsertPos(ID, IP)) + return I; + + void *Mem = RK.Allocator.Allocate(totalSizeToAlloc(Args.size()), + alignof(BitsInit)); + FormatOpInit *I = new (Mem) FormatOpInit(Fmt, Args.size()); + + std::uninitialized_copy(Args.begin(), Args.end(), + I->getTrailingObjects()); + RK.TheFormatOpInitPool.InsertNode(I, IP); + return I; +} + +Init *FormatOpInit::Fold(Record *CurRec) const { + if (!isConcrete()) + return const_cast(this); + + if (auto *FmtStrInit = dyn_cast(Fmt)) { + std::string Res = FmtStrInit->getAsUnquotedString(); + // Replace all placeholders with corresponding argment value. + for (unsigned I = 0; I < NumArgs; I++) { + auto *Arg = getArg(I); + // FIXME: This is a really simple string substitution. Shoule we make it + // just like std::format? + Res = replaceAll(Res, "{" + utostr(I) + "}", Arg->getAsUnquotedString()); + } + return StringInit::get(getRecordKeeper(), Res, FmtStrInit->getFormat()); + } + return const_cast(this); +} + +Init *FormatOpInit::resolveReferences(Resolver &R) const { + bool Changed = false; + Init *NewFmt = Fmt->resolveReferences(R); + Changed |= NewFmt != Fmt; + + SmallVector NewArgs; + for (auto *Arg : args()) { + Init *NewArg = Arg->resolveReferences(R); + NewArgs.push_back(NewArg); + Changed |= NewArg != Arg; + } + + if (Changed) + return (FormatOpInit::get(NewFmt, NewArgs))->Fold(R.getCurrentRecord()); + + return const_cast(this); +} + +bool FormatOpInit::isComplete() const { + for (auto *Arg : args()) + if (!Arg->isComplete()) + return false; + return Fmt->isComplete(); +} + +bool FormatOpInit::isConcrete() const { + for (auto *Arg : args()) + if (!Arg->isConcrete()) + return false; + return Fmt->isConcrete(); +} + +std::string FormatOpInit::getAsString() const { + std::string Result = "!format("; + Result += Fmt->getAsString(); + for (auto *Arg : args()) + Result += ", " + Arg->getAsString(); + return Result + ")"; +} + +Init *FormatOpInit::getBit(unsigned Bit) const { + return VarBitInit::get(const_cast(this), Bit); +} + RecTy *TypedInit::getFieldType(StringInit *FieldName) const { if (RecordRecTy *RecordType = dyn_cast(getType())) { for (Record *Rec : RecordType->getClasses()) { 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 @@ -131,6 +131,7 @@ XGetDagName, XSetDagArg, XSetDagName, + XFormat, // Boolean literals. TrueVal, 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 @@ -599,6 +599,7 @@ .Case("exists", tgtok::XExists) .Case("tolower", tgtok::XToLower) .Case("toupper", tgtok::XToUpper) + .Case("format", tgtok::XFormat) .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 @@ -1395,6 +1395,44 @@ return (ExistsOpInit::get(Type, Expr))->Fold(CurRec); } + case tgtok::XFormat: { + // Value ::= !format '(' fmt, args, ... ')' + Lex.Lex(); // eat the operation + if (!consume(tgtok::l_paren)) { + TokError("expected '(' after type of !format"); + return nullptr; + } + + SMLoc FmtLoc = Lex.getLoc(); + Init *Fmt = ParseValue(CurRec); + if (!Fmt) + return nullptr; + + TypedInit *FmtType = dyn_cast(Fmt); + if (!FmtType) { + Error(FmtLoc, "expected a string value"); + return nullptr; + } + + StringRecTy *SType = dyn_cast(FmtType->getType()); + if (!SType) { + Error(FmtLoc, "expected a string value"); + return nullptr; + } + + SmallVector Args; + while (!consume(tgtok::r_paren)) { + if (!consume(tgtok::comma)) { + TokError("expected ','"); + return nullptr; + } + Init *Arg = ParseValue(CurRec); + Args.push_back(Arg); + } + + return FormatOpInit::get(Fmt, Args)->Fold(CurRec); + } + case tgtok::XConcat: case tgtok::XADD: case tgtok::XSUB: @@ -2803,6 +2841,7 @@ return DagInit::get(Operator, OperatorName, DagArgs); } + case tgtok::XFormat: case tgtok::XHead: case tgtok::XTail: case tgtok::XSize: diff --git a/llvm/test/TableGen/format.td b/llvm/test/TableGen/format.td new file mode 100644 --- /dev/null +++ b/llvm/test/TableGen/format.td @@ -0,0 +1,67 @@ +// RUN: llvm-tblgen %s | FileCheck %s +// RUN: not llvm-tblgen -DERROR1 %s 2>&1 | FileCheck --check-prefix=ERROR1 %s +// RUN: not llvm-tblgen -DERROR2 %s 2>&1 | FileCheck --check-prefix=ERROR2 %s + +class ClassType; +def clz: ClassType; + +class BasicTest bitsValue, int intValue, + string stringValue, code codeValue, + list listValue, dag dagValue, ClassType classValue> { + string formatBit = !format("{0}", bitValue); + string formatBits = !format("{0}", bitsValue); + string formatInt = !format("{0}", intValue); + string formatString = !format("{0}", stringValue); + string formatCode = !format("{0}", codeValue); + string formatList = !format("{0}", listValue); + string formatDag = !format("{0}", dagValue); + string formatClass = !format("{0}", classValue); + string formatUnset = !format("{0}", ?); + string formatMultiTimes = !format("{0} {0}", 0); + string formatMultiArgs = !format("{0} {1}", 0, 1); + string formatInterleave = !format("{0} {1} {0}", 0, 1); + string formatNoArg = !format(""); + string formatLessArgs = !format("{0} {1}", 0); + string formatMoreArgs = !format("{0} {1}", 0, 1, 2); + string formatOperatorArg = !format("{0}", !if(bitValue, 1, 0)); + string formatOperatorFmt = !format(!if(!not(bitValue), "{0} {1}", "{1} {0}"), 0, 1); + string formatPasteFmt = !format("{" # "0" # "}", 0); + code formatCodeFmt = !format([{ + int {0}() {return {1};} + }], "test", 2333); +} + +// CHECK: def test { +// CHECK-NEXT: string formatBit = "1"; +// CHECK-NEXT: string formatBits = "{ 1, 1, 1, 1, 1 }"; +// CHECK-NEXT: string formatInt = "255"; +// CHECK-NEXT: string formatString = "1"; +// CHECK-NEXT: string formatCode = ""; +// CHECK-NEXT: string formatList = "[1]"; +// CHECK-NEXT: string formatDag = "(test ?, ?)"; +// CHECK-NEXT: string formatClass = "clz"; +// CHECK-NEXT: string formatUnset = "?"; +// CHECK-NEXT: string formatMultiTimes = "0 0"; +// CHECK-NEXT: string formatMultiArgs = "0 1"; +// CHECK-NEXT: string formatInterleave = "0 1 0"; +// CHECK-NEXT: string formatNoArg = ""; +// CHECK-NEXT: string formatLessArgs = "0 {1}"; +// CHECK-NEXT: string formatMoreArgs = "0 1"; +// CHECK-NEXT: string formatOperatorArg = "1"; +// CHECK-NEXT: string formatOperatorFmt = "1 0"; +// CHECK-NEXT: string formatPasteFmt = "0"; +// CHECK-NEXT: code formatCodeFmt = [{ +// CHECK-NEXT: int test() {return 2333;} +// CHECK-NEXT: }]; +// CHECK-NEXT: } +def test: BasicTest<1, 0b11111, 0xFF, "1", [{}], [1], (test ?, ?), clz>; + +#ifdef ERROR1 +// ERROR1: error: expected a string value +defvar error = !format(?) +#endif + +#ifdef ERROR2 +// ERROR2: error: expected a string value +defvar error = !format(1) +#endif