lib/std/lang/il/tests.rad 14.7 KiB raw
1
//! Tests for RIL formatting and source register iteration.
2
3
use std::testing;
4
use std::lang::sexpr;
5
use std::lang::il::printer;
6
use std::lang::alloc;
7
8
/// Qualified names preserve empty segments and exact separator placement.
9
@test unsafe fn testQualifiedNames() throws (testing::TestError) {
10
    static STORAGE: [u8; 2048] = [0; 2048];
11
    let mut arena = alloc::new(&mut STORAGE[..]);
12
    try testing::expectBytesEq(super::formatQualifiedName(&mut arena, &[], ""), "");
13
    try testing::expectBytesEq(super::formatQualifiedName(&mut arena, &[], "method"), "method");
14
    let path = ["package", "module", "Type"];
15
    try testing::expectBytesEq(super::formatQualifiedName(&mut arena, &path[..], "method"), "package::module::Type::method");
16
    let empty = ["", "Type", ""];
17
    try testing::expectBytesEq(super::formatQualifiedName(&mut arena, &empty[..], ""), "::Type::::");
18
    let table = ["vtable", "Widget"];
19
    try testing::expectBytesEq(super::formatQualifiedName(&mut arena, &table[..], "Printable"), "vtable::Widget::Printable");
20
}
21
22
/// Check one instruction between the function header and closing delimiter.
23
unsafe fn checkPrintedInstruction(instr: super::Instr, expected: &[u8]) throws (testing::TestError) {
24
    let mut instructions = [instr];
25
    let blocks = [super::Block {
26
        label: "entry", params: &[], instrs: &mut instructions[..],
27
        locs: &[], preds: &[], loopDepth: 0,
28
    }, super::Block {
29
        label: "exit", params: &[], instrs: &mut [],
30
        locs: &[], preds: &[], loopDepth: 0,
31
    }];
32
    let function = super::Fn {
33
        name: "sample", params: &[], returnType: super::Type::W64,
34
        isExtern: false, isLeaf: true, blocks: &blocks[..],
35
    };
36
    let functions: [*unsafe super::Fn; 1] = [&function];
37
    let program = super::Program { data: &[], fns: &functions[..] };
38
    let mut storage: [u8; 512] = [0; 512];
39
    let bytes: 'output = &mut storage[..] in {
40
        let mut out = sexpr::Buffer 'output { buf: bytes, pos: 0 };
41
        printer::printProgram(&mut out, &program);
42
        let prefix = "fn w64 $sample() {\n  @entry\n    ";
43
        let suffix = ";\n}\n";
44
        try testing::expect(out.pos == prefix.len + expected.len + suffix.len);
45
        try testing::expectBytesEq(&out.buf[..prefix.len], prefix);
46
        try testing::expectBytesEq(&out.buf[prefix.len..prefix.len + expected.len], expected);
47
        try testing::expectBytesEq(&out.buf[prefix.len + expected.len..out.pos], suffix);
48
    }
