compiler/
kernel/
lib/
examples/
std/
arch/
char/
collections/
lang/
alloc/
ast/
gen/
il/
module/
parser/
resolver/
scanner/
alloc.rad
7.1 KiB
ast.rad
26.7 KiB
gen.rad
513 B
il.rad
19.5 KiB
lower.rad
308.1 KiB
module.rad
14.9 KiB
package.rad
1.3 KiB
parser.rad
89.5 KiB
resolver.rad
439.6 KiB
scanner.rad
18.0 KiB
sexpr.rad
6.4 KiB
strings.rad
2.2 KiB
types.rad
1.6 KiB
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.3 KiB
intrinsics.rad
467 B
io.rad
1.7 KiB
lang.rad
276 B
mem.rad
2.3 KiB
sys.rad
179 B
testing.rad
2.4 KiB
tests.rad
15.7 KiB
vec.rad
3.2 KiB
std.rad
281 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
9.2 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.5 KiB
std.lib.test
662 B
lib/std/lang/lower.rad
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 | instanceState: InstanceCursor, |
| 285 | /// Whether the instance cursor contains an active declaration. |
| 286 | instanceActive: bool, |
| 287 | } |
| 288 | |
| 289 | /// Persistent v-table names and position within an instance declaration. |
| 290 | export record InstanceCursor: Copy { |
| 291 | /// Trait whose method slots define the v-table. |
| 292 | traitInfo: *unsafe resolver::TraitType, |
| 293 | /// Trait source name. |
| 294 | traitName: *[u8], |
| 295 | /// Concrete type source name. |
| 296 | typeName: *[u8], |
| 297 | /// Method declarations in source order. |
| 298 | methods: *[*ast::Node], |
| 299 | /// Next method to process. |
| 300 | next: u32, |
| 301 | /// Persistent qualified function names, indexed by trait method slot. |
| 302 | methodNames: [*[u8]; ast::MAX_TRAIT_METHODS], |
| 303 | /// Slots initialized by concrete methods. |
| 304 | methodNameSet: [bool; ast::MAX_TRAIT_METHODS], |
| 305 | } |
| 306 | |
| 307 | /// Module-level lowering context. Shared across all function lowerings. |
| 308 | /// Holds global state like the data section (strings, constants) and provides |
| 309 | /// access to the resolver for type queries. |
| 310 | export record Lowerer: 'arena + 'phase where 'arena: 'phase { |
| 311 | /// Arena for persistent lowering state, including data and symbol names. |
| 312 | arena: *unsafe mut alloc::Arena, |
| 313 | /// Resolver for type information. Used to query types, symbols, and |
| 314 | /// compile-time constant values during lowering. |
| 315 | resolver: &'phase resolver::Resolver 'arena, |
| 316 | /// Module graph for cross-module symbol resolution. |
| 317 | moduleGraph: ?&'phase module::ModuleGraph, |
| 318 | /// Package name for qualified symbol names. |
| 319 | pkgName: *[u8], |
| 320 | /// Current module being lowered. |
| 321 | currentMod: ?u16, |
| 322 | /// Global data items (string literals, constants, static arrays). |
| 323 | /// These become the data sections in the final binary. |
| 324 | data: *mut [il::Data], |
| 325 | /// First data entry owned by the package currently being lowered. |
| 326 | packageDataStart: u32, |
| 327 | /// Functions retained for accumulated output. |
| 328 | functions: *unsafe mut [*unsafe il::Fn], |
| 329 | /// Function and data symbols with their emitted names. |
| 330 | symbolNames: *mut [SymbolNameEntry], |
| 331 | /// Global error type tag table. Maps nominal types to unique tags. |
| 332 | errTags: *mut [ErrTagEntry], |
| 333 | /// Next error tag to assign (starts at 1; 0 = success). |
| 334 | errTagCounter: u32, |
| 335 | /// Lowering options. |
| 336 | options: LowerOptions, |
| 337 | } |
| 338 | |
| 339 | /// Entry mapping a function or data symbol to its emitted name. |
| 340 | record SymbolNameEntry: Copy { |
| 341 | /// Stable resolver symbol identity. |
| 342 | sym: *unsafe resolver::Symbol, |
| 343 | /// Qualified name in the emitted program. |
| 344 | qualName: *[u8], |
| 345 | } |
| 346 | |
| 347 | /// Entry in the global error tag table. |
| 348 | record ErrTagEntry: Copy { |
| 349 | /// The type of this error, identified by its interned pointer. |
| 350 | ty: resolver::Type, |
| 351 | /// The globally unique tag assigned to this error type (non-zero). |
| 352 | tag: u32, |
| 353 | } |
| 354 | |
| 355 | /// Compute the maximum size of any error type in a throw list. |
| 356 | unsafe fn maxErrSize(throwList: *[*resolver::Type]) -> u32 { |
| 357 | let mut maxSize: u32 = 0; |
| 358 | for ty in throwList { |
| 359 | let size = resolver::getTypeLayout(*ty).size; |
| 360 | if size > maxSize { |
| 361 | set maxSize = size; |
| 362 | } |
| 363 | } |
| 364 | return maxSize; |
| 365 | } |
| 366 | |
| 367 | /// Get or assign a globally unique error tag for the given error type. |
| 368 | /// Tag `0` is reserved for success; error tags start at `1`. |
| 369 | unsafe fn getOrAssignErrorTag 'arena 'phase (self: &mut Lowerer 'arena 'phase, errType: resolver::Type) -> u32 where 'arena: 'phase { |
| 370 | for entry in &self.errTags[..] { |
| 371 | if resolver::erasedTypesEqual(entry.ty, errType) { |
| 372 | return entry.tag; |
| 373 | } |
| 374 | } |
| 375 | let tag = self.errTagCounter; |
| 376 | |
| 377 | set self.errTagCounter += 1; |
| 378 | self.errTags.append(ErrTagEntry { ty: errType, tag }, alloc::arenaAllocator(self.arena)); |
| 379 | |
| 380 | return tag; |
| 381 | } |
| 382 | |
| 383 | /// Retain a lowered function for accumulated IL output. |
| 384 | unsafe fn emitFunction 'arena 'phase (self: &mut Lowerer 'arena 'phase, func: *unsafe il::Fn) where 'arena: 'phase { |
| 385 | self.functions.append(func, alloc::arenaAllocator(self.arena)); |
| 386 | } |
| 387 | |
| 388 | /// Get the function role for a top-level function declaration. |
| 389 | fn lowerFnRole(isRoot: bool, attrs: ?ast::Attributes) -> FnRole { |
| 390 | if isRoot and checkAttr(attrs, ast::Attribute::Default) { |
| 391 | return FnRole::Default; |
| 392 | } |
| 393 | return FnRole::Normal; |
| 394 | } |
| 395 | |
| 396 | /// Builder for accumulating data values during constant lowering. |
| 397 | record DataValueBuilder { |
| 398 | /// Allocator used to grow the value storage. |
| 399 | allocator: alloc::Allocator, |
| 400 | /// Owned values under construction. |
| 401 | values: *mut [il::DataValue], |
| 402 | /// Whether all pushed values can be represented by zero-filled memory. |
| 403 | zeroInit: bool, |
| 404 | } |
| 405 | |
| 406 | /// Result of lowering constant data. |
| 407 | record ConstDataResult: Copy { |
| 408 | /// Immutable initializer values. |
| 409 | values: *[il::DataValue], |
| 410 | /// Whether the values can be represented by zero-filled memory. |
| 411 | zeroInit: bool, |
| 412 | } |
| 413 | |
| 414 | /// Create a new builder. |
| 415 | fn dataBuilder(allocator: alloc::Allocator) -> DataValueBuilder { |
| 416 | return DataValueBuilder { allocator, values: &mut [], zeroInit: true }; |
| 417 | } |
| 418 | |
| 419 | /// Return whether a data item can be omitted from a zero-filled image. |
| 420 | fn dataIsZero(item: il::DataItem) -> bool { |
| 421 | match item { |
| 422 | case il::DataItem::Undef => return true, |
| 423 | case il::DataItem::Val { val, .. } => return val == 0, |
| 424 | else => return false, |
| 425 | } |
| 426 | } |
| 427 | |
| 428 | /// Return whether all data values can be omitted from a zero-filled image. |
| 429 | fn dataValuesAreZeroInit(values: *[il::DataValue]) -> bool { |
| 430 | for value in values { |
| 431 | if value.count > 0 and not dataIsZero(value.item) { |
| 432 | return false; |
| 433 | } |
| 434 | } |
| 435 | return true; |
| 436 | } |
| 437 | |
| 438 | /// Append a data value to the builder. |
| 439 | fn dataBuilderPush(b: &mut DataValueBuilder, value: il::DataValue) { |
| 440 | b.values.append(value, b.allocator); |
| 441 | |
| 442 | if value.count > 0 and not dataIsZero(value.item) { |
| 443 | set b.zeroInit = false; |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | /// Return the accumulated values. |
| 448 | fn dataBuilderFinish(b: DataValueBuilder) -> ConstDataResult { |
| 449 | let case DataValueBuilder { values, zeroInit, .. } = b |
| 450 | else panic "expected data builder"; |
| 451 | return ConstDataResult { values, zeroInit }; |
| 452 | } |
| 453 | |
| 454 | /////////////////////////// |
| 455 | // SSA Variable Tracking // |
| 456 | /////////////////////////// |
| 457 | |
| 458 | // The SSA construction algorithm tracks variable definitions per-block. |
| 459 | // Each source-level variable gets a [`Var`] handle, and each block maintains |
| 460 | // a mapping from [`Var`] to current SSA value. When control flow merges, |
| 461 | // block parameters are inserted to merge values from different control flow paths. |
| 462 | |
| 463 | /// A variable handle. Represents a source-level variable during lowering. |
| 464 | /// The same [`Var`] can have different SSA values in different blocks. |
| 465 | export record Var: Copy(u32); |
| 466 | |
| 467 | /// Metadata for a source-level variable, stored once per function. |
| 468 | /// |
| 469 | /// Each variable declaration in the source creates one [`VarData`] entry in the |
| 470 | /// function's `variables` array, indexed by `id`. This contains static |
| 471 | /// properties that don't change across basic blocks. |
| 472 | /// |
| 473 | /// Per-block SSA values are tracked separately in [`BlockData::vars`] as [`?il::Val`], |
| 474 | /// where `nil` means "not yet assigned in this block". Together they implement |
| 475 | /// SSA construction. |
| 476 | /// |
| 477 | record VarData: Copy { |
| 478 | /// Variable name, used by [`lookupVarByName`] to resolve identifiers. |
| 479 | /// Nil for anonymous variables (e.g., internal loop counters). |
| 480 | name: ?*[u8], |
| 481 | /// IL type of this variable. Set at declaration time and used when |
| 482 | /// generating loads, stores, and type-checking assignments. |
| 483 | type: il::Type, |
| 484 | /// Whether this variable was declared with `mut`. Controls whether [`defVar`] |
| 485 | /// is allowed after the initial definition. |
| 486 | mutable: bool, |
| 487 | /// Whether this variable's address has been taken (e.g. via `&mut x`). |
| 488 | /// When true, the SSA value is a pointer to a stack slot and reads/writes |
| 489 | /// must go through memory instead of using the cached SSA value directly. |
| 490 | addressTaken: bool, |
| 491 | } |
| 492 | |
| 493 | /// Links a function parameter to its corresponding variable for the entry block. |
| 494 | /// After creating the entry block, we iterate through these to define initial values. |
| 495 | record FnParamBinding: Copy { |
| 496 | /// The variable that receives this parameter's value. |
| 497 | var: Var, |
| 498 | /// SSA register containing the parameter value from the caller. |
| 499 | reg: il::Reg, |
| 500 | } |
| 501 | |
| 502 | //////////////////////////////// |
| 503 | // Basic Block Representation // |
| 504 | //////////////////////////////// |
| 505 | |
| 506 | // During lowering, we build a CFG of basic blocks. Each block accumulates |
| 507 | // instructions until terminated by a jump, branch, return, or unreachable. |
| 508 | // Blocks can be created before they're filled (forward references for jumps). |
| 509 | |
| 510 | /// A handle to a basic block within the current function. |
| 511 | /// Block handles are stable, they don't change as more blocks are added. |
| 512 | export record BlockId: Copy(u32); |
| 513 | |
| 514 | /// Internal block state during construction. |
| 515 | /// |
| 516 | /// The key invariants: |
| 517 | /// |
| 518 | /// - A block is "open" if it has no terminator; instructions can be added. |
| 519 | /// - A block is "sealed" when all predecessor edges are known. |
| 520 | /// - Sealing resolves the predecessor arguments for block parameters. |
| 521 | /// |
| 522 | /// This differs from the final [`il::Block`] which is immutable and fully formed. |
| 523 | record BlockData: Copy { |
| 524 | /// Block label for debugging and IL printing. |
| 525 | label: *[u8], |
| 526 | /// Block parameters for merging values at control flow joins. These |
| 527 | /// receive values from predecessor edges when control flow merges. |
| 528 | params: *unsafe mut [il::Param], |
| 529 | /// Variable ids in parameter order. Before sealing, these are the variables |
| 530 | /// whose predecessor arguments must be resolved. |
| 531 | paramVars: *unsafe mut [u32], |
| 532 | /// Instructions accumulated so far. The last instruction should eventually |
| 533 | /// be a terminator. |
| 534 | instrs: *unsafe mut [il::Instr], |
| 535 | /// Debug source locations, one per instruction. Only populated when |
| 536 | /// debug info is enabled. |
| 537 | locs: *unsafe mut [il::SrcLoc], |
| 538 | /// Predecessor block ids. Used for SSA construction to propagate values |
| 539 | /// from predecessors when a variable is used before being defined locally. |
| 540 | preds: *unsafe mut [u32], |
| 541 | /// The current SSA value of each variable in this block. Indexed by variable |
| 542 | /// id. A `nil` means the variable wasn't assigned in this block. Updated by |
| 543 | /// [`defVar`], queried by [`useVarInBlock`]. |
| 544 | vars: *unsafe mut [?il::Val], |
| 545 | /// Sealing state. Once sealed, all predecessors are known and we can resolve |
| 546 | /// variable uses that need to pull values from predecessors. |
| 547 | sealState: Sealed, |
| 548 | /// Loop nesting depth when this block was created. |
| 549 | loopDepth: u32, |
| 550 | } |
| 551 | |
| 552 | /// Block sealing state for SSA construction. |
| 553 | /// |
| 554 | /// A block is "unsealed" while its predecessors are still being discovered. |
| 555 | /// Before sealing, `BlockData.paramVars` records each variable that needs a |
| 556 | /// block parameter. Once all predecessors are known, the block is sealed and |
| 557 | /// those parameters are resolved via [`resolveBlockArgs`]. |
| 558 | union Sealed: Copy { |
| 559 | /// Block is unsealed; predecessors may still be added. |
| 560 | No, |
| 561 | /// Block is sealed; all predecessors are known. |
| 562 | Yes, |
| 563 | } |
| 564 | |
| 565 | /////////////////////////////////// |
| 566 | // Loop and Control Flow Context // |
| 567 | /////////////////////////////////// |
| 568 | |
| 569 | /// Context for break/continue statements within a loop. |
| 570 | /// Each nested loop pushes a new context onto the loop stack. |
| 571 | export record LoopCtx: Copy { |
| 572 | /// Where `break` should transfer control (the loop's exit block). |
| 573 | breakTarget: BlockId, |
| 574 | /// Where `continue` should transfer control. |
| 575 | continueTarget: ?BlockId, |
| 576 | } |
| 577 | |
| 578 | /// Logical operator. |
| 579 | union LogicalOp: Copy { And, Or } |
| 580 | |
| 581 | /// Iterator state for for-loop lowering. |
| 582 | union ForIter: Copy { |
| 583 | /// Range iterator: `for i in 0..n`. |
| 584 | Range { |
| 585 | valVar: Var, |
| 586 | indexVar: ?Var, |
| 587 | endVal: il::Val, |
| 588 | valType: il::Type, |
| 589 | unsigned: bool, |
| 590 | }, |
| 591 | /// Collection iterator: `for elem in slice`. |
| 592 | Collection { |
| 593 | valVar: ?Var, |
| 594 | idxVar: Var, |
| 595 | dataReg: il::Reg, |
| 596 | lengthVal: il::Val, |
| 597 | elemType: *resolver::Type, |
| 598 | }, |
| 599 | } |
| 600 | |
| 601 | ////////////////////////////// |
| 602 | // Pattern Matching Support // |
| 603 | ////////////////////////////// |
| 604 | |
| 605 | // Match expressions are lowered by evaluating the subject once, then emitting |
| 606 | // a chain of comparison-and-branch sequences for each arm. The algorithm |
| 607 | // handles several subject types specially: |
| 608 | // |
| 609 | // - Optional pointers: compared against `null`. |
| 610 | // - Optional aggregates: tag checked then payload extracted. |
| 611 | // - Unions: tag compared against variant indices. |
| 612 | |
| 613 | /// Cached information about a match subject. Computed once and reused across |
| 614 | /// all arms to avoid redundant lowering and type queries. |
| 615 | record MatchSubject: Copy { |
| 616 | /// The lowered subject value. |
| 617 | val: il::Val, |
| 618 | /// Source-level type from the resolver. |
| 619 | type: resolver::Type, |
| 620 | /// IL-level type for code generation. |
| 621 | ilType: il::Type, |
| 622 | /// The type that binding arms should use. For optionals, this is the |
| 623 | /// inner type; for regular values, it's the same as `type`. |
| 624 | bindType: resolver::Type, |
| 625 | /// Classification of how the subject should be compared and destructured. |
| 626 | kind: MatchSubjectKind, |
| 627 | /// How bindings are created: by value, or by reference. |
| 628 | by: resolver::MatchBy, |
| 629 | } |
| 630 | |
| 631 | /// Classifies a match subject by how it should be compared and destructured. |
| 632 | union MatchSubjectKind: Copy { |
| 633 | /// Regular value: direct equality comparison. |
| 634 | Regular, |
| 635 | /// Optional with null pointer optimization: `?*T`, `?*[T]`. |
| 636 | OptionalPtr, |
| 637 | /// Optional aggregate `?T`: tagged union with payload. |
| 638 | OptionalAggregate, |
| 639 | /// Union type: tag compared against variant indices. |
| 640 | Union(resolver::UnionType), |
| 641 | } |
| 642 | |
| 643 | /// Determine the kind of a match subject from its type. |
| 644 | unsafe fn matchSubjectKind(type: resolver::Type) -> MatchSubjectKind { |
| 645 | if resolver::isOptionalPointer(type) { |
| 646 | return MatchSubjectKind::OptionalPtr; |
| 647 | } |
| 648 | if resolver::isOptionalAggregate(type) { |
| 649 | return MatchSubjectKind::OptionalAggregate; |
| 650 | } |
| 651 | if let info = unionInfoFromType(type) { |
| 652 | return MatchSubjectKind::Union(info); |
| 653 | } |
| 654 | return MatchSubjectKind::Regular; |
| 655 | } |
| 656 | |
| 657 | ////////////////////////// |
| 658 | // Field Access Support // |
| 659 | ////////////////////////// |
| 660 | |
| 661 | /// Result of resolving a place expression to a memory location. |
| 662 | record FieldRef: Copy { |
| 663 | /// Base pointer register (points to the container). |
| 664 | base: il::Reg, |
| 665 | /// Byte offset of the value within the container. |
| 666 | offset: i32, |
| 667 | /// Type of the value held at that location. |
| 668 | fieldType: resolver::Type, |
| 669 | } |
| 670 | |
| 671 | /// Result of computing an element pointer for array/slice subscript operations. |
| 672 | /// Used by [`lowerElemPtr`] to return both the element address register and |
| 673 | /// the element type for subsequent load or address-of operations. |
| 674 | record ElemPtrResult: Copy { |
| 675 | /// Register holding the computed element address. |
| 676 | elemReg: il::Reg, |
| 677 | /// Source-level type of the element. |
| 678 | elemType: resolver::Type, |
| 679 | } |
| 680 | |
| 681 | /// Result of resolving a slice range to a data pointer and element count. |
| 682 | record SliceRangeResult: Copy { |
| 683 | dataReg: il::Reg, |
| 684 | count: il::Val, |
| 685 | } |
| 686 | |
| 687 | ///////////////////////////// |
| 688 | // Function Lowering State // |
| 689 | ///////////////////////////// |
| 690 | |
| 691 | /// Per-function lowering state. Created fresh for each function and contains |
| 692 | /// all the mutable state needed during function body lowering. |
| 693 | record FnLowerer: 'arena + 'phase + 'function where 'arena: 'phase, 'phase: 'function { |
| 694 | /// Reference to the module-level lowerer. |
| 695 | low: &'function mut Lowerer 'arena 'phase, |
| 696 | /// Arena for allocations owned by this function. |
| 697 | arena: &'function mut alloc::Arena, |
| 698 | /// Type signature of the function being lowered. |
| 699 | fnType: *resolver::FnType, |
| 700 | /// Number of SSA variable slots required for each block. |
| 701 | localCount: u32, |
| 702 | /// Function name, used as prefix for generated data symbols. |
| 703 | fnName: *[u8], |
| 704 | |
| 705 | // ~ SSA variable tracking ~ // |
| 706 | |
| 707 | /// Metadata (name, type, mutability) for each variable. Indexed by variable |
| 708 | /// id. Doesn't change after declaration. For the SSA value of a variable in |
| 709 | /// a specific block, see [`BlockData::vars`]. |
| 710 | vars: *unsafe mut [VarData], |
| 711 | /// Parameter-to-variable bindings, initialized in the entry block. |
| 712 | params: [FnParamBinding; resolver::MAX_FN_PARAMS], |
| 713 | /// Number of initialized parameter bindings. |
| 714 | paramsLen: u32, |
| 715 | |
| 716 | // ~ Basic block management ~ // |
| 717 | |
| 718 | /// Block storage array, indexed by block id. |
| 719 | blockData: *unsafe mut [BlockData], |
| 720 | /// The entry block for this function. |
| 721 | entryBlock: ?BlockId, |
| 722 | /// The block currently receiving new instructions. |
| 723 | currentBlock: ?BlockId, |
| 724 | |
| 725 | // ~ Loop management ~ // |
| 726 | |
| 727 | /// Loop contexts indexed by nesting depth. |
| 728 | /// The active prefix contains `loopDepth` entries. |
| 729 | loopStack: [LoopCtx; MAX_LOOP_DEPTH], |
| 730 | /// Current nesting depth (index into loopStack). |
| 731 | loopDepth: u32, |
| 732 | |
| 733 | // ~ Counters ~ // |
| 734 | |
| 735 | /// Counter for generating unique block labels like `then#0`, `loop#1`, etc. |
| 736 | labelCounter: u32, |
| 737 | /// Counter for generating unique data names within this function. |
| 738 | /// Each literal gets a name like `fnName$literal$N`. |
| 739 | dataCounter: u32, |
| 740 | /// Counter for generating SSA register numbers. |
| 741 | regCounter: u32, |
| 742 | /// When the function returns an aggregate type, the caller passes a hidden |
| 743 | /// pointer as the first parameter. The callee writes the return value into |
| 744 | /// this buffer and returns the pointer. |
| 745 | returnReg: ?il::Reg, |
| 746 | /// Whether the function is a leaf. |
| 747 | isLeaf: bool, |
| 748 | |
| 749 | // ~ Debug info ~ // |
| 750 | |
| 751 | /// Current debug source location, set when processing AST nodes. |
| 752 | srcLoc: il::SrcLoc, |
| 753 | } |
| 754 | |
| 755 | ///////////////////////////////// |
| 756 | // Module Lowering Entry Point // |
| 757 | ///////////////////////////////// |
| 758 | |
| 759 | /// Lower a complete module AST to an IL program. |
| 760 | /// |
| 761 | /// This is the main entry point for the lowering pass. It: |
| 762 | /// 1. Counts functions to preallocate the output array. |
| 763 | /// 2. Iterates over top-level declarations, lowering each. |
| 764 | /// 3. Returns the complete IL program with functions and data section. |
| 765 | /// |
| 766 | /// The resolver must have already processed the AST -- we rely on its type |
| 767 | /// annotations, symbol table, and constant evaluations. |
| 768 | export unsafe fn lower 'arena ( |
| 769 | res: &resolver::Resolver 'arena, |
| 770 | root: *ast::Node, |
| 771 | pkgName: *[u8], |
| 772 | arena: &mut alloc::Arena |
| 773 | ) -> il::Program throws (LowerError) { |
| 774 | let functionArena = (&mut *arena) as *unsafe mut alloc::Arena; |
| 775 | let resolved: 'phase = &*res where 'arena: 'phase in { |
| 776 | let mut low = Lowerer 'arena 'phase { |
| 777 | arena: (&mut *arena) as *unsafe mut alloc::Arena, |
| 778 | resolver: resolved, |
| 779 | moduleGraph: nil, |
| 780 | pkgName, |
| 781 | currentMod: nil, |
| 782 | data: &mut [], |
| 783 | packageDataStart: 0, |
| 784 | functions: &mut [], |
| 785 | symbolNames: &mut [], |
| 786 | errTags: &mut [], |
| 787 | errTagCounter: 1, |
| 788 | options: LowerOptions { debug: false, buildTest: false }, |
| 789 | }; |
| 790 | try lowerDecls(&mut low, root, true, functionArena); |
| 791 | |
| 792 | return finalize(low); |
| 793 | } |
| 794 | } |
| 795 | |
| 796 | ///////////////////////////////// |
| 797 | // Multi-Module Lowering API // |
| 798 | ///////////////////////////////// |
| 799 | |
| 800 | /// Create a lowerer for multi-module compilation. |
| 801 | /// The resolver, graph, and persistent arena must outlive the returned lowerer. |
| 802 | export unsafe fn lowerer 'arena 'phase ( |
| 803 | res: &'phase resolver::Resolver 'arena, |
| 804 | graph: &'phase module::ModuleGraph, |
| 805 | pkgName: *[u8], |
| 806 | arena: *unsafe mut alloc::Arena, |
| 807 | options: LowerOptions |
| 808 | ) -> Lowerer 'arena 'phase where 'arena: 'phase { |
| 809 | return Lowerer 'arena 'phase { |
| 810 | arena, |
| 811 | resolver: res, |
| 812 | moduleGraph: graph, |
| 813 | pkgName, |
| 814 | currentMod: nil, |
| 815 | data: &mut [], |
| 816 | packageDataStart: 0, |
| 817 | functions: &mut [], |
| 818 | symbolNames: &mut [], |
| 819 | errTags: &mut [], |
| 820 | errTagCounter: 1, |
| 821 | options, |
| 822 | }; |
| 823 | } |
| 824 | |
| 825 | /// Lower a module's AST into the lowerer. |
| 826 | /// Call this for each module in the package, then use `finalize` to get the program. |
| 827 | export unsafe fn lowerModule 'arena 'phase ( |
| 828 | low: &mut Lowerer 'arena 'phase, |
| 829 | moduleId: u16, |
| 830 | root: *ast::Node, |
| 831 | isRoot: bool, |
| 832 | functionArena: &mut alloc::Arena |
| 833 | ) throws (LowerError) where 'arena: 'phase { |
| 834 | set low.currentMod = moduleId; |
| 835 | try lowerDecls(low, root, isRoot, functionArena); |
| 836 | } |
| 837 | |
| 838 | /// Start lowering the declarations of one module. |
| 839 | export unsafe fn moduleCursor(root: *ast::Node, isRoot: bool) -> ModuleCursor throws (LowerError) { |
| 840 | let case ast::NodeValue::Block(block) = root.value else { |
| 841 | throw LowerError::ExpectedBlock(root); |
| 842 | }; |
| 843 | return ModuleCursor { declarations: block.statements, next: 0, isRoot, instanceState: undefined, instanceActive: false }; |
| 844 | } |
| 845 | |
| 846 | /// Lower declarations until one function is ready or the module is complete. |
| 847 | /// The caller must finish using a function before reclaiming its arena storage. |
| 848 | export unsafe fn lowerNext 'arena 'phase ( |
| 849 | low: &mut Lowerer 'arena 'phase, |
| 850 | cursor: &mut ModuleCursor, |
| 851 | functionArena: &mut alloc::Arena |
| 852 | ) -> ?LoweredFunction throws (LowerError) where 'arena: 'phase { |
| 853 | loop { |
| 854 | if cursor.instanceActive { |
| 855 | if let function = try lowerNextInstance(low, &mut cursor.instanceState, functionArena) { |
| 856 | return LoweredFunction { function, role: FnRole::Normal }; |
| 857 | } |
| 858 | set cursor.instanceActive = false; |
| 859 | } |
| 860 | if cursor.next >= cursor.declarations.len { |
| 861 | return nil; |
| 862 | } |
| 863 | let node = cursor.declarations[cursor.next]; |
| 864 | set cursor.next += 1; |
| 865 | match node.value { |
| 866 | case ast::NodeValue::FnDecl(decl) => { |
| 867 | if let function = try lowerFnDecl(low, node, decl, functionArena) { |
| 868 | return LoweredFunction { function, role: lowerFnRole(cursor.isRoot, decl.attrs) }; |
| 869 | } |
| 870 | } |
| 871 | case ast::NodeValue::ConstDecl(decl) => |
| 872 | try lowerDataDecl(low, node, decl.value, true), |
| 873 | case ast::NodeValue::StaticDecl(decl) => |
| 874 | try lowerDataDecl(low, node, decl.value, false), |
| 875 | case ast::NodeValue::InstanceDecl { traitName, targetType, methods, .. } => { |
| 876 | set cursor.instanceState = try instanceCursor(low, traitName, targetType, methods); |
| 877 | set cursor.instanceActive = true; |
| 878 | } |
| 879 | case ast::NodeValue::MethodDecl { name, receiverName, sig, body, .. } => { |
| 880 | if let function = try lowerMethodDecl( |
| 881 | low, node, name, receiverName, sig, body, functionArena |
| 882 | ) { |
| 883 | return LoweredFunction { function, role: FnRole::Normal }; |
| 884 | } |
| 885 | } |
| 886 | else => {}, |
| 887 | } |
| 888 | } |
| 889 | } |
| 890 | |
| 891 | /// Lower all top-level declarations in a block. |
| 892 | unsafe fn lowerDecls 'arena 'phase ( |
| 893 | low: &mut Lowerer 'arena 'phase, |
| 894 | root: *ast::Node, |
| 895 | isRoot: bool, |
| 896 | functionArena: &mut alloc::Arena |
| 897 | ) throws (LowerError) where 'arena: 'phase { |
| 898 | let mut cursor = try moduleCursor(root, isRoot); |
| 899 | while let result = try lowerNext(low, &mut cursor, functionArena) { |
| 900 | emitFunction(low, result.function); |
| 901 | } |
| 902 | } |
| 903 | |
| 904 | /// Consume the lowerer and publish its global data as an immutable array. |
| 905 | export fn finalize 'arena 'phase (low: Lowerer 'arena 'phase) -> il::Program where 'arena: 'phase { |
| 906 | let case Lowerer 'arena 'phase { data, functions, .. } = low |
| 907 | else panic "expected lowerer"; |
| 908 | return il::Program { data, fns: functions }; |
| 909 | } |
| 910 | |
| 911 | ///////////////////////////////// |
| 912 | // Qualified Name Construction // |
| 913 | ///////////////////////////////// |
| 914 | |
| 915 | /// Get module path segments for the current or specified module. |
| 916 | /// Returns empty slice if no module graph or module not found. |
| 917 | unsafe fn getModulePath 'arena 'phase (self: &mut Lowerer 'arena 'phase, modId: ?u16) -> *[*[u8]] where 'arena: 'phase { |
| 918 | let graph = self.moduleGraph else { |
| 919 | return &[]; |
| 920 | }; |
| 921 | let mut id = modId; |
| 922 | if id == nil { |
| 923 | set id = self.currentMod; |
| 924 | } |
| 925 | let actualId = id else { |
| 926 | return &[]; |
| 927 | }; |
| 928 | let entry = module::get(graph, actualId) else { |
| 929 | return &[]; |
| 930 | }; |
| 931 | return module::moduleQualifiedPath(entry); |
| 932 | } |
| 933 | |
| 934 | /// Build a qualified name string for a symbol. |
| 935 | /// If `modId` is nil, uses current module. |
| 936 | unsafe fn qualifyName 'arena 'phase (self: &mut Lowerer 'arena 'phase, modId: ?u16, name: *[u8]) -> *[u8] where 'arena: 'phase { |
| 937 | let path = getModulePath(self, modId); |
| 938 | if path.len == 0 { |
| 939 | return name; |
| 940 | } |
| 941 | return il::formatQualifiedName(self.arena, path, name); |
| 942 | } |
| 943 | |
| 944 | /// Register the emitted name for a function or data symbol. |
| 945 | /// Calls and address expressions use this name across package boundaries. |
| 946 | unsafe fn registerSymbolName 'arena 'phase (self: &mut Lowerer 'arena 'phase, sym: *unsafe resolver::Symbol, qualName: *[u8]) where 'arena: 'phase { |
| 947 | self.symbolNames.append(SymbolNameEntry { sym, qualName }, alloc::arenaAllocator(self.arena)); |
| 948 | } |
| 949 | |
| 950 | /// Look up the emitted name for a function or data symbol. |
| 951 | /// Return `nil` if its declaration has not been lowered. |
| 952 | // TODO: This is kind of dubious as an optimization, if it depends on the order |
| 953 | // in which modules are lowered. |
| 954 | // TODO: Use a hash table here? |
| 955 | unsafe fn lookupSymbolName 'arena 'phase (self: &Lowerer 'arena 'phase, sym: *unsafe resolver::Symbol) -> ?*[u8] where 'arena: 'phase { |
| 956 | for entry in &self.symbolNames[..] { |
| 957 | if entry.sym == sym { |
| 958 | return entry.qualName; |
| 959 | } |
| 960 | } |
| 961 | return nil; |
| 962 | } |
| 963 | |
| 964 | /// Set the package context for lowering. |
| 965 | /// Called before lowering each package. |
| 966 | export unsafe fn setPackage 'arena 'phase (self: &mut Lowerer 'arena 'phase, pkgName: *[u8]) where 'arena: 'phase { |
| 967 | set self.pkgName = pkgName; |
| 968 | set self.currentMod = nil; |
| 969 | set self.packageDataStart = self.data.len; |
| 970 | } |
| 971 | |
| 972 | /// Create a new function lowerer for a given function type and name. |
| 973 | unsafe fn fnLowerer 'arena 'phase 'function ( |
| 974 | self: &'function mut Lowerer 'arena 'phase, |
| 975 | node: *ast::Node, |
| 976 | fnType: *resolver::FnType, |
| 977 | qualName: *[u8], |
| 978 | functionArena: &'function mut alloc::Arena |
| 979 | ) -> FnLowerer 'arena 'phase 'function where 'arena: 'phase, 'phase: 'function { |
| 980 | let localCount = resolver::nodeData(self.resolver, node).localCount; |
| 981 | let mut fnLow = FnLowerer 'arena 'phase 'function { |
| 982 | low: self, |
| 983 | arena: functionArena, |
| 984 | fnType, |
| 985 | localCount, |
| 986 | fnName: qualName, |
| 987 | vars: &mut [], |
| 988 | params: [FnParamBinding { var: Var(0), reg: il::Reg { n: 0 } }; resolver::MAX_FN_PARAMS], |
| 989 | paramsLen: 0, |
| 990 | blockData: &mut [], |
| 991 | entryBlock: nil, |
| 992 | currentBlock: nil, |
| 993 | loopStack: [LoopCtx { breakTarget: BlockId(0), continueTarget: nil }; MAX_LOOP_DEPTH], |
| 994 | loopDepth: 0, |
| 995 | labelCounter: 0, |
| 996 | dataCounter: 0, |
| 997 | regCounter: 0, |
| 998 | returnReg: nil, |
| 999 | isLeaf: true, |
| 1000 | srcLoc: undefined, |
| 1001 | }; |
| 1002 | if fnLow.low.options.debug { |
| 1003 | let modId = fnLow.low.currentMod else { |
| 1004 | panic "fnLowerer: debug enabled but no current module"; |
| 1005 | }; |
| 1006 | set fnLow.srcLoc = il::SrcLoc { |
| 1007 | moduleId: modId, |
| 1008 | offset: node.span.offset, |
| 1009 | }; |
| 1010 | } |
| 1011 | return fnLow; |
| 1012 | } |
| 1013 | |
| 1014 | /// Lower a function declaration. |
| 1015 | /// |
| 1016 | /// This sets up the per-function lowering state, processes parameters, |
| 1017 | /// then lowers the function body into a CFG of basic blocks. |
| 1018 | /// |
| 1019 | /// For throwing functions, the return type is a result aggregate |
| 1020 | /// rather than the declared return type. |
| 1021 | unsafe fn lowerFnDecl 'arena 'phase ( |
| 1022 | self: &mut Lowerer 'arena 'phase, |
| 1023 | node: *ast::Node, |
| 1024 | decl: ast::FnDecl, |
| 1025 | functionArena: &mut alloc::Arena |
| 1026 | ) -> ?*unsafe il::Fn throws (LowerError) where 'arena: 'phase { |
| 1027 | if not shouldLowerFn(&decl, self.options.buildTest) { |
| 1028 | return nil; |
| 1029 | } |
| 1030 | let case ast::NodeValue::Ident(name) = decl.name.value else { |
| 1031 | throw LowerError::ExpectedIdentifier; |
| 1032 | }; |
| 1033 | let data = resolver::nodeData(self.resolver, node); |
| 1034 | let case resolver::Type::Fn(fnType) = data.ty else { |
| 1035 | throw LowerError::ExpectedFunction; |
| 1036 | }; |
| 1037 | let isExtern = checkAttr(decl.attrs, ast::Attribute::Extern); |
| 1038 | |
| 1039 | // Build qualified function name for multi-module compilation. |
| 1040 | let qualName = qualifyName(self, nil, name); |
| 1041 | |
| 1042 | // Register function symbol for cross-package call resolution. |
| 1043 | if let sym = data.sym { |
| 1044 | registerSymbolName(self, sym, qualName); |
| 1045 | } |
| 1046 | let parent: 'function = &mut *self, arena = &mut *functionArena where 'phase: 'function in { |
| 1047 | let mut fnLow = fnLowerer(parent, node, fnType, qualName, arena); |
| 1048 | |
| 1049 | // If the function returns an aggregate or is throwing, prepend a hidden |
| 1050 | // return parameter. The caller allocates the buffer and passes it |
| 1051 | // as the first argument; the callee writes the return value into it. |
| 1052 | if requiresReturnParam(fnType) and not isExtern { |
| 1053 | set fnLow.returnReg = nextReg(&mut fnLow); |
| 1054 | } |
| 1055 | let lowParams = try lowerParams(&mut fnLow, *fnType, decl.sig.params, nil); |
| 1056 | let func = try! alloc::allocRaw(fnLow.arena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *unsafe mut il::Fn; |
| 1057 | |
| 1058 | // Throwing functions return a result aggregate (word-sized pointer). |
| 1059 | // TODO: The resolver should set an appropriate type that takes into account |
| 1060 | // the throws list. It shouldn't set the return type to the "success" |
| 1061 | // value only. |
| 1062 | let returnType = il::Type::W64 if fnType.throwList.len > 0 |
| 1063 | else ilType(fnLow.low, *fnType.returnType); |
| 1064 | set *func = il::Fn { |
| 1065 | name: qualName, |
| 1066 | params: lowParams, |
| 1067 | returnType, |
| 1068 | isExtern, |
| 1069 | isLeaf: true, |
| 1070 | blocks: &[], |
| 1071 | }; |
| 1072 | let body = decl.body else { |
| 1073 | // Extern functions have no body. |
| 1074 | assert isExtern; |
| 1075 | return func; |
| 1076 | }; |
| 1077 | set func.blocks = try lowerFnBody(&mut fnLow, body); |
| 1078 | set func.isLeaf = fnLow.isLeaf; |
| 1079 | |
| 1080 | return func; |
| 1081 | } |
| 1082 | } |
| 1083 | |
| 1084 | /// Build a qualified name of the form "Type::method". |
| 1085 | unsafe fn instanceMethodName 'arena 'phase (self: &mut Lowerer 'arena 'phase, modId: ?u16, typeName: *[u8], methodName: *[u8]) -> *[u8] where 'arena: 'phase { |
| 1086 | let sepLen: u32 = 2; // "::" |
| 1087 | let totalLen = typeName.len + sepLen + methodName.len; |
| 1088 | let buf = try! alloc::allocSlice(self.arena, 1, 1, totalLen) as *mut [u8]; |
| 1089 | let mut pos: u32 = 0; |
| 1090 | |
| 1091 | set pos += try! mem::copy(&mut buf[pos..], typeName); |
| 1092 | set pos += try! mem::copy(&mut buf[pos..], "::"); |
| 1093 | set pos += try! mem::copy(&mut buf[pos..], methodName); |
| 1094 | assert pos == totalLen; |
| 1095 | |
| 1096 | return qualifyName(self, modId, &buf[..totalLen]); |
| 1097 | } |
| 1098 | |
| 1099 | /// Build a v-table data name of the form "vtable::Type::Trait". |
| 1100 | unsafe fn vtableName 'arena 'phase (self: &mut Lowerer 'arena 'phase, modId: ?u16, typeName: *[u8], traitName: *[u8]) -> *[u8] where 'arena: 'phase { |
| 1101 | let prefix = "vtable::"; |
| 1102 | let sepLen: u32 = 2; // "::" |
| 1103 | let totalLen = prefix.len + typeName.len + sepLen + traitName.len; |
| 1104 | let buf = try! alloc::allocSlice(self.arena, 1, 1, totalLen) as *mut [u8]; |
| 1105 | let mut pos: u32 = 0; |
| 1106 | |
| 1107 | set pos += try! mem::copy(&mut buf[pos..], prefix); |
| 1108 | set pos += try! mem::copy(&mut buf[pos..], typeName); |
| 1109 | set pos += try! mem::copy(&mut buf[pos..], "::"); |
| 1110 | set pos += try! mem::copy(&mut buf[pos..], traitName); |
| 1111 | assert pos == totalLen; |
| 1112 | |
| 1113 | return qualifyName(self, modId, &buf[..totalLen]); |
| 1114 | } |
| 1115 | |
| 1116 | /// Lower an instance declaration (`instance Trait for Type { ... }`). |
| 1117 | /// |
| 1118 | /// Each method in the instance block is lowered as a standalone function |
| 1119 | /// with a qualified name of the form `Type::method`. A read-only v-table |
| 1120 | /// data record is emitted containing pointers to these functions, ordered |
| 1121 | /// by the trait's method indices. The v-table is later referenced when |
| 1122 | /// constructing trait objects for dynamic dispatch. |
| 1123 | unsafe fn instanceCursor 'arena 'phase ( |
| 1124 | self: &mut Lowerer 'arena 'phase, |
| 1125 | traitNameNode: *ast::Node, |
| 1126 | targetTypeNode: *ast::Node, |
| 1127 | methods: *[*ast::Node] |
| 1128 | ) -> InstanceCursor throws (LowerError) where 'arena: 'phase { |
| 1129 | // Look up the trait and type from the resolver. |
| 1130 | let traitSym = resolver::nodeData(self.resolver, traitNameNode).sym |
| 1131 | else throw LowerError::MissingSymbol(traitNameNode); |
| 1132 | let case resolver::SymbolData::Trait(traitInfo) = traitSym.data |
| 1133 | else throw LowerError::MissingMetadata; |
| 1134 | let typeSym = resolver::nodeData(self.resolver, targetTypeNode).sym |
| 1135 | else throw LowerError::MissingSymbol(targetTypeNode); |
| 1136 | |
| 1137 | // Lower each instance method as a regular function. |
| 1138 | // Collect qualified names for the v-table. Empty entries are filled |
| 1139 | // later from inherited supertrait methods. |
| 1140 | return InstanceCursor { |
| 1141 | traitInfo, traitName: traitSym.name, typeName: typeSym.name, methods, next: 0, |
| 1142 | methodNames: undefined, methodNameSet: [false; ast::MAX_TRAIT_METHODS], |
| 1143 | }; |
| 1144 | } |
| 1145 | |
| 1146 | /// Lower the next concrete method, or finish the instance's v-table. |
| 1147 | unsafe fn lowerNextInstance 'arena 'phase ( |
| 1148 | self: &mut Lowerer 'arena 'phase, |
| 1149 | state: &mut InstanceCursor, |
| 1150 | functionArena: &mut alloc::Arena |
| 1151 | ) -> ?*unsafe il::Fn throws (LowerError) where 'arena: 'phase { |
| 1152 | let traitInfo = state.traitInfo; |
| 1153 | let typeName = state.typeName; |
| 1154 | let tName = state.traitName; |
| 1155 | while state.next < state.methods.len { |
| 1156 | let methodNode = state.methods[state.next]; |
| 1157 | set state.next += 1; |
| 1158 | let case ast::NodeValue::MethodDecl { |
| 1159 | name, receiverName, sig, body, .. |
| 1160 | } = methodNode.value else continue; |
| 1161 | |
| 1162 | let case ast::NodeValue::Ident(mName) = name.value else { |
| 1163 | throw LowerError::ExpectedIdentifier; |
| 1164 | }; |
| 1165 | let qualName = instanceMethodName(self, nil, typeName, mName); |
| 1166 | let func = try lowerMethod(self, methodNode, qualName, receiverName, sig, body, functionArena) |
| 1167 | else continue; |
| 1168 | |
| 1169 | let method = resolver::findTraitMethod(traitInfo, mName) |
| 1170 | else panic "lowerInstanceDecl: method not found in trait"; |
| 1171 | |
| 1172 | set state.methodNames[method.index] = qualName; |
| 1173 | set state.methodNameSet[method.index] = true; |
| 1174 | return func; |
| 1175 | } |
| 1176 | |
| 1177 | // Fill inherited method slots from supertraits. |
| 1178 | // These methods were already lowered as part of the supertrait instance |
| 1179 | // declarations and use the same `Type::method` qualified name. |
| 1180 | for method, i in traitInfo.methods { |
| 1181 | if not state.methodNameSet[i] { |
| 1182 | set state.methodNames[i] = instanceMethodName(self, nil, typeName, method.name); |
| 1183 | } |
| 1184 | } |
| 1185 | |
| 1186 | // Create v-table in data section, used for dynamic dispatch. |
| 1187 | let vName = vtableName(self, nil, typeName, tName); |
| 1188 | let values = try! alloc::allocSlice( |
| 1189 | self.arena, @sizeOf(il::DataValue), @alignOf(il::DataValue), traitInfo.methods.len as u32 |
| 1190 | ) as *mut [il::DataValue]; |
| 1191 | |
| 1192 | for i in 0..traitInfo.methods.len { |
| 1193 | set values[i] = il::DataValue { |
| 1194 | item: il::DataItem::Fn(state.methodNames[i]), |
| 1195 | count: 1, |
| 1196 | }; |
| 1197 | } |
| 1198 | self.data.append(il::Data { |
| 1199 | name: vName, |
| 1200 | size: traitInfo.methods.len as u32 * resolver::PTR_SIZE, |
| 1201 | alignment: resolver::PTR_SIZE, |
| 1202 | readOnly: true, |
| 1203 | isZeroInit: false, |
| 1204 | values: &values[..traitInfo.methods.len as u32], |
| 1205 | }, alloc::arenaAllocator(self.arena)); |
| 1206 | return nil; |
| 1207 | } |
| 1208 | |
| 1209 | /// Lower a method node into an IL function with the given qualified name. |
| 1210 | /// Shared by both instance methods and standalone methods. |
| 1211 | unsafe fn lowerMethod 'arena 'phase ( |
| 1212 | self: &mut Lowerer 'arena 'phase, |
| 1213 | node: *ast::Node, |
| 1214 | qualName: *[u8], |
| 1215 | receiverName: *ast::Node, |
| 1216 | sig: ast::FnSig, |
| 1217 | body: *ast::Node, |
| 1218 | functionArena: &mut alloc::Arena, |
| 1219 | ) -> ?*unsafe il::Fn throws (LowerError) where 'arena: 'phase { |
| 1220 | let data = resolver::nodeData(self.resolver, node); |
| 1221 | let case resolver::Type::Fn(fnType) = data.ty else { |
| 1222 | throw LowerError::ExpectedFunction; |
| 1223 | }; |
| 1224 | let sym = data.sym else throw LowerError::MissingSymbol(node); |
| 1225 | registerSymbolName(self, sym, qualName); |
| 1226 | |
| 1227 | let parent: 'function = &mut *self, arena = &mut *functionArena where 'phase: 'function in { |
| 1228 | let mut fnLow = fnLowerer(parent, node, fnType, qualName, arena); |
| 1229 | if requiresReturnParam(fnType) { |
| 1230 | set fnLow.returnReg = nextReg(&mut fnLow); |
| 1231 | } |
| 1232 | let lowParams = try lowerParams(&mut fnLow, *fnType, sig.params, receiverName); |
| 1233 | let func = try! alloc::allocRaw(fnLow.arena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *unsafe mut il::Fn; |
| 1234 | |
| 1235 | let returnType = il::Type::W64 if fnType.throwList.len > 0 |
| 1236 | else ilType(fnLow.low, *fnType.returnType); |
| 1237 | set *func = il::Fn { |
| 1238 | name: qualName, |
| 1239 | params: lowParams, |
| 1240 | returnType, |
| 1241 | isExtern: false, |
| 1242 | isLeaf: true, |
| 1243 | blocks: &[], |
| 1244 | }; |
| 1245 | set func.blocks = try lowerFnBody(&mut fnLow, body); |
| 1246 | set func.isLeaf = fnLow.isLeaf; |
| 1247 | |
| 1248 | return func; |
| 1249 | } |
| 1250 | } |
| 1251 | |
| 1252 | /// Lower a standalone method declaration. |
| 1253 | /// Produces a function with qualified name `Type::method`. |
| 1254 | unsafe fn lowerMethodDecl 'arena 'phase ( |
| 1255 | self: &mut Lowerer 'arena 'phase, |
| 1256 | node: *ast::Node, |
| 1257 | name: *ast::Node, |
| 1258 | receiverName: *ast::Node, |
| 1259 | sig: ast::FnSig, |
| 1260 | body: *ast::Node, |
| 1261 | functionArena: &mut alloc::Arena, |
| 1262 | ) -> ?*unsafe il::Fn throws (LowerError) where 'arena: 'phase { |
| 1263 | let sym = resolver::nodeData(self.resolver, node).sym |
| 1264 | else throw LowerError::MissingSymbol(node); |
| 1265 | let case ast::NodeValue::Ident(mName) = name.value |
| 1266 | else throw LowerError::ExpectedIdentifier; |
| 1267 | let me = resolver::findMethodBySymbol(self.resolver, sym) |
| 1268 | else throw LowerError::MissingMetadata; |
| 1269 | let qualName = instanceMethodName(self, nil, me.concreteTypeName, mName); |
| 1270 | |
| 1271 | return try lowerMethod(self, node, qualName, receiverName, sig, body, functionArena); |
| 1272 | } |
| 1273 | |
| 1274 | /// Check if a function should be lowered. |
| 1275 | fn shouldLowerFn(decl: &ast::FnDecl, buildTest: bool) -> bool { |
| 1276 | if checkAttr(decl.attrs, ast::Attribute::Test) { |
| 1277 | return buildTest; |
| 1278 | } |
| 1279 | return true; |
| 1280 | } |
| 1281 | |
| 1282 | /// Check if a specific attribute is present in the attribute set. |
| 1283 | fn checkAttr(attrs: ?ast::Attributes, attr: ast::Attribute) -> bool { |
| 1284 | if let a = attrs { |
| 1285 | return ast::attributesContains(&a, attr); |
| 1286 | } |
| 1287 | return false; |
| 1288 | } |
| 1289 | |
| 1290 | /// Create a label with a numeric suffix, eg. `@base0`. |
| 1291 | /// This ensures unique labels like `@then0`, `@then1`, etc. |
| 1292 | unsafe fn labelWithSuffix 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, base: *[u8], suffix: u32) -> *[u8] throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 1293 | let mut digits: [u8; fmt::U32_STR_LEN] = undefined; |
| 1294 | let start = fmt::formatU32(suffix, &mut digits[..]); |
| 1295 | let totalLen = base.len + digits.len - start; |
| 1296 | let buf = try! alloc::allocSlice(self.arena, 1, 1, totalLen) as *mut [u8]; |
| 1297 | |
| 1298 | try! mem::copy(&mut buf[..base.len], base); |
| 1299 | try! mem::copy(&mut buf[base.len..totalLen], &digits[start..]); |
| 1300 | |
| 1301 | return &buf[..totalLen]; |
| 1302 | } |
| 1303 | |
| 1304 | /// Generate a unique label by appending the global counter to the base. |
| 1305 | unsafe fn nextLabel 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, base: *[u8]) -> *[u8] throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 1306 | let idx = self.labelCounter; |
| 1307 | set self.labelCounter += 1; |
| 1308 | |
| 1309 | return try labelWithSuffix(self, base, idx); |
| 1310 | } |
| 1311 | |
| 1312 | /////////////////////////////// |
| 1313 | // Data Section Construction // |
| 1314 | /////////////////////////////// |
| 1315 | |
| 1316 | // Functions for building the data section from constant/static |
| 1317 | // declarations and inline literals. |
| 1318 | |
| 1319 | /// Convert a constant integer payload to a signed 64-bit value. |
| 1320 | fn constIntToI64(intVal: resolver::ConstInt) -> i64 { |
| 1321 | return (0 - intVal.magnitude as i64) if intVal.negative else intVal.magnitude as i64; |
| 1322 | } |
| 1323 | |
| 1324 | /// Convert a scalar constant value to an i64. |
| 1325 | /// String constants are not handled here and should be checked before calling. |
| 1326 | /// Panics if a non-scalar value is passed. |
| 1327 | fn constToScalar(val: resolver::ConstValue) -> i64 { |
| 1328 | match val { |
| 1329 | case resolver::ConstValue::Bool(b) => return 1 if b else 0, |
| 1330 | case resolver::ConstValue::Char(c) => return c as i64, |
| 1331 | case resolver::ConstValue::Int(i) => return constIntToI64(i), |
| 1332 | else => panic, |
| 1333 | } |
| 1334 | } |
| 1335 | |
| 1336 | /// Convert a constant value to an IL value. |
| 1337 | 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 { |
| 1338 | if let case resolver::ConstValue::String(s) = val { |
| 1339 | return try lowerStringLit(self, node, s); |
| 1340 | } |
| 1341 | return il::Val::Imm(constToScalar(val)); |
| 1342 | } |
| 1343 | |
| 1344 | /// Convert a resolver constant value to an IL data initializer item. |
| 1345 | unsafe fn constValueToDataItem 'arena 'phase (self: &mut Lowerer 'arena 'phase, val: resolver::ConstValue, typ: resolver::Type) -> il::DataItem where 'arena: 'phase { |
| 1346 | if let case resolver::ConstValue::String(s) = val { |
| 1347 | return il::DataItem::Str(s); |
| 1348 | } |
| 1349 | // Bool and char are byte-sized; integer uses the declared type. |
| 1350 | let mut irTyp = il::Type::W8; |
| 1351 | if let case resolver::ConstValue::Int(_) = val { |
| 1352 | set irTyp = ilType(self, typ); |
| 1353 | } |
| 1354 | return il::DataItem::Val { typ: irTyp, val: constToScalar(val) }; |
| 1355 | } |
| 1356 | |
| 1357 | /// Lower scalar-like constant nodes into data values, including fallback handling |
| 1358 | /// for void-variant tags and slice string initializers. |
| 1359 | unsafe fn lowerConstScalarDataInto 'arena 'phase ( |
| 1360 | self: &mut Lowerer 'arena 'phase, |
| 1361 | node: *ast::Node, |
| 1362 | ty: resolver::Type, |
| 1363 | dataPrefix: *[u8], |
| 1364 | b: &mut DataValueBuilder |
| 1365 | ) throws (LowerError) where 'arena: 'phase { |
| 1366 | let val = resolver::constValueEntry(self.resolver, node) else { |
| 1367 | if let idx = voidVariantIndex(self.resolver, node) { |
| 1368 | dataBuilderPush(b, il::DataValue { |
| 1369 | item: il::DataItem::Val { typ: il::Type::W8, val: idx }, |
| 1370 | count: 1 |
| 1371 | }); |
| 1372 | return; |
| 1373 | } |
| 1374 | throw LowerError::MissingConst(node); |
| 1375 | }; |
| 1376 | |
| 1377 | if let case resolver::ConstValue::String(s) = val { |
| 1378 | if let case resolver::Type::Slice { .. } = ty { |
| 1379 | let strSym = try getOrCreateStringData(self, s, dataPrefix); |
| 1380 | dataSliceHeader(b, strSym, s.len); |
| 1381 | return; |
| 1382 | } |
| 1383 | } |
| 1384 | dataBuilderPush(b, il::DataValue { |
| 1385 | item: constValueToDataItem(self, val, ty), |
| 1386 | count: 1 |
| 1387 | }); |
| 1388 | } |
| 1389 | |
| 1390 | /// Lower a constant or static declaration to the data section. |
| 1391 | unsafe fn lowerDataDecl 'arena 'phase ( |
| 1392 | self: &mut Lowerer 'arena 'phase, |
| 1393 | node: *ast::Node, |
| 1394 | value: *ast::Node, |
| 1395 | readOnly: bool |
| 1396 | ) throws (LowerError) where 'arena: 'phase { |
| 1397 | let data = resolver::nodeData(self.resolver, node); |
| 1398 | let sym = data.sym else { |
| 1399 | throw LowerError::MissingSymbol(node); |
| 1400 | }; |
| 1401 | if data.ty == resolver::Type::Unknown { |
| 1402 | throw LowerError::MissingType(node); |
| 1403 | } |
| 1404 | let layout = resolver::getTypeLayout(data.ty); |
| 1405 | let qualName = lookupSymbolName(self, sym) else qualifyName(self, nil, sym.name); |
| 1406 | let mut b = dataBuilder(alloc::arenaAllocator(self.arena)); |
| 1407 | try lowerConstDataInto(self, value, data.ty, layout.size, qualName, &mut b); |
| 1408 | let result = dataBuilderFinish(b); |
| 1409 | |
| 1410 | self.data.append(il::Data { |
| 1411 | name: qualName, |
| 1412 | size: layout.size, |
| 1413 | alignment: layout.alignment, |
| 1414 | readOnly, |
| 1415 | isZeroInit: not readOnly and result.zeroInit, |
| 1416 | values: result.values, |
| 1417 | }, alloc::arenaAllocator(self.arena)); |
| 1418 | } |
| 1419 | |
| 1420 | /// Emit the in-memory representation of a slice header: `{ ptr, len, cap }`. |
| 1421 | fn dataSliceHeader(b: &mut DataValueBuilder, dataSym: *[u8], len: u32) { |
| 1422 | dataBuilderPush(b, il::DataValue { |
| 1423 | item: il::DataItem::Sym(dataSym), |
| 1424 | count: 1 |
| 1425 | }); |
| 1426 | dataBuilderPush(b, il::DataValue { |
| 1427 | item: il::DataItem::Val { |
| 1428 | typ: il::Type::W32, |
| 1429 | val: len as i64 |
| 1430 | }, |
| 1431 | count: 1 |
| 1432 | }); |
| 1433 | dataBuilderPush(b, il::DataValue { |
| 1434 | item: il::DataItem::Val { |
| 1435 | typ: il::Type::W32, |
| 1436 | val: len as i64 |
| 1437 | }, |
| 1438 | count: 1 |
| 1439 | }); |
| 1440 | } |
| 1441 | |
| 1442 | /// Lower a compile-time `&[...]` expression to a concrete slice header. |
| 1443 | unsafe fn lowerConstAddressSliceInto 'arena 'phase ( |
| 1444 | self: &mut Lowerer 'arena 'phase, |
| 1445 | addr: ast::AddressOf, |
| 1446 | ty: resolver::Type, |
| 1447 | dataPrefix: *[u8], |
| 1448 | b: &mut DataValueBuilder |
| 1449 | ) throws (LowerError) where 'arena: 'phase { |
| 1450 | let case resolver::Type::Slice { mutable, .. } = ty |
| 1451 | else throw LowerError::ExpectedSliceOrArray; |
| 1452 | let targetTy = resolver::typeFor(self.resolver, addr.target) |
| 1453 | else throw LowerError::MissingType(addr.target); |
| 1454 | let case resolver::Type::Array(arrInfo) = targetTy |
| 1455 | else throw LowerError::ExpectedArray; |
| 1456 | |
| 1457 | let mut nested = dataBuilder(alloc::arenaAllocator(self.arena)); |
| 1458 | let layout = resolver::getTypeLayout(targetTy); |
| 1459 | try lowerConstDataInto(self, addr.target, targetTy, layout.size, dataPrefix, &mut nested); |
| 1460 | |
| 1461 | let backing = dataBuilderFinish(nested); |
| 1462 | let readOnly = not mutable; |
| 1463 | let mut dataName: *[u8] = undefined; |
| 1464 | if readOnly { |
| 1465 | if let found = findConstData(self, backing.values, layout.alignment) { |
| 1466 | set dataName = found; |
| 1467 | } else { |
| 1468 | set dataName = try pushDeclData(self, layout.size, layout.alignment, readOnly, backing.values, dataPrefix); |
| 1469 | } |
| 1470 | } else { |
| 1471 | set dataName = try pushDeclData(self, layout.size, layout.alignment, readOnly, backing.values, dataPrefix); |
| 1472 | } |
| 1473 | dataSliceHeader(b, dataName, arrInfo.length); |
| 1474 | } |
| 1475 | |
| 1476 | /// Lower a constant expression payload into a builder without slot padding. |
| 1477 | /// Compute the type layout when nil or undefined data needs a byte count. |
| 1478 | unsafe fn lowerConstDataPayloadInto 'arena 'phase ( |
| 1479 | self: &mut Lowerer 'arena 'phase, |
| 1480 | node: *ast::Node, |
| 1481 | ty: resolver::Type, |
| 1482 | dataPrefix: *[u8], |
| 1483 | b: &mut DataValueBuilder |
| 1484 | ) throws (LowerError) where 'arena: 'phase { |
| 1485 | // Optional coercions define the tag and payload layout of constant values. |
| 1486 | if let case resolver::Type::Optional(inner) = ty { |
| 1487 | if let coercion = resolver::coercionFor(self.resolver, node) { |
| 1488 | if let case resolver::Coercion::OptionalLift(target) = coercion { |
| 1489 | if resolver::typesEqual(target, ty) { |
| 1490 | if resolver::isNullableType(*inner) { |
| 1491 | try lowerConstDataPayloadInto(self, node, *inner, dataPrefix, b); |
| 1492 | } else { |
| 1493 | let layout = resolver::getTypeLayout(ty); |
| 1494 | let offset = resolver::getOptionalValOffset(*inner); |
| 1495 | dataBuilderPush(b, il::DataValue { |
| 1496 | item: il::DataItem::Val { typ: il::Type::W8, val: 1 }, count: 1, |
| 1497 | }); |
| 1498 | if offset > 1 { |
| 1499 | dataBuilderPush(b, il::DataValue { item: il::DataItem::Undef, count: offset - 1 }); |
| 1500 | } |
| 1501 | try lowerConstDataInto(self, node, *inner, layout.size - offset, dataPrefix, b); |
| 1502 | } |
| 1503 | return; |
| 1504 | } |
| 1505 | } |
| 1506 | } |
| 1507 | } |
| 1508 | // Function pointer references in constant data. |
| 1509 | if let case resolver::Type::Fn(_) = ty { |
| 1510 | let sym = resolver::nodeData(self.resolver, node).sym |
| 1511 | else throw LowerError::MissingSymbol(node); |
| 1512 | let modId = resolver::moduleIdForSymbol(self.resolver, sym); |
| 1513 | let qualName = qualifyName(self, modId, sym.name); |
| 1514 | dataBuilderPush(b, il::DataValue { |
| 1515 | item: il::DataItem::Fn(qualName), count: 1, |
| 1516 | }); |
| 1517 | return; |
| 1518 | } |
| 1519 | // In constant data, a void variant of a mixed union still occupies the |
| 1520 | // full tagged-union slot. Emitting only the tag corrupts following fields. |
| 1521 | if let sym = resolver::nodeData(self.resolver, node).sym { |
| 1522 | if let case resolver::SymbolData::Variant { type: resolver::Type::Void, .. } = sym.data { |
| 1523 | if let case resolver::Type::Nominal(resolver::NominalType::Union(_)) = ty { |
| 1524 | try lowerConstUnionVariantInto(self, node, sym, ty, &mut [], dataPrefix, b); |
| 1525 | return; |
| 1526 | } |
| 1527 | } |
| 1528 | } |
| 1529 | match node.value { |
| 1530 | case ast::NodeValue::Nil => { |
| 1531 | let case resolver::Type::Optional(_) = ty else throw LowerError::NilInNonOptional; |
| 1532 | let layout = resolver::getTypeLayout(ty); |
| 1533 | dataBuilderPush(b, il::DataValue { |
| 1534 | item: il::DataItem::Val { typ: il::Type::W8, val: 0 }, count: layout.size, |
| 1535 | }); |
| 1536 | }, |
| 1537 | case ast::NodeValue::Undef => { |
| 1538 | let layout = resolver::getTypeLayout(ty); |
| 1539 | dataBuilderPush(b, il::DataValue { |
| 1540 | item: il::DataItem::Undef, |
| 1541 | count: layout.size |
| 1542 | }); |
| 1543 | } |
| 1544 | case ast::NodeValue::ArrayLit(elems) => |
| 1545 | try lowerConstArrayLitInto(self, elems, ty, dataPrefix, b), |
| 1546 | case ast::NodeValue::ArrayRepeatLit(repeat) => |
| 1547 | try lowerConstArrayRepeatInto(self, repeat, ty, dataPrefix, b), |
| 1548 | case ast::NodeValue::RecordLit(recLit) => |
| 1549 | try lowerConstRecordLitInto(self, node, recLit, ty, dataPrefix, b), |
| 1550 | case ast::NodeValue::Call(call) => { |
| 1551 | let calleeSym = resolver::nodeData(self.resolver, call.callee).sym |
| 1552 | else throw LowerError::MissingSymbol(call.callee); |
| 1553 | match calleeSym.data { |
| 1554 | case resolver::SymbolData::Variant { .. } => |
| 1555 | try lowerConstUnionVariantInto(self, node, calleeSym, ty, call.args, dataPrefix, b), |
| 1556 | case resolver::SymbolData::Type(resolver::NominalType::Record(recInfo)) => { |
| 1557 | try lowerConstRecordCtorInto(self, call.args, recInfo, dataPrefix, b); |
| 1558 | } |
| 1559 | else => throw LowerError::MissingConst(node) |
| 1560 | } |
| 1561 | } |
| 1562 | case ast::NodeValue::AddressOf(addr) => { |
| 1563 | try lowerConstAddressSliceInto(self, addr, ty, dataPrefix, b); |
| 1564 | } |
| 1565 | case ast::NodeValue::Ident(_) => { |
| 1566 | // Identifier referencing a constant. |
| 1567 | let sym = resolver::nodeData(self.resolver, node).sym |
| 1568 | else throw LowerError::MissingSymbol(node); |
| 1569 | let case ast::NodeValue::ConstDecl(decl) = sym.node.value |
| 1570 | else throw LowerError::MissingConst(node); |
| 1571 | |
| 1572 | try lowerConstDataPayloadInto(self, decl.value, ty, dataPrefix, b); |
| 1573 | }, |
| 1574 | case ast::NodeValue::ScopeAccess(_) => { |
| 1575 | let sym = resolver::nodeData(self.resolver, node).sym |
| 1576 | else throw LowerError::MissingSymbol(node); |
| 1577 | if let case ast::NodeValue::ConstDecl(decl) = sym.node.value { |
| 1578 | try lowerConstDataPayloadInto(self, decl.value, ty, dataPrefix, b); |
| 1579 | } else { |
| 1580 | try lowerConstScalarDataInto(self, node, ty, dataPrefix, b); |
| 1581 | } |
| 1582 | } |
| 1583 | else => { |
| 1584 | // Scalar values: integers, bools, strings, void union variants, etc. |
| 1585 | try lowerConstScalarDataInto(self, node, ty, dataPrefix, b); |
| 1586 | } |
| 1587 | } |
| 1588 | } |
| 1589 | |
| 1590 | /// Lower a constant expression into a builder, padding to the given slot size. |
| 1591 | unsafe fn lowerConstDataInto 'arena 'phase ( |
| 1592 | self: &mut Lowerer 'arena 'phase, |
| 1593 | node: *ast::Node, |
| 1594 | ty: resolver::Type, |
| 1595 | slotSize: u32, |
| 1596 | dataPrefix: *[u8], |
| 1597 | b: &mut DataValueBuilder |
| 1598 | ) throws (LowerError) where 'arena: 'phase { |
| 1599 | let layout = resolver::getTypeLayout(ty); |
| 1600 | try lowerConstDataPayloadInto(self, node, ty, dataPrefix, b); |
| 1601 | // Pad to fill the enclosing slot. |
| 1602 | let padding = slotSize - layout.size; |
| 1603 | if padding > 0 { |
| 1604 | dataBuilderPush(b, il::DataValue { item: il::DataItem::Undef, count: padding }); |
| 1605 | } |
| 1606 | } |
| 1607 | |
| 1608 | /// Flatten a constant array literal `[a, b, c]` into a builder. |
| 1609 | /// Each element payload fills its type size; no extra slot padding is needed. |
| 1610 | unsafe fn lowerConstArrayLitInto 'arena 'phase ( |
| 1611 | self: &mut Lowerer 'arena 'phase, |
| 1612 | elems: *[*ast::Node], |
| 1613 | ty: resolver::Type, |
| 1614 | dataPrefix: *[u8], |
| 1615 | b: &mut DataValueBuilder |
| 1616 | ) throws (LowerError) where 'arena: 'phase { |
| 1617 | let case resolver::Type::Array(arrInfo) = ty |
| 1618 | else throw LowerError::ExpectedArray; |
| 1619 | let elemTy = *arrInfo.item; |
| 1620 | |
| 1621 | for elem in elems { |
| 1622 | try lowerConstDataPayloadInto(self, elem, elemTy, dataPrefix, b); |
| 1623 | } |
| 1624 | } |
| 1625 | |
| 1626 | /// Build data values for a constant array repeat literal `[item; count]`. |
| 1627 | /// Repeat element payloads without extra slot padding. Undefined data uses |
| 1628 | /// the element layout to compute the total byte count. |
| 1629 | unsafe fn lowerConstArrayRepeatInto 'arena 'phase ( |
| 1630 | self: &mut Lowerer 'arena 'phase, |
| 1631 | repeat: ast::ArrayRepeatLit, |
| 1632 | ty: resolver::Type, |
| 1633 | dataPrefix: *[u8], |
| 1634 | b: &mut DataValueBuilder |
| 1635 | ) throws (LowerError) where 'arena: 'phase { |
| 1636 | let case resolver::Type::Array(arrInfo) = ty |
| 1637 | else throw LowerError::ExpectedArray; |
| 1638 | let length = arrInfo.length; |
| 1639 | let elemTy = *arrInfo.item; |
| 1640 | |
| 1641 | if let case resolver::Type::Optional(_) = elemTy { |
| 1642 | for _ in 0..length { |
| 1643 | try lowerConstDataPayloadInto(self, repeat.item, elemTy, dataPrefix, b); |
| 1644 | } |
| 1645 | return; |
| 1646 | } |
| 1647 | |
| 1648 | if let case ast::NodeValue::Undef = repeat.item.value { |
| 1649 | let elemLayout = resolver::getTypeLayout(elemTy); |
| 1650 | dataBuilderPush(b, il::DataValue { |
| 1651 | item: il::DataItem::Undef, |
| 1652 | count: elemLayout.size * length |
| 1653 | }); |
| 1654 | } else if let val = resolver::constValueEntry(self.resolver, repeat.item) { |
| 1655 | if let case resolver::ConstValue::String(_) = val { |
| 1656 | // A string used as a slice is represented by a three-word slice |
| 1657 | // header, not by the bytes of the string itself. |
| 1658 | for _ in 0..length { |
| 1659 | try lowerConstDataPayloadInto(self, repeat.item, elemTy, dataPrefix, b); |
| 1660 | } |
| 1661 | } else { |
| 1662 | dataBuilderPush(b, il::DataValue { |
| 1663 | item: constValueToDataItem(self, val, elemTy), |
| 1664 | count: length |
| 1665 | }); |
| 1666 | } |
| 1667 | } else { |
| 1668 | for _ in 0..length { |
| 1669 | try lowerConstDataPayloadInto(self, repeat.item, elemTy, dataPrefix, b); |
| 1670 | } |
| 1671 | } |
| 1672 | } |
| 1673 | |
| 1674 | /// Build data values for a constant record literal. |
| 1675 | /// Each field is lowered with a slot size that includes trailing padding. |
| 1676 | unsafe fn lowerConstRecordLitInto 'arena 'phase ( |
| 1677 | self: &mut Lowerer 'arena 'phase, |
| 1678 | node: *ast::Node, |
| 1679 | recLit: ast::RecordLit, |
| 1680 | ty: resolver::Type, |
| 1681 | dataPrefix: *[u8], |
| 1682 | b: &mut DataValueBuilder |
| 1683 | ) throws (LowerError) where 'arena: 'phase { |
| 1684 | match ty { |
| 1685 | case resolver::Type::Nominal(resolver::NominalType::Record(recInfo)) => { |
| 1686 | try lowerConstRecordCtorInto(self, recLit.fields, recInfo, dataPrefix, b); |
| 1687 | } |
| 1688 | case resolver::Type::Nominal(resolver::NominalType::Union(_)) => { |
| 1689 | let typeName = recLit.typeName else { |
| 1690 | throw LowerError::ExpectedVariant; |
| 1691 | }; |
| 1692 | let sym = resolver::nodeData(self.resolver, typeName).sym else { |
| 1693 | throw LowerError::MissingSymbol(typeName); |
| 1694 | }; |
| 1695 | try lowerConstUnionVariantInto(self, node, sym, ty, recLit.fields, dataPrefix, b); |
| 1696 | } |
| 1697 | else => throw LowerError::ExpectedRecord, |
| 1698 | } |
| 1699 | } |
| 1700 | |
| 1701 | /// Build data values for record constants. |
| 1702 | unsafe fn lowerConstRecordCtorInto 'arena 'phase ( |
| 1703 | self: &mut Lowerer 'arena 'phase, |
| 1704 | args: *[*ast::Node], |
| 1705 | recInfo: resolver::RecordType, |
| 1706 | dataPrefix: *[u8], |
| 1707 | b: &mut DataValueBuilder |
| 1708 | ) throws (LowerError) where 'arena: 'phase { |
| 1709 | let layout = *recInfo.layout; |
| 1710 | for argNode, i in args { |
| 1711 | let mut valueNode = argNode; |
| 1712 | if let case ast::NodeValue::RecordLitField(fieldLit) = argNode.value { |
| 1713 | set valueNode = fieldLit.value; |
| 1714 | } |
| 1715 | let fieldInfo = recInfo.fields[i]; |
| 1716 | let fieldOffset = fieldInfo.offset as u32; |
| 1717 | |
| 1718 | // Slot extends to the next field's offset, |
| 1719 | // or record size for the last field. |
| 1720 | let slotEnd = recInfo.fields[i + 1].offset as u32 if i + 1 < recInfo.fields.len else layout.size; |
| 1721 | let slotSize = slotEnd - fieldOffset; |
| 1722 | |
| 1723 | try lowerConstDataInto(self, valueNode, fieldInfo.fieldType, slotSize, dataPrefix, b); |
| 1724 | } |
| 1725 | } |
| 1726 | |
| 1727 | /// Build data values for a constant union variant value from payload fields/args. |
| 1728 | unsafe fn lowerConstUnionVariantInto 'arena 'phase ( |
| 1729 | self: &mut Lowerer 'arena 'phase, |
| 1730 | node: *ast::Node, |
| 1731 | variantSym: *unsafe mut resolver::Symbol, |
| 1732 | ty: resolver::Type, |
| 1733 | payloadArgs: *[*ast::Node], |
| 1734 | dataPrefix: *[u8], |
| 1735 | b: &mut DataValueBuilder |
| 1736 | ) throws (LowerError) where 'arena: 'phase { |
| 1737 | let case resolver::SymbolData::Variant { ordinal, index, .. } = variantSym.data |
| 1738 | else throw LowerError::UnexpectedNodeValue(node); |
| 1739 | |
| 1740 | let unionInfo = unionInfoFromType(ty) else { |
| 1741 | throw LowerError::MissingMetadata; |
| 1742 | }; |
| 1743 | let payloadType = unionInfo.variants[ordinal].valueType; |
| 1744 | let unionLayout = resolver::getTypeLayout(ty); |
| 1745 | let payloadSlotSize = unionLayout.size - unionInfo.valOffset; |
| 1746 | |
| 1747 | // Tag byte. |
| 1748 | dataBuilderPush(b, il::DataValue { |
| 1749 | item: il::DataItem::Val { |
| 1750 | typ: il::Type::W8, |
| 1751 | val: index as i64 |
| 1752 | }, |
| 1753 | count: 1 |
| 1754 | }); |
| 1755 | // Padding between tag and payload. |
| 1756 | if unionInfo.valOffset > 1 { |
| 1757 | dataBuilderPush(b, il::DataValue { |
| 1758 | item: il::DataItem::Undef, |
| 1759 | count: unionInfo.valOffset - 1 |
| 1760 | }); |
| 1761 | } |
| 1762 | if payloadType == resolver::Type::Void { |
| 1763 | if payloadSlotSize > 0 { |
| 1764 | dataBuilderPush(b, il::DataValue { |
| 1765 | item: il::DataItem::Undef, |
| 1766 | count: payloadSlotSize |
| 1767 | }); |
| 1768 | } |
| 1769 | return; |
| 1770 | } |
| 1771 | |
| 1772 | let case resolver::Type::Nominal(resolver::NominalType::Record(payloadRec)) = payloadType else { |
| 1773 | throw LowerError::ExpectedRecord; |
| 1774 | }; |
| 1775 | let payloadLayout = *payloadRec.layout; |
| 1776 | try lowerConstRecordCtorInto(self, payloadArgs, payloadRec, dataPrefix, b); |
| 1777 | |
| 1778 | // Unused bytes in the union payload slot for smaller variants. |
| 1779 | if payloadSlotSize > payloadLayout.size { |
| 1780 | dataBuilderPush(b, il::DataValue { |
| 1781 | item: il::DataItem::Undef, |
| 1782 | count: payloadSlotSize - payloadLayout.size |
| 1783 | }); |
| 1784 | } |
| 1785 | } |
| 1786 | |
| 1787 | /// Find an existing string data entry with matching content. |
| 1788 | // TODO: Optimize with hash table or remove? |
| 1789 | fn findStringData 'arena 'phase (self: &Lowerer 'arena 'phase, s: *[u8]) -> ?*[u8] where 'arena: 'phase { |
| 1790 | for d in &self.data[self.packageDataStart..] { |
| 1791 | if d.values.len == 1 { |
| 1792 | if let case il::DataItem::Str(existing) = d.values[0].item { |
| 1793 | if mem::eq(existing, s) { |
| 1794 | return d.name; |
| 1795 | } |
| 1796 | } |
| 1797 | } |
| 1798 | } |
| 1799 | return nil; |
| 1800 | } |
| 1801 | |
| 1802 | /// Compose a segmented symbol name from a list of path segments. |
| 1803 | /// Example: `["func", "nominal", "VALUE"]` -> `func$nominal$VALUE`. |
| 1804 | unsafe fn buildSegmentedName 'arena 'phase ( |
| 1805 | self: &mut Lowerer 'arena 'phase, |
| 1806 | segments: &[*[u8]] |
| 1807 | ) -> *[u8] throws (LowerError) where 'arena: 'phase { |
| 1808 | assert segments.len > 0; |
| 1809 | |
| 1810 | let mut totalLen: u32 = 0; |
| 1811 | for segment in segments { |
| 1812 | set totalLen += segment.len; |
| 1813 | } |
| 1814 | if segments.len > 1 { |
| 1815 | set totalLen += segments.len - 1; |
| 1816 | } |
| 1817 | let buf = try! alloc::allocSlice(self.arena, 1, 1, totalLen) as *mut [u8]; |
| 1818 | let mut pos: u32 = 0; |
| 1819 | |
| 1820 | for segment, i in segments { |
| 1821 | set pos += try! mem::copy(&mut buf[pos..], segment); |
| 1822 | if i + 1 <> segments.len { |
| 1823 | set buf[pos] = '$'; |
| 1824 | set pos += 1; |
| 1825 | } |
| 1826 | } |
| 1827 | assert pos == totalLen; |
| 1828 | |
| 1829 | return &buf[..totalLen]; |
| 1830 | } |
| 1831 | |
| 1832 | /// Generate a unique name for declaration-local backing data entries. |
| 1833 | unsafe fn nextDeclDataName 'arena 'phase ( |
| 1834 | self: &mut Lowerer 'arena 'phase, |
| 1835 | prefix: *[u8], |
| 1836 | count: u32, |
| 1837 | namespace: *[u8] |
| 1838 | ) -> *[u8] throws (LowerError) where 'arena: 'phase { |
| 1839 | let mut digits: [u8; fmt::U32_STR_LEN] = undefined; |
| 1840 | let start = fmt::formatU32(count, &mut digits[..]); |
| 1841 | let suffix = try! alloc::allocSlice(self.arena, 1, 1, digits.len - start) as *mut [u8]; |
| 1842 | try! mem::copy(suffix, &digits[start..]); |
| 1843 | let segments = [prefix, namespace, suffix]; |
| 1844 | |
| 1845 | return try buildSegmentedName(self, &segments[..]); |
| 1846 | } |
| 1847 | |
| 1848 | /// Append a data entry using a function-local literal namespace (`prefix$literal$N`). |
| 1849 | unsafe fn pushDeclData 'arena 'phase ( |
| 1850 | self: &mut Lowerer 'arena 'phase, |
| 1851 | size: u32, |
| 1852 | alignment: u32, |
| 1853 | readOnly: bool, |
| 1854 | values: *[il::DataValue], |
| 1855 | dataPrefix: *[u8] |
| 1856 | ) -> *[u8] throws (LowerError) where 'arena: 'phase { |
| 1857 | let dataCount = self.data.len; |
| 1858 | let name = try nextDeclDataName(self, dataPrefix, dataCount, "literal"); |
| 1859 | self.data.append(il::Data { |
| 1860 | name, |
| 1861 | size, |
| 1862 | alignment, |
| 1863 | readOnly, |
| 1864 | isZeroInit: not readOnly and dataValuesAreZeroInit(values), |
| 1865 | values, |
| 1866 | }, alloc::arenaAllocator(self.arena)); |
| 1867 | |
| 1868 | return name; |
| 1869 | } |
| 1870 | |
| 1871 | /// Find or create read-only string data and return its symbol name. |
| 1872 | unsafe fn getOrCreateStringData 'arena 'phase ( |
| 1873 | self: &mut Lowerer 'arena 'phase, |
| 1874 | s: *[u8], |
| 1875 | dataPrefix: *[u8] |
| 1876 | ) -> *[u8] throws (LowerError) where 'arena: 'phase { |
| 1877 | if let existing = findStringData(self, s) { |
| 1878 | return existing; |
| 1879 | } |
| 1880 | let values = try! alloc::allocSlice( |
| 1881 | self.arena, @sizeOf(il::DataValue), @alignOf(il::DataValue), 1 |
| 1882 | ) as *mut [il::DataValue]; |
| 1883 | |
| 1884 | set values[0] = il::DataValue { |
| 1885 | item: il::DataItem::Str(s), |
| 1886 | count: 1 |
| 1887 | }; |
| 1888 | return try pushDeclData(self, s.len, 1, true, &values[..1], dataPrefix); |
| 1889 | } |
| 1890 | |
| 1891 | /// Compare two data items for structural equality. |
| 1892 | /// Unlike raw byte comparison, this correctly ignores padding bytes in unions. |
| 1893 | fn dataItemEq(a: il::DataItem, b: il::DataItem) -> bool { |
| 1894 | match a { |
| 1895 | case il::DataItem::Val { typ: aTyp, val: aVal } => |
| 1896 | if let case il::DataItem::Val { typ: bTyp, val: bVal } = b { |
| 1897 | return aTyp == bTyp and aVal == bVal; |
| 1898 | } else { |
| 1899 | return false; |
| 1900 | }, |
| 1901 | case il::DataItem::Sym(aPtr) => |
| 1902 | if let case il::DataItem::Sym(bPtr) = b { |
| 1903 | return mem::eq(aPtr, bPtr); |
| 1904 | } else { |
| 1905 | return false; |
| 1906 | }, |
| 1907 | case il::DataItem::Fn(aName) => |
| 1908 | if let case il::DataItem::Fn(bName) = b { |
| 1909 | return mem::eq(aName, bName); |
| 1910 | } else { |
| 1911 | return false; |
| 1912 | }, |
| 1913 | case il::DataItem::Str(aStr) => |
| 1914 | if let case il::DataItem::Str(bStr) = b { |
| 1915 | return mem::eq(aStr, bStr); |
| 1916 | } else { |
| 1917 | return false; |
| 1918 | }, |
| 1919 | case il::DataItem::Undef => |
| 1920 | if let case il::DataItem::Undef = b { |
| 1921 | return true; |
| 1922 | } else { |
| 1923 | return false; |
| 1924 | }, |
| 1925 | } |
| 1926 | } |
| 1927 | |
| 1928 | /// Compare two data value slices for structural equality. |
| 1929 | fn dataValuesEq(a: &[il::DataValue], b: &[il::DataValue]) -> bool { |
| 1930 | if a.len <> b.len { |
| 1931 | return false; |
| 1932 | } |
| 1933 | for i in 0..a.len { |
| 1934 | if a[i].count <> b[i].count { |
| 1935 | return false; |
| 1936 | } |
| 1937 | if not dataItemEq(a[i].item, b[i].item) { |
| 1938 | return false; |
| 1939 | } |
| 1940 | } |
| 1941 | return true; |
| 1942 | } |
| 1943 | |
| 1944 | /// Find an existing read-only slice data entry with matching values. |
| 1945 | // TODO: Optimize with hash table or remove? |
| 1946 | fn findSliceData 'arena 'phase (self: &Lowerer 'arena 'phase, values: *[il::DataValue], alignment: u32) -> ?*[u8] where 'arena: 'phase { |
| 1947 | for d in &self.data[self.packageDataStart..] { |
| 1948 | if d.alignment == alignment and d.readOnly and dataValuesEq(d.values, values) { |
| 1949 | return d.name; |
| 1950 | } |
| 1951 | } |
| 1952 | return nil; |
| 1953 | } |
| 1954 | |
| 1955 | /// Find existing constant data entry with matching content. |
| 1956 | /// Handles both string data and slice data. |
| 1957 | fn findConstData 'arena 'phase (self: &Lowerer 'arena 'phase, values: *[il::DataValue], alignment: u32) -> ?*[u8] where 'arena: 'phase { |
| 1958 | // Fast path for strings. |
| 1959 | if values.len == 1 and alignment == 1 { |
| 1960 | if let case il::DataItem::Str(s) = values[0].item { |
| 1961 | return findStringData(self, s); |
| 1962 | } |
| 1963 | } |
| 1964 | // General case: byte comparison of data values. |
| 1965 | return findSliceData(self, values, alignment); |
| 1966 | } |
| 1967 | |
| 1968 | /// Lower constant data to a slice value. |
| 1969 | /// Creates or reuses a data section entry, then builds a slice header on the stack. |
| 1970 | unsafe fn lowerConstDataAsSlice 'arena 'phase 'function ( |
| 1971 | self: &mut FnLowerer 'arena 'phase 'function, |
| 1972 | result: &ConstDataResult, |
| 1973 | alignment: u32, |
| 1974 | readOnly: bool, |
| 1975 | elemTy: *resolver::Type, |
| 1976 | mutable: bool, |
| 1977 | length: u32 |
| 1978 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 1979 | let values = result.values; |
| 1980 | let elemLayout = resolver::getTypeLayout(*elemTy); |
| 1981 | let size = elemLayout.size * length; |
| 1982 | let mut dataName: *[u8] = undefined; |
| 1983 | let mut found: ?*[u8] = nil; |
| 1984 | if readOnly { |
| 1985 | set found = findConstData(self.low, values, alignment); |
| 1986 | } |
| 1987 | if let name = found { |
| 1988 | set dataName = name; |
| 1989 | } else { |
| 1990 | set dataName = try nextDataName(self); |
| 1991 | self.low.data.append(il::Data { |
| 1992 | name: dataName, |
| 1993 | size, |
| 1994 | alignment, |
| 1995 | readOnly, |
| 1996 | isZeroInit: not readOnly and result.zeroInit, |
| 1997 | values, |
| 1998 | }, alloc::arenaAllocator(self.low.arena)); |
| 1999 | } |
| 2000 | |
| 2001 | // Get data address. |
| 2002 | let ptrReg = nextReg(self); |
| 2003 | emit(self, il::Instr::Copy { dst: ptrReg, val: il::Val::DataSym(dataName) }); |
| 2004 | |
| 2005 | return try buildSliceValue( |
| 2006 | self, elemTy, mutable, il::Val::Reg(ptrReg), il::Val::Imm(length as i64), il::Val::Imm(length as i64) |
| 2007 | ); |
| 2008 | } |
| 2009 | |
| 2010 | /// Generate a unique data name for inline literals, eg. `fnName$literal$N`. |
| 2011 | unsafe fn nextDataName 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) -> *[u8] throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2012 | let counter = self.dataCounter; |
| 2013 | set self.dataCounter += 1; |
| 2014 | let fnName = self.fnName; |
| 2015 | return try nextDeclDataName(self.low, fnName, counter, "literal"); |
| 2016 | } |
| 2017 | |
| 2018 | /// Assign a unique function-local data symbol name. |
| 2019 | unsafe fn registerLocalDataDeclName 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2020 | let sym = resolver::nodeData(self.low.resolver, node).sym |
| 2021 | else throw LowerError::MissingSymbol(node); |
| 2022 | |
| 2023 | let prefix = self.fnName; |
| 2024 | let segments = [prefix, "nominal", sym.name]; |
| 2025 | let name = try buildSegmentedName(self.low, &segments[..]); |
| 2026 | |
| 2027 | let qualified = qualifyName(self.low, nil, name); |
| 2028 | registerSymbolName(self.low, sym, qualified); |
| 2029 | } |
| 2030 | |
| 2031 | /// Get the next available SSA register. |
| 2032 | fn nextReg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) -> il::Reg where 'arena: 'phase, 'phase: 'function { |
| 2033 | let reg = il::Reg { n: self.regCounter }; |
| 2034 | set self.regCounter += 1; |
| 2035 | return reg; |
| 2036 | } |
| 2037 | |
| 2038 | /// Look up the resolved type of an AST node, or throw `MissingType`. |
| 2039 | unsafe fn typeOf 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) -> resolver::Type throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2040 | let ty = resolver::typeFor(self.low.resolver, node) |
| 2041 | else throw LowerError::MissingType(node); |
| 2042 | return ty; |
| 2043 | } |
| 2044 | |
| 2045 | /// Look up the symbol for an AST node, or throw `MissingSymbol`. |
| 2046 | unsafe 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 { |
| 2047 | let sym = resolver::nodeData(self.low.resolver, node).sym |
| 2048 | else throw LowerError::MissingSymbol(node); |
| 2049 | return sym; |
| 2050 | } |
| 2051 | |
| 2052 | /// Remove the last block parameter and its associated variable. |
| 2053 | /// Used when detecting a trivial phi that can be eliminated. |
| 2054 | unsafe fn removeLastBlockParam 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId) where 'arena: 'phase, 'phase: 'function { |
| 2055 | let blk = getBlockMut(self, block); |
| 2056 | if blk.params.len > 0 { |
| 2057 | // TODO: Use `pop`? |
| 2058 | set blk.params = @sliceOf(blk.params.ptr, blk.params.len - 1, blk.params.cap); |
| 2059 | } |
| 2060 | if blk.paramVars.len > 0 { |
| 2061 | // TODO: Use `pop`? |
| 2062 | set blk.paramVars = @sliceOf(blk.paramVars.ptr, blk.paramVars.len - 1, blk.paramVars.cap); |
| 2063 | } |
| 2064 | } |
| 2065 | |
| 2066 | /// Rewrite cached SSA values for a variable across all blocks, and also |
| 2067 | /// rewrite any terminator arguments that reference the provisional register. |
| 2068 | /// The latter is necessary because recursive SSA resolution may have already |
| 2069 | /// patched terminator arguments with the provisional value before it was |
| 2070 | /// found to be trivial. |
| 2071 | unsafe fn rewriteCachedVarValue 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, v: Var, from: il::Val, to: il::Val) where 'arena: 'phase, 'phase: 'function { |
| 2072 | for i in 0..self.blockData.len { |
| 2073 | let blk = getBlockMut(self, BlockId(i)); |
| 2074 | if blk.vars[*v] == from { |
| 2075 | set blk.vars[*v] = to; |
| 2076 | } |
| 2077 | if blk.instrs.len > 0 { |
| 2078 | let ix = blk.instrs.len - 1; |
| 2079 | match &mut blk.instrs[ix] { |
| 2080 | case il::Instr::Jmp { args, .. } => |
| 2081 | rewriteValInSlice(*args, from, to), |
| 2082 | case il::Instr::Br { thenArgs, elseArgs, .. } => { |
| 2083 | rewriteValInSlice(*thenArgs, from, to); |
| 2084 | rewriteValInSlice(*elseArgs, from, to); |
| 2085 | } |
| 2086 | case il::Instr::Switch { defaultArgs, cases, .. } => { |
| 2087 | rewriteValInSlice(*defaultArgs, from, to); |
| 2088 | for j in 0..cases.len { |
| 2089 | rewriteValInSlice(cases[j].args, from, to); |
| 2090 | } |
| 2091 | } |
| 2092 | else => {} |
| 2093 | } |
| 2094 | } |
| 2095 | } |
| 2096 | } |
| 2097 | |
| 2098 | /// Replace all occurrences of `from` with `to` in an args slice. |
| 2099 | unsafe fn rewriteValInSlice(args: *unsafe mut [il::Val], from: il::Val, to: il::Val) { |
| 2100 | for i in 0..args.len { |
| 2101 | if args[i] == from { |
| 2102 | set args[i] = to; |
| 2103 | } |
| 2104 | } |
| 2105 | } |
| 2106 | |
| 2107 | //////////////////////////// |
| 2108 | // Basic Block Management // |
| 2109 | //////////////////////////// |
| 2110 | |
| 2111 | // Basic blocks are the fundamental unit of the CFG. Each block contains a |
| 2112 | // sequence of instructions ending in a terminator (jump, branch, return). |
| 2113 | // |
| 2114 | // The block management API supports forward references -- you can create a |
| 2115 | // block before switching to it and emitting instructions. |
| 2116 | // This is essential for control flow where we need to reference target blocks |
| 2117 | // before we've built them (e.g., the "else" block when building "then"). |
| 2118 | // |
| 2119 | // Sealing is a key concept for SSA construction: a block is sealed once all |
| 2120 | // its predecessor edges are known. Before sealing, we can't resolve variable |
| 2121 | // uses that require looking up values from predecessors. |
| 2122 | |
| 2123 | /// Create a new basic block with the given label base. |
| 2124 | /// |
| 2125 | /// The block is initially unsealed (predecessors may be added later) and empty. |
| 2126 | /// Returns a [`BlockId`] that can be used for jumps and branches. The block must |
| 2127 | /// be switched to via [`switchToBlock`] before instructions can be emitted. |
| 2128 | unsafe fn createBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, labelBase: *[u8]) -> BlockId throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2129 | let label = try nextLabel(self, labelBase); |
| 2130 | let id = BlockId(self.blockData.len); |
| 2131 | let varCount = self.localCount; |
| 2132 | let vars = try! alloc::allocRawSlice(self.arena, @sizeOf(?il::Val), @alignOf(?il::Val), varCount) as *unsafe mut [?il::Val]; |
| 2133 | |
| 2134 | for i in 0..varCount { |
| 2135 | set vars[i] = nil; |
| 2136 | } |
| 2137 | self.blockData.append(BlockData { |
| 2138 | label, |
| 2139 | params: &mut [], |
| 2140 | paramVars: &mut [], |
| 2141 | instrs: &mut [], |
| 2142 | locs: &mut [], |
| 2143 | preds: &mut [], |
| 2144 | vars, |
| 2145 | sealState: Sealed::No, |
| 2146 | loopDepth: self.loopDepth, |
| 2147 | }, alloc::arenaAllocator(self.arena)); |
| 2148 | |
| 2149 | return id; |
| 2150 | } |
| 2151 | |
| 2152 | /// Create a new block with a single parameter. |
| 2153 | unsafe fn createBlockWithParam 'arena 'phase 'function ( |
| 2154 | self: &mut FnLowerer 'arena 'phase 'function, |
| 2155 | labelBase: *[u8], |
| 2156 | param: il::Param |
| 2157 | ) -> BlockId throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2158 | let block = try createBlock(self, labelBase); |
| 2159 | let blk = getBlockMut(self, block); |
| 2160 | blk.params.append(param, alloc::arenaAllocator(self.arena)); |
| 2161 | |
| 2162 | return block; |
| 2163 | } |
| 2164 | |
| 2165 | /// Switch to building a different block. |
| 2166 | /// All subsequent `emit` calls will add instructions to this block. |
| 2167 | fn switchToBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId) where 'arena: 'phase, 'phase: 'function { |
| 2168 | set self.currentBlock = block; |
| 2169 | } |
| 2170 | |
| 2171 | /// Seal a block, indicating all predecessor edges are now known. |
| 2172 | /// |
| 2173 | /// Sealing enables SSA construction to resolve variable uses by looking up |
| 2174 | /// values from predecessors and inserting block parameters as needed. It |
| 2175 | /// does not prevent instructions from being added to the block. |
| 2176 | unsafe fn sealBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2177 | let blk = getBlockMut(self, block); |
| 2178 | let case Sealed::No = blk.sealState else { |
| 2179 | return; // Already sealed. |
| 2180 | }; |
| 2181 | // Keep the current parameter list. Resolution can add more parameters. |
| 2182 | let paramVars = blk.paramVars; |
| 2183 | set blk.sealState = Sealed::Yes; |
| 2184 | |
| 2185 | // Complete each parameter that was created before sealing. |
| 2186 | for varId, paramIdx in paramVars { |
| 2187 | try resolveBlockArgs(self, block, Var(varId), paramIdx); |
| 2188 | } |
| 2189 | } |
| 2190 | |
| 2191 | /// Seal a block and switch to it. |
| 2192 | unsafe fn switchToAndSeal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2193 | try sealBlock(self, block); |
| 2194 | switchToBlock(self, block); |
| 2195 | } |
| 2196 | |
| 2197 | /// Get block data by block id. |
| 2198 | unsafe fn getBlock 'arena 'phase 'function (self: &FnLowerer 'arena 'phase 'function, block: BlockId) -> *unsafe BlockData where 'arena: 'phase, 'phase: 'function { |
| 2199 | return &self.blockData[*block]; |
| 2200 | } |
| 2201 | |
| 2202 | /// Get mutable block data by block id. |
| 2203 | unsafe fn getBlockMut 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId) -> *unsafe mut BlockData where 'arena: 'phase, 'phase: 'function { |
| 2204 | return &mut self.blockData[*block]; |
| 2205 | } |
| 2206 | |
| 2207 | /// Get the current block being built. |
| 2208 | fn currentBlock 'arena 'phase 'function (self: &FnLowerer 'arena 'phase 'function) -> BlockId where 'arena: 'phase, 'phase: 'function { |
| 2209 | let block = self.currentBlock else { |
| 2210 | panic "currentBlock: no current block"; |
| 2211 | }; |
| 2212 | return block; |
| 2213 | } |
| 2214 | |
| 2215 | ////////////////////////// |
| 2216 | // Instruction Emission // |
| 2217 | ////////////////////////// |
| 2218 | |
| 2219 | /// Emit an instruction to the current block. |
| 2220 | unsafe fn emit 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, instr: il::Instr) where 'arena: 'phase, 'phase: 'function { |
| 2221 | let blk = self.currentBlock else panic; |
| 2222 | let mut block = getBlockMut(self, blk); |
| 2223 | |
| 2224 | // Track whether this function is a leaf. |
| 2225 | if il::isCall(instr) { |
| 2226 | set self.isLeaf = false; |
| 2227 | } |
| 2228 | // Record source location alongside instruction when enabled. |
| 2229 | if self.low.options.debug { |
| 2230 | block.locs.append(self.srcLoc, alloc::arenaAllocator(self.arena)); |
| 2231 | } |
| 2232 | block.instrs.append(instr, alloc::arenaAllocator(self.arena)); |
| 2233 | } |
| 2234 | |
| 2235 | /// Emit an unconditional jump to `target`. |
| 2236 | unsafe fn emitJmp 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2237 | emit(self, il::Instr::Jmp { target: *target, args: &mut [] }); |
| 2238 | addPredecessor(self, target, currentBlock(self)); |
| 2239 | } |
| 2240 | |
| 2241 | /// Emit an unconditional jump to `target` with a single argument. |
| 2242 | unsafe fn emitJmpWithArg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId, arg: il::Val) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2243 | let args = try allocVal(self, arg); |
| 2244 | emit(self, il::Instr::Jmp { target: *target, args }); |
| 2245 | addPredecessor(self, target, currentBlock(self)); |
| 2246 | } |
| 2247 | |
| 2248 | /// Emit an unconditional jump to `target` and switch to it. |
| 2249 | unsafe fn switchAndJumpTo 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2250 | try emitJmp(self, target); |
| 2251 | switchToBlock(self, target); |
| 2252 | } |
| 2253 | |
| 2254 | /// Emit a conditional branch based on `cond`. |
| 2255 | unsafe 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 { |
| 2256 | assert thenBlock <> elseBlock; |
| 2257 | emit(self, il::Instr::Br { |
| 2258 | op: il::CmpOp::Ne, |
| 2259 | typ: il::Type::W32, |
| 2260 | a: il::Val::Reg(cond), |
| 2261 | b: il::Val::Imm(0), |
| 2262 | thenTarget: *thenBlock, |
| 2263 | thenArgs: &mut [], |
| 2264 | elseTarget: *elseBlock, |
| 2265 | elseArgs: &mut [], |
| 2266 | }); |
| 2267 | addPredecessor(self, thenBlock, currentBlock(self)); |
| 2268 | addPredecessor(self, elseBlock, currentBlock(self)); |
| 2269 | } |
| 2270 | |
| 2271 | /// Emit a compare-and-branch instruction with the given comparison op. |
| 2272 | unsafe fn emitBrCmp 'arena 'phase 'function ( |
| 2273 | self: &mut FnLowerer 'arena 'phase 'function, |
| 2274 | op: il::CmpOp, |
| 2275 | typ: il::Type, |
| 2276 | a: il::Val, |
| 2277 | b: il::Val, |
| 2278 | thenBlock: BlockId, |
| 2279 | elseBlock: BlockId |
| 2280 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2281 | assert thenBlock <> elseBlock; |
| 2282 | emit(self, il::Instr::Br { |
| 2283 | op, typ, a, b, |
| 2284 | thenTarget: *thenBlock, thenArgs: &mut [], |
| 2285 | elseTarget: *elseBlock, elseArgs: &mut [], |
| 2286 | }); |
| 2287 | addPredecessor(self, thenBlock, currentBlock(self)); |
| 2288 | addPredecessor(self, elseBlock, currentBlock(self)); |
| 2289 | } |
| 2290 | |
| 2291 | /// Emit a guard that traps with `ebreak` when a comparison is false. |
| 2292 | unsafe fn emitTrapUnlessCmp 'arena 'phase 'function ( |
| 2293 | self: &mut FnLowerer 'arena 'phase 'function, |
| 2294 | op: il::CmpOp, |
| 2295 | typ: il::Type, |
| 2296 | a: il::Val, |
| 2297 | b: il::Val |
| 2298 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2299 | let passBlock = try createBlock(self, "guard#pass"); |
| 2300 | let trapBlock = try createBlock(self, "guard#trap"); |
| 2301 | |
| 2302 | try emitBrCmp(self, op, typ, a, b, passBlock, trapBlock); |
| 2303 | try switchToAndSeal(self, trapBlock); |
| 2304 | |
| 2305 | emit(self, il::Instr::Ebreak); |
| 2306 | emit(self, il::Instr::Unreachable); |
| 2307 | |
| 2308 | try switchToAndSeal(self, passBlock); |
| 2309 | } |
| 2310 | |
| 2311 | /// Return whether two IL values are known `u32` immediates satisfying `a <= b`. |
| 2312 | fn isLtEq(a: il::Val, b: il::Val) -> bool { |
| 2313 | if a == b { |
| 2314 | return true; |
| 2315 | } |
| 2316 | if a == il::Val::Imm(0) { |
| 2317 | return true; |
| 2318 | } |
| 2319 | let case il::Val::Imm(aa) = a else { |
| 2320 | return false; |
| 2321 | }; |
| 2322 | let case il::Val::Imm(bb) = b else { |
| 2323 | return false; |
| 2324 | }; |
| 2325 | if aa < 0 or bb < 0 { |
| 2326 | return false; |
| 2327 | } |
| 2328 | return aa <= bb; |
| 2329 | } |
| 2330 | |
| 2331 | /// Emit an `ebreak` when `a < b` holds. |
| 2332 | unsafe fn emitTrapIfLt 'arena 'phase 'function ( |
| 2333 | self: &mut FnLowerer 'arena 'phase 'function, |
| 2334 | typ: il::Type, |
| 2335 | a: il::Val, |
| 2336 | b: il::Val |
| 2337 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2338 | let trapBlock = try createBlock(self, "guard#trap"); |
| 2339 | let passBlock = try createBlock(self, "guard#pass"); |
| 2340 | |
| 2341 | try emitBrCmp(self, il::CmpOp::Ult, typ, a, b, trapBlock, passBlock); |
| 2342 | try switchToAndSeal(self, trapBlock); |
| 2343 | |
| 2344 | emit(self, il::Instr::Ebreak); |
| 2345 | emit(self, il::Instr::Unreachable); |
| 2346 | |
| 2347 | try switchToAndSeal(self, passBlock); |
| 2348 | } |
| 2349 | |
| 2350 | /// Emit a conditional branch. Uses fused compare-and-branch for simple scalar |
| 2351 | /// comparisons, falls back to separate comparison plus branch otherwise. |
| 2352 | unsafe fn emitCondBranch 'arena 'phase 'function ( |
| 2353 | self: &mut FnLowerer 'arena 'phase 'function, |
| 2354 | cond: *ast::Node, |
| 2355 | thenBlock: BlockId, |
| 2356 | elseBlock: BlockId |
| 2357 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2358 | // Try fused compare-and-branch for simple scalar comparisons. |
| 2359 | if let case ast::NodeValue::BinOp(binop) = cond.value { |
| 2360 | let leftTy = try typeOf(self, binop.left); |
| 2361 | let rightTy = try typeOf(self, binop.right); |
| 2362 | if not isAggregateType(leftTy) and not isAggregateType(rightTy) { |
| 2363 | let operandTy = scalarComparisonType(leftTy, rightTy); |
| 2364 | let unsigned = isUnsignedType(operandTy); |
| 2365 | if let op = cmpOpFrom(binop.op, unsigned) { |
| 2366 | let a = try lowerExpr(self, binop.left); |
| 2367 | let b = try lowerExpr(self, binop.right); |
| 2368 | let typ = ilType(self.low, operandTy); |
| 2369 | |
| 2370 | // Swap operands if needed. |
| 2371 | match binop.op { |
| 2372 | case ast::BinaryOp::Gt => // `a > b` = `b < a` |
| 2373 | try emitBrCmp(self, op, typ, b, a, thenBlock, elseBlock), |
| 2374 | case ast::BinaryOp::Lte => // `a <= b` = `!(b < a)` |
| 2375 | try emitBrCmp(self, op, typ, b, a, elseBlock, thenBlock), |
| 2376 | case ast::BinaryOp::Gte => // `a >= b` = `!(a < b)` |
| 2377 | try emitBrCmp(self, op, typ, a, b, elseBlock, thenBlock), |
| 2378 | else => |
| 2379 | try emitBrCmp(self, op, typ, a, b, thenBlock, elseBlock), |
| 2380 | } |
| 2381 | return; |
| 2382 | } |
| 2383 | } |
| 2384 | } |
| 2385 | // Fallback: evaluate condition and emit boolean branch. |
| 2386 | let condVal = try lowerExpr(self, cond); |
| 2387 | let condReg = emitValToReg(self, condVal); |
| 2388 | |
| 2389 | try emitBr(self, condReg, thenBlock, elseBlock); |
| 2390 | } |
| 2391 | |
| 2392 | /// Emit a 32-bit store instruction at the given offset. |
| 2393 | unsafe fn emitStoreW32At 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, src: il::Val, dst: il::Reg, offset: i32) where 'arena: 'phase, 'phase: 'function { |
| 2394 | emit(self, il::Instr::Store { typ: il::Type::W32, src, dst, offset }); |
| 2395 | } |
| 2396 | |
| 2397 | /// Emit a 32-bit load instruction at the given offset. |
| 2398 | unsafe fn emitLoadW32At 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, dst: il::Reg, src: il::Reg, offset: i32) where 'arena: 'phase, 'phase: 'function { |
| 2399 | emit(self, il::Instr::Load { typ: il::Type::W32, dst, src, offset }); |
| 2400 | } |
| 2401 | |
| 2402 | /// Emit an 8-bit store instruction at the given offset. |
| 2403 | unsafe fn emitStoreW8At 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, src: il::Val, dst: il::Reg, offset: i32) where 'arena: 'phase, 'phase: 'function { |
| 2404 | emit(self, il::Instr::Store { typ: il::Type::W8, src, dst, offset }); |
| 2405 | } |
| 2406 | |
| 2407 | /// Emit an 8-bit load instruction at the given offset. |
| 2408 | unsafe fn emitLoadW8At 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, dst: il::Reg, src: il::Reg, offset: i32) where 'arena: 'phase, 'phase: 'function { |
| 2409 | emit(self, il::Instr::Load { typ: il::Type::W8, dst, src, offset }); |
| 2410 | } |
| 2411 | |
| 2412 | /// Emit a 64-bit store instruction at the given offset. |
| 2413 | unsafe fn emitStoreW64At 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, src: il::Val, dst: il::Reg, offset: i32) where 'arena: 'phase, 'phase: 'function { |
| 2414 | emit(self, il::Instr::Store { typ: il::Type::W64, src, dst, offset }); |
| 2415 | } |
| 2416 | |
| 2417 | /// Emit a 64-bit load instruction at the given offset. |
| 2418 | unsafe fn emitLoadW64At 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, dst: il::Reg, src: il::Reg, offset: i32) where 'arena: 'phase, 'phase: 'function { |
| 2419 | emit(self, il::Instr::Load { typ: il::Type::W64, dst, src, offset }); |
| 2420 | } |
| 2421 | |
| 2422 | /// Load a tag from memory at `src` plus `offset` with the given IL type. |
| 2423 | unsafe 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 { |
| 2424 | let dst = nextReg(self); |
| 2425 | emit(self, il::Instr::Load { typ: tagType, dst, src, offset }); |
| 2426 | return il::Val::Reg(dst); |
| 2427 | } |
| 2428 | |
| 2429 | /// Load the data pointer from a slice value. |
| 2430 | unsafe fn loadSlicePtr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, sliceReg: il::Reg) -> il::Reg where 'arena: 'phase, 'phase: 'function { |
| 2431 | let ptrReg = nextReg(self); |
| 2432 | emitLoadW64At(self, ptrReg, sliceReg, SLICE_PTR_OFFSET); |
| 2433 | return ptrReg; |
| 2434 | } |
| 2435 | |
| 2436 | /// Load the length from a slice value. |
| 2437 | unsafe fn loadSliceLen 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, sliceReg: il::Reg) -> il::Val where 'arena: 'phase, 'phase: 'function { |
| 2438 | let lenReg = nextReg(self); |
| 2439 | emitLoadW32At(self, lenReg, sliceReg, SLICE_LEN_OFFSET); |
| 2440 | return il::Val::Reg(lenReg); |
| 2441 | } |
| 2442 | |
| 2443 | /// Load the capacity from a slice value. |
| 2444 | unsafe fn loadSliceCap 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, sliceReg: il::Reg) -> il::Val where 'arena: 'phase, 'phase: 'function { |
| 2445 | let capReg = nextReg(self); |
| 2446 | emitLoadW32At(self, capReg, sliceReg, SLICE_CAP_OFFSET); |
| 2447 | return il::Val::Reg(capReg); |
| 2448 | } |
| 2449 | |
| 2450 | /// Emit a load instruction for a scalar value at `src` plus `offset`. |
| 2451 | /// For reading values that may be aggregates, use `emitRead` instead. |
| 2452 | unsafe 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 { |
| 2453 | let dst = nextReg(self); |
| 2454 | let ilTyp = ilType(self.low, typ); |
| 2455 | |
| 2456 | if isSignedType(typ) { |
| 2457 | emit(self, il::Instr::Sload { typ: ilTyp, dst, src, offset }); |
| 2458 | } else { |
| 2459 | emit(self, il::Instr::Load { typ: ilTyp, dst, src, offset }); |
| 2460 | } |
| 2461 | return il::Val::Reg(dst); |
| 2462 | } |
| 2463 | |
| 2464 | /// Read a value from memory at `src` plus `offset`. Aggregates are represented |
| 2465 | /// as pointers, so we return the address directly. Scalars are loaded via [`emitLoad`]. |
| 2466 | unsafe 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 { |
| 2467 | if isAggregateType(typ) { |
| 2468 | let ptr = emitPtrOffset(self, src, offset); |
| 2469 | return il::Val::Reg(ptr); |
| 2470 | } |
| 2471 | return emitLoad(self, src, offset, typ); |
| 2472 | } |
| 2473 | |
| 2474 | /// Emit a copy instruction that loads a data symbol's address into a register. |
| 2475 | unsafe fn emitDataAddr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, sym: *unsafe resolver::Symbol) -> il::Reg where 'arena: 'phase, 'phase: 'function { |
| 2476 | let dst = nextReg(self); |
| 2477 | let modId = resolver::moduleIdForSymbol(self.low.resolver, sym); |
| 2478 | let qualName = lookupSymbolName(self.low, sym) else qualifyName(self.low, modId, sym.name); |
| 2479 | |
| 2480 | emit(self, il::Instr::Copy { dst, val: il::Val::DataSym(qualName) }); |
| 2481 | |
| 2482 | return dst; |
| 2483 | } |
| 2484 | |
| 2485 | /// Emit a copy instruction that loads a function's address into a register. |
| 2486 | unsafe fn emitFnAddr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, sym: *unsafe resolver::Symbol) -> il::Reg where 'arena: 'phase, 'phase: 'function { |
| 2487 | let dst = nextReg(self); |
| 2488 | let modId = resolver::moduleIdForSymbol(self.low.resolver, sym); |
| 2489 | let qualName = qualifyName(self.low, modId, sym.name); |
| 2490 | |
| 2491 | emit(self, il::Instr::Copy { dst, val: il::Val::FnAddr(qualName) }); |
| 2492 | |
| 2493 | return dst; |
| 2494 | } |
| 2495 | |
| 2496 | /// Emit pattern tests for a single pattern. |
| 2497 | unsafe fn emitPatternMatch 'arena 'phase 'function ( |
| 2498 | self: &mut FnLowerer 'arena 'phase 'function, |
| 2499 | subject: &MatchSubject, |
| 2500 | pattern: *ast::Node, |
| 2501 | matchBlock: BlockId, |
| 2502 | fallthrough: BlockId |
| 2503 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2504 | // Wildcards always match; array patterns are tested element-by-element |
| 2505 | // during binding, so they also unconditionally enter the match block. |
| 2506 | if isWildcardPattern(pattern) { |
| 2507 | try emitJmp(self, matchBlock); |
| 2508 | return; |
| 2509 | } |
| 2510 | if let case ast::NodeValue::ArrayLit(_) = pattern.value { |
| 2511 | try emitJmp(self, matchBlock); |
| 2512 | return; |
| 2513 | } |
| 2514 | if let case resolver::Type::Nominal(resolver::NominalType::Record(_)) = subject.type { |
| 2515 | match pattern.value { |
| 2516 | case ast::NodeValue::RecordLit(_), ast::NodeValue::Call(_) => { |
| 2517 | try emitJmp(self, matchBlock); |
| 2518 | return; |
| 2519 | } |
| 2520 | else => { |
| 2521 | }, |
| 2522 | } |
| 2523 | } |
| 2524 | let isNil = pattern.value == ast::NodeValue::Nil; |
| 2525 | |
| 2526 | match subject.kind { |
| 2527 | case MatchSubjectKind::OptionalPtr if isNil => { |
| 2528 | // Null pointer optimization: branch on the data pointer being null. |
| 2529 | let nilReg = try optionalNilReg(self, subject.val, subject.type); |
| 2530 | try emitBrCmp(self, il::CmpOp::Eq, il::Type::W64, il::Val::Reg(nilReg), il::Val::Imm(0), matchBlock, fallthrough); |
| 2531 | } |
| 2532 | case MatchSubjectKind::OptionalAggregate => { |
| 2533 | let base = emitValToReg(self, subject.val); |
| 2534 | |
| 2535 | if isNil { // Optional aggregate: `nil` means tag is zero. |
| 2536 | let tagReg = tvalTagReg(self, base); |
| 2537 | try emitBr(self, tagReg, fallthrough, matchBlock); |
| 2538 | } else { |
| 2539 | // `lowerExpr` applies the resolver's optional-lift coercion, |
| 2540 | // materializing the tagged representation for value patterns. |
| 2541 | let pattVal = try lowerExpr(self, pattern); |
| 2542 | let pattReg = emitValToReg(self, pattVal); |
| 2543 | let eq = try lowerOptionalEq(self, subject.bindType, base, pattReg, 0); |
| 2544 | let eqReg = emitValToReg(self, eq); |
| 2545 | |
| 2546 | try emitBr(self, eqReg, matchBlock, fallthrough); |
| 2547 | } |
| 2548 | } |
| 2549 | case MatchSubjectKind::Union(unionInfo) => { |
| 2550 | assert not isNil; |
| 2551 | |
| 2552 | let case resolver::NodeExtra::UnionVariant { tag: variantTag, .. } = |
| 2553 | resolver::nodeData(self.low.resolver, pattern).extra |
| 2554 | else { |
| 2555 | throw LowerError::ExpectedVariant; |
| 2556 | }; |
| 2557 | // Void unions are passed by value (the tag itself). |
| 2558 | // Non-void unions are passed by reference (need to load tag). |
| 2559 | // When matching by reference, always load from the pointer. |
| 2560 | if unionInfo.isAllVoid { |
| 2561 | let mut tagVal = subject.val; |
| 2562 | match subject.by { |
| 2563 | case resolver::MatchBy::Ref, resolver::MatchBy::MutRef => { |
| 2564 | let base = emitValToReg(self, subject.val); |
| 2565 | set tagVal = loadTag(self, base, 0, il::Type::W8); |
| 2566 | } |
| 2567 | case resolver::MatchBy::Value => {} |
| 2568 | } |
| 2569 | try emitBrCmp(self, il::CmpOp::Eq, il::Type::W8, tagVal, il::Val::Imm(variantTag as i64), matchBlock, fallthrough); |
| 2570 | } else { |
| 2571 | let base = emitValToReg(self, subject.val); |
| 2572 | let tagReg = tvalTagReg(self, base); |
| 2573 | |
| 2574 | try emitBrCmp(self, il::CmpOp::Eq, il::Type::W8, il::Val::Reg(tagReg), il::Val::Imm(variantTag as i64), matchBlock, fallthrough); |
| 2575 | } |
| 2576 | } |
| 2577 | else => { // Value comparison. |
| 2578 | assert not isNil; |
| 2579 | let pattVal = try lowerExpr(self, pattern); |
| 2580 | if isAggregateType(subject.type) { |
| 2581 | // Aggregate types need structural comparison rather than |
| 2582 | // scalar compare. |
| 2583 | let subjectReg = emitValToReg(self, subject.val); |
| 2584 | let pattReg = emitValToReg(self, pattVal); |
| 2585 | let eq = try lowerAggregateEq(self, subject.type, subjectReg, pattReg, 0); |
| 2586 | let eqReg = emitValToReg(self, eq); |
| 2587 | |
| 2588 | try emitBr(self, eqReg, matchBlock, fallthrough); |
| 2589 | } else { |
| 2590 | try emitBrCmp(self, il::CmpOp::Eq, subject.ilType, subject.val, pattVal, matchBlock, fallthrough); |
| 2591 | } |
| 2592 | } |
| 2593 | } |
| 2594 | } |
| 2595 | |
| 2596 | /// Emit branches for multiple patterns. The first pattern that matches |
| 2597 | /// causes a jump to the match block. If no patterns match, we jump to the |
| 2598 | /// fallthrough block. |
| 2599 | unsafe fn emitPatternMatches 'arena 'phase 'function ( |
| 2600 | self: &mut FnLowerer 'arena 'phase 'function, |
| 2601 | subject: &MatchSubject, |
| 2602 | patterns: &[*ast::Node], |
| 2603 | matchBlock: BlockId, |
| 2604 | fallthrough: BlockId |
| 2605 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2606 | assert patterns.len > 0; |
| 2607 | |
| 2608 | for i in 0..(patterns.len - 1) { |
| 2609 | let pattern = patterns[i]; |
| 2610 | let nextArm = try createBlock(self, "arm"); |
| 2611 | try emitPatternMatch(self, subject, pattern, matchBlock, nextArm); |
| 2612 | |
| 2613 | // Seal the intermediate arm block: all predecessor edges are known |
| 2614 | // This ensures SSA construction can resolve variable uses through |
| 2615 | // single-predecessor optimization instead of creating unresolved block |
| 2616 | // parameters. |
| 2617 | try switchToAndSeal(self, nextArm); |
| 2618 | } |
| 2619 | // Handle last pattern: go to fallthrough block on failure. |
| 2620 | let last = patterns[patterns.len - 1]; |
| 2621 | try emitPatternMatch(self, subject, last, matchBlock, fallthrough); |
| 2622 | } |
| 2623 | |
| 2624 | /// Emit a match binding pattern. |
| 2625 | /// Binding patterns always match for regular values, but for optionals they |
| 2626 | /// check for the presence of a value. Jumps to `valuePresent` on success, |
| 2627 | /// `valueAbsent` on failure. |
| 2628 | unsafe fn emitBindingTest 'arena 'phase 'function ( |
| 2629 | self: &mut FnLowerer 'arena 'phase 'function, |
| 2630 | subject: &MatchSubject, |
| 2631 | valuePresent: BlockId, |
| 2632 | valueAbsent: BlockId |
| 2633 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2634 | match subject.kind { |
| 2635 | case MatchSubjectKind::OptionalPtr, MatchSubjectKind::OptionalAggregate => { |
| 2636 | let nilReg = try optionalNilReg(self, subject.val, subject.type); |
| 2637 | try emitBr(self, nilReg, valuePresent, valueAbsent); |
| 2638 | } |
| 2639 | else => { |
| 2640 | // Regular values always match binding patterns unconditionally. |
| 2641 | try emitJmp(self, valuePresent); |
| 2642 | } |
| 2643 | } |
| 2644 | } |
| 2645 | |
| 2646 | /// Emit a jump to target if the current block hasn't terminated, then seal the target block. |
| 2647 | unsafe fn emitJmpAndSeal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2648 | if not blockHasTerminator(self) { |
| 2649 | try emitJmp(self, target); |
| 2650 | } |
| 2651 | try sealBlock(self, target); |
| 2652 | } |
| 2653 | |
| 2654 | /// Check if the current block already has a terminator instruction. |
| 2655 | unsafe fn blockHasTerminator 'arena 'phase 'function (self: &FnLowerer 'arena 'phase 'function) -> bool where 'arena: 'phase, 'phase: 'function { |
| 2656 | let blk = getBlock(self, currentBlock(self)); |
| 2657 | if blk.instrs.len == 0 { |
| 2658 | return false; |
| 2659 | } |
| 2660 | match blk.instrs[blk.instrs.len - 1] { |
| 2661 | case il::Instr::Ret { .. }, |
| 2662 | il::Instr::Jmp { .. }, |
| 2663 | il::Instr::Br { .. }, |
| 2664 | il::Instr::Switch { .. }, |
| 2665 | il::Instr::Unreachable => |
| 2666 | return true, |
| 2667 | else => |
| 2668 | return false, |
| 2669 | } |
| 2670 | } |
| 2671 | |
| 2672 | /// Emit a jump to merge block if the current block hasn't terminated. |
| 2673 | /// |
| 2674 | /// This is used after lowering branches of an if-else, for example. If the |
| 2675 | /// branch diverges, the block already has a terminator and no jump is needed. |
| 2676 | /// |
| 2677 | /// This handles cases like: |
| 2678 | /// |
| 2679 | /// if cond { |
| 2680 | /// return 1; // @then diverges, no jump to merge. |
| 2681 | /// } else { |
| 2682 | /// return 0; // @else diverges, no jump to merge. |
| 2683 | /// } |
| 2684 | /// |
| 2685 | /// In the above example, the merge block stays `nil`, and no code is generated |
| 2686 | /// after the `if`. The merge block is created on first use. |
| 2687 | unsafe fn emitMergeIfUnterminated 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, mergeBlock: &mut ?BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2688 | if not blockHasTerminator(self) { |
| 2689 | if *mergeBlock == nil { |
| 2690 | set *mergeBlock = try createBlock(self, "merge"); |
| 2691 | } |
| 2692 | let target = *mergeBlock else { throw LowerError::MissingTarget; }; |
| 2693 | try emitJmp(self, target); |
| 2694 | } |
| 2695 | } |
| 2696 | |
| 2697 | ////////////////////////////////// |
| 2698 | // Control Flow Edge Management // |
| 2699 | ////////////////////////////////// |
| 2700 | |
| 2701 | /// Add a predecessor edge from `pred` to `target`. |
| 2702 | /// Must be called before the target block is sealed. Duplicates are ignored. |
| 2703 | unsafe fn addPredecessor 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId, pred: BlockId) where 'arena: 'phase, 'phase: 'function { |
| 2704 | let blk = getBlockMut(self, target); |
| 2705 | assert blk.sealState <> Sealed::Yes, "addPredecessor: adding predecessor to sealed block"; |
| 2706 | let preds = &mut blk.preds; |
| 2707 | for i in 0..preds.len { |
| 2708 | if preds[i] == *pred { // Avoid duplicate predecessor entries. |
| 2709 | return; |
| 2710 | } |
| 2711 | } |
| 2712 | preds.append(*pred, alloc::arenaAllocator(self.arena)); |
| 2713 | } |
| 2714 | |
| 2715 | /// Finalize all blocks and return the block array. |
| 2716 | unsafe fn finalizeBlocks 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) -> *unsafe [il::Block] throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2717 | let blockCount = self.blockData.len; |
| 2718 | let blocks = try! alloc::allocRawSlice( |
| 2719 | self.arena, @sizeOf(il::Block), @alignOf(il::Block), blockCount |
| 2720 | ) as *unsafe mut [il::Block]; |
| 2721 | |
| 2722 | for i in 0..self.blockData.len { |
| 2723 | let data = &self.blockData[i]; |
| 2724 | |
| 2725 | set blocks[i] = il::Block { |
| 2726 | label: data.label, |
| 2727 | params: &data.params[..], |
| 2728 | instrs: data.instrs, |
| 2729 | locs: &data.locs[..], |
| 2730 | preds: &data.preds[..], |
| 2731 | loopDepth: data.loopDepth, |
| 2732 | }; |
| 2733 | } |
| 2734 | return &blocks[..self.blockData.len]; |
| 2735 | } |
| 2736 | |
| 2737 | ///////////////////// |
| 2738 | // Loop Management // |
| 2739 | ///////////////////// |
| 2740 | |
| 2741 | /// Enter a loop context for break/continue handling. |
| 2742 | /// `continueBlock` is `nil` when the continue target is created lazily. |
| 2743 | fn enterLoop 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, breakBlock: BlockId, continueBlock: ?BlockId) where 'arena: 'phase, 'phase: 'function { |
| 2744 | assert self.loopDepth < self.loopStack.len, "enterLoop: loop depth overflow"; |
| 2745 | let slot = &mut self.loopStack[self.loopDepth]; |
| 2746 | |
| 2747 | set slot.breakTarget = breakBlock; |
| 2748 | set slot.continueTarget = continueBlock; |
| 2749 | set self.loopDepth += 1; |
| 2750 | } |
| 2751 | |
| 2752 | /// Exit the current loop context. |
| 2753 | fn exitLoop 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) where 'arena: 'phase, 'phase: 'function { |
| 2754 | assert self.loopDepth <> 0, "exitLoop: loopDepth is zero"; |
| 2755 | set self.loopDepth -= 1; |
| 2756 | } |
| 2757 | |
| 2758 | /// Copy the current loop targets. |
| 2759 | fn currentLoop 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) -> ?LoopCtx where 'arena: 'phase, 'phase: 'function { |
| 2760 | if self.loopDepth == 0 { |
| 2761 | return nil; |
| 2762 | } |
| 2763 | return self.loopStack[self.loopDepth - 1]; |
| 2764 | } |
| 2765 | |
| 2766 | /// Get or lazily create the continue target block for the current loop. |
| 2767 | unsafe fn getOrCreateContinueBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) -> BlockId throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2768 | let ctx = currentLoop(self) else { |
| 2769 | throw LowerError::OutsideOfLoop; |
| 2770 | }; |
| 2771 | if let block = ctx.continueTarget { |
| 2772 | return block; |
| 2773 | } |
| 2774 | let block = try createBlock(self, "step"); |
| 2775 | set self.loopStack[self.loopDepth - 1].continueTarget = block; |
| 2776 | return block; |
| 2777 | } |
| 2778 | |
| 2779 | /// Allocate a slice of values in the lowering arena. |
| 2780 | unsafe fn allocVals 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, len: u32) -> *unsafe mut [il::Val] throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2781 | return try! alloc::allocRawSlice(self.arena, @sizeOf(il::Val), @alignOf(il::Val), len) as *unsafe mut [il::Val]; |
| 2782 | } |
| 2783 | |
| 2784 | /// Allocate a single-value slice in the lowering arena. |
| 2785 | 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 { |
| 2786 | let args = try allocVals(self, 1); |
| 2787 | set args[0] = val; |
| 2788 | return args; |
| 2789 | } |
| 2790 | |
| 2791 | //////////////////////// |
| 2792 | // SSA Var Management // |
| 2793 | //////////////////////// |
| 2794 | |
| 2795 | // This section implements SSA construction following "Simple and Efficient |
| 2796 | // Construction of Static Single Assignment Form" (Braun et al., 2013). The IL |
| 2797 | // uses block parameters (equivalent to phi nodes) that receive values via |
| 2798 | // terminator arguments. Block args are resolved eagerly at seal time via |
| 2799 | // [`resolveBlockArgs`], matching Braun's on-the-fly approach. |
| 2800 | // |
| 2801 | // In SSA form, each variable definition creates a unique value. When control |
| 2802 | // flow diverges and merges (like after an if-else), a variable might have |
| 2803 | // different definitions from different paths. |
| 2804 | // |
| 2805 | // # What "Value" means |
| 2806 | // |
| 2807 | // An [`il::Val`] is a compile-time representation of *where* a runtime value |
| 2808 | // lives, not the runtime value itself. Values can live in registers, as symbol |
| 2809 | // references, or as immediate values, ie. static constants. |
| 2810 | // |
| 2811 | // When we "find the value" of a variable, we're answering: "Which SSA register |
| 2812 | // (or constant) represents this variable at this program point?" |
| 2813 | // |
| 2814 | // Each source-level variable gets a `Var` handle on declaration. When a |
| 2815 | // variable is defined via [`defVar`], its SSA value is recorded in the current |
| 2816 | // block's variable mapping. When a variable is used with [`useVar`] or |
| 2817 | // [`useVarInBlock`], we either return the local definition or recursively look |
| 2818 | // up the value from predecessor blocks. When multiple predecessors define |
| 2819 | // different values for a given variable, we insert a block parameter |
| 2820 | // (equivalent to a phi node). |
| 2821 | // |
| 2822 | // The algorithm used by [`useVarInBlock`] handles three cases: |
| 2823 | // |
| 2824 | // 1. **Local definition exists**: If the variable was assigned in this block, |
| 2825 | // return that value immediately (fast path). |
| 2826 | // |
| 2827 | // 2. **Sealed block with single predecessor**: If all incoming edges are known |
| 2828 | // and there's exactly one predecessor, recurse to that predecessor. No merge |
| 2829 | // is needed since there's only one path. The result is cached. |
| 2830 | // |
| 2831 | // 3. **Unsealed block or multiple predecessors**: Create a block parameter to |
| 2832 | // receive the merged value. If sealed, look up each predecessor's value and |
| 2833 | // patch its terminator via [`resolveBlockArgs`]. If unsealed, `paramVars` |
| 2834 | // records the variable in parameter order. [`sealBlock`] resolves these |
| 2835 | // parameters after all incoming edges are known. |
| 2836 | // |
| 2837 | // Consider this code: |
| 2838 | // |
| 2839 | // let mut x = 1; |
| 2840 | // if cond { |
| 2841 | // x = 2; |
| 2842 | // } else { |
| 2843 | // x = f(); // Result in register %r. |
| 2844 | // } |
| 2845 | // print(x); // Which value? |
| 2846 | // |
| 2847 | // The Control Flow Graph (CFG) looks like: |
| 2848 | // |
| 2849 | // [entry] |
| 2850 | // _|_ |
| 2851 | // / \ |
| 2852 | // [then] [else] |
| 2853 | // x = 2 x = %r |
| 2854 | // \ / |
| 2855 | // [merge] |
| 2856 | // use(x) |
| 2857 | // |
| 2858 | // At the `print(x)` point, the compiler doesn't know which branch ran (that's |
| 2859 | // a runtime decision), but it needs to emit code that works for either case. |
| 2860 | // This is where [`useVarInBlock`] is called. |
| 2861 | // |
| 2862 | // The generated IL looks like this: |
| 2863 | // |
| 2864 | // @then |
| 2865 | // jmp @merge(2); // pass immediate `2` |
| 2866 | // @else |
| 2867 | // jmp @merge(%r); // pass register `%r` |
| 2868 | // @merge(w32 %m) // `%m` receives whichever value arrives at runtime |
| 2869 | // call w32 $print(%m); // `print` is called with runtime value in `%m` |
| 2870 | // |
| 2871 | // A block is "sealed" when all predecessor edges are known. This is crucial |
| 2872 | // because until sealed, we can't know how many paths merge into the block, |
| 2873 | // and thus can't create the right number of block parameter arguments. |
| 2874 | // |
| 2875 | // If a block isn't sealed but we need a variable's value, we still create |
| 2876 | // a block parameter, but defer filling in the terminator arguments. When the |
| 2877 | // block is later sealed via [`sealBlock`], all incomplete block params are resolved. |
| 2878 | |
| 2879 | /// Declare a new source-level variable and define its initial value. |
| 2880 | /// If called before any block exists (e.g., for parameters), the definition is skipped. |
| 2881 | unsafe fn newVar 'arena 'phase 'function ( |
| 2882 | self: &mut FnLowerer 'arena 'phase 'function, |
| 2883 | name: ?*[u8], |
| 2884 | type: il::Type, |
| 2885 | mutable: bool, |
| 2886 | val: il::Val |
| 2887 | ) -> Var where 'arena: 'phase, 'phase: 'function { |
| 2888 | let id = self.vars.len; |
| 2889 | self.vars.append( |
| 2890 | VarData { name, type, mutable, addressTaken: false }, |
| 2891 | alloc::arenaAllocator(self.arena) |
| 2892 | ); |
| 2893 | |
| 2894 | let v = Var(id); |
| 2895 | if self.currentBlock <> nil { |
| 2896 | defVar(self, v, val); |
| 2897 | } |
| 2898 | return v; |
| 2899 | } |
| 2900 | |
| 2901 | /// Define (write) a variable. Record the SSA value of a variable in the |
| 2902 | /// current block. Called when a variable is assigned or initialized (`let` |
| 2903 | /// bindings, assignments, loop updates). When [`useVar`] is later called, |
| 2904 | /// it will retrieve this value. |
| 2905 | unsafe fn defVar 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, v: Var, val: il::Val) where 'arena: 'phase, 'phase: 'function { |
| 2906 | assert *v < self.vars.len; |
| 2907 | set getBlockMut(self, currentBlock(self)).vars[*v] = val; |
| 2908 | } |
| 2909 | |
| 2910 | /// Use (read) the current value of a variable in the current block. |
| 2911 | /// May insert block parameters if the value must come from predecessors. |
| 2912 | unsafe fn useVar 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, v: Var) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2913 | return try useVarInBlock(self, currentBlock(self), v); |
| 2914 | } |
| 2915 | |
| 2916 | /// Resolve which SSA definition of a variable reaches a use point in a given block. |
| 2917 | /// |
| 2918 | /// Given a variable and a block where it's used, this function finds the |
| 2919 | /// correct [`il::Val`] that holds the variable's value at that program point. |
| 2920 | /// When control flow merges from multiple predecessors with different |
| 2921 | /// definitions, it creates a block parameter to unify them. |
| 2922 | unsafe fn useVarInBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId, v: Var) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2923 | assert *v < self.vars.len; |
| 2924 | |
| 2925 | let blk = getBlockMut(self, block); |
| 2926 | if let val = blk.vars[*v] { |
| 2927 | return val; |
| 2928 | } |
| 2929 | // Entry block cannot have block parameters. If variable isn't defined |
| 2930 | // in entry, we return undefined. |
| 2931 | if block == self.entryBlock { |
| 2932 | return il::Val::Undef; |
| 2933 | } |
| 2934 | if blk.sealState == Sealed::Yes { |
| 2935 | if blk.preds.len == 0 { |
| 2936 | // Variable used in sealed block with no predecessors. |
| 2937 | throw LowerError::InvalidUse; |
| 2938 | } |
| 2939 | // Single predecessor means no merge needed, variable is implicitly |
| 2940 | // available without a block parameter. |
| 2941 | if blk.preds.len == 1 { |
| 2942 | let pred = BlockId(blk.preds[0]); |
| 2943 | if *pred <> *block { |
| 2944 | let val = try useVarInBlock(self, pred, v); |
| 2945 | set blk.vars[*v] = val; // Cache. |
| 2946 | return val; |
| 2947 | } |
| 2948 | } |
| 2949 | } |
| 2950 | // Multiple predecessors or unsealed block: need a block parameter to merge |
| 2951 | // the control flow paths. |
| 2952 | return try createBlockParam(self, block, v); |
| 2953 | } |
| 2954 | |
| 2955 | /// Look up a variable by name in the current scope. |
| 2956 | /// Searches from most recently declared to first, enabling shadowing. |
| 2957 | unsafe fn lookupVarByName 'arena 'phase 'function (self: &FnLowerer 'arena 'phase 'function, name: *[u8]) -> ?Var where 'arena: 'phase, 'phase: 'function { |
| 2958 | let mut id = self.vars.len; |
| 2959 | while id > 0 { |
| 2960 | set id -= 1; |
| 2961 | if let varName = self.vars[id].name { |
| 2962 | // Names are interned strings, so pointer comparison suffices. |
| 2963 | if varName == name { |
| 2964 | return Var(id); |
| 2965 | } |
| 2966 | } |
| 2967 | } |
| 2968 | return nil; |
| 2969 | } |
| 2970 | |
| 2971 | /// Look up a local variable bound to an identifier node. |
| 2972 | unsafe fn lookupLocalVar 'arena 'phase 'function (self: &FnLowerer 'arena 'phase 'function, node: *ast::Node) -> ?Var where 'arena: 'phase, 'phase: 'function { |
| 2973 | let case ast::NodeValue::Ident(name) = node.value else { |
| 2974 | return nil; |
| 2975 | }; |
| 2976 | return lookupVarByName(self, name); |
| 2977 | } |
| 2978 | |
| 2979 | /// Save current lexical variable scope depth. |
| 2980 | unsafe fn enterVarScope 'arena 'phase 'function (self: &FnLowerer 'arena 'phase 'function) -> u32 where 'arena: 'phase, 'phase: 'function { |
| 2981 | return self.vars.len; |
| 2982 | } |
| 2983 | |
| 2984 | /// Restore lexical variable scope depth. |
| 2985 | unsafe fn exitVarScope 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, savedVarsLen: u32) where 'arena: 'phase, 'phase: 'function { |
| 2986 | set self.vars = @sliceOf(self.vars.ptr, savedVarsLen, self.vars.cap); |
| 2987 | } |
| 2988 | |
| 2989 | /// Get the metadata for a variable. |
| 2990 | unsafe fn getVar 'arena 'phase 'function (self: &FnLowerer 'arena 'phase 'function, v: Var) -> *unsafe VarData where 'arena: 'phase, 'phase: 'function { |
| 2991 | assert *v < self.vars.len; |
| 2992 | return &self.vars[*v]; |
| 2993 | } |
| 2994 | |
| 2995 | /// Create a block parameter to merge a variable's value from multiple |
| 2996 | /// control flow paths. |
| 2997 | /// |
| 2998 | /// Called when [`useVarInBlock`] cannot find a local definition and the block is |
| 2999 | /// unsealed or has multiple predecessors. For example, `x` can be used in `@end` |
| 3000 | /// but defined differently in `@then` and `@else`: |
| 3001 | /// |
| 3002 | /// @then |
| 3003 | /// jmp @end(1); // x = 1 |
| 3004 | /// @else |
| 3005 | /// jmp @end(2); // x = 2 |
| 3006 | /// @end(w32 %1) // x = %1, merged from predecessors |
| 3007 | /// ret %1; |
| 3008 | /// |
| 3009 | /// Create a register `%1` as a block parameter. In a sealed block, patch each |
| 3010 | /// predecessor's jump to pass its value of `x`. Otherwise, defer until sealing. |
| 3011 | unsafe fn createBlockParam 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId, v: Var) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3012 | // Entry block must not have block parameters. |
| 3013 | assert block <> self.entryBlock, "createBlockParam: entry block must not have block parameters"; |
| 3014 | // Allocate a register to hold the merged value. |
| 3015 | let reg = nextReg(self); |
| 3016 | let type = getVar(self, v).type; |
| 3017 | |
| 3018 | // Create block parameter and add it to the block. |
| 3019 | let param = il::Param { value: reg, type }; |
| 3020 | let blk = getBlockMut(self, block); |
| 3021 | let paramIdx = blk.paramVars.len; |
| 3022 | blk.params.append(param, alloc::arenaAllocator(self.arena)); |
| 3023 | blk.paramVars.append(*v, alloc::arenaAllocator(self.arena)); // Associate variable with parameter. |
| 3024 | |
| 3025 | // Record that this variable's value in this block is now the parameter register. |
| 3026 | // This must happen before the predecessor loop to handle self-referential loops. |
| 3027 | set blk.vars[*v] = il::Val::Reg(reg); |
| 3028 | |
| 3029 | if blk.sealState == Sealed::Yes { |
| 3030 | // Block sealed: check for trivial phi before committing. If all |
| 3031 | // predecessors provide the same value, we can remove the param we |
| 3032 | // just created and use that value directly. |
| 3033 | if let trivial = try getTrivialPhiVal(self, block, v) { |
| 3034 | let provisional = il::Val::Reg(reg); |
| 3035 | removeLastBlockParam(self, block); |
| 3036 | rewriteCachedVarValue(self, v, provisional, trivial); |
| 3037 | set getBlockMut(self, block).vars[*v] = trivial; |
| 3038 | return trivial; |
| 3039 | } |
| 3040 | // Non-trivial phi: patch predecessors to pass their values. |
| 3041 | try resolveBlockArgs(self, block, v, paramIdx); |
| 3042 | } |
| 3043 | return il::Val::Reg(reg); |
| 3044 | } |
| 3045 | |
| 3046 | /// Complete the block parameter at `paramIdx`. Look up the variable's value in |
| 3047 | /// each predecessor and patch its terminator with the edge argument. |
| 3048 | /// |
| 3049 | /// This is the block-parameter equivalent of adding operands to a phi-function in |
| 3050 | /// traditional SSA. Where a phi-function merges values at the join point: |
| 3051 | /// |
| 3052 | /// x3 = phi(x1, x2) |
| 3053 | /// |
| 3054 | /// This representation avoids the need for phi nodes to reference their |
| 3055 | /// predecessor blocks explicitly, since the control flow edges already encode |
| 3056 | /// that information. |
| 3057 | unsafe fn resolveBlockArgs 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId, v: Var, paramIdx: u32) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3058 | let blk = getBlock(self, block); |
| 3059 | |
| 3060 | // For each predecessor, recursively look up the variable's reaching definition |
| 3061 | // in that block, then patch the predecessor's terminator to pass that value |
| 3062 | // as an argument to this block's parameter. |
| 3063 | for predId in blk.preds { |
| 3064 | let pred = BlockId(predId); |
| 3065 | // This may recursively trigger more block arg resolution if the |
| 3066 | // predecessor also needs to look up the variable from its predecessors. |
| 3067 | let val = try useVarInBlock(self, pred, v); |
| 3068 | assert val <> il::Val::Undef, "createBlockParam: predecessor provides undef value for block parameter"; |
| 3069 | patchTerminatorArg(self, pred, *block, paramIdx, val); |
| 3070 | } |
| 3071 | } |
| 3072 | |
| 3073 | /// Check if a block parameter is trivial, i.e. all predecessors provide |
| 3074 | /// the same value. Returns the trivial value if so. |
| 3075 | unsafe fn getTrivialPhiVal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId, v: Var) -> ?il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3076 | let blk = getBlock(self, block); |
| 3077 | // Get the block parameter register. |
| 3078 | let paramReg = blk.vars[*v]; |
| 3079 | // Check if all predecessors provide the same value. |
| 3080 | let mut sameVal: ?il::Val = nil; |
| 3081 | |
| 3082 | for predId in blk.preds { |
| 3083 | let pred = BlockId(predId); |
| 3084 | let val = try useVarInBlock(self, pred, v); |
| 3085 | |
| 3086 | // Check if this is a self-reference. |
| 3087 | // This happens in cycles where the loop back-edge passes the phi to |
| 3088 | // itself. We skip self-references when checking for trivial phis. |
| 3089 | if let reg = paramReg { |
| 3090 | if val == reg { |
| 3091 | // Self-reference, skip this predecessor. |
| 3092 | } else if let sv = sameVal { |
| 3093 | if val <> sv { |
| 3094 | // Multiple different values, not trivial. |
| 3095 | return nil; |
| 3096 | } |
| 3097 | } else { |
| 3098 | set sameVal = val; |
| 3099 | } |
| 3100 | } else { // No param reg set yet, can't be trivial. |
| 3101 | return nil; |
| 3102 | } |
| 3103 | } |
| 3104 | return sameVal; |
| 3105 | } |
| 3106 | |
| 3107 | /// Patch a single terminator argument for a specific edge. This is used during |
| 3108 | /// SSA construction to pass variable values along control flow edges. |
| 3109 | unsafe fn patchTerminatorArg 'arena 'phase 'function ( |
| 3110 | self: &mut FnLowerer 'arena 'phase 'function, |
| 3111 | from: BlockId, // The predecessor block containing the terminator to patch. |
| 3112 | target: u32, // The index of the target block we're passing the value to. |
| 3113 | paramIdx: u32, // The index of the block parameter to set. |
| 3114 | val: il::Val // The value to pass as the argument. |
| 3115 | ) where 'arena: 'phase, 'phase: 'function { |
| 3116 | let data = getBlockMut(self, from); |
| 3117 | let ix = data.instrs.len - 1; // The terminator is always the last instruction. |
| 3118 | |
| 3119 | // TODO: We shouldn't need to use a mutable subscript here, given that the |
| 3120 | // fields are already mutable. |
| 3121 | match &mut data.instrs[ix] { |
| 3122 | case il::Instr::Jmp { args, .. } => { |
| 3123 | set *args = growArgs(self, *args, paramIdx + 1); |
| 3124 | set args[paramIdx] = val; |
| 3125 | } |
| 3126 | case il::Instr::Br { thenTarget, thenArgs, elseTarget, elseArgs, .. } => { |
| 3127 | // Nb. both branches could target the same block (e.g. `if cond { x } else { x }`). |
| 3128 | if *thenTarget == target { |
| 3129 | set *thenArgs = growArgs(self, *thenArgs, paramIdx + 1); |
| 3130 | set thenArgs[paramIdx] = val; |
| 3131 | } |
| 3132 | if *elseTarget == target { |
| 3133 | set *elseArgs = growArgs(self, *elseArgs, paramIdx + 1); |
| 3134 | set elseArgs[paramIdx] = val; |
| 3135 | } |
| 3136 | } |
| 3137 | case il::Instr::Switch { defaultTarget, defaultArgs, cases, .. } => { |
| 3138 | if *defaultTarget == target { |
| 3139 | set *defaultArgs = growArgs(self, *defaultArgs, paramIdx + 1); |
| 3140 | set defaultArgs[paramIdx] = val; |
| 3141 | } |
| 3142 | let idx = paramIdx + 1; |
| 3143 | for ci in 0..cases.len { |
| 3144 | if cases[ci].target == target { |
| 3145 | set cases[ci].args = growArgs(self, cases[ci].args, idx); |
| 3146 | set cases[ci].args[paramIdx] = val; |
| 3147 | } |
| 3148 | } |
| 3149 | } |
| 3150 | else => { |
| 3151 | // Other terminators (e.g. `Ret`, `Unreachable`) don't have successor blocks. |
| 3152 | } |
| 3153 | } |
| 3154 | } |
| 3155 | |
| 3156 | /// Grow an args array to hold at least the given capacity. |
| 3157 | unsafe fn growArgs 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, args: *unsafe mut [il::Val], capacity: u32) -> *unsafe mut [il::Val] where 'arena: 'phase, 'phase: 'function { |
| 3158 | if args.len >= capacity { |
| 3159 | return args; |
| 3160 | } |
| 3161 | let newArgs = try! alloc::allocRawSlice( |
| 3162 | self.arena, @sizeOf(il::Val), @alignOf(il::Val), capacity |
| 3163 | ) as *unsafe mut [il::Val]; |
| 3164 | |
| 3165 | for arg, i in args { |
| 3166 | set newArgs[i] = arg; |
| 3167 | } |
| 3168 | for i in args.len..capacity { |
| 3169 | set newArgs[i] = il::Val::Undef; |
| 3170 | } |
| 3171 | return newArgs; |
| 3172 | } |
| 3173 | |
| 3174 | /// Extract the parameter name from an [`FnParam`] AST node value. |
| 3175 | fn paramName(value: *ast::NodeValue) -> *[u8] throws (LowerError) { |
| 3176 | let case ast::NodeValue::FnParam(param) = *value else { |
| 3177 | throw LowerError::ExpectedFunctionParam; |
| 3178 | }; |
| 3179 | let case ast::NodeValue::Ident(name) = param.name.value else { |
| 3180 | throw LowerError::ExpectedIdentifier; |
| 3181 | }; |
| 3182 | return name; |
| 3183 | } |
| 3184 | |
| 3185 | /// Lower function parameters. Declares variables for each parameter. |
| 3186 | /// When a receiver name is passed, we're handling a trait method. |
| 3187 | unsafe fn lowerParams 'arena 'phase 'function ( |
| 3188 | self: &mut FnLowerer 'arena 'phase 'function, |
| 3189 | fnType: resolver::FnType, |
| 3190 | astParams: *[*ast::Node], |
| 3191 | receiverName: ?*ast::Node |
| 3192 | ) -> *unsafe [il::Param] throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3193 | let offset: u32 = 1 if self.returnReg <> nil else 0; |
| 3194 | let totalLen = fnType.paramTypes.len as u32 + offset; |
| 3195 | if totalLen == 0 { |
| 3196 | return &[]; |
| 3197 | } |
| 3198 | assert fnType.paramTypes.len as u32 <= resolver::MAX_FN_PARAMS; |
| 3199 | |
| 3200 | let params = try! alloc::allocRawSlice( |
| 3201 | self.arena, @sizeOf(il::Param), @alignOf(il::Param), totalLen |
| 3202 | ) as *unsafe mut [il::Param]; |
| 3203 | |
| 3204 | if let reg = self.returnReg { |
| 3205 | set params[0] = il::Param { value: reg, type: il::Type::W64 }; |
| 3206 | } |
| 3207 | for i in 0..fnType.paramTypes.len as u32 { |
| 3208 | let type = ilType(self.low, *fnType.paramTypes[i]); |
| 3209 | let reg = nextReg(self); |
| 3210 | |
| 3211 | set params[i + offset] = il::Param { value: reg, type }; |
| 3212 | |
| 3213 | // Declare the parameter variable. For the receiver, the name comes |
| 3214 | // from the receiver node. |
| 3215 | // For all other parameters, the name comes from the AST params. |
| 3216 | let mut name: *[u8] = undefined; |
| 3217 | if let recNode = receiverName { |
| 3218 | if i == 0 { |
| 3219 | let case ast::NodeValue::Ident(recName) = recNode.value else { |
| 3220 | throw LowerError::ExpectedIdentifier; |
| 3221 | }; |
| 3222 | set name = recName; |
| 3223 | } else { |
| 3224 | set name = try paramName(&astParams[i - 1].value); |
| 3225 | } |
| 3226 | } else { |
| 3227 | set name = try paramName(&astParams[i].value); |
| 3228 | } |
| 3229 | let v = newVar(self, name, type, false, il::Val::Undef); |
| 3230 | |
| 3231 | set self.params[i] = FnParamBinding { var: v, reg }; |
| 3232 | set self.paramsLen += 1; |
| 3233 | } |
| 3234 | return params; |
| 3235 | } |
| 3236 | |
| 3237 | /// Resolve match subject. |
| 3238 | unsafe fn lowerMatchSubject 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, subject: *ast::Node) -> MatchSubject throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3239 | let mut val = try lowerExpr(self, subject); |
| 3240 | let subjectType = try typeOf(self, subject); |
| 3241 | let unwrapped = resolver::unwrapMatchSubject(subjectType); |
| 3242 | |
| 3243 | // When matching an aggregate by value, copy it to a fresh stack slot so |
| 3244 | // that bindings are independent of the original memory. Without this, |
| 3245 | // the lowerer returns a pointer into the source and mutations to the |
| 3246 | // source silently corrupt the bound variables. |
| 3247 | if unwrapped.by == resolver::MatchBy::Value and isAggregateType(unwrapped.effectiveTy) { |
| 3248 | set val = try emitStackVal(self, unwrapped.effectiveTy, val); |
| 3249 | } |
| 3250 | |
| 3251 | let mut bindType = unwrapped.effectiveTy; |
| 3252 | if let case resolver::Type::Optional(inner) = unwrapped.effectiveTy { |
| 3253 | set bindType = *inner; |
| 3254 | } |
| 3255 | let ilType = ilType(self.low, unwrapped.effectiveTy); |
| 3256 | let kind = matchSubjectKind(unwrapped.effectiveTy); |
| 3257 | |
| 3258 | return MatchSubject { val, type: unwrapped.effectiveTy, ilType, bindType, kind, by: unwrapped.by }; |
| 3259 | } |
| 3260 | |
| 3261 | /// Check whether a case pattern is an unconditional wildcard. |
| 3262 | fn isWildcardPattern(pattern: *ast::Node) -> bool { |
| 3263 | match pattern.value { |
| 3264 | case ast::NodeValue::Placeholder => return true, |
| 3265 | else => return false, |
| 3266 | } |
| 3267 | } |
| 3268 | |
| 3269 | /// Check whether an AST node is the `undefined` literal. |
| 3270 | fn isUndef(node: *ast::Node) -> bool { |
| 3271 | match node.value { |
| 3272 | case ast::NodeValue::Undef => return true, |
| 3273 | else => return false, |
| 3274 | } |
| 3275 | } |
| 3276 | |
| 3277 | /// Load the tag byte from a tagged value aggregate (optionals and unions). |
| 3278 | unsafe fn tvalTagReg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, base: il::Reg) -> il::Reg where 'arena: 'phase, 'phase: 'function { |
| 3279 | let tagReg = nextReg(self); |
| 3280 | emitLoadW8At(self, tagReg, base, TVAL_TAG_OFFSET); |
| 3281 | return tagReg; |
| 3282 | } |
| 3283 | |
| 3284 | /// Load the tag word from a result aggregate. |
| 3285 | unsafe fn resultTagReg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, base: il::Reg) -> il::Reg where 'arena: 'phase, 'phase: 'function { |
| 3286 | let tagReg = nextReg(self); |
| 3287 | emitLoadW64At(self, tagReg, base, TVAL_TAG_OFFSET); |
| 3288 | return tagReg; |
| 3289 | } |
| 3290 | |
| 3291 | /// Get the register to compare against `0` for optional `nil` checking. |
| 3292 | /// For null-ptr-optimized types, loads the data pointer, or returns it |
| 3293 | /// directly for scalar pointers. For aggregates, returns the tag register. |
| 3294 | unsafe 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 { |
| 3295 | let reg = emitValToReg(self, val); |
| 3296 | |
| 3297 | match typ { |
| 3298 | case resolver::Type::Optional(resolver::Type::Slice { .. }) => { |
| 3299 | let ptrReg = nextReg(self); |
| 3300 | emitLoadW64At(self, ptrReg, reg, SLICE_PTR_OFFSET); |
| 3301 | return ptrReg; |
| 3302 | } |
| 3303 | case resolver::Type::Optional(resolver::Type::Pointer { .. }) => return reg, |
| 3304 | case resolver::Type::Optional(_) => return tvalTagReg(self, reg), |
| 3305 | else => return reg, |
| 3306 | } |
| 3307 | } |
| 3308 | |
| 3309 | /// Lower an optional nil check (`opt == nil` or `opt <> nil`). |
| 3310 | 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 { |
| 3311 | let optTy = try typeOf(self, opt); |
| 3312 | // Handle `nil == nil` or `nil <> nil`. |
| 3313 | if optTy == resolver::Type::Nil { |
| 3314 | return il::Val::Imm(1) if isEq else il::Val::Imm(0); |
| 3315 | } |
| 3316 | let val = try lowerExpr(self, opt); |
| 3317 | let cmpReg = try optionalNilReg(self, val, optTy); |
| 3318 | |
| 3319 | // Null-pointer-optimized types compare a 64-bit pointer against zero. |
| 3320 | // Aggregate optionals compare an 8-bit tag byte against zero. |
| 3321 | let cmpType = il::Type::W64 if resolver::isOptionalPointer(optTy) else il::Type::W8; |
| 3322 | |
| 3323 | let op = il::BinOp::Eq if isEq else il::BinOp::Ne; |
| 3324 | return emitTypedBinOp(self, op, cmpType, il::Val::Reg(cmpReg), il::Val::Imm(0)); |
| 3325 | } |
| 3326 | |
| 3327 | /// Load the payload value from a tagged value aggregate at the given offset. |
| 3328 | unsafe 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 { |
| 3329 | if payload == resolver::Type::Void { |
| 3330 | return il::Val::Undef; |
| 3331 | } |
| 3332 | return emitRead(self, base, valOffset, payload); |
| 3333 | } |
| 3334 | |
| 3335 | /// Compute the address of the payload in a tagged value aggregate. |
| 3336 | unsafe fn tvalPayloadAddr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, base: il::Reg, valOffset: i32) -> il::Val where 'arena: 'phase, 'phase: 'function { |
| 3337 | return il::Val::Reg(emitPtrOffset(self, base, valOffset)); |
| 3338 | } |
| 3339 | |
| 3340 | /// Bind a variable to a tagged value's payload. |
| 3341 | unsafe fn bindPayloadVariable 'arena 'phase 'function ( |
| 3342 | self: &mut FnLowerer 'arena 'phase 'function, |
| 3343 | name: *[u8], |
| 3344 | subjectVal: il::Val, |
| 3345 | bindType: resolver::Type, |
| 3346 | matchBy: resolver::MatchBy, |
| 3347 | valOffset: i32, |
| 3348 | mutable: bool |
| 3349 | ) -> Var throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3350 | let base = emitValToReg(self, subjectVal); |
| 3351 | let mut payload: il::Val = undefined; |
| 3352 | |
| 3353 | match matchBy { |
| 3354 | case resolver::MatchBy::Value => |
| 3355 | set payload = tvalPayloadVal(self, base, bindType, valOffset), |
| 3356 | case resolver::MatchBy::Ref, resolver::MatchBy::MutRef => |
| 3357 | set payload = tvalPayloadAddr(self, base, valOffset), |
| 3358 | }; |
| 3359 | return newVar(self, name, ilType(self.low, bindType), mutable, payload); |
| 3360 | } |
| 3361 | |
| 3362 | /// Bind an identifier from a matched subject. |
| 3363 | unsafe fn bindMatchVariable 'arena 'phase 'function ( |
| 3364 | self: &mut FnLowerer 'arena 'phase 'function, |
| 3365 | subject: &MatchSubject, |
| 3366 | binding: *ast::Node, |
| 3367 | mutable: bool |
| 3368 | ) -> ?Var throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3369 | // Only bind if the pattern is an identifier. |
| 3370 | let case ast::NodeValue::Ident(name) = binding.value else { |
| 3371 | return nil; |
| 3372 | }; |
| 3373 | // For optional aggregates, extract the payload from the tagged value. |
| 3374 | // The tag check already passed, so we know the payload is valid. |
| 3375 | if let case MatchSubjectKind::OptionalAggregate = subject.kind { |
| 3376 | let valOffset = resolver::getOptionalValOffset(subject.bindType) as i32; |
| 3377 | return try bindPayloadVariable(self, name, subject.val, subject.bindType, subject.by, valOffset, mutable); |
| 3378 | } |
| 3379 | // Declare the variable in the current block's scope. |
| 3380 | return newVar(self, name, ilType(self.low, subject.bindType), mutable, subject.val); |
| 3381 | } |
| 3382 | |
| 3383 | /// Bind variables from inside case patterns (union variants, records, slices). |
| 3384 | /// `failBlock` is passed when nested patterns may require additional tests |
| 3385 | /// that branch on mismatch (e.g. nested union variant tests). |
| 3386 | 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 { |
| 3387 | for pattern in patterns { |
| 3388 | |
| 3389 | // Handle simple variant patterns like `Variant(x)`. |
| 3390 | if let arg = resolver::variantPatternBinding(self.low.resolver, pattern) { |
| 3391 | let case MatchSubjectKind::Union(unionInfo) = subject.kind |
| 3392 | else panic "bindPatternVariables: expected union subject"; |
| 3393 | let valOffset = unionInfo.valOffset as i32; |
| 3394 | |
| 3395 | // Get the actual field type from the variant's record info. |
| 3396 | // This preserves the original data layout type (e.g. `*T`) even when |
| 3397 | // the resolver resolved the pattern against a dereferenced type (`T`). |
| 3398 | let variantExtra = resolver::nodeData(self.low.resolver, pattern).extra; |
| 3399 | let case resolver::NodeExtra::UnionVariant { ordinal, .. } = variantExtra |
| 3400 | else panic "bindPatternVariables: expected variant extra"; |
| 3401 | let payloadType = unionInfo.variants[ordinal].valueType; |
| 3402 | let payloadRec = resolver::getRecord(payloadType) |
| 3403 | else panic "bindPatternVariables: expected record payload"; |
| 3404 | let fieldType = payloadRec.fields[0].fieldType; |
| 3405 | |
| 3406 | match arg.value { |
| 3407 | case ast::NodeValue::Ident(name) => { |
| 3408 | try bindPayloadVariable(self, name, subject.val, fieldType, subject.by, valOffset, false); |
| 3409 | } |
| 3410 | case ast::NodeValue::Placeholder => {} |
| 3411 | else => { |
| 3412 | // Nested pattern inside a variant call, e.g. `Variant(Inner { x, y })`. |
| 3413 | let base = emitValToReg(self, subject.val); |
| 3414 | let payloadBase = emitPtrOffset(self, base, valOffset); |
| 3415 | let fieldInfo = resolver::RecordField { |
| 3416 | name: nil, |
| 3417 | fieldType, |
| 3418 | offset: 0, |
| 3419 | }; |
| 3420 | try bindFieldVariable(self, arg, payloadBase, fieldInfo, subject.by, failBlock); |
| 3421 | } |
| 3422 | } |
| 3423 | } |
| 3424 | match pattern.value { |
| 3425 | case ast::NodeValue::Call(call) => { |
| 3426 | if let body = resolver::getRecord(subject.type) { |
| 3427 | let base = emitValToReg(self, subject.val); |
| 3428 | for binding, i in call.args { |
| 3429 | try bindFieldVariable(self, binding, base, body.fields[i], subject.by, failBlock); |
| 3430 | } |
| 3431 | } |
| 3432 | } |
| 3433 | // Compound variant patterns like `Variant { a, b }`. |
| 3434 | case ast::NodeValue::RecordLit(lit) => |
| 3435 | try bindRecordPatternFields(self, subject, pattern, lit, failBlock), |
| 3436 | // Array patterns like `[a, b, c]`. |
| 3437 | case ast::NodeValue::ArrayLit(items) => |
| 3438 | try bindArrayPatternElements(self, subject, items, failBlock), |
| 3439 | // Literals, wildcards, identifiers: no bindings needed. |
| 3440 | else => {}, |
| 3441 | } |
| 3442 | } |
| 3443 | } |
| 3444 | |
| 3445 | /// Bind variables from an array literal pattern (e.g., `[a, 1, c]`). |
| 3446 | /// Each element is either bound as a variable, skipped (placeholder), or |
| 3447 | /// tested against the subject element, branching to `failBlock` on mismatch. |
| 3448 | unsafe fn bindArrayPatternElements 'arena 'phase 'function ( |
| 3449 | self: &mut FnLowerer 'arena 'phase 'function, |
| 3450 | subject: &MatchSubject, |
| 3451 | items: *[*ast::Node], |
| 3452 | failBlock: BlockId |
| 3453 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3454 | let case resolver::Type::Array(arrInfo) = subject.type |
| 3455 | else throw LowerError::ExpectedSliceOrArray; |
| 3456 | |
| 3457 | let elemTy = *arrInfo.item; |
| 3458 | let elemLayout = resolver::getTypeLayout(elemTy); |
| 3459 | let stride = elemLayout.size as i32; |
| 3460 | let base = emitValToReg(self, subject.val); |
| 3461 | |
| 3462 | for elem, i in items { |
| 3463 | let fieldInfo = resolver::RecordField { |
| 3464 | name: nil, |
| 3465 | fieldType: elemTy, |
| 3466 | offset: (i as i32) * stride, |
| 3467 | }; |
| 3468 | try bindFieldVariable(self, elem, base, fieldInfo, subject.by, failBlock); |
| 3469 | } |
| 3470 | } |
| 3471 | |
| 3472 | /// Bind fields from a record pattern. |
| 3473 | 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 { |
| 3474 | // No fields to bind (e.g., `{ .. }`). |
| 3475 | if lit.fields.len == 0 { |
| 3476 | return; |
| 3477 | } |
| 3478 | // Optional value patterns were already compared structurally by |
| 3479 | // `emitPatternMatch`; unlike union record patterns, they have no payload |
| 3480 | // bindings to extract here. |
| 3481 | if let case MatchSubjectKind::OptionalAggregate = subject.kind { |
| 3482 | return; |
| 3483 | } |
| 3484 | if let recInfo = resolver::getRecord(subject.type) { |
| 3485 | let base = emitValToReg(self, subject.val); |
| 3486 | try bindNestedRecordFields(self, base, lit, recInfo, subject.by, failBlock); |
| 3487 | return; |
| 3488 | } |
| 3489 | // Get the union type info from the subject. |
| 3490 | let case MatchSubjectKind::Union(unionInfo) = subject.kind |
| 3491 | else panic "bindRecordPatternFields: expected union subject"; |
| 3492 | |
| 3493 | // Get the variant index from the pattern node. |
| 3494 | let case resolver::NodeExtra::UnionVariant { ordinal: variantOrdinal, .. } = |
| 3495 | resolver::nodeData(self.low.resolver, pattern).extra |
| 3496 | else throw LowerError::MissingMetadata; |
| 3497 | |
| 3498 | // Get the record type from the variant's payload type. |
| 3499 | let payloadType = unionInfo.variants[variantOrdinal].valueType; |
| 3500 | let recInfo = resolver::getRecord(payloadType) |
| 3501 | else throw LowerError::ExpectedRecord; |
| 3502 | |
| 3503 | // Get the payload base pointer which points to the record within the tagged union. |
| 3504 | let base = emitValToReg(self, subject.val); |
| 3505 | let valOffset = unionInfo.valOffset as i32; |
| 3506 | let payloadBase = emitPtrOffset(self, base, valOffset); |
| 3507 | |
| 3508 | try bindNestedRecordFields(self, payloadBase, lit, recInfo, subject.by, failBlock); |
| 3509 | } |
| 3510 | |
| 3511 | /// Bind a single record field to a pattern variable, with support for nested |
| 3512 | /// pattern tests that branch to `failBlock` on mismatch. |
| 3513 | unsafe fn bindFieldVariable 'arena 'phase 'function ( |
| 3514 | self: &mut FnLowerer 'arena 'phase 'function, |
| 3515 | binding: *ast::Node, |
| 3516 | base: il::Reg, |
| 3517 | fieldInfo: resolver::RecordField, |
| 3518 | matchBy: resolver::MatchBy, |
| 3519 | failBlock: BlockId |
| 3520 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3521 | match binding.value { |
| 3522 | case ast::NodeValue::Ident(name) => { |
| 3523 | let val = emitRead(self, base, fieldInfo.offset, fieldInfo.fieldType) |
| 3524 | if matchBy == resolver::MatchBy::Value |
| 3525 | else il::Val::Reg(emitPtrOffset(self, base, fieldInfo.offset)); |
| 3526 | newVar(self, name, ilType(self.low, fieldInfo.fieldType), false, val); |
| 3527 | } |
| 3528 | case ast::NodeValue::Placeholder => {} |
| 3529 | case ast::NodeValue::RecordLit(lit) => { |
| 3530 | // Check if this record literal is a union variant pattern. |
| 3531 | if let keyNode = resolver::patternVariantKeyNode(binding) { |
| 3532 | if let case resolver::NodeExtra::UnionVariant { .. } = resolver::nodeData(self.low.resolver, keyNode).extra { |
| 3533 | try emitNestedFieldTest(self, binding, base, fieldInfo, matchBy, failBlock); |
| 3534 | return; |
| 3535 | } |
| 3536 | } |
| 3537 | // Plain nested record destructuring pattern. |
| 3538 | // Auto-deref: if the field is a pointer, load it first. |
| 3539 | let mut derefType = fieldInfo.fieldType; |
| 3540 | let mut nestedBase = emitPtrOffset(self, base, fieldInfo.offset); |
| 3541 | if let case resolver::Type::Pointer { target, .. } = fieldInfo.fieldType { |
| 3542 | let ptrReg = nextReg(self); |
| 3543 | emitLoadW64At(self, ptrReg, nestedBase, 0); |
| 3544 | set nestedBase = ptrReg; |
| 3545 | set derefType = *target; |
| 3546 | } |
| 3547 | let recInfo = resolver::getRecord(derefType) |
| 3548 | else throw LowerError::ExpectedRecord; |
| 3549 | |
| 3550 | try bindNestedRecordFields(self, nestedBase, lit, recInfo, matchBy, failBlock); |
| 3551 | } |
| 3552 | else => { |
| 3553 | // Nested pattern requiring a test (union variant scope access, literal, etc). |
| 3554 | try emitNestedFieldTest(self, binding, base, fieldInfo, matchBy, failBlock); |
| 3555 | } |
| 3556 | } |
| 3557 | } |
| 3558 | |
| 3559 | /// Emit a nested pattern test for a record field value, branching to |
| 3560 | /// `failBlock` if the pattern does not match. On success, continues in |
| 3561 | /// a fresh block and binds any nested variables. |
| 3562 | unsafe fn emitNestedFieldTest 'arena 'phase 'function ( |
| 3563 | self: &mut FnLowerer 'arena 'phase 'function, |
| 3564 | pattern: *ast::Node, |
| 3565 | base: il::Reg, |
| 3566 | fieldInfo: resolver::RecordField, |
| 3567 | matchBy: resolver::MatchBy, |
| 3568 | failBlock: BlockId |
| 3569 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3570 | let mut fieldType = fieldInfo.fieldType; |
| 3571 | let fieldPtr = emitPtrOffset(self, base, fieldInfo.offset); |
| 3572 | |
| 3573 | // Auto-deref: when the field is a pointer and the pattern destructures |
| 3574 | // the pointed-to value, load the pointer and use the target type. |
| 3575 | // The loaded pointer becomes the base address for the nested subject. |
| 3576 | let mut derefBase: ?il::Reg = nil; |
| 3577 | if let case resolver::Type::Pointer { target, .. } = fieldType { |
| 3578 | if resolver::isDestructuringPattern(pattern) { |
| 3579 | let ptrReg = nextReg(self); |
| 3580 | emitLoadW64At(self, ptrReg, fieldPtr, 0); |
| 3581 | set derefBase = ptrReg; |
| 3582 | set fieldType = *target; |
| 3583 | } |
| 3584 | } |
| 3585 | // Build a MatchSubject for the nested field. |
| 3586 | let ilTy = ilType(self.low, fieldType); |
| 3587 | let kind = matchSubjectKind(fieldType); |
| 3588 | |
| 3589 | // Determine the subject value. |
| 3590 | let mut val: il::Val = undefined; |
| 3591 | if let reg = derefBase { |
| 3592 | // Auto-deref: the loaded pointer is the address of the target value. |
| 3593 | set val = il::Val::Reg(reg); |
| 3594 | } else if isAggregateType(fieldType) { |
| 3595 | // Aggregate: use the pointer. |
| 3596 | set val = il::Val::Reg(fieldPtr); |
| 3597 | } else { |
| 3598 | // Scalar: load the value. |
| 3599 | set val = emitRead(self, base, fieldInfo.offset, fieldType); |
| 3600 | } |
| 3601 | let nestedSubject = MatchSubject { |
| 3602 | val, |
| 3603 | type: fieldType, |
| 3604 | ilType: ilTy, |
| 3605 | bindType: fieldType, |
| 3606 | kind, |
| 3607 | by: matchBy, |
| 3608 | }; |
| 3609 | |
| 3610 | // Emit the pattern test: on success jump to `continueBlock`, on fail to `failBlock`. |
| 3611 | let continueBlock = try createBlock(self, "nest"); |
| 3612 | try emitPatternMatch(self, &nestedSubject, pattern, continueBlock, failBlock); |
| 3613 | try switchToAndSeal(self, continueBlock); |
| 3614 | |
| 3615 | // After the test succeeds, bind any nested variables. |
| 3616 | let patterns = [pattern]; |
| 3617 | try bindPatternVariables(self, &nestedSubject, &patterns[..], failBlock); |
| 3618 | } |
| 3619 | |
| 3620 | /// Bind variables from a nested record literal pattern. |
| 3621 | unsafe fn bindNestedRecordFields 'arena 'phase 'function ( |
| 3622 | self: &mut FnLowerer 'arena 'phase 'function, |
| 3623 | base: il::Reg, |
| 3624 | lit: ast::RecordLit, |
| 3625 | recInfo: resolver::RecordType, |
| 3626 | matchBy: resolver::MatchBy, |
| 3627 | failBlock: BlockId |
| 3628 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3629 | for fieldNode in lit.fields { |
| 3630 | let case ast::NodeValue::RecordLitField(field) = fieldNode.value else { |
| 3631 | throw LowerError::UnexpectedNodeValue(fieldNode); |
| 3632 | }; |
| 3633 | let fieldIdx = resolver::recordFieldIndexFor(self.low.resolver, fieldNode) |
| 3634 | else throw LowerError::MissingMetadata; |
| 3635 | if fieldIdx >= recInfo.fields.len { |
| 3636 | throw LowerError::MissingMetadata; |
| 3637 | } |
| 3638 | let fieldInfo = recInfo.fields[fieldIdx]; |
| 3639 | |
| 3640 | try bindFieldVariable(self, field.value, base, fieldInfo, matchBy, failBlock); |
| 3641 | } |
| 3642 | } |
| 3643 | |
| 3644 | /// Lower function body to a list of basic blocks. |
| 3645 | 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 { |
| 3646 | // Create and switch to entry block. |
| 3647 | let entry = try createBlock(self, "entry"); |
| 3648 | set self.entryBlock = entry; |
| 3649 | switchToBlock(self, entry); |
| 3650 | |
| 3651 | /// Bind parameter registers to variables in the entry block. |
| 3652 | for i in 0..self.paramsLen { |
| 3653 | let def = self.params[i]; |
| 3654 | defVar(self, def.var, il::Val::Reg(def.reg)); |
| 3655 | } |
| 3656 | /// Lower function body. |
| 3657 | try lowerBlock(self, body); |
| 3658 | |
| 3659 | // Add implicit return if body doesn't diverge. |
| 3660 | if not blockHasTerminator(self) { |
| 3661 | if *self.fnType.returnType == resolver::Type::Never { |
| 3662 | emit(self, il::Instr::Unreachable); |
| 3663 | } else if self.fnType.throwList.len > 0 { |
| 3664 | if *self.fnType.returnType == resolver::Type::Void { |
| 3665 | // Implicit `void` return in throwing function: wrap in result success. |
| 3666 | let val = try buildResult(self, 0, nil, resolver::Type::Void); |
| 3667 | try emitRetVal(self, val); |
| 3668 | } else { |
| 3669 | // Non-void throwing function without explicit return should |
| 3670 | // not happen. |
| 3671 | panic "lowerFnBody: missing return in non-void function"; |
| 3672 | } |
| 3673 | } else { |
| 3674 | emit(self, il::Instr::Ret { val: nil }); |
| 3675 | } |
| 3676 | } |
| 3677 | return try finalizeBlocks(self); |
| 3678 | } |
| 3679 | |
| 3680 | /// Lower a scalar match as a switch instruction. |
| 3681 | 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 { |
| 3682 | let mut blocks: [BlockId; 32] = undefined; |
| 3683 | let mut cases: *unsafe mut [il::SwitchCase] = &mut []; |
| 3684 | let mut defaultIdx: u32 = 0; |
| 3685 | let entry = currentBlock(self); |
| 3686 | |
| 3687 | for p, i in prongs { |
| 3688 | let case ast::NodeValue::MatchProng(prong) = p.value |
| 3689 | else throw LowerError::UnexpectedNodeValue(p); |
| 3690 | |
| 3691 | match prong.arm { |
| 3692 | case ast::ProngArm::Binding(_), ast::ProngArm::Else => { |
| 3693 | set blocks[i] = try createBlock(self, "default"); |
| 3694 | set defaultIdx = i; |
| 3695 | } |
| 3696 | case ast::ProngArm::Case(pats) => { |
| 3697 | set blocks[i] = try createBlock(self, "case"); |
| 3698 | for pat in pats { |
| 3699 | let cv = resolver::constValueEntry(self.low.resolver, pat) |
| 3700 | else throw LowerError::MissingConst(pat); |
| 3701 | |
| 3702 | cases.append(il::SwitchCase { |
| 3703 | value: constToScalar(cv), |
| 3704 | target: *blocks[i], |
| 3705 | args: &mut [] |
| 3706 | }, alloc::arenaAllocator(self.arena)); |
| 3707 | } |
| 3708 | } |
| 3709 | } |
| 3710 | addPredecessor(self, blocks[i], entry); |
| 3711 | } |
| 3712 | emit(self, il::Instr::Switch { |
| 3713 | val: subject.val, |
| 3714 | defaultTarget: *blocks[defaultIdx], |
| 3715 | defaultArgs: &mut [], |
| 3716 | cases: &mut cases[..] |
| 3717 | }); |
| 3718 | |
| 3719 | for p, i in prongs { |
| 3720 | let case ast::NodeValue::MatchProng(prong) = p.value |
| 3721 | else throw LowerError::UnexpectedNodeValue(p); |
| 3722 | |
| 3723 | try switchToAndSeal(self, blocks[i]); |
| 3724 | try lowerNode(self, prong.body); |
| 3725 | try emitMergeIfUnterminated(self, mergeBlock); |
| 3726 | } |
| 3727 | if let blk = *mergeBlock { |
| 3728 | try switchToAndSeal(self, blk); |
| 3729 | } |
| 3730 | } |
| 3731 | |
| 3732 | /// Lower a match statement. |
| 3733 | /// |
| 3734 | /// Processes prongs sequentially, generating comparison code and branches for each. |
| 3735 | /// Prongs are processed in source order, so earlier prongs take precedence. |
| 3736 | /// |
| 3737 | /// Guards are handled by emitting an additional branch after pattern matching |
| 3738 | /// but before the body. |
| 3739 | /// |
| 3740 | /// Example: |
| 3741 | /// |
| 3742 | /// match x { |
| 3743 | /// case 0 => return 0, |
| 3744 | /// case 1 => return 1, |
| 3745 | /// else => return 2, |
| 3746 | /// } |
| 3747 | /// |
| 3748 | /// Generates: |
| 3749 | /// |
| 3750 | /// arm#0: |
| 3751 | /// br.eq w32 %x 0 case#0 arm#1; // if `x == 0`, jump to case#0, else arm#1 |
| 3752 | /// case#0: |
| 3753 | /// ret 0; // `case 0` body |
| 3754 | /// arm#1: |
| 3755 | /// br.eq w32 %x 1 case#1 arm#2; // if `x == 1`, jump to case#1, else arm#2 |
| 3756 | /// case#1: |
| 3757 | /// ret 1; // `case 1` body |
| 3758 | /// arm#2: |
| 3759 | /// jmp else#0; // fallthrough to `else` |
| 3760 | /// else#0: |
| 3761 | /// ret 2; // `else` body |
| 3762 | /// |
| 3763 | /// Example: Binding with guard |
| 3764 | /// |
| 3765 | /// match x { |
| 3766 | /// y if y > 0 => return y, |
| 3767 | /// else => return 0, |
| 3768 | /// } |
| 3769 | /// |
| 3770 | /// Generates: |
| 3771 | /// |
| 3772 | /// arm#0: |
| 3773 | /// jmp guard#0; // catch-all binding, jump to guard |
| 3774 | /// case#0(w32 %y): |
| 3775 | /// ret %y; // guarded case body, receives bound var |
| 3776 | /// guard#0: |
| 3777 | /// sgt w32 %cmp %x 0; // evaluate guard `y > 0` |
| 3778 | /// br.ne w32 %cmp 0 case#0 arm#1; // if `true`, jump to case body |
| 3779 | /// arm#1: |
| 3780 | /// jmp else#0; // guard failed, fallthrough to `else` |
| 3781 | /// else#0: |
| 3782 | /// ret 0; // `else` body |
| 3783 | /// |
| 3784 | 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 { |
| 3785 | assert m.prongs.len > 0; |
| 3786 | |
| 3787 | let prongs = m.prongs; |
| 3788 | // Lower the subject expression once; reused across all arms. |
| 3789 | let subject = try lowerMatchSubject(self, m.subject); |
| 3790 | // Merge block created lazily if any arm needs it (i.e., doesn't diverge). |
| 3791 | let mut mergeBlock: ?BlockId = nil; |
| 3792 | |
| 3793 | // Use `switch` instruction for matches with constant patterns. |
| 3794 | if resolver::isMatchConst(self.low.resolver, node) { |
| 3795 | try lowerMatchSwitch(self, prongs, &subject, &mut mergeBlock); |
| 3796 | return; |
| 3797 | } |
| 3798 | // Fallback: chained branches. |
| 3799 | let firstArm = try createBlock(self, "arm"); |
| 3800 | try emitJmp(self, firstArm); |
| 3801 | try switchToAndSeal(self, firstArm); |
| 3802 | |
| 3803 | for prongNode, i in prongs { |
| 3804 | let prongScope = enterVarScope(self); |
| 3805 | let case ast::NodeValue::MatchProng(prong) = prongNode.value |
| 3806 | else panic "lowerMatch: expected match prong"; |
| 3807 | |
| 3808 | let isLastArm = i + 1 == prongs.len; |
| 3809 | let hasGuard = prong.guard <> nil; |
| 3810 | let catchAll = resolver::isProngCatchAll(self.low.resolver, prongNode); |
| 3811 | |
| 3812 | // Entry block: guard block if present, otherwise the body block. |
| 3813 | // The guard block must be created before the body block so that |
| 3814 | // block indices are in reverse post-order (RPO), which the register |
| 3815 | // allocator requires. |
| 3816 | let mut entryBlock: BlockId = undefined; |
| 3817 | if hasGuard { |
| 3818 | set entryBlock = try createBlock(self, "guard"); |
| 3819 | } |
| 3820 | // Body block: where the case body lives. |
| 3821 | let mut bodyLabel = "case"; |
| 3822 | if prong.arm == ast::ProngArm::Else { |
| 3823 | set bodyLabel = "else"; |
| 3824 | } |
| 3825 | let mut bodyBlock = try createBlock(self, bodyLabel); |
| 3826 | if not hasGuard { |
| 3827 | set entryBlock = bodyBlock; |
| 3828 | } |
| 3829 | // Fallthrough block: jumped to when pattern or guard fails. |
| 3830 | let nextArm = try createBlock(self, "arm"); |
| 3831 | |
| 3832 | // Emit pattern test: branch to entry block on match, next arm on fail. |
| 3833 | match prong.arm { |
| 3834 | case ast::ProngArm::Binding(_) if not catchAll => |
| 3835 | try emitBindingTest(self, &subject, entryBlock, nextArm), |
| 3836 | case ast::ProngArm::Case(patterns) if not catchAll => |
| 3837 | try emitPatternMatches(self, &subject, patterns, entryBlock, nextArm), |
| 3838 | else => |
| 3839 | try emitJmp(self, entryBlock), |
| 3840 | } |
| 3841 | // Switch to entry block, where any variable bindings need to be created. |
| 3842 | try switchToAndSeal(self, entryBlock); |
| 3843 | |
| 3844 | // Bind pattern variables after successful match. Note that the guard |
| 3845 | // has not been evaluated yet. Nested patterns may emit additional |
| 3846 | // tests that branch to `nextArm` on failure, switching the current |
| 3847 | // block. |
| 3848 | match prong.arm { |
| 3849 | case ast::ProngArm::Binding(pat) => |
| 3850 | try bindMatchVariable(self, &subject, pat, false), |
| 3851 | case ast::ProngArm::Case(patterns) => |
| 3852 | try bindPatternVariables(self, &subject, patterns, nextArm), |
| 3853 | else => {}, |
| 3854 | } |
| 3855 | |
| 3856 | // Evaluate guard if present; can still fail to next arm. |
| 3857 | if let g = prong.guard { |
| 3858 | try emitCondBranch(self, g, bodyBlock, nextArm); |
| 3859 | } else if *currentBlock(self) <> *bodyBlock { |
| 3860 | // Nested tests changed the current block. Create a new body block |
| 3861 | // after the nest blocks to maintain RPO ordering, and jump to it. |
| 3862 | set bodyBlock = try createBlock(self, bodyLabel); |
| 3863 | try emitJmp(self, bodyBlock); |
| 3864 | } |
| 3865 | // Lower prong body and jump to merge if unterminated. |
| 3866 | try switchToAndSeal(self, bodyBlock); |
| 3867 | try lowerNode(self, prong.body); |
| 3868 | try emitMergeIfUnterminated(self, &mut mergeBlock); |
| 3869 | exitVarScope(self, prongScope); |
| 3870 | |
| 3871 | // Switch to next arm, unless last arm without guard. |
| 3872 | if not isLastArm or hasGuard { |
| 3873 | try switchToAndSeal(self, nextArm); |
| 3874 | if isLastArm { |
| 3875 | // Last arm with guard: guard failure jumps to merge. |
| 3876 | try emitMergeIfUnterminated(self, &mut mergeBlock); |
| 3877 | } |
| 3878 | } |
| 3879 | } |
| 3880 | // Continue in merge block if we have one, ie. if at least one arm doesn't |
| 3881 | // diverge. |
| 3882 | if let blk = mergeBlock { |
| 3883 | try switchToAndSeal(self, blk); |
| 3884 | } |
| 3885 | } |
| 3886 | |
| 3887 | /// Lower an `if let` statement. |
| 3888 | unsafe fn lowerIfLet 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, cond: ast::IfLet) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3889 | let savedVarsLen = enterVarScope(self); |
| 3890 | let subject = try lowerMatchSubject(self, cond.pattern.scrutinee); |
| 3891 | let mut thenBlock: BlockId = undefined; |
| 3892 | if cond.pattern.guard == nil { |
| 3893 | set thenBlock = try createBlock(self, "then"); |
| 3894 | } |
| 3895 | let elseBlock = try createBlock(self, "else"); |
| 3896 | let mut mergeBlock: ?BlockId = nil; |
| 3897 | |
| 3898 | // Pattern match: jump to @then on success, @else on failure. |
| 3899 | try lowerPatternMatch(self, &subject, &cond.pattern, &mut thenBlock, "then", elseBlock); |
| 3900 | |
| 3901 | // Lower then branch. |
| 3902 | try lowerNode(self, cond.thenBranch); |
| 3903 | try emitMergeIfUnterminated(self, &mut mergeBlock); |
| 3904 | // Pattern bindings are visible only in the success branch. Restore the |
| 3905 | // outer variable scope before lowering `else`, where a same-named outer |
| 3906 | // variable may be referenced. |
| 3907 | exitVarScope(self, savedVarsLen); |
| 3908 | |
| 3909 | // Lower else branch. |
| 3910 | try switchToAndSeal(self, elseBlock); |
| 3911 | if let elseBranch = cond.elseBranch { |
| 3912 | try lowerNode(self, elseBranch); |
| 3913 | } |
| 3914 | try emitMergeIfUnterminated(self, &mut mergeBlock); |
| 3915 | |
| 3916 | if let blk = mergeBlock { |
| 3917 | try switchToAndSeal(self, blk); |
| 3918 | } |
| 3919 | } |
| 3920 | |
| 3921 | /// Emit pattern match branch with optional guard, and bind variables. |
| 3922 | /// Used by `if-let`, `let-else`, and `while-let` lowering. |
| 3923 | /// |
| 3924 | /// When a guard is present, the guard block is created before `successBlock` |
| 3925 | /// to ensure block indices are in RPO. |
| 3926 | unsafe fn lowerPatternMatch 'arena 'phase 'function ( |
| 3927 | self: &mut FnLowerer 'arena 'phase 'function, |
| 3928 | subject: &MatchSubject, |
| 3929 | pat: &ast::PatternMatch, |
| 3930 | successBlock: &mut BlockId, |
| 3931 | successLabel: *[u8], |
| 3932 | failBlock: BlockId |
| 3933 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3934 | // If guard present, pattern match jumps to @guard, then guard evaluation |
| 3935 | // jumps to `successBlock` or `failBlock`. Otherwise, jump directly to |
| 3936 | // `successBlock`. |
| 3937 | let mut targetBlock: BlockId = undefined; |
| 3938 | if pat.guard <> nil { |
| 3939 | set targetBlock = try createBlock(self, "guard"); |
| 3940 | set *successBlock = try createBlock(self, successLabel); |
| 3941 | } else { |
| 3942 | set targetBlock = *successBlock; |
| 3943 | } |
| 3944 | match pat.kind { |
| 3945 | case ast::PatternKind::Case => { |
| 3946 | let patterns = [pat.pattern]; |
| 3947 | // Jump to `targetBlock` if the pattern matches, `failBlock` otherwise. |
| 3948 | try emitPatternMatches(self, subject, &patterns[..], targetBlock, failBlock); |
| 3949 | try switchToAndSeal(self, targetBlock); |
| 3950 | // Bind any variables inside the pattern. Nested patterns may |
| 3951 | // emit additional tests that branch to `failBlock`, switching |
| 3952 | // the current block. |
| 3953 | try bindPatternVariables(self, subject, &patterns[..], failBlock); |
| 3954 | } |
| 3955 | case ast::PatternKind::Binding => { |
| 3956 | // Jump to `targetBlock` if there is a value present, `failBlock` otherwise. |
| 3957 | try emitBindingTest(self, subject, targetBlock, failBlock); |
| 3958 | try switchToAndSeal(self, targetBlock); |
| 3959 | // Bind the matched value to the pattern variable. |
| 3960 | try bindMatchVariable(self, subject, pat.pattern, pat.mutable); |
| 3961 | } |
| 3962 | } |
| 3963 | // Handle guard: on success jump to `successBlock`, on failure jump to `failBlock`. |
| 3964 | if let g = pat.guard { |
| 3965 | try emitCondBranch(self, g, *successBlock, failBlock); |
| 3966 | try switchToAndSeal(self, *successBlock); |
| 3967 | } else if *currentBlock(self) <> *targetBlock { |
| 3968 | // Nested tests changed the current block. Create a new success block |
| 3969 | // after the nest blocks to maintain RPO ordering, and jump to it. |
| 3970 | set *successBlock = try createBlock(self, successLabel); |
| 3971 | |
| 3972 | try emitJmp(self, *successBlock); |
| 3973 | try switchToAndSeal(self, *successBlock); |
| 3974 | } |
| 3975 | } |
| 3976 | |
| 3977 | /// Lower a `let-else` statement. |
| 3978 | unsafe fn lowerLetElse 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, letElse: ast::LetElse) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 3979 | let subject = try lowerMatchSubject(self, letElse.pattern.scrutinee); |
| 3980 | let mut successBlock: BlockId = undefined; |
| 3981 | if letElse.pattern.guard == nil { |
| 3982 | set successBlock = try createBlock(self, "success"); |
| 3983 | } |
| 3984 | // The else branch executes when the pattern fails to match. |
| 3985 | let elseBlock = try createBlock(self, "else"); |
| 3986 | |
| 3987 | // Evaluate the pattern and jump to @success or @else. |
| 3988 | try lowerPatternMatch( |
| 3989 | self, |
| 3990 | &subject, |
| 3991 | &letElse.pattern, |
| 3992 | &mut successBlock, |
| 3993 | "success", |
| 3994 | elseBlock, |
| 3995 | ); |
| 3996 | let mut bindingVar: ?Var = nil; |
| 3997 | if let case ast::PatternKind::Binding = letElse.pattern.kind { |
| 3998 | set bindingVar = lookupLocalVar(self, letElse.pattern.pattern); |
| 3999 | } |
| 4000 | let mergeBlock = try createBlock(self, "merge"); |
| 4001 | try emitJmp(self, mergeBlock); |
| 4002 | |
| 4003 | try switchToAndSeal(self, elseBlock); |
| 4004 | if try typeOf(self, letElse.elseBranch) == resolver::Type::Never { |
| 4005 | try lowerNode(self, letElse.elseBranch); |
| 4006 | } else { |
| 4007 | let fallback = try lowerExpr(self, letElse.elseBranch); |
| 4008 | if let variable = bindingVar { |
| 4009 | defVar(self, variable, fallback); |
| 4010 | } |
| 4011 | try emitJmp(self, mergeBlock); |
| 4012 | } |
| 4013 | |
| 4014 | // Continue at @merge after a successful match or value-producing fallback. |
| 4015 | try switchToAndSeal(self, mergeBlock); |
| 4016 | } |
| 4017 | |
| 4018 | /// Lower a `while let` loop as a match-driven loop. |
| 4019 | unsafe fn lowerWhileLet 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, w: ast::WhileLet) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4020 | let savedVarsLen = enterVarScope(self); |
| 4021 | // Create control flow blocks: loop header, body (created lazily when |
| 4022 | // there's a guard), and exit. |
| 4023 | let whileBlock = try createBlock(self, "while"); |
| 4024 | let mut bodyBlock: BlockId = undefined; |
| 4025 | if w.pattern.guard == nil { |
| 4026 | set bodyBlock = try createBlock(self, "body"); |
| 4027 | } |
| 4028 | let endBlock = try createBlock(self, "merge"); |
| 4029 | |
| 4030 | // Enter loop context and jump to loop header. |
| 4031 | enterLoop(self, endBlock, whileBlock); |
| 4032 | try switchAndJumpTo(self, whileBlock); |
| 4033 | let subject = try lowerMatchSubject(self, w.pattern.scrutinee); |
| 4034 | |
| 4035 | // Evaluate pattern and jump to loop body or loop end. |
| 4036 | try lowerPatternMatch(self, &subject, &w.pattern, &mut bodyBlock, "body", endBlock); |
| 4037 | |
| 4038 | // Lower loop body, jump back to loop header, and exit loop context. |
| 4039 | try lowerBlock(self, w.body); |
| 4040 | try emitJmpAndSeal(self, whileBlock); |
| 4041 | |
| 4042 | exitLoop(self); |
| 4043 | try switchToAndSeal(self, endBlock); |
| 4044 | exitVarScope(self, savedVarsLen); |
| 4045 | } |
| 4046 | |
| 4047 | /////////////////// |
| 4048 | // Node Lowering // |
| 4049 | /////////////////// |
| 4050 | |
| 4051 | /// Lower an AST node. |
| 4052 | unsafe fn lowerNode 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4053 | if self.low.options.debug { |
| 4054 | set self.srcLoc.offset = node.span.offset; |
| 4055 | } |
| 4056 | match node.value { |
| 4057 | case ast::NodeValue::RegionBlock { bindings, body, .. } => { |
| 4058 | let savedVarsLen = enterVarScope(self); |
| 4059 | let values = try allocVals(self, bindings.len); |
| 4060 | for bindingNode, i in bindings { |
| 4061 | let case ast::NodeValue::RegionBinding(binding) = bindingNode.value |
| 4062 | else throw LowerError::ExpectedIdentifier; |
| 4063 | set values[i] = try lowerExpr(self, binding.value); |
| 4064 | if blockHasTerminator(self) { |
| 4065 | exitVarScope(self, savedVarsLen); |
| 4066 | return; |
| 4067 | } |
| 4068 | } |
| 4069 | for bindingNode, i in bindings { |
| 4070 | let case ast::NodeValue::RegionBinding(binding) = bindingNode.value |
| 4071 | else throw LowerError::ExpectedIdentifier; |
| 4072 | try bindLetValue(self, bindingNode, ast::borrowBinding(binding), values[i]); |
| 4073 | } |
| 4074 | try lowerBlock(self, body); |
| 4075 | exitVarScope(self, savedVarsLen); |
| 4076 | } |
| 4077 | case ast::NodeValue::Block(_) => { |
| 4078 | try lowerBlock(self, node); |
| 4079 | } |
| 4080 | case ast::NodeValue::Return { value } => { |
| 4081 | try lowerReturnStmt(self, node, value); |
| 4082 | } |
| 4083 | case ast::NodeValue::Throw { expr } => { |
| 4084 | try lowerThrowStmt(self, expr); |
| 4085 | } |
| 4086 | case ast::NodeValue::Let(l) => { |
| 4087 | try lowerLet(self, node, l); |
| 4088 | } |
| 4089 | case ast::NodeValue::ConstDecl(decl) => { |
| 4090 | // Local constants lower to data declarations and emit no runtime code. |
| 4091 | try registerLocalDataDeclName(self, node); |
| 4092 | try lowerDataDecl(self.low, node, decl.value, true); |
| 4093 | } |
| 4094 | case ast::NodeValue::StaticDecl(decl) => { |
| 4095 | // Local statics lower to data declarations and emit no runtime code. |
| 4096 | try registerLocalDataDeclName(self, node); |
| 4097 | try lowerDataDecl(self.low, node, decl.value, false); |
| 4098 | } |
| 4099 | case ast::NodeValue::If(i) => { |
| 4100 | try lowerIf(self, i); |
| 4101 | } |
| 4102 | case ast::NodeValue::IfLet(i) => { |
| 4103 | try lowerIfLet(self, i); |
| 4104 | } |
| 4105 | case ast::NodeValue::Assign(a) => { |
| 4106 | try lowerAssign(self, node, a); |
| 4107 | } |
| 4108 | case ast::NodeValue::Loop { body } => { |
| 4109 | try lowerLoop(self, body); |
| 4110 | } |
| 4111 | case ast::NodeValue::While(w) => { |
| 4112 | try lowerWhile(self, w); |
| 4113 | } |
| 4114 | case ast::NodeValue::WhileLet(w) => { |
| 4115 | try lowerWhileLet(self, w); |
| 4116 | } |
| 4117 | case ast::NodeValue::For(f) => { |
| 4118 | try lowerFor(self, node, f); |
| 4119 | } |
| 4120 | case ast::NodeValue::Break => { |
| 4121 | try lowerBreak(self); |
| 4122 | } |
| 4123 | case ast::NodeValue::Continue => { |
| 4124 | try lowerContinue(self); |
| 4125 | } |
| 4126 | case ast::NodeValue::Match(m) => { |
| 4127 | try lowerMatch(self, node, m); |
| 4128 | } |
| 4129 | case ast::NodeValue::LetElse(letElse) => { |
| 4130 | try lowerLetElse(self, letElse); |
| 4131 | } |
| 4132 | case ast::NodeValue::ExprStmt(expr) => { |
| 4133 | let _ = try lowerExpr(self, expr); |
| 4134 | } |
| 4135 | case ast::NodeValue::Panic { .. } => { |
| 4136 | emit(self, il::Instr::Unreachable); |
| 4137 | } |
| 4138 | case ast::NodeValue::Assert { condition, .. } => { |
| 4139 | // Lower `assert <cond>` as: `if not cond { unreachable; }`. |
| 4140 | let thenBlock = try createBlock(self, "assert.fail"); |
| 4141 | let endBlock = try createBlock(self, "assert.ok"); |
| 4142 | |
| 4143 | // Branch: if condition is `true`, go to `endBlock`; if `false`, go to `thenBlock`. |
| 4144 | try emitCondBranch(self, condition, endBlock, thenBlock); |
| 4145 | try sealBlock(self, thenBlock); |
| 4146 | |
| 4147 | // Emit `unreachable` in the failure block. |
| 4148 | switchToBlock(self, thenBlock); |
| 4149 | emit(self, il::Instr::Unreachable); |
| 4150 | |
| 4151 | // Continue after the assert. |
| 4152 | try switchToAndSeal(self, endBlock); |
| 4153 | } |
| 4154 | else => { |
| 4155 | // Treat as expression statement, discard result. |
| 4156 | let _ = try lowerExpr(self, node); |
| 4157 | } |
| 4158 | } |
| 4159 | } |
| 4160 | |
| 4161 | /// Lower a code block. |
| 4162 | unsafe fn lowerBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4163 | let case ast::NodeValue::Block(blk) = node.value else { |
| 4164 | throw LowerError::ExpectedBlock(node); |
| 4165 | }; |
| 4166 | let savedVarsLen = enterVarScope(self); |
| 4167 | for stmt in blk.statements { |
| 4168 | try lowerNode(self, stmt); |
| 4169 | |
| 4170 | // If the statement diverges, further statements are unreachable. |
| 4171 | if blockHasTerminator(self) { |
| 4172 | exitVarScope(self, savedVarsLen); |
| 4173 | return; |
| 4174 | } |
| 4175 | } |
| 4176 | exitVarScope(self, savedVarsLen); |
| 4177 | } |
| 4178 | |
| 4179 | /////////////////////////////////////// |
| 4180 | // Record and Aggregate Type Helpers // |
| 4181 | /////////////////////////////////////// |
| 4182 | |
| 4183 | /// Extract the nominal record info from a resolver type. |
| 4184 | unsafe fn recordInfoFromType(typ: resolver::Type) -> ?resolver::RecordType { |
| 4185 | let case resolver::Type::Nominal(resolver::NominalType::Record(recInfo)) = typ |
| 4186 | else return nil; |
| 4187 | |
| 4188 | return recInfo; |
| 4189 | } |
| 4190 | |
| 4191 | /// Extract the nominal union info from a resolver type. |
| 4192 | unsafe fn unionInfoFromType(typ: resolver::Type) -> ?resolver::UnionType { |
| 4193 | let case resolver::Type::Nominal(resolver::NominalType::Union(unionInfo)) = typ |
| 4194 | else return nil; |
| 4195 | |
| 4196 | return unionInfo; |
| 4197 | } |
| 4198 | |
| 4199 | /// Return the effective type of a node after any coercion applied by |
| 4200 | /// the resolver. `lowerExpr` already materializes the coercion in the |
| 4201 | /// IL value, so the lowerer must use the post-coercion type when |
| 4202 | /// choosing how to compare or store that value. |
| 4203 | unsafe fn effectiveType 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) -> resolver::Type throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4204 | let ty = try typeOf(self, node); |
| 4205 | if let coerce = resolver::coercionFor(self.low.resolver, node) { |
| 4206 | if let case resolver::Coercion::OptionalLift(optTy) = coerce { |
| 4207 | return optTy; |
| 4208 | } |
| 4209 | } |
| 4210 | return ty; |
| 4211 | } |
| 4212 | |
| 4213 | /// Check if a resolver type lowers to an aggregate in memory. |
| 4214 | unsafe fn isAggregateType(typ: resolver::Type) -> bool { |
| 4215 | match typ { |
| 4216 | case resolver::Type::Slice { .. }, |
| 4217 | resolver::Type::TraitObject { .. }, |
| 4218 | resolver::Type::Session(_) => return true, |
| 4219 | case resolver::Type::Optional(resolver::Type::Pointer { .. }) => { |
| 4220 | // Optional pointers are scalar due to NPO. |
| 4221 | return false; |
| 4222 | } |
| 4223 | case resolver::Type::Optional(_) => { |
| 4224 | // All other optionals, including optional slices, are aggregates. |
| 4225 | return true; |
| 4226 | } |
| 4227 | case resolver::Type::Nominal(_) => { |
| 4228 | // Void unions are small enough to pass by value. |
| 4229 | return not resolver::isVoidUnion(typ); |
| 4230 | } |
| 4231 | case resolver::Type::Array(_), |
| 4232 | resolver::Type::Nil => return true, |
| 4233 | else => return false, |
| 4234 | } |
| 4235 | } |
| 4236 | |
| 4237 | /// Check if a resolver type is a small aggregate that can be |
| 4238 | /// passed or returned by value in a register. |
| 4239 | unsafe fn isSmallAggregate(typ: resolver::Type) -> bool { |
| 4240 | match typ { |
| 4241 | case resolver::Type::Nominal(_) => { |
| 4242 | if resolver::isVoidUnion(typ) { |
| 4243 | return false; |
| 4244 | } |
| 4245 | let layout = resolver::getTypeLayout(typ); |
| 4246 | return layout.size <= resolver::PTR_SIZE; |
| 4247 | } |
| 4248 | else => return false, |
| 4249 | } |
| 4250 | } |
| 4251 | |
| 4252 | /// Whether a function needs a hidden return parameter. |
| 4253 | /// |
| 4254 | /// This is the case for throwing functions, which return a result aggregate, |
| 4255 | /// and for functions returning large aggregates that cannot be passed in |
| 4256 | /// registers. |
| 4257 | unsafe fn requiresReturnParam(fnType: *resolver::FnType) -> bool { |
| 4258 | return fnType.throwList.len > 0 |
| 4259 | or (isAggregateType(*fnType.returnType) |
| 4260 | and not isSmallAggregate(*fnType.returnType)); |
| 4261 | } |
| 4262 | |
| 4263 | /// Check if a node is a void union variant literal (e.g. `Color::Red`). |
| 4264 | /// If so, returns the variant's tag index. This enables optimized comparisons |
| 4265 | /// that only check the tag instead of doing full aggregate comparison. |
| 4266 | unsafe fn voidVariantIndex 'arena (res: &resolver::Resolver 'arena, node: *ast::Node) -> ?i64 { |
| 4267 | let data = resolver::nodeData(res, node); |
| 4268 | // Optional equality checks both the presence tag and the union value. |
| 4269 | if let case resolver::Coercion::OptionalLift(_) = data.coercion { |
| 4270 | return nil; |
| 4271 | } |
| 4272 | let sym = data.sym else { |
| 4273 | return nil; |
| 4274 | }; |
| 4275 | let case resolver::SymbolData::Variant { type: payloadType, index, .. } = sym.data else { |
| 4276 | return nil; |
| 4277 | }; |
| 4278 | // Only void variants can use tag-only comparison. |
| 4279 | if payloadType <> resolver::Type::Void { |
| 4280 | return nil; |
| 4281 | } |
| 4282 | return index as i64; |
| 4283 | } |
| 4284 | |
| 4285 | /// Reserve stack storage for a value of the given type. |
| 4286 | unsafe fn emitReserve 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, typ: resolver::Type) -> il::Reg throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4287 | let layout = resolver::getTypeLayout(typ); |
| 4288 | return emitReserveLayout(self, layout); |
| 4289 | } |
| 4290 | |
| 4291 | /// Reserve stack storage with an explicit layout. |
| 4292 | unsafe fn emitReserveLayout 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, layout: resolver::Layout) -> il::Reg where 'arena: 'phase, 'phase: 'function { |
| 4293 | let dst = nextReg(self); |
| 4294 | |
| 4295 | emit(self, il::Instr::Reserve { |
| 4296 | dst, |
| 4297 | size: il::Val::Imm(layout.size as i64), |
| 4298 | alignment: layout.alignment, |
| 4299 | }); |
| 4300 | return dst; |
| 4301 | } |
| 4302 | |
| 4303 | /// Store a value into an address. |
| 4304 | unsafe 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 { |
| 4305 | // `undefined` values need no store. |
| 4306 | if let case il::Val::Undef = src { |
| 4307 | return; |
| 4308 | } |
| 4309 | if isAggregateType(typ) { |
| 4310 | let dst = emitPtrOffset(self, base, offset); |
| 4311 | let src = emitValToReg(self, src); |
| 4312 | let layout = resolver::getTypeLayout(typ); |
| 4313 | |
| 4314 | emit(self, il::Instr::Blit { dst, src, size: il::Val::Imm(layout.size as i64) }); |
| 4315 | } else { |
| 4316 | emit(self, il::Instr::Store { |
| 4317 | typ: ilType(self.low, typ), |
| 4318 | src, |
| 4319 | dst: base, |
| 4320 | offset, |
| 4321 | }); |
| 4322 | } |
| 4323 | } |
| 4324 | |
| 4325 | /// Allocate stack space for a value and store it. Returns a pointer to the value. |
| 4326 | unsafe 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 { |
| 4327 | let ptr = try emitReserve(self, typ); |
| 4328 | try emitStore(self, ptr, 0, typ, val); |
| 4329 | return il::Val::Reg(ptr); |
| 4330 | } |
| 4331 | |
| 4332 | /// Generic helper to build any tagged aggregate. |
| 4333 | /// Reserves space based on the provided layout, stores the tag, and optionally |
| 4334 | /// stores the payload value at `valOffset`. |
| 4335 | unsafe fn buildTagged 'arena 'phase 'function ( |
| 4336 | self: &mut FnLowerer 'arena 'phase 'function, |
| 4337 | layout: resolver::Layout, |
| 4338 | tag: i64, |
| 4339 | payload: ?il::Val, |
| 4340 | payloadType: resolver::Type, |
| 4341 | tagSize: u32, |
| 4342 | valOffset: i32 |
| 4343 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4344 | let dst = nextReg(self); |
| 4345 | emit(self, il::Instr::Reserve { |
| 4346 | dst, |
| 4347 | size: il::Val::Imm(layout.size as i64), |
| 4348 | alignment: layout.alignment, |
| 4349 | }); |
| 4350 | if tagSize == 1 { |
| 4351 | emitStoreW8At(self, il::Val::Imm(tag), dst, TVAL_TAG_OFFSET); |
| 4352 | } else { |
| 4353 | emitStoreW64At(self, il::Val::Imm(tag), dst, TVAL_TAG_OFFSET); |
| 4354 | } |
| 4355 | |
| 4356 | if let val = payload { |
| 4357 | if payloadType <> resolver::Type::Void { |
| 4358 | try emitStore(self, dst, valOffset, payloadType, val); |
| 4359 | } |
| 4360 | } |
| 4361 | return il::Val::Reg(dst); |
| 4362 | } |
| 4363 | |
| 4364 | /// Wrap a value in an optional type. |
| 4365 | /// |
| 4366 | /// For optional pointers (`?*T`), the value is returned as-is since pointers |
| 4367 | /// use zero to represent `nil`. For other optionals, builds a tagged aggregate. |
| 4368 | /// with the tag set to `1`, and the value as payload. |
| 4369 | unsafe 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 { |
| 4370 | let case resolver::Type::Optional(inner) = optType else { |
| 4371 | throw LowerError::ExpectedOptional; |
| 4372 | }; |
| 4373 | // Null-pointer-optimized (NPO) types are used as-is -- valid values are never null. |
| 4374 | if resolver::isNullableType(*inner) { |
| 4375 | return val; |
| 4376 | } |
| 4377 | let layout = resolver::getTypeLayout(optType); |
| 4378 | let valOffset = resolver::getOptionalValOffset(*inner) as i32; |
| 4379 | |
| 4380 | return try buildTagged(self, layout, 1, val, *inner, 1, valOffset); |
| 4381 | } |
| 4382 | |
| 4383 | /// Build a `nil` value for an optional type. |
| 4384 | /// |
| 4385 | /// For optional pointers (`?*T`), returns an immediate `0` (null pointer). |
| 4386 | /// For other optionals, builds a tagged aggregate with tag set to `0` (absent). |
| 4387 | unsafe fn buildNilOptional 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, optType: resolver::Type) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4388 | let case resolver::Type::Optional(inner) = optType |
| 4389 | else throw LowerError::ExpectedOptional; |
| 4390 | if let case resolver::Type::Pointer { .. } = *inner { |
| 4391 | return il::Val::Imm(0); |
| 4392 | } |
| 4393 | if let case resolver::Type::Slice { item, mutable, .. } = *inner { |
| 4394 | return try buildSliceValue( |
| 4395 | self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0) |
| 4396 | ); |
| 4397 | } |
| 4398 | let valOffset = resolver::getOptionalValOffset(*inner) as i32; |
| 4399 | return try buildTagged(self, resolver::getTypeLayout(optType), 0, nil, *inner, 1, valOffset); |
| 4400 | } |
| 4401 | |
| 4402 | /// Build a result value for throwing functions. |
| 4403 | unsafe fn buildResult 'arena 'phase 'function ( |
| 4404 | self: &mut FnLowerer 'arena 'phase 'function, |
| 4405 | tag: i64, |
| 4406 | payload: ?il::Val, |
| 4407 | payloadType: resolver::Type |
| 4408 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4409 | let successType = *self.fnType.returnType; |
| 4410 | let layout = resolver::getResultLayout( |
| 4411 | successType, self.fnType.throwList |
| 4412 | ); |
| 4413 | return try buildTagged(self, layout, tag, payload, payloadType, resolver::PTR_SIZE as i32, RESULT_VAL_OFFSET); |
| 4414 | } |
| 4415 | |
| 4416 | /// Build a slice aggregate from a data pointer, length and capacity. |
| 4417 | unsafe fn buildSliceValue 'arena 'phase 'function ( |
| 4418 | self: &mut FnLowerer 'arena 'phase 'function, |
| 4419 | elemTy: *resolver::Type, |
| 4420 | mutable: bool, |
| 4421 | ptrVal: il::Val, |
| 4422 | lenVal: il::Val, |
| 4423 | capVal: il::Val |
| 4424 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4425 | let sliceType = resolver::Type::Slice { |
| 4426 | class: types::PointerClass::Unsafe, |
| 4427 | item: elemTy, |
| 4428 | mutable, |
| 4429 | }; |
| 4430 | let dst = try emitReserve(self, sliceType); |
| 4431 | let ptrTy = resolver::Type::Pointer { |
| 4432 | class: types::PointerClass::Unsafe, |
| 4433 | target: elemTy, |
| 4434 | mutable, |
| 4435 | }; |
| 4436 | |
| 4437 | try emitStore(self, dst, SLICE_PTR_OFFSET, ptrTy, ptrVal); |
| 4438 | try emitStore(self, dst, SLICE_LEN_OFFSET, resolver::Type::U32, lenVal); |
| 4439 | try emitStore(self, dst, SLICE_CAP_OFFSET, resolver::Type::U32, capVal); |
| 4440 | |
| 4441 | return il::Val::Reg(dst); |
| 4442 | } |
| 4443 | |
| 4444 | /// Build a trait object fat pointer from a data pointer and a v-table. |
| 4445 | unsafe fn buildTraitObject 'arena 'phase 'function ( |
| 4446 | self: &mut FnLowerer 'arena 'phase 'function, |
| 4447 | dataVal: il::Val, |
| 4448 | traitInfo: *unsafe resolver::TraitType, |
| 4449 | inst: &resolver::InstanceEntry |
| 4450 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4451 | let vName = vtableName(self.low, inst.moduleId, inst.concreteTypeName, traitInfo.name); |
| 4452 | |
| 4453 | // Reserve space for the trait object on the stack. |
| 4454 | let slot = emitReserveLayout(self, resolver::Layout { |
| 4455 | size: resolver::PTR_SIZE * 2, |
| 4456 | alignment: resolver::PTR_SIZE, |
| 4457 | }); |
| 4458 | |
| 4459 | // Store data pointer. |
| 4460 | emit(self, il::Instr::Store { |
| 4461 | typ: il::Type::W64, |
| 4462 | src: dataVal, |
| 4463 | dst: slot, |
| 4464 | offset: TRAIT_OBJ_DATA_OFFSET, |
| 4465 | }); |
| 4466 | |
| 4467 | // Store v-table address. |
| 4468 | emit(self, il::Instr::Store { |
| 4469 | typ: il::Type::W64, |
| 4470 | src: il::Val::DataSym(vName), |
| 4471 | dst: slot, |
| 4472 | offset: TRAIT_OBJ_VTABLE_OFFSET, |
| 4473 | }); |
| 4474 | return il::Val::Reg(slot); |
| 4475 | } |
| 4476 | |
| 4477 | /// Compute a field pointer by adding a byte offset to a base address. |
| 4478 | unsafe fn emitPtrOffset 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, base: il::Reg, offset: i32) -> il::Reg where 'arena: 'phase, 'phase: 'function { |
| 4479 | if offset == 0 { |
| 4480 | return base; |
| 4481 | } |
| 4482 | let dst = nextReg(self); |
| 4483 | |
| 4484 | emit(self, il::Instr::BinOp { |
| 4485 | op: il::BinOp::Add, |
| 4486 | typ: il::Type::W64, |
| 4487 | dst, |
| 4488 | a: il::Val::Reg(base), |
| 4489 | b: il::Val::Imm(offset as i64), |
| 4490 | }); |
| 4491 | return dst; |
| 4492 | } |
| 4493 | |
| 4494 | /// Emit an element address computation for array/slice indexing. |
| 4495 | /// Computes: `base + idx * stride`. |
| 4496 | unsafe 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 { |
| 4497 | // If index is zero, return base directly. |
| 4498 | if idx == il::Val::Imm(0) { |
| 4499 | return base; |
| 4500 | } |
| 4501 | // If stride is `1`, skip the multiply. |
| 4502 | if stride == 1 { |
| 4503 | let dst = nextReg(self); |
| 4504 | |
| 4505 | emit(self, il::Instr::BinOp { |
| 4506 | op: il::BinOp::Add, |
| 4507 | typ: il::Type::W64, |
| 4508 | dst, |
| 4509 | a: il::Val::Reg(base), |
| 4510 | b: idx |
| 4511 | }); |
| 4512 | return dst; |
| 4513 | } |
| 4514 | // Compute `offset = idx * stride`. |
| 4515 | let offset = nextReg(self); |
| 4516 | |
| 4517 | emit(self, il::Instr::BinOp { |
| 4518 | op: il::BinOp::Mul, |
| 4519 | typ: il::Type::W64, |
| 4520 | dst: offset, |
| 4521 | a: idx, |
| 4522 | b: il::Val::Imm(stride as i64) |
| 4523 | }); |
| 4524 | // Compute `dst = base + offset`. |
| 4525 | let dst = nextReg(self); |
| 4526 | emit(self, il::Instr::BinOp { |
| 4527 | op: il::BinOp::Add, |
| 4528 | typ: il::Type::W64, |
| 4529 | dst, |
| 4530 | a: il::Val::Reg(base), |
| 4531 | b: il::Val::Reg(offset) |
| 4532 | }); |
| 4533 | return dst; |
| 4534 | } |
| 4535 | |
| 4536 | /// Emit a typed binary operation, returning the result as a value. |
| 4537 | unsafe 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 { |
| 4538 | let dst = nextReg(self); |
| 4539 | emit(self, il::Instr::BinOp { op, typ, dst, a, b }); |
| 4540 | return il::Val::Reg(dst); |
| 4541 | } |
| 4542 | |
| 4543 | /// Emit a tag comparison for void variant equality/inequality. |
| 4544 | 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 |
| 4545 | throws (LowerError) where 'arena: 'phase, 'phase: 'function |
| 4546 | { |
| 4547 | let reg = emitValToReg(self, val); |
| 4548 | |
| 4549 | // For all-void unions, the value *is* the tag, not a pointer. |
| 4550 | let mut tag: il::Val = undefined; |
| 4551 | if resolver::isVoidUnion(valType) { |
| 4552 | set tag = il::Val::Reg(reg); |
| 4553 | } else { |
| 4554 | set tag = loadTag(self, reg, TVAL_TAG_OFFSET, il::Type::W8); |
| 4555 | } |
| 4556 | let binOp = il::BinOp::Eq if op == ast::BinaryOp::Eq else il::BinOp::Ne; |
| 4557 | return emitTypedBinOp(self, binOp, il::Type::W8, tag, il::Val::Imm(tagIdx)); |
| 4558 | } |
| 4559 | |
| 4560 | /// Logical "and" between two values. Returns the result in a register. |
| 4561 | unsafe fn emitLogicalAnd 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, left: ?il::Val, right: il::Val) -> il::Val where 'arena: 'phase, 'phase: 'function { |
| 4562 | let prev = left else { |
| 4563 | return right; |
| 4564 | }; |
| 4565 | return emitTypedBinOp(self, il::BinOp::And, il::Type::W32, prev, right); |
| 4566 | } |
| 4567 | |
| 4568 | ////////////////////////// |
| 4569 | // Aggregate Comparison // |
| 4570 | ////////////////////////// |
| 4571 | |
| 4572 | /// Emit an equality test for values at an offset of the given base registers. |
| 4573 | unsafe fn emitEqAtOffset 'arena 'phase 'function ( |
| 4574 | self: &mut FnLowerer 'arena 'phase 'function, |
| 4575 | left: il::Reg, |
| 4576 | right: il::Reg, |
| 4577 | offset: i32, |
| 4578 | fieldType: resolver::Type |
| 4579 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4580 | // For aggregate types, pass offset through and compare recursively. |
| 4581 | if isAggregateType(fieldType) { |
| 4582 | return try lowerAggregateEq(self, fieldType, left, right, offset); |
| 4583 | } |
| 4584 | // For scalar types, load and compare directly. |
| 4585 | let a = emitLoad(self, left, offset, fieldType); |
| 4586 | let b = emitLoad(self, right, offset, fieldType); |
| 4587 | let dst = nextReg(self); |
| 4588 | emit(self, il::Instr::BinOp { op: il::BinOp::Eq, typ: ilType(self.low, fieldType), dst, a, b }); |
| 4589 | |
| 4590 | return il::Val::Reg(dst); |
| 4591 | } |
| 4592 | |
| 4593 | /// Compare two record values for equality. |
| 4594 | unsafe fn lowerRecordEq 'arena 'phase 'function ( |
| 4595 | self: &mut FnLowerer 'arena 'phase 'function, |
| 4596 | recInfo: resolver::RecordType, |
| 4597 | a: il::Reg, |
| 4598 | b: il::Reg, |
| 4599 | offset: i32 |
| 4600 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4601 | let mut result: ?il::Val = nil; |
| 4602 | |
| 4603 | for field in recInfo.fields { |
| 4604 | let cmp = try emitEqAtOffset(self, a, b, offset + field.offset, field.fieldType); |
| 4605 | |
| 4606 | set result = emitLogicalAnd(self, result, cmp); |
| 4607 | } |
| 4608 | if let r = result { |
| 4609 | return r; |
| 4610 | } |
| 4611 | return il::Val::Imm(1); |
| 4612 | } |
| 4613 | |
| 4614 | /// Compare two slice values for equality. |
| 4615 | unsafe fn lowerSliceEq 'arena 'phase 'function ( |
| 4616 | self: &mut FnLowerer 'arena 'phase 'function, |
| 4617 | elemTy: *resolver::Type, |
| 4618 | mutable: bool, |
| 4619 | a: il::Reg, |
| 4620 | b: il::Reg, |
| 4621 | offset: i32 |
| 4622 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4623 | let ptrTy = resolver::Type::Pointer { |
| 4624 | class: types::PointerClass::Unsafe, |
| 4625 | target: elemTy, |
| 4626 | mutable, |
| 4627 | }; |
| 4628 | let ptrEq = try emitEqAtOffset(self, a, b, offset + SLICE_PTR_OFFSET, ptrTy); |
| 4629 | let lenEq = try emitEqAtOffset(self, a, b, offset + SLICE_LEN_OFFSET, resolver::Type::U32); |
| 4630 | |
| 4631 | return emitTypedBinOp(self, il::BinOp::And, il::Type::W32, ptrEq, lenEq); |
| 4632 | } |
| 4633 | |
| 4634 | /// Compare two optional aggregate values for equality. |
| 4635 | /// |
| 4636 | /// Two optionals are equal when their tags match and either both are `nil` or |
| 4637 | /// their payloads are equal. |
| 4638 | /// |
| 4639 | /// For inner types that are safe to compare even when uninitialised, we use a |
| 4640 | /// branchless formulation: `tagEq AND (tagNil OR payloadEq)` |
| 4641 | /// |
| 4642 | /// For inner types that may contain uninitialized data when `nil` (unions, |
| 4643 | /// nested optionals), the payload comparison is guarded behind a branch |
| 4644 | /// so that `nil` payloads are never inspected. |
| 4645 | unsafe fn lowerOptionalEq 'arena 'phase 'function ( |
| 4646 | self: &mut FnLowerer 'arena 'phase 'function, |
| 4647 | inner: resolver::Type, |
| 4648 | a: il::Reg, |
| 4649 | b: il::Reg, |
| 4650 | offset: i32 |
| 4651 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4652 | let valOffset = resolver::getOptionalValOffset(inner) as i32; |
| 4653 | |
| 4654 | // Load tags. |
| 4655 | let tagA = loadTag(self, a, offset + TVAL_TAG_OFFSET, il::Type::W8); |
| 4656 | let tagB = loadTag(self, b, offset + TVAL_TAG_OFFSET, il::Type::W8); |
| 4657 | |
| 4658 | // For simple inner types (no unions/nested optionals), use branchless comparison. |
| 4659 | // Unions and nested optionals may contain uninitialized payload bytes |
| 4660 | // when nil, so they need a guarded comparison. |
| 4661 | let isUnion = unionInfoFromType(inner) <> nil; |
| 4662 | let mut isOptional = false; |
| 4663 | if let case resolver::Type::Optional(_) = inner { |
| 4664 | set isOptional = true; |
| 4665 | } |
| 4666 | if not isUnion and not isOptional { |
| 4667 | let tagEq = emitTypedBinOp(self, il::BinOp::Eq, il::Type::W8, tagA, tagB); |
| 4668 | let tagNil = emitTypedBinOp(self, il::BinOp::Eq, il::Type::W8, tagA, il::Val::Imm(0)); |
| 4669 | let payloadEq = try emitEqAtOffset(self, a, b, offset + valOffset, inner); |
| 4670 | |
| 4671 | return emitTypedBinOp(self, il::BinOp::And, il::Type::W32, tagEq, |
| 4672 | emitTypedBinOp(self, il::BinOp::Or, il::Type::W32, tagNil, payloadEq)); |
| 4673 | } |
| 4674 | |
| 4675 | // For complex inner types, use branching comparison to avoid inspecting |
| 4676 | // uninitialized payload bytes. |
| 4677 | let resultReg = nextReg(self); |
| 4678 | let mergeBlock = try createBlockWithParam(self, "opteq#merge", il::Param { |
| 4679 | value: resultReg, type: il::Type::W8 |
| 4680 | }); |
| 4681 | let nilCheck = try createBlock(self, "opteq#nil"); |
| 4682 | let payloadCmp = try createBlock(self, "opteq#payload"); |
| 4683 | |
| 4684 | let falseArgs = try allocVal(self, il::Val::Imm(0)); |
| 4685 | let trueArgs = try allocVal(self, il::Val::Imm(1)); |
| 4686 | |
| 4687 | // Check if tags differ. |
| 4688 | emit(self, il::Instr::Br { |
| 4689 | op: il::CmpOp::Eq, typ: il::Type::W8, a: tagA, b: tagB, |
| 4690 | thenTarget: *nilCheck, thenArgs: &mut [], |
| 4691 | elseTarget: *mergeBlock, elseArgs: falseArgs, |
| 4692 | }); |
| 4693 | addPredecessor(self, nilCheck, currentBlock(self)); |
| 4694 | addPredecessor(self, mergeBlock, currentBlock(self)); |
| 4695 | |
| 4696 | // Check if both are `nil`. |
| 4697 | try switchToAndSeal(self, nilCheck); |
| 4698 | emit(self, il::Instr::Br { |
| 4699 | op: il::CmpOp::Ne, typ: il::Type::W8, a: tagA, b: il::Val::Imm(0), |
| 4700 | thenTarget: *payloadCmp, thenArgs: &mut [], |
| 4701 | elseTarget: *mergeBlock, elseArgs: trueArgs, |
| 4702 | }); |
| 4703 | addPredecessor(self, payloadCmp, currentBlock(self)); |
| 4704 | addPredecessor(self, mergeBlock, currentBlock(self)); |
| 4705 | |
| 4706 | // Both are non-`nil`, compare payloads. |
| 4707 | try switchToAndSeal(self, payloadCmp); |
| 4708 | let payloadEq = try emitEqAtOffset(self, a, b, offset + valOffset, inner); |
| 4709 | try emitJmpWithArg(self, mergeBlock, payloadEq); |
| 4710 | try switchToAndSeal(self, mergeBlock); |
| 4711 | |
| 4712 | return il::Val::Reg(resultReg); |
| 4713 | } |
| 4714 | |
| 4715 | /// Compare two union values for equality. |
| 4716 | /// |
| 4717 | /// Two unions are equal iff their tags match and, for non-void variants, |
| 4718 | /// their payloads are also equal. The comparison proceeds as follows. |
| 4719 | /// |
| 4720 | /// First, compare the tags. If they differ, the unions are not equal, so |
| 4721 | /// jump to the merge block with `false`. If they match, jump to the tag |
| 4722 | /// block to determine which variant we're dealing with. |
| 4723 | /// |
| 4724 | /// The tag block uses a switch on the tag value to dispatch to the appropriate |
| 4725 | /// comparison block. Void variants jump directly to merge with `true`. |
| 4726 | /// Non-void variants each have their own payload block that compares the |
| 4727 | /// payload and jumps to the merge block with the result. |
| 4728 | /// |
| 4729 | /// The merge block collects results from all paths via a block parameter |
| 4730 | /// and returns the final equality result. |
| 4731 | /// |
| 4732 | /// For all-void unions, we skip the control flow entirely and just compare |
| 4733 | /// the tags directly. |
| 4734 | /// |
| 4735 | /// TODO: Could be optimized to branchless when all non-void variants share |
| 4736 | /// the same payload type: `tagEq AND (isVoidVariant OR payloadEq)`. |
| 4737 | unsafe fn lowerUnionEq 'arena 'phase 'function ( |
| 4738 | self: &mut FnLowerer 'arena 'phase 'function, |
| 4739 | unionInfo: resolver::UnionType, |
| 4740 | a: il::Reg, |
| 4741 | b: il::Reg, |
| 4742 | offset: i32 |
| 4743 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4744 | // Compare tags. |
| 4745 | let tagA = loadTag(self, a, offset + TVAL_TAG_OFFSET, il::Type::W8); |
| 4746 | let tagB = loadTag(self, b, offset + TVAL_TAG_OFFSET, il::Type::W8); |
| 4747 | |
| 4748 | // Fast path: all-void union just needs tag comparison. |
| 4749 | if unionInfo.isAllVoid { |
| 4750 | return emitTypedBinOp(self, il::BinOp::Eq, il::Type::W8, tagA, tagB); |
| 4751 | } |
| 4752 | // Holds the equality result. |
| 4753 | let resultReg = nextReg(self); |
| 4754 | |
| 4755 | // Where control flow continues after equality check is done. Receives |
| 4756 | // the result as a parameter. |
| 4757 | let mergeBlock = try createBlockWithParam(self, "eq#merge", il::Param { |
| 4758 | value: resultReg, type: il::Type::W8 |
| 4759 | }); |
| 4760 | // Where we switch on the tag to compare payloads. |
| 4761 | let tagBlock = try createBlock(self, "eq#tag"); |
| 4762 | |
| 4763 | // Compare tags: if they differ, jump to merge with `false`; otherwise check payloads. |
| 4764 | let falseArgs = try allocVal(self, il::Val::Imm(0)); |
| 4765 | |
| 4766 | assert tagBlock <> mergeBlock; |
| 4767 | |
| 4768 | // TODO: Use the helper once the compiler supports more than eight function params. |
| 4769 | emit(self, il::Instr::Br { |
| 4770 | op: il::CmpOp::Eq, typ: il::Type::W8, a: tagA, b: tagB, |
| 4771 | thenTarget: *tagBlock, thenArgs: &mut [], |
| 4772 | elseTarget: *mergeBlock, elseArgs: falseArgs, |
| 4773 | }); |
| 4774 | addPredecessor(self, tagBlock, currentBlock(self)); |
| 4775 | addPredecessor(self, mergeBlock, currentBlock(self)); |
| 4776 | |
| 4777 | // Create comparison blocks for each non-void variant and build switch cases. |
| 4778 | // Void variants jump directly to merge with `true`. |
| 4779 | let trueArgs = try allocVal(self, il::Val::Imm(1)); |
| 4780 | let cases = try! alloc::allocRawSlice( |
| 4781 | self.arena, @sizeOf(il::SwitchCase), @alignOf(il::SwitchCase), unionInfo.variants.len as u32 |
| 4782 | ) as *unsafe mut [il::SwitchCase]; |
| 4783 | |
| 4784 | let mut caseBlocks: [?BlockId; resolver::MAX_UNION_VARIANTS] = undefined; |
| 4785 | for variant, i in unionInfo.variants { |
| 4786 | if variant.valueType == resolver::Type::Void { |
| 4787 | set cases[i] = il::SwitchCase { |
| 4788 | value: i as i64, |
| 4789 | target: *mergeBlock, |
| 4790 | args: trueArgs |
| 4791 | }; |
| 4792 | set caseBlocks[i] = nil; |
| 4793 | } else { |
| 4794 | let payloadBlock = try createBlock(self, "eq#payload"); |
| 4795 | set cases[i] = il::SwitchCase { |
| 4796 | value: i as i64, |
| 4797 | target: *payloadBlock, |
| 4798 | args: &mut [] |
| 4799 | }; |
| 4800 | set caseBlocks[i] = payloadBlock; |
| 4801 | } |
| 4802 | } |
| 4803 | |
| 4804 | // Emit switch in @tag block. Default arm is unreachable since we cover all variants. |
| 4805 | let unreachableBlock = try createBlock(self, "eq#unreachable"); |
| 4806 | try switchToAndSeal(self, tagBlock); |
| 4807 | emit(self, il::Instr::Switch { |
| 4808 | val: tagA, |
| 4809 | defaultTarget: *unreachableBlock, |
| 4810 | defaultArgs: &mut [], |
| 4811 | cases |
| 4812 | }); |
| 4813 | |
| 4814 | // Add predecessor edges for switch targets. |
| 4815 | addPredecessor(self, unreachableBlock, tagBlock); |
| 4816 | for i in 0..unionInfo.variants.len { |
| 4817 | if let caseBlock = caseBlocks[i] { |
| 4818 | addPredecessor(self, caseBlock, tagBlock); |
| 4819 | } else { |
| 4820 | addPredecessor(self, mergeBlock, tagBlock); |
| 4821 | } |
| 4822 | } |
| 4823 | let valOffset = unionInfo.valOffset as i32; |
| 4824 | |
| 4825 | // Emit payload comparison blocks for non-void variants. |
| 4826 | for variant, i in unionInfo.variants { |
| 4827 | if let caseBlock = caseBlocks[i] { |
| 4828 | try switchToAndSeal(self, caseBlock); |
| 4829 | let payloadEq = try emitEqAtOffset( |
| 4830 | self, a, b, offset + valOffset, variant.valueType |
| 4831 | ); |
| 4832 | try emitJmpWithArg(self, mergeBlock, payloadEq); |
| 4833 | } |
| 4834 | } |
| 4835 | // Emit unreachable block. |
| 4836 | try switchToAndSeal(self, unreachableBlock); |
| 4837 | emit(self, il::Instr::Unreachable); |
| 4838 | |
| 4839 | try switchToAndSeal(self, mergeBlock); |
| 4840 | return il::Val::Reg(resultReg); |
| 4841 | } |
| 4842 | |
| 4843 | /// Compare two array values for equality, element by element. |
| 4844 | unsafe fn lowerArrayEq 'arena 'phase 'function ( |
| 4845 | self: &mut FnLowerer 'arena 'phase 'function, |
| 4846 | arr: resolver::ArrayType, |
| 4847 | a: il::Reg, |
| 4848 | b: il::Reg, |
| 4849 | offset: i32 |
| 4850 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4851 | let elemLayout = resolver::getTypeLayout(*arr.item); |
| 4852 | let stride = elemLayout.size as i32; |
| 4853 | let mut result: ?il::Val = nil; |
| 4854 | |
| 4855 | for i in 0..arr.length { |
| 4856 | let elemOffset = offset + (i as i32) * stride; |
| 4857 | let cmp = try emitEqAtOffset(self, a, b, elemOffset, *arr.item); |
| 4858 | set result = emitLogicalAnd(self, result, cmp); |
| 4859 | } |
| 4860 | if let r = result { |
| 4861 | return r; |
| 4862 | } |
| 4863 | // Empty arrays are always equal. |
| 4864 | return il::Val::Imm(1); |
| 4865 | } |
| 4866 | |
| 4867 | /// Compare two aggregate values for equality. |
| 4868 | unsafe fn lowerAggregateEq 'arena 'phase 'function ( |
| 4869 | self: &mut FnLowerer 'arena 'phase 'function, |
| 4870 | typ: resolver::Type, |
| 4871 | a: il::Reg, |
| 4872 | b: il::Reg, |
| 4873 | offset: i32 |
| 4874 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4875 | match typ { |
| 4876 | case resolver::Type::Slice { item, mutable, .. } => |
| 4877 | return try lowerSliceEq(self, item, mutable, a, b, offset), |
| 4878 | case resolver::Type::Optional(inner) => { |
| 4879 | if let case resolver::Type::Slice { item, mutable, .. } = *inner { |
| 4880 | // Optional slices use null pointer optimization. |
| 4881 | return try lowerSliceEq(self, item, mutable, a, b, offset); |
| 4882 | } |
| 4883 | return try lowerOptionalEq(self, *inner, a, b, offset); |
| 4884 | } |
| 4885 | case resolver::Type::Array(arr) => |
| 4886 | return try lowerArrayEq(self, arr, a, b, offset), |
| 4887 | case resolver::Type::Nominal(resolver::NominalType::Record(recInfo)) => |
| 4888 | return try lowerRecordEq(self, recInfo, a, b, offset), |
| 4889 | case resolver::Type::Nominal(resolver::NominalType::Union(unionInfo)) => |
| 4890 | return try lowerUnionEq(self, unionInfo, a, b, offset), |
| 4891 | else => { |
| 4892 | let recInfo = recordInfoFromType(typ) else { |
| 4893 | throw LowerError::ExpectedRecord; |
| 4894 | }; |
| 4895 | return try lowerRecordEq(self, recInfo, a, b, offset); |
| 4896 | } |
| 4897 | } |
| 4898 | } |
| 4899 | |
| 4900 | /// Lower a record literal expression. Handles both plain records and union variant |
| 4901 | /// record literals like `Union::Variant { field: value }`. |
| 4902 | 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 { |
| 4903 | let typ = try typeOf(self, node); |
| 4904 | match typ { |
| 4905 | case resolver::Type::Nominal(resolver::NominalType::Record(recInfo)) => { |
| 4906 | let dst = try emitReserve(self, typ); |
| 4907 | try lowerRecordFields(self, dst, &recInfo, lit.fields, 0); |
| 4908 | |
| 4909 | return il::Val::Reg(dst); |
| 4910 | } |
| 4911 | case resolver::Type::Nominal(resolver::NominalType::Union(_)) => { |
| 4912 | let typeName = lit.typeName else { |
| 4913 | throw LowerError::ExpectedVariant; |
| 4914 | }; |
| 4915 | let sym = try symOf(self, typeName); |
| 4916 | let case resolver::SymbolData::Variant { type: payloadType, index, .. } = sym.data else { |
| 4917 | throw LowerError::ExpectedVariant; |
| 4918 | }; |
| 4919 | let recInfo = recordInfoFromType(payloadType) else { |
| 4920 | throw LowerError::ExpectedRecord; |
| 4921 | }; |
| 4922 | let unionInfo = unionInfoFromType(typ) else { |
| 4923 | throw LowerError::MissingMetadata; |
| 4924 | }; |
| 4925 | let valOffset = unionInfo.valOffset as i32; |
| 4926 | let dst = try emitReserve(self, typ); |
| 4927 | |
| 4928 | emitStoreW8At(self, il::Val::Imm(index as i64), dst, TVAL_TAG_OFFSET); |
| 4929 | try lowerRecordFields(self, dst, &recInfo, lit.fields, valOffset); |
| 4930 | |
| 4931 | return il::Val::Reg(dst); |
| 4932 | } |
| 4933 | else => throw LowerError::UnexpectedType(typ), |
| 4934 | } |
| 4935 | } |
| 4936 | |
| 4937 | /// Lower fields of a record literal into a destination register. |
| 4938 | /// The `offset` is added to each field's offset when storing. |
| 4939 | unsafe fn lowerRecordFields 'arena 'phase 'function ( |
| 4940 | self: &mut FnLowerer 'arena 'phase 'function, |
| 4941 | dst: il::Reg, |
| 4942 | recInfo: &resolver::RecordType, |
| 4943 | fields: *[*ast::Node], |
| 4944 | offset: i32 |
| 4945 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 4946 | for fieldNode, i in fields { |
| 4947 | let case ast::NodeValue::RecordLitField(field) = fieldNode.value else { |
| 4948 | throw LowerError::UnexpectedNodeValue(fieldNode); |
| 4949 | }; |
| 4950 | let mut fieldIdx: u32 = i; |
| 4951 | if recInfo.labeled { |
| 4952 | let idx = resolver::recordFieldIndexFor(self.low.resolver, fieldNode) else { |
| 4953 | throw LowerError::MissingMetadata; |
| 4954 | }; |
| 4955 | set fieldIdx = idx; |
| 4956 | } |
| 4957 | // Skip `undefined` fields, they need no initialization. |
| 4958 | // Emitting a blit from an uninitialised reserve produces a |
| 4959 | // phantom SSA source value that the backend cannot handle. |
| 4960 | if not isUndef(field.value) { |
| 4961 | let fieldTy = recInfo.fields[fieldIdx].fieldType; |
| 4962 | let fieldVal = try lowerExpr(self, field.value); |
| 4963 | try emitStore(self, dst, offset + recInfo.fields[fieldIdx].offset, fieldTy, fieldVal); |
| 4964 | } |
| 4965 | } |
| 4966 | } |
| 4967 | |
| 4968 | /// Lower an unlabeled record constructor call. |
| 4969 | 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 { |
| 4970 | let case resolver::NominalType::Record(recInfo) = *nominal else { |
| 4971 | throw LowerError::ExpectedRecord; |
| 4972 | }; |
| 4973 | let typ = resolver::Type::Nominal(nominal); |
| 4974 | let dst = try emitReserve(self, typ); |
| 4975 | |
| 4976 | for argNode, i in args { |
| 4977 | // Skip `undefined` arguments. |
| 4978 | if not isUndef(argNode) { |
| 4979 | let fieldTy = recInfo.fields[i].fieldType; |
| 4980 | let argVal = try lowerExpr(self, argNode); |
| 4981 | try emitStore(self, dst, recInfo.fields[i].offset, fieldTy, argVal); |
| 4982 | } |
| 4983 | } |
| 4984 | return il::Val::Reg(dst); |
| 4985 | } |
| 4986 | |
| 4987 | /// Lower an array literal expression like `[1, 2, 3]`. |
| 4988 | unsafe fn lowerArrayLit 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, elements: *[*ast::Node]) -> il::Val |
| 4989 | throws (LowerError) where 'arena: 'phase, 'phase: 'function |
| 4990 | { |
| 4991 | let typ = try typeOf(self, node); |
| 4992 | let case resolver::Type::Array(arrInfo) = typ else { |
| 4993 | throw LowerError::ExpectedArray; |
| 4994 | }; |
| 4995 | let elemTy = *arrInfo.item; |
| 4996 | let elemLayout = resolver::getTypeLayout(elemTy); |
| 4997 | let dst = try emitReserve(self, typ); |
| 4998 | |
| 4999 | for elemNode, i in elements { |
| 5000 | let elemVal = try lowerExpr(self, elemNode); |
| 5001 | let offset = i * elemLayout.size; |
| 5002 | |
| 5003 | try emitStore(self, dst, offset as i32, elemTy, elemVal); |
| 5004 | } |
| 5005 | return il::Val::Reg(dst); |
| 5006 | } |
| 5007 | |
| 5008 | /// Lower an array repeat literal expression like `[42; 3]`. |
| 5009 | /// Unrolls the initialization at compile time. |
| 5010 | // TODO: Beyond a certain length, lower this to a loop. |
| 5011 | unsafe fn lowerArrayRepeatLit 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, repeat: ast::ArrayRepeatLit) -> il::Val |
| 5012 | throws (LowerError) where 'arena: 'phase, 'phase: 'function |
| 5013 | { |
| 5014 | let typ = try typeOf(self, node); |
| 5015 | let case resolver::Type::Array(arrInfo) = typ else { |
| 5016 | throw LowerError::ExpectedArray; |
| 5017 | }; |
| 5018 | let elemTy = *arrInfo.item; |
| 5019 | let length = arrInfo.length; |
| 5020 | let elemLayout = resolver::getTypeLayout(elemTy); |
| 5021 | let dst = try emitReserve(self, typ); |
| 5022 | |
| 5023 | // Evaluate the repeated item once. |
| 5024 | let repeatVal = try lowerExpr(self, repeat.item); |
| 5025 | |
| 5026 | // Unroll: store at each offset. |
| 5027 | for i in 0..length { |
| 5028 | let offset = i * elemLayout.size; |
| 5029 | try emitStore(self, dst, offset as i32, elemTy, repeatVal); |
| 5030 | } |
| 5031 | return il::Val::Reg(dst); |
| 5032 | } |
| 5033 | |
| 5034 | /// Lower a union constructor call like `Union::Variant(...)`. |
| 5035 | 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 |
| 5036 | throws (LowerError) where 'arena: 'phase, 'phase: 'function |
| 5037 | { |
| 5038 | let unionTy = try typeOf(self, node); |
| 5039 | let case resolver::SymbolData::Variant { type: payloadType, index, .. } = sym.data else { |
| 5040 | throw LowerError::ExpectedVariant; |
| 5041 | }; |
| 5042 | let unionInfo = unionInfoFromType(unionTy) else { |
| 5043 | throw LowerError::MissingMetadata; |
| 5044 | }; |
| 5045 | let valOffset = unionInfo.valOffset as i32; |
| 5046 | let mut payloadVal: ?il::Val = nil; |
| 5047 | if payloadType <> resolver::Type::Void { |
| 5048 | let case resolver::Type::Nominal(payloadNominal) = payloadType else { |
| 5049 | throw LowerError::MissingMetadata; |
| 5050 | }; |
| 5051 | set payloadVal = try lowerRecordCtor(self, payloadNominal, call.args); |
| 5052 | } |
| 5053 | return try buildTagged(self, resolver::getTypeLayout(unionTy), index as i64, payloadVal, payloadType, 1, valOffset); |
| 5054 | } |
| 5055 | |
| 5056 | /// Lower a field access into a pointer to the field. |
| 5057 | unsafe fn lowerFieldRef 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, access: ast::Access) -> FieldRef throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5058 | let parentTy = try typeOf(self, access.parent); |
| 5059 | let subjectTy = resolver::autoDeref(parentTy); |
| 5060 | let fieldIdx = resolver::recordFieldIndexFor(self.low.resolver, access.child) else { |
| 5061 | throw LowerError::MissingMetadata; |
| 5062 | }; |
| 5063 | let fieldInfo = resolver::getRecordField(subjectTy, fieldIdx) else { |
| 5064 | throw LowerError::FieldNotFound; |
| 5065 | }; |
| 5066 | let baseVal = try lowerExpr(self, access.parent); |
| 5067 | let baseReg = emitValToReg(self, baseVal); |
| 5068 | |
| 5069 | return FieldRef { |
| 5070 | base: baseReg, |
| 5071 | offset: fieldInfo.offset, |
| 5072 | fieldType: fieldInfo.fieldType, |
| 5073 | }; |
| 5074 | } |
| 5075 | |
| 5076 | /// Lower a field access expression. |
| 5077 | unsafe fn lowerFieldAccess 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, access: ast::Access) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5078 | let fieldRef = try lowerFieldRef(self, access); |
| 5079 | return emitRead(self, fieldRef.base, fieldRef.offset, fieldRef.fieldType); |
| 5080 | } |
| 5081 | |
| 5082 | /// Compute data pointer and element count for a range into a container. |
| 5083 | /// Used by both slice range expressions (`&a[start..end]`) and slice |
| 5084 | /// assignments (`a[start..end] = value`). |
| 5085 | unsafe fn resolveSliceRangePtr 'arena 'phase 'function ( |
| 5086 | self: &mut FnLowerer 'arena 'phase 'function, |
| 5087 | container: *ast::Node, |
| 5088 | range: ast::Range, |
| 5089 | info: resolver::SliceRangeInfo |
| 5090 | ) -> SliceRangeResult throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5091 | let baseVal = try lowerExpr(self, container); |
| 5092 | let baseReg = emitValToReg(self, baseVal); |
| 5093 | |
| 5094 | // Extract data pointer and container length. |
| 5095 | let mut dataReg = baseReg; |
| 5096 | let mut containerLen: il::Val = undefined; |
| 5097 | if let cap = info.capacity { // Slice from array. |
| 5098 | set containerLen = il::Val::Imm(cap as i64); |
| 5099 | } else { // Slice from slice. |
| 5100 | set dataReg = loadSlicePtr(self, baseReg); |
| 5101 | set containerLen = loadSliceLen(self, baseReg); |
| 5102 | } |
| 5103 | |
| 5104 | // Compute range bounds. |
| 5105 | let mut startVal: il::Val = il::Val::Imm(0); |
| 5106 | if let start = range.start { |
| 5107 | set startVal = try lowerExpr(self, start); |
| 5108 | } |
| 5109 | let mut endVal = containerLen; |
| 5110 | if let end = range.end { |
| 5111 | set endVal = try lowerExpr(self, end); |
| 5112 | } |
| 5113 | |
| 5114 | // Runtime slice bounds checks for dynamic range expressions. |
| 5115 | if not isLtEq(startVal, containerLen) { |
| 5116 | try emitTrapIfLt(self, il::Type::W32, containerLen, startVal); |
| 5117 | } |
| 5118 | if not isLtEq(endVal, containerLen) { |
| 5119 | try emitTrapIfLt(self, il::Type::W32, containerLen, endVal); |
| 5120 | } |
| 5121 | if startVal <> il::Val::Imm(0) and endVal <> containerLen and not isLtEq(startVal, endVal) { |
| 5122 | try emitTrapIfLt(self, il::Type::W32, endVal, startVal); |
| 5123 | } |
| 5124 | |
| 5125 | // If the start value is known to be zero, the count is just the end |
| 5126 | // value. Otherwise, we have to compute it. |
| 5127 | let mut count = endVal; |
| 5128 | |
| 5129 | // Only compute range offset and count if the start value is not |
| 5130 | // statically known to be zero. |
| 5131 | if startVal <> il::Val::Imm(0) { |
| 5132 | // Offset the data pointer by the start value. |
| 5133 | set dataReg = emitElem( |
| 5134 | self, resolver::getTypeLayout(*info.itemType).size, dataReg, startVal |
| 5135 | ); |
| 5136 | // Compute the count as `end - start`. |
| 5137 | let lenReg = nextReg(self); |
| 5138 | emit(self, il::Instr::BinOp { |
| 5139 | op: il::BinOp::Sub, |
| 5140 | typ: il::Type::W32, |
| 5141 | dst: lenReg, |
| 5142 | a: endVal, |
| 5143 | b: startVal, |
| 5144 | }); |
| 5145 | set count = il::Val::Reg(lenReg); |
| 5146 | } |
| 5147 | return SliceRangeResult { dataReg, count }; |
| 5148 | } |
| 5149 | |
| 5150 | /// Lower a slice range expression into a slice header value. |
| 5151 | unsafe fn lowerSliceRange 'arena 'phase 'function ( |
| 5152 | self: &mut FnLowerer 'arena 'phase 'function, |
| 5153 | container: *ast::Node, |
| 5154 | range: ast::Range, |
| 5155 | sliceNode: *ast::Node |
| 5156 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5157 | let info = resolver::sliceRangeInfoFor(self.low.resolver, sliceNode) else { |
| 5158 | throw LowerError::MissingMetadata; |
| 5159 | }; |
| 5160 | let r = try resolveSliceRangePtr(self, container, range, info); |
| 5161 | return try buildSliceValue( |
| 5162 | self, info.itemType, info.mutable, il::Val::Reg(r.dataReg), r.count, r.count |
| 5163 | ); |
| 5164 | } |
| 5165 | |
| 5166 | /// Lower an address-of (`&x`) expression. |
| 5167 | 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 { |
| 5168 | // Handle subscript: `&ary[i]` or `&ary[start..end]`. |
| 5169 | if let case ast::NodeValue::Subscript { container, index } = addr.target.value { |
| 5170 | if let case ast::NodeValue::Range(range) = index.value { |
| 5171 | return try lowerSliceRange(self, container, range, node); |
| 5172 | } |
| 5173 | let result = try lowerElemPtr(self, container, index); |
| 5174 | |
| 5175 | return il::Val::Reg(result.elemReg); |
| 5176 | } |
| 5177 | // Handle field address: `&x.field`. |
| 5178 | if let case ast::NodeValue::FieldAccess(access) = addr.target.value { |
| 5179 | let fieldRef = try lowerFieldRef(self, access); |
| 5180 | let ptr = emitPtrOffset(self, fieldRef.base, fieldRef.offset); |
| 5181 | |
| 5182 | return il::Val::Reg(ptr); |
| 5183 | } |
| 5184 | // A qualified constant or static uses its resolved owner's data symbol. |
| 5185 | if let case ast::NodeValue::ScopeAccess(_) = addr.target.value { |
| 5186 | let sym = resolver::nodeData(self.low.resolver, addr.target).sym else { |
| 5187 | throw LowerError::MissingSymbol(addr.target); |
| 5188 | }; |
| 5189 | return il::Val::Reg(emitDataAddr(self, sym)); |
| 5190 | } |
| 5191 | // Handle variable address: `&x` |
| 5192 | if let case ast::NodeValue::Ident(_) = addr.target.value { |
| 5193 | if let v = lookupLocalVar(self, addr.target) { |
| 5194 | let val = try useVar(self, v); |
| 5195 | let typ = try typeOf(self, addr.target); |
| 5196 | // For aggregates, the value is already a pointer. |
| 5197 | if isAggregateType(typ) { |
| 5198 | return val; |
| 5199 | } |
| 5200 | // For scalars, if we've already materialized a stack slot for this |
| 5201 | // variable, the SSA value is that slot pointer. |
| 5202 | if self.vars[*v].addressTaken { |
| 5203 | // Already address-taken; return existing stack pointer. |
| 5204 | return val; |
| 5205 | } |
| 5206 | // Materialize a stack slot using the declaration's resolved |
| 5207 | // layout so `align(N)` on locals is honored. |
| 5208 | let layout = resolver::getLayout(self.low.resolver, addr.target, typ); |
| 5209 | let slot = emitReserveLayout(self, layout); |
| 5210 | try emitStore(self, slot, 0, typ, val); |
| 5211 | let stackVal = il::Val::Reg(slot); |
| 5212 | |
| 5213 | set self.vars[*v].addressTaken = true; |
| 5214 | defVar(self, v, stackVal); |
| 5215 | |
| 5216 | return stackVal; |
| 5217 | } |
| 5218 | // Fall back to symbol lookup for constants/statics. |
| 5219 | if let sym = resolver::nodeData(self.low.resolver, addr.target).sym { |
| 5220 | return il::Val::Reg(emitDataAddr(self, sym)); |
| 5221 | } else { |
| 5222 | throw LowerError::MissingSymbol(node); |
| 5223 | } |
| 5224 | } |
| 5225 | // Handle dereference address: `&(*ptr) = ptr`. |
| 5226 | if let case ast::NodeValue::Deref(target) = addr.target.value { |
| 5227 | return try lowerExpr(self, target); |
| 5228 | } |
| 5229 | // Array literals become slices when their address is taken. |
| 5230 | match addr.target.value { |
| 5231 | case ast::NodeValue::ArrayLit(_), |
| 5232 | ast::NodeValue::ArrayRepeatLit(_) => |
| 5233 | { |
| 5234 | return try lowerArrayLiteralSlice(self, node, addr.target); |
| 5235 | } |
| 5236 | else => {} |
| 5237 | } |
| 5238 | throw LowerError::UnexpectedNodeValue(addr.target); |
| 5239 | } |
| 5240 | |
| 5241 | /// Lower an addressed array literal as a slice. |
| 5242 | unsafe fn lowerArrayLiteralSlice 'arena 'phase 'function ( |
| 5243 | self: &mut FnLowerer 'arena 'phase 'function, |
| 5244 | sliceNode: *ast::Node, |
| 5245 | arrayNode: *ast::Node |
| 5246 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5247 | let sliceTy = try typeOf(self, sliceNode); |
| 5248 | let case resolver::Type::Slice { item, mutable, .. } = sliceTy else { |
| 5249 | throw LowerError::UnexpectedType(sliceTy); |
| 5250 | }; |
| 5251 | let arrayTy = try typeOf(self, arrayNode); |
| 5252 | let case resolver::Type::Array(arrayInfo) = arrayTy else { |
| 5253 | throw LowerError::ExpectedArray; |
| 5254 | }; |
| 5255 | let length = arrayInfo.length; |
| 5256 | if length == 0 { |
| 5257 | return try buildSliceValue( |
| 5258 | self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0) |
| 5259 | ); |
| 5260 | } |
| 5261 | if resolver::isConstExpr(self.low.resolver, arrayNode) { |
| 5262 | let fnName = self.fnName; |
| 5263 | let mut b = dataBuilder(alloc::arenaAllocator(self.low.arena)); |
| 5264 | match arrayNode.value { |
| 5265 | case ast::NodeValue::ArrayLit(elements) => |
| 5266 | try lowerConstArrayLitInto(self.low, elements, arrayTy, fnName, &mut b), |
| 5267 | case ast::NodeValue::ArrayRepeatLit(repeat) => |
| 5268 | try lowerConstArrayRepeatInto(self.low, repeat, arrayTy, fnName, &mut b), |
| 5269 | else => throw LowerError::UnexpectedNodeValue(arrayNode), |
| 5270 | } |
| 5271 | let result = dataBuilderFinish(b); |
| 5272 | let alignment = resolver::getTypeLayout(*item).alignment; |
| 5273 | return try lowerConstDataAsSlice( |
| 5274 | self, &result, alignment, not mutable, |
| 5275 | item, mutable, length |
| 5276 | ); |
| 5277 | } |
| 5278 | let data = try lowerExpr(self, arrayNode); |
| 5279 | let count = il::Val::Imm(length as i64); |
| 5280 | return try buildSliceValue(self, item, mutable, data, count, count); |
| 5281 | } |
| 5282 | |
| 5283 | /// Lower the common element pointer computation for subscript operations. |
| 5284 | /// Handles both arrays and slices by resolving the container type, extracting |
| 5285 | /// the data pointer (for slices), and emitting an [`il::Instr::Elem`] to compute |
| 5286 | /// the element address. |
| 5287 | unsafe fn lowerElemPtr 'arena 'phase 'function ( |
| 5288 | self: &mut FnLowerer 'arena 'phase 'function, container: *ast::Node, index: *ast::Node |
| 5289 | ) -> ElemPtrResult throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5290 | let containerTy = try typeOf(self, container); |
| 5291 | let subjectTy = resolver::autoDeref(containerTy); |
| 5292 | let baseVal = try lowerExpr(self, container); |
| 5293 | let indexVal = try lowerExpr(self, index); |
| 5294 | let baseReg = emitValToReg(self, baseVal); |
| 5295 | |
| 5296 | let mut dataReg = baseReg; |
| 5297 | let mut elemType: resolver::Type = undefined; |
| 5298 | |
| 5299 | match subjectTy { |
| 5300 | case resolver::Type::Slice { item, .. } => { |
| 5301 | set elemType = *item; |
| 5302 | let sliceLen = loadSliceLen(self, baseReg); |
| 5303 | // Runtime safety check: index must be strictly less than slice length. |
| 5304 | try emitTrapUnlessCmp(self, il::CmpOp::Ult, il::Type::W32, indexVal, sliceLen); |
| 5305 | |
| 5306 | set dataReg = loadSlicePtr(self, baseReg); |
| 5307 | } |
| 5308 | case resolver::Type::Array(arrInfo) => { |
| 5309 | set elemType = *arrInfo.item; |
| 5310 | // Runtime safety check: index must be strictly less than array length. |
| 5311 | // Skip when the index is a compile-time constant, since we check |
| 5312 | // that in the resolver. |
| 5313 | if not resolver::isConstExpr(self.low.resolver, index) { |
| 5314 | let arrLen = il::Val::Imm(arrInfo.length as i64); |
| 5315 | try emitTrapUnlessCmp(self, il::CmpOp::Ult, il::Type::W32, indexVal, arrLen); |
| 5316 | } |
| 5317 | } |
| 5318 | else => throw LowerError::ExpectedSliceOrArray, |
| 5319 | } |
| 5320 | let elemLayout = resolver::getTypeLayout(elemType); |
| 5321 | let elemReg = emitElem(self, elemLayout.size, dataReg, indexVal); |
| 5322 | |
| 5323 | return ElemPtrResult { elemReg, elemType }; |
| 5324 | } |
| 5325 | |
| 5326 | /// Lower a dereference expression. |
| 5327 | /// Handles both pointer deref (`*ptr`) and record deref (`*r` on single-field |
| 5328 | /// unlabeled record). Both read at offset 0 using the resolver-assigned type. |
| 5329 | 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 { |
| 5330 | let type = try typeOf(self, node); |
| 5331 | let ptrVal = try lowerExpr(self, target); |
| 5332 | let ptrReg = emitValToReg(self, ptrVal); |
| 5333 | |
| 5334 | let value = emitRead(self, ptrReg, 0, type); |
| 5335 | if let case resolver::Type::Cell { .. } = try typeOf(self, target); isAggregateType(type) { |
| 5336 | return try emitStackVal(self, type, value); |
| 5337 | } |
| 5338 | return value; |
| 5339 | } |
| 5340 | |
| 5341 | /// Lower a subscript expression. |
| 5342 | unsafe fn lowerSubscript 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, container: *ast::Node, index: *ast::Node) -> il::Val |
| 5343 | throws (LowerError) where 'arena: 'phase, 'phase: 'function |
| 5344 | { |
| 5345 | if let case ast::NodeValue::Range(_) = index.value { |
| 5346 | panic "lowerSubscript: range subscript must use address-of (&)"; |
| 5347 | } |
| 5348 | let result = try lowerElemPtr(self, container, index); |
| 5349 | |
| 5350 | return emitRead(self, result.elemReg, 0, result.elemType); |
| 5351 | } |
| 5352 | |
| 5353 | /// Lower a let binding. |
| 5354 | 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 { |
| 5355 | // Evaluate value. |
| 5356 | let val = try lowerExpr(self, l.value); |
| 5357 | if blockHasTerminator(self) { |
| 5358 | return; |
| 5359 | } |
| 5360 | try bindLetValue(self, node, l, val); |
| 5361 | } |
| 5362 | |
| 5363 | /// Bind an evaluated initializer in the current variable scope. |
| 5364 | unsafe fn bindLetValue 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, l: ast::Let, val: il::Val) |
| 5365 | throws (LowerError) where 'arena: 'phase, 'phase: 'function |
| 5366 | { |
| 5367 | // Handle placeholder pattern: `let _ = expr;` |
| 5368 | if let case ast::NodeValue::Placeholder = l.ident.value { |
| 5369 | return; |
| 5370 | } |
| 5371 | let case ast::NodeValue::Ident(name) = l.ident.value else { |
| 5372 | throw LowerError::ExpectedIdentifier; |
| 5373 | }; |
| 5374 | let typ = try typeOf(self, l.ident); |
| 5375 | let ilType = ilType(self.low, typ); |
| 5376 | let mut varVal = val; |
| 5377 | |
| 5378 | // Aggregates with persistent storage need a local copy to avoid aliasing. |
| 5379 | // Temporaries such as literals or call results can be adopted directly. |
| 5380 | // This is because aggregates are represented as memory addresses |
| 5381 | // internally, even though they have value semantics, so without an explicit |
| 5382 | // copy, only the address is is written. Function calls on the other hand |
| 5383 | // reserve their own local stack space, so copying would be redundant. |
| 5384 | // Void variant literals (e.g. `Option::None`) use scope access syntax and |
| 5385 | // are flagged as place expressions, but they are freshly constructed |
| 5386 | // temporaries with no persistent storage. |
| 5387 | if isAggregateType(typ) and |
| 5388 | ast::isPlaceExpr(l.value) and |
| 5389 | not resolver::isCellDeref(self.low.resolver, l.value) and |
| 5390 | voidVariantIndex(self.low.resolver, l.value) == nil { |
| 5391 | set varVal = try emitStackVal(self, typ, val); |
| 5392 | } |
| 5393 | |
| 5394 | // If the resolver determined that this variable's address is taken |
| 5395 | // anywhere in the function, allocate a stack slot immediately so the |
| 5396 | // SSA value is always a pointer. This avoids mixing integer and pointer |
| 5397 | // values in loop phis when `&var` or `&mut var` appears inside a loop. |
| 5398 | if not isAggregateType(typ) { |
| 5399 | if let sym = resolver::nodeData(self.low.resolver, node).sym { |
| 5400 | if let case resolver::SymbolData::Value { addressTaken, .. } = sym.data; addressTaken { |
| 5401 | let layout = resolver::getLayout(self.low.resolver, node, typ); |
| 5402 | let slot = emitReserveLayout(self, layout); |
| 5403 | try emitStore(self, slot, 0, typ, varVal); |
| 5404 | |
| 5405 | let v = newVar(self, name, ilType, l.mutable, il::Val::Reg(slot)); |
| 5406 | set self.vars[*v].addressTaken = true; |
| 5407 | |
| 5408 | return; |
| 5409 | } |
| 5410 | } |
| 5411 | } |
| 5412 | let _ = newVar(self, name, ilType, l.mutable, varVal); |
| 5413 | } |
| 5414 | |
| 5415 | /// Lower an if statement: `if <cond> { <then> } else { <else> }`. |
| 5416 | /// |
| 5417 | /// With else branch: |
| 5418 | /// |
| 5419 | /// @entry -> (true) @then ---> @merge <--. |
| 5420 | /// | ) |
| 5421 | /// `---> (false) @else ---------------' |
| 5422 | /// |
| 5423 | /// Without else branch: |
| 5424 | /// |
| 5425 | /// @entry -> (true) @then ---> @end <--. |
| 5426 | /// | ) |
| 5427 | /// `---- (false) -------------------' |
| 5428 | /// |
| 5429 | unsafe fn lowerIf 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, i: ast::If) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5430 | let thenBlock = try createBlock(self, "then"); |
| 5431 | |
| 5432 | if let elseNode = i.elseBranch { // If-else case. |
| 5433 | let elseBlock = try createBlock(self, "else"); |
| 5434 | try emitCondBranch(self, i.condition, thenBlock, elseBlock); |
| 5435 | |
| 5436 | // Both @then and @else have exactly one predecessor (@entry), so we can |
| 5437 | // seal them immediately. |
| 5438 | try sealBlock(self, thenBlock); |
| 5439 | try sealBlock(self, elseBlock); |
| 5440 | |
| 5441 | // The merge block is created lazily by [`emitMergeIfUnterminated`]. We |
| 5442 | // only need it if at least one branch doesn't diverge (i.e., needs to |
| 5443 | // continue execution after the `if`). If both branches diverge (eg. |
| 5444 | // both `return`), no merge block is created and control flow |
| 5445 | // doesn't continue past the `if` statement. |
| 5446 | let mut mergeBlock: ?BlockId = nil; |
| 5447 | |
| 5448 | // Lower the @then block: switch to it, emit its code, then jump to |
| 5449 | // merge if the block doesn't diverge. |
| 5450 | switchToBlock(self, thenBlock); |
| 5451 | try lowerBlock(self, i.thenBranch); |
| 5452 | try emitMergeIfUnterminated(self, &mut mergeBlock); |
| 5453 | |
| 5454 | // Lower the @else block similarly. |
| 5455 | switchToBlock(self, elseBlock); |
| 5456 | try lowerBlock(self, elseNode); |
| 5457 | try emitMergeIfUnterminated(self, &mut mergeBlock); |
| 5458 | |
| 5459 | // If a merge block was created (at least one branch flows into it), |
| 5460 | // switch to it for subsequent code. The merge block's predecessors |
| 5461 | // are the branches that jumped to it, so we seal it now. |
| 5462 | // If the merge block is `nil`, both branches diverged and there's no |
| 5463 | // continuation point. |
| 5464 | if let blk = mergeBlock { |
| 5465 | try switchToAndSeal(self, blk); |
| 5466 | } |
| 5467 | } else { // If without `else`. |
| 5468 | // The false branch goes directly to @end, which also serves as the |
| 5469 | // merge point after @then completes. |
| 5470 | let endBlock = try createBlock(self, "merge"); |
| 5471 | |
| 5472 | try emitCondBranch(self, i.condition, thenBlock, endBlock); |
| 5473 | |
| 5474 | // @then has one predecessor (@entry), seal it immediately. |
| 5475 | // @end is not sealed yet because @then might also jump to it. |
| 5476 | try sealBlock(self, thenBlock); |
| 5477 | |
| 5478 | // Lower the @then block, then jump to @end and seal it. |
| 5479 | // Unlike the if-else case, @end is always created because there is no |
| 5480 | // else branch that can diverge. |
| 5481 | switchToBlock(self, thenBlock); |
| 5482 | try lowerBlock(self, i.thenBranch); |
| 5483 | try emitJmpAndSeal(self, endBlock); |
| 5484 | |
| 5485 | // Continue execution at @end. |
| 5486 | try switchToAndSeal(self, endBlock); |
| 5487 | } |
| 5488 | } |
| 5489 | |
| 5490 | /// Lower an assignment target that designates a memory location, and return |
| 5491 | /// the address to store through. Returns `nil` without emitting anything for |
| 5492 | /// targets that aren't memory-backed, such as locals tracked in SSA. |
| 5493 | unsafe fn lowerPlace 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: *ast::Node) -> ?FieldRef throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5494 | match target.value { |
| 5495 | case ast::NodeValue::ScopeAccess(_) => { |
| 5496 | let sym = try symOf(self, target); |
| 5497 | let case resolver::SymbolData::Value { type, .. } = sym.data else { |
| 5498 | throw LowerError::ImmutableAssignment; |
| 5499 | }; |
| 5500 | return FieldRef { base: emitDataAddr(self, sym), offset: 0, fieldType: type }; |
| 5501 | } |
| 5502 | case ast::NodeValue::FieldAccess(access) => { |
| 5503 | return try lowerFieldRef(self, access); |
| 5504 | } |
| 5505 | case ast::NodeValue::Deref(pointer) => { |
| 5506 | // Dereference: `*ptr` or `*r` on a single-field unlabeled record. |
| 5507 | // Both address offset 0 using the resolver-assigned type. |
| 5508 | let base = emitValToReg(self, try lowerExpr(self, pointer)); |
| 5509 | return FieldRef { base, offset: 0, fieldType: try typeOf(self, target) }; |
| 5510 | } |
| 5511 | case ast::NodeValue::Subscript { container, index } => { |
| 5512 | // Array or slice element: `arr[i]`. |
| 5513 | let elem = try lowerElemPtr(self, container, index); |
| 5514 | return FieldRef { base: elem.elemReg, offset: 0, fieldType: elem.elemType }; |
| 5515 | } |
| 5516 | else => return nil, |
| 5517 | } |
| 5518 | } |
| 5519 | |
| 5520 | /// Lower a compound assignment whose target is memory-backed, and report |
| 5521 | /// whether it was handled. Nothing is emitted when it isn't. |
| 5522 | /// |
| 5523 | /// Compound assignments share their target node with the left operand of the |
| 5524 | /// desugared binary expression. Resolve that place once so side effects in a |
| 5525 | /// dereference, field parent, or subscript index are not repeated for the store. |
| 5526 | unsafe fn lowerCompoundAssign 'arena 'phase 'function ( |
| 5527 | self: &mut FnLowerer 'arena 'phase 'function, expr: *ast::Node, binop: ast::BinOp |
| 5528 | ) -> bool throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5529 | let place = try lowerPlace(self, binop.left) else return false; |
| 5530 | let current = emitRead(self, place.base, place.offset, place.fieldType); |
| 5531 | let left = try applyCoercion(self, binop.left, current); |
| 5532 | let right = try lowerExpr(self, binop.right); |
| 5533 | let exprType = try typeOf(self, expr); |
| 5534 | let result = emitScalarBinOp( |
| 5535 | self, binop.op, ilType(self.low, exprType), left, right, isUnsignedType(exprType) |
| 5536 | ); |
| 5537 | let assigned = try applyCoercion(self, expr, result); |
| 5538 | try emitStore(self, place.base, place.offset, place.fieldType, assigned); |
| 5539 | |
| 5540 | return true; |
| 5541 | } |
| 5542 | |
| 5543 | /// Lower an assignment statement. |
| 5544 | 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 { |
| 5545 | // Slice assignment: `slice[range] = value`. |
| 5546 | if let info = resolver::sliceRangeInfoFor(self.low.resolver, node) { |
| 5547 | let case ast::NodeValue::Subscript { container, index } = a.left.value |
| 5548 | else panic "lowerAssign: slice assign without subscript"; |
| 5549 | let case ast::NodeValue::Range(range) = index.value |
| 5550 | else panic "lowerAssign: slice assign without range"; |
| 5551 | try lowerSliceAssign(self, a.right, container, range, info); |
| 5552 | |
| 5553 | return; |
| 5554 | } |
| 5555 | // Compound assignments are represented as `target = target op rhs`, with |
| 5556 | // the exact same target node used in both places. Memory-backed targets |
| 5557 | // must be resolved once so calls in the target are evaluated once. |
| 5558 | if let case ast::NodeValue::BinOp(binop) = a.right.value { |
| 5559 | if binop.left == a.left and try lowerCompoundAssign(self, a.right, binop) { |
| 5560 | return; |
| 5561 | } |
| 5562 | } |
| 5563 | // Evaluate assignment value. |
| 5564 | let rhs = try lowerExpr(self, a.right); |
| 5565 | |
| 5566 | match a.left.value { |
| 5567 | case ast::NodeValue::Ident(_) => { |
| 5568 | // First try local variable lookup. |
| 5569 | if let v = lookupLocalVar(self, a.left) { |
| 5570 | if not getVar(self, v).mutable { |
| 5571 | throw LowerError::ImmutableAssignment; |
| 5572 | } |
| 5573 | let leftTy = try typeOf(self, a.left); |
| 5574 | if isAggregateType(leftTy) or getVar(self, v).addressTaken { |
| 5575 | // Aggregates and address-taken scalars are represented as |
| 5576 | // pointers to stack memory. Store through the pointer. |
| 5577 | let val = try useVar(self, v); |
| 5578 | let dst = emitValToReg(self, val); |
| 5579 | |
| 5580 | try emitStore(self, dst, 0, leftTy, rhs); |
| 5581 | } else { |
| 5582 | // Scalars are tracked directly in SSA. Each assignment |
| 5583 | // records a new SSA value. |
| 5584 | defVar(self, v, rhs); |
| 5585 | } |
| 5586 | } else { |
| 5587 | // Fall back to static variable assignment. |
| 5588 | try lowerStaticAssign(self, a.left, rhs); |
| 5589 | } |
| 5590 | } |
| 5591 | else => { |
| 5592 | let place = try lowerPlace(self, a.left) else { |
| 5593 | throw LowerError::UnexpectedNodeValue(a.left); |
| 5594 | }; |
| 5595 | try emitStore(self, place.base, place.offset, place.fieldType, rhs); |
| 5596 | } |
| 5597 | } |
| 5598 | } |
| 5599 | |
| 5600 | /// Lower `slice[range] = value`. |
| 5601 | unsafe fn lowerSliceAssign 'arena 'phase 'function ( |
| 5602 | self: &mut FnLowerer 'arena 'phase 'function, |
| 5603 | rhs: *ast::Node, |
| 5604 | container: *ast::Node, |
| 5605 | range: ast::Range, |
| 5606 | info: resolver::SliceRangeInfo |
| 5607 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5608 | let r = try resolveSliceRangePtr(self, container, range, info); |
| 5609 | let elemSize = resolver::getTypeLayout(*info.itemType).size; |
| 5610 | let rhsTy = try typeOf(self, rhs); |
| 5611 | |
| 5612 | if let case resolver::Type::Slice { .. } = rhsTy { |
| 5613 | // Copy from source slice. |
| 5614 | let srcReg = emitValToReg(self, try lowerExpr(self, rhs)); |
| 5615 | let srcData = loadSlicePtr(self, srcReg); |
| 5616 | let srcLen = loadSliceLen(self, srcReg); |
| 5617 | |
| 5618 | // Trap if source and destination lengths differ. |
| 5619 | try emitTrapUnlessCmp(self, il::CmpOp::Eq, il::Type::W32, r.count, srcLen); |
| 5620 | |
| 5621 | let bytes = emitTypedBinOp( |
| 5622 | self, il::BinOp::Mul, il::Type::W32, r.count, il::Val::Imm(elemSize as i64) |
| 5623 | ); |
| 5624 | try emitByteCopyLoop(self, r.dataReg, srcData, bytes, "copy"); |
| 5625 | } else { |
| 5626 | // Fill with scalar value. |
| 5627 | let fillVal = try lowerExpr(self, rhs); |
| 5628 | try emitFillLoop(self, r.dataReg, fillVal, r.count, *info.itemType, elemSize); |
| 5629 | } |
| 5630 | } |
| 5631 | |
| 5632 | /// Emit a typed fill loop: `for i in 0..count { dst[i * stride] = value; }`. |
| 5633 | unsafe fn emitFillLoop 'arena 'phase 'function ( |
| 5634 | self: &mut FnLowerer 'arena 'phase 'function, |
| 5635 | dst: il::Reg, |
| 5636 | value: il::Val, |
| 5637 | count: il::Val, |
| 5638 | elemType: resolver::Type, |
| 5639 | elemSize: u32 |
| 5640 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5641 | let iReg = nextReg(self); |
| 5642 | let header = try createBlockWithParam( |
| 5643 | self, "fill", il::Param { value: iReg, type: il::Type::W32 } |
| 5644 | ); |
| 5645 | let body = try createBlock(self, "fill"); |
| 5646 | let done = try createBlock(self, "fill"); |
| 5647 | |
| 5648 | try emitJmpWithArg(self, header, il::Val::Imm(0)); |
| 5649 | switchToBlock(self, header); |
| 5650 | try emitBrCmp(self, il::CmpOp::Ult, il::Type::W32, il::Val::Reg(iReg), count, body, done); |
| 5651 | |
| 5652 | try switchToAndSeal(self, body); |
| 5653 | let dstElem = emitElem(self, elemSize, dst, il::Val::Reg(iReg)); |
| 5654 | try emitStore(self, dstElem, 0, elemType, value); |
| 5655 | |
| 5656 | let nextI = emitTypedBinOp( |
| 5657 | self, il::BinOp::Add, il::Type::W32, il::Val::Reg(iReg), il::Val::Imm(1) |
| 5658 | ); |
| 5659 | try emitJmpWithArg(self, header, nextI); |
| 5660 | try sealBlock(self, header); |
| 5661 | try switchToAndSeal(self, done); |
| 5662 | } |
| 5663 | |
| 5664 | /////////////////// |
| 5665 | // Loop Lowering // |
| 5666 | /////////////////// |
| 5667 | |
| 5668 | // All loop forms are lowered to a common structure: |
| 5669 | // |
| 5670 | // - Loop header block: evaluates condition if any, branches to body or exit. |
| 5671 | // - Body block: executes loop body, may contain break/continue. |
| 5672 | // - Step block (for `for` loops): increments counter, jumps back to header. |
| 5673 | // - Exit block: target for break statements and normal loop exit. |
| 5674 | // |
| 5675 | // The loop stack tracks break/continue targets for nested loops. |
| 5676 | |
| 5677 | /// Lower an infinite loop: `loop { <body> }`. |
| 5678 | /// |
| 5679 | /// @entry -> @loop -> @loop |
| 5680 | /// | |
| 5681 | /// `----> @end |
| 5682 | /// |
| 5683 | unsafe fn lowerLoop 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, body: *ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5684 | let loopBlock = try createBlock(self, "loop"); |
| 5685 | let endBlock = try createBlock(self, "merge"); |
| 5686 | |
| 5687 | // Enter the loop with the given break and continue targets. |
| 5688 | // `break` jumps to `endBlock`, |
| 5689 | // `continue` jumps to `loopBlock`. |
| 5690 | enterLoop(self, endBlock, loopBlock); |
| 5691 | // Switch to and jump to the loop block, then lower all loop body statements into it. |
| 5692 | try switchAndJumpTo(self, loopBlock); |
| 5693 | try lowerBlock(self, body); |
| 5694 | |
| 5695 | // If the loop body doesn't diverge, jump back to the start. |
| 5696 | // This creates the infinite loop. |
| 5697 | // All predecessors are known, we can seal the loop block. |
| 5698 | try emitJmpAndSeal(self, loopBlock); |
| 5699 | // Exit the loop. |
| 5700 | exitLoop(self); |
| 5701 | |
| 5702 | // Only seal end block if it's actually reachable (ie. has predecessors). |
| 5703 | // If the loop has no breaks and only exits via return, the end block |
| 5704 | // remains unreachable and isn't added to the CFG. |
| 5705 | if getBlock(self, endBlock).preds.len > 0 { |
| 5706 | try switchToAndSeal(self, endBlock); |
| 5707 | } |
| 5708 | } |
| 5709 | |
| 5710 | /// Lower a while loop: `while <cond> { <body> }`. |
| 5711 | /// |
| 5712 | /// @entry -> @loop -> (true) @body -> @loop |
| 5713 | /// | |
| 5714 | /// `----> (false) @end |
| 5715 | /// |
| 5716 | unsafe fn lowerWhile 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, w: ast::While) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5717 | let whileBlock = try createBlock(self, "while"); |
| 5718 | let bodyBlock = try createBlock(self, "body"); |
| 5719 | let endBlock = try createBlock(self, "merge"); |
| 5720 | |
| 5721 | enterLoop(self, endBlock, whileBlock); |
| 5722 | |
| 5723 | // Loop condition. |
| 5724 | try switchAndJumpTo(self, whileBlock); |
| 5725 | |
| 5726 | // Based on the condition, either jump to the body, |
| 5727 | // or to the end of the loop. |
| 5728 | try emitCondBranch(self, w.condition, bodyBlock, endBlock); |
| 5729 | |
| 5730 | // Lower loop body and jump back to loop condition check. |
| 5731 | try switchToAndSeal(self, bodyBlock); |
| 5732 | try lowerBlock(self, w.body); |
| 5733 | try emitJmpAndSeal(self, whileBlock); |
| 5734 | |
| 5735 | try switchToAndSeal(self, endBlock); |
| 5736 | exitLoop(self); |
| 5737 | } |
| 5738 | |
| 5739 | /// Emit an increment of a variable by `1`. |
| 5740 | unsafe fn emitIncrement 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, v: Var, typ: il::Type) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5741 | let cur = try useVar(self, v); |
| 5742 | let next = nextReg(self); |
| 5743 | |
| 5744 | emit(self, il::Instr::BinOp { op: il::BinOp::Add, typ, dst: next, a: cur, b: il::Val::Imm(1) }); |
| 5745 | defVar(self, v, il::Val::Reg(next)); |
| 5746 | } |
| 5747 | |
| 5748 | /// Common for-loop lowering for both range and collection iterators. |
| 5749 | /// |
| 5750 | /// The step block is created lazily (after the loop body) so that it gets |
| 5751 | /// a block index higher than all body blocks. This ensures the register |
| 5752 | /// allocator processes definitions before uses in forward block order, |
| 5753 | /// avoiding stale assignments when a value defined deep in the body flows |
| 5754 | /// through the step block as a block argument. |
| 5755 | unsafe fn lowerForLoop 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, iter: &ForIter, body: *ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5756 | let loopBlock = try createBlock(self, "loop"); |
| 5757 | let bodyBlock = try createBlock(self, "body"); |
| 5758 | let endBlock = try createBlock(self, "merge"); |
| 5759 | |
| 5760 | enterLoop(self, endBlock, nil); |
| 5761 | try switchAndJumpTo(self, loopBlock); |
| 5762 | |
| 5763 | // Emit condition check. |
| 5764 | match *iter { |
| 5765 | case ForIter::Range { valVar, endVal, valType, unsigned, .. } => { |
| 5766 | let curVal = try useVar(self, valVar); |
| 5767 | let cmp = il::CmpOp::Ult if unsigned else il::CmpOp::Slt; |
| 5768 | try emitBrCmp(self, cmp, valType, curVal, endVal, bodyBlock, endBlock); |
| 5769 | } |
| 5770 | case ForIter::Collection { idxVar, lengthVal, .. } => { |
| 5771 | let curIdx = try useVar(self, idxVar); |
| 5772 | try emitBrCmp(self, il::CmpOp::Slt, il::Type::W32, curIdx, lengthVal, bodyBlock, endBlock); |
| 5773 | } |
| 5774 | } |
| 5775 | // Switch to loop body. |
| 5776 | try switchToAndSeal(self, bodyBlock); |
| 5777 | |
| 5778 | // Emit element binding, only for collections. |
| 5779 | // Reads the element at the current index. |
| 5780 | if let case ForIter::Collection { valVar, idxVar, dataReg, elemType, .. } = *iter { |
| 5781 | if let v = valVar { |
| 5782 | let curIdx = try useVar(self, idxVar); |
| 5783 | let elemReg = emitElem(self, resolver::getTypeLayout(*elemType).size, dataReg, curIdx); |
| 5784 | let val = emitRead(self, elemReg, 0, *elemType); |
| 5785 | |
| 5786 | defVar(self, v, val); |
| 5787 | } |
| 5788 | } |
| 5789 | // Lower the loop body. |
| 5790 | try lowerBlock(self, body); |
| 5791 | |
| 5792 | // Check if a `continue` statement created a step block. |
| 5793 | let ctx = currentLoop(self) else panic; |
| 5794 | if let stepBlock = ctx.continueTarget { |
| 5795 | // Body has `continue` statements: jump to step block, emit increment there. |
| 5796 | try emitJmpAndSeal(self, stepBlock); |
| 5797 | switchToBlock(self, stepBlock); |
| 5798 | } |
| 5799 | // Otherwise, emit increment directly in the current block, |
| 5800 | // saving the jump to a separate step block. |
| 5801 | if not blockHasTerminator(self) { |
| 5802 | match *iter { |
| 5803 | case ForIter::Range { valVar, valType, indexVar, .. } => { |
| 5804 | try emitIncrement(self, valVar, valType); |
| 5805 | if let idxVar = indexVar { |
| 5806 | try emitIncrement(self, idxVar, il::Type::W32); |
| 5807 | } |
| 5808 | } |
| 5809 | case ForIter::Collection { idxVar, .. } => { |
| 5810 | try emitIncrement(self, idxVar, il::Type::W32); |
| 5811 | } |
| 5812 | } |
| 5813 | try emitJmp(self, loopBlock); |
| 5814 | } |
| 5815 | exitLoop(self); |
| 5816 | |
| 5817 | try sealBlock(self, loopBlock); |
| 5818 | try sealBlock(self, endBlock); |
| 5819 | |
| 5820 | switchToBlock(self, endBlock); |
| 5821 | } |
| 5822 | |
| 5823 | /// Lower a `for` loop over a range, array, or slice. |
| 5824 | 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 { |
| 5825 | let savedVarsLen = enterVarScope(self); |
| 5826 | let info = resolver::forLoopInfoFor(self.low.resolver, node) else { |
| 5827 | throw LowerError::MissingMetadata; |
| 5828 | }; |
| 5829 | match info { |
| 5830 | case resolver::ForLoopInfo::Range { valType, range, bindingName, indexName } => { |
| 5831 | let endExpr = range.end else { |
| 5832 | throw LowerError::MissingMetadata; |
| 5833 | }; |
| 5834 | let mut startVal = il::Val::Imm(0); |
| 5835 | if let start = range.start { |
| 5836 | set startVal = try lowerExpr(self, start); |
| 5837 | } |
| 5838 | let endVal = try lowerExpr(self, endExpr); |
| 5839 | let iterType = ilType(self.low, *valType); |
| 5840 | let valVar = newVar(self, bindingName, iterType, false, startVal); |
| 5841 | |
| 5842 | let mut indexVar: ?Var = nil; |
| 5843 | if indexName <> nil { // Optional index always starts at zero. |
| 5844 | set indexVar = newVar(self, indexName, il::Type::W32, false, il::Val::Imm(0)); |
| 5845 | } |
| 5846 | let iter = ForIter::Range { |
| 5847 | valVar, indexVar, endVal, valType: iterType, |
| 5848 | unsigned: isUnsignedType(*valType), |
| 5849 | }; |
| 5850 | |
| 5851 | try lowerForLoop(self, &iter, f.body); |
| 5852 | } |
| 5853 | case resolver::ForLoopInfo::Collection { elemType, length, bindingName, indexName } => { |
| 5854 | let containerVal = try lowerExpr(self, f.iterable); |
| 5855 | let containerReg = emitValToReg(self, containerVal); |
| 5856 | |
| 5857 | let mut dataReg = containerReg; |
| 5858 | let mut lengthVal: il::Val = undefined; |
| 5859 | if let len = length { // Array (length is known). |
| 5860 | set lengthVal = il::Val::Imm(len as i64); |
| 5861 | } else { // Slice (length must be loaded). |
| 5862 | set lengthVal = loadSliceLen(self, containerReg); |
| 5863 | set dataReg = loadSlicePtr(self, containerReg); |
| 5864 | } |
| 5865 | // Declare index value binidng. |
| 5866 | let idxVar = newVar(self, indexName, il::Type::W32, false, il::Val::Imm(0)); |
| 5867 | |
| 5868 | // Declare element value binding. |
| 5869 | let mut valVar: ?Var = nil; |
| 5870 | if bindingName <> nil { |
| 5871 | set valVar = newVar( |
| 5872 | self, |
| 5873 | bindingName, |
| 5874 | ilType(self.low, *elemType), |
| 5875 | false, |
| 5876 | il::Val::Undef |
| 5877 | ); |
| 5878 | } |
| 5879 | let iter = ForIter::Collection { valVar, idxVar, dataReg, lengthVal, elemType }; |
| 5880 | |
| 5881 | try lowerForLoop(self, &iter, f.body); |
| 5882 | } |
| 5883 | } |
| 5884 | exitVarScope(self, savedVarsLen); |
| 5885 | } |
| 5886 | |
| 5887 | /// Lower a break statement. |
| 5888 | unsafe fn lowerBreak 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5889 | let ctx = currentLoop(self) else { |
| 5890 | throw LowerError::OutsideOfLoop; |
| 5891 | }; |
| 5892 | try emitJmp(self, ctx.breakTarget); |
| 5893 | } |
| 5894 | |
| 5895 | /// Lower a continue statement. |
| 5896 | unsafe fn lowerContinue 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5897 | let block = try getOrCreateContinueBlock(self); |
| 5898 | try emitJmp(self, block); |
| 5899 | } |
| 5900 | |
| 5901 | /// Emit a return, blitting into the caller's return buffer if needed. |
| 5902 | /// |
| 5903 | /// When the function has a return buffer parameter, the value is blitted |
| 5904 | /// into the buffer and the buffer pointer is returned. Otherwise, the value is |
| 5905 | /// returned directly. |
| 5906 | unsafe fn emitRetVal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, val: il::Val) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5907 | if let retReg = self.returnReg { |
| 5908 | let src = emitValToReg(self, val); |
| 5909 | let size = resolver::getResultLayout(*self.fnType.returnType, self.fnType.throwList).size |
| 5910 | if self.fnType.throwList.len > 0 |
| 5911 | else resolver::getTypeLayout(*self.fnType.returnType).size; |
| 5912 | |
| 5913 | emit(self, il::Instr::Blit { dst: retReg, src, size: il::Val::Imm(size as i64) }); |
| 5914 | emit(self, il::Instr::Ret { val: il::Val::Reg(retReg) }); |
| 5915 | } else if isSmallAggregate(*self.fnType.returnType) { |
| 5916 | let mut src = emitValToReg(self, val); |
| 5917 | let layout = resolver::getTypeLayout(*self.fnType.returnType); |
| 5918 | if layout.alignment < resolver::PTR_SIZE or layout.size < resolver::PTR_SIZE { |
| 5919 | // The return word must not read beyond the value or its alignment. |
| 5920 | let word = emitReserveLayout(self, resolver::Layout { |
| 5921 | size: resolver::PTR_SIZE, alignment: resolver::PTR_SIZE, |
| 5922 | }); |
| 5923 | emitStoreW64At(self, il::Val::Imm(0), word, 0); |
| 5924 | emit(self, il::Instr::Blit { dst: word, src, size: il::Val::Imm(layout.size as i64) }); |
| 5925 | set src = word; |
| 5926 | } |
| 5927 | let dst = nextReg(self); |
| 5928 | |
| 5929 | emit(self, il::Instr::Load { typ: il::Type::W64, dst, src, offset: 0 }); |
| 5930 | emit(self, il::Instr::Ret { val: il::Val::Reg(dst) }); |
| 5931 | } else { |
| 5932 | emit(self, il::Instr::Ret { val }); |
| 5933 | } |
| 5934 | } |
| 5935 | |
| 5936 | /// Lower a return statement. |
| 5937 | 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 { |
| 5938 | let mut val = il::Val::Undef; |
| 5939 | if let expr = value { |
| 5940 | set val = try lowerExpr(self, expr); |
| 5941 | } |
| 5942 | if blockHasTerminator(self) { |
| 5943 | return; |
| 5944 | } |
| 5945 | set val = try applyCoercion(self, node, val); |
| 5946 | try emitRetVal(self, val); |
| 5947 | } |
| 5948 | |
| 5949 | /// Lower a throw statement. |
| 5950 | unsafe fn lowerThrowStmt 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, expr: *ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 5951 | assert self.fnType.throwList.len > 0; |
| 5952 | |
| 5953 | let errType = *self.fnType.throwList[0] if self.fnType.throwList.len == 1 |
| 5954 | else try typeOf(self, expr); |
| 5955 | let tag = getOrAssignErrorTag(self.low, errType) as i64; |
| 5956 | let errVal = try lowerExpr(self, expr); |
| 5957 | let resultVal = try buildResult(self, tag, errVal, errType); |
| 5958 | |
| 5959 | try emitRetVal(self, resultVal); |
| 5960 | } |
| 5961 | |
| 5962 | /// Ensure a value is in a register (eg. for branch conditions). |
| 5963 | unsafe fn emitValToReg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, val: il::Val) -> il::Reg where 'arena: 'phase, 'phase: 'function { |
| 5964 | match val { |
| 5965 | case il::Val::Reg(r) => return r, |
| 5966 | case il::Val::Imm(_), il::Val::DataSym(_), il::Val::FnAddr(_) => { |
| 5967 | let dst = nextReg(self); |
| 5968 | emit(self, il::Instr::Copy { dst, val }); |
| 5969 | return dst; |
| 5970 | } |
| 5971 | case il::Val::Undef => { |
| 5972 | // TODO: We shouldn't hit this case, right? A register shouldn't be needed |
| 5973 | // if the value is undefined. |
| 5974 | return nextReg(self); |
| 5975 | } |
| 5976 | } |
| 5977 | } |
| 5978 | |
| 5979 | /// Lower a logical `and`/`or` with short-circuit evaluation. |
| 5980 | /// |
| 5981 | /// Short-circuit evaluation skips evaluating the right operand when the left |
| 5982 | /// operand already determines the result: |
| 5983 | /// |
| 5984 | /// - In `a and b`, if `a` is false, result is false without evaluating `b`. |
| 5985 | /// - In `a or b`, if `a` is true, result is true without evaluating `b`. |
| 5986 | /// |
| 5987 | /// This matters when `b` has side effects, or is expensive to evaluate. |
| 5988 | /// |
| 5989 | /// Example: `a and b` |
| 5990 | /// |
| 5991 | /// @entry |
| 5992 | /// br %a @then @else; |
| 5993 | /// @then |
| 5994 | /// // Evaluate b into %b |
| 5995 | /// // ... |
| 5996 | /// jmp @end(%b); |
| 5997 | /// @else |
| 5998 | /// jmp @end(0); // Skip evaluating b, result is false |
| 5999 | /// @end(w8 %result) |
| 6000 | /// ret %result; |
| 6001 | /// |
| 6002 | /// Example: `a or b` |
| 6003 | /// |
| 6004 | /// @entry |
| 6005 | /// br %a @then @else; |
| 6006 | /// @then |
| 6007 | /// jmp @end(1); // Skip evaluating b, result is true |
| 6008 | /// @else |
| 6009 | /// // Evaluate b into %b |
| 6010 | /// // ... |
| 6011 | /// jmp @end(%b); |
| 6012 | /// @end(w8 %result) |
| 6013 | /// ret %result; |
| 6014 | /// |
| 6015 | unsafe fn lowerLogicalOp 'arena 'phase 'function ( |
| 6016 | self: &mut FnLowerer 'arena 'phase 'function, |
| 6017 | binop: ast::BinOp, |
| 6018 | thenLabel: *[u8], |
| 6019 | elseLabel: *[u8], |
| 6020 | mergeLabel: *[u8], |
| 6021 | op: LogicalOp |
| 6022 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 6023 | let thenBlock = try createBlock(self, thenLabel); |
| 6024 | let elseBlock = try createBlock(self, elseLabel); |
| 6025 | |
| 6026 | let resultReg = nextReg(self); |
| 6027 | let mergeBlock = try createBlockWithParam( |
| 6028 | self, mergeLabel, il::Param { value: resultReg, type: il::Type::W8 } |
| 6029 | ); |
| 6030 | // Evaluate left operand and branch. |
| 6031 | try emitCondBranch(self, binop.left, thenBlock, elseBlock); |
| 6032 | |
| 6033 | // Block that skips evaluating `b`. |
| 6034 | let mut shortCircuitBlock: BlockId = undefined; |
| 6035 | // Block that evaluates `b`. |
| 6036 | let mut evalBlock: BlockId = undefined; |
| 6037 | // Result when short-circuiting (`0` or `1`). |
| 6038 | let mut shortCircuitVal: i64 = undefined; |
| 6039 | |
| 6040 | match op { |
| 6041 | case LogicalOp::And => { |
| 6042 | set shortCircuitBlock = elseBlock; |
| 6043 | set evalBlock = thenBlock; |
| 6044 | set shortCircuitVal = 0; |
| 6045 | } |
| 6046 | case LogicalOp::Or => { |
| 6047 | set shortCircuitBlock = thenBlock; |
| 6048 | set evalBlock = elseBlock; |
| 6049 | set shortCircuitVal = 1; |
| 6050 | } |
| 6051 | } |
| 6052 | // Emit short-circuit branch: jump to merge with constant result. |
| 6053 | try switchToAndSeal(self, shortCircuitBlock); |
| 6054 | try emitJmpWithArg(self, mergeBlock, il::Val::Imm(shortCircuitVal)); |
| 6055 | |
| 6056 | // Emit evaluation branch: evaluate right operand and jump to merge. |
| 6057 | try switchToAndSeal(self, evalBlock); |
| 6058 | try emitJmpWithArg(self, mergeBlock, try lowerExpr(self, binop.right)); |
| 6059 | |
| 6060 | try switchToAndSeal(self, mergeBlock); |
| 6061 | return il::Val::Reg(resultReg); |
| 6062 | } |
| 6063 | |
| 6064 | /// Lower a conditional expression (`thenExpr if condition else elseExpr`). |
| 6065 | unsafe fn lowerCondExpr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, cond: ast::CondExpr) -> il::Val |
| 6066 | throws (LowerError) where 'arena: 'phase, 'phase: 'function |
| 6067 | { |
| 6068 | let typ = try typeOf(self, node); |
| 6069 | let thenBlock = try createBlock(self, "cond#then"); |
| 6070 | let elseBlock = try createBlock(self, "cond#else"); |
| 6071 | |
| 6072 | if isAggregateType(typ) { |
| 6073 | let dst = try emitReserve(self, typ); |
| 6074 | let layout = resolver::getTypeLayout(typ); |
| 6075 | try emitCondBranch(self, cond.condition, thenBlock, elseBlock); |
| 6076 | |
| 6077 | let mergeBlock = try createBlock(self, "cond#merge"); |
| 6078 | try switchToAndSeal(self, thenBlock); |
| 6079 | |
| 6080 | let thenVal = emitValToReg(self, try lowerExpr(self, cond.thenExpr)); |
| 6081 | emit(self, il::Instr::Blit { dst, src: thenVal, size: il::Val::Imm(layout.size as i64) }); |
| 6082 | |
| 6083 | try emitJmp(self, mergeBlock); |
| 6084 | try switchToAndSeal(self, elseBlock); |
| 6085 | |
| 6086 | let elseVal = emitValToReg(self, try lowerExpr(self, cond.elseExpr)); |
| 6087 | emit(self, il::Instr::Blit { dst, src: elseVal, size: il::Val::Imm(layout.size as i64) }); |
| 6088 | |
| 6089 | try emitJmp(self, mergeBlock); |
| 6090 | try switchToAndSeal(self, mergeBlock); |
| 6091 | |
| 6092 | return il::Val::Reg(dst); |
| 6093 | } else { |
| 6094 | try emitCondBranch(self, cond.condition, thenBlock, elseBlock); |
| 6095 | |
| 6096 | let resultType = ilType(self.low, typ); |
| 6097 | let resultReg = nextReg(self); |
| 6098 | let mergeBlock = try createBlockWithParam( |
| 6099 | self, "cond#merge", il::Param { value: resultReg, type: resultType } |
| 6100 | ); |
| 6101 | try switchToAndSeal(self, thenBlock); |
| 6102 | try emitJmpWithArg(self, mergeBlock, try lowerExpr(self, cond.thenExpr)); |
| 6103 | try switchToAndSeal(self, elseBlock); |
| 6104 | try emitJmpWithArg(self, mergeBlock, try lowerExpr(self, cond.elseExpr)); |
| 6105 | try switchToAndSeal(self, mergeBlock); |
| 6106 | |
| 6107 | return il::Val::Reg(resultReg); |
| 6108 | } |
| 6109 | } |
| 6110 | |
| 6111 | /// Select the concrete operand type for a scalar comparison. |
| 6112 | fn scalarComparisonType(left: resolver::Type, right: resolver::Type) -> resolver::Type { |
| 6113 | return right if left == resolver::Type::Int else left; |
| 6114 | } |
| 6115 | |
| 6116 | /// Convert a binary operator to a comparison op, if applicable. |
| 6117 | /// For `Gt`, caller must swap operands: `a > b = b < a`. |
| 6118 | /// For `Gte`/`Lte`, caller must swap branch labels: `a >= b = !(a < b)`. |
| 6119 | /// For `Lte`, caller must also swap operands: `a <= b = !(b < a)`. |
| 6120 | fn cmpOpFrom(op: ast::BinaryOp, unsigned: bool) -> ?il::CmpOp { |
| 6121 | match op { |
| 6122 | case ast::BinaryOp::Eq => return il::CmpOp::Eq, |
| 6123 | case ast::BinaryOp::Ne => return il::CmpOp::Ne, |
| 6124 | case ast::BinaryOp::Lt, ast::BinaryOp::Gt, |
| 6125 | ast::BinaryOp::Gte, ast::BinaryOp::Lte => |
| 6126 | return il::CmpOp::Ult if unsigned else il::CmpOp::Slt, |
| 6127 | else => return nil, |
| 6128 | } |
| 6129 | } |
| 6130 | |
| 6131 | /// Lower a binary operation. |
| 6132 | 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 { |
| 6133 | // Short-circuit logical operators don't evaluate both operands eagerly. |
| 6134 | if binop.op == ast::BinaryOp::And { |
| 6135 | return try lowerLogicalOp(self, binop, "and#then", "and#else", "and#end", LogicalOp::And); |
| 6136 | } else if binop.op == ast::BinaryOp::Or { |
| 6137 | return try lowerLogicalOp(self, binop, "or#then", "or#else", "or#end", LogicalOp::Or); |
| 6138 | } |
| 6139 | |
| 6140 | // Handle comparison with a `nil` literal: just check the tag/pointer instead of |
| 6141 | // building a `nil` aggregate and doing full comparison. |
| 6142 | if binop.op == ast::BinaryOp::Eq or binop.op == ast::BinaryOp::Ne { |
| 6143 | let isEq = binop.op == ast::BinaryOp::Eq; |
| 6144 | let leftIsNil = binop.left.value == ast::NodeValue::Nil; |
| 6145 | let rightIsNil = binop.right.value == ast::NodeValue::Nil; |
| 6146 | |
| 6147 | if leftIsNil { |
| 6148 | return try lowerNilCheck(self, binop.right, isEq); |
| 6149 | } else if rightIsNil { |
| 6150 | return try lowerNilCheck(self, binop.left, isEq); |
| 6151 | } |
| 6152 | } |
| 6153 | // Lower operands. |
| 6154 | let a = try lowerExpr(self, binop.left); |
| 6155 | let b = try lowerExpr(self, binop.right); |
| 6156 | let isComparison = cmpOpFrom(binop.op, false) <> nil; |
| 6157 | |
| 6158 | // The result type for comparisons is always `bool`, while for arithmetic |
| 6159 | // operations, it's the operand type. We set this appropriately here. |
| 6160 | let nodeTy = try typeOf(self, node); |
| 6161 | let mut resultTy = nodeTy; |
| 6162 | |
| 6163 | if isComparison { |
| 6164 | let leftTy = try effectiveType(self, binop.left); |
| 6165 | let rightTy = try effectiveType(self, binop.right); |
| 6166 | // Optimize: comparing with a void variant just needs tag comparison. |
| 6167 | if let idx = voidVariantIndex(self.low.resolver, binop.left) { |
| 6168 | return try emitTagCmp(self, binop.op, b, idx, rightTy); |
| 6169 | } else if let idx = voidVariantIndex(self.low.resolver, binop.right) { |
| 6170 | return try emitTagCmp(self, binop.op, a, idx, leftTy); |
| 6171 | } |
| 6172 | // Aggregate types require element-wise comparison. |
| 6173 | // When comparing `?T` with `T`, wrap the scalar side. |
| 6174 | if isAggregateType(leftTy) { |
| 6175 | let mut rhs = b; |
| 6176 | if not isAggregateType(rightTy) { |
| 6177 | set rhs = try wrapInOptional(self, rhs, leftTy); |
| 6178 | } |
| 6179 | return try emitAggregateEqOp(self, binop.op, leftTy, a, rhs); |
| 6180 | } |
| 6181 | if isAggregateType(rightTy) { |
| 6182 | let lhs = try wrapInOptional(self, a, rightTy); |
| 6183 | return try emitAggregateEqOp(self, binop.op, rightTy, lhs, b); |
| 6184 | } |
| 6185 | set resultTy = scalarComparisonType(leftTy, rightTy); |
| 6186 | } |
| 6187 | return emitScalarBinOp(self, binop.op, ilType(self.low, resultTy), a, b, isUnsignedType(resultTy)); |
| 6188 | } |
| 6189 | |
| 6190 | /// Emit an aggregate equality or inequality comparison. |
| 6191 | unsafe fn emitAggregateEqOp 'arena 'phase 'function ( |
| 6192 | self: &mut FnLowerer 'arena 'phase 'function, |
| 6193 | op: ast::BinaryOp, |
| 6194 | typ: resolver::Type, |
| 6195 | a: il::Val, |
| 6196 | b: il::Val |
| 6197 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 6198 | let regA = emitValToReg(self, a); |
| 6199 | let regB = emitValToReg(self, b); |
| 6200 | let result = try lowerAggregateEq(self, typ, regA, regB, 0); |
| 6201 | |
| 6202 | if op == ast::BinaryOp::Ne { |
| 6203 | return emitTypedBinOp(self, il::BinOp::Eq, il::Type::W32, result, il::Val::Imm(0)); |
| 6204 | } |
| 6205 | return result; |
| 6206 | } |
| 6207 | |
| 6208 | /// Emit a scalar binary operation instruction. |
| 6209 | unsafe fn emitScalarBinOp 'arena 'phase 'function ( |
| 6210 | self: &mut FnLowerer 'arena 'phase 'function, |
| 6211 | op: ast::BinaryOp, |
| 6212 | typ: il::Type, |
| 6213 | a: il::Val, |
| 6214 | b: il::Val, |
| 6215 | unsigned: bool |
| 6216 | ) -> il::Val where 'arena: 'phase, 'phase: 'function { |
| 6217 | let dst = nextReg(self); |
| 6218 | let mut needsExt: bool = false; |
| 6219 | match op { |
| 6220 | case ast::BinaryOp::Add => { |
| 6221 | emit(self, il::Instr::BinOp { op: il::BinOp::Add, typ, dst, a, b }); |
| 6222 | set needsExt = true; |
| 6223 | } |
| 6224 | case ast::BinaryOp::Sub => { |
| 6225 | emit(self, il::Instr::BinOp { op: il::BinOp::Sub, typ, dst, a, b }); |
| 6226 | set needsExt = true; |
| 6227 | } |
| 6228 | case ast::BinaryOp::Mul => { |
| 6229 | emit(self, il::Instr::BinOp { op: il::BinOp::Mul, typ, dst, a, b }); |
| 6230 | set needsExt = true; |
| 6231 | } |
| 6232 | case ast::BinaryOp::Div => { |
| 6233 | let op = il::BinOp::Udiv if unsigned else il::BinOp::Sdiv; |
| 6234 | emit(self, il::Instr::BinOp { op, typ, dst, a, b }); |
| 6235 | set needsExt = true; |
| 6236 | } |
| 6237 | case ast::BinaryOp::Mod => { |
| 6238 | let op = il::BinOp::Urem if unsigned else il::BinOp::Srem; |
| 6239 | emit(self, il::Instr::BinOp { op, typ, dst, a, b }); |
| 6240 | set needsExt = true; |
| 6241 | } |
| 6242 | case ast::BinaryOp::BitAnd => emit(self, il::Instr::BinOp { op: il::BinOp::And, typ, dst, a, b }), |
| 6243 | case ast::BinaryOp::BitOr => emit(self, il::Instr::BinOp { op: il::BinOp::Or, typ, dst, a, b }), |
| 6244 | case ast::BinaryOp::BitXor => emit(self, il::Instr::BinOp { op: il::BinOp::Xor, typ, dst, a, b }), |
| 6245 | case ast::BinaryOp::Shl => { |
| 6246 | emit(self, il::Instr::BinOp { op: il::BinOp::Shl, typ, dst, a, b }); |
| 6247 | set needsExt = true; |
| 6248 | } |
| 6249 | case ast::BinaryOp::Shr => { |
| 6250 | let op = il::BinOp::Ushr if unsigned else il::BinOp::Sshr; |
| 6251 | emit(self, il::Instr::BinOp { op, typ, dst, a, b }); |
| 6252 | } |
| 6253 | case ast::BinaryOp::Eq => emit(self, il::Instr::BinOp { op: il::BinOp::Eq, typ, dst, a, b }), |
| 6254 | case ast::BinaryOp::Ne => emit(self, il::Instr::BinOp { op: il::BinOp::Ne, typ, dst, a, b }), |
| 6255 | case ast::BinaryOp::Lt => { |
| 6256 | let op = il::BinOp::Ult if unsigned else il::BinOp::Slt; |
| 6257 | emit(self, il::Instr::BinOp { op, typ, dst, a, b }); |
| 6258 | } |
| 6259 | case ast::BinaryOp::Gt => { // `a > b` = `b < a` |
| 6260 | let op = il::BinOp::Ult if unsigned else il::BinOp::Slt; |
| 6261 | emit(self, il::Instr::BinOp { op, typ, dst, a: b, b: a }); |
| 6262 | } |
| 6263 | case ast::BinaryOp::Lte => { // `a <= b` = `b >= a` |
| 6264 | let op = il::BinOp::Uge if unsigned else il::BinOp::Sge; |
| 6265 | emit(self, il::Instr::BinOp { op, typ, dst, a: b, b: a }); |
| 6266 | } |
| 6267 | case ast::BinaryOp::Gte => { |
| 6268 | let op = il::BinOp::Uge if unsigned else il::BinOp::Sge; |
| 6269 | emit(self, il::Instr::BinOp { op, typ, dst, a, b }); |
| 6270 | } |
| 6271 | // Logical xor on booleans is equivalent to not equal. |
| 6272 | case ast::BinaryOp::Xor => emit(self, il::Instr::BinOp { op: il::BinOp::Ne, typ, dst, a, b }), |
| 6273 | // Short-circuit ops are handled elsewhere. |
| 6274 | case ast::BinaryOp::And, ast::BinaryOp::Or => panic, |
| 6275 | } |
| 6276 | // Normalize sub-word arithmetic results so high bits are well-defined. |
| 6277 | // The lowering knows signedness, so it can pick the right extension. |
| 6278 | // [`il::Type::W32`] is handled in the backend via 32-bit instructions. |
| 6279 | if needsExt { |
| 6280 | return normalizeSubword(self, typ, unsigned, il::Val::Reg(dst)); |
| 6281 | } |
| 6282 | return il::Val::Reg(dst); |
| 6283 | } |
| 6284 | |
| 6285 | /// Normalize sub-word values to well-defined high bits. |
| 6286 | unsafe 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 { |
| 6287 | if typ == il::Type::W8 or typ == il::Type::W16 { |
| 6288 | let extDst: il::Reg = nextReg(self); |
| 6289 | if unsigned { |
| 6290 | emit(self, il::Instr::Zext { typ, dst: extDst, val }); |
| 6291 | } else { |
| 6292 | emit(self, il::Instr::Sext { typ, dst: extDst, val }); |
| 6293 | } |
| 6294 | return il::Val::Reg(extDst); |
| 6295 | } |
| 6296 | return val; |
| 6297 | } |
| 6298 | |
| 6299 | /// Lower a unary operation. |
| 6300 | 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 { |
| 6301 | if unop.op == ast::UnaryOp::Neg { |
| 6302 | if let case ast::NodeValue::Number(lit) = unop.value.value { |
| 6303 | return il::Val::Imm((0 - lit.magnitude) as i64); |
| 6304 | } |
| 6305 | } |
| 6306 | let val = try lowerExpr(self, unop.value); |
| 6307 | let t = try typeOf(self, node); |
| 6308 | let typ = ilType(self.low, t); |
| 6309 | let dst = nextReg(self); |
| 6310 | let mut needsExt: bool = false; |
| 6311 | |
| 6312 | match unop.op { |
| 6313 | case ast::UnaryOp::Not => { |
| 6314 | emit(self, il::Instr::BinOp { op: il::BinOp::Eq, typ, dst, a: val, b: il::Val::Imm(0) }); |
| 6315 | } |
| 6316 | case ast::UnaryOp::Neg => { |
| 6317 | emit(self, il::Instr::UnOp { op: il::UnOp::Neg, typ, dst, a: val }); |
| 6318 | set needsExt = true; |
| 6319 | } |
| 6320 | case ast::UnaryOp::BitNot => { |
| 6321 | emit(self, il::Instr::UnOp { op: il::UnOp::Not, typ, dst, a: val }); |
| 6322 | set needsExt = true; |
| 6323 | } |
| 6324 | } |
| 6325 | if needsExt { |
| 6326 | return normalizeSubword(self, typ, isUnsignedType(t), il::Val::Reg(dst)); |
| 6327 | } |
| 6328 | return il::Val::Reg(dst); |
| 6329 | } |
| 6330 | |
| 6331 | /// Lower a cast expression (`x as T`). |
| 6332 | 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 { |
| 6333 | let val = try lowerExpr(self, cast.value); |
| 6334 | |
| 6335 | let srcType = try typeOf(self, cast.value); |
| 6336 | let dstType = try typeOf(self, node); |
| 6337 | if resolver::typesEqual(srcType, dstType) { |
| 6338 | return val; |
| 6339 | } |
| 6340 | return lowerNumericCast(self, val, srcType, dstType); |
| 6341 | } |
| 6342 | |
| 6343 | /// Check whether a resolver type is a signed integer type. |
| 6344 | fn isSignedType(t: resolver::Type) -> bool { |
| 6345 | match t { |
| 6346 | case resolver::Type::I8, resolver::Type::I16, resolver::Type::I32, resolver::Type::I64, |
| 6347 | resolver::Type::Int => return true, |
| 6348 | else => return false, |
| 6349 | } |
| 6350 | } |
| 6351 | |
| 6352 | /// Check whether a resolver type is an unsigned integer type. |
| 6353 | fn isUnsignedType(t: resolver::Type) -> bool { |
| 6354 | match t { |
| 6355 | case resolver::Type::U8, resolver::Type::U16, resolver::Type::U32, resolver::Type::U64 => return true, |
| 6356 | else => return false, |
| 6357 | } |
| 6358 | } |
| 6359 | |
| 6360 | /// Lower a string literal to a slice value. |
| 6361 | /// |
| 6362 | /// String literals are stored as global data and the result is a slice |
| 6363 | /// pointing to the data with the appropriate length. |
| 6364 | 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 { |
| 6365 | // Get the slice type from the node. |
| 6366 | let sliceTy = try typeOf(self, node); |
| 6367 | let case resolver::Type::Slice { item, mutable, .. } = sliceTy |
| 6368 | else throw LowerError::ExpectedSliceOrArray; |
| 6369 | // Build the string data value. |
| 6370 | let ptr = try! alloc::alloc( |
| 6371 | self.low.arena, @sizeOf(il::DataValue), @alignOf(il::DataValue) |
| 6372 | ) as *mut il::DataValue; |
| 6373 | |
| 6374 | set *ptr = il::DataValue { item: il::DataItem::Str(s), count: 1 }; |
| 6375 | let result = ConstDataResult { values: @sliceOf(ptr, 1), zeroInit: false }; |
| 6376 | |
| 6377 | return try lowerConstDataAsSlice( |
| 6378 | self, &result, 1, true, item, mutable, s.len |
| 6379 | ); |
| 6380 | } |
| 6381 | |
| 6382 | /// Lower a builtin call expression. |
| 6383 | 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 { |
| 6384 | match kind { |
| 6385 | case ast::Builtin::SliceOf => return try lowerSliceOf(self, node, args), |
| 6386 | case ast::Builtin::SizeOf, ast::Builtin::AlignOf => { |
| 6387 | let constVal = resolver::constValueEntry(self.low.resolver, node) else { |
| 6388 | throw LowerError::MissingConst(node); |
| 6389 | }; |
| 6390 | return try constValueToVal(self, constVal, node); |
| 6391 | } |
| 6392 | } |
| 6393 | } |
| 6394 | |
| 6395 | /// Lower a `@sliceOf(ptr, len)` or `@sliceOf(ptr, len, cap)` builtin call. |
| 6396 | 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 { |
| 6397 | if args.len <> 2 and args.len <> 3 { |
| 6398 | throw LowerError::InvalidArgCount; |
| 6399 | } |
| 6400 | let sliceTy = try typeOf(self, node); |
| 6401 | let case resolver::Type::Slice { item, mutable, .. } = sliceTy |
| 6402 | else throw LowerError::ExpectedSliceOrArray; |
| 6403 | let ptrVal = try lowerExpr(self, args[0]); |
| 6404 | let lenVal = try lowerExpr(self, args[1]); |
| 6405 | let mut capVal = lenVal; |
| 6406 | if args.len == 3 { |
| 6407 | set capVal = try lowerExpr(self, args[2]); |
| 6408 | } |
| 6409 | if not isLtEq(lenVal, capVal) { |
| 6410 | try emitTrapIfLt(self, il::Type::W32, capVal, lenVal); |
| 6411 | } |
| 6412 | return try buildSliceValue(self, item, mutable, ptrVal, lenVal, capVal); |
| 6413 | } |
| 6414 | |
| 6415 | /// Lower a `try` expression. |
| 6416 | 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 { |
| 6417 | let case ast::NodeValue::Call(callExpr) = t.expr.value else { |
| 6418 | throw LowerError::ExpectedCall; |
| 6419 | }; |
| 6420 | let calleeTy = try typeOf(self, callExpr.callee); |
| 6421 | let case resolver::Type::Fn(calleeInfo) = calleeTy else { |
| 6422 | throw LowerError::ExpectedFunction; |
| 6423 | }; |
| 6424 | let okValueTy = *calleeInfo.returnType; // The type of the success payload. |
| 6425 | |
| 6426 | // Type of the try expression, which is either the return type of the function |
| 6427 | // if successful, or an optional of it, if using `try?`. |
| 6428 | let tryExprTy = try typeOf(self, node); |
| 6429 | // Check for trait method dispatch or standalone method call. |
| 6430 | let mut resVal: il::Val = undefined; |
| 6431 | let mut initialization: ?SessionInitialization = nil; |
| 6432 | let callNodeExtra = resolver::nodeData(self.low.resolver, t.expr).extra; |
| 6433 | if let case resolver::NodeExtra::SessionAllocation(allocation) = callNodeExtra { |
| 6434 | let prepared = try prepareSessionAllocation(self, callExpr, allocation); |
| 6435 | set initialization = prepared; |
| 6436 | set resVal = prepared.result; |
| 6437 | } else if let case resolver::NodeExtra::TraitMethodCall { |
| 6438 | traitInfo, methodIndex |
| 6439 | } = callNodeExtra { |
| 6440 | set resVal = try lowerTraitMethodCall(self, t.expr, callExpr, traitInfo, methodIndex); |
| 6441 | } else if let case resolver::NodeExtra::MethodCall { method } = callNodeExtra { |
| 6442 | set resVal = try lowerMethodCall(self, t.expr, callExpr, method); |
| 6443 | } else { |
| 6444 | set resVal = try lowerCall(self, t.expr, callExpr); |
| 6445 | } |
| 6446 | if blockHasTerminator(self) { |
| 6447 | return il::Val::Undef; |
| 6448 | } |
| 6449 | let base = emitValToReg(self, resVal); // The result value. |
| 6450 | let tagReg = resultTagReg(self, base); // The result tag. |
| 6451 | |
| 6452 | let okBlock = try createBlock(self, "ok"); // Block if success. |
| 6453 | let errBlock = try createBlock(self, "err"); // Block if failure. |
| 6454 | |
| 6455 | let mut mergeBlock: ?BlockId = nil; |
| 6456 | let mut resultSlot: ?il::Reg = nil; // `try` result value will be stored here. |
| 6457 | |
| 6458 | // Check if the `try` returns a success value or not. If so, reserve |
| 6459 | // space for it. |
| 6460 | let isVoid = tryExprTy == resolver::Type::Void or tryExprTy == resolver::Type::Never; |
| 6461 | if not isVoid { |
| 6462 | set resultSlot = try emitReserve(self, tryExprTy); |
| 6463 | } |
| 6464 | // Branch on tag: zero means ok, non-zero means error. |
| 6465 | try emitBr(self, tagReg, errBlock, okBlock); |
| 6466 | |
| 6467 | // We can now seal the blocks since all predecessors are known. |
| 6468 | try sealBlock(self, okBlock); |
| 6469 | try sealBlock(self, errBlock); |
| 6470 | |
| 6471 | // Success path: extract the successful value from the result and store it |
| 6472 | // in the result slot for later use after the merge point. |
| 6473 | switchToBlock(self, okBlock); |
| 6474 | if let prepared = initialization { |
| 6475 | try initializeSessionAllocation(self, prepared); |
| 6476 | } |
| 6477 | |
| 6478 | if okValueTy == resolver::Type::Never { |
| 6479 | emit(self, il::Instr::Unreachable); |
| 6480 | } else if let slot = resultSlot { |
| 6481 | // Extract the success payload. If the result type differs from the payload |
| 6482 | // type (e.g. `try?` wrapping `T` into `?T`), wrap the value. |
| 6483 | let payloadVal = tvalPayloadVal(self, base, okValueTy, RESULT_VAL_OFFSET); |
| 6484 | let mut okVal = payloadVal; |
| 6485 | |
| 6486 | if t.returnsOptional and tryExprTy <> okValueTy { |
| 6487 | set okVal = try wrapInOptional(self, payloadVal, tryExprTy); |
| 6488 | } |
| 6489 | try emitStore(self, slot, 0, tryExprTy, okVal); |
| 6490 | } |
| 6491 | // Jump to merge block if unterminated. |
| 6492 | try emitMergeIfUnterminated(self, &mut mergeBlock); |
| 6493 | |
| 6494 | // Error path: handle the failure case based on the try expression variant. |
| 6495 | switchToBlock(self, errBlock); |
| 6496 | |
| 6497 | if t.returnsOptional { |
| 6498 | // `try?` converts errors to `nil` -- store the `nil` and continue. |
| 6499 | if let slot = resultSlot { |
| 6500 | let errVal = try buildNilOptional(self, tryExprTy); |
| 6501 | try emitStore(self, slot, 0, tryExprTy, errVal); |
| 6502 | } |
| 6503 | try emitMergeIfUnterminated(self, &mut mergeBlock); |
| 6504 | } else if t.catches.len > 0 { |
| 6505 | // `try ... catch` -- handle the error. |
| 6506 | let firstNode = t.catches[0]; |
| 6507 | let case ast::NodeValue::CatchClause(first) = firstNode.value |
| 6508 | else panic "lowerTry: expected CatchClause"; |
| 6509 | |
| 6510 | if first.typeNode <> nil or t.catches.len > 1 { |
| 6511 | // Typed multi-catch: switch on global error tag. |
| 6512 | try lowerMultiCatch(self, t.catches, calleeInfo, base, tagReg, &mut mergeBlock); |
| 6513 | } else { |
| 6514 | // Single untyped catch clause. |
| 6515 | let savedVarsLen = enterVarScope(self); |
| 6516 | if let binding = first.binding { |
| 6517 | let case ast::NodeValue::Ident(name) = binding.value else { |
| 6518 | throw LowerError::ExpectedIdentifier; |
| 6519 | }; |
| 6520 | let errTy = *calleeInfo.throwList[0]; |
| 6521 | let errVal = tvalPayloadVal(self, base, errTy, RESULT_VAL_OFFSET); |
| 6522 | let _ = newVar(self, name, ilType(self.low, errTy), false, errVal); |
| 6523 | } |
| 6524 | try lowerBlock(self, first.body); |
| 6525 | try emitMergeIfUnterminated(self, &mut mergeBlock); |
| 6526 | exitVarScope(self, savedVarsLen); |
| 6527 | } |
| 6528 | } else if t.shouldPanic { |
| 6529 | // `try!` -- panic on error, emit unreachable since control won't continue. |
| 6530 | // TODO: We should have some kind of `panic` instruction? |
| 6531 | emit(self, il::Instr::Unreachable); |
| 6532 | } else { |
| 6533 | // Plain `try` -- propagate the error to the caller by returning early. |
| 6534 | // Forward the callee's global error tag and payload directly. |
| 6535 | let callerLayout = resolver::getResultLayout( |
| 6536 | *self.fnType.returnType, self.fnType.throwList |
| 6537 | ); |
| 6538 | let calleeErrSize = maxErrSize(calleeInfo.throwList); |
| 6539 | let dst = emitReserveLayout(self, callerLayout); |
| 6540 | |
| 6541 | emitStoreW64At(self, il::Val::Reg(tagReg), dst, TVAL_TAG_OFFSET); |
| 6542 | let srcPayload = emitPtrOffset(self, base, RESULT_VAL_OFFSET); |
| 6543 | let dstPayload = emitPtrOffset(self, dst, RESULT_VAL_OFFSET); |
| 6544 | emit(self, il::Instr::Blit { dst: dstPayload, src: srcPayload, size: il::Val::Imm(calleeErrSize as i64) }); |
| 6545 | |
| 6546 | try emitRetVal(self, il::Val::Reg(dst)); |
| 6547 | } |
| 6548 | |
| 6549 | // Switch to the merge block if one was created. If all paths diverged |
| 6550 | // (e.g both success and error returned), there's no merge block. |
| 6551 | if let blk = mergeBlock { |
| 6552 | try switchToAndSeal(self, blk); |
| 6553 | } else { |
| 6554 | return il::Val::Undef; |
| 6555 | } |
| 6556 | // Return the result value. For `void` expressions, return undefined. |
| 6557 | // For aggregates, return the slot pointer; for scalars, load the value. |
| 6558 | if let slot = resultSlot { |
| 6559 | if isAggregateType(tryExprTy) { |
| 6560 | return il::Val::Reg(slot); |
| 6561 | } |
| 6562 | return emitLoad(self, slot, 0, tryExprTy); |
| 6563 | } else { // Void return. |
| 6564 | return il::Val::Undef; |
| 6565 | } |
| 6566 | } |
| 6567 | |
| 6568 | /// Lower typed multi-catch clauses. |
| 6569 | /// |
| 6570 | /// Emits a switch on the global error tag to dispatch to the correct catch |
| 6571 | /// clause. Each typed clause extracts the error payload for its specific type |
| 6572 | /// and binds it to the clause's identifier. |
| 6573 | unsafe fn lowerMultiCatch 'arena 'phase 'function ( |
| 6574 | self: &mut FnLowerer 'arena 'phase 'function, |
| 6575 | catches: *[*ast::Node], |
| 6576 | calleeInfo: *resolver::FnType, |
| 6577 | base: il::Reg, |
| 6578 | tagReg: il::Reg, |
| 6579 | mergeBlock: &mut ?BlockId |
| 6580 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 6581 | let entry = currentBlock(self); |
| 6582 | |
| 6583 | // First pass: create blocks, resolve error types, and build switch cases. |
| 6584 | let mut blocks: [BlockId; MAX_CATCH_CLAUSES] = undefined; |
| 6585 | let mut errTypes: [?resolver::Type; MAX_CATCH_CLAUSES] = undefined; |
| 6586 | let mut cases: *unsafe mut [il::SwitchCase] = &mut []; |
| 6587 | let mut defaultIdx: ?u32 = nil; |
| 6588 | |
| 6589 | for clauseNode, i in catches { |
| 6590 | let case ast::NodeValue::CatchClause(clause) = clauseNode.value |
| 6591 | else panic "lowerMultiCatch: expected CatchClause"; |
| 6592 | |
| 6593 | set blocks[i] = try createBlock(self, "catch"); |
| 6594 | addPredecessor(self, blocks[i], entry); |
| 6595 | |
| 6596 | if let typeNode = clause.typeNode { |
| 6597 | let errTy = try typeOf(self, typeNode); |
| 6598 | set errTypes[i] = errTy; |
| 6599 | |
| 6600 | cases.append(il::SwitchCase { |
| 6601 | value: getOrAssignErrorTag(self.low, errTy) as i64, |
| 6602 | target: *blocks[i], |
| 6603 | args: &mut [] |
| 6604 | }, alloc::arenaAllocator(self.arena)); |
| 6605 | } else { |
| 6606 | set errTypes[i] = nil; |
| 6607 | set defaultIdx = i; |
| 6608 | } |
| 6609 | } |
| 6610 | |
| 6611 | // Emit switch. Default target is the catch-all block, or an unreachable block. |
| 6612 | let mut defaultTarget: BlockId = undefined; |
| 6613 | if let idx = defaultIdx { |
| 6614 | set defaultTarget = blocks[idx]; |
| 6615 | } else { |
| 6616 | set defaultTarget = try createBlock(self, "unreachable"); |
| 6617 | addPredecessor(self, defaultTarget, entry); |
| 6618 | } |
| 6619 | emit(self, il::Instr::Switch { |
| 6620 | val: il::Val::Reg(tagReg), |
| 6621 | defaultTarget: *defaultTarget, |
| 6622 | defaultArgs: &mut [], |
| 6623 | cases |
| 6624 | }); |
| 6625 | |
| 6626 | // Second pass: emit each catch clause body. |
| 6627 | for clauseNode, i in catches { |
| 6628 | let case ast::NodeValue::CatchClause(clause) = clauseNode.value |
| 6629 | else panic "lowerMultiCatch: expected CatchClause"; |
| 6630 | |
| 6631 | try switchToAndSeal(self, blocks[i]); |
| 6632 | let savedVarsLen = enterVarScope(self); |
| 6633 | |
| 6634 | if let binding = clause.binding { |
| 6635 | let case ast::NodeValue::Ident(name) = binding.value else { |
| 6636 | throw LowerError::ExpectedIdentifier; |
| 6637 | }; |
| 6638 | let errTy = errTypes[i] else panic "lowerMultiCatch: catch-all with binding"; |
| 6639 | let errVal = tvalPayloadVal(self, base, errTy, RESULT_VAL_OFFSET); |
| 6640 | |
| 6641 | newVar(self, name, ilType(self.low, errTy), false, errVal); |
| 6642 | } |
| 6643 | try lowerBlock(self, clause.body); |
| 6644 | try emitMergeIfUnterminated(self, mergeBlock); |
| 6645 | |
| 6646 | exitVarScope(self, savedVarsLen); |
| 6647 | } |
| 6648 | |
| 6649 | // Emit unreachable block if no catch-all. |
| 6650 | if defaultIdx == nil { |
| 6651 | try switchToAndSeal(self, defaultTarget); |
| 6652 | emit(self, il::Instr::Unreachable); |
| 6653 | } |
| 6654 | } |
| 6655 | |
| 6656 | /// Emit a byte-copy loop: `for i in 0..size { dst[i] = src[i]; }`. |
| 6657 | /// |
| 6658 | /// Used when `blit` cannot be used because the copy size is dynamic. |
| 6659 | /// Terminates the current block and leaves the builder positioned |
| 6660 | /// after the loop. |
| 6661 | unsafe fn emitByteCopyLoop 'arena 'phase 'function ( |
| 6662 | self: &mut FnLowerer 'arena 'phase 'function, |
| 6663 | dst: il::Reg, |
| 6664 | src: il::Reg, |
| 6665 | size: il::Val, |
| 6666 | label: *[u8] |
| 6667 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 6668 | let iReg = nextReg(self); |
| 6669 | let header = try createBlockWithParam( |
| 6670 | self, label, il::Param { value: iReg, type: il::Type::W32 } |
| 6671 | ); |
| 6672 | let body = try createBlock(self, label); |
| 6673 | let done = try createBlock(self, label); |
| 6674 | |
| 6675 | // Jump to header with initial counter is zero. |
| 6676 | try emitJmpWithArg(self, header, il::Val::Imm(0)); |
| 6677 | |
| 6678 | // Don't seal header yet -- the body will add another predecessor. |
| 6679 | switchToBlock(self, header); |
| 6680 | try emitBrCmp(self, il::CmpOp::Ult, il::Type::W32, il::Val::Reg(iReg), size, body, done); |
| 6681 | |
| 6682 | // Body: load byte from source, store to destination, increment counter. |
| 6683 | try switchToAndSeal(self, body); |
| 6684 | |
| 6685 | let srcElem = emitElem(self, 1, src, il::Val::Reg(iReg)); |
| 6686 | let byteReg = nextReg(self); |
| 6687 | emit(self, il::Instr::Load { typ: il::Type::W8, dst: byteReg, src: srcElem, offset: 0 }); |
| 6688 | |
| 6689 | let dstElem = emitElem(self, 1, dst, il::Val::Reg(iReg)); |
| 6690 | emit(self, il::Instr::Store { typ: il::Type::W8, src: il::Val::Reg(byteReg), dst: dstElem, offset: 0 }); |
| 6691 | |
| 6692 | let nextI = emitTypedBinOp(self, il::BinOp::Add, il::Type::W32, il::Val::Reg(iReg), il::Val::Imm(1)); |
| 6693 | // Jump back to header -- this adds body as a predecessor. |
| 6694 | try emitJmpWithArg(self, header, nextI); |
| 6695 | |
| 6696 | // Now all predecessors of header are known, seal it. |
| 6697 | try sealBlock(self, header); |
| 6698 | try switchToAndSeal(self, done); |
| 6699 | } |
| 6700 | |
| 6701 | /// Lower `slice.append(val, allocator)`. |
| 6702 | /// |
| 6703 | /// Emits inline grow-if-needed logic: |
| 6704 | /// |
| 6705 | /// load len, cap from slice header |
| 6706 | /// if len < cap: jmp @store |
| 6707 | /// else: jmp @grow |
| 6708 | /// |
| 6709 | /// @grow: |
| 6710 | /// newCap = max(cap * 2, 1) |
| 6711 | /// call allocator.func(allocator.ctx, newCap * stride, alignment) |
| 6712 | /// copy old data to new pointer |
| 6713 | /// update slice ptr and cap |
| 6714 | /// jmp @store |
| 6715 | /// |
| 6716 | /// @store: |
| 6717 | /// store element at ptr + len * stride |
| 6718 | /// increment len |
| 6719 | /// |
| 6720 | 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 { |
| 6721 | let case ast::NodeValue::FieldAccess(access) = call.callee.value |
| 6722 | else throw LowerError::MissingMetadata; |
| 6723 | |
| 6724 | // Get the address of the slice header. |
| 6725 | let sliceVal = try lowerExpr(self, access.parent); |
| 6726 | let sliceReg = emitValToReg(self, sliceVal); |
| 6727 | |
| 6728 | // Lower the value to append and the allocator. |
| 6729 | let elemVal = try lowerExpr(self, call.args[0]); |
| 6730 | let allocVal = try lowerExpr(self, call.args[1]); |
| 6731 | let allocReg = emitValToReg(self, allocVal); |
| 6732 | |
| 6733 | let elemLayout = resolver::getTypeLayout(*elemType); |
| 6734 | let stride = elemLayout.size; |
| 6735 | let alignment = elemLayout.alignment; |
| 6736 | |
| 6737 | // Load current length and capacity. |
| 6738 | let lenVal = loadSliceLen(self, sliceReg); |
| 6739 | let capVal = loadSliceCap(self, sliceReg); |
| 6740 | |
| 6741 | // Branch: if length is smaller than capacity, go to @store else @grow. |
| 6742 | let storeBlock = try createBlock(self, "append.store"); |
| 6743 | let growBlock = try createBlock(self, "append.grow"); |
| 6744 | try emitBrCmp(self, il::CmpOp::Ult, il::Type::W32, lenVal, capVal, storeBlock, growBlock); |
| 6745 | try switchToAndSeal(self, growBlock); |
| 6746 | |
| 6747 | // -- @grow block ---------------------------------------------------------- |
| 6748 | |
| 6749 | // `newCap = max(cap * 2, 1)`. |
| 6750 | // We are only here when at capacity, so we use `or` with `1` to ensure at least capacity `1`. |
| 6751 | let doubledVal = emitTypedBinOp(self, il::BinOp::Shl, il::Type::W32, capVal, il::Val::Imm(1)); |
| 6752 | let newCapVal = emitTypedBinOp(self, il::BinOp::Or, il::Type::W32, doubledVal, il::Val::Imm(1)); |
| 6753 | |
| 6754 | // Call allocator: `a.func(a.ctx, newCap * stride, alignment)`. |
| 6755 | let allocFnReg = nextReg(self); |
| 6756 | emitLoadW64At(self, allocFnReg, allocReg, 0); |
| 6757 | |
| 6758 | let allocCtxReg = nextReg(self); |
| 6759 | emitLoadW64At(self, allocCtxReg, allocReg, 8); |
| 6760 | |
| 6761 | let byteSize = emitTypedBinOp(self, il::BinOp::Mul, il::Type::W32, newCapVal, il::Val::Imm(stride as i64)); |
| 6762 | let args = try allocVals(self, 3); |
| 6763 | |
| 6764 | set args[0] = il::Val::Reg(allocCtxReg); |
| 6765 | set args[1] = byteSize; |
| 6766 | set args[2] = il::Val::Imm(alignment as i64); |
| 6767 | |
| 6768 | let newPtrReg = nextReg(self); |
| 6769 | emit(self, il::Instr::Call { |
| 6770 | retTy: il::Type::W64, |
| 6771 | dst: newPtrReg, |
| 6772 | func: il::Val::Reg(allocFnReg), |
| 6773 | args, |
| 6774 | }); |
| 6775 | |
| 6776 | // Copy old data byte-by-byte. |
| 6777 | let oldPtrReg = loadSlicePtr(self, sliceReg); |
| 6778 | let copyBytes = emitTypedBinOp(self, il::BinOp::Mul, il::Type::W32, lenVal, il::Val::Imm(stride as i64)); |
| 6779 | try emitByteCopyLoop(self, newPtrReg, oldPtrReg, copyBytes, "append"); |
| 6780 | |
| 6781 | // Update slice header. |
| 6782 | emitStoreW64At(self, il::Val::Reg(newPtrReg), sliceReg, SLICE_PTR_OFFSET); |
| 6783 | emitStoreW32At(self, newCapVal, sliceReg, SLICE_CAP_OFFSET); |
| 6784 | |
| 6785 | try emitJmp(self, storeBlock); |
| 6786 | try switchToAndSeal(self, storeBlock); |
| 6787 | |
| 6788 | // -- @store block --------------------------------------------------------- |
| 6789 | |
| 6790 | // Store element at `ptr + len * stride`. |
| 6791 | let ptrReg = loadSlicePtr(self, sliceReg); |
| 6792 | let elemDst = emitElem(self, stride, ptrReg, lenVal); |
| 6793 | try emitStore(self, elemDst, 0, *elemType, elemVal); |
| 6794 | |
| 6795 | // Increment len. |
| 6796 | let newLen = emitTypedBinOp(self, il::BinOp::Add, il::Type::W32, lenVal, il::Val::Imm(1)); |
| 6797 | emitStoreW32At(self, newLen, sliceReg, SLICE_LEN_OFFSET); |
| 6798 | |
| 6799 | return il::Val::Reg(sliceReg); |
| 6800 | } |
| 6801 | |
| 6802 | /// Lower `slice.delete(index)`. |
| 6803 | /// |
| 6804 | /// Bounds-check the index, shift elements after it by one stride |
| 6805 | /// via a byte-copy loop, and decrement `len`. |
| 6806 | 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 { |
| 6807 | let case ast::NodeValue::FieldAccess(access) = call.callee.value |
| 6808 | else throw LowerError::MissingMetadata; |
| 6809 | |
| 6810 | let elemLayout = resolver::getTypeLayout(*elemType); |
| 6811 | let stride = elemLayout.size; |
| 6812 | |
| 6813 | // Get slice header address. |
| 6814 | let sliceVal = try lowerExpr(self, access.parent); |
| 6815 | let sliceReg = emitValToReg(self, sliceVal); |
| 6816 | |
| 6817 | // Lower the index argument. |
| 6818 | let indexVal = try lowerExpr(self, call.args[0]); |
| 6819 | |
| 6820 | // Load len and bounds-check: index must be smaller than length. |
| 6821 | let lenVal = loadSliceLen(self, sliceReg); |
| 6822 | try emitTrapUnlessCmp(self, il::CmpOp::Ult, il::Type::W32, indexVal, lenVal); |
| 6823 | |
| 6824 | // Compute the destination and source for the shift. |
| 6825 | let ptrReg = loadSlicePtr(self, sliceReg); |
| 6826 | let dst = emitElem(self, stride, ptrReg, indexVal); |
| 6827 | |
| 6828 | // `src = dst + stride`. |
| 6829 | let src = emitPtrOffset(self, dst, stride as i32); |
| 6830 | |
| 6831 | // Move `(len - index - 1) * stride`. |
| 6832 | let tailLen = emitTypedBinOp(self, il::BinOp::Sub, il::Type::W32, lenVal, indexVal); |
| 6833 | let tailLenMinusOne = emitTypedBinOp(self, il::BinOp::Sub, il::Type::W32, tailLen, il::Val::Imm(1)); |
| 6834 | let moveBytes = emitTypedBinOp(self, il::BinOp::Mul, il::Type::W32, tailLenMinusOne, il::Val::Imm(stride as i64)); |
| 6835 | |
| 6836 | // Shift elements left via byte-copy loop. |
| 6837 | // When deleting the last element, the loop is a no-op. |
| 6838 | try emitByteCopyLoop(self, dst, src, moveBytes, "delete"); |
| 6839 | // Decrement length. |
| 6840 | let newLen = emitTypedBinOp(self, il::BinOp::Sub, il::Type::W32, lenVal, il::Val::Imm(1)); |
| 6841 | |
| 6842 | emitStoreW32At(self, newLen, sliceReg, SLICE_LEN_OFFSET); |
| 6843 | } |
| 6844 | |
| 6845 | /// Initializer values retained across a fallible session reservation. |
| 6846 | record SessionInitialization: Copy { |
| 6847 | /// Resolved allocation operation and element type. |
| 6848 | allocation: resolver::SessionAllocation, |
| 6849 | /// Runtime reservation result. |
| 6850 | result: il::Val, |
| 6851 | /// Evaluated object, fill value, or source slice. |
| 6852 | value: il::Val, |
| 6853 | /// Evaluated number of elements. |
| 6854 | count: il::Val, |
| 6855 | } |
| 6856 | |
| 6857 | /// Evaluate initializer values and reserve their session storage. |
| 6858 | unsafe fn prepareSessionAllocation 'arena 'phase 'function ( |
| 6859 | self: &mut FnLowerer 'arena 'phase 'function, call: ast::Call, allocation: resolver::SessionAllocation |
| 6860 | ) -> SessionInitialization throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 6861 | let case ast::NodeValue::FieldAccess(access) = call.callee.value else throw LowerError::ExpectedCall; |
| 6862 | let receiver = try lowerExpr(self, access.parent); |
| 6863 | let sessionReg = emitValToReg(self, receiver); |
| 6864 | let dataReg = nextReg(self); |
| 6865 | emitLoadW64At(self, dataReg, sessionReg, TRAIT_OBJ_DATA_OFFSET); |
| 6866 | let vtableReg = nextReg(self); |
| 6867 | emitLoadW64At(self, vtableReg, sessionReg, TRAIT_OBJ_VTABLE_OFFSET); |
| 6868 | let functionReg = nextReg(self); |
| 6869 | emitLoadW64At( |
| 6870 | self, functionReg, vtableReg, |
| 6871 | (allocation.methodIndex * resolver::PTR_SIZE) as i32, |
| 6872 | ); |
| 6873 | let value = try lowerCallArg(self, call.args[0], true); |
| 6874 | if blockHasTerminator(self) { |
| 6875 | return SessionInitialization { allocation, result: il::Val::Undef, value, count: il::Val::Undef }; |
| 6876 | } |
| 6877 | let slice = allocation.kind <> resolver::SessionAllocationKind::New; |
| 6878 | let mut count = il::Val::Imm(1); |
| 6879 | if allocation.kind == resolver::SessionAllocationKind::Copy { |
| 6880 | set count = loadSliceLen(self, emitValToReg(self, value)); |
| 6881 | } else if allocation.kind == resolver::SessionAllocationKind::Fill { |
| 6882 | set count = try lowerExpr(self, call.args[1]); |
| 6883 | if blockHasTerminator(self) { |
| 6884 | return SessionInitialization { allocation, result: il::Val::Undef, value, count: il::Val::Undef }; |
| 6885 | } |
| 6886 | } |
| 6887 | let layout = resolver::getTypeLayout(*allocation.item); |
| 6888 | let size = layout.size if layout.size > 0 else 1; |
| 6889 | let alignment = layout.alignment if layout.alignment > 0 else 1; |
| 6890 | let runtime = allocation.traitInfo.methods[allocation.methodIndex].fnType; |
| 6891 | let argOffset: u32 = 1 if requiresReturnParam(runtime) else 0; |
| 6892 | let args = try allocVals(self, (4 if slice else 3) + argOffset); |
| 6893 | set args[argOffset] = il::Val::Reg(dataReg); |
| 6894 | set args[argOffset + 1] = il::Val::Imm(size as i64); |
| 6895 | set args[argOffset + 2] = il::Val::Imm(alignment as i64); |
| 6896 | if slice { |
| 6897 | set args[argOffset + 3] = count; |
| 6898 | } |
| 6899 | let result = try emitCallValue(self, il::Val::Reg(functionReg), runtime, args); |
| 6900 | return SessionInitialization { allocation, result, value, count }; |
| 6901 | } |
| 6902 | |
| 6903 | /// Initialize successful reservation storage before its reference is published. |
| 6904 | unsafe fn initializeSessionAllocation 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, prepared: SessionInitialization) |
| 6905 | throws (LowerError) where 'arena: 'phase, 'phase: 'function |
| 6906 | { |
| 6907 | let allocation = prepared.allocation; |
| 6908 | let base = emitValToReg(self, prepared.result); |
| 6909 | let value = prepared.value; |
| 6910 | let count = prepared.count; |
| 6911 | let layout = resolver::getTypeLayout(*allocation.item); |
| 6912 | let destination = nextReg(self); |
| 6913 | emitLoadW64At(self, destination, base, RESULT_VAL_OFFSET); |
| 6914 | match allocation.kind { |
| 6915 | case resolver::SessionAllocationKind::New => |
| 6916 | try emitStore(self, destination, 0, *allocation.item, value), |
| 6917 | case resolver::SessionAllocationKind::Copy => { |
| 6918 | let source = loadSlicePtr(self, emitValToReg(self, value)); |
| 6919 | let bytes = emitTypedBinOp(self, il::BinOp::Mul, il::Type::W32, count, il::Val::Imm(layout.size as i64)); |
| 6920 | try emitByteCopyLoop(self, destination, source, bytes, "allocate.copy"); |
| 6921 | } |
| 6922 | case resolver::SessionAllocationKind::Fill => |
| 6923 | try emitFillLoop(self, destination, value, count, *allocation.item, layout.size), |
| 6924 | } |
| 6925 | } |
| 6926 | |
| 6927 | /// Lower a call expression, which may be a function call or type constructor. |
| 6928 | 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 { |
| 6929 | let nodeData = resolver::nodeData(self.low.resolver, node).extra; |
| 6930 | |
| 6931 | // Check for slice method dispatch. |
| 6932 | if let case resolver::NodeExtra::SliceAppend { elemType } = nodeData { |
| 6933 | return try lowerSliceAppend(self, call, elemType); |
| 6934 | } |
| 6935 | if let case resolver::NodeExtra::SliceDelete { elemType } = nodeData { |
| 6936 | try lowerSliceDelete(self, call, elemType); |
| 6937 | return il::Val::Undef; |
| 6938 | } |
| 6939 | // Check for trait method dispatch. |
| 6940 | if let case resolver::NodeExtra::TraitMethodCall { traitInfo, methodIndex } = nodeData { |
| 6941 | return try lowerTraitMethodCall(self, node, call, traitInfo, methodIndex); |
| 6942 | } |
| 6943 | // Check for standalone method call. |
| 6944 | if let case resolver::NodeExtra::MethodCall { method } = nodeData { |
| 6945 | return try lowerMethodCall(self, node, call, method); |
| 6946 | } |
| 6947 | if let sym = resolver::nodeData(self.low.resolver, call.callee).sym { |
| 6948 | if let case resolver::SymbolData::Type(_) = sym.data { |
| 6949 | let ty = try typeOf(self, node); |
| 6950 | let case resolver::Type::Nominal(nominal) = ty else throw LowerError::ExpectedRecord; |
| 6951 | return try lowerRecordCtor(self, nominal, call.args); |
| 6952 | } |
| 6953 | if let case resolver::SymbolData::Variant { .. } = sym.data { |
| 6954 | return try lowerUnionCtor(self, node, sym, call); |
| 6955 | } |
| 6956 | } |
| 6957 | return try lowerCall(self, node, call); |
| 6958 | } |
| 6959 | |
| 6960 | /// Lower a trait method call through v-table dispatch. |
| 6961 | /// |
| 6962 | /// Given `obj.method(args)` where `obj` is a trait object, emits: |
| 6963 | /// |
| 6964 | /// load w64 %data %obj 0 // data pointer |
| 6965 | /// load w64 %vtable %obj 8 // v-table pointer |
| 6966 | /// load w64 %fn %vtable <slot> // function pointer |
| 6967 | /// call <retTy> %ret %fn(%data, args...) |
| 6968 | /// |
| 6969 | unsafe fn lowerTraitMethodCall 'arena 'phase 'function ( |
| 6970 | self: &mut FnLowerer 'arena 'phase 'function, |
| 6971 | node: *ast::Node, |
| 6972 | call: ast::Call, |
| 6973 | traitInfo: *unsafe resolver::TraitType, |
| 6974 | methodIndex: u32 |
| 6975 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 6976 | // Method calls look like field accesses. |
| 6977 | let case ast::NodeValue::FieldAccess(access) = call.callee.value |
| 6978 | else throw LowerError::MissingMetadata; |
| 6979 | |
| 6980 | // Lower the trait object expression. |
| 6981 | let traitObjVal = try lowerExpr(self, access.parent); |
| 6982 | let traitObjReg = emitValToReg(self, traitObjVal); |
| 6983 | |
| 6984 | // Load data pointer from trait object. |
| 6985 | let dataReg = nextReg(self); |
| 6986 | emit(self, il::Instr::Load { |
| 6987 | typ: il::Type::W64, |
| 6988 | dst: dataReg, |
| 6989 | src: traitObjReg, |
| 6990 | offset: TRAIT_OBJ_DATA_OFFSET, |
| 6991 | }); |
| 6992 | |
| 6993 | // Load v-table pointer from trait object. |
| 6994 | let vtableReg = nextReg(self); |
| 6995 | emit(self, il::Instr::Load { |
| 6996 | typ: il::Type::W64, |
| 6997 | dst: vtableReg, |
| 6998 | src: traitObjReg, |
| 6999 | offset: TRAIT_OBJ_VTABLE_OFFSET, |
| 7000 | }); |
| 7001 | |
| 7002 | // Load function pointer from v-table at the method's slot offset. |
| 7003 | let fnPtrReg = nextReg(self); |
| 7004 | let slotOffset = (methodIndex * resolver::PTR_SIZE) as i32; |
| 7005 | |
| 7006 | emit(self, il::Instr::Load { |
| 7007 | typ: il::Type::W64, |
| 7008 | dst: fnPtrReg, |
| 7009 | src: vtableReg, |
| 7010 | offset: slotOffset, |
| 7011 | }); |
| 7012 | let methodFnType = traitInfo.methods[methodIndex].fnType; |
| 7013 | |
| 7014 | // Build args: optional return param slot + data pointer (receiver) + user args. |
| 7015 | let argOffset: u32 = 1 if requiresReturnParam(methodFnType) else 0; |
| 7016 | let args = try allocVals(self, call.args.len + 1 + argOffset); |
| 7017 | set args[argOffset] = il::Val::Reg(dataReg); |
| 7018 | |
| 7019 | for arg, i in call.args { |
| 7020 | set args[i + 1 + argOffset] = try lowerCallArg( |
| 7021 | self, arg, i + 1 < call.args.len |
| 7022 | ); |
| 7023 | } |
| 7024 | return try emitCallValue(self, il::Val::Reg(fnPtrReg), methodFnType, args); |
| 7025 | } |
| 7026 | |
| 7027 | /// Lower a call argument, snapshotting aggregate place expressions before |
| 7028 | /// evaluating later arguments. Aggregates are represented by addresses in the |
| 7029 | /// IL, so retaining the original address would let a later argument mutation |
| 7030 | /// change the value already supplied for this argument. |
| 7031 | unsafe fn lowerCallArg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, arg: *ast::Node, hasLater: bool) -> il::Val |
| 7032 | throws (LowerError) where 'arena: 'phase, 'phase: 'function |
| 7033 | { |
| 7034 | let val = try lowerExpr(self, arg); |
| 7035 | |
| 7036 | if hasLater and ast::isPlaceExpr(arg) { |
| 7037 | let argType = try effectiveType(self, arg); |
| 7038 | if isAggregateType(argType) { |
| 7039 | return try emitStackVal(self, argType, val); |
| 7040 | } |
| 7041 | } |
| 7042 | return val; |
| 7043 | } |
| 7044 | |
| 7045 | /// Emit a function call with return-parameter and small-aggregate handling. |
| 7046 | /// |
| 7047 | /// All call lowering paths (regular, trait method, standalone method) converge |
| 7048 | /// here after preparing the callee value, function type, and argument array. |
| 7049 | /// The `args` slice must already include a slot at index zero for the hidden |
| 7050 | /// return parameter; that slot is filled by this function. |
| 7051 | unsafe fn emitCallValue 'arena 'phase 'function ( |
| 7052 | self: &mut FnLowerer 'arena 'phase 'function, |
| 7053 | callee: il::Val, |
| 7054 | fnInfo: *resolver::FnType, |
| 7055 | args: *unsafe mut [il::Val], |
| 7056 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 7057 | let retTy = *fnInfo.returnType; |
| 7058 | |
| 7059 | if requiresReturnParam(fnInfo) { |
| 7060 | if fnInfo.throwList.len > 0 { |
| 7061 | let layout = resolver::getResultLayout(retTy, fnInfo.throwList); |
| 7062 | set args[0] = il::Val::Reg(emitReserveLayout(self, layout)); |
| 7063 | } else { |
| 7064 | set args[0] = il::Val::Reg(try emitReserve(self, retTy)); |
| 7065 | } |
| 7066 | let dst = nextReg(self); |
| 7067 | |
| 7068 | emit(self, il::Instr::Call { |
| 7069 | retTy: il::Type::W64, |
| 7070 | dst, |
| 7071 | func: callee, |
| 7072 | args, |
| 7073 | }); |
| 7074 | return il::Val::Reg(dst); |
| 7075 | } |
| 7076 | let mut dst: ?il::Reg = nil; |
| 7077 | if retTy <> resolver::Type::Void and retTy <> resolver::Type::Never { |
| 7078 | set dst = nextReg(self); |
| 7079 | } |
| 7080 | emit(self, il::Instr::Call { |
| 7081 | retTy: ilType(self.low, retTy), |
| 7082 | dst, |
| 7083 | func: callee, |
| 7084 | args, |
| 7085 | }); |
| 7086 | |
| 7087 | if retTy == resolver::Type::Never { |
| 7088 | emit(self, il::Instr::Unreachable); |
| 7089 | } |
| 7090 | if let d = dst { |
| 7091 | if isSmallAggregate(retTy) { |
| 7092 | let slot = emitReserveLayout(self, resolver::Layout { |
| 7093 | size: resolver::PTR_SIZE, |
| 7094 | alignment: resolver::PTR_SIZE, |
| 7095 | }); |
| 7096 | emit(self, il::Instr::Store { |
| 7097 | typ: il::Type::W64, |
| 7098 | src: il::Val::Reg(d), |
| 7099 | dst: slot, |
| 7100 | offset: 0, |
| 7101 | }); |
| 7102 | return il::Val::Reg(slot); |
| 7103 | } |
| 7104 | return il::Val::Reg(d); |
| 7105 | } |
| 7106 | return il::Val::Undef; |
| 7107 | } |
| 7108 | |
| 7109 | /// Lower a method receiver expression to a pointer value. |
| 7110 | /// |
| 7111 | /// If the parent is already a pointer type, the value is used directly. |
| 7112 | /// If the parent is a value type (eg. a local record), its address is taken. |
| 7113 | unsafe fn lowerReceiver 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, parent: *ast::Node, parentTy: resolver::Type) -> il::Val |
| 7114 | throws (LowerError) where 'arena: 'phase, 'phase: 'function |
| 7115 | { |
| 7116 | if let case resolver::Type::Pointer { .. } = parentTy { |
| 7117 | // Already a pointer: lower and use directly. |
| 7118 | return try lowerExpr(self, parent); |
| 7119 | } |
| 7120 | // Value type: take its address by lowering it and returning the slot pointer. |
| 7121 | // Aggregate types are already lowered as pointers to stack slots. |
| 7122 | let val = try lowerExpr(self, parent); |
| 7123 | if isAggregateType(parentTy) { |
| 7124 | return val; |
| 7125 | } |
| 7126 | // Scalar value: store to a stack slot and return the slot pointer. |
| 7127 | let layout = resolver::getLayout(self.low.resolver, parent, parentTy); |
| 7128 | let slot = emitReserveLayout(self, layout); |
| 7129 | try emitStore(self, slot, 0, parentTy, val); |
| 7130 | |
| 7131 | return il::Val::Reg(slot); |
| 7132 | } |
| 7133 | |
| 7134 | /// Lower a standalone method call via direct dispatch. |
| 7135 | /// |
| 7136 | /// Given `obj.method(args)` where `method` is a standalone method on a concrete type, |
| 7137 | /// emits a direct call with the receiver address as the first argument: |
| 7138 | /// |
| 7139 | /// call <retTy> %ret @Type::method(&obj, args...) |
| 7140 | /// |
| 7141 | unsafe fn lowerMethodCall 'arena 'phase 'function ( |
| 7142 | self: &mut FnLowerer 'arena 'phase 'function, |
| 7143 | node: *ast::Node, |
| 7144 | call: ast::Call, |
| 7145 | method: &resolver::MethodEntry, |
| 7146 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 7147 | let case ast::NodeValue::FieldAccess(access) = call.callee.value |
| 7148 | else throw LowerError::MissingMetadata; |
| 7149 | |
| 7150 | // Get the receiver as a pointer. |
| 7151 | let parentTy = try typeOf(self, access.parent); |
| 7152 | let receiverVal = try lowerReceiver(self, access.parent, parentTy); |
| 7153 | |
| 7154 | let qualName = instanceMethodName(self.low, nil, method.concreteTypeName, method.name); |
| 7155 | let case resolver::SymbolData::Value { type: resolver::Type::Fn(fnInfo), .. } = method.symbol.data |
| 7156 | else panic "lowerMethodCall: expected Fn type on method symbol"; |
| 7157 | |
| 7158 | // Build args: optional return param slot + receiver + user args. |
| 7159 | let argOffset: u32 = 1 if requiresReturnParam(fnInfo) else 0; |
| 7160 | let args = try allocVals(self, call.args.len + 1 + argOffset); |
| 7161 | set args[argOffset] = receiverVal; |
| 7162 | for arg, i in call.args { |
| 7163 | set args[i + 1 + argOffset] = try lowerCallArg( |
| 7164 | self, arg, i + 1 < call.args.len |
| 7165 | ); |
| 7166 | } |
| 7167 | return try emitCallValue(self, il::Val::FnAddr(qualName), fnInfo, args); |
| 7168 | } |
| 7169 | |
| 7170 | /// Check if a call is to a compiler intrinsic and lower it directly. |
| 7171 | unsafe fn lowerIntrinsicCall 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, call: ast::Call) -> ?il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 7172 | // Get the callee symbol and check if it's marked as an intrinsic. |
| 7173 | let sym = resolver::nodeData(self.low.resolver, call.callee).sym else { |
| 7174 | // Expressions or function pointers may not have an associated symbol. |
| 7175 | return nil; |
| 7176 | }; |
| 7177 | if not ast::hasAttribute(sym.attrs, ast::Attribute::Intrinsic) { |
| 7178 | return nil; |
| 7179 | } |
| 7180 | // Check for known intrinsic names. |
| 7181 | if mem::eq(sym.name, "ecall") { |
| 7182 | return try lowerEcall(self, call); |
| 7183 | } else if mem::eq(sym.name, "deviceRead8") { |
| 7184 | return try lowerDevice(self, call, il::Type::W8, false); |
| 7185 | } else if mem::eq(sym.name, "deviceWrite8") { |
| 7186 | return try lowerDevice(self, call, il::Type::W8, true); |
| 7187 | } else if mem::eq(sym.name, "deviceRead16") { |
| 7188 | return try lowerDevice(self, call, il::Type::W16, false); |
| 7189 | } else if mem::eq(sym.name, "deviceWrite16") { |
| 7190 | return try lowerDevice(self, call, il::Type::W16, true); |
| 7191 | } else if mem::eq(sym.name, "deviceRead32") { |
| 7192 | return try lowerDevice(self, call, il::Type::W32, false); |
| 7193 | } else if mem::eq(sym.name, "deviceWrite32") { |
| 7194 | return try lowerDevice(self, call, il::Type::W32, true); |
| 7195 | } else if mem::eq(sym.name, "deviceRead64") { |
| 7196 | return try lowerDevice(self, call, il::Type::W64, false); |
| 7197 | } else if mem::eq(sym.name, "deviceWrite64") { |
| 7198 | return try lowerDevice(self, call, il::Type::W64, true); |
| 7199 | } else if mem::eq(sym.name, "ebreak") { |
| 7200 | return try lowerEbreak(self, call); |
| 7201 | } else if mem::eq(sym.name, "memoryFence") { |
| 7202 | return try lowerMemoryFence(self, call); |
| 7203 | } else { |
| 7204 | throw LowerError::UnknownIntrinsic; |
| 7205 | } |
| 7206 | } |
| 7207 | |
| 7208 | /// Lower an ecall intrinsic: `ecall(num, a0, a1, a2, a3) -> i32`. |
| 7209 | unsafe fn lowerEcall 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, call: ast::Call) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 7210 | if call.args.len <> 5 { |
| 7211 | throw LowerError::InvalidArgCount; |
| 7212 | } |
| 7213 | let num = try lowerExpr(self, call.args[0]); |
| 7214 | let a0 = try lowerExpr(self, call.args[1]); |
| 7215 | let a1 = try lowerExpr(self, call.args[2]); |
| 7216 | let a2 = try lowerExpr(self, call.args[3]); |
| 7217 | let a3 = try lowerExpr(self, call.args[4]); |
| 7218 | let dst = nextReg(self); |
| 7219 | |
| 7220 | emit(self, il::Instr::Ecall { dst, num, a0, a1, a2, a3 }); |
| 7221 | |
| 7222 | return il::Val::Reg(dst); |
| 7223 | } |
| 7224 | |
| 7225 | /// Lower an ebreak intrinsic: `ebreak()`. |
| 7226 | unsafe fn lowerEbreak 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, call: ast::Call) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 7227 | if call.args.len <> 0 { |
| 7228 | throw LowerError::InvalidArgCount; |
| 7229 | } |
| 7230 | emit(self, il::Instr::Ebreak); |
| 7231 | |
| 7232 | return il::Val::Undef; |
| 7233 | } |
| 7234 | |
| 7235 | /// Lower `memoryFence()`. |
| 7236 | unsafe fn lowerMemoryFence 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, call: ast::Call) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 7237 | if call.args.len <> 0 { |
| 7238 | throw LowerError::InvalidArgCount; |
| 7239 | } |
| 7240 | emit(self, il::Instr::MemoryFence); |
| 7241 | return il::Val::Undef; |
| 7242 | } |
| 7243 | |
| 7244 | /// Resolve callee to an IL value. For direct function calls, use the symbol name. |
| 7245 | /// For variables holding function pointers or complex expressions (eg. `array[i]()`), |
| 7246 | /// lower the callee expression. |
| 7247 | unsafe fn lowerCallee 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, callee: *ast::Node) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 7248 | if let sym = resolver::nodeData(self.low.resolver, callee).sym { |
| 7249 | if let case ast::NodeValue::FnDecl(_) = sym.node.value { |
| 7250 | // First try to look up the symbol in our registered functions. |
| 7251 | // This handles cross-package calls correctly, since packages are |
| 7252 | // lowered in dependency order. |
| 7253 | if let qualName = lookupSymbolName(self.low, sym) { |
| 7254 | return il::Val::FnAddr(qualName); |
| 7255 | } |
| 7256 | // Fall back to computing the qualified name from the module graph. |
| 7257 | // This works for functions in the current package. |
| 7258 | let modId = resolver::moduleIdForSymbol(self.low.resolver, sym); |
| 7259 | return il::Val::FnAddr(qualifyName(self.low, modId, sym.name)); |
| 7260 | } |
| 7261 | } |
| 7262 | return try lowerExpr(self, callee); |
| 7263 | } |
| 7264 | |
| 7265 | /// Lower a function call expression. |
| 7266 | 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 { |
| 7267 | // Check for intrinsic calls before normal call lowering. |
| 7268 | if let intrinsicVal = try lowerIntrinsicCall(self, call) { |
| 7269 | return intrinsicVal; |
| 7270 | } |
| 7271 | let calleeTy = try typeOf(self, call.callee); |
| 7272 | let case resolver::Type::Fn(fnInfo) = calleeTy else { |
| 7273 | throw LowerError::ExpectedFunction; |
| 7274 | }; |
| 7275 | let callee = try lowerCallee(self, call.callee); |
| 7276 | let offset: u32 = 1 if requiresReturnParam(fnInfo) else 0; |
| 7277 | let args = try allocVals(self, call.args.len + offset); |
| 7278 | for arg, i in call.args { |
| 7279 | set args[i + offset] = try lowerCallArg( |
| 7280 | self, arg, i + 1 < call.args.len |
| 7281 | ); |
| 7282 | } |
| 7283 | |
| 7284 | return try emitCallValue(self, callee, fnInfo, args); |
| 7285 | } |
| 7286 | |
| 7287 | /// Apply coercions requested by the resolver. |
| 7288 | 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 { |
| 7289 | let coerce = resolver::coercionFor(self.low.resolver, node) else { |
| 7290 | return val; |
| 7291 | }; |
| 7292 | match coerce { |
| 7293 | case resolver::Coercion::OptionalLift(optType) => { |
| 7294 | if let case ast::NodeValue::Nil = node.value { |
| 7295 | return try buildNilOptional(self, optType); |
| 7296 | } |
| 7297 | return try wrapInOptional(self, val, optType); |
| 7298 | } |
| 7299 | case resolver::Coercion::NumericCast { from, to } => { |
| 7300 | return lowerNumericCast(self, val, from, to); |
| 7301 | } |
| 7302 | case resolver::Coercion::ResultWrap => { |
| 7303 | let payloadType = *self.fnType.returnType; |
| 7304 | return try buildResult(self, 0, val, payloadType); |
| 7305 | } |
| 7306 | case resolver::Coercion::TraitObject { traitInfo, inst } => { |
| 7307 | return try buildTraitObject(self, val, traitInfo, inst); |
| 7308 | } |
| 7309 | case resolver::Coercion::Identity => return val, |
| 7310 | } |
| 7311 | } |
| 7312 | |
| 7313 | /// Lower an implicit numeric cast coercion. |
| 7314 | /// |
| 7315 | /// Handles widening conversions between integer types. Uses sign-extension |
| 7316 | /// for signed source types and zero-extension for unsigned source types. |
| 7317 | 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 { |
| 7318 | let srcLayout = resolver::getTypeLayout(srcType); |
| 7319 | let dstLayout = resolver::getTypeLayout(dstType); |
| 7320 | |
| 7321 | if srcLayout.size == dstLayout.size { |
| 7322 | // Same size: bit pattern is unchanged, value is returned as-is. |
| 7323 | return val; |
| 7324 | } |
| 7325 | // Widening: extend based on source signedness. |
| 7326 | // Narrowing: truncate and normalize to destination width. |
| 7327 | let widening = srcLayout.size < dstLayout.size; |
| 7328 | let extType = ilType(self.low, srcType) if widening else ilType(self.low, dstType); |
| 7329 | let signed = isSignedType(srcType) if widening else isSignedType(dstType); |
| 7330 | let dst = nextReg(self); |
| 7331 | |
| 7332 | if signed { |
| 7333 | emit(self, il::Instr::Sext { typ: extType, dst, val }); |
| 7334 | } else { |
| 7335 | emit(self, il::Instr::Zext { typ: extType, dst, val }); |
| 7336 | } |
| 7337 | return il::Val::Reg(dst); |
| 7338 | } |
| 7339 | |
| 7340 | /// Lower a global value symbol. |
| 7341 | 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 { |
| 7342 | // Function pointer reference: return the function's address directly. |
| 7343 | // Functions have no separate storage cell in the data section. |
| 7344 | if let case resolver::Type::Fn(_) = ty { |
| 7345 | return il::Val::Reg(emitFnAddr(self, sym)); |
| 7346 | } |
| 7347 | let src = emitDataAddr(self, sym); |
| 7348 | |
| 7349 | return emitRead(self, src, 0, ty); |
| 7350 | } |
| 7351 | |
| 7352 | /// Lower an identifier that refers to a global symbol. |
| 7353 | unsafe fn lowerGlobalSymbol 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 7354 | // First try to get a compile-time constant value. |
| 7355 | if let constVal = resolver::constValueEntry(self.low.resolver, node) { |
| 7356 | return try constValueToVal(self, constVal, node); |
| 7357 | } |
| 7358 | // Otherwise get the symbol. |
| 7359 | let sym = try symOf(self, node); |
| 7360 | |
| 7361 | match sym.data { |
| 7362 | case resolver::SymbolData::Constant { type, .. } => { |
| 7363 | let src = emitDataAddr(self, sym); |
| 7364 | |
| 7365 | return emitRead(self, src, 0, type); |
| 7366 | } |
| 7367 | case resolver::SymbolData::Value { type, .. } => |
| 7368 | return lowerGlobalValue(self, sym, type), |
| 7369 | else => throw LowerError::UnexpectedNodeValue(node), |
| 7370 | } |
| 7371 | } |
| 7372 | |
| 7373 | /// Lower an assignment to a static variable. |
| 7374 | 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 { |
| 7375 | let sym = try symOf(self, target); |
| 7376 | let case resolver::SymbolData::Value { type, .. } = sym.data else { |
| 7377 | throw LowerError::ImmutableAssignment; |
| 7378 | }; |
| 7379 | let dst = emitDataAddr(self, sym); |
| 7380 | |
| 7381 | try emitStore(self, dst, 0, type, val); |
| 7382 | } |
| 7383 | |
| 7384 | /// Lower a scope access expression like `Module::Const` or `Union::Variant`. |
| 7385 | /// This doesn't handle record literal variants. |
| 7386 | unsafe fn lowerScopeAccess 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 7387 | // First try to get a compile-time constant value. |
| 7388 | if let constVal = resolver::constValueEntry(self.low.resolver, node) { |
| 7389 | return try constValueToVal(self, constVal, node); |
| 7390 | } |
| 7391 | // Otherwise get the associated symbol. |
| 7392 | let data = resolver::nodeData(self.low.resolver, node); |
| 7393 | let sym = data.sym else { |
| 7394 | throw LowerError::MissingSymbol(node); |
| 7395 | }; |
| 7396 | match sym.data { |
| 7397 | case resolver::SymbolData::Variant { index, .. } => { |
| 7398 | let mut indexValue = index as i64; |
| 7399 | if let idx = voidVariantIndex(self.low.resolver, node) { |
| 7400 | set indexValue = idx; |
| 7401 | } |
| 7402 | // Void union variant like `Option::None`. |
| 7403 | if data.ty == resolver::Type::Unknown { |
| 7404 | throw LowerError::MissingType(node); |
| 7405 | } |
| 7406 | // All-void unions are passed as scalars (the tag byte). |
| 7407 | // Return an immediate instead of building a tagged aggregate. |
| 7408 | if resolver::isVoidUnion(data.ty) { |
| 7409 | return il::Val::Imm(indexValue); |
| 7410 | } |
| 7411 | let unionInfo = unionInfoFromType(data.ty) else { |
| 7412 | throw LowerError::MissingMetadata; |
| 7413 | }; |
| 7414 | let valOffset = unionInfo.valOffset as i32; |
| 7415 | return try buildTagged(self, resolver::getTypeLayout(data.ty), indexValue, nil, resolver::Type::Void, 1, valOffset); |
| 7416 | } |
| 7417 | case resolver::SymbolData::Constant { type, .. } => { |
| 7418 | // Constant without compile-time value (e.g. record constant); |
| 7419 | // load from data section. |
| 7420 | let src = emitDataAddr(self, sym); |
| 7421 | |
| 7422 | // Aggregate constants live in read-only memory. Return a |
| 7423 | // mutable copy so that callers that assign through the |
| 7424 | // resulting pointer do not fault. |
| 7425 | if isAggregateType(type) { |
| 7426 | let layout = resolver::getTypeLayout(type); |
| 7427 | let dst = emitReserveLayout(self, layout); |
| 7428 | emit(self, il::Instr::Blit { dst, src, size: il::Val::Imm(layout.size as i64) }); |
| 7429 | |
| 7430 | return il::Val::Reg(dst); |
| 7431 | } |
| 7432 | return emitRead(self, src, 0, type); |
| 7433 | } |
| 7434 | case resolver::SymbolData::Value { type, .. } => |
| 7435 | return lowerGlobalValue(self, sym, type), |
| 7436 | else => |
| 7437 | throw LowerError::UnexpectedNodeValue(node), |
| 7438 | } |
| 7439 | } |
| 7440 | |
| 7441 | /// Lower an expression AST node to an IL value. |
| 7442 | /// This is the main expression dispatch, all expression nodes go through here. |
| 7443 | unsafe fn lowerExpr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 7444 | if self.low.options.debug { |
| 7445 | set self.srcLoc.offset = node.span.offset; |
| 7446 | } |
| 7447 | let mut val: il::Val = undefined; |
| 7448 | |
| 7449 | match node.value { |
| 7450 | case ast::NodeValue::Ident(_) => { |
| 7451 | // First try local variable lookup. |
| 7452 | // Otherwise fall back to global symbol lookup. |
| 7453 | if let v = lookupLocalVar(self, node) { |
| 7454 | set val = try useVar(self, v); |
| 7455 | if self.vars[*v].addressTaken { |
| 7456 | let typ = try typeOf(self, node); |
| 7457 | let ptr = emitValToReg(self, val); |
| 7458 | set val = emitRead(self, ptr, 0, typ); |
| 7459 | } |
| 7460 | } else { |
| 7461 | set val = try lowerGlobalSymbol(self, node); |
| 7462 | } |
| 7463 | } |
| 7464 | case ast::NodeValue::ScopeAccess(_) => { |
| 7465 | set val = try lowerScopeAccess(self, node); |
| 7466 | } |
| 7467 | case ast::NodeValue::Number(lit) => { |
| 7468 | set val = il::Val::Imm(lit.magnitude as i64); |
| 7469 | } |
| 7470 | case ast::NodeValue::Bool(b) => { |
| 7471 | set val = il::Val::Imm(1) if b else il::Val::Imm(0); |
| 7472 | } |
| 7473 | case ast::NodeValue::Char(c) => { |
| 7474 | set val = il::Val::Imm(c as i64); |
| 7475 | } |
| 7476 | case ast::NodeValue::Nil => { |
| 7477 | let typ = try typeOf(self, node); |
| 7478 | if let case resolver::Type::Optional(_) = typ { |
| 7479 | set val = try buildNilOptional(self, typ); |
| 7480 | } else if let case resolver::Type::Nil = typ { |
| 7481 | // Standalone `nil` without a concrete optional type. We can't |
| 7482 | // generate a proper value representation. |
| 7483 | throw LowerError::MissingType(node); |
| 7484 | } else { |
| 7485 | throw LowerError::NilInNonOptional; |
| 7486 | } |
| 7487 | } |
| 7488 | case ast::NodeValue::RecordLit(lit) => { |
| 7489 | set val = try lowerRecordLit(self, node, lit); |
| 7490 | } |
| 7491 | case ast::NodeValue::AddressOf(addr) => { |
| 7492 | set val = try lowerAddressOf(self, node, addr); |
| 7493 | } |
| 7494 | case ast::NodeValue::Deref(target) => { |
| 7495 | set val = try lowerDeref(self, node, target); |
| 7496 | } |
| 7497 | case ast::NodeValue::BinOp(binop) => { |
| 7498 | set val = try lowerBinOp(self, node, binop); |
| 7499 | } |
| 7500 | case ast::NodeValue::UnOp(unop) => { |
| 7501 | set val = try lowerUnOp(self, node, unop); |
| 7502 | } |
| 7503 | case ast::NodeValue::Subscript { container, index } => { |
| 7504 | set val = try lowerSubscript(self, node, container, index); |
| 7505 | } |
| 7506 | case ast::NodeValue::BuiltinCall { kind, args } => { |
| 7507 | set val = try lowerBuiltinCall(self, node, kind, args); |
| 7508 | } |
| 7509 | case ast::NodeValue::Call(call) => { |
| 7510 | set val = try lowerCallOrCtor(self, node, call); |
| 7511 | } |
| 7512 | case ast::NodeValue::Try(t) => { |
| 7513 | set val = try lowerTry(self, node, t); |
| 7514 | } |
| 7515 | case ast::NodeValue::FieldAccess(access) => { |
| 7516 | // Check for compile-time constant (e.g., `arr.len` on fixed-size arrays). |
| 7517 | if let constVal = resolver::constValueEntry(self.low.resolver, node) { |
| 7518 | match constVal { |
| 7519 | // TODO: Handle `u32` values that don't fit in an `i32`. |
| 7520 | // Perhaps just store the `ConstInt`. |
| 7521 | case resolver::ConstValue::Int(i) => set val = il::Val::Imm(constIntToI64(i)), |
| 7522 | else => set val = try lowerFieldAccess(self, access), |
| 7523 | } |
| 7524 | } else { |
| 7525 | set val = try lowerFieldAccess(self, access); |
| 7526 | } |
| 7527 | } |
| 7528 | case ast::NodeValue::ArrayLit(elements) => { |
| 7529 | set val = try lowerArrayLit(self, node, elements); |
| 7530 | } |
| 7531 | case ast::NodeValue::ArrayRepeatLit(repeat) => { |
| 7532 | set val = try lowerArrayRepeatLit(self, node, repeat); |
| 7533 | } |
| 7534 | case ast::NodeValue::RegionApply { value, .. } => { |
| 7535 | set val = try lowerExpr(self, value); |
| 7536 | } |
| 7537 | case ast::NodeValue::As(cast) => { |
| 7538 | set val = try lowerCast(self, node, cast); |
| 7539 | } |
| 7540 | case ast::NodeValue::CondExpr(cond) => { |
| 7541 | set val = try lowerCondExpr(self, node, cond); |
| 7542 | } |
| 7543 | case ast::NodeValue::String(s) => { |
| 7544 | set val = try lowerStringLit(self, node, s); |
| 7545 | } |
| 7546 | case ast::NodeValue::Undef => { |
| 7547 | let typ = try typeOf(self, node); |
| 7548 | if isAggregateType(typ) { |
| 7549 | // When `undefined` appears as a stand-alone expression, |
| 7550 | /// we need a stack slot for reads and writes. |
| 7551 | let slot = try emitReserve(self, typ); |
| 7552 | set val = il::Val::Reg(slot); |
| 7553 | } else { |
| 7554 | set val = il::Val::Undef; |
| 7555 | } |
| 7556 | } |
| 7557 | case ast::NodeValue::Panic { .. } => { |
| 7558 | // Panic in expression context (e.g. match arm). Emit unreachable |
| 7559 | // and return a dummy value since control won't continue. |
| 7560 | emit(self, il::Instr::Unreachable); |
| 7561 | set val = il::Val::Undef; |
| 7562 | } |
| 7563 | case ast::NodeValue::Assert { .. } => { |
| 7564 | // Assert in expression context. Lower as statement, return `void`. |
| 7565 | try lowerNode(self, node); |
| 7566 | set val = il::Val::Undef; |
| 7567 | } |
| 7568 | case ast::NodeValue::Block(_) => { |
| 7569 | try lowerBlock(self, node); |
| 7570 | set val = il::Val::Undef; |
| 7571 | } |
| 7572 | case ast::NodeValue::ExprStmt(expr) => { |
| 7573 | let _ = expr; |
| 7574 | set val = il::Val::Undef; |
| 7575 | } |
| 7576 | // Lower these as statements. |
| 7577 | case ast::NodeValue::ConstDecl(decl) => { |
| 7578 | try registerLocalDataDeclName(self, node); |
| 7579 | try lowerDataDecl(self.low, node, decl.value, true); |
| 7580 | set val = il::Val::Undef; |
| 7581 | } |
| 7582 | case ast::NodeValue::StaticDecl(decl) => { |
| 7583 | try registerLocalDataDeclName(self, node); |
| 7584 | try lowerDataDecl(self.low, node, decl.value, false); |
| 7585 | set val = il::Val::Undef; |
| 7586 | } |
| 7587 | case ast::NodeValue::Throw { .. }, |
| 7588 | ast::NodeValue::Return { .. }, |
| 7589 | ast::NodeValue::Continue, |
| 7590 | ast::NodeValue::Break => { |
| 7591 | try lowerNode(self, node); |
| 7592 | set val = il::Val::Undef; |
| 7593 | } |
| 7594 | else => { |
| 7595 | panic "lowerExpr: node is not an expression"; |
| 7596 | } |
| 7597 | } |
| 7598 | return try applyCoercion(self, node, val); |
| 7599 | } |
| 7600 | |
| 7601 | /// Translate a Radiance type to an IL type. |
| 7602 | /// |
| 7603 | /// The IL type system is much simpler than Radiance's, only primitive types |
| 7604 | /// are used. If a Radiance type doesn't fit in a machine word, it is passed |
| 7605 | /// by reference. |
| 7606 | /// |
| 7607 | /// The IL doesn't track signedness - that's encoded in the instructions |
| 7608 | /// (e.g., Slt vs Ult). |
| 7609 | unsafe fn ilType 'arena 'phase (self: &mut Lowerer 'arena 'phase, typ: resolver::Type) -> il::Type where 'arena: 'phase { |
| 7610 | match typ { |
| 7611 | case resolver::Type::Bool, |
| 7612 | resolver::Type::I8, |
| 7613 | resolver::Type::U8 => return il::Type::W8, |
| 7614 | case resolver::Type::I16, |
| 7615 | resolver::Type::U16 => return il::Type::W16, |
| 7616 | case resolver::Type::I32, |
| 7617 | resolver::Type::U32 => return il::Type::W32, |
| 7618 | case resolver::Type::I64, |
| 7619 | resolver::Type::U64, |
| 7620 | resolver::Type::Pointer { .. }, |
| 7621 | resolver::Type::Slice { .. }, |
| 7622 | resolver::Type::TraitObject { .. }, |
| 7623 | resolver::Type::Array(_), |
| 7624 | resolver::Type::Optional(_), |
| 7625 | resolver::Type::Fn(_), |
| 7626 | resolver::Type::Session(_), |
| 7627 | resolver::Type::Cell { .. } => return il::Type::W64, |
| 7628 | case resolver::Type::Nominal(_) => { |
| 7629 | if resolver::isVoidUnion(typ) { |
| 7630 | return il::Type::W8; |
| 7631 | } |
| 7632 | return il::Type::W64; |
| 7633 | } |
| 7634 | case resolver::Type::Void, resolver::Type::Never => return il::Type::W64, |
| 7635 | // [`Type::Int`] is the type of unsuffixed integer literals and their |
| 7636 | // compound expressions (e.g. `1 + 2`). It defaults to W64 (i64) here, |
| 7637 | // matching the native word size on RV64. It cannot be resolved earlier |
| 7638 | // because the resolver uses [`Type::Int`] to distinguish unsuffixed |
| 7639 | // expressions from explicitly typed ones, which affects coercion |
| 7640 | // behavior (e.g. implicit narrowing). |
| 7641 | case resolver::Type::Int => return il::Type::W64, |
| 7642 | case resolver::Type::Opaque => panic "ilType: opaque type must be behind a pointer", |
| 7643 | else => panic "ilType: type cannot be lowered", |
| 7644 | } |
| 7645 | } |
| 7646 | |
| 7647 | /// Lower one fixed-width MMIO operation without exposing a pointer value. |
| 7648 | unsafe fn lowerDevice 'arena 'phase 'function ( |
| 7649 | self: &mut FnLowerer 'arena 'phase 'function, |
| 7650 | call: ast::Call, |
| 7651 | typ: il::Type, |
| 7652 | writing: bool |
| 7653 | ) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 7654 | let count: u32 = 3 if writing else 2; |
| 7655 | if call.args.len <> count { |
| 7656 | throw LowerError::InvalidArgCount; |
| 7657 | } |
| 7658 | let handle = try lowerExpr(self, call.args[0]); |
| 7659 | let offset = try lowerExpr(self, call.args[1]); |
| 7660 | if writing { |
| 7661 | let value = try lowerExpr(self, call.args[2]); |
| 7662 | emit(self, il::Instr::DeviceWrite { typ, handle, offset, value }); |
| 7663 | return il::Val::Undef; |
| 7664 | } |
| 7665 | let dst = nextReg(self); |
| 7666 | emit(self, il::Instr::DeviceRead { typ, dst, handle, offset }); |
| 7667 | return il::Val::Reg(dst); |
| 7668 | } |