compiler/
lib/
scripts/
seed/
sublime/
test/
tests/
backend.rad
22.0 KiB
run
2.7 KiB
runner.rad
10.3 KiB
vim/
.gitignore
336 B
.gitsigners
112 B
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
4.1 KiB
README
2.5 KiB
STYLE
2.6 KiB
std.lib
1.2 KiB
std.lib.test
347 B
test/backend.rad
raw
| 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: ¶ms[..], 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 = ¶ms[..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 | } |