lib/std/lang/il/publishedTests.rad 8.7 KiB raw
1
//! Checked traversal of published IL operand tables.
2
3
use std::testing;
4
use std::lang::alloc;
5
use std::lang::il;
6
use std::lang::il::published;
7
8
/// Checked instruction construction preserves argument order and repeated uses.
9
@test fn testCheckedInstruction() throws (testing::TestError) {
10
    static DATA: [u8; 4096] = [0; 4096];
11
    let mut arena = alloc::new(&mut DATA[..]);
12
    use arena as storage in {
13
        let instruction = try! buildCall(&storage);
14
        assert published::identity(&instruction) == nil;
15
        assert published::isCall(&instruction);
16
        let destination = published::destination(&instruction) else panic;
17
        assert destination.n == 9;
18
        let regs = published::registers(&instruction);
19
        assert regs.len == 3 and regs[0].n == 2 and regs[1].n == 7 and regs[2].n == 7;
20
        assert published::successors(&instruction).len == 0;
21
    }
22
}
23
24
/// Construct a call whose argument source ends with this stack frame.
25
fn buildCall 'view (storage: &Session 'view)
26
    -> published::Instruction 'view throws (alloc::AllocError)
27
{
28
    let values = [il::Val::Imm(4), il::Val::Reg(il::Reg { n: 7 }),
29
        il::Val::Reg(il::Reg { n: 7 })];
30
    let args = try storage.copy(&values[..]);
31
    let code = try storage.new(published::Code 'view::Call { retTy: il::Type::W64,
32
        dst: il::Reg { n: 9 }, func: il::Val::Reg(il::Reg { n: 2 }), args });
33
    return try published::buildInstruction(code, nil, storage);
34
}
35
36
/// Publish operands from source tables that end with this stack frame.
37
unsafe fn transientInput 'view (storage: &Session 'view) -> published::Function 'view
38
    throws (alloc::AllocError)
39
{
40
    let params = [il::Param { value: il::Reg { n: 5 }, type: il::Type::W64 }];
41
    let blockParams = [il::Param { value: il::Reg { n: 6 }, type: il::Type::W64 }];
42
    let mut instructions = [
43
        il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: il::Reg { n: 9 },
44
            a: il::Val::Reg(il::Reg { n: 5 }), b: il::Val::Reg(il::Reg { n: 6 }) },
45
        il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 9 }) },
46
    ];
47
    let blocks = [il::Block { label: "entry", params: &blockParams[..], instrs: &mut instructions[..],
48
        locs: &[], preds: &[], loopDepth: 2 }];
49
    let function = il::Fn { name: "transient", params: &params[..], returnType: il::Type::W64,
50
        isExtern: false, isLeaf: true, blocks: &blocks[..] };
51
    return try published::publish(&function, storage);
