gruel_air/
inst.rs

1//! AIR instruction definitions.
2//!
3//! Like RIR, instructions are stored densely and referenced by index.
4//!
5//! # Place Expressions (ADR-0030 Phase 8)
6//!
7//! Memory locations are represented using [`AirPlace`], which consists of:
8//! - A base ([`AirPlaceBase`]): either a local variable slot or parameter slot
9//! - A list of projections ([`AirProjection`]): field accesses and array indices
10//!
11//! This design follows Rust MIR's proven approach and eliminates redundant
12//! Load instructions for nested access patterns like `arr[i].field`.
13
14use std::fmt;
15
16// Compile-time size assertions to prevent silent size growth during refactoring.
17// These limits are set slightly above current sizes to allow minor changes,
18// but will catch significant size regressions.
19//
20// Current sizes (as of 2025-12):
21// - AirInst: 40 bytes (AirInstData + Type + Span)
22// - AirInstData: 24 bytes
23const _: () = assert!(std::mem::size_of::<AirInst>() <= 48);
24const _: () = assert!(std::mem::size_of::<AirInstData>() <= 32);
25
26use crate::types::{StructId, Type};
27use gruel_span::Span;
28use lasso::{Key, Spur};
29
30// ============================================================================
31// Place Expressions (ADR-0030 Phase 8)
32// ============================================================================
33
34/// A reference to a place in AIR - stored as index into the places array.
35///
36/// This is a lightweight handle that can be copied and compared efficiently.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub struct AirPlaceRef(u32);
39
40impl AirPlaceRef {
41    /// Create a new place reference from a raw index.
42    #[inline]
43    pub const fn from_raw(index: u32) -> Self {
44        Self(index)
45    }
46
47    /// Get the raw index.
48    #[inline]
49    pub const fn as_u32(self) -> u32 {
50        self.0
51    }
52}
53
54impl fmt::Display for AirPlaceRef {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        write!(f, "place#{}", self.0)
57    }
58}
59
60/// A memory location that can be read from or written to.
61///
62/// A place represents a path to a memory location, consisting of a base
63/// (local variable or parameter) and zero or more projections (field access,
64/// array indexing).
65///
66/// # Examples
67///
68/// - `x` → `AirPlace { base: Local(0), projections_start: 0, projections_len: 0 }`
69/// - `arr[i]` → `AirPlace { base: Local(0), ... }` with `Index` projection
70/// - `point.x` → `AirPlace { base: Local(0), ... }` with `Field` projection
71/// - `arr[i].x` → `AirPlace { base: Local(0), ... }` with `Index` then `Field`
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct AirPlace {
74    /// The base of the place - either a local slot or parameter slot
75    pub base: AirPlaceBase,
76    /// Start index into Air's projections array
77    pub projections_start: u32,
78    /// Number of projections
79    pub projections_len: u32,
80}
81
82impl AirPlace {
83    /// Create a place for a local variable with no projections.
84    #[inline]
85    pub const fn local(slot: u32) -> Self {
86        Self {
87            base: AirPlaceBase::Local(slot),
88            projections_start: 0,
89            projections_len: 0,
90        }
91    }
92
93    /// Create a place for a parameter with no projections.
94    #[inline]
95    pub const fn param(slot: u32) -> Self {
96        Self {
97            base: AirPlaceBase::Param(slot),
98            projections_start: 0,
99            projections_len: 0,
100        }
101    }
102
103    /// Returns true if this place has no projections (is just a variable).
104    #[inline]
105    pub const fn is_simple(&self) -> bool {
106        self.projections_len == 0
107    }
108
109    /// Returns the local slot if this is a simple local place with no projections.
110    #[inline]
111    pub const fn as_local(&self) -> Option<u32> {
112        if self.projections_len == 0 {
113            match self.base {
114                AirPlaceBase::Local(slot) => Some(slot),
115                AirPlaceBase::Param(_) => None,
116            }
117        } else {
118            None
119        }
120    }
121
122    /// Returns the param slot if this is a simple param place with no projections.
123    #[inline]
124    pub const fn as_param(&self) -> Option<u32> {
125        if self.projections_len == 0 {
126            match self.base {
127                AirPlaceBase::Param(slot) => Some(slot),
128                AirPlaceBase::Local(_) => None,
129            }
130        } else {
131            None
132        }
133    }
134}
135
136/// The base of a place - where the memory location starts.
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub enum AirPlaceBase {
139    /// Local variable slot
140    Local(u32),
141    /// Parameter slot (for parameters, including inout)
142    Param(u32),
143}
144
145/// A projection applied to a place to reach a nested location.
146///
147/// Projections are stored in `Air::projections` and referenced by
148/// `AirPlace::projections_start` and `AirPlace::projections_len`.
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub enum AirProjection {
151    /// Field access: `.field_name`
152    ///
153    /// The struct_id identifies the struct type, and field_index is the
154    /// 0-based index of the field in declaration order.
155    Field {
156        struct_id: StructId,
157        field_index: u32,
158    },
159    /// Array index: `[index]`
160    ///
161    /// The array_type is needed for bounds checking and element size calculation.
162    /// The index is an AirRef that will be evaluated at runtime.
163    Index { array_type: Type, index: AirRef },
164}
165
166/// Parameter passing mode in AIR.
167#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
168pub enum AirParamMode {
169    /// Normal pass-by-value parameter
170    #[default]
171    Normal,
172    /// Inout parameter - mutated in place and returned to caller
173    Inout,
174    /// Borrow parameter - immutable borrow without ownership transfer
175    Borrow,
176}
177
178/// Argument passing mode in AIR.
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
180pub enum AirArgMode {
181    /// Normal pass-by-value argument
182    #[default]
183    Normal,
184    /// Inout argument - mutated in place
185    Inout,
186    /// Borrow argument - immutable borrow
187    Borrow,
188}
189
190impl AirArgMode {
191    /// Convert to u32 for storage in extra array.
192    #[inline]
193    pub fn as_u32(self) -> u32 {
194        match self {
195            AirArgMode::Normal => 0,
196            AirArgMode::Inout => 1,
197            AirArgMode::Borrow => 2,
198        }
199    }
200
201    /// Convert from u32 stored in extra array.
202    #[inline]
203    pub fn from_u32(v: u32) -> Self {
204        match v {
205            0 => AirArgMode::Normal,
206            1 => AirArgMode::Inout,
207            2 => AirArgMode::Borrow,
208            _ => panic!("invalid AirArgMode value: {}", v),
209        }
210    }
211}
212
213impl From<gruel_rir::RirArgMode> for AirArgMode {
214    fn from(mode: gruel_rir::RirArgMode) -> Self {
215        match mode {
216            gruel_rir::RirArgMode::Normal => AirArgMode::Normal,
217            gruel_rir::RirArgMode::Inout => AirArgMode::Inout,
218            gruel_rir::RirArgMode::Borrow => AirArgMode::Borrow,
219        }
220    }
221}
222
223/// An argument in a function call (AIR level).
224#[derive(Debug, Clone)]
225pub struct AirCallArg {
226    /// The argument expression
227    pub value: AirRef,
228    /// The passing mode for this argument
229    pub mode: AirArgMode,
230}
231
232impl AirCallArg {
233    /// Returns true if this argument is passed as inout.
234    /// This is a convenience method for backwards compatibility.
235    pub fn is_inout(&self) -> bool {
236        self.mode == AirArgMode::Inout
237    }
238
239    /// Returns true if this argument is passed as borrow.
240    pub fn is_borrow(&self) -> bool {
241        self.mode == AirArgMode::Borrow
242    }
243}
244
245impl fmt::Display for AirCallArg {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        match self.mode {
248            AirArgMode::Inout => write!(f, "inout {}", self.value),
249            AirArgMode::Borrow => write!(f, "borrow {}", self.value),
250            AirArgMode::Normal => write!(f, "{}", self.value),
251        }
252    }
253}
254
255/// A pattern in a match expression (AIR level - typed).
256#[derive(Debug, Clone)]
257pub enum AirPattern {
258    /// Wildcard pattern `_` - matches anything
259    Wildcard,
260    /// Integer literal pattern (can be positive or negative)
261    Int(i64),
262    /// Boolean literal pattern
263    Bool(bool),
264    /// Enum variant pattern (e.g., Color::Red)
265    EnumVariant {
266        /// The enum type ID
267        enum_id: crate::types::EnumId,
268        /// The variant index (0-based)
269        variant_index: u32,
270    },
271}
272
273/// Pattern type tags for extra array encoding.
274const PATTERN_WILDCARD: u32 = 0;
275const PATTERN_INT: u32 = 1;
276const PATTERN_BOOL: u32 = 2;
277const PATTERN_ENUM_VARIANT: u32 = 3;
278
279impl AirPattern {
280    /// Encode this pattern to the extra array, returning the number of u32s written.
281    /// Format:
282    /// - Wildcard: [tag, body_ref] = 2 words
283    /// - Int: [tag, body_ref, lo, hi] = 4 words (i64 as two u32s)
284    /// - Bool: [tag, body_ref, value] = 3 words
285    /// - EnumVariant: [tag, body_ref, enum_id, variant_index] = 4 words
286    pub fn encode(&self, body: AirRef, out: &mut Vec<u32>) {
287        match self {
288            AirPattern::Wildcard => {
289                out.push(PATTERN_WILDCARD);
290                out.push(body.as_u32());
291            }
292            AirPattern::Int(n) => {
293                out.push(PATTERN_INT);
294                out.push(body.as_u32());
295                // Encode i64 as two u32s (low, high)
296                out.push(*n as u32);
297                out.push((*n >> 32) as u32);
298            }
299            AirPattern::Bool(b) => {
300                out.push(PATTERN_BOOL);
301                out.push(body.as_u32());
302                out.push(if *b { 1 } else { 0 });
303            }
304            AirPattern::EnumVariant {
305                enum_id,
306                variant_index,
307            } => {
308                out.push(PATTERN_ENUM_VARIANT);
309                out.push(body.as_u32());
310                out.push(enum_id.0);
311                out.push(*variant_index);
312            }
313        }
314    }
315}
316
317/// Iterator for reading match arms from the extra array.
318pub struct MatchArmIterator<'a> {
319    data: &'a [u32],
320    remaining: usize,
321}
322
323impl Iterator for MatchArmIterator<'_> {
324    type Item = (AirPattern, AirRef);
325
326    fn next(&mut self) -> Option<Self::Item> {
327        if self.remaining == 0 {
328            return None;
329        }
330        self.remaining -= 1;
331
332        let tag = self.data[0];
333        let body = AirRef::from_raw(self.data[1]);
334
335        let (pattern, advance) = match tag {
336            PATTERN_WILDCARD => (AirPattern::Wildcard, 2),
337            PATTERN_INT => {
338                let lo = self.data[2] as i64;
339                let hi = (self.data[3] as i64) << 32;
340                (AirPattern::Int(lo | hi), 4)
341            }
342            PATTERN_BOOL => {
343                let b = self.data[2] != 0;
344                (AirPattern::Bool(b), 3)
345            }
346            PATTERN_ENUM_VARIANT => {
347                let enum_id = crate::types::EnumId(self.data[2]);
348                let variant_index = self.data[3];
349                (
350                    AirPattern::EnumVariant {
351                        enum_id,
352                        variant_index,
353                    },
354                    4,
355                )
356            }
357            _ => panic!("invalid pattern tag: {}", tag),
358        };
359
360        self.data = &self.data[advance..];
361        Some((pattern, body))
362    }
363}
364
365/// A reference to an instruction in the AIR.
366#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
367pub struct AirRef(u32);
368
369impl AirRef {
370    #[inline]
371    pub const fn from_raw(index: u32) -> Self {
372        Self(index)
373    }
374
375    #[inline]
376    pub const fn as_u32(self) -> u32 {
377        self.0
378    }
379}
380
381/// The complete AIR for a function.
382#[derive(Debug, Default)]
383pub struct Air {
384    instructions: Vec<AirInst>,
385    /// Extra data for variable-length instruction payloads (args, elements, etc.)
386    extra: Vec<u32>,
387    /// The return type of this function
388    return_type: Type,
389    /// Storage for place projections (ADR-0030 Phase 8).
390    /// AirPlace instructions store (start, len) indices into this array.
391    projections: Vec<AirProjection>,
392    /// Storage for places (ADR-0030 Phase 8).
393    /// AirPlaceRef values are indices into this array.
394    places: Vec<AirPlace>,
395}
396
397impl Air {
398    /// Create a new empty AIR.
399    pub fn new(return_type: Type) -> Self {
400        Self {
401            instructions: Vec::new(),
402            extra: Vec::new(),
403            return_type,
404            projections: Vec::new(),
405            places: Vec::new(),
406        }
407    }
408
409    /// Add an instruction and return its reference.
410    pub fn add_inst(&mut self, inst: AirInst) -> AirRef {
411        let index = self.instructions.len() as u32;
412        self.instructions.push(inst);
413        AirRef::from_raw(index)
414    }
415
416    /// Get an instruction by reference.
417    #[inline]
418    pub fn get(&self, inst_ref: AirRef) -> &AirInst {
419        &self.instructions[inst_ref.0 as usize]
420    }
421
422    /// The return type of this function.
423    #[inline]
424    pub fn return_type(&self) -> Type {
425        self.return_type
426    }
427
428    /// The number of instructions.
429    #[inline]
430    pub fn len(&self) -> usize {
431        self.instructions.len()
432    }
433
434    /// Whether there are no instructions.
435    #[inline]
436    pub fn is_empty(&self) -> bool {
437        self.instructions.is_empty()
438    }
439
440    /// Iterate over all instructions with their references.
441    pub fn iter(&self) -> impl Iterator<Item = (AirRef, &AirInst)> {
442        self.instructions
443            .iter()
444            .enumerate()
445            .map(|(i, inst)| (AirRef::from_raw(i as u32), inst))
446    }
447
448    /// Add extra data and return the start index.
449    pub fn add_extra(&mut self, data: &[u32]) -> u32 {
450        // Debug assertions for u32 overflow
451        debug_assert!(
452            self.extra.len() <= u32::MAX as usize,
453            "AIR extra data overflow: {} entries exceeds u32::MAX",
454            self.extra.len()
455        );
456        debug_assert!(
457            self.extra.len().saturating_add(data.len()) <= u32::MAX as usize,
458            "AIR extra data would overflow: {} + {} exceeds u32::MAX",
459            self.extra.len(),
460            data.len()
461        );
462
463        let start = self.extra.len() as u32;
464        self.extra.extend_from_slice(data);
465        start
466    }
467
468    /// Get extra data slice by start index and length.
469    #[inline]
470    pub fn get_extra(&self, start: u32, len: u32) -> &[u32] {
471        let start = start as usize;
472        let end = start + len as usize;
473        &self.extra[start..end]
474    }
475
476    // Helper methods for reading structured data from extra array
477
478    /// Get AirRefs from extra array (for blocks, array elements, intrinsic args, etc.).
479    #[inline]
480    pub fn get_air_refs(&self, start: u32, len: u32) -> impl Iterator<Item = AirRef> + '_ {
481        self.get_extra(start, len)
482            .iter()
483            .map(|&v| AirRef::from_raw(v))
484    }
485
486    /// Get call arguments from extra array.
487    /// Each call arg is encoded as 2 u32s: (air_ref, mode).
488    #[inline]
489    pub fn get_call_args(&self, start: u32, len: u32) -> impl Iterator<Item = AirCallArg> + '_ {
490        let data = self.get_extra(start, len * 2);
491        data.chunks_exact(2).map(|chunk| AirCallArg {
492            value: AirRef::from_raw(chunk[0]),
493            mode: AirArgMode::from_u32(chunk[1]),
494        })
495    }
496
497    /// Get match arms from extra array.
498    /// Each match arm is encoded based on pattern type plus the body AirRef.
499    #[inline]
500    pub fn get_match_arms(
501        &self,
502        start: u32,
503        len: u32,
504    ) -> impl Iterator<Item = (AirPattern, AirRef)> + '_ {
505        MatchArmIterator {
506            data: &self.extra[start as usize..],
507            remaining: len as usize,
508        }
509    }
510
511    /// Get struct init data from extra array.
512    /// Returns (field_refs_iterator, source_order_iterator).
513    #[inline]
514    pub fn get_struct_init(
515        &self,
516        fields_start: u32,
517        fields_len: u32,
518        source_order_start: u32,
519    ) -> (
520        impl Iterator<Item = AirRef> + '_,
521        impl Iterator<Item = usize> + '_,
522    ) {
523        let fields = self.get_air_refs(fields_start, fields_len);
524        let source_order = self
525            .get_extra(source_order_start, fields_len)
526            .iter()
527            .map(|&v| v as usize);
528        (fields, source_order)
529    }
530
531    /// Remap string constant IDs using the provided mapping function.
532    ///
533    /// This is used after parallel function analysis to convert local string IDs
534    /// (per-function) to global string IDs (across all functions). The mapping
535    /// function takes a local string ID and returns the global string ID.
536    pub fn remap_string_ids<F>(&mut self, map_fn: F)
537    where
538        F: Fn(u32) -> u32,
539    {
540        for inst in &mut self.instructions {
541            if let AirInstData::StringConst(ref mut id) = inst.data {
542                *id = map_fn(*id);
543            }
544        }
545    }
546
547    /// Get a reference to all instructions.
548    #[inline]
549    pub fn instructions(&self) -> &[AirInst] {
550        &self.instructions
551    }
552
553    /// Rewrite the data of an instruction at a given index.
554    ///
555    /// This is used by the specialization pass to rewrite `CallGeneric` to `Call`.
556    /// The type and span are preserved.
557    pub fn rewrite_inst_data(&mut self, index: usize, new_data: AirInstData) {
558        self.instructions[index].data = new_data;
559    }
560
561    // ========================================================================
562    // Place operations (ADR-0030 Phase 8)
563    // ========================================================================
564
565    /// Add projections to the projections array and return (start, len).
566    ///
567    /// Used for PlaceRead and PlaceWrite instructions.
568    pub fn push_projections(
569        &mut self,
570        projs: impl IntoIterator<Item = AirProjection>,
571    ) -> (u32, u32) {
572        let start = self.projections.len() as u32;
573        self.projections.extend(projs);
574        let len = self.projections.len() as u32 - start;
575        (start, len)
576    }
577
578    /// Get a slice from the projections array.
579    #[inline]
580    pub fn get_projections(&self, start: u32, len: u32) -> &[AirProjection] {
581        &self.projections[start as usize..(start + len) as usize]
582    }
583
584    /// Get projections for a place.
585    #[inline]
586    pub fn get_place_projections(&self, place: &AirPlace) -> &[AirProjection] {
587        self.get_projections(place.projections_start, place.projections_len)
588    }
589
590    /// Create a place with the given base and projections.
591    ///
592    /// This adds the projections to the projections array and returns a PlaceRef
593    /// that references the place.
594    pub fn make_place(
595        &mut self,
596        base: AirPlaceBase,
597        projs: impl IntoIterator<Item = AirProjection>,
598    ) -> AirPlaceRef {
599        let (projections_start, projections_len) = self.push_projections(projs);
600        let place = AirPlace {
601            base,
602            projections_start,
603            projections_len,
604        };
605        let index = self.places.len() as u32;
606        self.places.push(place);
607        AirPlaceRef::from_raw(index)
608    }
609
610    /// Get a place by reference.
611    #[inline]
612    pub fn get_place(&self, place_ref: AirPlaceRef) -> &AirPlace {
613        &self.places[place_ref.0 as usize]
614    }
615
616    /// Get all places.
617    #[inline]
618    pub fn places(&self) -> &[AirPlace] {
619        &self.places
620    }
621
622    /// Get all projections.
623    #[inline]
624    pub fn projections(&self) -> &[AirProjection] {
625        &self.projections
626    }
627}
628
629/// A single AIR instruction.
630#[derive(Debug, Clone)]
631pub struct AirInst {
632    pub data: AirInstData,
633    pub ty: Type,
634    pub span: Span,
635}
636
637/// AIR instruction data - fully typed operations.
638#[derive(Debug, Clone)]
639pub enum AirInstData {
640    /// Integer constant (typed)
641    Const(u64),
642
643    /// Floating-point constant, stored as f64 bits via `f64::to_bits()`.
644    FloatConst(u64),
645
646    /// Boolean constant
647    BoolConst(bool),
648
649    /// String constant (index into string table)
650    StringConst(u32),
651
652    /// Unit constant
653    UnitConst,
654
655    /// Type constant - a compile-time type value.
656    /// This is used for comptime type parameters (e.g., passing `i32` to `fn foo(comptime T: type)`).
657    /// The contained Type is the type being passed as a value.
658    /// This instruction has type `Type::COMPTIME_TYPE` and is erased during specialization.
659    TypeConst(crate::Type),
660
661    // Binary arithmetic operations
662    /// Addition
663    Add(AirRef, AirRef),
664    /// Subtraction
665    Sub(AirRef, AirRef),
666    /// Multiplication
667    Mul(AirRef, AirRef),
668    /// Division
669    Div(AirRef, AirRef),
670    /// Modulo
671    Mod(AirRef, AirRef),
672
673    // Comparison operations (return bool)
674    /// Equality
675    Eq(AirRef, AirRef),
676    /// Inequality
677    Ne(AirRef, AirRef),
678    /// Less than
679    Lt(AirRef, AirRef),
680    /// Greater than
681    Gt(AirRef, AirRef),
682    /// Less than or equal
683    Le(AirRef, AirRef),
684    /// Greater than or equal
685    Ge(AirRef, AirRef),
686
687    // Logical operations (return bool)
688    /// Logical AND
689    And(AirRef, AirRef),
690    /// Logical OR
691    Or(AirRef, AirRef),
692
693    // Bitwise operations
694    /// Bitwise AND
695    BitAnd(AirRef, AirRef),
696    /// Bitwise OR
697    BitOr(AirRef, AirRef),
698    /// Bitwise XOR
699    BitXor(AirRef, AirRef),
700    /// Left shift
701    Shl(AirRef, AirRef),
702    /// Right shift (arithmetic for signed, logical for unsigned)
703    Shr(AirRef, AirRef),
704
705    // Unary operations
706    /// Negation
707    Neg(AirRef),
708    /// Logical NOT
709    Not(AirRef),
710    /// Bitwise NOT
711    BitNot(AirRef),
712
713    // Control flow
714    /// Conditional branch
715    Branch {
716        cond: AirRef,
717        then_value: AirRef,
718        else_value: Option<AirRef>,
719    },
720
721    /// While loop
722    Loop { cond: AirRef, body: AirRef },
723
724    /// Infinite loop (produces Never type)
725    InfiniteLoop { body: AirRef },
726
727    /// Match expression
728    Match {
729        /// The value being matched (scrutinee)
730        scrutinee: AirRef,
731        /// Start index into extra array for match arms
732        arms_start: u32,
733        /// Number of match arms
734        arms_len: u32,
735    },
736
737    /// Break: exits the innermost loop
738    Break,
739
740    /// Continue: jumps to the next iteration of the innermost loop
741    Continue,
742
743    // Variable operations
744    /// Allocate local variable with initial value
745    /// Returns the slot index
746    Alloc {
747        /// Local variable slot index (0, 1, 2, ...)
748        slot: u32,
749        /// Initial value
750        init: AirRef,
751    },
752
753    /// Load value from local variable
754    Load {
755        /// Local variable slot index
756        slot: u32,
757    },
758
759    /// Store value to local variable
760    Store {
761        /// Local variable slot index
762        slot: u32,
763        /// Value to store
764        value: AirRef,
765        /// True if the slot held a live (non-moved) value before this assignment.
766        /// When true, the old value must be dropped before the new value is written.
767        /// When false (value was moved or this is initial allocation), no drop is needed.
768        had_live_value: bool,
769    },
770
771    /// Store value to a parameter (for inout params)
772    ParamStore {
773        /// Parameter's ABI slot (relative to params, not locals)
774        param_slot: u32,
775        /// Value to store
776        value: AirRef,
777    },
778
779    /// Return from function (None for `return;` in unit-returning functions)
780    Ret(Option<AirRef>),
781
782    /// Function call
783    Call {
784        /// Function name (interned symbol)
785        name: Spur,
786        /// Start index into extra array for arguments
787        args_start: u32,
788        /// Number of arguments
789        args_len: u32,
790    },
791
792    /// Generic function call - requires specialization before codegen.
793    ///
794    /// This is emitted when calling a function with `comptime T: type` parameters.
795    /// During a post-analysis specialization pass, this is rewritten to a regular
796    /// `Call` to a specialized version of the function (e.g., `identity__i32`).
797    ///
798    /// The type_args are encoded in the extra array as raw Type discriminant values.
799    /// The runtime args (non-comptime) are also in the extra array, after type_args.
800    CallGeneric {
801        /// Base function name (interned symbol)
802        name: Spur,
803        /// Start index into extra array for type arguments (raw Type values)
804        type_args_start: u32,
805        /// Number of type arguments
806        type_args_len: u32,
807        /// Start index into extra array for runtime arguments
808        args_start: u32,
809        /// Number of runtime arguments
810        args_len: u32,
811    },
812
813    /// Intrinsic call (e.g., @dbg)
814    Intrinsic {
815        /// Intrinsic name (without @, interned)
816        name: Spur,
817        /// Start index into extra array for arguments
818        args_start: u32,
819        /// Number of arguments
820        args_len: u32,
821    },
822
823    /// Reference to a function parameter
824    Param {
825        /// Parameter index (0-based)
826        index: u32,
827    },
828
829    /// Block expression with statements and final value.
830    /// Used to group side-effect statements with their result value,
831    /// enabling demand-driven lowering for short-circuit evaluation.
832    Block {
833        /// Start index into extra array for statement refs
834        stmts_start: u32,
835        /// Number of statements
836        stmts_len: u32,
837        /// The block's resulting value
838        value: AirRef,
839    },
840
841    // Struct operations
842    /// Create a new struct instance with initialized fields
843    StructInit {
844        /// The struct type being created
845        struct_id: StructId,
846        /// Start index into extra array for field refs (in declaration order)
847        fields_start: u32,
848        /// Number of fields
849        fields_len: u32,
850        /// Start index into extra array for source order indices
851        /// Each entry is an index into fields, specifying evaluation order
852        source_order_start: u32,
853    },
854
855    /// Load a field from a struct value
856    FieldGet {
857        /// The struct value
858        base: AirRef,
859        /// The struct type
860        struct_id: StructId,
861        /// Field index (0-based, in declaration order)
862        field_index: u32,
863    },
864
865    /// Store a value to a struct field (for local variables)
866    FieldSet {
867        /// The struct variable slot
868        slot: u32,
869        /// The struct type
870        struct_id: StructId,
871        /// Field index (0-based, in declaration order)
872        field_index: u32,
873        /// Value to store
874        value: AirRef,
875    },
876
877    /// Store a value to a struct field (for parameters, including inout)
878    ParamFieldSet {
879        /// The parameter's ABI slot (relative to params, not locals)
880        param_slot: u32,
881        /// Offset within the struct for nested field access (e.g., p.inner.x)
882        inner_offset: u32,
883        /// The struct type containing the field being set
884        struct_id: StructId,
885        /// Field index (0-based, in declaration order)
886        field_index: u32,
887        /// Value to store
888        value: AirRef,
889    },
890
891    // Array operations
892    /// Create a new array with initialized elements.
893    /// The array type is stored in `AirInst.ty` as `Type::new_array(...)`.
894    ArrayInit {
895        /// Start index into extra array for element refs
896        elems_start: u32,
897        /// Number of elements
898        elems_len: u32,
899    },
900
901    /// Load an element from an array.
902    /// The array type is stored in `AirInst.ty`.
903    IndexGet {
904        /// The array value
905        base: AirRef,
906        /// The array type (for bounds checking and element size)
907        array_type: Type,
908        /// Index expression
909        index: AirRef,
910    },
911
912    /// Store a value to an array element.
913    /// The array type is stored in `AirInst.ty`.
914    IndexSet {
915        /// The array variable slot
916        slot: u32,
917        /// The array type (for bounds checking and element size)
918        array_type: Type,
919        /// Index expression
920        index: AirRef,
921        /// Value to store
922        value: AirRef,
923    },
924
925    /// Store a value to an array element of an inout parameter.
926    /// The array type is stored in `AirInst.ty`.
927    ParamIndexSet {
928        /// The parameter's ABI slot (relative to params, not locals)
929        param_slot: u32,
930        /// The array type (for bounds checking and element size)
931        array_type: Type,
932        /// Index expression
933        index: AirRef,
934        /// Value to store
935        value: AirRef,
936    },
937
938    // Place operations (ADR-0030 Phase 8)
939    /// Read a value from a memory location.
940    ///
941    /// This unifies Load, IndexGet, and FieldGet into a single instruction
942    /// that can handle arbitrarily nested access patterns like `arr[i].field`.
943    /// Eventually, the separate FieldGet/IndexGet instructions will be removed.
944    PlaceRead {
945        /// Reference to the place to read from
946        place: AirPlaceRef,
947    },
948
949    /// Write a value to a memory location.
950    ///
951    /// This unifies Store, IndexSet, ParamIndexSet, FieldSet, and ParamFieldSet
952    /// into a single instruction that can handle nested writes.
953    /// Eventually, the separate *Set instructions will be removed.
954    PlaceWrite {
955        /// Reference to the place to write to
956        place: AirPlaceRef,
957        /// Value to write
958        value: AirRef,
959    },
960
961    // Enum operations
962    /// Create an enum variant value (unit variant or any variant of a unit-only enum)
963    EnumVariant {
964        /// The enum type ID
965        enum_id: crate::types::EnumId,
966        /// The variant index (0-based)
967        variant_index: u32,
968    },
969
970    /// Create a data enum variant value with associated field values.
971    /// Used when the enum has at least one data variant.
972    /// Field AirRefs are stored in the extra array at [fields_start..fields_start+fields_len].
973    EnumCreate {
974        /// The enum type ID
975        enum_id: crate::types::EnumId,
976        /// The variant index (0-based)
977        variant_index: u32,
978        /// Start index into extra array for field values
979        fields_start: u32,
980        /// Number of field values
981        fields_len: u32,
982    },
983
984    /// Extract a field value from an enum variant's payload.
985    /// Used in data variant match arm bodies to bind pattern variables.
986    EnumPayloadGet {
987        /// The enum value to extract from
988        base: AirRef,
989        /// The variant index (must match the enclosing arm's pattern)
990        variant_index: u32,
991        /// The field index within the variant
992        field_index: u32,
993    },
994
995    // Type conversion operations
996    /// Integer cast: convert between integer types with runtime range check.
997    /// Panics if the value cannot be represented in the target type.
998    /// The target type is stored in AirInst.ty.
999    IntCast {
1000        /// The value to cast
1001        value: AirRef,
1002        /// The source type (for determining signedness and size)
1003        from_ty: Type,
1004    },
1005
1006    /// Float cast: convert between floating-point types (fptrunc/fpext).
1007    /// The target type is stored in AirInst.ty.
1008    FloatCast {
1009        /// The value to cast
1010        value: AirRef,
1011        /// The source float type
1012        from_ty: Type,
1013    },
1014
1015    /// Integer to float conversion (sitofp/uitofp).
1016    /// The target type is stored in AirInst.ty.
1017    IntToFloat {
1018        /// The integer value to convert
1019        value: AirRef,
1020        /// The source integer type (for determining signedness)
1021        from_ty: Type,
1022    },
1023
1024    /// Float to integer conversion (fptosi/fptoui) with runtime range check.
1025    /// Panics if the value is NaN or out of range of the target integer type.
1026    /// The target type is stored in AirInst.ty.
1027    FloatToInt {
1028        /// The float value to convert
1029        value: AirRef,
1030        /// The source float type
1031        from_ty: Type,
1032    },
1033
1034    // Drop/destructor operations
1035    /// Drop a value, running its destructor if the type has one.
1036    /// For trivially droppable types, this is a no-op.
1037    /// The type is stored in the AirInst.ty field.
1038    Drop {
1039        /// The value to drop
1040        value: AirRef,
1041    },
1042
1043    // Storage liveness operations (for drop elaboration)
1044    /// Marks that a local slot becomes live (storage allocated).
1045    /// Emitted when a variable binding is created.
1046    /// The type is stored in AirInst.ty for drop elaboration.
1047    StorageLive {
1048        /// The slot that becomes live
1049        slot: u32,
1050    },
1051
1052    /// Marks that a local slot becomes dead (storage can be deallocated).
1053    /// Emitted at scope exit for variables declared in that scope.
1054    /// The type is stored in AirInst.ty for drop elaboration.
1055    /// Drop elaboration will insert a Drop before this if the type needs drop
1056    /// and the value wasn't moved.
1057    StorageDead {
1058        /// The slot that becomes dead
1059        slot: u32,
1060    },
1061}
1062
1063impl fmt::Display for AirRef {
1064    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1065        write!(f, "%{}", self.0)
1066    }
1067}
1068
1069impl fmt::Display for Air {
1070    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1071        writeln!(f, "air (return_type: {}) {{", self.return_type.name())?;
1072        for (inst_ref, inst) in self.iter() {
1073            write!(f, "    {} : {} = ", inst_ref, inst.ty.name())?;
1074            match &inst.data {
1075                AirInstData::Const(v) => writeln!(f, "const {}", v)?,
1076                AirInstData::FloatConst(bits) => writeln!(f, "const {}", f64::from_bits(*bits))?,
1077                AirInstData::BoolConst(v) => writeln!(f, "const {}", v)?,
1078                AirInstData::StringConst(idx) => writeln!(f, "string_const @{}", idx)?,
1079                AirInstData::UnitConst => writeln!(f, "const ()")?,
1080                AirInstData::TypeConst(ty) => writeln!(f, "type_const {}", ty.name())?,
1081                AirInstData::Add(lhs, rhs) => writeln!(f, "add {}, {}", lhs, rhs)?,
1082                AirInstData::Sub(lhs, rhs) => writeln!(f, "sub {}, {}", lhs, rhs)?,
1083                AirInstData::Mul(lhs, rhs) => writeln!(f, "mul {}, {}", lhs, rhs)?,
1084                AirInstData::Div(lhs, rhs) => writeln!(f, "div {}, {}", lhs, rhs)?,
1085                AirInstData::Mod(lhs, rhs) => writeln!(f, "mod {}, {}", lhs, rhs)?,
1086                AirInstData::Eq(lhs, rhs) => writeln!(f, "eq {}, {}", lhs, rhs)?,
1087                AirInstData::Ne(lhs, rhs) => writeln!(f, "ne {}, {}", lhs, rhs)?,
1088                AirInstData::Lt(lhs, rhs) => writeln!(f, "lt {}, {}", lhs, rhs)?,
1089                AirInstData::Gt(lhs, rhs) => writeln!(f, "gt {}, {}", lhs, rhs)?,
1090                AirInstData::Le(lhs, rhs) => writeln!(f, "le {}, {}", lhs, rhs)?,
1091                AirInstData::Ge(lhs, rhs) => writeln!(f, "ge {}, {}", lhs, rhs)?,
1092                AirInstData::And(lhs, rhs) => writeln!(f, "and {}, {}", lhs, rhs)?,
1093                AirInstData::Or(lhs, rhs) => writeln!(f, "or {}, {}", lhs, rhs)?,
1094                AirInstData::BitAnd(lhs, rhs) => writeln!(f, "bit_and {}, {}", lhs, rhs)?,
1095                AirInstData::BitOr(lhs, rhs) => writeln!(f, "bit_or {}, {}", lhs, rhs)?,
1096                AirInstData::BitXor(lhs, rhs) => writeln!(f, "bit_xor {}, {}", lhs, rhs)?,
1097                AirInstData::Shl(lhs, rhs) => writeln!(f, "shl {}, {}", lhs, rhs)?,
1098                AirInstData::Shr(lhs, rhs) => writeln!(f, "shr {}, {}", lhs, rhs)?,
1099                AirInstData::Neg(operand) => writeln!(f, "neg {}", operand)?,
1100                AirInstData::Not(operand) => writeln!(f, "not {}", operand)?,
1101                AirInstData::BitNot(operand) => writeln!(f, "bit_not {}", operand)?,
1102                AirInstData::Branch {
1103                    cond,
1104                    then_value,
1105                    else_value,
1106                } => {
1107                    if let Some(else_v) = else_value {
1108                        writeln!(f, "branch {}, {}, {}", cond, then_value, else_v)?
1109                    } else {
1110                        writeln!(f, "branch {}, {}", cond, then_value)?
1111                    }
1112                }
1113                AirInstData::Loop { cond, body } => writeln!(f, "loop {}, {}", cond, body)?,
1114                AirInstData::InfiniteLoop { body } => writeln!(f, "infinite_loop {}", body)?,
1115                AirInstData::Match {
1116                    scrutinee,
1117                    arms_start,
1118                    arms_len,
1119                } => {
1120                    write!(f, "match {} {{ ", scrutinee)?;
1121                    for (i, (pat, body)) in self.get_match_arms(*arms_start, *arms_len).enumerate()
1122                    {
1123                        if i > 0 {
1124                            write!(f, ", ")?;
1125                        }
1126                        let pat_str = match pat {
1127                            AirPattern::Wildcard => "_".to_string(),
1128                            AirPattern::Int(n) => n.to_string(),
1129                            AirPattern::Bool(b) => b.to_string(),
1130                            AirPattern::EnumVariant {
1131                                enum_id,
1132                                variant_index,
1133                            } => format!("enum#{}::{}", enum_id.0, variant_index),
1134                        };
1135                        write!(f, "{} => {}", pat_str, body)?;
1136                    }
1137                    writeln!(f, " }}")?;
1138                }
1139                AirInstData::Break => writeln!(f, "break")?,
1140                AirInstData::Continue => writeln!(f, "continue")?,
1141                AirInstData::Alloc { slot, init } => writeln!(f, "alloc ${} = {}", slot, init)?,
1142                AirInstData::Load { slot } => writeln!(f, "load ${}", slot)?,
1143                AirInstData::Store { slot, value, .. } => {
1144                    writeln!(f, "store ${} = {}", slot, value)?
1145                }
1146                AirInstData::ParamStore { param_slot, value } => {
1147                    writeln!(f, "param_store %{} = {}", param_slot, value)?
1148                }
1149                AirInstData::Ret(inner) => {
1150                    if let Some(inner) = inner {
1151                        writeln!(f, "ret {}", inner)?
1152                    } else {
1153                        writeln!(f, "ret")?
1154                    }
1155                }
1156                AirInstData::Call {
1157                    name,
1158                    args_start,
1159                    args_len,
1160                } => {
1161                    write!(f, "call @{}(", name.into_usize())?;
1162                    for (i, arg) in self.get_call_args(*args_start, *args_len).enumerate() {
1163                        if i > 0 {
1164                            write!(f, ", ")?;
1165                        }
1166                        write!(f, "{}", arg)?;
1167                    }
1168                    writeln!(f, ")")?;
1169                }
1170                AirInstData::CallGeneric {
1171                    name,
1172                    type_args_start,
1173                    type_args_len,
1174                    args_start,
1175                    args_len,
1176                } => {
1177                    write!(f, "call_generic @{}<", name.into_usize())?;
1178                    // Show type arguments
1179                    for i in 0..*type_args_len {
1180                        if i > 0 {
1181                            write!(f, ", ")?;
1182                        }
1183                        let type_val = self.extra[(*type_args_start + i) as usize];
1184                        write!(f, "type#{}", type_val)?;
1185                    }
1186                    write!(f, ">(")?;
1187                    // Show runtime arguments
1188                    for (i, arg) in self.get_call_args(*args_start, *args_len).enumerate() {
1189                        if i > 0 {
1190                            write!(f, ", ")?;
1191                        }
1192                        write!(f, "{}", arg)?;
1193                    }
1194                    writeln!(f, ")")?;
1195                }
1196                AirInstData::Intrinsic {
1197                    name,
1198                    args_start,
1199                    args_len,
1200                } => {
1201                    write!(f, "intrinsic @sym:{}(", name.into_usize())?;
1202                    for (i, arg) in self.get_air_refs(*args_start, *args_len).enumerate() {
1203                        if i > 0 {
1204                            write!(f, ", ")?;
1205                        }
1206                        write!(f, "{}", arg)?;
1207                    }
1208                    writeln!(f, ")")?;
1209                }
1210                AirInstData::Param { index } => writeln!(f, "param {}", index)?,
1211                AirInstData::Block {
1212                    stmts_start,
1213                    stmts_len,
1214                    value,
1215                } => {
1216                    write!(f, "block [")?;
1217                    for (i, s) in self.get_air_refs(*stmts_start, *stmts_len).enumerate() {
1218                        if i > 0 {
1219                            write!(f, ", ")?;
1220                        }
1221                        write!(f, "{}", s)?;
1222                    }
1223                    writeln!(f, "], {}", value)?;
1224                }
1225                AirInstData::StructInit {
1226                    struct_id,
1227                    fields_start,
1228                    fields_len,
1229                    source_order_start,
1230                } => {
1231                    write!(f, "struct_init #{} {{", struct_id.0)?;
1232                    let (fields, source_order) =
1233                        self.get_struct_init(*fields_start, *fields_len, *source_order_start);
1234                    for (i, field) in fields.enumerate() {
1235                        if i > 0 {
1236                            write!(f, ", ")?;
1237                        }
1238                        write!(f, "{}", field)?;
1239                    }
1240                    write!(f, "}} eval_order=[")?;
1241                    for (i, idx) in source_order.enumerate() {
1242                        if i > 0 {
1243                            write!(f, ", ")?;
1244                        }
1245                        write!(f, "{}", idx)?;
1246                    }
1247                    writeln!(f, "]")?;
1248                }
1249                AirInstData::FieldGet {
1250                    base,
1251                    struct_id,
1252                    field_index,
1253                } => {
1254                    writeln!(f, "field_get {}.#{}.{}", base, struct_id.0, field_index)?;
1255                }
1256                AirInstData::FieldSet {
1257                    slot,
1258                    struct_id,
1259                    field_index,
1260                    value,
1261                } => {
1262                    writeln!(
1263                        f,
1264                        "field_set ${}.#{}.{} = {}",
1265                        slot, struct_id.0, field_index, value
1266                    )?;
1267                }
1268                AirInstData::ParamFieldSet {
1269                    param_slot,
1270                    inner_offset,
1271                    struct_id,
1272                    field_index,
1273                    value,
1274                } => {
1275                    writeln!(
1276                        f,
1277                        "param_field_set %{}+{}.#{}.{} = {}",
1278                        param_slot, inner_offset, struct_id.0, field_index, value
1279                    )?;
1280                }
1281                AirInstData::ArrayInit {
1282                    elems_start,
1283                    elems_len,
1284                } => {
1285                    write!(f, "array_init [")?;
1286                    for (i, elem) in self.get_air_refs(*elems_start, *elems_len).enumerate() {
1287                        if i > 0 {
1288                            write!(f, ", ")?;
1289                        }
1290                        write!(f, "{}", elem)?;
1291                    }
1292                    writeln!(f, "]")?;
1293                }
1294                AirInstData::IndexGet {
1295                    base,
1296                    array_type,
1297                    index,
1298                } => {
1299                    writeln!(f, "index_get {}({})[{}]", base, array_type.name(), index)?;
1300                }
1301                AirInstData::IndexSet {
1302                    slot,
1303                    array_type,
1304                    index,
1305                    value,
1306                } => {
1307                    writeln!(
1308                        f,
1309                        "index_set ${}({})[{}] = {}",
1310                        slot,
1311                        array_type.name(),
1312                        index,
1313                        value
1314                    )?;
1315                }
1316                AirInstData::ParamIndexSet {
1317                    param_slot,
1318                    array_type,
1319                    index,
1320                    value,
1321                } => {
1322                    writeln!(
1323                        f,
1324                        "param_index_set param{}({})[{}] = {}",
1325                        param_slot,
1326                        array_type.name(),
1327                        index,
1328                        value
1329                    )?;
1330                }
1331                AirInstData::PlaceRead { place } => {
1332                    write!(f, "place_read ")?;
1333                    self.fmt_place(f, *place)?;
1334                    writeln!(f)?;
1335                }
1336                AirInstData::PlaceWrite { place, value } => {
1337                    write!(f, "place_write ")?;
1338                    self.fmt_place(f, *place)?;
1339                    writeln!(f, " = {}", value)?;
1340                }
1341                AirInstData::EnumVariant {
1342                    enum_id,
1343                    variant_index,
1344                } => {
1345                    writeln!(f, "enum_variant #{}::{}", enum_id.0, variant_index)?;
1346                }
1347                AirInstData::EnumCreate {
1348                    enum_id,
1349                    variant_index,
1350                    fields_start,
1351                    fields_len,
1352                } => {
1353                    let field_strs: Vec<String> = self
1354                        .get_air_refs(*fields_start, *fields_len)
1355                        .map(|r| format!("{}", r))
1356                        .collect();
1357                    writeln!(
1358                        f,
1359                        "enum_create #{}::{}({})",
1360                        enum_id.0,
1361                        variant_index,
1362                        field_strs.join(", ")
1363                    )?;
1364                }
1365                AirInstData::EnumPayloadGet {
1366                    base,
1367                    variant_index,
1368                    field_index,
1369                } => {
1370                    writeln!(
1371                        f,
1372                        "enum_payload_get {} variant={} field={}",
1373                        base, variant_index, field_index
1374                    )?;
1375                }
1376                AirInstData::IntCast { value, from_ty } => {
1377                    writeln!(f, "intcast {} from {}", value, from_ty.name())?;
1378                }
1379                AirInstData::FloatCast { value, from_ty } => {
1380                    writeln!(f, "floatcast {} from {}", value, from_ty.name())?;
1381                }
1382                AirInstData::IntToFloat { value, from_ty } => {
1383                    writeln!(f, "int_to_float {} from {}", value, from_ty.name())?;
1384                }
1385                AirInstData::FloatToInt { value, from_ty } => {
1386                    writeln!(f, "float_to_int {} from {}", value, from_ty.name())?;
1387                }
1388                AirInstData::Drop { value } => {
1389                    writeln!(f, "drop {}", value)?;
1390                }
1391                AirInstData::StorageLive { slot } => {
1392                    writeln!(f, "storage_live ${}", slot)?;
1393                }
1394                AirInstData::StorageDead { slot } => {
1395                    writeln!(f, "storage_dead ${}", slot)?;
1396                }
1397            }
1398        }
1399        writeln!(f, "}}")
1400    }
1401}
1402
1403impl Air {
1404    /// Format a place for display, showing the base and projections.
1405    fn fmt_place(&self, f: &mut fmt::Formatter<'_>, place_ref: AirPlaceRef) -> fmt::Result {
1406        let place = self.get_place(place_ref);
1407
1408        // Write the base
1409        match place.base {
1410            AirPlaceBase::Local(slot) => write!(f, "${}", slot)?,
1411            AirPlaceBase::Param(slot) => write!(f, "param%{}", slot)?,
1412        }
1413
1414        // Write the projections
1415        let projections = self.get_place_projections(place);
1416        for proj in projections {
1417            match proj {
1418                AirProjection::Field {
1419                    struct_id,
1420                    field_index,
1421                } => {
1422                    write!(f, ".#{}.{}", struct_id.0, field_index)?;
1423                }
1424                AirProjection::Index { array_type, index } => {
1425                    write!(f, "({})[{}]", array_type.name(), index)?;
1426                }
1427            }
1428        }
1429
1430        Ok(())
1431    }
1432}
1433
1434#[cfg(test)]
1435mod tests {
1436    use super::*;
1437
1438    #[test]
1439    fn test_air_ref_size() {
1440        assert_eq!(std::mem::size_of::<AirRef>(), 4);
1441    }
1442
1443    #[test]
1444    fn test_air_inst_size() {
1445        // Document actual sizes for future reference.
1446        // If this test fails, update the const assertions at the top of this file.
1447        let air_inst_size = std::mem::size_of::<AirInst>();
1448        let air_inst_data_size = std::mem::size_of::<AirInstData>();
1449
1450        // These assertions document the current sizes.
1451        // If the layout changes, update both these values and the const assertions.
1452        assert!(
1453            air_inst_size <= 48,
1454            "AirInst grew beyond 48 bytes: {}",
1455            air_inst_size
1456        );
1457        assert!(
1458            air_inst_data_size <= 32,
1459            "AirInstData grew beyond 32 bytes: {}",
1460            air_inst_data_size
1461        );
1462    }
1463
1464    #[test]
1465    fn test_add_and_get_inst() {
1466        let mut air = Air::new(Type::I32);
1467        let inst = AirInst {
1468            data: AirInstData::Const(42),
1469            ty: Type::I32,
1470            span: Span::new(0, 2),
1471        };
1472        let inst_ref = air.add_inst(inst);
1473
1474        let retrieved = air.get(inst_ref);
1475        assert!(matches!(retrieved.data, AirInstData::Const(42)));
1476        assert_eq!(retrieved.ty, Type::I32);
1477    }
1478}