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}