rv64: Propagate code generation errors directly

5573da2c8e071c59ab4bf8da6e65249b0bd4747c4b758dc33223fdb4ca7f76a4
Make RV64 program construction, instruction selection, relocation, data layout,
and output-buffer failures recoverable. Propagate these failures through the
streaming lowerer to the compiler driver, where they are reported as code
generation errors. Preserve arena checkpoints and output placeholders on failed
operations, and add focused backend failure regressions.
Alexis Sellier committed ago 1 parent 9b9efcd0
compiler/radiance.rad +16 -4
563 563
        throw error(&["module has no AST"]);
564 564
    };
565 565
    pkgLog(pkg, &["lowering", "(", entry.filePath, ")", ".."]);
566 566
567 567
    try lower::lowerModule(low, modId, modAst, isRoot) catch err {
568 +
        if err == lower::LowerError::OutputFailed {
569 +
            throw Error::Other;
570 +
        }
568 571
        io::printError("radiance: ");
569 572
        io::printError("internal error during lowering: ");
570 573
        lower::printError(err);
571 574
        io::printError("\n");
572 575
942 945
        throw error(&["fatal:", "assembly rodata too large"]);
943 946
    }
944 947
    try! mem::copy(&mut ASM_RO_DATA_BUF[*asmDataLen..], program.data);
945 948
    set *asmDataLen += program.data.len;
946 949
947 -
    rv64::addAssembly(generator, program);
950 +
    try rv64::addAssembly(generator, program) catch error {
951 +
        codegen::printError(error);
952 +
        throw Error::Other;
953 +
    };
948 954
}
949 955
950 956
/// Assemble all inputs collected in the package inputs.
951 957
unsafe fn assembleAsmInputs(
952 958
    ctx: &CompileContext,
990 996
        case CodegenEntryMode::DefaultEntry => {
991 997
            set entryPatch = rv64::EntryPatch::Reserved(nil);
992 998
        }
993 999
        else => {}
994 1000
    }
995 -
    let mut generator = rv64::beginProgram(
1001 +
    let mut generator = try rv64::beginProgram(
996 1002
        rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug },
997 1003
        &mut res.arena
998 -
    );
1004 +
    ) catch error {
1005 +
        codegen::printError(error);
1006 +
        throw Error::Other;
1007 +
    };
999 1008
    let mut codegenCtx = codegen::Context {
1000 1009
        generator: &mut generator,
1001 1010
        fnArena: (&mut *fnArena) as *unsafe mut alloc::Arena,
1002 1011
    };
1003 1012
    let mut low = lower::lowerer(
1023 1032
        else => {}
1024 1033
    }
1025 1034
    if let path = codegenOptions.logPath {
1026 1035
        pkgLog(entryPkg, &["generating code", "(", path, ")", ".."]);
1027 1036
    }
1028 -
    return rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]);
1037 +
    return try rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]) catch error {
1038 +
        codegen::printError(error);
1039 +
        throw Error::Other;
1040 +
    };
