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