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}