rv64: Return bounded code-generation failures

0432def4e539cd147de0ea24acd586caedd4fba73c197f45e4722f1279356c8b
Alexis Sellier committed ago 1 parent e237f812
compiler/radiance.rad +3 -3
1011 1011
        case CodegenEntryMode::DefaultEntry => {
1012 1012
            set entryPatch = rv64::EntryPatch::Reserved(nil);
1013 1013
        }
1014 1014
        else => {}
1015 1015
    }
1016 -
    let mut generator = rv64::beginProgram(
1016 +
    let mut generator = try rv64::beginProgram(
1017 1017
        rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug, placement: rv64::image::Placement::Hosted },
1018 1018
        &mut res.arena
1019 -
    );
1019 +
    ) catch { throw error(&["code generation workspace exhausted"]); };
1020 1020
    let mut codegenCtx = codegen::Context {
1021 1021
        generator: &mut generator,
1022 1022
        fnArena: (&mut *fnArena) as *unsafe mut alloc::Arena,
1023 1023
    };
1024 1024
    let mut low = lower::lowerer(
1045 1045
    }
1046 1046
    if let path = codegenOptions.logPath {
1047 1047
        pkgLog(entryPkg, &["generating code", "(", path, ")", ".."]);
1048 1048
    }
1049 1049
    return try rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]) catch {
1050 -
        throw error(&["invalid native image layout"]);
1050 +
        throw error(&["code generation failed: capacity, symbol, relocation, or image layout"]);
1051 1051
    };
