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

Add a function frame allocator we share across evaluations.

For code like
```c++
consteval int foo() {
  int a = 0;
  for (int i = 0; i != 10; ++i)
    inc(a);
  return a;
}
```
We previously `new[]`-ed a frame for every call of `inc()`, and `delete[]`d the 
memory again directly after, resulting in lots of small (in this case) 
allocations.

Add a `FrameAllocator` class that handles frames like a LIFO stack and 
allocates memory in 4kb chunks. If a newly created frame fits into an existing 
chunk, we don't have to allocate anything and simply placement-new the 
`InterpFrame` into the existing chunk. We allocate new chunks as needed.

We can't e.g. use a `std::vector<char>` for this since addresses into 
`InterpFrame` memory must stay stable during a frame lifetime.


When compiling `APFloat.cpp`, the stats print this output:
```console
*** FrameAllocator stats ***
Chunk 0: 176 / 4072 (4.32%)
Max allocated bytes: 13424
Largest frame: 5208
Frames created: 286986
Allocations: 63
Occupancy: 4.32%
```
So for 286986 created frames, we only did 63 allocations. The output was 
printed at the end of the compilation so the printed chunks and occupancy 
aren't very useful.

For a test case that creates lots of function frames, e.g.
```c++
consteval void inc4(int &a) { ++a; }
consteval void inc3(int &a) { inc4(a); }
consteval void inc2(int &a) { inc3(a); }
consteval void inc(int &a)  { inc2(a); }
consteval int foo() {
  int a = 0;

  for (int i = 0; i != 10; ++i)
    inc(a);

  return a;
}
static_assert(foo() == 10);
```
we save 2.5%. If all of the functions in the sample above gain a `char c[1024]` 
local variable, this patch improves the runtime by 12%.



>From 23b3ec2f033c6d550e8862621603480a3771eae9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Tue, 1 Sep 2026 07:15:28 +0200
Subject: [PATCH] FrameAlloc

---
 clang/lib/AST/ByteCode/Context.cpp      |  22 +--
 clang/lib/AST/ByteCode/Context.h        |   3 +
 clang/lib/AST/ByteCode/EvalEmitter.cpp  |   4 +-
 clang/lib/AST/ByteCode/EvalEmitter.h    |   4 +-
 clang/lib/AST/ByteCode/FrameAllocator.h | 185 ++++++++++++++++++++++++
 clang/lib/AST/ByteCode/Interp.cpp       |  33 +----
 clang/lib/AST/ByteCode/Interp.h         |  15 +-
 clang/lib/AST/ByteCode/InterpState.cpp  |  17 ++-
 clang/lib/AST/ByteCode/InterpState.h    |  32 +++-
 9 files changed, 253 insertions(+), 62 deletions(-)
 create mode 100644 clang/lib/AST/ByteCode/FrameAllocator.h

