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