module: (WIP) Associate updates with authority

413073d6c84f35132b0beba62a1a6121ee9d112e1b21c9990dcc42ffb541c512
Store module update payloads in permission-associated cells and keep immutable module identities separate from their mutable state. The module graph retains the external authority used by its update table.

This checkpoint does not compile yet: shared update access still needs a matching authority binding derived from the graph-owned permission reference.

Next, preserve that permission identity across graph access, pass the focused module and compiler checks, then reduce any remaining lifetime plumbing before running the full suite.

Assisted-by: Codex:gpt-5.6-sol
Alexis Sellier committed ago 1 parent d1a3f60b
compiler/radiance.rad +141 -127
193 193
    /// Whether to emit debug info (.debug file).
194 194
    debug: bool,
195 195
}
196 196
197 197
/// Compilation context.
198 -
record CompileContext {
198 +
record CompileContext: 'permission {
199 199
    /// Array of packages to compile.
200 200
    packages: [package::Package; MAX_PACKAGES],
201 201
    /// Driver inputs for each package slot.
202 202
    inputs: [PackageInput; MAX_PACKAGES],
203 203
    /// Number of packages.
204 204
    packageCount: u32,
205 205
    /// Index of entry package.
206 206
    entryPkgIdx: ?u32,
207 207
    /// Global module graph shared by all packages.
208 -
    graph: module::ModuleGraph,
208 +
    graph: module::ModuleGraph 'permission,
209 209
    /// Resolver configuration.
210 210
    config: resolver::Config,
211 211
    /// What to dump during compilation.
212 212
    dump: Dump,
213 213
    /// Output path for binary.
291 291
        asmPathCount: 0,
292 292
    };
293 293
}
294 294
295 295
/// Register, load, and parse `path` within `pkg`.
296 -
unsafe fn processModule(
296 +
unsafe fn processModule 'permission (
297 297
    pkg: *unsafe mut package::Package,
298 -
    graph: &mut module::ModuleGraph,
298 +
    graph: &mut module::ModuleGraph 'permission,
299 299
    path: *[u8],
300 300
    nodeArena: &mut ast::NodeArena,
301 301
    sourceArena: &mut alloc::Arena
302 302
) throws (Error) {
303 303
    pkgLog(pkg, &["parsing", "(", path, ")", ".."]);
486 486
        debug: debugEnabled,
487 487
    };
488 488
}
489 489
490 490
/// Parse CLI arguments and return compilation context.
491 -
unsafe fn processCommand(
491 +
unsafe fn processCommand 'permission (
492 492
    args: *[*[u8]],
493 -
    arena: &mut ast::NodeArena
494 -
) -> CompileContext throws (Error) {
493 +
    arena: &mut ast::NodeArena,
494 +
    permission: &'permission mut module::Permission
495 +
) -> CompileContext 'permission throws (Error) {
495 496
    let mut inputs = [packageInput(""); MAX_PACKAGES];
496 497
    let command = try parseCommand(args, &mut inputs[..]);
497 -
    let graph = module::moduleGraph(&mut MODULE_ENTRIES[..], arena);
498 -
    let mut ctx = CompileContext {
498 +
    let graph = module::moduleGraph(&mut MODULE_ENTRIES[..], arena, permission);
499 +
    let mut ctx = CompileContext 'permission {
499 500
        packages: undefined,
500 501
        inputs,
501 502
        packageCount: command.packageCount,
502 503
        entryPkgIdx: command.entryPkgIdx,
503 504
        graph,
520 521
    }
521 522
    return ctx;
522 523
}
523 524
524 525
/// Get the entry package from the context.
525 -
fn getEntryPackage(ctx: &CompileContext) -> package::Package throws (Error) {
526 +
fn getEntryPackage 'permission (ctx: &CompileContext 'permission) -> package::Package throws (Error) {
526 527
    let entryIdx = ctx.entryPkgIdx else {
527 528
        throw error(&["no entry package specified"]);
528 529
    };
529 530
    return ctx.packages[entryIdx];
530 531
}
531 532
532 533
/// Return the startup assembly path for the entry package, if one was supplied.
533 -
fn getEntryStartupPath(ctx: &CompileContext) -> ?*[u8] {
534 +
fn getEntryStartupPath 'permission (ctx: &CompileContext 'permission) -> ?*[u8] {
534 535
    let entryIdx = ctx.entryPkgIdx else {
535 536
        panic "getEntryStartupPath: no entry package";
536 537
    };
537 538
    return ctx.inputs[entryIdx].startupPath;
538 539
}
539 540
540 541
/// Get root module info from a package.
541 -
fn getRootModule(pkg: &package::Package, graph: &module::ModuleGraph) -> RootModule throws (Error) {
542 +
fn getRootModule 'permission (pkg: &package::Package, graph: &module::ModuleGraph 'permission) -> RootModule throws (Error) {
542 543
    let rootId = pkg.rootModuleId else {
543 544
        throw error(&["no root module found"]);
544 545
    };
545 546
    let rootEntry = module::get(graph, rootId) else {
546 547
        throw error(&["root module entry not found"]);
547 548
    };
548 -
    let rootAst = module::astFor(rootEntry) else {
549 +
    let rootAst = module::astFor(graph, rootEntry) else {
549 550
        throw error(&["root module has no AST"]);
550 551
    };
551 552
    return RootModule { entry: rootEntry, ast: rootAst };
552 553
}
553 554
554 555
/// Dump the module graph.
555 -
unsafe fn dumpGraph(ctx: &CompileContext) {
556 +
unsafe fn dumpGraph 'permission (ctx: &CompileContext 'permission) {
556 557
    let mut arena = alloc::new(&mut MAIN_ARENA[..]);
557 558
    module::printer::printGraph(&ctx.graph, &mut arena);
558 559
}
559 560
560 561
/// Dump the parsed AST.
561 -
unsafe fn dumpAst(ctx: &CompileContext) throws (Error) {
562 +
unsafe fn dumpAst 'permission (ctx: &CompileContext 'permission) throws (Error) {
562 563
    let pkg = try getEntryPackage(ctx);
563 564
    let root = try getRootModule(&pkg, &ctx.graph);
564 565
    let mut arena = alloc::new(&mut MAIN_ARENA[..]);
565 566
566 567
    ast::printer::printTree(root.ast, &mut arena);
567 568
}
568 569
569 570
/// Lower all packages into a single IL program.
570 571
/// Dependencies are lowered first, then the entry package.
571 -
unsafe fn lowerAllPackages 'arena (
572 -
    ctx: &CompileContext,
572 +
unsafe fn lowerAllPackages 'arena 'permission (
573 +
    ctx: &CompileContext 'permission,
573 574
    res: *unsafe mut resolver::Resolver 'arena
574 575
) -> il::Program throws (Error) {
575 576
    let entryIdx = ctx.entryPkgIdx else {
576 577
        panic "lowerAllPackages: no entry package";
577 578
    };
578 579
    let entryPkg = &ctx.packages[entryIdx];
579 580
580 581
    // Create the lowerer accumulator using entry package's name.
581 582
    let options = lower::LowerOptions { debug: ctx.debug, buildTest: ctx.config.buildTest };
582 583
    let arena = (&mut *res.arena) as *unsafe mut alloc::Arena;
583 -
    let resolved: 'phase = &*res, graph = &ctx.graph where 'arena: 'phase in {
584 -
        let mut low = lower::lowerer(
585 -
            resolved, graph, entryPkg.name, arena, options
586 -
        );
584 +
    let resolved: 'phase = &*res where 'arena: 'phase in {
585 +
        let mut low = lower::lowerer(resolved, entryPkg.name, arena, options);
587 586
        try lowerAllPackagesInto(ctx, &mut low, &mut *arena);
588 587
589 588
        // Finalize and return the unified program.
590 589
        return lower::finalize(low);
591 590
    }
592 591
}
593 592
594 593
/// Lower all packages into an existing lowerer.
595 -
unsafe fn lowerAllPackagesInto 'arena 'phase (
596 -
    ctx: &CompileContext,
594 +
unsafe fn lowerAllPackagesInto 'arena 'phase 'permission (
595 +
    ctx: &CompileContext 'permission,
597 596
    low: &mut lower::Lowerer 'arena 'phase,
598 597
    functionArena: &mut alloc::Arena
599 598
) throws (Error) where 'arena: 'phase {
600 599
    let entryIdx = ctx.entryPkgIdx else {
601 600
        panic "lowerAllPackagesInto: no entry package";
609 608
    // Lower entry package.
610 609
    try lowerPackage(ctx, low, &ctx.packages[entryIdx], true, functionArena);
611 610
}
612 611
613 612
/// Lower all modules in a package into the lowerer accumulator.
614 -
unsafe fn lowerPackage 'arena 'phase (
615 -
    ctx: &CompileContext,
613 +
unsafe fn lowerPackage 'arena 'phase 'permission (
614 +
    ctx: &CompileContext 'permission,
616 615
    low: &mut lower::Lowerer 'arena 'phase,
617 616
    pkg: &package::Package,
618 617
    isEntry: bool,
619 618
    functionArena: &mut alloc::Arena
620 619
) throws (Error) where 'arena: 'phase {
623 622
    };
624 623
    // Set lowerer's package context for qualified name generation.
625 624
    // TODO: We shouldn't have to call this manually.
626 625
    lower::setPackage(low, pkg.name);
627 626
628 -
    try lowerModuleTreeInto(ctx, low, &ctx.graph, rootId, isEntry, pkg, functionArena);
627 +
    try lowerModuleTreeInto(low, &ctx.graph, rootId, isEntry, pkg, functionArena);
629 628
}
630 629
631 630
/// Recursively lower a module and all its children into the accumulator.
632 -
unsafe fn lowerModuleTreeInto 'arena 'phase (
633 -
    ctx: &CompileContext,
631 +
unsafe fn lowerModuleTreeInto 'arena 'phase 'permission (
634 632
    low: &mut lower::Lowerer 'arena 'phase,
635 -
    graph: &module::ModuleGraph,
633 +
    graph: &module::ModuleGraph 'permission,
636 634
    modId: u16,
637 635
    isRoot: bool,
638 636
    pkg: &package::Package,
639 637
    functionArena: &mut alloc::Arena
640 638
) throws (Error) where 'arena: 'phase {
641 639
    let entry = module::get(graph, modId) else {
642 640
        throw error(&["module entry not found"]);
643 641
    };
644 -
    let modAst = module::astFor(entry) else {
642 +
    let modAst = module::astFor(graph, entry) else {
645 643
        throw error(&["module has no AST"]);
646 644
    };
647 645
    pkgLog(pkg, &["lowering", "(", entry.filePath, ")", ".."]);
648 646
649 647
    try lower::lowerModule(low, modId, modAst, isRoot, functionArena) catch err {
653 651
        io::printError("\n");
654 652
655 653
        throw Error::Other;
656 654
    };
657 655
    // Recurse into children.
658 -
    for i in 0..module::childCount(entry) {
659 -
        let childId = module::childAt(entry, i);
660 -
        try lowerModuleTreeInto(ctx, low, graph, childId, false, pkg, functionArena);
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);
661 659
    }
662 660
}
663 661
664 662
/// Build a scope access chain: a::b::c from a slice of identifiers.
665 663
unsafe fn synthScopeAccess(arena: &mut ast::NodeArena, path: &[*[u8]]) -> *ast::Node {
690 688
691 689
    return name;
692 690
}
693 691
694 692
/// Scan a single module's AST for `@test` functions and append them to `tests`.
695 -
fn collectModuleTests(
696 -
    entry: *module::ModuleEntry,
697 -
    tests: &mut [?TestDesc],
698 -
    testCount: &mut u32
699 -
) {
700 -
    let modAst = module::astFor(entry) else {
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 {
701 695
        return;
702 696
    };
703 697
    let case ast::NodeValue::Block(block) = modAst.value else {
704 698
        return;
705 699
    };
718 712
        }
719 713
    }
