il: Encode binary RIL values and instructions
ccafc74da702be838ef3523fc0741188b3b845e290eadcbb84ea2a214273a26d
Define explicit wire tags and encode values, instructions, and initializers into bounded caller-owned storage. Check exact byte fixtures, operation tags, and output capacity failures. Assisted-by: Codex:gpt-6
1 parent
0ea2981a
lib/std/lang/il.rad
+1 -0
| 57 | 57 | ||
| 58 | 58 | // TODO: Labels should have their own type. |
|
| 59 | 59 | // TODO: Blocks should have an instruction in `Instr`. |
|
| 60 | 60 | ||
| 61 | 61 | export mod printer; |
|
| 62 | + | export mod binary; |
|
| 62 | 63 | ||
| 63 | 64 | use std::mem; |
|
| 64 | 65 | use std::lang::alloc; |
|
| 65 | 66 | ||
| 66 | 67 | /// Source location for debug info. |
lib/std/lang/il/binary.rad
added
+140 -0
| 1 | + | //! Binary RIL wire definitions. Integers use little-endian byte order. |
|
| 2 | + | //! Sequences have a u32 element count. Symbols use u32 table indices. |
|
| 3 | + | //! Optional fields use a byte: zero for absent, one for present. |
|
| 4 | + | ||
| 5 | + | export mod writer; |
|
| 6 | + | ||
| 7 | + | @test export mod tests; |
|
| 8 | + | ||
| 9 | + | /// Binary RIL signature, encoded as the bytes RIL followed by zero. |
|
| 10 | + | export constant MAGIC: u32 = 0x004c4952; |
|
| 11 | + | /// Binary RIL format version. |
|
| 12 | + | export constant VERSION: u32 = 1; |
|
| 13 | + | ||
| 14 | + | /// A binary RIL encoding or decoding failure. |
|
| 15 | + | export union Error: Copy { |
|
| 16 | + | /// The output buffer has insufficient space. |
|
| 17 | + | Capacity, |
|
| 18 | + | /// A referenced name is absent from the symbol table. |
|
| 19 | + | Symbol, |
|
| 20 | + | /// An integer width, tag, or field is invalid. |
|
| 21 | + | Invalid, |
|
| 22 | + | /// The input ends inside a field. |
|
| 23 | + | Truncated, |
|
| 24 | + | /// The decode arena has insufficient space. |
|
| 25 | + | Storage, |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// Wire tag for value Reg. |
|
| 29 | + | export constant VALUE_REG: u8 = 0; |
|
| 30 | + | /// Wire tag for value Imm. |
|
| 31 | + | export constant VALUE_IMM: u8 = 1; |
|
| 32 | + | /// Wire tag for value DataSym. |
|
| 33 | + | export constant VALUE_DATASYM: u8 = 2; |
|
| 34 | + | /// Wire tag for value FnAddr. |
|
| 35 | + | export constant VALUE_FNADDR: u8 = 3; |
|
| 36 | + | /// Wire tag for value Undef. |
|
| 37 | + | export constant VALUE_UNDEF: u8 = 4; |
|
| 38 | + | ||
| 39 | + | /// Wire tag for instruction Reserve. |
|
| 40 | + | export constant INSTR_RESERVE: u8 = 0; |
|
| 41 | + | /// Wire tag for instruction Load. |
|
| 42 | + | export constant INSTR_LOAD: u8 = 1; |
|
| 43 | + | /// Wire tag for instruction Sload. |
|
| 44 | + | export constant INSTR_SLOAD: u8 = 2; |
|
| 45 | + | /// Wire tag for instruction Store. |
|
| 46 | + | export constant INSTR_STORE: u8 = 3; |
|
| 47 | + | /// Wire tag for instruction Blit. |
|
| 48 | + | export constant INSTR_BLIT: u8 = 4; |
|
| 49 | + | /// Wire tag for instruction Copy. |
|
| 50 | + | export constant INSTR_COPY: u8 = 5; |
|
| 51 | + | /// Wire tag for instruction BinOp. |
|
| 52 | + | export constant INSTR_BINOP: u8 = 6; |
|
| 53 | + | /// Wire tag for instruction UnOp. |
|
| 54 | + | export constant INSTR_UNOP: u8 = 7; |
|
| 55 | + | /// Wire tag for instruction Zext. |
|
| 56 | + | export constant INSTR_ZEXT: u8 = 8; |
|
| 57 | + | /// Wire tag for instruction Sext. |
|
| 58 | + | export constant INSTR_SEXT: u8 = 9; |
|
| 59 | + | /// Wire tag for instruction Call. |
|
| 60 | + | export constant INSTR_CALL: u8 = 10; |
|
| 61 | + | /// Wire tag for instruction Ret. |
|
| 62 | + | export constant INSTR_RET: u8 = 11; |
|
| 63 | + | /// Wire tag for instruction Jmp. |
|
| 64 | + | export constant INSTR_JMP: u8 = 12; |
|
| 65 | + | /// Wire tag for instruction Br. |
|
| 66 | + | export constant INSTR_BR: u8 = 13; |
|
| 67 | + | /// Wire tag for instruction Switch. |
|
| 68 | + | export constant INSTR_SWITCH: u8 = 14; |
|
| 69 | + | /// Wire tag for instruction Unreachable. |
|
| 70 | + | export constant INSTR_UNREACHABLE: u8 = 15; |
|
| 71 | + | /// Wire tag for instruction Ecall. |
|
| 72 | + | export constant INSTR_ECALL: u8 = 16; |
|
| 73 | + | /// Wire tag for instruction Ebreak. |
|
| 74 | + | export constant INSTR_EBREAK: u8 = 17; |
|
| 75 | + | /// Wire tag for instruction MemoryFence. |
|
| 76 | + | export constant INSTR_MEMORYFENCE: u8 = 18; |
|
| 77 | + | ||
| 78 | + | /// Wire tag for data Val. |
|
| 79 | + | export constant DATA_VAL: u8 = 0; |
|
| 80 | + | /// Wire tag for data Sym. |
|
| 81 | + | export constant DATA_SYM: u8 = 1; |
|
| 82 | + | /// Wire tag for data Fn. |
|
| 83 | + | export constant DATA_FN: u8 = 2; |
|
| 84 | + | /// Wire tag for data Str. |
|
| 85 | + | export constant DATA_STR: u8 = 3; |
|
| 86 | + | /// Wire tag for data Undef. |
|
| 87 | + | export constant DATA_UNDEF: u8 = 4; |
|
| 88 | + | ||
| 89 | + | /// Wire tag for binary operation Add. |
|
| 90 | + | export constant BIN_ADD: u8 = 0; |
|
| 91 | + | /// Wire tag for binary operation Sub. |
|
| 92 | + | export constant BIN_SUB: u8 = 1; |
|
| 93 | + | /// Wire tag for binary operation Mul. |
|
| 94 | + | export constant BIN_MUL: u8 = 2; |
|
| 95 | + | /// Wire tag for binary operation Sdiv. |
|
| 96 | + | export constant BIN_SDIV: u8 = 3; |
|
| 97 | + | /// Wire tag for binary operation Udiv. |
|
| 98 | + | export constant BIN_UDIV: u8 = 4; |
|
| 99 | + | /// Wire tag for binary operation Srem. |
|
| 100 | + | export constant BIN_SREM: u8 = 5; |
|
| 101 | + | /// Wire tag for binary operation Urem. |
|
| 102 | + | export constant BIN_UREM: u8 = 6; |
|
| 103 | + | /// Wire tag for binary operation Eq. |
|
| 104 | + | export constant BIN_EQ: u8 = 7; |
|
| 105 | + | /// Wire tag for binary operation Ne. |
|
| 106 | + | export constant BIN_NE: u8 = 8; |
|
| 107 | + | /// Wire tag for binary operation Slt. |
|
| 108 | + | export constant BIN_SLT: u8 = 9; |
|
| 109 | + | /// Wire tag for binary operation Sge. |
|
| 110 | + | export constant BIN_SGE: u8 = 10; |
|
| 111 | + | /// Wire tag for binary operation Ult. |
|
| 112 | + | export constant BIN_ULT: u8 = 11; |
|
| 113 | + | /// Wire tag for binary operation Uge. |
|
| 114 | + | export constant BIN_UGE: u8 = 12; |
|
| 115 | + | /// Wire tag for binary operation And. |
|
| 116 | + | export constant BIN_AND: u8 = 13; |
|
| 117 | + | /// Wire tag for binary operation Or. |
|
| 118 | + | export constant BIN_OR: u8 = 14; |
|
| 119 | + | /// Wire tag for binary operation Xor. |
|
| 120 | + | export constant BIN_XOR: u8 = 15; |
|
| 121 | + | /// Wire tag for binary operation Shl. |
|
| 122 | + | export constant BIN_SHL: u8 = 16; |
|
| 123 | + | /// Wire tag for binary operation Sshr. |
|
| 124 | + | export constant BIN_SSHR: u8 = 17; |
|
| 125 | + | /// Wire tag for binary operation Ushr. |
|
| 126 | + | export constant BIN_USHR: u8 = 18; |
|
| 127 | + | ||
| 128 | + | /// Wire tag for unary operation Neg. |
|
| 129 | + | export constant UN_NEG: u8 = 0; |
|
| 130 | + | /// Wire tag for unary operation Not. |
|
| 131 | + | export constant UN_NOT: u8 = 1; |
|
| 132 | + | ||
| 133 | + | /// Wire tag for comparison Eq. |
|
| 134 | + | export constant CMP_EQ: u8 = 0; |
|
| 135 | + | /// Wire tag for comparison Ne. |
|
| 136 | + | export constant CMP_NE: u8 = 1; |
|
| 137 | + | /// Wire tag for comparison Slt. |
|
| 138 | + | export constant CMP_SLT: u8 = 2; |
|
| 139 | + | /// Wire tag for comparison Ult. |
|
| 140 | + | export constant CMP_ULT: u8 = 3; |
lib/std/lang/il/binary/tests.rad
added
+233 -0
| 1 | + | //! Binary RIL encoding fixtures. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use std::lang::il; |
|
| 5 | + | use std::lang::il::binary::writer; |
|
| 6 | + | use std::lang::il::binary; |
|
| 7 | + | ||
| 8 | + | /// Decode arena backing storage. Tests reset it before each use. |
|
| 9 | + | static MEMORY: [u8; 512] = [0; 512]; |
|
| 10 | + | ||
| 11 | + | /// Check little-endian encoding for every integer width. |
|
| 12 | + | @test unsafe fn integers() throws (testing::TestError) { |
|
| 13 | + | let mut buffer: [u8; 15] = [0; 15]; |
|
| 14 | + | let mut out = writer::new(&mut buffer[..], &[]); |
|
| 15 | + | try writer::integer(&mut out, 0x12, 1) catch { throw testing::TestError::Failed; }; |
|
| 16 | + | try writer::integer(&mut out, 0x3456, 2) catch { throw testing::TestError::Failed; }; |
|
| 17 | + | try writer::integer(&mut out, 0x789abcde, 4) catch { throw testing::TestError::Failed; }; |
|
| 18 | + | try writer::integer(&mut out, 0x0123456789abcdef, 8) catch { throw testing::TestError::Failed; }; |
|
| 19 | + | try testing::expectBytesEq(&buffer[..], &[0x12, 0x56, 0x34, 0xde, 0xbc, 0x9a, 0x78, 0xef, 0xcd, |
|
| 20 | + | 0xab, 0x89, 0x67, 0x45, 0x23, 0x01]); |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | /// Compare one instruction with its fixed wire representation. |
|
| 24 | + | unsafe fn instruction(item: il::Instr, expected: &[u8]) throws (testing::TestError) { |
|
| 25 | + | let mut buffer: [u8; 256] = [0; 256]; |
|
| 26 | + | let mut out = writer::new(&mut buffer[..], &["data", "fn"]); |
|
| 27 | + | try writer::instr(&mut out, item) catch { throw testing::TestError::Failed; }; |
|
| 28 | + | try testing::expectBytesEq(&buffer[..out.offset], expected); |
|
| 29 | + | for capacity in 0..expected.len { |
|
| 30 | + | let mut short = writer::new(&mut buffer[..capacity], &["data", "fn"]); |
|
| 31 | + | let mut failed = false; |
|
| 32 | + | try writer::instr(&mut short, item) catch err { |
|
| 33 | + | try testing::expect(err == binary::Error::Capacity); |
|
| 34 | + | set failed = true; |
|
| 35 | + | }; |
|
| 36 | + | try testing::expect(failed); |
|
| 37 | + | try testing::expect(short.offset <= capacity); |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | /// Check all instruction tags and their field order. |
|
| 43 | + | @test unsafe fn instructions() throws (testing::TestError) { |
|
| 44 | + | let mut args: [il::Val; 1] = [il::Val::Undef]; |
|
| 45 | + | let mut cases: [il::SwitchCase; 1] = [il::SwitchCase { |
|
| 46 | + | value: -1, target: 2, args: &mut args[..], |
|
| 47 | + | }]; |
|
| 48 | + | try instruction(il::Instr::Reserve { |
|
| 49 | + | dst: il::Reg { n: 1 }, size: il::Val::Undef, alignment: 16, |
|
| 50 | + | }, &[0, 1, 0, 0, 0, 4, 16, 0, 0, 0]); |
|
| 51 | + | try instruction(il::Instr::Load { |
|
| 52 | + | typ: il::Type::W8, dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, offset: -1, |
|
| 53 | + | }, &[1, 1, 1, 0, 0, 0, 2, 0, 0, 0, 255, 255, 255, 255]); |
|
| 54 | + | try instruction(il::Instr::Sload { |
|
| 55 | + | typ: il::Type::W16, dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, offset: 3, |
|
| 56 | + | }, &[2, 2, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]); |
|
| 57 | + | try instruction(il::Instr::Store { |
|
| 58 | + | typ: il::Type::W32, src: il::Val::Undef, dst: il::Reg { n: 2 }, offset: 3, |
|
| 59 | + | }, &[3, 4, 4, 2, 0, 0, 0, 3, 0, 0, 0]); |
|
| 60 | + | try instruction(il::Instr::Blit { |
|
| 61 | + | dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, size: il::Val::Undef, |
|
| 62 | + | }, &[4, 1, 0, 0, 0, 2, 0, 0, 0, 4]); |
|
| 63 | + | try instruction(il::Instr::Copy { |
|
| 64 | + | dst: il::Reg { n: 1 }, val: il::Val::Undef, |
|
| 65 | + | }, &[5, 1, 0, 0, 0, 4]); |
|
| 66 | + | try instruction(il::Instr::BinOp { |
|
| 67 | + | op: il::BinOp::Add, typ: il::Type::W64, dst: il::Reg { n: 1 }, a: il::Val::Undef, |
|
| 68 | + | b: il::Val::Undef, |
|
| 69 | + | }, &[6, 0, 8, 1, 0, 0, 0, 4, 4]); |
|
| 70 | + | try instruction(il::Instr::UnOp { |
|
| 71 | + | op: il::UnOp::Neg, typ: il::Type::W64, dst: il::Reg { n: 1 }, a: il::Val::Undef, |
|
| 72 | + | }, &[7, 0, 8, 1, 0, 0, 0, 4]); |
|
| 73 | + | try instruction(il::Instr::Zext { |
|
| 74 | + | typ: il::Type::W8, dst: il::Reg { n: 1 }, val: il::Val::Undef, |
|
| 75 | + | }, &[8, 1, 1, 0, 0, 0, 4]); |
|
| 76 | + | try instruction(il::Instr::Sext { |
|
| 77 | + | typ: il::Type::W16, dst: il::Reg { n: 1 }, val: il::Val::Undef, |
|
| 78 | + | }, &[9, 2, 1, 0, 0, 0, 4]); |
|
| 79 | + | try instruction(il::Instr::Call { |
|
| 80 | + | retTy: il::Type::W64, dst: nil, func: il::Val::FnAddr("fn"), args: &[], |
|
| 81 | + | }, &[10, 8, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0]); |
|
| 82 | + | try instruction(il::Instr::Call { |
|
| 83 | + | retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: il::Val::FnAddr("fn"), args: &args[..], |
|
| 84 | + | }, &[10, 8, 1, 2, 0, 0, 0, 3, 1, 0, 0, 0, 1, 0, 0, 0, 4]); |
|
| 85 | + | try instruction(il::Instr::Ret { |
|
| 86 | + | val: nil, |
|
| 87 | + | }, &[11, 0]); |
|
| 88 | + | try instruction(il::Instr::Ret { |
|
| 89 | + | val: il::Val::Undef, |
|
| 90 | + | }, &[11, 1, 4]); |
|
| 91 | + | try instruction(il::Instr::Jmp { |
|
| 92 | + | target: 2, args: &mut args[..], |
|
| 93 | + | }, &[12, 2, 0, 0, 0, 1, 0, 0, 0, 4]); |
|
| 94 | + | try instruction(il::Instr::Br { |
|
| 95 | + | op: il::CmpOp::Eq, typ: il::Type::W32, a: il::Val::Undef, b: il::Val::Undef, thenTarget: 1, |
|
| 96 | + | thenArgs: &mut args[..], elseTarget: 2, elseArgs: &mut args[..], |
|
| 97 | + | }, &[13, 0, 4, 4, 4, 1, 0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 0, 0, 1, 0, 0, 0, 4]); |
|
| 98 | + | try instruction(il::Instr::Switch { |
|
| 99 | + | val: il::Val::Undef, defaultTarget: 1, defaultArgs: &mut args[..], cases: &mut cases[..], |
|
| 100 | + | }, &[ |
|
| 101 | + | 14, 4, 1, 0, 0, 0, 1, 0, 0, 0, 4, 1, |
|
| 102 | + | 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 2, |
|
| 103 | + | 0, 0, 0, 1, 0, 0, 0, 4, |
|
| 104 | + | ]); |
|
| 105 | + | try instruction(il::Instr::Unreachable, &[15]); |
|
| 106 | + | try instruction(il::Instr::Ecall { |
|
| 107 | + | dst: il::Reg { n: 1 }, num: il::Val::Undef, a0: il::Val::Undef, a1: il::Val::Undef, |
|
| 108 | + | a2: il::Val::Undef, a3: il::Val::Undef, |
|
| 109 | + | }, &[16, 1, 0, 0, 0, 4, 4, 4, 4, 4]); |
|
| 110 | + | try instruction(il::Instr::Ebreak, &[17]); |
|
| 111 | + | try instruction(il::Instr::MemoryFence, &[18]); |
|
| 112 | + | } |
|
| 113 | + | ||
| 114 | + | /// Check every value tag and empty and nonempty sequences. |
|
| 115 | + | @test unsafe fn values() throws (testing::TestError) { |
|
| 116 | + | let mut buffer: [u8; 64] = [0; 64]; |
|
| 117 | + | let mut out = writer::new(&mut buffer[..], &["data", "fn"]); |
|
| 118 | + | try writer::values(&mut out, &[]) catch { throw testing::TestError::Failed; }; |
|
| 119 | + | try writer::values(&mut out, &[ |
|
| 120 | + | il::Val::Reg(il::Reg { n: 0x12345678 }), |
|
| 121 | + | il::Val::Imm(-2), il::Val::DataSym("data"), |
|
| 122 | + | il::Val::FnAddr("fn"), il::Val::Undef, |
|
| 123 | + | ]) catch { throw testing::TestError::Failed; }; |
|
| 124 | + | try testing::expectBytesEq(&buffer[..out.offset], &[ |
|
| 125 | + | 0, 0, 0, 0, 5, 0, 0, 0, |
|
| 126 | + | 0, 0x78, 0x56, 0x34, 0x12, |
|
| 127 | + | 1, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
|
| 128 | + | 2, 0, 0, 0, 0, 3, 1, 0, 0, 0, 4, |
|
| 129 | + | ]); |
|
| 130 | + | ||
| 131 | + | } |
|
| 132 | + | ||
| 133 | + | /// Check initializer bytes and repetition counts. |
|
| 134 | + | @test unsafe fn initializers() throws (testing::TestError) { |
|
| 135 | + | let mut buffer: [u8; 128] = [0; 128]; |
|
| 136 | + | let mut out = writer::new(&mut buffer[..], &["data", "fn"]); |
|
| 137 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W8, |
|
| 138 | + | val: -1 }, count: 0 }) catch { throw testing::TestError::Failed; }; |
|
| 139 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W16, |
|
| 140 | + | val: -2 }, count: 1 }) catch { throw testing::TestError::Failed; }; |
|
| 141 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W32, |
|
| 142 | + | val: -3 }, count: 2 }) catch { throw testing::TestError::Failed; }; |
|
| 143 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, |
|
| 144 | + | val: -4 }, count: 3 }) catch { throw testing::TestError::Failed; }; |
|
| 145 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Sym("data"), count: 4 }) |
|
| 146 | + | catch { throw testing::TestError::Failed; }; |
|
| 147 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Fn("fn"), count: 5 }) catch |
|
| 148 | + | { throw testing::TestError::Failed; }; |
|
| 149 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Str("ab"), count: 6 }) catch |
|
| 150 | + | { throw testing::TestError::Failed; }; |
|
| 151 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Str(""), count: 7 }) catch { |
|
| 152 | + | throw testing::TestError::Failed; }; |
|
| 153 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Undef, count: 8 }) catch { |
|
| 154 | + | throw testing::TestError::Failed; }; |
|
| 155 | + | try testing::expectBytesEq(&buffer[..out.offset], &[0, 1, 255, 0, 0, 0, 0, 0, 2, 254, 255, 1, 0, |
|
| 156 | + | 0, 0, 0, 4, 253, 255, 255, 255, 2, 0, 0, 0, 0, 8, 252, 255, 255, 255, 255, 255, 255, 255, 3, |
|
| 157 | + | 0, 0, 0, 1, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1, 0, 0, 0, 5, 0, 0, 0, 3, 2, 0, 0, 0, 97, 98, 6, 0, |
|
| 158 | + | 0, 0, 3, 0, 0, 0, 0, 7, 0, 0, 0, 4, 8, 0, 0, 0]); |
|
| 159 | + | } |
|
| 160 | + | ||
| 161 | + | /// Reject invalid widths, insufficient storage, and absent symbols. |
|
| 162 | + | @test unsafe fn errors() throws (testing::TestError) { |
|
| 163 | + | let mut buffer: [u8; 8] = [0; 8]; |
|
| 164 | + | let mut out = writer::new(&mut buffer[..], &[]); |
|
| 165 | + | let mut failures: u32 = 0; |
|
| 166 | + | try writer::integer(&mut out, 1, 3) catch err { |
|
| 167 | + | try testing::expect(err == binary::Error::Invalid); |
|
| 168 | + | set failures += 1; |
|
| 169 | + | }; |
|
| 170 | + | try writer::symbol(&mut out, "absent") catch err { |
|
| 171 | + | try testing::expect(err == binary::Error::Symbol); |
|
| 172 | + | set failures += 1; |
|
| 173 | + | }; |
|
| 174 | + | try testing::expect(out.offset == 0); |
|
| 175 | + | try writer::integer(&mut out, 0, 8) catch { throw testing::TestError::Failed; }; |
|
| 176 | + | try writer::integer(&mut out, 1, 1) catch err { |
|
| 177 | + | try testing::expect(err == binary::Error::Capacity); |
|
| 178 | + | set failures += 1; |
|
| 179 | + | }; |
|
| 180 | + | try testing::expect(failures == 3); |
|
| 181 | + | try testing::expect(out.offset == 8); |
|
| 182 | + | } |
|
| 183 | + | ||
| 184 | + | /// Check operation tags independently of the native union representation. |
|
| 185 | + | @test unsafe fn operations() throws (testing::TestError) { |
|
| 186 | + | let binaryOps = &[ |
|
| 187 | + | il::BinOp::Add, il::BinOp::Sub, il::BinOp::Mul, |
|
| 188 | + | il::BinOp::Sdiv, il::BinOp::Udiv, il::BinOp::Srem, |
|
| 189 | + | il::BinOp::Urem, il::BinOp::Eq, il::BinOp::Ne, |
|
| 190 | + | il::BinOp::Slt, il::BinOp::Sge, il::BinOp::Ult, |
|
| 191 | + | il::BinOp::Uge, il::BinOp::And, il::BinOp::Or, |
|
| 192 | + | il::BinOp::Xor, il::BinOp::Shl, il::BinOp::Sshr, |
|
| 193 | + | il::BinOp::Ushr, |
|
| 194 | + | ]; |
|
| 195 | + | for op, tag in binaryOps { |
|
| 196 | + | try instruction(il::Instr::BinOp { |
|
| 197 | + | op, typ: il::Type::W64, dst: il::Reg { n: 1 }, |
|
| 198 | + | a: il::Val::Undef, b: il::Val::Undef, |
|
| 199 | + | }, &[6, tag as u8, 8, 1, 0, 0, 0, 4, 4]); |
|
| 200 | + | } |
|
| 201 | + | for op, tag in &[il::UnOp::Neg, il::UnOp::Not] { |
|
| 202 | + | try instruction(il::Instr::UnOp { |
|
| 203 | + | op, typ: il::Type::W64, dst: il::Reg { n: 1 }, |
|
| 204 | + | a: il::Val::Undef, |
|
| 205 | + | }, &[7, tag as u8, 8, 1, 0, 0, 0, 4]); |
|
| 206 | + | } |
|
| 207 | + | let mut empty: [il::Val; 0] = []; |
|
| 208 | + | for op, tag in &[il::CmpOp::Eq, il::CmpOp::Ne, il::CmpOp::Slt, il::CmpOp::Ult] { |
|
| 209 | + | try instruction(il::Instr::Br { |
|
| 210 | + | op, typ: il::Type::W8, a: il::Val::Undef, b: il::Val::Undef, |
|
| 211 | + | thenTarget: 1, thenArgs: &mut empty[..], |
|
| 212 | + | elseTarget: 2, elseArgs: &mut empty[..], |
|
| 213 | + | }, &[13, tag as u8, 1, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]); |
|
| 214 | + | } |
|
| 215 | + | } |
|
| 216 | + | ||
| 217 | + | /// Check byte-sequence bounds at every output size. |
|
| 218 | + | @test unsafe fn byteCapacity() throws (testing::TestError) { |
|
| 219 | + | let mut buffer: [u8; 7] = [0; 7]; |
|
| 220 | + | for capacity in 0..7 { |
|
| 221 | + | let mut out = writer::new(&mut buffer[..capacity], &[]); |
|
| 222 | + | let mut failed = false; |
|
| 223 | + | try writer::bytes(&mut out, "abc") catch err { |
|
| 224 | + | try testing::expect(err == binary::Error::Capacity); |
|
| 225 | + | set failed = true; |
|
| 226 | + | }; |
|
| 227 | + | try testing::expect(failed); |
|
| 228 | + | try testing::expect(out.offset <= capacity); |
|
| 229 | + | } |
|
| 230 | + | let mut out = writer::new(&mut buffer[..], &[]); |
|
| 231 | + | try writer::bytes(&mut out, "abc") catch { throw testing::TestError::Failed; }; |
|
| 232 | + | try testing::expectBytesEq(&buffer[..], &[3, 0, 0, 0, 97, 98, 99]); |
|
| 233 | + | } |
lib/std/lang/il/binary/writer.rad
added
+302 -0
| 1 | + | //! Bounded binary RIL encoding into caller-owned storage. |
|
| 2 | + | //! After an error, discard bytes written by the failed operation. |
|
| 3 | + | ||
| 4 | + | use std::lang::il; |
|
| 5 | + | use std::mem; |
|
| 6 | + | use std::lang::il::binary; |
|
| 7 | + | ||
| 8 | + | /// Output cursor and the symbol table used by this package. |
|
| 9 | + | /// The output buffer and symbol table must remain valid until the last write. |
|
| 10 | + | export record Writer: Copy { |
|
| 11 | + | /// Destination bytes. |
|
| 12 | + | bytes: *unsafe mut [u8], |
|
| 13 | + | /// Number of bytes written. |
|
| 14 | + | offset: u32, |
|
| 15 | + | /// Names in wire-index order. The caller supplies unique names. |
|
| 16 | + | symbols: *unsafe [*[u8]], |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | /// Create an output cursor without allocation. |
|
| 20 | + | /// The caller must retain exclusive access to the output buffer. |
|
| 21 | + | export unsafe fn new(bytes: &mut [u8], symbols: *unsafe [*[u8]]) -> Writer { |
|
| 22 | + | return Writer { bytes: bytes as *unsafe mut [u8], offset: 0, symbols }; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// Write the low bytes of an integer with width 1, 2, 4, or 8. |
|
| 26 | + | export unsafe fn integer(out: &mut Writer, value: u64, width: u32) throws (binary::Error) { |
|
| 27 | + | if width <> 1 and width <> 2 and width <> 4 and width <> 8 { |
|
| 28 | + | throw binary::Error::Invalid; |
|
| 29 | + | } |
|
| 30 | + | if out.offset > out.bytes.len or width > out.bytes.len - out.offset { |
|
| 31 | + | throw binary::Error::Capacity; |
|
| 32 | + | } |
|
| 33 | + | for i in 0..width { |
|
| 34 | + | set out.bytes[out.offset + i] = (value >> (i * 8) as u64) as u8; |
|
| 35 | + | } |
|
| 36 | + | set out.offset += width; |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | /// Write a byte sequence with its u32 length. |
|
| 40 | + | export unsafe fn bytes(out: &mut Writer, value: &[u8]) throws (binary::Error) { |
|
| 41 | + | try integer(out, value.len as u64, 4); |
|
| 42 | + | if value.len > out.bytes.len - out.offset { |
|
| 43 | + | throw binary::Error::Capacity; |
|
| 44 | + | } |
|
| 45 | + | for b, i in value { |
|
| 46 | + | set out.bytes[out.offset + i] = b; |
|
| 47 | + | } |
|
| 48 | + | set out.offset += value.len; |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | /// Write the index of a name in the package symbol table. |
|
| 52 | + | export unsafe fn symbol(out: &mut Writer, name: &[u8]) throws (binary::Error) { |
|
| 53 | + | for candidate, i in out.symbols { |
|
| 54 | + | if mem::eq(candidate, name) { |
|
| 55 | + | try integer(out, i as u64, 4); |
|
| 56 | + | return; |
|
| 57 | + | } |
|
| 58 | + | } |
|
| 59 | + | throw binary::Error::Symbol; |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | /// Write an IL type as its byte width. |
|
| 63 | + | export unsafe fn typ(out: &mut Writer, value: il::Type) throws (binary::Error) { |
|
| 64 | + | try integer(out, il::typeSize(value) as u64, 1); |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | /// Write a tagged value. Symbol addresses use table indices. |
|
| 68 | + | export unsafe fn val(out: &mut Writer, value: il::Val) throws (binary::Error) { |
|
| 69 | + | match value { |
|
| 70 | + | case il::Val::Reg(reg) => { |
|
| 71 | + | try integer(out, super::VALUE_REG as u64, 1); |
|
| 72 | + | try integer(out, reg.n as u64, 4); |
|
| 73 | + | }, |
|
| 74 | + | case il::Val::Imm(n) => { |
|
| 75 | + | try integer(out, super::VALUE_IMM as u64, 1); |
|
| 76 | + | try integer(out, n as u64, 8); |
|
| 77 | + | }, |
|
| 78 | + | case il::Val::DataSym(name) => { |
|
| 79 | + | try integer(out, super::VALUE_DATASYM as u64, 1); |
|
| 80 | + | try symbol(out, name); |
|
| 81 | + | }, |
|
| 82 | + | case il::Val::FnAddr(name) => { |
|
| 83 | + | try integer(out, super::VALUE_FNADDR as u64, 1); |
|
| 84 | + | try symbol(out, name); |
|
| 85 | + | }, |
|
| 86 | + | case il::Val::Undef => try integer(out, super::VALUE_UNDEF as u64, 1), |
|
| 87 | + | } |
|
| 88 | + | } |
|
| 89 | + | ||
| 90 | + | /// Write a sequence of values. |
|
| 91 | + | export unsafe fn values(out: &mut Writer, items: &[il::Val]) throws (binary::Error) { |
|
| 92 | + | try integer(out, items.len as u64, 4); |
|
| 93 | + | for item in items { |
|
| 94 | + | try val(out, item); |
|
| 95 | + | } |
|
| 96 | + | } |
|
| 97 | + | ||
| 98 | + | /// Write an explicit bin operation tag. |
|
| 99 | + | unsafe fn binOp(out: &mut Writer, value: il::BinOp) throws (binary::Error) { |
|
| 100 | + | match value { |
|
| 101 | + | case il::BinOp::Add => try integer(out, super::BIN_ADD as u64, 1), |
|
| 102 | + | case il::BinOp::Sub => try integer(out, super::BIN_SUB as u64, 1), |
|
| 103 | + | case il::BinOp::Mul => try integer(out, super::BIN_MUL as u64, 1), |
|
| 104 | + | case il::BinOp::Sdiv => try integer(out, super::BIN_SDIV as u64, 1), |
|
| 105 | + | case il::BinOp::Udiv => try integer(out, super::BIN_UDIV as u64, 1), |
|
| 106 | + | case il::BinOp::Srem => try integer(out, super::BIN_SREM as u64, 1), |
|
| 107 | + | case il::BinOp::Urem => try integer(out, super::BIN_UREM as u64, 1), |
|
| 108 | + | case il::BinOp::Eq => try integer(out, super::BIN_EQ as u64, 1), |
|
| 109 | + | case il::BinOp::Ne => try integer(out, super::BIN_NE as u64, 1), |
|
| 110 | + | case il::BinOp::Slt => try integer(out, super::BIN_SLT as u64, 1), |
|
| 111 | + | case il::BinOp::Sge => try integer(out, super::BIN_SGE as u64, 1), |
|
| 112 | + | case il::BinOp::Ult => try integer(out, super::BIN_ULT as u64, 1), |
|
| 113 | + | case il::BinOp::Uge => try integer(out, super::BIN_UGE as u64, 1), |
|
| 114 | + | case il::BinOp::And => try integer(out, super::BIN_AND as u64, 1), |
|
| 115 | + | case il::BinOp::Or => try integer(out, super::BIN_OR as u64, 1), |
|
| 116 | + | case il::BinOp::Xor => try integer(out, super::BIN_XOR as u64, 1), |
|
| 117 | + | case il::BinOp::Shl => try integer(out, super::BIN_SHL as u64, 1), |
|
| 118 | + | case il::BinOp::Sshr => try integer(out, super::BIN_SSHR as u64, 1), |
|
| 119 | + | case il::BinOp::Ushr => try integer(out, super::BIN_USHR as u64, 1), |
|
| 120 | + | } |
|
| 121 | + | } |
|
| 122 | + | ||
| 123 | + | /// Write an explicit un operation tag. |
|
| 124 | + | unsafe fn unOp(out: &mut Writer, value: il::UnOp) throws (binary::Error) { |
|
| 125 | + | match value { |
|
| 126 | + | case il::UnOp::Neg => try integer(out, super::UN_NEG as u64, 1), |
|
| 127 | + | case il::UnOp::Not => try integer(out, super::UN_NOT as u64, 1), |
|
| 128 | + | } |
|
| 129 | + | } |
|
| 130 | + | ||
| 131 | + | /// Write an explicit cmp operation tag. |
|
| 132 | + | unsafe fn cmpOp(out: &mut Writer, value: il::CmpOp) throws (binary::Error) { |
|
| 133 | + | match value { |
|
| 134 | + | case il::CmpOp::Eq => try integer(out, super::CMP_EQ as u64, 1), |
|
| 135 | + | case il::CmpOp::Ne => try integer(out, super::CMP_NE as u64, 1), |
|
| 136 | + | case il::CmpOp::Slt => try integer(out, super::CMP_SLT as u64, 1), |
|
| 137 | + | case il::CmpOp::Ult => try integer(out, super::CMP_ULT as u64, 1), |
|
| 138 | + | } |
|
| 139 | + | } |
|
| 140 | + | ||
| 141 | + | /// Write one instruction. Fields follow the IL record declaration order. |
|
| 142 | + | export unsafe fn instr(out: &mut Writer, item: il::Instr) throws (binary::Error) { |
|
| 143 | + | match item { |
|
| 144 | + | case il::Instr::Reserve { dst: vdst, size: vsize, alignment: valignment } => { |
|
| 145 | + | try integer(out, super::INSTR_RESERVE as u64, 1); |
|
| 146 | + | try integer(out, vdst.n as u64, 4); |
|
| 147 | + | try val(out, vsize); |
|
| 148 | + | try integer(out, valignment as u64, 4); |
|
| 149 | + | }, |
|
| 150 | + | case il::Instr::Load { typ: vtyp, dst: vdst, src: vsrc, offset: voffset } => { |
|
| 151 | + | try integer(out, super::INSTR_LOAD as u64, 1); |
|
| 152 | + | try typ(out, vtyp); |
|
| 153 | + | try integer(out, vdst.n as u64, 4); |
|
| 154 | + | try integer(out, vsrc.n as u64, 4); |
|
| 155 | + | try integer(out, voffset as u64, 4); |
|
| 156 | + | }, |
|
| 157 | + | case il::Instr::Sload { typ: vtyp, dst: vdst, src: vsrc, offset: voffset } => { |
|
| 158 | + | try integer(out, super::INSTR_SLOAD as u64, 1); |
|
| 159 | + | try typ(out, vtyp); |
|
| 160 | + | try integer(out, vdst.n as u64, 4); |
|
| 161 | + | try integer(out, vsrc.n as u64, 4); |
|
| 162 | + | try integer(out, voffset as u64, 4); |
|
| 163 | + | }, |
|
| 164 | + | case il::Instr::Store { typ: vtyp, src: vsrc, dst: vdst, offset: voffset } => { |
|
| 165 | + | try integer(out, super::INSTR_STORE as u64, 1); |
|
| 166 | + | try typ(out, vtyp); |
|
| 167 | + | try val(out, vsrc); |
|
| 168 | + | try integer(out, vdst.n as u64, 4); |
|
| 169 | + | try integer(out, voffset as u64, 4); |
|
| 170 | + | }, |
|
| 171 | + | case il::Instr::Blit { dst: vdst, src: vsrc, size: vsize } => { |
|
| 172 | + | try integer(out, super::INSTR_BLIT as u64, 1); |
|
| 173 | + | try integer(out, vdst.n as u64, 4); |
|
| 174 | + | try integer(out, vsrc.n as u64, 4); |
|
| 175 | + | try val(out, vsize); |
|
| 176 | + | }, |
|
| 177 | + | case il::Instr::Copy { dst: vdst, val: vval } => { |
|
| 178 | + | try integer(out, super::INSTR_COPY as u64, 1); |
|
| 179 | + | try integer(out, vdst.n as u64, 4); |
|
| 180 | + | try val(out, vval); |
|
| 181 | + | }, |
|
| 182 | + | case il::Instr::BinOp { op: vop, typ: vtyp, dst: vdst, a: va, b: vb } => { |
|
| 183 | + | try integer(out, super::INSTR_BINOP as u64, 1); |
|
| 184 | + | try binOp(out, vop); |
|
| 185 | + | try typ(out, vtyp); |
|
| 186 | + | try integer(out, vdst.n as u64, 4); |
|
| 187 | + | try val(out, va); |
|
| 188 | + | try val(out, vb); |
|
| 189 | + | }, |
|
| 190 | + | case il::Instr::UnOp { op: vop, typ: vtyp, dst: vdst, a: va } => { |
|
| 191 | + | try integer(out, super::INSTR_UNOP as u64, 1); |
|
| 192 | + | try unOp(out, vop); |
|
| 193 | + | try typ(out, vtyp); |
|
| 194 | + | try integer(out, vdst.n as u64, 4); |
|
| 195 | + | try val(out, va); |
|
| 196 | + | }, |
|
| 197 | + | case il::Instr::Zext { typ: vtyp, dst: vdst, val: vval } => { |
|
| 198 | + | try integer(out, super::INSTR_ZEXT as u64, 1); |
|
| 199 | + | try typ(out, vtyp); |
|
| 200 | + | try integer(out, vdst.n as u64, 4); |
|
| 201 | + | try val(out, vval); |
|
| 202 | + | }, |
|
| 203 | + | case il::Instr::Sext { typ: vtyp, dst: vdst, val: vval } => { |
|
| 204 | + | try integer(out, super::INSTR_SEXT as u64, 1); |
|
| 205 | + | try typ(out, vtyp); |
|
| 206 | + | try integer(out, vdst.n as u64, 4); |
|
| 207 | + | try val(out, vval); |
|
| 208 | + | }, |
|
| 209 | + | case il::Instr::Call { retTy: vretTy, dst: vdst, func: vfunc, args: vargs } => { |
|
| 210 | + | try integer(out, super::INSTR_CALL as u64, 1); |
|
| 211 | + | try typ(out, vretTy); |
|
| 212 | + | if let present = vdst { |
|
| 213 | + | try integer(out, 1, 1); |
|
| 214 | + | try integer(out, present.n as u64, 4); |
|
| 215 | + | } else { |
|
| 216 | + | try integer(out, 0, 1); |
|
| 217 | + | } |
|
| 218 | + | try val(out, vfunc); |
|
| 219 | + | try values(out, vargs); |
|
| 220 | + | }, |
|
| 221 | + | case il::Instr::Ret { val: vval } => { |
|
| 222 | + | try integer(out, super::INSTR_RET as u64, 1); |
|
| 223 | + | if let present = vval { |
|
| 224 | + | try integer(out, 1, 1); |
|
| 225 | + | try val(out, present); |
|
| 226 | + | } else { |
|
| 227 | + | try integer(out, 0, 1); |
|
| 228 | + | } |
|
| 229 | + | }, |
|
| 230 | + | case il::Instr::Jmp { target: vtarget, args: vargs } => { |
|
| 231 | + | try integer(out, super::INSTR_JMP as u64, 1); |
|
| 232 | + | try integer(out, vtarget as u64, 4); |
|
| 233 | + | try values(out, vargs); |
|
| 234 | + | }, |
|
| 235 | + | case il::Instr::Br { op: vop, typ: vtyp, a: va, b: vb, thenTarget: vthenTarget, thenArgs: vthenArgs, elseTarget: velseTarget, elseArgs: velseArgs } => { |
|
| 236 | + | try integer(out, super::INSTR_BR as u64, 1); |
|
| 237 | + | try cmpOp(out, vop); |
|
| 238 | + | try typ(out, vtyp); |
|
| 239 | + | try val(out, va); |
|
| 240 | + | try val(out, vb); |
|
| 241 | + | try integer(out, vthenTarget as u64, 4); |
|
| 242 | + | try values(out, vthenArgs); |
|
| 243 | + | try integer(out, velseTarget as u64, 4); |
|
| 244 | + | try values(out, velseArgs); |
|
| 245 | + | }, |
|
| 246 | + | case il::Instr::Switch { val: vval, defaultTarget: vdefaultTarget, defaultArgs: vdefaultArgs, cases: vcases } => { |
|
| 247 | + | try integer(out, super::INSTR_SWITCH as u64, 1); |
|
| 248 | + | try val(out, vval); |
|
| 249 | + | try integer(out, vdefaultTarget as u64, 4); |
|
| 250 | + | try values(out, vdefaultArgs); |
|
| 251 | + | try integer(out, vcases.len as u64, 4); |
|
| 252 | + | for branch in vcases { |
|
| 253 | + | try integer(out, branch.value as u64, 8); |
|
| 254 | + | try integer(out, branch.target as u64, 4); |
|
| 255 | + | try values(out, branch.args); |
|
| 256 | + | } |
|
| 257 | + | }, |
|
| 258 | + | case il::Instr::Unreachable => { |
|
| 259 | + | try integer(out, super::INSTR_UNREACHABLE as u64, 1); |
|
| 260 | + | }, |
|
| 261 | + | case il::Instr::Ecall { dst: vdst, num: vnum, a0: va0, a1: va1, a2: va2, a3: va3 } => { |
|
| 262 | + | try integer(out, super::INSTR_ECALL as u64, 1); |
|
| 263 | + | try integer(out, vdst.n as u64, 4); |
|
| 264 | + | try val(out, vnum); |
|
| 265 | + | try val(out, va0); |
|
| 266 | + | try val(out, va1); |
|
| 267 | + | try val(out, va2); |
|
| 268 | + | try val(out, va3); |
|
| 269 | + | }, |
|
| 270 | + | case il::Instr::Ebreak => { |
|
| 271 | + | try integer(out, super::INSTR_EBREAK as u64, 1); |
|
| 272 | + | }, |
|
| 273 | + | case il::Instr::MemoryFence => { |
|
| 274 | + | try integer(out, super::INSTR_MEMORYFENCE as u64, 1); |
|
| 275 | + | }, |
|
| 276 | + | } |
|
| 277 | + | } |
|
| 278 | + | ||
| 279 | + | /// Write one data initializer and its repetition count. |
|
| 280 | + | export unsafe fn dataValue(out: &mut Writer, value: il::DataValue) throws (binary::Error) { |
|
| 281 | + | match value.item { |
|
| 282 | + | case il::DataItem::Val { typ: t, val: n } => { |
|
| 283 | + | try integer(out, super::DATA_VAL as u64, 1); |
|
| 284 | + | try typ(out, t); |
|
| 285 | + | try integer(out, n as u64, il::typeSize(t)); |
|
| 286 | + | }, |
|
| 287 | + | case il::DataItem::Sym(name) => { |
|
| 288 | + | try integer(out, super::DATA_SYM as u64, 1); |
|
| 289 | + | try symbol(out, name); |
|
| 290 | + | }, |
|
| 291 | + | case il::DataItem::Fn(name) => { |
|
| 292 | + | try integer(out, super::DATA_FN as u64, 1); |
|
| 293 | + | try symbol(out, name); |
|
| 294 | + | }, |
|
| 295 | + | case il::DataItem::Str(text) => { |
|
| 296 | + | try integer(out, super::DATA_STR as u64, 1); |
|
| 297 | + | try bytes(out, text); |
|
| 298 | + | }, |
|
| 299 | + | case il::DataItem::Undef => try integer(out, super::DATA_UNDEF as u64, 1), |
|
| 300 | + | } |
|
| 301 | + | try integer(out, value.count as u64, 4); |
|
| 302 | + | } |
std.lib
+2 -0
| 29 | 29 | lib/std/lang/ast/printer.rad |
|
| 30 | 30 | lib/std/lang/scanner.rad |
|
| 31 | 31 | lib/std/lang/parser.rad |
|
| 32 | 32 | lib/std/lang/il.rad |
|
| 33 | 33 | lib/std/lang/il/printer.rad |
|
| 34 | + | lib/std/lang/il/binary.rad |
|
| 35 | + | lib/std/lang/il/binary/writer.rad |
|
| 34 | 36 | lib/std/lang/resolver.rad |
|
| 35 | 37 | lib/std/lang/resolver/printer.rad |
|
| 36 | 38 | lib/std/lang/lower.rad |
|
| 37 | 39 | lib/std/lang/module.rad |
|
| 38 | 40 | lib/std/lang/module/printer.rad |
std.lib.test
+1 -0
| 8 | 8 | lib/std/lang/parser/tests.rad |
|
| 9 | 9 | lib/std/lang/module/tests.rad |
|
| 10 | 10 | lib/std/lang/scanner/tests.rad |
|
| 11 | 11 | lib/std/lang/resolver/tests.rad |
|
| 12 | 12 | lib/std/lang/gen/bitset/tests.rad |
|
| 13 | + | lib/std/lang/il/binary/tests.rad |