compiler/radiance.rad 28.5 KiB raw
1
//! Radiance compiler front-end.
2
use std::mem;
3
use std::fmt;
4
use std::io;
5
use std::lang::alloc;
6
use std::lang::ast;
7
use std::lang::parser;
8
use std::lang::scanner;
9
use std::lang::resolver;
10
use std::lang::module;
11
use std::lang::strings;
12
use std::lang::package;
13
use std::lang::il;
14
use std::lang::lower;
15
use std::arch::rv64;
16
use std::arch::rv64::printer;
17
use std::lang::sexpr;
18
use std::sys;
19
use std::sys::unix;
20
21
/// Maximum number of modules we can load per package.
22
const MAX_LOADED_MODULES: u32 = 64;
23
/// Maximum number of packages we can compile.
24
const MAX_PACKAGES: u32 = 4;
25
/// Total module entries across all packages.
26
const MAX_TOTAL_MODULES: u32 = 192;
27
/// Source code buffer arena (2 MB).
28
const MAX_SOURCES_SIZE: u32 = 2097152;
29
/// Maximum number of test functions we can discover.
30
const MAX_TESTS: u32 = 1024;
31
32
/// Temporary arena size (32 MB) - retains all parsed AST until resolution.
33
/// Used for: AST during parsing, then codegen scratch space.
34
const TEMP_ARENA_SIZE: u32 = 33554432;
35
/// Main arena size (128 MB) - lives throughout compilation.
36
/// Used for: resolver data, types, symbols, lowered IL.
37
const MAIN_ARENA_SIZE: u32 = 134217728;
38
39
/// Temporary storage arena - reusable between phases.
40
static TEMP_ARENA: [u8; TEMP_ARENA_SIZE] = undefined;
41
/// Main storage arena - persists throughout compilation.
42
static MAIN_ARENA: [u8; MAIN_ARENA_SIZE] = undefined;
43
44
/// Module source code.
45
static MODULE_SOURCES: [u8; MAX_SOURCES_SIZE] = undefined;
46
/// Module entries for all packages.
47
static MODULE_ENTRIES: [module::ModuleEntry; MAX_TOTAL_MODULES] = undefined;
48
/// String pool.
49
static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
50
51
/// Package scope.
52
static RESOLVER_PKG_SCOPE: resolver::Scope = undefined;
53
/// Errors emitted by resolver.
54
static RESOLVER_ERRORS: [resolver::Error; resolver::MAX_ERRORS] = undefined;
55
56
/// Code generation storage.
57
static CODEGEN_DATA_SYMS: [rv64::DataSym; rv64::MAX_DATA_SYMS] = undefined;
58
59
/// Debug info file extension.
60
const DEBUG_EXT: *[u8] = ".debug";
61
62
/// Read-only data file extension.
63
const RO_DATA_EXT: *[u8] = ".ro.data";
64
/// Read-write data file extension.
65
const RW_DATA_EXT: *[u8] = ".rw.data";
66
/// Maximum rodata size (1MB).
67
const MAX_RO_DATA_SIZE: u32 = 1048576;
68
/// Maximum rwdata size (1MB).
69
const MAX_RW_DATA_SIZE: u32 = 1048576;
70
/// Maximum path length.
71
const MAX_PATH_LEN: u32 = 256;
72
/// Read-only data buffer.
73
static RO_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = undefined;
74
/// Read-write data buffer.
75
static RW_DATA_BUF: [u8; MAX_RW_DATA_SIZE] = undefined;
76
77
/// Usage string.
78
const USAGE: *[u8] =
79
    "usage: radiance -pkg <name> -mod <input>.. [-pkg <name> -mod <input>..] -entry <pkg> -o <output>\n";
