Skip to content

fix(evm): cache fallback decisions and narrow JIT suitability thresholds#357

Open
starwarfan wants to merge 4 commits intoDTVMStack:mainfrom
starwarfan:adjust-fallback-cond
Open

fix(evm): cache fallback decisions and narrow JIT suitability thresholds#357
starwarfan wants to merge 4 commits intoDTVMStack:mainfrom
starwarfan:adjust-fallback-cond

Conversation

@starwarfan
Copy link
Contributor

Two bugs in the ZEN_ENABLE_JIT_PRECOMPILE_FALLBACK mechanism:

  1. Per-call analyzer overhead: EVMAnalyzer was constructed on every execute() call, rebuilding a std::map over the full bytecode each time. For benchmarks running thousands of iterations this added ~245us per call even when no fallback was needed. Fix: cache the ShouldFallback boolean per module key (CRC32 + revision) in a new FallbackCache map on the DTVM VM struct.

  2. Over-conservative fallback thresholds: the BytecodeSize and MirEstimate conditions caused false positives on real contracts (snailtracer regressed 22x) and harmless synth benchmarks (PUSH22-32, MUL/b1). The 5 actually-pathological cases (MUL/b0, SIGNEXTEND/b1, SHL/b1, SHR/b1, SAR/b1) are all caught precisely by MaxConsecutiveExpensive > 128 and MaxBlockExpensiveCount > 256 alone (they each contain ~2000 RA-expensive ops in a single basic block). Remove BytecodeSize and MirEstimate from the fallback condition; keep only pattern-based thresholds.

Result: 221 benchmarks complete with no hangs; 207/216 common cases remain within 20% of the no-fallback JIT reference.

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

Copy link
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 improves the EVM JIT precompile fallback mechanism by caching per-bytecode fallback decisions in the EVMC VM and refining the analyzer’s fallback criteria to reduce false positives while preserving protection against pathological compilation cases.

Changes:

  • Add a per-module FallbackCache (CRC32 + revision key) to avoid re-running EVMAnalyzer on repeated executions.
  • Adjust JIT suitability thresholds by removing MIR-estimate-based fallback and increasing the bytecode-size threshold; keep pattern-based RA-expensive heuristics.

Reviewed changes

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

File Description
src/vm/dt_evmc_vm.cpp Adds FallbackCache and uses it in execute() to avoid repeated analyzer work.
src/compiler/evm_frontend/evm_analyzer.h Updates bytecode-size threshold and simplifies fallback verdict to pattern-based checks plus a size guard.

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

Comment on lines +82 to +84
#ifdef ZEN_ENABLE_JIT_PRECOMPILE_FALLBACK
std::unordered_map<uint64_t, bool> FallbackCache;
#endif
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

FallbackCache grows monotonically for the lifetime of the VM and is never pruned. In long-running processes that execute many distinct contracts (or untrusted inputs), this can become an unbounded memory growth vector. Consider bounding the cache (e.g., LRU / max entries) or clearing entries alongside module unload/retention policy.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. In practice the cache is bounded by the number of distinct (CRC32, revision) pairs seen across EVMC execute() calls — for normal EVM usage (one VM per node), the set of deployed contract hashes is finite and modest. For long-running fuzz harnesses that synthesize many unique bytecode blobs this could matter, so adding a max-entries eviction policy is sensible. Will track as a follow-up optimization.

Comment on lines +117 to +119
/// Bytecode size limit: 64 KB is well above the EIP-170 deployed-code limit
/// (24 576) and the EIP-3860 initcode limit (49 152), so real-world contracts
/// are never affected, while pathological synthetic tests (400 KB+) are caught.
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

The comment claims only “pathological synthetic tests (400 KB+)” are caught by this bytecode-size guard, but the threshold is 0x10000 (64 KiB), so it will also catch any bytecode larger than 64 KiB. Please adjust the wording to match the actual cutoff (or adjust the cutoff if 400+ KiB was the intent).

Suggested change
/// Bytecode size limit: 64 KB is well above the EIP-170 deployed-code limit
/// (24 576) and the EIP-3860 initcode limit (49 152), so real-world contracts
/// are never affected, while pathological synthetic tests (400 KB+) are caught.
/// Bytecode size limit: 64 KiB is well above the EIP-170 deployed-code limit
/// (24 576) and the EIP-3860 initcode limit (49 152), so real-world contracts
/// are never affected, while oversized synthetic tests above this threshold
/// (64 KiB+) are caught.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right — the comment says "400 KB+" but the actual threshold is 0x10000 = 64 KiB. Accepted the suggested wording fix. Will update the comment to accurately reflect the 64 KiB cutoff.

Comment on lines 318 to 321
JITResult.ShouldFallback =
BytecodeSize > MAX_JIT_BYTECODE_SIZE ||
JITResult.MirEstimate > MAX_JIT_MIR_ESTIMATE ||
JITResult.BytecodeSize > MAX_JIT_BYTECODE_SIZE ||
JITResult.MaxConsecutiveExpensive > MAX_CONSECUTIVE_RA_EXPENSIVE ||
JITResult.MaxBlockExpensiveCount > MAX_BLOCK_RA_EXPENSIVE ||
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

