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