720 714
}
721 715
722 716
/// Collect initialized test descriptors from one package in module order.
723 -
fn collectPackageTests(graph: &module::ModuleGraph, packageId: u16, tests: &mut [?TestDesc]) -> u32 {
717 +
fn collectPackageTests 'permission (graph: &module::ModuleGraph 'permission, packageId: u16, tests: &mut [?TestDesc]) -> u32 {
724 718
    let mut count: u32 = 0;
725 -
    for modIdx in 0..graph.entriesLen {
719 +
    for modIdx in 0..module::entryCount(graph) {
726 720
        if let entry = module::get(graph, modIdx as u16) {
727 721
            if entry.packageId == packageId {
728 -
                collectModuleTests(entry, tests, &mut count);
722 +
                collectModuleTests(graph, entry, tests, &mut count);
729 723
            }
730 724
        }
731 725
    }
732 726
    return count;
733 727
}
773 767
///     ]);
774 768
/// }
775 769
/// ```
776 770
///
777 771
/// Uses `#`-prefixed names to avoid conflicts with user code.
778 -
unsafe fn generateTestRunner(
779 -
    ctx: *unsafe mut CompileContext,
780 -
    arena: &mut ast::NodeArena
781 -
) throws (Error) {
772 +
unsafe fn generateTestRunner 'permission (ctx: *unsafe mut CompileContext 'permission, arena: &mut ast::NodeArena) throws (Error) {
782 773
    let entryPkg = try getEntryPackage(ctx);
783 774
    let root = try getRootModule(&entryPkg, &ctx.graph);
784 775
785 776
    // Collect test functions from the entry package's modules.
786 777
    let mut tests: [?TestDesc; MAX_TESTS] = [nil; MAX_TESTS];
921 912
}
922 913
923 914
/// Serialize debug entries and write the `.debug` file.
924 915
/// Resolves module IDs to file paths via the module graph.
925 916
/// Format per entry is `{pc: u32,  offset: u32, filePath: [u8], NULL}`.
926 -
fn writeDebugInfo(
917 +
fn writeDebugInfo 'permission (
927 918
    entries: &[types::DebugEntry],
928 -
    graph: &module::ModuleGraph,
919 +
    graph: &module::ModuleGraph 'permission,
929 920
    basePath: *[u8],
930 921
    buf: &mut [u8]
931 922
) throws (Error) {
932 923
    if entries.len == 0 {
933 924
        return;
953 944
    }
954 945
    try writeDataWithExt(&buf[..pos], basePath, DEBUG_EXT);
955 946
}
956 947
957 948
/// Run the resolver on the parsed modules.
958 -
unsafe fn runResolver 'arena (ctx: &CompileContext, mainArena: &'arena mut alloc::Arena, nodeCount: u32) -> resolver::Resolver 'arena throws (Error) {
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) {
959 954
    let entryPkg = try getEntryPackage(ctx);
960 955
961 956
    pkgLog(&entryPkg, &["resolving", ".."]);
962 957
963 958
    let nodeDataSize = nodeCount * @sizeOf(resolver::NodeData);
986 981
987 982
    // Resolve all packages.
988 983
    // TODO: Fix this error printing dance.
989 984
    let diags = try resolver::resolve(&mut res, &ctx.graph, &resolverPkgs[..resolverPackageCount]) catch {
990 985
        let diags = resolver::diagnostics(&mut res);
991 -
        resolver::printer::printDiagnostics(&diags, &res);
986 +
        resolver::printer::printDiagnostics(&diags, &res, &ctx.graph);
992 987
        throw Error::Other;
993 988
    };
994 989
    if not resolver::success(&diags) {
995 -
        resolver::printer::printDiagnostics(&diags, &res);
990 +
        resolver::printer::printDiagnostics(&diags, &res, &ctx.graph);
996 991
        let mut countBuf: [u8; 10] = undefined;
997 992
        let start = fmt::formatU32(diags.errors.len, &mut countBuf[..]);
998 993
        io::printError("radiance: failed: ");
999 994
        io::printError(&countBuf[start..]);
1000 995
        io::printError(" errors\n");
1049 1044
1050 1045
    rv64::addAssembly(generator, program);
1051 1046
}
1052 1047
1053 1048
/// Assemble all inputs collected in the package inputs.
1054 -
unsafe fn assembleAsmInputs(
1055 -
    ctx: &CompileContext,
1049 +
unsafe fn assembleAsmInputs 'permission (
1050 +
    ctx: &CompileContext 'permission,
1056 1051
    generator: &mut rv64::Generator,
1057 1052
    asmDataLen: &mut u32,
1058 1053
    arena: &mut alloc::Arena
1059 1054
) -> *[u8] throws (Error) {
1060 1055
    for i in 0..ctx.packageCount {
1070 1065
    }
1071 1066
    return &ASM_RO_DATA_BUF[..*asmDataLen];
1072 1067
}
1073 1068
1074 1069
/// Generate dependency packages before the entry package.
1075 -
unsafe fn generateAllPackagesInto 'arena 'phase (
1076 -
    ctx: &CompileContext, low: &mut lower::Lowerer 'arena 'phase,
1077 -
    generator: &mut rv64::Generator, fnArena: &mut alloc::Arena
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
1078 1075
) throws (Error) where 'arena: 'phase {
1079 1076
    let entryIdx = ctx.entryPkgIdx else panic "generateAllPackagesInto: no entry package";
1080 1077
    for i in 0..ctx.packageCount {
1081 1078
        if i <> entryIdx {
1082 1079
            try generatePackageInto(ctx, low, &ctx.packages[i], false, generator, fnArena);
1084 1081
    }
1085 1082
    try generatePackageInto(ctx, low, &ctx.packages[entryIdx], true, generator, fnArena);
1086 1083
}
1087 1084
1088 1085
/// Generate all functions in one package.
1089 -
unsafe fn generatePackageInto 'arena 'phase (
1090 -
    ctx: &CompileContext, low: &mut lower::Lowerer 'arena 'phase,
1091 -
    pkg: &package::Package, isEntry: bool,
1092 -
    generator: &mut rv64::Generator, fnArena: &mut alloc::Arena
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 1093
) throws (Error) where 'arena: 'phase {
1094 1094
    let rootId = pkg.rootModuleId else throw error(&["no root module found"]);
1095 1095
    lower::setPackage(low, pkg.name);
1096 1096
    try generateModuleTree(ctx, low, rootId, isEntry, pkg, generator, fnArena);
1097 1097
}
1098 1098
1099 1099
/// Lower and consume each function before visiting the next declaration.
1100 -
unsafe fn generateModuleTree 'arena 'phase (
1101 -
    ctx: &CompileContext, low: &mut lower::Lowerer 'arena 'phase,
1102 -
    modId: u16, isRoot: bool, pkg: &package::Package,
1103 -
    generator: &mut rv64::Generator, fnArena: &mut alloc::Arena
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
1104 1108
) throws (Error) where 'arena: 'phase {
1105 -
    let entry = module::get(&ctx.graph, modId) else throw error(&["module entry not found"]);
1106 -
    let modAst = module::astFor(entry) else throw error(&["module has no AST"]);
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"]);
1107 1113
    pkgLog(pkg, &["lowering", "(", entry.filePath, ")", ".."]);
1108 1114
    set low.currentMod = modId;
1109 1115
    let mut cursor = try lower::moduleCursor(modAst, isRoot) catch err {
1110 1116
        io::printError("radiance: internal error during lowering: ");
1111 1117
        lower::printError(err);
1120 1126
            throw Error::Other;
1121 1127
        };
1122 1128
        let next = result else break;
1123 1129
        codegen::emit(generator, fnArena, &*next.function, next.role);
1124 1130
    }
1125 -
    for i in 0..module::childCount(entry) {
1126 -
        let childId = module::childAt(entry, i);
1131 +
    for i in 0..module::childCount(&ctx.graph, entry) {
1132 +
        let childId = module::childAt(&ctx.graph, entry, i);
1127 1133
        try generateModuleTree(ctx, low, childId, false, pkg, generator, fnArena);
1128 1134
    }
1129 1135
}
1130 1136
1131 1137
/// Lower all packages while streaming each lowered function into RV64 codegen.
1132 -
unsafe fn lowerAndGenerateAllPackages 'arena (
1133 -
    ctx: &CompileContext,
1138 +
unsafe fn lowerAndGenerateAllPackages 'arena 'permission (
1139 +
    ctx: &CompileContext 'permission,
1134 1140
    res: *unsafe mut resolver::Resolver 'arena,
1135 1141
    fnArena: &mut alloc::Arena,
1136 1142
    codegenOptions: CodegenOptions
1137 1143
) -> rv64::Program throws (Error) {
1138 1144
    let entryIdx = ctx.entryPkgIdx else {
1170 1176
            placement: rv64::image::Placement::Hosted,
1171 1177
        },
1172 1178
        emitterStorage
1173 1179
    );
1174 1180
    let arena = (&mut *res.arena) as *unsafe mut alloc::Arena;
1175 -
    let resolved: 'phase = &*res, graph = &ctx.graph where 'arena: 'phase in {
1176 -
        let mut low = lower::lowerer(
1177 -
            resolved, graph, entryPkg.name, arena, options
1178 -
        );
1181 +
    let resolved: 'phase = &*res where 'arena: 'phase in {
1182 +
        let mut low = lower::lowerer(resolved, entryPkg.name, arena, options);
1179 1183
        let mut asmDataLen: u32 = 0;
1180 1184
        if let path = startupPath {
1181 1185
            try assembleAsmModule(&mut generator, entryPkg, path, &mut asmDataLen, arena);
1182 1186
        }
1183 -
        try generateAllPackagesInto(ctx, &mut low, &mut generator, fnArena);
1187 +
        try generateAllPackagesInto(
1188 +
            ctx, &mut low, &mut generator, fnArena
1189 +
        );
1184 1190
        let asmData = try assembleAsmInputs(ctx, &mut generator, &mut asmDataLen, arena);
1185 -
1186 1191
        match generator.entryPatch {
1187 1192
            case rv64::EntryPatch::Reserved(targetName) => {
1188 1193
                if targetName == nil {
1189 1194
                    throw error(&["fatal:", "no default function found"]);
1190 1195
                }
1218 1223
    /// Default function entry.
1219 1224
    entry: ?*[u8],
1220 1225
}
1221 1226
1222 1227
/// Collect exported definitions and the default entry from a package's source modules.
1223 -
unsafe fn packageExports(
1224 -
    ctx: &CompileContext,
1228 +
unsafe fn packageExports 'permission (
1229 +
    ctx: &CompileContext 'permission,
1225 1230
    pkg: &package::Package,
1226 1231
    ilProgram: &il::Program,
1227 1232
    exports: &mut [binary::Export],
1228 1233
    arena: &mut alloc::Arena
1229 1234
) -> PackageExports throws (Error) {
1230 1235
    let mut count: u32 = 0;
1231 1236
    let mut entryName: ?*[u8] = nil;
1232 -
    for i in 0..ctx.graph.entriesLen {
1237 +
    for i in 0..module::entryCount(&ctx.graph) {
1233 1238
        let modEntry = module::get(&ctx.graph, i as u16) else continue;
1234 1239
        if modEntry.packageId <> pkg.id {
1235 1240
            continue;
1236 1241
        }
1237 -
        let root = module::astFor(modEntry) else continue;
1242 +
        let root = module::astFor(&ctx.graph, modEntry) else continue;
1238 1243
        let case ast::NodeValue::Block(block) = root.value else continue;
1239 1244
        for node in block.statements {
1240 1245
            let mut ident: ?*ast::Node = nil;
1241 1246
            let mut attrs: ?ast::Attributes = nil;
1242 1247
            let mut kind = binary::ExportKind::Function;
1312 1317
        throw error(&["cannot write binary RIL package", name]);
1313 1318
    }
1314 1319
}
1315 1320
1316 1321
/// Emit one binary RIL file per package into an existing directory.
1317 -
unsafe fn emitPackages 'arena (
1318 -
    ctx: &CompileContext,
1322 +
unsafe fn emitPackages 'arena 'permission (
1323 +
    ctx: &CompileContext 'permission,
1319 1324
    res: *unsafe mut resolver::Resolver 'arena,
1320 1325
    directory: *[u8]
1321 1326
) throws (Error) {
1322 1327
    for i in 0..ctx.packageCount {
1323 1328
        if ctx.inputs[i].asmPathCount > 0 or ctx.inputs[i].startupPath <> nil {
1361 1366
    let suffix = mem::stripPrefix(owner, name) else return false;
1362 1367
    return suffix.len > 2 and suffix[0] == ':' and suffix[1] == ':';
1363 1368
}
1364 1369
1365 1370
/// Lower, optionally dump, and optionally generate binary output.
1366 -
unsafe fn compile 'arena (
1367 -
    ctx: &CompileContext,
1371 +
unsafe fn compile 'arena 'permission (
1372 +
    ctx: &CompileContext 'permission,
1368 1373
    res: *unsafe mut resolver::Resolver 'arena,
1369 1374
    fnArena: &mut alloc::Arena
1370 1375
) throws (Error) {
1371 1376
    let entryPkg = try getEntryPackage(ctx);
1372 1377
    if let directory = ctx.rilDirectory {
1381 1386
        il::printer::printProgram(&mut out, &program);
1382 1387
        io::print("\n");
1383 1388
        return;
1384 1389
    }
1385 1390
    if ctx.dump == Dump::Asm {
1386 -
        let result = try lowerAndGenerateAllPackages(ctx, res, fnArena, CodegenOptions {
1387 -
            logPath: nil,
1388 -
            debug: false,
1389 -
            entryMode: CodegenEntryMode::None,
1390 -
        });
1391 +
        let result = try lowerAndGenerateAllPackages(
1392 +
            ctx, res, fnArena, CodegenOptions {
1393 +
                logPath: nil,
1394 +
                debug: false,
1395 +
                entryMode: CodegenEntryMode::None,
1396 +
            }
1397 +
        );
1391 1398
        printer::printCodeTo(&mut out, entryPkg.name, result.code, result.funcs);
1392 1399
        io::print("\n");
1393 1400
1394 1401
        return;
1395 1402
    }
1397 1404
    let outPath = ctx.outputPath else {
1398 1405
        try lowerAllPackages(ctx, res);
1399 1406
        return;
1400 1407
    };
1401 1408
    let startupPath = getEntryStartupPath(ctx);
1402 -
    let result = try lowerAndGenerateAllPackages(ctx, res, fnArena, CodegenOptions {
1403 -
        logPath: outPath,
1404 -
        debug: ctx.debug,
1405 -
        entryMode: CodegenEntryMode::None
1406 -
            if startupPath <> nil
1407 -
            else CodegenEntryMode::DefaultEntry,
1408 -
    });
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 +
    );
1409 1418
1410 1419
    let codeBytes = @sliceOf(result.code.ptr as *u8, result.code.len * rv64::INSTR_SIZE as u32);
1411 1420
    if not writeImage(
1412 1421
        codeBytes,
1413 1422
        &RO_DATA_BUF[..result.roDataSize],
1424 1433
    }
1425 1434
    pkgLog(&entryPkg, &["ok", "(", outPath, ")"]);
1426 1435
}
1427 1436
1428 1437
@default unsafe fn main(env: *sys::Env) -> i32 {
1429 -
    let mut arena = ast::nodeArena(&mut TEMP_ARENA[..]);
1430 -
    let mut ctx = try processCommand(env.args, &mut arena) catch {
1431 -
        return 1;
1432 -
    };
1433 -
    match ctx.dump {
1434 -
        case Dump::Ast => {
1435 -
            try dumpAst(&ctx) catch {
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 {
1436 1460
                return 1;
1437 1461
            };
1438 -
            return 0;
1439 1462
        }
1440 -
        case Dump::Graph => {
1441 -
            dumpGraph(&ctx);
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 +
            };
1442 1477
            return 0;
1443 1478
        }
1444 -
        else => {}
1445 -
    }
1446 -
    // Generate test runner if in test mode.
1447 -
    if ctx.config.buildTest {
1448 -
        try generateTestRunner(&mut ctx, &mut arena) catch {
1449 -
            return 1;
1450 -
        };
1451 -
    }
1452 -
    // Run resolution phase.
1453 -
    let mut mainArena = alloc::new(&mut MAIN_ARENA[..]);
1454 -
    let arenaRef: 'arena = &mut mainArena, context = &ctx in {
1455 -
        let mut res = try runResolver(context, arenaRef, arena.nextId) catch {
1456 -
            return 1;
1457 -
        };
1458 -
        let mut fnArena = alloc::new(&mut FN_ARENA[..]);
1459 -
1460 -
        // Lower, dump, and/or generate output.
1461 -
        try compile(context, &mut res, &mut fnArena) catch {
1462 -
            return 1;
1463 -
        };
1464 -
        return 0;
1465 1479
    }
1466 1480
}
lib/std/lang/lower.rad +6 -14
310 310
    /// Arena for persistent lowering state, including data and symbol names.
311 311
    arena: *unsafe mut alloc::Arena,
312 312
    /// Resolver for type information. Used to query types, symbols, and
313 313
    /// compile-time constant values during lowering.
314 314
    resolver: &'phase resolver::Resolver 'arena,
315 -
    /// Module graph for cross-module symbol resolution.
316 -
    moduleGraph: ?&'phase module::ModuleGraph,
317 315
    /// Package name for qualified symbol names.
318 316
    pkgName: *[u8],
319 317
    /// Current module being lowered.
320 318
    currentMod: ?u16,
321 319
    /// Global data items (string literals, constants, static arrays).
859 857
    pkgName: *[u8],
860 858
    arena: &mut alloc::Arena
861 859
) -> il::Program throws (LowerError) {
862 860
    let functionArena = (&mut *arena) as *unsafe mut alloc::Arena;
863 861
    let resolved: 'phase = &*res where 'arena: 'phase in {
864 -
        let mut low = lowererState(resolved, nil, pkgName, functionArena,
862 +
        let mut low = lowererState(resolved, pkgName, functionArena,
865 863
            LowerOptions { debug: false, buildTest: false });
866 864
        try lowerDecls(&mut low, root, true, functionArena);
867 865
868 866
        return finalize(low);
869 867
    }
872 870
/////////////////////////////////
873 871
// Multi-Module Lowering API   //
874 872
/////////////////////////////////
875 873
876 874
/// Create a lowerer for multi-module compilation.
877 -
/// The resolver, graph, and persistent arena must outlive the returned lowerer.
875 +
/// The resolver and persistent arena must outlive the returned lowerer.
878 876
export unsafe fn lowerer 'arena 'phase (
879 877
    res: &'phase resolver::Resolver 'arena,
880 -
    graph: &'phase module::ModuleGraph,
881 878
    pkgName: *[u8],
882 879
    arena: *unsafe mut alloc::Arena,
883 880
    options: LowerOptions
884 881
) -> Lowerer 'arena 'phase where 'arena: 'phase {
885 -
    return lowererState(res, graph, pkgName, arena, options);
882 +
    return lowererState(res, pkgName, arena, options);
886 883
}
887 884
888 885
/// Initialize lowering state with empty owned collections and borrowed metadata.
889 886
fn lowererState 'arena 'phase (
890 887
    res: &'phase resolver::Resolver 'arena,
891 -
    graph: ?&'phase module::ModuleGraph,
892 888
    pkgName: *[u8],
893 889
    arena: *unsafe mut alloc::Arena,
894 890
    options: LowerOptions
895 891
) -> Lowerer 'arena 'phase where 'arena: 'phase {
896 892
    return Lowerer 'arena 'phase {
897 893
        arena,
898 894
        resolver: res,
899 -
        moduleGraph: graph,
900 895
        pkgName,
901 896
        currentMod: nil,
902 897
        data: &mut [],
903 898
        packageDataStart: 0,
904 899
        functions: &mut [],
1002 997
1003 998
/////////////////////////////////
1004 999
// Qualified Name Construction //
1005 1000
/////////////////////////////////
1006 1001
1007 -
/// Get module path segments for the current or specified module.
1008 -
/// Returns empty slice if no module graph or module not found.
1002 +
/// Return the qualified path for a module from retained resolver identities.
1003 +
/// Returns an empty slice if no module is active or its identity is missing.
1009 1004
fn getModulePath 'arena 'phase (self: &Lowerer 'arena 'phase, modId: ?u16) -> *[*[u8]] where 'arena: 'phase {
1010 -
    let graph = self.moduleGraph else {
1011 -
        return &[];
1012 -
    };
1013 1005
    let mut id = modId;
1014 1006
    if id == nil {
1015 1007
        set id = self.currentMod;
1016 1008
    }
1017 1009
    let actualId = id else {
1018 1010
        return &[];
1019 1011
    };
1020 -
    let entry = module::get(graph, actualId) else {
1012 +
    let entry = resolver::moduleFor(self.resolver, actualId) else {
1021 1013
        return &[];
1022 1014
    };
1023 1015
    return module::moduleQualifiedPath(entry);
1024 1016
}
1025 1017
lib/std/lang/module.rad +106 -63
52 52
    PathTooDeep,
53 53
    /// Attempt to register a module before its parent.
54 54
    MissingParent,
55 55
}
56 56
57 +
/// Zero-state owner for authority over one module graph's mutable state.
58 +
export record Permission {}
59 +
57 60
/// Module metadata recorded inside the dependency graph.
58 61
export record ModuleEntry: Copy {
59 62
    /// Numeric identifier for the slot (index in the graph array).
60 63
    id: u16,
61 64
    /// Package identifier this module belongs to.
70 73
    name: *[u8],
71 74
    /// Logical path from the root module to this module.
72 75
    path: [*[u8]; MAX_MODULE_PATH_DEPTH],
73 76
    /// Number of segments inside `path`.
74 77
    pathDepth: u32,
75 -
    /// Mutable module state shared by all entry views.
76 -
    updates: *cell ModuleUpdates,
77 78
}
78 79
79 -
/// Copyable state stored in a module entry's cell.
80 +
/// Copyable state stored in a module graph update cell.
80 81
export record ModuleUpdates: Copy {
81 82
    /// Current lifecycle state.
82 83
    state: ModuleState,
83 84
    /// Child module identifiers declared directly inside this module.
84 85
    children: [u16; MAX_MODULES],
89 90
    /// Source text for this module (for error reporting).
90 91
    source: ?*[u8],
91 92
}
92 93
93 94
/// Dense storage for all modules referenced by the compilation unit.
94 -
export record ModuleGraph {
95 +
export opaque record ModuleGraph: 'permission {
95 96
    /// Entry identities indexed by module identifier.
96 97
    entries: *mut [?*ModuleEntry],
98 +
    /// Permission-associated updates indexed by module identifier.
99 +
    updates: [?*cell 'permission ModuleUpdates; MAX_MODULES],
100 +
    /// External authority for the update cells in this graph.
101 +
    permission: &'permission mut Permission,
97 102
    /// Number of initialized entries.
98 103
    entriesLen: u32,
99 104
    /// Arena for AST nodes and stable entry allocations.
100 105
    arena: ?*unsafe mut ast::NodeArena,
101 106
}
102 107
103 108
/// Initialize an empty module graph backed by the provided storage.
104 109
/// The AST arena must outlive the graph and all retained entry views.
105 110
/// Entry allocation must occur outside speculative parser allocations.
106 -
export unsafe fn moduleGraph(
111 +
export unsafe fn moduleGraph 'permission (
107 112
    storage: *mut [?*ModuleEntry],
108 -
    arena: &mut ast::NodeArena
109 -
) -> ModuleGraph {
113 +
    arena: &mut ast::NodeArena,
114 +
    permission: &'permission mut Permission
115 +
) -> ModuleGraph 'permission {
110 116
    for i in 0..storage.len {
111 117
        set storage[i] = nil;
112 118
    }
113 -
    return ModuleGraph {
119 +
    return ModuleGraph 'permission {
114 120
        entries: storage,
121 +
        updates: [nil; MAX_MODULES],
122 +
        permission,
115 123
        entriesLen: 0,
116 124
        arena: arena as *unsafe mut ast::NodeArena,
117 125
    };
118 126
}
119 127
120 128
/// Register a root module residing at `path` for a package.
121 -
export unsafe fn registerRoot(graph: &mut ModuleGraph, pool: &mut strings::Pool, packageId: u16, filePath: *[u8]) -> u16 throws (ModuleError) {
129 +
export unsafe fn registerRoot 'permission (graph: &mut ModuleGraph 'permission, pool: &mut strings::Pool, packageId: u16, filePath: *[u8]) -> u16 throws (ModuleError) {
122 130
    let name = try basenameSlice(filePath);
123 131
    return try registerRootWithName(graph, pool, packageId, name, filePath);
124 132
}
125 133
126 134
/// Register a root module with an explicit name and file path for a package.
127 -
export unsafe fn registerRootWithName(
128 -
    graph: &mut ModuleGraph, pool: &mut strings::Pool,
135 +
export unsafe fn registerRootWithName 'permission (
136 +
    graph: &mut ModuleGraph 'permission, pool: &mut strings::Pool,
129 137
    packageId: u16,
130 138
    name: *[u8],
131 139
    filePath: *[u8]
132 140
) -> u16 throws (ModuleError) {
133 141
    let m = try allocModule(graph, pool, packageId, name, filePath);
138 146
    return id;
139 147
}
140 148
141 149
/// Register a child module.
142 150
/// Returns the module identifier.
143 -
export unsafe fn registerChild(
144 -
    graph: &mut ModuleGraph, pool: &mut strings::Pool,
151 +
export unsafe fn registerChild 'permission (
152 +
    graph: &mut ModuleGraph 'permission, pool: &mut strings::Pool,
145 153
    parentId: u16,
146 154
    name: *[u8],
147 155
    filePath: *[u8]
148 156
) -> u16 throws (ModuleError) {
149 157
    assert name.len > 0, "registerChild: name must not be empty";
165 173
    for p in moduleQualifiedPath(parent) {
166 174
        try appendPathSegment(m, p);
167 175
    }
168 176
    try appendPathSegment(m, name);
169 177
170 -
    let id = try addChild(parent, m.id);
178 +
    let id = try addChild(graph, parent, m.id);
171 179
    publish(graph, m);
172 180
    return id;
173 181
}
174 182
183 +
/// Return the number of registered module identities.
184 +
export fn entryCount 'permission (graph: &ModuleGraph 'permission) -> u32 {
185 +
    return graph.entriesLen;
186 +
}
187 +
175 188
/// Fetch a read-only view of the module identified by `id`.
176 -
export fn get(graph: &ModuleGraph, id: u16) -> ?*ModuleEntry {
189 +
export fn get 'permission (graph: &ModuleGraph 'permission, id: u16) -> ?*ModuleEntry {
177 190
    if not isValidId(graph, id) {
178 191
        return nil;
179 192
    }
180 193
    return graph.entries[id as u32];
181 194
}
182 195
183 196
/// Return the identifier of the child stored at `index`.
184 -
export fn childAt(m: *ModuleEntry, index: u32) -> u16 {
185 -
    let updates = *m.updates;
186 -
    assert index < updates.childrenLen, "childAt: index must be valid";
187 -
    return updates.children[index];
197 +
export fn childAt 'permission (graph: &ModuleGraph 'permission, m: *ModuleEntry, index: u32) -> u16 {
198 +
    let cell = updatesFor(graph, m.id);
199 +
    let authority: 'loan = &*graph.permission, updates = &*cell in {
200 +
        assert index < updates.childrenLen, "childAt: index must be valid";
201 +
        return updates.children[index];
202 +
    }
188 203
}
189 204
190 205
/// Public accessor for a module's directory prefix.
191 206
export fn moduleDir(m: *ModuleEntry) -> *[u8] {
192 207
    return &m.filePath[..m.dirLen];
197 212
    assert m.pathDepth > 0, "moduleQualifiedPath: path must not be empty";
198 213
    return &m.path[..m.pathDepth];
199 214
}
200 215
201 216
/// Retrieve the lifecycle state for `id`.
202 -
export fn state(graph: &ModuleGraph, id: u16) -> ModuleState throws (ModuleError) {
217 +
export fn state 'permission (graph: &ModuleGraph 'permission, id: u16) -> ModuleState throws (ModuleError) {
203 218
    let m = get(graph, id) else {
204 219
        throw ModuleError::NotFound(id);
205 220
    };
206 -
    return (*m.updates).state;
221 +
    let cell = updatesFor(graph, m.id);
222 +
    let authority: 'loan = &*graph.permission, updates = &*cell in {
223 +
        return updates.state;
224 +
    }
207 225
}
208 226
209 227
/// Record the parsed AST root for `id`.
210 -
export fn setAst(graph: &mut ModuleGraph, id: u16, root: *ast::Node) throws (ModuleError) {
228 +
export fn setAst 'permission (graph: &mut ModuleGraph 'permission, id: u16, root: *ast::Node) throws (ModuleError) {
211 229
    let m = get(graph, id) else throw ModuleError::NotFound(id);
212 -
    let mut updates = *m.updates;
213 -
    set updates.ast = root;
214 -
    set updates.state = ModuleState::Parsed;
215 -
    set *m.updates = updates;
230 +
    let cell = updatesFor(graph, m.id);
231 +
    let authority: 'loan = &mut *graph.permission, updates = &mut *cell in {
232 +
        set updates.ast = root;
233 +
        set updates.state = ModuleState::Parsed;
234 +
    }
216 235
}
217 236
218 237
/// Set the source text for a module.
219 -
export fn setSource(graph: &mut ModuleGraph, id: u16, source: *[u8]) throws (ModuleError) {
238 +
export fn setSource 'permission (graph: &mut ModuleGraph 'permission, id: u16, source: *[u8]) throws (ModuleError) {
220 239
    let m = get(graph, id) else throw ModuleError::NotFound(id);
221 -
    let mut updates = *m.updates;
222 -
    set updates.source = source;
223 -
    set *m.updates = updates;
240 +
    let cell = updatesFor(graph, m.id);
241 +
    let authority: 'loan = &mut *graph.permission, updates = &mut *cell in {
242 +
        set updates.source = source;
243 +
    }
224 244
}
225 245
226 246
/// Look up a child module by name under the given parent.
227 -
export fn findChild(graph: &ModuleGraph, name: *[u8], parentId: u16) -> ?*ModuleEntry {
247 +
export fn findChild 'permission (graph: &ModuleGraph 'permission, name: *[u8], parentId: u16) -> ?*ModuleEntry {
228 248
    assert isValidId(graph, parentId), "findChild: parent identifier is valid";
229 249
230 250
    let parent = get(graph, parentId) else panic;
231 -
    let updates = *parent.updates;
232 -
    for i in 0..updates.childrenLen {
233 -
        let childId = updates.children[i];
234 -
        let child = get(graph, childId) else panic;
235 -
        if mem::eq(child.name, name) {
236 -
            return child;
251 +
    let cell = updatesFor(graph, parent.id);
252 +
    let authority: 'loan = &*graph.permission, updates = &*cell in {
253 +
        for i in 0..updates.childrenLen {
254 +
            let childId = updates.children[i];
255 +
            let child = get(graph, childId) else panic;
256 +
            if mem::eq(child.name, name) {
257 +
                return child;
258 +
            }
237 259
        }
238 260
    }
239 261
    return nil;
240 262
}
241 263
275 297
276 298
/// Register a module from a file path, creating the full hierarchy as needed.
277 299
/// The path is split into components and the module hierarchy is built accordingly.
278 300
/// If `rootId` is `nil`, registers a new root for the given package.
279 301
/// Returns the module ID of the last component.
280 -
export unsafe fn registerFromPath(graph: &mut ModuleGraph, pool: &mut strings::Pool, packageId: u16, rootId: ?u16, filePath: *[u8]) -> u16 throws (ModuleError) {
302 +
export unsafe fn registerFromPath 'permission (graph: &mut ModuleGraph 'permission, pool: &mut strings::Pool, packageId: u16, rootId: ?u16, filePath: *[u8]) -> u16 throws (ModuleError) {
281 303
    let root = rootId else {
282 304
        return try registerRoot(graph, pool, packageId, filePath);
283 305
    };
284 306
    let rootEntry = get(graph, root) else {
285 307
        panic "registerFromPath: root is missing from storage";
320 342
    }
321 343
    return try registerChild(graph, pool, parentId, childName, filePath);
322 344
}
323 345
324 346
/// Allocate a fresh entry in the graph.
325 -
unsafe fn allocModule(graph: &mut ModuleGraph, pool: &mut strings::Pool, packageId: u16, name: *[u8], filePath: *[u8]) -> *mut ModuleEntry throws (ModuleError) {
347 +
unsafe fn allocModule 'permission (graph: &mut ModuleGraph 'permission, pool: &mut strings::Pool, packageId: u16, name: *[u8], filePath: *[u8]) -> *mut ModuleEntry throws (ModuleError) {
326 348
    if graph.entriesLen >= graph.entries.len {
327 349
        throw ModuleError::CapacityExceeded;
328 350
    }
329 351
    let idx = graph.entriesLen;
330 352
    let arena = graph.arena else panic;
331 353
    let state = try! alloc::alloc(&mut arena.arena, @sizeOf(ModuleUpdates), @alignOf(ModuleUpdates)) as *mut ModuleUpdates;
332 -
    set *state = ModuleUpdates {
333 -
        state: ModuleState::Registered,
334 -
        children: [0; MAX_MODULES],
335 -
        childrenLen: 0,
336 -
        ast: nil,
337 -
        source: nil,
338 -
    };
339 -
    let updates = &cell *state;
354 +
    let updates: *cell 'permission ModuleUpdates = &cell 'permission *state;
355 +
    let authority: 'loan = &mut *graph.permission, value = &mut *updates in {
356 +
        set *value = ModuleUpdates {
357 +
            state: ModuleState::Registered,
358 +
            children: [0; MAX_MODULES],
359 +
            childrenLen: 0,
360 +
            ast: nil,
361 +
            source: nil,
362 +
        };
363 +
    }
364 +
    set graph.updates[idx] = updates;
340 365
341 366
    // TODO: This is a common pattern that needs better syntax.
342 367
    let m = try! alloc::alloc(&mut arena.arena, @sizeOf(ModuleEntry), @alignOf(ModuleEntry)) as *mut ModuleEntry;
343 368
    set *m = ModuleEntry {
344 369
        id: idx as u16,
347 372
        filePath,
348 373
        dirLen: dirLength(filePath),
349 374
        name: strings::intern(pool, name),
350 375
        path: [""; MAX_MODULE_PATH_DEPTH],
351 376
        pathDepth: 0,
352 -
        updates,
353 377
    };
354 378
355 379
    return m;
356 380
}
357 381
363 387
    set entry.path[entry.pathDepth] = segment;
364 388
    set entry.pathDepth += 1;
365 389
}
366 390
367 391
/// Append a child identifier to the parent's child list.
368 -
fn addChild(parent: *ModuleEntry, childId: u16) -> u16 throws (ModuleError) {
369 -
    let mut updates = *parent.updates;
370 -
    if updates.childrenLen >= updates.children.len {
371 -
        throw ModuleError::CapacityExceeded;
392 +
fn addChild 'permission (graph: &mut ModuleGraph 'permission, parent: *ModuleEntry, childId: u16) -> u16 throws (ModuleError) {
393 +
    let cell = updatesFor(graph, parent.id);
394 +
    let authority: 'loan = &mut *graph.permission, updates = &mut *cell in {
395 +
        if updates.childrenLen >= updates.children.len {
396 +
            throw ModuleError::CapacityExceeded;
397 +
        }
398 +
        set updates.children[updates.childrenLen] = childId;
399 +
        set updates.childrenLen += 1;
372 400
    }
373 -
    set updates.children[updates.childrenLen] = childId;
374 -
    set updates.childrenLen += 1;
375 -
    set *parent.updates = updates;
376 401
377 402
    return childId;
378 403
}
379 404
380 405
/// Publish a fully initialized module entry in identifier order.
381 -
fn publish(graph: &mut ModuleGraph, entry: *ModuleEntry) {
406 +
fn publish 'permission (graph: &mut ModuleGraph 'permission, entry: *ModuleEntry) {
382 407
    assert entry.id as u32 == graph.entriesLen;
383 408
    set graph.entries[graph.entriesLen] = entry;
384 409
    set graph.entriesLen += 1;
385 410
}
386 411
387 412
/// Return the number of registered children.
388 -
export fn childCount(entry: *ModuleEntry) -> u32 {
389 -
    return (*entry.updates).childrenLen;
413 +
export fn childCount 'permission (graph: &ModuleGraph 'permission, entry: *ModuleEntry) -> u32 {
414 +
    let cell = updatesFor(graph, entry.id);
415 +
    let authority: 'loan = &*graph.permission, updates = &*cell in {
416 +
        return updates.childrenLen;
417 +
    }
390 418
}
391 419
392 420
/// Return the current parsed root for an entry.
393 -
export fn astFor(entry: *ModuleEntry) -> ?*ast::Node {
394 -
    return (*entry.updates).ast;
421 +
export fn astFor 'permission (graph: &ModuleGraph 'permission, entry: *ModuleEntry) -> ?*ast::Node {
422 +
    let cell = updatesFor(graph, entry.id);
423 +
    let authority: 'loan = &*graph.permission, updates = &*cell in {
424 +
        return updates.ast;
425 +
    }
