compiler/radiance.rad 42.7 KiB 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
}