Skip to main content

revmc_runtime/runtime/
config.rs

1//! Runtime configuration.
2
3use crate::{CompileTimings, runtime::storage::ArtifactStore};
4use alloy_primitives::B256;
5use revm_context_interface::cfg::GasParams;
6use revm_primitives::hardfork::SpecId;
7use std::{path::PathBuf, sync::Arc, time::Duration};
8
9/// Runtime configuration.
10#[derive(Clone, derive_more::Debug)]
11pub struct RuntimeConfig {
12    /// Whether compiled-code lookup is enabled.
13    ///
14    /// Defaults to `false` (safe rollout default).
15    pub enabled: bool,
16
17    /// Name for the backend thread.
18    ///
19    /// Defaults to `"revmc-backend"`.
20    pub thread_name: String,
21
22    /// Artifact store for loading precompiled AOT artifacts.
23    ///
24    /// `None` means no AOT preload—only JIT will populate the map (in later phases).
25    #[debug(skip)]
26    pub store: Option<Arc<dyn ArtifactStore>>,
27
28    /// Tuning knobs.
29    pub tuning: RuntimeTuning,
30
31    /// Base directory for compiler debug dumps.
32    ///
33    /// When set, the compiler dumps IR, assembly, and bytecode for each compiled contract
34    /// to `{dump_dir}/{spec_id}/{code_hash}/`.
35    ///
36    /// Defaults to `None` (no dumps).
37    pub dump_dir: Option<PathBuf>,
38
39    /// Enable debug assertions in compiled code.
40    ///
41    /// When `true`, the compiler inserts runtime checks (e.g. stack bounds)
42    /// that `panic!` on violation. Useful for diagnosing JIT correctness bugs.
43    ///
44    /// Defaults to `false`.
45    pub debug_assertions: bool,
46
47    /// Disable the block deduplication pass.
48    ///
49    /// When `true`, the dedup pass that merges identical basic blocks is skipped.
50    /// Useful for isolating dedup-related JIT correctness bugs.
51    ///
52    /// Defaults to `false`.
53    pub no_dedup: bool,
54
55    /// Disable the dead store elimination pass.
56    ///
57    /// When `true`, DSE is skipped. Useful for debugging JIT correctness issues
58    /// where DSE incorrectly eliminates live stack operations.
59    ///
60    /// Defaults to `false`.
61    pub no_dse: bool,
62
63    /// Custom gas parameters for compile-time gas folding.
64    ///
65    /// Overrides the default gas schedule derived from `spec_id` when compiling
66    /// bytecode. Useful for custom chains with non-standard gas costs (e.g.
67    /// modified SSTORE, CREATE, or EXP costs).
68    ///
69    /// When `None`, the compiler uses `GasParams::new_spec(spec_id)`.
70    ///
71    /// Defaults to `None`.
72    pub gas_params: Option<GasParams>,
73
74    /// AOT mode: observed misses are promoted to AOT compilation instead of JIT.
75    ///
76    /// Defaults to `false`.
77    pub aot: bool,
78
79    /// Blocking mode: every lookup synchronously JIT-compiles on miss and never
80    /// falls back to the interpreter.
81    ///
82    /// When `true`, [`lookup()`](super::JitBackend::lookup) behaves like
83    /// [`lookup_blocking()`](super::JitBackend::lookup_blocking): if the
84    /// compiled function is not already resident, the calling thread blocks
85    /// until JIT compilation completes. This implies `enabled = true` and
86    /// `jit_hot_threshold = 0`.
87    ///
88    /// Intended for debugging and testing only — not for production use.
89    ///
90    /// Defaults to `false`.
91    pub blocking: bool,
92
93    /// Callback invoked after each compilation completes (success or failure).
94    ///
95    /// Defaults to `None`.
96    #[debug(skip)]
97    pub on_compilation: Option<Arc<dyn Fn(CompilationEvent) + Send + Sync>>,
98}
99
100/// Event emitted after a compilation attempt completes.
101#[derive(Clone, Debug)]
102pub struct CompilationEvent {
103    /// The code hash of the compiled bytecode.
104    pub code_hash: B256,
105    /// The hardfork spec the bytecode was compiled for.
106    pub spec_id: SpecId,
107    /// Wall-clock time spent compiling.
108    pub duration: Duration,
109    /// Whether this was a JIT or AOT compilation.
110    pub kind: CompilationKind,
111    /// Whether compilation succeeded.
112    pub success: bool,
113    /// Per-phase timing breakdown (translate, optimize, codegen).
114    pub timings: CompileTimings,
115}
116
117/// The kind of compilation that was performed.
118#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119pub enum CompilationKind {
120    /// JIT compilation (in-memory function pointer).
121    Jit,
122    /// AOT compilation (shared library artifact).
123    Aot,
124}
125
126impl Default for RuntimeConfig {
127    fn default() -> Self {
128        Self {
129            enabled: false,
130            thread_name: "revmc-backend".into(),
131            store: None,
132            tuning: RuntimeTuning::default(),
133            dump_dir: None,
134            debug_assertions: false,
135            no_dedup: false,
136            no_dse: false,
137            gas_params: None,
138            aot: false,
139            blocking: false,
140            on_compilation: None,
141        }
142    }
143}
144
145/// Tuning knobs for the runtime.
146#[derive(Clone, Copy, Debug)]
147pub struct RuntimeTuning {
148    /// Capacity of the channel between API callers and the backend.
149    ///
150    /// Defaults to `4096`.
151    pub channel_capacity: usize,
152
153    /// Maximum lookup events processed per backend wakeup.
154    ///
155    /// Defaults to `4096`.
156    pub max_events_per_drain: usize,
157
158    /// Maximum delay between lookup observation and hotness accounting.
159    ///
160    /// Defaults to `100ms`.
161    pub event_drain_interval: Duration,
162
163    /// Timeout for joining the backend thread during shutdown.
164    ///
165    /// Defaults to `5s`.
166    pub shutdown_timeout: Duration,
167
168    /// Number of observed misses before a key is promoted to JIT compilation.
169    ///
170    /// Defaults to `8`.
171    pub jit_hot_threshold: usize,
172
173    /// Maximum bytecode length eligible for compilation. `0` = no limit.
174    ///
175    /// Defaults to `0`.
176    pub jit_max_bytecode_len: usize,
177
178    /// Maximum number of JIT compilation jobs in flight.
179    ///
180    /// Defaults to `2048`.
181    pub jit_max_pending_jobs: usize,
182
183    /// Number of JIT compilation worker threads.
184    ///
185    /// Defaults to `min(max(1, cpus/2), 4)`.
186    pub jit_worker_count: usize,
187
188    /// Capacity of the per-worker job queue.
189    ///
190    /// Defaults to `64`.
191    pub jit_worker_queue_capacity: usize,
192
193    /// Optimization level for JIT compilation.
194    ///
195    /// Defaults to [`OptimizationLevel::Default`](crate::OptimizationLevel::Default).
196    pub jit_opt_level: crate::OptimizationLevel,
197
198    /// Optimization level for AOT compilation.
199    ///
200    /// Defaults to [`OptimizationLevel::Default`](crate::OptimizationLevel::Default).
201    pub aot_opt_level: crate::OptimizationLevel,
202
203    /// Maximum total resident compiled code size in bytes. `0` = no limit.
204    ///
205    /// When exceeded, least-recently-used entries are evicted.
206    ///
207    /// Defaults to `0`.
208    pub resident_code_cache_bytes: usize,
209
210    /// Duration after which a resident program with no lookup hits is evicted.
211    /// `None` disables idle eviction.
212    ///
213    /// Defaults to `None`.
214    pub idle_evict_duration: Option<Duration>,
215
216    /// How often the backend runs eviction sweeps, if `idle_evict_duration` is set.
217    ///
218    /// Defaults to `60s`.
219    pub eviction_sweep_interval: Duration,
220
221    /// Number of compilations before recycling the compiler to reclaim
222    /// accumulated memory. `0` = never recycle.
223    ///
224    /// Defaults to `1000`.
225    pub compiler_recycle_threshold: usize,
226}
227
228impl RuntimeTuning {
229    /// Returns whether `bytecode` is eligible for JIT/AOT compilation.
230    #[inline]
231    pub fn should_compile(&self, bytecode: &[u8]) -> bool {
232        if bytecode.is_empty() {
233            return false;
234        }
235        if self.jit_max_bytecode_len > 0 && bytecode.len() > self.jit_max_bytecode_len {
236            return false;
237        }
238        true
239    }
240}
241
242impl Default for RuntimeTuning {
243    fn default() -> Self {
244        let cpus = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
245        let worker_count = cpus.div_ceil(2).clamp(1, 4);
246
247        Self {
248            channel_capacity: 4096,
249            max_events_per_drain: 4096,
250            event_drain_interval: Duration::from_millis(100),
251            shutdown_timeout: Duration::from_secs(5),
252            jit_hot_threshold: 8,
253            jit_max_bytecode_len: 0,
254            jit_max_pending_jobs: 2048,
255            jit_worker_count: worker_count,
256            jit_worker_queue_capacity: 64,
257            jit_opt_level: crate::OptimizationLevel::default(),
258            aot_opt_level: crate::OptimizationLevel::default(),
259            resident_code_cache_bytes: 1024 * 1024 * 1024,
260            idle_evict_duration: Some(Duration::from_secs(600)),
261            eviction_sweep_interval: Duration::from_secs(60),
262            compiler_recycle_threshold: 1000,
263        }
264    }
265}