395 426
}
396 427
397 428
/// Return the current source text for an entry.
398 -
export fn sourceFor(entry: *ModuleEntry) -> ?*[u8] {
399 -
    return (*entry.updates).source;
429 +
export fn sourceFor 'permission (graph: &ModuleGraph 'permission, entry: *ModuleEntry) -> ?*[u8] {
430 +
    let cell = updatesFor(graph, entry.id);
431 +
    let authority: 'loan = &*graph.permission, updates = &*cell in {
432 +
        return updates.source;
433 +
    }
434 +
}
435 +
436 +
/// Return the permission-associated update cell for `id`.
437 +
fn updatesFor 'permission (graph: &ModuleGraph 'permission, id: u16) -> *cell 'permission ModuleUpdates {
438 +
    assert isValidId(graph, id), "updatesFor: module identifier is valid";
439 +
    let cell = graph.updates[id as u32] else {
440 +
        panic "updatesFor: missing update cell";
441 +
    };
442 +
    return cell;
400 443
}
401 444
402 445
/// Check if `id` points at an allocated entry.
403 -
fn isValidId(graph: &ModuleGraph, id: u16) -> bool {
446 +
fn isValidId 'permission (graph: &ModuleGraph 'permission, id: u16) -> bool {
404 447
    return (id as u32) < graph.entriesLen;
405 448
}
406 449
407 450
/// Return the length of the directory prefix for `path`.
408 451
/// Return zero if the path has no separator.
lib/std/lang/module/printer.rad +8 -7
27 27
        case super::ModuleState::Ready => return sexpr::sym("ready"),
