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