Stream function lowering into RV64 codegen

e69f75fc4fd7ec3a4ef3ab0d933e466dc7caa43a80d6cd14ef91e66b1f7f12ef
Lower functions into a per-function arena and emit them to RV64 as they
are produced, allowing function-local IL to be reclaimed after each
function.

Split RV64 generation into begin, per-function, and finish phases so
data layout and address patching can happen after all functions are
generated. Track the default entry through codegen entry patching
instead of storing it on the IL program.
Alexis Sellier committed ago 1 parent 64585227
compiler/radiance.rad +151 -30
33 33
constant MAX_TESTS: u32 = 1024;
34 34
35 35
/// Temporary arena size (32 MB) - retains all parsed AST until resolution.
36 36
/// Used for: AST during parsing, then codegen scratch space.
37 37
constant TEMP_ARENA_SIZE: u32 = 33554432;
38 -
/// Main arena size (128 MB) - lives throughout compilation.
39 -
/// Used for: resolver data, types, symbols, lowered IL.
40 -
constant MAIN_ARENA_SIZE: u32 = 134217728;
38 +
/// Main arena size (64 MB) - lives throughout compilation.
39 +
/// Used for: resolver data, types, symbols, global IL data, and codegen output.
40 +
constant MAIN_ARENA_SIZE: u32 = 67108864;
41 41
42 42
/// Temporary storage arena - reusable between phases.
43 43
static TEMP_ARENA: [u8; TEMP_ARENA_SIZE] = undefined;
44 44
/// Main storage arena - persists throughout compilation.
45 45
static MAIN_ARENA: [u8; MAIN_ARENA_SIZE] = undefined;
134 134
record RootModule {
135 135
    entry: *module::ModuleEntry,
136 136
    ast: *mut ast::Node,
137 137
}
138 138
139 +
/// State carried by the streaming lowerer/codegen callback.
140 +
record CodegenSinkContext {
141 +
    /// RV64 generator receiving lowered functions.
142 +
    generator: *mut rv64::Generator,
143 +
    /// Arena used for codegen scratch allocations.
144 +
    codegenArena: *mut alloc::Arena,
145 +
    /// Arena holding the current function's lowered IL.
146 +
    fnArena: *mut alloc::Arena,
147 +
}
148 +
149 +
/// Entry handling for streamed code generation.
150 +
union CodegenEntryMode {
151 +
    /// Do not reserve an entry jump.
152 +
    None,
153 +
    /// Reserve and patch an entry jump to the `@default` function.
154 +
    DefaultEntry,
155 +
}
156 +
157 +
/// Options controlling streamed lowering and code generation.
158 +
record CodegenOptions {
159 +
    /// Optional output path used for progress logging.
160 +
    logPath: ?*[u8],
161 +
    /// Whether to emit debug source locations.
162 +
    debug: bool,
163 +
    /// How the generated program should handle entry.
164 +
    entryMode: CodegenEntryMode,
165 +
}
166 +
139 167
/// Print a log line for the given package.
140 168
fn pkgLog(pkg: *package::Package, msg: *[*[u8]]) {
141 169
    io::printError("radiance: ");
142 170
    io::printError(pkg.name);
143 171
    io::printError(": ");
398 426
    };
399 427
    let entryPkg = &ctx.packages[entryIdx];
400 428
401 429
    // Create the lowerer accumulator using entry package's name.
402 430
    let options = lower::LowerOptions { debug: ctx.debug, buildTest: ctx.config.buildTest };
403 -
    let mut low = lower::lowerer(res, &ctx.graph, entryPkg.name, &mut res.arena, options);
431 +
    let mut low = lower::lowerer(
432 +
        res, &ctx.graph, entryPkg.name, &mut res.arena, &mut res.arena, options
433 +
    );
434 +
    try lowerAllPackagesInto(ctx, res, &mut low);
435 +
436 +
    // Finalize and return the unified program.
437 +
    return lower::finalize(&low);