28 28
    }
29 29
}
30 30
31 31
/// Recursively convert a module entry and its descendants to an S-expression.
32 -
unsafe fn subtreeToExpr(
32 +
unsafe fn subtreeToExpr 'permission (
33 33
    a: &mut alloc::Arena,
34 -
    graph: &super::ModuleGraph,
34 +
    graph: &super::ModuleGraph 'permission,
35 35
    entry: *super::ModuleEntry
36 36
) -> sexpr::Expr {
37 37
    let idText = formatId(a, entry.id as u32);
38 38
    let path = super::moduleQualifiedPath(entry);
39 39
    let mut pathBuf: *[sexpr::Expr] = &[];
42 42
        for seg, i in path { set buf[i] = sexpr::sym(seg); }
43 43
        set pathBuf = buf;
44 44
    }
45 45
46 46
    let mut childBuf: *[sexpr::Expr] = &[];
47 -
    let childCount = super::childCount(entry);
47 +
    let childCount = super::childCount(graph, entry);
48 48
    if childCount > 0 {
49 49
        let buf = try! sexpr::allocExprs(a, childCount);
50 50
        for i in 0..childCount {
51 -
            if let child = super::get(graph, super::childAt(entry, i)) {
51 +
            if let child = super::get(graph, super::childAt(graph, entry, i)) {
52 52
                set buf[i] = subtreeToExpr(a, graph, child);
53 53
            }
54 54
        }
55 55
        set childBuf = buf;
56 56
    }
57 57
58 +
    let state = try! super::state(graph, entry.id);
58 59
    return sexpr::block(a, "module", &[
59 60
        sexpr::sym(entry.name),
60 61
        sexpr::list(a, "id", &[sexpr::sym(idText)]),
61 -
        sexpr::list(a, "state", &[stateToExpr((*entry.updates).state)]),
62 +
        sexpr::list(a, "state", &[stateToExpr(state)]),
62 63
        sexpr::Expr::Str(entry.filePath),
63 64
        sexpr::Expr::List { head: "::", tail: pathBuf, multiline: false }
64 65
    ], childBuf);
65 66
}
66 67
67 68
/// Print the entire module graph in S-expression format.
68 -
export unsafe fn printGraph(graph: &super::ModuleGraph, arena: &mut alloc::Arena) {
69 +
export unsafe fn printGraph 'permission (graph: &super::ModuleGraph 'permission, arena: &mut alloc::Arena) {
69 70
    // Print all root modules.
70 -
    for i in 0..graph.entriesLen {
71 +
    for i in 0..super::entryCount(graph) {
71 72
        let entry = super::get(graph, i as u16) else panic;
72 73
        if entry.parent == nil {
73 74
            sexpr::print(subtreeToExpr(arena, graph, entry), 0);
74 75
            io::print("\n");
75 76
        }
lib/std/lang/module/tests.rad +224 -191
50 50
}
51 51
52 52
@test unsafe fn testRegisterChildren() throws (testing::TestError) {
53 53
    static storage: [?*super::ModuleEntry; 4] = [nil; 4];
54 54
    let mut arena = ast::nodeArena(&mut TEST_ARENA[..]);
55 -
    let mut graph = super::moduleGraph(&mut storage[..], &mut arena);
56 -
57 -
    let rootId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "src/root.rad") catch {
58 -
        throw testing::TestError::Failed;
59 -
    };
60 -
    let root = super::get(&graph, rootId) else {
61 -
        throw testing::TestError::Failed;
62 -
    };
63 -
    try expectPathSegments(root, &["root"]);
64 -
65 -
    // First child.
66 -
    let firstId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "src/root/first.rad") catch {
67 -
        throw testing::TestError::Failed;
68 -
    };
69 -
    let first = super::get(&graph, firstId) else {
70 -
        throw testing::TestError::Failed;
71 -
    };
72 -
    try expectSliceEq(first.filePath, "src/root/first.rad");
73 -
    try expectSliceEq(first.name, "first");
74 -
    try expectPathSegments(first, &["root", "first"]);
75 -
76 -
    // Second child.
77 -
    let secondId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "src/root/second.rad") catch {
78 -
        throw testing::TestError::Failed;
79 -
    };