1052 1052
}
1053 1053
1054 1054
/// Source exports selected for one binary RIL package.
1055 1055
record PackageExports: Copy {
lib/std/arch/rv64.rad +48 -18
18 18
export mod isel;
19 19
export mod printer;
20 20
export mod asm;
21 21
22 22
@test mod tests;
23 +
@test mod bounds;
23 24
24 25
use std::mem;
25 26
use std::collections::dict;
26 27
use std::lang::il;
27 28
use std::lang::alloc;
29 30
use std::lang::gen::labels;
30 31
use std::lang::gen::regalloc;
31 32
use std::lang::gen::data;
32 33
use std::lang::gen::types;
33 34
35 +
/// Recoverable code-generation failure.
36 +
export union Error: Copy {
37 +
    /// The code-generation or function arena is full.
38 +
    Allocation,
39 +
    /// A fixed output or metadata table is full.
40 +
    Capacity,
41 +
    /// A required symbol is missing or invalid.
42 +
    Symbol,
43 +
    /// A branch or address exceeds its instruction range.
44 +
    Relocation,
45 +
    /// The native image layout is invalid.
46 +
    Image(image::Error),
47 +
    /// Data layout or initialization failed.
48 +
    Data(data::Error),
49 +
}
50 +
34 51
////////////////
35 52
// Registers  //
36 53
////////////////
37 54
38 55
export constant ZERO: gen::Reg = gen::Reg(0);   /// Hard-wired zero.
217 234
    /// Address policy for code and data.
218 235
    placement: image::Placement,
219 236
}
220 237
221 238
/// Begin RV64 code generation for a program's global state.
239 +
/// Restore the arena offset if emitter storage cannot be allocated.
222 240
export unsafe fn beginProgram(
223 241
    options: ProgramOptions,
224 242
    arena: &mut alloc::Arena
225 -
) -> Generator {
226 -
    let mut e = try! emit::emitter(arena, options.debug);
243 +
) -> Generator throws (Error) {
244 +
    let checkpoint = alloc::save(arena);
245 +
    let mut e = try emit::emitter(arena, options.debug) catch {
246 +
        alloc::restore(arena, checkpoint);
247 +
        throw Error::Allocation;
248 +
    };
227 249
228 250
    // Emit placeholder entry jump when requested.
229 251
    // We'll patch this at the end once we know where the function is.
230 252
    match options.entryPatch {
231 253
        case EntryPatch::Reserved(_) => {
241 263
        placement: options.placement,
242 264
    };
243 265
}
244 266
245 267
/// Generate code for one IL function.
268 +
/// Record failures in the emitter and restore the function arena offset.
246 269
export unsafe fn generateFunction(
247 270
    generator: &mut Generator,
248 271
    func: *unsafe il::Fn,
249 272
    arena: &mut alloc::Arena
250 273
) {
251 -
    if func.isExtern {
274 +
    if generator.e.error <> nil or func.isExtern {
252 275
        return;
253 276
    }
254 277
    let checkpoint = alloc::save(arena);
255 278
    let config = targetConfig();
256 -
    let ralloc = try! regalloc::allocate(func, &config, arena);
279 +
    let ralloc = try regalloc::allocate(func, &config, arena) catch {
280 +
        alloc::restore(arena, checkpoint);
281 +
        set generator.e.error = Error::Allocation;
282 +
        return;
283 +
    };
257 284
258 285
    isel::selectFn(&mut generator.e, &ralloc, func);
259 286
260 287
    // Reclaim unused memory after instruction selection.
261 288
    alloc::restore(arena, checkpoint);
313 340
    globalData: &[il::Data],
314 341
    storage: Storage,
315 342
    roDataPrefix: *[u8],
316 343
    roDataBuf: &mut [u8],
317 344
    rwDataBuf: &mut [u8]
318 -
) -> Program throws (image::Error) {
345 +
) -> Program throws (Error) {
346 +
    try emit::check(&generator.e);
319 347
    let mut roBase: u64 = RO_DATA_BASE as u64;
320 348
    let mut rwBase: u64 = RW_DATA_BASE as u64;
321 349
    match generator.placement {
322 350
        case image::Placement::Physical { roData, rwData, .. } => {
323 351
            set roBase = roData;
330 358
    let case Storage { dataSyms: symbolBuf, dataSymEntries } = storage
331 359
        else panic "expected code generation storage";
332 360
    let mut dataSymCount: u32 = 0;
333 361
    let roLayoutSize = try data::layoutSectionAtOffset(
334 362
        globalData, symbolBuf, &mut dataSymCount, roBase, roDataPrefix.len, true
335 -
    ) catch { throw image::Error::Size; };
336 -
    let rwLayoutSize = try data::layoutSection(globalData, symbolBuf, &mut dataSymCount, rwBase, false) catch { throw image::Error::Size; };
363 +
    ) catch err { throw Error::Data(err); };
364 +
    let rwLayoutSize = try data::layoutSection(globalData, symbolBuf, &mut dataSymCount, rwBase, false) catch err { throw Error::Data(err); };
337 365
338 366
    let dataSyms = &symbolBuf[..dataSymCount];
339 -
    let dataSymMap = data::buildMap(dataSyms, dataSymEntries);
367 +
    let dataSymMap = try data::buildMap(dataSyms, dataSymEntries) catch err { throw Error::Data(err); };
340 368
    if roBase > 0xffffffffffffffff - roLayoutSize as u64 - 7 {
341 -
        throw image::Error::Overflow;
369 +
        throw Error::Image(image::Error::Overflow);
342 370
    }
343 371
    let mut codeBase: u64 = (roBase + roLayoutSize as u64 + 7) & ~7;
344 372
    let mut entry = codeBase;
345 373
    match generator.placement {
346 374
        case image::Placement::Physical { code, entry: address, .. } => {
354 382
        entry,
355 383
        code: image::Segment { address: codeBase, initialized: codeBytes, memory: codeBytes },
356 384
        roData: image::Segment { address: roBase, initialized: 0, memory: roLayoutSize },
357 385
        rwData: image::Segment { address: rwBase, initialized: 0, memory: rwLayoutSize },
358 386
    };
359 -
    try image::validate(layout);
387 +
    try image::validate(layout) catch err { throw Error::Image(err); };
360 388
361 389
    match generator.entryPatch {
362 390
        case EntryPatch::Reserved(targetName) => {
363 391
            let target = targetName else {
364 -
                panic "finishProgram: entry jump reserved without default function";
392 +
                throw Error::Symbol;
365 393
            };
366 -
            let offset = emit::branchOffsetToFunc(&generator.e, 0, target);
394 +
            let offset = emit::branchOffsetToFunc(&mut generator.e, 0, target);
367 395
            let s = emit::splitImm(offset);
368 396
369 397
            emit::patch(&mut generator.e, 0, encode::auipc(SCRATCH1, s.hi));
370 398
            emit::patch(&mut generator.e, 1, encode::jalr(ZERO, SCRATCH1, s.lo));
371 399
        }
374 402
    // Patch function calls and address loads now that all functions are emitted.
375 403
    emit::patchJumps(&mut generator.e);
376 404
    emit::patchCalls(&mut generator.e);
377 405
    try emit::patchAddrLoads(&mut generator.e, &dataSymMap, codeBase);
378 406
407 +
    try emit::check(&generator.e);
408 +
379 409
    // Emit data sections.
380 -
    assert roDataPrefix.len <= roDataBuf.len, "finishProgram: rodata prefix buffer overflow";
410 +
    if roDataPrefix.len > roDataBuf.len { throw Error::Capacity; }
381 411
    try! mem::copy(roDataBuf, roDataPrefix);
382 412
383 -
    let roDataSize = data::emitSectionAtOffset(
413 +
    let roDataSize = try data::emitSectionAtOffset(
384 414
        globalData, &dataSymMap, &generator.e.labels, codeBase, roDataBuf, true, roDataPrefix.len
385 -
    );
386 -
    let rwDataSize = data::emitSection(
415 +
    ) catch err { throw Error::Data(err); };
416 +
    let rwDataSize = try data::emitSection(
387 417
        globalData, &dataSymMap, &generator.e.labels, codeBase, rwDataBuf, false
388 -
    );
418 +
    ) catch err { throw Error::Data(err); };
389 419
    set layout.roData.initialized = roDataSize;
390 420
    set layout.rwData.initialized = rwDataSize;
391 -
    try image::validate(layout);
421 +
    try image::validate(layout) catch err { throw Error::Image(err); };
392 422
    return Program {
393 423
        code: emit::getCode(&generator.e),
394 424
        funcs: &generator.e.funcs[..],
395 425
        roDataSize,
396 426
        rwDataSize,
lib/std/arch/rv64/bounds.rad added +192 -0
1 +
//! Recoverable backend capacity and relocation checks.
2 +
3 +
use std::testing;
4 +
use std::io;
5 +
use std::lang::alloc;
6 +
use std::lang::il;
7 +
use std::lang::gen::data;
8 +
use std::lang::gen::labels;
9 +
use std::lang::gen::bitset;
10 +
use std::lang::gen::regalloc;
11 +
use std::collections::dict;
12 +
use super::emit;
13 +
use super::encode;
14 +
15 +
/// Reusable emitter allocation storage.
16 +
static MEMORY: [u8; 16777216] = [0; 16777216];
17 +
/// Function and liveness test storage.
18 +
static SCRATCH: [u8; 65536] = [0; 65536];
19 +
20 +
/// Dictionary storage for bounded map tests.
21 +
unsafe static ENTRIES: [dict::Entry; 4] = undefined;
22 +
23 +
/// Build a non-debug generator with a fixed code address.
24 +
unsafe fn generator(arena: &mut alloc::Arena) -> super::Generator {
25 +
    return try! super::beginProgram(super::ProgramOptions {
26 +
        entryPatch: super::EntryPatch::None, debug: false,
27 +
        placement: super::image::Placement::Physical {
28 +
            code: 0x80000000, roData: 0x80001000, rwData: 0x80002000, entry: 0x80000000,
29 +
        },
30 +
    }, arena);
31 +
}
32 +
33 +
/// Exhaust each emitter storage class and ensure writes stop at the first error.
34 +
@test unsafe fn emissionCapacity() throws (testing::TestError) {
35 +
    for kind in 0..8 {
36 +
        let mut arena = alloc::new(&mut MEMORY[..]);
37 +
        let mut e = try! emit::emitter(&mut arena, false);
38 +
        match kind {
39 +
            case 0 => { set e.code = &mut e.code[..0]; emit::emit(&mut e, encode::nop()); },
40 +
            case 1 => {
41 +
                set e.pendingBranches.len = 0; set e.pendingBranches.cap = 0;
42 +
                emit::recordBranch(&mut e, 0, emit::BranchKind::Jump);
43 +
            },
44 +
            case 2 => {
45 +
                set e.pendingCalls.len = 0; set e.pendingCalls.cap = 0;
46 +
                emit::recordCall(&mut e, "p::call");
47 +
            },
48 +
            case 3 => {
49 +
                set e.pendingJumps.len = 0; set e.pendingJumps.cap = 0;
50 +
                emit::recordJumpAt(&mut e, "p::jump", super::ZERO, 0);
51 +
            },
52 +
            case 4 => {
53 +
                set e.pendingAddrLoads.len = 0; set e.pendingAddrLoads.cap = 0;
54 +
                emit::recordDataAddrLoad(&mut e, "p::data", super::A0);
55 +
            },
56 +
            case 5 => {
57 +
                set e.funcs.len = 0; set e.funcs.cap = 0;
58 +
                emit::recordFunc(&mut e, "p::call");
59 +
            },
60 +
            case 6 => {
61 +
                let entries = &mut ENTRIES[..2];
62 +
                set e.labels.funcs = dict::init(&mut entries[..]);
63 +
                emit::recordFuncOffset(&mut e, "p::first");
64 +
                emit::recordFuncOffset(&mut e, "p::second");
65 +
            },
66 +
            else => { emit::recordSrcLoc(&mut e, il::SrcLoc { moduleId: 0, offset: 0 }); },
67 +
        }
68 +
        try testing::expect(e.error == super::Error::Capacity);
69 +
        let count = e.codeLen;
70 +
        emit::emit(&mut e, encode::ebreak());
71 +
        try testing::expect(e.codeLen == count);
72 +
        let mut rejected = false;
73 +
        try emit::check(&e) catch err {
74 +
            try testing::expect(err == super::Error::Capacity); set rejected = true;
75 +
        };
76 +
        try testing::expect(rejected);
77 +
    }
78 +
}
79 +
80 +
/// Missing labels and long jumps return errors without corrupting instructions.
81 +
@test unsafe fn relocationFailures() throws (testing::TestError) {
82 +
    let mut arena = alloc::new(&mut MEMORY[..]);
83 +
    let mut e = try! emit::emitter(&mut arena, false);
84 +
    emit::recordCall(&mut e, "missing");
85 +
    emit::patchCalls(&mut e);
86 +
    try check(e.error == super::Error::Symbol, "missing function");
87 +
    alloc::reset(&mut arena);
88 +
    set e = try! emit::emitter(&mut arena, false);
89 +
    emit::recordBranch(&mut e, 0, emit::BranchKind::Jump);
90 +
    labels::recordBlock(&mut e.labels, 0, 0x200000);
91 +
    emit::patchLocalBranches(&mut e);
92 +
    try check(e.error == super::Error::Relocation, "long branch");
93 +
    try check(e.code[0] == encode::nop(), "rejected branch unchanged");
94 +
    alloc::reset(&mut arena);
95 +
    set e = try! emit::emitter(&mut arena, false);
96 +
    let blockCount = e.labels.blockOffsets.len;
97 +
    emit::recordBlock(&mut e, blockCount);
98 +
    try check(e.error == super::Error::Capacity, "block capacity");
99 +
}
100 +
101 +
/// Arena setup and per-function failures restore their saved offsets for reuse.
102 +
@test unsafe fn arenaRecovery() throws (testing::TestError) {
103 +
    let small = &mut SCRATCH[..64];
104 +
    let mut arena = alloc::new(&mut small[..]);
105 +
    set arena.offset = 8;
106 +
    let mut failed = false;
107 +
    try super::beginProgram(super::ProgramOptions {
108 +
        entryPatch: super::EntryPatch::None, debug: false, placement: super::image::Placement::Hosted,
109 +
    }, &mut arena) catch err {
110 +
        try check(err == super::Error::Allocation, "generator allocation"); set failed = true;
111 +
    };
112 +
    try check(failed and arena.offset == 8, "generator arena restored");
113 +
    let mut code = alloc::new(&mut MEMORY[..]);
114 +
    let mut gen = generator(&mut code);
115 +
    let mut instructions = [
116 +
        il::Instr::Copy { dst: il::Reg { n: 0 }, val: il::Val::Imm(1) },
117 +
        il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 0 }) },
118 +
    ];
119 +
    let func = il::Fn {
120 +
        name: "p::one", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
121 +
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }],
122 +
    };
123 +
    super::generateFunction(&mut gen, &func, &mut arena);
124 +
    try check(gen.e.error == super::Error::Allocation and arena.offset == 8, "function arena restored");
125 +
    alloc::reset(&mut code);
126 +
    set gen = generator(&mut code);
127 +
    let mut scratch = alloc::new(&mut SCRATCH[..]);
128 +
    set scratch.offset = 16;
129 +
    super::generateFunction(&mut gen, &func, &mut scratch);
130 +
    try check(gen.e.error == nil and gen.e.codeLen > 0 and scratch.offset == 16, "function retry");
131 +
    alloc::reset(&mut code);
132 +
    set gen = generator(&mut code);
133 +
    set instructions[0] = il::Instr::Copy { dst: il::Reg { n: 8192 }, val: il::Val::Imm(1) };
134 +
    super::generateFunction(&mut gen, &func, &mut scratch);
135 +
    try check(gen.e.error == super::Error::Allocation and scratch.offset == 16, "SSA capacity");
136 +
}
137 +
138 +
/// Spill candidate storage fails explicitly when too many values are live.
139 +
@test unsafe fn registerStorage() throws (testing::TestError) {
140 +
    let mut arena = alloc::new(&mut SCRATCH[..]);
141 +
    let mut liveSet = try! bitset::allocate(&mut arena, 257);
142 +
    for i in 0..257 { bitset::put(&mut liveSet, i); }
143 +
    let mut out = [liveSet];
144 +
    let live = regalloc::liveness::LiveInfo {
145 +
        liveIn: &mut [], liveOut: &mut out[..], defs: &mut [], uses: &mut [], blockCount: 1, maxReg: 257,
146 +
    };
147 +
    let func = il::Fn {
148 +
        name: "p::pressure", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
149 +
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut [], locs: &[], preds: &[], loopDepth: 0 }],
150 +
    };
151 +
    let mut failed = false;
152 +
    try regalloc::spill::analyze(&func, &live, 23, 11, 8, &mut arena) catch {
153 +
        set failed = true;
154 +
    };
155 +
    try testing::expect(failed);
156 +
}
157 +
158 +
/// Data output and symbol maps reject insufficient or ambiguous storage.
159 +
@test unsafe fn dataStorage() throws (testing::TestError) {
160 +
    let mut arena = alloc::new(&mut MEMORY[..]);
161 +
    let e = try! emit::emitter(&mut arena, false);
162 +
    let syms = &[data::DataSym { name: "p::data", addr: 0x80002000 }];
163 +
    let entries = &mut ENTRIES[..2];
164 +
    let map = try! data::buildMap(syms, &mut entries[..]);
165 +
    let items = &[il::Data {
166 +
        name: "p::data", size: 8, alignment: 8, readOnly: false, isZeroInit: false,
167 +
        values: &[il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: 1 }, count: 1 }],
168 +
    }];
