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