Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 54 additions & 8 deletions src/evm/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<EVMFrame> &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);
Expand All @@ -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<uint64_t>(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<uint8_t> &Data) {
Expand Down
25 changes: 13 additions & 12 deletions src/evm/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ class InterpreterExecContext {
private:
runtime::EVMInstance *Inst;
std::vector<EVMFrame> 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<uint8_t> ReturnData;
evmc::Result ExeResult;
Expand All @@ -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; }
Expand Down
Loading