diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 77a0e8f5e..a351209cf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -161,7 +161,7 @@ add_library( zetaengine-c.cpp ) if(ZEN_ENABLE_EVM) - target_link_libraries(dtvmcore PRIVATE evmc::instructions) + target_link_libraries(dtvmcore PUBLIC evmc::instructions) endif() if(NOT ZEN_ENABLE_SGX) target_link_libraries(dtvmcore PRIVATE Threads::Threads) diff --git a/src/action/evm_bytecode_visitor.h b/src/action/evm_bytecode_visitor.h index 19bd85a57..454ea6f60 100644 --- a/src/action/evm_bytecode_visitor.h +++ b/src/action/evm_bytecode_visitor.h @@ -83,6 +83,12 @@ template class EVMByteCodeVisitor { } bool IsJumpDest = (Opcode == OP_JUMPDEST); if (!IsJumpDest) { + if (!Builder.isOpcodeDefined(Opcode)) { + handleEndBlock(); + Builder.handleUndefined(); + PC++; + continue; + } Builder.meterOpcode(Opcode, PC); } diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index a58679edc..25aef379c 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -5,7 +5,7 @@ add_executable(dtvm dtvm.cpp) target_link_libraries(dtvm PRIVATE dtvmcore CLI11::CLI11) if(ZEN_ENABLE_EVM) - target_link_libraries(dtvm PUBLIC evmc::evmc) + target_link_libraries(dtvm PUBLIC evmc::evmc evmc::instructions) endif() if(ZEN_ENABLE_PROFILER) diff --git a/src/compiler/CMakeLists.txt b/src/compiler/CMakeLists.txt index f6cae3b7e..fad9b680c 100644 --- a/src/compiler/CMakeLists.txt +++ b/src/compiler/CMakeLists.txt @@ -111,6 +111,9 @@ set_property( add_library(compiler STATIC ${COMPILER_SRCS} $) target_link_libraries(compiler PRIVATE ${llvm_libs}) +if(ZEN_ENABLE_EVM) + target_link_libraries(compiler PUBLIC evmc::instructions) +endif() add_executable(ircompiler ircompiler.cpp) target_link_libraries(ircompiler PRIVATE compiler dtvmcore CLI11::CLI11) diff --git a/src/compiler/cgir/lowering.h b/src/compiler/cgir/lowering.h index 35f8f0938..643ba4132 100644 --- a/src/compiler/cgir/lowering.h +++ b/src/compiler/cgir/lowering.h @@ -257,6 +257,10 @@ template class CgLowering { case OP_popcnt: ISDOpcode = ISD::CTPOP; break; + case OP_bswap: + ZEN_ASSERT(IsInteger); + ISDOpcode = ISD::BSWAP; + break; case OP_fpabs: ZEN_ASSERT(!IsInteger); return SELF.lowerFPAbsExpr(OperandVT, OperandReg); diff --git a/src/compiler/evm_compiler.cpp b/src/compiler/evm_compiler.cpp index c35a6f316..5684f6bd9 100644 --- a/src/compiler/evm_compiler.cpp +++ b/src/compiler/evm_compiler.cpp @@ -60,6 +60,7 @@ void EagerEVMJITCompiler::compile() { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER Ctx.setGasRegisterEnabled(true); #endif + Ctx.setRevision(EVMMod->getRevision()); Ctx.setBytecode(reinterpret_cast(EVMMod->Code), EVMMod->CodeSize); const auto &Cache = EVMMod->getBytecodeCache(); diff --git a/src/compiler/evm_frontend/evm_imported.cpp b/src/compiler/evm_frontend/evm_imported.cpp index 155cbae56..c9460c227 100644 --- a/src/compiler/evm_frontend/evm_imported.cpp +++ b/src/compiler/evm_frontend/evm_imported.cpp @@ -91,19 +91,15 @@ const RuntimeFunctions &getRuntimeFunctionTable() { .GetBaseFee = &evmGetBaseFee, .GetBlobHash = &evmGetBlobHash, .GetBlobBaseFee = &evmGetBlobBaseFee, - .GetMSize = &evmGetMSize, - .GetMLoad = &evmGetMLoad, - .SetMStore = &evmSetMStore, - .SetMStore8 = &evmSetMStore8, .GetSLoad = &evmGetSLoad, .SetSStore = &evmSetSStore, .GetGas = &evmGetGas, .GetTLoad = &evmGetTLoad, .SetTStore = &evmSetTStore, - .SetMCopy = &evmSetMCopy, .SetCallDataCopy = &evmSetCallDataCopy, .SetExtCodeCopy = &evmSetExtCodeCopy, .SetReturnDataCopy = &evmSetReturnDataCopy, + .ExpandMemoryNoGas = &evmExpandMemoryNoGas, .GetReturnDataSize = &evmGetReturnDataSize, .EmitLog0 = &evmEmitLog0, .EmitLog1 = &evmEmitLog1, @@ -520,60 +516,6 @@ const intx::uint256 *evmGetBlobBaseFee(zen::runtime::EVMInstance *Instance) { intx::be::load(TxContext.blob_base_fee)); } -uint64_t evmGetMSize(zen::runtime::EVMInstance *Instance) { - return Instance->getMemorySize(); -} -const intx::uint256 *evmGetMLoad(zen::runtime::EVMInstance *Instance, - uint64_t Offset) { - if (!Instance->expandMemoryChecked(Offset, 32)) { - return storeUint256Result(intx::uint256{0}); - } - auto &Memory = Instance->getMemory(); - - uint8_t ValueBytes[32]; - std::memcpy(ValueBytes, Memory.data() + Offset, 32); - - intx::uint256 Result = intx::be::load(ValueBytes); - return storeUint256Result(Result); -} -void evmSetMStore(zen::runtime::EVMInstance *Instance, uint64_t Offset, - const intx::uint256 &Value) { - if (!Instance->expandMemoryChecked(Offset, 32)) { - return; - } - - auto &Memory = Instance->getMemory(); - uint8_t ValueBytes[32]; - intx::be::store(ValueBytes, Value); - std::memcpy(Memory.data() + Offset, ValueBytes, sizeof(ValueBytes)); -} - -void evmSetMStore8(zen::runtime::EVMInstance *Instance, uint64_t Offset, - const intx::uint256 &Value) { - if (!Instance->expandMemoryChecked(Offset, 1)) { - return; - } - - auto &Memory = Instance->getMemory(); - uint8_t ByteValue = static_cast(Value & intx::uint256{0xFF}); - Memory[Offset] = ByteValue; -} - -void evmSetMCopy(zen::runtime::EVMInstance *Instance, uint64_t Dest, - uint64_t Src, uint64_t Len) { - if (Len == 0) { - return; - } - if (uint64_t CopyGas = calculateWordCopyGas(Len)) { - Instance->chargeGas(CopyGas); - } - if (!Instance->expandMemoryChecked(Dest, Len, Src, Len)) { - return; - } - - auto &Memory = Instance->getMemory(); - std::memmove(&Memory[Dest], &Memory[Src], Len); -} void evmSetReturn(zen::runtime::EVMInstance *Instance, uint64_t Offset, uint64_t Len) { std::vector ReturnData; @@ -581,9 +523,9 @@ void evmSetReturn(zen::runtime::EVMInstance *Instance, uint64_t Offset, if (!Instance->expandMemoryChecked(Offset, Len)) { return; } - auto &Memory = Instance->getMemory(); - ReturnData = std::vector(Memory.begin() + Offset, - Memory.begin() + Offset + Len); + uint8_t *MemoryBase = Instance->getMemoryBase(); + ReturnData = + std::vector(MemoryBase + Offset, MemoryBase + Offset + Len); } Instance->setReturnData(ReturnData); @@ -611,7 +553,7 @@ void evmSetCallDataCopy(zen::runtime::EVMInstance *Instance, const evmc_message *Msg = Instance->getCurrentMessage(); ZEN_ASSERT(Msg && "No current message set in EVMInstance"); - auto &Memory = Instance->getMemory(); + uint8_t *MemoryBase = Instance->getMemoryBase(); // Calculate actual source offset and copy size uint64_t ActualOffset = @@ -623,13 +565,13 @@ void evmSetCallDataCopy(zen::runtime::EVMInstance *Instance, : 0; if (CopySize > 0) { - std::memcpy(Memory.data() + DestOffset, Msg->input_data + ActualOffset, + std::memcpy(MemoryBase + DestOffset, Msg->input_data + ActualOffset, CopySize); } // Fill remaining bytes with zeros if needed if (Size > CopySize) { - std::memset(Memory.data() + DestOffset + CopySize, 0, Size - CopySize); + std::memset(MemoryBase + DestOffset + CopySize, 0, Size - CopySize); } } @@ -657,22 +599,21 @@ void evmSetExtCodeCopy(zen::runtime::EVMInstance *Instance, Instance->chargeGas(CopyGas); } - auto &Memory = Instance->getMemory(); + uint8_t *MemoryBase = Instance->getMemoryBase(); size_t CodeSize = Module->Host->get_code_size(Addr); if (Offset >= CodeSize) { // If offset is beyond code size, fill with zeros - std::memset(Memory.data() + DestOffset, 0, Size); + std::memset(MemoryBase + DestOffset, 0, Size); } else { uint64_t CopySize = std::min(Size, static_cast(CodeSize) - Offset); size_t CopiedSize = Module->Host->copy_code( - Addr, Offset, Memory.data() + DestOffset, CopySize); + Addr, Offset, MemoryBase + DestOffset, CopySize); // Fill remaining bytes with zeros if needed if (Size > CopiedSize) { - std::memset(Memory.data() + DestOffset + CopiedSize, 0, - Size - CopiedSize); + std::memset(MemoryBase + DestOffset + CopiedSize, 0, Size - CopiedSize); } } } @@ -691,23 +632,27 @@ void evmSetReturnDataCopy(zen::runtime::EVMInstance *Instance, } const auto &ReturnData = Instance->getReturnData(); - auto &Memory = Instance->getMemory(); + uint8_t *MemoryBase = Instance->getMemoryBase(); if (Offset >= ReturnData.size()) { - std::memset(Memory.data() + DestOffset, 0, Size); + std::memset(MemoryBase + DestOffset, 0, Size); } else { uint64_t CopySize = std::min( Size, static_cast(ReturnData.size()) - Offset); - std::memcpy(Memory.data() + DestOffset, ReturnData.data() + Offset, - CopySize); + std::memcpy(MemoryBase + DestOffset, ReturnData.data() + Offset, CopySize); // Fill remaining bytes with zeros if (Size > CopySize) { - std::memset(Memory.data() + DestOffset + CopySize, 0, Size - CopySize); + std::memset(MemoryBase + DestOffset + CopySize, 0, Size - CopySize); } } } +void evmExpandMemoryNoGas(zen::runtime::EVMInstance *Instance, + uint64_t RequiredSize) { + Instance->expandMemoryNoGas(RequiredSize); +} + uint64_t evmGetReturnDataSize(zen::runtime::EVMInstance *Instance) { const auto &ReturnData = Instance->getReturnData(); return ReturnData.size(); @@ -733,8 +678,8 @@ static void evmEmitLogGeneric(zen::runtime::EVMInstance *Instance, if (!Instance->expandMemoryChecked(Offset, Size)) { return; } - auto &Memory = Instance->getMemory(); - Data = Memory.data() + Offset; + uint8_t *MemoryBase = Instance->getMemoryBase(); + Data = MemoryBase + Offset; } // Build topic array - only include non-null topics @@ -840,8 +785,8 @@ const uint8_t *evmHandleCreateInternal(zen::runtime::EVMInstance *Instance, return ZeroAddress; } - auto &Memory = Instance->getMemory(); - InitCode = Memory.data() + Offset; + uint8_t *MemoryBase = Instance->getMemoryBase(); + InitCode = MemoryBase + Offset; } // Create message for CREATE/CREATE2 @@ -925,10 +870,6 @@ static uint64_t evmHandleCallInternal(zen::runtime::EVMInstance *Instance, if (Rev >= EVMC_BERLIN && Module->Host->access_account(TargetAddr) == EVMC_ACCESS_COLD) { Instance->chargeGas(zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); - } else if (Rev >= EVMC_TANGERINE_WHISTLE && Rev <= EVMC_ISTANBUL) { - if (CallKind == EVMC_DELEGATECALL) { - Instance->chargeGas(zen::evm::DELEGATECALL_EXTRA_GAS_TW_TO_ISTANBUL); - } } const bool TransfersValue = @@ -974,15 +915,25 @@ static uint64_t evmHandleCallInternal(zen::runtime::EVMInstance *Instance, // Only expand memory if we actually need to access it bool needArgsMemory = ArgsSize > 0; bool needRetMemory = RetSize > 0; - if (needArgsMemory || needRetMemory) { + if (needArgsMemory && needRetMemory) { if (!Instance->expandMemoryChecked(ArgsOffset, ArgsSize, RetOffset, RetSize)) { Instance->setReturnData({}); return 0; } + } else if (needArgsMemory) { + if (!Instance->expandMemoryChecked(ArgsOffset, ArgsSize)) { + Instance->setReturnData({}); + return 0; + } + } else if (needRetMemory) { + if (!Instance->expandMemoryChecked(RetOffset, RetSize)) { + Instance->setReturnData({}); + return 0; + } } - auto &Memory = Instance->getMemory(); + uint8_t *MemoryBase = Instance->getMemoryBase(); uint64_t CallGas = Gas; uint64_t GasLeft = Instance->getGas(); if (Rev >= EVMC_TANGERINE_WHISTLE) { @@ -997,6 +948,11 @@ static uint64_t evmHandleCallInternal(zen::runtime::EVMInstance *Instance, CallGas += zen::evm::CALL_GAS_STIPEND; } + if (CurrentMsg->depth >= zen::evm::MAXSTACK) { + Instance->setReturnData({}); + return 0; + } + // Create message for call evmc_message CallMsg{ .kind = CallKind, @@ -1009,7 +965,7 @@ static uint64_t evmHandleCallInternal(zen::runtime::EVMInstance *Instance, : CurrentMsg->recipient, .sender = (CallKind == EVMC_DELEGATECALL) ? CurrentMsg->sender : CurrentMsg->recipient, - .input_data = Memory.data() + ArgsOffset, + .input_data = MemoryBase + ArgsOffset, .input_size = ArgsSize, .value = (CallKind == EVMC_DELEGATECALL) ? CurrentMsg->value @@ -1045,11 +1001,12 @@ static uint64_t evmHandleCallInternal(zen::runtime::EVMInstance *Instance, Instance->addGasRefund(Result.gas_refund); } - // Copy return data to memory if output area is specified + // Copy return data to memory if output area is specified. + // Per EVM semantics, bytes beyond returned data length remain unchanged. if (RetSize > 0 && Result.output_size > 0) { size_t CopySize = std::min(static_cast(RetSize), Result.output_size); - std::memcpy(Memory.data() + RetOffset, Result.output_data, CopySize); + std::memcpy(MemoryBase + RetOffset, Result.output_data, CopySize); } // Store full return data for RETURNDATASIZE/RETURNDATACOPY @@ -1124,9 +1081,9 @@ void evmSetRevert(zen::runtime::EVMInstance *Instance, uint64_t Offset, if (!Instance->expandMemoryChecked(Offset, Size)) { return; } - auto &Memory = Instance->getMemory(); - ReturnData = std::vector(Memory.begin() + Offset, - Memory.begin() + Offset + Size); + uint8_t *MemoryBase = Instance->getMemoryBase(); + ReturnData = + std::vector(MemoryBase + Offset, MemoryBase + Offset + Size); } Instance->setReturnData(std::move(ReturnData)); const int64_t GasLeft = @@ -1157,17 +1114,17 @@ void evmSetCodeCopy(zen::runtime::EVMInstance *Instance, uint64_t DestOffset, const zen::common::Byte *Code = Module->Code; size_t CodeSize = Module->CodeSize; - auto &Memory = Instance->getMemory(); + uint8_t *MemoryBase = Instance->getMemoryBase(); if (Offset < CodeSize) { auto CopySize = std::min(Size, CodeSize - Offset); - std::memcpy(Memory.data() + DestOffset, Code + Offset, CopySize); + std::memcpy(MemoryBase + DestOffset, Code + Offset, CopySize); if (Size > CopySize) { - std::memset(Memory.data() + DestOffset + CopySize, 0, Size - CopySize); + std::memset(MemoryBase + DestOffset + CopySize, 0, Size - CopySize); } } else { if (Size > 0) { - std::memset(Memory.data() + DestOffset, 0, Size); + std::memset(MemoryBase + DestOffset, 0, Size); } } } @@ -1182,8 +1139,8 @@ const uint8_t *evmGetKeccak256(zen::runtime::EVMInstance *Instance, const uint64_t ExtraGas = static_cast(numWords(static_cast(Length))) * 6; Instance->chargeGas(ExtraGas); - auto &Memory = Instance->getMemory(); - InputData = Memory.data() + Offset; + uint8_t *MemoryBase = Instance->getMemoryBase(); + InputData = MemoryBase + Offset; } auto &Cache = Instance->getMessageCache(); diff --git a/src/compiler/evm_frontend/evm_imported.h b/src/compiler/evm_frontend/evm_imported.h index 0270209b3..01df8874e 100644 --- a/src/compiler/evm_frontend/evm_imported.h +++ b/src/compiler/evm_frontend/evm_imported.h @@ -26,12 +26,9 @@ using SizeWithBytes32Fn = uint64_t (*)(zen::runtime::EVMInstance *, const uint8_t *); using U256WithBytes32Fn = const intx::uint256 *(*)(zen::runtime::EVMInstance *, const uint8_t *); -using U256WithUInt64Fn = const intx::uint256 *(*)(zen::runtime::EVMInstance *, - uint64_t); -using VoidWithUInt64U256Fn = void (*)(zen::runtime::EVMInstance *, uint64_t, - const intx::uint256 &); using VoidWithUInt64UInt64Fn = void (*)(zen::runtime::EVMInstance *, uint64_t, uint64_t); +using VoidWithUInt64Fn = void (*)(zen::runtime::EVMInstance *, uint64_t); using VoidWithUInt64UInt64UInt64Fn = void (*)(zen::runtime::EVMInstance *, uint64_t, uint64_t, uint64_t); using VoidWithBytes32UInt64UInt64UInt64Fn = void (*)( @@ -108,19 +105,15 @@ struct RuntimeFunctions { U256Fn GetBaseFee; Bytes32WithUint64Fn GetBlobHash; U256Fn GetBlobBaseFee; - SizeFn GetMSize; - U256WithUInt64Fn GetMLoad; - VoidWithUInt64U256Fn SetMStore; - VoidWithUInt64U256Fn SetMStore8; U256WithU256Fn GetSLoad; VoidWithU256U256Fn SetSStore; SizeFn GetGas; U256WithU256Fn GetTLoad; VoidWithU256U256Fn SetTStore; - VoidWithUInt64UInt64UInt64Fn SetMCopy; VoidWithUInt64UInt64UInt64Fn SetCallDataCopy; VoidWithBytes32UInt64UInt64UInt64Fn SetExtCodeCopy; VoidWithUInt64UInt64UInt64Fn SetReturnDataCopy; + VoidWithUInt64Fn ExpandMemoryNoGas; SizeFn GetReturnDataSize; Log0Fn EmitLog0; Log1Fn EmitLog1; @@ -203,15 +196,6 @@ const intx::uint256 *evmGetBaseFee(zen::runtime::EVMInstance *Instance); const uint8_t *evmGetBlobHash(zen::runtime::EVMInstance *Instance, uint64_t Index); const intx::uint256 *evmGetBlobBaseFee(zen::runtime::EVMInstance *Instance); -uint64_t evmGetMSize(zen::runtime::EVMInstance *Instance); -const intx::uint256 *evmGetMLoad(zen::runtime::EVMInstance *Instance, - uint64_t Addr); -void evmSetMStore(zen::runtime::EVMInstance *Instance, uint64_t Addr, - const intx::uint256 &Value); -void evmSetMStore8(zen::runtime::EVMInstance *Instance, uint64_t Addr, - const intx::uint256 &Value); -void evmSetMCopy(zen::runtime::EVMInstance *Instance, uint64_t DestAddr, - uint64_t SrcAddr, uint64_t Length); void evmSetCallDataCopy(zen::runtime::EVMInstance *Instance, uint64_t DestOffset, uint64_t Offset, uint64_t Size); void evmSetExtCodeCopy(zen::runtime::EVMInstance *Instance, @@ -219,6 +203,8 @@ void evmSetExtCodeCopy(zen::runtime::EVMInstance *Instance, uint64_t Offset, uint64_t Size); void evmSetReturnDataCopy(zen::runtime::EVMInstance *Instance, uint64_t DestOffset, uint64_t Offset, uint64_t Size); +void evmExpandMemoryNoGas(zen::runtime::EVMInstance *Instance, + uint64_t RequiredSize); uint64_t evmGetReturnDataSize(zen::runtime::EVMInstance *Instance); void evmEmitLog0(zen::runtime::EVMInstance *Instance, uint64_t Offset, uint64_t Size); diff --git a/src/compiler/evm_frontend/evm_mir_compiler.cpp b/src/compiler/evm_frontend/evm_mir_compiler.cpp index 8f485fe16..89fde70a5 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.cpp +++ b/src/compiler/evm_frontend/evm_mir_compiler.cpp @@ -5,8 +5,10 @@ #include "action/evm_bytecode_visitor.h" #include "compiler/evm_frontend/evm_imported.h" #include "compiler/mir/module.h" +#include "evm/gas_storage_cost.h" #include "runtime/evm_instance.h" #include "utils/hash_utils.h" +#include #include #ifdef ZEN_ENABLE_EVM_GAS_REGISTER @@ -69,7 +71,7 @@ EVMFrontendContext::EVMFrontendContext(const EVMFrontendContext &OtherCtx) BytecodeSize(OtherCtx.BytecodeSize), GasMeteringEnabled(OtherCtx.GasMeteringEnabled), GasChunkEnd(OtherCtx.GasChunkEnd), GasChunkCost(OtherCtx.GasChunkCost), - GasChunkSize(OtherCtx.GasChunkSize) + GasChunkSize(OtherCtx.GasChunkSize), Revision(OtherCtx.Revision) #ifdef ZEN_ENABLE_EVM_GAS_REGISTER , GasRegisterEnabled(OtherCtx.GasRegisterEnabled) @@ -110,6 +112,24 @@ void EVMMirBuilder::loadEVMInstanceAttr() { createInstruction(true, &(Ctx.VoidType), StackTopAddr, StackTopVar->getVarIdx()); + // Cache memory base in a local for cheaper access + MemoryBaseVar = CurFunc->createVariable(&Ctx.I64Type); + MPointerType *VoidPtrType = createVoidPtrType(); + const int32_t MemoryBaseOffset = + zen::runtime::EVMInstance::getMemoryBaseOffset(); + MInstruction *MemPtr = getInstanceElement(VoidPtrType, MemoryBaseOffset); + MInstruction *MemBaseInt = createInstruction( + false, OP_ptrtoint, &Ctx.I64Type, MemPtr); + createInstruction(true, &(Ctx.VoidType), MemBaseInt, + MemoryBaseVar->getVarIdx()); + // Cache memory size in a local for MSIZE and memory growth checks + MemorySizeVar = CurFunc->createVariable(&Ctx.I64Type); + const int32_t MemorySizeOffset = + zen::runtime::EVMInstance::getMemorySizeOffset(); + MInstruction *MemSize = getInstanceElement(&Ctx.I64Type, MemorySizeOffset); + createInstruction(true, &(Ctx.VoidType), MemSize, + MemorySizeVar->getVarIdx()); + ExceptionReturnBB = CurFunc->createExceptionReturnBB(); } @@ -118,14 +138,23 @@ void EVMMirBuilder::initEVM(CompilerContext *Context) { MBasicBlock *EntryBB = createBasicBlock(); setInsertBlock(EntryBB); - InstructionMetrics = - evmc_get_instruction_metrics_table(zen::evm::DEFAULT_REVISION); + const auto *EvmCtx = static_cast(&Ctx); + const evmc_revision Rev = EvmCtx->getRevision(); + InstructionMetrics = evmc_get_instruction_metrics_table(Rev); + InstructionNames = evmc_get_instruction_names_table(Rev); + if (!InstructionMetrics) { + InstructionMetrics = + evmc_get_instruction_metrics_table(zen::evm::DEFAULT_REVISION); + } + if (!InstructionNames) { + InstructionNames = + evmc_get_instruction_names_table(zen::evm::DEFAULT_REVISION); + } createJumpTable(); ReturnBB = createBasicBlock(); loadEVMInstanceAttr(); - const auto *EvmCtx = static_cast(&Ctx); GasChunkEnd = EvmCtx->getGasChunkEnd(); GasChunkCost = EvmCtx->getGasChunkCost(); GasChunkSize = EvmCtx->getGasChunkSize(); @@ -269,6 +298,19 @@ void EVMMirBuilder::meterOpcode(evmc_opcode Opcode, uint64_t PC) { meterGas(static_cast(Metrics.gas_cost)); } +bool EVMMirBuilder::isOpcodeDefined(evmc_opcode Opcode) const { + const uint8_t Index = static_cast(Opcode); + if (InstructionNames && InstructionNames[Index] != nullptr) { + return true; + } + if (!InstructionMetrics) { + return true; + } + const auto &Metrics = InstructionMetrics[Index]; + return Metrics.gas_cost != 0 || Metrics.stack_height_required != 0 || + Metrics.stack_height_change != 0; +} + void EVMMirBuilder::meterGas(uint64_t GasCost) { if (!Ctx.isGasMeteringEnabled() || GasCost == 0) { return; @@ -1875,6 +1917,7 @@ void EVMMirBuilder::handleCodeCopy(Operand DestOffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif + reloadMemorySizeFromInstance(); } typename EVMMirBuilder::Operand @@ -1965,45 +2008,144 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleBlobBaseFee() { } typename EVMMirBuilder::Operand EVMMirBuilder::handleMSize() { - const auto &RuntimeFunctions = getRuntimeFunctionTable(); - return callRuntimeFor(RuntimeFunctions.GetMSize); + MInstruction *MemSize = getMemorySize(); + // Capture MSIZE at this opcode to prevent later memory growth reordering. + MemSize = protectUnsafeValue(MemSize, &Ctx.I64Type); + return convertSingleInstrToU256Operand(MemSize); } typename EVMMirBuilder::Operand EVMMirBuilder::handleMLoad(Operand AddrComponents) { - const auto &RuntimeFunctions = getRuntimeFunctionTable(); normalizeOperandU64(AddrComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - auto Result = callRuntimeFor( - RuntimeFunctions.GetMLoad, AddrComponents); + MType *I64Type = &Ctx.I64Type; + + U256Inst AddrParts = extractU256Operand(AddrComponents); + MInstruction *Offset = AddrParts[0]; + + MInstruction *SizeConst = createIntConstInstruction(I64Type, 32); + MInstruction *RequiredSize = createInstruction( + false, OP_add, I64Type, Offset, SizeConst); + MInstruction *Overflow = createInstruction( + false, CmpInstruction::Predicate::ICMP_ULT, I64Type, RequiredSize, + Offset); + expandMemoryIR(RequiredSize, Overflow); + + MInstruction *MemBase = getMemoryDataPointer(); + MInstruction *MemAddrInt = createInstruction( + false, OP_add, I64Type, MemBase, Offset); + MInstruction *MemPtr = createInstruction( + false, OP_inttoptr, createVoidPtrType(), MemAddrInt); + + Operand Bytes32Op(MemPtr, EVMType::BYTES32); + Operand Result = convertBytes32ToU256Operand(Bytes32Op); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif return Result; } + void EVMMirBuilder::handleMStore(Operand AddrComponents, Operand ValueComponents) { - const auto &RuntimeFunctions = getRuntimeFunctionTable(); normalizeOperandU64(AddrComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - callRuntimeFor( - RuntimeFunctions.SetMStore, AddrComponents, ValueComponents); + MType *I64Type = &Ctx.I64Type; + + U256Inst AddrParts = extractU256Operand(AddrComponents); + MInstruction *Offset = AddrParts[0]; + U256Inst ValueParts = extractU256Operand(ValueComponents); + + MInstruction *SizeConst = createIntConstInstruction(I64Type, 32); + MInstruction *RequiredSize = createInstruction( + false, OP_add, I64Type, Offset, SizeConst); + // Tie expansion ordering to the stored value to prevent reordering. + MInstruction *Zero = createIntConstInstruction(I64Type, 0); + MInstruction *ValueDep = createInstruction( + false, OP_or, I64Type, ValueParts[0], ValueParts[1]); + ValueDep = createInstruction(false, OP_or, I64Type, + ValueDep, ValueParts[2]); + ValueDep = createInstruction(false, OP_or, I64Type, + ValueDep, ValueParts[3]); + ValueDep = createInstruction(false, OP_and, I64Type, + ValueDep, Zero); + RequiredSize = createInstruction(false, OP_add, I64Type, + RequiredSize, ValueDep); + MInstruction *Overflow = createInstruction( + false, CmpInstruction::Predicate::ICMP_ULT, I64Type, RequiredSize, + Offset); + expandMemoryIR(RequiredSize, Overflow); + + MInstruction *MemBase = getMemoryDataPointer(); + MInstruction *BaseAddrInt = createInstruction( + false, OP_add, I64Type, MemBase, Offset); + + MPointerType *U64PtrType = MPointerType::create(Ctx, Ctx.I64Type); + + auto ByteSwap64 = [&](MInstruction *Value) -> MInstruction * { + return createInstruction(false, OP_bswap, I64Type, Value); + }; + + for (int Component = 0; Component < 4; ++Component) { + MInstruction *RawValue = ValueParts[3 - Component]; + MInstruction *Swapped = ByteSwap64(RawValue); + + MInstruction *OffsetValue = createIntConstInstruction( + I64Type, static_cast(Component * 8)); + MInstruction *Addr = createInstruction( + false, OP_add, I64Type, BaseAddrInt, OffsetValue); + MInstruction *Ptr = createInstruction( + false, OP_inttoptr, U64PtrType, Addr); + createInstruction(true, &Ctx.VoidType, Swapped, Ptr); + } #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif } + void EVMMirBuilder::handleMStore8(Operand AddrComponents, Operand ValueComponents) { - const auto &RuntimeFunctions = getRuntimeFunctionTable(); normalizeOperandU64(AddrComponents); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - callRuntimeFor( - RuntimeFunctions.SetMStore8, AddrComponents, ValueComponents); + MType *I64Type = &Ctx.I64Type; + + U256Inst AddrParts = extractU256Operand(AddrComponents); + MInstruction *Offset = AddrParts[0]; + U256Inst ValueParts = extractU256Operand(ValueComponents); + + MInstruction *SizeConst = createIntConstInstruction(I64Type, 1); + MInstruction *RequiredSize = createInstruction( + false, OP_add, I64Type, Offset, SizeConst); + // Tie expansion ordering to the stored value to prevent reordering. + MInstruction *Zero = createIntConstInstruction(I64Type, 0); + MInstruction *ValueDep = createInstruction( + false, OP_and, I64Type, ValueParts[0], Zero); + RequiredSize = createInstruction(false, OP_add, I64Type, + RequiredSize, ValueDep); + MInstruction *Overflow = createInstruction( + false, CmpInstruction::Predicate::ICMP_ULT, I64Type, RequiredSize, + Offset); + expandMemoryIR(RequiredSize, Overflow); + + MInstruction *MemBase = getMemoryDataPointer(); + MInstruction *AddrInt = createInstruction( + false, OP_add, I64Type, MemBase, Offset); + + MPointerType *I8PtrType = MPointerType::create(Ctx, Ctx.I8Type); + MInstruction *AddrPtr = createInstruction( + false, OP_inttoptr, I8PtrType, AddrInt); + + MInstruction *Low64 = ValueParts[0]; + MInstruction *Mask = createIntConstInstruction(I64Type, 0xFF); + MInstruction *Masked = + createInstruction(false, OP_and, I64Type, Low64, Mask); + MInstruction *ByteValue = createInstruction( + false, OP_trunc, &Ctx.I8Type, Masked); + createInstruction(true, &Ctx.VoidType, ByteValue, AddrPtr); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif @@ -2011,53 +2153,26 @@ void EVMMirBuilder::handleMStore8(Operand AddrComponents, void EVMMirBuilder::handleMCopy(Operand DestAddrComponents, Operand SrcAddrComponents, Operand LengthComponents) { - const auto &RuntimeFunctions = getRuntimeFunctionTable(); - - auto IsConstZero = [](const Operand &Op) -> bool { - if (!Op.isConstant()) { - return false; - } - const auto &Val = Op.getConstValue(); - return Val[0] == 0 && Val[1] == 0 && Val[2] == 0 && Val[3] == 0; - }; + MType *I64Type = &Ctx.I64Type; + MInstruction *Zero = createIntConstInstruction(I64Type, 0); - if (IsConstZero(LengthComponents)) { - return; - } + U256Inst LenParts = extractU256Operand(LengthComponents); + MInstruction *LenOr = createInstruction( + false, OP_or, I64Type, LenParts[0], LenParts[1]); + LenOr = createInstruction(false, OP_or, I64Type, LenOr, + LenParts[2]); + LenOr = createInstruction(false, OP_or, I64Type, LenOr, + LenParts[3]); + MInstruction *IsZero = createInstruction( + false, CmpInstruction::Predicate::ICMP_EQ, I64Type, LenOr, Zero); - MBasicBlock *SkipCopyBB = nullptr; - if (!LengthComponents.isConstant()) { - MType *I64Type = EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64); - MInstruction *Zero = createIntConstInstruction(I64Type, 0); - U256Inst Parts = extractU256Operand(LengthComponents); - MInstruction *IsZero0 = createInstruction( - false, CmpInstruction::Predicate::ICMP_EQ, &Ctx.I64Type, Parts[0], - Zero); - MInstruction *IsZero1 = createInstruction( - false, CmpInstruction::Predicate::ICMP_EQ, &Ctx.I64Type, Parts[1], - Zero); - MInstruction *IsZero2 = createInstruction( - false, CmpInstruction::Predicate::ICMP_EQ, &Ctx.I64Type, Parts[2], - Zero); - MInstruction *IsZero3 = createInstruction( - false, CmpInstruction::Predicate::ICMP_EQ, &Ctx.I64Type, Parts[3], - Zero); + MBasicBlock *CopyBB = createBasicBlock(); + MBasicBlock *DoneBB = createBasicBlock(); + createInstruction(true, Ctx, IsZero, DoneBB, CopyBB); + addSuccessor(DoneBB); + addSuccessor(CopyBB); - MInstruction *Cond01 = createInstruction( - false, OP_and, I64Type, IsZero0, IsZero1); - MInstruction *Cond23 = createInstruction( - false, OP_and, I64Type, IsZero2, IsZero3); - MInstruction *IsAllZero = createInstruction( - false, OP_and, I64Type, Cond01, Cond23); - - MBasicBlock *CopyBB = createBasicBlock(); - SkipCopyBB = createBasicBlock(); - createInstruction(true, Ctx, IsAllZero, SkipCopyBB, - CopyBB); - addSuccessor(SkipCopyBB); - addSuccessor(CopyBB); - setInsertBlock(CopyBB); - } + setInsertBlock(CopyBB); normalizeOperandU64(DestAddrComponents); normalizeOperandU64(SrcAddrComponents); @@ -2065,18 +2180,70 @@ void EVMMirBuilder::handleMCopy(Operand DestAddrComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER syncGasToMemory(); #endif - callRuntimeFor( - RuntimeFunctions.SetMCopy, DestAddrComponents, SrcAddrComponents, - LengthComponents); + + U256Inst DestParts = extractU256Operand(DestAddrComponents); + U256Inst SrcParts = extractU256Operand(SrcAddrComponents); + U256Inst LenPartsNorm = extractU256Operand(LengthComponents); + MInstruction *DestOffset = DestParts[0]; + MInstruction *SrcOffset = SrcParts[0]; + MInstruction *Len = LenPartsNorm[0]; + + // Charge word copy gas: words = (len + 31) / 32 + MInstruction *Const31 = createIntConstInstruction(I64Type, 31); + MInstruction *Shift5 = createIntConstInstruction(I64Type, 5); + MInstruction *LenPlus31 = createInstruction( + false, OP_add, I64Type, Len, Const31); + MInstruction *Words = createInstruction( + false, OP_ushr, I64Type, LenPlus31, Shift5); + MInstruction *WordCopyCost = + createIntConstInstruction(I64Type, zen::evm::WORD_COPY_COST); + MInstruction *CopyGas = createInstruction( + false, OP_mul, I64Type, Words, WordCopyCost); + chargeDynamicGasIR(CopyGas); + + // Expand memory for both source and destination ranges. + MInstruction *DestEnd = createInstruction( + false, OP_add, I64Type, DestOffset, Len); + MInstruction *SrcEnd = createInstruction( + false, OP_add, I64Type, SrcOffset, Len); + MInstruction *DestOverflow = createInstruction( + false, CmpInstruction::Predicate::ICMP_ULT, I64Type, DestEnd, DestOffset); + MInstruction *SrcOverflow = createInstruction( + false, CmpInstruction::Predicate::ICMP_ULT, I64Type, SrcEnd, SrcOffset); + MInstruction *Overflow = createInstruction( + false, OP_or, I64Type, DestOverflow, SrcOverflow); + + MInstruction *DestGreater = createInstruction( + false, CmpInstruction::Predicate::ICMP_UGT, I64Type, DestEnd, SrcEnd); + MInstruction *RequiredSize = createInstruction( + false, I64Type, DestGreater, DestEnd, SrcEnd); + expandMemoryIR(RequiredSize, Overflow); + + MInstruction *MemBase = getMemoryDataPointer(); + MInstruction *DestBase = createInstruction( + false, OP_add, I64Type, MemBase, DestOffset); + MInstruction *SrcBase = createInstruction( + false, OP_add, I64Type, MemBase, SrcOffset); + MPointerType *VoidPtrType = createVoidPtrType(); + MInstruction *DestPtr = createInstruction( + false, OP_inttoptr, VoidPtrType, DestBase); + MInstruction *SrcPtr = createInstruction( + false, OP_inttoptr, VoidPtrType, SrcBase); + MInstruction *MemmoveAddr = createIntConstInstruction( + I64Type, reinterpret_cast(std::memmove)); + CompileVector MemmoveArgs{ + {DestPtr, SrcPtr, Len}, + Ctx.MemPool, + }; + createInstruction(true, &Ctx.VoidType, MemmoveAddr, + MemmoveArgs); #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif + createInstruction(true, Ctx, DoneBB); + addSuccessor(DoneBB); - if (SkipCopyBB != nullptr) { - createInstruction(true, Ctx, SkipCopyBB); - addSuccessor(SkipCopyBB); - setInsertBlock(SkipCopyBB); - } + setInsertBlock(DoneBB); } template @@ -2127,6 +2294,7 @@ EVMMirBuilder::handleCreate(Operand ValueOp, Operand OffsetOp, Operand SizeOp) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif + reloadMemorySizeFromInstance(); return Result; } @@ -2146,6 +2314,7 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleCreate2(Operand ValueOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif + reloadMemorySizeFromInstance(); return Result; } @@ -2171,6 +2340,7 @@ EVMMirBuilder::handleCall(Operand GasOp, Operand ToAddrOp, Operand ValueOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif + reloadMemorySizeFromInstance(); return Result; } @@ -2196,6 +2366,7 @@ EVMMirBuilder::handleCallCode(Operand GasOp, Operand ToAddrOp, Operand ValueOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif + reloadMemorySizeFromInstance(); return Result; } @@ -2243,6 +2414,7 @@ EVMMirBuilder::handleDelegateCall(Operand GasOp, Operand ToAddrOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif + reloadMemorySizeFromInstance(); return Result; } @@ -2267,6 +2439,7 @@ EVMMirBuilder::handleStaticCall(Operand GasOp, Operand ToAddrOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif + reloadMemorySizeFromInstance(); return Result; } @@ -2389,6 +2562,7 @@ EVMMirBuilder::handleKeccak256(Operand OffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif + reloadMemorySizeFromInstance(); return Result; } @@ -2531,7 +2705,9 @@ EVMMirBuilder::convertU256InstrToU256Operand(MInstruction *U256Instr) { U256Inst Result = {}; MType *I64Type = EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64); MType *PtrType = U256Instr->getType(); - ZEN_ASSERT(PtrType->isPointer()); + if (!PtrType->isPointer()) { + return convertSingleInstrToU256Operand(U256Instr); + } Variable *PtrVar = storeInstructionInTemp(U256Instr, PtrType); const int32_t Offsets[] = {0, 8, 16, 24}; @@ -2544,6 +2720,8 @@ EVMMirBuilder::convertU256InstrToU256Operand(MInstruction *U256Instr) { if (BaseValue->getType()->isPointer()) { BaseAddr = createInstruction( false, OP_ptrtoint, &Ctx.I64Type, BaseValue); + } else if (!BaseValue->getType()->isI64()) { + BaseAddr = zeroExtendToI64(BaseValue); } MInstruction *OffsetValue = createIntConstInstruction(I64Type, Offsets[I]); @@ -2579,49 +2757,8 @@ EVMMirBuilder::convertBytes32ToU256Operand(const Operand &Bytes32Op) { false, OP_ptrtoint, &Ctx.I64Type, Bytes32Ptr); } - // Precompute constants used for 64-bit byte swap - MInstruction *Shift8 = createIntConstInstruction(I64Type, 8); - MInstruction *Shift16 = createIntConstInstruction(I64Type, 16); - MInstruction *Shift32 = createIntConstInstruction(I64Type, 32); - MInstruction *MaskFF00FF00FF00FF00 = - createIntConstInstruction(I64Type, 0xFF00FF00FF00FF00ULL); - MInstruction *Mask00FF00FF00FF00FF = - createIntConstInstruction(I64Type, 0x00FF00FF00FF00FFULL); - MInstruction *MaskFFFF0000FFFF0000 = - createIntConstInstruction(I64Type, 0xFFFF0000FFFF0000ULL); - MInstruction *Mask0000FFFF0000FFFF = - createIntConstInstruction(I64Type, 0x0000FFFF0000FFFFULL); - auto ByteSwap64 = [&](MInstruction *Value) -> MInstruction * { - // Perform 64-bit byte swap using standard mask/shift cascades - MInstruction *LoShift = createInstruction( - false, OP_shl, I64Type, Value, Shift8); - MInstruction *HiShift = createInstruction( - false, OP_ushr, I64Type, Value, Shift8); - MInstruction *LowMasked = createInstruction( - false, OP_and, I64Type, LoShift, MaskFF00FF00FF00FF00); - MInstruction *HighMasked = createInstruction( - false, OP_and, I64Type, HiShift, Mask00FF00FF00FF00FF); - MInstruction *Stage1 = createInstruction( - false, OP_or, I64Type, LowMasked, HighMasked); - - MInstruction *LowShift16 = createInstruction( - false, OP_shl, I64Type, Stage1, Shift16); - MInstruction *HighShift16 = createInstruction( - false, OP_ushr, I64Type, Stage1, Shift16); - MInstruction *LowMasked16 = createInstruction( - false, OP_and, I64Type, LowShift16, MaskFFFF0000FFFF0000); - MInstruction *HighMasked16 = createInstruction( - false, OP_and, I64Type, HighShift16, Mask0000FFFF0000FFFF); - MInstruction *Stage2 = createInstruction( - false, OP_or, I64Type, LowMasked16, HighMasked16); - - MInstruction *LowShift32 = createInstruction( - false, OP_shl, I64Type, Stage2, Shift32); - MInstruction *HighShift32 = createInstruction( - false, OP_ushr, I64Type, Stage2, Shift32); - return createInstruction(false, OP_or, I64Type, - LowShift32, HighShift32); + return createInstruction(false, OP_bswap, I64Type, Value); }; for (int Component = 0; Component < 4; ++Component) { @@ -3043,6 +3180,7 @@ void EVMMirBuilder::handleCallDataCopy(Operand DestOffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif + reloadMemorySizeFromInstance(); } void EVMMirBuilder::handleExtCodeCopy(Operand AddressComponents, @@ -3062,6 +3200,7 @@ void EVMMirBuilder::handleExtCodeCopy(Operand AddressComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif + reloadMemorySizeFromInstance(); } void EVMMirBuilder::handleReturnDataCopy(Operand DestOffsetComponents, @@ -3080,10 +3219,251 @@ void EVMMirBuilder::handleReturnDataCopy(Operand DestOffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif + reloadMemorySizeFromInstance(); } typename EVMMirBuilder::Operand EVMMirBuilder::handleReturnDataSize() { const auto &RuntimeFunctions = getRuntimeFunctionTable(); return callRuntimeFor(RuntimeFunctions.GetReturnDataSize); } + +// ==================== Memory Operation Helper Methods ==================== + +MInstruction *EVMMirBuilder::getMemoryDataPointer() { + MType *I64Type = &Ctx.I64Type; + MPointerType *VoidPtrType = createVoidPtrType(); + const int32_t MemoryBaseOffset = + zen::runtime::EVMInstance::getMemoryBaseOffset(); + MInstruction *MemPtr = getInstanceElement(VoidPtrType, MemoryBaseOffset); + MInstruction *MemBaseInt = createInstruction( + false, OP_ptrtoint, I64Type, MemPtr); + if (MemoryBaseVar) { + createInstruction(true, &(Ctx.VoidType), MemBaseInt, + MemoryBaseVar->getVarIdx()); + } + return MemBaseInt; +} + +MInstruction *EVMMirBuilder::getMemorySize() { + if (MemorySizeVar) { + return loadVariable(MemorySizeVar); + } + MType *I64Type = &Ctx.I64Type; + const int32_t MemorySizeOffset = + zen::runtime::EVMInstance::getMemorySizeOffset(); + return getInstanceElement(I64Type, MemorySizeOffset); +} + +void EVMMirBuilder::reloadMemorySizeFromInstance() { + if (!MemorySizeVar) { + return; + } + MType *I64Type = &Ctx.I64Type; + const int32_t MemorySizeOffset = + zen::runtime::EVMInstance::getMemorySizeOffset(); + MInstruction *MemSize = getInstanceElement(I64Type, MemorySizeOffset); + createInstruction(true, &(Ctx.VoidType), MemSize, + MemorySizeVar->getVarIdx()); +} + +MInstruction * +EVMMirBuilder::calculateMemoryGasCostIR(MInstruction *SizeInBytes) { + // EVM memory gas cost formula: + // cost = (sizeInWords^2 / 512) + (3 * sizeInWords) + // where sizeInWords = (sizeInBytes + 31) / 32 + + MType *I64Type = &Ctx.I64Type; + + // Convert bytes to words: (SizeInBytes + 31) / 32 + MInstruction *Const31 = createIntConstInstruction(I64Type, 31); + MInstruction *Shift5 = createIntConstInstruction(I64Type, 5); + MInstruction *SizePlus31 = createInstruction( + false, OP_add, I64Type, SizeInBytes, Const31); + MInstruction *SizeInWords = createInstruction( + false, OP_ushr, I64Type, SizePlus31, Shift5); + + // Calculate sizeInWords^2 + MInstruction *SizeSquared = createInstruction( + false, OP_mul, I64Type, SizeInWords, SizeInWords); + + // Calculate sizeInWords^2 / 512 + MInstruction *Const512 = createIntConstInstruction(I64Type, 512); + MInstruction *QuadraticCost = createInstruction( + false, OP_udiv, I64Type, SizeSquared, Const512); + + // Calculate 3 * sizeInWords + MInstruction *Const3 = createIntConstInstruction(I64Type, 3); + MInstruction *LinearCost = createInstruction( + false, OP_mul, I64Type, Const3, SizeInWords); + + // Total cost = QuadraticCost + LinearCost + MInstruction *TotalCost = createInstruction( + false, OP_add, I64Type, QuadraticCost, LinearCost); + + return TotalCost; +} + +void EVMMirBuilder::chargeDynamicGasIR(MInstruction *GasCost) { + MType *I64Type = &Ctx.I64Type; + MInstruction *GasOffsetValue = createIntConstInstruction( + I64Type, zen::runtime::EVMInstance::getGasFieldOffset()); + MInstruction *GasAddrInt = createInstruction( + false, OP_add, I64Type, InstanceAddr, GasOffsetValue); + + MPointerType *I64PtrType = MPointerType::create(Ctx, Ctx.I64Type); + MInstruction *GasPtr = createInstruction( + false, OP_inttoptr, I64PtrType, GasAddrInt); + + // Load current gas + MInstruction *GasValue = + createInstruction(false, I64Type, GasPtr); + + // Check if we have enough gas + MInstruction *IsOutOfGas = createInstruction( + false, CmpInstruction::Predicate::ICMP_ULT, I64Type, GasValue, GasCost); + + // Branch on out of gas condition + MBasicBlock *OutOfGasBB = + getOrCreateExceptionSetBB(ErrorCode::GasLimitExceeded); + MBasicBlock *ContinueBB = createBasicBlock(); + + createInstruction(true, Ctx, IsOutOfGas, OutOfGasBB, + ContinueBB); + addSuccessor(OutOfGasBB); + addSuccessor(ContinueBB); + + // Continue: subtract gas and store back + setInsertBlock(ContinueBB); + MInstruction *NewGas = createInstruction( + false, OP_sub, I64Type, GasValue, GasCost); + createInstruction(true, &Ctx.VoidType, NewGas, GasPtr); + + // Also update the message gas field + const int32_t CurrentMessageOffset = + zen::runtime::EVMInstance::getCurrentMessagePointerOffset(); + MPointerType *VoidPtrType = createVoidPtrType(); + MInstruction *MsgPtr = getInstanceElement(VoidPtrType, CurrentMessageOffset); + MInstruction *MsgPtrInt = createInstruction( + false, OP_ptrtoint, I64Type, MsgPtr); + MInstruction *Zero = createIntConstInstruction(I64Type, 0); + MInstruction *HasMsg = createInstruction( + false, CmpInstruction::Predicate::ICMP_NE, I64Type, MsgPtrInt, Zero); + MBasicBlock *MsgStoreBB = createBasicBlock(); + MBasicBlock *MsgSkipBB = createBasicBlock(); + createInstruction(true, Ctx, HasMsg, MsgStoreBB, MsgSkipBB); + addSuccessor(MsgStoreBB); + addSuccessor(MsgSkipBB); + + setInsertBlock(MsgStoreBB); + const int32_t MsgGasOffset = zen::runtime::EVMInstance::getMessageGasOffset(); + MInstruction *MsgGasOffsetVal = + createIntConstInstruction(I64Type, MsgGasOffset); + MInstruction *MsgGasAddrInt = createInstruction( + false, OP_add, I64Type, MsgPtrInt, MsgGasOffsetVal); + MInstruction *MsgGasPtr = createInstruction( + false, OP_inttoptr, I64PtrType, MsgGasAddrInt); + + // Store new gas to message (as int64_t) + createInstruction(true, &Ctx.VoidType, NewGas, MsgGasPtr); + createInstruction(true, Ctx, MsgSkipBB); + addSuccessor(MsgSkipBB); + setInsertBlock(MsgSkipBB); +} + +void EVMMirBuilder::chargeMemoryExpansionGasIR(MInstruction *OldSize, + MInstruction *NewSize) { + // Calculate expansion cost: cost(new) - cost(old) + MInstruction *NewCost = calculateMemoryGasCostIR(NewSize); + MInstruction *OldCost = calculateMemoryGasCostIR(OldSize); + + MInstruction *ExpansionCost = createInstruction( + false, OP_sub, &Ctx.I64Type, NewCost, OldCost); + chargeDynamicGasIR(ExpansionCost); +} + +void EVMMirBuilder::expandMemoryIR(MInstruction *RequiredSize, + MInstruction *Overflow) { + // This function expands memory if needed + // For now, we still call the runtime function for actual resize + // but we inline the gas calculation + + MType *I64Type = &Ctx.I64Type; + + MInstruction *MaxSize = + createIntConstInstruction(I64Type, zen::evm::MAX_REQUIRED_MEMORY_SIZE); + MInstruction *TooLarge = createInstruction( + false, CmpInstruction::Predicate::ICMP_UGT, I64Type, RequiredSize, + MaxSize); + MInstruction *InvalidSize = TooLarge; + if (Overflow != nullptr) { + InvalidSize = createInstruction(false, OP_or, I64Type, + Overflow, TooLarge); + } + + MBasicBlock *InvalidBB = + getOrCreateExceptionSetBB(ErrorCode::GasLimitExceeded); + MBasicBlock *ValidBB = createBasicBlock(); + createInstruction(true, Ctx, InvalidSize, InvalidBB, + ValidBB); + addSuccessor(InvalidBB); + addSuccessor(ValidBB); + + setInsertBlock(ValidBB); + + // Load current memory size + MInstruction *CurrentSize = getMemorySize(); + + // Check if expansion is needed + MInstruction *NeedExpand = createInstruction( + false, CmpInstruction::Predicate::ICMP_UGT, I64Type, RequiredSize, + CurrentSize); + + MBasicBlock *ExpandBB = createBasicBlock(); + MBasicBlock *ContinueBB = createBasicBlock(); + + createInstruction(true, Ctx, NeedExpand, ExpandBB, + ContinueBB); + addSuccessor(ExpandBB); + addSuccessor(ContinueBB); + + // ExpandBB: Calculate aligned size and charge gas + setInsertBlock(ExpandBB); + + // Align to 32 bytes: newSize = (requiredSize + 31) / 32 * 32 + MInstruction *Const31 = createIntConstInstruction(I64Type, 31); + MInstruction *Shift5 = createIntConstInstruction(I64Type, 5); + MInstruction *AlignedWords = createInstruction( + false, OP_add, I64Type, RequiredSize, Const31); + AlignedWords = createInstruction(false, OP_ushr, I64Type, + AlignedWords, Shift5); + MInstruction *AlignedSize = createInstruction( + false, OP_shl, I64Type, AlignedWords, Shift5); + + // Charge memory expansion gas + chargeMemoryExpansionGasIR(CurrentSize, AlignedSize); + + const auto &RuntimeFunctions = getRuntimeFunctionTable(); + callRuntimeFor(RuntimeFunctions.ExpandMemoryNoGas, + Operand(AlignedSize, EVMType::UINT64)); + if (MemorySizeVar) { + createInstruction(true, &(Ctx.VoidType), AlignedSize, + MemorySizeVar->getVarIdx()); + } + if (MemoryBaseVar) { + MPointerType *VoidPtrType = createVoidPtrType(); + const int32_t MemoryBaseOffset = + zen::runtime::EVMInstance::getMemoryBaseOffset(); + MInstruction *MemPtr = getInstanceElement(VoidPtrType, MemoryBaseOffset); + MInstruction *MemBaseInt = createInstruction( + false, OP_ptrtoint, I64Type, MemPtr); + createInstruction(true, &(Ctx.VoidType), MemBaseInt, + MemoryBaseVar->getVarIdx()); + } + + createInstruction(true, Ctx, ContinueBB); + addSuccessor(ContinueBB); + + setInsertBlock(ContinueBB); +} + } // namespace COMPILER diff --git a/src/compiler/evm_frontend/evm_mir_compiler.h b/src/compiler/evm_frontend/evm_mir_compiler.h index 2d859c040..82873873a 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.h +++ b/src/compiler/evm_frontend/evm_mir_compiler.h @@ -9,6 +9,7 @@ #include "compiler/mir/function.h" #include "compiler/mir/instructions.h" #include "compiler/mir/pointer.h" +#include "evm/evm.h" #include "evmc/instructions.h" #include "intx/intx.hpp" #include @@ -77,6 +78,9 @@ class EVMFrontendContext final : public CompileContext { return GasChunkEnd && GasChunkCost && GasChunkSize > 0; } + void setRevision(evmc_revision Rev) { Revision = Rev; } + evmc_revision getRevision() const { return Revision; } + #ifdef ZEN_ENABLE_EVM_GAS_REGISTER void setGasRegisterEnabled(bool Enabled) { GasRegisterEnabled = Enabled; } bool isGasRegisterEnabled() const { return GasRegisterEnabled; } @@ -89,6 +93,7 @@ class EVMFrontendContext final : public CompileContext { const uint32_t *GasChunkEnd = nullptr; const uint64_t *GasChunkCost = nullptr; size_t GasChunkSize = 0; + evmc_revision Revision = zen::evm::DEFAULT_REVISION; #ifdef ZEN_ENABLE_EVM_GAS_REGISTER bool GasRegisterEnabled = false; #endif @@ -181,6 +186,7 @@ class EVMMirBuilder final { void finalizeEVMBase(); void meterOpcode(evmc_opcode Opcode, uint64_t PC); + bool isOpcodeDefined(evmc_opcode Opcode) const; void meterGas(uint64_t GasCost); // Complete jump implementation with jump table @@ -608,6 +614,7 @@ class EVMMirBuilder final { // exit when has exception MBasicBlock *ExceptionReturnBB = nullptr; const evmc_instruction_metrics *InstructionMetrics = nullptr; + const char *const *InstructionNames = nullptr; // Jump table for dynamic jumps bool HasIndirectJump = false; @@ -622,6 +629,18 @@ class EVMMirBuilder final { MBasicBlock *StackCheckBB = nullptr; Variable *StackTopVar = nullptr; Variable *StackSizeVar = nullptr; + Variable *MemoryBaseVar = nullptr; + Variable *MemorySizeVar = nullptr; + + // Helper methods for memory operations + MInstruction *getMemoryDataPointer(); + MInstruction *getMemorySize(); + void reloadMemorySizeFromInstance(); + void expandMemoryIR(MInstruction *RequiredSize, MInstruction *Overflow); + void chargeDynamicGasIR(MInstruction *GasCost); + void chargeMemoryExpansionGasIR(MInstruction *CurrentSize, + MInstruction *NewSize); + MInstruction *calculateMemoryGasCostIR(MInstruction *SizeInBytes); // Chunk gas metering const uint32_t *GasChunkEnd = nullptr; diff --git a/src/compiler/mir/opcodes.def b/src/compiler/mir/opcodes.def index 3b2b08513..cfd5f3c84 100644 --- a/src/compiler/mir/opcodes.def +++ b/src/compiler/mir/opcodes.def @@ -6,6 +6,7 @@ OPCODE(clz) // OP_UNARY_EXPR_START, OP_START OPCODE(ctz) OPCODE(not) OPCODE(popcnt) +OPCODE(bswap) OPCODE(fpabs) OPCODE(fpneg) OPCODE(fpsqrt) diff --git a/src/compiler/mir/pass/verifier.cpp b/src/compiler/mir/pass/verifier.cpp index 2e9c8233b..f9b417a3e 100644 --- a/src/compiler/mir/pass/verifier.cpp +++ b/src/compiler/mir/pass/verifier.cpp @@ -13,6 +13,7 @@ void MVerifier::visitUnaryInstruction(UnaryInstruction &I) { case OP_ctz: case OP_not: case OP_popcnt: + case OP_bswap: CHECK(OperandType->isInteger(), "The type of " + getOpcodeString(Opc) + " operand must be integer"); break; diff --git a/src/evm/evm_cache.cpp b/src/evm/evm_cache.cpp index 8539a1f44..4c5fca62f 100644 --- a/src/evm/evm_cache.cpp +++ b/src/evm/evm_cache.cpp @@ -959,7 +959,7 @@ static bool buildGasChunksSPP(const zen::common::Byte *Code, size_t CodeSize, } // namespace void buildBytecodeCache(EVMBytecodeCache &Cache, const common::Byte *Code, - size_t CodeSize) { + size_t CodeSize, evmc_revision Rev) { Cache.JumpDestMap.assign(CodeSize, 0); Cache.PushValueMap.resize(CodeSize); Cache.GasChunkEnd.assign(CodeSize, 0); @@ -967,8 +967,10 @@ void buildBytecodeCache(EVMBytecodeCache &Cache, const common::Byte *Code, buildJumpDestMapAndPushCache(Code, CodeSize, Cache.JumpDestMap, Cache.PushValueMap); - static const auto *MetricsTable = - evmc_get_instruction_metrics_table(DEFAULT_REVISION); + const auto *MetricsTable = evmc_get_instruction_metrics_table(Rev); + if (!MetricsTable) { + MetricsTable = evmc_get_instruction_metrics_table(DEFAULT_REVISION); + } buildGasChunksSPP(Code, CodeSize, MetricsTable, Cache.JumpDestMap, Cache.PushValueMap, Cache.GasChunkEnd, Cache.GasChunkCost); diff --git a/src/evm/evm_cache.h b/src/evm/evm_cache.h index d24d3a7b9..cd7dd69ec 100644 --- a/src/evm/evm_cache.h +++ b/src/evm/evm_cache.h @@ -7,6 +7,8 @@ #include "intx/intx.hpp" #include "platform/platform.h" +#include + #include #include #include @@ -21,7 +23,7 @@ struct EVMBytecodeCache { }; void buildBytecodeCache(EVMBytecodeCache &Cache, const common::Byte *Code, - size_t CodeSize); + size_t CodeSize, evmc_revision Rev); } // namespace zen::evm diff --git a/src/evm/opcode_handlers.cpp b/src/evm/opcode_handlers.cpp index 82d9683f6..058ef6403 100644 --- a/src/evm/opcode_handlers.cpp +++ b/src/evm/opcode_handlers.cpp @@ -1360,12 +1360,19 @@ void CallHandler::doExecute() { return; } - if (!expandMemoryAndChargeGas(Frame, - uint256ToUint64(InputOffset + InputSize)) or - !expandMemoryAndChargeGas(Frame, - uint256ToUint64(OutputOffset + OutputSize))) { - Context->setStatus(EVMC_OUT_OF_GAS); - return; + if (InputSize != 0) { + if (!expandMemoryAndChargeGas(Frame, + uint256ToUint64(InputOffset + InputSize))) { + Context->setStatus(EVMC_OUT_OF_GAS); + return; + } + } + if (OutputSize != 0) { + if (!expandMemoryAndChargeGas(Frame, + uint256ToUint64(OutputOffset + OutputSize))) { + Context->setStatus(EVMC_OUT_OF_GAS); + return; + } } evmc_message NewMsg{ diff --git a/src/runtime/evm_instance.cpp b/src/runtime/evm_instance.cpp index ad307d3a5..e0500577a 100644 --- a/src/runtime/evm_instance.cpp +++ b/src/runtime/evm_instance.cpp @@ -9,6 +9,7 @@ #include "evm/evm.h" #include "utils/backtrace.h" #include +#include #include namespace zen::runtime { @@ -24,6 +25,16 @@ bool calcRequiredMemorySize(uint64_t Offset, uint64_t Size, RequiredSize = Offset + Size; return true; } + +void initMemoryFrame(std::unique_ptr &Memory, uint8_t *&Base, + uint64_t &Size) { + if (!Memory) { + // Allocate raw buffer; expansion will zero the used range. + Memory.reset(new uint8_t[zen::evm::MAX_REQUIRED_MEMORY_SIZE]); + } + Base = Memory.get(); + Size = 0; +} } // namespace EVMInstanceUniquePtr EVMInstance::newEVMInstance(Isolation &Iso, @@ -54,11 +65,10 @@ void EVMInstance::setGas(uint64_t NewGas) { Gas = NewGas; } void EVMInstance::pushMessage(evmc_message *Msg) { if (MessageStack.empty()) { MemoryStack.clear(); - Memory.clear(); } else { - MemoryStack.push_back(std::move(Memory)); - Memory.clear(); + MemoryStack.push_back({std::move(Memory), MemorySize}); } + initMemoryFrame(Memory, MemoryBase, MemorySize); MessageStack.push_back(Msg); CurrentMessage = Msg; Gas = Msg ? Msg->gas : 0; @@ -70,10 +80,13 @@ void EVMInstance::popMessage() { } CurrentMessage = MessageStack.empty() ? nullptr : MessageStack.back(); if (!MemoryStack.empty()) { - Memory = std::move(MemoryStack.back()); + Memory = std::move(MemoryStack.back().Data); + MemorySize = MemoryStack.back().Size; MemoryStack.pop_back(); + MemoryBase = Memory.get(); } else { - Memory.clear(); + MemoryBase = Memory.get(); + MemorySize = 0; } Gas = CurrentMessage ? CurrentMessage->gas : 0; } @@ -137,14 +150,38 @@ void EVMInstance::triggerInstanceExceptionOnJIT(EVMInstance *Inst, void EVMInstance::expandMemory(uint64_t RequiredSize) { auto NewSize = (RequiredSize + 31) / 32 * 32; - uint64_t ExpansionCost = calculateMemoryExpansionCost(Memory.size(), NewSize); + uint64_t ExpansionCost = calculateMemoryExpansionCost(MemorySize, NewSize); chargeGas(ExpansionCost); - if (NewSize > Memory.size()) { - Memory.resize(NewSize, 0); + if (NewSize > MemorySize) { + if (!MemoryBase) { + initMemoryFrame(Memory, MemoryBase, MemorySize); + } + if (NewSize > MemorySize) { + std::memset(MemoryBase + MemorySize, 0, + static_cast(NewSize - MemorySize)); + MemorySize = NewSize; + } + } +} + +void EVMInstance::expandMemoryNoGas(uint64_t RequiredSize) { + auto NewSize = (RequiredSize + 31) / 32 * 32; + if (NewSize > MemorySize) { + if (!MemoryBase) { + initMemoryFrame(Memory, MemoryBase, MemorySize); + } + if (NewSize > MemorySize) { + std::memset(MemoryBase + MemorySize, 0, + static_cast(NewSize - MemorySize)); + MemorySize = NewSize; + } } } bool EVMInstance::expandMemoryChecked(uint64_t Offset, uint64_t Size) { + if (Size == 0) { + return true; + } uint64_t RequiredSize = 0; if (!calcRequiredMemorySize(Offset, Size, RequiredSize)) { chargeGas(getGas() + 1); @@ -160,6 +197,17 @@ bool EVMInstance::expandMemoryChecked(uint64_t Offset, uint64_t Size) { bool EVMInstance::expandMemoryChecked(uint64_t OffsetA, uint64_t SizeA, uint64_t OffsetB, uint64_t SizeB) { + const bool NeedA = SizeA > 0; + const bool NeedB = SizeB > 0; + if (!NeedA && !NeedB) { + return true; + } + if (NeedA && !NeedB) { + return expandMemoryChecked(OffsetA, SizeA); + } + if (!NeedA && NeedB) { + return expandMemoryChecked(OffsetB, SizeB); + } uint64_t RequiredSizeA = 0; uint64_t RequiredSizeB = 0; if (!calcRequiredMemorySize(OffsetA, SizeA, RequiredSizeA) || diff --git a/src/runtime/evm_instance.h b/src/runtime/evm_instance.h index 908dd10e0..8d60cee64 100644 --- a/src/runtime/evm_instance.h +++ b/src/runtime/evm_instance.h @@ -12,6 +12,7 @@ #include "runtime/instance.h" #include #include +#include // Forward declaration for evmc_message struct evmc_message; @@ -51,6 +52,7 @@ class EVMInstance final : public RuntimeObject { uint64_t NewSize); void consumeMemoryExpansionGas(uint64_t RequiredSize); void 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); @@ -62,8 +64,9 @@ class EVMInstance final : public RuntimeObject { void setRevision(evmc_revision NewRev) { Rev = NewRev; } // ==================== Memory Methods ==================== - size_t getMemorySize() const { return Memory.size(); } - std::vector &getMemory() { return Memory; } + uint64_t getMemorySize() const { return MemorySize; } + uint8_t *getMemoryBase() const { return MemoryBase; } + uint8_t *getMemory() { return Memory.get(); } // ==================== Evmc Message Stack Methods ==================== // Note: These methods manage the call stack for JIT host interface functions @@ -201,6 +204,20 @@ class EVMInstance final : public RuntimeObject { return static_cast(offsetof(EVMInstance, EVMStackSize)); } + static constexpr int32_t getMemoryBaseOffset() { + static_assert(offsetof(EVMInstance, MemoryBase) <= + std::numeric_limits::max(), + "EVMInstance offsets should fit in 32-bit signed range"); + return static_cast(offsetof(EVMInstance, MemoryBase)); + } + + static constexpr int32_t getMemorySizeOffset() { + static_assert(offsetof(EVMInstance, MemorySize) <= + std::numeric_limits::max(), + "EVMInstance offsets should fit in 32-bit signed range"); + return static_cast(offsetof(EVMInstance, MemorySize)); + } + // Capacity for EVMStack: 1024 * 256 / 8 = 32768 static const size_t EVMStackCapacity = 32768; @@ -267,8 +284,14 @@ class EVMInstance final : public RuntimeObject { const EVMModule *Mod = nullptr; uint64_t GasRefund = 0; // memory - std::vector Memory; - std::vector> MemoryStack; + uint8_t *MemoryBase = nullptr; + uint64_t MemorySize = 0; + std::unique_ptr Memory; + struct MemoryFrame { + std::unique_ptr Data; + uint64_t Size = 0; + }; + std::vector MemoryStack; std::vector ReturnData; evmc::Result ExeResult{EVMC_SUCCESS, 0, 0}; diff --git a/src/runtime/evm_module.cpp b/src/runtime/evm_module.cpp index b1a3fb27e..673054110 100644 --- a/src/runtime/evm_module.cpp +++ b/src/runtime/evm_module.cpp @@ -38,12 +38,14 @@ EVMModule::~EVMModule() { } EVMModuleUniquePtr EVMModule::newEVMModule(Runtime &RT, - CodeHolderUniquePtr CodeHolder) { + CodeHolderUniquePtr CodeHolder, + evmc_revision Rev) { void *ObjBuf = RT.allocate(sizeof(EVMModule)); ZEN_ASSERT(ObjBuf); auto *RawMod = new (ObjBuf) EVMModule(&RT); EVMModuleUniquePtr Mod(RawMod); + Mod->setRevision(Rev); const uint8_t *Data = static_cast(CodeHolder->getData()); size_t CodeSize = CodeHolder->getSize(); @@ -79,7 +81,7 @@ const evm::EVMBytecodeCache &EVMModule::getBytecodeCache() const { } void EVMModule::initBytecodeCache() const { - evm::buildBytecodeCache(BytecodeCache, Code, CodeSize); + evm::buildBytecodeCache(BytecodeCache, Code, CodeSize, Revision); } } // namespace zen::runtime diff --git a/src/runtime/evm_module.h b/src/runtime/evm_module.h index 5700a4699..cdfa896d5 100644 --- a/src/runtime/evm_module.h +++ b/src/runtime/evm_module.h @@ -3,6 +3,7 @@ #ifndef ZEN_RUNTIME_EVM_MODULE_H #define ZEN_RUNTIME_EVM_MODULE_H +#include "evm/evm.h" #include "evm/evm_cache.h" #include "evmc/evmc.hpp" #include "runtime/module.h" @@ -23,8 +24,8 @@ class EVMModule final : public BaseModule { public: using Byte = zen::common::Byte; - static EVMModuleUniquePtr newEVMModule(Runtime &RT, - CodeHolderUniquePtr CodeHolder); + static EVMModuleUniquePtr + newEVMModule(Runtime &RT, CodeHolderUniquePtr CodeHolder, evmc_revision Rev); virtual ~EVMModule(); @@ -33,6 +34,8 @@ class EVMModule final : public BaseModule { evmc::Host *Host; const evm::EVMBytecodeCache &getBytecodeCache() const; + evmc_revision getRevision() const { return Revision; } + void setRevision(evmc_revision Rev) { Revision = Rev; } #ifdef ZEN_ENABLE_JIT common::CodeMemPool &getJITCodeMemPool() { return JITCodeMemPool; } @@ -58,6 +61,7 @@ class EVMModule final : public BaseModule { void initBytecodeCache() const; mutable bool BytecodeCacheInitialized = false; mutable evm::EVMBytecodeCache BytecodeCache; + evmc_revision Revision = zen::evm::DEFAULT_REVISION; #ifdef ZEN_ENABLE_JIT common::CodeMemPool JITCodeMemPool; diff --git a/src/runtime/runtime.cpp b/src/runtime/runtime.cpp index 884a671e0..e175836fe 100644 --- a/src/runtime/runtime.cpp +++ b/src/runtime/runtime.cpp @@ -16,6 +16,7 @@ #include "common/type.h" #include "entrypoint/entrypoint.h" #ifdef ZEN_ENABLE_EVM +#include "evm/evm.h" #include "evm/interpreter.h" #include "runtime/evm_instance.h" #include "utils/evm.h" @@ -260,7 +261,7 @@ Runtime::loadEVMModule(const std::string &Filename) noexcept { // Create CodeHolder with decoded bytes auto Code = CodeHolder::newRawDataCodeHolder(*this, DecodedBytes->data(), DecodedBytes->size()); - return loadEVMModule(Name, std::move(Code)); + return loadEVMModule(Name, std::move(Code), zen::evm::DEFAULT_REVISION); } catch (const Error &Err) { Stats.clearAllTimers(); freeSymbol(Name); @@ -273,8 +274,8 @@ Runtime::loadEVMModule(const std::string &Filename) noexcept { } MayBe Runtime::loadEVMModule(const std::string &ModName, - const void *Data, - size_t Size) noexcept { + const void *Data, size_t Size, + evmc_revision Rev) noexcept { if (ModName.empty() || (Size != 0 && Data == nullptr)) { return getError(ErrorCode::InvalidRawData); } @@ -287,7 +288,7 @@ MayBe Runtime::loadEVMModule(const std::string &ModName, try { auto Code = CodeHolder::newRawDataCodeHolder(*this, Data, Size); - return loadEVMModule(Name, std::move(Code)); + return loadEVMModule(Name, std::move(Code), Rev); } catch (const Error &Err) { Stats.clearAllTimers(); freeSymbol(Name); @@ -297,12 +298,13 @@ MayBe Runtime::loadEVMModule(const std::string &ModName, // Before executing this function, it is necessary to ensure that name is unique EVMModule *Runtime::loadEVMModule(EVMSymbol Name, - CodeHolderUniquePtr CodeHolder) { + CodeHolderUniquePtr CodeHolder, + evmc_revision Rev) { ZEN_ASSERT(Name); ZEN_ASSERT(CodeHolder); EVMModuleUniquePtr Mod = - EVMModule::newEVMModule(*this, std::move(CodeHolder)); + EVMModule::newEVMModule(*this, std::move(CodeHolder), Rev); // All errors in Module::newModule are thrown as exceptions, so the return // value must be valid when the following line is executed ZEN_ASSERT(Mod); diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index f2efebee7..d4f9cd06d 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -10,6 +10,7 @@ #include "common/mem_pool.h" #include "common/type.h" #ifdef ZEN_ENABLE_EVM +#include "evm/evm.h" #include "evmc/evmc.hpp" #endif // ZEN_ENABLE_EVM #include "runtime/config.h" @@ -154,20 +155,24 @@ class Runtime final { loadModule(const std::string &ModName, const void *Data, size_t DataSize, const std::string &EntryHint = "") noexcept; +#ifdef ZEN_ENABLE_EVM /// \warning not thread-safe common::MayBe loadEVMModule(const std::string &Filename) noexcept; /// \warning not thread-safe - common::MayBe loadEVMModule(const std::string &ModName, - const void *Data, - size_t DataSize) noexcept; + common::MayBe + loadEVMModule(const std::string &ModName, const void *Data, size_t DataSize, + evmc_revision Rev = zen::evm::DEFAULT_REVISION) noexcept; +#endif // ZEN_ENABLE_EVM /// \warning not thread-safe bool unloadModule(const Module *Mod) noexcept; +#ifdef ZEN_ENABLE_EVM /// \warning not thread-safe bool unloadEVMModule(const EVMModule *Mod) noexcept; +#endif // ZEN_ENABLE_EVM Isolation *createManagedIsolation() noexcept; @@ -339,7 +344,10 @@ class Runtime final { Module *loadModule(WASMSymbol ModName, CodeHolderUniquePtr CodeHolder, const std::string &EntryHint = ""); - EVMModule *loadEVMModule(EVMSymbol ModName, CodeHolderUniquePtr CodeHolder); +#ifdef ZEN_ENABLE_EVM + EVMModule *loadEVMModule(EVMSymbol ModName, CodeHolderUniquePtr CodeHolder, + evmc_revision Rev); +#endif // ZEN_ENABLE_EVM void callWasmFunctionInInterpMode(Instance &Inst, uint32_t FuncIdx, const std::vector &Args, diff --git a/src/tests/evm_interp_tests.cpp b/src/tests/evm_interp_tests.cpp index 3a52d1044..85784fc5b 100644 --- a/src/tests/evm_interp_tests.cpp +++ b/src/tests/evm_interp_tests.cpp @@ -8,7 +8,6 @@ #include "evm/evm.h" #include "evm/interpreter.h" #include "evm_test_host.hpp" -#include "evmc/mocked_host.hpp" #include "runtime/evm_module.h" #include "utils/evm.h" #include "zetaengine.h" @@ -163,11 +162,14 @@ TEST_P(EVMSampleTest, ExecuteSample) { RuntimeConfig Config; Config.Mode = common::RunMode::InterpMode; - std::unique_ptr Host = std::make_unique(); + auto MockedHost = std::make_unique(); - auto RT = Runtime::newEVMRuntime(Config, Host.get()); + auto RT = Runtime::newEVMRuntime(Config, MockedHost.get()); ASSERT_TRUE(RT != nullptr) << "Failed to create runtime"; + // Set runtime for ZenMockedEVMHost to enable precompile calls + MockedHost->setRuntime(RT.get()); + auto ModRet = RT->loadEVMModule(FilePath); ASSERT_TRUE(ModRet) << "Failed to load module: " << FilePath; diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt index 38931adff..6b2575d0f 100644 --- a/src/vm/CMakeLists.txt +++ b/src/vm/CMakeLists.txt @@ -50,7 +50,7 @@ if(ZEN_ENABLE_EVM) # Link with EVMC library if available if(TARGET evmc::evmc) - target_link_libraries(dtvmapi PRIVATE evmc::evmc) + target_link_libraries(dtvmapi PRIVATE evmc::evmc evmc::instructions) endif() # Configure static linking for cross-environment compatibility This ensures diff --git a/src/vm/dt_evmc_vm.cpp b/src/vm/dt_evmc_vm.cpp index 8bc396668..4af896cc7 100644 --- a/src/vm/dt_evmc_vm.cpp +++ b/src/vm/dt_evmc_vm.cpp @@ -77,7 +77,7 @@ struct DTVM : evmc_vm { .EnableEvmGasMetering = true}; std::unique_ptr RT; std::unique_ptr ExecHost; - std::unordered_map LoadedMods; + std::unordered_map LoadedMods; Isolation *Iso = nullptr; }; @@ -154,7 +154,10 @@ evmc_result execute(evmc_vm *EVMInstance, const evmc_host_interface *Host, } uint32_t CheckSum = crc32(Code, CodeSize); - auto ModRet = VM->RT->loadEVMModule(std::to_string(CheckSum), 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()); @@ -163,7 +166,7 @@ evmc_result execute(evmc_vm *EVMInstance, const evmc_host_interface *Host, } EVMModule *Mod = *ModRet; - VM->LoadedMods[CheckSum] = Mod; + VM->LoadedMods[ModKey] = Mod; if (!VM->Iso) { VM->Iso = VM->RT->createManagedIsolation(); } diff --git a/tests/evm_asm/call_output_tail_preserve.easm b/tests/evm_asm/call_output_tail_preserve.easm new file mode 100644 index 000000000..30142b9dd --- /dev/null +++ b/tests/evm_asm/call_output_tail_preserve.easm @@ -0,0 +1,70 @@ +// Test CALL output tail preservation: when return data < retSize, +// bytes beyond return data length should remain unchanged. + +// Fill return area (0x80) with 0xAA bytes +PUSH32 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +PUSH1 0x80 +MSTORE + +// Setup MODEXP input at memory[0x00]: +// Format: base_len(32) | exp_len(32) | mod_len(32) | base | exp | mod +// We use: base=2, exp=3, mod=7 => 2^3 mod 7 = 1 +// base_len = 1, exp_len = 1, mod_len = 1 + +// memory[0x00:0x20] = 1 (base length) +PUSH1 0x01 +PUSH1 0x00 +MSTORE + +// memory[0x20:0x40] = 1 (exp length) +PUSH1 0x01 +PUSH1 0x20 +MSTORE + +// memory[0x40:0x60] = 1 (mod length) +PUSH1 0x01 +PUSH1 0x40 +MSTORE + +// memory[0x60] = 2 (base) +PUSH1 0x02 +PUSH1 0x60 +MSTORE8 + +// memory[0x61] = 3 (exp) +PUSH1 0x03 +PUSH1 0x61 +MSTORE8 + +// memory[0x62] = 7 (mod) +PUSH1 0x07 +PUSH1 0x62 +MSTORE8 + +// CALL MODEXP precompile (0x05) +// argsOffset=0, argsSize=0x63 (99 bytes), retOffset=0x80, retSize=0x10 (16 bytes) +// MODEXP returns 1 byte (mod_len), so [0x81, 0x90) should stay 0xAA +// Stack for CALL: gas, addr, value, argsOffset, argsSize, retOffset, retSize +PUSH1 0x10 +PUSH1 0x80 +PUSH1 0x63 +PUSH1 0x00 +PUSH1 0x00 +PUSH1 0x05 +PUSH4 0x100000 +CALL +POP + +// Verify: MLOAD(0x80) should be 0x01 followed by 0xAA... (31 bytes) +// Expected: 0x01AAAAAA...AA (first byte is result, rest unchanged) +PUSH1 0x80 +MLOAD +PUSH32 0x01aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +EQ + +// Return 0x01 on success, 0x00 on failure +PUSH1 0x00 +MSTORE +PUSH1 0x20 +PUSH1 0x00 +RETURN diff --git a/tests/evm_asm/call_output_tail_preserve.expected b/tests/evm_asm/call_output_tail_preserve.expected new file mode 100644 index 000000000..61ccc551d --- /dev/null +++ b/tests/evm_asm/call_output_tail_preserve.expected @@ -0,0 +1,8 @@ +status: success +error_code: 0 +stack: [] +memory: '000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001020307000000000000000000000000000000000000000000000000000000000001aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +storage: {} +transient_storage: {} +return: '0000000000000000000000000000000000000000000000000000000000000001' +events: []