80 -
    let second = super::get(&graph, secondId) else {
81 -
        throw testing::TestError::Failed;
82 -
    };
83 -
    try expectSliceEq(second.filePath, "src/root/second.rad");
84 -
    try expectSliceEq(second.name, "second");
85 -
    try expectPathSegments(second, &["root", "second"]);
86 -
87 -
    let parent = super::get(&graph, rootId) else {
88 -
        throw testing::TestError::Failed;
89 -
    };
90 -
    try testing::expect(graph.entriesLen == 3);
91 -
    try testing::expect(super::childCount(parent) == 2);
92 -
    try testing::expect(super::childAt(parent, 0) == firstId);
93 -
    try testing::expect(super::childAt(parent, 1) == secondId);
55 +
    let mut owner = super::Permission {};
56 +
    let permission: 'permission = &mut owner in {
57 +
        let mut graph = super::moduleGraph(&mut storage[..], &mut arena, permission);
58 +
59 +
        let rootId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "src/root.rad") catch {
60 +
            throw testing::TestError::Failed;
61 +
        };
62 +
        let root = super::get(&graph, rootId) else {
63 +
            throw testing::TestError::Failed;
64 +
        };
65 +
        try expectPathSegments(root, &["root"]);
66 +
67 +
        // First child.
68 +
        let firstId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "src/root/first.rad") catch {
69 +
            throw testing::TestError::Failed;
70 +
        };
71 +
        let first = super::get(&graph, firstId) else {
72 +
            throw testing::TestError::Failed;
73 +
        };
74 +
        try expectSliceEq(first.filePath, "src/root/first.rad");
75 +
        try expectSliceEq(first.name, "first");
76 +
        try expectPathSegments(first, &["root", "first"]);
77 +
78 +
        // Second child.
79 +
        let secondId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "src/root/second.rad") catch {
80 +
            throw testing::TestError::Failed;
81 +
        };
82 +
        let second = super::get(&graph, secondId) else {
83 +
            throw testing::TestError::Failed;
84 +
        };
85 +
        try expectSliceEq(second.filePath, "src/root/second.rad");
86 +
        try expectSliceEq(second.name, "second");
87 +
        try expectPathSegments(second, &["root", "second"]);
88 +
89 +
        let parent = super::get(&graph, rootId) else {
90 +
            throw testing::TestError::Failed;
91 +
        };
92 +
        try testing::expect(super::entryCount(&graph) == 3);
93 +
        try testing::expect(super::childCount(&graph, parent) == 2);
94 +
        try testing::expect(super::childAt(&graph, parent, 0) == firstId);
95 +
        try testing::expect(super::childAt(&graph, parent, 1) == secondId);
96 +
    }
94 97
}
95 98
96 99
@test unsafe fn testRegisterChildReusesExisting() throws (testing::TestError) {
97 100
    static storage: [?*super::ModuleEntry; 4] = [nil; 4];
98 101
    let mut arena = ast::nodeArena(&mut TEST_ARENA[..]);
99 -
    let mut graph = super::moduleGraph(&mut storage[..], &mut arena);
100 -
101 -
    let rootId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "src/main.rad") catch {
102 -
        throw testing::TestError::Failed;
103 -
    };
104 -
    let firstId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "src/main/util.rad") catch {
105 -
        throw testing::TestError::Failed;
106 -
    };
107 -
    let secondId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "src/main/util.rad") catch {
108 -
        throw testing::TestError::Failed;
109 -
    };
110 -
    try testing::expect(firstId == secondId);
111 -
112 -
    let parent = super::get(&graph, rootId) else {
113 -
        throw testing::TestError::Failed;
114 -
    };
115 -
    try testing::expect(graph.entriesLen == 2);
116 -
    try testing::expect(super::childCount(parent) == 1);
102 +
    let mut owner = super::Permission {};
103 +
    let permission: 'permission = &mut owner in {
104 +
        let mut graph = super::moduleGraph(&mut storage[..], &mut arena, permission);
105 +
106 +
        let rootId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "src/main.rad") catch {
107 +
            throw testing::TestError::Failed;
108 +
        };
109 +
        let firstId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "src/main/util.rad") catch {
110 +
            throw testing::TestError::Failed;
111 +
        };
112 +
        let secondId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "src/main/util.rad") catch {
113 +
            throw testing::TestError::Failed;
114 +
        };
115 +
        try testing::expect(firstId == secondId);
116 +
117 +
        let parent = super::get(&graph, rootId) else {
118 +
            throw testing::TestError::Failed;
119 +
        };
120 +
        try testing::expect(super::entryCount(&graph) == 2);
121 +
        try testing::expect(super::childCount(&graph, parent) == 1);
122 +
    }
117 123
}
118 124
119 125
@test fn testTrimExtensionWithRadExtension() throws (testing::TestError) {
120 126
    let input = "parser.rad";
121 127
    let result = super::trimExtension(input)
165 171
}
166 172
167 173
@test unsafe fn testRegisterFromPathHierarchy() throws (testing::TestError) {
168 174
    static storage: [?*super::ModuleEntry; 8] = [nil; 8];
169 175
    let mut arena = ast::nodeArena(&mut TEST_ARENA[..]);
170 -
    let mut graph = super::moduleGraph(&mut storage[..], &mut arena);
171 -
172 -
    // Register root module.
173 -
    let stdId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "lib/std.rad") catch {
174 -
        throw testing::TestError::Failed;
175 -
    };
176 -
    let std = super::get(&graph, stdId) else {
177 -
        throw testing::TestError::Failed;
178 -
    };
179 -
    try expectSliceEq(std.filePath, "lib/std.rad");
180 -
    try expectSliceEq(super::moduleDir(std), "lib/");
181 -
    try expectPathSegments(std, &["std"]);
182 -
183 -
    // Register child of root.
184 -
    let langId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, stdId, "lib/std/lang.rad") catch {
185 -
        throw testing::TestError::Failed;
186 -
    };
187 -
    let lang = super::get(&graph, langId) else {
188 -
        throw testing::TestError::Failed;
189 -
    };
190 -
    try expectSliceEq(lang.filePath, "lib/std/lang.rad");
191 -
    try expectSliceEq(lang.name, "lang");
192 -
    try expectSliceEq(super::moduleDir(lang), "lib/std/");
193 -
    try expectPathSegments(lang, &["std", "lang"]);
194 -
195 -
    // Register grandchild.
196 -
    let parserId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, stdId, "lib/std/lang/parser.rad") catch {
197 -
        throw testing::TestError::Failed;
198 -
    };
199 -
    let parser = super::get(&graph, parserId) else {
200 -
        throw testing::TestError::Failed;
201 -
    };
202 -
    try expectSliceEq(parser.filePath, "lib/std/lang/parser.rad");
203 -
    try expectSliceEq(parser.name, "parser");
204 -
    try expectSliceEq(super::moduleDir(parser), "lib/std/lang/");
205 -
    try expectPathSegments(parser, &["std", "lang", "parser"]);
206 -
207 -
    // Verify parent-child relationships.
208 -
    try testing::expect(graph.entriesLen == 3);
209 -
    try testing::expect(super::childCount(std) == 1);
210 -
    try testing::expect(super::childAt(std, 0) == langId);
211 -
    try testing::expect(super::childCount(lang) == 1);
212 -
    try testing::expect(super::childAt(lang, 0) == parserId);
213 -
    try testing::expect(super::childCount(parser) == 0);
176 +
    let mut owner = super::Permission {};
177 +
    let permission: 'permission = &mut owner in {
178 +
        let mut graph = super::moduleGraph(&mut storage[..], &mut arena, permission);
179 +
180 +
        // Register root module.
181 +
        let stdId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "lib/std.rad") catch {
182 +
            throw testing::TestError::Failed;
183 +
        };
184 +
        let std = super::get(&graph, stdId) else {
185 +
            throw testing::TestError::Failed;
186 +
        };
187 +
        try expectSliceEq(std.filePath, "lib/std.rad");
188 +
        try expectSliceEq(super::moduleDir(std), "lib/");
189 +
        try expectPathSegments(std, &["std"]);
190 +
191 +
        // Register child of root.
192 +
        let langId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, stdId, "lib/std/lang.rad") catch {
193 +
            throw testing::TestError::Failed;
194 +
        };
195 +
        let lang = super::get(&graph, langId) else {
196 +
            throw testing::TestError::Failed;
197 +
        };
198 +
        try expectSliceEq(lang.filePath, "lib/std/lang.rad");
199 +
        try expectSliceEq(lang.name, "lang");
200 +
        try expectSliceEq(super::moduleDir(lang), "lib/std/");
