compiler: Collect inline instruction names in checked code
1de1349d4f7c098e324e0c9b25e1188bdf0b03aa38d379c94feca098fb986ab6
1 parent
edab4b3c
lib/std/lang/il/binary/collect.rad
+28 -19
| 85 | 85 | try value(names, owner, item); |
|
| 86 | 86 | } |
|
| 87 | 87 | } |
|
| 88 | 88 | ||
| 89 | 89 | /// Collect all symbolic instruction operands. |
|
| 90 | - | unsafe fn instruction 'tables (names: &mut Names 'tables, owner: *[u8], instr: il::Instr) throws (binary::Error) { |
|
| 91 | - | match instr { |
|
| 92 | - | case il::Instr::Reserve { size, .. } => try value(names, owner, size), |
|
| 93 | - | case il::Instr::Blit { size, .. } => try value(names, owner, size), |
|
| 94 | - | case il::Instr::Store { src, .. } => try value(names, owner, src), |
|
| 95 | - | case il::Instr::Copy { val, .. } => try value(names, owner, val), |
|
| 96 | - | case il::Instr::Zext { val, .. } => try value(names, owner, val), |
|
| 97 | - | case il::Instr::Sext { val, .. } => try value(names, owner, val), |
|
| 98 | - | case il::Instr::BinOp { a, b, .. } => { |
|
| 99 | - | try value(names, owner, a); |
|
| 100 | - | try value(names, owner, b); |
|
| 101 | - | }, |
|
| 102 | - | case il::Instr::UnOp { a, .. } => try value(names, owner, a), |
|
| 90 | + | unsafe fn instruction 'tables (names: &mut Names 'tables, owner: *[u8], instr: &il::Instr) throws (binary::Error) { |
|
| 91 | + | match *instr { |
|
| 103 | 92 | case il::Instr::Call { func, args, .. } => { |
|
| 104 | 93 | try value(names, owner, func); |
|
| 105 | 94 | try values(names, owner, args); |
|
| 106 | 95 | }, |
|
| 107 | - | case il::Instr::Ret { val } => { |
|
| 108 | - | if let item = val { |
|
| 109 | - | try value(names, owner, item); |
|
| 110 | - | } |
|
| 111 | - | }, |
|
| 112 | 96 | case il::Instr::Jmp { args, .. } => try values(names, owner, args), |
|
| 113 | 97 | case il::Instr::Br { a, b, thenArgs, elseArgs, .. } => { |
|
| 114 | 98 | try value(names, owner, a); |
|
| 115 | 99 | try value(names, owner, b); |
|
| 116 | 100 | try values(names, owner, thenArgs); |
| 121 | 105 | try values(names, owner, defaultArgs); |
|
| 122 | 106 | for branch in cases { |
|
| 123 | 107 | try values(names, owner, branch.args); |
|
| 124 | 108 | } |
|
| 125 | 109 | }, |
|
| 110 | + | else => try fixedInstruction(names, owner, instr), |
|
| 111 | + | } |
|
| 112 | + | } |
|
| 113 | + | ||
| 114 | + | /// Collect symbolic operands stored directly in an instruction. |
|
| 115 | + | fn fixedInstruction 'tables (names: &mut Names 'tables, owner: *[u8], instr: &il::Instr) throws (binary::Error) { |
|
| 116 | + | match *instr { |
|
| 117 | + | case il::Instr::Reserve { size, .. } => try value(names, owner, size), |
|
| 118 | + | case il::Instr::Blit { size, .. } => try value(names, owner, size), |
|
| 119 | + | case il::Instr::Store { src, .. } => try value(names, owner, src), |
|
| 120 | + | case il::Instr::Copy { val, .. } => try value(names, owner, val), |
|
| 121 | + | case il::Instr::Zext { val, .. } => try value(names, owner, val), |
|
| 122 | + | case il::Instr::Sext { val, .. } => try value(names, owner, val), |
|
| 123 | + | case il::Instr::BinOp { a, b, .. } => { |
|
| 124 | + | try value(names, owner, a); |
|
| 125 | + | try value(names, owner, b); |
|
| 126 | + | }, |
|
| 127 | + | case il::Instr::UnOp { a, .. } => try value(names, owner, a), |
|
| 128 | + | case il::Instr::Ret { val } => { |
|
| 129 | + | if let item = val { |
|
| 130 | + | try value(names, owner, item); |
|
| 131 | + | } |
|
| 132 | + | }, |
|
| 126 | 133 | case il::Instr::Ecall { num, a0, a1, a2, a3, .. } => { |
|
| 127 | 134 | try value(names, owner, num); |
|
| 128 | 135 | try value(names, owner, a0); |
|
| 129 | 136 | try value(names, owner, a1); |
|
| 130 | 137 | try value(names, owner, a2); |
| 137 | 144 | try value(names, owner, handle); try value(names, owner, offset); try value(names, owner, source); |
|
| 138 | 145 | }, |
|
| 139 | 146 | case il::Instr::Load { .. }, il::Instr::Sload { .. }, il::Instr::Unreachable, |
|
| 140 | 147 | il::Instr::Ebreak, il::Instr::MemoryFence => { |
|
| 141 | 148 | }, |
|
| 149 | + | case il::Instr::Call { .. }, il::Instr::Jmp { .. }, il::Instr::Br { .. }, il::Instr::Switch { .. } => |
|
| 150 | + | panic "fixedInstruction: expected inline operands", |
|
| 142 | 151 | } |
|
| 143 | 152 | } |
|
| 144 | 153 | ||
| 145 | 154 | /// Check that a definition belongs to the selected package. |
|
| 146 | 155 | fn definition 'tables (names: &mut Names 'tables, owner: *[u8], name: *[u8]) throws (binary::Error) { |
| 187 | 196 | try collectData(names, owner, &program.data[..]); |
|
| 188 | 197 | for func in program.fns { |
|
| 189 | 198 | try definition(names, owner, func.name); |
|
| 190 | 199 | for block in func.blocks { |
|
| 191 | 200 | for instr in block.instrs { |
|
| 192 | - | try instruction(names, owner, instr); |
|
| 201 | + | try instruction(names, owner, &instr); |
|
| 193 | 202 | } |
|
| 194 | 203 | } |
|
| 195 | 204 | } |
|
| 196 | 205 | return binary::Package { |
|
| 197 | 206 | symbols: &names.symbols[..names.symbolCount], name: owner, |
lib/std/lang/il/binary/decodeTests.rad
+70 -0
| 360 | 360 | try testing::expectBytesEq(package.dependencies[0], "dep"); |
|
| 361 | 361 | try testing::expect(package.symbols.len == 4); |
|
| 362 | 362 | } |
|
| 363 | 363 | } |
|
| 364 | 364 | ||
| 365 | + | /// Fixed instruction operands retain symbol order and bounded table writes. |
|
| 366 | + | @test unsafe fn fixedInstructionCollection() throws (testing::TestError) { |
|
| 367 | + | let r = il::Reg { n: 0 }; |
|
| 368 | + | let a = il::Val::DataSym("z::data"); |
|
| 369 | + | let b = il::Val::FnAddr("a::fn"); |
|
| 370 | + | let c = il::Val::DataSym("c::data"); |
|
| 371 | + | let d = il::Val::FnAddr("d::fn"); |
|
| 372 | + | let e = il::Val::DataSym("e::data"); |
|
| 373 | + | let single: [*[u8]; 2] = ["z::data", "z"]; |
|
| 374 | + | let pair: [*[u8]; 4] = ["z::data", "z", "a::fn", "a"]; |
|
| 375 | + | for instr in [ |
|
| 376 | + | il::Instr::Reserve { dst: r, size: a, alignment: 8 }, |
|
| 377 | + | il::Instr::Blit { dst: r, src: r, size: a }, |
|
| 378 | + | il::Instr::Store { typ: il::Type::W64, src: a, dst: r, offset: 0 }, |
|
| 379 | + | il::Instr::Copy { dst: r, val: a }, |
|
| 380 | + | il::Instr::Zext { typ: il::Type::W8, dst: r, val: a }, |
|
| 381 | + | il::Instr::Sext { typ: il::Type::W8, dst: r, val: a }, |
|
| 382 | + | il::Instr::UnOp { op: il::UnOp::Neg, typ: il::Type::W64, dst: r, a }, |
|
| 383 | + | il::Instr::Ret { val: a }, |
|
| 384 | + | il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: r, a, b: a }, |
|
| 385 | + | ] { try checkInstructionNames(instr, &single[..]); } |
|
| 386 | + | for instr in [ |
|
| 387 | + | il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: r, a, b }, |
|
| 388 | + | il::Instr::DeviceRead { typ: il::Type::W8, dst: r, handle: a, offset: b }, |
|
| 389 | + | ] { try checkInstructionNames(instr, &pair[..]); } |
|
| 390 | + | try checkInstructionNames(il::Instr::Ecall { dst: r, num: a, a0: b, a1: c, a2: d, a3: e }, |
|
| 391 | + | &["z::data", "z", "a::fn", "a", "c::data", "c", "d::fn", "d", "e::data", "e"]); |
|
| 392 | + | try checkInstructionNames(il::Instr::DeviceWrite { typ: il::Type::W8, handle: a, offset: b, value: c }, |
|
| 393 | + | &["z::data", "z", "a::fn", "a", "c::data", "c"]); |
|
| 394 | + | for instr in [ |
|
| 395 | + | il::Instr::Load { typ: il::Type::W8, dst: r, src: r, offset: 0 }, |
|
| 396 | + | il::Instr::Sload { typ: il::Type::W8, dst: r, src: r, offset: 0 }, |
|
| 397 | + | il::Instr::Ret { val: nil }, |
|
| 398 | + | il::Instr::Copy { dst: r, val: il::Val::Imm(9) }, |
|
| 399 | + | il::Instr::Unreachable, il::Instr::Ebreak, il::Instr::MemoryFence, |
|
| 400 | + | ] { try checkInstructionNames(instr, &[]); } |
|
| 401 | + | } |
|
| 402 | + | ||
| 403 | + | /// Collect one instruction at exact-fit and every shorter symbol capacity. |
|
| 404 | + | unsafe fn checkInstructionNames(instr: il::Instr, expected: &[*[u8]]) throws (testing::TestError) { |
|
| 405 | + | let mut body = [instr]; |
|
| 406 | + | let function = il::Fn { |
|
| 407 | + | name: "p::f", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
|
| 408 | + | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut body[..], locs: &[], preds: &[], loopDepth: 0 }], |
|
| 409 | + | }; |
|
| 410 | + | let local = il::Program { data: &[], fns: &[&function] }; |
|
| 411 | + | for capacity in 0..expected.len + 3 { |
|
| 412 | + | let mut symbols: [*[u8]; 16] = [""; 16]; |
|
| 413 | + | let mut dependencies: [*[u8]; 5] = [""; 5]; |
|
| 414 | + | let symbolTable: 'tables = &mut symbols[..capacity], dependencyTable = &mut dependencies[..] in { |
|
| 415 | + | let mut names = collect::new(symbolTable, dependencyTable); |
|
| 416 | + | let mut failed = false; |
|
| 417 | + | try collect::package(&mut names, "p", local, &[], nil) catch err { |
|
| 418 | + | assert err == binary::Error::Capacity; |
|
| 419 | + | set failed = true; |
|
| 420 | + | }; |
|
| 421 | + | assert failed == (capacity < expected.len + 2); |
|
| 422 | + | assert names.symbolCount <= capacity; |
|
| 423 | + | if not failed { |
|
| 424 | + | assert names.symbolCount == expected.len + 2; |
|
| 425 | + | try testing::expectBytesEq(names.symbols[0], "p"); |
|
| 426 | + | try testing::expectBytesEq(names.symbols[1], "p::f"); |
|
| 427 | + | for name, i in expected { try testing::expectBytesEq(names.symbols[i + 2], name); } |
|
| 428 | + | assert names.dependencyCount == expected.len / 2; |
|
| 429 | + | } |
|
| 430 | + | } |
|
| 431 | + | for i in capacity..symbols.len { assert symbols[i].len == 0; } |
|
| 432 | + | } |
|
| 433 | + | } |
|
| 434 | + | ||
| 365 | 435 | /// Data collection retains first-use order and unique dependencies. |
|
| 366 | 436 | @test unsafe fn dataCollectionOrder() throws (testing::TestError) { |
|
| 367 | 437 | let item = il::Data { |
|
| 368 | 438 | name: "p::data", size: 64, alignment: 8, readOnly: true, isZeroInit: false, |
|
| 369 | 439 | values: &[ |