winch_codegen/codegen/
mod.rs

1use crate::{
2    abi::{ABIOperand, ABISig, RetArea, vmctx},
3    codegen::BlockSig,
4    isa::reg::{Reg, writable},
5    masm::{
6        AtomicWaitKind, Extend, Imm, IntCmpKind, IntScratch, LaneSelector, LoadKind,
7        MacroAssembler, OperandSize, RegImm, RmwOp, SPOffset, ShiftKind, StoreKind, TrapCode,
8        UNTRUSTED_FLAGS, Zero,
9    },
10    stack::TypedReg,
11};
12use anyhow::{Result, anyhow, bail, ensure};
13use cranelift_codegen::{
14    binemit::CodeOffset,
15    ir::{RelSourceLoc, SourceLoc},
16};
17use smallvec::SmallVec;
18use std::marker::PhantomData;
19use wasmparser::{
20    BinaryReader, FuncValidator, MemArg, Operator, OperatorsReader, ValidatorResources,
21    VisitOperator, VisitSimdOperator,
22};
23use wasmtime_cranelift::{TRAP_BAD_SIGNATURE, TRAP_HEAP_MISALIGNED, TRAP_TABLE_OUT_OF_BOUNDS};
24use wasmtime_environ::{
25    FUNCREF_MASK, GlobalIndex, MemoryIndex, PtrSize, TableIndex, Tunables, TypeIndex, WasmHeapType,
26    WasmValType,
27};
28
29mod context;
30pub(crate) use context::*;
31mod env;
32pub use env::*;
33mod call;
34pub(crate) use call::*;
35mod control;
36pub(crate) use control::*;
37mod builtin;
38pub use builtin::*;
39pub(crate) mod bounds;
40
41use bounds::{Bounds, ImmOffset, Index};
42
43mod phase;
44pub(crate) use phase::*;
45
46mod error;
47pub(crate) use error::*;
48
49/// Branch states in the compiler, enabling the derivation of the
50/// reachability state.
51pub(crate) trait BranchState {
52    /// Whether the compiler will enter in an unreachable state after
53    /// the branch is emitted.
54    fn unreachable_state_after_emission() -> bool;
55}
56
57/// A conditional branch state, with a fallthrough.
58pub(crate) struct ConditionalBranch;
59
60impl BranchState for ConditionalBranch {
61    fn unreachable_state_after_emission() -> bool {
62        false
63    }
64}
65
66/// Unconditional branch state.
67pub(crate) struct UnconditionalBranch;
68
69impl BranchState for UnconditionalBranch {
70    fn unreachable_state_after_emission() -> bool {
71        true
72    }
73}
74
75/// Holds metadata about the source code location and the machine code emission.
76/// The fields of this struct are opaque and are not interpreted in any way.
77/// They serve as a mapping between source code and machine code.
78#[derive(Default)]
79pub(crate) struct SourceLocation {
80    /// The base source location.
81    pub base: Option<SourceLoc>,
82    /// The current relative source code location along with its associated
83    /// machine code offset.
84    pub current: (CodeOffset, RelSourceLoc),
85}
86
87/// The code generation abstraction.
88pub(crate) struct CodeGen<'a, 'translation: 'a, 'data: 'translation, M, P>
89where
90    M: MacroAssembler,
91    P: CodeGenPhase,
92{
93    /// The ABI-specific representation of the function signature, excluding results.
94    pub sig: ABISig,
95
96    /// The code generation context.
97    pub context: CodeGenContext<'a, P>,
98
99    /// A reference to the function compilation environment.
100    pub env: FuncEnv<'a, 'translation, 'data, M::Ptr>,
101
102    /// The MacroAssembler.
103    pub masm: &'a mut M,
104
105    /// Stack frames for control flow.
106    // NB The 64 is set arbitrarily, we can adjust it as
107    // we see fit.
108    pub control_frames: SmallVec<[ControlStackFrame; 64]>,
109
110    /// Information about the source code location.
111    pub source_location: SourceLocation,
112
113    /// Compilation settings for code generation.
114    pub tunables: &'a Tunables,
115
116    /// Local counter to track fuel consumption.
117    pub fuel_consumed: i64,
118    phase: PhantomData<P>,
119}
120
121impl<'a, 'translation, 'data, M> CodeGen<'a, 'translation, 'data, M, Prologue>
122where
123    M: MacroAssembler,
124{
125    pub fn new(
126        tunables: &'a Tunables,
127        masm: &'a mut M,
128        context: CodeGenContext<'a, Prologue>,
129        env: FuncEnv<'a, 'translation, 'data, M::Ptr>,
130        sig: ABISig,
131    ) -> CodeGen<'a, 'translation, 'data, M, Prologue> {
132        Self {
133            sig,
134            context,
135            masm,
136            env,
137            tunables,
138            source_location: Default::default(),
139            control_frames: Default::default(),
140            // Empty functions should consume at least 1 fuel unit.
141            fuel_consumed: 1,
142            phase: PhantomData,
143        }
144    }
145
146    /// Code generation prologue.
147    pub fn emit_prologue(mut self) -> Result<CodeGen<'a, 'translation, 'data, M, Emission>> {
148        let vmctx = self
149            .sig
150            .params()
151            .first()
152            .ok_or_else(|| anyhow!(CodeGenError::vmcontext_arg_expected()))?
153            .unwrap_reg();
154
155        self.masm.start_source_loc(Default::default())?;
156        // We need to use the vmctx parameter before pinning it for stack checking.
157        self.masm.prologue(vmctx)?;
158
159        // Pin the `VMContext` pointer.
160        self.masm.mov(
161            writable!(vmctx!(M)),
162            vmctx.into(),
163            self.env.ptr_type().try_into()?,
164        )?;
165
166        self.masm.reserve_stack(self.context.frame.locals_size)?;
167        self.spill_register_arguments()?;
168
169        let defined_locals_range = &self.context.frame.defined_locals_range;
170        self.masm.zero_mem_range(defined_locals_range.as_range())?;
171
172        // Save the results base parameter register into its slot.
173
174        if self.sig.params.has_retptr() {
175            match self.sig.params.unwrap_results_area_operand() {
176                ABIOperand::Reg { ty, reg, .. } => {
177                    let results_base_slot = self.context.frame.results_base_slot.as_ref().unwrap();
178                    ensure!(
179                        results_base_slot.addressed_from_sp(),
180                        CodeGenError::sp_addressing_expected(),
181                    );
182                    let addr = self.masm.local_address(results_base_slot)?;
183                    self.masm.store((*reg).into(), addr, (*ty).try_into()?)?;
184                }
185                // The result base parameter is a stack parameter, addressed
186                // from FP.
187                _ => {}
188            }
189        }
190
191        self.masm.end_source_loc()?;
192
193        Ok(CodeGen {
194            sig: self.sig,
195            context: self.context.for_emission(),
196            masm: self.masm,
197            env: self.env,
198            tunables: self.tunables,
199            source_location: self.source_location,
200            control_frames: self.control_frames,
201            fuel_consumed: self.fuel_consumed,
202            phase: PhantomData,
203        })
204    }
205
206    fn spill_register_arguments(&mut self) -> Result<()> {
207        use WasmValType::*;
208        for (operand, slot) in self
209            .sig
210            .params_without_retptr()
211            .iter()
212            .zip(self.context.frame.locals())
213        {
214            match (operand, slot) {
215                (ABIOperand::Reg { ty, reg, .. }, slot) => {
216                    let addr = self.masm.local_address(slot)?;
217                    match &ty {
218                        I32 | I64 | F32 | F64 | V128 => {
219                            self.masm.store((*reg).into(), addr, (*ty).try_into()?)?;
220                        }
221                        Ref(rt) => match rt.heap_type {
222                            WasmHeapType::Func | WasmHeapType::Extern => {
223                                self.masm.store_ptr(*reg, addr)?;
224                            }
225                            _ => bail!(CodeGenError::unsupported_wasm_type()),
226                        },
227                    }
228                }
229                // Skip non-register arguments
230                _ => {}
231            }
232        }
233        Ok(())
234    }
235}
236
237impl<'a, 'translation, 'data, M> CodeGen<'a, 'translation, 'data, M, Emission>
238where
239    M: MacroAssembler,
240{
241    /// Emit the function body to machine code.
242    pub fn emit(
243        &mut self,
244        body: BinaryReader<'a>,
245        validator: &mut FuncValidator<ValidatorResources>,
246    ) -> Result<()> {
247        self.emit_body(body, validator)
248            .and_then(|_| self.emit_end())?;
249
250        Ok(())
251    }
252
253    /// Pops a control frame from the control frame stack.
254    pub fn pop_control_frame(&mut self) -> Result<ControlStackFrame> {
255        self.control_frames
256            .pop()
257            .ok_or_else(|| anyhow!(CodeGenError::control_frame_expected()))
258    }
259
260    /// Derives a [RelSourceLoc] from a [SourceLoc].
261    pub fn source_loc_from(&mut self, loc: SourceLoc) -> RelSourceLoc {
262        if self.source_location.base.is_none() && !loc.is_default() {
263            self.source_location.base = Some(loc);
264        }
265
266        RelSourceLoc::from_base_offset(self.source_location.base.unwrap_or_default(), loc)
267    }
268
269    /// The following two helpers, handle else or end instructions when the
270    /// compiler has entered into an unreachable code state. These instructions
271    /// must be observed to determine if the reachability state should be
272    /// restored.
273    ///
274    /// When the compiler is in an unreachable state, all the other instructions
275    /// are not visited.
276    pub fn handle_unreachable_else(&mut self) -> Result<()> {
277        let frame = self
278            .control_frames
279            .last_mut()
280            .ok_or_else(|| CodeGenError::control_frame_expected())?;
281        ensure!(frame.is_if(), CodeGenError::if_control_frame_expected());
282        if frame.is_next_sequence_reachable() {
283            // We entered an unreachable state when compiling the
284            // if-then branch, but if the `if` was reachable at
285            // entry, the if-else branch will be reachable.
286            self.context.reachable = true;
287            frame.ensure_stack_state(self.masm, &mut self.context)?;
288            frame.bind_else(self.masm, &mut self.context)?;
289        }
290        Ok(())
291    }
292
293    pub fn handle_unreachable_end(&mut self) -> Result<()> {
294        let mut frame = self.pop_control_frame()?;
295        // We just popped the outermost block.
296        let is_outermost = self.control_frames.len() == 0;
297
298        if frame.is_next_sequence_reachable() {
299            self.context.reachable = true;
300            frame.ensure_stack_state(self.masm, &mut self.context)?;
301            frame.bind_end(self.masm, &mut self.context)
302        } else if is_outermost {
303            // If we reach the end of the function in an unreachable
304            // state, perform the necessary cleanup to leave the stack
305            // and SP in the expected state.  The compiler can enter
306            // in this state through an infinite loop.
307            frame.ensure_stack_state(self.masm, &mut self.context)
308        } else {
309            Ok(())
310        }
311    }
312
313    fn emit_body(
314        &mut self,
315        body: BinaryReader<'a>,
316        validator: &mut FuncValidator<ValidatorResources>,
317    ) -> Result<()> {
318        self.maybe_emit_fuel_check()?;
319
320        self.maybe_emit_epoch_check()?;
321
322        // Once we have emitted the epilogue and reserved stack space for the locals, we push the
323        // base control flow block.
324        self.control_frames.push(ControlStackFrame::block(
325            BlockSig::from_sig(self.sig.clone()),
326            self.masm,
327            &mut self.context,
328        )?);
329
330        // Set the return area of the results *after* initializing the block. In
331        // the function body block case, we'll treat the results as any other
332        // case, addressed from the stack pointer, and when ending the function
333        // the return area will be set to the return pointer.
334        if self.sig.params.has_retptr() {
335            self.sig
336                .results
337                .set_ret_area(RetArea::slot(self.context.frame.results_base_slot.unwrap()));
338        }
339
340        let mut ops = OperatorsReader::new(body);
341        while !ops.eof() {
342            let offset = ops.original_position();
343            ops.visit_operator(&mut ValidateThenVisit(
344                validator.simd_visitor(offset),
345                self,
346                offset,
347            ))??;
348        }
349        ops.finish()?;
350        return Ok(());
351
352        struct ValidateThenVisit<'a, T, U>(T, &'a mut U, usize);
353
354        macro_rules! validate_then_visit {
355            ($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident $ann:tt)*) => {
356                $(
357                    fn $visit(&mut self $($(,$arg: $argty)*)?) -> Self::Output {
358                        self.0.$visit($($($arg.clone()),*)?)?;
359                        let op = Operator::$op $({ $($arg: $arg.clone()),* })?;
360                        if self.1.visit(&op) {
361                            self.1.before_visit_op(&op, self.2)?;
362                            let res = self.1.$visit($($($arg),*)?)?;
363                            self.1.after_visit_op()?;
364                            Ok(res)
365                        } else {
366                            Ok(())
367                        }
368                    }
369                )*
370            };
371        }
372
373        fn visit_op_when_unreachable(op: &Operator) -> bool {
374            use Operator::*;
375            match op {
376                If { .. } | Block { .. } | Loop { .. } | Else | End => true,
377                _ => false,
378            }
379        }
380
381        /// Trait to handle hooks that must happen before and after visiting an
382        /// operator.
383        trait VisitorHooks {
384            /// Hook prior to visiting an operator.
385            fn before_visit_op(&mut self, operator: &Operator, offset: usize) -> Result<()>;
386            /// Hook after visiting an operator.
387            fn after_visit_op(&mut self) -> Result<()>;
388
389            /// Returns `true` if the operator will be visited.
390            ///
391            /// Operators will be visited if the following invariants are met:
392            /// * The compiler is in a reachable state.
393            /// * The compiler is in an unreachable state, but the current
394            ///   operator is a control flow operator. These operators need to be
395            ///   visited in order to keep the control stack frames balanced and
396            ///   to determine if the reachability state must be restored.
397            fn visit(&self, op: &Operator) -> bool;
398        }
399
400        impl<'a, 'translation, 'data, M: MacroAssembler> VisitorHooks
401            for CodeGen<'a, 'translation, 'data, M, Emission>
402        {
403            fn visit(&self, op: &Operator) -> bool {
404                self.context.reachable || visit_op_when_unreachable(op)
405            }
406
407            fn before_visit_op(&mut self, operator: &Operator, offset: usize) -> Result<()> {
408                // Handle source location mapping.
409                self.source_location_before_visit_op(offset)?;
410
411                // Handle fuel.
412                if self.tunables.consume_fuel {
413                    self.fuel_before_visit_op(operator)?;
414                }
415                Ok(())
416            }
417
418            fn after_visit_op(&mut self) -> Result<()> {
419                // Handle source code location mapping.
420                self.source_location_after_visit_op()
421            }
422        }
423
424        impl<'a, T, U> VisitOperator<'a> for ValidateThenVisit<'_, T, U>
425        where
426            T: VisitSimdOperator<'a, Output = wasmparser::Result<()>>,
427            U: VisitSimdOperator<'a, Output = Result<()>> + VisitorHooks,
428        {
429            type Output = U::Output;
430
431            fn simd_visitor(
432                &mut self,
433            ) -> Option<&mut dyn VisitSimdOperator<'a, Output = Self::Output>>
434            where
435                T:,
436            {
437                Some(self)
438            }
439
440            wasmparser::for_each_visit_operator!(validate_then_visit);
441        }
442
443        impl<'a, T, U> VisitSimdOperator<'a> for ValidateThenVisit<'_, T, U>
444        where
445            T: VisitSimdOperator<'a, Output = wasmparser::Result<()>>,
446            U: VisitSimdOperator<'a, Output = Result<()>> + VisitorHooks,
447        {
448            wasmparser::for_each_visit_simd_operator!(validate_then_visit);
449        }
450    }
451
452    /// Emits a a series of instructions that will type check a function reference call.
453    pub fn emit_typecheck_funcref(
454        &mut self,
455        funcref_ptr: Reg,
456        type_index: TypeIndex,
457    ) -> Result<()> {
458        let ptr_size: OperandSize = self.env.ptr_type().try_into()?;
459        let sig_index_bytes = self.env.vmoffsets.size_of_vmshared_type_index();
460        let sig_size = OperandSize::from_bytes(sig_index_bytes);
461        let sig_index = self.env.translation.module.types[type_index].unwrap_module_type_index();
462        let sig_offset = sig_index
463            .as_u32()
464            .checked_mul(sig_index_bytes.into())
465            .unwrap();
466        let signatures_base_offset = self.env.vmoffsets.ptr.vmctx_type_ids_array();
467        let funcref_sig_offset = self.env.vmoffsets.ptr.vm_func_ref_type_index();
468        // Get the caller id.
469        let caller_id = self.context.any_gpr(self.masm)?;
470
471        self.masm.with_scratch::<IntScratch, _>(|masm, scratch| {
472            // Load the signatures address into the scratch register.
473            masm.load(
474                masm.address_at_vmctx(signatures_base_offset.into())?,
475                scratch.writable(),
476                ptr_size,
477            )?;
478
479            masm.load(
480                masm.address_at_reg(scratch.inner(), sig_offset)?,
481                writable!(caller_id),
482                sig_size,
483            )
484        })?;
485
486        let callee_id = self.context.any_gpr(self.masm)?;
487        self.masm.load(
488            self.masm
489                .address_at_reg(funcref_ptr, funcref_sig_offset.into())?,
490            writable!(callee_id),
491            sig_size,
492        )?;
493
494        // Typecheck.
495        self.masm
496            .cmp(caller_id, callee_id.into(), OperandSize::S32)?;
497        self.masm.trapif(IntCmpKind::Ne, TRAP_BAD_SIGNATURE)?;
498        self.context.free_reg(callee_id);
499        self.context.free_reg(caller_id);
500        anyhow::Ok(())
501    }
502
503    /// Emit the usual function end instruction sequence.
504    fn emit_end(&mut self) -> Result<()> {
505        // The implicit body block is treated a normal block (it pushes results
506        // to the stack); so when reaching the end, we pop them taking as
507        // reference the current function's signature.
508        let base = SPOffset::from_u32(self.context.frame.locals_size);
509        self.masm.start_source_loc(Default::default())?;
510        if self.context.reachable {
511            ControlStackFrame::pop_abi_results_impl(
512                &mut self.sig.results,
513                &mut self.context,
514                self.masm,
515                |results, _, _| Ok(results.ret_area().copied()),
516            )?;
517        } else {
518            // If we reach the end of the function in an unreachable code state,
519            // simply truncate to the expected values.
520            // The compiler could enter this state through an infinite loop.
521            self.context.truncate_stack_to(0)?;
522            self.masm.reset_stack_pointer(base)?;
523        }
524        ensure!(
525            self.context.stack.len() == 0,
526            CodeGenError::unexpected_value_in_value_stack()
527        );
528        self.masm.free_stack(self.context.frame.locals_size)?;
529        self.masm.epilogue()?;
530        self.masm.end_source_loc()?;
531        Ok(())
532    }
533
534    /// Pops the value at the stack top and assigns it to the local at
535    /// the given index, returning the typed register holding the
536    /// source value.
537    pub fn emit_set_local(&mut self, index: u32) -> Result<TypedReg> {
538        // Materialize any references to the same local index that are in the
539        // value stack by spilling.
540        if self.context.stack.contains_latent_local(index) {
541            self.context.spill(self.masm)?;
542        }
543        let src = self.context.pop_to_reg(self.masm, None)?;
544        // Need to get address of local after `pop_to_reg` since `pop_to_reg`
545        // will pop the machine stack causing an incorrect address to be
546        // calculated.
547        let (ty, addr) = self.context.frame.get_local_address(index, self.masm)?;
548        self.masm
549            .store(RegImm::reg(src.reg), addr, ty.try_into()?)?;
550
551        Ok(src)
552    }
553
554    /// Loads the address of the given global.
555    pub fn emit_get_global_addr(&mut self, index: GlobalIndex) -> Result<(WasmValType, Reg, u32)> {
556        let data = self.env.resolve_global(index);
557
558        if data.imported {
559            let global_base = self.masm.address_at_reg(vmctx!(M), data.offset)?;
560            let dst = self.context.any_gpr(self.masm)?;
561            self.masm.load_ptr(global_base, writable!(dst))?;
562            Ok((data.ty, dst, 0))
563        } else {
564            Ok((data.ty, vmctx!(M), data.offset))
565        }
566    }
567
568    pub fn emit_lazy_init_funcref(&mut self, table_index: TableIndex) -> Result<()> {
569        assert!(self.tunables.table_lazy_init, "unsupported eager init");
570        let table_data = self.env.resolve_table_data(table_index);
571        let ptr_type = self.env.ptr_type();
572        let builtin = self
573            .env
574            .builtins
575            .table_get_lazy_init_func_ref::<M::ABI, M::Ptr>()?;
576
577        // Request the builtin's  result register and use it to hold the table
578        // element value. We preemptively spill and request this register to
579        // avoid conflict at the control flow merge below. Requesting the result
580        // register is safe since we know ahead-of-time the builtin's signature.
581        self.context.spill(self.masm)?;
582        let elem_value: Reg = self.context.reg(
583            builtin.sig().results.unwrap_singleton().unwrap_reg(),
584            self.masm,
585        )?;
586
587        let index = self.context.pop_to_reg(self.masm, None)?;
588        let base = self.context.any_gpr(self.masm)?;
589
590        let elem_addr = self.emit_compute_table_elem_addr(index.into(), base, &table_data)?;
591        self.masm.load_ptr(elem_addr, writable!(elem_value))?;
592        // Free the register used as base, once we have loaded the element
593        // address into the element value register.
594        self.context.free_reg(base);
595
596        let (defined, cont) = (self.masm.get_label()?, self.masm.get_label()?);
597
598        // Push the built-in arguments to the stack.
599        self.context
600            .stack
601            .extend([table_index.as_u32().try_into().unwrap(), index.into()]);
602
603        self.masm.branch(
604            IntCmpKind::Ne,
605            elem_value,
606            elem_value.into(),
607            defined,
608            ptr_type.try_into()?,
609        )?;
610        // Free the element value register.
611        // This is safe since the FnCall::emit call below, will ensure
612        // that the result register is placed on the value stack.
613        self.context.free_reg(elem_value);
614        FnCall::emit::<M>(
615            &mut self.env,
616            self.masm,
617            &mut self.context,
618            Callee::Builtin(builtin.clone()),
619        )?;
620
621        // We know the signature of the libcall in this case, so we assert that there's
622        // one element in the stack and that it's  the ABI signature's result register.
623        let top = self
624            .context
625            .stack
626            .peek()
627            .ok_or_else(|| CodeGenError::missing_values_in_stack())?;
628        let top = top.unwrap_reg();
629        ensure!(
630            top.reg == elem_value,
631            CodeGenError::table_element_value_expected()
632        );
633        self.masm.jmp(cont)?;
634
635        // In the defined case, mask the funcref address in place, by peeking into the
636        // last element of the value stack, which was pushed by the `indirect` function
637        // call above.
638        //
639        // Note that `FUNCREF_MASK` as type `usize` but here we want a 64-bit
640        // value so assert its actual value and then use a `-2` literal.
641        self.masm.bind(defined)?;
642        assert_eq!(FUNCREF_MASK as isize, -2);
643        let imm = RegImm::i64(-2);
644        let dst = top.into();
645        self.masm
646            .and(writable!(dst), dst, imm, top.ty.try_into()?)?;
647
648        self.masm.bind(cont)
649    }
650
651    /// Emits a series of instructions to bounds check and calculate the address
652    /// of the given WebAssembly memory.
653    /// This function returns a register containing the requested address.
654    ///
655    /// In essence, when computing the heap address for a WebAssembly load or
656    /// store instruction the objective is to ensure that such access is safe,
657    /// but also to perform the least amount of checks, and rely on the system to
658    /// detect illegal memory accesses where applicable.
659    ///
660    /// Winch follows almost the same principles as Cranelift when it comes to
661    /// bounds checks, for a more detailed explanation refer to
662    /// prepare_addr in wasmtime-cranelift.
663    ///
664    /// Winch implementation differs in that, it defaults to the general case
665    /// for dynamic heaps rather than optimizing for doing the least amount of
666    /// work possible at runtime, this is done to align with Winch's principle
667    /// of doing the least amount of work possible at compile time. For static
668    /// heaps, Winch does a bit more of work, given that some of the cases that
669    /// are checked against, can benefit compilation times, like for example,
670    /// detecting an out of bounds access at compile time.
671    pub fn emit_compute_heap_address(
672        &mut self,
673        memarg: &MemArg,
674        access_size: OperandSize,
675    ) -> Result<Option<Reg>> {
676        let ptr_size: OperandSize = self.env.ptr_type().try_into()?;
677        let enable_spectre_mitigation = self.env.heap_access_spectre_mitigation();
678        let add_offset_and_access_size = |offset: ImmOffset, access_size: OperandSize| {
679            (access_size.bytes() as u64) + (offset.as_u32() as u64)
680        };
681
682        let memory_index = MemoryIndex::from_u32(memarg.memory);
683        let heap = self.env.resolve_heap(memory_index);
684        let index = Index::from_typed_reg(self.context.pop_to_reg(self.masm, None)?);
685        let offset = bounds::ensure_index_and_offset(
686            self.masm,
687            index,
688            memarg.offset,
689            heap.index_type().try_into()?,
690        )?;
691        let offset_with_access_size = add_offset_and_access_size(offset, access_size);
692
693        let can_elide_bounds_check = heap
694            .memory
695            .can_elide_bounds_check(self.tunables, self.env.page_size_log2);
696
697        let addr = if offset_with_access_size > heap.memory.maximum_byte_size().unwrap_or(u64::MAX)
698        {
699            // Detect at compile time if the access is out of bounds.
700            // Doing so will put the compiler in an unreachable code state,
701            // optimizing the work that the compiler has to do until the
702            // reachability is restored or when reaching the end of the
703            // function.
704
705            self.emit_fuel_increment()?;
706            self.masm.trap(TrapCode::HEAP_OUT_OF_BOUNDS)?;
707            self.context.reachable = false;
708            None
709        } else if !can_elide_bounds_check {
710            // Account for the general case for bounds-checked memories. The
711            // access is out of bounds if:
712            // * index + offset + access_size overflows
713            //   OR
714            // * index + offset + access_size > bound
715            let bounds = bounds::load_dynamic_heap_bounds::<_>(
716                &mut self.context,
717                self.masm,
718                &heap,
719                ptr_size,
720            )?;
721
722            let index_reg = index.as_typed_reg().reg;
723            // Allocate a temporary register to hold
724            //      index + offset + access_size
725            //  which will serve as the check condition.
726            let index_offset_and_access_size = self.context.any_gpr(self.masm)?;
727
728            // Move the value of the index to the
729            // index_offset_and_access_size register to perform the overflow
730            // check to avoid clobbering the initial index value.
731            //
732            // We derive size of the operation from the heap type since:
733            //
734            // * This is the first assignment to the
735            // `index_offset_and_access_size` register
736            //
737            // * The memory64 proposal specifies that the index is bound to
738            // the heap type instead of hardcoding it to 32-bits (i32).
739            self.masm.mov(
740                writable!(index_offset_and_access_size),
741                index_reg.into(),
742                heap.index_type().try_into()?,
743            )?;
744            // Perform
745            // index = index + offset + access_size, trapping if the
746            // addition overflows.
747            //
748            // We use the target's pointer size rather than depending on the heap
749            // type since we want to check for overflow; even though the
750            // offset and access size are guaranteed to be bounded by the heap
751            // type, when added, if used with the wrong operand size, their
752            // result could be clamped, resulting in an erroneous overflow
753            // check.
754            self.masm.checked_uadd(
755                writable!(index_offset_and_access_size),
756                index_offset_and_access_size,
757                RegImm::i64(offset_with_access_size as i64),
758                ptr_size,
759                TrapCode::HEAP_OUT_OF_BOUNDS,
760            )?;
761
762            let addr = bounds::load_heap_addr_checked(
763                self.masm,
764                &mut self.context,
765                ptr_size,
766                &heap,
767                enable_spectre_mitigation,
768                bounds,
769                index,
770                offset,
771                |masm, bounds, _| {
772                    let bounds_reg = bounds.as_typed_reg().reg;
773                    masm.cmp(
774                        index_offset_and_access_size,
775                        bounds_reg.into(),
776                        // We use the pointer size to keep the bounds
777                        // comparison consistent with the result of the
778                        // overflow check above.
779                        ptr_size,
780                    )?;
781                    Ok(IntCmpKind::GtU)
782                },
783            )?;
784            self.context.free_reg(bounds.as_typed_reg().reg);
785            self.context.free_reg(index_offset_and_access_size);
786            Some(addr)
787
788        // Account for the case in which we can completely elide the bounds
789        // checks.
790        //
791        // This case, makes use of the fact that if a memory access uses
792        // a 32-bit index, then we be certain that
793        //
794        //      index <= u32::MAX
795        //
796        // Therefore if any 32-bit index access occurs in the region
797        // represented by
798        //
799        //      bound + guard_size - (offset + access_size)
800        //
801        // We are certain that it's in bounds or that the underlying virtual
802        // memory subsystem will report an illegal access at runtime.
803        //
804        // Note:
805        //
806        // * bound - (offset + access_size) cannot wrap, because it's checked
807        // in the condition above.
808        // * bound + heap.offset_guard_size is guaranteed to not overflow if
809        // the heap configuration is correct, given that it's address must
810        // fit in 64-bits.
811        // * If the heap type is 32-bits, the offset is at most u32::MAX, so
812        // no  adjustment is needed as part of
813        // [bounds::ensure_index_and_offset].
814        } else if u64::from(u32::MAX)
815            <= self.tunables.memory_reservation + self.tunables.memory_guard_size
816                - offset_with_access_size
817        {
818            assert!(can_elide_bounds_check);
819            assert!(heap.index_type() == WasmValType::I32);
820            let addr = self.context.any_gpr(self.masm)?;
821            bounds::load_heap_addr_unchecked(self.masm, &heap, index, offset, addr, ptr_size)?;
822            Some(addr)
823
824        // Account for the all remaining cases, aka. The access is out
825        // of bounds if:
826        //
827        // index > bound - (offset + access_size)
828        //
829        // bound - (offset + access_size) cannot wrap, because we already
830        // checked that (offset + access_size) > bound, above.
831        } else {
832            assert!(can_elide_bounds_check);
833            assert!(heap.index_type() == WasmValType::I32);
834            let bounds = Bounds::from_u64(self.tunables.memory_reservation);
835            let addr = bounds::load_heap_addr_checked(
836                self.masm,
837                &mut self.context,
838                ptr_size,
839                &heap,
840                enable_spectre_mitigation,
841                bounds,
842                index,
843                offset,
844                |masm, bounds, index| {
845                    let adjusted_bounds = bounds.as_u64() - offset_with_access_size;
846                    let index_reg = index.as_typed_reg().reg;
847                    masm.cmp(
848                        index_reg,
849                        RegImm::i64(adjusted_bounds as i64),
850                        // Similar to the dynamic heap case, even though the
851                        // offset and access size are bound through the heap
852                        // type, when added they can overflow, resulting in
853                        // an erroneous comparison, therfore we rely on the
854                        // target pointer size.
855                        ptr_size,
856                    )?;
857                    Ok(IntCmpKind::GtU)
858                },
859            )?;
860            Some(addr)
861        };
862
863        self.context.free_reg(index.as_typed_reg().reg);
864        Ok(addr)
865    }
866
867    /// Emit checks to ensure that the address at `memarg` is correctly aligned for `size`.
868    fn emit_check_align(&mut self, memarg: &MemArg, size: OperandSize) -> Result<()> {
869        if size.bytes() > 1 {
870            // Peek addr from top of the stack by popping and pushing.
871            let addr = *self
872                .context
873                .stack
874                .peek()
875                .ok_or_else(|| CodeGenError::missing_values_in_stack())?;
876            let tmp = self.context.any_gpr(self.masm)?;
877            self.context.move_val_to_reg(&addr, tmp, self.masm)?;
878
879            if memarg.offset != 0 {
880                self.masm.add(
881                    writable!(tmp),
882                    tmp,
883                    RegImm::Imm(Imm::I64(memarg.offset)),
884                    size,
885                )?;
886            }
887
888            self.masm.and(
889                writable!(tmp),
890                tmp,
891                RegImm::Imm(Imm::I32(size.bytes() - 1)),
892                size,
893            )?;
894
895            self.masm.cmp(tmp, RegImm::Imm(Imm::i64(0)), size)?;
896            self.masm.trapif(IntCmpKind::Ne, TRAP_HEAP_MISALIGNED)?;
897            self.context.free_reg(tmp);
898        }
899
900        Ok(())
901    }
902
903    pub fn emit_compute_heap_address_align_checked(
904        &mut self,
905        memarg: &MemArg,
906        access_size: OperandSize,
907    ) -> Result<Option<Reg>> {
908        self.emit_check_align(memarg, access_size)?;
909        self.emit_compute_heap_address(memarg, access_size)
910    }
911
912    /// Emit a WebAssembly load.
913    pub fn emit_wasm_load(
914        &mut self,
915        arg: &MemArg,
916        target_type: WasmValType,
917        kind: LoadKind,
918    ) -> Result<()> {
919        let emit_load = |this: &mut Self, dst, addr, kind| -> Result<()> {
920            let src = this.masm.address_at_reg(addr, 0)?;
921            this.masm.wasm_load(src, writable!(dst), kind)?;
922            this.context
923                .stack
924                .push(TypedReg::new(target_type, dst).into());
925            this.context.free_reg(addr);
926            Ok(())
927        };
928
929        // Ensure that the destination register is not allocated if
930        // `emit_compute_heap_address` does not return an address.
931        match kind {
932            LoadKind::VectorLane(_) => {
933                // Destination vector register is at the top of the stack and
934                // `emit_compute_heap_address` expects an integer register
935                // containing the address to load to be at the top of the stack.
936                let dst = self.context.pop_to_reg(self.masm, None)?;
937                let addr = self.emit_compute_heap_address(&arg, kind.derive_operand_size())?;
938                if let Some(addr) = addr {
939                    emit_load(self, dst.reg, addr, kind)?;
940                } else {
941                    self.context.free_reg(dst);
942                }
943            }
944            _ => {
945                let maybe_addr = match kind {
946                    LoadKind::Atomic(_, _) => self.emit_compute_heap_address_align_checked(
947                        &arg,
948                        kind.derive_operand_size(),
949                    )?,
950                    _ => self.emit_compute_heap_address(&arg, kind.derive_operand_size())?,
951                };
952
953                if let Some(addr) = maybe_addr {
954                    let dst = match target_type {
955                        WasmValType::I32 | WasmValType::I64 => self.context.any_gpr(self.masm)?,
956                        WasmValType::F32 | WasmValType::F64 => self.context.any_fpr(self.masm)?,
957                        WasmValType::V128 => self.context.reg_for_type(target_type, self.masm)?,
958                        _ => bail!(CodeGenError::unsupported_wasm_type()),
959                    };
960
961                    emit_load(self, dst, addr, kind)?;
962                }
963            }
964        }
965
966        Ok(())
967    }
968
969    /// Emit a WebAssembly store.
970    pub fn emit_wasm_store(&mut self, arg: &MemArg, kind: StoreKind) -> Result<()> {
971        let src = self.context.pop_to_reg(self.masm, None)?;
972
973        let maybe_addr = match kind {
974            StoreKind::Atomic(size) => self.emit_compute_heap_address_align_checked(&arg, size)?,
975            StoreKind::Operand(size) | StoreKind::VectorLane(LaneSelector { size, .. }) => {
976                self.emit_compute_heap_address(&arg, size)?
977            }
978        };
979
980        if let Some(addr) = maybe_addr {
981            self.masm
982                .wasm_store(src.reg, self.masm.address_at_reg(addr, 0)?, kind)?;
983
984            self.context.free_reg(addr);
985        }
986        self.context.free_reg(src);
987
988        Ok(())
989    }
990
991    /// Loads the address of the table element at a given index. Returns the
992    /// address of the table element using the provided register as base.
993    pub fn emit_compute_table_elem_addr(
994        &mut self,
995        index: Reg,
996        base: Reg,
997        table_data: &TableData,
998    ) -> Result<M::Address> {
999        let bound = self.context.any_gpr(self.masm)?;
1000        let tmp = self.context.any_gpr(self.masm)?;
1001        let ptr_size: OperandSize = self.env.ptr_type().try_into()?;
1002
1003        if let Some(offset) = table_data.import_from {
1004            // If the table data declares a particular offset base,
1005            // load the address into a register to further use it as
1006            // the table address.
1007            self.masm
1008                .load_ptr(self.masm.address_at_vmctx(offset)?, writable!(base))?;
1009        } else {
1010            // Else, simply move the vmctx register into the addr register as
1011            // the base to calculate the table address.
1012            self.masm.mov(writable!(base), vmctx!(M).into(), ptr_size)?;
1013        };
1014
1015        // OOB check.
1016        let bound_addr = self
1017            .masm
1018            .address_at_reg(base, table_data.current_elems_offset)?;
1019        let bound_size = table_data.current_elements_size;
1020        self.masm.load(bound_addr, writable!(bound), bound_size)?;
1021        self.masm.cmp(index, bound.into(), bound_size)?;
1022        self.masm
1023            .trapif(IntCmpKind::GeU, TRAP_TABLE_OUT_OF_BOUNDS)?;
1024
1025        // Move the index into the scratch register to calculate the table
1026        // element address.
1027        // Moving the value of the index register to the scratch register
1028        // also avoids overwriting the context of the index register.
1029        self.masm.with_scratch::<IntScratch, _>(|masm, scratch| {
1030            masm.mov(scratch.writable(), index.into(), bound_size)?;
1031            masm.mul(
1032                scratch.writable(),
1033                scratch.inner(),
1034                RegImm::i32(table_data.element_size.bytes() as i32),
1035                table_data.element_size,
1036            )?;
1037            masm.load_ptr(
1038                masm.address_at_reg(base, table_data.offset)?,
1039                writable!(base),
1040            )?;
1041            // Copy the value of the table base into a temporary register
1042            // so that we can use it later in case of a misspeculation.
1043            masm.mov(writable!(tmp), base.into(), ptr_size)?;
1044            // Calculate the address of the table element.
1045            masm.add(writable!(base), base, scratch.inner().into(), ptr_size)
1046        })?;
1047        if self.env.table_access_spectre_mitigation() {
1048            // Perform a bounds check and override the value of the
1049            // table element address in case the index is out of bounds.
1050            self.masm.cmp(index, bound.into(), OperandSize::S32)?;
1051            self.masm
1052                .cmov(writable!(base), tmp, IntCmpKind::GeU, ptr_size)?;
1053        }
1054        self.context.free_reg(bound);
1055        self.context.free_reg(tmp);
1056        self.masm.address_at_reg(base, 0)
1057    }
1058
1059    /// Retrieves the size of the table, pushing the result to the value stack.
1060    pub fn emit_compute_table_size(&mut self, table_data: &TableData) -> Result<()> {
1061        let size = self.context.any_gpr(self.masm)?;
1062        let ptr_size: OperandSize = self.env.ptr_type().try_into()?;
1063
1064        self.masm.with_scratch::<IntScratch, _>(|masm, scratch| {
1065            if let Some(offset) = table_data.import_from {
1066                masm.load_ptr(masm.address_at_vmctx(offset)?, scratch.writable())?;
1067            } else {
1068                masm.mov(scratch.writable(), vmctx!(M).into(), ptr_size)?;
1069            };
1070
1071            let size_addr =
1072                masm.address_at_reg(scratch.inner(), table_data.current_elems_offset)?;
1073            masm.load(size_addr, writable!(size), table_data.current_elements_size)
1074        })?;
1075
1076        self.context.stack.push(TypedReg::i32(size).into());
1077        Ok(())
1078    }
1079
1080    /// Retrieves the size of the memory, pushing the result to the value stack.
1081    pub fn emit_compute_memory_size(&mut self, heap_data: &HeapData) -> Result<()> {
1082        let size_reg = self.context.any_gpr(self.masm)?;
1083
1084        self.masm.with_scratch::<IntScratch, _>(|masm, scratch| {
1085            let base = if let Some(offset) = heap_data.import_from {
1086                masm.load_ptr(masm.address_at_vmctx(offset)?, scratch.writable())?;
1087                scratch.inner()
1088            } else {
1089                vmctx!(M)
1090            };
1091
1092            let size_addr = masm.address_at_reg(base, heap_data.current_length_offset)?;
1093            masm.load_ptr(size_addr, writable!(size_reg))
1094        })?;
1095        // Emit a shift to get the size in pages rather than in bytes.
1096        let dst = TypedReg::new(heap_data.index_type(), size_reg);
1097        let pow = heap_data.memory.page_size_log2;
1098        self.masm.shift_ir(
1099            writable!(dst.reg),
1100            Imm::i32(pow as i32),
1101            dst.into(),
1102            ShiftKind::ShrU,
1103            heap_data.index_type().try_into()?,
1104        )?;
1105        self.context.stack.push(dst.into());
1106        Ok(())
1107    }
1108
1109    /// Checks if fuel consumption is enabled and emits a series of instructions
1110    /// that check the current fuel usage by performing a zero-comparison with
1111    /// the number of units stored in `VMStoreContext`.
1112    pub fn maybe_emit_fuel_check(&mut self) -> Result<()> {
1113        if !self.tunables.consume_fuel {
1114            return Ok(());
1115        }
1116
1117        self.emit_fuel_increment()?;
1118        let out_of_fuel = self.env.builtins.out_of_gas::<M::ABI, M::Ptr>()?;
1119        let fuel_reg = self.context.without::<Result<Reg>, M, _>(
1120            &out_of_fuel.sig().regs,
1121            self.masm,
1122            |cx, masm| cx.any_gpr(masm),
1123        )??;
1124
1125        self.emit_load_fuel_consumed(fuel_reg)?;
1126
1127        // The  continuation label if the current fuel is under the limit.
1128        let continuation = self.masm.get_label()?;
1129
1130        // Spill locals and registers to avoid conflicts at the out-of-fuel
1131        // control flow merge.
1132        self.context.spill(self.masm)?;
1133        // Fuel is stored as a negative i64, so if the number is less than zero,
1134        // we're still under the fuel limits.
1135        self.masm.branch(
1136            IntCmpKind::LtS,
1137            fuel_reg,
1138            RegImm::i64(0),
1139            continuation,
1140            OperandSize::S64,
1141        )?;
1142        // Out-of-fuel branch.
1143        FnCall::emit::<M>(
1144            &mut self.env,
1145            self.masm,
1146            &mut self.context,
1147            Callee::Builtin(out_of_fuel.clone()),
1148        )?;
1149        self.context.pop_and_free(self.masm)?;
1150
1151        // Under fuel limits branch.
1152        self.masm.bind(continuation)?;
1153        self.context.free_reg(fuel_reg);
1154
1155        Ok(())
1156    }
1157
1158    /// Emits a series of instructions that load the `fuel_consumed` field from
1159    /// `VMStoreContext`.
1160    fn emit_load_fuel_consumed(&mut self, fuel_reg: Reg) -> Result<()> {
1161        let store_context_offset = self.env.vmoffsets.ptr.vmctx_store_context();
1162        let fuel_offset = self.env.vmoffsets.ptr.vmstore_context_fuel_consumed();
1163        self.masm.load_ptr(
1164            self.masm
1165                .address_at_vmctx(u32::from(store_context_offset))?,
1166            writable!(fuel_reg),
1167        )?;
1168
1169        self.masm.load(
1170            self.masm.address_at_reg(fuel_reg, u32::from(fuel_offset))?,
1171            writable!(fuel_reg),
1172            // Fuel is an i64.
1173            OperandSize::S64,
1174        )
1175    }
1176
1177    /// Checks if epoch interruption is configured and emits a series of
1178    /// instructions that check the current epoch against its deadline.
1179    pub fn maybe_emit_epoch_check(&mut self) -> Result<()> {
1180        if !self.tunables.epoch_interruption {
1181            return Ok(());
1182        }
1183
1184        // The continuation branch if the current epoch hasn't reached the
1185        // configured deadline.
1186        let cont = self.masm.get_label()?;
1187        let new_epoch = self.env.builtins.new_epoch::<M::ABI, M::Ptr>()?;
1188
1189        // Checks for runtime limits (e.g., fuel, epoch) are special since they
1190        // require inserting arbitrary function calls and control flow.
1191        // Special care must be taken to ensure that all invariants are met. In
1192        // this case, since `new_epoch` takes an argument and returns a value,
1193        // we must ensure that any registers used to hold the current epoch
1194        // value and deadline are not going to be needed later on by the
1195        // function call.
1196        let (epoch_deadline_reg, epoch_counter_reg) =
1197            self.context.without::<Result<(Reg, Reg)>, M, _>(
1198                &new_epoch.sig().regs,
1199                self.masm,
1200                |cx, masm| Ok((cx.any_gpr(masm)?, cx.any_gpr(masm)?)),
1201            )??;
1202
1203        self.emit_load_epoch_deadline_and_counter(epoch_deadline_reg, epoch_counter_reg)?;
1204
1205        // Spill locals and registers to avoid conflicts at the control flow
1206        // merge below.
1207        self.context.spill(self.masm)?;
1208        self.masm.branch(
1209            IntCmpKind::LtU,
1210            epoch_counter_reg,
1211            RegImm::reg(epoch_deadline_reg),
1212            cont,
1213            OperandSize::S64,
1214        )?;
1215        // Epoch deadline reached branch.
1216        FnCall::emit::<M>(
1217            &mut self.env,
1218            self.masm,
1219            &mut self.context,
1220            Callee::Builtin(new_epoch.clone()),
1221        )?;
1222        // `new_epoch` returns the new deadline. However we don't
1223        // perform any caching, so we simply drop this value.
1224        self.visit_drop()?;
1225
1226        // Under epoch deadline branch.
1227        self.masm.bind(cont)?;
1228
1229        self.context.free_reg(epoch_deadline_reg);
1230        self.context.free_reg(epoch_counter_reg);
1231        Ok(())
1232    }
1233
1234    fn emit_load_epoch_deadline_and_counter(
1235        &mut self,
1236        epoch_deadline_reg: Reg,
1237        epoch_counter_reg: Reg,
1238    ) -> Result<()> {
1239        let epoch_ptr_offset = self.env.vmoffsets.ptr.vmctx_epoch_ptr();
1240        let store_context_offset = self.env.vmoffsets.ptr.vmctx_store_context();
1241        let epoch_deadline_offset = self.env.vmoffsets.ptr.vmstore_context_epoch_deadline();
1242
1243        // Load the current epoch value into `epoch_counter_var`.
1244        self.masm.load_ptr(
1245            self.masm.address_at_vmctx(u32::from(epoch_ptr_offset))?,
1246            writable!(epoch_counter_reg),
1247        )?;
1248
1249        // `epoch_deadline_var` contains the address of the value, so we need
1250        // to extract it.
1251        self.masm.load(
1252            self.masm.address_at_reg(epoch_counter_reg, 0)?,
1253            writable!(epoch_counter_reg),
1254            OperandSize::S64,
1255        )?;
1256
1257        // Load the `VMStoreContext`.
1258        self.masm.load_ptr(
1259            self.masm
1260                .address_at_vmctx(u32::from(store_context_offset))?,
1261            writable!(epoch_deadline_reg),
1262        )?;
1263
1264        self.masm.load(
1265            self.masm
1266                .address_at_reg(epoch_deadline_reg, u32::from(epoch_deadline_offset))?,
1267            writable!(epoch_deadline_reg),
1268            // The deadline value is a u64.
1269            OperandSize::S64,
1270        )
1271    }
1272
1273    /// Increments the fuel consumed in `VMStoreContext` by flushing
1274    /// `self.fuel_consumed` to memory.
1275    fn emit_fuel_increment(&mut self) -> Result<()> {
1276        let fuel_at_point = std::mem::replace(&mut self.fuel_consumed, 0);
1277        if fuel_at_point == 0 {
1278            return Ok(());
1279        }
1280
1281        let store_context_offset = self.env.vmoffsets.ptr.vmctx_store_context();
1282        let fuel_offset = self.env.vmoffsets.ptr.vmstore_context_fuel_consumed();
1283        let limits_reg = self.context.any_gpr(self.masm)?;
1284
1285        // Load `VMStoreContext` into the `limits_reg` reg.
1286        self.masm.load_ptr(
1287            self.masm
1288                .address_at_vmctx(u32::from(store_context_offset))?,
1289            writable!(limits_reg),
1290        )?;
1291
1292        self.masm.with_scratch::<IntScratch, _>(|masm, scratch| {
1293            // Load the fuel consumed at point into the scratch register.
1294            masm.load(
1295                masm.address_at_reg(limits_reg, u32::from(fuel_offset))?,
1296                scratch.writable(),
1297                OperandSize::S64,
1298            )?;
1299
1300            // Add the fuel consumed at point with the value in the scratch
1301            // register.
1302            masm.add(
1303                scratch.writable(),
1304                scratch.inner(),
1305                RegImm::i64(fuel_at_point),
1306                OperandSize::S64,
1307            )?;
1308
1309            // Store the updated fuel consumed to `VMStoreContext`.
1310            masm.store(
1311                scratch.inner().into(),
1312                masm.address_at_reg(limits_reg, u32::from(fuel_offset))?,
1313                OperandSize::S64,
1314            )
1315        })?;
1316
1317        self.context.free_reg(limits_reg);
1318
1319        Ok(())
1320    }
1321
1322    /// Hook to handle fuel before visiting an operator.
1323    fn fuel_before_visit_op(&mut self, op: &Operator) -> Result<()> {
1324        if !self.context.reachable {
1325            // `self.fuel_consumed` must be correctly flushed to memory when
1326            // entering an unreachable state.
1327            ensure!(self.fuel_consumed == 0, CodeGenError::illegal_fuel_state())
1328        }
1329
1330        // Generally, most instructions require 1 fuel unit.
1331        //
1332        // However, there are exceptions, which are detailed in the code below.
1333        // Note that the fuel accounting semantics align with those of
1334        // Cranelift; for further information, refer to
1335        // `crates/cranelift/src/func_environ.rs`.
1336        //
1337        // The primary distinction between the two implementations is that Winch
1338        // does not utilize a local-based cache to track fuel consumption.
1339        // Instead, each increase in fuel necessitates loading from and storing
1340        // to memory.
1341        //
1342        // Memory traffic will undoubtedly impact runtime performance. One
1343        // potential optimization is to designate a register as non-allocatable,
1344        // when fuel consumption is enabled, effectively using it as a local
1345        // fuel cache.
1346        self.fuel_consumed += match op {
1347            Operator::Nop | Operator::Drop => 0,
1348            Operator::Block { .. }
1349            | Operator::Loop { .. }
1350            | Operator::Unreachable
1351            | Operator::Return
1352            | Operator::Else
1353            | Operator::End => 0,
1354            _ => 1,
1355        };
1356
1357        match op {
1358            Operator::Unreachable
1359            | Operator::Loop { .. }
1360            | Operator::If { .. }
1361            | Operator::Else { .. }
1362            | Operator::Br { .. }
1363            | Operator::BrIf { .. }
1364            | Operator::BrTable { .. }
1365            | Operator::End
1366            | Operator::Return
1367            | Operator::CallIndirect { .. }
1368            | Operator::Call { .. }
1369            | Operator::ReturnCall { .. }
1370            | Operator::ReturnCallIndirect { .. } => self.emit_fuel_increment(),
1371            _ => Ok(()),
1372        }
1373    }
1374
1375    // Hook to handle source location mapping before visiting an operator.
1376    fn source_location_before_visit_op(&mut self, offset: usize) -> Result<()> {
1377        let loc = SourceLoc::new(offset as u32);
1378        let rel = self.source_loc_from(loc);
1379        self.source_location.current = self.masm.start_source_loc(rel)?;
1380        Ok(())
1381    }
1382
1383    // Hook to handle source location mapping after visiting an operator.
1384    fn source_location_after_visit_op(&mut self) -> Result<()> {
1385        // Because in Winch binary emission is done in a single pass
1386        // and because the MachBuffer performs optimizations during
1387        // emission, we have to be careful when calling
1388        // [`MacroAssembler::end_source_location`] to avoid breaking the
1389        // invariant that checks that the end [CodeOffset] must be equal
1390        // or greater than the start [CodeOffset].
1391        if self.masm.current_code_offset()? >= self.source_location.current.0 {
1392            self.masm.end_source_loc()?;
1393        }
1394
1395        Ok(())
1396    }
1397
1398    pub(crate) fn emit_atomic_rmw(
1399        &mut self,
1400        arg: &MemArg,
1401        op: RmwOp,
1402        size: OperandSize,
1403        extend: Option<Extend<Zero>>,
1404    ) -> Result<()> {
1405        // We need to pop-push the operand to compute the address before passing control over to
1406        // masm, because some architectures may have specific requirements for the registers used
1407        // in some atomic operations.
1408        let operand = self.context.pop_to_reg(self.masm, None)?;
1409        if let Some(addr) = self.emit_compute_heap_address_align_checked(arg, size)? {
1410            let src = self.masm.address_at_reg(addr, 0)?;
1411            self.context.stack.push(operand.into());
1412            self.masm
1413                .atomic_rmw(&mut self.context, src, size, op, UNTRUSTED_FLAGS, extend)?;
1414            self.context.free_reg(addr);
1415        }
1416
1417        Ok(())
1418    }
1419
1420    pub(crate) fn emit_atomic_cmpxchg(
1421        &mut self,
1422        arg: &MemArg,
1423        size: OperandSize,
1424        extend: Option<Extend<Zero>>,
1425    ) -> Result<()> {
1426        // Emission for this instruction is a bit trickier. The address for the CAS is the 3rd from
1427        // the top of the stack, and we must emit instruction to compute the actual address with
1428        // `emit_compute_heap_address_align_checked`, while we still have access to self. However,
1429        // some ISAs have requirements with regard to the registers used for some arguments, so we
1430        // need to pass the context to the masm. To solve this issue, we pop the two first
1431        // arguments from the stack, compute the address, push back the arguments, and hand over
1432        // the control to masm. The implementer of `atomic_cas` can expect to find `expected` and
1433        // `replacement` at the top the context's stack.
1434
1435        // pop the args
1436        let replacement = self.context.pop_to_reg(self.masm, None)?;
1437        let expected = self.context.pop_to_reg(self.masm, None)?;
1438
1439        if let Some(addr) = self.emit_compute_heap_address_align_checked(arg, size)? {
1440            // push back the args
1441            self.context.stack.push(expected.into());
1442            self.context.stack.push(replacement.into());
1443
1444            let src = self.masm.address_at_reg(addr, 0)?;
1445            self.masm
1446                .atomic_cas(&mut self.context, src, size, UNTRUSTED_FLAGS, extend)?;
1447
1448            self.context.free_reg(addr);
1449        }
1450        Ok(())
1451    }
1452
1453    #[cfg(not(feature = "threads"))]
1454    pub fn emit_atomic_wait(&mut self, _arg: &MemArg, _kind: AtomicWaitKind) -> Result<()> {
1455        Err(CodeGenError::unimplemented_wasm_instruction().into())
1456    }
1457
1458    /// Emit the sequence of instruction for a `memory.atomic.wait*`.
1459    #[cfg(feature = "threads")]
1460    pub fn emit_atomic_wait(&mut self, arg: &MemArg, kind: AtomicWaitKind) -> Result<()> {
1461        // The `memory_atomic_wait*` builtins expect the following arguments:
1462        // - `memory`, as u32
1463        // - `address`, as u64
1464        // - `expected`, as either u64 or u32
1465        // - `timeout`, as u64
1466        // At this point our stack only contains the `timeout`, the `expected` and the address, so
1467        // we need to:
1468        // - insert the memory as the first argument
1469        // - compute the actual memory offset from the `MemArg`, if necessary.
1470        // Note that the builtin function performs the alignment and bounds checks for us, so we
1471        // don't need to emit that.
1472
1473        let timeout = self.context.pop_to_reg(self.masm, None)?;
1474        let expected = self.context.pop_to_reg(self.masm, None)?;
1475        let addr = self.context.pop_to_reg(self.masm, None)?;
1476
1477        // Put the target memory index as the first argument.
1478        self.context
1479            .stack
1480            .push(crate::stack::Val::I32(arg.memory as i32));
1481
1482        if arg.offset != 0 {
1483            self.masm.add(
1484                writable!(addr.reg),
1485                addr.reg,
1486                RegImm::i64(arg.offset as i64),
1487                OperandSize::S64,
1488            )?;
1489        }
1490
1491        self.context
1492            .stack
1493            .push(TypedReg::new(WasmValType::I64, addr.reg).into());
1494        self.context.stack.push(expected.into());
1495        self.context.stack.push(timeout.into());
1496
1497        let builtin = match kind {
1498            AtomicWaitKind::Wait32 => self.env.builtins.memory_atomic_wait32::<M::ABI, M::Ptr>()?,
1499            AtomicWaitKind::Wait64 => self.env.builtins.memory_atomic_wait64::<M::ABI, M::Ptr>()?,
1500        };
1501
1502        FnCall::emit::<M>(
1503            &mut self.env,
1504            self.masm,
1505            &mut self.context,
1506            Callee::Builtin(builtin.clone()),
1507        )?;
1508
1509        Ok(())
1510    }
1511
1512    #[cfg(not(feature = "threads"))]
1513    pub fn emit_atomic_notify(&mut self, _arg: &MemArg) -> Result<()> {
1514        Err(CodeGenError::unimplemented_wasm_instruction().into())
1515    }
1516
1517    #[cfg(feature = "threads")]
1518    pub fn emit_atomic_notify(&mut self, arg: &MemArg) -> Result<()> {
1519        // The memory `memory_atomic_notify` builtin expects the following arguments:
1520        // - `memory`, as u32
1521        // - `address`, as u64
1522        // - `count`: as u32
1523        // At this point our stack only contains the `count` and the `address`, so we need to:
1524        // - insert the memory as the first argument
1525        // - compute the actual memory offset from the `MemArg`, if necessary.
1526        // Note that the builtin function performs the alignment and bounds checks for us, so we
1527        // don't need to emit that.
1528
1529        // pop the arguments from the stack.
1530        let count = self.context.pop_to_reg(self.masm, None)?;
1531        let addr = self.context.pop_to_reg(self.masm, None)?;
1532
1533        // Put the target memory index as the first argument.
1534        self.context
1535            .stack
1536            .push(crate::stack::Val::I32(arg.memory as i32));
1537
1538        if arg.offset != 0 {
1539            self.masm.add(
1540                writable!(addr.reg),
1541                addr.reg,
1542                RegImm::i64(arg.offset as i64),
1543                OperandSize::S64,
1544            )?;
1545        }
1546
1547        // push remaining arguments.
1548        self.context
1549            .stack
1550            .push(TypedReg::new(WasmValType::I64, addr.reg).into());
1551        self.context.stack.push(count.into());
1552
1553        let builtin = self.env.builtins.memory_atomic_notify::<M::ABI, M::Ptr>()?;
1554
1555        FnCall::emit::<M>(
1556            &mut self.env,
1557            self.masm,
1558            &mut self.context,
1559            Callee::Builtin(builtin.clone()),
1560        )?;
1561
1562        Ok(())
1563    }
1564}
1565
1566/// Returns the index of the [`ControlStackFrame`] for the given
1567/// depth.
1568pub fn control_index(depth: u32, control_length: usize) -> Result<usize> {
1569    (control_length - 1)
1570        .checked_sub(depth as usize)
1571        .ok_or_else(|| anyhow!(CodeGenError::control_frame_expected()))
1572}