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