compiler/
radiance.rad
37.5 KiB
lib/
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
LICENSE
1.1 KiB
Makefile
3.7 KiB
README
4.8 KiB
STYLE
2.5 KiB
std.lib
1.2 KiB
std.lib.test
347 B
compiler/radiance.rad
raw
| 1 | //! Radiance compiler front-end. |
| 2 | use std::mem; |
| 3 | use std::fmt; |
| 4 | use std::io; |
| 5 | use std::lang::alloc; |
| 6 | use std::lang::ast; |
| 7 | use std::lang::parser; |
| 8 | use std::lang::scanner; |
| 9 | use std::lang::resolver; |
| 10 | use std::lang::module; |
| 11 | use std::lang::strings; |
| 12 | use std::lang::package; |
| 13 | use std::lang::il; |
| 14 | use std::lang::lower; |
| 15 | use std::arch::rv64; |
| 16 | use std::arch::rv64::asm; |
| 17 | use std::arch::rv64::printer; |
| 18 | use std::lang::sexpr; |
| 19 | use std::lang::gen::data; |
| 20 | use std::lang::gen::types; |
| 21 | use std::sys; |
| 22 | use std::sys::unix; |
| 23 | use std::collections::dict; |
| 24 | |
| 25 | /// Maximum number of modules we can load per package. |
| 26 | constant MAX_LOADED_MODULES: u32 = 64; |
| 27 | /// Maximum number of packages we can compile. |
| 28 | constant MAX_PACKAGES: u32 = 4; |
| 29 | /// Total module entries across all packages. |
| 30 | constant MAX_TOTAL_MODULES: u32 = 192; |
| 31 | /// Source code buffer arena (2 MB). |
| 32 | constant MAX_SOURCES_SIZE: u32 = 2097152; |
| 33 | /// Maximum number of test functions we can discover. |
| 34 | constant MAX_TESTS: u32 = 1024; |
| 35 | /// Maximum number of assembly source paths we can load per package. |
| 36 | constant MAX_ASM_MODULES: u32 = 64; |
| 37 | |
| 38 | /// AST arena size (40 MB) - retains parsed nodes throughout compilation. |
| 39 | constant TEMP_ARENA_SIZE: u32 = 41943040; |
| 40 | /// Per-function lowering and register-allocation arena size (16 MB). |
| 41 | constant FN_ARENA_SIZE: u32 = 16777216; |
| 42 | /// Main arena size (80 MB) - lives throughout compilation. |
| 43 | /// Used for: resolver data, types, symbols, global IL data, and codegen output. |
| 44 | constant MAIN_ARENA_SIZE: u32 = 83886080; |
| 45 | |
| 46 | /// AST storage arena. |
| 47 | static TEMP_ARENA: [u8; TEMP_ARENA_SIZE] = undefined; |
| 48 | /// Scratch storage reclaimed after each generated function. |
| 49 | static FN_ARENA: [u8; FN_ARENA_SIZE] = undefined; |
| 50 | /// Main storage arena - persists throughout compilation. |
| 51 | static MAIN_ARENA: [u8; MAIN_ARENA_SIZE] = undefined; |
| 52 | |
| 53 | /// Module source code. |
| 54 | static MODULE_SOURCES: [u8; MAX_SOURCES_SIZE] = undefined; |
| 55 | /// Module entries for all packages. |
| 56 | static MODULE_ENTRIES: [module::ModuleEntry; MAX_TOTAL_MODULES] = undefined; |
| 57 | /// String pool. |
| 58 | static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 }; |
| 59 | |
| 60 | /// Package scope. |
| 61 | static RESOLVER_PKG_SCOPE: resolver::Scope = undefined; |
| 62 | /// Errors emitted by resolver. |
| 63 | static RESOLVER_ERRORS: [resolver::Error; resolver::MAX_ERRORS] = undefined; |
| 64 | |
| 65 | /// Code generation storage. |
| 66 | static CODEGEN_DATA_SYMS: [data::DataSym; data::MAX_DATA_SYMS] = undefined; |
| 67 | /// Hash table entries for data symbol lookup. |
| 68 | static CODEGEN_DATA_SYM_ENTRIES: [dict::Entry; data::DATA_SYM_TABLE_SIZE] = undefined; |
| 69 | |
| 70 | /// Debug info file extension. |
| 71 | constant DEBUG_EXT: *[u8] = ".debug"; |
| 72 | |
| 73 | /// Maximum rodata size (4MB). |
| 74 | constant MAX_RO_DATA_SIZE: u32 = 4194304; |
| 75 | /// Maximum rwdata size (4MB). |
| 76 | constant MAX_RW_DATA_SIZE: u32 = 4194304; |
| 77 | /// Maximum path length. |
| 78 | constant MAX_PATH_LEN: u32 = 256; |
| 79 | /// Read-only data buffer. |
| 80 | static RO_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = undefined; |
| 81 | /// Read-write data buffer. |
| 82 | static RW_DATA_BUF: [u8; MAX_RW_DATA_SIZE] = undefined; |
| 83 | /// Assembly module source buffer. |
| 84 | static ASM_SOURCE_BUF: [u8; MAX_SOURCES_SIZE] = undefined; |
| 85 | /// Temporary assembly text buffer. |
| 86 | static ASM_TEXT_BUF: [u32; 262144] = undefined; |
| 87 | /// Temporary assembly data buffer. |
| 88 | static ASM_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = undefined; |
| 89 | /// Accumulated assembly read-only data. |
| 90 | static ASM_RO_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = undefined; |
| 91 | |
| 92 | /// Assembly source file extension. |
| 93 | constant ASM_SOURCE_EXT: *[u8] = ".ras"; |
| 94 | /// Symbol name exported for startup code to call the semantic entry function. |
| 95 | constant DEFAULT_ENTRY_SYMBOL: *[u8] = "::default"; |
| 96 | |
| 97 | /// Usage string. |
| 98 | constant USAGE: *[u8] = |
| 99 | "usage: radiance -pkg <name> [-start <input.ras>] -mod <input>.. [-pkg <name> -mod <input>..] -entry <pkg> -o <output>\n"; |
| 100 | |
| 101 | /// Compiler error. |
| 102 | union Error { |
| 103 | Other, |
| 104 | } |
| 105 | |
| 106 | /// What to dump during compilation. |
| 107 | union Dump { |
| 108 | /// Don't dump anything. |
| 109 | None, |
| 110 | /// Dump the parsed AST before semantic analysis. |
| 111 | Ast, |
| 112 | /// Dump the module graph. |
| 113 | Graph, |
| 114 | /// Dump the IL. |
| 115 | Il, |
| 116 | /// Dump the generated assembly. |
| 117 | Asm, |
| 118 | } |
| 119 | |
| 120 | /// A discovered test function. |
| 121 | record TestDesc { |
| 122 | /// Full module qualified path segments (eg. ["std", "tests"]). |
| 123 | modPath: *[*[u8]], |
| 124 | /// Test function name (eg. "testFoo"). |
| 125 | fnName: *[u8], |
| 126 | } |
| 127 | |
| 128 | /// Source inputs belonging to one command-line package. |
| 129 | record PackageInput { |
| 130 | /// Package name from the `-pkg` argument. |
| 131 | name: *[u8], |
| 132 | /// Optional startup assembly emitted before generated text. |
| 133 | startupPath: ?*[u8], |
| 134 | /// Radiance source paths for this package. |
| 135 | radPaths: [*[u8]; MAX_LOADED_MODULES], |
| 136 | /// Number of Radiance source paths. |
| 137 | radPathCount: u32, |
| 138 | /// Assembly source paths for this package. |
| 139 | asmPaths: [*[u8]; MAX_ASM_MODULES], |
| 140 | /// Number of assembly source paths. |
| 141 | asmPathCount: u32, |
| 142 | } |
| 143 | |
| 144 | /// Compilation context. |
| 145 | record CompileContext { |
| 146 | /// Array of packages to compile. |
| 147 | packages: [package::Package; MAX_PACKAGES], |
| 148 | /// Driver inputs for each package slot. |
| 149 | inputs: [PackageInput; MAX_PACKAGES], |
| 150 | /// Number of packages. |
| 151 | packageCount: u32, |
| 152 | /// Index of entry package. |
| 153 | entryPkgIdx: ?u32, |
| 154 | /// Global module graph shared by all packages. |
| 155 | graph: module::ModuleGraph, |
| 156 | /// Resolver configuration. |
| 157 | config: resolver::Config, |
| 158 | /// What to dump during compilation. |
| 159 | dump: Dump, |
| 160 | /// Output path for binary. |
| 161 | outputPath: ?*[u8], |
| 162 | /// Whether to emit debug info (.debug file). |
| 163 | debug: bool, |
| 164 | } |
| 165 | |
| 166 | /// Root module info for a package. |
| 167 | record RootModule { |
| 168 | entry: *module::ModuleEntry, |
| 169 | ast: *mut ast::Node, |
| 170 | } |
| 171 | |
| 172 | /// State carried by the streaming lowerer/codegen callback. |
| 173 | record CodegenSinkContext { |
| 174 | /// RV64 generator receiving lowered functions. |
| 175 | generator: *mut rv64::Generator, |
| 176 | /// Arena holding the current function's lowered IL. |
| 177 | fnArena: *mut alloc::Arena, |
| 178 | } |
| 179 | |
| 180 | /// Entry handling for streamed code generation. |
| 181 | union CodegenEntryMode { |
| 182 | /// Do not reserve an entry jump. |
| 183 | None, |
| 184 | /// Reserve and patch an entry jump to the `@default` function. |
| 185 | DefaultEntry, |
| 186 | } |
| 187 | |
| 188 | /// Options controlling streamed lowering and code generation. |
| 189 | record CodegenOptions { |
| 190 | /// Optional output path used for progress logging. |
| 191 | logPath: ?*[u8], |
| 192 | /// Whether to emit debug source locations. |
| 193 | debug: bool, |
| 194 | /// How the generated program should handle entry. |
| 195 | entryMode: CodegenEntryMode, |
| 196 | } |
| 197 | |
| 198 | /// Print a driver error line. |
| 199 | fn error(msg: *[*[u8]]) -> Error { |
| 200 | io::printError("radiance: "); |
| 201 | |
| 202 | for part, i in msg { |
| 203 | io::printError(part); |
| 204 | if i < msg.len - 1 { |
| 205 | io::printError(" "); |
| 206 | } |
| 207 | } |
| 208 | io::printError("\n"); |
| 209 | return Error::Other; |
| 210 | } |
| 211 | |
| 212 | /// Print a log line for the given package. |
| 213 | fn pkgLog(pkg: *package::Package, msg: *[*[u8]]) { |
| 214 | io::printError("radiance: "); |
| 215 | io::printError(pkg.name); |
| 216 | io::printError(": "); |
| 217 | |
| 218 | for part, i in msg { |
| 219 | io::printError(part); |
| 220 | if i < msg.len - 1 { |
| 221 | io::printError(" "); |
| 222 | } |
| 223 | } |
| 224 | io::printError("\n"); |
| 225 | } |
| 226 | |
| 227 | /// Return `true` when `path` ends with `ext`. |
| 228 | fn hasExtension(path: *[u8], ext: *[u8]) -> bool { |
| 229 | if path.len < ext.len { |
| 230 | return false; |
| 231 | } |
| 232 | let start = path.len - ext.len; |
| 233 | return mem::eq(&path[start..], ext); |
| 234 | } |
| 235 | |
| 236 | /// Create an empty source input set for one package. |
| 237 | fn packageInput(name: *[u8]) -> PackageInput { |
| 238 | return PackageInput { |
| 239 | name, |
| 240 | startupPath: nil, |
| 241 | radPaths: undefined, |
| 242 | radPathCount: 0, |
| 243 | asmPaths: undefined, |
| 244 | asmPathCount: 0, |
| 245 | }; |
| 246 | } |
| 247 | |
| 248 | /// Register, load, and parse `path` within `pkg`. |
| 249 | fn processModule( |
| 250 | pkg: *mut package::Package, |
| 251 | graph: *mut module::ModuleGraph, |
| 252 | path: *[u8], |
| 253 | nodeArena: *mut ast::NodeArena, |
| 254 | sourceArena: *mut alloc::Arena |
| 255 | ) throws (Error) { |
| 256 | pkgLog(pkg, &["parsing", "(", path, ")", ".."]); |
| 257 | |
| 258 | let moduleId = try package::registerModule(pkg, graph, path) catch { |
| 259 | throw error(&["error registering module"]); |
| 260 | }; |
| 261 | // Read file into remaining arena space. |
| 262 | let buffer = alloc::remainingBuf(sourceArena); |
| 263 | if buffer.len == 0 { |
| 264 | throw error(&["fatal:", "source arena exhausted"]); |
| 265 | } |
| 266 | let source = unix::readFile(path, buffer) else { |
| 267 | throw error(&["error reading file"]); |
| 268 | }; |
| 269 | if source.len == buffer.len { |
| 270 | throw error(&["fatal:", "source arena too small, file truncated:", path]); |
| 271 | } |
| 272 | // Commit only what was read. |
| 273 | alloc::commit(sourceArena, source.len); |
| 274 | |
| 275 | let ast = try parser::parse(scanner::SourceLoc::File(path), source, nodeArena, &mut STRING_POOL) catch { |
| 276 | throw Error::Other; |
| 277 | }; |
| 278 | try module::setAst(graph, moduleId, ast) catch { |
| 279 | throw error(&["error setting AST"]); |
| 280 | }; |
| 281 | try module::setSource(graph, moduleId, source) catch { |
| 282 | throw error(&["error setting source"]); |
| 283 | }; |
| 284 | } |
| 285 | |
| 286 | /// Consume the next argument, or print an error and throw. |
| 287 | fn nextArg(args: *[*[u8]], idx: *mut u32, msg: *[*[u8]]) -> *[u8] throws (Error) { |
| 288 | set *idx += 1; |
| 289 | if *idx >= args.len { |
| 290 | throw error(msg); |
| 291 | } |
| 292 | return args[*idx]; |
| 293 | } |
| 294 | |
| 295 | /// Parse CLI arguments and return compilation context. |
| 296 | fn processCommand( |
| 297 | args: *[*[u8]], |
| 298 | arena: *mut ast::NodeArena |
| 299 | ) -> CompileContext throws (Error) { |
| 300 | let mut buildTest = false; |
| 301 | let mut debugEnabled = false; |
| 302 | let mut outputPath: ?*[u8] = nil; |
| 303 | let mut dump = Dump::None; |
| 304 | let mut entryPkgName: ?*[u8] = nil; |
| 305 | |
| 306 | // Per-package source path tracking. |
| 307 | let mut inputs: [PackageInput; MAX_PACKAGES] = undefined; |
| 308 | let mut pkgCount: u32 = 0; |
| 309 | let mut currentPkgIdx: ?u32 = nil; |
| 310 | |
| 311 | if args.len == 0 { |
| 312 | io::printError(USAGE); |
| 313 | throw Error::Other; |
| 314 | } |
| 315 | let mut idx: u32 = 0; |
| 316 | |
| 317 | while idx < args.len { |
| 318 | let arg = args[idx]; |
| 319 | if mem::eq(arg, "-pkg") { |
| 320 | try nextArg(args, &mut idx, &["`-pkg` requires a package name"]); |
| 321 | if pkgCount >= MAX_PACKAGES { |
| 322 | throw error(&["too many packages specified"]); |
| 323 | } |
| 324 | set inputs[pkgCount] = packageInput(args[idx]); |
| 325 | set currentPkgIdx = pkgCount; |
| 326 | set pkgCount += 1; |
| 327 | } else if mem::eq(arg, "-mod") { |
| 328 | try nextArg(args, &mut idx, &["`-mod` requires a module path"]); |
| 329 | let pkgIdx = currentPkgIdx else { |
| 330 | throw error(&["`-mod` must follow a `-pkg` argument"]); |
| 331 | }; |
| 332 | let input = &mut inputs[pkgIdx]; |
| 333 | if hasExtension(args[idx], ASM_SOURCE_EXT) { |
| 334 | if input.asmPathCount >= MAX_ASM_MODULES { |
| 335 | throw error(&["too many assembly modules specified"]); |
| 336 | } |
| 337 | set input.asmPaths[input.asmPathCount] = args[idx]; |
| 338 | set input.asmPathCount += 1; |
| 339 | } else { |
| 340 | if input.radPathCount >= MAX_LOADED_MODULES { |
| 341 | throw error(&["too many modules specified for package"]); |
| 342 | } |
| 343 | set input.radPaths[input.radPathCount] = args[idx]; |
| 344 | set input.radPathCount += 1; |
| 345 | } |
| 346 | } else if mem::eq(arg, "-start") { |
| 347 | try nextArg(args, &mut idx, &["`-start` requires an assembly path"]); |
| 348 | let pkgIdx = currentPkgIdx else { |
| 349 | throw error(&["`-start` must follow a `-pkg` argument"]); |
| 350 | }; |
| 351 | let input = &mut inputs[pkgIdx]; |
| 352 | if input.startupPath <> nil { |
| 353 | throw error(&["package", input.name, "has more than one startup file"]); |
| 354 | } |
| 355 | if not hasExtension(args[idx], ASM_SOURCE_EXT) { |
| 356 | throw error(&["`-start` requires a `.ras` assembly file"]); |
| 357 | } |
| 358 | set input.startupPath = args[idx]; |
| 359 | } else if mem::eq(arg, "-entry") { |
| 360 | try nextArg(args, &mut idx, &["`-entry` requires a package name"]); |
| 361 | set entryPkgName = args[idx]; |
| 362 | } else if mem::eq(arg, "-test") { |
| 363 | set buildTest = true; |
| 364 | } else if mem::eq(arg, "-debug") { |
| 365 | set debugEnabled = true; |
| 366 | } else if mem::eq(arg, "-o") { |
| 367 | try nextArg(args, &mut idx, &["`-o` requires an output path"]); |
| 368 | set outputPath = args[idx]; |
| 369 | } else if mem::eq(arg, "-dump") { |
| 370 | try nextArg(args, &mut idx, &["`-dump` requires a mode (eg. ast)"]); |
| 371 | let mode = args[idx]; |
| 372 | if mem::eq(mode, "ast") { |
| 373 | set dump = Dump::Ast; |
| 374 | } else if mem::eq(mode, "graph") { |
| 375 | set dump = Dump::Graph; |
| 376 | } else if mem::eq(mode, "il") { |
| 377 | set dump = Dump::Il; |
| 378 | } else if mem::eq(mode, "asm") { |
| 379 | set dump = Dump::Asm; |
| 380 | } else { |
| 381 | throw error(&["unknown dump mode", mode, "(expected: ast, graph, il, asm)"]); |
| 382 | } |
| 383 | } else { |
| 384 | throw error(&["unknown argument", arg]); |
| 385 | } |
| 386 | set idx += 1; |
| 387 | } |
| 388 | if pkgCount == 0 { |
| 389 | throw error(&["no package specified"]); |
| 390 | } |
| 391 | for i in 0..pkgCount { |
| 392 | if inputs[i].radPathCount == 0 { |
| 393 | throw error(&["package", inputs[i].name, "has no Radiance modules specified"]); |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | // Determine entry package index. |
| 398 | let mut entryPkgIdx: ?u32 = nil; |
| 399 | if pkgCount == 1 { |
| 400 | // Single package: it is the entry. |
| 401 | set entryPkgIdx = 0; |
| 402 | } else { |
| 403 | // Multiple packages: need -entry. |
| 404 | let entryName = entryPkgName else { |
| 405 | throw error(&["`-entry` required when multiple packages specified"]); |
| 406 | }; |
| 407 | for i in 0..pkgCount { |
| 408 | if mem::eq(inputs[i].name, entryName) { |
| 409 | set entryPkgIdx = i; |
| 410 | break; |
| 411 | } |
| 412 | } |
| 413 | if entryPkgIdx == nil { |
| 414 | throw error(&["fatal:", "entry package", entryName, "not found"]); |
| 415 | } |
| 416 | } |
| 417 | let entryIdx = entryPkgIdx else { |
| 418 | panic "processCommand: no entry package"; |
| 419 | }; |
| 420 | for i in 0..pkgCount { |
| 421 | if i <> entryIdx and inputs[i].startupPath <> nil { |
| 422 | throw error(&["`-start` is only supported on the entry package"]); |
| 423 | } |
| 424 | } |
| 425 | let graph = module::moduleGraph(&mut MODULE_ENTRIES[..], &mut STRING_POOL, arena); |
| 426 | let mut ctx = CompileContext { |
| 427 | packages: undefined, |
| 428 | inputs, |
| 429 | packageCount: pkgCount, |
| 430 | entryPkgIdx, |
| 431 | graph, |
| 432 | config: resolver::Config { buildTest }, |
| 433 | dump, |
| 434 | outputPath, |
| 435 | debug: debugEnabled, |
| 436 | }; |
| 437 | // Initialize and parse all packages. |
| 438 | let mut sourceArena = alloc::new(&mut MODULE_SOURCES[..]); |
| 439 | for i in 0..pkgCount { |
| 440 | package::init(&mut ctx.packages[i], i as u16, ctx.inputs[i].name, &mut STRING_POOL); |
| 441 | |
| 442 | for j in 0..ctx.inputs[i].radPathCount { |
| 443 | let path = ctx.inputs[i].radPaths[j]; |
| 444 | try processModule(&mut ctx.packages[i], &mut ctx.graph, path, arena, &mut sourceArena); |
| 445 | } |
| 446 | } |
| 447 | return ctx; |
| 448 | } |
| 449 | |
| 450 | /// Get the entry package from the context. |
| 451 | fn getEntryPackage(ctx: *CompileContext) -> *package::Package throws (Error) { |
| 452 | let entryIdx = ctx.entryPkgIdx else { |
| 453 | throw error(&["no entry package specified"]); |
| 454 | }; |
| 455 | return &ctx.packages[entryIdx]; |
| 456 | } |
| 457 | |
| 458 | /// Return the startup assembly path for the entry package, if one was supplied. |
| 459 | fn getEntryStartupPath(ctx: *CompileContext) -> ?*[u8] { |
| 460 | let entryIdx = ctx.entryPkgIdx else { |
| 461 | panic "getEntryStartupPath: no entry package"; |
| 462 | }; |
| 463 | return ctx.inputs[entryIdx].startupPath; |
| 464 | } |
| 465 | |
| 466 | /// Get root module info from a package. |
| 467 | fn getRootModule(pkg: *package::Package, graph: *module::ModuleGraph) -> RootModule throws (Error) { |
| 468 | let rootId = pkg.rootModuleId else { |
| 469 | throw error(&["no root module found"]); |
| 470 | }; |
| 471 | let rootEntry = module::get(graph, rootId) else { |
| 472 | throw error(&["root module entry not found"]); |
| 473 | }; |
| 474 | let rootAst = rootEntry.ast else { |
| 475 | throw error(&["root module has no AST"]); |
| 476 | }; |
| 477 | return RootModule { entry: rootEntry, ast: rootAst }; |
| 478 | } |
| 479 | |
| 480 | /// Dump the module graph. |
| 481 | fn dumpGraph(ctx: *CompileContext) { |
| 482 | let mut arena = alloc::new(&mut MAIN_ARENA[..]); |
| 483 | module::printer::printGraph(&ctx.graph, &mut arena); |
| 484 | } |
| 485 | |
| 486 | /// Dump the parsed AST. |
| 487 | fn dumpAst(ctx: *CompileContext) throws (Error) { |
| 488 | let pkg = try getEntryPackage(ctx); |
| 489 | let root = try getRootModule(pkg, &ctx.graph); |
| 490 | let mut arena = alloc::new(&mut MAIN_ARENA[..]); |
| 491 | |
| 492 | ast::printer::printTree(root.ast, &mut arena); |
| 493 | } |
| 494 | |
| 495 | /// Lower all packages into a single IL program. |
| 496 | /// Dependencies are lowered first, then the entry package. |
| 497 | fn lowerAllPackages( |
| 498 | ctx: *mut CompileContext, |
| 499 | res: *mut resolver::Resolver |
| 500 | ) -> il::Program throws (Error) { |
| 501 | let entryIdx = ctx.entryPkgIdx else { |
| 502 | panic "lowerAllPackages: no entry package"; |
| 503 | }; |
| 504 | let entryPkg = &ctx.packages[entryIdx]; |
| 505 | |
| 506 | // Create the lowerer accumulator using entry package's name. |
| 507 | let options = lower::LowerOptions { debug: ctx.debug, buildTest: ctx.config.buildTest }; |
| 508 | let mut low = lower::lowerer( |
| 509 | res, &ctx.graph, entryPkg.name, &mut res.arena, &mut res.arena, options |
| 510 | ); |
| 511 | try lowerAllPackagesInto(ctx, res, &mut low); |
| 512 | |
| 513 | // Finalize and return the unified program. |
| 514 | return lower::finalize(&low); |
| 515 | } |
| 516 | |
| 517 | /// Lower all packages into an existing lowerer. |
| 518 | fn lowerAllPackagesInto( |
| 519 | ctx: *mut CompileContext, |
| 520 | res: *mut resolver::Resolver, |
| 521 | low: *mut lower::Lowerer |
| 522 | ) throws (Error) { |
| 523 | let entryIdx = ctx.entryPkgIdx else { |
| 524 | panic "lowerAllPackagesInto: no entry package"; |
| 525 | }; |
| 526 | // Lower all packages except entry. |
| 527 | for i in 0..ctx.packageCount { |
| 528 | if i <> entryIdx { |
| 529 | try lowerPackage(ctx, res, low, &mut ctx.packages[i], false); |
| 530 | } |
| 531 | } |
| 532 | // Lower entry package. |
| 533 | try lowerPackage(ctx, res, low, &mut ctx.packages[entryIdx], true); |
| 534 | } |
| 535 | |
| 536 | /// Lower all modules in a package into the lowerer accumulator. |
| 537 | fn lowerPackage( |
| 538 | ctx: *CompileContext, |
| 539 | res: *mut resolver::Resolver, |
| 540 | low: *mut lower::Lowerer, |
| 541 | pkg: *mut package::Package, |
| 542 | isEntry: bool |
| 543 | ) throws (Error) { |
| 544 | let rootId = pkg.rootModuleId else { |
| 545 | throw error(&["no root module found"]); |
| 546 | }; |
| 547 | // Set lowerer's package context for qualified name generation. |
| 548 | // TODO: We shouldn't have to call this manually. |
| 549 | lower::setPackage(low, &ctx.graph, pkg.name); |
| 550 | |
| 551 | try lowerModuleTreeInto(ctx, low, &ctx.graph, rootId, isEntry, pkg); |
| 552 | } |
| 553 | |
| 554 | /// Recursively lower a module and all its children into the accumulator. |
| 555 | fn lowerModuleTreeInto( |
| 556 | ctx: *CompileContext, |
| 557 | low: *mut lower::Lowerer, |
| 558 | graph: *module::ModuleGraph, |
| 559 | modId: u16, |
| 560 | isRoot: bool, |
| 561 | pkg: *package::Package |
| 562 | ) throws (Error) { |
| 563 | let entry = module::get(graph, modId) else { |
| 564 | throw error(&["module entry not found"]); |
| 565 | }; |
| 566 | let modAst = entry.ast else { |
| 567 | throw error(&["module has no AST"]); |
| 568 | }; |
| 569 | pkgLog(pkg, &["lowering", "(", entry.filePath, ")", ".."]); |
| 570 | |
| 571 | try lower::lowerModule(low, modId, modAst, isRoot) catch err { |
| 572 | io::printError("radiance: "); |
| 573 | io::printError("internal error during lowering: "); |
| 574 | lower::printError(err); |
| 575 | io::printError("\n"); |
| 576 | |
| 577 | throw Error::Other; |
| 578 | }; |
| 579 | // Recurse into children. |
| 580 | for i in 0..entry.childrenLen { |
| 581 | let childId = module::childAt(entry, i); |
| 582 | try lowerModuleTreeInto(ctx, low, graph, childId, false, pkg); |
| 583 | } |
| 584 | } |
| 585 | |
| 586 | /// Build a scope access chain: a::b::c from a slice of identifiers. |
| 587 | fn synthScopeAccess(arena: *mut ast::NodeArena, path: *[*[u8]]) -> *ast::Node { |
| 588 | let mut result = ast::synthNode( |
| 589 | arena, |
| 590 | ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, path[0])) |
| 591 | ); |
| 592 | for i in 1..path.len { |
| 593 | let child = ast::synthNode( |
| 594 | arena, |
| 595 | ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, path[i])) |
| 596 | ); |
| 597 | set result = ast::synthNode(arena, ast::NodeValue::ScopeAccess(ast::Access { |
| 598 | parent: result, child, |
| 599 | })); |
| 600 | } |
| 601 | return result; |
| 602 | } |
| 603 | |
| 604 | /// Check if a function declaration has the `@test` attribute and return its name if so. |
| 605 | fn getTestFnName(decl: *ast::FnDecl) -> ?*[u8] { |
| 606 | let attrs = decl.attrs else { return nil; }; |
| 607 | if not ast::attributesContains(&attrs, ast::Attribute::Test) { |
| 608 | return nil; |
| 609 | } |
| 610 | let case ast::NodeValue::Ident(name) = decl.name.value |
| 611 | else return nil; |
| 612 | |
| 613 | return name; |
| 614 | } |
| 615 | |
| 616 | /// Scan a single module's AST for `@test` functions and append them to `tests`. |
| 617 | fn collectModuleTests( |
| 618 | entry: *module::ModuleEntry, |
| 619 | tests: *mut [TestDesc], |
| 620 | testCount: *mut u32 |
| 621 | ) { |
| 622 | let modAst = entry.ast else { |
| 623 | return; |
| 624 | }; |
| 625 | let case ast::NodeValue::Block(block) = modAst.value else { |
| 626 | return; |
| 627 | }; |
| 628 | let modPath = module::moduleQualifiedPath(entry); |
| 629 | |
| 630 | for stmt in block.statements { |
| 631 | if let case ast::NodeValue::FnDecl(decl) = stmt.value { |
| 632 | if let fnName = getTestFnName(&decl) { |
| 633 | if *testCount < tests.len { |
| 634 | set tests[*testCount] = TestDesc { modPath, fnName }; |
| 635 | set *testCount += 1; |
| 636 | } else { |
| 637 | panic "collectModuleTests: too many tests"; |
| 638 | } |
| 639 | } |
| 640 | } |
| 641 | } |
| 642 | } |
| 643 | |
| 644 | /// Synthesize a `testing::test("mod", "name", mod::fn)` call for one test. |
| 645 | fn synthTestCall(arena: *mut ast::NodeArena, desc: *TestDesc) -> *ast::Node { |
| 646 | let callee = synthScopeAccess(arena, &["testing", "test"]); |
| 647 | let modStr = il::formatQualifiedName( |
| 648 | &mut arena.arena, |
| 649 | &desc.modPath[..desc.modPath.len - 1], |
| 650 | desc.modPath[desc.modPath.len - 1] |
| 651 | ); |
| 652 | let modArg = ast::synthNode(arena, ast::NodeValue::String(modStr)); |
| 653 | let nameArg = ast::synthNode(arena, ast::NodeValue::String(desc.fnName)); |
| 654 | |
| 655 | // Intra-package path: skip the package name prefix. |
| 656 | let mut funcPath: [*[u8]; 16] = undefined; |
| 657 | for j in 1..desc.modPath.len { |
| 658 | set funcPath[j - 1] = desc.modPath[j]; |
| 659 | } |
| 660 | set funcPath[desc.modPath.len - 1] = desc.fnName; |
| 661 | let funcArg = synthScopeAccess(arena, &funcPath[..desc.modPath.len]); |
| 662 | |
| 663 | let a = alloc::arenaAllocator(&mut arena.arena); |
| 664 | let args = ast::nodeSlice(arena, 3) |
| 665 | .append(modArg, a) |
| 666 | .append(nameArg, a) |
| 667 | .append(funcArg, a); |
| 668 | |
| 669 | return ast::synthNode(arena, ast::NodeValue::Call(ast::Call { callee, args })); |
| 670 | } |
| 671 | |
| 672 | /// Inject a test runner into the entry package's root module. |
| 673 | /// |
| 674 | /// Scans all parsed modules for `@test fn` declarations, then appends |
| 675 | /// a synthetic entry point to the root module's AST block: |
| 676 | /// |
| 677 | /// ``` |
| 678 | /// @default fn #testMain() -> i32 { |
| 679 | /// return testing::runAllTests(&[ |
| 680 | /// testing::test("std::tests", "testFoo", tests::testFoo), |
| 681 | /// ... |
| 682 | /// ]); |
| 683 | /// } |
| 684 | /// ``` |
| 685 | /// |
| 686 | /// Uses `#`-prefixed names to avoid conflicts with user code. |
| 687 | fn generateTestRunner( |
| 688 | ctx: *mut CompileContext, |
| 689 | arena: *mut ast::NodeArena |
| 690 | ) throws (Error) { |
| 691 | let entryPkg = try getEntryPackage(ctx); |
| 692 | let root = try getRootModule(entryPkg, &ctx.graph); |
| 693 | |
| 694 | // Collect all test functions across all modules. |
| 695 | let mut tests: [TestDesc; MAX_TESTS] = undefined; |
| 696 | let mut testCount: u32 = 0; |
| 697 | |
| 698 | for modIdx in 0..ctx.graph.entriesLen { |
| 699 | if let entry = module::get(&ctx.graph, modIdx as u16) { |
| 700 | collectModuleTests(entry, &mut tests[..], &mut testCount); |
| 701 | } |
| 702 | } |
| 703 | if testCount == 0 { |
| 704 | throw error(&["fatal:", "no test functions found"]); |
| 705 | } |
| 706 | let mut countBuf: [u8; 10] = undefined; |
| 707 | let countStr = fmt::formatU32(testCount, &mut countBuf[..]); |
| 708 | pkgLog(entryPkg, &["found", countStr, "test(s)"]); |
| 709 | |
| 710 | // Synthesize the `@default` function and append to the root module. |
| 711 | let fnDecl = synthTestMainFn(arena, &tests[..testCount]); |
| 712 | |
| 713 | injectIntoBlock(root.ast, arena, fnDecl); |
| 714 | } |
| 715 | |
| 716 | /// Synthesize the test entry point. |
| 717 | fn synthTestMainFn(arena: *mut ast::NodeArena, tests: *[TestDesc]) -> *ast::Node { |
| 718 | // Build array literal: `[testing::test(...), ...]`. |
| 719 | let a = alloc::arenaAllocator(&mut arena.arena); |
| 720 | let mut elements = ast::nodeSlice(arena, tests.len as u32); |
| 721 | for i in 0..tests.len { |
| 722 | elements.append(synthTestCall(arena, &tests[i]), a); |
| 723 | } |
| 724 | let arrayLit = ast::synthNode(arena, ast::NodeValue::ArrayLit(elements)); |
| 725 | |
| 726 | // Build: `&[...]`. |
| 727 | let testsRef = ast::synthNode(arena, ast::NodeValue::AddressOf(ast::AddressOf { |
| 728 | target: arrayLit, mutable: false, |
| 729 | })); |
| 730 | |
| 731 | // Build: `testing::runAllTests(&[...])`. |
| 732 | let runFn = synthScopeAccess(arena, &["testing", "runAllTests"]); |
| 733 | let callArgs = ast::nodeSlice(arena, 1).append(testsRef, a); |
| 734 | let callExpr = ast::synthNode(arena, ast::NodeValue::Call(ast::Call { |
| 735 | callee: runFn, args: callArgs, |
| 736 | })); |
| 737 | |
| 738 | // Build: `return testing::runAllTests(&[...]);` |
| 739 | let retStmt = ast::synthNode(arena, ast::NodeValue::Return { value: callExpr }); |
| 740 | let bodyStmts = ast::nodeSlice(arena, 1).append(retStmt, a); |
| 741 | let fnBody = ast::synthNode(arena, ast::NodeValue::Block(ast::Block { statements: bodyStmts })); |
| 742 | |
| 743 | // Build: `fn #testMain() -> i32` |
| 744 | let fnName = ast::synthNode(arena, ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, "#testMain"))); |
| 745 | let returnType = ast::synthNode(arena, ast::NodeValue::TypeSig(ast::TypeSig::Integer { |
| 746 | width: 4, sign: ast::Signedness::Signed, |
| 747 | })); |
| 748 | let fnSig = ast::FnSig { |
| 749 | params: ast::nodeSlice(arena, 0), |
| 750 | returnType, |
| 751 | throwList: ast::nodeSlice(arena, 0), |
| 752 | }; |
| 753 | |
| 754 | // `@default` attribute. |
| 755 | let attrNode = ast::synthNode(arena, ast::NodeValue::Attribute(ast::Attribute::Default)); |
| 756 | let attrList = ast::nodeSlice(arena, 1).append(attrNode, a); |
| 757 | let fnAttrs = ast::Attributes { list: attrList }; |
| 758 | |
| 759 | return ast::synthNode(arena, ast::NodeValue::FnDecl(ast::FnDecl { |
| 760 | name: fnName, params: ast::nodeSlice(arena, 0), sig: fnSig, body: fnBody, attrs: fnAttrs, |
| 761 | })); |
| 762 | } |
| 763 | |
| 764 | /// Append a declaration to a block node's statement list. |
| 765 | fn injectIntoBlock( |
| 766 | blockNode: *mut ast::Node, |
| 767 | arena: *mut ast::NodeArena, |
| 768 | decl: *ast::Node |
| 769 | ) { |
| 770 | let case ast::NodeValue::Block(block) = blockNode.value else { |
| 771 | panic "injectIntoBlock: expected Block node"; |
| 772 | }; |
| 773 | let stmts = block.statements.append(decl, alloc::arenaAllocator(&mut arena.arena)); |
| 774 | set blockNode.value = ast::NodeValue::Block(ast::Block { statements: stmts }); |
| 775 | } |
| 776 | |
| 777 | /// Write a self-contained RV64 image containing text and data sections. |
| 778 | fn writeImage( |
| 779 | code: *[u32], |
| 780 | roData: *[u8], |
| 781 | rwData: *[u8], |
| 782 | path: *[u8] |
| 783 | ) -> bool { |
| 784 | let mut header = rv64::imageHeader(code.len * rv64::INSTR_SIZE as u32, roData.len, rwData.len); |
| 785 | let headerWords = &header[..]; |
| 786 | let headerBytes = @sliceOf(headerWords.ptr as *u8, headerWords.len * rv64::WORD_SIZE as u32); |
| 787 | let codeBytes = @sliceOf(code.ptr as *u8, code.len * rv64::INSTR_SIZE as u32); |
| 788 | |
| 789 | return unix::writeFileParts(path, &[headerBytes, codeBytes, roData, rwData]); |
| 790 | } |
| 791 | |
| 792 | /// Write a data section to a file at `basePath` + `ext`. |
| 793 | /// Empty data truncates any stale sidecar left by an earlier build. |
| 794 | fn writeDataWithExt( |
| 795 | data: *[u8], |
| 796 | basePath: *[u8], |
| 797 | ext: *[u8] |
| 798 | ) throws (Error) { |
| 799 | let mut path: [u8; MAX_PATH_LEN] = undefined; |
| 800 | let mut pos: u32 = 0; |
| 801 | |
| 802 | set pos += try! mem::copy(&mut path[pos..], basePath); |
| 803 | set pos += try! mem::copy(&mut path[pos..], ext); |
| 804 | set path[pos] = 0; // Null-terminate for syscall. |
| 805 | |
| 806 | if not unix::writeFile(&path[..pos], data) { |
| 807 | throw error(&["fatal:", "failed to write data file"]); |
| 808 | } |
| 809 | } |
| 810 | |
| 811 | /// Serialize debug entries and write the `.debug` file. |
| 812 | /// Resolves module IDs to file paths via the module graph. |
| 813 | /// Format per entry is `{pc: u32, offset: u32, filePath: [u8], NULL}`. |
| 814 | fn writeDebugInfo( |
| 815 | entries: *[types::DebugEntry], |
| 816 | graph: *module::ModuleGraph, |
| 817 | basePath: *[u8], |
| 818 | arena: *mut alloc::Arena |
| 819 | ) throws (Error) { |
| 820 | if entries.len == 0 { |
| 821 | return; |
| 822 | } |
| 823 | // Use remaining arena space as serialization buffer. |
| 824 | let buf = alloc::remainingBuf(arena); |
| 825 | let mut pos: u32 = 0; |
| 826 | |
| 827 | for i in 0..entries.len { |
| 828 | let entry = &entries[i]; |
| 829 | let modEntry = module::get(graph, entry.moduleId) else { |
| 830 | panic "writeDebugInfo: module not found for debug entry"; |
| 831 | }; |
| 832 | set pos += try! mem::copy(&mut buf[pos..], @sliceOf(&entry.pc as *u8, 4)); |
| 833 | set pos += try! mem::copy(&mut buf[pos..], @sliceOf(&entry.offset as *u8, 4)); |
| 834 | set pos += try! mem::copy(&mut buf[pos..], modEntry.filePath); |
| 835 | |
| 836 | set buf[pos] = 0; |
| 837 | set pos += 1; |
| 838 | } |
| 839 | try writeDataWithExt(&buf[..pos], basePath, DEBUG_EXT); |
| 840 | } |
| 841 | |
| 842 | /// Run the resolver on the parsed modules. |
| 843 | fn runResolver(ctx: *mut CompileContext, nodeCount: u32) -> resolver::Resolver throws (Error) { |
| 844 | let mut mainArena = alloc::new(&mut MAIN_ARENA[..]); |
| 845 | let entryPkg = try getEntryPackage(ctx); |
| 846 | |
| 847 | pkgLog(entryPkg, &["resolving", ".."]); |
| 848 | |
| 849 | let nodeDataSize = nodeCount * @sizeOf(resolver::NodeData); |
| 850 | let nodeDataPtr = try! alloc::alloc(&mut mainArena, nodeDataSize, @alignOf(resolver::NodeData)); |
| 851 | let nodeData = @sliceOf(nodeDataPtr as *mut resolver::NodeData, nodeCount); |
| 852 | let storage = resolver::ResolverStorage { |
| 853 | arena: mainArena, |
| 854 | nodeData, |
| 855 | pkgScope: &mut RESOLVER_PKG_SCOPE, |
| 856 | errors: &mut RESOLVER_ERRORS[..], |
| 857 | }; |
| 858 | let mut res = resolver::resolver(storage, ctx.config); |
| 859 | |
| 860 | // Build the semantic package list consumed by the resolver. |
| 861 | let mut resolverPkgs: [resolver::Pkg; MAX_PACKAGES] = undefined; |
| 862 | let mut resolverPackageCount: u32 = 0; |
| 863 | for i in 0..ctx.packageCount { |
| 864 | let pkg = &ctx.packages[i]; |
| 865 | let root = try getRootModule(pkg, &ctx.graph); |
| 866 | |
| 867 | set resolverPkgs[resolverPackageCount] = resolver::Pkg { |
| 868 | rootEntry: root.entry, |
| 869 | rootAst: root.ast, |
| 870 | }; |
| 871 | set resolverPackageCount += 1; |
| 872 | } |
| 873 | |
| 874 | // Resolve all packages. |
| 875 | // TODO: Fix this error printing dance. |
| 876 | let diags = try resolver::resolve(&mut res, &ctx.graph, &resolverPkgs[..resolverPackageCount]) catch { |
| 877 | let diags = resolver::Diagnostics { errors: res.errors }; |
| 878 | resolver::printer::printDiagnostics(&diags, &res); |
| 879 | throw Error::Other; |
| 880 | }; |
| 881 | if not resolver::success(&diags) { |
| 882 | resolver::printer::printDiagnostics(&diags, &res); |
| 883 | let mut countBuf: [u8; 10] = undefined; |
| 884 | let countStr = fmt::formatU32(diags.errors.len, &mut countBuf[..]); |
| 885 | throw error(&["failed:", countStr, "errors"]); |
| 886 | } |
| 887 | return res; |
| 888 | } |
| 889 | |
| 890 | /// Emit one lowered function to machine code and reclaim its IL arena. |
| 891 | fn generateLoweredFn(ctxPtr: *mut opaque, func: *il::Fn, role: lower::FnRole) { |
| 892 | let ctx = ctxPtr as *mut CodegenSinkContext; |
| 893 | |
| 894 | match role { |
| 895 | case lower::FnRole::Default => { |
| 896 | rv64::recordFunctionAlias(ctx.generator, DEFAULT_ENTRY_SYMBOL); |
| 897 | match ctx.generator.entryPatch { |
| 898 | case rv64::EntryPatch::Reserved(_) => { |
| 899 | set ctx.generator.entryPatch = rv64::EntryPatch::Reserved(func.name); |
| 900 | } |
| 901 | // No entry jump was reserved: startup assembly calls the |
| 902 | // default function through `DEFAULT_ENTRY_SYMBOL` instead. |
| 903 | case rv64::EntryPatch::None => {} |
| 904 | } |
| 905 | } |
| 906 | else => {} |
| 907 | } |
| 908 | rv64::generateFunction(ctx.generator, func, ctx.fnArena); |
| 909 | alloc::reset(ctx.fnArena); |
| 910 | } |
| 911 | |
| 912 | /// Assemble one `.ras` input and merge it into the active code generator. |
| 913 | /// |
| 914 | /// Text symbols are appended to `generator`. Data emitted by the assembler is |
| 915 | /// copied into `ASM_RO_DATA_BUF` at `*asmDataLen`, and `*asmDataLen` is advanced |
| 916 | /// so the next assembly module receives the correct rodata base address. |
| 917 | fn assembleAsmModule( |
| 918 | generator: *mut rv64::Generator, |
| 919 | pkg: *package::Package, |
| 920 | path: *[u8], |
| 921 | asmDataLen: *mut u32, |
| 922 | arena: *mut alloc::Arena |
| 923 | ) throws (Error) { |
| 924 | pkgLog(pkg, &["asm:", "parsing", "(", path, ")", ".."]); |
| 925 | |
| 926 | let source = unix::readFile(path, &mut ASM_SOURCE_BUF[..]) else { |
| 927 | throw error(&["error reading assembly file"]); |
| 928 | }; |
| 929 | if source.len == ASM_SOURCE_BUF.len { |
| 930 | throw error(&["fatal:", "assembly source too large:", path]); |
| 931 | } |
| 932 | let program = try asm::assemble( |
| 933 | asm::scanner::SourceKind::File { path }, |
| 934 | source, |
| 935 | &mut ASM_TEXT_BUF[..], |
| 936 | &mut ASM_DATA_BUF[..], |
| 937 | arena, |
| 938 | &mut STRING_POOL, |
| 939 | rv64::RO_DATA_BASE + *asmDataLen |
| 940 | ) catch { |
| 941 | throw error(&["assembly failed:", path]); |
| 942 | }; |
| 943 | if *asmDataLen + program.data.len > ASM_RO_DATA_BUF.len { |
| 944 | throw error(&["fatal:", "assembly rodata too large"]); |
| 945 | } |
| 946 | try! mem::copy(&mut ASM_RO_DATA_BUF[*asmDataLen..], program.data); |
| 947 | set *asmDataLen += program.data.len; |
| 948 | |
| 949 | rv64::addAssembly(generator, program); |
| 950 | } |
| 951 | |
| 952 | /// Assemble all inputs collected in the package inputs. |
| 953 | fn assembleAsmInputs( |
| 954 | ctx: *CompileContext, |
| 955 | generator: *mut rv64::Generator, |
| 956 | asmDataLen: *mut u32, |
| 957 | arena: *mut alloc::Arena |
| 958 | ) -> *[u8] throws (Error) { |
| 959 | for i in 0..ctx.packageCount { |
| 960 | let input = &ctx.inputs[i]; |
| 961 | for j in 0..input.asmPathCount { |
| 962 | try assembleAsmModule( |
| 963 | generator, |
| 964 | &ctx.packages[i], |
| 965 | input.asmPaths[j], |
| 966 | asmDataLen, |
| 967 | arena |
| 968 | ); |
| 969 | } |
| 970 | } |
| 971 | return &ASM_RO_DATA_BUF[..*asmDataLen]; |
| 972 | } |
| 973 | |
| 974 | /// Lower all packages while streaming each lowered function into RV64 codegen. |
| 975 | fn lowerAndGenerateAllPackages( |
| 976 | ctx: *mut CompileContext, |
| 977 | res: *mut resolver::Resolver, |
| 978 | fnArena: *mut alloc::Arena, |
| 979 | codegenOptions: CodegenOptions |
| 980 | ) -> rv64::Program throws (Error) { |
| 981 | let entryIdx = ctx.entryPkgIdx else { |
| 982 | panic "lowerAndGenerateAllPackages: no entry package"; |
| 983 | }; |
| 984 | let entryPkg = &ctx.packages[entryIdx]; |
| 985 | let startupPath = getEntryStartupPath(ctx); |
| 986 | let options = lower::LowerOptions { debug: ctx.debug, buildTest: ctx.config.buildTest }; |
| 987 | let storage = rv64::Storage { |
| 988 | dataSyms: &mut CODEGEN_DATA_SYMS[..], |
| 989 | dataSymEntries: &mut CODEGEN_DATA_SYM_ENTRIES[..], |
| 990 | }; |
| 991 | let mut entryPatch = rv64::EntryPatch::None; |
| 992 | match codegenOptions.entryMode { |
| 993 | case CodegenEntryMode::DefaultEntry => { |
| 994 | set entryPatch = rv64::EntryPatch::Reserved(nil); |
| 995 | } |
| 996 | else => {} |
| 997 | } |
| 998 | let mut generator = rv64::beginProgram( |
| 999 | rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug }, |
| 1000 | &mut res.arena |
| 1001 | ); |
| 1002 | let mut codegenCtx = CodegenSinkContext { |
| 1003 | generator: &mut generator, |
| 1004 | fnArena, |
| 1005 | }; |
| 1006 | let mut low = lower::lowerer( |
| 1007 | res, &ctx.graph, entryPkg.name, &mut res.arena, fnArena, options |
| 1008 | ); |
| 1009 | set low.output = lower::FnOutput::Stream(lower::FnSink { |
| 1010 | ctx: &mut codegenCtx as *mut opaque, |
| 1011 | emitFn: generateLoweredFn, |
| 1012 | }); |
| 1013 | let mut asmDataLen: u32 = 0; |
| 1014 | if let path = startupPath { |
| 1015 | try assembleAsmModule(&mut generator, entryPkg, path, &mut asmDataLen, &mut res.arena); |
| 1016 | } |
| 1017 | try lowerAllPackagesInto(ctx, res, &mut low); |
| 1018 | let asmData = try assembleAsmInputs(ctx, &mut generator, &mut asmDataLen, &mut res.arena); |
| 1019 | |
| 1020 | match generator.entryPatch { |
| 1021 | case rv64::EntryPatch::Reserved(targetName) => { |
| 1022 | if targetName == nil { |
| 1023 | throw error(&["fatal:", "no default function found"]); |
| 1024 | } |
| 1025 | } |
| 1026 | else => {} |
| 1027 | } |
| 1028 | if let path = codegenOptions.logPath { |
| 1029 | pkgLog(entryPkg, &["generating code", "(", path, ")", ".."]); |
| 1030 | } |
| 1031 | return rv64::finishProgram(&mut generator, &low.data[..], storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]); |
| 1032 | } |
| 1033 | |
| 1034 | /// Lower, optionally dump, and optionally generate binary output. |
| 1035 | fn compile( |
| 1036 | ctx: *mut CompileContext, |
| 1037 | res: *mut resolver::Resolver, |
| 1038 | fnArena: *mut alloc::Arena |
| 1039 | ) throws (Error) { |
| 1040 | let entryPkg = try getEntryPackage(ctx); |
| 1041 | let mut out = sexpr::Output::Stdout; |
| 1042 | |
| 1043 | if ctx.dump == Dump::Il { |
| 1044 | // Lower all packages into a single unified IL program for dumping. |
| 1045 | let program = try lowerAllPackages(ctx, res); |
| 1046 | il::printer::printProgram(&mut out, &mut res.arena, &program); |
| 1047 | io::print("\n"); |
| 1048 | return; |
| 1049 | } |
| 1050 | if ctx.dump == Dump::Asm { |
| 1051 | let result = try lowerAndGenerateAllPackages(ctx, res, fnArena, CodegenOptions { |
| 1052 | logPath: nil, |
| 1053 | debug: false, |
| 1054 | entryMode: CodegenEntryMode::None, |
| 1055 | }); |
| 1056 | printer::printCodeTo(&mut out, entryPkg.name, result.code, result.funcs, &mut res.arena); |
| 1057 | io::print("\n"); |
| 1058 | |
| 1059 | return; |
| 1060 | } |
| 1061 | // Generate binary output if path specified. |
| 1062 | let outPath = ctx.outputPath else { |
| 1063 | try lowerAllPackages(ctx, res); |
| 1064 | return; |
| 1065 | }; |
| 1066 | let startupPath = getEntryStartupPath(ctx); |
| 1067 | let result = try lowerAndGenerateAllPackages(ctx, res, fnArena, CodegenOptions { |
| 1068 | logPath: outPath, |
| 1069 | debug: ctx.debug, |
| 1070 | entryMode: CodegenEntryMode::None |
| 1071 | if startupPath <> nil |
| 1072 | else CodegenEntryMode::DefaultEntry, |
| 1073 | }); |
| 1074 | |
| 1075 | if not writeImage( |
| 1076 | result.code, |
| 1077 | &RO_DATA_BUF[..result.roDataSize], |
| 1078 | &RW_DATA_BUF[..result.rwDataSize], |
| 1079 | outPath |
| 1080 | ) { |
| 1081 | throw error(&["fatal:", "failed to write output file"]); |
| 1082 | } |
| 1083 | |
| 1084 | // Write debug info file if enabled. |
| 1085 | if ctx.debug { |
| 1086 | try writeDebugInfo(result.debugEntries, &ctx.graph, outPath, &mut res.arena); |
| 1087 | } |
| 1088 | pkgLog(entryPkg, &["ok", "(", outPath, ")"]); |
| 1089 | } |
| 1090 | |
| 1091 | @default fn main(env: *sys::Env) -> i32 { |
| 1092 | let mut arena = ast::nodeArena(&mut TEMP_ARENA[..]); |
| 1093 | let mut ctx = try processCommand(env.args, &mut arena) catch { |
| 1094 | return 1; |
| 1095 | }; |
| 1096 | match ctx.dump { |
| 1097 | case Dump::Ast => { |
| 1098 | try dumpAst(&ctx) catch { |
| 1099 | return 1; |
| 1100 | }; |
| 1101 | return 0; |
| 1102 | } |
| 1103 | case Dump::Graph => { |
| 1104 | dumpGraph(&ctx); |
| 1105 | return 0; |
| 1106 | } |
| 1107 | else => {} |
| 1108 | } |
| 1109 | // Generate test runner if in test mode. |
| 1110 | if ctx.config.buildTest { |
| 1111 | try generateTestRunner(&mut ctx, &mut arena) catch { |
| 1112 | return 1; |
| 1113 | }; |
| 1114 | } |
| 1115 | // Run resolution phase. |
| 1116 | let mut res = try runResolver(&mut ctx, arena.nextId) catch { |
| 1117 | return 1; |
| 1118 | }; |
| 1119 | let mut fnArena = alloc::new(&mut FN_ARENA[..]); |
| 1120 | |
| 1121 | // Lower, dump, and/or generate output. |
| 1122 | try compile(&mut ctx, &mut res, &mut fnArena) catch { |
| 1123 | return 1; |
| 1124 | }; |
| 1125 | return 0; |
| 1126 | } |