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