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