Skip to main content

revmc_backend/
traits.rs

1use crate::{Pointer, Result};
2use ruint::aliases::U256;
3use std::{
4    fmt,
5    path::{Path, PathBuf},
6};
7
8/// Backend configuration.
9///
10/// Collects all tuneable settings that [`EvmCompiler`](crate::Backend) forwards to the backend.
11/// Backends receive a full snapshot via [`Backend::apply_config`] whenever the compiler
12/// changes a setting, so they can apply side-effects (e.g. toggling ASM verbosity) in one place.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct BackendConfig {
15    /// Optimization level.
16    pub opt_level: OptimizationLevel,
17    /// Whether IR output is being dumped (enables verbose names, asm comments, etc.).
18    pub is_dumping: bool,
19    /// Whether to enable debug assertions in generated code.
20    pub debug_assertions: bool,
21    /// Whether to enable JIT debug support (GDB/LLDB registration).
22    ///
23    /// Applied once per process on first JIT compilation.
24    pub debug_support: bool,
25    /// Whether to enable JIT profiling support (perf jitdump).
26    ///
27    /// Applied once per process on first JIT compilation.
28    pub profiling_support: bool,
29    /// Whether to enable the simple perf map plugin.
30    ///
31    /// Writes `/tmp/perf-<pid>.map` in the perf map format so that profilers
32    /// can resolve JIT-compiled symbols without the jitdump machinery.
33    ///
34    /// Not suitable for long-running programs. The map file is append-only
35    /// and never cleaned up, so entries for freed JIT code accumulate
36    /// indefinitely. Prefer [`profiling_support`](Self::profiling_support)
37    /// (jitdump) for long-lived processes.
38    ///
39    /// Applied once per process on first JIT compilation.
40    pub simple_perf: bool,
41    /// Debug info source file path. `Some` enables debug info emission.
42    pub debug_file: Option<PathBuf>,
43}
44
45impl Default for BackendConfig {
46    fn default() -> Self {
47        Self {
48            opt_level: OptimizationLevel::Default,
49            is_dumping: false,
50            debug_assertions: cfg!(debug_assertions),
51            debug_support: true,
52            profiling_support: false,
53            simple_perf: true,
54            debug_file: None,
55        }
56    }
57}
58
59/// Optimization level.
60#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
61pub enum OptimizationLevel {
62    /// No optimizations.
63    None,
64    /// Less optimizations.
65    Less,
66    /// Default optimizations. Highly recommended.
67    #[default]
68    Default,
69    /// Aggressive optimizations.
70    ///
71    /// Not recommended, since it's generally a lot slower and doesn't really produce better code
72    /// than the default level.
73    Aggressive,
74}
75
76impl std::str::FromStr for OptimizationLevel {
77    type Err = String;
78
79    fn from_str(s: &str) -> Result<Self, Self::Err> {
80        Ok(match s {
81            "0" | "none" => Self::None,
82            "1" | "less" => Self::Less,
83            "2" | "default" => Self::Default,
84            "3" | "aggressive" => Self::Aggressive,
85            _ => return Err(format!("unknown optimization level: {s}")),
86        })
87    }
88}
89
90/// Integer comparison condition.
91#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
92pub enum IntCC {
93    /// `==`.
94    Equal,
95    /// `!=`.
96    NotEqual,
97    /// Signed `<`.
98    SignedLessThan,
99    /// Signed `>=`.
100    SignedGreaterThanOrEqual,
101    /// Signed `>`.
102    SignedGreaterThan,
103    /// Signed `<=`.
104    SignedLessThanOrEqual,
105    /// Unsigned `<`.
106    UnsignedLessThan,
107    /// Unsigned `>=`.
108    UnsignedGreaterThanOrEqual,
109    /// Unsigned `>`.
110    UnsignedGreaterThan,
111    /// Unsigned `<=`.
112    UnsignedLessThanOrEqual,
113}
114
115/// Function or parameter attribute.
116///
117/// Mostly copied from [LLVM](https://llvm.org/docs/LangRef.html).
118#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119#[non_exhaustive]
120pub enum Attribute {
121    // Function attributes.
122    WillReturn,
123    NoReturn,
124    NoFree,
125    NoRecurse,
126    NoSync,
127    NoUnwind,
128    NonLazyBind,
129    UWTable,
130    AllFramePointers,
131    NativeTargetCpu,
132    Cold,
133    Hot,
134    HintInline,
135    AlwaysInline,
136    NoInline,
137    Speculatable,
138
139    // Parameter attributes.
140    NoAlias,
141    NoCapture,
142    NoUndef,
143    Align(u64),
144    NonNull,
145    Dereferenceable(u64),
146    /// Size of the return type in bytes.
147    SRet(u64),
148    ReadNone,
149    ReadOnly,
150    WriteOnly,
151    Writable,
152    /// `memory(argmem: readwrite)` — function only accesses memory through pointer arguments.
153    ArgMemOnly,
154    /// `initializes((0, N))` — function initializes bytes `[0, N)` through this pointer.
155    Initializes(u64),
156    /// `dead_on_return` — the contents of the pointed-to memory are dead after the function
157    /// returns, allowing the caller to elide stores to the memory.
158    DeadOnReturn,
159    // TODO: Range?
160}
161
162/// Linkage type.
163#[derive(Clone, Copy, Debug, PartialEq, Eq)]
164pub enum Linkage {
165    /// Defined outside of the module.
166    Import,
167    /// Defined in the module and visible outside.
168    Public,
169    /// Defined in the module, but not visible outside.
170    Private,
171}
172
173/// Determines where on a function an attribute is assigned to.
174#[derive(Clone, Copy, Debug, PartialEq, Eq)]
175pub enum FunctionAttributeLocation {
176    /// Assign to the function's return type.
177    Return,
178    /// Assign to one of the function's params (0-indexed).
179    Param(u32),
180    /// Assign to the function itself.
181    Function,
182}
183
184/// Calling convention.
185#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
186pub enum CallConv {
187    #[default]
188    Default,
189    /// Preserve most caller registers across the call to reduce callsite register pressure.
190    Cold,
191}
192
193/// Tail call kind.
194#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
195pub enum TailCallKind {
196    #[default]
197    None,
198    Tail,
199    MustTail,
200    NoTail,
201}
202
203pub trait BackendTypes: Sized {
204    type Type: Copy + Eq + fmt::Debug;
205    type Value: Copy + Eq + fmt::Debug;
206    type StackSlot: Copy + Eq + fmt::Debug;
207    type BasicBlock: Copy + Eq + fmt::Debug;
208    type Function: Copy + Eq + fmt::Debug;
209}
210
211#[allow(clippy::missing_safety_doc)]
212pub trait Backend: BackendTypes + TypeMethods {
213    type Builder<'a>: Builder<
214            Type = Self::Type,
215            Value = Self::Value,
216            StackSlot = Self::StackSlot,
217            BasicBlock = Self::BasicBlock,
218            Function = Self::Function,
219        >
220    where
221        Self: 'a;
222    type FuncId: Copy + Eq + std::hash::Hash + fmt::Debug;
223
224    fn ir_extension(&self) -> &'static str;
225
226    fn set_module_name(&mut self, name: &str);
227
228    /// Returns the current backend configuration.
229    fn config(&self) -> &BackendConfig;
230
231    /// Applies the given configuration snapshot.
232    ///
233    /// Backends should apply any side-effects (e.g. toggling ASM verbosity) here.
234    fn apply_config(&mut self, config: BackendConfig);
235
236    /// Finalizes any pending debug info metadata.
237    ///
238    /// Must be called before verification, optimization, or code emission.
239    fn finalize_debug_info(&mut self) -> Result<()> {
240        Ok(())
241    }
242    fn dump_ir(&mut self, path: &Path) -> Result<()>;
243    fn dump_disasm(&mut self, path: &Path) -> Result<()>;
244
245    fn is_aot(&self) -> bool;
246
247    fn function_name_is_unique(&self, name: &str) -> bool;
248
249    fn build_function(
250        &mut self,
251        name: &str,
252        ret: Option<Self::Type>,
253        params: &[Self::Type],
254        param_names: &[&str],
255        linkage: Linkage,
256    ) -> Result<(Self::Builder<'_>, Self::FuncId)>;
257    fn verify_module(&mut self) -> Result<()>;
258    fn optimize_module(&mut self) -> Result<()>;
259    fn write_object<W: std::io::Write>(&mut self, w: W) -> Result<()>;
260    fn jit_function(&mut self, id: Self::FuncId) -> Result<usize>;
261
262    /// Returns the name of a compiled function by its ID.
263    fn function_name(&self, id: Self::FuncId) -> Option<&str>;
264
265    /// Returns the estimated sizes of compiled functions as `(name, size)` pairs.
266    fn function_sizes(&self) -> Vec<(String, usize)> {
267        Vec::new()
268    }
269
270    /// Clears the IR module, freeing memory used by IR representations.
271    ///
272    /// This does **not** free JIT-compiled machine code, so previously obtained function pointers
273    /// remain valid. The module is left in a state where new functions can be translated.
274    fn clear_ir(&mut self) -> Result<()>;
275
276    unsafe fn free_function(&mut self, id: Self::FuncId) -> Result<()>;
277    unsafe fn free_all_functions(&mut self) -> Result<()>;
278}
279
280pub trait TypeMethods: BackendTypes {
281    fn type_ptr(&self) -> Self::Type;
282    fn type_ptr_sized_int(&self) -> Self::Type;
283    fn type_int(&self, bits: u32) -> Self::Type;
284    fn type_array(&self, ty: Self::Type, size: u32) -> Self::Type;
285    fn type_bit_width(&self, ty: Self::Type) -> u32;
286}
287
288pub trait Builder: BackendTypes + TypeMethods {
289    fn create_block(&mut self, name: &str) -> Self::BasicBlock;
290    fn create_block_after(&mut self, after: Self::BasicBlock, name: &str) -> Self::BasicBlock;
291    fn switch_to_block(&mut self, block: Self::BasicBlock);
292    fn seal_block(&mut self, block: Self::BasicBlock);
293    fn seal_all_blocks(&mut self);
294    fn set_current_block_cold(&mut self);
295    fn current_block(&mut self) -> Option<Self::BasicBlock>;
296    fn block_addr(&mut self, block: Self::BasicBlock) -> Option<Self::Value>;
297
298    fn add_comment_to_current_inst(&mut self, comment: &str);
299
300    /// Sets the current debug source location for subsequently emitted instructions.
301    fn set_debug_location(&mut self, _line: u32, _col: u32) {}
302
303    /// Clears the current debug source location.
304    fn clear_debug_location(&mut self) {}
305
306    fn fn_param(&mut self, index: usize) -> Self::Value;
307    fn num_fn_params(&self) -> usize;
308
309    fn bool_const(&mut self, value: bool) -> Self::Value;
310    /// Sign-extends negative values to `ty`.
311    fn iconst(&mut self, ty: Self::Type, value: i64) -> Self::Value;
312    fn uconst(&mut self, ty: Self::Type, value: u64) -> Self::Value;
313    fn iconst_256(&mut self, value: impl TryInto<U256>) -> Self::Value;
314    fn cstr_const(&mut self, value: &std::ffi::CStr) -> Self::Value {
315        self.str_const(value.to_str().unwrap())
316    }
317    fn str_const(&mut self, value: &str) -> Self::Value;
318    fn nullptr(&mut self) -> Self::Value;
319
320    fn new_stack_slot(&mut self, ty: Self::Type, name: &str) -> Pointer<Self> {
321        Pointer::new_stack_slot(self, ty, name)
322    }
323    fn new_stack_slot_raw(&mut self, ty: Self::Type, name: &str) -> Self::StackSlot;
324    fn stack_load(&mut self, ty: Self::Type, slot: Self::StackSlot, name: &str) -> Self::Value;
325    fn stack_store(&mut self, value: Self::Value, slot: Self::StackSlot);
326    fn stack_addr(&mut self, ty: Self::Type, slot: Self::StackSlot) -> Self::Value;
327
328    /// Loads a value from a pointer, assuming natural alignment.
329    fn load(&mut self, ty: Self::Type, ptr: Self::Value, name: &str) -> Self::Value;
330    /// Loads a value from a pointer with an explicit alignment override.
331    fn load_aligned(
332        &mut self,
333        ty: Self::Type,
334        ptr: Self::Value,
335        align: usize,
336        name: &str,
337    ) -> Self::Value;
338    /// Stores a value to a pointer, assuming natural alignment.
339    fn store(&mut self, value: Self::Value, ptr: Self::Value);
340    /// Stores a value to a pointer with an explicit alignment override.
341    fn store_aligned(&mut self, value: Self::Value, ptr: Self::Value, align: usize);
342
343    fn nop(&mut self);
344    fn ret(&mut self, values: &[Self::Value]);
345    fn assume(&mut self, cond: Self::Value) {
346        let _ = cond;
347    }
348
349    fn icmp(&mut self, cond: IntCC, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
350    fn icmp_imm(&mut self, cond: IntCC, lhs: Self::Value, rhs: i64) -> Self::Value;
351    fn is_null(&mut self, ptr: Self::Value) -> Self::Value;
352    fn is_not_null(&mut self, ptr: Self::Value) -> Self::Value;
353
354    fn br(&mut self, dest: Self::BasicBlock);
355    fn brif(
356        &mut self,
357        cond: Self::Value,
358        then_block: Self::BasicBlock,
359        else_block: Self::BasicBlock,
360    );
361    fn brif_cold(
362        &mut self,
363        cond: Self::Value,
364        then_block: Self::BasicBlock,
365        else_block: Self::BasicBlock,
366        then_is_cold: bool,
367    ) {
368        let _ = then_is_cold;
369        self.brif(cond, then_block, else_block)
370    }
371    fn switch(
372        &mut self,
373        index: Self::Value,
374        default: Self::BasicBlock,
375        targets: &[(u64, Self::BasicBlock)],
376        default_is_cold: bool,
377    );
378    fn br_indirect(&mut self, address: Self::Value, destinations: &[Self::BasicBlock]);
379    fn phi(&mut self, ty: Self::Type, incoming: &[(Self::Value, Self::BasicBlock)]) -> Self::Value;
380    fn select(
381        &mut self,
382        cond: Self::Value,
383        then_value: Self::Value,
384        else_value: Self::Value,
385    ) -> Self::Value;
386
387    fn iadd(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
388    fn isub(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
389    fn imul(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
390    fn udiv(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
391    fn sdiv(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
392    fn urem(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
393    fn srem(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
394
395    fn iadd_imm(&mut self, lhs: Self::Value, rhs: i64) -> Self::Value;
396    fn isub_imm(&mut self, lhs: Self::Value, rhs: i64) -> Self::Value;
397    fn imul_imm(&mut self, lhs: Self::Value, rhs: i64) -> Self::Value;
398
399    // `(result, overflow)`
400    fn uadd_overflow(&mut self, lhs: Self::Value, rhs: Self::Value) -> (Self::Value, Self::Value);
401    fn usub_overflow(&mut self, lhs: Self::Value, rhs: Self::Value) -> (Self::Value, Self::Value);
402
403    fn uadd_sat(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
404
405    fn umax(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
406    fn umin(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
407    fn bswap(&mut self, value: Self::Value) -> Self::Value;
408
409    fn bitor(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
410    fn bitand(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
411    fn bitxor(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
412    fn bitnot(&mut self, value: Self::Value) -> Self::Value;
413    fn clz(&mut self, value: Self::Value) -> Self::Value;
414
415    fn bitor_imm(&mut self, lhs: Self::Value, rhs: i64) -> Self::Value;
416    fn bitand_imm(&mut self, lhs: Self::Value, rhs: i64) -> Self::Value;
417    fn bitxor_imm(&mut self, lhs: Self::Value, rhs: i64) -> Self::Value;
418
419    fn ishl(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
420    fn ushr(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
421    fn sshr(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
422
423    fn zext(&mut self, ty: Self::Type, value: Self::Value) -> Self::Value;
424    fn sext(&mut self, ty: Self::Type, value: Self::Value) -> Self::Value;
425    #[doc(alias = "trunc")]
426    fn ireduce(&mut self, to: Self::Type, value: Self::Value) -> Self::Value;
427
428    /// Converts an integer value to a pointer.
429    fn inttoptr(&mut self, value: Self::Value, ty: Self::Type) -> Self::Value;
430
431    fn gep(
432        &mut self,
433        ty: Self::Type,
434        ptr: Self::Value,
435        indexes: &[Self::Value],
436        name: &str,
437    ) -> Self::Value;
438
439    #[must_use]
440    fn call(&mut self, function: Self::Function, args: &[Self::Value]) -> Option<Self::Value> {
441        self.tail_call(function, args, TailCallKind::None)
442    }
443    #[must_use]
444    fn tail_call(
445        &mut self,
446        function: Self::Function,
447        args: &[Self::Value],
448        tail_call: TailCallKind,
449    ) -> Option<Self::Value>;
450
451    /// Returns `Some(is_value_compile_time)`, or `None` if unsupported.
452    fn is_compile_time_known(&mut self, value: Self::Value) -> Option<Self::Value>;
453
454    fn memcpy(&mut self, dst: Self::Value, src: Self::Value, len: Self::Value);
455    fn memcpy_inline(&mut self, dst: Self::Value, src: Self::Value, len: i64) {
456        let len = self.iconst(self.type_int(64), len);
457        self.memcpy(dst, src, len);
458    }
459
460    fn unreachable(&mut self);
461
462    fn get_or_build_function(
463        &mut self,
464        name: &str,
465        params: &[Self::Type],
466        ret: Option<Self::Type>,
467        linkage: Linkage,
468        build: impl FnOnce(&mut Self),
469    ) -> Self::Function;
470
471    fn get_function(&mut self, name: &str) -> Option<Self::Function>;
472
473    fn get_printf_function(&mut self) -> Self::Function;
474
475    /// Adds a function to the module that's located at `address`.
476    ///
477    /// If `address` is `None`, the function must be built.
478    fn add_function(
479        &mut self,
480        name: &str,
481        params: &[Self::Type],
482        ret: Option<Self::Type>,
483        address: Option<usize>,
484        linkage: Linkage,
485        call_conv: CallConv,
486    ) -> Self::Function;
487
488    /// Adds a local stub with the given calling convention for an existing function.
489    fn add_function_stub(
490        &mut self,
491        function: Self::Function,
492        call_conv: CallConv,
493    ) -> Self::Function {
494        let _ = call_conv;
495        function
496    }
497
498    /// Adds an attribute to a function, one of its parameters, or its return value.
499    ///
500    /// If `function` is `None`, the attribute is added to the current function.
501    fn add_function_attribute(
502        &mut self,
503        function: Option<Self::Function>,
504        attribute: Attribute,
505        loc: FunctionAttributeLocation,
506    );
507}
508
509#[cfg(test)]
510mod tests {
511    use super::{BackendConfig, OptimizationLevel};
512    use std::{path::PathBuf, str::FromStr};
513
514    #[test]
515    fn backend_config_defaults_match_runtime_settings() {
516        let config = BackendConfig::default();
517
518        assert_eq!(config.opt_level, OptimizationLevel::Default);
519        assert!(!config.is_dumping);
520        assert_eq!(config.debug_assertions, cfg!(debug_assertions));
521        assert!(config.debug_support);
522        assert!(!config.profiling_support);
523        assert!(config.simple_perf);
524        assert_eq!(config.debug_file, None);
525    }
526
527    #[test]
528    fn backend_config_is_cloneable_and_comparable() {
529        let config = BackendConfig {
530            opt_level: OptimizationLevel::Aggressive,
531            is_dumping: true,
532            debug_assertions: true,
533            debug_support: false,
534            profiling_support: true,
535            simple_perf: false,
536            debug_file: Some(PathBuf::from("debug.sol")),
537        };
538
539        assert_eq!(config.clone(), config);
540    }
541
542    #[test]
543    fn optimization_level_parses_numeric_and_named_values() {
544        for (input, expected) in [
545            ("0", OptimizationLevel::None),
546            ("none", OptimizationLevel::None),
547            ("1", OptimizationLevel::Less),
548            ("less", OptimizationLevel::Less),
549            ("2", OptimizationLevel::Default),
550            ("default", OptimizationLevel::Default),
551            ("3", OptimizationLevel::Aggressive),
552            ("aggressive", OptimizationLevel::Aggressive),
553        ] {
554            assert_eq!(OptimizationLevel::from_str(input), Ok(expected));
555        }
556    }
557
558    #[test]
559    fn optimization_level_reports_unknown_values() {
560        assert_eq!(
561            OptimizationLevel::from_str("fast").unwrap_err(),
562            "unknown optimization level: fast"
563        );
564    }
565}