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