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.
255    values: FxHashMap<InstructionId, Value>,
256    pub(crate) table: SpaceTable,
257    /// Values the terminator reads, in export-buffer slot order.
258    pub(crate) exports: Vec<Export>,
259}
260
261/// The runtime entry points compiled code calls when an access cannot be
262/// settled inline, as declared in the module.
263#[derive(Debug, Clone, Copy)]
264pub struct Helpers {
265    pub load: FuncId,
266    pub store: FuncId,
267    /// The 128-bit divisions, in `Division` order.
268    pub divisions: [FuncId; 4],
269}
270
271/// The same pair, resolved against one function being built.
272#[derive(Debug, Clone, Copy)]
273pub(crate) struct HelperRefs {
274    pub(crate) load: codegen::ir::FuncRef,
275    pub(crate) store: codegen::ir::FuncRef,
276    pub(crate) divisions: [codegen::ir::FuncRef; 4],
277}
278
279/// One value compiled code hands back for the interpreter to read.
280#[derive(Debug, Clone, Copy)]
281pub struct Export {
282    /// The instruction whose result this is; the key it is filed under in the
283    /// interpreter's value table.
284    pub insn: InstructionId,
285    /// Its declared width in bytes, which the slot's low bytes hold.
286    pub size: usize,
287}
288
289impl<'a, 'ctx> BlockTranslator<'a, 'ctx> {
290    pub(crate) fn new(
291        ctx: &'ctx Context<'ctx>,
292        builder: FunctionBuilder<'a>,
293        entry: cranelift::prelude::Block,
294        helpers: HelperRefs,
295    ) -> Self {
296        let spaces_arg = builder.block_params(entry)[0];
297        let exports_arg = builder.block_params(entry)[1];
298        let tlb_arg = builder.block_params(entry)[2];
299        let memory_arg = builder.block_params(entry)[3];
300        Self {
301            ctx,
302            builder,
303            spaces_arg,
304            exports_arg,
305            tlb_arg,
306            memory_arg,
307            helpers,
308            fault_block: None,
309            load_slot: None,
310            bases: FxHashMap::default(),
311            values: FxHashMap::default(),
312            table: SpaceTable::default(),
313            exports: Vec::new(),
314        }
315    }
316
317    /// Loads (once) the base pointer for a space slot.
318    fn base(&mut self, slot: usize) -> Value {
319        if let Some(base) = self.bases.get(&slot) {
320            return *base;
321        }
322        let offset = (slot * std::mem::size_of::<*mut u8>()) as i32;
323        let base =
324            self.builder
325                .ins()
326                .load(types::I64, MemFlags::trusted(), self.spaces_arg, offset);
327        self.bases.insert(slot, base);
328        base
329    }
330
331    /// Whether `space` is one this backend may address directly.
332    ///
333    /// Guest RAM is not, however constant its address: every access to it owes
334    /// a permission check and a fault report, which is the MMU's to give. A
335    /// RIP-relative operand resolves to a perfectly constant address and is
336    /// still RAM — treating one as flat storage silently reads a fabricated,
337    /// zero-filled space instead of the guest's memory. RAM is reached through
338    /// [`Self::inline_access`] instead.
339    fn is_flat(&self, space: MemorySpaceId) -> bool {
340        space != MemorySpaceId::Shared(self.ctx.shared.default_space)
341    }
342
343    /// The address a flat access resolves to, or `None` if it is not a constant.
344    fn constant_address(&self, ptr: ValueId) -> Option<u64> {
345        match ValueRef::new(ptr, self.ctx) {
346            ValueRef::Literal(literal) => Some(literal.value()),
347            ValueRef::Temp(temp) => Some(temp.address() as u64),
348            ValueRef::Varnode(varnode) => Some(varnode.address() as u64),
349            _ => None,
350        }
351    }
352
353    /// Produces the Cranelift value for a QCode operand.
354    fn operand(&mut self, id: ValueId, size: usize) -> Result<Value, Unsupported> {
355        let ty = int_type(size)?;
356        match id {
357            ValueId::Literal(_) => {
358                let ValueRef::Literal(literal) = ValueRef::new(id, self.ctx) else {
359                    return Err(Unsupported::Operand("literal did not resolve"));
360                };
361                // A QCode literal is at most 64 bits wide, so widening one to
362                // a 16-byte operand loses nothing.
363                Ok(self.constant(ty, u128::from(literal.value())))
364            }
365            ValueId::Instruction(insn) => self
366                .values
367                .get(&insn)
368                .copied()
369                .ok_or(Unsupported::Operand("value produced outside this block")),
370            // A varnode or temp used as a *value* is its address, which only
371            // appears as a pointer operand and is handled there.
372            _ => Err(Unsupported::Operand("not a literal or in-block value")),
373        }
374    }
375
376    /// The declared width of an operand.
377    fn width_of(&self, id: ValueId) -> Result<usize, Unsupported> {
378        let ty = self
379            .ctx
380            .stored_type_of(id)
381            .ok_or(Unsupported::Operand("operand has no type"))?;
382        Ok(self.ctx.shared.types.size_of(ty))
383    }
384
385    /// Translates every non-terminator instruction of `block`, then emits the
386    /// exports its terminator will need.
387    pub(crate) fn translate_body(&mut self, block: BlockId) -> Result<(), Unsupported> {
388        let insns: Vec<InstructionId> = BasicBlock::from_id(self.ctx, block).instruction_ids();
389        let Some((&terminator, body)) = insns.split_last() else {
390            return Err(Unsupported::Terminator("block is empty"));
391        };
392        let own: FxHashSet<InstructionId> = insns.iter().copied().collect();
393
394        for &insn_id in body {
395            self.translate_one(insn_id)?;
396            self.check_confined(insn_id, &own)?;
397        }
398
399        self.export_terminator_operands(terminator, &own)
400    }
401
402    /// Declines the block if `insn_id`'s result is read from outside it.
403    ///
404    /// Compiled code keeps a block-local value in a machine register, so a use
405    /// from another block would read whatever the interpreter's value table
406    /// happened to hold. Lifted guest code does not produce such a use — state
407    /// crosses blocks through registers and uniques, which are memory — but a
408    /// pass that introduced one must make the block decline, not miscompile.
409    fn check_confined(
410        &self,
411        insn_id: InstructionId,
412        own: &FxHashSet<InstructionId>,
413    ) -> Result<(), Unsupported> {
414        let value = ValueId::Instruction(insn_id);
415        if self
416            .ctx
417            .users_of(value)
418            .iter()
419            .any(|user| !own.contains(user))
420        {
421            return Err(Unsupported::Escapes("result used from another block"));
422        }
423        Ok(())
424    }
425
426    /// Writes every terminator operand this block defines into the export
427    /// buffer, so the interpreter can read it back before running the
428    /// terminator.
429    ///
430    /// Operands the interpreter can already resolve on its own — literals,
431    /// varnode and temp addresses, results of earlier blocks it walked — need
432    /// nothing, so a terminator that reads only those exports nothing.
433    fn export_terminator_operands(
434        &mut self,
435        terminator: InstructionId,
436        own: &FxHashSet<InstructionId>,
437    ) -> Result<(), Unsupported> {
438        let insn = qcode::value::Instruction::from_id(self.ctx, terminator);
439        let operands: Vec<ValueId> = insn
440            .mnemonic()
441            .args()
442            .into_iter()
443            .map(|arg| arg.qualify(terminator.func))
444            .collect();
445
446        for operand in operands {
447            let ValueId::Instruction(def) = operand else {
448                continue;
449            };
450            if !own.contains(&def) {
451                continue;
452            }
453            if self.exports.iter().any(|export| export.insn == def) {
454                continue;
455            }
456            let value = *self
457                .values
458                .get(&def)
459                .ok_or(Unsupported::Terminator("operand was not compiled"))?;
460            let size = self.width_of(operand)?;
461            // An export slot is a `u64`. A terminator operand is a condition or
462            // a small integer in practice, so this is a decline that has never
463            // been observed rather than a width worth widening the buffer for.
464            if size > std::mem::size_of::<u64>() {
465                return Err(Unsupported::Terminator("operand wider than an export slot"));
466            }
467            let slot = self.exports.len();
468            let widened = self.widen_to_u64(value);
469            self.builder.ins().store(
470                MemFlags::trusted(),
471                widened,
472                self.exports_arg,
473                (slot * std::mem::size_of::<u64>()) as i32,
474            );
475            self.exports.push(Export { insn: def, size });
476        }
477        Ok(())
478    }
479
480    /// `value` zero-extended to the export buffer's slot width.
481    fn widen_to_u64(&mut self, value: Value) -> Value {
482        if self.builder.func.dfg.value_type(value) == types::I64 {
483            value
484        } else {
485            self.builder.ins().uextend(types::I64, value)
486        }
487    }
488
489    fn translate_one(&mut self, insn_id: InstructionId) -> Result<(), Unsupported> {
490        let insn = qcode::value::Instruction::from_id(self.ctx, insn_id);
491        let func = insn_id.func;
492        let result = match insn.mnemonic() {
493            &Mnemonic::Load(Load { space, ptr, size }) => {
494                let space = space.qualify(func);
495                if !self.is_flat(space) {
496                    let addr = self.guest_address(ptr.qualify(func))?;
497                    let value = self.ram_load(addr, size)?;
498                    return self.record(insn_id, Some(value));
499                }
500                let addr = self
501                    .constant_address(ptr.qualify(func))
502                    .ok_or(Unsupported::Access("non-constant address"))?;
503                let ty = int_type(size)?;
504                let slot = self.table.slot(space, addr as usize + size);
505                let base = self.base(slot);
506                Some(self.builder.ins().load(
507                    ty,
508                    MemFlags::trusted(),
509                    base,
510                    i32::try_from(addr).map_err(|_| Unsupported::Access("address too large"))?,
511                ))
512            }
513
514            &Mnemonic::Store(Store {
515                space,
516                ptr,
517                size,
518                src,
519            }) => {
520                let space = space.qualify(func);
521                if !self.is_flat(space) {
522                    let addr = self.guest_address(ptr.qualify(func))?;
523                    let value = self.operand(src.qualify(func), size)?;
524                    self.ram_store(addr, value, size)?;
525                    return Ok(());
526                }
527                let addr = self
528                    .constant_address(ptr.qualify(func))
529                    .ok_or(Unsupported::Access("non-constant address"))?;
530                int_type(size)?;
531                let value = self.operand(src.qualify(func), size)?;
532                let slot = self.table.slot(space, addr as usize + size);
533                let base = self.base(slot);
534                self.builder.ins().store(
535                    MemFlags::trusted(),
536                    value,
537                    base,
538                    i32::try_from(addr).map_err(|_| Unsupported::Access("address too large"))?,
539                );
540                None
541            }
542
543            Mnemonic::Binop(Binary { op, lhs, rhs }) => {
544                let lhs_id = lhs.qualify(func);
545                let rhs_id = rhs.qualify(func);
546                let width = self.width_of(lhs_id)?;
547                if self.width_of(rhs_id)? != width {
548                    return Err(Unsupported::Operand("mismatched operand widths"));
549                }
550                let a = self.operand(lhs_id, width)?;
551                let b = self.operand(rhs_id, width)?;
552                Some(self.binop(*op, a, b)?)
553            }
554
555            Mnemonic::Unop(Unary { op, src }) => {
556                let src_id = src.qualify(func);
557                let width = self.width_of(src_id)?;
558                let value = self.operand(src_id, width)?;
559                match op {
560                    Unop::IntNot => Some(self.builder.ins().bnot(value)),
561                    Unop::IntNegate => Some(self.builder.ins().ineg(value)),
562                    _ => return Err(Unsupported::Mnemonic("float unop")),
563                }
564            }
565
566            &Mnemonic::Zext(Zext { src, size }) => {
567                let src_id = src.qualify(func);
568                let from = self.width_of(src_id)?;
569                let value = self.operand(src_id, from)?;
570                let ty = int_type(size)?;
571                Some(match from.cmp(&size) {
572                    std::cmp::Ordering::Less => self.builder.ins().uextend(ty, value),
573                    std::cmp::Ordering::Equal => value,
574                    std::cmp::Ordering::Greater => self.builder.ins().ireduce(ty, value),
575                })
576            }
577
578            &Mnemonic::Sext(Sext { src, size }) => {
579                let src_id = src.qualify(func);
580                let from = self.width_of(src_id)?;
581                let value = self.operand(src_id, from)?;
582                let ty = int_type(size)?;
583                Some(match from.cmp(&size) {
584                    std::cmp::Ordering::Less => self.builder.ins().sextend(ty, value),
585                    std::cmp::Ordering::Equal => value,
586                    std::cmp::Ordering::Greater => self.builder.ins().ireduce(ty, value),
587                })
588            }
589
590            &Mnemonic::Range(Range { src, start, size }) => {
591                let src_id = src.qualify(func);
592                let from = self.width_of(src_id)?;
593                let value = self.operand(src_id, from)?;
594                let from_ty = int_type(from)?;
595                let ty = int_type(size)?;
596                let shifted = if start == 0 {
597                    value
598                } else {
599                    let amount = self.constant(from_ty, (start * 8) as u128);
600                    self.builder.ins().ushr(value, amount)
601                };
602                Some(if from == size {
603                    shifted
604                } else {
605                    self.builder.ins().ireduce(ty, shifted)
606                })
607            }
608
609            // The status-flag primitives. x86 lifting emits these for almost
610            // every arithmetic instruction, so without them a block of ordinary
611            // integer code would be declined outright.
612            &Mnemonic::PopCount(PopCount { src }) => {
613                let src_id = src.qualify(func);
614                let from = self.width_of(src_id)?;
615                // Cranelift has no 128-bit `popcnt` lowering, and reaching it
616                // would be a panic inside the backend rather than a decline.
617                if from > std::mem::size_of::<u64>() {
618                    return Err(Unsupported::Width(from));
619                }
620                let value = self.operand(src_id, from)?;
621                let counted = self.builder.ins().popcnt(value);
622                let out = self.width_of(ValueId::Instruction(insn_id))?;
623                Some(self.resize(counted, from, out)?)
624            }
625
626            &Mnemonic::Carry(Carry { lhs, rhs }) => {
627                let (a, b) = self.pair(lhs.qualify(func), rhs.qualify(func))?;
628                // Unsigned overflow: the sum wrapped below either operand.
629                let sum = self.builder.ins().iadd(a, b);
630                Some(self.builder.ins().icmp(IntCC::UnsignedLessThan, sum, a))
631            }
632
633            &Mnemonic::SCarry(SCarry { lhs, rhs }) => {
634                let (a, b) = self.pair(lhs.qualify(func), rhs.qualify(func))?;
635                // Signed overflow of `a + b`: both operands differ in sign from
636                // the result.
637                let sum = self.builder.ins().iadd(a, b);
638                let a_differs = self.builder.ins().bxor(a, sum);
639                let b_differs = self.builder.ins().bxor(b, sum);
640                let both = self.builder.ins().band(a_differs, b_differs);
641                let ty = self.builder.func.dfg.value_type(both);
642                let zero = self.constant(ty, 0);
643                Some(self.builder.ins().icmp(IntCC::SignedLessThan, both, zero))
644            }
645
646            &Mnemonic::SBorrow(SBorrow { lhs, rhs }) => {
647                let (a, b) = self.pair(lhs.qualify(func), rhs.qualify(func))?;
648                // Signed overflow of `a - b`: the operands differ in sign and
649                // the result takes the subtrahend's.
650                let diff = self.builder.ins().isub(a, b);
651                let operands_differ = self.builder.ins().bxor(a, b);
652                let result_differs = self.builder.ins().bxor(a, diff);
653                let both = self.builder.ins().band(operands_differ, result_differs);
654                let ty = self.builder.func.dfg.value_type(both);
655                let zero = self.constant(ty, 0);
656                Some(self.builder.ins().icmp(IntCC::SignedLessThan, both, zero))
657            }
658
659            // The user-ops the interpreter models without architectural
660            // effect. Mirrored here exactly rather than approximated: `undef`
661            // is SLEIGH's explicit write of an undefined value, which concrete
662            // emulation resolves to zero, and the LOCK markers constrain
663            // nothing observable in a single-threaded replay.
664            Mnemonic::PCodeOp(op) => {
665                let name = &self.ctx.shared.pcode_ops[op.id];
666                match (name.as_ref(), op.args.as_slice()) {
667                    ("undef", []) => {
668                        let out = self.width_of(ValueId::Instruction(insn_id))?;
669                        let ty = int_type(out)?;
670                        Some(self.constant(ty, 0))
671                    }
672                    ("LOCK" | "UNLOCK", []) => None,
673                    _ => return Err(Unsupported::Mnemonic("user p-code op")),
674                }
675            }
676
677            other => return Err(Unsupported::Mnemonic(other.opcode())),
678        };
679
680        if let Some(value) = result {
681            self.values.insert(insn_id, value);
682        }
683        Ok(())
684    }
685
686    /// Flags for an access to guest memory or its permission bytes.
687    ///
688    /// Not [`MemFlags::trusted`]: that asserts alignment, and a guest access is
689    /// unaligned whenever the guest says so. No alias region either, so
690    /// Cranelift keeps these ordered against the flat-space stores around them
691    /// — a faulting access must leave exactly the prefix that ran before it.
692    ///
693    /// The endianness is the host's, which is the guest's: this backend is
694    /// built for an x86-64 guest on an x86-64 host, and the flat spaces have
695    /// always been read the same way.
696    fn guest_flags() -> MemFlags {
697        // `notrap` is earned: the address has been translated and its
698        // permissions checked before any of these run.
699        MemFlags::new().with_notrap()
700    }
701
702    /// A constant of `ty`.
703    ///
704    /// `iconst` cannot make an `I128` — Cranelift builds one from its halves —
705    /// so every constant the translator needs goes through here rather than
706    /// each caller having to remember which widths are reachable.
707    fn constant(&mut self, ty: Type, value: u128) -> Value {
708        if ty == types::I128 {
709            let low = self.builder.ins().iconst(types::I64, value as u64 as i64);
710            let high = self
711                .builder
712                .ins()
713                .iconst(types::I64, (value >> 64) as u64 as i64);
714            self.builder.ins().iconcat(low, high)
715        } else {
716            self.builder.ins().iconst(ty, value as u64 as i64)
717        }
718    }
719
720    /// `byte` repeated across every byte of `ty`.
721    fn splat(byte: u8, ty: Type) -> i64 {
722        let mut bits = [0u8; 8];
723        for slot in bits.iter_mut().take(ty.bytes() as usize) {
724            *slot = byte;
725        }
726        i64::from_le_bytes(bits)
727    }
728
729    /// The block that stops the run and reports a fault to the caller.
730    ///
731    /// Every faulting access jumps to this one block rather than carrying its
732    /// own epilogue. It is only *created* here — its body is emitted by
733    /// [`Self::finish`], because a builder may not leave a block that has not
734    /// been terminated yet, and the block asking for this one is mid-access.
735    fn fault_block(&mut self) -> cranelift::prelude::Block {
736        if let Some(block) = self.fault_block {
737            return block;
738        }
739        let block = self.builder.create_block();
740        self.builder.set_cold_block(block);
741        self.fault_block = Some(block);
742        block
743    }
744
745    /// Continues in a fresh block, taking `target` instead when `cond` is
746    /// non-zero.
747    fn bail_if(&mut self, cond: Value, target: cranelift::prelude::Block) {
748        let carry_on = self.builder.create_block();
749        self.builder.ins().brif(cond, target, &[], carry_on, &[]);
750        self.builder.switch_to_block(carry_on);
751    }
752
753    /// As [`Self::bail_if`], taking `target` when `cond` is zero.
754    fn bail_unless(&mut self, cond: Value, target: cranelift::prelude::Block) {
755        let carry_on = self.builder.create_block();
756        self.builder.ins().brif(cond, carry_on, &[], target, &[]);
757        self.builder.switch_to_block(carry_on);
758    }
759
760    /// A guest address as a 64-bit value.
761    ///
762    /// Unlike a flat access, a RAM pointer is a computed guest value: a
763    /// literal, or something this block worked out. A varnode or temp *id*
764    /// reaching here would mean the block is addressing RAM by the storage
765    /// location's own address, which is not what a guest pointer is.
766    fn guest_address(&mut self, ptr: ValueId) -> Result<Value, Unsupported> {
767        let width = self.width_of(ptr)?;
768        if width > 8 {
769            return Err(Unsupported::Access("address wider than 64 bits"));
770        }
771        let value = self.operand(ptr, width)?;
772        // Widened from the value's own type rather than the declared width. The
773        // two agree on everything lifted so far, and if they ever stop, a
774        // mismatched `iadd` below is a panic inside Cranelift rather than a
775        // declined block.
776        Ok(match self.builder.func.dfg.value_type(value) {
777            types::I64 => value,
778            ty if ty.is_int() => self.builder.ins().uextend(types::I64, value),
779            _ => return Err(Unsupported::Access("address is not an integer")),
780        })
781    }
782
783    /// Checks that a value really is the `size`-byte integer it is declared to
784    /// be, so a machine store writes the width the guest asked for.
785    fn checked_width(&self, value: Value, size: usize) -> Result<(), Unsupported> {
786        if self.builder.func.dfg.value_type(value) == int_type(size)? {
787            Ok(())
788        } else {
789            Err(Unsupported::Operand("value is not its declared width"))
790        }
791    }
792
793    /// Translates `addr` and checks its permissions inline, branching to
794    /// `fallback` at the first thing it cannot settle.
795    ///
796    /// Returns the host address of the access and the permission bytes it
797    /// found there — the store path reuses the latter rather than loading it
798    /// twice.
799    fn inline_access(
800        &mut self,
801        addr: Value,
802        size: usize,
803        kind: Access,
804        fallback: cranelift::prelude::Block,
805    ) -> Result<(Value, Value), Unsupported> {
806        let ty = int_type(size)?;
807        let page_mask = (PAGE_SIZE - 1) as i64;
808
809        // A translation covers one page, so an access spilling into the next
810        // one is not this path's to make. A single byte never can.
811        if size > 1 {
812            let offset = self.builder.ins().band_imm(addr, page_mask);
813            let last = self.builder.ins().iadd_imm(offset, size as i64 - 1);
814            let spills = self.builder.ins().band_imm(last, !page_mask);
815            self.bail_if(spills, fallback);
816        }
817
818        // The entry's *byte* offset comes straight out of the address: shifting
819        // by the page bits would give the page number, so shifting by that
820        // much less the entry size gives the offset of its entry directly.
821        let entry_size = std::mem::size_of::<TlbEntry>() as i64;
822        debug_assert!(entry_size.count_ones() == 1);
823        let entry_bits = entry_size.trailing_zeros() as i64;
824        let index_shift = PAGE_SIZE.trailing_zeros() as i64 - entry_bits;
825        let shifted = self.builder.ins().ushr_imm(addr, index_shift);
826        let offset = self
827            .builder
828            .ins()
829            .band_imm(shifted, (TLB_ENTRIES as i64 - 1) << entry_bits);
830        let entry = self.builder.ins().iadd(self.tlb_arg, offset);
831
832        // The tag and the offset beside it are loaded as plain memory, not as
833        // a no-alias region: the fallback rewrites this table, and a tag
834        // hoisted above that call while its offset stayed below would pair one
835        // page's tag with another page's address.
836        let flags = MemFlags::trusted();
837        let cached = self.builder.ins().load(types::I64, flags, entry, 0);
838        let tag = self.builder.ins().band_imm(addr, !page_mask);
839        let hit = self.builder.ins().icmp(IntCC::Equal, tag, cached);
840        self.bail_unless(hit, fallback);
841
842        let delta = self.builder.ins().load(types::I64, flags, entry, 8);
843        let host = self.builder.ins().iadd(addr, delta);
844
845        // Permissions are per byte and sit one fixed offset past the data, so
846        // `size` of them load as one integer and check as one mask: every
847        // required bit present in every byte is `required & !held == 0`.
848        let held = self
849            .builder
850            .ins()
851            .load(ty, Self::guest_flags(), host, PAGE_PERM_OFFSET as i32);
852        let required = self
853            .builder
854            .ins()
855            .iconst(ty, Self::splat(kind.required(), ty));
856        let missing = self.builder.ins().band_not(required, held);
857        self.bail_if(missing, fallback);
858
859        Ok((host, held))
860    }
861
862    /// Whether a RAM access of `size` is one the inlined path is built for.
863    ///
864    /// The permission check splats its mask into an `Imm64` and the fallback
865    /// passes the value as a `u64`, so both stop at eight bytes. Nothing wider
866    /// reaches guest RAM in code built without SSE — the 128-bit values x86
867    /// integer code produces live in lifter temporaries, which are flat.
868    fn narrow_enough_for_ram(&self, size: usize) -> Result<(), Unsupported> {
869        if size > std::mem::size_of::<u64>() {
870            return Err(Unsupported::Access("guest RAM access wider than 8 bytes"));
871        }
872        Ok(())
873    }
874
875    /// The stack slot the slow-path load writes through.
876    fn load_slot(&mut self) -> codegen::ir::StackSlot {
877        if let Some(slot) = self.load_slot {
878            return slot;
879        }
880        let slot = self.builder.create_sized_stack_slot(StackSlotData::new(
881            StackSlotKind::ExplicitSlot,
882            8,
883            3,
884        ));
885        self.load_slot = Some(slot);
886        slot
887    }
888
889    /// Reads `size` bytes of guest RAM at `addr`.
890    fn ram_load(&mut self, addr: Value, size: usize) -> Result<Value, Unsupported> {
891        let ty = int_type(size)?;
892        self.narrow_enough_for_ram(size)?;
893        let done = self.builder.create_block();
894        self.builder.append_block_param(done, ty);
895        let fallback = self.builder.create_block();
896        self.builder.set_cold_block(fallback);
897
898        let (host, _) = self.inline_access(addr, size, Access::Load, fallback)?;
899        let value = self.builder.ins().load(ty, Self::guest_flags(), host, 0);
900        self.builder.ins().jump(done, &[BlockArg::from(value)]);
901
902        self.builder.switch_to_block(fallback);
903        let slot = self.load_slot();
904        let out = self.builder.ins().stack_addr(types::I64, slot, 0);
905        let width = self.builder.ins().iconst(types::I32, size as i64);
906        let call = self
907            .builder
908            .ins()
909            .call(self.helpers.load, &[self.memory_arg, addr, width, out]);
910        let status = self.builder.inst_results(call)[0];
911        let faulted = self.fault_block();
912        self.bail_if(status, faulted);
913        let wide = self.builder.ins().stack_load(types::I64, slot, 0);
914        let narrowed = if ty == types::I64 {
915            wide
916        } else {
917            self.builder.ins().ireduce(ty, wide)
918        };
919        self.builder.ins().jump(done, &[BlockArg::from(narrowed)]);
920
921        self.builder.switch_to_block(done);
922        Ok(self.builder.block_params(done)[0])
923    }
924
925    /// Writes `value` to `size` bytes of guest RAM at `addr`.
926    fn ram_store(&mut self, addr: Value, value: Value, size: usize) -> Result<(), Unsupported> {
927        let ty = int_type(size)?;
928        self.narrow_enough_for_ram(size)?;
929        self.checked_width(value, size)?;
930        let done = self.builder.create_block();
931        let fallback = self.builder.create_block();
932        self.builder.set_cold_block(fallback);
933
934        let (host, held) = self.inline_access(addr, size, Access::Store, fallback)?;
935        self.builder
936            .ins()
937            .store(Self::guest_flags(), value, host, 0);
938        // A written byte is a defined byte. The MMU's own `write` records this,
939        // and the inline path has to as well: the bit is guest-visible state
940        // the moment `check_uninit` is turned on, and a run whose JIT-written
941        // bytes read as undefined would diverge from the same run interpreted.
942        let init = self
943            .builder
944            .ins()
945            .bor_imm(held, Self::splat(perm::INIT, ty));
946        self.builder
947            .ins()
948            .store(Self::guest_flags(), init, host, PAGE_PERM_OFFSET as i32);
949        self.builder.ins().jump(done, &[]);
950
951        self.builder.switch_to_block(fallback);
952        let width = self.builder.ins().iconst(types::I32, size as i64);
953        let wide = self.widen_to_u64(value);
954        let call = self
955            .builder
956            .ins()
957            .call(self.helpers.store, &[self.memory_arg, addr, width, wide]);
958        let status = self.builder.inst_results(call)[0];
959        let faulted = self.fault_block();
960        self.bail_if(status, faulted);
961        self.builder.ins().jump(done, &[]);
962
963        self.builder.switch_to_block(done);
964        Ok(())
965    }
966
967    /// Files an instruction's result, if it has one, and reports success.
968    fn record(&mut self, insn_id: InstructionId, result: Option<Value>) -> Result<(), Unsupported> {
969        if let Some(value) = result {
970            self.values.insert(insn_id, value);
971        }
972        Ok(())
973    }
974
975    /// Both operands of a two-operand primitive, checked for equal width.
976    fn pair(&mut self, lhs: ValueId, rhs: ValueId) -> Result<(Value, Value), Unsupported> {
977        let width = self.width_of(lhs)?;
978        if self.width_of(rhs)? != width {
979            return Err(Unsupported::Operand("mismatched operand widths"));
980        }
981        Ok((self.operand(lhs, width)?, self.operand(rhs, width)?))
982    }
983
984    /// Widens or narrows `value` from `from` bytes to `to` bytes, unsigned.
985    fn resize(&mut self, value: Value, from: usize, to: usize) -> Result<Value, Unsupported> {
986        let ty = int_type(to)?;
987        int_type(from)?;
988        Ok(match from.cmp(&to) {
989            std::cmp::Ordering::Less => self.builder.ins().uextend(ty, value),
990            std::cmp::Ordering::Equal => value,
991            std::cmp::Ordering::Greater => self.builder.ins().ireduce(ty, value),
992        })
993    }
994
995    /// What an over-wide shift leaves behind.
996    fn guard_shift(&mut self, shifted: Value, a: Value, amount: Value, kind: ShiftKind) -> Value {
997        let ty = self.builder.func.dfg.value_type(a);
998        let bits = u128::from(ty.bits());
999        // Built as a value rather than with the `_imm` form: those take an
1000        // `Imm64`, which cannot describe a 128-bit operand.
1001        let width = self.constant(ty, bits);
1002        let in_range = self
1003            .builder
1004            .ins()
1005            .icmp(IntCC::UnsignedLessThan, amount, width);
1006        let saturated = match kind {
1007            ShiftKind::Logical => self.constant(ty, 0),
1008            // Every bit becomes the sign bit.
1009            ShiftKind::Arithmetic => {
1010                let all = self.constant(ty, bits - 1);
1011                self.builder.ins().sshr(a, all)
1012            }
1013        };
1014        self.builder.ins().select(in_range, shifted, saturated)
1015    }
1016
1017    /// Integer division, with the cases Cranelift traps on steered around.
1018    ///
1019    /// QCode leaves nothing undefined here, so neither does this:
1020    ///
1021    /// * a zero divisor yields zero, for all four operations;
1022    /// * signed division of the most negative value by `-1` wraps — the
1023    ///   quotient is that same value and the remainder is zero — where the
1024    ///   machine instruction would fault.
1025    ///
1026    /// Both are handled by dividing by `1` instead and then correcting, which
1027    /// needs no branch. The correction is only needed for the zero divisor:
1028    /// dividing the most negative value by `1` *already* gives exactly the
1029    /// wrapped quotient, and its remainder of zero, so the overflow case needs
1030    /// nothing but to be kept away from the divide.
1031    fn divide(&mut self, a: Value, b: Value, kind: Division) -> Result<Value, Unsupported> {
1032        let ty = self.builder.func.dfg.value_type(a);
1033        if ty == types::I128 {
1034            return Ok(self.divide_wide(a, b, kind));
1035        }
1036        let zero = self.constant(ty, 0);
1037        let one = self.constant(ty, 1);
1038        let by_zero = self.builder.ins().icmp(IntCC::Equal, b, zero);
1039
1040        let mut avoid = by_zero;
1041        if kind.is_signed() {
1042            let most_negative = self.constant(ty, 1u128 << (ty.bits() - 1));
1043            let minus_one = self.constant(ty, u128::MAX);
1044            let a_is_min = self.builder.ins().icmp(IntCC::Equal, a, most_negative);
1045            let b_is_minus_one = self.builder.ins().icmp(IntCC::Equal, b, minus_one);
1046            let overflows = self.builder.ins().band(a_is_min, b_is_minus_one);
1047            avoid = self.builder.ins().bor(by_zero, overflows);
1048        }
1049
1050        let divisor = self.builder.ins().select(avoid, one, b);
1051        let result = match kind {
1052            Division::Unsigned => self.builder.ins().udiv(a, divisor),
1053            Division::UnsignedRem => self.builder.ins().urem(a, divisor),
1054            Division::Signed => self.builder.ins().sdiv(a, divisor),
1055            Division::SignedRem => self.builder.ins().srem(a, divisor),
1056        };
1057        Ok(self.builder.ins().select(by_zero, zero, result))
1058    }
1059
1060    /// A 128-bit division, through the runtime.
1061    ///
1062    /// x86-64 has no instruction for it and Cranelift no lowering to synthesise
1063    /// one, so this is a call — but only for the division itself. Declining
1064    /// instead would send the whole block to the interpreter, and on the
1065    /// benchmarks that divide, that block is the loop body.
1066    fn divide_wide(&mut self, a: Value, b: Value, kind: Division) -> Value {
1067        let slot = self.builder.create_sized_stack_slot(StackSlotData::new(
1068            StackSlotKind::ExplicitSlot,
1069            16,
1070            4,
1071        ));
1072        let out = self.builder.ins().stack_addr(types::I64, slot, 0);
1073        // The halves are passed separately rather than as one 128-bit
1074        // argument: how a `__int128` travels is an ABI detail the two sides
1075        // would have to agree on silently, and getting it wrong corrupts
1076        // results rather than failing to link.
1077        let (a_low, a_high) = self.builder.ins().isplit(a);
1078        let (b_low, b_high) = self.builder.ins().isplit(b);
1079        let helper = self.helpers.divisions[kind.helper()];
1080        self.builder
1081            .ins()
1082            .call(helper, &[a_low, a_high, b_low, b_high, out]);
1083        let low = self.builder.ins().stack_load(types::I64, slot, 0);
1084        let high = self.builder.ins().stack_load(types::I64, slot, 8);
1085        self.builder.ins().iconcat(low, high)
1086    }
1087
1088    fn binop(&mut self, op: Binop, a: Value, b: Value) -> Result<Value, Unsupported> {
1089        let ins = self.builder.ins();
1090        Ok(match op {
1091            Binop::Int(int) => match int {
1092                IntBinop::Add => ins.iadd(a, b),
1093                IntBinop::Sub => ins.isub(a, b),
1094                IntBinop::And => ins.band(a, b),
1095                IntBinop::Or => ins.bor(a, b),
1096                IntBinop::Xor => ins.bxor(a, b),
1097                IntBinop::Mul => ins.imul(a, b),
1098                // An out-of-range shift is where QCode and Cranelift disagree,
1099                // so it cannot be left to the machine. QCode follows p-code: a
1100                // shift by the operand's width or more yields zero, and an
1101                // arithmetic one yields the sign. Cranelift instead *masks* the
1102                // amount, so a shift by 64 of a 64-bit value would be a shift by
1103                // none. Both are handled with a select rather than a branch.
1104                IntBinop::ShiftLeft => {
1105                    let shifted = ins.ishl(a, b);
1106                    return Ok(self.guard_shift(shifted, a, b, ShiftKind::Logical));
1107                }
1108                IntBinop::ShiftRight => {
1109                    let shifted = ins.ushr(a, b);
1110                    return Ok(self.guard_shift(shifted, a, b, ShiftKind::Logical));
1111                }
1112                IntBinop::SShiftRight => {
1113                    let shifted = ins.sshr(a, b);
1114                    return Ok(self.guard_shift(shifted, a, b, ShiftKind::Arithmetic));
1115                }
1116                IntBinop::Equal => ins.icmp(IntCC::Equal, a, b),
1117                IntBinop::NotEqual => ins.icmp(IntCC::NotEqual, a, b),
1118                IntBinop::Less => ins.icmp(IntCC::UnsignedLessThan, a, b),
1119                IntBinop::LessEqual => ins.icmp(IntCC::UnsignedLessThanOrEqual, a, b),
1120                IntBinop::SLess => ins.icmp(IntCC::SignedLessThan, a, b),
1121                IntBinop::SLessEqual => ins.icmp(IntCC::SignedLessThanOrEqual, a, b),
1122                // Division is not the guest architecture's business at this
1123                // level: QCode defines it completely — a zero divisor yields
1124                // zero, and signed division wraps rather than trapping. What
1125                // Cranelift does instead is *trap*, so the trapping cases are
1126                // steered away from rather than left to the interpreter.
1127                IntBinop::Div => return self.divide(a, b, Division::Unsigned),
1128                IntBinop::Rem => return self.divide(a, b, Division::UnsignedRem),
1129                IntBinop::Sdiv => return self.divide(a, b, Division::Signed),
1130                IntBinop::Srem => return self.divide(a, b, Division::SignedRem),
1131                _ => return Err(Unsupported::Mnemonic("integer binop")),
1132            },
1133            Binop::Float(_) => return Err(Unsupported::Mnemonic("float binop")),
1134            _ => return Err(Unsupported::Mnemonic("binop")),
1135        })
1136    }
1137
1138    pub(crate) fn finish(mut self) {
1139        let ok = self.builder.ins().iconst(types::I32, BLOCK_OK);
1140        self.builder.ins().return_(&[ok]);
1141        // Now that the body's last block is terminated, the shared fault
1142        // epilogue can be filled.
1143        if let Some(block) = self.fault_block {
1144            self.builder.switch_to_block(block);
1145            let status = self.builder.ins().iconst(types::I32, BLOCK_FAULT);
1146            self.builder.ins().return_(&[status]);
1147        }
1148        // The fault block and the continuations every inline check splits off
1149        // are reached only by branches already emitted, so they can all be
1150        // sealed at once now that no more will be added.
1151        self.builder.seal_all_blocks();
1152        self.builder.finalize();
1153    }
1154}