52
}
53
54
/// Analysis tables remain valid after their source stack frame returns.
55
@test unsafe fn testTransientPublishedInput() throws (testing::TestError) {
56
    static DATA: [u8; 4096] = [0; 4096];
57
    let mut arena = alloc::new(&mut DATA[..]);
58
    use arena as storage in {
59
        let view = try! transientInput(&storage);
60
        assert view.params.len == 1 and view.params[0].value.n == 5;
61
        let block = &view.blocks[0];
62
        assert block.params.len == 1 and block.params[0].value.n == 6;
63
        assert block.loopDepth == 2;
64
        let registers = published::registers(&block.instructions[0]);
65
        assert registers.len == 2 and registers[0].n == 5 and registers[1].n == 6;
66
        let destination = published::destination(&block.instructions[0]) else panic;
67
        assert destination.n == 9;
68
        assert not published::isCall(&block.instructions[0]);
69
        let returned = published::registers(&block.instructions[1]);
70
        assert returned.len == 1 and returned[0].n == 9;
71
        assert published::destination(&block.instructions[1]) == nil;
72
        assert published::successors(&block.instructions[1]).len == 0;
73
        let case published::Code::Fixed(fixed) = published::code(&block.instructions[1])
74
            else panic;
75
        let case il::Instr::Ret { val } = fixed else panic;
76
        let value = val else panic;
77
        let case il::Val::Reg(reg) = value else panic;
78
        assert reg.n == 9;
79
    }
80
}
81
82
/// Empty functions require no analysis storage.
83
@test unsafe fn testEmptyPublication() throws (testing::TestError) {
84
    let function = il::Fn { name: "empty", params: &[], returnType: il::Type::W64,
85
        isExtern: true, isLeaf: true, blocks: &[] };
86
    static DATA: [u8; 1] = [0; 1];
87
    let mut arena = alloc::new(&mut DATA[..0]);
88
    use arena as storage in {
89
        let view = try! published::publish(&function, &storage);
90
        assert view.params.len == 0 and view.blocks.len == 0;
91
    }
92
    assert alloc::used(&arena) == 0;
93
}
94
95
/// Publication retains source identities and scans nested argument groups.
96
@test unsafe fn testPublishedOperands() throws (testing::TestError) {
97
    let value = il::Val::Reg(il::Reg { n: 7 });
98
    let mut arguments = [il::Val::Imm(3), value, value];
99
    let mut cases = [il::SwitchCase { value: 1, target: 0, args: &mut arguments[..] }];
100
    let mut instructions = [il::Instr::Switch { val: value, defaultTarget: 0,
101
        defaultArgs: &mut [], cases: &mut cases[..] }];
102
    let blocks = [il::Block { label: "entry", params: &[], instrs: &mut instructions[..],
103
        locs: &[], preds: &[], loopDepth: 0 }];
104
    let function = il::Fn { name: "published", params: &[], returnType: il::Type::W64,
105
        isExtern: false, isLeaf: true, blocks: &blocks[..] };
106
    static DATA: [u8; 4096] = [0; 4096];
107
    let mut arena = alloc::new(&mut DATA[..]);
108
    let input: 'input = &function in {
109
        use arena as storage in {
110
            let view = try! published::publish(input, &storage);
111
            assert view.blocks.len == 1;
112
            let blockIdentity = view.blocks[0].identity else panic;
113
            assert blockIdentity == &blocks[0];
114
            let instruction = &view.blocks[0].instructions[0];
115
            let identity = published::identity(instruction) else panic;
116
            assert identity == &instructions[0];
117
            set arguments[1] = il::Val::Reg(il::Reg { n: 99 });
118
            set cases[0].target = 42;
119
            set instructions[0] = il::Instr::Ret { val: nil };
120
            let registers = published::registers(instruction);
121
            assert registers.len == 3;
122
            for reg in registers {
123
                assert reg.n == 7;
124
            }
125
            let targets = published::successors(instruction);
126
            assert targets.len == 2 and targets[0] == 0 and targets[1] == 0;
127
            let case published::Code::Switch { cases, defaultArgs, .. } = published::code(instruction)
128
                else panic;
129
            assert defaultArgs.len == 0 and cases.len == 1;
130
            assert cases[0].value == 1 and cases[0].target == 0;
131
            assert cases[0].args.len == 3;
132
            let case il::Val::Reg(reg) = cases[0].args[1] else panic;
133
            assert reg.n == 7;
134
        }
135
    }
136
}
137
138
/// Allocation failures leave no borrowed source storage in published results.
139
@test unsafe fn testPublicationAllocationFailure() throws (testing::TestError) {
140
    let mut args = [il::Val::Reg(il::Reg { n: 4 })];
141
    let locations = [il::SrcLoc { moduleId: 5, offset: 17 }, il::SrcLoc { moduleId: 5, offset: 18 }];
142
    let mut cases = [il::SwitchCase { value: 1, target: 0, args: &mut args[..] },
143
        il::SwitchCase { value: 2, target: 0, args: &mut args[..] }];
144
    let mut instructions = [il::Instr::Call { retTy: il::Type::W64, dst: nil,
145
        func: il::Val::FnAddr("callee"), args: &args[..] },
146
        il::Instr::Switch { val: il::Val::Imm(1), defaultTarget: 0,
147
            defaultArgs: &mut args[..], cases: &mut cases[..] }];
148
    let blocks = [il::Block { label: "entry", params: &[], instrs: &mut instructions[..],
149
        locs: &locations[..], preds: &[], loopDepth: 0 }];
150
    let function = il::Fn { name: "allocation", params: &[], returnType: il::Type::W64,
151
        isExtern: false, isLeaf: false, blocks: &blocks[..] };
152
    let mut succeeded = false;
153
    for capacity in 0..2049 {
154
        static DATA: [u8; 2048] = [0; 2048];
155
        let mut arena = alloc::new(&mut DATA[..capacity]);
156
        let input: 'input = &function in {
157
            use arena as storage in {
158
                if let view = try? published::publish(input, &storage) {
159
                    let registers = published::registers(&view.blocks[0].instructions[0]);
160
                    assert registers.len == 1 and registers[0].n == 4;
161
                    assert view.blocks[0].locations.len == 2;
162
                    assert view.blocks[0].locations[0].offset == 17;
163
                    let case published::Code::Switch { cases, defaultArgs, .. } =
164
                        *published::code(&view.blocks[0].instructions[1]) else panic;
165
                    assert cases.len == 2 and cases[1].value == 2;
166
                    assert cases[1].args.len == 1 and defaultArgs.len == 1;
167
                    set succeeded = true;
168
                } else {
169
                    assert not succeeded;
170
                }
171
            }
172
        }
173
        alloc::reset(&mut arena);
174
        assert alloc::used(&arena) == 0;
175
    }
176
    assert succeeded;
177
}