Skip to main content

ir_lang/
inst.rs

1//! Instructions, terminators, and the pieces they are built from.
2
3use alloc::boxed::Box;
4use alloc::vec::Vec;
5use core::fmt;
6
7use crate::entity::{AggId, Block, FuncId, GlobalId, SigId, StackSlot, Value};
8use crate::ops::{BinaryOp, CmpOp, ConvOp, Policy, UnaryOp};
9use crate::types::{MemType, Type};
10
11/// Alignment and volatility of a memory access.
12///
13/// `align` is the alignment the access may assume, in bytes (a power of two, at most
14/// 2^29). An access whose address is not a multiple of it is undefined behaviour
15/// (the reference interpreter reports it). A volatile access is never removed,
16/// duplicated, merged, or reordered with other volatile accesses.
17///
18/// # Examples
19///
20/// ```
21/// use ir_lang::MemFlags;
22///
23/// let f = MemFlags::new(8).volatile();
24/// assert_eq!((f.align, f.volatile), (8, true));
25/// assert_eq!(MemFlags::new(4).to_string(), "align 4");
26/// ```
27#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
28pub struct MemFlags {
29    /// Assumed alignment in bytes (power of two).
30    pub align: u32,
31    /// Whether the access is volatile.
32    pub volatile: bool,
33}
34
35impl MemFlags {
36    /// The largest alignment an access may claim.
37    pub const MAX_ALIGN: u32 = 1 << 29;
38
39    /// A non-volatile access with the given alignment.
40    ///
41    /// # Examples
42    ///
43    /// ```
44    /// use ir_lang::MemFlags;
45    ///
46    /// assert!(!MemFlags::new(1).volatile);
47    /// ```
48    #[must_use]
49    pub const fn new(align: u32) -> MemFlags {
50        MemFlags {
51            align,
52            volatile: false,
53        }
54    }
55
56    /// The same flags, volatile.
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// use ir_lang::MemFlags;
62    ///
63    /// assert!(MemFlags::new(1).volatile().volatile);
64    /// ```
65    #[must_use]
66    pub const fn volatile(mut self) -> MemFlags {
67        self.volatile = true;
68        self
69    }
70}
71
72impl fmt::Display for MemFlags {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(f, "align {}", self.align)?;
75        if self.volatile {
76            f.write_str(" volatile")?;
77        }
78        Ok(())
79    }
80}
81
82/// An atomic memory ordering (C++20), mirroring HIR v2's `Intrinsic` orderings.
83///
84/// Loads are not `Release`/`AcqRel`, stores are not `Acquire`/`AcqRel`, fences are
85/// not `Relaxed`, and a compare-exchange failure ordering is not `Release`/`AcqRel`
86/// and not stronger than its success ordering.
87///
88/// # Examples
89///
90/// ```
91/// use ir_lang::MemOrder;
92///
93/// assert_eq!(MemOrder::AcqRel.name(), "acq_rel");
94/// assert!(MemOrder::SeqCst.strength() > MemOrder::Acquire.strength());
95/// ```
96#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
97#[non_exhaustive]
98pub enum MemOrder {
99    /// No ordering beyond atomicity.
100    Relaxed,
101    /// Acquire.
102    Acquire,
103    /// Release.
104    Release,
105    /// Acquire and release.
106    AcqRel,
107    /// Sequentially consistent.
108    SeqCst,
109}
110
111impl MemOrder {
112    /// Every ordering, in a fixed order.
113    pub const ALL: [MemOrder; 5] = [
114        MemOrder::Relaxed,
115        MemOrder::Acquire,
116        MemOrder::Release,
117        MemOrder::AcqRel,
118        MemOrder::SeqCst,
119    ];
120
121    /// The textual name.
122    ///
123    /// # Examples
124    ///
125    /// ```
126    /// use ir_lang::MemOrder;
127    ///
128    /// assert_eq!(MemOrder::SeqCst.name(), "seq_cst");
129    /// ```
130    #[must_use]
131    pub const fn name(self) -> &'static str {
132        match self {
133            MemOrder::Relaxed => "relaxed",
134            MemOrder::Acquire => "acquire",
135            MemOrder::Release => "release",
136            MemOrder::AcqRel => "acq_rel",
137            MemOrder::SeqCst => "seq_cst",
138        }
139    }
140
141    /// Looks an ordering up by its textual name.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use ir_lang::MemOrder;
147    ///
148    /// assert_eq!(MemOrder::from_name("release"), Some(MemOrder::Release));
149    /// ```
150    #[must_use]
151    pub fn from_name(name: &str) -> Option<MemOrder> {
152        MemOrder::ALL.iter().copied().find(|o| o.name() == name)
153    }
154
155    /// The strength used for the "failure not stronger than success" rule:
156    /// relaxed 0, acquire and release 1, acq_rel 2, seq_cst 3.
157    ///
158    /// # Examples
159    ///
160    /// ```
161    /// use ir_lang::MemOrder;
162    ///
163    /// assert_eq!(MemOrder::Relaxed.strength(), 0);
164    /// ```
165    #[must_use]
166    pub const fn strength(self) -> u8 {
167        match self {
168            MemOrder::Relaxed => 0,
169            MemOrder::Acquire | MemOrder::Release => 1,
170            MemOrder::AcqRel => 2,
171            MemOrder::SeqCst => 3,
172        }
173    }
174}
175
176impl fmt::Display for MemOrder {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        f.write_str(self.name())
179    }
180}
181
182/// The operation of an atomic read-modify-write. Signedness (for `min`/`max`)
183/// comes from the value type; `add`/`sub` wrap.
184///
185/// # Examples
186///
187/// ```
188/// use ir_lang::RmwOp;
189///
190/// assert_eq!(RmwOp::from_name("xchg"), Some(RmwOp::Xchg));
191/// ```
192#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
193#[non_exhaustive]
194pub enum RmwOp {
195    /// Store the operand; return the old value.
196    Xchg,
197    /// Wrapping add.
198    Add,
199    /// Wrapping subtract.
200    Sub,
201    /// Bitwise and.
202    And,
203    /// Bitwise or.
204    Or,
205    /// Bitwise xor.
206    Xor,
207    /// Bitwise not-and: `!(old & operand)`.
208    Nand,
209    /// Minimum (signedness from the type).
210    Min,
211    /// Maximum (signedness from the type).
212    Max,
213}
214
215impl RmwOp {
216    /// Every operation, in a fixed order.
217    pub const ALL: [RmwOp; 9] = [
218        RmwOp::Xchg,
219        RmwOp::Add,
220        RmwOp::Sub,
221        RmwOp::And,
222        RmwOp::Or,
223        RmwOp::Xor,
224        RmwOp::Nand,
225        RmwOp::Min,
226        RmwOp::Max,
227    ];
228
229    /// The textual name.
230    ///
231    /// # Examples
232    ///
233    /// ```
234    /// use ir_lang::RmwOp;
235    ///
236    /// assert_eq!(RmwOp::Nand.name(), "nand");
237    /// ```
238    #[must_use]
239    pub const fn name(self) -> &'static str {
240        match self {
241            RmwOp::Xchg => "xchg",
242            RmwOp::Add => "add",
243            RmwOp::Sub => "sub",
244            RmwOp::And => "and",
245            RmwOp::Or => "or",
246            RmwOp::Xor => "xor",
247            RmwOp::Nand => "nand",
248            RmwOp::Min => "min",
249            RmwOp::Max => "max",
250        }
251    }
252
253    /// Looks an operation up by its textual name.
254    ///
255    /// # Examples
256    ///
257    /// ```
258    /// use ir_lang::RmwOp;
259    ///
260    /// assert_eq!(RmwOp::from_name("max"), Some(RmwOp::Max));
261    /// ```
262    #[must_use]
263    pub fn from_name(name: &str) -> Option<RmwOp> {
264        RmwOp::ALL.iter().copied().find(|o| o.name() == name)
265    }
266}
267
268/// Attributes of a function declaration or a call site, as a bit set.
269///
270/// `NOUNWIND`, `NORETURN`, and `NOGC` are checked by the validator (see the
271/// `specs/IR.md` rules); the others are optimization hints.
272///
273/// # Examples
274///
275/// ```
276/// use ir_lang::Attrs;
277///
278/// let a = Attrs::NOUNWIND | Attrs::COLD;
279/// assert!(a.contains(Attrs::NOUNWIND));
280/// assert!(!a.contains(Attrs::NOGC));
281/// assert_eq!(a.to_string(), "[nounwind, cold]");
282/// assert_eq!(Attrs::NONE.to_string(), "");
283/// ```
284#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
285pub struct Attrs(u16);
286
287impl Attrs {
288    /// No attributes.
289    pub const NONE: Attrs = Attrs(0);
290    /// Never unwinds. A `nounwind` function contains no `resume`, and every call in
291    /// it is `nounwind` (at the site or by the callee's declaration) or an `invoke`.
292    pub const NOUNWIND: Attrs = Attrs(1);
293    /// Never returns normally. A `noreturn` function contains no `ret`.
294    pub const NORETURN: Attrs = Attrs(1 << 1);
295    /// Never collects garbage: a call to it is not a safepoint. A `nogc` function
296    /// contains no safepoint.
297    pub const NOGC: Attrs = Attrs(1 << 2);
298    /// Reads but never writes memory visible to the caller (hint).
299    pub const READONLY: Attrs = Attrs(1 << 3);
300    /// Neither reads nor writes memory visible to the caller (hint).
301    pub const READNONE: Attrs = Attrs(1 << 4);
302    /// Rarely executed (hint).
303    pub const COLD: Attrs = Attrs(1 << 5);
304    /// Do not inline (hint).
305    pub const NOINLINE: Attrs = Attrs(1 << 6);
306    /// Inline whenever possible (hint).
307    pub const INLINE: Attrs = Attrs(1 << 7);
308
309    const NAMES: [(Attrs, &'static str); 8] = [
310        (Attrs::NOUNWIND, "nounwind"),
311        (Attrs::NORETURN, "noreturn"),
312        (Attrs::NOGC, "nogc"),
313        (Attrs::READONLY, "readonly"),
314        (Attrs::READNONE, "readnone"),
315        (Attrs::COLD, "cold"),
316        (Attrs::NOINLINE, "noinline"),
317        (Attrs::INLINE, "inline"),
318    ];
319
320    /// Whether every attribute of `other` is set.
321    ///
322    /// # Examples
323    ///
324    /// ```
325    /// use ir_lang::Attrs;
326    ///
327    /// assert!(Attrs::COLD.contains(Attrs::NONE));
328    /// ```
329    #[must_use]
330    pub const fn contains(self, other: Attrs) -> bool {
331        self.0 & other.0 == other.0
332    }
333
334    /// The union of two sets.
335    ///
336    /// # Examples
337    ///
338    /// ```
339    /// use ir_lang::Attrs;
340    ///
341    /// assert_eq!(Attrs::COLD.union(Attrs::INLINE), Attrs::COLD | Attrs::INLINE);
342    /// ```
343    #[must_use]
344    pub const fn union(self, other: Attrs) -> Attrs {
345        Attrs(self.0 | other.0)
346    }
347
348    /// Whether no attribute is set.
349    ///
350    /// # Examples
351    ///
352    /// ```
353    /// use ir_lang::Attrs;
354    ///
355    /// assert!(Attrs::NONE.is_empty());
356    /// ```
357    #[must_use]
358    pub const fn is_empty(self) -> bool {
359        self.0 == 0
360    }
361
362    /// Looks one attribute up by its textual name.
363    ///
364    /// # Examples
365    ///
366    /// ```
367    /// use ir_lang::Attrs;
368    ///
369    /// assert_eq!(Attrs::from_name("nogc"), Some(Attrs::NOGC));
370    /// ```
371    #[must_use]
372    pub fn from_name(name: &str) -> Option<Attrs> {
373        Attrs::NAMES
374            .iter()
375            .find(|(_, n)| *n == name)
376            .map(|(a, _)| *a)
377    }
378
379    /// The raw bits (for side tables).
380    ///
381    /// # Examples
382    ///
383    /// ```
384    /// use ir_lang::Attrs;
385    ///
386    /// assert_eq!(Attrs::NOUNWIND.bits(), 1);
387    /// ```
388    #[must_use]
389    pub const fn bits(self) -> u16 {
390        self.0
391    }
392}
393
394impl core::ops::BitOr for Attrs {
395    type Output = Attrs;
396
397    fn bitor(self, rhs: Attrs) -> Attrs {
398        self.union(rhs)
399    }
400}
401
402impl core::ops::BitOrAssign for Attrs {
403    fn bitor_assign(&mut self, rhs: Attrs) {
404        *self = self.union(rhs);
405    }
406}
407
408impl fmt::Display for Attrs {
409    /// Prints `[name, ...]`, or nothing for the empty set.
410    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411        if self.is_empty() {
412            return Ok(());
413        }
414        let mut sep = "[";
415        for (attr, name) in Attrs::NAMES {
416            if self.contains(attr) {
417                write!(f, "{sep}{name}")?;
418                sep = ", ";
419            }
420        }
421        f.write_str("]")
422    }
423}
424
425/// What a call calls.
426///
427/// # Examples
428///
429/// ```
430/// use ir_lang::{CallTarget, FuncId};
431///
432/// let t = CallTarget::Direct(FuncId::from_u32(0));
433/// assert!(matches!(t, CallTarget::Direct(_)));
434/// ```
435#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
436#[non_exhaustive]
437pub enum CallTarget {
438    /// A function declared in the module (defined or imported).
439    Direct(FuncId),
440    /// The function whose address is `callee` (a `ptr`), which must have signature
441    /// `sig`.
442    Indirect {
443        /// The callee's signature.
444        sig: SigId,
445        /// The callee's address.
446        callee: Value,
447    },
448}
449
450/// A call: target, arguments, the GC references it relocates, and call-site
451/// attributes.
452///
453/// A call is a **safepoint** unless it is `nogc` (at the site or by the callee's
454/// declaration). Every `ref` value (and every pointer derived from one) that is
455/// needed after a safepoint must be listed in `gc`; the call then defines one
456/// relocated `ref` per entry, after its return values. Using the old value after
457/// the safepoint is invalid. A `nogc` call must have an empty `gc` list.
458///
459/// # Examples
460///
461/// ```
462/// use ir_lang::{Attrs, Call, FuncId, Value};
463///
464/// let c = Call::direct(FuncId::from_u32(1), &[Value::from_u32(0)]).with_attrs(Attrs::NOUNWIND);
465/// assert_eq!(c.args.len(), 1);
466/// assert!(c.gc.is_empty());
467/// ```
468#[derive(Clone, PartialEq, Eq, Hash, Debug)]
469pub struct Call {
470    /// What is called.
471    pub target: CallTarget,
472    /// The arguments, matching the callee's parameters.
473    pub args: Vec<Value>,
474    /// The `ref` values relocated by this safepoint.
475    pub gc: Vec<Value>,
476    /// Call-site attributes.
477    pub attrs: Attrs,
478}
479
480impl Call {
481    /// A direct call with no GC list and no attributes.
482    ///
483    /// # Examples
484    ///
485    /// ```
486    /// use ir_lang::{Call, FuncId};
487    ///
488    /// assert!(Call::direct(FuncId::from_u32(0), &[]).args.is_empty());
489    /// ```
490    #[must_use]
491    pub fn direct(func: FuncId, args: &[Value]) -> Call {
492        Call {
493            target: CallTarget::Direct(func),
494            args: args.to_vec(),
495            gc: Vec::new(),
496            attrs: Attrs::NONE,
497        }
498    }
499
500    /// An indirect call through `callee` with signature `sig`.
501    ///
502    /// # Examples
503    ///
504    /// ```
505    /// use ir_lang::{Call, SigId, Value};
506    ///
507    /// let c = Call::indirect(SigId::from_u32(0), Value::from_u32(2), &[]);
508    /// assert!(c.args.is_empty());
509    /// ```
510    #[must_use]
511    pub fn indirect(sig: SigId, callee: Value, args: &[Value]) -> Call {
512        Call {
513            target: CallTarget::Indirect { sig, callee },
514            args: args.to_vec(),
515            gc: Vec::new(),
516            attrs: Attrs::NONE,
517        }
518    }
519
520    /// The same call relocating the given `ref` values.
521    ///
522    /// # Examples
523    ///
524    /// ```
525    /// use ir_lang::{Call, FuncId, Value};
526    ///
527    /// let c = Call::direct(FuncId::from_u32(0), &[]).with_gc(&[Value::from_u32(3)]);
528    /// assert_eq!(c.gc.len(), 1);
529    /// ```
530    #[must_use]
531    pub fn with_gc(mut self, gc: &[Value]) -> Call {
532        self.gc = gc.to_vec();
533        self
534    }
535
536    /// The same call with the given call-site attributes.
537    ///
538    /// # Examples
539    ///
540    /// ```
541    /// use ir_lang::{Attrs, Call, FuncId};
542    ///
543    /// let c = Call::direct(FuncId::from_u32(0), &[]).with_attrs(Attrs::COLD);
544    /// assert!(c.attrs.contains(Attrs::COLD));
545    /// ```
546    #[must_use]
547    pub fn with_attrs(mut self, attrs: Attrs) -> Call {
548        self.attrs = attrs;
549        self
550    }
551
552    /// Calls `f` on every value the call reads (callee, arguments, GC list).
553    pub(crate) fn for_each_operand(&self, mut f: impl FnMut(Value)) {
554        if let CallTarget::Indirect { callee, .. } = self.target {
555            f(callee);
556        }
557        self.args.iter().copied().for_each(&mut f);
558        self.gc.iter().copied().for_each(f);
559    }
560
561    /// Rewrites every value the call reads.
562    pub(crate) fn map_operands(&mut self, mut f: impl FnMut(Value) -> Value) {
563        if let CallTarget::Indirect { callee, .. } = &mut self.target {
564            *callee = f(*callee);
565        }
566        for v in self.args.iter_mut().chain(self.gc.iter_mut()) {
567            *v = f(*v);
568        }
569    }
570}
571
572/// The data of one non-terminator instruction.
573///
574/// Result types are not stored here: each result value carries its type, and the
575/// validator checks it against the type the instruction produces. Instructions with
576/// a policy whose fields include `error` are only valid inside a
577/// [`Terminator::Check`]; as plain instructions they are rejected.
578///
579/// # Examples
580///
581/// ```
582/// use ir_lang::{BinaryOp, InstData, Overflow, Policy, Value};
583///
584/// let add = InstData::Binary {
585///     op: BinaryOp::Add,
586///     policy: Policy::overflow(Overflow::Wrap),
587///     args: [Value::from_u32(0), Value::from_u32(1)],
588/// };
589/// let mut used = Vec::new();
590/// add.for_each_operand(|v| used.push(v));
591/// assert_eq!(used.len(), 2);
592/// assert!(!add.has_side_effects());
593/// ```
594#[derive(Clone, PartialEq, Eq, Hash, Debug)]
595#[non_exhaustive]
596pub enum InstData {
597    /// A constant of a scalar type. `bits` holds the value's bit pattern masked to
598    /// the type's width (integers as two's complement, floats as IEEE bits, `bool`
599    /// as 0/1, `ptr`/`ref` only 0, the null address/reference).
600    Const {
601        /// The constant's type.
602        ty: Type,
603        /// The bit pattern.
604        bits: u64,
605    },
606    /// A unary operation.
607    Unary {
608        /// The operation.
609        op: UnaryOp,
610        /// Its policy.
611        policy: Policy,
612        /// The operand.
613        arg: Value,
614    },
615    /// A binary operation.
616    Binary {
617        /// The operation.
618        op: BinaryOp,
619        /// Its policy.
620        policy: Policy,
621        /// The operands.
622        args: [Value; 2],
623    },
624    /// Fused multiply-add `a * b + c` on floats, correctly rounded once.
625    Fma {
626        /// `a`, `b`, `c`.
627        args: [Value; 3],
628    },
629    /// A comparison giving `bool`.
630    Compare {
631        /// The comparison.
632        op: CmpOp,
633        /// The operands.
634        args: [Value; 2],
635    },
636    /// A conversion to `to`.
637    Convert {
638        /// The conversion.
639        op: ConvOp,
640        /// Its policy.
641        policy: Policy,
642        /// The result type.
643        to: Type,
644        /// The operand.
645        arg: Value,
646    },
647    /// `cond ? args[0] : args[1]`.
648    Select {
649        /// The `bool` condition.
650        cond: Value,
651        /// The values chosen when true and when false.
652        args: [Value; 2],
653    },
654    /// The address of a stack slot of this function.
655    StackAddr {
656        /// The slot.
657        slot: StackSlot,
658    },
659    /// The address of a global.
660    GlobalAddr {
661        /// The global.
662        global: GlobalId,
663    },
664    /// The address of a function (for indirect calls).
665    FuncAddr {
666        /// The function.
667        func: FuncId,
668    },
669    /// `base` plus the offset of field `field` of struct `agg`.
670    FieldAddr {
671        /// The struct's address.
672        base: Value,
673        /// The struct type.
674        agg: AggId,
675        /// The field index.
676        field: u32,
677    },
678    /// `base + index * size(elem)`, wrapping at the address width. A signed index
679    /// is sign-extended, an unsigned one zero-extended.
680    ElemAddr {
681        /// The first element's address.
682        base: Value,
683        /// The element type (its size is the stride).
684        elem: MemType,
685        /// The element index (any integer type).
686        index: Value,
687    },
688    /// `base + offset` bytes, wrapping at the address width.
689    PtrOffset {
690        /// The address.
691        base: Value,
692        /// The byte offset (any integer type; signed is sign-extended).
693        offset: Value,
694    },
695    /// Read a `ty` from `addr`.
696    Load {
697        /// The loaded type.
698        ty: Type,
699        /// The address.
700        addr: Value,
701        /// Alignment and volatility.
702        flags: MemFlags,
703    },
704    /// Write `value` to `addr`.
705    Store {
706        /// The stored value.
707        value: Value,
708        /// The address.
709        addr: Value,
710        /// Alignment and volatility.
711        flags: MemFlags,
712    },
713    /// Atomically read a `ty` (an integer or `ptr`) from naturally aligned `addr`.
714    AtomicLoad {
715        /// The loaded type.
716        ty: Type,
717        /// The address.
718        addr: Value,
719        /// The ordering.
720        order: MemOrder,
721    },
722    /// Atomically write `value` (an integer or `ptr`) to naturally aligned `addr`.
723    AtomicStore {
724        /// The stored value.
725        value: Value,
726        /// The address.
727        addr: Value,
728        /// The ordering.
729        order: MemOrder,
730    },
731    /// Atomically replace the integer at `addr` with `op(old, value)`; gives `old`.
732    AtomicRmw {
733        /// The operation.
734        op: RmwOp,
735        /// The address.
736        addr: Value,
737        /// The operand.
738        value: Value,
739        /// The ordering.
740        order: MemOrder,
741    },
742    /// Atomic compare-exchange; gives the old value and whether it was replaced.
743    CmpXchg {
744        /// The address.
745        addr: Value,
746        /// The expected old value.
747        expected: Value,
748        /// The replacement.
749        new: Value,
750        /// The ordering on success.
751        success: MemOrder,
752        /// The ordering on failure.
753        failure: MemOrder,
754    },
755    /// A memory fence.
756    Fence {
757        /// The ordering (not `relaxed`).
758        order: MemOrder,
759    },
760    /// Copy `len` bytes from `src` to `dst`; the ranges must not overlap.
761    MemCopy {
762        /// Destination address.
763        dst: Value,
764        /// Source address.
765        src: Value,
766        /// Byte count (an unsigned integer).
767        len: Value,
768        /// Alignment of both addresses, and volatility.
769        flags: MemFlags,
770    },
771    /// Copy `len` bytes from `src` to `dst`; the ranges may overlap.
772    MemMove {
773        /// Destination address.
774        dst: Value,
775        /// Source address.
776        src: Value,
777        /// Byte count (an unsigned integer).
778        len: Value,
779        /// Alignment of both addresses, and volatility.
780        flags: MemFlags,
781    },
782    /// Set `len` bytes at `dst` to `byte` (a `u8`).
783    MemSet {
784        /// Destination address.
785        dst: Value,
786        /// The fill byte.
787        byte: Value,
788        /// Byte count (an unsigned integer).
789        len: Value,
790        /// Alignment and volatility.
791        flags: MemFlags,
792    },
793    /// A call. Results: the callee's returns, then one relocated `ref` per `gc`
794    /// entry.
795    Call(Box<Call>),
796    /// An explicit safepoint (a GC poll). Results: one relocated `ref` per `gc`
797    /// entry.
798    Safepoint {
799        /// The `ref` values relocated.
800        gc: Box<[Value]>,
801    },
802}
803
804impl InstData {
805    /// Calls `f` on every value the instruction reads, in operand order.
806    ///
807    /// # Examples
808    ///
809    /// ```
810    /// use ir_lang::{InstData, Value};
811    ///
812    /// let mut n = 0;
813    /// InstData::Fma { args: [Value::from_u32(0); 3] }.for_each_operand(|_| n += 1);
814    /// assert_eq!(n, 3);
815    /// ```
816    pub fn for_each_operand(&self, mut f: impl FnMut(Value)) {
817        match self {
818            InstData::Const { .. }
819            | InstData::StackAddr { .. }
820            | InstData::GlobalAddr { .. }
821            | InstData::FuncAddr { .. }
822            | InstData::Fence { .. } => {}
823            InstData::Unary { arg, .. } | InstData::Convert { arg, .. } => f(*arg),
824            InstData::Binary { args, .. } | InstData::Compare { args, .. } => {
825                f(args[0]);
826                f(args[1]);
827            }
828            InstData::Fma { args } => args.iter().copied().for_each(f),
829            InstData::Select { cond, args } => {
830                f(*cond);
831                f(args[0]);
832                f(args[1]);
833            }
834            InstData::FieldAddr { base, .. } => f(*base),
835            InstData::ElemAddr { base, index, .. } => {
836                f(*base);
837                f(*index);
838            }
839            InstData::PtrOffset { base, offset } => {
840                f(*base);
841                f(*offset);
842            }
843            InstData::Load { addr, .. } | InstData::AtomicLoad { addr, .. } => f(*addr),
844            InstData::Store { value, addr, .. } | InstData::AtomicStore { value, addr, .. } => {
845                f(*value);
846                f(*addr);
847            }
848            InstData::AtomicRmw { addr, value, .. } => {
849                f(*addr);
850                f(*value);
851            }
852            InstData::CmpXchg {
853                addr,
854                expected,
855                new,
856                ..
857            } => {
858                f(*addr);
859                f(*expected);
860                f(*new);
861            }
862            InstData::MemCopy { dst, src, len, .. } | InstData::MemMove { dst, src, len, .. } => {
863                f(*dst);
864                f(*src);
865                f(*len);
866            }
867            InstData::MemSet { dst, byte, len, .. } => {
868                f(*dst);
869                f(*byte);
870                f(*len);
871            }
872            InstData::Call(call) => call.for_each_operand(f),
873            InstData::Safepoint { gc } => gc.iter().copied().for_each(f),
874        }
875    }
876
877    /// Rewrites every value the instruction reads.
878    ///
879    /// # Examples
880    ///
881    /// ```
882    /// use ir_lang::{InstData, UnaryOp, Policy, Value};
883    ///
884    /// let mut i = InstData::Unary { op: UnaryOp::Not, policy: Policy::NONE, arg: Value::from_u32(1) };
885    /// i.map_operands(|v| Value::from_u32(v.as_u32() + 10));
886    /// assert_eq!(i, InstData::Unary { op: UnaryOp::Not, policy: Policy::NONE, arg: Value::from_u32(11) });
887    /// ```
888    pub fn map_operands(&mut self, mut f: impl FnMut(Value) -> Value) {
889        match self {
890            InstData::Const { .. }
891            | InstData::StackAddr { .. }
892            | InstData::GlobalAddr { .. }
893            | InstData::FuncAddr { .. }
894            | InstData::Fence { .. } => {}
895            InstData::Unary { arg, .. } | InstData::Convert { arg, .. } => *arg = f(*arg),
896            InstData::Binary { args, .. } | InstData::Compare { args, .. } => {
897                args[0] = f(args[0]);
898                args[1] = f(args[1]);
899            }
900            InstData::Fma { args } => {
901                for a in args.iter_mut() {
902                    *a = f(*a);
903                }
904            }
905            InstData::Select { cond, args } => {
906                *cond = f(*cond);
907                args[0] = f(args[0]);
908                args[1] = f(args[1]);
909            }
910            InstData::FieldAddr { base, .. } => *base = f(*base),
911            InstData::ElemAddr { base, index, .. } => {
912                *base = f(*base);
913                *index = f(*index);
914            }
915            InstData::PtrOffset { base, offset } => {
916                *base = f(*base);
917                *offset = f(*offset);
918            }
919            InstData::Load { addr, .. } | InstData::AtomicLoad { addr, .. } => *addr = f(*addr),
920            InstData::Store { value, addr, .. } | InstData::AtomicStore { value, addr, .. } => {
921                *value = f(*value);
922                *addr = f(*addr);
923            }
924            InstData::AtomicRmw { addr, value, .. } => {
925                *addr = f(*addr);
926                *value = f(*value);
927            }
928            InstData::CmpXchg {
929                addr,
930                expected,
931                new,
932                ..
933            } => {
934                *addr = f(*addr);
935                *expected = f(*expected);
936                *new = f(*new);
937            }
938            InstData::MemCopy { dst, src, len, .. } | InstData::MemMove { dst, src, len, .. } => {
939                *dst = f(*dst);
940                *src = f(*src);
941                *len = f(*len);
942            }
943            InstData::MemSet { dst, byte, len, .. } => {
944                *dst = f(*dst);
945                *byte = f(*byte);
946                *len = f(*len);
947            }
948            InstData::Call(call) => call.map_operands(f),
949            InstData::Safepoint { gc } => {
950                for v in gc.iter_mut() {
951                    *v = f(*v);
952                }
953            }
954        }
955    }
956
957    /// Whether the instruction may do anything besides computing its results:
958    /// write memory, read volatile memory, synchronize, call, poll the GC, or trap
959    /// under a `trap` policy. An instruction without side effects whose results are
960    /// unused can be removed.
961    ///
962    /// # Examples
963    ///
964    /// ```
965    /// use ir_lang::{BinaryOp, InstData, Overflow, Policy, Value};
966    ///
967    /// let v = Value::from_u32(0);
968    /// let trapping = InstData::Binary { op: BinaryOp::Add, policy: Policy::overflow(Overflow::Trap), args: [v, v] };
969    /// assert!(trapping.has_side_effects());
970    /// ```
971    #[must_use]
972    pub fn has_side_effects(&self) -> bool {
973        use crate::ops::{DivZero, Overflow};
974        let traps = |p: &Policy| {
975            matches!(p.overflow, Some(Overflow::Trap)) || matches!(p.div_zero, Some(DivZero::Trap))
976        };
977        match self {
978            InstData::Unary { policy, .. }
979            | InstData::Binary { policy, .. }
980            | InstData::Convert { policy, .. } => traps(policy) || policy.can_error(),
981            InstData::Load { flags, .. } => flags.volatile,
982            InstData::Store { .. }
983            | InstData::AtomicLoad { .. }
984            | InstData::AtomicStore { .. }
985            | InstData::AtomicRmw { .. }
986            | InstData::CmpXchg { .. }
987            | InstData::Fence { .. }
988            | InstData::MemCopy { .. }
989            | InstData::MemMove { .. }
990            | InstData::MemSet { .. }
991            | InstData::Call(_)
992            | InstData::Safepoint { .. } => true,
993            InstData::Const { .. }
994            | InstData::Fma { .. }
995            | InstData::Compare { .. }
996            | InstData::Select { .. }
997            | InstData::StackAddr { .. }
998            | InstData::GlobalAddr { .. }
999            | InstData::FuncAddr { .. }
1000            | InstData::FieldAddr { .. }
1001            | InstData::ElemAddr { .. }
1002            | InstData::PtrOffset { .. } => false,
1003        }
1004    }
1005
1006    /// The textual opcode of the instruction (`add`, `load`, `call`, ...).
1007    ///
1008    /// # Examples
1009    ///
1010    /// ```
1011    /// use ir_lang::{InstData, Type};
1012    ///
1013    /// assert_eq!(InstData::Const { ty: Type::I8, bits: 0 }.opcode(), "const");
1014    /// ```
1015    #[must_use]
1016    pub fn opcode(&self) -> &'static str {
1017        match self {
1018            InstData::Const { .. } => "const",
1019            InstData::Unary { op, .. } => op.name(),
1020            InstData::Binary { op, .. } => op.name(),
1021            InstData::Fma { .. } => "fma",
1022            InstData::Compare { op, .. } => op.name(),
1023            InstData::Convert { op, .. } => op.name(),
1024            InstData::Select { .. } => "select",
1025            InstData::StackAddr { .. } => "stack_addr",
1026            InstData::GlobalAddr { .. } => "global_addr",
1027            InstData::FuncAddr { .. } => "func_addr",
1028            InstData::FieldAddr { .. } => "field_addr",
1029            InstData::ElemAddr { .. } => "elem_addr",
1030            InstData::PtrOffset { .. } => "ptr_offset",
1031            InstData::Load { .. } => "load",
1032            InstData::Store { .. } => "store",
1033            InstData::AtomicLoad { .. } => "atomic_load",
1034            InstData::AtomicStore { .. } => "atomic_store",
1035            InstData::AtomicRmw { .. } => "atomic_rmw",
1036            InstData::CmpXchg { .. } => "cmpxchg",
1037            InstData::Fence { .. } => "fence",
1038            InstData::MemCopy { .. } => "memcpy",
1039            InstData::MemMove { .. } => "memmove",
1040            InstData::MemSet { .. } => "memset",
1041            InstData::Call(call) => match call.target {
1042                CallTarget::Direct(_) => "call",
1043                CallTarget::Indirect { .. } => "call_indirect",
1044            },
1045            InstData::Safepoint { .. } => "safepoint",
1046        }
1047    }
1048}
1049
1050/// An argument passed along a control-flow edge to a block parameter.
1051///
1052/// Besides ordinary values, the edges of `invoke` and `check` terminators can pass
1053/// what the terminator itself produces, which is not an SSA value before the edge
1054/// is taken:
1055///
1056/// | Argument | Valid on | Meaning |
1057/// |---|---|---|
1058/// | `Result(i)` | `invoke` normal edge; `check` normal edge (`i = 0`) | the `i`-th result (returns, then relocated refs) |
1059/// | `Result(i)` | `invoke` unwind edge, `i >= returns` | a relocated ref |
1060/// | `Exception` | `invoke` unwind edge | the exception payload (`ptr`) |
1061/// | `ErrorCode` | `check` error edge | the OPS error code (`u32`) |
1062///
1063/// # Examples
1064///
1065/// ```
1066/// use ir_lang::{BlockArg, Value};
1067///
1068/// let a: BlockArg = Value::from_u32(3).into();
1069/// assert_eq!(a, BlockArg::Value(Value::from_u32(3)));
1070/// assert_eq!(BlockArg::Result(1).to_string(), "res1");
1071/// ```
1072#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
1073#[non_exhaustive]
1074pub enum BlockArg {
1075    /// An SSA value.
1076    Value(Value),
1077    /// A result of the `invoke` or `check` terminator.
1078    Result(u32),
1079    /// The exception payload of an `invoke`'s unwind edge.
1080    Exception,
1081    /// The error code of a `check`'s error edge.
1082    ErrorCode,
1083}
1084
1085impl From<Value> for BlockArg {
1086    fn from(v: Value) -> Self {
1087        BlockArg::Value(v)
1088    }
1089}
1090
1091impl fmt::Display for BlockArg {
1092    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1093        match self {
1094            BlockArg::Value(v) => write!(f, "{v}"),
1095            BlockArg::Result(i) => write!(f, "res{i}"),
1096            BlockArg::Exception => f.write_str("exn"),
1097            BlockArg::ErrorCode => f.write_str("err"),
1098        }
1099    }
1100}
1101
1102/// A control-flow edge: a target block and the arguments for its parameters.
1103///
1104/// # Examples
1105///
1106/// ```
1107/// use ir_lang::{Block, BlockCall, Value};
1108///
1109/// let c = BlockCall::new(Block::from_u32(2), &[Value::from_u32(0)]);
1110/// assert_eq!(c.to_string(), "b2(v0)");
1111/// assert_eq!(BlockCall::new(Block::from_u32(1), &[]).to_string(), "b1");
1112/// ```
1113#[derive(Clone, PartialEq, Eq, Hash, Debug)]
1114pub struct BlockCall {
1115    /// The target block.
1116    pub block: Block,
1117    /// One argument per target parameter.
1118    pub args: Vec<BlockArg>,
1119}
1120
1121impl BlockCall {
1122    /// An edge passing SSA values.
1123    ///
1124    /// # Examples
1125    ///
1126    /// ```
1127    /// use ir_lang::{Block, BlockCall};
1128    ///
1129    /// assert!(BlockCall::new(Block::from_u32(0), &[]).args.is_empty());
1130    /// ```
1131    #[must_use]
1132    pub fn new(block: Block, args: &[Value]) -> BlockCall {
1133        BlockCall {
1134            block,
1135            args: args.iter().copied().map(BlockArg::Value).collect(),
1136        }
1137    }
1138
1139    /// An edge passing arbitrary [`BlockArg`]s.
1140    ///
1141    /// # Examples
1142    ///
1143    /// ```
1144    /// use ir_lang::{Block, BlockArg, BlockCall};
1145    ///
1146    /// let c = BlockCall::with_args(Block::from_u32(1), &[BlockArg::Exception]);
1147    /// assert_eq!(c.to_string(), "b1(exn)");
1148    /// ```
1149    #[must_use]
1150    pub fn with_args(block: Block, args: &[BlockArg]) -> BlockCall {
1151        BlockCall {
1152            block,
1153            args: args.to_vec(),
1154        }
1155    }
1156
1157    fn for_each_value(&self, mut f: impl FnMut(Value)) {
1158        for arg in &self.args {
1159            if let BlockArg::Value(v) = arg {
1160                f(*v);
1161            }
1162        }
1163    }
1164
1165    fn map_values(&mut self, mut f: impl FnMut(Value) -> Value) {
1166        for arg in &mut self.args {
1167            if let BlockArg::Value(v) = arg {
1168                *v = f(*v);
1169            }
1170        }
1171    }
1172}
1173
1174impl fmt::Display for BlockCall {
1175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1176        write!(f, "{}", self.block)?;
1177        if !self.args.is_empty() {
1178            f.write_str("(")?;
1179            for (i, a) in self.args.iter().enumerate() {
1180                if i > 0 {
1181                    f.write_str(", ")?;
1182                }
1183                write!(f, "{a}")?;
1184            }
1185            f.write_str(")")?;
1186        }
1187        Ok(())
1188    }
1189}
1190
1191/// One case of a [`Terminator::Switch`].
1192///
1193/// # Examples
1194///
1195/// ```
1196/// use ir_lang::{Block, BlockCall, SwitchCase};
1197///
1198/// let c = SwitchCase { value: 7, dest: BlockCall::new(Block::from_u32(1), &[]) };
1199/// assert_eq!(c.value, 7);
1200/// ```
1201#[derive(Clone, PartialEq, Eq, Hash, Debug)]
1202pub struct SwitchCase {
1203    /// The matched value, as bits masked to the scrutinee's width.
1204    pub value: u64,
1205    /// Where control goes when the scrutinee equals `value`.
1206    pub dest: BlockCall,
1207}
1208
1209/// The instruction that ends a block and says where control goes next.
1210///
1211/// # Examples
1212///
1213/// ```
1214/// use ir_lang::{Block, BlockCall, Terminator};
1215///
1216/// let t = Terminator::Jump(BlockCall::new(Block::from_u32(1), &[]));
1217/// assert_eq!(t.successors().count(), 1);
1218/// assert!(Terminator::Unreachable.successors().next().is_none());
1219/// ```
1220#[derive(Clone, PartialEq, Eq, Hash, Debug)]
1221#[non_exhaustive]
1222pub enum Terminator {
1223    /// Unconditional jump.
1224    Jump(BlockCall),
1225    /// Two-way branch on a `bool`.
1226    Branch {
1227        /// The condition.
1228        cond: Value,
1229        /// Taken when `cond` is true.
1230        then_dest: BlockCall,
1231        /// Taken when `cond` is false.
1232        else_dest: BlockCall,
1233    },
1234    /// Multi-way branch on an integer; case values are distinct.
1235    Switch {
1236        /// The scrutinee.
1237        value: Value,
1238        /// The cases.
1239        cases: Vec<SwitchCase>,
1240        /// Taken when no case matches.
1241        default: BlockCall,
1242    },
1243    /// Return from the function with values matching its signature's returns.
1244    Return {
1245        /// The returned values.
1246        values: Vec<Value>,
1247    },
1248    /// Control never reaches here; reaching it is undefined behaviour.
1249    Unreachable,
1250    /// Abort the program deterministically with `code` (OPS §6 codes 1–5 for
1251    /// operation errors; other codes are the language's own).
1252    Trap {
1253        /// The trap code.
1254        code: u32,
1255    },
1256    /// A tail call: the callee's returns become this function's returns. The
1257    /// callee's convention and returns must equal this function's; `gc` must be
1258    /// empty (the frame is gone).
1259    TailCall(Box<Call>),
1260    /// A call with an exception edge: on normal return control goes to `normal`,
1261    /// if the callee unwinds it goes to `unwind`, a landing pad.
1262    Invoke {
1263        /// The call.
1264        call: Box<Call>,
1265        /// The normal-return edge.
1266        normal: BlockCall,
1267        /// The unwind edge.
1268        unwind: BlockCall,
1269    },
1270    /// An operation whose policy has an `error` field (or `char_from_u32`): on
1271    /// success control goes to `normal` (its result is `Result(0)`); on failure to
1272    /// `error` with the error code as `ErrorCode`.
1273    Check {
1274        /// The operation: a `Unary`, `Binary`, or `Convert` instruction.
1275        op: Box<InstData>,
1276        /// The success edge.
1277        normal: BlockCall,
1278        /// The error edge.
1279        error: BlockCall,
1280    },
1281    /// Unwind out of this function with `payload` (a `ptr`): rethrows after
1282    /// cleanup in a landing pad, or raises an exception the runtime prepared.
1283    Resume {
1284        /// The exception payload.
1285        payload: Value,
1286    },
1287}
1288
1289/// An iterator over the outgoing edges of a terminator.
1290#[derive(Debug)]
1291pub struct Successors<'a> {
1292    first: [Option<&'a BlockCall>; 2],
1293    cases: core::slice::Iter<'a, SwitchCase>,
1294}
1295
1296impl<'a> Iterator for Successors<'a> {
1297    type Item = &'a BlockCall;
1298
1299    fn next(&mut self) -> Option<&'a BlockCall> {
1300        if let Some(c) = self.cases.next() {
1301            return Some(&c.dest);
1302        }
1303        if let Some(c) = self.first[0].take() {
1304            return Some(c);
1305        }
1306        self.first[1].take()
1307    }
1308}
1309
1310impl Terminator {
1311    /// The outgoing edges, in a fixed order (switch cases first, then the
1312    /// default; `then` before `else`; `normal` before `unwind`/`error`).
1313    ///
1314    /// # Examples
1315    ///
1316    /// ```
1317    /// use ir_lang::{Block, BlockCall, Terminator, Value};
1318    ///
1319    /// let t = Terminator::Branch {
1320    ///     cond: Value::from_u32(0),
1321    ///     then_dest: BlockCall::new(Block::from_u32(1), &[]),
1322    ///     else_dest: BlockCall::new(Block::from_u32(2), &[]),
1323    /// };
1324    /// let targets: Vec<_> = t.successors().map(|c| c.block.index()).collect();
1325    /// assert_eq!(targets, [1, 2]);
1326    /// ```
1327    #[must_use]
1328    pub fn successors(&self) -> Successors<'_> {
1329        let none: [Option<&BlockCall>; 2] = [None, None];
1330        match self {
1331            Terminator::Jump(c) => Successors {
1332                first: [Some(c), None],
1333                cases: [].iter(),
1334            },
1335            Terminator::Branch {
1336                then_dest,
1337                else_dest,
1338                ..
1339            } => Successors {
1340                first: [Some(then_dest), Some(else_dest)],
1341                cases: [].iter(),
1342            },
1343            Terminator::Switch { cases, default, .. } => Successors {
1344                first: [Some(default), None],
1345                cases: cases.iter(),
1346            },
1347            Terminator::Invoke { normal, unwind, .. } => Successors {
1348                first: [Some(normal), Some(unwind)],
1349                cases: [].iter(),
1350            },
1351            Terminator::Check { normal, error, .. } => Successors {
1352                first: [Some(normal), Some(error)],
1353                cases: [].iter(),
1354            },
1355            Terminator::Return { .. }
1356            | Terminator::Unreachable
1357            | Terminator::Trap { .. }
1358            | Terminator::TailCall(_)
1359            | Terminator::Resume { .. } => Successors {
1360                first: none,
1361                cases: [].iter(),
1362            },
1363        }
1364    }
1365
1366    /// Mutable access to the outgoing edges, in the same order as
1367    /// [`successors`](Terminator::successors).
1368    ///
1369    /// # Examples
1370    ///
1371    /// ```
1372    /// use ir_lang::{Block, BlockCall, Terminator};
1373    ///
1374    /// let mut t = Terminator::Jump(BlockCall::new(Block::from_u32(1), &[]));
1375    /// t.for_each_successor_mut(|c| c.block = Block::from_u32(5));
1376    /// assert_eq!(t.successors().next().unwrap().block, Block::from_u32(5));
1377    /// ```
1378    pub fn for_each_successor_mut(&mut self, mut f: impl FnMut(&mut BlockCall)) {
1379        match self {
1380            Terminator::Jump(c) => f(c),
1381            Terminator::Branch {
1382                then_dest,
1383                else_dest,
1384                ..
1385            } => {
1386                f(then_dest);
1387                f(else_dest);
1388            }
1389            Terminator::Switch { cases, default, .. } => {
1390                for c in cases {
1391                    f(&mut c.dest);
1392                }
1393                f(default);
1394            }
1395            Terminator::Invoke { normal, unwind, .. } => {
1396                f(normal);
1397                f(unwind);
1398            }
1399            Terminator::Check { normal, error, .. } => {
1400                f(normal);
1401                f(error);
1402            }
1403            Terminator::Return { .. }
1404            | Terminator::Unreachable
1405            | Terminator::Trap { .. }
1406            | Terminator::TailCall(_)
1407            | Terminator::Resume { .. } => {}
1408        }
1409    }
1410
1411    /// Calls `f` on every SSA value the terminator reads, including the values
1412    /// passed along its edges.
1413    ///
1414    /// # Examples
1415    ///
1416    /// ```
1417    /// use ir_lang::{Terminator, Value};
1418    ///
1419    /// let mut n = 0;
1420    /// Terminator::Return { values: vec![Value::from_u32(0)] }.for_each_operand(|_| n += 1);
1421    /// assert_eq!(n, 1);
1422    /// ```
1423    pub fn for_each_operand(&self, mut f: impl FnMut(Value)) {
1424        self.for_each_own_operand(&mut f);
1425        for dest in self.successors() {
1426            dest.for_each_value(&mut f);
1427        }
1428    }
1429
1430    /// Calls `f` on the values the terminator itself consumes, excluding edge
1431    /// arguments (the call's operands, the condition, the returned values).
1432    pub(crate) fn for_each_own_operand(&self, mut f: impl FnMut(Value)) {
1433        match self {
1434            Terminator::Jump(_) | Terminator::Unreachable | Terminator::Trap { .. } => {}
1435            Terminator::Branch { cond, .. } => f(*cond),
1436            Terminator::Switch { value, .. } => f(*value),
1437            Terminator::Return { values } => values.iter().copied().for_each(f),
1438            Terminator::TailCall(call) => call.for_each_operand(f),
1439            Terminator::Invoke { call, .. } => call.for_each_operand(f),
1440            Terminator::Check { op, .. } => op.for_each_operand(f),
1441            Terminator::Resume { payload } => f(*payload),
1442        }
1443    }
1444
1445    /// Rewrites every SSA value the terminator reads, including edge arguments.
1446    ///
1447    /// # Examples
1448    ///
1449    /// ```
1450    /// use ir_lang::{Terminator, Value};
1451    ///
1452    /// let mut t = Terminator::Resume { payload: Value::from_u32(0) };
1453    /// t.map_operands(|_| Value::from_u32(9));
1454    /// assert_eq!(t, Terminator::Resume { payload: Value::from_u32(9) });
1455    /// ```
1456    pub fn map_operands(&mut self, mut f: impl FnMut(Value) -> Value) {
1457        self.map_own_operands(&mut f);
1458        self.for_each_successor_mut(|c| c.map_values(&mut f));
1459    }
1460
1461    /// Rewrites the values the terminator itself consumes, excluding edge
1462    /// arguments.
1463    pub(crate) fn map_own_operands(&mut self, mut f: impl FnMut(Value) -> Value) {
1464        match self {
1465            Terminator::Jump(_) | Terminator::Unreachable | Terminator::Trap { .. } => {}
1466            Terminator::Branch { cond, .. } => *cond = f(*cond),
1467            Terminator::Switch { value, .. } => *value = f(*value),
1468            Terminator::Return { values } => {
1469                for v in values.iter_mut() {
1470                    *v = f(*v);
1471                }
1472            }
1473            Terminator::TailCall(call) | Terminator::Invoke { call, .. } => {
1474                call.map_operands(&mut f);
1475            }
1476            Terminator::Check { op, .. } => op.map_operands(&mut f),
1477            Terminator::Resume { payload } => *payload = f(*payload),
1478        }
1479    }
1480
1481    /// The textual opcode (`jump`, `br`, `invoke`, ...).
1482    ///
1483    /// # Examples
1484    ///
1485    /// ```
1486    /// use ir_lang::Terminator;
1487    ///
1488    /// assert_eq!(Terminator::Unreachable.opcode(), "unreachable");
1489    /// ```
1490    #[must_use]
1491    pub fn opcode(&self) -> &'static str {
1492        match self {
1493            Terminator::Jump(_) => "jump",
1494            Terminator::Branch { .. } => "br",
1495            Terminator::Switch { .. } => "switch",
1496            Terminator::Return { .. } => "ret",
1497            Terminator::Unreachable => "unreachable",
1498            Terminator::Trap { .. } => "trap",
1499            Terminator::TailCall(call) => match call.target {
1500                CallTarget::Direct(_) => "tail_call",
1501                CallTarget::Indirect { .. } => "tail_call_indirect",
1502            },
1503            Terminator::Invoke { call, .. } => match call.target {
1504                CallTarget::Direct(_) => "invoke",
1505                CallTarget::Indirect { .. } => "invoke_indirect",
1506            },
1507            Terminator::Check { .. } => "check",
1508            Terminator::Resume { .. } => "resume",
1509        }
1510    }
1511}
1512
1513#[cfg(test)]
1514mod tests {
1515    use super::*;
1516    use alloc::string::ToString;
1517    use alloc::vec;
1518
1519    #[test]
1520    fn test_switch_successors_list_cases_then_default() {
1521        let t = Terminator::Switch {
1522            value: Value::from_u32(0),
1523            cases: vec![
1524                SwitchCase {
1525                    value: 1,
1526                    dest: BlockCall::new(Block::from_u32(1), &[]),
1527                },
1528                SwitchCase {
1529                    value: 2,
1530                    dest: BlockCall::new(Block::from_u32(2), &[]),
1531                },
1532            ],
1533            default: BlockCall::new(Block::from_u32(3), &[]),
1534        };
1535        let order: Vec<usize> = t.successors().map(|c| c.block.index()).collect();
1536        assert_eq!(order, [1, 2, 3]);
1537    }
1538
1539    #[test]
1540    fn test_terminator_operands_include_edge_values_only() {
1541        let t = Terminator::Invoke {
1542            call: Box::new(Call::direct(FuncId::from_u32(0), &[Value::from_u32(1)])),
1543            normal: BlockCall::with_args(
1544                Block::from_u32(1),
1545                &[BlockArg::Result(0), BlockArg::Value(Value::from_u32(2))],
1546            ),
1547            unwind: BlockCall::with_args(Block::from_u32(2), &[BlockArg::Exception]),
1548        };
1549        let mut seen = Vec::new();
1550        t.for_each_operand(|v| seen.push(v.index()));
1551        assert_eq!(seen, [1, 2]);
1552    }
1553
1554    #[test]
1555    fn test_attrs_display_and_parse() {
1556        let a = Attrs::NOGC | Attrs::NORETURN;
1557        assert_eq!(a.to_string(), "[noreturn, nogc]");
1558        assert_eq!(Attrs::from_name("inline"), Some(Attrs::INLINE));
1559        assert_eq!(Attrs::from_name("bogus"), None);
1560    }
1561
1562    #[test]
1563    fn test_mem_order_names_round_trip() {
1564        for o in MemOrder::ALL {
1565            assert_eq!(MemOrder::from_name(o.name()), Some(o));
1566        }
1567        for o in RmwOp::ALL {
1568            assert_eq!(RmwOp::from_name(o.name()), Some(o));
1569        }
1570    }
1571
1572    #[test]
1573    fn test_inst_data_stays_small() {
1574        // Instructions live in a dense arena; keep the record compact.
1575        assert!(core::mem::size_of::<InstData>() <= 24);
1576    }
1577
1578    #[test]
1579    fn test_block_arg_display() {
1580        assert_eq!(BlockArg::Exception.to_string(), "exn");
1581        assert_eq!(BlockArg::ErrorCode.to_string(), "err");
1582        assert_eq!(BlockArg::Value(Value::from_u32(2)).to_string(), "v2");
1583    }
1584}