compiler/
lib/
examples/
std/
arch/
char/
collections/
graph/
lang/
alloc/
ast/
gen/
bitset/
regalloc/
liveness/
tests.rad
28.6 KiB
assign.rad
11.7 KiB
flow.rad
4.1 KiB
liveness.rad
9.0 KiB
spill.rad
12.4 KiB
bitset.rad
4.6 KiB
data.rad
8.6 KiB
labels.rad
2.4 KiB
regalloc.rad
2.4 KiB
types.rad
594 B
il/
module/
parser/
resolver/
scanner/
alloc.rad
7.1 KiB
ast.rad
26.9 KiB
gen.rad
513 B
il.rad
20.4 KiB
lower.rad
321.7 KiB
module.rad
17.3 KiB
package.rad
1.3 KiB
parser.rad
92.2 KiB
resolver.rad
511.1 KiB
scanner.rad
17.9 KiB
sexpr.rad
6.7 KiB
strings.rad
2.2 KiB
types.rad
1.6 KiB
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.3 KiB
graph.rad
4.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
299 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CELL_PERMISSIONS
6.8 KiB
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
808 B
lib/std/lang/gen/regalloc/liveness/tests.rad
raw
| 1 | //! Tests for liveness analysis. |
| 2 | |
| 3 | use std::testing; |
| 4 | use std::lang::alloc; |
| 5 | use std::lang::il; |
| 6 | use std::lang::il::published; |
| 7 | use std::lang::gen::bitset; |
| 8 | use std::lang::gen::regalloc::spill; |
| 9 | use std::lang::gen::regalloc::assign; |
| 10 | use std::lang::gen::regalloc; |
| 11 | use std::lang::gen::regalloc::flow; |
| 12 | |
| 13 | /// Publish fixture inputs outside the arena whose analysis capacity is tested. |
| 14 | unsafe fn analyze 'scratch (function: &il::Fn, storage: &Session 'scratch) |
| 15 | -> super::LiveInfo 'scratch throws (alloc::AllocError) |
| 16 | { |
| 17 | static DATA: [u8; 65536] = [0; 65536]; |
| 18 | let mut arena = alloc::new(&mut DATA[..]); |
| 19 | use arena as inputs in { |
| 20 | let view = try published::publish(function, &inputs); |
| 21 | return try super::analyze(&view, storage); |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | /// Publish fixture inputs outside the arena whose spill capacity is tested. |
| 26 | unsafe fn analyzeSpills 'scratch (function: &il::Fn, live: &super::LiveInfo 'scratch, |
| 27 | numRegs: u32, numCalleeSaved: u32, slotSize: u32, storage: &Session 'scratch) |
| 28 | -> spill::SpillInfo 'scratch throws (alloc::AllocError) |
| 29 | { |
| 30 | static DATA: [u8; 65536] = [0; 65536]; |
| 31 | let mut arena = alloc::new(&mut DATA[..]); |
| 32 | use arena as inputs in { |
| 33 | let view = try published::publish(function, &inputs); |
| 34 | return try spill::analyze(&view, live, numRegs, numCalleeSaved, slotSize, storage); |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | /// Publish fixture inputs outside the arena whose assignment capacity is tested. |
| 39 | unsafe fn assignRegisters 'scratch (function: &il::Fn, live: &super::LiveInfo 'scratch, |
| 40 | spills: &spill::SpillInfo 'scratch, config: ®alloc::TargetConfig, storage: &Session 'scratch) |
| 41 | -> assign::AssignInfo 'scratch throws (alloc::AllocError) |
| 42 | { |
| 43 | static DATA: [u8; 65536] = [0; 65536]; |
| 44 | let mut arena = alloc::new(&mut DATA[..]); |
| 45 | use arena as inputs in { |
| 46 | let view = try published::publish(function, &inputs); |
| 47 | return try assign::assign(&view, live, spills, config, storage); |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | /// Publish CFG fixture inputs outside the graph allocation test arena. |
| 52 | unsafe fn snapshot 'scratch 'permission ( |
| 53 | blocks: &[il::Block], |
| 54 | storage: &Session 'scratch, |
| 55 | permission: &'permission mut flow::Permission, |
| 56 | ) -> flow::Flow 'scratch 'permission throws (alloc::AllocError) { |
| 57 | let function = il::Fn { name: "topology", params: &[], returnType: il::Type::W64, |
| 58 | isExtern: false, isLeaf: true, blocks: blocks as *unsafe [il::Block] }; |
| 59 | static DATA: [u8; 4096] = [0; 4096]; |
| 60 | let mut arena = alloc::new(&mut DATA[..]); |
| 61 | use arena as inputs in { |
| 62 | let view = try published::publish(&function, &inputs); |
| 63 | return try flow::snapshot(view.blocks, storage, permission); |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | /// Frozen topology owns successor tables and preserves cyclic block identities. |
| 68 | @test unsafe fn testOwnedFlowSnapshot() throws (testing::TestError) { |
| 69 | let mut cases = [il::SwitchCase { value: 1, target: 1, args: &mut [] }]; |
| 70 | let mut first = [il::Instr::Switch { val: il::Val::Imm(0), defaultTarget: 0, |
| 71 | defaultArgs: &mut [], cases: &mut cases[..] }]; |
| 72 | let mut second = [il::Instr::Jmp { target: 0, args: &mut [] }]; |
| 73 | let blocks = [block("first", &mut first[..]), block("second", &mut second[..])]; |
| 74 | static DATA: [u8; 2048] = [0; 2048]; |
| 75 | let mut arena = alloc::new(&mut DATA[..]); |
| 76 | let mut owner = flow::Permission {}; |
| 77 | use arena as storage in { |
| 78 | let permission: 'permission = &mut owner in { |
| 79 | let frozen = try! snapshot(&blocks[..], &storage, permission); |
| 80 | set cases[0].target = 0; |
| 81 | set first[0] = il::Instr::Ret { val: il::Val::Imm(0) }; |
| 82 | set second[0] = il::Instr::Ret { val: il::Val::Imm(0) }; |
| 83 | let successors = flow::successors(&frozen, 0, permission); |
| 84 | assert successors.len == 2; |
| 85 | assert flow::index(successors[0]) == 0; |
| 86 | assert flow::index(successors[1]) == 1; |
| 87 | let back = flow::successors(&frozen, 1, permission); |
| 88 | assert back.len == 1 and back[0] == successors[0]; |
| 89 | } |
| 90 | } |
| 91 | alloc::reset(&mut arena); |
| 92 | assert alloc::used(&arena) == 0; |
| 93 | } |
| 94 | |
| 95 | /// Every incomplete allocation stage reports failure and permits arena disposal. |
| 96 | @test unsafe fn testFlowAllocationFailure() throws (testing::TestError) { |
| 97 | let mut instructions = [il::Instr::Jmp { target: 0, args: &mut [] }]; |
| 98 | let blocks = [block("loop", &mut instructions[..])]; |
| 99 | let mut succeeded = false; |
| 100 | for capacity in 0..257 { |
| 101 | static DATA: [u8; 256] = [0; 256]; |
| 102 | let mut arena = alloc::new(&mut DATA[..capacity]); |
| 103 | let mut owner = flow::Permission {}; |
| 104 | use arena as storage in { |
| 105 | let permission: 'permission = &mut owner in { |
| 106 | if let frozen = try? snapshot(&blocks[..], &storage, permission) { |
| 107 | let links = flow::successors(&frozen, 0, permission); |
| 108 | assert links.len == 1 and flow::index(links[0]) == 0; |
| 109 | set succeeded = true; |
| 110 | } else { |
| 111 | assert not succeeded; |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | alloc::reset(&mut arena); |
| 116 | assert alloc::used(&arena) == 0; |
| 117 | } |
| 118 | assert succeeded; |
| 119 | } |
| 120 | |
| 121 | /// Construct a block whose instruction storage belongs to the caller. |
| 122 | unsafe fn block(name: *[u8], instructions: *unsafe mut [il::Instr]) -> il::Block { |
| 123 | return il::Block { label: name, params: &[], instrs: instructions, |
| 124 | locs: &[], preds: &[], loopDepth: 0 }; |
| 125 | } |
| 126 | |
| 127 | /// Empty functions and register-free blocks produce empty liveness matrices. |
| 128 | @test unsafe fn testEmptyLivenessStorage() throws (testing::TestError) { |
| 129 | let mut instructions = [il::Instr::Ret { val: il::Val::Imm(0) }]; |
| 130 | let blocks = [block("entry", &mut instructions[..])]; |
| 131 | for count in [0 as u32, 1] { |
| 132 | let function = il::Fn { name: "empty", params: &[], returnType: il::Type::W64, |
| 133 | isExtern: false, isLeaf: true, blocks: &blocks[..count] }; |
| 134 | static DATA: [u8; 1024] = [0; 1024]; |
| 135 | let mut arena = alloc::new(&mut DATA[..]); |
| 136 | use arena as analysis in { |
| 137 | let live = try! analyze(&function, &analysis); |
| 138 | assert live.blockCount == count; |
| 139 | assert live.maxReg == 0; |
| 140 | assert live.words == 0; |
| 141 | assert live.liveIn.len == 0 and live.liveOut.len == 0; |
| 142 | assert live.defs.len == 0 and live.uses.len == 0; |
| 143 | let spills = try! analyzeSpills(&function, &live, 0, 0, 8, &analysis); |
| 144 | assert spills.maxReg == 0; |
| 145 | assert spills.frameSize == 0; |
| 146 | assert spills.slots.len == 0 and spills.calleeClass.len == 0; |
| 147 | let config = regalloc::TargetConfig { allocatable: &[], argRegs: &[], calleeSaved: &[], slotSize: 8 }; |
| 148 | let assigned = try! assignRegisters(&function, &live, &spills, &config, &analysis); |
| 149 | assert assigned.assignments.len == 0; |
| 150 | assert assigned.usedCalleeSaved == 0; |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | /// Insufficient matrix storage reports an allocation error. |
| 156 | @test unsafe fn testLivenessStorageExhaustion() throws (testing::TestError) { |
| 157 | let mut instructions = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 66 }) }]; |
| 158 | let blocks = [block("entry", &mut instructions[..])]; |
| 159 | let function = il::Fn { name: "storage", params: &[], returnType: il::Type::W64, |
| 160 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 161 | static DATA: [u8; 16] = [0; 16]; |
| 162 | let mut arena = alloc::new(&mut DATA[..]); |
| 163 | let mut failed = false; |
| 164 | use arena as analysis in { |
| 165 | try analyze(&function, &analysis) catch { |
| 166 | set failed = true; |
| 167 | }; |
| 168 | } |
| 169 | assert failed; |
| 170 | } |
| 171 | |
| 172 | /// Spill storage exhaustion reports an error after liveness succeeds. |
| 173 | @test unsafe fn testSpillStorageExhaustion() throws (testing::TestError) { |
| 174 | let mut instructions = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 66 }) }]; |
| 175 | let blocks = [block("entry", &mut instructions[..])]; |
| 176 | let function = il::Fn { name: "spillStorage", params: &[], returnType: il::Type::W64, |
| 177 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 178 | static DATA: [u8; 128] = [0; 128]; |
| 179 | let mut arena = alloc::new(&mut DATA[..]); |
| 180 | let mut failed = false; |
| 181 | use arena as analysis in { |
| 182 | let live = try! analyze(&function, &analysis); |
| 183 | try analyzeSpills(&function, &live, 0, 0, 8, &analysis) catch { |
| 184 | set failed = true; |
| 185 | }; |
| 186 | } |
| 187 | assert failed; |
| 188 | } |
| 189 | |
| 190 | /// Switch successors merge distinct words and tolerate repeated destinations. |
| 191 | @test unsafe fn testSwitchSuccessorLiveness() throws (testing::TestError) { |
| 192 | for count in [0 as u32, 1, 3] { |
| 193 | let mut cases = [ |
| 194 | il::SwitchCase { value: 0, target: 1, args: &mut [] }, |
| 195 | il::SwitchCase { value: 1, target: 1, args: &mut [] }, |
| 196 | il::SwitchCase { value: 2, target: 2, args: &mut [] }, |
| 197 | ]; |
| 198 | let mut entry = [il::Instr::Switch { val: il::Val::Imm(0), |
| 199 | defaultTarget: 3, defaultArgs: &mut [], cases: &mut cases[..count] }]; |
| 200 | let mut first = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 0 }) }]; |
| 201 | let mut second = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 33 }) }]; |
| 202 | let mut fallback = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 66 }) }]; |
| 203 | let blocks = [block("entry", &mut entry[..]), block("first", &mut first[..]), |
| 204 | block("second", &mut second[..]), block("default", &mut fallback[..]), |
| 205 | block("empty", &mut [])]; |
| 206 | let function = il::Fn { name: "switch", params: &[], returnType: il::Type::W64, |
| 207 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 208 | static DATA: [u8; 8192] = [0; 8192]; |
| 209 | let mut arena = alloc::new(&mut DATA[..]); |
| 210 | use arena as analysis in { |
| 211 | let live = try! analyze(&function, &analysis); |
| 212 | try check(super::liveOutRow(&live, 0), count > 0, count == 3, true); |
| 213 | try check(super::liveInRow(&live, 0), count > 0, count == 3, true); |
| 214 | try check(super::liveOutRow(&live, 4), false, false, false); |
| 215 | } |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | /// A cyclic graph propagates uses against block order until all rows stabilize. |
| 220 | @test unsafe fn testDelayedCyclicPropagation() throws (testing::TestError) { |
| 221 | let value = il::Reg { n: 66 }; |
| 222 | let mut entry = [il::Instr::Copy { dst: value, val: il::Val::Imm(7) }, |
| 223 | il::Instr::Jmp { target: 3, args: &mut [] }]; |
| 224 | let mut exit = [il::Instr::Ret { val: il::Val::Reg(value) }]; |
| 225 | let mut bridge = [il::Instr::Jmp { target: 1, args: &mut [] }]; |
| 226 | let mut cycle = [il::Instr::Br { op: il::CmpOp::Eq, typ: il::Type::W64, |
| 227 | a: il::Val::Imm(0), b: il::Val::Imm(1), thenTarget: 3, thenArgs: &mut [], |
| 228 | elseTarget: 2, elseArgs: &mut [] }]; |
| 229 | let blocks = [block("entry", &mut entry[..]), block("exit", &mut exit[..]), |
| 230 | block("bridge", &mut bridge[..]), block("cycle", &mut cycle[..]), |
| 231 | block("empty", &mut [])]; |
| 232 | let function = il::Fn { name: "cycle", params: &[], returnType: il::Type::W64, |
| 233 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 234 | static DATA: [u8; 8192] = [0; 8192]; |
| 235 | let mut arena = alloc::new(&mut DATA[..]); |
| 236 | use arena as analysis in { |
| 237 | let live = try! analyze(&function, &analysis); |
| 238 | for index in 0..blocks.len { |
| 239 | try check(super::liveInRow(&live, index), false, false, index > 0 and index < 4); |
| 240 | try check(super::liveOutRow(&live, index), false, false, index <> 1 and index < 4); |
| 241 | } |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | /// Later-use queries include outgoing values and exclude the current instruction. |
| 246 | @test unsafe fn testLaterUses() throws (testing::TestError) { |
| 247 | let outgoing = il::Reg { n: 0 }; |
| 248 | let argument = il::Reg { n: 1 }; |
| 249 | let result = il::Reg { n: 33 }; |
| 250 | let unused = il::Reg { n: 66 }; |
| 251 | let params = [il::Param { value: argument, type: il::Type::W64 }]; |
| 252 | let args = [il::Val::Imm(0), il::Val::Reg(argument), il::Val::Reg(argument)]; |
| 253 | let mut entry = [ |
| 254 | il::Instr::Copy { dst: outgoing, val: il::Val::Imm(1) }, |
| 255 | il::Instr::Call { retTy: il::Type::W64, dst: result, |
| 256 | func: il::Val::FnAddr("callee"), args: &args[..] }, |
| 257 | il::Instr::Copy { dst: unused, val: il::Val::Reg(result) }, |
| 258 | il::Instr::Jmp { target: 1, args: &mut [] }, |
| 259 | ]; |
| 260 | let mut exit = [il::Instr::Ret { val: il::Val::Reg(outgoing) }]; |
| 261 | let blocks = [block("entry", &mut entry[..]), block("exit", &mut exit[..])]; |
| 262 | let function = il::Fn { name: "later", params: ¶ms[..], returnType: il::Type::W64, |
| 263 | isExtern: false, isLeaf: false, blocks: &blocks[..] }; |
| 264 | static DATA: [u8; 8192] = [0; 8192]; |
| 265 | let mut arena = alloc::new(&mut DATA[..]); |
| 266 | use arena as analysis in { |
| 267 | let live = try! analyze(&function, &analysis); |
| 268 | let view = try! published::publish(&function, &analysis); |
| 269 | for index in 0..entry.len { |
| 270 | assert super::hasLaterUse(&live, &view, 0, index, outgoing); |
| 271 | assert super::hasLaterUse(&live, &view, 0, index, argument) == (index == 0); |
| 272 | assert super::hasLaterUse(&live, &view, 0, index, result) == (index < 2); |
| 273 | assert not super::hasLaterUse(&live, &view, 0, index, unused); |
| 274 | } |
| 275 | assert not super::hasLaterUse(&live, &view, 1, 0, outgoing); |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | /// Local uses precede definitions, and block parameters define their registers. |
| 280 | @test unsafe fn testLocalDefinitionOrder() throws (testing::TestError) { |
| 281 | let parameter = il::Reg { n: 33 }; |
| 282 | let temporary = il::Reg { n: 66 }; |
| 283 | let external = il::Reg { n: 0 }; |
| 284 | let params = [il::Param { value: parameter, type: il::Type::W64 }]; |
| 285 | let mut instructions = [ |
| 286 | il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: temporary, |
| 287 | a: il::Val::Reg(external), b: il::Val::Reg(parameter) }, |
| 288 | il::Instr::Copy { dst: external, val: il::Val::Reg(temporary) }, |
| 289 | il::Instr::Ret { val: il::Val::Reg(external) }, |
| 290 | ]; |
| 291 | let blocks = [il::Block { label: "entry", params: ¶ms[..], |
| 292 | instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }, |
| 293 | block("empty", &mut [])]; |
| 294 | let function = il::Fn { name: "local", params: &[], returnType: il::Type::W64, |
| 295 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 296 | static DATA: [u8; 8192] = [0; 8192]; |
| 297 | let mut arena = alloc::new(&mut DATA[..]); |
| 298 | use arena as analysis in { |
| 299 | let live = try! analyze(&function, &analysis); |
| 300 | try check(&live.defs[..live.words], true, true, true); |
| 301 | try check(&live.uses[..live.words], true, false, false); |
| 302 | try check(super::liveInRow(&live, 0), true, false, false); |
| 303 | try check(&live.defs[live.words..], false, false, false); |
| 304 | try check(&live.uses[live.words..], false, false, false); |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | /// Liveness sizes its rows from register operands and definitions. |
| 309 | @test unsafe fn testLargeRegisterExtent() throws (testing::TestError) { |
| 310 | for index in [8191 as u32, 8192, 8193, 16384] { |
| 311 | let source = il::Reg { n: index }; |
| 312 | let destination = il::Reg { n: index + 1 }; |
| 313 | let mut instructions = [ |
| 314 | il::Instr::Copy { dst: destination, val: il::Val::Reg(source) }, |
| 315 | il::Instr::Ret { val: il::Val::Reg(destination) }, |
| 316 | ]; |
| 317 | let blocks = [block("entry", &mut instructions[..])]; |
| 318 | let function = il::Fn { name: "extent", params: &[], returnType: il::Type::W64, |
| 319 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 320 | static DATA: [u8; 16384] = [0; 16384]; |
| 321 | let mut arena = alloc::new(&mut DATA[..]); |
| 322 | use arena as analysis in { |
| 323 | let live = try analyze(&function, &analysis) catch { |
| 324 | throw testing::TestError::Failed; |
| 325 | }; |
| 326 | assert live.maxReg == index + 2; |
| 327 | assert bitset::contains(super::liveInRow(&live, 0), source.n); |
| 328 | assert bitset::contains(&live.defs[..], destination.n); |
| 329 | assert not bitset::contains(super::liveInRow(&live, 0), destination.n); |
| 330 | } |
| 331 | } |
| 332 | } |
| 333 | |
| 334 | /// Nested switch arguments contribute to the extent and reject index overflow. |
| 335 | @test unsafe fn testSwitchArgumentExtent() throws (testing::TestError) { |
| 336 | for index in [8192 as u32, 16384, 0xffffffff] { |
| 337 | for inDefault in [false, true] { |
| 338 | let source = il::Reg { n: index }; |
| 339 | let mut caseArgs = [il::Val::Imm(0)]; |
| 340 | let mut defaultArgs = [il::Val::Imm(0)]; |
| 341 | if inDefault { |
| 342 | set defaultArgs[0] = il::Val::Reg(source); |
| 343 | } else { |
| 344 | set caseArgs[0] = il::Val::Reg(source); |
| 345 | } |
| 346 | let mut cases = [il::SwitchCase { value: 1, target: 1, args: &mut caseArgs[..] }]; |
| 347 | let mut entry = [il::Instr::Switch { val: il::Val::Imm(0), |
| 348 | defaultTarget: 1, defaultArgs: &mut defaultArgs[..], cases: &mut cases[..] }]; |
| 349 | let params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }]; |
| 350 | let mut exit = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 0 }) }]; |
| 351 | let blocks = [block("entry", &mut entry[..]), il::Block { label: "exit", |
| 352 | params: ¶ms[..], instrs: &mut exit[..], locs: &[], preds: &[], loopDepth: 0 }]; |
| 353 | let function = il::Fn { name: "argument", params: &[], returnType: il::Type::W64, |
| 354 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 355 | static DATA: [u8; 32768] = [0; 32768]; |
| 356 | let mut arena = alloc::new(&mut DATA[..]); |
| 357 | use arena as analysis in { |
| 358 | let live = try analyze(&function, &analysis) catch { |
| 359 | assert index == 0xffffffff; |
| 360 | continue; |
| 361 | }; |
| 362 | assert index <> 0xffffffff; |
| 363 | assert live.maxReg == index + 1; |
| 364 | assert bitset::contains(super::liveInRow(&live, 0), index); |
| 365 | } |
| 366 | } |
| 367 | } |
| 368 | } |
| 369 | |
| 370 | /// An unrepresentable register extent fails before any bitset access. |
| 371 | @test unsafe fn testRegisterExtentOverflow() throws (testing::TestError) { |
| 372 | for instruction in [ |
| 373 | il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 0xffffffff }) }, |
| 374 | il::Instr::Copy { dst: il::Reg { n: 0xffffffff }, val: il::Val::Imm(0) }, |
| 375 | ] { |
| 376 | let mut instructions = [instruction]; |
| 377 | let blocks = [block("entry", &mut instructions[..])]; |
| 378 | let function = il::Fn { name: "overflow", params: &[], returnType: il::Type::W64, |
| 379 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 380 | static DATA: [u8; 1024] = [0; 1024]; |
| 381 | let mut arena = alloc::new(&mut DATA[..]); |
| 382 | let mut failed = false; |
| 383 | use arena as analysis in { |
| 384 | try analyze(&function, &analysis) catch { |
| 385 | set failed = true; |
| 386 | }; |
| 387 | } |
| 388 | assert failed; |
| 389 | assert arena.offset == 0; |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | /// Check one bit in each word of a three-word row. |
| 394 | fn check(words: &[u32], first: bool, second: bool, third: bool) throws (testing::TestError) { |
| 395 | try testing::expect(words.len == 3); |
| 396 | try testing::expect(bitset::contains(words, 0) == first); |
| 397 | try testing::expect(bitset::contains(words, 33) == second); |
| 398 | try testing::expect(bitset::contains(words, 66) == third); |
| 399 | } |
| 400 | |
| 401 | /// Multiple spill slots preserve register order and reject total-frame overflow. |
| 402 | @test unsafe fn testMultipleSpillOffsets() throws (testing::TestError) { |
| 403 | for size in [0 as u32, 8, 0x2aaaaaaa, 0x2aaaaaab] { |
| 404 | let first = il::Reg { n: 0 }; |
| 405 | let second = il::Reg { n: 33 }; |
| 406 | let result = il::Reg { n: 66 }; |
| 407 | let mut instructions = [ |
| 408 | il::Instr::Copy { dst: first, val: il::Val::Imm(1) }, |
| 409 | il::Instr::Copy { dst: second, val: il::Val::Imm(2) }, |
| 410 | il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, |
| 411 | dst: result, a: il::Val::Reg(first), b: il::Val::Reg(second) }, |
| 412 | il::Instr::Ret { val: il::Val::Reg(result) }, |
| 413 | ]; |
| 414 | let blocks = [block("entry", &mut instructions[..])]; |
| 415 | let function = il::Fn { name: "offsets", params: &[], returnType: il::Type::W64, |
| 416 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 417 | static DATA: [u8; 16384] = [0; 16384]; |
| 418 | let mut arena = alloc::new(&mut DATA[..]); |
| 419 | use arena as analysis in { |
| 420 | let live = try! analyze(&function, &analysis); |
| 421 | let info = try analyzeSpills(&function, &live, 0, 0, size, &analysis) catch { |
| 422 | assert size == 0x2aaaaaab; |
| 423 | continue; |
| 424 | }; |
| 425 | assert size <= 0x2aaaaaaa; |
| 426 | assert info.slots[0] == 0; |
| 427 | assert info.slots[33] == size as i32; |
| 428 | assert info.slots[66] == (size * 2) as i32; |
| 429 | assert info.slots[1] == -1; |
| 430 | assert info.frameSize == (size * 3) as i32; |
| 431 | } |
| 432 | } |
| 433 | } |
| 434 | |
| 435 | /// Spill frame sizes must fit their signed byte-offset representation. |
| 436 | @test unsafe fn testSpillFrameExtent() throws (testing::TestError) { |
| 437 | for size in [0x7fffffff as u32, 0x80000000, 0xffffffff] { |
| 438 | let value = il::Reg { n: 0 }; |
| 439 | let mut instructions = [ |
| 440 | il::Instr::Copy { dst: value, val: il::Val::Imm(1) }, |
| 441 | il::Instr::Ret { val: il::Val::Reg(value) }, |
| 442 | ]; |
| 443 | let blocks = [block("entry", &mut instructions[..])]; |
| 444 | let function = il::Fn { name: "frame", params: &[], returnType: il::Type::W64, |
| 445 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 446 | static DATA: [u8; 4096] = [0; 4096]; |
| 447 | let mut arena = alloc::new(&mut DATA[..]); |
| 448 | use arena as analysis in { |
| 449 | let live = try! analyze(&function, &analysis); |
| 450 | let info = try analyzeSpills(&function, &live, 0, 0, size, &analysis) catch { |
| 451 | assert size > 0x7fffffff; |
| 452 | continue; |
| 453 | }; |
| 454 | assert size == 0x7fffffff; |
| 455 | assert info.frameSize == 2147483647; |
| 456 | assert info.slots[0] == 0; |
| 457 | } |
| 458 | } |
| 459 | } |
| 460 | |
| 461 | /// Loop-weighted uses retain hot values and cap large loop depths. |
| 462 | @test unsafe fn testLoopWeightedSpills() throws (testing::TestError) { |
| 463 | let first = il::Reg { n: 0 }; |
| 464 | let second = il::Reg { n: 33 }; |
| 465 | let temporary = il::Reg { n: 66 }; |
| 466 | let mut entry = [ |
| 467 | il::Instr::Copy { dst: first, val: il::Val::Imm(1) }, |
| 468 | il::Instr::Copy { dst: second, val: il::Val::Imm(2) }, |
| 469 | il::Instr::Jmp { target: 1, args: &mut [] }, |
| 470 | ]; |
| 471 | let mut body = [ |
| 472 | il::Instr::Copy { dst: temporary, val: il::Val::Reg(first) }, |
| 473 | il::Instr::Jmp { target: 2, args: &mut [] }, |
| 474 | ]; |
| 475 | let mut exit = [il::Instr::Ret { val: il::Val::Reg(second) }]; |
| 476 | for depth in [0 as u32, 1, 10, 11, 32, 0xffffffff] { |
| 477 | let mut weighted = block("body", &mut body[..]); |
| 478 | set weighted.loopDepth = depth; |
| 479 | let blocks = [block("entry", &mut entry[..]), weighted, block("exit", &mut exit[..])]; |
| 480 | let function = il::Fn { name: "weighted", params: &[], returnType: il::Type::W64, |
| 481 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 482 | static DATA: [u8; 8192] = [0; 8192]; |
| 483 | let mut arena = alloc::new(&mut DATA[..]); |
| 484 | use arena as analysis in { |
| 485 | let live = try! analyze(&function, &analysis); |
| 486 | let info = try! analyzeSpills(&function, &live, 1, 0, 8, &analysis); |
| 487 | assert spill::isSpilled(&info, first) == (depth == 0); |
| 488 | assert spill::isSpilled(&info, second) == (depth > 0); |
| 489 | assert not spill::isSpilled(&info, temporary); |
| 490 | assert info.frameSize == 8; |
| 491 | } |
| 492 | } |
| 493 | } |
| 494 | |
| 495 | /// Cross-call pressure excludes the result and spills equal-cost values in order. |
| 496 | @test unsafe fn testCrossCallPressure() throws (testing::TestError) { |
| 497 | let first = il::Reg { n: 0 }; |
| 498 | let second = il::Reg { n: 33 }; |
| 499 | let result = il::Reg { n: 66 }; |
| 500 | let sum = il::Reg { n: 67 }; |
| 501 | let total = il::Reg { n: 68 }; |
| 502 | let params = [il::Param { value: first, type: il::Type::W64 }, |
| 503 | il::Param { value: second, type: il::Type::W64 }]; |
| 504 | let args = [il::Val::Reg(first), il::Val::Reg(second)]; |
| 505 | let mut instructions = [ |
| 506 | il::Instr::Call { retTy: il::Type::W64, dst: result, |
| 507 | func: il::Val::FnAddr("callee"), args: &args[..] }, |
| 508 | il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: sum, |
| 509 | a: il::Val::Reg(first), b: il::Val::Reg(second) }, |
| 510 | il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: total, |
| 511 | a: il::Val::Reg(sum), b: il::Val::Reg(result) }, |
| 512 | il::Instr::Ret { val: il::Val::Reg(total) }, |
| 513 | ]; |
| 514 | let blocks = [block("entry", &mut instructions[..]), block("empty", &mut [])]; |
| 515 | let function = il::Fn { name: "crossing", params: ¶ms[..], returnType: il::Type::W64, |
| 516 | isExtern: false, isLeaf: false, blocks: &blocks[..] }; |
| 517 | for capacity in [0 as u32, 1, 2] { |
| 518 | static DATA: [u8; 8192] = [0; 8192]; |
| 519 | let mut arena = alloc::new(&mut DATA[..]); |
| 520 | use arena as analysis in { |
| 521 | let live = try! analyze(&function, &analysis); |
| 522 | let info = try! analyzeSpills(&function, &live, 8, capacity, 8, &analysis); |
| 523 | assert spill::isSpilled(&info, first) == (capacity < 2); |
| 524 | assert spill::isSpilled(&info, second) == (capacity == 0); |
| 525 | assert not spill::isSpilled(&info, result); |
| 526 | assert not spill::isSpilled(&info, sum); |
| 527 | assert not spill::isSpilled(&info, total); |
| 528 | assert bitset::contains(info.calleeClass, first.n) == (capacity == 2); |
| 529 | assert bitset::contains(info.calleeClass, second.n) == (capacity > 0); |
| 530 | assert not bitset::contains(info.calleeClass, result.n); |
| 531 | assert info.frameSize == ((2 - capacity) * 8) as i32; |
| 532 | } |
| 533 | } |
| 534 | } |
| 535 | |
| 536 | /// Function and block parameter extents receive the same overflow checks. |
| 537 | @test unsafe fn testParameterRegisterExtent() throws (testing::TestError) { |
| 538 | for index in [16384 as u32, 0xffffffff] { |
| 539 | for isFunction in [false, true] { |
| 540 | let parameters = [il::Param { value: il::Reg { n: index }, type: il::Type::W64 }]; |
| 541 | let mut instructions = [il::Instr::Ret { val: il::Val::Imm(0) }]; |
| 542 | let mut entry = block("entry", &mut instructions[..]); |
| 543 | if not isFunction { |
| 544 | set entry.params = ¶meters[..]; |
| 545 | } |
| 546 | let blocks = [entry]; |
| 547 | let mut function = il::Fn { name: "parameter", params: &[], returnType: il::Type::W64, |
| 548 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 549 | if isFunction { |
| 550 | set function.params = ¶meters[..]; |
| 551 | } |
| 552 | static DATA: [u8; 16384] = [0; 16384]; |
| 553 | let mut arena = alloc::new(&mut DATA[..]); |
| 554 | use arena as analysis in { |
| 555 | let live = try analyze(&function, &analysis) catch { |
| 556 | assert index == 0xffffffff; |
| 557 | continue; |
| 558 | }; |
| 559 | assert index == 16384; |
| 560 | assert live.maxReg == index + 1; |
| 561 | assert bitset::contains(&live.defs[..], index) == not isFunction; |
| 562 | } |
| 563 | } |
| 564 | } |
| 565 | } |
| 566 | |
| 567 | @test unsafe fn testLoopLiveness() throws (testing::TestError) { |
| 568 | let r0 = il::Reg { n: 0 }; |
| 569 | let r33 = il::Reg { n: 33 }; |
| 570 | let r66 = il::Reg { n: 66 }; |
| 571 | let mut entry = [ |
| 572 | il::Instr::Copy { dst: r0, val: il::Val::Imm(7) }, |
| 573 | il::Instr::Jmp { target: 1, args: &mut [] }, |
| 574 | ]; |
| 575 | let mut head = [ |
| 576 | il::Instr::Copy { dst: r33, val: il::Val::Reg(r0) }, |
| 577 | il::Instr::Br { op: il::CmpOp::Eq, typ: il::Type::W64, |
| 578 | a: il::Val::Reg(r33), b: il::Val::Imm(0), |
| 579 | thenTarget: 2, thenArgs: &mut [], elseTarget: 3, elseArgs: &mut [] }, |
| 580 | ]; |
| 581 | let mut body = [ |
| 582 | il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, |
| 583 | dst: r66, a: il::Val::Reg(r33), b: il::Val::Imm(1) }, |
| 584 | il::Instr::Jmp { target: 1, args: &mut [] }, |
| 585 | ]; |
| 586 | let mut exit = [il::Instr::Ret { val: il::Val::Reg(r33) }]; |
| 587 | let blocks = [block("entry", &mut entry[..]), block("head", &mut head[..]), |
| 588 | block("body", &mut body[..]), block("exit", &mut exit[..])]; |
| 589 | let function = il::Fn { name: "loop", params: &[], returnType: il::Type::W64, |
| 590 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 591 | static DATA: [u8; 8192] = [0; 8192]; |
| 592 | let mut arena = alloc::new(&mut DATA[..]); |
| 593 | let mut answer: u32 = 0; |
| 594 | use arena as analysis in { |
| 595 | let live = try! analyze(&function, &analysis); |
| 596 | try testing::expect(live.blockCount == 4); |
| 597 | try testing::expect(live.maxReg == 67); |
| 598 | try testing::expect(live.words == 3); |
| 599 | try check(super::liveInRow(&live, 0), false, false, false); |
| 600 | try check(super::liveOutRow(&live, 0), true, false, false); |
| 601 | try check(super::liveInRow(&live, 1), true, false, false); |
| 602 | try check(super::liveOutRow(&live, 1), true, true, false); |
| 603 | try check(super::liveInRow(&live, 2), true, true, false); |
| 604 | try check(super::liveOutRow(&live, 2), true, false, false); |
| 605 | try check(super::liveInRow(&live, 3), false, true, false); |
| 606 | try check(super::liveOutRow(&live, 3), false, false, false); |
| 607 | try check(&live.defs[6..9], false, false, true); |
| 608 | try check(&live.uses[6..9], false, true, false); |
| 609 | set answer = 42; |
| 610 | } |
| 611 | alloc::reset(&mut arena); |
| 612 | try testing::expect(answer == 42); |
| 613 | } |