compiler/
kernel/
lib/
examples/
std/
arch/
char/
collections/
lang/
alloc/
ast/
gen/
il/
binary/
images/
binary.rad
13.9 KiB
images.rad
10.6 KiB
printer.rad
14.7 KiB
module/
parser/
resolver/
scanner/
alloc.rad
4.3 KiB
ast.rad
23.3 KiB
gen.rad
513 B
il.rad
16.1 KiB
lower.rad
272.5 KiB
module.rad
13.2 KiB
package.rad
1.3 KiB
parser.rad
77.8 KiB
resolver.rad
308.6 KiB
scanner.rad
17.5 KiB
sexpr.rad
6.3 KiB
strings.rad
2.2 KiB
types.rad
286 B
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.1 KiB
intrinsics.rad
467 B
io.rad
1.3 KiB
lang.rad
276 B
mem.rad
2.2 KiB
sys.rad
179 B
testing.rad
2.4 KiB
tests.rad
15.2 KiB
vec.rad
3.2 KiB
std.rad
281 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
351 B
.gitsigners
112 B
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
3.8 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.4 KiB
std.lib.test
380 B
lib/std/lang/il/binary.rad
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 | } |