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}