Skip to main content

backend_lang/isel/
mod.rs

1//! Instruction selection: one ir-lang 2.0 function to a MIR function on
2//! virtual registers.
3//!
4//! Blocks and block parameters map one to one (IR.md §15): an IR edge's
5//! arguments become the MIR branch's arguments, resolved as a parallel copy
6//! at emission. Every value of an integer, `bool`, `ptr`, or `ref` type lives
7//! in one general-purpose virtual register.
8//!
9//! # Value representation
10//!
11//! A value of a type narrower than 64 bits occupies the low bits of its
12//! register; the bits above its width are **unspecified**, exactly as the
13//! x86-64 32-bit operations leave them. Arithmetic on 8-, 16-, and 32-bit
14//! types runs at 32 bits; wherever the upper bits matter (comparisons,
15//! division, right shifts, overflow checks, widening conversions, switch
16//! values, array indices) the value is extended first by its type's
17//! signedness. `bool` is the exception: always exactly 0 or 1, so it can be
18//! tested, stored, and converted directly; booleans entering from outside
19//! (parameters, call results, loads) are normalized.
20//!
21//! # Policies
22//!
23//! Every OPS integer operation is selected under every policy ir-lang can
24//! carry (IR.md §4): `wrap` computes modulo the width; `trap` branches to a
25//! per-function trap block that calls the runtime trap handler
26//! ([`Options::trap_symbol`](crate::Options)) with the OPS code; `error`
27//! (only inside a `check` terminator) branches to the error edge, passing
28//! the OPS code as `err`. Division checks zero, then `MIN / -1`, before it
29//! divides, so the processor never faults. Shifts check or mask their
30//! amount.
31//!
32//! # What is not selected yet
33//!
34//! Floating-point types and operations wait on isa-lang's SSE forms (ISSUES
35//! P11), as do `lock`-prefixed atomic read-modify-writes other than `xchg`
36//! and `cmpxchg`; `aapcs64` waits on the AArch64 target; 32-bit pointer
37//! modules on a 32-bit target. Each is reported as
38//! [`IselError::Unsupported`], never miscompiled.
39
40mod call;
41mod mem;
42mod ops;
43mod term;
44
45use alloc::collections::BTreeMap;
46use alloc::string::String;
47use alloc::vec;
48use alloc::vec::Vec;
49use core::fmt;
50
51use ir_lang::{
52    Block as IBlock, CallConv, FuncId, InstData, Module, PointerWidth, StackSlot as IStackSlot,
53    Terminator, Type, Value,
54};
55
56use crate::compile::Options;
57use crate::mir::{self, Block, Effects, Function, Inst, MirError, Operand, StackSlot, Symbol};
58use crate::regs::{PReg, Reg};
59use crate::target::abi::Abi;
60use crate::x86_64::{self, GPR, op};
61
62/// Why a function could not be selected.
63///
64/// # Examples
65///
66/// ```
67/// use backend_lang::isel::IselError;
68/// use backend_lang::Options;
69/// use ir_lang::{Linkage, Module, Signature, Type};
70///
71/// let mut m = Module::new("m");
72/// let f = m.declare_function("f", &Signature::new(&[Type::F64], &[]), Linkage::Export)?;
73/// let mut b = m.build(f)?;
74/// b.ret(&[])?;
75/// let err = backend_lang::isel::select(&m, f, &Options::sysv()).unwrap_err();
76/// assert!(matches!(err, IselError::Unsupported { .. }));
77/// # Ok::<(), Box<dyn std::error::Error>>(())
78/// ```
79#[derive(Clone, Debug, PartialEq, Eq)]
80#[non_exhaustive]
81pub enum IselError {
82    /// The module does not validate (ir-lang's [`Module::validate`]).
83    Invalid(ir_lang::ValidationError),
84    /// The function handle is not in the module, or it is an import.
85    NoBody(FuncId),
86    /// A construct this release does not select, and why.
87    Unsupported {
88        /// The function.
89        func: FuncId,
90        /// What, and what it waits on.
91        what: &'static str,
92    },
93    /// Building the MIR failed (a 32-bit table limit, or an internal
94    /// inconsistency reported rather than panicking).
95    Mir(MirError),
96}
97
98impl fmt::Display for IselError {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        match self {
101            IselError::Invalid(e) => write!(f, "invalid module: {e}"),
102            IselError::NoBody(func) => write!(f, "{func} has no body to select"),
103            IselError::Unsupported { func, what } => write!(f, "{func}: {what}"),
104            IselError::Mir(e) => write!(f, "building machine IR: {e}"),
105        }
106    }
107}
108
109impl core::error::Error for IselError {
110    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
111        match self {
112            IselError::Invalid(e) => Some(e),
113            IselError::Mir(e) => Some(e),
114            _ => None,
115        }
116    }
117}
118
119impl From<MirError> for IselError {
120    fn from(e: MirError) -> IselError {
121        IselError::Mir(e)
122    }
123}
124
125type Result<T> = core::result::Result<T, IselError>;
126
127/// Selects `func` of a validated `module` into a MIR function on virtual
128/// registers, named after the function and under the calling convention its
129/// signature selects. The module is validated first.
130///
131/// # Errors
132///
133/// [`IselError`].
134///
135/// # Examples
136///
137/// ```
138/// use backend_lang::Options;
139/// use ir_lang::{Linkage, Module, Overflow, Signature, Type};
140///
141/// let mut m = Module::new("m");
142/// let f = m.declare_function("inc", &Signature::new(&[Type::I64], &[Type::I64]), Linkage::Export)?;
143/// let mut b = m.build(f)?;
144/// let x = b.param(0)?;
145/// let one = b.iconst(Type::I64, 1)?;
146/// let y = b.add(x, one, Overflow::Wrap)?;
147/// b.ret(&[y])?;
148///
149/// let mir = backend_lang::isel::select(&m, f, &Options::sysv())?;
150/// assert_eq!(mir.name(), "inc");
151/// assert!(mir.to_string().contains("add64"));
152/// # Ok::<(), Box<dyn std::error::Error>>(())
153/// ```
154pub fn select(module: &Module, func: FuncId, options: &Options) -> Result<Function> {
155    module.validate().map_err(IselError::Invalid)?;
156    select_validated(module, func, options)
157}
158
159/// [`select`] for a module the caller has already validated.
160pub(crate) fn select_validated(
161    module: &Module,
162    func: FuncId,
163    options: &Options,
164) -> Result<Function> {
165    let decl = module.func_decl(func).ok_or(IselError::NoBody(func))?;
166    let body = module.function(func).ok_or(IselError::NoBody(func))?;
167    let sig = module.func_signature(func).ok_or(IselError::NoBody(func))?;
168    if module.pointer_width() != PointerWidth::P64 {
169        return Err(IselError::Unsupported {
170            func,
171            what: "32-bit pointers need a 32-bit target",
172        });
173    }
174    let abi = abi_for(func, sig.conv, options)?;
175    let mut sel = Sel::new(module, func, body, decl.name(), abi, options);
176    sel.run(sig)?;
177    Ok(sel.f)
178}
179
180/// The convention a signature's tag selects.
181pub(crate) fn abi_for(func: FuncId, conv: CallConv, options: &Options) -> Result<&'static Abi> {
182    match conv {
183        CallConv::Fast | CallConv::C => Ok(options.abi),
184        CallConv::SysV64 => Ok(&x86_64::SYSV),
185        CallConv::Win64 => Ok(&x86_64::WIN64),
186        _ => Err(IselError::Unsupported {
187            func,
188            what: "the aapcs64 convention needs the AArch64 target",
189        }),
190    }
191}
192
193/// How a narrow value is widened when its upper bits matter.
194#[derive(Clone, Copy, Debug, PartialEq, Eq)]
195pub(crate) enum Ext {
196    /// By the type's signedness.
197    Type,
198    /// With zeros.
199    Zero,
200    /// With copies of the sign bit.
201    Sign,
202}
203
204/// What a failing operation does: branch to a `check`'s error edge, or trap.
205#[derive(Clone, Copy, Debug)]
206pub(crate) enum Fail<'a> {
207    /// The error edge of a `check` (the OPS code becomes `err`).
208    Edge(&'a ir_lang::BlockCall),
209    /// A deterministic trap with the OPS code.
210    Trap,
211}
212
213/// Where an address comes from, for folding constant offsets into memory
214/// operands.
215#[derive(Clone, Copy, Debug)]
216pub(crate) enum Addr {
217    /// `slot + offset`, addressed from the stack pointer.
218    Slot(StackSlot, i64),
219    /// `reg + offset`.
220    Reg(Reg, i64),
221}
222
223/// The selection state of one function.
224pub(crate) struct Sel<'m> {
225    pub(crate) m: &'m Module,
226    pub(crate) func: FuncId,
227    pub(crate) irf: &'m ir_lang::Function,
228    pub(crate) opts: &'m Options,
229    pub(crate) abi: &'static Abi,
230    pub(crate) f: Function,
231    /// The register of each IR value.
232    vals: Vec<Option<Reg>>,
233    /// The bits of each IR value known to be a constant.
234    consts: Vec<Option<u64>>,
235    /// Folded addresses of IR address values.
236    addrs: Vec<Option<Addr>>,
237    /// Uses of each IR value.
238    uses: Vec<u32>,
239    /// Compares lowered at their single use (a branch or select in the same
240    /// block) so the flags reach it.
241    pub(crate) fused: Vec<bool>,
242    blocks: Vec<Option<Block>>,
243    /// IR slot → MIR slot, and the alignment to round its address up to when
244    /// the frame cannot align it.
245    slots: Vec<(StackSlot, u32)>,
246    syms: BTreeMap<String, Symbol>,
247    traps: BTreeMap<u32, Block>,
248    gc_slots: Vec<StackSlot>,
249    incoming: BTreeMap<u32, StackSlot>,
250    fence_slot: Option<StackSlot>,
251    /// The block instructions are appended to.
252    pub(crate) cur: Block,
253}
254
255impl<'m> Sel<'m> {
256    fn new(
257        m: &'m Module,
258        func: FuncId,
259        irf: &'m ir_lang::Function,
260        name: &str,
261        abi: &'static Abi,
262        opts: &'m Options,
263    ) -> Sel<'m> {
264        let nv = irf.value_count();
265        let mut f = Function::with_capacity(name, abi, irf.inst_count() * 2, irf.inst_count() * 6);
266        if !opts.frame_pointer {
267            f.omit_frame_pointer();
268        }
269        Sel {
270            m,
271            func,
272            irf,
273            opts,
274            abi,
275            f,
276            vals: vec![None; nv],
277            consts: vec![None; nv],
278            addrs: vec![None; nv],
279            uses: vec![0; nv],
280            fused: vec![false; nv],
281            blocks: vec![None; irf.block_count()],
282            slots: Vec::new(),
283            syms: BTreeMap::new(),
284            traps: BTreeMap::new(),
285            gc_slots: Vec::new(),
286            incoming: BTreeMap::new(),
287            fence_slot: None,
288            cur: Block::new(0),
289        }
290    }
291
292    pub(crate) fn unsupported<T>(&self, what: &'static str) -> Result<T> {
293        Err(IselError::Unsupported {
294            func: self.func,
295            what,
296        })
297    }
298
299    fn run(&mut self, sig: &ir_lang::Signature) -> Result<()> {
300        let irf = self.irf;
301        // Reverse postorder of the reachable IR blocks: MIR blocks are laid
302        // out in this order, the entry first.
303        let order = ir_rpo(irf);
304        self.count_uses(&order);
305        for &b in &order {
306            let mb = self.f.block()?;
307            self.blocks[b.index()] = Some(mb);
308        }
309        for &b in &order {
310            if b == irf.entry() {
311                continue;
312            }
313            let mb = self.block(b)?;
314            for &p in irf.block_params(b) {
315                let ty = self.ty(p)?;
316                let _ = ty;
317                let v = self.new_reg()?;
318                self.vals[p.index()] = Some(v);
319                self.f.add_param(mb, v.into())?;
320            }
321        }
322        for slot in 0..irf.slot_count() {
323            let data = irf
324                .slot(IStackSlot::from_u32(slot as u32))
325                .ok_or(MirError::Limit)?;
326            // The frame aligns slots to at most 16; a larger alignment is
327            // reached by rounding the slot's address up at run time inside a
328            // slot made large enough for it.
329            let (size, align, round) = if data.align > 16 {
330                (data.size.saturating_add(data.align - 16), 16, data.align)
331            } else {
332                (data.size, data.align.max(1), 0)
333            };
334            let s = self.f.stack_slot(mir::SlotKind::Local { size, align })?;
335            self.slots.push((s, round));
336        }
337        self.entry(sig)?;
338        for &b in &order {
339            self.cur = self.block(b)?;
340            let insts: Vec<ir_lang::Inst> = irf.insts(b).collect();
341            for inst in insts {
342                self.inst(inst)?;
343            }
344            let term = irf.terminator(b).ok_or(MirError::NoBlocks)?;
345            self.terminator(b, term)?;
346        }
347        Ok(())
348    }
349
350    /// The entry: incoming arguments from their ABI locations into virtual
351    /// registers with one parallel copy.
352    fn entry(&mut self, sig: &ir_lang::Signature) -> Result<()> {
353        let irf = self.irf;
354        let entry = self.block(irf.entry())?;
355        self.cur = entry;
356        let classes = self.arg_classes(&sig.params)?;
357        let mut locs = Vec::new();
358        let _ = self.abi.assign_args(&classes, &mut locs);
359        let mut dsts = Vec::with_capacity(locs.len());
360        let mut srcs = Vec::with_capacity(locs.len());
361        for (k, (&p, loc)) in irf.block_params(irf.entry()).iter().zip(&locs).enumerate() {
362            let src: Operand = match *loc {
363                crate::target::abi::ArgLoc::Reg(r) => {
364                    self.f.add_param(entry, r.into())?;
365                    r.into()
366                }
367                crate::target::abi::ArgLoc::Stack(off) => {
368                    let s = self.incoming_slot(off)?;
369                    self.f.add_param(entry, Operand::slot(s))?;
370                    Operand::slot(s)
371                }
372            };
373            let v = self.new_reg()?;
374            dsts.push(Operand::from(v));
375            srcs.push(src);
376            self.vals[p.index()] = Some(v);
377            let _ = k;
378        }
379        if !dsts.is_empty() {
380            dsts.extend(srcs);
381            let _ = self.f.push(entry, op::PCOPY, &dsts)?;
382        }
383        // Booleans from outside are normalized to 0 or 1.
384        for &p in irf.block_params(irf.entry()) {
385            if irf.value_type(p) == Some(Type::Bool) {
386                let r = self.val(p)?;
387                let n = self.emit_def(op::MOVZX8, &[r.into()])?;
388                self.vals[p.index()] = Some(n);
389            }
390        }
391        Ok(())
392    }
393
394    /// The incoming-argument slot at `offset` above the canonical frame
395    /// address (one per offset, shared by the entry and tail calls).
396    pub(crate) fn incoming_slot(&mut self, offset: u32) -> Result<StackSlot> {
397        if let Some(&s) = self.incoming.get(&offset) {
398            return Ok(s);
399        }
400        let s = self
401            .f
402            .stack_slot(mir::SlotKind::Incoming { offset, size: 8 })?;
403        let _ = self.incoming.insert(offset, s);
404        Ok(s)
405    }
406
407    /// The `i`-th GC root slot (shared by every safepoint: each reloads its
408    /// relocated references right after its call).
409    pub(crate) fn gc_slot(&mut self, i: usize) -> Result<StackSlot> {
410        while self.gc_slots.len() <= i {
411            let s = self
412                .f
413                .stack_slot(mir::SlotKind::Local { size: 8, align: 8 })?;
414            self.gc_slots.push(s);
415        }
416        self.gc_slots
417            .get(i)
418            .copied()
419            .ok_or(IselError::Mir(MirError::Limit))
420    }
421
422    /// A slot whose only use is a full-barrier `xchg`.
423    pub(crate) fn fence_slot(&mut self) -> Result<StackSlot> {
424        if let Some(s) = self.fence_slot {
425            return Ok(s);
426        }
427        let s = self
428            .f
429            .stack_slot(mir::SlotKind::Local { size: 8, align: 8 })?;
430        self.fence_slot = Some(s);
431        Ok(s)
432    }
433
434    fn count_uses(&mut self, order: &[IBlock]) {
435        let irf = self.irf;
436        for &b in order {
437            for inst in irf.insts(b) {
438                if let Some(data) = irf.inst(inst) {
439                    data.for_each_operand(|v| {
440                        let v = irf.resolve(v);
441                        if let Some(n) = self.uses.get_mut(v.index()) {
442                            *n += 1;
443                        }
444                    });
445                }
446            }
447            if let Some(t) = irf.terminator(b) {
448                t.for_each_operand(|v| {
449                    let v = irf.resolve(v);
450                    if let Some(n) = self.uses.get_mut(v.index()) {
451                        *n += 1;
452                    }
453                });
454            }
455        }
456        // A compare whose only use is the branch or a select later in its own
457        // block is lowered there, so the flags it sets reach the use. The
458        // candidates of the current block are stamped with its position in
459        // `order` (plus one), so each test is O(1) however long the block.
460        let mut stamp: Vec<u32> = vec![0; self.fused.len()];
461        for (k, &b) in order.iter().enumerate() {
462            let here = k as u32 + 1;
463            let candidate = |stamp: &[u32], c: Value| stamp.get(c.index()) == Some(&here);
464            for inst in irf.insts(b) {
465                if let Some(InstData::Compare { args, .. }) = irf.inst(inst) {
466                    let r = irf.results(inst);
467                    if let Some(v) = r.first() {
468                        let fusible_ty = irf
469                            .value_type(irf.resolve(args[0]))
470                            .is_some_and(|t| !t.is_float());
471                        if self.uses.get(v.index()) == Some(&1) && fusible_ty {
472                            if let Some(s) = stamp.get_mut(v.index()) {
473                                *s = here;
474                            }
475                        }
476                    }
477                }
478                if let Some(InstData::Select { cond, .. }) = irf.inst(inst) {
479                    let c = irf.resolve(*cond);
480                    if candidate(&stamp, c) {
481                        self.fused[c.index()] = true;
482                    }
483                }
484            }
485            if let Some(Terminator::Branch { cond, .. }) = irf.terminator(b) {
486                let c = irf.resolve(*cond);
487                if candidate(&stamp, c) {
488                    self.fused[c.index()] = true;
489                }
490            }
491        }
492    }
493
494    /// The MIR block of an IR block.
495    pub(crate) fn block(&self, b: IBlock) -> Result<Block> {
496        self.blocks
497            .get(b.index())
498            .copied()
499            .flatten()
500            .ok_or(IselError::Mir(MirError::UnknownBlock(Block::new(
501                b.as_u32(),
502            ))))
503    }
504
505    /// A fresh general-purpose virtual register.
506    pub(crate) fn new_reg(&mut self) -> Result<Reg> {
507        Ok(self.f.vreg(GPR)?)
508    }
509
510    /// The type of an IR value; floats are not selected yet.
511    pub(crate) fn ty(&self, v: Value) -> Result<Type> {
512        let t = self
513            .irf
514            .value_type(self.irf.resolve(v))
515            .ok_or(IselError::Mir(MirError::Limit))?;
516        if t.is_float() {
517            return self.unsupported("floating point waits on isa-lang's SSE forms (ISSUES P11)");
518        }
519        Ok(t)
520    }
521
522    /// The register holding IR value `v`.
523    pub(crate) fn val(&self, v: Value) -> Result<Reg> {
524        let v = self.irf.resolve(v);
525        self.vals
526            .get(v.index())
527            .copied()
528            .flatten()
529            .ok_or(IselError::Mir(MirError::Limit))
530    }
531
532    /// The constant bits of `v`, if it is one.
533    pub(crate) fn konst(&self, v: Value) -> Option<u64> {
534        let v = self.irf.resolve(v);
535        self.consts.get(v.index()).copied().flatten()
536    }
537
538    /// The operands of a commutative two-address operation in the order that
539    /// spares a copy: the definition shares the first operand's register,
540    /// which it can only do when that operand dies here. So a constant moves
541    /// to the right (where it becomes an immediate), and a left operand still
542    /// needed later trades places with a right operand used only here.
543    pub(crate) fn commute(&self, args: [Value; 2]) -> [Value; 2] {
544        let uses = |v: Value| {
545            let v = self.irf.resolve(v);
546            self.uses.get(v.index()).copied().unwrap_or(0)
547        };
548        let (k0, k1) = (self.konst(args[0]).is_some(), self.konst(args[1]).is_some());
549        if (k0 && !k1) || (!k1 && uses(args[0]) > 1 && uses(args[1]) == 1) {
550            [args[1], args[0]]
551        } else {
552            args
553        }
554    }
555
556    pub(crate) fn set_val(&mut self, v: Value, r: Reg) {
557        if let Some(slot) = self.vals.get_mut(v.index()) {
558            *slot = Some(r);
559        }
560    }
561
562    pub(crate) fn set_addr(&mut self, v: Value, a: Addr) {
563        if let Some(slot) = self.addrs.get_mut(v.index()) {
564            *slot = Some(a);
565        }
566    }
567
568    pub(crate) fn addr_of(&self, v: Value) -> Option<Addr> {
569        let v = self.irf.resolve(v);
570        self.addrs.get(v.index()).copied().flatten()
571    }
572
573    pub(crate) fn mir_slot(&self, s: IStackSlot) -> Result<(StackSlot, u32)> {
574        self.slots
575            .get(s.index())
576            .copied()
577            .ok_or(IselError::Mir(MirError::UnknownSlot(StackSlot::new(
578                s.as_u32(),
579            ))))
580    }
581
582    /// The symbol for `name` in the function under construction.
583    pub(crate) fn symbol(&mut self, name: &str) -> Result<Symbol> {
584        if let Some(&s) = self.syms.get(name) {
585            return Ok(s);
586        }
587        let s = self.f.symbol(name)?;
588        let _ = self.syms.insert(String::from(name), s);
589        Ok(s)
590    }
591
592    /// Appends an instruction to the current block.
593    pub(crate) fn emit(&mut self, opcode: crate::target::Opcode, ops: &[Operand]) -> Result<Inst> {
594        Ok(self.f.push(self.cur, opcode, ops)?)
595    }
596
597    /// Appends an instruction with extra implicit operands.
598    pub(crate) fn emit_with(
599        &mut self,
600        opcode: crate::target::Opcode,
601        ops: &[Operand],
602        effects: Effects<'_>,
603    ) -> Result<Inst> {
604        Ok(self.f.push_with(self.cur, opcode, ops, effects)?)
605    }
606
607    /// Appends `d = opcode ops...` with a fresh `d`, returning it.
608    pub(crate) fn emit_def(
609        &mut self,
610        opcode: crate::target::Opcode,
611        ops: &[Operand],
612    ) -> Result<Reg> {
613        let d = self.new_reg()?;
614        let mut all = [Operand::Imm(0); 5];
615        all[0] = d.into();
616        let n = ops.len().min(4);
617        all[1..=n].copy_from_slice(&ops[..n]);
618        let _ = self.emit(opcode, &all[..=n])?;
619        Ok(d)
620    }
621
622    /// `d = src` into a fresh register.
623    pub(crate) fn copy_to_new(&mut self, src: Operand) -> Result<Reg> {
624        let d = self.new_reg()?;
625        let _ = self.emit(op::COPY, &[d.into(), src])?;
626        Ok(d)
627    }
628
629    /// Starts a new block (with no parameters) and makes it current.
630    pub(crate) fn new_block(&mut self) -> Result<Block> {
631        Ok(self.f.block()?)
632    }
633
634    /// `jcc cc, then(), else()` from the current block.
635    pub(crate) fn branch(
636        &mut self,
637        cc: isa_lang::x86_64::Cond,
638        then: Block,
639        other: Block,
640    ) -> Result<()> {
641        let t = self.f.block_call(then, &[])?;
642        let e = self.f.block_call(other, &[])?;
643        let _ = self.emit(op::JCC, &[x86_64::cond(cc).into(), t.into(), e.into()])?;
644        Ok(())
645    }
646
647    /// `jmp target(args)` from the current block.
648    pub(crate) fn jump(&mut self, target: Block, args: &[Operand]) -> Result<()> {
649        let c = self.f.block_call(target, args)?;
650        let _ = self.emit(op::JMP, &[c.into()])?;
651        Ok(())
652    }
653
654    /// The trap block for `code`: the code into the first argument register,
655    /// a call to the trap handler, `ud2` after it in case it returns.
656    pub(crate) fn trap_block(&mut self, code: u32) -> Result<Block> {
657        if let Some(&b) = self.traps.get(&code) {
658            return Ok(b);
659        }
660        let saved = self.cur;
661        let b = self.new_block()?;
662        self.cur = b;
663        self.trap_call(code)?;
664        self.cur = saved;
665        let _ = self.traps.insert(code, b);
666        Ok(b)
667    }
668
669    /// In the current block: call the trap handler with `code`, then `ud2`.
670    pub(crate) fn trap_call(&mut self, code: u32) -> Result<()> {
671        let c_abi = self.opts.abi;
672        let arg = first_int_arg(c_abi)?;
673        let sym = self.symbol(self.opts.trap_symbol)?;
674        let _ = self.emit(op::COPY, &[arg.into(), Operand::Imm(i64::from(code))])?;
675        let uses = [arg];
676        let _ = self.emit_with(
677            op::CALL,
678            &[sym.into()],
679            Effects::new().uses(&uses).clobbers(c_abi.call_clobbers()),
680        )?;
681        let _ = self.emit(op::UD2, &[])?;
682        Ok(())
683    }
684
685    /// Branches to the failure of an operation when `cc` holds, continuing in
686    /// a fresh block otherwise. `code` is the OPS error code.
687    pub(crate) fn fail_if(
688        &mut self,
689        cc: isa_lang::x86_64::Cond,
690        fail: Fail<'_>,
691        code: u32,
692    ) -> Result<()> {
693        let next = self.new_block()?;
694        match fail {
695            Fail::Trap => {
696                let t = self.trap_block(code)?;
697                self.branch(cc, t, next)?;
698            }
699            Fail::Edge(dest) => {
700                let call = self.edge(dest, &[], None, Some(Operand::Imm(i64::from(code))))?;
701                let e = self.f.block_call(next, &[])?;
702                let _ = self.emit(op::JCC, &[x86_64::cond(cc).into(), call.into(), e.into()])?;
703            }
704        }
705        self.cur = next;
706        Ok(())
707    }
708
709    /// Lowers an IR edge to a MIR branch target: arguments are values (an
710    /// immediate when constant), call results, the exception payload, or the
711    /// error code.
712    pub(crate) fn edge(
713        &mut self,
714        dest: &ir_lang::BlockCall,
715        results: &[Operand],
716        exn: Option<Operand>,
717        err: Option<Operand>,
718    ) -> Result<mir::BlockCall> {
719        let target = self.block(dest.block)?;
720        let mut args = Vec::with_capacity(dest.args.len());
721        for a in &dest.args {
722            let op = match *a {
723                ir_lang::BlockArg::Value(v) => self.operand(v)?,
724                ir_lang::BlockArg::Result(i) => results
725                    .get(i as usize)
726                    .copied()
727                    .ok_or(IselError::Mir(MirError::Limit))?,
728                ir_lang::BlockArg::Exception => exn.ok_or(IselError::Mir(MirError::Limit))?,
729                ir_lang::BlockArg::ErrorCode => err.ok_or(IselError::Mir(MirError::Limit))?,
730                _ => return self.unsupported("an edge argument kind this release does not know"),
731            };
732            args.push(op);
733        }
734        Ok(self.f.block_call(target, &args)?)
735    }
736
737    /// A value as an operand: an immediate when it is a constant, else its
738    /// register.
739    pub(crate) fn operand(&self, v: Value) -> Result<Operand> {
740        match self.konst(v) {
741            Some(bits) => Ok(Operand::Imm(bits as i64)),
742            None => Ok(self.val(v)?.into()),
743        }
744    }
745
746    fn inst(&mut self, inst: ir_lang::Inst) -> Result<()> {
747        let irf = self.irf;
748        let data = irf.inst(inst).ok_or(MirError::Limit)?.clone();
749        let results = irf.results(inst);
750        match data {
751            InstData::Const { ty, bits } => {
752                if ty.is_float() {
753                    return self
754                        .unsupported("floating point waits on isa-lang's SSE forms (ISSUES P11)");
755                }
756                let r = results.first().ok_or(MirError::Limit)?;
757                let d = self.emit_def(op::MOV64RI, &[Operand::Imm(bits as i64)])?;
758                self.set_val(r, d);
759                if let Some(c) = self.consts.get_mut(r.index()) {
760                    *c = Some(bits);
761                }
762                Ok(())
763            }
764            InstData::Unary { op, policy, arg } => {
765                let r = results.first().ok_or(MirError::Limit)?;
766                let d = self.unary(op, policy, arg, Fail::Trap)?;
767                self.set_val(r, d);
768                Ok(())
769            }
770            InstData::Binary { op, policy, args } => {
771                let r = results.first().ok_or(MirError::Limit)?;
772                let d = self.binary(op, policy, args, Fail::Trap)?;
773                self.set_val(r, d);
774                Ok(())
775            }
776            InstData::Compare { op, args } => {
777                let r = results.first().ok_or(MirError::Limit)?;
778                if self.fused.get(r.index()).copied().unwrap_or(false) {
779                    return Ok(());
780                }
781                let cc = self.compare(op, args)?;
782                let s = self.emit_def(op::SETCC, &[x86_64::cond(cc).into()])?;
783                let d = self.emit_def(op::MOVZX8, &[s.into()])?;
784                self.set_val(r, d);
785                Ok(())
786            }
787            InstData::Convert {
788                op,
789                policy,
790                to,
791                arg,
792            } => {
793                let r = results.first().ok_or(MirError::Limit)?;
794                let d = self.convert(op, policy, to, arg, Fail::Trap)?;
795                self.set_val(r, d);
796                Ok(())
797            }
798            InstData::Select { cond, args } => {
799                let r = results.first().ok_or(MirError::Limit)?;
800                let _ = self.ty(r)?;
801                let cc = self.condition(cond)?;
802                let (a, b) = (self.val(args[0])?, self.val(args[1])?);
803                // d = cc ? a : b
804                let d =
805                    self.emit_def(op::CMOV64, &[b.into(), a.into(), x86_64::cond(cc).into()])?;
806                self.set_val(r, d);
807                Ok(())
808            }
809            InstData::Fma { .. } => self.unsupported(
810                "fma is floating point, which waits on isa-lang's SSE forms (ISSUES P11)",
811            ),
812            InstData::Call(call) => self.call(&call, results),
813            InstData::Safepoint { gc } => self.safepoint(&gc, results),
814            other => self.memory(&other, results),
815        }
816    }
817
818    /// The condition code that holds when IR boolean `cond` is true, with
819    /// the flags set: a fused compare, or a test of the boolean.
820    pub(crate) fn condition(&mut self, cond: Value) -> Result<isa_lang::x86_64::Cond> {
821        let c = self.irf.resolve(cond);
822        if self.fused.get(c.index()).copied().unwrap_or(false) {
823            if let Some(inst) = self.irf.defining_inst(c) {
824                if let Some(InstData::Compare { op, args }) = self.irf.inst(inst) {
825                    let (op, args) = (*op, *args);
826                    return self.compare(op, args);
827                }
828            }
829        }
830        let r = self.val(c)?;
831        let _ = self.emit(op::TEST32, &[r.into(), r.into()])?;
832        Ok(isa_lang::x86_64::Cond::Ne)
833    }
834}
835
836/// The first integer argument register of a convention.
837pub(crate) fn first_int_arg(abi: &Abi) -> Result<PReg> {
838    abi.args()
839        .int()
840        .first()
841        .copied()
842        .ok_or(IselError::Mir(MirError::Limit))
843}
844
845/// The reachable blocks of an IR function in reverse postorder.
846fn ir_rpo(f: &ir_lang::Function) -> Vec<IBlock> {
847    let n = f.block_count();
848    let mut seen = vec![false; n];
849    let mut post = Vec::new();
850    let entry = f.entry();
851    if entry.index() >= n {
852        return post;
853    }
854    let mut stack: Vec<(IBlock, Vec<IBlock>, usize)> = Vec::new();
855    let succs = |b: IBlock| -> Vec<IBlock> {
856        f.terminator(b)
857            .map(|t| t.successors().map(|c| c.block).collect())
858            .unwrap_or_default()
859    };
860    seen[entry.index()] = true;
861    stack.push((entry, succs(entry), 0));
862    while let Some(top) = stack.last_mut() {
863        if let Some(&s) = top.1.get(top.2) {
864            top.2 += 1;
865            if s.index() < n && !seen[s.index()] {
866                seen[s.index()] = true;
867                let next = succs(s);
868                stack.push((s, next, 0));
869            }
870        } else {
871            post.push(top.0);
872            let _ = stack.pop();
873        }
874    }
875    post.reverse();
876    post
877}