https://github.com/tbaederr created 
https://github.com/llvm/llvm-project/pull/185835

Instead of heap-allocating an `InterpFrame` and then immediately 
heap-allocating more space for the local variables, do only one heap-allocation 
and use tail storage for the local variables.
We already know how many bytes we need to for the tail storage after all.
This also makes `InterpFrame` a little smaller since we don't need to save an 
explicit pointer for the local variable memory.






For an artificial test case doing lots of function calls with local variables 
like:
```c++
constexpr int plus(int a, int b) {
        int x = a;
        int y = b;
        int z = x + y;
        return z;
}

constexpr int minus(int a, int b) {
        int x = a;
        int y = b;
        int z = x - y;
        return z;
}
constexpr int foo() {
        int a = 0;
        for (unsigned I = 0; I != 1'000'000; ++I) {
                int b = I;
                a = plus(a,b );
                a = minus(a,I);
        }
        return a;
}
static_assert(foo() == 0);
```
this saves us over 6%.

>From a693bbae79178ffa163e2d1db9959c1feb1327c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Tue, 10 Mar 2026 13:52:16 +0100
Subject: [PATCH] locals

---
 clang/lib/AST/ByteCode/Context.cpp     |  8 ++++++++
 clang/lib/AST/ByteCode/Interp.cpp      | 14 ++++++++------
 clang/lib/AST/ByteCode/InterpFrame.cpp | 10 +++-------
 clang/lib/AST/ByteCode/InterpFrame.h   | 23 +++++++++++++++++------
 clang/lib/AST/ByteCode/InterpState.cpp |  5 ++---
 5 files changed, 38 insertions(+), 22 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Context.cpp 
