Skip to content

perf(evm): implement EVM lazy compile (segment-based compilation)#416

Open
ys8888john wants to merge 1 commit intoDTVMStack:mainfrom
ys8888john:evm-lazy-compile
Open

perf(evm): implement EVM lazy compile (segment-based compilation)#416
ys8888john wants to merge 1 commit intoDTVMStack:mainfrom
ys8888john:evm-lazy-compile

Conversation

@ys8888john
Copy link
Copy Markdown
Contributor

1. Does this PR affect any open issues?(Y/N) and add issue references (e.g. "fix #123", "re #123".):

  • N
  • Y

2. What is the scope of this PR (e.g. component or file name):

3. Provide a description of the PR(e.g. more details, effects, motivations or doc link):

  • Affects user behaviors
  • Contains CI/CD configuration changes
  • Contains documentation changes
  • Contains experimental features
  • Performance regression: Consumes more CPU
  • Performance regression: Consumes more Memory
  • Other

4. Are there any breaking changes?(Y/N) and describe the breaking changes(e.g. more details, motivations or doc link):

  • N
  • Y

5. Are there test cases for these changes?(Y/N) select and add more details, references or doc links:

  • Unit test
  • Integration test
  • Benchmark (add benchmark stats below)
  • Manual test (add detailed scripts or steps below)
  • Other

6. Release note

None

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 18, 2026

⚡ Performance Regression Check Results

✅ Performance Check Passed (interpreter)

Performance Benchmark Results (threshold: 25%)

