lib/std/lang/lower.rad 321.7 KiB raw
1
//! AST to IL lowering pass.
2
//!
3
//! This module converts the typed AST produced by the resolver into a linear
4
//! SSA-based intermediate language (IL). The IL is suitable for further
5
//! optimization and code generation.
6
//!
7
//! # Design Overview
8
//!
9
//! The lowering process works in two main phases:
10
//!
11
//! 1. Module-level pass: Iterates over top-level declarations, lowering
12
//!    each function independently while accumulating global data items (strings,
13
//!    constants, static arrays) into a shared data section.
14
//!
15
//! 2. Function-level pass: For each function, constructs an SSA-form control
16
//!    flow graph (CFG) with basic blocks connected by jumps and branches. Uses
17
//!    a simplified SSA construction algorithm where variables are tracked per-block
18
//!    and block parameters are inserted lazily when a variable is used before
19
//!    being defined in a block.
20
//!
21
//! # Memory Model
22
//!
23
//! All allocations use an arena allocator passed through the `Lowerer` context.
24
//! The IL is entirely stack-based at runtime -- there's no heap allocation during
25
//! program execution. Aggregate values (records, slices, optionals) are passed
26
//! by pointer on the stack.
27
//!
28
//! # SSA Construction
29
//!
30
//! SSA form is built incrementally as the AST is walked. When a variable is
31
//! defined (via `defVar`), its current value is recorded in the current block.
32
//! When a variable is used (via `useVar`), the algorithm either:
33
//! - Returns the value if defined in this block
34
//! - Recurses to predecessors if the block is sealed (all predecessors known)
35
//! - Inserts a block parameter if the variable value must come from multiple paths
36
//!
37
//! Block "sealing" indicates that all predecessor edges are known, enabling
38
//! the SSA construction to resolve cross-block variable references.
39
//!
40
//! # SSA Variable API
41
//!
42
//! 1. `newVar` creates a logical variable that can be defined differently in each block.
43
//! 2. `defVar` defines the variable's value in the current block.
44
//! 3. `useVar` reads the variable; if called in a block with multiple
45
//!             predecessors that defined it differently, the SSA algorithm
46
//!             automatically creates a block parameter.
47
//!
48
//! # Expression Lowering
49
//!
50
//! Expressions produce IL values which can be:
51
//!
52
//! - Imm(i64): immediate/constant values
53
//! - Reg(u32): SSA register references
54
//! - Sym(name): symbol references (for function pointers, data addresses)
55
//! - Undef: for unused values
56
//!
57
//! For aggregate types (records, arrays, slices, optionals), the "value" is
58
//! actually a pointer to stack-allocated memory containing the aggregate.
59
//!
60
//! # Block ordering invariant
61
//!
62
//! The register allocator processes blocks in forward index order and uses a
63
//! single global assignment array. This means a value's definition block must
64
//! have a lower index than any block that uses the value (except through
65
//! back-edges, where block parameters handle the merge). The lowerer maintains
66
//! this by creating blocks in control-flow order. For `for` loops, the step
67
//! block is created lazily (after the loop body) so that its index is higher
68
//! than all body blocks -- see [`lowerForLoop`] and [`getOrCreateContinueBlock`].
69
//!
70
//! A more robust alternative would be per-block register maps with edge-copy
71
//! resolution (as in QBE's `rega.c`), which is block-order-independent. That
72
//! would eliminate this invariant at the cost of a more complex allocator.
73
//!
74
//! # Notes on constant data lowering
75
//!
76
//! The lowerer flattens constant AST expressions directly into IL data values.
77
//! Primitive constants use [`resolver::ConstValue`] as an intermediate form.
78
//! Record padding is explicit: `undef * N;` for N padding bytes.
79
//!
80
//!   AST Node (ArrayLit, RecordLit, literals, etc.)
81
//!       ↓ [`lowerConstData`]
82
//!   resolver::ConstValue (Bool, Char, String, Int)
83
//!       ↓ [`constValueToDataItem`]
84
//!   il::DataItem (Val, Sym, Str, Undef)
85
//!       ↓
86
//!   il::DataValue
87
//!       ↓
88
//!   il::Data
89
//!
90
use std::fmt;
91
use std::io;
92
use std::lang::alloc;
93
use std::lang::types;
94
use std::mem;
95
use std::lang::ast;
96
use std::lang::il;
97
use std::lang::module;
98
use std::lang::resolver;
99
100
// TODO: Search for all `_ as i32` to ensure that casts from u32 to i32 don't
101
// happen, since they are potentially truncating values.
102
103
// TODO: Support constant union lowering.
104
// TODO: Void unions should be passed by value.
105
106
////////////////////
107
// Error Handling //
108
////////////////////
109
110
/// Lowering errors are typically unrecoverable since they indicate bugs in
111
/// the resolver or malformed AST that should have been caught earlier.
112
export union LowerError: Copy {
113
    /// A node's symbol was not set before lowering.
114
    MissingSymbol(*ast::Node),
115
    /// A node's type was not set before lowering.
116
    MissingType(*ast::Node),
117
    /// A node's constant value was not set before lowering.
118
    MissingConst(*ast::Node),
119
    /// An optional type was expected.
120
    ExpectedOptional,
121
    /// A record type was expected.
122
    ExpectedRecord,
123
    /// An array was expected.
124
    ExpectedArray,
125
    /// A block was expected.
126
    ExpectedBlock(*ast::Node),
127
    /// An identifier was expected.
128
    ExpectedIdentifier,
129
    /// A function parameter was expected.
130
    ExpectedFunctionParam,
131
    /// A function type was expected.
132
    ExpectedFunction,
133
    /// Trying to lower loop construct outside of loop.
134
    OutsideOfLoop,
135
    /// Invalid variable use.
136
    InvalidUse,
137
    /// Unexpected node value.
138
    UnexpectedNodeValue(*ast::Node),
139
    /// Unexpected type.
140
    UnexpectedType(resolver::Type),
141
142
    /// Missing control flow target.
143
    MissingTarget,
144
    /// Missing metadata that should have been set by resolver.
145
    MissingMetadata,
146
    /// Expected a slice or array type for operation.
147
    ExpectedSliceOrArray,
148
    /// Expected a variant symbol.
149
    ExpectedVariant,
150
    /// Expected a call expression.
151
    ExpectedCall,
152
    /// Field not found in record or invalid field access.
153
    FieldNotFound,
154
    /// Assignment to an immutable variable.
155
    ImmutableAssignment,
156
    /// Nil used in non-optional context.
157
    NilInNonOptional,
158
    /// Invalid argument count for builtin.
159
    InvalidArgCount,
160
    /// Feature or pattern not supported by the lowerer.
161
    Unsupported,
162
    /// Unknown intrinsic function.
163
    UnknownIntrinsic,
164
    /// Allocation failure.
165
    AllocationFailed,
166
}
167
168
/// Print a LowerError for debugging.
169
export fn printError(err: LowerError) {
170
    match err {
171
        case LowerError::MissingSymbol(_) => io::print("MissingSymbol"),
172
        case LowerError::MissingType(_) => io::print("MissingType"),
173
        case LowerError::MissingConst(_) => io::print("MissingConst"),
174
        case LowerError::ExpectedOptional => io::print("ExpectedOptional"),
175
        case LowerError::ExpectedRecord => io::print("ExpectedRecord"),
176
        case LowerError::ExpectedArray => io::print("ExpectedArray"),
177
        case LowerError::ExpectedBlock(_) => io::print("ExpectedBlock"),
178
        case LowerError::ExpectedIdentifier => io::print("ExpectedIdentifier"),
179
        case LowerError::ExpectedFunctionParam => io::print("ExpectedFunctionParam"),
180
        case LowerError::ExpectedFunction => io::print("ExpectedFunction"),
181
        case LowerError::OutsideOfLoop => io::print("OutsideOfLoop"),
182
        case LowerError::InvalidUse => io::print("InvalidUse"),
183
        case LowerError::UnexpectedNodeValue(_) => io::print("UnexpectedNodeValue"),
184
        case LowerError::UnexpectedType(_) => io::print("UnexpectedType"),
185
        case LowerError::MissingTarget => io::print("MissingTarget"),
186
        case LowerError::MissingMetadata => io::print("MissingMetadata"),
187
        case LowerError::ExpectedSliceOrArray => io::print("ExpectedSliceOrArray"),
188
        case LowerError::ExpectedVariant => io::print("ExpectedVariant"),
189
        case LowerError::ExpectedCall => io::print("ExpectedCall"),
190
        case LowerError::FieldNotFound => io::print("FieldNotFound"),
191
        case LowerError::ImmutableAssignment => io::print("ImmutableAssignment"),
192
        case LowerError::NilInNonOptional => io::print("NilInNonOptional"),
193
        case LowerError::InvalidArgCount => io::print("InvalidArgCount"),
194
        case LowerError::Unsupported => io::print("Unsupported"),
195
        case LowerError::UnknownIntrinsic => io::print("UnknownIntrinsic"),
196
        case LowerError::AllocationFailed => io::print("AllocationFailed"),
197
    }
198
}
199
200
///////////////
201
// Constants //
202
///////////////
203
204
/// Maximum nesting depth of loops.
205
constant MAX_LOOP_DEPTH: u32 = 16;
206
/// Maximum number of `catch` clauses per `try`.
207
constant MAX_CATCH_CLAUSES: u32 = 32;
208
209
// Slice Layout
210
//
211
// A slice is a fat pointer consisting of a data pointer, a length and a capacity.
212
// `{ ptr: u32, len: u32, cap: u32 }`.
213
214
/// Slice data pointer offset.
215
constant SLICE_PTR_OFFSET: i32 = 0;
216
/// Offset of slice length in slice data structure.
217
constant SLICE_LEN_OFFSET: i32 = resolver::PTR_SIZE as i32;
218
/// Offset of slice capacity in slice data structure.
219
constant SLICE_CAP_OFFSET: i32 = resolver::PTR_SIZE as i32 + 4;
220
221
// Trait Object Layout
222
//
223
// A trait object is a fat pointer consisting of a data pointer and a
224
// v-table pointer. `{ data: *T, vtable: *VTable }`.
225
226
/// Trait object data pointer offset.
227
constant TRAIT_OBJ_DATA_OFFSET: i32 = 0;
228
/// Trait object v-table pointer offset.
229
constant TRAIT_OBJ_VTABLE_OFFSET: i32 = resolver::PTR_SIZE as i32;
230
231
// Tagged Value Layout (optionals, tagged unions)
232
//
233
// Optionals and unions use 1-byte tags. Results use 8-byte tags.
234
//
235
// `{ tag: u8, [padding], payload: T }`
236
//
237
// Optionals use `tag: 0` for `nil` and `tag: 1` otherwise.
238
// When `T` is a pointer, the entire optional is stored as a single pointer.
239
//
240
// Tagged unions have a payload the size of the maximum variant size.
241
242
/// Offset of tag in tagged value data structure.
243
constant TVAL_TAG_OFFSET: i32 = 0;
244
/// Offset of value in result data structure (8-byte tag).
245
constant RESULT_VAL_OFFSET: i32 = resolver::PTR_SIZE as i32;
246
247
//////////////////////////
248
// Core Data Structures //
249
//////////////////////////
250
251
/// Options controlling the lowering pass.
252
export record LowerOptions: Copy {
253
    /// Whether to emit source location info.
254
    debug: bool,
255
    /// Whether to lower `@test` functions.
256
    buildTest: bool,
257
}
258
259
/// Role of a lowered function in the final program.
260
export union FnRole: Copy {
261
    /// Regular function.
262
    Normal,
263
    /// Program entry function marked with `@default`.
264
    Default,
265
}
266
267
/// One function ready for a caller to consume before lowering continues.
268
export record LoweredFunction: Copy {
269
    /// Function allocated in the current function arena.
270
    function: *unsafe il::Fn,
271
    /// Entry-point role of the source declaration.
272
    role: FnRole,
273
}
274
275
/// Position within one module's declarations and instance methods.
276
export record ModuleCursor {
277
    /// Top-level declaration nodes in source order.
278
    declarations: *[*ast::Node],
279
    /// Next declaration to process.
280
    next: u32,
281
    /// Whether default functions in this module are program entry points.
282
    isRoot: bool,
283
    /// Instance whose methods are currently being lowered.
284
    /// Nil when no instance declaration is active.
285
    instanceState: ?InstanceCursor,
286
}
287
288
/// Persistent v-table names and position within an instance declaration.
289
export record InstanceCursor: Copy {
290
    /// Trait whose method slots define the v-table.
291
    traitInfo: *unsafe resolver::TraitType,
292
    /// Trait source name.
293
    traitName: *[u8],
294
    /// Concrete type source name.
295
    typeName: *[u8],
296
    /// Method declarations in source order.
297
    methods: *[*ast::Node],
298
    /// Next method to process.
299
    next: u32,
300
    /// Persistent qualified function names, indexed by trait method slot.
301
    methodNames: [*[u8]; ast::MAX_TRAIT_METHODS],
302
    /// Slots initialized by concrete methods.
303
    methodNameSet: [bool; ast::MAX_TRAIT_METHODS],
304
}
305
306
/// Module-level lowering context. Shared across all function lowerings.
307
/// Holds global state like the data section (strings, constants) and provides
308
/// access to the resolver for type queries.
309
export record Lowerer: 'arena + 'phase where 'arena: 'phase {
310
    /// Arena for persistent lowering state, including data and symbol names.
311
    arena: *unsafe mut alloc::Arena,
312
    /// Resolver for type information. Used to query types, symbols, and
313
    /// compile-time constant values during lowering.
314
    resolver: &'phase resolver::Resolver 'arena,
315
    /// Package name for qualified symbol names.
316
    pkgName: *[u8],
317
    /// Current module being lowered.
318
    currentMod: ?u16,
319
    /// Global data items (string literals, constants, static arrays).
320
    /// These become the data sections in the final binary.
321
    data: *mut [il::Data],
322
    /// First data entry owned by the package currently being lowered.
323
    packageDataStart: u32,
324
    /// Functions retained for accumulated output.
325
    functions: *mut [*unsafe il::Fn],
326
    /// Function and data symbols with their emitted names.
327
    symbolNames: *mut [SymbolNameEntry],
328
    /// Global error type tag table. Maps nominal types to unique tags.
329
    errTags: *mut [ErrTagEntry],
330
    /// Next error tag to assign (starts at 1; 0 = success).
331
    errTagCounter: u32,
332
    /// Lowering options.
333
    options: LowerOptions,
334
}
335
336
/// Entry mapping a function or data symbol to its emitted name.
337
record SymbolNameEntry: Copy {
338
    /// Symbol identity within the lowering context's resolver.
339
    symbolId: u32,
340
    /// Qualified name in the emitted program.
341
    qualName: *[u8],
342
}
343
344
/// Entry in the global error tag table.
345
record ErrTagEntry: Copy {
346
    /// The type of this error, identified by its interned pointer.
347
    ty: resolver::Type,
348
    /// The globally unique tag assigned to this error type (non-zero).
349
    tag: u32,
350
}
351
352
/// Compute the maximum size of any error type in a throw list.
353
unsafe fn maxErrSize(throwList: *[*resolver::Type]) -> u32 {
354
    let mut maxSize: u32 = 0;
355
    for ty in throwList {
356
        let size = resolver::getTypeLayout(*ty).size;
357
        if size > maxSize {
358
            set maxSize = size;
359
        }
360
    }
361
    return maxSize;
362
}
363
364
/// Get or assign a globally unique error tag for the given error type.
365
/// Tag `0` is reserved for success; error tags start at `1`.
366
unsafe fn getOrAssignErrorTag 'arena 'phase (self: &mut Lowerer 'arena 'phase, errType: resolver::Type) -> u32 where 'arena: 'phase {
367
    for entry in &self.errTags[..] {
368
        if resolver::erasedTypesEqual(entry.ty, errType) {
369
            return entry.tag;
370
        }
371
    }
372
    let tag = self.errTagCounter;
373
374
    set self.errTagCounter += 1;
375
    self.errTags.append(ErrTagEntry { ty: errType, tag }, alloc::arenaAllocator(self.arena));
376
377
    return tag;
378
}
379
380
/// Retain a lowered function for accumulated IL output.
381
fn emitFunction 'arena 'phase (self: &mut Lowerer 'arena 'phase, func: *unsafe il::Fn, allocator: alloc::Allocator) where 'arena: 'phase {
382
    self.functions.append(func, allocator);
383
}
384
385
/// Get the function role for a top-level function declaration.
386
fn lowerFnRole(isRoot: bool, attrs: ?ast::Attributes) -> FnRole {
387
    if isRoot and checkAttr(attrs, ast::Attribute::Default) {
388
        return FnRole::Default;
389
    }
390
    return FnRole::Normal;
391
}
392
393
/// Builder for accumulating data values during constant lowering.
394
record DataValueBuilder {
395
    /// Allocator used to grow the value storage.
396
    allocator: alloc::Allocator,
397
    /// Owned values under construction.
398
    values: *mut [il::DataValue],
399
    /// Whether all pushed values can be represented by zero-filled memory.
400
    zeroInit: bool,
401
}
402
403
/// Result of lowering constant data.
404
record ConstDataResult: Copy {
405
    /// Immutable initializer values.
406
    values: *[il::DataValue],
407
    /// Whether the values can be represented by zero-filled memory.
408
    zeroInit: bool,
409
}
410
411
/// Create a new builder.
412
fn dataBuilder(allocator: alloc::Allocator) -> DataValueBuilder {
413
    return DataValueBuilder { allocator, values: &mut [], zeroInit: true };
414
}
415
416
/// Return whether a data item can be omitted from a zero-filled image.
417
fn dataIsZero(item: il::DataItem) -> bool {
418
    match item {
419
        case il::DataItem::Undef => return true,
420
        case il::DataItem::Val { val, .. } => return val == 0,
421
        else => return false,
422
    }
423
}
424
425
/// Return whether all data values can be omitted from a zero-filled image.
426
fn dataValuesAreZeroInit(values: *[il::DataValue]) -> bool {
427
    for value in values {
428
        if value.count > 0 and not dataIsZero(value.item) {
429
            return false;
430
        }
431
    }
432
    return true;
433
}
434
435
/// Append a data value to the builder.
436
fn dataBuilderPush(b: &mut DataValueBuilder, value: il::DataValue) {
437
    b.values.append(value, b.allocator);
438
439
    if value.count > 0 and not dataIsZero(value.item) {
440
        set b.zeroInit = false;
441
    }
442
}
443
444
/// Return the accumulated values.
445
fn dataBuilderFinish(b: DataValueBuilder) -> ConstDataResult {
446
    let case DataValueBuilder { values, zeroInit, .. } = b
447
        else panic "expected data builder";
448
    return ConstDataResult { values, zeroInit };
449
}
450
451
///////////////////////////
452
// SSA Variable Tracking //
453
///////////////////////////
454
455
// The SSA construction algorithm tracks variable definitions per-block.
456
// Each source-level variable gets a [`Var`] handle, and each block maintains
457
// a mapping from [`Var`] to current SSA value. When control flow merges,
458
// block parameters are inserted to merge values from different control flow paths.
459
460
/// A variable handle. Represents a source-level variable during lowering.
461
/// The same [`Var`] can have different SSA values in different blocks.
462
export record Var: Copy(u32);
463
464
/// Source of a variable value at a block entry or use.
465
union VarSource: Copy {
466
    /// A value already available in the block.
467
    Value(il::Val),
468
    /// A value supplied by one distinct predecessor.
469
    Predecessor(BlockId),
470
    /// A block parameter must merge incoming values.
471
    Merge,
472
    /// A sealed non-entry block has no predecessor.
473
    Invalid,
474
}
475
476
/// Metadata for a source-level variable, stored once per function.
477
///
478
/// Each variable declaration in the source creates one [`VarData`] entry in the
479
/// function's `variables` array, indexed by `id`. This contains static
480
/// properties that don't change across basic blocks.
481
///
482
/// Per-block SSA values are tracked separately in [`BlockData::vars`] as [`?il::Val`],
483
/// where `nil` means "not yet assigned in this block". Together they implement
484
/// SSA construction.
485
///
486
record VarData: Copy {
487
    /// Variable name, used by [`lookupVarByName`] to resolve identifiers.
488
    /// Nil for anonymous variables (e.g., internal loop counters).
489
    name: ?*[u8],
490
    /// IL type of this variable. Set at declaration time and used when
491
    /// generating loads, stores, and type-checking assignments.
492
    type: il::Type,
493
    /// Whether this variable was declared with `mut`. Controls whether [`defVar`]
494
    /// is allowed after the initial definition.
495
    mutable: bool,
496
    /// Whether this variable's address has been taken (e.g. via `&mut x`).
497
    /// When true, the SSA value is a pointer to a stack slot and reads/writes
498
    /// must go through memory instead of using the cached SSA value directly.
499
    addressTaken: bool,
500
}
501
502
/// Checked metadata storage and the active lexical variable prefix.
503
record Variables: 'function {
504
    /// Slots reserved from the resolver's local-variable bound.
505
    items: &'function mut [VarData],
506
    /// Number of active variable bindings.
507
    len: u32,
508
}
509
510
/// Links a function parameter to its corresponding variable for the entry block.
511
/// After creating the entry block, we iterate through these to define initial values.
512
record FnParamBinding: Copy {
513
    /// The variable that receives this parameter's value.
514
    var: Var,
515
    /// SSA register containing the parameter value from the caller.
516
    reg: il::Reg,
517
}
518
519
////////////////////////////////
520
// Basic Block Representation //
521
////////////////////////////////
522
523
// During lowering, we build a CFG of basic blocks. Each block accumulates
524
// instructions until terminated by a jump, branch, return, or unreachable.
525
// Blocks can be created before they're filled (forward references for jumps).
526
527
/// A handle to a basic block within the current function.
528
/// Block handles are stable, they don't change as more blocks are added.
529
export record BlockId: Copy(u32);
530
531
/// Construction-time switch case with safely owned edge arguments.
532
record LowerSwitchCase {
533
    /// Constant value selecting this case.
534
    value: i64,
535
    /// Destination block.
536
    target: BlockId,
537
    /// Mutable argument capacity retained until final IL publication.
538
    args: *mut [il::Val],
539
}
540
541
/// Construction-time terminator edges with safely owned argument storage.
542
union LowerTerminator {
543
    /// No jump, branch, or switch has been recorded.
544
    None,
545
    /// One unconditional edge.
546
    Jmp {
547
        /// Destination block.
548
        target: BlockId,
549
        /// Mutable argument capacity retained until final IL publication.
550
        args: *mut [il::Val],
551
    },
552
    /// Two conditional edges.
553
    Br {
554
        /// Destination when the comparison succeeds.
555
        thenTarget: BlockId,
556
        /// Mutable argument capacity for the successful edge.
557
        thenArgs: *mut [il::Val],
558
        /// Destination when the comparison fails.
559
        elseTarget: BlockId,
560
        /// Mutable argument capacity for the failed edge.
561
        elseArgs: *mut [il::Val],
562
    },
563
    /// Default and case edges for a switch.
564
    Switch {
565
        /// Destination when no case matches.
566
        defaultTarget: BlockId,
567
        /// Mutable argument capacity for the default edge.
568
        defaultArgs: *mut [il::Val],
569
        /// Cases and their mutable argument capacities.
570
        cases: *mut [LowerSwitchCase],
571
    },
572
}
573
574
/// Internal block state during construction.
575
///
576
/// The key invariants:
577
///
578
/// - A block is "open" if it has no terminator; instructions can be added.
579
/// - A block is "sealed" when all predecessor edges are known.
580
/// - Sealing resolves the predecessor arguments for block parameters.
581
///
582
/// This differs from the final [`il::Block`] which is immutable and fully formed.
583
record BlockData {
584
    /// Block label for debugging and IL printing.
585
    label: *[u8],
586
    /// Block parameters for merging values at control flow joins. These
587
    /// receive values from predecessor edges when control flow merges.
588
    params: *mut [il::Param],
589
    /// Variable ids in parameter order. Before sealing, these are the variables
590
    /// whose predecessor arguments must be resolved.
591
    paramVars: *mut [u32],
592
    /// Instructions accumulated so far. The last instruction should eventually
593
    /// be a terminator.
594
    instrs: *mut [il::Instr],
595
    /// Safely owned edge data for the final jump, branch, or switch instruction.
596
    terminator: LowerTerminator,
597
    /// Debug source locations, one per instruction. Only populated when
598
    /// debug info is enabled.
599
    locs: *mut [il::SrcLoc],
600
    /// Predecessor block ids. Used for SSA construction to propagate values
601
    /// from predecessors when a variable is used before being defined locally.
602
    preds: *mut [u32],
603
    /// The current SSA value of each variable in this block. Indexed by variable
604
    /// id. A `nil` means the variable wasn't assigned in this block. Updated by
605
    /// [`defVar`], queried by [`useVarInBlock`].
606
    vars: *mut [?il::Val],
607
    /// Sealing state. Once sealed, all predecessors are known and we can resolve
608
    /// variable uses that need to pull values from predecessors.
609
    sealState: Sealed,
610
    /// Loop nesting depth when this block was created.
611
    loopDepth: u32,
612
}
613
614
/// Block sealing state for SSA construction.
615
///
616
/// A block is "unsealed" while its predecessors are still being discovered.
617
/// Before sealing, `BlockData.paramVars` records each variable that needs a
618
/// block parameter. Once all predecessors are known, the block is sealed and
619
/// those parameters are resolved via [`resolveBlockArgs`].
620
union Sealed: Copy {
621
    /// Block is unsealed; predecessors may still be added.
622
    No,
623
    /// Block is sealed; all predecessors are known.
624
    Yes,
625
}
626
627
///////////////////////////////////
628
// Loop and Control Flow Context //
629
///////////////////////////////////
630
631
/// Context for break/continue statements within a loop.
632
/// Each nested loop pushes a new context onto the loop stack.
633
export record LoopCtx: Copy {
634
    /// Where `break` should transfer control (the loop's exit block).
635
    breakTarget: BlockId,
636
    /// Where `continue` should transfer control.
637
    continueTarget: ?BlockId,
638
}
639
640
/// Logical operator.
641
union LogicalOp: Copy { And, Or }
642
643
/// Control-flow destinations and result for short-circuit evaluation.
644
record LogicalTargets: Copy {
645
    /// Block that skips evaluating `b`.
646
    shortCircuitBlock: BlockId,
647
    /// Block that evaluates `b`.
648
    evalBlock: BlockId,
649
    /// Result when short-circuiting (`0` or `1`).
650
    shortCircuitVal: i64,
651
}
652
653
/// Select both logical edges and their constant result from the operator.
654
fn logicalTargets(op: LogicalOp, thenBlock: BlockId, elseBlock: BlockId) -> LogicalTargets {
655
    match op {
656
        case LogicalOp::And => return LogicalTargets {
657
            shortCircuitBlock: elseBlock, evalBlock: thenBlock, shortCircuitVal: 0,
658
        },
659
        case LogicalOp::Or => return LogicalTargets {
660
            shortCircuitBlock: thenBlock, evalBlock: elseBlock, shortCircuitVal: 1,
661
        },
662
    }
663
}
664
665
/// Iterator state for for-loop lowering.
666
union ForIter: Copy {
667
    /// Range iterator: `for i in 0..n`.
668
    Range {
669
        valVar: Var,
670
        indexVar: ?Var,
671
        endVal: il::Val,
672
        valType: il::Type,
673
        unsigned: bool,
674
    },
675
    /// Collection iterator: `for elem in slice`.
676
    Collection {
677
        valVar: ?Var,
678
        idxVar: Var,
679
        dataReg: il::Reg,
680
        lengthVal: il::Val,
681
        elemType: *resolver::Type,
682
    },
683
}
684
685
//////////////////////////////
686
// Pattern Matching Support //
687
//////////////////////////////
688
689
// Match expressions are lowered by evaluating the subject once, then emitting
690
// a chain of comparison-and-branch sequences for each arm. The algorithm
691
// handles several subject types specially:
692
//
693
// - Optional pointers: compared against `null`.
694
// - Optional aggregates: tag checked then payload extracted.
695
// - Unions: tag compared against variant indices.
696
697
/// Cached information about a match subject. Computed once and reused across
698
/// all arms to avoid redundant lowering and type queries.
699
record MatchSubject: Copy {
700
    /// The lowered subject value.
701
    val: il::Val,
702
    /// Source-level type from the resolver.
703
    type: resolver::Type,
704
    /// IL-level type for code generation.
705
    ilType: il::Type,
706
    /// The type that binding arms should use. For optionals, this is the
707
    /// inner type; for regular values, it's the same as `type`.
708
    bindType: resolver::Type,
709
    /// Classification of how the subject should be compared and destructured.
710
    kind: MatchSubjectKind,
711
    /// How bindings are created: by value, or by reference.
712
    by: resolver::MatchBy,
713
}
714
715
/// Classifies a match subject by how it should be compared and destructured.
716
union MatchSubjectKind: Copy {
717
    /// Regular value: direct equality comparison.
718
    Regular,
719
    /// Optional with null pointer optimization: `?*T`, `?*[T]`.
720
    OptionalPtr,
721
    /// Optional aggregate `?T`: tagged union with payload.
722
    OptionalAggregate,
723
    /// Union type: tag compared against variant indices.
724
    Union(resolver::UnionType),
725
}
726
727
/// Determine the kind of a match subject from its type.
728
fn matchSubjectKind(type: resolver::Type) -> MatchSubjectKind {
729
    if resolver::isOptionalPointer(type) {
730
        return MatchSubjectKind::OptionalPtr;
731
    }
732
    if resolver::isOptionalAggregate(type) {
733
        return MatchSubjectKind::OptionalAggregate;
734
    }
735
    if let info = unionInfoFromType(type) {
736
        return MatchSubjectKind::Union(info);
737
    }
738
    return MatchSubjectKind::Regular;
739
}
740
741
//////////////////////////
742
// Field Access Support //
743
//////////////////////////
744
745
/// Result of resolving a place expression to a memory location.
746
record FieldRef: Copy {
747
    /// Base pointer register (points to the container).
748
    base: il::Reg,
749
    /// Byte offset of the value within the container.
750
    offset: i32,
751
    /// Type of the value held at that location.
752
    fieldType: resolver::Type,
753
}
754
755
/// Result of computing an element pointer for array/slice subscript operations.
756
/// Used by [`lowerElemPtr`] to return both the element address register and
757
/// the element type for subsequent load or address-of operations.
758
record ElemPtrResult: Copy {
759
    /// Register holding the computed element address.
760
    elemReg: il::Reg,
761
    /// Source-level type of the element.
762
    elemType: resolver::Type,
763
}
764
765
/// Result of resolving a slice range to a data pointer and element count.
766
record SliceRangeResult: Copy {
767
    dataReg: il::Reg,
768
    count: il::Val,
769
}
770
771
/////////////////////////////
772
// Function Lowering State //
773
/////////////////////////////
774
775
/// Per-function lowering state. Created fresh for each function and contains
776
/// all the mutable state needed during function body lowering.
777
record FnLowerer: 'arena + 'phase + 'function where 'arena: 'phase, 'phase: 'function {
778
    /// Reference to the module-level lowerer.
779
    low: &'function mut Lowerer 'arena 'phase,
780
    /// Arena for allocations owned by this function.
781
    arena: &'function mut alloc::Arena,
782
    /// Allocator backed by the function arena for the full lowering scope.
783
    allocator: alloc::Allocator,
784
    /// Type signature of the function being lowered.
785
    fnType: *resolver::FnType,
786
    /// Number of SSA variable slots required for each block.
787
    localCount: u32,
788
    /// Function name, used as prefix for generated data symbols.
789
    fnName: *[u8],
790
791
    // ~ SSA variable tracking ~ //
792
793
    /// Metadata (name, type, mutability) for each variable. Indexed by variable
794
    /// id. Doesn't change after declaration. For the SSA value of a variable in
795
    /// a specific block, see [`BlockData::vars`].
796
    vars: Variables 'function,
797
    /// Parameter-to-variable bindings, initialized in the entry block.
798
    params: [FnParamBinding; resolver::MAX_FN_PARAMS],
799
    /// Number of initialized parameter bindings.
800
    paramsLen: u32,
801
802
    // ~ Basic block management ~ //
803
804
    /// Block storage array, indexed by block id.
805
    blockData: *mut [BlockData],
806
    /// The entry block for this function.
807
    entryBlock: ?BlockId,
808
    /// The block currently receiving new instructions.
809
    currentBlock: ?BlockId,
810
811
    // ~ Loop management ~ //
812
813
    /// Loop contexts indexed by nesting depth.
814
    /// The active prefix contains `loopDepth` entries.
815
    loopStack: [LoopCtx; MAX_LOOP_DEPTH],
816
    /// Current nesting depth (index into loopStack).
817
    loopDepth: u32,
818
819
    // ~ Counters ~ //
820
821
    /// Counter for generating unique block labels like `then#0`, `loop#1`, etc.
822
    labelCounter: u32,
823
    /// Counter for generating unique data names within this function.
824
    /// Each literal gets a name like `fnName$literal$N`.
825
    dataCounter: u32,
826
    /// Counter for generating SSA register numbers.
827
    regCounter: u32,
828
    /// When the function returns an aggregate type, the caller passes a hidden
829
    /// pointer as the first parameter. The callee writes the return value into
830
    /// this buffer and returns the pointer.
831
    returnReg: ?il::Reg,
832
    /// Whether the function is a leaf.
833
    isLeaf: bool,
834
835
    // ~ Debug info ~ //
836
837
    /// Current debug source location, set when processing AST nodes.
838
    srcLoc: il::SrcLoc,
839
}
840
841
/////////////////////////////////
842
// Module Lowering Entry Point //
843
/////////////////////////////////
844
845
/// Lower a complete module AST to an IL program.
846
///
847
/// This is the main entry point for the lowering pass. It:
848
/// 1. Counts functions to preallocate the output array.
849
/// 2. Iterates over top-level declarations, lowering each.
850
/// 3. Returns the complete IL program with functions and data section.
851
///
852
/// The resolver must have already processed the AST -- we rely on its type
853
/// annotations, symbol table, and constant evaluations.
854
export unsafe fn lower 'arena (
855
    res: &resolver::Resolver 'arena,
856
    root: *ast::Node,
857
    pkgName: *[u8],
858
    arena: &mut alloc::Arena
859
) -> il::Program throws (LowerError) {
860
    let functionArena = (&mut *arena) as *unsafe mut alloc::Arena;
861
    let resolved: 'phase = &*res where 'arena: 'phase in {
862
        let mut low = lowererState(resolved, pkgName, functionArena,
863
            LowerOptions { debug: false, buildTest: false });
864
        try lowerDecls(&mut low, root, true, functionArena);
865
866
        return finalize(low);
867
    }
868
}
869
870
/////////////////////////////////
871
// Multi-Module Lowering API   //
872
/////////////////////////////////
873
874
/// Create a lowerer for multi-module compilation.
875
/// The resolver and persistent arena must outlive the returned lowerer.
876
export unsafe fn lowerer 'arena 'phase (
877
    res: &'phase resolver::Resolver 'arena,
878
    pkgName: *[u8],
879
    arena: *unsafe mut alloc::Arena,
880
    options: LowerOptions
881
) -> Lowerer 'arena 'phase where 'arena: 'phase {
882
    return lowererState(res, pkgName, arena, options);
883
}
884
885
/// Initialize lowering state with empty owned collections and borrowed metadata.
886
fn lowererState 'arena 'phase (
887
    res: &'phase resolver::Resolver 'arena,
888
    pkgName: *[u8],
889
    arena: *unsafe mut alloc::Arena,
890
    options: LowerOptions
891
) -> Lowerer 'arena 'phase where 'arena: 'phase {
892
    return Lowerer 'arena 'phase {
893
        arena,
894
        resolver: res,
895
        pkgName,
896
        currentMod: nil,
897
        data: &mut [],
898
        packageDataStart: 0,
899
        functions: &mut [],
900
        symbolNames: &mut [],
901
        errTags: &mut [],
902
        errTagCounter: 1,
903
        options,
904
    };
905
}
906
907
/// Lower a module's AST into the lowerer.
908
/// Call this for each module in the package, then use `finalize` to get the program.
909
export unsafe fn lowerModule 'arena 'phase (
910
    low: &mut Lowerer 'arena 'phase,
911
    moduleId: u16,
912
    root: *ast::Node,
913
    isRoot: bool,
914
    functionArena: &mut alloc::Arena
915
) throws (LowerError) where 'arena: 'phase {
916
    set low.currentMod = moduleId;
917
    try lowerDecls(low, root, isRoot, functionArena);
918
}
919
920
/// Start lowering the declarations of one module.
921
export fn moduleCursor(root: *ast::Node, isRoot: bool) -> ModuleCursor throws (LowerError) {
922
    let case ast::NodeValue::Block(block) = root.value else {
923
        throw LowerError::ExpectedBlock(root);
924
    };
925
    return ModuleCursor { declarations: block.statements, next: 0, isRoot, instanceState: nil };
926
}
927
928
/// Lower declarations until one function is ready or the module is complete.
929
/// The caller must finish using a function before reclaiming its arena storage.
930
export unsafe fn lowerNext 'arena 'phase (
931
    low: &mut Lowerer 'arena 'phase,
932
    cursor: &mut ModuleCursor,
933
    functionArena: &mut alloc::Arena
934
) -> ?LoweredFunction throws (LowerError) where 'arena: 'phase {
935
    loop {
936
        match &mut cursor.instanceState {
937
            case nil => {},
938
            state => {
939
                if let function = try lowerNextInstance(low, state, functionArena) {
940
                    return LoweredFunction { function, role: FnRole::Normal };
941
                }
942
            }
943
        }
944
        set cursor.instanceState = nil;
945
        if cursor.next >= cursor.declarations.len {
946
            return nil;
947
        }
948
        let node = cursor.declarations[cursor.next];
949
        set cursor.next += 1;
950
        match node.value {
951
            case ast::NodeValue::FnDecl(decl) => {
952
                if let function = try lowerFnDecl(low, node, decl, functionArena) {
953
                    return LoweredFunction { function, role: lowerFnRole(cursor.isRoot, decl.attrs) };
954
                }
955
            }
956
            case ast::NodeValue::ConstDecl(decl) =>
957
                try lowerDataDecl(low, node, decl.value, true),
958
            case ast::NodeValue::StaticDecl(decl) =>
959
                try lowerDataDecl(low, node, decl.value, false),
960
            case ast::NodeValue::InstanceDecl { traitName, targetType, methods, .. } => {
961
                set cursor.instanceState = try instanceCursor(low, traitName, targetType, methods);
962
            }
963
            case ast::NodeValue::MethodDecl { name, receiverName, sig, body, .. } => {
964
                if let function = try lowerMethodDecl(
965
                    low, node, name, receiverName, sig, body, functionArena
966
                ) {
967
                    return LoweredFunction { function, role: FnRole::Normal };
968
                }
969
            }
970
            else => {},
971
        }
972
    }
973
}
974
975
/// Lower all top-level declarations in a block.
976
unsafe fn lowerDecls 'arena 'phase (
977
    low: &mut Lowerer 'arena 'phase,
978
    root: *ast::Node,
979
    isRoot: bool,
980
    functionArena: &mut alloc::Arena
981
) throws (LowerError) where 'arena: 'phase {
982
    let mut cursor = try moduleCursor(root, isRoot);
983
    while let result = try lowerNext(low, &mut cursor, functionArena) {
984
        let allocator = alloc::arenaAllocator(low.arena);
985
        emitFunction(low, result.function, allocator);
986
    }
987
}
988
989
/// Consume the lowerer and publish its global data as an immutable array.
990
export fn finalize 'arena 'phase (low: Lowerer 'arena 'phase) -> il::Program where 'arena: 'phase {
991
    let case Lowerer 'arena 'phase { data, functions, .. } = low
992
        else panic "expected lowerer";
993
    unsafe {
994
        return il::Program { data, fns: (&functions[..]) as *unsafe [*unsafe il::Fn] };
995
    }
996
}
997
998
/////////////////////////////////
999
// Qualified Name Construction //
1000
/////////////////////////////////
1001
1002
/// Return the qualified path for a module from retained resolver identities.
1003
/// Returns an empty slice if no module is active or its identity is missing.
1004
fn getModulePath 'arena 'phase (self: &Lowerer 'arena 'phase, modId: ?u16) -> *[*[u8]] where 'arena: 'phase {
1005
    let mut id = modId;
1006
    if id == nil {
1007
        set id = self.currentMod;
1008
    }
1009
    let actualId = id else {
1010
        return &[];
1011
    };
1012
    let entry = resolver::moduleFor(self.resolver, actualId) else {
1013
        return &[];
1014
    };
1015
    return module::moduleQualifiedPath(entry);
1016
}
1017
1018
/// Build a qualified name string for a symbol.
1019
/// If `modId` is nil, uses current module.
1020
unsafe fn qualifyName 'arena 'phase (self: &mut Lowerer 'arena 'phase, modId: ?u16, name: *[u8]) -> *[u8] where 'arena: 'phase {
1021
    let path = getModulePath(self, modId);
1022
    if path.len == 0 {
1023
        return name;
1024
    }
1025
    return il::formatQualifiedName(self.arena, path, name);
1026
}
1027
1028
/// Register the emitted name for a function or data symbol.
1029
/// Calls and address expressions use this name across package boundaries.
1030
fn registerSymbolName(names: &mut *mut [SymbolNameEntry], sym: &resolver::Symbol, qualName: *[u8], allocator: alloc::Allocator) {
1031
    names.append(SymbolNameEntry { symbolId: sym.id, qualName }, allocator);
1032
}
1033
1034
/// Look up the emitted name for a function or data symbol.
1035
/// Return `nil` if its declaration has not been lowered.
1036
// TODO: This is kind of dubious as an optimization, if it depends on the order
1037
// in which modules are lowered.
1038
// TODO: Use a hash table here?
1039
fn lookupSymbolName 'arena 'phase (self: &Lowerer 'arena 'phase, symbolId: u32) -> ?*[u8] where 'arena: 'phase {
1040
    for entry in &self.symbolNames[..] {
1041
        if entry.symbolId == symbolId {
1042
            return entry.qualName;
1043
        }
1044
    }
1045
    return nil;
1046
}
1047
1048
/// Set the package context for lowering.
1049
/// Called before lowering each package.
1050
export fn setPackage 'arena 'phase (self: &mut Lowerer 'arena 'phase, pkgName: *[u8]) where 'arena: 'phase {
1051
    set self.pkgName = pkgName;
1052
    set self.currentMod = nil;
1053
    set self.packageDataStart = self.data.len;
1054
}
1055
1056
/// Allocate initialized variable metadata for one function.
1057
fn variableStorage(count: u32, allocator: alloc::Allocator) -> *mut [VarData] {
1058
    let mut entries: *mut [VarData] = &mut [];
1059
    for i in 0..count {
1060
        entries.append(VarData { name: nil, type: il::Type::W64, mutable: false, addressTaken: false }, allocator);
1061
    }
1062
    return entries;
1063
}
1064
1065
/// Create a new function lowerer for a given function type and name.
1066
fn fnLowerer 'arena 'phase 'function (
1067
    self: &'function mut Lowerer 'arena 'phase,
1068
    sourceOffset: u32,
1069
    fnType: *resolver::FnType,
1070
    qualName: *[u8],
1071
    functionArena: &'function mut alloc::Arena,
1072
    variables: &'function mut [VarData],
1073
    allocator: alloc::Allocator
1074
) -> FnLowerer 'arena 'phase 'function where 'arena: 'phase, 'phase: 'function {
1075
    let localCount = variables.len;
1076
    let mut fnLow = FnLowerer 'arena 'phase 'function {
1077
        low: self,
1078
        arena: functionArena,
1079
        allocator,
1080
        fnType,
1081
        localCount,
1082
        fnName: qualName,
1083
        vars: Variables 'function { items: variables, len: 0 },
1084
        params: [FnParamBinding { var: Var(0), reg: il::Reg { n: 0 } }; resolver::MAX_FN_PARAMS],
1085
        paramsLen: 0,
1086
        blockData: &mut [],
1087
        entryBlock: nil,
1088
        currentBlock: nil,
1089
        loopStack: [LoopCtx { breakTarget: BlockId(0), continueTarget: nil }; MAX_LOOP_DEPTH],
1090
        loopDepth: 0,
1091
        labelCounter: 0,
1092
        dataCounter: 0,
1093
        regCounter: 0,
1094
        returnReg: nil,
1095
        isLeaf: true,
1096
        srcLoc: il::SrcLoc { moduleId: 0, offset: 0 },
1097
    };
1098
    if fnLow.low.options.debug {
1099
        let modId = fnLow.low.currentMod else {
1100
            panic "fnLowerer: debug enabled but no current module";
1101
        };
1102
        set fnLow.srcLoc = il::SrcLoc {
1103
            moduleId: modId,
1104
            offset: sourceOffset,
1105
        };
1106
    }
1107
    return fnLow;
1108
}
1109
1110
/// Construct a function descriptor before its body is lowered.
1111
fn functionDescriptor(
1112
    name: *[u8],
1113
    params: *unsafe [il::Param],
1114
    returnType: il::Type,
1115
    attrs: ?ast::Attributes,
1116
    blocks: *unsafe [il::Block],
1117
) -> il::Fn {
1118
    return il::Fn {
1119
        name,
1120
        params,
1121
        returnType,
1122
        isExtern: checkAttr(attrs, ast::Attribute::Extern),
1123
        isLeaf: true,
1124
        blocks,
1125
    };
1126
}
1127
1128
/// Lower a function declaration.
1129
///
1130
/// This sets up the per-function lowering state, processes parameters,
1131
/// then lowers the function body into a CFG of basic blocks.
1132
///
1133
/// For throwing functions, the return type is a result aggregate
1134
/// rather than the declared return type.
1135
unsafe fn lowerFnDecl 'arena 'phase (
1136
    self: &mut Lowerer 'arena 'phase,
1137
    node: *ast::Node,
1138
    decl: ast::FnDecl,
1139
    functionArena: &mut alloc::Arena
1140
) -> ?*unsafe il::Fn throws (LowerError) where 'arena: 'phase {
1141
    if not shouldLowerFn(&decl, self.options.buildTest) {
1142
        return nil;
1143
    }
1144
    let case ast::NodeValue::Ident(name) = decl.name.value else {
1145
        throw LowerError::ExpectedIdentifier;
1146
    };
1147
    let data = resolver::nodeData(self.resolver, node);
1148
    let case resolver::Type::Fn(fnType) = data.ty else {
1149
        throw LowerError::ExpectedFunction;
1150
    };
1151
    let isExtern = checkAttr(decl.attrs, ast::Attribute::Extern);
1152
1153
    // Build qualified function name for multi-module compilation.
1154
    let qualName = qualifyName(self, nil, name);
1155
1156
    // Register function symbol for cross-package call resolution.
1157
    if let sym = resolver::symbolFor(self.resolver, node) {
1158
        registerSymbolName(&mut self.symbolNames, sym, qualName, alloc::arenaAllocator(self.arena));
1159
    }
1160
    let allocator = alloc::arenaAllocator(functionArena);
1161
    let variableSlots = variableStorage(data.localCount, allocator);
1162
    let parent: 'function = &mut *self, arena = &mut *functionArena, variables = &mut variableSlots[..] where 'phase: 'function in {
1163
        let mut fnLow = fnLowerer(parent, node.span.offset, fnType, qualName, arena, variables, allocator);
1164
1165
        // If the function returns an aggregate or is throwing, prepend a hidden
1166
        // return parameter. The caller allocates the buffer and passes it
1167
        // as the first argument; the callee writes the return value into it.
1168
        if requiresReturnParam(fnType) and not isExtern {
1169
            set fnLow.returnReg = nextReg(&mut fnLow);
1170
        }
1171
        let lowParams = try lowerParams(&mut fnLow, *fnType, decl.sig.params, nil);
1172
        let func = try! alloc::alloc(&mut *fnLow.arena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *mut il::Fn;
1173
1174
        // Throwing functions return a result aggregate (word-sized pointer).
1175
        // TODO: The resolver should set an appropriate type that takes into account
1176
        //       the throws list. It shouldn't set the return type to the "success"
1177
        //       value only.
1178
        let returnType = il::Type::W64 if fnType.throwList.len > 0
1179
            else ilType(fnLow.low, *fnType.returnType);
1180
        set *func = functionDescriptor(qualName, lowParams, returnType, decl.attrs, &[]);
1181
        let body = decl.body else {
1182
            // Extern functions have no body.
1183
            assert isExtern;
1184
            return (&*func) as *unsafe il::Fn;
1185
        };
1186
        set func.blocks = try lowerFnBody(&mut fnLow, body);
1187
        set func.isLeaf = fnLow.isLeaf;
1188
1189
        return (&*func) as *unsafe il::Fn;
1190
    }
1191
}
1192
1193
/// Build a qualified name of the form "Type::method".
1194
unsafe fn instanceMethodName 'arena 'phase (self: &mut Lowerer 'arena 'phase, modId: ?u16, typeName: *[u8], methodName: *[u8]) -> *[u8] where 'arena: 'phase {
1195
    // "::" separates the type and method names.
1196
    let path = [typeName];
1197
    let name = il::formatQualifiedName(self.arena, &path[..], methodName);
1198
    return qualifyName(self, modId, name);
1199
}
1200
1201
/// Build a v-table data name of the form "vtable::Type::Trait".
1202
unsafe fn vtableName 'arena 'phase (self: &mut Lowerer 'arena 'phase, modId: ?u16, typeName: *[u8], traitName: *[u8]) -> *[u8] where 'arena: 'phase {
1203
    // "::" separates the namespace, type, and trait names.
1204
    let path = ["vtable", typeName];
1205
    let name = il::formatQualifiedName(self.arena, &path[..], traitName);
1206
    return qualifyName(self, modId, name);
1207
}
1208
1209
/// Lower an instance declaration (`instance Trait for Type { ... }`).
1210
///
1211
/// Each method in the instance block is lowered as a standalone function
1212
/// with a qualified name of the form `Type::method`. A read-only v-table
1213
/// data record is emitted containing pointers to these functions, ordered
1214
/// by the trait's method indices. The v-table is later referenced when
1215
/// constructing trait objects for dynamic dispatch.
1216
unsafe fn instanceCursor 'arena 'phase (
1217
    self: &mut Lowerer 'arena 'phase,
1218
    traitNameNode: *ast::Node,
1219
    targetTypeNode: *ast::Node,
1220
    methods: *[*ast::Node]
1221
) -> InstanceCursor throws (LowerError) where 'arena: 'phase {
1222
    // Look up the trait and type from the resolver.
1223
    let traitSym = resolver::symbolFor(self.resolver, traitNameNode)
1224
        else throw LowerError::MissingSymbol(traitNameNode);
1225
    let case resolver::SymbolData::Trait(traitInfo) = traitSym.data
1226
        else throw LowerError::MissingMetadata;
1227
    let typeSym = resolver::symbolFor(self.resolver, targetTypeNode)
1228
        else throw LowerError::MissingSymbol(targetTypeNode);
1229
1230
    // Lower each instance method as a regular function.
1231
    // Collect qualified names for the v-table. Empty entries are filled
1232
    // later from inherited supertrait methods.
1233
    return InstanceCursor {
1234
        traitInfo, traitName: traitSym.name, typeName: typeSym.name, methods, next: 0,
1235
        methodNames: [""; ast::MAX_TRAIT_METHODS], methodNameSet: [false; ast::MAX_TRAIT_METHODS],
1236
    };
1237
}
1238
1239
/// Lower the next concrete method, or finish the instance's v-table.
1240
unsafe fn lowerNextInstance 'arena 'phase (
1241
    self: &mut Lowerer 'arena 'phase,
1242
    state: &mut InstanceCursor,
1243
    functionArena: &mut alloc::Arena
1244
) -> ?*unsafe il::Fn throws (LowerError) where 'arena: 'phase {
1245
    let traitInfo = state.traitInfo;
1246
    let typeName = state.typeName;
1247
    let tName = state.traitName;
1248
    while state.next < state.methods.len {
1249
        let methodNode = state.methods[state.next];
1250
        set state.next += 1;
1251
        let case ast::NodeValue::MethodDecl {
1252
            name, receiverName, sig, body, ..
1253
        } = methodNode.value else continue;
1254
1255
        let case ast::NodeValue::Ident(mName) = name.value else {
1256
            throw LowerError::ExpectedIdentifier;
1257
        };
1258
        let qualName = instanceMethodName(self, nil, typeName, mName);
1259
        let func = try lowerMethod(self, methodNode, qualName, receiverName, sig, body, functionArena)
1260
            else continue;
1261
1262
        let method = resolver::findTraitMethod(&traitInfo.methods[..], mName)
1263
            else panic "lowerInstanceDecl: method not found in trait";
1264
1265
        set state.methodNames[method.index] = qualName;
1266
        set state.methodNameSet[method.index] = true;
1267
        return func;
1268
    }
1269
1270
    // Fill inherited method slots from supertraits.
1271
    // These methods were already lowered as part of the supertrait instance
1272
    // declarations and use the same `Type::method` qualified name.
1273
    for method, i in traitInfo.methods {
1274
        if not state.methodNameSet[i] {
1275
            set state.methodNames[i] = instanceMethodName(self, nil, typeName, method.name);
1276
        }
1277
    }
1278
1279
    // Create v-table in data section, used for dynamic dispatch.
1280
    let vName = vtableName(self, nil, typeName, tName);
1281
    let allocator = alloc::arenaAllocator(self.arena);
1282
    let data = vtableData(vName, &state.methodNames[..traitInfo.methods.len], allocator);
1283
    self.data.append(data, allocator);
1284
    return nil;
1285
}
1286
1287
/// Construct read-only v-table data in method-index order.
1288
fn vtableData(name: *[u8], methods: &[*[u8]], allocator: alloc::Allocator) -> il::Data {
1289
    let mut builder = dataBuilder(allocator);
1290
    for method in methods {
1291
        dataBuilderPush(&mut builder, il::DataValue {
1292
            item: il::DataItem::Fn(method),
1293
            count: 1,
1294
        });
1295
    }
1296
    let result = dataBuilderFinish(builder);
1297
    return il::Data {
1298
        name,
1299
        size: methods.len * resolver::PTR_SIZE,
1300
        alignment: resolver::PTR_SIZE,
1301
        readOnly: true,
1302
        isZeroInit: false,
1303
        values: result.values,
1304
    };
1305
}
1306
1307
/// Lower a method node into an IL function with the given qualified name.
1308
/// Shared by both instance methods and standalone methods.
1309
unsafe fn lowerMethod 'arena 'phase (
1310
    self: &mut Lowerer 'arena 'phase,
1311
    node: *ast::Node,
1312
    qualName: *[u8],
1313
    receiverName: *ast::Node,
1314
    sig: ast::FnSig,
1315
    body: *ast::Node,
1316
    functionArena: &mut alloc::Arena,
1317
) -> ?*unsafe il::Fn throws (LowerError) where 'arena: 'phase {
1318
    let data = resolver::nodeData(self.resolver, node);
1319
    let case resolver::Type::Fn(fnType) = data.ty else {
1320
        throw LowerError::ExpectedFunction;
1321
    };
1322
    let sym = resolver::symbolFor(self.resolver, node) else throw LowerError::MissingSymbol(node);
1323
    registerSymbolName(&mut self.symbolNames, sym, qualName, alloc::arenaAllocator(self.arena));
1324
1325
    let allocator = alloc::arenaAllocator(functionArena);
1326
    let variableSlots = variableStorage(data.localCount, allocator);
1327
    let parent: 'function = &mut *self, arena = &mut *functionArena, variables = &mut variableSlots[..] where 'phase: 'function in {
1328
        let mut fnLow = fnLowerer(parent, node.span.offset, fnType, qualName, arena, variables, allocator);
1329
        if requiresReturnParam(fnType) {
1330
            set fnLow.returnReg = nextReg(&mut fnLow);
1331
        }
1332
        let lowParams = try lowerParams(&mut fnLow, *fnType, sig.params, receiverName);
1333
        let func = try! alloc::alloc(&mut *fnLow.arena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *mut il::Fn;
1334
1335
        let returnType = il::Type::W64 if fnType.throwList.len > 0
1336
            else ilType(fnLow.low, *fnType.returnType);
1337
        set *func = functionDescriptor(qualName, lowParams, returnType, nil, &[]);
1338
        set func.blocks = try lowerFnBody(&mut fnLow, body);
1339
        set func.isLeaf = fnLow.isLeaf;
1340
1341
        return (&*func) as *unsafe il::Fn;
1342
    }
1343
}
1344
1345
/// Lower a standalone method declaration.
1346
/// Produces a function with qualified name `Type::method`.
1347
unsafe fn lowerMethodDecl 'arena 'phase (
1348
    self: &mut Lowerer 'arena 'phase,
1349
    node: *ast::Node,
1350
    name: *ast::Node,
1351
    receiverName: *ast::Node,
1352
    sig: ast::FnSig,
1353
    body: *ast::Node,
1354
    functionArena: &mut alloc::Arena,
1355
) -> ?*unsafe il::Fn throws (LowerError) where 'arena: 'phase {
1356
    let sym = resolver::symbolFor(self.resolver, node)
1357
        else throw LowerError::MissingSymbol(node);
1358
    let case ast::NodeValue::Ident(mName) = name.value
1359
        else throw LowerError::ExpectedIdentifier;
1360
    let me = resolver::findMethodBySymbol(self.resolver, sym.id)
1361
        else throw LowerError::MissingMetadata;
1362
    let qualName = instanceMethodName(self, nil, me.concreteTypeName, mName);
1363
1364
    return try lowerMethod(self, node, qualName, receiverName, sig, body, functionArena);
1365
}
1366
1367
/// Check if a function should be lowered.
1368
fn shouldLowerFn(decl: &ast::FnDecl, buildTest: bool) -> bool {
1369
    if checkAttr(decl.attrs, ast::Attribute::Test) {
1370
        return buildTest;
1371
    }
1372
    return true;
1373
}
1374
1375
/// Check if a specific attribute is present in the attribute set.
1376
fn checkAttr(attrs: ?ast::Attributes, attr: ast::Attribute) -> bool {
1377
    if let a = attrs {
1378
        return ast::attributesContains(&a, attr);
1379
    }
1380
    return false;
1381
}
1382
1383
/// Create a label with a numeric suffix, eg. `@base0`.
1384
/// This ensures unique labels like `@then0`, `@then1`, etc.
1385
fn labelWithSuffix(base: &[u8], suffix: u32, allocator: alloc::Allocator) -> *[u8] {
1386
    let mut digits: [u8; fmt::U32_STR_LEN] = [0; fmt::U32_STR_LEN];
1387
    let start = fmt::formatU32(suffix, &mut digits[..]);
1388
    let mut buf: *mut [u8] = &mut [];
1389
    for byte in base {
1390
        buf.append(byte, allocator);
1391
    }
1392
    for byte in &digits[start..] {
1393
        buf.append(byte, allocator);
1394
    }
1395
    return buf;
1396
}
1397
1398
/// Generate a unique label by appending the global counter to the base.
1399
fn nextLabel 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, base: *[u8]) -> *[u8] throws (LowerError) where 'arena: 'phase, 'phase: 'function {
1400
    let idx = self.labelCounter;
1401
    set self.labelCounter += 1;
1402
1403
    return labelWithSuffix(base, idx, self.allocator);
1404
}
1405
1406
///////////////////////////////
1407
// Data Section Construction //
1408
///////////////////////////////
1409
1410
// Functions for building the data section from constant/static
1411
// declarations and inline literals.
1412
1413
/// Convert a constant integer payload to a signed 64-bit value.
1414
fn constIntToI64(intVal: resolver::ConstInt) -> i64 {
1415
    return (0 - intVal.magnitude as i64) if intVal.negative else intVal.magnitude as i64;
1416
}
1417
1418
/// Convert a scalar constant value to an i64.
1419
/// String constants are not handled here and should be checked before calling.
1420
/// Panics if a non-scalar value is passed.
1421
fn constToScalar(val: resolver::ConstValue) -> i64 {
1422
    match val {
1423
        case resolver::ConstValue::Bool(b) => return 1 if b else 0,
1424
        case resolver::ConstValue::Char(c) => return c as i64,
1425
        case resolver::ConstValue::Int(i) => return constIntToI64(i),
1426
        else => panic,
1427
    }
1428
}
1429
1430
/// Convert a constant value to an IL value.
1431
unsafe fn constValueToVal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, val: resolver::ConstValue, node: *ast::Node) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
1432
    if let case resolver::ConstValue::String(s) = val {
1433
        return try lowerStringLit(self, node, s);
1434
    }
1435
    return il::Val::Imm(constToScalar(val));
1436
}
1437
1438
/// Convert a resolver constant value to an IL data initializer item.
1439
unsafe fn constValueToDataItem 'arena 'phase (self: &mut Lowerer 'arena 'phase, val: resolver::ConstValue, typ: resolver::Type) -> il::DataItem where 'arena: 'phase {
1440
    if let case resolver::ConstValue::String(s) = val {
1441
        return il::DataItem::Str(s);
1442
    }
1443
    // Bool and char are byte-sized; integer uses the declared type.
1444
    let mut irTyp = il::Type::W8;
1445
    if let case resolver::ConstValue::Int(_) = val {
1446
        set irTyp = ilType(self, typ);
1447
    }
1448
    return il::DataItem::Val { typ: irTyp, val: constToScalar(val) };
1449
}
1450
1451
/// Lower scalar-like constant nodes into data values, including fallback handling
1452
/// for void-variant tags and slice string initializers.
1453
unsafe fn lowerConstScalarDataInto 'arena 'phase (
1454
    self: &mut Lowerer 'arena 'phase,
1455
    node: *ast::Node,
1456
    ty: resolver::Type,
1457
    dataPrefix: *[u8],
1458
    b: &mut DataValueBuilder
1459
) throws (LowerError) where 'arena: 'phase {
1460
    let val = resolver::constValueEntry(self.resolver, node) else {
1461
        if let idx = voidVariantIndex(self.resolver, node) {
1462
            dataBuilderPush(b, il::DataValue {
1463
                item: il::DataItem::Val { typ: il::Type::W8, val: idx },
1464
                count: 1
1465
            });
1466
            return;
1467
        }
1468
        throw LowerError::MissingConst(node);
1469
    };
1470
1471
    if let case resolver::ConstValue::String(s) = val {
1472
        if let case resolver::Type::Slice { .. } = ty {
1473
            let strSym = try getOrCreateStringData(self, s, dataPrefix);
1474
            dataSliceHeader(b, strSym, s.len);
1475
            return;
1476
        }
1477
    }
1478
    dataBuilderPush(b, il::DataValue {
1479
        item: constValueToDataItem(self, val, ty),
1480
        count: 1
1481
    });
1482
}
1483
1484
/// Lower a constant or static declaration to the data section.
1485
unsafe fn lowerDataDecl 'arena 'phase (
1486
    self: &mut Lowerer 'arena 'phase,
1487
    node: *ast::Node,
1488
    value: *ast::Node,
1489
    readOnly: bool
1490
) throws (LowerError) where 'arena: 'phase {
1491
    let data = resolver::nodeData(self.resolver, node);
1492
    let sym = resolver::symbolFor(self.resolver, node) else {
1493
        throw LowerError::MissingSymbol(node);
1494
    };
1495
    if data.ty == resolver::Type::Unknown {
1496
        throw LowerError::MissingType(node);
1497
    }
1498
    let layout = resolver::getTypeLayout(data.ty);
1499
    let qualName = lookupSymbolName(self, sym.id) else qualifyName(self, nil, sym.name);
1500
    let mut b = dataBuilder(alloc::arenaAllocator(self.arena));
1501
    try lowerConstDataInto(self, value, data.ty, layout.size, qualName, &mut b);
1502
    let result = dataBuilderFinish(b);
1503
1504
    self.data.append(il::Data {
1505
        name: qualName,
1506
        size: layout.size,
1507
        alignment: layout.alignment,
1508
        readOnly,
1509
        isZeroInit: not readOnly and result.zeroInit,
1510
        values: result.values,
1511
    }, alloc::arenaAllocator(self.arena));
1512
}
1513
1514
/// Emit the in-memory representation of a slice header: `{ ptr, len, cap }`.
1515
fn dataSliceHeader(b: &mut DataValueBuilder, dataSym: *[u8], len: u32) {
1516
    dataBuilderPush(b, il::DataValue {
1517
        item: il::DataItem::Sym(dataSym),
1518
        count: 1
1519
    });
1520
    dataBuilderPush(b, il::DataValue {
1521
        item: il::DataItem::Val {
1522
            typ: il::Type::W32,
1523
            val: len as i64
1524
        },
1525
        count: 1
1526
    });
1527
    dataBuilderPush(b, il::DataValue {
1528
        item: il::DataItem::Val {
1529
            typ: il::Type::W32,
1530
            val: len as i64
1531
        },
1532
        count: 1
1533
    });
1534
}
1535
1536
/// Lower a compile-time `&[...]` expression to a concrete slice header.
1537
unsafe fn lowerConstAddressSliceInto 'arena 'phase (
1538
    self: &mut Lowerer 'arena 'phase,
1539
    addr: ast::AddressOf,
1540
    ty: resolver::Type,
1541
    dataPrefix: *[u8],
1542
    b: &mut DataValueBuilder
1543
) throws (LowerError) where 'arena: 'phase {
1544
    let case resolver::Type::Slice { mutable, .. } = ty
1545
        else throw LowerError::ExpectedSliceOrArray;
1546
    let targetTy = resolver::typeFor(self.resolver, addr.target)
1547
        else throw LowerError::MissingType(addr.target);
1548
    let case resolver::Type::Array(arrInfo) = targetTy
1549
        else throw LowerError::ExpectedArray;
1550
1551
    let mut nested = dataBuilder(alloc::arenaAllocator(self.arena));
1552
    let layout = resolver::getTypeLayout(targetTy);
1553
    try lowerConstDataInto(self, addr.target, targetTy, layout.size, dataPrefix, &mut nested);
1554
1555
    let backing = dataBuilderFinish(nested);
1556
    let readOnly = not mutable;
1557
    let mut found: ?*[u8] = nil;
1558
    if readOnly {
1559
        set found = findConstData(self, backing.values, layout.alignment);
1560
    }
1561
    let dataName = found else try pushDeclData(
1562
        self, layout.size, layout.alignment, readOnly, backing.values, dataPrefix
1563
    );
1564
    dataSliceHeader(b, dataName, arrInfo.length);
1565
}
1566
1567
/// Lower a constant expression payload into a builder without slot padding.
1568
/// Compute the type layout when nil or undefined data needs a byte count.
1569
unsafe fn lowerConstDataPayloadInto 'arena 'phase (
1570
    self: &mut Lowerer 'arena 'phase,
1571
    node: *ast::Node,
1572
    ty: resolver::Type,
1573
    dataPrefix: *[u8],
1574
    b: &mut DataValueBuilder
1575
) throws (LowerError) where 'arena: 'phase {
1576
    // Optional coercions define the tag and payload layout of constant values.
1577
    if let case resolver::Type::Optional(inner) = ty {
1578
        if let coercion = resolver::coercionFor(self.resolver, node) {
1579
            if let case resolver::Coercion::OptionalLift(target) = coercion {
1580
                if resolver::typesEqual(target, ty) {
1581
                    if resolver::isNullableType(*inner) {
1582
                        try lowerConstDataPayloadInto(self, node, *inner, dataPrefix, b);
1583
                    } else {
1584
                        let layout = resolver::getTypeLayout(ty);
1585
                        let offset = resolver::getOptionalValOffset(resolver::getTypeLayout(*inner));
1586
                        dataBuilderPush(b, il::DataValue {
1587
                            item: il::DataItem::Val { typ: il::Type::W8, val: 1 }, count: 1,
1588
                        });
1589
                        if offset > 1 {
1590
                            dataBuilderPush(b, il::DataValue { item: il::DataItem::Undef, count: offset - 1 });
1591
                        }
1592
                        try lowerConstDataInto(self, node, *inner, layout.size - offset, dataPrefix, b);
1593
                    }
1594
                    return;
1595
                }
1596
            }
1597
        }
1598
    }
1599
    // Function pointer references in constant data.
1600
    if let case resolver::Type::Fn(_) = ty {
1601
        let sym = resolver::symbolFor(self.resolver, node)
1602
            else throw LowerError::MissingSymbol(node);
1603
        let modId = resolver::moduleIdForSymbol(self.resolver, sym);
1604
        let qualName = qualifyName(self, modId, sym.name);
1605
        dataBuilderPush(b, il::DataValue {
1606
            item: il::DataItem::Fn(qualName), count: 1,
1607
        });
1608
        return;
1609
    }
1610
    // In constant data, a void variant of a mixed union still occupies the
1611
    // full tagged-union slot. Emitting only the tag corrupts following fields.
1612
    if let sym = resolver::symbolFor(self.resolver, node) {
1613
        if let case resolver::SymbolData::Variant { type: resolver::Type::Void, .. } = sym.data {
1614
            if let case resolver::Type::Nominal(resolver::NominalType::Union(_)) = ty {
1615
                try lowerConstUnionVariantInto(self, node, sym, ty, &mut [], dataPrefix, b);
1616
                return;
1617
            }
1618
        }
1619
    }
1620
    match node.value {
1621
        case ast::NodeValue::Nil => {
1622
            let case resolver::Type::Optional(_) = ty else throw LowerError::NilInNonOptional;
1623
            let layout = resolver::getTypeLayout(ty);
1624
            dataBuilderPush(b, il::DataValue {
1625
                item: il::DataItem::Val { typ: il::Type::W8, val: 0 }, count: layout.size,
1626
            });
1627
        },
1628
        case ast::NodeValue::Undef => {
1629
            let layout = resolver::getTypeLayout(ty);
1630
            dataBuilderPush(b, il::DataValue {
1631
                item: il::DataItem::Undef,
1632
                count: layout.size
1633
            });
1634
        }
1635
        case ast::NodeValue::ArrayLit(elems) =>
1636
            try lowerConstArrayLitInto(self, elems, ty, dataPrefix, b),
1637
        case ast::NodeValue::ArrayRepeatLit(repeat) =>
1638
            try lowerConstArrayRepeatInto(self, repeat, ty, dataPrefix, b),
1639
        case ast::NodeValue::RecordLit(recLit) =>
1640
            try lowerConstRecordLitInto(self, node, recLit, ty, dataPrefix, b),
1641
        case ast::NodeValue::Call(call) => {
1642
            let calleeSym = resolver::symbolFor(self.resolver, call.callee)
1643
                else throw LowerError::MissingSymbol(call.callee);
1644
            match calleeSym.data {
1645
                case resolver::SymbolData::Variant { .. } =>
1646
                    try lowerConstUnionVariantInto(self, node, calleeSym, ty, call.args, dataPrefix, b),
1647
                case resolver::SymbolData::Type(resolver::NominalType::Record(recInfo)) => {
1648
                    try lowerConstRecordCtorInto(self, call.args, recInfo, dataPrefix, b);
1649
                }
1650
                else => throw LowerError::MissingConst(node)
1651
            }
1652
        }
1653
        case ast::NodeValue::AddressOf(addr) => {
1654
            try lowerConstAddressSliceInto(self, addr, ty, dataPrefix, b);
1655
        }
1656
        case ast::NodeValue::Ident(_) => {
1657
            // Identifier referencing a constant.
1658
            let sym = resolver::symbolFor(self.resolver, node)
1659
                else throw LowerError::MissingSymbol(node);
1660
            let case ast::NodeValue::ConstDecl(decl) = sym.node.value
1661
                else throw LowerError::MissingConst(node);
1662
1663
            try lowerConstDataPayloadInto(self, decl.value, ty, dataPrefix, b);
1664
        },
1665
        case ast::NodeValue::ScopeAccess(_) => {
1666
            let sym = resolver::symbolFor(self.resolver, node)
1667
                else throw LowerError::MissingSymbol(node);
1668
            if let case ast::NodeValue::ConstDecl(decl) = sym.node.value {
1669
                try lowerConstDataPayloadInto(self, decl.value, ty, dataPrefix, b);
1670
            } else {
1671
                try lowerConstScalarDataInto(self, node, ty, dataPrefix, b);
1672
            }
1673
        }
1674
        else => {
1675
            // Scalar values: integers, bools, strings, void union variants, etc.
1676
            try lowerConstScalarDataInto(self, node, ty, dataPrefix, b);
1677
        }
1678
    }
1679
}
1680
1681
/// Lower a constant expression into a builder, padding to the given slot size.
1682
unsafe fn lowerConstDataInto 'arena 'phase (
1683
    self: &mut Lowerer 'arena 'phase,
1684
    node: *ast::Node,
1685
    ty: resolver::Type,
1686
    slotSize: u32,
1687
    dataPrefix: *[u8],
1688
    b: &mut DataValueBuilder
1689
) throws (LowerError) where 'arena: 'phase {
1690
    let layout = resolver::getTypeLayout(ty);
1691
    try lowerConstDataPayloadInto(self, node, ty, dataPrefix, b);
1692
    // Pad to fill the enclosing slot.
1693
    let padding = slotSize - layout.size;
1694
    if padding > 0 {
1695
        dataBuilderPush(b, il::DataValue { item: il::DataItem::Undef, count: padding });
1696
    }
1697
}
1698
1699
/// Flatten a constant array literal `[a, b, c]` into a builder.
1700
/// Each element payload fills its type size; no extra slot padding is needed.
1701
unsafe fn lowerConstArrayLitInto 'arena 'phase (
1702
    self: &mut Lowerer 'arena 'phase,
1703
    elems: *[*ast::Node],
1704
    ty: resolver::Type,
1705
    dataPrefix: *[u8],
1706
    b: &mut DataValueBuilder
1707
) throws (LowerError) where 'arena: 'phase {
1708
    let case resolver::Type::Array(arrInfo) = ty
1709
        else throw LowerError::ExpectedArray;
1710
    let elemTy = *arrInfo.item;
1711
1712
    for elem in elems {
1713
        try lowerConstDataPayloadInto(self, elem, elemTy, dataPrefix, b);
1714
    }
1715
}
1716
1717
/// Build data values for a constant array repeat literal `[item; count]`.
1718
/// Repeat element payloads without extra slot padding. Undefined data uses
1719
/// the element layout to compute the total byte count.
1720
unsafe fn lowerConstArrayRepeatInto 'arena 'phase (
1721
    self: &mut Lowerer 'arena 'phase,
1722
    repeat: ast::ArrayRepeatLit,
1723
    ty: resolver::Type,
1724
    dataPrefix: *[u8],
1725
    b: &mut DataValueBuilder
1726
) throws (LowerError) where 'arena: 'phase {
1727
    let case resolver::Type::Array(arrInfo) = ty
1728
        else throw LowerError::ExpectedArray;
1729
    let length = arrInfo.length;
1730
    let elemTy = *arrInfo.item;
1731
1732
    if let case resolver::Type::Optional(_) = elemTy {
1733
        for _ in 0..length {
1734
            try lowerConstDataPayloadInto(self, repeat.item, elemTy, dataPrefix, b);
1735
        }
1736
        return;
1737
    }
1738
1739
    if let case ast::NodeValue::Undef = repeat.item.value {
1740
        let elemLayout = resolver::getTypeLayout(elemTy);
1741
        dataBuilderPush(b, il::DataValue {
1742
            item: il::DataItem::Undef,
1743
            count: elemLayout.size * length
1744
        });
1745
    } else if let val = resolver::constValueEntry(self.resolver, repeat.item) {
1746
        if let case resolver::ConstValue::String(_) = val {
1747
            // A string used as a slice is represented by a three-word slice
1748
            // header, not by the bytes of the string itself.
1749
            for _ in 0..length {
1750
                try lowerConstDataPayloadInto(self, repeat.item, elemTy, dataPrefix, b);
1751
            }
1752
        } else {
1753
            dataBuilderPush(b, il::DataValue {
1754
                item: constValueToDataItem(self, val, elemTy),
1755
                count: length
1756
            });
1757
        }
1758
    } else {
1759
        for _ in 0..length {
1760
            try lowerConstDataPayloadInto(self, repeat.item, elemTy, dataPrefix, b);
1761
        }
1762
    }
1763
}
1764
1765
/// Build data values for a constant record literal.
1766
/// Each field is lowered with a slot size that includes trailing padding.
1767
unsafe fn lowerConstRecordLitInto 'arena 'phase (
1768
    self: &mut Lowerer 'arena 'phase,
1769
    node: *ast::Node,
1770
    recLit: ast::RecordLit,
1771
    ty: resolver::Type,
1772
    dataPrefix: *[u8],
1773
    b: &mut DataValueBuilder
1774
) throws (LowerError) where 'arena: 'phase {
1775
    match ty {
1776
        case resolver::Type::Nominal(resolver::NominalType::Record(recInfo)) => {
1777
            try lowerConstRecordCtorInto(self, recLit.fields, recInfo, dataPrefix, b);
1778
        }
1779
        case resolver::Type::Nominal(resolver::NominalType::Union(_)) => {
1780
            let typeName = recLit.typeName else {
1781
                throw LowerError::ExpectedVariant;
1782
            };
1783
            let sym = resolver::symbolFor(self.resolver, typeName) else {
1784
                throw LowerError::MissingSymbol(typeName);
1785
            };
1786
            try lowerConstUnionVariantInto(self, node, sym, ty, recLit.fields, dataPrefix, b);
1787
        }
1788
        else => throw LowerError::ExpectedRecord,
1789
    }
1790
}
1791
1792
/// Build data values for record constants.
1793
unsafe fn lowerConstRecordCtorInto 'arena 'phase (
1794
    self: &mut Lowerer 'arena 'phase,
1795
    args: *[*ast::Node],
1796
    recInfo: resolver::RecordType,
1797
    dataPrefix: *[u8],
1798
    b: &mut DataValueBuilder
1799
) throws (LowerError) where 'arena: 'phase {
1800
    let layout = *recInfo.layout;
1801
    for argNode, i in args {
1802
        let mut valueNode = argNode;
1803
        if let case ast::NodeValue::RecordLitField(fieldLit) = argNode.value {
1804
            set valueNode = fieldLit.value;
1805
        }
1806
        let fieldInfo = recInfo.fields[i];
1807
        let fieldOffset = fieldInfo.offset as u32;
1808
1809
        // Slot extends to the next field's offset,
1810
        // or record size for the last field.
1811
        let slotEnd = recInfo.fields[i + 1].offset as u32 if i + 1 < recInfo.fields.len else layout.size;
1812
        let slotSize = slotEnd - fieldOffset;
1813
1814
        try lowerConstDataInto(self, valueNode, fieldInfo.fieldType, slotSize, dataPrefix, b);
1815
    }
1816
}
1817
1818
/// Build data values for a constant union variant value from payload fields/args.
1819
unsafe fn lowerConstUnionVariantInto 'arena 'phase (
1820
    self: &mut Lowerer 'arena 'phase,
1821
    node: *ast::Node,
1822
    variantSym: *unsafe mut resolver::Symbol,
1823
    ty: resolver::Type,
1824
    payloadArgs: *[*ast::Node],
1825
    dataPrefix: *[u8],
1826
    b: &mut DataValueBuilder
1827
) throws (LowerError) where 'arena: 'phase {
1828
    let case resolver::SymbolData::Variant { ordinal, index, .. } = variantSym.data
1829
        else throw LowerError::UnexpectedNodeValue(node);
1830
1831
    let unionInfo = unionInfoFromType(ty) else {
1832
        throw LowerError::MissingMetadata;
1833
    };
1834
    let payloadType = unionInfo.variants[ordinal].valueType;
1835
    let unionLayout = resolver::getTypeLayout(ty);
1836
    let payloadSlotSize = unionLayout.size - unionInfo.valOffset;
1837
1838
    // Tag byte.
1839
    dataBuilderPush(b, il::DataValue {
1840
        item: il::DataItem::Val {
1841
            typ: il::Type::W8,
1842
            val: index as i64
1843
        },
1844
        count: 1
1845
    });
1846
    // Padding between tag and payload.
1847
    if unionInfo.valOffset > 1 {
1848
        dataBuilderPush(b, il::DataValue {
1849
            item: il::DataItem::Undef,
1850
            count: unionInfo.valOffset - 1
1851
        });
1852
    }
1853
    if payloadType == resolver::Type::Void {
1854
        if payloadSlotSize > 0 {
1855
            dataBuilderPush(b, il::DataValue {
1856
                item: il::DataItem::Undef,
1857
                count: payloadSlotSize
1858
            });
1859
        }
1860
        return;
1861
    }
1862
1863
    let case resolver::Type::Nominal(resolver::NominalType::Record(payloadRec)) = payloadType else {
1864
        throw LowerError::ExpectedRecord;
1865
    };
1866
    let payloadLayout = *payloadRec.layout;
1867
    try lowerConstRecordCtorInto(self, payloadArgs, payloadRec, dataPrefix, b);
1868
1869
    // Unused bytes in the union payload slot for smaller variants.
1870
    if payloadSlotSize > payloadLayout.size {
1871
        dataBuilderPush(b, il::DataValue {
1872
            item: il::DataItem::Undef,
1873
            count: payloadSlotSize - payloadLayout.size
1874
        });
1875
    }
1876
}
1877
1878
/// Find an existing string data entry with matching content.
1879
// TODO: Optimize with hash table or remove?
1880
fn findStringData 'arena 'phase (self: &Lowerer 'arena 'phase, s: *[u8]) -> ?*[u8] where 'arena: 'phase {
1881
    for d in &self.data[self.packageDataStart..] {
1882
        if d.values.len == 1 {
1883
            if let case il::DataItem::Str(existing) = d.values[0].item {
1884
                if mem::eq(existing, s) {
1885
                    return d.name;
1886
                }
1887
            }
1888
        }
1889
    }
1890
    return nil;
1891
}
1892
1893
/// Compose a segmented symbol name from a list of path segments.
1894
/// Example: `["func", "nominal", "VALUE"]` -> `func$nominal$VALUE`.
1895
fn buildSegmentedName(segments: &[*[u8]], allocator: alloc::Allocator) -> *[u8] {
1896
    assert segments.len > 0;
1897
    let mut buf: *mut [u8] = &mut [];
1898
    for segment, i in segments {
1899
        for byte in segment {
1900
            buf.append(byte, allocator);
1901
        }
1902
        if i + 1 <> segments.len {
1903
            buf.append('$', allocator);
1904
        }
1905
    }
1906
    return buf;
1907
}
1908
1909
/// Generate a unique name for declaration-local backing data entries.
1910
fn nextDeclDataName(
1911
    prefix: *[u8],
1912
    count: u32,
1913
    namespace: *[u8],
1914
    allocator: alloc::Allocator,
1915
) -> *[u8] {
1916
    let suffix = labelWithSuffix("", count, allocator);
1917
    let segments = [prefix, namespace, suffix];
1918
1919
    return buildSegmentedName(&segments[..], allocator);
1920
}
1921
1922
/// Append a data entry using a function-local literal namespace (`prefix$literal$N`).
1923
unsafe fn pushDeclData 'arena 'phase (
1924
    self: &mut Lowerer 'arena 'phase,
1925
    size: u32,
1926
    alignment: u32,
1927
    readOnly: bool,
1928
    values: *[il::DataValue],
1929
    dataPrefix: *[u8]
1930
) -> *[u8] throws (LowerError) where 'arena: 'phase {
1931
    let dataCount = self.data.len;
1932
    let name = nextDeclDataName(dataPrefix, dataCount, "literal", alloc::arenaAllocator(self.arena));
1933
    self.data.append(il::Data {
1934
        name,
1935
        size,
1936
        alignment,
1937
        readOnly,
1938
        isZeroInit: not readOnly and dataValuesAreZeroInit(values),
1939
        values,
1940
    }, alloc::arenaAllocator(self.arena));
1941
1942
    return name;
1943
}
1944
1945
/// Find or create read-only string data and return its symbol name.
1946
unsafe fn getOrCreateStringData 'arena 'phase (
1947
    self: &mut Lowerer 'arena 'phase,
1948
    s: *[u8],
1949
    dataPrefix: *[u8]
1950
) -> *[u8] throws (LowerError) where 'arena: 'phase {
1951
    if let existing = findStringData(self, s) {
1952
        return existing;
1953
    }
1954
    let mut builder = dataBuilder(alloc::arenaAllocator(self.arena));
1955
    dataBuilderPush(&mut builder, il::DataValue { item: il::DataItem::Str(s), count: 1 });
1956
    let result = dataBuilderFinish(builder);
1957
    return try pushDeclData(self, s.len, 1, true, result.values, dataPrefix);
1958
}
1959
1960
/// Compare two data items for structural equality.
1961
/// Unlike raw byte comparison, this correctly ignores padding bytes in unions.
1962
fn dataItemEq(a: il::DataItem, b: il::DataItem) -> bool {
1963
    match a {
1964
        case il::DataItem::Val { typ: aTyp, val: aVal } =>
1965
            if let case il::DataItem::Val { typ: bTyp, val: bVal } = b {
1966
                return aTyp == bTyp and aVal == bVal;
1967
            } else {
1968
                return false;
1969
            },
1970
        case il::DataItem::Sym(aPtr) =>
1971
            if let case il::DataItem::Sym(bPtr) = b {
1972
                return mem::eq(aPtr, bPtr);
1973
            } else {
1974
                return false;
1975
            },
1976
        case il::DataItem::Fn(aName) =>
1977
            if let case il::DataItem::Fn(bName) = b {
1978
                return mem::eq(aName, bName);
1979
            } else {
1980
                return false;
1981
            },
1982
        case il::DataItem::Str(aStr) =>
1983
            if let case il::DataItem::Str(bStr) = b {
1984
                return mem::eq(aStr, bStr);
1985
            } else {
1986
                return false;
1987
            },
1988
        case il::DataItem::Undef =>
1989
            if let case il::DataItem::Undef = b {
1990
                return true;
1991
            } else {
1992
                return false;
1993
            },
1994
    }
1995
}
1996
1997
/// Compare two data value slices for structural equality.
1998
fn dataValuesEq(a: &[il::DataValue], b: &[il::DataValue]) -> bool {
1999
    if a.len <> b.len {
2000
        return false;
2001
    }
2002
    for i in 0..a.len {
2003
        if a[i].count <> b[i].count {
2004
            return false;
2005
        }
2006
        if not dataItemEq(a[i].item, b[i].item) {
2007
            return false;
2008
        }
2009
    }
2010
    return true;
2011
}
2012
2013
/// Find an existing read-only slice data entry with matching values.
2014
// TODO: Optimize with hash table or remove?
2015
fn findSliceData 'arena 'phase (self: &Lowerer 'arena 'phase, values: *[il::DataValue], alignment: u32) -> ?*[u8] where 'arena: 'phase {
2016
    for d in &self.data[self.packageDataStart..] {
2017
        if d.alignment == alignment and d.readOnly and dataValuesEq(d.values, values) {
2018
            return d.name;
2019
        }
2020
    }
2021
    return nil;
2022
}
2023
2024
/// Find existing constant data entry with matching content.
2025
/// Handles both string data and slice data.
2026
fn findConstData 'arena 'phase (self: &Lowerer 'arena 'phase, values: *[il::DataValue], alignment: u32) -> ?*[u8] where 'arena: 'phase {
2027
    // Fast path for strings.
2028
    if values.len == 1 and alignment == 1 {
2029
        if let case il::DataItem::Str(s) = values[0].item {
2030
            return findStringData(self, s);
2031
        }
2032
    }
2033
    // General case: byte comparison of data values.
2034
    return findSliceData(self, values, alignment);
2035
}
2036
2037
/// Lower constant data to a slice value.
2038
/// Creates or reuses a data section entry, then builds a slice header on the stack.
2039
unsafe fn lowerConstDataAsSlice 'arena 'phase 'function (
2040
    self: &mut FnLowerer 'arena 'phase 'function,
2041
    result: &ConstDataResult,
2042
    alignment: u32,
2043
    readOnly: bool,
2044
    elemTy: *resolver::Type,
2045
    mutable: bool,
2046
    length: u32
2047
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2048
    let values = result.values;
2049
    let elemLayout = resolver::getTypeLayout(*elemTy);
2050
    let size = elemLayout.size * length;
2051
    let mut found: ?*[u8] = nil;
2052
    if readOnly {
2053
        set found = findConstData(self.low, values, alignment);
2054
    }
2055
    let dataName = found else try nextDataName(self);
2056
    if found == nil {
2057
        self.low.data.append(il::Data {
2058
            name: dataName,
2059
            size,
2060
            alignment,
2061
            readOnly,
2062
            isZeroInit: not readOnly and result.zeroInit,
2063
            values,
2064
        }, alloc::arenaAllocator(self.low.arena));
2065
    }
2066
2067
    // Get data address.
2068
    let ptrReg = nextReg(self);
2069
    emit(self, il::Instr::Copy { dst: ptrReg, val: il::Val::DataSym(dataName) });
2070
2071
    return try buildSliceValue(
2072
        self, elemTy, mutable, il::Val::Reg(ptrReg), il::Val::Imm(length as i64), il::Val::Imm(length as i64)
2073
    );
2074
}
2075
2076
/// Generate a unique data name for inline literals, eg. `fnName$literal$N`.
2077
unsafe fn nextDataName 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) -> *[u8] throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2078
    let counter = self.dataCounter;
2079
    set self.dataCounter += 1;
2080
    let fnName = self.fnName;
2081
    return nextDeclDataName(fnName, counter, "literal", alloc::arenaAllocator(self.low.arena));
2082
}
2083
2084
/// Assign a unique function-local data symbol name.
2085
unsafe fn registerLocalDataDeclName 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2086
    let sym = resolver::symbolFor(self.low.resolver, node)
2087
        else throw LowerError::MissingSymbol(node);
2088
2089
    let prefix = self.fnName;
2090
    let segments = [prefix, "nominal", sym.name];
2091
    let name = buildSegmentedName(&segments[..], alloc::arenaAllocator(self.low.arena));
2092
2093
    let qualified = qualifyName(self.low, nil, name);
2094
    registerSymbolName(&mut self.low.symbolNames, sym, qualified, alloc::arenaAllocator(self.low.arena));
2095
}
2096
2097
/// Get the next available SSA register.
2098
fn nextReg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) -> il::Reg where 'arena: 'phase, 'phase: 'function {
2099
    let reg = il::Reg { n: self.regCounter };
2100
    set self.regCounter += 1;
2101
    return reg;
2102
}
2103
2104
/// Look up the resolved type of an AST node, or throw `MissingType`.
2105
fn typeOf 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) -> resolver::Type throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2106
    let ty = resolver::typeFor(self.low.resolver, node)
2107
        else throw LowerError::MissingType(node);
2108
    return ty;
2109
}
2110
2111
/// Look up the symbol for an AST node, or throw `MissingSymbol`.
2112
fn symOf 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) -> *unsafe mut resolver::Symbol throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2113
    let sym = resolver::symbolFor(self.low.resolver, node)
2114
        else throw LowerError::MissingSymbol(node);
2115
    return sym;
2116
}
2117
2118
/// Remove the last block parameter and its associated variable.
2119
/// Used when detecting a trivial phi that can be eliminated.
2120
fn removeLastBlockParam 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId) where 'arena: 'phase, 'phase: 'function {
2121
    let blk = &mut self.blockData[*block];
2122
    if blk.params.len > 0 {
2123
        // TODO: Use `pop`?
2124
        blk.params.delete(blk.params.len - 1);
2125
    }
2126
    if blk.paramVars.len > 0 {
2127
        // TODO: Use `pop`?
2128
        blk.paramVars.delete(blk.paramVars.len - 1);
2129
    }
2130
}
2131
2132
/// Rewrite cached SSA values for a variable across all blocks, and also
2133
/// rewrite any terminator arguments that reference the provisional register.
2134
/// The latter is necessary because recursive SSA resolution may have already
2135
/// patched terminator arguments with the provisional value before it was
2136
/// found to be trivial.
2137
fn rewriteCachedVarValue 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, v: Var, from: il::Val, to: il::Val) where 'arena: 'phase, 'phase: 'function {
2138
    for i in 0..self.blockData.len {
2139
        let blk = &mut self.blockData[i];
2140
        if blk.vars[*v] == from {
2141
            set blk.vars[*v] = to;
2142
        }
2143
        rewriteTerminatorArgs(&mut blk.terminator, from, to);
2144
    }
2145
}
2146
2147
/// Rewrite provisional values in safely owned terminator edge arguments.
2148
fn rewriteTerminatorArgs(
2149
    terminator: &mut LowerTerminator,
2150
    from: il::Val,
2151
    to: il::Val,
2152
) {
2153
    match terminator {
2154
        case LowerTerminator::None => {}
2155
        case LowerTerminator::Jmp { args, .. } => {
2156
            rewriteValInSlice(&mut args[..], from, to);
2157
        }
2158
        case LowerTerminator::Br { thenArgs, elseArgs, .. } => {
2159
            rewriteValInSlice(&mut thenArgs[..], from, to);
2160
            rewriteValInSlice(&mut elseArgs[..], from, to);
2161
        }
2162
        case LowerTerminator::Switch { defaultArgs, cases, .. } => {
2163
            rewriteValInSlice(&mut defaultArgs[..], from, to);
2164
            rewriteSwitchArgs(&mut cases[..], from, to);
2165
        }
2166
    }
2167
}
2168
2169
/// Replace a provisional value in each construction-time switch argument sequence.
2170
fn rewriteSwitchArgs(cases: &mut [LowerSwitchCase], from: il::Val, to: il::Val) {
2171
    for i in 0..cases.len {
2172
        rewriteValInSlice(cases[i].args, from, to);
2173
    }
2174
}
2175
2176
/// Replace all occurrences of `from` with `to` in an args slice.
2177
fn rewriteValInSlice(args: &mut [il::Val], from: il::Val, to: il::Val) {
2178
    for i in 0..args.len {
2179
        if args[i] == from {
2180
            set args[i] = to;
2181
        }
2182
    }
2183
}
2184
2185
////////////////////////////
2186
// Basic Block Management //
2187
////////////////////////////
2188
2189
// Basic blocks are the fundamental unit of the CFG. Each block contains a
2190
// sequence of instructions ending in a terminator (jump, branch, return).
2191
//
2192
// The block management API supports forward references -- you can create a
2193
// block before switching to it and emitting instructions.
2194
// This is essential for control flow where we need to reference target blocks
2195
// before we've built them (e.g., the "else" block when building "then").
2196
//
2197
// Sealing is a key concept for SSA construction: a block is sealed once all
2198
// its predecessor edges are known. Before sealing, we can't resolve variable
2199
// uses that require looking up values from predecessors.
2200
2201
/// Create a new basic block with the given label base.
2202
///
2203
/// The block is initially unsealed (predecessors may be added later) and empty.
2204
/// Returns a [`BlockId`] that can be used for jumps and branches. The block must
2205
/// be switched to via [`switchToBlock`] before instructions can be emitted.
2206
fn createBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, labelBase: *[u8]) -> BlockId throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2207
    let label = try nextLabel(self, labelBase);
2208
    let varCount = self.localCount;
2209
    let allocator = self.allocator;
2210
    let depth = self.loopDepth;
2211
    return appendBlock(&mut self.blockData, label, varCount, depth, allocator);
2212
}
2213
2214
/// Append a complete unsealed block with initialized variable slots.
2215
fn appendBlock(
2216
    blocks: &mut *mut [BlockData],
2217
    label: *[u8],
2218
    varCount: u32,
2219
    loopDepth: u32,
2220
    allocator: alloc::Allocator,
2221
) -> BlockId {
2222
    let id = BlockId(blocks.len);
2223
    let mut vars: *mut [?il::Val] = &mut [];
2224
    for i in 0..varCount {
2225
        vars.append(nil, allocator);
2226
    }
2227
    blocks.append(BlockData {
2228
        label,
2229
        params: &mut [],
2230
        paramVars: &mut [],
2231
        instrs: &mut [],
2232
        terminator: LowerTerminator::None,
2233
        locs: &mut [],
2234
        preds: &mut [],
2235
        vars,
2236
        sealState: Sealed::No,
2237
        loopDepth,
2238
    }, allocator);
2239
2240
    return id;
2241
}
2242
2243
/// Create a new block with a single parameter.
2244
fn createBlockWithParam 'arena 'phase 'function (
2245
    self: &mut FnLowerer 'arena 'phase 'function,
2246
    labelBase: *[u8],
2247
    param: il::Param
2248
) -> BlockId throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2249
    let block = try createBlock(self, labelBase);
2250
    let allocator = self.allocator;
2251
    self.blockData[*block].params.append(param, allocator);
2252
2253
    return block;
2254
}
2255
2256
/// Switch to building a different block.
2257
/// All subsequent `emit` calls will add instructions to this block.
2258
fn switchToBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId) where 'arena: 'phase, 'phase: 'function {
2259
    set self.currentBlock = block;
2260
}
2261
2262
/// Seal a block, indicating all predecessor edges are now known.
2263
///
2264
/// Sealing enables SSA construction to resolve variable uses by looking up
2265
/// values from predecessors and inserting block parameters as needed. It
2266
/// does not prevent instructions from being added to the block.
2267
fn sealBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2268
    let paramCount = markBlockSealed(&mut self.blockData[*block]) else return;
2269
2270
    // Complete each parameter that was created before sealing.
2271
    for paramIdx in 0..paramCount {
2272
        let varId = self.blockData[*block].paramVars[paramIdx];
2273
        try resolveBlockArgs(self, block, Var(varId), paramIdx);
2274
    }
2275
}
2276
2277
/// Mark a block sealed and return its pending parameter count, or nil if already sealed.
2278
fn markBlockSealed(block: &mut BlockData) -> ?u32 {
2279
    let case Sealed::No = block.sealState else {
2280
        return nil; // Already sealed.
2281
    };
2282
    // Keep the current parameter count. Resolution can add more parameters.
2283
    let paramCount = block.paramVars.len;
2284
    set block.sealState = Sealed::Yes;
2285
    return paramCount;
2286
}
2287
2288
/// Seal a block and switch to it.
2289
fn switchToAndSeal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2290
    try sealBlock(self, block);
2291
    switchToBlock(self, block);
2292
}
2293
2294
/// Get the number of predecessors for a block.
2295
fn predecessorCount(blocks: &[BlockData], block: BlockId) -> u32 {
2296
    return blocks[*block].preds.len;
2297
}
2298
2299
/// Get the current block being built.
2300
fn currentBlock 'arena 'phase 'function (self: &FnLowerer 'arena 'phase 'function) -> BlockId where 'arena: 'phase, 'phase: 'function {
2301
    let block = self.currentBlock else {
2302
        panic "currentBlock: no current block";
2303
    };
2304
    return block;
2305
}
2306
2307
//////////////////////////
2308
// Instruction Emission //
2309
//////////////////////////
2310
2311
/// Emit an instruction to the current block.
2312
fn emit 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, instr: il::Instr) where 'arena: 'phase, 'phase: 'function {
2313
    let allocator = self.allocator;
2314
    recordInstruction(self, &instr, allocator);
2315
}
2316
2317
/// Append an instruction and its enabled source location to the current block.
2318
fn recordInstruction 'arena 'phase 'function (
2319
    self: &mut FnLowerer 'arena 'phase 'function,
2320
    instr: &il::Instr,
2321
    allocator: alloc::Allocator
2322
) where 'arena: 'phase, 'phase: 'function {
2323
    let blk = currentBlock(self);
2324
    let debug = self.low.options.debug;
2325
    let srcLoc = self.srcLoc;
2326
    // Track whether this function is a leaf.
2327
    if il::isCall(*instr) {
2328
        set self.isLeaf = false;
2329
    }
2330
    let block = &mut self.blockData[*blk];
2331
    // Record source location alongside instruction when enabled.
2332
    if debug {
2333
        block.locs.append(srcLoc, allocator);
2334
    }
2335
    block.instrs.append(*instr, allocator);
2336
}
2337
2338
/// Record a terminator instruction and retain its mutable edges in safe storage.
2339
fn recordTerminator 'arena 'phase 'function (
2340
    self: &mut FnLowerer 'arena 'phase 'function,
2341
    instr: il::Instr,
2342
    terminator: LowerTerminator,
2343
) where 'arena: 'phase, 'phase: 'function {
2344
    let block = currentBlock(self);
2345
    emit(self, instr);
2346
    set self.blockData[*block].terminator = terminator;
2347
}
2348
2349
/// Emit a jump placeholder while retaining its mutable edge arguments.
2350
fn emitJmpTerminator 'arena 'phase 'function (
2351
    self: &mut FnLowerer 'arena 'phase 'function,
2352
    target: BlockId,
2353
    args: *mut [il::Val],
2354
) where 'arena: 'phase, 'phase: 'function {
2355
    unsafe {
2356
        recordTerminator(
2357
            self,
2358
            il::Instr::Jmp { target: *target, args: &mut [] },
2359
            LowerTerminator::Jmp { target, args },
2360
        );
2361
    }
2362
}
2363
2364
/// Emit a branch placeholder while retaining its mutable edge arguments.
2365
fn emitBrTerminator 'arena 'phase 'function (
2366
    self: &mut FnLowerer 'arena 'phase 'function,
2367
    op: il::CmpOp,
2368
    typ: il::Type,
2369
    a: il::Val,
2370
    b: il::Val,
2371
    thenTarget: BlockId,
2372
    elseTarget: BlockId,
2373
    terminator: LowerTerminator,
2374
) where 'arena: 'phase, 'phase: 'function {
2375
    unsafe {
2376
        recordTerminator(
2377
            self,
2378
            il::Instr::Br {
2379
                op, typ, a, b,
2380
                thenTarget: *thenTarget, thenArgs: &mut [],
2381
                elseTarget: *elseTarget, elseArgs: &mut [],
2382
            },
2383
            terminator,
2384
        );
2385
    }
2386
}
2387
2388
/// Emit a switch placeholder while retaining its mutable edge arguments and cases.
2389
fn emitSwitchTerminator 'arena 'phase 'function (
2390
    self: &mut FnLowerer 'arena 'phase 'function,
2391
    val: il::Val,
2392
    defaultTarget: BlockId,
2393
    terminator: LowerTerminator,
2394
) where 'arena: 'phase, 'phase: 'function {
2395
    unsafe {
2396
        recordTerminator(
2397
            self,
2398
            il::Instr::Switch {
2399
                val,
2400
                defaultTarget: *defaultTarget,
2401
                defaultArgs: &mut [],
2402
                cases: &mut [],
2403
            },
2404
            terminator,
2405
        );
2406
    }
2407
}
2408
2409
/// Emit an unconditional jump to `target`.
2410
fn emitJmp 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2411
    emitJmpTerminator(self, target, &mut []);
2412
    addPredecessor(self, target, currentBlock(self));
2413
}
2414
2415
/// Emit an unconditional jump to `target` with a single argument.
2416
fn emitJmpWithArg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId, arg: il::Val) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2417
    let args = try allocVal(self, arg);
2418
    emitJmpTerminator(self, target, args);
2419
    addPredecessor(self, target, currentBlock(self));
2420
}
2421
2422
/// Emit an unconditional jump to `target` and switch to it.
2423
fn switchAndJumpTo 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2424
    try emitJmp(self, target);
2425
    switchToBlock(self, target);
2426
}
2427
2428
/// Emit a conditional branch based on `cond`.
2429
fn emitBr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, cond: il::Reg, thenBlock: BlockId, elseBlock: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2430
    assert thenBlock <> elseBlock;
2431
    emitBrTerminator(
2432
        self,
2433
        il::CmpOp::Ne,
2434
        il::Type::W32,
2435
        il::Val::Reg(cond),
2436
        il::Val::Imm(0),
2437
        thenBlock, elseBlock,
2438
        LowerTerminator::Br {
2439
            thenTarget: thenBlock, thenArgs: &mut [],
2440
            elseTarget: elseBlock, elseArgs: &mut [],
2441
        },
2442
    );
2443
    addPredecessor(self, thenBlock, currentBlock(self));
2444
    addPredecessor(self, elseBlock, currentBlock(self));
2445
}
2446
2447
/// Emit a compare-and-branch instruction with the given comparison op.
2448
fn emitBrCmp 'arena 'phase 'function (
2449
    self: &mut FnLowerer 'arena 'phase 'function,
2450
    op: il::CmpOp,
2451
    typ: il::Type,
2452
    a: il::Val,
2453
    b: il::Val,
2454
    thenBlock: BlockId,
2455
    elseBlock: BlockId
2456
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2457
    assert thenBlock <> elseBlock;
2458
    emitBrTerminator(
2459
        self,
2460
        op, typ, a, b,
2461
        thenBlock, elseBlock,
2462
        LowerTerminator::Br {
2463
            thenTarget: thenBlock, thenArgs: &mut [],
2464
            elseTarget: elseBlock, elseArgs: &mut [],
2465
        },
2466
    );
2467
    addPredecessor(self, thenBlock, currentBlock(self));
2468
    addPredecessor(self, elseBlock, currentBlock(self));
2469
}
2470
2471
/// Emit a guard that traps with `ebreak` when a comparison is false.
2472
fn emitTrapUnlessCmp 'arena 'phase 'function (
2473
    self: &mut FnLowerer 'arena 'phase 'function,
2474
    op: il::CmpOp,
2475
    typ: il::Type,
2476
    a: il::Val,
2477
    b: il::Val
2478
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2479
    let passBlock = try createBlock(self, "guard#pass");
2480
    let trapBlock = try createBlock(self, "guard#trap");
2481
2482
    try emitBrCmp(self, op, typ, a, b, passBlock, trapBlock);
2483
    try switchToAndSeal(self, trapBlock);
2484
2485
    emit(self, il::Instr::Ebreak);
2486
    emit(self, il::Instr::Unreachable);
2487
2488
    try switchToAndSeal(self, passBlock);
2489
}
2490
2491
/// Return whether two IL values are known `u32` immediates satisfying `a <= b`.
2492
fn isLtEq(a: il::Val, b: il::Val) -> bool {
2493
    if a == b {
2494
        return true;
2495
    }
2496
    if a == il::Val::Imm(0) {
2497
        return true;
2498
    }
2499
    let case il::Val::Imm(aa) = a else {
2500
        return false;
2501
    };
2502
    let case il::Val::Imm(bb) = b else {
2503
        return false;
2504
    };
2505
    if aa < 0 or bb < 0 {
2506
        return false;
2507
    }
2508
    return aa <= bb;
2509
}
2510
2511
/// Emit an `ebreak` when `a < b` holds.
2512
fn emitTrapIfLt 'arena 'phase 'function (
2513
    self: &mut FnLowerer 'arena 'phase 'function,
2514
    typ: il::Type,
2515
    a: il::Val,
2516
    b: il::Val
2517
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2518
    let trapBlock = try createBlock(self, "guard#trap");
2519
    let passBlock = try createBlock(self, "guard#pass");
2520
2521
    try emitBrCmp(self, il::CmpOp::Ult, typ, a, b, trapBlock, passBlock);
2522
    try switchToAndSeal(self, trapBlock);
2523
2524
    emit(self, il::Instr::Ebreak);
2525
    emit(self, il::Instr::Unreachable);
2526
2527
    try switchToAndSeal(self, passBlock);
2528
}
2529
2530
/// Emit a conditional branch. Uses fused compare-and-branch for simple scalar
2531
/// comparisons, falls back to separate comparison plus branch otherwise.
2532
unsafe fn emitCondBranch 'arena 'phase 'function (
2533
    self: &mut FnLowerer 'arena 'phase 'function,
2534
    cond: *ast::Node,
2535
    thenBlock: BlockId,
2536
    elseBlock: BlockId
2537
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2538
    // Try fused compare-and-branch for simple scalar comparisons.
2539
    if let case ast::NodeValue::BinOp(binop) = cond.value {
2540
        let leftTy = try typeOf(self, binop.left);
2541
        let rightTy = try typeOf(self, binop.right);
2542
        if not isAggregateType(leftTy) and not isAggregateType(rightTy) {
2543
            let operandTy = scalarComparisonType(leftTy, rightTy);
2544
            let unsigned = isUnsignedType(operandTy);
2545
            if let op = cmpOpFrom(binop.op, unsigned) {
2546
                let a = try lowerExpr(self, binop.left);
2547
                let b = try lowerExpr(self, binop.right);
2548
                let typ = ilType(self.low, operandTy);
2549
2550
                // Swap operands if needed.
2551
                match binop.op {
2552
                    case ast::BinaryOp::Gt => // `a > b` = `b < a`
2553
                        try emitBrCmp(self, op, typ, b, a, thenBlock, elseBlock),
2554
                    case ast::BinaryOp::Lte => // `a <= b` = `!(b < a)`
2555
                        try emitBrCmp(self, op, typ, b, a, elseBlock, thenBlock),
2556
                    case ast::BinaryOp::Gte => // `a >= b` = `!(a < b)`
2557
                        try emitBrCmp(self, op, typ, a, b, elseBlock, thenBlock),
2558
                    else =>
2559
                        try emitBrCmp(self, op, typ, a, b, thenBlock, elseBlock),
2560
                }
2561
                return;
2562
            }
2563
        }
2564
    }
2565
    // Fallback: evaluate condition and emit boolean branch.
2566
    let condVal = try lowerExpr(self, cond);
2567
    let condReg = emitValToReg(self, condVal);
2568
2569
    try emitBr(self, condReg, thenBlock, elseBlock);
2570
}
2571
2572
/// Emit a 32-bit store instruction at the given offset.
2573
fn emitStoreW32At 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, src: il::Val, dst: il::Reg, offset: i32) where 'arena: 'phase, 'phase: 'function {
2574
    emit(self, il::Instr::Store { typ: il::Type::W32, src, dst, offset });
2575
}
2576
2577
/// Emit a 32-bit load instruction at the given offset.
2578
fn emitLoadW32At 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, dst: il::Reg, src: il::Reg, offset: i32) where 'arena: 'phase, 'phase: 'function {
2579
    emit(self, il::Instr::Load { typ: il::Type::W32, dst, src, offset });
2580
}
2581
2582
/// Emit an 8-bit store instruction at the given offset.
2583
fn emitStoreW8At 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, src: il::Val, dst: il::Reg, offset: i32) where 'arena: 'phase, 'phase: 'function {
2584
    emit(self, il::Instr::Store { typ: il::Type::W8, src, dst, offset });
2585
}
2586
2587
/// Emit an 8-bit load instruction at the given offset.
2588
fn emitLoadW8At 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, dst: il::Reg, src: il::Reg, offset: i32) where 'arena: 'phase, 'phase: 'function {
2589
    emit(self, il::Instr::Load { typ: il::Type::W8, dst, src, offset });
2590
}
2591
2592
/// Emit a 64-bit store instruction at the given offset.
2593
fn emitStoreW64At 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, src: il::Val, dst: il::Reg, offset: i32) where 'arena: 'phase, 'phase: 'function {
2594
    emit(self, il::Instr::Store { typ: il::Type::W64, src, dst, offset });
2595
}
2596
2597
/// Emit a 64-bit load instruction at the given offset.
2598
fn emitLoadW64At 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, dst: il::Reg, src: il::Reg, offset: i32) where 'arena: 'phase, 'phase: 'function {
2599
    emit(self, il::Instr::Load { typ: il::Type::W64, dst, src, offset });
2600
}
2601
2602
/// Load a tag from memory at `src` plus `offset` with the given IL type.
2603
fn loadTag 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, src: il::Reg, offset: i32, tagType: il::Type) -> il::Val where 'arena: 'phase, 'phase: 'function {
2604
    let dst = nextReg(self);
2605
    emit(self, il::Instr::Load { typ: tagType, dst, src, offset });
2606
    return il::Val::Reg(dst);
2607
}
2608
2609
/// Load the data pointer from a slice value.
2610
fn loadSlicePtr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, sliceReg: il::Reg) -> il::Reg where 'arena: 'phase, 'phase: 'function {
2611
    let ptrReg = nextReg(self);
2612
    emitLoadW64At(self, ptrReg, sliceReg, SLICE_PTR_OFFSET);
2613
    return ptrReg;
2614
}
2615
2616
/// Load the length from a slice value.
2617
fn loadSliceLen 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, sliceReg: il::Reg) -> il::Val where 'arena: 'phase, 'phase: 'function {
2618
    let lenReg = nextReg(self);
2619
    emitLoadW32At(self, lenReg, sliceReg, SLICE_LEN_OFFSET);
2620
    return il::Val::Reg(lenReg);
2621
}
2622
2623
/// Return a fixed array length or load the length from a slice value.
2624
fn containerLength 'arena 'phase 'function (
2625
    self: &mut FnLowerer 'arena 'phase 'function,
2626
    base: il::Reg,
2627
    length: ?u32
2628
) -> il::Val where 'arena: 'phase, 'phase: 'function {
2629
    if let len = length { // Array (length is known).
2630
        return il::Val::Imm(len as i64);
2631
    }
2632
    // Slice (length must be loaded).
2633
    return loadSliceLen(self, base);
2634
}
2635
2636
/// Load the capacity from a slice value.
2637
fn loadSliceCap 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, sliceReg: il::Reg) -> il::Val where 'arena: 'phase, 'phase: 'function {
2638
    let capReg = nextReg(self);
2639
    emitLoadW32At(self, capReg, sliceReg, SLICE_CAP_OFFSET);
2640
    return il::Val::Reg(capReg);
2641
}
2642
2643
/// Emit a load instruction for a scalar value at `src` plus `offset`.
2644
/// For reading values that may be aggregates, use `emitRead` instead.
2645
fn emitLoad 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, src: il::Reg, offset: i32, typ: resolver::Type) -> il::Val where 'arena: 'phase, 'phase: 'function {
2646
    let dst = nextReg(self);
2647
    let ilTyp = ilType(self.low, typ);
2648
2649
    if isSignedType(typ) {
2650
        emit(self, il::Instr::Sload { typ: ilTyp, dst, src, offset });
2651
    } else {
2652
        emit(self, il::Instr::Load { typ: ilTyp, dst, src, offset });
2653
    }
2654
    return il::Val::Reg(dst);
2655
}
2656
2657
/// Read a value from memory at `src` plus `offset`. Aggregates are represented
2658
/// as pointers, so we return the address directly. Scalars are loaded via [`emitLoad`].
2659
fn emitRead 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, src: il::Reg, offset: i32, typ: resolver::Type) -> il::Val where 'arena: 'phase, 'phase: 'function {
2660
    if isAggregateType(typ) {
2661
        let ptr = emitPtrOffset(self, src, offset);
2662
        return il::Val::Reg(ptr);
2663
    }
2664
    return emitLoad(self, src, offset, typ);
2665
}
2666
2667
/// Emit a copy instruction that loads a data symbol's address into a register.
2668
unsafe fn emitDataAddr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, sym: *unsafe resolver::Symbol) -> il::Reg where 'arena: 'phase, 'phase: 'function {
2669
    let dst = nextReg(self);
2670
    let modId = resolver::moduleIdForSymbol(self.low.resolver, sym);
2671
    let qualName = lookupSymbolName(self.low, sym.id) else qualifyName(self.low, modId, sym.name);
2672
2673
    emit(self, il::Instr::Copy { dst, val: il::Val::DataSym(qualName) });
2674
2675
    return dst;
2676
}
2677
2678
/// Emit a copy instruction that loads a function's address into a register.
2679
unsafe fn emitFnAddr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, sym: *unsafe resolver::Symbol) -> il::Reg where 'arena: 'phase, 'phase: 'function {
2680
    let dst = nextReg(self);
2681
    let modId = resolver::moduleIdForSymbol(self.low.resolver, sym);
2682
    let qualName = qualifyName(self.low, modId, sym.name);
2683
2684
    emit(self, il::Instr::Copy { dst, val: il::Val::FnAddr(qualName) });
2685
2686
    return dst;
2687
}
2688
2689
/// Emit pattern tests for a single pattern.
2690
unsafe fn emitPatternMatch 'arena 'phase 'function (
2691
    self: &mut FnLowerer 'arena 'phase 'function,
2692
    subject: &MatchSubject,
2693
    pattern: *ast::Node,
2694
    matchBlock: BlockId,
2695
    fallthrough: BlockId
2696
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2697
    // Wildcards always match; array patterns are tested element-by-element
2698
    // during binding, so they also unconditionally enter the match block.
2699
    if isWildcardPattern(pattern) {
2700
        try emitJmp(self, matchBlock);
2701
        return;
2702
    }
2703
    if let case ast::NodeValue::ArrayLit(_) = pattern.value {
2704
        try emitJmp(self, matchBlock);
2705
        return;
2706
    }
2707
    if let case resolver::Type::Nominal(resolver::NominalType::Record(_)) = subject.type {
2708
        match pattern.value {
2709
            case ast::NodeValue::RecordLit(_), ast::NodeValue::Call(_) => {
2710
                try emitJmp(self, matchBlock);
2711
                return;
2712
            }
2713
            else => {
2714
            },
2715
        }
2716
    }
2717
    let isNil = pattern.value == ast::NodeValue::Nil;
2718
2719
    match subject.kind {
2720
        case MatchSubjectKind::OptionalPtr if isNil => {
2721
            // Null pointer optimization: branch on the data pointer being null.
2722
            let nilReg = try optionalNilReg(self, subject.val, subject.type);
2723
            try emitBrCmp(self, il::CmpOp::Eq, il::Type::W64, il::Val::Reg(nilReg), il::Val::Imm(0), matchBlock, fallthrough);
2724
        }
2725
        case MatchSubjectKind::OptionalAggregate => {
2726
            let base = emitValToReg(self, subject.val);
2727
2728
            if isNil { // Optional aggregate: `nil` means tag is zero.
2729
                let tagReg = tvalTagReg(self, base);
2730
                try emitBr(self, tagReg, fallthrough, matchBlock);
2731
            } else {
2732
                // `lowerExpr` applies the resolver's optional-lift coercion,
2733
                // materializing the tagged representation for value patterns.
2734
                let pattVal = try lowerExpr(self, pattern);
2735
                let pattReg = emitValToReg(self, pattVal);
2736
                let eq = try lowerOptionalEq(self, subject.bindType, base, pattReg, 0);
2737
                let eqReg = emitValToReg(self, eq);
2738
2739
                try emitBr(self, eqReg, matchBlock, fallthrough);
2740
            }
2741
        }
2742
        case MatchSubjectKind::Union(unionInfo) => {
2743
            assert not isNil;
2744
2745
            let case resolver::NodeExtra::UnionVariant { tag: variantTag, .. } =
2746
                resolver::nodeData(self.low.resolver, pattern).extra
2747
            else {
2748
                throw LowerError::ExpectedVariant;
2749
            };
2750
            // Void unions are passed by value (the tag itself).
2751
            // Non-void unions are passed by reference (need to load tag).
2752
            // When matching by reference, always load from the pointer.
2753
            if unionInfo.isAllVoid {
2754
                let mut tagVal = subject.val;
2755
                match subject.by {
2756
                    case resolver::MatchBy::Ref(_), resolver::MatchBy::MutRef => {
2757
                        let base = emitValToReg(self, subject.val);
2758
                        set tagVal = loadTag(self, base, 0, il::Type::W8);
2759
                    }
2760
                    case resolver::MatchBy::Value => {}
2761
                }
2762
                try emitBrCmp(self, il::CmpOp::Eq, il::Type::W8, tagVal, il::Val::Imm(variantTag as i64), matchBlock, fallthrough);
2763
            } else {
2764
                let base = emitValToReg(self, subject.val);
2765
                let tagReg = tvalTagReg(self, base);
2766
2767
                try emitBrCmp(self, il::CmpOp::Eq, il::Type::W8, il::Val::Reg(tagReg), il::Val::Imm(variantTag as i64), matchBlock, fallthrough);
2768
            }
2769
        }
2770
        else => { // Value comparison.
2771
            assert not isNil;
2772
            let pattVal = try lowerExpr(self, pattern);
2773
            if isAggregateType(subject.type) {
2774
                // Aggregate types need structural comparison rather than
2775
                // scalar compare.
2776
                let subjectReg = emitValToReg(self, subject.val);
2777
                let pattReg = emitValToReg(self, pattVal);
2778
                let eq = try lowerAggregateEq(self, subject.type, subjectReg, pattReg, 0);
2779
                let eqReg = emitValToReg(self, eq);
2780
2781
                try emitBr(self, eqReg, matchBlock, fallthrough);
2782
            } else {
2783
                try emitBrCmp(self, il::CmpOp::Eq, subject.ilType, subject.val, pattVal, matchBlock, fallthrough);
2784
            }
2785
        }
2786
    }
2787
}
2788
2789
/// Emit branches for multiple patterns. The first pattern that matches
2790
/// causes a jump to the match block. If no patterns match, we jump to the
2791
/// fallthrough block.
2792
unsafe fn emitPatternMatches 'arena 'phase 'function (
2793
    self: &mut FnLowerer 'arena 'phase 'function,
2794
    subject: &MatchSubject,
2795
    patterns: &[*ast::Node],
2796
    matchBlock: BlockId,
2797
    fallthrough: BlockId
2798
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2799
    assert patterns.len > 0;
2800
2801
    for i in 0..(patterns.len - 1) {
2802
        let pattern = patterns[i];
2803
        let nextArm = try createBlock(self, "arm");
2804
        try emitPatternMatch(self, subject, pattern, matchBlock, nextArm);
2805
2806
        // Seal the intermediate arm block: all predecessor edges are known
2807
        // This ensures SSA construction can resolve variable uses through
2808
        // single-predecessor optimization instead of creating unresolved block
2809
        // parameters.
2810
        try switchToAndSeal(self, nextArm);
2811
    }
2812
    // Handle last pattern: go to fallthrough block on failure.
2813
    let last = patterns[patterns.len - 1];
2814
    try emitPatternMatch(self, subject, last, matchBlock, fallthrough);
2815
}
2816
2817
/// Emit a match binding pattern.
2818
/// Binding patterns always match for regular values, but for optionals they
2819
/// check for the presence of a value. Jumps to `valuePresent` on success,
2820
/// `valueAbsent` on failure.
2821
fn emitBindingTest 'arena 'phase 'function (
2822
    self: &mut FnLowerer 'arena 'phase 'function,
2823
    subject: &MatchSubject,
2824
    valuePresent: BlockId,
2825
    valueAbsent: BlockId
2826
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2827
    match subject.kind {
2828
        case MatchSubjectKind::OptionalPtr, MatchSubjectKind::OptionalAggregate => {
2829
            let nilReg = try optionalNilReg(self, subject.val, subject.type);
2830
            try emitBr(self, nilReg, valuePresent, valueAbsent);
2831
        }
2832
        else => {
2833
            // Regular values always match binding patterns unconditionally.
2834
            try emitJmp(self, valuePresent);
2835
        }
2836
    }
2837
}
2838
2839
/// Emit a jump to target if the current block hasn't terminated, then seal the target block.
2840
fn emitJmpAndSeal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2841
    if not blockHasTerminator(&self.blockData[*currentBlock(self)].instrs[..]) {
2842
        try emitJmp(self, target);
2843
    }
2844
    try sealBlock(self, target);
2845
}
2846
2847
/// Check if a block's instruction list ends with a terminator.
2848
fn blockHasTerminator(instrs: &[il::Instr]) -> bool {
2849
    if instrs.len == 0 {
2850
        return false;
2851
    }
2852
    match instrs[instrs.len - 1] {
2853
        case il::Instr::Ret { .. },
2854
             il::Instr::Jmp { .. },
2855
             il::Instr::Br { .. },
2856
             il::Instr::Switch { .. },
2857
             il::Instr::Unreachable =>
2858
            return true,
2859
        else =>
2860
            return false,
2861
    }
2862
}
2863
2864
/// Emit a jump to merge block if the current block hasn't terminated.
2865
///
2866
/// This is used after lowering branches of an if-else, for example. If the
2867
/// branch diverges, the block already has a terminator and no jump is needed.
2868
///
2869
/// This handles cases like:
2870
///
2871
///     if cond {
2872
///         return 1;   // @then diverges, no jump to merge.
2873
///     } else {
2874
///         return 0;   // @else diverges, no jump to merge.
2875
///     }
2876
///
2877
/// In the above example, the merge block stays `nil`, and no code is generated
2878
/// after the `if`. The merge block is created on first use.
2879
fn emitMergeIfUnterminated 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, mergeBlock: &mut ?BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2880
    if not blockHasTerminator(&self.blockData[*currentBlock(self)].instrs[..]) {
2881
        if *mergeBlock == nil {
2882
            set *mergeBlock = try createBlock(self, "merge");
2883
        }
2884
        let target = *mergeBlock else { throw LowerError::MissingTarget; };
2885
        try emitJmp(self, target);
2886
    }
2887
}
2888
2889
//////////////////////////////////
2890
// Control Flow Edge Management //
2891
//////////////////////////////////
2892
2893
/// Add a predecessor edge from `pred` to `target`.
2894
/// Must be called before the target block is sealed. Duplicates are ignored.
2895
fn addPredecessor 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId, pred: BlockId) where 'arena: 'phase, 'phase: 'function {
2896
    let allocator = self.allocator;
2897
    insertPredecessor(&mut self.blockData[..], target, pred, allocator);
2898
}
2899
2900
/// Insert one distinct predecessor into an unsealed block's owned list.
2901
fn insertPredecessor(blocks: &mut [BlockData], target: BlockId, pred: BlockId, allocator: alloc::Allocator) {
2902
    let blk = &mut blocks[*target];
2903
    assert blk.sealState <> Sealed::Yes, "insertPredecessor: adding predecessor to sealed block";
2904
    let preds = &mut blk.preds;
2905
    for i in 0..preds.len {
2906
        if preds[i] == *pred { // Avoid duplicate predecessor entries.
2907
            return;
2908
        }
2909
    }
2910
    preds.append(*pred, allocator);
2911
}
2912
2913
/// Return whether construction-time edge data must be published.
2914
fn hasLowerTerminator(terminator: &LowerTerminator) -> bool {
2915
    match terminator {
2916
        case LowerTerminator::None => return false,
2917
        case LowerTerminator::Jmp { .. },
2918
             LowerTerminator::Br { .. },
2919
             LowerTerminator::Switch { .. } =>
2920
            return true,
2921
    }
2922
}
2923
2924
/// Publish one block's safely owned edge arguments into its final IL terminator.
2925
unsafe fn publishTerminator(
2926
    instr: il::Instr,
2927
    terminator: &mut LowerTerminator,
2928
    allocator: alloc::Allocator,
2929
) -> il::Instr {
2930
    match terminator {
2931
        case LowerTerminator::None => return instr,
2932
        case LowerTerminator::Jmp { args, .. } => {
2933
            let case il::Instr::Jmp { target, .. } = instr
2934
                else panic "publishTerminator: expected jump terminator";
2935
            return il::Instr::Jmp {
2936
                target,
2937
                args: (&mut args[..]) as *unsafe mut [il::Val],
2938
            };
2939
        }
2940
        case LowerTerminator::Br { thenArgs, elseArgs, .. } => {
2941
            let case il::Instr::Br {
2942
                op, typ, a, b, thenTarget, elseTarget, ..
2943
            } = instr
2944
            else panic "publishTerminator: expected branch terminator";
2945
            return il::Instr::Br {
2946
                op, typ, a, b,
2947
                thenTarget,
2948
                thenArgs: (&mut thenArgs[..]) as *unsafe mut [il::Val],
2949
                elseTarget,
2950
                elseArgs: (&mut elseArgs[..]) as *unsafe mut [il::Val],
2951
            };
2952
        }
2953
        case LowerTerminator::Switch { defaultArgs, cases, .. } => {
2954
            let case il::Instr::Switch { val, defaultTarget, .. } = instr
2955
                else panic "publishTerminator: expected switch terminator";
2956
            let mut publishedCases: *mut [il::SwitchCase] = &mut [];
2957
            for i in 0..cases.len {
2958
                let branch = &mut cases[i];
2959
                publishedCases.append(il::SwitchCase {
2960
                    value: branch.value,
2961
                    target: *branch.target,
2962
                    args: (&mut branch.args[..]) as *unsafe mut [il::Val],
2963
                }, allocator);
2964
            }
2965
            return il::Instr::Switch {
2966
                val,
2967
                defaultTarget,
2968
                defaultArgs: (&mut defaultArgs[..]) as *unsafe mut [il::Val],
2969
                cases: (&mut publishedCases[..]) as *unsafe mut [il::SwitchCase],
2970
            };
2971
        }
2972
    }
2973
}
2974
2975
/// Finalize all blocks and return the block array.
2976
unsafe fn finalizeBlocks 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) -> *unsafe [il::Block] throws (LowerError) where 'arena: 'phase, 'phase: 'function {
2977
    let mut blocks: *mut [il::Block] = &mut [];
2978
    let allocator = self.allocator;
2979
2980
    for i in 0..self.blockData.len {
2981
        if hasLowerTerminator(&self.blockData[i].terminator) {
2982
            let instrIdx = self.blockData[i].instrs.len - 1;
2983
            let instr = self.blockData[i].instrs[instrIdx];
2984
            let published = publishTerminator(
2985
                instr,
2986
                &mut self.blockData[i].terminator,
2987
                allocator,
2988
            );
2989
            set self.blockData[i].instrs[instrIdx] = published;
2990
        }
2991
2992
        let data = &mut self.blockData[i];
2993
        blocks.append(il::Block {
2994
            label: data.label,
2995
            params: &data.params[..],
2996
            instrs: (&mut data.instrs[..]) as *unsafe mut [il::Instr],
2997
            locs: &data.locs[..],
2998
            preds: &data.preds[..],
2999
            loopDepth: data.loopDepth,
3000
        }, allocator);
3001
    }
3002
    return (&blocks[..]) as *unsafe [il::Block];
3003
}
3004
3005
/////////////////////
3006
// Loop Management //
3007
/////////////////////
3008
3009
/// Enter a loop context for break/continue handling.
3010
/// `continueBlock` is `nil` when the continue target is created lazily.
3011
fn enterLoop 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, breakBlock: BlockId, continueBlock: ?BlockId) where 'arena: 'phase, 'phase: 'function {
3012
    assert self.loopDepth < self.loopStack.len, "enterLoop: loop depth overflow";
3013
    let slot = &mut self.loopStack[self.loopDepth];
3014
3015
    set slot.breakTarget = breakBlock;
3016
    set slot.continueTarget = continueBlock;
3017
    set self.loopDepth += 1;
3018
}
3019
3020
/// Exit the current loop context.
3021
fn exitLoop 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) where 'arena: 'phase, 'phase: 'function {
3022
    assert self.loopDepth <> 0, "exitLoop: loopDepth is zero";
3023
    set self.loopDepth -= 1;
3024
}
3025
3026
/// Copy the current loop targets.
3027
fn currentLoop 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) -> ?LoopCtx where 'arena: 'phase, 'phase: 'function {
3028
    if self.loopDepth == 0 {
3029
        return nil;
3030
    }
3031
    return self.loopStack[self.loopDepth - 1];
3032
}
3033
3034
/// Get or lazily create the continue target block for the current loop.
3035
fn getOrCreateContinueBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) -> BlockId throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3036
    let ctx = currentLoop(self) else {
3037
        throw LowerError::OutsideOfLoop;
3038
    };
3039
    if let block = ctx.continueTarget {
3040
        return block;
3041
    }
3042
    let block = try createBlock(self, "step");
3043
    set self.loopStack[self.loopDepth - 1].continueTarget = block;
3044
    return block;
3045
}
3046
3047
/// Copy complete values into the lowering arena.
3048
unsafe fn copyVals 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, values: &[il::Val]) -> *unsafe mut [il::Val] throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3049
    let result = valueStorage(values, values.len, alloc::arenaAllocator(self.arena));
3050
    return (&mut result[..]) as *unsafe mut [il::Val];
3051
}
3052
3053
/// Copy a value prefix into owned storage and initialize the suffix to Undef.
3054
fn valueStorage(values: &[il::Val], length: u32, allocator: alloc::Allocator) -> *mut [il::Val] {
3055
    assert length >= values.len, "valueStorage: length is smaller than value prefix";
3056
    let mut result: *mut [il::Val] = &mut [];
3057
    for value in values {
3058
        result.append(value, allocator);
3059
    }
3060
    for i in values.len..length {
3061
        result.append(il::Val::Undef, allocator);
3062
    }
3063
    return result;
3064
}
3065
3066
/// Allocate a single-value slice in the lowering arena.
3067
fn allocVal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, val: il::Val) -> *mut [il::Val] throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3068
    let values = [val];
3069
    return valueStorage(&values[..], values.len, self.allocator);
3070
}
3071
3072
////////////////////////
3073
// SSA Var Management //
3074
////////////////////////
3075
3076
// This section implements SSA construction following "Simple and Efficient
3077
// Construction of Static Single Assignment Form" (Braun et al., 2013). The IL
3078
// uses block parameters (equivalent to phi nodes) that receive values via
3079
// terminator arguments. Block args are resolved eagerly at seal time via
3080
// [`resolveBlockArgs`], matching Braun's on-the-fly approach.
3081
//
3082
// In SSA form, each variable definition creates a unique value. When control
3083
// flow diverges and merges (like after an if-else), a variable might have
3084
// different definitions from different paths.
3085
//
3086
// # What "Value" means
3087
//
3088
// An [`il::Val`] is a compile-time representation of *where* a runtime value
3089
// lives, not the runtime value itself. Values can live in registers, as symbol
3090
// references, or as immediate values, ie. static constants.
3091
//
3092
// When we "find the value" of a variable, we're answering: "Which SSA register
3093
// (or constant) represents this variable at this program point?"
3094
//
3095
// Each source-level variable gets a `Var` handle on declaration. When a
3096
// variable is defined via [`defVar`], its SSA value is recorded in the current
3097
// block's variable mapping. When a variable is used with [`useVar`] or
3098
// [`useVarInBlock`], we either return the local definition or recursively look
3099
// up the value from predecessor blocks. When multiple predecessors define
3100
// different values for a given variable, we insert a block parameter
3101
// (equivalent to a phi node).
3102
//
3103
// The algorithm used by [`useVarInBlock`] handles three cases:
3104
//
3105
// 1. **Local definition exists**: If the variable was assigned in this block,
3106
//    return that value immediately (fast path).
3107
//
3108
// 2. **Sealed block with single predecessor**: If all incoming edges are known
3109
//    and there's exactly one predecessor, recurse to that predecessor. No merge
3110
//    is needed since there's only one path. The result is cached.
3111
//
3112
// 3. **Unsealed block or multiple predecessors**: Create a block parameter to
3113
//    receive the merged value. If sealed, look up each predecessor's value and
3114
//    patch its terminator via [`resolveBlockArgs`]. If unsealed, `paramVars`
3115
//    records the variable in parameter order. [`sealBlock`] resolves these
3116
//    parameters after all incoming edges are known.
3117
//
3118
// Consider this code:
3119
//
3120
//     let mut x = 1;
3121
//     if cond {
3122
//         x = 2;
3123
//     } else {
3124
//         x = f();   // Result in register %r.
3125
//     }
3126
//     print(x);      // Which value?
3127
//
3128
// The Control Flow Graph (CFG) looks like:
3129
//
3130
//         [entry]
3131
//           _|_
3132
//          /   \
3133
//     [then]   [else]
3134
//     x = 2    x = %r
3135
//         \    /
3136
//        [merge]
3137
//         use(x)
3138
//
3139
// At the `print(x)` point, the compiler doesn't know which branch ran (that's
3140
// a runtime decision), but it needs to emit code that works for either case.
3141
// This is where [`useVarInBlock`] is called.
3142
//
3143
// The generated IL looks like this:
3144
//
3145
//     @then
3146
//       jmp @merge(2);           // pass immediate `2`
3147
//     @else
3148
//       jmp @merge(%r);          // pass register `%r`
3149
//     @merge(w32 %m)             // `%m` receives whichever value arrives at runtime
3150
//       call w32 $print(%m);     // `print` is called with runtime value in `%m`
3151
//
3152
// A block is "sealed" when all predecessor edges are known. This is crucial
3153
// because until sealed, we can't know how many paths merge into the block,
3154
// and thus can't create the right number of block parameter arguments.
3155
//
3156
// If a block isn't sealed but we need a variable's value, we still create
3157
// a block parameter, but defer filling in the terminator arguments. When the
3158
// block is later sealed via [`sealBlock`], all incomplete block params are resolved.
3159
3160
/// Declare a new source-level variable and define its initial value.
3161
/// If called before any block exists (e.g., for parameters), the definition is skipped.
3162
fn newVar 'arena 'phase 'function (
3163
    self: &mut FnLowerer 'arena 'phase 'function,
3164
    name: ?*[u8],
3165
    type: il::Type,
3166
    mutable: bool,
3167
    val: il::Val
3168
) -> Var where 'arena: 'phase, 'phase: 'function {
3169
    let id = self.vars.len;
3170
    assert id < self.vars.items.len, "newVar: variable metadata capacity exceeded";
3171
    set self.vars.items[id] = VarData { name, type, mutable, addressTaken: false };
3172
    set self.vars.len += 1;
3173
    let v = Var(id);
3174
    if self.currentBlock <> nil {
3175
        defVar(self, v, val);
3176
    }
3177
    return v;
3178
}
3179
3180
/// Define (write) a variable. Record the SSA value of a variable in the
3181
/// current block. Called when a variable is assigned or initialized (`let`
3182
/// bindings, assignments, loop updates). When [`useVar`] is later called,
3183
/// it will retrieve this value.
3184
fn defVar 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, v: Var, val: il::Val) where 'arena: 'phase, 'phase: 'function {
3185
    assert *v < self.vars.len;
3186
    let block = currentBlock(self);
3187
    set self.blockData[*block].vars[*v] = val;
3188
}
3189
3190
/// Use (read) the current value of a variable in the current block.
3191
/// May insert block parameters if the value must come from predecessors.
3192
fn useVar 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, v: Var) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3193
    return try useVarInBlock(self, currentBlock(self), v);
3194
}
3195
3196
/// Resolve which SSA definition of a variable reaches a use point in a given block.
3197
///
3198
/// Given a variable and a block where it's used, this function finds the
3199
/// correct [`il::Val`] that holds the variable's value at that program point.
3200
/// When control flow merges from multiple predecessors with different
3201
/// definitions, it creates a block parameter to unify them.
3202
fn useVarInBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId, v: Var) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3203
    assert *v < self.vars.len;
3204
3205
    match varSource(&self.blockData[..], block, self.entryBlock, v) {
3206
        case VarSource::Value(val) => return val,
3207
        case VarSource::Predecessor(pred) => {
3208
            let val = try useVarInBlock(self, pred, v);
3209
            set self.blockData[*block].vars[*v] = val; // Cache.
3210
            return val;
3211
        }
3212
        case VarSource::Merge => {
3213
            let allocator = self.allocator;
3214
            return try createBlockParam(self, block, v, allocator);
3215
        },
3216
        case VarSource::Invalid => throw LowerError::InvalidUse,
3217
    }
3218
}
3219
3220
/// Select the cached, predecessor, or merge source while borrowing block state.
3221
fn varSource(blocks: &[BlockData], block: BlockId, entryBlock: ?BlockId, v: Var) -> VarSource {
3222
    let blk = &blocks[*block];
3223
    if let val = blk.vars[*v] {
3224
        return VarSource::Value(val);
3225
    }
3226
    // Entry block cannot have block parameters. If variable isn't defined
3227
    // in entry, we return undefined.
3228
    if block == entryBlock {
3229
        return VarSource::Value(il::Val::Undef);
3230
    }
3231
    if blk.sealState == Sealed::Yes {
3232
        if blk.preds.len == 0 {
3233
            // Variable used in sealed block with no predecessors.
3234
            return VarSource::Invalid;
3235
        }
3236
        // Single predecessor means no merge needed, variable is implicitly
3237
        // available without a block parameter.
3238
        if blk.preds.len == 1 {
3239
            let pred = BlockId(blk.preds[0]);
3240
            if *pred <> *block {
3241
                return VarSource::Predecessor(pred);
3242
            }
3243
        }
3244
    }
3245
    // Multiple predecessors or unsealed block: need a block parameter to merge
3246
    // the control flow paths.
3247
    return VarSource::Merge;
3248
}
3249
3250
/// Look up a variable by name in the current scope.
3251
/// Searches from most recently declared to first, enabling shadowing.
3252
fn lookupVarByName 'function (variables: &Variables 'function, name: *[u8]) -> ?Var {
3253
    let mut id = variables.len;
3254
    while id > 0 {
3255
        set id -= 1;
3256
        if let varName = variables.items[id].name {
3257
            // Names are interned strings, so pointer comparison suffices.
3258
            if varName == name {
3259
                return Var(id);
3260
            }
3261
        }
3262
    }
3263
    return nil;
3264
}
3265
3266
/// Look up a local variable bound to an identifier node.
3267
fn lookupLocalVar 'function (variables: &Variables 'function, node: *ast::Node) -> ?Var {
3268
    let case ast::NodeValue::Ident(name) = node.value else {
3269
        return nil;
3270
    };
3271
    return lookupVarByName(variables, name);
3272
}
3273
3274
/// Save current lexical variable scope depth.
3275
fn enterVarScope 'function (variables: &Variables 'function) -> u32 {
3276
    return variables.len;
3277
}
3278
3279
/// Restore lexical variable scope depth.
3280
fn exitVarScope 'function (variables: &mut Variables 'function, savedVarsLen: u32) {
3281
    assert savedVarsLen <= variables.len, "exitVarScope: invalid saved scope";
3282
    set variables.len = savedVarsLen;
3283
}
3284
3285
/// Get the metadata for a variable.
3286
fn getVar 'function (variables: &Variables 'function, v: Var) -> VarData {
3287
    assert *v < variables.len;
3288
    return variables.items[*v];
3289
}
3290
3291
/// Create a block parameter to merge a variable's value from multiple
3292
/// control flow paths.
3293
///
3294
/// Called when [`useVarInBlock`] cannot find a local definition and the block is
3295
/// unsealed or has multiple predecessors. For example, `x` can be used in `@end`
3296
/// but defined differently in `@then` and `@else`:
3297
///
3298
///     @then
3299
///       jmp @end(1);            // x = 1
3300
///     @else
3301
///       jmp @end(2);            // x = 2
3302
///     @end(w32 %1)              // x = %1, merged from predecessors
3303
///       ret %1;
3304
///
3305
/// Create a register `%1` as a block parameter. In a sealed block, patch each
3306
/// predecessor's jump to pass its value of `x`. Otherwise, defer until sealing.
3307
fn createBlockParam 'arena 'phase 'function (
3308
    self: &mut FnLowerer 'arena 'phase 'function,
3309
    block: BlockId,
3310
    v: Var,
3311
    allocator: alloc::Allocator
3312
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3313
    // Entry block must not have block parameters.
3314
    assert block <> self.entryBlock, "createBlockParam: entry block must not have block parameters";
3315
    // Allocate a register to hold the merged value.
3316
    let reg = nextReg(self);
3317
    let type = getVar(&self.vars, v).type;
3318
3319
    // Create block parameter and add it to the block.
3320
    let param = il::Param { value: reg, type };
3321
    let paramIdx = bindBlockParam(&mut self.blockData[*block], param, v, allocator);
3322
3323
    if self.blockData[*block].sealState == Sealed::Yes {
3324
        // Block sealed: check for trivial phi before committing. If all
3325
        // predecessors provide the same value, we can remove the param we
3326
        // just created and use that value directly.
3327
        if let trivial = try getTrivialPhiVal(self, block, v) {
3328
            let provisional = il::Val::Reg(reg);
3329
            removeLastBlockParam(self, block);
3330
            rewriteCachedVarValue(self, v, provisional, trivial);
3331
            set self.blockData[*block].vars[*v] = trivial;
3332
            return trivial;
3333
        }
3334
        // Non-trivial phi: patch predecessors to pass their values.
3335
        try resolveBlockArgs(self, block, v, paramIdx);
3336
    }
3337
    return il::Val::Reg(reg);
3338
}
3339
3340
/// Register a block parameter and its provisional variable value.
3341
fn bindBlockParam(block: &mut BlockData, param: il::Param, v: Var, allocator: alloc::Allocator) -> u32 {
3342
    let paramIdx = block.paramVars.len;
3343
    block.params.append(param, allocator);
3344
    block.paramVars.append(*v, allocator); // Associate variable with parameter.
3345
3346
    // Record that this variable's value in this block is now the parameter register.
3347
    // This must happen before the predecessor loop to handle self-referential loops.
3348
    set block.vars[*v] = il::Val::Reg(param.value);
3349
    return paramIdx;
3350
}
3351
3352
/// Complete the block parameter at `paramIdx`. Look up the variable's value in
3353
/// each predecessor and patch its terminator with the edge argument.
3354
///
3355
/// This is the block-parameter equivalent of adding operands to a phi-function in
3356
/// traditional SSA. Where a phi-function merges values at the join point:
3357
///
3358
///     x3 = phi(x1, x2)
3359
///
3360
/// This representation avoids the need for phi nodes to reference their
3361
/// predecessor blocks explicitly, since the control flow edges already encode
3362
/// that information.
3363
fn resolveBlockArgs 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId, v: Var, paramIdx: u32) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3364
    let count = predecessorCount(&self.blockData[..], block);
3365
3366
    // For each predecessor, recursively look up the variable's reaching definition
3367
    // in that block, then patch the predecessor's terminator to pass that value
3368
    // as an argument to this block's parameter.
3369
    for index in 0..count {
3370
        let pred = BlockId(self.blockData[*block].preds[index]);
3371
        // This may recursively trigger more block arg resolution if the
3372
        // predecessor also needs to look up the variable from its predecessors.
3373
        let val = try useVarInBlock(self, pred, v);
3374
        assert val <> il::Val::Undef, "createBlockParam: predecessor provides undef value for block parameter";
3375
        patchTerminatorArg(self, pred, block, paramIdx, val);
3376
    }
3377
}
3378
3379
/// Check if a block parameter is trivial, i.e. all predecessors provide
3380
/// the same value. Returns the trivial value if so.
3381
fn getTrivialPhiVal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId, v: Var) -> ?il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3382
    let count = predecessorCount(&self.blockData[..], block);
3383
    // Get the block parameter register.
3384
    let paramReg = self.blockData[*block].vars[*v];
3385
    // Check if all predecessors provide the same value.
3386
    let mut sameVal: ?il::Val = nil;
3387
3388
    for index in 0..count {
3389
        let pred = BlockId(self.blockData[*block].preds[index]);
3390
        let val = try useVarInBlock(self, pred, v);
3391
3392
        // Check if this is a self-reference.
3393
        // This happens in cycles where the loop back-edge passes the phi to
3394
        // itself. We skip self-references when checking for trivial phis.
3395
        if let reg = paramReg {
3396
            if val == reg {
3397
                // Self-reference, skip this predecessor.
3398
            } else if let sv = sameVal {
3399
                if val <> sv {
3400
                    // Multiple different values, not trivial.
3401
                    return nil;
3402
                }
3403
            } else {
3404
                set sameVal = val;
3405
            }
3406
        } else { // No param reg set yet, can't be trivial.
3407
            return nil;
3408
        }
3409
    }
3410
    return sameVal;
3411
}
3412
3413
/// Patch a single terminator argument for a specific edge. This is used during
3414
/// SSA construction to pass variable values along control flow edges.
3415
fn patchTerminatorArg 'arena 'phase 'function (
3416
    self: &mut FnLowerer 'arena 'phase 'function,
3417
    from: BlockId,         // The predecessor block containing the terminator to patch.
3418
    target: BlockId,       // The target block receiving the value.
3419
    paramIdx: u32,         // The index of the block parameter to set.
3420
    val: il::Val           // The value to pass as the argument.
3421
) where 'arena: 'phase, 'phase: 'function {
3422
    let allocator = self.allocator;
3423
    let data = &mut self.blockData[*from];
3424
    patchEdgeArgs(&mut data.terminator, target, paramIdx, val, allocator);
3425
}
3426
3427
/// Patch all construction-time edges that pass a value to the target block.
3428
fn patchEdgeArgs(
3429
    terminator: &mut LowerTerminator,
3430
    target: BlockId,
3431
    paramIdx: u32,
3432
    val: il::Val,
3433
    allocator: alloc::Allocator,
3434
) {
3435
    let capacity = paramIdx + 1;
3436
    match terminator {
3437
        case LowerTerminator::None =>
3438
            panic "patchEdgeArgs: predecessor has no edge terminator",
3439
        case LowerTerminator::Jmp { args, .. } => {
3440
            growArgs(args, capacity, allocator);
3441
            set args[paramIdx] = val;
3442
        }
3443
        case LowerTerminator::Br { thenTarget, thenArgs, elseTarget, elseArgs } => {
3444
            // Both branches may target the same block.
3445
            if *thenTarget == target {
3446
                growArgs(thenArgs, capacity, allocator);
3447
                set thenArgs[paramIdx] = val;
3448
            }
3449
            if *elseTarget == target {
3450
                growArgs(elseArgs, capacity, allocator);
3451
                set elseArgs[paramIdx] = val;
3452
            }
3453
        }
3454
        case LowerTerminator::Switch { defaultTarget, defaultArgs, cases } => {
3455
            if *defaultTarget == target {
3456
                growArgs(defaultArgs, capacity, allocator);
3457
                set defaultArgs[paramIdx] = val;
3458
            }
3459
            patchSwitchArgs(&mut cases[..], target, paramIdx, val, allocator);
3460
        }
3461
    }
3462
}
3463
3464
/// Patch every construction-time switch case targeting the given block.
3465
fn patchSwitchArgs(
3466
    cases: &mut [LowerSwitchCase],
3467
    target: BlockId,
3468
    paramIdx: u32,
3469
    val: il::Val,
3470
    allocator: alloc::Allocator,
3471
) {
3472
    let capacity = paramIdx + 1;
3473
    for i in 0..cases.len {
3474
        let branch = &mut cases[i];
3475
        if branch.target == target {
3476
            growArgs(&mut branch.args, capacity, allocator);
3477
            set branch.args[paramIdx] = val;
3478
        }
3479
    }
3480
}
3481
3482
/// Grow an argument array to hold the requested capacity.
3483
fn growArgs(args: &mut *mut [il::Val], capacity: u32, allocator: alloc::Allocator) {
3484
    if args.len >= capacity {
3485
        return;
3486
    }
3487
    set *args = valueStorage(&args[..], capacity, allocator);
3488
}
3489
3490
/// Select a receiver or explicit parameter name from its AST declaration.
3491
fn paramName(params: &[*ast::Node], receiver: ?*ast::Node, index: u32) -> *[u8] throws (LowerError) {
3492
    let mut position = index;
3493
    if let node = receiver {
3494
        if index == 0 {
3495
            let case ast::NodeValue::Ident(name) = node.value else {
3496
                throw LowerError::ExpectedIdentifier;
3497
            };
3498
            return name;
3499
        }
3500
        set position -= 1;
3501
    }
3502
    let case ast::NodeValue::FnParam(param) = params[position].value else {
3503
        throw LowerError::ExpectedFunctionParam;
3504
    };
3505
    let case ast::NodeValue::Ident(name) = param.name.value else {
3506
        throw LowerError::ExpectedIdentifier;
3507
    };
3508
    return name;
3509
}
3510
3511
/// Lower function parameters. Declares variables for each parameter.
3512
/// When a receiver name is passed, we're handling a trait method.
3513
unsafe fn lowerParams 'arena 'phase 'function (
3514
    self: &mut FnLowerer 'arena 'phase 'function,
3515
    fnType: resolver::FnType,
3516
    astParams: *[*ast::Node],
3517
    receiverName: ?*ast::Node
3518
) -> *unsafe [il::Param] throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3519
    let offset: u32 = 1 if self.returnReg <> nil else 0;
3520
    let totalLen = fnType.paramTypes.len as u32 + offset;
3521
    if totalLen == 0 {
3522
        return &[];
3523
    }
3524
    assert fnType.paramTypes.len as u32 <= resolver::MAX_FN_PARAMS;
3525
3526
    let mut params: *mut [il::Param] = &mut [];
3527
3528
    if let reg = self.returnReg {
3529
        params.append(il::Param { value: reg, type: il::Type::W64 }, alloc::arenaAllocator(self.arena));
3530
    }
3531
    for i in 0..fnType.paramTypes.len as u32 {
3532
        let type = ilType(self.low, *fnType.paramTypes[i]);
3533
        let reg = nextReg(self);
3534
3535
        params.append(il::Param { value: reg, type }, alloc::arenaAllocator(self.arena));
3536
3537
        // Declare the parameter variable. For the receiver, the name comes
3538
        // from the receiver node.
3539
        // For all other parameters, the name comes from the AST params.
3540
        let name = try paramName(&astParams[..], receiverName, i);
3541
        let v = newVar(self, name, type, false, il::Val::Undef);
3542
3543
        set self.params[i] = FnParamBinding { var: v, reg };
3544
        set self.paramsLen += 1;
3545
    }
3546
    return (&params[..]) as *unsafe [il::Param];
3547
}
3548
3549
/// Resolve match subject.
3550
unsafe fn lowerMatchSubject 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, subject: *ast::Node) -> MatchSubject throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3551
    let mut val = try lowerExpr(self, subject);
3552
    let subjectType = try typeOf(self, subject);
3553
    let unwrapped = resolver::unwrapMatchSubject(subjectType);
3554
3555
    // When matching an aggregate by value, copy it to a fresh stack slot so
3556
    // that bindings are independent of the original memory.  Without this,
3557
    // the lowerer returns a pointer into the source and mutations to the
3558
    // source silently corrupt the bound variables.
3559
    if unwrapped.by == resolver::MatchBy::Value and isAggregateType(unwrapped.effectiveTy) {
3560
        set val = try emitStackVal(self, unwrapped.effectiveTy, val);
3561
    }
3562
3563
    let mut bindType = unwrapped.effectiveTy;
3564
    if let case resolver::Type::Optional(inner) = unwrapped.effectiveTy {
3565
        set bindType = *inner;
3566
    }
3567
    let ilType = ilType(self.low, unwrapped.effectiveTy);
3568
    let kind = matchSubjectKind(unwrapped.effectiveTy);
3569
3570
    return MatchSubject { val, type: unwrapped.effectiveTy, ilType, bindType, kind, by: unwrapped.by };
3571
}
3572
3573
/// Check whether a case pattern is an unconditional wildcard.
3574
fn isWildcardPattern(pattern: *ast::Node) -> bool {
3575
    match pattern.value {
3576
        case ast::NodeValue::Placeholder => return true,
3577
        else => return false,
3578
    }
3579
}
3580
3581
/// Check whether an AST node is the `undefined` literal.
3582
fn isUndef(node: *ast::Node) -> bool {
3583
    match node.value {
3584
        case ast::NodeValue::Undef => return true,
3585
        else => return false,
3586
    }
3587
}
3588
3589
/// Load the tag byte from a tagged value aggregate (optionals and unions).
3590
fn tvalTagReg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, base: il::Reg) -> il::Reg where 'arena: 'phase, 'phase: 'function {
3591
    let tagReg = nextReg(self);
3592
    emitLoadW8At(self, tagReg, base, TVAL_TAG_OFFSET);
3593
    return tagReg;
3594
}
3595
3596
/// Load the tag word from a result aggregate.
3597
fn resultTagReg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, base: il::Reg) -> il::Reg where 'arena: 'phase, 'phase: 'function {
3598
    let tagReg = nextReg(self);
3599
    emitLoadW64At(self, tagReg, base, TVAL_TAG_OFFSET);
3600
    return tagReg;
3601
}
3602
3603
/// Get the register to compare against `0` for optional `nil` checking.
3604
/// For null-ptr-optimized types, loads the data pointer, or returns it
3605
/// directly for scalar pointers. For aggregates, returns the tag register.
3606
fn optionalNilReg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, val: il::Val, typ: resolver::Type) -> il::Reg throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3607
    let reg = emitValToReg(self, val);
3608
3609
    match typ {
3610
        case resolver::Type::Optional(resolver::Type::Slice { .. }) => {
3611
            let ptrReg = nextReg(self);
3612
            emitLoadW64At(self, ptrReg, reg, SLICE_PTR_OFFSET);
3613
            return ptrReg;
3614
        }
3615
        case resolver::Type::Optional(resolver::Type::Pointer { .. }) => return reg,
3616
        case resolver::Type::Optional(_) => return tvalTagReg(self, reg),
3617
        else => return reg,
3618
    }
3619
}
3620
3621
/// Lower an optional nil check (`opt == nil` or `opt <> nil`).
3622
unsafe fn lowerNilCheck 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, opt: *ast::Node, isEq: bool) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3623
    let optTy = try typeOf(self, opt);
3624
    // Handle `nil == nil` or `nil <> nil`.
3625
    if optTy == resolver::Type::Nil {
3626
        return il::Val::Imm(1) if isEq else il::Val::Imm(0);
3627
    }
3628
    let val = try lowerExpr(self, opt);
3629
    let cmpReg = try optionalNilReg(self, val, optTy);
3630
3631
    // Null-pointer-optimized types compare a 64-bit pointer against zero.
3632
    // Aggregate optionals compare an 8-bit tag byte against zero.
3633
    let cmpType = il::Type::W64 if resolver::isOptionalPointer(optTy) else il::Type::W8;
3634
3635
    let op = il::BinOp::Eq if isEq else il::BinOp::Ne;
3636
    return emitTypedBinOp(self, op, cmpType, il::Val::Reg(cmpReg), il::Val::Imm(0));
3637
}
3638
3639
/// Load the payload value from a tagged value aggregate at the given offset.
3640
fn tvalPayloadVal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, base: il::Reg, payload: resolver::Type, valOffset: i32) -> il::Val where 'arena: 'phase, 'phase: 'function {
3641
    if payload == resolver::Type::Void {
3642
        return il::Val::Undef;
3643
    }
3644
    return emitRead(self, base, valOffset, payload);
3645
}
3646
3647
/// Select the payload value or address for the declared match mode.
3648
fn tvalPayloadBinding 'arena 'phase 'function (
3649
    self: &mut FnLowerer 'arena 'phase 'function,
3650
    base: il::Reg,
3651
    bindType: resolver::Type,
3652
    matchBy: resolver::MatchBy,
3653
    valOffset: i32
3654
) -> il::Val where 'arena: 'phase, 'phase: 'function {
3655
    match matchBy {
3656
        case resolver::MatchBy::Value =>
3657
            return tvalPayloadVal(self, base, bindType, valOffset),
3658
        case resolver::MatchBy::Ref(_), resolver::MatchBy::MutRef =>
3659
            return il::Val::Reg(emitPtrOffset(self, base, valOffset)),
3660
    }
3661
}
3662
3663
/// Bind a variable to a tagged value's payload.
3664
fn bindPayloadVariable 'arena 'phase 'function (
3665
    self: &mut FnLowerer 'arena 'phase 'function,
3666
    name: *[u8],
3667
    subjectVal: il::Val,
3668
    bindType: resolver::Type,
3669
    matchBy: resolver::MatchBy,
3670
    valOffset: i32,
3671
    mutable: bool
3672
) -> Var throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3673
    let base = emitValToReg(self, subjectVal);
3674
    let payload = tvalPayloadBinding(self, base, bindType, matchBy, valOffset);
3675
    return newVar(self, name, ilType(self.low, bindType), mutable, payload);
3676
}
3677
3678
/// Bind an identifier from a matched subject.
3679
unsafe fn bindMatchVariable 'arena 'phase 'function (
3680
    self: &mut FnLowerer 'arena 'phase 'function,
3681
    subject: &MatchSubject,
3682
    binding: *ast::Node,
3683
    mutable: bool
3684
) -> ?Var throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3685
    // Only bind if the pattern is an identifier.
3686
    let case ast::NodeValue::Ident(name) = binding.value else {
3687
        return nil;
3688
    };
3689
    // For optional aggregates, extract the payload from the tagged value.
3690
    // The tag check already passed, so we know the payload is valid.
3691
    if let case MatchSubjectKind::OptionalAggregate = subject.kind {
3692
        let valOffset = resolver::getOptionalValOffset(resolver::getTypeLayout(subject.bindType)) as i32;
3693
        return try bindPayloadVariable(self, name, subject.val, subject.bindType, subject.by, valOffset, mutable);
3694
    }
3695
    // Declare the variable in the current block's scope.
3696
    return newVar(self, name, ilType(self.low, subject.bindType), mutable, subject.val);
3697
}
3698
3699
/// Bind variables from inside case patterns (union variants, records, slices).
3700
/// `failBlock` is passed when nested patterns may require additional tests
3701
/// that branch on mismatch (e.g. nested union variant tests).
3702
unsafe fn bindPatternVariables 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, subject: &MatchSubject, patterns: &[*ast::Node], failBlock: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3703
    for pattern in patterns {
3704
3705
        // Handle simple variant patterns like `Variant(x)`.
3706
        if let arg = resolver::variantPatternBinding(self.low.resolver, pattern) {
3707
            let case MatchSubjectKind::Union(unionInfo) = subject.kind
3708
                else panic "bindPatternVariables: expected union subject";
3709
            let valOffset = unionInfo.valOffset as i32;
3710
3711
            // Get the actual field type from the variant's record info.
3712
            // This preserves the original data layout type (e.g. `*T`) even when
3713
            // the resolver resolved the pattern against a dereferenced type (`T`).
3714
            let variantExtra = resolver::nodeData(self.low.resolver, pattern).extra;
3715
            let case resolver::NodeExtra::UnionVariant { ordinal, .. } = variantExtra
3716
                else panic "bindPatternVariables: expected variant extra";
3717
            let payloadType = unionInfo.variants[ordinal].valueType;
3718
            let payloadRec = resolver::getRecord(payloadType)
3719
                else panic "bindPatternVariables: expected record payload";
3720
            let fieldType = payloadRec.fields[0].fieldType;
3721
3722
            match arg.value {
3723
                case ast::NodeValue::Ident(name) => {
3724
                    try bindPayloadVariable(self, name, subject.val, fieldType, subject.by, valOffset, false);
3725
                }
3726
                case ast::NodeValue::Placeholder => {}
3727
                else => {
3728
                    // Nested pattern inside a variant call, e.g. `Variant(Inner { x, y })`.
3729
                    let base = emitValToReg(self, subject.val);
3730
                    let payloadBase = emitPtrOffset(self, base, valOffset);
3731
                    let fieldInfo = resolver::RecordField {
3732
                        name: nil,
3733
                        fieldType,
3734
                        offset: 0,
3735
                    };
3736
                    try bindFieldVariable(self, arg, payloadBase, fieldInfo, subject.by, failBlock);
3737
                }
3738
            }
3739
        }
3740
        match pattern.value {
3741
            case ast::NodeValue::Call(call) => {
3742
                if let body = resolver::getRecord(subject.type) {
3743
                    let base = emitValToReg(self, subject.val);
3744
                    for binding, i in call.args {
3745
                        try bindFieldVariable(self, binding, base, body.fields[i], subject.by, failBlock);
3746
                    }
3747
                }
3748
            }
3749
            // Compound variant patterns like `Variant { a, b }`.
3750
            case ast::NodeValue::RecordLit(lit) =>
3751
                try bindRecordPatternFields(self, subject, pattern, lit, failBlock),
3752
            // Array patterns like `[a, b, c]`.
3753
            case ast::NodeValue::ArrayLit(items) =>
3754
                try bindArrayPatternElements(self, subject, items, failBlock),
3755
            // Literals, wildcards, identifiers: no bindings needed.
3756
            else => {},
3757
        }
3758
    }
3759
}
3760
3761
/// Bind variables from an array literal pattern (e.g., `[a, 1, c]`).
3762
/// Each element is either bound as a variable, skipped (placeholder), or
3763
/// tested against the subject element, branching to `failBlock` on mismatch.
3764
unsafe fn bindArrayPatternElements 'arena 'phase 'function (
3765
    self: &mut FnLowerer 'arena 'phase 'function,
3766
    subject: &MatchSubject,
3767
    items: *[*ast::Node],
3768
    failBlock: BlockId
3769
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3770
    let case resolver::Type::Array(arrInfo) = subject.type
3771
        else throw LowerError::ExpectedSliceOrArray;
3772
3773
    let elemTy = *arrInfo.item;
3774
    let elemLayout = resolver::getTypeLayout(elemTy);
3775
    let stride = elemLayout.size as i32;
3776
    let base = emitValToReg(self, subject.val);
3777
3778
    for elem, i in items {
3779
        let fieldInfo = resolver::RecordField {
3780
            name: nil,
3781
            fieldType: elemTy,
3782
            offset: (i as i32) * stride,
3783
        };
3784
        try bindFieldVariable(self, elem, base, fieldInfo, subject.by, failBlock);
3785
    }
3786
}
3787
3788
/// Bind fields from a record pattern.
3789
unsafe fn bindRecordPatternFields 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, subject: &MatchSubject, pattern: *ast::Node, lit: ast::RecordLit, failBlock: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3790
    // No fields to bind (e.g., `{ .. }`).
3791
    if lit.fields.len == 0 {
3792
        return;
3793
    }
3794
    // Optional value patterns were already compared structurally by
3795
    // `emitPatternMatch`; unlike union record patterns, they have no payload
3796
    // bindings to extract here.
3797
    if let case MatchSubjectKind::OptionalAggregate = subject.kind {
3798
        return;
3799
    }
3800
    if let recInfo = resolver::getRecord(subject.type) {
3801
        let base = emitValToReg(self, subject.val);
3802
        try bindNestedRecordFields(self, base, lit, recInfo, subject.by, failBlock);
3803
        return;
3804
    }
3805
    // Get the union type info from the subject.
3806
    let case MatchSubjectKind::Union(unionInfo) = subject.kind
3807
        else panic "bindRecordPatternFields: expected union subject";
3808
3809
    // Get the variant index from the pattern node.
3810
    let case resolver::NodeExtra::UnionVariant { ordinal: variantOrdinal, .. } =
3811
        resolver::nodeData(self.low.resolver, pattern).extra
3812
    else throw LowerError::MissingMetadata;
3813
3814
    // Get the record type from the variant's payload type.
3815
    let payloadType = unionInfo.variants[variantOrdinal].valueType;
3816
    let recInfo = resolver::getRecord(payloadType)
3817
        else throw LowerError::ExpectedRecord;
3818
3819
    // Get the payload base pointer which points to the record within the tagged union.
3820
    let base = emitValToReg(self, subject.val);
3821
    let valOffset = unionInfo.valOffset as i32;
3822
    let payloadBase = emitPtrOffset(self, base, valOffset);
3823
3824
    try bindNestedRecordFields(self, payloadBase, lit, recInfo, subject.by, failBlock);
3825
}
3826
3827
/// Bind a single record field to a pattern variable, with support for nested
3828
/// pattern tests that branch to `failBlock` on mismatch.
3829
unsafe fn bindFieldVariable 'arena 'phase 'function (
3830
    self: &mut FnLowerer 'arena 'phase 'function,
3831
    binding: *ast::Node,
3832
    base: il::Reg,
3833
    fieldInfo: resolver::RecordField,
3834
    matchBy: resolver::MatchBy,
3835
    failBlock: BlockId
3836
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3837
    match binding.value {
3838
        case ast::NodeValue::Ident(name) => {
3839
            let val = emitRead(self, base, fieldInfo.offset, fieldInfo.fieldType)
3840
                if matchBy == resolver::MatchBy::Value
3841
                else il::Val::Reg(emitPtrOffset(self, base, fieldInfo.offset));
3842
            newVar(self, name, ilType(self.low, fieldInfo.fieldType), false, val);
3843
        }
3844
        case ast::NodeValue::Placeholder => {}
3845
        case ast::NodeValue::RecordLit(lit) => {
3846
            // Check if this record literal is a union variant pattern.
3847
            if let keyNode = resolver::patternVariantKeyNode(binding) {
3848
                if let case resolver::NodeExtra::UnionVariant { .. } = resolver::nodeData(self.low.resolver, keyNode).extra {
3849
                    try emitNestedFieldTest(self, binding, base, fieldInfo, matchBy, failBlock);
3850
                    return;
3851
                }
3852
            }
3853
            // Plain nested record destructuring pattern.
3854
            // Auto-deref: if the field is a pointer, load it first.
3855
            let mut derefType = fieldInfo.fieldType;
3856
            let mut nestedBase = emitPtrOffset(self, base, fieldInfo.offset);
3857
            if let case resolver::Type::Pointer { target, .. } = fieldInfo.fieldType {
3858
                let ptrReg = nextReg(self);
3859
                emitLoadW64At(self, ptrReg, nestedBase, 0);
3860
                set nestedBase = ptrReg;
3861
                set derefType = *target;
3862
            }
3863
            let recInfo = resolver::getRecord(derefType)
3864
                else throw LowerError::ExpectedRecord;
3865
3866
            try bindNestedRecordFields(self, nestedBase, lit, recInfo, matchBy, failBlock);
3867
        }
3868
        else => {
3869
            // Nested pattern requiring a test (union variant scope access, literal, etc).
3870
            try emitNestedFieldTest(self, binding, base, fieldInfo, matchBy, failBlock);
3871
        }
3872
    }
3873
}
3874
3875
/// Emit a nested pattern test for a record field value, branching to
3876
/// `failBlock` if the pattern does not match. On success, continues in
3877
/// a fresh block and binds any nested variables.
3878
unsafe fn emitNestedFieldTest 'arena 'phase 'function (
3879
    self: &mut FnLowerer 'arena 'phase 'function,
3880
    pattern: *ast::Node,
3881
    base: il::Reg,
3882
    fieldInfo: resolver::RecordField,
3883
    matchBy: resolver::MatchBy,
3884
    failBlock: BlockId
3885
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3886
    let mut fieldType = fieldInfo.fieldType;
3887
    let fieldPtr = emitPtrOffset(self, base, fieldInfo.offset);
3888
3889
    // Auto-deref: when the field is a pointer and the pattern destructures
3890
    // the pointed-to value, load the pointer and use the target type.
3891
    // The loaded pointer becomes the base address for the nested subject.
3892
    let mut derefBase: ?il::Reg = nil;
3893
    if let case resolver::Type::Pointer { target, .. } = fieldType {
3894
        if resolver::isDestructuringPattern(pattern) {
3895
            let ptrReg = nextReg(self);
3896
            emitLoadW64At(self, ptrReg, fieldPtr, 0);
3897
            set derefBase = ptrReg;
3898
            set fieldType = *target;
3899
        }
3900
    }
3901
    // Build a MatchSubject for the nested field.
3902
    let ilTy = ilType(self.low, fieldType);
3903
    let kind = matchSubjectKind(fieldType);
3904
3905
    // Determine the subject value.
3906
    // Auto-deref: the loaded pointer is the address of the target value.
3907
    let subjectBase = derefBase else fieldPtr;
3908
    // Aggregate: use the pointer.
3909
    // Scalar: load the value.
3910
    let val = il::Val::Reg(subjectBase)
3911
        if derefBase <> nil or isAggregateType(fieldType)
3912
        else emitRead(self, base, fieldInfo.offset, fieldType);
3913
    let nestedSubject = MatchSubject {
3914
        val,
3915
        type: fieldType,
3916
        ilType: ilTy,
3917
        bindType: fieldType,
3918
        kind,
3919
        by: matchBy,
3920
    };
3921
3922
    // Emit the pattern test: on success jump to `continueBlock`, on fail to `failBlock`.
3923
    let continueBlock = try createBlock(self, "nest");
3924
    try emitPatternMatch(self, &nestedSubject, pattern, continueBlock, failBlock);
3925
    try switchToAndSeal(self, continueBlock);
3926
3927
    // After the test succeeds, bind any nested variables.
3928
    let patterns = [pattern];
3929
    try bindPatternVariables(self, &nestedSubject, &patterns[..], failBlock);
3930
}
3931
3932
/// Bind variables from a nested record literal pattern.
3933
unsafe fn bindNestedRecordFields 'arena 'phase 'function (
3934
    self: &mut FnLowerer 'arena 'phase 'function,
3935
    base: il::Reg,
3936
    lit: ast::RecordLit,
3937
    recInfo: resolver::RecordType,
3938
    matchBy: resolver::MatchBy,
3939
    failBlock: BlockId
3940
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3941
    for fieldNode in lit.fields {
3942
        let case ast::NodeValue::RecordLitField(field) = fieldNode.value else {
3943
            throw LowerError::UnexpectedNodeValue(fieldNode);
3944
        };
3945
        let fieldIdx = resolver::recordFieldIndexFor(self.low.resolver, fieldNode)
3946
            else throw LowerError::MissingMetadata;
3947
        if fieldIdx >= recInfo.fields.len {
3948
            throw LowerError::MissingMetadata;
3949
        }
3950
        let fieldInfo = recInfo.fields[fieldIdx];
3951
3952
        try bindFieldVariable(self, field.value, base, fieldInfo, matchBy, failBlock);
3953
    }
3954
}
3955
3956
/// Lower function body to a list of basic blocks.
3957
unsafe fn lowerFnBody 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, body: *ast::Node) -> *unsafe [il::Block] throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3958
    // Create and switch to entry block.
3959
    let entry = try createBlock(self, "entry");
3960
    set self.entryBlock = entry;
3961
    switchToBlock(self, entry);
3962
3963
    /// Bind parameter registers to variables in the entry block.
3964
    for i in 0..self.paramsLen {
3965
        let def = self.params[i];
3966
        defVar(self, def.var, il::Val::Reg(def.reg));
3967
    }
3968
    /// Lower function body.
3969
    try lowerBlock(self, body);
3970
3971
    // Add implicit return if body doesn't diverge.
3972
    if not blockHasTerminator(&self.blockData[*currentBlock(self)].instrs[..]) {
3973
        if *self.fnType.returnType == resolver::Type::Never {
3974
            emit(self, il::Instr::Unreachable);
3975
        } else if self.fnType.throwList.len > 0 {
3976
            if *self.fnType.returnType == resolver::Type::Void {
3977
                // Implicit `void` return in throwing function: wrap in result success.
3978
                let val = try buildResult(self, 0, nil, resolver::Type::Void);
3979
                try emitRetVal(self, val);
3980
            } else {
3981
                // Non-void throwing function without explicit return should
3982
                // not happen.
3983
                panic "lowerFnBody: missing return in non-void function";
3984
            }
3985
        } else {
3986
            emit(self, il::Instr::Ret { val: nil });
3987
        }
3988
    }
3989
    return try finalizeBlocks(self);
3990
}
3991
3992
/// Append a construction-time switch case with safely owned arguments.
3993
fn appendSwitchCase(
3994
    cases: &mut *mut [LowerSwitchCase],
3995
    value: i64,
3996
    target: BlockId,
3997
    args: *mut [il::Val],
3998
    allocator: alloc::Allocator,
3999
) {
4000
    cases.append(LowerSwitchCase { value, target, args }, allocator);
4001
}
4002
4003
/// Lower a scalar match as a switch instruction.
4004
unsafe fn lowerMatchSwitch 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, prongs: *[*ast::Node], subject: &MatchSubject, mergeBlock: &mut ?BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4005
    let mut blocks: *mut [BlockId] = &mut [];
4006
    let mut cases: *mut [LowerSwitchCase] = &mut [];
4007
    let mut defaultIdx: u32 = 0;
4008
    let entry = currentBlock(self);
4009
4010
    for p, i in prongs {
4011
        let case ast::NodeValue::MatchProng(prong) = p.value
4012
            else throw LowerError::UnexpectedNodeValue(p);
4013
4014
        match prong.arm {
4015
            case ast::ProngArm::Binding(_), ast::ProngArm::Else => {
4016
                blocks.append(try createBlock(self, "default"), alloc::arenaAllocator(self.arena));
4017
                set defaultIdx = i;
4018
            }
4019
            case ast::ProngArm::Case(pats) => {
4020
                blocks.append(try createBlock(self, "case"), alloc::arenaAllocator(self.arena));
4021
                for pat in pats {
4022
                    let cv = resolver::constValueEntry(self.low.resolver, pat)
4023
                        else throw LowerError::MissingConst(pat);
4024
4025
                    appendSwitchCase(&mut cases, constToScalar(cv), blocks[i], &mut [], alloc::arenaAllocator(self.arena));
4026
                }
4027
            }
4028
        }
4029
        addPredecessor(self, blocks[i], entry);
4030
    }
4031
    emitSwitchTerminator(
4032
        self,
4033
        subject.val,
4034
        blocks[defaultIdx],
4035
        LowerTerminator::Switch {
4036
            defaultTarget: blocks[defaultIdx],
4037
            defaultArgs: &mut [],
4038
            cases,
4039
        },
4040
    );
4041
4042
    for p, i in prongs {
4043
        let case ast::NodeValue::MatchProng(prong) = p.value
4044
            else throw LowerError::UnexpectedNodeValue(p);
4045
4046
        try switchToAndSeal(self, blocks[i]);
4047
        try lowerNode(self, prong.body);
4048
        try emitMergeIfUnterminated(self, mergeBlock);
4049
    }
4050
    if let blk = *mergeBlock {
4051
        try switchToAndSeal(self, blk);
4052
    }
4053
}
4054
4055
/// Lower a match statement.
4056
///
4057
/// Processes prongs sequentially, generating comparison code and branches for each.
4058
/// Prongs are processed in source order, so earlier prongs take precedence.
4059
///
4060
/// Guards are handled by emitting an additional branch after pattern matching
4061
/// but before the body.
4062
///
4063
/// Example:
4064
///
4065
///   match x {
4066
///       case 0 => return 0,
4067
///       case 1 => return 1,
4068
///       else => return 2,
4069
///   }
4070
///
4071
/// Generates:
4072
///
4073
///   arm#0:
4074
///       br.eq w32 %x 0 case#0 arm#1;    // if `x == 0`, jump to case#0, else arm#1
4075
///   case#0:
4076
///       ret 0;                          // `case 0` body
4077
///   arm#1:
4078
///       br.eq w32 %x 1 case#1 arm#2;    // if `x == 1`, jump to case#1, else arm#2
4079
///   case#1:
4080
///       ret 1;                          // `case 1` body
4081
///   arm#2:
4082
///       jmp else#0;                     // fallthrough to `else`
4083
///   else#0:
4084
///       ret 2;                          // `else` body
4085
///
4086
/// Example: Binding with guard
4087
///
4088
///   match x {
4089
///       y if y > 0 => return y,
4090
///       else => return 0,
4091
///   }
4092
///
4093
/// Generates:
4094
///
4095
///   arm#0:
4096
///       jmp guard#0;                    // catch-all binding, jump to guard
4097
///   case#0(w32 %y):
4098
///       ret %y;                         // guarded case body, receives bound var
4099
///   guard#0:
4100
///       sgt w32 %cmp %x 0;              // evaluate guard `y > 0`
4101
///       br.ne w32 %cmp 0 case#0 arm#1;  // if `true`, jump to case body
4102
///   arm#1:
4103
///       jmp else#0;                     // guard failed, fallthrough to `else`
4104
///   else#0:
4105
///       ret 0;                          // `else` body
4106
///
4107
unsafe fn lowerMatch 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, m: ast::Match) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4108
    assert m.prongs.len > 0;
4109
4110
    let prongs = m.prongs;
4111
    // Lower the subject expression once; reused across all arms.
4112
    let subject = try lowerMatchSubject(self, m.subject);
4113
    // Merge block created lazily if any arm needs it (i.e., doesn't diverge).
4114
    let mut mergeBlock: ?BlockId = nil;
4115
4116
    // Use `switch` instruction for matches with constant patterns.
4117
    if resolver::isMatchConst(self.low.resolver, node) {
4118
        try lowerMatchSwitch(self, prongs, &subject, &mut mergeBlock);
4119
        return;
4120
    }
4121
    // Fallback: chained branches.
4122
    let firstArm = try createBlock(self, "arm");
4123
    try emitJmp(self, firstArm);
4124
    try switchToAndSeal(self, firstArm);
4125
4126
    for prongNode, i in prongs {
4127
        let prongScope = enterVarScope(&self.vars);
4128
        let case ast::NodeValue::MatchProng(prong) = prongNode.value
4129
            else panic "lowerMatch: expected match prong";
4130
4131
        let isLastArm = i + 1 == prongs.len;
4132
        let hasGuard = prong.guard <> nil;
4133
        let catchAll = resolver::isProngCatchAll(self.low.resolver, prongNode);
4134
4135
        // Entry block: guard block if present, otherwise the body block.
4136
        // The guard block must be created before the body block so that
4137
        // block indices are in reverse post-order (RPO), which the register
4138
        // allocator requires.
4139
        let mut guardBlock: ?BlockId = nil;
4140
        if hasGuard {
4141
            set guardBlock = try createBlock(self, "guard");
4142
        }
4143
        // Body block: where the case body lives.
4144
        let mut bodyLabel = "case";
4145
        if prong.arm == ast::ProngArm::Else {
4146
            set bodyLabel = "else";
4147
        }
4148
        let mut bodyBlock = try createBlock(self, bodyLabel);
4149
        let entryBlock = guardBlock else bodyBlock;
4150
        // Fallthrough block: jumped to when pattern or guard fails.
4151
        let nextArm = try createBlock(self, "arm");
4152
4153
        // Emit pattern test: branch to entry block on match, next arm on fail.
4154
        match prong.arm {
4155
            case ast::ProngArm::Binding(_) if not catchAll =>
4156
                try emitBindingTest(self, &subject, entryBlock, nextArm),
4157
            case ast::ProngArm::Case(patterns) if not catchAll =>
4158
                try emitPatternMatches(self, &subject, patterns, entryBlock, nextArm),
4159
            else =>
4160
                try emitJmp(self, entryBlock),
4161
        }
4162
        // Switch to entry block, where any variable bindings need to be created.
4163
        try switchToAndSeal(self, entryBlock);
4164
4165
        // Bind pattern variables after successful match. Note that the guard
4166
        // has not been evaluated yet. Nested patterns may emit additional
4167
        // tests that branch to `nextArm` on failure, switching the current
4168
        // block.
4169
        match prong.arm {
4170
            case ast::ProngArm::Binding(pat) =>
4171
                try bindMatchVariable(self, &subject, pat, false),
4172
            case ast::ProngArm::Case(patterns) =>
4173
                try bindPatternVariables(self, &subject, patterns, nextArm),
4174
            else => {},
4175
        }
4176
4177
        // Evaluate guard if present; can still fail to next arm.
4178
        if let g = prong.guard {
4179
            try emitCondBranch(self, g, bodyBlock, nextArm);
4180
        } else if *currentBlock(self) <> *bodyBlock {
4181
            // Nested tests changed the current block. Create a new body block
4182
            // after the nest blocks to maintain RPO ordering, and jump to it.
4183
            set bodyBlock = try createBlock(self, bodyLabel);
4184
            try emitJmp(self, bodyBlock);
4185
        }
4186
        // Lower prong body and jump to merge if unterminated.
4187
        try switchToAndSeal(self, bodyBlock);
4188
        try lowerNode(self, prong.body);
4189
        try emitMergeIfUnterminated(self, &mut mergeBlock);
4190
        exitVarScope(&mut self.vars, prongScope);
4191
4192
        // Switch to next arm, unless last arm without guard.
4193
        if not isLastArm or hasGuard {
4194
            try switchToAndSeal(self, nextArm);
4195
            if isLastArm {
4196
                // Last arm with guard: guard failure jumps to merge.
4197
                try emitMergeIfUnterminated(self, &mut mergeBlock);
4198
            }
4199
        }
4200
    }
4201
    // Continue in merge block if we have one, ie. if at least one arm doesn't
4202
    // diverge.
4203
    if let blk = mergeBlock {
4204
        try switchToAndSeal(self, blk);
4205
    }
4206
}
4207
4208
/// Lower an `if let` statement.
4209
unsafe fn lowerIfLet 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, cond: ast::IfLet) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4210
    let savedVarsLen = enterVarScope(&self.vars);
4211
    let subject = try lowerMatchSubject(self, cond.pattern.scrutinee);
4212
    let mut thenBlock: ?BlockId = nil;
4213
    if cond.pattern.guard == nil {
4214
        set thenBlock = try createBlock(self, "then");
4215
    }
4216
    let elseBlock = try createBlock(self, "else");
4217
    let mut mergeBlock: ?BlockId = nil;
4218
4219
    // Pattern match: jump to @then on success, @else on failure.
4220
    try lowerPatternMatch(self, &subject, &cond.pattern, thenBlock, "then", elseBlock);
4221
4222
    // Lower then branch.
4223
    try lowerNode(self, cond.thenBranch);
4224
    try emitMergeIfUnterminated(self, &mut mergeBlock);
4225
    // Pattern bindings are visible only in the success branch. Restore the
4226
    // outer variable scope before lowering `else`, where a same-named outer
4227
    // variable may be referenced.
4228
    exitVarScope(&mut self.vars, savedVarsLen);
4229
4230
    // Lower else branch.
4231
    try switchToAndSeal(self, elseBlock);
4232
    if let elseBranch = cond.elseBranch {
4233
        try lowerNode(self, elseBranch);
4234
    }
4235
    try emitMergeIfUnterminated(self, &mut mergeBlock);
4236
4237
    if let blk = mergeBlock {
4238
        try switchToAndSeal(self, blk);
4239
    }
4240
}
4241
4242
/// Emit pattern match branch with optional guard, and bind variables.
4243
/// Used by `if-let`, `let-else`, and `while-let` lowering.
4244
///
4245
/// When a guard is present, the guard block is created before `successBlock`
4246
/// to ensure block indices are in RPO.
4247
unsafe fn lowerPatternMatch 'arena 'phase 'function (
4248
    self: &mut FnLowerer 'arena 'phase 'function,
4249
    subject: &MatchSubject,
4250
    pat: &ast::PatternMatch,
4251
    initialSuccess: ?BlockId,
4252
    successLabel: *[u8],
4253
    failBlock: BlockId
4254
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4255
    // If guard present, pattern match jumps to @guard, then guard evaluation
4256
    // jumps to `successBlock` or `failBlock`. Otherwise, jump directly to
4257
    // `successBlock`.
4258
    let mut guardBlock: ?BlockId = nil;
4259
    if pat.guard <> nil {
4260
        set guardBlock = try createBlock(self, "guard");
4261
    }
4262
    let successBlock = initialSuccess else try createBlock(self, successLabel);
4263
    let targetBlock = guardBlock else successBlock;
4264
    match pat.kind {
4265
        case ast::PatternKind::Case => {
4266
            let patterns = [pat.pattern];
4267
            // Jump to `targetBlock` if the pattern matches, `failBlock` otherwise.
4268
            try emitPatternMatches(self, subject, &patterns[..], targetBlock, failBlock);
4269
            try switchToAndSeal(self, targetBlock);
4270
            // Bind any variables inside the pattern. Nested patterns may
4271
            // emit additional tests that branch to `failBlock`, switching
4272
            // the current block.
4273
            try bindPatternVariables(self, subject, &patterns[..], failBlock);
4274
        }
4275
        case ast::PatternKind::Binding => {
4276
            // Jump to `targetBlock` if there is a value present, `failBlock` otherwise.
4277
            try emitBindingTest(self, subject, targetBlock, failBlock);
4278
            try switchToAndSeal(self, targetBlock);
4279
            // Bind the matched value to the pattern variable.
4280
            try bindMatchVariable(self, subject, pat.pattern, pat.mutable);
4281
        }
4282
    }
4283
    // Handle guard: on success jump to `successBlock`, on failure jump to `failBlock`.
4284
    if let g = pat.guard {
4285
        try emitCondBranch(self, g, successBlock, failBlock);
4286
        try switchToAndSeal(self, successBlock);
4287
    } else if *currentBlock(self) <> *targetBlock {
4288
        // Nested tests changed the current block. Create a new success block
4289
        // after the nest blocks to maintain RPO ordering, and jump to it.
4290
        let nestedSuccess = try createBlock(self, successLabel);
4291
4292
        try emitJmp(self, nestedSuccess);
4293
        try switchToAndSeal(self, nestedSuccess);
4294
    }
4295
}
4296
4297
/// Lower a `let-else` statement.
4298
unsafe fn lowerLetElse 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, letElse: ast::LetElse) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4299
    let subject = try lowerMatchSubject(self, letElse.pattern.scrutinee);
4300
    let mut successBlock: ?BlockId = nil;
4301
    if letElse.pattern.guard == nil {
4302
        set successBlock = try createBlock(self, "success");
4303
    }
4304
    // The else branch executes when the pattern fails to match.
4305
    let elseBlock = try createBlock(self, "else");
4306
    let matchBlock = currentBlock(self);
4307
    let mut fallback: ?il::Val = nil;
4308
    // The fallback uses the enclosing scope before pattern bindings exist.
4309
    switchToBlock(self, elseBlock);
4310
    if try typeOf(self, letElse.elseBranch) == resolver::Type::Never {
4311
        try lowerNode(self, letElse.elseBranch);
4312
    } else {
4313
        set fallback = try lowerExpr(self, letElse.elseBranch);
4314
    }
4315
    let fallbackBlock = currentBlock(self);
4316
    switchToBlock(self, matchBlock);
4317
4318
    // Evaluate the pattern and jump to @success or @else.
4319
    try lowerPatternMatch(
4320
        self,
4321
        &subject,
4322
        &letElse.pattern,
4323
        successBlock,
4324
        "success",
4325
        elseBlock,
4326
    );
4327
    let mut bindingVar: ?Var = nil;
4328
    if let case ast::PatternKind::Binding = letElse.pattern.kind {
4329
        set bindingVar = lookupLocalVar(&self.vars, letElse.pattern.pattern);
4330
    }
4331
    let mergeBlock = try createBlock(self, "merge");
4332
    try emitJmp(self, mergeBlock);
4333
4334
    try sealBlock(self, elseBlock);
4335
    if let value = fallback {
4336
        switchToBlock(self, fallbackBlock);
4337
        if let variable = bindingVar {
4338
            defVar(self, variable, value);
4339
        }
4340
        try emitJmp(self, mergeBlock);
4341
    }
4342
4343
    // Continue at @merge after a successful match or value-producing fallback.
4344
    try switchToAndSeal(self, mergeBlock);
4345
}
4346
4347
/// Lower a `while let` loop as a match-driven loop.
4348
unsafe fn lowerWhileLet 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, w: ast::WhileLet) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4349
    let savedVarsLen = enterVarScope(&self.vars);
4350
    // Create control flow blocks: loop header, body (created lazily when
4351
    // there's a guard), and exit.
4352
    let whileBlock = try createBlock(self, "while");
4353
    let mut bodyBlock: ?BlockId = nil;
4354
    if w.pattern.guard == nil {
4355
        set bodyBlock = try createBlock(self, "body");
4356
    }
4357
    let endBlock = try createBlock(self, "merge");
4358
4359
    // Enter loop context and jump to loop header.
4360
    enterLoop(self, endBlock, whileBlock);
4361
    try switchAndJumpTo(self, whileBlock);
4362
    let subject = try lowerMatchSubject(self, w.pattern.scrutinee);
4363
4364
    // Evaluate pattern and jump to loop body or loop end.
4365
    try lowerPatternMatch(self, &subject, &w.pattern, bodyBlock, "body", endBlock);
4366
4367
    // Lower loop body, jump back to loop header, and exit loop context.
4368
    try lowerBlock(self, w.body);
4369
    try emitJmpAndSeal(self, whileBlock);
4370
4371
    exitLoop(self);
4372
    try switchToAndSeal(self, endBlock);
4373
    exitVarScope(&mut self.vars, savedVarsLen);
4374
}
4375
4376
///////////////////
4377
// Node Lowering //
4378
///////////////////
4379
4380
/// Lower an AST node.
4381
unsafe fn lowerNode 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4382
    if self.low.options.debug {
4383
        set self.srcLoc.offset = node.span.offset;
4384
    }
4385
    match node.value {
4386
        case ast::NodeValue::RegionBlock { bindings, body, .. } => {
4387
            let savedVarsLen = enterVarScope(&self.vars);
4388
            let mut values: *mut [il::Val] = &mut [];
4389
            for bindingNode in bindings {
4390
                let case ast::NodeValue::RegionBinding(binding) = bindingNode.value
4391
                    else throw LowerError::ExpectedIdentifier;
4392
                let value = try lowerExpr(self, binding.value);
4393
                if blockHasTerminator(&self.blockData[*currentBlock(self)].instrs[..]) {
4394
                    exitVarScope(&mut self.vars, savedVarsLen);
4395
                    return;
4396
                }
4397
                values.append(value, alloc::arenaAllocator(self.arena));
4398
            }
4399
            for bindingNode, i in bindings {
4400
                let case ast::NodeValue::RegionBinding(binding) = bindingNode.value
4401
                    else throw LowerError::ExpectedIdentifier;
4402
                try bindLetValue(self, bindingNode, ast::borrowBinding(binding), values[i]);
4403
            }
4404
            try lowerBlock(self, body);
4405
            exitVarScope(&mut self.vars, savedVarsLen);
4406
        }
4407
        case ast::NodeValue::Block(_) => {
4408
            try lowerBlock(self, node);
4409
        }
4410
        case ast::NodeValue::Return { value } => {
4411
            try lowerReturnStmt(self, node, value);
4412
        }
4413
        case ast::NodeValue::Throw { expr } => {
4414
            try lowerThrowStmt(self, expr);
4415
        }
4416
        case ast::NodeValue::Let(l) => {
4417
            try lowerLet(self, node, l);
4418
        }
4419
        case ast::NodeValue::ConstDecl(decl) => {
4420
            // Local constants lower to data declarations and emit no runtime code.
4421
            try registerLocalDataDeclName(self, node);
4422
            try lowerDataDecl(self.low, node, decl.value, true);
4423
        }
4424
        case ast::NodeValue::StaticDecl(decl) => {
4425
            // Local statics lower to data declarations and emit no runtime code.
4426
            try registerLocalDataDeclName(self, node);
4427
            try lowerDataDecl(self.low, node, decl.value, false);
4428
        }
4429
        case ast::NodeValue::If(i) => {
4430
            try lowerIf(self, i);
4431
        }
4432
        case ast::NodeValue::IfLet(i) => {
4433
            try lowerIfLet(self, i);
4434
        }
4435
        case ast::NodeValue::Assign(a) => {
4436
            try lowerAssign(self, node, a);
4437
        }
4438
        case ast::NodeValue::Loop { body } => {
4439
            try lowerLoop(self, body);
4440
        }
4441
        case ast::NodeValue::While(w) => {
4442
            try lowerWhile(self, w);
4443
        }
4444
        case ast::NodeValue::WhileLet(w) => {
4445
            try lowerWhileLet(self, w);
4446
        }
4447
        case ast::NodeValue::For(f) => {
4448
            try lowerFor(self, node, f);
4449
        }
4450
        case ast::NodeValue::Break => {
4451
            try lowerBreak(self);
4452
        }
4453
        case ast::NodeValue::Continue => {
4454
            try lowerContinue(self);
4455
        }
4456
        case ast::NodeValue::Match(m) => {
4457
            try lowerMatch(self, node, m);
4458
        }
4459
        case ast::NodeValue::LetElse(letElse) => {
4460
            try lowerLetElse(self, letElse);
4461
        }
4462
        case ast::NodeValue::ExprStmt(expr) => {
4463
            let _ = try lowerExpr(self, expr);
4464
        }
4465
        case ast::NodeValue::Panic { .. } => {
4466
            emit(self, il::Instr::Unreachable);
4467
        }
4468
        case ast::NodeValue::Assert { condition, .. } => {
4469
            // Lower `assert <cond>` as: `if not cond { unreachable; }`.
4470
            let thenBlock = try createBlock(self, "assert.fail");
4471
            let endBlock = try createBlock(self, "assert.ok");
4472
4473
            // Branch: if condition is `true`, go to `endBlock`; if `false`, go to `thenBlock`.
4474
            try emitCondBranch(self, condition, endBlock, thenBlock);
4475
            try sealBlock(self, thenBlock);
4476
4477
            // Emit `unreachable` in the failure block.
4478
            switchToBlock(self, thenBlock);
4479
            emit(self, il::Instr::Unreachable);
4480
4481
            // Continue after the assert.
4482
            try switchToAndSeal(self, endBlock);
4483
        }
4484
        else => {
4485
            // Treat as expression statement, discard result.
4486
            let _ = try lowerExpr(self, node);
4487
        }
4488
    }
4489
}
4490
4491
/// Lower a code block.
4492
unsafe fn lowerBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4493
    let case ast::NodeValue::Block(blk) = node.value else {
4494
        throw LowerError::ExpectedBlock(node);
4495
    };
4496
    let savedVarsLen = enterVarScope(&self.vars);
4497
    for stmt in blk.statements {
4498
        try lowerNode(self, stmt);
4499
4500
        // If the statement diverges, further statements are unreachable.
4501
        if blockHasTerminator(&self.blockData[*currentBlock(self)].instrs[..]) {
4502
            exitVarScope(&mut self.vars, savedVarsLen);
4503
            return;
4504
        }
4505
    }
4506
    exitVarScope(&mut self.vars, savedVarsLen);
4507
}
4508
4509
///////////////////////////////////////
4510
// Record and Aggregate Type Helpers //
4511
///////////////////////////////////////
4512
4513
/// Extract the nominal record info from a resolver type.
4514
fn recordInfoFromType(typ: resolver::Type) -> ?resolver::RecordType {
4515
    let case resolver::Type::Nominal(info) = typ
4516
        else return nil;
4517
    unsafe {
4518
        let case resolver::NominalType::Record(recInfo) = *info
4519
            else return nil;
4520
        return recInfo;
4521
    }
4522
}
4523
4524
/// Extract the nominal union info from a resolver type.
4525
fn unionInfoFromType(typ: resolver::Type) -> ?resolver::UnionType {
4526
    let case resolver::Type::Nominal(info) = typ
4527
        else return nil;
4528
    unsafe {
4529
        let case resolver::NominalType::Union(unionInfo) = *info
4530
            else return nil;
4531
        return unionInfo;
4532
    }
4533
}
4534
4535
/// Return the effective type of a node after any coercion applied by
4536
/// the resolver. `lowerExpr` already materializes the coercion in the
4537
/// IL value, so the lowerer must use the post-coercion type when
4538
/// choosing how to compare or store that value.
4539
fn effectiveType 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) -> resolver::Type throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4540
    let ty = try typeOf(self, node);
4541
    if let coerce = resolver::coercionFor(self.low.resolver, node) {
4542
        if let case resolver::Coercion::OptionalLift(optTy) = coerce {
4543
            return optTy;
4544
        }
4545
    }
4546
    return ty;
4547
}
4548
4549
/// Check if a resolver type lowers to an aggregate in memory.
4550
fn isAggregateType(typ: resolver::Type) -> bool {
4551
    match typ {
4552
        case resolver::Type::Slice { .. },
4553
             resolver::Type::TraitObject { .. },
4554
             resolver::Type::Session(_) => return true,
4555
        case resolver::Type::Optional(resolver::Type::Pointer { .. }) => {
4556
            // Optional pointers are scalar due to NPO.
4557
            return false;
4558
        }
4559
        case resolver::Type::Optional(_) => {
4560
            // All other optionals, including optional slices, are aggregates.
4561
            return true;
4562
        }
4563
        case resolver::Type::Nominal(_) => {
4564
            // Void unions are small enough to pass by value.
4565
            unsafe {
4566
                return not resolver::isVoidUnion(typ);
4567
            }
4568
        }
4569
        case resolver::Type::Array(_),
4570
             resolver::Type::Nil => return true,
4571
        else => return false,
4572
    }
4573
}
4574
4575
/// Check if a resolver type is a small aggregate that can be
4576
/// passed or returned by value in a register.
4577
fn isSmallAggregate(typ: resolver::Type) -> bool {
4578
    match typ {
4579
        case resolver::Type::Nominal(info) => {
4580
            unsafe {
4581
                return nominalFitsRegister(*info);
4582
            }
4583
        }
4584
        else => return false,
4585
    }
4586
}
4587
4588
/// Check whether a nominal aggregate uses one register for its value.
4589
fn nominalFitsRegister(info: resolver::NominalType) -> bool {
4590
    if let case resolver::NominalType::Union(unionInfo) = info {
4591
        if unionInfo.isAllVoid {
4592
            return false;
4593
        }
4594
    }
4595
    let layout = resolver::getNominalLayout(info);
4596
    return layout.size <= resolver::PTR_SIZE;
4597
}
4598
4599
/// Whether a function needs a hidden return parameter.
4600
///
4601
/// This is the case for throwing functions, which return a result aggregate,
4602
/// and for functions returning large aggregates that cannot be passed in
4603
/// registers.
4604
fn requiresReturnParam(fnType: *resolver::FnType) -> bool {
4605
    return fnType.throwList.len > 0
4606
        or (isAggregateType(*fnType.returnType)
4607
        and not isSmallAggregate(*fnType.returnType));
4608
}
4609
4610
/// Check if a node is a void union variant literal (e.g. `Color::Red`).
4611
/// If so, returns the variant's tag index. This enables optimized comparisons
4612
/// that only check the tag instead of doing full aggregate comparison.
4613
unsafe fn voidVariantIndex 'arena (res: &resolver::Resolver 'arena, node: *ast::Node) -> ?i64 {
4614
    let data = resolver::nodeData(res, node);
4615
    // Optional equality checks both the presence tag and the union value.
4616
    if let case resolver::Coercion::OptionalLift(_) = data.coercion {
4617
        return nil;
4618
    }
4619
    let sym = resolver::symbolFor(res, node) else {
4620
        return nil;
4621
    };
4622
    let case resolver::SymbolData::Variant { type: payloadType, index, .. } = sym.data else {
4623
        return nil;
4624
    };
4625
    // Only void variants can use tag-only comparison.
4626
    if payloadType <> resolver::Type::Void {
4627
        return nil;
4628
    }
4629
    return index as i64;
4630
}
4631
4632
/// Reserve stack storage for a value of the given type.
4633
fn emitReserve 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, typ: resolver::Type) -> il::Reg throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4634
    unsafe {
4635
        let layout = resolver::getTypeLayout(typ);
4636
        return emitReserveLayout(self, layout);
4637
    }
4638
}
4639
4640
/// Reserve stack storage with an explicit layout.
4641
fn emitReserveLayout 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, layout: resolver::Layout) -> il::Reg where 'arena: 'phase, 'phase: 'function {
4642
    let dst = nextReg(self);
4643
4644
    emit(self, il::Instr::Reserve {
4645
        dst,
4646
        size: il::Val::Imm(layout.size as i64),
4647
        alignment: layout.alignment,
4648
    });
4649
    return dst;
4650
}
4651
4652
/// Store a value into an address.
4653
fn emitStore 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, base: il::Reg, offset: i32, typ: resolver::Type, src: il::Val) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4654
    // `undefined` values need no store.
4655
    if let case il::Val::Undef = src {
4656
        return;
4657
    }
4658
    if isAggregateType(typ) {
4659
        let dst = emitPtrOffset(self, base, offset);
4660
        let src = emitValToReg(self, src);
4661
        unsafe {
4662
            let layout = resolver::getTypeLayout(typ);
4663
            emit(self, il::Instr::Blit { dst, src, size: il::Val::Imm(layout.size as i64) });
4664
        }
4665
    } else {
4666
        emit(self, il::Instr::Store {
4667
            typ: ilType(self.low, typ),
4668
            src,
4669
            dst: base,
4670
            offset,
4671
        });
4672
    }
4673
}
4674
4675
/// Allocate stack space for a value and store it. Returns a pointer to the value.
4676
fn emitStackVal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, typ: resolver::Type, val: il::Val) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4677
    let ptr = try emitReserve(self, typ);
4678
    try emitStore(self, ptr, 0, typ, val);
4679
    return il::Val::Reg(ptr);
4680
}
4681
4682
/// Generic helper to build any tagged aggregate.
4683
/// Reserves space based on the provided layout, stores the tag, and optionally
4684
/// stores the payload value at `valOffset`.
4685
fn buildTagged 'arena 'phase 'function (
4686
    self: &mut FnLowerer 'arena 'phase 'function,
4687
    layout: resolver::Layout,
4688
    tag: i64,
4689
    payload: ?il::Val,
4690
    payloadType: resolver::Type,
4691
    tagSize: u32,
4692
    valOffset: i32
4693
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4694
    let dst = nextReg(self);
4695
    emit(self, il::Instr::Reserve {
4696
        dst,
4697
        size: il::Val::Imm(layout.size as i64),
4698
        alignment: layout.alignment,
4699
    });
4700
    if tagSize == 1 {
4701
        emitStoreW8At(self, il::Val::Imm(tag), dst, TVAL_TAG_OFFSET);
4702
    } else {
4703
        emitStoreW64At(self, il::Val::Imm(tag), dst, TVAL_TAG_OFFSET);
4704
    }
4705
4706
    if let val = payload {
4707
        if payloadType <> resolver::Type::Void {
4708
            try emitStore(self, dst, valOffset, payloadType, val);
4709
        }
4710
    }
4711
    return il::Val::Reg(dst);
4712
}
4713
4714
/// Wrap a value in an optional type.
4715
///
4716
/// For optional pointers (`?*T`), the value is returned as-is since pointers
4717
/// use zero to represent `nil`. For other optionals, builds a tagged aggregate.
4718
/// with the tag set to `1`, and the value as payload.
4719
fn wrapInOptional 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, val: il::Val, optType: resolver::Type) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4720
    let case resolver::Type::Optional(inner) = optType else {
4721
        throw LowerError::ExpectedOptional;
4722
    };
4723
    // Null-pointer-optimized (NPO) types are used as-is -- valid values are never null.
4724
    if resolver::isNullableType(*inner) {
4725
        return val;
4726
    }
4727
    unsafe {
4728
        let layout = resolver::getTypeLayout(optType);
4729
        let valOffset = resolver::getOptionalValOffset(resolver::getTypeLayout(*inner)) as i32;
4730
4731
        return try buildTagged(self, layout, 1, val, *inner, 1, valOffset);
4732
    }
4733
}
4734
4735
/// Build a `nil` value for an optional type.
4736
///
4737
/// For optional pointers (`?*T`), returns an immediate `0` (null pointer).
4738
/// For other optionals, builds a tagged aggregate with tag set to `0` (absent).
4739
fn buildNilOptional 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, optType: resolver::Type) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4740
    let case resolver::Type::Optional(inner) = optType
4741
        else throw LowerError::ExpectedOptional;
4742
    if let case resolver::Type::Pointer { .. } = *inner {
4743
        return il::Val::Imm(0);
4744
    }
4745
    if let case resolver::Type::Slice { item, mutable, .. } = *inner {
4746
        return try buildSliceValue(
4747
            self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0)
4748
        );
4749
    }
4750
    unsafe {
4751
        let valOffset = resolver::getOptionalValOffset(resolver::getTypeLayout(*inner)) as i32;
4752
        return try buildTagged(self, resolver::getTypeLayout(optType), 0, nil, *inner, 1, valOffset);
4753
    }
4754
}
4755
4756
/// Build a result value for throwing functions.
4757
unsafe fn buildResult 'arena 'phase 'function (
4758
    self: &mut FnLowerer 'arena 'phase 'function,
4759
    tag: i64,
4760
    payload: ?il::Val,
4761
    payloadType: resolver::Type
4762
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4763
    let successType = *self.fnType.returnType;
4764
    let layout = resolver::getResultLayout(
4765
        successType, self.fnType.throwList
4766
    );
4767
    return try buildTagged(self, layout, tag, payload, payloadType, resolver::PTR_SIZE as i32, RESULT_VAL_OFFSET);
4768
}
4769
4770
/// Build a slice aggregate from a data pointer, length and capacity.
4771
fn buildSliceValue 'arena 'phase 'function (
4772
    self: &mut FnLowerer 'arena 'phase 'function,
4773
    elemTy: *resolver::Type,
4774
    mutable: bool,
4775
    ptrVal: il::Val,
4776
    lenVal: il::Val,
4777
    capVal: il::Val
4778
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4779
    let sliceType = resolver::Type::Slice {
4780
        class: types::PointerClass::Unsafe,
4781
        item: elemTy,
4782
        mutable,
4783
    };
4784
    let dst = try emitReserve(self, sliceType);
4785
    let ptrTy = resolver::Type::Pointer {
4786
        class: types::PointerClass::Unsafe,
4787
        target: elemTy,
4788
        mutable,
4789
    };
4790
4791
    try emitStore(self, dst, SLICE_PTR_OFFSET, ptrTy, ptrVal);
4792
    try emitStore(self, dst, SLICE_LEN_OFFSET, resolver::Type::U32, lenVal);
4793
    try emitStore(self, dst, SLICE_CAP_OFFSET, resolver::Type::U32, capVal);
4794
4795
    return il::Val::Reg(dst);
4796
}
4797
4798
/// Build a trait object fat pointer from a data pointer and a v-table.
4799
unsafe fn buildTraitObject 'arena 'phase 'function (
4800
    self: &mut FnLowerer 'arena 'phase 'function,
4801
    dataVal: il::Val,
4802
    traitInfo: *unsafe resolver::TraitType,
4803
    inst: &resolver::InstanceEntry
4804
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4805
    let vName = vtableName(self.low, inst.moduleId, inst.concreteTypeName, traitInfo.name);
4806
    return emitTraitObject(self, dataVal, vName);
4807
}
4808
4809
/// Store a trait object's data pointer and resolved v-table address.
4810
fn emitTraitObject 'arena 'phase 'function (
4811
    self: &mut FnLowerer 'arena 'phase 'function,
4812
    dataVal: il::Val,
4813
    vName: *[u8]
4814
) -> il::Val where 'arena: 'phase, 'phase: 'function {
4815
    // Reserve space for the trait object on the stack.
4816
    let slot = emitReserveLayout(self, resolver::Layout {
4817
        size: resolver::PTR_SIZE * 2,
4818
        alignment: resolver::PTR_SIZE,
4819
    });
4820
4821
    // Store data pointer.
4822
    emit(self, il::Instr::Store {
4823
        typ: il::Type::W64,
4824
        src: dataVal,
4825
        dst: slot,
4826
        offset: TRAIT_OBJ_DATA_OFFSET,
4827
    });
4828
4829
    // Store v-table address.
4830
    emit(self, il::Instr::Store {
4831
        typ: il::Type::W64,
4832
        src: il::Val::DataSym(vName),
4833
        dst: slot,
4834
        offset: TRAIT_OBJ_VTABLE_OFFSET,
4835
    });
4836
    return il::Val::Reg(slot);
4837
}
4838
4839
/// Compute a field pointer by adding a byte offset to a base address.
4840
fn emitPtrOffset 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, base: il::Reg, offset: i32) -> il::Reg where 'arena: 'phase, 'phase: 'function {
4841
    if offset == 0 {
4842
        return base;
4843
    }
4844
    let dst = nextReg(self);
4845
4846
    emit(self, il::Instr::BinOp {
4847
        op: il::BinOp::Add,
4848
        typ: il::Type::W64,
4849
        dst,
4850
        a: il::Val::Reg(base),
4851
        b: il::Val::Imm(offset as i64),
4852
    });
4853
    return dst;
4854
}
4855
4856
/// Emit an element address computation for array/slice indexing.
4857
/// Computes: `base + idx * stride`.
4858
fn emitElem 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, stride: u32, base: il::Reg, idx: il::Val) -> il::Reg where 'arena: 'phase, 'phase: 'function {
4859
    // If index is zero, return base directly.
4860
    if idx == il::Val::Imm(0) {
4861
        return base;
4862
    }
4863
    // If stride is `1`, skip the multiply.
4864
    if stride == 1 {
4865
        let dst = nextReg(self);
4866
4867
        emit(self, il::Instr::BinOp {
4868
            op: il::BinOp::Add,
4869
            typ: il::Type::W64,
4870
            dst,
4871
            a: il::Val::Reg(base),
4872
            b: idx
4873
        });
4874
        return dst;
4875
    }
4876
    // Compute `offset = idx * stride`.
4877
    let offset = nextReg(self);
4878
4879
    emit(self, il::Instr::BinOp {
4880
        op: il::BinOp::Mul,
4881
        typ: il::Type::W64,
4882
        dst: offset,
4883
        a: idx,
4884
        b: il::Val::Imm(stride as i64)
4885
    });
4886
    // Compute `dst = base + offset`.
4887
    let dst = nextReg(self);
4888
    emit(self, il::Instr::BinOp {
4889
        op: il::BinOp::Add,
4890
        typ: il::Type::W64,
4891
        dst,
4892
        a: il::Val::Reg(base),
4893
        b: il::Val::Reg(offset)
4894
    });
4895
    return dst;
4896
}
4897
4898
/// Emit a typed binary operation, returning the result as a value.
4899
fn emitTypedBinOp 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, op: il::BinOp, typ: il::Type, a: il::Val, b: il::Val) -> il::Val where 'arena: 'phase, 'phase: 'function {
4900
    let dst = nextReg(self);
4901
    emit(self, il::Instr::BinOp { op, typ, dst, a, b });
4902
    return il::Val::Reg(dst);
4903
}
4904
4905
/// Emit a tag comparison for void variant equality/inequality.
4906
unsafe fn emitTagCmp 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, op: ast::BinaryOp, val: il::Val, tagIdx: i64, valType: resolver::Type) -> il::Val
4907
    throws (LowerError) where 'arena: 'phase, 'phase: 'function
4908
{
4909
    let reg = emitValToReg(self, val);
4910
4911
    // For all-void unions, the value *is* the tag, not a pointer.
4912
    let tag = il::Val::Reg(reg) if resolver::isVoidUnion(valType)
4913
        else loadTag(self, reg, TVAL_TAG_OFFSET, il::Type::W8);
4914
    let binOp = il::BinOp::Eq if op == ast::BinaryOp::Eq else il::BinOp::Ne;
4915
    return emitTypedBinOp(self, binOp, il::Type::W8, tag, il::Val::Imm(tagIdx));
4916
}
4917
4918
/// Logical "and" between two values. Returns the result in a register.
4919
fn emitLogicalAnd 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, left: ?il::Val, right: il::Val) -> il::Val where 'arena: 'phase, 'phase: 'function {
4920
    let prev = left else {
4921
        return right;
4922
    };
4923
    return emitTypedBinOp(self, il::BinOp::And, il::Type::W32, prev, right);
4924
}
4925
4926
//////////////////////////
4927
// Aggregate Comparison //
4928
//////////////////////////
4929
4930
/// Emit an equality test for values at an offset of the given base registers.
4931
unsafe fn emitEqAtOffset 'arena 'phase 'function (
4932
    self: &mut FnLowerer 'arena 'phase 'function,
4933
    left: il::Reg,
4934
    right: il::Reg,
4935
    offset: i32,
4936
    fieldType: resolver::Type
4937
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4938
    // For aggregate types, pass offset through and compare recursively.
4939
    if isAggregateType(fieldType) {
4940
        return try lowerAggregateEq(self, fieldType, left, right, offset);
4941
    }
4942
    // For scalar types, load and compare directly.
4943
    let a = emitLoad(self, left, offset, fieldType);
4944
    let b = emitLoad(self, right, offset, fieldType);
4945
    let dst = nextReg(self);
4946
    emit(self, il::Instr::BinOp { op: il::BinOp::Eq, typ: ilType(self.low, fieldType), dst, a, b });
4947
4948
    return il::Val::Reg(dst);
4949
}
4950
4951
/// Compare two record values for equality.
4952
unsafe fn lowerRecordEq 'arena 'phase 'function (
4953
    self: &mut FnLowerer 'arena 'phase 'function,
4954
    recInfo: resolver::RecordType,
4955
    a: il::Reg,
4956
    b: il::Reg,
4957
    offset: i32
4958
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4959
    let mut result: ?il::Val = nil;
4960
4961
    for field in recInfo.fields {
4962
        let cmp = try emitEqAtOffset(self, a, b, offset + field.offset, field.fieldType);
4963
4964
        set result = emitLogicalAnd(self, result, cmp);
4965
    }
4966
    if let r = result {
4967
        return r;
4968
    }
4969
    return il::Val::Imm(1);
4970
}
4971
4972
/// Compare two slice values for equality.
4973
unsafe fn lowerSliceEq 'arena 'phase 'function (
4974
    self: &mut FnLowerer 'arena 'phase 'function,
4975
    elemTy: *resolver::Type,
4976
    mutable: bool,
4977
    a: il::Reg,
4978
    b: il::Reg,
4979
    offset: i32
4980
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4981
    let ptrTy = resolver::Type::Pointer {
4982
        class: types::PointerClass::Unsafe,
4983
        target: elemTy,
4984
        mutable,
4985
    };
4986
    let ptrEq = try emitEqAtOffset(self, a, b, offset + SLICE_PTR_OFFSET, ptrTy);
4987
    let lenEq = try emitEqAtOffset(self, a, b, offset + SLICE_LEN_OFFSET, resolver::Type::U32);
4988
4989
    return emitTypedBinOp(self, il::BinOp::And, il::Type::W32, ptrEq, lenEq);
4990
}
4991
4992
/// Compare two optional aggregate values for equality.
4993
///
4994
/// Two optionals are equal when their tags match and either both are `nil` or
4995
/// their payloads are equal.
4996
///
4997
/// For inner types that are safe to compare even when uninitialised, we use a
4998
/// branchless formulation: `tagEq AND (tagNil OR payloadEq)`
4999
///
5000
/// For inner types that may contain uninitialized data when `nil` (unions,
5001
/// nested optionals), the payload comparison is guarded behind a branch
5002
/// so that `nil` payloads are never inspected.
5003
unsafe fn lowerOptionalEq 'arena 'phase 'function (
5004
    self: &mut FnLowerer 'arena 'phase 'function,
5005
    inner: resolver::Type,
5006
    a: il::Reg,
5007
    b: il::Reg,
5008
    offset: i32
5009
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5010
    let valOffset = resolver::getOptionalValOffset(resolver::getTypeLayout(inner)) as i32;
5011
5012
    // Load tags.
5013
    let tagA = loadTag(self, a, offset + TVAL_TAG_OFFSET, il::Type::W8);
5014
    let tagB = loadTag(self, b, offset + TVAL_TAG_OFFSET, il::Type::W8);
5015
5016
    // For simple inner types (no unions/nested optionals), use branchless comparison.
5017
    // Unions and nested optionals may contain uninitialized payload bytes
5018
    // when nil, so they need a guarded comparison.
5019
    let isUnion = unionInfoFromType(inner) <> nil;
5020
    let mut isOptional = false;
5021
    if let case resolver::Type::Optional(_) = inner {
5022
        set isOptional = true;
5023
    }
5024
    if not isUnion and not isOptional {
5025
        let tagEq = emitTypedBinOp(self, il::BinOp::Eq, il::Type::W8, tagA, tagB);
5026
        let tagNil = emitTypedBinOp(self, il::BinOp::Eq, il::Type::W8, tagA, il::Val::Imm(0));
5027
        let payloadEq = try emitEqAtOffset(self, a, b, offset + valOffset, inner);
5028
5029
        return emitTypedBinOp(self, il::BinOp::And, il::Type::W32, tagEq,
5030
            emitTypedBinOp(self, il::BinOp::Or, il::Type::W32, tagNil, payloadEq));
5031
    }
5032
5033
    // For complex inner types, use branching comparison to avoid inspecting
5034
    // uninitialized payload bytes.
5035
    let resultReg = nextReg(self);
5036
    let mergeBlock = try createBlockWithParam(self, "opteq#merge", il::Param {
5037
        value: resultReg, type: il::Type::W8
5038
    });
5039
    let nilCheck = try createBlock(self, "opteq#nil");
5040
    let payloadCmp = try createBlock(self, "opteq#payload");
5041
5042
    let falseArgs = try allocVal(self, il::Val::Imm(0));
5043
    let trueArgs = try allocVal(self, il::Val::Imm(1));
5044
5045
    // Check if tags differ.
5046
    emitBrTerminator(
5047
        self,
5048
        il::CmpOp::Eq, il::Type::W8, tagA, tagB,
5049
        nilCheck, mergeBlock,
5050
        LowerTerminator::Br {
5051
            thenTarget: nilCheck, thenArgs: &mut [],
5052
            elseTarget: mergeBlock, elseArgs: falseArgs,
5053
        },
5054
    );
5055
    addPredecessor(self, nilCheck, currentBlock(self));
5056
    addPredecessor(self, mergeBlock, currentBlock(self));
5057
5058
    // Check if both are `nil`.
5059
    try switchToAndSeal(self, nilCheck);
5060
    emitBrTerminator(
5061
        self,
5062
        il::CmpOp::Ne, il::Type::W8, tagA, il::Val::Imm(0),
5063
        payloadCmp, mergeBlock,
5064
        LowerTerminator::Br {
5065
            thenTarget: payloadCmp, thenArgs: &mut [],
5066
            elseTarget: mergeBlock, elseArgs: trueArgs,
5067
        },
5068
    );
5069
    addPredecessor(self, payloadCmp, currentBlock(self));
5070
    addPredecessor(self, mergeBlock, currentBlock(self));
5071
5072
    // Both are non-`nil`, compare payloads.
5073
    try switchToAndSeal(self, payloadCmp);
5074
    let payloadEq = try emitEqAtOffset(self, a, b, offset + valOffset, inner);
5075
    try emitJmpWithArg(self, mergeBlock, payloadEq);
5076
    try switchToAndSeal(self, mergeBlock);
5077
5078
    return il::Val::Reg(resultReg);
5079
}
5080
5081
/// Compare two union values for equality.
5082
///
5083
/// Two unions are equal iff their tags match and, for non-void variants,
5084
/// their payloads are also equal. The comparison proceeds as follows.
5085
///
5086
/// First, compare the tags. If they differ, the unions are not equal, so
5087
/// jump to the merge block with `false`. If they match, jump to the tag
5088
/// block to determine which variant we're dealing with.
5089
///
5090
/// The tag block uses a switch on the tag value to dispatch to the appropriate
5091
/// comparison block. Void variants jump directly to merge with `true`.
5092
/// Non-void variants each have their own payload block that compares the
5093
/// payload and jumps to the merge block with the result.
5094
///
5095
/// The merge block collects results from all paths via a block parameter
5096
/// and returns the final equality result.
5097
///
5098
/// For all-void unions, we skip the control flow entirely and just compare
5099
/// the tags directly.
5100
///
5101
/// TODO: Could be optimized to branchless when all non-void variants share
5102
/// the same payload type: `tagEq AND (isVoidVariant OR payloadEq)`.
5103
unsafe fn lowerUnionEq 'arena 'phase 'function (
5104
    self: &mut FnLowerer 'arena 'phase 'function,
5105
    unionInfo: resolver::UnionType,
5106
    a: il::Reg,
5107
    b: il::Reg,
5108
    offset: i32
5109
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5110
    // Compare tags.
5111
    let tagA = loadTag(self, a, offset + TVAL_TAG_OFFSET, il::Type::W8);
5112
    let tagB = loadTag(self, b, offset + TVAL_TAG_OFFSET, il::Type::W8);
5113
5114
    // Fast path: all-void union just needs tag comparison.
5115
    if unionInfo.isAllVoid {
5116
        return emitTypedBinOp(self, il::BinOp::Eq, il::Type::W8, tagA, tagB);
5117
    }
5118
    // Holds the equality result.
5119
    let resultReg = nextReg(self);
5120
5121
    // Where control flow continues after equality check is done. Receives
5122
    // the result as a parameter.
5123
    let mergeBlock = try createBlockWithParam(self, "eq#merge", il::Param {
5124
        value: resultReg, type: il::Type::W8
5125
    });
5126
    // Where we switch on the tag to compare payloads.
5127
    let tagBlock = try createBlock(self, "eq#tag");
5128
5129
    // Compare tags: if they differ, jump to merge with `false`; otherwise check payloads.
5130
    let falseArgs = try allocVal(self, il::Val::Imm(0));
5131
5132
    assert tagBlock <> mergeBlock;
5133
5134
    emitBrTerminator(
5135
        self,
5136
        il::CmpOp::Eq, il::Type::W8, tagA, tagB,
5137
        tagBlock, mergeBlock,
5138
        LowerTerminator::Br {
5139
            thenTarget: tagBlock, thenArgs: &mut [],
5140
            elseTarget: mergeBlock, elseArgs: falseArgs,
5141
        },
5142
    );
5143
    addPredecessor(self, tagBlock, currentBlock(self));
5144
    addPredecessor(self, mergeBlock, currentBlock(self));
5145
5146
    // Create comparison blocks for each non-void variant and build switch cases.
5147
    // Void variants jump directly to merge with `true`.
5148
    let mut cases: *mut [LowerSwitchCase] = &mut [];
5149
    let mut caseBlocks: *mut [BlockId] = &mut [];
5150
    for variant, i in unionInfo.variants {
5151
        if variant.valueType == resolver::Type::Void {
5152
            let trueArgs = try allocVal(self, il::Val::Imm(1));
5153
            caseBlocks.append(mergeBlock, alloc::arenaAllocator(self.arena));
5154
            appendSwitchCase(&mut cases, i as i64, mergeBlock, trueArgs, alloc::arenaAllocator(self.arena));
5155
        } else {
5156
            let payloadBlock = try createBlock(self, "eq#payload");
5157
            caseBlocks.append(payloadBlock, alloc::arenaAllocator(self.arena));
5158
            appendSwitchCase(&mut cases, i as i64, payloadBlock, &mut [], alloc::arenaAllocator(self.arena));
5159
        }
5160
    }
5161
5162
    // Emit switch in @tag block. Default arm is unreachable since we cover all variants.
5163
    let unreachableBlock = try createBlock(self, "eq#unreachable");
5164
    try switchToAndSeal(self, tagBlock);
5165
    emitSwitchTerminator(
5166
        self,
5167
        tagA,
5168
        unreachableBlock,
5169
        LowerTerminator::Switch {
5170
            defaultTarget: unreachableBlock,
5171
            defaultArgs: &mut [],
5172
            cases,
5173
        },
5174
    );
5175
5176
    // Add predecessor edges for switch targets.
5177
    addPredecessor(self, unreachableBlock, tagBlock);
5178
    for caseBlock in &caseBlocks[..] {
5179
        addPredecessor(self, caseBlock, tagBlock);
5180
    }
5181
    let valOffset = unionInfo.valOffset as i32;
5182
5183
    // Emit payload comparison blocks for non-void variants.
5184
    for variant, i in unionInfo.variants {
5185
        let caseBlock = caseBlocks[i];
5186
        if caseBlock <> mergeBlock {
5187
            try switchToAndSeal(self, caseBlock);
5188
            let payloadEq = try emitEqAtOffset(
5189
                self, a, b, offset + valOffset, variant.valueType
5190
            );
5191
            try emitJmpWithArg(self, mergeBlock, payloadEq);
5192
        }
5193
    }
5194
    // Emit unreachable block.
5195
    try switchToAndSeal(self, unreachableBlock);
5196
    emit(self, il::Instr::Unreachable);
5197
5198
    try switchToAndSeal(self, mergeBlock);
5199
    return il::Val::Reg(resultReg);
5200
}
5201
5202
/// Compare two array values for equality, element by element.
5203
unsafe fn lowerArrayEq 'arena 'phase 'function (
5204
    self: &mut FnLowerer 'arena 'phase 'function,
5205
    arr: resolver::ArrayType,
5206
    a: il::Reg,
5207
    b: il::Reg,
5208
    offset: i32
5209
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5210
    let elemLayout = resolver::getTypeLayout(*arr.item);
5211
    let stride = elemLayout.size as i32;
5212
    let mut result: ?il::Val = nil;
5213
5214
    for i in 0..arr.length {
5215
        let elemOffset = offset + (i as i32) * stride;
5216
        let cmp = try emitEqAtOffset(self, a, b, elemOffset, *arr.item);
5217
        set result = emitLogicalAnd(self, result, cmp);
5218
    }
5219
    if let r = result {
5220
        return r;
5221
    }
5222
    // Empty arrays are always equal.
5223
    return il::Val::Imm(1);
5224
}
5225
5226
/// Compare two aggregate values for equality.
5227
unsafe fn lowerAggregateEq 'arena 'phase 'function (
5228
    self: &mut FnLowerer 'arena 'phase 'function,
5229
    typ: resolver::Type,
5230
    a: il::Reg,
5231
    b: il::Reg,
5232
    offset: i32
5233
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5234
    match typ {
5235
        case resolver::Type::Slice { item, mutable, .. } =>
5236
            return try lowerSliceEq(self, item, mutable, a, b, offset),
5237
        case resolver::Type::Optional(inner) => {
5238
            if let case resolver::Type::Slice { item, mutable, .. } = *inner {
5239
                // Optional slices use null pointer optimization.
5240
                return try lowerSliceEq(self, item, mutable, a, b, offset);
5241
            }
5242
            return try lowerOptionalEq(self, *inner, a, b, offset);
5243
        }
5244
        case resolver::Type::Array(arr) =>
5245
            return try lowerArrayEq(self, arr, a, b, offset),
5246
        case resolver::Type::Nominal(resolver::NominalType::Record(recInfo)) =>
5247
            return try lowerRecordEq(self, recInfo, a, b, offset),
5248
        case resolver::Type::Nominal(resolver::NominalType::Union(unionInfo)) =>
5249
            return try lowerUnionEq(self, unionInfo, a, b, offset),
5250
        else => {
5251
            let recInfo = recordInfoFromType(typ) else {
5252
                throw LowerError::ExpectedRecord;
5253
            };
5254
            return try lowerRecordEq(self, recInfo, a, b, offset);
5255
        }
5256
    }
5257
}
5258
5259
/// Lower a record literal expression. Handles both plain records and union variant
5260
/// record literals like `Union::Variant { field: value }`.
5261
unsafe fn lowerRecordLit 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, lit: ast::RecordLit) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5262
    let typ = try typeOf(self, node);
5263
    match typ {
5264
        case resolver::Type::Nominal(resolver::NominalType::Record(recInfo)) => {
5265
            let dst = try emitReserve(self, typ);
5266
            try lowerRecordFields(self, dst, &recInfo, lit.fields, 0);
5267
5268
            return il::Val::Reg(dst);
5269
        }
5270
        case resolver::Type::Nominal(resolver::NominalType::Union(_)) => {
5271
            let typeName = lit.typeName else {
5272
                throw LowerError::ExpectedVariant;
5273
            };
5274
            let sym = try symOf(self, typeName);
5275
            let case resolver::SymbolData::Variant { type: payloadType, index, .. } = sym.data else {
5276
                throw LowerError::ExpectedVariant;
5277
            };
5278
            let recInfo = recordInfoFromType(payloadType) else {
5279
                throw LowerError::ExpectedRecord;
5280
            };
5281
            let unionInfo = unionInfoFromType(typ) else {
5282
                throw LowerError::MissingMetadata;
5283
            };
5284
            let valOffset = unionInfo.valOffset as i32;
5285
            let dst = try emitReserve(self, typ);
5286
5287
            emitStoreW8At(self, il::Val::Imm(index as i64), dst, TVAL_TAG_OFFSET);
5288
            try lowerRecordFields(self, dst, &recInfo, lit.fields, valOffset);
5289
5290
            return il::Val::Reg(dst);
5291
        }
5292
        else => throw LowerError::UnexpectedType(typ),
5293
    }
5294
}
5295
5296
/// Lower fields of a record literal into a destination register.
5297
/// The `offset` is added to each field's offset when storing.
5298
unsafe fn lowerRecordFields 'arena 'phase 'function (
5299
    self: &mut FnLowerer 'arena 'phase 'function,
5300
    dst: il::Reg,
5301
    recInfo: &resolver::RecordType,
5302
    fields: *[*ast::Node],
5303
    offset: i32
5304
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5305
    for fieldNode, i in fields {
5306
        let case ast::NodeValue::RecordLitField(field) = fieldNode.value else {
5307
            throw LowerError::UnexpectedNodeValue(fieldNode);
5308
        };
5309
        let mut fieldIdx: u32 = i;
5310
        if recInfo.labeled {
5311
            let idx = resolver::recordFieldIndexFor(self.low.resolver, fieldNode) else {
5312
                throw LowerError::MissingMetadata;
5313
            };
5314
            set fieldIdx = idx;
5315
        }
5316
        // Skip `undefined` fields, they need no initialization.
5317
        // Emitting a blit from an uninitialised reserve produces a
5318
        // phantom SSA source value that the backend cannot handle.
5319
        if not isUndef(field.value) {
5320
            let fieldTy = recInfo.fields[fieldIdx].fieldType;
5321
            let fieldVal = try lowerExpr(self, field.value);
5322
            try emitStore(self, dst, offset + recInfo.fields[fieldIdx].offset, fieldTy, fieldVal);
5323
        }
5324
    }
5325
}
5326
5327
/// Lower an unlabeled record constructor call.
5328
unsafe fn lowerRecordCtor 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, nominal: *unsafe resolver::NominalType, args: *[*ast::Node]) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5329
    let case resolver::NominalType::Record(recInfo) = *nominal else {
5330
        throw LowerError::ExpectedRecord;
5331
    };
5332
    let typ = resolver::Type::Nominal(nominal);
5333
    let dst = try emitReserve(self, typ);
5334
5335
    for argNode, i in args {
5336
        // Skip `undefined` arguments.
5337
        if not isUndef(argNode) {
5338
            let fieldTy = recInfo.fields[i].fieldType;
5339
            let argVal = try lowerExpr(self, argNode);
5340
            try emitStore(self, dst, recInfo.fields[i].offset, fieldTy, argVal);
5341
        }
5342
    }
5343
    return il::Val::Reg(dst);
5344
}
5345
5346
/// Lower an array literal expression like `[1, 2, 3]`.
5347
unsafe fn lowerArrayLit 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, elements: *[*ast::Node]) -> il::Val
5348
    throws (LowerError) where 'arena: 'phase, 'phase: 'function
5349
{
5350
    let typ = try typeOf(self, node);
5351
    let case resolver::Type::Array(arrInfo) = typ else {
5352
        throw LowerError::ExpectedArray;
5353
    };
5354
    let elemTy = *arrInfo.item;
5355
    let elemLayout = resolver::getTypeLayout(elemTy);
5356
    let dst = try emitReserve(self, typ);
5357
5358
    for elemNode, i in elements {
5359
        let elemVal = try lowerExpr(self, elemNode);
5360
        let offset = i * elemLayout.size;
5361
5362
        try emitStore(self, dst, offset as i32, elemTy, elemVal);
5363
    }
5364
    return il::Val::Reg(dst);
5365
}
5366
5367
/// Lower an array repeat literal expression like `[42; 3]`.
5368
/// Unrolls the initialization at compile time.
5369
// TODO: Beyond a certain length, lower this to a loop.
5370
unsafe fn lowerArrayRepeatLit 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, repeat: ast::ArrayRepeatLit) -> il::Val
5371
    throws (LowerError) where 'arena: 'phase, 'phase: 'function
5372
{
5373
    let typ = try typeOf(self, node);
5374
    let case resolver::Type::Array(arrInfo) = typ else {
5375
        throw LowerError::ExpectedArray;
5376
    };
5377
    let elemTy = *arrInfo.item;
5378
    let length = arrInfo.length;
5379
    let elemLayout = resolver::getTypeLayout(elemTy);
5380
    let dst = try emitReserve(self, typ);
5381
5382
    // Evaluate the repeated item once.
5383
    let repeatVal = try lowerExpr(self, repeat.item);
5384
5385
    // Unroll: store at each offset.
5386
    for i in 0..length {
5387
        let offset = i * elemLayout.size;
5388
        try emitStore(self, dst, offset as i32, elemTy, repeatVal);
5389
    }
5390
    return il::Val::Reg(dst);
5391
}
5392
5393
/// Lower a union constructor call like `Union::Variant(...)`.
5394
unsafe fn lowerUnionCtor 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, sym: *unsafe mut resolver::Symbol, call: ast::Call) -> il::Val
5395
    throws (LowerError) where 'arena: 'phase, 'phase: 'function
5396
{
5397
    let unionTy = try typeOf(self, node);
5398
    let case resolver::SymbolData::Variant { type: payloadType, index, .. } = sym.data else {
5399
        throw LowerError::ExpectedVariant;
5400
    };
5401
    let unionInfo = unionInfoFromType(unionTy) else {
5402
        throw LowerError::MissingMetadata;
5403
    };
5404
    let valOffset = unionInfo.valOffset as i32;
5405
    let mut payloadVal: ?il::Val = nil;
5406
    if payloadType <> resolver::Type::Void {
5407
        let case resolver::Type::Nominal(payloadNominal) = payloadType else {
5408
            throw LowerError::MissingMetadata;
5409
        };
5410
        set payloadVal = try lowerRecordCtor(self, payloadNominal, call.args);
5411
    }
5412
    return try buildTagged(self, resolver::getTypeLayout(unionTy), index as i64, payloadVal, payloadType, 1, valOffset);
5413
}
5414
5415
/// Lower a field access into a pointer to the field.
5416
unsafe fn lowerFieldRef 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, access: ast::Access) -> FieldRef throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5417
    let parentTy = try typeOf(self, access.parent);
5418
    let subjectTy = resolver::autoDeref(parentTy);
5419
    let fieldIdx = resolver::recordFieldIndexFor(self.low.resolver, access.child) else {
5420
        throw LowerError::MissingMetadata;
5421
    };
5422
    let fieldInfo = resolver::getRecordField(subjectTy, fieldIdx) else {
5423
        throw LowerError::FieldNotFound;
5424
    };
5425
    let baseVal = try lowerExpr(self, access.parent);
5426
    let baseReg = emitValToReg(self, baseVal);
5427
5428
    return FieldRef {
5429
        base: baseReg,
5430
        offset: fieldInfo.offset,
5431
        fieldType: fieldInfo.fieldType,
5432
    };
5433
}
5434
5435
/// Lower a field access expression.
5436
unsafe fn lowerFieldAccess 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, access: ast::Access) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5437
    let fieldRef = try lowerFieldRef(self, access);
5438
    return emitRead(self, fieldRef.base, fieldRef.offset, fieldRef.fieldType);
5439
}
5440
5441
/// Compute data pointer and element count for a range into a container.
5442
/// Used by both slice range expressions (`&a[start..end]`) and slice
5443
/// assignments (`a[start..end] = value`).
5444
unsafe fn resolveSliceRangePtr 'arena 'phase 'function (
5445
    self: &mut FnLowerer 'arena 'phase 'function,
5446
    container: *ast::Node,
5447
    range: ast::Range,
5448
    info: resolver::SliceRangeInfo
5449
) -> SliceRangeResult throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5450
    let baseVal = try lowerExpr(self, container);
5451
    let baseReg = emitValToReg(self, baseVal);
5452
5453
    // Extract data pointer and container length.
5454
    // Slice from array: the base register is the data pointer.
5455
    let mut dataReg = baseReg;
5456
    if info.capacity == nil { // Slice from slice.
5457
        set dataReg = loadSlicePtr(self, baseReg);
5458
    }
5459
    let containerLen = containerLength(self, baseReg, info.capacity);
5460
5461
    // Compute range bounds.
5462
    let mut startVal: il::Val = il::Val::Imm(0);
5463
    if let start = range.start {
5464
        set startVal = try lowerExpr(self, start);
5465
    }
5466
    let mut endVal = containerLen;
5467
    if let end = range.end {
5468
        set endVal = try lowerExpr(self, end);
5469
    }
5470
5471
    // Runtime slice bounds checks for dynamic range expressions.
5472
    if not isLtEq(startVal, containerLen) {
5473
        try emitTrapIfLt(self, il::Type::W32, containerLen, startVal);
5474
    }
5475
    if not isLtEq(endVal, containerLen) {
5476
        try emitTrapIfLt(self, il::Type::W32, containerLen, endVal);
5477
    }
5478
    if startVal <> il::Val::Imm(0) and endVal <> containerLen and not isLtEq(startVal, endVal) {
5479
        try emitTrapIfLt(self, il::Type::W32, endVal, startVal);
5480
    }
5481
5482
    // If the start value is known to be zero, the count is just the end
5483
    // value. Otherwise, we have to compute it.
5484
    let mut count = endVal;
5485
5486
    // Only compute range offset and count if the start value is not
5487
    // statically known to be zero.
5488
    if startVal <> il::Val::Imm(0) {
5489
        // Offset the data pointer by the start value.
5490
        set dataReg = emitElem(
5491
            self, resolver::getTypeLayout(*info.itemType).size, dataReg, startVal
5492
        );
5493
        // Compute the count as `end - start`.
5494
        let lenReg = nextReg(self);
5495
        emit(self, il::Instr::BinOp {
5496
            op: il::BinOp::Sub,
5497
            typ: il::Type::W32,
5498
            dst: lenReg,
5499
            a: endVal,
5500
            b: startVal,
5501
        });
5502
        set count = il::Val::Reg(lenReg);
5503
    }
5504
    return SliceRangeResult { dataReg, count };
5505
}
5506
5507
/// Lower a slice range expression into a slice header value.
5508
unsafe fn lowerSliceRange 'arena 'phase 'function (
5509
    self: &mut FnLowerer 'arena 'phase 'function,
5510
    container: *ast::Node,
5511
    range: ast::Range,
5512
    sliceNode: *ast::Node
5513
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5514
    let info = resolver::sliceRangeInfoFor(self.low.resolver, sliceNode) else {
5515
        throw LowerError::MissingMetadata;
5516
    };
5517
    let r = try resolveSliceRangePtr(self, container, range, info);
5518
    return try buildSliceValue(
5519
        self, info.itemType, info.mutable, il::Val::Reg(r.dataReg), r.count, r.count
5520
    );
5521
}
5522
5523
/// Lower an address-of (`&x`) expression.
5524
unsafe fn lowerAddressOf 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, addr: ast::AddressOf) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5525
    // Handle subscript: `&ary[i]` or `&ary[start..end]`.
5526
    if let case ast::NodeValue::Subscript { container, index } = addr.target.value {
5527
        if let case ast::NodeValue::Range(range) = index.value {
5528
            return try lowerSliceRange(self, container, range, node);
5529
        }
5530
        let result = try lowerElemPtr(self, container, index);
5531
5532
        return il::Val::Reg(result.elemReg);
5533
    }
5534
    // Handle field address: `&x.field`.
5535
    if let case ast::NodeValue::FieldAccess(access) = addr.target.value {
5536
        let fieldRef = try lowerFieldRef(self, access);
5537
        let ptr = emitPtrOffset(self, fieldRef.base, fieldRef.offset);
5538
5539
        return il::Val::Reg(ptr);
5540
    }
5541
    // A qualified constant or static uses its resolved owner's data symbol.
5542
    if let case ast::NodeValue::ScopeAccess(_) = addr.target.value {
5543
        let sym = resolver::symbolFor(self.low.resolver, addr.target) else {
5544
            throw LowerError::MissingSymbol(addr.target);
5545
        };
5546
        return il::Val::Reg(emitDataAddr(self, sym));
5547
    }
5548
    // Handle variable address: `&x`
5549
    if let case ast::NodeValue::Ident(_) = addr.target.value {
5550
        if let v = lookupLocalVar(&self.vars, addr.target) {
5551
            let val = try useVar(self, v);
5552
            let typ = try typeOf(self, addr.target);
5553
            // For aggregates, the value is already a pointer.
5554
            if isAggregateType(typ) {
5555
                return val;
5556
            }
5557
            // For scalars, if we've already materialized a stack slot for this
5558
            // variable, the SSA value is that slot pointer.
5559
            if self.vars.items[*v].addressTaken {
5560
                // Already address-taken; return existing stack pointer.
5561
                return val;
5562
            }
5563
            // Materialize a stack slot using the declaration's resolved
5564
            // layout so `align(N)` on locals is honored.
5565
            let layout = resolver::getLayout(self.low.resolver, addr.target, typ);
5566
            let slot = emitReserveLayout(self, layout);
5567
            try emitStore(self, slot, 0, typ, val);
5568
            let stackVal = il::Val::Reg(slot);
5569
5570
            set self.vars.items[*v].addressTaken = true;
5571
            defVar(self, v, stackVal);
5572
5573
            return stackVal;
5574
        }
5575
        // Fall back to symbol lookup for constants/statics.
5576
        if let sym = resolver::symbolFor(self.low.resolver, addr.target) {
5577
            return il::Val::Reg(emitDataAddr(self, sym));
5578
        } else {
5579
            throw LowerError::MissingSymbol(node);
5580
        }
5581
    }
5582
    // Handle dereference address: `&(*ptr) = ptr`.
5583
    if let case ast::NodeValue::Deref(target) = addr.target.value {
5584
        return try lowerExpr(self, target);
5585
    }
5586
    // Array literals become slices when their address is taken.
5587
    match addr.target.value {
5588
        case ast::NodeValue::ArrayLit(_),
5589
             ast::NodeValue::ArrayRepeatLit(_) =>
5590
        {
5591
            return try lowerArrayLiteralSlice(self, node, addr.target);
5592
        }
5593
        else => {}
5594
    }
5595
    throw LowerError::UnexpectedNodeValue(addr.target);
5596
}
5597
5598
/// Lower an addressed array literal as a slice.
5599
unsafe fn lowerArrayLiteralSlice 'arena 'phase 'function (
5600
    self: &mut FnLowerer 'arena 'phase 'function,
5601
    sliceNode: *ast::Node,
5602
    arrayNode: *ast::Node
5603
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5604
    let sliceTy = try typeOf(self, sliceNode);
5605
    let case resolver::Type::Slice { item, mutable, .. } = sliceTy else {
5606
        throw LowerError::UnexpectedType(sliceTy);
5607
    };
5608
    let arrayTy = try typeOf(self, arrayNode);
5609
    let case resolver::Type::Array(arrayInfo) = arrayTy else {
5610
        throw LowerError::ExpectedArray;
5611
    };
5612
    let length = arrayInfo.length;
5613
    if length == 0 {
5614
        return try buildSliceValue(
5615
            self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0)
5616
        );
5617
    }
5618
    if resolver::isConstExpr(self.low.resolver, arrayNode) {
5619
        let fnName = self.fnName;
5620
        let mut b = dataBuilder(alloc::arenaAllocator(self.low.arena));
5621
        match arrayNode.value {
5622
            case ast::NodeValue::ArrayLit(elements) =>
5623
                try lowerConstArrayLitInto(self.low, elements, arrayTy, fnName, &mut b),
5624
            case ast::NodeValue::ArrayRepeatLit(repeat) =>
5625
                try lowerConstArrayRepeatInto(self.low, repeat, arrayTy, fnName, &mut b),
5626
            else => throw LowerError::UnexpectedNodeValue(arrayNode),
5627
        }
5628
        let result = dataBuilderFinish(b);
5629
        let alignment = resolver::getTypeLayout(*item).alignment;
5630
        return try lowerConstDataAsSlice(
5631
            self, &result, alignment, not mutable,
5632
            item, mutable, length
5633
        );
5634
    }
5635
    let data = try lowerExpr(self, arrayNode);
5636
    let count = il::Val::Imm(length as i64);
5637
    return try buildSliceValue(self, item, mutable, data, count, count);
5638
}
5639
5640
/// Lower the common element pointer computation for subscript operations.
5641
/// Handles both arrays and slices by resolving the container type, extracting
5642
/// the data pointer (for slices), and emitting an [`il::Instr::Elem`] to compute
5643
/// the element address.
5644
unsafe fn lowerElemPtr 'arena 'phase 'function (
5645
    self: &mut FnLowerer 'arena 'phase 'function, container: *ast::Node, index: *ast::Node
5646
) -> ElemPtrResult throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5647
    let containerTy = try typeOf(self, container);
5648
    let subjectTy = resolver::autoDeref(containerTy);
5649
    let baseVal = try lowerExpr(self, container);
5650
    let indexVal = try lowerExpr(self, index);
5651
    let baseReg = emitValToReg(self, baseVal);
5652
5653
    match subjectTy {
5654
        case resolver::Type::Slice { item, .. } => {
5655
            let elemType = *item;
5656
            let sliceLen = loadSliceLen(self, baseReg);
5657
            // Runtime safety check: index must be strictly less than slice length.
5658
            try emitTrapUnlessCmp(self, il::CmpOp::Ult, il::Type::W32, indexVal, sliceLen);
5659
5660
            let dataReg = loadSlicePtr(self, baseReg);
5661
            let elemLayout = resolver::getTypeLayout(elemType);
5662
            let elemReg = emitElem(self, elemLayout.size, dataReg, indexVal);
5663
            return ElemPtrResult { elemReg, elemType };
5664
        }
5665
        case resolver::Type::Array(arrInfo) => {
5666
            let elemType = *arrInfo.item;
5667
            // Runtime safety check: index must be strictly less than array length.
5668
            // Skip when the index is a compile-time constant, since we check
5669
            // that in the resolver.
5670
            if not resolver::isConstExpr(self.low.resolver, index) {
5671
                let arrLen = il::Val::Imm(arrInfo.length as i64);
5672
                try emitTrapUnlessCmp(self, il::CmpOp::Ult, il::Type::W32, indexVal, arrLen);
5673
            }
5674
            let elemLayout = resolver::getTypeLayout(elemType);
5675
            let elemReg = emitElem(self, elemLayout.size, baseReg, indexVal);
5676
            return ElemPtrResult { elemReg, elemType };
5677
        }
5678
        else => throw LowerError::ExpectedSliceOrArray,
5679
    }
5680
}
5681
5682
/// Lower a dereference expression.
5683
/// Handles both pointer deref (`*ptr`) and record deref (`*r` on single-field
5684
/// unlabeled record). Both read at offset 0 using the resolver-assigned type.
5685
unsafe fn lowerDeref 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, target: *ast::Node) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5686
    let type = try typeOf(self, node);
5687
    let ptrVal = try lowerExpr(self, target);
5688
    let ptrReg = emitValToReg(self, ptrVal);
5689
5690
    let value = emitRead(self, ptrReg, 0, type);
5691
    if let case resolver::Type::Cell { .. } = try typeOf(self, target); isAggregateType(type) {
5692
        return try emitStackVal(self, type, value);
5693
    }
5694
    return value;
5695
}
5696
5697
/// Lower a subscript expression.
5698
unsafe fn lowerSubscript 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, container: *ast::Node, index: *ast::Node) -> il::Val
5699
    throws (LowerError) where 'arena: 'phase, 'phase: 'function
5700
{
5701
    if let case ast::NodeValue::Range(_) = index.value {
5702
        panic "lowerSubscript: range subscript must use address-of (&)";
5703
    }
5704
    let result = try lowerElemPtr(self, container, index);
5705
5706
    return emitRead(self, result.elemReg, 0, result.elemType);
5707
}
5708
5709
/// Lower a let binding.
5710
unsafe fn lowerLet 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, l: ast::Let) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5711
    // Evaluate value.
5712
    let val = try lowerExpr(self, l.value);
5713
    if blockHasTerminator(&self.blockData[*currentBlock(self)].instrs[..]) {
5714
        return;
5715
    }
5716
    try bindLetValue(self, node, l, val);
5717
}
5718
5719
/// Bind an evaluated initializer in the current variable scope.
5720
unsafe fn bindLetValue 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, l: ast::Let, val: il::Val)
5721
    throws (LowerError) where 'arena: 'phase, 'phase: 'function
5722
{
5723
    // Handle placeholder pattern: `let _ = expr;`
5724
    if let case ast::NodeValue::Placeholder = l.ident.value {
5725
        return;
5726
    }
5727
    let case ast::NodeValue::Ident(name) = l.ident.value else {
5728
        throw LowerError::ExpectedIdentifier;
5729
    };
5730
    let typ = try typeOf(self, l.ident);
5731
    let ilType = ilType(self.low, typ);
5732
    let mut varVal = val;
5733
5734
    // Aggregates with persistent storage need a local copy to avoid aliasing.
5735
    // Temporaries such as literals or call results can be adopted directly.
5736
    // This is because aggregates are represented as memory addresses
5737
    // internally, even though they have value semantics, so without an explicit
5738
    // copy, only the address is is written. Function calls on the other hand
5739
    // reserve their own local stack space, so copying would be redundant.
5740
    // Void variant literals (e.g. `Option::None`) use scope access syntax and
5741
    // are flagged as place expressions, but they are freshly constructed
5742
    // temporaries with no persistent storage.
5743
    if isAggregateType(typ) and
5744
        ast::isPlaceExpr(l.value) and
5745
        not resolver::isCellDeref(self.low.resolver, l.value) and
5746
        voidVariantIndex(self.low.resolver, l.value) == nil {
5747
        set varVal = try emitStackVal(self, typ, val);
5748
    }
5749
5750
    // If the resolver determined that this variable's address is taken
5751
    // anywhere in the function, allocate a stack slot immediately so the
5752
    // SSA value is always a pointer. This avoids mixing integer and pointer
5753
    // values in loop phis when `&var` or `&mut var` appears inside a loop.
5754
    if not isAggregateType(typ) {
5755
        if let sym = resolver::symbolFor(self.low.resolver, node) {
5756
            if let case resolver::SymbolData::Value { addressTaken, .. } = sym.data; addressTaken {
5757
                let layout = resolver::getLayout(self.low.resolver, node, typ);
5758
                let slot = emitReserveLayout(self, layout);
5759
                try emitStore(self, slot, 0, typ, varVal);
5760
5761
                let v = newVar(self, name, ilType, l.mutable, il::Val::Reg(slot));
5762
                set self.vars.items[*v].addressTaken = true;
5763
5764
                return;
5765
            }
5766
        }
5767
    }
5768
    let _ = newVar(self, name, ilType, l.mutable, varVal);
5769
}
5770
5771
/// Lower an if statement: `if <cond> { <then> } else { <else> }`.
5772
///
5773
/// With else branch:
5774
///
5775
///     @entry -> (true)  @then ---> @merge <--.
5776
///         |                                   )
5777
///         `---> (false) @else ---------------'
5778
///
5779
/// Without else branch:
5780
///
5781
///     @entry -> (true)  @then ---> @end <--.
5782
///         |                                 )
5783
///         `---- (false) -------------------'
5784
///
5785
unsafe fn lowerIf 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, i: ast::If) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5786
    let thenBlock = try createBlock(self, "then");
5787
5788
    if let elseNode = i.elseBranch { // If-else case.
5789
        let elseBlock = try createBlock(self, "else");
5790
        try emitCondBranch(self, i.condition, thenBlock, elseBlock);
5791
5792
        // Both @then and @else have exactly one predecessor (@entry), so we can
5793
        // seal them immediately.
5794
        try sealBlock(self, thenBlock);
5795
        try sealBlock(self, elseBlock);
5796
5797
        // The merge block is created lazily by [`emitMergeIfUnterminated`]. We
5798
        // only need it if at least one branch doesn't diverge (i.e., needs to
5799
        // continue execution after the `if`). If both branches diverge (eg.
5800
        // both `return`), no merge block is created and control flow
5801
        // doesn't continue past the `if` statement.
5802
        let mut mergeBlock: ?BlockId = nil;
5803
5804
        // Lower the @then block: switch to it, emit its code, then jump to
5805
        // merge if the block doesn't diverge.
5806
        switchToBlock(self, thenBlock);
5807
        try lowerBlock(self, i.thenBranch);
5808
        try emitMergeIfUnterminated(self, &mut mergeBlock);
5809
5810
        // Lower the @else block similarly.
5811
        switchToBlock(self, elseBlock);
5812
        try lowerBlock(self, elseNode);
5813
        try emitMergeIfUnterminated(self, &mut mergeBlock);
5814
5815
        // If a merge block was created (at least one branch flows into it),
5816
        // switch to it for subsequent code. The merge block's predecessors
5817
        // are the branches that jumped to it, so we seal it now.
5818
        // If the merge block is `nil`, both branches diverged and there's no
5819
        // continuation point.
5820
        if let blk = mergeBlock {
5821
            try switchToAndSeal(self, blk);
5822
        }
5823
    } else { // If without `else`.
5824
        // The false branch goes directly to @end, which also serves as the
5825
        // merge point after @then completes.
5826
        let endBlock = try createBlock(self, "merge");
5827
5828
        try emitCondBranch(self, i.condition, thenBlock, endBlock);
5829
5830
        // @then has one predecessor (@entry), seal it immediately.
5831
        // @end is not sealed yet because @then might also jump to it.
5832
        try sealBlock(self, thenBlock);
5833
5834
        // Lower the @then block, then jump to @end and seal it.
5835
        // Unlike the if-else case, @end is always created because there is no
5836
        // else branch that can diverge.
5837
        switchToBlock(self, thenBlock);
5838
        try lowerBlock(self, i.thenBranch);
5839
        try emitJmpAndSeal(self, endBlock);
5840
5841
        // Continue execution at @end.
5842
        try switchToAndSeal(self, endBlock);
5843
    }
5844
}
5845
5846
/// Lower an assignment target that designates a memory location, and return
5847
/// the address to store through. Returns `nil` without emitting anything for
5848
/// targets that aren't memory-backed, such as locals tracked in SSA.
5849
unsafe fn lowerPlace 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: *ast::Node) -> ?FieldRef throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5850
    match target.value {
5851
        case ast::NodeValue::ScopeAccess(_) => {
5852
            let sym = try symOf(self, target);
5853
            let case resolver::SymbolData::Value { type, .. } = sym.data else {
5854
                throw LowerError::ImmutableAssignment;
5855
            };
5856
            return FieldRef { base: emitDataAddr(self, sym), offset: 0, fieldType: type };
5857
        }
5858
        case ast::NodeValue::FieldAccess(access) => {
5859
            return try lowerFieldRef(self, access);
5860
        }
5861
        case ast::NodeValue::Deref(pointer) => {
5862
            // Dereference: `*ptr` or `*r` on a single-field unlabeled record.
5863
            // Both address offset 0 using the resolver-assigned type.
5864
            let base = emitValToReg(self, try lowerExpr(self, pointer));
5865
            return FieldRef { base, offset: 0, fieldType: try typeOf(self, target) };
5866
        }
5867
        case ast::NodeValue::Subscript { container, index } => {
5868
            // Array or slice element: `arr[i]`.
5869
            let elem = try lowerElemPtr(self, container, index);
5870
            return FieldRef { base: elem.elemReg, offset: 0, fieldType: elem.elemType };
5871
        }
5872
        else => return nil,
5873
    }
5874
}
5875
5876
/// Lower a compound assignment whose target is memory-backed, and report
5877
/// whether it was handled. Nothing is emitted when it isn't.
5878
///
5879
/// Compound assignments share their target node with the left operand of the
5880
/// desugared binary expression. Resolve that place once so side effects in a
5881
/// dereference, field parent, or subscript index are not repeated for the store.
5882
unsafe fn lowerCompoundAssign 'arena 'phase 'function (
5883
    self: &mut FnLowerer 'arena 'phase 'function, expr: *ast::Node, binop: ast::BinOp
5884
) -> bool throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5885
    let place = try lowerPlace(self, binop.left) else return false;
5886
    let current = emitRead(self, place.base, place.offset, place.fieldType);
5887
    let left = try applyCoercion(self, binop.left, current);
5888
    let right = try lowerExpr(self, binop.right);
5889
    let exprType = try typeOf(self, expr);
5890
    let result = emitScalarBinOp(
5891
        self, binop.op, ilType(self.low, exprType), left, right, isUnsignedType(exprType)
5892
    );
5893
    let assigned = try applyCoercion(self, expr, result);
5894
    try emitStore(self, place.base, place.offset, place.fieldType, assigned);
5895
5896
    return true;
5897
}
5898
5899
/// Lower an assignment statement.
5900
unsafe fn lowerAssign 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, a: ast::Assign) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5901
    // Slice assignment: `slice[range] = value`.
5902
    if let info = resolver::sliceRangeInfoFor(self.low.resolver, node) {
5903
        let case ast::NodeValue::Subscript { container, index } = a.left.value
5904
            else panic "lowerAssign: slice assign without subscript";
5905
        let case ast::NodeValue::Range(range) = index.value
5906
            else panic "lowerAssign: slice assign without range";
5907
        try lowerSliceAssign(self, a.right, container, range, info);
5908
5909
        return;
5910
    }
5911
    // Compound assignments are represented as `target = target op rhs`, with
5912
    // the exact same target node used in both places. Memory-backed targets
5913
    // must be resolved once so calls in the target are evaluated once.
5914
    if let case ast::NodeValue::BinOp(binop) = a.right.value {
5915
        if binop.left == a.left and try lowerCompoundAssign(self, a.right, binop) {
5916
            return;
5917
        }
5918
    }
5919
    // Evaluate assignment value.
5920
    let rhs = try lowerExpr(self, a.right);
5921
5922
    match a.left.value {
5923
        case ast::NodeValue::Ident(_) => {
5924
            // First try local variable lookup.
5925
            if let v = lookupLocalVar(&self.vars, a.left) {
5926
                if not getVar(&self.vars, v).mutable {
5927
                    throw LowerError::ImmutableAssignment;
5928
                }
5929
                let leftTy = try typeOf(self, a.left);
5930
                if isAggregateType(leftTy) or getVar(&self.vars, v).addressTaken {
5931
                    // Aggregates and address-taken scalars are represented as
5932
                    // pointers to stack memory. Store through the pointer.
5933
                    let val = try useVar(self, v);
5934
                    let dst = emitValToReg(self, val);
5935
5936
                    try emitStore(self, dst, 0, leftTy, rhs);
5937
                } else {
5938
                    // Scalars are tracked directly in SSA. Each assignment
5939
                    // records a new SSA value.
5940
                    defVar(self, v, rhs);
5941
                }
5942
            } else {
5943
                // Fall back to static variable assignment.
5944
                try lowerStaticAssign(self, a.left, rhs);
5945
            }
5946
        }
5947
        else => {
5948
            let place = try lowerPlace(self, a.left) else {
5949
                throw LowerError::UnexpectedNodeValue(a.left);
5950
            };
5951
            try emitStore(self, place.base, place.offset, place.fieldType, rhs);
5952
        }
5953
    }
5954
}
5955
5956
/// Lower `slice[range] = value`.
5957
unsafe fn lowerSliceAssign 'arena 'phase 'function (
5958
    self: &mut FnLowerer 'arena 'phase 'function,
5959
    rhs: *ast::Node,
5960
    container: *ast::Node,
5961
    range: ast::Range,
5962
    info: resolver::SliceRangeInfo
5963
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5964
    let r = try resolveSliceRangePtr(self, container, range, info);
5965
    let elemSize = resolver::getTypeLayout(*info.itemType).size;
5966
    let rhsTy = try typeOf(self, rhs);
5967
5968
    if let case resolver::Type::Slice { .. } = rhsTy {
5969
        // Copy from source slice.
5970
        let srcReg = emitValToReg(self, try lowerExpr(self, rhs));
5971
        let srcData = loadSlicePtr(self, srcReg);
5972
        let srcLen = loadSliceLen(self, srcReg);
5973
5974
        // Trap if source and destination lengths differ.
5975
        try emitTrapUnlessCmp(self, il::CmpOp::Eq, il::Type::W32, r.count, srcLen);
5976
5977
        let bytes = emitTypedBinOp(
5978
            self, il::BinOp::Mul, il::Type::W32, r.count, il::Val::Imm(elemSize as i64)
5979
        );
5980
        try emitByteCopyLoop(self, r.dataReg, srcData, bytes, "copy");
5981
    } else {
5982
        // Fill with scalar value.
5983
        let fillVal = try lowerExpr(self, rhs);
5984
        try emitFillLoop(self, r.dataReg, fillVal, r.count, *info.itemType, elemSize);
5985
    }
5986
}
5987
5988
/// Emit a typed fill loop: `for i in 0..count { dst[i * stride] = value; }`.
5989
fn emitFillLoop 'arena 'phase 'function (
5990
    self: &mut FnLowerer 'arena 'phase 'function,
5991
    dst: il::Reg,
5992
    value: il::Val,
5993
    count: il::Val,
5994
    elemType: resolver::Type,
5995
    elemSize: u32
5996
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5997
    let iReg = nextReg(self);
5998
    let header = try createBlockWithParam(
5999
        self, "fill", il::Param { value: iReg, type: il::Type::W32 }
6000
    );
6001
    let body = try createBlock(self, "fill");
6002
    let done = try createBlock(self, "fill");
6003
6004
    try emitJmpWithArg(self, header, il::Val::Imm(0));
6005
    switchToBlock(self, header);
6006
    try emitBrCmp(self, il::CmpOp::Ult, il::Type::W32, il::Val::Reg(iReg), count, body, done);
6007
6008
    try switchToAndSeal(self, body);
6009
    let dstElem = emitElem(self, elemSize, dst, il::Val::Reg(iReg));
6010
    try emitStore(self, dstElem, 0, elemType, value);
6011
6012
    let nextI = emitTypedBinOp(
6013
        self, il::BinOp::Add, il::Type::W32, il::Val::Reg(iReg), il::Val::Imm(1)
6014
    );
6015
    try emitJmpWithArg(self, header, nextI);
6016
    try sealBlock(self, header);
6017
    try switchToAndSeal(self, done);
6018
}
6019
6020
///////////////////
6021
// Loop Lowering //
6022
///////////////////
6023
6024
// All loop forms are lowered to a common structure:
6025
//
6026
// - Loop header block: evaluates condition if any, branches to body or exit.
6027
// - Body block: executes loop body, may contain break/continue.
6028
// - Step block (for `for` loops): increments counter, jumps back to header.
6029
// - Exit block: target for break statements and normal loop exit.
6030
//
6031
// The loop stack tracks break/continue targets for nested loops.
6032
6033
/// Lower an infinite loop: `loop { <body> }`.
6034
///
6035
///   @entry -> @loop -> @loop
6036
///               |
6037
///               `----> @end
6038
///
6039
unsafe fn lowerLoop 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, body: *ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6040
    let loopBlock = try createBlock(self, "loop");
6041
    let endBlock = try createBlock(self, "merge");
6042
6043
    // Enter the loop with the given break and continue targets.
6044
    // `break` jumps to `endBlock`,
6045
    // `continue` jumps to `loopBlock`.
6046
    enterLoop(self, endBlock, loopBlock);
6047
    // Switch to and jump to the loop block, then lower all loop body statements into it.
6048
    try switchAndJumpTo(self, loopBlock);
6049
    try lowerBlock(self, body);
6050
6051
    // If the loop body doesn't diverge, jump back to the start.
6052
    // This creates the infinite loop.
6053
    // All predecessors are known, we can seal the loop block.
6054
    try emitJmpAndSeal(self, loopBlock);
6055
    // Exit the loop.
6056
    exitLoop(self);
6057
6058
    // Only seal end block if it's actually reachable (ie. has predecessors).
6059
    // If the loop has no breaks and only exits via return, the end block
6060
    // remains unreachable and isn't added to the CFG.
6061
    if predecessorCount(&self.blockData[..], endBlock) > 0 {
6062
        try switchToAndSeal(self, endBlock);
6063
    }
6064
}
6065
6066
/// Lower a while loop: `while <cond> { <body> }`.
6067
///
6068
///   @entry -> @loop -> (true)  @body -> @loop
6069
///               |
6070
///               `----> (false) @end
6071
///
6072
unsafe fn lowerWhile 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, w: ast::While) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6073
    let whileBlock = try createBlock(self, "while");
6074
    let bodyBlock = try createBlock(self, "body");
6075
    let endBlock = try createBlock(self, "merge");
6076
6077
    enterLoop(self, endBlock, whileBlock);
6078
6079
    // Loop condition.
6080
    try switchAndJumpTo(self, whileBlock);
6081
6082
    // Based on the condition, either jump to the body,
6083
    // or to the end of the loop.
6084
    try emitCondBranch(self, w.condition, bodyBlock, endBlock);
6085
6086
    // Lower loop body and jump back to loop condition check.
6087
    try switchToAndSeal(self, bodyBlock);
6088
    try lowerBlock(self, w.body);
6089
    try emitJmpAndSeal(self, whileBlock);
6090
6091
    try switchToAndSeal(self, endBlock);
6092
    exitLoop(self);
6093
}
6094
6095
/// Emit an increment of a variable by `1`.
6096
fn emitIncrement 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, v: Var, typ: il::Type) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6097
    let cur = try useVar(self, v);
6098
    let next = nextReg(self);
6099
6100
    emit(self, il::Instr::BinOp { op: il::BinOp::Add, typ, dst: next, a: cur, b: il::Val::Imm(1) });
6101
    defVar(self, v, il::Val::Reg(next));
6102
}
6103
6104
/// Common for-loop lowering for both range and collection iterators.
6105
///
6106
/// The step block is created lazily (after the loop body) so that it gets
6107
/// a block index higher than all body blocks. This ensures the register
6108
/// allocator processes definitions before uses in forward block order,
6109
/// avoiding stale assignments when a value defined deep in the body flows
6110
/// through the step block as a block argument.
6111
unsafe fn lowerForLoop 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, iter: &ForIter, body: *ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6112
    let loopBlock = try createBlock(self, "loop");
6113
    let bodyBlock = try createBlock(self, "body");
6114
    let endBlock = try createBlock(self, "merge");
6115
6116
    enterLoop(self, endBlock, nil);
6117
    try switchAndJumpTo(self, loopBlock);
6118
6119
    // Emit condition check.
6120
    match *iter {
6121
        case ForIter::Range { valVar, endVal, valType, unsigned, .. } => {
6122
            let curVal = try useVar(self, valVar);
6123
            let cmp = il::CmpOp::Ult if unsigned else il::CmpOp::Slt;
6124
            try emitBrCmp(self, cmp, valType, curVal, endVal, bodyBlock, endBlock);
6125
        }
6126
        case ForIter::Collection { idxVar, lengthVal, .. } => {
6127
            let curIdx = try useVar(self, idxVar);
6128
            try emitBrCmp(self, il::CmpOp::Slt, il::Type::W32, curIdx, lengthVal, bodyBlock, endBlock);
6129
        }
6130
    }
6131
    // Switch to loop body.
6132
    try switchToAndSeal(self, bodyBlock);
6133
6134
    // Emit element binding, only for collections.
6135
    // Reads the element at the current index.
6136
    if let case ForIter::Collection { valVar, idxVar, dataReg, elemType, .. } = *iter {
6137
        if let v = valVar {
6138
            let curIdx = try useVar(self, idxVar);
6139
            let elemReg = emitElem(self, resolver::getTypeLayout(*elemType).size, dataReg, curIdx);
6140
            let val = emitRead(self, elemReg, 0, *elemType);
6141
6142
            defVar(self, v, val);
6143
        }
6144
    }
6145
    // Lower the loop body.
6146
    try lowerBlock(self, body);
6147
6148
    // Check if a `continue` statement created a step block.
6149
    let ctx = currentLoop(self) else panic;
6150
    if let stepBlock = ctx.continueTarget {
6151
        // Body has `continue` statements: jump to step block, emit increment there.
6152
        try emitJmpAndSeal(self, stepBlock);
6153
        switchToBlock(self, stepBlock);
6154
    }
6155
    // Otherwise, emit increment directly in the current block,
6156
    // saving the jump to a separate step block.
6157
    if not blockHasTerminator(&self.blockData[*currentBlock(self)].instrs[..]) {
6158
        match *iter {
6159
            case ForIter::Range { valVar, valType, indexVar, .. } => {
6160
                try emitIncrement(self, valVar, valType);
6161
                if let idxVar = indexVar {
6162
                    try emitIncrement(self, idxVar, il::Type::W32);
6163
                }
6164
            }
6165
            case ForIter::Collection { idxVar, .. } => {
6166
                try emitIncrement(self, idxVar, il::Type::W32);
6167
            }
6168
        }
6169
        try emitJmp(self, loopBlock);
6170
    }
6171
    exitLoop(self);
6172
6173
    try sealBlock(self, loopBlock);
6174
    try sealBlock(self, endBlock);
6175
6176
    switchToBlock(self, endBlock);
6177
}
6178
6179
/// Lower a `for` loop over a range, array, or slice.
6180
unsafe fn lowerFor 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, f: ast::For) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6181
    let savedVarsLen = enterVarScope(&self.vars);
6182
    let info = resolver::forLoopInfoFor(self.low.resolver, node) else {
6183
        throw LowerError::MissingMetadata;
6184
    };
6185
    match info {
6186
        case resolver::ForLoopInfo::Range { valType, range, bindingName, indexName } => {
6187
            let endExpr = range.end else {
6188
                throw LowerError::MissingMetadata;
6189
            };
6190
            let mut startVal = il::Val::Imm(0);
6191
            if let start = range.start {
6192
                set startVal = try lowerExpr(self, start);
6193
            }
6194
            let endVal = try lowerExpr(self, endExpr);
6195
            let iterType = ilType(self.low, *valType);
6196
            let valVar = newVar(self, bindingName, iterType, false, startVal);
6197
6198
            let mut indexVar: ?Var = nil;
6199
            if indexName <> nil { // Optional index always starts at zero.
6200
                set indexVar = newVar(self, indexName, il::Type::W32, false, il::Val::Imm(0));
6201
            }
6202
            let iter = ForIter::Range {
6203
                valVar, indexVar, endVal, valType: iterType,
6204
                unsigned: isUnsignedType(*valType),
6205
            };
6206
6207
            try lowerForLoop(self, &iter, f.body);
6208
        }
6209
        case resolver::ForLoopInfo::Collection { elemType, length, bindingName, indexName } => {
6210
            let containerVal = try lowerExpr(self, f.iterable);
6211
            let containerReg = emitValToReg(self, containerVal);
6212
6213
            let mut dataReg = containerReg;
6214
            let lengthVal = containerLength(self, containerReg, length);
6215
            if length == nil {
6216
                set dataReg = loadSlicePtr(self, containerReg);
6217
            }
6218
            // Declare index value binidng.
6219
            let idxVar = newVar(self, indexName, il::Type::W32, false, il::Val::Imm(0));
6220
6221
            // Declare element value binding.
6222
            let mut valVar: ?Var = nil;
6223
            if bindingName <> nil {
6224
                set valVar = newVar(
6225
                    self,
6226
                    bindingName,
6227
                    ilType(self.low, *elemType),
6228
                    false,
6229
                    il::Val::Undef
6230
                );
6231
            }
6232
            let iter = ForIter::Collection { valVar, idxVar, dataReg, lengthVal, elemType };
6233
6234
            try lowerForLoop(self, &iter, f.body);
6235
        }
6236
    }
6237
    exitVarScope(&mut self.vars, savedVarsLen);
6238
}
6239
6240
/// Lower a break statement.
6241
fn lowerBreak 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6242
    let ctx = currentLoop(self) else {
6243
        throw LowerError::OutsideOfLoop;
6244
    };
6245
    try emitJmp(self, ctx.breakTarget);
6246
}
6247
6248
/// Lower a continue statement.
6249
fn lowerContinue 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6250
    let block = try getOrCreateContinueBlock(self);
6251
    try emitJmp(self, block);
6252
}
6253
6254
/// Emit a return, blitting into the caller's return buffer if needed.
6255
///
6256
/// When the function has a return buffer parameter, the value is blitted
6257
/// into the buffer and the buffer pointer is returned. Otherwise, the value is
6258
/// returned directly.
6259
unsafe fn emitRetVal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, val: il::Val) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6260
    if let retReg = self.returnReg {
6261
        let src = emitValToReg(self, val);
6262
        let size = resolver::getResultLayout(*self.fnType.returnType, self.fnType.throwList).size
6263
            if self.fnType.throwList.len > 0
6264
            else resolver::getTypeLayout(*self.fnType.returnType).size;
6265
6266
        emit(self, il::Instr::Blit { dst: retReg, src, size: il::Val::Imm(size as i64) });
6267
        emit(self, il::Instr::Ret { val: il::Val::Reg(retReg) });
6268
    } else if isSmallAggregate(*self.fnType.returnType) {
6269
        let mut src = emitValToReg(self, val);
6270
        let layout = resolver::getTypeLayout(*self.fnType.returnType);
6271
        if layout.alignment < resolver::PTR_SIZE or layout.size < resolver::PTR_SIZE {
6272
            // The return word must not read beyond the value or its alignment.
6273
            let word = emitReserveLayout(self, resolver::Layout {
6274
                size: resolver::PTR_SIZE, alignment: resolver::PTR_SIZE,
6275
            });
6276
            emitStoreW64At(self, il::Val::Imm(0), word, 0);
6277
            emit(self, il::Instr::Blit { dst: word, src, size: il::Val::Imm(layout.size as i64) });
6278
            set src = word;
6279
        }
6280
        let dst = nextReg(self);
6281
6282
        emit(self, il::Instr::Load { typ: il::Type::W64, dst, src, offset: 0 });
6283
        emit(self, il::Instr::Ret { val: il::Val::Reg(dst) });
6284
    } else {
6285
        emit(self, il::Instr::Ret { val });
6286
    }
6287
}
6288
6289
/// Lower a return statement.
6290
unsafe fn lowerReturnStmt 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, value: ?*ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6291
    let mut val = il::Val::Undef;
6292
    if let expr = value {
6293
        set val = try lowerExpr(self, expr);
6294
    }
6295
    if blockHasTerminator(&self.blockData[*currentBlock(self)].instrs[..]) {
6296
        return;
6297
    }
6298
    set val = try applyCoercion(self, node, val);
6299
    try emitRetVal(self, val);
6300
}
6301
6302
/// Lower a throw statement.
6303
unsafe fn lowerThrowStmt 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, expr: *ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6304
    assert self.fnType.throwList.len > 0;
6305
6306
    let errType = *self.fnType.throwList[0] if self.fnType.throwList.len == 1
6307
        else try typeOf(self, expr);
6308
    let tag = getOrAssignErrorTag(self.low, errType) as i64;
6309
    let errVal = try lowerExpr(self, expr);
6310
    let resultVal = try buildResult(self, tag, errVal, errType);
6311
6312
    try emitRetVal(self, resultVal);
6313
}
6314
6315
/// Ensure a value is in a register (eg. for branch conditions).
6316
fn emitValToReg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, val: il::Val) -> il::Reg where 'arena: 'phase, 'phase: 'function {
6317
    match val {
6318
        case il::Val::Reg(r) => return r,
6319
        case il::Val::Imm(_), il::Val::DataSym(_), il::Val::FnAddr(_) => {
6320
            let dst = nextReg(self);
6321
            emit(self, il::Instr::Copy { dst, val });
6322
            return dst;
6323
        }
6324
        case il::Val::Undef => {
6325
            // TODO: We shouldn't hit this case, right? A register shouldn't be needed
6326
            // if the value is undefined.
6327
            return nextReg(self);
6328
        }
6329
    }
6330
}
6331
6332
/// Lower a logical `and`/`or` with short-circuit evaluation.
6333
///
6334
/// Short-circuit evaluation skips evaluating the right operand when the left
6335
/// operand already determines the result:
6336
///
6337
/// - In `a and b`, if `a` is false, result is false without evaluating `b`.
6338
/// - In `a or b`, if `a` is true, result is true without evaluating `b`.
6339
///
6340
/// This matters when `b` has side effects, or is expensive to evaluate.
6341
///
6342
/// Example: `a and b`
6343
///
6344
///     @entry
6345
///       br %a @then @else;
6346
///     @then
6347
///       // Evaluate b into %b
6348
///       // ...
6349
///       jmp @end(%b);
6350
///     @else
6351
///       jmp @end(0);                  // Skip evaluating b, result is false
6352
///     @end(w8 %result)
6353
///       ret %result;
6354
///
6355
/// Example: `a or b`
6356
///
6357
///     @entry
6358
///       br %a @then @else;
6359
///     @then
6360
///       jmp @end(1);                  // Skip evaluating b, result is true
6361
///     @else
6362
///       // Evaluate b into %b
6363
///       // ...
6364
///       jmp @end(%b);
6365
///     @end(w8 %result)
6366
///       ret %result;
6367
///
6368
unsafe fn lowerLogicalOp 'arena 'phase 'function (
6369
    self: &mut FnLowerer 'arena 'phase 'function,
6370
    binop: ast::BinOp,
6371
    thenLabel: *[u8],
6372
    elseLabel: *[u8],
6373
    mergeLabel: *[u8],
6374
    op: LogicalOp
6375
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6376
    let thenBlock = try createBlock(self, thenLabel);
6377
    let elseBlock = try createBlock(self, elseLabel);
6378
6379
    let resultReg = nextReg(self);
6380
    let mergeBlock = try createBlockWithParam(
6381
        self, mergeLabel, il::Param { value: resultReg, type: il::Type::W8 }
6382
    );
6383
    // Evaluate left operand and branch.
6384
    try emitCondBranch(self, binop.left, thenBlock, elseBlock);
6385
6386
    let targets = logicalTargets(op, thenBlock, elseBlock);
6387
    // Emit short-circuit branch: jump to merge with constant result.
6388
    try switchToAndSeal(self, targets.shortCircuitBlock);
6389
    try emitJmpWithArg(self, mergeBlock, il::Val::Imm(targets.shortCircuitVal));
6390
6391
    // Emit evaluation branch: evaluate right operand and jump to merge.
6392
    try switchToAndSeal(self, targets.evalBlock);
6393
    try emitJmpWithArg(self, mergeBlock, try lowerExpr(self, binop.right));
6394
6395
    try switchToAndSeal(self, mergeBlock);
6396
    return il::Val::Reg(resultReg);
6397
}
6398
6399
/// Lower a conditional expression (`thenExpr if condition else elseExpr`).
6400
unsafe fn lowerCondExpr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, cond: ast::CondExpr) -> il::Val
6401
    throws (LowerError) where 'arena: 'phase, 'phase: 'function
6402
{
6403
    let typ = try typeOf(self, node);
6404
    let thenBlock = try createBlock(self, "cond#then");
6405
    let elseBlock = try createBlock(self, "cond#else");
6406
6407
    if isAggregateType(typ) {
6408
        let dst = try emitReserve(self, typ);
6409
        let layout = resolver::getTypeLayout(typ);
6410
        try emitCondBranch(self, cond.condition, thenBlock, elseBlock);
6411
6412
        let mergeBlock = try createBlock(self, "cond#merge");
6413
        try switchToAndSeal(self, thenBlock);
6414
6415
        let thenVal = emitValToReg(self, try lowerExpr(self, cond.thenExpr));
6416
        emit(self, il::Instr::Blit { dst, src: thenVal, size: il::Val::Imm(layout.size as i64) });
6417
6418
        try emitJmp(self, mergeBlock);
6419
        try switchToAndSeal(self, elseBlock);
6420
6421
        let elseVal = emitValToReg(self, try lowerExpr(self, cond.elseExpr));
6422
        emit(self, il::Instr::Blit { dst, src: elseVal, size: il::Val::Imm(layout.size as i64) });
6423
6424
        try emitJmp(self, mergeBlock);
6425
        try switchToAndSeal(self, mergeBlock);
6426
6427
        return il::Val::Reg(dst);
6428
    } else {
6429
        try emitCondBranch(self, cond.condition, thenBlock, elseBlock);
6430
6431
        let resultType = ilType(self.low, typ);
6432
        let resultReg = nextReg(self);
6433
        let mergeBlock = try createBlockWithParam(
6434
            self, "cond#merge", il::Param { value: resultReg, type: resultType }
6435
        );
6436
        try switchToAndSeal(self, thenBlock);
6437
        try emitJmpWithArg(self, mergeBlock, try lowerExpr(self, cond.thenExpr));
6438
        try switchToAndSeal(self, elseBlock);
6439
        try emitJmpWithArg(self, mergeBlock, try lowerExpr(self, cond.elseExpr));
6440
        try switchToAndSeal(self, mergeBlock);
6441
6442
        return il::Val::Reg(resultReg);
6443
    }
6444
}
6445
6446
/// Select the concrete operand type for a scalar comparison.
6447
fn scalarComparisonType(left: resolver::Type, right: resolver::Type) -> resolver::Type {
6448
    return right if left == resolver::Type::Int else left;
6449
}
6450
6451
/// Convert a binary operator to a comparison op, if applicable.
6452
/// For `Gt`, caller must swap operands: `a > b = b < a`.
6453
/// For `Gte`/`Lte`, caller must swap branch labels: `a >= b = !(a < b)`.
6454
/// For `Lte`, caller must also swap operands: `a <= b = !(b < a)`.
6455
fn cmpOpFrom(op: ast::BinaryOp, unsigned: bool) -> ?il::CmpOp {
6456
    match op {
6457
        case ast::BinaryOp::Eq => return il::CmpOp::Eq,
6458
        case ast::BinaryOp::Ne => return il::CmpOp::Ne,
6459
        case ast::BinaryOp::Lt, ast::BinaryOp::Gt,
6460
             ast::BinaryOp::Gte, ast::BinaryOp::Lte =>
6461
            return il::CmpOp::Ult if unsigned else il::CmpOp::Slt,
6462
        else => return nil,
6463
    }
6464
}
6465
6466
/// Lower a binary operation.
6467
unsafe fn lowerBinOp 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, binop: ast::BinOp) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6468
    // Short-circuit logical operators don't evaluate both operands eagerly.
6469
    if binop.op == ast::BinaryOp::And {
6470
        return try lowerLogicalOp(self, binop, "and#then", "and#else", "and#end", LogicalOp::And);
6471
    } else if binop.op == ast::BinaryOp::Or {
6472
        return try lowerLogicalOp(self, binop, "or#then", "or#else", "or#end", LogicalOp::Or);
6473
    }
6474
6475
    // Handle comparison with a `nil` literal: just check the tag/pointer instead of
6476
    // building a `nil` aggregate and doing full comparison.
6477
    if binop.op == ast::BinaryOp::Eq or binop.op == ast::BinaryOp::Ne {
6478
        let isEq = binop.op == ast::BinaryOp::Eq;
6479
        let leftIsNil = binop.left.value == ast::NodeValue::Nil;
6480
        let rightIsNil = binop.right.value == ast::NodeValue::Nil;
6481
6482
        if leftIsNil {
6483
            return try lowerNilCheck(self, binop.right, isEq);
6484
        } else if rightIsNil {
6485
            return try lowerNilCheck(self, binop.left, isEq);
6486
        }
6487
    }
6488
    // Lower operands.
6489
    let a = try lowerExpr(self, binop.left);
6490
    let b = try lowerExpr(self, binop.right);
6491
    let isComparison = cmpOpFrom(binop.op, false) <> nil;
6492
6493
    // The result type for comparisons is always `bool`, while for arithmetic
6494
    // operations, it's the operand type. We set this appropriately here.
6495
    let nodeTy = try typeOf(self, node);
6496
    let mut resultTy = nodeTy;
6497
6498
    if isComparison {
6499
        let leftTy = try effectiveType(self, binop.left);
6500
        let rightTy = try effectiveType(self, binop.right);
6501
        // Optimize: comparing with a void variant just needs tag comparison.
6502
        if let idx = voidVariantIndex(self.low.resolver, binop.left) {
6503
            return try emitTagCmp(self, binop.op, b, idx, rightTy);
6504
        } else if let idx = voidVariantIndex(self.low.resolver, binop.right) {
6505
            return try emitTagCmp(self, binop.op, a, idx, leftTy);
6506
        }
6507
        // Aggregate types require element-wise comparison.
6508
        // When comparing `?T` with `T`, wrap the scalar side.
6509
        if isAggregateType(leftTy) {
6510
            let mut rhs = b;
6511
            if not isAggregateType(rightTy) {
6512
                set rhs = try wrapInOptional(self, rhs, leftTy);
6513
            }
6514
            return try emitAggregateEqOp(self, binop.op, leftTy, a, rhs);
6515
        }
6516
        if isAggregateType(rightTy) {
6517
            let lhs = try wrapInOptional(self, a, rightTy);
6518
            return try emitAggregateEqOp(self, binop.op, rightTy, lhs, b);
6519
        }
6520
        set resultTy = scalarComparisonType(leftTy, rightTy);
6521
    }
6522
    return emitScalarBinOp(self, binop.op, ilType(self.low, resultTy), a, b, isUnsignedType(resultTy));
6523
}
6524
6525
/// Emit an aggregate equality or inequality comparison.
6526
unsafe fn emitAggregateEqOp 'arena 'phase 'function (
6527
    self: &mut FnLowerer 'arena 'phase 'function,
6528
    op: ast::BinaryOp,
6529
    typ: resolver::Type,
6530
    a: il::Val,
6531
    b: il::Val
6532
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6533
    let regA = emitValToReg(self, a);
6534
    let regB = emitValToReg(self, b);
6535
    let result = try lowerAggregateEq(self, typ, regA, regB, 0);
6536
6537
    if op == ast::BinaryOp::Ne {
6538
        return emitTypedBinOp(self, il::BinOp::Eq, il::Type::W32, result, il::Val::Imm(0));
6539
    }
6540
    return result;
6541
}
6542
6543
/// Emit a scalar binary operation instruction.
6544
fn emitScalarBinOp 'arena 'phase 'function (
6545
    self: &mut FnLowerer 'arena 'phase 'function,
6546
    op: ast::BinaryOp,
6547
    typ: il::Type,
6548
    a: il::Val,
6549
    b: il::Val,
6550
    unsigned: bool
6551
) -> il::Val where 'arena: 'phase, 'phase: 'function {
6552
    let dst = nextReg(self);
6553
    let mut needsExt: bool = false;
6554
    match op {
6555
        case ast::BinaryOp::Add => {
6556
            emit(self, il::Instr::BinOp { op: il::BinOp::Add, typ, dst, a, b });
6557
            set needsExt = true;
6558
        }
6559
        case ast::BinaryOp::Sub => {
6560
            emit(self, il::Instr::BinOp { op: il::BinOp::Sub, typ, dst, a, b });
6561
            set needsExt = true;
6562
        }
6563
        case ast::BinaryOp::Mul => {
6564
            emit(self, il::Instr::BinOp { op: il::BinOp::Mul, typ, dst, a, b });
6565
            set needsExt = true;
6566
        }
6567
        case ast::BinaryOp::Div => {
6568
            let op = il::BinOp::Udiv if unsigned else il::BinOp::Sdiv;
6569
            emit(self, il::Instr::BinOp { op, typ, dst, a, b });
6570
            set needsExt = true;
6571
        }
6572
        case ast::BinaryOp::Mod => {
6573
            let op = il::BinOp::Urem if unsigned else il::BinOp::Srem;
6574
            emit(self, il::Instr::BinOp { op, typ, dst, a, b });
6575
            set needsExt = true;
6576
        }
6577
        case ast::BinaryOp::BitAnd => emit(self, il::Instr::BinOp { op: il::BinOp::And, typ, dst, a, b }),
6578
        case ast::BinaryOp::BitOr => emit(self, il::Instr::BinOp { op: il::BinOp::Or, typ, dst, a, b }),
6579
        case ast::BinaryOp::BitXor => emit(self, il::Instr::BinOp { op: il::BinOp::Xor, typ, dst, a, b }),
6580
        case ast::BinaryOp::Shl => {
6581
            emit(self, il::Instr::BinOp { op: il::BinOp::Shl, typ, dst, a, b });
6582
            set needsExt = true;
6583
        }
6584
        case ast::BinaryOp::Shr => {
6585
            let op = il::BinOp::Ushr if unsigned else il::BinOp::Sshr;
6586
            emit(self, il::Instr::BinOp { op, typ, dst, a, b });
6587
        }
6588
        case ast::BinaryOp::Eq => emit(self, il::Instr::BinOp { op: il::BinOp::Eq, typ, dst, a, b }),
6589
        case ast::BinaryOp::Ne => emit(self, il::Instr::BinOp { op: il::BinOp::Ne, typ, dst, a, b }),
6590
        case ast::BinaryOp::Lt => {
6591
            let op = il::BinOp::Ult if unsigned else il::BinOp::Slt;
6592
            emit(self, il::Instr::BinOp { op, typ, dst, a, b });
6593
        }
6594
        case ast::BinaryOp::Gt => { // `a > b` = `b < a`
6595
            let op = il::BinOp::Ult if unsigned else il::BinOp::Slt;
6596
            emit(self, il::Instr::BinOp { op, typ, dst, a: b, b: a });
6597
        }
6598
        case ast::BinaryOp::Lte => { // `a <= b` = `b >= a`
6599
            let op = il::BinOp::Uge if unsigned else il::BinOp::Sge;
6600
            emit(self, il::Instr::BinOp { op, typ, dst, a: b, b: a });
6601
        }
6602
        case ast::BinaryOp::Gte => {
6603
            let op = il::BinOp::Uge if unsigned else il::BinOp::Sge;
6604
            emit(self, il::Instr::BinOp { op, typ, dst, a, b });
6605
        }
6606
        // Logical xor on booleans is equivalent to not equal.
6607
        case ast::BinaryOp::Xor => emit(self, il::Instr::BinOp { op: il::BinOp::Ne, typ, dst, a, b }),
6608
        // Short-circuit ops are handled elsewhere.
6609
        case ast::BinaryOp::And, ast::BinaryOp::Or => panic,
6610
    }
6611
    // Normalize sub-word arithmetic results so high bits are well-defined.
6612
    // The lowering knows signedness, so it can pick the right extension.
6613
    // [`il::Type::W32`] is handled in the backend via 32-bit instructions.
6614
    if needsExt {
6615
        return normalizeSubword(self, typ, unsigned, il::Val::Reg(dst));
6616
    }
6617
    return il::Val::Reg(dst);
6618
}
6619
6620
/// Normalize sub-word values to well-defined high bits.
6621
fn normalizeSubword 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, typ: il::Type, unsigned: bool, val: il::Val) -> il::Val where 'arena: 'phase, 'phase: 'function {
6622
    if typ == il::Type::W8 or typ == il::Type::W16 {
6623
        let extDst: il::Reg = nextReg(self);
6624
        if unsigned {
6625
            emit(self, il::Instr::Zext { typ, dst: extDst, val });
6626
        } else {
6627
            emit(self, il::Instr::Sext { typ, dst: extDst, val });
6628
        }
6629
        return il::Val::Reg(extDst);
6630
    }
6631
    return val;
6632
}
6633
6634
/// Lower a unary operation.
6635
unsafe fn lowerUnOp 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, unop: ast::UnOp) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6636
    if unop.op == ast::UnaryOp::Neg {
6637
        if let case ast::NodeValue::Number(lit) = unop.value.value {
6638
            return il::Val::Imm((0 - lit.magnitude) as i64);
6639
        }
6640
    }
6641
    let val = try lowerExpr(self, unop.value);
6642
    let t = try typeOf(self, node);
6643
    let typ = ilType(self.low, t);
6644
    return emitScalarUnOp(self, unop.op, typ, val, isUnsignedType(t));
6645
}
6646
6647
/// Emit a unary scalar operation and normalize its subword result.
6648
fn emitScalarUnOp 'arena 'phase 'function (
6649
    self: &mut FnLowerer 'arena 'phase 'function,
6650
    op: ast::UnaryOp,
6651
    typ: il::Type,
6652
    val: il::Val,
6653
    unsigned: bool
6654
) -> il::Val where 'arena: 'phase, 'phase: 'function {
6655
    let dst = nextReg(self);
6656
    let mut needsExt: bool = false;
6657
6658
    match op {
6659
        case ast::UnaryOp::Not => {
6660
            emit(self, il::Instr::BinOp { op: il::BinOp::Eq, typ, dst, a: val, b: il::Val::Imm(0) });
6661
        }
6662
        case ast::UnaryOp::Neg => {
6663
            emit(self, il::Instr::UnOp { op: il::UnOp::Neg, typ, dst, a: val });
6664
            set needsExt = true;
6665
        }
6666
        case ast::UnaryOp::BitNot => {
6667
            emit(self, il::Instr::UnOp { op: il::UnOp::Not, typ, dst, a: val });
6668
            set needsExt = true;
6669
        }
6670
    }
6671
    if needsExt {
6672
        return normalizeSubword(self, typ, unsigned, il::Val::Reg(dst));
6673
    }
6674
    return il::Val::Reg(dst);
6675
}
6676
6677
/// Lower a cast expression (`x as T`).
6678
unsafe fn lowerCast 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, cast: ast::As) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6679
    let val = try lowerExpr(self, cast.value);
6680
6681
    let srcType = try typeOf(self, cast.value);
6682
    let dstType = try typeOf(self, node);
6683
    if resolver::typesEqual(srcType, dstType) {
6684
        return val;
6685
    }
6686
    return lowerNumericCast(self, val, srcType, dstType);
6687
}
6688
6689
/// Check whether a resolver type is a signed integer type.
6690
fn isSignedType(t: resolver::Type) -> bool {
6691
    match t {
6692
        case resolver::Type::I8, resolver::Type::I16, resolver::Type::I32, resolver::Type::I64,
6693
             resolver::Type::Int => return true,
6694
        else => return false,
6695
    }
6696
}
6697
6698
/// Check whether a resolver type is an unsigned integer type.
6699
fn isUnsignedType(t: resolver::Type) -> bool {
6700
    match t {
6701
        case resolver::Type::U8, resolver::Type::U16, resolver::Type::U32, resolver::Type::U64 => return true,
6702
        else => return false,
6703
    }
6704
}
6705
6706
/// Lower a string literal to a slice value.
6707
///
6708
/// String literals are stored as global data and the result is a slice
6709
/// pointing to the data with the appropriate length.
6710
unsafe fn lowerStringLit 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, s: *[u8]) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6711
    // Get the slice type from the node.
6712
    let sliceTy = try typeOf(self, node);
6713
    let case resolver::Type::Slice { item, mutable, .. } = sliceTy
6714
        else throw LowerError::ExpectedSliceOrArray;
6715
    // Build the string data value.
6716
    let mut builder = dataBuilder(alloc::arenaAllocator(self.low.arena));
6717
    dataBuilderPush(&mut builder, il::DataValue { item: il::DataItem::Str(s), count: 1 });
6718
    let result = dataBuilderFinish(builder);
6719
6720
    return try lowerConstDataAsSlice(
6721
        self, &result, 1, true, item, mutable, s.len
6722
    );
6723
}
6724
6725
/// Lower a builtin call expression.
6726
unsafe fn lowerBuiltinCall 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, kind: ast::Builtin, args: *[*ast::Node]) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6727
    match kind {
6728
        case ast::Builtin::SliceOf => return try lowerSliceOf(self, node, args),
6729
        case ast::Builtin::SizeOf, ast::Builtin::AlignOf => {
6730
            let constVal = resolver::constValueEntry(self.low.resolver, node) else {
6731
                throw LowerError::MissingConst(node);
6732
            };
6733
            return try constValueToVal(self, constVal, node);
6734
        }
6735
    }
6736
}
6737
6738
/// Lower a `@sliceOf(ptr, len)` or `@sliceOf(ptr, len, cap)` builtin call.
6739
unsafe fn lowerSliceOf 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, args: *[*ast::Node]) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6740
    if args.len <> 2 and args.len <> 3 {
6741
        throw LowerError::InvalidArgCount;
6742
    }
6743
    let sliceTy = try typeOf(self, node);
6744
    let case resolver::Type::Slice { item, mutable, .. } = sliceTy
6745
        else throw LowerError::ExpectedSliceOrArray;
6746
    let ptrVal = try lowerExpr(self, args[0]);
6747
    let lenVal = try lowerExpr(self, args[1]);
6748
    let mut capVal = lenVal;
6749
    if args.len == 3 {
6750
        set capVal = try lowerExpr(self, args[2]);
6751
    }
6752
    if not isLtEq(lenVal, capVal) {
6753
        try emitTrapIfLt(self, il::Type::W32, capVal, lenVal);
6754
    }
6755
    return try buildSliceValue(self, item, mutable, ptrVal, lenVal, capVal);
6756
}
6757
6758
/// Prepare a throwing call and record deferred session writes when required.
6759
/// The caller supplies an empty initialization slot.
6760
unsafe fn prepareTryCall 'arena 'phase 'function (
6761
    self: &mut FnLowerer 'arena 'phase 'function,
6762
    node: *ast::Node,
6763
    call: ast::Call,
6764
    initialization: &mut ?SessionInitialization
6765
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6766
    let extra = resolver::nodeData(self.low.resolver, node).extra;
6767
    if let case resolver::NodeExtra::SessionAllocation(allocation) = extra {
6768
        let prepared = try prepareSessionAllocation(self, call, allocation);
6769
        set *initialization = prepared;
6770
        return prepared.result;
6771
    }
6772
    if let case resolver::NodeExtra::TraitMethodCall { traitInfo, methodIndex } = extra {
6773
        return try lowerTraitMethodCall(self, node, call, traitInfo, methodIndex);
6774
    }
6775
    if let case resolver::NodeExtra::MethodCall { method } = extra {
6776
        return try lowerMethodCall(self, node, call, method);
6777
    }
6778
    return try lowerCall(self, node, call);
6779
}
6780
6781
/// Lower a `try` expression.
6782
unsafe fn lowerTry 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, t: ast::Try) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6783
    let case ast::NodeValue::Call(callExpr) = t.expr.value else {
6784
        throw LowerError::ExpectedCall;
6785
    };
6786
    let calleeTy = try typeOf(self, callExpr.callee);
6787
    let case resolver::Type::Fn(calleeInfo) = calleeTy else {
6788
        throw LowerError::ExpectedFunction;
6789
    };
6790
    let okValueTy = *calleeInfo.returnType; // The type of the success payload.
6791
6792
    // Type of the try expression, which is either the return type of the function
6793
    // if successful, or an optional of it, if using `try?`.
6794
    let tryExprTy = try typeOf(self, node);
6795
    // Check for trait method dispatch or standalone method call.
6796
    let mut initialization: ?SessionInitialization = nil;
6797
    let resVal = try prepareTryCall(self, t.expr, callExpr, &mut initialization);
6798
    if blockHasTerminator(&self.blockData[*currentBlock(self)].instrs[..]) {
6799
        return il::Val::Undef;
6800
    }
6801
    let base = emitValToReg(self, resVal); // The result value.
6802
    let tagReg = resultTagReg(self, base); // The result tag.
6803
6804
    let okBlock = try createBlock(self, "ok"); // Block if success.
6805
    let errBlock = try createBlock(self, "err"); // Block if failure.
6806
6807
    let mut mergeBlock: ?BlockId = nil;
6808
    let mut resultSlot: ?il::Reg = nil; // `try` result value will be stored here.
6809
6810
    // Check if the `try` returns a success value or not. If so, reserve
6811
    // space for it.
6812
    let isVoid = tryExprTy == resolver::Type::Void or tryExprTy == resolver::Type::Never;
6813
    if not isVoid {
6814
        set resultSlot = try emitReserve(self, tryExprTy);
6815
    }
6816
    // Branch on tag: zero means ok, non-zero means error.
6817
    try emitBr(self, tagReg, errBlock, okBlock);
6818
6819
    // We can now seal the blocks since all predecessors are known.
6820
    try sealBlock(self, okBlock);
6821
    try sealBlock(self, errBlock);
6822
6823
    // Success path: extract the successful value from the result and store it
6824
    // in the result slot for later use after the merge point.
6825
    switchToBlock(self, okBlock);
6826
    if let prepared = initialization {
6827
        try initializeSessionAllocation(self, prepared);
6828
    }
6829
6830
    if okValueTy == resolver::Type::Never {
6831
        emit(self, il::Instr::Unreachable);
6832
    } else if let slot = resultSlot {
6833
        // Extract the success payload. If the result type differs from the payload
6834
        // type (e.g. `try?` wrapping `T` into `?T`), wrap the value.
6835
        let payloadVal = tvalPayloadVal(self, base, okValueTy, RESULT_VAL_OFFSET);
6836
        let mut okVal = payloadVal;
6837
6838
        if t.returnsOptional and tryExprTy <> okValueTy {
6839
            set okVal = try wrapInOptional(self, payloadVal, tryExprTy);
6840
        }
6841
        try emitStore(self, slot, 0, tryExprTy, okVal);
6842
    }
6843
    // Jump to merge block if unterminated.
6844
    try emitMergeIfUnterminated(self, &mut mergeBlock);
6845
6846
    // Error path: handle the failure case based on the try expression variant.
6847
    switchToBlock(self, errBlock);
6848
6849
    if t.returnsOptional {
6850
        // `try?` converts errors to `nil` -- store the `nil` and continue.
6851
        if let slot = resultSlot {
6852
            let errVal = try buildNilOptional(self, tryExprTy);
6853
            try emitStore(self, slot, 0, tryExprTy, errVal);
6854
        }
6855
        try emitMergeIfUnterminated(self, &mut mergeBlock);
6856
    } else if t.catches.len > 0 {
6857
        // `try ... catch` -- handle the error.
6858
        let firstNode = t.catches[0];
6859
        let case ast::NodeValue::CatchClause(first) = firstNode.value
6860
            else panic "lowerTry: expected CatchClause";
6861
6862
        if first.typeNode <> nil or t.catches.len > 1 {
6863
            // Typed multi-catch: switch on global error tag.
6864
            try lowerMultiCatch(self, t.catches, calleeInfo, base, tagReg, &mut mergeBlock);
6865
        } else {
6866
            // Single untyped catch clause.
6867
            let savedVarsLen = enterVarScope(&self.vars);
6868
            if let binding = first.binding {
6869
                let case ast::NodeValue::Ident(name) = binding.value else {
6870
                    throw LowerError::ExpectedIdentifier;
6871
                };
6872
                let errTy = *calleeInfo.throwList[0];
6873
                let errVal = tvalPayloadVal(self, base, errTy, RESULT_VAL_OFFSET);
6874
                let _ = newVar(self, name, ilType(self.low, errTy), false, errVal);
6875
            }
6876
            try lowerBlock(self, first.body);
6877
            try emitMergeIfUnterminated(self, &mut mergeBlock);
6878
            exitVarScope(&mut self.vars, savedVarsLen);
6879
        }
6880
    } else if t.shouldPanic {
6881
        // `try!` -- panic on error, emit unreachable since control won't continue.
6882
        // TODO: We should have some kind of `panic` instruction?
6883
        emit(self, il::Instr::Unreachable);
6884
    } else {
6885
        // Plain `try` -- propagate the error to the caller by returning early.
6886
        // Forward the callee's global error tag and payload directly.
6887
        let callerLayout = resolver::getResultLayout(
6888
            *self.fnType.returnType, self.fnType.throwList
6889
        );
6890
        let calleeErrSize = maxErrSize(calleeInfo.throwList);
6891
        let dst = emitReserveLayout(self, callerLayout);
6892
6893
        emitStoreW64At(self, il::Val::Reg(tagReg), dst, TVAL_TAG_OFFSET);
6894
        let srcPayload = emitPtrOffset(self, base, RESULT_VAL_OFFSET);
6895
        let dstPayload = emitPtrOffset(self, dst, RESULT_VAL_OFFSET);
6896
        emit(self, il::Instr::Blit { dst: dstPayload, src: srcPayload, size: il::Val::Imm(calleeErrSize as i64) });
6897
6898
        try emitRetVal(self, il::Val::Reg(dst));
6899
    }
6900
6901
    // Switch to the merge block if one was created. If all paths diverged
6902
    // (e.g both success and error returned), there's no merge block.
6903
    if let blk = mergeBlock {
6904
        try switchToAndSeal(self, blk);
6905
    } else {
6906
        return il::Val::Undef;
6907
    }
6908
    // Return the result value. For `void` expressions, return undefined.
6909
    // For aggregates, return the slot pointer; for scalars, load the value.
6910
    if let slot = resultSlot {
6911
        if isAggregateType(tryExprTy) {
6912
            return il::Val::Reg(slot);
6913
        }
6914
        return emitLoad(self, slot, 0, tryExprTy);
6915
    } else { // Void return.
6916
        return il::Val::Undef;
6917
    }
6918
}
6919
6920
/// Destination and optional payload type for one catch clause.
6921
record CatchTarget: Copy {
6922
    /// Block that receives the selected error.
6923
    block: BlockId,
6924
    /// Error type for a typed clause, or nil for a catch-all.
6925
    errorType: ?resolver::Type,
6926
}
6927
6928
/// Read a catch destination that the first lowering pass initialized.
6929
fn catchTarget(targets: &[?CatchTarget], index: u32) -> CatchTarget {
6930
    let target = targets[index] else panic "catchTarget: missing catch destination";
6931
    return target;
6932
}
6933
6934
/// Lower typed multi-catch clauses.
6935
///
6936
/// Emits a switch on the global error tag to dispatch to the correct catch
6937
/// clause. Each typed clause extracts the error payload for its specific type
6938
/// and binds it to the clause's identifier.
6939
unsafe fn lowerMultiCatch 'arena 'phase 'function (
6940
    self: &mut FnLowerer 'arena 'phase 'function,
6941
    catches: *[*ast::Node],
6942
    calleeInfo: *resolver::FnType,
6943
    base: il::Reg,
6944
    tagReg: il::Reg,
6945
    mergeBlock: &mut ?BlockId
6946
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6947
    let entry = currentBlock(self);
6948
6949
    // First pass: create blocks, resolve error types, and build switch cases.
6950
    let mut targets: [?CatchTarget; MAX_CATCH_CLAUSES] = [nil; MAX_CATCH_CLAUSES];
6951
    let mut cases: *mut [LowerSwitchCase] = &mut [];
6952
    let mut defaultIdx: ?u32 = nil;
6953
6954
    for clauseNode, i in catches {
6955
        let case ast::NodeValue::CatchClause(clause) = clauseNode.value
6956
            else panic "lowerMultiCatch: expected CatchClause";
6957
6958
        let block = try createBlock(self, "catch");
6959
        addPredecessor(self, block, entry);
6960
6961
        if let typeNode = clause.typeNode {
6962
            let errTy = try typeOf(self, typeNode);
6963
            set targets[i] = CatchTarget { block, errorType: errTy };
6964
6965
            let tag = getOrAssignErrorTag(self.low, errTy) as i64;
6966
            appendSwitchCase(&mut cases, tag, block, &mut [], alloc::arenaAllocator(self.arena));
6967
        } else {
6968
            set targets[i] = CatchTarget { block, errorType: nil };
6969
            set defaultIdx = i;
6970
        }
6971
    }
6972
6973
    // Emit switch. Default target is the catch-all block, or an unreachable block.
6974
    let mut selectedDefault: ?BlockId = nil;
6975
    if let idx = defaultIdx {
6976
        set selectedDefault = catchTarget(&targets[..], idx).block;
6977
    } else {
6978
        let block = try createBlock(self, "unreachable");
6979
        addPredecessor(self, block, entry);
6980
        set selectedDefault = block;
6981
    }
6982
    let defaultTarget = selectedDefault else panic "lowerMultiCatch: missing default destination";
6983
    emitSwitchTerminator(
6984
        self,
6985
        il::Val::Reg(tagReg),
6986
        defaultTarget,
6987
        LowerTerminator::Switch {
6988
            defaultTarget,
6989
            defaultArgs: &mut [],
6990
            cases,
6991
        },
6992
    );
6993
6994
    // Second pass: emit each catch clause body.
6995
    for clauseNode, i in catches {
6996
        let case ast::NodeValue::CatchClause(clause) = clauseNode.value
6997
            else panic "lowerMultiCatch: expected CatchClause";
6998
6999
        let target = catchTarget(&targets[..], i);
7000
        try switchToAndSeal(self, target.block);
7001
        let savedVarsLen = enterVarScope(&self.vars);
7002
7003
        if let binding = clause.binding {
7004
            let case ast::NodeValue::Ident(name) = binding.value else {
7005
                throw LowerError::ExpectedIdentifier;
7006
            };
7007
            let errTy = target.errorType else panic "lowerMultiCatch: catch-all with binding";
7008
            let errVal = tvalPayloadVal(self, base, errTy, RESULT_VAL_OFFSET);
7009
7010
            newVar(self, name, ilType(self.low, errTy), false, errVal);
7011
        }
7012
        try lowerBlock(self, clause.body);
7013
        try emitMergeIfUnterminated(self, mergeBlock);
7014
7015
        exitVarScope(&mut self.vars, savedVarsLen);
7016
    }
7017
7018
    // Emit unreachable block if no catch-all.
7019
    if defaultIdx == nil {
7020
        try switchToAndSeal(self, defaultTarget);
7021
        emit(self, il::Instr::Unreachable);
7022
    }
7023
}
7024
7025
/// Emit a byte-copy loop: `for i in 0..size { dst[i] = src[i]; }`.
7026
///
7027
/// Used when `blit` cannot be used because the copy size is dynamic.
7028
/// Terminates the current block and leaves the builder positioned
7029
/// after the loop.
7030
fn emitByteCopyLoop 'arena 'phase 'function (
7031
    self: &mut FnLowerer 'arena 'phase 'function,
7032
    dst: il::Reg,
7033
    src: il::Reg,
7034
    size: il::Val,
7035
    label: *[u8]
7036
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7037
    let iReg = nextReg(self);
7038
    let header = try createBlockWithParam(
7039
        self, label, il::Param { value: iReg, type: il::Type::W32 }
7040
    );
7041
    let body = try createBlock(self, label);
7042
    let done = try createBlock(self, label);
7043
7044
    // Jump to header with initial counter is zero.
7045
    try emitJmpWithArg(self, header, il::Val::Imm(0));
7046
7047
    // Don't seal header yet -- the body will add another predecessor.
7048
    switchToBlock(self, header);
7049
    try emitBrCmp(self, il::CmpOp::Ult, il::Type::W32, il::Val::Reg(iReg), size, body, done);
7050
7051
    // Body: load byte from source, store to destination, increment counter.
7052
    try switchToAndSeal(self, body);
7053
7054
    let srcElem = emitElem(self, 1, src, il::Val::Reg(iReg));
7055
    let byteReg = nextReg(self);
7056
    emit(self, il::Instr::Load { typ: il::Type::W8, dst: byteReg, src: srcElem, offset: 0 });
7057
7058
    let dstElem = emitElem(self, 1, dst, il::Val::Reg(iReg));
7059
    emit(self, il::Instr::Store { typ: il::Type::W8, src: il::Val::Reg(byteReg), dst: dstElem, offset: 0 });
7060
7061
    let nextI = emitTypedBinOp(self, il::BinOp::Add, il::Type::W32, il::Val::Reg(iReg), il::Val::Imm(1));
7062
    // Jump back to header -- this adds body as a predecessor.
7063
    try emitJmpWithArg(self, header, nextI);
7064
7065
    // Now all predecessors of header are known, seal it.
7066
    try sealBlock(self, header);
7067
    try switchToAndSeal(self, done);
7068
}
7069
7070
/// Lower `slice.append(val, allocator)`.
7071
///
7072
/// Emits inline grow-if-needed logic:
7073
///
7074
///     load len, cap from slice header
7075
///     if len < cap: jmp @store
7076
///     else:         jmp @grow
7077
///
7078
///     @grow:
7079
///       newCap = max(cap * 2, 1)
7080
///       call allocator.func(allocator.ctx, newCap * stride, alignment)
7081
///       copy old data to new pointer
7082
///       update slice ptr and cap
7083
///       jmp @store
7084
///
7085
///     @store:
7086
///       store element at ptr + len * stride
7087
///       increment len
7088
///
7089
unsafe fn lowerSliceAppend 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, call: ast::Call, elemType: *resolver::Type) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7090
    let case ast::NodeValue::FieldAccess(access) = call.callee.value
7091
        else throw LowerError::MissingMetadata;
7092
7093
    // Get the address of the slice header.
7094
    let sliceVal = try lowerExpr(self, access.parent);
7095
    let sliceReg = emitValToReg(self, sliceVal);
7096
7097
    // Lower the value to append and the allocator.
7098
    let elemVal = try lowerExpr(self, call.args[0]);
7099
    let allocVal = try lowerExpr(self, call.args[1]);
7100
    let allocReg = emitValToReg(self, allocVal);
7101
7102
    let elemLayout = resolver::getTypeLayout(*elemType);
7103
    let stride = elemLayout.size;
7104
    let alignment = elemLayout.alignment;
7105
7106
    // Load current length and capacity.
7107
    let lenVal = loadSliceLen(self, sliceReg);
7108
    let capVal = loadSliceCap(self, sliceReg);
7109
7110
    // Branch: if length is smaller than capacity, go to @store else @grow.
7111
    let storeBlock = try createBlock(self, "append.store");
7112
    let growBlock = try createBlock(self, "append.grow");
7113
    try emitBrCmp(self, il::CmpOp::Ult, il::Type::W32, lenVal, capVal, storeBlock, growBlock);
7114
    try switchToAndSeal(self, growBlock);
7115
7116
    // -- @grow block ----------------------------------------------------------
7117
7118
    // `newCap = max(cap * 2, 1)`.
7119
    // We are only here when at capacity, so we use `or` with `1` to ensure at least capacity `1`.
7120
    let doubledVal = emitTypedBinOp(self, il::BinOp::Shl, il::Type::W32, capVal, il::Val::Imm(1));
7121
    let newCapVal = emitTypedBinOp(self, il::BinOp::Or, il::Type::W32, doubledVal, il::Val::Imm(1));
7122
7123
    // Call allocator: `a.func(a.ctx, newCap * stride, alignment)`.
7124
    let allocFnReg = nextReg(self);
7125
    emitLoadW64At(self, allocFnReg, allocReg, 0);
7126
7127
    let allocCtxReg = nextReg(self);
7128
    emitLoadW64At(self, allocCtxReg, allocReg, 8);
7129
7130
    let byteSize = emitTypedBinOp(self, il::BinOp::Mul, il::Type::W32, newCapVal, il::Val::Imm(stride as i64));
7131
    let values = [il::Val::Reg(allocCtxReg), byteSize, il::Val::Imm(alignment as i64)];
7132
    let args = try copyVals(self, &values[..]);
7133
7134
    let newPtrReg = nextReg(self);
7135
    emit(self, il::Instr::Call {
7136
        retTy: il::Type::W64,
7137
        dst: newPtrReg,
7138
        func: il::Val::Reg(allocFnReg),
7139
        args,
7140
    });
7141
7142
    // Copy old data byte-by-byte.
7143
    let oldPtrReg = loadSlicePtr(self, sliceReg);
7144
    let copyBytes = emitTypedBinOp(self, il::BinOp::Mul, il::Type::W32, lenVal, il::Val::Imm(stride as i64));
7145
    try emitByteCopyLoop(self, newPtrReg, oldPtrReg, copyBytes, "append");
7146
7147
    // Update slice header.
7148
    emitStoreW64At(self, il::Val::Reg(newPtrReg), sliceReg, SLICE_PTR_OFFSET);
7149
    emitStoreW32At(self, newCapVal, sliceReg, SLICE_CAP_OFFSET);
7150
7151
    try emitJmp(self, storeBlock);
7152
    try switchToAndSeal(self, storeBlock);
7153
7154
    // -- @store block ---------------------------------------------------------
7155
7156
    // Store element at `ptr + len * stride`.
7157
    let ptrReg = loadSlicePtr(self, sliceReg);
7158
    let elemDst = emitElem(self, stride, ptrReg, lenVal);
7159
    try emitStore(self, elemDst, 0, *elemType, elemVal);
7160
7161
    // Increment len.
7162
    let newLen = emitTypedBinOp(self, il::BinOp::Add, il::Type::W32, lenVal, il::Val::Imm(1));
7163
    emitStoreW32At(self, newLen, sliceReg, SLICE_LEN_OFFSET);
7164
7165
    return il::Val::Reg(sliceReg);
7166
}
7167
7168
/// Lower `slice.delete(index)`.
7169
///
7170
/// Bounds-check the index, shift elements after it by one stride
7171
/// via a byte-copy loop, and decrement `len`.
7172
unsafe fn lowerSliceDelete 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, call: ast::Call, elemType: *resolver::Type) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7173
    let case ast::NodeValue::FieldAccess(access) = call.callee.value
7174
        else throw LowerError::MissingMetadata;
7175
7176
    let elemLayout = resolver::getTypeLayout(*elemType);
7177
    let stride = elemLayout.size;
7178
7179
    // Get slice header address.
7180
    let sliceVal = try lowerExpr(self, access.parent);
7181
    let sliceReg = emitValToReg(self, sliceVal);
7182
7183
    // Lower the index argument.
7184
    let indexVal = try lowerExpr(self, call.args[0]);
7185
7186
    // Load len and bounds-check: index must be smaller than length.
7187
    let lenVal = loadSliceLen(self, sliceReg);
7188
    try emitTrapUnlessCmp(self, il::CmpOp::Ult, il::Type::W32, indexVal, lenVal);
7189
7190
    // Compute the destination and source for the shift.
7191
    let ptrReg = loadSlicePtr(self, sliceReg);
7192
    let dst = emitElem(self, stride, ptrReg, indexVal);
7193
7194
    // `src = dst + stride`.
7195
    let src = emitPtrOffset(self, dst, stride as i32);
7196
7197
    // Move `(len - index - 1) * stride`.
7198
    let tailLen = emitTypedBinOp(self, il::BinOp::Sub, il::Type::W32, lenVal, indexVal);
7199
    let tailLenMinusOne = emitTypedBinOp(self, il::BinOp::Sub, il::Type::W32, tailLen, il::Val::Imm(1));
7200
    let moveBytes = emitTypedBinOp(self, il::BinOp::Mul, il::Type::W32, tailLenMinusOne, il::Val::Imm(stride as i64));
7201
7202
    // Shift elements left via byte-copy loop.
7203
    // When deleting the last element, the loop is a no-op.
7204
    try emitByteCopyLoop(self, dst, src, moveBytes, "delete");
7205
    // Decrement length.
7206
    let newLen = emitTypedBinOp(self, il::BinOp::Sub, il::Type::W32, lenVal, il::Val::Imm(1));
7207
7208
    emitStoreW32At(self, newLen, sliceReg, SLICE_LEN_OFFSET);
7209
}
7210
7211
/// Initializer values retained across a fallible session reservation.
7212
record SessionInitialization: Copy {
7213
    /// Resolved allocation operation and element type.
7214
    allocation: resolver::SessionAllocation,
7215
    /// Runtime reservation result.
7216
    result: il::Val,
7217
    /// Evaluated object, fill value, or source slice.
7218
    value: il::Val,
7219
    /// Evaluated number of elements.
7220
    count: il::Val,
7221
}
7222
7223
/// Evaluate initializer values and reserve their session storage.
7224
unsafe fn prepareSessionAllocation 'arena 'phase 'function (
7225
    self: &mut FnLowerer 'arena 'phase 'function, call: ast::Call, allocation: resolver::SessionAllocation
7226
) -> SessionInitialization throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7227
    let case ast::NodeValue::FieldAccess(access) = call.callee.value else throw LowerError::ExpectedCall;
7228
    let receiver = try lowerExpr(self, access.parent);
7229
    let sessionReg = emitValToReg(self, receiver);
7230
    let dataReg = nextReg(self);
7231
    emitLoadW64At(self, dataReg, sessionReg, TRAIT_OBJ_DATA_OFFSET);
7232
    let vtableReg = nextReg(self);
7233
    emitLoadW64At(self, vtableReg, sessionReg, TRAIT_OBJ_VTABLE_OFFSET);
7234
    let functionReg = nextReg(self);
7235
    emitLoadW64At(
7236
        self, functionReg, vtableReg,
7237
        (allocation.methodIndex * resolver::PTR_SIZE) as i32,
7238
    );
7239
    let value = try lowerCallArg(self, call.args[0], true);
7240
    if blockHasTerminator(&self.blockData[*currentBlock(self)].instrs[..]) {
7241
        return SessionInitialization { allocation, result: il::Val::Undef, value, count: il::Val::Undef };
7242
    }
7243
    let slice = allocation.kind <> resolver::SessionAllocationKind::New;
7244
    let mut count = il::Val::Imm(1);
7245
    if allocation.kind == resolver::SessionAllocationKind::Copy {
7246
        set count = loadSliceLen(self, emitValToReg(self, value));
7247
    } else if allocation.kind == resolver::SessionAllocationKind::Fill {
7248
        set count = try lowerExpr(self, call.args[1]);
7249
        if blockHasTerminator(&self.blockData[*currentBlock(self)].instrs[..]) {
7250
            return SessionInitialization { allocation, result: il::Val::Undef, value, count: il::Val::Undef };
7251
        }
7252
    }
7253
    let layout = resolver::getTypeLayout(*allocation.item);
7254
    let size = layout.size if layout.size > 0 else 1;
7255
    let alignment = layout.alignment if layout.alignment > 0 else 1;
7256
    let runtime = allocation.traitInfo.methods[allocation.methodIndex].fnType;
7257
    let mut args = callArgs(self, runtime);
7258
    args.append(il::Val::Reg(dataReg), self.allocator);
7259
    args.append(il::Val::Imm(size as i64), self.allocator);
7260
    args.append(il::Val::Imm(alignment as i64), self.allocator);
7261
    if slice {
7262
        args.append(count, self.allocator);
7263
    }
7264
    let result = try emitCallValue(self, il::Val::Reg(functionReg), runtime, args);
7265
    return SessionInitialization { allocation, result, value, count };
7266
}
7267
7268
/// Initialize successful reservation storage before its reference is published.
7269
fn initializeSessionAllocation 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, prepared: SessionInitialization)
7270
    throws (LowerError) where 'arena: 'phase, 'phase: 'function
7271
{
7272
    let allocation = prepared.allocation;
7273
    let base = emitValToReg(self, prepared.result);
7274
    unsafe {
7275
        let layout = resolver::getTypeLayout(*allocation.item);
7276
        try emitSessionInitialization(self, prepared, base, layout.size);
7277
    }
7278
}
7279
7280
/// Emit writes to reserved session storage using its resolved item size.
7281
fn emitSessionInitialization 'arena 'phase 'function (
7282
    self: &mut FnLowerer 'arena 'phase 'function, prepared: SessionInitialization,
7283
    base: il::Reg, itemSize: u32
7284
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7285
    let allocation = prepared.allocation;
7286
    let value = prepared.value;
7287
    let count = prepared.count;
7288
    let destination = nextReg(self);
7289
    emitLoadW64At(self, destination, base, RESULT_VAL_OFFSET);
7290
    match allocation.kind {
7291
        case resolver::SessionAllocationKind::New =>
7292
            try emitStore(self, destination, 0, *allocation.item, value),
7293
        case resolver::SessionAllocationKind::Copy => {
7294
            let source = loadSlicePtr(self, emitValToReg(self, value));
7295
            let bytes = emitTypedBinOp(self, il::BinOp::Mul, il::Type::W32, count, il::Val::Imm(itemSize as i64));
7296
            try emitByteCopyLoop(self, destination, source, bytes, "allocate.copy");
7297
        }
7298
        case resolver::SessionAllocationKind::Fill =>
7299
            try emitFillLoop(self, destination, value, count, *allocation.item, itemSize),
7300
    }
7301
}
7302
7303
/// Lower a call expression, which may be a function call or type constructor.
7304
unsafe fn lowerCallOrCtor 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, call: ast::Call) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7305
    let nodeData = resolver::nodeData(self.low.resolver, node).extra;
7306
7307
    // Check for slice method dispatch.
7308
    if let case resolver::NodeExtra::SliceAppend { elemType } = nodeData {
7309
        return try lowerSliceAppend(self, call, elemType);
7310
    }
7311
    if let case resolver::NodeExtra::SliceDelete { elemType } = nodeData {
7312
        try lowerSliceDelete(self, call, elemType);
7313
        return il::Val::Undef;
7314
    }
7315
    // Check for trait method dispatch.
7316
    if let case resolver::NodeExtra::TraitMethodCall { traitInfo, methodIndex } = nodeData {
7317
        return try lowerTraitMethodCall(self, node, call, traitInfo, methodIndex);
7318
    }
7319
    // Check for standalone method call.
7320
    if let case resolver::NodeExtra::MethodCall { method } = nodeData {
7321
        return try lowerMethodCall(self, node, call, method);
7322
    }
7323
    if let sym = resolver::symbolFor(self.low.resolver, call.callee) {
7324
        if let case resolver::SymbolData::Type(_) = sym.data {
7325
            let ty = try typeOf(self, node);
7326
            let case resolver::Type::Nominal(nominal) = ty else throw LowerError::ExpectedRecord;
7327
            return try lowerRecordCtor(self, nominal, call.args);
7328
        }
7329
        if let case resolver::SymbolData::Variant { .. } = sym.data {
7330
            return try lowerUnionCtor(self, node, sym, call);
7331
        }
7332
    }
7333
    return try lowerCall(self, node, call);
7334
}
7335
7336
/// Lower a trait method call through v-table dispatch.
7337
///
7338
/// Given `obj.method(args)` where `obj` is a trait object, emits:
7339
///
7340
///     load w64 %data %obj 0          // data pointer
7341
///     load w64 %vtable %obj 8        // v-table pointer
7342
///     load w64 %fn %vtable <slot>    // function pointer
7343
///     call <retTy> %ret %fn(%data, args...)
7344
///
7345
unsafe fn lowerTraitMethodCall 'arena 'phase 'function (
7346
    self: &mut FnLowerer 'arena 'phase 'function,
7347
    node: *ast::Node,
7348
    call: ast::Call,
7349
    traitInfo: *unsafe resolver::TraitType,
7350
    methodIndex: u32
7351
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7352
    // Method calls look like field accesses.
7353
    let case ast::NodeValue::FieldAccess(access) = call.callee.value
7354
        else throw LowerError::MissingMetadata;
7355
7356
    // Lower the trait object expression.
7357
    let traitObjVal = try lowerExpr(self, access.parent);
7358
    let traitObjReg = emitValToReg(self, traitObjVal);
7359
7360
    // Load data pointer from trait object.
7361
    let dataReg = nextReg(self);
7362
    emit(self, il::Instr::Load {
7363
        typ: il::Type::W64,
7364
        dst: dataReg,
7365
        src: traitObjReg,
7366
        offset: TRAIT_OBJ_DATA_OFFSET,
7367
    });
7368
7369
    // Load v-table pointer from trait object.
7370
    let vtableReg = nextReg(self);
7371
    emit(self, il::Instr::Load {
7372
        typ: il::Type::W64,
7373
        dst: vtableReg,
7374
        src: traitObjReg,
7375
        offset: TRAIT_OBJ_VTABLE_OFFSET,
7376
    });
7377
7378
    // Load function pointer from v-table at the method's slot offset.
7379
    let fnPtrReg = nextReg(self);
7380
    let slotOffset = (methodIndex * resolver::PTR_SIZE) as i32;
7381
7382
    emit(self, il::Instr::Load {
7383
        typ: il::Type::W64,
7384
        dst: fnPtrReg,
7385
        src: vtableReg,
7386
        offset: slotOffset,
7387
    });
7388
    let methodFnType = traitInfo.methods[methodIndex].fnType;
7389
7390
    // Build args: optional return param slot + data pointer (receiver) + user args.
7391
    let mut args = callArgs(self, methodFnType);
7392
    args.append(il::Val::Reg(dataReg), self.allocator);
7393
7394
    for arg, i in call.args {
7395
        let value = try lowerCallArg(
7396
            self, arg, i + 1 < call.args.len
7397
        );
7398
        args.append(value, self.allocator);
7399
    }
7400
    return try emitCallValue(self, il::Val::Reg(fnPtrReg), methodFnType, args);
7401
}
7402
7403
/// Lower a call argument, snapshotting aggregate place expressions before
7404
/// evaluating later arguments. Aggregates are represented by addresses in the
7405
/// IL, so retaining the original address would let a later argument mutation
7406
/// change the value already supplied for this argument.
7407
unsafe fn lowerCallArg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, arg: *ast::Node, hasLater: bool) -> il::Val
7408
    throws (LowerError) where 'arena: 'phase, 'phase: 'function
7409
{
7410
    let val = try lowerExpr(self, arg);
7411
7412
    if hasLater and ast::isPlaceExpr(arg) {
7413
        let argType = try effectiveType(self, arg);
7414
        if isAggregateType(argType) {
7415
            return try emitStackVal(self, argType, val);
7416
        }
7417
    }
7418
    return val;
7419
}
7420
7421
/// Start an argument table with a placeholder for the hidden return pointer.
7422
fn callArgs 'arena 'phase 'function (
7423
    self: &mut FnLowerer 'arena 'phase 'function,
7424
    fnInfo: *resolver::FnType
7425
) -> *mut [il::Val] where 'arena: 'phase, 'phase: 'function {
7426
    let mut args: *mut [il::Val] = &mut [];
7427
    if requiresReturnParam(fnInfo) {
7428
        args.append(il::Val::Undef, self.allocator);
7429
    }
7430
    return args;
7431
}
7432
7433
/// Emit a function call with return-parameter and small-aggregate handling.
7434
///
7435
/// All call lowering paths (regular, trait method, standalone method) converge
7436
/// here after preparing the callee value, function type, and argument array.
7437
/// The `args` slice must already include a slot at index zero for the hidden
7438
/// return parameter; that slot is filled by this function.
7439
fn emitCallValue 'arena 'phase 'function (
7440
    self: &mut FnLowerer 'arena 'phase 'function,
7441
    callee: il::Val,
7442
    fnInfo: *resolver::FnType,
7443
    args: *mut [il::Val],
7444
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7445
    let retTy = *fnInfo.returnType;
7446
7447
    if requiresReturnParam(fnInfo) {
7448
        if fnInfo.throwList.len > 0 {
7449
            unsafe {
7450
                let layout = resolver::getResultLayout(retTy, fnInfo.throwList);
7451
                set args[0] = il::Val::Reg(emitReserveLayout(self, layout));
7452
            }
7453
        } else {
7454
            set args[0] = il::Val::Reg(try emitReserve(self, retTy));
7455
        }
7456
        let dst = nextReg(self);
7457
7458
        unsafe {
7459
            emit(self, il::Instr::Call {
7460
                retTy: il::Type::W64,
7461
                dst,
7462
                func: callee,
7463
                args: (&mut args[..]) as *unsafe mut [il::Val],
7464
            });
7465
        }
7466
        return il::Val::Reg(dst);
7467
    }
7468
    let mut dst: ?il::Reg = nil;
7469
    if retTy <> resolver::Type::Void and retTy <> resolver::Type::Never {
7470
        set dst = nextReg(self);
7471
    }
7472
    unsafe {
7473
        emit(self, il::Instr::Call {
7474
            retTy: ilType(self.low, retTy),
7475
            dst,
7476
            func: callee,
7477
            args: (&mut args[..]) as *unsafe mut [il::Val],
7478
        });
7479
    }
7480
7481
    if retTy == resolver::Type::Never {
7482
        emit(self, il::Instr::Unreachable);
7483
    }
7484
    if let d = dst {
7485
        if isSmallAggregate(retTy) {
7486
            let slot = emitReserveLayout(self, resolver::Layout {
7487
                size: resolver::PTR_SIZE,
7488
                alignment: resolver::PTR_SIZE,
7489
            });
7490
            emit(self, il::Instr::Store {
7491
                typ: il::Type::W64,
7492
                src: il::Val::Reg(d),
7493
                dst: slot,
7494
                offset: 0,
7495
            });
7496
            return il::Val::Reg(slot);
7497
        }
7498
        return il::Val::Reg(d);
7499
    }
7500
    return il::Val::Undef;
7501
}
7502
7503
/// Lower a method receiver expression to a pointer value.
7504
///
7505
/// If the parent is already a pointer type, the value is used directly.
7506
/// If the parent is a value type (eg. a local record), its address is taken.
7507
unsafe fn lowerReceiver 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, parent: *ast::Node, parentTy: resolver::Type) -> il::Val
7508
    throws (LowerError) where 'arena: 'phase, 'phase: 'function
7509
{
7510
    if let case resolver::Type::Pointer { .. } = parentTy {
7511
        // Already a pointer: lower and use directly.
7512
        return try lowerExpr(self, parent);
7513
    }
7514
    // Value type: take its address by lowering it and returning the slot pointer.
7515
    // Aggregate types are already lowered as pointers to stack slots.
7516
    let val = try lowerExpr(self, parent);
7517
    if isAggregateType(parentTy) {
7518
        return val;
7519
    }
7520
    // Scalar value: store to a stack slot and return the slot pointer.
7521
    let layout = resolver::getLayout(self.low.resolver, parent, parentTy);
7522
    let slot = emitReserveLayout(self, layout);
7523
    try emitStore(self, slot, 0, parentTy, val);
7524
7525
    return il::Val::Reg(slot);
7526
}
7527
7528
/// Lower a standalone method call via direct dispatch.
7529
///
7530
/// Given `obj.method(args)` where `method` is a standalone method on a concrete type,
7531
/// emits a direct call with the receiver address as the first argument:
7532
///
7533
///     call <retTy> %ret @Type::method(&obj, args...)
7534
///
7535
unsafe fn lowerMethodCall 'arena 'phase 'function (
7536
    self: &mut FnLowerer 'arena 'phase 'function,
7537
    node: *ast::Node,
7538
    call: ast::Call,
7539
    method: &resolver::MethodEntry,
7540
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7541
    let case ast::NodeValue::FieldAccess(access) = call.callee.value
7542
        else throw LowerError::MissingMetadata;
7543
7544
    // Get the receiver as a pointer.
7545
    let parentTy = try typeOf(self, access.parent);
7546
    let receiverVal = try lowerReceiver(self, access.parent, parentTy);
7547
7548
    let qualName = instanceMethodName(self.low, method.moduleId, method.concreteTypeName, method.name);
7549
    let fnInfo = method.fullFnType;
7550
7551
    // Build args: optional return param slot + receiver + user args.
7552
    let mut args = callArgs(self, fnInfo);
7553
    args.append(receiverVal, self.allocator);
7554
    for arg, i in call.args {
7555
        let value = try lowerCallArg(
7556
            self, arg, i + 1 < call.args.len
7557
        );
7558
        args.append(value, self.allocator);
7559
    }
7560
    return try emitCallValue(self, il::Val::FnAddr(qualName), fnInfo, args);
7561
}
7562
7563
/// Check if a call is to a compiler intrinsic and lower it directly.
7564
unsafe fn lowerIntrinsicCall 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, call: ast::Call) -> ?il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7565
    // Get the callee symbol and check if it's marked as an intrinsic.
7566
    let sym = resolver::symbolFor(self.low.resolver, call.callee) else {
7567
        // Expressions or function pointers may not have an associated symbol.
7568
        return nil;
7569
    };
7570
    if not ast::hasAttribute(sym.attrs, ast::Attribute::Intrinsic) {
7571
        return nil;
7572
    }
7573
    // Check for known intrinsic names.
7574
    if mem::eq(sym.name, "ecall") {
7575
        return try lowerEcall(self, call);
7576
    } else if mem::eq(sym.name, "deviceRead8") {
7577
        return try lowerDevice(self, call, il::Type::W8, false);
7578
    } else if mem::eq(sym.name, "deviceWrite8") {
7579
        return try lowerDevice(self, call, il::Type::W8, true);
7580
    } else if mem::eq(sym.name, "deviceRead16") {
7581
        return try lowerDevice(self, call, il::Type::W16, false);
7582
    } else if mem::eq(sym.name, "deviceWrite16") {
7583
        return try lowerDevice(self, call, il::Type::W16, true);
7584
    } else if mem::eq(sym.name, "deviceRead32") {
7585
        return try lowerDevice(self, call, il::Type::W32, false);
7586
    } else if mem::eq(sym.name, "deviceWrite32") {
7587
        return try lowerDevice(self, call, il::Type::W32, true);
7588
    } else if mem::eq(sym.name, "deviceRead64") {
7589
        return try lowerDevice(self, call, il::Type::W64, false);
7590
    } else if mem::eq(sym.name, "deviceWrite64") {
7591
        return try lowerDevice(self, call, il::Type::W64, true);
7592
    } else if mem::eq(sym.name, "ebreak") {
7593
        return try lowerEbreak(self, call);
7594
    } else if mem::eq(sym.name, "memoryFence") {
7595
        return try lowerMemoryFence(self, call);
7596
    } else {
7597
        throw LowerError::UnknownIntrinsic;
7598
    }
7599
}
7600
7601
/// Lower an ecall intrinsic: `ecall(num, a0, a1, a2, a3) -> i32`.
7602
unsafe fn lowerEcall 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, call: ast::Call) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7603
    if call.args.len <> 5 {
7604
        throw LowerError::InvalidArgCount;
7605
    }
7606
    let num = try lowerExpr(self, call.args[0]);
7607
    let a0 = try lowerExpr(self, call.args[1]);
7608
    let a1 = try lowerExpr(self, call.args[2]);
7609
    let a2 = try lowerExpr(self, call.args[3]);
7610
    let a3 = try lowerExpr(self, call.args[4]);
7611
    let dst = nextReg(self);
7612
7613
    emit(self, il::Instr::Ecall { dst, num, a0, a1, a2, a3 });
7614
7615
    return il::Val::Reg(dst);
7616
}
7617
7618
/// Lower an ebreak intrinsic: `ebreak()`.
7619
unsafe fn lowerEbreak 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, call: ast::Call) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7620
    if call.args.len <> 0 {
7621
        throw LowerError::InvalidArgCount;
7622
    }
7623
    emit(self, il::Instr::Ebreak);
7624
7625
    return il::Val::Undef;
7626
}
7627
7628
/// Lower `memoryFence()`.
7629
unsafe fn lowerMemoryFence 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, call: ast::Call) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7630
    if call.args.len <> 0 {
7631
        throw LowerError::InvalidArgCount;
7632
    }
7633
    emit(self, il::Instr::MemoryFence);
7634
    return il::Val::Undef;
7635
}
7636
7637
/// Resolve callee to an IL value. For direct function calls, use the symbol name.
7638
/// For variables holding function pointers or complex expressions (eg. `array[i]()`),
7639
/// lower the callee expression.
7640
unsafe fn lowerCallee 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, callee: *ast::Node) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7641
    if let sym = resolver::symbolFor(self.low.resolver, callee) {
7642
        if let case ast::NodeValue::FnDecl(_) = sym.node.value {
7643
            // First try to look up the symbol in our registered functions.
7644
            // This handles cross-package calls correctly, since packages are
7645
            // lowered in dependency order.
7646
            if let qualName = lookupSymbolName(self.low, sym.id) {
7647
                return il::Val::FnAddr(qualName);
7648
            }
7649
            // Fall back to computing the qualified name from the module graph.
7650
            // This works for functions in the current package.
7651
            let modId = resolver::moduleIdForSymbol(self.low.resolver, sym);
7652
            return il::Val::FnAddr(qualifyName(self.low, modId, sym.name));
7653
        }
7654
    }
7655
    return try lowerExpr(self, callee);
7656
}
7657
7658
/// Lower a function call expression.
7659
unsafe fn lowerCall 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, call: ast::Call) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7660
    // Check for intrinsic calls before normal call lowering.
7661
    if let intrinsicVal = try lowerIntrinsicCall(self, call) {
7662
        return intrinsicVal;
7663
    }
7664
    let calleeTy = try typeOf(self, call.callee);
7665
    let case resolver::Type::Fn(fnInfo) = calleeTy else {
7666
        throw LowerError::ExpectedFunction;
7667
    };
7668
    let callee = try lowerCallee(self, call.callee);
7669
    let mut args = callArgs(self, fnInfo);
7670
    for arg, i in call.args {
7671
        let value = try lowerCallArg(
7672
            self, arg, i + 1 < call.args.len
7673
        );
7674
        args.append(value, self.allocator);
7675
    }
7676
7677
    return try emitCallValue(self, callee, fnInfo, args);
7678
}
7679
7680
/// Apply coercions requested by the resolver.
7681
unsafe fn applyCoercion 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, val: il::Val) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7682
    let coerce = resolver::coercionFor(self.low.resolver, node) else {
7683
        return val;
7684
    };
7685
    match coerce {
7686
        case resolver::Coercion::OptionalLift(optType) => {
7687
            if let case ast::NodeValue::Nil = node.value {
7688
                return try buildNilOptional(self, optType);
7689
            }
7690
            return try wrapInOptional(self, val, optType);
7691
        }
7692
        case resolver::Coercion::NumericCast { from, to } => {
7693
            return lowerNumericCast(self, val, from, to);
7694
        }
7695
        case resolver::Coercion::ResultWrap => {
7696
            let payloadType = *self.fnType.returnType;
7697
            return try buildResult(self, 0, val, payloadType);
7698
        }
7699
        case resolver::Coercion::TraitObject { traitInfo, inst } => {
7700
            return try buildTraitObject(self, val, traitInfo, inst);
7701
        }
7702
        case resolver::Coercion::Identity => return val,
7703
    }
7704
}
7705
7706
/// Lower an implicit numeric cast coercion.
7707
///
7708
/// Handles widening conversions between integer types. Uses sign-extension
7709
/// for signed source types and zero-extension for unsigned source types.
7710
unsafe fn lowerNumericCast 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, val: il::Val, srcType: resolver::Type, dstType: resolver::Type) -> il::Val where 'arena: 'phase, 'phase: 'function {
7711
    let srcLayout = resolver::getTypeLayout(srcType);
7712
    let dstLayout = resolver::getTypeLayout(dstType);
7713
    return emitNumericCast(self, val, srcType, dstType, srcLayout.size, dstLayout.size);
7714
}
7715
7716
/// Emit an integer conversion from resolved storage sizes and signedness.
7717
fn emitNumericCast 'arena 'phase 'function (
7718
    self: &mut FnLowerer 'arena 'phase 'function, val: il::Val,
7719
    srcType: resolver::Type, dstType: resolver::Type, srcSize: u32, dstSize: u32
7720
) -> il::Val where 'arena: 'phase, 'phase: 'function {
7721
    if srcSize == dstSize {
7722
        // Same size: bit pattern is unchanged, value is returned as-is.
7723
        return val;
7724
    }
7725
    // Widening: extend based on source signedness.
7726
    // Narrowing: truncate and normalize to destination width.
7727
    let widening = srcSize < dstSize;
7728
    let extType = ilType(self.low, srcType) if widening else ilType(self.low, dstType);
7729
    let signed = isSignedType(srcType) if widening else isSignedType(dstType);
7730
    let dst = nextReg(self);
7731
7732
    if signed {
7733
        emit(self, il::Instr::Sext { typ: extType, dst, val });
7734
    } else {
7735
        emit(self, il::Instr::Zext { typ: extType, dst, val });
7736
    }
7737
    return il::Val::Reg(dst);
7738
}
7739
7740
/// Lower a global value symbol.
7741
unsafe fn lowerGlobalValue 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, sym: *unsafe resolver::Symbol, ty: resolver::Type) -> il::Val where 'arena: 'phase, 'phase: 'function {
7742
    // Function pointer reference: return the function's address directly.
7743
    // Functions have no separate storage cell in the data section.
7744
    if let case resolver::Type::Fn(_) = ty {
7745
        return il::Val::Reg(emitFnAddr(self, sym));
7746
    }
7747
    let src = emitDataAddr(self, sym);
7748
7749
    return emitRead(self, src, 0, ty);
7750
}
7751
7752
/// Lower an identifier that refers to a global symbol.
7753
unsafe fn lowerGlobalSymbol 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7754
    // First try to get a compile-time constant value.
7755
    if let constVal = resolver::constValueEntry(self.low.resolver, node) {
7756
        return try constValueToVal(self, constVal, node);
7757
    }
7758
    // Otherwise get the symbol.
7759
    let sym = try symOf(self, node);
7760
7761
    match sym.data {
7762
        case resolver::SymbolData::Constant { type, .. } => {
7763
            let src = emitDataAddr(self, sym);
7764
7765
            return emitRead(self, src, 0, type);
7766
        }
7767
        case resolver::SymbolData::Value { type, .. } =>
7768
            return lowerGlobalValue(self, sym, type),
7769
        else => throw LowerError::UnexpectedNodeValue(node),
7770
    }
7771
}
7772
7773
/// Lower an assignment to a static variable.
7774
unsafe fn lowerStaticAssign 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: *ast::Node, val: il::Val) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7775
    let sym = try symOf(self, target);
7776
    let case resolver::SymbolData::Value { type, .. } = sym.data else {
7777
        throw LowerError::ImmutableAssignment;
7778
    };
7779
    let dst = emitDataAddr(self, sym);
7780
7781
    try emitStore(self, dst, 0, type, val);
7782
}
7783
7784
/// Lower a scope access expression like `Module::Const` or `Union::Variant`.
7785
/// This doesn't handle record literal variants.
7786
unsafe fn lowerScopeAccess 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7787
    // First try to get a compile-time constant value.
7788
    if let constVal = resolver::constValueEntry(self.low.resolver, node) {
7789
        return try constValueToVal(self, constVal, node);
7790
    }
7791
    // Otherwise get the associated symbol.
7792
    let data = resolver::nodeData(self.low.resolver, node);
7793
    let sym = resolver::symbolFor(self.low.resolver, node) else {
7794
        throw LowerError::MissingSymbol(node);
7795
    };
7796
    match sym.data {
7797
        case resolver::SymbolData::Variant { index, .. } => {
7798
            let mut indexValue = index as i64;
7799
            if let idx = voidVariantIndex(self.low.resolver, node) {
7800
                set indexValue = idx;
7801
            }
7802
            // Void union variant like `Option::None`.
7803
            if data.ty == resolver::Type::Unknown {
7804
                throw LowerError::MissingType(node);
7805
            }
7806
            // All-void unions are passed as scalars (the tag byte).
7807
            // Return an immediate instead of building a tagged aggregate.
7808
            if resolver::isVoidUnion(data.ty) {
7809
                return il::Val::Imm(indexValue);
7810
            }
7811
            let unionInfo = unionInfoFromType(data.ty) else {
7812
                throw LowerError::MissingMetadata;
7813
            };
7814
            let valOffset = unionInfo.valOffset as i32;
7815
            return try buildTagged(self, resolver::getTypeLayout(data.ty), indexValue, nil, resolver::Type::Void, 1, valOffset);
7816
        }
7817
        case resolver::SymbolData::Constant { type, .. } => {
7818
            // Constant without compile-time value (e.g. record constant);
7819
            // load from data section.
7820
            let src = emitDataAddr(self, sym);
7821
7822
            // Aggregate constants live in read-only memory.  Return a
7823
            // mutable copy so that callers that assign through the
7824
            // resulting pointer do not fault.
7825
            if isAggregateType(type) {
7826
                let layout = resolver::getTypeLayout(type);
7827
                let dst = emitReserveLayout(self, layout);
7828
                emit(self, il::Instr::Blit { dst, src, size: il::Val::Imm(layout.size as i64) });
7829
7830
                return il::Val::Reg(dst);
7831
            }
7832
            return emitRead(self, src, 0, type);
7833
        }
7834
        case resolver::SymbolData::Value { type, .. } =>
7835
            return lowerGlobalValue(self, sym, type),
7836
        else =>
7837
            throw LowerError::UnexpectedNodeValue(node),
7838
    }
7839
}
7840
7841
/// Lower an expression AST node to an IL value.
7842
/// This is the main expression dispatch, all expression nodes go through here.
7843
unsafe fn lowerExpr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7844
    if self.low.options.debug {
7845
        set self.srcLoc.offset = node.span.offset;
7846
    }
7847
    let val = try lowerExprValue(self, node);
7848
    return try applyCoercion(self, node, val);
7849
}
7850
7851
/// Construct an expression value before its resolver-requested coercion.
7852
unsafe fn lowerExprValue 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
7853
    match node.value {
7854
        case ast::NodeValue::Ident(_) => {
7855
            // First try local variable lookup.
7856
            // Otherwise fall back to global symbol lookup.
7857
            if let v = lookupLocalVar(&self.vars, node) {
7858
                let val = try useVar(self, v);
7859
                if self.vars.items[*v].addressTaken {
7860
                    let typ = try typeOf(self, node);
7861
                    let ptr = emitValToReg(self, val);
7862
                    return emitRead(self, ptr, 0, typ);
7863
                }
7864
                return val;
7865
            } else {
7866
                return try lowerGlobalSymbol(self, node);
7867
            }
7868
        }
7869
        case ast::NodeValue::ScopeAccess(_) => {
7870
            return try lowerScopeAccess(self, node);
7871
        }
7872
        case ast::NodeValue::Number(lit) => {
7873
            return il::Val::Imm(lit.magnitude as i64);
7874
        }
7875
        case ast::NodeValue::Bool(b) => {
7876
            return il::Val::Imm(1) if b else il::Val::Imm(0);
7877
        }
7878
        case ast::NodeValue::Char(c) => {
7879
            return il::Val::Imm(c as i64);
7880
        }
7881
        case ast::NodeValue::Nil => {
7882
            let typ = try typeOf(self, node);
7883
            if let case resolver::Type::Optional(_) = typ {
7884
                return try buildNilOptional(self, typ);
7885
            } else if let case resolver::Type::Nil = typ {
7886
                // Standalone `nil` without a concrete optional type. We can't
7887
                // generate a proper value representation.
7888
                throw LowerError::MissingType(node);
7889
            } else {
7890
                throw LowerError::NilInNonOptional;
7891
            }
7892
        }
7893
        case ast::NodeValue::RecordLit(lit) => {
7894
            return try lowerRecordLit(self, node, lit);
7895
        }
7896
        case ast::NodeValue::AddressOf(addr) => {
7897
            return try lowerAddressOf(self, node, addr);
7898
        }
7899
        case ast::NodeValue::Deref(target) => {
7900
            return try lowerDeref(self, node, target);
7901
        }
7902
        case ast::NodeValue::BinOp(binop) => {
7903
            return try lowerBinOp(self, node, binop);
7904
        }
7905
        case ast::NodeValue::UnOp(unop) => {
7906
            return try lowerUnOp(self, node, unop);
7907
        }
7908
        case ast::NodeValue::Subscript { container, index } => {
7909
            return try lowerSubscript(self, node, container, index);
7910
        }
7911
        case ast::NodeValue::BuiltinCall { kind, args } => {
7912
            return try lowerBuiltinCall(self, node, kind, args);
7913
        }
7914
        case ast::NodeValue::Call(call) => {
7915
            return try lowerCallOrCtor(self, node, call);
7916
        }
7917
        case ast::NodeValue::Try(t) => {
7918
            return try lowerTry(self, node, t);
7919
        }
7920
        case ast::NodeValue::FieldAccess(access) => {
7921
            // Check for compile-time constant (e.g., `arr.len` on fixed-size arrays).
7922
            if let constVal = resolver::constValueEntry(self.low.resolver, node) {
7923
                match constVal {
7924
                    // TODO: Handle `u32` values that don't fit in an `i32`.
7925
                    //       Perhaps just store the `ConstInt`.
7926
                    case resolver::ConstValue::Int(i) => return il::Val::Imm(constIntToI64(i)),
7927
                    else => return try lowerFieldAccess(self, access),
7928
                }
7929
            } else {
7930
                return try lowerFieldAccess(self, access);
7931
            }
7932
        }
7933
        case ast::NodeValue::ArrayLit(elements) => {
7934
            return try lowerArrayLit(self, node, elements);
7935
        }
7936
        case ast::NodeValue::ArrayRepeatLit(repeat) => {
7937
            return try lowerArrayRepeatLit(self, node, repeat);
7938
        }
7939
        case ast::NodeValue::RegionApply { value, .. } => {
7940
            return try lowerExpr(self, value);
7941
        }
7942
        case ast::NodeValue::As(cast) => {
7943
            return try lowerCast(self, node, cast);
7944
        }
7945
        case ast::NodeValue::CondExpr(cond) => {
7946
            return try lowerCondExpr(self, node, cond);
7947
        }
7948
        case ast::NodeValue::String(s) => {
7949
            return try lowerStringLit(self, node, s);
7950
        }
7951
        case ast::NodeValue::Undef => {
7952
            let typ = try typeOf(self, node);
7953
            if isAggregateType(typ) {
7954
                // When `undefined` appears as a stand-alone expression,
7955
                /// we need a stack slot for reads and writes.
7956
                let slot = try emitReserve(self, typ);
7957
                return il::Val::Reg(slot);
7958
            } else {
7959
                return il::Val::Undef;
7960
            }
7961
        }
7962
        case ast::NodeValue::Panic { .. } => {
7963
            // Panic in expression context (e.g. match arm). Emit unreachable
7964
            // and return a dummy value since control won't continue.
7965
            emit(self, il::Instr::Unreachable);
7966
            return il::Val::Undef;
7967
        }
7968
        case ast::NodeValue::Assert { .. } => {
7969
            // Assert in expression context. Lower as statement, return `void`.
7970
            try lowerNode(self, node);
7971
            return il::Val::Undef;
7972
        }
7973
        case ast::NodeValue::Block(_) => {
7974
            try lowerBlock(self, node);
7975
            return il::Val::Undef;
7976
        }
7977
        case ast::NodeValue::ExprStmt(expr) => {
7978
            let _ = expr;
7979
            return il::Val::Undef;
7980
        }
7981
        // Lower these as statements.
7982
        case ast::NodeValue::ConstDecl(decl) => {
7983
            try registerLocalDataDeclName(self, node);
7984
            try lowerDataDecl(self.low, node, decl.value, true);
7985
            return il::Val::Undef;
7986
        }
7987
        case ast::NodeValue::StaticDecl(decl) => {
7988
            try registerLocalDataDeclName(self, node);
7989
            try lowerDataDecl(self.low, node, decl.value, false);
7990
            return il::Val::Undef;
7991
        }
7992
        case ast::NodeValue::Throw { .. },
7993
             ast::NodeValue::Return { .. },
7994
             ast::NodeValue::Continue,
7995
             ast::NodeValue::Break => {
7996
            try lowerNode(self, node);
7997
            return il::Val::Undef;
7998
        }
7999
        else => {
8000
            panic "lowerExprValue: node is not an expression";
8001
        }
8002
    }
8003
}
8004
8005
/// Translate a Radiance type to an IL type.
8006
///
8007
/// The IL type system is much simpler than Radiance's, only primitive types
8008
/// are used. If a Radiance type doesn't fit in a machine word, it is passed
8009
/// by reference.
8010
///
8011
/// The IL doesn't track signedness - that's encoded in the instructions
8012
/// (e.g., Slt vs Ult).
8013
fn ilType 'arena 'phase (self: &mut Lowerer 'arena 'phase, typ: resolver::Type) -> il::Type where 'arena: 'phase {
8014
    match typ {
8015
        case resolver::Type::Bool,
8016
             resolver::Type::I8,
8017
             resolver::Type::U8 => return il::Type::W8,
8018
        case resolver::Type::I16,
8019
             resolver::Type::U16 => return il::Type::W16,
8020
        case resolver::Type::I32,
8021
             resolver::Type::U32 => return il::Type::W32,
8022
        case resolver::Type::I64,
8023
             resolver::Type::U64,
8024
             resolver::Type::Pointer { .. },
8025
             resolver::Type::Slice { .. },
8026
             resolver::Type::TraitObject { .. },
8027
             resolver::Type::Array(_),
8028
             resolver::Type::Optional(_),
8029
             resolver::Type::Fn(_),
8030
             resolver::Type::Session(_),
8031
             resolver::Type::Cell { .. } => return il::Type::W64,
8032
        case resolver::Type::Nominal(_) => {
8033
            unsafe {
8034
                if resolver::isVoidUnion(typ) {
8035
                    return il::Type::W8;
8036
                }
8037
            }
8038
            return il::Type::W64;
8039
        }
8040
        case resolver::Type::Void, resolver::Type::Never => return il::Type::W64,
8041
        // [`Type::Int`] is the type of unsuffixed integer literals and their
8042
        // compound expressions (e.g. `1 + 2`). It defaults to W64 (i64) here,
8043
        // matching the native word size on RV64. It cannot be resolved earlier
8044
        // because the resolver uses [`Type::Int`] to distinguish unsuffixed
8045
        // expressions from explicitly typed ones, which affects coercion
8046
        // behavior (e.g. implicit narrowing).
8047
        case resolver::Type::Int => return il::Type::W64,
8048
        case resolver::Type::Opaque => panic "ilType: opaque type must be behind a pointer",
8049
        else => panic "ilType: type cannot be lowered",
8050
    }
8051
}
8052
8053
/// Lower one fixed-width MMIO operation without exposing a pointer value.
8054
unsafe fn lowerDevice 'arena 'phase 'function (
8055
    self: &mut FnLowerer 'arena 'phase 'function,
8056
    call: ast::Call,
8057
    typ: il::Type,
8058
    writing: bool
8059
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
8060
    let count: u32 = 3 if writing else 2;
8061
    if call.args.len <> count {
8062
        throw LowerError::InvalidArgCount;
8063
    }
8064
    let handle = try lowerExpr(self, call.args[0]);
8065
    let offset = try lowerExpr(self, call.args[1]);
8066
    if writing {
8067
        let value = try lowerExpr(self, call.args[2]);
8068
        emit(self, il::Instr::DeviceWrite { typ, handle, offset, value });
8069
        return il::Val::Undef;
8070
    }
8071
    let dst = nextReg(self);
8072
    emit(self, il::Instr::DeviceRead { typ, dst, handle, offset });
8073
    return il::Val::Reg(dst);
8074
}