diff --git a/src/compiler/evm_frontend/evm_mir_compiler.cpp b/src/compiler/evm_frontend/evm_mir_compiler.cpp index f12c392b..a346a958 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.cpp +++ b/src/compiler/evm_frontend/evm_mir_compiler.cpp @@ -4773,7 +4773,7 @@ void EVMMirBuilder::handleCodeCopy(Operand DestOffsetComponents, } #endif if (!UsePreparedMemory) { - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); } } @@ -5579,7 +5579,7 @@ void EVMMirBuilder::handleLogWithTopics(Operand OffsetOp, Operand SizeOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); } typename EVMMirBuilder::Operand @@ -5596,7 +5596,7 @@ EVMMirBuilder::handleCreate(Operand ValueOp, Operand OffsetOp, Operand SizeOp) { #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); return Result; } @@ -5616,7 +5616,7 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleCreate2(Operand ValueOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); return Result; } @@ -5698,7 +5698,7 @@ EVMMirBuilder::handleCall(Operand GasOp, Operand ToAddrOp, Operand ValueOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); return Result; } @@ -5730,7 +5730,7 @@ EVMMirBuilder::handleCallCode(Operand GasOp, Operand ToAddrOp, Operand ValueOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); return Result; } @@ -5798,7 +5798,7 @@ EVMMirBuilder::handleDelegateCall(Operand GasOp, Operand ToAddrOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); return Result; } @@ -5828,7 +5828,7 @@ EVMMirBuilder::handleStaticCall(Operand GasOp, Operand ToAddrOp, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); return Result; } @@ -6011,7 +6011,7 @@ EVMMirBuilder::handleKeccak256(Operand OffsetComponents, reloadGasFromMemory(); #endif if (!UsePreparedMemory) { - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); } return Result; } @@ -6043,7 +6043,7 @@ EVMMirBuilder::handleKeccak256TwoWord(Operand OffsetComponents, Operand Word0, reloadGasFromMemory(); #endif if (!UsePreparedMemory) { - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); } return Result; } @@ -6076,7 +6076,7 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleKeccak256CallDataConstSlot( reloadGasFromMemory(); #endif if (!UsePreparedMemory) { - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); } return Result; } @@ -6108,7 +6108,7 @@ EVMMirBuilder::handleKeccak256CallerConstSlot(Operand OffsetComponents, reloadGasFromMemory(); #endif if (!UsePreparedMemory) { - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); } return Result; } @@ -7410,7 +7410,7 @@ void EVMMirBuilder::handleCallDataCopy(Operand DestOffsetComponents, } #endif if (!UsePreparedMemory) { - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); } } @@ -7448,7 +7448,7 @@ void EVMMirBuilder::handleExtCodeCopy(Operand AddressComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); } void EVMMirBuilder::handleReturnDataCopy(Operand DestOffsetComponents, @@ -7490,7 +7490,7 @@ void EVMMirBuilder::handleReturnDataCopy(Operand DestOffsetComponents, #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif - reloadMemorySizeFromInstance(); + reloadMemoryCachesFromInstance(); } typename EVMMirBuilder::Operand EVMMirBuilder::handleReturnDataSize() { @@ -9524,22 +9524,42 @@ MInstruction *EVMMirBuilder::getMemorySize() { return getInstanceElement(I64Type, MemorySizeOffset); } -void EVMMirBuilder::reloadMemorySizeFromInstance() { +void EVMMirBuilder::reloadMemoryBaseFromInstance() { + if (!MemoryBaseVar) { + return; + } + 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()); +} + +void EVMMirBuilder::reloadMemoryCachesFromInstance() { #ifdef ZEN_ENABLE_MULTIPASS_JIT_LOGGING ++MemStats.ReloadMemorySizeCount; if (CurBlockMemStats.Active) { CurBlockMemStats.ReloadMemSizeCount++; } #endif // ZEN_ENABLE_MULTIPASS_JIT_LOGGING - if (!MemorySizeVar) { - return; + if (MemorySizeVar) { + MType *I64Type = &Ctx.I64Type; + const int32_t MemorySizeOffset = + zen::runtime::EVMInstance::getMemorySizeOffset(); + MInstruction *MemSize = getInstanceElement(I64Type, MemorySizeOffset); + createInstruction(true, &(Ctx.VoidType), MemSize, + MemorySizeVar->getVarIdx()); } - MType *I64Type = &Ctx.I64Type; - const int32_t MemorySizeOffset = - zen::runtime::EVMInstance::getMemorySizeOffset(); - MInstruction *MemSize = getInstanceElement(I64Type, MemorySizeOffset); - createInstruction(true, &(Ctx.VoidType), MemSize, - MemorySizeVar->getVarIdx()); + // The base must be reloaded with the size, never on its own schedule. A + // runtime helper that grows memory performs the frame's lazy first + // allocation, which moves MemoryBase from null to the new buffer. Refreshing + // only the size leaves a cached null base that later memory ops then trust, + // because a large enough cached size makes them skip expandMemoryIR - the + // only other place the base is refreshed. + reloadMemoryBaseFromInstance(); } MInstruction * @@ -9783,16 +9803,7 @@ void EVMMirBuilder::expandMemoryIR(MInstruction *RequiredSize, 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()); - } + reloadMemoryBaseFromInstance(); createInstruction(true, Ctx, ContinueBB); addSuccessor(ContinueBB); diff --git a/src/compiler/evm_frontend/evm_mir_compiler.h b/src/compiler/evm_frontend/evm_mir_compiler.h index 02450d30..655bfe8e 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.h +++ b/src/compiler/evm_frontend/evm_mir_compiler.h @@ -1889,7 +1889,12 @@ class EVMMirBuilder final { MInstruction *getConstBlockDirectMemoryBasePtr(); MInstruction *getLargeStaticWorkspaceDirectMemoryBasePtr(); MInstruction *getMemorySize(); - void reloadMemorySizeFromInstance(); + // Refresh the cached EVM memory base from the instance. Callers must keep + // this paired with the cached size: the two are one snapshot of the frame's + // memory, and refreshing either alone lets generated code combine a fresh + // size with a stale base. + void reloadMemoryBaseFromInstance(); + void reloadMemoryCachesFromInstance(); void expandMemoryIR(MInstruction *RequiredSize, MInstruction *Overflow); void chargeWordCopyGasIR(MInstruction *Size); void chargeDynamicGasIR(MInstruction *GasCost); diff --git a/src/tests/evm_differential_tests.cpp b/src/tests/evm_differential_tests.cpp index 39be402f..86613343 100644 --- a/src/tests/evm_differential_tests.cpp +++ b/src/tests/evm_differential_tests.cpp @@ -284,6 +284,51 @@ TEST(EVMPreparedCopyFallbackDifferential, EXPECT_EQ(Output, "60045F5F"); } +TEST(EVMMemoryBaseCacheDifferential, + HelperGrownMemoryIsAddressableFromALaterBlock) { + // The JIT caches the EVM memory base and size in function-entry locals. A + // frame starts with a null base, because EVM memory is allocated lazily on + // the frame's first growth, and the expansion branch is the only place the + // base cache is refreshed. + // + // Here the first growth happens inside the CALLDATACOPY runtime helper - a + // dynamic copy length keeps it on the generic, memory-growing helper rather + // than a prepared one - so the helper performs the lazy allocation and moves + // the instance's base off null. The two constant-offset stores in the next + // block share one block precheck, whose expansion is already satisfied by + // the reloaded size, so they address memory through the cached base without + // taking the expansion branch. If that cache was not refreshed alongside the + // size it still holds the entry-time null and the stores dereference it. + const std::vector Bytecode = { + 0x36, // PC0: CALLDATASIZE, dynamic copy length + 0x5f, // PC1: PUSH0 calldata offset + 0x5f, // PC2: PUSH0 destination offset + 0x37, // PC3: CALLDATACOPY, grows memory inside the helper + 0x60, 0x08, // PC4: PUSH1 successor + 0x56, // PC6: JUMP + 0x5b, // PC7: unreachable padding + 0x5b, // PC8: JUMPDEST + 0x60, 0x01, // PC9: PUSH1 1 + 0x5f, // PC11: PUSH0 store offset + 0x52, // PC12: MSTORE through the cached base + 0x60, 0x02, // PC13: PUSH1 2 + 0x60, 0x20, // PC15: PUSH1 store offset + 0x52, // PC17: MSTORE, second op sharing the block precheck + 0x60, 0x40, // PC18: PUSH1 return length + 0x5f, // PC20: PUSH0 return offset + 0xf3, // PC21: RETURN + }; + // 64 bytes of calldata, so the helper grows memory to exactly the 64 bytes + // the two stores need and the block precheck finds nothing left to expand. + const std::vector CallData(64, 0xab); + + const auto Output = expectInterpMatchesMultipassWithGas( + "memory_base_cache_after_helper_growth", Bytecode, CallData); + EXPECT_EQ(Output, + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002"); +} + TEST(EVMKeccakMemoryProofDifferential, CrossBlockProofReusePreservesHashAndGas) { const std::vector Bytecode = {