169 +
    let mut bytes: [u8; 8] = [255; 8];
170 +
    let mut failed: u32 = 0;
171 +
    try data::emitSection(items, &map, &e.labels, 0x80000000, &mut bytes[..7], false) catch err {
172 +
        try testing::expect(err == data::Error::Capacity); set failed += 1;
173 +
    };
174 +
    try data::buildMap(syms, &mut entries[..1]) catch err {
175 +
        try testing::expect(err == data::Error::Capacity); set failed += 1;
176 +
    };
177 +
    let larger = &mut ENTRIES[..4];
178 +
    unsafe static duplicate: [data::DataSym; 2] = undefined;
179 +
    set duplicate = [syms[0], syms[0]];
180 +
    try data::buildMap(&duplicate[..], &mut larger[..]) catch err {
181 +
        try testing::expect(err == data::Error::Symbol); set failed += 1;
182 +
    };
183 +
    try testing::expect(failed == 3);
184 +
    let length = try data::emitSection(items, &map, &e.labels, 0x80000000, &mut bytes[..], false)
185 +
        catch { throw testing::TestError::Failed; };
186 +
    try testing::expect(length == 8 and bytes[0] == 1);
187 +
}
188 +
189 +
/// Name the failed invariant before returning to the test runner.
190 +
fn check(condition: bool, name: *[u8]) throws (testing::TestError) {
191 +
    if not condition { io::printLn(name); throw testing::TestError::Failed; }
192 +
}
lib/std/arch/rv64/emit.rad +94 -36
94 94
    offset: i32,