Benchmark Baseline (us) Current (us) Change Status
total/main/blake2b_huff/8415nulls 1.52 1.51 -0.8% PASS
total/main/blake2b_huff/empty 0.02 0.02 -1.0% PASS
total/main/blake2b_shifts/8415nulls 11.74 11.84 +0.8% PASS
total/main/sha1_divs/5311 5.12 5.16 +0.8% PASS
total/main/sha1_divs/empty 0.06 0.06 -0.3% PASS
total/main/sha1_shifts/5311 2.90 2.86 -1.4% PASS
total/main/sha1_shifts/empty 0.04 0.04 -2.8% PASS
total/main/snailtracer/benchmark 52.92 52.52 -0.7% PASS
total/main/structarray_alloc/nfts_rank 0.97 0.97 -0.0% PASS
total/main/swap_math/insufficient_liquidity 0.00 0.00 -3.4% PASS
total/main/swap_math/received 0.00 0.00 -1.9% PASS
total/main/swap_math/spent 0.00 0.00 -2.2% PASS
total/main/weierstrudel/1 0.28 0.29 +1.9% PASS
total/main/weierstrudel/15 3.15 3.17 +0.7% PASS
total/micro/JUMPDEST_n0/empty 1.31 1.47 +12.3% PASS
total/micro/jump_around/empty 0.10 0.10 +0.4% PASS
total/micro/loop_with_many_jumpdests/empty 20.05 22.63 +12.9% PASS
total/micro/memory_grow_mload/by1 0.09 0.09 +0.8% PASS
total/micro/memory_grow_mload/by16 0.10 0.09 -7.5% PASS
total/micro/memory_grow_mload/by32 0.11 0.11 -0.2% PASS
total/micro/memory_grow_mload/nogrow 0.09 0.09 -5.1% PASS
total/micro/memory_grow_mstore/by1 0.10 0.10 -0.5% PASS
total/micro/memory_grow_mstore/by16 0.10 0.10 -1.6% PASS
total/micro/memory_grow_mstore/by32 0.12 0.11 -0.2% PASS
total/micro/memory_grow_mstore/nogrow 0.10 0.09 -6.3% PASS
total/micro/signextend/one 0.23 0.23 +1.4% PASS
total/micro/signextend/zero 0.23 0.23 +1.9% PASS
total/synth/ADD/b0 2.01 1.99 -1.3% PASS
total/synth/ADD/b1 2.02 1.97 -2.6% PASS
total/synth/ADDRESS/a0 4.77 4.77 +0.1% PASS
total/synth/ADDRESS/a1 5.38 5.24 -2.6% PASS
total/synth/AND/b0 1.71 1.72 +0.1% PASS
total/synth/AND/b1 1.74 1.72 -1.2% PASS
total/synth/BYTE/b0 6.14 6.10 -0.7% PASS
total/synth/BYTE/b1 4.81 4.74 -1.6% PASS
total/synth/CALLDATASIZE/a0 3.60 3.34 -7.0% PASS
total/synth/CALLDATASIZE/a1 3.63 3.86 +6.4% PASS
total/synth/CALLER/a0 4.75 4.76 +0.2% PASS
total/synth/CALLER/a1 5.37 5.23 -2.6% PASS
total/synth/CALLVALUE/a0 3.76 3.51 -6.6% PASS
total/synth/CALLVALUE/a1 3.80 3.52 -7.4% PASS
total/synth/CODESIZE/a0 3.99 3.92 -1.7% PASS
total/synth/CODESIZE/a1 4.04 3.92 -2.9% PASS
total/synth/DUP1/d0 1.30 1.15 -12.0% PASS
total/synth/DUP1/d1 1.40 1.31 -6.5% PASS
total/synth/DUP10/d0 1.31 1.15 -12.0% PASS
total/synth/DUP10/d1 1.16 1.23 +6.4% PASS
total/synth/DUP11/d0 1.27 1.15 -9.6% PASS
total/synth/DUP11/d1 1.40 1.23 -11.7% PASS
total/synth/DUP12/d0 1.13 1.15 +1.2% PASS
total/synth/DUP12/d1 1.16 1.24 +6.3% PASS
total/synth/DUP13/d0 1.12 1.15 +2.7% PASS
total/synth/DUP13/d1 1.40 1.23 -11.9% PASS
total/synth/DUP14/d0 1.07 1.23 +14.6% PASS
total/synth/DUP14/d1 1.40 1.23 -11.6% PASS
total/synth/DUP15/d0 1.25 1.14 -8.2% PASS
total/synth/DUP15/d1 1.40 1.00 -28.4% PASS
total/synth/DUP16/d0 1.06 1.15 +8.4% PASS
total/synth/DUP16/d1 1.40 1.23 -11.9% PASS
total/synth/DUP2/d0 1.12 1.23 +9.9% PASS
total/synth/DUP2/d1 1.40 1.02 -27.2% PASS
total/synth/DUP3/d0 1.07 1.11 +4.1% PASS
total/synth/DUP3/d1 1.39 1.23 -11.5% PASS
total/synth/DUP4/d0 1.06 1.15 +8.4% PASS
total/synth/DUP4/d1 1.40 1.23 -11.8% PASS
total/synth/DUP5/d0 1.31 1.15 -12.2% PASS
total/synth/DUP5/d1 1.40 1.00 -28.4% PASS
total/synth/DUP6/d0 1.07 1.17 +8.8% PASS
total/synth/DUP6/d1 1.39 1.23 -11.5% PASS
total/synth/DUP7/d0 1.33 1.23 -8.0% PASS
total/synth/DUP7/d1 1.39 1.23 -11.7% PASS
total/synth/DUP8/d0 1.31 1.15 -12.3% PASS
total/synth/DUP8/d1 1.16 1.23 +6.2% PASS
total/synth/DUP9/d0 1.31 1.12 -14.5% PASS
total/synth/DUP9/d1 1.40 1.00 -28.5% PASS
total/synth/EQ/b0 2.77 2.77 +0.0% PASS
total/synth/EQ/b1 1.35 1.40 +3.7% PASS
total/synth/GAS/a0 3.83 3.92 +2.3% PASS
total/synth/GAS/a1 3.89 3.93 +1.1% PASS
total/synth/GT/b0 2.62 2.62 +0.1% PASS
total/synth/GT/b1 1.57 1.48 -5.8% PASS
total/synth/ISZERO/u0 1.15 1.15 -0.1% PASS
total/synth/JUMPDEST/n0 1.31 1.96 +49.2% PASS
total/synth/LT/b0 2.63 2.63 +0.0% PASS
total/synth/LT/b1 1.57 1.48 -5.8% PASS
total/synth/MSIZE/a0 4.25 4.26 +0.3% PASS
total/synth/MSIZE/a1 4.88 4.76 -2.4% PASS
total/synth/MUL/b0 5.31 5.32 +0.1% PASS
total/synth/MUL/b1 5.47 5.38 -1.6% PASS
total/synth/NOT/u0 1.80 1.70 -5.7% PASS
total/synth/OR/b0 1.65 1.64 -0.5% PASS
total/synth/OR/b1 1.74 1.72 -1.4% PASS
total/synth/PC/a0 3.59 3.39 -5.6% PASS
total/synth/PC/a1 3.55 3.45 -2.9% PASS
total/synth/PUSH1/p0 1.07 1.07 -0.0% PASS
total/synth/PUSH1/p1 1.41 1.31 -7.6% PASS
total/synth/PUSH10/p0 1.14 1.15 +0.6% PASS
total/synth/PUSH10/p1 1.42 1.31 -8.0% PASS
total/synth/PUSH11/p0 1.15 1.09 -5.3% PASS
total/synth/PUSH11/p1 1.42 1.33 -6.5% PASS
total/synth/PUSH12/p0 1.07 1.19 +11.8% PASS
total/synth/PUSH12/p1 1.43 1.35 -5.5% PASS
total/synth/PUSH13/p0 1.09 1.15 +5.2% PASS
total/synth/PUSH13/p1 1.42 1.37 -3.3% PASS
total/synth/PUSH14/p0 1.15 1.12 -2.6% PASS
total/synth/PUSH14/p1 1.43 1.33 -7.0% PASS
total/synth/PUSH15/p0 1.15 1.04 -9.3% PASS
total/synth/PUSH15/p1 1.54 1.41 -7.9% PASS
total/synth/PUSH16/p0 1.07 1.15 +7.4% PASS
total/synth/PUSH16/p1 1.43 1.35 -5.5% PASS
total/synth/PUSH17/p0 1.15 1.15 -0.3% PASS
total/synth/PUSH17/p1 1.43 1.32 -7.4% PASS
total/synth/PUSH18/p0 1.15 1.20 +4.0% PASS
total/synth/PUSH18/p1 1.42 1.35 -4.6% PASS
total/synth/PUSH19/p0 1.15 1.15 +0.2% PASS
total/synth/PUSH19/p1 1.43 1.33 -6.6% PASS
total/synth/PUSH2/p0 1.07 1.15 +7.1% PASS
total/synth/PUSH2/p1 1.41 1.32 -6.5% PASS
total/synth/PUSH20/p0 1.11 1.14 +2.5% PASS
total/synth/PUSH20/p1 1.43 1.34 -6.7% PASS
total/synth/PUSH21/p0 1.01 1.15 +13.5% PASS
total/synth/PUSH21/p1 1.44 1.34 -7.1% PASS
total/synth/PUSH22/p0 1.15 1.13 -1.5% PASS
total/synth/PUSH22/p1 1.43 1.37 -4.6% PASS
total/synth/PUSH23/p0 0.89 1.15 +29.5% PASS
total/synth/PUSH23/p1 1.44 1.35 -6.4% PASS
total/synth/PUSH24/p0 1.15 1.15 -0.1% PASS
total/synth/PUSH24/p1 1.42 1.34 -5.9% PASS
total/synth/PUSH25/p0 1.15 1.15 -0.4% PASS
total/synth/PUSH25/p1 1.43 1.34 -6.5% PASS
total/synth/PUSH26/p0 1.07 1.00 -6.5% PASS
total/synth/PUSH26/p1 1.44 1.34 -6.6% PASS
total/synth/PUSH27/p0 1.12 1.15 +2.2% PASS
total/synth/PUSH27/p1 1.44 1.35 -6.5% PASS
total/synth/PUSH28/p0 0.97 1.15 +19.0% PASS
total/synth/PUSH28/p1 1.44 1.31 -8.7% PASS
total/synth/PUSH29/p0 1.15 0.99 -14.0% PASS
total/synth/PUSH29/p1 1.45 1.35 -6.9% PASS
total/synth/PUSH3/p0 1.15 1.15 -0.3% PASS
total/synth/PUSH3/p1 1.42 1.32 -6.8% PASS
total/synth/PUSH30/p0 1.17 1.16 -1.2% PASS
total/synth/PUSH30/p1 1.43 1.36 -4.9% PASS
total/synth/PUSH31/p0 0.99 1.15 +16.3% PASS
total/synth/PUSH31/p1 1.58 1.52 -4.1% PASS
total/synth/PUSH32/p0 1.13 1.15 +1.9% PASS
total/synth/PUSH32/p1 1.44 1.37 -4.7% PASS
total/synth/PUSH4/p0 0.91 1.22 +34.1% PASS
total/synth/PUSH4/p1 1.45 1.35 -6.8% PASS
total/synth/PUSH5/p0 1.14 1.08 -5.5% PASS
total/synth/PUSH5/p1 1.42 1.33 -6.6% PASS
total/synth/PUSH6/p0 0.86 1.03 +19.6% PASS
total/synth/PUSH6/p1 1.42 1.34 -5.0% PASS
total/synth/PUSH7/p0 1.15 1.23 +7.1% PASS
total/synth/PUSH7/p1 1.43 1.34 -6.4% PASS
total/synth/PUSH8/p0 0.91 1.15 +26.2% PASS
total/synth/PUSH8/p1 1.42 1.33 -6.5% PASS
total/synth/PUSH9/p0 1.07 1.08 +0.5% PASS
total/synth/PUSH9/p1 1.43 1.33 -6.8% PASS
total/synth/RETURNDATASIZE/a0 3.99 3.52 -11.8% PASS
total/synth/RETURNDATASIZE/a1 4.03 3.77 -6.5% PASS
total/synth/SAR/b0 3.81 3.79 -0.5% PASS
total/synth/SAR/b1 4.39 4.31 -1.9% PASS
total/synth/SGT/b0 2.62 2.62 +0.0% PASS
total/synth/SGT/b1 1.57 1.64 +4.6% PASS
total/synth/SHL/b0 3.05 3.05 +0.1% PASS
total/synth/SHL/b1 1.66 1.60 -3.6% PASS
total/synth/SHR/b0 2.94 2.94 -0.1% PASS
total/synth/SHR/b1 1.65 1.56 -5.5% PASS
total/synth/SIGNEXTEND/b0 3.59 3.30 -8.0% PASS
total/synth/SIGNEXTEND/b1 3.79 3.44 -9.2% PASS
total/synth/SLT/b0 2.62 2.60 -0.8% PASS
total/synth/SLT/b1 1.57 1.64 +4.6% PASS
total/synth/SUB/b0 1.99 1.98 -0.2% PASS
total/synth/SUB/b1 2.00 1.97 -1.3% PASS
total/synth/SWAP1/s0 1.49 1.49 -0.0% PASS
total/synth/SWAP10/s0 1.51 1.51 +0.2% PASS
total/synth/SWAP11/s0 1.51 1.51 +0.1% PASS
total/synth/SWAP12/s0 1.51 1.51 -0.0% PASS
total/synth/SWAP13/s0 1.51 1.51 +0.2% PASS
total/synth/SWAP14/s0 1.51 1.51 +0.1% PASS
total/synth/SWAP15/s0 1.51 1.51 +0.0% PASS
total/synth/SWAP16/s0 1.51 1.55 +2.8% PASS
total/synth/SWAP2/s0 1.49 1.49 -0.1% PASS
total/synth/SWAP3/s0 1.50 1.50 -0.1% PASS
total/synth/SWAP4/s0 1.50 1.49 -0.1% PASS
total/synth/SWAP5/s0 1.50 1.50 -0.0% PASS
total/synth/SWAP6/s0 1.50 1.50 +0.0% PASS
total/synth/SWAP7/s0 1.50 1.50 +0.1% PASS
total/synth/SWAP8/s0 1.50 1.50 +0.1% PASS
total/synth/SWAP9/s0 1.50 1.50 +0.1% PASS
total/synth/XOR/b0 1.55 1.55 +0.0% PASS
total/synth/XOR/b1 1.57 1.55 -1.4% PASS
total/synth/loop_v1 4.45 4.64 +4.2% PASS
total/synth/loop_v2 4.44 4.58 +3.2% PASS

