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