rv64: Return bounded code-generation failures
7cf509a80a7cd249344a4c0e3afa9c7cf18b341c82a77a1c66f4fe3ae2e4433a
Assisted-by: Codex:gpt-6
1 parent
7b0dbbf9
compiler/radiance.rad
+3 -3
| 1013 | 1013 | case CodegenEntryMode::DefaultEntry => { |
|
| 1014 | 1014 | set entryPatch = rv64::EntryPatch::Reserved(nil); |
|
| 1015 | 1015 | } |
|
| 1016 | 1016 | else => {} |
|
| 1017 | 1017 | } |
|
| 1018 | - | let mut generator = rv64::beginProgram( |
|
| 1018 | + | let mut generator = try rv64::beginProgram( |
|
| 1019 | 1019 | rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug, placement: rv64::image::Placement::Hosted }, |
|
| 1020 | 1020 | &mut res.arena |
|
| 1021 | - | ); |
|
| 1021 | + | ) catch { throw error(&["code generation workspace exhausted"]); }; |
|
| 1022 | 1022 | let mut codegenCtx = codegen::Context { |
|
| 1023 | 1023 | generator: &mut generator, |
|
| 1024 | 1024 | fnArena: (&mut *fnArena) as *unsafe mut alloc::Arena, |
|
| 1025 | 1025 | }; |
|
| 1026 | 1026 | let mut low = lower::lowerer( |
| 1047 | 1047 | } |
|
| 1048 | 1048 | if let path = codegenOptions.logPath { |
|
| 1049 | 1049 | pkgLog(entryPkg, &["generating code", "(", path, ")", ".."]); |
|
| 1050 | 1050 | } |
|
| 1051 | 1051 | return try rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]) catch { |
|
| 1052 | - | throw error(&["invalid native image layout"]); |
|
| 1052 | + | throw error(&["code generation failed: capacity, symbol, relocation, or image layout"]); |
|
| 1053 | 1053 | }; |
|
| 1054 | 1054 | } |
|
| 1055 | 1055 | ||
| 1056 | 1056 | /// Source exports selected for one binary RIL package. |
|
| 1057 | 1057 | 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: ®alloc::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 |
test/native/build.rad
+1 -1
| 19 | 19 | /// Compile data accesses and conditional finish writes into a native image. |
|
| 20 | 20 | @default unsafe fn main(env: *sys::Env) -> i32 { |
|
| 21 | 21 | assert env.args.len == 2; |
|
| 22 | 22 | let mut arena = alloc::new(&mut MEMORY[..]); |
|
| 23 | 23 | let mut scratch = alloc::new(&mut SCRATCH[..]); |
|
| 24 | - | let mut generator = rv64::beginProgram(rv64::ProgramOptions { |
|
| 24 | + | let mut generator = try! rv64::beginProgram(rv64::ProgramOptions { |
|
| 25 | 25 | entryPatch: rv64::EntryPatch::None, debug: false, |
|
| 26 | 26 | placement: image::Placement::Physical { |
|
| 27 | 27 | code: 0x80010000, roData: 0x80018000, rwData: 0x80020000, entry: 0x80010000, |
|
| 28 | 28 | }, |
|
| 29 | 29 | }, &mut arena); |