b/clang/lib/AST/ByteCode/Context.cpp
index 7d4534a5da5c6..9a4c1f3d520a5 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -498,11 +498,19 @@ const llvm::fltSemantics 
&Context::getFloatSemantics(QualType T) const {
 
 bool Context::Run(State &Parent, const Function *Func) {
   InterpState State(Parent, *P, Stk, *this, Func);
+  auto Memory = std::make_unique<char[]>(InterpFrame::allocSize(Func));
+  InterpFrame *Frame = new (Memory.get()) InterpFrame(
+      State, Func, /*Caller=*/nullptr, CodePtr(), Func->getArgSize());
+  State.Current = Frame;
+
   if (Interpret(State)) {
     assert(Stk.empty());
     return true;
   }
+
   Stk.clear();
+  Frame->~InterpFrame();
+  State.Current = &State.BottomFrame;
   return false;
 }
 
diff --git a/clang/lib/AST/ByteCode/Interp.cpp 
b/clang/lib/AST/ByteCode/Interp.cpp
index ebc7220aa5671..2cb1cd7289d13 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1656,19 +1656,20 @@ bool CallVar(InterpState &S, CodePtr OpPC, const 
Function *Func,
   if (!CheckCallDepth(S, OpPC))
     return false;
 
-  auto NewFrame = std::make_unique<InterpFrame>(S, Func, OpPC, VarArgSize);
+  auto Memory = new char[InterpFrame::allocSize(Func)];
+  auto NewFrame = new (Memory) InterpFrame(S, Func, OpPC, VarArgSize);
   InterpFrame *FrameBefore = S.Current;
-  S.Current = NewFrame.get();
+  S.Current = NewFrame;
 
   // Note that we cannot assert(CallResult.hasValue()) here since
   // Ret() above only sets the APValue if the curent frame doesn't
   // have a caller set.
   if (Interpret(S)) {
-    NewFrame.release(); // Frame was delete'd already.
     assert(S.Current == FrameBefore);
     return true;
   }
 
+  InterpFrame::free(NewFrame);
   // Interpreting the function failed somehow. Reset to
   // previous state.
   S.Current = FrameBefore;
@@ -1739,9 +1740,10 @@ bool Call(InterpState &S, CodePtr OpPC, const Function 
*Func,
   if (!CheckCallDepth(S, OpPC))
     return cleanup();
 
-  auto NewFrame = std::make_unique<InterpFrame>(S, Func, OpPC, VarArgSize);
+  auto Memory = new char[InterpFrame::allocSize(Func)];
+  auto NewFrame = new (Memory) InterpFrame(S, Func, OpPC, VarArgSize);
   InterpFrame *FrameBefore = S.Current;
-  S.Current = NewFrame.get();
+  S.Current = NewFrame;
 
   InterpStateCCOverride CCOverride(S, Func->isImmediate());
   // Note that we cannot assert(CallResult.hasValue()) here since
@@ -1753,13 +1755,13 @@ bool Call(InterpState &S, CodePtr OpPC, const Function 
*Func,
     S.InitializingBlocks.pop_back();
 
   if (!Success) {
+    InterpFrame::free(NewFrame);
     // Interpreting the function failed somehow. Reset to
     // previous state.
     S.Current = FrameBefore;
     return false;
   }
 
-  NewFrame.release(); // Frame was delete'd already.
   assert(S.Current == FrameBefore);
   return true;
 }
diff --git a/clang/lib/AST/ByteCode/InterpFrame.cpp 
b/clang/lib/AST/ByteCode/InterpFrame.cpp
index 3c185a0ad661a..3a175707774f4 100644
--- a/clang/lib/AST/ByteCode/InterpFrame.cpp
+++ b/clang/lib/AST/ByteCode/InterpFrame.cpp
@@ -31,14 +31,9 @@ InterpFrame::InterpFrame(InterpState &S, const Function 
*Func,
     : Caller(Caller), S(S), Depth(Caller ? Caller->Depth + 1 : 0), Func(Func),
       RetPC(RetPC), ArgSize(ArgSize), Args(static_cast<char *>(S.Stk.top())),
       FrameOffset(S.Stk.size()) {
-  if (!Func)
-    return;
-
-  unsigned FrameSize = Func->getFrameSize();
-  if (FrameSize == 0)
+  if (!Func || Func->getFrameSize() == 0)
     return;
 
-  Locals = std::make_unique<char[]>(FrameSize);
   for (auto &Scope : Func->scopes()) {
     for (auto &Local : Scope.locals()) {
       new (localBlock(Local.Offset)) Block(S.Ctx.getEvalID(), Local.Desc);
@@ -77,8 +72,9 @@ InterpFrame::~InterpFrame() {
 }
 
 void InterpFrame::destroyScopes() {
-  if (!Func)
+  if (!Func || Func->getFrameSize() == 0)
     return;
+
   for (auto &Scope : Func->scopes()) {
     for (auto &Local : Scope.locals()) {
       S.deallocate(localBlock(Local.Offset));
diff --git a/clang/lib/AST/ByteCode/InterpFrame.h 
b/clang/lib/AST/ByteCode/InterpFrame.h
index 0879260695d3e..d56196845b560 100644
--- a/clang/lib/AST/ByteCode/InterpFrame.h
+++ b/clang/lib/AST/ByteCode/InterpFrame.h
@@ -46,6 +46,12 @@ class InterpFrame final : public Frame {
   /// Destroys the frame, killing all live pointers to stack slots.
   ~InterpFrame();
 
+  /// Returns the number of bytes needed to allocate an InterpFrame for the
+  /// given function.
+  static size_t allocSize(const Function *F) {
+    return sizeof(InterpFrame) + F->getFrameSize();
+  }
+
   std::string getName() const {
     if (!Func)
       return "Bottom frame";
@@ -53,8 +59,10 @@ class InterpFrame final : public Frame {
   }
 
   static void free(InterpFrame *F) {
-    if (!F->isBottomFrame())
-      delete F;
+    if (!F->isBottomFrame()) {
+      F->~InterpFrame();
+      delete[] reinterpret_cast<char *>(F);
+    }
   }
 
   /// Invokes the destructors for a scope.
@@ -167,14 +175,19 @@ class InterpFrame final : public Frame {
     return localBlock(Offset)->deref<T>();
   }
 
+  char *locals() const {
+    return (reinterpret_cast<char *>(const_cast<InterpFrame *>(this))) +
+           sizeof(InterpFrame);
+  }
+
   /// Returns a pointer to a local's block.
   Block *localBlock(unsigned Offset) const {
-    return reinterpret_cast<Block *>(Locals.get() + Offset - sizeof(Block));
+    return reinterpret_cast<Block *>(locals() + Offset - sizeof(Block));
   }
 
   /// Returns the inline descriptor of the local.
   InlineDescriptor *localInlineDesc(unsigned Offset) const {
-    return reinterpret_cast<InlineDescriptor *>(Locals.get() + Offset);
+    return reinterpret_cast<InlineDescriptor *>(locals() + Offset);
   }
 
 private:
@@ -192,8 +205,6 @@ class InterpFrame final : public Frame {
   const unsigned ArgSize;
   /// Pointer to the arguments in the callee's frame.
   char *Args = nullptr;
-  /// Fixed, initial storage for known local variables.
-  std::unique_ptr<char[]> Locals;
   /// Offset on the stack at entry.
   const size_t FrameOffset;
   /// Mapping from arg offsets to their argument blocks.
diff --git a/clang/lib/AST/ByteCode/InterpState.cpp 
b/clang/lib/AST/ByteCode/InterpState.cpp
index fd69559af5917..2d6ed98e6b52c 100644
--- a/clang/lib/AST/ByteCode/InterpState.cpp
+++ b/clang/lib/AST/ByteCode/InterpState.cpp
@@ -33,9 +33,8 @@ InterpState::InterpState(const State &Parent, Program &P, 
InterpStack &Stk,
 InterpState::InterpState(const State &Parent, Program &P, InterpStack &Stk,
                          Context &Ctx, const Function *Func)
     : State(Ctx.getASTContext(), Parent.getEvalStatus()), M(nullptr), P(P),
-      Stk(Stk), Ctx(Ctx),
-      BottomFrame(*this, Func, nullptr, CodePtr(), Func->getArgSize()),
-      Current(&BottomFrame), StepsLeft(Ctx.getLangOpts().ConstexprStepLimit),
+      Stk(Stk), Ctx(Ctx), BottomFrame(*this), Current(&BottomFrame),
+      StepsLeft(Ctx.getLangOpts().ConstexprStepLimit),
       InfiniteSteps(StepsLeft == 0), EvalID(Ctx.getEvalID()) {
   InConstantContext = Parent.InConstantContext;
   CheckingPotentialConstantExpression =

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to