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