rv64: Return bounded code generation errors

01d930e34f923cf95d9b74065599de4efb23e4b62407a1db32f13a25ee8423f0
Alexis Sellier committed ago 1 parent e9c98886
Makefile +9 -1
21 21
22 22
# Verify the emulator binary exists.
23 23
EMU_PATH := $(shell command -v $(EMU) 2>/dev/null)
24 24
25 25
default: emulator $(RAD_BIN)
26 -
test: emulator seed-test std-test bin-test
26 +
test: emulator seed-test std-test backend-test bin-test
27 27
28 28
seed-test:
29 29
	@seed/test
30 30
31 31
# Emulator command check
126 126
.PHONY: test clean default seed-test std-test bin-test seed \
127 127
	clean-std-test clean-bin-test clean-rad emulator
128 128
.SUFFIXES:
129 129
.DELETE_ON_ERROR:
130 130
.SILENT:
131 +
132 +
# Backend bounds use production std to keep compiler test input bounded.
133 +
.PHONY: backend-test
134 +
backend-test: test/backend.rv64
135 +
	@$(EMU) $(EMU_FLAGS) -run test/backend.rv64
136 +
137 +
test/backend.rv64: test/backend.rad $(STD_LIB) $(RAD_BIN)
138 +
	@$(RADIANCE) $(STD) -pkg backend -mod test/backend.rad -entry backend -o $@
compiler/radiance.rad +14 -3
989 989
        case CodegenEntryMode::DefaultEntry => {
990 990
            set entryPatch = rv64::EntryPatch::Reserved(nil);
991 991
        }
992 992
        else => {}
993 993
    }
994 -
    let mut generator = rv64::beginProgram(
994 +
    let mut generator = try rv64::beginProgram(
995 995
        rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug },
996 996
        &mut res.arena
997 -
    );
997 +
    ) catch {
998 +
        throw Error::Other;
999 +
    };
998 1000
    let mut codegenCtx = codegen::Context {
999 1001
        generator: &mut generator,
1000 1002
        fnArena: (&mut *fnArena) as *unsafe mut alloc::Arena,
1001 1003
    };
1002 1004
    let mut low = lower::lowerer(
1022 1024
        else => {}
1023 1025
    }
1024 1026
    if let path = codegenOptions.logPath {
1025 1027
        pkgLog(entryPkg, &["generating code", "(", path, ")", ".."]);
1026 1028
    }
1027 -
    return rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]);
1029 +
    return try rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]) catch error {
1030 +
        match error {
1031 +
            case rv64::Error::Allocation => io::printError("radiance: code generation allocation failed\n"),
1032 +
            case rv64::Error::Capacity => io::printError("radiance: code generation capacity exceeded\n"),
1033 +
            case rv64::Error::Symbol => io::printError("radiance: invalid code generation symbol\n"),
1034 +
            case rv64::Error::Data(_) => io::printError("radiance: data section generation failed\n"),
1035 +
            case rv64::Error::Relocation => io::printError("radiance: code generation relocation out of range\n"),
1036 +
        }
1037 +
        throw Error::Other;
1038 +
    };
