[clang] 66ccc4c - [clang][bytecode] Add a function frame allocator (#220229)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 17 06:23:05 PDT 2026
Author: Timm Baeder
Date: 2026-09-17T15:22:59+02:00
New Revision: 66ccc4cee0b489cbb80847628c8582547795c080
URL: https://github.com/llvm/llvm-project/commit/66ccc4cee0b489cbb80847628c8582547795c080
DIFF: https://github.com/llvm/llvm-project/commit/66ccc4cee0b489cbb80847628c8582547795c080.diff
LOG: [clang][bytecode] Add a function frame allocator (#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%.
Added:
clang/lib/AST/ByteCode/FrameAllocator.h
Modified:
clang/lib/AST/ByteCode/Context.cpp
clang/lib/AST/ByteCode/Context.h
clang/lib/AST/ByteCode/EvalEmitter.cpp
clang/lib/AST/ByteCode/EvalEmitter.h
clang/lib/AST/ByteCode/Interp.cpp
clang/lib/AST/ByteCode/Interp.h
clang/lib/AST/ByteCode/InterpFrame.cpp
clang/lib/AST/ByteCode/InterpFrame.h
clang/lib/AST/ByteCode/InterpState.cpp
clang/lib/AST/ByteCode/InterpState.h
Removed:
################################################################################
diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp
index ab53ec1eda1db..d96bc68217576 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 2a250ad119842..a555896a822a5 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 93b332cda6e19..d0f41ec9fd075 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 4f02f796097aa..3e2b0b3bfcc93 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 0000000000000..8f5b74df62497
--- /dev/null
+++ b/clang/lib/AST/ByteCode/FrameAllocator.h
@@ -0,0 +1,210 @@
+//===-------------------- 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 FRAME_ALLOCATOR_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;
+ const 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 FRAME_ALLOCATOR_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 FRAME_ALLOCATOR_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 FRAME_ALLOCATOR_COLLECT_STATS
+ ++NumAllocs;
+#endif
+ }
+
+ void deallocTail() {
+ assert(Tail);
+ Chunk *C = Tail;
+ Tail = Tail->Prev;
+ delete[] reinterpret_cast<char *>(C);
+ }
+
+#if FRAME_ALLOCATOR_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 de7d8ce188f97..cf0f0d0d337df 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,
@@ -3699,11 +3680,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 8db8fbb1e5e01..edbf2adcda637 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 4079e9718f1c3..413f2b51713b5 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 c195165e6219f..acc9c337bfb1b 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 4143a39dac83f..fa59b52408611 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 ac7eb8060e032..920197d8021c0 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -16,10 +16,12 @@
#include "Context.h"
#include "DynamicAllocator.h"
#include "Floating.h"
+#include "FrameAllocator.h"
#include "Function.h"
#include "InterpFrame.h"
#include "InterpStack.h"
#include "State.h"
+#include <limits>
namespace clang {
namespace interp {
@@ -27,6 +29,7 @@ class Context;
class SourceMapper;
struct StdAllocatorCaller {
+
const Expr *Call = nullptr;
QualType AllocType;
explicit operator bool() { return Call; }
@@ -42,10 +45,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 +194,28 @@ 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) {
+ size_t FrameSize = InterpFrame::allocSize(F);
+ assert(FrameSize < std::numeric_limits<unsigned>::max());
+ 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 +230,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