1029 1041
}
1030 1042
1031 1043
/// Lower, optionally dump, and optionally generate binary output.
1032 1044
unsafe fn compile(
1033 1045
    ctx: *unsafe mut CompileContext,
compiler/radiance/codegen.rad +31 -4
1 1
//! Streaming code generation with caller-managed context storage.
2 2
3 3
use std::arch::rv64;
4 +
use std::io;
4 5
use std::lang::alloc;
5 6
use std::lang::il;
6 7
use std::lang::lower;
8 +
use std::lang::gen::data;
7 9
8 10
/// State carried by the streaming lowerer/codegen callback.
9 11
export record Context: Copy {
10 12
    /// Generator receiving functions. It must remain valid for every callback.
11 13
    generator: *unsafe mut rv64::Generator,
13 15
    fnArena: *unsafe mut alloc::Arena,
14 16
}
15 17
16 18
/// Emit one lowered function to machine code and reclaim its IL arena.
17 19
/// The context must point to a valid `Context` with live generator and arena storage.
18 -
export unsafe fn emit(ctxPtr: *unsafe mut opaque, func: *unsafe il::Fn, role: lower::FnRole) {
20 +
export unsafe fn emit(ctxPtr: *unsafe mut opaque, func: *unsafe il::Fn, role: lower::FnRole) throws (lower::LowerError) {
19 21
    let ctx = ctxPtr as *unsafe mut Context;
22 +
    try generateFunction(ctx, func, role) catch error {
23 +
        alloc::reset(ctx.fnArena);
24 +
        printError(error);
25 +
        throw lower::LowerError::OutputFailed;
26 +
    };
27 +
    alloc::reset(ctx.fnArena);
28 +
}
20 29
30 +
/// Generate a function and register its entry symbol when required.
31 +
unsafe fn generateFunction(ctx: *unsafe mut Context, func: *unsafe il::Fn, role: lower::FnRole) throws (rv64::Error) {
21 32
    match role {
22 33
        case lower::FnRole::Default => {
23 -
            rv64::recordFunctionAlias(ctx.generator, super::DEFAULT_ENTRY_SYMBOL);
34 +
            try rv64::recordFunctionAlias(ctx.generator, super::DEFAULT_ENTRY_SYMBOL);
24 35
            match ctx.generator.entryPatch {
25 36
                case rv64::EntryPatch::Reserved(_) => {
26 37
                    set ctx.generator.entryPatch = rv64::EntryPatch::Reserved(func.name);
27 38
                }
28 39
                // Startup assembly calls the default function through its
32 43
        }
33 44
        else => {}
34 45
    }
35 46
    let generator = ctx.generator;
36 47
    let arena = ctx.fnArena;
37 -
    rv64::generateFunction(generator, func, arena);
38 -
    alloc::reset(ctx.fnArena);
48 +
    try rv64::generateFunction(generator, func, arena);
49 +
}
50 +
51 +
/// Report a code generation failure.
52 +
export fn printError(error: rv64::Error) {
53 +
    match error {
54 +
        case rv64::Error::Allocation => io::printError("radiance: code generation allocation failed\n"),
55 +
        case rv64::Error::Capacity => io::printError("radiance: code generation capacity exceeded\n"),
56 +
        case rv64::Error::Symbol => io::printError("radiance: invalid code generation symbol\n"),
57 +
        case rv64::Error::Data(error) => {
58 +
            match error {
59 +
                case data::Error::Symbol => io::printError("radiance: missing data symbol\n"),
60 +
                case data::Error::Capacity => io::printError("radiance: data section capacity exceeded\n"),
61 +
                case data::Error::Overflow => io::printError("radiance: data section size or address overflow\n"),
62 +
            }
63 +
        },
64 +
        case rv64::Error::Relocation => io::printError("radiance: code generation relocation out of range\n"),
65 +
    }
39 66
}
lib/std/arch/rv64.rad +72 -32
20 20
@test mod tests;
21 21
22 22
use std::mem;
23 23
use std::collections::dict;
24 24
use std::lang::il;
25 +
use std::lang::parser;
25 26
use std::lang::alloc;
26 27
use std::lang::gen;
27 28
use std::lang::gen::labels;
28 29
use std::lang::gen::regalloc;
29 30
use std::lang::gen::data;
30 31
use std::lang::gen::types;
31 32
33 +
/// Recoverable code-generation failure.
34 +
export union Error: Copy {
35 +
    /// The code-generation or function arena is full.
36 +
    Allocation,
37 +
    /// A fixed output or metadata table is full.
38 +
    Capacity,
39 +
    /// A required symbol is missing or invalid.
40 +
    Symbol,
41 +
    /// A branch or address exceeds its instruction range.
42 +
    Relocation,
43 +
    /// Data section layout or emission failed.
44 +
    Data(data::Error),
45 +
}
46 +
32 47
////////////////
33 48
// Registers  //
34 49
////////////////
35 50
36 51
export constant ZERO: gen::Reg = gen::Reg(0);   /// Hard-wired zero.
212 227
213 228
/// Begin RV64 code generation for a program's global state.
214 229
export unsafe fn beginProgram(
215 230
    options: ProgramOptions,
216 231
    arena: &mut alloc::Arena
217 -
) -> Generator {
218 -
    let mut e = try! emit::emitter(arena, options.debug);
232 +
) -> Generator throws (Error) {
233 +
    let checkpoint = alloc::save(arena);
234 +
    let mut e = try emit::emitter(arena, options.debug) catch {
235 +
        alloc::restore(arena, checkpoint);
236 +
        throw Error::Allocation;
237 +
    };
219 238
220 239
    // Emit placeholder entry jump when requested.
221 240
    // We'll patch this at the end once we know where the function is.
222 241
    match options.entryPatch {
223 242
        case EntryPatch::Reserved(_) => {
224 -
            emit::emit(&mut e, encode::nop()); // Placeholder for two-instruction jump.
225 -
            emit::emit(&mut e, encode::nop()); //
243 +
            try emit::emit(&mut e, encode::nop()); // Placeholder for two-instruction jump.
244 +
            try emit::emit(&mut e, encode::nop()); //
226 245
        }
227 246
        else => {}
228 247
    }
229 248
230 249
    return Generator {
236 255
/// Generate code for one IL function.
237 256
export unsafe fn generateFunction(
238 257
    generator: &mut Generator,
239 258
    func: *unsafe il::Fn,
240 259
    arena: &mut alloc::Arena
241 -
) {
260 +
) throws (Error) {
242 261
    if func.isExtern {
243 262
        return;
244 263
    }
245 264
    let checkpoint = alloc::save(arena);
246 265
    let config = targetConfig();
247 -
    let ralloc = try! regalloc::allocate(func, &config, arena);
266 +
    let ralloc = try regalloc::allocate(func, &config, arena) catch {
267 +
        alloc::restore(arena, checkpoint);
268 +
        throw Error::Allocation;
269 +
    };
248 270
249 -
    isel::selectFn(&mut generator.e, &ralloc, func);
271 +
    try isel::selectFn(&mut generator.e, &ralloc, func) catch error {
272 +
        alloc::restore(arena, checkpoint);
273 +
        throw error;
274 +
    };
250 275
251 276
    // Reclaim unused memory after instruction selection.
252 277
    alloc::restore(arena, checkpoint);
253 278
}
254 279
255 280
/// Record an alternate name for the next function emitted.
256 -
export fn recordFunctionAlias(generator: &mut Generator, name: *[u8]) {
257 -
    let codeLen = generator.e.codeLen;
258 -
    emit::recordFuncOffsetAt(&mut generator.e, name, codeLen);
281 +
export fn recordFunctionAlias(generator: &mut Generator, name: *[u8]) throws (Error) {
282 +
    let codeLen = generator.e.code.len;
283 +
    try emit::recordFuncOffsetAt(&mut generator.e, name, codeLen);
259 284
}
260 285
261 286
/// Add the text section of an assembled program to the generator.
262 287
///
263 288
/// This function snapshots the generator's current code length as the base
268 293
/// separate assembly inputs may reuse the same local names.
269 294
///
270 295
/// Non-text symbols are ignored here because assembled data is not appended to
271 296
/// the generator's text stream. The driver merges assembled data into the RO data
272 297
/// prefix separately and passes that data to [`finishProgram`].
273 -
export fn addAssembly(generator: &mut Generator, program: asm::Program) {
274 -
    let baseIndex = generator.e.codeLen;
298 +
export fn addAssembly(generator: &mut Generator, program: asm::Program) throws (Error) {
299 +
    let baseIndex = generator.e.code.len;
275 300
276 301
    for symbol in program.symbols {
277 302
        if symbol.section == asm::Section::Text {
278 303
            let index = baseIndex + ((symbol.offset as u32) / INSTR_SIZE as u32);
279 -
            emit::recordFuncAt(&mut generator.e, symbol.name, index);
304 +
            try emit::recordFuncAt(&mut generator.e, symbol.name, index);
280 305
            if symbol.isExported {
281 -
                emit::recordFuncOffsetAt(&mut generator.e, symbol.name, index);
306 +
                try emit::recordFuncOffsetAt(&mut generator.e, symbol.name, index);
282 307
            }
283 308
        }
284 309
    }
285 310
    for fixup in program.externalFixups {
286 311
        match fixup.info {
287 312
            case asm::FixupInfo::Jal { rd, index } => {
288 -
                emit::recordJumpAt(&mut generator.e, fixup.symbol, rd, baseIndex + index);
313 +
                try emit::recordJumpAt(&mut generator.e, fixup.symbol, rd, baseIndex + index);
289 314
            }
290 315
            case asm::FixupInfo::Addr { rd, index } => {
291 -
                emit::recordAddrLoadAt(&mut generator.e, fixup.symbol, rd, baseIndex + index);
316 +
                try emit::recordAddrLoadAt(&mut generator.e, fixup.symbol, rd, baseIndex + index);
292 317
            }
293 318
            else => panic "addAssembly: invalid external fixup",
294 319
        }
295 320
    }
296 321
    for word in program.text {
297 -
        emit::emit(&mut generator.e, word);
322 +
        try emit::emit(&mut generator.e, word);
298 323
    }
299 324
}
300 325
301 326
/// Finish RV64 code generation and return the emitted program.
302 327
export unsafe fn finishProgram(
304 329
    globalData: &[il::Data],
305 330
    storage: Storage,
306 331
    roDataPrefix: *[u8],
307 332
    roDataBuf: &mut [u8],
308 333
    rwDataBuf: &mut [u8]
309 -
) -> Program {
334 +
) -> Program throws (Error) {
310 335
    // Build data map after function lowering. Function-local literals can add
311 336
    // global data while functions are lowered, so final layout belongs here.
312 337
    let case Storage { dataSyms: symbolBuf, dataSymEntries } = storage
313 338
        else panic "expected code generation storage";
314 339
    let mut dataSymCount: u32 = 0;
315 -
    let roLayoutSize = data::layoutSectionAtOffset(
340 +
    let roLayoutSize = try data::layoutSectionAtOffset(
316 341
        globalData, symbolBuf, &mut dataSymCount, RO_DATA_BASE, roDataPrefix.len, true
317 -
    );
318 -
    data::layoutSection(globalData, symbolBuf, &mut dataSymCount, RW_DATA_BASE, false);
342 +
    ) catch error {
343 +
        throw Error::Data(error);
344 +
    };
345 +
    try data::layoutSection(globalData, symbolBuf, &mut dataSymCount, RW_DATA_BASE, false) catch error {
346 +
        throw Error::Data(error);
347 +
    };
319 348
320 349
    let dataSyms = &symbolBuf[..dataSymCount];
321 -
    let dataSymMap = data::buildMap(dataSyms, dataSymEntries);
350 +
    let dataSymMap = try data::buildMap(dataSyms, dataSymEntries) catch error {
351 +
        throw Error::Data(error);
352 +
    };
353 +
    if roLayoutSize > parser::U32_MAX - RO_DATA_BASE - (DWORD_SIZE as u32 - 1) {
354 +
        throw Error::Data(data::Error::Overflow);
355 +
    }
322 356
    let codeBase = mem::alignUp(RO_DATA_BASE + roLayoutSize, DWORD_SIZE as u32);
323 357
324 358
    match generator.entryPatch {
325 359
        case EntryPatch::Reserved(targetName) => {
326 360
            let target = targetName else {
327 -
                panic "finishProgram: entry jump reserved without default function";
361 +
                throw Error::Symbol;
328 362
            };
329 -
            let offset = emit::branchOffsetToFunc(&generator.e, 0, target);
363 +
            let offset = try emit::branchOffsetToFunc(&mut generator.e, 0, target);
330 364
            let s = emit::splitImm(offset);
331 365
332 366
            emit::patch(&mut generator.e, 0, encode::auipc(SCRATCH1, s.hi));
333 367
            emit::patch(&mut generator.e, 1, encode::jalr(ZERO, SCRATCH1, s.lo));
334 368
        }
335 369
        else => {}
336 370
    }
337 371
    // Patch function calls and address loads now that all functions are emitted.
338 -
    emit::patchJumps(&mut generator.e);
339 -
    emit::patchCalls(&mut generator.e);
340 -
    emit::patchAddrLoads(&mut generator.e, &dataSymMap);
372 +
    try emit::patchJumps(&mut generator.e);
373 +
    try emit::patchCalls(&mut generator.e);
374 +
    try emit::patchAddrLoads(&mut generator.e, &dataSymMap);
341 375
342 376
    // Emit data sections.
343 -
    assert roDataPrefix.len <= roDataBuf.len, "finishProgram: rodata prefix buffer overflow";
377 +
    if roDataPrefix.len > roDataBuf.len {
378 +
        throw Error::Capacity;
379 +
    }
344 380
    try! mem::copy(roDataBuf, roDataPrefix);
345 381
346 -
    let roDataSize = data::emitSectionAtOffset(
382 +
    let roDataSize = try data::emitSectionAtOffset(
347 383
        globalData, &dataSymMap, &generator.e.labels, codeBase, roDataBuf, true, roDataPrefix.len
348 -
    );
349 -
    let rwDataSize = data::emitSection(
384 +
    ) catch error {
385 +
        throw Error::Data(error);
386 +
    };
387 +
    let rwDataSize = try data::emitSection(
350 388
        globalData, &dataSymMap, &generator.e.labels, codeBase, rwDataBuf, false
351 -
    );
389 +
    ) catch error {
390 +
        throw Error::Data(error);
391 +
    };
352 392
    return Program {
353 393
        code: emit::getCode(&generator.e),
354 394
        funcs: &generator.e.funcs[..],
355 395
        roDataSize,
356 396
        rwDataSize,
lib/std/arch/rv64/asm.rad +2 -0
84 84
    externalFixups: *[Fixup],
85 85
}
86 86
87 87
/// Errors reported while assembling source text.
88 88
export union Error: Copy {
89 +
    /// An assembler metadata table or symbol buffer is full.
90 +
    Capacity,
89 91
    /// Invalid syntax or operand form at a source offset.
90 92
    Invalid { offset: u32, message: *[u8] },
91 93
    /// The source emitted more text words than the caller-provided buffer holds.
92 94
    TextOverflow,
93 95
    /// The source emitted more data bytes than the caller-provided buffer holds.
lib/std/arch/rv64/asm/emit.rad +8 -4
7 7
use std::collections::dict;
8 8
use std::lang::alloc;
9 9
use std::lang::gen;
10 10
11 11
/// Define a symbol at the current text or data offset.
12 -
export unsafe fn defineSymbol(a: &mut super::Assembler, name: *[u8]) {
12 +
export unsafe fn defineSymbol(a: &mut super::Assembler, name: *[u8]) throws (super::Error) {
13 13
    let idx = a.symbols.len;
14 14
    let offset: i32 = a.data.len as i32
15 15
        if a.section == super::Section::Data
16 16
        else a.text.len as i32 * rv64::INSTR_SIZE;
17 17
18 -
    try! a.symbols.append(super::Symbol {
18 +
    try a.symbols.append(super::Symbol {
19 19
        name,
20 20
        section: a.section,
21 21
        offset,
22 22
        isExported: dict::get(&a.exportMap, name) <> nil,
23 -
    }, alloc::arenaAllocator(a.arena));
24 -
    dict::insert(&mut a.symbolMap, name, idx as i32);
23 +
    }, alloc::arenaAllocator(a.arena)) catch {
24 +
        throw super::Error::Capacity;
25 +
    };
26 +
    try dict::insert(&mut a.symbolMap, name, idx as i32) catch {
27 +
        throw super::Error::Capacity;
28 +
    };
25 29
}
26 30
27 31
/// Append one encoded instruction word to the text section.
28 32
export unsafe fn emitText(a: &mut super::Assembler, word: u32) throws (super::Error) {
29 33
    try a.text.append(word, alloc::arenaAllocator(a.arena)) catch {
lib/std/arch/rv64/asm/parser.rad +7 -3
100 100
/// Define a label at the current text or data offset.
101 101
unsafe fn defineSymbol(a: &mut super::Assembler, name: *[u8], tok: scanner::Token) throws (super::Error) {
102 102
    if dict::get(&a.symbolMap, name) <> nil {
103 103
        throw failOnToken(tok, "duplicate label");
104 104
    }
105 -
    emit::defineSymbol(a, name);
105 +
    try emit::defineSymbol(a, name);
106 106
}
107 107
108 108
/// Emit a parsed integer data value after applying source-level range checks.
109 109
unsafe fn emitDataValue(a: &mut super::Assembler, value: i64, width: super::DataWidth) throws (super::Error) {
110 110
    match width {
540 540
/// Parse a `.constant` directive.
541 541
unsafe fn parseConstantDirective(a: &mut super::Assembler) throws (super::Error) {
542 542
    let name = try parseSymbolName(a);
543 543
    let value = try expectI32Value(a, try parseExpr(a), "constant out of range");
544 544
545 -
    dict::insert(&mut a.constMap, name, value);
545 +
    try dict::insert(&mut a.constMap, name, value) catch {
546 +
        throw super::Error::Capacity;
547 +
    };
546 548
}
547 549
548 550
/// Parse a `.export` directive.
549 551
unsafe fn parseExportDirective(a: &mut super::Assembler) throws (super::Error) {
550 552
    let name = try parseLabelName(a);
551 -
    dict::insert(&mut a.exportMap, name, 1);
553 +
    try dict::insert(&mut a.exportMap, name, 1) catch {
554 +
        throw super::Error::Capacity;
555 +
    };
552 556
    if let idx = dict::get(&a.symbolMap, name) {
553 557
        set a.symbols[idx as u32].isExported = true;
554 558
    }
555 559
}
556 560
lib/std/arch/rv64/asm/tests.rad +2 -1
1 1
//! RV64 assembler tests.
2 2
3 3
use std::testing;
4 +
use std::collections::dict;
4 5
use std::mem;
5 6
use std::lang::alloc;
6 7
use std::lang::sexpr;
7 8
use std::lang::strings;
8 9
use std::arch::rv64;
9 10
use std::arch::rv64::encode;
10 11
use std::arch::rv64::printer;
11 12
12 13
use super::scanner;
13 14
15 +
unsafe static ASM_STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
14 16
static ASM_ARENA_STORAGE: [u8; 65536] = [0; 65536];
15 17
static ASM_TEXT_STORAGE: [u32; 256] = [0; 256];
16 18
static ASM_DATA_STORAGE: [u8; 1024] = [0; 1024];
17 -
unsafe static ASM_STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
18 19
static PRINT_ARENA_STORAGE: [u8; 1024] = [0; 1024];
19 20
static PRINT_BUFFER: [u8; 128] = [0; 128];
20 21
21 22
unsafe fn assembleSource(source: *[u8]) -> super::Program throws (testing::TestError) {
22 23
    let mut arena = alloc::new(&mut ASM_ARENA_STORAGE[..]);
lib/std/arch/rv64/emit.rad +196 -159
13 13
14 14
use super::encode;
15 15
16 16
/// Maximum number of instructions in code buffer.
17 17
constant MAX_INSTRS: u32 = 2097152;
18 -
/// Maximum code length before byte offset overflows signed 32-bits.
19 -
constant MAX_CODE_LEN: u32 = 0x7FFFFFFF / super::INSTR_SIZE as u32;
20 18
/// Maximum positive value encodable by a signed 32-bit address calculation.
21 19
constant MAX_I32_ADDR: u32 = 0x7FFFFFFF;
20 +
/// Minimum value encodable by a signed 32-bit address calculation.
21 +
constant MIN_I32_ADDR: i64 = -(MAX_I32_ADDR as i64) - 1;
22 +
/// Maximum code length before byte offset overflows signed 32-bits.
23 +
constant MAX_CODE_LEN: u32 = MAX_I32_ADDR / super::INSTR_SIZE as u32;
22 24
/// Maximum number of pending branches awaiting patching.
23 25
constant MAX_PENDING: u32 = 65536;
24 26
/// Maximum number of function entries.
25 27
constant MAX_FUNCS: u32 = 4096;
26 28
/// Maximum number of debug entries.
100 102
export record Emitter {
101 103
    /// Allocator for growing append-backed emitter lists.
102 104
    allocator: alloc::Allocator,
103 105
    /// Emitted instructions storage.
104 106
    code: *mut [u32],
105 -
    /// Current number of emitted instructions.
106 -
    codeLen: u32,
107 107
    /// Local branches needing offset patching.
108 108
    pendingBranches: *mut [PendingBranch],
109 109
    /// Function calls needing offset patching.
110 110
    pendingCalls: *mut [PendingCall],
111 111
    /// Assembly jumps needing offset patching.
116 116
    labels: labels::Labels,
117 117
    /// Function start positions for printing.
118 118
    funcs: *mut [types::FuncAddr],
119 119
    /// Debug entries mapping PCs to source locations.
120 120
    debugEntries: *mut [types::DebugEntry],
121 -
    /// Number of debug entries recorded.
122 -
    debugEntriesLen: u32,
123 121
}
124 122
125 123
/// Computed stack frame layout for a function.
126 124
export record Frame: Copy {
127 125
    /// Total frame size in bytes (aligned).
140 138
    /// When false, SP never changes after the prologue.
141 139
    isDynamic: bool,
142 140
}
143 141
144 142
/// Compute frame layout from local size and used callee-saved registers.
145 -
export fn computeFrame(localSize: i32, usedCalleeSaved: u32, epilogueBlock: u32, isLeaf: bool, isDynamic: bool) -> Frame {
143 +
export fn computeFrame(localSize: i32, usedCalleeSaved: u32, epilogueBlock: u32, isLeaf: bool, isDynamic: bool) -> Frame throws (super::Error) {
146 144
    let mut frame = Frame {
147 145
        totalSize: 0,
148 146
        savedRegs: [SavedReg { reg: super::ZERO, offset: 0 }; super::NUM_SAVED_REGISTERS],
149 147
        savedRegsLen: 0,
150 148
        epilogueBlock,
157 155
    if isLeaf and localSize == 0 and usedCalleeSaved == 0 {
158 156
        return frame;
159 157
    }
160 158
    // Compute total frame size. Includes RA and FP registers.
161 159
    let savedRegs = mem::popCount(usedCalleeSaved) + 2;
162 -
    let totalSize = mem::alignUpI32(
163 -
        localSize + savedRegs * super::DWORD_SIZE,
164 -
        super::STACK_ALIGNMENT
165 -
    );
160 +
    let savedSize = savedRegs * super::DWORD_SIZE;
161 +
    if localSize > MAX_I32_ADDR as i32 - savedSize - (super::STACK_ALIGNMENT - 1) {
162 +
        throw super::Error::Capacity;
163 +
    }
164 +
    let totalSize = mem::alignUpI32(localSize + savedSize, super::STACK_ALIGNMENT);
166 165
    set frame.totalSize = totalSize;
167 166
168 167
    // Build list of callee-saved registers with offsets.
169 168
    let mut offset = totalSize - (super::DWORD_SIZE * 3);
170 169
    for reg, i in super::CALLEE_SAVED {
196 195
    if debug {
197 196
        set debugEntries = try alloc::allocSlice(
198 197
            arena, @sizeOf(types::DebugEntry), @alignOf(types::DebugEntry), MAX_DEBUG_ENTRIES
199 198
        ) as *mut [types::DebugEntry];
200 199
    }
200 +
    let codeBuf = code as *mut [u32];
201 201
    let pendingBranchesBuf = pendingBranches as *mut [PendingBranch];
202 202
    let pendingCallsBuf = pendingCalls as *mut [PendingCall];
203 203
    let pendingJumpsBuf = pendingJumps as *mut [PendingJump];
204 204
    let pendingAddrLoadsBuf = pendingAddrLoads as *mut [PendingAddrLoad];
205 205
    let funcsBuf = funcs as *mut [types::FuncAddr];
206 206
207 207
    return Emitter {
208 208
        allocator: alloc::arenaAllocator(arena),
209 -
        code: code as *mut [u32],
210 -
        codeLen: 0,
209 +
        code: &mut codeBuf[..0],
211 210
        pendingBranches: &mut pendingBranchesBuf[..0],
212 211
        pendingCalls: &mut pendingCallsBuf[..0],
213 212
        pendingJumps: &mut pendingJumpsBuf[..0],
214 213
        pendingAddrLoads: &mut pendingAddrLoadsBuf[..0],
215 214
        labels: labels::init(blockOffsets as *mut [i32], funcEntries as *mut [dict::Entry]),
216 215
        funcs: &mut funcsBuf[..0],
217 -
        debugEntries,
218 -
        debugEntriesLen: 0,
216 +
        debugEntries: &mut debugEntries[..0],
219 217
    };
220 218
}
221 219
222 220
///////////////////////
223 221
// Emission Helpers  //
224 222
///////////////////////
225 223
226 224
/// Emit a single instruction.
227 -
export fn emit(e: &mut Emitter, instr: u32) {
228 -
    assert e.codeLen < e.code.len, "emit: code buffer full";
229 -
    set e.code[e.codeLen] = instr;
230 -
    set e.codeLen += 1;
225 +
export fn emit(e: &mut Emitter, instr: u32) throws (super::Error) {
226 +
    try e.code.append(instr, e.allocator) catch {
227 +
        throw super::Error::Allocation;
228 +
    };
231 229
}
232 230
233 231
/// Compute branch offset to a function by name.
234 -
export fn branchOffsetToFunc(e: &Emitter, srcIndex: u32, name: *[u8]) -> i32 {
235 -
    return labels::branchToFunc(&e.labels, srcIndex, name, super::INSTR_SIZE);
232 +
export fn branchOffsetToFunc(e: &Emitter, srcIndex: u32, name: *[u8]) -> i32 throws (super::Error) {
233 +
    let target = dict::get(&e.labels.funcs, name) else {
234 +
        throw super::Error::Symbol;
235 +
    };
236 +
    let offset = target as i64 - srcIndex as i64 * super::INSTR_SIZE as i64;
237 +
    if offset < MIN_I32_ADDR or offset > MAX_I32_ADDR as i64 {
238 +
        throw super::Error::Relocation;
239 +
    }
240 +
    return offset as i32;
236 241
}
237 242
238 243
/// Patch an instruction at a given index.
239 244
export fn patch(e: &mut Emitter, index: u32, instr: u32) {
240 245
    set e.code[index] = instr;
241 246
}
242 247
243 248
/// Record a block's address for branch resolution.
244 -
export fn recordBlock(e: &mut Emitter, blockIdx: u32) {
245 -
    assert e.codeLen <= MAX_CODE_LEN;
246 -
    labels::recordBlock(&mut e.labels, blockIdx, e.codeLen as i32 * super::INSTR_SIZE);
249 +
export fn recordBlock(e: &mut Emitter, blockIdx: u32) throws (super::Error) {
250 +
    if e.code.len > MAX_CODE_LEN {
251 +
        throw super::Error::Relocation;
252 +
    }
253 +
    try labels::recordBlock(&mut e.labels, blockIdx, e.code.len as i32 * super::INSTR_SIZE) catch {
254 +
        throw super::Error::Capacity;
255 +
    };
247 256
}
248 257
249 258
/// Record a function's code offset for call resolution.
250 -
export fn recordFuncOffset(e: &mut Emitter, name: *[u8]) {
251 -
    let codeLen = e.codeLen;
252 -
    recordFuncOffsetAt(e, name, codeLen);
259 +
export fn recordFuncOffset(e: &mut Emitter, name: *[u8]) throws (super::Error) {
260 +
    let codeLen = e.code.len;
261 +
    try recordFuncOffsetAt(e, name, codeLen);
253 262
}
254 263
255 264
/// Record a function's code offset at `index` for call resolution.
256 -
export fn recordFuncOffsetAt(e: &mut Emitter, name: *[u8], index: u32) {
257 -
    assert index <= MAX_CODE_LEN;
258 -
    dict::insert(&mut e.labels.funcs, name, index as i32 * super::INSTR_SIZE);
265 +
export fn recordFuncOffsetAt(e: &mut Emitter, name: *[u8], index: u32) throws (super::Error) {
266 +
    if index > MAX_CODE_LEN {
267 +
        throw super::Error::Relocation;
268 +
    }
269 +
    try dict::insert(&mut e.labels.funcs, name, index as i32 * super::INSTR_SIZE) catch {
270 +
        throw super::Error::Capacity;
271 +
    };
259 272
}
260 273
261 274
/// Record a function's start position for printing.
262 -
export fn recordFunc(e: &mut Emitter, name: *[u8]) {
263 -
    let codeLen = e.codeLen;
264 -
    recordFuncAt(e, name, codeLen);
275 +
export fn recordFunc(e: &mut Emitter, name: *[u8]) throws (super::Error) {
276 +
    let codeLen = e.code.len;
277 +
    try recordFuncAt(e, name, codeLen);
265 278
}
266 279
267 280
/// Record a function's start position at `index` for printing.
268 -
export fn recordFuncAt(e: &mut Emitter, name: *[u8], index: u32) {
269 -
    try! e.funcs.append(types::FuncAddr { name, index }, e.allocator);
281 +
export fn recordFuncAt(e: &mut Emitter, name: *[u8], index: u32) throws (super::Error) {
282 +
    try e.funcs.append(types::FuncAddr { name, index }, e.allocator) catch {
283 +
        throw super::Error::Allocation;
284 +
    };
270 285
}
271 286
272 287
/// Record a local branch needing later patching.
273 288
/// Unconditional jumps use a single slot (J-type, +-1MB range).
274 289
/// Conditional branches use two slots (B-type has only +-4KB range,
275 290
/// so large functions may need the inverted-branch + JAL fallback).
276 -
export fn recordBranch(e: &mut Emitter, targetBlock: u32, kind: BranchKind) {
277 -
    try! e.pendingBranches.append(PendingBranch {
278 -
        index: e.codeLen,
291 +
export fn recordBranch(e: &mut Emitter, targetBlock: u32, kind: BranchKind) throws (super::Error) {
292 +
    try e.pendingBranches.append(PendingBranch {
293 +
        index: e.code.len,
279 294
        target: targetBlock,
280 295
        kind: kind,
281 -
    }, e.allocator);
296 +
    }, e.allocator) catch {
297 +
        throw super::Error::Allocation;
298 +
    };
282 299
283 -
    emit(e, encode::nop()); // First slot, always needed.
300 +
    try emit(e, encode::nop()); // First slot, always needed.
284 301
285 302
    match kind {
286 303
        case BranchKind::Jump => {},
287 -
        else => emit(e, encode::nop()), // Second slot for conditional branches.
304 +
        else => try emit(e, encode::nop()), // Second slot for conditional branches.
288 305
    }
289 306
}
290 307
291 308
/// Record a function call needing later patching.
292 309
/// Emits placeholder instructions that will be patched later.
293 310
/// Uses two slots to support long-distance calls.
294 -
export fn recordCall(e: &mut Emitter, target: *[u8]) {
295 -
    try! e.pendingCalls.append(PendingCall {
296 -
        index: e.codeLen,
311 +
export fn recordCall(e: &mut Emitter, target: *[u8]) throws (super::Error) {
312 +
    try e.pendingCalls.append(PendingCall {
313 +
        index: e.code.len,
297 314
        target,
298 -
    }, e.allocator);
315 +
    }, e.allocator) catch {
316 +
        throw super::Error::Allocation;
317 +
    };
299 318
300 -
    emit(e, encode::nop()); // Placeholder for AUIPC.
301 -
    emit(e, encode::nop()); // Placeholder for JALR.
319 +
    try emit(e, encode::nop()); // Placeholder for AUIPC.
320 +
    try emit(e, encode::nop()); // Placeholder for JALR.
302 321
}
303 322
304 323
/// Record a jump emitted by assembly that needs whole-program patching.
305 -
export fn recordJumpAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) {
306 -
    try! e.pendingJumps.append(PendingJump {
324 +
export fn recordJumpAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) throws (super::Error) {
325 +
    try e.pendingJumps.append(PendingJump {
307 326
        index,
308 327
        target,
309 328
        rd,
310 -
    }, e.allocator);
329 +
    }, e.allocator) catch {
330 +
        throw super::Error::Allocation;
331 +
    };
311 332
}
312 333
313 334
/// Record a function address load needing later patching.
314 335
/// Emits placeholder instructions that will be patched to load the function's address.
315 336
/// Uses two slots to compute long-distance addresses.
316 -
export fn recordAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) {
317 -
    let codeLen = e.codeLen;
318 -
    recordAddrLoadAt(e, target, rd, codeLen);
337 +
export fn recordAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) throws (super::Error) {
338 +
    let codeLen = e.code.len;
339 +
    try recordAddrLoadAt(e, target, rd, codeLen);
319 340
320 -
    emit(e, encode::nop()); // Placeholder for AUIPC.
321 -
    emit(e, encode::nop()); // Placeholder for ADDI.
341 +
    try emit(e, encode::nop()); // Placeholder for AUIPC.
342 +
    try emit(e, encode::nop()); // Placeholder for ADDI.
322 343
}
323 344
324 345
/// Record a function address load already reserved by assembly.
325 -
export fn recordAddrLoadAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) {
326 -
    try! e.pendingAddrLoads.append(PendingAddrLoad {
346 +
export fn recordAddrLoadAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) throws (super::Error) {
347 +
    try e.pendingAddrLoads.append(PendingAddrLoad {
327 348
        index,
328 349
        target,
329 350
        rd: rd,
330 351
        isData: false,
331 -
    }, e.allocator);
352 +
    }, e.allocator) catch {
353 +
        throw super::Error::Allocation;
354 +
    };
332 355
}
333 356
334 357
/// Record a data address load needing later patching.
335 358
/// Uses an absolute 32-bit load sequence matching the current data memory map.
336 -
export fn recordDataAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) {
337 -
    try! e.pendingAddrLoads.append(PendingAddrLoad {
338 -
        index: e.codeLen,
359 +
export fn recordDataAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) throws (super::Error) {
360 +
    try e.pendingAddrLoads.append(PendingAddrLoad {
361 +
        index: e.code.len,
339 362
        target,
340 363
        rd: rd,
341 364
        isData: true,
342 -
    }, e.allocator);
365 +
    }, e.allocator) catch {
366 +
        throw super::Error::Allocation;
367 +
    };
343 368
344 -
    emit(e, encode::nop()); // Placeholder for LUI.
345 -
    emit(e, encode::nop()); // Placeholder for ADDIW.
369 +
    try emit(e, encode::nop()); // Placeholder for LUI.
370 +
    try emit(e, encode::nop()); // Placeholder for ADDIW.
346 371
}
347 372
348 373
/// Patch local branches and clear the pending list.
349 374
///
350 375
/// Called after each function.
351 376
///
352 377
/// Uses two-instruction sequences: short branches use `branch` and `nop`,
353 378
/// long branches use inverted branch  and `jal` or `auipc` and `jalr`.
354 -
export unsafe fn patchLocalBranches(e: &mut Emitter) {
379 +
export unsafe fn patchLocalBranches(e: &mut Emitter) throws (super::Error) {
355 380
    for i in 0..e.pendingBranches.len {
356 381
        let p = e.pendingBranches[i];
357 382
        let offset = labels::branchToBlock(&e.labels, p.index, p.target, super::INSTR_SIZE);
358 383
        match p.kind {
359 384
            case BranchKind::Cond { op, rs1, rs2 } => {
360 385
                if encode::isBranchImm(offset) {
361 386
                    patch(e, p.index, encodeCondBranch(op, rs1, rs2, offset));
362 387
                    patch(e, p.index + 1, encode::nop());
363 388
                } else {
364 389
                    let adj = offset - super::INSTR_SIZE;
390 +
                    if not encode::isJumpImm(adj) {
391 +
                        throw super::Error::Relocation;
392 +
                    }
365 393
                    patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, super::INSTR_SIZE * 2));
366 394
                    patch(e, p.index + 1, encode::jal(super::ZERO, adj));
367 395
                }
368 396
            },
369 397
            case BranchKind::InvertedCond { op, rs1, rs2 } => {
370 398
                if encode::isBranchImm(offset) {
371 399
                    patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, offset));
372 400
                    patch(e, p.index + 1, encode::nop());
373 401
                } else {
374 402
                    let adj = offset - super::INSTR_SIZE;
403 +
                    if not encode::isJumpImm(adj) {
404 +
                        throw super::Error::Relocation;
405 +
                    }
375 406
                    patch(e, p.index, encodeCondBranch(op, rs1, rs2, super::INSTR_SIZE * 2));
376 407
                    patch(e, p.index + 1, encode::jal(super::ZERO, adj));
377 408
                }
378 409
            },
379 410
            case BranchKind::Jump => {
380 411
                // Single-slot jump (J-type, +-1MB range).
381 -
                assert encode::isJumpImm(offset), "patchLocalBranches: jump offset too large";
412 +
                if not encode::isJumpImm(offset) {
413 +
                    throw super::Error::Relocation;
414 +
                }
382 415
                patch(e, p.index, encode::jal(super::ZERO, offset));
383 416
            },
384 417
        }
385 418
    }
386 -
    set e.pendingBranches = &mut e.pendingBranches[..0];
419 +
    set e.pendingBranches.len = 0;
387 420
}
388 421
389 422
/// Encode a conditional branch instruction.
390 423
fn encodeCondBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 {
391 424
    match op {
406 439
    }
407 440
}
408 441
409 442
/// Patch all pending function calls.
410 443
/// Called after all functions have been generated.
411 -
export fn patchCalls(e: &mut Emitter) {
444 +
export fn patchCalls(e: &mut Emitter) throws (super::Error) {
412 445
    for i in 0..e.pendingCalls.len {
413 446
        let p = e.pendingCalls[i];
414 -
        let offset = branchOffsetToFunc(e, p.index, p.target);
447 +
        let offset = try branchOffsetToFunc(e, p.index, p.target);
415 448
        let s = splitImm(offset);
416 449
417 450
        // `AUIPC scratch, hi(offset)`.
418 451
        patch(e, p.index, encode::auipc(super::SCRATCH1, s.hi));
419 452
        // `JALR ra, scratch, lo(offset)`.
420 453
        patch(e, p.index + 1, encode::jalr(super::RA, super::SCRATCH1, s.lo));
421 454
    }
422 455
}
423 456
424 457
/// Patch all pending assembly jumps.
425 -
export fn patchJumps(e: &mut Emitter) {
458 +
export fn patchJumps(e: &mut Emitter) throws (super::Error) {
426 459
    for i in 0..e.pendingJumps.len {
427 460
        let p = e.pendingJumps[i];
428 -
        let offset = branchOffsetToFunc(e, p.index, p.target);
461 +
        let offset = try branchOffsetToFunc(e, p.index, p.target);
429 462
430 -
        assert encode::isJumpImm(offset), "patchJumps: jump offset too large";
463 +
        if not encode::isJumpImm(offset) {
464 +
            throw super::Error::Relocation;
465 +
        }
431 466
        patch(e, p.index, encode::jal(p.rd, offset));
432 467
    }
433 468
}
434 469
435 470
/// Patch all pending function and data address loads.
436 471
/// Called after all functions have been generated and data layout is known.
437 -
export fn patchAddrLoads(e: &mut Emitter, dataSymMap: &data::DataSymMap) {
472 +
export fn patchAddrLoads(e: &mut Emitter, dataSymMap: &data::DataSymMap) throws (super::Error) {
438 473
    for i in 0..e.pendingAddrLoads.len {
439 474
        let p = e.pendingAddrLoads[i];
440 475
        if p.isData {
441 476
            let addr = data::lookupAddr(dataSymMap, p.target) else {
442 -
                panic "patchAddrLoads: data symbol not found";
477 +
                throw super::Error::Symbol;
443 478
            };
444 -
            assert addr <= MAX_I32_ADDR, "patchAddrLoads: data address too large";
479 +
            if addr > MAX_I32_ADDR {
480 +
                throw super::Error::Relocation;
481 +
            }
445 482
            let s = splitImm(addr as i32);
446 483
447 484
            patch(e, p.index, encode::lui(p.rd, s.hi));
448 485
            patch(e, p.index + 1, encode::addiw(p.rd, p.rd, s.lo));
449 486
450 487
            continue;
451 488
        }
452 489
453 -
        let offset = branchOffsetToFunc(e, p.index, p.target);
490 +
        let offset = try branchOffsetToFunc(e, p.index, p.target);
454 491
        let s = splitImm(offset);
455 492
        // `AUIPC rd, hi(offset)`.
456 493
        patch(e, p.index, encode::auipc(p.rd, s.hi));
457 494
        // `ADDI rd, rd, lo(offset)`.
458 495
        patch(e, p.index + 1, encode::addi(p.rd, p.rd, s.lo));
488 525
/// Adjust a large offset by loading *hi* bits into [`super::ADDR_SCRATCH`].
489 526
/// Returns adjusted base register and remaining offset.
490 527
///
491 528
/// When the offset fits a 12-bit signed immediate, returns it unchanged.
492 529
/// Otherwise uses [`super::ADDR_SCRATCH`] for the LUI+ADD decomposition.
493 -
fn adjustOffset(e: &mut Emitter, base: gen::Reg, offset: i32) -> AdjustedOffset {
530 +
fn adjustOffset(e: &mut Emitter, base: gen::Reg, offset: i32) -> AdjustedOffset throws (super::Error) {
494 531
    if offset >= super::MIN_IMM and offset <= super::MAX_IMM {
495 532
        return AdjustedOffset { base, offset };
496 533
    }
497 534
    let s = splitImm(offset);
498 -
    emit(e, encode::lui(super::ADDR_SCRATCH, s.hi));
499 -
    emit(e, encode::add(super::ADDR_SCRATCH, super::ADDR_SCRATCH, base));
535 +
    try emit(e, encode::lui(super::ADDR_SCRATCH, s.hi));
536 +
    try emit(e, encode::add(super::ADDR_SCRATCH, super::ADDR_SCRATCH, base));
500 537
501 538
    return AdjustedOffset { base: super::ADDR_SCRATCH, offset: s.lo };
502 539
}
503 540
504 541
/// Load an immediate value into a register.
505 542
/// Handles the full range of 64-bit immediates.
506 543
/// For values fitting in 12 bits, uses a single `ADDI`.
507 544
/// For values fitting in 32 bits, uses `LUI` + `ADDIW`.
508 545
/// For wider values, loads upper and lower halves then combines with shift and add.
509 -
export fn loadImm(e: &mut Emitter, rd: gen::Reg, imm: i64) {
546 +
export fn loadImm(e: &mut Emitter, rd: gen::Reg, imm: i64) throws (super::Error) {
510 547
    let immMin = super::MIN_IMM as i64;
511 548
    let immMax = super::MAX_IMM as i64;
512 549
513 550
    if imm >= immMin and imm <= immMax {
514 -
        emit(e, encode::addi(rd, super::ZERO, imm as i32));
551 +
        try emit(e, encode::addi(rd, super::ZERO, imm as i32));
515 552
        return;
516 553
    }
517 554
    // Check if the value fits in 32 bits (sign-extended).
518 555
    let lo32 = imm as i32;
519 556
    if lo32 as i64 == imm {
520 557
        let s = splitImm(lo32);
521 -
        emit(e, encode::lui(rd, s.hi));
558 +
        try emit(e, encode::lui(rd, s.hi));
522 559
        if s.lo <> 0 {
523 -
            emit(e, encode::addiw(rd, rd, s.lo));
560 +
            try emit(e, encode::addiw(rd, rd, s.lo));
524 561
        }
525 562
        return;
526 563
    }
527 564
    // Full 64-bit immediate: use only rd, no scratch registers.
528 565
    // Load upper 32 bits first via the 32-bit path (LUI+ADDIW),
530 567
    // sign-extension issues with ADDI's 12-bit signed immediate.
531 568
    let hi32 = (imm >> 32) as i32;
532 569
    let lower = imm as i32;
533 570
534 571
    // Load upper 32 bits.
535 -
    loadImm(e, rd, hi32 as i64);
572 +
    try loadImm(e, rd, hi32 as i64);
536 573
    // Shift left by 11, add bits [31:21].
537 -
    emit(e, encode::slli(rd, rd, 11));
574 +
    try emit(e, encode::slli(rd, rd, 11));
538 575
    let chunkHi = (lower >> 21) & 0x7FF;
539 576
    if chunkHi <> 0 {
540 -
        emit(e, encode::addi(rd, rd, chunkHi));
577 +
        try emit(e, encode::addi(rd, rd, chunkHi));
541 578
    }
542 579
    // Shift left by 11, add bits [20:10].
543 -
    emit(e, encode::slli(rd, rd, 11));
580 +
    try emit(e, encode::slli(rd, rd, 11));
544 581
    let chunkMid = (lower >> 10) & 0x7FF;
545 582
    if chunkMid <> 0 {
546 -
        emit(e, encode::addi(rd, rd, chunkMid));
583 +
        try emit(e, encode::addi(rd, rd, chunkMid));
547 584
    }
548 585
    // Shift left by 10, add bits [9:0].
549 -
    emit(e, encode::slli(rd, rd, 10));
586 +
    try emit(e, encode::slli(rd, rd, 10));
550 587
    let chunkLo = lower & 0x3FF;
551 588
    if chunkLo <> 0 {
552 -
        emit(e, encode::addi(rd, rd, chunkLo));
589 +
        try emit(e, encode::addi(rd, rd, chunkLo));
553 590
    }
554 591
}
555 592
556 593
/// Emit add-immediate, handling large immediates.
557 -
export fn emitAddImm(e: &mut Emitter, rd: gen::Reg, rs: gen::Reg, imm: i32) {
594 +
export fn emitAddImm(e: &mut Emitter, rd: gen::Reg, rs: gen::Reg, imm: i32) throws (super::Error) {
558 595
    if imm >= super::MIN_IMM and imm <= super::MAX_IMM {
559 -
        emit(e, encode::addi(rd, rs, imm));
596 +
        try emit(e, encode::addi(rd, rs, imm));
560 597
    } else {
561 -
        loadImm(e, super::SCRATCH1, imm as i64);
562 -
        emit(e, encode::add(rd, rs, super::SCRATCH1));
598 +
        try loadImm(e, super::SCRATCH1, imm as i64);
599 +
        try emit(e, encode::add(rd, rs, super::SCRATCH1));
563 600
    }
564 601
}
565 602
566 603
////////////////////////
567 604
// Load/Store Helpers //
568 605
////////////////////////
569 606
570 607
/// Emit unsigned load with automatic offset adjustment.
571 -
export fn emitLoad(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
572 -
    let adj = adjustOffset(e, base, offset);
608 +
export fn emitLoad(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) throws (super::Error) {
609 +
    let adj = try adjustOffset(e, base, offset);
573 610
    match typ {
574 -
        case il::Type::W8 => emit(e, encode::lbu(rd, adj.base, adj.offset)),
575 -
        case il::Type::W16 => emit(e, encode::lhu(rd, adj.base, adj.offset)),
576 -
        case il::Type::W32 => emit(e, encode::lwu(rd, adj.base, adj.offset)),
577 -
        case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)),
611 +
        case il::Type::W8 => try emit(e, encode::lbu(rd, adj.base, adj.offset)),
612 +
        case il::Type::W16 => try emit(e, encode::lhu(rd, adj.base, adj.offset)),
613 +
        case il::Type::W32 => try emit(e, encode::lwu(rd, adj.base, adj.offset)),
614 +
        case il::Type::W64 => try emit(e, encode::ld(rd, adj.base, adj.offset)),
578 615
    }
579 616
}
580 617
581 618
/// Emit signed load with automatic offset adjustment.
582 -
export fn emitSload(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
583 -
    let adj = adjustOffset(e, base, offset);
619 +
export fn emitSload(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) throws (super::Error) {
620 +
    let adj = try adjustOffset(e, base, offset);
584 621
    match typ {
585 -
        case il::Type::W8 => emit(e, encode::lb(rd, adj.base, adj.offset)),
586 -
        case il::Type::W16 => emit(e, encode::lh(rd, adj.base, adj.offset)),
587 -
        case il::Type::W32 => emit(e, encode::lw(rd, adj.base, adj.offset)),
588 -
        case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)),
622 +
        case il::Type::W8 => try emit(e, encode::lb(rd, adj.base, adj.offset)),
623 +
        case il::Type::W16 => try emit(e, encode::lh(rd, adj.base, adj.offset)),
624 +
        case il::Type::W32 => try emit(e, encode::lw(rd, adj.base, adj.offset)),
625 +
        case il::Type::W64 => try emit(e, encode::ld(rd, adj.base, adj.offset)),
589 626
    }
590 627
}
591 628
592 629
/// Emit store with automatic offset adjustment.
593 -
export fn emitStore(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
594 -
    let adj = adjustOffset(e, base, offset);
630 +
export fn emitStore(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) throws (super::Error) {
631 +
    let adj = try adjustOffset(e, base, offset);
595 632
    match typ {
596 -
        case il::Type::W8 => emit(e, encode::sb(rs, adj.base, adj.offset)),
597 -
        case il::Type::W16 => emit(e, encode::sh(rs, adj.base, adj.offset)),
598 -
        case il::Type::W32 => emit(e, encode::sw(rs, adj.base, adj.offset)),
599 -
        case il::Type::W64 => emit(e, encode::sd(rs, adj.base, adj.offset)),
633 +
        case il::Type::W8 => try emit(e, encode::sb(rs, adj.base, adj.offset)),
634 +
        case il::Type::W16 => try emit(e, encode::sh(rs, adj.base, adj.offset)),
635 +
        case il::Type::W32 => try emit(e, encode::sw(rs, adj.base, adj.offset)),
636 +
        case il::Type::W64 => try emit(e, encode::sd(rs, adj.base, adj.offset)),
600 637
    }
601 638
}
602 639
603 640
/// Emit 64-bit load with automatic offset adjustment.
604 -
export fn emitLd(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
605 -
    let adj = adjustOffset(e, base, offset);
606 -
    emit(e, encode::ld(rd, adj.base, adj.offset));
641 +
export fn emitLd(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) throws (super::Error) {
642 +
    let adj = try adjustOffset(e, base, offset);
643 +
    try emit(e, encode::ld(rd, adj.base, adj.offset));
607 644
}
608 645
609 646
/// Emit 64-bit store with automatic offset adjustment.
610 -
export fn emitSd(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
611 -
    let adj = adjustOffset(e, base, offset);
612 -
    emit(e, encode::sd(rs, adj.base, adj.offset));
647 +
export fn emitSd(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) throws (super::Error) {
648 +
    let adj = try adjustOffset(e, base, offset);
649 +
    try emit(e, encode::sd(rs, adj.base, adj.offset));
613 650
}
614 651
615 652
/// Emit 32-bit load with automatic offset adjustment.
616 -
export fn emitLw(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
617 -
    let adj = adjustOffset(e, base, offset);
618 -
    emit(e, encode::lw(rd, adj.base, adj.offset));
653 +
export fn emitLw(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) throws (super::Error) {
654 +
    let adj = try adjustOffset(e, base, offset);
655 +
    try emit(e, encode::lw(rd, adj.base, adj.offset));
619 656
}
620 657
621 658
/// Emit 32-bit store with automatic offset adjustment.
622 -
export fn emitSw(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
623 -
    let adj = adjustOffset(e, base, offset);
624 -
    emit(e, encode::sw(rs, adj.base, adj.offset));
659 +
export fn emitSw(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) throws (super::Error) {
660 +
    let adj = try adjustOffset(e, base, offset);
661 +
    try emit(e, encode::sw(rs, adj.base, adj.offset));
625 662
}
626 663
627 664
/// Emit 8-bit load with automatic offset adjustment.
628 -
export fn emitLb(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
629 -
    let adj = adjustOffset(e, base, offset);
630 -
    emit(e, encode::lb(rd, adj.base, adj.offset));
665 +
export fn emitLb(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) throws (super::Error) {
666 +
    let adj = try adjustOffset(e, base, offset);
667 +
    try emit(e, encode::lb(rd, adj.base, adj.offset));
631 668
}
632 669
633 670
/// Emit 8-bit store with automatic offset adjustment.
634 -
export fn emitSb(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
635 -
    let adj = adjustOffset(e, base, offset);
636 -
    emit(e, encode::sb(rs, adj.base, adj.offset));
671 +
export fn emitSb(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) throws (super::Error) {
672 +
    let adj = try adjustOffset(e, base, offset);
673 +
    try emit(e, encode::sb(rs, adj.base, adj.offset));
637 674
}
638 675
639 676
//////////////////////////
640 677
// Prologue / Epilogue  //
641 678
//////////////////////////
642 679
643 680
/// Emit function prologue.
644 681
/// Allocate the frame and save registers. Save FP only for dynamic frames.
645 -
export fn emitPrologue(e: &mut Emitter, frame: &Frame) {
682 +
export fn emitPrologue(e: &mut Emitter, frame: &Frame) throws (super::Error) {
646 683
    // Fast path: leaf function with no locals.
647 684
    if frame.totalSize == 0 {
648 685
        return;
649 686
    }
650 687
    let totalSize = frame.totalSize;
651 688
652 689
    // Allocate stack frame.
653 690
    let negFrame = 0 - totalSize;
654 691
    if negFrame >= super::MIN_IMM {
655 -
        emit(e, encode::addi(super::SP, super::SP, negFrame));
692 +
        try emit(e, encode::addi(super::SP, super::SP, negFrame));
656 693
    } else {
657 -
        loadImm(e, super::SCRATCH1, totalSize as i64);
658 -
        emit(e, encode::sub(super::SP, super::SP, super::SCRATCH1));
694 +
        try loadImm(e, super::SCRATCH1, totalSize as i64);
695 +
        try emit(e, encode::sub(super::SP, super::SP, super::SCRATCH1));
659 696
    }
660 697
    // Save return address.
661 698
    if not frame.isLeaf {
662 -
        emitSd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE);
699 +
        try emitSd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE);
663 700
    }
664 701
    // Save and set FP only when dynamic allocations can move SP.
665 702
    if frame.isDynamic {
666 -
        emitSd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2);
667 -
        emitAddImm(e, super::FP, super::SP, totalSize);
703 +
        try emitSd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2);
704 +
        try emitAddImm(e, super::FP, super::SP, totalSize);
668 705
    }
669 706
    // Save callee-saved registers.
670 707
    for i in 0..frame.savedRegsLen {
671 708
        let sr = frame.savedRegs[i];
672 -
        emitSd(e, sr.reg, super::SP, sr.offset);
709 +
        try emitSd(e, sr.reg, super::SP, sr.offset);
673 710
    }
674 711
}
675 712
676 713
/// Emit a return: jump to epilogue, or emit `ret` directly for leaf functions.
677 -
export fn emitReturn(e: &mut Emitter, frame: &Frame) {
714 +
export fn emitReturn(e: &mut Emitter, frame: &Frame) throws (super::Error) {
678 715
    if frame.totalSize == 0 {
679 716
        // Leaf function: no frame to tear down, emit ret directly.
680 -
        emit(e, encode::ret());
717 +
        try emit(e, encode::ret());
681 718
        return;
682 719
    }
683 -
    recordBranch(e, frame.epilogueBlock, BranchKind::Jump);
720 +
    try recordBranch(e, frame.epilogueBlock, BranchKind::Jump);
684 721
}
685 722
686 723
/// Emit function epilogue.
687 724
/// Restore saved registers and release the frame. Restore FP only for dynamic frames.
688 -
export fn emitEpilogue(e: &mut Emitter, frame: &Frame) {
725 +
export fn emitEpilogue(e: &mut Emitter, frame: &Frame) throws (super::Error) {
689 726
    // Record epilogue block address for return jumps.
690 -
    recordBlock(e, frame.epilogueBlock);
727 +
    try recordBlock(e, frame.epilogueBlock);
691 728
692 729
    // Fast path: leaf function with no locals.
693 730
    if frame.totalSize == 0 {
694 -
        emit(e, encode::ret());
731 +
        try emit(e, encode::ret());
695 732
        return;
696 733
    }
697 734
    let totalSize = frame.totalSize;
698 735
699 736
    // Restore SP to post-prologue value. Only needed when dynamic stack
700 737
    // allocation may have moved SP.
701 738
    if frame.isDynamic {
702 -
        emitAddImm(e, super::SP, super::FP, 0 - totalSize);
739 +
        try emitAddImm(e, super::SP, super::FP, 0 - totalSize);
703 740
    }
704 741
    // Restore callee-saved registers.
705 742
    for i in 0..frame.savedRegsLen {
706 743
        let sr = frame.savedRegs[i];
707 -
        emitLd(e, sr.reg, super::SP, sr.offset);
744 +
        try emitLd(e, sr.reg, super::SP, sr.offset);
708 745
    }
709 746
    // Restore FP only if the prologue saved and changed it.
710 747
    if frame.isDynamic {
711 -
        emitLd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2);
748 +
        try emitLd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2);
712 749
    }
713 750
    // Restore return address.
714 751
    if not frame.isLeaf {
715 -
        emitLd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE);
752 +
        try emitLd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE);
716 753
    }
717 754
    // Deallocate stack frame.
718 -
    emitAddImm(e, super::SP, super::SP, totalSize);
719 -
    emit(e, encode::ret());
755 +
    try emitAddImm(e, super::SP, super::SP, totalSize);
756 +
    try emit(e, encode::ret());
720 757
}
721 758
722 759
//////////////////
723 760
// Code Access  //
724 761
//////////////////
725 762
726 763
/// Get emitted code as a slice.
727 764
export fn getCode(e: &Emitter) -> *[u32] {
728 -
    return &e.code[..e.codeLen];
765 +
    return &e.code[..];
729 766
}
730 767
731 768
/// Record a debug entry mapping the current PC to a source location.
732 769
/// Deduplicates consecutive entries with the same location.
733 -
export fn recordSrcLoc(e: &mut Emitter, loc: il::SrcLoc) {
734 -
    let pc = e.codeLen * super::INSTR_SIZE as u32;
770 +
export fn recordSrcLoc(e: &mut Emitter, loc: il::SrcLoc) throws (super::Error) {
771 +
    let pc = e.code.len * super::INSTR_SIZE as u32;
735 772
736 773
    // Skip if this is the same location as the previous entry.
737 -
    if e.debugEntriesLen > 0 {
738 -
        let prev = &e.debugEntries[e.debugEntriesLen - 1];
774 +
    if e.debugEntries.len > 0 {
775 +
        let prev = &e.debugEntries[e.debugEntries.len - 1];
739 776
        if prev.offset == loc.offset and prev.moduleId == loc.moduleId {
740 777
            return;
741 778
        }
742 779
    }
743 -
    assert e.debugEntriesLen < e.debugEntries.len, "recordSrcLoc: debug entry buffer full";
744 -
    set e.debugEntries[e.debugEntriesLen] = types::DebugEntry {
780 +
    try e.debugEntries.append(types::DebugEntry {
745 781
        pc,
746 782
        moduleId: loc.moduleId,
747 783
        offset: loc.offset,
784 +
    }, e.allocator) catch {
785 +
        throw super::Error::Allocation;
748 786
    };
749 -
    set e.debugEntriesLen += 1;
750 787
}
751 788
752 789
/// Get debug entries as a slice.
753 790
export fn getDebugEntries(e: &Emitter) -> *[types::DebugEntry] {
754 -
    return &e.debugEntries[..e.debugEntriesLen];
791 +
    return &e.debugEntries[..];
755 792
}
lib/std/arch/rv64/isel.rad +241 -231
145 145
}
146 146
147 147
/// Get the source register for an SSA register.
148 148
/// If the register is spilled, loads the value from the spill slot into the
149 149
/// scratch register and returns it. Otherwise returns the physical register.
150 -
unsafe fn getSrcReg(s: &mut Selector, ssa: il::Reg, scratch: gen::Reg) -> gen::Reg {
150 +
unsafe fn getSrcReg(s: &mut Selector, ssa: il::Reg, scratch: gen::Reg) -> gen::Reg throws (super::Error) {
151 151
    if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, ssa) {
152 -
        emit::emitLd(s.e, scratch, spillBase(s), spillOffset(s, slot));
152 +
        try emit::emitLd(s.e, scratch, spillBase(s), spillOffset(s, slot));
153 153
        return scratch;
154 154
    }
155 155
    return getReg(s, ssa);
156 156
}
157 157
158 158
/// Resolve an IL value to the physical register holding it.
159 159
/// For non-spilled register values, returns the physical register directly.
160 160
/// For immediates, symbols, and spilled registers, materializes into `scratch`.
161 -
unsafe fn resolveVal(s: &mut Selector, scratch: gen::Reg, val: il::Val) -> gen::Reg {
161 +
unsafe fn resolveVal(s: &mut Selector, scratch: gen::Reg, val: il::Val) -> gen::Reg throws (super::Error) {
162 162
    match val {
163 163
        case il::Val::Reg(r) => {
164 -
            return getSrcReg(s, r, scratch);
164 +
            return try getSrcReg(s, r, scratch);
165 165
        },
166 166
        case il::Val::Imm(imm) => {
167 167
            if imm == 0 {
168 168
                return super::ZERO;
169 169
            }
170 -
            emit::loadImm(s.e, scratch, imm);
170 +
            try emit::loadImm(s.e, scratch, imm);
171 171
            return scratch;
172 172
        },
173 173
        case il::Val::DataSym(name) => {
174 -
            emit::recordDataAddrLoad(s.e, name, scratch);
174 +
            try emit::recordDataAddrLoad(s.e, name, scratch);
175 175
            return scratch;
176 176
        },
177 177
        case il::Val::FnAddr(name) => {
178 -
            emit::recordAddrLoad(s.e, name, scratch);
178 +
            try emit::recordAddrLoad(s.e, name, scratch);
179 179
            return scratch;
180 180
        },
181 181
        case il::Val::Undef => {
182 182
            return scratch;
183 183
        }
184 184
    }
185 185
}
186 186
187 187
/// Load an IL value into a specific physical register.
188 188
/// Like [`resolveVal`], but ensures the value ends up in `rd`.
189 -
unsafe fn loadVal(s: &mut Selector, rd: gen::Reg, val: il::Val) -> gen::Reg {
190 -
    let rs = resolveVal(s, rd, val);
191 -
    emitMv(s, rd, rs);
189 +
unsafe fn loadVal(s: &mut Selector, rd: gen::Reg, val: il::Val) -> gen::Reg throws (super::Error) {
190 +
    let rs = try resolveVal(s, rd, val);
191 +
    try emitMv(s, rd, rs);
192 192
    return rd;
193 193
}
194 194
195 195
/// Emit a move instruction if source and destination differ.
196 -
unsafe fn emitMv(s: &mut Selector, rd: gen::Reg, rs: gen::Reg) {
196 +
unsafe fn emitMv(s: &mut Selector, rd: gen::Reg, rs: gen::Reg) throws (super::Error) {
197 197
    if *rd <> *rs {
198 -
        emit::emit(s.e, encode::mv(rd, rs));
198 +
        try emit::emit(s.e, encode::mv(rd, rs));
199 199
    }
200 200
}
201 201
202 202
/// Emit zero-extension from a sub-word type to the full register width.
203 -
fn emitZext(e: &mut emit::Emitter, rd: gen::Reg, rs: gen::Reg, typ: il::Type) {
203 +
fn emitZext(e: &mut emit::Emitter, rd: gen::Reg, rs: gen::Reg, typ: il::Type) throws (super::Error) {
204 204
    match typ {
205 -
        case il::Type::W8 => emit::emit(e, encode::andi(rd, rs, MASK_W8)),
205 +
        case il::Type::W8 => try emit::emit(e, encode::andi(rd, rs, MASK_W8)),
206 206
        case il::Type::W16 => {
207 -
            emit::emit(e, encode::slli(rd, rs, SHIFT_W16));
208 -
            emit::emit(e, encode::srli(rd, rd, SHIFT_W16));
207 +
            try emit::emit(e, encode::slli(rd, rs, SHIFT_W16));
208 +
            try emit::emit(e, encode::srli(rd, rd, SHIFT_W16));
209 209
        },
210 210
        case il::Type::W32 => {
211 -
            emit::emit(e, encode::slli(rd, rs, SHIFT_W32));
212 -
            emit::emit(e, encode::srli(rd, rd, SHIFT_W32));
211 +
            try emit::emit(e, encode::slli(rd, rs, SHIFT_W32));
212 +
            try emit::emit(e, encode::srli(rd, rd, SHIFT_W32));
213 213
        },
214 214
        case il::Type::W64 => {}
215 215
    }
216 216
}
217 217
218 218
/// Emit sign-extension from a sub-word type to the full register width.
219 -
fn emitSext(e: &mut emit::Emitter, rd: gen::Reg, rs: gen::Reg, typ: il::Type) {
219 +
fn emitSext(e: &mut emit::Emitter, rd: gen::Reg, rs: gen::Reg, typ: il::Type) throws (super::Error) {
220 220
    match typ {
221 221
        case il::Type::W8 => {
222 -
            emit::emit(e, encode::slli(rd, rs, SHIFT_W8));
223 -
            emit::emit(e, encode::srai(rd, rd, SHIFT_W8));
222 +
            try emit::emit(e, encode::slli(rd, rs, SHIFT_W8));
223 +
            try emit::emit(e, encode::srai(rd, rd, SHIFT_W8));
224 224
        },
225 225
        case il::Type::W16 => {
226 -
            emit::emit(e, encode::slli(rd, rs, SHIFT_W16));
227 -
            emit::emit(e, encode::srai(rd, rd, SHIFT_W16));
226 +
            try emit::emit(e, encode::slli(rd, rs, SHIFT_W16));
227 +
            try emit::emit(e, encode::srai(rd, rd, SHIFT_W16));
228 228
        },
229 229
        case il::Type::W32 => {
230 -
            emit::emit(e, encode::addiw(rd, rs, 0));
230 +
            try emit::emit(e, encode::addiw(rd, rs, 0));
231 231
        },
232 232
        case il::Type::W64 => {}
233 233
    }
234 234
}
235 235
238 238
unsafe fn resolveAndTrapIfZero(
239 239
    s: &mut Selector,
240 240
    b: il::Val,
241 241
    typ: il::Type,
242 242
    signed: bool
243 -
) -> gen::Reg {
243 +
) -> gen::Reg throws (super::Error) {
244 244
    let mut divisor = b;
245 245
    if let case il::Val::Imm(imm) = b {
246 246
        set divisor = il::Val::Imm(canonicalCmpImm(imm, typ, signed));
247 247
    }
248 -
    let rs2 = resolveVal(s, super::SCRATCH2, divisor);
248 +
    let rs2 = try resolveVal(s, super::SCRATCH2, divisor);
249 249
    if not isExtendedImm(divisor, typ, signed) {
250 -
        emitCmpExt(s.e, rs2, rs2, typ, signed);
250 +
        try emitCmpExt(s.e, rs2, rs2, typ, signed);
251 251
    }
252 252
    let mut knownNonZero = false;
253 253
    if let case il::Val::Imm(imm) = divisor {
254 254
        set knownNonZero = imm <> 0;
255 255
    }
256 256
    if not knownNonZero {
257 -
        emit::emit(s.e, encode::bne(rs2, super::ZERO, super::INSTR_SIZE * 2));
258 -
        emit::emit(s.e, encode::ebreak());
257 +
        try emit::emit(s.e, encode::bne(rs2, super::ZERO, super::INSTR_SIZE * 2));
258 +
        try emit::emit(s.e, encode::ebreak());
259 259
    }
260 260
    return rs2;
261 261
}
262 262
263 263
////////////////////////
272 272
    isDynamic: bool,
273 273
}
274 274
275 275
/// Pre-scan all blocks for constant-sized reserve instructions.
276 276
/// Returns the total size needed for all static reserves, respecting alignment.
277 -
unsafe fn computeReserveInfo(func: *unsafe il::Fn) -> ReserveInfo {
277 +
unsafe fn computeReserveInfo(func: *unsafe il::Fn) -> ReserveInfo throws (super::Error) {
278 278
    let mut offset: i32 = 0;
279 279
    let mut isDynamic = false;
280 280
281 281
    for b in 0..func.blocks.len {
282 282
        let block = &func.blocks[b];
283 283
        for instr in block.instrs {
284 284
            match instr {
285 285
                case il::Instr::Reserve { size, alignment, .. } => {
286 286
                    if let case il::Val::Imm(sz) = size {
287 -
                        set offset = mem::alignUpI32(offset, alignment as i32);
288 -
                        set offset += sz as i32;
287 +
                        let aligned = mem::alignUp(offset as u32, alignment);
288 +
                        let end = aligned as u64 + sz as u64;
289 +
                        if end > I32_MAX as u64 {
290 +
                            throw super::Error::Capacity;
291 +
                        }
292 +
                        set offset = end as i32;
289 293
                    } else {
290 294
                        set isDynamic = true;
291 295
                    }
292 296
                },
293 297
                else => {},
300 304
/// Select instructions for a function.
301 305
export unsafe fn selectFn(
302 306
    e: &mut emit::Emitter,
303 307
    ralloc: &regalloc::AllocResult,
304 308
    func: *unsafe il::Fn
305 -
) {
309 +
) throws (super::Error) {
306 310
    // Reset block offsets for this function.
307 311
    labels::resetBlocks(&mut e.labels);
308 312
    // Pre-scan for constant-sized reserves to promote to fixed frame slots.
309 -
    let reserveInfo = computeReserveInfo(func);
313 +
    let reserveInfo = try computeReserveInfo(func);
314 +
    if reserveInfo.size > I32_MAX as i32 - ralloc.spill.frameSize {
315 +
        throw super::Error::Capacity;
316 +
    }
310 317
    let isLeaf = func.isLeaf;
311 318
    // Compute frame layout from spill slots, reserve slots, and used callee-saved registers.
312 -
    let frame = emit::computeFrame(
319 +
    let frame = try emit::computeFrame(
313 320
        ralloc.spill.frameSize + reserveInfo.size,
314 321
        ralloc.usedCalleeSaved,
315 322
        func.blocks.len,
316 323
        isLeaf,
317 324
        reserveInfo.isDynamic
324 331
        reserveOffset: 0, pendingSpill: nil,
325 332
        nextSynthBlock: func.blocks.len + 1,
326 333
        isDynamic: frame.isDynamic,
327 334
    };
328 335
    // Record function name for printing.
329 -
    emit::recordFunc(s.e, func.name);
336 +
    try emit::recordFunc(s.e, func.name);
330 337
    // Record function code offset for call patching.
331 -
    emit::recordFuncOffset(s.e, func.name);
338 +
    try emit::recordFuncOffset(s.e, func.name);
332 339
    // Emit prologue.
333 -
    emit::emitPrologue(s.e, &frame);
340 +
    try emit::emitPrologue(s.e, &frame);
334 341
335 342
    // Move function params from arg registers to assigned registers.
336 343
    // Cross-call params may have been assigned to callee-saved registers
337 344
    // instead of their natural arg registers. Spilled params are stored
338 345
    // directly to their spill slots.
341 348
            let param = funcParam.value;
342 349
            let argReg = super::ARG_REGS[i];
343 350
344 351
            if let slot = regalloc::spill::spillSlot(&ralloc.spill, param) {
345 352
                // Spilled parameter: store arg register to spill slot.
346 -
                emit::emitSd(s.e, argReg, spillBase(&s), spillOffset(&s, slot));
353 +
                try emit::emitSd(s.e, argReg, spillBase(&s), spillOffset(&s, slot));
347 354
            } else if let assigned = ralloc.assignments[param.n] {
348 -
                emitMv(&mut s, assigned, argReg);
355 +
                try emitMv(&mut s, assigned, argReg);
349 356
            }
350 357
        }
351 358
    }
352 359
353 360
    // Emit each block.
354 361
    for i in 0..func.blocks.len {
355 -
        selectBlock(&mut s, i, &func.blocks[i], &frame, func);
362 +
        try selectBlock(&mut s, i, &func.blocks[i], &frame, func);
356 363
    }
357 364
    // Emit epilogue.
358 -
    emit::emitEpilogue(s.e, &frame);
365 +
    try emit::emitEpilogue(s.e, &frame);
359 366
    // Patch local branches now that all blocks are emitted.
360 -
    emit::patchLocalBranches(s.e);
367 +
    try emit::patchLocalBranches(s.e);
361 368
}
362 369
363 370
/// Select instructions for a block.
364 -
unsafe fn selectBlock(s: &mut Selector, blockIdx: u32, block: *unsafe il::Block, frame: &emit::Frame, func: *unsafe il::Fn) {
371 +
unsafe fn selectBlock(s: &mut Selector, blockIdx: u32, block: *unsafe il::Block, frame: &emit::Frame, func: *unsafe il::Fn) throws (super::Error) {
365 372
    // Record block address for branch patching.
366 -
    emit::recordBlock(s.e, blockIdx);
373 +
    try emit::recordBlock(s.e, blockIdx);
367 374
368 375
    // Block parameters are handled at jump sites (in `Jmp`/`Br`).
369 376
    // By the time we enter the block, the arguments have already been
370 377
    // moved to the parameter registers by the predecessor's terminator.
371 378
372 379
    // Process each instruction, auto-committing any pending spill after each.
373 380
    let hasLocs = block.locs.len > 0;
374 381
    for instr, i in block.instrs {
375 382
        // Record debug location before emitting machine instructions.
376 383
        if hasLocs {
377 -
            emit::recordSrcLoc(s.e, block.locs[i]);
384 +
            try emit::recordSrcLoc(s.e, block.locs[i]);
378 385
        }
379 386
        set s.pendingSpill = nil;
380 -
        selectInstr(s, blockIdx, instr, frame, func);
387 +
        try selectInstr(s, blockIdx, instr, frame, func);
381 388
382 389
        // Flush the pending spill store, if any.
383 390
        if let p = s.pendingSpill {
384 391
            if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, p.ssa) {
385 -
                emit::emitSd(s.e, p.rd, spillBase(s), spillOffset(s, slot));
392 +
                try emit::emitSd(s.e, p.rd, spillBase(s), spillOffset(s, slot));
386 393
            }
387 394
            set s.pendingSpill = nil;
388 395
        }
389 396
    }
390 397
}
391 398
392 399
/// Select instructions for a single IL instruction.
393 -
unsafe fn selectInstr(s: &mut Selector, blockIdx: u32, instr: il::Instr, frame: &emit::Frame, func: *unsafe il::Fn) {
400 +
unsafe fn selectInstr(s: &mut Selector, blockIdx: u32, instr: il::Instr, frame: &emit::Frame, func: *unsafe il::Fn) throws (super::Error) {
394 401
    match instr {
395 402
        case il::Instr::BinOp { op, typ, dst, a, b } => {
396 403
            let rd = getDstReg(s, dst, super::SCRATCH1);
397 -
            let rs1 = resolveVal(s, super::SCRATCH1, a);
398 -
            selectAluBinOp(s, op, typ, rd, rs1, b);
404 +
            let rs1 = try resolveVal(s, super::SCRATCH1, a);
405 +
            try selectAluBinOp(s, op, typ, rd, rs1, b);
399 406
        },
400 407
        case il::Instr::UnOp { op, typ, dst, a } => {
401 408
            let rd = getDstReg(s, dst, super::SCRATCH1);
402 -
            let rs = resolveVal(s, super::SCRATCH1, a);
403 -
            selectAluUnOp(s, op, typ, rd, rs);
409 +
            let rs = try resolveVal(s, super::SCRATCH1, a);
410 +
            try selectAluUnOp(s, op, typ, rd, rs);
404 411
        },
405 412
        case il::Instr::Load { typ, dst, src, offset } => {
406 413
            let rd = getDstReg(s, dst, super::SCRATCH1);
407 -
            let base = getSrcReg(s, src, super::SCRATCH2);
408 -
            emit::emitLoad(s.e, rd, base, offset, typ);
414 +
            let base = try getSrcReg(s, src, super::SCRATCH2);
415 +
            try emit::emitLoad(s.e, rd, base, offset, typ);
409 416
        },
410 417
        case il::Instr::Sload { typ, dst, src, offset } => {
411 418
            let rd = getDstReg(s, dst, super::SCRATCH1);
412 -
            let base = getSrcReg(s, src, super::SCRATCH2);
413 -
            emit::emitSload(s.e, rd, base, offset, typ);
419 +
            let base = try getSrcReg(s, src, super::SCRATCH2);
420 +
            try emit::emitSload(s.e, rd, base, offset, typ);
414 421
        },
415 422
        case il::Instr::Store { typ, src, dst, offset } => {
416 -
            let base = getSrcReg(s, dst, super::SCRATCH2);
417 -
            let rs = resolveVal(s, super::SCRATCH1, src);
418 -
            emit::emitStore(s.e, rs, base, offset, typ);
423 +
            let base = try getSrcReg(s, dst, super::SCRATCH2);
424 +
            let rs = try resolveVal(s, super::SCRATCH1, src);
425 +
            try emit::emitStore(s.e, rs, base, offset, typ);
419 426
        },
420 427
        case il::Instr::Copy { dst, val } => {
421 428
            let rd = getDstReg(s, dst, super::SCRATCH1);
422 -
            let rs = resolveVal(s, super::SCRATCH1, val);
423 -
            emitMv(s, rd, rs);
429 +
            let rs = try resolveVal(s, super::SCRATCH1, val);
430 +
            try emitMv(s, rd, rs);
424 431
        },
425 432
        case il::Instr::Reserve { dst, size, alignment } => {
426 433
            match size {
427 434
                case il::Val::Imm(sz) => {
428 435
                    // Constant-sized reserve: use pre-allocated frame slot.
430 437
                    let aligned: i32 = mem::alignUpI32(s.reserveOffset, alignment as i32);
431 438
                    let base = spillBase(s);
432 439
                    let offset = s.ralloc.spill.frameSize + aligned
433 440
                        - (s.frameSize if s.isDynamic else 0);
434 441
435 -
                    emit::emitAddImm(s.e, rd, base, offset);
442 +
                    try emit::emitAddImm(s.e, rd, base, offset);
436 443
                    set s.reserveOffset = aligned + (sz as i32);
437 444
                },
438 445
                case il::Val::Reg(r) => {
439 446
                    // Dynamic-sized reserve: runtime SP adjustment.
440 447
                    let rd = getDstReg(s, dst, super::SCRATCH1);
441 -
                    let rs = getSrcReg(s, r, super::SCRATCH2);
448 +
                    let rs = try getSrcReg(s, r, super::SCRATCH2);
442 449
443 -
                    emit::emit(s.e, encode::sub(super::SP, super::SP, rs));
450 +
                    try emit::emit(s.e, encode::sub(super::SP, super::SP, rs));
444 451
445 452
                    if alignment > 1 {
446 453
                        let mask = 0 - alignment as i32;
447 454
                        assert encode::isSmallImm(mask);
448 455
449 -
                        emit::emit(s.e, encode::andi(super::SP, super::SP, mask));
456 +
                        try emit::emit(s.e, encode::andi(super::SP, super::SP, mask));
450 457
                    }
451 -
                    emit::emit(s.e, encode::mv(rd, super::SP));
458 +
                    try emit::emit(s.e, encode::mv(rd, super::SP));
452 459
                },
453 460
                else =>
454 461
                    panic "selectInstr: invalid reserve operand",
455 462
            }
456 463
        },
476 483
                    panic "selectInstr: blit dst not spilled";
477 484
                };
478 485
                let srcSlot = regalloc::spill::spillSlot(&s.ralloc.spill, src) else {
479 486
                    panic "selectInstr: blit src not spilled";
480 487
                };
481 -
                emit::emitLd(s.e, super::SCRATCH2, spillBase(s), spillOffset(s, dstSlot));
488 +
                try emit::emitLd(s.e, super::SCRATCH2, spillBase(s), spillOffset(s, dstSlot));
482 489
                set srcReload = spillOffset(s, srcSlot);
483 490
            } else {
484 -
                set rdst = getSrcReg(s, dst, super::SCRATCH2);
485 -
                set rsrc = getSrcReg(s, src, super::SCRATCH2);
491 +
                set rdst = try getSrcReg(s, dst, super::SCRATCH2);
492 +
                set rsrc = try getSrcReg(s, src, super::SCRATCH2);
486 493
            }
487 494
            let mut offset: i32 = 0;
488 495
            let mut remaining = staticSize as i32;
489 496
490 497
            // For large blits where both pointers are in real registers,
493 500
            let canLoop = not bothSpilled
494 501
                and *rsrc <> *super::SCRATCH1 and *rsrc <> *super::SCRATCH2
495 502
                and *rdst <> *super::SCRATCH1 and *rdst <> *super::SCRATCH2;
496 503
497 504
            if canLoop and dwordBytes >= super::BLIT_LOOP_THRESHOLD {
498 -
                emit::emitAddImm(s.e, super::SCRATCH1, rsrc, dwordBytes);
505 +
                try emit::emitAddImm(s.e, super::SCRATCH1, rsrc, dwordBytes);
499 506
500 -
                let loopStart = s.e.codeLen;
507 +
                let loopStart = s.e.code.len;
501 508
502 -
                emit::emitLd(s.e, super::SCRATCH2, rsrc, 0);
503 -
                emit::emitSd(s.e, super::SCRATCH2, rdst, 0);
504 -
                emit::emit(s.e, encode::addi(rsrc, rsrc, super::DWORD_SIZE));
509 +
                try emit::emitLd(s.e, super::SCRATCH2, rsrc, 0);
510 +
                try emit::emitSd(s.e, super::SCRATCH2, rdst, 0);
511 +
                try emit::emit(s.e, encode::addi(rsrc, rsrc, super::DWORD_SIZE));
505 512
506 513
                if *rdst <> *rsrc {
507 -
                    emit::emit(s.e, encode::addi(rdst, rdst, super::DWORD_SIZE));
514 +
                    try emit::emit(s.e, encode::addi(rdst, rdst, super::DWORD_SIZE));
508 515
                }
509 -
                let brOff = (loopStart as i32 - s.e.codeLen as i32) * super::INSTR_SIZE;
516 +
                let brOff = (loopStart as i32 - s.e.code.len as i32) * super::INSTR_SIZE;
510 517
511 -
                emit::emit(s.e, encode::bne(rsrc, super::SCRATCH1, brOff));
518 +
                try emit::emit(s.e, encode::bne(rsrc, super::SCRATCH1, brOff));
512 519
                set remaining -= dwordBytes;
513 520
            }
514 521
515 522
            // Copy remaining: 8 bytes, then 4 bytes, then 1 byte at a time.
516 523
            // Before each load/store pair, check whether the offset is
517 524
            // about to exceed the 12-bit signed immediate range. When
518 525
            // it does, advance the base registers by the accumulated
519 526
            // offset and reset to zero.
520 527
            while remaining >= super::DWORD_SIZE {
521 528
                if offset > super::MAX_IMM - super::DWORD_SIZE {
522 -
                    emit::emitAddImm(s.e, rsrc, rsrc, offset);
529 +
                    try emit::emitAddImm(s.e, rsrc, rsrc, offset);
523 530
                    if *rdst <> *rsrc {
524 -
                        emit::emitAddImm(s.e, rdst, rdst, offset);
531 +
                        try emit::emitAddImm(s.e, rdst, rdst, offset);
525 532
                    }
526 533
                    set offset = 0;
527 534
                }
528 535
                if let off = srcReload {
529 -
                    emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off);
530 -
                    emit::emitLd(s.e, super::SCRATCH1, super::SCRATCH1, offset);
536 +
                    try emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off);
537 +
                    try emit::emitLd(s.e, super::SCRATCH1, super::SCRATCH1, offset);
531 538
                } else {
532 -
                    emit::emitLd(s.e, super::SCRATCH1, rsrc, offset);
539 +
                    try emit::emitLd(s.e, super::SCRATCH1, rsrc, offset);
533 540
                }
534 -
                emit::emitSd(s.e, super::SCRATCH1, rdst, offset);
541 +
                try emit::emitSd(s.e, super::SCRATCH1, rdst, offset);
535 542
                set offset += super::DWORD_SIZE;
536 543
                set remaining -= super::DWORD_SIZE;
537 544
            }
538 545
            if remaining >= super::WORD_SIZE {
539 546
                if offset > super::MAX_IMM - super::WORD_SIZE {
540 -
                    emit::emitAddImm(s.e, rsrc, rsrc, offset);
547 +
                    try emit::emitAddImm(s.e, rsrc, rsrc, offset);
541 548
                    if *rdst <> *rsrc {
542 -
                        emit::emitAddImm(s.e, rdst, rdst, offset);
549 +
                        try emit::emitAddImm(s.e, rdst, rdst, offset);
543 550
                    }
544 551
                    set offset = 0;
545 552
                }
546 553
                if let off = srcReload {
547 -
                    emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off);
548 -
                    emit::emitLw(s.e, super::SCRATCH1, super::SCRATCH1, offset);
554 +
                    try emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off);
555 +
                    try emit::emitLw(s.e, super::SCRATCH1, super::SCRATCH1, offset);
549 556
                } else {
550 -
                    emit::emitLw(s.e, super::SCRATCH1, rsrc, offset);
557 +
                    try emit::emitLw(s.e, super::SCRATCH1, rsrc, offset);
551 558
                }
552 -
                emit::emitSw(s.e, super::SCRATCH1, rdst, offset);
559 +
                try emit::emitSw(s.e, super::SCRATCH1, rdst, offset);
553 560
                set offset += super::WORD_SIZE;
554 561
                set remaining -= super::WORD_SIZE;
555 562
            }
556 563
            while remaining > 0 {
557 564
                if offset > super::MAX_IMM - 1 {
558 -
                    emit::emitAddImm(s.e, rsrc, rsrc, offset);
565 +
                    try emit::emitAddImm(s.e, rsrc, rsrc, offset);
559 566
                    if *rdst <> *rsrc {
560 -
                        emit::emitAddImm(s.e, rdst, rdst, offset);
567 +
                        try emit::emitAddImm(s.e, rdst, rdst, offset);
561 568
                    }
562 569
                    set offset = 0;
563 570
                }
564 571
                if let off = srcReload {
565 -
                    emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off);
566 -
                    emit::emitLb(s.e, super::SCRATCH1, super::SCRATCH1, offset);
572 +
                    try emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off);
573 +
                    try emit::emitLb(s.e, super::SCRATCH1, super::SCRATCH1, offset);
567 574
                } else {
568 -
                    emit::emitLb(s.e, super::SCRATCH1, rsrc, offset);
575 +
                    try emit::emitLb(s.e, super::SCRATCH1, rsrc, offset);
569 576
                }
570 -
                emit::emitSb(s.e, super::SCRATCH1, rdst, offset);
577 +
                try emit::emitSb(s.e, super::SCRATCH1, rdst, offset);
571 578
                set offset += 1;
572 579
                set remaining -= 1;
573 580
            }
574 581
            // Restore base registers if they were advanced (never happens
575 582
            // in the both-spilled case since size <= MAX_IMM).
576 583
            if not bothSpilled {
577 584
                let advanced = staticSize as i32 - offset;
578 585
                if advanced <> 0 {
579 -
                    emit::emitAddImm(s.e, rsrc, rsrc, 0 - advanced);
586 +
                    try emit::emitAddImm(s.e, rsrc, rsrc, 0 - advanced);
580 587
                    if *rdst <> *rsrc {
581 -
                        emit::emitAddImm(s.e, rdst, rdst, 0 - advanced);
588 +
                        try emit::emitAddImm(s.e, rdst, rdst, 0 - advanced);
582 589
                    }
583 590
                }
584 591
            }
585 592
        },
586 593
        case il::Instr::Zext { typ, dst, val } => {
587 594
            let rd = getDstReg(s, dst, super::SCRATCH1);
588 -
            let rs = resolveVal(s, super::SCRATCH1, val);
589 -
            emitZext(s.e, rd, rs, typ);
595 +
            let rs = try resolveVal(s, super::SCRATCH1, val);
596 +
            try emitZext(s.e, rd, rs, typ);
590 597
        },
591 598
        case il::Instr::Sext { typ, dst, val } => {
592 599
            let rd = getDstReg(s, dst, super::SCRATCH1);
593 -
            let rs = resolveVal(s, super::SCRATCH1, val);
594 -
            emitSext(s.e, rd, rs, typ);
600 +
            let rs = try resolveVal(s, super::SCRATCH1, val);
601 +
            try emitSext(s.e, rd, rs, typ);
595 602
        },
596 603
        case il::Instr::Ret { val } => {
597 604
            if let v = val {
598 -
                let rs = resolveVal(s, super::SCRATCH1, v);
599 -
                emitMv(s, super::A0, rs);
605 +
                let rs = try resolveVal(s, super::SCRATCH1, v);
606 +
                try emitMv(s, super::A0, rs);
600 607
            }
601 608
            // Skip the jump to epilogue if this RET is in the last block,
602 609
            // since the epilogue immediately follows.
603 610
            if frame.totalSize <> 0 and blockIdx + 1 == frame.epilogueBlock {
604 611
                // Epilogue is the next block; fallthrough is sufficient.
605 612
            } else {
606 -
                emit::emitReturn(s.e, frame);
613 +
                try emit::emitReturn(s.e, frame);
607 614
            }
608 615
        },
609 616
        case il::Instr::Jmp { target, args } => {
610 617
            // Move arguments to target block's parameter registers.
611 -
            emitBlockArgs(s, func, target, args);
618 +
            try emitBlockArgs(s, func, target, args);
612 619
            // Skip branch if target is the next block (fallthrough).
613 620
            if target <> blockIdx + 1 {
614 -
                emit::recordBranch(s.e, target, emit::BranchKind::Jump);
621 +
                try emit::recordBranch(s.e, target, emit::BranchKind::Jump);
615 622
            }
616 623
        },
617 624
        case il::Instr::Br { op, typ, a, b, thenTarget, thenArgs, elseTarget, elseArgs } => {
618 625
            // Use zero register directly for immediate `0` operands.
619 626
            let aIsZero = isZeroImm(a);
620 627
            let bIsZero = isZeroImm(b);
621 628
622 -
            let rs1 = super::ZERO if aIsZero else resolveVal(s, super::SCRATCH1, a);
623 -
            let rs2 = super::ZERO if bIsZero else resolveVal(s, super::SCRATCH2, b);
629 +
            let rs1 = super::ZERO if aIsZero else try resolveVal(s, super::SCRATCH1, a);
630 +
            let rs2 = super::ZERO if bIsZero else try resolveVal(s, super::SCRATCH2, b);
624 631
625 632
            // Normalize sub-word operands so that both registers have the same
626 633
            // canonical representation. Without this, eg. `-1 : i8 ` loaded as
627 634
            // `0xFFFFFFFFFFFFFFFF` and `255 : i8` loaded as `0xFF` would compare
628 635
            // unequal even though they are the same 8-bit pattern.
636 643
            if let case il::CmpOp::Slt = op {
637 644
                set signed = true;
638 645
            }
639 646
            let useSext = cmpUsesSext(typ, signed);
640 647
            if not aIsZero and not isExtendedImm(a, typ, useSext) {
641 -
                emitCmpExt(s.e, rs1, rs1, typ, useSext);
648 +
                try emitCmpExt(s.e, rs1, rs1, typ, useSext);
642 649
            }
643 650
            if not bIsZero and not isExtendedImm(b, typ, useSext) {
644 -
                emitCmpExt(s.e, rs2, rs2, typ, useSext);
651 +
                try emitCmpExt(s.e, rs2, rs2, typ, useSext);
645 652
            }
646 653
            // Block-argument moves must only execute on the taken path.
647 654
            // When `thenArgs` is non-empty, invert the branch so that the
648 655
            // then-moves land on the fall-through (taken) side.
649 656
            //
652 659
            // conditional branch to skip to the *other* target and letting
653 660
            // execution fall through.
654 661
            if thenArgs.len > 0 and elseArgs.len > 0 {
655 662
                panic "selectInstr: both `then` and `else` have block arguments";
656 663
            } else if thenArgs.len > 0 {
657 -
                emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 });
658 -
                emitBlockArgs(s, func, thenTarget, thenArgs);
664 +
                try emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 });
665 +
                try emitBlockArgs(s, func, thenTarget, thenArgs);
659 666
                // Skip trailing jump if then is the next block (fallthrough).
660 667
                if thenTarget <> blockIdx + 1 {
661 -
                    emit::recordBranch(s.e, thenTarget, emit::BranchKind::Jump);
668 +
                    try emit::recordBranch(s.e, thenTarget, emit::BranchKind::Jump);
662 669
                }
663 670
            } else if thenTarget == blockIdx + 1 and elseArgs.len == 0 {
664 671
                // Then is the next block and no else args: invert the
665 672
                // condition to branch to else and fall through to then.
666 -
                emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 });
673 +
                try emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 });
667 674
            } else {
668 -
                emit::recordBranch(s.e, thenTarget, emit::BranchKind::Cond { op, rs1, rs2 });
669 -
                emitBlockArgs(s, func, elseTarget, elseArgs);
675 +
                try emit::recordBranch(s.e, thenTarget, emit::BranchKind::Cond { op, rs1, rs2 });
676 +
                try emitBlockArgs(s, func, elseTarget, elseArgs);
670 677
                // Skip trailing jump if else is the next block (fallthrough).
671 678
                if elseTarget <> blockIdx + 1 {
672 -
                    emit::recordBranch(s.e, elseTarget, emit::BranchKind::Jump);
679 +
                    try emit::recordBranch(s.e, elseTarget, emit::BranchKind::Jump);
673 680
                }
674 681
            }
675 682
        },
676 683
        case il::Instr::Switch { val, defaultTarget, defaultArgs, cases } => {
677 -
            let rs1 = resolveVal(s, super::SCRATCH1, val);
684 +
            let rs1 = try resolveVal(s, super::SCRATCH1, val);
678 685
            // When a case has block args, invert the branch to skip past
679 686
            // the arg moves.
680 687
            for c in cases {
681 -
                emit::loadImm(s.e, super::SCRATCH2, c.value);
688 +
                try emit::loadImm(s.e, super::SCRATCH2, c.value);
682 689
683 690
                if c.args.len > 0 {
684 691
                    let skip = s.nextSynthBlock;
685 692
                    set s.nextSynthBlock = skip + 1;
686 693
687 -
                    emit::recordBranch(s.e, skip, emit::BranchKind::InvertedCond {
694 +
                    try emit::recordBranch(s.e, skip, emit::BranchKind::InvertedCond {
688 695
                        op: il::CmpOp::Eq, rs1, rs2: super::SCRATCH2,
689 696
                    });
690 -
                    emitBlockArgs(s, func, c.target, c.args);
691 -
                    emit::recordBranch(s.e, c.target, emit::BranchKind::Jump);
692 -
                    emit::recordBlock(s.e, skip);
697 +
                    try emitBlockArgs(s, func, c.target, c.args);
698 +
                    try emit::recordBranch(s.e, c.target, emit::BranchKind::Jump);
699 +
                    try emit::recordBlock(s.e, skip);
693 700
                } else {
694 -
                    emit::recordBranch(s.e, c.target, emit::BranchKind::Cond {
701 +
                    try emit::recordBranch(s.e, c.target, emit::BranchKind::Cond {
695 702
                        op: il::CmpOp::Eq, rs1, rs2: super::SCRATCH2,
696 703
                    });
697 704
                }
698 705
            }
699 706
            // Fall through to default.
700 -
            emitBlockArgs(s, func, defaultTarget, defaultArgs);
701 -
            emit::recordBranch(s.e, defaultTarget, emit::BranchKind::Jump);
707 +
            try emitBlockArgs(s, func, defaultTarget, defaultArgs);
708 +
            try emit::recordBranch(s.e, defaultTarget, emit::BranchKind::Jump);
702 709
        },
703 710
        case il::Instr::Unreachable => {
704 -
            emit::emit(s.e, encode::ebreak());
711 +
            try emit::emit(s.e, encode::ebreak());
705 712
        },
706 713
        case il::Instr::Call { retTy, dst, func, args } => {
707 714
            // For indirect calls, save target to scratch register before arg
708 715
            // setup can clobber it.
709 716
            if let case il::Val::Reg(r) = func {
710 -
                let target = getSrcReg(s, r, super::SCRATCH2);
711 -
                emitMv(s, super::SCRATCH2, target);
717 +
                let target = try getSrcReg(s, r, super::SCRATCH2);
718 +
                try emitMv(s, super::SCRATCH2, target);
712 719
            }
713 720
            // Move arguments to A0-A7 using parallel move resolution.
714 -
            assert args.len <= super::ARG_REGS.len, "selectInstr: too many call arguments";
715 -
            emitParallelMoves(s, &super::ARG_REGS[..], args);
721 +
            if args.len > super::ARG_REGS.len {
722 +
                throw super::Error::Capacity;
723 +
            }
724 +
            try emitParallelMoves(s, &super::ARG_REGS[..], args);
716 725
717 726
            // Emit call.
718 727
            match func {
719 728
                case il::Val::FnAddr(name) => {
720 -
                    emit::recordCall(s.e, name);
729 +
                    try emit::recordCall(s.e, name);
721 730
                },
722 731
                case il::Val::Reg(_) => {
723 -
                    emit::emit(s.e, encode::jalr(super::RA, super::SCRATCH2, 0));
732 +
                    try emit::emit(s.e, encode::jalr(super::RA, super::SCRATCH2, 0));
724 733
                },
725 734
                else => {
726 735
                    panic "selectInstr: invalid call target";
727 736
                }
728 737
            }
729 738
            // Move result from A0.
730 739
            if let d = dst {
731 740
                let rd = getDstReg(s, d, super::SCRATCH1);
732 -
                emitMv(s, rd, super::A0);
741 +
                try emitMv(s, rd, super::A0);
733 742
            }
734 743
        },
735 744
        case il::Instr::Ecall { dst, num, a0, a1, a2, a3 } => {
736 745
            // Move arguments using parallel move.
737 746
            // TODO: Can't use slice literals here because the lowerer doesn't
738 747
            // support constant-evaluating struct/union values in them.
739 748
            let ecallDsts: [gen::Reg; 5] = [super::A7, super::A0, super::A1, super::A2, super::A3];
740 749
            let ecallArgs: [il::Val; 5] = [num, a0, a1, a2, a3];
741 750
742 -
            emitParallelMoves(s, &ecallDsts[..], &ecallArgs[..]);
743 -
            emit::emit(s.e, encode::ecall());
751 +
            try emitParallelMoves(s, &ecallDsts[..], &ecallArgs[..]);
752 +
            try emit::emit(s.e, encode::ecall());
744 753
745 754
            // Result in A0.
746 755
            let ecallRd = getDstReg(s, dst, super::SCRATCH1);
747 -
            emitMv(s, ecallRd, super::A0);
756 +
            try emitMv(s, ecallRd, super::A0);
748 757
        },
749 758
        case il::Instr::Ebreak => {
750 -
            emit::emit(s.e, encode::ebreak());
759 +
            try emit::emit(s.e, encode::ebreak());
751 760
        },
752 761
        case il::Instr::MemoryFence => {
753 -
            emit::emit(s.e, encode::fence());
762 +
            try emit::emit(s.e, encode::fence());
754 763
        },
755 764
    }
756 765
}
757 766
758 767
/// Choose the cheapest canonical representation that preserves the comparison.
767 776
    e: &mut emit::Emitter,
768 777
    rd: gen::Reg,
769 778
    rs: gen::Reg,
770 779
    typ: il::Type,
771 780
    useSext: bool
772 -
) {
781 +
) throws (super::Error) {
773 782
    if useSext {
774 -
        emitSext(e, rd, rs, typ);
783 +
        try emitSext(e, rd, rs, typ);
775 784
    } else {
776 -
        emitZext(e, rd, rs, typ);
785 +
        try emitZext(e, rd, rs, typ);
777 786
    }
778 787
}
779 788
780 789
/// Truncate and extend an immediate exactly as its register operand would be.
781 790
fn canonicalCmpImm(imm: i64, typ: il::Type, useSext: bool) -> i64 {
832 841
    return false;
833 842
}
834 843
835 844
/// Select a binary ALU operation, dispatching to the appropriate
836 845
/// instruction pattern based on the operation kind and type.
837 -
unsafe fn selectAluBinOp(s: &mut Selector, op: il::BinOp, typ: il::Type, rd: gen::Reg, rs1: gen::Reg, b: il::Val) {
846 +
unsafe fn selectAluBinOp(s: &mut Selector, op: il::BinOp, typ: il::Type, rd: gen::Reg, rs1: gen::Reg, b: il::Val) throws (super::Error) {
838 847
    match op {
839 848
        case il::BinOp::Add => {
840 849
            if typ == il::Type::W32 {
841 850
                // Inline W32 ADD with immediate optimization.
842 851
                if let case il::Val::Imm(imm) = b {
843 852
                    if encode::isSmallImm64(imm) {
844 -
                        emit::emit(s.e, encode::addiw(rd, rs1, imm as i32));
853 +
                        try emit::emit(s.e, encode::addiw(rd, rs1, imm as i32));
845 854
                        return;
846 855
                    }
847 856
                }
848 -
                let rs2 = resolveVal(s, super::SCRATCH2, b);
849 -
                emit::emit(s.e, encode::addw(rd, rs1, rs2));
857 +
                let rs2 = try resolveVal(s, super::SCRATCH2, b);
858 +
                try emit::emit(s.e, encode::addw(rd, rs1, rs2));
850 859
            } else {
851 -
                selectBinOp(s, rd, rs1, b, BinOp::Add, super::SCRATCH2);
860 +
                try selectBinOp(s, rd, rs1, b, BinOp::Add, super::SCRATCH2);
852 861
            }
853 862
        }
854 863
        case il::BinOp::Sub => {
855 864
            // Optimize subtraction by small immediate: use ADDI with negated value.
856 865
            if let case il::Val::Imm(imm) = b {
857 866
                let neg = -imm;
858 867
                if neg >= super::MIN_IMM as i64 and neg <= super::MAX_IMM as i64 {
859 -
                    emit::emit(s.e,
868 +
                    try emit::emit(s.e,
860 869
                        encode::addiw(rd, rs1, neg as i32)
861 870
                            if typ == il::Type::W32 else
862 871
                        encode::addi(rd, rs1, neg as i32));
863 872
                    return;
864 873
                }
865 874
            }
866 -
            let rs2 = resolveVal(s, super::SCRATCH2, b);
875 +
            let rs2 = try resolveVal(s, super::SCRATCH2, b);
867 876
868 -
            emit::emit(s.e,
877 +
            try emit::emit(s.e,
869 878
                encode::subw(rd, rs1, rs2)
870 879
                    if typ == il::Type::W32 else
871 880
                encode::sub(rd, rs1, rs2));
872 881
        }
873 882
        case il::BinOp::Mul => {
874 883
            // Strength-reduce multiplication by known constants.
875 884
            if let case il::Val::Imm(imm) = b {
876 885
                if imm == 0 {
877 -
                    emit::emit(s.e, encode::mv(rd, super::ZERO));
886 +
                    try emit::emit(s.e, encode::mv(rd, super::ZERO));
878 887
                    return;
879 888
                } else if imm == 1 {
880 -
                    emitMv(s, rd, rs1);
889 +
                    try emitMv(s, rd, rs1);
881 890
                    return;
882 891
                } else if imm == 2 {
883 -
                    emit::emit(s.e, encode::slli(rd, rs1, 1));
892 +
                    try emit::emit(s.e, encode::slli(rd, rs1, 1));
884 893
                    return;
885 894
                } else if imm == 4 {
886 -
                    emit::emit(s.e, encode::slli(rd, rs1, 2));
895 +
                    try emit::emit(s.e, encode::slli(rd, rs1, 2));
887 896
                    return;
888 897
                } else if imm == 8 {
889 -
                    emit::emit(s.e, encode::slli(rd, rs1, 3));
898 +
                    try emit::emit(s.e, encode::slli(rd, rs1, 3));
890 899
                    return;
891 900
                }
892 901
            }
893 -
            let rs2 = resolveVal(s, super::SCRATCH2, b);
894 -
            emit::emit(s.e,
902 +
            let rs2 = try resolveVal(s, super::SCRATCH2, b);
903 +
            try emit::emit(s.e,
895 904
                encode::mulw(rd, rs1, rs2)
896 905
                    if typ == il::Type::W32 else
897 906
                encode::mul(rd, rs1, rs2));
898 907
        }
899 908
        case il::BinOp::Sdiv => {
900 -
            let rs2 = resolveAndTrapIfZero(s, b, typ, true);
901 -
            emit::emit(s.e,
909 +
            let rs2 = try resolveAndTrapIfZero(s, b, typ, true);
910 +
            try emit::emit(s.e,
902 911
                encode::divw(rd, rs1, rs2)
903 912
                    if typ == il::Type::W32 else
904 913
                encode::div(rd, rs1, rs2));
905 914
        }
906 915
        case il::BinOp::Udiv => {
907 -
            let rs2 = resolveAndTrapIfZero(s, b, typ, false);
908 -
            emit::emit(s.e,
916 +
            let rs2 = try resolveAndTrapIfZero(s, b, typ, false);
917 +
            try emit::emit(s.e,
909 918
                encode::divuw(rd, rs1, rs2)
910 919
                    if typ == il::Type::W32 else
911 920
                encode::divu(rd, rs1, rs2));
912 921
        }
913 922
        case il::BinOp::Srem => {
914 -
            let rs2 = resolveAndTrapIfZero(s, b, typ, true);
915 -
            emit::emit(s.e,
923 +
            let rs2 = try resolveAndTrapIfZero(s, b, typ, true);
924 +
            try emit::emit(s.e,
916 925
                encode::remw(rd, rs1, rs2)
917 926
                    if typ == il::Type::W32 else
918 927
                encode::rem(rd, rs1, rs2));
919 928
        }
920 929
        case il::BinOp::Urem => {
921 -
            let rs2 = resolveAndTrapIfZero(s, b, typ, false);
922 -
            emit::emit(s.e,
930 +
            let rs2 = try resolveAndTrapIfZero(s, b, typ, false);
931 +
            try emit::emit(s.e,
923 932
                encode::remuw(rd, rs1, rs2)
924 933
                    if typ == il::Type::W32 else
925 934
                encode::remu(rd, rs1, rs2));
926 935
        }
927 936
        case il::BinOp::And =>
928 -
            selectBinOp(s, rd, rs1, b, BinOp::And, super::SCRATCH2),
937 +
            try selectBinOp(s, rd, rs1, b, BinOp::And, super::SCRATCH2),
929 938
        case il::BinOp::Or =>
930 -
            selectBinOp(s, rd, rs1, b, BinOp::Or, super::SCRATCH2),
939 +
            try selectBinOp(s, rd, rs1, b, BinOp::Or, super::SCRATCH2),
931 940
        case il::BinOp::Xor =>
932 -
            selectBinOp(s, rd, rs1, b, BinOp::Xor, super::SCRATCH2),
941 +
            try selectBinOp(s, rd, rs1, b, BinOp::Xor, super::SCRATCH2),
933 942
        case il::BinOp::Shl =>
934 -
            selectShift(s, rd, rs1, b, ShiftOp::Sll, typ, super::SCRATCH2),
943 +
            try selectShift(s, rd, rs1, b, ShiftOp::Sll, typ, super::SCRATCH2),
935 944
        case il::BinOp::Sshr =>
936 -
            selectShift(s, rd, rs1, b, ShiftOp::Sra, typ, super::SCRATCH2),
945 +
            try selectShift(s, rd, rs1, b, ShiftOp::Sra, typ, super::SCRATCH2),
937 946
        case il::BinOp::Ushr =>
938 -
            selectShift(s, rd, rs1, b, ShiftOp::Srl, typ, super::SCRATCH2),
947 +
            try selectShift(s, rd, rs1, b, ShiftOp::Srl, typ, super::SCRATCH2),
939 948
        case il::BinOp::Eq, il::BinOp::Ne => {
940 -
            let rs2 = resolveVal(s, super::SCRATCH2, b);
949 +
            let rs2 = try resolveVal(s, super::SCRATCH2, b);
941 950
            let useSext = cmpUsesSext(typ, false);
942 -
            emitCmpExt(s.e, rs1, rs1, typ, useSext);
951 +
            try emitCmpExt(s.e, rs1, rs1, typ, useSext);
943 952
            if not isExtendedImm(b, typ, useSext) {
944 -
                emitCmpExt(s.e, rs2, rs2, typ, useSext);
953 +
                try emitCmpExt(s.e, rs2, rs2, typ, useSext);
945 954
            }
946 -
            emit::emit(s.e, encode::xor(rd, rs1, rs2));
955 +
            try emit::emit(s.e, encode::xor(rd, rs1, rs2));
947 956
            if let case il::BinOp::Eq = op {
948 -
                emit::emit(s.e, encode::sltiu(rd, rd, 1));
957 +
                try emit::emit(s.e, encode::sltiu(rd, rd, 1));
949 958
            } else {
950 -
                emit::emit(s.e, encode::sltu(rd, super::ZERO, rd));
959 +
                try emit::emit(s.e, encode::sltu(rd, super::ZERO, rd));
951 960
            }
952 961
        }
953 962
        case il::BinOp::Slt =>
954 -
            selectCmp(s, typ, rd, rs1, b, CmpOp::Slt, false, super::SCRATCH2),
963 +
            try selectCmp(s, typ, rd, rs1, b, CmpOp::Slt, false),
955 964
        case il::BinOp::Ult =>
956 -
            selectCmp(s, typ, rd, rs1, b, CmpOp::Ult, false, super::SCRATCH2),
965 +
            try selectCmp(s, typ, rd, rs1, b, CmpOp::Ult, false),
957 966
        case il::BinOp::Sge =>
958 -
            selectCmp(s, typ, rd, rs1, b, CmpOp::Slt, true, super::SCRATCH2),
967 +
            try selectCmp(s, typ, rd, rs1, b, CmpOp::Slt, true),
959 968
        case il::BinOp::Uge =>
960 -
            selectCmp(s, typ, rd, rs1, b, CmpOp::Ult, true, super::SCRATCH2),
969 +
            try selectCmp(s, typ, rd, rs1, b, CmpOp::Ult, true),
961 970
    }
962 971
}
963 972
964 973
/// Select a unary ALU operation.
965 -
unsafe fn selectAluUnOp(s: &mut Selector, op: il::UnOp, typ: il::Type, rd: gen::Reg, rs: gen::Reg) {
974 +
unsafe fn selectAluUnOp(s: &mut Selector, op: il::UnOp, typ: il::Type, rd: gen::Reg, rs: gen::Reg) throws (super::Error) {
966 975
    match op {
967 976
        case il::UnOp::Neg => {
968 977
            if typ == il::Type::W32 {
969 -
                emit::emit(s.e, encode::subw(rd, super::ZERO, rs));
978 +
                try emit::emit(s.e, encode::subw(rd, super::ZERO, rs));
970 979
            } else {
971 -
                emit::emit(s.e, encode::neg(rd, rs));
980 +
                try emit::emit(s.e, encode::neg(rd, rs));
972 981
            }
973 982
        }
974 983
        case il::UnOp::Not =>
975 -
            emit::emit(s.e, encode::not_(rd, rs)),
984 +
            try emit::emit(s.e, encode::not_(rd, rs)),
976 985
    }
977 986
}
978 987
979 988
/// Select binary operation with immediate optimization.
980 -
unsafe fn selectBinOp(s: &mut Selector, rd: gen::Reg, rs1: gen::Reg, b: il::Val, op: BinOp, scratch: gen::Reg) {
989 +
unsafe fn selectBinOp(s: &mut Selector, rd: gen::Reg, rs1: gen::Reg, b: il::Val, op: BinOp, scratch: gen::Reg) throws (super::Error) {
981 990
    // Try immediate optimization first.
982 991
    if let case il::Val::Imm(imm) = b {
983 992
        if encode::isSmallImm64(imm) {
984 993
            let simm = imm as i32;
985 994
            match op {
986 -
                case BinOp::Add => emit::emit(s.e, encode::addi(rd, rs1, simm)),
987 -
                case BinOp::And => emit::emit(s.e, encode::andi(rd, rs1, simm)),
988 -
                case BinOp::Or  => emit::emit(s.e, encode::ori(rd, rs1, simm)),
989 -
                case BinOp::Xor => emit::emit(s.e, encode::xori(rd, rs1, simm)),
995 +
                case BinOp::Add => try emit::emit(s.e, encode::addi(rd, rs1, simm)),
996 +
                case BinOp::And => try emit::emit(s.e, encode::andi(rd, rs1, simm)),
997 +
                case BinOp::Or  => try emit::emit(s.e, encode::ori(rd, rs1, simm)),
998 +
                case BinOp::Xor => try emit::emit(s.e, encode::xori(rd, rs1, simm)),
990 999
            }
991 1000
            return;
992 1001
        }
993 1002
    }
994 1003
    // Fallback: load into register.
995 -
    let rs2 = resolveVal(s, scratch, b);
1004 +
    let rs2 = try resolveVal(s, scratch, b);
996 1005
    match op {
997 -
        case BinOp::Add => emit::emit(s.e, encode::add(rd, rs1, rs2)),
998 -
        case BinOp::And => emit::emit(s.e, encode::and_(rd, rs1, rs2)),
999 -
        case BinOp::Or  => emit::emit(s.e, encode::or_(rd, rs1, rs2)),
1000 -
        case BinOp::Xor => emit::emit(s.e, encode::xor(rd, rs1, rs2)),
1006 +
        case BinOp::Add => try emit::emit(s.e, encode::add(rd, rs1, rs2)),
1007 +
        case BinOp::And => try emit::emit(s.e, encode::and_(rd, rs1, rs2)),
1008 +
        case BinOp::Or  => try emit::emit(s.e, encode::or_(rd, rs1, rs2)),
1009 +
        case BinOp::Xor => try emit::emit(s.e, encode::xor(rd, rs1, rs2)),
1001 1010
    }
1002 1011
}
1003 1012
1004 1013
/// Select shift operation with immediate optimization.
1005 1014
/// For 32-bit operations, uses the `*w` variants that operate on the lower 32 bits
1006 1015
/// and sign-extend the result.
1007 -
unsafe fn selectShift(s: &mut Selector, rd: gen::Reg, rs1: gen::Reg, b: il::Val, op: ShiftOp, typ: il::Type, scratch: gen::Reg) {
1016 +
unsafe fn selectShift(s: &mut Selector, rd: gen::Reg, rs1: gen::Reg, b: il::Val, op: ShiftOp, typ: il::Type, scratch: gen::Reg) throws (super::Error) {
1008 1017
    let isW32: bool = typ == il::Type::W32;
1009 1018
1010 1019
    // Try immediate optimization first.
1011 1020
    if let case il::Val::Imm(shamt) = b {
1012 1021
        // Keep immediate forms only for encodable shift amounts.
1013 1022
        // Otherwise fall back to register shifts, which naturally mask the count.
1014 1023
        if shamt >= 0 and ((isW32 and shamt < 32) or (not isW32 and shamt < 64)) {
1015 1024
            let sa = shamt as i32;
1016 1025
            if isW32 {
1017 1026
                match op {
1018 -
                    case ShiftOp::Sll => emit::emit(s.e, encode::slliw(rd, rs1, sa)),
1019 -
                    case ShiftOp::Srl => emit::emit(s.e, encode::srliw(rd, rs1, sa)),
1020 -
                    case ShiftOp::Sra => emit::emit(s.e, encode::sraiw(rd, rs1, sa)),
1027 +
                    case ShiftOp::Sll => try emit::emit(s.e, encode::slliw(rd, rs1, sa)),
1028 +
                    case ShiftOp::Srl => try emit::emit(s.e, encode::srliw(rd, rs1, sa)),
1029 +
                    case ShiftOp::Sra => try emit::emit(s.e, encode::sraiw(rd, rs1, sa)),
1021 1030
                }
1022 1031
            } else {
1023 1032
                match op {
1024 -
                    case ShiftOp::Sll => emit::emit(s.e, encode::slli(rd, rs1, sa)),
1025 -
                    case ShiftOp::Srl => emit::emit(s.e, encode::srli(rd, rs1, sa)),
1026 -
                    case ShiftOp::Sra => emit::emit(s.e, encode::srai(rd, rs1, sa)),
1033 +
                    case ShiftOp::Sll => try emit::emit(s.e, encode::slli(rd, rs1, sa)),
1034 +
                    case ShiftOp::Srl => try emit::emit(s.e, encode::srli(rd, rs1, sa)),
1035 +
                    case ShiftOp::Sra => try emit::emit(s.e, encode::srai(rd, rs1, sa)),
1027 1036
                }
1028 1037
            }
1029 1038
            return;
1030 1039
        }
1031 1040
    }
1032 1041
    // Fallback: load into register.
1033 -
    let rs2 = resolveVal(s, scratch, b);
1042 +
    let rs2 = try resolveVal(s, scratch, b);
1034 1043
    if isW32 {
1035 1044
        match op {
1036 -
            case ShiftOp::Sll => emit::emit(s.e, encode::sllw(rd, rs1, rs2)),
1037 -
            case ShiftOp::Srl => emit::emit(s.e, encode::srlw(rd, rs1, rs2)),
1038 -
            case ShiftOp::Sra => emit::emit(s.e, encode::sraw(rd, rs1, rs2)),
1045 +
            case ShiftOp::Sll => try emit::emit(s.e, encode::sllw(rd, rs1, rs2)),
1046 +
            case ShiftOp::Srl => try emit::emit(s.e, encode::srlw(rd, rs1, rs2)),
1047 +
            case ShiftOp::Sra => try emit::emit(s.e, encode::sraw(rd, rs1, rs2)),
1039 1048
        }
1040 1049
    } else {
1041 1050
        match op {
1042 -
            case ShiftOp::Sll => emit::emit(s.e, encode::sll(rd, rs1, rs2)),
1043 -
            case ShiftOp::Srl => emit::emit(s.e, encode::srl(rd, rs1, rs2)),
1044 -
            case ShiftOp::Sra => emit::emit(s.e, encode::sra(rd, rs1, rs2)),
1051 +
            case ShiftOp::Sll => try emit::emit(s.e, encode::sll(rd, rs1, rs2)),
1052 +
            case ShiftOp::Srl => try emit::emit(s.e, encode::srl(rd, rs1, rs2)),
1053 +
            case ShiftOp::Sra => try emit::emit(s.e, encode::sra(rd, rs1, rs2)),
1045 1054
        }
1046 1055
    }
1047 1056
}
1048 1057
1049 1058
/// Resolve parallel moves from IL values to physical destination registers.
1055 1064
/// 1. Identifies "ready" moves.
1056 1065
/// 2. Executes ready moves.
1057 1066
/// 3. Breaks cycles using scratch register.
1058 1067
///
1059 1068
/// Entries with `ZERO` destination are skipped, as they are handled by caller.
1060 -
unsafe fn emitParallelMoves(s: &mut Selector, dsts: &[gen::Reg], args: &[il::Val]) {
1069 +
unsafe fn emitParallelMoves(s: &mut Selector, dsts: &[gen::Reg], args: &[il::Val]) throws (super::Error) {
1061 1070
    let n: u32 = args.len;
1062 1071
    if n == 0 {
1063 1072
        return;
1064 1073
    }
1065 1074
    assert n <= MAX_BLOCK_ARGS, "emitParallelMoves: too many arguments";
1126 1135
                    }
1127 1136
                }
1128 1137
                if isReady {
1129 1138
                    // Execute this move.
1130 1139
                    if isRegMove[i] {
1131 -
                        emitMv(s, dst, srcRegs[i]);
1140 +
                        try emitMv(s, dst, srcRegs[i]);
1132 1141
                    } else {
1133 1142
                        // Load immediate, symbol, or spilled value.
1134 -
                        loadVal(s, dst, args[i]);
1143 +
                        try loadVal(s, dst, args[i]);
1135 1144
                    }
1136 1145
                    set found = true;
1137 1146
                    set pending[i] = false;
1138 1147
                    set numPending -= 1;
1139 1148
1147 1156
            // Break it by saving one source to scratch.
1148 1157
            for i in 0..n {
1149 1158
                if pending[i] and isRegMove[i] {
1150 1159
                    let src = srcRegs[i];
1151 1160
                    // Save this source to scratch.
1152 -
                    emitMv(s, super::SCRATCH1, src);
1161 +
                    try emitMv(s, super::SCRATCH1, src);
1153 1162
                    // Update all pending moves that use this source.
1154 1163
                    for j in 0..n {
1155 1164
                        if pending[j] and isRegMove[j] and srcRegs[j] == src {
1156 1165
                            set srcRegs[j] = super::SCRATCH1;
1157 1166
                        }
1166 1175
/// Emit moves from block arguments to target block's parameter registers.
1167 1176
///
1168 1177
/// Handles spilled destinations directly, then delegates to [`emitParallelMoves`]
1169 1178
/// for the remaining register-to-register parallel move resolution. Edges that
1170 1179
/// would overwrite an unconsumed spill source are unsupported.
1171 -
unsafe fn emitBlockArgs(s: &mut Selector, func: *unsafe il::Fn, target: u32, args: &[il::Val]) {
1180 +
unsafe fn emitBlockArgs(s: &mut Selector, func: *unsafe il::Fn, target: u32, args: &[il::Val]) throws (super::Error) {
1172 1181
    if args.len == 0 {
1173 1182
        return;
1174 1183
    }
1175 1184
    let block = &func.blocks[target];
1176 1185
    assert args.len == block.params.len, "emitBlockArgs: argument/parameter count mismatch";
1177 -
    assert args.len <= MAX_BLOCK_ARGS, "emitBlockArgs: too many block arguments";
1186 +
    if args.len > MAX_BLOCK_ARGS {
1187 +
        throw super::Error::Capacity;
1188 +
    }
1178 1189
1179 1190
    // The parallel-move resolver only handles register destinations. Keep eager
1180 1191
    // stores for independent spill slots, but reject dependencies that would
1181 1192
    // require stack staging rather than silently miscompiling them.
1182 1193
    for arg, i in args {
1221 1232
        // These don't participate in the parallel move algorithm.
1222 1233
        if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, param) {
1223 1234
            if let case il::Val::Undef = arg {
1224 1235
                // Undefined values don't need any move.
1225 1236
            } else {
1226 -
                let rs = resolveVal(s, super::SCRATCH1, arg);
1227 -
                emit::emitSd(s.e, rs, spillBase(s), spillOffset(s, slot));
1237 +
                let rs = try resolveVal(s, super::SCRATCH1, arg);
1238 +
                try emit::emitSd(s.e, rs, spillBase(s), spillOffset(s, slot));
1228 1239
            }
1229 1240
        } else {
1230 1241
            set dsts[i] = getReg(s, param);
1231 1242
        }
1232 1243
    }
1233 -
    emitParallelMoves(s, &dsts[..], args);
1244 +
    try emitParallelMoves(s, &dsts[..], args);
1234 1245
}
1235 1246
1236 1247
/// Select a comparison with immediate optimization.
1237 1248
unsafe fn selectCmp(
1238 1249
    s: &mut Selector,
1239 1250
    typ: il::Type,
1240 1251
    rd: gen::Reg,
1241 1252
    rs1: gen::Reg,
1242 1253
    b: il::Val,
1243 1254
    op: CmpOp,
1244 -
    invert: bool,
1245 -
    scratch: gen::Reg
1246 -
) {
1255 +
    invert: bool
1256 +
) throws (super::Error) {
1247 1257
    let mut signed = false;
1248 1258
    if let case CmpOp::Slt = op {
1249 1259
        set signed = true;
1250 1260
    }
1251 1261
    let useSext = cmpUsesSext(typ, signed);
1252 -
    emitCmpExt(s.e, rs1, rs1, typ, useSext);
1262 +
    try emitCmpExt(s.e, rs1, rs1, typ, useSext);
1253 1263
1254 1264
    // Canonicalizing the immediate can expose an immediate instruction even
1255 1265
    // when the IL value used a different representation for the same width.
1256 1266
    let mut rhs = b;
1257 1267
    if let case il::Val::Imm(imm) = b {
1258 1268
        let canonical = canonicalCmpImm(imm, typ, useSext);
1259 1269
        set rhs = il::Val::Imm(canonical);
1260 1270
        if encode::isSmallImm64(canonical) {
1261 1271
            let simm = canonical as i32;
1262 1272
            match op {
1263 -
                case CmpOp::Slt => emit::emit(s.e, encode::slti(rd, rs1, simm)),
1264 -
                case CmpOp::Ult => emit::emit(s.e, encode::sltiu(rd, rs1, simm)),
1273 +
                case CmpOp::Slt => try emit::emit(s.e, encode::slti(rd, rs1, simm)),
1274 +
                case CmpOp::Ult => try emit::emit(s.e, encode::sltiu(rd, rs1, simm)),
1265 1275
            }
1266 1276
            if invert {
1267 -
                emit::emit(s.e, encode::xori(rd, rd, 1));
1277 +
                try emit::emit(s.e, encode::xori(rd, rd, 1));
1268 1278
            }
1269 1279
            return;
1270 1280
        }
1271 1281
    }
1272 1282
1273 -
    let rs2 = resolveVal(s, scratch, rhs);
1283 +
    let rs2 = try resolveVal(s, super::SCRATCH2, rhs);
1274 1284
    if not isExtendedImm(rhs, typ, useSext) {
1275 -
        emitCmpExt(s.e, rs2, rs2, typ, useSext);
1285 +
        try emitCmpExt(s.e, rs2, rs2, typ, useSext);
1276 1286
    }
1277 1287
    match op {
1278 -
        case CmpOp::Slt => emit::emit(s.e, encode::slt(rd, rs1, rs2)),
1279 -
        case CmpOp::Ult => emit::emit(s.e, encode::sltu(rd, rs1, rs2)),
1288 +
        case CmpOp::Slt => try emit::emit(s.e, encode::slt(rd, rs1, rs2)),
1289 +
        case CmpOp::Ult => try emit::emit(s.e, encode::sltu(rd, rs1, rs2)),
1280 1290
    }
1281 1291
    if invert {
1282 -
        emit::emit(s.e, encode::xori(rd, rd, 1));
1292 +
        try emit::emit(s.e, encode::xori(rd, rd, 1));
1283 1293
    }
1284 1294
}
lib/std/arch/rv64/tests.rad +122 -2
4 4
//! by comparing against known-good values.
5 5
6 6
use std::testing;
7 7
use std::lang::alloc;
8 8
use std::lang::il;
9 +
use std::lang::gen::data;
9 10
use std::lang::gen::bitset;
10 11
use std::lang::gen::regalloc;
11 12
use std::lang::gen::regalloc::liveness;
13 +
use std::arch::rv64;
12 14
use std::collections::dict;
13 15
14 16
use super::encode;
15 17
use super::asm;
16 18
19 +
/// Reusable emitter storage.
17 20
static ASSEMBLY_ARENA_STORAGE: [u8; 16777216] = [0; 16777216];
18 21
static ASSEMBLY_TEXT_STORAGE: [u32; 2] = [0; 2];
19 22
20 23
/// Scratch storage for register limit checks.
21 24
static REGISTER_SCRATCH: [u8; 65536] = [0; 65536];
73 76
        section: asm::Section::Text,
74 77
        offset: super::INSTR_SIZE,
75 78
        isExported: true,
76 79
    };
77 80
78 -
    let mut generator = super::beginProgram(
81 +
    let mut generator = try! super::beginProgram(
79 82
        super::ProgramOptions { entryPatch: super::EntryPatch::None, debug: false },
80 83
        &mut arena
81 84
    );
82 -
    super::addAssembly(
85 +
    try! super::addAssembly(
83 86
        &mut generator,
84 87
        asm::Program {
85 88
            text: &ASSEMBLY_TEXT_STORAGE[..],
86 89
            data: &[],
87 90
            symbols: symbolSlice,
611 614
    try regalloc::spill::analyze(&func, &live, 23, 11, 8, &mut arena) catch {
612 615
        set failed = true;
613 616
    };
614 617
    assert failed;
615 618
}
619 +
620 +
/// Unresolved calls preserve their placeholder instructions and report a symbol error.
621 +
@test unsafe fn testMissingCallSymbol() throws (testing::TestError) {
622 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
623 +
    let mut e = try! rv64::emit::emitter(&mut arena, false);
624 +
    try! rv64::emit::recordCall(&mut e, "missing");
625 +
    let mut failed = false;
626 +
    try rv64::emit::patchCalls(&mut e) catch error {
627 +
        assert error == rv64::Error::Symbol;
628 +
        set failed = true;
629 +
    };
630 +
    assert failed;
631 +
    assert e.code[0] == encode::nop();
632 +
    assert e.code[1] == encode::nop();
633 +
}
634 +
635 +
/// Local jumps outside their immediate range preserve the reserved instruction.
636 +
@test unsafe fn testLocalJumpRange() throws (testing::TestError) {
637 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
638 +
    let mut e = try! rv64::emit::emitter(&mut arena, false);
639 +
    try! rv64::emit::recordBranch(&mut e, 0, rv64::emit::BranchKind::Jump);
640 +
    set e.labels.blockOffsets[0] = 0x200000;
641 +
    set e.labels.blockCount = 1;
642 +
    let mut failed = false;
643 +
    try rv64::emit::patchLocalBranches(&mut e) catch error {
644 +
        assert error == rv64::Error::Relocation;
645 +
        set failed = true;
646 +
    };
647 +
    assert failed;
648 +
    assert e.code[0] == encode::nop();
649 +
}
650 +
651 +
/// Assembly jumps reject targets beyond the J-type displacement range.
652 +
@test unsafe fn testAssemblyJumpRange() throws (testing::TestError) {
653 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
654 +
    let mut e = try! rv64::emit::emitter(&mut arena, false);
655 +
    try! rv64::emit::emit(&mut e, encode::nop());
656 +
    try! rv64::emit::recordJumpAt(&mut e, "far", rv64::ZERO, 0);
657 +
    try! rv64::emit::recordFuncOffsetAt(&mut e, "far", 0x80000);
658 +
    let mut failed = false;
659 +
    try rv64::emit::patchJumps(&mut e) catch error {
660 +
        assert error == rv64::Error::Relocation;
661 +
        set failed = true;
662 +
    };
663 +
    assert failed;
664 +
    assert e.code[0] == encode::nop();
665 +
}
666 +
667 +
/// Missing data symbols preserve address-load placeholders.
668 +
@test unsafe fn testMissingDataSymbol() throws (testing::TestError) {
669 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
670 +
    let mut e = try! rv64::emit::emitter(&mut arena, false);
671 +
    static entries: [dict::Entry; 4] = undefined;
672 +
    let map = try! data::buildMap(&[], &mut entries[..]);
673 +
    try! rv64::emit::recordDataAddrLoad(&mut e, "missing", rv64::A0);
674 +
    let mut failed = false;
675 +
    try rv64::emit::patchAddrLoads(&mut e, &map) catch error {
676 +
        assert error == rv64::Error::Symbol;
677 +
        set failed = true;
678 +
    };
679 +
    assert failed;
680 +
    assert e.code[0] == encode::nop();
681 +
    assert e.code[1] == encode::nop();
682 +
}
683 +
684 +
/// Data addresses outside the supported signed address range are rejected.
685 +
@test unsafe fn testDataAddressRange() throws (testing::TestError) {
686 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
687 +
    let mut e = try! rv64::emit::emitter(&mut arena, false);
688 +
    static entries: [dict::Entry; 4] = undefined;
689 +
    static symbols: [data::DataSym; 1] = [data::DataSym { name: "far", addr: 0x80000000 }];
690 +
    let map = try! data::buildMap(&symbols[..], &mut entries[..]);
691 +
    try! rv64::emit::recordDataAddrLoad(&mut e, "far", rv64::A0);
692 +
    let mut failed = false;
693 +
    try rv64::emit::patchAddrLoads(&mut e, &map) catch error {
694 +
        assert error == rv64::Error::Relocation;
695 +
        set failed = true;
696 +
    };
697 +
    assert failed;
698 +
    assert e.code[0] == encode::nop();
699 +
    assert e.code[1] == encode::nop();
700 +
}
701 +
702 +
/// Data initializers report unresolved symbols before writing the slot.
703 +
@test unsafe fn testDataEmissionSymbol() throws (testing::TestError) {
704 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
705 +
    let e = try! rv64::emit::emitter(&mut arena, false);
706 +
    static entries: [dict::Entry; 4] = undefined;
707 +
    let map = try! data::buildMap(&[], &mut entries[..]);
708 +
    let items = [il::Data { name: "symbol", size: 8, alignment: 8, readOnly: true, isZeroInit: false,
709 +
        values: &[il::DataValue { item: il::DataItem::Sym("missing"), count: 1 }] }];
710 +
    let mut bytes = [0xaa as u8; 8];
711 +
    let mut failed = false;
712 +
    try data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..], true) catch error {
713 +
        assert error == data::Error::Symbol;
714 +
        set failed = true;
715 +
    };
716 +
    assert failed;
717 +
    assert bytes[0] == 0xaa;
718 +
}
719 +
720 +
/// Initialized and zero-filled data start at the requested alignment.
721 +
@test unsafe fn testDataAlignment() throws (testing::TestError) {
722 +
    for zeroInit in [false, true] {
723 +
        let items = [il::Data {
724 +
            name: "aligned", size: 8, alignment: 8, readOnly: true,
725 +
            isZeroInit: zeroInit, values: &[],
726 +
        }];
727 +
        let mut symbols: [data::DataSym; 1] = undefined;
728 +
        let mut count: u32 = 0;
729 +
        let size = try! data::layoutSectionAtOffset(
730 +
            &items[..], &mut symbols[..], &mut count, 16, 1, true
731 +
        );
732 +
        assert size == 16 and count == 1;
733 +
        assert symbols[0].addr == 24;
734 +
    }
735 +
}
lib/std/collections/dict.rad +13 -4
3 3
//! Uses DJB2 hashing with linear probing. The table size must be a power
4 4
//! of two and is provided by the caller via arena-allocated storage.
5 5
6 6
use std::mem;
7 7
8 +
/// Dictionary insertion failure.
9 +
export union Error: Copy {
10 +
    /// The table has reached its supported load.
11 +
    Capacity,
12 +
}
13 +
8 14
/// Hash map entry.
9 15
export record Entry: Copy {
10 16
    /// Key.
11 17
    key: *[u8],
12 18
    /// Associated value.
20 26
    /// Number of occupied entries.
21 27
    count: u32,
22 28
}
23 29
24 30
/// Create a dict backed by the given entry storage.
25 -
/// The storage length must be a power of two.
31 +
/// The storage length must be a power of two and at least two.
26 32
export fn init(entries: *mut [Entry]) -> Dict {
27 33
    for i in 0..entries.len {
28 34
        set entries[i] = Entry { key: &[], value: 0 };
29 35
    }
30 36
    return Dict { entries, count: 0 };
31 37
}
32 38
33 -
/// Insert or update a key-value pair. Panics if the table exceeds 50% load.
34 -
export fn insert(m: &mut Dict, key: *[u8], value: i32) {
39 +
/// Insert or update a key-value pair. Throws if the table exceeds 50% load.
40 +
/// The key must not be empty.
41 +
export fn insert(m: &mut Dict, key: *[u8], value: i32) throws (Error) {
35 42
    let mask = m.entries.len - 1;
36 43
    let mut idx = hash(key) & mask;
37 44
38 45
    loop {
39 46
        let entry = &m.entries[idx];
40 47
        if entry.key.len == 0 {
41 -
            assert m.count < m.entries.len / 2, "dict::insert: table full";
48 +
            if m.count >= m.entries.len / 2 {
49 +
                throw Error::Capacity;
50 +
            }
42 51
            set m.entries[idx] = Entry { key, value };
43 52
            set m.count += 1;
44 53
            return;
45 54
        }
46 55
        if mem::eq(entry.key, key) {
lib/std/lang/gen/data.rad +63 -32
4 4
//! serializing initialized data into binary sections.
5 5
6 6
use std::mem;
7 7
use std::collections::dict;
8 8
use std::lang::il;
9 +
use std::lang::parser;
9 10
use std::lang::gen::labels;
10 11
11 12
/// Maximum number of data symbols.
12 13
export constant MAX_DATA_SYMS: u32 = 8192;
13 14
14 15
/// Size of the data symbol hash table. Must be a power of two
15 16
/// and at least twice the size of [`MAX_DATA_SYMS`].
16 17
export constant DATA_SYM_TABLE_SIZE: u32 = MAX_DATA_SYMS * 2;
17 18
19 +
/// Data section layout or emission failure.
20 +
export union Error: Copy {
21 +
    /// A required symbol is missing.
22 +
    Symbol,
23 +
    /// A symbol table or output buffer is full.
24 +
    Capacity,
25 +
    /// A size or address exceeds the supported range.
26 +
    Overflow,
27 +
}
28 +
18 29
/// Data symbol entry mapping name to address.
19 30
export record DataSym: Copy {
20 31
    /// Symbol name.
21 32
    name: *[u8],
22 33
    /// Absolute address, including data base address.
39 50
    items: &[il::Data],
40 51
    syms: &mut [DataSym],
41 52
    count: &mut u32,
42 53
    base: u32,
43 54
    readOnly: bool
44 -
) -> u32 {
45 -
    return layoutSectionAtOffset(items, syms, count, base, 0, readOnly);
55 +
) -> u32 throws (Error) {
56 +
    return try layoutSectionAtOffset(items, syms, count, base, 0, readOnly);
46 57
}
47 58
48 59
/// Lay out data symbols for a single section starting at [`startOffset`].
49 60
export fn layoutSectionAtOffset(
50 61
    items: &[il::Data],
51 62
    syms: &mut [DataSym],
52 63
    count: &mut u32,
53 64
    base: u32,
54 65
    startOffset: u32,
55 66
    readOnly: bool
56 -
) -> u32 {
67 +
) -> u32 throws (Error) {
57 68
    let mut offset: u32 = startOffset;
58 69
59 70
    // Data requiring sidecar image bytes first.
60 -
    for i in 0..items.len {
61 -
        let data = items[i];
62 -
        if data.readOnly == readOnly and not data.isZeroInit {
63 -
            set offset = mem::alignUp(offset, data.alignment);
64 -
            set syms[*count] = DataSym { name: data.name, addr: base + offset };
65 -
            set *count += 1;
66 -
            set offset += data.size;
67 -
        }
68 -
    }
69 71
    // Zero-initialized data after.
70 -
    for i in 0..items.len {
71 -
        let data = items[i];
72 -
        if data.readOnly == readOnly and data.isZeroInit {
73 -
            set offset = mem::alignUp(offset, data.alignment);
74 -
            set syms[*count] = DataSym { name: data.name, addr: base + offset };
75 -
            set *count += 1;
76 -
            set offset += data.size;
72 +
    for zeroInit in [false, true] {
73 +
        for data in items {
74 +
            if data.readOnly == readOnly and data.isZeroInit == zeroInit {
75 +
                let aligned = try alignOffset(offset, data.alignment);
76 +
                if base as u64 + aligned as u64 + data.size as u64 > parser::U32_MAX as u64 {
77 +
                    throw Error::Overflow;
78 +
                }
79 +
                if *count >= syms.len {
80 +
                    throw Error::Capacity;
81 +
                }
82 +
                set syms[*count] = DataSym { name: data.name, addr: base + aligned };
83 +
                set *count += 1;
84 +
                set offset = aligned + data.size;
85 +
            }
77 86
        }
78 87
    }
79 88
    return offset;
80 89
}
81 90
91 +
/// Align a section offset without unsigned overflow.
92 +
fn alignOffset(offset: u32, alignment: u32) -> u32 throws (Error) {
93 +
    let aligned = mem::checkedAlignUp(offset, alignment) else {
94 +
        throw Error::Overflow;
95 +
    };
96 +
    return aligned;
97 +
}
98 +
82 99
/// Emit data bytes for a single section (read-only or read-write) into `buf`.
83 100
/// Iterates data requiring sidecar image bytes, serializing each data item.
84 101
/// Returns the total number of bytes written.
85 102
export unsafe fn emitSection(
86 103
    items: &[il::Data],
87 104
    dataSymMap: &DataSymMap,
88 105
    fnLabels: &labels::Labels,
89 106
    codeBase: u32,
90 107
    buf: &mut [u8],
91 108
    readOnly: bool
92 -
) -> u32 {
93 -
    return emitSectionAtOffset(items, dataSymMap, fnLabels, codeBase, buf, readOnly, 0);
109 +
) -> u32 throws (Error) {
110 +
    return try emitSectionAtOffset(items, dataSymMap, fnLabels, codeBase, buf, readOnly, 0);
94 111
}
95 112
96 113
/// Emit data bytes for a single section starting at `startOffset`.
97 114
export unsafe fn emitSectionAtOffset(
98 115
    items: &[il::Data],
100 117
    fnLabels: &labels::Labels,
101 118
    codeBase: u32,
102 119
    buf: &mut [u8],
103 120
    readOnly: bool,
104 121
    startOffset: u32
105 -
) -> u32 {
122 +
) -> u32 throws (Error) {
123 +
    if startOffset > buf.len {
124 +
        throw Error::Capacity;
125 +
    }
106 126
    let mut offset: u32 = startOffset;
107 127
108 128
    for i in 0..items.len {
109 129
        let data = items[i];
110 130
        if data.readOnly == readOnly and not data.isZeroInit {
111 -
            set offset = mem::alignUp(offset, data.alignment);
112 -
            assert offset + data.size <= buf.len, "emitSectionAtOffset: buffer overflow";
131 +
            let aligned = try alignOffset(offset, data.alignment);
132 +
            if aligned > buf.len or data.size > buf.len - aligned {
133 +
                throw Error::Capacity;
134 +
            }
135 +
            set offset = aligned;
113 136
            for j in 0..data.values.len {
114 137
                let v = &data.values[j];
115 138
                for _ in 0..v.count {
116 139
                    match v.item {
117 140
                        case il::DataItem::Val { typ, val } => {
120 143
121 144
                            set offset += size;
122 145
                        },
123 146
                        case il::DataItem::Sym(name) => {
124 147
                            let addr = lookupAddr(dataSymMap, name) else {
125 -
                                panic "emitSectionAtOffset: data symbol not found";
148 +
                                throw Error::Symbol;
126 149
                            };
127 150
                            let addr64: u64 = addr as u64;
128 -
                            try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, 8));
151 +
                            try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, @sizeOf(u64)));
129 152
130 153
                            set offset += @sizeOf(u64);
131 154
                        },
132 155
                        case il::DataItem::Fn(name) => {
133 -
                            let addr = codeBase + labels::funcOffset(fnLabels, name) as u32;
134 -
                            let addr64: u64 = addr as u64;
135 -
                            try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, 8));
156 +
                            let functionOffset = dict::get(&fnLabels.funcs, name) else {
157 +
                                throw Error::Symbol;
158 +
                            };
159 +
                            if codeBase as u64 + functionOffset as u64 > parser::U32_MAX as u64 {
160 +
                                throw Error::Overflow;
161 +
                            }
162 +
                            let addr64 = codeBase as u64 + functionOffset as u64;
163 +
                            try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, @sizeOf(u64)));
136 164
137 165
                            set offset += @sizeOf(*u8);
138 166
                        },
139 167
                        case il::DataItem::Str(s) => {
140 168
                            try! mem::copy(&mut buf[offset..], s);
151 179
    }
152 180
    return offset;
153 181
}
154 182
155 183
/// Build a hash-indexed data symbol map from the laid-out symbols.
156 -
/// The `entries` slice must have length `DATA_SYM_TABLE_SIZE`.
157 -
export fn buildMap(syms: *[DataSym], entries: *mut [dict::Entry]) -> DataSymMap {
184 +
/// The table length must be a power of two and at least two.
185 +
/// Throws if the symbol count exceeds half the table length.
186 +
export fn buildMap(syms: *[DataSym], entries: *mut [dict::Entry]) -> DataSymMap throws (Error) {
158 187
    let mut d = dict::init(entries);
159 188
    for i in 0..syms.len {
160 -
        dict::insert(&mut d, syms[i].name, syms[i].addr as i32);
189 +
        try dict::insert(&mut d, syms[i].name, syms[i].addr as i32) catch {
190 +
            throw Error::Capacity;
191 +
        };
161 192
    }
162 193
    return DataSymMap { dict: d, syms };
163 194
}
164 195
165 196
/// Resolve a data symbol to its final absolute address using the hash map.
lib/std/lang/gen/labels.rad +10 -2
9 9
/// Maximum number of functions.
10 10
export constant MAX_FUNCS: u32 = 8192;
11 11
/// Size of the function hash table. Must be a power of two.
12 12
export constant FUNC_TABLE_SIZE: u32 = MAX_FUNCS * 2;
13 13
14 +
/// Block label storage failure.
15 +
export union Error: Copy {
16 +
    /// The block index exceeds the label storage.
17 +
    Capacity,
18 +
}
19 +
14 20
/// Label tracking for code emission.
15 21
export record Labels {
16 22
    /// Block offsets indexed by block index.
17 23
    /// Per-function, reset each function.
18 24
    blockOffsets: *mut [i32],
35 41
export fn resetBlocks(l: &mut Labels) {
36 42
    set l.blockCount = 0;
37 43
}
38 44
39 45
/// Record a block's code offset by its index. O(1).
40 -
export fn recordBlock(l: &mut Labels, blockIdx: u32, offset: i32) {
41 -
    assert blockIdx < l.blockOffsets.len, "recordBlock: block index out of range";
46 +
export fn recordBlock(l: &mut Labels, blockIdx: u32, offset: i32) throws (Error) {
47 +
    if blockIdx >= l.blockOffsets.len {
48 +
        throw Error::Capacity;
49 +
    }
42 50
    set l.blockOffsets[blockIdx] = offset;
43 51
    set l.blockCount += 1;
44 52
}
45 53
46 54
/// Look up a block's byte offset by index. O(1).
lib/std/lang/lower.rad +9 -6
109 109
////////////////////
110 110
111 111
/// Lowering errors are typically unrecoverable since they indicate bugs in
112 112
/// the resolver or malformed AST that should have been caught earlier.
113 113
export union LowerError: Copy {
114 +
    /// The function sink reported an output failure.
115 +
    OutputFailed,
114 116
    /// A node's symbol was not set before lowering.
115 117
    MissingSymbol(*ast::Node),
116 118
    /// A node's type was not set before lowering.
117 119
    MissingType(*ast::Node),
118 120
    /// A node's constant value was not set before lowering.
167 169
}
168 170
169 171
/// Print a LowerError for debugging.
170 172
export fn printError(err: LowerError) {
171 173
    match err {
174 +
        case LowerError::OutputFailed => io::print("OutputFailed"),
172 175
        case LowerError::MissingSymbol(_) => io::print("MissingSymbol"),
173 176
        case LowerError::MissingType(_) => io::print("MissingType"),
174 177
        case LowerError::MissingConst(_) => io::print("MissingConst"),
175 178
        case LowerError::ExpectedOptional => io::print("ExpectedOptional"),
176 179
        case LowerError::ExpectedRecord => io::print("ExpectedRecord"),
268 271
/// Function sink used by lowerers that consume functions as they are produced.
269 272
export record FnSink: Copy {
270 273
    /// Opaque context. It must remain valid for every sink callback.
271 274
    ctx: *unsafe mut opaque,
272 275
    /// Callback invoked for each lowered function.
273 -
    emitFn: unsafe fn(*unsafe mut opaque, *unsafe il::Fn, FnRole),
276 +
    emitFn: unsafe fn(*unsafe mut opaque, *unsafe il::Fn, FnRole) throws (LowerError),
274 277
}
275 278
276 279
/// Destination for functions produced by the lowerer.
277 280
export union FnOutput: Copy {
278 281
    /// Store lowered functions in the provided slice.
356 359
357 360
    return tag;
358 361
}
359 362
360 363
/// Emit one function to the active output.
361 -
unsafe fn emitFunction(self: &mut Lowerer, func: *unsafe il::Fn, role: FnRole) {
364 +
unsafe fn emitFunction(self: &mut Lowerer, func: *unsafe il::Fn, role: FnRole) throws (LowerError) {
362 365
    match self.output {
363 366
        case FnOutput::Accumulate(accumulated) => {
364 367
            let mut fns = accumulated;
365 368
            try! fns.append(func, self.allocator);
366 369
            set self.output = FnOutput::Accumulate(fns);
367 370
        }
368 371
        case FnOutput::Stream(sink) => {
369 -
            sink.emitFn(sink.ctx, func, role);
372 +
            try sink.emitFn(sink.ctx, func, role);
370 373
        }
371 374
    }
372 375
}
373 376
374 377
/// Get the function role for a top-level function declaration.
831 834
    for node in stmtsList {
832 835
        match node.value {
833 836
            case ast::NodeValue::FnDecl(decl) => {
834 837
                if let f = try lowerFnDecl(low, node, decl) {
835 838
                    let role = lowerFnRole(isRoot, decl.attrs);
836 -
                    emitFunction(low, f, role);
839 +
                    try emitFunction(low, f, role);
837 840
                }
838 841
            }
839 842
            case ast::NodeValue::ConstDecl(decl) => {
840 843
                try lowerDataDecl(low, node, decl.value, true);
841 844
            }
845 848
            case ast::NodeValue::InstanceDecl { traitName, targetType, methods } => {
846 849
                try lowerInstanceDecl(low, node, traitName, targetType, methods);
847 850
            }
848 851
            case ast::NodeValue::MethodDecl { name, receiverName, receiverType, sig, body, .. } => {
849 852
                if let f = try lowerMethodDecl(low, node, name, receiverName, sig, body) {
850 -
                    emitFunction(low, f, FnRole::Normal);
853 +
                    try emitFunction(low, f, FnRole::Normal);
851 854
                }
852 855
            }
853 856
            else => {},
854 857
        }
855 858
    }
1114 1117
            throw LowerError::ExpectedIdentifier;
1115 1118
        };
1116 1119
        let qualName = instanceMethodName(self, nil, typeName, mName);
1117 1120
        let func = try lowerMethod(self, methodNode, qualName, receiverName, sig, body)
1118 1121
            else continue;
1119 -
        emitFunction(self, func, FnRole::Normal);
1122 +
        try emitFunction(self, func, FnRole::Normal);
1120 1123
1121 1124
        let method = resolver::findTraitMethod(traitInfo, mName)
1122 1125
            else panic "lowerInstanceDecl: method not found in trait";
1123 1126
1124 1127
        set methodNames[method.index] = qualName;