1028 1039
}
1029 1040
1030 1041
/// Lower, optionally dump, and optionally generate binary output.
1031 1042
unsafe fn compile(
1032 1043
    ctx: *unsafe mut CompileContext,
lib/std/arch/rv64.rad +59 -17
27 27
use std::lang::gen::labels;
28 28
use std::lang::gen::regalloc;
29 29
use std::lang::gen::data;
30 30
use std::lang::gen::types;
31 31
32 +
/// Recoverable code-generation failure.
33 +
export union Error: Copy {
34 +
    /// The code-generation or function arena is full.
35 +
    Allocation,
36 +
    /// A fixed output or metadata table is full.
37 +
    Capacity,
38 +
    /// A required symbol is missing or invalid.
39 +
    Symbol,
40 +
    /// A branch or address exceeds its instruction range.
41 +
    Relocation,
42 +
    /// Data section layout or emission failed.
43 +
    Data(data::Error),
44 +
}
45 +
32 46
////////////////
33 47
// Registers  //
34 48
////////////////
35 49
36 50
export constant ZERO: gen::Reg = gen::Reg(0);   /// Hard-wired zero.
212 226
213 227
/// Begin RV64 code generation for a program's global state.
214 228
export unsafe fn beginProgram(
215 229
    options: ProgramOptions,
216 230
    arena: &mut alloc::Arena
217 -
) -> Generator {
218 -
    let mut e = try! emit::emitter(arena, options.debug);
231 +
) -> Generator throws (Error) {
232 +
    let checkpoint = alloc::save(arena);
233 +
    let mut e = try emit::emitter(arena, options.debug) catch {
234 +
        alloc::restore(arena, checkpoint);
235 +
        throw Error::Allocation;
236 +
    };
219 237
220 238
    // Emit placeholder entry jump when requested.
221 239
    // We'll patch this at the end once we know where the function is.
222 240
    match options.entryPatch {
223 241
        case EntryPatch::Reserved(_) => {
237 255
export unsafe fn generateFunction(
238 256
    generator: &mut Generator,
239 257
    func: *unsafe il::Fn,
240 258
    arena: &mut alloc::Arena
241 259
) {
242 -
    if func.isExtern {
260 +
    if generator.e.error <> nil or func.isExtern {
243 261
        return;
244 262
    }
245 263
    let checkpoint = alloc::save(arena);
246 264
    let config = targetConfig();
247 -
    let ralloc = try! regalloc::allocate(func, &config, arena);
265 +
    let ralloc = try regalloc::allocate(func, &config, arena) catch {
266 +
        alloc::restore(arena, checkpoint);
267 +
        set generator.e.error = Error::Allocation;
268 +
        return;
269 +
    };
248 270
249 271
    isel::selectFn(&mut generator.e, &ralloc, func);
250 272
251 273
    // Reclaim unused memory after instruction selection.
252 274
    alloc::restore(arena, checkpoint);
304 326
    globalData: &[il::Data],
305 327
    storage: Storage,
306 328
    roDataPrefix: *[u8],
307 329
    roDataBuf: &mut [u8],
308 330
    rwDataBuf: &mut [u8]
309 -
) -> Program {
331 +
) -> Program throws (Error) {
332 +
    try emit::check(&generator.e);
310 333
    // Build data map after function lowering. Function-local literals can add
311 334
    // global data while functions are lowered, so final layout belongs here.
312 335
    let case Storage { dataSyms: symbolBuf, dataSymEntries } = storage
313 336
        else panic "expected code generation storage";
314 337
    let mut dataSymCount: u32 = 0;
315 -
    let roLayoutSize = data::layoutSectionAtOffset(
338 +
    let roLayoutSize = try data::layoutSectionAtOffset(
316 339
        globalData, symbolBuf, &mut dataSymCount, RO_DATA_BASE, roDataPrefix.len, true
317 -
    );
318 -
    data::layoutSection(globalData, symbolBuf, &mut dataSymCount, RW_DATA_BASE, false);
340 +
    ) catch error {
341 +
        throw Error::Data(error);
342 +
    };
343 +
    try data::layoutSection(globalData, symbolBuf, &mut dataSymCount, RW_DATA_BASE, false) catch error {
344 +
        throw Error::Data(error);
345 +
    };
319 346
320 347
    let dataSyms = &symbolBuf[..dataSymCount];
321 -
    let dataSymMap = data::buildMap(dataSyms, dataSymEntries);
322 -
    let codeBase = mem::alignUp(RO_DATA_BASE + roLayoutSize, DWORD_SIZE as u32);
348 +
    let dataSymMap = try data::buildMap(dataSyms, dataSymEntries) catch error {
349 +
        throw Error::Data(error);
350 +
    };
351 +
    let codeBase64 = (RO_DATA_BASE as u64 + roLayoutSize as u64 + DWORD_SIZE as u64 - 1)
352 +
        & ~(DWORD_SIZE as u64 - 1);
353 +
    if codeBase64 > 0xffffffff {
354 +
        throw Error::Data(data::Error::Overflow);
355 +
    }
356 +
    let codeBase = codeBase64 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 = 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
        }
337 371
    // Patch function calls and address loads now that all functions are emitted.
338 372
    emit::patchJumps(&mut generator.e);
339 373
    emit::patchCalls(&mut generator.e);
340 374
    emit::patchAddrLoads(&mut generator.e, &dataSymMap);
341 375
376 +
    try emit::check(&generator.e);
377 +
342 378
    // Emit data sections.
343 -
    assert roDataPrefix.len <= roDataBuf.len, "finishProgram: rodata prefix buffer overflow";
379 +
    if roDataPrefix.len > roDataBuf.len {
380 +
        throw Error::Capacity;
381 +
    }
344 382
    try! mem::copy(roDataBuf, roDataPrefix);
345 383
346 -
    let roDataSize = data::emitSectionAtOffset(
384 +
    let roDataSize = try data::emitSectionAtOffset(
347 385
        globalData, &dataSymMap, &generator.e.labels, codeBase, roDataBuf, true, roDataPrefix.len
348 -
    );
349 -
    let rwDataSize = data::emitSection(
386 +
    ) catch error {
387 +
        throw Error::Data(error);
388 +
    };
389 +
    let rwDataSize = try data::emitSection(
350 390
        globalData, &dataSymMap, &generator.e.labels, codeBase, rwDataBuf, false
351 -
    );
391 +
    ) catch error {
392 +
        throw Error::Data(error);
393 +
    };
352 394
    return Program {
353 395
        code: emit::getCode(&generator.e),
354 396
        funcs: &generator.e.funcs[..],
355 397
        roDataSize,
356 398
        rwDataSize,
lib/std/arch/rv64/emit.rad +224 -43
96 96
    offset: i32,
97 97
}
98 98
99 99
/// Emission context. Tracks state during code generation.
100 100
export record Emitter {
101 -
    /// Allocator for growing append-backed emitter lists.
102 -
    allocator: alloc::Allocator,
101 +
    /// First failure recorded during generation.
102 +
    error: ?super::Error,
103 103
    /// Emitted instructions storage.
104 104
    code: *mut [u32],
105 105
    /// Current number of emitted instructions.
106 106
    codeLen: u32,
107 107
    /// Local branches needing offset patching.
140 140
    /// When false, SP never changes after the prologue.
141 141
    isDynamic: bool,
142 142
}
143 143
144 144
/// 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 {
145 +
export fn computeFrame(localSize: i32, usedCalleeSaved: u32, epilogueBlock: u32, isLeaf: bool, isDynamic: bool) -> Frame throws (super::Error) {
146 146
    let mut frame = Frame {
147 147
        totalSize: 0,
148 148
        savedRegs: [SavedReg { reg: super::ZERO, offset: 0 }; super::NUM_SAVED_REGISTERS],
149 149
        savedRegsLen: 0,
150 150
        epilogueBlock,
157 157
    if isLeaf and localSize == 0 and usedCalleeSaved == 0 {
158 158
        return frame;
159 159
    }
160 160
    // Compute total frame size. Includes RA and FP registers.
161 161
    let savedRegs = mem::popCount(usedCalleeSaved) + 2;
162 -
    let totalSize = mem::alignUpI32(
163 -
        localSize + savedRegs * super::DWORD_SIZE,
164 -
        super::STACK_ALIGNMENT
165 -
    );
162 +
    if localSize < 0 or usedCalleeSaved >> (super::NUM_SAVED_REGISTERS as u32) <> 0 {
163 +
        throw super::Error::Capacity;
164 +
    }
165 +
    let total64 = (localSize as u64 + savedRegs as u64 * super::DWORD_SIZE as u64
166 +
        + super::STACK_ALIGNMENT as u64 - 1) & ~(super::STACK_ALIGNMENT as u64 - 1);
167 +
    if total64 > 0x7fffffff {
168 +
        throw super::Error::Capacity;
169 +
    }
170 +
    let totalSize = total64 as i32;
166 171
    set frame.totalSize = totalSize;
167 172
168 173
    // Build list of callee-saved registers with offsets.
169 174
    let mut offset = totalSize - (super::DWORD_SIZE * 3);
170 175
    for reg, i in super::CALLEE_SAVED {
196 201
    if debug {
197 202
        set debugEntries = try alloc::allocSlice(
198 203
            arena, @sizeOf(types::DebugEntry), @alignOf(types::DebugEntry), MAX_DEBUG_ENTRIES
199 204
        ) as *mut [types::DebugEntry];
200 205
    }
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];
206 +
    let mut pendingBranchesBuf = pendingBranches as *mut [PendingBranch];
207 +
    let mut pendingCallsBuf = pendingCalls as *mut [PendingCall];
208 +
    let mut pendingJumpsBuf = pendingJumps as *mut [PendingJump];
209 +
    let mut pendingAddrLoadsBuf = pendingAddrLoads as *mut [PendingAddrLoad];
210 +
    let mut funcsBuf = funcs as *mut [types::FuncAddr];
211 +
    set pendingBranchesBuf.len = 0;
212 +
    set pendingCallsBuf.len = 0;
213 +
    set pendingJumpsBuf.len = 0;
214 +
    set pendingAddrLoadsBuf.len = 0;
215 +
    set funcsBuf.len = 0;
206 216
    return Emitter {
207 -
        allocator: alloc::arenaAllocator(arena),
217 +
        error: nil,
208 218
        code: code as *mut [u32],
209 219
        codeLen: 0,
210 -
        pendingBranches: &mut pendingBranchesBuf[..0],
211 -
        pendingCalls: &mut pendingCallsBuf[..0],
212 -
        pendingJumps: &mut pendingJumpsBuf[..0],
213 -
        pendingAddrLoads: &mut pendingAddrLoadsBuf[..0],
220 +
        pendingBranches: pendingBranchesBuf,
221 +
        pendingCalls: pendingCallsBuf,
222 +
        pendingJumps: pendingJumpsBuf,
223 +
        pendingAddrLoads: pendingAddrLoadsBuf,
214 224
        labels: labels::init(blockOffsets as *mut [i32], funcEntries as *mut [dict::Entry]),
215 -
        funcs: &mut funcsBuf[..0],
225 +
        funcs: funcsBuf,
216 226
        debugEntries,
217 227
        debugEntriesLen: 0,
218 228
    };
219 229
}
220 230
222 232
// Emission Helpers  //
223 233
///////////////////////
224 234
225 235
/// Emit a single instruction.
226 236
export fn emit(e: &mut Emitter, instr: u32) {
227 -
    assert e.codeLen < e.code.len, "emit: code buffer full";
237 +
    if e.error <> nil {
238 +
        return;
239 +
    }
240 +
    if e.codeLen >= e.code.len {
241 +
        set e.error = super::Error::Capacity;
242 +
        return;
243 +
    }
228 244
    set e.code[e.codeLen] = instr;
229 245
    set e.codeLen += 1;
230 246
}
231 247
232 248
/// Compute branch offset to a function by name.
233 -
export fn branchOffsetToFunc(e: &Emitter, srcIndex: u32, name: *[u8]) -> i32 {
234 -
    return labels::branchToFunc(&e.labels, srcIndex, name, super::INSTR_SIZE);
249 +
export fn branchOffsetToFunc(e: &mut Emitter, srcIndex: u32, name: *[u8]) -> i32 {
250 +
    if e.error <> nil {
251 +
        return 0;
252 +
    }
253 +
    let target = dict::get(&e.labels.funcs, name) else {
254 +
        set e.error = super::Error::Symbol;
255 +
        return 0;
256 +
    };
257 +
    let offset = target as i64 - srcIndex as i64 * super::INSTR_SIZE as i64;
258 +
    if offset < -0x80000000 or offset > 0x7fffffff {
259 +
        set e.error = super::Error::Relocation;
260 +
        return 0;
261 +
    }
262 +
    return offset as i32;
235 263
}
236 264
237 265
/// Patch an instruction at a given index.
238 266
export fn patch(e: &mut Emitter, index: u32, instr: u32) {
267 +
    if e.error <> nil {
268 +
        return;
269 +
    }
270 +
    if index >= e.codeLen or index >= e.code.len {
271 +
        set e.error = super::Error::Capacity;
272 +
        return;
273 +
    }
239 274
    set e.code[index] = instr;
240 275
}
241 276
242 277
/// Record a block's address for branch resolution.
243 278
export fn recordBlock(e: &mut Emitter, blockIdx: u32) {
244 -
    assert e.codeLen <= MAX_CODE_LEN;
279 +
    if e.error <> nil {
280 +
        return;
281 +
    }
282 +
    if e.codeLen > MAX_CODE_LEN or blockIdx >= e.labels.blockOffsets.len {
283 +
        set e.error = super::Error::Capacity;
284 +
        return;
285 +
    }
245 286
    labels::recordBlock(&mut e.labels, blockIdx, e.codeLen as i32 * super::INSTR_SIZE);
246 287
}
247 288
248 289
/// Record a function's code offset for call resolution.
249 290
export fn recordFuncOffset(e: &mut Emitter, name: *[u8]) {
251 292
    recordFuncOffsetAt(e, name, codeLen);
252 293
}
253 294
254 295
/// Record a function's code offset at `index` for call resolution.
255 296
export fn recordFuncOffsetAt(e: &mut Emitter, name: *[u8], index: u32) {
256 -
    assert index <= MAX_CODE_LEN;
297 +
    if e.error <> nil {
298 +
        return;
299 +
    }
300 +
    if index > MAX_CODE_LEN or e.labels.funcs.entries.len < 2 {
301 +
        set e.error = super::Error::Capacity;
302 +
        return;
303 +
    }
304 +
    if name.len == 0 {
305 +
        set e.error = super::Error::Symbol;
306 +
        return;
307 +
    }
308 +
    if dict::get(&e.labels.funcs, name) == nil and
309 +
        e.labels.funcs.count >= e.labels.funcs.entries.len / 2 {
310 +
        set e.error = super::Error::Capacity;
311 +
        return;
312 +
    }
257 313
    dict::insert(&mut e.labels.funcs, name, index as i32 * super::INSTR_SIZE);
258 314
}
259 315
260 316
/// Record a function's start position for printing.
261 317
export fn recordFunc(e: &mut Emitter, name: *[u8]) {
263 319
    recordFuncAt(e, name, codeLen);
264 320
}
265 321
266 322
/// Record a function's start position at `index` for printing.
267 323
export fn recordFuncAt(e: &mut Emitter, name: *[u8], index: u32) {
268 -
    e.funcs.append(types::FuncAddr { name, index }, e.allocator);
324 +
    if e.error <> nil {
325 +
        return;
326 +
    }
327 +
    if e.funcs.len >= e.funcs.cap {
328 +
        set e.error = super::Error::Capacity;
329 +
        return;
330 +
    }
331 +
    let count = e.funcs.len;
332 +
    unsafe {
333 +
        set e.funcs.len = count + 1;
334 +
    }
335 +
    set e.funcs[count] = types::FuncAddr { name, index };
269 336
}
270 337
271 338
/// Record a local branch needing later patching.
272 339
/// Unconditional jumps use a single slot (J-type, +-1MB range).
273 340
/// Conditional branches use two slots (B-type has only +-4KB range,
274 341
/// so large functions may need the inverted-branch + JAL fallback).
275 342
export fn recordBranch(e: &mut Emitter, targetBlock: u32, kind: BranchKind) {
276 -
    e.pendingBranches.append(PendingBranch {
343 +
    if e.error <> nil {
344 +
        return;
345 +
    }
346 +
    if e.pendingBranches.len >= e.pendingBranches.cap {
347 +
        set e.error = super::Error::Capacity;
348 +
        return;
349 +
    }
350 +
    let count = e.pendingBranches.len;
351 +
    unsafe {
352 +
        set e.pendingBranches.len = count + 1;
353 +
    }
354 +
    set e.pendingBranches[count] = PendingBranch {
277 355
        index: e.codeLen,
278 356
        target: targetBlock,
279 357
        kind: kind,
280 -
    }, e.allocator);
358 +
    };
281 359
282 360
    emit(e, encode::nop()); // First slot, always needed.
283 361
284 362
    match kind {
285 363
        case BranchKind::Jump => {},
289 367
290 368
/// Record a function call needing later patching.
291 369
/// Emits placeholder instructions that will be patched later.
292 370
/// Uses two slots to support long-distance calls.
293 371
export fn recordCall(e: &mut Emitter, target: *[u8]) {
294 -
    e.pendingCalls.append(PendingCall {
372 +
    if e.error <> nil {
373 +
        return;
374 +
    }
375 +
    if e.pendingCalls.len >= e.pendingCalls.cap {
376 +
        set e.error = super::Error::Capacity;
377 +
        return;
378 +
    }
379 +
    let count = e.pendingCalls.len;
380 +
    unsafe {
381 +
        set e.pendingCalls.len = count + 1;
382 +
    }
383 +
    set e.pendingCalls[count] = PendingCall {
295 384
        index: e.codeLen,
296 385
        target,
297 -
    }, e.allocator);
386 +
    };
298 387
299 388
    emit(e, encode::nop()); // Placeholder for AUIPC.
300 389
    emit(e, encode::nop()); // Placeholder for JALR.
301 390
}
302 391
303 392
/// Record a jump emitted by assembly that needs whole-program patching.
304 393
export fn recordJumpAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) {
305 -
    e.pendingJumps.append(PendingJump {
394 +
    if e.error <> nil {
395 +
        return;
396 +
    }
397 +
    if e.pendingJumps.len >= e.pendingJumps.cap {
398 +
        set e.error = super::Error::Capacity;
399 +
        return;
400 +
    }
401 +
    let count = e.pendingJumps.len;
402 +
    unsafe {
403 +
        set e.pendingJumps.len = count + 1;
404 +
    }
405 +
    set e.pendingJumps[count] = PendingJump {
306 406
        index,
307 407
        target,
308 408
        rd,
309 -
    }, e.allocator);
409 +
    };
310 410
}
311 411
312 412
/// Record a function address load needing later patching.
313 413
/// Emits placeholder instructions that will be patched to load the function's address.
314 414
/// Uses two slots to compute long-distance addresses.
320 420
    emit(e, encode::nop()); // Placeholder for ADDI.
321 421
}
322 422
323 423
/// Record a function address load already reserved by assembly.
324 424
export fn recordAddrLoadAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) {
325 -
    e.pendingAddrLoads.append(PendingAddrLoad {
425 +
    if e.error <> nil {
426 +
        return;
427 +
    }
428 +
    if e.pendingAddrLoads.len >= e.pendingAddrLoads.cap {
429 +
        set e.error = super::Error::Capacity;
430 +
        return;
431 +
    }
432 +
    let count = e.pendingAddrLoads.len;
433 +
    unsafe {
434 +
        set e.pendingAddrLoads.len = count + 1;
435 +
    }
436 +
    set e.pendingAddrLoads[count] = PendingAddrLoad {
326 437
        index,
327 438
        target,
328 439
        rd: rd,
329 440
        isData: false,
330 -
    }, e.allocator);
441 +
    };
331 442
}
332 443
333 444
/// Record a data address load needing later patching.
334 445
/// Uses an absolute 32-bit load sequence matching the current data memory map.
335 446
export fn recordDataAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) {
336 -
    e.pendingAddrLoads.append(PendingAddrLoad {
447 +
    if e.error <> nil {
448 +
        return;
449 +
    }
450 +
    if e.pendingAddrLoads.len >= e.pendingAddrLoads.cap {
451 +
        set e.error = super::Error::Capacity;
452 +
        return;
453 +
    }
454 +
    let count = e.pendingAddrLoads.len;
455 +
    unsafe {
456 +
        set e.pendingAddrLoads.len = count + 1;
457 +
    }
458 +
    set e.pendingAddrLoads[count] = PendingAddrLoad {
337 459
        index: e.codeLen,
338 460
        target,
339 461
        rd: rd,
340 462
        isData: true,
341 -
    }, e.allocator);
463 +
    };
342 464
343 465
    emit(e, encode::nop()); // Placeholder for LUI.
344 466
    emit(e, encode::nop()); // Placeholder for ADDIW.
345 467
}
346 468
351 473
/// Uses two-instruction sequences: short branches use `branch` and `nop`,
352 474
/// long branches use inverted branch  and `jal` or `auipc` and `jalr`.
353 475
export unsafe fn patchLocalBranches(e: &mut Emitter) {
354 476
    for i in 0..e.pendingBranches.len {
355 477
        let p = e.pendingBranches[i];
356 -
        let offset = labels::branchToBlock(&e.labels, p.index, p.target, super::INSTR_SIZE);
478 +
        if e.error <> nil {
479 +
            return;
480 +
        }
481 +
        if p.target >= e.labels.blockCount or p.target >= e.labels.blockOffsets.len {
482 +
            set e.error = super::Error::Symbol;
483 +
            return;
484 +
        }
485 +
        let offset64 = e.labels.blockOffsets[p.target] as i64 - p.index as i64 * super::INSTR_SIZE as i64;
486 +
        if offset64 < -0x80000000 or offset64 > 0x7fffffff {
487 +
            set e.error = super::Error::Relocation;
488 +
            return;
489 +
        }
490 +
        let offset = offset64 as i32;
357 491
        match p.kind {
358 492
            case BranchKind::Cond { op, rs1, rs2 } => {
359 493
                if encode::isBranchImm(offset) {
360 494
                    patch(e, p.index, encodeCondBranch(op, rs1, rs2, offset));
361 495
                    patch(e, p.index + 1, encode::nop());
362 496
                } else {
363 -
                    let adj = offset - super::INSTR_SIZE;
497 +
                    let adjusted = offset64 - super::INSTR_SIZE as i64;
498 +
                    if adjusted < -0x100000 or adjusted > 0xffffe {
499 +
                        set e.error = super::Error::Relocation;
500 +
                        return;
501 +
                    }
502 +
                    let adj = adjusted as i32;
503 +
                    if not encode::isJumpImm(adj) {
504 +
                        set e.error = super::Error::Relocation;
505 +
                        return;
506 +
                    }
364 507
                    patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, super::INSTR_SIZE * 2));
365 508
                    patch(e, p.index + 1, encode::jal(super::ZERO, adj));
366 509
                }
367 510
            },
368 511
            case BranchKind::InvertedCond { op, rs1, rs2 } => {
369 512
                if encode::isBranchImm(offset) {
370 513
                    patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, offset));
371 514
                    patch(e, p.index + 1, encode::nop());
372 515
                } else {
373 -
                    let adj = offset - super::INSTR_SIZE;
516 +
                    let adjusted = offset64 - super::INSTR_SIZE as i64;
517 +
                    if adjusted < -0x100000 or adjusted > 0xffffe {
518 +
                        set e.error = super::Error::Relocation;
519 +
                        return;
520 +
                    }
521 +
                    let adj = adjusted as i32;
522 +
                    if not encode::isJumpImm(adj) {
523 +
                        set e.error = super::Error::Relocation;
524 +
                        return;
525 +
                    }
374 526
                    patch(e, p.index, encodeCondBranch(op, rs1, rs2, super::INSTR_SIZE * 2));
375 527
                    patch(e, p.index + 1, encode::jal(super::ZERO, adj));
376 528
                }
377 529
            },
378 530
            case BranchKind::Jump => {
379 531
                // Single-slot jump (J-type, +-1MB range).
380 -
                assert encode::isJumpImm(offset), "patchLocalBranches: jump offset too large";
532 +
                if not encode::isJumpImm(offset) {
533 +
                    set e.error = super::Error::Relocation;
534 +
                    return;
535 +
                }
381 536
                patch(e, p.index, encode::jal(super::ZERO, offset));
382 537
            },
383 538
        }
384 539
    }
385 -
    set e.pendingBranches = &mut e.pendingBranches[..0];
540 +
    set e.pendingBranches.len = 0;
386 541
}
387 542
388 543
/// Encode a conditional branch instruction.
389 544
fn encodeCondBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 {
390 545
    match op {
424 579
export fn patchJumps(e: &mut Emitter) {
425 580
    for i in 0..e.pendingJumps.len {
426 581
        let p = e.pendingJumps[i];
427 582
        let offset = branchOffsetToFunc(e, p.index, p.target);
428 583
429 -
        assert encode::isJumpImm(offset), "patchJumps: jump offset too large";
584 +
        if e.error <> nil {
585 +
            return;
586 +
        }
587 +
        if not encode::isJumpImm(offset) {
588 +
            set e.error = super::Error::Relocation;
589 +
            return;
590 +
        }
430 591
        patch(e, p.index, encode::jal(p.rd, offset));
431 592
    }
432 593
}
433 594
434 595
/// Patch all pending function and data address loads.
435 596
/// Called after all functions have been generated and data layout is known.
436 597
export fn patchAddrLoads(e: &mut Emitter, dataSymMap: &data::DataSymMap) {
437 598
    for i in 0..e.pendingAddrLoads.len {
599 +
        if e.error <> nil {
600 +
            return;
601 +
        }
438 602
        let p = e.pendingAddrLoads[i];
439 603
        if p.isData {
440 604
            let addr = data::lookupAddr(dataSymMap, p.target) else {
441 -
                panic "patchAddrLoads: data symbol not found";
605 +
                set e.error = super::Error::Symbol;
606 +
                return;
442 607
            };
443 -
            assert addr <= MAX_I32_ADDR, "patchAddrLoads: data address too large";
608 +
            if addr > MAX_I32_ADDR {
609 +
                set e.error = super::Error::Relocation;
610 +
                return;
611 +
            }
444 612
            let s = splitImm(addr as i32);
445 613
446 614
            patch(e, p.index, encode::lui(p.rd, s.hi));
447 615
            patch(e, p.index + 1, encode::addiw(p.rd, p.rd, s.lo));
448 616
728 896
}
729 897
730 898
/// Record a debug entry mapping the current PC to a source location.
731 899
/// Deduplicates consecutive entries with the same location.
732 900
export fn recordSrcLoc(e: &mut Emitter, loc: il::SrcLoc) {
901 +
    if e.error <> nil {
902 +
        return;
903 +
    }
733 904
    let pc = e.codeLen * super::INSTR_SIZE as u32;
734 905
735 906
    // Skip if this is the same location as the previous entry.
736 907
    if e.debugEntriesLen > 0 {
737 908
        let prev = &e.debugEntries[e.debugEntriesLen - 1];
738 909
        if prev.offset == loc.offset and prev.moduleId == loc.moduleId {
739 910
            return;
740 911
        }
741 912
    }
742 -
    assert e.debugEntriesLen < e.debugEntries.len, "recordSrcLoc: debug entry buffer full";
913 +
    if e.debugEntriesLen >= e.debugEntries.len {
914 +
        set e.error = super::Error::Capacity;
915 +
        return;
916 +
    }
743 917
    set e.debugEntries[e.debugEntriesLen] = types::DebugEntry {
744 918
        pc,
745 919
        moduleId: loc.moduleId,
746 920
        offset: loc.offset,
747 921
    };
750 924
751 925
/// Get debug entries as a slice.
752 926
export fn getDebugEntries(e: &Emitter) -> *[types::DebugEntry] {
753 927
    return &e.debugEntries[..e.debugEntriesLen];
754 928
}
929 +
930 +
/// Return the first error recorded during emission.
931 +
export fn check(e: &Emitter) throws (super::Error) {
932 +
    if let error = e.error {
933 +
        throw error;
934 +
    }
935 +
}
lib/std/arch/rv64/isel.rad +44 -9
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 +
                        if alignment == 0 or (alignment & (alignment - 1)) <> 0 or sz < 0 {
288 +
                            throw super::Error::Capacity;
289 +
                        }
290 +
                        let aligned = (offset as u64 + alignment as u64 - 1) & ~(alignment as u64 - 1);
291 +
                        if aligned > 0x7fffffff or sz as u64 > 0x7fffffff - aligned {
292 +
                            throw super::Error::Capacity;
293 +
                        }
294 +
                        set offset = (aligned + sz as u64) as i32;
289 295
                    } else {
290 296
                        set isDynamic = true;
291 297
                    }
292 298
                },
293 299
                else => {},
301 307
export unsafe fn selectFn(
302 308
    e: &mut emit::Emitter,
303 309
    ralloc: &regalloc::AllocResult,
304 310
    func: *unsafe il::Fn
305 311
) {
312 +
    if e.error <> nil {
313 +
        return;
314 +
    }
306 315
    // Reset block offsets for this function.
307 316
    labels::resetBlocks(&mut e.labels);
308 317
    // Pre-scan for constant-sized reserves to promote to fixed frame slots.
309 -
    let reserveInfo = computeReserveInfo(func);
318 +
    let reserveInfo = try computeReserveInfo(func) catch error {
319 +
        set e.error = error;
320 +
        return;
321 +
    };
322 +
    if ralloc.spill.frameSize < 0 or
323 +
        reserveInfo.size as u64 + ralloc.spill.frameSize as u64 > 0x7fffffff {
324 +
        set e.error = super::Error::Capacity;
325 +
        return;
326 +
    }
310 327
    let isLeaf = func.isLeaf;
311 328
    // Compute frame layout from spill slots, reserve slots, and used callee-saved registers.
312 -
    let frame = emit::computeFrame(
329 +
    let frame = try emit::computeFrame(
313 330
        ralloc.spill.frameSize + reserveInfo.size,
314 331
        ralloc.usedCalleeSaved,
315 332
        func.blocks.len,
316 333
        isLeaf,
317 334
        reserveInfo.isDynamic
318 -
    );
335 +
    ) catch error {
336 +
        set e.error = error;
337 +
        return;
338 +
    };
319 339
    // Synthetic block indices start after real blocks and the epilogue block.
320 340
    let mut s = Selector {
321 341
        e: e as *unsafe mut emit::Emitter,
322 342
        ralloc: ralloc as *unsafe regalloc::AllocResult,
323 343
        frameSize: frame.totalSize,
350 370
        }
351 371
    }
352 372
353 373
    // Emit each block.
354 374
    for i in 0..func.blocks.len {
375 +
        if s.e.error <> nil {
376 +
            return;
377 +
        }
355 378
        selectBlock(&mut s, i, &func.blocks[i], &frame, func);
356 379
    }
357 380
    // Emit epilogue.
358 381
    emit::emitEpilogue(s.e, &frame);
359 382
    // Patch local branches now that all blocks are emitted.
370 393
    // moved to the parameter registers by the predecessor's terminator.
371 394
372 395
    // Process each instruction, auto-committing any pending spill after each.
373 396
    let hasLocs = block.locs.len > 0;
374 397
    for instr, i in block.instrs {
398 +
        if s.e.error <> nil {
399 +
            return;
400 +
        }
375 401
        // Record debug location before emitting machine instructions.
376 402
        if hasLocs {
377 403
            emit::recordSrcLoc(s.e, block.locs[i]);
378 404
        }
379 405
        set s.pendingSpill = nil;
389 415
    }
390 416
}
391 417
392 418
/// Select instructions for a single IL instruction.
393 419
unsafe fn selectInstr(s: &mut Selector, blockIdx: u32, instr: il::Instr, frame: &emit::Frame, func: *unsafe il::Fn) {
420 +
    if s.e.error <> nil {
421 +
        return;
422 +
    }
394 423
    match instr {
395 424
        case il::Instr::BinOp { op, typ, dst, a, b } => {
396 425
            let rd = getDstReg(s, dst, super::SCRATCH1);
397 426
            let rs1 = resolveVal(s, super::SCRATCH1, a);
398 427
            selectAluBinOp(s, op, typ, rd, rs1, b);
709 738
            if let case il::Val::Reg(r) = func {
710 739
                let target = getSrcReg(s, r, super::SCRATCH2);
711 740
                emitMv(s, super::SCRATCH2, target);
712 741
            }
713 742
            // Move arguments to A0-A7 using parallel move resolution.
714 -
            assert args.len <= super::ARG_REGS.len, "selectInstr: too many call arguments";
743 +
            if args.len > super::ARG_REGS.len {
744 +
                set s.e.error = super::Error::Capacity;
745 +
                return;
746 +
            }
715 747
            emitParallelMoves(s, &super::ARG_REGS[..], args);
716 748
717 749
            // Emit call.
718 750
            match func {
719 751
                case il::Val::FnAddr(name) => {
1167 1199
///
1168 1200
/// Handles spilled destinations directly, then delegates to [`emitParallelMoves`]
1169 1201
/// for the remaining register-to-register parallel move resolution. Edges that
1170 1202
/// would overwrite an unconsumed spill source are unsupported.
1171 1203
unsafe fn emitBlockArgs(s: &mut Selector, func: *unsafe il::Fn, target: u32, args: &[il::Val]) {
1172 -
    if args.len == 0 {
1204 +
    if s.e.error <> nil or args.len == 0 {
1173 1205
        return;
1174 1206
    }
1175 1207
    let block = &func.blocks[target];
1176 1208
    assert args.len == block.params.len, "emitBlockArgs: argument/parameter count mismatch";
1177 -
    assert args.len <= MAX_BLOCK_ARGS, "emitBlockArgs: too many block arguments";
1209 +
    if args.len > MAX_BLOCK_ARGS {
1210 +
        set s.e.error = super::Error::Capacity;
1211 +
        return;
1212 +
    }
1178 1213
1179 1214
    // The parallel-move resolver only handles register destinations. Keep eager
1180 1215
    // stores for independent spill slots, but reject dependencies that would
1181 1216
    // require stack staging rather than silently miscompiling them.
1182 1217
    for arg, i in args {
lib/std/arch/rv64/tests.rad +1 -49
3 3
//! These tests verify that instruction encodings match the RISC-V specification
4 4
//! by comparing against known-good values.
5 5
6 6
use std::testing;
7 7
use std::lang::alloc;
8 -
use std::lang::il;
9 -
use std::lang::gen::bitset;
10 -
use std::lang::gen::regalloc;
11 8
use std::collections::dict;
12 9
13 10
use super::encode;
14 11
use super::asm;
15 12
38 35
        section: asm::Section::Text,
39 36
        offset: super::INSTR_SIZE,
40 37
        isExported: true,
41 38
    };
42 39
43 -
    let mut generator = super::beginProgram(
40 +
    let mut generator = try! super::beginProgram(
44 41
        super::ProgramOptions { entryPatch: super::EntryPatch::None, debug: false },
45 42
        &mut arena
46 43
    );
47 44
    super::addAssembly(
48 45
        &mut generator,
554 551
    try testing::expect(encode::isJumpImm(1048574));    // Max positive even
555 552
    try testing::expect(encode::isJumpImm(-1048576));   // Min negative
556 553
    try testing::expectNot(encode::isJumpImm(1));       // Must be even
557 554
    try testing::expectNot(encode::isJumpImm(1048576)); // Out of range
558 555
}
559 -
560 -
/// Register limit checks use bounded scratch storage.
561 -
static REGISTER_SCRATCH: [u8; 65536] = [0; 65536];
562 -
563 -
/// Register numbers at or above the supported count return allocation errors.
564 -
@test unsafe fn testRegisterLimit() throws (testing::TestError) {
565 -
    for number in [8191, 8192, 0xffffffff] {
566 -
        let mut arena = alloc::new(&mut REGISTER_SCRATCH[..]);
567 -
        let mut instructions = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: number }) }];
568 -
        let func = il::Fn {
569 -
            name: "limit", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
570 -
            blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }],
571 -
        };
572 -
        let mut failed = false;
573 -
        try regalloc::liveness::analyze(&func, &mut arena) catch {
574 -
            set failed = true;
575 -
        };
576 -
        assert failed == (number >= 8192);
577 -
        if failed {
578 -
            assert arena.offset == 0;
579 -
        }
580 -
    }
581 -
}
582 -
583 -
/// Excess live values return an error before the spill candidate table overflows.
584 -
@test unsafe fn testSpillCandidateLimit() throws (testing::TestError) {
585 -
    let mut arena = alloc::new(&mut REGISTER_SCRATCH[..]);
586 -
    let mut liveSet = try! bitset::allocate(&mut arena, 257);
587 -
    for i in 0..257 {
588 -
        bitset::put(&mut liveSet, i);
589 -
    }
590 -
    let mut out = [liveSet];
591 -
    let live = regalloc::liveness::LiveInfo {
592 -
        liveIn: &mut [], liveOut: &mut out[..], defs: &mut [], uses: &mut [], blockCount: 1, maxReg: 257,
593 -
    };
594 -
    let func = il::Fn {
595 -
        name: "pressure", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
596 -
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut [], locs: &[], preds: &[], loopDepth: 0 }],
597 -
    };
598 -
    let mut failed = false;
599 -
    try regalloc::spill::analyze(&func, &live, 23, 11, 8, &mut arena) catch {
600 -
        set failed = true;
601 -
    };
602 -
    assert failed;
603 -
}
lib/std/lang/gen/data.rad +79 -15
13 13
14 14
/// Size of the data symbol hash table. Must be a power of two
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 +
/// Data section layout or emission failure.
19 +
export union Error: Copy {
20 +
    /// A required symbol is missing or duplicated.
21 +
    Symbol,
22 +
    /// A symbol table or output buffer is full.
23 +
    Capacity,
24 +
    /// A size or address exceeds the supported range.
25 +
    Overflow,
26 +
    /// Alignment is zero or is not a power of two.
27 +
    Alignment,
28 +
}
29 +
18 30
/// Data symbol entry mapping name to address.
19 31
export record DataSym: Copy {
20 32
    /// Symbol name.
21 33
    name: *[u8],
22 34
    /// Absolute address, including data base address.
39 51
    items: &[il::Data],
40 52
    syms: &mut [DataSym],
41 53
    count: &mut u32,
42 54
    base: u32,
43 55
    readOnly: bool
44 -
) -> u32 {
45 -
    return layoutSectionAtOffset(items, syms, count, base, 0, readOnly);
56 +
) -> u32 throws (Error) {
57 +
    return try layoutSectionAtOffset(items, syms, count, base, 0, readOnly);
46 58
}
47 59
48 60
/// Lay out data symbols for a single section starting at [`startOffset`].
49 61
export fn layoutSectionAtOffset(
50 62
    items: &[il::Data],
51 63
    syms: &mut [DataSym],
52 64
    count: &mut u32,
53 65
    base: u32,
54 66
    startOffset: u32,
55 67
    readOnly: bool
56 -
) -> u32 {
68 +
) -> u32 throws (Error) {
57 69
    let mut offset: u32 = startOffset;
58 70
59 71
    // Data requiring sidecar image bytes first.
60 72
    for i in 0..items.len {
61 73
        let data = items[i];
62 74
        if data.readOnly == readOnly and not data.isZeroInit {
63 -
            set offset = mem::alignUp(offset, data.alignment);
75 +
            if data.alignment == 0 or (data.alignment & (data.alignment - 1)) <> 0 {
76 +
                throw Error::Alignment;
77 +
            }
78 +
            let aligned = (offset as u64 + data.alignment as u64 - 1) & ~(data.alignment as u64 - 1);
79 +
            if aligned + data.size as u64 > 0xffffffff or base as u64 + aligned + data.size as u64 > 0xffffffff {
80 +
                throw Error::Overflow;
81 +
            }
82 +
            if *count >= syms.len {
83 +
                throw Error::Capacity;
84 +
            }
85 +
            set offset = aligned as u32;
64 86
            set syms[*count] = DataSym { name: data.name, addr: base + offset };
65 87
            set *count += 1;
66 88
            set offset += data.size;
67 89
        }
68 90
    }
69 91
    // Zero-initialized data after.
70 92
    for i in 0..items.len {
71 93
        let data = items[i];
72 94
        if data.readOnly == readOnly and data.isZeroInit {
73 -
            set offset = mem::alignUp(offset, data.alignment);
95 +
            if data.alignment == 0 or (data.alignment & (data.alignment - 1)) <> 0 {
96 +
                throw Error::Alignment;
97 +
            }
98 +
            let aligned = (offset as u64 + data.alignment as u64 - 1) & ~(data.alignment as u64 - 1);
99 +
            if aligned + data.size as u64 > 0xffffffff or base as u64 + aligned + data.size as u64 > 0xffffffff {
100 +
                throw Error::Overflow;
101 +
            }
102 +
            if *count >= syms.len {
103 +
                throw Error::Capacity;
104 +
            }
105 +
            set offset = aligned as u32;
74 106
            set syms[*count] = DataSym { name: data.name, addr: base + offset };
75 107
            set *count += 1;
76 108
            set offset += data.size;
77 109
        }
78 110
    }
87 119
    dataSymMap: &DataSymMap,
88 120
    fnLabels: &labels::Labels,
89 121
    codeBase: u32,
90 122
    buf: &mut [u8],
91 123
    readOnly: bool
92 -
) -> u32 {
93 -
    return emitSectionAtOffset(items, dataSymMap, fnLabels, codeBase, buf, readOnly, 0);
124 +
) -> u32 throws (Error) {
125 +
    return try emitSectionAtOffset(items, dataSymMap, fnLabels, codeBase, buf, readOnly, 0);
94 126
}
95 127
96 128
/// Emit data bytes for a single section starting at `startOffset`.
97 129
export unsafe fn emitSectionAtOffset(
98 130
    items: &[il::Data],
100 132
    fnLabels: &labels::Labels,
101 133
    codeBase: u32,
102 134
    buf: &mut [u8],
103 135
    readOnly: bool,
104 136
    startOffset: u32
105 -
) -> u32 {
137 +
) -> u32 throws (Error) {
138 +
    if startOffset > buf.len {
139 +
        throw Error::Capacity;
140 +
    }
106 141
    let mut offset: u32 = startOffset;
107 142
108 143
    for i in 0..items.len {
109 144
        let data = items[i];
110 145
        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";
146 +
            if data.alignment == 0 or (data.alignment & (data.alignment - 1)) <> 0 {
147 +
                throw Error::Alignment;
148 +
            }
149 +
            let aligned = (offset as u64 + data.alignment as u64 - 1) & ~(data.alignment as u64 - 1);
150 +
            if aligned > buf.len as u64 or data.size as u64 > buf.len as u64 - aligned {
151 +
                throw Error::Capacity;
152 +
            }
153 +
            set offset = aligned as u32;
154 +
            let end = offset + data.size;
113 155
            for j in 0..data.values.len {
114 156
                let v = &data.values[j];
157 +
                let mut width: u32 = 1;
158 +
                match v.item {
159 +
                    case il::DataItem::Val { typ, .. } => set width = il::typeSize(typ),
160 +
                    case il::DataItem::Sym(_), il::DataItem::Fn(_) => set width = 8,
161 +
                    case il::DataItem::Str(text) => set width = text.len,
162 +
                    case il::DataItem::Undef => {}
163 +
                }
164 +
                if width as u64 * v.count as u64 > (end - offset) as u64 {
165 +
                    throw Error::Capacity;
166 +
                }
115 167
                for _ in 0..v.count {
116 168
                    match v.item {
117 169
                        case il::DataItem::Val { typ, val } => {
118 170
                            let size = il::typeSize(typ);
119 171
                            try! mem::copy(&mut buf[offset..], @sliceOf(&val as &u8, size));
120 172
121 173
                            set offset += size;
122 174
                        },
123 175
                        case il::DataItem::Sym(name) => {
124 176
                            let addr = lookupAddr(dataSymMap, name) else {
125 -
                                panic "emitSectionAtOffset: data symbol not found";
177 +
                                throw Error::Symbol;
126 178
                            };
127 179
                            let addr64: u64 = addr as u64;
128 180
                            try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, 8));
129 181
130 182
                            set offset += @sizeOf(u64);
131 183
                        },
132 184
                        case il::DataItem::Fn(name) => {
133 -
                            let addr = codeBase + labels::funcOffset(fnLabels, name) as u32;
134 -
                            let addr64: u64 = addr as u64;
185 +
                            let functionOffset = dict::get(&fnLabels.funcs, name) else {
186 +
                                throw Error::Symbol;
187 +
                            };
188 +
                            if functionOffset < 0 or codeBase as u64 + functionOffset as u64 > 0xffffffff {
189 +
                                throw Error::Overflow;
190 +
                            }
191 +
                            let addr64 = codeBase as u64 + functionOffset as u64;
135 192
                            try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, 8));
136 193
137 194
                            set offset += @sizeOf(*u8);
138 195
                        },
139 196
                        case il::DataItem::Str(s) => {
151 208
    }
152 209
    return offset;
153 210
}
154 211
155 212
/// 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 {
213 +
/// The table length must be a power of two and at least twice the symbol count.
214 +
/// The minimum table length is two.
215 +
export fn buildMap(syms: *[DataSym], entries: *mut [dict::Entry]) -> DataSymMap throws (Error) {
216 +
    if entries.len < 2 or (entries.len & (entries.len - 1)) <> 0 or syms.len > entries.len / 2 {
217 +
        throw Error::Capacity;
218 +
    }
158 219
    let mut d = dict::init(entries);
159 220
    for i in 0..syms.len {
221 +
        if syms[i].name.len == 0 or dict::get(&d, syms[i].name) <> nil {
222 +
            throw Error::Symbol;
223 +
        }
160 224
        dict::insert(&mut d, syms[i].name, syms[i].addr as i32);
161 225
    }
162 226
    return DataSymMap { dict: d, syms };
163 227
}
164 228
test/backend.rad added +555 -0
1 +
//! Executable checks for recoverable backend bounds.
2 +
use std::lang::alloc;
3 +
use std::lang::il;
4 +
use std::lang::gen::bitset;
5 +
use std::lang::gen::data;
6 +
use std::lang::gen::regalloc;
7 +
use std::collections::dict;
8 +
use std::arch::rv64;
9 +
use std::arch::rv64::encode;
10 +
11 +
/// A backend check could not obtain its required test storage.
12 +
union TestError: Copy {
13 +
    /// Test setup failed.
14 +
    Failed,
15 +
}
16 +
17 +
/// Reusable emitter storage.
18 +
static ASSEMBLY_ARENA_STORAGE: [u8; 16777216] = [0; 16777216];
19 +
20 +
/// Run all backend boundary checks.
21 +
@default unsafe fn main() -> u32 {
22 +
    try testBackendBounds() catch {
23 +
        panic "backend bounds failed";
24 +
    };
25 +
    return 0;
26 +
}
27 +
28 +
/// Register limit checks use bounded scratch storage.
29 +
static REGISTER_SCRATCH: [u8; 65536] = [0; 65536];
30 +
31 +
/// Register numbers at or above the supported count return allocation errors.
32 +
unsafe fn testRegisterLimit() throws (TestError) {
33 +
    for number in [8191, 8192, 0xffffffff] {
34 +
        let mut arena = alloc::new(&mut REGISTER_SCRATCH[..]);
35 +
        let mut instructions = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: number }) }];
36 +
        let func = il::Fn {
37 +
            name: "limit", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
38 +
            blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }],
