ril: decode binary graphs through the existing RV64 backend

55538f163510c0d080d50cefb9207661147ab0b6202df10f263a0562ba8d777c
Verified: make -C kernel check; make std-test bin-test; all pass with standalone binary RIL support.
Alexis Sellier committed ago 1 parent fdf8f5d1
Makefile +3 -2
36 36
endif
37 37
38 38
# Compiler build
39 39
40 40
SEED      := seed/radiance.rv64
41 -
SEED_OPTS := $(STD) -pkg radiance -mod compiler/radiance.rad -entry radiance
41 +
COMPILER_SOURCES := compiler/radiance.rad compiler/radiance/binary.rad
42 +
SEED_OPTS := $(STD) -pkg radiance $(patsubst %,-mod %,$(COMPILER_SOURCES)) -entry radiance
42 43
43 -
$(RAD_BIN): $(STD_LIB) compiler/radiance.rad | $(BIN_DIR)
44 +
$(RAD_BIN): $(STD_LIB) $(COMPILER_SOURCES) | $(BIN_DIR)
44 45
	@echo "radiance $(SEED) => $@"
45 46
	@$(EMU) $(EMU_FLAGS) -run $(SEED) $(SEED_OPTS) -o $@
46 47
47 48
$(BIN_DIR):
48 49
	@mkdir -p $@
compiler/radiance.rad +65 -16
1 1
//! Radiance compiler front-end.
2 +
3 +
/// Binary RIL file output and native loading.
4 +
mod binary;
2 5
use std::mem;
3 6
use std::fmt;
4 7
use std::io;
5 8
use std::lang::alloc;
6 9
use std::lang::ast;
21 24
use std::sys;
22 25
use std::sys::unix;
23 26
use std::collections::dict;
24 27
25 28
/// Maximum number of modules we can load per package.
26 -
constant MAX_LOADED_MODULES: u32 = 64;
29 +
constant MAX_LOADED_MODULES: u32 = module::MAX_MODULES;
27 30
/// Maximum number of packages we can compile.
28 31
constant MAX_PACKAGES: u32 = 4;
29 32
/// Total module entries across all packages.
30 33
constant MAX_TOTAL_MODULES: u32 = 192;
31 34
/// Source code buffer arena (2 MB).
94 97
/// Symbol name exported for startup code to call the semantic entry function.
95 98
constant DEFAULT_ENTRY_SYMBOL: *[u8] = "::default";
96 99
97 100
/// Usage string.
98 101
constant USAGE: *[u8] =
99 -
    "usage: radiance -pkg <name> [-start <input.ras>] -mod <input>.. [-pkg <name> -mod <input>..] -entry <pkg> -o <output>\n";
102 +
    "usage: radiance -pkg <name> [-start <input.ras>] -mod <input>.. [-pkg <name> -mod <input>..] -entry <pkg> [-emit ril] -o <output>\n       radiance -load <input.ril> [-start <input.ras>] [-mod <input.ras>] -o <output>\n";
100 103
101 104
/// Compiler error.
102 105
union Error: Copy {
103 106
    Other,
104 107
}
159 162
    dump: Dump,
160 163
    /// Output path for binary.
161 164
    outputPath: ?*[u8],
162 165
    /// Whether to emit debug info (.debug file).
163 166
    debug: bool,
167 +
    /// Write binary RIL instead of native instructions.
168 +
    emitIl: bool,
164 169
}
165 170
166 171
/// Root module info for a package.
167 172
record RootModule: Copy {
168 173
    entry: *module::ModuleEntry,
175 180
    generator: *mut rv64::Generator,
176 181
    /// Arena holding the current function's lowered IL.
177 182
    fnArena: *mut alloc::Arena,
178 183
}
179 184
185 +
/// Accumulated RIL functions and the semantic entry selected during lowering.
186 +
record IlSinkContext {
187 +
    /// Stable function bodies owned by the lowering arena.
188 +
    functions: *mut [*il::Fn],
189 +
    /// Function marked with `@default` in the entry package.
190 +
    entry: ?*[u8],
191 +
    /// Storage for the function list.
192 +
    arena: *mut alloc::Arena,
193 +
}
194 +
180 195
/// Entry handling for streamed code generation.
181 196
union CodegenEntryMode: Copy {
182 197
    /// Do not reserve an entry jump.
183 198
    None,
184 199
    /// Reserve and patch an entry jump to the `@default` function.
300 315
    let mut buildTest = false;
301 316
    let mut debugEnabled = false;
302 317
    let mut outputPath: ?*[u8] = nil;
303 318
    let mut dump = Dump::None;
304 319
    let mut entryPkgName: ?*[u8] = nil;
320 +
    let mut emitIl = false;
305 321
306 322
    // Per-package source path tracking.
307 323
    let mut inputs: [PackageInput; MAX_PACKAGES] = undefined;
308 324
    let mut pkgCount: u32 = 0;
309 325
    let mut currentPkgIdx: ?u32 = nil;
364 380
        } else if mem::eq(arg, "-debug") {
365 381
            set debugEnabled = true;
366 382
        } else if mem::eq(arg, "-o") {
367 383
            try nextArg(args, &mut idx, &["`-o` requires an output path"]);
368 384
            set outputPath = args[idx];
385 +
        } else if mem::eq(arg, "-emit") {
386 +
            let mode = try nextArg(args, &mut idx, &["`-emit` requires `ril`"]);
387 +
            if not mem::eq(mode, "ril") {
388 +
                throw error(&["unknown output format", mode, "(expected: ril)"]);
389 +
            }
390 +
            set emitIl = true;
369 391
        } else if mem::eq(arg, "-dump") {
370 392
            try nextArg(args, &mut idx, &["`-dump` requires a mode (eg. ast)"]);
371 393
            let mode = args[idx];
372 394
            if mem::eq(mode, "ast") {
373 395
                set dump = Dump::Ast;
383 405
        } else {
384 406
            throw error(&["unknown argument", arg]);
385 407
        }
386 408
        set idx += 1;
387 409
    }
410 +
    if emitIl and (outputPath == nil or dump <> Dump::None) {
411 +
        throw error(&["`-emit ril` requires `-o` and cannot be combined with `-dump`"]);
412 +
    }
388 413
    if pkgCount == 0 {
389 414
        throw error(&["no package specified"]);
390 415
    }
391 416
    for i in 0..pkgCount {
392 417
        if inputs[i].radPathCount == 0 {
393 418
            throw error(&["package", inputs[i].name, "has no Radiance modules specified"]);
394 419
        }
420 +
        if emitIl and (inputs[i].asmPathCount <> 0 or inputs[i].startupPath <> nil) {
421 +
            throw error(&["binary RIL cannot contain assembly; link assembly when loading the RIL"]);
422 +
        }
395 423
    }
396 424
397 425
    // Determine entry package index.
398 426
    let mut entryPkgIdx: ?u32 = nil;
399 427
    if pkgCount == 1 {
431 459
        graph,
432 460
        config: resolver::Config { buildTest },
433 461
        dump,
434 462
        outputPath,
435 463
        debug: debugEnabled,
464 +
        emitIl,
436 465
    };
437 466
    // Initialize and parse all packages.
438 467
    let mut sourceArena = alloc::new(&mut MODULE_SOURCES[..]);