The PR description says the bytecode-size guard was removed from the fallback condition, but ShouldFallback still checks JITResult.BytecodeSize > MAX_JIT_BYTECODE_SIZE. Either update the PR description to reflect the retained size guard, or remove the size guard here if that’s the intended behavior change.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. The bytecode size guard is intentionally retained — it was the MIR estimate check that was removed from the fallback condition (since the pattern-based thresholds are more targeted). Will clarify the PR description to accurately reflect that only the MIR-estimate threshold was dropped while the bytecode-size guard remains.

/// (24 576) and the EIP-3860 initcode limit (49 152), so real-world contracts
/// are never affected, while pathological synthetic tests (400 KB+) are caught.
static constexpr size_t MAX_JIT_BYTECODE_SIZE = 0x10000;
static constexpr size_t MAX_JIT_MIR_ESTIMATE = 50000;
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

MAX_JIT_MIR_ESTIMATE remains defined (and MirEstimate is still computed) but it no longer influences ShouldFallback. If MIR estimate is still meant for diagnostics, consider documenting that it is informational-only; otherwise remove the unused threshold to avoid confusion and dead configuration.

Suggested change
static constexpr size_t MAX_JIT_MIR_ESTIMATE = 50000;

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct — MAX_JIT_MIR_ESTIMATE is no longer used in the ShouldFallback decision. The MirEstimate field is still computed and available in JITSuitabilityResult for diagnostic logging, but the threshold constant itself is dead. Will either remove the constant and add a comment marking MirEstimate as informational, or drop both if no callers inspect MirEstimate.

Comment on lines +312 to 322
// Compute final fallback verdict.
// Pattern-based thresholds target pathological LLVM register-allocator
// cases (dense RA-expensive opcodes, DUP feedback loops).
// The bytecode size guard (64 KB) catches extreme synthetic tests whose
// sheer IR volume stalls LLVM, without affecting real contracts (bounded
// by EIP-170 / EIP-3860).
JITResult.ShouldFallback =
BytecodeSize > MAX_JIT_BYTECODE_SIZE ||
JITResult.MirEstimate > MAX_JIT_MIR_ESTIMATE ||
JITResult.BytecodeSize > MAX_JIT_BYTECODE_SIZE ||
JITResult.MaxConsecutiveExpensive > MAX_CONSECUTIVE_RA_EXPENSIVE ||
JITResult.MaxBlockExpensiveCount > MAX_BLOCK_RA_EXPENSIVE ||
JITResult.DupFeedbackPatternCount > MAX_DUP_FEEDBACK_PATTERN;
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

The fallback decision logic changed materially (removed MIR-estimate check; adjusted bytecode-size threshold). There are existing GTest suites under src/tests, but none appear to cover EVMAnalyzer suitability decisions. Adding unit tests for representative bytecode patterns (e.g., high RA-expensive density vs. benign PUSH-heavy code) would prevent regressions in these thresholds.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed that dedicated unit tests for EVMAnalyzer thresholds would be valuable. The existing evmFallbackExecutionTests cover end-to-end fallback behavior but don't test the suitability heuristic boundaries. Will add targeted tests with crafted bytecode patterns (high RA-expensive density, DUP feedback loops, benign PUSH-heavy code) as a follow-up.