39 +
        };
40 +
        let mut failed = false;
41 +
        try regalloc::liveness::analyze(&func, &mut arena) catch {
42 +
            set failed = true;
43 +
        };
44 +
        assert failed == (number >= 8192);
45 +
        if failed {
46 +
            assert arena.offset == 0;
47 +
        }
48 +
    }
49 +
}
50 +
51 +
/// Excess live values return an error before the spill candidate table overflows.
52 +
unsafe fn testSpillCandidateLimit() throws (TestError) {
53 +
    let mut arena = alloc::new(&mut REGISTER_SCRATCH[..]);
54 +
    let mut liveSet = try! bitset::allocate(&mut arena, 257);
55 +
    for i in 0..257 {
56 +
        bitset::put(&mut liveSet, i);
57 +
    }
58 +
    let mut out = [liveSet];
59 +
    let live = regalloc::liveness::LiveInfo {
60 +
        liveIn: &mut [], liveOut: &mut out[..], defs: &mut [], uses: &mut [], blockCount: 1, maxReg: 257,
61 +
    };
62 +
    let func = il::Fn {
63 +
        name: "pressure", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
64 +
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut [], locs: &[], preds: &[], loopDepth: 0 }],
65 +
    };
66 +
    let mut failed = false;
67 +
    try regalloc::spill::analyze(&func, &live, 23, 11, 8, &mut arena) catch {
68 +
        set failed = true;
69 +
    };
