Skip to content
Merged
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
2 changes: 2 additions & 0 deletions src/action/evm_bytecode_visitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,7 @@ template <typename IRBuilder> class EVMByteCodeVisitor {
Operand MemOffset = pop();
Operand Length = pop();
handleEndBlock();
Builder.noteHelperOpcodeInBlock(Opcode, PC);
Builder.handleReturn(MemOffset, Length);
break;
}
Expand All @@ -924,6 +925,7 @@ template <typename IRBuilder> class EVMByteCodeVisitor {
Operand OffsetOp = pop();
Operand SizeOp = pop();
handleEndBlock();
Builder.noteHelperOpcodeInBlock(Opcode, PC);
Builder.handleRevert(OffsetOp, SizeOp);
break;
}
Expand Down
40 changes: 34 additions & 6 deletions src/compiler/evm_frontend/evm_mir_compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5656,7 +5656,13 @@ void EVMMirBuilder::handleReturn(Operand MemOffsetComponents,
#ifdef ZEN_ENABLE_EVM_GAS_REGISTER
syncGasToMemoryFull();
#endif
preExpandMemoryRange(MemOffsetComponents, LengthComponents);
const bool UsedGuaranteedElision =
preExpandMemoryRange(MemOffsetComponents, LengthComponents, true);
#ifdef ZEN_ENABLE_MULTIPASS_JIT_LOGGING
if (UsedGuaranteedElision) {
++MemStats.ReturnGuaranteedElisionCount;
}
#endif
callRuntimeForWithErrorCheck<void, uint64_t, uint64_t>(
RuntimeFunctions.SetReturnNoExpand, MemOffsetComponents,
LengthComponents);
Expand Down Expand Up @@ -5732,7 +5738,13 @@ void EVMMirBuilder::handleRevert(Operand OffsetOp, Operand SizeOp) {
#ifdef ZEN_ENABLE_EVM_GAS_REGISTER
syncGasToMemoryFull();
#endif
preExpandMemoryRange(OffsetOp, SizeOp);
const bool UsedGuaranteedElision =
preExpandMemoryRange(OffsetOp, SizeOp, true);
#ifdef ZEN_ENABLE_MULTIPASS_JIT_LOGGING
if (UsedGuaranteedElision) {
++MemStats.RevertGuaranteedElisionCount;
}
#endif
callRuntimeForWithErrorCheck<void, uint64_t, uint64_t>(
RuntimeFunctions.SetRevertNoExpand, OffsetOp, SizeOp);

Expand Down Expand Up @@ -6737,15 +6749,24 @@ void EVMMirBuilder::checkStaticModeIR() {
setInsertBlock(ContinueBB);
}

void EVMMirBuilder::preExpandMemoryRange(Operand &Offset, Operand &Size) {
bool EVMMirBuilder::preExpandMemoryRange(Operand &Offset, Operand &Size,
bool AllowGuaranteedElision) {
if (Size.isZeroConstant()) {
const U256Value ZeroValue = {0, 0, 0, 0};
Offset = Operand(ZeroValue);
Size = Operand(ZeroValue);
return;
return false;
}

const bool OffsetWasConst = Offset.isConstU64();
const uint64_t ConstOffset = OffsetWasConst ? Offset.getConstValue()[0] : 0;
const bool SizeWasConst = Size.isConstU64();
const uint64_t ConstSize = SizeWasConst ? Size.getConstValue()[0] : 0;
normalizeOffsetWithSize(Offset, Size);
if (AllowGuaranteedElision && OffsetWasConst && SizeWasConst &&
tryUseGuaranteedMinBytesExpansionElision(true, ConstOffset, ConstSize)) {
return true;
}

MType *I64Type = &Ctx.I64Type;
MInstruction *OffsetLow = extractKnownU64LowOperand(Offset);
Expand All @@ -6756,6 +6777,7 @@ void EVMMirBuilder::preExpandMemoryRange(Operand &Offset, Operand &Size) {
false, CmpInstruction::Predicate::ICMP_ULT, I64Type, RequiredSize,
OffsetLow);
expandMemoryIR(RequiredSize, Overflow);
return false;
}

void EVMMirBuilder::preExpandKeccakTwoWordMemory(Operand &OffsetComponents) {
Expand Down Expand Up @@ -7327,6 +7349,8 @@ bool EVMMirBuilder::hasMemoryCompileStats() const {
MemStats.ReloadMemorySizeCount != 0 ||
MemStats.GetMemoryDataPointerCount != 0 ||
MemStats.ExpandNeedExpandCFGCount != 0 ||
MemStats.ReturnGuaranteedElisionCount != 0 ||
MemStats.RevertGuaranteedElisionCount != 0 ||
MemStats.MemoryExpansionPlanGroupingCandidates != 0 ||
MemStats.MemoryExpansionPlanLinearRegionCandidates != 0 ||
MemStats.MemoryExpansionPlanPrecheckCandidates != 0 ||
Expand Down Expand Up @@ -7762,7 +7786,8 @@ void EVMMirBuilder::dumpMemoryCompileStats() const {
"disp_bytes32_mload_ops=%llu disp_bytes32_mstore_ops=%llu "
"mstore_zero_limb_stores=%llu mstore_overlap_elided_limbs=%llu "
"mstore_addr_value_alias_reuse=%llu "
"need_expand_cfg=%llu",
"need_expand_cfg=%llu return_guaranteed_elision=%llu "
"revert_guaranteed_elision=%llu",
static_cast<unsigned long long>(MemStats.MLoadExpandCount),
static_cast<unsigned long long>(MemStats.MStoreExpandCount),
static_cast<unsigned long long>(MemStats.MStore8ExpandCount),
Expand Down Expand Up @@ -7930,7 +7955,9 @@ void EVMMirBuilder::dumpMemoryCompileStats() const {
static_cast<unsigned long long>(MemStats.MStoreZeroLimbStoreCount),
static_cast<unsigned long long>(MemStats.MStoreOverlapElidedLimbCount),
static_cast<unsigned long long>(MemStats.MStoreAddrValueAliasReuseCount),
static_cast<unsigned long long>(MemStats.ExpandNeedExpandCFGCount));
static_cast<unsigned long long>(MemStats.ExpandNeedExpandCFGCount),
static_cast<unsigned long long>(MemStats.ReturnGuaranteedElisionCount),
static_cast<unsigned long long>(MemStats.RevertGuaranteedElisionCount));

ZEN_LOG_DEBUG(
"[EVM-MEM-SUMMARY] small_frame_candidate_total=%llu "
Expand Down Expand Up @@ -8639,6 +8666,7 @@ void EVMMirBuilder::noteMemoryOpcodeInBlock(evmc_opcode Opcode, uint64_t PC) {
}

void EVMMirBuilder::noteHelperOpcodeInBlock(evmc_opcode Opcode, uint64_t PC) {
CurrentMemoryOpPC = PC;
if (!CurBlockMemStats.Active) {
return;
}
Expand Down
5 changes: 4 additions & 1 deletion src/compiler/evm_frontend/evm_mir_compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -1340,7 +1340,8 @@ class EVMMirBuilder final {
MInstruction *extractKnownU64LowOperand(const Operand &Opnd);
void checkStaticModeIR();
void normalizeOffsetWithSize(Operand &Offset, Operand &Size);
void preExpandMemoryRange(Operand &Offset, Operand &Size);
bool preExpandMemoryRange(Operand &Offset, Operand &Size,
bool AllowGuaranteedElision = false);

Operand convertSingleInstrToU256Operand(MInstruction *SingleInstr);
Operand convertU256InstrToU256Operand(MInstruction *U256Instr);
Expand Down Expand Up @@ -1557,6 +1558,8 @@ class EVMMirBuilder final {
uint64_t MemoryBaseCacheUseCount = 0;

uint64_t ExpandNeedExpandCFGCount = 0;
uint64_t ReturnGuaranteedElisionCount = 0;
uint64_t RevertGuaranteedElisionCount = 0;

uint64_t SmallFrameCandidateTotal = 0;
uint64_t SmallFramePrecheckedTotal = 0;
Expand Down
5 changes: 4 additions & 1 deletion src/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ if(ZEN_ENABLE_SPEC_TEST)
add_executable(evmCacheComplexityDemo evm_cache_complexity_demo.cpp)
target_link_libraries(evmCacheComplexityDemo PRIVATE dtvmcore)
if(ZEN_ENABLE_MULTIPASS_JIT)
add_executable(evmJitFrontendTests evm_jit_frontend_tests.cpp)
add_executable(
evmJitFrontendTests evm_jit_frontend_tests.cpp
evm_terminating_memory_tests.cpp
)
add_executable(evmRangeAnalyzerTests evm_range_analyzer_tests.cpp)
add_executable(evmDifferentialTests evm_differential_tests.cpp)
add_executable(
Expand Down
41 changes: 41 additions & 0 deletions src/tests/evm_differential_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <gtest/gtest.h>
#include <memory>
#include <string>
#include <tuple>
#include <vector>

#include "compiler/evm_frontend/evm_analyzer.h"
Expand Down Expand Up @@ -350,6 +351,46 @@ TEST(EVMMemoryTerminationDifferential, DynamicEmptyRevertIgnoresHighOffset) {
"dynamic_empty_revert_high_offset", Bytecode, EVMC_REVERT));
}

TEST(EVMMemoryTerminationDifferential,
CrossBlockCoveredRangeMatchesStatusOutputAndGas) {
const std::vector<uint8_t> Prefix = {
0x60, 0x01, // PC0 PUSH1 1: value
0x60, 0x80, // PC2 PUSH1 0x80: memory offset
0x52, // PC4 MSTORE: prove memory covers [0, 0xa0)
0x60, 0x08, // PC5 PUSH1 8
0x56, // PC7 JUMP
0x5b, // PC8 JUMPDEST
0x60, 0x20, // PC9 PUSH1 32: output size
0x60, 0x80, // PC11 PUSH1 0x80: output offset
};
const std::string ExpectedOutput =
"0000000000000000000000000000000000000000000000000000000000000001";

for (const auto &[Name, Terminator, ExpectedStatus] :
std::vector<std::tuple<const char *, uint8_t, evmc_status_code>>{
{"return", 0xf3, EVMC_SUCCESS},
{"revert", 0xfd, EVMC_REVERT},
}) {
std::vector<uint8_t> Bytecode = Prefix;
Bytecode.push_back(Terminator);

const auto Interp =
runEvmBytecode(std::string(Name) + "_covered_range_interp", Bytecode,
common::RunMode::InterpMode, {}, 0u, true);
const auto Multi =
runEvmBytecode(std::string(Name) + "_covered_range_multipass", Bytecode,
common::RunMode::MultipassMode, {}, 0u, true);
#ifdef ZEN_ENABLE_JIT
EXPECT_TRUE(Multi.JITCompiled);
#endif
EXPECT_EQ(Interp.Status, ExpectedStatus);
EXPECT_EQ(Multi.Status, Interp.Status);
EXPECT_EQ(Multi.GasLeft, Interp.GasLeft);
EXPECT_EQ(Multi.OutputHex, Interp.OutputHex);
EXPECT_EQ(Interp.OutputHex, ExpectedOutput);
}
}

// Range-narrowed lowering paths: the range-narrowed ISZERO/JUMPI folds,
// U64-tagged OR/XOR, and the signed-compare (SLT/SGT) u64-const fast paths in
// the multipass lowering. Each fixture feeds a dynamic operand so the narrowed
Expand Down
31 changes: 30 additions & 1 deletion src/tests/evm_jit_frontend_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1370,6 +1370,11 @@ struct MockMeterOpcodeRangeRecord {
uint64_t EndPCExclusive = 0;
};

struct MockHelperOpcodeRecord {
evmc_opcode Opcode = OP_STOP;
uint64_t PC = 0;
};

struct MockConstPrecheckPlanRecord {
uint64_t MaxRequiredSize = 0;
uint64_t CoveredDirectOps = 0;
Expand Down Expand Up @@ -1694,7 +1699,9 @@ class MockEVMBuilder {
LinearPrecheckPrepareCount++;
}
void noteMemoryOpcodeInBlock(evmc_opcode, uint64_t) {}
void noteHelperOpcodeInBlock(evmc_opcode, uint64_t) {}
void noteHelperOpcodeInBlock(evmc_opcode Opcode, uint64_t PC) {
HelperOpcodes.push_back({Opcode, PC});
}
void endMemoryCompileBlock() {}

void handleJump(Operand) {}
Expand All @@ -1721,6 +1728,10 @@ class MockEVMBuilder {
return MeteredRanges;
}

const std::vector<MockHelperOpcodeRecord> &helperOpcodes() const {
return HelperOpcodes;
}

const std::vector<MockConstPrecheckPlanRecord> &constPrecheckPlans() const {
return ConstPrecheckPlans;
}
Expand Down Expand Up @@ -1826,6 +1837,7 @@ class MockEVMBuilder {
std::array<MockStackAccessStats, 256> Stats = {};
std::array<uint32_t, 256> MeteredOpcodeCounts = {};
std::vector<MockMeterOpcodeRangeRecord> MeteredRanges;
std::vector<MockHelperOpcodeRecord> HelperOpcodes;
std::vector<MockConstPrecheckPlanRecord> ConstPrecheckPlans;
MockMStoreRecord LastMStore = {};
uint32_t MStoreCount = 0;
Expand Down Expand Up @@ -1957,6 +1969,23 @@ bool compileWithMockBuilder(const std::vector<uint8_t> &Bytecode,
return Visitor.compile();
}

TEST(EVMJITFrontendVisitorTest, TerminatingMemoryHelpersRetainExactOpcodePC) {
const std::vector<uint8_t> ReturnBytecode = {OP_PUSH0, OP_PUSH0, OP_RETURN};
const std::vector<uint8_t> RevertBytecode = {OP_PUSH0, OP_PUSH0, OP_REVERT};

MockEVMBuilder ReturnBuilder;
ASSERT_TRUE(compileWithMockBuilder(ReturnBytecode, ReturnBuilder));
ASSERT_EQ(ReturnBuilder.helperOpcodes().size(), 1u);
EXPECT_EQ(ReturnBuilder.helperOpcodes()[0].Opcode, OP_RETURN);
EXPECT_EQ(ReturnBuilder.helperOpcodes()[0].PC, 2u);

MockEVMBuilder RevertBuilder;
ASSERT_TRUE(compileWithMockBuilder(RevertBytecode, RevertBuilder));
ASSERT_EQ(RevertBuilder.helperOpcodes().size(), 1u);
EXPECT_EQ(RevertBuilder.helperOpcodes()[0].Opcode, OP_REVERT);
EXPECT_EQ(RevertBuilder.helperOpcodes()[0].PC, 2u);
}

TEST(EVMJITFrontendVisitorTest, DynamicJumpConsistencyErrorEscapesVisitor) {
const std::vector<uint8_t> Bytecode = {
0x60, 0x04, // PC0 PUSH1 0x04 (analyzer resolves a constant destination)
Expand Down
122 changes: 122 additions & 0 deletions src/tests/evm_terminating_memory_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (C) 2025 the DTVM authors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

#include "compiler/evm_frontend/evm_imported.h"
#include "compiler/evm_frontend/evm_mir_compiler.h"
#include "compiler/mir/module.h"

#include "llvm/Support/raw_ostream.h"
#include <gtest/gtest.h>

#include <array>
#include <optional>
#include <string>
#include <vector>

namespace {

using COMPILER::EVMMirBuilder;

#ifdef ZEN_ENABLE_EVM_MEMORY_PLAN_FRAMEWORK
std::optional<size_t>
countTerminatingExpandMemoryCalls(const std::vector<uint8_t> &Bytecode) {
COMPILER::EVMFrontendContext Ctx;
Ctx.setRevision(EVMC_CANCUN);
Ctx.setBytecode(reinterpret_cast<const zen::common::Byte *>(Bytecode.data()),
Bytecode.size());

COMPILER::MModule Mod(Ctx);
std::array<COMPILER::MType *, 1> ParamTypes = {
COMPILER::MPointerType::create(Ctx, Ctx.VoidType)};
COMPILER::MFunctionType *FuncType = COMPILER::MFunctionType::create(
Ctx, Ctx.VoidType, llvm::ArrayRef<COMPILER::MType *>(ParamTypes));
Mod.addFuncType(FuncType);

COMPILER::MFunction Func(Ctx, 0);
Func.setFunctionType(FuncType);
EVMMirBuilder Builder(Ctx, Func);
if (!Builder.compile(&Ctx)) {
return std::nullopt;
}

std::string Mir;
llvm::raw_string_ostream OS(Mir);
Func.print(OS);
OS.flush();

const auto &RuntimeFunctions = COMPILER::getRuntimeFunctionTable();
const auto Address =
COMPILER::getFunctionAddress(RuntimeFunctions.ExpandMemoryNoGas);
const std::string Needle =
"target = const.i64 " + std::to_string(Address) + ", ";
size_t Count = 0;
for (size_t Pos = Mir.find(Needle); Pos != std::string::npos;
Pos = Mir.find(Needle, Pos + Needle.size())) {
++Count;
}
return Count;
}

TEST(EVMMirBuilderTerminatingMemoryProofTest,
ReusesCrossBlockProofForReturnAndRevert) {
const std::vector<uint8_t> ZeroSizeReturn = {
OP_PUSH1, 0x01, OP_PUSH1, 0x80, OP_MSTORE, OP_PUSH1,
0x08, OP_JUMP, OP_JUMPDEST, OP_PUSH0, OP_PUSH0, OP_RETURN};
const std::vector<uint8_t> CoveredReturn = {
OP_PUSH1, 0x01, OP_PUSH1, 0x80, OP_MSTORE, OP_PUSH1, 0x08,
OP_JUMP, OP_JUMPDEST, OP_PUSH1, 0x20, OP_PUSH1, 0x80, OP_RETURN};
const std::vector<uint8_t> ZeroSizeRevert = {
OP_PUSH1, 0x01, OP_PUSH1, 0x80, OP_MSTORE, OP_PUSH1,
0x08, OP_JUMP, OP_JUMPDEST, OP_PUSH0, OP_PUSH0, OP_REVERT};
const std::vector<uint8_t> CoveredRevert = {
OP_PUSH1, 0x01, OP_PUSH1, 0x80, OP_MSTORE, OP_PUSH1, 0x08,
OP_JUMP, OP_JUMPDEST, OP_PUSH1, 0x20, OP_PUSH1, 0x80, OP_REVERT};

const auto ZeroReturnCalls =
countTerminatingExpandMemoryCalls(ZeroSizeReturn);
const auto ReturnCalls = countTerminatingExpandMemoryCalls(CoveredReturn);
const auto ZeroRevertCalls =
countTerminatingExpandMemoryCalls(ZeroSizeRevert);
const auto RevertCalls = countTerminatingExpandMemoryCalls(CoveredRevert);
ASSERT_TRUE(ZeroReturnCalls.has_value());
ASSERT_TRUE(ReturnCalls.has_value());
ASSERT_TRUE(ZeroRevertCalls.has_value());
ASSERT_TRUE(RevertCalls.has_value());
EXPECT_EQ(*ReturnCalls, *ZeroReturnCalls);
EXPECT_EQ(*RevertCalls, *ZeroRevertCalls);
}

TEST(EVMMirBuilderTerminatingMemoryProofTest,
RetainsUncoveredFallbackAndZeroSizeSemantics) {
const std::vector<uint8_t> ZeroSizeReturn = {
OP_PUSH1, 0x01, OP_PUSH1, 0x80, OP_MSTORE, OP_PUSH1,
0x08, OP_JUMP, OP_JUMPDEST, OP_PUSH0, OP_PUSH0, OP_RETURN};
const std::vector<uint8_t> UncoveredReturn = {
OP_PUSH1, 0x01, OP_PUSH1, 0x80, OP_MSTORE, OP_PUSH1, 0x08,
OP_JUMP, OP_JUMPDEST, OP_PUSH1, 0x20, OP_PUSH1, 0xa0, OP_RETURN};
const std::vector<uint8_t> HugeOffsetZeroSizeRevert = {
OP_PUSH1, 0x01, OP_PUSH1, 0x80, OP_MSTORE, OP_PUSH1, 0x08,
OP_JUMP, OP_JUMPDEST, OP_PUSH0, OP_PUSH8, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, OP_REVERT};
const std::vector<uint8_t> ZeroSizeRevert = {
OP_PUSH1, 0x01, OP_PUSH1, 0x80, OP_MSTORE, OP_PUSH1,
0x08, OP_JUMP, OP_JUMPDEST, OP_PUSH0, OP_PUSH0, OP_REVERT};

const auto ZeroReturnCalls =
countTerminatingExpandMemoryCalls(ZeroSizeReturn);
const auto UncoveredCalls =
countTerminatingExpandMemoryCalls(UncoveredReturn);
const auto HugeOffsetZeroSizeCalls =
countTerminatingExpandMemoryCalls(HugeOffsetZeroSizeRevert);
const auto ZeroRevertCalls =
countTerminatingExpandMemoryCalls(ZeroSizeRevert);
ASSERT_TRUE(ZeroReturnCalls.has_value());
ASSERT_TRUE(UncoveredCalls.has_value());
ASSERT_TRUE(HugeOffsetZeroSizeCalls.has_value());
ASSERT_TRUE(ZeroRevertCalls.has_value());
EXPECT_EQ(*UncoveredCalls, *ZeroReturnCalls + 1);
EXPECT_EQ(*HugeOffsetZeroSizeCalls, *ZeroRevertCalls);
}
#endif

} // namespace
Loading