Timm =?utf-8?q?Bäder?= <[email protected]>, Timm =?utf-8?q?Bäder?= <[email protected]> Message-ID: <llvm.org/llvm/llvm-project/pull/[email protected]> In-Reply-To:
https://github.com/tbaederr created https://github.com/llvm/llvm-project/pull/173756 This PR attempts to use a tailcall design for the bytecode interpreter instead of the giant-switch design, similar to [what Python has been doing](https://blog.reverberate.org/2025/02/10/tail-call-updates.html). Using the two benchmarks from https://developers.redhat.com/articles/2025/10/15/clang-bytecode-interpreter-update, I get these numbers: ``` -- Sqlite BASELINE: Benchmark 1: bin/clang++ -c -std=c++26 sqlite.cpp -fconstexpr-steps=1000000000 Time (mean ± σ): 18.197 s ± 0.370 s [User: 17.926 s, System: 0.269 s] Range (min … max): 17.657 s … 19.215 s 30 runs BEFORE: Benchmark 1: bin/clang++ -c -std=c++26 sqlite.cpp -fconstexpr-steps=1000000000 -fexperimental-new-constant-interpreter Time (mean ± σ): 8.156 s ± 0.148 s [User: 7.881 s, System: 0.273 s] Range (min … max): 7.909 s … 8.491 s 30 runs AFTER: Benchmark 1: bin/clang++ -c -std=c++26 sqlite.cpp -fconstexpr-steps=1000000000 -fexperimental-new-constant-interpreter Time (mean ± σ): 7.492 s ± 0.171 s [User: 7.214 s, System: 0.277 s] Range (min … max): 7.213 s … 7.876 s 30 runs -- Heap allocation: BASELINE: Benchmark 1: bin/clang++ -c -std=c++26 test.cpp -fconstexpr-steps=1000000000 Time (mean ± σ): 28.650 s ± 0.475 s [User: 28.627 s, System: 0.021 s] Range (min … max): 27.767 s … 29.554 s 30 runs BEFORE: Benchmark 1: bin/clang++ -c -std=c++26 test.cpp -fconstexpr-steps=1000000000 -fexperimental-new-constant-interpreter Time (mean ± σ): 4.543 s ± 0.194 s [User: 4.527 s, System: 0.016 s] Range (min … max): 4.340 s … 5.143 s 30 runs AFTER: Benchmark 1: bin/clang++ -c -std=c++26 test.cpp -fconstexpr-steps=1000000000 -fexperimental-new-constant-interpreter Time (mean ± σ): 4.055 s ± 0.140 s [User: 4.039 s, System: 0.015 s] Range (min … max): 3.827 s … 4.430 s 30 runs ``` However, the numbers on the compile-time tracker are slightly worse: https://llvm-compile-time-tracker.com/compare.php?from=699354a4146e2ebc7e7c7aa84d14c396a4300508&to=0b9191c493c35d33721c9aad7ab341fe2a731f3f&stat=instructions:u I did this mostly for research purposes, but the result is kinda neat and I wonder if we should go this route. Not sure who to add as a reviewer for feedback and to check if I'm doing everything right. >From 699354a4146e2ebc7e7c7aa84d14c396a4300508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Fri, 26 Dec 2025 09:07:13 +0100 Subject: [PATCH 1/3] Test --- clang/lib/AST/ExprConstant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index f80dabf5444c7..01f7126f4c942 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -931,7 +931,7 @@ namespace { : Ctx(const_cast<ASTContext &>(C)), EvalStatus(S), CurrentCall(nullptr), CallStackDepth(0), NextCallIndex(1), StepsLeft(C.getLangOpts().ConstexprStepLimit), - EnableNewConstInterp(C.getLangOpts().EnableNewConstInterp), + EnableNewConstInterp(true), BottomFrame(*this, SourceLocation(), /*Callee=*/nullptr, /*This=*/nullptr, /*CallExpr=*/nullptr, CallRef()), >From 622f3b474519dc7a0e5713ac8247a64761417243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Fri, 26 Dec 2025 09:06:45 +0100 Subject: [PATCH 2/3] musttail --- clang/lib/AST/ByteCode/Interp.cpp | 44 +++++----- clang/lib/AST/ByteCode/Interp.h | 7 +- clang/utils/TableGen/ClangOpcodesEmitter.cpp | 87 ++++++++++++++++++++ 3 files changed, 114 insertions(+), 24 deletions(-) diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 889ac1e1a9a7e..af1bf76db865d 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -28,7 +28,8 @@ using namespace clang; using namespace clang::interp; -static bool RetValue(InterpState &S, CodePtr &Pt) { +__attribute__((preserve_none)) static bool RetValue(InterpState &S, + CodePtr &Pt) { llvm::report_fatal_error("Interpreter cannot return values"); } @@ -2324,38 +2325,39 @@ bool FinishInitGlobal(InterpState &S, CodePtr OpPC) { return true; } -// https://github.com/llvm/llvm-project/issues/102513 -#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG) -#pragma optimize("", off) -#endif +__attribute__((preserve_none)) static bool InterpNext(InterpState &S, + CodePtr &PC); + bool Interpret(InterpState &S) { // The current stack frame when we started Interpret(). // This is being used by the ops to determine wheter // to return from this function and thus terminate // interpretation. - const InterpFrame *StartFrame = S.Current; assert(!S.Current->isRoot()); CodePtr PC = S.Current->getPC(); - // Empty program. - if (!PC) - return true; + return InterpNext(S, PC); +} - for (;;) { - auto Op = PC.read<Opcode>(); - CodePtr OpPC = PC; +#define GET_INTERPFNS_ +#include "Opcodes.inc" +#undef GET_INTERPFNS_ - switch (Op) { -#define GET_INTERP +using InterpFn = __attribute__((preserve_none)) bool (*)(InterpState &, + CodePtr &PC); + +const InterpFn InterpFunctions[] = { +#define GET_INTERPFNS #include "Opcodes.inc" -#undef GET_INTERP - } - } +#undef GET_INTERPFNS +}; + +__attribute__((preserve_none)) static bool InterpNext(InterpState &S, + CodePtr &PC) { + auto Op = PC.read<Opcode>(); + auto Fn = InterpFunctions[Op]; + [[clang::musttail]] return Fn(S, PC); } -// https://github.com/llvm/llvm-project/issues/102513 -#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG) -#pragma optimize("", on) -#endif } // namespace interp } // namespace clang diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 9accbbc1605a9..c32ade5e125b0 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -221,7 +221,7 @@ void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC, const Function *Func); template <PrimType Name, class T = typename PrimConv<Name>::T> -bool Ret(InterpState &S, CodePtr &PC) { +__attribute__((preserve_none)) bool Ret(InterpState &S, CodePtr &PC) { const T &Ret = S.Stk.pop<T>(); assert(S.Current); @@ -243,7 +243,8 @@ bool Ret(InterpState &S, CodePtr &PC) { return true; } -inline bool RetVoid(InterpState &S, CodePtr &PC) { +__attribute__((preserve_none)) inline bool RetVoid(InterpState &S, + CodePtr &PC) { assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame"); if (!S.checkingPotentialConstantExpression() || S.Current->Caller) @@ -3040,7 +3041,7 @@ static inline bool ShiftFixedPoint(InterpState &S, CodePtr OpPC, bool Left) { // NoRet //===----------------------------------------------------------------------===// -inline bool NoRet(InterpState &S, CodePtr OpPC) { +__attribute__((preserve_none)) inline bool NoRet(InterpState &S, CodePtr OpPC) { SourceLocation EndLoc = S.Current->getCallee()->getEndLoc(); S.FFDiag(EndLoc, diag::note_constexpr_no_return); return false; diff --git a/clang/utils/TableGen/ClangOpcodesEmitter.cpp b/clang/utils/TableGen/ClangOpcodesEmitter.cpp index d26122aca46bd..84c4a673e133a 100644 --- a/clang/utils/TableGen/ClangOpcodesEmitter.cpp +++ b/clang/utils/TableGen/ClangOpcodesEmitter.cpp @@ -36,6 +36,8 @@ class ClangOpcodesEmitter { /// Emits the switch case and the invocation in the interpreter. void EmitInterp(raw_ostream &OS, StringRef N, const Record *R); + void EmitInterpFns(raw_ostream &OS, StringRef N, const Record *R); + void EmitInterpFns_(raw_ostream &OS, StringRef N, const Record *R); /// Emits the disassembler. void EmitDisasm(raw_ostream &OS, StringRef N, const Record *R); @@ -92,6 +94,8 @@ void ClangOpcodesEmitter::run(raw_ostream &OS) { EmitEnum(OS, N, Opcode); EmitInterp(OS, N, Opcode); + EmitInterpFns(OS, N, Opcode); + EmitInterpFns_(OS, N, Opcode); EmitDisasm(OS, N, Opcode); EmitProto(OS, N, Opcode); EmitGroup(OS, N, Opcode); @@ -109,6 +113,89 @@ void ClangOpcodesEmitter::EmitEnum(raw_ostream &OS, StringRef N, OS << "#endif\n"; } +void ClangOpcodesEmitter::EmitInterpFns_(raw_ostream &OS, StringRef N, + const Record *R) { + OS << "#ifdef GET_INTERPFNS_\n"; + Enumerate(R, N, [&](ArrayRef<const Record *> TS, const Twine &ID) { + OS << "__attribute__((preserve_none))\nstatic bool Interp_" << ID + << "(InterpState &S, CodePtr &PC) {\n"; + + bool CanReturn = R->getValueAsBit("CanReturn"); + const auto &Args = R->getValueAsListOfDefs("Args"); + bool ChangesPC = R->getValueAsBit("ChangesPC"); + + if (Args.empty()) { + if (CanReturn) { + OS << " [[clang::musttail]] return " << N; + PrintTypes(OS, TS); + OS << "(S, PC);\n"; + OS << "}\n"; + return; + } + + // OS << "llvm::errs() << \"Calling \" << \"" << N << "\\n\";";//'\n'; + OS << " if (!" << N; + PrintTypes(OS, TS); + OS << "(S, PC))\n"; + OS << " return false;\n"; + OS << "[[clang::musttail]] return InterpNext(S, PC);\n"; + OS << "}\n"; + return; + } + + OS << "{\n"; + + if (!ChangesPC) + OS << " CodePtr OpPC = 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, PC);\n"; + } + + OS << " if (!" << N; + PrintTypes(OS, TS); + OS << "(S"; + // OS << ", OpPC"; + if (ChangesPC) + OS << ", PC"; + else + OS << ", OpPC"; + for (size_t I = 0, N = Args.size(); I < N; ++I) + OS << ", V" << I; + OS << "))\n"; + OS << " return false;\n"; + + OS << "}\n"; + + if (!CanReturn) + OS << "[[clang::musttail]] return InterpNext(S, PC);\n"; + else + OS << " return true;\n"; + // OS << " return false;\n"; + + OS << "}\n"; + }); + OS << "#endif\n"; +} + +void ClangOpcodesEmitter::EmitInterpFns(raw_ostream &OS, StringRef N, + const Record *R) { + OS << "#ifdef GET_INTERPFNS\n"; + Enumerate(R, N, [&OS](ArrayRef<const Record *>, const Twine &ID) { + OS << "&Interp_" << ID << ",\n"; + }); + OS << "#endif\n"; +} + void ClangOpcodesEmitter::EmitInterp(raw_ostream &OS, StringRef N, const Record *R) { OS << "#ifdef GET_INTERP\n"; >From b6fcb5732d182d7cae12d407aa5c52073582bbb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Sun, 28 Dec 2025 06:37:20 +0100 Subject: [PATCH 3/3] Revert "Test" This reverts commit 699354a4146e2ebc7e7c7aa84d14c396a4300508. --- clang/lib/AST/ExprConstant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 01f7126f4c942..f80dabf5444c7 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -931,7 +931,7 @@ namespace { : Ctx(const_cast<ASTContext &>(C)), EvalStatus(S), CurrentCall(nullptr), CallStackDepth(0), NextCallIndex(1), StepsLeft(C.getLangOpts().ConstexprStepLimit), - EnableNewConstInterp(true), + EnableNewConstInterp(C.getLangOpts().EnableNewConstInterp), BottomFrame(*this, SourceLocation(), /*Callee=*/nullptr, /*This=*/nullptr, /*CallExpr=*/nullptr, CallRef()), _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