80
81
/// Compiler error.
82
union Error {
83
    Other,
84
}
85
86
/// What to dump during compilation.
87
union Dump {
88
    /// Don't dump anything.
89
    None,
90
    /// Dump the parsed AST before semantic analysis.
91
    Ast,
92
    /// Dump the module graph.
93
    Graph,
94
    /// Dump the IL.
95
    Il,
96
    /// Dump the generated assembly.
97
    Asm,
98
}
99
100
/// A discovered test function.
101
record TestDesc {
102
    /// Full module qualified path segments (eg. ["std", "tests"]).
103
    modPath: *[*[u8]],
104
    /// Test function name (eg. "testFoo").
105
    fnName: *[u8],
106
}
107
108
/// Compilation context.
109
record CompileContext {
110
    /// Array of packages to compile.
111
    packages: [package::Package; MAX_PACKAGES],
112
    /// Number of packages.
113
    packageCount: u32,
114
    /// Index of entry package.
115
    entryPkgIdx: ?u32,
116
    /// Global module graph shared by all packages.
117
    graph: module::ModuleGraph,
118
    /// Resolver configuration.
119
    config: resolver::Config,
120
    /// What to dump during compilation.
121
    dump: Dump,
122
    /// Output path for binary.
123
    outputPath: ?*[u8],
124
    /// Whether to emit debug info (.debug file).
125
    debug: bool,
126
}
127
128
/// Root module info for a package.
129
record RootModule {
130
    entry: *module::ModuleEntry,
131
    ast: *mut ast::Node,
132
}
133
134
/// Print a log line for the given package.
135
fn pkgLog(pkg: *package::Package, msg: *[*[u8]]) {
136
    io::printError("radiance: ");
137
    io::printError(pkg.name);
138
    io::printError(": ");
139
140
    for part, i in msg {
141
        io::printError(part);
142
        if i < msg.len - 1 {
143
            io::printError(" ");
144
        }
145
    }
146
    io::printError("\n");
147
}
148
149
/// Register, load, and parse `path` within `pkg`.
150
fn processModule(
151
    pkg: *mut package::Package,
152
    graph: *mut module::ModuleGraph,
153
    path: *[u8],
154
    nodeArena: *mut ast::NodeArena,
155
    sourceArena: *mut alloc::Arena
156
) throws (Error) {
157
    pkgLog(pkg, &["parsing", "(", path, ")", ".."]);
158
159
    let moduleId = try package::registerModule(pkg, graph, path) catch {
160
        io::printError("radiance: error registering module\n");
161
        throw Error::Other;
162
    };
163
    // Read file into remaining arena space.
164
    let buffer = alloc::remainingBuf(sourceArena);
165
    if buffer.len == 0 {
166
        io::printError("radiance: fatal: source arena exhausted\n");
167
        throw Error::Other;
168
    }
169
    let source = unix::readFile(path, buffer) else {
170
        io::printError("radiance: error reading file\n");
171
        throw Error::Other;
172
    };
173
    if source.len == buffer.len {
174
        io::printError("radiance: fatal: source arena too small, file truncated: ");
175
        io::printError(path);
176
        io::printError("\n");
177
        throw Error::Other;
178
    }
179
    // Commit only what was read.
180
    alloc::commit(sourceArena, source.len);
181
182
    let ast = try parser::parse(scanner::SourceLoc::File(path), source, nodeArena, &mut STRING_POOL) catch {
183
        throw Error::Other;
184
    };
185
    try module::setAst(graph, moduleId, ast) catch {
186
        io::printError("radiance: error setting AST\n");
187
        throw Error::Other;
188
    };
189
    try module::setSource(graph, moduleId, source) catch {
190
        io::printError("radiance: error setting source\n");
191
        throw Error::Other;
192
    };
193
}
194
195
/// Parse CLI arguments and return compilation context.
196
fn processCommand(
197
    args: *[*[u8]],
198
    arena: *mut ast::NodeArena
199
) -> CompileContext throws (Error) {
200
    let mut buildTest = false;
201
    let mut debugEnabled = false;
202
    let mut outputPath: ?*[u8] = nil;
203
    let mut dump = Dump::None;
204
    let mut entryPkgName: ?*[u8] = nil;
205
206
    // Per-package module path tracking.
207
    let mut moduleCounts: [u32; MAX_PACKAGES] = undefined;
208
    let mut modulePaths: [[*[u8]; MAX_LOADED_MODULES]; MAX_PACKAGES] = undefined;
209
    let mut pkgNames: [*[u8]; MAX_PACKAGES] = undefined;
210
    let mut pkgCount: u32 = 0;
211
    let mut currentPkgIdx: ?u32 = nil;
212
213
    for i in 0..MAX_PACKAGES {
214
        moduleCounts[i] = 0;
215
    }
216
    if args.len == 0 {
217
        io::printError(USAGE);
218
        throw Error::Other;
219
    }
220
    let mut idx: u32 = 0;
221
222
    while idx < args.len {
223
        let arg = args[idx];
224
        if mem::eq(arg, "-pkg") {
225
            idx += 1;
226
            if idx >= args.len {
227
                io::printError("radiance: `-pkg` requires a package name\n");
228
                throw Error::Other;
229
            }
230
            if pkgCount >= MAX_PACKAGES {
231
                io::printError("radiance: too many packages specified\n");
232
                throw Error::Other;
233
            }
234
            pkgNames[pkgCount] = args[idx];
235
            currentPkgIdx = pkgCount;
236
            pkgCount += 1;
237
        } else if mem::eq(arg, "-mod") {
238
            idx += 1;
239
            if idx >= args.len {
240
                io::printError("radiance: `-mod` requires a module path\n");
241
                throw Error::Other;
242
            }
243
            let pkgIdx = currentPkgIdx else {
244
                io::printError("radiance: `-mod` must follow a `-pkg` argument\n");
245
                throw Error::Other;
246
            };
247
            if moduleCounts[pkgIdx] >= MAX_LOADED_MODULES {
248
                io::printError("radiance: too many modules specified for package\n");
249
                throw Error::Other;
250
            }
251
            modulePaths[pkgIdx][moduleCounts[pkgIdx]] = args[idx];
252
            moduleCounts[pkgIdx] += 1;
253
        } else if mem::eq(arg, "-entry") {
254
            idx += 1;
255
            if idx >= args.len {
256
                io::printError("radiance: `-entry` requires a package name\n");
257
                throw Error::Other;
258
            }
259
            entryPkgName = args[idx];
260
        } else if mem::eq(arg, "-test") {
261
            buildTest = true;
262
        } else if mem::eq(arg, "-debug") {
263
            debugEnabled = true;
264
        } else if mem::eq(arg, "-o") {
265
            idx += 1;
266
            if idx >= args.len {
267
                io::printError("radiance: `-o` requires an output path\n");
268
                throw Error::Other;
269
            }
270
            outputPath = args[idx];
271
        } else if mem::eq(arg, "-dump") {
272
            idx += 1;
273
            if idx >= args.len {
274
                io::printError("radiance: `-dump` requires a mode (eg. ast)\n");
275
                throw Error::Other;
276
            }
277
            let mode = args[idx];
278
            if mem::eq(mode, "ast") {
279
                dump = Dump::Ast;
280
            } else if mem::eq(mode, "graph") {
281
                dump = Dump::Graph;
282
            } else if mem::eq(mode, "il") {
283
                dump = Dump::Il;
284
            } else if mem::eq(mode, "asm") {
285
                dump = Dump::Asm;
286
            } else {
287
                io::printError("radiance: unknown dump mode `");
288
                io::printError(mode);
289
                io::printError("` (expected: ast, graph, il, asm)\n");
290
                throw Error::Other;
291
            }
292
        } else {
293
            io::printError("radiance: unknown argument `");
294
            io::printError(arg);
295
            io::printError("`\n");
296
            throw Error::Other;
297
        }
298
        idx += 1;
299
    }
300
    if pkgCount == 0 {
301
        io::printError("radiance: no package specified\n");
302
        throw Error::Other;
303
    }
304
305
    // Determine entry package index.
306
    let mut entryPkgIdx: ?u32 = nil;
307
    if pkgCount == 1 {
308
        // Single package: it is the entry.
309
        entryPkgIdx = 0;
310
    } else {
311
        // Multiple packages: need -entry.
312
        let entryName = entryPkgName else {
313
            io::printError("radiance: `-entry` required when multiple packages specified\n");
314
            throw Error::Other;
315
        };
316
        for i in 0..pkgCount {
317
            if mem::eq(pkgNames[i], entryName) {
318
                entryPkgIdx = i;
319
                break;
320
            }
321
        }
322
        if entryPkgIdx == nil {
323
            io::printError("radiance: fatal: entry package `");
324
            io::printError(entryName);
325
            io::printError("` not found\n");
326
327
            throw Error::Other;
328
        }
329
    }
330
    let graph = module::moduleGraph(&mut MODULE_ENTRIES[..], &mut STRING_POOL, arena);
331
    let mut ctx = CompileContext {
332
        packages: undefined,
333
        packageCount: pkgCount,
334
        entryPkgIdx,
335
        graph,
336
        config: resolver::Config { buildTest },
337
        dump,
338
        outputPath,
339
        debug: debugEnabled,
340
    };
341
    // Initialize and parse all packages.
342
    let mut sourceArena = alloc::new(&mut MODULE_SOURCES[..]);
343
    for i in 0..pkgCount {
344
        package::init(&mut ctx.packages[i], i as u16, pkgNames[i], &mut STRING_POOL);
345
346
        for j in 0..moduleCounts[i] {
347
            let path = modulePaths[i][j];
348
            try processModule(&mut ctx.packages[i], &mut ctx.graph, path, arena, &mut sourceArena);
349
        }
350
    }
351
    return ctx;
352
}
353
354
/// Get the entry package from the context.
355
fn getEntryPackage(ctx: *CompileContext) -> *package::Package throws (Error) {
356
    let entryIdx = ctx.entryPkgIdx else {
357
        io::printError("radiance: no entry package specified\n");
358
        throw Error::Other;
359
    };
360
    return &ctx.packages[entryIdx];
361
}
362
363
/// Get root module info from a package.
364
fn getRootModule(pkg: *package::Package, graph: *module::ModuleGraph) -> RootModule throws (Error) {
365
    let rootId = pkg.rootModuleId else {
366
        io::printError("radiance: no root module found\n");
367
        throw Error::Other;
368
    };
369
    let rootEntry = module::get(graph, rootId) else {
370
        io::printError("radiance: root module entry not found\n");
371
        throw Error::Other;
372
    };
373
    let rootAst = rootEntry.ast else {
374
        io::printError("radiance: root module has no AST\n");
375
        throw Error::Other;
376
    };
377
    return RootModule { entry: rootEntry, ast: rootAst };
378
}
379
380
/// Dump the module graph.
381
fn dumpGraph(ctx: *CompileContext) {
382
    let mut arena = alloc::new(&mut MAIN_ARENA[..]);
383
    module::printer::printGraph(&ctx.graph, &mut arena);
384
}
385
386
/// Dump the parsed AST.
387
fn dumpAst(ctx: *CompileContext) throws (Error) {
388
    let pkg = try getEntryPackage(ctx);
389
    let root = try getRootModule(pkg, &ctx.graph);
390
    let mut arena = alloc::new(&mut MAIN_ARENA[..]);
391
392
    ast::printer::printTree(root.ast, &mut arena);
393
}
394
395
/// Lower all packages into a single IL program.
396
/// Dependencies are lowered first, then the entry package.
397
fn lowerAllPackages(
398
    ctx: *mut CompileContext,
399
    res: *mut resolver::Resolver
400
) -> il::Program throws (Error) {
401
    let entryIdx = ctx.entryPkgIdx else {
402
        panic "lowerAllPackages: no entry package";
403
    };
404
    let entryPkg = &ctx.packages[entryIdx];
405
406
    // Create the lowerer accumulator using entry package's name.
407
    let options = lower::LowerOptions { debug: ctx.debug, buildTest: ctx.config.buildTest };
408
    let mut low = lower::lowerer(res, &ctx.graph, entryPkg.name, &mut res.arena, options);
409
410
    // Lower all packages except entry.
411
    for i in 0..ctx.packageCount {
412
        if i != entryIdx {
413
            try lowerPackage(ctx, res, &mut low, &mut ctx.packages[i], false);
414
        }
415
    }
416
    // Lower entry package.
417
    let defaultFn = try lowerPackage(ctx, res, &mut low, &mut ctx.packages[entryIdx], true);
418
419
    // Finalize and return the unified program.
420
    return lower::finalize(&low, defaultFn);
421
}
422
423
/// Lower all modules in a package into the lowerer accumulator.
424
fn lowerPackage(
425
    ctx: *CompileContext,
426
    res: *mut resolver::Resolver,
427
    low: *mut lower::Lowerer,
428
    pkg: *mut package::Package,
429
    isEntry: bool
430
) -> ?u32 throws (Error) {
431
    let rootId = pkg.rootModuleId else {
432
        io::printError("radiance: no root module found\n");
433
        throw Error::Other;
434
    };
435
    // Set lowerer's package context for qualified name generation.
436
    // TODO: We shouldn't have to call this manually.
437
    lower::setPackage(low, &ctx.graph, pkg.name);
438
439
    return try lowerModuleTreeInto(ctx, low, &ctx.graph, rootId, isEntry, pkg);
440
}
441
442
/// Recursively lower a module and all its children into the accumulator.
443
fn lowerModuleTreeInto(
444
    ctx: *CompileContext,
445
    low: *mut lower::Lowerer,
446
    graph: *module::ModuleGraph,
447
    modId: u16,
448
    isRoot: bool,
449
    pkg: *package::Package
450
) -> ?u32 throws (Error) {
451
    let entry = module::get(graph, modId) else {
452
        io::printError("radiance: module entry not found\n");
453
        throw Error::Other;
454
    };
455
    let modAst = entry.ast else {
456
        io::printError("radiance: module has no AST\n");
457
        throw Error::Other;
458
    };
459
    pkgLog(pkg, &["lowering", "(", entry.filePath, ")", ".."]);
460
461
    let defaultFn = try lower::lowerModule(low, modId, modAst, isRoot) catch err {
462
        io::printError("radiance: internal error during lowering: ");
463
        lower::printError(err);
464
        io::printError("\n");
465
        throw Error::Other;
466
    };
467
    // Recurse into children.
468
    for i in 0..entry.childrenLen {
469
        let childId = module::childAt(entry, i);
470
        try lowerModuleTreeInto(ctx, low, graph, childId, false, pkg);
471
    }
472
    return defaultFn;
473
}
474
475
/// Build a scope access chain: a::b::c from a slice of identifiers.
476
fn synthScopeAccess(arena: *mut ast::NodeArena, path: *[*[u8]]) -> *ast::Node {
477
    let mut result = ast::synthNode(
478
        arena,
479
        ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, path[0]))
