General code simplifications

a1e8824d346dec0608148a40e0c491528e588f25420489d5fe5e167bf16dd1fb
Changes cherry picked from an autoresearch run.
Alexis Sellier committed ago 1 parent 29fdb962
compiler/radiance.rad +15 -25
195 195
        io::printError("radiance: error setting source\n");
196 196
        throw Error::Other;
197 197
    };
198 198
}
199 199
200 +
/// Consume the next argument, or print an error and throw.
201 +
fn nextArg(args: *[*[u8]], idx: *mut u32, msg: *[u8]) -> *[u8] throws (Error) {
202 +
    *idx += 1;
203 +
    if *idx >= args.len {
204 +
        io::printError(msg);
205 +
        throw Error::Other;
206 +
    }
207 +
    return args[*idx];
208 +
}
209 +
200 210
/// Parse CLI arguments and return compilation context.
201 211
fn processCommand(
202 212
    args: *[*[u8]],
203 213
    arena: *mut ast::NodeArena
204 214
) -> CompileContext throws (Error) {
225 235
    let mut idx: u32 = 0;
226 236
227 237
    while idx < args.len {
228 238
        let arg = args[idx];
229 239
        if mem::eq(arg, "-pkg") {
230 -
            idx += 1;
231 -
            if idx >= args.len {
232 -
                io::printError("radiance: `-pkg` requires a package name\n");
233 -
                throw Error::Other;
234 -
            }
240 +
            try nextArg(args, &mut idx, "radiance: `-pkg` requires a package name\n");
235 241
            if pkgCount >= MAX_PACKAGES {
236 242
                io::printError("radiance: too many packages specified\n");
237 243
                throw Error::Other;
238 244
            }
239 245
            pkgNames[pkgCount] = args[idx];
240 246
            currentPkgIdx = pkgCount;
241 247
            pkgCount += 1;
242 248
        } else if mem::eq(arg, "-mod") {
243 -
            idx += 1;
244 -
            if idx >= args.len {
245 -
                io::printError("radiance: `-mod` requires a module path\n");
246 -
                throw Error::Other;
247 -
            }
249 +
            try nextArg(args, &mut idx, "radiance: `-mod` requires a module path\n");
248 250
            let pkgIdx = currentPkgIdx else {
249 251
                io::printError("radiance: `-mod` must follow a `-pkg` argument\n");
250 252
                throw Error::Other;
251 253
            };
252 254
            if moduleCounts[pkgIdx] >= MAX_LOADED_MODULES {
254 256
                throw Error::Other;
255 257
            }
256 258
            modulePaths[pkgIdx][moduleCounts[pkgIdx]] = args[idx];
257 259
            moduleCounts[pkgIdx] += 1;
258 260
        } else if mem::eq(arg, "-entry") {
259 -
            idx += 1;
260 -
            if idx >= args.len {
261 -
                io::printError("radiance: `-entry` requires a package name\n");
262 -
                throw Error::Other;
263 -
            }
261 +
            try nextArg(args, &mut idx, "radiance: `-entry` requires a package name\n");
264 262
            entryPkgName = args[idx];
265 263
        } else if mem::eq(arg, "-test") {
266 264
            buildTest = true;
267 265
        } else if mem::eq(arg, "-debug") {
268 266
            debugEnabled = true;
269 267
        } else if mem::eq(arg, "-o") {
270 -
            idx += 1;
271 -
            if idx >= args.len {
272 -
                io::printError("radiance: `-o` requires an output path\n");
273 -
                throw Error::Other;
274 -
            }
268 +
            try nextArg(args, &mut idx, "radiance: `-o` requires an output path\n");
275 269
            outputPath = args[idx];
276 270
        } else if mem::eq(arg, "-dump") {
277 -
            idx += 1;
278 -
            if idx >= args.len {
279 -
                io::printError("radiance: `-dump` requires a mode (eg. ast)\n");
280 -
                throw Error::Other;
281 -
            }
271 +
            try nextArg(args, &mut idx, "radiance: `-dump` requires a mode (eg. ast)\n");
282 272
            let mode = args[idx];
283 273
            if mem::eq(mode, "ast") {
284 274
                dump = Dump::Ast;
285 275
            } else if mem::eq(mode, "graph") {
286 276
                dump = Dump::Graph;
lib/std/arch/rv64/encode.rad +0 -12
579 579
/// Implemented as `blt rs2, rs1, imm` (swap operands).
580 580
pub fn bgt(rs1: super::Reg, rs2: super::Reg, imm: i32) -> u32 {
581 581
    return blt(rs2, rs1, imm);
582 582
}
583 583
584 -
/// Branch if less than or equal unsigned: `if (rs1 <= rs2) pc += imm`.
585 -
/// Implemented as `bgeu rs2, rs1, imm` (swap operands).
586 -
pub fn bleu(rs1: super::Reg, rs2: super::Reg, imm: i32) -> u32 {
587 -
    return bgeu(rs2, rs1, imm);
588 -
}
589 -
590 -
/// Branch if greater than unsigned: `if (rs1 > rs2) pc += imm`.
591 -
/// Implemented as `bltu rs2, rs1, imm` (swap operands).
592 -
pub fn bgtu(rs1: super::Reg, rs2: super::Reg, imm: i32) -> u32 {
593 -
    return bltu(rs2, rs1, imm);
594 -
}
595 -
596 584
/// Set if equal to zero: `rd = (rs == 0) ? 1 : 0`.
597 585
/// Implemented as `sltiu rd, rs, 1`.
598 586
pub fn seqz(rd: super::Reg, rs: super::Reg) -> u32 {
599 587
    return sltiu(rd, rs, 1);
600 588
}
lib/std/arch/rv64/isel.rad +18 -30
236 236
        },
237 237
        case il::Type::W64 => {}
238 238
    }
239 239
}
240 240
241 +
/// Resolve a value, trap if zero (unless known non-zero), and return the register.
242 +
fn resolveAndTrapIfZero(s: *mut Selector, b: il::Val) -> super::Reg {
243 +
    let rs2 = resolveVal(s, super::SCRATCH2, b);
244 +
    let mut knownNonZero = false;
245 +
    if let case il::Val::Imm(imm) = b {
246 +
        knownNonZero = imm != 0;
247 +
    }
248 +
    if not knownNonZero {
249 +
        emit::emit(s.e, encode::bne(rs2, super::ZERO, super::INSTR_SIZE * 2));
250 +
        emit::emit(s.e, encode::ebreak());
251 +
    }
252 +
    return rs2;
253 +
}
254 +
241 255
////////////////////////
242 256
// Instruction Select //
243 257
////////////////////////
244 258
245 259
/// Pre-scan result for reserve analysis.
709 723
        return imm == 0;
710 724
    }
711 725
    return false;
712 726
}
713 727
714 -
/// Check if a value is a known non-zero immediate.
715 -
fn isNonZeroImm(val: il::Val) -> bool {
716 -
    if let case il::Val::Imm(imm) = val {
717 -
        return imm != 0;
718 -
    }
719 -
    return false;
720 -
}
721 -
722 -
/// Emit runtime trap for division/modulo by zero.
723 -
fn emitTrapIfZero(s: *mut Selector, rs: super::Reg) {
724 -
    emit::emit(s.e, encode::bne(rs, super::ZERO, super::INSTR_SIZE * 2));
725 -
    emit::emit(s.e, encode::ebreak());
726 -
}
727 -
728 728
/// Select a binary ALU operation, dispatching to the appropriate
729 729
/// instruction pattern based on the operation kind and type.
730 730
fn selectAluBinOp(s: *mut Selector, op: il::BinOp, typ: il::Type, rd: super::Reg, rs1: super::Reg, b: il::Val) {
731 731
    match op {
732 732
        case il::BinOp::Add => {
782 782
                        if typ == il::Type::W32 else
783 783
                    encode::mul(rd, rs1, rs2));
784 784
            }
785 785
        }
786 786
        case il::BinOp::Sdiv => {
787 -
            let rs2 = resolveVal(s, super::SCRATCH2, b);
788 -
            if not isNonZeroImm(b) {
789 -
                emitTrapIfZero(s, rs2);
790 -
            }
787 +
            let rs2 = resolveAndTrapIfZero(s, b);
791 788
            emit::emit(s.e,
792 789
                encode::divw(rd, rs1, rs2)
793 790
                    if typ == il::Type::W32 else
794 791
                encode::div(rd, rs1, rs2));
795 792
        }
796 793
        case il::BinOp::Udiv => {
797 -
            let rs2 = resolveVal(s, super::SCRATCH2, b);
798 -
            if not isNonZeroImm(b) {
799 -
                emitTrapIfZero(s, rs2);
800 -
            }
794 +
            let rs2 = resolveAndTrapIfZero(s, b);
801 795
            emit::emit(s.e,
802 796
                encode::divuw(rd, rs1, rs2)
803 797
                    if typ == il::Type::W32 else
804 798
                encode::divu(rd, rs1, rs2));
805 799
        }
806 800
        case il::BinOp::Srem => {
807 -
            let rs2 = resolveVal(s, super::SCRATCH2, b);
808 -
            if not isNonZeroImm(b) {
809 -
                emitTrapIfZero(s, rs2);
810 -
            }
801 +
            let rs2 = resolveAndTrapIfZero(s, b);
811 802
            emit::emit(s.e,
812 803
                encode::remw(rd, rs1, rs2)
813 804
                    if typ == il::Type::W32 else
814 805
                encode::rem(rd, rs1, rs2));
815 806
        }
816 807
        case il::BinOp::Urem => {
817 -
            let rs2 = resolveVal(s, super::SCRATCH2, b);
818 -
            if not isNonZeroImm(b) {
819 -
                emitTrapIfZero(s, rs2);
820 -
            }
808 +
            let rs2 = resolveAndTrapIfZero(s, b);
821 809
            emit::emit(s.e,
822 810
                encode::remuw(rd, rs1, rs2)
823 811
                    if typ == il::Type::W32 else
824 812
                encode::remu(rd, rs1, rs2));
825 813
        }
lib/std/arch/rv64/printer.rad +0 -14
67 67
///////////////////////////////
68 68
69 69
/// Mnemonic column width for alignment.
70 70
const MNEMONIC_WIDTH: u32 = 8;
71 71
72 -
/// Print spaces for indentation.
73 -
fn writeIndent(out: *mut sexpr::Output) {
74 -
    write(out, "  ");
75 -
}
76 -
77 72
/// Write text wrapped in parentheses.
78 73
fn writeParens(out: *mut sexpr::Output, s: *[u8]) {
79 74
    write(out, "(");
80 75
    write(out, s);
81 76
    write(out, ")");
334 329
            return funcs[i].name;
335 330
        }
336 331
    }
337 332
    return nil;
338 333
}
339 -
340 -
/// Print code with labels to buffer, returns slice.
341 -
pub fn printCode(pkgName: *[u8], code: *[u32], funcs: *[types::FuncAddr], arena: *mut alloc::Arena, buf: *mut [u8]) -> *[u8] {
342 -
    let mut pos: u32 = 0;
343 -
    let mut out = sexpr::Output::Buffer { buf, pos: &mut pos };
344 -
    printCodeTo(&mut out, pkgName, code, funcs, arena);
345 -
346 -
    return &buf[..pos];
347 -
}
lib/std/lang/ast.rad +0 -3
5 5
use std::lang::alloc;
6 6
7 7
/// Maximum number of trait methods.
8 8
pub const MAX_TRAIT_METHODS: u32 = 8;
9 9
10 -
/// Maximum number of supertraits.
11 -
pub const MAX_SUPERTRAITS: u32 = 4;
12 -
13 10
/// Arena for all parser allocations.
14 11
///
15 12
/// Uses a bump allocator for both AST nodes and node pointer arrays.
16 13
pub record NodeArena {
17 14
    /// Bump allocator for all allocations.
lib/std/lang/gen/data.rad +0 -11
155 155
    if let v = dict::get(&m.dict, name) {
156 156
        return v as u32;
157 157
    }
158 158
    return nil;
159 159
}
160 -
161 -
/// Resolve a data symbol to its final absolute address (linear scan fallback).
162 -
pub fn lookupDataSymAddr(dataSyms: *[DataSym], name: *[u8]) -> ?u32 {
163 -
    for i in 0..dataSyms.len {
164 -
        let sym = dataSyms[i];
165 -
        if mem::eq(sym.name, name) {
166 -
            return sym.addr;
167 -
        }
168 -
    }
169 -
    return nil;
170 -
}
lib/std/lang/gen/regalloc/assign.rad +5 -10
167 167
                    assignments[dst.n] = rallocReg(&mut current, &mut usedRegs, dst.n, allocatable, config.calleeSaved, spillInfo);
168 168
                }
169 169
            }
170 170
        }
171 171
        // Save block-end state.
172 -
        rmapCopy(&mut blkEnd[b], &current);
172 +
        blkEnd[b].n = current.n;
173 +
        for ri in 0..current.n {
174 +
            blkEnd[b].virtRegs[ri] = current.virtRegs[ri];
175 +
            blkEnd[b].physRegs[ri] = current.physRegs[ri];
176 +
        }
173 177
    }
174 178
    // Compute bitmask of used callee-saved registers.
175 179
    let mut usedCalleeSaved: u32 = 0;
