il: Decode binary RIL programs

f720761acce639054093e6e0bef819d64096721ce1501a544b0a24680e65d91d
Reconstruct package tables and IL in caller-owned arena storage.
Validate structural bounds and restore the arena on failure. Test
round trips, truncation, invalid fields, and storage exhaustion.

Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent ccafc74d
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/binary.rad +46 -0
1 1
//! Binary RIL wire definitions. Integers use little-endian byte order.
2 2
//! Sequences have a u32 element count. Symbols use u32 table indices.
3 3
//! Optional fields use a byte: zero for absent, one for present.
4 4
5 5
export mod writer;
6 +
export mod reader;
7 +
export mod program;
6 8
9 +
use std::lang::il;
7 10
@test export mod tests;
11 +
@test export mod decodeTests;
8 12
9 13
/// Binary RIL signature, encoded as the bytes RIL followed by zero.
10 14
export constant MAGIC: u32 = 0x004c4952;
11 15
/// Binary RIL format version.
12 16
export constant VERSION: u32 = 1;
136 140
export constant CMP_NE: u8 = 1;
137 141
/// Wire tag for comparison Slt.
138 142
export constant CMP_SLT: u8 = 2;
139 143
/// Wire tag for comparison Ult.
140 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 +38 -0
2 2
3 3
use std::testing;
4 4
use std::lang::il;
5 5
use std::lang::il::binary::writer;
6 6
use std::lang::il::binary;
7 +
use std::lang::il::binary::reader;
8 +
use std::lang::alloc;
7 9
8 10
/// Decode arena backing storage. Tests reset it before each use.
9 11
static MEMORY: [u8; 512] = [0; 512];
10 12
11 13
/// Check little-endian encoding for every integer width.
34 36
            set failed = true;
35 37
        };
36 38
        try testing::expect(failed);
37 39
        try testing::expect(short.offset <= capacity);
38 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 +
    }
39 63
40 64
}
41 65
42 66
/// Check all instruction tags and their field order.
43 67
@test unsafe fn instructions() throws (testing::TestError) {
125 149
        0, 0, 0, 0, 5, 0, 0, 0,
126 150
        0, 0x78, 0x56, 0x34, 0x12,
127 151
        1, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
128 152
        2, 0, 0, 0, 0, 3, 1, 0, 0, 0, 4,
129 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]);
130 168
131 169
}
132 170
133 171
/// Check initializer bytes and repetition counts.
134 172
@test unsafe fn initializers() throws (testing::TestError) {
std.lib +2 -0
31 31
lib/std/lang/parser.rad
32 32
lib/std/lang/il.rad
33 33
lib/std/lang/il/printer.rad
34 34
lib/std/lang/il/binary.rad
35 35
lib/std/lang/il/binary/writer.rad
36 +
lib/std/lang/il/binary/reader.rad
37 +
lib/std/lang/il/binary/program.rad
36 38
lib/std/lang/resolver.rad
37 39
lib/std/lang/resolver/printer.rad
38 40
lib/std/lang/lower.rad
39 41
lib/std/lang/module.rad
40 42
lib/std/lang/module/printer.rad
std.lib.test +1 -0
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 13
lib/std/lang/il/binary/tests.rad
14 +
lib/std/lang/il/binary/decodeTests.rad