95 95
}
96 96
97 97
/// Emission context. Tracks state during code generation.
98 98
export record Emitter {
99 -
    /// Allocator for growing append-backed emitter lists.
100 -
    allocator: alloc::Allocator,
99 +
    /// First failure. Further emission stops until the workspace is rebuilt.
100 +
    error: ?super::Error,
101 101
    /// Data address loads use the current domain package-state table.
102 102
    sharedData: bool,
103 103
    /// Emitted instructions storage.
104 104
    code: *mut [u32],
105 105
    /// Current number of emitted instructions.
196 196
    if debug {
197 197
        set debugEntries = try alloc::allocSlice(
198 198
            arena, @sizeOf(types::DebugEntry), @alignOf(types::DebugEntry), MAX_DEBUG_ENTRIES
199 199
        ) as *mut [types::DebugEntry];
200 200
    }
201 -
    let pendingBranchesBuf = pendingBranches as *mut [PendingBranch];
202 -
    let pendingCallsBuf = pendingCalls as *mut [PendingCall];
203 -
    let pendingJumpsBuf = pendingJumps as *mut [PendingJump];
204 -
    let pendingAddrLoadsBuf = pendingAddrLoads as *mut [PendingAddrLoad];
205 -
    let funcsBuf = funcs as *mut [types::FuncAddr];
201 +
    let mut pendingBranchesBuf = pendingBranches as *mut [PendingBranch];
202 +
    let mut pendingCallsBuf = pendingCalls as *mut [PendingCall];
203 +
    let mut pendingJumpsBuf = pendingJumps as *mut [PendingJump];
204 +
    let mut pendingAddrLoadsBuf = pendingAddrLoads as *mut [PendingAddrLoad];
205 +
    let mut funcsBuf = funcs as *mut [types::FuncAddr];
206 +
    set pendingBranchesBuf.len = 0;
207 +
    set pendingCallsBuf.len = 0;
208 +
    set pendingJumpsBuf.len = 0;
209 +
    set pendingAddrLoadsBuf.len = 0;
210 +
    set funcsBuf.len = 0;
206 211
    return Emitter {
207 -
        allocator: alloc::arenaAllocator(arena),
212 +
        error: nil,
208 213
        sharedData: false,
209 214
        code: code as *mut [u32],
210 215
        codeLen: 0,
211 -
        pendingBranches: &mut pendingBranchesBuf[..0],
212 -
        pendingCalls: &mut pendingCallsBuf[..0],
213 -
        pendingJumps: &mut pendingJumpsBuf[..0],
214 -
        pendingAddrLoads: &mut pendingAddrLoadsBuf[..0],
216 +
        pendingBranches: pendingBranchesBuf,
217 +
        pendingCalls: pendingCallsBuf,
218 +
        pendingJumps: pendingJumpsBuf,
219 +
        pendingAddrLoads: pendingAddrLoadsBuf,
215 220
        labels: labels::init(blockOffsets as *mut [i32], funcEntries as *mut [dict::Entry]),
216 -
        funcs: &mut funcsBuf[..0],
221 +
        funcs: funcsBuf,
217 222
        debugEntries,
218 223
        debugEntriesLen: 0,
219 224
    };
220 225
}
221 226
223 228
// Emission Helpers  //
224 229
///////////////////////
225 230
226 231
/// Emit a single instruction.
227 232
export fn emit(e: &mut Emitter, instr: u32) {
228 -
    assert e.codeLen < e.code.len, "emit: code buffer full";
233 +
    if e.error <> nil { return; }
234 +
    if e.codeLen == e.code.len { set e.error = super::Error::Capacity; return; }
229 235
    set e.code[e.codeLen] = instr;
230 236
    set e.codeLen += 1;
231 237
}
232 238
233 239
/// 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);
240 +
export fn branchOffsetToFunc(e: &mut Emitter, srcIndex: u32, name: *[u8]) -> i32 {
241 +
    if e.error <> nil { return 0; }
242 +
    let target = dict::get(&e.labels.funcs, name) else {
243 +
        set e.error = super::Error::Symbol; return 0;
244 +
    };
245 +
    return target - srcIndex as i32 * super::INSTR_SIZE;
236 246
}
237 247
238 248
/// Patch an instruction at a given index.
239 249
export fn patch(e: &mut Emitter, index: u32, instr: u32) {
250 +
    if e.error <> nil { return; }
251 +
    if index >= e.codeLen { set e.error = super::Error::Capacity; return; }
240 252
    set e.code[index] = instr;
241 253
}
242 254
243 255
/// Record a block's address for branch resolution.
244 256
export fn recordBlock(e: &mut Emitter, blockIdx: u32) {
245 -
    assert e.codeLen <= MAX_CODE_LEN;
257 +
    if e.error <> nil { return; }
258 +
    if e.codeLen > MAX_CODE_LEN or blockIdx >= e.labels.blockOffsets.len {
259 +
        set e.error = super::Error::Capacity; return;
260 +
    }
246 261
    labels::recordBlock(&mut e.labels, blockIdx, e.codeLen as i32 * super::INSTR_SIZE);
247 262
}
248 263
249 264
/// Record a function's code offset for call resolution.
250 265
export fn recordFuncOffset(e: &mut Emitter, name: *[u8]) {
252 267
    recordFuncOffsetAt(e, name, codeLen);
253 268
}
254 269
255 270
/// Record a function's code offset at `index` for call resolution.
256 271
export fn recordFuncOffsetAt(e: &mut Emitter, name: *[u8], index: u32) {
257 -
    assert index <= MAX_CODE_LEN;
272 +
    if e.error <> nil { return; }
273 +
    if index > MAX_CODE_LEN { set e.error = super::Error::Capacity; return; }
274 +
    if name.len == 0 { set e.error = super::Error::Symbol; return; }
275 +
    if e.labels.funcs.count >= e.labels.funcs.entries.len / 2 and dict::get(&e.labels.funcs, name) == nil {
276 +
        set e.error = super::Error::Capacity; return;
277 +
    }
258 278
    dict::insert(&mut e.labels.funcs, name, index as i32 * super::INSTR_SIZE);
259 279
}
260 280
261 281
/// Record a function's start position for printing.
262 282
export fn recordFunc(e: &mut Emitter, name: *[u8]) {
264 284
    recordFuncAt(e, name, codeLen);
265 285
}
266 286
267 287
/// Record a function's start position at `index` for printing.
268 288
export fn recordFuncAt(e: &mut Emitter, name: *[u8], index: u32) {
269 -
    e.funcs.append(types::FuncAddr { name, index }, e.allocator);
289 +
    if e.error <> nil { return; }
290 +
    if e.funcs.len == e.funcs.cap { set e.error = super::Error::Capacity; return; }
291 +
    let count = e.funcs.len;
292 +
    unsafe { set e.funcs.len = count + 1; }
293 +
    set e.funcs[count] = types::FuncAddr { name, index };
270 294
}
271 295
272 296
/// Record a local branch needing later patching.
273 297
/// Unconditional jumps use a single slot (J-type, +-1MB range).
274 298
/// Conditional branches use two slots (B-type has only +-4KB range,
275 299
/// so large functions may need the inverted-branch + JAL fallback).
276 300
export fn recordBranch(e: &mut Emitter, targetBlock: u32, kind: BranchKind) {
277 -
    e.pendingBranches.append(PendingBranch {
301 +
    if e.error <> nil { return; }
302 +
    if e.pendingBranches.len == e.pendingBranches.cap { set e.error = super::Error::Capacity; return; }
303 +
    let count = e.pendingBranches.len;
304 +
    unsafe { set e.pendingBranches.len = count + 1; }
305 +
    set e.pendingBranches[count] = PendingBranch {
278 306
        index: e.codeLen,
279 307
        target: targetBlock,
280 308
        kind: kind,
281 -
    }, e.allocator);
309 +
    };
282 310
283 311
    emit(e, encode::nop()); // First slot, always needed.
284 312
285 313
    match kind {
286 314
        case BranchKind::Jump => {},
290 318
291 319
/// Record a function call needing later patching.
292 320
/// Emits placeholder instructions that will be patched later.
293 321
/// Uses two slots to support long-distance calls.
294 322
export fn recordCall(e: &mut Emitter, target: *[u8]) {
295 -
    e.pendingCalls.append(PendingCall {
323 +
    if e.error <> nil { return; }
324 +
    if e.pendingCalls.len == e.pendingCalls.cap { set e.error = super::Error::Capacity; return; }
325 +
    let count = e.pendingCalls.len;
326 +
    unsafe { set e.pendingCalls.len = count + 1; }
327 +
    set e.pendingCalls[count] = PendingCall {
296 328
        index: e.codeLen,
297 329
        target,
298 -
    }, e.allocator);
330 +
    };
299 331
300 332
    emit(e, encode::nop()); // Placeholder for AUIPC.
301 333
    emit(e, encode::nop()); // Placeholder for JALR.
302 334
}
303 335
304 336
/// Record a jump emitted by assembly that needs whole-program patching.
305 337
export fn recordJumpAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) {
306 -
    e.pendingJumps.append(PendingJump {
338 +
    if e.error <> nil { return; }
339 +
    if e.pendingJumps.len == e.pendingJumps.cap { set e.error = super::Error::Capacity; return; }
340 +
    let count = e.pendingJumps.len;
341 +
    unsafe { set e.pendingJumps.len = count + 1; }
342 +
    set e.pendingJumps[count] = PendingJump {
307 343
        index,
308 344
        target,
309 345
        rd,
310 -
    }, e.allocator);
346 +
    };
311 347
}
312 348
313 349
/// Record a function address load needing later patching.
314 350
/// Emits placeholder instructions that will be patched to load the function's address.
315 351
/// Uses two slots to compute long-distance addresses.
321 357
    emit(e, encode::nop()); // Placeholder for ADDI.
322 358
}
323 359
324 360
/// Record a function address load already reserved by assembly.
325 361
export fn recordAddrLoadAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) {
326 -
    e.pendingAddrLoads.append(PendingAddrLoad {
362 +
    if e.error <> nil { return; }
363 +
    if e.pendingAddrLoads.len == e.pendingAddrLoads.cap { set e.error = super::Error::Capacity; return; }
364 +
    let count = e.pendingAddrLoads.len;
365 +
    unsafe { set e.pendingAddrLoads.len = count + 1; }
366 +
    set e.pendingAddrLoads[count] = PendingAddrLoad {
327 367
        index,
328 368
        target,
329 369
        rd: rd,
330 370
        isData: false,
331 -
    }, e.allocator);
371 +
    };
332 372
}
333 373
334 374
/// Record a data address load needing later patching.
335 375
/// Reserves two instructions for a PC-relative address load.
336 376
export fn recordDataAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) {
337 -
    e.pendingAddrLoads.append(PendingAddrLoad {
377 +
    if e.error <> nil { return; }
378 +
    if e.pendingAddrLoads.len == e.pendingAddrLoads.cap { set e.error = super::Error::Capacity; return; }
379 +
    let count = e.pendingAddrLoads.len;
380 +
    unsafe { set e.pendingAddrLoads.len = count + 1; }
381 +
    set e.pendingAddrLoads[count] = PendingAddrLoad {
338 382
        index: e.codeLen,
339 383
        target,
340 384
        rd: rd,
341 385
        isData: true,
342 -
    }, e.allocator);
386 +
    };
343 387
344 388
    emit(e, encode::nop()); // Address-load upper instruction.
345 389
    emit(e, encode::nop()); // Address-load lower instruction.
346 390
    if e.sharedData {
347 391
        emit(e, encode::nop()); // Package offset addition.
354 398
/// Called after each function.
355 399
///
356 400
/// Uses two-instruction sequences: short branches use `branch` and `nop`,
357 401
/// long branches use inverted branch  and `jal` or `auipc` and `jalr`.
358 402
export unsafe fn patchLocalBranches(e: &mut Emitter) {
403 +
    if e.error <> nil { return; }
359 404
    for i in 0..e.pendingBranches.len {
360 405
        let p = e.pendingBranches[i];
406 +
        if p.target >= e.labels.blockCount { set e.error = super::Error::Symbol; return; }
361 407
        let offset = labels::branchToBlock(&e.labels, p.index, p.target, super::INSTR_SIZE);
362 408
        match p.kind {
363 409
            case BranchKind::Cond { op, rs1, rs2 } => {
364 410
                if encode::isBranchImm(offset) {
365 411
                    patch(e, p.index, encodeCondBranch(op, rs1, rs2, offset));
366 412
                    patch(e, p.index + 1, encode::nop());
367 413
                } else {
368 414
                    let adj = offset - super::INSTR_SIZE;
415 +
                    if not encode::isJumpImm(adj) { set e.error = super::Error::Relocation; return; }
369 416
                    patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, super::INSTR_SIZE * 2));
370 417
                    patch(e, p.index + 1, encode::jal(super::ZERO, adj));
371 418
                }
372 419
            },
373 420
            case BranchKind::InvertedCond { op, rs1, rs2 } => {
374 421
                if encode::isBranchImm(offset) {
375 422
                    patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, offset));
376 423
                    patch(e, p.index + 1, encode::nop());
377 424
                } else {
378 425
                    let adj = offset - super::INSTR_SIZE;
426 +
                    if not encode::isJumpImm(adj) { set e.error = super::Error::Relocation; return; }
379 427
                    patch(e, p.index, encodeCondBranch(op, rs1, rs2, super::INSTR_SIZE * 2));
380 428
                    patch(e, p.index + 1, encode::jal(super::ZERO, adj));
381 429
                }
382 430
            },
383 431
            case BranchKind::Jump => {
384 432
                // Single-slot jump (J-type, +-1MB range).
385 -
                assert encode::isJumpImm(offset), "patchLocalBranches: jump offset too large";
433 +
                if not encode::isJumpImm(offset) { set e.error = super::Error::Relocation; return; }
386 434
                patch(e, p.index, encode::jal(super::ZERO, offset));
387 435
            },
388 436
        }