438 +
}
404 439
440 +
/// Lower all packages into an existing lowerer.
441 +
fn lowerAllPackagesInto(
442 +
    ctx: *mut CompileContext,
443 +
    res: *mut resolver::Resolver,
444 +
    low: *mut lower::Lowerer
445 +
) throws (Error) {
446 +
    let entryIdx = ctx.entryPkgIdx else {
447 +
        panic "lowerAllPackagesInto: no entry package";
448 +
    };
405 449
    // Lower all packages except entry.
406 450
    for i in 0..ctx.packageCount {
407 451
        if i <> entryIdx {
408 -
            try lowerPackage(ctx, res, &mut low, &mut ctx.packages[i], false);
452 +
            try lowerPackage(ctx, res, low, &mut ctx.packages[i], false);
409 453
        }
410 454
    }
411 455
    // Lower entry package.
412 -
    let defaultFn = try lowerPackage(ctx, res, &mut low, &mut ctx.packages[entryIdx], true);
413 -
414 -
    // Finalize and return the unified program.
415 -
    return lower::finalize(&low, defaultFn);
456 +
    try lowerPackage(ctx, res, low, &mut ctx.packages[entryIdx], true);
416 457
}
417 458
418 459
/// Lower all modules in a package into the lowerer accumulator.
419 460
fn lowerPackage(
420 461
    ctx: *CompileContext,
421 462
    res: *mut resolver::Resolver,
422 463
    low: *mut lower::Lowerer,
423 464
    pkg: *mut package::Package,
424 465
    isEntry: bool
425 -
) -> ?u32 throws (Error) {
466 +
) throws (Error) {
426 467
    let rootId = pkg.rootModuleId else {
427 468
        io::printError("radiance: no root module found\n");
428 469
        throw Error::Other;
429 470
    };
430 471
    // Set lowerer's package context for qualified name generation.
431 472
    // TODO: We shouldn't have to call this manually.
432 473
    lower::setPackage(low, &ctx.graph, pkg.name);
433 474
434 -
    return try lowerModuleTreeInto(ctx, low, &ctx.graph, rootId, isEntry, pkg);
475 +
    try lowerModuleTreeInto(ctx, low, &ctx.graph, rootId, isEntry, pkg);
435 476
}
436 477
437 478
/// Recursively lower a module and all its children into the accumulator.
438 479
fn lowerModuleTreeInto(
439 480
    ctx: *CompileContext,
440 481
    low: *mut lower::Lowerer,
441 482
    graph: *module::ModuleGraph,
442 483
    modId: u16,
443 484
    isRoot: bool,
444 485
    pkg: *package::Package
445 -
) -> ?u32 throws (Error) {
486 +
) throws (Error) {
446 487
    let entry = module::get(graph, modId) else {
447 488
        io::printError("radiance: module entry not found\n");
448 489
        throw Error::Other;
449 490
    };
450 491
    let modAst = entry.ast else {
451 492
        io::printError("radiance: module has no AST\n");
452 493
        throw Error::Other;
453 494
    };
454 495
    pkgLog(pkg, &["lowering", "(", entry.filePath, ")", ".."]);
455 496
456 -
    let defaultFn = try lower::lowerModule(low, modId, modAst, isRoot) catch err {
497 +
    try lower::lowerModule(low, modId, modAst, isRoot) catch err {
457 498
        io::printError("radiance: internal error during lowering: ");
458 499
        lower::printError(err);
459 500
        io::printError("\n");
460 501
        throw Error::Other;
461 502
    };
462 503
    // Recurse into children.
463 504
    for i in 0..entry.childrenLen {
464 505
        let childId = module::childAt(entry, i);
465 506
        try lowerModuleTreeInto(ctx, low, graph, childId, false, pkg);
466 507
    }
467 -
    return defaultFn;
468 508
}
469 509
470 510
/// Build a scope access chain: a::b::c from a slice of identifiers.
471 511
fn synthScopeAccess(arena: *mut ast::NodeArena, path: *[*[u8]]) -> *ast::Node {
472 512
    let mut result = ast::synthNode(
768 808
        throw Error::Other;
769 809
    }
770 810
    return res;
771 811
}
772 812
813 +
/// Emit one lowered function to RV64 codegen and reclaim its IL arena.
814 +
fn generateLoweredFn(ctxPtr: *mut opaque, func: *il::Fn, role: lower::FnRole) {
815 +
    let ctx = ctxPtr as *mut CodegenSinkContext;
816 +
817 +
    match role {
818 +
        case lower::FnRole::Default => {
819 +
            match ctx.generator.entryPatch {
820 +
                case rv64::EntryPatch::Reserved(_) => {
821 +
                    set ctx.generator.entryPatch = rv64::EntryPatch::Reserved(func.name);
822 +
                }
823 +
                else => panic "generateLoweredFn: entry jump was not reserved",
824 +
            }
825 +
        }
826 +
        else => {}
827 +
    }
828 +
    rv64::generateFunction(ctx.generator, func, ctx.codegenArena);
829 +
    alloc::reset(ctx.fnArena);
830 +
}
831 +
832 +
/// Lower all packages while streaming each lowered function into RV64 codegen.
833 +
fn lowerAndGenerateAllPackages(
834 +
    ctx: *mut CompileContext,
835 +
    res: *mut resolver::Resolver,
836 +
    fnArena: *mut alloc::Arena,
837 +
    codegenOptions: CodegenOptions
838 +
) -> rv64::Program throws (Error) {
839 +
    let entryIdx = ctx.entryPkgIdx else {
840 +
        panic "lowerAndGenerateAllPackages: no entry package";
841 +
    };
842 +
    let entryPkg = &ctx.packages[entryIdx];
843 +
    let options = lower::LowerOptions { debug: ctx.debug, buildTest: ctx.config.buildTest };
844 +
    let storage = rv64::Storage {
845 +
        dataSyms: &mut CODEGEN_DATA_SYMS[..],
846 +
        dataSymEntries: &mut CODEGEN_DATA_SYM_ENTRIES[..],
847 +
    };
848 +
    let mut entryPatch = rv64::EntryPatch::None;
849 +
    match codegenOptions.entryMode {
850 +
        case CodegenEntryMode::DefaultEntry => {
851 +
            set entryPatch = rv64::EntryPatch::Reserved(nil);
852 +
        }
853 +
        else => {}
854 +
    }
855 +
    let mut generator = rv64::beginProgram(
856 +
        rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug },
857 +
        &mut res.arena
858 +
    );
859 +
    let mut codegenCtx = CodegenSinkContext {
860 +
        generator: &mut generator,
861 +
        codegenArena: &mut res.arena,
862 +
        fnArena,
863 +
    };
864 +
    let mut low = lower::lowerer(
865 +
        res, &ctx.graph, entryPkg.name, &mut res.arena, fnArena, options
866 +
    );
867 +
    set low.output = lower::FnOutput::Stream(lower::FnSink {
868 +
        ctx: &mut codegenCtx as *mut opaque,
869 +
        emitFn: generateLoweredFn,
870 +
    });
871 +
    try lowerAllPackagesInto(ctx, res, &mut low);
872 +
873 +
    match generator.entryPatch {
874 +
        case rv64::EntryPatch::Reserved(targetName) => {
875 +
            if targetName == nil {
876 +
                io::printError("radiance: fatal: no default function found\n");
877 +
                throw Error::Other;
878 +
            }
879 +
        }
880 +
        else => {}
881 +
    }
882 +
    if let path = codegenOptions.logPath {
883 +
        pkgLog(entryPkg, &["generating code", "(", path, ")", ".."]);
884 +
    }
885 +
    return rv64::finishProgram(&mut generator, &low.data[..], storage, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]);
886 +
}
887 +
773 888
/// Lower, optionally dump, and optionally generate binary output.
774 -
fn compile(ctx: *mut CompileContext, res: *mut resolver::Resolver) throws (Error) {
889 +
fn compile(
890 +
    ctx: *mut CompileContext,
891 +
    res: *mut resolver::Resolver,
892 +
    fnArena: *mut alloc::Arena
893 +
) throws (Error) {
775 894
    let entryPkg = try getEntryPackage(ctx);
776 -
777 -
    // Lower all packages into a single unified IL program.
778 -
    // Dependencies must be lowered before the packages that use them.
779 -
    let program = try lowerAllPackages(ctx, res);
780 895
    let mut out = sexpr::Output::Stdout;
781 896
782 897
    if ctx.dump == Dump::Il {
898 +
        // Lower all packages into a single unified IL program for dumping.
899 +
        let program = try lowerAllPackages(ctx, res);
783 900
        il::printer::printProgram(&mut out, &mut res.arena, &program);
784 901
        io::print("\n");
785 902
        return;
786 903
    }
787 904
    if ctx.dump == Dump::Asm {
788 -
        // TODO: Why do we generate code in two places?
789 -
        let result = rv64::generate(&program, rv64::Storage { dataSyms: &mut CODEGEN_DATA_SYMS[..], dataSymEntries: &mut CODEGEN_DATA_SYM_ENTRIES[..] }, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..], &mut res.arena, false);
905 +
        let result = try lowerAndGenerateAllPackages(ctx, res, fnArena, CodegenOptions {
906 +
            logPath: nil,
907 +
            debug: false,
908 +
            entryMode: CodegenEntryMode::None,
909 +
        });
790 910
        printer::printCodeTo(&mut out, entryPkg.name, result.code, result.funcs, &mut res.arena);
791 911
        io::print("\n");
912 +
792 913
        return;
793 914
    }
794 915
    // Generate binary output if path specified.
795 916
    let outPath = ctx.outputPath else {
917 +
        try lowerAllPackages(ctx, res);
796 918
        return;
797 919
    };
798 -
    // Check that we have an entry point.
799 -
    if program.defaultFnIdx == nil {
800 -
        io::printError("radiance: fatal: no default function found\n");
801 -
        throw Error::Other;
802 -
    }
803 -
    pkgLog(entryPkg, &["generating code", "(", outPath, ")", ".."]);
920 +
    let result = try lowerAndGenerateAllPackages(ctx, res, fnArena, CodegenOptions {
921 +
        logPath: outPath,
922 +
        debug: ctx.debug,
923 +
        entryMode: CodegenEntryMode::DefaultEntry,
924 +
    });
804 925
805 -
    let storage = rv64::Storage { dataSyms: &mut CODEGEN_DATA_SYMS[..], dataSymEntries: &mut CODEGEN_DATA_SYM_ENTRIES[..] };
806 -
    let result = rv64::generate(&program, storage, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..], &mut res.arena, ctx.debug);
