Skip to main content

revmc_builtins/
ir.rs

1use revmc_backend::{
2    Attribute, Backend, Builder, CallConv, FunctionAttributeLocation, TypeMethods,
3};
4
5// Must be kept in sync with `remvc-build`.
6const MANGLE_PREFIX: &str = "__revmc_builtin_";
7
8/// Builtin cache.
9#[derive(Debug)]
10pub struct Builtins<B: Backend>([Option<B::Function>; Builtin::COUNT]);
11
12unsafe impl<B: Backend> Send for Builtins<B> {}
13
14impl<B: Backend> Default for Builtins<B> {
15    fn default() -> Self {
16        Self::new()
17    }
18}
19
20impl<B: Backend> Builtins<B> {
21    /// Create a new cache.
22    pub fn new() -> Self {
23        Self([None; Builtin::COUNT])
24    }
25
26    /// Clear the cache.
27    pub fn clear(&mut self) {
28        *self = Self::new();
29    }
30
31    /// Get the function for the given builtin.
32    pub fn get(&mut self, builtin: Builtin, bcx: &mut B::Builder<'_>) -> B::Function {
33        *self.0[builtin as usize].get_or_insert_with(|| Self::init(builtin, bcx))
34    }
35
36    #[cold]
37    fn init(builtin: Builtin, bcx: &mut B::Builder<'_>) -> B::Function {
38        let name = builtin.name();
39        debug_assert!(name.starts_with(MANGLE_PREFIX), "{name:?}");
40        if builtin.call_conv() == CallConv::Default
41            && let Some(r) = bcx.get_function(name)
42        {
43            trace!(name, ?r, "pre-existing");
44            return r;
45        }
46
47        let r = Self::build(name, builtin, bcx);
48        trace!(name, ?r, "built");
49        r
50    }
51
52    fn build(name: &str, builtin: Builtin, bcx: &mut B::Builder<'_>) -> B::Function {
53        let ret = builtin.ret(bcx);
54        let params = builtin.params(bcx);
55        let address = builtin.addr();
56        let linkage = revmc_backend::Linkage::Import;
57        let f = bcx.add_function(name, &params, ret, Some(address), linkage, CallConv::Default);
58        Self::add_attrs(builtin, f, bcx);
59        match builtin.call_conv() {
60            CallConv::Default => f,
61            call_conv => {
62                let f = bcx.add_function_stub(f, call_conv);
63                Self::add_attrs(builtin, f, bcx);
64                f
65            }
66        }
67    }
68
69    fn add_attrs(builtin: Builtin, f: B::Function, bcx: &mut B::Builder<'_>) {
70        let param_attrs = builtin.param_attrs();
71        let mut attrs = Vec::with_capacity(16);
72        attrs.extend(builtin.attrs());
73        attrs.extend([
74            Attribute::NoFree,
75            Attribute::NoRecurse,
76            Attribute::NoSync,
77            Attribute::NoUnwind,
78        ]);
79        // `argmem` is only valid if the function does not access memory reachable through pointers
80        // *loaded* from its arguments (only memory directly derived from arguments via
81        // GEP/bitcast). Builtins that take a writable `EvmContext` mutate state through
82        // `ecx.host`, `ecx.gas`, etc., which are loaded from `ecx` and thus outside
83        // `argmem`. Apply `ArgMemOnly` only when no parameter is a writable `EvmContext`.
84        let evm_ctx_size = core::mem::size_of::<revmc_context::EvmContext<'static>>() as u64;
85        let writes_ecx = param_attrs.iter().any(|p| {
86            let writable = p.iter().any(|a| matches!(a, Attribute::Writable));
87            let is_ecx =
88                p.iter().any(|a| matches!(a, Attribute::Dereferenceable(s) if *s == evm_ctx_size));
89            writable && is_ecx
90        });
91        if !writes_ecx {
92            attrs.push(Attribute::ArgMemOnly);
93        }
94        for attr in attrs {
95            bcx.add_function_attribute(Some(f), attr, FunctionAttributeLocation::Function);
96        }
97        for (i, param_attrs) in param_attrs.iter().enumerate() {
98            for attr in param_attrs {
99                bcx.add_function_attribute(
100                    Some(f),
101                    *attr,
102                    FunctionAttributeLocation::Param(i as u32),
103                );
104            }
105        }
106    }
107}
108
109macro_rules! builtins {
110    (@count) => { 0 };
111    (@count $first:tt $(, $rest:tt)*) => { 1 + builtins!(@count $($rest),*) };
112
113    (@param_attr $default:ident) => { $default() };
114    (@param_attr $default:ident $name:expr) => { $name };
115
116    (@types |$bcx:ident| { $($types_init:tt)* }
117     @param_attrs |$op:ident| { $($attrs_init:tt)* }
118     $($ident:ident = $(#[$attr:expr])* $name:ident($($(@[$param_attr:expr])? $params:expr),* $(,)?) $ret:expr),* $(,)?
119    ) => { paste::paste! {
120        /// Builtins that can be called by the compiled functions.
121        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
122        pub enum Builtin {
123            $($ident,)*
124        }
125
126        #[allow(unused_variables)]
127        impl Builtin {
128            pub const COUNT: usize = builtins!(@count $($ident),*);
129
130            pub const fn name(self) -> &'static str {
131                match self {
132                    $(Self::$ident => stringify!($name),)*
133                }
134            }
135
136            pub fn addr(self) -> usize {
137                match self {
138                    $(Self::$ident => crate::$name as *const () as usize,)*
139                }
140            }
141
142            pub fn ret<B: TypeMethods>(self, $bcx: &mut B) -> Option<B::Type> {
143                $($types_init)*
144                match self {
145                    $(Self::$ident => $ret,)*
146                }
147            }
148
149            pub fn params<B: TypeMethods>(self, $bcx: &mut B) -> Vec<B::Type> {
150                $($types_init)*
151                match self {
152                    $(Self::$ident => vec![$($params),*],)*
153                }
154            }
155
156            pub fn attrs(self) -> &'static [Attribute] {
157                #[allow(unused_imports)]
158                use Attribute::*;
159                match self {
160                    $(Self::$ident => &[$($attr),*]),*
161                }
162            }
163
164            #[allow(non_upper_case_globals)]
165            pub fn param_attrs(self) -> Vec<Vec<Attribute>> {
166                #[allow(unused_imports)]
167                use Attribute::*;
168                let $op = self;
169                let default = || vec![Attribute::NoUndef];
170                $($attrs_init)*
171                match self {
172                    $(Self::$ident => vec![$(builtins!(@param_attr default $($param_attr)?)),*]),*
173                }
174            }
175
176            fn op(self) -> u8 {
177                use revm_bytecode::opcode::*;
178
179                // _in_out
180                const _0_0: u8 = STOP;
181                const _0_1: u8 = PUSH0;
182                const _1_0: u8 = POP;
183
184                const PANIC: u8 = _0_0;
185                const ASSERTSPECID: u8 = _0_0;
186
187                const EXPGAS: u8 = _1_0;
188                const KECCAK256CC: u8 = _0_1;
189
190                const CALLDATALOADC: u8 = _0_1;
191                const SLOADC: u8 = _0_1;
192
193                const MRESIZE: u8 = _0_0;
194
195                const LOG: u8 = _0_0;
196                const DORETURN: u8 = RETURN;
197                const DORETURNCC: u8 = _0_0;
198
199                match self {
200                    $(Self::$ident => [<$ident:upper>]),*
201                }
202            }
203        }
204    }};
205}
206
207builtins! {
208    @types |bcx| {
209        let ptr = bcx.type_ptr();
210        let usize = bcx.type_ptr_sized_int();
211        let u8 = bcx.type_int(8);
212    }
213
214    @param_attrs |op| {
215        fn size_and_align<T>() -> Vec<Attribute> {
216            size_and_align_with(Some(core::mem::size_of::<T>()), core::mem::align_of::<T>())
217        }
218
219        fn size_and_align_with(size: Option<usize>, align: usize) -> Vec<Attribute> {
220            let mut vec = Vec::with_capacity(8);
221            vec.push(Attribute::NoAlias);
222            vec.push(Attribute::NoCapture);
223            vec.push(Attribute::NoUndef);
224            vec.push(Attribute::Align(align as u64));
225            if let Some(size) = size {
226                vec.push(Attribute::Dereferenceable(size as u64));
227            }
228            vec
229        }
230
231        let op = op.op();
232        let (inputs, outputs) = if let Some(info) = revm_bytecode::opcode::OPCODE_INFO[op as usize] {
233            (info.inputs(), info.outputs())
234        } else {
235            (0, 0)
236        };
237
238        let ecx_base = size_and_align::<revmc_context::EvmContext<'static>>();
239        let mut ecx = ecx_base.clone();
240        ecx.push(Attribute::Writable);
241        let mut ecx_ro = ecx_base;
242        ecx_ro.push(Attribute::ReadOnly);
243
244        let sp_base = size_and_align_with(None, core::mem::align_of::<revmc_context::EvmWord>());
245        let mut sp_dyn = sp_base.clone();
246        sp_dyn.push(Attribute::Writable);
247
248        let mut sp = sp_base;
249        // `sp` is at `top - inputs`, we have access to `max(inputs, outputs)` words.
250        let n_stack_words = inputs.max(outputs);
251        let size_of_word = core::mem::size_of::<revmc_context::EvmWord>();
252        sp.push(Attribute::Dereferenceable(size_of_word as u64 * n_stack_words as u64));
253        match (inputs, outputs) {
254            (0, 0) => sp.push(Attribute::ReadNone),
255            (0, 1..) => {
256                sp.push(Attribute::Writable);
257                sp.push(Attribute::WriteOnly);
258                sp.push(Attribute::Initializes(size_of_word as u64 * outputs as u64));
259            }
260            (1.., 0) => sp.push(Attribute::ReadOnly),
261            (1.., 1..) => sp.push(Attribute::Writable),
262        }
263    }
264
265    Panic          = #[Cold] #[NoReturn] __revmc_builtin_panic(ptr, usize) None,
266    AssertSpecId   = __revmc_builtin_assert_spec_id(@[ecx] ptr, u8) None,
267
268    Div            = __revmc_builtin_div(@[sp] ptr) None,
269    SDiv           = __revmc_builtin_sdiv(@[sp] ptr) None,
270    Mod            = __revmc_builtin_mod(@[sp] ptr) None,
271    SMod           = __revmc_builtin_smod(@[sp] ptr) None,
272    AddMod         = __revmc_builtin_addmod(@[sp] ptr) None,
273    MulMod         = __revmc_builtin_mulmod(@[sp] ptr) None,
274    Exp            = __revmc_builtin_exp(@[ecx] ptr, @[sp] ptr) None,
275    ExpGas         = __revmc_builtin_exp_gas(@[ecx] ptr, @[sp] ptr) None,
276    Keccak256      = __revmc_builtin_keccak256(@[ecx] ptr, @[sp] ptr) None,
277    Keccak256CC    = __revmc_builtin_keccak256_cc(@[ecx] ptr, @[sp] ptr, usize, usize) None,
278    Balance        = __revmc_builtin_balance(@[ecx] ptr, @[sp] ptr) None,
279    Origin         = __revmc_builtin_origin(@[ecx_ro] ptr, @[sp] ptr) None,
280    CallDataLoad   = __revmc_builtin_calldataload(@[ecx_ro] ptr, @[sp] ptr) None,
281    CallDataLoadC  = __revmc_builtin_calldataload_c(@[ecx_ro] ptr, @[sp] ptr, usize) None,
282    CallDataCopy   = __revmc_builtin_calldatacopy(@[ecx] ptr, @[sp] ptr) None,
283    CodeCopy       = __revmc_builtin_codecopy(@[ecx] ptr, @[sp] ptr) None,
284    GasPrice       = __revmc_builtin_gas_price(@[ecx_ro] ptr, @[sp] ptr) None,
285    ExtCodeSize    = __revmc_builtin_extcodesize(@[ecx] ptr, @[sp] ptr) None,
286    ExtCodeCopy    = __revmc_builtin_extcodecopy(@[ecx] ptr, @[sp] ptr) None,
287    ReturnDataCopy = __revmc_builtin_returndatacopy(@[ecx] ptr, @[sp] ptr) None,
288    ExtCodeHash    = __revmc_builtin_extcodehash(@[ecx] ptr, @[sp] ptr) None,
289    BlockHash      = __revmc_builtin_blockhash(@[ecx] ptr, @[sp] ptr) None,
290    Coinbase       = __revmc_builtin_coinbase(@[ecx_ro] ptr, @[sp] ptr) None,
291    Timestamp      = __revmc_builtin_timestamp(@[ecx_ro] ptr, @[sp] ptr) None,
292    Number         = __revmc_builtin_number(@[ecx_ro] ptr, @[sp] ptr) None,
293    Difficulty     = __revmc_builtin_difficulty(@[ecx_ro] ptr, @[sp] ptr) None,
294    GasLimit       = __revmc_builtin_gaslimit(@[ecx_ro] ptr, @[sp] ptr) None,
295    ChainId        = __revmc_builtin_chainid(@[ecx_ro] ptr, @[sp] ptr) None,
296    SelfBalance    = __revmc_builtin_self_balance(@[ecx] ptr, @[sp] ptr) None,
297    Basefee        = __revmc_builtin_basefee(@[ecx_ro] ptr, @[sp] ptr) None,
298    BlobHash       = __revmc_builtin_blob_hash(@[ecx_ro] ptr, @[sp] ptr) None,
299    BlobBaseFee    = __revmc_builtin_blob_base_fee(@[ecx_ro] ptr, @[sp] ptr) None,
300    SlotNum        = __revmc_builtin_slot_num(@[ecx_ro] ptr, @[sp] ptr) None,
301    Mresize        = __revmc_builtin_mresize(@[ecx] ptr, usize) None,
302    Sload          = __revmc_builtin_sload(@[ecx] ptr, @[sp] ptr) None,
303    SloadC         = __revmc_builtin_sload_c(@[ecx] ptr, @[sp] ptr, usize) None,
304    Sstore         = __revmc_builtin_sstore(@[ecx] ptr, @[sp] ptr) None,
305    Tload          = __revmc_builtin_tload(@[ecx] ptr, @[sp] ptr) None,
306    Tstore         = __revmc_builtin_tstore(@[ecx] ptr, @[sp] ptr) None,
307    Mcopy          = __revmc_builtin_mcopy(@[ecx] ptr, @[sp] ptr) None,
308    Log            = __revmc_builtin_log(@[ecx] ptr, @[sp_dyn] ptr, u8) None,
309
310    Create         = __revmc_builtin_create(@[ecx] ptr, @[sp_dyn] ptr, u8) None,
311    Call           = __revmc_builtin_call(@[ecx] ptr, @[sp_dyn] ptr, u8) None,
312    DoReturn       = #[NoReturn] __revmc_builtin_do_return(@[ecx] ptr, @[sp] ptr, u8) None,
313    DoReturnCC     = #[NoReturn] __revmc_builtin_do_return_cc(@[ecx] ptr, usize, usize, u8) None,
314    SelfDestruct   = #[NoReturn] __revmc_builtin_selfdestruct(@[ecx] ptr, @[sp] ptr) None,
315}
316
317impl Builtin {
318    pub const fn call_conv(self) -> CallConv {
319        match self {
320            Self::Mresize => CallConv::Cold,
321            _ => CallConv::Default,
322        }
323    }
324}