il: Add binary RIL codecs
eba4a9436c8094d1a842c1a5eda64578f44fab471ffbdeaf65b1f376d679bf03
1 parent
56a00753
compiler/radiance.rad
+3 -3
| 23 | 23 | use std::sys; |
|
| 24 | 24 | use std::sys::unix; |
|
| 25 | 25 | use std::collections::dict; |
|
| 26 | 26 | ||
| 27 | 27 | /// Maximum number of modules we can load per package. |
|
| 28 | - | constant MAX_LOADED_MODULES: u32 = 64; |
|
| 28 | + | constant MAX_LOADED_MODULES: u32 = module::MAX_MODULES; |
|
| 29 | 29 | /// Maximum number of packages we can compile. |
|
| 30 | 30 | constant MAX_PACKAGES: u32 = 4; |
|
| 31 | 31 | /// Total module entries across all packages. |
|
| 32 | 32 | constant MAX_TOTAL_MODULES: u32 = 192; |
|
| 33 | 33 | /// Source code buffer arena (2 MB). |
| 35 | 35 | /// Maximum number of test functions we can discover. |
|
| 36 | 36 | constant MAX_TESTS: u32 = 1024; |
|
| 37 | 37 | /// Maximum number of assembly source paths we can load per package. |
|
| 38 | 38 | constant MAX_ASM_MODULES: u32 = 64; |
|
| 39 | 39 | ||
| 40 | - | /// AST arena size (32 MB) - retains parsed nodes throughout compilation. |
|
| 41 | - | constant TEMP_ARENA_SIZE: u32 = 33554432; |
|
| 40 | + | /// AST arena size (64 MB) - retains parsed nodes throughout compilation. |
|
| 41 | + | constant TEMP_ARENA_SIZE: u32 = 67108864; |
|
| 42 | 42 | /// Per-function lowering and register-allocation arena size (16 MB). |
|
| 43 | 43 | constant FN_ARENA_SIZE: u32 = 16777216; |
|
| 44 | 44 | /// Main arena size (96 MB) - lives throughout compilation. |
|
| 45 | 45 | /// Used for: resolver data, types, symbols, global IL data, and codegen output. |
|
| 46 | 46 | constant MAIN_ARENA_SIZE: u32 = 100663296; |
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
+186 -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 | + | export mod reader; |
|
| 7 | + | export mod program; |
|
| 8 | + | ||
| 9 | + | use std::lang::il; |
|
| 10 | + | @test export mod tests; |
|
| 11 | + | @test export mod decodeTests; |
|
| 12 | + | ||
| 13 | + | /// Binary RIL signature, encoded as the bytes RIL followed by zero. |
|
| 14 | + | export constant MAGIC: u32 = 0x004c4952; |
|
| 15 | + | /// Binary RIL format version. |
|
| 16 | + | export constant VERSION: u32 = 1; |
|
| 17 | + | ||
| 18 | + | /// A binary RIL encoding or decoding failure. |
|
| 19 | + | export union Error: Copy { |
|
| 20 | + | /// The output buffer has insufficient space. |
|
| 21 | + | Capacity, |
|
| 22 | + | /// A referenced name is absent from the symbol table. |
|
| 23 | + | Symbol, |
|
| 24 | + | /// An integer width, tag, or field is invalid. |
|
| 25 | + | Invalid, |
|
| 26 | + | /// The input ends inside a field. |
|
| 27 | + | Truncated, |
|
| 28 | + | /// The decode arena has insufficient space. |
|
| 29 | + | Storage, |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | /// Wire tag for value Reg. |
|
| 33 | + | export constant VALUE_REG: u8 = 0; |
|
| 34 | + | /// Wire tag for value Imm. |
|
| 35 | + | export constant VALUE_IMM: u8 = 1; |
|
| 36 | + | /// Wire tag for value DataSym. |
|
| 37 | + | export constant VALUE_DATASYM: u8 = 2; |
|
| 38 | + | /// Wire tag for value FnAddr. |
|
| 39 | + | export constant VALUE_FNADDR: u8 = 3; |
|
| 40 | + | /// Wire tag for value Undef. |
|
| 41 | + | export constant VALUE_UNDEF: u8 = 4; |
|
| 42 | + | ||
| 43 | + | /// Wire tag for instruction Reserve. |
|
| 44 | + | export constant INSTR_RESERVE: u8 = 0; |
|
| 45 | + | /// Wire tag for instruction Load. |
|
| 46 | + | export constant INSTR_LOAD: u8 = 1; |
|
| 47 | + | /// Wire tag for instruction Sload. |
|
| 48 | + | export constant INSTR_SLOAD: u8 = 2; |
|
| 49 | + | /// Wire tag for instruction Store. |
|
| 50 | + | export constant INSTR_STORE: u8 = 3; |
|
| 51 | + | /// Wire tag for instruction Blit. |
|
| 52 | + | export constant INSTR_BLIT: u8 = 4; |
|
| 53 | + | /// Wire tag for instruction Copy. |
|
| 54 | + | export constant INSTR_COPY: u8 = 5; |
|
| 55 | + | /// Wire tag for instruction BinOp. |
|
| 56 | + | export constant INSTR_BINOP: u8 = 6; |
|
| 57 | + | /// Wire tag for instruction UnOp. |
|
| 58 | + | export constant INSTR_UNOP: u8 = 7; |
|
| 59 | + | /// Wire tag for instruction Zext. |
|
| 60 | + | export constant INSTR_ZEXT: u8 = 8; |
|
| 61 | + | /// Wire tag for instruction Sext. |
|
| 62 | + | export constant INSTR_SEXT: u8 = 9; |
|
| 63 | + | /// Wire tag for instruction Call. |
|
| 64 | + | export constant INSTR_CALL: u8 = 10; |
|
| 65 | + | /// Wire tag for instruction Ret. |
|
| 66 | + | export constant INSTR_RET: u8 = 11; |
|
| 67 | + | /// Wire tag for instruction Jmp. |
|
| 68 | + | export constant INSTR_JMP: u8 = 12; |
|
| 69 | + | /// Wire tag for instruction Br. |
|
| 70 | + | export constant INSTR_BR: u8 = 13; |
|
| 71 | + | /// Wire tag for instruction Switch. |
|
| 72 | + | export constant INSTR_SWITCH: u8 = 14; |
|
| 73 | + | /// Wire tag for instruction Unreachable. |
|
| 74 | + | export constant INSTR_UNREACHABLE: u8 = 15; |
|
| 75 | + | /// Wire tag for instruction Ecall. |
|
| 76 | + | export constant INSTR_ECALL: u8 = 16; |
|
| 77 | + | /// Wire tag for instruction Ebreak. |
|
| 78 | + | export constant INSTR_EBREAK: u8 = 17; |
|
| 79 | + | /// Wire tag for instruction MemoryFence. |
|
| 80 | + | export constant INSTR_MEMORYFENCE: u8 = 18; |
|
| 81 | + | ||
| 82 | + | /// Wire tag for data Val. |
|
| 83 | + | export constant DATA_VAL: u8 = 0; |
|
| 84 | + | /// Wire tag for data Sym. |
|
| 85 | + | export constant DATA_SYM: u8 = 1; |
|
| 86 | + | /// Wire tag for data Fn. |
|
| 87 | + | export constant DATA_FN: u8 = 2; |
|
| 88 | + | /// Wire tag for data Str. |
|
| 89 | + | export constant DATA_STR: u8 = 3; |
|
| 90 | + | /// Wire tag for data Undef. |
|
| 91 | + | export constant DATA_UNDEF: u8 = 4; |
|
| 92 | + | ||
| 93 | + | /// Wire tag for binary operation Add. |
|
| 94 | + | export constant BIN_ADD: u8 = 0; |
|
| 95 | + | /// Wire tag for binary operation Sub. |
|
| 96 | + | export constant BIN_SUB: u8 = 1; |
|
| 97 | + | /// Wire tag for binary operation Mul. |
|
| 98 | + | export constant BIN_MUL: u8 = 2; |
|
| 99 | + | /// Wire tag for binary operation Sdiv. |
|
| 100 | + | export constant BIN_SDIV: u8 = 3; |
|
| 101 | + | /// Wire tag for binary operation Udiv. |
|
| 102 | + | export constant BIN_UDIV: u8 = 4; |
|
| 103 | + | /// Wire tag for binary operation Srem. |
|
| 104 | + | export constant BIN_SREM: u8 = 5; |
|
| 105 | + | /// Wire tag for binary operation Urem. |
|
| 106 | + | export constant BIN_UREM: u8 = 6; |
|
| 107 | + | /// Wire tag for binary operation Eq. |
|
| 108 | + | export constant BIN_EQ: u8 = 7; |
|
| 109 | + | /// Wire tag for binary operation Ne. |
|
| 110 | + | export constant BIN_NE: u8 = 8; |
|
| 111 | + | /// Wire tag for binary operation Slt. |
|
| 112 | + | export constant BIN_SLT: u8 = 9; |
|
| 113 | + | /// Wire tag for binary operation Sge. |
|
| 114 | + | export constant BIN_SGE: u8 = 10; |
|
| 115 | + | /// Wire tag for binary operation Ult. |
|
| 116 | + | export constant BIN_ULT: u8 = 11; |
|
| 117 | + | /// Wire tag for binary operation Uge. |
|
| 118 | + | export constant BIN_UGE: u8 = 12; |
|
| 119 | + | /// Wire tag for binary operation And. |
|
| 120 | + | export constant BIN_AND: u8 = 13; |
|
| 121 | + | /// Wire tag for binary operation Or. |
|
| 122 | + | export constant BIN_OR: u8 = 14; |
|
| 123 | + | /// Wire tag for binary operation Xor. |
|
| 124 | + | export constant BIN_XOR: u8 = 15; |
|
| 125 | + | /// Wire tag for binary operation Shl. |
|
| 126 | + | export constant BIN_SHL: u8 = 16; |
|
| 127 | + | /// Wire tag for binary operation Sshr. |
|
| 128 | + | export constant BIN_SSHR: u8 = 17; |
|
| 129 | + | /// Wire tag for binary operation Ushr. |
|
| 130 | + | export constant BIN_USHR: u8 = 18; |
|
| 131 | + | ||
| 132 | + | /// Wire tag for unary operation Neg. |
|
| 133 | + | export constant UN_NEG: u8 = 0; |
|
| 134 | + | /// Wire tag for unary operation Not. |
|
| 135 | + | export constant UN_NOT: u8 = 1; |
|
| 136 | + | ||
| 137 | + | /// Wire tag for comparison Eq. |
|
| 138 | + | export constant CMP_EQ: u8 = 0; |
|
| 139 | + | /// Wire tag for comparison Ne. |
|
| 140 | + | export constant CMP_NE: u8 = 1; |
|
| 141 | + | /// Wire tag for comparison Slt. |
|
| 142 | + | export constant CMP_SLT: u8 = 2; |
|
| 143 | + | /// Wire tag for comparison Ult. |
|
| 144 | + | export constant CMP_ULT: u8 = 3; |
|
| 145 | + | ||
| 146 | + | ||
| 147 | + | /// The kind of an exported package symbol. |
|
| 148 | + | export union ExportKind: Copy { |
|
| 149 | + | /// A callable function entry. |
|
| 150 | + | Function, |
|
| 151 | + | /// A global data definition. |
|
| 152 | + | Data, |
|
| 153 | + | } |
|
| 154 | + | ||
| 155 | + | /// A public symbol supplied by a package. |
|
| 156 | + | export record Export: Copy { |
|
| 157 | + | /// Qualified symbol name. |
|
| 158 | + | name: *[u8], |
|
| 159 | + | /// Definition kind. |
|
| 160 | + | kind: ExportKind, |
|
| 161 | + | } |
|
| 162 | + | ||
| 163 | + | /// A binary package and its IL definitions. |
|
| 164 | + | /// Referenced storage must remain valid and immutable while the package is read. |
|
| 165 | + | export record Package: Copy { |
|
| 166 | + | /// Unique symbol names in wire-index order. |
|
| 167 | + | symbols: *unsafe [*[u8]], |
|
| 168 | + | /// Immutable package name. |
|
| 169 | + | name: *[u8], |
|
| 170 | + | /// Names of required packages. |
|
| 171 | + | dependencies: *unsafe [*[u8]], |
|
| 172 | + | /// Public definitions. |
|
| 173 | + | exports: *unsafe [Export], |
|
| 174 | + | /// Default exported entry, if the package has one. |
|
| 175 | + | entry: ?*[u8], |
|
| 176 | + | /// Package-local functions and global data. |
|
| 177 | + | program: il::Program, |
|
| 178 | + | } |
|
| 179 | + | ||
| 180 | + | /// Structural limits supplied by the consumer of decoded IL. |
|
| 181 | + | export record Limits: Copy { |
|
| 182 | + | /// Exclusive upper bound for SSA register numbers. |
|
| 183 | + | registers: u32, |
|
| 184 | + | /// Maximum number of blocks in one function. |
|
| 185 | + | blocks: u32, |
|
| 186 | + | } |
lib/std/lang/il/binary/decodeTests.rad
added
+313 -0
| 1 | + | //! Package reconstruction, malformed input, and arena rollback tests. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use std::lang::alloc; |
|
| 5 | + | use std::lang::il; |
|
| 6 | + | use std::lang::il::binary; |
|
| 7 | + | use std::lang::il::binary::reader; |
|
| 8 | + | use std::lang::il::binary::program; |
|
| 9 | + | ||
| 10 | + | /// Decode arena backing storage. Tests reset it before each use. |
|
| 11 | + | static MEMORY: [u8; 2048] = [0; 2048]; |
|
| 12 | + | ||
| 13 | + | /// Structural limits for small test programs. |
|
| 14 | + | constant LIMITS: binary::Limits = binary::Limits { registers: 16, blocks: 4 }; |
|
| 15 | + | ||
| 16 | + | /// Check a package round trip and failure at every truncated byte length. |
|
| 17 | + | unsafe fn roundTrip(package: &binary::Package) throws (testing::TestError) { |
|
| 18 | + | let mut buffer: [u8; 2048] = [0; 2048]; |
|
| 19 | + | let length = try program::encode(&mut buffer[..], package) catch { |
|
| 20 | + | throw testing::TestError::Failed; |
|
| 21 | + | }; |
|
| 22 | + | let memory = &mut MEMORY[..2048]; |
|
| 23 | + | let mut arena = alloc::new(&mut memory[..]); |
|
| 24 | + | set arena.offset = 8; |
|
| 25 | + | let decoded = try program::decode(&buffer[..length], &mut arena, LIMITS) catch { |
|
| 26 | + | throw testing::TestError::Failed; |
|
| 27 | + | }; |
|
| 28 | + | try testing::expectBytesEq(decoded.name, package.name); |
|
| 29 | + | try testing::expect(decoded.dependencies.len == package.dependencies.len); |
|
| 30 | + | try testing::expect(decoded.exports.len == package.exports.len); |
|
| 31 | + | try testing::expect(decoded.program.fns.len == package.program.fns.len); |
|
| 32 | + | let mut encoded: [u8; 2048] = [0; 2048]; |
|
| 33 | + | let repeated = try program::encode(&mut encoded[..], &decoded) catch { |
|
| 34 | + | throw testing::TestError::Failed; |
|
| 35 | + | }; |
|
| 36 | + | try testing::expectBytesEq(&buffer[..length], &encoded[..repeated]); |
|
| 37 | + | // Decoded names and string initializers retain arena ownership. |
|
| 38 | + | for i in 0..length { |
|
| 39 | + | set buffer[i] = 0; |
|
| 40 | + | } |
|
| 41 | + | let copied = try program::encode(&mut buffer[..], &decoded) catch { |
|
| 42 | + | throw testing::TestError::Failed; |
|
| 43 | + | }; |
|
| 44 | + | try testing::expectBytesEq(&buffer[..copied], &encoded[..repeated]); |
|
| 45 | + | for end in 0..length { |
|
| 46 | + | set arena.offset = 8; |
|
| 47 | + | let mut failed = false; |
|
| 48 | + | try program::decode(&buffer[..end], &mut arena, LIMITS) catch err { |
|
| 49 | + | try testing::expect(err == binary::Error::Truncated); |
|
| 50 | + | set failed = true; |
|
| 51 | + | }; |
|
| 52 | + | try testing::expect(failed); |
|
| 53 | + | try testing::expect(arena.offset == 8); |
|
| 54 | + | } |
|
| 55 | + | set arena.offset = 8; |
|
| 56 | + | let mut input = reader::new(&[], &mut arena, &[]); |
|
| 57 | + | let mut exhausted = false; |
|
| 58 | + | try reader::storage(&mut input, 0xffffffff, 8, 0xffffffff) catch err { |
|
| 59 | + | try testing::expect(err == binary::Error::Storage); |
|
| 60 | + | set exhausted = true; |
|
| 61 | + | }; |
|
| 62 | + | try testing::expect(exhausted); |
|
| 63 | + | try testing::expect(arena.offset == 8); |
|
| 64 | + | // Exercise every smaller arena extent, including alignment boundaries. |
|
| 65 | + | let mut usedArena = alloc::new(&mut memory[..]); |
|
| 66 | + | let _ = try program::decode(&buffer[..length], &mut usedArena, LIMITS) catch { |
|
| 67 | + | throw testing::TestError::Failed; |
|
| 68 | + | }; |
|
| 69 | + | let required = usedArena.offset; |
|
| 70 | + | for capacity in 0..required { |
|
| 71 | + | let mut short = alloc::new(&mut MEMORY[..capacity]); |
|
| 72 | + | let mut failed = false; |
|
| 73 | + | try program::decode(&buffer[..length], &mut short, LIMITS) catch err { |
|
| 74 | + | try testing::expect(err == binary::Error::Storage); |
|
| 75 | + | set failed = true; |
|
| 76 | + | }; |
|
| 77 | + | try testing::expect(failed); |
|
| 78 | + | try testing::expect(short.offset == 0); |
|
| 79 | + | } |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | /// Check an empty program with a package identity. |
|
| 83 | + | @test unsafe fn emptyPackage() throws (testing::TestError) { |
|
| 84 | + | let package = binary::Package { |
|
| 85 | + | symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil, |
|
| 86 | + | program: il::Program { data: &[], fns: &[] }, |
|
| 87 | + | }; |
|
| 88 | + | try roundTrip(&package); |
|
| 89 | + | let mut buffer: [u8; 64] = [0; 64]; |
|
| 90 | + | let length = try program::encode(&mut buffer[..], &package) catch { |
|
| 91 | + | throw testing::TestError::Failed; |
|
| 92 | + | }; |
|
| 93 | + | try testing::expectBytesEq(&buffer[..length], &[ |
|
| 94 | + | 82, 73, 76, 0, 1, 0, 0, 0, 1, 0, 0, 0, |
|
| 95 | + | 1, 0, 0, 0, 112, 0, 0, 0, 0, |
|
| 96 | + | 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
| 97 | + | 0, 0, 0, 0, 0, 0, 0, 0, |
|
| 98 | + | ]); |
|
| 99 | + | } |
|
| 100 | + | ||
| 101 | + | /// Check functions, dependencies, exports, initializers, and block metadata. |
|
| 102 | + | @test unsafe fn fullPackage() throws (testing::TestError) { |
|
| 103 | + | let params: *unsafe [il::Param] = &[il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 }]; |
|
| 104 | + | let mut instrs = [ |
|
| 105 | + | il::Instr::Call { |
|
| 106 | + | retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: il::Val::FnAddr("dep::fn"), |
|
| 107 | + | args: &[il::Val::Reg(il::Reg { n: 1 })], |
|
| 108 | + | }, |
|
| 109 | + | il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 2 }) }, |
|
| 110 | + | ]; |
|
| 111 | + | let func = il::Fn { |
|
| 112 | + | name: "p::main", params, returnType: il::Type::W64, isExtern: false, isLeaf: false, |
|
| 113 | + | blocks: &[il::Block { |
|
| 114 | + | label: "entry", params: &[], instrs: &mut instrs[..], locs: &[], preds: &[0], loopDepth: 2, |
|
| 115 | + | }], |
|
| 116 | + | }; |
|
| 117 | + | let external = il::Fn { |
|
| 118 | + | name: "dep::fn", params, returnType: il::Type::W64, isExtern: true, isLeaf: true, blocks: &[], |
|
| 119 | + | }; |
|
| 120 | + | let data = il::Data { |
|
| 121 | + | name: "p::data", size: 64, alignment: 8, readOnly: false, isZeroInit: false, |
|
| 122 | + | values: &[ |
|
| 123 | + | il::DataValue { item: il::DataItem::Val { typ: il::Type::W8, val: -1 }, count: 2 }, |
|
| 124 | + | il::DataValue { item: il::DataItem::Val { typ: il::Type::W16, val: -2 }, count: 1 }, |
|
| 125 | + | il::DataValue { item: il::DataItem::Val { typ: il::Type::W32, val: -3 }, count: 1 }, |
|
| 126 | + | il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: -4 }, count: 1 }, |
|
| 127 | + | il::DataValue { item: il::DataItem::Sym("p::data"), count: 1 }, |
|
| 128 | + | il::DataValue { item: il::DataItem::Fn("dep::fn"), count: 1 }, |
|
| 129 | + | il::DataValue { item: il::DataItem::Str("bytes"), count: 2 }, |
|
| 130 | + | il::DataValue { item: il::DataItem::Str(""), count: 0 }, |
|
| 131 | + | il::DataValue { item: il::DataItem::Undef, count: 1 }, |
|
| 132 | + | ], |
|
| 133 | + | }; |
|
| 134 | + | let zero = il::Data { |
|
| 135 | + | name: "p::zero", size: 4096, alignment: 4096, readOnly: false, isZeroInit: true, values: &[], |
|
| 136 | + | }; |
|
| 137 | + | let package = binary::Package { |
|
| 138 | + | symbols: &["p", "dep", "p::main", "dep::fn", "p::data", "p::zero"], name: "p", |
|
| 139 | + | dependencies: &["dep"], |
|
| 140 | + | exports: &[ |
|
| 141 | + | binary::Export { name: "p::main", kind: binary::ExportKind::Function }, |
|
| 142 | + | binary::Export { name: "p::data", kind: binary::ExportKind::Data }, |
|
| 143 | + | ], |
|
| 144 | + | entry: "p::main", program: il::Program { data: retainData(&[data, zero]), fns: &[&func, &external] }, |
|
| 145 | + | }; |
|
| 146 | + | try roundTrip(&package); |
|
| 147 | + | } |
|
| 148 | + | ||
| 149 | + | /// Reject an invalid encoded instruction without a panic. |
|
| 150 | + | unsafe fn badInstruction(bytes: *[u8], expected: binary::Error) throws (testing::TestError) { |
|
| 151 | + | let memory = &mut MEMORY[..64]; |
|
| 152 | + | let mut arena = alloc::new(&mut memory[..]); |
|
| 153 | + | let mut input = reader::new(bytes, &mut arena, &["p"]); |
|
| 154 | + | set input.registers = 2; |
|
| 155 | + | set input.blocks = 1; |
|
| 156 | + | let mut failed = false; |
|
| 157 | + | try reader::instr(&mut input) catch err { |
|
| 158 | + | try testing::expect(err == expected); |
|
| 159 | + | set failed = true; |
|
| 160 | + | }; |
|
| 161 | + | try testing::expect(failed); |
|
| 162 | + | } |
|
| 163 | + | ||
| 164 | + | /// Check instruction tags, operand tags, types, optional flags, and indices. |
|
| 165 | + | @test unsafe fn malformedInstructions() throws (testing::TestError) { |
|
| 166 | + | try badInstruction(&[255], binary::Error::Invalid); |
|
| 167 | + | try badInstruction(&[5, 0, 0, 0, 0, 255], binary::Error::Invalid); |
|
| 168 | + | try badInstruction(&[1, 3], binary::Error::Invalid); |
|
| 169 | + | try badInstruction(&[6, 255], binary::Error::Invalid); |
|
| 170 | + | try badInstruction(&[7, 255], binary::Error::Invalid); |
|
| 171 | + | try badInstruction(&[13, 255], binary::Error::Invalid); |
|
| 172 | + | try badInstruction(&[11, 2], binary::Error::Invalid); |
|
| 173 | + | try badInstruction(&[10, 8, 2], binary::Error::Invalid); |
|
| 174 | + | try badInstruction(&[5, 2, 0, 0, 0], binary::Error::Invalid); |
|
| 175 | + | try badInstruction(&[5, 0, 0, 0, 0, 0, 2, 0, 0, 0], binary::Error::Invalid); |
|
| 176 | + | try badInstruction(&[12, 1, 0, 0, 0], binary::Error::Invalid); |
|
| 177 | + | try badInstruction(&[5, 0, 0, 0, 0, 2, 1, 0, 0, 0], binary::Error::Symbol); |
|
| 178 | + | try badInstruction(&[12, 0, 0, 0, 0, 255, 255, 255, 255], binary::Error::Truncated); |
|
| 179 | + | } |
|
| 180 | + | ||
| 181 | + | /// Check header validation and package symbol indices with arena rollback. |
|
| 182 | + | @test unsafe fn malformedPackages() throws (testing::TestError) { |
|
| 183 | + | let package = binary::Package { |
|
| 184 | + | symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil, |
|
| 185 | + | program: il::Program { data: &[], fns: &[] }, |
|
| 186 | + | }; |
|
| 187 | + | let mut buffer: [u8; 64] = [0; 64]; |
|
| 188 | + | let length = try program::encode(&mut buffer[..], &package) catch { |
|
| 189 | + | throw testing::TestError::Failed; |
|
| 190 | + | }; |
|
| 191 | + | let memory = &mut MEMORY[..64]; |
|
| 192 | + | let mut arena = alloc::new(&mut memory[..]); |
|
| 193 | + | for offset in &[0, 4, 17, 29] { |
|
| 194 | + | let original = buffer[offset]; |
|
| 195 | + | set buffer[offset] = 255; |
|
| 196 | + | set arena.offset = 8; |
|
| 197 | + | let mut failed = false; |
|
| 198 | + | try program::decode(&buffer[..length], &mut arena, LIMITS) catch { |
|
| 199 | + | set failed = true; |
|
| 200 | + | }; |
|
| 201 | + | try testing::expect(failed); |
|
| 202 | + | try testing::expect(arena.offset == 8); |
|
| 203 | + | set buffer[offset] = original; |
|
| 204 | + | } |
|
| 205 | + | let mut trailing = false; |
|
| 206 | + | try program::decode(&buffer[..length + 1], &mut arena, LIMITS) catch err { |
|
| 207 | + | try testing::expect(err == binary::Error::Invalid); |
|
| 208 | + | set trailing = true; |
|
| 209 | + | }; |
|
| 210 | + | try testing::expect(trailing); |
|
| 211 | + | try testing::expect(arena.offset == 8); |
|
| 212 | + | } |
|
| 213 | + | ||
| 214 | + | /// Encode a malformed package and check rejection with arena rollback. |
|
| 215 | + | unsafe fn rejected(package: &binary::Package, limits: binary::Limits) throws (testing::TestError) { |
|
| 216 | + | let mut buffer: [u8; 512] = [0; 512]; |
|
| 217 | + | let length = try program::encode(&mut buffer[..], package) catch { |
|
| 218 | + | throw testing::TestError::Failed; |
|
| 219 | + | }; |
|
| 220 | + | let memory = &mut MEMORY[..256]; |
|
| 221 | + | let mut arena = alloc::new(&mut memory[..]); |
|
| 222 | + | set arena.offset = 8; |
|
| 223 | + | let mut failed = false; |
|
| 224 | + | try program::decode(&buffer[..length], &mut arena, limits) catch err { |
|
| 225 | + | try testing::expect(err == binary::Error::Invalid); |
|
| 226 | + | set failed = true; |
|
| 227 | + | }; |
|
| 228 | + | try testing::expect(failed); |
|
| 229 | + | try testing::expect(arena.offset == 8); |
|
| 230 | + | } |
|
| 231 | + | ||
| 232 | + | /// Check initializer extent arithmetic, alignments, and duplicate symbols. |
|
| 233 | + | @test unsafe fn invalidData() throws (testing::TestError) { |
|
| 234 | + | let mut item = il::Data { |
|
| 235 | + | name: "p", size: 0xffffffff, alignment: 8, readOnly: false, isZeroInit: false, |
|
| 236 | + | values: &[ |
|
| 237 | + | il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: 0 }, count: 0xffffffff }, |
|
| 238 | + | ], |
|
| 239 | + | }; |
|
| 240 | + | let mut package = binary::Package { |
|
| 241 | + | symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil, |
|
| 242 | + | program: il::Program { data: retainData(&[item]), fns: &[] }, |
|
| 243 | + | }; |
|
| 244 | + | try rejected(&package, LIMITS); |
|
| 245 | + | set item.size = 1; |
|
| 246 | + | set item.values = &[ |
|
| 247 | + | il::DataValue { item: il::DataItem::Undef, count: 1 }, |
|
| 248 | + | il::DataValue { item: il::DataItem::Undef, count: 1 }, |
|
| 249 | + | ]; |
|
| 250 | + | set package.program = il::Program { data: retainData(&[item]), fns: &[] }; |
|
| 251 | + | try rejected(&package, LIMITS); |
|
| 252 | + | set item.values = &[]; |
|
| 253 | + | for alignment in &[0, 3] { |
|
| 254 | + | set item.alignment = alignment; |
|
| 255 | + | set package.program = il::Program { data: retainData(&[item]), fns: &[] }; |
|
| 256 | + | try rejected(&package, LIMITS); |
|
| 257 | + | } |
|
| 258 | + | set package.program = il::Program { data: &[], fns: &[] }; |
|
| 259 | + | set package.symbols = &["p", "p"]; |
|
| 260 | + | try rejected(&package, LIMITS); |
|
| 261 | + | set package.symbols = &["p", ""]; |
|
| 262 | + | try rejected(&package, LIMITS); |
|
| 263 | + | } |
|
| 264 | + | ||
| 265 | + | /// Check function block limits, parameter indices, and predecessor indices. |
|
| 266 | + | @test unsafe fn invalidFunctions() throws (testing::TestError) { |
|
| 267 | + | let mut instrs = [il::Instr::Ret { val: nil }]; |
|
| 268 | + | let mut block = il::Block { |
|
| 269 | + | label: "entry", params: &[], instrs: &mut instrs[..], locs: &[], preds: &[], loopDepth: 0, |
|
| 270 | + | }; |
|
| 271 | + | let mut func = il::Fn { |
|
| 272 | + | name: "p", params: &[], returnType: il::Type::W64, |
|
| 273 | + | isExtern: false, isLeaf: true, blocks: &[block], |
|
| 274 | + | }; |
|
| 275 | + | let package = binary::Package { |
|
| 276 | + | symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil, |
|
| 277 | + | program: il::Program { data: &[], fns: &[&func] }, |
|
| 278 | + | }; |
|
| 279 | + | try rejected(&package, binary::Limits { registers: 16, blocks: 0 }); |
|
| 280 | + | set func.isExtern = true; |
|
| 281 | + | try rejected(&package, LIMITS); |
|
| 282 | + | set func.isExtern = false; |
|
| 283 | + | set func.params = &[il::Param { value: il::Reg { n: 16 }, type: il::Type::W64 }]; |
|
| 284 | + | try rejected(&package, LIMITS); |
|
| 285 | + | set func.params = &[]; |
|
| 286 | + | set block.preds = &[1]; |
|
| 287 | + | set func.blocks = &[block]; |
|
| 288 | + | try rejected(&package, LIMITS); |
|
| 289 | + | } |
|
| 290 | + | ||
| 291 | + | /// Reject unknown initializer tags and widths before reading their payloads. |
|
| 292 | + | @test unsafe fn malformedInitializers() throws (testing::TestError) { |
|
| 293 | + | let memory = &mut MEMORY[..8]; |
|
| 294 | + | let mut arena = alloc::new(&mut memory[..]); |
|
| 295 | + | let fixtures: [*[u8]; 2] = [&[255], &[0, 3]]; |
|
| 296 | + | for bytes in &fixtures[..] { |
|
| 297 | + | let mut input = reader::new(bytes, &mut arena, &[]); |
|
| 298 | + | let mut failed = false; |
|
| 299 | + | try reader::dataValue(&mut input) catch err { |
|
| 300 | + | try testing::expect(err == binary::Error::Invalid); |
|
| 301 | + | set failed = true; |
|
| 302 | + | }; |
|
| 303 | + | try testing::expect(failed); |
|
| 304 | + | } |
|
| 305 | + | } |
|
| 306 | + | ||
| 307 | + | /// Copy data fixtures into stable storage for the package descriptor. |
|
| 308 | + | unsafe fn retainData(items: &[il::Data]) -> *[il::Data] { |
|
| 309 | + | unsafe static DATA: [il::Data; 2] = undefined; |
|
| 310 | + | assert items.len <= DATA.len; |
|
| 311 | + | for item, i in items { set DATA[i] = item; } |
|
| 312 | + | return &DATA[..items.len]; |
|
| 313 | + | } |
lib/std/lang/il/binary/program.rad
added
+250 -0
| 1 | + | //! Binary package envelopes and IL program reconstruction. |
|
| 2 | + | //! Tables occur in this order: symbols, dependencies, exports, data, functions. |
|
| 3 | + | //! Block records carry labels, parameters, loop depth, predecessors, instructions. |
|
| 4 | + | ||
| 5 | + | use std::lang::il; |
|
| 6 | + | use std::lang::il::binary; |
|
| 7 | + | use std::lang::il::binary::reader; |
|
| 8 | + | use std::lang::il::binary::writer; |
|
| 9 | + | use std::lang::alloc; |
|
| 10 | + | use std::mem; |
|
| 11 | + | ||
| 12 | + | /// Write a counted sequence of typed SSA parameters. |
|
| 13 | + | unsafe fn writeParams(out: &mut writer::Writer, params: &[il::Param]) throws (binary::Error) { |
|
| 14 | + | try writer::integer(out, params.len as u64, 4); |
|
| 15 | + | for param in params { |
|
| 16 | + | try writer::integer(out, param.value.n as u64, 4); |
|
| 17 | + | try writer::typ(out, param.type); |
|
| 18 | + | } |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | /// Write a package. Return the number of encoded bytes. |
|
| 22 | + | /// All package tables and IL storage must remain valid during encoding. |
|
| 23 | + | export unsafe fn encode(bytes: &mut [u8], package: &binary::Package) -> u32 throws (binary::Error) { |
|
| 24 | + | let mut out = writer::new(bytes, package.symbols); |
|
| 25 | + | try writer::integer(&mut out, binary::MAGIC as u64, 4); |
|
| 26 | + | try writer::integer(&mut out, binary::VERSION as u64, 4); |
|
| 27 | + | try writer::integer(&mut out, package.symbols.len as u64, 4); |
|
| 28 | + | for name in package.symbols { |
|
| 29 | + | try writer::bytes(&mut out, name); |
|
| 30 | + | } |
|
| 31 | + | try writer::symbol(&mut out, package.name); |
|
| 32 | + | try writer::integer(&mut out, package.dependencies.len as u64, 4); |
|
| 33 | + | for name in package.dependencies { |
|
| 34 | + | try writer::symbol(&mut out, name); |
|
| 35 | + | } |
|
| 36 | + | try writer::integer(&mut out, package.exports.len as u64, 4); |
|
| 37 | + | for item in package.exports { |
|
| 38 | + | try writer::symbol(&mut out, item.name); |
|
| 39 | + | match item.kind { |
|
| 40 | + | case binary::ExportKind::Function => try writer::integer(&mut out, 0, 1), |
|
| 41 | + | case binary::ExportKind::Data => try writer::integer(&mut out, 1, 1), |
|
| 42 | + | } |
|
| 43 | + | } |
|
| 44 | + | if let entry = package.entry { |
|
| 45 | + | try writer::integer(&mut out, 1, 1); |
|
| 46 | + | try writer::symbol(&mut out, entry); |
|
| 47 | + | } else { |
|
| 48 | + | try writer::integer(&mut out, 0, 1); |
|
| 49 | + | } |
|
| 50 | + | try writer::integer(&mut out, package.program.data.len as u64, 4); |
|
| 51 | + | for item in package.program.data { |
|
| 52 | + | try writer::symbol(&mut out, item.name); |
|
| 53 | + | try writer::integer(&mut out, item.size as u64, 4); |
|
| 54 | + | try writer::integer(&mut out, item.alignment as u64, 4); |
|
| 55 | + | try writer::integer(&mut out, 1 if item.readOnly else 0, 1); |
|
| 56 | + | try writer::integer(&mut out, 1 if item.isZeroInit else 0, 1); |
|
| 57 | + | try writer::integer(&mut out, item.values.len as u64, 4); |
|
| 58 | + | for value in item.values { |
|
| 59 | + | try writer::dataValue(&mut out, value); |
|
| 60 | + | } |
|
| 61 | + | } |
|
| 62 | + | try writer::integer(&mut out, package.program.fns.len as u64, 4); |
|
| 63 | + | for func in package.program.fns { |
|
| 64 | + | try writer::symbol(&mut out, func.name); |
|
| 65 | + | try writer::typ(&mut out, func.returnType); |
|
| 66 | + | try writer::integer(&mut out, 1 if func.isExtern else 0, 1); |
|
| 67 | + | try writeParams(&mut out, func.params); |
|
| 68 | + | try writer::integer(&mut out, func.blocks.len as u64, 4); |
|
| 69 | + | for block in func.blocks { |
|
| 70 | + | try writer::bytes(&mut out, block.label); |
|
| 71 | + | try writeParams(&mut out, block.params); |
|
| 72 | + | try writer::integer(&mut out, block.loopDepth as u64, 4); |
|
| 73 | + | try writer::integer(&mut out, block.preds.len as u64, 4); |
|
| 74 | + | for pred in block.preds { |
|
| 75 | + | try writer::integer(&mut out, pred as u64, 4); |
|
| 76 | + | } |
|
| 77 | + | try writer::integer(&mut out, block.instrs.len as u64, 4); |
|
| 78 | + | for instr in block.instrs { |
|
| 79 | + | try writer::instr(&mut out, instr); |
|
| 80 | + | } |
|
| 81 | + | } |
|
| 82 | + | } |
|
| 83 | + | return out.offset; |
|
| 84 | + | } |
|
| 85 | + | ||
| 86 | + | /// Read typed SSA parameters with checked register indices. |
|
| 87 | + | unsafe fn readParams(input: &mut reader::Reader) -> *unsafe [il::Param] throws (binary::Error) { |
|
| 88 | + | let n = try reader::count(input, 5); |
|
| 89 | + | let params = try reader::storage(input, @sizeOf(il::Param), @alignOf(il::Param), n) |
|
| 90 | + | as *mut [il::Param]; |
|
| 91 | + | for i in 0..n { |
|
| 92 | + | let value = try reader::reg(input); |
|
| 93 | + | let t = try reader::typ(input); |
|
| 94 | + | set params[i] = il::Param { value, type: t }; |
|
| 95 | + | } |
|
| 96 | + | return (¶ms[..]) as *unsafe [il::Param]; |
|
| 97 | + | } |
|
| 98 | + | ||
| 99 | + | /// Read global data and check initializer extents against declared storage. |
|
| 100 | + | unsafe fn readData(input: &mut reader::Reader) -> *[il::Data] throws (binary::Error) { |
|
| 101 | + | let n = try reader::count(input, 18); |
|
| 102 | + | let items = try reader::storage(input, @sizeOf(il::Data), @alignOf(il::Data), n) |
|
| 103 | + | as *mut [il::Data]; |
|
| 104 | + | for i in 0..n { |
|
| 105 | + | let name = try reader::symbol(input); |
|
| 106 | + | let size = try reader::integer(input, 4) as u32; |
|
| 107 | + | let alignment = try reader::integer(input, 4) as u32; |
|
| 108 | + | if alignment == 0 or (alignment & (alignment - 1)) <> 0 { |
|
| 109 | + | throw binary::Error::Invalid; |
|
| 110 | + | } |
|
| 111 | + | let readOnly = try reader::flag(input); |
|
| 112 | + | let isZeroInit = try reader::flag(input); |
|
| 113 | + | let count = try reader::count(input, 5); |
|
| 114 | + | let values = try reader::storage(input, @sizeOf(il::DataValue), @alignOf(il::DataValue), count) |
|
| 115 | + | as *mut [il::DataValue]; |
|
| 116 | + | let mut extent: u64 = 0; |
|
| 117 | + | for j in 0..count { |
|
| 118 | + | let value = try reader::dataValue(input); |
|
| 119 | + | let mut width: u32 = 0; |
|
| 120 | + | match value.item { |
|
| 121 | + | case il::DataItem::Val { typ, .. } => { set width = il::typeSize(typ); }, |
|
| 122 | + | case il::DataItem::Sym(_), il::DataItem::Fn(_) => { set width = 8; }, |
|
| 123 | + | case il::DataItem::Str(text) => { set width = text.len; }, |
|
| 124 | + | case il::DataItem::Undef => { set width = 1; }, |
|
| 125 | + | } |
|
| 126 | + | set extent += width as u64 * value.count as u64; |
|
| 127 | + | if extent > size as u64 { |
|
| 128 | + | throw binary::Error::Invalid; |
|
| 129 | + | } |
|
| 130 | + | set values[j] = value; |
|
| 131 | + | } |
|
| 132 | + | set items[i] = il::Data { name, size, alignment, readOnly, isZeroInit, values }; |
|
| 133 | + | } |
|
| 134 | + | return items; |
|
| 135 | + | } |
|
| 136 | + | ||
| 137 | + | /// Read functions with checked block and register indices. |
|
| 138 | + | unsafe fn readFunctions(input: &mut reader::Reader, limits: binary::Limits) |
|
| 139 | + | -> *unsafe [*unsafe il::Fn] throws (binary::Error) |
|
| 140 | + | { |
|
| 141 | + | let n = try reader::count(input, 14); |
|
| 142 | + | let fns = try reader::storage(input, @sizeOf(*il::Fn), @alignOf(*il::Fn), n) as *mut [*unsafe il::Fn]; |
|
| 143 | + | set input.registers = limits.registers; |
|
| 144 | + | for i in 0..n { |
|
| 145 | + | let name = try reader::symbol(input); |
|
| 146 | + | let returnType = try reader::typ(input); |
|
| 147 | + | let isExtern = try reader::flag(input); |
|
| 148 | + | let params = try readParams(input); |
|
| 149 | + | let count = try reader::count(input, 20); |
|
| 150 | + | if count > limits.blocks or (isExtern and count <> 0) { |
|
| 151 | + | throw binary::Error::Invalid; |
|
| 152 | + | } |
|
| 153 | + | set input.blocks = count; |
|
| 154 | + | let blocks = try reader::storage(input, @sizeOf(il::Block), @alignOf(il::Block), count) |
|
| 155 | + | as *mut [il::Block]; |
|
| 156 | + | let mut isLeaf = true; |
|
| 157 | + | for j in 0..count { |
|
| 158 | + | let label = try reader::bytes(input); |
|
| 159 | + | let blockParams = try readParams(input); |
|
| 160 | + | let loopDepth = try reader::integer(input, 4) as u32; |
|
| 161 | + | let predCount = try reader::count(input, 4); |
|
| 162 | + | let preds = try reader::storage(input, @sizeOf(u32), @alignOf(u32), predCount) as *mut [u32]; |
|
| 163 | + | for k in 0..predCount { |
|
| 164 | + | set preds[k] = try reader::target(input); |
|
| 165 | + | } |
|
| 166 | + | let instrCount = try reader::count(input, 1); |
|
| 167 | + | let instrs = try reader::storage(input, @sizeOf(il::Instr), @alignOf(il::Instr), instrCount) |
|
| 168 | + | as *mut [il::Instr]; |
|
| 169 | + | for k in 0..instrCount { |
|
| 170 | + | let instr = try reader::instr(input); |
|
| 171 | + | if il::isCall(instr) { |
|
| 172 | + | set isLeaf = false; |
|
| 173 | + | } |
|
| 174 | + | set instrs[k] = instr; |
|
| 175 | + | } |
|
| 176 | + | set blocks[j] = il::Block { |
|
| 177 | + | label, params: blockParams, instrs: (&mut instrs[..]) as *unsafe mut [il::Instr], locs: &[], preds: (&preds[..]) as *unsafe [u32], loopDepth, |
|
| 178 | + | }; |
|
| 179 | + | } |
|
| 180 | + | let func = try reader::storage(input, @sizeOf(il::Fn), @alignOf(il::Fn), 1) as *mut [il::Fn]; |
|
| 181 | + | set func[0] = il::Fn { name, params, returnType, isExtern, isLeaf, blocks: (&blocks[..]) as *unsafe [il::Block] }; |
|
| 182 | + | set fns[i] = &func[0]; |
|
| 183 | + | } |
|
| 184 | + | return (&fns[..]) as *unsafe [*unsafe il::Fn]; |
|
| 185 | + | } |
|
| 186 | + | ||
| 187 | + | /// Read the package tables. The input must contain exactly one package. |
|
| 188 | + | unsafe fn readPackage(input: &mut reader::Reader, limits: binary::Limits) |
|
| 189 | + | -> binary::Package throws (binary::Error) |
|
| 190 | + | { |
|
| 191 | + | let magic = try reader::integer(input, 4) as u32; |
|
| 192 | + | let version = try reader::integer(input, 4) as u32; |
|
| 193 | + | if magic <> binary::MAGIC or version <> binary::VERSION { |
|
| 194 | + | throw binary::Error::Invalid; |
|
| 195 | + | } |
|
| 196 | + | let symbolCount = try reader::count(input, 4); |
|
| 197 | + | let symbols = try reader::storage(input, @sizeOf(*[u8]), @alignOf(*[u8]), symbolCount) |
|
| 198 | + | as *mut [*[u8]]; |
|
| 199 | + | for i in 0..symbolCount { |
|
| 200 | + | let name = try reader::bytes(input); |
|
| 201 | + | if name.len == 0 { |
|
| 202 | + | throw binary::Error::Invalid; |
|
| 203 | + | } |
|
| 204 | + | for j in 0..i { |
|
| 205 | + | if mem::eq(name, symbols[j]) { |
|
| 206 | + | throw binary::Error::Invalid; |
|
| 207 | + | } |
|
| 208 | + | } |
|
| 209 | + | set symbols[i] = name; |
|
| 210 | + | } |
|
| 211 | + | set input.symbols = (&symbols[..]) as *unsafe [*[u8]]; |
|
| 212 | + | let name = try reader::symbol(input); |
|
| 213 | + | let depCount = try reader::count(input, 4); |
|
| 214 | + | let dependencies = try reader::storage(input, @sizeOf(*[u8]), @alignOf(*[u8]), depCount) |
|
| 215 | + | as *mut [*[u8]]; |
|
| 216 | + | for i in 0..depCount { |
|
| 217 | + | set dependencies[i] = try reader::symbol(input); |
|
| 218 | + | } |
|
| 219 | + | let exportCount = try reader::count(input, 5); |
|
| 220 | + | let exports = try reader::storage(input, @sizeOf(binary::Export), @alignOf(binary::Export), exportCount) |
|
| 221 | + | as *mut [binary::Export]; |
|
| 222 | + | for i in 0..exportCount { |
|
| 223 | + | let symbol = try reader::symbol(input); |
|
| 224 | + | let kind = binary::ExportKind::Data if try reader::flag(input) else binary::ExportKind::Function; |
|
| 225 | + | set exports[i] = binary::Export { name: symbol, kind }; |
|
| 226 | + | } |
|
| 227 | + | let mut entry: ?*[u8] = nil; |
|
| 228 | + | if try reader::flag(input) { |
|
| 229 | + | set entry = try reader::symbol(input); |
|
| 230 | + | } |
|
| 231 | + | let data = try readData(input); |
|
| 232 | + | let fns = try readFunctions(input, limits); |
|
| 233 | + | if input.offset <> input.bytes.len { |
|
| 234 | + | throw binary::Error::Invalid; |
|
| 235 | + | } |
|
| 236 | + | return binary::Package { symbols: (&symbols[..]) as *unsafe [*[u8]], name, dependencies: (&dependencies[..]) as *unsafe [*[u8]], exports: (&exports[..]) as *unsafe [binary::Export], entry, program: il::Program { data, fns } }; |
|
| 237 | + | } |
|
| 238 | + | ||
| 239 | + | /// Decode one package. Restore the arena offset on every failure. |
|
| 240 | + | /// The caller must retain the decoded allocations until the last package use. |
|
| 241 | + | export unsafe fn decode(bytes: &[u8], arena: &mut alloc::Arena, limits: binary::Limits) |
|
| 242 | + | -> binary::Package throws (binary::Error) |
|
| 243 | + | { |
|
| 244 | + | let saved = alloc::save(arena); |
|
| 245 | + | let mut input = reader::new(bytes, arena, &[]); |
|
| 246 | + | return try readPackage(&mut input, limits) catch err { |
|
| 247 | + | alloc::restore(arena, saved); |
|
| 248 | + | throw err; |
|
| 249 | + | }; |
|
| 250 | + | } |
lib/std/lang/il/binary/reader.rad
added
+358 -0
| 1 | + | //! Checked binary RIL decoding into caller-owned arena storage. |
|
| 2 | + | //! Callers discard partial results and restore the arena after a failed decode. |
|
| 3 | + | ||
| 4 | + | use std::lang::il; |
|
| 5 | + | use std::lang::il::binary; |
|
| 6 | + | use std::lang::alloc; |
|
| 7 | + | ||
| 8 | + | /// Input cursor and reconstruction storage. |
|
| 9 | + | /// Input bytes, symbols, and the arena must remain valid during each read. |
|
| 10 | + | export record Reader: Copy { |
|
| 11 | + | /// Encoded bytes. |
|
| 12 | + | bytes: *unsafe [u8], |
|
| 13 | + | /// Number of bytes consumed. |
|
| 14 | + | offset: u32, |
|
| 15 | + | /// Storage for decoded sequences and byte strings. |
|
| 16 | + | arena: *unsafe mut alloc::Arena, |
|
| 17 | + | /// Symbol names in wire-index order. |
|
| 18 | + | symbols: *unsafe [*[u8]], |
|
| 19 | + | /// Exclusive bound for SSA register numbers. |
|
| 20 | + | registers: u32, |
|
| 21 | + | /// Number of blocks in the current function. |
|
| 22 | + | blocks: u32, |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// Create a cursor. Set function bounds before reading instructions. |
|
| 26 | + | export unsafe fn new(bytes: &[u8], arena: &mut alloc::Arena, symbols: *unsafe [*[u8]]) -> Reader { |
|
| 27 | + | return Reader { bytes: bytes as *unsafe [u8], offset: 0, arena: arena as *unsafe mut alloc::Arena, symbols, registers: 0, blocks: 0 }; |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | /// Read an unsigned integer with width 1, 2, 4, or 8. |
|
| 31 | + | export unsafe fn integer(input: &mut Reader, width: u32) -> u64 throws (binary::Error) { |
|
| 32 | + | if width <> 1 and width <> 2 and width <> 4 and width <> 8 { |
|
| 33 | + | throw binary::Error::Invalid; |
|
| 34 | + | } |
|
| 35 | + | if input.offset > input.bytes.len or width > input.bytes.len - input.offset { |
|
| 36 | + | throw binary::Error::Truncated; |
|
| 37 | + | } |
|
| 38 | + | let mut result: u64 = 0; |
|
| 39 | + | for i in 0..width { |
|
| 40 | + | set result |= (input.bytes[input.offset + i] as u64) << (i * 8) as u64; |
|
| 41 | + | } |
|
| 42 | + | set input.offset += width; |
|
| 43 | + | return result; |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | /// Read a zero-or-one optional-field or boolean marker. |
|
| 47 | + | export unsafe fn flag(input: &mut Reader) -> bool throws (binary::Error) { |
|
| 48 | + | let n = try integer(input, 1); |
|
| 49 | + | if n > 1 { |
|
| 50 | + | throw binary::Error::Invalid; |
|
| 51 | + | } |
|
| 52 | + | return n == 1; |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | /// Read a count and check the minimum required input bytes before allocation. |
|
| 56 | + | export unsafe fn count(input: &mut Reader, minimum: u32) -> u32 throws (binary::Error) { |
|
| 57 | + | assert minimum > 0; |
|
| 58 | + | let n = try integer(input, 4) as u32; |
|
| 59 | + | if n > (input.bytes.len - input.offset) / minimum { |
|
| 60 | + | throw binary::Error::Truncated; |
|
| 61 | + | } |
|
| 62 | + | return n; |
|
| 63 | + | } |
|
| 64 | + | ||
| 65 | + | /// Allocate a typed sequence after checking size and alignment arithmetic. |
|
| 66 | + | export unsafe fn storage(input: &mut Reader, size: u32, alignment: u32, count: u32) |
|
| 67 | + | -> *mut [opaque] throws (binary::Error) |
|
| 68 | + | { |
|
| 69 | + | assert size > 0 and alignment > 0 and (alignment & (alignment - 1)) == 0; |
|
| 70 | + | if count == 0 { |
|
| 71 | + | return &mut []; |
|
| 72 | + | } |
|
| 73 | + | let aligned = (input.arena.offset as u64 + alignment as u64 - 1) & |
|
| 74 | + | ~(alignment as u64 - 1); |
|
| 75 | + | if aligned > input.arena.data.len as u64 { |
|
| 76 | + | throw binary::Error::Storage; |
|
| 77 | + | } |
|
| 78 | + | if count as u64 > (input.arena.data.len as u64 - aligned) / size as u64 { |
|
| 79 | + | throw binary::Error::Storage; |
|
| 80 | + | } |
|
| 81 | + | return try alloc::allocSlice(input.arena, size, alignment, count) catch { |
|
| 82 | + | throw binary::Error::Storage; |
|
| 83 | + | }; |
|
| 84 | + | } |
|
| 85 | + | ||
| 86 | + | /// Read a length-prefixed byte string and copy it into the arena. |
|
| 87 | + | export unsafe fn bytes(input: &mut Reader) -> *[u8] throws (binary::Error) { |
|
| 88 | + | let n = try count(input, 1); |
|
| 89 | + | let result = try storage(input, @sizeOf(u8), @alignOf(u8), n) as *mut [u8]; |
|
| 90 | + | for i in 0..n { |
|
| 91 | + | set result[i] = input.bytes[input.offset + i]; |
|
| 92 | + | } |
|
| 93 | + | set input.offset += n; |
|
| 94 | + | return result; |
|
| 95 | + | } |
|
| 96 | + | ||
| 97 | + | /// Resolve a checked symbol-table index. |
|
| 98 | + | export unsafe fn symbol(input: &mut Reader) -> *[u8] throws (binary::Error) { |
|
| 99 | + | let index = try integer(input, 4) as u32; |
|
| 100 | + | if index >= input.symbols.len { |
|
| 101 | + | throw binary::Error::Symbol; |
|
| 102 | + | } |
|
| 103 | + | return input.symbols[index]; |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | /// Read an IL type from its byte width. |
|
| 107 | + | export unsafe fn typ(input: &mut Reader) -> il::Type throws (binary::Error) { |
|
| 108 | + | let width = try integer(input, 1); |
|
| 109 | + | match width { |
|
| 110 | + | case 1 => return il::Type::W8, |
|
| 111 | + | case 2 => return il::Type::W16, |
|
| 112 | + | case 4 => return il::Type::W32, |
|
| 113 | + | case 8 => return il::Type::W64, |
|
| 114 | + | else => throw binary::Error::Invalid, |
|
| 115 | + | } |
|
| 116 | + | } |
|
| 117 | + | ||
| 118 | + | /// Read a register index within the current function's bound. |
|
| 119 | + | export unsafe fn reg(input: &mut Reader) -> il::Reg throws (binary::Error) { |
|
| 120 | + | let n = try integer(input, 4) as u32; |
|
| 121 | + | if n >= input.registers { |
|
| 122 | + | throw binary::Error::Invalid; |
|
| 123 | + | } |
|
| 124 | + | return il::Reg { n }; |
|
| 125 | + | } |
|
| 126 | + | ||
| 127 | + | /// Read a target block index within the current function. |
|
| 128 | + | export unsafe fn target(input: &mut Reader) -> u32 throws (binary::Error) { |
|
| 129 | + | let n = try integer(input, 4) as u32; |
|
| 130 | + | if n >= input.blocks { |
|
| 131 | + | throw binary::Error::Invalid; |
|
| 132 | + | } |
|
| 133 | + | return n; |
|
| 134 | + | } |
|
| 135 | + | ||
| 136 | + | /// Read a tagged value with checked register and symbol indices. |
|
| 137 | + | export unsafe fn val(input: &mut Reader) -> il::Val throws (binary::Error) { |
|
| 138 | + | let tag = try integer(input, 1) as u8; |
|
| 139 | + | match tag { |
|
| 140 | + | case super::VALUE_REG => return il::Val::Reg(try reg(input)), |
|
| 141 | + | case super::VALUE_IMM => return il::Val::Imm(try integer(input, 8) as i64), |
|
| 142 | + | case super::VALUE_DATASYM => return il::Val::DataSym(try symbol(input)), |
|
| 143 | + | case super::VALUE_FNADDR => return il::Val::FnAddr(try symbol(input)), |
|
| 144 | + | case super::VALUE_UNDEF => return il::Val::Undef, |
|
| 145 | + | else => throw binary::Error::Invalid, |
|
| 146 | + | } |
|
| 147 | + | } |
|
| 148 | + | ||
| 149 | + | /// Read a counted sequence of values. |
|
| 150 | + | export unsafe fn values(input: &mut Reader) -> *unsafe mut [il::Val] throws (binary::Error) { |
|
| 151 | + | let n = try count(input, 1); |
|
| 152 | + | let result = try storage(input, @sizeOf(il::Val), @alignOf(il::Val), n) as *mut [il::Val]; |
|
| 153 | + | for i in 0..n { |
|
| 154 | + | set result[i] = try val(input); |
|
| 155 | + | } |
|
| 156 | + | return (&mut result[..]) as *unsafe mut [il::Val]; |
|
| 157 | + | } |
|
| 158 | + | ||
| 159 | + | /// Read a checked bin operation tag. |
|
| 160 | + | unsafe fn binOp(input: &mut Reader) -> il::BinOp throws (binary::Error) { |
|
| 161 | + | let tag = try integer(input, 1) as u8; |
|
| 162 | + | match tag { |
|
| 163 | + | case super::BIN_ADD => return il::BinOp::Add, |
|
| 164 | + | case super::BIN_SUB => return il::BinOp::Sub, |
|
| 165 | + | case super::BIN_MUL => return il::BinOp::Mul, |
|
| 166 | + | case super::BIN_SDIV => return il::BinOp::Sdiv, |
|
| 167 | + | case super::BIN_UDIV => return il::BinOp::Udiv, |
|
| 168 | + | case super::BIN_SREM => return il::BinOp::Srem, |
|
| 169 | + | case super::BIN_UREM => return il::BinOp::Urem, |
|
| 170 | + | case super::BIN_EQ => return il::BinOp::Eq, |
|
| 171 | + | case super::BIN_NE => return il::BinOp::Ne, |
|
| 172 | + | case super::BIN_SLT => return il::BinOp::Slt, |
|
| 173 | + | case super::BIN_SGE => return il::BinOp::Sge, |
|
| 174 | + | case super::BIN_ULT => return il::BinOp::Ult, |
|
| 175 | + | case super::BIN_UGE => return il::BinOp::Uge, |
|
| 176 | + | case super::BIN_AND => return il::BinOp::And, |
|
| 177 | + | case super::BIN_OR => return il::BinOp::Or, |
|
| 178 | + | case super::BIN_XOR => return il::BinOp::Xor, |
|
| 179 | + | case super::BIN_SHL => return il::BinOp::Shl, |
|
| 180 | + | case super::BIN_SSHR => return il::BinOp::Sshr, |
|
| 181 | + | case super::BIN_USHR => return il::BinOp::Ushr, |
|
| 182 | + | else => throw binary::Error::Invalid, |
|
| 183 | + | } |
|
| 184 | + | } |
|
| 185 | + | ||
| 186 | + | /// Read a checked un operation tag. |
|
| 187 | + | unsafe fn unOp(input: &mut Reader) -> il::UnOp throws (binary::Error) { |
|
| 188 | + | let tag = try integer(input, 1) as u8; |
|
| 189 | + | match tag { |
|
| 190 | + | case super::UN_NEG => return il::UnOp::Neg, |
|
| 191 | + | case super::UN_NOT => return il::UnOp::Not, |
|
| 192 | + | else => throw binary::Error::Invalid, |
|
| 193 | + | } |
|
| 194 | + | } |
|
| 195 | + | ||
| 196 | + | /// Read a checked cmp operation tag. |
|
| 197 | + | unsafe fn cmpOp(input: &mut Reader) -> il::CmpOp throws (binary::Error) { |
|
| 198 | + | let tag = try integer(input, 1) as u8; |
|
| 199 | + | match tag { |
|
| 200 | + | case super::CMP_EQ => return il::CmpOp::Eq, |
|
| 201 | + | case super::CMP_NE => return il::CmpOp::Ne, |
|
| 202 | + | case super::CMP_SLT => return il::CmpOp::Slt, |
|
| 203 | + | case super::CMP_ULT => return il::CmpOp::Ult, |
|
| 204 | + | else => throw binary::Error::Invalid, |
|
| 205 | + | } |
|
| 206 | + | } |
|
| 207 | + | ||
| 208 | + | /// Read one instruction and reconstruct its operand sequences. |
|
| 209 | + | export unsafe fn instr(input: &mut Reader) -> il::Instr throws (binary::Error) { |
|
| 210 | + | let tag = try integer(input, 1) as u8; |
|
| 211 | + | match tag { |
|
| 212 | + | case super::INSTR_RESERVE => { |
|
| 213 | + | let vdst = try reg(input); |
|
| 214 | + | let vsize = try val(input); |
|
| 215 | + | let valignment = try integer(input, 4) as u32; |
|
| 216 | + | return il::Instr::Reserve { dst: vdst, size: vsize, alignment: valignment }; |
|
| 217 | + | }, |
|
| 218 | + | case super::INSTR_LOAD, super::INSTR_SLOAD => { |
|
| 219 | + | let vtyp = try typ(input); |
|
| 220 | + | let vdst = try reg(input); |
|
| 221 | + | let vsrc = try reg(input); |
|
| 222 | + | let voffset = try integer(input, 4) as i32; |
|
| 223 | + | if tag == super::INSTR_SLOAD { |
|
| 224 | + | return il::Instr::Sload { typ: vtyp, dst: vdst, src: vsrc, offset: voffset }; |
|
| 225 | + | } |
|
| 226 | + | return il::Instr::Load { typ: vtyp, dst: vdst, src: vsrc, offset: voffset }; |
|
| 227 | + | }, |
|
| 228 | + | case super::INSTR_STORE => { |
|
| 229 | + | let vtyp = try typ(input); |
|
| 230 | + | let vsrc = try val(input); |
|
| 231 | + | let vdst = try reg(input); |
|
| 232 | + | let voffset = try integer(input, 4) as i32; |
|
| 233 | + | return il::Instr::Store { typ: vtyp, src: vsrc, dst: vdst, offset: voffset }; |
|
| 234 | + | }, |
|
| 235 | + | case super::INSTR_BLIT => { |
|
| 236 | + | let vdst = try reg(input); |
|
| 237 | + | let vsrc = try reg(input); |
|
| 238 | + | let vsize = try val(input); |
|
| 239 | + | return il::Instr::Blit { dst: vdst, src: vsrc, size: vsize }; |
|
| 240 | + | }, |
|
| 241 | + | case super::INSTR_COPY => { |
|
| 242 | + | let vdst = try reg(input); |
|
| 243 | + | let vval = try val(input); |
|
| 244 | + | return il::Instr::Copy { dst: vdst, val: vval }; |
|
| 245 | + | }, |
|
| 246 | + | case super::INSTR_BINOP => { |
|
| 247 | + | let vop = try binOp(input); |
|
| 248 | + | let vtyp = try typ(input); |
|
| 249 | + | let vdst = try reg(input); |
|
| 250 | + | let va = try val(input); |
|
| 251 | + | let vb = try val(input); |
|
| 252 | + | return il::Instr::BinOp { op: vop, typ: vtyp, dst: vdst, a: va, b: vb }; |
|
| 253 | + | }, |
|
| 254 | + | case super::INSTR_UNOP => { |
|
| 255 | + | let vop = try unOp(input); |
|
| 256 | + | let vtyp = try typ(input); |
|
| 257 | + | let vdst = try reg(input); |
|
| 258 | + | let va = try val(input); |
|
| 259 | + | return il::Instr::UnOp { op: vop, typ: vtyp, dst: vdst, a: va }; |
|
| 260 | + | }, |
|
| 261 | + | case super::INSTR_ZEXT, super::INSTR_SEXT => { |
|
| 262 | + | let vtyp = try typ(input); |
|
| 263 | + | let vdst = try reg(input); |
|
| 264 | + | let vval = try val(input); |
|
| 265 | + | if tag == super::INSTR_SEXT { |
|
| 266 | + | return il::Instr::Sext { typ: vtyp, dst: vdst, val: vval }; |
|
| 267 | + | } |
|
| 268 | + | return il::Instr::Zext { typ: vtyp, dst: vdst, val: vval }; |
|
| 269 | + | }, |
|
| 270 | + | case super::INSTR_CALL => { |
|
| 271 | + | let vretTy = try typ(input); |
|
| 272 | + | let mut vdst: ?il::Reg = nil; |
|
| 273 | + | if try flag(input) { |
|
| 274 | + | set vdst = try reg(input); |
|
| 275 | + | } |
|
| 276 | + | let vfunc = try val(input); |
|
| 277 | + | let vargs = try values(input); |
|
| 278 | + | return il::Instr::Call { retTy: vretTy, dst: vdst, func: vfunc, args: vargs }; |
|
| 279 | + | }, |
|
| 280 | + | case super::INSTR_RET => { |
|
| 281 | + | let mut vval: ?il::Val = nil; |
|
| 282 | + | if try flag(input) { |
|
| 283 | + | set vval = try val(input); |
|
| 284 | + | } |
|
| 285 | + | return il::Instr::Ret { val: vval }; |
|
| 286 | + | }, |
|
| 287 | + | case super::INSTR_JMP => { |
|
| 288 | + | let vtarget = try target(input); |
|
| 289 | + | let vargs = try values(input); |
|
| 290 | + | return il::Instr::Jmp { target: vtarget, args: vargs }; |
|
| 291 | + | }, |
|
| 292 | + | case super::INSTR_BR => { |
|
| 293 | + | let vop = try cmpOp(input); |
|
| 294 | + | let vtyp = try typ(input); |
|
| 295 | + | let va = try val(input); |
|
| 296 | + | let vb = try val(input); |
|
| 297 | + | let vthenTarget = try target(input); |
|
| 298 | + | let vthenArgs = try values(input); |
|
| 299 | + | let velseTarget = try target(input); |
|
| 300 | + | let velseArgs = try values(input); |
|
| 301 | + | return il::Instr::Br { op: vop, typ: vtyp, a: va, b: vb, thenTarget: vthenTarget, thenArgs: vthenArgs, elseTarget: velseTarget, elseArgs: velseArgs }; |
|
| 302 | + | }, |
|
| 303 | + | case super::INSTR_SWITCH => { |
|
| 304 | + | let vval = try val(input); |
|
| 305 | + | let vdefaultTarget = try target(input); |
|
| 306 | + | let vdefaultArgs = try values(input); |
|
| 307 | + | let n = try count(input, 16); |
|
| 308 | + | let vcases = try storage(input, @sizeOf(il::SwitchCase), @alignOf(il::SwitchCase), n) |
|
| 309 | + | as *mut [il::SwitchCase]; |
|
| 310 | + | for i in 0..n { |
|
| 311 | + | let value = try integer(input, 8) as i64; |
|
| 312 | + | let block = try target(input); |
|
| 313 | + | let args = try values(input); |
|
| 314 | + | set vcases[i] = il::SwitchCase { value, target: block, args }; |
|
| 315 | + | } |
|
| 316 | + | return il::Instr::Switch { val: vval, defaultTarget: vdefaultTarget, defaultArgs: vdefaultArgs, cases: (&mut vcases[..]) as *unsafe mut [il::SwitchCase] }; |
|
| 317 | + | }, |
|
| 318 | + | case super::INSTR_UNREACHABLE => { |
|
| 319 | + | return il::Instr::Unreachable; |
|
| 320 | + | }, |
|
| 321 | + | case super::INSTR_ECALL => { |
|
| 322 | + | let vdst = try reg(input); |
|
| 323 | + | let vnum = try val(input); |
|
| 324 | + | let va0 = try val(input); |
|
| 325 | + | let va1 = try val(input); |
|
| 326 | + | let va2 = try val(input); |
|
| 327 | + | let va3 = try val(input); |
|
| 328 | + | return il::Instr::Ecall { dst: vdst, num: vnum, a0: va0, a1: va1, a2: va2, a3: va3 }; |
|
| 329 | + | }, |
|
| 330 | + | case super::INSTR_EBREAK => { |
|
| 331 | + | return il::Instr::Ebreak; |
|
| 332 | + | }, |
|
| 333 | + | case super::INSTR_MEMORYFENCE => { |
|
| 334 | + | return il::Instr::MemoryFence; |
|
| 335 | + | }, |
|
| 336 | + | else => throw binary::Error::Invalid, |
|
| 337 | + | } |
|
| 338 | + | } |
|
| 339 | + | ||
| 340 | + | /// Read an initializer with a repetition count. |
|
| 341 | + | export unsafe fn dataValue(input: &mut Reader) -> il::DataValue throws (binary::Error) { |
|
| 342 | + | let tag = try integer(input, 1) as u8; |
|
| 343 | + | let mut item: il::DataItem = il::DataItem::Undef; |
|
| 344 | + | match tag { |
|
| 345 | + | case super::DATA_VAL => { |
|
| 346 | + | let t = try typ(input); |
|
| 347 | + | let n = try integer(input, il::typeSize(t)); |
|
| 348 | + | set item = il::DataItem::Val { typ: t, val: n as i64 }; |
|
| 349 | + | }, |
|
| 350 | + | case super::DATA_SYM => { set item = il::DataItem::Sym(try symbol(input)); }, |
|
| 351 | + | case super::DATA_FN => { set item = il::DataItem::Fn(try symbol(input)); }, |
|
| 352 | + | case super::DATA_STR => { set item = il::DataItem::Str(try bytes(input)); }, |
|
| 353 | + | case super::DATA_UNDEF => { set item = il::DataItem::Undef; }, |
|
| 354 | + | else => throw binary::Error::Invalid, |
|
| 355 | + | } |
|
| 356 | + | let n = try integer(input, 4) as u32; |
|
| 357 | + | return il::DataValue { item, count: n }; |
|
| 358 | + | } |
lib/std/lang/il/binary/tests.rad
added
+271 -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 | + | use std::lang::il::binary::reader; |
|
| 8 | + | use std::lang::alloc; |
|
| 9 | + | ||
| 10 | + | /// Decode arena backing storage. Tests reset it before each use. |
|
| 11 | + | static MEMORY: [u8; 512] = [0; 512]; |
|
| 12 | + | ||
| 13 | + | /// Check little-endian encoding for every integer width. |
|
| 14 | + | @test unsafe fn integers() throws (testing::TestError) { |
|
| 15 | + | let mut buffer: [u8; 15] = [0; 15]; |
|
| 16 | + | let mut out = writer::new(&mut buffer[..], &[]); |
|
| 17 | + | try writer::integer(&mut out, 0x12, 1) catch { throw testing::TestError::Failed; }; |
|
| 18 | + | try writer::integer(&mut out, 0x3456, 2) catch { throw testing::TestError::Failed; }; |
|
| 19 | + | try writer::integer(&mut out, 0x789abcde, 4) catch { throw testing::TestError::Failed; }; |
|
| 20 | + | try writer::integer(&mut out, 0x0123456789abcdef, 8) catch { throw testing::TestError::Failed; }; |
|
| 21 | + | try testing::expectBytesEq(&buffer[..], &[0x12, 0x56, 0x34, 0xde, 0xbc, 0x9a, 0x78, 0xef, 0xcd, |
|
| 22 | + | 0xab, 0x89, 0x67, 0x45, 0x23, 0x01]); |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// Compare one instruction with its fixed wire representation. |
|
| 26 | + | unsafe fn instruction(item: il::Instr, expected: &[u8]) throws (testing::TestError) { |
|
| 27 | + | let mut buffer: [u8; 256] = [0; 256]; |
|
| 28 | + | let mut out = writer::new(&mut buffer[..], &["data", "fn"]); |
|
| 29 | + | try writer::instr(&mut out, item) catch { throw testing::TestError::Failed; }; |
|
| 30 | + | try testing::expectBytesEq(&buffer[..out.offset], expected); |
|
| 31 | + | for capacity in 0..expected.len { |
|
| 32 | + | let mut short = writer::new(&mut buffer[..capacity], &["data", "fn"]); |
|
| 33 | + | let mut failed = false; |
|
| 34 | + | try writer::instr(&mut short, item) catch err { |
|
| 35 | + | try testing::expect(err == binary::Error::Capacity); |
|
| 36 | + | set failed = true; |
|
| 37 | + | }; |
|
| 38 | + | try testing::expect(failed); |
|
| 39 | + | try testing::expect(short.offset <= capacity); |
|
| 40 | + | } |
|
| 41 | + | let memory = &mut MEMORY[..512]; |
|
| 42 | + | let mut arena = alloc::new(&mut memory[..]); |
|
| 43 | + | let mut input = reader::new(expected, &mut arena, &["data", "fn"]); |
|
| 44 | + | set input.registers = 16; |
|
| 45 | + | set input.blocks = 4; |
|
| 46 | + | let decoded = try reader::instr(&mut input) catch { throw testing::TestError::Failed; }; |
|
| 47 | + | try testing::expect(input.offset == expected.len); |
|
| 48 | + | set out.offset = 0; |
|
| 49 | + | try writer::instr(&mut out, decoded) catch { throw testing::TestError::Failed; }; |
|
| 50 | + | try testing::expectBytesEq(&buffer[..out.offset], expected); |
|
| 51 | + | for length in 0..expected.len { |
|
| 52 | + | alloc::reset(&mut arena); |
|
| 53 | + | set input = reader::new(&expected[..length], &mut arena, &["data", "fn"]); |
|
| 54 | + | set input.registers = 16; |
|
| 55 | + | set input.blocks = 4; |
|
| 56 | + | let mut failed = false; |
|
| 57 | + | try reader::instr(&mut input) catch err { |
|
| 58 | + | try testing::expect(err == binary::Error::Truncated); |
|
| 59 | + | set failed = true; |
|
| 60 | + | }; |
|
| 61 | + | try testing::expect(failed); |
|
| 62 | + | } |
|
| 63 | + | ||
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Check all instruction tags and their field order. |
|
| 67 | + | @test unsafe fn instructions() throws (testing::TestError) { |
|
| 68 | + | let mut args: [il::Val; 1] = [il::Val::Undef]; |
|
| 69 | + | let mut cases: [il::SwitchCase; 1] = [il::SwitchCase { |
|
| 70 | + | value: -1, target: 2, args: &mut args[..], |
|
| 71 | + | }]; |
|
| 72 | + | try instruction(il::Instr::Reserve { |
|
| 73 | + | dst: il::Reg { n: 1 }, size: il::Val::Undef, alignment: 16, |
|
| 74 | + | }, &[0, 1, 0, 0, 0, 4, 16, 0, 0, 0]); |
|
| 75 | + | try instruction(il::Instr::Load { |
|
| 76 | + | typ: il::Type::W8, dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, offset: -1, |
|
| 77 | + | }, &[1, 1, 1, 0, 0, 0, 2, 0, 0, 0, 255, 255, 255, 255]); |
|
| 78 | + | try instruction(il::Instr::Sload { |
|
| 79 | + | typ: il::Type::W16, dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, offset: 3, |
|
| 80 | + | }, &[2, 2, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]); |
|
| 81 | + | try instruction(il::Instr::Store { |
|
| 82 | + | typ: il::Type::W32, src: il::Val::Undef, dst: il::Reg { n: 2 }, offset: 3, |
|
| 83 | + | }, &[3, 4, 4, 2, 0, 0, 0, 3, 0, 0, 0]); |
|
| 84 | + | try instruction(il::Instr::Blit { |
|
| 85 | + | dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, size: il::Val::Undef, |
|
| 86 | + | }, &[4, 1, 0, 0, 0, 2, 0, 0, 0, 4]); |
|
| 87 | + | try instruction(il::Instr::Copy { |
|
| 88 | + | dst: il::Reg { n: 1 }, val: il::Val::Undef, |
|
| 89 | + | }, &[5, 1, 0, 0, 0, 4]); |
|
| 90 | + | try instruction(il::Instr::BinOp { |
|
| 91 | + | op: il::BinOp::Add, typ: il::Type::W64, dst: il::Reg { n: 1 }, a: il::Val::Undef, |
|
| 92 | + | b: il::Val::Undef, |
|
| 93 | + | }, &[6, 0, 8, 1, 0, 0, 0, 4, 4]); |
|
| 94 | + | try instruction(il::Instr::UnOp { |
|
| 95 | + | op: il::UnOp::Neg, typ: il::Type::W64, dst: il::Reg { n: 1 }, a: il::Val::Undef, |
|
| 96 | + | }, &[7, 0, 8, 1, 0, 0, 0, 4]); |
|
| 97 | + | try instruction(il::Instr::Zext { |
|
| 98 | + | typ: il::Type::W8, dst: il::Reg { n: 1 }, val: il::Val::Undef, |
|
| 99 | + | }, &[8, 1, 1, 0, 0, 0, 4]); |
|
| 100 | + | try instruction(il::Instr::Sext { |
|
| 101 | + | typ: il::Type::W16, dst: il::Reg { n: 1 }, val: il::Val::Undef, |
|
| 102 | + | }, &[9, 2, 1, 0, 0, 0, 4]); |
|
| 103 | + | try instruction(il::Instr::Call { |
|
| 104 | + | retTy: il::Type::W64, dst: nil, func: il::Val::FnAddr("fn"), args: &[], |
|
| 105 | + | }, &[10, 8, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0]); |
|
| 106 | + | try instruction(il::Instr::Call { |
|
| 107 | + | retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: il::Val::FnAddr("fn"), args: &args[..], |
|
| 108 | + | }, &[10, 8, 1, 2, 0, 0, 0, 3, 1, 0, 0, 0, 1, 0, 0, 0, 4]); |
|
| 109 | + | try instruction(il::Instr::Ret { |
|
| 110 | + | val: nil, |
|
| 111 | + | }, &[11, 0]); |
|
| 112 | + | try instruction(il::Instr::Ret { |
|
| 113 | + | val: il::Val::Undef, |
|
| 114 | + | }, &[11, 1, 4]); |
|
| 115 | + | try instruction(il::Instr::Jmp { |
|
| 116 | + | target: 2, args: &mut args[..], |
|
| 117 | + | }, &[12, 2, 0, 0, 0, 1, 0, 0, 0, 4]); |
|
| 118 | + | try instruction(il::Instr::Br { |
|
| 119 | + | op: il::CmpOp::Eq, typ: il::Type::W32, a: il::Val::Undef, b: il::Val::Undef, thenTarget: 1, |
|
| 120 | + | thenArgs: &mut args[..], elseTarget: 2, elseArgs: &mut args[..], |
|
| 121 | + | }, &[13, 0, 4, 4, 4, 1, 0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 0, 0, 1, 0, 0, 0, 4]); |
|
| 122 | + | try instruction(il::Instr::Switch { |
|
| 123 | + | val: il::Val::Undef, defaultTarget: 1, defaultArgs: &mut args[..], cases: &mut cases[..], |
|
| 124 | + | }, &[ |
|
| 125 | + | 14, 4, 1, 0, 0, 0, 1, 0, 0, 0, 4, 1, |
|
| 126 | + | 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 2, |
|
| 127 | + | 0, 0, 0, 1, 0, 0, 0, 4, |
|
| 128 | + | ]); |
|
| 129 | + | try instruction(il::Instr::Unreachable, &[15]); |
|
| 130 | + | try instruction(il::Instr::Ecall { |
|
| 131 | + | dst: il::Reg { n: 1 }, num: il::Val::Undef, a0: il::Val::Undef, a1: il::Val::Undef, |
|
| 132 | + | a2: il::Val::Undef, a3: il::Val::Undef, |
|
| 133 | + | }, &[16, 1, 0, 0, 0, 4, 4, 4, 4, 4]); |
|
| 134 | + | try instruction(il::Instr::Ebreak, &[17]); |
|
| 135 | + | try instruction(il::Instr::MemoryFence, &[18]); |
|
| 136 | + | } |
|
| 137 | + | ||
| 138 | + | /// Check every value tag and empty and nonempty sequences. |
|
| 139 | + | @test unsafe fn values() throws (testing::TestError) { |
|
| 140 | + | let mut buffer: [u8; 64] = [0; 64]; |
|
| 141 | + | let mut out = writer::new(&mut buffer[..], &["data", "fn"]); |
|
| 142 | + | try writer::values(&mut out, &[]) catch { throw testing::TestError::Failed; }; |
|
| 143 | + | try writer::values(&mut out, &[ |
|
| 144 | + | il::Val::Reg(il::Reg { n: 0x12345678 }), |
|
| 145 | + | il::Val::Imm(-2), il::Val::DataSym("data"), |
|
| 146 | + | il::Val::FnAddr("fn"), il::Val::Undef, |
|
| 147 | + | ]) catch { throw testing::TestError::Failed; }; |
|
| 148 | + | try testing::expectBytesEq(&buffer[..out.offset], &[ |
|
| 149 | + | 0, 0, 0, 0, 5, 0, 0, 0, |
|
| 150 | + | 0, 0x78, 0x56, 0x34, 0x12, |
|
| 151 | + | 1, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
|
| 152 | + | 2, 0, 0, 0, 0, 3, 1, 0, 0, 0, 4, |
|
| 153 | + | ]); |
|
| 154 | + | let memory = &mut MEMORY[..256]; |
|
| 155 | + | let mut arena = alloc::new(&mut memory[..]); |
|
| 156 | + | let mut input = reader::new(&buffer[..out.offset], &mut arena, &["data", "fn"]); |
|
| 157 | + | set input.registers = 0x12345679; |
|
| 158 | + | let empty = try reader::values(&mut input) catch { throw testing::TestError::Failed; }; |
|
| 159 | + | let decoded = try reader::values(&mut input) catch { throw testing::TestError::Failed; }; |
|
| 160 | + | try testing::expect(empty.len == 0); |
|
| 161 | + | try testing::expect(decoded.len == 5); |
|
| 162 | + | try testing::expect(input.offset == out.offset); |
|
| 163 | + | let mut repeated: [u8; 64] = [0; 64]; |
|
| 164 | + | let mut copy = writer::new(&mut repeated[..], &["data", "fn"]); |
|
| 165 | + | try writer::values(&mut copy, empty) catch { throw testing::TestError::Failed; }; |
|
| 166 | + | try writer::values(&mut copy, decoded) catch { throw testing::TestError::Failed; }; |
|
| 167 | + | try testing::expectBytesEq(&buffer[..out.offset], &repeated[..copy.offset]); |
|
| 168 | + | ||
| 169 | + | } |
|
| 170 | + | ||
| 171 | + | /// Check initializer bytes and repetition counts. |
|
| 172 | + | @test unsafe fn initializers() throws (testing::TestError) { |
|
| 173 | + | let mut buffer: [u8; 128] = [0; 128]; |
|
| 174 | + | let mut out = writer::new(&mut buffer[..], &["data", "fn"]); |
|
| 175 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W8, |
|
| 176 | + | val: -1 }, count: 0 }) catch { throw testing::TestError::Failed; }; |
|
| 177 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W16, |
|
| 178 | + | val: -2 }, count: 1 }) catch { throw testing::TestError::Failed; }; |
|
| 179 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W32, |
|
| 180 | + | val: -3 }, count: 2 }) catch { throw testing::TestError::Failed; }; |
|
| 181 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, |
|
| 182 | + | val: -4 }, count: 3 }) catch { throw testing::TestError::Failed; }; |
|
| 183 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Sym("data"), count: 4 }) |
|
| 184 | + | catch { throw testing::TestError::Failed; }; |
|
| 185 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Fn("fn"), count: 5 }) catch |
|
| 186 | + | { throw testing::TestError::Failed; }; |
|
| 187 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Str("ab"), count: 6 }) catch |
|
| 188 | + | { throw testing::TestError::Failed; }; |
|
| 189 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Str(""), count: 7 }) catch { |
|
| 190 | + | throw testing::TestError::Failed; }; |
|
| 191 | + | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Undef, count: 8 }) catch { |
|
| 192 | + | throw testing::TestError::Failed; }; |
|
| 193 | + | try testing::expectBytesEq(&buffer[..out.offset], &[0, 1, 255, 0, 0, 0, 0, 0, 2, 254, 255, 1, 0, |
|
| 194 | + | 0, 0, 0, 4, 253, 255, 255, 255, 2, 0, 0, 0, 0, 8, 252, 255, 255, 255, 255, 255, 255, 255, 3, |
|
| 195 | + | 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, |
|
| 196 | + | 0, 0, 3, 0, 0, 0, 0, 7, 0, 0, 0, 4, 8, 0, 0, 0]); |
|
| 197 | + | } |
|
| 198 | + | ||
| 199 | + | /// Reject invalid widths, insufficient storage, and absent symbols. |
|
| 200 | + | @test unsafe fn errors() throws (testing::TestError) { |
|
| 201 | + | let mut buffer: [u8; 8] = [0; 8]; |
|
| 202 | + | let mut out = writer::new(&mut buffer[..], &[]); |
|
| 203 | + | let mut failures: u32 = 0; |
|
| 204 | + | try writer::integer(&mut out, 1, 3) catch err { |
|
| 205 | + | try testing::expect(err == binary::Error::Invalid); |
|
| 206 | + | set failures += 1; |
|
| 207 | + | }; |
|
| 208 | + | try writer::symbol(&mut out, "absent") catch err { |
|
| 209 | + | try testing::expect(err == binary::Error::Symbol); |
|
| 210 | + | set failures += 1; |
|
| 211 | + | }; |
|
| 212 | + | try testing::expect(out.offset == 0); |
|
| 213 | + | try writer::integer(&mut out, 0, 8) catch { throw testing::TestError::Failed; }; |
|
| 214 | + | try writer::integer(&mut out, 1, 1) catch err { |
|
| 215 | + | try testing::expect(err == binary::Error::Capacity); |
|
| 216 | + | set failures += 1; |
|
| 217 | + | }; |
|
| 218 | + | try testing::expect(failures == 3); |
|
| 219 | + | try testing::expect(out.offset == 8); |
|
| 220 | + | } |
|
| 221 | + | ||
| 222 | + | /// Check operation tags independently of the native union representation. |
|
| 223 | + | @test unsafe fn operations() throws (testing::TestError) { |
|
| 224 | + | let binaryOps = &[ |
|
| 225 | + | il::BinOp::Add, il::BinOp::Sub, il::BinOp::Mul, |
|
| 226 | + | il::BinOp::Sdiv, il::BinOp::Udiv, il::BinOp::Srem, |
|
| 227 | + | il::BinOp::Urem, il::BinOp::Eq, il::BinOp::Ne, |
|
| 228 | + | il::BinOp::Slt, il::BinOp::Sge, il::BinOp::Ult, |
|
| 229 | + | il::BinOp::Uge, il::BinOp::And, il::BinOp::Or, |
|
| 230 | + | il::BinOp::Xor, il::BinOp::Shl, il::BinOp::Sshr, |
|
| 231 | + | il::BinOp::Ushr, |
|
| 232 | + | ]; |
|
| 233 | + | for op, tag in binaryOps { |
|
| 234 | + | try instruction(il::Instr::BinOp { |
|
| 235 | + | op, typ: il::Type::W64, dst: il::Reg { n: 1 }, |
|
| 236 | + | a: il::Val::Undef, b: il::Val::Undef, |
|
| 237 | + | }, &[6, tag as u8, 8, 1, 0, 0, 0, 4, 4]); |
|
| 238 | + | } |
|
| 239 | + | for op, tag in &[il::UnOp::Neg, il::UnOp::Not] { |
|
| 240 | + | try instruction(il::Instr::UnOp { |
|
| 241 | + | op, typ: il::Type::W64, dst: il::Reg { n: 1 }, |
|
| 242 | + | a: il::Val::Undef, |
|
| 243 | + | }, &[7, tag as u8, 8, 1, 0, 0, 0, 4]); |
|
| 244 | + | } |
|
| 245 | + | let mut empty: [il::Val; 0] = []; |
|
| 246 | + | for op, tag in &[il::CmpOp::Eq, il::CmpOp::Ne, il::CmpOp::Slt, il::CmpOp::Ult] { |
|
| 247 | + | try instruction(il::Instr::Br { |
|
| 248 | + | op, typ: il::Type::W8, a: il::Val::Undef, b: il::Val::Undef, |
|
| 249 | + | thenTarget: 1, thenArgs: &mut empty[..], |
|
| 250 | + | elseTarget: 2, elseArgs: &mut empty[..], |
|
| 251 | + | }, &[13, tag as u8, 1, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]); |
|
| 252 | + | } |
|
| 253 | + | } |
|
| 254 | + | ||
| 255 | + | /// Check byte-sequence bounds at every output size. |
|
| 256 | + | @test unsafe fn byteCapacity() throws (testing::TestError) { |
|
| 257 | + | let mut buffer: [u8; 7] = [0; 7]; |
|
| 258 | + | for capacity in 0..7 { |
|
| 259 | + | let mut out = writer::new(&mut buffer[..capacity], &[]); |
|
| 260 | + | let mut failed = false; |
|
| 261 | + | try writer::bytes(&mut out, "abc") catch err { |
|
| 262 | + | try testing::expect(err == binary::Error::Capacity); |
|
| 263 | + | set failed = true; |
|
| 264 | + | }; |
|
| 265 | + | try testing::expect(failed); |
|
| 266 | + | try testing::expect(out.offset <= capacity); |
|
| 267 | + | } |
|
| 268 | + | let mut out = writer::new(&mut buffer[..], &[]); |
|
| 269 | + | try writer::bytes(&mut out, "abc") catch { throw testing::TestError::Failed; }; |
|
| 270 | + | try testing::expectBytesEq(&buffer[..], &[3, 0, 0, 0, 97, 98, 99]); |
|
| 271 | + | } |
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
+4 -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 |
|
| 36 | + | lib/std/lang/il/binary/reader.rad |
|
| 37 | + | lib/std/lang/il/binary/program.rad |
|
| 34 | 38 | lib/std/lang/resolver.rad |
|
| 35 | 39 | lib/std/lang/resolver/printer.rad |
|
| 36 | 40 | lib/std/lang/lower.rad |
|
| 37 | 41 | lib/std/lang/module.rad |
|
| 38 | 42 | lib/std/lang/module/printer.rad |
std.lib.test
+2 -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 |
|
| 14 | + | lib/std/lang/il/binary/decodeTests.rad |