diff --git a/src/runtime/runtime.cpp b/src/runtime/runtime.cpp index 0f2a116c..e4266221 100644 --- a/src/runtime/runtime.cpp +++ b/src/runtime/runtime.cpp @@ -667,11 +667,34 @@ void Runtime::callWasmFunctionInInterpMode(Instance &Inst, uint32_t FuncIdx, #ifdef ZEN_ENABLE_EVM void Runtime::callEVMInInterpMode(EVMInstance &Inst, evmc_message &Msg, evmc::Result &Result) { - evm::InterpreterExecContext Ctx(&Inst); - evm::BaseInterpreter Interpreter(Ctx); - Ctx.allocTopFrame(&Msg); - Interpreter.interpret(); - Result = std::move(const_cast(Ctx.getExeResult())); + // Reuse a thread-local InterpreterExecContext for top-level calls to avoid + // re-allocating the ~33 KB EVMFrame (1024 × uint256 stack) on every call. + // For nested calls (CALL/CREATE re-entering this function via Host->call()), + // we must create a fresh context to avoid corrupting the outer call's state. + static thread_local evm::InterpreterExecContext *TLCtx = nullptr; + static thread_local bool TLCtxInUse = false; + + if (!TLCtxInUse) { + // Top-level call: reuse the cached context + if (!TLCtx) { + TLCtx = new evm::InterpreterExecContext(&Inst); + } else { + TLCtx->resetForNewCall(&Inst); + } + TLCtxInUse = true; + evm::BaseInterpreter Interpreter(*TLCtx); + TLCtx->allocTopFrame(&Msg); + Interpreter.interpret(); + Result = std::move(const_cast(TLCtx->getExeResult())); + TLCtxInUse = false; + } else { + // Nested call: use a stack-local context to avoid corrupting the outer one + evm::InterpreterExecContext Ctx(&Inst); + evm::BaseInterpreter Interpreter(Ctx); + Ctx.allocTopFrame(&Msg); + Interpreter.interpret(); + Result = std::move(const_cast(Ctx.getExeResult())); + } } #ifdef ZEN_ENABLE_VIRTUAL_STACK @@ -710,7 +733,15 @@ void Runtime::callEVMMain(EVMInstance &Inst, evmc_message &Msg, #endif #ifdef ZEN_ENABLE_VIRTUAL_STACK - if (Msg.depth == 0) { + // Interpreter mode does not need a virtual stack: it manages call depth + // via InterpreterExecContext::FrameStack and never emits native code that + // could overflow the physical stack in an unbounded way. Skipping the + // virtual-stack allocation/mprotect/setjmp round-trip on every call + // eliminates ~50 % of the per-execution overhead measured on ERC-20 + // transfers. + if (getConfig().Mode == RunMode::InterpMode) { + callEVMMainOnPhysStack(Inst, Msg, Result); + } else if (Msg.depth == 0) { VirtualStackInfo StackInfo; StackInfo.SavedPtr1 = &Inst; StackInfo.SavedPtr2 = &Msg;