176 180
    for i in 0..maxReg {
177 181
        if let phys = assignments[i] {
195 199
    let physRegs = try alloc::allocSlice(arena, @sizeOf(u8), @alignOf(u8), MAX_ACTIVE) as *mut [u8];
196 200
197 201
    return RegMap { virtRegs, physRegs, n: 0 };
198 202
}
199 203
200 -
/// Copy a register map.
201 -
fn rmapCopy(dst: *mut RegMap, src: *RegMap) {
202 -
    dst.n = src.n;
203 -
    for i in 0..src.n {
204 -
        dst.virtRegs[i] = src.virtRegs[i];
205 -
        dst.physRegs[i] = src.physRegs[i];
206 -
    }
207 -
}
208 -
209 204
/// Find physical register for a virtual register in RegMap.
210 205
fn rmapFind(rmap: *RegMap, virtReg: u32) -> ?u8 {
211 206
    for i in 0..rmap.n {
212 207
        if rmap.virtRegs[i] == virtReg {
213 208
            return rmap.physRegs[i];
lib/std/lang/il.rad +4 -4
417 417
}
418 418
419 419
/// Check if an instruction is a function call.
420 420
pub fn isCall(instr: Instr) -> bool {
421 421
    match instr {
422 -
        case Instr::Call { .. } => return true,
423 -
        case Instr::Ecall { .. } => return true,
422 +
        case Instr::Call { .. },
423 +
             Instr::Ecall { .. } => return true,
424 424
        else => return false,
425 425
    }
426 426
}
427 427
428 428
/// Call a function for each register used by an instruction.
496 496
            withReg(a0, f, ctx);
497 497
            withReg(a1, f, ctx);
498 498
            withReg(a2, f, ctx);
499 499
            withReg(a3, f, ctx);
500 500
        },
501 -
        case Instr::Unreachable => {},
502 -
        case Instr::Ebreak => {},
501 +
        case Instr::Unreachable,
502 +
             Instr::Ebreak => {},
503 503
    }
504 504
}
505 505
506 506
/// Call callback if value is a register.
507 507
fn withReg(val: Val, callback: fn(Reg, *mut opaque), ctx: *mut opaque) {
lib/std/lang/il/printer.rad +11 -24
7 7
8 8
///////////////////////
9 9
// String formatting //
10 10
///////////////////////
11 11
12 +
/// Copy a byte slice into arena-allocated storage.
13 +
fn copyToArena(a: *mut alloc::Arena, text: *[u8]) -> *[u8] {
14 +
    let slice = try! alloc::allocSlice(a, 1, 1, text.len) as *mut [u8];
15 +
    try! mem::copy(slice, text);
16 +
    return slice;
17 +
}
18 +
12 19
/// Format a `u32` into a string in the arena.
13 20
fn formatU32(a: *mut alloc::Arena, val: u32) -> *[u8] {
14 21
    let mut digits: [u8; 10] = undefined;
15 -
    let text = fmt::formatU32(val, &mut digits[..]);
16 -
    let ptr = try! alloc::allocSlice(a, 1, 1, text.len);
17 -
    let slice = ptr as *mut [u8];
18 -
    try! mem::copy(slice, text);
19 -
20 -
    return slice;
22 +
    return copyToArena(a, fmt::formatU32(val, &mut digits[..]));
21 23
}
22 24
23 25
/// Format an `i32` into a string in the arena.
24 26
fn formatI32(a: *mut alloc::Arena, val: i32) -> *[u8] {
25 27
    let mut digits: [u8; 12] = undefined;
26 -
    let text = fmt::formatI32(val, &mut digits[..]);
27 -
    let ptr = try! alloc::allocSlice(a, 1, 1, text.len);
28 -
    let slice = ptr as *mut [u8];
29 -
    try! mem::copy(slice, text);
30 -
31 -
    return slice;
28 +
    return copyToArena(a, fmt::formatI32(val, &mut digits[..]));
32 29
}
33 30
34 31
/// Format an `i64` into a string in the arena.
35 32
fn formatI64(a: *mut alloc::Arena, val: i64) -> *[u8] {
36 33
    let mut digits: [u8; 20] = undefined;
37 -
    let text = fmt::formatI64(val, &mut digits[..]);
38 -
    let ptr = try! alloc::allocSlice(a, 1, 1, text.len);
39 -
    let slice = ptr as *mut [u8];
40 -
    try! mem::copy(slice, text);
41 -
42 -
    return slice;
34 +
    return copyToArena(a, fmt::formatI64(val, &mut digits[..]));
43 35
}
44 36
45 37
/// Concatenate prefix and string in the arena.
46 38
fn prefixStr(a: *mut alloc::Arena, prefix: u8, str: *[u8]) -> *[u8] {
47 39
    let len = str.len + 1;
72 64
    for _ in 0..depth {
73 65
        write(out, "    ");
74 66
    }
75 67
}
76 68
77 -
/// Write a quoted string with escaping.
78 -
fn writeString(out: *mut sexpr::Output, s: *[u8]) {
79 -
    sexpr::printStringTo(out, s);
80 -
}
81 -
82 69
/// Check if name contains the path separator.
83 70
fn needsQuoting(name: *[u8]) -> bool {
84 71
    for ch in name {
85 72
        if ch == ':' {
86 73
            return true;
495 482
            write(out, "fn ");
496 483
            writeSymbol(out, name);
497 484
        }
498 485
        case super::DataItem::Str(s) => {
499 486
            write(out, "str ");
500 -
            writeString(out, s);
487 +
            sexpr::printStringTo(out, s);
501 488
        }
502 489
        case super::DataItem::Undef => {
503 490
            write(out, "undef");
504 491
        }
505 492
    }
lib/std/lang/lower.rad +104 -233
517 517
        lengthVal: il::Val,
518 518
        elemType: *resolver::Type,
519 519
    },
520 520
}
521 521
522 -
523 522
//////////////////////////////
524 523
// Pattern Matching Support //
525 524
//////////////////////////////
526 525
527 526
// Match expressions are lowered by evaluating the subject once, then emitting
1759 1758
    let reg = il::Reg { n: self.regCounter };
1760 1759
    self.regCounter += 1;
1761 1760
    return reg;
