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.
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 |