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

Reply via email to