1762 1761
}
1763 1762
1763 +
/// Look up the resolved type of an AST node, or throw `MissingType`.
1764 +
fn typeOf(self: *mut FnLowerer, node: *ast::Node) -> resolver::Type throws (LowerError) {
1765 +
    let ty = resolver::typeFor(self.low.resolver, node)
1766 +
        else throw LowerError::MissingType(node);
1767 +
    return ty;
1768 +
}
1769 +
1770 +
/// Look up the symbol for an AST node, or throw `MissingSymbol`.
1771 +
fn symOf(self: *mut FnLowerer, node: *ast::Node) -> *mut resolver::Symbol throws (LowerError) {
1772 +
    let sym = resolver::nodeData(self.low.resolver, node).sym
1773 +
        else throw LowerError::MissingSymbol(node);
1774 +
    return sym;
1775 +
}
1776 +
1764 1777
/// Remove the last block parameter and its associated variable.
1765 1778
/// Used when detecting a trivial phi that can be eliminated.
1766 1779
fn removeLastBlockParam(self: *mut FnLowerer, block: BlockId) {
1767 1780
    let blk = getBlockMut(self, block);
1768 1781
    if blk.params.len > 0 {
2030 2043
    thenBlock: BlockId,
2031 2044
    elseBlock: BlockId
2032 2045
) throws (LowerError) {
2033 2046
    // Try fused compare-and-branch for simple scalar comparisons.
2034 2047
    if let case ast::NodeValue::BinOp(binop) = cond.value {
2035 -
        let leftTy = resolver::typeFor(self.low.resolver, binop.left) else {
2036 -
            throw LowerError::MissingType(binop.left);
2037 -
        };
2038 -
        let rightTy = resolver::typeFor(self.low.resolver, binop.right) else {
2039 -
            throw LowerError::MissingType(binop.right);
2040 -
        };
2048 +
        let leftTy = try typeOf(self, binop.left);
2049 +
        let rightTy = try typeOf(self, binop.right);
2041 2050
        if not isAggregateType(leftTy) and not isAggregateType(rightTy) {
2042 2051
            let unsigned = isUnsignedType(leftTy);
2043 2052
            if let op = cmpOpFrom(binop.op, unsigned) {
2044 2053
                let a = try lowerExpr(self, binop.left);
2045 2054
                let b = try lowerExpr(self, binop.right);
2070 2079
/// Emit a 32-bit store instruction at the given offset.
2071 2080
fn emitStoreW32At(self: *mut FnLowerer, src: il::Val, dst: il::Reg, offset: i32) {
2072 2081
    emit(self, il::Instr::Store { typ: il::Type::W32, src, dst, offset });
2073 2082
}
2074 2083
2075 -
/// Emit a 32-bit store instruction with default alignment.
2076 -
fn emitStoreW32(self: *mut FnLowerer, src: il::Val, dst: il::Reg) {
2077 -
    emitStoreW32At(self, src, dst, 0);
2078 -
}
2079 -
2080 2084
/// Emit a 32-bit load instruction at the given offset.
2081 2085
fn emitLoadW32At(self: *mut FnLowerer, dst: il::Reg, src: il::Reg, offset: i32) {
2082 2086
    emit(self, il::Instr::Load { typ: il::Type::W32, dst, src, offset });
2083 2087
}
2084 2088
2085 -
/// Emit a 32-bit load instruction with default alignment.
2086 -
fn emitLoadW32(self: *mut FnLowerer, dst: il::Reg, src: il::Reg) {
2087 -
    emitLoadW32At(self, dst, src, 0);
2088 -
}
2089 -
2090 -
/// Emit an 8-bit store instruction.
2091 -
fn emitStoreW8(self: *mut FnLowerer, src: il::Val, dst: il::Reg) {
2092 -
    emit(self, il::Instr::Store { typ: il::Type::W8, src, dst, offset: 0 });
2093 -
}
2094 -
2095 2089
/// Emit an 8-bit store instruction at the given offset.
2096 2090
fn emitStoreW8At(self: *mut FnLowerer, src: il::Val, dst: il::Reg, offset: i32) {
2097 2091
    emit(self, il::Instr::Store { typ: il::Type::W8, src, dst, offset });
2098 2092
}
2099 2093
2100 -
/// Emit an 8-bit load instruction.
2101 -
fn emitLoadW8(self: *mut FnLowerer, dst: il::Reg, src: il::Reg) {
2102 -
    emit(self, il::Instr::Load { typ: il::Type::W8, dst, src, offset: 0 });
2103 -
}
2104 -
2105 2094
/// Emit an 8-bit load instruction at the given offset.
2106 2095
fn emitLoadW8At(self: *mut FnLowerer, dst: il::Reg, src: il::Reg, offset: i32) {
2107 2096
    emit(self, il::Instr::Load { typ: il::Type::W8, dst, src, offset });
2108 2097
}
2109 2098
2110 2099
/// Emit a 64-bit store instruction at the given offset.
2111 2100
fn emitStoreW64At(self: *mut FnLowerer, src: il::Val, dst: il::Reg, offset: i32) {
2112 2101
    emit(self, il::Instr::Store { typ: il::Type::W64, src, dst, offset });
2113 2102
}
2114 2103
2115 -
/// Emit a 64-bit store instruction with default alignment.
2116 -
fn emitStoreW64(self: *mut FnLowerer, src: il::Val, dst: il::Reg) {
2117 -
    emitStoreW64At(self, src, dst, 0);
2118 -
}
2119 -
2120 2104
/// Emit a 64-bit load instruction at the given offset.
2121 2105
fn emitLoadW64At(self: *mut FnLowerer, dst: il::Reg, src: il::Reg, offset: i32) {
2122 2106
    emit(self, il::Instr::Load { typ: il::Type::W64, dst, src, offset });
2123 2107
}
2124 2108
2125 -
/// Emit a 64-bit load instruction with default alignment.
2126 -
fn emitLoadW64(self: *mut FnLowerer, dst: il::Reg, src: il::Reg) {
2127 -
    emitLoadW64At(self, dst, src, 0);
2128 -
}
2129 -
2130 2109
/// Load a tag from memory at `src` plus `offset` with the given IL type.
2131 2110
fn loadTag(self: *mut FnLowerer, src: il::Reg, offset: i32, tagType: il::Type) -> il::Val {
2132 2111
    let dst = nextReg(self);
2133 2112
    emit(self, il::Instr::Load { typ: tagType, dst, src, offset });
2134 2113
    return il::Val::Reg(dst);
2813 2792
        }
2814 2793
    }
2815 2794
    return sameVal;
2816 2795
}
2817 2796
2818 -
/// Patch switch case arguments for a specific target.
2819 -
// TODO: Inline this when we don't have register pressure issues.
2820 -
fn patchSwitchCases(self: *mut FnLowerer, cases: *mut [il::SwitchCase], target: u32, paramIdx: u32, val: il::Val) {
2821 -
    let idx = paramIdx + 1;
2822 -
    for i in 0..cases.len {
2823 -
        if cases[i].target == target {
2824 -
            cases[i].args = growArgs(self, cases[i].args, idx);
2825 -
            cases[i].args[paramIdx] = val;
2826 -
        }
2827 -
    }
2828 -
}
2829 -
2830 2797
/// Patch a single terminator argument for a specific edge. This is used during
2831 2798
/// SSA construction to pass variable values along control flow edges.
2832 2799
fn patchTerminatorArg(
2833 2800
    self: *mut FnLowerer,
2834 2801
    from: BlockId,         // The predecessor block containing the terminator to patch.
2860 2827
        case il::Instr::Switch { defaultTarget, defaultArgs, cases, .. } => {
2861 2828
            if *defaultTarget == target {
2862 2829
                *defaultArgs = growArgs(self, *defaultArgs, paramIdx + 1);
2863 2830
                defaultArgs[paramIdx] = val;
2864 2831
            }
2865 -
            patchSwitchCases(self, *cases, target, paramIdx, val);
2832 +
            let idx = paramIdx + 1;
2833 +
            for ci in 0..cases.len {
2834 +
                if cases[ci].target == target {
2835 +
                    cases[ci].args = growArgs(self, cases[ci].args, idx);
2836 +
                    cases[ci].args[paramIdx] = val;
2837 +
                }
2838 +
            }
2866 2839
        }
2867 2840
        else => {
2868 2841
            // Other terminators (e.g. `Ret`, `Unreachable`) don't have successor blocks.
2869 2842
        }
2870 2843
    }
2951 2924
}
2952 2925
2953 2926
/// Resolve match subject.
2954 2927
fn lowerMatchSubject(self: *mut FnLowerer, subject: *ast::Node) -> MatchSubject throws (LowerError) {
2955 2928
    let mut val = try lowerExpr(self, subject);
2956 -
    let subjectType = resolver::typeFor(self.low.resolver, subject) else {
2957 -
        throw LowerError::MissingType(subject);
2958 -
    };
2929 +
    let subjectType = try typeOf(self, subject);
2959 2930
    let unwrapped = resolver::unwrapMatchSubject(subjectType);
2960 2931
2961 2932
    // When matching an aggregate by value, copy it to a fresh stack slot so
2962 2933
    // that bindings are independent of the original memory.  Without this,
2963 2934
    // the lowerer returns a pointer into the source and mutations to the
3024 2995
    }
3025 2996
}
3026 2997
3027 2998
/// Lower an optional nil check (`opt == nil` or `opt != nil`).
3028 2999
fn lowerNilCheck(self: *mut FnLowerer, opt: *ast::Node, isEq: bool) -> il::Val throws (LowerError) {
3029 -
    let optTy = resolver::typeFor(self.low.resolver, opt) else {
3030 -
        throw LowerError::MissingType(opt);
3031 -
    };
3000 +
    let optTy = try typeOf(self, opt);
3032 3001
    // Handle `nil == nil` or `nil != nil`.
3033 3002
    if optTy == resolver::Type::Nil {
3034 3003
        return il::Val::Imm(1) if isEq else il::Val::Imm(0);
3035 3004
    }
3036 3005
    let val = try lowerExpr(self, opt);
3205 3174
    let payloadBase = emitPtrOffset(self, base, valOffset);
3206 3175
3207 3176
    try bindNestedRecordFields(self, payloadBase, lit, recInfo, subject.by, failBlock);
3208 3177
}
3209 3178
3210 -
/// Emit a field read from a base pointer.
3211 -
fn emitFieldRead(self: *mut FnLowerer, base: il::Reg, fieldInfo: resolver::RecordField, matchBy: resolver::MatchBy) -> il::Val {
3212 -
    match matchBy {
3213 -
        case resolver::MatchBy::Value => {
3214 -
            return emitRead(self, base, fieldInfo.offset, fieldInfo.fieldType);
3215 -
        }
3216 -
        case resolver::MatchBy::Ref, resolver::MatchBy::MutRef => {
3217 -
            return il::Val::Reg(emitPtrOffset(self, base, fieldInfo.offset));
3218 -
        }
3219 -
    }
3220 -
}
3221 -
3222 3179
/// Bind a single record field to a pattern variable, with support for nested
3223 3180
/// pattern tests that branch to `failBlock` on mismatch.
3224 3181
fn bindFieldVariable(
3225 3182
    self: *mut FnLowerer,
3226 3183
    binding: *ast::Node,
3229 3186
    matchBy: resolver::MatchBy,
3230 3187
    failBlock: BlockId
3231 3188
) throws (LowerError) {
3232 3189
    match binding.value {
3233 3190
        case ast::NodeValue::Ident(name) => {
3234 -
            let val = emitFieldRead(self, base, fieldInfo, matchBy);
3191 +
            let val = emitRead(self, base, fieldInfo.offset, fieldInfo.fieldType)
3192 +
                if matchBy == resolver::MatchBy::Value
3193 +
                else il::Val::Reg(emitPtrOffset(self, base, fieldInfo.offset));
3235 3194
            newVar(self, name, ilType(self.low, fieldInfo.fieldType), false, val);
3236 3195
        }
3237 3196
        case ast::NodeValue::Placeholder => {}
3238 3197
        case ast::NodeValue::RecordLit(lit) => {
3239 3198
            // Check if this record literal is a union variant pattern.
3247 3206
            // Auto-deref: if the field is a pointer, load it first.
3248 3207
            let mut derefType = fieldInfo.fieldType;
3249 3208
            let mut nestedBase = emitPtrOffset(self, base, fieldInfo.offset);
3250 3209
            if let case resolver::Type::Pointer { target, .. } = fieldInfo.fieldType {
3251 3210
                let ptrReg = nextReg(self);
3252 -
                emitLoadW64(self, ptrReg, nestedBase);
3211 +
                emitLoadW64At(self, ptrReg, nestedBase, 0);
3253 3212
                nestedBase = ptrReg;
3254 3213
                derefType = *target;
3255 3214
            }
3256 3215
            let recInfo = resolver::getRecord(derefType)
3257 3216
                else throw LowerError::ExpectedRecord;
3284 3243
    // The loaded pointer becomes the base address for the nested subject.
3285 3244
    let mut derefBase: ?il::Reg = nil;
3286 3245
    if let case resolver::Type::Pointer { target, .. } = fieldType {
3287 3246
        if resolver::isDestructuringPattern(pattern) {
3288 3247
            let ptrReg = nextReg(self);
3289 -
            emitLoadW64(self, ptrReg, fieldPtr);
3248 +
            emitLoadW64At(self, ptrReg, fieldPtr, 0);
3290 3249
            derefBase = ptrReg;
3291 3250
            fieldType = *target;
3292 3251
        }
3293 3252
    }
3294 3253
    // Build a MatchSubject for the nested field.
3859 3818
/// Return the effective type of a node after any coercion applied by
3860 3819
/// the resolver. `lowerExpr` already materializes the coercion in the
3861 3820
/// IL value, so the lowerer must use the post-coercion type when
3862 3821
/// choosing how to compare or store that value.
3863 3822
fn effectiveType(self: *mut FnLowerer, node: *ast::Node) -> resolver::Type throws (LowerError) {
3864 -
    let ty = resolver::typeFor(self.low.resolver, node) else {
3865 -
        throw LowerError::MissingType(node);
3866 -
    };
3823 +
    let ty = try typeOf(self, node);
3867 3824
    if let coerce = resolver::coercionFor(self.low.resolver, node) {
3868 3825
        if let case resolver::Coercion::OptionalLift(optTy) = coerce {
3869 3826
            return optTy;
3870 3827
        }
3871 3828
    }
3877 3834
    match typ {
3878 3835
        case resolver::Type::Nominal(_) => {
3879 3836
            // Void unions are small enough to pass by value.
3880 3837
            return not resolver::isVoidUnion(typ);
3881 3838
        }
3882 -
        case resolver::Type::Slice { .. } => return true,
3883 -
        case resolver::Type::TraitObject { .. } => return true,
3884 -
        case resolver::Type::Array(_) => return true,
3885 -
        case resolver::Type::Nil => return true,
3839 +
        case resolver::Type::Slice { .. },
3840 +
             resolver::Type::TraitObject { .. },
3841 +
             resolver::Type::Array(_),
3842 +
             resolver::Type::Nil => return true,
3886 3843
        case resolver::Type::Optional(resolver::Type::Pointer { .. }) => {
3887 3844
            // Optional pointers are scalar due to NPO.
3888 3845
            return false;
3889 3846
        }
3890 3847
        case resolver::Type::Optional(_) => {
4565 4522
}
4566 4523
4567 4524
/// Lower a record literal expression. Handles both plain records and union variant
4568 4525
/// record literals like `Union::Variant { field: value }`.
4569 4526
fn lowerRecordLit(self: *mut FnLowerer, node: *ast::Node, lit: ast::RecordLit) -> il::Val throws (LowerError) {
4570 -
    let typ = resolver::typeFor(self.low.resolver, node) else {
4571 -
        throw LowerError::MissingType(node);
4572 -
    };
4527 +
    let typ = try typeOf(self, node);
4573 4528
    match typ {
4574 4529
        case resolver::Type::Nominal(resolver::NominalType::Record(recInfo)) => {
4575 4530
            let dst = try emitReserve(self, typ);
4576 4531
            try lowerRecordFields(self, dst, &recInfo, lit.fields, 0);
4577 4532
4588 4543
/// Lower a union variant record literal like `Union::Variant { field: value }`.
4589 4544
fn lowerUnionRecordLit(self: *mut FnLowerer, typ: resolver::Type, lit: ast::RecordLit) -> il::Val throws (LowerError) {
4590 4545
    let typeName = lit.typeName else {
4591 4546
        throw LowerError::ExpectedVariant;
4592 4547
    };
4593 -
    let sym = resolver::nodeData(self.low.resolver, typeName).sym else {
4594 -
        throw LowerError::MissingSymbol(typeName);
4595 -
    };
4548 +
    let sym = try symOf(self, typeName);
4596 4549
    let case resolver::SymbolData::Variant { type: payloadType, index, .. } = sym.data else {
4597 4550
        throw LowerError::ExpectedVariant;
4598 4551
    };
4599 4552
    let recInfo = recordInfoFromType(payloadType) else {
4600 4553
        throw LowerError::ExpectedRecord;
4663 4616
4664 4617
/// Lower an array literal expression like `[1, 2, 3]`.
4665 4618
fn lowerArrayLit(self: *mut FnLowerer, node: *ast::Node, elements: *mut [*ast::Node]) -> il::Val
4666 4619
    throws (LowerError)
4667 4620
{
4668 -
    let typ = resolver::typeFor(self.low.resolver, node) else {
4669 -
        throw LowerError::MissingType(node);
4670 -
    };
4621 +
    let typ = try typeOf(self, node);
4671 4622
    let case resolver::Type::Array(arrInfo) = typ else {
4672 4623
        throw LowerError::ExpectedArray;
4673 4624
    };
4674 4625
    let elemTy = *arrInfo.item;
4675 4626
    let elemLayout = resolver::getTypeLayout(elemTy);
4688 4639
/// Unrolls the initialization at compile time.
4689 4640
// TODO: Beyond a certain length, lower this to a loop.
4690 4641
fn lowerArrayRepeatLit(self: *mut FnLowerer, node: *ast::Node, repeat: ast::ArrayRepeatLit) -> il::Val
4691 4642
    throws (LowerError)
4692 4643
{
4693 -
    let typ = resolver::typeFor(self.low.resolver, node) else {
4694 -
        throw LowerError::MissingType(node);
4695 -
    };
4644 +
    let typ = try typeOf(self, node);
4696 4645
    let case resolver::Type::Array(arrInfo) = typ else {
4697 4646
        throw LowerError::ExpectedArray;
4698 4647
    };
4699 4648
    let elemTy = *arrInfo.item;
4700 4649
    let length = arrInfo.length;
4714 4663
4715 4664
/// Lower a union constructor call like `Union::Variant(...)`.
4716 4665
fn lowerUnionCtor(self: *mut FnLowerer, node: *ast::Node, sym: *mut resolver::Symbol, call: ast::Call) -> il::Val
4717 4666
    throws (LowerError)
4718 4667
{
4719 -
    let unionTy = resolver::typeFor(self.low.resolver, node) else {
4720 -
        throw LowerError::MissingType(node);
4721 -
    };
4668 +
    let unionTy = try typeOf(self, node);
4722 4669
    let case resolver::SymbolData::Variant { type: payloadType, index, .. } = sym.data else {
4723 4670
        throw LowerError::ExpectedVariant;
4724 4671
    };
4725 4672
    let unionInfo = unionInfoFromType(unionTy) else {
4726 4673
        throw LowerError::MissingMetadata;
4736 4683
    return try buildTagged(self, resolver::getTypeLayout(unionTy), index as i64, payloadVal, payloadType, 1, valOffset);
4737 4684
}
4738 4685
4739 4686
/// Lower a field access into a pointer to the field.
4740 4687
fn lowerFieldRef(self: *mut FnLowerer, access: ast::Access) -> FieldRef throws (LowerError) {
4741 -
    let parentTy = resolver::typeFor(self.low.resolver, access.parent) else {
4742 -
        throw LowerError::MissingType(access.parent);
4743 -
    };
4688 +
    let parentTy = try typeOf(self, access.parent);
4744 4689
    let subjectTy = resolver::autoDeref(parentTy);
4745 4690
    let fieldIdx = resolver::recordFieldIndexFor(self.low.resolver, access.child) else {
4746 4691
        throw LowerError::MissingMetadata;
4747 4692
    };
4748 4693
    let fieldInfo = resolver::getRecordField(subjectTy, fieldIdx) else {
4857 4802
    }
4858 4803
    // Handle variable address: `&x`
4859 4804
    if let case ast::NodeValue::Ident(_) = addr.target.value {
4860 4805
        if let v = lookupLocalVar(self, addr.target) {
4861 4806
            let val = try useVar(self, v);
4862 -
            let typ = resolver::typeFor(self.low.resolver, addr.target) else {
4863 -
                throw LowerError::MissingType(addr.target);
4864 -
            };
4807 +
            let typ = try typeOf(self, addr.target);
4865 4808
            // For aggregates, the value is already a pointer.
4866 4809
            if isAggregateType(typ) {
4867 4810
                return val;
4868 4811
            }
4869 4812
            // For scalars, if we've already materialized a stack slot for this
4910 4853
    sliceNode: *ast::Node,
4911 4854
    arrayNode: *ast::Node,
4912 4855
    elements: *mut [*ast::Node]
4913 4856
) -> il::Val throws (LowerError) {
4914 4857
    // Get the slice type from the address-of expression.
4915 -
    let sliceTy = resolver::typeFor(self.low.resolver, sliceNode) else {
4916 -
        throw LowerError::MissingType(sliceNode);
4917 -
    };
4858 +
    let sliceTy = try typeOf(self, sliceNode);
4918 4859
    let case resolver::Type::Slice { item, mutable } = sliceTy else {
4919 4860
        throw LowerError::UnexpectedType(&sliceTy);
4920 4861
    };
4921 4862
    if elements.len == 0 { // Empty slices don't need to be stored as data.
4922 4863
        return try buildSliceValue(self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0));
4982 4923
/// the data pointer (for slices), and emitting an [`il::Instr::Elem`] to compute
4983 4924
/// the element address.
4984 4925
fn lowerElemPtr(
4985 4926
    self: *mut FnLowerer, container: *ast::Node, index: *ast::Node
4986 4927
) -> ElemPtrResult throws (LowerError) {
4987 -
    let containerTy = resolver::typeFor(self.low.resolver, container) else {
4988 -
        throw LowerError::MissingType(container);
4989 -
    };
4928 +
    let containerTy = try typeOf(self, container);
4990 4929
    let subjectTy = resolver::autoDeref(containerTy);
4991 4930
    let indexVal = try lowerExpr(self, index);
4992 4931
    let baseVal = try lowerExpr(self, container);
4993 4932
    let baseReg = emitValToReg(self, baseVal);
4994 4933
5022 4961
    return ElemPtrResult { elemReg, elemType };
5023 4962
}
5024 4963
5025 4964
/// Lower a dereference expression.
5026 4965
fn lowerDeref(self: *mut FnLowerer, node: *ast::Node, target: *ast::Node) -> il::Val throws (LowerError) {
5027 -
    let type = resolver::typeFor(self.low.resolver, node) else {
5028 -
        throw LowerError::MissingType(node);
5029 -
    };
4966 +
    let type = try typeOf(self, node);
5030 4967
    let ptrVal = try lowerExpr(self, target);
5031 4968
    let ptrReg = emitValToReg(self, ptrVal);
5032 4969
5033 4970
    return emitRead(self, ptrReg, 0, type);
5034 4971
}
5054 4991
        return;
5055 4992
    }
5056 4993
    let case ast::NodeValue::Ident(name) = l.ident.value else {
5057 4994
        throw LowerError::ExpectedIdentifier;
5058 4995
    };
5059 -
    let typ = resolver::typeFor(self.low.resolver, l.value) else {
5060 -
        throw LowerError::MissingType(l.value);
5061 -
    };
4996 +
    let typ = try typeOf(self, l.value);
5062 4997
    let ilType = ilType(self.low, typ);
5063 4998
    let mut varVal = val;
5064 4999
5065 5000
    // Aggregates with persistent storage need a local copy to avoid aliasing.
5066 5001
    // Temporaries such as literals or call results can be adopted directly.
5188 5123
            // First try local variable lookup.
5189 5124
            if let v = lookupLocalVar(self, a.left) {
5190 5125
                if not getVar(self, v).mutable {
5191 5126
                    throw LowerError::ImmutableAssignment;
5192 5127
                }
5193 -
                let leftTy = resolver::typeFor(self.low.resolver, a.left) else {
5194 -
                    throw LowerError::MissingType(a.left);
5195 -
                };
5128 +
                let leftTy = try typeOf(self, a.left);
5196 5129
                if isAggregateType(leftTy) or getVar(self, v).addressTaken {
5197 5130
                    // Aggregates and address-taken scalars are represented as
5198 5131
                    // pointers to stack memory. Store through the pointer.
5199 5132
                    let val = try useVar(self, v);
5200 5133
                    let dst = emitValToReg(self, val);
5216 5149
        }
5217 5150
        case ast::NodeValue::Deref(target) => {
5218 5151
            // Assignment through pointer dereference: `*ptr = value`.
5219 5152
            let ptrVal = try lowerExpr(self, target);
5220 5153
            let ptrReg = emitValToReg(self, ptrVal);
5221 -
            let targetTy = resolver::typeFor(self.low.resolver, a.left) else {
5222 -
                throw LowerError::MissingType(a.left);
5223 -
            };
5154 +
            let targetTy = try typeOf(self, a.left);
5224 5155
            try emitStore(self, ptrReg, 0, targetTy, rhs);
5225 5156
        }
5226 5157
        case ast::NodeValue::Subscript { container, index } => {
5227 5158
            // Assignment to array/slice element: `arr[i] = value`.
5228 5159
            let result = try lowerElemPtr(self, container, index);
5240 5171
    range: ast::Range,
5241 5172
    info: resolver::SliceRangeInfo
5242 5173
) throws (LowerError) {
5243 5174
    let r = try resolveSliceRangePtr(self, container, range, info);
5244 5175
    let elemSize = resolver::getTypeLayout(*info.itemType).size;
5245 -
    let rhsTy = resolver::typeFor(self.low.resolver, rhs) else {
5246 -
        throw LowerError::MissingType(rhs);
5247 -
    };
5176 +
    let rhsTy = try typeOf(self, rhs);
5248 5177
5249 5178
    if let case resolver::Type::Slice { .. } = rhsTy {
5250 5179
        // Copy from source slice.
5251 5180
        let srcReg = emitValToReg(self, try lowerExpr(self, rhs));
5252 5181
        let srcData = loadSlicePtr(self, srcReg);
5541 5470
/// into the buffer and the buffer pointer is returned. Otherwise, the value is
5542 5471
/// returned directly.
5543 5472
fn emitRetVal(self: *mut FnLowerer, val: il::Val) throws (LowerError) {
5544 5473
    if let retReg = self.returnReg {
5545 5474
        let src = emitValToReg(self, val);
5546 -
        let size = retBufSize(self);
5475 +
        let size = resolver::getResultLayout(*self.fnType.returnType, self.fnType.throwList).size
5476 +
            if self.fnType.throwList.len > 0
5477 +
            else resolver::getTypeLayout(*self.fnType.returnType).size;
5547 5478
5548 5479
        emit(self, il::Instr::Blit { dst: retReg, src, size: il::Val::Imm(size as i64) });
5549 5480
        emit(self, il::Instr::Ret { val: il::Val::Reg(retReg) });
5550 5481
    } else if isSmallAggregate(*self.fnType.returnType) {
5551 5482
        let src = emitValToReg(self, val);
5556 5487
    } else {
5557 5488
        emit(self, il::Instr::Ret { val });
5558 5489
    }
5559 5490
}
5560 5491
5561 -
/// Compute the size of the return buffer for the current function.
5562 -
fn retBufSize(self: *mut FnLowerer) -> u32 {
5563 -
    if self.fnType.throwList.len > 0 {
5564 -
        let successType = *self.fnType.returnType;
5565 -
5566 -
        return resolver::getResultLayout(successType, self.fnType.throwList).size;
5567 -
    }
5568 -
    return resolver::getTypeLayout(*self.fnType.returnType).size;
5569 -
}
5570 -
5571 5492
/// Lower a return statement.
5572 5493
fn lowerReturnStmt(self: *mut FnLowerer, node: *ast::Node, value: ?*ast::Node) throws (LowerError) {
5573 5494
    let mut val = il::Val::Undef;
5574 5495
    if let expr = value {
5575 5496
        val = try lowerExpr(self, expr);
5580 5501
5581 5502
/// Lower a throw statement.
5582 5503
fn lowerThrowStmt(self: *mut FnLowerer, expr: *ast::Node) throws (LowerError) {
5583 5504
    assert self.fnType.throwList.len > 0;
5584 5505
5585 -
    let errType = resolver::typeFor(self.low.resolver, expr) else {
5586 -
        throw LowerError::MissingType(expr);
5587 -
    };
5506 +
    let errType = try typeOf(self, expr);
5588 5507
    let tag = getOrAssignErrorTag(self.low, errType) as i64;
5589 5508
    let errVal = try lowerExpr(self, expr);
5590 5509
    let resultVal = try buildResult(self, tag, errVal, errType);
5591 5510
5592 5511
    try emitRetVal(self, resultVal);
5696 5615
5697 5616
/// Lower a conditional expression (`thenExpr if condition else elseExpr`).
5698 5617
fn lowerCondExpr(self: *mut FnLowerer, node: *ast::Node, cond: ast::CondExpr) -> il::Val
5699 5618
    throws (LowerError)
5700 5619
{
5701 -
    let typ = resolver::typeFor(self.low.resolver, node) else {
5702 -
        throw LowerError::MissingType(node);
5703 -
    };
5620 +
    let typ = try typeOf(self, node);
5704 5621
    let thenBlock = try createBlock(self, "cond#then");
5705 5622
    let elseBlock = try createBlock(self, "cond#else");
5706 5623
5707 5624
    if isAggregateType(typ) {
5708 5625
        let dst = try emitReserve(self, typ);
5785 5702
    let b = try lowerExpr(self, binop.right);
5786 5703
    let isComparison = cmpOpFrom(binop.op, false) != nil;
5787 5704
5788 5705
    // The result type for comparisons is always `bool`, while for arithmetic
5789 5706
    // operations, it's the operand type. We set this appropriately here.
5790 -
    let nodeTy = resolver::typeFor(self.low.resolver, node) else {
5791 -
        throw LowerError::MissingType(node);
5792 -
    };
5707 +
    let nodeTy = try typeOf(self, node);
5793 5708
    let mut resultTy = nodeTy;
5794 5709
5795 5710
    if isComparison {
5796 5711
        let leftTy = try effectiveType(self, binop.left);
5797 5712
        let rightTy = try effectiveType(self, binop.right);
5929 5844
}
5930 5845
5931 5846
/// Lower a unary operation.
5932 5847
fn lowerUnOp(self: *mut FnLowerer, node: *ast::Node, unop: ast::UnOp) -> il::Val throws (LowerError) {
5933 5848
    let val = try lowerExpr(self, unop.value);
5934 -
    let t = resolver::typeFor(self.low.resolver, node) else {
5935 -
        throw LowerError::MissingType(node);
5936 -
    };
5849 +
    let t = try typeOf(self, node);
5937 5850
    let typ = ilType(self.low, t);
5938 5851
    let dst = nextReg(self);
5939 5852
    let mut needsExt: bool = false;
5940 5853
5941 5854
    match unop.op {
5960 5873
5961 5874
/// Lower a cast expression (`x as T`).
5962 5875
fn lowerCast(self: *mut FnLowerer, node: *ast::Node, cast: ast::As) -> il::Val throws (LowerError) {
5963 5876
    let val = try lowerExpr(self, cast.value);
5964 5877
5965 -
    let srcType = resolver::typeFor(self.low.resolver, cast.value) else {
5966 -
        throw LowerError::MissingType(cast.value);
5967 -
    };
5968 -
    let dstType = resolver::typeFor(self.low.resolver, node) else {
5969 -
        throw LowerError::MissingType(node);
5970 -
    };
5878 +
    let srcType = try typeOf(self, cast.value);
5879 +
    let dstType = try typeOf(self, node);
5971 5880
    if resolver::typesEqual(srcType, dstType) {
5972 5881
        return val;
5973 5882
    }
5974 5883
    return lowerNumericCast(self, val, srcType, dstType);
5975 5884
}
5994 5903
///
5995 5904
/// String literals are stored as global data and the result is a slice
5996 5905
/// pointing to the data with the appropriate length.
5997 5906
fn lowerStringLit(self: *mut FnLowerer, node: *ast::Node, s: *[u8]) -> il::Val throws (LowerError) {
5998 5907
    // Get the slice type from the node.
5999 -
    let sliceTy = resolver::typeFor(self.low.resolver, node) else {
6000 -
        throw LowerError::MissingType(node);
6001 -
    };
5908 +
    let sliceTy = try typeOf(self, node);
6002 5909
    let case resolver::Type::Slice { item, mutable } = sliceTy else {
6003 5910
        throw LowerError::ExpectedSliceOrArray;
6004 5911
    };
6005 5912
    // Build the string data value.
6006 5913
    let ptr = try! alloc::alloc(
6028 5935
/// Lower a `@sliceOf(ptr, len)` or `@sliceOf(ptr, len, cap)` builtin call.
6029 5936
fn lowerSliceOf(self: *mut FnLowerer, node: *ast::Node, args: *mut [*ast::Node]) -> il::Val throws (LowerError) {
6030 5937
    if args.len != 2 and args.len != 3 {
6031 5938
        throw LowerError::InvalidArgCount;
6032 5939
    }
6033 -
    let sliceTy = resolver::typeFor(self.low.resolver, node) else {
6034 -
        throw LowerError::MissingType(node);
6035 -
    };
5940 +
    let sliceTy = try typeOf(self, node);
6036 5941
    let case resolver::Type::Slice { item, mutable } = sliceTy else {
6037 5942
        throw LowerError::ExpectedSliceOrArray;
6038 5943
    };
6039 5944
    let ptrVal = try lowerExpr(self, args[0]);
6040 5945
    let lenVal = try lowerExpr(self, args[1]);
6048 5953
/// Lower a `try` expression.
6049 5954
fn lowerTry(self: *mut FnLowerer, node: *ast::Node, t: ast::Try) -> il::Val throws (LowerError) {
6050 5955
    let case ast::NodeValue::Call(callExpr) = t.expr.value else {
6051 5956
        throw LowerError::ExpectedCall;
6052 5957
    };
6053 -
    let calleeTy = resolver::typeFor(self.low.resolver, callExpr.callee) else {
6054 -
        throw LowerError::MissingType(callExpr.callee);
6055 -
    };
5958 +
    let calleeTy = try typeOf(self, callExpr.callee);
6056 5959
    let case resolver::Type::Fn(calleeInfo) = calleeTy else {
6057 5960
        throw LowerError::ExpectedFunction;
6058 5961
    };
6059 5962
    let okValueTy = *calleeInfo.returnType; // The type of the success payload.
6060 5963
6061 5964
    // Type of the try expression, which is either the return type of the function
6062 5965
    // if successful, or an optional of it, if using `try?`.
6063 -
    let tryExprTy = resolver::typeFor(self.low.resolver, node) else {
6064 -
        throw LowerError::MissingType(node);
6065 -
    };
5966 +
    let tryExprTy = try typeOf(self, node);
6066 5967
    // Check for trait method dispatch or standalone method call.
6067 5968
    let mut resVal: il::Val = undefined;
6068 5969
    let callNodeExtra = resolver::nodeData(self.low.resolver, t.expr).extra;
6069 5970
    if let case resolver::NodeExtra::TraitMethodCall {
6070 5971
        traitInfo, methodIndex
6216 6117
6217 6118
        blocks[i] = try createBlock(self, "catch");
6218 6119
        addPredecessor(self, blocks[i], entry);
6219 6120
6220 6121
        if let typeNode = clause.typeNode {
6221 -
            let errTy = resolver::typeFor(self.low.resolver, typeNode) else {
6222 -
                throw LowerError::MissingType(typeNode);
6223 -
            };
6122 +
            let errTy = try typeOf(self, typeNode);
6224 6123
            errTypes[i] = errTy;
6225 6124
6226 6125
            cases.append(il::SwitchCase {
6227 6126
                value: getOrAssignErrorTag(self.low, errTy) as i64,
6228 6127
                target: blocks[i].n,
6668 6567
) -> il::Val throws (LowerError) {
6669 6568
    let case ast::NodeValue::FieldAccess(access) = call.callee.value
6670 6569
        else throw LowerError::MissingMetadata;
6671 6570
6672 6571
    // Get the receiver as a pointer.
6673 -
    let parentTy = resolver::typeFor(self.low.resolver, access.parent)
6674 -
        else throw LowerError::MissingType(access.parent);
6572 +
    let parentTy = try typeOf(self, access.parent);
6675 6573
    let receiverVal = try lowerReceiver(self, access.parent, parentTy);
6676 6574
6677 6575
    let qualName = instanceMethodName(self.low, nil, method.concreteTypeName, method.name);
6678 6576
    let case resolver::SymbolData::Value { type: resolver::Type::Fn(fnInfo), .. } = method.symbol.data
6679 6577
        else panic "lowerMethodCall: expected Fn type on method symbol";
6762 6660
fn lowerCall(self: *mut FnLowerer, node: *ast::Node, call: ast::Call) -> il::Val throws (LowerError) {
6763 6661
    // Check for intrinsic calls before normal call lowering.
6764 6662
    if let intrinsicVal = try lowerIntrinsicCall(self, call) {
6765 6663
        return intrinsicVal;
6766 6664
    }
6767 -
    let calleeTy = resolver::typeFor(self.low.resolver, call.callee) else {
6768 -
        throw LowerError::MissingType(call.callee);
6769 -
    };
6665 +
    let calleeTy = try typeOf(self, call.callee);
6770 6666
    let case resolver::Type::Fn(fnInfo) = calleeTy else {
6771 6667
        throw LowerError::ExpectedFunction;
6772 6668
    };
6773 6669
    let callee = try lowerCallee(self, call.callee);
6774 6670
    let offset: u32 = 1 if requiresReturnParam(fnInfo) else 0;
6812 6708
/// for signed source types and zero-extension for unsigned source types.
6813 6709
fn lowerNumericCast(self: *mut FnLowerer, val: il::Val, srcType: resolver::Type, dstType: resolver::Type) -> il::Val {
6814 6710
    let srcLayout = resolver::getTypeLayout(srcType);
6815 6711
    let dstLayout = resolver::getTypeLayout(dstType);
6816 6712
6817 -
    if srcLayout.size < dstLayout.size {
6818 -
        // Widening conversion: sign/zero-extend based on source signedness.
6819 -
        let dst = nextReg(self);
6820 -
        let srcIrType = ilType(self.low, srcType);
6821 -
6822 -
        if isSignedType(srcType) {
6823 -
            emit(self, il::Instr::Sext { typ: srcIrType, dst, val });
6824 -
        } else {
6825 -
            emit(self, il::Instr::Zext { typ: srcIrType, dst, val });
6826 -
        }
6827 -
        return il::Val::Reg(dst);
6713 +
    if srcLayout.size == dstLayout.size {
6714 +
        // Same size: bit pattern is unchanged, value is returned as-is.
6715 +
        return val;
6828 6716
    }
6829 -
    if srcLayout.size > dstLayout.size {
6830 -
        // Narrowing conversion: truncate and normalize to destination width.
6831 -
        let dstIrType = ilType(self.low, dstType);
6832 -
        let dst = nextReg(self);
6717 +
    // Widening: extend based on source signedness.
6718 +
    // Narrowing: truncate and normalize to destination width.
6719 +
    let widening = srcLayout.size < dstLayout.size;
6720 +
    let extType = ilType(self.low, srcType) if widening else ilType(self.low, dstType);
6721 +
    let signed = isSignedType(srcType) if widening else isSignedType(dstType);
6722 +
    let dst = nextReg(self);
6833 6723
6834 -
        if isSignedType(dstType) {
6835 -
            emit(self, il::Instr::Sext { typ: dstIrType, dst, val });
6836 -
        } else {
6837 -
            emit(self, il::Instr::Zext { typ: dstIrType, dst, val });
6838 -
        }
6839 -
        return il::Val::Reg(dst);
6724 +
    if signed {
6725 +
        emit(self, il::Instr::Sext { typ: extType, dst, val });
6726 +
    } else {
6727 +
        emit(self, il::Instr::Zext { typ: extType, dst, val });
6840 6728
    }
6841 -
    // Same size: bit pattern is unchanged, value is returned as-is.
6842 -
    // The backend is responsible for normalizing to the correct width
6843 -
    // when performing comparisons.
6844 -
    return val;
6729 +
    return il::Val::Reg(dst);
6845 6730
}
6846 6731
6847 6732
/// Lower an identifier that refers to a global symbol.
6848 6733
fn lowerGlobalSymbol(self: *mut FnLowerer, node: *ast::Node) -> il::Val throws (LowerError) {
6849 6734
    // First try to get a compile-time constant value.
6850 6735
    if let constVal = resolver::constValueFor(self.low.resolver, node) {
6851 6736
        return try constValueToVal(self, constVal, node);
6852 6737
    }
6853 6738
    // Otherwise get the symbol.
6854 -
    let sym = resolver::nodeData(self.low.resolver, node).sym else {
6855 -
        throw LowerError::MissingSymbol(node);
6856 -
    };
6739 +
    let sym = try symOf(self, node);
6857 6740
    let mut ty: resolver::Type = undefined;
6858 6741
6859 6742
    match sym.data {
6860 6743
        case resolver::SymbolData::Constant { type, .. } =>
6861 6744
            ty = type,
6875 6758
    return emitRead(self, dst, 0, ty);
6876 6759
}
6877 6760
6878 6761
/// Lower an assignment to a static variable.
6879 6762
fn lowerStaticAssign(self: *mut FnLowerer, target: *ast::Node, val: il::Val) throws (LowerError) {
6880 -
    let sym = resolver::nodeData(self.low.resolver, target).sym else {
6881 -
        throw LowerError::MissingSymbol(target);
6882 -
    };
6763 +
    let sym = try symOf(self, target);
6883 6764
    let case resolver::SymbolData::Value { type, .. } = sym.data else {
6884 6765
        throw LowerError::ImmutableAssignment;
6885 6766
    };
6886 6767
    let dst = emitDataAddr(self, sym);
6887 6768
6958 6839
            // First try local variable lookup.
6959 6840
            // Otherwise fall back to global symbol lookup.
6960 6841
            if let v = lookupLocalVar(self, node) {
6961 6842
                val = try useVar(self, v);
6962 6843
                if self.vars[v.id].addressTaken {
6963 -
                    let typ = resolver::typeFor(self.low.resolver, node) else {
6964 -
                        throw LowerError::MissingType(node);
6965 -
                    };
6844 +
                    let typ = try typeOf(self, node);
6966 6845
                    let ptr = emitValToReg(self, val);
6967 6846
                    val = emitRead(self, ptr, 0, typ);
6968 6847
                }
6969 6848
            } else {
6970 6849
                val = try lowerGlobalSymbol(self, node);
6982 6861
        }
6983 6862
        case ast::NodeValue::Char(c) => {
6984 6863
            val = il::Val::Imm(c as i64);
6985 6864
        }
6986 6865
        case ast::NodeValue::Nil => {
6987 -
            let typ = resolver::typeFor(self.low.resolver, node) else {
6988 -
                throw LowerError::MissingType(node);
6989 -
            };
6866 +
            let typ = try typeOf(self, node);
6990 6867
            if let case resolver::Type::Optional(_) = typ {
6991 6868
                val = try buildNilOptional(self, typ);
6992 6869
            } else if let case resolver::Type::Nil = typ {
6993 6870
                // Standalone `nil` without a concrete optional type. We can't
6994 6871
                // generate a proper value representation.
7051 6928
        }
7052 6929
        case ast::NodeValue::String(s) => {
7053 6930
            val = try lowerStringLit(self, node, s);
7054 6931
        }
7055 6932
        case ast::NodeValue::Undef => {
7056 -
            let typ = resolver::typeFor(self.low.resolver, node) else {
7057 -
                throw LowerError::MissingType(node);
7058 -
            };
6933 +
            let typ = try typeOf(self, node);
7059 6934
            if isAggregateType(typ) {
7060 6935
                // When `undefined` appears as a stand-alone expression,
7061 6936
                /// we need a stack slot for reads and writes.
7062 6937
                let slot = try emitReserve(self, typ);
7063 6938
                val = il::Val::Reg(slot);
7115 6990
///
7116 6991
/// The IL doesn't track signedness - that's encoded in the instructions
7117 6992
/// (e.g., Slt vs Ult).
7118 6993
fn ilType(self: *mut Lowerer, typ: resolver::Type) -> il::Type {
7119 6994
    match typ {
7120 -
        // Scalars map to their natural widths.
7121 -
        case resolver::Type::Bool => return il::Type::W8,
7122 -
        case resolver::Type::I8 => return il::Type::W8,
7123 -
        case resolver::Type::I16 => return il::Type::W16,
7124 -
        case resolver::Type::I32 => return il::Type::W32,
7125 -
        case resolver::Type::U8 => return il::Type::W8,
7126 -
        case resolver::Type::U16 => return il::Type::W16,
7127 -
        case resolver::Type::U32 => return il::Type::W32,
7128 -
        case resolver::Type::I64 => return il::Type::W64,
7129 -
        case resolver::Type::U64 => return il::Type::W64,
7130 -
        // Reference types are all pointer-sized (64-bit on RV64).
7131 -
        case resolver::Type::Pointer { .. } => return il::Type::W64,
7132 -
        // Aggregates are represented as pointers to stack memory.
7133 -
        case resolver::Type::Slice { .. } => return il::Type::W64,
7134 -
        case resolver::Type::Array(_) => return il::Type::W64,
7135 -
        case resolver::Type::Optional(_) => return il::Type::W64,
6995 +
        case resolver::Type::Bool,
6996 +
             resolver::Type::I8,
6997 +
             resolver::Type::U8 => return il::Type::W8,
6998 +
        case resolver::Type::I16,
6999 +
             resolver::Type::U16 => return il::Type::W16,
7000 +
        case resolver::Type::I32,
7001 +
             resolver::Type::U32 => return il::Type::W32,
7002 +
        case resolver::Type::I64,
7003 +
             resolver::Type::U64,
7004 +
             resolver::Type::Pointer { .. },
7005 +
             resolver::Type::Slice { .. },
7006 +
             resolver::Type::Array(_),
7007 +
             resolver::Type::Optional(_),
7008 +
             resolver::Type::Fn(_),
7009 +
             resolver::Type::TraitObject { .. } => return il::Type::W64,
7136 7010
        case resolver::Type::Nominal(_) => {
7137 7011
            if resolver::isVoidUnion(typ) {
7138 7012
                return il::Type::W8;
7139 7013
            }
7140 7014
            return il::Type::W64;
7141 7015
        }
7142 -
        case resolver::Type::Fn(_) => return il::Type::W64,
7143 -
        case resolver::Type::TraitObject { .. } => return il::Type::W64,
7144 -
        // Void functions return zero at the IL level.
7145 -
        case resolver::Type::Void => return il::Type::W32,
7146 -
        // FIXME: We shouldn't try to lower this type, it should be behind a pointer.
7147 -
        case resolver::Type::Opaque => return il::Type::W32,
7148 -
        // FIXME: This should be resolved to a concrete integer type in the resolver.
7149 -
        case resolver::Type::Int => return il::Type::W32,
7016 +
        case resolver::Type::Void,
7017 +
             // FIXME: We shouldn't try to lower this type, it should be behind a pointer.
7018 +
             resolver::Type::Opaque,
7019 +
             // FIXME: This should be resolved to a concrete integer type in the resolver.
7020 +
             resolver::Type::Int => return il::Type::W32,
7150 7021
        else => panic "ilType: type cannot be lowered",
7151 7022
    }
7152 7023
}
lib/std/lang/module.rad +0 -20
13 13
use std::lang::ast;
14 14
use std::lang::strings;
15 15
16 16
/// Maximum number of modules tracked in a single compilation graph.
17 17
pub const MAX_MODULES: u32 = 128;
18 -
/// Maximum number of characters for a module name.
19 -
const MAX_MODULE_NAME_LEN: u32 = 64;
20 18
/// Maximum number of characters for a module path.
21 19
const MAX_PATH_LEN: u32 = 256;
22 20
/// Maximum number of components that make up a logical module path.
23 21
const MAX_MODULE_PATH_DEPTH: u32 = 16;
24 22
/// Filesystem separator used when constructing child paths.
44 42
45 43
/// Error categories that can be produced by the module graph.
46 44
pub union ModuleError {
47 45
    /// Module not found.
48 46
    NotFound(u16),
49 -
    /// Module path not found.
50 -
    PathNotFound(*[u8]),
51 47
    /// Adding the module would exceed the fixed storage.
52 48
    CapacityExceeded,
53 -
    /// Requested parent identifier is invalid.
54 -
    InvalidParent,
55 49
    /// Provided path is missing the required `.rad` extension or otherwise invalid.
56 50
    InvalidPath,
57 -
    /// Attempted to register a module whose name already exists in the same namespace.
58 -
    DuplicateModule,
59 -
    /// Attempt to register a root module when there already is one.
60 -
    DuplicateRoot,
61 -
    /// Fully qualified file path exceeds the fixed storage.
62 -
    PathTooLong,
63 -
    /// Module name exceeds the fixed storage.
64 -
    NameTooLong,
65 51
    /// Module graph path exceeded the maximum logical depth.
66 52
    PathTooDeep,
67 53
    /// Attempt to register a module before its parent.
68 54
    MissingParent,
69 55
}
207 193
pub fn moduleQualifiedPath(m: *ModuleEntry) -> *[*[u8]] {
208 194
    assert m.pathDepth > 0, "moduleQualifiedPath: path must not be empty";
209 195
    return &m.path[..m.pathDepth];
210 196
}
211 197
212 -
/// Update the recorded lifecycle state for `id`.
213 -
pub fn setState(graph: *mut ModuleGraph, id: u16, state: ModuleState) throws (ModuleError) {
214 -
    let m = getMut(graph, id) else throw ModuleError::NotFound(id);
215 -
    m.state = state;
216 -
}
217 -
218 198
/// Retrieve the lifecycle state for `id`.
219 199
pub fn state(graph: *ModuleGraph, id: u16) -> ModuleState throws (ModuleError) {
220 200
    let m = get(graph, id) else {
221 201
        throw ModuleError::NotFound(id);
222 202
    };
lib/std/lang/package.rad +0 -8
5 5
use std::lang::module;
6 6
use std::lang::strings;
7 7
8 8
/// Maximum number of packages processed in a single invocation.
9 9
pub const MAX_PACKAGES: u32 = 4;
10 -
/// Maximum byte length for a package name.
11 -
pub const MAX_PACKAGE_NAME_LEN: u32 = 64;
12 -
13 -
/// Errors produced while configuring packages.
14 -
pub union PackageError {
15 -
    /// The provided name exceeds the buffer.
16 -
    NameTooLong,
17 -
}
18 10
19 11
/// A compilation unit.
20 12
pub record Package {
21 13
    /// Package identifier (index in the package array).
22 14
    id: u16,
lib/std/lang/parser.rad +21 -74
261 261
/// Emit a number literal node with the provided literal metadata.
262 262
fn nodeNumber(p: *mut Parser, literal: ast::IntLiteral) -> *ast::Node {
263 263
    return node(p, ast::NodeValue::Number(literal));
264 264
}
265 265
266 -
/// Emit an identifier node with the given name.
267 -
fn nodeIdent(p: *mut Parser, name: *[u8]) -> *ast::Node {
268 -
    return node(p, ast::NodeValue::Ident(name));
269 -
}
270 -
271 266
/// Emit a `super` node.
272 267
fn nodeSuper(p: *mut Parser) -> *ast::Node {
273 268
    return node(p, ast::NodeValue::Super);
274 269
}
275 270
276 -
/// Emit a `nil` literal node.
277 -
fn nodeNil(p: *mut Parser) -> *ast::Node {
278 -
    return node(p, ast::NodeValue::Nil);
279 -
}
280 -
281 -
/// Emit an `undefined` literal node.
282 -
fn nodeUndef(p: *mut Parser) -> *ast::Node {
283 -
    return node(p, ast::NodeValue::Undef);
284 -
}
285 -
286 -
/// Emit a character literal node.
287 -
fn nodeChar(p: *mut Parser, value: u8) -> *ast::Node {
288 -
    return node(p, ast::NodeValue::Char(value));
289 -
}
290 -
291 -
/// Emit a string literal node.
292 -
fn nodeString(p: *mut Parser, data: *[u8]) -> *ast::Node {
293 -
    return node(p, ast::NodeValue::String(data));
294 -
}
295 -
296 271
/// Process escape sequences in a raw string, writing the result into `dst`.
297 272
/// Returns the number of bytes written.
298 273
fn unescapeString(raw: *[u8], dst: *mut [u8]) -> u32 {
299 274
    let mut i: u32 = 0;
300 275
    let mut j: u32 = 0;
328 303
/// Emit a unary operator node.
329 304
fn nodeUnary(p: *mut Parser, op: ast::UnaryOp, value: *ast::Node) -> *ast::Node {
330 305
    return node(p, ast::NodeValue::UnOp({ op, value }));
331 306
}
332 307
333 -
/// Emit an address-of (reference) node.
334 -
fn nodeAddressOf(p: *mut Parser, target: *ast::Node, mutable: bool) -> *ast::Node {
335 -
    return node(p, ast::NodeValue::AddressOf({ target, mutable }));
336 -
}
337 -
338 -
/// Emit a dereference node.
339 -
fn nodeDeref(p: *mut Parser, target: *ast::Node) -> *ast::Node {
340 -
    return node(p, ast::NodeValue::Deref(target));
341 -
}
342 -
343 308
/// Parse a parenthesized expression without applying postfix operators.
344 309
fn parseParenthesized(p: *mut Parser) -> *ast::Node
345 310
    throws (ParseError)
346 311
{
347 312
    try expect(p, scanner::TokenKind::LParen, "expected `(`");
563 528
            return nodeUnary(p, ast::UnaryOp::BitNot, value);
564 529
        }
565 530
        case scanner::TokenKind::Star => {
566 531
            advance(p);
567 532
            let value = try parseUnaryExpr(p);
568 -
            return nodeDeref(p, value);
533 +
            return node(p, ast::NodeValue::Deref(value));
569 534
        }
570 535
        case scanner::TokenKind::Amp => {
571 536
            advance(p);
572 537
            let mutable = consume(p, scanner::TokenKind::Mut);
573 538
            let target = try parseUnaryExpr(p);
574 -
            return nodeAddressOf(p, target, mutable);
539 +
            return node(p, ast::NodeValue::AddressOf({ target: target, mutable: mutable }));
575 540
        }
576 541
        else => {
577 542
            return try parsePrimary(p);
578 543
        }
579 544
    }
670 635
///
671 636
/// Statements that end with blocks don't require semicolons.
672 637
/// All other statements do.
673 638
fn expectsSemicolon(stmt: *ast::Node) -> bool {
674 639
    match stmt.value {
675 -
        case ast::NodeValue::If(_) => return false,
676 -
        case ast::NodeValue::IfLet(_) => return false,
677 -
        case ast::NodeValue::While(_) => return false,
678 -
        case ast::NodeValue::WhileLet(_) => return false,
679 -
        case ast::NodeValue::For(_) => return false,
680 -
        case ast::NodeValue::Loop { .. } => return false,
681 -
        case ast::NodeValue::Match(_) => return false,
682 -
        case ast::NodeValue::Block(_) => return false,
683 -
        case ast::NodeValue::FnDecl(_) => return false,
684 -
        case ast::NodeValue::RecordDecl(_) => return false,
685 -
        case ast::NodeValue::UnionDecl(_) => return false,
686 -
        case ast::NodeValue::TraitDecl { .. } => return false,
687 -
        case ast::NodeValue::InstanceDecl { .. } => return false,
688 -
        case ast::NodeValue::MethodDecl { .. } => return false,
640 +
        case ast::NodeValue::If(_),
641 +
             ast::NodeValue::IfLet(_),
642 +
             ast::NodeValue::While(_),
643 +
             ast::NodeValue::WhileLet(_),
644 +
             ast::NodeValue::For(_),
645 +
             ast::NodeValue::Loop { .. },
646 +
             ast::NodeValue::Match(_),
647 +
             ast::NodeValue::Block(_),
648 +
             ast::NodeValue::FnDecl(_),
649 +
             ast::NodeValue::RecordDecl(_),
650 +
             ast::NodeValue::UnionDecl(_),
651 +
             ast::NodeValue::TraitDecl { .. },
652 +
             ast::NodeValue::InstanceDecl { .. },
653 +
             ast::NodeValue::MethodDecl { .. } => return false,
689 654
        else => return true,
690 655
    }
691 656
}
692 657
693 658
/// Parse a primary leaf expression without postfix operators.
703 668
            advance(p);
704 669
            return nodeBool(p, false);
705 670
        }
706 671
        case scanner::TokenKind::Ident => {
707 672
            advance(p);
708 -
            return nodeIdent(p, p.previous.source);
673 +
            return node(p, ast::NodeValue::Ident(p.previous.source));
709 674
        }
710 675
        case scanner::TokenKind::Super => {
711 676
            advance(p);
712 677
            return nodeSuper(p);
713 678
        }
722 687
        case scanner::TokenKind::Try => {
723 688
            return try parseTryExpr(p);
724 689
        }
725 690
        case scanner::TokenKind::Nil => {
726 691
            advance(p);
727 -
            return nodeNil(p);
692 +
            return node(p, ast::NodeValue::Nil);
728 693
        }
729 694
        case scanner::TokenKind::Undefined => {
730 695
            advance(p);
731 -
            return nodeUndef(p);
696 +
            return node(p, ast::NodeValue::Undef);
732 697
        }
733 698
        case scanner::TokenKind::Char => {
734 699
            advance(p);
735 700
            let src = p.previous.source;
736 701
            let mut ch: u8 = 0;
745 710
                    else      => { ch = src[2]; }
746 711
                }
747 712
            } else {
748 713
                ch = src[1];
749 714
            }
750 -
            return nodeChar(p, ch);
715 +
            return node(p, ast::NodeValue::Char(ch));
751 716
        }
752 717
        case scanner::TokenKind::String => {
753 718
            advance(p);
754 719
            let src = p.previous.source;
755 720
            let raw = &src[1..src.len - 1]; // Strip quotes.
757 722
            // Process escape sequences into arena buffer.
758 723
            let buf = alloc::remainingBuf(&mut p.arena.arena);
759 724
            let len = unescapeString(raw, buf);
760 725
            alloc::commit(&mut p.arena.arena, len);
761 726
762 -
            return nodeString(p, &buf[..len]);
727 +
            return node(p, ast::NodeValue::String(&buf[..len]));
763 728
        }
764 729
        case scanner::TokenKind::Underscore => {
765 730
            advance(p);
766 731
            return node(p, ast::NodeValue::Placeholder);
767 732
        }
1439 1404
        message = try parseExpr(p);
1440 1405
    }
1441 1406
    return node(p, ast::NodeValue::Assert { condition, message });
1442 1407
}
1443 1408
1444 -
/// Parse a `break` statement.
1445 -
fn parseBreak(p: *mut Parser) -> *ast::Node
1446 -
    throws (ParseError)
1447 -
{
1448 -
    try expect(p, scanner::TokenKind::Break, "expected `break`");
1449 -
1450 -
    return node(p, ast::NodeValue::Break);
1451 -
}
1452 -
1453 -
/// Parse a `continue` statement.
1454 -
fn parseContinue(p: *mut Parser) -> *ast::Node
1455 -
    throws (ParseError)
1456 -
{
1457 -
    try expect(p, scanner::TokenKind::Continue, "expected `continue`");
1458 -
1459 -
    return node(p, ast::NodeValue::Continue);
1460 -
}
1461 -
1462 1409
/// Parse a `try` expression with optional `catch` clause(s).
1463 1410
fn parseTryExpr(p: *mut Parser) -> *ast::Node
1464 1411
    throws (ParseError)
1465 1412
{
1466 1413
    try expect(p, scanner::TokenKind::Try, "expected `try`");
lib/std/lang/resolver.rad +22 -72
434 434
    UnresolvedSymbol(*[u8]),
435 435
    /// Attempted to assign to an immutable binding.
436 436
    ImmutableBinding,
437 437
    /// Expected a compile-time constant expression.
438 438
    ConstExprRequired,
439 -
    /// Scope stack exceeded its fixed capacity.
440 -
    ScopeOverflow,
441 -
    /// Node table capacity reached.
442 -
    NodeOverflow,
443 439
    /// Symbol arena exhausted while binding identifiers.
444 440
    SymbolOverflow,
445 441
    /// Expression has the wrong type.
446 442
    TypeMismatch(TypeMismatch),
447 443
    /// Numeric literal does not fit within the required range.
472 468
    ExpectedRecord,
473 469
    /// Expected an array or slice value.
474 470
    ExpectedIndexable,
475 471
    /// Expected an iterable (array, slice, or range) for a `for` loop.
476 472
    ExpectedIterable,
477 -
    /// Expected a block node.
478 -
    ExpectedBlock,
479 473
    /// Invalid `as` cast between the provided types.
480 474
    InvalidAsCast(InvalidAsCast),
481 475
    /// Invalid alignment value specified.
482 476
    InvalidAlignmentValue(u32),
483 477
    /// Invalid module path.
526 520
    TryCatchDuplicateType,
527 521
    /// Typed catch clauses do not cover all error types.
528 522
    TryCatchNonExhaustive,
529 523
    /// Called a fallible function without using `try`.
530 524
    MissingTry,
531 -
    /// `else` branch of a `let` guard must not fall through.
532 -
    ElseBranchMustDiverge,
533 525
    /// Cannot use opaque type in this context.
534 526
    OpaqueTypeNotAllowed,
535 527
    /// Cannot dereference pointer to opaque type.
536 528
    OpaqueTypeDeref,
537 529
    /// Cannot perform pointer arithmetic on opaque pointer.
538 530
    OpaquePointerArithmetic,
539 531
    /// Cannot infer type from context.
540 532
    CannotInferType,
541 -
    /// Cyclic type dependency detected during resolution.
542 -
    CyclicTypeDependency(*[u8]),
543 533
    /// Cannot assign a void value to a variable.
544 534
    CannotAssignVoid,
545 535
    /// `default` attribute used on a non-function declaration.
546 536
    DefaultAttrOnlyOnFn,
547 537
    /// Union variant requires a payload but none was provided.
1351 1341
        return a;
1352 1342
    }
1353 1343
    return b;
1354 1344
}
1355 1345
1356 -
1357 1346
/// Get the layout of a type.
1358 1347
pub fn getTypeLayout(ty: Type) -> Layout {
1359 1348
    match ty {
1360 1349
        case Type::Void, Type::Never => return Layout { size: 0, alignment: 0 },
1361 -
        case Type::Bool => return Layout { size: 1, alignment: 1 },
1362 -
        case Type::U8, Type::I8 => return Layout { size: 1, alignment: 1 },
1350 +
        case Type::Bool, Type::U8, Type::I8 => return Layout { size: 1, alignment: 1 },
1363 1351
        case Type::U16, Type::I16 => return Layout { size: 2, alignment: 2 },
1364 1352
        case Type::U32, Type::I32, Type::Int => return Layout { size: 4, alignment: 4 },
1365 1353
        case Type::U64, Type::I64 => return Layout { size: 8, alignment: 8 },
1366 -
        case Type::Pointer { .. } => return Layout { size: PTR_SIZE, alignment: PTR_SIZE },
1367 -
        case Type::Slice { .. } => return Layout { size: PTR_SIZE * 2, alignment: PTR_SIZE },
1354 +
        case Type::Pointer { .. },
1355 +
             Type::Fn(_) => return Layout { size: PTR_SIZE, alignment: PTR_SIZE },
1356 +
        case Type::Slice { .. },
1357 +
             Type::TraitObject { .. } => return Layout { size: PTR_SIZE * 2, alignment: PTR_SIZE },
1368 1358
        case Type::Array(arr) => return getArrayLayout(arr),
1369 1359
        case Type::Optional(inner) => return getOptionalLayout(*inner),
1370 1360
        case Type::Nominal(info) => return getNominalLayout(*info),
1371 -
        case Type::Fn(_) => return Layout { size: PTR_SIZE, alignment: PTR_SIZE },
1372 -
        case Type::TraitObject { .. } => return Layout { size: PTR_SIZE * 2, alignment: PTR_SIZE },
1373 1361
        else => {
1374 1362
            panic "getTypeLayout: the given type cannot be layed out";
1375 1363
        }
1376 1364
    }
1377 1365
}
1554 1542
            bits: 16,
1555 1543
            min: I16_MIN as i64,
1556 1544
            max: I16_MAX as i64,
1557 1545
            lim: (I16_MAX as u64) + 1,
1558 1546
        },
1559 -
        case Type::I32 => return IntegerRange::Signed {
1547 +
        case Type::I32,
1548 +
             Type::Int => return IntegerRange::Signed {
1560 1549
            bits: 32,
1561 1550
            min: I32_MIN as i64,
1562 1551
            max: I32_MAX as i64,
1563 1552
            lim: (I32_MAX as u64) + 1,
1564 1553
        },
1566 1555
            bits: 64,
1567 1556
            min: I64_MIN,
1568 1557
            max: I64_MAX,
1569 1558
            lim: (I64_MAX as u64) + 1,
1570 1559
        },
1571 -
        case Type::Int => return IntegerRange::Signed {
1572 -
            bits: 32,
1573 -
            min: I32_MIN as i64,
1574 -
            max: I32_MAX as i64,
1575 -
            lim: (I32_MAX as u64) + 1,
1576 -
        },
1577 1560
        case Type::U8 => return IntegerRange::Unsigned { bits: 8, max: U8_MAX as u64 },
1578 1561
        case Type::U16 => return IntegerRange::Unsigned { bits: 16, max: U16_MAX as u64 },
1579 1562
        case Type::U32 => return IntegerRange::Unsigned { bits: 32, max: parser::U32_MAX as u64 },
1580 1563
        case Type::U64 => return IntegerRange::Unsigned { bits: 64, max: parser::U64_MAX },
1581 1564
        else => return nil,
1723 1706
                        return Coercion::Identity;
1724 1707
                    }
1725 1708
                    return nil;
1726 1709
                }
1727 1710
            }
1728 -
            // For non-literal arrays, require exact element type match.
1729 -
            if lhs.item == rhs.item {
1730 -
                return Coercion::Identity;
1731 -
            }
1732 -
            return nil;
1733 1711
        }
1734 1712
        case Type::Pointer { target: lhsTarget, mutable: lhsMutable } => {
1735 1713
            let case Type::Pointer { target: rhsTarget, mutable: rhsMutable } = from
1736 1714
                else return nil;
1737 1715
2460 2438
/// Function bodies are deferred to the definition phase.
2461 2439
///
2462 2440
/// Nb. User-defined types are already handled by this point.
2463 2441
fn visitDecl(self: *mut Resolver, node: *ast::Node) throws (ResolveError) {
2464 2442
    match node.value {
2465 -
        case ast::NodeValue::FnDecl(_) => {
2466 -
            // Handled in previous pass (function signature binding).
2467 -
        }
2468 -
        case ast::NodeValue::ConstDecl(_) => {
2469 -
            // Handled in previous pass.
2443 +
        case ast::NodeValue::FnDecl(_),
2444 +
             ast::NodeValue::ConstDecl(_),
2445 +
             ast::NodeValue::Mod(_),
2446 +
             ast::NodeValue::Use(_) => {
2447 +
            // Handled in previous passes.
2470 2448
        }
2471 2449
        case ast::NodeValue::StaticDecl(_) => {
2472 2450
            try infer(self, node);
2473 2451
        }
2474 -
        case ast::NodeValue::Mod(_) => {
2475 -
            // Handled in previous pass.
2476 -
        }
2477 -
        case ast::NodeValue::Use(_) => {
2478 -
            // Handled in previous pass.
2479 -
        }
2480 2452
        case ast::NodeValue::InstanceDecl { traitName, targetType, methods } => {
2481 2453
            try resolveInstanceDecl(self, node, traitName, targetType, methods);
2482 2454
        }
2483 2455
        case ast::NodeValue::MethodDecl { name, receiverName, receiverType, sig, body, attrs } => {
2484 2456
            try resolveMethodDecl(self, node, name, receiverName, receiverType, sig, attrs);
2506 2478
            let case ast::NodeValue::Block(block) = submod.root.value
2507 2479
                else panic "visitDef: expected block for module root";
2508 2480
            try resolveModuleDefs(self, &block);
2509 2481
            exitModuleScope(self, submod);
2510 2482
        }
2511 -
        case ast::NodeValue::RecordDecl(_) => {
2512 -
            // Skip: already analyzed in declaration phase.
2513 -
        }
2514 -
        case ast::NodeValue::UnionDecl(_) => {
2515 -
            // Skip: already analyzed in declaration phase.
2516 -
        }
2517 -
        case ast::NodeValue::Use(_) => {
2518 -
            // Skip: already analyzed in declaration phase.
2519 -
        }
2520 -
        case ast::NodeValue::TraitDecl { .. } => {
2483 +
        case ast::NodeValue::RecordDecl(_),
2484 +
             ast::NodeValue::UnionDecl(_),
2485 +
             ast::NodeValue::Use(_),
2486 +
             ast::NodeValue::TraitDecl { .. } => {
2521 2487
            // Skip: already analyzed in declaration phase.
2522 2488
        }
2523 2489
        case ast::NodeValue::InstanceDecl { methods, .. } => {
2524 2490
            try resolveInstanceMethodBodies(self, methods);
2525 2491
        }
2873 2839
                    }
2874 2840
                }
2875 2841
            }
2876 2842
            return true;
2877 2843
        },
2878 -
        case ast::NodeValue::Ident(_) => {
2879 -
            // Identifiers referencing constants or functions are constant expressions.
2880 -
            if let sym = symbolFor(self, node) {
2881 -
                match sym.data {
2882 -
                    case SymbolData::Constant { .. } => return true,
2883 -
                    case SymbolData::Value { type, .. } => {
2884 -
                        if let case Type::Fn(_) = type {
2885 -
                            return true;
2886 -
                        }
2887 -
                    }
2888 -
                    else => {}
2889 -
                }
2890 -
            }
2891 -
            return false;
2892 -
        },
2893 -
        case ast::NodeValue::ScopeAccess(_) => {
2894 -
            // Scope accesses to union variants (eg. TokenKind::Fn) are constant.
2895 -
            // The symbol for this node indicates whether it's a variant.
2896 -
            // Function references are also compile-time constants.
2844 +
        case ast::NodeValue::Ident(_),
2845 +
             ast::NodeValue::ScopeAccess(_) => {
2846 +
            // Identifiers and scope accesses referencing constants, union
2847 +
            // variants, or function values are constant expressions.
2897 2848
            if let sym = symbolFor(self, node) {
2898 2849
                match sym.data {
2899 -
                    case SymbolData::Variant { .. } => return true,
2900 -
                    case SymbolData::Constant { .. } => return true,
2850 +
                    case SymbolData::Variant { .. },
2851 +
                         SymbolData::Constant { .. } => return true,
2901 2852
                    case SymbolData::Value { type, .. } => {
2902 2853
                        if let case Type::Fn(_) = type {
2903 2854
                            return true;
2904 2855
                        }
2905 2856
                    }
6024 5975
        }
6025 5976
    }
6026 5977
    return setNodeType(self, node, tryResultTy);
6027 5978
}
6028 5979
6029 -
6030 5980
/// Check that a `catch` body is assignable to the expected result type, but only
6031 5981
/// in expression context (`hint` is neither `Unknown` nor `Void`).
6032 5982
fn checkCatchBody(self: *mut Resolver, body: *ast::Node, resultTy: Type, hint: Type)
6033 5983
    throws (ResolveError)
6034 5984
{
lib/std/lang/resolver/printer.rad +51 -224
2 2
use std::io;
3 3
use std::lang::ast;
4 4
use std::lang::scanner;
5 5
use std::lang::module;
6 6
7 -
/// Single indentation unit used when rendering nested scopes.
8 -
const INDENT_STEP: *[u8] = "  ";
9 -
10 -
/// Emit indentation for a given nesting depth.
11 -
fn printIndent(depth: u32) {
12 -
    for i in 0..depth {
13 -
        io::print(INDENT_STEP);
14 -
    }
15 -
}
16 -
17 7
/// Print a span in `@offset:length` form.
18 8
fn printSpan(span: ast::Span) {
19 9
    io::print("@");
20 10
    io::printU32(span.offset);
21 11
    io::print(":");
22 12
    io::printU32(span.length);
23 13
}
24 14
15 +
/// Print a count mismatch message: `<prefix>: <verb> <expected>, got <actual>`.
16 +
fn printMismatch(prefix: *[u8], verb: *[u8], m: super::CountMismatch) {
17 +
    io::print(prefix);
18 +
    io::print(": ");
19 +
    io::print(verb);
20 +
    io::print(" ");
21 +
    io::printU32(m.expected);
22 +
    io::print(", got ");
23 +
    io::printU32(m.actual);
24 +
}
25 +
26 +
/// Print `<prefix>'<name>'`.
27 +
fn printQuoted(prefix: *[u8], name: *[u8]) {
28 +
    io::print(prefix);
29 +
    io::print(name);
30 +
    io::print("'");
31 +
}
32 +
33 +
/// Print `*` or `*mut ` depending on mutability.
34 +
fn printPtrPrefix(mutable: bool) {
35 +
    io::print("*");
36 +
    if mutable {
37 +
        io::print("mut ");
38 +
    }
39 +
}
40 +
25 41
/// Print a resolved type in a textual form.
26 42
fn printType(ty: super::Type) {
27 43
    printTypeBody(ty, false);
28 44
}
29 45
82 98
        }
83 99
        case super::Type::I64 => {
84 100
            io::print("i64");
85 101
        }
86 102
        case super::Type::Pointer { target, mutable } => {
87 -
            io::print("*");
88 -
            if mutable {
89 -
                io::print("mut ");
90 -
            }
103 +
            printPtrPrefix(mutable);
91 104
            printTypeBody(*target, brief);
92 105
        }
93 106
        case super::Type::Slice { item, mutable } => {
94 -
            io::print("*");
95 -
            if mutable {
96 -
                io::print("mut ");
97 -
            }
107 +
            printPtrPrefix(mutable);
98 108
            io::print("[");
99 109
            printTypeBody(*item, brief);
100 110
            io::print("]");
101 111
        }
102 112
        case super::Type::Array(array) => {
137 147
            } else {
138 148
                printNominalType(info);
139 149
            }
140 150
        }
141 151
        case super::Type::TraitObject { traitInfo, mutable } => {
142 -
            io::print("*");
143 -
            if mutable {
144 -
                io::print("mut ");
145 -
            }
152 +
            printPtrPrefix(mutable);
146 153
            io::print("opaque ");
147 154
            io::print(traitInfo.name);
148 155
        }
149 156
        case super::Type::Range { start, end } => {
150 157
            if let s = start {
226 233
            io::print("<union>");
227 234
        }
228 235
    }
229 236
}
230 237
231 -
/// Print a single symbol within a scope.
232 -
fn printSymbol(symbol: *super::Symbol, depth: u32) {
233 -
    printIndent(depth);
234 -
235 -
    io::print("`");
236 -
    io::print(symbol.name);
237 -
    io::print("` ");
238 -
239 -
    match symbol.data {
240 -
        case super::SymbolData::Variant { .. } => {
241 -
            io::print("variant");
242 -
        }
243 -
        case super::SymbolData::Value { .. } => {
244 -
            io::print("value");
245 -
        }
246 -
        case super::SymbolData::Constant { .. } => {
247 -
            io::print("constant");
248 -
        }
249 -
        case super::SymbolData::Type(_) => {
250 -
            io::print("type");
251 -
        }
252 -
        case super::SymbolData::Module { .. } => {
253 -
            io::print("module");
254 -
        }
255 -
        case super::SymbolData::Trait(_) => {
256 -
            io::print("trait");
257 -
        }
258 -
    }
259 -
260 -
    match symbol.data {
261 -
        case super::SymbolData::Value { mutable, type, .. } => {
262 -
            if mutable {
263 -
                io::print(" mut");
264 -
            }
265 -
            io::print(" ");
266 -
            io::print(symbol.name);
267 -
            io::print(" ");
268 -
            printSpan(symbol.node.span);
269 -
            io::print(" : ");
270 -
            printType(type);
271 -
            io::print("\n");
272 -
        }
273 -
        case super::SymbolData::Constant { type, .. } => {
274 -
            io::print(" ");
275 -
            io::print(symbol.name);
276 -
            io::print(" ");
277 -
            printSpan(symbol.node.span);
278 -
            io::print(" : ");
279 -
            printType(type);
280 -
            io::print("\n");
281 -
        }
282 -
        case super::SymbolData::Type(t) => {
283 -
            printType(super::Type::Nominal(t));
284 -
        }
285 -
        case super::SymbolData::Variant { .. } => {
286 -
            // TODO
287 -
        }
288 -
        case super::SymbolData::Module { .. } => {
289 -
            // TODO
290 -
        }
291 -
        case super::SymbolData::Trait(_) => {
292 -
            io::print(" ");
293 -
            io::print(symbol.name);
294 -
            io::print("\n");
295 -
        }
296 -
    }
297 -
}
298 -
299 238
/// Print a single diagnostic entry.
300 239
fn printError(err: *super::Error, res: *super::Resolver) {
301 240
    if let node = err.node {
302 241
        // Find the module containing this error.
303 242
        if let moduleEntry = module::get(res.moduleGraph, err.moduleId) {
340 279
            printType(mismatch.expected);
341 280
            io::print(", got ");
342 281
            printType(mismatch.actual);
343 282
        }
344 283
        case super::ErrorKind::UnresolvedSymbol(name) => {
345 -
            io::print("unresolved symbol '");
346 -
            io::print(name);
347 -
            io::print("'");
284 +
            printQuoted("unresolved symbol '", name);
348 285
        }
349 286
        case super::ErrorKind::DuplicateBinding(name) => {
350 -
            io::print("duplicate binding '");
351 -
            io::print(name);
352 -
            io::print("'");
353 -
        }
354 -
        case super::ErrorKind::FnArgCountMismatch(mismatch) => {
355 -
            io::print("function argument count mismatch: expected ");
356 -
            io::printU32(mismatch.expected);
357 -
            io::print(", got ");
358 -
            io::printU32(mismatch.actual);
359 -
        }
360 -
        case super::ErrorKind::FnThrowCountMismatch(mismatch) => {
361 -
            io::print("function throws count mismatch: expected ");
362 -
            io::printU32(mismatch.expected);
363 -
            io::print(", got ");
364 -
            io::printU32(mismatch.actual);
365 -
        }
366 -
        case super::ErrorKind::RecordFieldCountMismatch(mismatch) => {
367 -
            io::print("record field count mismatch: expected ");
368 -
            io::printU32(mismatch.expected);
369 -
            io::print(", got ");
370 -
            io::printU32(mismatch.actual);
371 -
        }
287 +
            printQuoted("duplicate binding '", name);
288 +
        }
289 +
        case super::ErrorKind::FnArgCountMismatch(m) =>
290 +
            printMismatch("function argument count mismatch", "expected", m),
291 +
        case super::ErrorKind::FnThrowCountMismatch(m) =>
292 +
            printMismatch("function throws count mismatch", "expected", m),
293 +
        case super::ErrorKind::RecordFieldCountMismatch(m) =>
294 +
            printMismatch("record field count mismatch", "expected", m),
372 295
        case super::ErrorKind::RecordFieldMissing(name) => {
373 -
            io::print("record field missing: '");
374 -
            io::print(name);
375 -
            io::print("'");
296 +
            printQuoted("record field missing: '", name);
376 297
        }
377 298
        case super::ErrorKind::RecordFieldUnknown(name) => {
378 -
            io::print("record field unknown: '");
379 -
            io::print(name);
380 -
            io::print("'");
299 +
            printQuoted("record field unknown: '", name);
381 300
        }
382 301
        case super::ErrorKind::InvalidAsCast(info) => {
383 302
            io::print("invalid cast: cannot cast ");
384 303
            printType(info.from);
385 304
            io::print(" to ");
386 305
            printType(info.to);
387 306
        }
388 307
        case super::ErrorKind::ExpectedIterable => {
389 308
            io::print("expected iterable type");
390 309
        }
391 -
        case super::ErrorKind::ElseBranchMustDiverge => {
392 -
            io::print("else branch must not fall through");
393 -
        }
394 310
        case super::ErrorKind::FnMissingReturn => {
395 311
            io::print("function must return a value on all paths");
396 312
        }
397 313
        case super::ErrorKind::UnionMatchNonExhaustive(name) => {
398 -
            io::print("union match non-exhaustive: missing case for variant '");
399 -
            io::print(name);
400 -
            io::print("'");
314 +
            printQuoted("union match non-exhaustive: missing case for variant '", name);
401 315
        }
402 316
        case super::ErrorKind::OptionalMatchMissingValue => {
403 317
            io::print("optional match non-exhaustive: missing value case");
404 318
        }
405 319
        case super::ErrorKind::OptionalMatchMissingNil => {
426 340
            io::print("cannot assign to immutable binding");
427 341
        }
428 342
        case super::ErrorKind::ConstExprRequired => {
429 343
            io::print("expected compile-time constant expression");
430 344
        }
431 -
        case super::ErrorKind::ScopeOverflow => {
432 -
            io::print("scope stack overflow");
433 -
        }
434 -
        case super::ErrorKind::NodeOverflow => {
435 -
            io::print("node table overflow");
436 -
        }
437 345
        case super::ErrorKind::SymbolOverflow => {
438 346
            io::print("symbol arena overflow");
439 347
        }
440 348
        case super::ErrorKind::NumericLiteralOverflow => {
441 349
            io::print("numeric literal overflow");
459 367
            io::print("expected record type");
460 368
        }
461 369
        case super::ErrorKind::ExpectedIndexable => {
462 370
            io::print("expected array or slice");
463 371
        }
464 -
        case super::ErrorKind::ExpectedBlock => {
465 -
            io::print("expected block");
466 -
        }
467 372
        case super::ErrorKind::InvalidAlignmentValue(val) => {
468 373
            io::print("invalid alignment value: ");
469 374
            io::printU32(val);
470 375
        }
471 376
        case super::ErrorKind::InvalidModulePath => {
476 381
        }
477 382
        case super::ErrorKind::InvalidScopeAccess => {
478 383
            io::print("invalid scope access");
479 384
        }
480 385
        case super::ErrorKind::ArrayFieldUnknown(name) => {
481 -
            io::print("array field unknown: '");
482 -
            io::print(name);
483 -
            io::print("'");
386 +
            printQuoted("array field unknown: '", name);
484 387
        }
485 388
        case super::ErrorKind::SliceFieldUnknown(name) => {
486 -
            io::print("slice field unknown: '");
487 -
            io::print(name);
488 -
            io::print("'");
389 +
            printQuoted("slice field unknown: '", name);
489 390
        }
490 391
        case super::ErrorKind::SliceRequiresAddress => {
491 392
            io::print("slicing requires taking an address with '&'");
492 393
        }
493 394
        case super::ErrorKind::SliceRangeOutOfBounds => {
565 466
        }
566 467
        case super::ErrorKind::DuplicateInstance => {
567 468
            io::print("duplicate instance declaration for the same trait and type");
568 469
        }
569 470
        case super::ErrorKind::MissingTraitMethod(name) => {
570 -
            io::print("missing trait method '");
571 -
            io::print(name);
572 -
            io::print("'");
471 +
            printQuoted("missing trait method '", name);
573 472
        }
574 473
        case super::ErrorKind::UnexpectedTraitName => {
575 474
            io::print("trait name cannot be used as a value");
576 475
        }
577 476
        case super::ErrorKind::TraitReceiverMismatch => {
578 477
            io::print("trait method receiver must be a pointer to the declaring trait");
579 478
        }
580 -
        case super::ErrorKind::FnParamOverflow(mismatch) => {
581 -
            io::print("too many function parameters: maximum ");
582 -
            io::printU32(mismatch.expected);
583 -
            io::print(", got ");
584 -
            io::printU32(mismatch.actual);
585 -
        }
586 -
        case super::ErrorKind::FnThrowOverflow(mismatch) => {
587 -
            io::print("too many function throws: maximum ");
588 -
            io::printU32(mismatch.expected);
589 -
            io::print(", got ");
590 -
            io::printU32(mismatch.actual);
591 -
        }
592 -
        case super::ErrorKind::TraitMethodOverflow(mismatch) => {
593 -
            io::print("too many trait methods: maximum ");
594 -
            io::printU32(mismatch.expected);
595 -
            io::print(", got ");
596 -
            io::printU32(mismatch.actual);
597 -
        }
479 +
        case super::ErrorKind::FnParamOverflow(m) =>
480 +
            printMismatch("too many function parameters", "maximum", m),
481 +
        case super::ErrorKind::FnThrowOverflow(m) =>
482 +
            printMismatch("too many function throws", "maximum", m),
483 +
        case super::ErrorKind::TraitMethodOverflow(m) =>
484 +
            printMismatch("too many trait methods", "maximum", m),
598 485
        case super::ErrorKind::MissingSupertraitInstance(name) => {
599 -
            io::print("missing instance for supertrait '");
600 -
            io::print(name);
601 -
            io::print("'");
486 +
            printQuoted("missing instance for supertrait '", name);
602 487
        }
603 488
        case super::ErrorKind::Internal => {
604 489
            io::print("internal compiler error");
605 490
        }
606 491
        case super::ErrorKind::RecordFieldOutOfOrder { .. } => {
613 498
            io::print("opaque types cannot be dereferenced");
614 499
        }
615 500
        case super::ErrorKind::OpaquePointerArithmetic => {
616 501
            io::print("opaque pointer arithmetic is not allowed");
617 502
        }
618 -
        case super::ErrorKind::CyclicTypeDependency(_) => {
619 -
            io::print("cyclic type dependency");
620 -
        }
621 503
        case super::ErrorKind::BuiltinArgCountMismatch { .. } => {
622 504
            io::print("built-in argument count mismatch");
623 505
        }
624 506
    }
625 507
    io::print("\n");
626 508
}
627 509
628 -
/// Recursively print a scope and its nested children.
629 -
fn printScopeTree(res: *super::Resolver, scope: *super::Scope, depth: u32) {
630 -
    printIndent(depth);
631 -
    io::print("Scope ");
632 -
633 -
    if let owner = scope.owner {
634 -
        printSpan(owner.span);
635 -
    } else {
636 -
        io::print("<root>");
637 -
    }
638 -
    io::print("\n");
639 -
    printScope(scope, depth);
640 -
641 -
    for entry in res.nodeData.entries {
642 -
        if let childScopePtr = entry.scope {
643 -
            if childScopePtr.parent != nil and childScopePtr.parent == scope {
644 -
                printScopeTree(res, childScopePtr, depth + 1);
645 -
            }
646 -
        }
647 -
    }
648 -
}
649 -
650 -
/// Entry point for printing the resolver scope tree.
651 -
pub fn printScopes(res: *super::Resolver, root: *ast::Node) {
652 -
    if let scope = super::scopeFor(res, root) {
653 -
        printScopeTree(res, scope, 0);
654 -
    }
655 -
    io::print("\n");
656 -
}
657 -
658 -
/// Print the given scope.
659 -
pub fn printScope(scope: *super::Scope, depth: u32) {
660 -
    for i in 0..scope.symbolsLen {
661 -
        let symbol = scope.symbols[i];
662 -
        printSymbol(symbol, depth);
663 -
        io::print("\n");
664 -
    }
665 -
}
666 -
667 -
/// Print the given scope, and all enclosing scopes.
668 -
pub fn printEnclosingScopes(res: *super::Resolver, scope: *super::Scope) {
669 -
    let mut curr = scope;
670 -
671 -
    loop {
672 -
        io::printLn("Scope");
673 -
        printScope(curr, 1);
674 -
675 -
        if let parent = curr.parent {
676 -
            curr = parent;
677 -
        } else {
678 -
            break;
679 -
        }
680 -
    }
681 -
}
682 -
683 510
/// Entry point for printing resolver diagnostics in vim quickfix format.
684 511
pub fn printDiagnostics(diag: *super::Diagnostics, res: *super::Resolver) {
685 512
    for i in 0..diag.errors.len {
686 513
        printError(&diag.errors[i], res);
687 514
    }
lib/std/lang/scanner.rad +0 -113
66 66
    GtGtEqual,    // >>=
67 67
68 68
    // Boolean operators.
69 69
    Not, And, Or,
70 70
71 -
    /// Eg. `input:`
72 -
    Label,
73 71
    /// Eg. `fnord`
74 72
    Ident,
75 73
    /// Eg. `@default`
76 74
    AtIdent,
77 75
    /// The `log` keyword.
107 105
    // Type-related tokens.
108 106
    I8, I16, I32, I64, U8, U16, U32, U64,
109 107
    Void, Opaque, Fn, Bool, Union, Record, As
110 108
}
111 109
112 -
/// Convert a token kind to its string representation.
113 -
pub fn tokenKindToString(kind: TokenKind) -> *[u8] {
114 -
    match kind {
115 -
        case TokenKind::Eof => return "Eof",
116 -
        case TokenKind::Invalid => return "Invalid",
117 -
        case TokenKind::LParen => return "LParen",
118 -
        case TokenKind::RParen => return "RParen",
119 -
        case TokenKind::LBrace => return "LBrace",
120 -
        case TokenKind::RBrace => return "RBrace",
121 -
        case TokenKind::LBracket => return "LBracket",
122 -
        case TokenKind::RBracket => return "RBracket",
123 -
        case TokenKind::Comma => return "Comma",
124 -
        case TokenKind::Dot => return "Dot",
125 -
        case TokenKind::DotDot => return "DotDot",
126 -
        case TokenKind::Minus => return "Minus",
127 -
        case TokenKind::Plus => return "Plus",
128 -
        case TokenKind::Colon => return "Colon",
129 -
        case TokenKind::ColonColon => return "ColonColon",
130 -
        case TokenKind::Semicolon => return "Semicolon",
131 -
        case TokenKind::Slash => return "Slash",
132 -
        case TokenKind::Star => return "Star",
133 -
        case TokenKind::Percent => return "Percent",
134 -
        case TokenKind::Amp => return "Amp",
135 -
        case TokenKind::Pipe => return "Pipe",
136 -
        case TokenKind::Caret => return "Caret",
137 -
        case TokenKind::Tilde => return "Tilde",
138 -
        case TokenKind::Underscore => return "Underscore",
139 -
        case TokenKind::AtIdent => return "AtIdent",
140 -
        case TokenKind::Question => return "Question",
141 -
        case TokenKind::Bang => return "Bang",
142 -
        case TokenKind::BangEqual => return "BangEqual",
143 -
        case TokenKind::Equal => return "Equal",
144 -
        case TokenKind::EqualEqual => return "EqualEqual",
145 -
        case TokenKind::Gt => return "Gt",
146 -
        case TokenKind::GtEqual => return "GtEqual",
147 -
        case TokenKind::Lt => return "Lt",
148 -
        case TokenKind::LtEqual => return "LtEqual",
149 -
        case TokenKind::LtLt => return "LtLt",
150 -
        case TokenKind::GtGt => return "GtGt",
151 -
        case TokenKind::Arrow => return "Arrow",
152 -
        case TokenKind::FatArrow => return "FatArrow",
153 -
        case TokenKind::PlusEqual => return "PlusEqual",
154 -
        case TokenKind::MinusEqual => return "MinusEqual",
155 -
        case TokenKind::StarEqual => return "StarEqual",
156 -
        case TokenKind::SlashEqual => return "SlashEqual",
157 -
        case TokenKind::PercentEqual => return "PercentEqual",
158 -
        case TokenKind::AmpEqual => return "AmpEqual",
159 -
        case TokenKind::PipeEqual => return "PipeEqual",
160 -
        case TokenKind::CaretEqual => return "CaretEqual",
161 -
        case TokenKind::LtLtEqual => return "LtLtEqual",
162 -
        case TokenKind::GtGtEqual => return "GtGtEqual",
163 -
        case TokenKind::Not => return "Not",
164 -
        case TokenKind::And => return "And",
165 -
        case TokenKind::Or => return "Or",
166 -
        case TokenKind::Label => return "Label",
167 -
        case TokenKind::Ident => return "Ident",
168 -
        case TokenKind::Log => return "Log",
169 -
        case TokenKind::String => return "String",
170 -
        case TokenKind::Char => return "Char",
171 -
        case TokenKind::Number => return "Number",
172 -
        case TokenKind::True => return "True",
173 -
        case TokenKind::False => return "False",
174 -
        case TokenKind::Nil => return "Nil",
175 -
        case TokenKind::Undefined => return "Undefined",
176 -
        case TokenKind::If => return "If",
177 -
        case TokenKind::Else => return "Else",
178 -
        case TokenKind::Return => return "Return",
179 -
        case TokenKind::Break => return "Break",
180 -
        case TokenKind::Continue => return "Continue",
181 -
        case TokenKind::While => return "While",
182 -
        case TokenKind::For => return "For",
183 -
        case TokenKind::In => return "In",
184 -
        case TokenKind::Loop => return "Loop",
185 -
        case TokenKind::Match => return "Match",
186 -
        case TokenKind::Case => return "Case",
187 -
        case TokenKind::Try => return "Try",
188 -
        case TokenKind::Catch => return "Catch",
189 -
        case TokenKind::Throw => return "Throw",
190 -
        case TokenKind::Throws => return "Throws",
191 -
        case TokenKind::Panic => return "Panic",
192 -
        case TokenKind::Assert => return "Assert",
193 -
        case TokenKind::Let => return "Let",
194 -
        case TokenKind::Mut => return "Mut",
195 -
        case TokenKind::Const => return "Const",
196 -
        case TokenKind::Align => return "Align",
197 -
        case TokenKind::Mod => return "Mod",
198 -
        case TokenKind::Use => return "Use",
199 -
        case TokenKind::Super => return "Super",
200 -
        case TokenKind::Pub => return "Pub",
201 -
        case TokenKind::Extern => return "Extern",
202 -
        case TokenKind::Static => return "Static",
203 -
        case TokenKind::Trait => return "Trait",
204 -
        case TokenKind::Instance => return "Instance",
205 -
        case TokenKind::I8 => return "I8",
206 -
        case TokenKind::I16 => return "I16",
207 -
        case TokenKind::I32 => return "I32",
208 -
        case TokenKind::I64 => return "I64",
209 -
        case TokenKind::U8 => return "U8",
210 -
        case TokenKind::U16 => return "U16",
211 -
        case TokenKind::U32 => return "U32",
212 -
        case TokenKind::U64 => return "U64",
213 -
        case TokenKind::Void => return "Void",
214 -
        case TokenKind::Opaque => return "Opaque",
215 -
        case TokenKind::Fn => return "Fn",
216 -
        case TokenKind::Bool => return "Bool",
217 -
        case TokenKind::Union => return "Union",
218 -
        case TokenKind::Record => return "Record",
219 -
        case TokenKind::As => return "As",
220 -
    }
221 -
}
222 -
223 110
/// A reserved keyword.
224 111
record Keyword {
225 112
    /// Keyword string.
226 113
    name: *[u8],
227 114
    /// Corresponding token.
lib/std/lang/sexpr.rad +0 -27
51 51
        buf[i] = item;
52 52
    }
53 53
    return buf;
54 54
}
55 55
56 -
/// Allocate and copy prefix items followed by suffix items.
57 -
pub fn concatItems(a: *mut alloc::Arena, prefix: *[Expr], suffix: *[Expr]) -> *[Expr] {
58 -
    let total = prefix.len + suffix.len;
59 -
    if total == 0 {
60 -
        return &[];
61 -
    }
62 -
    let buf = try! allocExprs(a, total);
63 -
    for item, i in prefix {
64 -
        buf[i] = item;
65 -
    }
66 -
    for item, i in suffix {
67 -
        buf[prefix.len + i] = item;
68 -
    }
69 -
    return buf;
70 -
}
71 -
72 56
/// Shorthand for creating a symbol.
73 57
pub fn sym(s: *[u8]) -> Expr {
74 58
    return Expr::Sym(s);
75 59
}
76 60
220 204
pub fn indent(depth: u32) {
221 205
    let mut out = Output::Stdout;
222 206
    indentTo(&mut out, depth);
223 207
}
224 208
225 -
/// Print a quoted string with escape sequences to stdout.
226 -
pub fn printString(s: *[u8]) {
227 -
    let mut out = Output::Stdout;
228 -
    printStringTo(&mut out, s);
229 -
}
230 -
231 -
/// Print a character literal with escape sequences to stdout.
232 -
pub fn printChar(c: u8) {
233 -
    let mut out = Output::Stdout;
234 -
    printCharTo(&mut out, c);
235 -
}
lib/std/mem.rad +0 -2
1 1
/// Memory error.
2 2
union MemoryError {
3 3
    /// Buffer is too small.
4 4
    BufferTooSmall,
5 -
    /// An offset is out of bounds of the given buffer.
6 -
    OutOfBounds,
7 5
}
8 6
9 7
/// Copy bytes between two slices. Returns the number of bytes copied.
10 8
pub fn copy(into: *mut [u8], from: *[u8]) -> u32 throws (MemoryError) {
11 9
    if into.len < from.len {
lib/std/sys/unix.rad +0 -1
80 80
/// Returns `0` on success, or a negative value on error.
81 81
pub fn close(fd: i64) -> i64 {
82 82
    return intrinsics::ecall(57, fd, 0, 0, 0);
83 83
}
84 84
85 -
86 85
/// Reads the entire contents of a file at the given path into the provided buffer.
87 86
/// Returns a slice containing the data read, or `nil` on error.
88 87
pub fn readFile(path: *[u8], buf: *mut [u8]) -> ?*[u8] {
89 88
    let fd = open(path, O_RDONLY);
90 89
    if fd < 0 {
test/tests/array.math.rad +0 -1
19 19
        }
20 20
    }
21 21
    return sum;
22 22
}
23 23
24 -
25 24
fn transpose(matrix: [[i32; 3]; 2]) -> [[i32; 2]; 3] {
26 25
    let mut result: [[i32; 2]; 3] = [[0, 0], [0, 0], [0, 0]];
27 26
    let mut i: u32 = 0;
28 27
29 28
    while (i < matrix.len) {
test/tests/as.precedence.rad +0 -1
1 1
//! returns: 0
2 2
3 -
4 3
@default fn main() -> i32 {
5 4
    let p1: u8 = 1;
6 5
    let p2: u8 = 2;
7 6
8 7
    // Simple cast.
test/tests/prog.dijkstra.rad +0 -2
1 1
//! returns: 0
2 2
//! Dijkstra's shortest path algorithm.
3 3
//! Find shortest paths in a weighted directed graph using an adjacency matrix
4 4
//! and a min-heap priority queue. Reconstruct paths and verify distances.
5 5
6 -
7 6
const MAX_NODES: u32 = 16;
8 7
const INF: u32 = 0xFFFFFFFF;
9 8
10 9
record HeapEntry {
11 10
    dist: u32,
178 177
    addEdge(g, 2, 3, 10);
179 178
    addEdge(g, 3, 4, 10);
180 179
181 180
    dijkstra(g, 0);
182 181
183 -
184 182
    let expected: [u32; 5] = [0, 10, 20, 30, 40];
185 183
    for exp, i in expected {
186 184
        if g.dist[i] != exp {
187 185
            return i as i32 + 1;
188 186
        }
test/tests/prog.huffman.rad +0 -3
1 1
//! returns: 0
2 2
//! Huffman encoding.
3 3
//! Build a Huffman tree from character frequencies, generate prefix codes,
4 4
//! encode a message, decode it, and verify round-trip correctness.
5 5
6 -
7 6
const MAX_SYMBOLS: u32 = 32;
8 7
const MAX_NODES: u32 = 63;
9 8
const MAX_BITS: u32 = 512;
10 9
11 10
/// A node in the Huffman tree.
124 123
125 124
fn buildTree(s: *mut HuffState, freqs: *[u32]) -> u32 {
126 125
    s.nodeCount = 0;
127 126
    s.heapSize = 0;
128 127
129 -
130 128
    for freq, sym in freqs {
131 129
        if freq > 0 {
132 130
            let idx: u32 = newLeaf(s, freq, sym);
133 131
            heapPush(s, idx);
134 132
        }
183 181
    while i < MAX_BITS {
184 182
        s.bitstream[i] = 0;
185 183
        i += 1;
186 184
    }
187 185
188 -
189 186
    for sym in msg {
190 187
        let bits: u32 = s.codeBits[sym];
191 188
        let len: u32 = s.codeLen[sym];
192 189
        let mut b: u32 = 0;
193 190
        while b < len {
test/tests/prog.linkedlist.rad +0 -1
1 1
//! returns: 0
2 2
//! Linked list via array pool.
3 3
//! Implement a singly-linked list using an array of node records
4 4
//! (pool allocator pattern). Operations: push, pop, find, reverse, length.
5 5
6 -
7 6
/// A node in the linked list.
8 7
record Node {
9 8
    value: i32,
10 9
    next: ?u32,
11 10
}