lib/std/lang/gen/regalloc/liveness/tests.rad 23.9 KiB 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: &params[..], 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: &params[..],
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: &params[..], 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: &params[..], 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 = &parameters[..];
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 = &parameters[..];
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
}