49
}
50
51
/// Fixed instruction formatting preserves operands, widths, and optional returns.
52
@test unsafe fn testFixedInstructionPrinting() throws (testing::TestError) {
53
    let dst = super::Reg { n: 4294967295 };
54
    let reg = super::Reg { n: 2 };
55
    let value = super::Val::Imm(-9223372036854775808);
56
    let source = super::Val::Reg(reg);
57
    try checkPrintedInstruction(super::Instr::Reserve { dst, size: value, alignment: 8 }, "reserve %4294967295 -9223372036854775808 8");
58
    try checkPrintedInstruction(super::Instr::Load { typ: super::Type::W8, dst, src: reg, offset: -2147483648 }, "load w8 %4294967295 %2 -2147483648");
59
    try checkPrintedInstruction(super::Instr::Sload { typ: super::Type::W16, dst, src: reg, offset: 2147483647 }, "sload w16 %4294967295 %2 2147483647");
60
    try checkPrintedInstruction(super::Instr::Store { typ: super::Type::W32, src: source, dst: reg, offset: 0 }, "store w32 %2 %2 0");
61
    try checkPrintedInstruction(super::Instr::Blit { dst, src: reg, size: value }, "blit %4294967295 %2 -9223372036854775808");
62
    try checkPrintedInstruction(super::Instr::Copy { dst, val: super::Val::Undef }, "copy %4294967295 undefined");
63
    try checkPrintedInstruction(super::Instr::BinOp { op: super::BinOp::Add, typ: super::Type::W64, dst, a: source, b: value }, "add w64 %4294967295 %2 -9223372036854775808");
64
    try checkPrintedInstruction(super::Instr::UnOp { op: super::UnOp::Not, typ: super::Type::W64, dst, a: source }, "not w64 %4294967295 %2");
65
    try checkPrintedInstruction(super::Instr::Zext { typ: super::Type::W8, dst, val: source }, "zext w8 %4294967295 %2");
66
    try checkPrintedInstruction(super::Instr::Sext { typ: super::Type::W32, dst, val: value }, "sext w32 %4294967295 -9223372036854775808");
67
    try checkPrintedInstruction(super::Instr::Ret { val: nil }, "ret");
68
    try checkPrintedInstruction(super::Instr::Ret { val: value }, "ret -9223372036854775808");
69
    try checkPrintedInstruction(super::Instr::Unreachable, "unreachable");
70
    try checkPrintedInstruction(super::Instr::Ecall { dst, num: value, a0: source, a1: value, a2: source, a3: value }, "ecall %4294967295 -9223372036854775808 %2 -9223372036854775808 %2 -9223372036854775808");
71
    try checkPrintedInstruction(super::Instr::DeviceRead { typ: super::Type::W8, dst, handle: source, offset: value }, "device-read w8 %4294967295 %2 -9223372036854775808");
72
    try checkPrintedInstruction(super::Instr::DeviceWrite { typ: super::Type::W64, handle: source, offset: value, value: source }, "device-write w64 %2 -9223372036854775808 %2");
73
    try checkPrintedInstruction(super::Instr::Ebreak, "ebreak");
74
    try checkPrintedInstruction(super::Instr::MemoryFence, "memory-fence");
75
}
76
77
/// Call printing preserves destinations, callee forms, and argument order.
78
@test unsafe fn testCallPrinting() throws (testing::TestError) {
79
    let dst = super::Reg { n: 7 };
80
    let direct = super::Val::FnAddr("callee");
81
    let indirect = super::Val::Reg(super::Reg { n: 2 });
82
    let args = [super::Val::Imm(-1), super::Val::Reg(dst), super::Val::FnAddr("pkg::callback")];
83
    try checkPrintedInstruction(super::Instr::Call {
84
        retTy: super::Type::W8, dst: nil, func: direct, args: &[],
85
    }, "call w8 $callee()");
86
    try checkPrintedInstruction(super::Instr::Call {
87
        retTy: super::Type::W16, dst, func: direct, args: &[],
88
    }, "call w16 %7 $callee()");
89
    try checkPrintedInstruction(super::Instr::Call {
90
        retTy: super::Type::W32, dst: nil, func: indirect, args: &args[..1],
91
    }, "call w32 %2(-1)");
92
    try checkPrintedInstruction(super::Instr::Call {
93
        retTy: super::Type::W64, dst, func: indirect, args: &args[..],
94
    }, "call w64 %7 %2(-1, %7, $\"pkg::callback\")");
95
    try checkPrintedInstruction(super::Instr::Call {
96
        retTy: super::Type::W64, dst, func: super::Val::FnAddr("pkg::callee"), args: &args[..],
97
    }, "call w64 %7 $\"pkg::callee\"(-1, %7, $\"pkg::callback\")");
98
}
99
100
/// Control-flow printing preserves target labels and empty or populated arguments.
101
@test unsafe fn testControlFlowPrinting() throws (testing::TestError) {
102
    let value = super::Val::Reg(super::Reg { n: 3 });
103
    let other = super::Val::Imm(-7);
104
    let mut args = [value, other];
105
    try checkPrintedInstruction(super::Instr::Jmp { target: 0, args: &mut [] }, "jmp @entry");
106
    try checkPrintedInstruction(super::Instr::Jmp { target: 0, args: &mut args[..] }, "jmp @entry(%3, -7)");
107
    try checkPrintedInstruction(super::Instr::Br {
108
        op: super::CmpOp::Eq, typ: super::Type::W8, a: value, b: other,
109
        thenTarget: 0, thenArgs: &mut [], elseTarget: 1, elseArgs: &mut [],
110
    }, "br.eq w8 %3 -7 @entry @exit");
111
    try checkPrintedInstruction(super::Instr::Br {
112
        op: super::CmpOp::Ne, typ: super::Type::W16, a: value, b: other,
113
        thenTarget: 1, thenArgs: &mut args[..], elseTarget: 0, elseArgs: &mut [],
114
    }, "br.ne w16 %3 -7 @exit(%3, -7) @entry");
115
    try checkPrintedInstruction(super::Instr::Br {
116
        op: super::CmpOp::Slt, typ: super::Type::W32, a: value, b: other,
117
        thenTarget: 0, thenArgs: &mut [], elseTarget: 1, elseArgs: &mut args[..],
118
    }, "br.slt w32 %3 -7 @entry @exit(%3, -7)");
119
    try checkPrintedInstruction(super::Instr::Br {
120
        op: super::CmpOp::Ult, typ: super::Type::W64, a: value, b: other,
121
        thenTarget: 0, thenArgs: &mut args[..], elseTarget: 0, elseArgs: &mut args[..],
122
    }, "br.ult w64 %3 -7 @entry(%3, -7) @entry(%3, -7)");
123
    let mut cases = [
124
        super::SwitchCase { value: -9223372036854775808, target: 0, args: &mut [] },
125
        super::SwitchCase { value: 9223372036854775807, target: 0, args: &mut args[..] },
126
    ];
127
    try checkPrintedInstruction(super::Instr::Switch {
128
        val: value, defaultTarget: 0, defaultArgs: &mut [], cases: &mut [],
129
    }, "switch %3 @entry");
130
    try checkPrintedInstruction(super::Instr::Switch {
131
        val: value, defaultTarget: 0, defaultArgs: &mut args[..], cases: &mut cases[..],
132
    }, "switch %3 (-9223372036854775808 @entry) (9223372036854775807 @entry(%3, -7)) @entry(%3, -7)");
133
}
134
135
/// Buffered output truncates at capacity and preserves adjacent storage.
136
@test fn testSafePrintBuffer() throws (testing::TestError) {
137
    let mut storage: [u8; 7] = [42; 7];
138
    let bytes: 'output = &mut storage[1..6] in {
139
        let mut out = sexpr::Buffer 'output { buf: bytes, pos: 0 };
140
        sexpr::write(&mut out, "ab");
141
        sexpr::write(&mut out, "cdef");
142
        sexpr::write(&mut out, "ignored");
143
        try testing::expect(out.pos == 5);
144
        try testing::expectBytesEq(out.buf, "abcde");
145
    }