70 +
    assert failed;
71 +
}
72 +
73 +
/// Frame size arithmetic must not wrap into a negative stack allocation.
74 +
unsafe fn testFrameSizeOverflow() throws (TestError) {
75 +
    for size in [0x7fffffff, -1] {
76 +
        let mut failed = false;
77 +
        try rv64::emit::computeFrame(size, 0, 0, false, false) catch error {
78 +
            assert error == rv64::Error::Capacity;
79 +
            set failed = true;
80 +
        };
81 +
        assert failed;
82 +
    }
83 +
    let frame = try rv64::emit::computeFrame(16, 0, 0, false, false) catch {
84 +
        throw TestError::Failed;
85 +
    };
86 +
    assert frame.totalSize == 32;
87 +
}
88 +
89 +
/// Oversized reserves must fail before narrowing their byte counts.
90 +
unsafe fn testReserveOverflow() throws (TestError) {
91 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
92 +
    let mut scratch = alloc::new(&mut REGISTER_SCRATCH[..]);
93 +
    let mut generator = try! rv64::beginProgram(
94 +
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
95 +
    );
96 +
    let mut instructions = [
97 +
        il::Instr::Reserve { dst: il::Reg { n: 0 }, size: il::Val::Imm(0x100000000), alignment: 8 },
98 +
        il::Instr::Ret { val: nil },
99 +
    ];
100 +
    let func = il::Fn {
101 +
        name: "reserve", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
102 +
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }],
103 +
    };