Summary: 194 benchmarks, 0 regressions


✅ Performance Check Passed (multipass)

Performance Benchmark Results (threshold: 25%)

Benchmark Baseline (us) Current (us) Change Status
total/main/blake2b_huff/8415nulls 1.74 1.80 +3.1% PASS
total/main/blake2b_huff/empty 0.07 0.08 +15.2% PASS
total/main/blake2b_shifts/8415nulls 3.37 3.44 +2.0% PASS
total/main/sha1_divs/5311 0.62 0.65 +4.1% PASS
total/main/sha1_divs/empty 0.01 0.02 +5.7% PASS
total/main/sha1_shifts/5311 0.55 0.56 +1.2% PASS
total/main/sha1_shifts/empty 0.01 0.01 +8.8% PASS
total/main/snailtracer/benchmark 54.25 54.83 +1.1% PASS
total/main/structarray_alloc/nfts_rank 0.32 0.32 +1.9% PASS
total/main/swap_math/insufficient_liquidity 0.02 0.02 +8.9% PASS
total/main/swap_math/received 0.02 0.02 +8.4% PASS
total/main/swap_math/spent 0.02 0.02 +9.2% PASS
total/main/weierstrudel/1 0.35 0.36 +3.0% PASS
total/main/weierstrudel/15 3.41 3.43 +0.6% PASS
total/micro/JUMPDEST_n0/empty 0.13 0.13 +0.4% PASS
total/micro/jump_around/empty 0.58 0.59 +1.1% PASS
total/micro/loop_with_many_jumpdests/empty 1.98 1.97 -0.2% PASS
total/micro/memory_grow_mload/by1 0.18 0.20 +8.4% PASS
total/micro/memory_grow_mload/by16 0.19 0.21 +8.6% PASS
total/micro/memory_grow_mload/by32 0.20 0.22 +9.9% PASS
total/micro/memory_grow_mload/nogrow 0.18 0.19 +9.3% PASS
total/micro/memory_grow_mstore/by1 0.19 0.20 +5.4% PASS
total/micro/memory_grow_mstore/by16 0.21 0.22 +6.6% PASS
total/micro/memory_grow_mstore/by32 0.22 0.24 +7.7% PASS
total/micro/memory_grow_mstore/nogrow 0.19 0.20 +5.9% PASS
total/micro/signextend/one 0.38 0.39 +1.9% PASS
total/micro/signextend/zero 0.38 0.38 +0.8% PASS
total/synth/ADD/b0 0.01 0.01 +13.3% PASS
total/synth/ADD/b1 0.01 0.01 +10.2% PASS
total/synth/ADDRESS/a0 0.16 0.16 +1.4% PASS
total/synth/ADDRESS/a1 0.16 0.16 +1.3% PASS
total/synth/AND/b0 0.01 0.01 +13.2% PASS
total/synth/AND/b1 0.01 0.01 +10.1% PASS
total/synth/BYTE/b0 2.21 2.22 +0.1% PASS
total/synth/BYTE/b1 2.57 2.57 +0.1% PASS
total/synth/CALLDATASIZE/a0 0.08 0.08 +2.7% PASS
total/synth/CALLDATASIZE/a1 0.08 0.08 +2.2% PASS
total/synth/CALLER/a0 0.16 0.16 +1.4% PASS
total/synth/CALLER/a1 0.16 0.16 +1.1% PASS
total/synth/CALLVALUE/a0 0.31 0.31 +0.5% PASS
total/synth/CALLVALUE/a1 0.32 0.32 +0.4% PASS
total/synth/CODESIZE/a0 0.08 0.08 +2.7% PASS
total/synth/CODESIZE/a1 0.08 0.08 +2.2% PASS
total/synth/DUP1/d0 0.01 0.01 +13.3% PASS
total/synth/DUP1/d1 0.01 0.01 +10.1% PASS
total/synth/DUP10/d0 0.01 0.01 +13.6% PASS
total/synth/DUP10/d1 0.01 0.01 +10.2% PASS
total/synth/DUP11/d0 0.01 0.01 +13.3% PASS
total/synth/DUP11/d1 0.01 0.01 +10.2% PASS
total/synth/DUP12/d0 0.01 0.01 +13.2% PASS
total/synth/DUP12/d1 0.01 0.01 +10.1% PASS
total/synth/DUP13/d0 0.01 0.01 +13.4% PASS
total/synth/DUP13/d1 0.01 0.01 +10.2% PASS
total/synth/DUP14/d0 0.01 0.01 +13.2% PASS
total/synth/DUP14/d1 0.01 0.01 +10.1% PASS
total/synth/DUP15/d0 0.01 0.01 +13.4% PASS
total/synth/DUP15/d1 0.01 0.01 +10.2% PASS
total/synth/DUP16/d0 0.01 0.01 +13.3% PASS
total/synth/DUP16/d1 0.01 0.01 +10.2% PASS
total/synth/DUP2/d0 0.01 0.01 +13.2% PASS
total/synth/DUP2/d1 0.01 0.01 +10.2% PASS
total/synth/DUP3/d0 0.01 0.01 +13.3% PASS
total/synth/DUP3/d1 0.01 0.01 +10.2% PASS
total/synth/DUP4/d0 0.01 0.01 +13.2% PASS
total/synth/DUP4/d1 0.01 0.01 +10.2% PASS
total/synth/DUP5/d0 0.01 0.01 +13.3% PASS
total/synth/DUP5/d1 0.01 0.01 +10.1% PASS
total/synth/DUP6/d0 0.01 0.01 +13.4% PASS
total/synth/DUP6/d1 0.01 0.01 +10.0% PASS
total/synth/DUP7/d0 0.01 0.01 +13.3% PASS
total/synth/DUP7/d1 0.01 0.01 +10.2% PASS
total/synth/DUP8/d0 0.01 0.01 +13.2% PASS
total/synth/DUP8/d1 0.01 0.01 +10.3% PASS
total/synth/DUP9/d0 0.01 0.01 +13.3% PASS
total/synth/DUP9/d1 0.01 0.01 +10.1% PASS
total/synth/EQ/b0 0.01 0.01 +13.2% PASS
total/synth/EQ/b1 0.01 0.01 +10.2% PASS
total/synth/GAS/a0 0.87 0.87 +0.3% PASS
total/synth/GAS/a1 0.87 0.87 +0.3% PASS
total/synth/GT/b0 0.01 0.01 +13.2% PASS
total/synth/GT/b1 0.01 0.01 +10.2% PASS
total/synth/ISZERO/u0 0.01 0.01 +20.4% PASS
total/synth/JUMPDEST/n0 0.13 0.13 +0.2% PASS
total/synth/LT/b0 0.01 0.01 +13.2% PASS
total/synth/LT/b1 0.01 0.01 +10.2% PASS
total/synth/MSIZE/a0 0.01 0.01 +20.3% PASS
total/synth/MSIZE/a1 0.01 0.01 +16.9% PASS
total/synth/MUL/b0 0.01 0.01 +13.3% PASS
total/synth/MUL/b1 0.01 0.01 +10.2% PASS
total/synth/NOT/u0 0.01 0.01 +20.4% PASS
total/synth/OR/b0 0.01 0.01 +13.3% PASS
total/synth/OR/b1 0.01 0.01 +10.3% PASS
total/synth/PC/a0 0.01 0.01 +20.4% PASS
total/synth/PC/a1 0.01 0.01 +16.9% PASS
total/synth/PUSH1/p0 0.01 0.01 +17.3% PASS
total/synth/PUSH1/p1 0.01 0.01 +16.2% PASS
total/synth/PUSH10/p0 0.01 0.01 +17.4% PASS
total/synth/PUSH10/p1 0.01 0.01 +16.7% PASS
total/synth/PUSH11/p0 0.01 0.01 +17.3% PASS
total/synth/PUSH11/p1 0.01 0.01 +16.6% PASS
total/synth/PUSH12/p0 0.01 0.01 +17.4% PASS
total/synth/PUSH12/p1 0.01 0.01 +16.6% PASS
total/synth/PUSH13/p0 0.01 0.01 +17.3% PASS
total/synth/PUSH13/p1 0.01 0.01 +16.4% PASS
total/synth/PUSH14/p0 0.01 0.01 +17.2% PASS
total/synth/PUSH14/p1 0.01 0.01 +16.8% PASS
total/synth/PUSH15/p0 0.01 0.01 +17.4% PASS
total/synth/PUSH15/p1 0.01 0.01 +16.9% PASS
total/synth/PUSH16/p0 0.01 0.01 +17.3% PASS
total/synth/PUSH16/p1 0.01 0.01 +16.9% PASS
total/synth/PUSH17/p0 0.01 0.01 +17.5% PASS
total/synth/PUSH17/p1 0.01 0.01 +15.9% PASS
total/synth/PUSH18/p0 0.01 0.01 +17.4% PASS
total/synth/PUSH18/p1 0.01 0.01 +16.8% PASS
total/synth/PUSH19/p0 0.01 0.01 +17.4% PASS
total/synth/PUSH19/p1 0.01 0.01 +17.1% PASS
total/synth/PUSH2/p0 0.01 0.01 +17.4% PASS
total/synth/PUSH2/p1 0.01 0.01 +16.4% PASS
total/synth/PUSH20/p0 0.01 0.01 +17.5% PASS
total/synth/PUSH20/p1 0.01 0.01 +17.1% PASS
total/synth/PUSH21/p0 0.01 0.01 +17.4% PASS
total/synth/PUSH21/p1 0.01 0.01 +17.0% PASS
total/synth/PUSH22/p0 0.91 0.90 -1.0% PASS
total/synth/PUSH22/p1 1.22 1.14 -5.9% PASS
total/synth/PUSH23/p0 0.94 0.90 -4.5% PASS
total/synth/PUSH23/p1 1.21 1.13 -6.6% PASS
total/synth/PUSH24/p0 0.92 0.90 -2.1% PASS
total/synth/PUSH24/p1 1.22 1.13 -7.6% PASS
total/synth/PUSH25/p0 0.94 0.90 -4.0% PASS
total/synth/PUSH25/p1 1.23 1.14 -7.7% PASS
total/synth/PUSH26/p0 0.94 0.90 -4.7% PASS
total/synth/PUSH26/p1 1.24 1.16 -6.2% PASS
total/synth/PUSH27/p0 0.94 0.90 -3.5% PASS
total/synth/PUSH27/p1 1.27 1.13 -11.1% PASS
total/synth/PUSH28/p0 0.94 0.91 -3.3% PASS
total/synth/PUSH28/p1 1.23 1.20 -2.3% PASS
total/synth/PUSH29/p0 0.92 0.90 -1.4% PASS
total/synth/PUSH29/p1 1.22 1.13 -7.4% PASS
total/synth/PUSH3/p0 0.01 0.01 +17.3% PASS
total/synth/PUSH3/p1 0.01 0.01 +16.4% PASS
total/synth/PUSH30/p0 0.94 1.05 +12.4% PASS
total/synth/PUSH30/p1 1.24 1.13 -8.3% PASS
total/synth/PUSH31/p0 0.91 0.90 -0.6% PASS
total/synth/PUSH31/p1 1.26 1.19 -5.6% PASS
total/synth/PUSH32/p0 0.90 0.98 +8.3% PASS
total/synth/PUSH32/p1 1.24 1.21 -2.5% PASS
total/synth/PUSH4/p0 0.01 0.01 +17.5% PASS
total/synth/PUSH4/p1 0.01 0.01 +16.3% PASS
total/synth/PUSH5/p0 0.01 0.01 +17.5% PASS
total/synth/PUSH5/p1 0.01 0.01 +16.4% PASS
total/synth/PUSH6/p0 0.01 0.01 +17.3% PASS
total/synth/PUSH6/p1 0.01 0.01 +16.3% PASS
total/synth/PUSH7/p0 0.01 0.01 +17.4% PASS
total/synth/PUSH7/p1 0.01 0.01 +16.4% PASS
total/synth/PUSH8/p0 0.01 0.01 +17.3% PASS
total/synth/PUSH8/p1 0.01 0.01 +16.7% PASS
total/synth/PUSH9/p0 0.01 0.01 +17.6% PASS
total/synth/PUSH9/p1 0.01 0.01 +16.7% PASS
total/synth/RETURNDATASIZE/a0 0.04 0.04 +5.3% PASS
total/synth/RETURNDATASIZE/a1 0.04 0.04 +4.4% PASS
total/synth/SAR/b0 4.35 4.36 +0.3% PASS
total/synth/SAR/b1 4.83 4.82 -0.3% PASS
total/synth/SGT/b0 0.01 0.01 +13.3% PASS
total/synth/SGT/b1 0.01 0.01 +10.5% PASS
total/synth/SHL/b0 3.51 3.51 +0.0% PASS
total/synth/SHL/b1 1.68 1.68 +0.1% PASS
total/synth/SHR/b0 3.53 3.53 +0.0% PASS
total/synth/SHR/b1 1.68 1.63 -2.5% PASS
total/synth/SIGNEXTEND/b0 3.68 3.36 -8.8% PASS
total/synth/SIGNEXTEND/b1 3.65 3.39 -7.3% PASS
total/synth/SLT/b0 0.01 0.01 +13.2% PASS
total/synth/SLT/b1 0.01 0.01 +10.2% PASS
total/synth/SUB/b0 0.01 0.01 +13.3% PASS
total/synth/SUB/b1 0.01 0.01 +10.2% PASS
total/synth/SWAP1/s0 0.01 0.01 +6.9% PASS
total/synth/SWAP10/s0 0.01 0.01 +6.9% PASS
total/synth/SWAP11/s0 0.01 0.01 +6.8% PASS
total/synth/SWAP12/s0 0.01 0.01 +6.9% PASS
total/synth/SWAP13/s0 0.01 0.01 +6.8% PASS
total/synth/SWAP14/s0 0.01 0.01 +7.0% PASS
total/synth/SWAP15/s0 0.01 0.01 +6.9% PASS
total/synth/SWAP16/s0 0.01 0.01 +6.9% PASS
total/synth/SWAP2/s0 0.01 0.01 +6.8% PASS
total/synth/SWAP3/s0 0.01 0.01 +6.9% PASS
total/synth/SWAP4/s0 0.01 0.01 +6.8% PASS
total/synth/SWAP5/s0 0.01 0.01 +6.8% PASS
total/synth/SWAP6/s0 0.01 0.01 +6.9% PASS
total/synth/SWAP7/s0 0.01 0.01 +6.9% PASS
total/synth/SWAP8/s0 0.01 0.01 +7.0% PASS
total/synth/SWAP9/s0 0.01 0.01 +6.9% PASS
total/synth/XOR/b0 0.01 0.01 +13.3% PASS
total/synth/XOR/b1 0.01 0.01 +10.2% PASS
total/synth/loop_v1 1.45 1.44 -0.4% PASS
total/synth/loop_v2 1.28 1.29 +1.0% PASS

