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