[clang] [clang][bytecode] Add a function frame allocator (PR #220229)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 17 01:33:01 PDT 2026
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/220229
>From 650dacfe3b3f4436b4877702ff9bdf2a3616ff52 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
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 | 209 ++++++++++++++++++++++++
clang/lib/AST/ByteCode/Interp.cpp | 39 +----
clang/lib/AST/ByteCode/Interp.h | 15 +-
clang/lib/AST/ByteCode/InterpFrame.cpp | 8 +-
clang/lib/AST/ByteCode/InterpFrame.h | 14 --
clang/lib/AST/ByteCode/InterpState.cpp | 24 ++-
clang/lib/AST/ByteCode/InterpState.h | 32 +++-
11 files changed, 280 insertions(+), 94 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 ab53ec1eda1dbb..d96bc68217576a 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,
@@ -415,7 +415,7 @@ std::optional<uint64_t> Context::tryEvaluateObjectSize(State &Parent,
unsigned Kind,
bool IsDynamic) {
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.interpretAsLValuePointer(E, [&](InterpState &S, CodePtr OpPC,
@@ -452,7 +452,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);
@@ -595,7 +595,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 2a250ad119842e..a555896a822a56 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 93b332cda6e197..d0f41ec9fd0753 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) {}
/// Clean up all our resources. This needs to done in failed evaluations before
/// we call InterpStack::clear(), because there might be a Pointer on the stack
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.h b/clang/lib/AST/ByteCode/EvalEmitter.h
index 4f02f796097aae..3e2b0b3bfcc93a 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);
/// Define a label.
void emitLabel(LabelTy Label);
diff --git a/clang/lib/AST/ByteCode/FrameAllocator.h b/clang/lib/AST/ByteCode/FrameAllocator.h
new file mode 100644
index 00000000000000..251a25a9acd43b
--- /dev/null
+++ b/clang/lib/AST/ByteCode/FrameAllocator.h
@@ -0,0 +1,209 @@
+//===-------------------- 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
+
+#include "llvm/Support/Compiler.h"
+#ifndef NDEBUG
+#include "llvm/ADT/SmallVector.h"
+#endif
+#include <algorithm>
+#include <cassert>
+#include <new>
+
+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
+
+#ifndef NDEBUG
+ llvm::SmallVector<unsigned> FrameSizes;
+#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);
+
+#ifndef NDEBUG
+ FrameSizes.push_back(Size);
+#endif
+
+ 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) {
+#ifndef NDEBUG
+ assert(FrameSize == FrameSizes.back());
+#endif
+ // 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.
+ // Since we create chunks specicially for large frames, we need to loop
+ // here.
+ while (Tail->Used == 0 && Tail->Prev && Tail->Prev->Used == 0)
+ deallocTail();
+
+#ifndef NDEBUG
+ FrameSizes.pop_back();
+#endif
+ }
+
+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 5b0422d9ae9bdf..75891a867c1165 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2034,22 +2034,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,
@@ -2128,9 +2119,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());
@@ -2139,16 +2128,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, const Pointer &Ptr,
@@ -3693,11 +3674,7 @@ PRESERVE_NONE static bool InterpNext(InterpState &S) {
#endif
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.
- assert(!S.Current->isRoot());
+ assert(S.Current->getFunction());
S.PC = S.Current->getFunction()->getCodeBegin();
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 8db8fbb1e5e01d..edbf2adcda6370 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -287,41 +287,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/InterpFrame.cpp b/clang/lib/AST/ByteCode/InterpFrame.cpp
index 4079e9718f1c30..413f2b51713b51 100644
--- a/clang/lib/AST/ByteCode/InterpFrame.cpp
+++ b/clang/lib/AST/ByteCode/InterpFrame.cpp
@@ -76,15 +76,9 @@ InterpFrame::~InterpFrame() {
for (unsigned I = 0, N = Func->getNumWrittenParams(); I != N; ++I)
S.deallocate(argBlock(I));
- // When destroying the InterpFrame, call the Dtor for all block
+ // When destroying the InterpFrame, call the Dtor for all blocks
// that haven't been destroyed via a destroy() op yet.
// This happens when the execution is interruped midway-through.
- destroyScopes();
-}
-
-void InterpFrame::destroyScopes() {
- 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 c195165e6219f6..acc9c337bfb1b2 100644
--- a/clang/lib/AST/ByteCode/InterpFrame.h
+++ b/clang/lib/AST/ByteCode/InterpFrame.h
@@ -56,19 +56,9 @@ class InterpFrame final : public Frame {
return Func->getName();
}
- static void free(InterpFrame *F) {
- if (!F->isBottomFrame()) {
- F->~InterpFrame();
- delete[] reinterpret_cast<char *>(F);
- } else {
- F->~InterpFrame();
- }
- }
-
/// Invokes the destructors for a scope.
void destroy(unsigned Idx);
void initScope(unsigned Idx);
- void destroyScopes();
void enableLocal(unsigned Idx);
bool isLocalEnabled(unsigned Idx) const {
return localInlineDesc(Idx)->IsActive;
@@ -143,10 +133,6 @@ class InterpFrame final : public Frame {
return stackRef<Pointer>(0);
}
- /// Checks if the frame is a root frame - return should quit the interpreter.
- bool isRoot() const { return !Func; }
-
- /// Returns the return address of the frame.
CodePtr getRetPC() const { return RetPC; }
/// Returns the return address of the opcode in the caller frame.
CodePtr getRetOpPC() const {
diff --git a/clang/lib/AST/ByteCode/InterpState.cpp b/clang/lib/AST/ByteCode/InterpState.cpp
index 4143a39dac83f8..fa59b524086119 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 =
@@ -51,12 +54,7 @@ bool InterpState::inConstantContext() const {
}
InterpState::~InterpState() {
- while (Current && !Current->isBottomFrame()) {
- InterpFrame *Next = Current->Caller;
- delete Current;
- Current = Next;
- }
- BottomFrame.destroyScopes();
+ assert(Current->isBottomFrame());
while (DeadBlocks) {
DeadBlock *Next = DeadBlocks->Next;
diff --git a/clang/lib/AST/ByteCode/InterpState.h b/clang/lib/AST/ByteCode/InterpState.h
index ac7eb8060e032a..897699aa1b5ea6 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();
@@ -190,6 +193,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;
@@ -204,6 +228,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.
More information about the cfe-commits
mailing list