diff --git a/src/evm/interpreter.cpp b/src/evm/interpreter.cpp index 670746bc..d67e32cb 100644 --- a/src/evm/interpreter.cpp +++ b/src/evm/interpreter.cpp @@ -264,10 +264,55 @@ handleExecutionStatus(zen::evm::EVMFrame *&Frame, } // namespace -EVMFrame *InterpreterExecContext::allocTopFrame(evmc_message *Msg) { - FrameStack.emplace_back(); +namespace { + +/// Beyond this retained capacity, Memory / CallData are shrink_to_fit() after +/// clear() so reusing EVMFrame objects does not grow RSS without bound. +constexpr size_t kMaxRetainedFrameBufferBytes = 1024 * 1024; + +static void clearFrameTransientBuffers(EVMFrame &Frame) { + Frame.Memory.clear(); + if (Frame.Memory.capacity() > kMaxRetainedFrameBufferBytes) + Frame.Memory.shrink_to_fit(); + Frame.CallData.clear(); + if (Frame.CallData.capacity() > kMaxRetainedFrameBufferBytes) + Frame.CallData.shrink_to_fit(); +} - EVMFrame &Frame = FrameStack.back(); +static void releaseAllFrameBuffersIfLarge(std::vector &Frames) { + for (EVMFrame &F : Frames) + clearFrameTransientBuffers(F); +} + +} // namespace + +void InterpreterExecContext::resetForNewCall(runtime::EVMInstance *NewInst) { + Inst = NewInst; + FrameCount = 0; + releaseAllFrameBuffersIfLarge(FrameStack); + Status = EVMC_SUCCESS; + ReturnData.clear(); + IsJump = false; + ExeResult = evmc::Result{EVMC_SUCCESS, 0, 0}; +} + +EVMFrame *InterpreterExecContext::allocTopFrame(evmc_message *Msg) { + const bool Reuse = (FrameCount < FrameStack.size()); + if (!Reuse) { + FrameStack.emplace_back(); + } + EVMFrame &Frame = FrameStack[FrameCount]; + if (Reuse) { + // Reuse an existing EVMFrame object – avoids zero-initializing the + // 32 KB uint256 stack array. Only reset the fields that matter. + Frame.Sp = 0; + Frame.Pc = 0; + Frame.Host = nullptr; + clearFrameTransientBuffers(Frame); + Frame.MTx = {}; + Frame.Value = 0; + } + ++FrameCount; Frame.Msg = *Msg; Inst->pushMessage(&Frame.Msg); @@ -279,19 +324,20 @@ EVMFrame *InterpreterExecContext::allocTopFrame(evmc_message *Msg) { // We only need to free the last frame (top of the stack), // since EVM's control flow is purely stack-based. void InterpreterExecContext::freeBackFrame() { - if (FrameStack.empty()) + if (FrameCount == 0) return; - EVMFrame &Frame = FrameStack.back(); + EVMFrame &Frame = FrameStack[FrameCount - 1]; Inst->setGas(static_cast(Frame.Msg.gas)); - if (FrameStack.size() > 1) { + if (FrameCount > 1) { Inst->popMessage(); } - // Destroy frame (and its message) - FrameStack.pop_back(); + // Logically free the frame but keep the EVMFrame object alive so its + // 32 KB stack array can be reused by the next allocTopFrame(). + --FrameCount; } void InterpreterExecContext::setCallData(const std::vector &Data) { diff --git a/src/evm/interpreter.h b/src/evm/interpreter.h index 1fcf0ef5..35e6ec24 100644 --- a/src/evm/interpreter.h +++ b/src/evm/interpreter.h @@ -71,6 +71,10 @@ class InterpreterExecContext { private: runtime::EVMInstance *Inst; std::vector FrameStack; + // Number of logically active frames. May be less than FrameStack.size() + // because we keep previously-allocated EVMFrame objects alive to avoid + // re-zeroing the 32 KB uint256 stack array on every call. + size_t FrameCount = 0; evmc_status_code Status = EVMC_SUCCESS; std::vector ReturnData; evmc::Result ExeResult; @@ -82,25 +86,22 @@ class InterpreterExecContext { FrameStack.reserve(1024); // max call depth } - /// Reset state for reuse across calls. Keeps allocated capacity to avoid - /// re-allocating the ~32KB EVMFrame on every call. - void resetForNewCall(runtime::EVMInstance *NewInst) { - Inst = NewInst; - FrameStack.clear(); // keeps vector capacity - Status = EVMC_SUCCESS; - ReturnData.clear(); // keeps vector capacity - IsJump = false; - ExeResult = evmc::Result{EVMC_SUCCESS, 0, 0}; - } + /// Reset state for reuse across calls. Keeps allocated EVMFrame objects + /// (and their 32 KB stack arrays) alive so that the next allocTopFrame() + /// only needs to reset a few scalar fields instead of zero-initializing + /// the entire array. Per-frame Memory and CallData are cleared; if either + /// buffer's capacity is large, it may be released to cap steady-state RSS + /// (see interpreter.cpp). + void resetForNewCall(runtime::EVMInstance *NewInst); EVMFrame *allocTopFrame(evmc_message *Msg); void freeBackFrame(); EVMFrame *getCurFrame() { - if (FrameStack.empty()) { + if (FrameCount == 0) { return nullptr; } - return &FrameStack.back(); + return &FrameStack[FrameCount - 1]; } runtime::EVMInstance *getInstance() { return Inst; }