104 +
    rv64::generateFunction(&mut generator, &func, &mut scratch);
105 +
    assert generator.e.error == rv64::Error::Capacity;
106 +
    assert scratch.offset == 0;
107 +
108 +
    let config = rv64::targetConfig();
109 +
    let ralloc = try! regalloc::allocate(&func, &config, &mut scratch);
110 +
    set generator.e.error = rv64::Error::Symbol;
111 +
    rv64::isel::selectFn(&mut generator.e, &ralloc, &func);
112 +
    assert generator.e.error == rv64::Error::Symbol;
113 +
}
114 +
115 +
/// An exhausted instruction buffer records failure and stops writes.
116 +
unsafe fn testCodeCapacity() throws (TestError) {
117 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
118 +
    let mut e = try rv64::emit::emitter(&mut arena, false) catch {
119 +
        throw TestError::Failed;
120 +
    };
121 +
    set e.code = &mut e.code[..0];
122 +
    rv64::emit::emit(&mut e, encode::nop());
123 +
    assert e.error == rv64::Error::Capacity;
124 +
    rv64::emit::emit(&mut e, encode::ebreak());
125 +
    assert e.codeLen == 0;
126 +
}
127 +
128 +
/// Generator initialization must report exhausted storage without trapping.
129 +
unsafe fn testGeneratorAllocationFailure() throws (TestError) {
130 +
    let mut arena = alloc::new(&mut REGISTER_SCRATCH[..64]);
131 +
    set arena.offset = 8;
132 +
    let mut failed = false;
133 +
    try rv64::beginProgram(
134 +
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
135 +
    ) catch error {
136 +
        assert error == rv64::Error::Allocation;
137 +
        set failed = true;
138 +
    };
139 +
    assert failed;
140 +
    assert arena.offset == 8;
141 +
}
142 +
143 +
/// Function allocation failure preserves the scratch checkpoint.
144 +
unsafe fn testFunctionAllocationFailure() throws (TestError) {
145 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
146 +
    let mut scratch = alloc::new(&mut REGISTER_SCRATCH[..8]);
147 +
    set scratch.offset = 8;
148 +
    let mut generator = try! rv64::beginProgram(
149 +
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
150 +
    );
151 +
    let mut instructions = [il::Instr::Ret { val: il::Val::Imm(0) }];
152 +
    let func = il::Fn {
153 +
        name: "allocation", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
154 +
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }],
155 +
    };