439 468
    for i in 0..pkgCount {
440 469
        package::init(&mut ctx.packages[i], i as u16, ctx.inputs[i].name, &mut STRING_POOL);
490 519
    let mut arena = alloc::new(&mut MAIN_ARENA[..]);
491 520
492 521
    ast::printer::printTree(root.ast, &mut arena);
493 522
}
494 523
495 -
/// Lower all packages into a single IL program.
496 -
/// Dependencies are lowered first, then the entry package.
524 +
/// Retain a lowered function and its entry role in an arena-owned RIL image.
525 +
fn collectLoweredFn(ctxPtr: *mut opaque, func: *il::Fn, role: lower::FnRole) {
526 +
    let ctx = ctxPtr as *mut IlSinkContext;
527 +
    ctx.functions.append(func, alloc::arenaAllocator(ctx.arena));
528 +
    if role == lower::FnRole::Default {
529 +
        set ctx.entry = func.name;
530 +
    }
531 +
}
532 +
533 +
/// Lower dependencies and the entry package into one binary RIL compilation unit.
497 534
fn lowerAllPackages(
498 535
    ctx: *mut CompileContext,
499 536
    res: *mut resolver::Resolver
500 -
) -> il::Program throws (Error) {
501 -
    let entryIdx = ctx.entryPkgIdx else {
502 -
        panic "lowerAllPackages: no entry package";
503 -
    };
504 -
    let entryPkg = &ctx.packages[entryIdx];
505 -
506 -
    // Create the lowerer accumulator using entry package's name.
537 +
) -> il::binary::Image throws (Error) {
538 +
    let entryPkg = try getEntryPackage(ctx);
507 539
    let options = lower::LowerOptions { debug: ctx.debug, buildTest: ctx.config.buildTest };
508 540
    let mut low = lower::lowerer(
509 541
        res, &ctx.graph, entryPkg.name, &mut res.arena, &mut res.arena, options
510 542
    );
543 +
    let mut sink = IlSinkContext { functions: &mut [], entry: nil, arena: &mut res.arena };
544 +
    set low.output = lower::FnOutput::Stream(lower::FnSink {
545 +
        ctx: &mut sink as *mut opaque, emitFn: collectLoweredFn,
546 +
    });
511 547
    try lowerAllPackagesInto(ctx, res, &mut low);
512 -
513 -
    // Finalize and return the unified program.
514 -
    return lower::finalize(&low);
548 +
    return il::binary::Image {
549 +
        program: il::Program { data: low.data, fns: sink.functions },
550 +
        entry: sink.entry,
551 +
    };
515 552
}
516 553
517 554
/// Lower all packages into an existing lowerer.
518 555
fn lowerAllPackagesInto(
519 556
    ctx: *mut CompileContext,
1038 1075
    fnArena: *mut alloc::Arena
1039 1076
) throws (Error) {
1040 1077
    let entryPkg = try getEntryPackage(ctx);
1041 1078
    let mut out = sexpr::Output::Stdout;
1042 1079
1080 +
    if ctx.emitIl {
1081 +
        let path = ctx.outputPath else { throw error(&["binary RIL output requires a path"]); };
1082 +
        let image = try lowerAllPackages(ctx, res);
1083 +
        try binary::write(&image, path, &mut res.arena) catch {
1084 +
            throw error(&["failed to write binary RIL", path]);
1085 +
        };
1086 +
        return;
1087 +
    }
1088 +
1043 1089
    if ctx.dump == Dump::Il {
1044 1090
        // Lower all packages into a single unified IL program for dumping.
1045 -
        let program = try lowerAllPackages(ctx, res);
1046 -
        il::printer::printProgram(&mut out, &program);
1091 +
        let image = try lowerAllPackages(ctx, res);
1092 +
        il::printer::printProgram(&mut out, &image.program);
1047 1093
        io::print("\n");
1048 1094
        return;
1049 1095
    }
1050 1096
    if ctx.dump == Dump::Asm {
1051 1097
        let result = try lowerAndGenerateAllPackages(ctx, res, fnArena, CodegenOptions {
1087 1133
    }
1088 1134
    pkgLog(entryPkg, &["ok", "(", outPath, ")"]);
1089 1135
}
1090 1136
1091 1137
@default fn main(env: *sys::Env) -> i32 {
1138 +
    if env.args.len > 0 and mem::eq(env.args[0], "-load") {
1139 +
        return binary::run(env.args, &mut STRING_POOL);
1140 +
    }
1092 1141
    let mut arena = ast::nodeArena(&mut TEMP_ARENA[..]);
1093 1142
    let mut ctx = try processCommand(env.args, &mut arena) catch {
1094 1143
        return 1;
1095 1144
    };
1096 1145
    match ctx.dump {
compiler/radiance/binary.rad added +214 -0
1 +
//! Binary RIL file output and loading through the shared RV64 backend.
2 +
3 +
use std::mem;
4 +
use std::fmt;
5 +
use std::io;
6 +
use std::lang::alloc;
7 +
use std::lang::strings;
8 +
use std::lang::il;
9 +
use std::lang::gen::data;
10 +
use std::collections::dict;
11 +
use std::arch::rv64;
12 +
use std::arch::rv64::asm;
13 +
use std::arch::rv64::emit;
14 +
use std::sys::unix;
15 +
16 +
/// Maximum serialized RIL file size.
17 +
constant MAX_IMAGE: u32 = 16 * 1024 * 1024;
18 +
/// Maximum data bytes in each native section.
19 +
constant MAX_DATA: u32 = 4 * 1024 * 1024;
20 +
/// Maximum assembly inputs linked with an image.
21 +
constant MAX_ASM: u32 = 64;
22 +
/// Input storage includes one byte to detect an oversized file.
23 +
static IMAGE: [u8; MAX_IMAGE + 1] = undefined;
24 +
/// Stable decoded IL and native generator storage.
25 +
static ARENA: [u8; 48 * 1024 * 1024] = undefined;
26 +
/// Scratch storage reclaimed after each generated function.
27 +
static FN_ARENA: [u8; 16 * 1024 * 1024] = undefined;
28 +
/// Native read-only data.
29 +
static RO_DATA: [u8; MAX_DATA] = undefined;
30 +
/// Native writable initializers.
31 +
static RW_DATA: [u8; MAX_DATA] = undefined;
32 +
/// Assembly source input.
33 +
static ASM_SOURCE: [u8; 2 * 1024 * 1024] = undefined;
34 +
/// Encoded assembly instructions.
35 +
static ASM_TEXT: [u32; 262144] = undefined;
36 +
/// Temporary assembly data.
37 +
static ASM_DATA: [u8; MAX_DATA] = undefined;
38 +
/// Accumulated assembly read-only prefix.
39 +
static ASM_PREFIX: [u8; MAX_DATA] = undefined;
40 +
/// Native data symbol storage.
41 +
static DATA_SYMBOLS: [data::DataSym; data::MAX_DATA_SYMS] = undefined;
42 +
/// Native data symbol index.
43 +
static DATA_INDEX: [dict::Entry; data::DATA_SYM_TABLE_SIZE] = undefined;
44 +
45 +
/// Native loading options. Assembly is supplied separately from binary RIL.
46 +
record Options {
47 +
    /// Binary RIL input path.
48 +
    input: *[u8],
49 +
    /// Native output path.
50 +
    output: *[u8],
51 +
    /// Optional machine startup assembly.
52 +
    startup: ?*[u8],
53 +
    /// Trusted assembly modules linked after RIL functions.
54 +
    modules: [*[u8]; MAX_ASM],
55 +
    /// Number of module paths.
56 +
    moduleCount: u32,
57 +
}
58 +
59 +
/// Construct a file or command error without a binary byte offset.
60 +
fn error(message: *[u8]) -> il::binary::Error {
61 +
    return il::binary::Error { offset: 0, message };
62 +
}
63 +
64 +
/// Write a binary RIL image using the caller's disjoint scratch arena.
65 +
export fn write(image: *il::binary::Image, path: *[u8], arena: *mut alloc::Arena) throws (il::binary::Error) {
66 +
    let size = try il::binary::encode(image, arena, &mut IMAGE[..MAX_IMAGE]);
67 +
    if not unix::writeFile(path, &IMAGE[..size]) { throw error("cannot write binary RIL"); }
68 +
}
69 +
70 +
/// Parse the standalone binary loading command.
71 +
fn options(args: *[*[u8]]) -> Options throws (il::binary::Error) {
72 +
    if args.len < 2 { throw error("-load requires a binary RIL path"); }
73 +
    let mut result = Options { input: args[1], output: "", startup: nil, modules: undefined, moduleCount: 0 };
74 +
    let mut i: u32 = 2;
75 +
    while i < args.len {
76 +
        if i + 1 >= args.len { throw error("missing loading option value"); }
77 +
        let option = args[i];
78 +
        let value = args[i + 1];
79 +
        if mem::eq(option, "-o") {
80 +
            if result.output.len <> 0 { throw error("duplicate output path"); }
81 +
            set result.output = value;
82 +
        } else if mem::eq(option, "-start") {
83 +
            if result.startup <> nil { throw error("duplicate startup path"); }
84 +
            set result.startup = value;
85 +
        } else if mem::eq(option, "-mod") {
86 +
            if result.moduleCount == MAX_ASM { throw error("too many assembly modules"); }
87 +
            set result.modules[result.moduleCount] = value;
88 +
            set result.moduleCount += 1;
89 +
        } else { throw error("unknown binary loading option"); }
90 +
        set i += 2;
91 +
    }
92 +
    if result.output.len == 0 { throw error("-load requires -o"); }
93 +
    return result;
94 +
}
95 +
96 +
/// Check section capacity before the backend computes native addresses.
97 +
fn layout(program: *il::Program, prefix: u32) throws (il::binary::Error) {
98 +
    if program.data.len > data::MAX_DATA_SYMS { throw error("too many data symbols"); }
99 +
    for section in 0..2 {
100 +
        let readOnly = section == 0;
101 +
        let mut offset = prefix as u64 if readOnly else 0 as u64;
102 +
        for pass in 0..2 {
103 +
            for item in program.data {
104 +
                if item.readOnly <> readOnly or item.isZeroInit <> (pass == 1) { continue; }
105 +
                let alignment = item.alignment as u64;
106 +
                if alignment == 0 or alignment & (alignment - 1) <> 0 {
107 +
                    throw error("invalid data alignment");
108 +
                }
109 +
                set offset = ((offset + alignment - 1) & ~(alignment - 1)) + item.size as u64;
110 +
                if offset > MAX_DATA as u64 { throw error("native data section exceeds capacity"); }
111 +
            }
112 +
        }
113 +
    }
114 +
}
115 +
116 +
/// Append trusted assembly and retain its read-only data in the final layout.
117 +
fn assembly(generator: *mut rv64::Generator, path: *[u8], prefix: *mut u32,
118 +
    arena: *mut alloc::Arena, pool: *mut strings::Pool) throws (il::binary::Error) {
119 +
    let source = unix::readFile(path, &mut ASM_SOURCE[..]) else { throw error("cannot read assembly input"); };
120 +
    if source.len == ASM_SOURCE.len { throw error("assembly input exceeds capacity"); }
121 +
    let program = try asm::assemble(asm::scanner::SourceKind::File { path }, source,
122 +
        &mut ASM_TEXT[..], &mut ASM_DATA[..], arena, pool, rv64::RO_DATA_BASE + *prefix)
123 +
        catch { throw error("assembly failed"); };
124 +
    if program.data.len > ASM_PREFIX.len - *prefix { throw error("assembly data exceeds capacity"); }
125 +
    for symbol in program.symbols {
126 +
        if symbol.isExported and symbol.section == asm::Section::Text
127 +
            and dict::get(&generator.e.labels.funcs, symbol.name) <> nil {
128 +
            throw error("duplicate assembly function definition");
129 +
        }
130 +
    }
131 +
    try! mem::copy(&mut ASM_PREFIX[*prefix..], program.data);
132 +
    set *prefix += program.data.len;
133 +
    rv64::addAssembly(generator, program);
134 +
}
135 +
136 +
/// Reject unresolved function references before native relocation patching.
137 +
fn link(generator: *rv64::Generator, program: *il::Program) throws (il::binary::Error) {
138 +
    let names = &generator.e.labels.funcs;
139 +
    for call in generator.e.pendingCalls {
140 +
        if dict::get(names, call.target) == nil { throw error("unresolved function call"); }
141 +
    }
142 +
    for jump in generator.e.pendingJumps {
143 +
        if dict::get(names, jump.target) == nil { throw error("unresolved assembly jump"); }
144 +
    }
145 +
    for address in generator.e.pendingAddrLoads {
146 +
        if not address.isData and dict::get(names, address.target) == nil {
147 +
            throw error("unresolved function address");
148 +
        }
149 +
    }
150 +
    for item in program.data {
151 +
        for value in item.values {
152 +
            if value.count == 0 { continue; }
153 +
            if let case il::DataItem::Fn(name) = value.item {
154 +
                if dict::get(names, name) == nil { throw error("unresolved function initializer"); }
155 +
            }
156 +
        }
157 +
    }
158 +
}
159 +
160 +
/// Decode one trusted image and generate native code with optional assembly.
161 +
fn load(options: *Options, pool: *mut strings::Pool) throws (il::binary::Error) {
162 +
    let source = unix::readFile(options.input, &mut IMAGE[..]) else { throw error("cannot read binary RIL"); };
163 +
    if source.len > MAX_IMAGE { throw error("binary RIL exceeds capacity"); }
164 +
    let mut arena = alloc::new(&mut ARENA[..]);
165 +
    let mut fnArena = alloc::new(&mut FN_ARENA[..]);
166 +
    let image = try il::binary::decode(source, &mut arena, pool);
167 +
    let entry = image.entry else { throw error("binary RIL has no entry function"); };
168 +
    let entryPatch = rv64::EntryPatch::Reserved(entry)
169 +
        if options.startup == nil else rv64::EntryPatch::None;
170 +
    let mut generator = rv64::beginProgram(rv64::ProgramOptions { entryPatch, debug: false }, &mut arena);
171 +
    let mut prefix: u32 = 0;
172 +
    if let path = options.startup { try assembly(&mut generator, path, &mut prefix, &mut arena, pool); }
173 +
    for function in image.program.fns {
174 +
        if function.isExtern { continue; }
175 +
        if dict::get(&generator.e.labels.funcs, function.name) <> nil { throw error("duplicate function definition"); }
176 +
        rv64::generateFunction(&mut generator, function, &mut fnArena);
177 +
    }
178 +
    for i in 0..options.moduleCount {
179 +
        try assembly(&mut generator, options.modules[i], &mut prefix, &mut arena, pool);
180 +
    }
181 +
    let entryOffset = dict::get(&generator.e.labels.funcs, entry) else { throw error("unresolved entry function"); };
182 +
    if let previous = dict::get(&generator.e.labels.funcs, "::default") {
183 +
        if previous <> entryOffset { throw error("conflicting startup entry symbol"); }
184 +
    }
185 +
    emit::recordFuncOffsetAt(&mut generator.e, "::default", entryOffset as u32 / rv64::INSTR_SIZE as u32);
186 +
    try link(&generator, &image.program);
187 +
    try layout(&image.program, prefix);
188 +
    let result = rv64::finishProgram(&mut generator, image.program.data,
189 +
        rv64::Storage { dataSyms: &mut DATA_SYMBOLS[..], dataSymEntries: &mut DATA_INDEX[..] },
190 +
        &ASM_PREFIX[..prefix], &mut RO_DATA[..], &mut RW_DATA[..]);
191 +
    let code = @sliceOf(result.code.ptr as *u8, result.code.len * rv64::INSTR_SIZE as u32);
192 +
    let header = rv64::imageHeader(code.len, result.roDataSize, result.rwDataSize);
193 +
    let headerWords = &header[..];
194 +
    if not unix::writeFileParts(options.output, &[@sliceOf(headerWords.ptr as *u8, headerWords.len * rv64::WORD_SIZE as u32), code,
195 +
        &RO_DATA[..result.roDataSize], &RW_DATA[..result.rwDataSize]]) {
196 +
        throw error("cannot write native image");
197 +
    }
198 +
}
199 +
200 +
/// Run the binary loading command and report a bounded source offset on failure.
201 +
export fn run(args: *[*[u8]], pool: *mut strings::Pool) -> i32 {
202 +
    let options = try options(args) catch e {
203 +
        io::printError("radiance: "); io::printError(e.message); io::printError("\n");
204 +
        return 1;
205 +
    };
206 +
    try load(&options, pool) catch e {
207 +
        let mut digits: [u8; 10] = undefined;
208 +
        io::printError("radiance: binary RIL at byte ");
209 +
        io::printError(fmt::formatU32(e.offset, &mut digits[..]));
210 +
        io::printError(": "); io::printError(e.message); io::printError("\n");
211 +
        return 1;
212 +
    };
213 +
    return 0;
214 +
}
kernel/NOTES.md +17 -2
1 1
# Kernel implementation decisions
2 2
3 3
The specification at https://radiant.computer/system/kernel takes precedence
4 4
for fixed call numbers, handle layout, rights, and object behavior. These notes
5 -
record the contracts established through step 15 of the 22-step plan.
5 +
record the contracts established through step 16 of the 22-step plan.
6 6
7 7
## Source and trust boundary
8 8
9 9
- Kernel mechanisms use freestanding Radiance; RAS owns machine entry, register
10 10
  state, atomics, and MMIO. Hosted checks exercise the same mechanism modules.
197 197
  not source byte length. Whitespace, comments, and literal bytes consume no
198 198
  symbol slots.
199 199
- Bare fence orders memory and device I/O (iorw,iorw). fence.i synchronizes
200 200
  local instruction fetch. These are distinct ordering obligations.
201 201
202 +
## Binary RIL encoding and standalone compilation
203 +
204 +
- std::lang::il::binary preserves complete functions, instructions, block edges,
205 +
  types, and data records. Decoding reconstructs derived graph indices and
206 +
  validates declarations, references, control-flow shape, and structural bounds.
207 +
- RIL0 uses little-endian version 1, explicit wire tags, and a deduplicated symbol
208 +
  table. Files contain no compiler pointers or host-layout records. Every
209 +
  incomplete input prefix must fail decoding; nonbinary input fails at magic.
210 +
- The compiler writes inputs with -emit ril -o FILE and compiles them with
211 +
  -load FILE -o OUT. Trusted RAS startup/support can be linked during loading.
212 +
  Native compilation uses std::arch::rv64, its allocator, and its relocations.
213 +
- Structural validation is not pointer-provenance or type-safety verification.
214 +
  The trusted-input boundary remains unchanged. Round trips exercise switch
215 +
  arguments, loops with aggregate returns, and writable function pointers.
216 +
202 217
## Validation
203 218
204 219
Use the current machine-capable sibling emulator. Set `RAD_EMULATOR`, pass
205 220
`EMU` to the kernel Make invocation, or put `emulator` on PATH. The kernel build
206 221
checks compiler dependencies. From the repository root, run:
208 223
```sh
209 224
make -C kernel check
210 225
make std-test bin-test
211 226
```
212 227
213 -
Exercise name-token-bounded assembler allocation and both fence encodings along with the existing kernel machine paths.
228 +
Exercise malformed binary inputs, every incomplete prefix, and source-to-binary-to-native graphs with switch block arguments, aggregate-return loops, and writable function pointers. Kernel behavior remains the retained mechanism baseline.
214 229
215 230
Run the context reservation probe with an emulator that retains LR/SC
216 231
reservations across traps. This checks the kernel's reservation invalidation.
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 +329 -0
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 +
}
lib/std/lang/il/binary/instructions.rad added +325 -0
1 +
//! Explicit binary RIL instruction tags and operand payloads.
2 +
//!
3 +
//! Type tags are byte widths 1/2/4/8. Value tags are Reg=0, Imm=1,
4 +
//! DataSym=2, FnAddr=3, Undef=4. Instruction tags are Reserve=0, Load=1,
5 +
//! Sload=2, Store=3, Blit=4, Copy=5, BinOp=6, UnOp=7, Zext=8, Sext=9,
6 +
//! Call=10, Ret=11, Jmp=12, Br=13, Switch=14, Unreachable=15, Ecall=16,
7 +
//! Ebreak=17, MemoryFence=18. Payload fields follow the shared IL declaration
8 +
//! order, except BinOp/UnOp put their operation byte first. Optional operands
9 +
//! use a canonical 0/1 byte followed by the payload only when present.
10 +
11 +
use std::lang::il;
12 +
13 +
/// Write one logical word width.
14 +
export fn putType(w: *mut super::Writer, typ: il::Type) throws (super::Error) {
15 +
    try super::put8(w, il::typeSize(typ) as u8);
16 +
}
17 +
18 +
/// Read one logical word width, rejecting unknown tags.
19 +
export fn getType(r: *mut super::Reader) -> il::Type throws (super::Error) {
20 +
    let offset = r.offset;
21 +
    match try super::get8(r) {
22 +
        case 1 => return il::Type::W8,
23 +
        case 2 => return il::Type::W16,
24 +
        case 4 => return il::Type::W32,
25 +
        case 8 => return il::Type::W64,
26 +
        else => throw super::error(offset, "invalid RIL word width"),
27 +
    }
28 +
}
29 +
30 +
/// Write a typed value without conflating integers and symbolic addresses.
31 +
export fn putVal(w: *mut super::Writer, val: il::Val) throws (super::Error) {
32 +
    match val {
33 +
        case il::Val::Reg(reg) => { try super::put8(w, 0); try super::put32(w, reg.n); }
34 +
        case il::Val::Imm(value) => { try super::put8(w, 1); try super::put64(w, value as u64); }
35 +
        case il::Val::DataSym(name) => { try super::put8(w, 2); try super::putSymbol(w, name, 1); }
36 +
        case il::Val::FnAddr(name) => { try super::put8(w, 3); try super::putSymbol(w, name, 2); }
37 +
        case il::Val::Undef => try super::put8(w, 4),
38 +
    }
39 +
}
40 +
41 +
/// Read a typed value; no raw integer is ever interpreted as a host pointer.
42 +
export fn getVal(r: *mut super::Reader) -> il::Val throws (super::Error) {
43 +
    let offset = r.offset;
44 +
    match try super::get8(r) {
45 +
        case 0 => return il::Val::Reg(try getReg(r)),
46 +
        case 1 => return il::Val::Imm((try super::get64(r)) as i64),
47 +
        case 2 => return il::Val::DataSym(try super::getSymbol(r, 1)),
48 +
        case 3 => return il::Val::FnAddr(try super::getSymbol(r, 2)),
49 +
        case 4 => return il::Val::Undef,
50 +
        else => throw super::error(offset, "invalid RIL value tag"),
51 +
    }
52 +
}
53 +
54 +
/// Read an SSA register index for the shared allocator.
55 +
export fn getReg(r: *mut super::Reader) -> il::Reg throws (super::Error) {
56 +
    return il::Reg { n: try super::get32(r) };
57 +
}
58 +
59 +
/// Write a length-prefixed argument list.
60 +
export fn putArgs(w: *mut super::Writer, args: *[il::Val]) throws (super::Error) {
61 +
    try super::put32(w, args.len);
62 +
    for arg in args { try putVal(w, arg); }
63 +
}
64 +
65 +
/// Read a length-prefixed argument list into caller-owned mutable storage.
66 +
export fn getArgs(r: *mut super::Reader) -> *mut [il::Val] throws (super::Error) {
67 +
    let count = try super::count(r, 1);
68 +
    let args = try super::storage(r.arena, @sizeOf(il::Val), @alignOf(il::Val), count, r.offset) as *mut [il::Val];
69 +
    for i in 0..count { set args[i] = try getVal(r); }
70 +
    return args;
71 +
}
72 +
73 +
/// Stable binary ALU tags: arithmetic 0..6, comparisons 7..12, bitwise 13..18.
74 +
fn binTag(op: il::BinOp) -> u8 {
75 +
    match op {
76 +
        case il::BinOp::Add => return 0,
77 +
        case il::BinOp::Sub => return 1,
78 +
        case il::BinOp::Mul => return 2,
79 +
        case il::BinOp::Sdiv => return 3,
80 +
        case il::BinOp::Udiv => return 4,
81 +
        case il::BinOp::Srem => return 5,
82 +
        case il::BinOp::Urem => return 6,
83 +
        case il::BinOp::Eq => return 7,
84 +
        case il::BinOp::Ne => return 8,
85 +
        case il::BinOp::Slt => return 9,
86 +
        case il::BinOp::Sge => return 10,
87 +
        case il::BinOp::Ult => return 11,
88 +
        case il::BinOp::Uge => return 12,
89 +
        case il::BinOp::And => return 13,
90 +
        case il::BinOp::Or => return 14,
91 +
        case il::BinOp::Xor => return 15,
92 +
        case il::BinOp::Shl => return 16,
93 +
        case il::BinOp::Sshr => return 17,
94 +
        case il::BinOp::Ushr => return 18,
95 +
    }
96 +
}
97 +
98 +
/// Decode a binary ALU operation without depending on union layout.
99 +
fn getBinOp(r: *mut super::Reader) -> il::BinOp throws (super::Error) {
100 +
    let offset = r.offset;
101 +
    match try super::get8(r) {
102 +
        case 0 => return il::BinOp::Add,
103 +
        case 1 => return il::BinOp::Sub,
104 +
        case 2 => return il::BinOp::Mul,
105 +
        case 3 => return il::BinOp::Sdiv,
106 +
        case 4 => return il::BinOp::Udiv,
107 +
        case 5 => return il::BinOp::Srem,
108 +
        case 6 => return il::BinOp::Urem,
109 +
        case 7 => return il::BinOp::Eq,
110 +
        case 8 => return il::BinOp::Ne,
111 +
        case 9 => return il::BinOp::Slt,
112 +
        case 10 => return il::BinOp::Sge,
113 +
        case 11 => return il::BinOp::Ult,
114 +
        case 12 => return il::BinOp::Uge,
115 +
        case 13 => return il::BinOp::And,
116 +
        case 14 => return il::BinOp::Or,
117 +
        case 15 => return il::BinOp::Xor,
118 +
        case 16 => return il::BinOp::Shl,
119 +
        case 17 => return il::BinOp::Sshr,
120 +
        case 18 => return il::BinOp::Ushr,
121 +
        else => throw super::error(offset, "invalid RIL binary operation tag"),
122 +
    }
123 +
}
124 +
125 +
/// Stable compare-and-branch tags: Eq=0, Ne=1, Slt=2, Ult=3.
126 +
fn cmpTag(op: il::CmpOp) -> u8 {
127 +
    match op {
128 +
        case il::CmpOp::Eq => return 0,
129 +
        case il::CmpOp::Ne => return 1,
130 +
        case il::CmpOp::Slt => return 2,
131 +
        case il::CmpOp::Ult => return 3,
132 +
    }
133 +
}
134 +
135 +
/// Decode one compare-and-branch operation.
136 +
fn getCmpOp(r: *mut super::Reader) -> il::CmpOp throws (super::Error) {
137 +
    let offset = r.offset;
138 +
    match try super::get8(r) {
139 +
        case 0 => return il::CmpOp::Eq,
140 +
        case 1 => return il::CmpOp::Ne,
141 +
        case 2 => return il::CmpOp::Slt,
142 +
        case 3 => return il::CmpOp::Ult,
143 +
        else => throw super::error(offset, "invalid RIL comparison tag"),
144 +
    }
145 +
}
146 +
147 +
/// Read a nonzero power-of-two memory alignment.
148 +
export fn getAlignment(r: *mut super::Reader) -> u32 throws (super::Error) {
149 +
    let offset = r.offset;
150 +
    let alignment = try super::get32(r);
151 +
    if alignment == 0 or (alignment & (alignment - 1)) <> 0 {
152 +
        throw super::error(offset, "RIL alignment must be a nonzero power of two");
153 +
    }
154 +
    return alignment;
155 +
}
156 +
157 +
/// Serialize every existing instruction variant with explicit field encodings.
158 +
export fn put(w: *mut super::Writer, instr: il::Instr) throws (super::Error) {
159 +
    match instr {
160 +
        case il::Instr::Reserve { dst, size, alignment } => {
161 +
            try super::put8(w, 0); try super::put32(w, dst.n);
162 +
            try putVal(w, size); try super::put32(w, alignment);
163 +
        }
164 +
        case il::Instr::Load { typ, dst, src, offset } => {
165 +
            try super::put8(w, 1); try putType(w, typ); try super::put32(w, dst.n);
166 +
            try super::put32(w, src.n); try super::put32(w, offset as u32);
167 +
        }
168 +
        case il::Instr::Sload { typ, dst, src, offset } => {
169 +
            try super::put8(w, 2); try putType(w, typ); try super::put32(w, dst.n);
170 +
            try super::put32(w, src.n); try super::put32(w, offset as u32);
171 +
        }
172 +
        case il::Instr::Store { typ, src, dst, offset } => {
173 +
            try super::put8(w, 3); try putType(w, typ); try putVal(w, src);
174 +
            try super::put32(w, dst.n); try super::put32(w, offset as u32);
175 +
        }
176 +
        case il::Instr::Blit { dst, src, size, alignment } => {
177 +
            try super::put8(w, 4); try super::put32(w, dst.n); try super::put32(w, src.n);
178 +
            try putVal(w, size); try super::put32(w, alignment);
179 +
        }
180 +
        case il::Instr::Copy { dst, val } => {
181 +
            try super::put8(w, 5); try super::put32(w, dst.n); try putVal(w, val);
182 +
        }
183 +
        case il::Instr::BinOp { op, typ, dst, a, b } => {
184 +
            try super::put8(w, 6); try super::put8(w, binTag(op)); try putType(w, typ);
185 +
            try super::put32(w, dst.n); try putVal(w, a); try putVal(w, b);
186 +
        }
187 +
        case il::Instr::UnOp { op, typ, dst, a } => {
188 +
            try super::put8(w, 7);
189 +
            match op { case il::UnOp::Neg => try super::put8(w, 0), case il::UnOp::Not => try super::put8(w, 1) }
190 +
            try putType(w, typ); try super::put32(w, dst.n); try putVal(w, a);
191 +
        }
192 +
        case il::Instr::Zext { typ, dst, val } => {
193 +
            try super::put8(w, 8); try putType(w, typ); try super::put32(w, dst.n); try putVal(w, val);
194 +
        }
195 +
        case il::Instr::Sext { typ, dst, val } => {
196 +
            try super::put8(w, 9); try putType(w, typ); try super::put32(w, dst.n); try putVal(w, val);
197 +
        }
198 +
        case il::Instr::Call { retTy, dst, func, args } => {
199 +
            try super::put8(w, 10); try putType(w, retTy);
200 +
            if let reg = dst { try super::put8(w, 1); try super::put32(w, reg.n); }
201 +
            else { try super::put8(w, 0); }
202 +
            try putVal(w, func); try putArgs(w, args);
203 +
        }
204 +
        case il::Instr::Ret { val } => {
205 +
            try super::put8(w, 11);
206 +
            if let value = val { try super::put8(w, 1); try putVal(w, value); }
207 +
            else { try super::put8(w, 0); }
208 +
        }
209 +
        case il::Instr::Jmp { target, args } => {
210 +
            try super::put8(w, 12); try super::put32(w, target); try putArgs(w, args);
211 +
        }
212 +
        case il::Instr::Br { op, typ, a, b, thenTarget, thenArgs, elseTarget, elseArgs } => {
213 +
            try super::put8(w, 13); try super::put8(w, cmpTag(op)); try putType(w, typ);
214 +
            try putVal(w, a); try putVal(w, b); try super::put32(w, thenTarget);
215 +
            try putArgs(w, thenArgs); try super::put32(w, elseTarget); try putArgs(w, elseArgs);
216 +
        }
217 +
        case il::Instr::Switch { val, defaultTarget, defaultArgs, cases } => {
218 +
            try super::put8(w, 14); try putVal(w, val); try super::put32(w, defaultTarget);
219 +
            try putArgs(w, defaultArgs); try super::put32(w, cases.len);
220 +
            for item in cases {
221 +
                try super::put64(w, item.value as u64); try super::put32(w, item.target); try putArgs(w, item.args);
222 +
            }
223 +
        }
224 +
        case il::Instr::Unreachable => try super::put8(w, 15),
225 +
        case il::Instr::Ecall { dst, num, a0, a1, a2, a3 } => {
226 +
            try super::put8(w, 16); try super::put32(w, dst.n); try putVal(w, num);
227 +
            try putVal(w, a0); try putVal(w, a1); try putVal(w, a2); try putVal(w, a3);
228 +
        }
229 +
        case il::Instr::Ebreak => try super::put8(w, 17),
230 +
        case il::Instr::MemoryFence => try super::put8(w, 18),
231 +
    }
232 +
}
233 +
234 +
/// Decode every current instruction variant. Sequential locals make wire order
235 +
/// independent of record-literal expression evaluation and compiler ABI layout.
236 +
export fn get(r: *mut super::Reader) -> il::Instr throws (super::Error) {
237 +
    let offset = r.offset;
238 +
    let tag = try super::get8(r);
239 +
    match tag {
240 +
        case 0 => {
241 +
            let dst = try getReg(r); let size = try getVal(r); let alignment = try getAlignment(r);
242 +
            return il::Instr::Reserve { dst, size, alignment };
243 +
        }
244 +
        case 1, 2 => {
245 +
            let typ = try getType(r); let dst = try getReg(r); let src = try getReg(r);
246 +
            let offset = (try super::get32(r)) as i32;
247 +
            if tag == 1 { return il::Instr::Load { typ, dst, src, offset }; }
248 +
            return il::Instr::Sload { typ, dst, src, offset };
249 +
        }
250 +
        case 3 => {
251 +
            let typ = try getType(r); let src = try getVal(r); let dst = try getReg(r);
252 +
            let offset = (try super::get32(r)) as i32;
253 +
            return il::Instr::Store { typ, src, dst, offset };
254 +
        }
255 +
        case 4 => {
256 +
            let dst = try getReg(r); let src = try getReg(r); let size = try getVal(r);
257 +
            let alignment = try getAlignment(r);
258 +
            return il::Instr::Blit { dst, src, size, alignment };
259 +
        }
260 +
        case 5 => {
261 +
            let dst = try getReg(r); let val = try getVal(r);
262 +
            return il::Instr::Copy { dst, val };
263 +
        }
264 +
        case 6 => {
265 +
            let op = try getBinOp(r); let typ = try getType(r); let dst = try getReg(r);
266 +
            let a = try getVal(r); let b = try getVal(r);
267 +
            return il::Instr::BinOp { op, typ, dst, a, b };
268 +
        }
269 +
        case 7 => {
270 +
            let opOffset = r.offset; let opTag = try super::get8(r);
271 +
            if opTag > 1 { throw super::error(opOffset, "invalid RIL unary operation tag"); }
272 +
            let op = il::UnOp::Neg if opTag == 0 else il::UnOp::Not;
273 +
            let typ = try getType(r); let dst = try getReg(r); let a = try getVal(r);
274 +
            return il::Instr::UnOp { op, typ, dst, a };
275 +
        }
276 +
        case 8, 9 => {
277 +
            let typ = try getType(r); let dst = try getReg(r); let val = try getVal(r);
278 +
            if tag == 8 { return il::Instr::Zext { typ, dst, val }; }
279 +
            return il::Instr::Sext { typ, dst, val };
280 +
        }
281 +
        case 10 => {
282 +
            let retTy = try getType(r); let present = try super::getBool(r);
283 +
            let mut dst: ?il::Reg = nil;
284 +
            if present { set dst = try getReg(r); }
285 +
            let func = try getVal(r); let args = try getArgs(r);
286 +
            return il::Instr::Call { retTy, dst, func, args };
287 +
        }
288 +
        case 11 => {
289 +
            let present = try super::getBool(r);
290 +
            let mut val: ?il::Val = nil;
291 +
            if present { set val = try getVal(r); }
292 +
            return il::Instr::Ret { val };
293 +
        }
294 +
        case 12 => {
295 +
            let target = try super::get32(r); let args = try getArgs(r);
296 +
            return il::Instr::Jmp { target, args };
297 +
        }
298 +
        case 13 => {
299 +
            let op = try getCmpOp(r); let typ = try getType(r);
300 +
            let a = try getVal(r); let b = try getVal(r);
301 +
            let thenTarget = try super::get32(r); let thenArgs = try getArgs(r);
302 +
            let elseTarget = try super::get32(r); let elseArgs = try getArgs(r);
303 +
            return il::Instr::Br { op, typ, a, b, thenTarget, thenArgs, elseTarget, elseArgs };
304 +
        }
305 +
        case 14 => {
306 +
            let val = try getVal(r); let defaultTarget = try super::get32(r); let defaultArgs = try getArgs(r);
307 +
            let count = try super::count(r, 16);
308 +
            let cases = try super::storage(r.arena, @sizeOf(il::SwitchCase), @alignOf(il::SwitchCase), count, r.offset) as *mut [il::SwitchCase];
309 +
            for i in 0..count {
310 +
                let value = (try super::get64(r)) as i64; let target = try super::get32(r); let args = try getArgs(r);
311 +
                set cases[i] = il::SwitchCase { value, target, args };
312 +
            }
313 +
            return il::Instr::Switch { val, defaultTarget, defaultArgs, cases };
314 +
        }
315 +
        case 15 => return il::Instr::Unreachable,
316 +
        case 16 => {
317 +
            let dst = try getReg(r); let num = try getVal(r); let a0 = try getVal(r);
318 +
            let a1 = try getVal(r); let a2 = try getVal(r); let a3 = try getVal(r);
319 +
            return il::Instr::Ecall { dst, num, a0, a1, a2, a3 };
320 +
        }
321 +
        case 17 => return il::Instr::Ebreak,
322 +
        case 18 => return il::Instr::MemoryFence,
323 +
        else => throw super::error(offset, "invalid RIL instruction tag"),
324 +
    }
325 +
}
lib/std/lang/il/binary/records.rad added +182 -0
1 +
//! Binary data, function and block records built directly from shared RIL.
2 +
//!
3 +
//! Data: symbol:u32, size:u32, alignment:u32, flags:u8 (readonly bit 0,
4 +
//! zero-init bit 1), values:list. Each value is count:u32 then item tag:u8:
5 +
//! Val=0 (type:u8, bits:u64), Sym=1 (symbol:u32), Fn=2 (symbol:u32),
6 +
//! Str=3 (bytes:string), Undef=4 (one zero/padding byte per repetition).
7 +
//! Function: symbol:u32, return type:u8, extern:bool, params:list, blocks:list.
8 +
//! Parameter: register:u32, type:u8. Block: label:string, loopDepth:u32,
9 +
//! params:list, instructions:list. Debug locations are deliberately omitted.
10 +
11 +
use std::lang::il;
12 +
use super::instructions;
13 +
use super::structure;
14 +
15 +
/// Serialize typed parameters with their original register identifiers.
16 +
fn putParams(w: *mut super::Writer, params: *[il::Param]) throws (super::Error) {
17 +
    try super::put32(w, params.len);
18 +
    for param in params {
19 +
        try super::put32(w, param.value.n);
20 +
        try instructions::putType(w, param.type);
21 +
    }
22 +
}
23 +
24 +
/// Decode parameters into caller-owned storage.
25 +
fn getParams(r: *mut super::Reader) -> *[il::Param] throws (super::Error) {
26 +
    let count = try super::count(r, 5);
27 +
    let params = try super::storage(r.arena, @sizeOf(il::Param), @alignOf(il::Param), count, r.offset) as *mut [il::Param];
28 +
    for i in 0..count {
29 +
        let value = try instructions::getReg(r);
30 +
        let typ = try instructions::getType(r);
31 +
        set params[i] = il::Param { value, type: typ };
32 +
    }
33 +
    return params;
34 +
}
35 +
36 +
/// Serialize one data declaration including zero repetition counts and flags.
37 +
export fn putData(w: *mut super::Writer, data: *il::Data) throws (super::Error) {
38 +
    try checkData(data, w.offset);
39 +
    try super::putSymbol(w, data.name, 1);
40 +
    try super::put32(w, data.size);
41 +
    try super::put32(w, data.alignment);
42 +
    let mut flags: u8 = 0;
43 +
    if data.readOnly { set flags |= 1; }
44 +
    if data.isZeroInit { set flags |= 2; }
45 +
    try super::put8(w, flags);
46 +
    try super::put32(w, data.values.len);
47 +
    for value in data.values {
48 +
        try super::put32(w, value.count);
49 +
        match value.item {
50 +
            case il::DataItem::Val { typ, val } => {
51 +
                try super::put8(w, 0); try instructions::putType(w, typ); try super::put64(w, val as u64);
52 +
            }
53 +
            case il::DataItem::Sym(name) => { try super::put8(w, 1); try super::putSymbol(w, name, 1); }
54 +
            case il::DataItem::Fn(name) => { try super::put8(w, 2); try super::putSymbol(w, name, 2); }
55 +
            case il::DataItem::Str(bytes) => { try super::put8(w, 3); try super::putString(w, bytes); }
56 +
            case il::DataItem::Undef => try super::put8(w, 4),
57 +
        }
58 +
    }
59 +
}
60 +
61 +
/// Decode one initializer item with its exact explicit discriminant.
62 +
fn getItem(r: *mut super::Reader) -> il::DataItem throws (super::Error) {
63 +
    let offset = r.offset;
64 +
    match try super::get8(r) {
65 +
        case 0 => {
66 +
            let typ = try instructions::getType(r);
67 +
            let val = (try super::get64(r)) as i64;
68 +
            return il::DataItem::Val { typ, val };
69 +
        }
70 +
        case 1 => return il::DataItem::Sym(try super::getSymbol(r, 1)),
71 +
        case 2 => return il::DataItem::Fn(try super::getSymbol(r, 2)),
72 +
        case 3 => return il::DataItem::Str(try super::getString(r)),
73 +
        case 4 => return il::DataItem::Undef,
74 +
        else => throw super::error(offset, "invalid RIL data item tag"),
75 +
    }
76 +
}
77 +
78 +
/// Decode and structurally validate a data record before native emission.
79 +
export fn getData(r: *mut super::Reader) -> il::Data throws (super::Error) {
80 +
    let offset = r.offset;
81 +
    let name = try super::declaration(r, 1);
82 +
    let size = try super::get32(r);
83 +
    let alignment = try instructions::getAlignment(r);
84 +
    let flagOffset = r.offset;
85 +
    let flags = try super::get8(r);
86 +
    if flags > 3 { throw super::error(flagOffset, "unknown RIL data flags"); }
87 +
    let count = try super::count(r, 5);
88 +
    let values = try super::storage(r.arena, @sizeOf(il::DataValue), @alignOf(il::DataValue), count, r.offset) as *mut [il::DataValue];
89 +
    for i in 0..count {
90 +
        let repeat = try super::get32(r);
91 +
        let item = try getItem(r);
92 +
        set values[i] = il::DataValue { item, count: repeat };
93 +
    }
94 +
    let data = il::Data { name, size, alignment, readOnly: (flags & 1) <> 0, isZeroInit: (flags & 2) <> 0, values };
95 +
    try checkData(&data, offset);
96 +
    return data;
97 +
}
98 +
99 +
/// Bound initializer byte arithmetic against the declared extent. A zero-init
100 +
/// flag may not discard nonzero bytes or relocations during image placement.
101 +
fn checkData(data: *il::Data, offset: u32) throws (super::Error) {
102 +
    if data.alignment == 0 or (data.alignment & (data.alignment - 1)) <> 0 {
103 +
        throw super::error(offset, "RIL alignment must be a nonzero power of two");
104 +
    }
105 +
    if data.readOnly and data.isZeroInit {
106 +
        throw super::error(offset, "RIL read-only data requires an initializer");
107 +
    }
108 +
    let mut size: u64 = 0;
109 +
    for value in data.values {
110 +
        let mut width: u32 = 0;
111 +
        let mut zero = true;
112 +
        match value.item {
113 +
            case il::DataItem::Val { typ, val } => {
114 +
                set width = il::typeSize(typ);
115 +
                // Only the stored low bytes contribute to narrow initializers.
116 +
                if width == 8 { set zero = val == 0; }
117 +
                else { set zero = ((val as u64) & ((1 as u64 << (width as u64 * 8)) - 1)) == 0; }
118 +
            }
119 +
            case il::DataItem::Sym(_), il::DataItem::Fn(_) => { set width = 8; set zero = false; }
120 +
            case il::DataItem::Str(bytes) => {
121 +
                set width = bytes.len;
122 +
                for byte in bytes { if byte <> 0 { set zero = false; } }
123 +
            }
124 +
            case il::DataItem::Undef => set width = 1,
125 +
        }
126 +
        let bytes = width as u64 * value.count as u64;
127 +
        if bytes > data.size as u64 - size { throw super::error(offset, "RIL initializer exceeds data size"); }
128 +
        set size += bytes;
129 +
        if data.isZeroInit and value.count <> 0 and not zero {
130 +
            throw super::error(offset, "RIL zero-init data contains a nonzero initializer");
131 +
        }
132 +
    }
133 +
    if not data.isZeroInit and size <> data.size as u64 {
134 +
        throw super::error(offset, "RIL initializer size differs from data size");
135 +
    }
136 +
}
137 +
138 +
/// Serialize all blocks, including unreferenced empty blocks retained by lowering.
139 +
export fn putFn(w: *mut super::Writer, func: *il::Fn) throws (super::Error) {
140 +
    try super::putSymbol(w, func.name, 2);
141 +
    try instructions::putType(w, func.returnType);
142 +
    try super::put8(w, 1 if func.isExtern else 0);
143 +
    try putParams(w, func.params);
144 +
    try super::put32(w, func.blocks.len);
145 +
    for block in func.blocks {
146 +
        try super::putString(w, block.label);
147 +
        try super::put32(w, block.loopDepth);
148 +
        try putParams(w, block.params);
149 +
        try super::put32(w, block.instrs.len);
150 +
        for instr in block.instrs { try instructions::put(w, instr); }
151 +
    }
152 +
}
153 +
154 +
/// Decode a function and reconstruct allocator metadata only after all block
155 +
/// indices are available. Temporary locations identify malformed instructions.
156 +
export fn getFn(r: *mut super::Reader) -> il::Fn throws (super::Error) {
157 +
    let offset = r.offset;
158 +
    let name = try super::declaration(r, 2);
159 +
    let returnType = try instructions::getType(r);
160 +
    let isExtern = try super::getBool(r);
161 +
    let params = try getParams(r);
162 +
    let count = try super::count(r, 16);
163 +
    let blocks = try super::storage(r.arena, @sizeOf(il::Block), @alignOf(il::Block), count, r.offset) as *mut [il::Block];
164 +
    for i in 0..count {
165 +
        let labelOffset = r.offset;
166 +
        let bytes = try super::getString(r);
167 +
        let label = try super::intern(r, bytes, labelOffset);
168 +
        let loopDepth = try super::get32(r);
169 +
        let blockParams = try getParams(r);
170 +
        let instrCount = try super::count(r, 1);
171 +
        let instrs = try super::storage(r.arena, @sizeOf(il::Instr), @alignOf(il::Instr), instrCount, r.offset) as *mut [il::Instr];
172 +
        let locs = try super::storage(r.arena, @sizeOf(il::SrcLoc), @alignOf(il::SrcLoc), instrCount, r.offset) as *mut [il::SrcLoc];
173 +
        for j in 0..instrCount {
174 +
            set locs[j] = il::SrcLoc { moduleId: 0, offset: r.offset };
175 +
            set instrs[j] = try instructions::get(r);
176 +
        }
177 +
        set blocks[i] = il::Block { label, params: blockParams, instrs, locs, preds: &[], loopDepth };
178 +
    }
179 +
    let mut func = il::Fn { name, params, returnType, isExtern, isLeaf: true, blocks };
180 +
    try structure::reconstruct(r, &mut func, blocks, offset);
181 +
    return func;
182 +
}
lib/std/lang/il/binary/structure.rad added +159 -0
1 +
//! Structural reconstruction for binary RIL using existing allocator indexes.
2 +
//!
3 +
//! Register definitions use the existing byte-keyed dictionary. Numeric keys
4 +
//! have stable arena storage. Branch edges rebuild unique predecessors.
5 +
//! These checks establish structure, not dominance, provenance, or type safety.
6 +
7 +
use std::collections::dict;
8 +
use std::lang::il;
9 +
use std::lang::gen::regalloc::liveness;
10 +
11 +
/// State for counting and then materializing unique predecessor lists.
12 +
record Edges: Copy {
13 +
    /// Decoder-owned blocks.
14 +
    blocks: *mut [il::Block],
15 +
    /// Incoming counts, or absolute insertion cursors during the second pass.
16 +
    counts: *mut [u32],
17 +
    /// Last source block seen for each target; duplicate edges are ignored.
18 +
    last: *mut [u32],
19 +
    /// Contiguous mutable storage backing all published predecessor slices.
20 +
    predecessors: *mut [u32],
21 +
    /// Whether predecessor storage has been allocated and should be filled.
22 +
    fill: bool,
23 +
}
24 +
25 +
/// Defined-register index and the result of inspecting an instruction's operands.
26 +
record Registers: Copy {
27 +
    /// Dictionary whose keys are the binary u32 register indexes.
28 +
    definitions: dict::Dict,
29 +
    /// Every operand seen so far has a corresponding definition.
30 +
    valid: bool,
31 +
}
32 +
33 +
/// Insert a unique definition within the shared allocator's register capacity.
34 +
fn define(map: *mut dict::Dict, numbers: *mut [u32], index: u32, reg: il::Reg, offset: u32) throws (super::Error) {
35 +
    if reg.n >= liveness::MAX_SSA_REGS { throw super::error(offset, "RIL register exceeds allocator capacity"); }
36 +
    set numbers[index] = reg.n;
37 +
    let key = @sliceOf(&numbers[index] as *u8, @sizeOf(u32));
38 +
    if dict::get(map, key) <> nil { throw super::error(offset, "duplicate RIL register definition"); }
39 +
    dict::insert(map, key, index as i32);
40 +
}
41 +
42 +
/// Check one operand through the shared IL register visitor.
43 +
fn operand(reg: il::Reg, context: *mut opaque) {
44 +
    let state = context as *mut Registers;
45 +
    let number = reg.n;
46 +
    let key = @sliceOf(&number as *u8, @sizeOf(u32));
47 +
    if dict::get(&state.definitions, key) == nil { set state.valid = false; }
48 +
}
49 +
50 +
/// Identify the terminators used by existing lowering and instruction selection.
51 +
fn terminator(value: il::Instr) -> bool {
52 +
    match value {
53 +
        case il::Instr::Ret { .. }, il::Instr::Jmp { .. }, il::Instr::Br { .. },
54 +
             il::Instr::Switch { .. }, il::Instr::Unreachable => return true,
55 +
        else => return false,
56 +
    }
57 +
}
58 +
59 +
/// Check a target and its arity, then count or emit one unique predecessor.
60 +
fn edge(e: *mut Edges, source: u32, target: u32, count: u32, offset: u32) throws (super::Error) {
61 +
    if target >= e.blocks.len { throw super::error(offset, "RIL block target out of range"); }
62 +
    let block = &e.blocks[target];
63 +
    if count <> block.params.len { throw super::error(offset, "RIL branch argument count mismatch"); }
64 +
    if block.instrs.len == 0 { throw super::error(offset, "RIL branch targets an empty block"); }
65 +
    if e.last[target] == source { return; }
66 +
    set e.last[target] = source;
67 +
    if e.fill { set e.predecessors[e.counts[target]] = source; }
68 +
    set e.counts[target] += 1;
69 +
}
70 +
71 +
/// Traverse explicit branch fields, including switch default and case edges.
72 +
fn edges(e: *mut Edges, source: u32, value: il::Instr, offset: u32) throws (super::Error) {
73 +
    match value {
74 +
        case il::Instr::Jmp { target, args } => try edge(e, source, target, args.len, offset),
75 +
        case il::Instr::Br { thenTarget, thenArgs, elseTarget, elseArgs, .. } => {
76 +
            try edge(e, source, thenTarget, thenArgs.len, offset);
77 +
            try edge(e, source, elseTarget, elseArgs.len, offset);
78 +
        }
79 +
        case il::Instr::Switch { defaultTarget, defaultArgs, cases, .. } => {
80 +
            try edge(e, source, defaultTarget, defaultArgs.len, offset);
81 +
            for item in cases { try edge(e, source, item.target, item.args.len, offset); }
82 +
        }
83 +
        else => {},
84 +
    }
85 +
}
86 +
87 +
/// Reconstruct allocator metadata while retaining decoder ownership of blocks.
88 +
/// Existing register indexes and immutable argument slices are preserved.
89 +
export fn reconstruct(r: *mut super::Reader, func: *mut il::Fn, blocks: *mut [il::Block], offset: u32) throws (super::Error) {
90 +
    if func.isExtern {
91 +
        if blocks.len <> 0 { throw super::error(offset, "extern RIL function has a body"); }
92 +
    } else if blocks.len == 0 or blocks[0].instrs.len == 0 {
93 +
        throw super::error(offset, "RIL function has no entry block instructions");
94 +
    }
95 +
    let mut names = try super::dictionary(r.arena, blocks.len, offset);
96 +
    let mut definitions = func.params.len as u64;
97 +
    for block, i in blocks {
98 +
        if dict::get(&names, block.label) <> nil { throw super::error(offset, "duplicate RIL block label"); }
99 +
        dict::insert(&mut names, block.label, i as i32);
100 +
        set definitions += block.params.len as u64;
101 +
        for item in block.instrs { if il::instrDst(item) <> nil { set definitions += 1; } }
102 +
    }
103 +
    if definitions > liveness::MAX_SSA_REGS as u64 {
104 +
        throw super::error(offset, "RIL function exceeds allocator register capacity");
105 +
    }
106 +
    let numbers = try super::storage(r.arena, @sizeOf(u32), @alignOf(u32), definitions as u32, offset) as *mut [u32];
107 +
    let mut map = try super::dictionary(r.arena, definitions as u32, offset);
108 +
    let mut index: u32 = 0;
109 +
    for param in func.params {
110 +
        try define(&mut map, numbers, index, param.value, offset);
111 +
        set index += 1;
112 +
    }
113 +
    for block in blocks {
114 +
        for param in block.params {
115 +
            try define(&mut map, numbers, index, param.value, offset);
116 +
            set index += 1;
117 +
        }
118 +
        for item, i in block.instrs {
119 +
            if let dst = il::instrDst(item) {
120 +
                try define(&mut map, numbers, index, dst, block.locs[i].offset);
121 +
                set index += 1;
122 +
            }
123 +
        }
124 +
    }
125 +
    let counts = try super::storage(r.arena, @sizeOf(u32), @alignOf(u32), blocks.len, offset) as *mut [u32];
126 +
    let last = try super::storage(r.arena, @sizeOf(u32), @alignOf(u32), blocks.len, offset) as *mut [u32];
127 +
    for i in 0..blocks.len { set counts[i] = 0; set last[i] = 0xFFFFFFFF; }
128 +
    let mut e = Edges { blocks, counts, last, predecessors: &mut [], fill: false };
129 +
    let mut registers = Registers { definitions: map, valid: true };
130 +
    for block, b in blocks {
131 +
        for item, i in block.instrs {
132 +
            let pos = block.locs[i].offset;
133 +
            if terminator(item) and i + 1 <> block.instrs.len { throw super::error(pos, "RIL instruction follows a terminator"); }
134 +
            if not terminator(item) and i + 1 == block.instrs.len { throw super::error(pos, "RIL block is missing a terminator"); }
135 +
            if il::isCall(item) { set func.isLeaf = false; }
136 +
            try edges(&mut e, b, item, pos);
137 +
            il::forEachReg(item, operand, &mut registers as *mut opaque);
138 +
            if not registers.valid { throw super::error(pos, "undefined RIL register"); }
139 +
        }
140 +
    }
141 +
    let mut total: u64 = 0;
142 +
    for count in counts { set total += count as u64; }
143 +
    if total > 0xFFFFFFFF { throw super::error(offset, "too many RIL control-flow edges"); }
144 +
    set e.predecessors = try super::storage(r.arena, @sizeOf(u32), @alignOf(u32), total as u32, offset) as *mut [u32];
145 +
    let mut start: u32 = 0;
146 +
    for i in 0..blocks.len {
147 +
        let end = start + counts[i];
148 +
        set blocks[i].preds = &e.predecessors[start..end];
149 +
        set counts[i] = start;
150 +
        set last[i] = 0xFFFFFFFF;
151 +
        set start = end;
152 +
    }
153 +
    set e.fill = true;
154 +
    for block, b in blocks {
155 +
        for item, i in block.instrs { try edges(&mut e, b, item, block.locs[i].offset); }
156 +
    }
157 +
    // Binary offsets identify decoding errors, not source-language locations.
158 +
    for i in 0..blocks.len { set blocks[i].locs = &[]; }
159 +
}
lib/std/lang/il/binary/tests.rad added +55 -0
1 +
//! Binary RIL input-boundary regression tests.
2 +
3 +
use std::lang::alloc;
4 +
use std::lang::il;
5 +
use std::lang::strings;
6 +
use std::testing;
7 +
8 +
/// Scratch space reused after each failed decode.
9 +
static ARENA: [u8; 65536] = undefined;
10 +
/// Stable interned names refer to the same binary buffer throughout each test.
11 +
static POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
12 +
13 +
/// Return whether structural decoding rejects the supplied bytes.
14 +
fn rejected(bytes: *[u8], arena: *mut alloc::Arena) -> bool {
15 +
    alloc::reset(arena);
16 +
    try super::decode(bytes, arena, &mut POOL) catch { return true; };
17 +
    return false;
18 +
}
19 +
20 +
/// Every incomplete prefix fails, including cuts inside strings, literals and references.
21 +
@test fn truncatedImage() throws (testing::TestError) {
22 +
    for i in 0..POOL.table.len { set POOL.table[i] = &[]; }
23 +
    set POOL.count = 0;
24 +
    let bytes: [u8; 6] = [0, 255, 128, 34, 92, 10];
25 +
    let values = &[
26 +
        il::DataValue { item: il::DataItem::Fn("callee"), count: 1 },
27 +
        il::DataValue { item: il::DataItem::Sym("data"), count: 1 },
28 +
        il::DataValue { item: il::DataItem::Str(&bytes[..]), count: 1 },
29 +
        il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: -1 }, count: 1 },
30 +
    ];
31 +
    let data = &[il::Data { name: "data", size: 30, alignment: 8, readOnly: true, isZeroInit: false, values }];
32 +
    let mut body = [
33 +
        il::Instr::Copy { dst: il::Reg { n: 0 }, val: il::Val::DataSym("data") },
34 +
        il::Instr::Load { typ: il::Type::W64, dst: il::Reg { n: 1 }, src: il::Reg { n: 0 }, offset: 0 },
35 +
        il::Instr::Call { retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: il::Val::FnAddr("callee"), args: &[il::Val::Reg(il::Reg { n: 1 })] },
36 +
        il::Instr::MemoryFence,
37 +
        il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 2 }) },
