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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/compiler/evm_frontend/evm_analyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@ struct JITSuitabilityResult {

/// Thresholds for JIT suitability fallback. Normal contracts have <20
/// RA-expensive ops per block; these values are conservatively high.
static constexpr size_t MAX_JIT_BYTECODE_SIZE = 0x6000;
/// 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.
Comment on lines +117 to +119
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.
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.
static constexpr size_t MAX_CONSECUTIVE_RA_EXPENSIVE = 128;
static constexpr size_t MAX_BLOCK_RA_EXPENSIVE = 256;
Expand Down Expand Up @@ -306,10 +309,14 @@ class EVMAnalyzer {
BlockInfos.emplace(CurInfo.EntryPC, CurInfo);
}

// Compute final fallback verdict
// 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 ||
Comment on lines 318 to 321
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.
JITResult.DupFeedbackPatternCount > MAX_DUP_FEEDBACK_PATTERN;
Comment on lines +312 to 322
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.
Expand Down
30 changes: 17 additions & 13 deletions src/vm/dt_evmc_vm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ struct DTVM : evmc_vm {
std::unique_ptr<Runtime> RT;
std::unique_ptr<WrappedHost> ExecHost;
std::unordered_map<uint64_t, EVMModule *> LoadedMods;
#ifdef ZEN_ENABLE_JIT_PRECOMPILE_FALLBACK
std::unordered_map<uint64_t, bool> FallbackCache;
#endif
Comment on lines +82 to +84
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.
Isolation *Iso = nullptr;
};

Expand Down Expand Up @@ -148,28 +151,29 @@ evmc_result execute(evmc_vm *EVMInstance, const evmc_host_interface *Host,
return evmc_make_result(EVMC_FAILURE, 0, 0, nullptr, 0);
}
}
uint32_t CheckSum = crc32(Code, CodeSize);
uint64_t ModKey = (static_cast<uint64_t>(Rev) << 32) | CheckSum;

#ifdef ZEN_ENABLE_JIT_PRECOMPILE_FALLBACK
// Use interpreter mode for bytecode that would be too expensive to JIT.
// The EVMAnalyzer performs a pattern-aware O(n) scan that detects:
// - raw bytecode size / estimated MIR instruction count too large
// - high density of RA-expensive opcodes (SHL/SHR/SAR/MUL/SIGNEXTEND)
// - long consecutive runs of RA-expensive ops
// - DUP-induced feedback loops (b0 pattern)
std::unique_ptr<ScopedConfig> TempConfig;
if (VM->Config.Mode == RunMode::MultipassMode) {
COMPILER::EVMAnalyzer Analyzer(Rev);
Analyzer.analyze(Code, CodeSize);
const auto &JITResult = Analyzer.getJITSuitability();
if (JITResult.ShouldFallback) {
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;
Comment on lines +160 to +168
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.
}
if (NeedFallback) {
RuntimeConfig NewConfig = VM->Config;
NewConfig.Mode = RunMode::InterpMode;
TempConfig = std::make_unique<ScopedConfig>(VM->RT.get(), NewConfig);
}
}
#endif // ZEN_ENABLE_JIT_PRECOMPILE_FALLBACK

uint32_t CheckSum = crc32(Code, CodeSize);
uint64_t ModKey = (static_cast<uint64_t>(Rev) << 32) | CheckSum;
std::string ModName =
std::to_string(CheckSum) + "_" + std::to_string(static_cast<int>(Rev));
auto ModRet = VM->RT->loadEVMModule(ModName, Code, CodeSize, Rev);
Expand Down