diff --git a/src/evm/interpreter.h b/src/evm/interpreter.h index 398c4f91..90775c14 100644 --- a/src/evm/interpreter.h +++ b/src/evm/interpreter.h @@ -10,7 +10,6 @@ #include "intx/intx.hpp" #include -#include #include namespace zen { @@ -70,7 +69,7 @@ struct EVMFrame { class InterpreterExecContext { private: runtime::EVMInstance *Inst; - std::deque FrameStack; + std::vector FrameStack; evmc_status_code Status = EVMC_SUCCESS; std::vector ReturnData; evmc::Result ExeResult; @@ -80,6 +79,17 @@ class InterpreterExecContext { InterpreterExecContext(runtime::EVMInstance *Inst) : Inst(Inst) {} + /// 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}; + } + EVMFrame *allocTopFrame(evmc_message *Msg); void freeBackFrame(); diff --git a/src/runtime/evm_instance.cpp b/src/runtime/evm_instance.cpp index dc8ebbd3..8233402e 100644 --- a/src/runtime/evm_instance.cpp +++ b/src/runtime/evm_instance.cpp @@ -60,6 +60,42 @@ EVMInstanceUniquePtr EVMInstance::newEVMInstance(Isolation &Iso, EVMInstance::~EVMInstance() {} +void EVMInstance::resetForNewCall(evmc_revision NewRev) { + // Reset gas accounting + Gas = 0; + GasRefund = 0; + Rev = NewRev; + InstanceExitCode = 0; + Err = common::ErrorCode::NoError; + + // Reset message stack (clear but keep capacity) + CurrentMessage = nullptr; + MessageStack.clear(); + GasRefundStack.clear(); + + // Reset memory: keep the 16MB allocation, just reset the size + MemoryBase = nullptr; + MemorySize = 0; + // Don't release Memory - reuse the allocation on next pushMessage() + MemoryStack.clear(); + + // Reset output + ReturnData.clear(); + ExeResult = evmc::Result{EVMC_SUCCESS, 0, 0}; + + // Reset execution cache: clear() keeps allocated bucket arrays, + // avoiding repeated alloc/free of unordered_map internals. + InstanceExecutionCache.BlockHashes.clear(); + InstanceExecutionCache.BlobHashes.clear(); + InstanceExecutionCache.CalldataLoads.clear(); + InstanceExecutionCache.ExtcodeHashes.clear(); + InstanceExecutionCache.Keccak256Results.clear(); + InstanceExecutionCache.TxContextCached = false; + + // Reset JIT stack + EVMStackSize = 0; +} + void EVMInstance::setGas(uint64_t NewGas) { Gas = NewGas; } void EVMInstance::pushMessage(evmc_message *Msg) { diff --git a/src/runtime/evm_instance.h b/src/runtime/evm_instance.h index a21a36b9..bfe4c27e 100644 --- a/src/runtime/evm_instance.h +++ b/src/runtime/evm_instance.h @@ -101,6 +101,10 @@ class EVMInstance final : public RuntimeObject { void setError(const Error &E) { Err = E; } void clearError() { Err = ErrorCode::NoError; } + /// Reset instance state for reuse in interpreter mode. + /// Avoids the cost of destroy + recreate on every EVMC execute() call. + void resetForNewCall(evmc_revision NewRev); + // can only called by hostapi directly // setExceptionByHostapi must be inline to capture the hostapi's frame // pointer diff --git a/src/vm/dt_evmc_vm.cpp b/src/vm/dt_evmc_vm.cpp index 2ee646ba..d05d486f 100644 --- a/src/vm/dt_evmc_vm.cpp +++ b/src/vm/dt_evmc_vm.cpp @@ -4,6 +4,7 @@ #include "dt_evmc_vm.h" #include "common/enums.h" #include "common/errors.h" +#include "evm/interpreter.h" #include "runtime/config.h" #include "runtime/evm_instance.h" #include "runtime/isolation.h" @@ -40,33 +41,64 @@ class ScopedConfig { RuntimeConfig PreviousConfig; }; -// CRC32 checksum -uint32_t crc32(const uint8_t *Data, size_t Size) { - static uint32_t Table[256]; - static bool TableInitialized = false; - if (!TableInitialized) { - for (uint32_t I = 0; I < 256; ++I) { - uint32_t C = I; - for (int J = 0; J < 8; ++J) - C = (C & 1) ? (0xEDB88320u ^ (C >> 1)) : (C >> 1); - Table[I] = C; - } - TableInitialized = true; +// ---- Address-based module cache types ---- + +struct CodeAddrRevKey { + evmc_address Addr; + evmc_revision Rev; +}; + +struct CodeAddrRevHash { + size_t operator()(const CodeAddrRevKey &K) const { + uint64_t H; + std::memcpy(&H, K.Addr.bytes + 12, sizeof(H)); + return H ^ (static_cast(K.Rev) * 2654435761u); + } +}; + +struct CodeAddrRevEqual { + bool operator()(const CodeAddrRevKey &A, const CodeAddrRevKey &B) const { + return A.Rev == B.Rev && + std::memcmp(A.Addr.bytes, B.Addr.bytes, sizeof(A.Addr.bytes)) == 0; + } +}; + +/// Validate that the cached module's code matches the provided code. +/// Checks code_size + first 256 bytes + last 256 bytes. +bool validateCodeMatch(const uint8_t *Code, size_t CodeSize, + const EVMModule *Mod) { + if (CodeSize != Mod->CodeSize) + return false; + if (CodeSize == 0) + return true; + auto *ModCode = reinterpret_cast(Mod->Code); + size_t HeadLen = std::min(CodeSize, static_cast(256)); + if (std::memcmp(Code, ModCode, HeadLen) != 0) + return false; + if (CodeSize > 256) { + size_t TailLen = std::min(CodeSize, static_cast(256)); + size_t TailOffset = CodeSize - TailLen; + if (std::memcmp(Code + TailOffset, ModCode + TailOffset, TailLen) != 0) + return false; } - uint32_t Crc = 0xFFFFFFFFu; - for (size_t I = 0; I < Size; ++I) - Crc = Table[(Crc ^ Data[I]) & 0xFFu] ^ (Crc >> 8); - return Crc ^ 0xFFFFFFFFu; + return true; } // VM interface for DTVM struct DTVM : evmc_vm { DTVM(); ~DTVM() { - for (auto &P : LoadedMods) { - EVMModule *Mod = P.second; - if (!RT->unloadEVMModule(Mod)) { - ZEN_LOG_ERROR("failed to unload EVM module"); + // Clean up cached instance first (before modules it may reference) + if (CachedInst && Iso) { + Iso->deleteEVMInstance(CachedInst); + CachedInst = nullptr; + } + // Unload all address-cached modules + if (RT) { + for (auto &P : AddrCache) { + if (!RT->unloadEVMModule(P.second)) { + ZEN_LOG_ERROR("failed to unload EVM module"); + } } } if (Iso) { @@ -78,8 +110,22 @@ struct DTVM : evmc_vm { .EnableEvmGasMetering = true}; std::unique_ptr RT; std::unique_ptr ExecHost; - std::unordered_map LoadedMods; Isolation *Iso = nullptr; + + // ---- Module & instance cache (shared by interpreter and multipass) ---- + // L0: pointer-based inline cache (fastest, 2 integer comparisons) + const uint8_t *LastCodePtr = nullptr; + size_t LastCodeSize = 0; + EVMModule *L0Mod = nullptr; + // L1: address-based cache map (code_address + rev -> module) + std::unordered_map + AddrCache; + uint64_t ModCounter = 0; + // Cached EVMInstance to avoid alloc/free (~33KB) on every call + EVMInstance *CachedInst = nullptr; + // Cached InterpreterExecContext (interpreter mode only) + std::unique_ptr CachedCtx; }; /// The implementation of the evmc_vm::destroy() method. @@ -121,12 +167,167 @@ enum evmc_set_option_result set_option(evmc_vm *VMInstance, const char *Name, return EVMC_SET_OPTION_INVALID_NAME; } +/// Ensure RT and Iso are initialized. Returns false on failure. +bool ensureRuntimeAndIsolation(DTVM *VM) { + if (!VM->RT) { + VM->RT = Runtime::newEVMRuntime(VM->Config, VM->ExecHost.get()); + if (!VM->RT) + return false; + } + if (!VM->Iso) { + VM->Iso = VM->RT->createManagedIsolation(); + if (!VM->Iso) + return false; + } + return true; +} + +/// Find or load a cached EVMModule using two-level cache: +/// L0 (code pointer+size) -> L1 (address map) -> Cold load. +/// Shared by both interpreter and multipass paths. +/// Returns nullptr on failure. +EVMModule *findModuleCached(DTVM *VM, const uint8_t *Code, size_t CodeSize, + evmc_revision Rev, const evmc_message *Msg) { + // L0 disabled: pointer comparison is unsafe when callers reuse addresses + // for different bytecode (e.g. test frameworks, repeated allocations). + // Fall through to L1 address-based lookup with content validation. + + EVMModule *Mod = nullptr; + + // L1: Address-based map lookup + CodeAddrRevKey AddrKey{Msg->code_address, Rev}; + auto It = VM->AddrCache.find(AddrKey); + if (It != VM->AddrCache.end() && + validateCodeMatch(Code, CodeSize, It->second)) { + Mod = It->second; + } else { + // Cold path: full module load + // If validation failed for an existing entry, evict the stale module + if (It != VM->AddrCache.end()) { + EVMModule *OldMod = It->second; + if (VM->CachedInst && VM->CachedInst->getModule() == OldMod) { + VM->Iso->deleteEVMInstance(VM->CachedInst); + VM->CachedInst = nullptr; + } + if (VM->L0Mod == OldMod) + VM->L0Mod = nullptr; + VM->RT->unloadEVMModule(OldMod); + VM->AddrCache.erase(It); + } + std::string ModName = "mod_" + std::to_string(VM->ModCounter++); + auto ModRet = VM->RT->loadEVMModule(ModName, Code, CodeSize, Rev); + if (!ModRet) + return nullptr; + Mod = *ModRet; + VM->AddrCache[AddrKey] = Mod; + } + + // Update L0 cache + VM->LastCodePtr = Code; + VM->LastCodeSize = CodeSize; + VM->L0Mod = Mod; + return Mod; +} + +/// Get or create a cached EVMInstance for the given module. +/// Reuses the existing instance if it was created for the same module. +/// Returns nullptr on failure. +EVMInstance *getOrCreateInstance(DTVM *VM, EVMModule *Mod, evmc_revision Rev) { + EVMInstance *TheInst = VM->CachedInst; + if (!TheInst || TheInst->getModule() != Mod) { + if (TheInst) { + VM->Iso->deleteEVMInstance(TheInst); + VM->CachedInst = nullptr; + } + auto InstRet = VM->Iso->createEVMInstance(*Mod, 0); + if (!InstRet) + return nullptr; + TheInst = *InstRet; + VM->CachedInst = TheInst; + } + TheInst->resetForNewCall(Rev); + return TheInst; +} + +/// Fast path for interpreter mode: reuse cached instance, call interpreter +/// directly. This avoids per-call EVMInstance alloc/free and bypasses +/// Runtime::callEVMMain overhead. +evmc_result executeInterpreterFastPath(DTVM *VM, + const evmc_host_interface *Host, + evmc_host_context *Context, + evmc_revision Rev, + const evmc_message *Msg, + const uint8_t *Code, size_t CodeSize) { + // Reinitialize host context + const auto *PrevInterface = VM->ExecHost->getInterface(); + auto *PrevContext = VM->ExecHost->getContext(); + VM->ExecHost->reinitialize(Host, Context); + + // Ensure runtime and isolation exist + if (!ensureRuntimeAndIsolation(VM)) { + VM->ExecHost->reinitialize(PrevInterface, PrevContext); + return evmc_make_result(EVMC_FAILURE, 0, 0, nullptr, 0); + } + + // Module lookup: L1 address-based cache -> Cold load + EVMModule *Mod = findModuleCached(VM, Code, CodeSize, Rev, Msg); + if (!Mod) { + VM->ExecHost->reinitialize(PrevInterface, PrevContext); + return evmc_make_result(EVMC_FAILURE, 0, 0, nullptr, 0); + } + + // Instance reuse (shared) + EVMInstance *TheInst = getOrCreateInstance(VM, Mod, Rev); + if (!TheInst) { + VM->ExecHost->reinitialize(PrevInterface, PrevContext); + return evmc_make_result(EVMC_FAILURE, 0, 0, nullptr, 0); + } + + // Trigger bytecodeCache build if not yet done (lazy, cached on module) + (void)Mod->getBytecodeCache(); + + // Call interpreter directly, bypassing Runtime::callEVMMain overhead + evmc_message MsgWithCode = *Msg; + MsgWithCode.code = reinterpret_cast(Mod->Code); + MsgWithCode.code_size = Mod->CodeSize; + TheInst->setExeResult(evmc::Result{EVMC_SUCCESS, 0, 0}); + TheInst->pushMessage(&MsgWithCode); + + // Reuse cached InterpreterExecContext to avoid ~32KB frame alloc per call + if (!VM->CachedCtx) { + VM->CachedCtx = std::make_unique(TheInst); + } else { + VM->CachedCtx->resetForNewCall(TheInst); + } + auto &Ctx = *VM->CachedCtx; + zen::evm::BaseInterpreter Interpreter(Ctx); + Ctx.allocTopFrame(&MsgWithCode); + Interpreter.interpret(); + + evmc::Result Result = + std::move(const_cast(Ctx.getExeResult())); + Result.gas_left = TheInst->getGas(); + + // Restore host context + VM->ExecHost->reinitialize(PrevInterface, PrevContext); + + return Result.release_raw(); +} + /// The implementation of the evmc_vm::execute() method. evmc_result execute(evmc_vm *EVMInstance, const evmc_host_interface *Host, evmc_host_context *Context, enum evmc_revision Rev, const evmc_message *Msg, const uint8_t *Code, size_t CodeSize) { auto *VM = static_cast(EVMInstance); + + // Interpreter mode: use optimized fast path (bypasses callEVMMain) + if (VM->Config.Mode == RunMode::InterpMode) { + return executeInterpreterFastPath(VM, Host, Context, Rev, Msg, Code, + CodeSize); + } + + // ---- Multipass / other modes: use callEVMMain for JIT execution ---- struct HostContextScope { WrappedHost *ExecHost; const evmc_host_interface *PrevInterface; @@ -142,12 +343,10 @@ evmc_result execute(evmc_vm *EVMInstance, const evmc_host_interface *Host, HostContextScope HostScope(VM->ExecHost.get(), Host, Context); - if (!VM->RT) { - VM->RT = Runtime::newEVMRuntime(VM->Config, VM->ExecHost.get()); - if (!VM->RT) { - return evmc_make_result(EVMC_FAILURE, 0, 0, nullptr, 0); - } + if (!ensureRuntimeAndIsolation(VM)) { + return evmc_make_result(EVMC_FAILURE, 0, 0, nullptr, 0); } + #ifdef ZEN_ENABLE_JIT_PRECOMPILE_FALLBACK // Use interpreter mode for bytecode that would be too expensive to JIT. // The EVMAnalyzer performs a pattern-aware O(n) scan that detects: @@ -168,42 +367,22 @@ evmc_result execute(evmc_vm *EVMInstance, const evmc_host_interface *Host, } #endif // ZEN_ENABLE_JIT_PRECOMPILE_FALLBACK - uint32_t CheckSum = crc32(Code, CodeSize); - uint64_t ModKey = (static_cast(Rev) << 32) | CheckSum; - std::string ModName = - std::to_string(CheckSum) + "_" + std::to_string(static_cast(Rev)); - auto ModRet = VM->RT->loadEVMModule(ModName, Code, CodeSize, Rev); - if (!ModRet) { - const Error &Err = ModRet.getError(); - ZEN_ASSERT(!Err.isEmpty()); - const auto &ErrMsg = Err.getFormattedMessage(false); - return evmc_make_result(EVMC_FAILURE, 0, 0, nullptr, 0); - } - - EVMModule *Mod = *ModRet; - VM->LoadedMods[ModKey] = Mod; - if (!VM->Iso) { - VM->Iso = VM->RT->createManagedIsolation(); - } - if (!VM->Iso) { - return evmc_make_result(EVMC_FAILURE, 0, 0, nullptr, 0); - } - - auto InstRet = VM->Iso->createEVMInstance(*Mod, 1000000000); - if (!InstRet) { + // Module lookup: L0 -> L1 -> Cold (shared with interpreter path) + EVMModule *Mod = findModuleCached(VM, Code, CodeSize, Rev, Msg); + if (!Mod) { return evmc_make_result(EVMC_FAILURE, 0, 0, nullptr, 0); } - auto *TheInst = *InstRet; + // Instance reuse (shared with interpreter path) + auto *TheInst = getOrCreateInstance(VM, Mod, Rev); if (!TheInst) { return evmc_make_result(EVMC_FAILURE, 0, 0, nullptr, 0); } - TheInst->setRevision(Rev); + // Execute via callEVMMain (handles both JIT and interpreter fallback) evmc_message Message = *Msg; evmc::Result Result; VM->RT->callEVMMain(*TheInst, Message, Result); - VM->Iso->deleteEVMInstance(TheInst); return Result.release_raw(); }