156 +
    rv64::generateFunction(&mut generator, &func, &mut scratch);
157 +
    assert generator.e.error == rv64::Error::Allocation;
158 +
    assert scratch.offset == 8;
159 +
}
160 +
161 +
/// Full metadata tables reject appends before requesting more storage.
162 +
unsafe fn testMetadataCapacity() throws (TestError) {
163 +
    for kind in 0..5 {
164 +
        let mut e = boundsEmitter();
165 +
        match kind {
166 +
            case 0 => {
167 +
                set e.pendingBranches.len = 0;
168 +
                set e.pendingBranches.cap = 0;
169 +
                rv64::emit::recordBranch(&mut e, 0, rv64::emit::BranchKind::Jump);
170 +
            }
171 +
            case 1 => {
172 +
                set e.pendingCalls.len = 0;
173 +
                set e.pendingCalls.cap = 0;
174 +
                rv64::emit::recordCall(&mut e, "call");
175 +
            }
176 +
            case 2 => {
177 +
                set e.pendingJumps.len = 0;
178 +
                set e.pendingJumps.cap = 0;
179 +
                rv64::emit::recordJumpAt(&mut e, "jump", rv64::ZERO, 0);
180 +
            }
181 +
            case 3 => {
182 +
                set e.pendingAddrLoads.len = 0;
183 +
                set e.pendingAddrLoads.cap = 0;
184 +
                rv64::emit::recordDataAddrLoad(&mut e, "data", rv64::A0);
185 +
            }
186 +
            else => {
187 +
                set e.funcs.len = 0;
188 +
                set e.funcs.cap = 0;
189 +
                rv64::emit::recordFunc(&mut e, "function");
190 +
            }
191 +
        }
192 +
        assert e.error == rv64::Error::Capacity;
193 +
        let length = e.codeLen;
194 +
        rv64::emit::emit(&mut e, encode::nop());
195 +
        assert e.codeLen == length;
196 +
    }
197 +
}
198 +
199 +
/// Debug entries report exhausted storage without writing beyond the buffer.
200 +
unsafe fn testDebugCapacity() throws (TestError) {
201 +
    let mut e = boundsEmitter();
202 +
    rv64::emit::recordSrcLoc(&mut e, il::SrcLoc { moduleId: 0, offset: 0 });
203 +
    assert e.error == rv64::Error::Capacity;
204 +
    assert e.debugEntriesLen == 0;
205 +
}
206 +
207 +
/// Block labels report an out-of-range table index.
208 +
unsafe fn testBlockCapacity() throws (TestError) {
209 +
    let mut e = boundsEmitter();
210 +
    let count = e.labels.blockOffsets.len;
211 +
    rv64::emit::recordBlock(&mut e, count);
212 +
    assert e.error == rv64::Error::Capacity;
213 +
    assert e.labels.blockCount == 0;
214 +
}
215 +
216 +
/// Unresolved calls preserve their placeholder instructions and report a symbol error.
217 +
unsafe fn testMissingCallSymbol() throws (TestError) {
218 +
    let mut e = boundsEmitter();
219 +
    rv64::emit::recordCall(&mut e, "missing");
220 +
    rv64::emit::patchCalls(&mut e);
221 +
    assert e.error == rv64::Error::Symbol;
222 +
    assert e.code[0] == encode::nop();
223 +
    assert e.code[1] == encode::nop();
224 +
}
225 +
226 +
/// Local jumps outside their immediate range preserve the reserved instruction.
227 +
unsafe fn testLocalJumpRange() throws (TestError) {
228 +
    let mut e = boundsEmitter();
229 +
    rv64::emit::recordBranch(&mut e, 0, rv64::emit::BranchKind::Jump);
230 +
    set e.labels.blockOffsets[0] = 0x200000;
231 +
    set e.labels.blockCount = 1;
232 +
    rv64::emit::patchLocalBranches(&mut e);
233 +
    assert e.error == rv64::Error::Relocation;
234 +
    assert e.code[0] == encode::nop();
235 +
}
236 +
237 +
/// Assembly jumps reject targets beyond the J-type displacement range.
238 +
unsafe fn testAssemblyJumpRange() throws (TestError) {
239 +
    let mut e = boundsEmitter();
240 +
    rv64::emit::emit(&mut e, encode::nop());
241 +
    rv64::emit::recordJumpAt(&mut e, "far", rv64::ZERO, 0);
242 +
    rv64::emit::recordFuncOffsetAt(&mut e, "far", 0x80000);
243 +
    rv64::emit::patchJumps(&mut e);
244 +
    assert e.error == rv64::Error::Relocation;
245 +
    assert e.code[0] == encode::nop();
246 +
}
247 +
248 +
/// Missing data symbols preserve address-load placeholders.
249 +
unsafe fn testMissingDataSymbol() throws (TestError) {
250 +
    let mut e = boundsEmitter();
251 +
    static entries: [dict::Entry; 4] = undefined;
252 +
    let map = try! data::buildMap(&[], &mut entries[..]);
253 +
    rv64::emit::recordDataAddrLoad(&mut e, "missing", rv64::A0);
254 +
    rv64::emit::patchAddrLoads(&mut e, &map);
255 +
    assert e.error == rv64::Error::Symbol;
256 +
    assert e.code[0] == encode::nop();
257 +
    assert e.code[1] == encode::nop();
258 +
}
259 +
260 +
/// Data addresses outside the supported signed address range are rejected.
261 +
unsafe fn testDataAddressRange() throws (TestError) {
262 +
    let mut e = boundsEmitter();
263 +
    static entries: [dict::Entry; 4] = undefined;
264 +
    static symbols: [data::DataSym; 1] = [data::DataSym { name: "far", addr: 0x80000000 }];
265 +
    let map = try! data::buildMap(&symbols[..], &mut entries[..]);
266 +
    rv64::emit::recordDataAddrLoad(&mut e, "far", rv64::A0);
267 +
    rv64::emit::patchAddrLoads(&mut e, &map);
268 +
    assert e.error == rv64::Error::Relocation;
269 +
    assert e.code[0] == encode::nop();
270 +
    assert e.code[1] == encode::nop();
271 +
}
272 +
273 +
/// Data section sizes must not wrap when alignment exceeds the address space.
274 +
unsafe fn testDataLayoutOverflow() throws (TestError) {
275 +
    let items = [il::Data { name: "value", size: 8, alignment: 8, readOnly: true, isZeroInit: false, values: &[] }];
276 +
    let mut symbols: [data::DataSym; 1] = undefined;
277 +
    let mut count: u32 = 0;
278 +
    let mut failed = false;
279 +
    try data::layoutSectionAtOffset(&items[..], &mut symbols[..], &mut count, 0, 0xfffffffc, true) catch error {
280 +
        assert error == data::Error::Overflow;
281 +
        set failed = true;
282 +
    };
283 +
    assert failed;
284 +
    assert count == 0;
285 +
}
286 +
287 +
/// Initializers must not write past their declared data slot.
288 +
unsafe fn testDataInitializerBounds() throws (TestError) {
289 +
    try testDataMapCapacity();
290 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
291 +
    let e = try! rv64::emit::emitter(&mut arena, false);
292 +
    static entries: [dict::Entry; 4] = undefined;
293 +
    let map = try! data::buildMap(&[], &mut entries[..]);
294 +
    let mut items = [il::Data {
295 +
        name: "small", size: 1, alignment: 1, readOnly: true, isZeroInit: false,
296 +
        values: &[il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: 7 }, count: 1 }],
297 +
    }];