480
    );
481
    for i in 1..path.len {
482
        let child = ast::synthNode(
483
            arena,
484
            ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, path[i]))
485
        );
486
        result = ast::synthNode(arena, ast::NodeValue::ScopeAccess(ast::Access {
487
            parent: result, child,
488
        }));
489
    }
490
    return result;
491
}
492
493
/// Check if a function declaration has the `@test` attribute and return its name if so.
494
fn getTestFnName(decl: *ast::FnDecl) -> ?*[u8] {
495
    let attrs = decl.attrs else { return nil; };
496
    if not ast::attributesContains(&attrs, ast::Attribute::Test) {
497
        return nil;
498
    }
499
    let case ast::NodeValue::Ident(name) = decl.name.value
500
        else return nil;
501
502
    return name;
503
}
504
505
/// Scan a single module's AST for `@test` functions and append them to `tests`.
506
fn collectModuleTests(
507
    entry: *module::ModuleEntry,
508
    tests: *mut [TestDesc],
509
    testCount: *mut u32
510
) {
511
    let modAst = entry.ast else {
512
        return;
513
    };
514
    let case ast::NodeValue::Block(block) = modAst.value else {
515
        return;
516
    };
517
    let modPath = module::moduleQualifiedPath(entry);
518
519
    for stmt in block.statements {
520
        if let case ast::NodeValue::FnDecl(decl) = stmt.value {
521
            if let fnName = getTestFnName(&decl) {
522
                if *testCount < tests.len {
523
                    tests[*testCount] = TestDesc { modPath, fnName };
524
                    *testCount += 1;
525
                } else {
526
                    panic "collectModuleTests: too many tests";
527
                }
528
            }
529
        }
530
    }
531
}
532
533
/// Synthesize a `testing::test("mod", "name", mod::fn)` call for one test.
534
fn synthTestCall(arena: *mut ast::NodeArena, desc: *TestDesc) -> *ast::Node {
535
    let callee = synthScopeAccess(arena, &["testing", "test"]);
536
    let modStr = il::formatQualifiedName(
537
        &mut arena.arena,
538
        &desc.modPath[..desc.modPath.len - 1],
539
        desc.modPath[desc.modPath.len - 1]
540
    );
541
    let modArg = ast::synthNode(arena, ast::NodeValue::String(modStr));
542
    let nameArg = ast::synthNode(arena, ast::NodeValue::String(desc.fnName));
543
544
    // Intra-package path: skip the package name prefix.
545
    let mut funcPath: [*[u8]; 16] = undefined;
546
    for j in 1..desc.modPath.len {
547
        funcPath[j - 1] = desc.modPath[j];
548
    }
549
    funcPath[desc.modPath.len - 1] = desc.fnName;
550
    let funcArg = synthScopeAccess(arena, &funcPath[..desc.modPath.len]);
551
552
    let a = ast::nodeAllocator(arena);
553
    let mut args = ast::nodeSlice(arena, 3);
554
    args.append(modArg, a);
555
    args.append(nameArg, a);
556
    args.append(funcArg, a);
557
558
    return ast::synthNode(arena, ast::NodeValue::Call(ast::Call { callee, args }));
559
}
560
561
/// Inject a test runner into the entry package's root module.
562
///
563
/// Scans all parsed modules for `@test fn` declarations, then appends
564
/// a synthetic entry point to the root module's AST block:
565
///
566
/// ```
567
/// @default fn #testMain() -> i32 {
568
///     return testing::runAllTests(&[
569
///         testing::test("std::tests", "testFoo", tests::testFoo),
570
///         ...
571
///     ]);
572
/// }
573
/// ```
574
///
575
/// Uses `#`-prefixed names to avoid conflicts with user code.
576
fn generateTestRunner(
577
    ctx: *mut CompileContext,
578
    arena: *mut ast::NodeArena
579
) throws (Error) {
580
    let entryPkg = try getEntryPackage(ctx);
581
    let root = try getRootModule(entryPkg, &ctx.graph);
582
583
    // Collect all test functions across all modules.
584
    let mut tests: [TestDesc; MAX_TESTS] = undefined;
585
    let mut testCount: u32 = 0;
586
587
    for modIdx in 0..ctx.graph.entriesLen {
588
        if let entry = module::get(&ctx.graph, modIdx as u16) {
589
            collectModuleTests(entry, &mut tests[..], &mut testCount);
590
        }
591
    }
592
    if testCount == 0 {
593
        io::printError("radiance: fatal: no test functions found\n");
594
        throw Error::Other;
595
    }
596
    let mut countBuf: [u8; 10] = undefined;
597
    let countStr = fmt::formatU32(testCount, &mut countBuf[..]);
598
    pkgLog(entryPkg, &["found", countStr, "test(s)"]);
599
600
    // Synthesize the `@default` function and append to the root module.
601
    let fnDecl = synthTestMainFn(arena, &tests[..testCount]);
602
603
    injectIntoBlock(root.ast, arena, fnDecl);
604
}
605
606
/// Synthesize the test entry point.
607
fn synthTestMainFn(arena: *mut ast::NodeArena, tests: *[TestDesc]) -> *ast::Node {
608
    // Build array literal: `[testing::test(...), ...]`.
609
    let a = ast::nodeAllocator(arena);
610
    let mut elements = ast::nodeSlice(arena, tests.len as u32);
611
    for i in 0..tests.len {
612
        elements.append(synthTestCall(arena, &tests[i]), a);
613
    }
614
    let arrayLit = ast::synthNode(arena, ast::NodeValue::ArrayLit(elements));
615
616
    // Build: `&[...]`.
617
    let testsRef = ast::synthNode(arena, ast::NodeValue::AddressOf(ast::AddressOf {
618
        target: arrayLit, mutable: false,
619
    }));
620
621
    // Build: `testing::runAllTests(&[...])`.
622
    let runFn = synthScopeAccess(arena, &["testing", "runAllTests"]);
623
    let mut callArgs = ast::nodeSlice(arena, 1);
624
    callArgs.append(testsRef, a);
625
    let callExpr = ast::synthNode(arena, ast::NodeValue::Call(ast::Call {
626
        callee: runFn, args: callArgs,
627
    }));
628
629
    // Build: `return testing::runAllTests(&[...]);`
630
    let retStmt = ast::synthNode(arena, ast::NodeValue::Return { value: callExpr });
631
    let mut bodyStmts = ast::nodeSlice(arena, 1);
632
    bodyStmts.append(retStmt, a);
633
    let fnBody = ast::synthNode(arena, ast::NodeValue::Block(ast::Block { statements: bodyStmts }));
634
635
    // Build: `fn #testMain() -> i32`
636
    let fnName = ast::synthNode(arena, ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, "#testMain")));