Comment on lines +160 to +168
auto CacheIt = VM->FallbackCache.find(ModKey);
bool NeedFallback;
if (CacheIt != VM->FallbackCache.end()) {
NeedFallback = CacheIt->second;
} else {
COMPILER::EVMAnalyzer Analyzer(Rev);
Analyzer.analyze(Code, CodeSize);
NeedFallback = Analyzer.getJITSuitability().ShouldFallback;
VM->FallbackCache[ModKey] = NeedFallback;
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

On cache miss, this does a find() and then inserts via FallbackCache[ModKey] = NeedFallback, which performs a second hash lookup and may allocate a default entry first. Since this code is on a hot path for first-seen contracts, consider using emplace/try_emplace with the existing lookup result to avoid the extra work.

Suggested change
auto CacheIt = VM->FallbackCache.find(ModKey);
bool NeedFallback;
if (CacheIt != VM->FallbackCache.end()) {
NeedFallback = CacheIt->second;
} else {
COMPILER::EVMAnalyzer Analyzer(Rev);
Analyzer.analyze(Code, CodeSize);
NeedFallback = Analyzer.getJITSuitability().ShouldFallback;
VM->FallbackCache[ModKey] = NeedFallback;
auto [CacheIt, Inserted] = VM->FallbackCache.try_emplace(ModKey, false);
bool &NeedFallback = CacheIt->second;
if (Inserted) {
COMPILER::EVMAnalyzer Analyzer(Rev);
Analyzer.analyze(Code, CodeSize);
NeedFallback = Analyzer.getJITSuitability().ShouldFallback;

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good optimization — the current find() + operator[] does two hash lookups. Accepted the try_emplace suggestion. Will update to use try_emplace to avoid the redundant lookup and default-construction.

@github-actions
Copy link

github-actions bot commented Mar 2, 2026

⚡ Performance Regression Check Results

✅ Performance Check Passed (interpreter)

Performance Benchmark Results (threshold: 20%)

Benchmark Baseline (us) Current (us) Change Status
total/main/blake2b_huff/8415nulls 2.08 2.26 +8.7% PASS
total/main/blake2b_huff/empty 0.09 0.09 +1.4% PASS
total/main/blake2b_shifts/8415nulls 17.40 17.07 -1.9% PASS
total/main/sha1_divs/5311 7.51 7.32 -2.4% PASS
total/main/sha1_divs/empty 0.10 0.10 -2.3% PASS
total/main/sha1_shifts/5311 5.29 5.31 +0.5% PASS
total/main/sha1_shifts/empty 0.07 0.07 -0.4% PASS
total/main/snailtracer/benchmark 69.95 67.18 -4.0% PASS
total/main/structarray_alloc/nfts_rank 1.25 1.34 +6.8% PASS
total/main/swap_math/insufficient_liquidity 0.01 0.01 +3.0% PASS
total/main/swap_math/received 0.01 0.01 -0.5% PASS
total/main/swap_math/spent 0.01 0.01 +2.8% PASS
total/main/weierstrudel/1 0.32 0.32 -0.5% PASS
total/main/weierstrudel/15 2.87 2.86 -0.4% PASS
total/micro/JUMPDEST_n0/empty 1.20 1.48 +23.3% PASS
total/micro/jump_around/empty 0.13 0.13 -0.7% PASS
total/micro/loop_with_many_jumpdests/empty 18.11 29.94 +65.4% REGRESSED
total/micro/memory_grow_mload/by1 0.15 0.14 -3.3% PASS
total/micro/memory_grow_mload/by16 0.21 0.21 -0.6% PASS
total/micro/memory_grow_mload/by32 0.23 0.23 -0.6% PASS
total/micro/memory_grow_mload/nogrow 0.15 0.14 -3.8% PASS
total/micro/memory_grow_mstore/by1 0.19 0.19 -0.4% PASS
total/micro/memory_grow_mstore/by16 0.21 0.21 -1.7% PASS
total/micro/memory_grow_mstore/by32 0.23 0.22 -5.1% PASS
total/micro/memory_grow_mstore/nogrow 0.18 0.18 -0.3% PASS
total/micro/signextend/one 0.30 0.30 -1.1% PASS
total/micro/signextend/zero 0.30 0.29 -1.9% PASS
total/synth/ADD/b0 2.66 2.59 -2.8% PASS
total/synth/ADD/b1 2.64 2.49 -5.9% PASS
total/synth/ADDRESS/a0 4.42 4.48 +1.4% PASS
total/synth/ADDRESS/a1 4.87 4.72 -3.2% PASS
total/synth/AND/b0 2.46 2.40 -2.4% PASS
total/synth/AND/b1 2.55 2.42 -5.0% PASS
total/synth/BYTE/b0 6.13 6.14 +0.1% PASS
total/synth/BYTE/b1 5.02 4.95 -1.4% PASS
total/synth/CALLDATASIZE/a0 2.65 2.71 +2.2% PASS
total/synth/CALLDATASIZE/a1 3.34 3.46 +3.7% PASS
total/synth/CALLER/a0 4.41 4.50 +2.1% PASS
total/synth/CALLER/a1 4.78 4.72 -1.3% PASS
total/synth/CALLVALUE/a0 2.62 2.29 -12.5% PASS
total/synth/CALLVALUE/a1 2.67 2.26 -15.5% PASS
total/synth/CODESIZE/a0 2.94 3.18 +7.9% PASS
total/synth/CODESIZE/a1 3.52 3.53 +0.3% PASS
total/synth/DUP1/d0 1.48 1.33 -10.2% PASS
total/synth/DUP1/d1 1.57 1.41 -10.3% PASS
total/synth/DUP10/d0 1.49 1.25 -16.3% PASS
total/synth/DUP10/d1 1.57 1.34 -14.9% PASS
total/synth/DUP11/d0 1.49 1.33 -10.9% PASS
total/synth/DUP11/d1 1.57 1.33 -15.3% PASS
total/synth/DUP12/d0 1.49 1.25 -16.1% PASS
total/synth/DUP12/d1 1.57 1.33 -15.1% PASS
total/synth/DUP13/d0 1.50 1.25 -16.8% PASS
total/synth/DUP13/d1 1.57 1.41 -10.1% PASS
total/synth/DUP14/d0 1.49 1.25 -16.1% PASS
total/synth/DUP14/d1 1.57 1.33 -15.1% PASS
total/synth/DUP15/d0 1.49 1.26 -15.3% PASS
total/synth/DUP15/d1 1.57 1.33 -15.0% PASS
total/synth/DUP16/d0 1.49 1.25 -16.0% PASS
total/synth/DUP16/d1 1.57 1.33 -15.3% PASS
total/synth/DUP2/d0 1.48 1.32 -10.9% PASS
total/synth/DUP2/d1 1.57 1.33 -15.0% PASS
total/synth/DUP3/d0 1.48 1.24 -16.2% PASS
total/synth/DUP3/d1 1.57 1.33 -15.5% PASS
total/synth/DUP4/d0 1.48 1.24 -16.2% PASS
total/synth/DUP4/d1 1.57 1.33 -15.4% PASS
total/synth/DUP5/d0 1.48 1.24 -16.1% PASS
total/synth/DUP5/d1 1.57 1.33 -15.2% PASS
total/synth/DUP6/d0 1.49 1.24 -16.3% PASS
total/synth/DUP6/d1 1.57 1.33 -15.4% PASS
total/synth/DUP7/d0 1.49 1.25 -16.2% PASS
total/synth/DUP7/d1 1.57 1.33 -15.5% PASS
total/synth/DUP8/d0 1.49 1.25 -16.2% PASS
total/synth/DUP8/d1 1.57 1.33 -15.4% PASS
total/synth/DUP9/d0 1.49 1.25 -16.2% PASS
total/synth/DUP9/d1 1.59 1.33 -16.6% PASS
total/synth/EQ/b0 4.83 4.81 -0.3% PASS
total/synth/EQ/b1 4.99 4.91 -1.5% PASS
total/synth/GAS/a0 2.78 2.85 +2.5% PASS
total/synth/GAS/a1 3.31 3.52 +6.1% PASS
total/synth/GT/b0 4.57 4.61 +0.9% PASS
total/synth/GT/b1 4.92 4.70 -4.4% PASS
total/synth/ISZERO/u0 7.69 7.66 -0.3% PASS
total/synth/JUMPDEST/n0 1.20 1.50 +24.6% PASS
total/synth/LT/b0 4.57 4.61 +0.9% PASS
total/synth/LT/b1 4.85 4.72 -2.8% PASS
total/synth/MSIZE/a0 3.92 4.11 +5.1% PASS
total/synth/MSIZE/a1 4.29 4.23 -1.4% PASS
total/synth/MUL/b0 4.24 4.26 +0.5% PASS
total/synth/MUL/b1 4.44 4.31 -2.7% PASS
total/synth/NOT/u0 3.67 3.51 -4.4% PASS
total/synth/OR/b0 2.48 2.33 -6.2% PASS
total/synth/OR/b1 2.58 2.62 +1.8% PASS
total/synth/PC/a0 2.61 3.27 +25.6% PASS
total/synth/PC/a1 3.37 3.42 +1.4% PASS
total/synth/PUSH1/p0 1.76 1.78 +1.0% PASS
total/synth/PUSH1/p1 1.50 1.46 -2.3% PASS
total/synth/PUSH10/p0 1.79 1.81 +1.3% PASS
total/synth/PUSH10/p1 1.52 1.30 -14.8% PASS
total/synth/PUSH11/p0 1.79 1.82 +1.4% PASS
total/synth/PUSH11/p1 1.53 1.57 +2.7% PASS
total/synth/PUSH12/p0 1.80 1.83 +1.6% PASS
total/synth/PUSH12/p1 1.52 1.28 -15.7% PASS
total/synth/PUSH13/p0 1.82 1.83 +0.3% PASS
total/synth/PUSH13/p1 1.53 1.28 -16.0% PASS
total/synth/PUSH14/p0 1.82 1.83 +0.7% PASS
total/synth/PUSH14/p1 1.53 1.32 -13.9% PASS
total/synth/PUSH15/p0 1.82 1.83 +0.8% PASS
total/synth/PUSH15/p1 1.55 1.38 -10.9% PASS
total/synth/PUSH16/p0 1.80 1.83 +1.6% PASS
total/synth/PUSH16/p1 1.53 1.29 -15.5% PASS
total/synth/PUSH17/p0 1.81 1.83 +1.0% PASS
total/synth/PUSH17/p1 1.53 1.29 -15.7% PASS
total/synth/PUSH18/p0 1.81 1.83 +1.0% PASS
total/synth/PUSH18/p1 1.54 1.30 -15.8% PASS
total/synth/PUSH19/p0 1.81 1.84 +1.5% PASS
total/synth/PUSH19/p1 1.55 1.30 -15.8% PASS
total/synth/PUSH2/p0 1.77 1.79 +1.1% PASS
total/synth/PUSH2/p1 1.49 1.47 -1.3% PASS
total/synth/PUSH20/p0 1.81 1.84 +1.2% PASS
total/synth/PUSH20/p1 1.54 1.33 -13.6% PASS
total/synth/PUSH21/p0 1.83 1.84 +0.6% PASS
total/synth/PUSH21/p1 1.55 1.31 -15.3% PASS
total/synth/PUSH22/p0 1.82 1.86 +1.8% PASS
total/synth/PUSH22/p1 1.55 1.31 -15.6% PASS
total/synth/PUSH23/p0 1.83 1.85 +1.3% PASS
total/synth/PUSH23/p1 1.56 1.32 -15.4% PASS
total/synth/PUSH24/p0 1.82 1.85 +1.3% PASS
total/synth/PUSH24/p1 1.56 1.60 +2.8% PASS
total/synth/PUSH25/p0 1.84 1.86 +0.8% PASS
total/synth/PUSH25/p1 1.56 1.33 -14.8% PASS
total/synth/PUSH26/p0 1.83 1.88 +2.7% PASS
total/synth/PUSH26/p1 1.56 1.35 -13.7% PASS
total/synth/PUSH27/p0 1.83 1.87 +2.1% PASS
total/synth/PUSH27/p1 1.56 1.35 -13.7% PASS
total/synth/PUSH28/p0 1.84 1.86 +1.1% PASS
total/synth/PUSH28/p1 1.56 1.33 -15.2% PASS
total/synth/PUSH29/p0 1.84 1.86 +1.3% PASS
total/synth/PUSH29/p1 1.56 1.33 -15.0% PASS
total/synth/PUSH3/p0 1.77 1.80 +1.5% PASS
total/synth/PUSH3/p1 1.50 1.50 -0.1% PASS
total/synth/PUSH30/p0 1.86 1.88 +1.0% PASS
total/synth/PUSH30/p1 1.57 1.37 -12.6% PASS
total/synth/PUSH31/p0 1.84 1.87 +1.5% PASS
total/synth/PUSH31/p1 1.63 1.44 -11.4% PASS
total/synth/PUSH32/p0 1.85 1.88 +1.8% PASS
total/synth/PUSH32/p1 1.58 1.34 -15.0% PASS
total/synth/PUSH4/p0 1.77 1.80 +1.2% PASS
total/synth/PUSH4/p1 1.51 1.28 -14.9% PASS
total/synth/PUSH5/p0 1.78 1.80 +1.6% PASS
total/synth/PUSH5/p1 1.50 1.27 -15.9% PASS
total/synth/PUSH6/p0 1.79 1.80 +0.4% PASS
total/synth/PUSH6/p1 1.51 1.28 -15.0% PASS
total/synth/PUSH7/p0 1.78 1.82 +2.3% PASS
total/synth/PUSH7/p1 1.51 1.31 -13.4% PASS
total/synth/PUSH8/p0 1.78 1.81 +1.2% PASS
total/synth/PUSH8/p1 1.51 1.49 -1.3% PASS
total/synth/PUSH9/p0 1.79 1.81 +1.4% PASS
total/synth/PUSH9/p1 1.52 1.32 -13.1% PASS
total/synth/RETURNDATASIZE/a0 2.82 2.97 +5.4% PASS
total/synth/RETURNDATASIZE/a1 3.55 3.39 -4.3% PASS
total/synth/SAR/b0 3.55 3.54 -0.3% PASS
total/synth/SAR/b1 4.12 4.01 -2.6% PASS
total/synth/SGT/b0 4.84 4.76 -1.6% PASS
total/synth/SGT/b1 4.85 4.67 -3.7% PASS
total/synth/SHL/b0 4.00 3.95 -1.3% PASS
total/synth/SHL/b1 2.80 2.69 -3.9% PASS
total/synth/SHR/b0 3.09 3.14 +1.5% PASS
total/synth/SHR/b1 2.78 2.69 -3.4% PASS
total/synth/SIGNEXTEND/b0 2.49 2.43 -2.3% PASS
total/synth/SIGNEXTEND/b1 2.92 2.73 -6.4% PASS
total/synth/SLT/b0 4.83 4.74 -1.9% PASS
total/synth/SLT/b1 4.88 4.73 -3.0% PASS
total/synth/SUB/b0 2.72 2.60 -4.6% PASS
total/synth/SUB/b1 2.54 2.53 -0.5% PASS
total/synth/SWAP1/s0 2.14 1.82 -15.0% PASS
total/synth/SWAP10/s0 2.19 1.83 -16.5% PASS
total/synth/SWAP11/s0 2.16 1.99 -7.7% PASS
total/synth/SWAP12/s0 2.15 1.99 -7.5% PASS
total/synth/SWAP13/s0 2.17 1.99 -8.4% PASS
total/synth/SWAP14/s0 2.15 2.01 -6.9% PASS
total/synth/SWAP15/s0 2.20 2.00 -9.2% PASS
total/synth/SWAP16/s0 2.16 2.00 -7.4% PASS
total/synth/SWAP2/s0 2.14 1.82 -14.9% PASS
total/synth/SWAP3/s0 2.14 1.86 -13.2% PASS
total/synth/SWAP4/s0 2.15 1.82 -15.1% PASS
total/synth/SWAP5/s0 2.14 1.82 -15.0% PASS
total/synth/SWAP6/s0 2.15 1.82 -15.0% PASS
total/synth/SWAP7/s0 2.15 1.82 -15.0% PASS
total/synth/SWAP8/s0 2.16 1.99 -8.2% PASS
total/synth/SWAP9/s0 2.16 1.82 -15.5% PASS
total/synth/XOR/b0 2.46 2.39 -3.0% PASS
total/synth/XOR/b1 2.55 2.48 -2.8% PASS
total/synth/loop_v1 7.27 7.10 -2.4% PASS
total/synth/loop_v2 7.29 7.02 -3.8% PASS

Summary: 194 benchmarks, 1 regressions


✅ Performance Check Passed (multipass)

Performance Benchmark Results (threshold: 20%)

Benchmark Baseline (us) Current (us) Change Status
total/main/blake2b_huff/8415nulls 2.06 1.78 -13.8% PASS
total/main/blake2b_huff/empty 0.13 0.07 -45.7% PASS
total/main/blake2b_shifts/8415nulls 6.51 7.63 +17.2% PASS
total/main/sha1_divs/5311 3.44 3.14 -8.8% PASS
total/main/sha1_divs/empty 0.05 0.04 -24.1% PASS
total/main/sha1_shifts/5311 3.76 4.61 +22.6% PASS
total/main/sha1_shifts/empty 0.06 0.06 +3.5% PASS
total/main/snailtracer/benchmark 70.08 31.26 -55.4% PASS
total/main/structarray_alloc/nfts_rank 0.30 0.33 +7.5% PASS
total/main/swap_math/insufficient_liquidity 0.03 0.01 -72.4% PASS
total/main/swap_math/received 0.03 0.01 -67.9% PASS
total/main/swap_math/spent 0.03 0.01 -69.2% PASS
total/main/weierstrudel/1 0.42 0.24 -41.9% PASS
total/main/weierstrudel/15 3.05 2.31 -24.4% PASS
total/micro/JUMPDEST_n0/empty 0.18 0.01 -96.2% PASS
total/micro/jump_around/empty 0.74 0.11 -85.6% PASS
total/micro/loop_with_many_jumpdests/empty 2.47 0.06 -97.6% PASS
total/micro/memory_grow_mload/by1 0.24 0.11 -55.6% PASS
total/micro/memory_grow_mload/by16 0.25 0.15 -42.0% PASS
total/micro/memory_grow_mload/by32 0.28 0.16 -41.4% PASS
total/micro/memory_grow_mload/nogrow 0.24 0.10 -57.4% PASS
total/micro/memory_grow_mstore/by1 0.28 0.11 -59.3% PASS
total/micro/memory_grow_mstore/by16 0.28 0.15 -44.7% PASS
total/micro/memory_grow_mstore/by32 0.31 0.17 -45.0% PASS
total/micro/memory_grow_mstore/nogrow 0.27 0.11 -60.5% PASS
total/micro/signextend/one 0.41 0.28 -31.7% PASS
total/micro/signextend/zero 0.42 0.28 -32.5% PASS
total/synth/ADD/b0 0.02 0.01 -58.1% PASS
total/synth/ADD/b1 0.02 0.01 -60.9% PASS
total/synth/ADDRESS/a0 1.14 2.23 +95.1% PASS
total/synth/ADDRESS/a1 1.17 2.30 +96.6% PASS
total/synth/AND/b0 0.02 0.01 -57.9% PASS
total/synth/AND/b1 0.02 0.01 -61.0% PASS
total/synth/BYTE/b0 1.96 2.72 +38.5% PASS
total/synth/BYTE/b1 2.32 2.72 +17.1% PASS
total/synth/CALLDATASIZE/a0 0.66 1.46 +120.9% PASS
total/synth/CALLDATASIZE/a1 0.68 1.48 +117.8% PASS
total/synth/CALLER/a0 1.14 2.22 +94.7% PASS
total/synth/CALLER/a1 1.17 2.24 +91.1% PASS
total/synth/CALLVALUE/a0 0.65 1.37 +109.6% PASS
total/synth/CALLVALUE/a1 0.68 1.37 +101.6% PASS
total/synth/CODESIZE/a0 0.65 1.47 +125.4% PASS
total/synth/CODESIZE/a1 0.68 1.47 +117.3% PASS
total/synth/DUP1/d0 0.02 0.01 -59.5% PASS
total/synth/DUP1/d1 0.02 0.01 -61.9% PASS
total/synth/DUP10/d0 0.02 0.01 -59.4% PASS
total/synth/DUP10/d1 0.02 0.01 -61.7% PASS
total/synth/DUP11/d0 0.02 0.01 -59.5% PASS
total/synth/DUP11/d1 0.02 0.01 -61.9% PASS
total/synth/DUP12/d0 0.02 0.01 -59.6% PASS
total/synth/DUP12/d1 0.02 0.01 -61.9% PASS
total/synth/DUP13/d0 0.02 0.01 -59.7% PASS
total/synth/DUP13/d1 0.02 0.01 -61.9% PASS
total/synth/DUP14/d0 0.02 0.01 -59.6% PASS
total/synth/DUP14/d1 0.02 0.01 -61.9% PASS
total/synth/DUP15/d0 0.02 0.01 -59.1% PASS
total/synth/DUP15/d1 0.02 0.01 -61.9% PASS
total/synth/DUP16/d0 0.02 0.01 -59.7% PASS
total/synth/DUP16/d1 0.02 0.01 -61.9% PASS
total/synth/DUP2/d0 0.02 0.01 -59.4% PASS
total/synth/DUP2/d1 0.02 0.01 -61.9% PASS
total/synth/DUP3/d0 0.02 0.01 -59.5% PASS
total/synth/DUP3/d1 0.02 0.01 -61.9% PASS
total/synth/DUP4/d0 0.02 0.01 -59.5% PASS
total/synth/DUP4/d1 0.02 0.01 -61.9% PASS
total/synth/DUP5/d0 0.02 0.01 -59.5% PASS
total/synth/DUP5/d1 0.02 0.01 -61.9% PASS
total/synth/DUP6/d0 0.02 0.01 -59.5% PASS
total/synth/DUP6/d1 0.02 0.01 -61.8% PASS
total/synth/DUP7/d0 0.02 0.01 -59.5% PASS
total/synth/DUP7/d1 0.02 0.01 -61.8% PASS
total/synth/DUP8/d0 0.02 0.01 -59.5% PASS
total/synth/DUP8/d1 0.02 0.01 -61.8% PASS
total/synth/DUP9/d0 0.02 0.01 -59.6% PASS
total/synth/DUP9/d1 0.02 0.01 -61.7% PASS
total/synth/EQ/b0 0.02 0.01 -57.8% PASS
total/synth/EQ/b1 0.02 0.01 -60.9% PASS
total/synth/GAS/a0 1.01 1.50 +47.7% PASS
total/synth/GAS/a1 1.05 1.48 +40.7% PASS
total/synth/GT/b0 0.02 0.01 -58.0% PASS
total/synth/GT/b1 0.02 0.01 -61.1% PASS
total/synth/ISZERO/u0 0.02 0.01 -60.2% PASS
total/synth/JUMPDEST/n0 0.18 0.01 -96.1% PASS
total/synth/LT/b0 0.02 0.01 -57.9% PASS
total/synth/LT/b1 0.02 0.01 -61.0% PASS
total/synth/MSIZE/a0 0.02 0.01 -60.4% PASS
total/synth/MSIZE/a1 0.02 0.01 -61.2% PASS
total/synth/MUL/b0 4.23 4.06 -4.0% PASS
total/synth/MUL/b1 4.44 3.93 -11.5% PASS
total/synth/NOT/u0 0.02 0.01 -60.2% PASS
total/synth/OR/b0 0.02 0.01 -57.8% PASS
total/synth/OR/b1 0.02 0.01 -61.0% PASS
total/synth/PC/a0 0.02 0.01 -60.4% PASS
total/synth/PC/a1 0.02 0.01 -60.9% PASS
total/synth/PUSH1/p0 0.02 0.01 -53.4% PASS
total/synth/PUSH1/p1 0.02 0.01 -53.3% PASS
total/synth/PUSH10/p0 0.04 0.03 -27.9% PASS
total/synth/PUSH10/p1 0.04 0.03 -27.9% PASS
total/synth/PUSH11/p0 0.04 0.03 -26.6% PASS
total/synth/PUSH11/p1 0.04 0.03 -26.6% PASS
total/synth/PUSH12/p0 0.05 0.04 -25.6% PASS
total/synth/PUSH12/p1 0.05 0.04 -25.6% PASS
total/synth/PUSH13/p0 0.05 0.04 -24.6% PASS
total/synth/PUSH13/p1 0.05 0.04 -24.6% PASS
total/synth/PUSH14/p0 0.05 0.04 -23.7% PASS
total/synth/PUSH14/p1 0.05 0.04 -23.7% PASS
total/synth/PUSH15/p0 0.06 0.04 -23.4% PASS
total/synth/PUSH15/p1 0.05 0.04 -23.0% PASS
total/synth/PUSH16/p0 0.06 0.04 -22.7% PASS
total/synth/PUSH16/p1 0.06 0.04 -22.5% PASS
total/synth/PUSH17/p0 0.06 0.05 -22.2% PASS
total/synth/PUSH17/p1 0.06 0.05 -21.8% PASS
total/synth/PUSH18/p0 0.06 0.05 -21.2% PASS
total/synth/PUSH18/p1 0.06 0.05 -21.1% PASS
total/synth/PUSH19/p0 0.07 0.05 -20.5% PASS
total/synth/PUSH19/p1 0.07 0.05 -21.2% PASS
total/synth/PUSH2/p0 0.02 0.01 -47.8% PASS
total/synth/PUSH2/p1 0.02 0.01 -47.7% PASS
total/synth/PUSH20/p0 0.07 0.05 -20.5% PASS
total/synth/PUSH20/p1 0.07 0.05 -20.1% PASS
total/synth/PUSH21/p0 0.07 0.06 -19.9% PASS
total/synth/PUSH21/p1 0.07 0.06 -20.1% PASS
total/synth/PUSH22/p0 1.83 0.06 -96.8% PASS
total/synth/PUSH22/p1 1.56 0.06 -96.2% PASS
total/synth/PUSH23/p0 1.83 0.06 -96.7% PASS
total/synth/PUSH23/p1 1.56 0.06 -96.1% PASS
total/synth/PUSH24/p0 1.83 0.06 -96.5% PASS
total/synth/PUSH24/p1 1.56 0.06 -95.9% PASS
total/synth/PUSH25/p0 1.84 0.07 -96.4% PASS
total/synth/PUSH25/p1 1.57 0.07 -95.8% PASS
total/synth/PUSH26/p0 1.84 0.07 -96.3% PASS
total/synth/PUSH26/p1 1.57 0.07 -95.7% PASS
total/synth/PUSH27/p0 1.85 0.07 -96.2% PASS
total/synth/PUSH27/p1 1.58 0.07 -95.5% PASS
total/synth/PUSH28/p0 1.85 0.07 -96.0% PASS
total/synth/PUSH28/p1 1.57 0.07 -95.4% PASS
total/synth/PUSH29/p0 1.85 0.08 -95.9% PASS
total/synth/PUSH29/p1 1.58 0.08 -95.2% PASS
total/synth/PUSH3/p0 0.02 0.01 -43.5% PASS
total/synth/PUSH3/p1 0.02 0.01 -43.5% PASS
total/synth/PUSH30/p0 1.86 0.08 -95.8% PASS
total/synth/PUSH30/p1 1.58 0.08 -95.1% PASS
total/synth/PUSH31/p0 1.85 0.08 -95.7% PASS
total/synth/PUSH31/p1 1.62 0.08 -95.1% PASS
total/synth/PUSH32/p0 1.86 0.08 -95.6% PASS
total/synth/PUSH32/p1 1.59 0.08 -94.8% PASS
total/synth/PUSH4/p0 0.03 0.02 -39.9% PASS
total/synth/PUSH4/p1 0.03 0.02 -40.0% PASS
total/synth/PUSH5/p0 0.03 0.02 -37.2% PASS
total/synth/PUSH5/p1 0.03 0.02 -37.1% PASS
total/synth/PUSH6/p0 0.03 0.02 -34.7% PASS
total/synth/PUSH6/p1 0.03 0.02 -34.6% PASS
total/synth/PUSH7/p0 0.03 0.02 -32.6% PASS
total/synth/PUSH7/p1 0.03 0.02 -32.5% PASS
total/synth/PUSH8/p0 0.04 0.03 -30.8% PASS
total/synth/PUSH8/p1 0.04 0.03 -30.8% PASS
total/synth/PUSH9/p0 0.04 0.03 -29.2% PASS
total/synth/PUSH9/p1 0.04 0.03 -29.2% PASS
total/synth/RETURNDATASIZE/a0 0.65 1.35 +106.5% PASS
total/synth/RETURNDATASIZE/a1 0.68 1.35 +98.5% PASS
total/synth/SAR/b0 3.56 3.34 -6.2% PASS
total/synth/SAR/b1 4.10 3.61 -11.9% PASS
total/synth/SGT/b0 0.02 0.01 -57.8% PASS
total/synth/SGT/b1 0.02 0.01 -60.9% PASS
total/synth/SHL/b0 4.00 3.62 -9.5% PASS
total/synth/SHL/b1 2.77 2.51 -9.3% PASS
total/synth/SHR/b0 3.11 3.50 +12.7% PASS
total/synth/SHR/b1 2.69 2.52 -5.8% PASS
total/synth/SIGNEXTEND/b0 2.48 2.49 +0.7% PASS
total/synth/SIGNEXTEND/b1 2.82 2.34 -17.1% PASS
total/synth/SLT/b0 0.02 0.01 -57.9% PASS
total/synth/SLT/b1 0.02 0.01 -60.9% PASS
total/synth/SUB/b0 0.02 0.01 -58.0% PASS
total/synth/SUB/b1 0.02 0.01 -60.9% PASS
total/synth/SWAP1/s0 0.02 0.01 -57.7% PASS
total/synth/SWAP10/s0 0.02 0.01 -57.7% PASS
total/synth/SWAP11/s0 0.02 0.01 -57.9% PASS
total/synth/SWAP12/s0 0.02 0.01 -57.8% PASS
total/synth/SWAP13/s0 0.02 0.01 -57.9% PASS
total/synth/SWAP14/s0 0.02 0.01 -57.8% PASS
total/synth/SWAP15/s0 0.02 0.01 -57.8% PASS
total/synth/SWAP16/s0 0.02 0.01 -58.0% PASS
total/synth/SWAP2/s0 0.02 0.01 -57.8% PASS
total/synth/SWAP3/s0 0.02 0.01 -57.7% PASS
total/synth/SWAP4/s0 0.02 0.01 -57.7% PASS
total/synth/SWAP5/s0 0.02 0.01 -57.7% PASS
total/synth/SWAP6/s0 0.02 0.01 -57.7% PASS
total/synth/SWAP7/s0 0.02 0.01 -57.7% PASS
total/synth/SWAP8/s0 0.02 0.01 -57.7% PASS
total/synth/SWAP9/s0 0.02 0.01 -57.8% PASS
total/synth/XOR/b0 0.02 0.01 -57.8% PASS
total/synth/XOR/b1 0.02 0.01 -60.9% PASS
total/synth/loop_v1 1.89 1.84 -2.9% PASS
total/synth/loop_v2 1.82 1.84 +1.3% PASS

Summary: 194 benchmarks, 0 regressions


starwarfan and others added 3 commits March 2, 2026 05:59
Two fixes in the ZEN_ENABLE_JIT_PRECOMPILE_FALLBACK mechanism:

1. Per-call analyzer overhead: EVMAnalyzer was constructed on every
   execute() call, rebuilding a std::map over the full bytecode each
   time. For benchmarks running thousands of iterations this added
   ~245us per call even when no fallback was needed. Fix: cache the
   ShouldFallback boolean per module key (CRC32 + revision) in a new
   FallbackCache map on the DTVM VM struct.

2. Tuned fallback thresholds to eliminate false positives while
   covering all pathological cases:
   - Removed MirEstimate condition (caused false positives on PUSH22-32
     and other harmless synth benchmarks).
   - Raised BytecodeSize limit from 0x6000 (24 576) to 0x10000 (64 KB).
     The old limit overlapped the EIP-170 deployed-code ceiling and
     caused false positives on real contracts (snailtracer, MUL/b1, etc).
     The new limit is safely above both EIP-170 (24 576) and the EIP-3860
     initcode limit (49 152), while still catching extreme synthetic test
     bytecode (402 KB, 6 MB) that would stall LLVM.
   - Pattern-based thresholds (MaxConsecutiveExpensive > 128,
     MaxBlockExpensiveCount > 256, DupFeedbackPatternCount > 64) remain
     and precisely target the 5 pathological benchmark cases.

Verified: all 223 evmonetestsuite multipass tests pass; large synthetic
bytecode cases (evmone_block_gas_cost_overflow_*) correctly fall back to
interpreter; 207/216 common benchmark cases remain within 20% of the
no-fallback JIT reference.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Fix comment: 400 KB+ → 64 KiB to match actual MAX_JIT_BYTECODE_SIZE
- Remove unused MAX_JIT_MIR_ESTIMATE constant (MirEstimate kept as
  informational field for diagnostics)
- Use try_emplace instead of find + operator[] to avoid redundant hash
  lookup on cache miss

Made-with: Cursor
@starwarfan starwarfan force-pushed the adjust-fallback-cond branch from 07dc063 to a0623a1 Compare March 2, 2026 06:09
- Add size limit to FallbackCache to prevent unbounded growth in long-running
  processes executing many distinct contracts.
- Add unit tests for EVMAnalyzer to verify fallback suitability heuristics.

Made-with: Cursor
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