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