| //===-- ClangOpcodesEmitter.cpp - constexpr interpreter opcodes -----------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // These tablegen backends emit Clang AST node tables | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "TableGenBackends.h" | |
| #include "llvm/TableGen/Error.h" | |
| #include "llvm/TableGen/Record.h" | |
| #include "llvm/TableGen/StringMatcher.h" | |
| #include "llvm/TableGen/TableGenBackend.h" | |
| using namespace llvm; | |
| namespace { | |
| class ClangOpcodesEmitter { | |
| const RecordKeeper &Records; | |
| unsigned NumTypes; | |
| public: | |
| ClangOpcodesEmitter(const RecordKeeper &R) | |
| : Records(R), NumTypes(Records.getAllDerivedDefinitions("Type").size()) {} | |
| void run(raw_ostream &OS); | |
| private: | |
| /// Emits the opcode name for the opcode enum. | |
| /// The name is obtained by concatenating the name with the list of types. | |
| void EmitEnum(raw_ostream &OS, StringRef N, const Record *R); | |
| void EmitInterpFnList(raw_ostream &OS, StringRef N, const Record *R); | |
| void EmitInterpFnDispatchers(raw_ostream &OS, StringRef N, const Record *R); | |
| /// Emits the disassembler. | |
| void EmitDisasm(raw_ostream &OS, StringRef N, const Record *R); | |
| /// Emits the byte code emitter method. | |
| void EmitEmitter(raw_ostream &OS, StringRef N, const Record *R); | |
| /// Emits the prototype. | |
| void EmitProto(raw_ostream &OS, StringRef N, const Record *R); | |
| /// Emits the prototype to dispatch from a type. | |
| void EmitGroup(raw_ostream &OS, StringRef N, const Record *R); | |
| /// Emits the evaluator method. | |
| void EmitEval(raw_ostream &OS, StringRef N, const Record *R); | |
| void PrintTypes(raw_ostream &OS, ArrayRef<const Record *> Types); | |
| }; | |
| void Enumerate(const Record *R, StringRef N, | |
| std::function<void(ArrayRef<const Record *>, Twine)> &&F) { | |
| SmallVector<const Record *, 2> TypePath; | |
| const auto *Types = R->getValueAsListInit("Types"); | |
| std::function<void(size_t, const Twine &)> Rec; | |
| Rec = [&TypePath, Types, &Rec, &F](size_t I, const Twine &ID) { | |
| if (I >= Types->size()) { | |
| F(TypePath, ID); | |
| return; | |
| } | |
| if (const auto *TypeClass = dyn_cast<DefInit>(Types->getElement(I))) { | |
| for (const auto *Type : | |
| TypeClass->getDef()->getValueAsListOfDefs("Types")) { | |
| TypePath.push_back(Type); | |
| Rec(I + 1, ID + Type->getName()); | |
| TypePath.pop_back(); | |
| } | |
| } else { | |
| PrintFatalError("Expected a type class"); | |
| } | |
| }; | |
| Rec(0, N); | |
| } | |
| } // namespace | |
| void ClangOpcodesEmitter::run(raw_ostream &OS) { | |
| for (const auto *Opcode : Records.getAllDerivedDefinitions("Opcode")) { | |
| // The name is the record name, unless overriden. | |
| StringRef N = Opcode->getValueAsString("Name"); | |
| if (N.empty()) | |
| N = Opcode->getName(); | |
| EmitEnum(OS, N, Opcode); | |
| EmitInterpFnList(OS, N, Opcode); | |
| EmitInterpFnDispatchers(OS, N, Opcode); | |
| EmitDisasm(OS, N, Opcode); | |
| EmitProto(OS, N, Opcode); | |
| EmitGroup(OS, N, Opcode); | |
| EmitEmitter(OS, N, Opcode); | |
| EmitEval(OS, N, Opcode); | |
| } | |
| } | |
| void ClangOpcodesEmitter::EmitEnum(raw_ostream &OS, StringRef N, | |
| const Record *R) { | |
| OS << "#ifdef GET_OPCODE_NAMES\n"; | |
| Enumerate(R, N, [&OS](ArrayRef<const Record *>, const Twine &ID) { | |
| OS << "OP_" << ID << ",\n"; | |
| }); | |
| OS << "#endif\n"; | |
| } | |
| void ClangOpcodesEmitter::EmitInterpFnDispatchers(raw_ostream &OS, StringRef N, | |
| const Record *R) { | |
| OS << "#ifdef GET_INTERPFN_DISPATCHERS\n"; | |
| // PRESERVE NONE static bool Interp_* (InterpState &S, CodePtr &PC) { | |
| Enumerate(R, N, [&](ArrayRef<const Record *> TS, const Twine &ID) { | |
| OS << "PRESERVE_NONE\nstatic bool Interp_" << ID << "(InterpState &S) {\n"; | |
| if (ID.str() == "EndSpeculation") { | |
| OS << " MUSTTAIL return EndSpeculation(S);\n"; | |
| OS << "}\n"; | |
| return; | |
| } | |
| bool CanReturn = R->getValueAsBit("CanReturn"); | |
| const auto &Args = R->getValueAsListOfDefs("Args"); | |
| bool CanFail = R->getValueAsBit("CanFail"); | |
| bool PassOpPC = R->getValueAsBit("NeedsOpPC"); | |
| if (Args.empty()) { | |
| if (CanReturn) { | |
| OS << " MUSTTAIL return " << N; | |
| PrintTypes(OS, TS); | |
| OS << "(S);\n"; | |
| OS << "}\n"; | |
| return; | |
| } | |
| OS << " "; | |
| if (CanFail) | |
| OS << "if (!"; | |
| OS << N; | |
| PrintTypes(OS, TS); | |
| OS << "(S"; | |
| if (PassOpPC) | |
| OS << ", S.PC)"; | |
| else | |
| OS << ")"; | |
| if (CanFail) | |
| OS << ") return false"; | |
| OS << ";\n"; | |
| OS << "#if USE_TAILCALLS\n"; | |
| OS << " MUSTTAIL return InterpNext(S);\n"; | |
| OS << "#else\n"; | |
| OS << " return true;\n"; | |
| OS << "#endif\n"; | |
| OS << "}\n"; | |
| return; | |
| } | |
| OS << " {\n"; | |
| if (PassOpPC) | |
| OS << " CodePtr OpPC = S.PC;\n"; | |
| // Emit calls to read arguments. | |
| for (size_t I = 0, N = Args.size(); I < N; ++I) { | |
| const auto *Arg = Args[I]; | |
| bool AsRef = Arg->getValueAsBit("AsRef"); | |
| if (AsRef) | |
| OS << " const auto &V" << I; | |
| else | |
| OS << " const auto V" << I; | |
| OS << " = "; | |
| OS << "ReadArg<" << Arg->getValueAsString("Name") << ">(S, S.PC);\n"; | |
| } | |
| OS << " "; | |
| if (CanFail) | |
| OS << "if (!"; | |
| OS << N; | |
| PrintTypes(OS, TS); | |
| OS << "(S"; | |
| if (PassOpPC) | |
| OS << ", OpPC"; | |
| for (size_t I = 0, N = Args.size(); I < N; ++I) | |
| OS << ", V" << I; | |
| OS << ")"; | |
| if (CanFail) | |
| OS << ") return false"; | |
| OS << ";\n"; | |
| OS << " }\n"; | |
| if (!CanReturn) { | |
| OS << "#if USE_TAILCALLS\n"; | |
| OS << " MUSTTAIL return InterpNext(S);\n"; | |
| OS << "#else\n"; | |
| OS << " return true;\n"; | |
| OS << "#endif\n"; | |
| } else | |
| OS << " return true;\n"; | |
| OS << "}\n"; | |
| }); | |
| OS << "#endif\n"; | |
| } | |
| void ClangOpcodesEmitter::EmitInterpFnList(raw_ostream &OS, StringRef N, | |
| const Record *R) { | |
| OS << "#ifdef GET_INTERPFN_LIST\n"; | |
| Enumerate(R, N, [&OS](ArrayRef<const Record *>, const Twine &ID) { | |
| OS << "&Interp_" << ID << ",\n"; | |
| }); | |
| OS << "#endif\n"; | |
| } | |
| void ClangOpcodesEmitter::EmitDisasm(raw_ostream &OS, StringRef N, | |
| const Record *R) { | |
| OS << "#ifdef GET_DISASM\n"; | |
| Enumerate(R, N, [R, &OS](ArrayRef<const Record *>, const Twine &ID) { | |
| OS << "case OP_" << ID << ":\n"; | |
| OS << " Text.Op = PrintName(\"" << ID << "\");\n"; | |
| for (const auto *Arg : R->getValueAsListOfDefs("Args")) | |
| OS << " Text.Args.push_back(printArg<" << Arg->getValueAsString("Name") | |
| << ">(PC));\n"; | |
| OS << " break;\n"; | |
| }); | |
| OS << "#endif\n"; | |
| } | |
| void ClangOpcodesEmitter::EmitEmitter(raw_ostream &OS, StringRef N, | |
| const Record *R) { | |
| if (R->getValueAsBit("HasCustomLink")) | |
| return; | |
| OS << "#ifdef GET_LINK_IMPL\n"; | |
| Enumerate(R, N, [R, &OS](ArrayRef<const Record *>, const Twine &ID) { | |
| const auto &Args = R->getValueAsListOfDefs("Args"); | |
| // Emit the list of arguments. | |
| OS << "bool ByteCodeEmitter::emit" << ID << "("; | |
| for (size_t I = 0, N = Args.size(); I < N; ++I) { | |
| const auto *Arg = Args[I]; | |
| bool AsRef = Arg->getValueAsBit("AsRef"); | |
| auto Name = Arg->getValueAsString("Name"); | |
| OS << (AsRef ? "const " : " ") << Name << " " << (AsRef ? "&" : "") << "A" | |
| << I << ", "; | |
| } | |
| OS << "SourceInfo L) {\n"; | |
| // Emit a call to write the opcodes. | |
| OS << " return emitOp<"; | |
| for (size_t I = 0, N = Args.size(); I < N; ++I) { | |
| if (I != 0) | |
| OS << ", "; | |
| OS << Args[I]->getValueAsString("Name"); | |
| } | |
| OS << ">(OP_" << ID; | |
| for (size_t I = 0, N = Args.size(); I < N; ++I) | |
| OS << ", A" << I; | |
| OS << ", L);\n"; | |
| OS << "}\n"; | |
| }); | |
| OS << "#endif\n"; | |
| } | |
| void ClangOpcodesEmitter::EmitProto(raw_ostream &OS, StringRef N, | |
| const Record *R) { | |
| OS << "#if defined(GET_EVAL_PROTO) || defined(GET_LINK_PROTO)\n"; | |
| auto Args = R->getValueAsListOfDefs("Args"); | |
| Enumerate(R, N, [&OS, &Args](ArrayRef<const Record *> TS, const Twine &ID) { | |
| OS << "bool emit" << ID << "("; | |
| for (const Record *Arg : Args) { | |
| bool AsRef = Arg->getValueAsBit("AsRef"); | |
| auto Name = Arg->getValueAsString("Name"); | |
| OS << (AsRef ? "const " : " ") << Name << " " << (AsRef ? "&" : "") | |
| << ", "; | |
| } | |
| OS << "SourceInfo);\n"; | |
| }); | |
| // Emit a template method for custom emitters to have less to implement. | |
| auto TypeCount = R->getValueAsListInit("Types")->size(); | |
| if (R->getValueAsBit("HasCustomEval") && TypeCount) { | |
| OS << "#if defined(GET_EVAL_PROTO)\n"; | |
| OS << "template<"; | |
| for (size_t I = 0; I < TypeCount; ++I) { | |
| if (I != 0) | |
| OS << ", "; | |
| OS << "PrimType"; | |
| } | |
| OS << ">\n"; | |
| OS << "bool emit" << N << "("; | |
| for (const auto *Arg : Args) | |
| OS << Arg->getValueAsString("Name") << ", "; | |
| OS << "SourceInfo);\n"; | |
| OS << "#endif\n"; | |
| } | |
| OS << "#endif\n"; | |
| } | |
| void ClangOpcodesEmitter::EmitGroup(raw_ostream &OS, StringRef N, | |
| const Record *R) { | |
| if (!R->getValueAsBit("HasGroup")) | |
| return; | |
| const auto *Types = R->getValueAsListInit("Types"); | |
| const auto &Args = R->getValueAsListOfDefs("Args"); | |
| if (Types->empty()) { | |
| PrintFatalError("HasGroup only makes sense for opcodes with types"); | |
| return; | |
| } | |
| Twine EmitFuncName = "emit" + N; | |
| // Emit the prototype of the group emitter in the header. | |
| OS << "#if defined(GET_EVAL_PROTO) || defined(GET_LINK_PROTO)\n"; | |
| OS << "[[nodiscard]] bool " << EmitFuncName << "("; | |
| for (size_t I = 0, N = Types->size(); I < N; ++I) | |
| OS << "PrimType, "; | |
| for (auto *Arg : Args) | |
| OS << Arg->getValueAsString("Name") << ", "; | |
| OS << "SourceInfo I);\n"; | |
| OS << "#endif\n"; | |
| // Emit the dispatch implementation in the source. | |
| OS << "#if defined(GET_EVAL_IMPL) || defined(GET_LINK_IMPL)\n"; | |
| OS << "bool\n"; | |
| OS << "#if defined(GET_EVAL_IMPL)\n"; | |
| OS << "EvalEmitter\n"; | |
| OS << "#else\n"; | |
| OS << "ByteCodeEmitter\n"; | |
| OS << "#endif\n"; | |
| OS << "::" << EmitFuncName << "("; | |
| for (size_t I = 0, N = Types->size(); I < N; ++I) | |
| OS << "PrimType T" << I << ", "; | |
| for (size_t I = 0, N = Args.size(); I < N; ++I) { | |
| const auto *Arg = Args[I]; | |
| bool AsRef = Arg->getValueAsBit("AsRef"); | |
| auto Name = Arg->getValueAsString("Name"); | |
| OS << (AsRef ? "const " : " ") << Name << " " << (AsRef ? "&" : "") << "A" | |
| << I << ", "; | |
| } | |
| OS << "SourceInfo I) {\n"; | |
| std::function<void(size_t, const Twine &)> Rec; | |
| SmallVector<const Record *, 2> TS; | |
| Rec = [this, &Rec, &OS, Types, &Args, R, &TS, N, | |
| EmitFuncName](size_t I, const Twine &ID) { | |
| if (I >= Types->size()) { | |
| // Print a call to the emitter method. | |
| // Custom evaluator methods dispatch to template methods. | |
| if (R->getValueAsBit("HasCustomEval")) { | |
| OS << "#ifdef GET_LINK_IMPL\n"; | |
| OS << " return emit" << ID << "\n"; | |
| OS << "#else\n"; | |
| OS << " return emit" << N; | |
| PrintTypes(OS, TS); | |
| OS << "\n#endif\n"; | |
| OS << " "; | |
| } else { | |
| OS << " return emit" << ID; | |
| } | |
| OS << "("; | |
| for (size_t I = 0; I < Args.size(); ++I) { | |
| OS << "A" << I << ", "; | |
| } | |
| OS << "I);\n"; | |
| return; | |
| } | |
| // Print a switch statement selecting T. | |
| if (auto *TypeClass = dyn_cast<DefInit>(Types->getElement(I))) { | |
| OS << " switch (T" << I << ") {\n"; | |
| auto Cases = TypeClass->getDef()->getValueAsListOfDefs("Types"); | |
| for (auto *Case : Cases) { | |
| OS << " case PT_" << Case->getName() << ":\n"; | |
| TS.push_back(Case); | |
| Rec(I + 1, ID + Case->getName()); | |
| TS.pop_back(); | |
| } | |
| // Emit a default case if not all types are present. | |
| if (Cases.size() < NumTypes) | |
| OS << " default: llvm_unreachable(\"invalid type: " << EmitFuncName | |
| << "\");\n"; | |
| OS << " }\n"; | |
| OS << " llvm_unreachable(\"invalid enum value\");\n"; | |
| } else { | |
| PrintFatalError("Expected a type class"); | |
| } | |
| }; | |
| Rec(0, N); | |
| OS << "}\n"; | |
| OS << "#endif\n"; | |
| } | |
| void ClangOpcodesEmitter::EmitEval(raw_ostream &OS, StringRef N, | |
| const Record *R) { | |
| if (R->getValueAsBit("HasCustomEval")) | |
| return; | |
| OS << "#ifdef GET_EVAL_IMPL\n"; | |
| Enumerate(R, N, | |
| [this, R, &N, &OS](ArrayRef<const Record *> TS, const Twine &ID) { | |
| auto Args = R->getValueAsListOfDefs("Args"); | |
| OS << "bool EvalEmitter::emit" << ID << "("; | |
| for (size_t I = 0, N = Args.size(); I < N; ++I) { | |
| const auto *Arg = Args[I]; | |
| bool AsRef = Arg->getValueAsBit("AsRef"); | |
| auto Name = Arg->getValueAsString("Name"); | |
| OS << (AsRef ? "const " : " ") << Name << " " | |
| << (AsRef ? "&" : "") << "A" << I << ", "; | |
| } | |
| OS << "SourceInfo L) {\n"; | |
| OS << " if (!isActive()) return true;\n"; | |
| OS << " CurrentSource = L;\n"; | |
| if (N == "EndSpeculation") { | |
| OS << "return EndSpeculation(S);\n"; | |
| } else { | |
| bool PassOpPC = R->getValueAsBit("NeedsOpPC"); | |
| OS << " return " << N; | |
| PrintTypes(OS, TS); | |
| OS << "(S"; | |
| if (PassOpPC) | |
| OS << ", CodePtr()"; | |
| for (size_t I = 0, N = Args.size(); I < N; ++I) | |
| OS << ", A" << I; | |
| OS << ");\n"; | |
| } | |
| OS << "}\n"; | |
| }); | |
| OS << "#endif\n"; | |
| } | |
| void ClangOpcodesEmitter::PrintTypes(raw_ostream &OS, | |
| ArrayRef<const Record *> Types) { | |
| if (Types.empty()) | |
| return; | |
| OS << "<"; | |
| for (size_t I = 0, N = Types.size(); I < N; ++I) { | |
| if (I != 0) | |
| OS << ", "; | |
| OS << "PT_" << Types[I]->getName(); | |
| } | |
| OS << ">"; | |
| } | |
| void clang::EmitClangOpcodes(const RecordKeeper &Records, raw_ostream &OS) { | |
| ClangOpcodesEmitter(Records).run(OS); | |
| } |