compiler/
radiance/
radiance.rad
50.7 KiB
kernel/
lib/
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
9.2 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.5 KiB
std.lib.test
662 B
compiler/radiance.rad
raw
| 1 | //! Radiance compiler front-end. |
| 2 | mod codegen; |
| 3 | |
| 4 | use std::mem; |
| 5 | use std::fmt; |
| 6 | use std::io; |
| 7 | use std::lang::alloc; |
| 8 | use std::lang::ast; |
| 9 | use std::lang::parser; |
| 10 | use std::lang::scanner; |
| 11 | use std::lang::resolver; |
| 12 | use std::lang::module; |
| 13 | use std::lang::strings; |
| 14 | use std::lang::package; |
| 15 | use std::lang::il; |
| 16 | use std::lang::lower; |
| 17 | use std::arch::rv64; |
| 18 | use std::arch::rv64::asm; |
| 19 | use std::arch::rv64::emit; |
| 20 | use std::arch::rv64::printer; |
| 21 | use std::lang::sexpr; |
| 22 | use std::lang::gen::data; |
| 23 | use std::lang::il::binary; |
| 24 | use std::lang::il::binary::collect; |
| 25 | use std::lang::il::binary::program; |
| 26 | use std::lang::gen::labels; |
| 27 | use std::lang::gen::types; |
| 28 | use std::sys; |
| 29 | use std::sys::unix; |
| 30 | use std::collections::dict; |
| 31 | |
| 32 | /// Maximum number of modules we can load per package. |
| 33 | constant MAX_LOADED_MODULES: u32 = 128; |
| 34 | /// Maximum number of packages we can compile. |
| 35 | constant MAX_PACKAGES: u32 = 4; |
| 36 | /// Source code buffer arena (4 MB). |
| 37 | constant MAX_SOURCES_SIZE: u32 = 4194304; |
| 38 | /// Maximum number of test functions we can discover. |
| 39 | constant MAX_TESTS: u32 = 1024; |
| 40 | /// Maximum number of assembly source paths we can load per package. |
| 41 | constant MAX_ASM_MODULES: u32 = 64; |
| 42 | |
| 43 | /// AST arena size (64 MB) - retains parsed nodes throughout compilation. |
| 44 | constant TEMP_ARENA_SIZE: u32 = 67108864; |
| 45 | /// Per-function lowering and register-allocation arena size (16 MB). |
| 46 | constant FN_ARENA_SIZE: u32 = 16777216; |
| 47 | /// Main arena size (176 MB) - lives throughout compilation. |
| 48 | /// Used for: resolver data, types, symbols, global IL data, and codegen output. |
| 49 | constant MAIN_ARENA_SIZE: u32 = 184549376; |
| 50 | |
| 51 | /// AST storage arena. |
| 52 | static TEMP_ARENA: [u8; TEMP_ARENA_SIZE] = [0; TEMP_ARENA_SIZE]; |
| 53 | /// Scratch storage reclaimed after each generated function. |
| 54 | static FN_ARENA: [u8; FN_ARENA_SIZE] = [0; FN_ARENA_SIZE]; |
| 55 | /// Main storage arena - persists throughout compilation. |
| 56 | static MAIN_ARENA: [u8; MAIN_ARENA_SIZE] = [0; MAIN_ARENA_SIZE]; |
| 57 | |
| 58 | /// Module source code. |
| 59 | static MODULE_SOURCES: [u8; MAX_SOURCES_SIZE] = [0; MAX_SOURCES_SIZE]; |
| 60 | /// Module entries for all packages. |
| 61 | unsafe static MODULE_ENTRIES: [?*module::ModuleEntry; module::MAX_MODULES] = [nil; module::MAX_MODULES]; |
| 62 | /// String pool. |
| 63 | unsafe static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 }; |
| 64 | |
| 65 | /// Package scope. |
| 66 | unsafe static RESOLVER_PKG_SCOPE: resolver::Scope = undefined; |
| 67 | /// Errors emitted by resolver. |
| 68 | unsafe static RESOLVER_ERRORS: [resolver::Error; resolver::MAX_ERRORS] = undefined; |
| 69 | |
| 70 | /// Code generation storage. |
| 71 | unsafe static CODEGEN_DATA_SYMS: [data::DataSym; data::MAX_DATA_SYMS] = undefined; |
| 72 | /// Hash table entries for data symbol lookup. |
| 73 | unsafe static CODEGEN_DATA_SYM_ENTRIES: [dict::Entry; data::DATA_SYM_TABLE_SIZE] = undefined; |
| 74 | /// Emitted instruction storage. |
| 75 | static CODEGEN_INSTRUCTIONS: [u32; emit::MAX_INSTRS] = [0; emit::MAX_INSTRS]; |
| 76 | /// Local branch patch storage. |
| 77 | unsafe static CODEGEN_PENDING_BRANCHES: [emit::PendingBranch; emit::MAX_PENDING] = undefined; |
| 78 | /// Function call patch storage. |
| 79 | unsafe static CODEGEN_PENDING_CALLS: [emit::PendingCall; emit::MAX_PENDING] = undefined; |
| 80 | /// Assembly jump patch storage. |
| 81 | unsafe static CODEGEN_PENDING_JUMPS: [emit::PendingJump; emit::MAX_PENDING] = undefined; |
| 82 | /// Address load patch storage. |
| 83 | unsafe static CODEGEN_PENDING_ADDR_LOADS: [emit::PendingAddrLoad; emit::MAX_PENDING] = undefined; |
| 84 | /// Per-function block offset storage. |
| 85 | static CODEGEN_BLOCK_OFFSETS: [i32; labels::MAX_BLOCKS_PER_FN] = [0; labels::MAX_BLOCKS_PER_FN]; |
| 86 | /// Function label hash table storage. |
| 87 | unsafe static CODEGEN_FUNC_ENTRIES: [dict::Entry; labels::FUNC_TABLE_SIZE] = undefined; |
| 88 | /// Printed function address storage. |
| 89 | unsafe static CODEGEN_FUNCS: [types::FuncAddr; emit::MAX_FUNCS] = undefined; |
| 90 | /// Debug location storage. |
| 91 | unsafe static CODEGEN_DEBUG_ENTRIES: [types::DebugEntry; emit::MAX_DEBUG_ENTRIES] = undefined; |
| 92 | |
| 93 | /// Maximum number of exported definitions in one binary package. |
| 94 | constant MAX_PACKAGE_EXPORTS: u32 = 8192; |
| 95 | /// Maximum number of indexed names in one binary package. |
| 96 | constant MAX_PACKAGE_SYMBOLS: u32 = 16384; |
| 97 | /// Export workspace reused for each binary package. |
| 98 | unsafe static PACKAGE_EXPORTS: [binary::Export; MAX_PACKAGE_EXPORTS] = undefined; |
| 99 | /// Symbol workspace reused for each binary package. |
| 100 | unsafe static PACKAGE_SYMBOLS: [*[u8]; MAX_PACKAGE_SYMBOLS] = undefined; |
| 101 | |
| 102 | /// Debug info file extension. |
| 103 | constant DEBUG_EXT: *[u8] = ".debug"; |
| 104 | |
| 105 | /// Maximum rodata size (4MB). |
| 106 | constant MAX_RO_DATA_SIZE: u32 = 4194304; |
| 107 | /// Maximum rwdata size (4MB). |
| 108 | constant MAX_RW_DATA_SIZE: u32 = 4194304; |
| 109 | /// Maximum path length. |
| 110 | constant MAX_PATH_LEN: u32 = 256; |
| 111 | /// Read-only data buffer. |
| 112 | static RO_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = [0; MAX_RO_DATA_SIZE]; |
| 113 | /// Read-write data buffer. |
| 114 | static RW_DATA_BUF: [u8; MAX_RW_DATA_SIZE] = [0; MAX_RW_DATA_SIZE]; |
| 115 | /// Assembly module source buffer. |
| 116 | static ASM_SOURCE_BUF: [u8; MAX_SOURCES_SIZE] = [0; MAX_SOURCES_SIZE]; |
| 117 | /// Temporary assembly text buffer. |
| 118 | static ASM_TEXT_BUF: [u32; 262144] = [0; 262144]; |
| 119 | /// Temporary assembly data buffer. |
| 120 | static ASM_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = [0; MAX_RO_DATA_SIZE]; |
| 121 | /// Accumulated assembly read-only data. |
| 122 | static ASM_RO_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = [0; MAX_RO_DATA_SIZE]; |
| 123 | |
| 124 | /// Assembly source file extension. |
| 125 | constant ASM_SOURCE_EXT: *[u8] = ".ras"; |
| 126 | /// Symbol name exported for startup code to call the semantic entry function. |
| 127 | export constant DEFAULT_ENTRY_SYMBOL: *[u8] = "::default"; |
| 128 | |
| 129 | /// Usage string. |
| 130 | constant USAGE: *[u8] = |
| 131 | "usage: radiance -pkg <name> [-start <input.ras>] -mod <input>.. [-pkg <name> -mod <input>..] -entry <pkg> [-o <output> | -ril <directory>]\n"; |
| 132 | |
| 133 | /// Compiler error. |
| 134 | union Error: Copy { |
| 135 | Other, |
| 136 | } |
| 137 | |
| 138 | /// What to dump during compilation. |
| 139 | union Dump: Copy { |
| 140 | /// Don't dump anything. |
| 141 | None, |
| 142 | /// Dump the parsed AST before semantic analysis. |
| 143 | Ast, |
| 144 | /// Dump the module graph. |
| 145 | Graph, |
| 146 | /// Dump the IL. |
| 147 | Il, |
| 148 | /// Dump the generated assembly. |
| 149 | Asm, |
| 150 | } |
| 151 | |
| 152 | /// A discovered test function. |
| 153 | record TestDesc: Copy { |
| 154 | /// Full module qualified path segments (eg. ["std", "tests"]). |
| 155 | modPath: *[*[u8]], |
| 156 | /// Test function name (eg. "testFoo"). |
| 157 | fnName: *[u8], |
| 158 | } |
| 159 | |
| 160 | /// Source inputs belonging to one command-line package. |
| 161 | record PackageInput: Copy { |
| 162 | /// Package name from the `-pkg` argument. |
| 163 | name: *[u8], |
| 164 | /// Optional startup assembly emitted before generated text. |
| 165 | startupPath: ?*[u8], |
| 166 | /// Radiance source paths for this package. |
| 167 | radPaths: [*[u8]; MAX_LOADED_MODULES], |
| 168 | /// Number of Radiance source paths. |
| 169 | radPathCount: u32, |
| 170 | /// Assembly source paths for this package. |
| 171 | asmPaths: [*[u8]; MAX_ASM_MODULES], |
| 172 | /// Number of assembly source paths. |
| 173 | asmPathCount: u32, |
| 174 | } |
| 175 | |
| 176 | /// Compilation context. |
| 177 | record CompileContext { |
| 178 | /// Array of packages to compile. |
| 179 | packages: [package::Package; MAX_PACKAGES], |
| 180 | /// Driver inputs for each package slot. |
| 181 | inputs: [PackageInput; MAX_PACKAGES], |
| 182 | /// Number of packages. |
| 183 | packageCount: u32, |
| 184 | /// Index of entry package. |
| 185 | entryPkgIdx: ?u32, |
| 186 | /// Global module graph shared by all packages. |
| 187 | graph: module::ModuleGraph, |
| 188 | /// Resolver configuration. |
| 189 | config: resolver::Config, |
| 190 | /// What to dump during compilation. |
| 191 | dump: Dump, |
| 192 | /// Output path for binary. |
| 193 | outputPath: ?*[u8], |
| 194 | /// Output directory for separate binary RIL packages. |
| 195 | rilDirectory: ?*[u8], |
| 196 | /// Whether to emit debug info (.debug file). |
| 197 | debug: bool, |
| 198 | } |
| 199 | |
| 200 | /// Root module info for a package. |
| 201 | record RootModule: Copy { |
| 202 | entry: *module::ModuleEntry, |
| 203 | ast: *ast::Node, |
| 204 | } |
| 205 | |
| 206 | /// Entry handling for streamed code generation. |
| 207 | union CodegenEntryMode: Copy { |
| 208 | /// Do not reserve an entry jump. |
| 209 | None, |
| 210 | /// Reserve and patch an entry jump to the `@default` function. |
| 211 | DefaultEntry, |
| 212 | } |
| 213 | |
| 214 | /// Options controlling streamed lowering and code generation. |
| 215 | record CodegenOptions: Copy { |
| 216 | /// Optional output path used for progress logging. |
| 217 | logPath: ?*[u8], |
| 218 | /// Whether to emit debug source locations. |
| 219 | debug: bool, |
| 220 | /// How the generated program should handle entry. |
| 221 | entryMode: CodegenEntryMode, |
| 222 | } |
| 223 | |
| 224 | /// Print a driver error line. |
| 225 | fn error(msg: &[*[u8]]) -> Error { |
| 226 | io::printError("radiance: "); |
| 227 | |
| 228 | for part, i in msg { |
| 229 | io::printError(part); |
| 230 | if i < msg.len - 1 { |
| 231 | io::printError(" "); |
| 232 | } |
| 233 | } |
| 234 | io::printError("\n"); |
| 235 | return Error::Other; |
| 236 | } |
| 237 | |
| 238 | /// Print a log line for the given package. |
| 239 | fn pkgLog(pkg: &package::Package, msg: &[*[u8]]) { |
| 240 | io::printError("radiance: "); |
| 241 | io::printError(pkg.name); |
| 242 | io::printError(": "); |
| 243 | |
| 244 | for part, i in msg { |
| 245 | io::printError(part); |
| 246 | if i < msg.len - 1 { |
| 247 | io::printError(" "); |
| 248 | } |
| 249 | } |
| 250 | io::printError("\n"); |
| 251 | } |
| 252 | |
| 253 | /// Return `true` when `path` ends with `ext`. |
| 254 | fn hasExtension(path: *[u8], ext: *[u8]) -> bool { |
| 255 | if path.len < ext.len { |
| 256 | return false; |
| 257 | } |
| 258 | let start = path.len - ext.len; |
| 259 | return mem::eq(&path[start..], ext); |
| 260 | } |
| 261 | |
| 262 | /// Create an empty source input set for one package. |
| 263 | fn packageInput(name: *[u8]) -> PackageInput { |
| 264 | return PackageInput { |
| 265 | name, |
| 266 | startupPath: nil, |
| 267 | radPaths: [""; MAX_LOADED_MODULES], |
| 268 | radPathCount: 0, |
| 269 | asmPaths: [""; MAX_ASM_MODULES], |
| 270 | asmPathCount: 0, |
| 271 | }; |
| 272 | } |
| 273 | |
| 274 | /// Register, load, and parse `path` within `pkg`. |
| 275 | unsafe fn processModule( |
| 276 | pkg: *unsafe mut package::Package, |
| 277 | graph: &mut module::ModuleGraph, |
| 278 | path: *[u8], |
| 279 | nodeArena: &mut ast::NodeArena, |
| 280 | sourceArena: &mut alloc::Arena |
| 281 | ) throws (Error) { |
| 282 | pkgLog(pkg, &["parsing", "(", path, ")", ".."]); |
| 283 | |
| 284 | let moduleId = try package::registerModule(pkg, graph, &mut STRING_POOL, path) catch { |
| 285 | throw error(&["error registering module"]); |
| 286 | }; |
| 287 | // Read file into remaining arena space. |
| 288 | let buffer = alloc::remainingBuf(&mut *sourceArena); |
| 289 | if buffer.len == 0 { |
| 290 | throw error(&["fatal:", "source arena exhausted"]); |
| 291 | } |
| 292 | let sourceLen = unix::readFile(path, buffer) else { |
| 293 | throw error(&["error reading file"]); |
| 294 | }; |
| 295 | let source = &buffer[..sourceLen]; |
| 296 | if source.len == buffer.len { |
| 297 | throw error(&["fatal:", "source arena too small, file truncated:", path]); |
| 298 | } |
| 299 | // Commit only what was read. |
| 300 | alloc::commit(sourceArena, source.len); |
| 301 | |
| 302 | let ast = try parser::parse(scanner::SourceLoc::File(path), source, nodeArena, &mut STRING_POOL) catch { |
| 303 | throw Error::Other; |
| 304 | }; |
| 305 | try module::setAst(graph, moduleId, ast) catch { |
| 306 | throw error(&["error setting AST"]); |
| 307 | }; |
| 308 | try module::setSource(graph, moduleId, source) catch { |
| 309 | throw error(&["error setting source"]); |
| 310 | }; |
| 311 | } |
| 312 | |
| 313 | /// Consume the next argument, or print an error and throw. |
| 314 | fn nextArg(args: *[*[u8]], idx: &mut u32, msg: &[*[u8]]) -> *[u8] throws (Error) { |
| 315 | set *idx += 1; |
| 316 | if *idx >= args.len { |
| 317 | throw error(msg); |
| 318 | } |
| 319 | return args[*idx]; |
| 320 | } |
| 321 | |
| 322 | /// Parse CLI arguments and return compilation context. |
| 323 | unsafe fn processCommand( |
| 324 | args: *[*[u8]], |
| 325 | arena: &mut ast::NodeArena |
| 326 | ) -> CompileContext throws (Error) { |
| 327 | let mut buildTest = false; |
| 328 | let mut debugEnabled = false; |
| 329 | let mut outputPath: ?*[u8] = nil; |
| 330 | let mut rilDirectory: ?*[u8] = nil; |
| 331 | let mut dump = Dump::None; |
| 332 | let mut entryPkgName: ?*[u8] = nil; |
| 333 | |
| 334 | // Per-package source path tracking. |
| 335 | let mut inputs: [PackageInput; MAX_PACKAGES] = undefined; |
| 336 | let mut pkgCount: u32 = 0; |
| 337 | let mut currentPkgIdx: ?u32 = nil; |
| 338 | |
| 339 | if args.len == 0 { |
| 340 | io::printError(USAGE); |
| 341 | throw Error::Other; |
| 342 | } |
| 343 | let mut idx: u32 = 0; |
| 344 | |
| 345 | while idx < args.len { |
| 346 | let arg = args[idx]; |
| 347 | if mem::eq(arg, "-pkg") { |
| 348 | try nextArg(args, &mut idx, &["`-pkg` requires a package name"]); |
| 349 | if pkgCount >= MAX_PACKAGES { |
| 350 | throw error(&["too many packages specified"]); |
| 351 | } |
| 352 | set inputs[pkgCount] = packageInput(args[idx]); |
| 353 | set currentPkgIdx = pkgCount; |
| 354 | set pkgCount += 1; |
| 355 | } else if mem::eq(arg, "-mod") { |
| 356 | try nextArg(args, &mut idx, &["`-mod` requires a module path"]); |
| 357 | let pkgIdx = currentPkgIdx else { |
| 358 | throw error(&["`-mod` must follow a `-pkg` argument"]); |
| 359 | }; |
| 360 | let input: *unsafe mut PackageInput = &mut inputs[pkgIdx]; |
| 361 | if hasExtension(args[idx], ASM_SOURCE_EXT) { |
| 362 | if input.asmPathCount >= MAX_ASM_MODULES { |
| 363 | throw error(&["too many assembly modules specified"]); |
| 364 | } |
| 365 | set input.asmPaths[input.asmPathCount] = args[idx]; |
| 366 | set input.asmPathCount += 1; |
| 367 | } else { |
| 368 | if input.radPathCount >= MAX_LOADED_MODULES { |
| 369 | throw error(&["too many modules specified for package"]); |
| 370 | } |
| 371 | set input.radPaths[input.radPathCount] = args[idx]; |
| 372 | set input.radPathCount += 1; |
| 373 | } |
| 374 | } else if mem::eq(arg, "-start") { |
| 375 | try nextArg(args, &mut idx, &["`-start` requires an assembly path"]); |
| 376 | let pkgIdx = currentPkgIdx else { |
| 377 | throw error(&["`-start` must follow a `-pkg` argument"]); |
| 378 | }; |
| 379 | let input: *unsafe mut PackageInput = &mut inputs[pkgIdx]; |
| 380 | if input.startupPath <> nil { |
| 381 | throw error(&["package", input.name, "has more than one startup file"]); |
| 382 | } |
| 383 | if not hasExtension(args[idx], ASM_SOURCE_EXT) { |
| 384 | throw error(&["`-start` requires a `.ras` assembly file"]); |
| 385 | } |
| 386 | set input.startupPath = args[idx]; |
| 387 | } else if mem::eq(arg, "-entry") { |
| 388 | try nextArg(args, &mut idx, &["`-entry` requires a package name"]); |
| 389 | set entryPkgName = args[idx]; |
| 390 | } else if mem::eq(arg, "-test") { |
| 391 | set buildTest = true; |
| 392 | } else if mem::eq(arg, "-debug") { |
| 393 | set debugEnabled = true; |
| 394 | } else if mem::eq(arg, "-o") { |
| 395 | try nextArg(args, &mut idx, &["`-o` requires an output path"]); |
| 396 | set outputPath = args[idx]; |
| 397 | } else if mem::eq(arg, "-ril") { |
| 398 | try nextArg(args, &mut idx, &["`-ril` requires an output directory"]); |
| 399 | set rilDirectory = args[idx]; |
| 400 | } else if mem::eq(arg, "-dump") { |
| 401 | try nextArg(args, &mut idx, &["`-dump` requires a mode (eg. ast)"]); |
| 402 | let mode = args[idx]; |
| 403 | if mem::eq(mode, "ast") { |
| 404 | set dump = Dump::Ast; |
| 405 | } else if mem::eq(mode, "graph") { |
| 406 | set dump = Dump::Graph; |
| 407 | } else if mem::eq(mode, "il") { |
| 408 | set dump = Dump::Il; |
| 409 | } else if mem::eq(mode, "asm") { |
| 410 | set dump = Dump::Asm; |
| 411 | } else { |
| 412 | throw error(&["unknown dump mode", mode, "(expected: ast, graph, il, asm)"]); |
| 413 | } |
| 414 | } else { |
| 415 | throw error(&["unknown argument", arg]); |
| 416 | } |
| 417 | set idx += 1; |
| 418 | } |
| 419 | if pkgCount == 0 { |
| 420 | throw error(&["no package specified"]); |
| 421 | } |
| 422 | for i in 0..pkgCount { |
| 423 | if inputs[i].radPathCount == 0 { |
| 424 | throw error(&["package", inputs[i].name, "has no Radiance modules specified"]); |
| 425 | } |
| 426 | } |
| 427 | |
| 428 | // Determine entry package index. |
| 429 | let mut entryPkgIdx: ?u32 = nil; |
| 430 | if pkgCount == 1 { |
| 431 | // Single package: it is the entry. |
| 432 | set entryPkgIdx = 0; |
| 433 | } else { |
| 434 | // Multiple packages: need -entry. |
| 435 | let entryName = entryPkgName else { |
| 436 | throw error(&["`-entry` required when multiple packages specified"]); |
| 437 | }; |
| 438 | for i in 0..pkgCount { |
| 439 | if mem::eq(inputs[i].name, entryName) { |
| 440 | set entryPkgIdx = i; |
| 441 | break; |
| 442 | } |
| 443 | } |
| 444 | if entryPkgIdx == nil { |
| 445 | throw error(&["fatal:", "entry package", entryName, "not found"]); |
| 446 | } |
| 447 | } |
| 448 | let entryIdx = entryPkgIdx else { |
| 449 | panic "processCommand: no entry package"; |
| 450 | }; |
| 451 | for i in 0..pkgCount { |
| 452 | if i <> entryIdx and inputs[i].startupPath <> nil { |
| 453 | throw error(&["`-start` is only supported on the entry package"]); |
| 454 | } |
| 455 | } |
| 456 | if rilDirectory <> nil and (outputPath <> nil or dump <> Dump::None) { |
| 457 | throw error(&["`-ril` requires a separate invocation from `-o` or `-dump`"]); |
| 458 | } |
| 459 | let graph = module::moduleGraph(&mut MODULE_ENTRIES[..], arena); |
| 460 | let mut ctx = CompileContext { |
| 461 | packages: undefined, |
| 462 | inputs, |
| 463 | packageCount: pkgCount, |
| 464 | entryPkgIdx, |
| 465 | graph, |
| 466 | config: resolver::Config { buildTest }, |
| 467 | dump, |
| 468 | outputPath, |
| 469 | rilDirectory, |
| 470 | debug: debugEnabled, |
| 471 | }; |
| 472 | // Initialize and parse all packages. |
| 473 | let mut sourceArena = alloc::new(&mut MODULE_SOURCES[..]); |
| 474 | for i in 0..pkgCount { |
| 475 | let name = ctx.inputs[i].name; |
| 476 | package::init(&mut ctx.packages[i], i as u16, name, &mut STRING_POOL); |
| 477 | |
| 478 | for j in 0..ctx.inputs[i].radPathCount { |
| 479 | let path = ctx.inputs[i].radPaths[j]; |
| 480 | try processModule(&mut ctx.packages[i], &mut ctx.graph, path, arena, &mut sourceArena); |
| 481 | } |
| 482 | } |
| 483 | return ctx; |
| 484 | } |
| 485 | |
| 486 | /// Get the entry package from the context. |
| 487 | fn getEntryPackage(ctx: &CompileContext) -> package::Package throws (Error) { |
| 488 | let entryIdx = ctx.entryPkgIdx else { |
| 489 | throw error(&["no entry package specified"]); |
| 490 | }; |
| 491 | return ctx.packages[entryIdx]; |
| 492 | } |
| 493 | |
| 494 | /// Return the startup assembly path for the entry package, if one was supplied. |
| 495 | fn getEntryStartupPath(ctx: &CompileContext) -> ?*[u8] { |
| 496 | let entryIdx = ctx.entryPkgIdx else { |
| 497 | panic "getEntryStartupPath: no entry package"; |
| 498 | }; |
| 499 | return ctx.inputs[entryIdx].startupPath; |
| 500 | } |
| 501 | |
| 502 | /// Get root module info from a package. |
| 503 | fn getRootModule(pkg: &package::Package, graph: &module::ModuleGraph) -> RootModule throws (Error) { |
| 504 | let rootId = pkg.rootModuleId else { |
| 505 | throw error(&["no root module found"]); |
| 506 | }; |
| 507 | let rootEntry = module::get(graph, rootId) else { |
| 508 | throw error(&["root module entry not found"]); |
| 509 | }; |
| 510 | let rootAst = module::astFor(rootEntry) else { |
| 511 | throw error(&["root module has no AST"]); |
| 512 | }; |
| 513 | return RootModule { entry: rootEntry, ast: rootAst }; |
| 514 | } |
| 515 | |
| 516 | /// Dump the module graph. |
| 517 | unsafe fn dumpGraph(ctx: &CompileContext) { |
| 518 | let mut arena = alloc::new(&mut MAIN_ARENA[..]); |
| 519 | module::printer::printGraph(&ctx.graph, &mut arena); |
| 520 | } |
| 521 | |
| 522 | /// Dump the parsed AST. |
| 523 | unsafe fn dumpAst(ctx: &CompileContext) throws (Error) { |
| 524 | let pkg = try getEntryPackage(ctx); |
| 525 | let root = try getRootModule(&pkg, &ctx.graph); |
| 526 | let mut arena = alloc::new(&mut MAIN_ARENA[..]); |
| 527 | |
| 528 | ast::printer::printTree(root.ast, &mut arena); |
| 529 | } |
| 530 | |
| 531 | /// Lower all packages into a single IL program. |
| 532 | /// Dependencies are lowered first, then the entry package. |
| 533 | unsafe fn lowerAllPackages 'arena ( |
| 534 | ctx: *unsafe mut CompileContext, |
| 535 | res: *unsafe mut resolver::Resolver 'arena |
| 536 | ) -> il::Program throws (Error) { |
| 537 | let entryIdx = ctx.entryPkgIdx else { |
| 538 | panic "lowerAllPackages: no entry package"; |
| 539 | }; |
| 540 | let entryPkg = &ctx.packages[entryIdx]; |
| 541 | |
| 542 | // Create the lowerer accumulator using entry package's name. |
| 543 | let options = lower::LowerOptions { debug: ctx.debug, buildTest: ctx.config.buildTest }; |
| 544 | let arena = (&mut *res.arena) as *unsafe mut alloc::Arena; |
| 545 | let resolved: 'phase = &*res, graph = &ctx.graph where 'arena: 'phase in { |
| 546 | let mut low = lower::lowerer( |
| 547 | resolved, graph, entryPkg.name, arena, options |
| 548 | ); |
| 549 | try lowerAllPackagesInto(ctx, &mut low, &mut *arena); |
| 550 | |
| 551 | // Finalize and return the unified program. |
| 552 | return lower::finalize(low); |
| 553 | } |
| 554 | } |
| 555 | |
| 556 | /// Lower all packages into an existing lowerer. |
| 557 | unsafe fn lowerAllPackagesInto 'arena 'phase ( |
| 558 | ctx: &CompileContext, |
| 559 | low: &mut lower::Lowerer 'arena 'phase, |
| 560 | functionArena: &mut alloc::Arena |
| 561 | ) throws (Error) where 'arena: 'phase { |
| 562 | let entryIdx = ctx.entryPkgIdx else { |
| 563 | panic "lowerAllPackagesInto: no entry package"; |
| 564 | }; |
| 565 | // Lower all packages except entry. |
| 566 | for i in 0..ctx.packageCount { |
| 567 | if i <> entryIdx { |
| 568 | try lowerPackage(ctx, low, &ctx.packages[i], false, functionArena); |
| 569 | } |
| 570 | } |
| 571 | // Lower entry package. |
| 572 | try lowerPackage(ctx, low, &ctx.packages[entryIdx], true, functionArena); |
| 573 | } |
| 574 | |
| 575 | /// Lower all modules in a package into the lowerer accumulator. |
| 576 | unsafe fn lowerPackage 'arena 'phase ( |
| 577 | ctx: &CompileContext, |
| 578 | low: &mut lower::Lowerer 'arena 'phase, |
| 579 | pkg: &package::Package, |
| 580 | isEntry: bool, |
| 581 | functionArena: &mut alloc::Arena |
| 582 | ) throws (Error) where 'arena: 'phase { |
| 583 | let rootId = pkg.rootModuleId else { |
| 584 | throw error(&["no root module found"]); |
| 585 | }; |
| 586 | // Set lowerer's package context for qualified name generation. |
| 587 | // TODO: We shouldn't have to call this manually. |
| 588 | lower::setPackage(low, pkg.name); |
| 589 | |
| 590 | try lowerModuleTreeInto(ctx, low, &ctx.graph, rootId, isEntry, pkg, functionArena); |
| 591 | } |
| 592 | |
| 593 | /// Recursively lower a module and all its children into the accumulator. |
| 594 | unsafe fn lowerModuleTreeInto 'arena 'phase ( |
| 595 | ctx: &CompileContext, |
| 596 | low: &mut lower::Lowerer 'arena 'phase, |
| 597 | graph: &module::ModuleGraph, |
| 598 | modId: u16, |
| 599 | isRoot: bool, |
| 600 | pkg: &package::Package, |
| 601 | functionArena: &mut alloc::Arena |
| 602 | ) throws (Error) where 'arena: 'phase { |
| 603 | let entry = module::get(graph, modId) else { |
| 604 | throw error(&["module entry not found"]); |
| 605 | }; |
| 606 | let modAst = module::astFor(entry) else { |
| 607 | throw error(&["module has no AST"]); |
| 608 | }; |
| 609 | pkgLog(pkg, &["lowering", "(", entry.filePath, ")", ".."]); |
| 610 | |
| 611 | try lower::lowerModule(low, modId, modAst, isRoot, functionArena) catch err { |
| 612 | io::printError("radiance: "); |
| 613 | io::printError("internal error during lowering: "); |
| 614 | lower::printError(err); |
| 615 | io::printError("\n"); |
| 616 | |
| 617 | throw Error::Other; |
| 618 | }; |
| 619 | // Recurse into children. |
| 620 | for i in 0..module::childCount(entry) { |
| 621 | let childId = module::childAt(entry, i); |
| 622 | try lowerModuleTreeInto(ctx, low, graph, childId, false, pkg, functionArena); |
| 623 | } |
| 624 | } |
| 625 | |
| 626 | /// Build a scope access chain: a::b::c from a slice of identifiers. |
| 627 | unsafe fn synthScopeAccess(arena: &mut ast::NodeArena, path: &[*[u8]]) -> *ast::Node { |
| 628 | let mut result = ast::synthNode( |
| 629 | arena, |
| 630 | ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, path[0])) |
| 631 | ); |
| 632 | for i in 1..path.len { |
| 633 | let child = ast::synthNode( |
| 634 | arena, |
| 635 | ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, path[i])) |
| 636 | ); |
| 637 | set result = ast::synthNode(arena, ast::NodeValue::ScopeAccess(ast::Access { |
| 638 | parent: result, child, |
| 639 | })); |
| 640 | } |
| 641 | return result; |
| 642 | } |
| 643 | |
| 644 | /// Check if a function declaration has the `@test` attribute and return its name if so. |
| 645 | fn getTestFnName(decl: &ast::FnDecl) -> ?*[u8] { |
| 646 | let attrs = decl.attrs else { return nil; }; |
| 647 | if not ast::attributesContains(&attrs, ast::Attribute::Test) { |
| 648 | return nil; |
| 649 | } |
| 650 | let case ast::NodeValue::Ident(name) = decl.name.value |
| 651 | else return nil; |
| 652 | |
| 653 | return name; |
| 654 | } |
| 655 | |
| 656 | /// Scan a single module's AST for `@test` functions and append them to `tests`. |
| 657 | fn collectModuleTests( |
| 658 | entry: *module::ModuleEntry, |
| 659 | tests: &mut [TestDesc], |
| 660 | testCount: &mut u32 |
| 661 | ) { |
| 662 | let modAst = module::astFor(entry) else { |
| 663 | return; |
| 664 | }; |
| 665 | let case ast::NodeValue::Block(block) = modAst.value else { |
| 666 | return; |
| 667 | }; |
| 668 | let modPath = module::moduleQualifiedPath(entry); |
| 669 | |
| 670 | for stmt in block.statements { |
| 671 | if let case ast::NodeValue::FnDecl(decl) = stmt.value { |
| 672 | if let fnName = getTestFnName(&decl) { |
| 673 | if *testCount < tests.len { |
| 674 | set tests[*testCount] = TestDesc { modPath, fnName }; |
| 675 | set *testCount += 1; |
| 676 | } else { |
| 677 | panic "collectModuleTests: too many tests"; |
| 678 | } |
| 679 | } |
| 680 | } |
| 681 | } |
| 682 | } |
| 683 | |
| 684 | /// Synthesize a `testing::test("mod", "name", mod::fn)` call for one test. |
| 685 | unsafe fn synthTestCall(arena: &mut ast::NodeArena, desc: &TestDesc) -> *ast::Node { |
| 686 | let callee = synthScopeAccess(arena, &["testing", "test"]); |
| 687 | let modStr = il::formatQualifiedName( |
| 688 | &mut arena.arena, |
| 689 | &desc.modPath[..desc.modPath.len - 1], |
| 690 | desc.modPath[desc.modPath.len - 1] |
| 691 | ); |
| 692 | let modArg = ast::synthNode(arena, ast::NodeValue::String(modStr)); |
| 693 | let nameArg = ast::synthNode(arena, ast::NodeValue::String(desc.fnName)); |
| 694 | |
| 695 | // Intra-package path: skip the package name prefix. |
| 696 | let mut funcPath: [*[u8]; 16] = undefined; |
| 697 | for j in 1..desc.modPath.len { |
| 698 | set funcPath[j - 1] = desc.modPath[j]; |
| 699 | } |
| 700 | set funcPath[desc.modPath.len - 1] = desc.fnName; |
| 701 | let funcArg = synthScopeAccess(arena, &funcPath[..desc.modPath.len]); |
| 702 | |
| 703 | let a = alloc::arenaAllocator(&mut arena.arena); |
| 704 | let args = ast::nodeSlice(arena, 3) |
| 705 | .append(modArg, a) |
| 706 | .append(nameArg, a) |
| 707 | .append(funcArg, a); |
| 708 | |
| 709 | return ast::synthNode(arena, ast::NodeValue::Call(ast::Call { callee, args })); |
| 710 | } |
| 711 | |
| 712 | /// Inject a test runner into the entry package's root module. |
| 713 | /// |
| 714 | /// Scans the entry package for `@test fn` declarations, then appends |
| 715 | /// a synthetic entry point to the root module's AST block: |
| 716 | /// |
| 717 | /// ``` |
| 718 | /// @default fn #testMain() -> i32 { |
| 719 | /// return testing::runAllTests(&[ |
| 720 | /// testing::test("std::tests", "testFoo", tests::testFoo), |
| 721 | /// ... |
| 722 | /// ]); |
| 723 | /// } |
| 724 | /// ``` |
| 725 | /// |
| 726 | /// Uses `#`-prefixed names to avoid conflicts with user code. |
| 727 | unsafe fn generateTestRunner( |
| 728 | ctx: *unsafe mut CompileContext, |
| 729 | arena: &mut ast::NodeArena |
| 730 | ) throws (Error) { |
| 731 | let entryPkg = try getEntryPackage(ctx); |
| 732 | let root = try getRootModule(&entryPkg, &ctx.graph); |
| 733 | |
| 734 | // Collect test functions from the entry package's modules. |
| 735 | let mut tests: [TestDesc; MAX_TESTS] = undefined; |
| 736 | let mut testCount: u32 = 0; |
| 737 | |
| 738 | for modIdx in 0..ctx.graph.entriesLen { |
| 739 | if let entry = module::get(&ctx.graph, modIdx as u16) { |
| 740 | if entry.packageId == entryPkg.id { |
| 741 | collectModuleTests(entry, &mut tests[..], &mut testCount); |
| 742 | } |
| 743 | } |
| 744 | } |
| 745 | if testCount == 0 { |
| 746 | throw error(&["fatal:", "no test functions found"]); |
| 747 | } |
| 748 | let mut countBuf: [u8; 10] = undefined; |
| 749 | let start = fmt::formatU32(testCount, &mut countBuf[..]); |
| 750 | io::printError("radiance: "); |
| 751 | io::printError(entryPkg.name); |
| 752 | io::printError(": found "); |
| 753 | io::printError(&countBuf[start..]); |
| 754 | io::printError(" test(s)\n"); |
| 755 | |
| 756 | // Synthesize the `@default` function and append to the root module. |
| 757 | let fnDecl = synthTestMainFn(arena, &tests[..testCount]); |
| 758 | |
| 759 | let updatedRoot = injectIntoBlock(root.ast, arena, fnDecl); |
| 760 | try module::setAst(&mut ctx.graph, root.entry.id, updatedRoot) catch { |
| 761 | throw error(&["failed to set test runner AST"]); |
| 762 | }; |
| 763 | } |
| 764 | |
| 765 | /// Synthesize the test entry point. |
| 766 | unsafe fn synthTestMainFn(arena: &mut ast::NodeArena, tests: &[TestDesc]) -> *ast::Node { |
| 767 | // Build array literal: `[testing::test(...), ...]`. |
| 768 | let a = alloc::arenaAllocator(&mut arena.arena); |
| 769 | let mut elements = ast::nodeSlice(arena, tests.len as u32); |
| 770 | for i in 0..tests.len { |
| 771 | elements.append(synthTestCall(arena, &tests[i]), a); |
| 772 | } |
| 773 | let arrayLit = ast::synthNode(arena, ast::NodeValue::ArrayLit(elements)); |
| 774 | |
| 775 | // Build: `&[...]`. |
| 776 | let testsRef = ast::synthNode(arena, ast::NodeValue::AddressOf(ast::AddressOf { |
| 777 | target: arrayLit, kind: ast::AddressKind::Shared, |
| 778 | })); |
| 779 | |
| 780 | // Build: `testing::runAllTests(&[...])`. |
| 781 | let runFn = synthScopeAccess(arena, &["testing", "runAllTests"]); |
| 782 | let callArgs = ast::nodeSlice(arena, 1).append(testsRef, a); |
| 783 | let callExpr = ast::synthNode(arena, ast::NodeValue::Call(ast::Call { |
| 784 | callee: runFn, args: callArgs, |
| 785 | })); |
| 786 | |
| 787 | // Build: `return testing::runAllTests(&[...]);` |
| 788 | let retStmt = ast::synthNode(arena, ast::NodeValue::Return { value: callExpr }); |
| 789 | let bodyStmts = ast::nodeSlice(arena, 1).append(retStmt, a); |
| 790 | let fnBody = ast::synthNode(arena, ast::NodeValue::Block(ast::Block { statements: bodyStmts, isUnsafe: false })); |
| 791 | |
| 792 | // Build: `unsafe fn #testMain() -> i32` |
| 793 | let fnName = ast::synthNode(arena, ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, "#testMain"))); |
| 794 | let returnType = ast::synthNode(arena, ast::NodeValue::TypeSig(ast::TypeSig::Integer { |
| 795 | width: 4, sign: ast::Signedness::Signed, |
| 796 | })); |
| 797 | let fnSig = ast::FnSig { |
| 798 | params: ast::nodeSlice(arena, 0), |
| 799 | returnType, |
| 800 | throwList: ast::nodeSlice(arena, 0), |
| 801 | }; |
| 802 | |
| 803 | // Entry and function safety attributes. |
| 804 | let attrNode = ast::synthNode(arena, ast::NodeValue::Attribute(ast::Attribute::Default)); |
| 805 | let unsafeAttr = ast::synthNode(arena, ast::NodeValue::Attribute(ast::Attribute::Unsafe)); |
| 806 | let attrList = ast::nodeSlice(arena, 2).append(attrNode, a).append(unsafeAttr, a); |
| 807 | let fnAttrs = ast::Attributes { list: attrList }; |
| 808 | |
| 809 | return ast::synthNode(arena, ast::NodeValue::FnDecl(ast::FnDecl { |
| 810 | name: fnName, regions: &[], sig: fnSig, body: fnBody, attrs: fnAttrs, |
| 811 | })); |
| 812 | } |
| 813 | |
| 814 | /// Build a block node with a declaration appended to its statement list. |
| 815 | unsafe fn injectIntoBlock( |
| 816 | blockNode: *ast::Node, |
| 817 | arena: &mut ast::NodeArena, |
| 818 | decl: *ast::Node |
| 819 | ) -> *ast::Node { |
| 820 | let case ast::NodeValue::Block(block) = blockNode.value else { |
| 821 | panic "injectIntoBlock: expected Block node"; |
| 822 | }; |
| 823 | let allocator = alloc::arenaAllocator(&mut arena.arena); |
| 824 | let mut stmts = ast::nodeSlice(arena, block.statements.len + 1); |
| 825 | for stmt in block.statements { |
| 826 | stmts.append(stmt, allocator); |
| 827 | } |
| 828 | stmts.append(decl, allocator); |
| 829 | return ast::allocNode(arena, blockNode.span, ast::NodeValue::Block( |
| 830 | ast::Block { statements: stmts, isUnsafe: block.isUnsafe } |
| 831 | )); |
| 832 | } |
| 833 | |
| 834 | /// Write a self-contained RV64 image containing text and data sections. |
| 835 | unsafe fn writeImage( |
| 836 | code: *[u32], |
| 837 | roData: *[u8], |
| 838 | rwData: *[u8], |
| 839 | path: *[u8] |
| 840 | ) -> bool { |
| 841 | let mut header = rv64::imageHeader(code.len * rv64::INSTR_SIZE as u32, roData.len, rwData.len); |
| 842 | let headerBytes: *unsafe [u8] = @sliceOf(&header[0] as *unsafe u8, header.len * rv64::WORD_SIZE as u32); |
| 843 | let codeBytes = @sliceOf(code.ptr as *u8, code.len * rv64::INSTR_SIZE as u32); |
| 844 | |
| 845 | let fd = unix::openOpts(path, unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420); |
| 846 | if fd < 0 { return false; } |
| 847 | let written = unix::writeAll(fd, headerBytes) and unix::writeAll(fd, codeBytes) |
| 848 | and unix::writeAll(fd, roData) and unix::writeAll(fd, rwData); |
| 849 | let closed = unix::close(fd) == 0; |
| 850 | return written and closed; |
| 851 | } |
| 852 | |
| 853 | /// Write a data section to a file at `basePath` + `ext`. |
| 854 | /// Empty data truncates any stale sidecar left by an earlier build. |
| 855 | fn writeDataWithExt( |
| 856 | data: *[u8], |
| 857 | basePath: *[u8], |
| 858 | ext: *[u8] |
| 859 | ) throws (Error) { |
| 860 | let mut path: [u8; MAX_PATH_LEN] = [0; MAX_PATH_LEN]; |
| 861 | let mut pos: u32 = 0; |
| 862 | |
| 863 | set pos += try! mem::copy(&mut path[pos..], basePath); |
| 864 | set pos += try! mem::copy(&mut path[pos..], ext); |
| 865 | set path[pos] = 0; // Null-terminate for syscall. |
| 866 | |
| 867 | if not unix::writeFile(&path[..pos], data) { |
| 868 | throw error(&["fatal:", "failed to write data file"]); |
| 869 | } |
| 870 | } |
| 871 | |
| 872 | /// Serialize debug entries and write the `.debug` file. |
| 873 | /// Resolves module IDs to file paths via the module graph. |
| 874 | /// Format per entry is `{pc: u32, offset: u32, filePath: [u8], NULL}`. |
| 875 | unsafe fn writeDebugInfo( |
| 876 | entries: *[types::DebugEntry], |
| 877 | graph: &module::ModuleGraph, |
| 878 | basePath: *[u8], |
| 879 | arena: &mut alloc::Arena |
| 880 | ) throws (Error) { |
| 881 | if entries.len == 0 { |
| 882 | return; |
| 883 | } |
| 884 | // Use remaining arena space as serialization buffer. |
| 885 | let buf = alloc::remainingBuf(&mut *arena); |
| 886 | let mut pos: u32 = 0; |
| 887 | |
| 888 | for i in 0..entries.len { |
| 889 | let entry = &entries[i]; |
| 890 | let modEntry = module::get(graph, entry.moduleId) else { |
| 891 | panic "writeDebugInfo: module not found for debug entry"; |
| 892 | }; |
| 893 | set pos += try! mem::copy(&mut buf[pos..], @sliceOf(&entry.pc as *u8, 4)); |
| 894 | set pos += try! mem::copy(&mut buf[pos..], @sliceOf(&entry.offset as *u8, 4)); |
| 895 | set pos += try! mem::copy(&mut buf[pos..], modEntry.filePath); |
| 896 | |
| 897 | set buf[pos] = 0; |
| 898 | set pos += 1; |
| 899 | } |
| 900 | try writeDataWithExt(&buf[..pos], basePath, DEBUG_EXT); |
| 901 | } |
| 902 | |
| 903 | /// Run the resolver on the parsed modules. |
| 904 | unsafe fn runResolver 'arena (ctx: *unsafe mut CompileContext, mainArena: &'arena mut alloc::Arena, nodeCount: u32) -> resolver::Resolver 'arena throws (Error) { |
| 905 | let entryPkg = try getEntryPackage(ctx); |
| 906 | |
| 907 | pkgLog(&entryPkg, &["resolving", ".."]); |
| 908 | |
| 909 | let nodeDataSize = nodeCount * @sizeOf(resolver::NodeData); |
| 910 | let nodeDataPtr = try! alloc::alloc(&mut *mainArena, nodeDataSize, @alignOf(resolver::NodeData)); |
| 911 | let nodeData = @sliceOf(nodeDataPtr as *mut resolver::NodeData, nodeCount); |
| 912 | let storage = resolver::ResolverStorage { |
| 913 | nodeData, |
| 914 | pkgScope: &mut RESOLVER_PKG_SCOPE, |
| 915 | errors: &mut RESOLVER_ERRORS[..], |
| 916 | }; |
| 917 | let mut res = resolver::resolver(mainArena, storage, ctx.config); |
| 918 | |
| 919 | // Build the semantic package list consumed by the resolver. |
| 920 | let mut resolverPkgs: [resolver::Pkg; MAX_PACKAGES] = undefined; |
| 921 | let mut resolverPackageCount: u32 = 0; |
| 922 | for i in 0..ctx.packageCount { |
| 923 | let pkg = &ctx.packages[i]; |
| 924 | let root = try getRootModule(pkg, &ctx.graph); |
| 925 | |
| 926 | set resolverPkgs[resolverPackageCount] = resolver::Pkg { |
| 927 | rootEntry: root.entry, |
| 928 | rootAst: root.ast, |
| 929 | }; |
| 930 | set resolverPackageCount += 1; |
| 931 | } |
| 932 | |
| 933 | // Resolve all packages. |
| 934 | // TODO: Fix this error printing dance. |
| 935 | let diags = try resolver::resolve(&mut res, &ctx.graph, &resolverPkgs[..resolverPackageCount]) catch { |
| 936 | let diags = resolver::diagnostics(&mut res); |
| 937 | resolver::printer::printDiagnostics(&diags, &res); |
| 938 | throw Error::Other; |
| 939 | }; |
| 940 | if not resolver::success(&diags) { |
| 941 | resolver::printer::printDiagnostics(&diags, &res); |
| 942 | let mut countBuf: [u8; 10] = undefined; |
| 943 | let start = fmt::formatU32(diags.errors.len, &mut countBuf[..]); |
| 944 | io::printError("radiance: failed: "); |
| 945 | io::printError(&countBuf[start..]); |
| 946 | io::printError(" errors\n"); |
| 947 | throw Error::Other; |
| 948 | } |
| 949 | return res; |
| 950 | } |
| 951 | |
| 952 | /// Assemble one `.ras` input and merge it into the active code generator. |
| 953 | /// |
| 954 | /// Text symbols are appended to `generator`. Data emitted by the assembler is |
| 955 | /// copied into `ASM_RO_DATA_BUF` at `*asmDataLen`, and `*asmDataLen` is advanced |
| 956 | /// so the next assembly module receives the correct rodata base address. |
| 957 | unsafe fn assembleAsmModule( |
| 958 | generator: &mut rv64::Generator, |
| 959 | pkg: &package::Package, |
| 960 | path: *[u8], |
| 961 | asmDataLen: &mut u32, |
| 962 | arena: &mut alloc::Arena |
| 963 | ) throws (Error) { |
| 964 | pkgLog(pkg, &["asm:", "parsing", "(", path, ")", ".."]); |
| 965 | |
| 966 | let sourceLen = unix::readFile(path, &mut ASM_SOURCE_BUF[..]) else { |
| 967 | throw error(&["error reading assembly file"]); |
| 968 | }; |
| 969 | let input = &ASM_SOURCE_BUF[..sourceLen]; |
| 970 | if input.len == ASM_SOURCE_BUF.len { |
| 971 | throw error(&["fatal:", "assembly source too large:", path]); |
| 972 | } |
| 973 | // Assembly symbols borrow source bytes until final linking. |
| 974 | let buffer = try alloc::allocSlice(arena, 1, 1, input.len) catch { |
| 975 | throw error(&["assembly source workspace exhausted"]); |
| 976 | }; |
| 977 | let source = buffer as *mut [u8]; |
| 978 | try! mem::copy(source, input); |
| 979 | let program = try asm::assemble( |
| 980 | asm::scanner::SourceKind::File { path }, |
| 981 | source, |
| 982 | &mut ASM_TEXT_BUF[..], |
| 983 | &mut ASM_DATA_BUF[..], |
| 984 | arena, |
| 985 | &mut STRING_POOL, |
| 986 | rv64::RO_DATA_BASE + *asmDataLen |
| 987 | ) catch { |
| 988 | throw error(&["assembly failed:", path]); |
| 989 | }; |
| 990 | if *asmDataLen + program.data.len > ASM_RO_DATA_BUF.len { |
| 991 | throw error(&["fatal:", "assembly rodata too large"]); |
| 992 | } |
| 993 | try! mem::copy(&mut ASM_RO_DATA_BUF[*asmDataLen..], program.data); |
| 994 | set *asmDataLen += program.data.len; |
| 995 | |
| 996 | rv64::addAssembly(generator, program); |
| 997 | } |
| 998 | |
| 999 | /// Assemble all inputs collected in the package inputs. |
| 1000 | unsafe fn assembleAsmInputs( |
| 1001 | ctx: &CompileContext, |
| 1002 | generator: &mut rv64::Generator, |
| 1003 | asmDataLen: &mut u32, |
| 1004 | arena: &mut alloc::Arena |
| 1005 | ) -> *[u8] throws (Error) { |
| 1006 | for i in 0..ctx.packageCount { |
| 1007 | for j in 0..ctx.inputs[i].asmPathCount { |
| 1008 | try assembleAsmModule( |
| 1009 | generator, |
| 1010 | &ctx.packages[i], |
| 1011 | ctx.inputs[i].asmPaths[j], |
| 1012 | asmDataLen, |
| 1013 | arena |
| 1014 | ); |
| 1015 | } |
| 1016 | } |
| 1017 | return &ASM_RO_DATA_BUF[..*asmDataLen]; |
| 1018 | } |
| 1019 | |
| 1020 | /// Generate dependency packages before the entry package. |
| 1021 | unsafe fn generateAllPackagesInto 'arena 'phase ( |
| 1022 | ctx: &CompileContext, low: &mut lower::Lowerer 'arena 'phase, |
| 1023 | generator: &mut rv64::Generator, fnArena: &mut alloc::Arena |
| 1024 | ) throws (Error) where 'arena: 'phase { |
| 1025 | let entryIdx = ctx.entryPkgIdx else panic "generateAllPackagesInto: no entry package"; |
| 1026 | for i in 0..ctx.packageCount { |
| 1027 | if i <> entryIdx { |
| 1028 | try generatePackageInto(ctx, low, &ctx.packages[i], false, generator, fnArena); |
| 1029 | } |
| 1030 | } |
| 1031 | try generatePackageInto(ctx, low, &ctx.packages[entryIdx], true, generator, fnArena); |
| 1032 | } |
| 1033 | |
| 1034 | /// Generate all functions in one package. |
| 1035 | unsafe fn generatePackageInto 'arena 'phase ( |
| 1036 | ctx: &CompileContext, low: &mut lower::Lowerer 'arena 'phase, |
| 1037 | pkg: &package::Package, isEntry: bool, |
| 1038 | generator: &mut rv64::Generator, fnArena: &mut alloc::Arena |
| 1039 | ) throws (Error) where 'arena: 'phase { |
| 1040 | let rootId = pkg.rootModuleId else throw error(&["no root module found"]); |
| 1041 | lower::setPackage(low, pkg.name); |
| 1042 | try generateModuleTree(ctx, low, rootId, isEntry, pkg, generator, fnArena); |
| 1043 | } |
| 1044 | |
| 1045 | /// Lower and consume each function before visiting the next declaration. |
| 1046 | unsafe fn generateModuleTree 'arena 'phase ( |
| 1047 | ctx: &CompileContext, low: &mut lower::Lowerer 'arena 'phase, |
| 1048 | modId: u16, isRoot: bool, pkg: &package::Package, |
| 1049 | generator: &mut rv64::Generator, fnArena: &mut alloc::Arena |
| 1050 | ) throws (Error) where 'arena: 'phase { |
| 1051 | let entry = module::get(&ctx.graph, modId) else throw error(&["module entry not found"]); |
| 1052 | let modAst = module::astFor(entry) else throw error(&["module has no AST"]); |
| 1053 | pkgLog(pkg, &["lowering", "(", entry.filePath, ")", ".."]); |
| 1054 | set low.currentMod = modId; |
| 1055 | let mut cursor = try lower::moduleCursor(modAst, isRoot) catch err { |
| 1056 | io::printError("radiance: internal error during lowering: "); |
| 1057 | lower::printError(err); |
| 1058 | io::printError("\n"); |
| 1059 | throw Error::Other; |
| 1060 | }; |
| 1061 | loop { |
| 1062 | let result = try lower::lowerNext(low, &mut cursor, fnArena) catch err { |
| 1063 | io::printError("radiance: internal error during lowering: "); |
| 1064 | lower::printError(err); |
| 1065 | io::printError("\n"); |
| 1066 | throw Error::Other; |
| 1067 | }; |
| 1068 | let next = result else break; |
| 1069 | codegen::emit(generator, fnArena, &*next.function, next.role); |
| 1070 | } |
| 1071 | for i in 0..module::childCount(entry) { |
| 1072 | let childId = module::childAt(entry, i); |
| 1073 | try generateModuleTree(ctx, low, childId, false, pkg, generator, fnArena); |
| 1074 | } |
| 1075 | } |
| 1076 | |
| 1077 | /// Lower all packages while streaming each lowered function into RV64 codegen. |
| 1078 | unsafe fn lowerAndGenerateAllPackages 'arena ( |
| 1079 | ctx: *unsafe mut CompileContext, |
| 1080 | res: *unsafe mut resolver::Resolver 'arena, |
| 1081 | fnArena: &mut alloc::Arena, |
| 1082 | codegenOptions: CodegenOptions |
| 1083 | ) -> rv64::Program throws (Error) { |
| 1084 | let entryIdx = ctx.entryPkgIdx else { |
| 1085 | panic "lowerAndGenerateAllPackages: no entry package"; |
| 1086 | }; |
| 1087 | let entryPkg = &ctx.packages[entryIdx]; |
| 1088 | let startupPath = getEntryStartupPath(ctx); |
| 1089 | let options = lower::LowerOptions { debug: ctx.debug, buildTest: ctx.config.buildTest }; |
| 1090 | let storage = rv64::Storage { |
| 1091 | dataSyms: &mut CODEGEN_DATA_SYMS[..], |
| 1092 | dataSymEntries: &mut CODEGEN_DATA_SYM_ENTRIES[..], |
| 1093 | }; |
| 1094 | let mut entryPatch = rv64::EntryPatch::None; |
| 1095 | match codegenOptions.entryMode { |
| 1096 | case CodegenEntryMode::DefaultEntry => { |
| 1097 | set entryPatch = rv64::EntryPatch::Reserved(nil); |
| 1098 | } |
| 1099 | else => {} |
| 1100 | } |
| 1101 | let emitterStorage = emit::Storage { |
| 1102 | code: &mut CODEGEN_INSTRUCTIONS[..], |
| 1103 | pendingBranches: &mut CODEGEN_PENDING_BRANCHES[..], |
| 1104 | pendingCalls: &mut CODEGEN_PENDING_CALLS[..], |
| 1105 | pendingJumps: &mut CODEGEN_PENDING_JUMPS[..], |
| 1106 | pendingAddrLoads: &mut CODEGEN_PENDING_ADDR_LOADS[..], |
| 1107 | blockOffsets: &mut CODEGEN_BLOCK_OFFSETS[..], |
| 1108 | funcEntries: &mut CODEGEN_FUNC_ENTRIES[..], |
| 1109 | funcs: &mut CODEGEN_FUNCS[..], |
| 1110 | debugEntries: &mut CODEGEN_DEBUG_ENTRIES[..], |
| 1111 | }; |
| 1112 | let mut generator = rv64::beginProgramWithStorage( |
| 1113 | rv64::ProgramOptions { |
| 1114 | entryPatch, |
| 1115 | debug: codegenOptions.debug, |
| 1116 | placement: rv64::image::Placement::Hosted, |
| 1117 | }, |
| 1118 | emitterStorage |
| 1119 | ); |
| 1120 | let arena = (&mut *res.arena) as *unsafe mut alloc::Arena; |
| 1121 | let resolved: 'phase = &*res, graph = &ctx.graph where 'arena: 'phase in { |
| 1122 | let mut low = lower::lowerer( |
| 1123 | resolved, graph, entryPkg.name, arena, options |
| 1124 | ); |
| 1125 | let mut asmDataLen: u32 = 0; |
| 1126 | if let path = startupPath { |
| 1127 | try assembleAsmModule(&mut generator, entryPkg, path, &mut asmDataLen, arena); |
| 1128 | } |
| 1129 | try generateAllPackagesInto(ctx, &mut low, &mut generator, fnArena); |
| 1130 | let asmData = try assembleAsmInputs(ctx, &mut generator, &mut asmDataLen, arena); |
| 1131 | |
| 1132 | match generator.entryPatch { |
| 1133 | case rv64::EntryPatch::Reserved(targetName) => { |
| 1134 | if targetName == nil { |
| 1135 | throw error(&["fatal:", "no default function found"]); |
| 1136 | } |
| 1137 | } |
| 1138 | else => { |
| 1139 | } |
| 1140 | } |
| 1141 | if let path = codegenOptions.logPath { |
| 1142 | pkgLog(entryPkg, &["generating code", "(", path, ")", ".."]); |
| 1143 | } |
| 1144 | return try rv64::finishProgram( |
| 1145 | generator, low.data, storage, asmData, |
| 1146 | &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..] |
| 1147 | ) catch err { |
| 1148 | match err { |
| 1149 | case rv64::Error::Allocation => throw error(&["code generation workspace exhausted"]), |
| 1150 | case rv64::Error::Capacity => throw error(&["code generation output capacity exceeded"]), |
| 1151 | case rv64::Error::Symbol => throw error(&["code generation has an unresolved symbol"]), |
| 1152 | case rv64::Error::Relocation => throw error(&["code generation relocation is out of range"]), |
| 1153 | case rv64::Error::Image(_) => throw error(&["code generation image layout is invalid"]), |
| 1154 | case rv64::Error::Data(_) => throw error(&["code generation data layout is invalid"]), |
| 1155 | } |
| 1156 | }; |
| 1157 | } |
| 1158 | } |
| 1159 | |
| 1160 | /// Source exports selected for one binary RIL package. |
| 1161 | record PackageExports: Copy { |
| 1162 | /// Number of initialized entries in the caller's export table. |
| 1163 | count: u32, |
| 1164 | /// Default function entry. |
| 1165 | entry: ?*[u8], |
| 1166 | } |
| 1167 | |
| 1168 | /// Collect exported definitions and the default entry from a package's source modules. |
| 1169 | unsafe fn packageExports( |
| 1170 | ctx: &CompileContext, |
| 1171 | pkg: &package::Package, |
| 1172 | ilProgram: &il::Program, |
| 1173 | exports: &mut [binary::Export], |
| 1174 | arena: &mut alloc::Arena |
| 1175 | ) -> PackageExports throws (Error) { |
| 1176 | let mut count: u32 = 0; |
| 1177 | let mut entryName: ?*[u8] = nil; |
| 1178 | for i in 0..ctx.graph.entriesLen { |
| 1179 | let modEntry = module::get(&ctx.graph, i as u16) else continue; |
| 1180 | if modEntry.packageId <> pkg.id { |
| 1181 | continue; |
| 1182 | } |
| 1183 | let root = module::astFor(modEntry) else continue; |
| 1184 | let case ast::NodeValue::Block(block) = root.value else continue; |
| 1185 | for node in block.statements { |
| 1186 | let mut ident: ?*ast::Node = nil; |
| 1187 | let mut attrs: ?ast::Attributes = nil; |
| 1188 | let mut kind = binary::ExportKind::Function; |
| 1189 | match node.value { |
| 1190 | case ast::NodeValue::FnDecl(decl) => { |
| 1191 | set ident = decl.name; |
| 1192 | set attrs = decl.attrs; |
| 1193 | }, |
| 1194 | case ast::NodeValue::ConstDecl(decl) => { |
| 1195 | set ident = decl.ident; set attrs = decl.attrs; set kind = binary::ExportKind::Data; |
| 1196 | }, |
| 1197 | case ast::NodeValue::StaticDecl(decl) => { |
| 1198 | set ident = decl.ident; set attrs = decl.attrs; set kind = binary::ExportKind::Data; |
| 1199 | }, |
| 1200 | else => continue, |
| 1201 | } |
| 1202 | let attributes = attrs else continue; |
| 1203 | if ast::attributesContains(&attributes, ast::Attribute::Intrinsic) { |
| 1204 | continue; |
| 1205 | } |
| 1206 | let isDefault = ast::attributesContains(&attributes, ast::Attribute::Default) |
| 1207 | and pkg.rootModuleId == modEntry.id; |
| 1208 | if not isDefault and not ast::attributesContains(&attributes, ast::Attribute::Export) { |
| 1209 | continue; |
| 1210 | } |
| 1211 | let nameNode = ident else continue; |
| 1212 | let case ast::NodeValue::Ident(name) = nameNode.value else continue; |
| 1213 | let qualified = il::formatQualifiedName(arena, module::moduleQualifiedPath(modEntry), name); |
| 1214 | let mut present = false; |
| 1215 | match kind { |
| 1216 | case binary::ExportKind::Function => { |
| 1217 | for func in ilProgram.fns { |
| 1218 | if mem::eq(func.name, qualified) { |
| 1219 | set present = true; |
| 1220 | } |
| 1221 | } |
| 1222 | }, |
| 1223 | case binary::ExportKind::Data => { |
| 1224 | for item in ilProgram.data { |
| 1225 | if mem::eq(item.name, qualified) { |
| 1226 | set present = true; |
| 1227 | } |
| 1228 | } |
| 1229 | }, |
| 1230 | } |
| 1231 | if not present { |
| 1232 | continue; |
| 1233 | } |
| 1234 | if count == exports.len { |
| 1235 | throw error(&["too many package exports"]); |
| 1236 | } |
| 1237 | set exports[count] = binary::Export { name: qualified, kind }; |
| 1238 | set count += 1; |
| 1239 | if isDefault { |
| 1240 | set entryName = qualified; |
| 1241 | } |
| 1242 | } |
| 1243 | } |
| 1244 | return PackageExports { count, entry: entryName }; |
| 1245 | } |
| 1246 | |
| 1247 | /// Emit one binary RIL file per package into an existing directory. |
| 1248 | unsafe fn emitPackages 'arena ( |
| 1249 | ctx: *unsafe mut CompileContext, |
| 1250 | res: *unsafe mut resolver::Resolver 'arena, |
| 1251 | directory: *[u8] |
| 1252 | ) throws (Error) { |
| 1253 | for i in 0..ctx.packageCount { |
| 1254 | if ctx.inputs[i].asmPathCount > 0 or ctx.inputs[i].startupPath <> nil { |
| 1255 | throw error(&["binary RIL output requires Radiance source modules"]); |
| 1256 | } |
| 1257 | } |
| 1258 | let unified = try lowerAllPackages(ctx, res); |
| 1259 | let allocator = alloc::arenaAllocator(&mut *res.arena); |
| 1260 | for i in 0..ctx.packageCount { |
| 1261 | let pkg = &ctx.packages[i]; |
| 1262 | let mut dataItems: *mut [il::Data] = &mut []; |
| 1263 | let mut functions: *unsafe mut [*unsafe il::Fn] = &mut []; |
| 1264 | for item in unified.data { |
| 1265 | if ownsSymbol(pkg.name, item.name) { |
| 1266 | dataItems.append(item, allocator); |
| 1267 | } |
| 1268 | } |
| 1269 | for func in unified.fns { |
| 1270 | if ownsSymbol(pkg.name, func.name) { |
| 1271 | functions.append(func, allocator); |
| 1272 | } |
| 1273 | } |
| 1274 | let local = il::Program { data: &dataItems[..], fns: functions }; |
| 1275 | let selected = try packageExports(ctx, pkg, &local, &mut PACKAGE_EXPORTS[..], &mut *res.arena); |
| 1276 | let mut dependencies: [*[u8]; MAX_PACKAGES] = undefined; |
| 1277 | let mut names = collect::new(&mut PACKAGE_SYMBOLS[..], &mut dependencies[..]); |
| 1278 | let image = try collect::package(&mut names, pkg.name, local, &PACKAGE_EXPORTS[..selected.count], selected.entry) catch { |
| 1279 | throw error(&["cannot collect binary RIL package", pkg.name]); |
| 1280 | }; |
| 1281 | let length = try program::encode(&mut FN_ARENA[..], &image) catch { |
| 1282 | throw error(&["binary RIL output capacity exceeded", pkg.name]); |
| 1283 | }; |
| 1284 | let mut path: [u8; MAX_PATH_LEN] = undefined; |
| 1285 | let mut pos: u32 = 0; |
| 1286 | for part in &[directory, "/", pkg.name, ".ril"] { |
| 1287 | set pos += try mem::copy(&mut path[pos..MAX_PATH_LEN - 1], part) catch { |
| 1288 | throw error(&["binary RIL output path is too long"]); |
| 1289 | }; |
| 1290 | } |
| 1291 | set path[pos] = 0; |
| 1292 | if not unix::writeFile(&path[..pos], &FN_ARENA[..length]) { |
| 1293 | throw error(&["cannot write binary RIL package", pkg.name]); |
| 1294 | } |
| 1295 | } |
| 1296 | } |
| 1297 | |
| 1298 | /// Match a qualified definition to its package name. |
| 1299 | fn ownsSymbol(owner: *[u8], name: *[u8]) -> bool { |
| 1300 | let suffix = mem::stripPrefix(owner, name) else return false; |
| 1301 | return suffix.len > 2 and suffix[0] == ':' and suffix[1] == ':'; |
| 1302 | } |
| 1303 | |
| 1304 | /// Lower, optionally dump, and optionally generate binary output. |
| 1305 | unsafe fn compile 'arena ( |
| 1306 | ctx: *unsafe mut CompileContext, |
| 1307 | res: *unsafe mut resolver::Resolver 'arena, |
| 1308 | fnArena: &mut alloc::Arena |
| 1309 | ) throws (Error) { |
| 1310 | let entryPkg = try getEntryPackage(ctx); |
| 1311 | if let directory = ctx.rilDirectory { |
| 1312 | try emitPackages(ctx, res, directory); |
| 1313 | return; |
| 1314 | } |
| 1315 | let mut out = sexpr::Output::Stdout; |
| 1316 | |
| 1317 | if ctx.dump == Dump::Il { |
| 1318 | // Lower all packages into a single unified IL program for dumping. |
| 1319 | let program = try lowerAllPackages(ctx, res); |
| 1320 | il::printer::printProgram(&mut out, &program); |
| 1321 | io::print("\n"); |
| 1322 | return; |
| 1323 | } |
| 1324 | if ctx.dump == Dump::Asm { |
| 1325 | let result = try lowerAndGenerateAllPackages(ctx, res, fnArena, CodegenOptions { |
| 1326 | logPath: nil, |
| 1327 | debug: false, |
| 1328 | entryMode: CodegenEntryMode::None, |
| 1329 | }); |
| 1330 | printer::printCodeTo(&mut out, entryPkg.name, result.code, result.funcs, res.arena); |
| 1331 | io::print("\n"); |
| 1332 | |
| 1333 | return; |
| 1334 | } |
| 1335 | // Generate binary output if path specified. |
| 1336 | let outPath = ctx.outputPath else { |
| 1337 | try lowerAllPackages(ctx, res); |
| 1338 | return; |
| 1339 | }; |
| 1340 | let startupPath = getEntryStartupPath(ctx); |
| 1341 | let result = try lowerAndGenerateAllPackages(ctx, res, fnArena, CodegenOptions { |
| 1342 | logPath: outPath, |
| 1343 | debug: ctx.debug, |
| 1344 | entryMode: CodegenEntryMode::None |
| 1345 | if startupPath <> nil |
| 1346 | else CodegenEntryMode::DefaultEntry, |
| 1347 | }); |
| 1348 | |
| 1349 | if not writeImage( |
| 1350 | result.code, |
| 1351 | &RO_DATA_BUF[..result.roDataSize], |
| 1352 | &RW_DATA_BUF[..result.rwDataSize], |
| 1353 | outPath |
| 1354 | ) { |
| 1355 | throw error(&["fatal:", "failed to write output file"]); |
| 1356 | } |
| 1357 | |
| 1358 | // Write debug info file if enabled. |
| 1359 | if ctx.debug { |
| 1360 | try writeDebugInfo(result.debugEntries, &ctx.graph, outPath, res.arena); |
| 1361 | } |
| 1362 | pkgLog(&entryPkg, &["ok", "(", outPath, ")"]); |
| 1363 | } |
| 1364 | |
| 1365 | @default unsafe fn main(env: *sys::Env) -> i32 { |
| 1366 | let mut arena = ast::nodeArena(&mut TEMP_ARENA[..]); |
| 1367 | let mut ctx = try processCommand(env.args, &mut arena) catch { |
| 1368 | return 1; |
| 1369 | }; |
| 1370 | match ctx.dump { |
| 1371 | case Dump::Ast => { |
| 1372 | try dumpAst(&ctx) catch { |
| 1373 | return 1; |
| 1374 | }; |
| 1375 | return 0; |
| 1376 | } |
| 1377 | case Dump::Graph => { |
| 1378 | dumpGraph(&ctx); |
| 1379 | return 0; |
| 1380 | } |
| 1381 | else => {} |
| 1382 | } |
| 1383 | // Generate test runner if in test mode. |
| 1384 | if ctx.config.buildTest { |
| 1385 | try generateTestRunner(&mut ctx, &mut arena) catch { |
| 1386 | return 1; |
| 1387 | }; |
| 1388 | } |
| 1389 | // Run resolution phase. |
| 1390 | let mut mainArena = alloc::new(&mut MAIN_ARENA[..]); |
| 1391 | let arenaRef: 'arena = &mut mainArena in { |
| 1392 | let mut res = try runResolver(&mut ctx, arenaRef, arena.nextId) catch { |
| 1393 | return 1; |
| 1394 | }; |
| 1395 | let mut fnArena = alloc::new(&mut FN_ARENA[..]); |
| 1396 | |
| 1397 | // Lower, dump, and/or generate output. |
| 1398 | try compile(&mut ctx, &mut res, &mut fnArena) catch { |
| 1399 | return 1; |
| 1400 | }; |
| 1401 | return 0; |
| 1402 | } |
| 1403 | } |