kernel: execute source-built binary images through the shared backend
14a97c541f8c53d773c51cd00bce8b3ecd15fc62f58d06d124c46c69bc0b6cd1
Verified: make -C kernel check; make std-test bin-test; seven binary catalogs execute in U-mode, including bounds faults, Page access, and event consumption.
1 parent
55538f16
.gitignore
+1 -0
| 14 | 14 | # Compiler/build output |
|
| 15 | 15 | /bin |
|
| 16 | 16 | *.rv64 |
|
| 17 | 17 | *.rv64.debug |
|
| 18 | 18 | *.o |
|
| 19 | + | /kernel/build/ |
|
| 19 | 20 | ||
| 20 | 21 | # Seed binary (tracked, but not stage intermediates) |
|
| 21 | 22 | !seed/ |
|
| 22 | 23 | !seed/* |
|
| 23 | 24 | seed/radiance.rv64.s* |
Makefile
+1 -1
| 36 | 36 | endif |
|
| 37 | 37 | ||
| 38 | 38 | # Compiler build |
|
| 39 | 39 | ||
| 40 | 40 | SEED := seed/radiance.rv64 |
|
| 41 | - | COMPILER_SOURCES := compiler/radiance.rad compiler/radiance/binary.rad |
|
| 41 | + | COMPILER_SOURCES := compiler/radiance.rad $(wildcard compiler/radiance/*.rad compiler/radiance/*/*.rad) |
|
| 42 | 42 | SEED_OPTS := $(STD) -pkg radiance $(patsubst %,-mod %,$(COMPILER_SOURCES)) -entry radiance |
|
| 43 | 43 | ||
| 44 | 44 | $(RAD_BIN): $(STD_LIB) $(COMPILER_SOURCES) | $(BIN_DIR) |
|
| 45 | 45 | @echo "radiance $(SEED) => $@" |
|
| 46 | 46 | @$(EMU) $(EMU_FLAGS) -run $(SEED) $(SEED_OPTS) -o $@ |
compiler/radiance.rad
+87 -12
| 1 | 1 | //! Radiance compiler front-end. |
|
| 2 | 2 | ||
| 3 | 3 | /// Binary RIL file output and native loading. |
|
| 4 | 4 | mod binary; |
|
| 5 | + | /// Trusted binary image catalogs linked into a native program. |
|
| 6 | + | mod images; |
|
| 5 | 7 | use std::mem; |
|
| 6 | 8 | use std::fmt; |
|
| 7 | 9 | use std::io; |
|
| 8 | 10 | use std::lang::alloc; |
|
| 9 | 11 | use std::lang::ast; |
| 35 | 37 | constant MAX_SOURCES_SIZE: u32 = 2097152; |
|
| 36 | 38 | /// Maximum number of test functions we can discover. |
|
| 37 | 39 | constant MAX_TESTS: u32 = 1024; |
|
| 38 | 40 | /// Maximum number of assembly source paths we can load per package. |
|
| 39 | 41 | constant MAX_ASM_MODULES: u32 = 64; |
|
| 42 | + | /// Maximum binary image inputs linked into a native catalog. |
|
| 43 | + | constant MAX_IMAGES: u32 = 64; |
|
| 40 | 44 | ||
| 41 | 45 | /// AST arena size (32 MB) - retains parsed nodes throughout compilation. |
|
| 42 | 46 | constant TEMP_ARENA_SIZE: u32 = 33554432; |
|
| 43 | 47 | /// Per-function lowering and register-allocation arena size (16 MB). |
|
| 44 | 48 | constant FN_ARENA_SIZE: u32 = 16777216; |
| 97 | 101 | /// Symbol name exported for startup code to call the semantic entry function. |
|
| 98 | 102 | constant DEFAULT_ENTRY_SYMBOL: *[u8] = "::default"; |
|
| 99 | 103 | ||
| 100 | 104 | /// Usage string. |
|
| 101 | 105 | constant USAGE: *[u8] = |
|
| 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"; |
|
| 106 | + | "usage: radiance -pkg <name> [-start <input.ras>] -mod <input>.. [-pkg <name> -mod <input>..] -entry <pkg> [-emit ril] [-image <input.ril>] -o <output>\n radiance -load <input.ril> [-start <input.ras>] [-mod <input.ras>] -o <output>\n radiance -catalog <output.rad> -image <input.ril>..\n"; |
|
| 103 | 107 | ||
| 104 | 108 | /// Compiler error. |
|
| 105 | 109 | union Error: Copy { |
|
| 106 | 110 | Other, |
|
| 107 | 111 | } |
| 164 | 168 | outputPath: ?*[u8], |
|
| 165 | 169 | /// Whether to emit debug info (.debug file). |
|
| 166 | 170 | debug: bool, |
|
| 167 | 171 | /// Write binary RIL instead of native instructions. |
|
| 168 | 172 | emitIl: bool, |
|
| 173 | + | /// Ordered trusted binary image inputs for native linking. |
|
| 174 | + | imagePaths: [*[u8]; MAX_IMAGES], |
|
| 175 | + | /// Number of binary image paths. |
|
| 176 | + | imageCount: u32, |
|
| 177 | + | /// Include zero-initialized storage in the native image's data sections. |
|
| 178 | + | zeroBss: bool, |
|
| 169 | 179 | } |
|
| 170 | 180 | ||
| 181 | + | /// Stable driver input storage for the single compilation invocation. |
|
| 182 | + | static CONTEXT: CompileContext = undefined; |
|
| 183 | + | ||
| 171 | 184 | /// Root module info for a package. |
|
| 172 | 185 | record RootModule: Copy { |
|
| 173 | 186 | entry: *module::ModuleEntry, |
|
| 174 | 187 | ast: *mut ast::Node, |
|
| 175 | 188 | } |
| 305 | 318 | throw error(msg); |
|
| 306 | 319 | } |
|
| 307 | 320 | return args[*idx]; |
|
| 308 | 321 | } |
|
| 309 | 322 | ||
| 310 | - | /// Parse CLI arguments and return compilation context. |
|
| 323 | + | /// Write a catalog declaration from the ordered binary image inputs. |
|
| 324 | + | fn catalogCommand(args: *[*[u8]]) -> i32 { |
|
| 325 | + | if args.len < 4 { |
|
| 326 | + | let _error = error(&["`-catalog` requires an output path and image inputs"]); |
|
| 327 | + | return 1; |
|
| 328 | + | } |
|
| 329 | + | let mut paths: [*[u8]; MAX_IMAGES] = undefined; |
|
| 330 | + | let mut count: u32 = 0; |
|
| 331 | + | let mut index: u32 = 2; |
|
| 332 | + | while index < args.len { |
|
| 333 | + | if not mem::eq(args[index], "-image") or index + 1 >= args.len or count == MAX_IMAGES { |
|
| 334 | + | let _error = error(&["invalid catalog image arguments"]); |
|
| 335 | + | return 1; |
|
| 336 | + | } |
|
| 337 | + | set paths[count] = args[index + 1]; |
|
| 338 | + | set count += 1; |
|
| 339 | + | set index += 2; |
|
| 340 | + | } |
|
| 341 | + | try images::catalog(&paths[..count], args[1], &mut STRING_POOL) catch e { |
|
| 342 | + | let _error = error(&[e.message]); |
|
| 343 | + | return 1; |
|
| 344 | + | }; |
|
| 345 | + | return 0; |
|
| 346 | + | } |
|
| 347 | + | ||
| 348 | + | /// Parse CLI arguments and initialize the caller's compilation context. |
|
| 311 | 349 | fn processCommand( |
|
| 312 | 350 | args: *[*[u8]], |
|
| 313 | - | arena: *mut ast::NodeArena |
|
| 314 | - | ) -> CompileContext throws (Error) { |
|
| 351 | + | arena: *mut ast::NodeArena, |
|
| 352 | + | ctx: *mut CompileContext |
|
| 353 | + | ) throws (Error) { |
|
| 315 | 354 | let mut buildTest = false; |
|
| 316 | 355 | let mut debugEnabled = false; |
|
| 317 | 356 | let mut outputPath: ?*[u8] = nil; |
|
| 318 | 357 | let mut dump = Dump::None; |
|
| 319 | 358 | let mut entryPkgName: ?*[u8] = nil; |
|
| 320 | 359 | let mut emitIl = false; |
|
| 360 | + | let mut zeroBss = false; |
|
| 361 | + | let mut imagePaths: [*[u8]; MAX_IMAGES] = undefined; |
|
| 362 | + | let mut imageCount: u32 = 0; |
|
| 321 | 363 | ||
| 322 | 364 | // Per-package source path tracking. |
|
| 323 | 365 | let mut inputs: [PackageInput; MAX_PACKAGES] = undefined; |
|
| 324 | 366 | let mut pkgCount: u32 = 0; |
|
| 325 | 367 | let mut currentPkgIdx: ?u32 = nil; |
| 377 | 419 | set entryPkgName = args[idx]; |
|
| 378 | 420 | } else if mem::eq(arg, "-test") { |
|
| 379 | 421 | set buildTest = true; |
|
| 380 | 422 | } else if mem::eq(arg, "-debug") { |
|
| 381 | 423 | set debugEnabled = true; |
|
| 424 | + | } else if mem::eq(arg, "-zero-bss") { |
|
| 425 | + | set zeroBss = true; |
|
| 382 | 426 | } else if mem::eq(arg, "-o") { |
|
| 383 | 427 | try nextArg(args, &mut idx, &["`-o` requires an output path"]); |
|
| 384 | 428 | set outputPath = args[idx]; |
|
| 429 | + | } else if mem::eq(arg, "-image") { |
|
| 430 | + | let path = try nextArg(args, &mut idx, &["`-image` requires a binary RIL path"]); |
|
| 431 | + | if imageCount == MAX_IMAGES { throw error(&["too many binary image inputs"]); } |
|
| 432 | + | set imagePaths[imageCount] = path; |
|
| 433 | + | set imageCount += 1; |
|
| 385 | 434 | } else if mem::eq(arg, "-emit") { |
|
| 386 | 435 | let mode = try nextArg(args, &mut idx, &["`-emit` requires `ril`"]); |
|
| 387 | 436 | if not mem::eq(mode, "ril") { |
|
| 388 | 437 | throw error(&["unknown output format", mode, "(expected: ril)"]); |
|
| 389 | 438 | } |
| 408 | 457 | set idx += 1; |
|
| 409 | 458 | } |
|
| 410 | 459 | if emitIl and (outputPath == nil or dump <> Dump::None) { |
|
| 411 | 460 | throw error(&["`-emit ril` requires `-o` and cannot be combined with `-dump`"]); |
|
| 412 | 461 | } |
|
| 462 | + | if (imageCount <> 0 or zeroBss) and (emitIl or (dump <> Dump::None and dump <> Dump::Asm)) { |
|
| 463 | + | throw error(&["binary images and `-zero-bss` require native output"]); |
|
| 464 | + | } |
|
| 413 | 465 | if pkgCount == 0 { |
|
| 414 | 466 | throw error(&["no package specified"]); |
|
| 415 | 467 | } |
|
| 416 | 468 | for i in 0..pkgCount { |
|
| 417 | 469 | if inputs[i].radPathCount == 0 { |
| 449 | 501 | if i <> entryIdx and inputs[i].startupPath <> nil { |
|
| 450 | 502 | throw error(&["`-start` is only supported on the entry package"]); |
|
| 451 | 503 | } |
|
| 452 | 504 | } |
|
| 453 | 505 | let graph = module::moduleGraph(&mut MODULE_ENTRIES[..], &mut STRING_POOL, arena); |
|
| 454 | - | let mut ctx = CompileContext { |
|
| 506 | + | set *ctx = CompileContext { |
|
| 455 | 507 | packages: undefined, |
|
| 456 | 508 | inputs, |
|
| 457 | 509 | packageCount: pkgCount, |
|
| 458 | 510 | entryPkgIdx, |
|
| 459 | 511 | graph, |
|
| 460 | 512 | config: resolver::Config { buildTest }, |
|
| 461 | 513 | dump, |
|
| 462 | 514 | outputPath, |
|
| 463 | 515 | debug: debugEnabled, |
|
| 464 | 516 | emitIl, |
|
| 517 | + | imagePaths, |
|
| 518 | + | imageCount, |
|
| 519 | + | zeroBss, |
|
| 465 | 520 | }; |
|
| 466 | 521 | // Initialize and parse all packages. |
|
| 467 | 522 | let mut sourceArena = alloc::new(&mut MODULE_SOURCES[..]); |
|
| 468 | 523 | for i in 0..pkgCount { |
|
| 469 | 524 | package::init(&mut ctx.packages[i], i as u16, ctx.inputs[i].name, &mut STRING_POOL); |
| 471 | 526 | for j in 0..ctx.inputs[i].radPathCount { |
|
| 472 | 527 | let path = ctx.inputs[i].radPaths[j]; |
|
| 473 | 528 | try processModule(&mut ctx.packages[i], &mut ctx.graph, path, arena, &mut sourceArena); |
|
| 474 | 529 | } |
|
| 475 | 530 | } |
|
| 476 | - | return ctx; |
|
| 477 | 531 | } |
|
| 478 | 532 | ||
| 479 | 533 | /// Get the entry package from the context. |
|
| 480 | 534 | fn getEntryPackage(ctx: *CompileContext) -> *package::Package throws (Error) { |
|
| 481 | 535 | let entryIdx = ctx.entryPkgIdx else { |
| 1051 | 1105 | if let path = startupPath { |
|
| 1052 | 1106 | try assembleAsmModule(&mut generator, entryPkg, path, &mut asmDataLen, &mut res.arena); |
|
| 1053 | 1107 | } |
|
| 1054 | 1108 | try lowerAllPackagesInto(ctx, res, &mut low); |
|
| 1055 | 1109 | let asmData = try assembleAsmInputs(ctx, &mut generator, &mut asmDataLen, &mut res.arena); |
|
| 1110 | + | try images::append(&mut generator, &ctx.imagePaths[..ctx.imageCount], &mut STRING_POOL, |
|
| 1111 | + | &mut res.arena, fnArena, &mut low.data) catch e { |
|
| 1112 | + | throw error(&[e.message]); |
|
| 1113 | + | }; |
|
| 1114 | + | if ctx.zeroBss { |
|
| 1115 | + | for i in 0..low.data.len { |
|
| 1116 | + | let item = &mut low.data[i]; |
|
| 1117 | + | if not item.isZeroInit { continue; } |
|
| 1118 | + | let value = try! alloc::alloc(&mut res.arena, @sizeOf(il::DataValue), @alignOf(il::DataValue)) as *mut il::DataValue; |
|
| 1119 | + | set *value = il::DataValue { item: il::DataItem::Undef, count: item.size }; |
|
| 1120 | + | set item.values = @sliceOf(value, 1); |
|
| 1121 | + | set item.isZeroInit = false; |
|
| 1122 | + | } |
|
| 1123 | + | } |
|
| 1124 | + | try binary::layout(&low.data[..], asmData.len) catch e { |
|
| 1125 | + | throw error(&[e.message]); |
|
| 1126 | + | }; |
|
| 1056 | 1127 | ||
| 1057 | 1128 | match generator.entryPatch { |
|
| 1058 | 1129 | case rv64::EntryPatch::Reserved(targetName) => { |
|
| 1059 | 1130 | if targetName == nil { |
|
| 1060 | 1131 | throw error(&["fatal:", "no default function found"]); |
| 1136 | 1207 | ||
| 1137 | 1208 | @default fn main(env: *sys::Env) -> i32 { |
|
| 1138 | 1209 | if env.args.len > 0 and mem::eq(env.args[0], "-load") { |
|
| 1139 | 1210 | return binary::run(env.args, &mut STRING_POOL); |
|
| 1140 | 1211 | } |
|
| 1212 | + | if env.args.len > 0 and mem::eq(env.args[0], "-catalog") { |
|
| 1213 | + | return catalogCommand(env.args); |
|
| 1214 | + | } |
|
| 1141 | 1215 | let mut arena = ast::nodeArena(&mut TEMP_ARENA[..]); |
|
| 1142 | - | let mut ctx = try processCommand(env.args, &mut arena) catch { |
|
| 1216 | + | let ctx = &mut CONTEXT; |
|
| 1217 | + | try processCommand(env.args, &mut arena, ctx) catch { |
|
| 1143 | 1218 | return 1; |
|
| 1144 | 1219 | }; |
|
| 1145 | 1220 | match ctx.dump { |
|
| 1146 | 1221 | case Dump::Ast => { |
|
| 1147 | - | try dumpAst(&ctx) catch { |
|
| 1222 | + | try dumpAst(ctx) catch { |
|
| 1148 | 1223 | return 1; |
|
| 1149 | 1224 | }; |
|
| 1150 | 1225 | return 0; |
|
| 1151 | 1226 | } |
|
| 1152 | 1227 | case Dump::Graph => { |
|
| 1153 | - | dumpGraph(&ctx); |
|
| 1228 | + | dumpGraph(ctx); |
|
| 1154 | 1229 | return 0; |
|
| 1155 | 1230 | } |
|
| 1156 | 1231 | else => {} |
|
| 1157 | 1232 | } |
|
| 1158 | 1233 | // Generate test runner if in test mode. |
|
| 1159 | 1234 | if ctx.config.buildTest { |
|
| 1160 | - | try generateTestRunner(&mut ctx, &mut arena) catch { |
|
| 1235 | + | try generateTestRunner(ctx, &mut arena) catch { |
|
| 1161 | 1236 | return 1; |
|
| 1162 | 1237 | }; |
|
| 1163 | 1238 | } |
|
| 1164 | 1239 | // Run resolution phase. |
|
| 1165 | - | let mut res = try runResolver(&mut ctx, arena.nextId) catch { |
|
| 1240 | + | let mut res = try runResolver(ctx, arena.nextId) catch { |
|
| 1166 | 1241 | return 1; |
|
| 1167 | 1242 | }; |
|
| 1168 | 1243 | let mut fnArena = alloc::new(&mut FN_ARENA[..]); |
|
| 1169 | 1244 | ||
| 1170 | 1245 | // Lower, dump, and/or generate output. |
|
| 1171 | - | try compile(&mut ctx, &mut res, &mut fnArena) catch { |
|
| 1246 | + | try compile(ctx, &mut res, &mut fnArena) catch { |
|
| 1172 | 1247 | return 1; |
|
| 1173 | 1248 | }; |
|
| 1174 | 1249 | return 0; |
|
| 1175 | 1250 | } |
compiler/radiance/binary.rad
+13 -5
| 92 | 92 | if result.output.len == 0 { throw error("-load requires -o"); } |
|
| 93 | 93 | return result; |
|
| 94 | 94 | } |
|
| 95 | 95 | ||
| 96 | 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"); } |
|
| 97 | + | export fn layout(items: *[il::Data], prefix: u32) throws (il::binary::Error) { |
|
| 98 | + | if items.len > data::MAX_DATA_SYMS { throw error("too many data symbols"); } |
|
| 99 | 99 | for section in 0..2 { |
|
| 100 | 100 | let readOnly = section == 0; |
|
| 101 | 101 | let mut offset = prefix as u64 if readOnly else 0 as u64; |
|
| 102 | + | let base = rv64::RO_DATA_BASE if readOnly else rv64::RW_DATA_BASE; |
|
| 103 | + | if offset > MAX_DATA as u64 { throw error("native data section exceeds capacity"); } |
|
| 102 | 104 | for pass in 0..2 { |
|
| 103 | - | for item in program.data { |
|
| 105 | + | for item in items { |
|
| 104 | 106 | if item.readOnly <> readOnly or item.isZeroInit <> (pass == 1) { continue; } |
|
| 105 | 107 | let alignment = item.alignment as u64; |
|
| 106 | 108 | if alignment == 0 or alignment & (alignment - 1) <> 0 { |
|
| 107 | 109 | throw error("invalid data alignment"); |
|
| 108 | 110 | } |
|
| 109 | 111 | 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"); } |
|
| 112 | + | if (not item.isZeroInit or readOnly) and offset > MAX_DATA as u64 { |
|
| 113 | + | throw error("native data section exceeds capacity"); |
|
| 114 | + | } |
|
| 115 | + | // Native data address loads use signed 32-bit immediates. |
|
| 116 | + | if offset + base as u64 > 0x7fffffff { |
|
| 117 | + | throw error("native data address exceeds capacity"); |
|
| 118 | + | } |
|
| 111 | 119 | } |
|
| 112 | 120 | } |
|
| 113 | 121 | } |
|
| 114 | 122 | } |
|
| 115 | 123 |
| 182 | 190 | if let previous = dict::get(&generator.e.labels.funcs, "::default") { |
|
| 183 | 191 | if previous <> entryOffset { throw error("conflicting startup entry symbol"); } |
|
| 184 | 192 | } |
|
| 185 | 193 | emit::recordFuncOffsetAt(&mut generator.e, "::default", entryOffset as u32 / rv64::INSTR_SIZE as u32); |
|
| 186 | 194 | try link(&generator, &image.program); |
|
| 187 | - | try layout(&image.program, prefix); |
|
| 195 | + | try layout(image.program.data, prefix); |
|
| 188 | 196 | let result = rv64::finishProgram(&mut generator, image.program.data, |
|
| 189 | 197 | rv64::Storage { dataSyms: &mut DATA_SYMBOLS[..], dataSymEntries: &mut DATA_INDEX[..] }, |
|
| 190 | 198 | &ASM_PREFIX[..prefix], &mut RO_DATA[..], &mut RW_DATA[..]); |
|
| 191 | 199 | let code = @sliceOf(result.code.ptr as *u8, result.code.len * rv64::INSTR_SIZE as u32); |
|
| 192 | 200 | let header = rv64::imageHeader(code.len, result.roDataSize, result.rwDataSize); |
compiler/radiance/images.rad
added
+164 -0
| 1 | + | //! Binary RIL image catalogs and native image append through the shared backend. |
|
| 2 | + | ||
| 3 | + | mod graph; |
|
| 4 | + | mod native; |
|
| 5 | + | ||
| 6 | + | use std::mem; |
|
| 7 | + | use std::fmt; |
|
| 8 | + | use std::sys::unix; |
|
| 9 | + | use std::collections::dict; |
|
| 10 | + | use std::lang::alloc; |
|
| 11 | + | use std::lang::strings; |
|
| 12 | + | use std::lang::il; |
|
| 13 | + | use std::arch::rv64; |
|
| 14 | + | ||
| 15 | + | /// Maximum input bytes, plus a sentinel byte to detect oversized files. |
|
| 16 | + | constant MAX_IMAGE: u32 = 16 * 1024 * 1024; |
|
| 17 | + | /// Catalog order is image identity; image zero is the boot root. |
|
| 18 | + | constant MAX_IMAGES: u32 = 64; |
|
| 19 | + | /// Bounded reusable binary input, borrowed only until the next image is loaded. |
|
| 20 | + | static INPUT: [u8; MAX_IMAGE + 1] = undefined; |
|
| 21 | + | /// Decoding names never escape into the caller's persistent interning pool. |
|
| 22 | + | static DECODE_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 }; |
|
| 23 | + | /// Catalog-only graph storage, reclaimed after every image's placement is known. |
|
| 24 | + | static CATALOG_ARENA: [u8; 48 * 1024 * 1024] = undefined; |
|
| 25 | + | /// Generated trusted Radiance source, independent of decode scratch storage. |
|
| 26 | + | static SOURCE: [u8; 256 * 1024] = undefined; |
|
| 27 | + | ||
| 28 | + | /// Bounded source writer for the small trusted catalog module. |
|
| 29 | + | record Text: Copy { bytes: *mut [u8], len: u32 } |
|
| 30 | + | ||
| 31 | + | /// Append static text without exposing allocator or formatting assertions. |
|
| 32 | + | fn put(text: *mut Text, bytes: *[u8]) throws (il::binary::Error) { |
|
| 33 | + | if bytes.len > text.bytes.len - text.len { throw il::binary::error(0, "image catalog source exceeds capacity"); } |
|
| 34 | + | try! mem::copy(&mut text.bytes[text.len..], bytes); |
|
| 35 | + | set text.len += bytes.len; |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | /// Append one decimal constant or trusted helper-name suffix. |
|
| 39 | + | fn number(text: *mut Text, value: u32) throws (il::binary::Error) { |
|
| 40 | + | let mut digits: [u8; 10] = undefined; |
|
| 41 | + | try put(text, fmt::formatU32(value, &mut digits[..])); |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | /// Read only one bounded input; decoder-owned names remain local to this image. |
|
| 45 | + | fn load(path: *[u8], arena: *mut alloc::Arena) -> il::binary::Image throws (il::binary::Error) { |
|
| 46 | + | for i in 0..DECODE_POOL.table.len { set DECODE_POOL.table[i] = ""; } |
|
| 47 | + | set DECODE_POOL.count = 0; |
|
| 48 | + | let source = unix::readFile(path, &mut INPUT[..]) else { throw il::binary::error(0, "cannot read binary image"); }; |
|
| 49 | + | if source.len > MAX_IMAGE { throw il::binary::error(0, "binary image exceeds capacity"); } |
|
| 50 | + | return try il::binary::decode(source, arena, &mut DECODE_POOL); |
|
| 51 | + | } |
|
| 52 | + | ||
| 53 | + | /// Require a real root and bound IDs before catalog arrays or name generation. |
|
| 54 | + | fn checkPaths(paths: *[*[u8]]) throws (il::binary::Error) { |
|
| 55 | + | if paths.len == 0 { throw il::binary::error(0, "image catalog requires a root image"); } |
|
| 56 | + | if paths.len > MAX_IMAGES { throw il::binary::error(0, "too many catalog images"); } |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | /// Write the trusted source catalog. The caller pool is deliberately not populated |
|
| 60 | + | /// with names borrowing the temporary binary input; only numeric metadata escapes. |
|
| 61 | + | export fn catalog(paths: *[*[u8]], output: *[u8], pool: *mut strings::Pool) throws (il::binary::Error) { |
|
| 62 | + | try checkPaths(paths); |
|
| 63 | + | let _ = pool; |
|
| 64 | + | let mut sizes: [u32; MAX_IMAGES] = undefined; |
|
| 65 | + | let mut alignments: [u32; MAX_IMAGES] = undefined; |
|
| 66 | + | let mut arena = alloc::new(&mut CATALOG_ARENA[..]); |
|
| 67 | + | for path, id in paths { |
|
| 68 | + | let image = try load(path, &mut arena); |
|
| 69 | + | let plan = try graph::plan(&image, &mut arena); |
|
| 70 | + | set sizes[id] = plan.size; |
|
| 71 | + | set alignments[id] = plan.alignment; |
|
| 72 | + | alloc::reset(&mut arena); |
|
| 73 | + | } |
|
| 74 | + | let mut text = Text { bytes: &mut SOURCE[..], len: 0 }; |
|
| 75 | + | try put(&mut text, "//! Trusted binary image catalog. Catalog order is image identity.\n\n"); |
|
| 76 | + | for id in 0..paths.len { |
|
| 77 | + | try put(&mut text, "fn entry_"); try number(&mut text, id); try put(&mut text, "() -> u64;\n"); |
|
| 78 | + | try put(&mut text, "fn initial_"); try number(&mut text, id); try put(&mut text, "() -> u64;\n"); |
|
| 79 | + | try put(&mut text, "fn relocate_"); try number(&mut text, id); try put(&mut text, "(base: u64);\n"); |
|
| 80 | + | } |
|
| 81 | + | try put(&mut text, "\n/// Number of immutable image descriptors.\nexport constant COUNT: u32 = "); |
|
| 82 | + | try number(&mut text, paths.len); |
|
| 83 | + | try put(&mut text, ";\n/// The first command-line image is the boot root.\nexport constant ROOT: u32 = 0;\n\n"); |
|
| 84 | + | try put(&mut text, "/// Shared code and initializer with separately allocated private state.\nexport record Descriptor: Copy {\n"); |
|
| 85 | + | try put(&mut text, " /// Native entry accepting the context Env pointer.\n entry: u64,\n"); |
|
| 86 | + | try put(&mut text, " /// Shared read-only bytes copied into each domain.\n initial: u64,\n"); |
|
| 87 | + | try put(&mut text, " /// Bytes required by private image state.\n stateSize: u32,\n"); |
|
| 88 | + | try put(&mut text, " /// Power-of-two alignment of the private allocation.\n stateAlignment: u32,\n"); |
|
| 89 | + | try put(&mut text, " /// Fix private pointers after copying initial bytes to base.\n relocate: fn(u64),\n}\n\n"); |
|
| 90 | + | try put(&mut text, "/// Return linked addresses; out-of-range indices are kernel bugs.\nexport fn get(index: u32) -> Descriptor {\n assert index < COUNT;\n"); |
|
| 91 | + | for id in 0..paths.len { |
|
| 92 | + | if id + 1 < paths.len { try put(&mut text, " if index == "); try number(&mut text, id); try put(&mut text, " {\n"); } |
|
| 93 | + | let indent = " " if id + 1 < paths.len else " "; |
|
| 94 | + | try put(&mut text, indent); try put(&mut text, "return Descriptor {\n"); |
|
| 95 | + | try put(&mut text, indent); try put(&mut text, " entry: entry_"); try number(&mut text, id); try put(&mut text, "(),\n"); |
|
| 96 | + | try put(&mut text, indent); try put(&mut text, " initial: initial_"); try number(&mut text, id); try put(&mut text, "(),\n"); |
|
| 97 | + | try put(&mut text, indent); try put(&mut text, " stateSize: "); try number(&mut text, sizes[id]); try put(&mut text, ",\n"); |
|
| 98 | + | try put(&mut text, indent); try put(&mut text, " stateAlignment: "); try number(&mut text, alignments[id]); try put(&mut text, ",\n"); |
|
| 99 | + | try put(&mut text, indent); try put(&mut text, " relocate: relocate_"); try number(&mut text, id); try put(&mut text, ",\n"); |
|
| 100 | + | try put(&mut text, indent); try put(&mut text, "};\n"); |
|
| 101 | + | if id + 1 < paths.len { try put(&mut text, " }\n"); } |
|
| 102 | + | } |
|
| 103 | + | try put(&mut text, "}\n"); |
|
| 104 | + | if not unix::writeFile(output, &SOURCE[..text.len]) { throw il::binary::error(0, "cannot write image catalog source"); } |
|
| 105 | + | } |
|
| 106 | + | ||
| 107 | + | /// Append one decoded graph. All stored data, names, and pending relocations are |
|
| 108 | + | /// copied into the persistent arena before scratch or input bytes are reused. |
|
| 109 | + | fn appendImage(generator: *mut rv64::Generator, path: *[u8], id: u32, pool: *mut strings::Pool, |
|
| 110 | + | arena: *mut alloc::Arena, scratch: *mut alloc::Arena, dataItems: *mut *mut [il::Data]) throws (il::binary::Error) { |
|
| 111 | + | let image = try load(path, scratch); |
|
| 112 | + | let plan = try graph::plan(&image, scratch); |
|
| 113 | + | for function in image.program.fns { |
|
| 114 | + | if function.isExtern and dict::get(&generator.e.labels.funcs, function.name) == nil { |
|
| 115 | + | throw il::binary::error(0, "unresolved image sys native declaration"); |
|
| 116 | + | } |
|
| 117 | + | } |
|
| 118 | + | let names = try graph::names(&image.program, id, pool, arena); |
|
| 119 | + | let stateName = try graph::name("images::state_", id, "", pool, arena); |
|
| 120 | + | let initial = try native::initializer(&plan, &names, stateName, arena, scratch); |
|
| 121 | + | if (*dataItems).len as u64 + initial.items.len as u64 > 8192 { |
|
| 122 | + | throw il::binary::error(0, "native data symbol capacity exceeded"); |
|
| 123 | + | } |
|
| 124 | + | let mut definitions = try il::binary::dictionary(scratch, (*dataItems).len + initial.items.len, 0); |
|
| 125 | + | for item in *dataItems { dict::insert(&mut definitions, item.name, 0); } |
|
| 126 | + | for item in initial.items { |
|
| 127 | + | if dict::get(&definitions, item.name) <> nil { throw il::binary::error(0, "duplicate native image data symbol"); } |
|
| 128 | + | dict::insert(&mut definitions, item.name, 0); |
|
| 129 | + | } |
|
| 130 | + | try native::appendData(dataItems, initial.items, arena); |
|
| 131 | + | let mut offsets = try il::binary::dictionary(scratch, plan.order.len, 0); |
|
| 132 | + | for item, i in plan.items { |
|
| 133 | + | if not item.readOnly { dict::insert(&mut offsets, names.data[i], plan.offsets[i] as i32); } |
|
| 134 | + | } |
|
| 135 | + | set generator.e.instanceData = &offsets; |
|
| 136 | + | for function, i in image.program.fns { |
|
| 137 | + | if function.isExtern { continue; } |
|
| 138 | + | let renamed = try graph::function(function, i, &plan, &names, scratch); |
|
| 139 | + | try native::function(generator, &renamed, scratch); |
|
| 140 | + | } |
|
| 141 | + | set generator.e.instanceData = nil; |
|
| 142 | + | try native::helpers(generator, id, &plan, &names, &initial, pool, arena); |
|
| 143 | + | } |
|
| 144 | + | ||
| 145 | + | /// Append native images to the kernel's generator and persistent growable data. |
|
| 146 | + | /// The native sys assembly must already be present in the global symbol table. |
|
| 147 | + | /// Caller-owned function scratch is restored after each fully emitted image; |
|
| 148 | + | /// persistent data and relocation names remain valid until finishProgram. |
|
| 149 | + | export fn append(generator: *mut rv64::Generator, paths: *[*[u8]], pool: *mut strings::Pool, |
|
| 150 | + | arena: *mut alloc::Arena, fnArena: *mut alloc::Arena, dataItems: *mut *mut [il::Data]) throws (il::binary::Error) { |
|
| 151 | + | if paths.len == 0 { return; } |
|
| 152 | + | try checkPaths(paths); |
|
| 153 | + | let saved = alloc::save(fnArena); |
|
| 154 | + | let previous = generator.e.instanceData; |
|
| 155 | + | for path, id in paths { |
|
| 156 | + | try appendImage(generator, path, id, pool, arena, fnArena, dataItems) catch error { |
|
| 157 | + | set generator.e.instanceData = previous; |
|
| 158 | + | alloc::restore(fnArena, saved); |
|
| 159 | + | throw error; |
|
| 160 | + | }; |
|
| 161 | + | set generator.e.instanceData = previous; |
|
| 162 | + | alloc::restore(fnArena, saved); |
|
| 163 | + | } |
|
| 164 | + | } |
compiler/radiance/images/graph.rad
added
+312 -0
| 1 | + | //! Closed binary image graphs, private-state placement, and symbol rewriting. |
|
| 2 | + | ||
| 3 | + | use std::mem; |
|
| 4 | + | use std::fmt; |
|
| 5 | + | use std::collections::dict; |
|
| 6 | + | use std::lang::alloc; |
|
| 7 | + | use std::lang::strings; |
|
| 8 | + | use std::lang::il; |
|
| 9 | + | use std::lang::gen::data; |
|
| 10 | + | ||
| 11 | + | /// Maximum bytes in an image's private state or one native data section. |
|
| 12 | + | export constant MAX_DATA: u32 = 4 * 1024 * 1024; |
|
| 13 | + | ||
| 14 | + | /// One reverse initializer edge used to promote address-bearing constants. |
|
| 15 | + | record Edge: Copy { |
|
| 16 | + | /// Declaration containing the pointer initializer. |
|
| 17 | + | owner: u32, |
|
| 18 | + | /// Next reverse edge for the same target. |
|
| 19 | + | next: ?u32, |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | /// Shared placement facts used by catalog generation and native emission. |
|
| 23 | + | export record Plan: Copy { |
|
| 24 | + | /// Original data declaration indexes. |
|
| 25 | + | dataIndex: dict::Dict, |
|
| 26 | + | /// Original function declaration indexes. |
|
| 27 | + | fnIndex: dict::Dict, |
|
| 28 | + | /// Data declarations classified for the ordinary data layout routine. |
|
| 29 | + | items: *mut [il::Data], |
|
| 30 | + | /// Private byte offsets, indexed by the original data declaration. |
|
| 31 | + | offsets: *mut [u32], |
|
| 32 | + | /// Native layout order of the private declarations. |
|
| 33 | + | order: *[data::DataSym], |
|
| 34 | + | /// Number of private bytes. |
|
| 35 | + | size: u32, |
|
| 36 | + | /// Required private allocation alignment. |
|
| 37 | + | alignment: u32, |
|
| 38 | + | /// Entry function declaration index. |
|
| 39 | + | entry: u32, |
|
| 40 | + | /// The entry returns a status word. |
|
| 41 | + | returnsStatus: bool, |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | /// Persistent names corresponding to the declaration indexes in Plan. |
|
| 45 | + | export record Names: Copy { |
|
| 46 | + | /// Namespaced data declaration names. |
|
| 47 | + | data: *[*[u8]], |
|
| 48 | + | /// Namespaced function names and permitted native external names. |
|
| 49 | + | functions: *[*[u8]], |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | /// Allocate through the binary codec's checked public arena interface. |
|
| 53 | + | export fn storage(arena: *mut alloc::Arena, size: u32, alignment: u32, count: u32) -> *mut [opaque] throws (il::binary::Error) { |
|
| 54 | + | return try il::binary::storage(arena, size, alignment, count, 0); |
|
| 55 | + | } |
|
| 56 | + | ||
| 57 | + | /// Copy bytes whose lifetime would otherwise end with the decoded image. |
|
| 58 | + | export fn copy(arena: *mut alloc::Arena, bytes: *[u8]) -> *[u8] throws (il::binary::Error) { |
|
| 59 | + | let result = try storage(arena, 1, 1, bytes.len) as *mut [u8]; |
|
| 60 | + | try! mem::copy(result, bytes); |
|
| 61 | + | return result; |
|
| 62 | + | } |
|
| 63 | + | ||
| 64 | + | /// Intern persistent names without overflowing the caller's shared pool. |
|
| 65 | + | fn intern(pool: *mut strings::Pool, arena: *mut alloc::Arena, bytes: *[u8]) -> *[u8] throws (il::binary::Error) { |
|
| 66 | + | if let existing = strings::find(pool, bytes) { return existing; } |
|
| 67 | + | if pool.count >= pool.table.len / 2 { throw il::binary::error(0, "image string pool exhausted"); } |
|
| 68 | + | return strings::intern(pool, try copy(arena, bytes)); |
|
| 69 | + | } |
|
| 70 | + | ||
| 71 | + | /// Construct a trusted helper or namespace name with a decimal image ID. |
|
| 72 | + | export fn name(prefix: *[u8], id: u32, suffix: *[u8], pool: *mut strings::Pool, arena: *mut alloc::Arena) -> *[u8] throws (il::binary::Error) { |
|
| 73 | + | let mut digits: [u8; 10] = undefined; |
|
| 74 | + | let number = fmt::formatU32(id, &mut digits[..]); |
|
| 75 | + | let size = prefix.len as u64 + number.len as u64 + suffix.len as u64; |
|
| 76 | + | if size > 0x7FFFFFFF { throw il::binary::error(0, "image symbol is too long"); } |
|
| 77 | + | let saved = alloc::save(arena); |
|
| 78 | + | let bytes = try storage(arena, 1, 1, size as u32) as *mut [u8]; |
|
| 79 | + | let mut pos = try! mem::copy(bytes, prefix); |
|
| 80 | + | set pos += try! mem::copy(&mut bytes[pos..], number); |
|
| 81 | + | try! mem::copy(&mut bytes[pos..], suffix); |
|
| 82 | + | if let existing = strings::find(pool, bytes) { |
|
| 83 | + | alloc::restore(arena, saved); |
|
| 84 | + | return existing; |
|
| 85 | + | } |
|
| 86 | + | if pool.count >= pool.table.len / 2 { throw il::binary::error(0, "image string pool exhausted"); } |
|
| 87 | + | return strings::intern(pool, bytes); |
|
| 88 | + | } |
|
| 89 | + | ||
| 90 | + | /// Decode already established declaration references into an index. |
|
| 91 | + | export fn index(map: *dict::Dict, symbol: *[u8]) -> u32 throws (il::binary::Error) { |
|
| 92 | + | let value = dict::get(map, symbol) else { throw il::binary::error(0, "unresolved image graph reference"); }; |
|
| 93 | + | return value as u32; |
|
| 94 | + | } |
|
| 95 | + | ||
| 96 | + | /// Width of one repetition, matching the shared data emitter. |
|
| 97 | + | export fn width(item: il::DataItem) -> u32 { |
|
| 98 | + | match item { |
|
| 99 | + | case il::DataItem::Val { typ, .. } => return il::typeSize(typ), |
|
| 100 | + | case il::DataItem::Sym(_), il::DataItem::Fn(_) => return 8, |
|
| 101 | + | case il::DataItem::Str(bytes) => return bytes.len, |
|
| 102 | + | case il::DataItem::Undef => return 1, |
|
| 103 | + | } |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | /// Classify transitive private pointers, then use the ordinary data layout order. |
|
| 107 | + | export fn plan(image: *il::binary::Image, arena: *mut alloc::Arena) -> Plan throws (il::binary::Error) { |
|
| 108 | + | let program = &image.program; |
|
| 109 | + | if program.data.len >= data::MAX_DATA_SYMS { throw il::binary::error(0, "too many image data symbols"); } |
|
| 110 | + | if program.fns.len > 8192 { throw il::binary::error(0, "too many image functions"); } |
|
| 111 | + | let mut dataIndex = try il::binary::dictionary(arena, program.data.len, 0); |
|
| 112 | + | let mut fnIndex = try il::binary::dictionary(arena, program.fns.len, 0); |
|
| 113 | + | let items = try storage(arena, @sizeOf(il::Data), @alignOf(il::Data), program.data.len) as *mut [il::Data]; |
|
| 114 | + | let offsets = try storage(arena, @sizeOf(u32), @alignOf(u32), items.len) as *mut [u32]; |
|
| 115 | + | let heads = try storage(arena, @sizeOf(?u32), @alignOf(?u32), items.len) as *mut [?u32]; |
|
| 116 | + | let queue = try storage(arena, @sizeOf(u32), @alignOf(u32), items.len) as *mut [u32]; |
|
| 117 | + | let mut queued: u32 = 0; |
|
| 118 | + | let mut edgeCount: u64 = 0; |
|
| 119 | + | for item, i in program.data { |
|
| 120 | + | dict::insert(&mut dataIndex, item.name, i as i32); |
|
| 121 | + | set items[i] = item; |
|
| 122 | + | set offsets[i] = 0; |
|
| 123 | + | set heads[i] = nil; |
|
| 124 | + | if not item.readOnly { set queue[queued] = i; set queued += 1; } |
|
| 125 | + | for value in item.values { |
|
| 126 | + | if value.count == 0 { continue; } |
|
| 127 | + | if let case il::DataItem::Sym(_) = value.item { set edgeCount += 1; } |
|
| 128 | + | } |
|
| 129 | + | } |
|
| 130 | + | for function, i in program.fns { |
|
| 131 | + | dict::insert(&mut fnIndex, function.name, i as i32); |
|
| 132 | + | if function.isExtern and (function.name.len <= 11 or not mem::eq(&function.name[..11], "user::sys::")) { |
|
| 133 | + | throw il::binary::error(0, "image extern must be a declared sys native function"); |
|
| 134 | + | } |
|
| 135 | + | } |
|
| 136 | + | if edgeCount > 0x7FFFFFFF { throw il::binary::error(0, "too many image initializer references"); } |
|
| 137 | + | let edges = try storage(arena, @sizeOf(Edge), @alignOf(Edge), edgeCount as u32) as *mut [Edge]; |
|
| 138 | + | let mut edgeIndex: u32 = 0; |
|
| 139 | + | for item, i in items { |
|
| 140 | + | for value in item.values { |
|
| 141 | + | if value.count == 0 { continue; } |
|
| 142 | + | match value.item { |
|
| 143 | + | case il::DataItem::Sym(symbol) => { |
|
| 144 | + | let target = try index(&dataIndex, symbol); |
|
| 145 | + | set edges[edgeIndex] = Edge { owner: i, next: heads[target] }; |
|
| 146 | + | set heads[target] = edgeIndex; |
|
| 147 | + | set edgeIndex += 1; |
|
| 148 | + | } |
|
| 149 | + | case il::DataItem::Fn(symbol) => { let _ = try index(&fnIndex, symbol); } |
|
| 150 | + | else => {}, |
|
| 151 | + | } |
|
| 152 | + | } |
|
| 153 | + | } |
|
| 154 | + | let mut cursor: u32 = 0; |
|
| 155 | + | while cursor < queued { |
|
| 156 | + | let mut next = heads[queue[cursor]]; |
|
| 157 | + | while let e = next { |
|
| 158 | + | let edge = edges[e]; |
|
| 159 | + | if items[edge.owner].readOnly { |
|
| 160 | + | set items[edge.owner].readOnly = false; |
|
| 161 | + | set queue[queued] = edge.owner; |
|
| 162 | + | set queued += 1; |
|
| 163 | + | } |
|
| 164 | + | set next = edge.next; |
|
| 165 | + | } |
|
| 166 | + | set cursor += 1; |
|
| 167 | + | } |
|
| 168 | + | let mut alignment: u32 = 8; |
|
| 169 | + | let mut expected: u64 = 0; |
|
| 170 | + | for pass in 0..2 { |
|
| 171 | + | for item in items { |
|
| 172 | + | if item.readOnly or item.isZeroInit <> (pass == 1) { continue; } |
|
| 173 | + | let a = item.alignment as u64; |
|
| 174 | + | if a == 0 or a & (a - 1) <> 0 or a > MAX_DATA as u64 { |
|
| 175 | + | throw il::binary::error(0, "image state alignment exceeds capacity"); |
|
| 176 | + | } |
|
| 177 | + | if item.alignment > alignment { set alignment = item.alignment; } |
|
| 178 | + | set expected = ((expected + a - 1) & ~(a - 1)) + item.size as u64; |
|
| 179 | + | if expected > MAX_DATA as u64 { throw il::binary::error(0, "image private state exceeds capacity"); } |
|
| 180 | + | } |
|
| 181 | + | } |
|
| 182 | + | let symbols = try storage(arena, @sizeOf(data::DataSym), @alignOf(data::DataSym), queued) as *mut [data::DataSym]; |
|
| 183 | + | let mut symbolCount: u32 = 0; |
|
| 184 | + | let size = data::layoutSection(items, symbols, &mut symbolCount, 0, false); |
|
| 185 | + | for symbol in symbols { set offsets[try index(&dataIndex, symbol.name)] = symbol.addr; } |
|
| 186 | + | let entryName = image.entry else { throw il::binary::error(0, "image has no entry function"); }; |
|
| 187 | + | let entry = try index(&fnIndex, entryName); |
|
| 188 | + | let function = program.fns[entry]; |
|
| 189 | + | if function.isExtern or function.params.len <> 1 or il::typeSize(function.params[0].type) <> 8 { |
|
| 190 | + | throw il::binary::error(0, "image entry must receive one Env pointer"); |
|
| 191 | + | } |
|
| 192 | + | let mut returnsStatus = false; |
|
| 193 | + | let mut returnsVoid = false; |
|
| 194 | + | for block in function.blocks { |
|
| 195 | + | for instruction in block.instrs { |
|
| 196 | + | if let case il::Instr::Ret { val } = instruction { |
|
| 197 | + | if let value = val { |
|
| 198 | + | match value { |
|
| 199 | + | case il::Val::Undef => set returnsVoid = true, |
|
| 200 | + | else => set returnsStatus = true, |
|
| 201 | + | } |
|
| 202 | + | } else { set returnsVoid = true; } |
|
| 203 | + | } |
|
| 204 | + | } |
|
| 205 | + | } |
|
| 206 | + | if returnsStatus and (returnsVoid or il::typeSize(function.returnType) <> 4) { |
|
| 207 | + | throw il::binary::error(0, "image entry must return void or u32"); |
|
| 208 | + | } |
|
| 209 | + | return Plan { dataIndex, fnIndex, items, offsets, order: symbols, size, alignment, entry, returnsStatus }; |
|
| 210 | + | } |
|
| 211 | + | ||
| 212 | + | /// Allocate all names once; native externs alone retain their original spelling. |
|
| 213 | + | export fn names(program: *il::Program, id: u32, pool: *mut strings::Pool, arena: *mut alloc::Arena) -> Names throws (il::binary::Error) { |
|
| 214 | + | let dataNames = try storage(arena, @sizeOf(*[u8]), @alignOf(*[u8]), program.data.len) as *mut [*[u8]]; |
|
| 215 | + | let fnNames = try storage(arena, @sizeOf(*[u8]), @alignOf(*[u8]), program.fns.len) as *mut [*[u8]]; |
|
| 216 | + | let prefix = try name("images::image_", id, "::", pool, arena); |
|
| 217 | + | for item, i in program.data { set dataNames[i] = try qualified(prefix, item.name, pool, arena); } |
|
| 218 | + | for function, i in program.fns { |
|
| 219 | + | set fnNames[i] = try intern(pool, arena, function.name) if function.isExtern |
|
| 220 | + | else try qualified(prefix, function.name, pool, arena); |
|
| 221 | + | } |
|
| 222 | + | return Names { data: dataNames, functions: fnNames }; |
|
| 223 | + | } |
|
| 224 | + | ||
| 225 | + | /// Prefix an original whole-graph name, retaining its complete qualified path. |
|
| 226 | + | fn qualified(prefix: *[u8], suffix: *[u8], pool: *mut strings::Pool, arena: *mut alloc::Arena) -> *[u8] throws (il::binary::Error) { |
|
| 227 | + | let size = prefix.len as u64 + suffix.len as u64; |
|
| 228 | + | if size > 0x7FFFFFFF { throw il::binary::error(0, "image symbol is too long"); } |
|
| 229 | + | let saved = alloc::save(arena); |
|
| 230 | + | let bytes = try storage(arena, 1, 1, size as u32) as *mut [u8]; |
|
| 231 | + | let pos = try! mem::copy(bytes, prefix); |
|
| 232 | + | try! mem::copy(&mut bytes[pos..], suffix); |
|
| 233 | + | if let existing = strings::find(pool, bytes) { alloc::restore(arena, saved); return existing; } |
|
| 234 | + | if pool.count >= pool.table.len / 2 { throw il::binary::error(0, "image string pool exhausted"); } |
|
| 235 | + | return strings::intern(pool, bytes); |
|
| 236 | + | } |
|
| 237 | + | ||
| 238 | + | /// Rewrite a typed symbol reference, rejecting unresolved graph references. |
|
| 239 | + | fn val(value: il::Val, plan: *Plan, names: *Names) -> il::Val throws (il::binary::Error) { |
|
| 240 | + | match value { |
|
| 241 | + | case il::Val::DataSym(symbol) => return il::Val::DataSym(names.data[try index(&plan.dataIndex, symbol)]), |
|
| 242 | + | case il::Val::FnAddr(symbol) => return il::Val::FnAddr(names.functions[try index(&plan.fnIndex, symbol)]), |
|
| 243 | + | else => return value, |
|
| 244 | + | } |
|
| 245 | + | } |
|
| 246 | + | ||
| 247 | + | /// Rewrite mutable block-edge arguments in their decoder-owned storage. |
|
| 248 | + | fn args(values: *mut [il::Val], plan: *Plan, names: *Names) throws (il::binary::Error) { |
|
| 249 | + | for value, i in values { set values[i] = try val(value, plan, names); } |
|
| 250 | + | } |
|
| 251 | + | ||
| 252 | + | /// Copy immutable call arguments only when they contain a symbol to rename. |
|
| 253 | + | fn callArgs(values: *[il::Val], plan: *Plan, names: *Names, arena: *mut alloc::Arena) -> *[il::Val] throws (il::binary::Error) { |
|
| 254 | + | for value in values { |
|
| 255 | + | match value { |
|
| 256 | + | case il::Val::DataSym(_), il::Val::FnAddr(_) => { |
|
| 257 | + | let copied = try storage(arena, @sizeOf(il::Val), @alignOf(il::Val), values.len) as *mut [il::Val]; |
|
| 258 | + | for argument, i in values { set copied[i] = try val(argument, plan, names); } |
|
| 259 | + | return copied; |
|
| 260 | + | }, |
|
| 261 | + | else => {}, |
|
| 262 | + | } |
|
| 263 | + | } |
|
| 264 | + | return values; |
|
| 265 | + | } |
|
| 266 | + | ||
| 267 | + | /// Rewrite every symbolic operand without reconstructing any source-language types. |
|
| 268 | + | fn instruction(item: il::Instr, plan: *Plan, names: *Names, arena: *mut alloc::Arena) -> il::Instr throws (il::binary::Error) { |
|
| 269 | + | match item { |
|
| 270 | + | case il::Instr::Reserve { dst, size, alignment } => return il::Instr::Reserve { dst, size: try val(size, plan, names), alignment }, |
|
| 271 | + | case il::Instr::Store { typ, src, dst, offset } => return il::Instr::Store { typ, src: try val(src, plan, names), dst, offset }, |
|
| 272 | + | case il::Instr::Blit { dst, src, size, alignment } => return il::Instr::Blit { dst, src, size: try val(size, plan, names), alignment }, |
|
| 273 | + | case il::Instr::Copy { dst, val: value } => return il::Instr::Copy { dst, val: try val(value, plan, names) }, |
|
| 274 | + | case il::Instr::BinOp { op, typ, dst, a, b } => return il::Instr::BinOp { op, typ, dst, a: try val(a, plan, names), b: try val(b, plan, names) }, |
|
| 275 | + | case il::Instr::UnOp { op, typ, dst, a } => return il::Instr::UnOp { op, typ, dst, a: try val(a, plan, names) }, |
|
| 276 | + | case il::Instr::Zext { typ, dst, val: value } => return il::Instr::Zext { typ, dst, val: try val(value, plan, names) }, |
|
| 277 | + | case il::Instr::Sext { typ, dst, val: value } => return il::Instr::Sext { typ, dst, val: try val(value, plan, names) }, |
|
| 278 | + | case il::Instr::Call { retTy, dst, func, args: values } => { |
|
| 279 | + | return il::Instr::Call { retTy, dst, func: try val(func, plan, names), args: try callArgs(values, plan, names, arena) }; |
|
| 280 | + | } |
|
| 281 | + | case il::Instr::Ret { val: value } => { |
|
| 282 | + | if let v = value { return il::Instr::Ret { val: try val(v, plan, names) }; } |
|
| 283 | + | return item; |
|
| 284 | + | } |
|
| 285 | + | case il::Instr::Jmp { args: values, .. } => { try args(values, plan, names); return item; } |
|
| 286 | + | case il::Instr::Br { op, typ, a, b, thenTarget, thenArgs, elseTarget, elseArgs } => { |
|
| 287 | + | try args(thenArgs, plan, names); try args(elseArgs, plan, names); |
|
| 288 | + | return il::Instr::Br { op, typ, a: try val(a, plan, names), b: try val(b, plan, names), thenTarget, thenArgs, elseTarget, elseArgs }; |
|
| 289 | + | } |
|
| 290 | + | case il::Instr::Switch { val: value, defaultTarget, defaultArgs, cases } => { |
|
| 291 | + | try args(defaultArgs, plan, names); |
|
| 292 | + | for c in cases { try args(c.args, plan, names); } |
|
| 293 | + | return il::Instr::Switch { val: try val(value, plan, names), defaultTarget, defaultArgs, cases }; |
|
| 294 | + | } |
|
| 295 | + | case il::Instr::Ecall { dst, num, a0, a1, a2, a3 } => return il::Instr::Ecall { |
|
| 296 | + | dst, num: try val(num, plan, names), a0: try val(a0, plan, names), a1: try val(a1, plan, names), |
|
| 297 | + | a2: try val(a2, plan, names), a3: try val(a3, plan, names), |
|
| 298 | + | }, |
|
| 299 | + | case il::Instr::Load { .. }, il::Instr::Sload { .. }, il::Instr::Unreachable, |
|
| 300 | + | il::Instr::Ebreak, il::Instr::MemoryFence => return item, |
|
| 301 | + | } |
|
| 302 | + | } |
|
| 303 | + | ||
| 304 | + | /// Rename a decoder-owned body in place; its storage survives until emission ends. |
|
| 305 | + | export fn function(function: *il::Fn, index: u32, plan: *Plan, names: *Names, arena: *mut alloc::Arena) -> il::Fn throws (il::binary::Error) { |
|
| 306 | + | let mut result = *function; |
|
| 307 | + | set result.name = names.functions[index]; |
|
| 308 | + | for block in result.blocks { |
|
| 309 | + | for item, i in block.instrs { set block.instrs[i] = try instruction(item, plan, names, arena); } |
|
| 310 | + | } |
|
| 311 | + | return result; |
|
| 312 | + | } |
compiler/radiance/images/native.rad
added
+343 -0
| 1 | + | //! Shared-backend image code, read-only initializers, and trusted linker glue. |
|
| 2 | + | ||
| 3 | + | use std::collections::dict; |
|
| 4 | + | use std::lang::alloc; |
|
| 5 | + | use std::lang::strings; |
|
| 6 | + | use std::lang::il; |
|
| 7 | + | use std::lang::gen::data; |
|
| 8 | + | use std::lang::gen::bitset; |
|
| 9 | + | use std::lang::gen::regalloc; |
|
| 10 | + | use std::arch::rv64; |
|
| 11 | + | use std::arch::rv64::emit; |
|
| 12 | + | use std::arch::rv64::encode; |
|
| 13 | + | use super::graph; |
|
| 14 | + | ||
| 15 | + | /// One repeated private pointer initializer, expressed in state-relative bytes. |
|
| 16 | + | record Fixup: Copy { |
|
| 17 | + | /// First pointer slot's private byte offset. |
|
| 18 | + | offset: u32, |
|
| 19 | + | /// Target declaration's private byte offset. |
|
| 20 | + | target: u32, |
|
| 21 | + | /// Number of consecutive eight-byte pointer slots. |
|
| 22 | + | count: u32, |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// Persistent read-only definitions and scratch relocation runs. |
|
| 26 | + | export record Initializer: Copy { |
|
| 27 | + | /// Shared initializer and immutable data definitions. |
|
| 28 | + | items: *[il::Data], |
|
| 29 | + | /// Private pointer runs applied to each new instance. |
|
| 30 | + | fixups: *[Fixup], |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | /// Retain initializer payloads and keep ordinary RO/function relocations intact. |
|
| 34 | + | fn value(value: il::DataValue, plan: *graph::Plan, names: *graph::Names, arena: *mut alloc::Arena) -> il::DataValue throws (il::binary::Error) { |
|
| 35 | + | let mut item = value.item; |
|
| 36 | + | match item { |
|
| 37 | + | case il::DataItem::Sym(symbol) => { |
|
| 38 | + | let index = try graph::index(&plan.dataIndex, symbol); |
|
| 39 | + | set item = il::DataItem::Sym(names.data[index]) if plan.items[index].readOnly |
|
| 40 | + | else il::DataItem::Val { typ: il::Type::W64, val: plan.offsets[index] as i64 }; |
|
| 41 | + | } |
|
| 42 | + | case il::DataItem::Fn(symbol) => set item = il::DataItem::Fn(names.functions[try graph::index(&plan.fnIndex, symbol)]), |
|
| 43 | + | case il::DataItem::Str(bytes) => set item = il::DataItem::Str(try graph::copy(arena, bytes)), |
|
| 44 | + | else => {}, |
|
| 45 | + | } |
|
| 46 | + | return il::DataValue { item, count: value.count }; |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | /// Flatten private declarations into one RO initializer using their native layout. |
|
| 50 | + | export fn initializer(plan: *graph::Plan, names: *graph::Names, stateName: *[u8], arena: *mut alloc::Arena, scratch: *mut alloc::Arena) -> Initializer throws (il::binary::Error) { |
|
| 51 | + | let mut shared: u32 = 0; |
|
| 52 | + | let mut count: u64 = plan.order.len as u64 * 2; |
|
| 53 | + | let mut fixupCount: u64 = 0; |
|
| 54 | + | for item in plan.items { |
|
| 55 | + | if item.readOnly { set shared += 1; continue; } |
|
| 56 | + | if item.isZeroInit { continue; } |
|
| 57 | + | set count += item.values.len as u64; |
|
| 58 | + | for v in item.values { |
|
| 59 | + | if v.count == 0 { continue; } |
|
| 60 | + | if let case il::DataItem::Sym(symbol) = v.item { |
|
| 61 | + | if not plan.items[try graph::index(&plan.dataIndex, symbol)].readOnly { set fixupCount += 1; } |
|
| 62 | + | } |
|
| 63 | + | } |
|
| 64 | + | } |
|
| 65 | + | if count > 0x7FFFFFFF or fixupCount > 0x7FFFFFFF { throw il::binary::error(0, "too many image initializer values"); } |
|
| 66 | + | let values = try graph::storage(arena, @sizeOf(il::DataValue), @alignOf(il::DataValue), count as u32) as *mut [il::DataValue]; |
|
| 67 | + | let fixups = try graph::storage(scratch, @sizeOf(Fixup), @alignOf(Fixup), fixupCount as u32) as *mut [Fixup]; |
|
| 68 | + | let items = try graph::storage(arena, @sizeOf(il::Data), @alignOf(il::Data), shared + 1) as *mut [il::Data]; |
|
| 69 | + | let mut valueCount: u32 = 0; |
|
| 70 | + | let mut fixupIndex: u32 = 0; |
|
| 71 | + | let mut offset: u32 = 0; |
|
| 72 | + | for symbol in plan.order { |
|
| 73 | + | let item = plan.items[try graph::index(&plan.dataIndex, symbol.name)]; |
|
| 74 | + | if symbol.addr > offset { |
|
| 75 | + | set values[valueCount] = il::DataValue { item: il::DataItem::Undef, count: symbol.addr - offset }; |
|
| 76 | + | set valueCount += 1; |
|
| 77 | + | set offset = symbol.addr; |
|
| 78 | + | } |
|
| 79 | + | if item.isZeroInit { |
|
| 80 | + | set values[valueCount] = il::DataValue { item: il::DataItem::Undef, count: item.size }; |
|
| 81 | + | set valueCount += 1; |
|
| 82 | + | set offset += item.size; |
|
| 83 | + | continue; |
|
| 84 | + | } |
|
| 85 | + | for v in item.values { |
|
| 86 | + | if v.count > 0 { |
|
| 87 | + | if let case il::DataItem::Sym(target) = v.item { |
|
| 88 | + | let targetIndex = try graph::index(&plan.dataIndex, target); |
|
| 89 | + | if not plan.items[targetIndex].readOnly { |
|
| 90 | + | set fixups[fixupIndex] = Fixup { offset, target: plan.offsets[targetIndex], count: v.count }; |
|
| 91 | + | set fixupIndex += 1; |
|
| 92 | + | } |
|
| 93 | + | } |
|
| 94 | + | } |
|
| 95 | + | set values[valueCount] = try value(v, plan, names, arena); |
|
| 96 | + | set valueCount += 1; |
|
| 97 | + | set offset += graph::width(v.item) * v.count; |
|
| 98 | + | } |
|
| 99 | + | } |
|
| 100 | + | set items[0] = il::Data { name: stateName, size: plan.size, alignment: plan.alignment, |
|
| 101 | + | readOnly: true, isZeroInit: false, values: &values[..valueCount] }; |
|
| 102 | + | let mut itemIndex: u32 = 1; |
|
| 103 | + | for item, i in plan.items { |
|
| 104 | + | if not item.readOnly { continue; } |
|
| 105 | + | let count = 1 if item.isZeroInit else item.values.len; |
|
| 106 | + | let copied = try graph::storage(arena, @sizeOf(il::DataValue), @alignOf(il::DataValue), count) as *mut [il::DataValue]; |
|
| 107 | + | if item.isZeroInit { set copied[0] = il::DataValue { item: il::DataItem::Undef, count: item.size }; } |
|
| 108 | + | else { for v, j in item.values { set copied[j] = try value(v, plan, names, arena); } } |
|
| 109 | + | set items[itemIndex] = il::Data { name: names.data[i], size: item.size, alignment: item.alignment, |
|
| 110 | + | readOnly: true, isZeroInit: false, values: copied }; |
|
| 111 | + | set itemIndex += 1; |
|
| 112 | + | } |
|
| 113 | + | return Initializer { items, fixups }; |
|
| 114 | + | } |
|
| 115 | + | ||
| 116 | + | /// Reserve the caller's growable list using checked allocation before any append. |
|
| 117 | + | export fn appendData(target: *mut *mut [il::Data], items: *[il::Data], arena: *mut alloc::Arena) throws (il::binary::Error) { |
|
| 118 | + | let total = (*target).len as u64 + items.len as u64; |
|
| 119 | + | if total > data::MAX_DATA_SYMS as u64 { throw il::binary::error(0, "native data symbol capacity exceeded"); } |
|
| 120 | + | let mut slice = *target; |
|
| 121 | + | if total > slice.cap as u64 { |
|
| 122 | + | let mut capacity: u32 = 16; |
|
| 123 | + | while capacity < total as u32 { set capacity *= 2; } |
|
| 124 | + | let grown = try graph::storage(arena, @sizeOf(il::Data), @alignOf(il::Data), capacity) as *mut [il::Data]; |
|
| 125 | + | for item, i in slice { set grown[i] = item; } |
|
| 126 | + | set slice = @sliceOf(grown.ptr, slice.len, capacity); |
|
| 127 | + | } |
|
| 128 | + | let allocator = alloc::arenaAllocator(arena); |
|
| 129 | + | for item in items { slice.append(item, allocator); } |
|
| 130 | + | set *target = slice; |
|
| 131 | + | } |
|
| 132 | + | ||
| 133 | + | /// Check hard backend limits and a conservative expansion bound for one function. |
|
| 134 | + | fn bounds(e: *emit::Emitter, function: *il::Fn) throws (il::binary::Error) { |
|
| 135 | + | if function.params.len > rv64::ARG_REGS.len { throw il::binary::error(0, "image function has too many arguments"); } |
|
| 136 | + | let mut blocks = function.blocks.len as u64 + 1; |
|
| 137 | + | let mut words: u64 = 256; |
|
| 138 | + | let mut reserve: u64 = 0; |
|
| 139 | + | let mut operands: u64 = 0; |
|
| 140 | + | let mut calls: u64 = 0; |
|
| 141 | + | let mut branches: u64 = 0; |
|
| 142 | + | for block in function.blocks { |
|
| 143 | + | if block.params.len > 16 { throw il::binary::error(0, "image block has too many parameters"); } |
|
| 144 | + | for item in block.instrs { |
|
| 145 | + | set words += 64; |
|
| 146 | + | set operands += 5; |
|
| 147 | + | set branches += 2; |
|
| 148 | + | match item { |
|
| 149 | + | case il::Instr::Call { args, .. } => { |
|
| 150 | + | if args.len > rv64::ARG_REGS.len { throw il::binary::error(0, "image call has too many arguments"); } |
|
| 151 | + | set words += args.len as u64 * 32; |
|
| 152 | + | set operands += args.len as u64; |
|
| 153 | + | set calls += 1; |
|
| 154 | + | } |
|
| 155 | + | case il::Instr::Jmp { args, .. } => { |
|
| 156 | + | set words += args.len as u64 * 32; |
|
| 157 | + | set operands += args.len as u64; |
|
| 158 | + | } |
|
| 159 | + | case il::Instr::Br { thenArgs, elseArgs, .. } => { |
|
| 160 | + | if thenArgs.len > 0 and elseArgs.len > 0 { throw il::binary::error(0, "image branch requires split argument edges"); } |
|
| 161 | + | set words += (thenArgs.len as u64 + elseArgs.len as u64) * 32; |
|
| 162 | + | set operands += thenArgs.len as u64 + elseArgs.len as u64; |
|
| 163 | + | } |
|
| 164 | + | case il::Instr::Switch { defaultArgs, cases, .. } => { |
|
| 165 | + | set words += defaultArgs.len as u64 * 32; |
|
| 166 | + | set operands += defaultArgs.len as u64; |
|
| 167 | + | for c in cases { |
|
| 168 | + | set words += 32 + c.args.len as u64 * 32; |
|
| 169 | + | set operands += c.args.len as u64; |
|
| 170 | + | set branches += 2; |
|
| 171 | + | if c.args.len > 0 { set blocks += 1; } |
|
| 172 | + | } |
|
| 173 | + | } |
|
| 174 | + | case il::Instr::Reserve { size, alignment, .. } => { |
|
| 175 | + | if alignment == 0 or alignment & (alignment - 1) <> 0 or alignment > graph::MAX_DATA { |
|
| 176 | + | throw il::binary::error(0, "image stack alignment exceeds capacity"); |
|
| 177 | + | } |
|
| 178 | + | match size { |
|
| 179 | + | case il::Val::Imm(bytes) => { |
|
| 180 | + | if bytes < 0 or bytes > graph::MAX_DATA as i64 { throw il::binary::error(0, "image stack reservation exceeds capacity"); } |
|
| 181 | + | let a = alignment as u64; |
|
| 182 | + | set reserve = ((reserve + a - 1) & ~(a - 1)) + bytes as u64; |
|
| 183 | + | if reserve > graph::MAX_DATA as u64 { throw il::binary::error(0, "image stack frame exceeds capacity"); } |
|
| 184 | + | } |
|
| 185 | + | case il::Val::Reg(_) => { |
|
| 186 | + | if alignment > 2048 { throw il::binary::error(0, "dynamic image stack alignment exceeds backend capacity"); } |
|
| 187 | + | } |
|
| 188 | + | else => throw il::binary::error(0, "invalid image stack reservation"), |
|
| 189 | + | } |
|
| 190 | + | } |
|
| 191 | + | case il::Instr::Blit { size, alignment, .. } => { |
|
| 192 | + | let case il::Val::Imm(bytes) = size else { throw il::binary::error(0, "image blit requires an immediate size"); }; |
|
| 193 | + | if bytes < 0 or bytes > graph::MAX_DATA as i64 or alignment == 0 or alignment & (alignment - 1) <> 0 { |
|
| 194 | + | throw il::binary::error(0, "image blit exceeds backend capacity"); |
|
| 195 | + | } |
|
| 196 | + | // A spilled base can force an unrolled copy with adjusted addresses. |
|
| 197 | + | let width = il::typeSize(il::copyType(bytes as u32, alignment)) if bytes > 0 else 1; |
|
| 198 | + | set words += (bytes as u64 / width as u64 + 8) * 12; |
|
| 199 | + | } |
|
| 200 | + | else => {}, |
|
| 201 | + | } |
|
| 202 | + | } |
|
| 203 | + | } |
|
| 204 | + | if blocks > e.labels.blockOffsets.len as u64 { throw il::binary::error(0, "image function exceeds block capacity"); } |
|
| 205 | + | // Local jumps use one JAL slot and must stay strictly inside its positive range. |
|
| 206 | + | if words >= 0x100000 / rv64::INSTR_SIZE as u64 { throw il::binary::error(0, "image function exceeds local branch reach"); } |
|
| 207 | + | if words > (e.code.len - e.codeLen) as u64 { throw il::binary::error(0, "native code capacity exceeded"); } |
|
| 208 | + | if e.labels.funcs.count >= e.labels.funcs.entries.len / 2 { throw il::binary::error(0, "native function symbol capacity exceeded"); } |
|
| 209 | + | if dict::get(&e.labels.funcs, function.name) <> nil { throw il::binary::error(0, "duplicate native image function"); } |
|
| 210 | + | if e.funcs.len >= e.funcs.cap or operands + e.pendingAddrLoads.len as u64 > e.pendingAddrLoads.cap as u64 |
|
| 211 | + | or calls + e.pendingCalls.len as u64 > e.pendingCalls.cap as u64 |
|
| 212 | + | or branches + e.pendingBranches.len as u64 > e.pendingBranches.cap as u64 { |
|
| 213 | + | throw il::binary::error(0, "native image relocation capacity exceeded"); |
|
| 214 | + | } |
|
| 215 | + | } |
|
| 216 | + | ||
| 217 | + | /// Shared liveness state used to bound the spill-candidate buffer. |
|
| 218 | + | record Pressure: Copy { |
|
| 219 | + | /// Registers live at the inspected instruction. |
|
| 220 | + | live: bitset::Bitset, |
|
| 221 | + | /// Number of live registers. |
|
| 222 | + | count: u32, |
|
| 223 | + | } |
|
| 224 | + | ||
| 225 | + | /// Add one existing IL operand to the current live register set. |
|
| 226 | + | fn liveUse(reg: il::Reg, context: *mut opaque) { |
|
| 227 | + | let pressure = context as *mut Pressure; |
|
| 228 | + | if not bitset::contains(&pressure.live, reg.n) { |
|
| 229 | + | bitset::put(&mut pressure.live, reg.n); |
|
| 230 | + | set pressure.count += 1; |
|
| 231 | + | } |
|
| 232 | + | } |
|
| 233 | + | ||
| 234 | + | /// Run the existing allocation stages, checking their fixed pressure capacity. |
|
| 235 | + | fn allocate(function: *il::Fn, arena: *mut alloc::Arena) -> regalloc::AllocResult throws (il::binary::Error) { |
|
| 236 | + | let config = rv64::targetConfig(); |
|
| 237 | + | let live = try regalloc::liveness::analyze(function, arena) |
|
| 238 | + | catch { throw il::binary::error(0, "image liveness arena exhausted"); }; |
|
| 239 | + | let saved = alloc::save(arena); |
|
| 240 | + | let words = try graph::storage(arena, @sizeOf(u32), @alignOf(u32), bitset::wordsFor(live.maxReg)) as *mut [u32]; |
|
| 241 | + | let mut pressure = Pressure { live: bitset::init(words), count: 0 }; |
|
| 242 | + | for block, b in function.blocks { |
|
| 243 | + | bitset::copy(&mut pressure.live, &live.liveOut[b]); |
|
| 244 | + | set pressure.count = bitset::count(&pressure.live); |
|
| 245 | + | if pressure.count > 256 { throw il::binary::error(0, "image exceeds live register capacity"); } |
|
| 246 | + | let mut index = block.instrs.len; |
|
| 247 | + | while index > 0 { |
|
| 248 | + | set index -= 1; |
|
| 249 | + | let item = block.instrs[index]; |
|
| 250 | + | if let dst = il::instrDst(item) { |
|
| 251 | + | if bitset::contains(&pressure.live, dst.n) { |
|
| 252 | + | bitset::clear(&mut pressure.live, dst.n); |
|
| 253 | + | set pressure.count -= 1; |
|
| 254 | + | } |
|
| 255 | + | } |
|
| 256 | + | il::forEachReg(item, liveUse, &mut pressure as *mut opaque); |
|
| 257 | + | if pressure.count > 256 { throw il::binary::error(0, "image exceeds live register capacity"); } |
|
| 258 | + | } |
|
| 259 | + | } |
|
| 260 | + | alloc::restore(arena, saved); |
|
| 261 | + | let spill = try regalloc::spill::analyze(function, &live, config.allocatable.len, config.calleeSaved.len, config.slotSize, arena) |
|
| 262 | + | catch { throw il::binary::error(0, "image spill arena exhausted"); }; |
|
| 263 | + | let assignment = try regalloc::assign::assign(function, &live, &spill, &config, arena) |
|
| 264 | + | catch { throw il::binary::error(0, "image register assignment arena exhausted"); }; |
|
| 265 | + | return regalloc::AllocResult { assignments: assignment.assignments, spill, usedCalleeSaved: assignment.usedCalleeSaved }; |
|
| 266 | + | } |
|
| 267 | + | ||
| 268 | + | /// Emit through the existing allocator and selector, propagating scratch exhaustion. |
|
| 269 | + | export fn function(generator: *mut rv64::Generator, function: *il::Fn, scratch: *mut alloc::Arena) throws (il::binary::Error) { |
|
| 270 | + | try bounds(&generator.e, function); |
|
| 271 | + | let saved = alloc::save(scratch); |
|
| 272 | + | let allocation = try allocate(function, scratch) catch error { |
|
| 273 | + | alloc::restore(scratch, saved); |
|
| 274 | + | throw error; |
|
| 275 | + | }; |
|
| 276 | + | rv64::isel::selectFn(&mut generator.e, &allocation, function); |
|
| 277 | + | alloc::restore(scratch, saved); |
|
| 278 | + | } |
|
| 279 | + | ||
| 280 | + | /// Reserve a trusted leaf helper in the same global symbol and relocation tables. |
|
| 281 | + | fn helper(e: *mut emit::Emitter, name: *[u8], words: u32) throws (il::binary::Error) { |
|
| 282 | + | if dict::get(&e.labels.funcs, name) <> nil { throw il::binary::error(0, "duplicate image helper symbol"); } |
|
| 283 | + | if e.labels.funcs.count >= e.labels.funcs.entries.len / 2 { throw il::binary::error(0, "native function symbol capacity exceeded"); } |
|
| 284 | + | if words > e.code.len - e.codeLen { throw il::binary::error(0, "native code capacity exceeded"); } |
|
| 285 | + | if e.funcs.len >= e.funcs.cap or e.pendingCalls.len == e.pendingCalls.cap |
|
| 286 | + | or e.pendingAddrLoads.len == e.pendingAddrLoads.cap { |
|
| 287 | + | throw il::binary::error(0, "native image helper capacity exceeded"); |
|
| 288 | + | } |
|
| 289 | + | emit::recordFunc(e, name); |
|
| 290 | + | emit::recordFuncOffset(e, name); |
|
| 291 | + | } |
|
| 292 | + | ||
| 293 | + | /// Build trusted startup, address getters, and private-pointer relocation routines. |
|
| 294 | + | export fn helpers(generator: *mut rv64::Generator, id: u32, plan: *graph::Plan, names: *graph::Names, |
|
| 295 | + | initial: *Initializer, pool: *mut strings::Pool, arena: *mut alloc::Arena) throws (il::binary::Error) { |
|
| 296 | + | let stateName = initial.items[0].name; |
|
| 297 | + | let entry = try graph::name("images::native_", id, "", pool, arena); |
|
| 298 | + | let entryGetter = try graph::name("images::entry_", id, "", pool, arena); |
|
| 299 | + | let initialGetter = try graph::name("images::initial_", id, "", pool, arena); |
|
| 300 | + | let relocate = try graph::name("images::relocate_", id, "", pool, arena); |
|
| 301 | + | let e = &mut generator.e; |
|
| 302 | + | try helper(e, entry, 16); |
|
| 303 | + | emit::emit(e, encode::mv(rv64::TP, rv64::A0)); |
|
| 304 | + | emit::emitLd(e, rv64::SP, rv64::A0, 72); |
|
| 305 | + | emit::recordCall(e, names.functions[plan.entry]); |
|
| 306 | + | if not plan.returnsStatus { emit::emit(e, encode::mv(rv64::A0, rv64::ZERO)); } |
|
| 307 | + | else { |
|
| 308 | + | emit::emit(e, encode::slli(rv64::A0, rv64::A0, 32)); |
|
| 309 | + | emit::emit(e, encode::srli(rv64::A0, rv64::A0, 32)); |
|
| 310 | + | } |
|
| 311 | + | emit::loadImm(e, rv64::A7, 49); |
|
| 312 | + | emit::emit(e, encode::ecall()); |
|
| 313 | + | emit::emit(e, encode::ebreak()); |
|
| 314 | + | try helper(e, entryGetter, 3); |
|
| 315 | + | emit::recordAddrLoad(e, entry, rv64::A0); |
|
| 316 | + | emit::emit(e, encode::ret()); |
|
| 317 | + | try helper(e, initialGetter, 3); |
|
| 318 | + | emit::recordDataAddrLoad(e, stateName, rv64::A0); |
|
| 319 | + | emit::emit(e, encode::ret()); |
|
| 320 | + | let words = initial.fixups.len as u64 * 32 + 1; |
|
| 321 | + | if words > 0x7FFFFFFF { throw il::binary::error(0, "image relocation helper exceeds capacity"); } |
|
| 322 | + | try helper(e, relocate, words as u32); |
|
| 323 | + | for fixup in initial.fixups { |
|
| 324 | + | emit::emitAddImm(e, rv64::T0, rv64::A0, fixup.offset as i32); |
|
| 325 | + | emit::emitAddImm(e, rv64::T1, rv64::A0, fixup.target as i32); |
|
| 326 | + | emit::loadImm(e, rv64::T2, fixup.count as i64); |
|
| 327 | + | let start = e.codeLen; |
|
| 328 | + | if fixup.offset % 8 == 0 { emit::emitSd(e, rv64::T1, rv64::T0, 0); } |
|
| 329 | + | else { |
|
| 330 | + | emit::emit(e, encode::mv(rv64::T3, rv64::T1)); |
|
| 331 | + | for byte in 0..8 { |
|
| 332 | + | emit::emitSb(e, rv64::T3, rv64::T0, byte as i32); |
|
| 333 | + | if byte < 7 { emit::emit(e, encode::srli(rv64::T3, rv64::T3, 8)); } |
|
| 334 | + | } |
|
| 335 | + | } |
|
| 336 | + | if fixup.count > 1 { |
|
| 337 | + | emit::emit(e, encode::addi(rv64::T0, rv64::T0, 8)); |
|
| 338 | + | emit::emit(e, encode::addi(rv64::T2, rv64::T2, -1)); |
|
| 339 | + | emit::emit(e, encode::bne(rv64::T2, rv64::ZERO, (start as i32 - e.codeLen as i32) * rv64::INSTR_SIZE)); |
|
| 340 | + | } |
|
| 341 | + | } |
|
| 342 | + | emit::emit(e, encode::ret()); |
|
| 343 | + | } |
kernel/Makefile
+39 -22
| 1 | 1 | # Freestanding kernel and hosted mechanism checks. |
|
| 2 | 2 | EMU ?= $(or $(RAD_EMULATOR),emulator) |
|
| 3 | 3 | HOST_EMU ?= $(EMU) |
|
| 4 | 4 | COMPILER := ../bin/radiance.rv64.dev |
|
| 5 | 5 | COMPILE := $(HOST_EMU) -memory-size=385024 -data-size=348160 -stack-size=512 -run $(COMPILER) |
|
| 6 | - | MODULES := core/fdt.rad core/platform.rad core/frames.rad core/abi.rad core/handles.rad \ |
|
| 7 | - | core/events.rad core/domains.rad core/resources.rad core/capabilities.rad \ |
|
| 8 | - | core/memory.rad core/state.rad core/pages.rad core/atomic.rad core/cpu.rad \ |
|
| 9 | - | core/budgets.rad core/contexts.rad core/clock.rad core/budget_caps.rad \ |
|
| 10 | - | core/timers.rad core/notifications.rad core/devices.rad core/interrupts.rad core/mmio.rad |
|
| 11 | - | CORE_ASM := arch/atomic.ras arch/context.ras arch/clock.ras arch/mmio.ras |
|
| 6 | + | MODULES := $(wildcard core/*.rad) |
|
| 7 | + | CORE_ASM := arch/atomic.ras arch/context.ras arch/clock.ras arch/mmio.ras arch/physical.ras |
|
| 12 | 8 | CORE := -pkg core -mod core.rad $(addprefix -mod ,$(MODULES) $(CORE_ASM)) |
|
| 13 | - | CHECK_MODULES := check/boot.rad check/fixture.rad check/frames.rad check/handles.rad \ |
|
| 14 | - | check/domains.rad check/capabilities.rad check/pages.rad check/events.rad \ |
|
| 15 | - | check/budgets.rad check/budget_caps.rad check/timers.rad check/notifications.rad check/devices.rad |
|
| 9 | + | CHECK_MODULES := $(wildcard check/*.rad) |
|
| 10 | + | USER_INPUTS := user.rad $(wildcard user/*.rad user/*/*.rad) core/abi.rad |
|
| 11 | + | USER_BASE := -pkg abi -mod core/abi.rad -pkg user -mod user.rad -mod user/sys.rad -pkg probe -mod user/probe.rad |
|
| 12 | + | BASE_IMAGES := sample_root sample_control sample_scalars sample_memory sample_overflow sample_events sample_alias |
|
| 13 | + | BASE_RIL := $(addprefix build/,$(addsuffix .ril,$(BASE_IMAGES))) |
|
| 14 | + | IMAGE_MODULES_sample_control := user/sample_control/math.rad |
|
| 15 | + | BASE_CATALOG := -zero-bss -pkg images -mod build/baseline/images.rad -mod user/sys.ras $(addprefix -image ,$(BASE_RIL)) |
|
| 16 | + | MACHINE := $(EMU) -machine -no-guard-stack -max-steps=100000000 -count-instructions |
|
| 16 | 17 | ||
| 17 | 18 | .PHONY: all check clean compiler-check |
|
| 19 | + | .DELETE_ON_ERROR: |
|
| 18 | 20 | all: kernel.rv64 |
|
| 19 | 21 | ||
| 20 | 22 | compiler-check: |
|
| 21 | 23 | ||
| 22 | 24 | $(COMPILER): compiler-check |
|
| 23 | 25 | $(MAKE) -C .. RAD_EMULATOR=$(abspath $(shell command -v $(HOST_EMU))) |
|
| 24 | 26 | ||
| 25 | - | kernel.rv64: main.rad arch/entry.ras core.rad $(MODULES) $(CORE_ASM) $(COMPILER) |
|
| 26 | - | $(COMPILE) $(CORE) -pkg kernel -start arch/entry.ras -mod main.rad -entry kernel -o $@ |
|
| 27 | + | build: |
|
| 28 | + | mkdir -p $@ |
|
| 27 | 29 | ||
| 28 | - | check.rv64: check.rad core.rad $(MODULES) $(CORE_ASM) $(CHECK_MODULES) $(COMPILER) |
|
| 29 | - | $(COMPILE) $(CORE) -pkg check -mod check.rad $(addprefix -mod ,$(CHECK_MODULES)) -entry check -o $@ |
|
| 30 | + | build/%.ril: user/%.rad $(USER_INPUTS) $(COMPILER) | build |
|
| 31 | + | $(COMPILE) $(USER_BASE) -pkg $* -mod $< $(addprefix -mod ,$(IMAGE_MODULES_$*)) -entry $* -emit ril -o $@ |
|
| 30 | 32 | ||
| 31 | - | context.rv64: context.rad context/wait.rad check/context.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) $(COMPILER) |
|
| 32 | - | $(COMPILE) $(CORE) -pkg context -start arch/entry.ras -mod context.rad -mod context/wait.rad -mod check/context.ras -entry context -o $@ |
|
| 33 | + | build/baseline/images.rad: $(BASE_RIL) $(COMPILER) |
|
| 34 | + | mkdir -p $(@D) |
|
| 35 | + | $(COMPILE) -catalog $@ $(addprefix -image ,$(BASE_RIL)) |
|
| 33 | 36 | ||
| 34 | - | interrupt.rv64: interrupt.rad check/interrupt.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) $(COMPILER) |
|
| 35 | - | $(COMPILE) $(CORE) -pkg interrupt -start arch/entry.ras -mod interrupt.rad -mod check/interrupt.ras -entry interrupt -o $@ |
|
| 37 | + | kernel.rv64: main.rad arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER) |
|
| 38 | + | $(COMPILE) $(BASE_CATALOG) $(CORE) -pkg kernel -start arch/entry.ras -mod main.rad -entry kernel -o $@ |
|
| 36 | 39 | ||
| 37 | - | check: all check.rv64 context.rv64 interrupt.rv64 |
|
| 40 | + | check.rv64: check.rad core.rad $(MODULES) $(CORE_ASM) $(CHECK_MODULES) build/baseline/images.rad user/sys.ras $(COMPILER) |
|
| 41 | + | $(COMPILE) $(BASE_CATALOG) $(CORE) -pkg check -mod check.rad $(addprefix -mod ,$(CHECK_MODULES)) -entry check -o $@ |
|
| 42 | + | ||
| 43 | + | context.rv64: context.rad context/wait.rad check/context.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER) |
|
| 44 | + | $(COMPILE) $(BASE_CATALOG) $(CORE) -pkg context -start arch/entry.ras -mod context.rad -mod context/wait.rad -mod check/context.ras -entry context -o $@ |
|
| 45 | + | ||
| 46 | + | interrupt.rv64: interrupt.rad check/interrupt.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER) |
|
| 47 | + | $(COMPILE) $(BASE_CATALOG) $(CORE) -pkg interrupt -start arch/entry.ras -mod interrupt.rad -mod check/interrupt.ras -entry interrupt -o $@ |
|
| 48 | + | ||
| 49 | + | native.rv64: native.rad arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER) |
|
| 50 | + | $(COMPILE) $(BASE_CATALOG) $(CORE) -pkg native -start arch/entry.ras -mod native.rad -entry native -o $@ |
|
| 51 | + | ||
| 52 | + | check: all check.rv64 context.rv64 interrupt.rv64 native.rv64 |
|
| 38 | 53 | $(HOST_EMU) -run check.rv64 |
|
| 39 | - | $(EMU) -machine -no-guard-stack -max-steps=100000000 -count-instructions -run kernel.rv64 |
|
| 40 | - | $(EMU) -machine -no-guard-stack -max-steps=1000000 -count-instructions -run context.rv64 |
|
| 41 | - | $(EMU) -machine -harts=2 -no-guard-stack -irq=3 -irq-at=4000000 -uart-rx=52 -uart-rx-at=8000000 -max-steps=100000000 -count-instructions -run interrupt.rv64 |
|
| 54 | + | $(MACHINE) -run kernel.rv64 |
|
| 55 | + | $(MACHINE) -max-steps=1000000 -run context.rv64 |
|
| 56 | + | $(MACHINE) -harts=2 -irq=3 -irq-at=4000000 -uart-rx=52 -uart-rx-at=8000000 -run interrupt.rv64 |
|
| 57 | + | $(MACHINE) -run native.rv64 |
|
| 42 | 58 | ||
| 43 | 59 | clean: |
|
| 44 | - | rm -f kernel.rv64 check.rv64 context.rv64 interrupt.rv64 |
|
| 60 | + | rm -rf build |
|
| 61 | + | rm -f kernel.rv64 check.rv64 context.rv64 interrupt.rv64 native.rv64 |
kernel/NOTES.md
+29 -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 16 of the 22-step plan. |
|
| 5 | + | record the contracts established through step 17 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. |
| 212 | 212 | Native compilation uses std::arch::rv64, its allocator, and its relocations. |
|
| 213 | 213 | - Structural validation is not pointer-provenance or type-safety verification. |
|
| 214 | 214 | The trusted-input boundary remains unchanged. Round trips exercise switch |
|
| 215 | 215 | arguments, loops with aggregate returns, and writable function pointers. |
|
| 216 | 216 | ||
| 217 | + | ## Shared catalogs and trusted native images |
|
| 218 | + | ||
| 219 | + | - -catalog output.rad -image input.ril ... defines up to 64 ordered images. |
|
| 220 | + | Index zero is root; an index is not creation or execution authority. Native |
|
| 221 | + | linking consumes the same ordered inputs through -image. |
|
| 222 | + | - Use the compiler's RV64 instruction selection, register allocation, assembler, |
|
| 223 | + | data layout, and relocations. Make supplies paths and dependency order. |
|
| 224 | + | - Namespace image functions and constants. Writable globals and data referring |
|
| 225 | + | to them are instance state. Copy the immutable initializer and relocate private |
|
| 226 | + | pointers, including repeated and unaligned pointer fields. |
|
| 227 | + | - gp addresses private image state and tp the current Env. Native locals, |
|
| 228 | + | saved registers, and return addresses use a private 64 KiB sp stack. Trusted |
|
| 229 | + | inputs must respect its bounds; the supplied Page stack is an authorized Env |
|
| 230 | + | range, not the native spill stack. |
|
| 231 | + | - The immutable 80-byte Env contains ten consecutive u64 fields: argument base, |
|
| 232 | + | argument size, Events handle, Events address, private image-state base, |
|
| 233 | + | Page-stack lower bound, Page-stack upper bound, context identity, private-stack |
|
| 234 | + | lower bound, private-stack upper bound. Env and native-stack memory remain |
|
| 235 | + | private to the context and are not granted as Pages. |
|
| 236 | + | - External native references are restricted to linked user::sys:: primitives. |
|
| 237 | + | Typed wrappers check kernel results before constructing Page views or issuing |
|
| 238 | + | MMIO. Events consumption uses acquire/release and a shared consumer lock. |
|
| 239 | + | - -zero-bss includes zero-initialized kernel storage in native data sections so |
|
| 240 | + | the image header and firmware reservations cover globals. Physical clear/copy |
|
| 241 | + | callbacks accept validated owned ranges; constructing physical byte views is |
|
| 242 | + | unsafe. |
|
| 243 | + | ||
| 217 | 244 | ## Validation |
|
| 218 | 245 | ||
| 219 | 246 | Use the current machine-capable sibling emulator. Set `RAD_EMULATOR`, pass |
|
| 220 | 247 | `EMU` to the kernel Make invocation, or put `emulator` on PATH. The kernel build |
|
| 221 | 248 | checks compiler dependencies. From the repository root, run: |
| 223 | 250 | ```sh |
|
| 224 | 251 | make -C kernel check |
|
| 225 | 252 | make std-test bin-test |
|
| 226 | 253 | ``` |
|
| 227 | 254 | ||
| 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. |
|
| 255 | + | Run seven source-compiled binary images through native machine traps. The fixture handles PageAllocate (30), QueryPage (44), checked Page materialization (60), and Exit (49), plus breakpoint fault entry. Check control flow, scalar operations, Page bounds, Events consumption, and overlapping memory. Fixture setup supplies private state, Env, and stack; this is not the complete calls dispatcher. |
|
| 229 | 256 | ||
| 230 | 257 | Run the context reservation probe with an emulator that retains LR/SC |
|
| 231 | 258 | reservations across traps. This checks the kernel's reservation invalidation. |
kernel/arch/physical.ras
added
+29 -0
| 1 | + | // Validated physical byte views and disjoint kernel-owned RAM operations. |
|
| 2 | + | .text; |
|
| 3 | + | .export @"core::physical::pointer"; |
|
| 4 | + | @"core::physical::pointer" |
|
| 5 | + | ret; |
|
| 6 | + | ||
| 7 | + | .export @"core::physical::clear"; |
|
| 8 | + | @"core::physical::clear" |
|
| 9 | + | beqz %a1 @clearDone; |
|
| 10 | + | add %t0 %a0 %a1; |
|
| 11 | + | @clearWord |
|
| 12 | + | sd %zero 0(%a0); |
|
| 13 | + | addi %a0 %a0 8; |
|
| 14 | + | bltu %a0 %t0 @clearWord; |
|
| 15 | + | @clearDone |
|
| 16 | + | ret; |
|
| 17 | + | ||
| 18 | + | .export @"core::physical::copy"; |
|
| 19 | + | @"core::physical::copy" |
|
| 20 | + | beqz %a2 @copyDone; |
|
| 21 | + | add %t0 %a1 %a2; |
|
| 22 | + | @copyByte |
|
| 23 | + | lbu %t1 0(%a1); |
|
| 24 | + | sb %t1 0(%a0); |
|
| 25 | + | addi %a0 %a0 1; |
|
| 26 | + | addi %a1 %a1 1; |
|
| 27 | + | bltu %a1 %t0 @copyByte; |
|
| 28 | + | @copyDone |
|
| 29 | + | ret; |
kernel/core.rad
+1 -0
| 21 | 21 | export mod timers; |
|
| 22 | 22 | export mod notifications; |
|
| 23 | 23 | export mod devices; |
|
| 24 | 24 | export mod interrupts; |
|
| 25 | 25 | export mod mmio; |
|
| 26 | + | export mod physical; |
kernel/core/physical.rad
added
+17 -0
| 1 | + | //! Physical access for kernel-owned RAM and immutable image initializers. |
|
| 2 | + | ||
| 3 | + | /// Convert a validated mapped address at the physical boundary. |
|
| 4 | + | unsafe fn pointer(base: u64) -> *mut u8; |
|
| 5 | + | ||
| 6 | + | /// View mapped bytes whose address, extent, and lifetime the caller validated. |
|
| 7 | + | export unsafe fn bytes(base: u64, size: u32) -> *mut [u8] { |
|
| 8 | + | return @sliceOf(pointer(base), size); |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// Allocator callback for an owned range aligned to eight bytes in base and size. |
|
| 12 | + | /// Only the serialized frame allocator can supply this physical range. |
|
| 13 | + | export fn clear(base: u64, size: u32); |
|
| 14 | + | ||
| 15 | + | /// Initializer callback for disjoint validated source and destination ranges. |
|
| 16 | + | /// Source bytes are immutable image data; destination bytes are owned RAM. |
|
| 17 | + | export fn copy(destination: u64, source: u64, size: u32); |
kernel/native.rad
added
+124 -0
| 1 | + | //! Real user-mode execution of admitted graphs and their memory-safety checks. |
|
| 2 | + | ||
| 3 | + | use core::abi; |
|
| 4 | + | use core::atomic; |
|
| 5 | + | use core::cpu; |
|
| 6 | + | use core::contexts; |
|
| 7 | + | use core::domains; |
|
| 8 | + | use core::events; |
|
| 9 | + | use core::fdt; |
|
| 10 | + | use core::frames; |
|
| 11 | + | use core::memory; |
|
| 12 | + | use core::pages; |
|
| 13 | + | use core::physical; |
|
| 14 | + | use core::platform; |
|
| 15 | + | use core::resources; |
|
| 16 | + | use core::state; |
|
| 17 | + | use images; |
|
| 18 | + | ||
| 19 | + | /// Isolated consumer used by the native graph regressions. |
|
| 20 | + | static DOMAINS: [domains::Domain; 1] = undefined; |
|
| 21 | + | /// Page objects exercised by checked materialization calls. |
|
| 22 | + | static OBJECTS: [resources::Slot; 16] = undefined; |
|
| 23 | + | /// Storage for the execution-state view. |
|
| 24 | + | static CONTEXTS: [contexts::Context; 1] = undefined; |
|
| 25 | + | /// Physical frame ownership from the firmware reservations. |
|
| 26 | + | static POOL: frames::Pool = undefined; |
|
| 27 | + | /// Persistent physical Page pins. |
|
| 28 | + | static PINS: [u16; frames::MAX_FRAMES] = undefined; |
|
| 29 | + | /// Live Page claims. |
|
| 30 | + | static CLAIMS: [bool; frames::MAX_FRAMES] = undefined; |
|
| 31 | + | /// Domain-lifetime frame bitmap. |
|
| 32 | + | static GRANTS: [u64; frames::MAX_FRAMES / 64] = undefined; |
|
| 33 | + | /// Actual capability and Page mechanisms used by the fixture calls. |
|
| 34 | + | static KERNEL: state::State = undefined; |
|
| 35 | + | ||
| 36 | + | /// Run one graph against actual Page authority and check its terminal call. |
|
| 37 | + | unsafe fn run(image: u32, expected: u32, own: abi::Handle, queued: bool) { |
|
| 38 | + | let descriptor = images::get(image); |
|
| 39 | + | let stack = try! pages::allocate(&mut KERNEL, 0, own, 1, physical::clear); |
|
| 40 | + | let range = try! pages::access(&KERNEL, 0, stack, abi::READ | abi::WRITE); |
|
| 41 | + | let args = physical::bytes(range.base, 8).ptr as *mut u64; |
|
| 42 | + | set *args = stack.bits if image == 6 else own.bits; |
|
| 43 | + | let private = frames::install(try! frames::allocate(&mut POOL, 16, physical::clear)); |
|
| 44 | + | let data = frames::install(try! frames::allocate(&mut POOL, 1, physical::clear)); |
|
| 45 | + | let privateBase = private.first as u64 * frames::PAGE_SIZE as u64; |
|
| 46 | + | let dataBase = data.first as u64 * frames::PAGE_SIZE as u64; |
|
| 47 | + | assert descriptor.stateSize <= frames::PAGE_SIZE and descriptor.stateAlignment <= frames::PAGE_SIZE; |
|
| 48 | + | physical::copy(dataBase, descriptor.initial, descriptor.stateSize); |
|
| 49 | + | descriptor.relocate(dataBase); |
|
| 50 | + | let mut env = DOMAINS[0].env; |
|
| 51 | + | set env.argsPointer = range.base; |
|
| 52 | + | set env.argsSize = 8; |
|
| 53 | + | set env.stackBase = range.base; |
|
| 54 | + | set env.stackTop = range.base + range.size; |
|
| 55 | + | set env.stateBase = dataBase; |
|
| 56 | + | set env.privateStackBase = privateBase; |
|
| 57 | + | set env.eventsPointer = &DOMAINS[0].events.ring as u64; |
|
| 58 | + | set env.privateStackTop = privateBase + 65536; |
|
| 59 | + | if queued { |
|
| 60 | + | try! events::push(&mut DOMAINS[0].events, events::Event { |
|
| 61 | + | kind: 1, reserved: 0, code: 42, value: 0x123456789abcdef, |
|
| 62 | + | }, events::Class::Ordinary); |
|
| 63 | + | } |
|
| 64 | + | let mut frame: cpu::Frame = undefined; |
|
| 65 | + | cpu::init(&mut frame, descriptor.entry, env.stackTop, &env as u64, dataBase); |
|
| 66 | + | loop { |
|
| 67 | + | cpu::enter(&mut frame); |
|
| 68 | + | assert frame.status & 0x1800 == 0; |
|
| 69 | + | if image == 4 { |
|
| 70 | + | assert frame.cause == 3; |
|
| 71 | + | break; |
|
| 72 | + | } |
|
| 73 | + | assert frame.cause == 8; |
|
| 74 | + | let args = &frame.registers; |
|
| 75 | + | match args[17] { |
|
| 76 | + | case 30 => { |
|
| 77 | + | let page = try! pages::allocate(&mut KERNEL, 0, abi::Handle { bits: args[10] }, args[11], physical::clear); |
|
| 78 | + | cpu::reply(&mut frame, abi::Error::Ok, page.bits, 0, 0, 0); |
|
| 79 | + | }, |
|
| 80 | + | case 44 => { |
|
| 81 | + | let area = try! pages::access(&KERNEL, 0, abi::Handle { bits: args[10] }, 0); |
|
| 82 | + | cpu::reply(&mut frame, abi::Error::Ok, area.base, area.size / frames::PAGE_SIZE as u64, 0, 0); |
|
| 83 | + | }, |
|
| 84 | + | case 60 => { |
|
| 85 | + | assert args[11] <= 3; |
|
| 86 | + | let area = try! pages::access(&KERNEL, 0, abi::Handle { bits: args[10] }, args[11] as u16); |
|
| 87 | + | cpu::reply(&mut frame, abi::Error::Ok, area.base, area.size, 0, 0); |
|
| 88 | + | }, |
|
| 89 | + | case 49 => { |
|
| 90 | + | assert args[10] == expected as u64; |
|
| 91 | + | break; |
|
| 92 | + | }, |
|
| 93 | + | else => panic "native: unexpected kernel call", |
|
| 94 | + | } |
|
| 95 | + | } |
|
| 96 | + | if image == 5 { |
|
| 97 | + | assert atomic::load(&DOMAINS[0].events.ring.head) == atomic::load(&DOMAINS[0].events.ring.tail); |
|
| 98 | + | } |
|
| 99 | + | frames::reclaim(&mut POOL, private); |
|
| 100 | + | frames::reclaim(&mut POOL, data); |
|
| 101 | + | } |
|
| 102 | + | ||
| 103 | + | /// Check graph linking, native control flow, bounded memory, and protected spills. |
|
| 104 | + | @default unsafe fn main(hart: u64, description: *u8) -> u32 { |
|
| 105 | + | assert hart == 0 and images::COUNT == 7; |
|
| 106 | + | let size = try! fdt::word(@sliceOf(description, 40), 4); |
|
| 107 | + | assert size >= 40 and size <= fdt::MAX_BYTES; |
|
| 108 | + | let mut tree: fdt::Tree = undefined; |
|
| 109 | + | try! fdt::decode(@sliceOf(description, size), &mut tree); |
|
| 110 | + | let mut machine: platform::Platform = undefined; |
|
| 111 | + | try! platform::discover(&tree, &mut machine); |
|
| 112 | + | try! frames::init(&mut POOL, &machine); |
|
| 113 | + | domains::init(&mut DOMAINS[..]); |
|
| 114 | + | resources::init(&mut OBJECTS[..]); |
|
| 115 | + | contexts::init(&mut CONTEXTS[0], 0); |
|
| 116 | + | let mut ram: memory::Memory = undefined; |
|
| 117 | + | memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut CLAIMS[..], &mut GRANTS[..], 1); |
|
| 118 | + | set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram }; |
|
| 119 | + | let own = domains::root(&mut DOMAINS[..], images::ROOT); |
|
| 120 | + | let expected: [u32; 7] = [42, 21, 73, 77, 0, 42, 81]; |
|
| 121 | + | for i in 0..images::COUNT { run(i, expected[i], own, i == 5); } |
|
| 122 | + | run(5, 42, own, false); |
|
| 123 | + | return 0; |
|
| 124 | + | } |
kernel/user.rad
added
+3 -0
| 1 | + | //! User bindings for the kernel's capability-checked direct-call ABI. |
|
| 2 | + | ||
| 3 | + | export unsafe mod sys; |
kernel/user/probe.rad
added
+20 -0
| 1 | + | //! Shared argument and Page views for source image probes. |
|
| 2 | + | use abi; |
|
| 3 | + | use user::sys; |
|
| 4 | + | ||
| 5 | + | /// Decode a caller-local handle supplied in the workload arguments. |
|
| 6 | + | export fn handle(bits: u64) -> abi::Handle { return abi::Handle { bits }; } |
|
| 7 | + | /// View aligned startup arguments as complete u64 words. |
|
| 8 | + | export fn args(env: *sys::Env) -> *[u64] { |
|
| 9 | + | let bytes = sys::envArgs(env); |
|
| 10 | + | assert bytes.len % 8 == 0; |
|
| 11 | + | assert bytes.ptr as u64 % 8 == 0; |
|
| 12 | + | return @sliceOf(bytes.ptr as *u64, bytes.len / 8); |
|
| 13 | + | } |
|
| 14 | + | /// Obtain a writable word view through Page authority. |
|
| 15 | + | export fn words(page: abi::Handle) -> *mut [u64] { |
|
| 16 | + | let info = sys::queryPage(page); |
|
| 17 | + | assert info.count <= 0xffffffff / 4096; |
|
| 18 | + | let bytes = try! sys::pageSliceMut(page, 0, info.count * 4096); |
|
| 19 | + | return @sliceOf(bytes.ptr as *mut u64, bytes.len / 8); |
|
| 20 | + | } |
kernel/user/sample_alias.rad
added
+15 -0
| 1 | + | //! Wiping the supplied Page stack preserves private native frames. |
|
| 2 | + | use abi; |
|
| 3 | + | use user::sys; |
|
| 4 | + | use probe; |
|
| 5 | + | ||
| 6 | + | fn wipe(page: abi::Handle) -> u32 { |
|
| 7 | + | let sentinel: [u32; 8] = [81, 82, 83, 84, 85, 86, 87, 88]; |
|
| 8 | + | let words = probe::words(page); |
|
| 9 | + | for i in 0..words.len { set words[i] = 0; } |
|
| 10 | + | return sentinel[0]; |
|
| 11 | + | } |
|
| 12 | + | @default fn main(env: *sys::Env) -> u32 { |
|
| 13 | + | let args = probe::args(env); |
|
| 14 | + | return wipe(probe::handle(args[0])); |
|
| 15 | + | } |
kernel/user/sample_control.rad
added
+17 -0
| 1 | + | //! Indirect recursion and simultaneous loop-variable swaps terminate with 21. |
|
| 2 | + | use user::sys; |
|
| 3 | + | mod math; |
|
| 4 | + | ||
| 5 | + | @default fn main(env: *sys::Env) -> u32 { |
|
| 6 | + | let f = math::fib; |
|
| 7 | + | let result = f(8); |
|
| 8 | + | let mut a: u32 = 1; |
|
| 9 | + | let mut b: u32 = 2; |
|
| 10 | + | for i in 0..3 { |
|
| 11 | + | let previous = a; |
|
| 12 | + | set a = b; |
|
| 13 | + | set b = previous; |
|
| 14 | + | } |
|
| 15 | + | if a <> 2 or b <> 1 { return 100; } |
|
| 16 | + | return result; |
|
| 17 | + | } |
kernel/user/sample_control/math.rad
added
+9 -0
| 1 | + | //! Recursive calls preserve private local values and return addresses. |
|
| 2 | + | /// Compute Fibonacci recursively while retaining a local array across calls. |
|
| 3 | + | export fn fib(n: u32) -> u32 { |
|
| 4 | + | if n < 2 { return n; } |
|
| 5 | + | let local: [u32; 8] = [n, 0, 0, 0, 0, 0, 0, 0]; |
|
| 6 | + | let left = fib(n - 1); |
|
| 7 | + | let right = fib(local[0] - 2); |
|
| 8 | + | return left + right; |
|
| 9 | + | } |
kernel/user/sample_events.rad
added
+13 -0
| 1 | + | //! Optional event payload widths and repeated empty pops terminate with 42. |
|
| 2 | + | use user::sys; |
|
| 3 | + | ||
| 4 | + | @default fn main(env: *sys::Env) -> u32 { |
|
| 5 | + | if let event = sys::eventsPop(env) { |
|
| 6 | + | if event.kind <> 1 or event.code <> 42 or event.value <> 0x123456789abcdef { |
|
| 7 | + | return 100; |
|
| 8 | + | } |
|
| 9 | + | } |
|
| 10 | + | if let _event = sys::eventsPop(env) { return 100; } |
|
| 11 | + | if let _event = sys::eventsPop(env) { return 100; } |
|
| 12 | + | return 42; |
|
| 13 | + | } |
kernel/user/sample_memory.rad
added
+20 -0
| 1 | + | //! Aggregate copies, borrowed fields and bounded subslices terminate with 77. |
|
| 2 | + | use user::sys; |
|
| 3 | + | ||
| 4 | + | record Pair: Copy { first: u64, second: u64 } |
|
| 5 | + | constant SOURCE: Pair = Pair { first: 77, second: 88 }; |
|
| 6 | + | ||
| 7 | + | fn borrow(base: *mut Pair) -> *mut u64 { return &mut base.second; } |
|
| 8 | + | fn copy(destination: *mut Pair, source: *Pair) { set *destination = *source; } |
|
| 9 | + | ||
| 10 | + | @default fn main(env: *sys::Env) -> u32 { |
|
| 11 | + | let mut local: Pair = undefined; |
|
| 12 | + | copy(&mut local, &SOURCE); |
|
| 13 | + | let field = borrow(&mut local); |
|
| 14 | + | set *field = local.first; |
|
| 15 | + | // Self-copy exercises aliasing through the same aggregate API. |
|
| 16 | + | copy(&mut local, &local); |
|
| 17 | + | let words = @sliceOf(&local.first, 2); |
|
| 18 | + | let middle = &words[1..2]; |
|
| 19 | + | return middle[0] as u32; |
|
| 20 | + | } |
kernel/user/sample_overflow.rad
added
+13 -0
| 1 | + | //! An invalid runtime subrange faults before a store or normal terminal report. |
|
| 2 | + | use user::sys; |
|
| 3 | + | ||
| 4 | + | fn outside(words: *mut [u64], index: u32) { |
|
| 5 | + | set words[index] = 123; |
|
| 6 | + | } |
|
| 7 | + | @default fn main(env: *sys::Env) -> u32 { |
|
| 8 | + | let mut local: [u64; 2] = [0, 0]; |
|
| 9 | + | // Radiance slices have u32 lengths; source bounds traps replace text RIL's |
|
| 10 | + | // unrepresentable u64 byte-extent overflow with the same no-write behavior. |
|
| 11 | + | outside(&mut local[..], 0xffffffff); |
|
| 12 | + | return 100; |
|
| 13 | + | } |
kernel/user/sample_root.rad
added
+15 -0
| 1 | + | //! Allocation and checked Page access terminate with status 42. |
|
| 2 | + | use user::sys; |
|
| 3 | + | use probe; |
|
| 4 | + | ||
| 5 | + | static COUNT: u64 = 0; |
|
| 6 | + | ||
| 7 | + | @default fn main(env: *sys::Env) -> u32 { |
|
| 8 | + | let args = probe::args(env); |
|
| 9 | + | let page = try sys::pageAllocate(probe::handle(args[0]), 1) catch { return 100; }; |
|
| 10 | + | let words = probe::words(page); |
|
| 11 | + | set words[0] = 42; |
|
| 12 | + | set COUNT = words[0]; |
|
| 13 | + | if COUNT <> 42 { return 100; } |
|
| 14 | + | return 42; |
|
| 15 | + | } |
kernel/user/sample_scalars.rad
added
+38 -0
| 1 | + | //! Narrow signedness, scalar operations, wide data and matching return 73. |
|
| 2 | + | use user::sys; |
|
| 3 | + | ||
| 4 | + | constant WIDE: [u64; 4] = [0x7fffffff, 0x80000000, 0x8000000000000000, 0x123456789abcdef]; |
|
| 5 | + | ||
| 6 | + | fn check(a: u8, b: u16, c: u32, negative: i8, unsigned: u64) -> bool { |
|
| 7 | + | let add: u8 = a + 10; |
|
| 8 | + | let sub: u16 = b - 3; |
|
| 9 | + | let mul: u32 = c * 7; |
|
| 10 | + | let div: i8 = negative / 2; |
|
| 11 | + | let rem: i8 = negative % 2; |
|
| 12 | + | let one: i8 = 1; |
|
| 13 | + | let zero: u8 = 0; |
|
| 14 | + | let x: u8 = 10; |
|
| 15 | + | let y: u8 = 6; |
|
| 16 | + | let signed: i8 = -8; |
|
| 17 | + | let high: u8 = 248; |
|
| 18 | + | let minus: i8 = -1; |
|
| 19 | + | let byte: u8 = 255; |
|
| 20 | + | return add == 4 and sub == 65534 and mul == 21 |
|
| 21 | + | and div == -3 and rem == -1 |
|
| 22 | + | and unsigned / 3 == 3 and unsigned % 3 == 1 |
|
| 23 | + | and -one == -1 and ~zero == 255 |
|
| 24 | + | and (x & y) == 2 and (x | y) == 14 and (x ^ y) == 12 |
|
| 25 | + | and (one << 1) == 2 and (signed >> 2) == -2 and (high >> 2) == 62 |
|
| 26 | + | and minus as i32 == -1 and byte as u32 == 255 |
|
| 27 | + | and x <> y and minus < one and one >= minus |
|
| 28 | + | and 1 as u8 < byte and byte >= 1 as u8; |
|
| 29 | + | } |
|
| 30 | + | @default fn main(env: *sys::Env) -> u32 { |
|
| 31 | + | let passed = check(250, 1, 3, -7, 10) |
|
| 32 | + | and WIDE[0] == 0x7fffffff and WIDE[1] == 0x80000000 |
|
| 33 | + | and WIDE[2] == 0x8000000000000000 and WIDE[3] == 0x123456789abcdef; |
|
| 34 | + | match passed { |
|
| 35 | + | case true => return 73, |
|
| 36 | + | case false => return 100, |
|
| 37 | + | } |
|
| 38 | + | } |
kernel/user/sys.rad
added
+180 -0
| 1 | + | //! Capability-checked user calls and views of kernel-authorized memory. |
|
| 2 | + | //! The image catalog links the native primitives in user/sys.ras. |
|
| 3 | + | ||
| 4 | + | use abi; |
|
| 5 | + | ||
| 6 | + | /// Startup ABI: exactly the ten u64 fields of core::domains::Env, in order. |
|
| 7 | + | /// Each entry receives its own immutable Env pointer in a0. Retain that pointer |
|
| 8 | + | /// explicitly; a process-global cached Env would name the wrong context. |
|
| 9 | + | export record Env: Copy { |
|
| 10 | + | /// Readable startup argument address. |
|
| 11 | + | argsPointer: u64, |
|
| 12 | + | /// Number of readable startup argument bytes. |
|
| 13 | + | argsSize: u64, |
|
| 14 | + | /// Installed local Events handle. |
|
| 15 | + | eventsHandle: u64, |
|
| 16 | + | /// Address of the domain's shared event ring. |
|
| 17 | + | eventsPointer: u64, |
|
| 18 | + | /// Private image-instance mutable data base. |
|
| 19 | + | stateBase: u64, |
|
| 20 | + | /// Lowest address in the authorized Page stack. |
|
| 21 | + | stackBase: u64, |
|
| 22 | + | /// Exclusive upper bound of the authorized Page stack. |
|
| 23 | + | stackTop: u64, |
|
| 24 | + | /// Exact execution-context incarnation, not authority. |
|
| 25 | + | context: u64, |
|
| 26 | + | /// Lowest native stack address for locals and saved registers. |
|
| 27 | + | privateStackBase: u64, |
|
| 28 | + | /// Exclusive upper bound of the context's private native stack. |
|
| 29 | + | privateStackTop: u64, |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | /// Native reply layout: a0 error followed by a1..a4 value words. |
|
| 33 | + | record Reply: Copy { |
|
| 34 | + | /// Kernel ABI error number. |
|
| 35 | + | error: u64, |
|
| 36 | + | /// Operation-specific result words. |
|
| 37 | + | values: [u64; 4], |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | /// Seven contiguous argument words for the native register bridge. |
|
| 41 | + | record Arguments: Copy { |
|
| 42 | + | /// Values loaded into a0 through a6. |
|
| 43 | + | registers: [u64; 7], |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | /// Native bridge has only three formal parameters; arguments is a thin pointer |
|
| 47 | + | /// to seven words, not a slice. The bridge preserves ordinary callee-saved state. |
|
| 48 | + | fn rawCall(number: u64, arguments: *Arguments, reply: *mut Reply); |
|
| 49 | + | /// Convert only an address validated by the kernel or the trusted startup ABI. |
|
| 50 | + | unsafe fn pointer(address: u64) -> *mut opaque; |
|
| 51 | + | /// Acquire one naturally aligned shared u32 through readable Page authority. |
|
| 52 | + | export fn loadAcquire(address: *u32) -> u32; |
|
| 53 | + | /// Release one naturally aligned shared u32 through writable Page authority. |
|
| 54 | + | export fn storeRelease(address: *mut u32, value: u32); |
|
| 55 | + | /// Exchange the private consumer lock; never expose producer state as an API. |
|
| 56 | + | fn lockAcquire(address: *mut u32) -> u32; |
|
| 57 | + | /// Release the shared consumer lock after copying an event. |
|
| 58 | + | fn lockRelease(address: *mut u32); |
|
| 59 | + | ||
| 60 | + | /// All input registers are initialized, including unused argument words. |
|
| 61 | + | fn invoke(number: u64, a: u64, b: u64, c: u64, d: u64) -> Reply { |
|
| 62 | + | let arguments = Arguments { registers: [a, b, c, d, 0, 0, 0] }; |
|
| 63 | + | let mut reply: Reply = undefined; |
|
| 64 | + | rawCall(number, &arguments, &mut reply); |
|
| 65 | + | return reply; |
|
| 66 | + | } |
|
| 67 | + | ||
| 68 | + | /// Decode the kernel ABI error number. Unknown numbers are fatal ABI errors. |
|
| 69 | + | fn check(error: u64) throws (abi::Error) { |
|
| 70 | + | match error { |
|
| 71 | + | case 0 => return, |
|
| 72 | + | case 1 => throw abi::Error::BadHandle, |
|
| 73 | + | case 2 => throw abi::Error::Denied, |
|
| 74 | + | case 3 => throw abi::Error::OutOfMemory, |
|
| 75 | + | case 4 => throw abi::Error::InvalidArg, |
|
| 76 | + | case 5 => throw abi::Error::Busy, |
|
| 77 | + | case 6 => throw abi::Error::VerifyFailed, |
|
| 78 | + | case 7 => throw abi::Error::NotPending, |
|
| 79 | + | case 8 => throw abi::Error::Exhausted, |
|
| 80 | + | else => panic "sys: unknown ABI error", |
|
| 81 | + | } |
|
| 82 | + | } |
|
| 83 | + | ||
| 84 | + | /// Allocate count contiguous zeroed frames using explicit Allocate authority. |
|
| 85 | + | export fn pageAllocate(authority: abi::Handle, count: u64) -> abi::Handle throws (abi::Error) { |
|
| 86 | + | let reply = invoke(30, authority.bits, count, 0, 0); |
|
| 87 | + | try check(reply.error); |
|
| 88 | + | return abi::Handle { bits: reply.values[0] }; |
|
| 89 | + | } |
|
| 90 | + | ||
| 91 | + | /// Page metadata only; base alone is not dereferenceable authority. |
|
| 92 | + | export record PageInfo: Copy { |
|
| 93 | + | /// Physical address of the first frame. |
|
| 94 | + | base: u64, |
|
| 95 | + | /// Number of contiguous 4 KiB frames. |
|
| 96 | + | count: u32, |
|
| 97 | + | } |
|
| 98 | + | ||
| 99 | + | /// Invalid metadata queries fault the domain in the kernel, never return Error. |
|
| 100 | + | export fn queryPage(handle: abi::Handle) -> PageInfo { |
|
| 101 | + | let reply = invoke(44, handle.bits, 0, 0, 0); |
|
| 102 | + | return PageInfo { base: reply.values[0], count: reply.values[1] as u32 }; |
|
| 103 | + | } |
|
| 104 | + | ||
| 105 | + | /// Materialize a checked byte subrange, never a metadata-derived arbitrary pointer. |
|
| 106 | + | /// Subtraction-based checks avoid overflow before any pointer is constructed. |
|
| 107 | + | fn pageRange(handle: abi::Handle, offset: u64, size: u32, rights: u16) -> u64 throws (abi::Error) { |
|
| 108 | + | let reply = invoke(60, handle.bits, rights as u64, 0, 0); |
|
| 109 | + | try check(reply.error); |
|
| 110 | + | let base = reply.values[0]; |
|
| 111 | + | let extent = reply.values[1]; |
|
| 112 | + | if offset > extent or size as u64 > extent - offset |
|
| 113 | + | or base > 0xffffffffffffffff - offset { |
|
| 114 | + | throw abi::Error::InvalidArg; |
|
| 115 | + | } |
|
| 116 | + | let address = base + offset; |
|
| 117 | + | if address > 0xffffffffffffffff - size as u64 { throw abi::Error::InvalidArg; } |
|
| 118 | + | return address; |
|
| 119 | + | } |
|
| 120 | + | ||
| 121 | + | /// Mutable slices permit both loads and stores, therefore require Read AND Write. |
|
| 122 | + | export fn pageSliceMut(handle: abi::Handle, offset: u64, size: u32) -> *mut [u8] throws (abi::Error) { |
|
| 123 | + | let address = try pageRange(handle, offset, size, abi::READ | abi::WRITE); |
|
| 124 | + | return @sliceOf(pointer(address) as *mut u8, size); |
|
| 125 | + | } |
|
| 126 | + | ||
| 127 | + | /// Immutable startup bytes; fail rather than truncate an unrepresentable extent. |
|
| 128 | + | export fn envArgs(env: *Env) -> *[u8] { |
|
| 129 | + | assert env.argsSize <= 0xffffffff; |
|
| 130 | + | assert env.argsPointer <= 0xffffffffffffffff - env.argsSize; |
|
| 131 | + | return @sliceOf(pointer(env.argsPointer) as *u8, env.argsSize as u32); |
|
| 132 | + | } |
|
| 133 | + | ||
| 134 | + | /// The fixed shared event ring capacity, matching core::events::CAPACITY. |
|
| 135 | + | constant EVENTS_CAPACITY: u32 = 256; |
|
| 136 | + | /// Notification wire layout: kind@0, reserved@2, code@4, value@8; 16 bytes. |
|
| 137 | + | export record Event: Copy { |
|
| 138 | + | /// Interrupt=1, Timeout=2, Fault=3, ChildExit=4, Wakeup=5. |
|
| 139 | + | kind: u16, |
|
| 140 | + | /// Reserved ABI field, always zero in kernel-produced events. |
|
| 141 | + | reserved: u16, |
|
| 142 | + | /// Token, source number, exit status, or fault code. |
|
| 143 | + | code: u32, |
|
| 144 | + | /// Event-specific payload or sender identity. |
|
| 145 | + | value: u64, |
|
| 146 | + | } |
|
| 147 | + | /// Shared layout only; no user API exposes this mutable producer state. |
|
| 148 | + | record Events: Copy { |
|
| 149 | + | /// Fixed notification slots. |
|
| 150 | + | data: [Event; EVENTS_CAPACITY], |
|
| 151 | + | /// Consumer-released count of copied entries. |
|
| 152 | + | head: u32, |
|
| 153 | + | /// Kernel-published count of produced entries. |
|
| 154 | + | tail: u32, |
|
| 155 | + | /// Ring index mask. |
|
| 156 | + | mask: u32, |
|
| 157 | + | /// Shared lock for consumers in the same domain. |
|
| 158 | + | consumer: u32, |
|
| 159 | + | } |
|
| 160 | + | ||
| 161 | + | /// Consume at most one event from the current domain's installed ring. Every |
|
| 162 | + | /// context uses this same consumer lock; no syscall or callback runs while held. |
|
| 163 | + | /// Acquire tail before copying; release head only after the entire record is |
|
| 164 | + | /// private. Empty and nonempty paths both release the lock. Kernel production |
|
| 165 | + | /// does not acquire this lock. Counters wrap at 32 bits, not at ring capacity. |
|
| 166 | + | export fn eventsPop(env: *Env) -> ?Event { |
|
| 167 | + | let ring = pointer(env.eventsPointer) as *mut Events; |
|
| 168 | + | while lockAcquire(&mut ring.consumer) <> 0 {} |
|
| 169 | + | let head = loadAcquire(&ring.head); |
|
| 170 | + | let tail = loadAcquire(&ring.tail); |
|
| 171 | + | if head == tail { |
|
| 172 | + | lockRelease(&mut ring.consumer); |
|
| 173 | + | return nil; |
|
| 174 | + | } |
|
| 175 | + | let event = ring.data[head & (EVENTS_CAPACITY - 1)]; |
|
| 176 | + | let next = ((head as u64 + 1) & 0xffffffff) as u32; |
|
| 177 | + | storeRelease(&mut ring.head, next); |
|
| 178 | + | lockRelease(&mut ring.consumer); |
|
| 179 | + | return event; |
|
| 180 | + | } |
kernel/user/sys.ras
added
+115 -0
| 1 | + | // Trusted RV64 primitives for the user::sys module. |
|
| 2 | + | // Register bridges, ordered memory access, and checked-address materialization |
|
| 3 | + | // use the compiler's native calling convention. |
|
| 4 | + | .text; |
|
| 5 | + | ||
| 6 | + | // a0=number, a1=thin pointer to seven u64 arguments, a2=Reply pointer. |
|
| 7 | + | // Reply is error@0 then four u64 values@8..32. Preserve ordinary callee-saved |
|
| 8 | + | // registers by never changing them. Save continuation/output on the aligned |
|
| 9 | + | // private stack: no caller-saved register is assumed to survive an ecall. |
|
| 10 | + | .export @"user::sys::rawCall"; |
|
| 11 | + | @"user::sys::rawCall" |
|
| 12 | + | addi %sp %sp -16; |
|
| 13 | + | sd %a2 0(%sp); |
|
| 14 | + | sd %ra 8(%sp); |
|
| 15 | + | mv %a7 %a0; |
|
| 16 | + | mv %t0 %a1; |
|
| 17 | + | ld %a0 0(%t0); |
|
| 18 | + | ld %a1 8(%t0); |
|
| 19 | + | ld %a2 16(%t0); |
|
| 20 | + | ld %a3 24(%t0); |
|
| 21 | + | ld %a4 32(%t0); |
|
| 22 | + | ld %a5 40(%t0); |
|
| 23 | + | ld %a6 48(%t0); |
|
| 24 | + | ecall; |
|
| 25 | + | ld %t0 0(%sp); |
|
| 26 | + | sd %a0 0(%t0); |
|
| 27 | + | sd %a1 8(%t0); |
|
| 28 | + | sd %a2 16(%t0); |
|
| 29 | + | sd %a3 24(%t0); |
|
| 30 | + | sd %a4 32(%t0); |
|
| 31 | + | ld %ra 8(%sp); |
|
| 32 | + | addi %sp %sp 16; |
|
| 33 | + | ret; |
|
| 34 | + | ||
| 35 | + | // Identity conversion; only the RAD wrappers supply validated mapped addresses. |
|
| 36 | + | .export @"user::sys::pointer"; |
|
| 37 | + | @"user::sys::pointer" |
|
| 38 | + | ret; |
|
| 39 | + | ||
| 40 | + | // Naturally aligned shared u32 accesses. Full fences match core::atomic. |
|
| 41 | + | .export @"user::sys::loadAcquire"; |
|
| 42 | + | @"user::sys::loadAcquire" |
|
| 43 | + | lwu %a0 0(%a0); |
|
| 44 | + | fence; |
|
| 45 | + | ret; |
|
| 46 | + | .export @"user::sys::storeRelease"; |
|
| 47 | + | @"user::sys::storeRelease" |
|
| 48 | + | fence; |
|
| 49 | + | sw %a1 0(%a0); |
|
| 50 | + | ret; |
|
| 51 | + | ||
| 52 | + | // The consumer lock is separate from kernel producer progress. Acquiring it |
|
| 53 | + | // returns its prior value; release does not change head or tail. |
|
| 54 | + | .export @"user::sys::lockAcquire"; |
|
| 55 | + | @"user::sys::lockAcquire" |
|
| 56 | + | li %t0 1; |
|
| 57 | + | amoswap.w.aq %a0 %t0 0(%a0); |
|
| 58 | + | slli %a0 %a0 32; |
|
| 59 | + | srli %a0 %a0 32; |
|
| 60 | + | ret; |
|
| 61 | + | .export @"user::sys::lockRelease"; |
|
| 62 | + | @"user::sys::lockRelease" |
|
| 63 | + | amoswap.w.rl %zero %zero 0(%a0); |
|
| 64 | + | ret; |
|
| 65 | + | ||
| 66 | + | // One-shot MMIO accesses. RAD obtains each address from call 61 immediately |
|
| 67 | + | // before invoking its matching primitive; no reusable Device pointer escapes. |
|
| 68 | + | .export @"user::sys::read8"; |
|
| 69 | + | @"user::sys::read8" |
|
| 70 | + | fence; |
|
| 71 | + | lbu %a0 0(%a0); |
|
| 72 | + | fence; |
|
| 73 | + | ret; |
|
| 74 | + | .export @"user::sys::read16"; |
|
| 75 | + | @"user::sys::read16" |
|
| 76 | + | fence; |
|
| 77 | + | lhu %a0 0(%a0); |
|
| 78 | + | fence; |
|
| 79 | + | ret; |
|
| 80 | + | .export @"user::sys::read32"; |
|
| 81 | + | @"user::sys::read32" |
|
| 82 | + | fence; |
|
| 83 | + | lwu %a0 0(%a0); |
|
| 84 | + | fence; |
|
| 85 | + | ret; |
|
| 86 | + | .export @"user::sys::read64"; |
|
| 87 | + | @"user::sys::read64" |
|
| 88 | + | fence; |
|
| 89 | + | ld %a0 0(%a0); |
|
| 90 | + | fence; |
|
| 91 | + | ret; |
|
| 92 | + | .export @"user::sys::write8"; |
|
| 93 | + | @"user::sys::write8" |
|
| 94 | + | fence; |
|
| 95 | + | sb %a1 0(%a0); |
|
| 96 | + | fence; |
|
| 97 | + | ret; |
|
| 98 | + | .export @"user::sys::write16"; |
|
| 99 | + | @"user::sys::write16" |
|
| 100 | + | fence; |
|
| 101 | + | sh %a1 0(%a0); |
|
| 102 | + | fence; |
|
| 103 | + | ret; |
|
| 104 | + | .export @"user::sys::write32"; |
|
| 105 | + | @"user::sys::write32" |
|
| 106 | + | fence; |
|
| 107 | + | sw %a1 0(%a0); |
|
| 108 | + | fence; |
|
| 109 | + | ret; |
|
| 110 | + | .export @"user::sys::write64"; |
|
| 111 | + | @"user::sys::write64" |
|
| 112 | + | fence; |
|
| 113 | + | sd %a1 0(%a0); |
|
| 114 | + | fence; |
|
| 115 | + | ret; |
lib/std/arch/rv64/emit.rad
+3 -0
| 110 | 110 | pendingCalls: *mut [PendingCall], |
|
| 111 | 111 | /// Assembly jumps needing offset patching. |
|
| 112 | 112 | pendingJumps: *mut [PendingJump], |
|
| 113 | 113 | /// Function address loads needing offset patching. |
|
| 114 | 114 | pendingAddrLoads: *mut [PendingAddrLoad], |
|
| 115 | + | /// Current image's private data offsets relative to gp, or nil for fixed data. |
|
| 116 | + | instanceData: ?*dict::Dict, |
|
| 115 | 117 | /// Block label tracking. |
|
| 116 | 118 | labels: labels::Labels, |
|
| 117 | 119 | /// Function start positions for printing. |
|
| 118 | 120 | funcs: *mut [types::FuncAddr], |
|
| 119 | 121 | /// Debug entries mapping PCs to source locations. |
| 204 | 206 | codeLen: 0, |
|
| 205 | 207 | pendingBranches: @sliceOf((pendingBranches as *mut [PendingBranch]).ptr, 0, MAX_PENDING), |
|
| 206 | 208 | pendingCalls: @sliceOf((pendingCalls as *mut [PendingCall]).ptr, 0, MAX_PENDING), |
|
| 207 | 209 | pendingJumps: @sliceOf((pendingJumps as *mut [PendingJump]).ptr, 0, MAX_PENDING), |
|
| 208 | 210 | pendingAddrLoads: @sliceOf((pendingAddrLoads as *mut [PendingAddrLoad]).ptr, 0, MAX_PENDING), |
|
| 211 | + | instanceData: nil, |
|
| 209 | 212 | labels: labels::init(blockOffsets as *mut [i32], funcEntries as *mut [dict::Entry]), |
|
| 210 | 213 | funcs: @sliceOf((funcs as *mut [types::FuncAddr]).ptr, 0, MAX_FUNCS), |
|
| 211 | 214 | debugEntries, |
|
| 212 | 215 | debugEntriesLen: 0, |
|
| 213 | 216 | }; |
lib/std/arch/rv64/isel.rad
+14 -0
| 26 | 26 | //! loadVal(rd, val) -> Reg |
|
| 27 | 27 | //! Force an [`il::Val`] into a specific register `rd`. Built on [`resolveVal`] + [`emitMv`]. |
|
| 28 | 28 | //! Used when the instruction requires the value in `rd` (e.g. `sub rd, rd, rs2`). |
|
| 29 | 29 | ||
| 30 | 30 | use std::mem; |
|
| 31 | + | use std::collections::dict; |
|
| 31 | 32 | use std::lang::il; |
|
| 32 | 33 | use std::lang::gen; |
|
| 33 | 34 | use std::lang::gen::regalloc; |
|
| 34 | 35 | use std::lang::gen::labels; |
|
| 35 | 36 |
| 169 | 170 | } |
|
| 170 | 171 | emit::loadImm(s.e, scratch, imm); |
|
| 171 | 172 | return scratch; |
|
| 172 | 173 | }, |
|
| 173 | 174 | case il::Val::DataSym(name) => { |
|
| 175 | + | if let offsets = s.e.instanceData { |
|
| 176 | + | if let offset = dict::get(offsets, name) { |
|
| 177 | + | assert offset >= 0; |
|
| 178 | + | if offset == 0 { return super::GP; } |
|
| 179 | + | if offset <= super::MAX_IMM { |
|
| 180 | + | emit::emit(s.e, encode::addi(scratch, super::GP, offset)); |
|
| 181 | + | } else { |
|
| 182 | + | emit::loadImm(s.e, scratch, offset as i64); |
|
| 183 | + | emit::emit(s.e, encode::add(scratch, super::GP, scratch)); |
|
| 184 | + | } |
|
| 185 | + | return scratch; |
|
| 186 | + | } |
|
| 187 | + | } |
|
| 174 | 188 | emit::recordDataAddrLoad(s.e, name, scratch); |
|
| 175 | 189 | return scratch; |
|
| 176 | 190 | }, |
|
| 177 | 191 | case il::Val::FnAddr(name) => { |
|
| 178 | 192 | emit::recordAddrLoad(s.e, name, scratch); |
seed/update
+2 -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 -mod compiler/radiance/binary.rad -entry radiance" |
|
| 44 | + | COMPILER_MODS="$(for source in compiler/radiance.rad compiler/radiance/*.rad compiler/radiance/*/*.rad; do if [ -f "$source" ]; then printf '%s %s ' -mod "$source"; fi; done)" |
|
| 45 | + | OPTS="-pkg std ${STD_MODS} -pkg radiance ${COMPILER_MODS} -entry radiance" |
|
| 45 | 46 | ||
| 46 | 47 | # --------------------------------------------------------------------------- |
|
| 47 | 48 | # Emulator settings |
|
| 48 | 49 | # --------------------------------------------------------------------------- |
|
| 49 | 50 |