201 +
        try expectPathSegments(lang, &["std", "lang"]);
202 +
203 +
        // Register grandchild.
204 +
        let parserId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, stdId, "lib/std/lang/parser.rad") catch {
205 +
            throw testing::TestError::Failed;
206 +
        };
207 +
        let parser = super::get(&graph, parserId) else {
208 +
            throw testing::TestError::Failed;
209 +
        };
210 +
        try expectSliceEq(parser.filePath, "lib/std/lang/parser.rad");
211 +
        try expectSliceEq(parser.name, "parser");
212 +
        try expectSliceEq(super::moduleDir(parser), "lib/std/lang/");
213 +
        try expectPathSegments(parser, &["std", "lang", "parser"]);
214 +
215 +
        // Verify parent-child relationships.
216 +
        try testing::expect(super::entryCount(&graph) == 3);
217 +
        try testing::expect(super::childCount(&graph, std) == 1);
218 +
        try testing::expect(super::childAt(&graph, std, 0) == langId);
219 +
        try testing::expect(super::childCount(&graph, lang) == 1);
220 +
        try testing::expect(super::childAt(&graph, lang, 0) == parserId);
221 +
        try testing::expect(super::childCount(&graph, parser) == 0);
222 +
    }
214 223
}
215 224
216 225
@test unsafe fn testRegisterFromPathMissingParent() throws (testing::TestError) {
217 226
    static storage: [?*super::ModuleEntry; 8] = [nil; 8];
218 227
    let mut arena = ast::nodeArena(&mut TEST_ARENA[..]);
219 -
    let mut graph = super::moduleGraph(&mut storage[..], &mut arena);
220 -
221 -
    let rootId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "std.rad") catch {
228 +
    let mut owner = super::Permission {};
229 +
    let permission: 'permission = &mut owner in {
230 +
        let mut graph = super::moduleGraph(&mut storage[..], &mut arena, permission);
231 +
232 +
        let rootId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "std.rad") catch {
233 +
            throw testing::TestError::Failed;
234 +
        };
235 +
        let _ = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "std/lang/parser.rad") catch {
236 +
            // Expected to fail due to missing intermediate parent.
237 +
            return;
238 +
        };
222 239
        throw testing::TestError::Failed;
223 -
    };
224 -
    let _ = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "std/lang/parser.rad") catch {
225 -
        // Expected to fail due to missing intermediate parent.
226 -
        return;
227 -
    };
228 -
    throw testing::TestError::Failed;
240 +
    }
229 241
}
230 242
231 243
@test unsafe fn testRegisterFromPathDuplicateRoot() throws (testing::TestError) {
232 244
    static storage: [?*super::ModuleEntry; 8] = [nil; 8];
233 245
    let mut arena = ast::nodeArena(&mut TEST_ARENA[..]);
234 -
    let mut graph = super::moduleGraph(&mut storage[..], &mut arena);
235 -
236 -
    let rootId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "std.rad") catch {
246 +
    let mut owner = super::Permission {};
247 +
    let permission: 'permission = &mut owner in {
248 +
        let mut graph = super::moduleGraph(&mut storage[..], &mut arena, permission);
249 +
250 +
        let rootId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "std.rad") catch {
251 +
            throw testing::TestError::Failed;
252 +
        };
253 +
        let _ = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "parser.rad") catch {
254 +
            // Expected to fail due to missing intermediate parent.
255 +
            return;
256 +
        };
237 257
        throw testing::TestError::Failed;
238 -
    };
239 -
    let _ = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "parser.rad") catch {
240 -
        // Expected to fail due to missing intermediate parent.
241 -
        return;
242 -
    };
243 -
    throw testing::TestError::Failed;
258 +
    }
244 259
}
245 260
246 261
@test unsafe fn testRegisterFromPathRegistersRoot() throws (testing::TestError) {
247 262
    static storage: [?*super::ModuleEntry; 8] = [nil; 8];
248 263
    let mut arena = ast::nodeArena(&mut TEST_ARENA[..]);
249 -
    let mut graph = super::moduleGraph(&mut storage[..], &mut arena);
250 -
251 -
    let rootId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "lib/std.rad") catch {
252 -
        throw testing::TestError::Failed;
253 -
    };
254 -
    // Verify root module was registered with correct ID.
255 -
    try testing::expect(rootId == 0);
256 -
257 -
    let root = super::get(&graph, rootId) else {
258 -
        throw testing::TestError::Failed;
259 -
    };
260 -
    try expectSliceEq(root.name, "std");
261 -
    try expectSliceEq(root.filePath, "lib/std.rad");
262 -
    try expectPathSegments(root, &["std"]);
264 +
    let mut owner = super::Permission {};
265 +
    let permission: 'permission = &mut owner in {
266 +
        let mut graph = super::moduleGraph(&mut storage[..], &mut arena, permission);
267 +
268 +
        let rootId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "lib/std.rad") catch {
269 +
            throw testing::TestError::Failed;
270 +
        };
271 +
        // Verify root module was registered with correct ID.
272 +
        try testing::expect(rootId == 0);
273 +
274 +
        let root = super::get(&graph, rootId) else {
275 +
            throw testing::TestError::Failed;
276 +
        };
277 +
        try expectSliceEq(root.name, "std");
278 +
        try expectSliceEq(root.filePath, "lib/std.rad");
279 +
        try expectPathSegments(root, &["std"]);
280 +
    }
263 281
}
264 282
265 283
@test unsafe fn testRegisterFromPathIgnoresLeadingDirectories() throws (testing::TestError) {
266 284
    static storage: [?*super::ModuleEntry; 8] = [nil; 8];
267 285
    let mut arena = ast::nodeArena(&mut TEST_ARENA[..]);
268 -
    let mut graph = super::moduleGraph(&mut storage[..], &mut arena);
269 -
270 -
    let rootId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "src/pkg/root.rad") catch {
271 -
        throw testing::TestError::Failed;
272 -
    };
273 -
    // Verify root module was registered with correct ID.
274 -
    try testing::expect(rootId == 0);
275 -
276 -
    let langId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "src/pkg/root/lang.rad") catch {
277 -
        throw testing::TestError::Failed;
278 -
    };
279 -
    let lang = super::get(&graph, langId) else {
280 -
        throw testing::TestError::Failed;
281 -
    };
282 -
    try expectSliceEq(lang.name, "lang");
283 -
    try expectPathSegments(lang, &["root", "lang"]);
284 -
285 -
    let parserId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "src/pkg/root/lang/parser.rad") catch {
286 -
        throw testing::TestError::Failed;
287 -
    };
288 -
    let parser = super::get(&graph, parserId) else {
289 -
        throw testing::TestError::Failed;
290 -
    };
291 -
    try expectSliceEq(parser.name, "parser");
292 -
    try expectPathSegments(parser, &["root", "lang", "parser"]);
286 +
    let mut owner = super::Permission {};
287 +
    let permission: 'permission = &mut owner in {
288 +
        let mut graph = super::moduleGraph(&mut storage[..], &mut arena, permission);
289 +
290 +
        let rootId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, nil, "src/pkg/root.rad") catch {
291 +
            throw testing::TestError::Failed;
292 +
        };
293 +
        // Verify root module was registered with correct ID.
294 +
        try testing::expect(rootId == 0);
295 +
296 +
        let langId = try super::registerFromPath(&mut graph, &mut STRING_POOL, 0, rootId, "src/pkg/root/lang.rad") catch {
297 +
            throw testing::TestError::Failed;
298 +
        };
299 +
        let lang = super::get(&graph, langId) else {
300 +
            throw testing::TestError::Failed;
301 +
        };
302 +
        try expectSliceEq(lang.name, "lang");
303 +
        try expectPathSegments(lang, &["root", "lang"]);
304 +
305 +
        let parserId = try super::registerFromPath(
306 +
            &mut graph, &mut STRING_POOL, 0, rootId, "src/pkg/root/lang/parser.rad"
307 +
        ) catch {
308 +
            throw testing::TestError::Failed;
309 +
        };
310 +
        let parser = super::get(&graph, parserId) else {
311 +
            throw testing::TestError::Failed;
312 +
        };
313 +
        try expectSliceEq(parser.name, "parser");
314 +
        try expectPathSegments(parser, &["root", "lang", "parser"]);
315 +
    }
293 316
}
317 +
294 318
/// Move the graph owner while its published entries retain their identities.
295 -
fn relocate(graph: super::ModuleGraph) -> super::ModuleGraph {
319 +
fn relocate 'permission (
320 +
    graph: super::ModuleGraph 'permission
321 +
) -> super::ModuleGraph 'permission {
296 322
    return graph;
297 323
}
324 +
298 325
@test unsafe fn testGraphRelocation() throws (testing::TestError) {
299 326
    static DATA: [u8; 16384] = [0; 16384];
300 327
    static ENTRIES: [?*super::ModuleEntry; 2] = [nil; 2];
301 328
    static POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
302 329
    let mut arena = ast::nodeArena(&mut DATA[..]);
303 -
    let mut original = super::moduleGraph(&mut ENTRIES[..], &mut arena);
304 -
    let rootId = try! super::registerRoot(&mut original, &mut POOL, 0, "root.rad");
305 -
    let root = super::get(&original, rootId) else panic;
306 -
    let alias = root;
307 -
    let mut graph = relocate(original);
308 -
    let child = try! super::registerChild(&mut graph, &mut POOL, rootId, "child", "root/child.rad");
309 -
    try testing::expect(super::childCount(root) == 1);
310 -
    try testing::expect(super::childAt(alias, 0) == child);
311 -
    let published = super::get(&graph, rootId) else panic;
312 -
    try testing::expect(published as u64 == root as u64);
313 -
    let saved = alloc::used(&arena.arena);
314 -
    let reused = try! super::registerChild(&mut graph, &mut POOL, rootId, "child", "root/child.rad");
315 -
    try testing::expect(reused == child);
316 -
    try testing::expect(alloc::used(&arena.arena) == saved);
317 -
    let mut failed = false;
318 -
    try super::registerChild(&mut graph, &mut POOL, rootId, "full", "root/full.rad") catch {
319 -
        set failed = true;
320 -
    };
321 -
    try testing::expect(failed);
322 -
    try testing::expect(graph.entriesLen == 2);
323 -
    try testing::expect(super::childCount(alias) == 1);
324 -
    try testing::expect(alloc::used(&arena.arena) == saved);
325 -
    let source = "fn answer() -> u32 { return 42; }";
326 -
    let parsed: *ast::Node = try! parser::parse(scanner::SourceLoc::String, source, &mut arena, &mut POOL);
327 -
    try! super::setAst(&mut graph, rootId, parsed);
328 -
    try! super::setSource(&mut graph, rootId, source);
329 -
    let retainedAst = super::astFor(alias) else panic;
330 -
    try testing::expect(retainedAst as u64 == parsed as u64);
331 -
    let retainedSource = super::sourceFor(root) else panic;
332 -
    try testing::expect(mem::eq(retainedSource, source));
333 -
    try testing::expect(try! super::state(&graph, rootId) == super::ModuleState::Parsed);
334 -
330 +
    let mut owner = super::Permission {};
331 +
    let permission: 'permission = &mut owner in {
332 +
        let mut original = super::moduleGraph(&mut ENTRIES[..], &mut arena, permission);
333 +
        let rootId = try! super::registerRoot(&mut original, &mut POOL, 0, "root.rad");
334 +
        let root = super::get(&original, rootId) else panic;
335 +
        let alias = root;
336 +
        let mut graph = relocate(original);
337 +
        let child = try! super::registerChild(&mut graph, &mut POOL, rootId, "child", "root/child.rad");
338 +
        try testing::expect(super::childCount(&graph, root) == 1);
339 +
        try testing::expect(super::childAt(&graph, alias, 0) == child);
340 +
        let published = super::get(&graph, rootId) else panic;
341 +
        try testing::expect(published as u64 == root as u64);
342 +
        let saved = alloc::used(&arena.arena);
343 +
        let reused = try! super::registerChild(&mut graph, &mut POOL, rootId, "child", "root/child.rad");
344 +
        try testing::expect(reused == child);
345 +
        try testing::expect(alloc::used(&arena.arena) == saved);
346 +
        let mut failed = false;
347 +
        try super::registerChild(&mut graph, &mut POOL, rootId, "full", "root/full.rad") catch {
348 +
            set failed = true;
349 +
        };
350 +
        try testing::expect(failed);
351 +
        try testing::expect(super::entryCount(&graph) == 2);
352 +
        try testing::expect(super::childCount(&graph, alias) == 1);
353 +
        try testing::expect(alloc::used(&arena.arena) == saved);
354 +
        let source = "fn answer() -> u32 { return 42; }";
355 +
        let parsed: *ast::Node = try! parser::parse(
356 +
            scanner::SourceLoc::String, source, &mut arena, &mut POOL
357 +
        );
358 +
        try! super::setAst(&mut graph, rootId, parsed);
359 +
        try! super::setSource(&mut graph, rootId, source);
360 +
        let retainedAst = super::astFor(&graph, alias) else panic;
361 +
        try testing::expect(retainedAst as u64 == parsed as u64);
362 +
        let retainedSource = super::sourceFor(&graph, root) else panic;
363 +
        try testing::expect(mem::eq(retainedSource, source));
364 +
        try testing::expect(
365 +
            try! super::state(&graph, rootId) == super::ModuleState::Parsed
366 +
        );
367 +
    }
