compiler: Emit separate binary RIL packages
d1be47b57c298a91a6623c05a13e174ed27cba8175fe5090d9553db0609e2d40
1 parent
eba4a943
compiler/radiance.rad
+145 -1
| 17 | 17 | use std::arch::rv64; |
|
| 18 | 18 | use std::arch::rv64::asm; |
|
| 19 | 19 | use std::arch::rv64::printer; |
|
| 20 | 20 | use std::lang::sexpr; |
|
| 21 | 21 | use std::lang::gen::data; |
|
| 22 | + | use std::lang::il::binary; |
|
| 23 | + | use std::lang::il::binary::collect; |
|
| 24 | + | use std::lang::il::binary::program; |
|
| 22 | 25 | use std::lang::gen::types; |
|
| 23 | 26 | use std::sys; |
|
| 24 | 27 | use std::sys::unix; |
|
| 25 | 28 | use std::collections::dict; |
|
| 26 | 29 |
| 67 | 70 | /// Code generation storage. |
|
| 68 | 71 | unsafe static CODEGEN_DATA_SYMS: [data::DataSym; data::MAX_DATA_SYMS] = undefined; |
|
| 69 | 72 | /// Hash table entries for data symbol lookup. |
|
| 70 | 73 | unsafe static CODEGEN_DATA_SYM_ENTRIES: [dict::Entry; data::DATA_SYM_TABLE_SIZE] = undefined; |
|
| 71 | 74 | ||
| 75 | + | /// Maximum number of exported definitions in one binary package. |
|
| 76 | + | constant MAX_PACKAGE_EXPORTS: u32 = 8192; |
|
| 77 | + | /// Maximum number of indexed names in one binary package. |
|
| 78 | + | constant MAX_PACKAGE_SYMBOLS: u32 = 16384; |
|
| 79 | + | /// Export workspace reused for each binary package. |
|
| 80 | + | unsafe static PACKAGE_EXPORTS: [binary::Export; MAX_PACKAGE_EXPORTS] = undefined; |
|
| 81 | + | /// Symbol workspace reused for each binary package. |
|
| 82 | + | unsafe static PACKAGE_SYMBOLS: [*[u8]; MAX_PACKAGE_SYMBOLS] = undefined; |
|
| 83 | + | ||
| 72 | 84 | /// Debug info file extension. |
|
| 73 | 85 | constant DEBUG_EXT: *[u8] = ".debug"; |
|
| 74 | 86 | ||
| 75 | 87 | /// Maximum rodata size (4MB). |
|
| 76 | 88 | constant MAX_RO_DATA_SIZE: u32 = 4194304; |
| 96 | 108 | /// Symbol name exported for startup code to call the semantic entry function. |
|
| 97 | 109 | export constant DEFAULT_ENTRY_SYMBOL: *[u8] = "::default"; |
|
| 98 | 110 | ||
| 99 | 111 | /// Usage string. |
|
| 100 | 112 | constant USAGE: *[u8] = |
|
| 101 | - | "usage: radiance -pkg <name> [-start <input.ras>] -mod <input>.. [-pkg <name> -mod <input>..] -entry <pkg> -o <output>\n"; |
|
| 113 | + | "usage: radiance -pkg <name> [-start <input.ras>] -mod <input>.. [-pkg <name> -mod <input>..] -entry <pkg> [-o <output> | -ril <directory>]\n"; |
|
| 102 | 114 | ||
| 103 | 115 | /// Compiler error. |
|
| 104 | 116 | union Error: Copy { |
|
| 105 | 117 | Other, |
|
| 106 | 118 | } |
| 159 | 171 | config: resolver::Config, |
|
| 160 | 172 | /// What to dump during compilation. |
|
| 161 | 173 | dump: Dump, |
|
| 162 | 174 | /// Output path for binary. |
|
| 163 | 175 | outputPath: ?*[u8], |
|
| 176 | + | /// Output directory for separate binary RIL packages. |
|
| 177 | + | rilDirectory: ?*[u8], |
|
| 164 | 178 | /// Whether to emit debug info (.debug file). |
|
| 165 | 179 | debug: bool, |
|
| 166 | 180 | } |
|
| 167 | 181 | ||
| 168 | 182 | /// Root module info for a package. |
| 293 | 307 | arena: &mut ast::NodeArena |
|
| 294 | 308 | ) -> CompileContext throws (Error) { |
|
| 295 | 309 | let mut buildTest = false; |
|
| 296 | 310 | let mut debugEnabled = false; |
|
| 297 | 311 | let mut outputPath: ?*[u8] = nil; |
|
| 312 | + | let mut rilDirectory: ?*[u8] = nil; |
|
| 298 | 313 | let mut dump = Dump::None; |
|
| 299 | 314 | let mut entryPkgName: ?*[u8] = nil; |
|
| 300 | 315 | ||
| 301 | 316 | // Per-package source path tracking. |
|
| 302 | 317 | let mut inputs: [PackageInput; MAX_PACKAGES] = undefined; |
| 359 | 374 | } else if mem::eq(arg, "-debug") { |
|
| 360 | 375 | set debugEnabled = true; |
|
| 361 | 376 | } else if mem::eq(arg, "-o") { |
|
| 362 | 377 | try nextArg(args, &mut idx, &["`-o` requires an output path"]); |
|
| 363 | 378 | set outputPath = args[idx]; |
|
| 379 | + | } else if mem::eq(arg, "-ril") { |
|
| 380 | + | try nextArg(args, &mut idx, &["`-ril` requires an output directory"]); |
|
| 381 | + | set rilDirectory = args[idx]; |
|
| 364 | 382 | } else if mem::eq(arg, "-dump") { |
|
| 365 | 383 | try nextArg(args, &mut idx, &["`-dump` requires a mode (eg. ast)"]); |
|
| 366 | 384 | let mode = args[idx]; |
|
| 367 | 385 | if mem::eq(mode, "ast") { |
|
| 368 | 386 | set dump = Dump::Ast; |
| 415 | 433 | for i in 0..pkgCount { |
|
| 416 | 434 | if i <> entryIdx and inputs[i].startupPath <> nil { |
|
| 417 | 435 | throw error(&["`-start` is only supported on the entry package"]); |
|
| 418 | 436 | } |
|
| 419 | 437 | } |
|
| 438 | + | if rilDirectory <> nil and (outputPath <> nil or dump <> Dump::None) { |
|
| 439 | + | throw error(&["`-ril` requires a separate invocation from `-o` or `-dump`"]); |
|
| 440 | + | } |
|
| 420 | 441 | let graph = module::moduleGraph(&mut MODULE_ENTRIES[..], arena); |
|
| 421 | 442 | let mut ctx = CompileContext { |
|
| 422 | 443 | packages: undefined, |
|
| 423 | 444 | inputs, |
|
| 424 | 445 | packageCount: pkgCount, |
|
| 425 | 446 | entryPkgIdx, |
|
| 426 | 447 | graph, |
|
| 427 | 448 | config: resolver::Config { buildTest }, |
|
| 428 | 449 | dump, |
|
| 429 | 450 | outputPath, |
|
| 451 | + | rilDirectory, |
|
| 430 | 452 | debug: debugEnabled, |
|
| 431 | 453 | }; |
|
| 432 | 454 | // Initialize and parse all packages. |
|
| 433 | 455 | let mut sourceArena = alloc::new(&mut MODULE_SOURCES[..]); |
|
| 434 | 456 | for i in 0..pkgCount { |
| 1025 | 1047 | pkgLog(entryPkg, &["generating code", "(", path, ")", ".."]); |
|
| 1026 | 1048 | } |
|
| 1027 | 1049 | return rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]); |
|
| 1028 | 1050 | } |
|
| 1029 | 1051 | ||
| 1052 | + | /// Source exports selected for one binary RIL package. |
|
| 1053 | + | record PackageExports: Copy { |
|
| 1054 | + | /// Number of initialized entries in the caller's export table. |
|
| 1055 | + | count: u32, |
|
| 1056 | + | /// Default function entry. |
|
| 1057 | + | entry: ?*[u8], |
|
| 1058 | + | } |
|
| 1059 | + | ||
| 1060 | + | /// Collect exported definitions and the default entry from a package's source modules. |
|
| 1061 | + | unsafe fn packageExports( |
|
| 1062 | + | ctx: &CompileContext, |
|
| 1063 | + | pkg: &package::Package, |
|
| 1064 | + | ilProgram: &il::Program, |
|
| 1065 | + | exports: &mut [binary::Export], |
|
| 1066 | + | arena: &mut alloc::Arena |
|
| 1067 | + | ) -> PackageExports throws (Error) { |
|
| 1068 | + | let mut count: u32 = 0; |
|
| 1069 | + | let mut entryName: ?*[u8] = nil; |
|
| 1070 | + | for i in 0..ctx.graph.entriesLen { |
|
| 1071 | + | let modEntry = module::get(&ctx.graph, i as u16) else continue; |
|
| 1072 | + | if modEntry.packageId <> pkg.id { continue; } |
|
| 1073 | + | let root = modEntry.ast else continue; |
|
| 1074 | + | let case ast::NodeValue::Block(block) = root.value else continue; |
|
| 1075 | + | for node in block.statements { |
|
| 1076 | + | let mut ident: ?*ast::Node = nil; |
|
| 1077 | + | let mut attrs: ?ast::Attributes = nil; |
|
| 1078 | + | let mut kind = binary::ExportKind::Function; |
|
| 1079 | + | match node.value { |
|
| 1080 | + | case ast::NodeValue::FnDecl(decl) => { set ident = decl.name; set attrs = decl.attrs; }, |
|
| 1081 | + | case ast::NodeValue::ConstDecl(decl) => { |
|
| 1082 | + | set ident = decl.ident; set attrs = decl.attrs; set kind = binary::ExportKind::Data; |
|
| 1083 | + | }, |
|
| 1084 | + | case ast::NodeValue::StaticDecl(decl) => { |
|
| 1085 | + | set ident = decl.ident; set attrs = decl.attrs; set kind = binary::ExportKind::Data; |
|
| 1086 | + | }, |
|
| 1087 | + | else => continue, |
|
| 1088 | + | } |
|
| 1089 | + | let attributes = attrs else continue; |
|
| 1090 | + | let isDefault = ast::attributesContains(&attributes, ast::Attribute::Default) |
|
| 1091 | + | and pkg.rootModuleId == modEntry.id; |
|
| 1092 | + | if not isDefault and not ast::attributesContains(&attributes, ast::Attribute::Export) { |
|
| 1093 | + | continue; |
|
| 1094 | + | } |
|
| 1095 | + | let nameNode = ident else continue; |
|
| 1096 | + | let case ast::NodeValue::Ident(name) = nameNode.value else continue; |
|
| 1097 | + | let qualified = il::formatQualifiedName(arena, module::moduleQualifiedPath(modEntry), name); |
|
| 1098 | + | let mut present = false; |
|
| 1099 | + | match kind { |
|
| 1100 | + | case binary::ExportKind::Function => { |
|
| 1101 | + | for func in ilProgram.fns { |
|
| 1102 | + | if mem::eq(func.name, qualified) { set present = true; } |
|
| 1103 | + | } |
|
| 1104 | + | }, |
|
| 1105 | + | case binary::ExportKind::Data => { |
|
| 1106 | + | for item in ilProgram.data { |
|
| 1107 | + | if mem::eq(item.name, qualified) { set present = true; } |
|
| 1108 | + | } |
|
| 1109 | + | }, |
|
| 1110 | + | } |
|
| 1111 | + | if not present { continue; } |
|
| 1112 | + | if count == exports.len { throw error(&["too many package exports"]); } |
|
| 1113 | + | set exports[count] = binary::Export { name: qualified, kind }; |
|
| 1114 | + | set count += 1; |
|
| 1115 | + | if isDefault { set entryName = qualified; } |
|
| 1116 | + | } |
|
| 1117 | + | } |
|
| 1118 | + | return PackageExports { count, entry: entryName }; |
|
| 1119 | + | } |
|
| 1120 | + | ||
| 1121 | + | /// Emit one binary RIL file per package into an existing directory. |
|
| 1122 | + | unsafe fn emitPackages(ctx: *unsafe mut CompileContext, res: *unsafe mut resolver::Resolver, directory: *[u8]) throws (Error) { |
|
| 1123 | + | for i in 0..ctx.packageCount { |
|
| 1124 | + | if ctx.inputs[i].asmPathCount > 0 or ctx.inputs[i].startupPath <> nil { |
|
| 1125 | + | throw error(&["binary RIL output requires Radiance source modules"]); |
|
| 1126 | + | } |
|
| 1127 | + | } |
|
| 1128 | + | let unified = try lowerAllPackages(ctx, res); |
|
| 1129 | + | let allocator = alloc::arenaAllocator(&mut res.arena); |
|
| 1130 | + | for i in 0..ctx.packageCount { |
|
| 1131 | + | let pkg = &ctx.packages[i]; |
|
| 1132 | + | let mut dataItems: *mut [il::Data] = &mut []; |
|
| 1133 | + | let mut functions: *unsafe mut [*unsafe il::Fn] = &mut []; |
|
| 1134 | + | for item in unified.data { |
|
| 1135 | + | if ownsSymbol(pkg.name, item.name) { dataItems.append(item, allocator); } |
|
| 1136 | + | } |
|
| 1137 | + | for func in unified.fns { |
|
| 1138 | + | if ownsSymbol(pkg.name, func.name) { functions.append(func, allocator); } |
|
| 1139 | + | } |
|
| 1140 | + | let local = il::Program { data: &dataItems[..], fns: functions }; |
|
| 1141 | + | let selected = try packageExports(ctx, pkg, &local, &mut PACKAGE_EXPORTS[..], &mut res.arena); |
|
| 1142 | + | let mut dependencies: [*[u8]; MAX_PACKAGES] = undefined; |
|
| 1143 | + | let mut names = collect::new(&mut PACKAGE_SYMBOLS[..], &mut dependencies[..]); |
|
| 1144 | + | let image = try collect::package(&mut names, pkg.name, local, &PACKAGE_EXPORTS[..selected.count], selected.entry) catch { |
|
| 1145 | + | throw error(&["cannot collect binary RIL package", pkg.name]); |
|
| 1146 | + | }; |
|
| 1147 | + | let length = try program::encode(&mut FN_ARENA[..], &image) catch { |
|
| 1148 | + | throw error(&["binary RIL output capacity exceeded", pkg.name]); |
|
| 1149 | + | }; |
|
| 1150 | + | let mut path: [u8; MAX_PATH_LEN] = undefined; |
|
| 1151 | + | let mut pos: u32 = 0; |
|
| 1152 | + | for part in &[directory, "/", pkg.name, ".ril"] { |
|
| 1153 | + | set pos += try mem::copy(&mut path[pos..MAX_PATH_LEN - 1], part) catch { |
|
| 1154 | + | throw error(&["binary RIL output path is too long"]); |
|
| 1155 | + | }; |
|
| 1156 | + | } |
|
| 1157 | + | set path[pos] = 0; |
|
| 1158 | + | if not unix::writeFile(&path[..pos], &FN_ARENA[..length]) { |
|
| 1159 | + | throw error(&["cannot write binary RIL package", pkg.name]); |
|
| 1160 | + | } |
|
| 1161 | + | } |
|
| 1162 | + | } |
|
| 1163 | + | ||
| 1164 | + | /// Match a qualified definition to its package name. |
|
| 1165 | + | fn ownsSymbol(owner: *[u8], name: *[u8]) -> bool { |
|
| 1166 | + | let suffix = mem::stripPrefix(owner, name) else return false; |
|
| 1167 | + | return suffix.len > 2 and suffix[0] == ':' and suffix[1] == ':'; |
|
| 1168 | + | } |
|
| 1169 | + | ||
| 1030 | 1170 | /// Lower, optionally dump, and optionally generate binary output. |
|
| 1031 | 1171 | unsafe fn compile( |
|
| 1032 | 1172 | ctx: *unsafe mut CompileContext, |
|
| 1033 | 1173 | res: *unsafe mut resolver::Resolver, |
|
| 1034 | 1174 | fnArena: &mut alloc::Arena |
|
| 1035 | 1175 | ) throws (Error) { |
|
| 1036 | 1176 | let entryPkg = try getEntryPackage(ctx); |
|
| 1177 | + | if let directory = ctx.rilDirectory { |
|
| 1178 | + | try emitPackages(ctx, res, directory); |
|
| 1179 | + | return; |
|
| 1180 | + | } |
|
| 1037 | 1181 | let mut out = sexpr::Output::Stdout; |
|
| 1038 | 1182 | ||
| 1039 | 1183 | if ctx.dump == Dump::Il { |
|
| 1040 | 1184 | // Lower all packages into a single unified IL program for dumping. |
|
| 1041 | 1185 | let program = try lowerAllPackages(ctx, res); |
lib/std/lang/il/binary.rad
+1 -0
| 3 | 3 | //! Optional fields use a byte: zero for absent, one for present. |
|
| 4 | 4 | ||
| 5 | 5 | export mod writer; |
|
| 6 | 6 | export mod reader; |
|
| 7 | 7 | export mod program; |
|
| 8 | + | export mod collect; |
|
| 8 | 9 | ||
| 9 | 10 | use std::lang::il; |
|
| 10 | 11 | @test export mod tests; |
|
| 11 | 12 | @test export mod decodeTests; |
|
| 12 | 13 |
lib/std/lang/il/binary/collect.rad
added
+186 -0
| 1 | + | //! Deterministic symbol and dependency collection for package-local IL. |
|
| 2 | + | ||
| 3 | + | use std::mem; |
|
| 4 | + | use std::lang::il; |
|
| 5 | + | use std::lang::il::binary; |
|
| 6 | + | ||
| 7 | + | /// Caller-owned storage for a package's indexed names. |
|
| 8 | + | /// Both tables must outlive the collector and all packages that use them. |
|
| 9 | + | export record Names: Copy { |
|
| 10 | + | /// Unique names in first-use order. |
|
| 11 | + | symbols: *unsafe mut [*[u8]], |
|
| 12 | + | /// Number of initialized symbol entries. |
|
| 13 | + | symbolCount: u32, |
|
| 14 | + | /// Unique dependency names in first-use order. |
|
| 15 | + | dependencies: *unsafe mut [*[u8]], |
|
| 16 | + | /// Number of initialized dependency entries. |
|
| 17 | + | dependencyCount: u32, |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// Start collection with empty caller-provided tables. |
|
| 21 | + | /// The caller must retain exclusive access to both tables during collection. |
|
| 22 | + | export unsafe fn new(symbols: &mut [*[u8]], dependencies: &mut [*[u8]]) -> Names { |
|
| 23 | + | return Names { symbols: symbols as *unsafe mut [*[u8]], symbolCount: 0, dependencies: dependencies as *unsafe mut [*[u8]], dependencyCount: 0 }; |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// Add a unique name and check the symbol table's capacity. |
|
| 27 | + | unsafe fn add(names: &mut Names, name: *[u8]) throws (binary::Error) { |
|
| 28 | + | if name.len == 0 { |
|
| 29 | + | throw binary::Error::Invalid; |
|
| 30 | + | } |
|
| 31 | + | for i in 0..names.symbolCount { |
|
| 32 | + | if mem::eq(names.symbols[i], name) { |
|
| 33 | + | return; |
|
| 34 | + | } |
|
| 35 | + | } |
|
| 36 | + | if names.symbolCount == names.symbols.len { |
|
| 37 | + | throw binary::Error::Capacity; |
|
| 38 | + | } |
|
| 39 | + | set names.symbols[names.symbolCount] = name; |
|
| 40 | + | set names.symbolCount += 1; |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | /// Add a qualified reference and its owning package dependency. |
|
| 44 | + | unsafe fn reference(names: &mut Names, owner: *[u8], name: *[u8]) throws (binary::Error) { |
|
| 45 | + | try add(names, name); |
|
| 46 | + | for i in 0..name.len { |
|
| 47 | + | if name[i] == ':' and i + 1 < name.len and name[i + 1] == ':' { |
|
| 48 | + | if i == 0 or i + 2 == name.len { |
|
| 49 | + | throw binary::Error::Invalid; |
|
| 50 | + | } |
|
| 51 | + | let dependency = &name[..i]; |
|
| 52 | + | if mem::eq(dependency, owner) { |
|
| 53 | + | return; |
|
| 54 | + | } |
|
| 55 | + | try add(names, dependency); |
|
| 56 | + | for j in 0..names.dependencyCount { |
|
| 57 | + | if mem::eq(names.dependencies[j], dependency) { |
|
| 58 | + | return; |
|
| 59 | + | } |
|
| 60 | + | } |
|
| 61 | + | if names.dependencyCount == names.dependencies.len { |
|
| 62 | + | throw binary::Error::Capacity; |
|
| 63 | + | } |
|
| 64 | + | set names.dependencies[names.dependencyCount] = dependency; |
|
| 65 | + | set names.dependencyCount += 1; |
|
| 66 | + | return; |
|
| 67 | + | } |
|
| 68 | + | } |
|
| 69 | + | throw binary::Error::Invalid; |
|
| 70 | + | } |
|
| 71 | + | ||
| 72 | + | /// Collect names referenced by one IL value. |
|
| 73 | + | unsafe fn value(names: &mut Names, owner: *[u8], item: il::Val) throws (binary::Error) { |
|
| 74 | + | match item { |
|
| 75 | + | case il::Val::DataSym(name) => try reference(names, owner, name), |
|
| 76 | + | case il::Val::FnAddr(name) => try reference(names, owner, name), |
|
| 77 | + | else => {}, |
|
| 78 | + | } |
|
| 79 | + | } |
|
| 80 | + | ||
| 81 | + | /// Collect names from an instruction's variable-length operand sequence. |
|
| 82 | + | unsafe fn values(names: &mut Names, owner: *[u8], items: &[il::Val]) throws (binary::Error) { |
|
| 83 | + | for item in items { |
|
| 84 | + | try value(names, owner, item); |
|
| 85 | + | } |
|
| 86 | + | } |
|
| 87 | + | ||
| 88 | + | /// Collect all symbolic instruction operands. |
|
| 89 | + | unsafe fn instruction(names: &mut Names, owner: *[u8], instr: il::Instr) throws (binary::Error) { |
|
| 90 | + | match instr { |
|
| 91 | + | case il::Instr::Reserve { size, .. } => try value(names, owner, size), |
|
| 92 | + | case il::Instr::Blit { size, .. } => try value(names, owner, size), |
|
| 93 | + | case il::Instr::Store { src, .. } => try value(names, owner, src), |
|
| 94 | + | case il::Instr::Copy { val, .. } => try value(names, owner, val), |
|
| 95 | + | case il::Instr::Zext { val, .. } => try value(names, owner, val), |
|
| 96 | + | case il::Instr::Sext { val, .. } => try value(names, owner, val), |
|
| 97 | + | case il::Instr::BinOp { a, b, .. } => { |
|
| 98 | + | try value(names, owner, a); |
|
| 99 | + | try value(names, owner, b); |
|
| 100 | + | }, |
|
| 101 | + | case il::Instr::UnOp { a, .. } => try value(names, owner, a), |
|
| 102 | + | case il::Instr::Call { func, args, .. } => { |
|
| 103 | + | try value(names, owner, func); |
|
| 104 | + | try values(names, owner, args); |
|
| 105 | + | }, |
|
| 106 | + | case il::Instr::Ret { val } => { |
|
| 107 | + | if let item = val { |
|
| 108 | + | try value(names, owner, item); |
|
| 109 | + | } |
|
| 110 | + | }, |
|
| 111 | + | case il::Instr::Jmp { args, .. } => try values(names, owner, args), |
|
| 112 | + | case il::Instr::Br { a, b, thenArgs, elseArgs, .. } => { |
|
| 113 | + | try value(names, owner, a); |
|
| 114 | + | try value(names, owner, b); |
|
| 115 | + | try values(names, owner, thenArgs); |
|
| 116 | + | try values(names, owner, elseArgs); |
|
| 117 | + | }, |
|
| 118 | + | case il::Instr::Switch { val, defaultArgs, cases, .. } => { |
|
| 119 | + | try value(names, owner, val); |
|
| 120 | + | try values(names, owner, defaultArgs); |
|
| 121 | + | for branch in cases { |
|
| 122 | + | try values(names, owner, branch.args); |
|
| 123 | + | } |
|
| 124 | + | }, |
|
| 125 | + | case il::Instr::Ecall { num, a0, a1, a2, a3, .. } => { |
|
| 126 | + | try value(names, owner, num); |
|
| 127 | + | try value(names, owner, a0); |
|
| 128 | + | try value(names, owner, a1); |
|
| 129 | + | try value(names, owner, a2); |
|
| 130 | + | try value(names, owner, a3); |
|
| 131 | + | }, |
|
| 132 | + | case il::Instr::Load { .. }, il::Instr::Sload { .. }, il::Instr::Unreachable, |
|
| 133 | + | il::Instr::Ebreak, il::Instr::MemoryFence => {}, |
|
| 134 | + | } |
|
| 135 | + | } |
|
| 136 | + | ||
| 137 | + | /// Check that a definition belongs to the selected package. |
|
| 138 | + | unsafe fn definition(names: &mut Names, owner: *[u8], name: *[u8]) throws (binary::Error) { |
|
| 139 | + | let rest = mem::stripPrefix(owner, name) else { |
|
| 140 | + | throw binary::Error::Invalid; |
|
| 141 | + | }; |
|
| 142 | + | if rest.len < 3 or rest[0] <> ':' or rest[1] <> ':' { |
|
| 143 | + | throw binary::Error::Invalid; |
|
| 144 | + | } |
|
| 145 | + | try add(names, name); |
|
| 146 | + | } |
|
| 147 | + | ||
| 148 | + | /// Build package tables from local definitions and their qualified references. |
|
| 149 | + | /// Returned tables borrow `names`. Reset the collector before building another package. |
|
| 150 | + | export unsafe fn package( |
|
| 151 | + | names: &mut Names, |
|
| 152 | + | owner: *[u8], |
|
| 153 | + | program: il::Program, |
|
| 154 | + | exports: &[binary::Export], |
|
| 155 | + | entry: ?*[u8] |
|
| 156 | + | ) -> binary::Package throws (binary::Error) { |
|
| 157 | + | try add(names, owner); |
|
| 158 | + | for item in exports { |
|
| 159 | + | try definition(names, owner, item.name); |
|
| 160 | + | } |
|
| 161 | + | if let name = entry { |
|
| 162 | + | try definition(names, owner, name); |
|
| 163 | + | } |
|
| 164 | + | for item in program.data { |
|
| 165 | + | try definition(names, owner, item.name); |
|
| 166 | + | for data in item.values { |
|
| 167 | + | match data.item { |
|
| 168 | + | case il::DataItem::Sym(name) => try reference(names, owner, name), |
|
| 169 | + | case il::DataItem::Fn(name) => try reference(names, owner, name), |
|
| 170 | + | else => {}, |
|
| 171 | + | } |
|
| 172 | + | } |
|
| 173 | + | } |
|
| 174 | + | for func in program.fns { |
|
| 175 | + | try definition(names, owner, func.name); |
|
| 176 | + | for block in func.blocks { |
|
| 177 | + | for instr in block.instrs { |
|
| 178 | + | try instruction(names, owner, instr); |
|
| 179 | + | } |
|
| 180 | + | } |
|
| 181 | + | } |
|
| 182 | + | return binary::Package { |
|
| 183 | + | symbols: &names.symbols[..names.symbolCount], name: owner, |
|
| 184 | + | dependencies: &names.dependencies[..names.dependencyCount], exports: exports as *unsafe [binary::Export], entry, program, |
|
| 185 | + | }; |
|
| 186 | + | } |
lib/std/lang/il/binary/decodeTests.rad
+41 -0
| 4 | 4 | use std::lang::alloc; |
|
| 5 | 5 | use std::lang::il; |
|
| 6 | 6 | use std::lang::il::binary; |
|
| 7 | 7 | use std::lang::il::binary::reader; |
|
| 8 | 8 | use std::lang::il::binary::program; |
|
| 9 | + | use std::lang::il::binary::collect; |
|
| 9 | 10 | ||
| 10 | 11 | /// Decode arena backing storage. Tests reset it before each use. |
|
| 11 | 12 | static MEMORY: [u8; 2048] = [0; 2048]; |
|
| 12 | 13 | ||
| 13 | 14 | /// Structural limits for small test programs. |
| 302 | 303 | }; |
|
| 303 | 304 | try testing::expect(failed); |
|
| 304 | 305 | } |
|
| 305 | 306 | } |
|
| 306 | 307 | ||
| 308 | + | /// Check symbol collection capacity and package ownership failures. |
|
| 309 | + | @test unsafe fn collectionBounds() throws (testing::TestError) { |
|
| 310 | + | let mut symbols: [*[u8]; 8] = undefined; |
|
| 311 | + | let mut dependencies: [*[u8]; 1] = undefined; |
|
| 312 | + | let empty = il::Program { data: &[], fns: &[] }; |
|
| 313 | + | let mut names = collect::new(&mut symbols[..0], &mut dependencies[..]); |
|
| 314 | + | let mut failed = false; |
|
| 315 | + | try collect::package(&mut names, "p", empty, &[], nil) catch err { |
|
| 316 | + | try testing::expect(err == binary::Error::Capacity); |
|
| 317 | + | set failed = true; |
|
| 318 | + | }; |
|
| 319 | + | try testing::expect(failed); |
|
| 320 | + | let item = il::Data { |
|
| 321 | + | name: "p::data", size: 8, alignment: 8, readOnly: false, isZeroInit: false, |
|
| 322 | + | values: &[il::DataValue { item: il::DataItem::Fn("dep::fn"), count: 1 }], |
|
| 323 | + | }; |
|
| 324 | + | let local = il::Program { data: retainData(&[item]), fns: &[] }; |
|
| 325 | + | set names = collect::new(&mut symbols[..], &mut dependencies[..0]); |
|
| 326 | + | set failed = false; |
|
| 327 | + | try collect::package(&mut names, "p", local, &[], nil) catch err { |
|
| 328 | + | try testing::expect(err == binary::Error::Capacity); |
|
| 329 | + | set failed = true; |
|
| 330 | + | }; |
|
| 331 | + | try testing::expect(failed); |
|
| 332 | + | set names = collect::new(&mut symbols[..], &mut dependencies[..]); |
|
| 333 | + | set failed = false; |
|
| 334 | + | try collect::package(&mut names, "other", local, &[], nil) catch err { |
|
| 335 | + | try testing::expect(err == binary::Error::Invalid); |
|
| 336 | + | set failed = true; |
|
| 337 | + | }; |
|
| 338 | + | try testing::expect(failed); |
|
| 339 | + | set names = collect::new(&mut symbols[..], &mut dependencies[..]); |
|
| 340 | + | let package = try collect::package(&mut names, "p", local, &[], nil) catch { |
|
| 341 | + | throw testing::TestError::Failed; |
|
| 342 | + | }; |
|
| 343 | + | try testing::expect(package.dependencies.len == 1); |
|
| 344 | + | try testing::expectBytesEq(package.dependencies[0], "dep"); |
|
| 345 | + | try testing::expect(package.symbols.len == 4); |
|
| 346 | + | } |
|
| 347 | + | ||
| 307 | 348 | /// Copy data fixtures into stable storage for the package descriptor. |
|
| 308 | 349 | unsafe fn retainData(items: &[il::Data]) -> *[il::Data] { |
|
| 309 | 350 | unsafe static DATA: [il::Data; 2] = undefined; |
|
| 310 | 351 | assert items.len <= DATA.len; |
|
| 311 | 352 | for item, i in items { set DATA[i] = item; } |
std.lib
+1 -0
| 33 | 33 | lib/std/lang/il/printer.rad |
|
| 34 | 34 | lib/std/lang/il/binary.rad |
|
| 35 | 35 | lib/std/lang/il/binary/writer.rad |
|
| 36 | 36 | lib/std/lang/il/binary/reader.rad |
|
| 37 | 37 | lib/std/lang/il/binary/program.rad |
|
| 38 | + | lib/std/lang/il/binary/collect.rad |
|
| 38 | 39 | lib/std/lang/resolver.rad |
|
| 39 | 40 | lib/std/lang/resolver/printer.rad |
|
| 40 | 41 | lib/std/lang/lower.rad |
|
| 41 | 42 | lib/std/lang/module.rad |
|
| 42 | 43 | lib/std/lang/module/printer.rad |