il: Add binary RIL codecs

eba4a9436c8094d1a842c1a5eda64578f44fab471ffbdeaf65b1f376d679bf03
Alexis Sellier committed ago 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 (&params[..]) 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