Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 45 additions & 34 deletions src/compiler/evm_frontend/evm_mir_compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4773,7 +4773,7 @@ void EVMMirBuilder::handleCodeCopy(Operand DestOffsetComponents,
}
#endif
if (!UsePreparedMemory) {
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
}
}

Expand Down Expand Up @@ -5579,7 +5579,7 @@ void EVMMirBuilder::handleLogWithTopics(Operand OffsetOp, Operand SizeOp,
#ifdef ZEN_ENABLE_EVM_GAS_REGISTER
reloadGasFromMemory();
#endif
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
}

typename EVMMirBuilder::Operand
Expand All @@ -5596,7 +5596,7 @@ EVMMirBuilder::handleCreate(Operand ValueOp, Operand OffsetOp, Operand SizeOp) {
#ifdef ZEN_ENABLE_EVM_GAS_REGISTER
reloadGasFromMemory();
#endif
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
return Result;
}

Expand All @@ -5616,7 +5616,7 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleCreate2(Operand ValueOp,
#ifdef ZEN_ENABLE_EVM_GAS_REGISTER
reloadGasFromMemory();
#endif
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
return Result;
}

Expand Down Expand Up @@ -5698,7 +5698,7 @@ EVMMirBuilder::handleCall(Operand GasOp, Operand ToAddrOp, Operand ValueOp,
#ifdef ZEN_ENABLE_EVM_GAS_REGISTER
reloadGasFromMemory();
#endif
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
return Result;
}

Expand Down Expand Up @@ -5730,7 +5730,7 @@ EVMMirBuilder::handleCallCode(Operand GasOp, Operand ToAddrOp, Operand ValueOp,
#ifdef ZEN_ENABLE_EVM_GAS_REGISTER
reloadGasFromMemory();
#endif
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
return Result;
}

Expand Down Expand Up @@ -5798,7 +5798,7 @@ EVMMirBuilder::handleDelegateCall(Operand GasOp, Operand ToAddrOp,
#ifdef ZEN_ENABLE_EVM_GAS_REGISTER
reloadGasFromMemory();
#endif
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
return Result;
}

Expand Down Expand Up @@ -5828,7 +5828,7 @@ EVMMirBuilder::handleStaticCall(Operand GasOp, Operand ToAddrOp,
#ifdef ZEN_ENABLE_EVM_GAS_REGISTER
reloadGasFromMemory();
#endif
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
return Result;
}

Expand Down Expand Up @@ -6011,7 +6011,7 @@ EVMMirBuilder::handleKeccak256(Operand OffsetComponents,
reloadGasFromMemory();
#endif
if (!UsePreparedMemory) {
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
}
return Result;
}
Expand Down Expand Up @@ -6043,7 +6043,7 @@ EVMMirBuilder::handleKeccak256TwoWord(Operand OffsetComponents, Operand Word0,
reloadGasFromMemory();
#endif
if (!UsePreparedMemory) {
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
}
return Result;
}
Expand Down Expand Up @@ -6076,7 +6076,7 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleKeccak256CallDataConstSlot(
reloadGasFromMemory();
#endif
if (!UsePreparedMemory) {
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
}
return Result;
}
Expand Down Expand Up @@ -6108,7 +6108,7 @@ EVMMirBuilder::handleKeccak256CallerConstSlot(Operand OffsetComponents,
reloadGasFromMemory();
#endif
if (!UsePreparedMemory) {
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
}
return Result;
}
Expand Down Expand Up @@ -7410,7 +7410,7 @@ void EVMMirBuilder::handleCallDataCopy(Operand DestOffsetComponents,
}
#endif
if (!UsePreparedMemory) {
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
}
}

Expand Down Expand Up @@ -7448,7 +7448,7 @@ void EVMMirBuilder::handleExtCodeCopy(Operand AddressComponents,
#ifdef ZEN_ENABLE_EVM_GAS_REGISTER
reloadGasFromMemory();
#endif
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
}

void EVMMirBuilder::handleReturnDataCopy(Operand DestOffsetComponents,
Expand Down Expand Up @@ -7490,7 +7490,7 @@ void EVMMirBuilder::handleReturnDataCopy(Operand DestOffsetComponents,
#ifdef ZEN_ENABLE_EVM_GAS_REGISTER
reloadGasFromMemory();
#endif
reloadMemorySizeFromInstance();
reloadMemoryCachesFromInstance();
}

typename EVMMirBuilder::Operand EVMMirBuilder::handleReturnDataSize() {
Expand Down Expand Up @@ -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<ConversionInstruction>(
false, OP_ptrtoint, &Ctx.I64Type, MemPtr);
createInstruction<DassignInstruction>(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<DassignInstruction>(true, &(Ctx.VoidType), MemSize,
MemorySizeVar->getVarIdx());
}
MType *I64Type = &Ctx.I64Type;
const int32_t MemorySizeOffset =
zen::runtime::EVMInstance::getMemorySizeOffset();
MInstruction *MemSize = getInstanceElement(I64Type, MemorySizeOffset);
createInstruction<DassignInstruction>(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 *
Expand Down Expand Up @@ -9783,16 +9803,7 @@ void EVMMirBuilder::expandMemoryIR(MInstruction *RequiredSize,
createInstruction<DassignInstruction>(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<ConversionInstruction>(
false, OP_ptrtoint, I64Type, MemPtr);
createInstruction<DassignInstruction>(true, &(Ctx.VoidType), MemBaseInt,
MemoryBaseVar->getVarIdx());
}
reloadMemoryBaseFromInstance();

createInstruction<BrInstruction>(true, Ctx, ContinueBB);
addSuccessor(ContinueBB);
Expand Down
7 changes: 6 additions & 1 deletion src/compiler/evm_frontend/evm_mir_compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
45 changes: 45 additions & 0 deletions src/tests/evm_differential_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint8_t> 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<uint8_t> 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<uint8_t> Bytecode = {
Expand Down
Loading