From e16be82eb044c2dc431e737993e8be3d6f788dc8 Mon Sep 17 00:00:00 2001 From: LNTUyhy <1261137940@qq.com> Date: Sun, 28 Jun 2026 13:32:19 +0800 Subject: [PATCH] perf(evm): elide statically empty copy codegen Return early from JIT lowering for compile-time zero-length MCOPY and lower compile-time zero-size EXTCODECOPY to an account-access-only runtime helper. Avoid emitting dynamic zero-length copy branches, memory expansion and copy-gas IR, runtime copy calls, and memory-size reloads for statically empty copy operations while preserving EIP-2929 account access charging for EXTCODECOPY. --- src/compiler/evm_frontend/evm_imported.cpp | 16 ++++++++++++++++ src/compiler/evm_frontend/evm_imported.h | 3 +++ src/compiler/evm_frontend/evm_mir_compiler.cpp | 17 +++++++++++++++++ tests/evm_asm/extcodecopy_zero_size.easm | 12 ++++++++++++ tests/evm_asm/extcodecopy_zero_size.evm.hex | 1 + tests/evm_asm/extcodecopy_zero_size.expected | 8 ++++++++ tests/evm_asm/mcopy_zero_size.easm | 11 +++++++++++ tests/evm_asm/mcopy_zero_size.evm.hex | 1 + tests/evm_asm/mcopy_zero_size.expected | 8 ++++++++ 9 files changed, 77 insertions(+) create mode 100644 tests/evm_asm/extcodecopy_zero_size.easm create mode 100644 tests/evm_asm/extcodecopy_zero_size.evm.hex create mode 100644 tests/evm_asm/extcodecopy_zero_size.expected create mode 100644 tests/evm_asm/mcopy_zero_size.easm create mode 100644 tests/evm_asm/mcopy_zero_size.evm.hex create mode 100644 tests/evm_asm/mcopy_zero_size.expected diff --git a/src/compiler/evm_frontend/evm_imported.cpp b/src/compiler/evm_frontend/evm_imported.cpp index 7ba2a9f0..895a3ea6 100644 --- a/src/compiler/evm_frontend/evm_imported.cpp +++ b/src/compiler/evm_frontend/evm_imported.cpp @@ -216,6 +216,7 @@ const RuntimeFunctions &getRuntimeFunctionTable() { .GetTLoad = &evmGetTLoad, .SetTStore = &evmSetTStore, .SetCallDataCopy = &evmSetCallDataCopy, + .TouchExtCodeCopyAccount = &evmTouchExtCodeCopyAccount, .SetExtCodeCopy = &evmSetExtCodeCopy, .SetReturnDataCopy = &evmSetReturnDataCopy, .ExpandMemoryNoGas = &evmExpandMemoryNoGas, @@ -674,6 +675,21 @@ void evmSetCallDataCopy(zen::runtime::EVMInstance *Instance, } } +void evmTouchExtCodeCopyAccount(zen::runtime::EVMInstance *Instance, + const uint8_t *Address) { + const zen::runtime::EVMModule *Module = Instance->getModule(); + ZEN_ASSERT(Module && Module->Host); + evmc::address Addr = loadAddressFromLE(Address); + + evmc_revision Rev = Instance->getRevision(); + if (Rev >= EVMC_BERLIN && + Module->Host->access_account(Addr) == EVMC_ACCESS_COLD) { + if (!Instance->chargeGas(zen::evm::ADDITIONAL_COLD_ACCOUNT_ACCESS_COST)) { + return; + } + } +} + void evmSetExtCodeCopy(zen::runtime::EVMInstance *Instance, const uint8_t *Address, uint64_t DestOffset, uint64_t Offset, uint64_t Size) { diff --git a/src/compiler/evm_frontend/evm_imported.h b/src/compiler/evm_frontend/evm_imported.h index be297904..717166d5 100644 --- a/src/compiler/evm_frontend/evm_imported.h +++ b/src/compiler/evm_frontend/evm_imported.h @@ -127,6 +127,7 @@ struct RuntimeFunctions { U256WithU256Fn GetTLoad; VoidWithU256U256Fn SetTStore; VoidWithUInt64UInt64UInt64Fn SetCallDataCopy; + VoidWithBytes32Fn TouchExtCodeCopyAccount; VoidWithBytes32UInt64UInt64UInt64Fn SetExtCodeCopy; UInt64WithUInt64UInt64UInt64Fn SetReturnDataCopy; VoidWithUInt64Fn ExpandMemoryNoGas; @@ -218,6 +219,8 @@ const uint8_t *evmGetBlobHash(zen::runtime::EVMInstance *Instance, const intx::uint256 *evmGetBlobBaseFee(zen::runtime::EVMInstance *Instance); void evmSetCallDataCopy(zen::runtime::EVMInstance *Instance, uint64_t DestOffset, uint64_t Offset, uint64_t Size); +void evmTouchExtCodeCopyAccount(zen::runtime::EVMInstance *Instance, + const uint8_t *Address); void evmSetExtCodeCopy(zen::runtime::EVMInstance *Instance, const uint8_t *Address, uint64_t DestOffset, 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 660b8b87..a1c44d17 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.cpp +++ b/src/compiler/evm_frontend/evm_mir_compiler.cpp @@ -5035,6 +5035,11 @@ void EVMMirBuilder::handleMStore8(Operand AddrComponents, void EVMMirBuilder::handleMCopy(Operand DestAddrComponents, Operand SrcAddrComponents, Operand LengthComponents) { + if (LengthComponents.isConstU64() && + LengthComponents.getConstValue()[0] == 0) { + return; + } + MType *I64Type = &Ctx.I64Type; MInstruction *Zero = createIntConstInstruction(I64Type, 0); @@ -6737,6 +6742,18 @@ void EVMMirBuilder::handleExtCodeCopy(Operand AddressComponents, Operand OffsetComponents, Operand SizeComponents) { const auto &RuntimeFunctions = getRuntimeFunctionTable(); + if (SizeComponents.isConstU64() && SizeComponents.getConstValue()[0] == 0) { +#ifdef ZEN_ENABLE_EVM_GAS_REGISTER + syncGasToMemory(); +#endif + callRuntimeForWithErrorCheck( + RuntimeFunctions.TouchExtCodeCopyAccount, AddressComponents); +#ifdef ZEN_ENABLE_EVM_GAS_REGISTER + reloadGasFromMemory(); +#endif + return; + } + // Use max uint64_t value if the offset/size is not 64-bit, because the // extcodecopy will fill zeros when offset is beyond code size or handle large // size properly. diff --git a/tests/evm_asm/extcodecopy_zero_size.easm b/tests/evm_asm/extcodecopy_zero_size.easm new file mode 100644 index 00000000..5d3f3a4d --- /dev/null +++ b/tests/evm_asm/extcodecopy_zero_size.easm @@ -0,0 +1,12 @@ +// EXTCODECOPY with zero size keeps existing memory and skips code copy work +PUSH1 0x2C +PUSH1 0x00 +MSTORE +PUSH1 0x00 +PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +PUSH1 0x00 +EXTCODECOPY +PUSH1 0x20 +PUSH1 0x00 +RETURN diff --git a/tests/evm_asm/extcodecopy_zero_size.evm.hex b/tests/evm_asm/extcodecopy_zero_size.evm.hex new file mode 100644 index 00000000..7d1821da --- /dev/null +++ b/tests/evm_asm/extcodecopy_zero_size.evm.hex @@ -0,0 +1 @@ +602C60005260007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF60003C60206000F3 diff --git a/tests/evm_asm/extcodecopy_zero_size.expected b/tests/evm_asm/extcodecopy_zero_size.expected new file mode 100644 index 00000000..4769e7f1 --- /dev/null +++ b/tests/evm_asm/extcodecopy_zero_size.expected @@ -0,0 +1,8 @@ +status: success +error_code: 0 +stack: [] +memory: '000000000000000000000000000000000000000000000000000000000000002C' +storage: {} +transient_storage: {} +return: '000000000000000000000000000000000000000000000000000000000000002C' +events: [] diff --git a/tests/evm_asm/mcopy_zero_size.easm b/tests/evm_asm/mcopy_zero_size.easm new file mode 100644 index 00000000..3a8cce70 --- /dev/null +++ b/tests/evm_asm/mcopy_zero_size.easm @@ -0,0 +1,11 @@ +// MCOPY with zero size keeps existing memory and skips source/destination access +PUSH1 0x2D +PUSH1 0x00 +MSTORE +PUSH1 0x00 +PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +MCOPY +PUSH1 0x20 +PUSH1 0x00 +RETURN diff --git a/tests/evm_asm/mcopy_zero_size.evm.hex b/tests/evm_asm/mcopy_zero_size.evm.hex new file mode 100644 index 00000000..525e0e1c --- /dev/null +++ b/tests/evm_asm/mcopy_zero_size.evm.hex @@ -0,0 +1 @@ +602D60005260007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E60206000F3 diff --git a/tests/evm_asm/mcopy_zero_size.expected b/tests/evm_asm/mcopy_zero_size.expected new file mode 100644 index 00000000..fda71026 --- /dev/null +++ b/tests/evm_asm/mcopy_zero_size.expected @@ -0,0 +1,8 @@ +status: success +error_code: 0 +stack: [] +memory: '000000000000000000000000000000000000000000000000000000000000002D' +storage: {} +transient_storage: {} +return: '000000000000000000000000000000000000000000000000000000000000002D' +events: []