807 926
    if not writeCode(result.code, outPath) {
808 927
        io::printError("radiance: fatal: failed to write output file\n");
809 928
        throw Error::Other;
810 929
    }
811 930
    // Write data files.
845 964
    }
846 965
    // Run resolution phase.
847 966
    let mut res = try runResolver(&mut ctx, arena.nextId) catch {
848 967
        return 1;
849 968
    };
969 +
    let mut fnArena = alloc::new(alloc::remainingBuf(&mut arena.arena));
970 +
850 971
    // Lower, dump, and/or generate output.
851 -
    try compile(&mut ctx, &mut res) catch {
972 +
    try compile(&mut ctx, &mut res, &mut fnArena) catch {
852 973
        return 1;
853 974
    };
854 975
    return 0;
855 976
}
lib/std/arch/rv64.rad +97 -51
168 168
    rwDataSize: u32,
169 169
    /// Debug entries mapping PCs to source locations. Empty when debug is off.
170 170
    debugEntries: *[types::DebugEntry],
171 171
}
172 172
173 -
/// Generate code for an IL program.
174 -
export fn generate(
175 -
    program: *il::Program,
173 +
/// Entry jump patching requested for the generated program.
174 +
export union EntryPatch {
175 +
    /// No entry jump is emitted.
176 +
    None,
177 +
    /// Reserve code slot zero for a jump to the default function.
178 +
    Reserved(?*[u8]),
179 +
}
180 +
181 +
/// Options controlling incremental RV64 program generation.
182 +
export record ProgramOptions {
183 +
    /// Entry jump patching mode.
184 +
    entryPatch: EntryPatch,
185 +
    /// Whether to emit debug source locations.
186 +
    debug: bool,
187 +
}
188 +
189 +
/// State for incremental RV64 program generation.
190 +
///
191 +
/// The generator owns global codegen state that must survive across function
192 +
/// emission. Function-local scratch stays outside this record so callers can
193 +
/// reclaim it after each function.
194 +
export record Generator {
195 +
    /// Binary emitter and relocation state.
196 +
    e: emit::Emitter,
197 +
    /// Entry jump patching state.
198 +
    entryPatch: EntryPatch,
199 +
}
200 +
201 +
/// Begin RV64 code generation for a program's global state.
202 +
export fn beginProgram(
203 +
    options: ProgramOptions,
204 +
    arena: *mut alloc::Arena
205 +
) -> Generator {
206 +
    let mut e = try! emit::emitter(arena, options.debug);
207 +
208 +
    // Emit placeholder entry jump when requested.
209 +
    // We'll patch this at the end once we know where the function is.
210 +
    match options.entryPatch {
211 +
        case EntryPatch::Reserved(_) => {
212 +
            emit::emit(&mut e, encode::nop()); // Placeholder for two-instruction jump.
213 +
            emit::emit(&mut e, encode::nop()); //
214 +
        }
215 +
        else => {}
216 +
    }
217 +
218 +
    return Generator {
219 +
        e,
220 +
        entryPatch: options.entryPatch,
221 +
    };
222 +
}
223 +
224 +
/// Generate code for one IL function.
225 +
export fn generateFunction(
226 +
    generator: *mut Generator,
227 +
    func: *il::Fn,
228 +
    arena: *mut alloc::Arena
229 +
) {
230 +
    if func.isExtern {
231 +
        return;
232 +
    }
233 +
    let checkpoint = alloc::save(arena);
234 +
    let config = targetConfig();
235 +
    let ralloc = try! regalloc::allocate(func, &config, arena);
236 +
237 +
    isel::selectFn(&mut generator.e, &ralloc, func);
238 +
239 +
    // Reclaim unused memory after instruction selection.
240 +
    alloc::restore(arena, checkpoint);
241 +
}
242 +
243 +
/// Finish RV64 code generation and return the emitted program.
244 +
export fn finishProgram(
245 +
    generator: *mut Generator,
246 +
    globalData: *[il::Data],
176 247
    storage: Storage,
177 248
    roDataBuf: *mut [u8],
178 -
    rwDataBuf: *mut [u8],
179 -
    arena: *mut alloc::Arena,
180 -
    debug: bool
249 +
    rwDataBuf: *mut [u8]
181 250
) -> Program {
182 -
    let mut e = try! emit::emitter(arena, debug);
183 -
    let config = targetConfig();
184 -
185 -
    // Build data map.
251 +
    // Build data map after function lowering. Function-local literals can add
252 +
    // global data while functions are lowered, so final layout belongs here.
186 253
    let mut dataSymCount: u32 = 0;
254 +
    let roLayoutSize = data::layoutSection(globalData, storage.dataSyms, &mut dataSymCount, RO_DATA_BASE, true);
255 +
    data::layoutSection(globalData, storage.dataSyms, &mut dataSymCount, RW_DATA_BASE, false);
187 256
188 -
    let roLayoutSize = data::layoutSection(program, storage.dataSyms, &mut dataSymCount, RO_DATA_BASE, true);
189 -
    data::layoutSection(program, storage.dataSyms, &mut dataSymCount, RW_DATA_BASE, false);
190 -
191 -
    // Build hash-indexed data symbol map for O(1) lookups.
192 257
    let dataSyms = &storage.dataSyms[..dataSymCount];
193 -
    let mut dataSymMap = data::buildMap(dataSyms, storage.dataSymEntries);
194 -
195 -
    // Code base address: code follows read-only data, aligned to dword boundary.
258 +
    let dataSymMap = data::buildMap(dataSyms, storage.dataSymEntries);
196 259
    let codeBase = mem::alignUp(RO_DATA_BASE + roLayoutSize, DWORD_SIZE as u32);
197 260
198 -
    // Emit placeholder entry jump to default function if there is one.
199 -
    // We'll patch this at the end once we know where the function is.
200 -
    let mut defaultName: ?*[u8] = nil;
201 -
    if let defIdx = program.defaultFnIdx {
202 -
        set defaultName = program.fns[defIdx].name;
203 -
        emit::emit(&mut e, encode::nop()); // Placeholder for two-instruction jump.
204 -
        emit::emit(&mut e, encode::nop()); //
205 -
    }
206 -
207 -
    // Generate code for all functions.
208 -
    for i in 0..program.fns.len {
209 -
        let func = program.fns[i];
210 -
        if not func.isExtern {
211 -
            let checkpoint = alloc::save(arena);
212 -
            let ralloc = try! regalloc::allocate(func, &config, arena);
213 -
            isel::selectFn(&mut e, &dataSymMap, &ralloc, func);
261 +
    match generator.entryPatch {
262 +
        case EntryPatch::Reserved(targetName) => {
263 +
            let target = targetName else {
264 +
                panic "finishProgram: entry jump reserved without default function";
265 +
            };
266 +
            let offset = emit::branchOffsetToFunc(&generator.e, 0, target);
267 +
            let s = emit::splitImm(offset);
214 268
215 -
            // Reclaim unused memory after instruction selection.
216 -
            alloc::restore(arena, checkpoint);
269 +
            emit::patch(&mut generator.e, 0, encode::auipc(SCRATCH1, s.hi));
270 +
            emit::patch(&mut generator.e, 1, encode::jalr(ZERO, SCRATCH1, s.lo));
217 271
        }
218 -
    }
219 -
    // Patch entry jump now that we know where the default function is.
220 -
    // Nb. we use a two-instruction jump even if it isn't always needed.
221 -
    if let target = defaultName {
222 -
        let offset = emit::branchOffsetToFunc(&e, 0, target);
223 -
        let s = emit::splitImm(offset);
224 -
225 -
        emit::patch(&mut e, 0, encode::auipc(SCRATCH1, s.hi));
226 -
        emit::patch(&mut e, 1, encode::jalr(ZERO, SCRATCH1, s.lo));
272 +
        else => {}
227 273
    }
228 274
    // Patch function calls and address loads now that all functions are emitted.
229 -
    emit::patchCalls(&mut e);
230 -
    emit::patchAddrLoads(&mut e);
275 +
    emit::patchCalls(&mut generator.e);
276 +
    emit::patchAddrLoads(&mut generator.e, &dataSymMap);
231 277
232 278
    // Emit data sections.
233 -
    let roDataSize = data::emitSection(program, &dataSymMap, &e.labels, codeBase, roDataBuf, true);
234 -
    let rwDataSize = data::emitSection(program, &dataSymMap, &e.labels, codeBase, rwDataBuf, false);
279 +
    let roDataSize = data::emitSection(globalData, &dataSymMap, &generator.e.labels, codeBase, roDataBuf, true);
280 +
    let rwDataSize = data::emitSection(globalData, &dataSymMap, &generator.e.labels, codeBase, rwDataBuf, false);
235 281
236 282
    return Program {
237 -
        code: emit::getCode(&e),
238 -
        funcs: emit::getFuncs(&e),
283 +
        code: emit::getCode(&generator.e),
284 +
        funcs: emit::getFuncs(&generator.e),
239 285
        roDataSize,
240 286
        rwDataSize,
241 -
        debugEntries: emit::getDebugEntries(&e),
287 +
        debugEntries: emit::getDebugEntries(&generator.e),
242 288
    };
243 289
}
lib/std/arch/rv64/emit.rad +40 -8
3 3
//! Emits RV64 machine code as `u32` list.
4 4
5 5
use std::lang::il;
6 6
use std::lang::alloc;
7 7
use std::lang::gen;
8 +
use std::lang::gen::data;
8 9
use std::lang::gen::labels;
9 10
use std::lang::gen::types;
10 11
use std::collections::dict;
11 12
use std::mem;
12 13
14 15
15 16
/// Maximum number of instructions in code buffer.
16 17
constant MAX_INSTRS: u32 = 2097152;
17 18
/// Maximum code length before byte offset overflows signed 32-bits.
18 19
constant MAX_CODE_LEN: u32 = 0x7FFFFFFF / super::INSTR_SIZE as u32;
20 +
/// Maximum positive value encodable by a signed 32-bit address calculation.
21 +
constant MAX_I32_ADDR: u32 = 0x7FFFFFFF;
19 22
/// Maximum number of pending branches awaiting patching.
20 23
constant MAX_PENDING: u32 = 65536;
21 24
/// Maximum number of function entries.
22 25
constant MAX_FUNCS: u32 = 4096;
23 26
/// Maximum number of debug entries.
53 56
    index: u32,
54 57
    /// Target function name.
55 58
    target: *[u8],
56 59
}
57 60
58 -
/// Function address load that needs offset patching.
59 -
/// Used when taking a function's address as a value.
61 +
/// Address load that needs patching after layout is known.
60 62
export record PendingAddrLoad {
61 63
    /// Index in code buffer where the load was emitted.
62 64
    index: u32,
63 -
    /// Target function name.
65 +
    /// Target function or data symbol name.
64 66
    target: *[u8],
65 67
    /// Destination register.
66 68
    rd: gen::Reg,
69 +
    /// Whether this is an absolute data symbol address.
70 +
    isData: bool,
67 71
}
68 72
69 73
/// Adjusted base register and offset for addressing.
70 74
export record AdjustedOffset {
71 75
    /// Base register.
287 291
    assert e.pendingAddrLoadsLen < e.pendingAddrLoads.len, "recordAddrLoad: buffer full";
288 292
    set e.pendingAddrLoads[e.pendingAddrLoadsLen] = PendingAddrLoad {
289 293
        index: e.codeLen,
290 294
        target,
291 295
        rd: rd,
296 +
        isData: false,
292 297
    };
293 298
    set e.pendingAddrLoadsLen += 1;
294 299
295 300
    emit(e, encode::nop()); // Placeholder for AUIPC.
296 301
    emit(e, encode::nop()); // Placeholder for ADDI.
297 302
}
298 303
304 +
/// Record a data address load needing later patching.
305 +
/// Uses an absolute 32-bit load sequence matching the current data memory map.
306 +
export fn recordDataAddrLoad(e: *mut Emitter, target: *[u8], rd: gen::Reg) {
307 +
    assert e.pendingAddrLoadsLen < e.pendingAddrLoads.len, "recordDataAddrLoad: buffer full";
308 +
    set e.pendingAddrLoads[e.pendingAddrLoadsLen] = PendingAddrLoad {
309 +
        index: e.codeLen,
310 +
        target,
311 +
        rd: rd,
312 +
        isData: true,
313 +
    };
314 +
    set e.pendingAddrLoadsLen += 1;
315 +
316 +
    emit(e, encode::nop()); // Placeholder for LUI.
317 +
    emit(e, encode::nop()); // Placeholder for ADDIW.
318 +
}
319 +
299 320
/// Patch local branches and clear the pending list.
300 321
///
301 322
/// Called after each function.
302 323
///
303 324
/// Uses two-instruction sequences: short branches use `branch` and `nop`,
370 391
        // `JALR ra, scratch, lo(offset)`.
371 392
        patch(e, p.index + 1, encode::jalr(super::RA, super::SCRATCH1, s.lo));
372 393
    }
373 394
}
374 395
375 -
/// Patch all pending function address loads.
376 -
/// Called after all functions have been generated.
377 -
/// Uses PC-relative addresses up to 2GB away.
378 -
export fn patchAddrLoads(e: *mut Emitter) {
396 +
/// Patch all pending function and data address loads.
397 +
/// Called after all functions have been generated and data layout is known.
398 +
export fn patchAddrLoads(e: *mut Emitter, dataSymMap: *data::DataSymMap) {
379 399
    for i in 0..e.pendingAddrLoadsLen {
380 400
        let p = e.pendingAddrLoads[i];
401 +
        if p.isData {
402 +
            let addr = data::lookupAddr(dataSymMap, p.target) else {
403 +
                panic "patchAddrLoads: data symbol not found";
404 +
            };
405 +
            assert addr <= MAX_I32_ADDR, "patchAddrLoads: data address too large";
406 +
            let s = splitImm(addr as i32);
407 +
408 +
            patch(e, p.index, encode::lui(p.rd, s.hi));
409 +
            patch(e, p.index + 1, encode::addiw(p.rd, p.rd, s.lo));
410 +
411 +
            continue;
412 +
        }
413 +
381 414
        let offset = branchOffsetToFunc(e, p.index, p.target);
382 415
        let s = splitImm(offset);
383 -
384 416
        // `AUIPC rd, hi(offset)`.
385 417
        patch(e, p.index, encode::auipc(p.rd, s.hi));
386 418
        // `ADDI rd, rd, lo(offset)`.
387 419
        patch(e, p.index + 1, encode::addi(p.rd, p.rd, s.lo));
388 420
    }
lib/std/arch/rv64/isel.rad +2 -22
30 30
use std::mem;
31 31
use std::lang::il;
32 32
use std::lang::gen;
33 33
use std::lang::gen::regalloc;
34 34
use std::lang::gen::labels;
35 -
use std::lang::gen::data;
36 35
37 36
use super::encode;
38 37
use super::emit;
39 38
40 39
///////////////
70 69
/// Shift operation.
71 70
union ShiftOp { Sll, Srl, Sra }
72 71
/// Compare operation.
73 72
union CmpOp { Slt, Ult }
74 73
75 -
/// Selector errors.
76 -
export union SelectorError {
77 -
    Internal,
78 -
}
79 -
80 74
/// A pending spill store to be flushed after instruction selection.
81 75
record PendingSpill {
82 76
    /// The SSA register that was spilled.
83 77
    ssa: il::Reg,
84 78
    /// The physical register holding the value to store.
93 87
export record Selector {
94 88
    /// Emitter for outputting instructions.
95 89
    e: *mut emit::Emitter,
96 90
    /// Register allocation result.
97 91
    ralloc: *regalloc::AllocResult,
98 -
    /// Hash-indexed data symbol map.
99 -
    dataSymMap: *data::DataSymMap,
100 92
    /// Total stack frame size.
101 93
    frameSize: i32,
102 94
    /// Running offset into the reserve region of the frame.
103 95
    /// Tracks current position within the pre-allocated reserve slots.
104 96
    reserveOffset: i32,
161 153
        return scratch;
162 154
    }
163 155
    return getReg(s, ssa);
164 156
}
165 157
166 -
/// Look up symbol address in data map.
167 -
fn lookupDataSym(s: *Selector, name: *[u8]) -> u32 throws (SelectorError) {
168 -
    let addr = data::lookupAddr(s.dataSymMap, name) else {
169 -
        throw SelectorError::Internal;
170 -
    };
171 -
    return addr;
172 -
}
173 -
174 158
/// Resolve an IL value to the physical register holding it.
175 159
/// For non-spilled register values, returns the physical register directly.
176 160
/// For immediates, symbols, and spilled registers, materializes into `scratch`.
177 161
fn resolveVal(s: *mut Selector, scratch: gen::Reg, val: il::Val) -> gen::Reg {
178 162
    match val {
185 169
            }
186 170
            emit::loadImm(s.e, scratch, imm);
187 171
            return scratch;
188 172
        },
189 173
        case il::Val::DataSym(name) => {
190 -
            let addr = try lookupDataSym(s, name) catch {
191 -
                panic "resolveVal: data symbol not found";
192 -
            };
193 -
            emit::loadImm(s.e, scratch, addr as i64);
174 +
            emit::recordDataAddrLoad(s.e, name, scratch);
194 175
            return scratch;
195 176
        },
196 177
        case il::Val::FnAddr(name) => {
197 178
            emit::recordAddrLoad(s.e, name, scratch);
198 179
            return scratch;
304 285
}
305 286
306 287
/// Select instructions for a function.
307 288
export fn selectFn(
308 289
    e: *mut emit::Emitter,
309 -
    dataSymMap: *data::DataSymMap,
310 290
    ralloc: *regalloc::AllocResult,
311 291
    func: *il::Fn
312 292
) {
313 293
    // Reset block offsets for this function.
314 294
    labels::resetBlocks(&mut e.labels);
323 303
        isLeaf,
324 304
        reserveInfo.isDynamic
325 305
    );
326 306
    // Synthetic block indices start after real blocks and the epilogue block.
327 307
    let mut s = Selector {
328 -
        e, ralloc, dataSymMap, frameSize: frame.totalSize,
308 +
        e, ralloc, frameSize: frame.totalSize,
329 309
        reserveOffset: 0, pendingSpill: nil,
330 310
        nextSynthBlock: func.blocks.len + 1,
331 311
        isDynamic: frame.isDynamic,
332 312
    };
333 313
    // Record function name for printing.
lib/std/lang/gen/data.rad +8 -8
34 34
/// Lay out data symbols for a single section.
35 35
/// Initialized data is placed first, then uninitialized, so that only
36 36
/// initialized data needs to be written to the output file.
37 37
/// Returns the updated offset past all placed symbols.
38 38
export fn layoutSection(
39 -
    program: *il::Program,
39 +
    items: *[il::Data],
40 40
    syms: *mut [DataSym],
41 41
    count: *mut u32,
42 42
    base: u32,
43 43
    readOnly: bool
44 44
) -> u32 {
45 45
    let mut offset: u32 = 0;
46 46
47 47
    // Initialized data first.
48 -
    for i in 0..program.data.len {
49 -
        let data = &program.data[i];
48 +
    for i in 0..items.len {
49 +
        let data = &items[i];
50 50
        if data.readOnly == readOnly and not data.isUndefined {
51 51
            set offset = mem::alignUp(offset, data.alignment);
52 52
            set syms[*count] = DataSym { name: data.name, addr: base + offset };
53 53
            set *count += 1;
54 54
            set offset += data.size;
55 55
        }
56 56
    }
57 57
    // Uninitialized data after.
58 -
    for i in 0..program.data.len {
59 -
        let data = &program.data[i];
58 +
    for i in 0..items.len {
59 +
        let data = &items[i];
60 60
        if data.readOnly == readOnly and data.isUndefined {
61 61
            set offset = mem::alignUp(offset, data.alignment);
62 62
            set syms[*count] = DataSym { name: data.name, addr: base + offset };
63 63
            set *count += 1;
64 64
            set offset += data.size;
69 69
70 70
/// Emit data bytes for a single section (read-only or read-write) into `buf`.
71 71
/// Iterates initialized data in the IL program, serializing each data item.
72 72
/// Returns the total number of bytes written.
73 73
export fn emitSection(
74 -
    program: *il::Program,
74 +
    items: *[il::Data],
75 75
    dataSymMap: *DataSymMap,
76 76
    fnLabels: *labels::Labels,
77 77
    codeBase: u32,
78 78
    buf: *mut [u8],
79 79
    readOnly: bool
80 80
) -> u32 {
81 81
    let mut offset: u32 = 0;
82 82
83 -
    for i in 0..program.data.len {
84 -
        let data = &program.data[i];
83 +
    for i in 0..items.len {
84 +
        let data = &items[i];
85 85
        if data.readOnly == readOnly and not data.isUndefined {
86 86
            set offset = mem::alignUp(offset, data.alignment);
87 87
            assert offset + data.size <= buf.len, "emitSection: buffer overflow";
88 88
            for j in 0..data.values.len {
89 89
                let v = &data.values[j];
lib/std/lang/il.rad +0 -2
389 389
export record Program {
390 390
    /// Global data.
391 391
    data: *[Data],
392 392
    /// Functions.
393 393
    fns: *[*Fn],
394 -
    /// Index of entry point function, if any.
395 -
    defaultFnIdx: ?u32,
396 394
}
397 395
398 396
///////////////////////
399 397
// Utility Functions //
400 398
///////////////////////
lib/std/lang/lower.rad +88 -37
253 253
    debug: bool,
254 254
    /// Whether to lower `@test` functions.
255 255
    buildTest: bool,
256 256
}
257 257
258 +
/// Role of a lowered function in the final program.
259 +
export union FnRole {
260 +
    /// Regular function.
261 +
    Normal,
262 +
    /// Program entry function marked with `@default`.
263 +
    Default,
264 +
}
265 +
266 +
/// Function sink used by lowerers that consume functions as they are produced.
267 +
export record FnSink {
268 +
    /// Opaque context passed to the sink callback.
269 +
    ctx: *mut opaque,
270 +
    /// Callback invoked for each lowered function.
271 +
    emitFn: fn(*mut opaque, *il::Fn, FnRole),
272 +
}
273 +
274 +
/// Destination for functions produced by the lowerer.
275 +
export union FnOutput {
276 +
    /// Store lowered functions in the provided slice.
277 +
    Accumulate(*mut [*il::Fn]),
278 +
    /// Send lowered functions to an external consumer immediately.
279 +
    Stream(FnSink),
280 +
}
281 +
258 282
/// Module-level lowering context. Shared across all function lowerings.
259 283
/// Holds global state like the data section (strings, constants) and provides
260 284
/// access to the resolver for type queries.
261 285
export record Lowerer {
262 -
    /// Arena for IL allocations. All IL nodes are allocated here.
286 +
    /// Arena for persistent lowering state, including data and symbol names.
263 287
    arena: *mut alloc::Arena,
288 +
    /// Arena for allocations owned by the function currently being lowered.
289 +
    fnArena: *mut alloc::Arena,
264 290
    /// Allocator backed by the arena.
265 291
    allocator: alloc::Allocator,
266 292
    /// Resolver for type information. Used to query types, symbols, and
267 293
    /// compile-time constant values during lowering.
268 294
    resolver: *resolver::Resolver,
273 299
    /// Current module being lowered.
274 300
    currentMod: ?u16,
275 301
    /// Global data items (string literals, constants, static arrays).
276 302
    /// These become the data sections in the final binary.
277 303
    data: *mut [il::Data],
278 -
    /// Lowered functions.
279 -
    fns: *mut [*il::Fn],
304 +
    /// Destination for lowered functions.
305 +
    output: FnOutput,
280 306
    /// Map of function symbols to qualified names.
281 307
    fnSyms: *mut [FnSymEntry],
282 308
    /// Global error type tag table. Maps nominal types to unique tags.
283 309
    errTags: *mut [ErrTagEntry],
284 310
    /// Next error tag to assign (starts at 1; 0 = success).
327 353
    self.errTags.append(ErrTagEntry { ty: errType, tag }, self.allocator);
328 354
329 355
    return tag;
330 356
}
331 357
358 +
/// Emit one function to the active output.
359 +
fn emitFunction(self: *mut Lowerer, func: *il::Fn, role: FnRole) {
360 +
    match self.output {
361 +
        case FnOutput::Accumulate(accumulated) => {
362 +
            let mut fns = accumulated;
363 +
            fns.append(func, self.allocator);
364 +
            set self.output = FnOutput::Accumulate(fns);
365 +
        }
366 +
        case FnOutput::Stream(sink) => {
367 +
            sink.emitFn(sink.ctx, func, role);
368 +
        }
369 +
    }
370 +
}
371 +
372 +
/// Get the function role for a top-level function declaration.
373 +
fn lowerFnRole(isRoot: bool, attrs: ?ast::Attributes) -> FnRole {
374 +
    if isRoot and checkAttr(attrs, ast::Attribute::Default) {
375 +
        return FnRole::Default;
376 +
    }
377 +
    return FnRole::Normal;
378 +
}
379 +
332 380
/// Builder for accumulating data values during constant lowering.
333 381
record DataValueBuilder {
334 382
    allocator: alloc::Allocator,
335 383
    values: *mut [il::DataValue],
336 384
    /// Whether all values pushed are undefined.
680 728
    pkgName: *[u8],
681 729
    arena: *mut alloc::Arena
682 730
) -> il::Program throws (LowerError) {
683 731
    let mut low = Lowerer {
684 732
        arena: arena,
733 +
        fnArena: arena,
685 734
        allocator: alloc::arenaAllocator(arena),
686 735
        resolver: res,
687 736
        moduleGraph: nil,
688 737
        pkgName,
689 738
        currentMod: nil,
690 739
        data: &mut [],
691 -
        fns: &mut [],
740 +
        output: FnOutput::Accumulate(&mut []),
692 741
        fnSyms: &mut [],
693 742
        errTags: &mut [],
694 743
        errTagCounter: 1,
695 744
        options: LowerOptions { debug: false, buildTest: false },
696 745
    };
697 -
    let defaultFnIdx = try lowerDecls(&mut low, root, true);
746 +
    try lowerDecls(&mut low, root, true);
698 747
699 -
    return il::Program {
700 -
        data: &low.data[..],
701 -
        fns: &low.fns[..],
702 -
        defaultFnIdx,
703 -
    };
748 +
    return finalize(&low);
704 749
}
705 750
706 751
/////////////////////////////////
707 752
// Multi-Module Lowering API   //
708 753
/////////////////////////////////
711 756
export fn lowerer(
712 757
    res: *resolver::Resolver,
713 758
    graph: *module::ModuleGraph,
714 759
    pkgName: *[u8],
715 760
    arena: *mut alloc::Arena,
761 +
    fnArena: *mut alloc::Arena,
716 762
    options: LowerOptions
717 763
) -> Lowerer {
718 764
    return Lowerer {
719 765
        arena,
766 +
        fnArena,
720 767
        allocator: alloc::arenaAllocator(arena),
721 768
        resolver: res,
722 769
        moduleGraph: graph,
723 770
        pkgName,
724 771
        currentMod: nil,
725 772
        data: &mut [],
726 -
        fns: &mut [],
773 +
        output: FnOutput::Accumulate(&mut []),
727 774
        fnSyms: &mut [],
728 775
        errTags: &mut [],
729 776
        errTagCounter: 1,
730 777
        options,
731 778
    };
732 779
}
733 780
734 -
/// Lower a module's AST into the lowerer accumulator.
781 +
/// Lower a module's AST into the lowerer.
735 782
/// Call this for each module in the package, then use `finalize` to get the program.
736 783
export fn lowerModule(
737 784
    low: *mut Lowerer,
738 785
    moduleId: u16,
739 786
    root: *ast::Node,
740 787
    isRoot: bool
741 -
) -> ?u32 throws (LowerError) {
788 +
) throws (LowerError) {
742 789
    set low.currentMod = moduleId;
743 -
    return try lowerDecls(low, root, isRoot);
790 +
    try lowerDecls(low, root, isRoot);
744 791
}
745 792
746 793
/// Lower all top-level declarations in a block.
747 -
fn lowerDecls(low: *mut Lowerer, root: *ast::Node, isRoot: bool) -> ?u32 throws (LowerError) {
794 +
fn lowerDecls(low: *mut Lowerer, root: *ast::Node, isRoot: bool) throws (LowerError) {
748 795
    let case ast::NodeValue::Block(block) = root.value else {
749 796
        throw LowerError::ExpectedBlock(root);
750 797
    };
751 798
    let stmtsList = block.statements;
752 -
    let mut defaultFnIdx: ?u32 = nil;
753 799
754 800
    for node in stmtsList {
755 801
        match node.value {
756 802
            case ast::NodeValue::FnDecl(decl) => {
757 803
                if let f = try lowerFnDecl(low, node, decl) {
758 -
                    if isRoot and checkAttr(decl.attrs, ast::Attribute::Default) {
759 -
                        set defaultFnIdx = low.fns.len;
760 -
                    }
761 -
                    low.fns.append(f, low.allocator);
804 +
                    let role = lowerFnRole(isRoot, decl.attrs);
805 +
                    emitFunction(low, f, role);
762 806
                }
763 807
            }
764 808
            case ast::NodeValue::ConstDecl(decl) => {
765 809
                try lowerDataDecl(low, node, decl.value, true);
766 810
            }
770 814
            case ast::NodeValue::InstanceDecl { traitName, targetType, methods } => {
771 815
                try lowerInstanceDecl(low, node, traitName, targetType, methods);
772 816
            }
773 817
            case ast::NodeValue::MethodDecl { name, receiverName, receiverType, sig, body, .. } => {
774 818
                if let f = try lowerMethodDecl(low, node, name, receiverName, sig, body) {
775 -
                    low.fns.append(f, low.allocator);
819 +
                    emitFunction(low, f, FnRole::Normal);
776 820
                }
777 821
            }
778 822
            else => {},
779 823
        }
780 824
    }
781 -
    return defaultFnIdx;
782 825
}
783 826
784 827
/// Finalize lowering and return the unified IL program.
785 -
export fn finalize(low: *Lowerer, defaultFnIdx: ?u32) -> il::Program {
828 +
export fn finalize(low: *Lowerer) -> il::Program {
829 +
    let mut fns: *mut [*il::Fn] = undefined;
830 +
    match low.output {
831 +
        case FnOutput::Accumulate(accumulated) => {
832 +
            set fns = accumulated;
833 +
        }
834 +
        case FnOutput::Stream(_) => {
835 +
            panic "finalize: cannot finalize streaming lowerer";
836 +
        }
837 +
    }
786 838
    return il::Program {
787 839
        data: &low.data[..],
788 -
        fns: &low.fns[..],
789 -
        defaultFnIdx,
840 +
        fns: &fns[..],
790 841
    };
791 842
}
792 843
793 844
/////////////////////////////////
794 845
// Qualified Name Construction //
857 908
    self: *mut Lowerer,
858 909
    node: *ast::Node,
859 910
    fnType: *resolver::FnType,
860 911
    qualName: *[u8]
861 912
) -> FnLowerer {
862 -
    let loopStack = try! alloc::allocSlice(self.arena, @sizeOf(LoopCtx), @alignOf(LoopCtx), MAX_LOOP_DEPTH) as *mut [LoopCtx];
913 +
    let loopStack = try! alloc::allocSlice(self.fnArena, @sizeOf(LoopCtx), @alignOf(LoopCtx), MAX_LOOP_DEPTH) as *mut [LoopCtx];
863 914
864 915
    let mut fnLow = FnLowerer {
865 916
        low: self,
866 -
        allocator: alloc::arenaAllocator(self.arena),
917 +
        allocator: alloc::arenaAllocator(self.fnArena),
867 918
        fnType: fnType,
868 919
        fnName: qualName,
869 920
        vars: &mut [],
870 921
        params: &mut [],
871 922
        blockData: &mut [],
926 977
    // as the first argument; the callee writes the return value into it.
927 978
    if requiresReturnParam(fnType) and not isExtern {
928 979
        set fnLow.returnReg = nextReg(&mut fnLow);
929 980
    }
930 981
    let lowParams = try lowerParams(&mut fnLow, *fnType, decl.sig.params, nil);
931 -
    let func = try! alloc::alloc(self.arena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *mut il::Fn;
982 +
    let func = try! alloc::alloc(self.fnArena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *mut il::Fn;
932 983
933 984
    set *func = il::Fn {
934 985
        name: qualName,
935 986
        params: lowParams,
936 987
        returnType: undefined,
1030 1081
            throw LowerError::ExpectedIdentifier;
1031 1082
        };
1032 1083
        let qualName = instanceMethodName(self, nil, typeName, mName);
1033 1084
        let func = try lowerMethod(self, methodNode, qualName, receiverName, sig, body)
1034 1085
            else continue;
1035 -
        self.fns.append(func, self.allocator);
1086 +
        emitFunction(self, func, FnRole::Normal);
1036 1087
1037 1088
        let method = resolver::findTraitMethod(traitInfo, mName)
1038 1089
            else panic "lowerInstanceDecl: method not found in trait";
1039 1090
1040 1091
        set methodNames[method.index] = qualName;
1092 1143
    let mut fnLow = fnLowerer(self, node, fnType, qualName);
1093 1144
    if requiresReturnParam(fnType) {
1094 1145
        set fnLow.returnReg = nextReg(&mut fnLow);
1095 1146
    }
1096 1147
    let lowParams = try lowerParams(&mut fnLow, *fnType, sig.params, receiverName);
1097 -
    let func = try! alloc::alloc(self.arena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *mut il::Fn;
1148 +
    let func = try! alloc::alloc(self.fnArena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *mut il::Fn;
1098 1149
1099 1150
    set *func = il::Fn {
1100 1151
        name: qualName,
1101 1152
        params: lowParams,
1102 1153
        returnType: ilType(self, *fnType.returnType),
1154 1205
/// This ensures unique labels like `@then0`, `@then1`, etc.
1155 1206
fn labelWithSuffix(self: *mut FnLowerer, base: *[u8], suffix: u32) -> *[u8] throws (LowerError) {
1156 1207
    let mut digits: [u8; fmt::U32_STR_LEN] = undefined;
1157 1208
    let suffixText = fmt::formatU32(suffix, &mut digits[..]);
1158 1209
    let totalLen = base.len + suffixText.len;
1159 -
    let buf = try! alloc::allocSlice(self.low.arena, 1, 1, totalLen) as *mut [u8];
1210 +
    let buf = try! alloc::allocSlice(self.low.fnArena, 1, 1, totalLen) as *mut [u8];
1160 1211
1161 1212
    try! mem::copy(&mut buf[..base.len], base);
1162 1213
    try! mem::copy(&mut buf[base.len..totalLen], suffixText);
1163 1214
1164 1215
    return &buf[..totalLen];
1923 1974
/// be switched to via [`switchToBlock`] before instructions can be emitted.
1924 1975
fn createBlock(self: *mut FnLowerer, labelBase: *[u8]) -> BlockId throws (LowerError) {
1925 1976
    let label = try nextLabel(self, labelBase);
1926 1977
    let id = BlockId(self.blockData.len);
1927 1978
    let varCount = self.fnType.localCount;
1928 -
    let vars = try! alloc::allocSlice(self.low.arena, @sizeOf(?il::Val), @alignOf(?il::Val), varCount) as *mut [?il::Val];
1979 +
    let vars = try! alloc::allocSlice(self.low.fnArena, @sizeOf(?il::Val), @alignOf(?il::Val), varCount) as *mut [?il::Val];
1929 1980
1930 1981
    for i in 0..varCount {
1931 1982
        set vars[i] = nil;
1932 1983
    }
1933 1984
    self.blockData.append(BlockData {
2502 2553
}
2503 2554
2504 2555
/// Finalize all blocks and return the block array.
2505 2556
fn finalizeBlocks(self: *mut FnLowerer) -> *[il::Block] throws (LowerError) {
2506 2557
    let blocks = try! alloc::allocSlice(
2507 -
        self.low.arena, @sizeOf(il::Block), @alignOf(il::Block), self.blockData.len
2558 +
        self.low.fnArena, @sizeOf(il::Block), @alignOf(il::Block), self.blockData.len
2508 2559
    ) as *mut [il::Block];
2509 2560
2510 2561
    for i in 0..self.blockData.len {
2511 2562
        let data = &self.blockData[i];
2512 2563
2564 2615
    return block;
2565 2616
}
2566 2617
2567 2618
/// Allocate a slice of values in the lowering arena.
2568 2619
fn allocVals(self: *mut FnLowerer, len: u32) -> *mut [il::Val] throws (LowerError) {
2569 -
    return try! alloc::allocSlice(self.low.arena, @sizeOf(il::Val), @alignOf(il::Val), len) as *mut [il::Val];
2620 +
    return try! alloc::allocSlice(self.low.fnArena, @sizeOf(il::Val), @alignOf(il::Val), len) as *mut [il::Val];
2570 2621
}
2571 2622
2572 2623
/// Allocate a single-value slice in the lowering arena.
2573 2624
fn allocVal(self: *mut FnLowerer, val: il::Val) -> *mut [il::Val] throws (LowerError) {
2574 2625
    let args = try allocVals(self, 1);
2957 3008
fn growArgs(self: *mut FnLowerer, args: *mut [il::Val], capacity: u32) -> *mut [il::Val] {
2958 3009
    if args.len >= capacity {
2959 3010
        return args;
2960 3011
    }
2961 3012
    let newArgs = try! alloc::allocSlice(
2962 -
        self.low.arena, @sizeOf(il::Val), @alignOf(il::Val), capacity
3013 +
        self.low.fnArena, @sizeOf(il::Val), @alignOf(il::Val), capacity
2963 3014
    ) as *mut [il::Val];
2964 3015
2965 3016
    for arg, i in args {
2966 3017
        set newArgs[i] = arg;
2967 3018
    }
2996 3047
        return &[];
2997 3048
    }
2998 3049
    assert fnType.paramTypes.len as u32 <= resolver::MAX_FN_PARAMS;
2999 3050
3000 3051
    let params = try! alloc::allocSlice(
3001 -
        self.low.arena, @sizeOf(il::Param), @alignOf(il::Param), totalLen
3052 +
        self.low.fnArena, @sizeOf(il::Param), @alignOf(il::Param), totalLen
3002 3053
    ) as *mut [il::Param];
3003 3054
3004 3055
    if let reg = self.returnReg {
3005 3056
        set params[0] = il::Param { value: reg, type: il::Type::W64 };
3006 3057
    }
4491 4542
4492 4543
    // Create comparison blocks for each non-void variant and build switch cases.
4493 4544
    // Void variants jump directly to merge with `true`.
4494 4545
    let trueArgs = try allocVal(self, il::Val::Imm(1));
4495 4546
    let cases = try! alloc::allocSlice(
4496 -
        self.low.arena, @sizeOf(il::SwitchCase), @alignOf(il::SwitchCase), unionInfo.variants.len as u32
4547 +
        self.low.fnArena, @sizeOf(il::SwitchCase), @alignOf(il::SwitchCase), unionInfo.variants.len as u32
4497 4548
    ) as *mut [il::SwitchCase];
4498 4549
4499 4550
    let mut caseBlocks: [?BlockId; resolver::MAX_UNION_VARIANTS] = undefined;
4500 4551
    for variant, i in unionInfo.variants {
4501 4552
        if variant.valueType == resolver::Type::Void {