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}