389 437
    }
390 -
    set e.pendingBranches = &mut e.pendingBranches[..0];
438 +
    set e.pendingBranches.len = 0;
391 439
}
392 440
393 441
/// Encode a conditional branch instruction.
394 442
fn encodeCondBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 {
395 443
    match op {
414 462
/// Called after all functions have been generated.
415 463
export fn patchCalls(e: &mut Emitter) {
416 464
    for i in 0..e.pendingCalls.len {
417 465
        let p = e.pendingCalls[i];
418 466
        let offset = branchOffsetToFunc(e, p.index, p.target);
467 +
        if offset > 0x7ffff7ff { set e.error = super::Error::Relocation; return; }
419 468
        let s = splitImm(offset);
420 469
421 470
        // `AUIPC scratch, hi(offset)`.
422 471
        patch(e, p.index, encode::auipc(super::SCRATCH1, s.hi));
423 472
        // `JALR ra, scratch, lo(offset)`.
429 478
export fn patchJumps(e: &mut Emitter) {
430 479
    for i in 0..e.pendingJumps.len {
431 480
        let p = e.pendingJumps[i];
432 481
        let offset = branchOffsetToFunc(e, p.index, p.target);
433 482
434 -
        assert encode::isJumpImm(offset), "patchJumps: jump offset too large";
483 +
        if not encode::isJumpImm(offset) { set e.error = super::Error::Relocation; return; }
435 484
        patch(e, p.index, encode::jal(p.rd, offset));
436 485
    }
437 486
}
438 487
439 488
/// Patch all pending function and data address loads.
440 489
/// Called after all functions have been generated and data layout is known.
441 -
export fn patchAddrLoads(e: &mut Emitter, dataSymMap: &data::DataSymMap, codeBase: u64) throws (super::image::Error) {
490 +
export fn patchAddrLoads(e: &mut Emitter, dataSymMap: &data::DataSymMap, codeBase: u64) throws (super::Error) {
491 +
    try check(e);
442 492
    for i in 0..e.pendingAddrLoads.len {
443 493
        let p = e.pendingAddrLoads[i];
444 494
        if p.isData {
445 495
            let addr = data::lookupAddr(dataSymMap, p.target) else {
446 -
                panic "patchAddrLoads: data symbol not found";
496 +
                throw super::Error::Symbol;
447 497
            };
448 498
            let offset = super::image::displacement(codeBase + p.index as u64 * 4, addr) else {
449 -
                throw super::image::Error::Relocation;
499 +
                throw super::Error::Relocation;
450 500
            };
451 501
            let s = splitImm(offset);
452 502
453 503
            patch(e, p.index, encode::auipc(p.rd, s.hi));
454 504
            patch(e, p.index + 1, encode::addi(p.rd, p.rd, s.lo));
455 505
456 506
            continue;
457 507
        }
458 508
459 509
        let offset = branchOffsetToFunc(e, p.index, p.target);
510 +
        if offset > 0x7ffff7ff { set e.error = super::Error::Relocation; return; }
460 511
        let s = splitImm(offset);
461 512
        // `AUIPC rd, hi(offset)`.
462 513
        patch(e, p.index, encode::auipc(p.rd, s.hi));
463 514
        // `ADDI rd, rd, lo(offset)`.
464 515
        patch(e, p.index + 1, encode::addi(p.rd, p.rd, s.lo));
465 516
    }
517 +
    try check(e);
466 518
}
467 519
468 520
/////////////////////////
469 521
// Immediate Handling  //
470 522
/////////////////////////
735 787
}
736 788
737 789
/// Record a debug entry mapping the current PC to a source location.
738 790
/// Deduplicates consecutive entries with the same location.
739 791
export fn recordSrcLoc(e: &mut Emitter, loc: il::SrcLoc) {
792 +
    if e.error <> nil { return; }
740 793
    let pc = e.codeLen * super::INSTR_SIZE as u32;
741 794
742 795
    // Skip if this is the same location as the previous entry.
743 796
    if e.debugEntriesLen > 0 {
744 797
        let prev = &e.debugEntries[e.debugEntriesLen - 1];
745 798
        if prev.offset == loc.offset and prev.moduleId == loc.moduleId {
746 799
            return;
747 800
        }
748 801
    }
749 -
    assert e.debugEntriesLen < e.debugEntries.len, "recordSrcLoc: debug entry buffer full";
802 +
    if e.debugEntriesLen == e.debugEntries.len { set e.error = super::Error::Capacity; return; }
750 803
    set e.debugEntries[e.debugEntriesLen] = types::DebugEntry {
751 804
        pc,
752 805
        moduleId: loc.moduleId,
753 806
        offset: loc.offset,
754 807
    };
757 810
758 811
/// Get debug entries as a slice.
759 812
export fn getDebugEntries(e: &Emitter) -> *[types::DebugEntry] {
760 813
    return &e.debugEntries[..e.debugEntriesLen];
761 814
}
815 +
816 +
/// Return the first emission failure before any generated output is published.
817 +
export fn check(e: &Emitter) throws (super::Error) {
818 +
    if let error = e.error { throw error; }
819 +
}
lib/std/arch/rv64/image/tests.rad +2 -2
87 87
}
88 88
89 89
/// Check high physical placement, zero-fill extents, and 64-bit data relocations.
90 90
@test unsafe fn nativeProgram() throws (testing::TestError) {
91 91
    let mut arena = alloc::new(&mut MEMORY[..]);
92 -
    let mut generator = rv64::beginProgram(rv64::ProgramOptions {
92 +
    let mut generator = try! rv64::beginProgram(rv64::ProgramOptions {
93 93
        entryPatch: rv64::EntryPatch::None, debug: false,
94 94
        placement: image::Placement::Physical {
95 95
            code: 0x180000000, roData: 0x180001000, rwData: 0x180002000, entry: 0x180000000,
96 96
        },
97 97
    }, &mut arena);
151 151
}
152 152
153 153
/// Keep declared symbol extents and alignment gaps in initialized data.
154 154
@test unsafe fn initializedExtents() throws (testing::TestError) {
155 155
    let mut arena = alloc::new(&mut MEMORY[..]);
156 -
    let mut generator = rv64::beginProgram(rv64::ProgramOptions {
156 +
    let mut generator = try! rv64::beginProgram(rv64::ProgramOptions {
157 157
        entryPatch: rv64::EntryPatch::None, debug: false,
158 158
        placement: image::Placement::Physical {
159 159
            code: 0x80000000, roData: 0x80001000, rwData: 0x80002000, entry: 0x80000000,
160 160
        },
161 161
    }, &mut arena);
lib/std/arch/rv64/isel.rad +15 -7
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 {
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 +
                        if alignment == 0 or (alignment & (alignment - 1)) <> 0 or sz < 0 { return nil; }
288 +
                        let aligned = (offset as u64 + alignment as u64 - 1) & ~(alignment as u64 - 1);
289 +
                        if aligned > 0x7fff0000 or sz as u64 > 0x7fff0000 - aligned { return nil; }
290 +
                        set offset = (aligned + sz as u64) as i32;
289 291
                    } else {
290 292
                        set isDynamic = true;
291 293
                    }
292 294
                },
293 295
                else => {},
301 303
export unsafe fn selectFn(
302 304
    e: &mut emit::Emitter,
303 305
    ralloc: &regalloc::AllocResult,
304 306
    func: *unsafe il::Fn
305 307
) {
308 +
    if e.error <> nil { return; }
306 309
    // Reset block offsets for this function.
307 310
    labels::resetBlocks(&mut e.labels);
308 311
    // Pre-scan for constant-sized reserves to promote to fixed frame slots.
309 -
    let reserveInfo = computeReserveInfo(func);
312 +
    let reserveInfo = computeReserveInfo(func) else { set e.error = super::Error::Capacity; return; };
313 +
    if reserveInfo.size as u64 + ralloc.spill.frameSize as u64 > 0x7fffff00 {
314 +
        set e.error = super::Error::Capacity; return;
315 +
    }
310 316
    let isLeaf = func.isLeaf;
311 317
    // Compute frame layout from spill slots, reserve slots, and used callee-saved registers.
312 318
    let frame = emit::computeFrame(
313 319
        ralloc.spill.frameSize + reserveInfo.size,
314 320
        ralloc.usedCalleeSaved,
350 356
        }
351 357
    }
352 358
353 359
    // Emit each block.
354 360
    for i in 0..func.blocks.len {
361 +
        if s.e.error <> nil { return; }
355 362
        selectBlock(&mut s, i, &func.blocks[i], &frame, func);
356 363
    }
357 364
    // Emit epilogue.
358 365
    emit::emitEpilogue(s.e, &frame);
359 366
    // Patch local branches now that all blocks are emitted.
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 {
382 +
        if s.e.error <> nil { return; }
375 383
        // Record debug location before emitting machine instructions.
376 384
        if hasLocs {
377 385
            emit::recordSrcLoc(s.e, block.locs[i]);
378 386
        }
379 387
        set s.pendingSpill = nil;
709 717
            if let case il::Val::Reg(r) = func {
710 718
                let target = getSrcReg(s, r, super::SCRATCH2);
711 719
                emitMv(s, super::SCRATCH2, target);
712 720
            }
713 721
            // Move arguments to A0-A7 using parallel move resolution.
714 -
            assert args.len <= super::ARG_REGS.len, "selectInstr: too many call arguments";
722 +
            if args.len > super::ARG_REGS.len { set s.e.error = super::Error::Capacity; return; }
715 723
            emitParallelMoves(s, &super::ARG_REGS[..], args);
716 724
717 725
            // Emit call.
718 726
            match func {
719 727
                case il::Val::FnAddr(name) => {
1060 1068
unsafe fn emitParallelMoves(s: &mut Selector, dsts: &[gen::Reg], args: &[il::Val]) {
1061 1069
    let n: u32 = args.len;
1062 1070
    if n == 0 {
1063 1071
        return;
1064 1072
    }
1065 -
    assert n <= MAX_BLOCK_ARGS, "emitParallelMoves: too many arguments";
1073 +
    if n > MAX_BLOCK_ARGS { set s.e.error = super::Error::Capacity; return; }
1066 1074
    // Source registers for each arg.
1067 1075
    let mut srcRegs: [gen::Reg; MAX_BLOCK_ARGS] = [super::ZERO; MAX_BLOCK_ARGS];
1068 1076
    // If this is a register-to-register move.
1069 1077
    let mut isRegMove: [bool; MAX_BLOCK_ARGS] = [false; MAX_BLOCK_ARGS];
1070 1078
    // If this move still needs to be executed.
1172 1180
    if args.len == 0 {
1173 1181
        return;
1174 1182
    }
1175 1183
    let block = &func.blocks[target];
1176 1184
    assert args.len == block.params.len, "emitBlockArgs: argument/parameter count mismatch";
1177 -
    assert args.len <= MAX_BLOCK_ARGS, "emitBlockArgs: too many block arguments";
1185 +
    if args.len > MAX_BLOCK_ARGS { set s.e.error = super::Error::Capacity; return; }
1178 1186
1179 1187
    // The parallel-move resolver only handles register destinations. Keep eager
1180 1188
    // stores for independent spill slots, but reject dependencies that would
1181 1189
    // require stack staging rather than silently miscompiling them.
1182 1190
    for arg, i in args {
lib/std/arch/rv64/shared.rad +7 -2
15 15
/// Maximum number of resident package slots in a domain state table.
16 16
export constant MAX_PACKAGES: u32 = 256;
17 17
18 18
/// Package linking or instance storage failure.
19 19
export union Error: Copy {
20 +
    /// Reusable backend generation failed.
21 +
    Codegen(super::Error),
20 22
    /// A caller-supplied output buffer is full.
21 23
    Capacity,
22 24
    /// A symbol is absent, duplicated, or has the wrong kind.
23 25
    Symbol,
24 26
    /// An address or data extent is outside the supported range.
239 241
                },
240 242
                case il::DataItem::Str(s) => { set width = s.len; },
241 243
                case il::DataItem::Undef => {},
242 244
            }
243 245
            if width > 0 and value.count > (end - offset) / width { throw Error::Range; }
246 +
            if width == 0 { continue; }
244 247
            for _ in 0..value.count {
245 248
                match value.item {
246 249
                    case il::DataItem::Str(s) => { try! mem::copy(&mut bytes[offset..offset + width], s); },
247 250
                    else => { integer(bytes, offset, number, width); },
248 251
                }
264 267
    if slot >= MAX_PACKAGES { throw Error::Range; }
265 268
    if (codeAddress & 3) <> 0 { throw Error::Alignment; }
266 269
    for imported, i in imports {
267 270
        if lookup(&imports[..i], imported.name) <> nil { throw Error::Symbol; }
268 271
    }
269 -
    let mut generator = super::beginProgram(super::ProgramOptions {
272 +
    let mut generator = try super::beginProgram(super::ProgramOptions {
270 273
        entryPatch: super::EntryPatch::None, debug: false, placement: image::Placement::Hosted,
271 -
    }, arena);
274 +
    }, arena) catch err { throw Error::Codegen(err); };
272 275
    set generator.e.sharedData = true;
273 276
    for func in input.program.fns { super::generateFunction(&mut generator, func, scratch); }
277 +
    try emit::check(&generator.e) catch err { throw Error::Codegen(err); };
274 278
    if codeAddress > 0xffffffffffffffff - generator.e.codeLen as u64 * 4 { throw Error::Range; }
275 279
    let mut symbols: u32 = 0;
276 280
    for func in &generator.e.funcs[..] {
277 281
        try define(symbolStorage, &mut symbols, func.name, Target::Function(codeAddress + func.index as u64 * 4));
278 282
    }
286 290
        try define(symbolStorage, &mut symbols, item.name, Target::Data(DataRef { slot, offset: item.addr as u32 }));
287 291
    }
288 292
    let local = &symbolStorage[..symbols];
289 293
    for symbol in local { if lookup(imports, symbol.name) <> nil { throw Error::Symbol; } }
290 294
    try link(&mut generator.e, codeAddress, local, imports);
295 +
    try emit::check(&generator.e) catch err { throw Error::Codegen(err); };
291 296
    let size = try template(input.program.data, local, imports, templateStorage, relocationStorage);
292 297
    if input.exports.len > exportStorage.len { throw Error::Capacity; }
293 298
    for exported, i in input.exports {
294 299
        let target = try resolve(local, &[], exported.name);
295 300
        match exported.kind {
lib/std/arch/rv64/tests.rad +1 -1
35 35
        section: asm::Section::Text,
36 36
        offset: super::INSTR_SIZE,
37 37
        isExported: true,
38 38
    };
39 39
40 -
    let mut generator = super::beginProgram(
40 +
    let mut generator = try! super::beginProgram(
41 41
        super::ProgramOptions { entryPatch: super::EntryPatch::None, debug: false, placement: super::image::Placement::Hosted },
42 42
        &mut arena
43 43
    );
44 44
    super::addAssembly(
45 45
        &mut generator,
lib/std/lang/alloc.rad +6 -4
37 37
/// appropriate type and initializing the memory.
38 38
export fn alloc(arena: &mut Arena, size: u32, alignment: u32) -> *mut opaque throws (AllocError) {
39 39
    assert alignment > 0;
40 40
    assert size > 0;
41 41
42 -
    let aligned = mem::alignUp(arena.offset, alignment);
43 -
    let newOffset = aligned + size;
44 -
45 -
    if newOffset > arena.data.len as u32 {
42 +
    let aligned64 = (arena.offset as u64 + alignment as u64 - 1) & ~(alignment as u64 - 1);
43 +
    if aligned64 > arena.data.len as u64 or size as u64 > arena.data.len as u64 - aligned64 {
46 44
        throw AllocError::OutOfMemory;
47 45
    }
46 +
    let aligned = aligned64 as u32;
47 +
    let newOffset = aligned + size;
48 +
48 49
    let base: *mut u8 = &mut arena.data[aligned];
49 50
    set arena.offset = newOffset;
50 51
51 52
    return base as *mut opaque;
52 53
}
97 98
/// Throws `AllocError` if the arena is exhausted.
98 99
export unsafe fn allocSlice(arena: &mut Arena, size: u32, alignment: u32, count: u32) -> *mut [opaque] throws (AllocError) {
99 100
    if count == 0 {
100 101
        return &mut [];
101 102
    }
103 +
    if size > 0xffffffff / count { throw AllocError::OutOfMemory; }
102 104
    let ptr = try alloc(arena, size * count, alignment);
103 105
104 106
    return @sliceOf(ptr, count);
105 107
}
106 108
lib/std/lang/alloc/tests.rad +17 -0
118 118
    try testing::expect(super::used(&arena) == 24);
119 119
120 120
    // Verify the pointers are distinct.
121 121
    try testing::expect(p1 as u64 <> p2 as u64);
122 122
}
123 +
124 +
/// Large counts and offsets must fail before arithmetic wraps or storage changes.
125 +
@test unsafe fn testAllocOverflow() throws (testing::TestError) {
126 +
    static bytes: [u8; 64] = [0; 64];
127 +
    let mut arena = super::new(&mut bytes[..]);
128 +
    set arena.offset = 8;
129 +
    let mut failed: u32 = 0;
130 +
    try super::allocSlice(&mut arena, 8, 8, 0x20000000) catch { set failed += 1; };
131 +
    try super::alloc(&mut arena, 0xffffffff, 8) catch { set failed += 1; };
132 +
    try testing::expect(failed == 2 and arena.offset == 8);
133 +
    set arena.offset = 0xfffffff8;
134 +
    try super::alloc(&mut arena, 16, 16) catch { set failed += 1; };
135 +
    try testing::expect(failed == 3 and arena.offset == 0xfffffff8);
136 +
    set arena.offset = 8;
137 +
    let storage = try super::alloc(&mut arena, 8, 8) catch { throw testing::TestError::Failed; };
138 +
    try testing::expect(arena.offset == 16);
139 +
}
lib/std/lang/gen/data.rad +30 -10
15 15
/// and at least twice the size of [`MAX_DATA_SYMS`].
16 16
export constant DATA_SYM_TABLE_SIZE: u32 = MAX_DATA_SYMS * 2;
17 17
18 18
/// A data section cannot fit its address or symbol storage.
19 19
export union Error: Copy {
20 +
    /// A required data or function symbol is missing or duplicated.
21 +
    Symbol,
20 22
    /// The symbol array is full.
21 23
    Capacity,
22 24
    /// A section size or address would overflow.
23 25
    Overflow,
24 26
    /// A data alignment is zero or is not a power of two.
110 112
    dataSymMap: &DataSymMap,
111 113
    fnLabels: &labels::Labels,
112 114
    codeBase: u64,
113 115
    buf: &mut [u8],
114 116
    readOnly: bool
115 -
) -> u32 {
116 -
    return emitSectionAtOffset(items, dataSymMap, fnLabels, codeBase, buf, readOnly, 0);
117 +
) -> u32 throws (Error) {
118 +
    return try emitSectionAtOffset(items, dataSymMap, fnLabels, codeBase, buf, readOnly, 0);
117 119
}
118 120
119 121
/// Emit data bytes for a single section starting at `startOffset`.
120 122
export unsafe fn emitSectionAtOffset(
121 123
    items: &[il::Data],
123 125
    fnLabels: &labels::Labels,
124 126
    codeBase: u64,
125 127
    buf: &mut [u8],
126 128
    readOnly: bool,
127 129
    startOffset: u32
128 -
) -> u32 {
130 +
) -> u32 throws (Error) {
129 131
    let mut offset: u32 = startOffset;
132 +
    if offset > buf.len { throw Error::Capacity; }
130 133
131 134
    for i in 0..items.len {
132 135
        let data = items[i];
133 136
        if data.readOnly == readOnly and not data.isZeroInit {
134 137
            let start = offset;
135 -
            set offset = mem::alignUp(offset, data.alignment);
136 -
            assert offset <= buf.len and data.size <= buf.len - offset, "emitSectionAtOffset: buffer overflow";
138 +
            if data.alignment == 0 or (data.alignment & (data.alignment - 1)) <> 0 { throw Error::Alignment; }
139 +
            let aligned = (offset as u64 + data.alignment as u64 - 1) & ~(data.alignment as u64 - 1);
140 +
            if aligned > buf.len as u64 { throw Error::Capacity; }
141 +
            set offset = aligned as u32;
142 +
            if data.size > buf.len - offset { throw Error::Capacity; }
137 143
            let end = offset + data.size;
138 144
            for j in start..end { set buf[j] = 0; }
139 145
            for j in 0..data.values.len {
140 146
                let v = &data.values[j];
147 +
                let mut width: u32 = 1;
148 +
                match v.item {
149 +
                    case il::DataItem::Val { typ, .. } => { set width = il::typeSize(typ); },
150 +
                    case il::DataItem::Sym(_), il::DataItem::Fn(_) => { set width = 8; },
151 +
                    case il::DataItem::Str(bytes) => { set width = bytes.len; },
152 +
                    else => {},
153 +
                }
154 +
                if width > 0 and v.count > (end - offset) / width { throw Error::Overflow; }
155 +
                if width == 0 { continue; }
141 156
                for _ in 0..v.count {
142 157
                    match v.item {
143 158
                        case il::DataItem::Val { typ, val } => {
144 159
                            let size = il::typeSize(typ);
145 160
                            try! mem::copy(&mut buf[offset..], @sliceOf(&val as &u8, size));
146 161
147 162
                            set offset += size;
148 163
                        },
149 164
                        case il::DataItem::Sym(name) => {
150 165
                            let addr = lookupAddr(dataSymMap, name) else {
151 -
                                panic "emitSectionAtOffset: data symbol not found";
166 +
                                throw Error::Symbol;
152 167
                            };
153 168
                            let addr64: u64 = addr as u64;
154 169
                            try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, 8));
155 170
156 171
                            set offset += @sizeOf(u64);
157 172
                        },
158 173
                        case il::DataItem::Fn(name) => {
159 -
                            let addr = codeBase + labels::funcOffset(fnLabels, name) as u64;
174 +
                            let fnOffset = dict::get(&fnLabels.funcs, name) else { throw Error::Symbol; };
175 +
                            if fnOffset < 0 or codeBase > 0xffffffffffffffff - fnOffset as u64 { throw Error::Overflow; }
176 +
                            let addr = codeBase + fnOffset as u64;
160 177
                            let addr64: u64 = addr as u64;
161 178
                            try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, 8));
162 179
163 180
                            set offset += @sizeOf(*u8);
164 181
                        },
171 188
                            set offset += 1;
172 189
                        },
173 190
                    }
174 191
                }
175 192
            }
176 -
            assert offset <= end, "emitSectionAtOffset: initializer exceeds symbol size";
177 193
            set offset = end;
178 194
        }
179 195
    }
180 196
    return offset;
181 197
}
182 198
183 199
/// Build a hash-indexed data symbol map from the laid-out symbols.
184 -
/// The `entries` slice must have length `DATA_SYM_TABLE_SIZE`.
185 -
export fn buildMap(syms: *[DataSym], entries: *mut [dict::Entry]) -> DataSymMap {
200 +
/// The entry count must be a power of two, at least twice the symbol count.
201 +
export fn buildMap(syms: *[DataSym], entries: *mut [dict::Entry]) -> DataSymMap throws (Error) {
202 +
    if entries.len == 0 or (entries.len & (entries.len - 1)) <> 0 or syms.len > entries.len / 2 {
203 +
        throw Error::Capacity;
204 +
    }
186 205
    let mut d = dict::init(entries);
187 206
    for i in 0..syms.len {
207 +
        if syms[i].name.len == 0 or dict::get(&d, syms[i].name) <> nil { throw Error::Symbol; }
188 208
        dict::insert(&mut d, syms[i].name, i as i32);
189 209
    }
190 210
    return DataSymMap { dict: d, syms };
191 211
}
192 212
lib/std/lang/gen/regalloc/liveness.rad +2 -1
89 89
            if let dst = il::instrDst(block.instrs[i]) {
90 90
                set maxReg = maxRegNum(dst.n, maxReg);
91 91
            }
92 92
        }
93 93
    }
94 -
    assert maxReg <= MAX_SSA_REGS, "analyze: maximum SSA registers exceeded";
94 +
    if maxReg > MAX_SSA_REGS { throw alloc::AllocError::OutOfMemory; }
95 95
    // Allocate per-block bitsets.
96 96
    let liveIn = try alloc::allocRawSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *unsafe mut [bitset::Bitset];
97 97
    let liveOut = try alloc::allocRawSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *unsafe mut [bitset::Bitset];
98 98
    let defs = try alloc::allocRawSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *unsafe mut [bitset::Bitset];
99 99
    let uses = try alloc::allocRawSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *unsafe mut [bitset::Bitset];
190 190
    set *max = maxRegNum(reg.n, *max);
191 191
}
192 192
193 193
/// Return the larger of n+1 and current.
194 194
fn maxRegNum(n: u32, current: u32) -> u32 {
195 +
    if n >= MAX_SSA_REGS { return MAX_SSA_REGS + 1; }
195 196
    if n + 1 > current {
196 197
        return n + 1;
197 198
    }
198 199
    return current;
199 200
}
lib/std/lang/gen/regalloc/spill.rad +9 -8
126 126
        while i > 0 {
127 127
            set i -= 1;
128 128
            let instr = block.instrs[i];
129 129
130 130
            // Limit register pressure before processing this instruction.
131 -
            limitPressure(&mut scratch, &mut spilled, costs, numRegs);
131 +
            try limitPressure(&mut scratch, &mut spilled, costs, numRegs);
132 132
133 133
            // Enforce cross-call pressure at call sites.
134 134
            if il::isCall(instr) {
135 -
                limitCrossCallPressure(
135 +
                try limitCrossCallPressure(
136 136
                    &mut scratch, &mut spilled, costs,
137 137
                    &mut calleeClass, numCalleeSaved, il::instrDst(instr)
138 138
                );
139 139
            }
140 140
            // Remove definition from live set.
143 143
            }
144 144
            // Add uses to live set.
145 145
            il::forEachReg(instr, addRegToSetCallback, &mut scratch as &mut opaque);
146 146
        }
147 147
        // Also limit pressure at block entry.
148 -
        limitPressure(&mut scratch, &mut spilled, costs, numRegs);
148 +
        try limitPressure(&mut scratch, &mut spilled, costs, numRegs);
149 149
    }
150 150
151 151
    // Phase 3: Enforce global callee-class limit.
152 152
    // The per-call-site limit may leave the callee-class set larger than
153 153
    // `numCalleeSaved` when different call sites keep different subsets.
233 233
        bitset::clear(source, c.entries[i].reg);
234 234
    }
235 235
}
236 236
237 237
/// Collect all values from a bitset into a candidates buffer with their costs.
238 -
unsafe fn collectCandidates(bs: &bitset::Bitset, costs: *unsafe [SpillCost]) -> Candidates {
238 +
unsafe fn collectCandidates(bs: &bitset::Bitset, costs: *unsafe [SpillCost]) -> Candidates throws (alloc::AllocError) {
239 239
    let mut c = Candidates { entries: undefined, n: 0 };
240 240
    let mut it = bitset::iter(bs);
241 241
    while let reg = bitset::iterNext(&mut it) {
242 -
        assert c.n < MAX_CANDIDATES, "collectCandidates: too many live values";
242 +
        if c.n == MAX_CANDIDATES { throw alloc::AllocError::OutOfMemory; }
243 243
        if reg < costs.len {
244 244
            set c.entries[c.n] = CostEntry { reg, cost: costs[reg].defs + costs[reg].uses };
245 245
            set c.n += 1;
246 246
        }
247 247
    }
252 252
unsafe fn limitPressure(
253 253
    live: &mut bitset::Bitset,
254 254
    spilled: &mut bitset::Bitset,
255 255
    costs: *unsafe [SpillCost],
256 256
    numRegs: u32
257 -
) {
257 +
) throws (alloc::AllocError) {
258 258
    let liveCount = bitset::count(live);
259 259
    if liveCount <= numRegs {
260 260
        return;
261 261
    }
262 -
    let mut c = collectCandidates(live, costs);
262 +
    let mut c = try collectCandidates(live, costs);
263 263
    spillCheapest(&mut c, liveCount - numRegs, live, spilled);
264 264
}
265 265
266 266
/// Check whether an SSA register is the destination of a call instruction.
267 267
fn isCallDst(callDst: ?il::Reg, n: u32) -> bool {
281 281
    spilled: &mut bitset::Bitset,
282 282
    costs: *unsafe [SpillCost],
283 283
    calleeClass: &mut bitset::Bitset,
284 284
    numCalleeSaved: u32,
285 285
    callDst: ?il::Reg
286 -
) {
286 +
) throws (alloc::AllocError) {
287 287
    // Collect crossing candidates: live values excluding the call destination.
288 288
    let mut candidates: [CostEntry; 256] = undefined;
289 289
    let mut numCandidates: u32 = 0;
290 290
    let mut it = bitset::iter(live);
291 291
    while let n = bitset::iterNext(&mut it) {
292 292
        if not isCallDst(callDst, n) and n < costs.len {
293 +
            if numCandidates == MAX_CANDIDATES { throw alloc::AllocError::OutOfMemory; }
293 294
            set candidates[numCandidates] = CostEntry {
294 295
                reg: n,
295 296
                cost: costs[n].defs + costs[n].uses,
296 297
            };
297 298
            set numCandidates += 1;
lib/std/lang/il/binary/reader.rad +0 -11
65 65
/// Allocate a typed sequence after checking size and alignment arithmetic.
66 66
export unsafe fn storage(input: &mut Reader, size: u32, alignment: u32, count: u32)
67 67
    -> *mut [opaque] throws (binary::Error)
68 68
{
69 69
    assert size > 0 and alignment > 0 and (alignment & (alignment - 1)) == 0;
70 -
    if count == 0 {
71 -
        return &mut [];
72 -
    }
73 -
    let aligned = (input.arena.offset as u64 + alignment as u64 - 1) &
74 -
        ~(alignment as u64 - 1);
75 -
    if aligned > input.arena.data.len as u64 {
76 -
        throw binary::Error::Storage;
77 -
    }
78 -
    if count as u64 > (input.arena.data.len as u64 - aligned) / size as u64 {
79 -
        throw binary::Error::Storage;
80 -
    }
81 70
    return try alloc::allocSlice(input.arena, size, alignment, count) catch {
82 71
        throw binary::Error::Storage;
83 72
    };
84 73
}
85 74
std.lib.test +1 -0
12 12
lib/std/lang/gen/bitset/tests.rad
13 13
lib/std/lang/il/binary/tests.rad
14 14
lib/std/lang/il/binary/decodeTests.rad
15 15
lib/std/arch/rv64/image/tests.rad
16 16
lib/std/arch/rv64/shared/tests.rad
17 +
lib/std/arch/rv64/bounds.rad