Skip to main content

qcode_jit/
compile.rs

1//! Translating one QCode block into a native function.
2//!
3//! # What this compiles, and what it declines
4//!
5//! A QCode block is already SSA, so the translation to Cranelift's SSA is
6//! direct: an instruction's result becomes a Cranelift value, and a value used
7//! only inside the block never reaches memory at all. That is the point of
8//! compiling. The interpreter has to materialise every intermediate into its
9//! value table because it cannot see past one operation at a time; compiled
10//! code keeps them in registers.
11//!
12//! The compiler is deliberately partial. It handles integer arithmetic and
13//! accesses to *flat* spaces — registers, uniques, per-function temporaries —
14//! whose addresses are constants known at compile time, so a register access
15//! becomes a load at a fixed offset from a base pointer. Anything else
16//! ([`Unsupported`]) is declined, and the caller runs that block on the
17//! interpreter instead. Declining is a normal outcome, not a failure: it is what
18//! lets this be an alternative strategy rather than a replacement.
19//!
20//! Guest RAM *is* handled here, but not by addressing it: an access to it owes
21//! a translation, a permission check and a fault report, and those belong to
22//! the MMU. What is inlined is the MMU's *answer* — a software TLB entry giving
23//! the host address of a resident guest page, and the permission bytes sitting
24//! beside that page's data. An access that the inlined form cannot settle (a
25//! page not yet cached, one straddling a boundary, a permission it refuses)
26//! calls back into the VM, which is the only implementation of what an access
27//! means. See [`qcode_vm::jit_abi`].
28//!
29//! A faulting access stops the block where it happened and hands the fault back
30//! through the function's status result. The stores that ran before it stay
31//! applied, which is what the interpreter would have left behind too — so guest
32//! RAM and the flat spaces are deliberately compiled *without* alias regions,
33//! keeping Cranelift from reordering one store past another and making that
34//! prefix something other than a prefix.
35//!
36//! # Terminators
37//!
38//! Control flow itself stays with the interpreter: it runs the terminator after
39//! compiled code has run the body, so branch resolution, block parameters and
40//! call semantics live in exactly one implementation. What the compiler has to
41//! supply is the terminator's *operands* — a `cbranch` condition, a branch's
42//! block arguments — because those are values the body computed and compiled
43//! code keeps in registers, where the interpreter cannot see them.
44//!
45//! So a block's compiled function takes a second argument: an *export buffer*.
46//! Every terminator operand defined in this block is written there as a `u64`,
47//! and the runtime copies it into the interpreter's value table before handing
48//! the terminator back. A terminator whose operands are all literals, addresses
49//! or values from earlier blocks exports nothing and costs nothing — which is
50//! the argument-less unconditional branch that straight-line guest code lifts
51//! to.
52//!
53//! # Escaping values
54//!
55//! The same reasoning applies to any value that outlives the block, not just
56//! the ones the terminator reads. Values crossing a block boundary as bare SSA
57//! references do not arise in SLEIGH-lifted code — guest state travels through
58//! registers and uniques, which are memory — but nothing in QCode forbids them,
59//! and dropping one would be a silent miscompile rather than a decline. So a
60//! block with a result used from outside it is declined outright.
61
62use cranelift::codegen::ir::BlockArg;
63use cranelift::prelude::*;
64use cranelift_module::FuncId;
65use qcode::{
66    context::Context,
67    space::MemorySpaceId,
68    value::{
69        BasicBlock, BlockId, ValueId, ValueRef,
70        insn::{
71            Binary, Binop, Carry, InstructionId, IntBinop, Load, Mnemonic, PopCount, Range,
72            SBorrow, SCarry, Sext, Store, Unary, Unop, Zext,
73        },
74    },
75};
76use qcode_vm::{PAGE_PERM_OFFSET, PAGE_SIZE, TLB_ENTRIES, TlbEntry, perm};
77use rustc_hash::{FxHashMap, FxHashSet};
78
79/// Status a compiled block returns: it ran to the end of its body.
80pub const BLOCK_OK: i64 = 0;
81/// Status a compiled block returns: an access faulted and the block stopped
82/// there. The fault itself is on the VM's memory.
83pub const BLOCK_FAULT: i64 = 1;
84
85/// Which side of memory an access is on, and what it therefore owes.
86#[derive(Clone, Copy)]
87enum Access {
88    Load,
89    Store,
90}
91
92impl Access {
93    /// The permission bits every byte of the access must already have.
94    ///
95    /// [`perm::INIT`] is absent from the read set on purpose. Requiring it
96    /// would be wrong whenever `check_uninit` is off — an uninitialized read is
97    /// then perfectly legal — and the MMU refuses to cache a translation at all
98    /// while it is on, so the inline path never runs under that rule.
99    fn required(self) -> u8 {
100        match self {
101            Self::Load => perm::MAP | perm::READ,
102            Self::Store => perm::MAP | perm::WRITE,
103        }
104    }
105}
106
107/// Why a block could not be compiled.
108///
109/// Carried as a value because declining is expected: the caller falls back to
110/// the interpreter, and the reason is useful for reporting coverage.
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub enum Unsupported {
113    /// A mnemonic the compiler does not translate.
114    Mnemonic(&'static str),
115    /// An operand or result whose width is not a machine integer width.
116    Width(usize),
117    /// A memory access this backend will not perform directly.
118    Access(&'static str),
119    /// The block's terminator is not one the compiler leaves to the caller.
120    Terminator(&'static str),
121    /// An operand whose value the compiler cannot produce.
122    Operand(&'static str),
123    /// A value defined in this block and read from outside it.
124    Escapes(&'static str),
125}
126
127impl std::fmt::Display for Unsupported {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        match self {
130            Self::Mnemonic(what) => write!(f, "unsupported mnemonic `{what}`"),
131            Self::Width(size) => write!(f, "unsupported operand width {size}"),
132            Self::Access(what) => write!(f, "unsupported memory access: {what}"),
133            Self::Terminator(what) => write!(f, "unsupported terminator `{what}`"),
134            Self::Operand(what) => write!(f, "unsupported operand: {what}"),
135            Self::Escapes(what) => write!(f, "value escapes the block: {what}"),
136        }
137    }
138}
139
140/// Which of the four integer divisions is being compiled.
141#[derive(Clone, Copy, PartialEq, Eq)]
142enum Division {
143    Unsigned,
144    UnsignedRem,
145    Signed,
146    SignedRem,
147}
148
149impl Division {
150    fn is_signed(self) -> bool {
151        matches!(self, Self::Signed | Self::SignedRem)
152    }
153
154    /// Index of this operation's runtime helper, for the widths the machine
155    /// cannot divide itself.
156    fn helper(self) -> usize {
157        match self {
158            Self::Unsigned => 0,
159            Self::UnsignedRem => 1,
160            Self::Signed => 2,
161            Self::SignedRem => 3,
162        }
163    }
164}
165
166/// Whether an over-wide shift leaves zero or the sign.
167#[derive(Clone, Copy)]
168enum ShiftKind {
169    Logical,
170    Arithmetic,
171}
172
173/// The machine integer type for a `size`-byte value.
174///
175/// 16 bytes is here because x86 integer code produces it constantly without any
176/// 128-bit types being in sight: SLEIGH lifts a 64-bit `imul` as a widening
177/// multiply into a 128-bit temporary, then slices the halves back out. Declining
178/// that width left the multiply *and every block containing one* to the
179/// interpreter, which was 99.9% of the interpreted block entries on the
180/// benchmarks that lagged.
181pub(crate) fn int_type(size: usize) -> Result<Type, Unsupported> {
182    match size {
183        1 => Ok(types::I8),
184        2 => Ok(types::I16),
185        4 => Ok(types::I32),
186        8 => Ok(types::I64),
187        16 => Ok(types::I128),
188        other => Err(Unsupported::Width(other)),
189    }
190}
191
192/// The flat spaces a compiled block touches, and how many bytes of each it must
193/// be able to address.
194///
195/// Collected while compiling so the runtime can grow each space *before* taking
196/// a base pointer: growing reallocates, and compiled code holds the pointer for
197/// the length of the block.
198#[derive(Debug, Default, Clone)]
199pub struct SpaceTable {
200    entries: Vec<(MemorySpaceId, usize)>,
201}
202
203impl SpaceTable {
204    /// The index compiled code uses to find this space's base pointer, adding
205    /// it to the table if it is new and widening its required size.
206    fn slot(&mut self, space: MemorySpaceId, required: usize) -> usize {
207        if let Some(index) = self.entries.iter().position(|(id, _)| *id == space) {
208            self.entries[index].1 = self.entries[index].1.max(required);
209            return index;
210        }
211        self.entries.push((space, required));
212        self.entries.len() - 1
213    }
214
215    pub fn entries(&self) -> &[(MemorySpaceId, usize)] {
216        &self.entries
217    }
218
219    pub fn len(&self) -> usize {
220        self.entries.len()
221    }
222
223    pub fn is_empty(&self) -> bool {
224        self.entries.is_empty()
225    }
226}
227
228/// Translates the body of one block into an already-open Cranelift function.
229pub(crate) struct BlockTranslator<'a, 'ctx> {
230    ctx: &'ctx Context<'ctx>,
231    builder: FunctionBuilder<'a>,
232    /// Base of the array of space base pointers, the compiled function's first
233    /// argument.
234    spaces_arg: Value,
235    /// Base of the export buffer, the compiled function's second argument: one
236    /// `u64` slot per entry of [`Self::exports`].
237    exports_arg: Value,
238    /// Base of the software TLB, the third argument.
239    tlb_arg: Value,
240    /// The `VmMemory` the fallback helpers act on, the fourth argument.
241    memory_arg: Value,
242    /// The runtime's slow-path load and store, already referenced in this
243    /// function.
244    helpers: HelperRefs,
245    /// The block that abandons the run and reports a fault, created on the
246    /// first access that could take one.
247    fault_block: Option<cranelift::prelude::Block>,
248    /// Where the slow-path load leaves its result. One slot serves every
249    /// access in the block: only one call is live at a time.
250    load_slot: Option<codegen::ir::StackSlot>,
251    /// Cached base pointer per space slot, loaded once per block rather than per
252    /// access.
253    bases: FxHashMap<usize, Value>,
254    /// Values produced by instructions in this block, and imports already
255    /// loaded.
256    values: FxHashMap<InstructionId, Value>,
257    /// This block's own instructions. A result read from outside them is an
258    /// import: computed earlier — by the interpreter, or by compiled code
259    /// that exported it — and waiting in the value buffer.
260    own: FxHashSet<InstructionId>,
261    /// Results of this code that instructions outside it read, and so must be
262    /// written to the value buffer when it returns.
263    escaping: Vec<InstructionId>,
264    pub(crate) table: SpaceTable,
265    /// Values read from the buffer on entry, in slot order. They take the
266    /// first slots; exports follow.
267    pub(crate) imports: Vec<Export>,
268    /// Values the rest of the block reads, in slot order after the imports.
269    pub(crate) exports: Vec<Export>,
270}
271
272/// The runtime entry points compiled code calls when an access cannot be
273/// settled inline, as declared in the module.
274#[derive(Debug, Clone, Copy)]
275pub struct Helpers {
276    pub load: FuncId,
277    pub store: FuncId,
278    /// The 128-bit divisions, in `Division` order.
279    pub divisions: [FuncId; 4],
280}
281
282/// The same pair, resolved against one function being built.
283#[derive(Debug, Clone, Copy)]
284pub(crate) struct HelperRefs {
285    pub(crate) load: codegen::ir::FuncRef,
286    pub(crate) store: codegen::ir::FuncRef,
287    pub(crate) divisions: [codegen::ir::FuncRef; 4],
288}
289
290/// One value compiled code hands back for the interpreter to read.
291#[derive(Debug, Clone, Copy)]
292pub struct Export {
293    /// The instruction whose result this is; the key it is filed under in the
294    /// interpreter's value table.
295    pub insn: InstructionId,
296    /// Its declared width in bytes, which the slot's low bytes hold.
297    pub size: usize,
298}
299
300impl<'a, 'ctx> BlockTranslator<'a, 'ctx> {
301    pub(crate) fn new(
302        ctx: &'ctx Context<'ctx>,
303        builder: FunctionBuilder<'a>,
304        entry: cranelift::prelude::Block,
305        helpers: HelperRefs,
306    ) -> Self {
307        let spaces_arg = builder.block_params(entry)[0];
308        let exports_arg = builder.block_params(entry)[1];
309        let tlb_arg = builder.block_params(entry)[2];
310        let memory_arg = builder.block_params(entry)[3];
311        Self {
312            ctx,
313            builder,
314            spaces_arg,
315            exports_arg,
316            tlb_arg,
317            memory_arg,
318            helpers,
319            fault_block: None,
320            load_slot: None,
321            bases: FxHashMap::default(),
322            values: FxHashMap::default(),
323            own: FxHashSet::default(),
324            escaping: Vec::new(),
325            table: SpaceTable::default(),
326            imports: Vec::new(),
327            exports: Vec::new(),
328        }
329    }
330
331    /// Loads (once) the base pointer for a space slot.
332    fn base(&mut self, slot: usize) -> Value {
333        if let Some(base) = self.bases.get(&slot) {
334            return *base;
335        }
336        let offset = (slot * std::mem::size_of::<*mut u8>()) as i32;
337        let base =
338            self.builder
339                .ins()
340                .load(types::I64, MemFlags::trusted(), self.spaces_arg, offset);
341        self.bases.insert(slot, base);
342        base
343    }
344
345    /// Whether `space` is one this backend may address directly.
346    ///
347    /// Guest RAM is not, however constant its address: every access to it owes
348    /// a permission check and a fault report, which is the MMU's to give. A
349    /// RIP-relative operand resolves to a perfectly constant address and is
350    /// still RAM — treating one as flat storage silently reads a fabricated,
351    /// zero-filled space instead of the guest's memory. RAM is reached through
352    /// [`Self::inline_access`] instead.
353    fn is_flat(&self, space: MemorySpaceId) -> bool {
354        space != MemorySpaceId::Shared(self.ctx.shared.default_space)
355    }
356
357    /// The address a flat access resolves to, or `None` if it is not a constant.
358    fn constant_address(&self, ptr: ValueId) -> Option<u64> {
359        match ValueRef::new(ptr, self.ctx) {
360            ValueRef::Literal(literal) => Some(literal.value()),
361            ValueRef::Temp(temp) => Some(temp.address() as u64),
362            ValueRef::Varnode(varnode) => Some(varnode.address() as u64),
363            _ => None,
364        }
365    }
366
367    /// Produces the Cranelift value for a QCode operand.
368    fn operand(&mut self, id: ValueId, size: usize) -> Result<Value, Unsupported> {
369        let ty = int_type(size)?;
370        match id {
371            ValueId::Literal(_) => {
372                let ValueRef::Literal(literal) = ValueRef::new(id, self.ctx) else {
373                    return Err(Unsupported::Operand("literal did not resolve"));
374                };
375                // A QCode literal is at most 64 bits wide, so widening one to
376                // a 16-byte operand loses nothing.
377                Ok(self.constant(ty, u128::from(literal.value())))
378            }
379            ValueId::Instruction(insn) => {
380                if let Some(&value) = self.values.get(&insn) {
381                    return Ok(value);
382                }
383                self.import(insn, size)
384            }
385            // A varnode or temp used as a *value* is its address, which only
386            // appears as a pointer operand and is handled there.
387            _ => Err(Unsupported::Operand("not a literal or in-block value")),
388        }
389    }
390
391    /// The declared width of an operand.
392    fn width_of(&self, id: ValueId) -> Result<usize, Unsupported> {
393        let ty = self
394            .ctx
395            .stored_type_of(id)
396            .ok_or(Unsupported::Operand("operand has no type"))?;
397        Ok(self.ctx.shared.types.size_of(ty))
398    }
399
400    /// Translates the body of `block` up to its first interrupting user
401    /// operation, or all of it, then emits the exports the rest of the block
402    /// will need.
403    ///
404    /// Returns the body index the interpreter continues from once the
405    /// compiled code has run: the terminator's when nothing interrupts, and
406    /// the interrupting op's otherwise. Compiled code runs the part before it,
407    /// and the interpreter — positioned at the op by that index — raises the
408    /// interrupt, exactly as it would have with no compiled code at all. The
409    /// values the op and everything after it read from the compiled part are
410    /// exported, the same way a terminator's operands are.
411    ///
412    /// `start` is the body index to begin at: 0 for a whole block, or the
413    /// instruction after an interrupting op when the interpreter has run the
414    /// block up to there and hands the rest back. Results of instructions
415    /// before `start` that the compiled part reads are imported from the value
416    /// buffer, where the caller places them from the interpreter's table.
417    pub(crate) fn translate_body(
418        &mut self,
419        block: BlockId,
420        start: usize,
421    ) -> Result<usize, Unsupported> {
422        let insns: Vec<InstructionId> = BasicBlock::from_id(self.ctx, block).instruction_ids();
423        if insns.is_empty() {
424            return Err(Unsupported::Terminator("block is empty"));
425        }
426        let body = &insns[..insns.len() - 1];
427        if start > body.len() {
428            return Err(Unsupported::Terminator("entry point past the body"));
429        }
430        let cut = body[start..]
431            .iter()
432            .position(|&insn| self.interrupts(insn))
433            .map_or(body.len(), |offset| start + offset);
434        self.own = insns.iter().copied().collect();
435
436        for &insn_id in &body[start..cut] {
437            self.translate_one(insn_id)?;
438            self.note_escapes(insn_id);
439        }
440
441        let own = std::mem::take(&mut self.own);
442        for &reader in &insns[cut..] {
443            self.export_operands(reader, &own)?;
444        }
445        for &def in &std::mem::take(&mut self.escaping) {
446            self.export(def)?;
447        }
448        Ok(cut)
449    }
450
451    /// Whether the interpreter stops at this instruction for the host: a user
452    /// p-code op with no semantics of its own, which this backend has no way
453    /// to run either. The ops it does model are the ones `translate_one`
454    /// translates in place.
455    fn interrupts(&self, insn_id: InstructionId) -> bool {
456        let insn = qcode::value::Instruction::from_id(self.ctx, insn_id);
457        let Mnemonic::PCodeOp(op) = insn.mnemonic() else {
458            return false;
459        };
460        let name = &self.ctx.shared.pcode_ops[op.id];
461        !matches!(
462            (name.as_ref(), op.args.as_slice()),
463            ("undef", []) | ("LOCK" | "UNLOCK", [])
464        )
465    }
466
467    /// Notes that `insn_id`'s result is read from outside this block, so it
468    /// has to be exported when the code returns.
469    ///
470    /// Compiled code keeps a block-local value in a machine register, which
471    /// another block cannot see; exporting it to the value buffer is what
472    /// lets that block import it. Lifted guest code rarely produces such a
473    /// use — state crosses blocks through registers and uniques, which are
474    /// memory — but an injected hook that splits a block does, and a value it
475    /// computed before the split is read by the code after it.
476    fn note_escapes(&mut self, insn_id: InstructionId) {
477        let value = ValueId::Instruction(insn_id);
478        if self
479            .ctx
480            .users_of(value)
481            .iter()
482            .any(|user| !self.own.contains(user))
483        {
484            self.escaping.push(insn_id);
485        }
486    }
487
488    /// Writes every operand of `reader` that compiled code computed into the
489    /// export buffer, so the interpreter can read it back before running
490    /// `reader` itself — the terminator, or the tail of a block cut at an
491    /// interrupt.
492    ///
493    /// Operands the interpreter can already resolve on its own — literals,
494    /// varnode and temp addresses, results of earlier blocks it walked — need
495    /// nothing, so a terminator that reads only those exports nothing.
496    fn export_operands(
497        &mut self,
498        reader: InstructionId,
499        own: &FxHashSet<InstructionId>,
500    ) -> Result<(), Unsupported> {
501        let insn = qcode::value::Instruction::from_id(self.ctx, reader);
502        let operands: Vec<ValueId> = insn
503            .mnemonic()
504            .args()
505            .into_iter()
506            .map(|arg| arg.qualify(reader.func))
507            .collect();
508
509        for operand in operands {
510            let ValueId::Instruction(def) = operand else {
511                continue;
512            };
513            if !own.contains(&def) {
514                continue;
515            }
516            self.export(def)?;
517        }
518        Ok(())
519    }
520
521    /// Writes the result of `def` into the next value-buffer slot, once,
522    /// if compiled code produced it.
523    ///
524    /// A definition in this block that compiled code did not produce — an
525    /// instruction past the cut — is the interpreter's to compute, and needs
526    /// nothing.
527    fn export(&mut self, def: InstructionId) -> Result<(), Unsupported> {
528        if self.exports.iter().any(|export| export.insn == def) {
529            return Ok(());
530        }
531        let Some(&value) = self.values.get(&def) else {
532            return Ok(());
533        };
534        let size = self.width_of(ValueId::Instruction(def))?;
535        // A slot is a `u64`. A terminator operand or an escaping value is a
536        // condition or a small integer in practice, so this is a decline that
537        // has never been observed rather than a width worth widening for.
538        if size > std::mem::size_of::<u64>() {
539            return Err(Unsupported::Terminator("operand wider than an export slot"));
540        }
541        let slot = self.imports.len() + self.exports.len();
542        let widened = self.widen_to_u64(value);
543        self.builder.ins().store(
544            MemFlags::trusted(),
545            widened,
546            self.exports_arg,
547            (slot * std::mem::size_of::<u64>()) as i32,
548        );
549        self.exports.push(Export { insn: def, size });
550        Ok(())
551    }
552
553    /// `value` zero-extended to the export buffer's slot width.
554    fn widen_to_u64(&mut self, value: Value) -> Value {
555        if self.builder.func.dfg.value_type(value) == types::I64 {
556            value
557        } else {
558            self.builder.ins().uextend(types::I64, value)
559        }
560    }
561
562    /// Loads the result of `insn`, computed before this code was entered,
563    /// from the next value-buffer slot.
564    ///
565    /// The result is there whenever the instruction ran: the interpreter
566    /// files every result it computes, and compiled code exports the ones
567    /// read from outside it. SSA guarantees the definition ran before any
568    /// use, so a missing import is a runtime decline, not a wrong value.
569    ///
570    /// Imports are numbered from zero as they are met, and the exports emitted
571    /// at the end of translation take the slots after them.
572    fn import(&mut self, insn: InstructionId, size: usize) -> Result<Value, Unsupported> {
573        if size > std::mem::size_of::<u64>() {
574            return Err(Unsupported::Operand("import wider than a value slot"));
575        }
576        let slot = self.imports.len();
577        let wide = self.builder.ins().load(
578            types::I64,
579            MemFlags::trusted(),
580            self.exports_arg,
581            (slot * std::mem::size_of::<u64>()) as i32,
582        );
583        let ty = int_type(size)?;
584        let value = if ty == types::I64 {
585            wide
586        } else {
587            self.builder.ins().ireduce(ty, wide)
588        };
589        self.imports.push(Export { insn, size });
590        self.values.insert(insn, value);
591        Ok(value)
592    }
593
594    fn translate_one(&mut self, insn_id: InstructionId) -> Result<(), Unsupported> {
595        let insn = qcode::value::Instruction::from_id(self.ctx, insn_id);
596        let func = insn_id.func;
597        let result = match insn.mnemonic() {
598            &Mnemonic::Load(Load { space, ptr, size }) => {
599                let space = space.qualify(func);
600                if !self.is_flat(space) {
601                    let addr = self.guest_address(ptr.qualify(func))?;
602                    let value = self.ram_load(addr, size)?;
603                    return self.record(insn_id, Some(value));
604                }
605                let addr = self
606                    .constant_address(ptr.qualify(func))
607                    .ok_or(Unsupported::Access("non-constant address"))?;
608                let ty = int_type(size)?;
609                let slot = self.table.slot(space, addr as usize + size);
610                let base = self.base(slot);
611                Some(self.builder.ins().load(
612                    ty,
613                    MemFlags::trusted(),
614                    base,
615                    i32::try_from(addr).map_err(|_| Unsupported::Access("address too large"))?,
616                ))
617            }
618
619            &Mnemonic::Store(Store {
620                space,
621                ptr,
622                size,
623                src,
624            }) => {
625                let space = space.qualify(func);
626                if !self.is_flat(space) {
627                    let addr = self.guest_address(ptr.qualify(func))?;
628                    let value = self.operand(src.qualify(func), size)?;
629                    self.ram_store(addr, value, size)?;
630                    return Ok(());
631                }
632                let addr = self
633                    .constant_address(ptr.qualify(func))
634                    .ok_or(Unsupported::Access("non-constant address"))?;
635                int_type(size)?;
636                let value = self.operand(src.qualify(func), size)?;
637                let slot = self.table.slot(space, addr as usize + size);
638                let base = self.base(slot);
639                self.builder.ins().store(
640                    MemFlags::trusted(),
641                    value,
642                    base,
643                    i32::try_from(addr).map_err(|_| Unsupported::Access("address too large"))?,
644                );
645                None
646            }
647
648            Mnemonic::Binop(Binary { op, lhs, rhs }) => {
649                let lhs_id = lhs.qualify(func);
650                let rhs_id = rhs.qualify(func);
651                let width = self.width_of(lhs_id)?;
652                if self.width_of(rhs_id)? != width {
653                    return Err(Unsupported::Operand("mismatched operand widths"));
654                }
655                let a = self.operand(lhs_id, width)?;
656                let b = self.operand(rhs_id, width)?;
657                Some(self.binop(*op, a, b)?)
658            }
659
660            Mnemonic::Unop(Unary { op, src }) => {
661                let src_id = src.qualify(func);
662                let width = self.width_of(src_id)?;
663                let value = self.operand(src_id, width)?;
664                match op {
665                    Unop::IntNot => Some(self.builder.ins().bnot(value)),
666                    Unop::IntNegate => Some(self.builder.ins().ineg(value)),
667                    _ => return Err(Unsupported::Mnemonic("float unop")),
668                }
669            }
670
671            &Mnemonic::Zext(Zext { src, size }) => {
672                let src_id = src.qualify(func);
673                let from = self.width_of(src_id)?;
674                let value = self.operand(src_id, from)?;
675                let ty = int_type(size)?;
676                Some(match from.cmp(&size) {
677                    std::cmp::Ordering::Less => self.builder.ins().uextend(ty, value),
678                    std::cmp::Ordering::Equal => value,
679                    std::cmp::Ordering::Greater => self.builder.ins().ireduce(ty, value),
680                })
681            }
682
683            &Mnemonic::Sext(Sext { src, size }) => {
684                let src_id = src.qualify(func);
685                let from = self.width_of(src_id)?;
686                let value = self.operand(src_id, from)?;
687                let ty = int_type(size)?;
688                Some(match from.cmp(&size) {
689                    std::cmp::Ordering::Less => self.builder.ins().sextend(ty, value),
690                    std::cmp::Ordering::Equal => value,
691                    std::cmp::Ordering::Greater => self.builder.ins().ireduce(ty, value),
692                })
693            }
694
695            &Mnemonic::Range(Range { src, start, size }) => {
696                let src_id = src.qualify(func);
697                let from = self.width_of(src_id)?;
698                let value = self.operand(src_id, from)?;
699                let from_ty = int_type(from)?;
700                let ty = int_type(size)?;
701                let shifted = if start == 0 {
702                    value
703                } else {
704                    let amount = self.constant(from_ty, (start * 8) as u128);
705                    self.builder.ins().ushr(value, amount)
706                };
707                Some(if from == size {
708                    shifted
709                } else {
710                    self.builder.ins().ireduce(ty, shifted)
711                })
712            }
713
714            // The status-flag primitives. x86 lifting emits these for almost
715            // every arithmetic instruction, so without them a block of ordinary
716            // integer code would be declined outright.
717            &Mnemonic::PopCount(PopCount { src }) => {
718                let src_id = src.qualify(func);
719                let from = self.width_of(src_id)?;
720                // Cranelift has no 128-bit `popcnt` lowering, and reaching it
721                // would be a panic inside the backend rather than a decline.
722                if from > std::mem::size_of::<u64>() {
723                    return Err(Unsupported::Width(from));
724                }
725                let value = self.operand(src_id, from)?;
726                let counted = self.builder.ins().popcnt(value);
727                let out = self.width_of(ValueId::Instruction(insn_id))?;
728                Some(self.resize(counted, from, out)?)
729            }
730
731            &Mnemonic::Carry(Carry { lhs, rhs }) => {
732                let (a, b) = self.pair(lhs.qualify(func), rhs.qualify(func))?;
733                // Unsigned overflow: the sum wrapped below either operand.
734                let sum = self.builder.ins().iadd(a, b);
735                Some(self.builder.ins().icmp(IntCC::UnsignedLessThan, sum, a))
736            }
737
738            &Mnemonic::SCarry(SCarry { lhs, rhs }) => {
739                let (a, b) = self.pair(lhs.qualify(func), rhs.qualify(func))?;
740                // Signed overflow of `a + b`: both operands differ in sign from
741                // the result.
742                let sum = self.builder.ins().iadd(a, b);
743                let a_differs = self.builder.ins().bxor(a, sum);
744                let b_differs = self.builder.ins().bxor(b, sum);
745                let both = self.builder.ins().band(a_differs, b_differs);
746                let ty = self.builder.func.dfg.value_type(both);
747                let zero = self.constant(ty, 0);
748                Some(self.builder.ins().icmp(IntCC::SignedLessThan, both, zero))
749            }
750
751            &Mnemonic::SBorrow(SBorrow { lhs, rhs }) => {
752                let (a, b) = self.pair(lhs.qualify(func), rhs.qualify(func))?;
753                // Signed overflow of `a - b`: the operands differ in sign and
754                // the result takes the subtrahend's.
755                let diff = self.builder.ins().isub(a, b);
756                let operands_differ = self.builder.ins().bxor(a, b);
757                let result_differs = self.builder.ins().bxor(a, diff);
758                let both = self.builder.ins().band(operands_differ, result_differs);
759                let ty = self.builder.func.dfg.value_type(both);
760                let zero = self.constant(ty, 0);
761                Some(self.builder.ins().icmp(IntCC::SignedLessThan, both, zero))
762            }
763
764            // The user-ops the interpreter models without architectural
765            // effect. Mirrored here exactly rather than approximated: `undef`
766            // is SLEIGH's explicit write of an undefined value, which concrete
767            // emulation resolves to zero, and the LOCK markers constrain
768            // nothing observable in a single-threaded replay.
769            Mnemonic::PCodeOp(op) => {
770                let name = &self.ctx.shared.pcode_ops[op.id];
771                match (name.as_ref(), op.args.as_slice()) {
772                    ("undef", []) => {
773                        let out = self.width_of(ValueId::Instruction(insn_id))?;
774                        let ty = int_type(out)?;
775                        Some(self.constant(ty, 0))
776                    }
777                    ("LOCK" | "UNLOCK", []) => None,
778                    _ => return Err(Unsupported::Mnemonic("user p-code op")),
779                }
780            }
781
782            other => return Err(Unsupported::Mnemonic(other.opcode())),
783        };
784
785        if let Some(value) = result {
786            self.values.insert(insn_id, value);
787        }
788        Ok(())
789    }
790
791    /// Flags for an access to guest memory or its permission bytes.
792    ///
793    /// Not [`MemFlags::trusted`]: that asserts alignment, and a guest access is
794    /// unaligned whenever the guest says so. No alias region either, so
795    /// Cranelift keeps these ordered against the flat-space stores around them
796    /// — a faulting access must leave exactly the prefix that ran before it.
797    ///
798    /// The endianness is the host's, which is the guest's: this backend is
799    /// built for an x86-64 guest on an x86-64 host, and the flat spaces have
800    /// always been read the same way.
801    fn guest_flags() -> MemFlags {
802        // `notrap` is earned: the address has been translated and its
803        // permissions checked before any of these run.
804        MemFlags::new().with_notrap()
805    }
806
807    /// A constant of `ty`.
808    ///
809    /// `iconst` cannot make an `I128` — Cranelift builds one from its halves —
810    /// so every constant the translator needs goes through here rather than
811    /// each caller having to remember which widths are reachable.
812    fn constant(&mut self, ty: Type, value: u128) -> Value {
813        if ty == types::I128 {
814            let low = self.builder.ins().iconst(types::I64, value as u64 as i64);
815            let high = self
816                .builder
817                .ins()
818                .iconst(types::I64, (value >> 64) as u64 as i64);
819            self.builder.ins().iconcat(low, high)
820        } else {
821            self.builder.ins().iconst(ty, value as u64 as i64)
822        }
823    }
824
825    /// `byte` repeated across every byte of `ty`.
826    fn splat(byte: u8, ty: Type) -> i64 {
827        let mut bits = [0u8; 8];
828        for slot in bits.iter_mut().take(ty.bytes() as usize) {
829            *slot = byte;
830        }
831        i64::from_le_bytes(bits)
832    }
833
834    /// The block that stops the run and reports a fault to the caller.
835    ///
836    /// Every faulting access jumps to this one block rather than carrying its
837    /// own epilogue. It is only *created* here — its body is emitted by
838    /// [`Self::finish`], because a builder may not leave a block that has not
839    /// been terminated yet, and the block asking for this one is mid-access.
840    fn fault_block(&mut self) -> cranelift::prelude::Block {
841        if let Some(block) = self.fault_block {
842            return block;
843        }
844        let block = self.builder.create_block();
845        self.builder.set_cold_block(block);
846        self.fault_block = Some(block);
847        block
848    }
849
850    /// Continues in a fresh block, taking `target` instead when `cond` is
851    /// non-zero.
852    fn bail_if(&mut self, cond: Value, target: cranelift::prelude::Block) {
853        let carry_on = self.builder.create_block();
854        self.builder.ins().brif(cond, target, &[], carry_on, &[]);
855        self.builder.switch_to_block(carry_on);
856    }
857
858    /// As [`Self::bail_if`], taking `target` when `cond` is zero.
859    fn bail_unless(&mut self, cond: Value, target: cranelift::prelude::Block) {
860        let carry_on = self.builder.create_block();
861        self.builder.ins().brif(cond, carry_on, &[], target, &[]);
862        self.builder.switch_to_block(carry_on);
863    }
864
865    /// A guest address as a 64-bit value.
866    ///
867    /// Unlike a flat access, a RAM pointer is a computed guest value: a
868    /// literal, or something this block worked out. A varnode or temp *id*
869    /// reaching here would mean the block is addressing RAM by the storage
870    /// location's own address, which is not what a guest pointer is.
871    fn guest_address(&mut self, ptr: ValueId) -> Result<Value, Unsupported> {
872        let width = self.width_of(ptr)?;
873        if width > 8 {
874            return Err(Unsupported::Access("address wider than 64 bits"));
875        }
876        let value = self.operand(ptr, width)?;
877        // Widened from the value's own type rather than the declared width. The
878        // two agree on everything lifted so far, and if they ever stop, a
879        // mismatched `iadd` below is a panic inside Cranelift rather than a
880        // declined block.
881        Ok(match self.builder.func.dfg.value_type(value) {
882            types::I64 => value,
883            ty if ty.is_int() => self.builder.ins().uextend(types::I64, value),
884            _ => return Err(Unsupported::Access("address is not an integer")),
885        })
886    }
887
888    /// Checks that a value really is the `size`-byte integer it is declared to
889    /// be, so a machine store writes the width the guest asked for.
890    fn checked_width(&self, value: Value, size: usize) -> Result<(), Unsupported> {
891        if self.builder.func.dfg.value_type(value) == int_type(size)? {
892            Ok(())
893        } else {
894            Err(Unsupported::Operand("value is not its declared width"))
895        }
896    }
897
898    /// Translates `addr` and checks its permissions inline, branching to
899    /// `fallback` at the first thing it cannot settle.
900    ///
901    /// Returns the host address of the access and the permission bytes it
902    /// found there — the store path reuses the latter rather than loading it
903    /// twice.
904    fn inline_access(
905        &mut self,
906        addr: Value,
907        size: usize,
908        kind: Access,
909        fallback: cranelift::prelude::Block,
910    ) -> Result<(Value, Value), Unsupported> {
911        let ty = int_type(size)?;
912        let page_mask = (PAGE_SIZE - 1) as i64;
913
914        // A translation covers one page, so an access spilling into the next
915        // one is not this path's to make. A single byte never can.
916        if size > 1 {
917            let offset = self.builder.ins().band_imm(addr, page_mask);
918            let last = self.builder.ins().iadd_imm(offset, size as i64 - 1);
919            let spills = self.builder.ins().band_imm(last, !page_mask);
920            self.bail_if(spills, fallback);
921        }
922
923        // The entry's *byte* offset comes straight out of the address: shifting
924        // by the page bits would give the page number, so shifting by that
925        // much less the entry size gives the offset of its entry directly.
926        let entry_size = std::mem::size_of::<TlbEntry>() as i64;
927        debug_assert!(entry_size.count_ones() == 1);
928        let entry_bits = entry_size.trailing_zeros() as i64;
929        let index_shift = PAGE_SIZE.trailing_zeros() as i64 - entry_bits;
930        let shifted = self.builder.ins().ushr_imm(addr, index_shift);
931        let offset = self
932            .builder
933            .ins()
934            .band_imm(shifted, (TLB_ENTRIES as i64 - 1) << entry_bits);
935        let entry = self.builder.ins().iadd(self.tlb_arg, offset);
936
937        // The tag and the offset beside it are loaded as plain memory, not as
938        // a no-alias region: the fallback rewrites this table, and a tag
939        // hoisted above that call while its offset stayed below would pair one
940        // page's tag with another page's address.
941        let flags = MemFlags::trusted();
942        let cached = self.builder.ins().load(types::I64, flags, entry, 0);
943        let tag = self.builder.ins().band_imm(addr, !page_mask);
944        let hit = self.builder.ins().icmp(IntCC::Equal, tag, cached);
945        self.bail_unless(hit, fallback);
946
947        let delta = self.builder.ins().load(types::I64, flags, entry, 8);
948        let host = self.builder.ins().iadd(addr, delta);
949
950        // Permissions are per byte and sit one fixed offset past the data, so
951        // `size` of them load as one integer and check as one mask: every
952        // required bit present in every byte is `required & !held == 0`.
953        let held = self
954            .builder
955            .ins()
956            .load(ty, Self::guest_flags(), host, PAGE_PERM_OFFSET as i32);
957        let required = self
958            .builder
959            .ins()
960            .iconst(ty, Self::splat(kind.required(), ty));
961        let missing = self.builder.ins().band_not(required, held);
962        self.bail_if(missing, fallback);
963
964        Ok((host, held))
965    }
966
967    /// Whether a RAM access of `size` is one the inlined path is built for.
968    ///
969    /// The permission check splats its mask into an `Imm64` and the fallback
970    /// passes the value as a `u64`, so both stop at eight bytes. Nothing wider
971    /// reaches guest RAM in code built without SSE — the 128-bit values x86
972    /// integer code produces live in lifter temporaries, which are flat.
973    fn narrow_enough_for_ram(&self, size: usize) -> Result<(), Unsupported> {
974        if size > std::mem::size_of::<u64>() {
975            return Err(Unsupported::Access("guest RAM access wider than 8 bytes"));
976        }
977        Ok(())
978    }
979
980    /// The stack slot the slow-path load writes through.
981    fn load_slot(&mut self) -> codegen::ir::StackSlot {
982        if let Some(slot) = self.load_slot {
983            return slot;
984        }
985        let slot = self.builder.create_sized_stack_slot(StackSlotData::new(
986            StackSlotKind::ExplicitSlot,
987            8,
988            3,
989        ));
990        self.load_slot = Some(slot);
991        slot
992    }
993
994    /// Reads `size` bytes of guest RAM at `addr`.
995    fn ram_load(&mut self, addr: Value, size: usize) -> Result<Value, Unsupported> {
996        let ty = int_type(size)?;
997        self.narrow_enough_for_ram(size)?;
998        let done = self.builder.create_block();
999        self.builder.append_block_param(done, ty);
1000        let fallback = self.builder.create_block();
1001        self.builder.set_cold_block(fallback);
1002
1003        let (host, _) = self.inline_access(addr, size, Access::Load, fallback)?;
1004        let value = self.builder.ins().load(ty, Self::guest_flags(), host, 0);
1005        self.builder.ins().jump(done, &[BlockArg::from(value)]);
1006
1007        self.builder.switch_to_block(fallback);
1008        let slot = self.load_slot();
1009        let out = self.builder.ins().stack_addr(types::I64, slot, 0);
1010        let width = self.builder.ins().iconst(types::I32, size as i64);
1011        let call = self
1012            .builder
1013            .ins()
1014            .call(self.helpers.load, &[self.memory_arg, addr, width, out]);
1015        let status = self.builder.inst_results(call)[0];
1016        let faulted = self.fault_block();
1017        self.bail_if(status, faulted);
1018        let wide = self.builder.ins().stack_load(types::I64, slot, 0);
1019        let narrowed = if ty == types::I64 {
1020            wide
1021        } else {
1022            self.builder.ins().ireduce(ty, wide)
1023        };
1024        self.builder.ins().jump(done, &[BlockArg::from(narrowed)]);
1025
1026        self.builder.switch_to_block(done);
1027        Ok(self.builder.block_params(done)[0])
1028    }
1029
1030    /// Writes `value` to `size` bytes of guest RAM at `addr`.
1031    fn ram_store(&mut self, addr: Value, value: Value, size: usize) -> Result<(), Unsupported> {
1032        let ty = int_type(size)?;
1033        self.narrow_enough_for_ram(size)?;
1034        self.checked_width(value, size)?;
1035        let done = self.builder.create_block();
1036        let fallback = self.builder.create_block();
1037        self.builder.set_cold_block(fallback);
1038
1039        let (host, held) = self.inline_access(addr, size, Access::Store, fallback)?;
1040        self.builder
1041            .ins()
1042            .store(Self::guest_flags(), value, host, 0);
1043        // A written byte is a defined byte. The MMU's own `write` records this,
1044        // and the inline path has to as well: the bit is guest-visible state
1045        // the moment `check_uninit` is turned on, and a run whose JIT-written
1046        // bytes read as undefined would diverge from the same run interpreted.
1047        let init = self
1048            .builder
1049            .ins()
1050            .bor_imm(held, Self::splat(perm::INIT, ty));
1051        self.builder
1052            .ins()
1053            .store(Self::guest_flags(), init, host, PAGE_PERM_OFFSET as i32);
1054        self.builder.ins().jump(done, &[]);
1055
1056        self.builder.switch_to_block(fallback);
1057        let width = self.builder.ins().iconst(types::I32, size as i64);
1058        let wide = self.widen_to_u64(value);
1059        let call = self
1060            .builder
1061            .ins()
1062            .call(self.helpers.store, &[self.memory_arg, addr, width, wide]);
1063        let status = self.builder.inst_results(call)[0];
1064        let faulted = self.fault_block();
1065        self.bail_if(status, faulted);
1066        self.builder.ins().jump(done, &[]);
1067
1068        self.builder.switch_to_block(done);
1069        Ok(())
1070    }
1071
1072    /// Files an instruction's result, if it has one, and reports success.
1073    fn record(&mut self, insn_id: InstructionId, result: Option<Value>) -> Result<(), Unsupported> {
1074        if let Some(value) = result {
1075            self.values.insert(insn_id, value);
1076        }
1077        Ok(())
1078    }
1079
1080    /// Both operands of a two-operand primitive, checked for equal width.
1081    fn pair(&mut self, lhs: ValueId, rhs: ValueId) -> Result<(Value, Value), Unsupported> {
1082        let width = self.width_of(lhs)?;
1083        if self.width_of(rhs)? != width {
1084            return Err(Unsupported::Operand("mismatched operand widths"));
1085        }
1086        Ok((self.operand(lhs, width)?, self.operand(rhs, width)?))
1087    }
1088
1089    /// Widens or narrows `value` from `from` bytes to `to` bytes, unsigned.
1090    fn resize(&mut self, value: Value, from: usize, to: usize) -> Result<Value, Unsupported> {
1091        let ty = int_type(to)?;
1092        int_type(from)?;
1093        Ok(match from.cmp(&to) {
1094            std::cmp::Ordering::Less => self.builder.ins().uextend(ty, value),
1095            std::cmp::Ordering::Equal => value,
1096            std::cmp::Ordering::Greater => self.builder.ins().ireduce(ty, value),
1097        })
1098    }
1099
1100    /// What an over-wide shift leaves behind.
1101    fn guard_shift(&mut self, shifted: Value, a: Value, amount: Value, kind: ShiftKind) -> Value {
1102        let ty = self.builder.func.dfg.value_type(a);
1103        let bits = u128::from(ty.bits());
1104        // Built as a value rather than with the `_imm` form: those take an
1105        // `Imm64`, which cannot describe a 128-bit operand.
1106        let width = self.constant(ty, bits);
1107        let in_range = self
1108            .builder
1109            .ins()
1110            .icmp(IntCC::UnsignedLessThan, amount, width);
1111        let saturated = match kind {
1112            ShiftKind::Logical => self.constant(ty, 0),
1113            // Every bit becomes the sign bit.
1114            ShiftKind::Arithmetic => {
1115                let all = self.constant(ty, bits - 1);
1116                self.builder.ins().sshr(a, all)
1117            }
1118        };
1119        self.builder.ins().select(in_range, shifted, saturated)
1120    }
1121
1122    /// Integer division, with the cases Cranelift traps on steered around.
1123    ///
1124    /// QCode leaves nothing undefined here, so neither does this:
1125    ///
1126    /// * a zero divisor yields zero, for all four operations;
1127    /// * signed division of the most negative value by `-1` wraps — the
1128    ///   quotient is that same value and the remainder is zero — where the
1129    ///   machine instruction would fault.
1130    ///
1131    /// Both are handled by dividing by `1` instead and then correcting, which
1132    /// needs no branch. The correction is only needed for the zero divisor:
1133    /// dividing the most negative value by `1` *already* gives exactly the
1134    /// wrapped quotient, and its remainder of zero, so the overflow case needs
1135    /// nothing but to be kept away from the divide.
1136    fn divide(&mut self, a: Value, b: Value, kind: Division) -> Result<Value, Unsupported> {
1137        let ty = self.builder.func.dfg.value_type(a);
1138        if ty == types::I128 {
1139            return Ok(self.divide_wide(a, b, kind));
1140        }
1141        let zero = self.constant(ty, 0);
1142        let one = self.constant(ty, 1);
1143        let by_zero = self.builder.ins().icmp(IntCC::Equal, b, zero);
1144
1145        let mut avoid = by_zero;
1146        if kind.is_signed() {
1147            let most_negative = self.constant(ty, 1u128 << (ty.bits() - 1));
1148            let minus_one = self.constant(ty, u128::MAX);
1149            let a_is_min = self.builder.ins().icmp(IntCC::Equal, a, most_negative);
1150            let b_is_minus_one = self.builder.ins().icmp(IntCC::Equal, b, minus_one);
1151            let overflows = self.builder.ins().band(a_is_min, b_is_minus_one);
1152            avoid = self.builder.ins().bor(by_zero, overflows);
1153        }
1154
1155        let divisor = self.builder.ins().select(avoid, one, b);
1156        let result = match kind {
1157            Division::Unsigned => self.builder.ins().udiv(a, divisor),
1158            Division::UnsignedRem => self.builder.ins().urem(a, divisor),
1159            Division::Signed => self.builder.ins().sdiv(a, divisor),
1160            Division::SignedRem => self.builder.ins().srem(a, divisor),
1161        };
1162        Ok(self.builder.ins().select(by_zero, zero, result))
1163    }
1164
1165    /// A 128-bit division, through the runtime.
1166    ///
1167    /// x86-64 has no instruction for it and Cranelift no lowering to synthesise
1168    /// one, so this is a call — but only for the division itself. Declining
1169    /// instead would send the whole block to the interpreter, and on the
1170    /// benchmarks that divide, that block is the loop body.
1171    fn divide_wide(&mut self, a: Value, b: Value, kind: Division) -> Value {
1172        let slot = self.builder.create_sized_stack_slot(StackSlotData::new(
1173            StackSlotKind::ExplicitSlot,
1174            16,
1175            4,
1176        ));
1177        let out = self.builder.ins().stack_addr(types::I64, slot, 0);
1178        // The halves are passed separately rather than as one 128-bit
1179        // argument: how a `__int128` travels is an ABI detail the two sides
1180        // would have to agree on silently, and getting it wrong corrupts
1181        // results rather than failing to link.
1182        let (a_low, a_high) = self.builder.ins().isplit(a);
1183        let (b_low, b_high) = self.builder.ins().isplit(b);
1184        let helper = self.helpers.divisions[kind.helper()];
1185        self.builder
1186            .ins()
1187            .call(helper, &[a_low, a_high, b_low, b_high, out]);
1188        let low = self.builder.ins().stack_load(types::I64, slot, 0);
1189        let high = self.builder.ins().stack_load(types::I64, slot, 8);
1190        self.builder.ins().iconcat(low, high)
1191    }
1192
1193    fn binop(&mut self, op: Binop, a: Value, b: Value) -> Result<Value, Unsupported> {
1194        let ins = self.builder.ins();
1195        Ok(match op {
1196            Binop::Int(int) => match int {
1197                IntBinop::Add => ins.iadd(a, b),
1198                IntBinop::Sub => ins.isub(a, b),
1199                IntBinop::And => ins.band(a, b),
1200                IntBinop::Or => ins.bor(a, b),
1201                IntBinop::Xor => ins.bxor(a, b),
1202                IntBinop::Mul => ins.imul(a, b),
1203                // An out-of-range shift is where QCode and Cranelift disagree,
1204                // so it cannot be left to the machine. QCode follows p-code: a
1205                // shift by the operand's width or more yields zero, and an
1206                // arithmetic one yields the sign. Cranelift instead *masks* the
1207                // amount, so a shift by 64 of a 64-bit value would be a shift by
1208                // none. Both are handled with a select rather than a branch.
1209                IntBinop::ShiftLeft => {
1210                    let shifted = ins.ishl(a, b);
1211                    return Ok(self.guard_shift(shifted, a, b, ShiftKind::Logical));
1212                }
1213                IntBinop::ShiftRight => {
1214                    let shifted = ins.ushr(a, b);
1215                    return Ok(self.guard_shift(shifted, a, b, ShiftKind::Logical));
1216                }
1217                IntBinop::SShiftRight => {
1218                    let shifted = ins.sshr(a, b);
1219                    return Ok(self.guard_shift(shifted, a, b, ShiftKind::Arithmetic));
1220                }
1221                IntBinop::Equal => ins.icmp(IntCC::Equal, a, b),
1222                IntBinop::NotEqual => ins.icmp(IntCC::NotEqual, a, b),
1223                IntBinop::Less => ins.icmp(IntCC::UnsignedLessThan, a, b),
1224                IntBinop::LessEqual => ins.icmp(IntCC::UnsignedLessThanOrEqual, a, b),
1225                IntBinop::SLess => ins.icmp(IntCC::SignedLessThan, a, b),
1226                IntBinop::SLessEqual => ins.icmp(IntCC::SignedLessThanOrEqual, a, b),
1227                // Division is not the guest architecture's business at this
1228                // level: QCode defines it completely — a zero divisor yields
1229                // zero, and signed division wraps rather than trapping. What
1230                // Cranelift does instead is *trap*, so the trapping cases are
1231                // steered away from rather than left to the interpreter.
1232                IntBinop::Div => return self.divide(a, b, Division::Unsigned),
1233                IntBinop::Rem => return self.divide(a, b, Division::UnsignedRem),
1234                IntBinop::Sdiv => return self.divide(a, b, Division::Signed),
1235                IntBinop::Srem => return self.divide(a, b, Division::SignedRem),
1236                _ => return Err(Unsupported::Mnemonic("integer binop")),
1237            },
1238            Binop::Float(_) => return Err(Unsupported::Mnemonic("float binop")),
1239            _ => return Err(Unsupported::Mnemonic("binop")),
1240        })
1241    }
1242
1243    pub(crate) fn finish(mut self) {
1244        let ok = self.builder.ins().iconst(types::I32, BLOCK_OK);
1245        self.builder.ins().return_(&[ok]);
1246        // Now that the body's last block is terminated, the shared fault
1247        // epilogue can be filled.
1248        if let Some(block) = self.fault_block {
1249            self.builder.switch_to_block(block);
1250            let status = self.builder.ins().iconst(types::I32, BLOCK_FAULT);
1251            self.builder.ins().return_(&[status]);
1252        }
1253        // The fault block and the continuations every inline check splits off
1254        // are reached only by branches already emitted, so they can all be
1255        // sealed at once now that no more will be added.
1256        self.builder.seal_all_blocks();
1257        self.builder.finalize();
1258    }
1259}