Skip to main content

revmc_codegen/compiler/translate/
mod.rs

1//! EVM to IR translation.
2
3use super::default_attrs;
4use crate::{
5    Backend, Builder, Bytecode, EvmContext, Inst, InstData, InstFlags, IntCC, Result, StackSection,
6    decode_pair, decode_single,
7};
8use oxc_index::IndexVec;
9use revm_bytecode::opcode as op;
10use revm_interpreter::{InputsImpl, InstructionResult};
11use revm_primitives::U256;
12use revmc_backend::{Attribute, BackendTypes, FunctionAttributeLocation, Pointer, TypeMethods};
13use revmc_builtins::{Builtin, Builtins, CallKind, CreateKind};
14use std::mem;
15
16mod peephole;
17
18mod vstack;
19use vstack::{VSlot, VStack};
20
21const STACK_CAP: usize = 1024;
22// const WORD_SIZE: usize = 32;
23
24#[derive(Clone, Copy, Debug)]
25pub(super) struct FcxConfig {
26    pub(super) comments: bool,
27    pub(super) debug_assertions: bool,
28    pub(super) frame_pointers: bool,
29
30    pub(super) debug: bool,
31    pub(super) inspect_stack: bool,
32    pub(super) stack_bound_checks: bool,
33    pub(super) gas_metering: bool,
34    pub(super) single_error: bool,
35}
36
37impl Default for FcxConfig {
38    fn default() -> Self {
39        Self {
40            debug_assertions: cfg!(debug_assertions),
41            comments: false,
42            frame_pointers: cfg!(debug_assertions) || cfg!(force_frame_pointers),
43            debug: false,
44            inspect_stack: false,
45            stack_bound_checks: true,
46            gas_metering: true,
47            single_error: true,
48        }
49    }
50}
51
52/// A list of incoming values for a block. Represents a `phi` node.
53type Incoming<B> = Vec<(<B as BackendTypes>::Value, <B as BackendTypes>::BasicBlock)>;
54
55/// A list of `switch` targets.
56#[allow(dead_code)]
57type SwitchTargets<B> = Vec<(u64, <B as BackendTypes>::BasicBlock)>;
58
59pub(super) struct FunctionCx<'a, B: Backend> {
60    // Configuration.
61    config: FcxConfig,
62
63    /// The backend's function builder.
64    bcx: B::Builder<'a>,
65
66    // Common types.
67    isize_type: B::Type,
68    word_type: B::Type,
69    address_type: B::Type,
70    i8_type: B::Type,
71
72    // Locals.
73    /// The stack length. Either passed in the arguments as a pointer or allocated locally.
74    stack_len: Pointer<B::Builder<'a>>,
75    /// The stack value. Constant throughout the function, either passed in the arguments as a
76    /// pointer or allocated locally.
77    stack: Pointer<B::Builder<'a>>,
78    /// The stack argument pointer. Only used when `local_stack` is enabled and the stack needs
79    /// to be copied in/out at entry/exit boundaries.
80    sp_arg: Option<B::Value>,
81    /// The EVM context. Opaque pointer, only passed to builtins.
82    ecx: B::Value,
83    /// Stack length before the current instruction.
84    len_before: B::Value,
85    /// Stack length offset for the current instruction, used for push/pop.
86    len_offset: i8,
87
88    /// Section-local virtual stack that caches values as SSA instead of
89    /// immediately storing/loading from the stack alloca.
90    vstack: VStack<B::Value>,
91    /// Stack length at the start of the current stack section, loaded once from the alloca.
92    /// All intra-section `len_before` values are derived from this + `section_len_offset`.
93    section_start_len: B::Value,
94    /// Stack pointer at the start of the current stack section (`&stack[section_start_len]`).
95    /// All intra-section stack pointer GEPs are derived from this base to preserve pointer
96    /// provenance, which lets LLVM prove aliasing and fold redundant operations.
97    section_start_sp: B::Value,
98    /// Cumulative stack diff from the section start to the current instruction (compile-time).
99    /// Updated after the opcode handler so that push/pop/sp helpers see the pre-diff value.
100    section_len_offset: i32,
101    /// The cumulative offset that `len.addr` currently holds relative to `section_start_len`.
102    /// At section start this is 0 (len.addr == section_start_len). After each store it becomes
103    /// `section_len_offset + diff`. Stores are skipped when the new offset matches this value.
104    stored_len_offset: i32,
105    cached_mem_base: Option<B::Value>,
106
107    /// The bytecode being translated.
108    bytecode: &'a Bytecode<'a>,
109    /// Instruction index to 1-based line number in bytecode.txt (for debug info).
110    inst_lines: IndexVec<Inst, u32>,
111    /// All entry blocks for each instruction.
112    inst_entries: IndexVec<Inst, B::BasicBlock>,
113    /// The current instruction being translated.
114    current_inst: Option<Inst>,
115
116    // Basic blocks are `None` when outside of a main function.
117    /// `dynamic_jump_table` incoming values.
118    incoming_dynamic_jumps: Incoming<B>,
119    /// The dynamic jump table block where all dynamic jumps branch to.
120    dynamic_jump_table: B::BasicBlock,
121
122    /// `failure_block` incoming values.
123    incoming_failures: Incoming<B>,
124    /// The block that all failures branch to.
125    failure_block: Option<B::BasicBlock>,
126    /// `return_block` incoming values.
127    incoming_returns: Incoming<B>,
128    /// The return block that all return instructions branch to.
129    return_block: Option<B::BasicBlock>,
130
131    /// `resume_block` switch values.
132    resume_blocks: Vec<B::BasicBlock>,
133    /// `suspend_block` incoming values.
134    suspend_blocks: Incoming<B>,
135    /// The suspend block that all suspend instructions branch to.
136    suspend_block: B::BasicBlock,
137
138    /// Builtins.
139    builtins: &'a mut Builtins<B>,
140}
141
142impl<'a, B: Backend> FunctionCx<'a, B> {
143    /// Translates an EVM bytecode into a native function.
144    ///
145    /// Example pseudo-code:
146    ///
147    /// ```ignore (pseudo-code)
148    /// // `cfg(may_suspend) = bytecode.may_suspend()`: `true` if it contains a
149    /// // `*CALL*` or `CREATE*` instruction.
150    /// fn evm_bytecode(args: ...) {
151    ///     setup_locals();
152    ///
153    ///     #[cfg(debug_assertions)]
154    ///     if args.<ptr>.is_null() { panic!("...") };
155    ///
156    ///     load_arguments();
157    ///
158    ///     #[cfg(may_suspend)]
159    ///     resume: {
160    ///         goto match ecx.resume_at {
161    ///             0 => inst0,
162    ///             1 => first_call_or_create_inst + 1, // + 1 as in the block after.
163    ///             2 => second_call_or_create_inst + 1,
164    ///             ... => ...,
165    ///             _ => unreachable, // Assumed to be valid.
166    ///         };
167    ///     };
168    ///
169    ///     op.inst0: { /* ... */ };
170    ///     op.inst1: { /* ... */ };
171    ///     // ...
172    ///     #[cfg(may_suspend)]
173    ///     first_call_or_create_inst: {
174    ///          // ...
175    ///          goto suspend(1);
176    ///     };
177    ///     // ...
178    ///     // There will always be at least one diverging instruction.
179    ///     op.stop: {
180    ///         goto return(InstructionResult::Stop);
181    ///     };
182    ///
183    ///     #[cfg(may_suspend)]
184    ///     suspend(resume_at: u32): {
185    ///         ecx.resume_at = resume_at;
186    ///         goto return(Ok(())); // Caller checks next_action
187    ///     };
188    ///
189    ///     // All paths lead to here.
190    ///     return(ir: InstructionResult): {
191    ///         #[cfg(inspect_stack)]
192    ///         *args.stack_len = stack_len;
193    ///         return ir;
194    ///     }
195    /// }
196    /// ```
197    #[allow(rustdoc::invalid_rust_codeblocks)] // Syntax highlighting.
198    pub(super) fn translate(
199        mut bcx: B::Builder<'a>,
200        config: FcxConfig,
201        builtins: &'a mut Builtins<B>,
202        bytecode: &'a Bytecode<'a>,
203    ) -> Result<()> {
204        let entry_block = bcx.current_block().unwrap();
205
206        // Clear debug location for prologue code.
207        if config.debug {
208            bcx.clear_debug_location();
209        }
210
211        // Get common types.
212        let isize_type = bcx.type_ptr_sized_int();
213        let i8_type = bcx.type_int(8);
214        let word_type = bcx.type_int(256);
215        let address_type = bcx.type_int(160);
216
217        // Set up entry block.
218        let ecx = bcx.fn_param(0);
219
220        let sp_arg = bcx.fn_param(1);
221        // Use a local alloca for the stack to allow the backend to eliminate dead stores to
222        // stack slots above `stack_len` at function exit (e.g. `PUSH0 POP`).
223        // Disabled when `inspect_stack` is set because the caller observes every store.
224        let local_stack = !config.inspect_stack;
225        let stack = if local_stack {
226            let stack_type = bcx.type_array(word_type, STACK_CAP as u32);
227            bcx.new_stack_slot(stack_type, "stack.addr")
228        } else {
229            Pointer::new_address(word_type, sp_arg)
230        };
231
232        let stack_len_arg = bcx.fn_param(2);
233        // This is initialized later in `post_entry_block`.
234        let stack_len = bcx.new_stack_slot(isize_type, "len.addr");
235
236        // Create all instruction entry blocks. Dead-code instructions map to
237        // `unreachable_block`; dedup redirects are resolved at control-flow callsites.
238        let unreachable_block = bcx.create_block("unreachable");
239        let inst_entries: IndexVec<Inst, _> = bytecode
240            .iter_all_insts()
241            .map(|(i, data)| {
242                if data.is_dead_code() {
243                    unreachable_block
244                } else {
245                    let name = if config.debug { &bytecode.op_block_name(Some(i), "") } else { "" };
246                    bcx.create_block(name)
247                }
248            })
249            .collect();
250        assert!(!inst_entries.is_empty(), "translating empty bytecode");
251
252        let dynamic_jump_table = bcx.create_block("dynamic_jump_table");
253        let suspend_block = bcx.create_block("suspend");
254        let failure_block = bcx.create_block("failure");
255        let return_block = bcx.create_block("return");
256
257        let section_start_sp = stack.addr(&mut bcx);
258        let zero = bcx.iconst(isize_type, 0);
259        let mut fx = FunctionCx {
260            config,
261
262            isize_type,
263            word_type,
264            address_type,
265            i8_type,
266            stack_len,
267            stack,
268            sp_arg: local_stack.then_some(sp_arg),
269            ecx,
270            len_before: zero,
271            len_offset: 0,
272            section_start_len: zero,
273            section_start_sp,
274            section_len_offset: 0,
275            stored_len_offset: 0,
276            cached_mem_base: None,
277            bcx,
278
279            bytecode,
280            inst_lines: if config.debug { bytecode.take_inst_lines() } else { IndexVec::new() },
281            inst_entries,
282            current_inst: None,
283
284            incoming_dynamic_jumps: Vec::new(),
285            dynamic_jump_table,
286
287            incoming_failures: Vec::new(),
288            failure_block: Some(failure_block),
289            incoming_returns: Vec::new(),
290            return_block: Some(return_block),
291
292            resume_blocks: Vec::new(),
293            suspend_blocks: Vec::new(),
294            suspend_block,
295
296            builtins,
297
298            vstack: VStack::default(),
299        };
300
301        // Add debug assertions for the parameters.
302        if config.debug_assertions {
303            // Assert that the runtime spec_id matches the compilation spec_id.
304            let compiled_spec = fx.bcx.iconst(fx.i8_type, bytecode.spec_id as i64);
305            let _ = fx.call_builtin(Builtin::AssertSpecId, &[ecx, compiled_spec]);
306        }
307
308        // The bytecode is guaranteed to have at least one instruction.
309        let first_inst_block = fx.inst_entries[Inst::from_usize(0)];
310        let post_entry_block = fx.bcx.create_block_after(entry_block, "entry.post");
311        let resume_block = fx.bcx.create_block_after(post_entry_block, "resume");
312        fx.bcx.br(post_entry_block);
313
314        // Translate individual instructions into their respective blocks.
315        for (inst, _) in bytecode.iter_insts() {
316            fx.translate_inst(inst)?;
317        }
318
319        // Clear debug location for all synthetic / epilogue blocks.
320        if config.debug {
321            fx.bcx.clear_debug_location();
322        }
323
324        // Finalize the dynamic jump table.
325        fx.bcx.switch_to_block(unreachable_block);
326        fx.bcx.unreachable();
327        if bytecode.has_dynamic_jumps() {
328            fx.bcx.switch_to_block(fx.dynamic_jump_table);
329            let jumpdests = bytecode.iter_insts().filter(|(_, data)| data.opcode == op::JUMPDEST);
330            let targets = jumpdests
331                .map(|(inst, data)| (data.jumpdest_pc() as u64, fx.effective_entry(inst)))
332                .collect::<Vec<_>>();
333            let i64_type = fx.bcx.type_int(64);
334            let index = fx.bcx.phi(i64_type, &fx.incoming_dynamic_jumps);
335            let invalid_jump = fx.add_invalid_jump();
336            fx.bcx.switch(index, invalid_jump, &targets, true);
337        } else {
338            // No dynamic jumps.
339            debug_assert!(fx.incoming_dynamic_jumps.is_empty());
340            fx.bcx.switch_to_block(fx.dynamic_jump_table);
341            fx.bcx.unreachable();
342        }
343
344        // Finalize the suspend and resume blocks. Must come before the return block.
345        // Also here is where the stack length is initialized.
346        let load_len_at_start = |fx: &mut Self| {
347            // Loaded from args only for the config.
348            if config.inspect_stack {
349                let stack_len = fx.bcx.load(fx.isize_type, stack_len_arg, "stack_len");
350                fx.stack_len.store(&mut fx.bcx, stack_len);
351                fx.copy_stack_from_arg(stack_len);
352            } else {
353                fx.stack_len.store_imm(&mut fx.bcx, 0);
354            }
355        };
356        let generate_resume = bytecode.may_suspend();
357        if generate_resume {
358            let get_ecx_resume_at_ptr = |fx: &mut Self| {
359                fx.get_field(
360                    fx.ecx,
361                    mem::offset_of!(EvmContext<'_>, resume_at),
362                    "ecx.resume_at.addr",
363                )
364            };
365
366            let resume_ty = fx.isize_type;
367
368            // Resume block: load the `resume_at` value and switch to the corresponding block.
369            // Invalid values are treated as unreachable.
370            {
371                // Special-case the no resume case to load 0 into the length if possible.
372                let no_resume_block = fx.bcx.create_block_after(resume_block, "no_resume");
373
374                fx.bcx.switch_to_block(post_entry_block);
375                let resume_at = get_ecx_resume_at_ptr(&mut fx);
376                let resume_at = fx.bcx.load_aligned(resume_ty, resume_at, 1, "ecx.resume_at");
377                let no_resume = fx.bcx.icmp_imm(IntCC::Equal, resume_at, 0);
378                fx.bcx.brif(no_resume, no_resume_block, resume_block);
379
380                fx.bcx.switch_to_block(no_resume_block);
381                load_len_at_start(&mut fx);
382                fx.bcx.br(first_inst_block);
383
384                // Dispatch to the resume block.
385                fx.bcx.switch_to_block(resume_block);
386                let stack_len = fx.bcx.load(fx.isize_type, stack_len_arg, "stack_len");
387                fx.stack_len.store(&mut fx.bcx, stack_len);
388                fx.copy_stack_from_arg(stack_len);
389                let default = fx.bcx.create_block_after(resume_block, "resume_invalid");
390                fx.bcx.switch_to_block(default);
391                fx.call_panic("invalid `resume_at` value");
392
393                fx.bcx.switch_to_block(resume_block);
394                let targets = fx
395                    .resume_blocks
396                    .iter()
397                    .enumerate()
398                    .map(|(i, b)| (i as u64 + 1, *b))
399                    .collect::<Vec<_>>();
400                fx.bcx.switch(resume_at, default, &targets, true);
401            }
402
403            // Suspend block: store the `resume_at` value and return `Stop`.
404            {
405                fx.bcx.switch_to_block(fx.suspend_block);
406                let resume_value = fx.bcx.phi(resume_ty, &fx.suspend_blocks);
407                let resume_at = get_ecx_resume_at_ptr(&mut fx);
408                fx.bcx.store_aligned(resume_value, resume_at, 1);
409
410                // Save stack back to caller only when suspending, or always if inspecting.
411                // This matches the inverse of the condition in the return block.
412                if !config.inspect_stack {
413                    fx.copy_stack_to_arg();
414                    fx.save_stack_len();
415                }
416
417                fx.build_return_imm(InstructionResult::Stop);
418            }
419        } else {
420            debug_assert!(fx.resume_blocks.is_empty());
421            debug_assert!(fx.suspend_blocks.is_empty());
422
423            fx.bcx.switch_to_block(post_entry_block);
424            load_len_at_start(&mut fx);
425            fx.bcx.br(first_inst_block);
426
427            fx.bcx.switch_to_block(resume_block);
428            fx.bcx.unreachable();
429            fx.bcx.switch_to_block(fx.suspend_block);
430            fx.bcx.unreachable();
431        }
432
433        // Finalize the failure block.
434        fx.bcx.switch_to_block(fx.failure_block.unwrap());
435        if !fx.incoming_failures.is_empty() {
436            // Semantically, the EVM has only one halt; the distinct failure
437            // `InstructionResult` codes exist purely for debugging and error messages.
438            // `single_error` collapses every failure path to a single placeholder
439            // (OOG), letting LLVM DCE the per-site materialization and the phi.
440            let failure_value = if config.single_error {
441                fx.bcx.iconst(fx.i8_type, InstructionResult::OutOfGas as i64)
442            } else {
443                fx.bcx.phi(fx.i8_type, &fx.incoming_failures)
444            };
445            fx.bcx.set_current_block_cold();
446            // Do not sync the stack here: this is a shared synthetic merge block for
447            // failures that may occur before their instruction body has materialized
448            // the current virtual stack. Syncing here can use values that do not
449            // dominate all failure predecessors.
450            fx.build_return_inner(failure_value);
451        } else {
452            fx.bcx.unreachable();
453        }
454
455        // Finalize the return block.
456        fx.bcx.switch_to_block(fx.return_block.unwrap());
457        if !fx.incoming_returns.is_empty() {
458            let return_value = fx.bcx.phi(fx.i8_type, &fx.incoming_returns);
459            if config.inspect_stack {
460                fx.copy_stack_to_arg();
461                fx.save_stack_len();
462            }
463            fx.bcx.ret(&[return_value]);
464        } else {
465            fx.bcx.unreachable();
466        }
467
468        fx.bcx.seal_all_blocks();
469
470        Ok(())
471    }
472
473    /// Returns the instruction that is physically next in bytecode order.
474    fn lexical_next_inst(&self, inst: Inst) -> Option<Inst> {
475        let next = inst + 1;
476        self.inst_entries.get(next)?;
477        Some(next)
478    }
479
480    /// Resolves an instruction through dead-block redirects created by dedup.
481    fn effective_inst(&self, inst: Inst) -> Inst {
482        self.bytecode.redirects.get(&inst).copied().unwrap_or(inst)
483    }
484
485    /// Returns the IR block for an instruction after applying dedup redirects.
486    fn effective_entry(&self, inst: Inst) -> B::BasicBlock {
487        self.inst_entries[self.effective_inst(inst)]
488    }
489
490    /// Returns the runtime fallthrough target after applying dedup redirects.
491    fn effective_next_inst(&self, inst: Inst) -> Option<Inst> {
492        self.lexical_next_inst(inst).map(|next| self.effective_inst(next))
493    }
494
495    /// Returns the IR block for the runtime fallthrough target.
496    fn effective_next_entry(&self, inst: Inst) -> Option<B::BasicBlock> {
497        self.effective_next_inst(inst).map(|next| self.effective_entry(next))
498    }
499
500    #[instrument(level = "debug", skip_all, fields(inst = %self.bytecode.inst(inst).to_op()))]
501    fn translate_inst(&mut self, inst: Inst) -> Result<()> {
502        self.current_inst = Some(inst);
503        let data = self.bytecode.inst(inst);
504        let opcode = data.opcode;
505        let entry_block = self.inst_entries[inst];
506        self.bcx.switch_to_block(entry_block);
507
508        if self.config.debug {
509            self.bcx.set_debug_location(self.inst_lines[inst], 1);
510        }
511
512        // self.call_printf(format_printf!("{}\n", self.op_block_name("")), &[]);
513
514        let branch_to_next_opcode = |this: &mut Self| {
515            debug_assert!(
516                !this.bytecode.is_instr_diverging(inst),
517                "attempted to branch to next instruction in a diverging instruction: {data:?}",
518            );
519            if let Some(next) = this.effective_next_entry(inst) {
520                this.bcx.br(next);
521            }
522        };
523
524        /// Makes sure to run cleanup code and return.
525        /// Use `no_branch` to skip the branch to the next opcode.
526        /// Use `build` to build the return instruction and skip the branch.
527        macro_rules! goto_return {
528            (no_branch $($comment:expr)?) => {
529                $(
530                    if self.config.comments {
531                        self.add_comment($comment);
532                    }
533                )?
534                return Ok(());
535            };
536            (build $ret:expr) => {{
537                self.build_return_imm($ret);
538                goto_return!(no_branch);
539            }};
540            (fail $ret:expr) => {{
541                self.build_fail_imm($ret);
542                goto_return!(no_branch);
543            }};
544            ($($comment:expr)?) => {
545                // Flush virtual stack before leaving the section.
546                if let Some(next_inst) = self.effective_next_inst(inst) {
547                    let next = self.bytecode.inst(next_inst);
548                    if !next.is_dead_code() && next.is_stack_section_head() {
549                        self.materialize_live_stack();
550                    } else {
551                        self.relieve_vstack_pressure();
552                    }
553                }
554                branch_to_next_opcode(self);
555                goto_return!(no_branch $($comment)?);
556            };
557        }
558
559        // Assert that we already skipped the block.
560        debug_assert!(!data.flags.contains(InstFlags::DEAD_CODE));
561
562        #[cfg(test)]
563        if opcode == crate::TEST_SUSPEND {
564            self.suspend();
565            goto_return!(no_branch);
566        }
567
568        // Disabled instructions don't pay gas.
569        if data.flags.contains(InstFlags::DISABLED) {
570            goto_return!(fail InstructionResult::NotActivated);
571        }
572        if data.flags.contains(InstFlags::UNKNOWN) {
573            goto_return!(fail InstructionResult::OpcodeNotFound);
574        }
575
576        // Pay static gas for the current section.
577        self.gas_cost_imm(data.gas_section.gas_cost as u64);
578
579        // Compute len_before for this instruction.
580        // At section heads: load from the alloca once and reset the section offset.
581        // Within a section: derive from section_start_len + compile-time offset.
582        let (inp, out) = data.stack_io();
583        let diff = effective_stack_diff(inp, out, data);
584        self.len_offset = 0;
585        if data.is_stack_section_head() {
586            self.section_start_len = self.stack_len.load(&mut self.bcx, "stack_len");
587            self.section_start_sp = self.sp_at(self.section_start_len);
588            self.section_len_offset = 0;
589            self.stored_len_offset = 0;
590            self.cached_mem_base = None;
591
592            let section = data.stack_section;
593            self.vstack.reset(section.inputs as usize, section.max_growth.max(0) as usize);
594        }
595        self.len_before = if self.section_len_offset == 0 {
596            self.section_start_len
597        } else {
598            self.bcx.iadd_imm(self.section_start_len, self.section_len_offset as i64)
599        };
600
601        // Check stack length for the current section.
602        if self.config.stack_bound_checks && data.is_stack_section_head() {
603            self.check_stack_bounds(data.stack_section);
604        }
605
606        // NOOPs need no codegen beyond gas and bounds checks.
607        if data.flags.contains(InstFlags::NOOP) {
608            self.sync_noop_diff(inst, diff);
609            self.section_len_offset += diff;
610            goto_return!("noop");
611        }
612
613        // Store the updated stack length. Skip when `len.addr` already holds the
614        // correct value, i.e. the offset we'd write matches what's already stored.
615        let new_len_offset = self.section_len_offset + diff;
616        if new_len_offset != self.stored_len_offset {
617            let len_changed = self.bcx.iadd_imm(self.len_before, diff as i64);
618            self.stack_len.store(&mut self.bcx, len_changed);
619            self.stored_len_offset = new_len_offset;
620        }
621
622        // If the output is a known constant and the opcode has no dynamic gas or side effects,
623        // skip the real logic and just write the result.
624        // The inputs are not loaded; we simply adjust the stack offset to consume them and
625        // push the folded constant. This turns e.g. `PUSH 3, PUSH 4, ADD` into a single
626        // store of `7`.
627        // Pure ops with a known-constant output: skip the opcode logic and just
628        // store the folded constant. Works for single-output arithmetic (ADD, MUL,
629        // ...) and DUP (which adds one new TOS without consuming its input).
630        if out >= 1
631            && let Some(const_out) = self.bytecode.const_output(inst)
632        {
633            debug_assert!(
634                out == 1 || out == inp + 1,
635                "const_output assumes single synthesized push: inp={inp}, out={out}",
636            );
637            debug_assert!(!data.may_suspend() && !data.is_branching());
638            // We push exactly 1 value, so consume `inp + 1 - out` to match the
639            // real stack diff. For DUP (out = inp+1) this is 0; for ADD (out = 1)
640            // this equals inp.
641            let drop_count = (inp + 1 - out) as usize;
642            self.vstack.drop_top(drop_count);
643            self.len_offset -= drop_count as i8;
644            let value = self.bcx.iconst_256(const_out);
645            self.push(value);
646            self.section_len_offset += diff;
647            goto_return!("const output");
648        }
649
650        if self.try_peephole(data) {
651            self.sync_virtual_stack_diff(diff);
652            self.section_len_offset += diff;
653            if self.current_inst().is_diverging() {
654                goto_return!(no_branch);
655            } else {
656                goto_return!("peephole");
657            }
658        }
659
660        // Macro utils.
661        macro_rules! unop {
662            ($op:ident) => {{
663                let mut a = self.pop();
664                a = self.bcx.$op(a);
665                self.push(a);
666            }};
667        }
668
669        macro_rules! binop {
670            ($op:ident) => {{
671                let [a, b] = self.popn();
672                let r = self.bcx.$op(a, b);
673                self.push(r);
674            }};
675            (@shift $op:ident, | $value:ident, $shift:ident | $default:expr) => {{
676                let [$shift, $value] = self.popn();
677                let r = self.bcx.$op($value, $shift);
678                let overflow = self.bcx.icmp_imm(IntCC::UnsignedGreaterThan, $shift, 255);
679                let default = $default;
680                let r = self.bcx.select(overflow, default, r);
681                self.push(r);
682            }};
683        }
684
685        /// Gets the pointer to a field via chained `offset_of!`.
686        macro_rules! field {
687            // Gets the pointer to a field.
688            (@get $base:expr, $($paths:path),*; $($spec:tt).*) => {
689                self.get_field($base, 0 $(+ mem::offset_of!($paths, $spec))*, stringify!($($spec).*.addr))
690            };
691            // Loads a field.
692            // `@[endian]` is the endianness of the stored value. If native, omit it.
693            (@load $(@[endian = $endian:tt])? $ty:expr, $base:expr, $($paths:path),*; $($spec:tt).*) => {{
694                let ptr = field!(@get $base, $($paths),*; $($spec).*);
695                #[allow(unused_mut)]
696                let mut value = self.bcx.load_aligned($ty, ptr, 1, stringify!($($spec).*));
697                $(
698                    if !cfg!(target_endian = $endian) {
699                        value = self.bcx.bswap(value);
700                    }
701                )?
702                value
703            }};
704            // Loads, extends (if necessary), and pushes a field.
705            // `@[endian]` is the endianness of the stored value. If native, omit it.
706            (@push $(@[endian = $endian:tt])? $ty:expr, $base:expr, $($rest:tt)*) => {{
707                let mut value = field!(@load $(@[endian = $endian])? $ty, $base, $($rest)*);
708                if self.bcx.type_bit_width($ty) < 256 {
709                    value = self.bcx.zext(self.word_type, value);
710                }
711                self.push(value);
712            }};
713        }
714
715        match data.opcode {
716            op::STOP => goto_return!(build InstructionResult::Stop),
717
718            op::ADD => binop!(iadd),
719            op::MUL => binop!(imul),
720            op::SUB => binop!(isub),
721            op::DIV => {
722                let sp = self.sp_after_inputs();
723                let _ = self.call_builtin(Builtin::Div, &[sp]);
724            }
725            op::SDIV => {
726                let sp = self.sp_after_inputs();
727                let _ = self.call_builtin(Builtin::SDiv, &[sp]);
728            }
729            op::MOD => {
730                let sp = self.sp_after_inputs();
731                let _ = self.call_builtin(Builtin::Mod, &[sp]);
732            }
733            op::SMOD => {
734                let sp = self.sp_after_inputs();
735                let _ = self.call_builtin(Builtin::SMod, &[sp]);
736            }
737            op::ADDMOD => {
738                let sp = self.sp_after_inputs();
739                let _ = self.call_builtin(Builtin::AddMod, &[sp]);
740            }
741            op::MULMOD => {
742                let sp = self.sp_after_inputs();
743                let _ = self.call_builtin(Builtin::MulMod, &[sp]);
744            }
745            op::EXP => {
746                let sp = self.sp_after_inputs();
747                self.call_fallible_builtin(Builtin::Exp, &[self.ecx, sp]);
748            }
749            op::SIGNEXTEND => {
750                // let shift = 248 - 8 * ext;
751                // ext < 31
752                //   ? (x << shift) >>s shift
753                //   : x
754                let [ext, x] = self.popn();
755
756                let might_do_something = self.bcx.icmp_imm(IntCC::UnsignedLessThan, ext, 31);
757
758                let shift = self.bcx.imul_imm(ext, 8);
759                let c248 = self.bcx.iconst_256(248);
760                let shift = self.bcx.isub(c248, shift);
761                let shifted = self.bcx.ishl(x, shift);
762                let sext = self.bcx.sshr(shifted, shift);
763
764                let r = self.bcx.select(might_do_something, sext, x);
765                self.push(r);
766            }
767
768            op::LT | op::GT | op::SLT | op::SGT | op::EQ => {
769                let cond = match opcode {
770                    op::LT => IntCC::UnsignedLessThan,
771                    op::GT => IntCC::UnsignedGreaterThan,
772                    op::SLT => IntCC::SignedLessThan,
773                    op::SGT => IntCC::SignedGreaterThan,
774                    op::EQ => IntCC::Equal,
775                    _ => unreachable!(),
776                };
777
778                let [a, b] = self.popn();
779                let r = self.bcx.icmp(cond, a, b);
780                let r = self.bcx.zext(self.word_type, r);
781                self.push(r);
782            }
783            op::ISZERO => {
784                let a = self.pop();
785                let r = self.bcx.icmp_imm(IntCC::Equal, a, 0);
786                let r = self.bcx.zext(self.word_type, r);
787                self.push(r);
788            }
789            op::AND => binop!(bitand),
790            op::OR => binop!(bitor),
791            op::XOR => binop!(bitxor),
792            op::NOT => unop!(bitnot),
793            op::BYTE => {
794                // index < 32
795                //   ? (value >> (248 - index * 8)) & 0xFF
796                //   : 0
797                let [index, value] = self.popn();
798
799                let in_range = self.bcx.icmp_imm(IntCC::UnsignedLessThan, index, 32);
800
801                let shift = self.bcx.imul_imm(index, 8);
802                let c248 = self.bcx.iconst_256(248);
803                let shift = self.bcx.isub(c248, shift);
804                let shifted = self.bcx.ushr(value, shift);
805                let mask = self.bcx.iconst_256(0xFF);
806                let byte = self.bcx.bitand(shifted, mask);
807
808                let zero = self.bcx.iconst_256(0);
809
810                let r = self.bcx.select(in_range, byte, zero);
811                self.push(r);
812            }
813            op::SHL => binop!(@shift ishl, |value, shift| self.bcx.iconst_256(0)),
814            op::SHR => binop!(@shift ushr, |value, shift| self.bcx.iconst_256(0)),
815            op::SAR => binop!(@shift sshr, |value, shift| {
816                let is_negative = self.bcx.icmp_imm(IntCC::SignedLessThan, value, 0);
817                let max = self.bcx.iconst_256(U256::MAX);
818                let zero = self.bcx.iconst_256(0);
819                self.bcx.select(is_negative, max, zero)
820            }),
821            op::CLZ => unop!(clz),
822
823            op::KECCAK256 => {
824                let sp = self.sp_after_inputs();
825                self.call_fallible_builtin(Builtin::Keccak256, &[self.ecx, sp]);
826            }
827
828            op::ADDRESS => {
829                let input = self.load_input();
830                field!(@push @[endian = "big"] self.address_type, input, InputsImpl; target_address);
831            }
832            op::BALANCE => {
833                let sp = self.sp_after_inputs();
834                self.call_fallible_builtin(Builtin::Balance, &[self.ecx, sp]);
835            }
836            op::ORIGIN => {
837                let slot = self.sp_at_top();
838                let _ = self.call_builtin(Builtin::Origin, &[self.ecx, slot]);
839                self.narrow_to_address(slot);
840            }
841            op::CALLER => {
842                let input = self.load_input();
843                field!(@push @[endian = "big"] self.address_type, input, InputsImpl; caller_address);
844            }
845            op::CALLVALUE => {
846                let input = self.load_input();
847                field!(@push self.word_type, input, InputsImpl; call_value);
848            }
849            op::CALLDATALOAD => {
850                let sp = self.sp_after_inputs();
851                let _ = self.call_builtin(Builtin::CallDataLoad, &[self.ecx, sp]);
852            }
853            op::CALLDATASIZE => {
854                field!(@push self.isize_type, self.ecx, EvmContext<'_>; calldatasize);
855            }
856            op::CALLDATACOPY => {
857                let sp = self.sp_after_inputs();
858                self.call_fallible_builtin(Builtin::CallDataCopy, &[self.ecx, sp]);
859            }
860            op::CODESIZE => {
861                let len = self.bcx.iconst(self.word_type, self.bytecode.codesize() as i64);
862                self.push(len);
863            }
864            op::CODECOPY => {
865                let sp = self.sp_after_inputs();
866                self.call_fallible_builtin(Builtin::CodeCopy, &[self.ecx, sp]);
867            }
868
869            op::GASPRICE => {
870                let sp = self.sp_after_inputs();
871                let _ = self.call_builtin(Builtin::GasPrice, &[self.ecx, sp]);
872            }
873            op::EXTCODESIZE => {
874                let sp = self.sp_after_inputs();
875                self.call_fallible_builtin(Builtin::ExtCodeSize, &[self.ecx, sp]);
876            }
877            op::EXTCODECOPY => {
878                let sp = self.sp_after_inputs();
879                self.call_fallible_builtin(Builtin::ExtCodeCopy, &[self.ecx, sp]);
880            }
881            op::RETURNDATASIZE => {
882                field!(@push self.isize_type, self.ecx, EvmContext<'_>, pf::Slice; return_data.len);
883            }
884            op::RETURNDATACOPY => {
885                let sp = self.sp_after_inputs();
886                self.call_fallible_builtin(Builtin::ReturnDataCopy, &[self.ecx, sp]);
887            }
888            op::EXTCODEHASH => {
889                let sp = self.sp_after_inputs();
890                self.call_fallible_builtin(Builtin::ExtCodeHash, &[self.ecx, sp]);
891            }
892            op::BLOCKHASH => {
893                let sp = self.sp_after_inputs();
894                self.call_fallible_builtin(Builtin::BlockHash, &[self.ecx, sp]);
895            }
896            op::COINBASE => {
897                let slot = self.sp_at_top();
898                let _ = self.call_builtin(Builtin::Coinbase, &[self.ecx, slot]);
899                self.narrow_to_address(slot);
900            }
901            op::TIMESTAMP => {
902                let slot = self.sp_at_top();
903                let _ = self.call_builtin(Builtin::Timestamp, &[self.ecx, slot]);
904            }
905            op::NUMBER => {
906                let slot = self.sp_at_top();
907                let _ = self.call_builtin(Builtin::Number, &[self.ecx, slot]);
908            }
909            op::DIFFICULTY => {
910                let slot = self.sp_at_top();
911                let _ = self.call_builtin(Builtin::Difficulty, &[self.ecx, slot]);
912            }
913            op::GASLIMIT => {
914                let slot = self.sp_at_top();
915                let _ = self.call_builtin(Builtin::GasLimit, &[self.ecx, slot]);
916            }
917            op::CHAINID => {
918                let slot = self.sp_at_top();
919                let _ = self.call_builtin(Builtin::ChainId, &[self.ecx, slot]);
920            }
921            op::SELFBALANCE => {
922                let slot = self.sp_at_top();
923                self.call_fallible_builtin(Builtin::SelfBalance, &[self.ecx, slot]);
924            }
925            op::BASEFEE => {
926                let slot = self.sp_at_top();
927                let _ = self.call_builtin(Builtin::Basefee, &[self.ecx, slot]);
928            }
929            op::BLOBHASH => {
930                let sp = self.sp_after_inputs();
931                let _ = self.call_builtin(Builtin::BlobHash, &[self.ecx, sp]);
932            }
933            op::BLOBBASEFEE => {
934                let slot = self.sp_at_top();
935                let _ = self.call_builtin(Builtin::BlobBaseFee, &[self.ecx, slot]);
936            }
937            op::SLOTNUM => {
938                let slot = self.sp_at_top();
939                let _ = self.call_builtin(Builtin::SlotNum, &[self.ecx, slot]);
940            }
941
942            op::POP => {
943                self.pop_ignore(1);
944            }
945            op::MLOAD => {
946                let offset = self.pop();
947                let addr = self.build_ensure_memory(offset, 32);
948                let value = self.bcx.load_aligned(self.word_type, addr, 1, "mload.value");
949                let value = if self.little_endian() { self.bcx.bswap(value) } else { value };
950                self.push(value);
951            }
952            op::MSTORE => {
953                let [offset, value] = self.popn();
954                let addr = self.build_ensure_memory(offset, 32);
955                let value = if self.little_endian() { self.bcx.bswap(value) } else { value };
956                self.bcx.store_aligned(value, addr, 1);
957            }
958            op::MSTORE8 => {
959                let [offset, value] = self.popn();
960                let addr = self.build_ensure_memory(offset, 1);
961                let value = self.bcx.ireduce(self.i8_type, value);
962                self.bcx.store_aligned(value, addr, 1);
963            }
964            op::SLOAD => {
965                let sp = self.sp_after_inputs();
966                self.call_fallible_builtin(Builtin::Sload, &[self.ecx, sp]);
967            }
968            op::SSTORE => {
969                let sp = self.sp_after_inputs();
970                self.call_fallible_builtin(Builtin::Sstore, &[self.ecx, sp]);
971            }
972            op::JUMP | op::JUMPI => {
973                let is_invalid = data.flags.contains(InstFlags::INVALID_JUMP);
974                let has_const_jumpi_condition = data.has_const_jumpi_condition();
975                if is_invalid && opcode == op::JUMP {
976                    // Pop and discard the target; it's always on the stack.
977                    self.pop_ignore(1);
978                    self.build_fail_imm(InstructionResult::InvalidJump);
979                } else {
980                    let target = if is_invalid {
981                        debug_assert_eq!(*data, op::JUMPI);
982                        // The jump target is invalid, but we still need to pop it.
983                        self.pop_ignore(1);
984                        self.add_invalid_jump()
985                    } else if data.flags.contains(InstFlags::MULTI_JUMP) {
986                        let target_value = self.pop();
987                        let targets = self.bytecode.multi_jump_targets(inst).unwrap();
988
989                        if opcode == op::JUMPI && !has_const_jumpi_condition {
990                            let cond_word = self.pop();
991                            self.materialize_live_stack();
992                            let cond = self.bcx.icmp_imm(IntCC::NotEqual, cond_word, 0);
993                            let next = self
994                                .effective_next_entry(inst)
995                                .expect("JUMPI must have a fallthrough target");
996                            let switch_block = self.bcx.create_block("multi_jump");
997                            self.bcx.brif(cond, switch_block, next);
998                            self.bcx.switch_to_block(switch_block);
999                        } else {
1000                            if opcode == op::JUMPI {
1001                                self.pop_ignore(1);
1002                            }
1003                            self.materialize_live_stack();
1004                        }
1005
1006                        let switch_targets: Vec<_> = targets
1007                            .iter()
1008                            .map(|&t| {
1009                                let pc = self.bytecode.inst(t).jumpdest_pc() as u64;
1010                                (pc, self.effective_entry(t))
1011                            })
1012                            .collect();
1013                        let invalid_jump = self.add_invalid_jump();
1014                        self.bcx.switch(target_value, invalid_jump, &switch_targets, true);
1015
1016                        goto_return!(no_branch);
1017                    } else if data.flags.contains(InstFlags::STATIC_JUMP) {
1018                        // Pop and discard the target; it's always on the stack.
1019                        self.pop_ignore(1);
1020                        let target_inst = data.static_jump_target();
1021                        debug_assert!(
1022                            *self.bytecode.inst(target_inst) == op::JUMPDEST
1023                                || (opcode == op::JUMPI && has_const_jumpi_condition),
1024                            "jumping to non-JUMPDEST; target_inst={target_inst}",
1025                        );
1026                        self.effective_entry(target_inst)
1027                    } else {
1028                        // Dynamic jump.
1029                        debug_assert!(self.bytecode.has_dynamic_jumps());
1030                        let target = self.pop();
1031                        let target = self.u256_to_u64_saturating(target, 64);
1032                        self.incoming_dynamic_jumps
1033                            .push((target, self.bcx.current_block().unwrap()));
1034                        self.dynamic_jump_table
1035                    };
1036
1037                    if opcode == op::JUMPI && !has_const_jumpi_condition {
1038                        let cond_word = self.pop();
1039                        // Flush virtual values before leaving the section.
1040                        self.materialize_live_stack();
1041                        let cond = self.bcx.icmp_imm(IntCC::NotEqual, cond_word, 0);
1042                        let next = self
1043                            .effective_next_entry(inst)
1044                            .expect("JUMPI must have a fallthrough target");
1045                        self.bcx.brif(cond, target, next);
1046                    } else {
1047                        if opcode == op::JUMPI {
1048                            self.pop_ignore(1);
1049                        }
1050                        // Flush virtual values before leaving the section.
1051                        self.materialize_live_stack();
1052                        self.bcx.br(target);
1053                    }
1054                }
1055
1056                goto_return!(no_branch);
1057            }
1058            op::PC => {
1059                let pc = self.bcx.iconst_256(data.pc_imm());
1060                self.push(pc);
1061            }
1062            op::MSIZE => {
1063                let mem_len_field = self.get_field(
1064                    self.ecx,
1065                    mem::offset_of!(EvmContext<'_>, mem_len),
1066                    "ecx.mem_len.addr",
1067                );
1068                let mem_len = self.bcx.load(self.isize_type, mem_len_field, "ecx.mem_len");
1069                let msize = self.bcx.zext(self.word_type, mem_len);
1070                self.push(msize);
1071            }
1072            op::GAS => {
1073                let addr = self.gas_remaining_addr();
1074                let i64_type = self.bcx.type_int(64);
1075                let remaining = self.bcx.load(i64_type, addr, "gas.remaining");
1076                let remaining = self.bcx.zext(self.word_type, remaining);
1077                self.push(remaining);
1078            }
1079            op::JUMPDEST => {
1080                self.bcx.nop();
1081            }
1082            op::TLOAD => {
1083                let sp = self.sp_after_inputs();
1084                let _ = self.call_builtin(Builtin::Tload, &[self.ecx, sp]);
1085            }
1086            op::TSTORE => {
1087                let sp = self.sp_after_inputs();
1088                self.call_fallible_builtin(Builtin::Tstore, &[self.ecx, sp]);
1089            }
1090            op::MCOPY => {
1091                let sp = self.sp_after_inputs();
1092                self.call_fallible_builtin(Builtin::Mcopy, &[self.ecx, sp]);
1093            }
1094
1095            op::PUSH0..=op::PUSH32 => {
1096                unreachable!("handled in const_output");
1097            }
1098
1099            op::DUP1..=op::DUP16 => self.dup((opcode - op::DUP1 + 1) as usize),
1100            op::DUPN => match decode_single(data.imm_byte()) {
1101                Some(n) => self.dup(n as usize),
1102                None => goto_return!(fail InstructionResult::InvalidImmediateEncoding),
1103            },
1104
1105            op::SWAP1..=op::SWAP16 => self.swap((opcode - op::SWAP1 + 1) as usize),
1106            op::SWAPN => match decode_single(data.imm_byte()) {
1107                Some(n) => self.swap(n as usize),
1108                None => goto_return!(fail InstructionResult::InvalidImmediateEncoding),
1109            },
1110
1111            op::EXCHANGE => match decode_pair(data.imm_byte()) {
1112                Some((n, m)) => self.exchange(n as usize, (m - n) as usize),
1113                None => goto_return!(fail InstructionResult::InvalidImmediateEncoding),
1114            },
1115
1116            op::LOG0..=op::LOG4 => {
1117                let n = opcode - op::LOG0;
1118                let sp = self.sp_after_inputs();
1119                let n = self.bcx.iconst(self.i8_type, n as i64);
1120                self.call_fallible_builtin(Builtin::Log, &[self.ecx, sp, n]);
1121            }
1122
1123            op::CREATE => {
1124                self.create_common(CreateKind::Create);
1125                goto_return!(no_branch);
1126            }
1127            op::CALL => {
1128                self.call_common(CallKind::Call);
1129                goto_return!(no_branch);
1130            }
1131            op::CALLCODE => {
1132                self.call_common(CallKind::CallCode);
1133                goto_return!(no_branch);
1134            }
1135            op::RETURN => {
1136                self.return_common(InstructionResult::Return);
1137                goto_return!(no_branch);
1138            }
1139            op::DELEGATECALL => {
1140                self.call_common(CallKind::DelegateCall);
1141                goto_return!(no_branch);
1142            }
1143            op::CREATE2 => {
1144                self.create_common(CreateKind::Create2);
1145                goto_return!(no_branch);
1146            }
1147
1148            op::STATICCALL => {
1149                self.call_common(CallKind::StaticCall);
1150                goto_return!(no_branch);
1151            }
1152
1153            op::REVERT => {
1154                self.return_common(InstructionResult::Revert);
1155                goto_return!(no_branch);
1156            }
1157            op::INVALID => goto_return!(fail InstructionResult::InvalidFEOpcode),
1158            op::SELFDESTRUCT => {
1159                let sp = self.sp_after_inputs();
1160                self.sync_diverging_stack_effect();
1161                let _ = self.call_builtin(Builtin::SelfDestruct, &[self.ecx, sp]);
1162                self.bcx.unreachable();
1163                goto_return!(no_branch);
1164            }
1165
1166            _ => unreachable!("unimplemented instruction: {data:?}"),
1167        }
1168
1169        self.sync_virtual_stack_diff(diff);
1170        self.section_len_offset += diff;
1171        goto_return!("normal exit");
1172    }
1173
1174    /// Syncs the virtual stack for a NOOP instruction. Unlike `sync_virtual_stack_diff`,
1175    /// this preserves known-constant outputs as `Virtual` values so that boundary
1176    /// materialization (e.g. `materialize_live_stack` before branch/suspend) can flush
1177    /// them to physical memory. Without this, NOOP'd constants would be marked
1178    /// `Materialized` with no actual store, leaving garbage in memory.
1179    fn sync_noop_diff(&mut self, inst: Inst, diff: i32) {
1180        let expected_top = self.section_len_offset + diff;
1181        let current_top = self.vstack.top_offset();
1182        if current_top == expected_top {
1183            return;
1184        }
1185        let delta = expected_top - current_top;
1186        if delta < 0 {
1187            self.vstack.drop_top((-delta) as usize);
1188        } else {
1189            // For NOOP instructions that push values, use the known constant output
1190            // if available. This ensures the value can be materialized later.
1191            if delta == 1
1192                && let Some(c) = self.bytecode.const_output(inst)
1193            {
1194                let value = self.bcx.iconst_256(c);
1195                self.vstack.push(value);
1196            } else {
1197                for _ in 0..delta {
1198                    self.vstack.push_mem();
1199                }
1200            }
1201        }
1202    }
1203
1204    /// Syncs the virtual stack's `top_offset` with the expected value after applying
1205    /// the instruction's stack diff. For inline ops (push/pop), the virtual stack is
1206    /// already up to date. For builtin ops (sp_after_inputs/sp_at_top), this adjusts
1207    /// the virtual stack to account for consumed inputs and materialized outputs.
1208    ///
1209    /// Also invalidates any virtual slots in the output area that the builtin may have
1210    /// overwritten in physical memory, to prevent stale virtual values from shadowing
1211    /// the builtin's actual output.
1212    fn sync_virtual_stack_diff(&mut self, diff: i32) {
1213        let expected_top = self.section_len_offset + diff;
1214        let current_top = self.vstack.top_offset();
1215        if current_top == expected_top {
1216            return;
1217        }
1218        let delta = expected_top - current_top;
1219        if expected_top < self.vstack.live_range().start {
1220            let inst = self.current_inst.unwrap();
1221            // Walk backward to find the section head for diagnostic context.
1222            let mut head = inst;
1223            for i in (0..inst.index()).rev() {
1224                let idx = crate::Inst::from_usize(i);
1225                let d = self.bytecode.inst(idx);
1226                if d.is_dead_code() {
1227                    continue;
1228                }
1229                if d.is_stack_section_head() {
1230                    head = idx;
1231                    break;
1232                }
1233            }
1234            // Dump the section from head to current inst.
1235            let mut section_dump = String::new();
1236            for i in head.index()..=inst.index() {
1237                let idx = crate::Inst::from_usize(i);
1238                let d = self.bytecode.inst(idx);
1239                if d.is_dead_code() {
1240                    continue;
1241                }
1242                use std::fmt::Write;
1243                let _ = write!(
1244                    section_dump,
1245                    "\n  ic{i} pc={} {:?} io={:?} flags={:?} gas={:?} stack={:?}{}{}",
1246                    self.bytecode.pc(idx),
1247                    d.to_op(),
1248                    d.stack_io(),
1249                    d.flags,
1250                    d.gas_section,
1251                    d.stack_section,
1252                    if d.is_stack_section_head() { " SECTION_HEAD" } else { "" },
1253                    if d.is_dead_code() { " DEAD" } else { "" },
1254                );
1255            }
1256            let head_data = self.bytecode.inst(head);
1257            panic!(
1258                "sync: expected_top={expected_top} < base={}, section_len_offset={}, \
1259                 diff={diff}, current_top={current_top}, inst={:?} (ic{})\n\
1260                 section head=ic{}, head_stack_section={:?}, section:{section_dump}",
1261                self.vstack.live_range().start,
1262                self.section_len_offset,
1263                self.current_inst().to_op(),
1264                inst.index(),
1265                head.index(),
1266                head_data.stack_section,
1267            );
1268        }
1269        if delta < 0 {
1270            self.vstack.drop_top((-delta) as usize);
1271        } else {
1272            for _ in 0..delta {
1273                self.vstack.push_mem();
1274            }
1275        }
1276
1277        // Mark the builtin's output area as materialized. The builtin wrote to
1278        // physical memory at `sp[0..outputs]`, which corresponds to offsets
1279        // `expected_top - outputs .. expected_top`. Any pre-existing Virtual
1280        // entries (e.g. from NOOP'd producers) must be invalidated.
1281        let (_, outputs) = self.current_inst().stack_io();
1282        let outputs = outputs as i32;
1283        if outputs > 0 {
1284            self.vstack.mark_materialized_range(expected_top - outputs..expected_top);
1285        }
1286
1287        debug_assert_eq!(
1288            self.vstack.top_offset(),
1289            expected_top,
1290            "virtual stack sync mismatch after {:?}",
1291            self.current_inst().to_op(),
1292        );
1293    }
1294
1295    /// Pushes a 256-bit value onto the stack.
1296    fn push(&mut self, value: B::Value) {
1297        self.vstack.push(value);
1298        self.len_offset += 1;
1299    }
1300
1301    /// Returns the known constant values of the topmost `N` stack operands, in the same order
1302    /// as [`popn`](Self::popn): index 0 is TOS, index 1 is second from top, etc.
1303    fn const_operands<const N: usize>(&self) -> [Option<U256>; N] {
1304        let inst = self.current_inst.unwrap();
1305        std::array::from_fn(|i| self.bytecode.const_operand(inst, i))
1306    }
1307
1308    /// Discards `n` stack inputs and pushes a compile-time constant.
1309    fn fold_const(&mut self, value: impl TryInto<U256>) {
1310        self.pop_ignore(self.current_inst().stack_io().0 as usize);
1311        let v = self.bcx.iconst_256(value);
1312        self.push(v);
1313    }
1314
1315    /// Consumes the topmost `n` elements from the stack without loading them.
1316    fn pop_ignore(&mut self, n: usize) {
1317        self.vstack.drop_top(n);
1318        self.len_offset -= n as i8;
1319    }
1320
1321    /// Removes the topmost element from the stack and returns it.
1322    fn pop(&mut self) -> B::Value {
1323        self.popn::<1>()[0]
1324    }
1325
1326    /// Removes the topmost `N` elements from the stack and returns them.
1327    fn popn<const N: usize>(&mut self) -> [B::Value; N] {
1328        assert_ne!(N, 0);
1329
1330        let operand_depth_base = (-self.len_offset) as usize;
1331        let values = std::array::from_fn(|i| {
1332            let operand_depth = operand_depth_base + i;
1333            let name = b'a' + i as u8;
1334            self.stack_value_at_depth(operand_depth, i, std::str::from_utf8(&[name]).unwrap())
1335        });
1336        self.pop_ignore(N);
1337        values
1338    }
1339
1340    /// Duplicates the `n`th value from the top of the stack.
1341    /// `n` cannot be `0`.
1342    fn dup(&mut self, n: usize) {
1343        assert_ne!(n, 0);
1344        let name = if self.config.debug { &format!("dup{n}") } else { "" };
1345        let value = self.stack_value_at_depth(n - 1, n - 1, name);
1346        self.push(value);
1347    }
1348
1349    /// Swaps the topmost value with the `n`th value from the top.
1350    /// `n` cannot be `0`.
1351    fn swap(&mut self, n: usize) {
1352        self.exchange(0, n);
1353    }
1354
1355    /// Exchange two values on the stack.
1356    /// `n` is the first index, and the second index is calculated as `n + m`.
1357    /// `m` cannot be `0`.
1358    fn exchange(&mut self, n: usize, m: usize) {
1359        assert_ne!(m, 0);
1360        let a = self.stack_value_at_depth(n, n, "swap.a");
1361        let b = self.stack_value_at_depth(n + m, n + m, "swap.b");
1362        self.vstack.set(n, b);
1363        self.vstack.set(n + m, a);
1364    }
1365
1366    /// `RETURN` or `REVERT` instruction.
1367    fn return_common(&mut self, ir: InstructionResult) {
1368        let sp = self.sp_after_inputs();
1369        let ir_const = self.bcx.iconst(self.i8_type, ir as i64);
1370        self.sync_diverging_stack_effect();
1371        let _ = self.call_builtin(Builtin::DoReturn, &[self.ecx, sp, ir_const]);
1372        self.bcx.unreachable();
1373    }
1374
1375    fn sync_diverging_stack_effect(&mut self) {
1376        let data = self.current_inst();
1377        let (inp, out) = data.stack_io();
1378        let diff = effective_stack_diff(inp, out, data);
1379        self.sync_virtual_stack_diff(diff);
1380        if self.config.inspect_stack {
1381            self.materialize_live_stack();
1382            self.copy_stack_to_arg();
1383            self.save_stack_len();
1384        }
1385    }
1386
1387    /// Builds a `CREATE` or `CREATE2` instruction.
1388    fn create_common(&mut self, create_kind: CreateKind) {
1389        let sp = self.sp_after_inputs();
1390        let create_kind = self.bcx.iconst(self.i8_type, create_kind as i64);
1391        self.call_fallible_builtin(Builtin::Create, &[self.ecx, sp, create_kind]);
1392        self.suspend();
1393    }
1394
1395    /// Builds `*CALL*` instructions.
1396    fn call_common(&mut self, call_kind: CallKind) {
1397        let sp = self.sp_after_inputs();
1398        let call_kind = self.bcx.iconst(self.i8_type, call_kind as i64);
1399        self.call_fallible_builtin(Builtin::Call, &[self.ecx, sp, call_kind]);
1400        self.suspend();
1401    }
1402
1403    /// Suspend execution, storing the resume point in the context.
1404    fn suspend(&mut self) {
1405        // Flush virtual stack before entering the shared suspend block.
1406        self.materialize_live_stack();
1407
1408        // Register the next instruction as the resume block.
1409        let idx = self.resume_blocks.len();
1410        let resume_at = self
1411            .effective_next_entry(self.current_inst.unwrap())
1412            .expect("suspending instruction must have a resume target");
1413        self.add_resume_at(resume_at);
1414
1415        // Register the current block as the suspend block.
1416        let value = self.bcx.iconst(self.isize_type, idx as i64 + 1);
1417        self.suspend_blocks.push((value, self.bcx.current_block().unwrap()));
1418
1419        // Branch to the suspend block.
1420        self.bcx.br(self.suspend_block);
1421    }
1422
1423    /// Adds a resume point.
1424    fn add_resume_at(&mut self, block: B::BasicBlock) {
1425        self.resume_blocks.push(block);
1426    }
1427
1428    /// Loads the word at the given pointer.
1429    fn load_word(&mut self, ptr: B::Value, name: &str) -> B::Value {
1430        self.bcx.load(self.word_type, ptr, name)
1431    }
1432
1433    /// Gets a field at the given offset.
1434    fn get_field(&mut self, ptr: B::Value, offset: usize, name: &str) -> B::Value {
1435        get_field(&mut self.bcx, ptr, offset, name)
1436    }
1437
1438    /// Loads the `ecx.input` pointer on demand.
1439    fn load_input(&mut self) -> B::Value {
1440        let ptr_type = self.bcx.type_ptr();
1441        let input_field = get_field(
1442            &mut self.bcx,
1443            self.ecx,
1444            mem::offset_of!(EvmContext<'_>, input),
1445            "ecx.input.addr",
1446        );
1447        self.bcx.load(ptr_type, input_field, "ecx.input")
1448    }
1449
1450    /// Re-loads the address at `slot` as i160, zero-extends to i256, and stores it back.
1451    ///
1452    /// On little-endian the low 160 bits sit at byte offset 0, so a direct
1453    /// `load i160` + `zext i256` gives LLVM a typed narrow load — no AND needed
1454    /// to prove the high 96 bits are zero.
1455    #[allow(clippy::assertions_on_constants)]
1456    fn narrow_to_address(&mut self, slot: B::Value) {
1457        debug_assert!(self.little_endian(), "big-endian not yet supported");
1458        let value = self.bcx.load(self.address_type, slot, "address");
1459        let value = self.bcx.zext(self.word_type, value);
1460        self.bcx.store(value, slot);
1461    }
1462
1463    fn gas_remaining_addr(&mut self) -> B::Value {
1464        const OFFSET: usize =
1465            mem::offset_of!(EvmContext<'_>, gas) + mem::offset_of!(pf::Gas, tracker.remaining);
1466        let offset = self.bcx.iconst(self.isize_type, OFFSET as i64);
1467        self.bcx.gep(self.i8_type, self.ecx, &[offset], "gas.remaining.addr")
1468    }
1469
1470    /// Saves the local `stack_len` to `stack_len_arg`.
1471    fn save_stack_len(&mut self) {
1472        let len = self.stack_len.load(&mut self.bcx, "stack_len");
1473        let ptr = self.stack_len_arg();
1474        self.bcx.store(len, ptr);
1475    }
1476
1477    /// Copies the live prefix of the stack from the argument to the local alloca.
1478    /// `len` is the number of live stack elements.
1479    fn copy_stack_from_arg(&mut self, len: B::Value) {
1480        if let Some(src) = self.sp_arg {
1481            let dst = self.stack.addr(&mut self.bcx);
1482            let word_size = 32i64;
1483            let byte_len = self.bcx.imul_imm(len, word_size);
1484            self.bcx.memcpy(dst, src, byte_len);
1485        }
1486    }
1487
1488    /// Copies the live prefix of the stack from the local alloca to the argument.
1489    fn copy_stack_to_arg(&mut self) {
1490        if let Some(dst) = self.sp_arg {
1491            let len = self.stack_len.load(&mut self.bcx, "stack_len");
1492            let src = self.stack.addr(&mut self.bcx);
1493            let word_size = 32i64;
1494            let byte_len = self.bcx.imul_imm(len, word_size);
1495            self.bcx.memcpy(dst, src, byte_len);
1496        }
1497    }
1498
1499    /// Returns the stack length argument.
1500    fn stack_len_arg(&mut self) -> B::Value {
1501        self.bcx.fn_param(2)
1502    }
1503
1504    /// Returns the stack pointer at the top (`&stack[stack.len]`).
1505    ///
1506    /// Used by builtins that write a single output directly to memory.
1507    /// The virtual stack is synced at instruction end via `sync_virtual_stack_diff`.
1508    #[must_use]
1509    fn sp_at_top(&mut self) -> B::Value {
1510        self.sp_from_section(self.section_len_offset as i64)
1511    }
1512
1513    /// Returns the stack pointer after the input has been popped
1514    /// (`&stack[stack.len - op.input()]`).
1515    ///
1516    /// This materializes all virtual values in the input/output window so builtins
1517    /// can read/write the physical stack. For any input whose value is a known constant,
1518    /// the constant is written into the corresponding stack slot. This allows DSE to NOOP
1519    /// the producing PUSH even for builtin-delegated opcodes that read operands directly
1520    /// from the stack pointer.
1521    ///
1522    /// The virtual stack is synced with the builtin's stack effect at instruction end
1523    /// via `sync_virtual_stack_diff`.
1524    #[must_use]
1525    fn sp_after_inputs(&mut self) -> B::Value {
1526        let (inputs, outputs) = self.current_inst().stack_io();
1527        let inputs = inputs as usize;
1528        let outputs = outputs as usize;
1529        let top = self.section_len_offset;
1530        let start = top - inputs as i32;
1531        let window = inputs.max(outputs) as i32;
1532        self.materialize_range(start, start + window);
1533        self.write_const_operands(inputs);
1534        self.sp_from_top(inputs)
1535    }
1536
1537    /// Like [`sp_after_inputs`](Self::sp_after_inputs) but only materializes the
1538    /// specified operand depths.
1539    #[must_use]
1540    fn sp_after_inputs_with(&mut self, depths: &[usize]) -> B::Value {
1541        let (inputs, _) = self.current_inst().stack_io();
1542        let inputs = inputs as usize;
1543        let top = self.section_len_offset;
1544        for &depth in depths {
1545            let off = top - inputs as i32 + (inputs - 1 - depth) as i32;
1546            self.materialize_range(off, off + 1);
1547        }
1548        self.write_const_operands(inputs);
1549        self.sp_from_top(inputs)
1550    }
1551
1552    /// Writes known-constant operands into the physical stack so that builtins see
1553    /// correct values even when DSE has NOOP'd the producing instruction.
1554    fn write_const_operands(&mut self, inputs: usize) {
1555        let inst = self.current_inst.unwrap();
1556        let top = self.section_len_offset;
1557        for depth in 0..inputs {
1558            let off = top - inputs as i32 + (inputs - 1 - depth) as i32;
1559            if let VSlot::Materialized = self.vstack.get_at_offset(off)
1560                && let Some(c) = self.bytecode.const_operand(inst, depth)
1561            {
1562                let value = self.bcx.iconst_256(c);
1563                let sp = self.sp_from_section(off as i64);
1564                self.bcx.store(value, sp);
1565            }
1566        }
1567    }
1568
1569    /// Returns a stack pointer offset from `section_start_sp`.
1570    fn sp_from_section(&mut self, offset: i64) -> B::Value {
1571        if offset == 0 {
1572            return self.section_start_sp;
1573        }
1574        let offset = self.bcx.iconst(self.isize_type, offset);
1575        self.bcx.gep(self.word_type, self.section_start_sp, &[offset], "sp")
1576    }
1577
1578    /// Returns the stack pointer at `len` (`&stack[len]`).
1579    fn sp_at(&mut self, len: B::Value) -> B::Value {
1580        let ptr = self.stack.addr(&mut self.bcx);
1581        self.bcx.gep(self.word_type, ptr, &[len], "sp")
1582    }
1583
1584    /// Returns the stack pointer at `n` from the top (`&stack[len - n]`).
1585    fn sp_from_top(&mut self, n: usize) -> B::Value {
1586        self.sp_from_section(self.section_len_offset as i64 - n as i64)
1587    }
1588
1589    /// Resolves a stack value at the given depth via the virtual stack.
1590    ///
1591    /// - `operand_depth`: depth for `const_operand` lookup (0 = first popped by the instruction).
1592    /// - `live_depth`: depth into the virtual stack's current live range (0 = current TOS).
1593    ///
1594    /// These differ only inside `popn` where multiple pops happen: `operand_depth` counts
1595    /// from the instruction start, while `live_depth` counts from the current virtual TOS.
1596    fn stack_value_at_depth(
1597        &mut self,
1598        operand_depth: usize,
1599        live_depth: usize,
1600        name: &str,
1601    ) -> B::Value {
1602        let inst = self.current_inst.unwrap();
1603        if let Some(c) = self.bytecode.const_operand(inst, operand_depth) {
1604            return self.bcx.iconst_256(c);
1605        }
1606        match self.vstack.get(live_depth) {
1607            VSlot::Virtual(v) => v,
1608            VSlot::Materialized => {
1609                let off = self.vstack.offset_at_depth(live_depth);
1610                let sp = self.sp_from_section(off as i64);
1611                let value = self.load_word(sp, name);
1612                self.vstack.set(live_depth, value);
1613                value
1614            }
1615        }
1616    }
1617
1618    /// Materializes all live virtual slots in the current section to memory.
1619    fn materialize_live_stack(&mut self) {
1620        let range = self.vstack.live_range();
1621        self.materialize_range(range.start, range.end);
1622    }
1623
1624    /// Eagerly materializes the coldest virtual slots when too many are live,
1625    /// preventing excessive register pressure in long sections.
1626    fn relieve_vstack_pressure(&mut self) {
1627        /// Materialize when more than this many virtual slots are live.
1628        const HIGH_WATER: usize = 2;
1629        /// Never materialize the top N slots (they're likely used soon).
1630        const KEEP_HOT: usize = 2;
1631
1632        let live = self.vstack.live_range();
1633        if (live.end - live.start) as usize <= HIGH_WATER {
1634            return;
1635        }
1636
1637        let virtual_count = self.vstack.virtual_count();
1638        if virtual_count <= HIGH_WATER {
1639            return;
1640        }
1641
1642        // Materialize everything below the hot window.
1643        let cold_end = (self.vstack.top_offset() - KEEP_HOT as i32).max(live.start);
1644        if cold_end > live.start {
1645            self.materialize_range(live.start, cold_end);
1646        }
1647    }
1648
1649    /// Materializes all virtual slots in the given section-relative offset range.
1650    fn materialize_range(&mut self, start: i32, end: i32) {
1651        let pending: Vec<_> = self.vstack.pending_stores(start..end).collect();
1652        for (off, value) in pending {
1653            let sp = self.sp_from_section(off as i64);
1654            self.bcx.store(value, sp);
1655        }
1656        self.vstack.mark_materialized_range(start..end);
1657    }
1658
1659    /// Builds a gas cost deduction for an immediate value.
1660    fn gas_cost_imm(&mut self, cost: u64) {
1661        if !self.config.gas_metering || cost == 0 {
1662            return;
1663        }
1664        let value = self.bcx.iconst(self.isize_type, cost as i64);
1665        self.gas_cost(value);
1666    }
1667
1668    /// Builds a gas cost deduction for a value.
1669    fn gas_cost(&mut self, cost: B::Value) {
1670        if !self.config.gas_metering {
1671            return;
1672        }
1673
1674        // Modified from `Gas::record_cost`.
1675        // This can overflow the gas counters, which has to be adjusted for after the call.
1676        let addr = self.gas_remaining_addr();
1677        let i64_type = self.bcx.type_int(64);
1678        let gas_remaining = self.bcx.load(i64_type, addr, "gas.remaining");
1679        let (res, overflow) = self.bcx.usub_overflow(gas_remaining, cost);
1680        self.bcx.store(res, addr);
1681        self.build_check(overflow, InstructionResult::OutOfGas);
1682    }
1683
1684    /// Ensures the memory is large enough for `offset + len` bytes, calling the `mresize`
1685    /// builtin on the cold path if needed. Returns the pointer to `mem_base + offset`.
1686    fn build_ensure_memory(&mut self, offset: B::Value, len: u64) -> B::Value {
1687        // 63 bits lets us avoid overflow in the addition below.
1688        let offset = self.u256_to_u64_saturating(offset, 63);
1689        // Analysis proved this access is already covered on every path.
1690        if self.current_inst.is_some_and(|inst| self.can_skip_ensure_memory(inst)) {
1691            return self.build_memory_addr(offset);
1692        }
1693
1694        let isize_type = self.isize_type;
1695        let len_const = self.bcx.iconst(isize_type, len as i64);
1696        let min_size = self.bcx.iadd(offset, len_const);
1697
1698        let direct_resize_size = self
1699            .current_inst
1700            .map(|inst| self.bytecode.memory_section(inst).direct_resize_size)
1701            .unwrap_or_default();
1702        // Analysis proved this access always grows memory to exactly `min_size`.
1703        if direct_resize_size != 0 {
1704            self.call_fallible_builtin(Builtin::Mresize, &[self.ecx, min_size]);
1705            self.cached_mem_base = None;
1706            return self.build_memory_addr(offset);
1707        }
1708
1709        // Otherwise emit the normal checked-resize branch.
1710        // Fast path: offset + len <= mem_len (no overflow).
1711        let mem_len_field =
1712            self.get_field(self.ecx, mem::offset_of!(EvmContext<'_>, mem_len), "ecx.mem_len.addr");
1713        let mem_len = self.bcx.load(isize_type, mem_len_field, "ecx.mem_len");
1714        let exceeds = self.bcx.icmp(IntCC::UnsignedGreaterThan, min_size, mem_len);
1715        self.cached_mem_base = None;
1716
1717        self.if_then(exceeds, |this| {
1718            this.bcx.set_current_block_cold();
1719            this.call_fallible_builtin(Builtin::Mresize, &[this.ecx, min_size]);
1720        });
1721        self.build_memory_addr(offset)
1722    }
1723
1724    fn can_skip_ensure_memory(&self, inst: Inst) -> bool {
1725        let section = self.bytecode.memory_section(inst);
1726        if section.known_size < section.required_size {
1727            return false;
1728        }
1729        let mut has_memory_access = false;
1730        for (offset, len) in self.bytecode.const_memory_accesses(inst).into_iter().flatten() {
1731            has_memory_access = true;
1732            if offset.is_none() || len.is_none() {
1733                return false;
1734            }
1735        }
1736        has_memory_access
1737    }
1738
1739    fn build_memory_addr(&mut self, offset: B::Value) -> B::Value {
1740        let mem_base = self.cached_mem_base.unwrap_or_else(|| self.load_memory_base());
1741        self.cached_mem_base = Some(mem_base);
1742        self.bcx.gep(self.i8_type, mem_base, &[offset], "mem.addr")
1743    }
1744
1745    fn load_memory_base(&mut self) -> B::Value {
1746        let ptr_type = self.bcx.type_ptr();
1747        let mem_base_field = self.get_field(
1748            self.ecx,
1749            mem::offset_of!(EvmContext<'_>, mem_base),
1750            "ecx.mem_base.addr",
1751        );
1752        self.bcx.load(ptr_type, mem_base_field, "ecx.mem_base")
1753    }
1754
1755    /*
1756    /// Builds a check, failing if the condition is false.
1757    ///
1758    /// `if success_cond { ... } else { return ret }`
1759    fn build_failure_inv(&mut self, success_cond: B::Value, ret: InstructionResult) {
1760        self.build_failure_imm_inner(false, success_cond, ret);
1761    }
1762    */
1763
1764    /// Emits under/overflow bounds checks for a stack section.
1765    fn check_stack_bounds(&mut self, stack_section: StackSection) {
1766        let inp = stack_section.inputs;
1767        let diff = stack_section.max_growth as i64;
1768
1769        let underflow = |this: &mut Self| {
1770            debug_assert!(inp > 0);
1771            this.bcx.icmp_imm(IntCC::UnsignedLessThan, this.len_before, inp as i64)
1772        };
1773        let overflow = |this: &mut Self| {
1774            debug_assert!(diff > 0);
1775            if diff > STACK_CAP as i64 {
1776                return this.bcx.bool_const(true);
1777            }
1778            this.bcx.icmp_imm(IntCC::UnsignedGreaterThan, this.len_before, STACK_CAP as i64 - diff)
1779        };
1780
1781        let may_underflow = inp > 0;
1782        let may_overflow = diff > 0;
1783        if may_underflow && may_overflow {
1784            let underflow = underflow(self);
1785            let overflow = overflow(self);
1786            let cond = self.bcx.bitor(underflow, overflow);
1787            let ret = {
1788                let under = self.bcx.iconst(self.i8_type, InstructionResult::StackUnderflow as i64);
1789                let over = self.bcx.iconst(self.i8_type, InstructionResult::StackOverflow as i64);
1790                self.bcx.select(underflow, under, over)
1791            };
1792            let target = self.build_check_inner(true, cond, ret);
1793            self.bcx.switch_to_block(target);
1794        } else if may_underflow {
1795            let cond = underflow(self);
1796            self.build_check(cond, InstructionResult::StackUnderflow);
1797        } else if may_overflow {
1798            let cond = overflow(self);
1799            self.build_check(cond, InstructionResult::StackOverflow);
1800        }
1801    }
1802
1803    /// Builds a check, failing if the condition is true.
1804    ///
1805    /// `if failure_cond { return ret } else { ... }`
1806    fn build_check(&mut self, failure_cond: B::Value, ret: InstructionResult) {
1807        self.build_check_imm_inner(true, failure_cond, ret);
1808    }
1809
1810    fn build_check_imm_inner(&mut self, is_failure: bool, cond: B::Value, ret: InstructionResult) {
1811        let ret_value = self.bcx.iconst(self.i8_type, ret as i64);
1812        let target = self.build_check_inner(is_failure, cond, ret_value);
1813        if self.config.comments {
1814            self.add_comment(&format!("check {ret:?}"));
1815        }
1816        self.bcx.switch_to_block(target);
1817    }
1818
1819    #[must_use]
1820    fn build_check_inner(
1821        &mut self,
1822        is_failure: bool,
1823        cond: B::Value,
1824        ret: B::Value,
1825    ) -> B::BasicBlock {
1826        let current_block = self.current_block();
1827        let target = self.create_block_after(current_block, "contd");
1828
1829        let exit_block = if is_failure {
1830            if let Some(failure_block) = self.failure_block {
1831                self.incoming_failures.push((ret, current_block));
1832                failure_block
1833            } else {
1834                self.create_block_after(target, "failure")
1835            }
1836        } else if let Some(return_block) = self.return_block {
1837            self.incoming_returns.push((ret, current_block));
1838            return_block
1839        } else {
1840            self.create_block_after(target, "return")
1841        };
1842        let then_block = if is_failure { exit_block } else { target };
1843        let else_block = if is_failure { target } else { exit_block };
1844        self.bcx.brif_cold(cond, then_block, else_block, is_failure);
1845
1846        if (is_failure && self.failure_block.is_none())
1847            || (!is_failure && self.return_block.is_none())
1848        {
1849            self.bcx.switch_to_block(exit_block);
1850            self.bcx.ret(&[ret]);
1851        }
1852
1853        target
1854    }
1855
1856    /// Builds a branch to the failure block.
1857    fn build_fail_imm(&mut self, ret: InstructionResult) {
1858        let ret_value = self.bcx.iconst(self.i8_type, ret as i64);
1859        self.build_fail(ret_value);
1860        if self.config.comments {
1861            self.add_comment(&format!("fail {ret:?}"));
1862        }
1863    }
1864
1865    /// Builds a branch to the failure block.
1866    fn build_fail(&mut self, ret: B::Value) {
1867        if self.config.inspect_stack {
1868            self.materialize_live_stack();
1869        }
1870        if let Some(block) = self.failure_block {
1871            self.incoming_failures.push((ret, self.bcx.current_block().unwrap()));
1872            self.bcx.br(block);
1873        } else {
1874            self.bcx.ret(&[ret]);
1875        }
1876    }
1877
1878    /// Builds a branch to the return block.
1879    fn build_return_imm(&mut self, ret: InstructionResult) {
1880        let ret_value = self.bcx.iconst(self.i8_type, ret as i64);
1881        self.build_return(ret_value);
1882        if self.config.comments {
1883            self.add_comment(&format!("return {ret:?}"));
1884        }
1885    }
1886
1887    /// Builds a branch to the return block.
1888    fn build_return(&mut self, ret: B::Value) {
1889        if self.config.inspect_stack {
1890            self.materialize_live_stack();
1891        }
1892        self.build_return_inner(ret);
1893    }
1894
1895    fn build_return_inner(&mut self, ret: B::Value) {
1896        if let Some(block) = self.return_block {
1897            self.incoming_returns.push((ret, self.bcx.current_block().unwrap()));
1898            self.bcx.br(block);
1899        } else {
1900            self.bcx.ret(&[ret]);
1901        }
1902    }
1903
1904    fn add_invalid_jump(&mut self) -> B::BasicBlock {
1905        let block = self.failure_block.unwrap();
1906        self.incoming_failures.push((
1907            self.bcx.iconst(self.i8_type, InstructionResult::InvalidJump as i64),
1908            self.bcx.current_block().unwrap(),
1909        ));
1910        block
1911    }
1912
1913    /// Build a call to the panic builtin.
1914    fn call_panic(&mut self, msg: &str) {
1915        let function = self.builtin_function(Builtin::Panic);
1916        let ptr = self.bcx.str_const(msg);
1917        let len = self.bcx.iconst(self.isize_type, msg.len() as i64);
1918        let _ = self.bcx.call(function, &[ptr, len]);
1919        self.bcx.unreachable();
1920    }
1921
1922    #[allow(dead_code)]
1923    fn call_printf(&mut self, template: &std::ffi::CStr, values: &[B::Value]) {
1924        let mut args = Vec::with_capacity(values.len() + 1);
1925        args.push(self.bcx.cstr_const(template));
1926        args.extend_from_slice(values);
1927        let printf = self.bcx.get_printf_function();
1928        let _ = self.bcx.call(printf, &args);
1929    }
1930
1931    /// Build a call to a fallible builtin.
1932    ///
1933    /// The builtin longjmps on error, so no return value check is needed.
1934    fn call_fallible_builtin(&mut self, builtin: Builtin, args: &[B::Value]) {
1935        let _ = self.call_builtin(builtin, args);
1936    }
1937
1938    /// Build a call to a builtin.
1939    #[must_use]
1940    fn call_builtin(&mut self, builtin: Builtin, args: &[B::Value]) -> Option<B::Value> {
1941        let function = self.builtin_function(builtin);
1942        // self.call_printf(
1943        //     format_printf!("{} - calling {}\n", self.op_block_name(""), builtin.name()),
1944        //     &[],
1945        // );
1946        let invalidate_mem_base =
1947            !self.current_inst.is_some_and(|inst| self.can_skip_ensure_memory(inst));
1948        let value = self.bcx.call(function, args);
1949        if invalidate_mem_base {
1950            self.cached_mem_base = None;
1951        }
1952        value
1953    }
1954
1955    /// Gets the function for the given builtin.
1956    fn builtin_function(&mut self, builtin: Builtin) -> B::Function {
1957        self.builtins.get(builtin, &mut self.bcx)
1958    }
1959
1960    /// Adds a comment to the current instruction.
1961    fn add_comment(&mut self, comment: &str) {
1962        if comment.is_empty() || !self.config.comments {
1963            return;
1964        }
1965        self.bcx.add_comment_to_current_inst(comment);
1966    }
1967
1968    /// Returns the current instruction.
1969    fn current_inst(&self) -> &InstData {
1970        self.bytecode.inst(self.current_inst.unwrap())
1971    }
1972
1973    fn if_then(&mut self, cond: B::Value, then: impl FnOnce(&mut Self)) {
1974        let current_block = self.current_block();
1975        let then_block = self.create_block_after(current_block, "then");
1976        let done_block = self.create_block_after(then_block, "contd");
1977
1978        self.bcx.brif(cond, then_block, done_block);
1979
1980        self.bcx.switch_to_block(then_block);
1981        then(self);
1982        self.bcx.br(done_block);
1983
1984        self.bcx.switch_to_block(done_block);
1985    }
1986
1987    /// Returns the current block.
1988    fn current_block(&mut self) -> B::BasicBlock {
1989        // There always is a block present.
1990        self.bcx.current_block().expect("no blocks")
1991    }
1992
1993    fn little_endian(&self) -> bool {
1994        true
1995    }
1996
1997    /*
1998    /// Creates a named block.
1999    fn create_block(&mut self, name: &str) -> B::BasicBlock {
2000        let name = self.op_block_name(name);
2001        self.bcx.create_block(&name)
2002    }
2003    */
2004
2005    /// Creates a named block after the given block.
2006    fn create_block_after(&mut self, after: B::BasicBlock, name: &str) -> B::BasicBlock {
2007        let name = self.op_block_name(name);
2008        self.bcx.create_block_after(after, &name)
2009    }
2010
2011    /// Returns the block name for the current opcode with the given suffix.
2012    fn op_block_name(&self, name: &str) -> String {
2013        if !self.config.debug {
2014            return String::new();
2015        }
2016        self.bytecode.op_block_name(self.current_inst, name)
2017    }
2018
2019    /// Converts a 256-bit unsigned integer to a 64-bit unsigned integer, saturating at
2020    /// `2^bits - 1`.
2021    fn u256_to_u64_saturating(&mut self, value: B::Value, bits: usize) -> B::Value {
2022        let i64_type = self.bcx.type_int(64);
2023        let reduced = self.bcx.ireduce(i64_type, value);
2024        let sentinel_lit = 1u128.checked_shl(bits as u32).unwrap_or(0).wrapping_sub(1);
2025        let sentinel_u256 = self.bcx.iconst_256(U256::from(sentinel_lit));
2026        let fits = self.bcx.icmp(IntCC::UnsignedLessThanOrEqual, value, sentinel_u256);
2027        let sentinel = self.bcx.iconst(i64_type, sentinel_lit as i64);
2028        self.bcx.select(fits, reduced, sentinel)
2029    }
2030}
2031
2032/// IR builtins.
2033impl<B: Backend> FunctionCx<'_, B> {
2034    #[allow(dead_code)]
2035    #[must_use]
2036    fn call_ir_builtin(
2037        &mut self,
2038        name: &str,
2039        args: &[B::Value],
2040        arg_types: &[B::Type],
2041        ret: Option<B::Type>,
2042        build: impl FnOnce(&mut Self),
2043    ) -> Option<B::Value> {
2044        let prefix = "__revmc_ir_builtin_";
2045        let name = &format!("{prefix}{name}")[..];
2046
2047        // self.call_printf(format_printf!("{} - calling {name}\n", self.op_block_name("")), &[]);
2048
2049        debug_assert_eq!(args.len(), arg_types.len());
2050        let linkage = revmc_backend::Linkage::Private;
2051        // SAFETY: `this` aliases `self` to work around the borrow checker: we need `self.bcx`
2052        // borrowed by `get_or_build_function` while also swapping `self.bcx` inside the closure.
2053        // The closure's `bcx` is a fresh builder (not `self.bcx`), so the swap is safe.
2054        let debug_location = self
2055            .config
2056            .debug
2057            .then(|| self.current_inst.map(|inst| self.inst_lines[inst]))
2058            .flatten();
2059        self.bcx.clear_debug_location();
2060
2061        let this = unsafe { &mut *(self as *mut Self) };
2062        let f = self.bcx.get_or_build_function(name, arg_types, ret, linkage, |bcx| {
2063            let prev_return_block = this.return_block.take();
2064            let prev_failure_block = this.failure_block.take();
2065            // SAFETY: `this.bcx` and `bcx` are non-overlapping (bcx is a fresh builder).
2066            unsafe { std::ptr::swap(&mut this.bcx, bcx) };
2067
2068            for attr in default_attrs::for_fn().chain(std::iter::once(Attribute::NoUnwind)) {
2069                this.bcx.add_function_attribute(None, attr, FunctionAttributeLocation::Function)
2070            }
2071            for i in 0..this.bcx.num_fn_params() as u32 {
2072                for attr in default_attrs::for_param() {
2073                    this.bcx.add_function_attribute(None, attr, FunctionAttributeLocation::Param(i))
2074                }
2075            }
2076            build(this);
2077
2078            // SAFETY: same as above.
2079            unsafe { std::ptr::swap(&mut this.bcx, bcx) };
2080            this.failure_block = prev_failure_block;
2081            this.return_block = prev_return_block;
2082        });
2083        if let Some(line) = debug_location {
2084            self.bcx.set_debug_location(line, 1);
2085        }
2086        self.bcx.call(f, args)
2087    }
2088}
2089
2090// HACK: Need these structs' fields to be public for `offset_of!`.
2091// `pf == private_fields`.
2092#[allow(dead_code)]
2093mod pf {
2094    use super::*;
2095
2096    #[repr(C)] // See core::ptr::metadata::PtrComponents
2097    pub(super) struct Slice {
2098        pub(super) ptr: *const u8,
2099        pub(super) len: usize,
2100    }
2101    const _: [(); mem::size_of::<&'static [u8]>()] = [(); mem::size_of::<Slice>()];
2102
2103    pub(super) struct Gas {
2104        /// GasTracker fields (EIP-8037 reservoir model).
2105        pub(super) tracker: GasTracker,
2106        /// Memory gas tracking (words_num: usize, expansion_cost: u64).
2107        pub(super) memory: MemoryGas,
2108    }
2109
2110    pub(super) struct GasTracker {
2111        /// The initial gas limit.
2112        pub(super) limit: u64,
2113        /// The remaining gas.
2114        pub(super) remaining: u64,
2115        /// State gas reservoir (EIP-8037).
2116        pub(super) reservoir: u64,
2117        /// Total state gas spent.
2118        pub(super) state_gas_spent: u64,
2119        /// Refunded gas.
2120        pub(super) refunded: i64,
2121    }
2122
2123    #[repr(C)]
2124    pub(super) struct MemoryGas {
2125        pub(super) words_num: usize,
2126        pub(super) expansion_cost: u64,
2127    }
2128    const _: [(); mem::size_of::<revm_interpreter::Gas>()] = [(); mem::size_of::<Gas>()];
2129}
2130
2131/// Computes the effective stack diff for an instruction, matching the codegen semantics.
2132fn effective_stack_diff(inp: u8, out: u8, data: &InstData) -> i32 {
2133    let mut diff = out as i32 - inp as i32;
2134    // Suspending ops return one value pushed by the caller after resume.
2135    if data.may_suspend() {
2136        diff -= 1;
2137    }
2138    diff
2139}
2140
2141fn get_field<B: Builder>(bcx: &mut B, ptr: B::Value, offset: usize, name: &str) -> B::Value {
2142    let offset = bcx.iconst(bcx.type_ptr_sized_int(), offset as i64);
2143    bcx.gep(bcx.type_int(8), ptr, &[offset], name)
2144}
2145
2146#[allow(unused)]
2147macro_rules! format_printf {
2148    ($($t:tt)*) => {
2149        &std::ffi::CString::new(format!($($t)*)).unwrap()
2150    };
2151}
2152#[allow(unused)]
2153use format_printf;