diff --git a/clang/lib/AST/ByteCode/Context.cpp 
b/clang/lib/AST/ByteCode/Context.cpp
index 0212574e4accd..09ec3ce66ee14 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -62,7 +62,7 @@ void Context::isPotentialConstantExprUnevaluated(State 
&Parent, const Expr *E,
   assert(Stk.empty());
   ++EvalID;
   size_t StackSizeBefore = Stk.size();
-  Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
+  Compiler<EvalEmitter> C(*this, *P, Parent, Stk, FrameAlloc);
 
   if (!C.interpretCall(FD, E)) {
     C.cleanup();
@@ -74,7 +74,7 @@ bool Context::evaluateAsRValue(State &Parent, const Expr *E, 
APValue &Result) {
   ++EvalID;
   bool Recursing = !Stk.empty();
   size_t StackSizeBefore = Stk.size();
-  Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
+  Compiler<EvalEmitter> C(*this, *P, Parent, Stk, FrameAlloc);
 
   auto Res = C.interpretExpr(E, /*ConvertResultToRValue=*/E->isGLValue());
 
@@ -105,7 +105,7 @@ bool Context::evaluate(State &Parent, const Expr *E, 
APValue &Result,
   ++EvalID;
   bool Recursing = !Stk.empty();
   size_t StackSizeBefore = Stk.size();
-  Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
+  Compiler<EvalEmitter> C(*this, *P, Parent, Stk, FrameAlloc);
 
   auto Res = C.interpretExpr(E, /*ConvertResultToRValue=*/false,
                              /*DestroyToplevelScope=*/true);
@@ -134,7 +134,7 @@ bool Context::evaluateAsInitializer(State &Parent, const 
VarDecl *VD,
   ++EvalID;
   bool Recursing = !Stk.empty();
   size_t StackSizeBefore = Stk.size();
-  Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
+  Compiler<EvalEmitter> C(*this, *P, Parent, Stk, FrameAlloc);
 
   bool CheckGlobalInitialized =
       shouldBeGloballyIndexed(VD) &&
@@ -164,7 +164,7 @@ bool Context::evaluateAsInitializer(State &Parent, const 
VarDecl *VD,
 bool Context::evaluateDestruction(State &Parent, const VarDecl *VD,
                                   APValue Value) {
   assert(Stk.empty());
-  Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
+  Compiler<EvalEmitter> C(*this, *P, Parent, Stk, FrameAlloc);
 
   auto Res = C.interpretDestructor(VD, Value);
 
@@ -183,7 +183,7 @@ template <typename ResultT>
 bool Context::evaluateStringRepr(State &Parent, const Expr *SizeExpr,
                                  const Expr *PtrExpr, ResultT &Result) {
   assert(Stk.empty());
-  Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
+  Compiler<EvalEmitter> C(*this, *P, Parent, Stk, FrameAlloc);
 
   // Evaluate size value.
   APValue SizeValue;
@@ -282,7 +282,7 @@ bool Context::evaluateCharRange(State &Parent, const Expr 
*SizeExpr,
 bool Context::evaluateString(State &Parent, const Expr *E,
                              std::string &Result) {
   assert(Stk.empty());
-  Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
+  Compiler<EvalEmitter> C(*this, *P, Parent, Stk, FrameAlloc);
 
   auto PtrRes = C.interpretAsPointer(E, [&](InterpState &S, CodePtr OpPC,
                                             const Pointer &Ptr) {
@@ -346,7 +346,7 @@ bool Context::evaluateString(State &Parent, const Expr *E,
 
 std::optional<uint64_t> Context::evaluateStrlen(State &Parent, const Expr *E) {
   assert(Stk.empty());
-  Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
+  Compiler<EvalEmitter> C(*this, *P, Parent, Stk, FrameAlloc);
 
   std::optional<uint64_t> Result;
   auto PtrRes = C.interpretAsPointer(E, [&](InterpState &S, CodePtr OpPC,
@@ -413,7 +413,7 @@ std::optional<uint64_t> Context::evaluateStrlen(State 
&Parent, const Expr *E) {
 std::optional<uint64_t>
 Context::tryEvaluateObjectSize(State &Parent, const Expr *E, unsigned Kind) {
   assert(Stk.empty());
-  Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
+  Compiler<EvalEmitter> C(*this, *P, Parent, Stk, FrameAlloc);
 
   std::optional<uint64_t> Result;
 
@@ -454,7 +454,7 @@ Context::evaluateWithSubstitution(State &Parent, const 
FunctionDecl *Callee,
   }
 
   assert(Stk.empty());
-  Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
+  Compiler<EvalEmitter> C(*this, *P, Parent, Stk, FrameAlloc);
   std::optional<bool> Result =
       C.interpretWithSubstitutions(Callee, Args, This, Condition);
 
@@ -597,7 +597,7 @@ const llvm::fltSemantics 
&Context::getFloatSemantics(QualType T) const {
 
 bool Context::Run(State &Parent, const Function *Func) {
   auto Memory = std::make_unique<char[]>(InterpFrame::allocSize(Func));
-  InterpState State(Parent, *P, Stk, *this, Func);
+  InterpState State(Parent, *P, Stk, FrameAlloc, *this, Func);
   InterpFrame *Frame = new (Memory.get()) InterpFrame(
       State, Func, /*Caller=*/nullptr, CodePtr(), Func->getArgSize());
   State.Current = Frame;
diff --git a/clang/lib/AST/ByteCode/Context.h b/clang/lib/AST/ByteCode/Context.h
index 586b600614596..f80741112edf5 100644
--- a/clang/lib/AST/ByteCode/Context.h
+++ b/clang/lib/AST/ByteCode/Context.h
@@ -16,6 +16,7 @@
 #ifndef LLVM_CLANG_AST_INTERP_CONTEXT_H
 #define LLVM_CLANG_AST_INTERP_CONTEXT_H
 
+#include "FrameAllocator.h"
 #include "InterpStack.h"
 #include "clang/AST/ASTContext.h"
 
@@ -200,6 +201,8 @@ class Context final {
   ASTContext &Ctx;
   /// Interpreter stack, shared across invocations.
   InterpStack Stk;
+  /// (Function) frame allocator, also shared.
+  FrameAllocator FrameAlloc;
   /// Constexpr program.
   std::unique_ptr<Program> P;
   /// ID identifying an evaluation.
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.cpp 
b/clang/lib/AST/ByteCode/EvalEmitter.cpp
index 9d38113ce77c2..569dbe88dc152 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.cpp
+++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp
@@ -18,8 +18,8 @@ using namespace clang;
 using namespace clang::interp;
 
 EvalEmitter::EvalEmitter(Context &Ctx, Program &P, State &Parent,
-                         InterpStack &Stk)
-    : Ctx(Ctx), P(P), S(Parent, P, Stk, Ctx, this), EvalResult(&Ctx) {}
+                         InterpStack &Stk, FrameAllocator &FA)
+    : Ctx(Ctx), P(P), S(Parent, P, Stk, FA, Ctx, this), EvalResult(&Ctx) {}
 
 EvalEmitter::~EvalEmitter() {
   for (auto &V : Locals) {
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.h 
b/clang/lib/AST/ByteCode/EvalEmitter.h
index a80b573a7df8b..1818088053f71 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.h
+++ b/clang/lib/AST/ByteCode/EvalEmitter.h
@@ -24,6 +24,7 @@ namespace interp {
 class Context;
 class Function;
 class InterpStack;
+class FrameAllocator;
 class Program;
 enum Opcode : uint32_t;
 
@@ -61,7 +62,8 @@ class EvalEmitter : public SourceMapper {
   SourceInfo getSource(CodePtr PC) const override { return CurrentSource; }
 
 protected:
-  EvalEmitter(Context &Ctx, Program &P, State &Parent, InterpStack &Stk);
+  EvalEmitter(Context &Ctx, Program &P, State &Parent, InterpStack &Stk,
+              FrameAllocator &FrameAlloc);
 
   virtual ~EvalEmitter();
 
diff --git a/clang/lib/AST/ByteCode/FrameAllocator.h 
b/clang/lib/AST/ByteCode/FrameAllocator.h
new file mode 100644
index 0000000000000..0e5e2855db58f
--- /dev/null
+++ b/clang/lib/AST/ByteCode/FrameAllocator.h
@@ -0,0 +1,185 @@
+//===-------------------- FrameAllocator.h ----------------------*- C++ 
-*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_AST_INTERP_FRAME_ALLOCATOR_H
+#define LLVM_CLANG_AST_INTERP_FRAME_ALLOCATOR_H
+
+namespace clang {
+namespace interp {
+
+// Set this to 1 to collect some light statistics.
+// Print via printStats().
+#define COLLECT_STATS 0
+
+/// Allocator for function frames.
+///
+/// The function frame size includes the size reserved for local variables.
+/// Function frames are allocated strictly in a LIFO manner, i.e. the last
+/// created frame is the first frame that is destroyed.
+///
+/// Since the address a function frame is allocated in needs to stay stable
+/// during the lifetime of the frame, we allocate them here in chunks.
+///
+/// A chunk is of (at least) MinChunkSize size and only gets deallocated once 
it
+/// is empty AND the previous chunk is also empty.
+///
+class FrameAllocator final {
+private:
+  struct Chunk {
+    Chunk *Prev = nullptr;
+    unsigned Size;
+    unsigned Used = 0;
+    alignas(sizeof(void *)) char Memory[1];
+
+    Chunk(unsigned Size) : Size(Size) {}
+    unsigned bytesUnused() const { return Size - Used; }
+  };
+  static constexpr unsigned MinChunkSize = (4u * 1024u) - sizeof(Chunk);
+
+  Chunk *Tail = nullptr;
+#if COLLECT_STATS
+  size_t MaxSize = 0;
+  unsigned LargestFrame = 0;
+  unsigned NumFrames = 0;
+  unsigned NumAllocs = 0;
+#endif
+
+public:
+  FrameAllocator() = default;
+  FrameAllocator(FrameAllocator &) = delete;
+  FrameAllocator(FrameAllocator &&) = delete;
+  ~FrameAllocator() {
+    while (Tail)
+      deallocTail();
+  }
+
+  char *reserve(unsigned Size) {
+    if (LLVM_UNLIKELY(!Tail))
+      allocateNewChunk(std::max(Size, MinChunkSize));
+
+    assert(Tail);
+
+    char *Mem;
+    if (Chunk *C = getChunkToUse(Size); C->bytesUnused() >= Size) {
+      Mem = &C->Memory[C->Used];
+      C->Used += Size;
+    } else {
+      // We need to allocate a new chunk. If the requested size is larger than
+      // the minimum, use that.
+      allocateNewChunk(std::max(Size, MinChunkSize));
+      Tail->Used += Size;
+      Mem = Tail->Memory;
+    }
+
+#if COLLECT_STATS
+    LargestFrame = std::max(Size, LargestFrame);
+    MaxSize = std::max(MaxSize, countAllBytes());
+    ++NumFrames;
+#endif
+
+    return Mem;
+  }
+
+  /// Pop the memory of the last function frame that was added.
+  /// The passed \c FrameSize needs to match the latest size passed to
+  /// reserve(). If it doesn't, bad things will happen.
+  void pop(unsigned FrameSize) {
+    // Frame destructor must've already been called.
+    assert(Tail);
+    Chunk *C = Tail->Used == 0 ? Tail->Prev : Tail;
+    assert(C);
+    assert(FrameSize <= C->Used);
+    C->Used -= FrameSize;
+
+    // Deallocate the tail chunk *if* it is empty _and_ the previous chunk is
+    // also empty.
+    if (Tail->Used == 0 && Tail->Prev && Tail->Prev->Used == 0)
+      deallocTail();
+  }
+
+private:
+  /// Return the chunk to use to allocate a new frame into.
+  /// This is not always this->Tail, since Tail might be empty AND have a
+  /// previous chunk. In that case, we use the previous chunk, if it does have
+  /// \p Size bytes left.
+  Chunk *getChunkToUse(unsigned Size) {
+    assert(Tail);
+    if (Tail->Used == 0 && Tail->Prev && Tail->Prev->bytesUnused() >= Size)
+      return Tail->Prev;
+    return Tail;
+  }
+
+  void allocateNewChunk(unsigned Size) {
+    char *Mem = new char[sizeof(Chunk) + Size];
+    auto *C = new (Mem) Chunk(Size);
+    C->Prev = Tail;
+    Tail = C;
+
+    assert(Tail);
+
+#if COLLECT_STATS
+    ++NumAllocs;
+#endif
+  }
+
+  void deallocTail() {
+    assert(Tail);
+    Chunk *C = Tail;
+    Tail = Tail->Prev;
+    delete[] reinterpret_cast<char *>(C);
+  }
+
+#if COLLECT_STATS
+  size_t countAllBytes() const {
+    size_t Result = 0;
+    Chunk *C = Tail;
+    while (C) {
+      Result += C->Size + sizeof(Chunk);
+      C = C->Prev;
+    }
+    return Result;
+  }
+  void printStats() const {
+    llvm::errs() << "*** FrameAllocator stats ***\n";
+    if (!Tail) {
+      llvm::errs() << "empty\n";
+      return;
+    }
+
+    Chunk *C = Tail;
+    unsigned N = 0;
+    while (C) {
+      llvm::errs() << "Chunk " << N << ": " << C->Used << " / " << C->Size
+                   << " (";
+      double Percentage =
+          (static_cast<double>(C->Used) / static_cast<double>(C->Size)) * 100;
+      llvm::errs() << llvm::formatv("{0:2}", Percentage) << "%)\n";
+      ++N;
+      C = C->Prev;
+    }
+    llvm::errs() << "Max allocated bytes: " << MaxSize << '\n';
+    llvm::errs() << "Largest frame: " << LargestFrame << '\n';
+    llvm::errs() << "Frames created: " << NumFrames << '\n';
+    llvm::errs() << "Allocations: " << NumAllocs << '\n';
+
+    llvm::errs() << "Occupancy: ";
+    size_t AllUsed = 0;
+    size_t AllSize = 0;
+    for (Chunk *C = Tail; C; C = C->Prev) {
+      AllUsed += C->Used;
+      AllSize += C->Size;
+    }
+    double Occupancy =
+        (static_cast<double>(AllUsed) / static_cast<double>(AllSize)) * 100;
+    llvm::errs() << llvm::formatv("{0:2}", Occupancy) << "%\n";
+  }
+#endif
+};
+} // namespace interp
+} // namespace clang
+
+#endif
diff --git a/clang/lib/AST/ByteCode/Interp.cpp 
b/clang/lib/AST/ByteCode/Interp.cpp
index 8be502e851504..89a3ad70ab522 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1885,22 +1885,13 @@ bool CallVar(InterpState &S, CodePtr OpPC, const 
Function *Func,
   if (!CheckCallDepth(S, OpPC))
     return false;
 
-  auto *Memory = new char[InterpFrame::allocSize(Func)];
-  auto *NewFrame = new (Memory) InterpFrame(S, Func, S.PC, VarArgSize);
-  InterpFrame *FrameBefore = S.Current;
+  InterpFrame *NewFrame = S.allocFrame(Func, S.PC, VarArgSize);
   S.Current = NewFrame;
 
   InterpStateCCOverride CCOverride(S, Func->isImmediate());
-  if (Interpret(S)) {
-    assert(S.Current == FrameBefore);
-    return true;
-  }
-
-  InterpFrame::free(NewFrame);
-  // Interpreting the function failed somehow. Reset to
-  // previous state.
-  S.Current = FrameBefore;
-  return false;
+  bool Success = Interpret(S);
+  S.resetCurrentFrame();
+  return Success;
 }
 bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
           uint32_t VarArgSize) {
@@ -1978,9 +1969,7 @@ bool Call(InterpState &S, CodePtr OpPC, const Function 
*Func,
   if (!CheckCallDepth(S, OpPC))
     return cleanup();
 
-  auto *Memory = new char[InterpFrame::allocSize(Func)];
-  auto *NewFrame = new (Memory) InterpFrame(S, Func, S.PC, VarArgSize);
-  InterpFrame *FrameBefore = S.Current;
+  InterpFrame *NewFrame = S.allocFrame(Func, S.PC, VarArgSize);
   S.Current = NewFrame;
 
   InterpStateCCOverride CCOverride(S, Func->isImmediate());
@@ -1989,16 +1978,8 @@ bool Call(InterpState &S, CodePtr OpPC, const Function 
*Func,
   if (InstancePtrTracked)
     S.InitializingPtrs.pop_back();
 
-  if (!Success) {
-    InterpFrame::free(NewFrame);
-    // Interpreting the function failed somehow. Reset to
-    // previous state.
-    S.Current = FrameBefore;
-    return false;
-  }
-
-  assert(S.Current == FrameBefore);
-  return true;
+  S.resetCurrentFrame();
+  return Success;
 }
 
 static bool getDynamicDecl(InterpState &S, CodePtr OpPC, PtrView TypePtr,
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index d58f3ce46d41a..4a0de3954fb68 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -289,41 +289,32 @@ PRESERVE_NONE bool Ret(InterpState &S) {
   const T &Ret = S.Stk.pop<T>();
 
   assert(S.Current);
-
 #ifndef NDEBUG
   assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame");
 #endif
 
-  InterpFrame *Caller = S.Current->Caller;
-
   // This only happens via Context::Run().
-  if (!Caller)
+  if (S.Current->isBottomFrame())
     return true;
 
   cleanupAfterFunctionCall(S, S.Current->getFunction());
-
   S.PC = S.Current->getRetPC();
-  InterpFrame::free(S.Current);
-  S.Current = Caller;
   S.Stk.push<T>(Ret);
   return true;
 }
 
 PRESERVE_NONE inline bool RetVoid(InterpState &S) {
+  assert(S.Current);
 #ifndef NDEBUG
   assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame");
 #endif
 
-  InterpFrame *Caller = S.Current->Caller;
   // This only happens via Context::Run().
-  if (!Caller)
+  if (S.Current->isBottomFrame())
     return true;
 
   cleanupAfterFunctionCall(S, S.Current->getFunction());
-
   S.PC = S.Current->getRetPC();
-  InterpFrame::free(S.Current);
-  S.Current = Caller;
   return true;
 }
 
diff --git a/clang/lib/AST/ByteCode/InterpState.cpp 
b/clang/lib/AST/ByteCode/InterpState.cpp
index 4143a39dac83f..89b9f3e1a4b44 100644
--- a/clang/lib/AST/ByteCode/InterpState.cpp
+++ b/clang/lib/AST/ByteCode/InterpState.cpp
@@ -18,10 +18,11 @@ using namespace clang;
 using namespace clang::interp;
 
 InterpState::InterpState(const State &Parent, Program &P, InterpStack &Stk,
-                         Context &Ctx, SourceMapper *M)
-    : State(Ctx.getASTContext(), Parent.getEvalStatus()), M(M), P(P), Stk(Stk),
-      Ctx(Ctx), BottomFrame(*this), Current(&BottomFrame),
-      StepsLeft(Ctx.getLangOpts().ConstexprStepLimit),
+                         FrameAllocator &FrameAlloc, Context &Ctx,
+                         SourceMapper *M)
+    : State(Ctx.getASTContext(), Parent.getEvalStatus()), M(M),
+      FrameAlloc(FrameAlloc), P(P), Stk(Stk), Ctx(Ctx), BottomFrame(*this),
+      Current(&BottomFrame), StepsLeft(Ctx.getLangOpts().ConstexprStepLimit),
       InfiniteSteps(StepsLeft == 0), EvalID(Ctx.getEvalID()) {
   InConstantContext = Parent.InConstantContext;
   CheckingPotentialConstantExpression =
@@ -31,10 +32,12 @@ InterpState::InterpState(const State &Parent, Program &P, 
InterpStack &Stk,
 }
 
 InterpState::InterpState(const State &Parent, Program &P, InterpStack &Stk,
+                         FrameAllocator &FrameAlloc,
+
                          Context &Ctx, const Function *Func)
-    : State(Ctx.getASTContext(), Parent.getEvalStatus()), M(nullptr), P(P),
-      Stk(Stk), Ctx(Ctx), BottomFrame(*this), Current(&BottomFrame),
-      StepsLeft(Ctx.getLangOpts().ConstexprStepLimit),
+    : State(Ctx.getASTContext(), Parent.getEvalStatus()), M(nullptr),
+      FrameAlloc(FrameAlloc), P(P), Stk(Stk), Ctx(Ctx), BottomFrame(*this),
+      Current(&BottomFrame), StepsLeft(Ctx.getLangOpts().ConstexprStepLimit),
       InfiniteSteps(StepsLeft == 0), EvalID(Ctx.getEvalID()) {
   InConstantContext = Parent.InConstantContext;
   CheckingPotentialConstantExpression =
diff --git a/clang/lib/AST/ByteCode/InterpState.h 
b/clang/lib/AST/ByteCode/InterpState.h
index 5977162df445c..9562f7495c409 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -16,6 +16,7 @@
 #include "Context.h"
 #include "DynamicAllocator.h"
 #include "Floating.h"
+#include "FrameAllocator.h"
 #include "Function.h"
 #include "InterpFrame.h"
 #include "InterpStack.h"
@@ -27,6 +28,7 @@ class Context;
 class SourceMapper;
 
 struct StdAllocatorCaller {
+
   const Expr *Call = nullptr;
   QualType AllocType;
   explicit operator bool() { return Call; }
@@ -42,10 +44,11 @@ enum class EvaluationKind : uint8_t {
 /// Interpreter context.
 class InterpState final : public State {
 public:
-  InterpState(const State &Parent, Program &P, InterpStack &Stk, Context &Ctx,
+  InterpState(const State &Parent, Program &P, InterpStack &Stk,
+              FrameAllocator &FrameAlloc, Context &Ctx,
               SourceMapper *M = nullptr);
-  InterpState(const State &Parent, Program &P, InterpStack &Stk, Context &Ctx,
-              const Function *Func);
+  InterpState(const State &Parent, Program &P, InterpStack &Stk,
+              FrameAllocator &FrameAlloc, Context &Ctx, const Function *Func);
 
   ~InterpState();
 
@@ -169,6 +172,27 @@ class InterpState final : public State {
 
   unsigned newStringID() { return StringID++; }
 
+  /// Allocate memory and create a new InterpFrame for the given function.
+  template <typename... Ts>
+  InterpFrame *allocFrame(const Function *F, Ts &&...Args) {
+    unsigned FrameSize = InterpFrame::allocSize(F);
+    InterpFrame *NewFrame = new (FrameAlloc.reserve(FrameSize))
+        InterpFrame(*this, F, std::forward<Ts>(Args)...);
+    assert(NewFrame);
+    return NewFrame;
+  }
+
+  /// Free resources associated with the current frame and set the caller to be
+  /// the new current frame.
+  void resetCurrentFrame() {
+    assert(Current);
+    unsigned CurrentSize = InterpFrame::allocSize(Current->getFunction());
+    InterpFrame *Caller = Current->Caller;
+    Current->~InterpFrame();
+    FrameAlloc.pop(CurrentSize);
+    Current = Caller;
+  }
+
 private:
   friend class EvaluationResult;
   friend class InterpStateCCOverride;
@@ -183,6 +207,8 @@ class InterpState final : public State {
   /// Diagnose that we've reached the constexpr step limit.
   bool diagnoseStepLimitExceeded(CodePtr OpPC);
 
+  FrameAllocator &FrameAlloc;
+
 public:
   CodePtr PC;
   /// Reference to the module containing all bytecode.

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

Reply via email to