From 6fbac7386d6fcd55870fc2723014c14ad54d089e Mon Sep 17 00:00:00 2001 From: ECNUyhy <93872942+ECNUyhy@users.noreply.github.com> Date: Sat, 25 Jul 2026 05:51:12 +0000 Subject: [PATCH 1/2] perf(evm): propagate MSTORE stride through fused precheck Forward the stride operand already recovered by the fused MSTORE and ADD macro-op into the linear memory precheck. Previously the precheck reused the unfused bytecode stack index, so the reduced logical stack skipped the shared range proof and left every MSTORE on the expand path. --- src/action/evm_bytecode_visitor.h | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/action/evm_bytecode_visitor.h b/src/action/evm_bytecode_visitor.h index 20d88056..1f51679f 100644 --- a/src/action/evm_bytecode_visitor.h +++ b/src/action/evm_bytecode_visitor.h @@ -1618,14 +1618,23 @@ template class EVMByteCodeVisitor { RequiredSize <= Plan.MaxRequiredSize; } + void maybePrepareLinearBlockMemoryPrecheck(evmc_opcode Opcode, + Operand Stride) { + if (!CurBlockLinearPrecheckPlan.Eligible || + CurBlockLinearPrecheckPlan.CoveredOpcode != Opcode) { + return; + } + Builder.prepareLinearBlockMemoryPrecheck(Stride); + } + void maybePrepareLinearBlockMemoryPrecheck(evmc_opcode Opcode) { if (!CurBlockLinearPrecheckPlan.Eligible || CurBlockLinearPrecheckPlan.CoveredOpcode != Opcode || Stack.getSize() <= CurBlockLinearPrecheckPlan.StrideStackIndex) { return; } - Builder.prepareLinearBlockMemoryPrecheck( - Stack.peek(CurBlockLinearPrecheckPlan.StrideStackIndex)); + maybePrepareLinearBlockMemoryPrecheck( + Opcode, Stack.peek(CurBlockLinearPrecheckPlan.StrideStackIndex)); } LargeStaticWorkspaceVerifierResult @@ -3085,7 +3094,7 @@ template class EVMByteCodeVisitor { Operand Current = Stack.peek(0); Operand Stride = Stack.peek(1); Builder.noteMemoryOpcodeInBlock(OP_MSTORE, MStorePC); - maybePrepareLinearBlockMemoryPrecheck(OP_MSTORE); + maybePrepareLinearBlockMemoryPrecheck(OP_MSTORE, Stride); Builder.handleMStore(Current, Current); Operand Next = Builder.template handleBinaryArithmetic(Current, From 88f6974acab8d19a75a5415149e4a4b4d42ca570 Mon Sep 17 00:00:00 2001 From: ECNUyhy <93872942+ECNUyhy@users.noreply.github.com> Date: Sat, 25 Jul 2026 06:08:46 +0000 Subject: [PATCH 2/2] test(evm): cover fused MSTORE stride propagation Record the dynamic stride passed into linear memory prechecks and verify both the fused MSTORE macro-op and the existing MSTORE8 path preserve it. The focused regression fails when the fused path falls back to the stale unfused stack index. --- src/tests/evm_jit_frontend_tests.cpp | 56 +++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/tests/evm_jit_frontend_tests.cpp b/src/tests/evm_jit_frontend_tests.cpp index f046c7ad..3f80d982 100644 --- a/src/tests/evm_jit_frontend_tests.cpp +++ b/src/tests/evm_jit_frontend_tests.cpp @@ -1554,7 +1554,7 @@ class MockEVMBuilder { MOCK_OPERAND_STUB(handleBlobHash); MOCK_OPERAND_STUB(handleBlockHash); MOCK_OPERAND_STUB(handleByte); - MOCK_OPERAND_STUB(handleCallDataLoad); + Operand handleCallDataLoad(Operand) { return Operand(CallDataLoadResult); } MOCK_OPERAND_STUB(handleCallDataSize); MOCK_OPERAND_STUB(handleCallValue); MOCK_OPERAND_STUB(handleCaller); @@ -1690,8 +1690,9 @@ class MockEVMBuilder { LargeStaticWorkspaceRejectHelperByteExactRisk += RejectHelperByteExactRisk; LargeStaticWorkspaceRejectTooFewOps += RejectTooFewOps; } - void prepareLinearBlockMemoryPrecheck(Operand) { + void prepareLinearBlockMemoryPrecheck(Operand Stride) { LinearPrecheckPrepareCount++; + LastLinearPrecheckStride = Stride.resolvedValue()[0]; } void noteMemoryOpcodeInBlock(evmc_opcode, uint64_t) {} void noteHelperOpcodeInBlock(evmc_opcode, uint64_t) {} @@ -1796,6 +1797,10 @@ class MockEVMBuilder { return LinearPrecheckPrepareCount; } + void setCallDataLoadResult(uint64_t Value) { CallDataLoadResult = Value; } + + uint64_t lastLinearPrecheckStride() const { return LastLinearPrecheckStride; } + bool Trapped = false; bool Undefined = false; uint64_t LargeStaticWorkspaceCandidates = 0; @@ -1850,6 +1855,8 @@ class MockEVMBuilder { uint64_t LastLinearPrecheckCoveredDirectOps = 0; bool LastLinearPrecheckValueEqualsFirstAddr = false; uint32_t LinearPrecheckPrepareCount = 0; + uint64_t LastLinearPrecheckStride = 0; + uint64_t CallDataLoadResult = 0; #undef MOCK_OPERAND_STUB #undef MOCK_VOID_STUB @@ -3053,6 +3060,49 @@ TEST(EVMJITFrontendVisitorTest, FusesLinearMStoreNextMotifIntoMeteredRange) { EXPECT_EQ(Builder.topStackValue()[0], 0x60U); } +TEST(EVMJITFrontendVisitorTest, + PlansFusedLinearMStoreMemoryPrecheckWithDynamicStride) { + const std::vector Bytecode = { + 0x5f, // PUSH0 calldata offset for stride + 0x35, // CALLDATALOAD + 0x5f, // PUSH0 current + 0x80, // DUP1 + 0x80, // DUP1 + 0x52, // MSTORE + 0x81, // DUP2 + 0x01, // ADD + 0x80, // DUP1 + 0x80, // DUP1 + 0x52, // MSTORE + 0x81, // DUP2 + 0x01, // ADD + 0x00 // STOP + }; + + COMPILER::EVMFrontendContext Ctx; + Ctx.setRevision(EVMC_CANCUN); + Ctx.setBytecode(reinterpret_cast(Bytecode.data()), + Bytecode.size()); + + MockEVMBuilder Builder; + Builder.setCallDataLoadResult(7); + COMPILER::EVMByteCodeVisitor Visitor(Builder, &Ctx); + EXPECT_TRUE(Visitor.compile()); + EXPECT_FALSE(Builder.Trapped); + EXPECT_FALSE(Builder.Undefined); + + EXPECT_EQ(Builder.linearPrecheckPlanCount(), 1U); + EXPECT_EQ(Builder.lastLinearPrecheckAccessWidth(), 32U); + EXPECT_EQ(Builder.lastLinearPrecheckCoveredDirectOps(), 2U); + EXPECT_TRUE(Builder.lastLinearPrecheckValueEqualsFirstAddr()); + EXPECT_EQ(Builder.linearPrecheckPrepareCount(), 2U); + EXPECT_EQ(Builder.lastLinearPrecheckStride(), 7U); + EXPECT_EQ(Builder.meteredOpcodeCount(OP_MSTORE), 2U); + EXPECT_EQ(Builder.mstoreCount(), 2U); + EXPECT_EQ(Builder.runtimeStackDepth(), 2U); + EXPECT_EQ(Builder.topStackValue()[0], 14U); +} + TEST(EVMJITFrontendVisitorTest, PlansLinearMStore8NextMotifMemoryPrecheck) { const std::vector Bytecode = { 0x5f, // PUSH0 calldata offset for stride @@ -3077,6 +3127,7 @@ TEST(EVMJITFrontendVisitorTest, PlansLinearMStore8NextMotifMemoryPrecheck) { Bytecode.size()); MockEVMBuilder Builder; + Builder.setCallDataLoadResult(11); COMPILER::EVMByteCodeVisitor Visitor(Builder, &Ctx); EXPECT_TRUE(Visitor.compile()); EXPECT_FALSE(Builder.Trapped); @@ -3087,6 +3138,7 @@ TEST(EVMJITFrontendVisitorTest, PlansLinearMStore8NextMotifMemoryPrecheck) { EXPECT_EQ(Builder.lastLinearPrecheckCoveredDirectOps(), 2U); EXPECT_FALSE(Builder.lastLinearPrecheckValueEqualsFirstAddr()); EXPECT_EQ(Builder.linearPrecheckPrepareCount(), 2U); + EXPECT_EQ(Builder.lastLinearPrecheckStride(), 11U); EXPECT_EQ(Builder.meteredOpcodeCount(OP_MSTORE8), 2U); EXPECT_EQ(Builder.runtimeStackDepth(), 2U); }