298 +
    let mut bytes = [0xaa as u8; 16];
299 +
    let mut failed = false;
300 +
    try data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..], true) catch error {
301 +
        assert error == data::Error::Capacity;
302 +
        set failed = true;
303 +
    };
304 +
    assert failed;
305 +
    assert bytes[0] == 0xaa and bytes[1] == 0xaa;
306 +
307 +
    set items[0].size = 8;
308 +
    let written = try! data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..8], true);
309 +
    assert written == 8;
310 +
    assert bytes[0] == 7;
311 +
    for i in 1..8 {
312 +
        assert bytes[i] == 0;
313 +
    }
314 +
    assert bytes[8] == 0xaa;
315 +
}
316 +
317 +
/// Data maps must reject inputs beyond the backing dictionary capacity.
318 +
unsafe fn testDataMapCapacity() throws (TestError) {
319 +
    static mapCapacityEntries: [dict::Entry; 2] = undefined;
320 +
    static mapCapacitySymbols: [data::DataSym; 2] = [
321 +
        data::DataSym { name: "first", addr: 0 },
322 +
        data::DataSym { name: "second", addr: 8 },
323 +
    ];
324 +
    let mut failed = false;
325 +
    try data::buildMap(&mapCapacitySymbols[..], &mut mapCapacityEntries[..]) catch error {
326 +
        assert error == data::Error::Capacity;
327 +
        set failed = true;
328 +
    };
329 +
    assert failed;
330 +
}
331 +
332 +
/// Create an emitter in reusable static storage for bounds checks.
333 +
unsafe fn boundsEmitter() -> rv64::emit::Emitter {
334 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
335 +
    return try! rv64::emit::emitter(&mut arena, false);
336 +
}
337 +
338 +
/// Verify recoverable backend bounds and allocation failures.
339 +
unsafe fn testBackendBounds() throws (TestError) {
340 +
    try testFunctionMapCapacity();
341 +
    try testRegisterLimit();
342 +
    try testSpillCandidateLimit();
343 +
    try testFrameSizeOverflow();
344 +
    try testReserveOverflow();
345 +
    try testCallArgumentCapacity();
346 +
    try testBlockArgumentCapacity();
347 +
    try testCodeCapacity();
348 +
    try testGeneratorAllocationFailure();
349 +
    try testFunctionAllocationFailure();
350 +
    try testMetadataCapacity();
351 +
    try testDebugCapacity();
352 +
    try testBlockCapacity();
353 +
    try testMissingCallSymbol();
354 +
    try testLocalJumpRange();
355 +
    try testAssemblyJumpRange();
356 +
    try testMissingDataSymbol();
357 +
    try testDataAddressRange();
358 +
    try testDataLayoutOverflow();
359 +
    try testDataAlignment();
360 +
    try testDataInitializerBounds();
361 +
    try testDataEmissionSymbol();
362 +
    try testFunctionInitializerSymbol();
363 +
    try testOutputPrefixCapacity();
364 +
    try testMissingEntry();
365 +
    try testCodeBaseOverflow();
366 +
}
367 +
368 +
/// Function labels reject a full dictionary before insertion.
369 +
unsafe fn testFunctionMapCapacity() throws (TestError) {
370 +
    let mut e = boundsEmitter();
371 +
    static entries: [dict::Entry; 2] = undefined;
372 +
    set e.labels.funcs = dict::init(&mut entries[..]);
373 +
    rv64::emit::recordFuncOffsetAt(&mut e, "first", 0);
374 +
    rv64::emit::recordFuncOffsetAt(&mut e, "second", 1);
375 +
    assert e.error == rv64::Error::Capacity;
376 +
    assert e.labels.funcs.count == 1;
377 +
}
378 +
379 +
/// Data initializers report unresolved symbols before writing the slot.
380 +
unsafe fn testDataEmissionSymbol() throws (TestError) {
381 +
    let e = boundsEmitter();
382 +
    static entries: [dict::Entry; 4] = undefined;
383 +
    let map = try! data::buildMap(&[], &mut entries[..]);
384 +
    let items = [il::Data { name: "symbol", size: 8, alignment: 8, readOnly: true, isZeroInit: false,
385 +
        values: &[il::DataValue { item: il::DataItem::Sym("missing"), count: 1 }] }];
386 +
    let mut bytes = [0xaa as u8; 8];
387 +
    let mut failed = false;
388 +
    try data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..], true) catch error {
389 +
        assert error == data::Error::Symbol;
390 +
        set failed = true;
391 +
    };
