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