Summary: 194 benchmarks, 0 regressions


@zoowii zoowii requested a review from Copilot March 19, 2026 10:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an EVM “lazy compile” mode (implemented as Multipass JIT + a lazy flag) and adds initial infrastructure for segment-based compilation (bytecode segmentation at JUMPDEST boundaries), along with basic unit tests.

Changes:

  • Add lazy mode handling and a new DTVM_EVM_LAZY_COMPILE env var / enable_lazy_compile EVMC option to toggle Multipass lazy compilation.
  • Implement LazyEVMJITCompiler + EVMSegmentAnalyzer scaffolding and wire lazy compilation into performEVMJITCompile().
  • Add a new evmLazyCompileTests test target with segment analyzer coverage.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/vm/dt_evmc_vm.cpp Adds lazy mode parsing and env/option toggles for lazy compilation.
src/action/compiler.cpp Switches Multipass+lazy EVM compilation path to use LazyEVMJITCompiler.
src/runtime/evm_module.h Adds storage/accessors for a per-module LazyEVMJITCompiler.
src/runtime/evm_module.cpp Implements EVMModule::newLazyEVMJITCompiler().
src/compiler/evm_lazy_compiler.h Declares EVMSegmentAnalyzer and LazyEVMJITCompiler APIs.
src/compiler/evm_lazy_compiler.cpp Implements segment analysis and the initial “lazy” compiler behavior.
src/compiler/CMakeLists.txt Adds evm_lazy_compiler.cpp to the compiler library sources.
src/tests/evm_lazy_compile_tests.cpp Adds unit tests for the segment analyzer.
src/tests/CMakeLists.txt Builds/runs the new evmLazyCompileTests when Multipass JIT is enabled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/tests/evm_lazy_compile_tests.cpp Outdated
Comment on lines +163 to +168
// Bytecode: JUMPDEST at PC=3
std::vector<uint8_t> Bytecode = {
0x60, 0x01, // PUSH1 0x01 (PC 0-1)
0x60, 0x02, // PUSH1 0x02 (PC 2-3)
0x5B, // JUMPDEST (PC 4)
0x00 // STOP (PC 5)
Comment thread src/compiler/evm_lazy_compiler.cpp Outdated
Comment on lines +212 to +226
if (SegmentIdx < SegmentAnalyzer.getNumSegments() &&
CompileStatuses[SegmentIdx] == CompileStatus::Done) {
return SegmentCodePtrs[SegmentIdx];
}

// Fallback: compile the full bytecode if not yet done
std::lock_guard<std::mutex> Lock(ForegroundMutex);

auto Timer =
Stats.startRecord(zen::utils::StatisticPhase::JITLazyFgCompilation);

// Re-check after acquiring lock
if (CompileStatuses[SegmentIdx] == CompileStatus::Done) {
Stats.stopRecord(Timer);
return SegmentCodePtrs[SegmentIdx];
Comment thread src/compiler/evm_lazy_compiler.h Outdated
Comment on lines +61 to +78
/// Instead of compiling the entire EVM bytecode at once, this compiler:
/// 1. Analyzes the bytecode to identify segments (JUMPDEST boundaries)
/// 2. Compiles only the entry segment initially
/// 3. Creates stubs for other segments that trigger on-demand compilation
/// 4. Optionally dispatches background compilation tasks for remaining segments
///
/// This is analogous to the WASM LazyJITCompiler but adapted for EVM's
/// single-function, segment-based structure.
class LazyEVMJITCompiler final : public EVMJITCompiler {
public:
LazyEVMJITCompiler(runtime::EVMModule *EVMMod);
~LazyEVMJITCompiler() override;

/// Perform initial precompilation:
/// - Analyze bytecode into segments
/// - Compile the entry segment eagerly
/// - Create stubs for remaining segments
/// - Optionally dispatch background compilation tasks
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an initial implementation of an EVM “lazy compile” mode intended to support segment-based (JUMPDEST-delimited) compilation in the multipass JIT pipeline.

Changes:

  • Add lazy mode / EnableMultipassLazy toggles for the EVMC VM (options + env var).
  • Introduce EVMSegmentAnalyzer and LazyEVMJITCompiler, and wire it into EVM JIT compilation selection.
  • Add a new evmLazyCompileTests target with unit tests for the segment analyzer.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/vm/dt_evmc_vm.cpp Adds EVMC options/env var to enable lazy compilation mode.
src/action/compiler.cpp Switches EVM multipass compilation to use LazyEVMJITCompiler when enabled.
src/runtime/evm_module.h Stores/exports a lazy EVM JIT compiler instance on EVMModule.
src/runtime/evm_module.cpp Implements EVMModule::newLazyEVMJITCompiler().
src/compiler/evm_lazy_compiler.h Declares segment analyzer + lazy EVM JIT compiler APIs.
src/compiler/evm_lazy_compiler.cpp Implements segment analysis and the lazy compiler (currently eager-full compile).
src/compiler/CMakeLists.txt Adds evm_lazy_compiler.cpp to compiler sources when EVM is enabled.
src/tests/evm_lazy_compile_tests.cpp Adds segment analyzer unit tests.
src/tests/CMakeLists.txt Builds/runs evmLazyCompileTests when ZEN_ENABLE_MULTIPASS_JIT is enabled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/runtime/evm_module.h
Comment on lines 83 to 88
#ifdef ZEN_ENABLE_JIT
common::CodeMemPool JITCodeMemPool;
void *JITCode = nullptr;
size_t JITCodeSize = 0;
std::unique_ptr<COMPILER::LazyEVMJITCompiler> LazyJITCompiler;
#endif // ZEN_ENABLE_JIT
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LazyJITCompiler is declared as std::unique_ptr<COMPILER::LazyEVMJITCompiler> under ZEN_ENABLE_JIT, but ZEN_ENABLE_JIT is defined for both singlepass and multipass builds (see src/CMakeLists.txt). In a singlepass-only build, LazyEVMJITCompiler is only forward-declared here, so EVMModule’s destructor will need a complete type and this will not compile. Guard the lazy-compiler member (and related APIs) with ZEN_ENABLE_MULTIPASS_JIT (or provide a custom deleter / include the full definition whenever ZEN_ENABLE_JIT is set).

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +69
/// Lazy JIT compiler for EVM bytecode.
///
/// Instead of compiling the entire EVM bytecode at once, this compiler:
/// 1. Analyzes the bytecode to identify segments (JUMPDEST boundaries)
/// 2. Compiles only the entry segment initially
/// 3. Creates stubs for other segments that trigger on-demand compilation
/// 4. Optionally dispatches background compilation tasks for remaining segments
///
/// This is analogous to the WASM LazyJITCompiler but adapted for EVM's
/// single-function, segment-based structure.
class LazyEVMJITCompiler final : public EVMJITCompiler {
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header comment describes stub-based, segment-level lazy compilation (entry-only compile + on-demand compilation for other segments), but precompile() currently compiles the full bytecode eagerly and marks all segments Done. Either update the documentation to match the current behavior or implement the described stub/on-demand compilation so callers aren’t misled about what “lazy compile” does.

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +45
EVMSegmentAnalyzer Analyzer;
Analyzer.analyze(reinterpret_cast<const std::byte *>(Bytecode.data()),
Bytecode.size(), EVMC_OSAKA);

Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests pass reinterpret_cast<const std::byte*> into APIs expecting zen::common::Byte*. This will break when ZEN_DISABLE_CXX17_STL is enabled (where zen::common::Byte is libcxx::byte, not std::byte). Prefer using reinterpret_cast<const zen::common::Byte*> (or a helper) so the tests compile in both configurations.

Copilot uses AI. Check for mistakes.
Comment thread src/compiler/evm_lazy_compiler.cpp Outdated
Comment on lines +237 to +241
EVMMod->setJITCodeAndSize(CodePtr, MainContext->CodeSize);

size_t CodeSize = CodeMPool.getMemEnd() - JITCode;
zen::platform::mprotect(JITCode, TO_MPROTECT_CODE_SIZE_LAZY(CodeSize),
PROT_READ | PROT_EXEC);
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the fallback path of compileSegmentOnRequest(), after mprotect() you never call EVMMod->setJITCodeAndSize(JITCode, CodeSize) (unlike precompile() and EagerEVMJITCompiler). This leaves JITCode/JITCodeSize potentially inconsistent (e.g., trap handler code-range checks) depending on what CodePtr/MainContext->CodeSize are. Consider mirroring the precompile()/eager sequence: set entry pointer/size if needed, then set module JIT code to the pool start + protected size.

Suggested change
EVMMod->setJITCodeAndSize(CodePtr, MainContext->CodeSize);
size_t CodeSize = CodeMPool.getMemEnd() - JITCode;
zen::platform::mprotect(JITCode, TO_MPROTECT_CODE_SIZE_LAZY(CodeSize),
PROT_READ | PROT_EXEC);
size_t CodeSize = CodeMPool.getMemEnd() - JITCode;
zen::platform::mprotect(JITCode, TO_MPROTECT_CODE_SIZE_LAZY(CodeSize),
PROT_READ | PROT_EXEC);
EVMMod->setJITCodeAndSize(JITCode, CodeSize);

Copilot uses AI. Check for mistakes.
Comment thread src/runtime/evm_module.h
Comment on lines +61 to +67
COMPILER::LazyEVMJITCompiler *newLazyEVMJITCompiler();

COMPILER::LazyEVMJITCompiler *getLazyEVMJITCompiler() const {
return LazyJITCompiler.get();
}

bool hasLazyJITCompiler() const { return LazyJITCompiler != nullptr; }
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newLazyEVMJITCompiler() is declared under ZEN_ENABLE_JIT but defined in evm_module.cpp only under ZEN_ENABLE_MULTIPASS_JIT. To avoid mismatched feature-guards (and the incomplete-type issue from the lazy compiler member), put this API under the same ZEN_ENABLE_MULTIPASS_JIT guard as its implementation (or provide a definition for all ZEN_ENABLE_JIT builds).

Copilot uses AI. Check for mistakes.
Comment thread src/vm/dt_evmc_vm.cpp
Comment on lines 232 to +238
} else if (std::strcmp(Value, "multipass") == 0) {
VM->Config.Mode = RunMode::MultipassMode;
return EVMC_SET_OPTION_SUCCESS;
} else if (std::strcmp(Value, "lazy") == 0) {
VM->Config.Mode = RunMode::MultipassMode;
VM->Config.EnableMultipassLazy = true;
return EVMC_SET_OPTION_SUCCESS;
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Selecting mode=multipass does not clear EnableMultipassLazy. If the VM was previously configured with mode=lazy (or enable_lazy_compile=true), switching back to multipass will still run in lazy mode. Consider explicitly setting Config.EnableMultipassLazy = false in the multipass (and interpreter) branches so the mode option is self-contained and deterministic.

Copilot uses AI. Check for mistakes.
Comment on lines +209 to +227
uint8_t *LazyEVMJITCompiler::compileSegmentOnRequest(uint32_t SegmentIdx) {
// In the current implementation, all segments are compiled during precompile.
// This method is a placeholder for future segment-level lazy compilation.
if (SegmentIdx < SegmentAnalyzer.getNumSegments() &&
CompileStatuses[SegmentIdx] == CompileStatus::Done) {
return SegmentCodePtrs[SegmentIdx];
}

// Fallback: compile the full bytecode if not yet done
std::lock_guard<std::mutex> Lock(ForegroundMutex);

auto Timer =
Stats.startRecord(zen::utils::StatisticPhase::JITLazyFgCompilation);

// Re-check after acquiring lock
if (CompileStatuses[SegmentIdx] == CompileStatus::Done) {
Stats.stopRecord(Timer);
return SegmentCodePtrs[SegmentIdx];
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compileSegmentOnRequest() can read out of bounds / dereference null: after the initial bounds check, the code unconditionally evaluates CompileStatuses[SegmentIdx] again (and later writes it) even when SegmentIdx >= getNumSegments() or when precompile() was never called (leaving CompileStatuses null). Add an early guard that validates SegmentIdx < getNumSegments() and that per-segment state has been initialized (or make this function unreachable before precompile()).

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +45
// Get instruction metrics for opcode length calculation
const auto *Metrics = evmc_get_instruction_metrics_table(Rev);
if (!Metrics) {
Metrics = evmc_get_instruction_metrics_table(zen::evm::DEFAULT_REVISION);
}

Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics is computed but never used. If opcode-length calculation only needs PUSH handling, remove the unused metrics lookup; otherwise, use Metrics to advance PC safely/consistently for non-PUSH opcodes.

Suggested change
// Get instruction metrics for opcode length calculation
const auto *Metrics = evmc_get_instruction_metrics_table(Rev);
if (!Metrics) {
Metrics = evmc_get_instruction_metrics_table(zen::evm::DEFAULT_REVISION);
}

Copilot uses AI. Check for mistakes.
Comment thread src/tests/evm_lazy_compile_tests.cpp Outdated
Comment on lines +163 to +169
// Bytecode: JUMPDEST at PC=3
std::vector<uint8_t> Bytecode = {
0x60, 0x01, // PUSH1 0x01 (PC 0-1)
0x60, 0x02, // PUSH1 0x02 (PC 2-3)
0x5B, // JUMPDEST (PC 4)
0x00 // STOP (PC 5)
};
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says “JUMPDEST at PC=3” but with two PUSH1 instructions (2 bytes each) the JUMPDEST is actually at PC=4. Please fix the comment to match the bytecode layout to avoid confusion when maintaining these tests.

Copilot uses AI. Check for mistakes.
@ys8888john ys8888john force-pushed the evm-lazy-compile branch 3 times, most recently from ece08ab to 1512729 Compare March 23, 2026 13:01
@zoowii zoowii requested a review from Copilot March 23, 2026 13:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +243 to +244
EVMMod->setJITCodeAndSize(EntryCodePtr, 0);

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

precompile() sets EVMModule JIT code size to 0. The runtime/trap handlers use getJITCodeSize() to compute the JIT address range (e.g. for backtraces), so a zero size breaks that logic even though the entry stub is callable. Consider setting JITCode to the code pool start (or entry stub, if it is the pool start) and JITCodeSize to CodeMemPool.getMemEnd() - getMemStart() after stub/resolver allocation, and updating it again after the first compilation when the pool grows.

Suggested change
EVMMod->setJITCodeAndSize(EntryCodePtr, 0);
// Initialize JIT code size based on the code memory pool so that
// runtime/trap handlers see a valid JIT address range (for backtraces, etc.)
size_t JITSize = 0;
if (MainContext && MainContext->CodeMPool) {
void *MemStart = MainContext->CodeMPool->getMemStart();
void *MemEnd = MainContext->CodeMPool->getMemEnd();
if (MemStart && MemEnd && MemEnd > MemStart) {
auto *Start = static_cast<uint8_t *>(MemStart);
auto *End = static_cast<uint8_t *>(MemEnd);
JITSize = static_cast<size_t>(End - Start);
}
}
EVMMod->setJITCodeAndSize(EntryCodePtr, JITSize);

Copilot uses AI. Check for mistakes.
Comment on lines +248 to +266
uint8_t *LazyEVMJITCompiler::compileSegmentOnRequest(uint32_t SegmentIdx) {
uint32_t NumSegments = SegmentAnalyzer.getNumSegments();

// Fast path: already compiled
if (SegmentIdx < NumSegments &&
CompileStatuses[SegmentIdx] == CompileStatus::Done) {
return SegmentCodePtrs[SegmentIdx];
}

// Slow path: compile on demand with thread safety
std::lock_guard<std::mutex> Lock(ForegroundMutex);

auto Timer =
Stats.startRecord(zen::utils::StatisticPhase::JITLazyFgCompilation);

// Double-check after acquiring lock
if (CompileStatuses[SegmentIdx] == CompileStatus::Done) {
Stats.stopRecord(Timer);
return SegmentCodePtrs[SegmentIdx];
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compileSegmentOnRequest() uses CompileStatuses[SegmentIdx] without validating SegmentIdx < NumSegments after taking the lock. If an invalid segment index is ever passed (e.g. corrupted stub address), this is out-of-bounds UB. Add a bounds check early in the slow path and return/abort with an error if SegmentIdx is invalid.

Copilot uses AI. Check for mistakes.
Comment thread src/vm/dt_evmc_vm.cpp
EnableLazy != nullptr) {
bool ParsedEnableLazy = false;
if (parseBoolEnvValue(EnableLazy, ParsedEnableLazy)) {
Config.EnableMultipassLazy = ParsedEnableLazy;
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DTVM_EVM_LAZY_COMPILE can enable EnableMultipassLazy without switching Config.Mode to MultipassMode, even though the option comment says lazy compile requires multipass. To avoid confusing/partial configurations, consider forcing Config.Mode = RunMode::MultipassMode when the env var parses to true (mirroring set_option(enable_lazy_compile)).

Suggested change
Config.EnableMultipassLazy = ParsedEnableLazy;
Config.EnableMultipassLazy = ParsedEnableLazy;
if (ParsedEnableLazy) {
Config.Mode = RunMode::MultipassMode;
}

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +72
static uint64_t
compileSegmentOnRequestTrampoline([[maybe_unused]] zen::runtime::Instance *Inst,
uint8_t *NextStubCodePtr) {
// Validate instance pointer
if (!Inst) {
ZEN_LOG_ERROR("EVM lazy compile: Instance pointer is null in trampoline");
return 0;
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the trampoline error paths you return 0, but the stub resolver uses the return value as the re-entry address (it overwrites the saved RIP). Returning 0 will jump to address 0 and crash. If these conditions are truly impossible, prefer ZEN_ASSERT/ZEN_ABORT; otherwise return a valid failure stub/throw an exception so control flow can’t continue with a null target.

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +70
uint32_t SegmentStartPC = 0;
uint32_t SegmentEndPC = static_cast<uint32_t>(BytecodeSize);
const EVMFrontendContext *EvmCtx =
dynamic_cast<const EVMFrontendContext *>(Ctx);
if (EvmCtx) {
SegmentStartPC = EvmCtx->getSegmentStartPC();
SegmentEndPC = EvmCtx->getSegmentEndPC();
if (SegmentEndPC == UINT32_MAX ||
SegmentEndPC > static_cast<uint32_t>(BytecodeSize)) {
SegmentEndPC = static_cast<uint32_t>(BytecodeSize);
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dynamic_cast requires RTTI, but the project is built with -fno-rtti (see top-level CMakeLists). This will fail to compile. Since Ctx in the EVM JIT pipeline is an EVMFrontendContext, use a static_cast (or add an explicit kind/tag on CompileContext) instead of dynamic_cast.

Suggested change
uint32_t SegmentStartPC = 0;
uint32_t SegmentEndPC = static_cast<uint32_t>(BytecodeSize);
const EVMFrontendContext *EvmCtx =
dynamic_cast<const EVMFrontendContext *>(Ctx);
if (EvmCtx) {
SegmentStartPC = EvmCtx->getSegmentStartPC();
SegmentEndPC = EvmCtx->getSegmentEndPC();
if (SegmentEndPC == UINT32_MAX ||
SegmentEndPC > static_cast<uint32_t>(BytecodeSize)) {
SegmentEndPC = static_cast<uint32_t>(BytecodeSize);
}
const EVMFrontendContext *EvmCtx =
static_cast<const EVMFrontendContext *>(Ctx);
uint32_t SegmentStartPC = EvmCtx->getSegmentStartPC();
uint32_t SegmentEndPC = EvmCtx->getSegmentEndPC();
if (SegmentEndPC == UINT32_MAX ||
SegmentEndPC > static_cast<uint32_t>(BytecodeSize)) {
SegmentEndPC = static_cast<uint32_t>(BytecodeSize);

Copilot uses AI. Check for mistakes.
Co-authored-by: Aone Copilot <noreply@alibaba-inc.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +955 to +956
dynamic_cast<const EVMFrontendContext *>(&Ctx);
if (IsImplicit && EvmCtx && EvmCtx->Lazy) {
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dynamic_cast requires RTTI, but the project is built with -fno-rtti (see top-level CMakeLists). This will not compile. Since EVMMirBuilder is already tied to EVMFrontendContext, replace this with a static_cast<const EVMFrontendContext*>(&Ctx) (or equivalent) and remove RTTI usage.

Suggested change
dynamic_cast<const EVMFrontendContext *>(&Ctx);
if (IsImplicit && EvmCtx && EvmCtx->Lazy) {
static_cast<const EVMFrontendContext *>(&Ctx);
if (IsImplicit && EvmCtx->Lazy) {

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +83
const EVMFrontendContext *EvmCtx =
dynamic_cast<const EVMFrontendContext *>(Ctx);
if (EvmCtx) {
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dynamic_cast requires RTTI, but the build uses -fno-rtti, so this will fail to compile. If this visitor is only used with EVMFrontendContext, use a static_cast (and optionally an assert) instead of RTTI-based casting.

Copilot uses AI. Check for mistakes.
SmallVector<CgOperand, 2> JumpOperands;
JumpOperands.push_back(CgOperand::createRegOperand(TargetCodeReg));
JumpOperands.push_back(
CgOperand::createRegOperand(X86::RDI, /*isDef=*/false));
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says RDI is added as an implicit use, but it is created as an explicit operand (IsImplicit=false by default). X86MCInstLower::lowerMachineOperand will then emit it into the MCInst, and after you lower TAILJMPr64 to JMP64r this extra operand can break encoding / operand count expectations. Mark the RDI operand as implicit (or use the RegState::Implicit flag) so it participates in liveness but is not encoded.

Suggested change
CgOperand::createRegOperand(X86::RDI, /*isDef=*/false));
CgOperand::createRegOperand(X86::RDI, /*isDef=*/false,
/*isImplicit=*/true));

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +29
// -5 because the jmp instructions has 5 bytes
int64_t CallRelOffset = TargetPtr - CurStubCodePtr - 5;
ZEN_ASSERT(CallRelOffset <= UINT32_MAX);
int32_t CallRelOffsetI32 = static_cast<int32_t>(CallRelOffset);

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relative JMP rel32 offsets can be negative and must fit in a signed 32-bit range. The current check CallRelOffset <= UINT32_MAX is ineffective for negative offsets (it will always pass) and does not prevent values < INT32_MIN from overflowing when cast to int32_t. Validate INT32_MIN <= CallRelOffset && CallRelOffset <= INT32_MAX before patching.

Copilot uses AI. Check for mistakes.
Comment on lines +176 to +183
// not from the call instruction itself. Since call is 5 bytes (E8 + 4-byte
// offset), we subtract 5 to get the correct relative offset.
int64_t CallRelOffset = StubResolverPtr - NewStubTmplPatchPointPtr - 5;
ZEN_ASSERT(CallRelOffset <= UINT32_MAX);

// StubResolver not too far, use call(0xe8) offset
int32_t CallRelOffsetI32 = static_cast<int32_t>(CallRelOffset);
std::memcpy(NewStubTmplPatchPointPtr + 1, &CallRelOffsetI32, 4);
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above: the RIP-relative call offset is signed 32-bit. CallRelOffset <= UINT32_MAX does not correctly validate range and allows negative offsets that overflow int32_t. Validate against INT32_MIN..INT32_MAX (and consider handling the out-of-range case rather than asserting in release builds).

Copilot uses AI. Check for mistakes.
Comment on lines +284 to +291
// Allocate stub space and compile resolver
StubBuilder->allocateStubSpace(NumSegments);
StubBuilder->compileStubResolver(this);

// Create stubs for all segments
for (const auto &Seg : AnalyzerSegments) {
StubBuilder->compileSegmentToStub(Seg.SegmentIdx, Seg.StartPC);
uint8_t *StubPtr = StubBuilder->getSegmentStubCodePtr(Seg.SegmentIdx);
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allocateStubSpace() can fail and returns early leaving StubsCodePtr null, but precompile() continues and unconditionally calls compileSegmentToStub(), which will pointer-arithmetic on a null base and likely crash. Propagate allocation failure (e.g., make allocateStubSpace() return bool) and abort lazy setup if stub allocation fails.

Copilot uses AI. Check for mistakes.
Comment on lines +117 to +119
// In lazy mode, gas chunks must not cross segment boundaries.
// UINT32_MAX means no segment boundary restriction (eager mode).
uint32_t LazySegmentEndPC = UINT32_MAX;
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LazySegmentEndPC is introduced in EVMFrontendContext but is not referenced anywhere (gas-chunk boundary checking uses EVMMirBuilder::LazySegmentEndPC). This looks like dead state and increases confusion about which field is authoritative. Either wire this field into the logic (with accessors and correct initialization/copy) or remove it.

Suggested change
// In lazy mode, gas chunks must not cross segment boundaries.
// UINT32_MAX means no segment boundary restriction (eager mode).
uint32_t LazySegmentEndPC = UINT32_MAX;

Copilot uses AI. Check for mistakes.
Comment on lines +757 to +765
// Check if we're in lazy compilation mode (segment doesn't start at PC=0)
// In lazy mode, we skip stack check for the entry block to avoid creating
// control flow edges that could make entry block a loop header
const EVMFrontendContext *EvmCtx =
static_cast<const EVMFrontendContext *>(&Ctx);
if (EvmCtx && EvmCtx->getSegmentStartPC() != 0) {
// In lazy compilation mode, skip stack check
return;
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says “skip stack check for the entry block”, but the condition actually skips stack checks when segmentStartPC != 0 (i.e., for non-entry segments). If the intent is to skip checks for non-entry segments, please fix the comment; if the intent is different, adjust the condition accordingly.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants