lib/std/lang/il/binary.rad 13.9 KiB raw
1
//! Versioned, pointer-free binary serialization of the shared RIL model.
2
//!
3
//! Wire integers are little-endian. The header is "RIL0", version u32=1,
4
//! reserved flags u32=0, symbol count, length-prefixed symbol byte strings,
5
//! entry symbol index (0xffffffff for none), data count and function count.
6
//! Data records precede functions. Every list and byte string has a u32 count.
7
//! Instruction, value and data tags are explicit and independent of ABI layout.
8
//! Decoding checks structural consistency, not pointer provenance or type safety.
9
10
mod instructions;
11
mod records;
12
mod structure;
13
14
/// Binary input-boundary checks.
15
@test mod tests;
16
17
use std::mem;
18
use std::collections::dict;
19
use std::lang::alloc;
20
use std::lang::strings;
21
use std::lang::il;
22
23
/// A compilation unit and its optional declared entry function.
24
export record Image: Copy {
25
    /// Existing RIL representation consumed by lowering and code generation.
26
    program: il::Program,
27
    /// Entry function name, or nil for a library without an entry point.
28
    entry: ?*[u8],
29
}
30
31
/// A malformed input, unsupported structure, or exhausted caller buffer.
32
export record Error: Copy {
33
    /// Byte offset in the binary input or output where the error was detected.
34
    offset: u32,
35
    /// Static diagnostic message.
36
    message: *[u8],
37
}
38
39
/// One deduplicated wire symbol and its declaration/reference metadata.
40
export record Symbol: Copy {
41
    /// Interned or caller-owned symbol bytes.
42
    name: *[u8],
43
    /// Declaration kind: 0 undeclared, 1 data, 2 function.
44
    kind: u8,
45
    /// Required reference kinds, using the same data/function bit values.
46
    uses: u8,
47
    /// Offset identifying this symbol in the binary stream.
48
    offset: u32,
49
}
50
51
/// Internal bounded writer, shared with the instruction and record codecs.
52
export record Writer: Copy {
53
    /// Caller-provided destination, disjoint from the scratch arena.
54
    output: *mut [u8],
55
    /// Next output byte.
56
    offset: u32,
57
    /// Declaration-indexed wire symbols.
58
    symbols: *[Symbol],
59
    /// Name-to-wire-index dictionary with checked capacity.
60
    symbolMap: dict::Dict,
61
}
62
63
/// Internal bounded reader, shared with the instruction and record codecs.
64
export record Reader: Copy {
65
    /// Immutable binary source; returned strings may borrow it.
66
    input: *[u8],
67
    /// Next input byte.
68
    offset: u32,
69
    /// Caller storage for all returned records and mutable slices.
70
    arena: *mut alloc::Arena,
71
    /// Shared identifier pool, whose lifetime must cover the decoded image.
72
    pool: *mut strings::Pool,
73
    /// Deduplicated symbol table and deferred reference checks.
74
    symbols: *mut [Symbol],
75
}
76
77
/// Create a diagnostic at a known binary offset.
78
export fn error(offset: u32, message: *[u8]) -> Error {
79
    return Error { offset, message };
80
}
81
82
/// Allocate without overflowing the shared arena's u32 size arithmetic.
83
export fn storage(arena: *mut alloc::Arena, size: u32, alignment: u32, count: u32, offset: u32) -> *mut [opaque] throws (Error) {
84
    if count == 0 { return &mut []; }
85
    let bytes = size as u64 * count as u64;
86
    let padding = (alignment - (arena.offset % alignment)) % alignment;
87
    if arena.offset > arena.data.len or bytes + padding as u64 > (arena.data.len - arena.offset) as u64 {
88
        throw error(offset, "RIL arena exhausted");
89
    }
90
    return try alloc::allocSlice(arena, size, alignment, count) catch {
91
        throw error(offset, "RIL arena exhausted");
92
    };
93
}
94
95
/// Build an empty dictionary with enough space for `count` unique entries.
96
export fn dictionary(arena: *mut alloc::Arena, count: u32, offset: u32) -> dict::Dict throws (Error) {
97
    if count > 0x20000000 { throw error(offset, "RIL symbol count is too large"); }
98
    let mut capacity: u32 = 2;
99
    while capacity / 2 < count { set capacity *= 2; }
100
    let entries = try storage(arena, @sizeOf(dict::Entry), @alignOf(dict::Entry), capacity, offset) as *mut [dict::Entry];
101
    return dict::init(entries);
102
}
103
104
/// Write bytes after checking the caller's complete remaining capacity.
105
export fn putBytes(w: *mut Writer, bytes: *[u8]) throws (Error) {
106
    if bytes.len > w.output.len - w.offset { throw error(w.offset, "RIL output buffer exhausted"); }
107
    try mem::copy(&mut w.output[w.offset..w.offset + bytes.len], bytes) catch {
108
        throw error(w.offset, "RIL output buffer exhausted");
109
    };
110
    set w.offset += bytes.len;
111
}
112
113
/// Write a single wire byte.
114
export fn put8(w: *mut Writer, value: u8) throws (Error) {
115
    if w.offset == w.output.len { throw error(w.offset, "RIL output buffer exhausted"); }
116
    set w.output[w.offset] = value;
117
    set w.offset += 1;
118
}
119
120
/// Write an explicitly little-endian u32.
121
export fn put32(w: *mut Writer, value: u32) throws (Error) {
122
    if w.output.len - w.offset < 4 { throw error(w.offset, "RIL output buffer exhausted"); }
123
    for i in 0..4 { set w.output[w.offset + i] = (value >> (i * 8)) as u8; }
124
    set w.offset += 4;
125
}
126
127
/// Write an explicitly little-endian u64, preserving signed literal bits.
128
export fn put64(w: *mut Writer, value: u64) throws (Error) {
129
    if w.output.len - w.offset < 8 { throw error(w.offset, "RIL output buffer exhausted"); }
130
    for i in 0..8 { set w.output[w.offset + i] = (value >> (i as u64 * 8)) as u8; }
131
    set w.offset += 8;
132
}
133
134
/// Write a length-prefixed byte string, including embedded zero bytes.
135
export fn putString(w: *mut Writer, value: *[u8]) throws (Error) {
136
    try put32(w, value.len);
137
    try putBytes(w, value);
138
}
139
140
/// Read a source-backed byte slice after a subtraction-based bounds check.
141
export fn getBytes(r: *mut Reader, count: u32) -> *[u8] throws (Error) {
142
    if count > r.input.len - r.offset { throw error(r.offset, "truncated binary RIL"); }
143
    let bytes = &r.input[r.offset..r.offset + count];
144
    set r.offset += count;
145
    return bytes;
146
}
147
148
/// Read one wire byte.
149
export fn get8(r: *mut Reader) -> u8 throws (Error) {
150
    let bytes = try getBytes(r, 1);
151
    return bytes[0];
152
}
153
154
/// Read an explicitly little-endian u32 without alignment assumptions.
155
export fn get32(r: *mut Reader) -> u32 throws (Error) {
156
    let bytes = try getBytes(r, 4);
157
    let mut value: u32 = 0;
158
    for i in 0..4 { set value |= (bytes[i] as u32) << (i * 8); }
159
    return value;
160
}
161
162
/// Read an explicitly little-endian u64 without host pointer reinterpretation.
163
export fn get64(r: *mut Reader) -> u64 throws (Error) {
164
    let bytes = try getBytes(r, 8);
165
    let mut value: u64 = 0;
166
    for i in 0..8 { set value |= (bytes[i] as u64) << (i as u64 * 8); }
167
    return value;
168
}
169
170
/// Read a canonical wire boolean; other bytes are malformed, not truthy.
171
export fn getBool(r: *mut Reader) -> bool throws (Error) {
172
    let offset = r.offset;
173
    let value = try get8(r);
174
    if value > 1 { throw error(offset, "invalid RIL boolean"); }
175
    return value == 1;
176
}
177
178
/// Read a count bounded by the smallest possible wire representation per item.
179
export fn count(r: *mut Reader, minimumBytes: u32) -> u32 throws (Error) {
180
    let offset = r.offset;
181
    let value = try get32(r);
182
    if value > (r.input.len - r.offset) / minimumBytes {
183
        throw error(offset, "RIL list count exceeds remaining input");
184
    }
185
    return value;
186
}
187
188
/// Read a byte string without copying its payload.
189
export fn getString(r: *mut Reader) -> *[u8] throws (Error) {
190
    let len = try get32(r);
191
    return try getBytes(r, len);
192
}
193
194
/// Intern a nonempty symbol, checking the fixed shared pool before insertion.
195
export fn intern(r: *mut Reader, name: *[u8], offset: u32) -> *[u8] throws (Error) {
196
    if name.len == 0 { throw error(offset, "empty RIL symbol name"); }
197
    if let existing = strings::find(r.pool, name) { return existing; }
198
    if r.pool.count >= r.pool.table.len / 2 { throw error(offset, "RIL string pool exhausted"); }
199
    return strings::intern(r.pool, name);
200
}
201
202
/// Write a symbol reference, rejecting undeclared or wrong-kind names.
203
export fn putSymbol(w: *mut Writer, name: *[u8], kind: u8) throws (Error) {
204
    let index = dict::get(&w.symbolMap, name) else {
205
        throw error(w.offset, "undefined RIL symbol");
206
    };
207
    if w.symbols[index as u32].kind <> kind { throw error(w.offset, "RIL symbol kind mismatch"); }
208
    try put32(w, index as u32);
209
}
210
211
/// Read a reference by table index and defer declaration-kind checks until EOF.
212
export fn getSymbol(r: *mut Reader, kind: u8) -> *[u8] throws (Error) {
213
    let offset = r.offset;
214
    let index = try get32(r);
215
    if index >= r.symbols.len { throw error(offset, "RIL symbol index out of range"); }
216
    set r.symbols[index].uses |= kind;
217
    return r.symbols[index].name;
218
}
219
220
/// Read and register a unique data or function declaration.
221
export fn declaration(r: *mut Reader, kind: u8) -> *[u8] throws (Error) {
222
    let offset = r.offset;
223
    let index = try get32(r);
224
    if index >= r.symbols.len { throw error(offset, "RIL declaration index out of range"); }
225
    let symbol = &mut r.symbols[index];
226
    if symbol.kind <> 0 { throw error(offset, "duplicate RIL declaration"); }
227
    set symbol.kind = kind;
228
    return symbol.name;
229
}
230
231
/// Encode into disjoint caller output, restoring all arena scratch on success or error.
232
/// Returned bytes contain no addresses from the source program or host ABI.
233
export fn encode(image: *Image, arena: *mut alloc::Arena, output: *mut [u8]) -> u32 throws (Error) {
234
    let saved = alloc::save(arena);
235
    let result = try encodeImage(image, arena, output) catch err {
236
        alloc::restore(arena, saved);
237
        throw err;
238
    };
239
    alloc::restore(arena, saved);
240
    return result;
241
}
242
243
/// Build the declaration-only symbol table and serialize the image.
244
fn encodeImage(image: *Image, arena: *mut alloc::Arena, output: *mut [u8]) -> u32 throws (Error) {
245
    let total = image.program.data.len as u64 + image.program.fns.len as u64;
246
    if total > 0x7FFFFFFF { throw error(0, "too many RIL declarations"); }
247
    let symbols = try storage(arena, @sizeOf(Symbol), @alignOf(Symbol), total as u32, 0) as *mut [Symbol];
248
    let mut map = try dictionary(arena, total as u32, 0);
249
    for d, i in image.program.data {
250
        set symbols[i] = { name: d.name, kind: 1, uses: 0, offset: 0 };
251
    }
252
    for f, i in image.program.fns {
253
        set symbols[image.program.data.len + i] = { name: f.name, kind: 2, uses: 0, offset: 0 };
254
    }
255
    for symbol, i in symbols {
256
        if symbol.name.len == 0 { throw error(0, "empty RIL declaration name"); }
257
        if dict::get(&map, symbol.name) <> nil { throw error(0, "duplicate RIL declaration"); }
258
        dict::insert(&mut map, symbol.name, i as i32);
259
    }
260
    let mut w = Writer { output, offset: 0, symbols, symbolMap: map };
261
    try putBytes(&mut w, "RIL0");
262
    try put32(&mut w, 1);
263
    try put32(&mut w, 0);
264
    try put32(&mut w, symbols.len);
265
    for symbol in symbols { try putString(&mut w, symbol.name); }
266
    if let entry = image.entry { try putSymbol(&mut w, entry, 2); }
267
    else { try put32(&mut w, 0xFFFFFFFF); }
268
    try put32(&mut w, image.program.data.len);
269
    try put32(&mut w, image.program.fns.len);
270
    for data in image.program.data { try records::putData(&mut w, &data); }
271
    for func in image.program.fns { try records::putFn(&mut w, func); }
272
    return w.offset;
273
}
274
275
/// Decode bounded binary RIL into the existing shared IL records.
276
/// Input, arena, and pool must outlive the image. Failed decoding may consume
277
/// arena space and intern input strings; callers reclaim them as one load unit.
278
/// This is structural decoding of trusted input, not a safety verifier.
279
export fn decode(input: *[u8], arena: *mut alloc::Arena, pool: *mut strings::Pool) -> Image throws (Error) {
280
    let mut r = Reader { input, offset: 0, arena, pool, symbols: &mut [] };
281
    if not mem::eq(try getBytes(&mut r, 4), "RIL0") { throw error(0, "invalid binary RIL magic"); }
282
    if try get32(&mut r) <> 1 { throw error(4, "unsupported binary RIL version"); }
283
    if try get32(&mut r) <> 0 { throw error(8, "unsupported binary RIL flags"); }
284
    let symbolCount = try count(&mut r, 4);
285
    set r.symbols = try storage(arena, @sizeOf(Symbol), @alignOf(Symbol), symbolCount, r.offset) as *mut [Symbol];
286
    let mut map = try dictionary(arena, symbolCount, r.offset);
287
    for i in 0..symbolCount {
288
        let offset = r.offset;
289
        let bytes = try getString(&mut r);
290
        let name = try intern(&mut r, bytes, offset);
291
        if dict::get(&map, name) <> nil { throw error(offset, "duplicate RIL symbol table entry"); }
292
        dict::insert(&mut map, name, i as i32);
293
        set r.symbols[i] = { name, kind: 0, uses: 0, offset };
294
    }
295
    let entryOffset = r.offset;
296
    let entryIndex = try get32(&mut r);
297
    let mut entry: ?*[u8] = nil;
298
    if entryIndex <> 0xFFFFFFFF {
299
        if entryIndex >= symbolCount { throw error(entryOffset, "RIL entry symbol index out of range"); }
300
        set entry = r.symbols[entryIndex].name;
301
        set r.symbols[entryIndex].uses |= 2;
302
    }
303
    let dataCount = try count(&mut r, 17);
304
    let fnCount = try count(&mut r, 14);
305
    if dataCount as u64 + fnCount as u64 <> symbolCount as u64 {
306
        throw error(r.offset, "RIL declaration count differs from symbol count");
307
    }
308
    let data = try storage(arena, @sizeOf(il::Data), @alignOf(il::Data), dataCount, r.offset) as *mut [il::Data];
309
    let fns = try storage(arena, @sizeOf(*il::Fn), @alignOf(*il::Fn), fnCount, r.offset) as *mut [*il::Fn];
310
    for i in 0..dataCount { set data[i] = try records::getData(&mut r); }
311
    for i in 0..fnCount {
312
        let func = try storage(arena, @sizeOf(il::Fn), @alignOf(il::Fn), 1, r.offset) as *mut [il::Fn];
313
        set func[0] = try records::getFn(&mut r);
314
        set fns[i] = &func[0];
315
    }
316
    if r.offset <> input.len { throw error(r.offset, "trailing bytes after binary RIL"); }
317
    for symbol in r.symbols {
318
        if symbol.kind == 0 { throw error(symbol.offset, "undeclared RIL symbol"); }
319
        if (symbol.uses & symbol.kind) <> symbol.uses { throw error(symbol.offset, "RIL symbol kind mismatch"); }
320
    }
321
    if entryIndex <> 0xFFFFFFFF {
322
        for func in fns {
323
            if mem::eq(func.name, r.symbols[entryIndex].name) and func.isExtern {
324
                throw error(entryOffset, "RIL entry function has no body");
325
            }
326
        }
327
    }
328
    return Image { program: il::Program { data, fns }, entry };
329
}