335 368
}
lib/std/lang/package.rad +1 -1
31 31
    set pkg.rootModuleId = nil;
32 32
33 33
}
34 34
35 35
/// Register a module described by the file path.
36 -
export unsafe fn registerModule(pkg: &mut Package, graph: &mut module::ModuleGraph, pool: &mut strings::Pool, filePath: *[u8]) -> u16
36 +
export unsafe fn registerModule 'permission (pkg: &mut Package, graph: &mut module::ModuleGraph 'permission, pool: &mut strings::Pool, filePath: *[u8]) -> u16
37 37
    throws (module::ModuleError)
38 38
{
39 39
    let modId = try module::registerFromPath(graph, pool, pkg.id, pkg.rootModuleId, filePath);
40 40
    // First registered module becomes the root.
41 41
    if pkg.rootModuleId == nil {
lib/std/lang/resolver.rad +18 -10
1167 1167
    types: *unsafe mut [?*TypeNode],
1168 1168
    /// Diagnostics recorded so far.
1169 1169
    errors: DiagnosticBuffer,
1170 1170
    /// Stable module identities indexed by module ID.
1171 1171
    moduleEntries: [?*module::ModuleEntry; module::MAX_MODULES],
1172 +
    /// Parsed roots captured while module update authority is available.
1173 +
    moduleRoots: [?*ast::Node; module::MAX_MODULES],
1172 1174
    /// Cache of module scopes indexed by module ID.
1173 1175
    moduleScopes: [?*unsafe mut Scope; module::MAX_MODULES],
1174 1176
    /// Trait instance registry.
1175 1177
    instances: [InstanceEntry; MAX_INSTANCES],
1176 1178
    /// Number of registered instances.
1791 1793
        arena,
1792 1794
        nodeData: NodeDataTable { entries: nodeData },
1793 1795
        types,
1794 1796
        errors: DiagnosticBuffer { entries: errors, len: 0 },
1795 1797
        moduleEntries: [nil; module::MAX_MODULES],
1798 +
        moduleRoots: [nil; module::MAX_MODULES],
1796 1799
        moduleScopes,
1797 1800
        instances: undefined,
1798 1801
        instancesLen: 0,
1799 1802
        methods: undefined,
1800 1803
        methodsLen: 0,
1898 1901
1899 1902
/// Enter a sub-module. Changes the current scope into that of the sub-module.
1900 1903
unsafe fn enterSubModule 'arena (self: &mut Resolver 'arena, name: *[u8], node: *ast::Node) -> ModuleScope throws (ResolveError) {
1901 1904
    let modEntry = findChildModule(self, name, self.currentMod)
1902 1905
        else throw emitError(self, node, ErrorKind::UnresolvedSymbol(name));
1903 -
    let modRoot = module::astFor(modEntry)
1906 +
    let modRoot = self.moduleRoots[modEntry.id as u32]
1904 1907
        else panic "enterSubModule: analyzing module that wasn't parsed";
1905 1908
1906 1909
    return enterModuleScope(self, modRoot, modEntry);
1907 1910
}
1908 1911
3816 3819
    return self.moduleEntries[id as u32];
3817 3820
}
3818 3821
3819 3822
/// Find a retained child identity by its parent and name.
3820 3823
fn findChildModule 'arena (self: &Resolver 'arena, name: *[u8], parentId: u16) -> ?*module::ModuleEntry {
3821 -
    let parent = moduleFor(self, parentId) else return nil;
3822 -
    for i in 0..module::childCount(parent) {
3823 -
        let child = moduleFor(self, module::childAt(parent, i))
3824 -
            else panic "findChildModule: missing child identity";
3825 -
        if mem::eq(child.name, name) {
3826 -
            return child;
3824 +
    for child in self.moduleEntries {
3825 +
        if let entry = child {
3826 +
            if entry.parent == parentId and mem::eq(entry.name, name) {
3827 +
                return entry;
3828 +
            }
3827 3829
        }
3828 3830
    }
3829 3831
    return nil;
3830 3832
}
3831 3833
13142 13144
    }
13143 13145
}
13144 13146
13145 13147
/// Resolve all packages.
13146 13148
/// Module entries retain their identity throughout resolution and diagnostics.
13147 -
export unsafe fn resolve 'arena (self: &mut Resolver 'arena, graph: &module::ModuleGraph, packages: &[Pkg]) -> Diagnostics throws (ResolveError) {
13148 -
    assert graph.entriesLen <= self.moduleEntries.len, "resolve: module registry capacity exceeded";
13149 +
export unsafe fn resolve 'arena 'permission (self: &mut Resolver 'arena, graph: &module::ModuleGraph 'permission, packages: &[Pkg]) -> Diagnostics throws (ResolveError) {
13150 +
    assert module::entryCount(graph) <= self.moduleEntries.len, "resolve: module registry capacity exceeded";
13149 13151
    for i in 0..self.moduleEntries.len {
13150 -
        set self.moduleEntries[i] = module::get(graph, i as u16);
13152 +
        let entry = module::get(graph, i as u16);
13153 +
        set self.moduleEntries[i] = entry;
13154 +
        if let present = entry {
13155 +
            set self.moduleRoots[i] = module::astFor(graph, present);
13156 +
        } else {
13157 +
            set self.moduleRoots[i] = nil;
13158 +
        }
13151 13159
    }
13152 13160
13153 13161
    // 1. Bind all package roots to enable cross-package references.
13154 13162
    for i in 0..packages.len {
13155 13163
        let pkg = packages[i];
lib/std/lang/resolver/opaqueTests.rad +3 -3
52 52
            let model = try tests::registerModule(&mut tests::MODULE_GRAPH, root, "model", OPAQUE_RECORD_MODEL, &mut arena);
53 53
            let result = try tests::resolveModuleTree(&mut res, root);
54 54
            try tests::expectErrorKind(&result, resolver::ErrorKind::OpaqueRecordAccess) catch error {
55 55
                io::print(program);
56 56
                io::print("\n");
57 -
                resolver::printer::printDiagnostics(&result.diagnostics, &res);
57 +
                tests::printDiagnostics(&result.diagnostics, &res);
58 58
                throw error;
59 59
            };
60 60
        }
61 61
    }
62 62
}
73 73
            let program = "use root::model; fn f(p: model::Value) -> u32 { return p.number; }";
74 74
            let nested = try tests::registerModule(&mut tests::MODULE_GRAPH, model, "child", program if child else "", &mut arena);
75 75
            let client = try tests::registerModule(&mut tests::MODULE_GRAPH, root, "client", "" if child else program, &mut arena);
76 76
            let result = try tests::resolveModuleTree(&mut res, root);
77 77
            try tests::expectErrorKind(&result, resolver::ErrorKind::OpaqueRecordAccess) catch error {
78 -
                resolver::printer::printDiagnostics(&result.diagnostics, &res);
78 +
                tests::printDiagnostics(&result.diagnostics, &res);
79 79
                throw error;
80 80
            };
81 81
        }
82 82
    }
83 83
}
89 89
        let mut res = tests::testResolver(memory);
90 90
        let result = try tests::resolveProgramStr(&mut res,
91 91
            "opaque record Value: Copy { number: u32 } opaque record Word: Copy(u32); opaque record View: 'r + Copy { number: &'r cell u32 } fn f() { let mut p = Value { number: 1 }; set p.number += 2; let q: Value = { number: p.number }; let r: ?Value = { number: 4 }; let case Value { number } = q else panic; if let case Value { number: n } = p; n > 0 {} while let case Value { number: n } = p { break; } match p { case Value { number: n } => {} else => {} } let mut w = Word(1); set *w = 2; { let field = &mut *w; set *field = 3; } let case Word(n) = w else panic; } fn g 'r (number: &'r cell u32) -> View 'r { let p = View 'r { number }; set *p.number = 42; let case View 'r { number: value } = p else panic; return { number: value }; }"
92 92
        );
93 93
        try tests::expectNoErrors(&result) catch error {
94 -
            resolver::printer::printDiagnostics(&result.diagnostics, &res);
94 +
            tests::printDiagnostics(&result.diagnostics, &res);
95 95
            throw error;
96 96
        };
97 97
    }
98 98
}
lib/std/lang/resolver/printer.rad +4 -5
280 280
        printNominalArguments(applied);
281 281
    }
