Skip to main content

reth_evm_ethereum/
factory.rs

1//! revmc JIT compiler integration for EVM execution (requires the `jit` feature).
2//!
3//! Re-exports types from `revmc::alloy_evm` and provides [`RethEvmFactory`], a newtype that
4//! implements [`Debug`].
5
6#[cfg(feature = "jit")]
7use alloc::string::String;
8use alloy_evm::{Database, EvmEnv, EvmFactory};
9use revm::{
10    context::{BlockEnv, DBErrorMarker},
11    context_interface::result::{EVMError, HaltReason},
12    inspector::NoOpInspector,
13    primitives::hardfork::SpecId,
14    Inspector,
15};
16#[cfg(feature = "jit")]
17use revmc::alloy_evm::JitEvmFactory;
18
19#[cfg(feature = "jit")]
20pub use revmc::{
21    runtime::{
22        maybe_run_jit_helper, CompilationEvent, CompilationKind, JitBackend, JitMode,
23        RuntimeConfig, RuntimeStatsSnapshot, RuntimeTuning,
24    },
25    CompileTimings,
26};
27
28#[cfg(feature = "jit")]
29type Inner = JitEvmFactory;
30#[cfg(not(feature = "jit"))]
31type Inner = alloy_evm::EthEvmFactory;
32
33/// Reth EVM factory.
34///
35/// With the `jit` feature, this wraps [`JitEvmFactory`] and owns the shared revmc backend. The
36/// backend is constructed for every node so runtime RPC controls can enable it later.
37///
38/// An EVM can execute JIT-compiled code only when all three gates are enabled: the binary was built
39/// with the `jit` feature, runtime compilation was enabled with `--jit` or the `reth_jit` RPC
40/// method, and the local EVM config selected JIT support with
41/// [`ConfigureEvm::with_jit_support`](reth_evm::ConfigureEvm::with_jit_support).
42///
43/// Without the `jit` feature, this is a thin wrapper around [`alloy_evm::EthEvmFactory`].
44#[derive(Debug)]
45pub struct RethEvmFactory {
46    inner: Inner,
47    #[cfg(feature = "jit")]
48    disabled: JitBackend,
49    #[cfg(feature = "jit")]
50    metrics: RevmcMetrics,
51    #[cfg(feature = "jit")]
52    jit_support: bool,
53}
54
55impl Clone for RethEvmFactory {
56    fn clone(&self) -> Self {
57        Self {
58            #[cfg(feature = "jit")]
59            inner: self.inner.clone(),
60            #[cfg(not(feature = "jit"))]
61            inner: self.inner,
62            #[cfg(feature = "jit")]
63            disabled: self.disabled.clone(),
64            #[cfg(feature = "jit")]
65            metrics: self.metrics.clone(),
66            #[cfg(feature = "jit")]
67            jit_support: self.jit_support,
68        }
69    }
70}
71
72#[allow(clippy::derivable_impls)]
73impl Default for RethEvmFactory {
74    fn default() -> Self {
75        #[cfg(feature = "jit")]
76        {
77            Self::new(JitBackend::disabled())
78        }
79        #[cfg(not(feature = "jit"))]
80        {
81            Self { inner: Default::default() }
82        }
83    }
84}
85
86#[cfg(feature = "jit")]
87impl RethEvmFactory {
88    /// Creates a new factory that owns the backend.
89    pub fn new(backend: JitBackend) -> Self {
90        Self::new_with_metrics(backend, RevmcMetrics::default())
91    }
92
93    /// Creates a new factory that owns the backend and records metrics into the given handles.
94    pub fn new_with_metrics(backend: JitBackend, metrics: RevmcMetrics) -> Self {
95        Self {
96            inner: JitEvmFactory::new(backend),
97            disabled: JitBackend::disabled(),
98            metrics,
99            jit_support: false,
100        }
101    }
102
103    /// Creates a [`RethEvmFactory`] with JIT support and compilation disabled.
104    pub fn disabled() -> Self {
105        Self::default()
106    }
107
108    /// Returns a reference to the JIT backend.
109    pub const fn backend(&self) -> &JitBackend {
110        self.inner.backend()
111    }
112
113    /// Enables or disables local JIT support for subsequently created EVMs.
114    ///
115    /// Enabling support only selects the JIT-capable EVM factory path. An EVM still requires the
116    /// `jit` feature and runtime compilation enabled by `--jit` or `reth_jit` before it can execute
117    /// JIT-compiled code.
118    pub const fn set_jit_support(&mut self, enabled: bool) {
119        self.jit_support = enabled;
120    }
121
122    /// Returns whether subsequently created EVMs use the JIT-capable factory path.
123    pub const fn jit_support_enabled(&self) -> bool {
124        self.jit_support
125    }
126
127    /// Pauses JIT helper execution while keeping queueing and resident lookups enabled.
128    fn pause_jit(&self) {
129        let backend = self.inner.backend();
130        let was_paused = backend.is_paused();
131        backend.pause();
132        let is_paused = backend.is_paused();
133        if !was_paused && is_paused {
134            self.metrics.pauses_total.increment(1);
135        }
136        self.metrics.paused.set(is_paused as u8 as f64);
137    }
138
139    /// Resumes background JIT promotion.
140    fn resume_jit(&self) {
141        let backend = self.inner.backend();
142        let was_paused = backend.is_paused();
143        backend.resume();
144        let is_paused = backend.is_paused();
145        if was_paused && !is_paused {
146            self.metrics.resumes_total.increment(1);
147        }
148        self.metrics.paused.set(is_paused as u8 as f64);
149    }
150}
151
152#[cfg(feature = "jit")]
153impl reth_evm::JitBackend for RethEvmFactory {
154    fn set_enabled(&self, enabled: bool) -> Result<(), String> {
155        self.inner.backend().set_enabled(enabled).map_err(|err| err.to_string())
156    }
157
158    fn pause(&self) {
159        self.pause_jit();
160    }
161
162    fn resume(&self) {
163        self.resume_jit();
164    }
165
166    fn clear(&self) {
167        self.inner.backend().clear_all();
168    }
169}
170
171impl EvmFactory for RethEvmFactory {
172    type Evm<DB: Database, I: Inspector<alloy_evm::eth::EthEvmContext<DB>>> =
173        <Inner as EvmFactory>::Evm<DB, I>;
174    type Context<DB: Database> = <Inner as EvmFactory>::Context<DB>;
175    type Tx = <Inner as EvmFactory>::Tx;
176    type Error<DBError: DBErrorMarker> = EVMError<DBError>;
177    type HaltReason = HaltReason;
178    type Spec = SpecId;
179    type BlockEnv = BlockEnv;
180    type Precompiles = <Inner as EvmFactory>::Precompiles;
181
182    fn create_evm<DB: Database>(&self, db: DB, input: EvmEnv) -> Self::Evm<DB, NoOpInspector> {
183        #[cfg(feature = "jit")]
184        {
185            if self.jit_support {
186                self.inner.create_evm(db, input)
187            } else {
188                JitEvmFactory::new(self.disabled.clone()).create_evm(db, input)
189            }
190        }
191        #[cfg(not(feature = "jit"))]
192        {
193            self.inner.create_evm(db, input)
194        }
195    }
196
197    fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>>>(
198        &self,
199        db: DB,
200        input: EvmEnv,
201        inspector: I,
202    ) -> Self::Evm<DB, I> {
203        #[cfg(feature = "jit")]
204        {
205            if self.jit_support {
206                self.inner.create_evm_with_inspector(db, input, inspector)
207            } else {
208                JitEvmFactory::new(self.disabled.clone())
209                    .create_evm_with_inspector(db, input, inspector)
210            }
211        }
212        #[cfg(not(feature = "jit"))]
213        {
214            self.inner.create_evm_with_inspector(db, input, inspector)
215        }
216    }
217}
218
219/// Prometheus metrics for revmc JIT runtime stats.
220#[cfg(feature = "jit")]
221#[derive(reth_metrics::Metrics, Clone)]
222#[metrics(scope = "revmc.jit")]
223pub struct RevmcMetrics {
224    /// Total lookups that returned a compiled function.
225    pub lookup_hits: metrics::Gauge,
226    /// Total lookups that returned interpret (not ready).
227    pub lookup_misses: metrics::Gauge,
228    /// Lookup-observed events currently queued.
229    pub events_queued: metrics::Gauge,
230    /// Lookup-observed events dropped (channel full).
231    pub events_dropped: metrics::Gauge,
232    /// Number of entries in the resident compiled map.
233    pub resident_entries: metrics::Gauge,
234    /// Approximate total bytes of compiled machine code in the resident map.
235    pub jit_code_bytes: metrics::Gauge,
236    /// Approximate total bytes of JIT-related data (relocations, metadata, etc.).
237    pub jit_data_bytes: metrics::Gauge,
238    /// Number of pending control commands queued for the backend.
239    pub command_queue_len: metrics::Gauge,
240    /// Number of compilation jobs dispatched but not completed yet.
241    pub pending_jobs: metrics::Gauge,
242    /// Total number of entries evicted (idle + budget).
243    pub evictions: metrics::Gauge,
244    /// Total number of compilations dispatched (JIT promotions + AOT requests).
245    pub compilations_dispatched: metrics::Gauge,
246    /// Total number of successful compilations (JIT + AOT).
247    pub compilations_succeeded: metrics::Gauge,
248    /// Total number of failed compilations (JIT + AOT).
249    pub compilations_failed: metrics::Gauge,
250    /// Total number of JIT helper processes spawned.
251    pub jit_helper_spawns: metrics::Gauge,
252    /// Total number of JIT helper process spawn failures.
253    pub jit_helper_spawn_failures: metrics::Gauge,
254    /// Total number of JIT helper process restarts.
255    pub jit_helper_restarts: metrics::Gauge,
256    /// Total number of JIT helper job timeouts.
257    pub jit_helper_timeouts: metrics::Gauge,
258    /// Total number of JIT helper process disconnects.
259    pub jit_helper_disconnects: metrics::Gauge,
260    /// Total number of transitions into paused JIT helper execution.
261    pub pauses_total: metrics::Counter,
262    /// Total number of transitions out of paused JIT helper execution.
263    pub resumes_total: metrics::Counter,
264    /// Whether JIT helper execution is currently paused.
265    pub paused: metrics::Gauge,
266    /// Histogram of total JIT compilation durations (seconds).
267    pub jit_compilation_duration: metrics::Histogram,
268    /// Duration of the last JIT compilation (seconds).
269    pub jit_compilation_duration_last: metrics::Gauge,
270    /// Histogram of parse phase durations (seconds).
271    pub jit_parse_duration: metrics::Histogram,
272    /// Histogram of translate phase durations (seconds).
273    pub jit_translate_duration: metrics::Histogram,
274    /// Histogram of optimize phase durations (seconds).
275    pub jit_optimize_duration: metrics::Histogram,
276    /// Histogram of codegen phase durations (seconds).
277    pub jit_codegen_duration: metrics::Histogram,
278}
279
280#[cfg(feature = "jit")]
281impl RevmcMetrics {
282    /// Records a [`RuntimeStatsSnapshot`] into the metrics.
283    pub fn record(&self, stats: &RuntimeStatsSnapshot) {
284        let RuntimeStatsSnapshot {
285            lookup_hits,
286            lookup_misses,
287            events_dropped,
288            resident_entries,
289            events_queued,
290            command_queue_len,
291            pending_jobs,
292            jit_code_bytes,
293            jit_data_bytes,
294            evictions,
295            compilations_dispatched,
296            compilations_succeeded,
297            compilations_failed,
298            jit_helper_spawns,
299            jit_helper_spawn_failures,
300            jit_helper_restarts,
301            jit_helper_timeouts,
302            jit_helper_disconnects,
303            ..
304        } = *stats;
305        self.lookup_hits.set(lookup_hits as f64);
306        self.lookup_misses.set(lookup_misses as f64);
307        self.events_queued.set(events_queued as f64);
308        self.events_dropped.set(events_dropped as f64);
309        self.resident_entries.set(resident_entries as f64);
310        self.jit_code_bytes.set(jit_code_bytes as f64);
311        self.jit_data_bytes.set(jit_data_bytes as f64);
312        self.command_queue_len.set(command_queue_len as f64);
313        self.pending_jobs.set(pending_jobs as f64);
314        self.evictions.set(evictions as f64);
315        self.compilations_dispatched.set(compilations_dispatched as f64);
316        self.compilations_succeeded.set(compilations_succeeded as f64);
317        self.compilations_failed.set(compilations_failed as f64);
318        self.jit_helper_spawns.set(jit_helper_spawns as f64);
319        self.jit_helper_spawn_failures.set(jit_helper_spawn_failures as f64);
320        self.jit_helper_restarts.set(jit_helper_restarts as f64);
321        self.jit_helper_timeouts.set(jit_helper_timeouts as f64);
322        self.jit_helper_disconnects.set(jit_helper_disconnects as f64);
323    }
324
325    /// Records a [`CompilationEvent`] into the histogram metrics.
326    pub fn record_compilation(&self, event: &CompilationEvent) {
327        let duration_secs = event.duration.as_secs_f64();
328        self.jit_compilation_duration.record(duration_secs);
329        self.jit_compilation_duration_last.set(duration_secs);
330        self.jit_parse_duration.record(event.timings.parse.as_secs_f64());
331        self.jit_translate_duration.record(event.timings.translate.as_secs_f64());
332        self.jit_optimize_duration.record(event.timings.optimize.as_secs_f64());
333        self.jit_codegen_duration.record(event.timings.codegen.as_secs_f64());
334    }
335}