From f947c519d32f93b2abfa6437b14a5e10c47e6bbd Mon Sep 17 00:00:00 2001 From: cmgCr Date: Thu, 14 May 2026 08:33:00 +0000 Subject: [PATCH 1/3] fix(evm): prevent C++ exception from crossing JIT frames in check mode When ZEN_ENABLE_CPU_EXCEPTION=OFF (check mode), JIT-compiled code lacks DWARF unwind info, so C++ exceptions thrown from host API functions (e.g. chargeGas's throw on out-of-gas) cannot propagate through JIT frames and cause std::terminate. Fix by replacing all throw paths in host API call chains with setInstanceExceptionOnJIT + early return, and adding post-call error code checks in the compiler IR: - Add chargeGasHostApi() wrapper that pre-checks gas and uses setInstanceExceptionOnJIT instead of throw in JIT+check mode - Guard expandMemory/expandMemoryChecked with pre-check and markOutOfGas lambda (no-throw in JIT+check mode) - Add JIT+check branch in addGas() overflow handling - Add overflow pre-check before addGas(CALL_GAS_STIPEND) in evmHandleCallInternal - Add evmGetErrorCode host function and insert error-code checks after every callRuntimeFor in the MIR compiler, branching to GasLimitExceeded or EVMStaticModeViolation exception BBs --- src/compiler/evm_frontend/evm_imported.cpp | 136 +++++++++++++++--- src/compiler/evm_frontend/evm_imported.h | 3 + .../evm_frontend/evm_mir_compiler.cpp | 86 +++++++++++ src/runtime/evm_instance.cpp | 50 ++++++- 4 files changed, 247 insertions(+), 28 deletions(-) diff --git a/src/compiler/evm_frontend/evm_imported.cpp b/src/compiler/evm_frontend/evm_imported.cpp index 5292db6e7..fe0592253 100644 --- a/src/compiler/evm_frontend/evm_imported.cpp +++ b/src/compiler/evm_frontend/evm_imported.cpp @@ -103,6 +103,30 @@ inline void triggerStaticModeViolation(zen::runtime::EVMInstance *Instance) { Instance, zen::common::ErrorCode::EVMStaticModeViolation); } +inline bool chargeGasHostApi(zen::runtime::EVMInstance *Instance, + uint64_t GasCost) { + if (GasCost == 0) { + return true; + } +#if defined(ZEN_ENABLE_JIT) && !defined(ZEN_ENABLE_CPU_EXCEPTION) + const uint64_t GasLeft = Instance->getGas(); + if (GasLeft < GasCost) { + zen::runtime::EVMInstance::setInstanceExceptionOnJIT( + Instance, zen::common::ErrorCode::GasLimitExceeded); + return false; + } + const uint64_t NewGas = GasLeft - GasCost; + Instance->setGas(NewGas); + if (evmc_message *Msg = Instance->getCurrentMessage()) { + Msg->gas = static_cast(NewGas); + } + return true; +#else + Instance->chargeGas(GasCost); + return true; +#endif +} + constexpr uint8_t DelegationMagicBytes[] = {0xef, 0x01, 0x00}; bool resolveDelegatedCallCodeAddress(zen::runtime::EVMInstance *Instance, @@ -135,8 +159,7 @@ bool resolveDelegatedCallCodeAddress(zen::runtime::EVMInstance *Instance, Module->Host->access_account(CodeAddr) == EVMC_ACCESS_COLD ? zen::evm::COLD_ACCOUNT_ACCESS_COST : zen::evm::WARM_STORAGE_READ_COST; - Instance->chargeGas(DelegateAccessCost); - return true; + return chargeGasHostApi(Instance, DelegateAccessCost); } } // namespace @@ -174,6 +197,7 @@ const RuntimeFunctions &getRuntimeFunctionTable() { .GetBlobHash = &evmGetBlobHash, .GetBlobBaseFee = &evmGetBlobBaseFee, .GetSLoad = &evmGetSLoad, + .GetErrorCode = &evmGetErrorCode, .SetSStore = &evmSetSStore, .GetGas = &evmGetGas, .GetTLoad = &evmGetTLoad, @@ -305,7 +329,9 @@ const intx::uint256 *evmGetExp(zen::runtime::EVMInstance *Instance, const uint64_t GasPerByte = Rev < EVMC_SPURIOUS_DRAGON ? zen::evm::EXP_BYTE_GAS_PRE_SPURIOUS_DRAGON : zen::evm::EXP_BYTE_GAS; - Instance->chargeGas(ExponentByteSize * GasPerByte); + if (!chargeGasHostApi(Instance, ExponentByteSize * GasPerByte)) { + return storeUint256Result(intx::uint256{0}); + } // EVM: (Base ^ Exponent) % (2^256) return storeUint256Result(intx::exp(Base, Exponent)); @@ -330,7 +356,10 @@ const intx::uint256 *evmGetBalance(zen::runtime::EVMInstance *Instance, evmc_revision Rev = Instance->getRevision(); if (Rev >= EVMC_BERLIN && Module->Host->access_account(Addr) == EVMC_ACCESS_COLD) { - Instance->chargeGas(zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); + if (!chargeGasHostApi(Instance, + zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { + return storeUint256Result(intx::uint256{0}); + } } evmc::bytes32 BalanceBytes = Module->Host->get_balance(Addr); @@ -406,7 +435,10 @@ uint64_t evmGetExtCodeSize(zen::runtime::EVMInstance *Instance, evmc_revision Rev = Instance->getRevision(); if (Rev >= EVMC_BERLIN && Module->Host->access_account(Addr) == EVMC_ACCESS_COLD) { - Instance->chargeGas(zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); + if (!chargeGasHostApi(Instance, + zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { + return 0; + } } uint64_t Size = Module->Host->get_code_size(Addr); @@ -423,7 +455,10 @@ const intx::uint256 *evmGetExtCodeHash(zen::runtime::EVMInstance *Instance, evmc_revision Rev = Instance->getRevision(); if (Rev >= EVMC_BERLIN && Module->Host->access_account(Addr) == EVMC_ACCESS_COLD) { - Instance->chargeGas(zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); + if (!chargeGasHostApi(Instance, + zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { + return storeUint256Result(intx::uint256{0}); + } } evmc::bytes32 Hash = Module->Host->get_code_hash(Addr); @@ -597,7 +632,9 @@ void evmSetCallDataCopy(zen::runtime::EVMInstance *Instance, return; } if (uint64_t CopyGas = calculateWordCopyGas(Size)) { - Instance->chargeGas(CopyGas); + if (!chargeGasHostApi(Instance, CopyGas)) { + return; + } } const evmc_message *Msg = Instance->getCurrentMessage(); @@ -637,7 +674,10 @@ void evmSetExtCodeCopy(zen::runtime::EVMInstance *Instance, evmc_revision Rev = Instance->getRevision(); if (Rev >= EVMC_BERLIN && Module->Host->access_account(Addr) == EVMC_ACCESS_COLD) { - Instance->chargeGas(zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); + if (!chargeGasHostApi(Instance, + zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { + return; + } } if (!Instance->expandMemoryChecked(DestOffset, Size)) { @@ -645,7 +685,9 @@ void evmSetExtCodeCopy(zen::runtime::EVMInstance *Instance, } if (uint64_t CopyGas = calculateWordCopyGas(Size)) { - Instance->chargeGas(CopyGas); + if (!chargeGasHostApi(Instance, CopyGas)) { + return; + } } // When Size is 0, no memory operations are needed @@ -694,7 +736,9 @@ uint64_t evmSetReturnDataCopy(zen::runtime::EVMInstance *Instance, return 0; } if (uint64_t CopyGas = calculateWordCopyGas(Size)) { - Instance->chargeGas(CopyGas); + if (!chargeGasHostApi(Instance, CopyGas)) { + return 0; + } } uint8_t *MemoryBase = Instance->getMemoryBase(); @@ -736,7 +780,9 @@ static void evmEmitLogGeneric(zen::runtime::EVMInstance *Instance, } const uint64_t LogDataCost = 8 * Size; if (LogDataCost != 0) { - Instance->chargeGas(LogDataCost); + if (!chargeGasHostApi(Instance, LogDataCost)) { + return; + } } uint8_t *MemoryBase = Instance->getMemoryBase(); Data = MemoryBase + Offset; @@ -822,7 +868,10 @@ const uint8_t *evmHandleCreateInternal(zen::runtime::EVMInstance *Instance, evmc_revision Rev = Instance->getRevision(); if (Rev >= EVMC_SHANGHAI && Size > zen::evm::MAX_SIZE_OF_INITCODE) { - Instance->chargeGas(Instance->getGas() + 1); + if (!chargeGasHostApi(Instance, Instance->getGas() + 1)) { + Instance->setReturnData({}); + return ZeroAddress; + } } uint64_t InitCodeWordCost = 0; if (CallKind == EVMC_CREATE2) { @@ -835,7 +884,10 @@ const uint8_t *evmHandleCreateInternal(zen::runtime::EVMInstance *Instance, uint64_t InitCodeWords = (Size + 31) / 32; uint64_t InitCodeCost = InitCodeWordCost * InitCodeWords; if (InitCodeCost != 0) { - Instance->chargeGas(InitCodeCost); + if (!chargeGasHostApi(Instance, InitCodeCost)) { + Instance->setReturnData({}); + return ZeroAddress; + } } } @@ -880,7 +932,10 @@ const uint8_t *evmHandleCreateInternal(zen::runtime::EVMInstance *Instance, Result.gas_left > 0 ? static_cast(Result.gas_left) : 0; uint64_t GasUsed = ProvidedGas > GasLeft ? ProvidedGas - GasLeft : 0; if (GasUsed != 0) { - Instance->chargeGas(GasUsed); + if (!chargeGasHostApi(Instance, GasUsed)) { + Instance->setReturnData({}); + return ZeroAddress; + } } // Track subcall refund (may be negative) Instance->addGasRefund(Result.gas_refund); @@ -925,7 +980,11 @@ static uint64_t evmHandleCallInternal( evmc_revision Rev = Instance->getRevision(); if (Rev >= EVMC_BERLIN && Module->Host->access_account(TargetAddr) == EVMC_ACCESS_COLD) { - Instance->chargeGas(zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); + if (!chargeGasHostApi(Instance, + zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { + Instance->setReturnData({}); + return 0; + } } evmc::address CodeAddr = TargetAddr; @@ -979,7 +1038,10 @@ static uint64_t evmHandleCallInternal( } } - Instance->chargeGas(GasCost); + if (!chargeGasHostApi(Instance, GasCost)) { + Instance->setReturnData({}); + return 0; + } } uint64_t GasLeft = Instance->getGas(); @@ -994,6 +1056,13 @@ static uint64_t evmHandleCallInternal( } if (HasValueArgs && HasValue) { + if (Instance->getGas() > + std::numeric_limits::max() - zen::evm::CALL_GAS_STIPEND) { + zen::runtime::EVMInstance::setInstanceExceptionOnJIT( + Instance, zen::common::ErrorCode::GasLimitExceeded); + Instance->setReturnData({}); + return 0; + } CallGas += zen::evm::CALL_GAS_STIPEND; Instance->addGas(zen::evm::CALL_GAS_STIPEND); const auto CallerBalance = Module->Host->get_balance(CurrentMsg->recipient); @@ -1052,7 +1121,10 @@ static uint64_t evmHandleCallInternal( GasLeft = Result.gas_left > 0 ? static_cast(Result.gas_left) : 0; uint64_t GasUsed = CallGas > GasLeft ? CallGas - GasLeft : 0; if (GasUsed > 0) { - Instance->chargeGas(GasUsed); + if (!chargeGasHostApi(Instance, GasUsed)) { + Instance->setReturnData({}); + return 0; + } } // Track subcall refund (may be negative) @@ -1171,7 +1243,9 @@ void evmSetCodeCopy(zen::runtime::EVMInstance *Instance, uint64_t DestOffset, return; } if (uint64_t CopyGas = calculateWordCopyGas(Size)) { - Instance->chargeGas(CopyGas); + if (!chargeGasHostApi(Instance, CopyGas)) { + return; + } } const zen::runtime::EVMModule *Module = Instance->getModule(); @@ -1203,7 +1277,9 @@ const uint8_t *evmGetKeccak256(zen::runtime::EVMInstance *Instance, } const uint64_t ExtraGas = static_cast(numWords(static_cast(Length))) * 6; - Instance->chargeGas(ExtraGas); + if (!chargeGasHostApi(Instance, ExtraGas)) { + return nullptr; + } uint8_t *MemoryBase = Instance->getMemoryBase(); InputData = MemoryBase + Offset; } @@ -1281,11 +1357,19 @@ const intx::uint256 *evmGetSLoad(zen::runtime::EVMInstance *Instance, const auto Key = intx::be::store(Index); if (Rev >= EVMC_BERLIN && Module->Host->access_storage(Msg->recipient, Key) == EVMC_ACCESS_COLD) { - Instance->chargeGas(zen::evm::ADDITIONAL_COLD_SLOAD_COST); + if (!chargeGasHostApi(Instance, zen::evm::ADDITIONAL_COLD_SLOAD_COST)) { + return storeUint256Result(intx::uint256{0}); + } } const auto Value = Module->Host->get_storage(Msg->recipient, Key); return storeUint256Result(intx::be::load(Value)); } + +uint64_t evmGetErrorCode(zen::runtime::EVMInstance *Instance) { + return static_cast( + zen::common::to_underlying(Instance->getError().getCode())); +} + void evmSetSStore(zen::runtime::EVMInstance *Instance, const intx::uint256 &Index, const intx::uint256 &Value) { const zen::runtime::EVMModule *Module = Instance->getModule(); @@ -1315,7 +1399,9 @@ void evmSetSStore(zen::runtime::EVMInstance *Instance, const auto [GasCostWarm, GasReFund] = zen::evm::SSTORE_COSTS[Rev][Status]; const auto GasCost = GasCostCold + GasCostWarm; - Instance->chargeGas(GasCost); + if (!chargeGasHostApi(Instance, GasCost)) { + return; + } Instance->addGasRefund(GasReFund); } @@ -1363,7 +1449,9 @@ void evmHandleSelfDestruct(zen::runtime::EVMInstance *Instance, const bool IsCold = Module->Host->access_account(BenefAddr) == EVMC_ACCESS_COLD; if (IsCold) { - Instance->chargeGas(zen::evm::COLD_ACCOUNT_ACCESS_COST); + if (!chargeGasHostApi(Instance, zen::evm::COLD_ACCOUNT_ACCESS_COST)) { + return; + } } } @@ -1372,7 +1460,9 @@ void evmHandleSelfDestruct(zen::runtime::EVMInstance *Instance, if (Rev == EVMC_TANGERINE_WHISTLE || Module->Host->get_balance(Msg->recipient)) { if (!Module->Host->account_exists(BenefAddr)) { - Instance->chargeGas(zen::evm::ACCOUNT_CREATION_COST); + if (!chargeGasHostApi(Instance, zen::evm::ACCOUNT_CREATION_COST)) { + return; + } } } } diff --git a/src/compiler/evm_frontend/evm_imported.h b/src/compiler/evm_frontend/evm_imported.h index a5a90a533..22c67edcb 100644 --- a/src/compiler/evm_frontend/evm_imported.h +++ b/src/compiler/evm_frontend/evm_imported.h @@ -42,6 +42,7 @@ using Bytes32WithUInt64UInt64Fn = using VoidFn = void (*)(zen::runtime::EVMInstance *); using U256WithU256Fn = const intx::uint256 *(*)(zen::runtime::EVMInstance *, const intx::uint256 &); +using ErrorCodeFn = uint64_t (*)(zen::runtime::EVMInstance *); using VoidWithU256U256Fn = void (*)(zen::runtime::EVMInstance *, const intx::uint256 &, const intx::uint256 &); @@ -111,6 +112,7 @@ struct RuntimeFunctions { Bytes32WithUint64Fn GetBlobHash; U256Fn GetBlobBaseFee; U256WithU256Fn GetSLoad; + ErrorCodeFn GetErrorCode; VoidWithU256U256Fn SetSStore; SizeFn GetGas; U256WithU256Fn GetTLoad; @@ -259,6 +261,7 @@ const uint8_t *evmGetKeccak256(zen::runtime::EVMInstance *Instance, void evmHandleFallback(zen::runtime::EVMInstance *Instance, uint64_t PC); const intx::uint256 *evmGetSLoad(zen::runtime::EVMInstance *Instance, const intx::uint256 &Index); +uint64_t evmGetErrorCode(zen::runtime::EVMInstance *Instance); void evmSetSStore(zen::runtime::EVMInstance *Instance, const intx::uint256 &Index, const intx::uint256 &Value); uint64_t evmGetGas(zen::runtime::EVMInstance *Instance); diff --git a/src/compiler/evm_frontend/evm_mir_compiler.cpp b/src/compiler/evm_frontend/evm_mir_compiler.cpp index c66401d16..c6854fe71 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.cpp +++ b/src/compiler/evm_frontend/evm_mir_compiler.cpp @@ -5650,6 +5650,49 @@ EVMMirBuilder::callRuntimeFor(RetType (*RuntimeFunc)(runtime::EVMInstance *)) { IsStmt, ReturnType, FuncAddrInst, llvm::ArrayRef(InstancePtr)); +#if !defined(ZEN_ENABLE_CPU_EXCEPTION) + // In check mode, hostapi reports soft errors by writing instance error code. + // Convert it into explicit control flow immediately after each hostapi call. + MInstruction *GetErrAddr = createIntConstInstruction( + &Ctx.I64Type, getFunctionAddress(evmGetErrorCode)); + MInstruction *ErrCodeInstr = createInstruction( + false, EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), + GetErrAddr, llvm::ArrayRef(InstancePtr)); + Variable *ErrCodeVar = + storeInstructionInTemp(ErrCodeInstr, ErrCodeInstr->getType()); + MInstruction *ErrCodeValue = loadVariable(ErrCodeVar); + MInstruction *StaticViolationCode = createIntConstInstruction( + EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), + common::to_underlying(ErrorCode::EVMStaticModeViolation)); + MInstruction *GasExceededCode = createIntConstInstruction( + EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), + common::to_underlying(ErrorCode::GasLimitExceeded)); + MInstruction *HasGasExceeded = createInstruction( + false, CmpInstruction::Predicate::ICMP_EQ, + EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), ErrCodeValue, + GasExceededCode); + MBasicBlock *CheckStaticBB = createBasicBlock(); + MBasicBlock *ContinueBB = createBasicBlock(); + MBasicBlock *GasTrapBB = + getOrCreateExceptionSetBB(ErrorCode::GasLimitExceeded); + createInstruction(true, Ctx, HasGasExceeded, GasTrapBB, + CheckStaticBB); + addUniqueSuccessor(GasTrapBB); + addSuccessor(CheckStaticBB); + setInsertBlock(CheckStaticBB); + MInstruction *HasStaticViolation = createInstruction( + false, CmpInstruction::Predicate::ICMP_EQ, + EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), ErrCodeValue, + StaticViolationCode); + MBasicBlock *StaticTrapBB = + getOrCreateExceptionSetBB(ErrorCode::EVMStaticModeViolation); + createInstruction(true, Ctx, HasStaticViolation, + StaticTrapBB, ContinueBB); + addUniqueSuccessor(StaticTrapBB); + addSuccessor(ContinueBB); + setInsertBlock(ContinueBB); +#endif + return convertCallResult(CallInstr); } @@ -5873,6 +5916,49 @@ EVMMirBuilder::Operand EVMMirBuilder::callRuntimeFor( MInstruction *CallInstr = createInstruction( IsStmt, ReturnType, FuncAddrInst, llvm::ArrayRef{Args}); +#if !defined(ZEN_ENABLE_CPU_EXCEPTION) + // Keep check mode deterministic by converting hostapi soft errors into + // explicit exception control flow. + MInstruction *GetErrAddr = createIntConstInstruction( + &Ctx.I64Type, getFunctionAddress(evmGetErrorCode)); + MInstruction *ErrCodeInstr = createInstruction( + false, EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), + GetErrAddr, llvm::ArrayRef(InstancePtr)); + Variable *ErrCodeVar = + storeInstructionInTemp(ErrCodeInstr, ErrCodeInstr->getType()); + MInstruction *ErrCodeValue = loadVariable(ErrCodeVar); + MInstruction *StaticViolationCode = createIntConstInstruction( + EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), + common::to_underlying(ErrorCode::EVMStaticModeViolation)); + MInstruction *GasExceededCode = createIntConstInstruction( + EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), + common::to_underlying(ErrorCode::GasLimitExceeded)); + MInstruction *HasGasExceeded = createInstruction( + false, CmpInstruction::Predicate::ICMP_EQ, + EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), ErrCodeValue, + GasExceededCode); + MBasicBlock *CheckStaticBB = createBasicBlock(); + MBasicBlock *ContinueBB = createBasicBlock(); + MBasicBlock *GasTrapBB = + getOrCreateExceptionSetBB(ErrorCode::GasLimitExceeded); + createInstruction(true, Ctx, HasGasExceeded, GasTrapBB, + CheckStaticBB); + addUniqueSuccessor(GasTrapBB); + addSuccessor(CheckStaticBB); + setInsertBlock(CheckStaticBB); + MInstruction *HasStaticViolation = createInstruction( + false, CmpInstruction::Predicate::ICMP_EQ, + EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), ErrCodeValue, + StaticViolationCode); + MBasicBlock *StaticTrapBB = + getOrCreateExceptionSetBB(ErrorCode::EVMStaticModeViolation); + createInstruction(true, Ctx, HasStaticViolation, + StaticTrapBB, ContinueBB); + addUniqueSuccessor(StaticTrapBB); + addSuccessor(ContinueBB); + setInsertBlock(ContinueBB); +#endif + return convertCallResult(CallInstr); } diff --git a/src/runtime/evm_instance.cpp b/src/runtime/evm_instance.cpp index f635f405a..84fff5258 100644 --- a/src/runtime/evm_instance.cpp +++ b/src/runtime/evm_instance.cpp @@ -209,7 +209,20 @@ void EVMInstance::triggerInstanceExceptionOnJIT(EVMInstance *Inst, void EVMInstance::expandMemory(uint64_t RequiredSize) { auto NewSize = (RequiredSize + 31) / 32 * 32; uint64_t ExpansionCost = calculateMemoryExpansionCost(MemorySize, NewSize); +#if defined(ZEN_ENABLE_JIT) && !defined(ZEN_ENABLE_CPU_EXCEPTION) + const uint64_t GasLeft = getGas(); + if (GasLeft < ExpansionCost) { + setInstanceExceptionOnJIT(this, common::ErrorCode::GasLimitExceeded); + return; + } + const uint64_t NewGas = GasLeft - ExpansionCost; + setGas(NewGas); + if (evmc_message *Msg = getCurrentMessage()) { + Msg->gas = static_cast(NewGas); + } +#else chargeGas(ExpansionCost); +#endif if (NewSize > MemorySize) { if (!MemoryBase) { ensureMemoryBuffer(Memory, MemoryBase); @@ -237,24 +250,45 @@ void EVMInstance::expandMemoryNoGas(uint64_t RequiredSize) { } bool EVMInstance::expandMemoryChecked(uint64_t Offset, uint64_t Size) { + auto markOutOfGas = [&]() { +#if defined(ZEN_ENABLE_JIT) && !defined(ZEN_ENABLE_CPU_EXCEPTION) + // Check mode: mark soft OOG and return to caller. + setInstanceExceptionOnJIT(this, common::ErrorCode::GasLimitExceeded); +#else + // Interpreter/CPU-exception modes preserve existing throw/trap behavior. + chargeGas(getGas() + 1); +#endif + }; if (Size == 0) { return true; } uint64_t RequiredSize = 0; if (!calcRequiredMemorySize(Offset, Size, RequiredSize)) { - chargeGas(getGas() + 1); + markOutOfGas(); return false; } if (RequiredSize > zen::evm::MAX_REQUIRED_MEMORY_SIZE) { - chargeGas(getGas() + 1); + markOutOfGas(); return false; } expandMemory(RequiredSize); + if (getError().getCode() == common::ErrorCode::GasLimitExceeded) { + return false; + } return true; } bool EVMInstance::expandMemoryChecked(uint64_t OffsetA, uint64_t SizeA, uint64_t OffsetB, uint64_t SizeB) { + auto markOutOfGas = [&]() { +#if defined(ZEN_ENABLE_JIT) && !defined(ZEN_ENABLE_CPU_EXCEPTION) + // Check mode: mark soft OOG and return to caller. + setInstanceExceptionOnJIT(this, common::ErrorCode::GasLimitExceeded); +#else + // Interpreter/CPU-exception modes preserve existing throw/trap behavior. + chargeGas(getGas() + 1); +#endif + }; const bool NeedA = SizeA > 0; const bool NeedB = SizeB > 0; if (!NeedA && !NeedB) { @@ -270,15 +304,18 @@ bool EVMInstance::expandMemoryChecked(uint64_t OffsetA, uint64_t SizeA, uint64_t RequiredSizeB = 0; if (!calcRequiredMemorySize(OffsetA, SizeA, RequiredSizeA) || !calcRequiredMemorySize(OffsetB, SizeB, RequiredSizeB)) { - chargeGas(getGas() + 1); + markOutOfGas(); return false; } const uint64_t RequiredSize = std::max(RequiredSizeA, RequiredSizeB); if (RequiredSize > zen::evm::MAX_REQUIRED_MEMORY_SIZE) { - chargeGas(getGas() + 1); + markOutOfGas(); return false; } expandMemory(RequiredSize); + if (getError().getCode() == common::ErrorCode::GasLimitExceeded) { + return false; + } return true; } @@ -303,7 +340,10 @@ void EVMInstance::addGas(uint64_t GasAmount) { ZEN_ASSERT(Msg && "Active message required for gas accounting"); uint64_t GasLeft = getGas(); if (GasLeft > UINT64_MAX - GasAmount) { -#if defined(ZEN_ENABLE_JIT) && defined(ZEN_ENABLE_CPU_EXCEPTION) +#if defined(ZEN_ENABLE_JIT) && !defined(ZEN_ENABLE_CPU_EXCEPTION) + setInstanceExceptionOnJIT(this, common::ErrorCode::GasLimitExceeded); + return; +#elif defined(ZEN_ENABLE_JIT) && defined(ZEN_ENABLE_CPU_EXCEPTION) triggerInstanceExceptionOnJIT(this, common::ErrorCode::GasLimitExceeded); #else throw common::getError(common::ErrorCode::GasLimitExceeded); From 5f934dbe58366a2f9c628e5cf76b84ebc4b9c233 Mon Sep 17 00:00:00 2001 From: cmgCr Date: Thu, 14 May 2026 11:14:01 +0000 Subject: [PATCH 2/3] refactor(evm): unify gas error handling across JIT check mode - Make chargeGas() and addGas() return bool; all three modes (interpreter, JIT+CPU_EXCEPTION, JIT+check) now handled internally, eliminating the chargeGasHostApi wrapper - Simplify expandMemory() to use chargeGas() return value instead of separate #if branches; remove markOutOfGas lambda from expandMemoryChecked() - Remove addGas(CALL_GAS_STIPEND) overflow pre-check in favor of addGas() returning false on overflow - Split callRuntimeFor into callRuntimeFor (pure reads) and callRuntimeForMayFail (may charge gas/fail), extracting emitRuntimeSoftErrorCheck as a shared helper --- src/compiler/evm_frontend/evm_imported.cpp | 81 +++---- .../evm_frontend/evm_mir_compiler.cpp | 199 ++++++++++-------- src/compiler/evm_frontend/evm_mir_compiler.h | 7 + src/runtime/evm_instance.cpp | 69 ++---- src/runtime/evm_instance.h | 6 +- 5 files changed, 160 insertions(+), 202 deletions(-) diff --git a/src/compiler/evm_frontend/evm_imported.cpp b/src/compiler/evm_frontend/evm_imported.cpp index fe0592253..166b26c8f 100644 --- a/src/compiler/evm_frontend/evm_imported.cpp +++ b/src/compiler/evm_frontend/evm_imported.cpp @@ -103,30 +103,6 @@ inline void triggerStaticModeViolation(zen::runtime::EVMInstance *Instance) { Instance, zen::common::ErrorCode::EVMStaticModeViolation); } -inline bool chargeGasHostApi(zen::runtime::EVMInstance *Instance, - uint64_t GasCost) { - if (GasCost == 0) { - return true; - } -#if defined(ZEN_ENABLE_JIT) && !defined(ZEN_ENABLE_CPU_EXCEPTION) - const uint64_t GasLeft = Instance->getGas(); - if (GasLeft < GasCost) { - zen::runtime::EVMInstance::setInstanceExceptionOnJIT( - Instance, zen::common::ErrorCode::GasLimitExceeded); - return false; - } - const uint64_t NewGas = GasLeft - GasCost; - Instance->setGas(NewGas); - if (evmc_message *Msg = Instance->getCurrentMessage()) { - Msg->gas = static_cast(NewGas); - } - return true; -#else - Instance->chargeGas(GasCost); - return true; -#endif -} - constexpr uint8_t DelegationMagicBytes[] = {0xef, 0x01, 0x00}; bool resolveDelegatedCallCodeAddress(zen::runtime::EVMInstance *Instance, @@ -159,7 +135,7 @@ bool resolveDelegatedCallCodeAddress(zen::runtime::EVMInstance *Instance, Module->Host->access_account(CodeAddr) == EVMC_ACCESS_COLD ? zen::evm::COLD_ACCOUNT_ACCESS_COST : zen::evm::WARM_STORAGE_READ_COST; - return chargeGasHostApi(Instance, DelegateAccessCost); + return Instance->chargeGas(DelegateAccessCost); } } // namespace @@ -329,7 +305,7 @@ const intx::uint256 *evmGetExp(zen::runtime::EVMInstance *Instance, const uint64_t GasPerByte = Rev < EVMC_SPURIOUS_DRAGON ? zen::evm::EXP_BYTE_GAS_PRE_SPURIOUS_DRAGON : zen::evm::EXP_BYTE_GAS; - if (!chargeGasHostApi(Instance, ExponentByteSize * GasPerByte)) { + if (!Instance->chargeGas(ExponentByteSize * GasPerByte)) { return storeUint256Result(intx::uint256{0}); } @@ -356,8 +332,7 @@ const intx::uint256 *evmGetBalance(zen::runtime::EVMInstance *Instance, evmc_revision Rev = Instance->getRevision(); if (Rev >= EVMC_BERLIN && Module->Host->access_account(Addr) == EVMC_ACCESS_COLD) { - if (!chargeGasHostApi(Instance, - zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { + if (!Instance->chargeGas(zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { return storeUint256Result(intx::uint256{0}); } } @@ -435,8 +410,7 @@ uint64_t evmGetExtCodeSize(zen::runtime::EVMInstance *Instance, evmc_revision Rev = Instance->getRevision(); if (Rev >= EVMC_BERLIN && Module->Host->access_account(Addr) == EVMC_ACCESS_COLD) { - if (!chargeGasHostApi(Instance, - zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { + if (!Instance->chargeGas(zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { return 0; } } @@ -455,8 +429,7 @@ const intx::uint256 *evmGetExtCodeHash(zen::runtime::EVMInstance *Instance, evmc_revision Rev = Instance->getRevision(); if (Rev >= EVMC_BERLIN && Module->Host->access_account(Addr) == EVMC_ACCESS_COLD) { - if (!chargeGasHostApi(Instance, - zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { + if (!Instance->chargeGas(zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { return storeUint256Result(intx::uint256{0}); } } @@ -632,7 +605,7 @@ void evmSetCallDataCopy(zen::runtime::EVMInstance *Instance, return; } if (uint64_t CopyGas = calculateWordCopyGas(Size)) { - if (!chargeGasHostApi(Instance, CopyGas)) { + if (!Instance->chargeGas(CopyGas)) { return; } } @@ -674,8 +647,7 @@ void evmSetExtCodeCopy(zen::runtime::EVMInstance *Instance, evmc_revision Rev = Instance->getRevision(); if (Rev >= EVMC_BERLIN && Module->Host->access_account(Addr) == EVMC_ACCESS_COLD) { - if (!chargeGasHostApi(Instance, - zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { + if (!Instance->chargeGas(zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { return; } } @@ -685,7 +657,7 @@ void evmSetExtCodeCopy(zen::runtime::EVMInstance *Instance, } if (uint64_t CopyGas = calculateWordCopyGas(Size)) { - if (!chargeGasHostApi(Instance, CopyGas)) { + if (!Instance->chargeGas(CopyGas)) { return; } } @@ -736,7 +708,7 @@ uint64_t evmSetReturnDataCopy(zen::runtime::EVMInstance *Instance, return 0; } if (uint64_t CopyGas = calculateWordCopyGas(Size)) { - if (!chargeGasHostApi(Instance, CopyGas)) { + if (!Instance->chargeGas(CopyGas)) { return 0; } } @@ -780,7 +752,7 @@ static void evmEmitLogGeneric(zen::runtime::EVMInstance *Instance, } const uint64_t LogDataCost = 8 * Size; if (LogDataCost != 0) { - if (!chargeGasHostApi(Instance, LogDataCost)) { + if (!Instance->chargeGas(LogDataCost)) { return; } } @@ -868,7 +840,7 @@ const uint8_t *evmHandleCreateInternal(zen::runtime::EVMInstance *Instance, evmc_revision Rev = Instance->getRevision(); if (Rev >= EVMC_SHANGHAI && Size > zen::evm::MAX_SIZE_OF_INITCODE) { - if (!chargeGasHostApi(Instance, Instance->getGas() + 1)) { + if (!Instance->chargeGas(Instance->getGas() + 1)) { Instance->setReturnData({}); return ZeroAddress; } @@ -884,7 +856,7 @@ const uint8_t *evmHandleCreateInternal(zen::runtime::EVMInstance *Instance, uint64_t InitCodeWords = (Size + 31) / 32; uint64_t InitCodeCost = InitCodeWordCost * InitCodeWords; if (InitCodeCost != 0) { - if (!chargeGasHostApi(Instance, InitCodeCost)) { + if (!Instance->chargeGas(InitCodeCost)) { Instance->setReturnData({}); return ZeroAddress; } @@ -932,7 +904,7 @@ const uint8_t *evmHandleCreateInternal(zen::runtime::EVMInstance *Instance, Result.gas_left > 0 ? static_cast(Result.gas_left) : 0; uint64_t GasUsed = ProvidedGas > GasLeft ? ProvidedGas - GasLeft : 0; if (GasUsed != 0) { - if (!chargeGasHostApi(Instance, GasUsed)) { + if (!Instance->chargeGas(GasUsed)) { Instance->setReturnData({}); return ZeroAddress; } @@ -980,8 +952,7 @@ static uint64_t evmHandleCallInternal( evmc_revision Rev = Instance->getRevision(); if (Rev >= EVMC_BERLIN && Module->Host->access_account(TargetAddr) == EVMC_ACCESS_COLD) { - if (!chargeGasHostApi(Instance, - zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { + if (!Instance->chargeGas(zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { Instance->setReturnData({}); return 0; } @@ -1038,7 +1009,7 @@ static uint64_t evmHandleCallInternal( } } - if (!chargeGasHostApi(Instance, GasCost)) { + if (!Instance->chargeGas(GasCost)) { Instance->setReturnData({}); return 0; } @@ -1056,15 +1027,11 @@ static uint64_t evmHandleCallInternal( } if (HasValueArgs && HasValue) { - if (Instance->getGas() > - std::numeric_limits::max() - zen::evm::CALL_GAS_STIPEND) { - zen::runtime::EVMInstance::setInstanceExceptionOnJIT( - Instance, zen::common::ErrorCode::GasLimitExceeded); + CallGas += zen::evm::CALL_GAS_STIPEND; + if (!Instance->addGas(zen::evm::CALL_GAS_STIPEND)) { Instance->setReturnData({}); return 0; } - CallGas += zen::evm::CALL_GAS_STIPEND; - Instance->addGas(zen::evm::CALL_GAS_STIPEND); const auto CallerBalance = Module->Host->get_balance(CurrentMsg->recipient); const intx::uint256 CallerValue = intx::be::load(CallerBalance); @@ -1121,7 +1088,7 @@ static uint64_t evmHandleCallInternal( GasLeft = Result.gas_left > 0 ? static_cast(Result.gas_left) : 0; uint64_t GasUsed = CallGas > GasLeft ? CallGas - GasLeft : 0; if (GasUsed > 0) { - if (!chargeGasHostApi(Instance, GasUsed)) { + if (!Instance->chargeGas(GasUsed)) { Instance->setReturnData({}); return 0; } @@ -1243,7 +1210,7 @@ void evmSetCodeCopy(zen::runtime::EVMInstance *Instance, uint64_t DestOffset, return; } if (uint64_t CopyGas = calculateWordCopyGas(Size)) { - if (!chargeGasHostApi(Instance, CopyGas)) { + if (!Instance->chargeGas(CopyGas)) { return; } } @@ -1277,7 +1244,7 @@ const uint8_t *evmGetKeccak256(zen::runtime::EVMInstance *Instance, } const uint64_t ExtraGas = static_cast(numWords(static_cast(Length))) * 6; - if (!chargeGasHostApi(Instance, ExtraGas)) { + if (!Instance->chargeGas(ExtraGas)) { return nullptr; } uint8_t *MemoryBase = Instance->getMemoryBase(); @@ -1357,7 +1324,7 @@ const intx::uint256 *evmGetSLoad(zen::runtime::EVMInstance *Instance, const auto Key = intx::be::store(Index); if (Rev >= EVMC_BERLIN && Module->Host->access_storage(Msg->recipient, Key) == EVMC_ACCESS_COLD) { - if (!chargeGasHostApi(Instance, zen::evm::ADDITIONAL_COLD_SLOAD_COST)) { + if (!Instance->chargeGas(zen::evm::ADDITIONAL_COLD_SLOAD_COST)) { return storeUint256Result(intx::uint256{0}); } } @@ -1399,7 +1366,7 @@ void evmSetSStore(zen::runtime::EVMInstance *Instance, const auto [GasCostWarm, GasReFund] = zen::evm::SSTORE_COSTS[Rev][Status]; const auto GasCost = GasCostCold + GasCostWarm; - if (!chargeGasHostApi(Instance, GasCost)) { + if (!Instance->chargeGas(GasCost)) { return; } Instance->addGasRefund(GasReFund); @@ -1449,7 +1416,7 @@ void evmHandleSelfDestruct(zen::runtime::EVMInstance *Instance, const bool IsCold = Module->Host->access_account(BenefAddr) == EVMC_ACCESS_COLD; if (IsCold) { - if (!chargeGasHostApi(Instance, zen::evm::COLD_ACCOUNT_ACCESS_COST)) { + if (!Instance->chargeGas(zen::evm::COLD_ACCOUNT_ACCESS_COST)) { return; } } @@ -1460,7 +1427,7 @@ void evmHandleSelfDestruct(zen::runtime::EVMInstance *Instance, if (Rev == EVMC_TANGERINE_WHISTLE || Module->Host->get_balance(Msg->recipient)) { if (!Module->Host->account_exists(BenefAddr)) { - if (!chargeGasHostApi(Instance, zen::evm::ACCOUNT_CREATION_COST)) { + if (!Instance->chargeGas(zen::evm::ACCOUNT_CREATION_COST)) { return; } } diff --git a/src/compiler/evm_frontend/evm_mir_compiler.cpp b/src/compiler/evm_frontend/evm_mir_compiler.cpp index c6854fe71..58a6bffc5 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.cpp +++ b/src/compiler/evm_frontend/evm_mir_compiler.cpp @@ -3922,7 +3922,7 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleBalance(Operand Address) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - auto Result = callRuntimeFor( + auto Result = callRuntimeForMayFail( RuntimeFunctions.GetBalance, Address); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); @@ -3989,7 +3989,7 @@ void EVMMirBuilder::handleCodeCopy(Operand DestOffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - callRuntimeFor( + callRuntimeForMayFail( RuntimeFunctions.SetCodeCopy, DestOffsetComponents, OffsetComponents, SizeComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER @@ -4004,7 +4004,7 @@ EVMMirBuilder::handleExtCodeSize(Operand Address) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - auto Result = callRuntimeFor( + auto Result = callRuntimeForMayFail( RuntimeFunctions.GetExtCodeSize, Address); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); @@ -4018,7 +4018,7 @@ EVMMirBuilder::handleExtCodeHash(Operand Address) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - auto Result = callRuntimeFor( + auto Result = callRuntimeForMayFail( RuntimeFunctions.GetExtCodeHash, Address); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); @@ -4554,21 +4554,22 @@ void EVMMirBuilder::handleLogWithTopics(Operand OffsetOp, Operand SizeOp, syncGasToMemory(); #endif if constexpr (NumTopics == 0) { - callRuntimeFor(RuntimeFunctions.EmitLog0, - OffsetOp, SizeOp); + callRuntimeForMayFail(RuntimeFunctions.EmitLog0, + OffsetOp, SizeOp); } else if constexpr (NumTopics == 1) { - callRuntimeFor( + callRuntimeForMayFail( RuntimeFunctions.EmitLog1, OffsetOp, SizeOp, Topics...); } else if constexpr (NumTopics == 2) { - callRuntimeFor( - RuntimeFunctions.EmitLog2, OffsetOp, SizeOp, Topics...); + callRuntimeForMayFail(RuntimeFunctions.EmitLog2, OffsetOp, + SizeOp, Topics...); } else if constexpr (NumTopics == 3) { - callRuntimeFor(RuntimeFunctions.EmitLog3, OffsetOp, SizeOp, - Topics...); + callRuntimeForMayFail( + RuntimeFunctions.EmitLog3, OffsetOp, SizeOp, Topics...); } else { // NumTopics == 4 - callRuntimeFor( + callRuntimeForMayFail( RuntimeFunctions.EmitLog4, OffsetOp, SizeOp, Topics...); } #ifdef ZEN_ENABLE_EVM_GAS_REGISTER @@ -4584,9 +4585,9 @@ EVMMirBuilder::handleCreate(Operand ValueOp, Operand OffsetOp, Operand SizeOp) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - auto Result = callRuntimeFor(RuntimeFunctions.HandleCreate, ValueOp, - OffsetOp, SizeOp); + auto Result = callRuntimeForMayFail( + RuntimeFunctions.HandleCreate, ValueOp, OffsetOp, SizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -4603,8 +4604,8 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleCreate2(Operand ValueOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - auto Result = callRuntimeFor( + auto Result = callRuntimeForMayFail( RuntimeFunctions.HandleCreate2, ValueOp, OffsetOp, SizeOp, SaltOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); @@ -4628,11 +4629,11 @@ EVMMirBuilder::handleCall(Operand GasOp, Operand ToAddrOp, Operand ValueOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - auto Result = - callRuntimeFor( - RuntimeFunctions.HandleCall, GasOp, ToAddrOp, ValueOp, ArgsOffsetOp, - ArgsSizeOp, RetOffsetOp, RetSizeOp); + auto Result = callRuntimeForMayFail( + RuntimeFunctions.HandleCall, GasOp, ToAddrOp, ValueOp, ArgsOffsetOp, + ArgsSizeOp, RetOffsetOp, RetSizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -4655,11 +4656,11 @@ EVMMirBuilder::handleCallCode(Operand GasOp, Operand ToAddrOp, Operand ValueOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - auto Result = - callRuntimeFor( - RuntimeFunctions.HandleCallCode, GasOp, ToAddrOp, ValueOp, - ArgsOffsetOp, ArgsSizeOp, RetOffsetOp, RetSizeOp); + auto Result = callRuntimeForMayFail( + RuntimeFunctions.HandleCallCode, GasOp, ToAddrOp, ValueOp, ArgsOffsetOp, + ArgsSizeOp, RetOffsetOp, RetSizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -4676,7 +4677,7 @@ void EVMMirBuilder::handleReturn(Operand MemOffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - callRuntimeFor( + callRuntimeForMayFail( RuntimeFunctions.SetReturn, MemOffsetComponents, LengthComponents); // The runtime SetReturn may charge memory expansion gas via chargeGas(), @@ -4708,8 +4709,8 @@ EVMMirBuilder::handleDelegateCall(Operand GasOp, Operand ToAddrOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - auto Result = callRuntimeFor( + auto Result = callRuntimeForMayFail( RuntimeFunctions.HandleDelegateCall, GasOp, ToAddrOp, ArgsOffsetOp, ArgsSizeOp, RetOffsetOp, RetSizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER @@ -4734,8 +4735,8 @@ EVMMirBuilder::handleStaticCall(Operand GasOp, Operand ToAddrOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - auto Result = callRuntimeFor( + auto Result = callRuntimeForMayFail( RuntimeFunctions.HandleStaticCall, GasOp, ToAddrOp, ArgsOffsetOp, ArgsSizeOp, RetOffsetOp, RetSizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER @@ -4753,8 +4754,8 @@ void EVMMirBuilder::handleRevert(Operand OffsetOp, Operand SizeOp) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - callRuntimeFor(RuntimeFunctions.SetRevert, OffsetOp, - SizeOp); + callRuntimeForMayFail(RuntimeFunctions.SetRevert, + OffsetOp, SizeOp); // The runtime SetRevert may charge memory expansion gas via chargeGas(), // which updates Instance->Gas directly. We must NOT branch to the shared @@ -4809,8 +4810,9 @@ EVMMirBuilder::handleSLoad(Operand KeyComponents) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - auto Result = callRuntimeFor( - RuntimeFunctions.GetSLoad, KeyComponents); + auto Result = + callRuntimeForMayFail( + RuntimeFunctions.GetSLoad, KeyComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -4822,7 +4824,7 @@ void EVMMirBuilder::handleSStore(Operand KeyComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - callRuntimeFor( + callRuntimeForMayFail( RuntimeFunctions.SetSStore, KeyComponents, ValueComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); @@ -4846,8 +4848,8 @@ void EVMMirBuilder::handleSelfDestruct(Operand Beneficiary) { // leftover amount to the caller. syncGasToMemoryFull(); #endif - callRuntimeFor(RuntimeFunctions.HandleSelfDestruct, - Beneficiary); + callRuntimeForMayFail( + RuntimeFunctions.HandleSelfDestruct, Beneficiary); // The runtime function (evmHandleSelfDestruct) calls popMessage() which may // set CurrentMessage to nullptr when there is no parent frame. The shared @@ -4872,7 +4874,7 @@ EVMMirBuilder::handleKeccak256(Operand OffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - auto Result = callRuntimeFor( + auto Result = callRuntimeForMayFail( RuntimeFunctions.GetKeccak256, OffsetComponents, LengthComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); @@ -5650,49 +5652,23 @@ EVMMirBuilder::callRuntimeFor(RetType (*RuntimeFunc)(runtime::EVMInstance *)) { IsStmt, ReturnType, FuncAddrInst, llvm::ArrayRef(InstancePtr)); -#if !defined(ZEN_ENABLE_CPU_EXCEPTION) - // In check mode, hostapi reports soft errors by writing instance error code. - // Convert it into explicit control flow immediately after each hostapi call. - MInstruction *GetErrAddr = createIntConstInstruction( - &Ctx.I64Type, getFunctionAddress(evmGetErrorCode)); - MInstruction *ErrCodeInstr = createInstruction( - false, EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), - GetErrAddr, llvm::ArrayRef(InstancePtr)); - Variable *ErrCodeVar = - storeInstructionInTemp(ErrCodeInstr, ErrCodeInstr->getType()); - MInstruction *ErrCodeValue = loadVariable(ErrCodeVar); - MInstruction *StaticViolationCode = createIntConstInstruction( - EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), - common::to_underlying(ErrorCode::EVMStaticModeViolation)); - MInstruction *GasExceededCode = createIntConstInstruction( - EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), - common::to_underlying(ErrorCode::GasLimitExceeded)); - MInstruction *HasGasExceeded = createInstruction( - false, CmpInstruction::Predicate::ICMP_EQ, - EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), ErrCodeValue, - GasExceededCode); - MBasicBlock *CheckStaticBB = createBasicBlock(); - MBasicBlock *ContinueBB = createBasicBlock(); - MBasicBlock *GasTrapBB = - getOrCreateExceptionSetBB(ErrorCode::GasLimitExceeded); - createInstruction(true, Ctx, HasGasExceeded, GasTrapBB, - CheckStaticBB); - addUniqueSuccessor(GasTrapBB); - addSuccessor(CheckStaticBB); - setInsertBlock(CheckStaticBB); - MInstruction *HasStaticViolation = createInstruction( - false, CmpInstruction::Predicate::ICMP_EQ, - EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), ErrCodeValue, - StaticViolationCode); - MBasicBlock *StaticTrapBB = - getOrCreateExceptionSetBB(ErrorCode::EVMStaticModeViolation); - createInstruction(true, Ctx, HasStaticViolation, - StaticTrapBB, ContinueBB); - addUniqueSuccessor(StaticTrapBB); - addSuccessor(ContinueBB); - setInsertBlock(ContinueBB); -#endif + return convertCallResult(CallInstr); +} + +template +typename EVMMirBuilder::Operand EVMMirBuilder::callRuntimeForMayFail( + RetType (*RuntimeFunc)(runtime::EVMInstance *)) { + MType *I64Type = EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64); + uint64_t FuncAddr = getFunctionAddress(RuntimeFunc); + MInstruction *FuncAddrInst = createIntConstInstruction(I64Type, FuncAddr); + MInstruction *InstancePtr = getCurrentInstancePointer(); + MType *ReturnType = getMIRReturnType(); + const bool IsStmt = std::is_same_v; + MInstruction *CallInstr = createInstruction( + IsStmt, ReturnType, FuncAddrInst, + llvm::ArrayRef(InstancePtr)); + emitRuntimeSoftErrorCheck(InstancePtr); return convertCallResult(CallInstr); } @@ -5916,9 +5892,45 @@ EVMMirBuilder::Operand EVMMirBuilder::callRuntimeFor( MInstruction *CallInstr = createInstruction( IsStmt, ReturnType, FuncAddrInst, llvm::ArrayRef{Args}); + return convertCallResult(CallInstr); +} + +template +EVMMirBuilder::Operand EVMMirBuilder::callRuntimeForMayFail( + RetType (*RuntimeFunc)(runtime::EVMInstance *, ArgTypes...), + const ParamTypes &...Params) { + MType *I64Type = EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64); + uint64_t FuncAddr = getFunctionAddress(RuntimeFunc); + MInstruction *FuncAddrInst = createIntConstInstruction(I64Type, FuncAddr); + MInstruction *InstancePtr = getCurrentInstancePointer(); + + std::vector Args = {InstancePtr}; + auto ParamsTuple = std::forward_as_tuple(Params...); + std::size_t ScratchCursor = 0; + auto PushOne = [this, &Args, &ParamsTuple, &ScratchCursor](auto IndexTag) { + constexpr std::size_t I = decltype(IndexTag)::value; + using ArgT = typename std::tuple_element>::type; + this->appendRuntimeArg(Args, std::get(ParamsTuple), ScratchCursor); + }; + auto PushAll = [&](auto Self, auto IndexTag) -> void { + constexpr std::size_t I = decltype(IndexTag)::value; + if constexpr (I < sizeof...(ArgTypes)) { + PushOne(IndexTag); + Self(Self, std::integral_constant{}); + } + }; + PushAll(PushAll, std::integral_constant{}); + + MType *ReturnType = getMIRReturnType(); + const bool IsStmt = std::is_same_v; + MInstruction *CallInstr = createInstruction( + IsStmt, ReturnType, FuncAddrInst, llvm::ArrayRef{Args}); + emitRuntimeSoftErrorCheck(InstancePtr); + return convertCallResult(CallInstr); +} + +void EVMMirBuilder::emitRuntimeSoftErrorCheck(MInstruction *InstancePtr) { #if !defined(ZEN_ENABLE_CPU_EXCEPTION) - // Keep check mode deterministic by converting hostapi soft errors into - // explicit exception control flow. MInstruction *GetErrAddr = createIntConstInstruction( &Ctx.I64Type, getFunctionAddress(evmGetErrorCode)); MInstruction *ErrCodeInstr = createInstruction( @@ -5957,9 +5969,9 @@ EVMMirBuilder::Operand EVMMirBuilder::callRuntimeFor( addUniqueSuccessor(StaticTrapBB); addSuccessor(ContinueBB); setInsertBlock(ContinueBB); +#else + (void)InstancePtr; #endif - - return convertCallResult(CallInstr); } MInstruction *EVMMirBuilder::getCurrentInstancePointer() { @@ -5980,7 +5992,7 @@ void EVMMirBuilder::handleCallDataCopy(Operand DestOffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - callRuntimeFor( + callRuntimeForMayFail( RuntimeFunctions.SetCallDataCopy, DestOffsetComponents, OffsetComponents, SizeComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER @@ -6004,7 +6016,7 @@ void EVMMirBuilder::handleExtCodeCopy(Operand AddressComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - callRuntimeFor( + callRuntimeForMayFail( RuntimeFunctions.SetExtCodeCopy, AddressComponents, DestOffsetComponents, OffsetComponents, SizeComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER @@ -6027,9 +6039,10 @@ void EVMMirBuilder::handleReturnDataCopy(Operand DestOffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - Operand StatusOp = callRuntimeFor( - RuntimeFunctions.SetReturnDataCopy, DestOffsetComponents, - OffsetComponents, SizeComponents); + Operand StatusOp = + callRuntimeForMayFail( + RuntimeFunctions.SetReturnDataCopy, DestOffsetComponents, + OffsetComponents, SizeComponents); MType *I64Type = EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64); U256Inst StatusParts = extractU256Operand(StatusOp); diff --git a/src/compiler/evm_frontend/evm_mir_compiler.h b/src/compiler/evm_frontend/evm_mir_compiler.h index 6519acc38..71abc0ffa 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.h +++ b/src/compiler/evm_frontend/evm_mir_compiler.h @@ -1035,6 +1035,8 @@ class EVMMirBuilder final { // Template versions of runtime calls template Operand callRuntimeFor(RetType (*RuntimeFunc)(runtime::EVMInstance *)); + template + Operand callRuntimeForMayFail(RetType (*RuntimeFunc)(runtime::EVMInstance *)); template U256Inst convertOperandToInstruction(const Operand &Param); @@ -1049,6 +1051,11 @@ class EVMMirBuilder final { Operand callRuntimeFor(RetType (*RuntimeFunc)(runtime::EVMInstance *, ArgTypes...), const ParamTypes &...Params); + template + Operand callRuntimeForMayFail(RetType (*RuntimeFunc)(runtime::EVMInstance *, + ArgTypes...), + const ParamTypes &...Params); + void emitRuntimeSoftErrorCheck(MInstruction *InstancePtr); // Helper template functions for runtime call type mapping template MType *getMIRReturnType(); diff --git a/src/runtime/evm_instance.cpp b/src/runtime/evm_instance.cpp index 84fff5258..e8d5b6e07 100644 --- a/src/runtime/evm_instance.cpp +++ b/src/runtime/evm_instance.cpp @@ -206,23 +206,12 @@ void EVMInstance::triggerInstanceExceptionOnJIT(EVMInstance *Inst, } #endif // ZEN_ENABLE_JIT -void EVMInstance::expandMemory(uint64_t RequiredSize) { +bool EVMInstance::expandMemory(uint64_t RequiredSize) { auto NewSize = (RequiredSize + 31) / 32 * 32; uint64_t ExpansionCost = calculateMemoryExpansionCost(MemorySize, NewSize); -#if defined(ZEN_ENABLE_JIT) && !defined(ZEN_ENABLE_CPU_EXCEPTION) - const uint64_t GasLeft = getGas(); - if (GasLeft < ExpansionCost) { - setInstanceExceptionOnJIT(this, common::ErrorCode::GasLimitExceeded); - return; - } - const uint64_t NewGas = GasLeft - ExpansionCost; - setGas(NewGas); - if (evmc_message *Msg = getCurrentMessage()) { - Msg->gas = static_cast(NewGas); + if (!chargeGas(ExpansionCost)) { + return false; } -#else - chargeGas(ExpansionCost); -#endif if (NewSize > MemorySize) { if (!MemoryBase) { ensureMemoryBuffer(Memory, MemoryBase); @@ -233,6 +222,7 @@ void EVMInstance::expandMemory(uint64_t RequiredSize) { MemorySize = NewSize; } } + return true; } void EVMInstance::expandMemoryNoGas(uint64_t RequiredSize) { @@ -250,45 +240,23 @@ void EVMInstance::expandMemoryNoGas(uint64_t RequiredSize) { } bool EVMInstance::expandMemoryChecked(uint64_t Offset, uint64_t Size) { - auto markOutOfGas = [&]() { -#if defined(ZEN_ENABLE_JIT) && !defined(ZEN_ENABLE_CPU_EXCEPTION) - // Check mode: mark soft OOG and return to caller. - setInstanceExceptionOnJIT(this, common::ErrorCode::GasLimitExceeded); -#else - // Interpreter/CPU-exception modes preserve existing throw/trap behavior. - chargeGas(getGas() + 1); -#endif - }; if (Size == 0) { return true; } uint64_t RequiredSize = 0; if (!calcRequiredMemorySize(Offset, Size, RequiredSize)) { - markOutOfGas(); + (void)chargeGas(getGas() + 1); return false; } if (RequiredSize > zen::evm::MAX_REQUIRED_MEMORY_SIZE) { - markOutOfGas(); - return false; - } - expandMemory(RequiredSize); - if (getError().getCode() == common::ErrorCode::GasLimitExceeded) { + (void)chargeGas(getGas() + 1); return false; } - return true; + return expandMemory(RequiredSize); } bool EVMInstance::expandMemoryChecked(uint64_t OffsetA, uint64_t SizeA, uint64_t OffsetB, uint64_t SizeB) { - auto markOutOfGas = [&]() { -#if defined(ZEN_ENABLE_JIT) && !defined(ZEN_ENABLE_CPU_EXCEPTION) - // Check mode: mark soft OOG and return to caller. - setInstanceExceptionOnJIT(this, common::ErrorCode::GasLimitExceeded); -#else - // Interpreter/CPU-exception modes preserve existing throw/trap behavior. - chargeGas(getGas() + 1); -#endif - }; const bool NeedA = SizeA > 0; const bool NeedB = SizeB > 0; if (!NeedA && !NeedB) { @@ -304,47 +272,49 @@ bool EVMInstance::expandMemoryChecked(uint64_t OffsetA, uint64_t SizeA, uint64_t RequiredSizeB = 0; if (!calcRequiredMemorySize(OffsetA, SizeA, RequiredSizeA) || !calcRequiredMemorySize(OffsetB, SizeB, RequiredSizeB)) { - markOutOfGas(); + (void)chargeGas(getGas() + 1); return false; } const uint64_t RequiredSize = std::max(RequiredSizeA, RequiredSizeB); if (RequiredSize > zen::evm::MAX_REQUIRED_MEMORY_SIZE) { - markOutOfGas(); - return false; - } - expandMemory(RequiredSize); - if (getError().getCode() == common::ErrorCode::GasLimitExceeded) { + (void)chargeGas(getGas() + 1); return false; } - return true; + return expandMemory(RequiredSize); } -void EVMInstance::chargeGas(uint64_t GasCost) { +bool EVMInstance::chargeGas(uint64_t GasCost) { evmc_message *Msg = getCurrentMessage(); ZEN_ASSERT(Msg && "Active message required for gas accounting"); uint64_t GasLeft = getGas(); if (GasLeft < GasCost) { #if defined(ZEN_ENABLE_JIT) && defined(ZEN_ENABLE_CPU_EXCEPTION) triggerInstanceExceptionOnJIT(this, common::ErrorCode::GasLimitExceeded); +#elif defined(ZEN_ENABLE_JIT) && !defined(ZEN_ENABLE_CPU_EXCEPTION) + setInstanceExceptionOnJIT(this, common::ErrorCode::GasLimitExceeded); + return false; #else throw common::getError(common::ErrorCode::GasLimitExceeded); #endif + return false; } uint64_t NewGas = GasLeft - GasCost; setGas(NewGas); Msg->gas = static_cast(NewGas); + return true; } -void EVMInstance::addGas(uint64_t GasAmount) { +bool EVMInstance::addGas(uint64_t GasAmount) { evmc_message *Msg = getCurrentMessage(); ZEN_ASSERT(Msg && "Active message required for gas accounting"); uint64_t GasLeft = getGas(); if (GasLeft > UINT64_MAX - GasAmount) { #if defined(ZEN_ENABLE_JIT) && !defined(ZEN_ENABLE_CPU_EXCEPTION) setInstanceExceptionOnJIT(this, common::ErrorCode::GasLimitExceeded); - return; + return false; #elif defined(ZEN_ENABLE_JIT) && defined(ZEN_ENABLE_CPU_EXCEPTION) triggerInstanceExceptionOnJIT(this, common::ErrorCode::GasLimitExceeded); + return false; #else throw common::getError(common::ErrorCode::GasLimitExceeded); #endif @@ -352,6 +322,7 @@ void EVMInstance::addGas(uint64_t GasAmount) { uint64_t NewGas = GasLeft + GasAmount; setGas(NewGas); Msg->gas = static_cast(NewGas); + return true; } } // namespace zen::runtime diff --git a/src/runtime/evm_instance.h b/src/runtime/evm_instance.h index 63801cb04..6199da8fd 100644 --- a/src/runtime/evm_instance.h +++ b/src/runtime/evm_instance.h @@ -62,13 +62,13 @@ class EVMInstance final : public RuntimeObject { static uint64_t calculateMemoryExpansionCost(uint64_t CurrentSize, uint64_t NewSize); void consumeMemoryExpansionGas(uint64_t RequiredSize); - void expandMemory(uint64_t RequiredSize); + bool expandMemory(uint64_t RequiredSize); void expandMemoryNoGas(uint64_t RequiredSize); bool expandMemoryChecked(uint64_t Offset, uint64_t Size); bool expandMemoryChecked(uint64_t OffsetA, uint64_t SizeA, uint64_t OffsetB, uint64_t SizeB); - void chargeGas(uint64_t GasCost); - void addGas(uint64_t GasAmount); + bool chargeGas(uint64_t GasCost); + bool addGas(uint64_t GasAmount); void addGasRefund(int64_t Amount) { GasRefund += Amount; } void setGasRefund(int64_t Amount) { GasRefund = Amount; } From 5849547a0b20cb4a9782d0fdf4ec569c35e7a3cb Mon Sep 17 00:00:00 2001 From: cmgCr Date: Fri, 15 May 2026 09:01:51 +0000 Subject: [PATCH 3/3] refactor(evm): clarify runtime error-check call semantics Rename callRuntimeForMayFail to callRuntimeForWithErrorCheck, keep TSTORE on checked runtime path, and document oversize-memory OOG signaling to make check-mode behavior explicit and safer to maintain. --- .../evm_frontend/evm_mir_compiler.cpp | 159 ++++++++++-------- src/compiler/evm_frontend/evm_mir_compiler.h | 11 +- src/runtime/evm_instance.cpp | 19 ++- 3 files changed, 114 insertions(+), 75 deletions(-) diff --git a/src/compiler/evm_frontend/evm_mir_compiler.cpp b/src/compiler/evm_frontend/evm_mir_compiler.cpp index 58a6bffc5..2fdca8295 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.cpp +++ b/src/compiler/evm_frontend/evm_mir_compiler.cpp @@ -3922,8 +3922,9 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleBalance(Operand Address) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - auto Result = callRuntimeForMayFail( - RuntimeFunctions.GetBalance, Address); + auto Result = + callRuntimeForWithErrorCheck( + RuntimeFunctions.GetBalance, Address); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -3989,7 +3990,7 @@ void EVMMirBuilder::handleCodeCopy(Operand DestOffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - callRuntimeForMayFail( + callRuntimeForWithErrorCheck( RuntimeFunctions.SetCodeCopy, DestOffsetComponents, OffsetComponents, SizeComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER @@ -4004,7 +4005,7 @@ EVMMirBuilder::handleExtCodeSize(Operand Address) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - auto Result = callRuntimeForMayFail( + auto Result = callRuntimeForWithErrorCheck( RuntimeFunctions.GetExtCodeSize, Address); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); @@ -4018,8 +4019,9 @@ EVMMirBuilder::handleExtCodeHash(Operand Address) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - auto Result = callRuntimeForMayFail( - RuntimeFunctions.GetExtCodeHash, Address); + auto Result = + callRuntimeForWithErrorCheck( + RuntimeFunctions.GetExtCodeHash, Address); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -4554,23 +4556,24 @@ void EVMMirBuilder::handleLogWithTopics(Operand OffsetOp, Operand SizeOp, syncGasToMemory(); #endif if constexpr (NumTopics == 0) { - callRuntimeForMayFail(RuntimeFunctions.EmitLog0, - OffsetOp, SizeOp); + callRuntimeForWithErrorCheck( + RuntimeFunctions.EmitLog0, OffsetOp, SizeOp); } else if constexpr (NumTopics == 1) { - callRuntimeForMayFail( + callRuntimeForWithErrorCheck( RuntimeFunctions.EmitLog1, OffsetOp, SizeOp, Topics...); } else if constexpr (NumTopics == 2) { - callRuntimeForMayFail(RuntimeFunctions.EmitLog2, OffsetOp, - SizeOp, Topics...); + callRuntimeForWithErrorCheck(RuntimeFunctions.EmitLog2, + OffsetOp, SizeOp, Topics...); } else if constexpr (NumTopics == 3) { - callRuntimeForMayFail( + callRuntimeForWithErrorCheck( RuntimeFunctions.EmitLog3, OffsetOp, SizeOp, Topics...); } else { // NumTopics == 4 - callRuntimeForMayFail( - RuntimeFunctions.EmitLog4, OffsetOp, SizeOp, Topics...); + callRuntimeForWithErrorCheck(RuntimeFunctions.EmitLog4, + OffsetOp, SizeOp, Topics...); } #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); @@ -4585,9 +4588,10 @@ EVMMirBuilder::handleCreate(Operand ValueOp, Operand OffsetOp, Operand SizeOp) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - auto Result = callRuntimeForMayFail( - RuntimeFunctions.HandleCreate, ValueOp, OffsetOp, SizeOp); + auto Result = + callRuntimeForWithErrorCheck( + RuntimeFunctions.HandleCreate, ValueOp, OffsetOp, SizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -4604,9 +4608,10 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleCreate2(Operand ValueOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - auto Result = callRuntimeForMayFail( - RuntimeFunctions.HandleCreate2, ValueOp, OffsetOp, SizeOp, SaltOp); + auto Result = + callRuntimeForWithErrorCheck( + RuntimeFunctions.HandleCreate2, ValueOp, OffsetOp, SizeOp, SaltOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -4629,11 +4634,12 @@ EVMMirBuilder::handleCall(Operand GasOp, Operand ToAddrOp, Operand ValueOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - auto Result = callRuntimeForMayFail( - RuntimeFunctions.HandleCall, GasOp, ToAddrOp, ValueOp, ArgsOffsetOp, - ArgsSizeOp, RetOffsetOp, RetSizeOp); + auto Result = + callRuntimeForWithErrorCheck( + RuntimeFunctions.HandleCall, GasOp, ToAddrOp, ValueOp, ArgsOffsetOp, + ArgsSizeOp, RetOffsetOp, RetSizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -4656,11 +4662,12 @@ EVMMirBuilder::handleCallCode(Operand GasOp, Operand ToAddrOp, Operand ValueOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - auto Result = callRuntimeForMayFail( - RuntimeFunctions.HandleCallCode, GasOp, ToAddrOp, ValueOp, ArgsOffsetOp, - ArgsSizeOp, RetOffsetOp, RetSizeOp); + auto Result = + callRuntimeForWithErrorCheck( + RuntimeFunctions.HandleCallCode, GasOp, ToAddrOp, ValueOp, + ArgsOffsetOp, ArgsSizeOp, RetOffsetOp, RetSizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -4677,7 +4684,7 @@ void EVMMirBuilder::handleReturn(Operand MemOffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - callRuntimeForMayFail( + callRuntimeForWithErrorCheck( RuntimeFunctions.SetReturn, MemOffsetComponents, LengthComponents); // The runtime SetReturn may charge memory expansion gas via chargeGas(), @@ -4709,10 +4716,11 @@ EVMMirBuilder::handleDelegateCall(Operand GasOp, Operand ToAddrOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - auto Result = callRuntimeForMayFail( - RuntimeFunctions.HandleDelegateCall, GasOp, ToAddrOp, ArgsOffsetOp, - ArgsSizeOp, RetOffsetOp, RetSizeOp); + auto Result = + callRuntimeForWithErrorCheck( + RuntimeFunctions.HandleDelegateCall, GasOp, ToAddrOp, ArgsOffsetOp, + ArgsSizeOp, RetOffsetOp, RetSizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -4735,10 +4743,11 @@ EVMMirBuilder::handleStaticCall(Operand GasOp, Operand ToAddrOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - auto Result = callRuntimeForMayFail( - RuntimeFunctions.HandleStaticCall, GasOp, ToAddrOp, ArgsOffsetOp, - ArgsSizeOp, RetOffsetOp, RetSizeOp); + auto Result = + callRuntimeForWithErrorCheck( + RuntimeFunctions.HandleStaticCall, GasOp, ToAddrOp, ArgsOffsetOp, + ArgsSizeOp, RetOffsetOp, RetSizeOp); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -4754,8 +4763,8 @@ void EVMMirBuilder::handleRevert(Operand OffsetOp, Operand SizeOp) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemoryFull(); #endif - callRuntimeForMayFail(RuntimeFunctions.SetRevert, - OffsetOp, SizeOp); + callRuntimeForWithErrorCheck( + RuntimeFunctions.SetRevert, OffsetOp, SizeOp); // The runtime SetRevert may charge memory expansion gas via chargeGas(), // which updates Instance->Gas directly. We must NOT branch to the shared @@ -4810,9 +4819,9 @@ EVMMirBuilder::handleSLoad(Operand KeyComponents) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - auto Result = - callRuntimeForMayFail( - RuntimeFunctions.GetSLoad, KeyComponents); + auto Result = callRuntimeForWithErrorCheck( + RuntimeFunctions.GetSLoad, KeyComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -4824,7 +4833,8 @@ void EVMMirBuilder::handleSStore(Operand KeyComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - callRuntimeForMayFail( + callRuntimeForWithErrorCheck( RuntimeFunctions.SetSStore, KeyComponents, ValueComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); @@ -4837,7 +4847,8 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleTLoad(Operand Index) { } void EVMMirBuilder::handleTStore(Operand Index, Operand ValueComponents) { const auto &RuntimeFunctions = getRuntimeFunctionTable(); - callRuntimeFor( + callRuntimeForWithErrorCheck( RuntimeFunctions.SetTStore, Index, ValueComponents); } void EVMMirBuilder::handleSelfDestruct(Operand Beneficiary) { @@ -4848,7 +4859,7 @@ void EVMMirBuilder::handleSelfDestruct(Operand Beneficiary) { // leftover amount to the caller. syncGasToMemoryFull(); #endif - callRuntimeForMayFail( + callRuntimeForWithErrorCheck( RuntimeFunctions.HandleSelfDestruct, Beneficiary); // The runtime function (evmHandleSelfDestruct) calls popMessage() which may @@ -4874,8 +4885,9 @@ EVMMirBuilder::handleKeccak256(Operand OffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - auto Result = callRuntimeForMayFail( - RuntimeFunctions.GetKeccak256, OffsetComponents, LengthComponents); + auto Result = + callRuntimeForWithErrorCheck( + RuntimeFunctions.GetKeccak256, OffsetComponents, LengthComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -5656,7 +5668,7 @@ EVMMirBuilder::callRuntimeFor(RetType (*RuntimeFunc)(runtime::EVMInstance *)) { } template -typename EVMMirBuilder::Operand EVMMirBuilder::callRuntimeForMayFail( +typename EVMMirBuilder::Operand EVMMirBuilder::callRuntimeForWithErrorCheck( RetType (*RuntimeFunc)(runtime::EVMInstance *)) { MType *I64Type = EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64); uint64_t FuncAddr = getFunctionAddress(RuntimeFunc); @@ -5896,7 +5908,7 @@ EVMMirBuilder::Operand EVMMirBuilder::callRuntimeFor( } template -EVMMirBuilder::Operand EVMMirBuilder::callRuntimeForMayFail( +EVMMirBuilder::Operand EVMMirBuilder::callRuntimeForWithErrorCheck( RetType (*RuntimeFunc)(runtime::EVMInstance *, ArgTypes...), const ParamTypes &...Params) { MType *I64Type = EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64); @@ -5931,26 +5943,38 @@ EVMMirBuilder::Operand EVMMirBuilder::callRuntimeForMayFail( void EVMMirBuilder::emitRuntimeSoftErrorCheck(MInstruction *InstancePtr) { #if !defined(ZEN_ENABLE_CPU_EXCEPTION) + MType *U64Type = EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64); MInstruction *GetErrAddr = createIntConstInstruction( &Ctx.I64Type, getFunctionAddress(evmGetErrorCode)); MInstruction *ErrCodeInstr = createInstruction( - false, EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), - GetErrAddr, llvm::ArrayRef(InstancePtr)); + false, U64Type, GetErrAddr, llvm::ArrayRef(InstancePtr)); Variable *ErrCodeVar = storeInstructionInTemp(ErrCodeInstr, ErrCodeInstr->getType()); MInstruction *ErrCodeValue = loadVariable(ErrCodeVar); + MInstruction *NoErrorCode = createIntConstInstruction( + U64Type, common::to_underlying(ErrorCode::NoError)); + MInstruction *HasNoError = createInstruction( + false, CmpInstruction::Predicate::ICMP_EQ, U64Type, ErrCodeValue, + NoErrorCode); + MBasicBlock *ContinueBB = createBasicBlock(); + MBasicBlock *CheckKnownSoftErrBB = createBasicBlock(); + createInstruction(true, Ctx, HasNoError, ContinueBB, + CheckKnownSoftErrBB); + addSuccessor(ContinueBB); + addSuccessor(CheckKnownSoftErrBB); + setInsertBlock(CheckKnownSoftErrBB); + + // We intentionally do NOT trap on every non-zero code: + // InstanceExit and some control-flow codes are expected runtime states. + // Only trap the two hostapi soft-failure codes that must stop JIT execution. MInstruction *StaticViolationCode = createIntConstInstruction( - EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), - common::to_underlying(ErrorCode::EVMStaticModeViolation)); + U64Type, common::to_underlying(ErrorCode::EVMStaticModeViolation)); MInstruction *GasExceededCode = createIntConstInstruction( - EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), - common::to_underlying(ErrorCode::GasLimitExceeded)); + U64Type, common::to_underlying(ErrorCode::GasLimitExceeded)); MInstruction *HasGasExceeded = createInstruction( - false, CmpInstruction::Predicate::ICMP_EQ, - EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64), ErrCodeValue, + false, CmpInstruction::Predicate::ICMP_EQ, U64Type, ErrCodeValue, GasExceededCode); MBasicBlock *CheckStaticBB = createBasicBlock(); - MBasicBlock *ContinueBB = createBasicBlock(); MBasicBlock *GasTrapBB = getOrCreateExceptionSetBB(ErrorCode::GasLimitExceeded); createInstruction(true, Ctx, HasGasExceeded, GasTrapBB, @@ -5967,7 +5991,7 @@ void EVMMirBuilder::emitRuntimeSoftErrorCheck(MInstruction *InstancePtr) { createInstruction(true, Ctx, HasStaticViolation, StaticTrapBB, ContinueBB); addUniqueSuccessor(StaticTrapBB); - addSuccessor(ContinueBB); + addUniqueSuccessor(ContinueBB); setInsertBlock(ContinueBB); #else (void)InstancePtr; @@ -5992,7 +6016,7 @@ void EVMMirBuilder::handleCallDataCopy(Operand DestOffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - callRuntimeForMayFail( + callRuntimeForWithErrorCheck( RuntimeFunctions.SetCallDataCopy, DestOffsetComponents, OffsetComponents, SizeComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER @@ -6016,7 +6040,8 @@ void EVMMirBuilder::handleExtCodeCopy(Operand AddressComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - callRuntimeForMayFail( + callRuntimeForWithErrorCheck( RuntimeFunctions.SetExtCodeCopy, AddressComponents, DestOffsetComponents, OffsetComponents, SizeComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER @@ -6040,7 +6065,7 @@ void EVMMirBuilder::handleReturnDataCopy(Operand DestOffsetComponents, syncGasToMemory(); #endif Operand StatusOp = - callRuntimeForMayFail( + callRuntimeForWithErrorCheck( RuntimeFunctions.SetReturnDataCopy, DestOffsetComponents, OffsetComponents, SizeComponents); diff --git a/src/compiler/evm_frontend/evm_mir_compiler.h b/src/compiler/evm_frontend/evm_mir_compiler.h index 71abc0ffa..8423c334b 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.h +++ b/src/compiler/evm_frontend/evm_mir_compiler.h @@ -1035,8 +1035,10 @@ class EVMMirBuilder final { // Template versions of runtime calls template Operand callRuntimeFor(RetType (*RuntimeFunc)(runtime::EVMInstance *)); + // Emits host soft-error checks in check mode after runtime call. template - Operand callRuntimeForMayFail(RetType (*RuntimeFunc)(runtime::EVMInstance *)); + Operand callRuntimeForWithErrorCheck( + RetType (*RuntimeFunc)(runtime::EVMInstance *)); template U256Inst convertOperandToInstruction(const Operand &Param); @@ -1051,10 +1053,11 @@ class EVMMirBuilder final { Operand callRuntimeFor(RetType (*RuntimeFunc)(runtime::EVMInstance *, ArgTypes...), const ParamTypes &...Params); + // Emits host soft-error checks in check mode after runtime call. template - Operand callRuntimeForMayFail(RetType (*RuntimeFunc)(runtime::EVMInstance *, - ArgTypes...), - const ParamTypes &...Params); + Operand callRuntimeForWithErrorCheck( + RetType (*RuntimeFunc)(runtime::EVMInstance *, ArgTypes...), + const ParamTypes &...Params); void emitRuntimeSoftErrorCheck(MInstruction *InstancePtr); // Helper template functions for runtime call type mapping diff --git a/src/runtime/evm_instance.cpp b/src/runtime/evm_instance.cpp index e8d5b6e07..1cf1fccdc 100644 --- a/src/runtime/evm_instance.cpp +++ b/src/runtime/evm_instance.cpp @@ -240,16 +240,23 @@ void EVMInstance::expandMemoryNoGas(uint64_t RequiredSize) { } bool EVMInstance::expandMemoryChecked(uint64_t Offset, uint64_t Size) { + auto markOutOfGasForOversizedMemory = [this]() { + // Intentionally force an OOG path so all modes share one behavior: + // - check mode: chargeGas sets GasLimitExceeded (soft error, no throw) + // - CPU exception mode: trigger trap + // - interpreter mode: throw + (void)chargeGas(getGas() + 1); + }; if (Size == 0) { return true; } uint64_t RequiredSize = 0; if (!calcRequiredMemorySize(Offset, Size, RequiredSize)) { - (void)chargeGas(getGas() + 1); + markOutOfGasForOversizedMemory(); return false; } if (RequiredSize > zen::evm::MAX_REQUIRED_MEMORY_SIZE) { - (void)chargeGas(getGas() + 1); + markOutOfGasForOversizedMemory(); return false; } return expandMemory(RequiredSize); @@ -257,6 +264,10 @@ bool EVMInstance::expandMemoryChecked(uint64_t Offset, uint64_t Size) { bool EVMInstance::expandMemoryChecked(uint64_t OffsetA, uint64_t SizeA, uint64_t OffsetB, uint64_t SizeB) { + auto markOutOfGasForOversizedMemory = [this]() { + // Keep oversize/overflow handling consistent with single-range overload. + (void)chargeGas(getGas() + 1); + }; const bool NeedA = SizeA > 0; const bool NeedB = SizeB > 0; if (!NeedA && !NeedB) { @@ -272,12 +283,12 @@ bool EVMInstance::expandMemoryChecked(uint64_t OffsetA, uint64_t SizeA, uint64_t RequiredSizeB = 0; if (!calcRequiredMemorySize(OffsetA, SizeA, RequiredSizeA) || !calcRequiredMemorySize(OffsetB, SizeB, RequiredSizeB)) { - (void)chargeGas(getGas() + 1); + markOutOfGasForOversizedMemory(); return false; } const uint64_t RequiredSize = std::max(RequiredSizeA, RequiredSizeB); if (RequiredSize > zen::evm::MAX_REQUIRED_MEMORY_SIZE) { - (void)chargeGas(getGas() + 1); + markOutOfGasForOversizedMemory(); return false; } return expandMemory(RequiredSize);