282 282
}
283 283
284 284
/// Print a single diagnostic entry.
285 -
unsafe fn printError 'arena (err: &super::Error, res: &super::Resolver 'arena) {
285 +
unsafe fn printError 'arena 'permission (err: &super::Error, res: &super::Resolver 'arena, graph: &module::ModuleGraph 'permission) {
286 286
    if let node = err.node {
287 287
        // Find the module containing this error.
288 288
        if let moduleEntry = super::moduleFor(res, err.moduleId) {
289 289
            // Get the source text if available.
290 -
            if let source = module::sourceFor(moduleEntry) {
290 +
            if let source = module::sourceFor(graph, moduleEntry) {
291 291
                // Convert offset to location.
292 292
                if let loc = scanner::getLocation(scanner::SourceLoc::File(moduleEntry.filePath), source, node.span.offset) {
293 293
                    // Print: filename:line:col: error: message
294 294
                    if let case scanner::SourceLoc::File(path) = loc.source {
295 295
                        io::print(path);
315 315
            }
316 316
        }
317 317
    } else {
318 318
        io::print("error: ");
319 319
    }
320 -
321 320
    // Print the error message.
322 321
    match err.kind {
323 322
        case super::ErrorKind::TypeMismatch(mismatch) => {
324 323
            io::print("type mismatch: expected ");
325 324
            printType(mismatch.expected);
662 661
    }
663 662
    io::print("\n");
664 663
}
665 664
666 665
/// Entry point for printing resolver diagnostics in vim quickfix format.
667 -
export unsafe fn printDiagnostics 'arena (diag: &super::Diagnostics, res: &super::Resolver 'arena) {
666 +
export unsafe fn printDiagnostics 'arena 'permission (diag: &super::Diagnostics, res: &super::Resolver 'arena, graph: &module::ModuleGraph 'permission) {
668 667
    for i in 0..diag.errors.len {
669 -
        printError(&diag.errors[i], res);
668 +
        printError(&diag.errors[i], res, graph);
670 669
    }
671 670
}
672 671
673 672
/// Print an applied nominal type's explicit region arguments.
674 673
unsafe fn printNominalArguments(applied: *unsafe super::NominalApplication) {
lib/std/lang/resolver/tests.rad +148 -53
388 388
unsafe static ERROR_STORAGE: [super::Error; 16] = undefined;
389 389
390 390
/// Package scope used by resolver tests.
391 391
unsafe static PKG_SCOPE: super::Scope = undefined;
392 392
393 -
/// Module entries used by resolver tests.
393 +
/// One parsed module staged for permission-checked graph construction.
394 +
record TestModule: Copy {
395 +
    /// Parent identity, or nil for a package root.
396 +
    parentId: ?u16,
397 +
    /// Source module name.
398 +
    name: *[u8],
399 +
    /// Parsed module root.
400 +
    root: *ast::Node,
401 +
    /// Source text used to parse the root.
402 +
    source: *[u8],
403 +
}
404 +
405 +
/// Permission-independent module fixture populated by resolver tests.
406 +
export opaque record TestModuleGraph {
407 +
    /// Staged modules in identifier order.
408 +
    entries: [?TestModule; 8],
409 +
    /// Number of initialized staged modules.
410 +
    entriesLen: u32,
411 +
}
412 +
413 +
/// Module entries used when materializing resolver test graphs.
394 414
unsafe static MODULE_ENTRIES: [?*module::ModuleEntry; 8] = [nil; 8];
395 415
396 -
/// Module graph used by resolver tests.
397 -
export unsafe static MODULE_GRAPH: module::ModuleGraph = undefined;
416 +
/// Staged module graph used by resolver tests.
417 +
export unsafe static MODULE_GRAPH: TestModuleGraph = TestModuleGraph {
418 +
    entries: [nil; 8],
419 +
    entriesLen: 0,
420 +
};
398 421
399 422
/// Module AST arena storage used by resolver tests.
400 423
static MODULE_ARENA_STORAGE: [u8; 16384] = [0; 16384];
401 424
402 425
/// Module AST arena used by resolver tests.
434 457
/// Create an arena owner for one resolver test scope.
435 458
export unsafe fn testArena() -> alloc::Arena {
436 459
    return alloc::new(&mut ARENA_STORAGE[..]);
437 460
}
438 461
439 -
/// Construct a resolver backed by test storage and a synthetic module graph.
462 +
/// Construct a resolver backed by test storage.
440 463
export unsafe fn testResolver 'arena (arena: &'arena mut alloc::Arena) -> super::Resolver 'arena {
441 464
    // TODO: This should be initialized only once.
442 465
    for i in 0..LITERALS.len {
443 466
        strings::intern(&mut STRING_POOL, LITERALS[i]);
444 467
    }
445 -
    // TODO: Use local static for this.
446 -
    // Reset the module graph for each test.
447 -
    set MODULE_ARENA = ast::nodeArena(&mut MODULE_ARENA_STORAGE[..]);
448 -
    set MODULE_GRAPH = module::moduleGraph(&mut MODULE_ENTRIES[..], &mut MODULE_ARENA);
468 +
    set MODULE_GRAPH.entriesLen = 0;
449 469
    let config = super::Config { buildTest: true };
450 470
    let res = super::resolver(arena, testStorage(), config);
451 471
452 472
    return res;
453 473
}
492 512
    };
493 513
    return TestResult { diagnostics, root: stmt };
494 514
}
495 515
496 516
/// Resolve session diagnostics against the standard allocator declaration contract.
497 -
export unsafe fn resolveSessionProgramStr 'arena (self: &mut super::Resolver 'arena, program: *[u8]) -> TestResult
498 -
    throws (testing::TestError)
499 -
{
517 +
export unsafe fn resolveSessionProgramStr 'arena (
518 +
    self: &mut super::Resolver 'arena,
519 +
    program: *[u8]
520 +
) -> TestResult throws (testing::TestError) {
500 521
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
501 -
    let root = try registerModule(&mut MODULE_GRAPH, nil, "std", "export mod lang; mod app;", &mut arena);
502 -
    let lang = try registerModule(&mut MODULE_GRAPH, root, "lang", "export mod alloc;", &mut arena);
522 +
    let root = try registerModule(
523 +
        &mut MODULE_GRAPH, nil, "std", "export mod lang; mod app;", &mut arena
524 +
    );
525 +
    let lang = try registerModule(
526 +
        &mut MODULE_GRAPH, root, "lang", "export mod alloc;", &mut arena
527 +
    );
503 528
    let allocator = try registerModule(&mut MODULE_GRAPH, lang, "alloc",
504 529
        "export record Arena { data: *mut [u8], offset: u32 } export fn reset(arena: &mut Arena) { set arena.offset = 0; } export union AllocError: Copy { OutOfMemory } export trait Alloc { unsafe fn (&mut Alloc) reserve(size: u32, alignment: u32) -> *unsafe mut opaque throws (AllocError); unsafe fn (&mut Alloc) reserveSlice(itemSize: u32, itemAlignment: u32, count: u32) -> *unsafe mut [opaque] throws (AllocError); } instance Alloc for Arena { unsafe fn (arena: &mut Arena) reserve(size: u32, alignment: u32) -> *unsafe mut opaque throws (AllocError) { panic; } unsafe fn (arena: &mut Arena) reserveSlice(size: u32, alignment: u32, count: u32) -> *unsafe mut [opaque] throws (AllocError) { panic; } }",
505 530
        &mut arena);
506 531
    let app = try registerModule(&mut MODULE_GRAPH, root, "app", program, &mut arena);
507 532
    return try resolveModuleTree(self, root);
524 549
        diagnostics: analysis.diagnostics,
525 550
        root: analysis.root,
526 551
    };
527 552
}
528 553
529 -
/// Resolve a module with the full resolution process.
554 +
/// Materialize staged modules under one externally owned permission.
555 +
unsafe fn materializeModuleGraph 'permission (
556 +
    fixture: &TestModuleGraph,
557 +
    permission: &'permission mut module::Permission
558 +
) -> module::ModuleGraph 'permission throws (testing::TestError) {
559 +
    set MODULE_ARENA = ast::nodeArena(&mut MODULE_ARENA_STORAGE[..]);
560 +
    let mut graph = module::moduleGraph(
561 +
        &mut MODULE_ENTRIES[..], &mut MODULE_ARENA, permission
562 +
    );
563 +
    for i in 0..fixture.entriesLen {
564 +
        let staged = fixture.entries[i] else throw testing::TestError::Failed;
565 +
        let id = if let parentId = staged.parentId {
566 +
            try module::registerChild(
567 +
                &mut graph, &mut STRING_POOL, parentId, staged.name, "<test>"
568 +
            ) catch {
569 +
                throw testing::TestError::Failed;
570 +
            }
571 +
        } else {
572 +
            try module::registerRootWithName(
573 +
                &mut graph, &mut STRING_POOL, 0, staged.name, "<test>"
574 +
            ) catch {
575 +
                throw testing::TestError::Failed;
576 +
            }
577 +
        };
578 +
        if id as u32 != i {
579 +
            throw testing::TestError::Failed;
580 +
        }
581 +
        try module::setAst(&mut graph, id, staged.root) catch {
582 +
            throw testing::TestError::Failed;
583 +
        };
584 +
        try module::setSource(&mut graph, id, staged.source) catch {
585 +
            throw testing::TestError::Failed;
586 +
        };
587 +
    }
588 +
    return graph;
589 +
}
590 +
591 +
/// Print module diagnostics using a freshly materialized permission-checked graph.
592 +
export unsafe fn printDiagnostics 'arena (
593 +
    diagnostics: &super::Diagnostics,
594 +
    res: &super::Resolver 'arena
595 +
) {
596 +
    let mut owner = module::Permission {};
597 +
    let permission: 'permission = &mut owner in {
598 +
        let graph = try! materializeModuleGraph(&MODULE_GRAPH, permission);
599 +
        super::printer::printDiagnostics(diagnostics, res, &graph);
600 +
    }
601 +
}
602 +
603 +
/// Resolve a staged module tree with the full resolution process.
530 604
export unsafe fn resolveModuleTree 'arena (
531 605
    res: &mut super::Resolver 'arena,
532 606
    rootId: u16
533 607
) -> TestResult throws (testing::TestError) {
534 -
    let root = module::get(&MODULE_GRAPH, rootId)
535 -
        else throw testing::TestError::Failed;
536 -
    let rootAst = module::astFor(root)
537 -
        else throw testing::TestError::Failed;
538 -
    let packages = [super::Pkg {
539 -
        rootEntry: root,
540 -
        rootAst,
541 -
    }];
542 -
    let diagnostics = try super::resolve(res, &MODULE_GRAPH, &packages[..]) catch {
543 -
        throw testing::TestError::Failed;
544 -
    };
545 -
    return TestResult { diagnostics, root: rootAst };
608 +
    let mut owner = module::Permission {};
609 +
    let permission: 'permission = &mut owner in {
610 +
        let graph = try materializeModuleGraph(&MODULE_GRAPH, permission);
611 +
        let root = module::get(&graph, rootId)
612 +
            else throw testing::TestError::Failed;
613 +
        let rootAst = module::astFor(&graph, root)
614 +
            else throw testing::TestError::Failed;
615 +
        let packages = [super::Pkg {
616 +
            rootEntry: root,
617 +
            rootAst,
618 +
        }];
619 +
        let diagnostics = try super::resolve(
620 +
            res, &graph, &packages[..]
621 +
        ) catch {
622 +
            throw testing::TestError::Failed;
623 +
        };
624 +
        return TestResult { diagnostics, root: rootAst };
625 +
    }
546 626
}
547 627
548 -
/// Register a module in the graph and attach a parsed AST to it.
549 -
/// If parentId is nil, registers as a root module.
628 +
/// Register a parsed module in the permission-independent test fixture.
629 +
/// If `parentId` is nil, registers a root module.
550 630
export unsafe fn registerModule(
551 -
    graph: &mut module::ModuleGraph,
631 +
    graph: &mut TestModuleGraph,
552 632
    parentId: ?u16,
553 633
    name: *[u8],
554 634
    code: *[u8],
555 635
    arena: &mut ast::NodeArena
556 636
) -> u16 throws (testing::TestError) {
557 -
    let filePath = "<test>";
558 -
    let mut modId: u16 = undefined;
559 -
    if let parent = parentId {
560 -
        set modId = try module::registerChild(graph, &mut STRING_POOL, parent, name, filePath) catch {
561 -
            throw testing::TestError::Failed;
562 -
        };
563 -
    } else {
564 -
        set modId = try module::registerRootWithName(graph, &mut STRING_POOL, 0, name, filePath) catch {
565 -
            throw testing::TestError::Failed;
566 -
        };
637 +
    if graph.entriesLen >= graph.entries.len {
638 +
        throw testing::TestError::Failed;
567 639
    }
568 -
    let root = try parser::parse(scanner::SourceLoc::String, code, arena, &mut STRING_POOL) catch {
640 +
    let root = try parser::parse(
641 +
        scanner::SourceLoc::String, code, arena, &mut STRING_POOL
642 +
    ) catch {
569 643
        panic "registerModule: parsing failed";
570 644
    };
571 -
    try module::setAst(graph, modId, root) catch {
572 -
        panic "registerModule: module not found";
645 +
    let id = graph.entriesLen as u16;
646 +
    set graph.entries[graph.entriesLen] = TestModule {
647 +
        parentId,
648 +
        name,
649 +
        root,
650 +
        source: code,
573 651
    };
574 -
    return modId;
652 +
    set graph.entriesLen += 1;
653 +
    return id;
575 654
}
576 655
577 656
/// Ensure an expression statement produces the expected type and return the expression node.
578 657
fn expectExprStmtType 'arena (self: &super::Resolver 'arena, node: *ast::Node, expected: super::Type) -> *ast::Node
579 658
    throws (testing::TestError)
8593 8672
    let storage: 'test = &mut arena in {
8594 8673
        let mut res = testResolver(storage);
8595 8674
        if let _ = super::moduleFor(&res, 0) {
8596 8675
            throw testing::TestError::Failed;
8597 8676
        }
8598 -
        let root = try! module::registerRootWithName(&mut MODULE_GRAPH, &mut STRING_POOL, 0, "root", "/root.rad");
8599 -
        let child = try! module::registerChild(&mut MODULE_GRAPH, &mut STRING_POOL, root, "child", "/child.rad");
8600 -
        let diagnostics = try! super::resolve(&mut res, &MODULE_GRAPH, &[]);
8601 -
        assert super::success(&diagnostics);
8602 -
        set MODULE_GRAPH.entriesLen = 0;
8603 -
        set MODULE_ENTRIES[root as u32] = nil;
8604 -
        set MODULE_ENTRIES[child as u32] = nil;
8605 -
        try checkModuleQueries(&res, root, child);
8606 -
        let empty = try! super::resolve(&mut res, &MODULE_GRAPH, &[]);
8607 -
        assert super::success(&empty);
8677 +
        set MODULE_ARENA = ast::nodeArena(&mut MODULE_ARENA_STORAGE[..]);
8678 +
        let mut root: u16 = 0;
8679 +
        let mut child: u16 = 0;
8680 +
        let mut owner = module::Permission {};
8681 +
        let permission: 'permission = &mut owner in {
8682 +
            let mut graph = module::moduleGraph(
8683 +
                &mut MODULE_ENTRIES[..], &mut MODULE_ARENA, permission
8684 +
            );
8685 +
            set root = try! module::registerRootWithName(
8686 +
                &mut graph, &mut STRING_POOL, 0, "root", "/root.rad"
8687 +
            );
8688 +
            set child = try! module::registerChild(
8689 +
                &mut graph, &mut STRING_POOL, root, "child", "/child.rad"
8690 +
            );
8691 +
            let diagnostics = try! super::resolve(&mut res, &graph, &[]);
8692 +
            assert super::success(&diagnostics);
8693 +
        }
8694 +
        let mut emptyOwner = module::Permission {};
8695 +
        let emptyPermission: 'empty = &mut emptyOwner in {
8696 +
            let graph = module::moduleGraph(
8697 +
                &mut MODULE_ENTRIES[..], &mut MODULE_ARENA, emptyPermission
8698 +
            );
8699 +
            try checkModuleQueries(&res, root, child);
8700 +
            let empty = try! super::resolve(&mut res, &graph, &[]);
8701 +
            assert super::success(&empty);
8702 +
        }
8608 8703
        if let _ = super::moduleFor(&res, root) {
8609 8704
            throw testing::TestError::Failed;
8610 8705
        }
8611 8706
    }
8612 8707
}