compiler/
lib/
examples/
std/
arch/
char/
collections/
graph/
lang/
alloc/
ast/
gen/
il/
binary/
binary.rad
5.9 KiB
printer.rad
16.0 KiB
published.rad
13.4 KiB
publishedTests.rad
8.7 KiB
tests.rad
14.7 KiB
module/
parser/
resolver/
scanner/
alloc.rad
7.1 KiB
ast.rad
26.9 KiB
gen.rad
513 B
il.rad
20.4 KiB
lower.rad
321.7 KiB
module.rad
17.3 KiB
package.rad
1.3 KiB
parser.rad
92.2 KiB
resolver.rad
511.1 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
graph.rad
4.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
299 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CELL_PERMISSIONS
6.8 KiB
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
808 B
lib/std/lang/il/tests.rad
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 | } |