637
    let returnType = ast::synthNode(arena, ast::NodeValue::TypeSig(ast::TypeSig::Integer {
638
        width: 4, sign: ast::Signedness::Signed,
639
    }));
640
    let fnSig = ast::FnSig {
641
        params: ast::nodeSlice(arena, 0),
642
        returnType,
643
        throwList: ast::nodeSlice(arena, 0),
644
    };
645
646
    // `@default` attribute.
647
    let attrNode = ast::synthNode(arena, ast::NodeValue::Attribute(ast::Attribute::Default));
648
    let mut attrList = ast::nodeSlice(arena, 1);
649
    attrList.append(attrNode, a);
650
    let fnAttrs = ast::Attributes { list: attrList };
651
652
    return ast::synthNode(arena, ast::NodeValue::FnDecl(ast::FnDecl {
653
        name: fnName, sig: fnSig, body: fnBody, attrs: fnAttrs,
654
    }));
655
}
656
657
/// Append a declaration to a block node's statement list.
658
fn injectIntoBlock(
659
    blockNode: *mut ast::Node,
660
    arena: *mut ast::NodeArena,
661
    decl: *ast::Node
662
) {
663
    let case ast::NodeValue::Block(block) = blockNode.value else {
664
        panic "injectIntoBlock: expected Block node";
665
    };
666
    let mut stmts = block.statements;
667
    stmts.append(decl, ast::nodeAllocator(arena));
668
669
    blockNode.value = ast::NodeValue::Block(ast::Block { statements: stmts });
670
}
671
672
/// Write code buffer to file as raw bytes.
673
fn writeCode(code: *[u32], path: *[u8]) -> bool {
674
    // Convert `u32` slice to `u8` slice (little-endian).
675
    let byteLen = code.len * 4;
676
    let bytePtr = code.ptr as *u8;
677
    let bytes = @sliceOf(bytePtr, byteLen);
678
679
    return unix::writeFile(path, bytes);
680
}
681
682
/// Write a data section to a file at `basePath` + `ext`.
683
/// If the data is empty, no file is written.
684
fn writeDataWithExt(
685
    data: *[u8],
686
    basePath: *[u8],
687
    ext: *[u8]
688
) throws (Error) {
689
    if data.len == 0 {
690
        return;
691
    }
692
    let mut path: [u8; MAX_PATH_LEN] = undefined;
693
    let mut pos: u32 = 0;
694
695
    pos += try! mem::copy(&mut path[pos..], basePath);
696
    pos += try! mem::copy(&mut path[pos..], ext);
697
    path[pos] = 0; // Null-terminate for syscall.
698
699
    if not unix::writeFile(&path[..pos], data) {
700
        io::printError("radiance: fatal: failed to write data file\n");
701
        throw Error::Other;
702
    }
703
}
704
705
/// Serialize debug entries and write the `.debug` file.
706
/// Resolves module IDs to file paths via the module graph.
707
/// Format per entry is `{pc: u32,  offset: u32, filePath: [u8], NULL}`.
708
fn writeDebugInfo(
709
    entries: *[rv64::emit::DebugEntry],
710
    graph: *module::ModuleGraph,
711
    basePath: *[u8],
712
    arena: *mut alloc::Arena
713
) throws (Error) {
714
    if entries.len == 0 {
715
        return;
716
    }
717
    // Use remaining arena space as serialization buffer.
718
    let buf = alloc::remainingBuf(arena);
719
    let mut pos: u32 = 0;
720
721
    for i in 0..entries.len {
722
        let entry = &entries[i];
723
        let modEntry = module::get(graph, entry.moduleId) else {
724
            panic "writeDebugInfo: module not found for debug entry";
725
        };
726
        pos += try! mem::copy(&mut buf[pos..], @sliceOf(&entry.pc as *u8, 4));
727
        pos += try! mem::copy(&mut buf[pos..], @sliceOf(&entry.offset as *u8, 4));
728
        pos += try! mem::copy(&mut buf[pos..], modEntry.filePath);
729
730
        buf[pos] = 0;
731
        pos += 1;
732
    }
733
    try writeDataWithExt(&buf[..pos], basePath, DEBUG_EXT);
734
}
735
736
/// Run the resolver on the parsed modules.
737
fn runResolver(ctx: *mut CompileContext, nodeCount: u32) -> resolver::Resolver throws (Error) {
738
    let mut mainArena = alloc::new(&mut MAIN_ARENA[..]);
739
    let entryPkg = try getEntryPackage(ctx);
740
741
    pkgLog(entryPkg, &["resolving", ".."]);
742
743
    let nodeDataSize = nodeCount * @sizeOf(resolver::NodeData);
744
    let nodeDataPtr = try! alloc::alloc(&mut mainArena, nodeDataSize, @alignOf(resolver::NodeData));
745
    let nodeData = @sliceOf(nodeDataPtr as *mut resolver::NodeData, nodeCount);
746
    let storage = resolver::ResolverStorage {
747
        arena: mainArena,
748
        nodeData,
749
        pkgScope: &mut RESOLVER_PKG_SCOPE,
750
        errors: &mut RESOLVER_ERRORS[..],
751
    };
752
    let mut res = resolver::resolver(storage, ctx.config);
753
754
    // Build package inputs.
755
    let mut packages: [resolver::Pkg; MAX_PACKAGES] = undefined;
756
    for i in 0..ctx.packageCount {
757
        let pkg = &ctx.packages[i];
758
        let root = try getRootModule(pkg, &ctx.graph);
759
760
        packages[i] = resolver::Pkg {
761
            rootEntry: root.entry,
762
            rootAst: root.ast,
763
        };
764
    }
765
766
    // Resolve all packages.
767
    // TODO: Fix this error printing dance.
768
    let diags = try resolver::resolve(&mut res, &ctx.graph, &packages[..ctx.packageCount]) catch {
769
        let diags = resolver::Diagnostics { errors: res.errors };
770
        resolver::printer::printDiagnostics(&diags, &res);
771
        throw Error::Other;
772
    };
773
    if not resolver::success(&diags) {
774
        resolver::printer::printDiagnostics(&diags, &res);
775
        io::print("radiance: failed: ");
776
        io::printU32(diags.errors.len);
777
        io::printLn(" errors");
778
        throw Error::Other;
779
    }
780
    return res;
781
}
782
783
/// Lower, optionally dump, and optionally generate binary output.
784
fn compile(ctx: *mut CompileContext, res: *mut resolver::Resolver) throws (Error) {
785
    let entryPkg = try getEntryPackage(ctx);
786
787
    // Lower all packages into a single unified IL program.
788
    // Dependencies must be lowered before the packages that use them.
789
    let program = try lowerAllPackages(ctx, res);
790
    let mut out = sexpr::Output::Stdout;
791
792
    if ctx.dump == Dump::Il {
793
        il::printer::printProgram(&mut out, &mut res.arena, &program);
794
        io::print("\n");
795
        return;
796
    }
797
    if ctx.dump == Dump::Asm {
798
        // TODO: Why do we generate code in two places?
799
        let result = rv64::generate(&program, rv64::Storage { dataSyms: &mut CODEGEN_DATA_SYMS[..] }, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..], &mut res.arena, false);
800
        printer::printCodeTo(&mut out, entryPkg.name, result.code, result.funcs, &mut res.arena);
801
        io::print("\n");
802
        return;
803
    }
