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