-
Notifications
You must be signed in to change notification settings - Fork 25
fix(evm): cache fallback decisions and narrow JIT suitability thresholds #357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -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. | ||||
| static constexpr size_t MAX_JIT_BYTECODE_SIZE = 0x10000; | ||||
| static constexpr size_t MAX_JIT_MIR_ESTIMATE = 50000; | ||||
|
||||
| static constexpr size_t MAX_JIT_MIR_ESTIMATE = 50000; |
Copilot
AI
Feb 28, 2026
There was a problem hiding this comment.
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
AI
Feb 28, 2026
There was a problem hiding this comment.
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.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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
|
||||||||||||||||||||||||||||||||
| Isolation *Iso = nullptr; | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
@@ -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
|
||||||||||||||||||||||||||||||||
| 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; |
There was a problem hiding this comment.
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).