392 +
    assert failed;
393 +
    assert bytes[0] == 0xaa;
394 +
}
395 +
396 +
/// Function initializers report unresolved symbols before writing the slot.
397 +
unsafe fn testFunctionInitializerSymbol() throws (TestError) {
398 +
    let e = boundsEmitter();
399 +
    static entries: [dict::Entry; 4] = undefined;
400 +
    let map = try! data::buildMap(&[], &mut entries[..]);
401 +
    let items = [il::Data { name: "symbol", size: 8, alignment: 8, readOnly: true, isZeroInit: false,
402 +
        values: &[il::DataValue { item: il::DataItem::Fn("missing"), count: 1 }] }];
403 +
    let mut bytes = [0xaa as u8; 8];
404 +
    let mut failed = false;
405 +
    try data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..], true) catch error {
406 +
        assert error == data::Error::Symbol;
407 +
        set failed = true;
408 +
    };
409 +
    assert failed;
410 +
    assert bytes[0] == 0xaa;
411 +
}
412 +
413 +
/// The read-only prefix must fit its output buffer.
414 +
unsafe fn testOutputPrefixCapacity() throws (TestError) {
415 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
416 +
    let mut generator = try! rv64::beginProgram(
417 +
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
418 +
    );
419 +
    static symbols: [data::DataSym; 1] = undefined;
420 +
    static entries: [dict::Entry; 4] = undefined;
421 +
    let storage = rv64::Storage { dataSyms: &mut symbols[..], dataSymEntries: &mut entries[..] };
422 +
    let mut failed = false;
423 +
    try rv64::finishProgram(&mut generator, &[], storage, "x", &mut [], &mut []) catch error {
424 +
        assert error == rv64::Error::Capacity;
425 +
        set failed = true;
426 +
    };
427 +
    assert failed;
428 +
}
429 +
430 +
/// A reserved entry jump requires an entry symbol.
431 +
unsafe fn testMissingEntry() throws (TestError) {
432 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
433 +
    let mut generator = try! rv64::beginProgram(
434 +
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::Reserved(nil), debug: false }, &mut arena
435 +
    );
436 +
    static symbols: [data::DataSym; 1] = undefined;
437 +
    static entries: [dict::Entry; 4] = undefined;
438 +
    let storage = rv64::Storage { dataSyms: &mut symbols[..], dataSymEntries: &mut entries[..] };
439 +
    let mut failed = false;
440 +
    try rv64::finishProgram(&mut generator, &[], storage, "", &mut [], &mut []) catch error {
441 +
        assert error == rv64::Error::Symbol;
442 +
        set failed = true;
443 +
    };
444 +
    assert failed;
445 +
}
446 +
447 +
/// Code placement must not wrap after the read-only section.
448 +
unsafe fn testCodeBaseOverflow() throws (TestError) {
449 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
450 +
    let mut generator = try! rv64::beginProgram(
451 +
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
452 +
    );
453 +
    static symbols: [data::DataSym; 1] = undefined;
454 +
    static entries: [dict::Entry; 4] = undefined;
455 +
    let storage = rv64::Storage { dataSyms: &mut symbols[..], dataSymEntries: &mut entries[..] };
456 +
    let items = [il::Data { name: "large", size: 0xfffeffff, alignment: 1, readOnly: true, isZeroInit: true, values: &[] }];
457 +
    let mut failed = false;
458 +
    try rv64::finishProgram(&mut generator, &items[..], storage, "", &mut [], &mut []) catch error {
459 +
        assert error == rv64::Error::Data(data::Error::Overflow);
460 +
        set failed = true;
461 +
    };
462 +
    assert failed;
463 +
}
464 +
465 +
/// Call arguments must fit the architectural argument register bank.
466 +
unsafe fn testCallArgumentCapacity() throws (TestError) {
467 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
468 +
    let mut scratch = alloc::new(&mut REGISTER_SCRATCH[..]);
469 +
    let mut generator = try! rv64::beginProgram(
470 +
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
471 +
    );
472 +
    let args = [il::Val::Imm(0); 9];
473 +
    let mut instructions = [
474 +
        il::Instr::Call { retTy: il::Type::W64, dst: nil, func: il::Val::FnAddr("target"), args: &args[..] },
475 +
        il::Instr::Ret { val: nil },
476 +
    ];
477 +
    let func = il::Fn {
478 +
        name: "call", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: false,
479 +
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }],
480 +
    };
481 +
    rv64::generateFunction(&mut generator, &func, &mut scratch);
482 +
    assert generator.e.error == rv64::Error::Capacity;
483 +
    assert scratch.offset == 0;
484 +
485 +
    set generator.e.error = nil;
486 +
    set instructions[0] = il::Instr::Call {
487 +
        retTy: il::Type::W64, dst: nil, func: il::Val::FnAddr("target"), args: &args[..8],
488 +
    };
489 +
    rv64::generateFunction(&mut generator, &func, &mut scratch);
490 +
    assert generator.e.error == nil;
491 +
    assert scratch.offset == 0;
492 +
}
493 +
494 +
/// Block argument lists must fit the parallel move storage.
495 +
unsafe fn testBlockArgumentCapacity() throws (TestError) {
496 +
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
497 +
    let mut scratch = alloc::new(&mut REGISTER_SCRATCH[..]);
498 +
    let mut generator = try! rv64::beginProgram(
499 +
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
500 +
    );
501 +
    let mut args = [il::Val::Imm(0); 17];
502 +
    let mut params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }; 17];
503 +
    for i in 0..params.len {
504 +
        set params[i].value.n = i;
505 +
    }
506 +
    let mut entry = [il::Instr::Jmp { target: 1, args: &mut args[..] }];
507 +
    let mut exit = [il::Instr::Ret { val: nil }];
508 +
    let mut blocks = [
509 +
        il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 },
510 +
        il::Block { label: "exit", params: &params[..], instrs: &mut exit[..], locs: &[], preds: &[0], loopDepth: 0 },
511 +
    ];
512 +
    let func = il::Fn {
513 +
        name: "block", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
514 +
        blocks: &blocks[..],
515 +
    };
516 +
    rv64::generateFunction(&mut generator, &func, &mut scratch);
517 +
    assert generator.e.error == rv64::Error::Capacity;
518 +
    assert scratch.offset == 0;
519 +
520 +
    set generator.e.error = nil;
521 +
    set entry[0] = il::Instr::Jmp { target: 1, args: &mut args[..16] };
522 +
    set blocks[1].params = &params[..16];
523 +
    rv64::generateFunction(&mut generator, &func, &mut scratch);
524 +
    assert generator.e.error == nil;
525 +
    assert scratch.offset == 0;
526 +
}
527 +
528 +
/// Initialized and zero-filled data require power-of-two alignment.
529 +
unsafe fn testDataAlignment() throws (TestError) {
530 +
    for zeroInit in [false, true] {
531 +
        for alignment in [0, 3, 8] {
532 +
            let items = [il::Data {
533 +
                name: "aligned", size: 8, alignment, readOnly: true,
534 +
                isZeroInit: zeroInit, values: &[],
535 +
            }];
536 +
            let mut symbols: [data::DataSym; 1] = undefined;
537 +
            let mut count: u32 = 0;
538 +
            let mut failed = false;
539 +
            let size = try data::layoutSectionAtOffset(
540 +
                &items[..], &mut symbols[..], &mut count, 16, 1, true
541 +
            ) catch error {
542 +
                assert error == data::Error::Alignment;
543 +
                set failed = true;
544 +
                0;
545 +
            };
546 +
            assert failed == (alignment <> 8);
547 +
            if not failed {
548 +
                assert size == 16 and count == 1;
549 +
                assert symbols[0].addr == 24;
550 +
            } else {
551 +
                assert count == 0;
552 +
            }
553 +
        }
554 +
    }
555 +
}