38 +
    ];
39 +
    let mut calleeBody = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 0 }) }];
40 +
    let main = il::Fn {
41 +
        name: "main", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: false,
42 +
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut body[..], locs: &[], preds: &[], loopDepth: 0 }],
43 +
    };
44 +
    let callee = il::Fn {
45 +
        name: "callee", params: &[il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }],
46 +
        returnType: il::Type::W64, isExtern: false, isLeaf: true,
47 +
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut calleeBody[..], locs: &[], preds: &[], loopDepth: 0 }],
48 +
    };
49 +
    let image = super::Image { program: il::Program { data, fns: &[&main, &callee] }, entry: "main" };
50 +
    let mut arena = alloc::new(&mut ARENA[..]);
51 +
    let mut output: [u8; 1024] = undefined;
52 +
    let size = try! super::encode(&image, &mut arena, &mut output[..]);
53 +
    for i in 0..size { try testing::expect(rejected(&output[..i], &mut arena)); }
54 +
    try testing::expectNot(rejected(&output[..size], &mut arena));
55 +
}
seed/update +1 -1
39 39
# ---------------------------------------------------------------------------
40 40
# Command line flags for Radiance compiler
41 41
# ---------------------------------------------------------------------------
42 42
43 43
STD_MODS="$(sed 's/^/-mod /' std.lib | tr '\n' ' ')"
44 -
OPTS="-pkg std ${STD_MODS} -pkg radiance -mod compiler/radiance.rad -entry radiance"
44 +
OPTS="-pkg std ${STD_MODS} -pkg radiance -mod compiler/radiance.rad -mod compiler/radiance/binary.rad -entry radiance"
45 45
46 46
# ---------------------------------------------------------------------------
47 47
# Emulator settings
48 48
# ---------------------------------------------------------------------------
49 49
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/instructions.rad
36 +
lib/std/lang/il/binary/records.rad
37 +
lib/std/lang/il/binary/structure.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 +1 -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