146
    try testing::expect(storage[0] == 42);
147
    try testing::expect(storage[6] == 42);
148
    let bytes: 'empty = &mut storage[0..0] in {
149
        let mut out = sexpr::Buffer 'empty { buf: bytes, pos: 0 };
150
        sexpr::write(&mut out, "ignored");
151
        try testing::expect(out.pos == 0);
152
    }
153
}
154
155
/// S-expression formatting uses checked output storage.
156
@test fn testSafeExpressionPrinter() throws (testing::TestError) {
157
    let mut storage: [u8; 64] = [0; 64];
158
    let bytes: 'output = &mut storage[..] in {
159
        let mut out = sexpr::Buffer 'output { buf: bytes, pos: 0 };
160
        sexpr::printTo(sexpr::Expr::Str("a\n\t\\"), 0, &mut out);
161
        sexpr::printTo(sexpr::Expr::Char('\n'), 0, &mut out);
162
        try testing::expectBytesEq(&out.buf[..out.pos], "\"a\\n\\t\\\\\"'\\n'");
163
    }
164
}
165
166
/// Fixed register cursors can be initialized in safe code.
167
@test fn testSafeRegisterCursor() throws (testing::TestError) {
168
    let instr = super::Instr::Ecall {
169
        dst: super::Reg { n: 0 },
170
        num: super::Val::Reg(super::Reg { n: 1 }),
171
        a0: super::Val::Reg(super::Reg { n: 2 }),
172
        a1: super::Val::Reg(super::Reg { n: 3 }),
173
        a2: super::Val::Reg(super::Reg { n: 4 }),
174
        a3: super::Val::Reg(super::Reg { n: 5 }),
175
    };
176
    let cursor = super::registers(&instr);
177
    try testing::expect(cursor.count == 5);
178
    for reg, i in &cursor.fixed[..] {
179
        try testing::expect(reg.n == i + 1);
180
    }
181
    let emptyInstr = super::Instr::Unreachable;
182
    let empty = super::registers(&emptyInstr);
183
    try testing::expect(empty.count == 0);
184
    for reg in &empty.fixed[..] {
185
        try testing::expect(reg.n == 0);
186
    }
187
}
188
189
/// Require exact source-register order and stable exhaustion.
190
unsafe fn check(instr: super::Instr, expected: &[u32]) throws (testing::TestError) {
191
    let mut cursor = super::registers(&instr);
192
    for value in expected {
193
        let reg = super::nextReg(&mut cursor, &instr) else panic;
194
        try testing::expect(reg.n == value);
195
    }
196
    try testing::expect(super::nextReg(&mut cursor, &instr) == nil);
197
    try testing::expect(super::nextReg(&mut cursor, &instr) == nil);
198
}
199
200
@test unsafe fn testRegisterIteration() throws (testing::TestError) {
201
    let dst = super::Reg { n: 99 };
202
    let r1 = super::Reg { n: 1 };
203
    let r2 = super::Reg { n: 2 };
204
    let a = super::Val::Reg(r1);
205
    let b = super::Val::Reg(r2);
206
    let imm = super::Val::Imm(7);
207
    try check(super::Instr::Reserve { dst, size: a, alignment: 8 }, &[1]);
208
    try check(super::Instr::Reserve { dst, size: imm, alignment: 8 }, &[]);
209
    try check(super::Instr::Load { typ: super::Type::W64, dst, src: r1, offset: 0 }, &[1]);
210
    try check(super::Instr::Sload { typ: super::Type::W32, dst, src: r2, offset: 0 }, &[2]);
211
    try check(super::Instr::Store { typ: super::Type::W64, src: a, dst: r2, offset: 0 }, &[1, 2]);
212
    try check(super::Instr::Store { typ: super::Type::W64, src: imm, dst: r2, offset: 0 }, &[2]);
213
    try check(super::Instr::Blit { dst: r1, src: r2, size: a }, &[1, 2, 1]);
214
    try check(super::Instr::Blit { dst: r1, src: r2, size: imm }, &[1, 2]);
215
    try check(super::Instr::Copy { dst, val: a }, &[1]);
216
    try check(super::Instr::Copy { dst, val: imm }, &[]);
217
    try check(super::Instr::Copy { dst, val: super::Val::DataSym("data") }, &[]);
218
    try check(super::Instr::Copy { dst, val: super::Val::Undef }, &[]);
219
    try check(super::Instr::BinOp { op: super::BinOp::Add, typ: super::Type::W64, dst, a: imm, b }, &[2]);
220
    try check(super::Instr::BinOp { op: super::BinOp::Add, typ: super::Type::W64, dst, a, b: a }, &[1, 1]);
221
    try check(super::Instr::UnOp { op: super::UnOp::Neg, typ: super::Type::W64, dst, a }, &[1]);
222
    try check(super::Instr::Zext { typ: super::Type::W8, dst, val: a }, &[1]);
223
    try check(super::Instr::Sext { typ: super::Type::W8, dst, val: b }, &[2]);
224
    let mut args = [imm, a, b, a];
225
    try check(super::Instr::Call { retTy: super::Type::W64, dst, func: b, args: &args[..] }, &[2, 1, 2, 1]);
226
    try check(super::Instr::Call { retTy: super::Type::W64, dst: nil, func: super::Val::FnAddr("callee"), args: &[] }, &[]);
227
    try check(super::Instr::Ret { val: a }, &[1]);
228
    try check(super::Instr::Ret { val: nil }, &[]);
229
    try check(super::Instr::Jmp { target: 0, args: &mut args[..] }, &[1, 2, 1]);
230
    let mut other = [b, imm];
231
    try check(super::Instr::Br { op: super::CmpOp::Eq, typ: super::Type::W64, a, b,
232
        thenTarget: 0, thenArgs: &mut args[..], elseTarget: 1, elseArgs: &mut other[..] }, &[1, 2, 1, 2, 1, 2]);
233
    try check(super::Instr::Br { op: super::CmpOp::Eq, typ: super::Type::W64, a: imm, b: imm,
234
        thenTarget: 0, thenArgs: &mut [], elseTarget: 1, elseArgs: &mut other[..] }, &[2]);
235
    try check(super::Instr::Br { op: super::CmpOp::Eq, typ: super::Type::W64, a, b,
236
        thenTarget: 0, thenArgs: &mut other[..], elseTarget: 1, elseArgs: &mut [] }, &[1, 2, 2]);
237
    let mut cases = [
238
        super::SwitchCase { value: 0, target: 0, args: &mut [] },
239
        super::SwitchCase { value: 1, target: 1, args: &mut args[..] },
240
        super::SwitchCase { value: 2, target: 2, args: &mut [] },
241
        super::SwitchCase { value: 3, target: 3, args: &mut other[..] },
242
    ];
243
    try check(super::Instr::Switch { val: b, defaultTarget: 0, defaultArgs: &mut [], cases: &mut cases[..] }, &[2, 1, 2, 1, 2]);
244
    try check(super::Instr::Switch { val: a, defaultTarget: 0, defaultArgs: &mut other[..], cases: &mut cases[..] }, &[1, 2, 1, 2, 1, 2]);
245
    try check(super::Instr::Switch { val: imm, defaultTarget: 0, defaultArgs: &mut args[..], cases: &mut [] }, &[1, 2, 1]);
246
    try check(super::Instr::Switch { val: imm, defaultTarget: 0, defaultArgs: &mut [], cases: &mut [] }, &[]);
247
    try check(super::Instr::Ecall { dst, num: a, a0: imm, a1: b, a2: a, a3: imm }, &[1, 2, 1]);
248
    try check(super::Instr::Ecall { dst, num: a, a0: b, a1: a, a2: b, a3: a }, &[1, 2, 1, 2, 1]);
249
    try check(super::Instr::Unreachable, &[]);
250
    try check(super::Instr::Ebreak, &[]);
251
    try check(super::Instr::MemoryFence, &[]);
252
253
}