Skip to main content

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_util::{BinOp, Span, UnaryOp};
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, serde::Serialize, serde::Deserialize)]
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, serde::Serialize, serde::Deserialize)]
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///
138/// Re-export of [`gruel_util::PlaceBase`] under the AIR-flavoured name to
139/// avoid a needless rename at every call site.
140pub use gruel_util::PlaceBase as AirPlaceBase;
141
142/// A projection applied to a place to reach a nested location.
143///
144/// Projections are stored in `Air::projections` and referenced by
145/// `AirPlace::projections_start` and `AirPlace::projections_len`.
146#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
147pub enum AirProjection {
148    /// Field access: `.field_name`
149    ///
150    /// The struct_id identifies the struct type, and field_index is the
151    /// 0-based index of the field in declaration order.
152    Field {
153        struct_id: StructId,
154        field_index: u32,
155    },
156    /// Array index: `[index]`
157    ///
158    /// The array_type is needed for bounds checking and element size calculation.
159    /// The index is an AirRef that will be evaluated at runtime.
160    Index { array_type: Type, index: AirRef },
161}
162
163/// Parameter passing mode in AIR.
164///
165/// Mirrors [`gruel_rir::RirParamMode`]. `MutRef` / `Ref` are the
166/// vestigial-but-used by-pointer markers that survive ADR-0076 for
167/// parameters whose declared type cannot itself be wrapped as
168/// `MutRef(...)` / `Ref(...)` in the type pool (notably interface params).
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
170pub enum AirParamMode {
171    /// Normal pass-by-value parameter (or any reference whose ref-ness is
172    /// already encoded in the parameter `Type`).
173    #[default]
174    Normal,
175    /// Exclusive mutable borrow (interface-by-pointer ABI).
176    MutRef,
177    /// Shared immutable borrow (interface-by-pointer ABI).
178    Ref,
179}
180
181impl AirParamMode {
182    /// Returns true if the parameter is passed by reference per the legacy
183    /// mode mechanism. Type-driven `Ref(T)` / `MutRef(T)` parameters are
184    /// not detected here; callers must additionally inspect the parameter
185    /// `Type`.
186    #[inline]
187    pub fn is_by_ref(self) -> bool {
188        matches!(self, AirParamMode::MutRef | AirParamMode::Ref)
189    }
190
191    /// Returns true if the parameter is an exclusive mutable borrow per
192    /// the legacy mode mechanism.
193    #[inline]
194    pub fn is_mut_ref(self) -> bool {
195        matches!(self, AirParamMode::MutRef)
196    }
197
198    /// Returns true if the parameter is a shared immutable borrow per the
199    /// legacy mode mechanism.
200    #[inline]
201    pub fn is_ref(self) -> bool {
202        matches!(self, AirParamMode::Ref)
203    }
204}
205
206impl From<gruel_rir::RirParamMode> for AirParamMode {
207    fn from(mode: gruel_rir::RirParamMode) -> Self {
208        match mode {
209            gruel_rir::RirParamMode::MutRef => AirParamMode::MutRef,
210            gruel_rir::RirParamMode::Ref => AirParamMode::Ref,
211            // Comptime params are erased by the time they reach codegen;
212            // treat them as normal pass-by-value.
213            gruel_rir::RirParamMode::Normal | gruel_rir::RirParamMode::Comptime => {
214                AirParamMode::Normal
215            }
216        }
217    }
218}
219
220/// Argument passing mode in AIR. Mirrors [`AirParamMode`].
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
222pub enum AirArgMode {
223    /// Normal pass-by-value argument.
224    #[default]
225    Normal,
226    /// Exclusive mutable reborrow (by-pointer ABI).
227    MutRef,
228    /// Shared immutable reborrow (by-pointer ABI).
229    Ref,
230}
231
232impl AirArgMode {
233    /// Convert to u32 for storage in extra array.
234    #[inline]
235    pub fn as_u32(self) -> u32 {
236        match self {
237            AirArgMode::Normal => 0,
238            AirArgMode::MutRef => 1,
239            AirArgMode::Ref => 2,
240        }
241    }
242
243    /// Convert from u32 stored in extra array.
244    #[inline]
245    pub fn from_u32(v: u32) -> Self {
246        match v {
247            0 => AirArgMode::Normal,
248            1 => AirArgMode::MutRef,
249            2 => AirArgMode::Ref,
250            _ => panic!("invalid AirArgMode value: {}", v),
251        }
252    }
253}
254
255impl From<gruel_rir::RirArgMode> for AirArgMode {
256    fn from(mode: gruel_rir::RirArgMode) -> Self {
257        match mode {
258            gruel_rir::RirArgMode::Normal => AirArgMode::Normal,
259            gruel_rir::RirArgMode::MutRef => AirArgMode::MutRef,
260            gruel_rir::RirArgMode::Ref => AirArgMode::Ref,
261        }
262    }
263}
264
265/// An argument in a function call (AIR level).
266#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
267pub struct AirCallArg {
268    /// The argument expression
269    pub value: AirRef,
270    /// The passing mode for this argument
271    pub mode: AirArgMode,
272}
273
274impl AirCallArg {
275    /// Returns true if this argument is passed as an exclusive mutable
276    /// reborrow per the legacy mode mechanism.
277    pub fn is_mut_ref(&self) -> bool {
278        self.mode == AirArgMode::MutRef
279    }
280
281    /// Returns true if this argument is passed as a shared immutable
282    /// reborrow per the legacy mode mechanism.
283    pub fn is_ref(&self) -> bool {
284        self.mode == AirArgMode::Ref
285    }
286}
287
288impl fmt::Display for AirCallArg {
289    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290        match self.mode {
291            AirArgMode::MutRef => write!(f, "mut_ref {}", self.value),
292            AirArgMode::Ref => write!(f, "ref {}", self.value),
293            AirArgMode::Normal => write!(f, "{}", self.value),
294        }
295    }
296}
297
298/// A pattern in a match expression (AIR level - typed).
299///
300/// Recursive shape introduced by ADR-0051. `Wildcard`, `Int`, `Bool`, and
301/// `EnumVariant` are the flat variants produced by the pre-ADR-0051 sema
302/// path; `Bind`, `Tuple`, `Struct`, `EnumDataVariant`, `EnumStructVariant`,
303/// and `EnumUnitVariant` are the recursive variants produced by the new
304/// lowering. During Phases 1-3 both encodings coexist; Phase 4 drops the
305/// flat `EnumVariant`.
306#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
307pub enum AirPattern {
308    /// Wildcard pattern `_` - matches anything, binds nothing.
309    Wildcard,
310    /// Integer literal pattern (can be positive or negative).
311    Int(i64),
312    /// Boolean literal pattern.
313    Bool(bool),
314    /// Legacy flat enum-variant pattern produced by the pre-ADR-0051
315    /// lowering path. Kept for backward compatibility until Phase 4.
316    EnumVariant {
317        enum_id: crate::types::EnumId,
318        variant_index: u32,
319    },
320    /// Bind the scrutinee (or its projection) to a local. `inner` is the
321    /// sub-pattern applied to the same value; `None` is a bare binding,
322    /// `Some(p)` is `name @ p` (not yet exposed in surface syntax).
323    Bind {
324        name: lasso::Spur,
325        is_mut: bool,
326        inner: Option<Box<AirPattern>>,
327    },
328    /// Tuple dispatch; `elems[i]` applies to projection `i`.
329    Tuple { elems: Vec<AirPattern> },
330    /// Named-struct dispatch. Unlisted fields (from `..`) become explicit
331    /// `Wildcard` entries at sema lowering time.
332    Struct {
333        struct_id: StructId,
334        fields: Vec<(u32, AirPattern)>,
335    },
336    /// Enum data-variant dispatch. `fields[i]` applies to positional
337    /// field `i` of the variant.
338    EnumDataVariant {
339        enum_id: crate::types::EnumId,
340        variant_index: u32,
341        fields: Vec<AirPattern>,
342    },
343    /// Enum struct-variant dispatch. Same shape as `Struct` but tagged by
344    /// variant.
345    EnumStructVariant {
346        enum_id: crate::types::EnumId,
347        variant_index: u32,
348        fields: Vec<(u32, AirPattern)>,
349    },
350    /// Unit enum variant. Recursive-lowering counterpart of the legacy
351    /// `EnumVariant`.
352    EnumUnitVariant {
353        enum_id: crate::types::EnumId,
354        variant_index: u32,
355    },
356}
357
358/// Pattern type tags for extra array encoding.
359const PATTERN_WILDCARD: u32 = 0;
360const PATTERN_INT: u32 = 1;
361const PATTERN_BOOL: u32 = 2;
362const PATTERN_ENUM_VARIANT: u32 = 3;
363const PATTERN_BIND: u32 = 4;
364const PATTERN_TUPLE: u32 = 5;
365const PATTERN_STRUCT: u32 = 6;
366const PATTERN_ENUM_DATA: u32 = 7;
367const PATTERN_ENUM_STRUCT: u32 = 8;
368const PATTERN_ENUM_UNIT: u32 = 9;
369
370impl AirPattern {
371    /// Encode this arm as `[body_ref, ...pattern_tree]` appended to `out`.
372    /// The pattern tree is self-describing: decoding consumes exactly the
373    /// words this function produced (see `decode_pattern_tree`).
374    pub fn encode(&self, body: AirRef, out: &mut Vec<u32>) {
375        out.push(body.as_u32());
376        encode_pattern_tree(self, out);
377    }
378}
379
380fn encode_pattern_tree(pattern: &AirPattern, out: &mut Vec<u32>) {
381    match pattern {
382        AirPattern::Wildcard => out.push(PATTERN_WILDCARD),
383        AirPattern::Int(n) => {
384            out.push(PATTERN_INT);
385            out.push(*n as u32);
386            out.push((*n >> 32) as u32);
387        }
388        AirPattern::Bool(b) => {
389            out.push(PATTERN_BOOL);
390            out.push(if *b { 1 } else { 0 });
391        }
392        AirPattern::EnumVariant {
393            enum_id,
394            variant_index,
395        } => {
396            out.push(PATTERN_ENUM_VARIANT);
397            out.push(enum_id.0);
398            out.push(*variant_index);
399        }
400        AirPattern::Bind {
401            name,
402            is_mut,
403            inner,
404        } => {
405            out.push(PATTERN_BIND);
406            out.push(name.into_usize() as u32);
407            let flags = (if *is_mut { 1u32 } else { 0 }) | (if inner.is_some() { 2u32 } else { 0 });
408            out.push(flags);
409            if let Some(inner) = inner {
410                encode_pattern_tree(inner, out);
411            }
412        }
413        AirPattern::Tuple { elems } => {
414            out.push(PATTERN_TUPLE);
415            out.push(elems.len() as u32);
416            for e in elems {
417                encode_pattern_tree(e, out);
418            }
419        }
420        AirPattern::Struct { struct_id, fields } => {
421            out.push(PATTERN_STRUCT);
422            out.push(struct_id.0);
423            out.push(fields.len() as u32);
424            for (idx, p) in fields {
425                out.push(*idx);
426                encode_pattern_tree(p, out);
427            }
428        }
429        AirPattern::EnumDataVariant {
430            enum_id,
431            variant_index,
432            fields,
433        } => {
434            out.push(PATTERN_ENUM_DATA);
435            out.push(enum_id.0);
436            out.push(*variant_index);
437            out.push(fields.len() as u32);
438            for p in fields {
439                encode_pattern_tree(p, out);
440            }
441        }
442        AirPattern::EnumStructVariant {
443            enum_id,
444            variant_index,
445            fields,
446        } => {
447            out.push(PATTERN_ENUM_STRUCT);
448            out.push(enum_id.0);
449            out.push(*variant_index);
450            out.push(fields.len() as u32);
451            for (idx, p) in fields {
452                out.push(*idx);
453                encode_pattern_tree(p, out);
454            }
455        }
456        AirPattern::EnumUnitVariant {
457            enum_id,
458            variant_index,
459        } => {
460            out.push(PATTERN_ENUM_UNIT);
461            out.push(enum_id.0);
462            out.push(*variant_index);
463        }
464    }
465}
466
467/// Decode a single pattern tree from `data`, returning the pattern and
468/// the number of u32s consumed.
469fn decode_pattern_tree(data: &[u32]) -> (AirPattern, usize) {
470    let tag = data[0];
471    match tag {
472        PATTERN_WILDCARD => (AirPattern::Wildcard, 1),
473        PATTERN_INT => {
474            let lo = data[1] as i64;
475            let hi = (data[2] as i64) << 32;
476            (AirPattern::Int(lo | hi), 3)
477        }
478        PATTERN_BOOL => (AirPattern::Bool(data[1] != 0), 2),
479        PATTERN_ENUM_VARIANT => {
480            let enum_id = crate::types::EnumId(data[1]);
481            let variant_index = data[2];
482            (
483                AirPattern::EnumVariant {
484                    enum_id,
485                    variant_index,
486                },
487                3,
488            )
489        }
490        PATTERN_BIND => {
491            let name = lasso::Spur::try_from_usize(data[1] as usize)
492                .expect("invalid Spur encoding in pattern");
493            let flags = data[2];
494            let is_mut = (flags & 1) != 0;
495            let has_inner = (flags & 2) != 0;
496            let mut offset = 3;
497            let inner = if has_inner {
498                let (p, consumed) = decode_pattern_tree(&data[offset..]);
499                offset += consumed;
500                Some(Box::new(p))
501            } else {
502                None
503            };
504            (
505                AirPattern::Bind {
506                    name,
507                    is_mut,
508                    inner,
509                },
510                offset,
511            )
512        }
513        PATTERN_TUPLE => {
514            let n = data[1] as usize;
515            let mut offset = 2;
516            let mut elems = Vec::with_capacity(n);
517            for _ in 0..n {
518                let (p, consumed) = decode_pattern_tree(&data[offset..]);
519                elems.push(p);
520                offset += consumed;
521            }
522            (AirPattern::Tuple { elems }, offset)
523        }
524        PATTERN_STRUCT => {
525            let struct_id = StructId(data[1]);
526            let n = data[2] as usize;
527            let mut offset = 3;
528            let mut fields = Vec::with_capacity(n);
529            for _ in 0..n {
530                let field_index = data[offset];
531                offset += 1;
532                let (p, consumed) = decode_pattern_tree(&data[offset..]);
533                offset += consumed;
534                fields.push((field_index, p));
535            }
536            (AirPattern::Struct { struct_id, fields }, offset)
537        }
538        PATTERN_ENUM_DATA => {
539            let enum_id = crate::types::EnumId(data[1]);
540            let variant_index = data[2];
541            let n = data[3] as usize;
542            let mut offset = 4;
543            let mut fields = Vec::with_capacity(n);
544            for _ in 0..n {
545                let (p, consumed) = decode_pattern_tree(&data[offset..]);
546                offset += consumed;
547                fields.push(p);
548            }
549            (
550                AirPattern::EnumDataVariant {
551                    enum_id,
552                    variant_index,
553                    fields,
554                },
555                offset,
556            )
557        }
558        PATTERN_ENUM_STRUCT => {
559            let enum_id = crate::types::EnumId(data[1]);
560            let variant_index = data[2];
561            let n = data[3] as usize;
562            let mut offset = 4;
563            let mut fields = Vec::with_capacity(n);
564            for _ in 0..n {
565                let field_index = data[offset];
566                offset += 1;
567                let (p, consumed) = decode_pattern_tree(&data[offset..]);
568                offset += consumed;
569                fields.push((field_index, p));
570            }
571            (
572                AirPattern::EnumStructVariant {
573                    enum_id,
574                    variant_index,
575                    fields,
576                },
577                offset,
578            )
579        }
580        PATTERN_ENUM_UNIT => {
581            let enum_id = crate::types::EnumId(data[1]);
582            let variant_index = data[2];
583            (
584                AirPattern::EnumUnitVariant {
585                    enum_id,
586                    variant_index,
587                },
588                3,
589            )
590        }
591        _ => panic!("invalid pattern tag: {}", tag),
592    }
593}
594
595/// Iterator for reading match arms from the extra array.
596pub struct MatchArmIterator<'a> {
597    data: &'a [u32],
598    remaining: usize,
599}
600
601impl Iterator for MatchArmIterator<'_> {
602    type Item = (AirPattern, AirRef);
603
604    fn next(&mut self) -> Option<Self::Item> {
605        if self.remaining == 0 {
606            return None;
607        }
608        self.remaining -= 1;
609
610        let body = AirRef::from_raw(self.data[0]);
611        let (pattern, consumed) = decode_pattern_tree(&self.data[1..]);
612        self.data = &self.data[1 + consumed..];
613        Some((pattern, body))
614    }
615}
616
617impl fmt::Display for AirPattern {
618    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
619        match self {
620            AirPattern::Wildcard => write!(f, "_"),
621            AirPattern::Int(n) => write!(f, "{}", n),
622            AirPattern::Bool(b) => write!(f, "{}", b),
623            AirPattern::EnumVariant {
624                enum_id,
625                variant_index,
626            }
627            | AirPattern::EnumUnitVariant {
628                enum_id,
629                variant_index,
630            } => write!(f, "enum#{}::{}", enum_id.0, variant_index),
631            AirPattern::Bind {
632                name,
633                is_mut,
634                inner,
635            } => {
636                if *is_mut {
637                    write!(f, "mut ")?;
638                }
639                write!(f, "${}", name.into_usize())?;
640                if let Some(inner) = inner {
641                    write!(f, " @ {}", inner)?;
642                }
643                Ok(())
644            }
645            AirPattern::Tuple { elems } => {
646                write!(f, "(")?;
647                for (i, e) in elems.iter().enumerate() {
648                    if i > 0 {
649                        write!(f, ", ")?;
650                    }
651                    write!(f, "{}", e)?;
652                }
653                write!(f, ")")
654            }
655            AirPattern::Struct { struct_id, fields } => {
656                write!(f, "struct#{} {{ ", struct_id.0)?;
657                for (i, (idx, p)) in fields.iter().enumerate() {
658                    if i > 0 {
659                        write!(f, ", ")?;
660                    }
661                    write!(f, ".{}: {}", idx, p)?;
662                }
663                write!(f, " }}")
664            }
665            AirPattern::EnumDataVariant {
666                enum_id,
667                variant_index,
668                fields,
669            } => {
670                write!(f, "enum#{}::{}(", enum_id.0, variant_index)?;
671                for (i, p) in fields.iter().enumerate() {
672                    if i > 0 {
673                        write!(f, ", ")?;
674                    }
675                    write!(f, "{}", p)?;
676                }
677                write!(f, ")")
678            }
679            AirPattern::EnumStructVariant {
680                enum_id,
681                variant_index,
682                fields,
683            } => {
684                write!(f, "enum#{}::{} {{ ", enum_id.0, variant_index)?;
685                for (i, (idx, p)) in fields.iter().enumerate() {
686                    if i > 0 {
687                        write!(f, ", ")?;
688                    }
689                    write!(f, ".{}: {}", idx, p)?;
690                }
691                write!(f, " }}")
692            }
693        }
694    }
695}
696
697/// A reference to an instruction in the AIR.
698#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
699pub struct AirRef(u32);
700
701impl AirRef {
702    #[inline]
703    pub const fn from_raw(index: u32) -> Self {
704        Self(index)
705    }
706
707    #[inline]
708    pub const fn as_u32(self) -> u32 {
709        self.0
710    }
711}
712
713/// The complete AIR for a function.
714#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
715pub struct Air {
716    instructions: Vec<AirInst>,
717    /// Extra data for variable-length instruction payloads (args, elements, etc.)
718    extra: Vec<u32>,
719    /// The return type of this function
720    return_type: Type,
721    /// Storage for place projections (ADR-0030 Phase 8).
722    /// AirPlace instructions store (start, len) indices into this array.
723    projections: Vec<AirProjection>,
724    /// Storage for places (ADR-0030 Phase 8).
725    /// AirPlaceRef values are indices into this array.
726    places: Vec<AirPlace>,
727    /// Comptime value arguments captured at each `CallGeneric` site, keyed by
728    /// the `CallGeneric` instruction's index. Populated when the call has
729    /// `comptime n: i32` (or other value comptime) parameters; the
730    /// specialization pass reads these to build a unique
731    /// `(name, type_args, value_args)` key per call so per-call `comptime
732    /// if`/`@compile_error` checks fire only for the values they apply to.
733    #[serde(default)]
734    comptime_value_args: rustc_hash::FxHashMap<u32, Vec<crate::sema::ConstValue>>,
735    /// ADR-0084: per-`@spawn` site bookkeeping. Keyed by the spawn
736    /// instruction's index, carries the worker function name and the
737    /// arg/return types so codegen can emit the per-instantiation
738    /// thunk + `__gruel_thread_spawn` call without re-deriving them.
739    #[serde(default)]
740    spawn_targets: rustc_hash::FxHashMap<u32, SpawnTarget>,
741}
742
743/// ADR-0084: codegen bookkeeping for one `@spawn(fn, arg)` call.
744#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
745pub struct SpawnTarget {
746    /// Interned name of the worker function (resolves to a top-level
747    /// `fn` in the program).
748    pub worker_fn: lasso::Spur,
749    /// Worker's parameter type (= argument type at the spawn site).
750    pub arg_type: Type,
751    /// Worker's return type. Must be `≥ Send` per ADR-0084.
752    pub return_type: Type,
753}
754
755impl Air {
756    /// Create a new empty AIR.
757    pub fn new(return_type: Type) -> Self {
758        Self {
759            instructions: Vec::new(),
760            extra: Vec::new(),
761            return_type,
762            projections: Vec::new(),
763            places: Vec::new(),
764            comptime_value_args: rustc_hash::FxHashMap::default(),
765            spawn_targets: rustc_hash::FxHashMap::default(),
766        }
767    }
768
769    /// ADR-0084: record codegen bookkeeping for a `@spawn` site.
770    pub fn record_spawn_target(
771        &mut self,
772        air_ref: AirRef,
773        worker_fn: lasso::Spur,
774        arg_type: Type,
775        return_type: Type,
776    ) {
777        self.spawn_targets.insert(
778            air_ref.as_u32(),
779            SpawnTarget {
780                worker_fn,
781                arg_type,
782                return_type,
783            },
784        );
785    }
786
787    /// Look up the bookkeeping recorded for a `@spawn` site, if any.
788    pub fn spawn_target(&self, air_ref: AirRef) -> Option<&SpawnTarget> {
789        self.spawn_targets.get(&air_ref.as_u32())
790    }
791
792    /// Record the comptime value arguments captured at a `CallGeneric` site.
793    /// Indexed by the `CallGeneric` instruction's index in `instructions`.
794    pub fn set_comptime_value_args(
795        &mut self,
796        inst_index: u32,
797        value_args: Vec<crate::sema::ConstValue>,
798    ) {
799        if !value_args.is_empty() {
800            self.comptime_value_args.insert(inst_index, value_args);
801        }
802    }
803
804    /// Retrieve the comptime value arguments captured at the given
805    /// `CallGeneric` site, or an empty slice if none were recorded.
806    pub fn comptime_value_args(&self, inst_index: u32) -> &[crate::sema::ConstValue] {
807        self.comptime_value_args
808            .get(&inst_index)
809            .map(|v| v.as_slice())
810            .unwrap_or(&[])
811    }
812
813    /// Add an instruction and return its reference.
814    pub fn add_inst(&mut self, inst: AirInst) -> AirRef {
815        let index = self.instructions.len() as u32;
816        self.instructions.push(inst);
817        AirRef::from_raw(index)
818    }
819
820    /// Get an instruction by reference.
821    #[inline]
822    pub fn get(&self, inst_ref: AirRef) -> &AirInst {
823        &self.instructions[inst_ref.0 as usize]
824    }
825
826    /// The return type of this function.
827    #[inline]
828    pub fn return_type(&self) -> Type {
829        self.return_type
830    }
831
832    /// The number of instructions.
833    #[inline]
834    pub fn len(&self) -> usize {
835        self.instructions.len()
836    }
837
838    /// Whether there are no instructions.
839    #[inline]
840    pub fn is_empty(&self) -> bool {
841        self.instructions.is_empty()
842    }
843
844    /// Iterate over all instructions with their references.
845    pub fn iter(&self) -> impl Iterator<Item = (AirRef, &AirInst)> {
846        self.instructions
847            .iter()
848            .enumerate()
849            .map(|(i, inst)| (AirRef::from_raw(i as u32), inst))
850    }
851
852    /// Add extra data and return the start index.
853    pub fn add_extra(&mut self, data: &[u32]) -> u32 {
854        // Debug assertions for u32 overflow
855        debug_assert!(
856            self.extra.len() <= u32::MAX as usize,
857            "AIR extra data overflow: {} entries exceeds u32::MAX",
858            self.extra.len()
859        );
860        debug_assert!(
861            self.extra.len().saturating_add(data.len()) <= u32::MAX as usize,
862            "AIR extra data would overflow: {} + {} exceeds u32::MAX",
863            self.extra.len(),
864            data.len()
865        );
866
867        let start = self.extra.len() as u32;
868        self.extra.extend_from_slice(data);
869        start
870    }
871
872    /// Get extra data slice by start index and length.
873    #[inline]
874    pub fn get_extra(&self, start: u32, len: u32) -> &[u32] {
875        let start = start as usize;
876        let end = start + len as usize;
877        &self.extra[start..end]
878    }
879
880    // Helper methods for reading structured data from extra array
881
882    /// Get AirRefs from extra array (for blocks, array elements, intrinsic args, etc.).
883    #[inline]
884    pub fn get_air_refs(&self, start: u32, len: u32) -> impl Iterator<Item = AirRef> + '_ {
885        self.get_extra(start, len)
886            .iter()
887            .map(|&v| AirRef::from_raw(v))
888    }
889
890    /// Get call arguments from extra array.
891    /// Each call arg is encoded as 2 u32s: (air_ref, mode).
892    #[inline]
893    pub fn get_call_args(&self, start: u32, len: u32) -> impl Iterator<Item = AirCallArg> + '_ {
894        let data = self.get_extra(start, len * 2);
895        data.chunks_exact(2).map(|chunk| AirCallArg {
896            value: AirRef::from_raw(chunk[0]),
897            mode: AirArgMode::from_u32(chunk[1]),
898        })
899    }
900
901    /// Get match arms from extra array.
902    /// Each match arm is encoded based on pattern type plus the body AirRef.
903    #[inline]
904    pub fn get_match_arms(
905        &self,
906        start: u32,
907        len: u32,
908    ) -> impl Iterator<Item = (AirPattern, AirRef)> + '_ {
909        MatchArmIterator {
910            data: &self.extra[start as usize..],
911            remaining: len as usize,
912        }
913    }
914
915    /// Get struct init data from extra array.
916    /// Returns (field_refs_iterator, source_order_iterator).
917    #[inline]
918    pub fn get_struct_init(
919        &self,
920        fields_start: u32,
921        fields_len: u32,
922        source_order_start: u32,
923    ) -> (
924        impl Iterator<Item = AirRef> + '_,
925        impl Iterator<Item = usize> + '_,
926    ) {
927        let fields = self.get_air_refs(fields_start, fields_len);
928        let source_order = self
929            .get_extra(source_order_start, fields_len)
930            .iter()
931            .map(|&v| v as usize);
932        (fields, source_order)
933    }
934
935    /// Remap string constant IDs using the provided mapping function.
936    ///
937    /// This is used after parallel function analysis to convert local string IDs
938    /// (per-function) to global string IDs (across all functions). The mapping
939    /// function takes a local string ID and returns the global string ID.
940    pub fn remap_string_ids<F>(&mut self, map_fn: F)
941    where
942        F: Fn(u32) -> u32,
943    {
944        for inst in &mut self.instructions {
945            if let AirInstData::StringConst(ref mut id) = inst.data {
946                *id = map_fn(*id);
947            }
948        }
949    }
950
951    /// Remap byte-blob IDs after merging per-function bytes pools into a
952    /// single global pool (mirrors `remap_string_ids`).
953    pub fn remap_bytes_ids<F>(&mut self, map_fn: F)
954    where
955        F: Fn(u32) -> u32,
956    {
957        for inst in &mut self.instructions {
958            if let AirInstData::BytesConst(ref mut id) = inst.data {
959                *id = map_fn(*id);
960            }
961        }
962    }
963
964    /// Get a reference to all instructions.
965    #[inline]
966    pub fn instructions(&self) -> &[AirInst] {
967        &self.instructions
968    }
969
970    /// Rewrite the data of an instruction at a given index.
971    ///
972    /// This is used by the specialization pass to rewrite `CallGeneric` to `Call`.
973    /// The type and span are preserved.
974    pub fn rewrite_inst_data(&mut self, index: usize, new_data: AirInstData) {
975        self.instructions[index].data = new_data;
976    }
977
978    // ========================================================================
979    // Place operations (ADR-0030 Phase 8)
980    // ========================================================================
981
982    /// Add projections to the projections array and return (start, len).
983    ///
984    /// Used for PlaceRead and PlaceWrite instructions.
985    pub fn push_projections(
986        &mut self,
987        projs: impl IntoIterator<Item = AirProjection>,
988    ) -> (u32, u32) {
989        let start = self.projections.len() as u32;
990        self.projections.extend(projs);
991        let len = self.projections.len() as u32 - start;
992        (start, len)
993    }
994
995    /// Get a slice from the projections array.
996    #[inline]
997    pub fn get_projections(&self, start: u32, len: u32) -> &[AirProjection] {
998        &self.projections[start as usize..(start + len) as usize]
999    }
1000
1001    /// Get projections for a place.
1002    #[inline]
1003    pub fn get_place_projections(&self, place: &AirPlace) -> &[AirProjection] {
1004        self.get_projections(place.projections_start, place.projections_len)
1005    }
1006
1007    /// Create a place with the given base and projections.
1008    ///
1009    /// This adds the projections to the projections array and returns a PlaceRef
1010    /// that references the place.
1011    pub fn make_place(
1012        &mut self,
1013        base: AirPlaceBase,
1014        projs: impl IntoIterator<Item = AirProjection>,
1015    ) -> AirPlaceRef {
1016        let (projections_start, projections_len) = self.push_projections(projs);
1017        let place = AirPlace {
1018            base,
1019            projections_start,
1020            projections_len,
1021        };
1022        let index = self.places.len() as u32;
1023        self.places.push(place);
1024        AirPlaceRef::from_raw(index)
1025    }
1026
1027    /// Get a place by reference.
1028    #[inline]
1029    pub fn get_place(&self, place_ref: AirPlaceRef) -> &AirPlace {
1030        &self.places[place_ref.0 as usize]
1031    }
1032
1033    /// Get all places.
1034    #[inline]
1035    pub fn places(&self) -> &[AirPlace] {
1036        &self.places
1037    }
1038
1039    /// Get all projections.
1040    #[inline]
1041    pub fn projections(&self) -> &[AirProjection] {
1042        &self.projections
1043    }
1044}
1045
1046/// A single AIR instruction.
1047#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1048pub struct AirInst {
1049    pub data: AirInstData,
1050    pub ty: Type,
1051    pub span: Span,
1052}
1053
1054/// AIR instruction data - fully typed operations.
1055#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1056pub enum AirInstData {
1057    /// Integer constant (typed)
1058    Const(u64),
1059
1060    /// Floating-point constant, stored as f64 bits via `f64::to_bits()`.
1061    FloatConst(u64),
1062
1063    /// Boolean constant
1064    BoolConst(bool),
1065
1066    /// String constant (index into string table)
1067    StringConst(u32),
1068
1069    /// Byte-blob constant (index into the bytes table). Typed as `Slice(u8)`;
1070    /// the slice borrows from a binary-baked global at codegen.
1071    BytesConst(u32),
1072
1073    /// Unit constant
1074    UnitConst,
1075
1076    /// Type constant - a compile-time type value.
1077    /// This is used for comptime type parameters (e.g., passing `i32` to `fn foo(comptime T: type)`).
1078    /// The contained Type is the type being passed as a value.
1079    /// This instruction has type `Type::COMPTIME_TYPE` and is erased during specialization.
1080    TypeConst(crate::Type),
1081
1082    /// Binary operation: arithmetic, comparison, logical, or bitwise.
1083    /// Logical `And`/`Or` are short-circuiting; they are lowered to
1084    /// control flow during CFG construction.
1085    Bin(BinOp, AirRef, AirRef),
1086
1087    /// Unary operation: `-`, `!`, or `~`.
1088    Unary(UnaryOp, AirRef),
1089
1090    /// Reference construction (ADR-0062): `&x` (`is_mut = false`) or
1091    /// `&mut x` (`is_mut = true`). Operand must be an lvalue. Lowers to
1092    /// the address of the operand's slot.
1093    MakeRef { operand: AirRef, is_mut: bool },
1094
1095    /// Slice construction by borrow over a range subscript (ADR-0064).
1096    ///
1097    /// Lowered from `&arr[range]` / `&mut arr[range]`. `base` must
1098    /// designate an array place. `lo` defaults to `0`, `hi` defaults to
1099    /// `arr.len()` when absent.
1100    MakeSlice {
1101        base: AirRef,
1102        lo: Option<AirRef>,
1103        hi: Option<AirRef>,
1104        is_mut: bool,
1105    },
1106
1107    // Control flow
1108    /// Conditional branch
1109    Branch {
1110        cond: AirRef,
1111        then_value: AirRef,
1112        else_value: Option<AirRef>,
1113    },
1114
1115    /// While loop
1116    Loop { cond: AirRef, body: AirRef },
1117
1118    /// Infinite loop (produces Never type)
1119    InfiniteLoop { body: AirRef },
1120
1121    /// Match expression
1122    Match {
1123        /// The value being matched (scrutinee)
1124        scrutinee: AirRef,
1125        /// Start index into extra array for match arms
1126        arms_start: u32,
1127        /// Number of match arms
1128        arms_len: u32,
1129    },
1130
1131    /// Break: exits the innermost loop
1132    Break,
1133
1134    /// Continue: jumps to the next iteration of the innermost loop
1135    Continue,
1136
1137    // Variable operations
1138    /// Allocate local variable with initial value
1139    /// Returns the slot index
1140    Alloc {
1141        /// Local variable slot index (0, 1, 2, ...)
1142        slot: u32,
1143        /// Initial value
1144        init: AirRef,
1145    },
1146
1147    /// Load value from local variable
1148    Load {
1149        /// Local variable slot index
1150        slot: u32,
1151    },
1152
1153    /// Store value to local variable
1154    Store {
1155        /// Local variable slot index
1156        slot: u32,
1157        /// Value to store
1158        value: AirRef,
1159        /// True if the slot held a live (non-moved) value before this assignment.
1160        /// When true, the old value must be dropped before the new value is written.
1161        /// When false (value was moved or this is initial allocation), no drop is needed.
1162        had_live_value: bool,
1163    },
1164
1165    /// Store value to a parameter (for inout params)
1166    ParamStore {
1167        /// Parameter's ABI slot (relative to params, not locals)
1168        param_slot: u32,
1169        /// Value to store
1170        value: AirRef,
1171    },
1172
1173    /// Bare-name write-through for a `MutRef(T)`-typed local binding
1174    /// (ADR-0076 Phase 3). Loads the pointer held in the local's slot and
1175    /// stores `value` (typed as the referent `T`) through that pointer.
1176    RefStore {
1177        /// Slot index of the local (whose stored value is the pointer)
1178        slot: u32,
1179        /// Value to store at the pointee
1180        value: AirRef,
1181    },
1182
1183    /// Return from function (None for `return;` in unit-returning functions)
1184    Ret(Option<AirRef>),
1185
1186    /// Function call
1187    Call {
1188        /// Function name (interned symbol)
1189        name: Spur,
1190        /// Start index into extra array for arguments
1191        args_start: u32,
1192        /// Number of arguments
1193        args_len: u32,
1194    },
1195
1196    /// Generic function call - requires specialization before codegen.
1197    ///
1198    /// This is emitted when calling a function with `comptime T: type` parameters.
1199    /// During a post-analysis specialization pass, this is rewritten to a regular
1200    /// `Call` to a specialized version of the function (e.g., `identity__i32`).
1201    ///
1202    /// The type_args are encoded in the extra array as raw Type discriminant values.
1203    /// The runtime args (non-comptime) are also in the extra array, after type_args.
1204    CallGeneric {
1205        /// Base function name (interned symbol)
1206        name: Spur,
1207        /// Start index into extra array for type arguments (raw Type values)
1208        type_args_start: u32,
1209        /// Number of type arguments
1210        type_args_len: u32,
1211        /// Start index into extra array for runtime arguments
1212        args_start: u32,
1213        /// Number of runtime arguments
1214        args_len: u32,
1215    },
1216
1217    /// Intrinsic call (e.g., @dbg)
1218    Intrinsic {
1219        /// Intrinsic name (without @, interned)
1220        name: Spur,
1221        /// Start index into extra array for arguments
1222        args_start: u32,
1223        /// Number of arguments
1224        args_len: u32,
1225    },
1226
1227    /// Reference to a function parameter
1228    Param {
1229        /// Parameter index (0-based)
1230        index: u32,
1231    },
1232
1233    /// Block expression with statements and final value.
1234    /// Used to group side-effect statements with their result value,
1235    /// enabling demand-driven lowering for short-circuit evaluation.
1236    Block {
1237        /// Start index into extra array for statement refs
1238        stmts_start: u32,
1239        /// Number of statements
1240        stmts_len: u32,
1241        /// The block's resulting value
1242        value: AirRef,
1243    },
1244
1245    // Struct operations
1246    /// Create a new struct instance with initialized fields
1247    StructInit {
1248        /// The struct type being created
1249        struct_id: StructId,
1250        /// Start index into extra array for field refs (in declaration order)
1251        fields_start: u32,
1252        /// Number of fields
1253        fields_len: u32,
1254        /// Start index into extra array for source order indices
1255        /// Each entry is an index into fields, specifying evaluation order
1256        source_order_start: u32,
1257    },
1258
1259    /// Load a field from a struct value
1260    FieldGet {
1261        /// The struct value
1262        base: AirRef,
1263        /// The struct type
1264        struct_id: StructId,
1265        /// Field index (0-based, in declaration order)
1266        field_index: u32,
1267    },
1268
1269    /// Store a value to a struct field (for local variables)
1270    FieldSet {
1271        /// The struct variable slot
1272        slot: u32,
1273        /// The struct type
1274        struct_id: StructId,
1275        /// Field index (0-based, in declaration order)
1276        field_index: u32,
1277        /// Value to store
1278        value: AirRef,
1279    },
1280
1281    /// Store a value to a struct field (for parameters, including inout)
1282    ParamFieldSet {
1283        /// The parameter's ABI slot (relative to params, not locals)
1284        param_slot: u32,
1285        /// Offset within the struct for nested field access (e.g., p.inner.x)
1286        inner_offset: u32,
1287        /// The struct type containing the field being set
1288        struct_id: StructId,
1289        /// Field index (0-based, in declaration order)
1290        field_index: u32,
1291        /// Value to store
1292        value: AirRef,
1293    },
1294
1295    // Array operations
1296    /// Create a new array with initialized elements.
1297    /// The array type is stored in `AirInst.ty` as `Type::new_array(...)`.
1298    ArrayInit {
1299        /// Start index into extra array for element refs
1300        elems_start: u32,
1301        /// Number of elements
1302        elems_len: u32,
1303    },
1304
1305    /// Load an element from an array.
1306    /// The array type is stored in `AirInst.ty`.
1307    IndexGet {
1308        /// The array value
1309        base: AirRef,
1310        /// The array type (for bounds checking and element size)
1311        array_type: Type,
1312        /// Index expression
1313        index: AirRef,
1314    },
1315
1316    /// Store a value to an array element.
1317    /// The array type is stored in `AirInst.ty`.
1318    IndexSet {
1319        /// The array variable slot
1320        slot: u32,
1321        /// The array type (for bounds checking and element size)
1322        array_type: Type,
1323        /// Index expression
1324        index: AirRef,
1325        /// Value to store
1326        value: AirRef,
1327    },
1328
1329    /// Store a value to an array element of an inout parameter.
1330    /// The array type is stored in `AirInst.ty`.
1331    ParamIndexSet {
1332        /// The parameter's ABI slot (relative to params, not locals)
1333        param_slot: u32,
1334        /// The array type (for bounds checking and element size)
1335        array_type: Type,
1336        /// Index expression
1337        index: AirRef,
1338        /// Value to store
1339        value: AirRef,
1340    },
1341
1342    // Place operations (ADR-0030 Phase 8)
1343    /// Read a value from a memory location.
1344    ///
1345    /// This unifies Load, IndexGet, and FieldGet into a single instruction
1346    /// that can handle arbitrarily nested access patterns like `arr[i].field`.
1347    /// Eventually, the separate FieldGet/IndexGet instructions will be removed.
1348    PlaceRead {
1349        /// Reference to the place to read from
1350        place: AirPlaceRef,
1351    },
1352
1353    /// Write a value to a memory location.
1354    ///
1355    /// This unifies Store, IndexSet, ParamIndexSet, FieldSet, and ParamFieldSet
1356    /// into a single instruction that can handle nested writes.
1357    /// Eventually, the separate *Set instructions will be removed.
1358    PlaceWrite {
1359        /// Reference to the place to write to
1360        place: AirPlaceRef,
1361        /// Value to write
1362        value: AirRef,
1363    },
1364
1365    // Enum operations
1366    /// Create an enum variant value (unit variant or any variant of a unit-only enum)
1367    EnumVariant {
1368        /// The enum type ID
1369        enum_id: crate::types::EnumId,
1370        /// The variant index (0-based)
1371        variant_index: u32,
1372    },
1373
1374    /// Create a data enum variant value with associated field values.
1375    /// Used when the enum has at least one data variant.
1376    /// Field AirRefs are stored in the extra array at [fields_start..fields_start+fields_len].
1377    EnumCreate {
1378        /// The enum type ID
1379        enum_id: crate::types::EnumId,
1380        /// The variant index (0-based)
1381        variant_index: u32,
1382        /// Start index into extra array for field values
1383        fields_start: u32,
1384        /// Number of field values
1385        fields_len: u32,
1386    },
1387
1388    /// Extract a field value from an enum variant's payload.
1389    /// Used in data variant match arm bodies to bind pattern variables.
1390    EnumPayloadGet {
1391        /// The enum value to extract from
1392        base: AirRef,
1393        /// The variant index (must match the enclosing arm's pattern)
1394        variant_index: u32,
1395        /// The field index within the variant
1396        field_index: u32,
1397    },
1398
1399    // Type conversion operations
1400    /// Integer cast: convert between integer types with runtime range check.
1401    /// Panics if the value cannot be represented in the target type.
1402    /// The target type is stored in AirInst.ty.
1403    IntCast {
1404        /// The value to cast
1405        value: AirRef,
1406        /// The source type (for determining signedness and size)
1407        from_ty: Type,
1408    },
1409
1410    /// Float cast: convert between floating-point types (fptrunc/fpext).
1411    /// The target type is stored in AirInst.ty.
1412    FloatCast {
1413        /// The value to cast
1414        value: AirRef,
1415        /// The source float type
1416        from_ty: Type,
1417    },
1418
1419    /// Integer to float conversion (sitofp/uitofp).
1420    /// The target type is stored in AirInst.ty.
1421    IntToFloat {
1422        /// The integer value to convert
1423        value: AirRef,
1424        /// The source integer type (for determining signedness)
1425        from_ty: Type,
1426    },
1427
1428    /// Float to integer conversion (fptosi/fptoui) with runtime range check.
1429    /// Panics if the value is NaN or out of range of the target integer type.
1430    /// The target type is stored in AirInst.ty.
1431    FloatToInt {
1432        /// The float value to convert
1433        value: AirRef,
1434        /// The source float type
1435        from_ty: Type,
1436    },
1437
1438    // Drop/destructor operations
1439    /// Drop a value, running its destructor if the type has one.
1440    /// For trivially droppable types, this is a no-op.
1441    /// The type is stored in the AirInst.ty field.
1442    Drop {
1443        /// The value to drop
1444        value: AirRef,
1445    },
1446
1447    // Storage liveness operations (for drop elaboration)
1448    /// Marks that a local slot becomes live (storage allocated).
1449    /// Emitted when a variable binding is created.
1450    /// The type is stored in AirInst.ty for drop elaboration.
1451    StorageLive {
1452        /// The slot that becomes live
1453        slot: u32,
1454    },
1455
1456    /// Marks that a local slot becomes dead (storage can be deallocated).
1457    /// Emitted at scope exit for variables declared in that scope.
1458    /// The type is stored in AirInst.ty for drop elaboration.
1459    /// Drop elaboration will insert a Drop before this if the type needs drop
1460    /// and the value wasn't moved.
1461    StorageDead {
1462        /// The slot that becomes dead
1463        slot: u32,
1464    },
1465
1466    /// Coerce a concrete value to an interface fat pointer (ADR-0056).
1467    ///
1468    /// `value` is a place of concrete type `Foo`. The result is a fat
1469    /// pointer `(data: &Foo, vtable: &VTable_Foo_Iface)`. Codegen (Phase 4d)
1470    /// materializes the data pointer and the vtable global.
1471    ///
1472    /// The result type (`AirInst.ty`) is `Type::new_interface(interface_id)`.
1473    MakeInterfaceRef {
1474        /// The concrete value being coerced. Must be a place expression
1475        /// (parameter ref, var ref, etc.) — codegen takes its address.
1476        value: AirRef,
1477        /// The concrete struct ID of `value`. Used by codegen to locate the
1478        /// `(struct_id, interface_id)` vtable.
1479        struct_id: crate::types::StructId,
1480        /// The target interface.
1481        interface_id: crate::types::InterfaceId,
1482    },
1483
1484    /// Dynamic-dispatch method call on an interface receiver (ADR-0056).
1485    ///
1486    /// `recv` is a value of type `Type::new_interface(iid)` (a fat pointer).
1487    /// Codegen loads function-pointer slot `slot` from `recv`'s vtable and
1488    /// calls it, passing `recv.data_ptr` as the receiver and the regular
1489    /// `args` afterwards.
1490    ///
1491    /// The result type is the interface method's declared return type.
1492    MethodCallDyn {
1493        /// The interface being dispatched on (used to type the vtable).
1494        interface_id: crate::types::InterfaceId,
1495        /// The vtable slot index (corresponds to the method's declaration
1496        /// order in the interface).
1497        slot: u32,
1498        /// The fat-pointer receiver (must have type `Type::new_interface(iid)`).
1499        recv: AirRef,
1500        /// Start of additional args (excluding the receiver) in the extra
1501        /// array. Encoded as `[value, mode]` pairs like ordinary `Call`.
1502        args_start: u32,
1503        /// Number of additional args.
1504        args_len: u32,
1505    },
1506}
1507
1508impl fmt::Display for AirRef {
1509    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1510        write!(f, "%{}", self.0)
1511    }
1512}
1513
1514impl fmt::Display for Air {
1515    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1516        writeln!(f, "air (return_type: {}) {{", self.return_type.name())?;
1517        for (inst_ref, inst) in self.iter() {
1518            write!(f, "    {} : {} = ", inst_ref, inst.ty.name())?;
1519            match &inst.data {
1520                AirInstData::Const(v) => writeln!(f, "const {}", v)?,
1521                AirInstData::FloatConst(bits) => writeln!(f, "const {}", f64::from_bits(*bits))?,
1522                AirInstData::BoolConst(v) => writeln!(f, "const {}", v)?,
1523                AirInstData::StringConst(idx) => writeln!(f, "string_const @{}", idx)?,
1524                AirInstData::BytesConst(idx) => writeln!(f, "bytes_const @{}", idx)?,
1525                AirInstData::UnitConst => writeln!(f, "const ()")?,
1526                AirInstData::TypeConst(ty) => writeln!(f, "type_const {}", ty.name())?,
1527                AirInstData::Bin(op, lhs, rhs) => writeln!(f, "{} {}, {}", op, lhs, rhs)?,
1528                AirInstData::Unary(op, operand) => writeln!(f, "{} {}", op, operand)?,
1529                AirInstData::MakeRef { operand, is_mut } => writeln!(
1530                    f,
1531                    "make_ref{} {}",
1532                    if *is_mut { "_mut" } else { "" },
1533                    operand
1534                )?,
1535                AirInstData::MakeSlice {
1536                    base,
1537                    lo,
1538                    hi,
1539                    is_mut,
1540                } => {
1541                    write!(
1542                        f,
1543                        "make_slice{} {}",
1544                        if *is_mut { "_mut" } else { "" },
1545                        base
1546                    )?;
1547                    if let Some(lo) = lo {
1548                        write!(f, ", lo={}", lo)?;
1549                    }
1550                    if let Some(hi) = hi {
1551                        write!(f, ", hi={}", hi)?;
1552                    }
1553                    writeln!(f)?;
1554                }
1555                AirInstData::Branch {
1556                    cond,
1557                    then_value,
1558                    else_value,
1559                } => {
1560                    if let Some(else_v) = else_value {
1561                        writeln!(f, "branch {}, {}, {}", cond, then_value, else_v)?
1562                    } else {
1563                        writeln!(f, "branch {}, {}", cond, then_value)?
1564                    }
1565                }
1566                AirInstData::Loop { cond, body } => writeln!(f, "loop {}, {}", cond, body)?,
1567                AirInstData::InfiniteLoop { body } => writeln!(f, "infinite_loop {}", body)?,
1568                AirInstData::Match {
1569                    scrutinee,
1570                    arms_start,
1571                    arms_len,
1572                } => {
1573                    write!(f, "match {} {{ ", scrutinee)?;
1574                    for (i, (pat, body)) in self.get_match_arms(*arms_start, *arms_len).enumerate()
1575                    {
1576                        if i > 0 {
1577                            write!(f, ", ")?;
1578                        }
1579                        write!(f, "{} => {}", pat, body)?;
1580                    }
1581                    writeln!(f, " }}")?;
1582                }
1583                AirInstData::Break => writeln!(f, "break")?,
1584                AirInstData::Continue => writeln!(f, "continue")?,
1585                AirInstData::Alloc { slot, init } => writeln!(f, "alloc ${} = {}", slot, init)?,
1586                AirInstData::Load { slot } => writeln!(f, "load ${}", slot)?,
1587                AirInstData::Store { slot, value, .. } => {
1588                    writeln!(f, "store ${} = {}", slot, value)?
1589                }
1590                AirInstData::ParamStore { param_slot, value } => {
1591                    writeln!(f, "param_store %{} = {}", param_slot, value)?
1592                }
1593                AirInstData::RefStore { slot, value } => {
1594                    writeln!(f, "ref_store ${} = {}", slot, value)?
1595                }
1596                AirInstData::Ret(inner) => {
1597                    if let Some(inner) = inner {
1598                        writeln!(f, "ret {}", inner)?
1599                    } else {
1600                        writeln!(f, "ret")?
1601                    }
1602                }
1603                AirInstData::Call {
1604                    name,
1605                    args_start,
1606                    args_len,
1607                } => {
1608                    write!(f, "call @{}(", name.into_usize())?;
1609                    for (i, arg) in self.get_call_args(*args_start, *args_len).enumerate() {
1610                        if i > 0 {
1611                            write!(f, ", ")?;
1612                        }
1613                        write!(f, "{}", arg)?;
1614                    }
1615                    writeln!(f, ")")?;
1616                }
1617                AirInstData::CallGeneric {
1618                    name,
1619                    type_args_start,
1620                    type_args_len,
1621                    args_start,
1622                    args_len,
1623                } => {
1624                    write!(f, "call_generic @{}<", name.into_usize())?;
1625                    // Show type arguments
1626                    for i in 0..*type_args_len {
1627                        if i > 0 {
1628                            write!(f, ", ")?;
1629                        }
1630                        let type_val = self.extra[(*type_args_start + i) as usize];
1631                        write!(f, "type#{}", type_val)?;
1632                    }
1633                    write!(f, ">(")?;
1634                    // Show runtime arguments
1635                    for (i, arg) in self.get_call_args(*args_start, *args_len).enumerate() {
1636                        if i > 0 {
1637                            write!(f, ", ")?;
1638                        }
1639                        write!(f, "{}", arg)?;
1640                    }
1641                    writeln!(f, ")")?;
1642                }
1643                AirInstData::Intrinsic {
1644                    name,
1645                    args_start,
1646                    args_len,
1647                } => {
1648                    write!(f, "intrinsic @sym:{}(", name.into_usize())?;
1649                    for (i, arg) in self.get_air_refs(*args_start, *args_len).enumerate() {
1650                        if i > 0 {
1651                            write!(f, ", ")?;
1652                        }
1653                        write!(f, "{}", arg)?;
1654                    }
1655                    writeln!(f, ")")?;
1656                }
1657                AirInstData::Param { index } => writeln!(f, "param {}", index)?,
1658                AirInstData::Block {
1659                    stmts_start,
1660                    stmts_len,
1661                    value,
1662                } => {
1663                    write!(f, "block [")?;
1664                    for (i, s) in self.get_air_refs(*stmts_start, *stmts_len).enumerate() {
1665                        if i > 0 {
1666                            write!(f, ", ")?;
1667                        }
1668                        write!(f, "{}", s)?;
1669                    }
1670                    writeln!(f, "], {}", value)?;
1671                }
1672                AirInstData::StructInit {
1673                    struct_id,
1674                    fields_start,
1675                    fields_len,
1676                    source_order_start,
1677                } => {
1678                    write!(f, "struct_init #{} {{", struct_id.0)?;
1679                    let (fields, source_order) =
1680                        self.get_struct_init(*fields_start, *fields_len, *source_order_start);
1681                    for (i, field) in fields.enumerate() {
1682                        if i > 0 {
1683                            write!(f, ", ")?;
1684                        }
1685                        write!(f, "{}", field)?;
1686                    }
1687                    write!(f, "}} eval_order=[")?;
1688                    for (i, idx) in source_order.enumerate() {
1689                        if i > 0 {
1690                            write!(f, ", ")?;
1691                        }
1692                        write!(f, "{}", idx)?;
1693                    }
1694                    writeln!(f, "]")?;
1695                }
1696                AirInstData::FieldGet {
1697                    base,
1698                    struct_id,
1699                    field_index,
1700                } => {
1701                    writeln!(f, "field_get {}.#{}.{}", base, struct_id.0, field_index)?;
1702                }
1703                AirInstData::FieldSet {
1704                    slot,
1705                    struct_id,
1706                    field_index,
1707                    value,
1708                } => {
1709                    writeln!(
1710                        f,
1711                        "field_set ${}.#{}.{} = {}",
1712                        slot, struct_id.0, field_index, value
1713                    )?;
1714                }
1715                AirInstData::ParamFieldSet {
1716                    param_slot,
1717                    inner_offset,
1718                    struct_id,
1719                    field_index,
1720                    value,
1721                } => {
1722                    writeln!(
1723                        f,
1724                        "param_field_set %{}+{}.#{}.{} = {}",
1725                        param_slot, inner_offset, struct_id.0, field_index, value
1726                    )?;
1727                }
1728                AirInstData::ArrayInit {
1729                    elems_start,
1730                    elems_len,
1731                } => {
1732                    write!(f, "array_init [")?;
1733                    for (i, elem) in self.get_air_refs(*elems_start, *elems_len).enumerate() {
1734                        if i > 0 {
1735                            write!(f, ", ")?;
1736                        }
1737                        write!(f, "{}", elem)?;
1738                    }
1739                    writeln!(f, "]")?;
1740                }
1741                AirInstData::IndexGet {
1742                    base,
1743                    array_type,
1744                    index,
1745                } => {
1746                    writeln!(f, "index_get {}({})[{}]", base, array_type.name(), index)?;
1747                }
1748                AirInstData::IndexSet {
1749                    slot,
1750                    array_type,
1751                    index,
1752                    value,
1753                } => {
1754                    writeln!(
1755                        f,
1756                        "index_set ${}({})[{}] = {}",
1757                        slot,
1758                        array_type.name(),
1759                        index,
1760                        value
1761                    )?;
1762                }
1763                AirInstData::ParamIndexSet {
1764                    param_slot,
1765                    array_type,
1766                    index,
1767                    value,
1768                } => {
1769                    writeln!(
1770                        f,
1771                        "param_index_set param{}({})[{}] = {}",
1772                        param_slot,
1773                        array_type.name(),
1774                        index,
1775                        value
1776                    )?;
1777                }
1778                AirInstData::PlaceRead { place } => {
1779                    write!(f, "place_read ")?;
1780                    self.fmt_place(f, *place)?;
1781                    writeln!(f)?;
1782                }
1783                AirInstData::PlaceWrite { place, value } => {
1784                    write!(f, "place_write ")?;
1785                    self.fmt_place(f, *place)?;
1786                    writeln!(f, " = {}", value)?;
1787                }
1788                AirInstData::EnumVariant {
1789                    enum_id,
1790                    variant_index,
1791                } => {
1792                    writeln!(f, "enum_variant #{}::{}", enum_id.0, variant_index)?;
1793                }
1794                AirInstData::EnumCreate {
1795                    enum_id,
1796                    variant_index,
1797                    fields_start,
1798                    fields_len,
1799                } => {
1800                    let field_strs: Vec<String> = self
1801                        .get_air_refs(*fields_start, *fields_len)
1802                        .map(|r| format!("{}", r))
1803                        .collect();
1804                    writeln!(
1805                        f,
1806                        "enum_create #{}::{}({})",
1807                        enum_id.0,
1808                        variant_index,
1809                        field_strs.join(", ")
1810                    )?;
1811                }
1812                AirInstData::EnumPayloadGet {
1813                    base,
1814                    variant_index,
1815                    field_index,
1816                } => {
1817                    writeln!(
1818                        f,
1819                        "enum_payload_get {} variant={} field={}",
1820                        base, variant_index, field_index
1821                    )?;
1822                }
1823                AirInstData::IntCast { value, from_ty } => {
1824                    writeln!(f, "intcast {} from {}", value, from_ty.name())?;
1825                }
1826                AirInstData::FloatCast { value, from_ty } => {
1827                    writeln!(f, "floatcast {} from {}", value, from_ty.name())?;
1828                }
1829                AirInstData::IntToFloat { value, from_ty } => {
1830                    writeln!(f, "int_to_float {} from {}", value, from_ty.name())?;
1831                }
1832                AirInstData::FloatToInt { value, from_ty } => {
1833                    writeln!(f, "float_to_int {} from {}", value, from_ty.name())?;
1834                }
1835                AirInstData::Drop { value } => {
1836                    writeln!(f, "drop {}", value)?;
1837                }
1838                AirInstData::StorageLive { slot } => {
1839                    writeln!(f, "storage_live ${}", slot)?;
1840                }
1841                AirInstData::StorageDead { slot } => {
1842                    writeln!(f, "storage_dead ${}", slot)?;
1843                }
1844                AirInstData::MakeInterfaceRef {
1845                    value,
1846                    struct_id,
1847                    interface_id,
1848                } => {
1849                    writeln!(
1850                        f,
1851                        "make_interface_ref {} (struct=#{}, iface=#{})",
1852                        value, struct_id.0, interface_id.0
1853                    )?;
1854                }
1855                AirInstData::MethodCallDyn {
1856                    interface_id,
1857                    slot,
1858                    recv,
1859                    args_len,
1860                    ..
1861                } => {
1862                    writeln!(
1863                        f,
1864                        "method_call_dyn iface=#{} slot={} recv={} (+{} args)",
1865                        interface_id.0, slot, recv, args_len
1866                    )?;
1867                }
1868            }
1869        }
1870        writeln!(f, "}}")
1871    }
1872}
1873
1874impl Air {
1875    /// Format a place for display, showing the base and projections.
1876    fn fmt_place(&self, f: &mut fmt::Formatter<'_>, place_ref: AirPlaceRef) -> fmt::Result {
1877        let place = self.get_place(place_ref);
1878
1879        // Write the base
1880        match place.base {
1881            AirPlaceBase::Local(slot) => write!(f, "${}", slot)?,
1882            AirPlaceBase::Param(slot) => write!(f, "param%{}", slot)?,
1883        }
1884
1885        // Write the projections
1886        let projections = self.get_place_projections(place);
1887        for proj in projections {
1888            match proj {
1889                AirProjection::Field {
1890                    struct_id,
1891                    field_index,
1892                } => {
1893                    write!(f, ".#{}.{}", struct_id.0, field_index)?;
1894                }
1895                AirProjection::Index { array_type, index } => {
1896                    write!(f, "({})[{}]", array_type.name(), index)?;
1897                }
1898            }
1899        }
1900
1901        Ok(())
1902    }
1903}
1904
1905#[cfg(test)]
1906mod tests {
1907    use super::*;
1908
1909    #[test]
1910    fn test_air_ref_size() {
1911        assert_eq!(std::mem::size_of::<AirRef>(), 4);
1912    }
1913
1914    #[test]
1915    fn test_air_inst_size() {
1916        // Document actual sizes for future reference.
1917        // If this test fails, update the const assertions at the top of this file.
1918        let air_inst_size = std::mem::size_of::<AirInst>();
1919        let air_inst_data_size = std::mem::size_of::<AirInstData>();
1920
1921        // These assertions document the current sizes.
1922        // If the layout changes, update both these values and the const assertions.
1923        assert!(
1924            air_inst_size <= 48,
1925            "AirInst grew beyond 48 bytes: {}",
1926            air_inst_size
1927        );
1928        assert!(
1929            air_inst_data_size <= 32,
1930            "AirInstData grew beyond 32 bytes: {}",
1931            air_inst_data_size
1932        );
1933    }
1934
1935    #[test]
1936    fn test_add_and_get_inst() {
1937        let mut air = Air::new(Type::I32);
1938        let inst = AirInst {
1939            data: AirInstData::Const(42),
1940            ty: Type::I32,
1941            span: Span::new(0, 2),
1942        };
1943        let inst_ref = air.add_inst(inst);
1944
1945        let retrieved = air.get(inst_ref);
1946        assert!(matches!(retrieved.data, AirInstData::Const(42)));
1947        assert_eq!(retrieved.ty, Type::I32);
1948    }
1949
1950    // ADR-0051 Phase 1: round-trip encode/decode coverage for every
1951    // AirPattern shape, including nested recursive ones.
1952    mod pattern_encoding {
1953        use super::*;
1954        use crate::types::EnumId;
1955        use lasso::{Key, Spur};
1956
1957        fn roundtrip(pattern: AirPattern) {
1958            let body = AirRef::from_raw(0xDEAD_BEEF);
1959            let mut buf = Vec::new();
1960            pattern.encode(body, &mut buf);
1961            let mut iter = MatchArmIterator {
1962                data: &buf,
1963                remaining: 1,
1964            };
1965            let (decoded, decoded_body) = iter.next().expect("one arm");
1966            assert_eq!(decoded_body.as_u32(), body.as_u32());
1967            assert!(iter.next().is_none(), "exactly one arm consumed");
1968            // Re-encode the decoded pattern and check bytes match.
1969            let mut buf2 = Vec::new();
1970            decoded.encode(body, &mut buf2);
1971            assert_eq!(buf, buf2, "round-trip differs; pattern = {:?}", pattern);
1972        }
1973
1974        fn spur(n: usize) -> Spur {
1975            Spur::try_from_usize(n).unwrap()
1976        }
1977
1978        #[test]
1979        fn wildcard() {
1980            roundtrip(AirPattern::Wildcard);
1981        }
1982
1983        #[test]
1984        fn int_positive_and_negative() {
1985            roundtrip(AirPattern::Int(42));
1986            roundtrip(AirPattern::Int(-1));
1987            roundtrip(AirPattern::Int(i64::MIN));
1988            roundtrip(AirPattern::Int(i64::MAX));
1989        }
1990
1991        #[test]
1992        fn bool_both() {
1993            roundtrip(AirPattern::Bool(true));
1994            roundtrip(AirPattern::Bool(false));
1995        }
1996
1997        #[test]
1998        fn enum_variant_legacy() {
1999            roundtrip(AirPattern::EnumVariant {
2000                enum_id: EnumId(7),
2001                variant_index: 3,
2002            });
2003        }
2004
2005        #[test]
2006        fn enum_unit_variant() {
2007            roundtrip(AirPattern::EnumUnitVariant {
2008                enum_id: EnumId(7),
2009                variant_index: 3,
2010            });
2011        }
2012
2013        #[test]
2014        fn bind_bare() {
2015            roundtrip(AirPattern::Bind {
2016                name: spur(5),
2017                is_mut: false,
2018                inner: None,
2019            });
2020            roundtrip(AirPattern::Bind {
2021                name: spur(5),
2022                is_mut: true,
2023                inner: None,
2024            });
2025        }
2026
2027        #[test]
2028        fn bind_at_inner() {
2029            roundtrip(AirPattern::Bind {
2030                name: spur(9),
2031                is_mut: false,
2032                inner: Some(Box::new(AirPattern::Int(7))),
2033            });
2034        }
2035
2036        #[test]
2037        fn tuple_flat_and_nested() {
2038            roundtrip(AirPattern::Tuple {
2039                elems: vec![
2040                    AirPattern::Int(1),
2041                    AirPattern::Wildcard,
2042                    AirPattern::Bool(true),
2043                ],
2044            });
2045            roundtrip(AirPattern::Tuple {
2046                elems: vec![
2047                    AirPattern::Tuple {
2048                        elems: vec![AirPattern::Int(1), AirPattern::Int(2)],
2049                    },
2050                    AirPattern::Wildcard,
2051                ],
2052            });
2053        }
2054
2055        #[test]
2056        fn struct_pattern() {
2057            roundtrip(AirPattern::Struct {
2058                struct_id: StructId(2),
2059                fields: vec![
2060                    (0, AirPattern::Int(1)),
2061                    (1, AirPattern::Wildcard),
2062                    (2, AirPattern::Bool(false)),
2063                ],
2064            });
2065        }
2066
2067        #[test]
2068        fn enum_data_variant_nested() {
2069            // Some(Some(42))
2070            roundtrip(AirPattern::EnumDataVariant {
2071                enum_id: EnumId(1),
2072                variant_index: 0,
2073                fields: vec![AirPattern::EnumDataVariant {
2074                    enum_id: EnumId(1),
2075                    variant_index: 0,
2076                    fields: vec![AirPattern::Int(42)],
2077                }],
2078            });
2079        }
2080
2081        #[test]
2082        fn enum_struct_variant_nested() {
2083            roundtrip(AirPattern::EnumStructVariant {
2084                enum_id: EnumId(3),
2085                variant_index: 1,
2086                fields: vec![
2087                    (0, AirPattern::Int(5)),
2088                    (
2089                        1,
2090                        AirPattern::Bind {
2091                            name: spur(11),
2092                            is_mut: false,
2093                            inner: None,
2094                        },
2095                    ),
2096                ],
2097            });
2098        }
2099
2100        #[test]
2101        fn multiple_arms_round_trip() {
2102            // Ensure sequential decode tracks variable-width arms correctly.
2103            let arms = vec![
2104                (AirPattern::Int(1), AirRef::from_raw(10)),
2105                (
2106                    AirPattern::Tuple {
2107                        elems: vec![AirPattern::Int(1), AirPattern::Wildcard],
2108                    },
2109                    AirRef::from_raw(11),
2110                ),
2111                (AirPattern::Wildcard, AirRef::from_raw(12)),
2112                (
2113                    AirPattern::EnumDataVariant {
2114                        enum_id: EnumId(0),
2115                        variant_index: 0,
2116                        fields: vec![AirPattern::Bind {
2117                            name: spur(1),
2118                            is_mut: false,
2119                            inner: None,
2120                        }],
2121                    },
2122                    AirRef::from_raw(13),
2123                ),
2124            ];
2125            let mut buf = Vec::new();
2126            for (p, b) in &arms {
2127                p.encode(*b, &mut buf);
2128            }
2129            let iter = MatchArmIterator {
2130                data: &buf,
2131                remaining: arms.len(),
2132            };
2133            let decoded: Vec<_> = iter.collect();
2134            assert_eq!(decoded.len(), arms.len());
2135            for ((orig_p, orig_b), (dec_p, dec_b)) in arms.iter().zip(decoded.iter()) {
2136                assert_eq!(orig_b.as_u32(), dec_b.as_u32());
2137                // Compare by re-encoding (patterns are not PartialEq).
2138                let mut a = Vec::new();
2139                let mut b = Vec::new();
2140                orig_p.encode(AirRef::from_raw(0), &mut a);
2141                dec_p.encode(AirRef::from_raw(0), &mut b);
2142                assert_eq!(a, b);
2143            }
2144        }
2145
2146        #[test]
2147        fn display_renders_surface_shapes() {
2148            let p = AirPattern::EnumDataVariant {
2149                enum_id: EnumId(2),
2150                variant_index: 0,
2151                fields: vec![AirPattern::EnumUnitVariant {
2152                    enum_id: EnumId(2),
2153                    variant_index: 1,
2154                }],
2155            };
2156            assert_eq!(format!("{}", p), "enum#2::0(enum#2::1)");
2157
2158            let t = AirPattern::Tuple {
2159                elems: vec![AirPattern::Int(1), AirPattern::Wildcard],
2160            };
2161            assert_eq!(format!("{}", t), "(1, _)");
2162        }
2163    }
2164}