compiler/
lib/
examples/
std/
arch/
rv64/
asm/
image/
shared/
asm.rad
24.8 KiB
atomicTests.rad
5.0 KiB
atomics.rad
3.6 KiB
bounds.rad
21.0 KiB
decode.rad
15.2 KiB
emit.rad
33.2 KiB
encode.rad
22.0 KiB
image.rad
4.5 KiB
isel.rad
52.1 KiB
printer.rad
13.9 KiB
shared.rad
16.8 KiB
tests.rad
17.2 KiB
rv64.rad
17.5 KiB
char/
collections/
lang/
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.3 KiB
intrinsics.rad
467 B
io.rad
1.7 KiB
lang.rad
276 B
mem.rad
2.3 KiB
sys.rad
179 B
testing.rad
2.4 KiB
tests.rad
15.7 KiB
vec.rad
3.2 KiB
std.rad
281 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
5.4 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.5 KiB
std.lib.test
662 B
lib/std/arch/rv64/bounds.rad
raw
| 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 | /// Code output with guard words for instruction selection checks. |
| 20 | static SELECTION_WORDS: [u32; 130] = [0; 130]; |
| 21 | |
| 22 | /// Dictionary storage for bounded map tests. |
| 23 | unsafe static ENTRIES: [dict::Entry; 4] = undefined; |
| 24 | |
| 25 | /// Build a non-debug generator with a fixed code address. |
| 26 | unsafe fn generator(arena: &mut alloc::Arena) -> super::Generator { |
| 27 | return try! super::beginProgram(super::ProgramOptions { |
| 28 | entryPatch: super::EntryPatch::None, debug: false, |
| 29 | placement: super::image::Placement::Physical { |
| 30 | code: 0x80000000, roData: 0x80001000, rwData: 0x80002000, entry: 0x80000000, |
| 31 | }, |
| 32 | }, arena); |
| 33 | } |
| 34 | |
| 35 | /// Exhaust each emitter storage class and ensure writes stop at the first error. |
| 36 | @test unsafe fn emissionCapacity() throws (testing::TestError) { |
| 37 | for kind in 0..8 { |
| 38 | let mut arena = alloc::new(&mut MEMORY[..]); |
| 39 | let mut e = try! emit::emitter(&mut arena, false); |
| 40 | match kind { |
| 41 | case 0 => { |
| 42 | set e.code = &mut e.code[..0]; |
| 43 | emit::emit(&mut e, encode::nop()); |
| 44 | }, |
| 45 | case 1 => { |
| 46 | set e.pendingBranchesLen = e.pendingBranches.len; |
| 47 | emit::recordBranch(&mut e, 0, emit::BranchKind::Jump); |
| 48 | }, |
| 49 | case 2 => { |
| 50 | set e.pendingCallsLen = e.pendingCalls.len; |
| 51 | emit::recordCall(&mut e, "p::call"); |
| 52 | }, |
| 53 | case 3 => { |
| 54 | set e.pendingJumpsLen = e.pendingJumps.len; |
| 55 | emit::recordJumpAt(&mut e, "p::jump", super::ZERO, 0); |
| 56 | }, |
| 57 | case 4 => { |
| 58 | set e.pendingAddrLoadsLen = e.pendingAddrLoads.len; |
| 59 | emit::recordDataAddrLoad(&mut e, "p::data", super::A0); |
| 60 | }, |
| 61 | case 5 => { |
| 62 | set e.funcsLen = e.funcs.len; |
| 63 | emit::recordFunc(&mut e, "p::call"); |
| 64 | }, |
| 65 | case 6 => { |
| 66 | let entries = &mut ENTRIES[..2]; |
| 67 | set e.labels.funcs = dict::init(&mut entries[..]); |
| 68 | emit::recordFuncOffset(&mut e, "p::first"); |
| 69 | emit::recordFuncOffset(&mut e, "p::second"); |
| 70 | }, |
| 71 | else => { |
| 72 | emit::recordSrcLoc(&mut e, il::SrcLoc { moduleId: 0, offset: 0 }); |
| 73 | }, |
| 74 | } |
| 75 | try testing::expect(e.error == super::Error::Capacity); |
| 76 | let count = e.codeLen; |
| 77 | emit::emit(&mut e, encode::ebreak()); |
| 78 | try testing::expect(e.codeLen == count); |
| 79 | let mut rejected = false; |
| 80 | try emit::check(&e) catch err { |
| 81 | try testing::expect(err == super::Error::Capacity); set rejected = true; |
| 82 | }; |
| 83 | try testing::expect(rejected); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | /// Missing labels and long jumps return errors without corrupting instructions. |
| 88 | @test unsafe fn relocationFailures() throws (testing::TestError) { |
| 89 | let mut arena = alloc::new(&mut MEMORY[..]); |
| 90 | let mut e = try! emit::emitter(&mut arena, false); |
| 91 | emit::recordCall(&mut e, "missing"); |
| 92 | emit::patchCalls(&mut e); |
| 93 | try check(e.error == super::Error::Symbol, "missing function"); |
| 94 | alloc::reset(&mut arena); |
| 95 | set e = try! emit::emitter(&mut arena, false); |
| 96 | emit::recordBranch(&mut e, 0, emit::BranchKind::Jump); |
| 97 | labels::recordBlock(&mut e.labels, 0, 0x200000); |
| 98 | emit::patchLocalBranches(&mut e); |
| 99 | try check(e.error == super::Error::Relocation, "long branch"); |
| 100 | try check(e.code[0] == encode::nop(), "rejected branch unchanged"); |
| 101 | alloc::reset(&mut arena); |
| 102 | set e = try! emit::emitter(&mut arena, false); |
| 103 | let blockCount = e.labels.blockOffsets.len; |
| 104 | emit::recordBlock(&mut e, blockCount); |
| 105 | try check(e.error == super::Error::Capacity, "block capacity"); |
| 106 | } |
| 107 | |
| 108 | /// Arena setup and per-function failures restore their saved offsets for reuse. |
| 109 | @test unsafe fn arenaRecovery() throws (testing::TestError) { |
| 110 | let small = &mut SCRATCH[..64]; |
| 111 | let mut arena = alloc::new(&mut small[..]); |
| 112 | set arena.offset = 8; |
| 113 | let mut failed = false; |
| 114 | try super::beginProgram(super::ProgramOptions { |
| 115 | entryPatch: super::EntryPatch::None, debug: false, placement: super::image::Placement::Hosted, |
| 116 | }, &mut arena) catch err { |
| 117 | try check(err == super::Error::Allocation, "generator allocation"); set failed = true; |
| 118 | }; |
| 119 | try check(failed and arena.offset == 8, "generator arena restored"); |
| 120 | let mut code = alloc::new(&mut MEMORY[..]); |
| 121 | let mut gen = generator(&mut code); |
| 122 | let mut instructions = [ |
| 123 | il::Instr::Copy { dst: il::Reg { n: 0 }, val: il::Val::Imm(1) }, |
| 124 | il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 0 }) }, |
| 125 | ]; |
| 126 | let func = il::Fn { |
| 127 | name: "p::one", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
| 128 | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }], |
| 129 | }; |
| 130 | super::generateFunction(&mut gen, &func, &mut arena); |
| 131 | try check(gen.e.error == super::Error::Allocation and arena.offset == 8, "function arena restored"); |
| 132 | alloc::reset(&mut code); |
| 133 | set gen = generator(&mut code); |
| 134 | let mut scratch = alloc::new(&mut SCRATCH[..]); |
| 135 | set scratch.offset = 16; |
| 136 | super::generateFunction(&mut gen, &func, &mut scratch); |
| 137 | try check(gen.e.error == nil and gen.e.codeLen > 0 and scratch.offset == 16, "function retry"); |
| 138 | alloc::reset(&mut code); |
| 139 | set gen = generator(&mut code); |
| 140 | set instructions[0] = il::Instr::Copy { dst: il::Reg { n: 8192 }, val: il::Val::Imm(1) }; |
| 141 | super::generateFunction(&mut gen, &func, &mut scratch); |
| 142 | try check(gen.e.error == super::Error::Allocation and scratch.offset == 16, "SSA capacity"); |
| 143 | } |
| 144 | |
| 145 | /// Spill candidate storage fails explicitly when too many values are live. |
| 146 | @test unsafe fn registerStorage() throws (testing::TestError) { |
| 147 | let mut arena = alloc::new(&mut SCRATCH[..]); |
| 148 | use arena as bits in { |
| 149 | let liveSet = try! bitset::allocate(&bits, 257); |
| 150 | for i in 0..257 { |
| 151 | bitset::put(liveSet, i); |
| 152 | } |
| 153 | let live = regalloc::liveness::LiveInfo 'bits { |
| 154 | liveIn: &liveSet[..0], liveOut: &liveSet[..], |
| 155 | defs: &liveSet[..0], uses: &liveSet[..0], |
| 156 | words: bitset::wordsFor(257), blockCount: 1, maxReg: 257, |
| 157 | }; |
| 158 | let func = il::Fn { |
| 159 | name: "p::pressure", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
| 160 | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut [], locs: &[], preds: &[], loopDepth: 0 }], |
| 161 | }; |
| 162 | let mut failed = false; |
| 163 | try regalloc::spill::analyze(&func, &live, 23, 11, 8, &bits) catch { |
| 164 | set failed = true; |
| 165 | }; |
| 166 | try testing::expect(failed); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | /// Data output and symbol maps reject insufficient or ambiguous storage. |
| 171 | @test unsafe fn dataStorage() throws (testing::TestError) { |
| 172 | let mut arena = alloc::new(&mut MEMORY[..]); |
| 173 | let e = try! emit::emitter(&mut arena, false); |
| 174 | let syms = &[data::DataSym { name: "p::data", addr: 0x80002000 }]; |
| 175 | let entries = &mut ENTRIES[..2]; |
| 176 | let map = try! data::buildMap(syms, &mut entries[..]); |
| 177 | let items = &[il::Data { |
| 178 | name: "p::data", size: 8, alignment: 8, readOnly: false, isZeroInit: false, |
| 179 | values: &[il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: 1 }, count: 1 }], |
| 180 | }]; |
| 181 | let mut bytes: [u8; 8] = [255; 8]; |
| 182 | let mut failed: u32 = 0; |
| 183 | try data::emitSection(items, &map, &e.labels, 0x80000000, &mut bytes[..7], false) catch err { |
| 184 | try testing::expect(err == data::Error::Capacity); set failed += 1; |
| 185 | }; |
| 186 | try data::buildMap(syms, &mut entries[..1]) catch err { |
| 187 | try testing::expect(err == data::Error::Capacity); set failed += 1; |
| 188 | }; |
| 189 | let larger = &mut ENTRIES[..4]; |
| 190 | unsafe static duplicate: [data::DataSym; 2] = undefined; |
| 191 | set duplicate = [syms[0], syms[0]]; |
| 192 | try data::buildMap(&duplicate[..], &mut larger[..]) catch err { |
| 193 | try testing::expect(err == data::Error::Symbol); set failed += 1; |
| 194 | }; |
| 195 | try testing::expect(failed == 3); |
| 196 | let length = try data::emitSection(items, &map, &e.labels, 0x80000000, &mut bytes[..], false) |
| 197 | catch { |
| 198 | throw testing::TestError::Failed; |
| 199 | }; |
| 200 | try testing::expect(length == 8 and bytes[0] == 1); |
| 201 | } |
| 202 | |
| 203 | /// Inline instruction selection respects every shorter output capacity. |
| 204 | @test unsafe fn inlineSelectionCapacity() throws (testing::TestError) { |
| 205 | let dst = il::Reg { n: 3 }; |
| 206 | let first = il::Reg { n: 0 }; |
| 207 | let second = il::Reg { n: 1 }; |
| 208 | let a = il::Val::Reg(first); |
| 209 | let b = il::Val::Reg(second); |
| 210 | let instructions = [ |
| 211 | il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst, a, b }, |
| 212 | il::Instr::UnOp { op: il::UnOp::Neg, typ: il::Type::W32, dst, a }, |
| 213 | il::Instr::Load { typ: il::Type::W8, dst, src: first, offset: 4096 }, |
| 214 | il::Instr::Sload { typ: il::Type::W16, dst, src: first, offset: -4096 }, |
| 215 | il::Instr::Store { typ: il::Type::W32, src: a, dst: second, offset: 4096 }, |
| 216 | il::Instr::Copy { dst, val: il::Val::Imm(0x123456789abcdef) }, |
| 217 | il::Instr::Reserve { dst, size: il::Val::Imm(32), alignment: 16 }, |
| 218 | il::Instr::Reserve { dst, size: a, alignment: 16 }, |
| 219 | il::Instr::Blit { dst: first, src: second, size: il::Val::Imm(0) }, |
| 220 | il::Instr::Blit { dst: first, src: second, size: il::Val::Imm(8) }, |
| 221 | il::Instr::Blit { dst: first, src: second, size: il::Val::Imm(40) }, |
| 222 | il::Instr::Zext { typ: il::Type::W16, dst, val: a }, |
| 223 | il::Instr::Sext { typ: il::Type::W8, dst, val: a }, |
| 224 | il::Instr::Ret { val: a }, |
| 225 | il::Instr::Unreachable, |
| 226 | il::Instr::Ecall { dst, num: a, a0: b, a1: a, a2: b, a3: a }, |
| 227 | il::Instr::DeviceRead { typ: il::Type::W64, dst, handle: a, offset: b }, |
| 228 | il::Instr::DeviceWrite { typ: il::Type::W8, handle: a, offset: b, value: a }, |
| 229 | il::Instr::Ebreak, |
| 230 | il::Instr::MemoryFence, |
| 231 | ]; |
| 232 | for instr in instructions { |
| 233 | try checkSelectionCapacity(instr, nil); |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | /// Direct and indirect calls check argument and output capacities. |
| 238 | @test unsafe fn callSelectionCapacity() throws (testing::TestError) { |
| 239 | let a = il::Val::Reg(il::Reg { n: 0 }); |
| 240 | let b = il::Val::Reg(il::Reg { n: 1 }); |
| 241 | let args = [b, a, b, a, il::Val::Imm(7), a, b, a, a]; |
| 242 | for callee in [il::Val::FnAddr("p::callee"), a] { |
| 243 | for dst in [nil as ?il::Reg, il::Reg { n: 3 }] { |
| 244 | for count in [0 as u32, 8, 9] { |
| 245 | let instr = il::Instr::Call { |
| 246 | retTy: il::Type::W64, dst, func: callee, args: &args[..count], |
| 247 | }; |
| 248 | let expected: ?super::Error = super::Error::Capacity if count == 9 else nil; |
| 249 | try checkSelectionCapacity(instr, expected); |
| 250 | } |
| 251 | } |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | /// Switch cases and default edges respect every shorter output capacity. |
| 256 | @test unsafe fn switchSelectionCapacity() throws (testing::TestError) { |
| 257 | for count in 0..3 { |
| 258 | for argumentMask in 0..8 { |
| 259 | try checkSwitchCapacity(count, argumentMask); |
| 260 | } |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | /// Build a switch with independently selected case and default arguments. |
| 265 | unsafe fn checkSwitchCapacity(count: u32, argumentMask: u32) throws (testing::TestError) { |
| 266 | let params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }]; |
| 267 | let firstParams = [il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 }]; |
| 268 | let secondParams = [il::Param { value: il::Reg { n: 2 }, type: il::Type::W64 }]; |
| 269 | let defaultParams = [il::Param { value: il::Reg { n: 3 }, type: il::Type::W64 }]; |
| 270 | let firstCount: u32 = 1 if argumentMask & 1 <> 0 else 0; |
| 271 | let secondCount: u32 = 1 if argumentMask & 2 <> 0 else 0; |
| 272 | let defaultCount: u32 = 1 if argumentMask & 4 <> 0 else 0; |
| 273 | let mut firstArgs = [il::Val::Imm(11)]; |
| 274 | let mut secondArgs = [il::Val::Imm(22)]; |
| 275 | let mut defaultArgs = [il::Val::Reg(il::Reg { n: 0 })]; |
| 276 | let mut cases = [ |
| 277 | il::SwitchCase { value: 0, target: 1, args: &mut firstArgs[..firstCount] }, |
| 278 | il::SwitchCase { value: 0x123456789abcdef, target: 2, args: &mut secondArgs[..secondCount] }, |
| 279 | ]; |
| 280 | let mut entry = [il::Instr::Switch { |
| 281 | val: il::Val::Reg(il::Reg { n: 0 }), defaultTarget: 3, |
| 282 | defaultArgs: &mut defaultArgs[..defaultCount], cases: &mut cases[..count], |
| 283 | }]; |
| 284 | let mut first = [il::Instr::Ret { |
| 285 | val: il::Val::Reg(il::Reg { n: 1 }) if firstCount > 0 else il::Val::Imm(11), |
| 286 | }]; |
| 287 | let mut second = [il::Instr::Ret { |
| 288 | val: il::Val::Reg(il::Reg { n: 2 }) if secondCount > 0 else il::Val::Imm(22), |
| 289 | }]; |
| 290 | let mut fallback = [il::Instr::Ret { |
| 291 | val: il::Val::Reg(il::Reg { n: 3 }) if defaultCount > 0 else il::Val::Imm(33), |
| 292 | }]; |
| 293 | let predecessors = [0 as u32]; |
| 294 | let blocks = [ |
| 295 | il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 }, |
| 296 | il::Block { |
| 297 | label: "first", params: &firstParams[..firstCount], instrs: &mut first[..], |
| 298 | locs: &[], preds: &predecessors[..1 if count > 0 else 0], loopDepth: 0, |
| 299 | }, |
| 300 | il::Block { |
| 301 | label: "second", params: &secondParams[..secondCount], instrs: &mut second[..], |
| 302 | locs: &[], preds: &predecessors[..1 if count > 1 else 0], loopDepth: 0, |
| 303 | }, |
| 304 | il::Block { |
| 305 | label: "default", params: &defaultParams[..defaultCount], instrs: &mut fallback[..], |
| 306 | locs: &[], preds: &predecessors[..], loopDepth: 0, |
| 307 | }, |
| 308 | ]; |
| 309 | let func = il::Fn { |
| 310 | name: "p::switch", params: ¶ms[..], returnType: il::Type::W64, |
| 311 | isExtern: false, isLeaf: true, blocks: &blocks[..], |
| 312 | }; |
| 313 | try checkFunctionCapacity(&func, nil); |
| 314 | } |
| 315 | |
| 316 | /// Jumps preserve argument moves and capacity checks in both block layouts. |
| 317 | @test unsafe fn jumpSelectionCapacity() throws (testing::TestError) { |
| 318 | for target in [1 as u32, 2] { |
| 319 | for count in 0..3 { |
| 320 | let params = [ |
| 321 | il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }, |
| 322 | il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 }, |
| 323 | ]; |
| 324 | let targetParams = [ |
| 325 | il::Param { value: il::Reg { n: 2 }, type: il::Type::W64 }, |
| 326 | il::Param { value: il::Reg { n: 3 }, type: il::Type::W64 }, |
| 327 | ]; |
| 328 | let mut args = [il::Val::Reg(il::Reg { n: 1 }), il::Val::Reg(il::Reg { n: 0 })]; |
| 329 | let mut entry = [il::Instr::Jmp { target, args: &mut args[..count] }]; |
| 330 | let mut body = [il::Instr::Ret { |
| 331 | val: il::Val::Reg(il::Reg { n: count + 1 }) if count > 0 else il::Val::Imm(7), |
| 332 | }]; |
| 333 | let mut unused = [il::Instr::Ret { val: il::Val::Imm(0) }]; |
| 334 | let destination = il::Block { |
| 335 | label: "destination", params: &targetParams[..count], instrs: &mut body[..], |
| 336 | locs: &[], preds: &[0], loopDepth: 0, |
| 337 | }; |
| 338 | let other = il::Block { |
| 339 | label: "other", params: &[], instrs: &mut unused[..], |
| 340 | locs: &[], preds: &[], loopDepth: 0, |
| 341 | }; |
| 342 | let blocks = [ |
| 343 | il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 }, |
| 344 | destination if target == 1 else other, |
| 345 | other if target == 1 else destination, |
| 346 | ]; |
| 347 | let func = il::Fn { |
| 348 | name: "p::jump", params: ¶ms[..], returnType: il::Type::W64, |
| 349 | isExtern: false, isLeaf: true, blocks: &blocks[..], |
| 350 | }; |
| 351 | try checkFunctionCapacity(&func, nil); |
| 352 | } |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | /// Conditional edges preserve capacity checks for each comparison and layout. |
| 357 | @test unsafe fn branchSelectionCapacity() throws (testing::TestError) { |
| 358 | for op in [il::CmpOp::Eq, il::CmpOp::Ne, il::CmpOp::Slt, il::CmpOp::Ult] { |
| 359 | for typ in [il::Type::W8, il::Type::W16, il::Type::W32, il::Type::W64] { |
| 360 | for layout in 0..2 { |
| 361 | for argumentSide in 0..3 { |
| 362 | try checkBranchCapacity(op, typ, layout, argumentSide); |
| 363 | } |
| 364 | } |
| 365 | } |
| 366 | } |
| 367 | } |
| 368 | |
| 369 | /// Build a branch with no arguments or arguments on exactly one edge. |
| 370 | unsafe fn checkBranchCapacity(op: il::CmpOp, typ: il::Type, layout: u32, argumentSide: u32) |
| 371 | throws (testing::TestError) |
| 372 | { |
| 373 | let a = il::Val::Reg(il::Reg { n: 0 }); |
| 374 | let b = il::Val::Reg(il::Reg { n: 1 }); |
| 375 | let params = [ |
| 376 | il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }, |
| 377 | il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 }, |
| 378 | ]; |
| 379 | let thenParams = [il::Param { value: il::Reg { n: 2 }, type: il::Type::W64 }]; |
| 380 | let elseParams = [il::Param { value: il::Reg { n: 3 }, type: il::Type::W64 }]; |
| 381 | let thenCount: u32 = 1 if argumentSide == 1 else 0; |
| 382 | let elseCount: u32 = 1 if argumentSide == 2 else 0; |
| 383 | let mut thenArgs = [b]; |
| 384 | let mut elseArgs = [a]; |
| 385 | let mut entry = [il::Instr::Br { |
| 386 | op, typ, a, b: il::Val::Imm(0) if argumentSide == 1 else b, |
| 387 | thenTarget: 1 if layout == 0 else 2, thenArgs: &mut thenArgs[..thenCount], |
| 388 | elseTarget: 2 if layout == 0 else 1, elseArgs: &mut elseArgs[..elseCount], |
| 389 | }]; |
| 390 | let mut thenBody = [il::Instr::Ret { |
| 391 | val: il::Val::Reg(il::Reg { n: 2 }) if thenCount > 0 else il::Val::Imm(10), |
| 392 | }]; |
| 393 | let mut elseBody = [il::Instr::Ret { |
| 394 | val: il::Val::Reg(il::Reg { n: 3 }) if elseCount > 0 else il::Val::Imm(20), |
| 395 | }]; |
| 396 | let thenBlock = il::Block { |
| 397 | label: "then", params: &thenParams[..thenCount], instrs: &mut thenBody[..], |
| 398 | locs: &[], preds: &[0], loopDepth: 0, |
| 399 | }; |
| 400 | let elseBlock = il::Block { |
| 401 | label: "else", params: &elseParams[..elseCount], instrs: &mut elseBody[..], |
| 402 | locs: &[], preds: &[0], loopDepth: 0, |
| 403 | }; |
| 404 | let blocks = [ |
| 405 | il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 }, |
| 406 | thenBlock if layout == 0 else elseBlock, |
| 407 | elseBlock if layout == 0 else thenBlock, |
| 408 | ]; |
| 409 | let func = il::Fn { |
| 410 | name: "p::branch", params: ¶ms[..], returnType: il::Type::W64, |
| 411 | isExtern: false, isLeaf: true, blocks: &blocks[..], |
| 412 | }; |
| 413 | try checkFunctionCapacity(&func, nil); |
| 414 | } |
| 415 | |
| 416 | /// Verify instruction errors, exact-fit output, and shorter-buffer canaries. |
| 417 | unsafe fn checkSelectionCapacity(instr: il::Instr, expectedError: ?super::Error) throws (testing::TestError) { |
| 418 | let mut body = [instr, il::Instr::Ret { val: il::Val::Imm(0) }]; |
| 419 | let mut count: u32 = 2; |
| 420 | if let case il::Instr::Ret { .. } = instr { |
| 421 | set count = 1; |
| 422 | } |
| 423 | let params = [ |
| 424 | il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }, |
| 425 | il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 }, |
| 426 | ]; |
| 427 | let func = il::Fn { |
| 428 | name: "p::inline", params: ¶ms[..], returnType: il::Type::W64, |
| 429 | isExtern: false, isLeaf: not il::isCall(instr), |
| 430 | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut body[..count], locs: &[], preds: &[], loopDepth: 0 }], |
| 431 | }; |
| 432 | try checkFunctionCapacity(&func, expectedError); |
| 433 | } |
| 434 | |
| 435 | /// Verify function output capacity and failure propagation with guard words. |
| 436 | unsafe fn checkFunctionCapacity(func: &il::Fn, expectedError: ?super::Error) throws (testing::TestError) { |
| 437 | let mut arena = alloc::new(&mut MEMORY[..]); |
| 438 | let mut gen = generator(&mut arena); |
| 439 | let mut scratch = alloc::new(&mut SCRATCH[..]); |
| 440 | super::generateFunction(&mut gen, func, &mut scratch); |
| 441 | assert gen.e.error == expectedError; |
| 442 | if expectedError <> nil { |
| 443 | let count = gen.e.codeLen; |
| 444 | emit::emit(&mut gen.e, encode::nop()); |
| 445 | assert gen.e.codeLen == count; |
| 446 | return; |
| 447 | } |
| 448 | let length = gen.e.codeLen; |
| 449 | let mut expected: [u32; 128] = [0; 128]; |
| 450 | assert length > 0 and length <= expected.len; |
| 451 | for i in 0..length { |
| 452 | set expected[i] = gen.e.code[i]; |
| 453 | } |
| 454 | for capacity in 0..(length + 1) { |
| 455 | let words = &mut SELECTION_WORDS[..]; |
| 456 | for i in 0..words.len { |
| 457 | set words[i] = 0xdeadbeef; |
| 458 | } |
| 459 | alloc::reset(&mut arena); |
| 460 | set gen = generator(&mut arena); |
| 461 | set gen.e.code = &mut words[1..capacity + 1]; |
| 462 | super::generateFunction(&mut gen, func, &mut scratch); |
| 463 | assert gen.e.codeLen <= capacity; |
| 464 | assert words[0] == 0xdeadbeef; |
| 465 | for i in (capacity + 1)..words.len { |
| 466 | assert words[i] == 0xdeadbeef; |
| 467 | } |
| 468 | if capacity == length { |
| 469 | assert gen.e.error == nil; |
| 470 | assert gen.e.codeLen == length; |
| 471 | for i in 0..length { |
| 472 | assert words[i + 1] == expected[i]; |
| 473 | } |
| 474 | } else { |
| 475 | assert gen.e.error == super::Error::Capacity; |
| 476 | } |
| 477 | } |
| 478 | } |
| 479 | |
| 480 | /// Name the failed invariant before returning to the test runner. |
| 481 | fn check(condition: bool, name: *[u8]) throws (testing::TestError) { |
| 482 | if not condition { |
| 483 | io::printLn(name); |
| 484 | throw testing::TestError::Failed; |
| 485 | } |
| 486 | } |