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