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