https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/220229
>From aa5b596c5e0f604b8a37b9ef0d6e3e7ca522d74a 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 | 204 ++++++++++++++++++++++++ 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, 275 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 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 80eb593c2f6c4..d5a27e8c2a0e7 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..0937afd64969b --- /dev/null +++ b/clang/lib/AST/ByteCode/FrameAllocator.h @@ -0,0 +1,204 @@ +//===-------------------- 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" +#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 + SmallVector<unsigned> Sizes; +#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 + Sizes.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 == Sizes.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(); + + Sizes.pop_back(); + } + +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 a41cc1f5a564b..67aa62808a068 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1931,22 +1931,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, @@ -2025,9 +2016,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()); @@ -2036,16 +2025,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, @@ -3443,11 +3424,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 2aa5aee788230..d23ae37e15850 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/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..897699aa1b5ea 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. _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
