Skip to main content

gruel_rir/
inst.rs

1//! RIR instruction definitions.
2//!
3//! Instructions are stored in a dense array and referenced by index.
4//! This provides good cache locality and efficient traversal.
5
6use std::fmt;
7
8use gruel_builtins::Posture;
9use gruel_util::{BinOp, Span, UnaryOp};
10use lasso::{Key, Spur};
11
12/// A reference to an instruction in the RIR.
13///
14/// This is a lightweight handle (4 bytes) that indexes into the instruction array.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
16pub struct InstRef(u32);
17
18impl InstRef {
19    /// Create an instruction reference from a raw index.
20    #[inline]
21    pub const fn from_raw(index: u32) -> Self {
22        Self(index)
23    }
24
25    /// Get the raw index.
26    #[inline]
27    pub const fn as_u32(self) -> u32 {
28        self.0
29    }
30}
31
32/// A directive in the RIR (e.g., @allow(unused_variable))
33#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
34pub struct RirDirective {
35    /// Directive name (e.g., "allow")
36    pub name: Spur,
37    /// Arguments (e.g., ["unused_variable"])
38    pub args: Vec<Spur>,
39    /// Span covering the directive
40    pub span: Span,
41}
42
43/// Parameter passing mode in RIR.
44///
45/// Per ADR-0076, the surface language no longer has `inout` / `borrow`
46/// keyword forms. The legacy mode names are gone; `MutRef` / `Ref` survive
47/// as a transport mechanism for places where the param's `Type` cannot
48/// itself be wrapped (notably interface-typed parameters, where the type
49/// pool cannot intern `Ref(Interface)` / `MutRef(Interface)`).
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
51pub enum RirParamMode {
52    /// Normal pass-by-value parameter (or any reference whose ref-ness is
53    /// already encoded in the parameter `Type`).
54    #[default]
55    Normal,
56    /// Exclusive mutable borrow on a parameter whose declared type cannot
57    /// itself be wrapped as `MutRef(...)` (e.g. interface params).
58    MutRef,
59    /// Shared immutable borrow with the same caveat as `MutRef`.
60    Ref,
61    /// Comptime parameter - evaluated at compile time (used for type parameters)
62    Comptime,
63}
64
65/// A parameter in a function declaration.
66#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
67pub struct RirParam {
68    /// Parameter name
69    pub name: Spur,
70    /// Parameter type
71    pub ty: Spur,
72    /// Parameter passing mode
73    pub mode: RirParamMode,
74    /// Whether this parameter is evaluated at compile time
75    pub is_comptime: bool,
76}
77
78/// Argument passing mode in RIR.
79///
80/// Mirrors [`RirParamMode`]: `MutRef` / `Ref` are vestigial-but-used
81/// markers carried alongside arguments whose ref-ness cannot be encoded in
82/// the AIR value type (interface forwarding) so codegen still routes them
83/// through the by-pointer ABI.
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
85pub enum RirArgMode {
86    /// Normal pass-by-value argument (or a `&x` / `&mut x` whose ref-ness
87    /// is already in the AIR value type).
88    #[default]
89    Normal,
90    /// Exclusive mutable reborrow forwarded to a `MutRef`-mode parameter.
91    MutRef,
92    /// Shared immutable reborrow forwarded to a `Ref`-mode parameter.
93    Ref,
94}
95
96/// An argument in a function call.
97#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
98pub struct RirCallArg {
99    /// The argument expression
100    pub value: InstRef,
101    /// The passing mode for this argument
102    pub mode: RirArgMode,
103}
104
105/// A pattern in a match expression (RIR level - untyped).
106///
107/// Recursive shape introduced by ADR-0051 Phase 4: `Ident`, `Tuple`, and
108/// `Struct` variants let astgen carry source-level nesting straight to sema
109/// instead of pre-elaborating tuple/struct match roots. The existing
110/// variant-pattern shapes (`DataVariant`, `StructVariant`) are unchanged;
111/// nested sub-patterns inside variant fields still go through the
112/// elaboration layer until a follow-up RIR migration replaces their flat
113/// bindings with `RirPattern` leaves.
114#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
115pub enum RirPattern {
116    /// Wildcard pattern `_` - matches anything
117    Wildcard(Span),
118    /// Integer literal pattern (can be positive or negative)
119    Int(i64, Span),
120    /// Boolean literal pattern
121    Bool(bool, Span),
122    /// Path pattern for enum variants (e.g., `Color::Red` or `module.Color::Red`)
123    Path {
124        /// Optional module reference for qualified paths (e.g., the `module` in `module.Color::Red`)
125        module: Option<InstRef>,
126        /// The enum type name
127        type_name: Spur,
128        /// The variant name
129        variant: Spur,
130        /// Span of the pattern
131        span: Span,
132    },
133    /// Data variant pattern with field bindings (e.g., `Option::Some(x)`)
134    DataVariant {
135        /// Optional module reference for qualified paths
136        module: Option<InstRef>,
137        /// The enum type name
138        type_name: Spur,
139        /// The variant name
140        variant: Spur,
141        /// Bindings for each field
142        bindings: Vec<RirPatternBinding>,
143        /// Span of the pattern
144        span: Span,
145    },
146    /// Struct variant pattern with named field bindings (e.g., `Shape::Circle { radius }`)
147    StructVariant {
148        /// Optional module reference for qualified paths
149        module: Option<InstRef>,
150        /// The enum type name
151        type_name: Spur,
152        /// The variant name
153        variant: Spur,
154        /// Named field bindings
155        field_bindings: Vec<RirStructPatternBinding>,
156        /// Span of the pattern
157        span: Span,
158    },
159    /// Name binding `x` or `mut x` (ADR-0051). Used at the arm root or as
160    /// a sub-pattern inside `Tuple` / `Struct`. Equivalent to `x @ _`.
161    Ident {
162        name: Spur,
163        is_mut: bool,
164        span: Span,
165    },
166    /// Tuple pattern `(p0, p1, ...)` (ADR-0051). `elems` carries the
167    /// explicit sub-patterns only; `rest_position` records where a `..`
168    /// rest marker appeared so sema can expand it to wildcards filling
169    /// the scrutinee's arity. `None` means no rest; `Some(i)` means the
170    /// rest was written at source index `i` (so `elems[..i]` are the
171    /// prefix and `elems[i..]` the suffix).
172    Tuple {
173        elems: Vec<RirPattern>,
174        rest_position: Option<u32>,
175        span: Span,
176    },
177    /// Named-struct pattern `TypeName { field: pat, .. }` (ADR-0051).
178    /// `has_rest` marks an explicit `..` trailing the field list.
179    Struct {
180        module: Option<InstRef>,
181        type_name: Spur,
182        fields: Vec<RirStructField>,
183        has_rest: bool,
184        span: Span,
185    },
186    /// ADR-0079 Phase 3: a `comptime_unroll for` arm template. Sema
187    /// evaluates `iterable` at comptime, synthesizes one regular arm
188    /// per element, and substitutes `binding` as a comptime value in
189    /// the arm body. Only valid at the top level of a match arm.
190    ComptimeUnrollArm {
191        binding: Spur,
192        iterable: InstRef,
193        span: Span,
194    },
195}
196
197/// A binding in a data variant pattern.
198///
199/// Shapes:
200/// - `is_wildcard = true` → `_` (no binding, matches anything)
201/// - `is_wildcard = false, name = Some(x), sub_pattern = None` → `x` (bind field to `x`)
202/// - `is_wildcard = false, name = None, sub_pattern = Some(p)` → `p` (nested refutable sub-pattern, ADR-0051)
203/// - `is_wildcard = false, name = Some(x), sub_pattern = Some(p)` → `x @ p` (reserved; not yet
204///   exposed in surface syntax)
205#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
206pub struct RirPatternBinding {
207    /// Whether this is a wildcard binding (`_`)
208    pub is_wildcard: bool,
209    /// Whether this is a mutable binding (only meaningful if not wildcard)
210    pub is_mut: bool,
211    /// The binding name (None for wildcard or nested sub-pattern bindings)
212    pub name: Option<Spur>,
213    /// Nested sub-pattern for refutable field matches like `Some(Ok(v))`.
214    /// When `Some`, the binding's match is `sub_pattern` recursively;
215    /// when `None`, this is a flat leaf binding.
216    pub sub_pattern: Option<Box<RirPattern>>,
217}
218
219/// A named field binding in a struct variant pattern.
220#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
221pub struct RirStructPatternBinding {
222    /// The field name being matched
223    pub field_name: Spur,
224    /// The binding for this field
225    pub binding: RirPatternBinding,
226}
227
228/// A field in an ADR-0051 `RirPattern::Struct` arm, carrying the matched
229/// field's name plus its recursive sub-pattern.
230#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
231pub struct RirStructField {
232    pub field_name: Spur,
233    pub pattern: RirPattern,
234}
235
236impl RirPattern {
237    /// Get the span of this pattern.
238    pub fn span(&self) -> Span {
239        match self {
240            RirPattern::Wildcard(span) => *span,
241            RirPattern::Int(_, span) => *span,
242            RirPattern::Bool(_, span) => *span,
243            RirPattern::Path { span, .. } => *span,
244            RirPattern::DataVariant { span, .. } => *span,
245            RirPattern::StructVariant { span, .. } => *span,
246            RirPattern::Ident { span, .. } => *span,
247            RirPattern::Tuple { span, .. } => *span,
248            RirPattern::Struct { span, .. } => *span,
249            RirPattern::ComptimeUnrollArm { span, .. } => *span,
250        }
251    }
252}
253
254/// Encode a `RirPatternBinding` to `out`. Layout:
255/// `[flags, name_raw, <sub_pattern_tree if flags bit 2 set>]`.
256/// Flags: bit 0 = is_wildcard, bit 1 = is_mut, bit 2 = has_sub_pattern.
257fn encode_binding(b: &RirPatternBinding, out: &mut Vec<u32>) {
258    let mut flags = if b.is_wildcard { 1u32 } else { 0 };
259    if b.is_mut {
260        flags |= 2;
261    }
262    if b.sub_pattern.is_some() {
263        flags |= 4;
264    }
265    out.push(flags);
266    out.push(b.name.map_or(u32::MAX, |s| s.into_usize() as u32));
267    if let Some(sub) = &b.sub_pattern {
268        encode_pattern_tree(sub, out);
269    }
270}
271
272/// Decode a `RirPatternBinding` from `data`, returning the binding and
273/// words consumed.
274fn decode_binding(data: &[u32]) -> (RirPatternBinding, usize) {
275    let flags = data[0];
276    let name_raw = data[1];
277    let name = if name_raw == u32::MAX {
278        None
279    } else {
280        Some(Spur::try_from_usize(name_raw as usize).unwrap())
281    };
282    let mut offset = 2;
283    let sub_pattern = if flags & 4 != 0 {
284        let (p, consumed) = decode_pattern_tree(&data[offset..]);
285        offset += consumed;
286        Some(Box::new(p))
287    } else {
288        None
289    };
290    (
291        RirPatternBinding {
292            is_wildcard: flags & 1 != 0,
293            is_mut: flags & 2 != 0,
294            name,
295            sub_pattern,
296        },
297        offset,
298    )
299}
300
301/// Encode a `RirStructPatternBinding` to `out`. Layout:
302/// `[field_name, <binding encoding>]`.
303fn encode_struct_binding(fb: &RirStructPatternBinding, out: &mut Vec<u32>) {
304    out.push(fb.field_name.into_usize() as u32);
305    encode_binding(&fb.binding, out);
306}
307
308/// Decode a `RirStructPatternBinding` from `data`, returning the binding
309/// and words consumed.
310fn decode_struct_binding(data: &[u32]) -> (RirStructPatternBinding, usize) {
311    let field_name = Spur::try_from_usize(data[0] as usize).unwrap();
312    let (binding, consumed) = decode_binding(&data[1..]);
313    (
314        RirStructPatternBinding {
315            field_name,
316            binding,
317        },
318        1 + consumed,
319    )
320}
321
322/// Encode a `RirPattern` as a self-describing tree into `out`. Unlike the
323/// per-kind top-level arm encoding, this form carries no `body_ref` and
324/// is used for nested sub-patterns inside ADR-0051 `Tuple` / `Struct`
325/// arm elements. The layout mirrors the per-kind top-level layout minus
326/// the body word, with variable-width recursion for nested patterns.
327fn encode_pattern_tree(pattern: &RirPattern, out: &mut Vec<u32>) {
328    match pattern {
329        RirPattern::Wildcard(span) => {
330            out.push(PatternKind::Wildcard as u32);
331            out.push(span.start());
332            out.push(span.len());
333        }
334        RirPattern::Int(value, span) => {
335            out.push(PatternKind::Int as u32);
336            out.push(span.start());
337            out.push(span.len());
338            out.push(*value as u32);
339            out.push((*value >> 32) as u32);
340        }
341        RirPattern::Bool(value, span) => {
342            out.push(PatternKind::Bool as u32);
343            out.push(span.start());
344            out.push(span.len());
345            out.push(if *value { 1 } else { 0 });
346        }
347        RirPattern::Path {
348            module,
349            type_name,
350            variant,
351            span,
352        } => {
353            out.push(PatternKind::Path as u32);
354            out.push(span.start());
355            out.push(span.len());
356            out.push(module.map_or(u32::MAX, |r| r.as_u32()));
357            out.push(type_name.into_usize() as u32);
358            out.push(variant.into_usize() as u32);
359        }
360        RirPattern::DataVariant {
361            module,
362            type_name,
363            variant,
364            bindings,
365            span,
366        } => {
367            out.push(PatternKind::DataVariant as u32);
368            out.push(span.start());
369            out.push(span.len());
370            out.push(module.map_or(u32::MAX, |r| r.as_u32()));
371            out.push(type_name.into_usize() as u32);
372            out.push(variant.into_usize() as u32);
373            out.push(bindings.len() as u32);
374            for b in bindings {
375                encode_binding(b, out);
376            }
377        }
378        RirPattern::StructVariant {
379            module,
380            type_name,
381            variant,
382            field_bindings,
383            span,
384        } => {
385            out.push(PatternKind::StructVariant as u32);
386            out.push(span.start());
387            out.push(span.len());
388            out.push(module.map_or(u32::MAX, |r| r.as_u32()));
389            out.push(type_name.into_usize() as u32);
390            out.push(variant.into_usize() as u32);
391            out.push(field_bindings.len() as u32);
392            for fb in field_bindings {
393                encode_struct_binding(fb, out);
394            }
395        }
396        RirPattern::Ident { name, is_mut, span } => {
397            out.push(PatternKind::Ident as u32);
398            out.push(span.start());
399            out.push(span.len());
400            out.push(name.into_usize() as u32);
401            out.push(if *is_mut { 1 } else { 0 });
402        }
403        RirPattern::Tuple {
404            elems,
405            rest_position,
406            span,
407        } => {
408            out.push(PatternKind::Tuple as u32);
409            out.push(span.start());
410            out.push(span.len());
411            out.push(rest_position.map_or(u32::MAX, |i| i));
412            out.push(elems.len() as u32);
413            for elem in elems {
414                encode_pattern_tree(elem, out);
415            }
416        }
417        RirPattern::Struct {
418            module,
419            type_name,
420            fields,
421            has_rest,
422            span,
423        } => {
424            out.push(PatternKind::Struct as u32);
425            out.push(span.start());
426            out.push(span.len());
427            out.push(module.map_or(u32::MAX, |r| r.as_u32()));
428            out.push(type_name.into_usize() as u32);
429            out.push(if *has_rest { 1 } else { 0 });
430            out.push(fields.len() as u32);
431            for f in fields {
432                out.push(f.field_name.into_usize() as u32);
433                encode_pattern_tree(&f.pattern, out);
434            }
435        }
436        // ADR-0079 Phase 3: an unroll-arm template only ever
437        // appears at the top level of a match (sema rejects it
438        // elsewhere), but we still need a tree encoding for the
439        // shared encode dispatch — emit a stable shape and let
440        // the decoder round-trip it identically.
441        RirPattern::ComptimeUnrollArm {
442            binding,
443            iterable,
444            span,
445        } => {
446            out.push(PatternKind::ComptimeUnrollArm as u32);
447            out.push(span.start());
448            out.push(span.len());
449            out.push(binding.into_usize() as u32);
450            out.push(iterable.as_u32());
451        }
452    }
453}
454
455/// Decode a single `RirPattern` from the tree encoding in `data`,
456/// returning the pattern and how many u32 words it consumed.
457fn decode_pattern_tree(data: &[u32]) -> (RirPattern, usize) {
458    let kind = data[0];
459    if kind == PatternKind::Wildcard as u32 {
460        let span = Span::new(data[1], data[1] + data[2]);
461        (RirPattern::Wildcard(span), 3)
462    } else if kind == PatternKind::Int as u32 {
463        let span = Span::new(data[1], data[1] + data[2]);
464        let value_lo = data[3] as i64;
465        let value_hi = data[4] as i64;
466        let value = value_lo | (value_hi << 32);
467        (RirPattern::Int(value, span), 5)
468    } else if kind == PatternKind::Bool as u32 {
469        let span = Span::new(data[1], data[1] + data[2]);
470        let value = data[3] != 0;
471        (RirPattern::Bool(value, span), 4)
472    } else if kind == PatternKind::Path as u32 {
473        let span = Span::new(data[1], data[1] + data[2]);
474        let module_raw = data[3];
475        let module = if module_raw == u32::MAX {
476            None
477        } else {
478            Some(InstRef::from_raw(module_raw))
479        };
480        let type_name = Spur::try_from_usize(data[4] as usize).unwrap();
481        let variant = Spur::try_from_usize(data[5] as usize).unwrap();
482        (
483            RirPattern::Path {
484                module,
485                type_name,
486                variant,
487                span,
488            },
489            6,
490        )
491    } else if kind == PatternKind::DataVariant as u32 {
492        let span = Span::new(data[1], data[1] + data[2]);
493        let module_raw = data[3];
494        let module = if module_raw == u32::MAX {
495            None
496        } else {
497            Some(InstRef::from_raw(module_raw))
498        };
499        let type_name = Spur::try_from_usize(data[4] as usize).unwrap();
500        let variant = Spur::try_from_usize(data[5] as usize).unwrap();
501        let n = data[6] as usize;
502        let mut bindings = Vec::with_capacity(n);
503        let mut offset = 7;
504        for _ in 0..n {
505            let (b, consumed) = decode_binding(&data[offset..]);
506            bindings.push(b);
507            offset += consumed;
508        }
509        (
510            RirPattern::DataVariant {
511                module,
512                type_name,
513                variant,
514                bindings,
515                span,
516            },
517            offset,
518        )
519    } else if kind == PatternKind::StructVariant as u32 {
520        let span = Span::new(data[1], data[1] + data[2]);
521        let module_raw = data[3];
522        let module = if module_raw == u32::MAX {
523            None
524        } else {
525            Some(InstRef::from_raw(module_raw))
526        };
527        let type_name = Spur::try_from_usize(data[4] as usize).unwrap();
528        let variant = Spur::try_from_usize(data[5] as usize).unwrap();
529        let n = data[6] as usize;
530        let mut field_bindings = Vec::with_capacity(n);
531        let mut offset = 7;
532        for _ in 0..n {
533            let (fb, consumed) = decode_struct_binding(&data[offset..]);
534            field_bindings.push(fb);
535            offset += consumed;
536        }
537        (
538            RirPattern::StructVariant {
539                module,
540                type_name,
541                variant,
542                field_bindings,
543                span,
544            },
545            offset,
546        )
547    } else if kind == PatternKind::Ident as u32 {
548        let span = Span::new(data[1], data[1] + data[2]);
549        let name = Spur::try_from_usize(data[3] as usize).unwrap();
550        let is_mut = data[4] != 0;
551        (RirPattern::Ident { name, is_mut, span }, 5)
552    } else if kind == PatternKind::Tuple as u32 {
553        let span = Span::new(data[1], data[1] + data[2]);
554        let rest_raw = data[3];
555        let rest_position = if rest_raw == u32::MAX {
556            None
557        } else {
558            Some(rest_raw)
559        };
560        let n = data[4] as usize;
561        let mut offset = 5;
562        let mut elems = Vec::with_capacity(n);
563        for _ in 0..n {
564            let (p, consumed) = decode_pattern_tree(&data[offset..]);
565            elems.push(p);
566            offset += consumed;
567        }
568        (
569            RirPattern::Tuple {
570                elems,
571                rest_position,
572                span,
573            },
574            offset,
575        )
576    } else if kind == PatternKind::Struct as u32 {
577        let span = Span::new(data[1], data[1] + data[2]);
578        let module_raw = data[3];
579        let module = if module_raw == u32::MAX {
580            None
581        } else {
582            Some(InstRef::from_raw(module_raw))
583        };
584        let type_name = Spur::try_from_usize(data[4] as usize).unwrap();
585        let has_rest = data[5] != 0;
586        let n = data[6] as usize;
587        let mut offset = 7;
588        let mut fields = Vec::with_capacity(n);
589        for _ in 0..n {
590            let field_name = Spur::try_from_usize(data[offset] as usize).unwrap();
591            offset += 1;
592            let (pattern, consumed) = decode_pattern_tree(&data[offset..]);
593            offset += consumed;
594            fields.push(RirStructField {
595                field_name,
596                pattern,
597            });
598        }
599        (
600            RirPattern::Struct {
601                module,
602                type_name,
603                fields,
604                has_rest,
605                span,
606            },
607            offset,
608        )
609    } else if kind == PatternKind::ComptimeUnrollArm as u32 {
610        let span = Span::new(data[1], data[1] + data[2]);
611        let binding = Spur::try_from_usize(data[3] as usize).unwrap();
612        let iterable = InstRef::from_raw(data[4]);
613        (
614            RirPattern::ComptimeUnrollArm {
615                binding,
616                iterable,
617                span,
618            },
619            5,
620        )
621    } else {
622        panic!("Unknown pattern tree tag: {}", kind);
623    }
624}
625
626/// Extra data marker types for type-safe storage in the extra array.
627/// These types represent data stored in the extra array.
628/// Stored representation of RirCallArg in the extra array.
629/// Layout: [value: u32, mode: u32] = 2 u32s per arg
630const CALL_ARG_SIZE: u32 = 2;
631
632/// Stored representation of RirParam in the extra array.
633/// Layout: [name: u32, ty: u32, mode: u32, is_comptime: u32] = 4 u32s per param
634const PARAM_SIZE: u32 = 4;
635
636/// Stored representation of match arm in the extra array.
637/// Layout: pattern data + [body: u32]
638/// Pattern data varies by kind (see PatternKind enum).
639/// Pattern kinds encoded in extra array
640#[repr(u32)]
641#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
642pub enum PatternKind {
643    /// Wildcard pattern: [kind, span_start, span_len]
644    Wildcard = 0,
645    /// Int pattern: [kind, span_start, span_len, value_lo, value_hi]
646    Int = 1,
647    /// Bool pattern: [kind, span_start, span_len, value]
648    Bool = 2,
649    /// Path pattern: [kind, span_start, span_len, module, type_name, variant]
650    /// module is u32::MAX for None, otherwise an InstRef
651    Path = 3,
652    /// Data variant pattern: [kind, span_start, span_len, module, type_name, variant, body, bindings_len, (flags, name)...]
653    /// Each binding is 2 u32s: flags (bit0=is_wildcard, bit1=is_mut), name (Spur, u32::MAX if wildcard)
654    DataVariant = 4,
655    /// Struct variant pattern: [kind, span_start, span_len, module, type_name, variant, body, bindings_len, (field_name, flags, binding_name)...]
656    /// Each field binding is 3 u32s: field_name (Spur), flags (bit0=is_wildcard, bit1=is_mut), binding_name (Spur, u32::MAX if wildcard)
657    StructVariant = 5,
658    /// ADR-0051 Ident pattern: [kind, span_start, span_len, body, name, flags]
659    /// flags bit 0 = is_mut.
660    Ident = 6,
661    /// ADR-0051 Tuple pattern: [kind, span_start, span_len, body, elems_len, ...recursive_tree per elem]
662    Tuple = 7,
663    /// ADR-0051 Struct pattern: [kind, span_start, span_len, body, module_raw, type_name, has_rest, fields_len,
664    ///                           (field_name, ...recursive_tree) * fields_len]
665    Struct = 8,
666    /// ADR-0079 Phase 3 unroll arm template: [kind, span_start, span_len, body, binding, iterable]
667    ComptimeUnrollArm = 9,
668}
669
670/// Size of each pattern kind in the extra array (including body InstRef)
671const PATTERN_WILDCARD_SIZE: u32 = 4; // kind, span_start, span_len, body
672const PATTERN_INT_SIZE: u32 = 6; // kind, span_start, span_len, value_lo, value_hi, body
673const PATTERN_BOOL_SIZE: u32 = 5; // kind, span_start, span_len, value, body
674const PATTERN_PATH_SIZE: u32 = 7; // kind, span_start, span_len, module, type_name, variant, body
675// DataVariant size: 8 + 2 * bindings_len (variable)
676
677/// Stored representation of a destructure field in the extra array.
678/// Layout: [field_name: u32, binding_name: u32 (0 = shorthand), is_wildcard: u32, is_mut: u32]
679const DESTRUCTURE_FIELD_SIZE: u32 = 4;
680
681/// A decoded destructure field from the extra array.
682#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
683pub struct RirDestructureField {
684    /// The struct field being bound
685    pub field_name: Spur,
686    /// Binding name (None for shorthand or wildcard)
687    pub binding_name: Option<Spur>,
688    /// Whether this is a wildcard binding (`field: _`)
689    pub is_wildcard: bool,
690    /// Whether the binding is mutable
691    pub is_mut: bool,
692}
693
694/// Stored representation of struct field initializer.
695/// Layout: [field_name: u32, value: u32] = 2 u32s per field
696const FIELD_INIT_SIZE: u32 = 2;
697
698/// Stored representation of struct field declaration.
699/// Layout: [field_name: u32, field_type: u32] = 2 u32s per field
700const FIELD_DECL_SIZE: u32 = 3;
701
702/// Stored representation of directive in the extra array.
703/// Layout: [name: u32, span_start: u32, span_len: u32, args_len: u32, args...]
704/// Variable size due to args.
705/// A span marking the boundaries of a function in the RIR.
706///
707/// This allows efficient per-function analysis by identifying which instructions
708/// belong to each function without scanning the entire instruction array.
709#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
710pub struct FunctionSpan {
711    /// Function name symbol
712    pub name: Spur,
713    /// Index of the first instruction of this function's body.
714    /// This is the first instruction generated for the function's expressions/statements.
715    pub body_start: InstRef,
716    /// Index of the FnDecl instruction for this function.
717    /// This is always the last instruction of the function.
718    pub decl: InstRef,
719}
720
721impl FunctionSpan {
722    /// Create a new function span.
723    pub fn new(name: Spur, body_start: InstRef, decl: InstRef) -> Self {
724        Self {
725            name,
726            body_start,
727            decl,
728        }
729    }
730
731    /// Get the number of instructions in this function (including the FnDecl).
732    pub fn instruction_count(&self) -> u32 {
733        self.decl.as_u32() - self.body_start.as_u32() + 1
734    }
735}
736
737/// A view into a function's instructions within the RIR.
738///
739/// This provides a way to iterate over just the instructions belonging to a
740/// specific function, enabling per-function analysis without copying data.
741#[derive(Debug)]
742pub struct RirFunctionView<'a> {
743    rir: &'a Rir,
744    body_start: InstRef,
745    decl: InstRef,
746}
747
748impl<'a> RirFunctionView<'a> {
749    /// Get the instruction at the given reference.
750    ///
751    /// Note: The reference must be within this function's range.
752    #[inline]
753    pub fn get(&self, inst_ref: InstRef) -> &'a Inst {
754        debug_assert!(
755            inst_ref.as_u32() >= self.body_start.as_u32()
756                && inst_ref.as_u32() <= self.decl.as_u32(),
757            "InstRef {} is outside function range [{}, {}]",
758            inst_ref,
759            self.body_start,
760            self.decl
761        );
762        self.rir.get(inst_ref)
763    }
764
765    /// Get the FnDecl instruction for this function.
766    #[inline]
767    pub fn fn_decl(&self) -> &'a Inst {
768        self.rir.get(self.decl)
769    }
770
771    /// Iterate over all instructions in this function (including FnDecl).
772    pub fn iter(&self) -> impl Iterator<Item = (InstRef, &'a Inst)> {
773        let start = self.body_start.as_u32();
774        let end = self.decl.as_u32() + 1;
775        (start..end).map(move |i| {
776            let inst_ref = InstRef::from_raw(i);
777            (inst_ref, self.rir.get(inst_ref))
778        })
779    }
780
781    /// Get the number of instructions in this function view.
782    pub fn len(&self) -> usize {
783        (self.decl.as_u32() - self.body_start.as_u32() + 1) as usize
784    }
785
786    /// Whether this view is empty (should never be true for valid functions).
787    pub fn is_empty(&self) -> bool {
788        self.body_start.as_u32() > self.decl.as_u32()
789    }
790
791    /// Access the underlying RIR for operations that need the full context
792    /// (e.g., accessing extra data).
793    pub fn rir(&self) -> &'a Rir {
794        self.rir
795    }
796}
797
798/// ADR-0085: a body-less extern fn declared inside a `link_extern("…") { … }`
799/// block.
800///
801/// These are tracked as a side-set on the [`Rir`] rather than as
802/// `FnDecl` instructions because their lowering pipeline diverges from
803/// regular fns: no body to translate, no Gruel mangling, library name
804/// to thread through to the linker.
805#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
806pub struct RirExternFn {
807    /// Library name (interned, as written in the `link_extern("…")` head).
808    pub library: Spur,
809    /// Function name (as it appears in Gruel source).
810    pub name: Spur,
811    /// Index into the extra array where this fn's directives start.
812    pub directives_start: u32,
813    /// Number of directives.
814    pub directives_len: u32,
815    /// Index into the extra array where this fn's params start.
816    pub params_start: u32,
817    /// Number of parameters.
818    pub params_len: u32,
819    /// Return type as a `Spur` (defaults to `()` when omitted in source).
820    pub return_type: Spur,
821    /// Span of the fn declaration.
822    pub span: Span,
823    /// Span of the enclosing `link_extern(...)` block.
824    pub block_span: Span,
825    /// ADR-0086: dynamic (`link_extern`) vs static (`static_link_extern`).
826    #[serde(default = "default_rir_link_mode")]
827    pub link_mode: RirLinkMode,
828}
829
830/// ADR-0086: mirrors `gruel_parser::ast::LinkMode` after astgen.
831#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
832pub enum RirLinkMode {
833    Dynamic,
834    Static,
835}
836
837fn default_rir_link_mode() -> RirLinkMode {
838    RirLinkMode::Dynamic
839}
840
841/// The complete RIR for a source file.
842#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
843pub struct Rir {
844    /// All instructions in the file
845    instructions: Vec<Inst>,
846    /// Extra data for variable-length instruction payloads
847    extra: Vec<u32>,
848    /// Function boundaries for per-function analysis
849    function_spans: Vec<FunctionSpan>,
850    /// ADR-0085: body-less extern fns declared in `link_extern(...)` blocks.
851    extern_fns: Vec<RirExternFn>,
852    /// ADR-0085: `link_extern("lib") { }` blocks with no items. Tracked
853    /// separately so sema can validate the library name and emit the
854    /// `-l<lib>` flag even when no symbols are declared.
855    /// ADR-0086 widened the tuple to `(library, link_mode, span)`.
856    empty_link_extern_blocks: Vec<(Spur, RirLinkMode, Span)>,
857}
858
859impl Rir {
860    /// Create a new empty RIR.
861    pub fn new() -> Self {
862        Self::default()
863    }
864
865    /// Add an instruction and return its reference.
866    pub fn add_inst(&mut self, inst: Inst) -> InstRef {
867        // Debug assertion for u32 overflow - catches pathological inputs during development
868        debug_assert!(
869            self.instructions.len() < u32::MAX as usize,
870            "RIR instruction count overflow: {} instructions exceeds u32::MAX - 1",
871            self.instructions.len()
872        );
873
874        let index = self.instructions.len() as u32;
875        self.instructions.push(inst);
876        InstRef::from_raw(index)
877    }
878
879    /// Get an instruction by reference.
880    #[inline]
881    pub fn get(&self, inst_ref: InstRef) -> &Inst {
882        &self.instructions[inst_ref.0 as usize]
883    }
884
885    /// Get a mutable reference to an instruction.
886    #[inline]
887    pub fn get_mut(&mut self, inst_ref: InstRef) -> &mut Inst {
888        &mut self.instructions[inst_ref.0 as usize]
889    }
890
891    /// The number of instructions.
892    #[inline]
893    pub fn len(&self) -> usize {
894        self.instructions.len()
895    }
896
897    /// Whether there are no instructions.
898    #[inline]
899    pub fn is_empty(&self) -> bool {
900        self.instructions.is_empty()
901    }
902
903    /// Iterate over all instructions with their references.
904    pub fn iter(&self) -> impl Iterator<Item = (InstRef, &Inst)> {
905        self.instructions
906            .iter()
907            .enumerate()
908            .map(|(i, inst)| (InstRef::from_raw(i as u32), inst))
909    }
910
911    /// Add extra data and return the start index.
912    pub fn add_extra(&mut self, data: &[u32]) -> u32 {
913        // Debug assertions for u32 overflow - catches pathological inputs during development
914        debug_assert!(
915            self.extra.len() <= u32::MAX as usize,
916            "RIR extra data overflow: {} entries exceeds u32::MAX",
917            self.extra.len()
918        );
919        debug_assert!(
920            self.extra.len().saturating_add(data.len()) <= u32::MAX as usize,
921            "RIR extra data would overflow: {} + {} exceeds u32::MAX",
922            self.extra.len(),
923            data.len()
924        );
925
926        let start = self.extra.len() as u32;
927        self.extra.extend_from_slice(data);
928        start
929    }
930
931    /// Get extra data by index.
932    #[inline]
933    pub fn get_extra(&self, start: u32, len: u32) -> &[u32] {
934        let start = start as usize;
935        let end = start + len as usize;
936        &self.extra[start..end]
937    }
938
939    // ===== Helper methods for storing/retrieving typed data in the extra array =====
940
941    /// Store a slice of InstRefs and return (start, len).
942    pub fn add_inst_refs(&mut self, refs: &[InstRef]) -> (u32, u32) {
943        let data: Vec<u32> = refs.iter().map(|r| r.as_u32()).collect();
944        let start = self.add_extra(&data);
945        (start, refs.len() as u32)
946    }
947
948    /// Retrieve InstRefs from the extra array.
949    pub fn get_inst_refs(&self, start: u32, len: u32) -> Vec<InstRef> {
950        self.get_extra(start, len)
951            .iter()
952            .map(|&v| InstRef::from_raw(v))
953            .collect()
954    }
955
956    /// Store a slice of Spurs and return (start, len).
957    pub fn add_symbols(&mut self, symbols: &[Spur]) -> (u32, u32) {
958        let data: Vec<u32> = symbols.iter().map(|s| s.into_usize() as u32).collect();
959        let start = self.add_extra(&data);
960        (start, symbols.len() as u32)
961    }
962
963    /// Retrieve Spurs from the extra array.
964    pub fn get_symbols(&self, start: u32, len: u32) -> Vec<Spur> {
965        self.get_extra(start, len)
966            .iter()
967            .map(|&v| Spur::try_from_usize(v as usize).unwrap())
968            .collect()
969    }
970
971    /// Store enum variant declarations and return (start, variant_count).
972    ///
973    /// Each variant is encoded as variable-length data in the extra array:
974    ///   `[name_spur, field_count, is_struct, field_type_0, ..., field_type_n, field_name_0?, ..., field_name_n?]`
975    ///
976    /// - `is_struct`: 0 for unit/tuple variants, 1 for struct variants.
977    /// - For struct variants, field names follow field types.
978    /// - Unit variants have `field_count = 0`.
979    pub fn add_enum_variant_decls(
980        &mut self,
981        variants: &[(Spur, Vec<Spur>, Vec<Spur>)],
982    ) -> (u32, u32) {
983        let start = self.extra.len() as u32;
984        for (name, field_types, field_names) in variants {
985            self.extra.push(name.into_usize() as u32);
986            self.extra.push(field_types.len() as u32);
987            let is_struct = if field_names.is_empty() { 0u32 } else { 1u32 };
988            self.extra.push(is_struct);
989            for field_ty in field_types {
990                self.extra.push(field_ty.into_usize() as u32);
991            }
992            for field_name in field_names {
993                self.extra.push(field_name.into_usize() as u32);
994            }
995        }
996        (start, variants.len() as u32)
997    }
998
999    /// Retrieve enum variant declarations from the extra array.
1000    /// Returns a vec of `(variant_name, field_types, field_names)` triples.
1001    /// `field_names` is empty for unit/tuple variants.
1002    pub fn get_enum_variant_decls(
1003        &self,
1004        start: u32,
1005        variant_count: u32,
1006    ) -> Vec<(Spur, Vec<Spur>, Vec<Spur>)> {
1007        let mut result = Vec::with_capacity(variant_count as usize);
1008        let mut pos = start as usize;
1009        for _ in 0..variant_count {
1010            let name = Spur::try_from_usize(self.extra[pos] as usize).unwrap();
1011            let field_count = self.extra[pos + 1] as usize;
1012            let is_struct = self.extra[pos + 2] != 0;
1013            pos += 3;
1014            let field_types: Vec<Spur> = (0..field_count)
1015                .map(|i| Spur::try_from_usize(self.extra[pos + i] as usize).unwrap())
1016                .collect();
1017            pos += field_count;
1018            let field_names = if is_struct {
1019                let names: Vec<Spur> = (0..field_count)
1020                    .map(|i| Spur::try_from_usize(self.extra[pos + i] as usize).unwrap())
1021                    .collect();
1022                pos += field_count;
1023                names
1024            } else {
1025                Vec::new()
1026            };
1027            result.push((name, field_types, field_names));
1028        }
1029        result
1030    }
1031
1032    /// Store RirCallArgs and return (start, len).
1033    /// Layout: [value: u32, mode: u32] per arg
1034    pub fn add_call_args(&mut self, args: &[RirCallArg]) -> (u32, u32) {
1035        let mut data = Vec::with_capacity(args.len() * CALL_ARG_SIZE as usize);
1036        for arg in args {
1037            data.push(arg.value.as_u32());
1038            data.push(arg.mode as u32);
1039        }
1040        let start = self.add_extra(&data);
1041        (start, args.len() as u32)
1042    }
1043
1044    /// Retrieve RirCallArgs from the extra array.
1045    pub fn get_call_args(&self, start: u32, len: u32) -> Vec<RirCallArg> {
1046        let data = self.get_extra(start, len * CALL_ARG_SIZE);
1047        let mut args = Vec::with_capacity(len as usize);
1048        for chunk in data.chunks(CALL_ARG_SIZE as usize) {
1049            let value = InstRef::from_raw(chunk[0]);
1050            let mode = match chunk[1] {
1051                0 => RirArgMode::Normal,
1052                1 => RirArgMode::MutRef,
1053                2 => RirArgMode::Ref,
1054                _ => RirArgMode::Normal, // Fallback, shouldn't happen
1055            };
1056            args.push(RirCallArg { value, mode });
1057        }
1058        args
1059    }
1060
1061    /// Store RirParams and return (start, len).
1062    /// Layout: [name: u32, ty: u32, mode: u32, is_comptime: u32] per param
1063    pub fn add_params(&mut self, params: &[RirParam]) -> (u32, u32) {
1064        let mut data = Vec::with_capacity(params.len() * PARAM_SIZE as usize);
1065        for param in params {
1066            data.push(param.name.into_usize() as u32);
1067            data.push(param.ty.into_usize() as u32);
1068            data.push(param.mode as u32);
1069            data.push(param.is_comptime as u32);
1070        }
1071        let start = self.add_extra(&data);
1072        (start, params.len() as u32)
1073    }
1074
1075    /// Retrieve RirParams from the extra array.
1076    pub fn get_params(&self, start: u32, len: u32) -> Vec<RirParam> {
1077        let data = self.get_extra(start, len * PARAM_SIZE);
1078        let mut params = Vec::with_capacity(len as usize);
1079        for chunk in data.chunks(PARAM_SIZE as usize) {
1080            let name = Spur::try_from_usize(chunk[0] as usize).unwrap();
1081            let ty = Spur::try_from_usize(chunk[1] as usize).unwrap();
1082            let mode = match chunk[2] {
1083                0 => RirParamMode::Normal,
1084                1 => RirParamMode::MutRef,
1085                2 => RirParamMode::Ref,
1086                3 => RirParamMode::Comptime,
1087                _ => RirParamMode::Normal, // Fallback
1088            };
1089            let is_comptime = chunk[3] != 0;
1090            params.push(RirParam {
1091                name,
1092                ty,
1093                mode,
1094                is_comptime,
1095            });
1096        }
1097        params
1098    }
1099
1100    /// Store match arms (pattern + body pairs) and return (start, arm_count).
1101    /// Each arm is stored with variable size depending on pattern kind.
1102    pub fn add_match_arms(&mut self, arms: &[(RirPattern, InstRef)]) -> (u32, u32) {
1103        let start = self.extra.len() as u32;
1104        for (pattern, body) in arms {
1105            match pattern {
1106                RirPattern::Wildcard(span) => {
1107                    self.extra.push(PatternKind::Wildcard as u32);
1108                    self.extra.push(span.start());
1109                    self.extra.push(span.len());
1110                    self.extra.push(body.as_u32());
1111                }
1112                RirPattern::Int(value, span) => {
1113                    self.extra.push(PatternKind::Int as u32);
1114                    self.extra.push(span.start());
1115                    self.extra.push(span.len());
1116                    // Store i64 as two u32s (little-endian)
1117                    self.extra.push(*value as u32);
1118                    self.extra.push((*value >> 32) as u32);
1119                    self.extra.push(body.as_u32());
1120                }
1121                RirPattern::Bool(value, span) => {
1122                    self.extra.push(PatternKind::Bool as u32);
1123                    self.extra.push(span.start());
1124                    self.extra.push(span.len());
1125                    self.extra.push(if *value { 1 } else { 0 });
1126                    self.extra.push(body.as_u32());
1127                }
1128                RirPattern::Path {
1129                    module,
1130                    type_name,
1131                    variant,
1132                    span,
1133                } => {
1134                    self.extra.push(PatternKind::Path as u32);
1135                    self.extra.push(span.start());
1136                    self.extra.push(span.len());
1137                    // Store module as u32::MAX for None, otherwise the InstRef
1138                    self.extra.push(module.map_or(u32::MAX, |r| r.as_u32()));
1139                    self.extra.push(type_name.into_usize() as u32);
1140                    self.extra.push(variant.into_usize() as u32);
1141                    self.extra.push(body.as_u32());
1142                }
1143                RirPattern::DataVariant {
1144                    module,
1145                    type_name,
1146                    variant,
1147                    bindings,
1148                    span,
1149                } => {
1150                    self.extra.push(PatternKind::DataVariant as u32);
1151                    self.extra.push(span.start());
1152                    self.extra.push(span.len());
1153                    self.extra.push(module.map_or(u32::MAX, |r| r.as_u32()));
1154                    self.extra.push(type_name.into_usize() as u32);
1155                    self.extra.push(variant.into_usize() as u32);
1156                    self.extra.push(body.as_u32());
1157                    self.extra.push(bindings.len() as u32);
1158                    for binding in bindings {
1159                        encode_binding(binding, &mut self.extra);
1160                    }
1161                }
1162                RirPattern::StructVariant {
1163                    module,
1164                    type_name,
1165                    variant,
1166                    field_bindings,
1167                    span,
1168                } => {
1169                    self.extra.push(PatternKind::StructVariant as u32);
1170                    self.extra.push(span.start());
1171                    self.extra.push(span.len());
1172                    self.extra.push(module.map_or(u32::MAX, |r| r.as_u32()));
1173                    self.extra.push(type_name.into_usize() as u32);
1174                    self.extra.push(variant.into_usize() as u32);
1175                    self.extra.push(body.as_u32());
1176                    self.extra.push(field_bindings.len() as u32);
1177                    for fb in field_bindings {
1178                        encode_struct_binding(fb, &mut self.extra);
1179                    }
1180                }
1181                RirPattern::Ident { name, is_mut, span } => {
1182                    self.extra.push(PatternKind::Ident as u32);
1183                    self.extra.push(span.start());
1184                    self.extra.push(span.len());
1185                    self.extra.push(body.as_u32());
1186                    self.extra.push(name.into_usize() as u32);
1187                    self.extra.push(if *is_mut { 1 } else { 0 });
1188                }
1189                RirPattern::Tuple {
1190                    elems,
1191                    rest_position,
1192                    span,
1193                } => {
1194                    self.extra.push(PatternKind::Tuple as u32);
1195                    self.extra.push(span.start());
1196                    self.extra.push(span.len());
1197                    self.extra.push(body.as_u32());
1198                    self.extra.push(rest_position.map_or(u32::MAX, |i| i));
1199                    self.extra.push(elems.len() as u32);
1200                    for elem in elems {
1201                        encode_pattern_tree(elem, &mut self.extra);
1202                    }
1203                }
1204                RirPattern::Struct {
1205                    module,
1206                    type_name,
1207                    fields,
1208                    has_rest,
1209                    span,
1210                } => {
1211                    self.extra.push(PatternKind::Struct as u32);
1212                    self.extra.push(span.start());
1213                    self.extra.push(span.len());
1214                    self.extra.push(body.as_u32());
1215                    self.extra.push(module.map_or(u32::MAX, |r| r.as_u32()));
1216                    self.extra.push(type_name.into_usize() as u32);
1217                    self.extra.push(if *has_rest { 1 } else { 0 });
1218                    self.extra.push(fields.len() as u32);
1219                    for field in fields {
1220                        self.extra.push(field.field_name.into_usize() as u32);
1221                        encode_pattern_tree(&field.pattern, &mut self.extra);
1222                    }
1223                }
1224                RirPattern::ComptimeUnrollArm {
1225                    binding,
1226                    iterable,
1227                    span,
1228                } => {
1229                    self.extra.push(PatternKind::ComptimeUnrollArm as u32);
1230                    self.extra.push(span.start());
1231                    self.extra.push(span.len());
1232                    self.extra.push(body.as_u32());
1233                    self.extra.push(binding.into_usize() as u32);
1234                    self.extra.push(iterable.as_u32());
1235                }
1236            }
1237        }
1238        (start, arms.len() as u32)
1239    }
1240
1241    /// Retrieve match arms from the extra array.
1242    pub fn get_match_arms(&self, start: u32, arm_count: u32) -> Vec<(RirPattern, InstRef)> {
1243        let mut arms = Vec::with_capacity(arm_count as usize);
1244        let mut pos = start as usize;
1245
1246        for _ in 0..arm_count {
1247            let kind = self.extra[pos];
1248            match kind {
1249                k if k == PatternKind::Wildcard as u32 => {
1250                    let span_start = self.extra[pos + 1];
1251                    let span_len = self.extra[pos + 2];
1252                    let span = Span::new(span_start, span_start + span_len);
1253                    let body = InstRef::from_raw(self.extra[pos + 3]);
1254                    arms.push((RirPattern::Wildcard(span), body));
1255                    pos += PATTERN_WILDCARD_SIZE as usize;
1256                }
1257                k if k == PatternKind::Int as u32 => {
1258                    let span_start = self.extra[pos + 1];
1259                    let span_len = self.extra[pos + 2];
1260                    let span = Span::new(span_start, span_start + span_len);
1261                    let value_lo = self.extra[pos + 3] as i64;
1262                    let value_hi = self.extra[pos + 4] as i64;
1263                    let value = value_lo | (value_hi << 32);
1264                    let body = InstRef::from_raw(self.extra[pos + 5]);
1265                    arms.push((RirPattern::Int(value, span), body));
1266                    pos += PATTERN_INT_SIZE as usize;
1267                }
1268                k if k == PatternKind::Bool as u32 => {
1269                    let span_start = self.extra[pos + 1];
1270                    let span_len = self.extra[pos + 2];
1271                    let span = Span::new(span_start, span_start + span_len);
1272                    let value = self.extra[pos + 3] != 0;
1273                    let body = InstRef::from_raw(self.extra[pos + 4]);
1274                    arms.push((RirPattern::Bool(value, span), body));
1275                    pos += PATTERN_BOOL_SIZE as usize;
1276                }
1277                k if k == PatternKind::Path as u32 => {
1278                    let span_start = self.extra[pos + 1];
1279                    let span_len = self.extra[pos + 2];
1280                    let span = Span::new(span_start, span_start + span_len);
1281                    // Decode module: u32::MAX means None
1282                    let module_raw = self.extra[pos + 3];
1283                    let module = if module_raw == u32::MAX {
1284                        None
1285                    } else {
1286                        Some(InstRef::from_raw(module_raw))
1287                    };
1288                    let type_name = Spur::try_from_usize(self.extra[pos + 4] as usize).unwrap();
1289                    let variant = Spur::try_from_usize(self.extra[pos + 5] as usize).unwrap();
1290                    let body = InstRef::from_raw(self.extra[pos + 6]);
1291                    arms.push((
1292                        RirPattern::Path {
1293                            module,
1294                            type_name,
1295                            variant,
1296                            span,
1297                        },
1298                        body,
1299                    ));
1300                    pos += PATTERN_PATH_SIZE as usize;
1301                }
1302                k if k == PatternKind::DataVariant as u32 => {
1303                    let span_start = self.extra[pos + 1];
1304                    let span_len = self.extra[pos + 2];
1305                    let span = Span::new(span_start, span_start + span_len);
1306                    let module_raw = self.extra[pos + 3];
1307                    let module = if module_raw == u32::MAX {
1308                        None
1309                    } else {
1310                        Some(InstRef::from_raw(module_raw))
1311                    };
1312                    let type_name = Spur::try_from_usize(self.extra[pos + 4] as usize).unwrap();
1313                    let variant = Spur::try_from_usize(self.extra[pos + 5] as usize).unwrap();
1314                    let body = InstRef::from_raw(self.extra[pos + 6]);
1315                    let bindings_len = self.extra[pos + 7] as usize;
1316                    let mut bindings = Vec::with_capacity(bindings_len);
1317                    let mut offset = 8;
1318                    for _ in 0..bindings_len {
1319                        let (b, consumed) = decode_binding(&self.extra[pos + offset..]);
1320                        bindings.push(b);
1321                        offset += consumed;
1322                    }
1323                    arms.push((
1324                        RirPattern::DataVariant {
1325                            module,
1326                            type_name,
1327                            variant,
1328                            bindings,
1329                            span,
1330                        },
1331                        body,
1332                    ));
1333                    pos += offset;
1334                }
1335                k if k == PatternKind::StructVariant as u32 => {
1336                    let span_start = self.extra[pos + 1];
1337                    let span_len = self.extra[pos + 2];
1338                    let span = Span::new(span_start, span_start + span_len);
1339                    let module_raw = self.extra[pos + 3];
1340                    let module = if module_raw == u32::MAX {
1341                        None
1342                    } else {
1343                        Some(InstRef::from_raw(module_raw))
1344                    };
1345                    let type_name = Spur::try_from_usize(self.extra[pos + 4] as usize).unwrap();
1346                    let variant = Spur::try_from_usize(self.extra[pos + 5] as usize).unwrap();
1347                    let body = InstRef::from_raw(self.extra[pos + 6]);
1348                    let bindings_len = self.extra[pos + 7] as usize;
1349                    let mut field_bindings = Vec::with_capacity(bindings_len);
1350                    let mut offset = 8;
1351                    for _ in 0..bindings_len {
1352                        let (fb, consumed) = decode_struct_binding(&self.extra[pos + offset..]);
1353                        field_bindings.push(fb);
1354                        offset += consumed;
1355                    }
1356                    arms.push((
1357                        RirPattern::StructVariant {
1358                            module,
1359                            type_name,
1360                            variant,
1361                            field_bindings,
1362                            span,
1363                        },
1364                        body,
1365                    ));
1366                    pos += offset;
1367                }
1368                k if k == PatternKind::Ident as u32 => {
1369                    let span_start = self.extra[pos + 1];
1370                    let span_len = self.extra[pos + 2];
1371                    let span = Span::new(span_start, span_start + span_len);
1372                    let body = InstRef::from_raw(self.extra[pos + 3]);
1373                    let name = Spur::try_from_usize(self.extra[pos + 4] as usize).unwrap();
1374                    let is_mut = self.extra[pos + 5] != 0;
1375                    arms.push((RirPattern::Ident { name, is_mut, span }, body));
1376                    pos += 6;
1377                }
1378                k if k == PatternKind::Tuple as u32 => {
1379                    let span_start = self.extra[pos + 1];
1380                    let span_len = self.extra[pos + 2];
1381                    let span = Span::new(span_start, span_start + span_len);
1382                    let body = InstRef::from_raw(self.extra[pos + 3]);
1383                    let rest_raw = self.extra[pos + 4];
1384                    let rest_position = if rest_raw == u32::MAX {
1385                        None
1386                    } else {
1387                        Some(rest_raw)
1388                    };
1389                    let n = self.extra[pos + 5] as usize;
1390                    let mut elems = Vec::with_capacity(n);
1391                    let mut offset = 6;
1392                    for _ in 0..n {
1393                        let (p, consumed) = decode_pattern_tree(&self.extra[pos + offset..]);
1394                        elems.push(p);
1395                        offset += consumed;
1396                    }
1397                    arms.push((
1398                        RirPattern::Tuple {
1399                            elems,
1400                            rest_position,
1401                            span,
1402                        },
1403                        body,
1404                    ));
1405                    pos += offset;
1406                }
1407                k if k == PatternKind::Struct as u32 => {
1408                    let span_start = self.extra[pos + 1];
1409                    let span_len = self.extra[pos + 2];
1410                    let span = Span::new(span_start, span_start + span_len);
1411                    let body = InstRef::from_raw(self.extra[pos + 3]);
1412                    let module_raw = self.extra[pos + 4];
1413                    let module = if module_raw == u32::MAX {
1414                        None
1415                    } else {
1416                        Some(InstRef::from_raw(module_raw))
1417                    };
1418                    let type_name = Spur::try_from_usize(self.extra[pos + 5] as usize).unwrap();
1419                    let has_rest = self.extra[pos + 6] != 0;
1420                    let n = self.extra[pos + 7] as usize;
1421                    let mut fields = Vec::with_capacity(n);
1422                    let mut offset = 8;
1423                    for _ in 0..n {
1424                        let field_name =
1425                            Spur::try_from_usize(self.extra[pos + offset] as usize).unwrap();
1426                        offset += 1;
1427                        let (pattern, consumed) = decode_pattern_tree(&self.extra[pos + offset..]);
1428                        offset += consumed;
1429                        fields.push(RirStructField {
1430                            field_name,
1431                            pattern,
1432                        });
1433                    }
1434                    arms.push((
1435                        RirPattern::Struct {
1436                            module,
1437                            type_name,
1438                            fields,
1439                            has_rest,
1440                            span,
1441                        },
1442                        body,
1443                    ));
1444                    pos += offset;
1445                }
1446                k if k == PatternKind::ComptimeUnrollArm as u32 => {
1447                    let span_start = self.extra[pos + 1];
1448                    let span_len = self.extra[pos + 2];
1449                    let span = Span::new(span_start, span_start + span_len);
1450                    let body = InstRef::from_raw(self.extra[pos + 3]);
1451                    let binding = Spur::try_from_usize(self.extra[pos + 4] as usize).unwrap();
1452                    let iterable = InstRef::from_raw(self.extra[pos + 5]);
1453                    arms.push((
1454                        RirPattern::ComptimeUnrollArm {
1455                            binding,
1456                            iterable,
1457                            span,
1458                        },
1459                        body,
1460                    ));
1461                    pos += 6;
1462                }
1463                _ => panic!("Unknown pattern kind: {}", kind),
1464            }
1465        }
1466        arms
1467    }
1468
1469    /// Store field initializers (name, value) and return (start, len).
1470    /// Layout: [name: u32, value: u32] per field
1471    pub fn add_field_inits(&mut self, fields: &[(Spur, InstRef)]) -> (u32, u32) {
1472        let mut data = Vec::with_capacity(fields.len() * FIELD_INIT_SIZE as usize);
1473        for (name, value) in fields {
1474            data.push(name.into_usize() as u32);
1475            data.push(value.as_u32());
1476        }
1477        let start = self.add_extra(&data);
1478        (start, fields.len() as u32)
1479    }
1480
1481    /// Retrieve field initializers from the extra array.
1482    pub fn get_field_inits(&self, start: u32, len: u32) -> Vec<(Spur, InstRef)> {
1483        let data = self.get_extra(start, len * FIELD_INIT_SIZE);
1484        let mut fields = Vec::with_capacity(len as usize);
1485        for chunk in data.chunks(FIELD_INIT_SIZE as usize) {
1486            let name = Spur::try_from_usize(chunk[0] as usize).unwrap();
1487            let value = InstRef::from_raw(chunk[1]);
1488            fields.push((name, value));
1489        }
1490        fields
1491    }
1492
1493    /// Store field declarations (name, type, is_pub) and return (start, len).
1494    /// Layout: [name: u32, type: u32, is_pub: u32] per field. ADR-0073 added
1495    /// the `is_pub` slot; pre-ADR call sites pass `false` for compatibility.
1496    pub fn add_field_decls(&mut self, fields: &[(Spur, Spur)]) -> (u32, u32) {
1497        let with_vis: Vec<(Spur, Spur, bool)> =
1498            fields.iter().map(|(n, t)| (*n, *t, false)).collect();
1499        self.add_field_decls_with_vis(&with_vis)
1500    }
1501
1502    /// Store field declarations including visibility (ADR-0073).
1503    pub fn add_field_decls_with_vis(&mut self, fields: &[(Spur, Spur, bool)]) -> (u32, u32) {
1504        let mut data = Vec::with_capacity(fields.len() * FIELD_DECL_SIZE as usize);
1505        for (name, ty, is_pub) in fields {
1506            data.push(name.into_usize() as u32);
1507            data.push(ty.into_usize() as u32);
1508            data.push(if *is_pub { 1 } else { 0 });
1509        }
1510        let start = self.add_extra(&data);
1511        (start, fields.len() as u32)
1512    }
1513
1514    /// Retrieve field declarations (name, type) from the extra array.
1515    pub fn get_field_decls(&self, start: u32, len: u32) -> Vec<(Spur, Spur)> {
1516        self.get_field_decls_with_vis(start, len)
1517            .into_iter()
1518            .map(|(n, t, _)| (n, t))
1519            .collect()
1520    }
1521
1522    /// Retrieve field declarations with visibility (ADR-0073).
1523    pub fn get_field_decls_with_vis(&self, start: u32, len: u32) -> Vec<(Spur, Spur, bool)> {
1524        let data = self.get_extra(start, len * FIELD_DECL_SIZE);
1525        let mut fields = Vec::with_capacity(len as usize);
1526        for chunk in data.chunks(FIELD_DECL_SIZE as usize) {
1527            let name = Spur::try_from_usize(chunk[0] as usize).unwrap();
1528            let ty = Spur::try_from_usize(chunk[1] as usize).unwrap();
1529            let is_pub = chunk[2] != 0;
1530            fields.push((name, ty, is_pub));
1531        }
1532        fields
1533    }
1534
1535    /// Store directives and return (start, directive_count).
1536    /// Layout: [name: u32, span_start: u32, span_len: u32, args_len: u32, args...] per directive
1537    pub fn add_directives(&mut self, directives: &[RirDirective]) -> (u32, u32) {
1538        let start = self.extra.len() as u32;
1539        for directive in directives {
1540            self.extra.push(directive.name.into_usize() as u32);
1541            self.extra.push(directive.span.start());
1542            self.extra.push(directive.span.len());
1543            self.extra.push(directive.args.len() as u32);
1544            for arg in &directive.args {
1545                self.extra.push(arg.into_usize() as u32);
1546            }
1547        }
1548        (start, directives.len() as u32)
1549    }
1550
1551    /// Retrieve directives from the extra array.
1552    pub fn get_directives(&self, start: u32, directive_count: u32) -> Vec<RirDirective> {
1553        let mut directives = Vec::with_capacity(directive_count as usize);
1554        let mut pos = start as usize;
1555
1556        for _ in 0..directive_count {
1557            let name = Spur::try_from_usize(self.extra[pos] as usize).unwrap();
1558            let span = Span::new(self.extra[pos + 1], self.extra[pos + 2]);
1559            let args_len = self.extra[pos + 3] as usize;
1560            pos += 4;
1561
1562            let args: Vec<Spur> = (0..args_len)
1563                .map(|i| Spur::try_from_usize(self.extra[pos + i] as usize).unwrap())
1564                .collect();
1565            pos += args_len;
1566
1567            directives.push(RirDirective { name, args, span });
1568        }
1569        directives
1570    }
1571
1572    /// Store destructure fields and return (start, field_count).
1573    ///
1574    /// `binding_name` is `Option<Spur>`. `None` encodes as `u32::MAX` —
1575    /// using `0` as the sentinel was wrong because `Spur::into_usize` is
1576    /// 0-indexed (the first-interned Spur lives at index 0), so the very
1577    /// first name interned in the build would round-trip back as `None`
1578    /// and lose its binding. `u32::MAX` matches the encoding already used
1579    /// by `encode_binding` / `decode_binding` above.
1580    pub fn add_destructure_fields(&mut self, fields: &[RirDestructureField]) -> (u32, u32) {
1581        let start = self.extra.len() as u32;
1582        for field in fields {
1583            self.extra.push(field.field_name.into_usize() as u32);
1584            self.extra.push(
1585                field
1586                    .binding_name
1587                    .map_or(u32::MAX, |s| s.into_usize() as u32),
1588            );
1589            self.extra.push(field.is_wildcard as u32);
1590            self.extra.push(field.is_mut as u32);
1591        }
1592        (start, fields.len() as u32)
1593    }
1594
1595    /// Retrieve destructure fields from the extra array.
1596    pub fn get_destructure_fields(&self, start: u32, count: u32) -> Vec<RirDestructureField> {
1597        let mut fields = Vec::with_capacity(count as usize);
1598        for i in 0..count {
1599            let pos = (start + i * DESTRUCTURE_FIELD_SIZE) as usize;
1600            let field_name = Spur::try_from_usize(self.extra[pos] as usize).unwrap();
1601            let binding_raw = self.extra[pos + 1];
1602            let binding_name = if binding_raw == u32::MAX {
1603                None
1604            } else {
1605                Some(Spur::try_from_usize(binding_raw as usize).unwrap())
1606            };
1607            let is_wildcard = self.extra[pos + 2] != 0;
1608            let is_mut = self.extra[pos + 3] != 0;
1609            fields.push(RirDestructureField {
1610                field_name,
1611                binding_name,
1612                is_wildcard,
1613                is_mut,
1614            });
1615        }
1616        fields
1617    }
1618
1619    // ===== Function span methods =====
1620
1621    /// Add a function span to track function boundaries.
1622    /// ADR-0085: append an extern fn declaration (from inside a `link_extern`
1623    /// block).
1624    pub fn add_extern_fn(&mut self, extern_fn: RirExternFn) {
1625        self.extern_fns.push(extern_fn);
1626    }
1627
1628    /// ADR-0085: read-only view over all extern fns declared in this file.
1629    pub fn extern_fns(&self) -> &[RirExternFn] {
1630        &self.extern_fns
1631    }
1632
1633    /// ADR-0085: append an empty `link_extern("lib") { }` block.
1634    pub fn add_empty_link_extern_block(
1635        &mut self,
1636        library: Spur,
1637        link_mode: RirLinkMode,
1638        span: Span,
1639    ) {
1640        self.empty_link_extern_blocks
1641            .push((library, link_mode, span));
1642    }
1643
1644    /// ADR-0085: read-only view over `link_extern` blocks that declared
1645    /// no symbols. Sema validates the library name and emits the
1646    /// corresponding `-l<lib>` link flag.
1647    pub fn empty_link_extern_blocks(&self) -> &[(Spur, RirLinkMode, Span)] {
1648        &self.empty_link_extern_blocks
1649    }
1650
1651    pub fn add_function_span(&mut self, span: FunctionSpan) {
1652        self.function_spans.push(span);
1653    }
1654
1655    /// Get all function spans.
1656    pub fn function_spans(&self) -> &[FunctionSpan] {
1657        &self.function_spans
1658    }
1659
1660    /// Iterate over function spans.
1661    pub fn functions(&self) -> impl Iterator<Item = &FunctionSpan> {
1662        self.function_spans.iter()
1663    }
1664
1665    /// Get the number of functions.
1666    pub fn function_count(&self) -> usize {
1667        self.function_spans.len()
1668    }
1669
1670    /// Get a view of just one function's instructions.
1671    pub fn function_view(&self, fn_span: &FunctionSpan) -> RirFunctionView<'_> {
1672        RirFunctionView {
1673            rir: self,
1674            body_start: fn_span.body_start,
1675            decl: fn_span.decl,
1676        }
1677    }
1678
1679    /// Find a function span by name.
1680    pub fn find_function(&self, name: Spur) -> Option<&FunctionSpan> {
1681        self.function_spans.iter().find(|span| span.name == name)
1682    }
1683
1684    /// Get the current instruction count (useful for tracking body start).
1685    pub fn current_inst_index(&self) -> u32 {
1686        self.instructions.len() as u32
1687    }
1688
1689    /// Merge multiple RIRs into a single RIR.
1690    ///
1691    /// This is used for parallel per-file RIR generation. Each file generates
1692    /// its own RIR in parallel, then they are merged into a single RIR with
1693    /// all instruction references renumbered to be valid in the merged RIR.
1694    ///
1695    /// # Arguments
1696    ///
1697    /// * `rirs` - Slice of RIRs to merge (typically one per source file)
1698    ///
1699    /// # Returns
1700    ///
1701    /// A new merged RIR containing all instructions from all input RIRs.
1702    pub fn merge(rirs: &[Rir]) -> Rir {
1703        if rirs.is_empty() {
1704            return Rir::new();
1705        }
1706
1707        if rirs.len() == 1 {
1708            // Clone the single RIR directly
1709            return Rir {
1710                instructions: rirs[0].instructions.clone(),
1711                extra: rirs[0].extra.clone(),
1712                function_spans: rirs[0].function_spans.clone(),
1713                extern_fns: rirs[0].extern_fns.clone(),
1714                empty_link_extern_blocks: rirs[0].empty_link_extern_blocks.clone(),
1715            };
1716        }
1717
1718        // Calculate total sizes for preallocation
1719        let total_instructions: usize = rirs.iter().map(|r| r.instructions.len()).sum();
1720        let total_extra: usize = rirs.iter().map(|r| r.extra.len()).sum();
1721        let total_functions: usize = rirs.iter().map(|r| r.function_spans.len()).sum();
1722        let total_extern: usize = rirs.iter().map(|r| r.extern_fns.len()).sum();
1723        let total_empty_blocks: usize = rirs.iter().map(|r| r.empty_link_extern_blocks.len()).sum();
1724
1725        let mut merged = Rir {
1726            instructions: Vec::with_capacity(total_instructions),
1727            extra: Vec::with_capacity(total_extra),
1728            function_spans: Vec::with_capacity(total_functions),
1729            extern_fns: Vec::with_capacity(total_extern),
1730            empty_link_extern_blocks: Vec::with_capacity(total_empty_blocks),
1731        };
1732
1733        // Track offsets as we merge each RIR
1734        let mut inst_offset: u32 = 0;
1735        let mut extra_offset: u32 = 0;
1736
1737        for rir in rirs {
1738            // Merge extra data first (append raw bytes)
1739            merged.extra.extend_from_slice(&rir.extra);
1740
1741            // Merge and renumber instructions
1742            for inst in &rir.instructions {
1743                let renumbered = Self::renumber_instruction(inst, inst_offset, extra_offset);
1744                merged.instructions.push(renumbered);
1745            }
1746
1747            // Renumber InstRefs in the extra array
1748            // This handles call args, match arms, field inits, etc.
1749            Self::renumber_extra_inst_refs(
1750                &mut merged.extra,
1751                &rir.instructions,
1752                inst_offset,
1753                extra_offset,
1754            );
1755
1756            // Merge function spans with renumbered references
1757            for fn_span in &rir.function_spans {
1758                merged.function_spans.push(FunctionSpan {
1759                    name: fn_span.name,
1760                    body_start: InstRef::from_raw(fn_span.body_start.as_u32() + inst_offset),
1761                    decl: InstRef::from_raw(fn_span.decl.as_u32() + inst_offset),
1762                });
1763            }
1764
1765            // ADR-0085: extern fns live in the side table; their
1766            // directive/params indices reference the extra array, so we
1767            // need to shift them by the merged-file offset.
1768            for ext in &rir.extern_fns {
1769                merged.extern_fns.push(RirExternFn {
1770                    library: ext.library,
1771                    name: ext.name,
1772                    directives_start: ext.directives_start + extra_offset,
1773                    directives_len: ext.directives_len,
1774                    params_start: ext.params_start + extra_offset,
1775                    params_len: ext.params_len,
1776                    return_type: ext.return_type,
1777                    span: ext.span,
1778                    block_span: ext.block_span,
1779                    link_mode: ext.link_mode,
1780                });
1781            }
1782
1783            // ADR-0085: empty `link_extern` blocks need their own
1784            // pass-through to preserve `-l<lib>` flags when the block
1785            // declares no symbols.
1786            merged
1787                .empty_link_extern_blocks
1788                .extend_from_slice(&rir.empty_link_extern_blocks);
1789
1790            // Update offsets for the next RIR
1791            inst_offset += rir.instructions.len() as u32;
1792            extra_offset += rir.extra.len() as u32;
1793        }
1794
1795        merged
1796    }
1797
1798    /// Renumber a single instruction's InstRef fields.
1799    fn renumber_instruction(inst: &Inst, inst_offset: u32, extra_offset: u32) -> Inst {
1800        let renumber = |r: InstRef| InstRef::from_raw(r.as_u32() + inst_offset);
1801        let renumber_opt = |r: Option<InstRef>| r.map(renumber);
1802
1803        let data = match &inst.data {
1804            // No renumbering needed for these
1805            InstData::IntConst(v) => InstData::IntConst(*v),
1806            InstData::FloatConst(bits) => InstData::FloatConst(*bits),
1807            InstData::BoolConst(v) => InstData::BoolConst(*v),
1808            InstData::CharConst(v) => InstData::CharConst(*v),
1809            InstData::StringConst(s) => InstData::StringConst(*s),
1810            InstData::UnitConst => InstData::UnitConst,
1811            InstData::Break => InstData::Break,
1812            InstData::Continue => InstData::Continue,
1813            InstData::VarRef { name } => InstData::VarRef { name: *name },
1814            InstData::ParamRef { index, name } => InstData::ParamRef {
1815                index: *index,
1816                name: *name,
1817            },
1818            InstData::EnumVariant {
1819                module,
1820                type_name,
1821                variant,
1822            } => InstData::EnumVariant {
1823                module: module.map(renumber),
1824                type_name: *type_name,
1825                variant: *variant,
1826            },
1827            InstData::EnumStructVariant {
1828                module,
1829                type_name,
1830                variant,
1831                fields_start,
1832                fields_len,
1833            } => InstData::EnumStructVariant {
1834                module: module.map(renumber),
1835                type_name: *type_name,
1836                variant: *variant,
1837                fields_start: *fields_start,
1838                fields_len: *fields_len,
1839            },
1840            InstData::TypeIntrinsic { name, type_arg } => InstData::TypeIntrinsic {
1841                name: *name,
1842                type_arg: *type_arg,
1843            },
1844            InstData::TypeInterfaceIntrinsic {
1845                name,
1846                type_arg,
1847                type_inst,
1848                interface_arg,
1849            } => InstData::TypeInterfaceIntrinsic {
1850                name: *name,
1851                type_arg: *type_arg,
1852                type_inst: type_inst.map(renumber),
1853                interface_arg: *interface_arg,
1854            },
1855
1856            InstData::Bin { op, lhs, rhs } => InstData::Bin {
1857                op: *op,
1858                lhs: renumber(*lhs),
1859                rhs: renumber(*rhs),
1860            },
1861            InstData::Unary { op, operand } => InstData::Unary {
1862                op: *op,
1863                operand: renumber(*operand),
1864            },
1865            InstData::MakeRef { operand, is_mut } => InstData::MakeRef {
1866                operand: renumber(*operand),
1867                is_mut: *is_mut,
1868            },
1869            InstData::BareRangeSubscript => InstData::BareRangeSubscript,
1870            InstData::MakeSlice {
1871                base,
1872                lo,
1873                hi,
1874                is_mut,
1875            } => InstData::MakeSlice {
1876                base: renumber(*base),
1877                lo: renumber_opt(*lo),
1878                hi: renumber_opt(*hi),
1879                is_mut: *is_mut,
1880            },
1881
1882            // Control flow
1883            InstData::Branch {
1884                cond,
1885                then_block,
1886                else_block,
1887                is_comptime,
1888            } => InstData::Branch {
1889                cond: renumber(*cond),
1890                then_block: renumber(*then_block),
1891                else_block: renumber_opt(*else_block),
1892                is_comptime: *is_comptime,
1893            },
1894            InstData::Loop { cond, body } => InstData::Loop {
1895                cond: renumber(*cond),
1896                body: renumber(*body),
1897            },
1898            InstData::For {
1899                binding,
1900                is_mut,
1901                iterable,
1902                body,
1903            } => InstData::For {
1904                binding: *binding,
1905                is_mut: *is_mut,
1906                iterable: renumber(*iterable),
1907                body: renumber(*body),
1908            },
1909            InstData::InfiniteLoop { body } => InstData::InfiniteLoop {
1910                body: renumber(*body),
1911            },
1912            InstData::Ret(value) => InstData::Ret(renumber_opt(*value)),
1913
1914            // Match - InstRefs in extra are handled separately
1915            InstData::Match {
1916                scrutinee,
1917                arms_start,
1918                arms_len,
1919            } => InstData::Match {
1920                scrutinee: renumber(*scrutinee),
1921                arms_start: *arms_start + extra_offset,
1922                arms_len: *arms_len,
1923            },
1924
1925            // Block - InstRefs in extra are handled separately
1926            InstData::Block { extra_start, len } => InstData::Block {
1927                extra_start: *extra_start + extra_offset,
1928                len: *len,
1929            },
1930
1931            // Variable operations
1932            InstData::Alloc {
1933                directives_start,
1934                directives_len,
1935                name,
1936                is_mut,
1937                ty,
1938                init,
1939            } => InstData::Alloc {
1940                directives_start: *directives_start + extra_offset,
1941                directives_len: *directives_len,
1942                name: *name,
1943                is_mut: *is_mut,
1944                ty: *ty,
1945                init: renumber(*init),
1946            },
1947            InstData::StructDestructure {
1948                type_name,
1949                fields_start,
1950                fields_len,
1951                init,
1952            } => InstData::StructDestructure {
1953                type_name: *type_name,
1954                fields_start: *fields_start + extra_offset,
1955                fields_len: *fields_len,
1956                init: renumber(*init),
1957            },
1958            InstData::Assign { name, value } => InstData::Assign {
1959                name: *name,
1960                value: renumber(*value),
1961            },
1962
1963            // Function definition - body and params in extra
1964            InstData::FnDecl {
1965                directives_start,
1966                directives_len,
1967                is_pub,
1968                is_unchecked,
1969                name,
1970                params_start,
1971                params_len,
1972                return_type,
1973                body,
1974                has_self,
1975                receiver_mode,
1976            } => InstData::FnDecl {
1977                directives_start: *directives_start + extra_offset,
1978                directives_len: *directives_len,
1979                is_pub: *is_pub,
1980                is_unchecked: *is_unchecked,
1981                name: *name,
1982                params_start: *params_start + extra_offset,
1983                params_len: *params_len,
1984                return_type: *return_type,
1985                body: renumber(*body),
1986                has_self: *has_self,
1987                receiver_mode: *receiver_mode,
1988            },
1989
1990            // Constant declaration - init is an InstRef
1991            InstData::ConstDecl {
1992                directives_start,
1993                directives_len,
1994                is_pub,
1995                name,
1996                ty,
1997                init,
1998            } => InstData::ConstDecl {
1999                directives_start: *directives_start + extra_offset,
2000                directives_len: *directives_len,
2001                is_pub: *is_pub,
2002                name: *name,
2003                ty: *ty,
2004                init: renumber(*init),
2005            },
2006
2007            // Function call - args in extra
2008            InstData::Call {
2009                name,
2010                args_start,
2011                args_len,
2012            } => InstData::Call {
2013                name: *name,
2014                args_start: *args_start + extra_offset,
2015                args_len: *args_len,
2016            },
2017
2018            // Intrinsic - args in extra
2019            InstData::Intrinsic {
2020                name,
2021                args_start,
2022                args_len,
2023            } => InstData::Intrinsic {
2024                name: *name,
2025                args_start: *args_start + extra_offset,
2026                args_len: *args_len,
2027            },
2028
2029            // Struct operations
2030            InstData::StructDecl {
2031                directives_start,
2032                directives_len,
2033                is_pub,
2034                posture,
2035                name,
2036                fields_start,
2037                fields_len,
2038                methods_start,
2039                methods_len,
2040            } => InstData::StructDecl {
2041                directives_start: *directives_start + extra_offset,
2042                directives_len: *directives_len,
2043                is_pub: *is_pub,
2044                posture: *posture,
2045                name: *name,
2046                fields_start: *fields_start + extra_offset,
2047                fields_len: *fields_len,
2048                methods_start: *methods_start + extra_offset,
2049                methods_len: *methods_len,
2050            },
2051            InstData::StructInit {
2052                module,
2053                type_name,
2054                fields_start,
2055                fields_len,
2056            } => InstData::StructInit {
2057                module: module.map(renumber),
2058                type_name: *type_name,
2059                fields_start: *fields_start + extra_offset,
2060                fields_len: *fields_len,
2061            },
2062            InstData::FieldGet { base, field } => InstData::FieldGet {
2063                base: renumber(*base),
2064                field: *field,
2065            },
2066            InstData::FieldSet { base, field, value } => InstData::FieldSet {
2067                base: renumber(*base),
2068                field: *field,
2069                value: renumber(*value),
2070            },
2071
2072            // Interface declarations - method-sig refs in extra
2073            InstData::InterfaceDecl {
2074                is_pub,
2075                name,
2076                methods_start,
2077                methods_len,
2078                directives_start,
2079                directives_len,
2080            } => InstData::InterfaceDecl {
2081                is_pub: *is_pub,
2082                name: *name,
2083                methods_start: *methods_start + extra_offset,
2084                methods_len: *methods_len,
2085                directives_start: if *directives_len == 0 {
2086                    *directives_start
2087                } else {
2088                    *directives_start + extra_offset
2089                },
2090                directives_len: *directives_len,
2091            },
2092            InstData::InterfaceMethodSig {
2093                name,
2094                params_start,
2095                params_len,
2096                return_type,
2097                receiver_mode,
2098                is_unchecked,
2099                directives_start,
2100                directives_len,
2101            } => InstData::InterfaceMethodSig {
2102                name: *name,
2103                params_start: *params_start + extra_offset,
2104                params_len: *params_len,
2105                return_type: *return_type,
2106                receiver_mode: *receiver_mode,
2107                is_unchecked: *is_unchecked,
2108                directives_start: if *directives_len == 0 {
2109                    *directives_start
2110                } else {
2111                    *directives_start + extra_offset
2112                },
2113                directives_len: *directives_len,
2114            },
2115
2116            // Enum operations
2117            InstData::EnumDecl {
2118                is_pub,
2119                posture,
2120                name,
2121                variants_start,
2122                variants_len,
2123                methods_start,
2124                methods_len,
2125                directives_start,
2126                directives_len,
2127            } => InstData::EnumDecl {
2128                is_pub: *is_pub,
2129                posture: *posture,
2130                name: *name,
2131                variants_start: *variants_start + extra_offset,
2132                variants_len: *variants_len,
2133                methods_start: *methods_start + extra_offset,
2134                methods_len: *methods_len,
2135                directives_start: if *directives_len == 0 {
2136                    *directives_start
2137                } else {
2138                    *directives_start + extra_offset
2139                },
2140                directives_len: *directives_len,
2141            },
2142
2143            // Array operations
2144            InstData::ArrayInit {
2145                elems_start,
2146                elems_len,
2147            } => InstData::ArrayInit {
2148                elems_start: *elems_start + extra_offset,
2149                elems_len: *elems_len,
2150            },
2151            InstData::IndexGet { base, index } => InstData::IndexGet {
2152                base: renumber(*base),
2153                index: renumber(*index),
2154            },
2155            InstData::IndexSet { base, index, value } => InstData::IndexSet {
2156                base: renumber(*base),
2157                index: renumber(*index),
2158                value: renumber(*value),
2159            },
2160
2161            // Method operations
2162            InstData::MethodCall {
2163                receiver,
2164                method,
2165                args_start,
2166                args_len,
2167            } => InstData::MethodCall {
2168                receiver: renumber(*receiver),
2169                method: *method,
2170                args_start: *args_start + extra_offset,
2171                args_len: *args_len,
2172            },
2173            InstData::AssocFnCall {
2174                type_name,
2175                function,
2176                args_start,
2177                args_len,
2178            } => InstData::AssocFnCall {
2179                type_name: *type_name,
2180                function: *function,
2181                args_start: *args_start + extra_offset,
2182                args_len: *args_len,
2183            },
2184            InstData::DeriveDecl {
2185                name,
2186                methods_start,
2187                methods_len,
2188            } => InstData::DeriveDecl {
2189                name: *name,
2190                methods_start: *methods_start + extra_offset,
2191                methods_len: *methods_len,
2192            },
2193            InstData::Comptime { expr } => InstData::Comptime {
2194                expr: renumber(*expr),
2195            },
2196            InstData::ComptimeUnrollFor {
2197                binding,
2198                iterable,
2199                body,
2200            } => InstData::ComptimeUnrollFor {
2201                binding: *binding,
2202                iterable: renumber(*iterable),
2203                body: renumber(*body),
2204            },
2205            InstData::Checked { expr } => InstData::Checked {
2206                expr: renumber(*expr),
2207            },
2208            InstData::TypeConst { type_name } => InstData::TypeConst {
2209                type_name: *type_name,
2210            },
2211            InstData::AnonStructType {
2212                directives_start,
2213                directives_len,
2214                fields_start,
2215                fields_len,
2216                methods_start,
2217                methods_len,
2218            } => InstData::AnonStructType {
2219                directives_start: if *directives_len == 0 {
2220                    *directives_start
2221                } else {
2222                    *directives_start + extra_offset
2223                },
2224                directives_len: *directives_len,
2225                fields_start: *fields_start + extra_offset,
2226                fields_len: *fields_len,
2227                methods_start: *methods_start + extra_offset,
2228                methods_len: *methods_len,
2229            },
2230            InstData::AnonEnumType {
2231                directives_start,
2232                directives_len,
2233                variants_start,
2234                variants_len,
2235                methods_start,
2236                methods_len,
2237            } => InstData::AnonEnumType {
2238                directives_start: if *directives_len == 0 {
2239                    *directives_start
2240                } else {
2241                    *directives_start + extra_offset
2242                },
2243                directives_len: *directives_len,
2244                variants_start: *variants_start + extra_offset,
2245                variants_len: *variants_len,
2246                methods_start: *methods_start + extra_offset,
2247                methods_len: *methods_len,
2248            },
2249            InstData::AnonInterfaceType {
2250                methods_start,
2251                methods_len,
2252            } => InstData::AnonInterfaceType {
2253                methods_start: *methods_start + extra_offset,
2254                methods_len: *methods_len,
2255            },
2256            InstData::TupleInit {
2257                elems_start,
2258                elems_len,
2259            } => InstData::TupleInit {
2260                elems_start: *elems_start + extra_offset,
2261                elems_len: *elems_len,
2262            },
2263            InstData::AnonFnValue { method } => InstData::AnonFnValue {
2264                method: renumber(*method),
2265            },
2266        };
2267
2268        Inst {
2269            data,
2270            span: inst.span,
2271        }
2272    }
2273
2274    /// Renumber InstRefs stored in the extra array.
2275    ///
2276    /// This handles:
2277    /// - Block instruction refs (simple u32 array)
2278    /// - Array init element refs (simple u32 array)
2279    /// - Intrinsic arg refs (simple u32 array)
2280    /// - Impl method refs (simple u32 array)
2281    /// - Call args (value field of each arg)
2282    /// - Field inits (value field of each init)
2283    /// - Match arm bodies (last field of each arm)
2284    fn renumber_extra_inst_refs(
2285        extra: &mut [u32],
2286        instructions: &[Inst],
2287        inst_offset: u32,
2288        extra_offset: u32,
2289    ) {
2290        for inst in instructions {
2291            match &inst.data {
2292                // Block - contains InstRef array
2293                InstData::Block { extra_start, len } => {
2294                    let start = (*extra_start + extra_offset) as usize;
2295                    for i in 0..*len as usize {
2296                        extra[start + i] += inst_offset;
2297                    }
2298                }
2299
2300                // Array init - contains InstRef array
2301                InstData::ArrayInit {
2302                    elems_start,
2303                    elems_len,
2304                } => {
2305                    let start = (*elems_start + extra_offset) as usize;
2306                    for i in 0..*elems_len as usize {
2307                        extra[start + i] += inst_offset;
2308                    }
2309                }
2310
2311                // Tuple init - contains InstRef array (same layout as ArrayInit)
2312                InstData::TupleInit {
2313                    elems_start,
2314                    elems_len,
2315                } => {
2316                    let start = (*elems_start + extra_offset) as usize;
2317                    for i in 0..*elems_len as usize {
2318                        extra[start + i] += inst_offset;
2319                    }
2320                }
2321
2322                // Intrinsic - contains InstRef array
2323                InstData::Intrinsic {
2324                    args_start,
2325                    args_len,
2326                    ..
2327                } => {
2328                    let start = (*args_start + extra_offset) as usize;
2329                    for i in 0..*args_len as usize {
2330                        extra[start + i] += inst_offset;
2331                    }
2332                }
2333
2334                // Struct decl - contains InstRef array for methods
2335                InstData::StructDecl {
2336                    methods_start,
2337                    methods_len,
2338                    ..
2339                } => {
2340                    let start = (*methods_start + extra_offset) as usize;
2341                    for i in 0..*methods_len as usize {
2342                        extra[start + i] += inst_offset;
2343                    }
2344                }
2345
2346                // Interface decl - contains InstRef array for method signatures
2347                InstData::InterfaceDecl {
2348                    methods_start,
2349                    methods_len,
2350                    ..
2351                } => {
2352                    let start = (*methods_start + extra_offset) as usize;
2353                    for i in 0..*methods_len as usize {
2354                        extra[start + i] += inst_offset;
2355                    }
2356                }
2357
2358                // Derive decl - contains InstRef array for method declarations
2359                InstData::DeriveDecl {
2360                    methods_start,
2361                    methods_len,
2362                    ..
2363                } => {
2364                    let start = (*methods_start + extra_offset) as usize;
2365                    for i in 0..*methods_len as usize {
2366                        extra[start + i] += inst_offset;
2367                    }
2368                }
2369
2370                // Anonymous interface type - contains InstRef array for method signatures
2371                InstData::AnonInterfaceType {
2372                    methods_start,
2373                    methods_len,
2374                } => {
2375                    let start = (*methods_start + extra_offset) as usize;
2376                    for i in 0..*methods_len as usize {
2377                        extra[start + i] += inst_offset;
2378                    }
2379                }
2380
2381                // Anonymous struct type - contains InstRef array for methods
2382                InstData::AnonStructType {
2383                    methods_start,
2384                    methods_len,
2385                    ..
2386                } => {
2387                    let start = (*methods_start + extra_offset) as usize;
2388                    for i in 0..*methods_len as usize {
2389                        extra[start + i] += inst_offset;
2390                    }
2391                }
2392
2393                // Anonymous enum type - contains InstRef array for methods
2394                InstData::AnonEnumType {
2395                    methods_start,
2396                    methods_len,
2397                    ..
2398                } => {
2399                    let start = (*methods_start + extra_offset) as usize;
2400                    for i in 0..*methods_len as usize {
2401                        extra[start + i] += inst_offset;
2402                    }
2403                }
2404
2405                // Call args - layout: [value, mode] pairs
2406                InstData::Call {
2407                    args_start,
2408                    args_len,
2409                    ..
2410                }
2411                | InstData::MethodCall {
2412                    args_start,
2413                    args_len,
2414                    ..
2415                }
2416                | InstData::AssocFnCall {
2417                    args_start,
2418                    args_len,
2419                    ..
2420                } => {
2421                    let start = (*args_start + extra_offset) as usize;
2422                    for i in 0..*args_len as usize {
2423                        // Each arg is 2 u32s: [value, mode]
2424                        extra[start + i * 2] += inst_offset;
2425                    }
2426                }
2427
2428                // Field inits - layout: [name, value] pairs
2429                InstData::StructInit {
2430                    fields_start,
2431                    fields_len,
2432                    ..
2433                }
2434                | InstData::EnumStructVariant {
2435                    fields_start,
2436                    fields_len,
2437                    ..
2438                } => {
2439                    let start = (*fields_start + extra_offset) as usize;
2440                    for i in 0..*fields_len as usize {
2441                        // Each field is 2 u32s: [name, value]
2442                        extra[start + i * 2 + 1] += inst_offset;
2443                    }
2444                }
2445
2446                // Match arms - variable size patterns with body InstRef at end
2447                InstData::Match {
2448                    arms_start,
2449                    arms_len,
2450                    ..
2451                } => {
2452                    let mut pos = (*arms_start + extra_offset) as usize;
2453                    for _ in 0..*arms_len {
2454                        let kind = extra[pos];
2455                        let pattern_size = match kind {
2456                            k if k == PatternKind::Wildcard as u32 => {
2457                                PATTERN_WILDCARD_SIZE as usize
2458                            }
2459                            k if k == PatternKind::Int as u32 => PATTERN_INT_SIZE as usize,
2460                            k if k == PatternKind::Bool as u32 => PATTERN_BOOL_SIZE as usize,
2461                            k if k == PatternKind::Path as u32 => PATTERN_PATH_SIZE as usize,
2462                            _ => panic!("Unknown pattern kind during merge: {}", kind),
2463                        };
2464                        // The body InstRef is always the last element of each pattern
2465                        extra[pos + pattern_size - 1] += inst_offset;
2466                        pos += pattern_size;
2467                    }
2468                }
2469
2470                // These don't have InstRefs in extra
2471                InstData::IntConst(_)
2472                | InstData::FloatConst(_)
2473                | InstData::BoolConst(_)
2474                | InstData::CharConst(_)
2475                | InstData::StringConst(_)
2476                | InstData::UnitConst
2477                | InstData::Bin { .. }
2478                | InstData::Unary { .. }
2479                | InstData::MakeRef { .. }
2480                | InstData::BareRangeSubscript
2481                | InstData::MakeSlice { .. }
2482                | InstData::Branch { .. }
2483                | InstData::Loop { .. }
2484                | InstData::For { .. }
2485                | InstData::InfiniteLoop { .. }
2486                | InstData::Break
2487                | InstData::Continue
2488                | InstData::Ret(_)
2489                | InstData::VarRef { .. }
2490                | InstData::ParamRef { .. }
2491                | InstData::Alloc { .. }
2492                | InstData::Assign { .. }
2493                | InstData::FnDecl { .. }
2494                | InstData::ConstDecl { .. }
2495                | InstData::FieldGet { .. }
2496                | InstData::FieldSet { .. }
2497                | InstData::EnumDecl { .. }
2498                | InstData::EnumVariant { .. }
2499                | InstData::IndexGet { .. }
2500                | InstData::IndexSet { .. }
2501                | InstData::TypeIntrinsic { .. }
2502                | InstData::TypeInterfaceIntrinsic { .. }
2503                | InstData::InterfaceMethodSig { .. }
2504                | InstData::Comptime { .. }
2505                | InstData::ComptimeUnrollFor { .. }
2506                | InstData::Checked { .. }
2507                | InstData::TypeConst { .. }
2508                | InstData::StructDestructure { .. }
2509                | InstData::AnonFnValue { .. } => {}
2510            }
2511        }
2512    }
2513}
2514
2515/// A single RIR instruction.
2516#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2517pub struct Inst {
2518    pub data: InstData,
2519    pub span: Span,
2520}
2521
2522/// Instruction data - the actual operation.
2523#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2524pub enum InstData {
2525    /// Integer constant
2526    IntConst(u64),
2527
2528    /// Floating-point constant, stored as f64 bits for Eq/Hash/Copy compatibility.
2529    /// Use `f64::from_bits()` to recover the value.
2530    FloatConst(u64),
2531
2532    /// Boolean constant
2533    BoolConst(bool),
2534
2535    /// Char constant — Unicode scalar value (ADR-0071).
2536    CharConst(u32),
2537
2538    /// String constant (interned string content)
2539    StringConst(Spur),
2540
2541    /// Unit constant (for blocks that produce unit type)
2542    UnitConst,
2543
2544    /// Binary operation: `lhs <op> rhs`. Covers arithmetic, comparison,
2545    /// logical, and bitwise ops. The specific op is in `BinOp`. Logical
2546    /// `And`/`Or` are short-circuiting and are lowered to control flow
2547    /// during CFG construction.
2548    Bin {
2549        op: BinOp,
2550        lhs: InstRef,
2551        rhs: InstRef,
2552    },
2553
2554    /// Unary operation: `<op> operand`. Covers `-`, `!`, and `~`.
2555    Unary { op: UnaryOp, operand: InstRef },
2556
2557    /// Reference construction (ADR-0062): `&x` (`is_mut = false`) or
2558    /// `&mut x` (`is_mut = true`). Operand must be an lvalue. Result type
2559    /// is `Ref(T)` or `MutRef(T)` where `T` is the operand's type.
2560    MakeRef { operand: InstRef, is_mut: bool },
2561
2562    /// ADR-0064: a range-shaped subscript that wasn't borrowed by `&` or
2563    /// `&mut`. There is no slice value without a borrow, so this carries
2564    /// no operands; sema reports the error and continues.
2565    BareRangeSubscript,
2566
2567    /// Slice construction by borrow over a range subscript (ADR-0064).
2568    ///
2569    /// Lowered from `&arr[range]` (`is_mut = false`) and
2570    /// `&mut arr[range]` (`is_mut = true`). The `base` must be an lvalue
2571    /// of array type. `lo` and `hi` are the range endpoints; `None` means
2572    /// the implicit default (`0` for `lo`, `arr.len()` for `hi`).
2573    MakeSlice {
2574        base: InstRef,
2575        lo: Option<InstRef>,
2576        hi: Option<InstRef>,
2577        is_mut: bool,
2578    },
2579
2580    // Control flow
2581    /// Branch: if cond then then_block else else_block
2582    Branch {
2583        cond: InstRef,
2584        then_block: InstRef,
2585        else_block: Option<InstRef>,
2586        /// ADR-0079 follow-up: when set, sema evaluates `cond` at
2587        /// comptime and emits *only* the chosen branch — the
2588        /// discarded branch is never analyzed (so it's free to
2589        /// reference shapes that don't apply to the surrounding
2590        /// type, e.g. `@uninit(Self)` inside a struct-only branch
2591        /// when `Self` is enum). Source form: `comptime if cond { … }`.
2592        is_comptime: bool,
2593    },
2594
2595    /// While loop: while cond { body }
2596    Loop { cond: InstRef, body: InstRef },
2597
2598    /// For-in loop: for [mut] binding in iterable { body }
2599    For {
2600        /// The loop variable name
2601        binding: Spur,
2602        /// Whether the loop variable is mutable
2603        is_mut: bool,
2604        /// The iterable expression (array or @range result)
2605        iterable: InstRef,
2606        /// The loop body
2607        body: InstRef,
2608    },
2609
2610    /// Infinite loop: loop { body }
2611    InfiniteLoop { body: InstRef },
2612
2613    /// Match expression: match scrutinee { pattern => expr, ... }
2614    /// Arms are stored in the extra array using add_match_arms/get_match_arms.
2615    Match {
2616        /// The value being matched
2617        scrutinee: InstRef,
2618        /// Index into extra data where arms start
2619        arms_start: u32,
2620        /// Number of match arms
2621        arms_len: u32,
2622    },
2623
2624    /// Break: exits the innermost loop
2625    Break,
2626
2627    /// Continue: jumps to the next iteration of the innermost loop
2628    Continue,
2629
2630    /// Function definition
2631    /// Contains: name symbol, parameters, return type symbol, body instruction ref
2632    /// Directives and params are stored in the extra array.
2633    FnDecl {
2634        /// Index into extra data where directives start
2635        directives_start: u32,
2636        /// Number of directives
2637        directives_len: u32,
2638        /// Whether this function is public (requires --preview modules)
2639        is_pub: bool,
2640        /// Whether this function is marked `unchecked` (can only be called from checked blocks)
2641        is_unchecked: bool,
2642        name: Spur,
2643        /// Index into extra data where params start
2644        params_start: u32,
2645        /// Number of parameters
2646        params_len: u32,
2647        return_type: Spur,
2648        body: InstRef,
2649        /// Whether this function/method takes `self` as a receiver.
2650        /// Only true for methods in impl blocks that have a self parameter.
2651        /// Used by sema to know to add the implicit self parameter.
2652        has_self: bool,
2653        /// Receiver shape for methods (ADR-0060, ADR-0076). Encoded as a
2654        /// byte: 0 = `self`/`self : Self` (by-value), 1 = `self : MutRef(Self)`,
2655        /// 2 = `self : Ref(Self)`. Ignored when `!has_self`.
2656        receiver_mode: u8,
2657    },
2658
2659    /// Constant declaration
2660    /// Contains: name symbol, optional type, initializer expression ref
2661    /// Directives are stored in the extra array.
2662    /// Used for module re-exports: `pub const strings = @import("utils/strings.gruel");`
2663    ConstDecl {
2664        /// Index into extra data where directives start
2665        directives_start: u32,
2666        /// Number of directives
2667        directives_len: u32,
2668        /// Whether this constant is public (requires --preview modules)
2669        is_pub: bool,
2670        /// Constant name
2671        name: Spur,
2672        /// Optional type annotation (interned string, None if inferred)
2673        ty: Option<Spur>,
2674        /// Initializer expression
2675        init: InstRef,
2676    },
2677
2678    /// Function call
2679    /// Args are stored in the extra array using add_call_args/get_call_args.
2680    Call {
2681        /// Function name
2682        name: Spur,
2683        /// Index into extra data where args start
2684        args_start: u32,
2685        /// Number of arguments
2686        args_len: u32,
2687    },
2688
2689    /// Intrinsic call with expression arguments (e.g., @dbg)
2690    /// Args are stored in the extra array using add_inst_refs/get_inst_refs.
2691    Intrinsic {
2692        /// Intrinsic name (without @)
2693        name: Spur,
2694        /// Index into extra data where args start
2695        args_start: u32,
2696        /// Number of arguments
2697        args_len: u32,
2698    },
2699
2700    /// Intrinsic call with a type argument (e.g., @size_of, @align_of)
2701    TypeIntrinsic {
2702        /// Intrinsic name (without @)
2703        name: Spur,
2704        /// Type argument (as an interned string, e.g., "i32", "Point", "[i32; 4]")
2705        type_arg: Spur,
2706    },
2707
2708    /// Intrinsic call with a type argument and an interface argument
2709    /// (e.g., `@implements(T, Drop)`).
2710    ///
2711    /// `type_arg` is the interned name when the source has a bare
2712    /// type name or type expression. `type_inst`, when `Some`, is an
2713    /// arbitrary expression that comptime-evaluates to a `Type`
2714    /// value (e.g. `f.field_type` projecting out of `@type_info`).
2715    /// Sema prefers `type_inst` when set.
2716    TypeInterfaceIntrinsic {
2717        /// Intrinsic name (without @)
2718        name: Spur,
2719        /// Type argument (interned name).
2720        type_arg: Spur,
2721        /// Optional comptime-evaluable type expression. When set,
2722        /// supersedes `type_arg`.
2723        type_inst: Option<InstRef>,
2724        /// Interface argument (interned name).
2725        interface_arg: Spur,
2726    },
2727
2728    /// Reference to a function parameter
2729    ParamRef {
2730        /// Parameter index (0-based)
2731        index: u32,
2732        /// Parameter name (for error messages)
2733        name: Spur,
2734    },
2735
2736    /// Return value from function (None for `return;` in unit-returning functions)
2737    Ret(Option<InstRef>),
2738
2739    /// Block of instructions (for function bodies)
2740    /// The result is the last instruction in the block
2741    Block {
2742        /// Index into extra data where instruction refs start
2743        extra_start: u32,
2744        /// Number of instructions in the block
2745        len: u32,
2746    },
2747
2748    // Variable operations
2749    /// Local variable declaration: allocates storage and initializes
2750    /// If name is None, this is a wildcard pattern that discards the value
2751    /// Directives are stored in the extra array using add_directives/get_directives.
2752    Alloc {
2753        /// Index into extra data where directives start
2754        directives_start: u32,
2755        /// Number of directives
2756        directives_len: u32,
2757        /// Variable name (None for wildcard `_` pattern that discards the value)
2758        name: Option<Spur>,
2759        /// Whether the variable is mutable
2760        is_mut: bool,
2761        /// Optional type annotation
2762        ty: Option<Spur>,
2763        /// Initial value instruction
2764        init: InstRef,
2765    },
2766
2767    /// Struct destructuring: `let TypeName { fields } = expr;`
2768    /// Fields are stored in the extra array as groups of 4 u32s:
2769    /// [field_name, binding_name (0 = shorthand), is_wildcard, is_mut]
2770    StructDestructure {
2771        /// Struct type name
2772        type_name: Spur,
2773        /// Index into extra data where field data starts
2774        fields_start: u32,
2775        /// Number of fields
2776        fields_len: u32,
2777        /// Initializer expression
2778        init: InstRef,
2779    },
2780
2781    /// Variable reference: reads the value of a variable
2782    VarRef {
2783        /// Variable name
2784        name: Spur,
2785    },
2786
2787    /// Assignment: stores a value into a mutable variable
2788    Assign {
2789        /// Variable name
2790        name: Spur,
2791        /// Value to store
2792        value: InstRef,
2793    },
2794
2795    // Struct operations
2796    /// Struct type declaration
2797    /// Directives, fields, and methods are stored in the extra array.
2798    StructDecl {
2799        /// Index into extra data where directives start
2800        directives_start: u32,
2801        /// Number of directives
2802        directives_len: u32,
2803        /// Whether this struct is public (requires --preview modules)
2804        is_pub: bool,
2805        /// Declared ownership posture (ADR-0080). `Affine` when neither
2806        /// `@mark(copy)` nor `@mark(linear)` appears in the directive list.
2807        posture: Posture,
2808        /// Struct name
2809        name: Spur,
2810        /// Index into extra data where fields start
2811        fields_start: u32,
2812        /// Number of fields
2813        fields_len: u32,
2814        /// Index into extra data where method refs start
2815        methods_start: u32,
2816        /// Number of methods
2817        methods_len: u32,
2818    },
2819
2820    /// Struct literal: creates a new struct instance
2821    /// Fields are stored in the extra array using add_field_inits/get_field_inits.
2822    StructInit {
2823        /// Optional module reference (for qualified struct literals like `module.Point { ... }`)
2824        /// If Some, the struct is looked up in the module's exports.
2825        module: Option<InstRef>,
2826        /// Struct type name
2827        type_name: Spur,
2828        /// Index into extra data where fields start
2829        fields_start: u32,
2830        /// Number of fields
2831        fields_len: u32,
2832    },
2833
2834    /// Field access: reads a field from a struct
2835    FieldGet {
2836        /// Base struct value
2837        base: InstRef,
2838        /// Field name
2839        field: Spur,
2840    },
2841
2842    /// Field assignment: writes a value to a struct field
2843    FieldSet {
2844        /// Base struct value
2845        base: InstRef,
2846        /// Field name
2847        field: Spur,
2848        /// Value to store
2849        value: InstRef,
2850    },
2851
2852    // Enum operations
2853    /// Enum type declaration
2854    /// Variants are stored in the extra array using add_enum_variant_decls/get_enum_variant_decls.
2855    /// Each variant encodes: [name_spur, field_count, field_type_0, ..., field_type_n].
2856    EnumDecl {
2857        /// Whether this enum is public (requires --preview modules)
2858        is_pub: bool,
2859        /// Declared ownership posture (ADR-0080). `Affine` when neither
2860        /// `@mark(copy)` nor `@mark(linear)` appears in the directive list.
2861        posture: Posture,
2862        /// Enum name
2863        name: Spur,
2864        /// Index into extra data where variants start
2865        variants_start: u32,
2866        /// Number of variants
2867        variants_len: u32,
2868        /// Index into extra data where method refs start
2869        methods_start: u32,
2870        /// Number of methods
2871        methods_len: u32,
2872        /// Index into extra data where directives start (ADR-0079).
2873        directives_start: u32,
2874        /// Number of directives.
2875        directives_len: u32,
2876    },
2877
2878    /// Enum variant: creates a value of an enum type
2879    EnumVariant {
2880        /// Optional module reference (for qualified paths like `module.Color::Red`)
2881        /// If Some, the enum is looked up in the module's exports.
2882        module: Option<InstRef>,
2883        /// Enum type name
2884        type_name: Spur,
2885        /// Variant name
2886        variant: Spur,
2887    },
2888
2889    /// Enum struct variant construction: `Enum::Variant { field: value, ... }`
2890    /// Fields are stored in the extra array using add_field_inits/get_field_inits.
2891    EnumStructVariant {
2892        /// Optional module reference (for qualified paths)
2893        module: Option<InstRef>,
2894        /// Enum type name
2895        type_name: Spur,
2896        /// Variant name
2897        variant: Spur,
2898        /// Start of field initializers in extra array
2899        fields_start: u32,
2900        /// Number of field initializers
2901        fields_len: u32,
2902    },
2903
2904    // Array operations
2905    /// Array literal: creates a new array from element values
2906    /// Elements are stored in the extra array using add_inst_refs/get_inst_refs.
2907    ArrayInit {
2908        /// Index into extra data where elements start
2909        elems_start: u32,
2910        /// Number of elements
2911        elems_len: u32,
2912    },
2913
2914    /// Array index read: reads an element from an array
2915    IndexGet {
2916        /// Base array value
2917        base: InstRef,
2918        /// Index expression
2919        index: InstRef,
2920    },
2921
2922    /// Array index write: writes a value to an array element
2923    IndexSet {
2924        /// Base array value (must be a VarRef to a mutable variable)
2925        base: InstRef,
2926        /// Index expression
2927        index: InstRef,
2928        /// Value to store
2929        value: InstRef,
2930    },
2931
2932    // Method operations
2933    /// Method call: receiver.method(args)
2934    /// Args are stored in the extra array using add_call_args/get_call_args.
2935    MethodCall {
2936        /// Receiver expression (the struct value)
2937        receiver: InstRef,
2938        /// Method name
2939        method: Spur,
2940        /// Index into extra data where args start
2941        args_start: u32,
2942        /// Number of arguments
2943        args_len: u32,
2944    },
2945
2946    /// Associated function call: Type::function(args)
2947    /// Args are stored in the extra array using add_call_args/get_call_args.
2948    AssocFnCall {
2949        /// Type name (e.g., Point)
2950        type_name: Spur,
2951        /// Function name (e.g., origin)
2952        function: Spur,
2953        /// Index into extra data where args start
2954        args_start: u32,
2955        /// Number of arguments
2956        args_len: u32,
2957    },
2958
2959    /// Interface declaration (ADR-0056): a structurally typed set of method
2960    /// requirements.
2961    ///
2962    /// Method signatures are stored as InstRefs to `InterfaceMethodSig`
2963    /// instructions in the inst-refs extra array.
2964    InterfaceDecl {
2965        /// Whether this interface is public.
2966        is_pub: bool,
2967        /// Interface name.
2968        name: Spur,
2969        /// Start of method-sig inst refs in extra data.
2970        methods_start: u32,
2971        /// Number of method signatures.
2972        methods_len: u32,
2973        /// Start of directives in extra data (ADR-0079).
2974        directives_start: u32,
2975        /// Number of directives.
2976        directives_len: u32,
2977    },
2978
2979    /// A single method signature inside an `InterfaceDecl`.
2980    ///
2981    /// No body. The receiver is always present (interface methods cannot
2982    /// be associated functions in MVP); its surface shape (`self`,
2983    /// `self : MutRef(Self)`, or `self : Ref(Self)`) is encoded in
2984    /// `receiver_mode` using the byte mapping 0/1/2.
2985    InterfaceMethodSig {
2986        /// Method name.
2987        name: Spur,
2988        /// Start of params in extra data (excluding self).
2989        params_start: u32,
2990        /// Number of explicit parameters.
2991        params_len: u32,
2992        /// Return type symbol (`()` if none was written).
2993        return_type: Spur,
2994        /// Receiver mode encoded as `RirParamMode` (ADR-0060).
2995        receiver_mode: u8,
2996        /// ADR-0088: whether this signature was declared `@mark(unchecked)`.
2997        is_unchecked: bool,
2998        /// ADR-0088: start of the signature's directive list in the
2999        /// extra-array, kept so sema can reject non-applicable markers
3000        /// (e.g. `@mark(c)`) at validation time.
3001        directives_start: u32,
3002        /// Number of directives.
3003        directives_len: u32,
3004    },
3005
3006    /// Derive declaration (ADR-0058): `derive Name { fn ... }`.
3007    ///
3008    /// Holds a list of method declarations (each emitted as a `FnDecl`
3009    /// instruction with `has_self` set when the method takes `self`). When a
3010    /// `@derive(Name)` directive on a struct or enum names this derive, the
3011    /// methods are spliced into the host type's method list with `Self`
3012    /// bound to the host.
3013    DeriveDecl {
3014        /// Derive name (e.g., `Drop`).
3015        name: Spur,
3016        /// Start of method-decl inst refs in extra data.
3017        methods_start: u32,
3018        /// Number of methods.
3019        methods_len: u32,
3020    },
3021
3022    /// Comptime block expression: comptime { expr }
3023    /// The inner expression must be evaluable at compile time.
3024    Comptime {
3025        /// The expression to evaluate at compile time
3026        expr: InstRef,
3027    },
3028
3029    /// Comptime unroll for loop: comptime_unroll for binding in iterable { body }
3030    /// The iterable must be evaluable at compile time. The body is duplicated
3031    /// once per iteration with the binding replaced by each element's value.
3032    ComptimeUnrollFor {
3033        /// The loop variable name
3034        binding: Spur,
3035        /// The iterable expression (must be comptime-evaluable, e.g. @type_info fields)
3036        iterable: InstRef,
3037        /// The loop body (will be unrolled at compile time)
3038        body: InstRef,
3039    },
3040
3041    /// Checked block expression: checked { expr }
3042    /// Unchecked operations (raw pointer manipulation, calling unchecked functions)
3043    /// are only allowed inside checked blocks.
3044    Checked {
3045        /// The expression inside the checked block
3046        expr: InstRef,
3047    },
3048
3049    /// Type constant: a type used as a value expression (e.g., `i32` in `identity(i32, 42)`)
3050    /// The type_name is the symbol for the type (e.g., "i32", "bool").
3051    TypeConst {
3052        /// The type name symbol
3053        type_name: Spur,
3054    },
3055
3056    /// Anonymous struct type: a struct type used as a value expression
3057    /// (e.g., `struct { first: T, second: T, fn method(self) -> T { ... } }` in comptime type construction)
3058    /// Fields are stored in the extra array using add_field_decls/get_field_decls.
3059    /// Methods are stored as InstRefs to FnDecl instructions in the extra array.
3060    AnonStructType {
3061        /// Index into extra data where directives start (ADR-0058 anon hosts).
3062        directives_start: u32,
3063        /// Number of directives.
3064        directives_len: u32,
3065        /// Index into extra data where fields start
3066        fields_start: u32,
3067        /// Number of fields
3068        fields_len: u32,
3069        /// Index into extra data where method InstRefs start
3070        methods_start: u32,
3071        /// Number of methods (InstRefs to FnDecl instructions)
3072        methods_len: u32,
3073    },
3074
3075    /// Anonymous enum type: an enum type used as a value expression
3076    /// (e.g., `enum { Some(T), None, fn method(self) -> T { ... } }` in comptime type construction)
3077    /// Variants are stored in the extra array using add_enum_variant_decls/get_enum_variant_decls.
3078    /// Methods are stored as InstRefs to FnDecl instructions in the extra array.
3079    AnonEnumType {
3080        /// Index into extra data where directives start (ADR-0058 anon hosts).
3081        directives_start: u32,
3082        /// Number of directives.
3083        directives_len: u32,
3084        /// Index into extra data where variants start
3085        variants_start: u32,
3086        /// Number of variants
3087        variants_len: u32,
3088        /// Index into extra data where method InstRefs start
3089        methods_start: u32,
3090        /// Number of methods (InstRefs to FnDecl instructions)
3091        methods_len: u32,
3092    },
3093
3094    /// Anonymous interface type (ADR-0057): an interface type used as a
3095    /// value expression inside a comptime function body.
3096    ///
3097    /// The methods are stored as InstRefs to `InterfaceMethodSig` instructions
3098    /// in the inst-refs extra array, mirroring `InterfaceDecl` for named
3099    /// interfaces.
3100    AnonInterfaceType {
3101        /// Index into extra data where method-sig inst refs start.
3102        methods_start: u32,
3103        /// Number of method signatures.
3104        methods_len: u32,
3105    },
3106
3107    /// Tuple literal: `(e0, e1, ..., eN-1)` (ADR-0048).
3108    /// Lowered in sema to a StructInit against a synthesised anon struct
3109    /// with field names "0", "1", ... and field types inferred from the elements.
3110    /// Elements are stored as raw u32 InstRefs in the extra array.
3111    ///
3112    /// Tuple field access `t.N` is *not* a distinct RIR instruction; astgen
3113    /// lowers it to a regular `FieldGet` whose `field` is the stringified index
3114    /// interned as a Spur. Struct field names are identifiers (never start with
3115    /// a digit), so synthetic tuple field names cannot collide with user code.
3116    TupleInit {
3117        /// Index into extra data where element InstRefs start
3118        elems_start: u32,
3119        /// Number of elements
3120        elems_len: u32,
3121    },
3122
3123    /// Anonymous function value (ADR-0055). Lowered in sema to a fresh
3124    /// lambda-tagged anon struct with zero fields and one `__call` method,
3125    /// then instantiated as an empty StructInit. The struct type is unique
3126    /// per-site — sema skips structural dedup for lambda-origin structs so
3127    /// two same-signature `fn(...)` expressions produce distinct types.
3128    AnonFnValue {
3129        /// InstRef to the FnDecl for the synthesized `__call` method.
3130        method: InstRef,
3131    },
3132}
3133
3134impl fmt::Display for InstRef {
3135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3136        write!(f, "%{}", self.0)
3137    }
3138}
3139
3140#[cfg(test)]
3141mod tests {
3142    use super::*;
3143    use crate::print::RirPrinter;
3144    use lasso::ThreadedRodeo;
3145
3146    #[test]
3147    fn test_inst_ref_size() {
3148        assert_eq!(std::mem::size_of::<InstRef>(), 4);
3149    }
3150
3151    #[test]
3152    fn test_add_and_get_inst() {
3153        let mut rir = Rir::new();
3154        let inst = Inst {
3155            data: InstData::IntConst(42),
3156            span: Span::new(0, 2),
3157        };
3158        let inst_ref = rir.add_inst(inst);
3159
3160        let retrieved = rir.get(inst_ref);
3161        assert!(matches!(retrieved.data, InstData::IntConst(42)));
3162    }
3163
3164    #[test]
3165    fn test_rir_is_empty() {
3166        let rir = Rir::new();
3167        assert!(rir.is_empty());
3168        assert_eq!(rir.len(), 0);
3169    }
3170
3171    #[test]
3172    fn test_rir_extra_data() {
3173        let mut rir = Rir::new();
3174        let data = [1, 2, 3, 4, 5];
3175        let start = rir.add_extra(&data);
3176        assert_eq!(start, 0);
3177
3178        let retrieved = rir.get_extra(start, 5);
3179        assert_eq!(retrieved, &data);
3180
3181        // Add more extra data
3182        let data2 = [10, 20];
3183        let start2 = rir.add_extra(&data2);
3184        assert_eq!(start2, 5);
3185    }
3186
3187    #[test]
3188    fn test_rir_iter() {
3189        let mut rir = Rir::new();
3190        rir.add_inst(Inst {
3191            data: InstData::IntConst(1),
3192            span: Span::new(0, 1),
3193        });
3194        rir.add_inst(Inst {
3195            data: InstData::IntConst(2),
3196            span: Span::new(2, 3),
3197        });
3198
3199        let items: Vec<_> = rir.iter().collect();
3200        assert_eq!(items.len(), 2);
3201        assert_eq!(items[0].0.as_u32(), 0);
3202        assert_eq!(items[1].0.as_u32(), 1);
3203    }
3204
3205    #[test]
3206    fn test_inst_ref_display() {
3207        let inst_ref = InstRef::from_raw(42);
3208        assert_eq!(format!("{}", inst_ref), "%42");
3209    }
3210
3211    // RirPattern tests
3212    #[test]
3213    fn test_rir_pattern_wildcard_span() {
3214        let span = Span::new(10, 11);
3215        let pattern = RirPattern::Wildcard(span);
3216        assert_eq!(pattern.span(), span);
3217    }
3218
3219    #[test]
3220    fn test_rir_pattern_int_span() {
3221        let span = Span::new(20, 22);
3222        let pattern = RirPattern::Int(42, span);
3223        assert_eq!(pattern.span(), span);
3224
3225        // Test negative int
3226        let pattern_neg = RirPattern::Int(-100, span);
3227        assert_eq!(pattern_neg.span(), span);
3228    }
3229
3230    #[test]
3231    fn test_rir_pattern_bool_span() {
3232        let span = Span::new(30, 34);
3233        let pattern = RirPattern::Bool(true, span);
3234        assert_eq!(pattern.span(), span);
3235
3236        let pattern_false = RirPattern::Bool(false, span);
3237        assert_eq!(pattern_false.span(), span);
3238    }
3239
3240    #[test]
3241    fn test_rir_pattern_path_span() {
3242        let span = Span::new(40, 50);
3243        let interner = ThreadedRodeo::new();
3244        let type_name = interner.get_or_intern("Color");
3245        let variant = interner.get_or_intern("Red");
3246
3247        let pattern = RirPattern::Path {
3248            module: None,
3249            type_name,
3250            variant,
3251            span,
3252        };
3253        assert_eq!(pattern.span(), span);
3254    }
3255
3256    // RirCallArg tests
3257    #[test]
3258    fn test_rir_call_arg_modes_distinct() {
3259        let arg_normal = RirCallArg {
3260            value: InstRef::from_raw(0),
3261            mode: RirArgMode::Normal,
3262        };
3263        let arg_mut_ref = RirCallArg {
3264            value: InstRef::from_raw(0),
3265            mode: RirArgMode::MutRef,
3266        };
3267        let arg_ref = RirCallArg {
3268            value: InstRef::from_raw(0),
3269            mode: RirArgMode::Ref,
3270        };
3271        assert_eq!(arg_normal.mode, RirArgMode::Normal);
3272        assert_eq!(arg_mut_ref.mode, RirArgMode::MutRef);
3273        assert_eq!(arg_ref.mode, RirArgMode::Ref);
3274        assert_ne!(arg_normal.mode, arg_mut_ref.mode);
3275        assert_ne!(arg_normal.mode, arg_ref.mode);
3276        assert_ne!(arg_mut_ref.mode, arg_ref.mode);
3277    }
3278
3279    // RirPrinter tests
3280    fn create_printer_test_rir() -> (Rir, ThreadedRodeo) {
3281        let rir = Rir::new();
3282        let interner = ThreadedRodeo::new();
3283        (rir, interner)
3284    }
3285
3286    #[test]
3287    fn test_printer_int_const() {
3288        let (mut rir, interner) = create_printer_test_rir();
3289        rir.add_inst(Inst {
3290            data: InstData::IntConst(42),
3291            span: Span::new(0, 2),
3292        });
3293
3294        let printer = RirPrinter::new(&rir, &interner);
3295        let output = printer.to_string();
3296        assert!(output.contains("%0 = const 42"));
3297    }
3298
3299    #[test]
3300    fn test_printer_bool_const() {
3301        let (mut rir, interner) = create_printer_test_rir();
3302        rir.add_inst(Inst {
3303            data: InstData::BoolConst(true),
3304            span: Span::new(0, 4),
3305        });
3306        rir.add_inst(Inst {
3307            data: InstData::BoolConst(false),
3308            span: Span::new(0, 5),
3309        });
3310
3311        let printer = RirPrinter::new(&rir, &interner);
3312        let output = printer.to_string();
3313        assert!(output.contains("%0 = const true"));
3314        assert!(output.contains("%1 = const false"));
3315    }
3316
3317    #[test]
3318    fn test_printer_string_const() {
3319        let (mut rir, interner) = create_printer_test_rir();
3320        let hello = interner.get_or_intern("hello world");
3321        rir.add_inst(Inst {
3322            data: InstData::StringConst(hello),
3323            span: Span::new(0, 13),
3324        });
3325
3326        let printer = RirPrinter::new(&rir, &interner);
3327        let output = printer.to_string();
3328        assert!(output.contains("%0 = const \"hello world\""));
3329    }
3330
3331    #[test]
3332    fn test_printer_unit_const() {
3333        let (mut rir, interner) = create_printer_test_rir();
3334        rir.add_inst(Inst {
3335            data: InstData::UnitConst,
3336            span: Span::new(0, 2),
3337        });
3338
3339        let printer = RirPrinter::new(&rir, &interner);
3340        let output = printer.to_string();
3341        assert!(output.contains("%0 = const ()"));
3342    }
3343
3344    #[test]
3345    fn test_printer_binary_ops() {
3346        let (mut rir, interner) = create_printer_test_rir();
3347        let lhs = rir.add_inst(Inst {
3348            data: InstData::IntConst(1),
3349            span: Span::new(0, 1),
3350        });
3351        let rhs = rir.add_inst(Inst {
3352            data: InstData::IntConst(2),
3353            span: Span::new(2, 3),
3354        });
3355
3356        // Test all binary operations
3357        let ops = [
3358            BinOp::Add,
3359            BinOp::Sub,
3360            BinOp::Mul,
3361            BinOp::Div,
3362            BinOp::Mod,
3363            BinOp::Eq,
3364            BinOp::Ne,
3365            BinOp::Lt,
3366            BinOp::Gt,
3367            BinOp::Le,
3368            BinOp::Ge,
3369            BinOp::And,
3370            BinOp::Or,
3371            BinOp::BitAnd,
3372            BinOp::BitOr,
3373            BinOp::BitXor,
3374            BinOp::Shl,
3375            BinOp::Shr,
3376        ];
3377        let _ = (lhs, rhs);
3378
3379        for op in ops {
3380            let mut test_rir = Rir::new();
3381            let lhs = test_rir.add_inst(Inst {
3382                data: InstData::IntConst(1),
3383                span: Span::new(0, 1),
3384            });
3385            let rhs = test_rir.add_inst(Inst {
3386                data: InstData::IntConst(2),
3387                span: Span::new(2, 3),
3388            });
3389            let data = InstData::Bin { op, lhs, rhs };
3390            let op_name = op.as_str();
3391            test_rir.add_inst(Inst {
3392                data,
3393                span: Span::new(0, 5),
3394            });
3395
3396            let printer = RirPrinter::new(&test_rir, &interner);
3397            let output = printer.to_string();
3398            let expected = format!("%2 = {} %0, %1", op_name);
3399            assert!(
3400                output.contains(&expected),
3401                "Expected '{}' in output:\n{}",
3402                expected,
3403                output
3404            );
3405        }
3406    }
3407
3408    #[test]
3409    fn test_printer_unary_ops() {
3410        let (mut rir, interner) = create_printer_test_rir();
3411        let operand = rir.add_inst(Inst {
3412            data: InstData::IntConst(42),
3413            span: Span::new(0, 2),
3414        });
3415
3416        rir.add_inst(Inst {
3417            data: InstData::Unary {
3418                op: UnaryOp::Neg,
3419                operand,
3420            },
3421            span: Span::new(0, 3),
3422        });
3423        rir.add_inst(Inst {
3424            data: InstData::Unary {
3425                op: UnaryOp::Not,
3426                operand,
3427            },
3428            span: Span::new(0, 3),
3429        });
3430        rir.add_inst(Inst {
3431            data: InstData::Unary {
3432                op: UnaryOp::BitNot,
3433                operand,
3434            },
3435            span: Span::new(0, 3),
3436        });
3437
3438        let printer = RirPrinter::new(&rir, &interner);
3439        let output = printer.to_string();
3440        assert!(output.contains("neg %0"));
3441        assert!(output.contains("not %0"));
3442        assert!(output.contains("bit_not %0"));
3443    }
3444
3445    #[test]
3446    fn test_printer_branch() {
3447        let (mut rir, interner) = create_printer_test_rir();
3448        let cond = rir.add_inst(Inst {
3449            data: InstData::BoolConst(true),
3450            span: Span::new(0, 4),
3451        });
3452        let then_block = rir.add_inst(Inst {
3453            data: InstData::IntConst(1),
3454            span: Span::new(0, 1),
3455        });
3456        let else_block = rir.add_inst(Inst {
3457            data: InstData::IntConst(0),
3458            span: Span::new(0, 1),
3459        });
3460
3461        // With else block
3462        rir.add_inst(Inst {
3463            data: InstData::Branch {
3464                cond,
3465                then_block,
3466                else_block: Some(else_block),
3467                is_comptime: false,
3468            },
3469            span: Span::new(0, 20),
3470        });
3471
3472        let printer = RirPrinter::new(&rir, &interner);
3473        let output = printer.to_string();
3474        assert!(output.contains("branch %0, %1, %2"));
3475    }
3476
3477    #[test]
3478    fn test_printer_branch_no_else() {
3479        let (mut rir, interner) = create_printer_test_rir();
3480        let cond = rir.add_inst(Inst {
3481            data: InstData::BoolConst(true),
3482            span: Span::new(0, 4),
3483        });
3484        let then_block = rir.add_inst(Inst {
3485            data: InstData::IntConst(1),
3486            span: Span::new(0, 1),
3487        });
3488
3489        rir.add_inst(Inst {
3490            data: InstData::Branch {
3491                cond,
3492                then_block,
3493                else_block: None,
3494                is_comptime: false,
3495            },
3496            span: Span::new(0, 15),
3497        });
3498
3499        let printer = RirPrinter::new(&rir, &interner);
3500        let output = printer.to_string();
3501        // Should not have the third argument
3502        assert!(output.contains("branch %0, %1\n"));
3503    }
3504
3505    #[test]
3506    fn test_printer_loop() {
3507        let (mut rir, interner) = create_printer_test_rir();
3508        let cond = rir.add_inst(Inst {
3509            data: InstData::BoolConst(true),
3510            span: Span::new(0, 4),
3511        });
3512        let body = rir.add_inst(Inst {
3513            data: InstData::IntConst(0),
3514            span: Span::new(0, 1),
3515        });
3516
3517        rir.add_inst(Inst {
3518            data: InstData::Loop { cond, body },
3519            span: Span::new(0, 20),
3520        });
3521
3522        let printer = RirPrinter::new(&rir, &interner);
3523        let output = printer.to_string();
3524        assert!(output.contains("loop %0, %1"));
3525    }
3526
3527    #[test]
3528    fn test_printer_infinite_loop() {
3529        let (mut rir, interner) = create_printer_test_rir();
3530        let body = rir.add_inst(Inst {
3531            data: InstData::IntConst(0),
3532            span: Span::new(0, 1),
3533        });
3534
3535        rir.add_inst(Inst {
3536            data: InstData::InfiniteLoop { body },
3537            span: Span::new(0, 15),
3538        });
3539
3540        let printer = RirPrinter::new(&rir, &interner);
3541        let output = printer.to_string();
3542        assert!(output.contains("infinite_loop %0"));
3543    }
3544
3545    #[test]
3546    fn test_printer_break_continue() {
3547        let (mut rir, interner) = create_printer_test_rir();
3548        rir.add_inst(Inst {
3549            data: InstData::Break,
3550            span: Span::new(0, 5),
3551        });
3552        rir.add_inst(Inst {
3553            data: InstData::Continue,
3554            span: Span::new(0, 8),
3555        });
3556
3557        let printer = RirPrinter::new(&rir, &interner);
3558        let output = printer.to_string();
3559        assert!(output.contains("break\n"));
3560        assert!(output.contains("continue\n"));
3561    }
3562
3563    #[test]
3564    fn test_printer_ret() {
3565        let (mut rir, interner) = create_printer_test_rir();
3566        let value = rir.add_inst(Inst {
3567            data: InstData::IntConst(42),
3568            span: Span::new(0, 2),
3569        });
3570
3571        // Return with value
3572        rir.add_inst(Inst {
3573            data: InstData::Ret(Some(value)),
3574            span: Span::new(0, 10),
3575        });
3576        // Return without value
3577        rir.add_inst(Inst {
3578            data: InstData::Ret(None),
3579            span: Span::new(0, 6),
3580        });
3581
3582        let printer = RirPrinter::new(&rir, &interner);
3583        let output = printer.to_string();
3584        assert!(output.contains("ret %0"));
3585        assert!(output.contains("%2 = ret\n"));
3586    }
3587
3588    #[test]
3589    fn test_printer_fn_decl() {
3590        let (mut rir, interner) = create_printer_test_rir();
3591        let body = rir.add_inst(Inst {
3592            data: InstData::IntConst(42),
3593            span: Span::new(0, 2),
3594        });
3595
3596        let name = interner.get_or_intern("main");
3597        let return_type = interner.get_or_intern("i32");
3598        let param_name = interner.get_or_intern("x");
3599        let param_type = interner.get_or_intern("i32");
3600
3601        let (directives_start, directives_len) = rir.add_directives(&[]);
3602        let (params_start, params_len) = rir.add_params(&[RirParam {
3603            name: param_name,
3604            ty: param_type,
3605            mode: RirParamMode::Normal,
3606            is_comptime: false,
3607        }]);
3608
3609        rir.add_inst(Inst {
3610            data: InstData::FnDecl {
3611                directives_start,
3612                directives_len,
3613                is_pub: false,
3614                is_unchecked: false,
3615                name,
3616                params_start,
3617                params_len,
3618                return_type,
3619                body,
3620                has_self: false,
3621                receiver_mode: 0,
3622            },
3623            span: Span::new(0, 30),
3624        });
3625
3626        let printer = RirPrinter::new(&rir, &interner);
3627        let output = printer.to_string();
3628        assert!(output.contains("fn main(x: i32) -> i32"));
3629    }
3630
3631    #[test]
3632    fn test_printer_fn_decl_with_self() {
3633        let (mut rir, interner) = create_printer_test_rir();
3634        let body = rir.add_inst(Inst {
3635            data: InstData::IntConst(0),
3636            span: Span::new(0, 1),
3637        });
3638
3639        let name = interner.get_or_intern("get_x");
3640        let return_type = interner.get_or_intern("i32");
3641
3642        let (directives_start, directives_len) = rir.add_directives(&[]);
3643        let (params_start, params_len) = rir.add_params(&[]);
3644
3645        rir.add_inst(Inst {
3646            data: InstData::FnDecl {
3647                directives_start,
3648                directives_len,
3649                is_pub: false,
3650                is_unchecked: false,
3651                name,
3652                params_start,
3653                params_len,
3654                return_type,
3655                body,
3656                has_self: true,
3657                receiver_mode: 0,
3658            },
3659            span: Span::new(0, 30),
3660        });
3661
3662        let printer = RirPrinter::new(&rir, &interner);
3663        let output = printer.to_string();
3664        assert!(output.contains("fn get_x(self, ) -> i32"));
3665    }
3666
3667    #[test]
3668    fn test_printer_fn_decl_param_modes() {
3669        let (mut rir, interner) = create_printer_test_rir();
3670        let body = rir.add_inst(Inst {
3671            data: InstData::UnitConst,
3672            span: Span::new(0, 2),
3673        });
3674
3675        let name = interner.get_or_intern("modify");
3676        let return_type = interner.get_or_intern("()");
3677        let param1_name = interner.get_or_intern("a");
3678        let param1_type = interner.get_or_intern("i32");
3679        let param2_name = interner.get_or_intern("b");
3680        let param2_type = interner.get_or_intern("i32");
3681        let param3_name = interner.get_or_intern("c");
3682        let param3_type = interner.get_or_intern("i32");
3683
3684        let (directives_start, directives_len) = rir.add_directives(&[]);
3685        let (params_start, params_len) = rir.add_params(&[
3686            RirParam {
3687                name: param1_name,
3688                ty: param1_type,
3689                mode: RirParamMode::Normal,
3690                is_comptime: false,
3691            },
3692            RirParam {
3693                name: param2_name,
3694                ty: param2_type,
3695                mode: RirParamMode::MutRef,
3696                is_comptime: false,
3697            },
3698            RirParam {
3699                name: param3_name,
3700                ty: param3_type,
3701                mode: RirParamMode::Ref,
3702                is_comptime: false,
3703            },
3704        ]);
3705
3706        rir.add_inst(Inst {
3707            data: InstData::FnDecl {
3708                directives_start,
3709                directives_len,
3710                is_pub: false,
3711                is_unchecked: false,
3712                name,
3713                params_start,
3714                params_len,
3715                return_type,
3716                body,
3717                has_self: false,
3718                receiver_mode: 0,
3719            },
3720            span: Span::new(0, 50),
3721        });
3722
3723        let printer = RirPrinter::new(&rir, &interner);
3724        let output = printer.to_string();
3725        assert!(output.contains("a: i32"));
3726        assert!(output.contains("mut_ref b: i32"));
3727        assert!(output.contains("ref c: i32"));
3728    }
3729
3730    #[test]
3731    fn test_printer_call() {
3732        let (mut rir, interner) = create_printer_test_rir();
3733        let arg = rir.add_inst(Inst {
3734            data: InstData::IntConst(10),
3735            span: Span::new(0, 2),
3736        });
3737
3738        let name = interner.get_or_intern("foo");
3739
3740        let (args_start, args_len) = rir.add_call_args(&[RirCallArg {
3741            value: arg,
3742            mode: RirArgMode::Normal,
3743        }]);
3744
3745        rir.add_inst(Inst {
3746            data: InstData::Call {
3747                name,
3748                args_start,
3749                args_len,
3750            },
3751            span: Span::new(0, 8),
3752        });
3753
3754        let printer = RirPrinter::new(&rir, &interner);
3755        let output = printer.to_string();
3756        assert!(output.contains("call foo(%0)"));
3757    }
3758
3759    #[test]
3760    fn test_printer_call_with_arg_modes() {
3761        let (mut rir, interner) = create_printer_test_rir();
3762        let arg1 = rir.add_inst(Inst {
3763            data: InstData::IntConst(1),
3764            span: Span::new(0, 1),
3765        });
3766        let arg2 = rir.add_inst(Inst {
3767            data: InstData::IntConst(2),
3768            span: Span::new(0, 1),
3769        });
3770        let arg3 = rir.add_inst(Inst {
3771            data: InstData::IntConst(3),
3772            span: Span::new(0, 1),
3773        });
3774
3775        let name = interner.get_or_intern("modify");
3776
3777        let (args_start, args_len) = rir.add_call_args(&[
3778            RirCallArg {
3779                value: arg1,
3780                mode: RirArgMode::Normal,
3781            },
3782            RirCallArg {
3783                value: arg2,
3784                mode: RirArgMode::MutRef,
3785            },
3786            RirCallArg {
3787                value: arg3,
3788                mode: RirArgMode::Ref,
3789            },
3790        ]);
3791
3792        rir.add_inst(Inst {
3793            data: InstData::Call {
3794                name,
3795                args_start,
3796                args_len,
3797            },
3798            span: Span::new(0, 20),
3799        });
3800
3801        let printer = RirPrinter::new(&rir, &interner);
3802        let output = printer.to_string();
3803        assert!(output.contains("call modify(%0, mut_ref %1, ref %2)"));
3804    }
3805
3806    #[test]
3807    fn test_printer_intrinsic() {
3808        let (mut rir, interner) = create_printer_test_rir();
3809        let arg = rir.add_inst(Inst {
3810            data: InstData::IntConst(42),
3811            span: Span::new(0, 2),
3812        });
3813
3814        let name = interner.get_or_intern("dbg");
3815
3816        let (args_start, args_len) = rir.add_call_args(&[RirCallArg {
3817            value: arg,
3818            mode: RirArgMode::Normal,
3819        }]);
3820
3821        rir.add_inst(Inst {
3822            data: InstData::Intrinsic {
3823                name,
3824                args_start,
3825                args_len,
3826            },
3827            span: Span::new(0, 10),
3828        });
3829
3830        let printer = RirPrinter::new(&rir, &interner);
3831        let output = printer.to_string();
3832        assert!(output.contains("intrinsic @dbg(%0)"));
3833    }
3834
3835    #[test]
3836    fn test_printer_type_intrinsic() {
3837        let (mut rir, interner) = create_printer_test_rir();
3838        let name = interner.get_or_intern("size_of");
3839        let type_arg = interner.get_or_intern("i32");
3840
3841        rir.add_inst(Inst {
3842            data: InstData::TypeIntrinsic { name, type_arg },
3843            span: Span::new(0, 15),
3844        });
3845
3846        let printer = RirPrinter::new(&rir, &interner);
3847        let output = printer.to_string();
3848        assert!(output.contains("type_intrinsic @size_of(i32)"));
3849    }
3850
3851    #[test]
3852    fn test_printer_param_ref() {
3853        let (mut rir, interner) = create_printer_test_rir();
3854        let name = interner.get_or_intern("x");
3855
3856        rir.add_inst(Inst {
3857            data: InstData::ParamRef { index: 0, name },
3858            span: Span::new(0, 1),
3859        });
3860
3861        let printer = RirPrinter::new(&rir, &interner);
3862        let output = printer.to_string();
3863        assert!(output.contains("param 0 (x)"));
3864    }
3865
3866    #[test]
3867    fn test_printer_block() {
3868        let (mut rir, interner) = create_printer_test_rir();
3869        rir.add_inst(Inst {
3870            data: InstData::Block {
3871                extra_start: 0,
3872                len: 3,
3873            },
3874            span: Span::new(0, 20),
3875        });
3876
3877        let printer = RirPrinter::new(&rir, &interner);
3878        let output = printer.to_string();
3879        assert!(output.contains("block(0, 3)"));
3880    }
3881
3882    #[test]
3883    fn test_printer_alloc() {
3884        let (mut rir, interner) = create_printer_test_rir();
3885        let init = rir.add_inst(Inst {
3886            data: InstData::IntConst(42),
3887            span: Span::new(0, 2),
3888        });
3889
3890        let name = interner.get_or_intern("x");
3891        let ty = interner.get_or_intern("i32");
3892
3893        let (directives_start, directives_len) = rir.add_directives(&[]);
3894
3895        // Normal alloc with type
3896        rir.add_inst(Inst {
3897            data: InstData::Alloc {
3898                directives_start,
3899                directives_len,
3900                name: Some(name),
3901                is_mut: false,
3902                ty: Some(ty),
3903                init,
3904            },
3905            span: Span::new(0, 15),
3906        });
3907
3908        let printer = RirPrinter::new(&rir, &interner);
3909        let output = printer.to_string();
3910        assert!(output.contains("alloc x: i32= %0"));
3911    }
3912
3913    #[test]
3914    fn test_printer_alloc_mut() {
3915        let (mut rir, interner) = create_printer_test_rir();
3916        let init = rir.add_inst(Inst {
3917            data: InstData::IntConst(42),
3918            span: Span::new(0, 2),
3919        });
3920
3921        let name = interner.get_or_intern("x");
3922
3923        let (directives_start, directives_len) = rir.add_directives(&[]);
3924
3925        rir.add_inst(Inst {
3926            data: InstData::Alloc {
3927                directives_start,
3928                directives_len,
3929                name: Some(name),
3930                is_mut: true,
3931                ty: None,
3932                init,
3933            },
3934            span: Span::new(0, 15),
3935        });
3936
3937        let printer = RirPrinter::new(&rir, &interner);
3938        let output = printer.to_string();
3939        assert!(output.contains("alloc mut x= %0"));
3940    }
3941
3942    #[test]
3943    fn test_printer_alloc_wildcard() {
3944        let (mut rir, interner) = create_printer_test_rir();
3945        let init = rir.add_inst(Inst {
3946            data: InstData::IntConst(42),
3947            span: Span::new(0, 2),
3948        });
3949
3950        let (directives_start, directives_len) = rir.add_directives(&[]);
3951
3952        rir.add_inst(Inst {
3953            data: InstData::Alloc {
3954                directives_start,
3955                directives_len,
3956                name: None,
3957                is_mut: false,
3958                ty: None,
3959                init,
3960            },
3961            span: Span::new(0, 10),
3962        });
3963
3964        let printer = RirPrinter::new(&rir, &interner);
3965        let output = printer.to_string();
3966        assert!(output.contains("alloc _= %0"));
3967    }
3968
3969    #[test]
3970    fn test_printer_var_ref() {
3971        let (mut rir, interner) = create_printer_test_rir();
3972        let name = interner.get_or_intern("x");
3973
3974        rir.add_inst(Inst {
3975            data: InstData::VarRef { name },
3976            span: Span::new(0, 1),
3977        });
3978
3979        let printer = RirPrinter::new(&rir, &interner);
3980        let output = printer.to_string();
3981        assert!(output.contains("var_ref x"));
3982    }
3983
3984    #[test]
3985    fn test_printer_assign() {
3986        let (mut rir, interner) = create_printer_test_rir();
3987        let value = rir.add_inst(Inst {
3988            data: InstData::IntConst(10),
3989            span: Span::new(0, 2),
3990        });
3991
3992        let name = interner.get_or_intern("x");
3993
3994        rir.add_inst(Inst {
3995            data: InstData::Assign { name, value },
3996            span: Span::new(0, 6),
3997        });
3998
3999        let printer = RirPrinter::new(&rir, &interner);
4000        let output = printer.to_string();
4001        assert!(output.contains("assign x = %0"));
4002    }
4003
4004    #[test]
4005    fn test_printer_struct_decl() {
4006        let (mut rir, interner) = create_printer_test_rir();
4007        let name = interner.get_or_intern("Point");
4008        let x_name = interner.get_or_intern("x");
4009        let y_name = interner.get_or_intern("y");
4010        let i32_type = interner.get_or_intern("i32");
4011
4012        let (directives_start, directives_len) = rir.add_directives(&[]);
4013        let (fields_start, fields_len) =
4014            rir.add_field_decls(&[(x_name, i32_type), (y_name, i32_type)]);
4015        let (methods_start, methods_len) = rir.add_inst_refs(&[]);
4016
4017        rir.add_inst(Inst {
4018            data: InstData::StructDecl {
4019                directives_start,
4020                directives_len,
4021                is_pub: false,
4022                posture: Posture::Affine,
4023                name,
4024                fields_start,
4025                fields_len,
4026                methods_start,
4027                methods_len,
4028            },
4029            span: Span::new(0, 30),
4030        });
4031
4032        let printer = RirPrinter::new(&rir, &interner);
4033        let output = printer.to_string();
4034        assert!(output.contains("struct Point { x: i32, y: i32 }"));
4035    }
4036
4037    #[test]
4038    fn test_printer_struct_decl_with_directive() {
4039        let (mut rir, interner) = create_printer_test_rir();
4040        let name = interner.get_or_intern("Point");
4041        let x_name = interner.get_or_intern("x");
4042        let i32_type = interner.get_or_intern("i32");
4043        let directive_name = interner.get_or_intern("handle");
4044
4045        let (directives_start, directives_len) = rir.add_directives(&[RirDirective {
4046            name: directive_name,
4047            args: vec![],
4048            span: Span::new(0, 7),
4049        }]);
4050        let (fields_start, fields_len) = rir.add_field_decls(&[(x_name, i32_type)]);
4051        let (methods_start, methods_len) = rir.add_inst_refs(&[]);
4052
4053        rir.add_inst(Inst {
4054            data: InstData::StructDecl {
4055                directives_start,
4056                directives_len,
4057                is_pub: false,
4058                posture: Posture::Affine,
4059                name,
4060                fields_start,
4061                fields_len,
4062                methods_start,
4063                methods_len,
4064            },
4065            span: Span::new(0, 30),
4066        });
4067
4068        let printer = RirPrinter::new(&rir, &interner);
4069        let output = printer.to_string();
4070        assert!(output.contains("@handle struct Point { x: i32 }"));
4071    }
4072
4073    #[test]
4074    fn test_printer_struct_init() {
4075        let (mut rir, interner) = create_printer_test_rir();
4076        let x_val = rir.add_inst(Inst {
4077            data: InstData::IntConst(10),
4078            span: Span::new(0, 2),
4079        });
4080        let y_val = rir.add_inst(Inst {
4081            data: InstData::IntConst(20),
4082            span: Span::new(0, 2),
4083        });
4084
4085        let type_name = interner.get_or_intern("Point");
4086        let x_name = interner.get_or_intern("x");
4087        let y_name = interner.get_or_intern("y");
4088
4089        let (fields_start, fields_len) = rir.add_field_inits(&[(x_name, x_val), (y_name, y_val)]);
4090
4091        rir.add_inst(Inst {
4092            data: InstData::StructInit {
4093                module: None,
4094                type_name,
4095                fields_start,
4096                fields_len,
4097            },
4098            span: Span::new(0, 25),
4099        });
4100
4101        let printer = RirPrinter::new(&rir, &interner);
4102        let output = printer.to_string();
4103        assert!(output.contains("struct_init Point { x: %0, y: %1 }"));
4104    }
4105
4106    #[test]
4107    fn test_printer_field_get() {
4108        let (mut rir, interner) = create_printer_test_rir();
4109        let base = rir.add_inst(Inst {
4110            data: InstData::IntConst(0), // placeholder for a struct value
4111            span: Span::new(0, 1),
4112        });
4113
4114        let field = interner.get_or_intern("x");
4115
4116        rir.add_inst(Inst {
4117            data: InstData::FieldGet { base, field },
4118            span: Span::new(0, 5),
4119        });
4120
4121        let printer = RirPrinter::new(&rir, &interner);
4122        let output = printer.to_string();
4123        assert!(output.contains("field_get %0.x"));
4124    }
4125
4126    #[test]
4127    fn test_printer_field_set() {
4128        let (mut rir, interner) = create_printer_test_rir();
4129        let base = rir.add_inst(Inst {
4130            data: InstData::IntConst(0), // placeholder
4131            span: Span::new(0, 1),
4132        });
4133        let value = rir.add_inst(Inst {
4134            data: InstData::IntConst(42),
4135            span: Span::new(0, 2),
4136        });
4137
4138        let field = interner.get_or_intern("x");
4139
4140        rir.add_inst(Inst {
4141            data: InstData::FieldSet { base, field, value },
4142            span: Span::new(0, 10),
4143        });
4144
4145        let printer = RirPrinter::new(&rir, &interner);
4146        let output = printer.to_string();
4147        assert!(output.contains("field_set %0.x = %1"));
4148    }
4149
4150    #[test]
4151    fn test_printer_enum_decl() {
4152        let (mut rir, interner) = create_printer_test_rir();
4153        let name = interner.get_or_intern("Color");
4154        let red = interner.get_or_intern("Red");
4155        let green = interner.get_or_intern("Green");
4156        let blue = interner.get_or_intern("Blue");
4157
4158        // Unit variants: no fields
4159        let (variants_start, variants_len) = rir.add_enum_variant_decls(&[
4160            (red, vec![], vec![]),
4161            (green, vec![], vec![]),
4162            (blue, vec![], vec![]),
4163        ]);
4164
4165        rir.add_inst(Inst {
4166            data: InstData::EnumDecl {
4167                is_pub: false,
4168                posture: Posture::Affine,
4169                name,
4170                variants_start,
4171                variants_len,
4172                methods_start: 0,
4173                methods_len: 0,
4174                directives_start: 0,
4175                directives_len: 0,
4176            },
4177            span: Span::new(0, 35),
4178        });
4179
4180        let printer = RirPrinter::new(&rir, &interner);
4181        let output = printer.to_string();
4182        assert!(output.contains("enum Color { Red, Green, Blue }"));
4183    }
4184
4185    #[test]
4186    fn test_printer_enum_decl_with_data() {
4187        let (mut rir, interner) = create_printer_test_rir();
4188        let name = interner.get_or_intern("IntOption");
4189        let none = interner.get_or_intern("None");
4190        let some = interner.get_or_intern("Some");
4191        let i32_ty = interner.get_or_intern("i32");
4192
4193        let (variants_start, variants_len) =
4194            rir.add_enum_variant_decls(&[(none, vec![], vec![]), (some, vec![i32_ty], vec![])]);
4195
4196        rir.add_inst(Inst {
4197            data: InstData::EnumDecl {
4198                is_pub: false,
4199                posture: Posture::Affine,
4200                name,
4201                variants_start,
4202                variants_len,
4203                methods_start: 0,
4204                methods_len: 0,
4205                directives_start: 0,
4206                directives_len: 0,
4207            },
4208            span: Span::new(0, 35),
4209        });
4210
4211        let printer = RirPrinter::new(&rir, &interner);
4212        let output = printer.to_string();
4213        assert!(output.contains("enum IntOption { None, Some(i32) }"));
4214    }
4215
4216    #[test]
4217    fn test_printer_enum_variant() {
4218        let (mut rir, interner) = create_printer_test_rir();
4219        let type_name = interner.get_or_intern("Color");
4220        let variant = interner.get_or_intern("Red");
4221
4222        rir.add_inst(Inst {
4223            data: InstData::EnumVariant {
4224                module: None,
4225                type_name,
4226                variant,
4227            },
4228            span: Span::new(0, 10),
4229        });
4230
4231        let printer = RirPrinter::new(&rir, &interner);
4232        let output = printer.to_string();
4233        assert!(output.contains("enum_variant Color::Red"));
4234    }
4235
4236    #[test]
4237    fn test_printer_array_init() {
4238        let (mut rir, interner) = create_printer_test_rir();
4239        let elem1 = rir.add_inst(Inst {
4240            data: InstData::IntConst(1),
4241            span: Span::new(0, 1),
4242        });
4243        let elem2 = rir.add_inst(Inst {
4244            data: InstData::IntConst(2),
4245            span: Span::new(0, 1),
4246        });
4247        let elem3 = rir.add_inst(Inst {
4248            data: InstData::IntConst(3),
4249            span: Span::new(0, 1),
4250        });
4251
4252        let (elems_start, elems_len) = rir.add_inst_refs(&[elem1, elem2, elem3]);
4253
4254        rir.add_inst(Inst {
4255            data: InstData::ArrayInit {
4256                elems_start,
4257                elems_len,
4258            },
4259            span: Span::new(0, 10),
4260        });
4261
4262        let printer = RirPrinter::new(&rir, &interner);
4263        let output = printer.to_string();
4264        assert!(output.contains("array_init [%0, %1, %2]"));
4265    }
4266
4267    #[test]
4268    fn test_printer_index_get() {
4269        let (mut rir, interner) = create_printer_test_rir();
4270        let base = rir.add_inst(Inst {
4271            data: InstData::IntConst(0), // placeholder for array
4272            span: Span::new(0, 1),
4273        });
4274        let index = rir.add_inst(Inst {
4275            data: InstData::IntConst(1),
4276            span: Span::new(0, 1),
4277        });
4278
4279        rir.add_inst(Inst {
4280            data: InstData::IndexGet { base, index },
4281            span: Span::new(0, 5),
4282        });
4283
4284        let printer = RirPrinter::new(&rir, &interner);
4285        let output = printer.to_string();
4286        assert!(output.contains("index_get %0[%1]"));
4287    }
4288
4289    #[test]
4290    fn test_printer_index_set() {
4291        let (mut rir, interner) = create_printer_test_rir();
4292        let base = rir.add_inst(Inst {
4293            data: InstData::IntConst(0), // placeholder for array
4294            span: Span::new(0, 1),
4295        });
4296        let index = rir.add_inst(Inst {
4297            data: InstData::IntConst(1),
4298            span: Span::new(0, 1),
4299        });
4300        let value = rir.add_inst(Inst {
4301            data: InstData::IntConst(42),
4302            span: Span::new(0, 2),
4303        });
4304
4305        rir.add_inst(Inst {
4306            data: InstData::IndexSet { base, index, value },
4307            span: Span::new(0, 10),
4308        });
4309
4310        let printer = RirPrinter::new(&rir, &interner);
4311        let output = printer.to_string();
4312        assert!(output.contains("index_set %0[%1] = %2"));
4313    }
4314
4315    // Struct with methods tests
4316    #[test]
4317    fn test_printer_struct_decl_with_methods() {
4318        let (mut rir, interner) = create_printer_test_rir();
4319
4320        // Create a method first
4321        let method_body = rir.add_inst(Inst {
4322            data: InstData::IntConst(0),
4323            span: Span::new(0, 1),
4324        });
4325        let method_name = interner.get_or_intern("get_x");
4326        let return_type = interner.get_or_intern("i32");
4327
4328        let (directives_start, directives_len) = rir.add_directives(&[]);
4329        let (params_start, params_len) = rir.add_params(&[]);
4330
4331        let method_ref = rir.add_inst(Inst {
4332            data: InstData::FnDecl {
4333                directives_start,
4334                directives_len,
4335                is_pub: false,
4336                is_unchecked: false,
4337                name: method_name,
4338                params_start,
4339                params_len,
4340                return_type,
4341                body: method_body,
4342                has_self: true,
4343                receiver_mode: 0,
4344            },
4345            span: Span::new(0, 30),
4346        });
4347
4348        let struct_name = interner.get_or_intern("Point");
4349        let x_field = interner.get_or_intern("x");
4350        let i32_type = interner.get_or_intern("i32");
4351
4352        let (fields_start, fields_len) = rir.add_field_decls(&[(x_field, i32_type)]);
4353        let (methods_start, methods_len) = rir.add_inst_refs(&[method_ref]);
4354
4355        rir.add_inst(Inst {
4356            data: InstData::StructDecl {
4357                directives_start,
4358                directives_len,
4359                is_pub: false,
4360                posture: Posture::Affine,
4361                name: struct_name,
4362                fields_start,
4363                fields_len,
4364                methods_start,
4365                methods_len,
4366            },
4367            span: Span::new(0, 50),
4368        });
4369
4370        let printer = RirPrinter::new(&rir, &interner);
4371        let output = printer.to_string();
4372        assert!(output.contains("struct Point { x: i32 } methods: [%1]"));
4373    }
4374
4375    #[test]
4376    fn test_printer_method_call() {
4377        let (mut rir, interner) = create_printer_test_rir();
4378        let receiver = rir.add_inst(Inst {
4379            data: InstData::IntConst(0), // placeholder for struct value
4380            span: Span::new(0, 1),
4381        });
4382        let arg = rir.add_inst(Inst {
4383            data: InstData::IntConst(10),
4384            span: Span::new(0, 2),
4385        });
4386
4387        let method = interner.get_or_intern("add");
4388
4389        let (args_start, args_len) = rir.add_call_args(&[RirCallArg {
4390            value: arg,
4391            mode: RirArgMode::Normal,
4392        }]);
4393
4394        rir.add_inst(Inst {
4395            data: InstData::MethodCall {
4396                receiver,
4397                method,
4398                args_start,
4399                args_len,
4400            },
4401            span: Span::new(0, 15),
4402        });
4403
4404        let printer = RirPrinter::new(&rir, &interner);
4405        let output = printer.to_string();
4406        assert!(output.contains("method_call %0.add(%1)"));
4407    }
4408
4409    #[test]
4410    fn test_printer_method_call_with_arg_modes() {
4411        let (mut rir, interner) = create_printer_test_rir();
4412        let receiver = rir.add_inst(Inst {
4413            data: InstData::IntConst(0),
4414            span: Span::new(0, 1),
4415        });
4416        let arg1 = rir.add_inst(Inst {
4417            data: InstData::IntConst(1),
4418            span: Span::new(0, 1),
4419        });
4420        let arg2 = rir.add_inst(Inst {
4421            data: InstData::IntConst(2),
4422            span: Span::new(0, 1),
4423        });
4424
4425        let method = interner.get_or_intern("modify");
4426
4427        let (args_start, args_len) = rir.add_call_args(&[
4428            RirCallArg {
4429                value: arg1,
4430                mode: RirArgMode::MutRef,
4431            },
4432            RirCallArg {
4433                value: arg2,
4434                mode: RirArgMode::Ref,
4435            },
4436        ]);
4437
4438        rir.add_inst(Inst {
4439            data: InstData::MethodCall {
4440                receiver,
4441                method,
4442                args_start,
4443                args_len,
4444            },
4445            span: Span::new(0, 25),
4446        });
4447
4448        let printer = RirPrinter::new(&rir, &interner);
4449        let output = printer.to_string();
4450        assert!(output.contains("method_call %0.modify(mut_ref %1, ref %2)"));
4451    }
4452
4453    #[test]
4454    fn test_printer_assoc_fn_call() {
4455        let (mut rir, interner) = create_printer_test_rir();
4456
4457        let type_name = interner.get_or_intern("Point");
4458        let function = interner.get_or_intern("origin");
4459
4460        let (args_start, args_len) = rir.add_call_args(&[]);
4461
4462        rir.add_inst(Inst {
4463            data: InstData::AssocFnCall {
4464                type_name,
4465                function,
4466                args_start,
4467                args_len,
4468            },
4469            span: Span::new(0, 15),
4470        });
4471
4472        let printer = RirPrinter::new(&rir, &interner);
4473        let output = printer.to_string();
4474        assert!(output.contains("assoc_fn_call Point::origin()"));
4475    }
4476
4477    #[test]
4478    fn test_printer_assoc_fn_call_with_args() {
4479        let (mut rir, interner) = create_printer_test_rir();
4480        let arg1 = rir.add_inst(Inst {
4481            data: InstData::IntConst(10),
4482            span: Span::new(0, 2),
4483        });
4484        let arg2 = rir.add_inst(Inst {
4485            data: InstData::IntConst(20),
4486            span: Span::new(0, 2),
4487        });
4488
4489        let type_name = interner.get_or_intern("Point");
4490        let function = interner.get_or_intern("new");
4491
4492        let (args_start, args_len) = rir.add_call_args(&[
4493            RirCallArg {
4494                value: arg1,
4495                mode: RirArgMode::Normal,
4496            },
4497            RirCallArg {
4498                value: arg2,
4499                mode: RirArgMode::Normal,
4500            },
4501        ]);
4502
4503        rir.add_inst(Inst {
4504            data: InstData::AssocFnCall {
4505                type_name,
4506                function,
4507                args_start,
4508                args_len,
4509            },
4510            span: Span::new(0, 20),
4511        });
4512
4513        let printer = RirPrinter::new(&rir, &interner);
4514        let output = printer.to_string();
4515        assert!(output.contains("assoc_fn_call Point::new(%0, %1)"));
4516    }
4517
4518    // Match and pattern tests
4519    #[test]
4520    fn test_printer_match_wildcard() {
4521        let (mut rir, interner) = create_printer_test_rir();
4522        let scrutinee = rir.add_inst(Inst {
4523            data: InstData::IntConst(42),
4524            span: Span::new(0, 2),
4525        });
4526        let body = rir.add_inst(Inst {
4527            data: InstData::IntConst(0),
4528            span: Span::new(0, 1),
4529        });
4530
4531        let (arms_start, arms_len) =
4532            rir.add_match_arms(&[(RirPattern::Wildcard(Span::new(0, 1)), body)]);
4533
4534        rir.add_inst(Inst {
4535            data: InstData::Match {
4536                scrutinee,
4537                arms_start,
4538                arms_len,
4539            },
4540            span: Span::new(0, 20),
4541        });
4542
4543        let printer = RirPrinter::new(&rir, &interner);
4544        let output = printer.to_string();
4545        assert!(output.contains("match %0 { _ => %1 }"));
4546    }
4547
4548    #[test]
4549    fn test_printer_match_int_pattern() {
4550        let (mut rir, interner) = create_printer_test_rir();
4551        let scrutinee = rir.add_inst(Inst {
4552            data: InstData::IntConst(42),
4553            span: Span::new(0, 2),
4554        });
4555        let body1 = rir.add_inst(Inst {
4556            data: InstData::IntConst(1),
4557            span: Span::new(0, 1),
4558        });
4559        let body2 = rir.add_inst(Inst {
4560            data: InstData::IntConst(2),
4561            span: Span::new(0, 1),
4562        });
4563        let body_default = rir.add_inst(Inst {
4564            data: InstData::IntConst(0),
4565            span: Span::new(0, 1),
4566        });
4567
4568        let (arms_start, arms_len) = rir.add_match_arms(&[
4569            (RirPattern::Int(1, Span::new(0, 1)), body1),
4570            (RirPattern::Int(-5, Span::new(0, 2)), body2),
4571            (RirPattern::Wildcard(Span::new(0, 1)), body_default),
4572        ]);
4573
4574        rir.add_inst(Inst {
4575            data: InstData::Match {
4576                scrutinee,
4577                arms_start,
4578                arms_len,
4579            },
4580            span: Span::new(0, 30),
4581        });
4582
4583        let printer = RirPrinter::new(&rir, &interner);
4584        let output = printer.to_string();
4585        assert!(output.contains("match %0 { 1 => %1, -5 => %2, _ => %3 }"));
4586    }
4587
4588    #[test]
4589    fn test_printer_match_bool_pattern() {
4590        let (mut rir, interner) = create_printer_test_rir();
4591        let scrutinee = rir.add_inst(Inst {
4592            data: InstData::BoolConst(true),
4593            span: Span::new(0, 4),
4594        });
4595        let body_true = rir.add_inst(Inst {
4596            data: InstData::IntConst(1),
4597            span: Span::new(0, 1),
4598        });
4599        let body_false = rir.add_inst(Inst {
4600            data: InstData::IntConst(0),
4601            span: Span::new(0, 1),
4602        });
4603
4604        let (arms_start, arms_len) = rir.add_match_arms(&[
4605            (RirPattern::Bool(true, Span::new(0, 4)), body_true),
4606            (RirPattern::Bool(false, Span::new(0, 5)), body_false),
4607        ]);
4608
4609        rir.add_inst(Inst {
4610            data: InstData::Match {
4611                scrutinee,
4612                arms_start,
4613                arms_len,
4614            },
4615            span: Span::new(0, 30),
4616        });
4617
4618        let printer = RirPrinter::new(&rir, &interner);
4619        let output = printer.to_string();
4620        assert!(output.contains("match %0 { true => %1, false => %2 }"));
4621    }
4622
4623    #[test]
4624    fn test_printer_match_path_pattern() {
4625        let (mut rir, interner) = create_printer_test_rir();
4626        let scrutinee = rir.add_inst(Inst {
4627            data: InstData::IntConst(0), // placeholder for enum value
4628            span: Span::new(0, 1),
4629        });
4630        let body_red = rir.add_inst(Inst {
4631            data: InstData::IntConst(1),
4632            span: Span::new(0, 1),
4633        });
4634        let body_green = rir.add_inst(Inst {
4635            data: InstData::IntConst(2),
4636            span: Span::new(0, 1),
4637        });
4638        let body_default = rir.add_inst(Inst {
4639            data: InstData::IntConst(0),
4640            span: Span::new(0, 1),
4641        });
4642
4643        let color = interner.get_or_intern("Color");
4644        let red = interner.get_or_intern("Red");
4645        let green = interner.get_or_intern("Green");
4646
4647        let (arms_start, arms_len) = rir.add_match_arms(&[
4648            (
4649                RirPattern::Path {
4650                    module: None,
4651                    type_name: color,
4652                    variant: red,
4653                    span: Span::new(0, 10),
4654                },
4655                body_red,
4656            ),
4657            (
4658                RirPattern::Path {
4659                    module: None,
4660                    type_name: color,
4661                    variant: green,
4662                    span: Span::new(0, 12),
4663                },
4664                body_green,
4665            ),
4666            (RirPattern::Wildcard(Span::new(0, 1)), body_default),
4667        ]);
4668
4669        rir.add_inst(Inst {
4670            data: InstData::Match {
4671                scrutinee,
4672                arms_start,
4673                arms_len,
4674            },
4675            span: Span::new(0, 50),
4676        });
4677
4678        let printer = RirPrinter::new(&rir, &interner);
4679        let output = printer.to_string();
4680        assert!(output.contains("match %0 { Color::Red => %1, Color::Green => %2, _ => %3 }"));
4681    }
4682
4683    #[test]
4684    fn test_printer_display_trait() {
4685        let (mut rir, interner) = create_printer_test_rir();
4686        rir.add_inst(Inst {
4687            data: InstData::IntConst(42),
4688            span: Span::new(0, 2),
4689        });
4690
4691        let printer = RirPrinter::new(&rir, &interner);
4692        // Test Display trait implementation
4693        let output = format!("{}", printer);
4694        assert!(output.contains("%0 = const 42"));
4695    }
4696
4697    // ===== RIR merge tests =====
4698
4699    #[test]
4700    fn test_merge_empty_rirs() {
4701        let merged = Rir::merge(&[]);
4702        assert!(merged.is_empty());
4703        assert!(merged.function_spans().is_empty());
4704    }
4705
4706    #[test]
4707    fn test_merge_single_rir() {
4708        let mut rir = Rir::new();
4709        rir.add_inst(Inst {
4710            data: InstData::IntConst(42),
4711            span: Span::new(0, 2),
4712        });
4713        rir.add_inst(Inst {
4714            data: InstData::BoolConst(true),
4715            span: Span::new(3, 7),
4716        });
4717
4718        let merged = Rir::merge(&[rir]);
4719        assert_eq!(merged.len(), 2);
4720
4721        // Check that instructions are preserved
4722        assert!(matches!(
4723            merged.get(InstRef::from_raw(0)).data,
4724            InstData::IntConst(42)
4725        ));
4726        assert!(matches!(
4727            merged.get(InstRef::from_raw(1)).data,
4728            InstData::BoolConst(true)
4729        ));
4730    }
4731
4732    #[test]
4733    fn test_merge_two_rirs_simple() {
4734        // RIR 1: just an int constant
4735        let mut rir1 = Rir::new();
4736        rir1.add_inst(Inst {
4737            data: InstData::IntConst(10),
4738            span: Span::new(0, 2),
4739        });
4740
4741        // RIR 2: another int constant
4742        let mut rir2 = Rir::new();
4743        rir2.add_inst(Inst {
4744            data: InstData::IntConst(20),
4745            span: Span::new(5, 7),
4746        });
4747
4748        let merged = Rir::merge(&[rir1, rir2]);
4749        assert_eq!(merged.len(), 2);
4750
4751        // First instruction from rir1
4752        assert!(matches!(
4753            merged.get(InstRef::from_raw(0)).data,
4754            InstData::IntConst(10)
4755        ));
4756        // Second instruction from rir2 (renumbered to index 1)
4757        assert!(matches!(
4758            merged.get(InstRef::from_raw(1)).data,
4759            InstData::IntConst(20)
4760        ));
4761    }
4762
4763    #[test]
4764    fn test_merge_renumbers_inst_refs() {
4765        // RIR 1: const and an add that references it
4766        let mut rir1 = Rir::new();
4767        let const1 = rir1.add_inst(Inst {
4768            data: InstData::IntConst(5),
4769            span: Span::new(0, 1),
4770        });
4771        rir1.add_inst(Inst {
4772            data: InstData::Bin {
4773                op: BinOp::Add,
4774                lhs: const1,
4775                rhs: const1,
4776            },
4777            span: Span::new(2, 5),
4778        });
4779
4780        // RIR 2: const and an add that references it (local indices)
4781        let mut rir2 = Rir::new();
4782        let const2 = rir2.add_inst(Inst {
4783            data: InstData::IntConst(10),
4784            span: Span::new(10, 12),
4785        });
4786        rir2.add_inst(Inst {
4787            data: InstData::Bin {
4788                op: BinOp::Add,
4789                lhs: const2,
4790                rhs: const2,
4791            },
4792            span: Span::new(12, 16),
4793        });
4794
4795        let merged = Rir::merge(&[rir1, rir2]);
4796        assert_eq!(merged.len(), 4);
4797
4798        // Check rir1's add still references %0
4799        if let InstData::Bin {
4800            op: BinOp::Add,
4801            lhs,
4802            rhs,
4803        } = &merged.get(InstRef::from_raw(1)).data
4804        {
4805            assert_eq!(lhs.as_u32(), 0);
4806            assert_eq!(rhs.as_u32(), 0);
4807        } else {
4808            panic!("Expected Add instruction at index 1");
4809        }
4810
4811        // Check rir2's add now references %2 (renumbered from %0)
4812        if let InstData::Bin {
4813            op: BinOp::Add,
4814            lhs,
4815            rhs,
4816        } = &merged.get(InstRef::from_raw(3)).data
4817        {
4818            assert_eq!(lhs.as_u32(), 2);
4819            assert_eq!(rhs.as_u32(), 2);
4820        } else {
4821            panic!("Expected Add instruction at index 3");
4822        }
4823    }
4824
4825    #[test]
4826    fn test_merge_renumbers_extra_data() {
4827        // RIR 1: function call with args in extra
4828        let mut rir1 = Rir::new();
4829        let interner = ThreadedRodeo::new();
4830        let fn_name = interner.get_or_intern("foo");
4831
4832        let const1 = rir1.add_inst(Inst {
4833            data: InstData::IntConst(1),
4834            span: Span::new(0, 1),
4835        });
4836        let (args_start, args_len) = rir1.add_call_args(&[RirCallArg {
4837            value: const1,
4838            mode: RirArgMode::Normal,
4839        }]);
4840        rir1.add_inst(Inst {
4841            data: InstData::Call {
4842                name: fn_name,
4843                args_start,
4844                args_len,
4845            },
4846            span: Span::new(2, 8),
4847        });
4848
4849        // RIR 2: function call with args in extra
4850        let mut rir2 = Rir::new();
4851        let const2 = rir2.add_inst(Inst {
4852            data: InstData::IntConst(2),
4853            span: Span::new(10, 11),
4854        });
4855        let (args_start2, args_len2) = rir2.add_call_args(&[RirCallArg {
4856            value: const2,
4857            mode: RirArgMode::Normal,
4858        }]);
4859        rir2.add_inst(Inst {
4860            data: InstData::Call {
4861                name: fn_name,
4862                args_start: args_start2,
4863                args_len: args_len2,
4864            },
4865            span: Span::new(12, 18),
4866        });
4867
4868        let merged = Rir::merge(&[rir1, rir2]);
4869        assert_eq!(merged.len(), 4);
4870
4871        // Check rir1's call still has correct args_start
4872        if let InstData::Call {
4873            args_start,
4874            args_len,
4875            ..
4876        } = &merged.get(InstRef::from_raw(1)).data
4877        {
4878            let args = merged.get_call_args(*args_start, *args_len);
4879            assert_eq!(args.len(), 1);
4880            assert_eq!(args[0].value.as_u32(), 0); // Still references const1 at %0
4881        } else {
4882            panic!("Expected Call instruction at index 1");
4883        }
4884
4885        // Check rir2's call has updated args_start and renumbered arg value
4886        if let InstData::Call {
4887            args_start,
4888            args_len,
4889            ..
4890        } = &merged.get(InstRef::from_raw(3)).data
4891        {
4892            let args = merged.get_call_args(*args_start, *args_len);
4893            assert_eq!(args.len(), 1);
4894            assert_eq!(args[0].value.as_u32(), 2); // Now references const2 at %2
4895        } else {
4896            panic!("Expected Call instruction at index 3");
4897        }
4898    }
4899
4900    #[test]
4901    fn test_merge_function_spans() {
4902        let interner = ThreadedRodeo::new();
4903        let main_name = interner.get_or_intern("main");
4904        let helper_name = interner.get_or_intern("helper");
4905
4906        // RIR 1: main function
4907        let mut rir1 = Rir::new();
4908        let body_start1 = InstRef::from_raw(rir1.current_inst_index());
4909        let const1 = rir1.add_inst(Inst {
4910            data: InstData::IntConst(0),
4911            span: Span::new(0, 1),
4912        });
4913        let (params_start, params_len) = rir1.add_params(&[]);
4914        let (dirs_start, dirs_len) = rir1.add_directives(&[]);
4915        let ret_type = interner.get_or_intern("i32");
4916        let decl1 = rir1.add_inst(Inst {
4917            data: InstData::FnDecl {
4918                directives_start: dirs_start,
4919                directives_len: dirs_len,
4920                is_pub: false,
4921                is_unchecked: false,
4922                name: main_name,
4923                params_start,
4924                params_len,
4925                return_type: ret_type,
4926                body: const1,
4927                has_self: false,
4928                receiver_mode: 0,
4929            },
4930            span: Span::new(0, 10),
4931        });
4932        rir1.add_function_span(FunctionSpan::new(main_name, body_start1, decl1));
4933
4934        // RIR 2: helper function
4935        let mut rir2 = Rir::new();
4936        let body_start2 = InstRef::from_raw(rir2.current_inst_index());
4937        let const2 = rir2.add_inst(Inst {
4938            data: InstData::IntConst(42),
4939            span: Span::new(20, 22),
4940        });
4941        let (params_start2, params_len2) = rir2.add_params(&[]);
4942        let (dirs_start2, dirs_len2) = rir2.add_directives(&[]);
4943        let decl2 = rir2.add_inst(Inst {
4944            data: InstData::FnDecl {
4945                directives_start: dirs_start2,
4946                directives_len: dirs_len2,
4947                is_pub: false,
4948                is_unchecked: false,
4949                name: helper_name,
4950                params_start: params_start2,
4951                params_len: params_len2,
4952                return_type: ret_type,
4953                body: const2,
4954                has_self: false,
4955                receiver_mode: 0,
4956            },
4957            span: Span::new(20, 35),
4958        });
4959        rir2.add_function_span(FunctionSpan::new(helper_name, body_start2, decl2));
4960
4961        let merged = Rir::merge(&[rir1, rir2]);
4962
4963        // Check we have 2 function spans
4964        assert_eq!(merged.function_spans().len(), 2);
4965
4966        // Check main function span (from rir1, indices unchanged)
4967        let main_span = &merged.function_spans()[0];
4968        assert_eq!(main_span.name, main_name);
4969        assert_eq!(main_span.body_start.as_u32(), 0);
4970        assert_eq!(main_span.decl.as_u32(), 1);
4971
4972        // Check helper function span (from rir2, indices shifted by 2)
4973        let helper_span = &merged.function_spans()[1];
4974        assert_eq!(helper_span.name, helper_name);
4975        assert_eq!(helper_span.body_start.as_u32(), 2); // Was 0, now 0 + 2 = 2
4976        assert_eq!(helper_span.decl.as_u32(), 3); // Was 1, now 1 + 2 = 3
4977    }
4978
4979    #[test]
4980    fn test_merge_three_rirs() {
4981        let mut rir1 = Rir::new();
4982        rir1.add_inst(Inst {
4983            data: InstData::IntConst(1),
4984            span: Span::new(0, 1),
4985        });
4986
4987        let mut rir2 = Rir::new();
4988        rir2.add_inst(Inst {
4989            data: InstData::IntConst(2),
4990            span: Span::new(10, 11),
4991        });
4992
4993        let mut rir3 = Rir::new();
4994        rir3.add_inst(Inst {
4995            data: InstData::IntConst(3),
4996            span: Span::new(20, 21),
4997        });
4998
4999        let merged = Rir::merge(&[rir1, rir2, rir3]);
5000        assert_eq!(merged.len(), 3);
5001
5002        assert!(matches!(
5003            merged.get(InstRef::from_raw(0)).data,
5004            InstData::IntConst(1)
5005        ));
5006        assert!(matches!(
5007            merged.get(InstRef::from_raw(1)).data,
5008            InstData::IntConst(2)
5009        ));
5010        assert!(matches!(
5011            merged.get(InstRef::from_raw(2)).data,
5012            InstData::IntConst(3)
5013        ));
5014    }
5015
5016    #[test]
5017    fn test_merge_preserves_spans() {
5018        let mut rir1 = Rir::new();
5019        rir1.add_inst(Inst {
5020            data: InstData::IntConst(1),
5021            span: Span::new(5, 10),
5022        });
5023
5024        let mut rir2 = Rir::new();
5025        rir2.add_inst(Inst {
5026            data: InstData::IntConst(2),
5027            span: Span::new(100, 105),
5028        });
5029
5030        let merged = Rir::merge(&[rir1, rir2]);
5031
5032        // Spans should be preserved exactly
5033        assert_eq!(merged.get(InstRef::from_raw(0)).span, Span::new(5, 10));
5034        assert_eq!(merged.get(InstRef::from_raw(1)).span, Span::new(100, 105));
5035    }
5036}