Skip to main content

gruel_cfg/
inst.rs

1//! CFG instruction definitions.
2//!
3//! Unlike AIR, the CFG has explicit basic blocks and terminators.
4//! Control flow only happens at block boundaries via terminators.
5//!
6//! # Place Expressions (ADR-0030)
7//!
8//! Memory locations are represented using [`Place`], which consists of:
9//! - A base ([`PlaceBase`]): either a local variable slot or parameter slot
10//! - A list of projections ([`Projection`]): field accesses and array indices
11//!
12//! This design follows Rust MIR's proven approach and eliminates redundant
13//! Load instructions for nested access patterns like `arr[i].field`.
14
15use std::fmt;
16
17// Compile-time size assertions to prevent silent size growth during refactoring.
18// These limits are set slightly above current sizes to allow minor changes,
19// but will catch significant size regressions.
20//
21// Current sizes (as of 2025-12):
22// - CfgInst: 40 bytes (CfgInstData + Type + Span)
23// - CfgInstData: 24 bytes
24const _: () = assert!(std::mem::size_of::<CfgInst>() <= 48);
25const _: () = assert!(std::mem::size_of::<CfgInstData>() <= 32);
26
27use gruel_air::{AirParamMode, EnumId, StructId, Type};
28use gruel_util::{BinOp, Span, UnaryOp};
29use lasso::{Key, Spur};
30
31/// Boxed payload for [`CfgInstData::MakeSlice`] (ADR-0064 / ADR-0066).
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct MakeSliceData {
34    pub place: Place,
35    pub array_len: u64,
36    pub lo: Option<CfgValue>,
37    pub hi: Option<CfgValue>,
38    pub is_mut: bool,
39    /// ADR-0066: when set, indicates that the base is a `Vec(T)`. The
40    /// `place` references the Vec aggregate; codegen reads `ptr` and `len`
41    /// from the live fields at runtime, ignoring `array_len`.
42    pub vec_base: bool,
43}
44
45// ============================================================================
46// Place Expressions (ADR-0030)
47// ============================================================================
48
49/// A memory location that can be read from or written to.
50///
51/// A place represents a path to a memory location, consisting of a base
52/// (local variable or parameter) and zero or more projections (field access,
53/// array indexing).
54///
55/// # Examples
56///
57/// - `x` → `Place { base: Local(0), proj_start: 0, proj_len: 0 }`
58/// - `arr[i]` → `Place { base: Local(0), proj_start: 0, proj_len: 1 }` with `Index` projection
59/// - `point.x` → `Place { base: Local(0), proj_start: 0, proj_len: 1 }` with `Field` projection
60/// - `arr[i].x` → `Place { base: Local(0), proj_start: 0, proj_len: 2 }` with `Index` then `Field`
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct Place {
63    /// The base of the place - either a local slot or parameter slot
64    pub base: PlaceBase,
65    /// Start index into Cfg's projections array
66    pub proj_start: u32,
67    /// Number of projections
68    pub proj_len: u32,
69}
70
71impl Place {
72    /// Create a place for a local variable with no projections.
73    #[inline]
74    pub const fn local(slot: u32) -> Self {
75        Self {
76            base: PlaceBase::Local(slot),
77            proj_start: 0,
78            proj_len: 0,
79        }
80    }
81
82    /// Create a place for a parameter with no projections.
83    #[inline]
84    pub const fn param(slot: u32) -> Self {
85        Self {
86            base: PlaceBase::Param(slot),
87            proj_start: 0,
88            proj_len: 0,
89        }
90    }
91
92    /// Returns true if this place has no projections (is just a variable).
93    #[inline]
94    pub const fn is_simple(&self) -> bool {
95        self.proj_len == 0
96    }
97
98    /// Returns the local slot if this is a simple local place with no projections.
99    #[inline]
100    pub const fn as_local(&self) -> Option<u32> {
101        if self.proj_len == 0 {
102            match self.base {
103                PlaceBase::Local(slot) => Some(slot),
104                PlaceBase::Param(_) => None,
105            }
106        } else {
107            None
108        }
109    }
110
111    /// Returns the param slot if this is a simple param place with no projections.
112    #[inline]
113    pub const fn as_param(&self) -> Option<u32> {
114        if self.proj_len == 0 {
115            match self.base {
116                PlaceBase::Param(slot) => Some(slot),
117                PlaceBase::Local(_) => None,
118            }
119        } else {
120            None
121        }
122    }
123}
124
125impl fmt::Display for Place {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        match self.base {
128            PlaceBase::Local(slot) => write!(f, "${}", slot)?,
129            PlaceBase::Param(slot) => write!(f, "%{}", slot)?,
130        }
131        if self.proj_len > 0 {
132            write!(
133                f,
134                "[{}..{}]",
135                self.proj_start,
136                self.proj_start + self.proj_len
137            )?;
138        }
139        Ok(())
140    }
141}
142
143/// The base of a place - where the memory location starts.
144///
145/// Re-export of [`gruel_util::PlaceBase`].
146pub use gruel_util::PlaceBase;
147
148/// A projection applied to a place to reach a nested location.
149///
150/// Projections are stored in `Cfg::projections` and referenced by
151/// `Place::proj_start` and `Place::proj_len`.
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub enum Projection {
154    /// Field access: `.field_name`
155    ///
156    /// The struct_id identifies the struct type, and field_index is the
157    /// 0-based index of the field in declaration order.
158    Field {
159        struct_id: StructId,
160        field_index: u32,
161    },
162    /// Array index: `[index]`
163    ///
164    /// The array_type is needed for bounds checking and element size calculation.
165    /// The index is a CfgValue that will be evaluated at runtime.
166    Index { array_type: Type, index: CfgValue },
167}
168
169/// A basic block identifier.
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
171pub struct BlockId(pub(crate) u32);
172
173impl BlockId {
174    /// Create a new block ID from a raw index.
175    #[inline]
176    pub const fn from_raw(index: u32) -> Self {
177        Self(index)
178    }
179
180    /// Get the raw index.
181    #[inline]
182    pub const fn as_u32(self) -> u32 {
183        self.0
184    }
185}
186
187impl fmt::Display for BlockId {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        write!(f, "bb{}", self.0)
190    }
191}
192
193/// A reference to a value (instruction result) in the CFG.
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
195pub struct CfgValue(u32);
196
197impl CfgValue {
198    /// Create a new value reference from a raw index.
199    #[inline]
200    pub const fn from_raw(index: u32) -> Self {
201        Self(index)
202    }
203
204    /// Get the raw index.
205    #[inline]
206    pub const fn as_u32(self) -> u32 {
207        self.0
208    }
209}
210
211impl fmt::Display for CfgValue {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        write!(f, "v{}", self.0)
214    }
215}
216
217/// A single CFG instruction with its metadata.
218#[derive(Debug, Clone)]
219pub struct CfgInst {
220    pub data: CfgInstData,
221    pub ty: Type,
222    pub span: Span,
223}
224
225/// Argument passing mode in CFG. Mirrors [`gruel_air::AirArgMode`]; the
226/// `MutRef` / `Ref` markers survive ADR-0076 as the legacy by-pointer ABI
227/// signal used for parameters whose declared type cannot itself be wrapped
228/// as `Ref(...)` / `MutRef(...)` (notably interface-typed parameters).
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
230pub enum CfgArgMode {
231    /// Normal pass-by-value argument.
232    #[default]
233    Normal,
234    /// Exclusive mutable reborrow (by-pointer ABI).
235    MutRef,
236    /// Shared immutable reborrow (by-pointer ABI).
237    Ref,
238}
239
240impl From<gruel_air::AirArgMode> for CfgArgMode {
241    fn from(mode: gruel_air::AirArgMode) -> Self {
242        match mode {
243            gruel_air::AirArgMode::Normal => CfgArgMode::Normal,
244            gruel_air::AirArgMode::MutRef => CfgArgMode::MutRef,
245            gruel_air::AirArgMode::Ref => CfgArgMode::Ref,
246        }
247    }
248}
249
250/// An argument in a function call.
251#[derive(Debug, Clone, Copy)]
252pub struct CfgCallArg {
253    /// The argument value
254    pub value: CfgValue,
255    /// The passing mode for this argument
256    pub mode: CfgArgMode,
257}
258
259impl CfgCallArg {
260    /// Returns true if this argument is passed by exclusive mutable
261    /// reborrow per the legacy by-pointer ABI (ADR-0076 transport for
262    /// interface params).
263    pub fn is_mut_ref(&self) -> bool {
264        self.mode == CfgArgMode::MutRef
265    }
266
267    /// Returns true if this argument is passed by shared immutable
268    /// reborrow per the legacy by-pointer ABI.
269    pub fn is_ref(&self) -> bool {
270        self.mode == CfgArgMode::Ref
271    }
272
273    /// Returns true if this argument is passed by reference (either
274    /// `MutRef` or `Ref` per the legacy ABI markers).
275    pub fn is_by_ref(&self) -> bool {
276        matches!(self.mode, CfgArgMode::MutRef | CfgArgMode::Ref)
277    }
278}
279
280/// CFG instruction data.
281///
282/// Unlike AIR, there are NO control flow instructions here.
283/// Control flow is handled entirely by terminators.
284#[derive(Debug, Clone)]
285pub enum CfgInstData {
286    /// Integer constant (typed)
287    Const(u64),
288
289    /// Floating-point constant, stored as f64 bits via `f64::to_bits()`.
290    FloatConst(u64),
291
292    /// Boolean constant
293    BoolConst(bool),
294
295    /// String constant (index into string table)
296    StringConst(u32),
297
298    /// Byte-blob constant (index into the bytes table). Typed as
299    /// `Slice(u8)`; lowered to a binary-baked global at codegen.
300    BytesConst(u32),
301
302    /// Reference to a function parameter
303    Param { index: u32 },
304
305    /// Block parameter (like phi, but explicit)
306    /// Only valid at the start of a block
307    BlockParam { index: u32 },
308
309    /// Binary operation: arithmetic, comparison, or bitwise. Short-circuit
310    /// `And`/`Or` (from RIR/AIR) are lowered to control flow during CFG
311    /// construction and never appear here.
312    Bin(BinOp, CfgValue, CfgValue),
313
314    /// Unary operation: `-`, `!`, or `~`.
315    Unary(UnaryOp, CfgValue),
316
317    /// Reference construction (ADR-0062): produce the address of a place.
318    /// `is_mut` is informational; codegen produces the same alloca pointer
319    /// (or GEP for projected places) for both immutable and mutable
320    /// references — the borrow checker has already enforced exclusivity at
321    /// sema time.
322    MakeRef { place: Place, is_mut: bool },
323
324    /// Slice construction (ADR-0064): produce a fat pointer `{ptr, len}`
325    /// over a sub-range of an array place. The payload is boxed to keep
326    /// `CfgInstData` small.
327    MakeSlice(Box<MakeSliceData>),
328
329    // Variable operations
330    /// Allocate local variable with initial value
331    Alloc { slot: u32, init: CfgValue },
332    /// Load value from local variable
333    Load { slot: u32 },
334    /// Store value to local variable
335    Store { slot: u32, value: CfgValue },
336    /// Store value to a parameter (for inout params)
337    ParamStore { param_slot: u32, value: CfgValue },
338    /// Bare-name write-through for a `MutRef(T)`-typed local binding
339    /// (ADR-0076 Phase 3). Loads the pointer held in the local slot and
340    /// stores `value` (typed as the referent `T`) through that pointer.
341    RefStore { slot: u32, value: CfgValue },
342
343    // Place operations (ADR-0030)
344    /// Read a value from a memory location.
345    ///
346    /// This unifies Load, IndexGet, and FieldGet into a single instruction
347    /// that can handle arbitrarily nested access patterns like `arr[i].field`.
348    PlaceRead { place: Place },
349
350    /// Write a value to a memory location.
351    ///
352    /// This unifies Store, IndexSet, ParamIndexSet, FieldSet, and ParamFieldSet
353    /// into a single instruction that can handle nested writes.
354    PlaceWrite { place: Place, value: CfgValue },
355
356    // Function calls
357    /// Function call. Arguments are stored in the Cfg's call_args array.
358    /// Use `Cfg::get_call_args(args_start, args_len)` to retrieve them.
359    Call {
360        /// Function name (interned symbol)
361        name: Spur,
362        /// Start index into Cfg's call_args array
363        args_start: u32,
364        /// Number of arguments
365        args_len: u32,
366    },
367
368    /// Intrinsic call (e.g., @dbg). Arguments are stored in the Cfg's extra array.
369    /// Use `Cfg::get_extra(args_start, args_len)` to retrieve them.
370    Intrinsic {
371        /// Intrinsic name (interned symbol)
372        name: Spur,
373        /// Start index into Cfg's extra array
374        args_start: u32,
375        /// Number of arguments
376        args_len: u32,
377    },
378
379    // Struct operations
380    /// Struct initialization. Field values are stored in the Cfg's extra array.
381    /// Use `Cfg::get_extra(fields_start, fields_len)` to retrieve them.
382    StructInit {
383        struct_id: StructId,
384        /// Start index into Cfg's extra array
385        fields_start: u32,
386        /// Number of fields
387        fields_len: u32,
388    },
389    FieldSet {
390        slot: u32,
391        struct_id: StructId,
392        field_index: u32,
393        value: CfgValue,
394    },
395    /// Store a value to a struct field (for parameters, including inout)
396    ParamFieldSet {
397        /// The parameter's ABI slot (relative to params, not locals)
398        param_slot: u32,
399        /// Offset within the struct for nested field access
400        inner_offset: u32,
401        struct_id: StructId,
402        field_index: u32,
403        value: CfgValue,
404    },
405
406    // Array operations
407    /// Array initialization. Element values are stored in the Cfg's extra array.
408    /// Use `Cfg::get_extra(elements_start, elements_len)` to retrieve them.
409    /// The array type is stored in `CfgInst.ty`.
410    ArrayInit {
411        /// Start index into Cfg's extra array
412        elements_start: u32,
413        /// Number of elements
414        elements_len: u32,
415    },
416    /// Store a value to an array element.
417    IndexSet {
418        slot: u32,
419        /// The array type (for bounds checking and element size)
420        array_type: Type,
421        index: CfgValue,
422        value: CfgValue,
423    },
424    /// Store a value to an array element of an inout parameter
425    ParamIndexSet {
426        /// The parameter's ABI slot (relative to params, not locals)
427        param_slot: u32,
428        /// The array type (for bounds checking and element size)
429        array_type: Type,
430        /// Index expression
431        index: CfgValue,
432        /// Value to store
433        value: CfgValue,
434    },
435
436    // Enum operations
437    /// Create an enum variant (discriminant value) for unit-only enums.
438    EnumVariant { enum_id: EnumId, variant_index: u32 },
439
440    /// Create a data enum variant with associated field values.
441    /// Used when the enum has at least one data variant.
442    /// Field values are stored in the Cfg's extra array.
443    EnumCreate {
444        enum_id: EnumId,
445        variant_index: u32,
446        /// Start index into Cfg's extra array for field CfgValues
447        fields_start: u32,
448        /// Number of field values
449        fields_len: u32,
450    },
451
452    /// Extract a field value from an enum variant's payload.
453    /// Used in data variant match arm bodies to bind pattern variables.
454    EnumPayloadGet {
455        /// The enum value to extract from
456        base: CfgValue,
457        /// The variant index (must match the arm's pattern)
458        variant_index: u32,
459        /// The field index within the variant
460        field_index: u32,
461    },
462
463    /// Extract the discriminant of an enum value as a plain integer.
464    /// For data enums the LLVM layout is `{ disc, payload_union }` and
465    /// codegen emits `extract_value 0`; for unit-only enums the value
466    /// *is* the discriminant and codegen is the identity. Used by
467    /// ADR-0052 cascading pattern dispatch when an enum arm has
468    /// refutable nested fields — the standalone discriminant check
469    /// cannot flow into a normal `Eq` against a struct value.
470    GetDiscriminant {
471        /// The enum value to inspect.
472        base: CfgValue,
473    },
474
475    // Type conversion operations
476    /// Integer cast: convert between integer types with runtime range check.
477    /// Panics if the value cannot be represented in the target type.
478    /// The target type is stored in CfgInst.ty.
479    IntCast {
480        /// The value to cast
481        value: CfgValue,
482        /// The source type (for determining signedness and size)
483        from_ty: Type,
484    },
485
486    /// Float cast: convert between floating-point types (fptrunc/fpext).
487    /// The target type is stored in CfgInst.ty.
488    FloatCast {
489        /// The value to cast
490        value: CfgValue,
491        /// The source float type
492        from_ty: Type,
493    },
494
495    /// Integer to float conversion (sitofp/uitofp).
496    /// The target type is stored in CfgInst.ty.
497    IntToFloat {
498        /// The integer value to convert
499        value: CfgValue,
500        /// The source integer type (for determining signedness)
501        from_ty: Type,
502    },
503
504    /// Float to integer conversion (fptosi/fptoui) with runtime range check.
505    /// Panics if the value is NaN or out of range of the target integer type.
506    /// The target type is stored in CfgInst.ty.
507    FloatToInt {
508        /// The float value to convert
509        value: CfgValue,
510        /// The source float type
511        from_ty: Type,
512    },
513
514    // Drop/destructor operations
515    /// Drop a value, running its destructor if the type has one.
516    /// For trivially droppable types, this is a no-op that will be elided.
517    Drop { value: CfgValue },
518
519    // Storage liveness operations (for drop elaboration and stack allocation)
520    /// Marks that a local slot becomes live (storage allocated).
521    /// The slot is now valid to write to.
522    StorageLive { slot: u32 },
523
524    /// Marks that a local slot becomes dead (storage can be deallocated).
525    /// The slot is now invalid to read from.
526    /// Drop elaboration inserts Drop before this if the type needs drop.
527    StorageDead { slot: u32 },
528
529    /// Coerce a concrete value to an interface fat pointer (ADR-0056).
530    ///
531    /// Lowered by codegen to a literal `{ data_ptr, vtable_ptr }` struct
532    /// value. The data pointer addresses the source value (which must
533    /// outlive this coercion); the vtable pointer is a global constant
534    /// keyed on `(struct_id, interface_id)`.
535    MakeInterfaceRef {
536        /// The source value to wrap. Codegen takes its address.
537        value: CfgValue,
538        /// The concrete struct type of `value`.
539        struct_id: gruel_air::StructId,
540        /// The target interface.
541        interface_id: gruel_air::InterfaceId,
542    },
543
544    /// Dynamic-dispatch method call (ADR-0056). Args (excluding the receiver)
545    /// are stored in the call_args extra array at `[args_start..+args_len]`.
546    MethodCallDyn {
547        interface_id: gruel_air::InterfaceId,
548        slot: u32,
549        recv: CfgValue,
550        args_start: u32,
551        args_len: u32,
552    },
553}
554
555/// Block terminator - how control leaves a basic block.
556///
557/// Terminators are the ONLY place where control flow happens in the CFG.
558///
559/// Block arguments are stored in the CFG's `extra` array for efficiency.
560/// Use `Cfg::get_goto_args()`, `Cfg::get_branch_then_args()`, and
561/// `Cfg::get_branch_else_args()` to retrieve the arguments.
562#[derive(Debug, Clone, Copy)]
563pub enum Terminator {
564    /// Unconditional jump to another block.
565    /// Arguments are stored in Cfg's extra array.
566    Goto {
567        target: BlockId,
568        /// Start index into Cfg's extra array
569        args_start: u32,
570        /// Number of arguments
571        args_len: u32,
572    },
573
574    /// Conditional branch.
575    /// Arguments for each branch are stored in Cfg's extra array.
576    Branch {
577        cond: CfgValue,
578        then_block: BlockId,
579        /// Start index into Cfg's extra array for then branch args
580        then_args_start: u32,
581        /// Number of arguments for then branch
582        then_args_len: u32,
583        else_block: BlockId,
584        /// Start index into Cfg's extra array for else branch args
585        else_args_start: u32,
586        /// Number of arguments for else branch
587        else_args_len: u32,
588    },
589
590    /// Multi-way branch (switch/match).
591    /// Cases are stored in Cfg's switch_cases array.
592    Switch {
593        /// The value to switch on
594        scrutinee: CfgValue,
595        /// Start index into Cfg's switch_cases array
596        cases_start: u32,
597        /// Number of cases
598        cases_len: u32,
599        /// Default block (for wildcard pattern)
600        default: BlockId,
601    },
602
603    /// Return from function (None for unit-returning functions).
604    Return { value: Option<CfgValue> },
605
606    /// Unreachable - control never reaches here.
607    /// Used after diverging expressions.
608    Unreachable,
609
610    /// Placeholder for blocks under construction.
611    /// Should not exist in a valid CFG.
612    None,
613}
614
615/// A basic block in the CFG.
616#[derive(Debug, Clone)]
617pub struct BasicBlock {
618    /// Block identifier
619    pub id: BlockId,
620    /// Block parameters (receive values from predecessors)
621    pub params: Vec<(CfgValue, Type)>,
622    /// Instructions in this block (straight-line, no control flow)
623    pub insts: Vec<CfgValue>,
624    /// How this block exits
625    pub terminator: Terminator,
626    /// Predecessor blocks (filled in after construction)
627    pub preds: Vec<BlockId>,
628}
629
630impl BasicBlock {
631    /// Create a new empty basic block.
632    pub fn new(id: BlockId) -> Self {
633        Self {
634            id,
635            params: Vec::new(),
636            insts: Vec::new(),
637            terminator: Terminator::None,
638            preds: Vec::new(),
639        }
640    }
641}
642
643/// The complete CFG for a function.
644#[derive(Debug)]
645pub struct Cfg {
646    /// All basic blocks
647    blocks: Vec<BasicBlock>,
648    /// Entry block
649    pub entry: BlockId,
650    /// Return type
651    return_type: Type,
652    /// All instructions (values) - blocks reference these by CfgValue
653    values: Vec<CfgInst>,
654    /// Extra storage for variable-length CfgValue data (struct fields, array elements, intrinsic args,
655    /// and terminator block arguments). Instructions and terminators store (start, len) indices into this array.
656    extra: Vec<CfgValue>,
657    /// Extra storage for call arguments (CfgCallArg).
658    /// Call instructions store (start, len) indices into this array.
659    call_args: Vec<CfgCallArg>,
660    /// Extra storage for switch cases (value, target block pairs).
661    /// Switch terminators store (start, len) indices into this array.
662    switch_cases: Vec<(i64, BlockId)>,
663    /// Extra storage for place projections (ADR-0030).
664    /// Place instructions store (start, len) indices into this array.
665    projections: Vec<Projection>,
666    /// Number of local variable slots
667    num_locals: u32,
668    /// Number of parameter slots
669    num_params: u32,
670    /// Function name
671    fn_name: String,
672    /// Passing mode for each parameter slot (normal, inout, or borrow).
673    param_modes: Vec<AirParamMode>,
674    /// Type of each parameter slot (parallel to param_modes).
675    /// Retained here so that backends can declare function signatures even when
676    /// DCE has removed unused `Param { index }` instructions from the body.
677    param_types: Vec<Type>,
678    /// ADR-0084: per-`@spawn` site bookkeeping. Keyed by the value
679    /// index of the CfgInstData::Intrinsic { name="spawn" } node;
680    /// codegen reads it to emit the per-instantiation thunk and
681    /// `__gruel_thread_spawn` call.
682    spawn_targets: rustc_hash::FxHashMap<u32, SpawnTarget>,
683}
684
685/// ADR-0084: codegen bookkeeping for one `@spawn(fn, arg)` call.
686#[derive(Debug, Clone, Copy)]
687pub struct SpawnTarget {
688    /// Interned name of the worker function (resolves to a top-level
689    /// `fn` in the program).
690    pub worker_fn: lasso::Spur,
691    /// Worker's parameter type (= argument type at the spawn site).
692    pub arg_type: Type,
693    /// Worker's return type. Must be `≥ Send` per ADR-0084.
694    pub return_type: Type,
695}
696
697impl Cfg {
698    /// Create a new CFG.
699    pub fn new(
700        return_type: Type,
701        num_locals: u32,
702        num_params: u32,
703        fn_name: String,
704        param_modes: Vec<AirParamMode>,
705        param_types: Vec<Type>,
706    ) -> Self {
707        Self {
708            blocks: Vec::new(),
709            entry: BlockId(0),
710            return_type,
711            values: Vec::new(),
712            extra: Vec::new(),
713            call_args: Vec::new(),
714            switch_cases: Vec::new(),
715            projections: Vec::new(),
716            num_locals,
717            num_params,
718            fn_name,
719            param_modes,
720            param_types,
721            spawn_targets: rustc_hash::FxHashMap::default(),
722        }
723    }
724
725    /// ADR-0084: record the worker fn + types for a `@spawn` instruction.
726    pub fn record_spawn_target(&mut self, value: CfgValue, target: SpawnTarget) {
727        self.spawn_targets.insert(value.0, target);
728    }
729
730    /// Look up the bookkeeping recorded for a `@spawn` site, if any.
731    pub fn spawn_target(&self, value: CfgValue) -> Option<&SpawnTarget> {
732        self.spawn_targets.get(&value.0)
733    }
734
735    /// Get the return type.
736    #[inline]
737    pub fn return_type(&self) -> Type {
738        self.return_type
739    }
740
741    /// Get the number of local variable slots.
742    #[inline]
743    pub fn num_locals(&self) -> u32 {
744        self.num_locals
745    }
746
747    /// Allocate a new temporary local slot for spilling computed values.
748    ///
749    /// This is used during CFG construction when a computed value (e.g., method
750    /// call result) needs to be accessed via a place expression. The value is
751    /// spilled to this temporary slot.
752    ///
753    /// Returns the slot number for the new local.
754    #[inline]
755    pub fn alloc_temp_local(&mut self) -> u32 {
756        let slot = self.num_locals;
757        self.num_locals += 1;
758        slot
759    }
760
761    /// Get the number of parameter slots.
762    #[inline]
763    pub fn num_params(&self) -> u32 {
764        self.num_params
765    }
766
767    /// Get the function name.
768    #[inline]
769    pub fn fn_name(&self) -> &str {
770        &self.fn_name
771    }
772
773    /// Get the passing mode for a parameter slot.
774    #[inline]
775    pub fn param_mode(&self, slot: u32) -> AirParamMode {
776        self.param_modes
777            .get(slot as usize)
778            .copied()
779            .unwrap_or(AirParamMode::Normal)
780    }
781
782    /// Get whether a parameter slot is an exclusive mutable borrow per the
783    /// legacy mode mechanism.
784    #[inline]
785    pub fn is_param_mut_ref(&self, slot: u32) -> bool {
786        self.param_mode(slot).is_mut_ref()
787    }
788
789    /// Get whether a parameter slot is a shared immutable borrow (legacy
790    /// `Ref` mode or ADR-0062 `Ref(T)`-typed parameter).
791    #[inline]
792    pub fn is_param_ref(&self, slot: u32) -> bool {
793        if self.param_mode(slot).is_ref() {
794            return true;
795        }
796        // ADR-0062: a `Ref(T)`-typed parameter has the same calling
797        // convention shape as a `borrow` parameter — it's a noalias readonly
798        // pointer at the LLVM level.
799        matches!(
800            self.param_type(slot).map(|t| t.kind()),
801            Some(gruel_air::TypeKind::Ref(_))
802        )
803    }
804
805    /// Get whether a parameter slot is passed by reference (inout or borrow,
806    /// or an ADR-0062 `Ref(T)` / `MutRef(T)` parameter type).
807    ///
808    /// ADR-0056: interface-typed parameters carry their own data pointer
809    /// inside the fat-pointer struct, so they are passed *by value* at the
810    /// LLVM ABI level even when the source-level mode is `borrow`/`inout`.
811    /// The borrow/inout semantics still apply to the underlying data via
812    /// the `data_ptr` field of the fat pointer; the ABI just doesn't add
813    /// another layer of indirection.
814    #[inline]
815    pub fn is_param_by_ref(&self, slot: u32) -> bool {
816        if let Some(ty) = self.param_type(slot) {
817            match ty.kind() {
818                gruel_air::TypeKind::Interface(_) => return false,
819                gruel_air::TypeKind::Ref(_) | gruel_air::TypeKind::MutRef(_) => {
820                    return true;
821                }
822                _ => {}
823            }
824        }
825        self.param_mode(slot).is_by_ref()
826    }
827
828    /// Get the parameter modes slice.
829    #[inline]
830    pub fn param_modes(&self) -> &[AirParamMode] {
831        &self.param_modes
832    }
833
834    /// Get the type of a parameter slot.
835    ///
836    /// Returns `None` if the slot index is out of range.
837    #[inline]
838    pub fn param_type(&self, slot: u32) -> Option<Type> {
839        self.param_types.get(slot as usize).copied()
840    }
841
842    /// Create a new basic block and return its ID.
843    pub fn new_block(&mut self) -> BlockId {
844        let id = BlockId(self.blocks.len() as u32);
845        self.blocks.push(BasicBlock::new(id));
846        id
847    }
848
849    /// Get a block by ID.
850    #[inline]
851    pub fn get_block(&self, id: BlockId) -> &BasicBlock {
852        &self.blocks[id.0 as usize]
853    }
854
855    /// Get a block mutably by ID.
856    #[inline]
857    pub fn get_block_mut(&mut self, id: BlockId) -> &mut BasicBlock {
858        &mut self.blocks[id.0 as usize]
859    }
860
861    /// Add an instruction and return its value reference.
862    pub fn add_inst(&mut self, inst: CfgInst) -> CfgValue {
863        let value = CfgValue::from_raw(self.values.len() as u32);
864        self.values.push(inst);
865        value
866    }
867
868    /// Get an instruction by value reference.
869    #[inline]
870    pub fn get_inst(&self, value: CfgValue) -> &CfgInst {
871        &self.values[value.0 as usize]
872    }
873
874    /// Get a mutable instruction by value reference.
875    #[inline]
876    pub fn get_inst_mut(&mut self, value: CfgValue) -> &mut CfgInst {
877        &mut self.values[value.0 as usize]
878    }
879
880    /// Get the total number of values (instructions) in the CFG.
881    #[inline]
882    pub fn value_count(&self) -> usize {
883        self.values.len()
884    }
885
886    /// Add values to the extra array and return (start, len).
887    ///
888    /// Used for StructInit fields, ArrayInit elements, and Intrinsic args.
889    pub fn push_extra(&mut self, values: impl IntoIterator<Item = CfgValue>) -> (u32, u32) {
890        let start = self.extra.len() as u32;
891        self.extra.extend(values);
892        let len = self.extra.len() as u32 - start;
893        (start, len)
894    }
895
896    /// Get a slice from the extra array.
897    #[inline]
898    pub fn get_extra(&self, start: u32, len: u32) -> &[CfgValue] {
899        &self.extra[start as usize..(start + len) as usize]
900    }
901
902    /// Add call arguments to the call_args array and return (start, len).
903    ///
904    /// Used for Call instruction arguments.
905    pub fn push_call_args(&mut self, args: impl IntoIterator<Item = CfgCallArg>) -> (u32, u32) {
906        let start = self.call_args.len() as u32;
907        self.call_args.extend(args);
908        let len = self.call_args.len() as u32 - start;
909        (start, len)
910    }
911
912    /// Get a slice from the call_args array.
913    #[inline]
914    pub fn get_call_args(&self, start: u32, len: u32) -> &[CfgCallArg] {
915        &self.call_args[start as usize..(start + len) as usize]
916    }
917
918    /// Add switch cases to the switch_cases array and return (start, len).
919    ///
920    /// Used for Switch terminator cases.
921    pub fn push_switch_cases(
922        &mut self,
923        cases: impl IntoIterator<Item = (i64, BlockId)>,
924    ) -> (u32, u32) {
925        let start = self.switch_cases.len() as u32;
926        self.switch_cases.extend(cases);
927        let len = self.switch_cases.len() as u32 - start;
928        (start, len)
929    }
930
931    /// Get a slice from the switch_cases array.
932    #[inline]
933    pub fn get_switch_cases(&self, start: u32, len: u32) -> &[(i64, BlockId)] {
934        &self.switch_cases[start as usize..(start + len) as usize]
935    }
936
937    /// Add projections to the projections array and return (start, len).
938    ///
939    /// Used for PlaceRead and PlaceWrite instructions (ADR-0030).
940    pub fn push_projections(&mut self, projs: impl IntoIterator<Item = Projection>) -> (u32, u32) {
941        let start = self.projections.len() as u32;
942        self.projections.extend(projs);
943        let len = self.projections.len() as u32 - start;
944        (start, len)
945    }
946
947    /// Get a slice from the projections array.
948    #[inline]
949    pub fn get_projections(&self, start: u32, len: u32) -> &[Projection] {
950        &self.projections[start as usize..(start + len) as usize]
951    }
952
953    /// Get projections for a place.
954    #[inline]
955    pub fn get_place_projections(&self, place: &Place) -> &[Projection] {
956        self.get_projections(place.proj_start, place.proj_len)
957    }
958
959    /// Create a place with the given base and projections.
960    ///
961    /// This adds the projections to the projections array and returns a Place
962    /// that references them.
963    pub fn make_place(
964        &mut self,
965        base: PlaceBase,
966        projs: impl IntoIterator<Item = Projection>,
967    ) -> Place {
968        let (proj_start, proj_len) = self.push_projections(projs);
969        Place {
970            base,
971            proj_start,
972            proj_len,
973        }
974    }
975
976    /// Get the block arguments from a Goto terminator.
977    ///
978    /// # Panics
979    ///
980    /// Panics if the terminator is not a Goto.
981    #[inline]
982    pub fn get_goto_args(&self, term: &Terminator) -> &[CfgValue] {
983        match term {
984            Terminator::Goto {
985                args_start,
986                args_len,
987                ..
988            } => self.get_extra(*args_start, *args_len),
989            _ => panic!("get_goto_args called on non-Goto terminator"),
990        }
991    }
992
993    /// Get the then branch arguments from a Branch terminator.
994    ///
995    /// # Panics
996    ///
997    /// Panics if the terminator is not a Branch.
998    #[inline]
999    pub fn get_branch_then_args(&self, term: &Terminator) -> &[CfgValue] {
1000        match term {
1001            Terminator::Branch {
1002                then_args_start,
1003                then_args_len,
1004                ..
1005            } => self.get_extra(*then_args_start, *then_args_len),
1006            _ => panic!("get_branch_then_args called on non-Branch terminator"),
1007        }
1008    }
1009
1010    /// Get the else branch arguments from a Branch terminator.
1011    ///
1012    /// # Panics
1013    ///
1014    /// Panics if the terminator is not a Branch.
1015    #[inline]
1016    pub fn get_branch_else_args(&self, term: &Terminator) -> &[CfgValue] {
1017        match term {
1018            Terminator::Branch {
1019                else_args_start,
1020                else_args_len,
1021                ..
1022            } => self.get_extra(*else_args_start, *else_args_len),
1023            _ => panic!("get_branch_else_args called on non-Branch terminator"),
1024        }
1025    }
1026
1027    /// Add an instruction to a block.
1028    pub fn add_inst_to_block(&mut self, block: BlockId, inst: CfgInst) -> CfgValue {
1029        let value = self.add_inst(inst);
1030        self.blocks[block.0 as usize].insts.push(value);
1031        value
1032    }
1033
1034    /// Add a block parameter and return its value.
1035    pub fn add_block_param(&mut self, block: BlockId, ty: Type) -> CfgValue {
1036        let param_index = self.blocks[block.0 as usize].params.len() as u32;
1037        let inst = CfgInst {
1038            data: CfgInstData::BlockParam { index: param_index },
1039            ty,
1040            span: Span::new(0, 0),
1041        };
1042        let value = self.add_inst(inst);
1043        self.blocks[block.0 as usize].params.push((value, ty));
1044        value
1045    }
1046
1047    /// Set the terminator for a block.
1048    pub fn set_terminator(&mut self, block: BlockId, term: Terminator) {
1049        self.blocks[block.0 as usize].terminator = term;
1050    }
1051
1052    /// Get all blocks.
1053    pub fn blocks(&self) -> &[BasicBlock] {
1054        &self.blocks
1055    }
1056
1057    /// Get the number of blocks.
1058    #[inline]
1059    pub fn block_count(&self) -> usize {
1060        self.blocks.len()
1061    }
1062
1063    /// Iterate over block IDs.
1064    pub fn block_ids(&self) -> impl Iterator<Item = BlockId> {
1065        (0..self.blocks.len() as u32).map(BlockId)
1066    }
1067
1068    /// Compute predecessor lists for all blocks.
1069    pub fn compute_predecessors(&mut self) {
1070        // Clear existing predecessors
1071        for block in &mut self.blocks {
1072            block.preds.clear();
1073        }
1074
1075        // Collect edges
1076        let mut edges: Vec<(BlockId, BlockId)> = Vec::new();
1077        for block in &self.blocks {
1078            match &block.terminator {
1079                Terminator::Goto { target, .. } => {
1080                    edges.push((block.id, *target));
1081                }
1082                Terminator::Branch {
1083                    then_block,
1084                    else_block,
1085                    ..
1086                } => {
1087                    edges.push((block.id, *then_block));
1088                    edges.push((block.id, *else_block));
1089                }
1090                Terminator::Switch {
1091                    cases_start,
1092                    cases_len,
1093                    default,
1094                    ..
1095                } => {
1096                    for (_, target) in self.get_switch_cases(*cases_start, *cases_len) {
1097                        edges.push((block.id, *target));
1098                    }
1099                    edges.push((block.id, *default));
1100                }
1101                Terminator::Return { .. } | Terminator::Unreachable | Terminator::None => {}
1102            }
1103        }
1104
1105        // Add predecessors
1106        for (from, to) in edges {
1107            self.blocks[to.0 as usize].preds.push(from);
1108        }
1109    }
1110}
1111
1112impl fmt::Display for Cfg {
1113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1114        writeln!(
1115            f,
1116            "cfg {} (return_type: {}) {{",
1117            self.fn_name,
1118            self.return_type.name()
1119        )?;
1120        for block in &self.blocks {
1121            write!(f, "  {}:", block.id)?;
1122            if !block.params.is_empty() {
1123                write!(f, "(")?;
1124                for (i, (val, ty)) in block.params.iter().enumerate() {
1125                    if i > 0 {
1126                        write!(f, ", ")?;
1127                    }
1128                    write!(f, "{}: {}", val, ty.name())?;
1129                }
1130                write!(f, ")")?;
1131            }
1132            writeln!(f)?;
1133
1134            // Print predecessors
1135            if !block.preds.is_empty() {
1136                write!(f, "    ; preds: ")?;
1137                for (i, pred) in block.preds.iter().enumerate() {
1138                    if i > 0 {
1139                        write!(f, ", ")?;
1140                    }
1141                    write!(f, "{}", pred)?;
1142                }
1143                writeln!(f)?;
1144            }
1145
1146            // Print instructions
1147            for &val in &block.insts {
1148                let inst = self.get_inst(val);
1149                write!(f, "    {} : {} = ", val, inst.ty.name())?;
1150                self.fmt_inst_data(f, &inst.data)?;
1151                writeln!(f)?;
1152            }
1153
1154            // Print terminator
1155            write!(f, "    ")?;
1156            match &block.terminator {
1157                Terminator::Goto {
1158                    target,
1159                    args_start,
1160                    args_len,
1161                } => {
1162                    write!(f, "goto {}", target)?;
1163                    let args = self.get_extra(*args_start, *args_len);
1164                    if !args.is_empty() {
1165                        write!(f, "(")?;
1166                        for (i, arg) in args.iter().enumerate() {
1167                            if i > 0 {
1168                                write!(f, ", ")?;
1169                            }
1170                            write!(f, "{}", arg)?;
1171                        }
1172                        write!(f, ")")?;
1173                    }
1174                }
1175                Terminator::Branch {
1176                    cond,
1177                    then_block,
1178                    then_args_start,
1179                    then_args_len,
1180                    else_block,
1181                    else_args_start,
1182                    else_args_len,
1183                } => {
1184                    write!(f, "branch {}, {}", cond, then_block)?;
1185                    let then_args = self.get_extra(*then_args_start, *then_args_len);
1186                    if !then_args.is_empty() {
1187                        write!(f, "(")?;
1188                        for (i, arg) in then_args.iter().enumerate() {
1189                            if i > 0 {
1190                                write!(f, ", ")?;
1191                            }
1192                            write!(f, "{}", arg)?;
1193                        }
1194                        write!(f, ")")?;
1195                    }
1196                    write!(f, ", {}", else_block)?;
1197                    let else_args = self.get_extra(*else_args_start, *else_args_len);
1198                    if !else_args.is_empty() {
1199                        write!(f, "(")?;
1200                        for (i, arg) in else_args.iter().enumerate() {
1201                            if i > 0 {
1202                                write!(f, ", ")?;
1203                            }
1204                            write!(f, "{}", arg)?;
1205                        }
1206                        write!(f, ")")?;
1207                    }
1208                }
1209                Terminator::Switch {
1210                    scrutinee,
1211                    cases_start,
1212                    cases_len,
1213                    default,
1214                } => {
1215                    write!(f, "switch {} [", scrutinee)?;
1216                    let cases = self.get_switch_cases(*cases_start, *cases_len);
1217                    for (i, (val, target)) in cases.iter().enumerate() {
1218                        if i > 0 {
1219                            write!(f, ", ")?;
1220                        }
1221                        write!(f, "{} => {}", val, target)?;
1222                    }
1223                    write!(f, "], default: {}", default)?;
1224                }
1225                Terminator::Return { value } => {
1226                    if let Some(value) = value {
1227                        write!(f, "return {}", value)?;
1228                    } else {
1229                        write!(f, "return")?;
1230                    }
1231                }
1232                Terminator::Unreachable => {
1233                    write!(f, "unreachable")?;
1234                }
1235                Terminator::None => {
1236                    write!(f, "<no terminator>")?;
1237                }
1238            }
1239            writeln!(f)?;
1240            writeln!(f)?;
1241        }
1242        writeln!(f, "}}")
1243    }
1244}
1245
1246impl Cfg {
1247    fn fmt_inst_data(&self, f: &mut fmt::Formatter<'_>, data: &CfgInstData) -> fmt::Result {
1248        match data {
1249            CfgInstData::Const(v) => write!(f, "const {}", v),
1250            CfgInstData::FloatConst(bits) => write!(f, "const {}", f64::from_bits(*bits)),
1251            CfgInstData::BoolConst(v) => write!(f, "const {}", v),
1252            CfgInstData::StringConst(idx) => write!(f, "string_const @{}", idx),
1253            CfgInstData::BytesConst(idx) => write!(f, "bytes_const @{}", idx),
1254            CfgInstData::Param { index } => write!(f, "param {}", index),
1255            CfgInstData::BlockParam { index } => write!(f, "block_param {}", index),
1256            CfgInstData::Bin(op, lhs, rhs) => write!(f, "{} {}, {}", op, lhs, rhs),
1257            CfgInstData::Unary(op, v) => write!(f, "{} {}", op, v),
1258            CfgInstData::MakeRef { place, is_mut } => {
1259                write!(f, "make_ref{} {}", if *is_mut { "_mut" } else { "" }, place)
1260            }
1261            CfgInstData::MakeSlice(data) => {
1262                write!(
1263                    f,
1264                    "make_slice{} {}/{}",
1265                    if data.is_mut { "_mut" } else { "" },
1266                    data.place,
1267                    data.array_len
1268                )?;
1269                if let Some(lo) = data.lo {
1270                    write!(f, ", lo={}", lo)?;
1271                }
1272                if let Some(hi) = data.hi {
1273                    write!(f, ", hi={}", hi)?;
1274                }
1275                Ok(())
1276            }
1277            CfgInstData::Alloc { slot, init } => write!(f, "alloc ${} = {}", slot, init),
1278            CfgInstData::Load { slot } => write!(f, "load ${}", slot),
1279            CfgInstData::Store { slot, value } => write!(f, "store ${} = {}", slot, value),
1280            CfgInstData::ParamStore { param_slot, value } => {
1281                write!(f, "param_store %{} = {}", param_slot, value)
1282            }
1283            CfgInstData::RefStore { slot, value } => {
1284                write!(f, "ref_store ${} = {}", slot, value)
1285            }
1286            CfgInstData::PlaceRead { place } => {
1287                write!(f, "place_read ")?;
1288                self.fmt_place(f, place)
1289            }
1290            CfgInstData::PlaceWrite { place, value } => {
1291                write!(f, "place_write ")?;
1292                self.fmt_place(f, place)?;
1293                write!(f, " = {}", value)
1294            }
1295            CfgInstData::Call {
1296                name,
1297                args_start,
1298                args_len,
1299            } => {
1300                // Display symbol as @{id} since we don't have interner access here
1301                write!(f, "call @{}(", name.into_usize())?;
1302                let args = self.get_call_args(*args_start, *args_len);
1303                for (i, arg) in args.iter().enumerate() {
1304                    if i > 0 {
1305                        write!(f, ", ")?;
1306                    }
1307                    match arg.mode {
1308                        CfgArgMode::MutRef => write!(f, "mut_ref {}", arg.value)?,
1309                        CfgArgMode::Ref => write!(f, "ref {}", arg.value)?,
1310                        CfgArgMode::Normal => write!(f, "{}", arg.value)?,
1311                    }
1312                }
1313                write!(f, ")")
1314            }
1315            CfgInstData::Intrinsic {
1316                name,
1317                args_start,
1318                args_len,
1319            } => {
1320                // Display symbol as @{id} since we don't have interner access here
1321                write!(f, "intrinsic @{}(", name.into_usize())?;
1322                let args = self.get_extra(*args_start, *args_len);
1323                for (i, arg) in args.iter().enumerate() {
1324                    if i > 0 {
1325                        write!(f, ", ")?;
1326                    }
1327                    write!(f, "{}", arg)?;
1328                }
1329                write!(f, ")")
1330            }
1331            CfgInstData::StructInit {
1332                struct_id,
1333                fields_start,
1334                fields_len,
1335            } => {
1336                write!(f, "struct_init #{} {{", struct_id.0)?;
1337                let fields = self.get_extra(*fields_start, *fields_len);
1338                for (i, field) in fields.iter().enumerate() {
1339                    if i > 0 {
1340                        write!(f, ", ")?;
1341                    }
1342                    write!(f, "{}", field)?;
1343                }
1344                write!(f, "}}")
1345            }
1346            CfgInstData::FieldSet {
1347                slot,
1348                struct_id,
1349                field_index,
1350                value,
1351            } => {
1352                write!(
1353                    f,
1354                    "field_set ${}.#{}.{} = {}",
1355                    slot, struct_id.0, field_index, value
1356                )
1357            }
1358            CfgInstData::ParamFieldSet {
1359                param_slot,
1360                inner_offset,
1361                struct_id,
1362                field_index,
1363                value,
1364            } => {
1365                write!(
1366                    f,
1367                    "param_field_set %{}+{}.#{}.{} = {}",
1368                    param_slot, inner_offset, struct_id.0, field_index, value
1369                )
1370            }
1371            CfgInstData::ArrayInit {
1372                elements_start,
1373                elements_len,
1374            } => {
1375                write!(f, "array_init [")?;
1376                let elements = self.get_extra(*elements_start, *elements_len);
1377                for (i, elem) in elements.iter().enumerate() {
1378                    if i > 0 {
1379                        write!(f, ", ")?;
1380                    }
1381                    write!(f, "{}", elem)?;
1382                }
1383                write!(f, "]")
1384            }
1385            CfgInstData::IndexSet {
1386                slot,
1387                array_type,
1388                index,
1389                value,
1390            } => {
1391                write!(
1392                    f,
1393                    "index_set ${}({})[{}] = {}",
1394                    slot,
1395                    array_type.name(),
1396                    index,
1397                    value
1398                )
1399            }
1400            CfgInstData::ParamIndexSet {
1401                param_slot,
1402                array_type,
1403                index,
1404                value,
1405            } => {
1406                write!(
1407                    f,
1408                    "param_index_set %{}({})[{}] = {}",
1409                    param_slot,
1410                    array_type.name(),
1411                    index,
1412                    value
1413                )
1414            }
1415            CfgInstData::EnumVariant {
1416                enum_id,
1417                variant_index,
1418            } => {
1419                write!(f, "enum_variant #{}::{}", enum_id.0, variant_index)
1420            }
1421            CfgInstData::EnumCreate {
1422                enum_id,
1423                variant_index,
1424                fields_start,
1425                fields_len,
1426            } => {
1427                let fields = self.get_extra(*fields_start, *fields_len);
1428                let field_strs: Vec<String> = fields.iter().map(|v| format!("{}", v)).collect();
1429                write!(
1430                    f,
1431                    "enum_create #{}::{}({})",
1432                    enum_id.0,
1433                    variant_index,
1434                    field_strs.join(", ")
1435                )
1436            }
1437            CfgInstData::EnumPayloadGet {
1438                base,
1439                variant_index,
1440                field_index,
1441            } => {
1442                write!(
1443                    f,
1444                    "enum_payload_get {} variant={} field={}",
1445                    base, variant_index, field_index
1446                )
1447            }
1448            CfgInstData::GetDiscriminant { base } => {
1449                write!(f, "get_discriminant {}", base)
1450            }
1451            CfgInstData::IntCast { value, from_ty } => {
1452                write!(f, "intcast {} from {}", value, from_ty.name())
1453            }
1454            CfgInstData::FloatCast { value, from_ty } => {
1455                write!(f, "floatcast {} from {}", value, from_ty.name())
1456            }
1457            CfgInstData::IntToFloat { value, from_ty } => {
1458                write!(f, "int_to_float {} from {}", value, from_ty.name())
1459            }
1460            CfgInstData::FloatToInt { value, from_ty } => {
1461                write!(f, "float_to_int {} from {}", value, from_ty.name())
1462            }
1463            CfgInstData::Drop { value } => {
1464                write!(f, "drop {}", value)
1465            }
1466            CfgInstData::StorageLive { slot } => {
1467                write!(f, "storage_live ${}", slot)
1468            }
1469            CfgInstData::StorageDead { slot } => {
1470                write!(f, "storage_dead ${}", slot)
1471            }
1472            CfgInstData::MakeInterfaceRef {
1473                value,
1474                struct_id,
1475                interface_id,
1476            } => {
1477                write!(
1478                    f,
1479                    "make_interface_ref {} (struct=#{}, iface=#{})",
1480                    value, struct_id.0, interface_id.0
1481                )
1482            }
1483            CfgInstData::MethodCallDyn {
1484                interface_id,
1485                slot,
1486                recv,
1487                args_len,
1488                ..
1489            } => {
1490                write!(
1491                    f,
1492                    "method_call_dyn iface=#{} slot={} recv={} (+{} args)",
1493                    interface_id.0, slot, recv, args_len
1494                )
1495            }
1496        }
1497    }
1498
1499    /// Format a place for display, showing the base and projections.
1500    fn fmt_place(&self, f: &mut fmt::Formatter<'_>, place: &Place) -> fmt::Result {
1501        // Write the base
1502        match place.base {
1503            PlaceBase::Local(slot) => write!(f, "${}", slot)?,
1504            PlaceBase::Param(slot) => write!(f, "param%{}", slot)?,
1505        }
1506
1507        // Write the projections
1508        let projections = self.get_place_projections(place);
1509        for proj in projections {
1510            match proj {
1511                Projection::Field {
1512                    struct_id,
1513                    field_index,
1514                } => {
1515                    write!(f, ".#{}.{}", struct_id.0, field_index)?;
1516                }
1517                Projection::Index { array_type, index } => {
1518                    write!(f, "({})[{}]", array_type.name(), index)?;
1519                }
1520            }
1521        }
1522
1523        Ok(())
1524    }
1525}
1526
1527#[cfg(test)]
1528mod tests {
1529    use super::*;
1530
1531    #[test]
1532    fn test_block_id_size() {
1533        assert_eq!(std::mem::size_of::<BlockId>(), 4);
1534    }
1535
1536    #[test]
1537    fn test_cfg_value_size() {
1538        assert_eq!(std::mem::size_of::<CfgValue>(), 4);
1539    }
1540
1541    #[test]
1542    fn test_cfg_inst_size() {
1543        // Document actual sizes for future reference.
1544        // If this test fails, update the const assertions at the top of this file.
1545        let cfg_inst_size = std::mem::size_of::<CfgInst>();
1546        let cfg_inst_data_size = std::mem::size_of::<CfgInstData>();
1547
1548        // These assertions document the current sizes.
1549        // If the layout changes, update both these values and the const assertions.
1550        assert!(
1551            cfg_inst_size <= 48,
1552            "CfgInst grew beyond 48 bytes: {}",
1553            cfg_inst_size
1554        );
1555        assert!(
1556            cfg_inst_data_size <= 32,
1557            "CfgInstData grew beyond 32 bytes: {}",
1558            cfg_inst_data_size
1559        );
1560    }
1561
1562    #[test]
1563    fn test_terminator_size() {
1564        // Terminator should be a reasonable size (no heap allocations inside)
1565        // 32 bytes: 8 (CfgValue cond) + 4+4+4+4 (BlockId, start, len x2) + 4+4+4 (else) = 36, rounded to 40
1566        // Actually: Branch is the largest with cond(4) + then_block(4) + then_start(4) + then_len(4) + else_block(4) + else_start(4) + else_len(4) = 28 bytes + discriminant
1567        let size = std::mem::size_of::<Terminator>();
1568        assert!(size <= 40, "Terminator is {} bytes, expected <= 40", size);
1569    }
1570
1571    #[test]
1572    fn test_create_cfg() {
1573        let mut cfg = Cfg::new(Type::I32, 0, 0, "test".to_string(), vec![], vec![]);
1574        let entry = cfg.new_block();
1575        cfg.entry = entry;
1576
1577        let const_val = cfg.add_inst_to_block(
1578            entry,
1579            CfgInst {
1580                data: CfgInstData::Const(42),
1581                ty: Type::I32,
1582                span: Span::new(0, 2),
1583            },
1584        );
1585
1586        cfg.set_terminator(
1587            entry,
1588            Terminator::Return {
1589                value: Some(const_val),
1590            },
1591        );
1592
1593        assert_eq!(cfg.block_count(), 1);
1594    }
1595}