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