804
    // Generate binary output if path specified.
805
    let outPath = ctx.outputPath else {
806
        return;
807
    };
808
    // Check that we have an entry point.
809
    if program.defaultFnIdx == nil {
810
        io::printError("radiance: fatal: no default function found\n");
811
        throw Error::Other;
812
    }
813
    pkgLog(entryPkg, &["generating code", "(", outPath, ")", ".."]);
814
815
    let storage = rv64::Storage { dataSyms: &mut CODEGEN_DATA_SYMS[..] };
816
    let result = rv64::generate(&program, storage, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..], &mut res.arena, ctx.debug);
817
    if not writeCode(result.code, outPath) {
818
        io::printError("radiance: fatal: failed to write output file\n");
819
        throw Error::Other;
820
    }
821
    // Write data files.
822
    try writeDataWithExt(&RO_DATA_BUF[..result.roDataSize], outPath, RO_DATA_EXT);
823
    try writeDataWithExt(&RW_DATA_BUF[..result.rwDataSize], outPath, RW_DATA_EXT);
824
825
    // Write debug info file if enabled.
826
    if ctx.debug {
827
        try writeDebugInfo(result.debugEntries, &ctx.graph, outPath, &mut res.arena);
828
    }
829
    pkgLog(entryPkg, &["ok", "(", outPath, ")"]);
830
}
831
832
@default fn main(env: *sys::Env) -> i32 {
833
    let mut arena = ast::nodeArena(&mut TEMP_ARENA[..]);
834
    let mut ctx = try processCommand(env.args, &mut arena) catch {
835
        return 1;
836
    };
837
    match ctx.dump {
838
        case Dump::Ast => {
839
            try dumpAst(&ctx) catch {
840
                return 1;
841
            };
842
            return 0;
843
        }
844
        case Dump::Graph => {
845
            dumpGraph(&ctx);
846
            return 0;
847
        }
848
        else => {}
849
    }
850
    // Generate test runner if in test mode.
851
    if ctx.config.buildTest {
852
        try generateTestRunner(&mut ctx, &mut arena) catch {
853
            return 1;
854
        };
855
    }
856
    // Run resolution phase.
857
    let mut res = try runResolver(&mut ctx, arena.nextId) catch {
858
        return 1;
859
    };
860
    // Lower, dump, and/or generate output.
861
    try compile(&mut ctx, &mut res) catch {
862
        return 1;
863
    };
864
    return 0;
865
}