lang: enforce unsafe memory access

01c735add52799e08abd60fc88afe9714a85bec7122d67d7e88d7ef10022ab3b
Require unsafe context for raw pointer operations, mutable global state,
and unsafe bindings. Track loans across expressions and reject conflicting
borrows, invalid escapes, and ownership conversions.

Add scoped unsafe blocks, harden arena arithmetic, use call-scoped callback
references, and update the compiler, standard library, bootstrap image, and
test fixtures for the enforced safety rules.
Alexis Sellier committed ago 1 parent 1e2fdea9
Makefile +2 -2
36 36
endif
37 37
38 38
# Compiler build
39 39
40 40
SEED      := seed/radiance.rv64
41 -
SEED_OPTS := $(STD) -pkg radiance -mod compiler/radiance.rad -entry radiance
41 +
SEED_OPTS := $(STD) -pkg radiance -mod compiler/radiance.rad -mod compiler/radiance/codegenSink.rad -entry radiance
42 42
43 -
$(RAD_BIN): $(STD_LIB) compiler/radiance.rad | $(BIN_DIR)
43 +
$(RAD_BIN): $(STD_LIB) compiler/radiance.rad compiler/radiance/codegenSink.rad | $(BIN_DIR)
44 44
	@echo "radiance $(SEED) => $@"
45 45
	@$(EMU) $(EMU_FLAGS) -run $(SEED) $(SEED_OPTS) -o $@
46 46
47 47
$(BIN_DIR):
48 48
	@mkdir -p $@
compiler/radiance.rad +57 -83
42 42
/// Main arena size (96 MB) - lives throughout compilation.
43 43
/// Used for: resolver data, types, symbols, global IL data, and codegen output.
44 44
constant MAIN_ARENA_SIZE: u32 = 100663296;
45 45
46 46
/// AST storage arena.
47 -
static TEMP_ARENA: [u8; TEMP_ARENA_SIZE] = undefined;
47 +
unsafe static TEMP_ARENA: [u8; TEMP_ARENA_SIZE] = undefined;
48 48
/// Scratch storage reclaimed after each generated function.
49 -
static FN_ARENA: [u8; FN_ARENA_SIZE] = undefined;
49 +
unsafe static FN_ARENA: [u8; FN_ARENA_SIZE] = undefined;
50 50
/// Main storage arena - persists throughout compilation.
51 -
static MAIN_ARENA: [u8; MAIN_ARENA_SIZE] = undefined;
51 +
unsafe static MAIN_ARENA: [u8; MAIN_ARENA_SIZE] = undefined;
52 52
53 53
/// Module source code.
54 -
static MODULE_SOURCES: [u8; MAX_SOURCES_SIZE] = undefined;
54 +
unsafe static MODULE_SOURCES: [u8; MAX_SOURCES_SIZE] = undefined;
55 55
/// Module entries for all packages.
56 -
static MODULE_ENTRIES: [module::ModuleEntry; MAX_TOTAL_MODULES] = undefined;
56 +
unsafe static MODULE_ENTRIES: [module::ModuleEntry; MAX_TOTAL_MODULES] = undefined;
57 57
/// String pool.
58 -
static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
58 +
unsafe static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
59 59
60 60
/// Package scope.
61 -
static RESOLVER_PKG_SCOPE: resolver::Scope = undefined;
61 +
unsafe static RESOLVER_PKG_SCOPE: resolver::Scope = undefined;
62 62
/// Errors emitted by resolver.
63 -
static RESOLVER_ERRORS: [resolver::Error; resolver::MAX_ERRORS] = undefined;
63 +
unsafe static RESOLVER_ERRORS: [resolver::Error; resolver::MAX_ERRORS] = undefined;
64 64
65 65
/// Code generation storage.
66 -
static CODEGEN_DATA_SYMS: [data::DataSym; data::MAX_DATA_SYMS] = undefined;
66 +
unsafe static CODEGEN_DATA_SYMS: [data::DataSym; data::MAX_DATA_SYMS] = undefined;
67 67
/// Hash table entries for data symbol lookup.
68 -
static CODEGEN_DATA_SYM_ENTRIES: [dict::Entry; data::DATA_SYM_TABLE_SIZE] = undefined;
68 +
unsafe static CODEGEN_DATA_SYM_ENTRIES: [dict::Entry; data::DATA_SYM_TABLE_SIZE] = undefined;
69 69
70 70
/// Debug info file extension.
71 -
constant DEBUG_EXT: *[u8] = ".debug";
71 +
unsafe constant DEBUG_EXT: *[u8] = ".debug";
72 72
73 73
/// Maximum rodata size (4MB).
74 74
constant MAX_RO_DATA_SIZE: u32 = 4194304;
75 75
/// Maximum rwdata size (4MB).
76 76
constant MAX_RW_DATA_SIZE: u32 = 4194304;
77 77
/// Maximum path length.
78 78
constant MAX_PATH_LEN: u32 = 256;
79 79
/// Read-only data buffer.
80 -
static RO_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = undefined;
80 +
unsafe static RO_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = undefined;
81 81
/// Read-write data buffer.
82 -
static RW_DATA_BUF: [u8; MAX_RW_DATA_SIZE] = undefined;
82 +
unsafe static RW_DATA_BUF: [u8; MAX_RW_DATA_SIZE] = undefined;
83 83
/// Assembly module source buffer.
84 -
static ASM_SOURCE_BUF: [u8; MAX_SOURCES_SIZE] = undefined;
84 +
unsafe static ASM_SOURCE_BUF: [u8; MAX_SOURCES_SIZE] = undefined;
85 85
/// Temporary assembly text buffer.
86 -
static ASM_TEXT_BUF: [u32; 262144] = undefined;
86 +
unsafe static ASM_TEXT_BUF: [u32; 262144] = undefined;
87 87
/// Temporary assembly data buffer.
88 -
static ASM_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = undefined;
88 +
unsafe static ASM_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = undefined;
89 89
/// Accumulated assembly read-only data.
90 -
static ASM_RO_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = undefined;
90 +
unsafe static ASM_RO_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = undefined;
91 91
92 92
/// Assembly source file extension.
93 -
constant ASM_SOURCE_EXT: *[u8] = ".ras";
93 +
unsafe constant ASM_SOURCE_EXT: *[u8] = ".ras";
94 94
/// Symbol name exported for startup code to call the semantic entry function.
95 -
constant DEFAULT_ENTRY_SYMBOL: *[u8] = "::default";
95 +
unsafe constant DEFAULT_ENTRY_SYMBOL: *[u8] = "::default";
96 96
97 97
/// Usage string.
98 -
constant USAGE: *[u8] =
98 +
unsafe constant USAGE: *[u8] =
99 99
    "usage: radiance -pkg <name> [-start <input.ras>] -mod <input>.. [-pkg <name> -mod <input>..] -entry <pkg> -o <output>\n";
100 100
101 101
/// Compiler error.
102 102
union Error {
103 103
    Other,
167 167
record RootModule {
168 168
    entry: *module::ModuleEntry,
169 169
    ast: *mut ast::Node,
170 170
}
171 171
172 -
/// State carried by the streaming lowerer/codegen callback.
173 -
record CodegenSinkContext {
174 -
    /// RV64 generator receiving lowered functions.
175 -
    generator: *mut rv64::Generator,
176 -
    /// Arena holding the current function's lowered IL.
177 -
    fnArena: *mut alloc::Arena,
178 -
}
172 +
/// Trusted implementation for streamed code generation.
173 +
unsafe mod codegenSink;
179 174
180 175
/// Entry handling for streamed code generation.
181 176
union CodegenEntryMode {
182 177
    /// Do not reserve an entry jump.
183 178
    None,
194 189
    /// How the generated program should handle entry.
195 190
    entryMode: CodegenEntryMode,
196 191
}
197 192
198 193
/// Print a driver error line.
199 -
fn error(msg: *[*[u8]]) -> Error {
194 +
unsafe fn error(msg: *[*[u8]]) -> Error {
200 195
    io::printError("radiance: ");
201 196
202 197
    for part, i in msg {
203 198
        io::printError(part);
204 199
        if i < msg.len - 1 {
208 203
    io::printError("\n");
209 204
    return Error::Other;
210 205
}
211 206
212 207
/// Print a log line for the given package.
213 -
fn pkgLog(pkg: *package::Package, msg: *[*[u8]]) {
208 +
unsafe fn pkgLog(pkg: *package::Package, msg: *[*[u8]]) {
214 209
    io::printError("radiance: ");
215 210
    io::printError(pkg.name);
216 211
    io::printError(": ");
217 212
218 213
    for part, i in msg {
223 218
    }
224 219
    io::printError("\n");
225 220
}
226 221
227 222
/// Return `true` when `path` ends with `ext`.
228 -
fn hasExtension(path: *[u8], ext: *[u8]) -> bool {
223 +
unsafe fn hasExtension(path: *[u8], ext: *[u8]) -> bool {
229 224
    if path.len < ext.len {
230 225
        return false;
231 226
    }
232 227
    let start = path.len - ext.len;
233 228
    return mem::eq(&path[start..], ext);
234 229
}
235 230
236 231
/// Create an empty source input set for one package.
237 -
fn packageInput(name: *[u8]) -> PackageInput {
232 +
unsafe fn packageInput(name: *[u8]) -> PackageInput {
238 233
    return PackageInput {
239 234
        name,
240 235
        startupPath: nil,
241 236
        radPaths: undefined,
242 237
        radPathCount: 0,
244 239
        asmPathCount: 0,
245 240
    };
246 241
}
247 242
248 243
/// Register, load, and parse `path` within `pkg`.
249 -
fn processModule(
244 +
unsafe fn processModule(
250 245
    pkg: *mut package::Package,
251 246
    graph: *mut module::ModuleGraph,
252 247
    path: *[u8],
253 248
    nodeArena: *mut ast::NodeArena,
254 249
    sourceArena: *mut alloc::Arena
282 277
        throw error(&["error setting source"]);
283 278
    };
284 279
}
285 280
286 281
/// Consume the next argument, or print an error and throw.
287 -
fn nextArg(args: *[*[u8]], idx: *mut u32, msg: *[*[u8]]) -> *[u8] throws (Error) {
282 +
unsafe fn nextArg(args: *[*[u8]], idx: *mut u32, msg: *[*[u8]]) -> *[u8] throws (Error) {
288 283
    set *idx += 1;
289 284
    if *idx >= args.len {
290 285
        throw error(msg);
291 286
    }
292 287
    return args[*idx];
293 288
}
294 289
295 290
/// Parse CLI arguments and return compilation context.
296 -
fn processCommand(
291 +
unsafe fn processCommand(
297 292
    args: *[*[u8]],
298 293
    arena: *mut ast::NodeArena
299 294
) -> CompileContext throws (Error) {
300 295
    let mut buildTest = false;
301 296
    let mut debugEnabled = false;
446 441
    }
447 442
    return ctx;
448 443
}
449 444
450 445
/// Get the entry package from the context.
451 -
fn getEntryPackage(ctx: *CompileContext) -> *package::Package throws (Error) {
446 +
unsafe fn getEntryPackage(ctx: *CompileContext) -> *package::Package throws (Error) {
452 447
    let entryIdx = ctx.entryPkgIdx else {
453 448
        throw error(&["no entry package specified"]);
454 449
    };
455 450
    return &ctx.packages[entryIdx];
456 451
}
457 452
458 453
/// Return the startup assembly path for the entry package, if one was supplied.
459 -
fn getEntryStartupPath(ctx: *CompileContext) -> ?*[u8] {
454 +
unsafe fn getEntryStartupPath(ctx: *CompileContext) -> ?*[u8] {
460 455
    let entryIdx = ctx.entryPkgIdx else {
461 456
        panic "getEntryStartupPath: no entry package";
462 457
    };
463 458
    return ctx.inputs[entryIdx].startupPath;
464 459
}
465 460
466 461
/// Get root module info from a package.
467 -
fn getRootModule(pkg: *package::Package, graph: *module::ModuleGraph) -> RootModule throws (Error) {
462 +
unsafe fn getRootModule(pkg: *package::Package, graph: *module::ModuleGraph) -> RootModule throws (Error) {
468 463
    let rootId = pkg.rootModuleId else {
469 464
        throw error(&["no root module found"]);
470 465
    };
471 466
    let rootEntry = module::get(graph, rootId) else {
472 467
        throw error(&["root module entry not found"]);
476 471
    };
477 472
    return RootModule { entry: rootEntry, ast: rootAst };
478 473
}
479 474
480 475
/// Dump the module graph.
481 -
fn dumpGraph(ctx: *CompileContext) {
476 +
unsafe fn dumpGraph(ctx: *CompileContext) {
482 477
    let mut arena = alloc::new(&mut MAIN_ARENA[..]);
483 478
    module::printer::printGraph(&ctx.graph, &mut arena);
484 479
}
485 480
486 481
/// Dump the parsed AST.
487 -
fn dumpAst(ctx: *CompileContext) throws (Error) {
482 +
unsafe fn dumpAst(ctx: *CompileContext) throws (Error) {
488 483
    let pkg = try getEntryPackage(ctx);
489 484
    let root = try getRootModule(pkg, &ctx.graph);
490 485
    let mut arena = alloc::new(&mut MAIN_ARENA[..]);
491 486
492 487
    ast::printer::printTree(root.ast, &mut arena);
493 488
}
494 489
495 490
/// Lower all packages into a single IL program.
496 491
/// Dependencies are lowered first, then the entry package.
497 -
fn lowerAllPackages(
492 +
unsafe fn lowerAllPackages(
498 493
    ctx: *mut CompileContext,
499 494
    res: *mut resolver::Resolver
500 495
) -> il::Program throws (Error) {
501 496
    let entryIdx = ctx.entryPkgIdx else {
502 497
        panic "lowerAllPackages: no entry package";
513 508
    // Finalize and return the unified program.
514 509
    return lower::finalize(&low);
515 510
}
516 511
517 512
/// Lower all packages into an existing lowerer.
518 -
fn lowerAllPackagesInto(
513 +
unsafe fn lowerAllPackagesInto(
519 514
    ctx: *mut CompileContext,
520 515
    res: *mut resolver::Resolver,
521 516
    low: *mut lower::Lowerer
522 517
) throws (Error) {
523 518
    let entryIdx = ctx.entryPkgIdx else {
532 527
    // Lower entry package.
533 528
    try lowerPackage(ctx, res, low, &mut ctx.packages[entryIdx], true);
534 529
}
535 530
536 531
/// Lower all modules in a package into the lowerer accumulator.
537 -
fn lowerPackage(
532 +
unsafe fn lowerPackage(
538 533
    ctx: *CompileContext,
539 534
    res: *mut resolver::Resolver,
540 535
    low: *mut lower::Lowerer,
541 536
    pkg: *mut package::Package,
542 537
    isEntry: bool
550 545
551 546
    try lowerModuleTreeInto(ctx, low, &ctx.graph, rootId, isEntry, pkg);
552 547
}
553 548
554 549
/// Recursively lower a module and all its children into the accumulator.
555 -
fn lowerModuleTreeInto(
550 +
unsafe fn lowerModuleTreeInto(
556 551
    ctx: *CompileContext,
557 552
    low: *mut lower::Lowerer,
558 553
    graph: *module::ModuleGraph,
559 554
    modId: u16,
560 555
    isRoot: bool,
582 577
        try lowerModuleTreeInto(ctx, low, graph, childId, false, pkg);
583 578
    }
584 579
}
585 580
586 581
/// Build a scope access chain: a::b::c from a slice of identifiers.
587 -
fn synthScopeAccess(arena: *mut ast::NodeArena, path: *[*[u8]]) -> *ast::Node {
582 +
unsafe fn synthScopeAccess(arena: *mut ast::NodeArena, path: *[*[u8]]) -> *ast::Node {
588 583
    let mut result = ast::synthNode(
589 584
        arena,
590 585
        ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, path[0]))
591 586
    );
592 587
    for i in 1..path.len {
600 595
    }
601 596
    return result;
602 597
}
603 598
604 599
/// Check if a function declaration has the `@test` attribute and return its name if so.
605 -
fn getTestFnName(decl: *ast::FnDecl) -> ?*[u8] {
600 +
unsafe fn getTestFnName(decl: *ast::FnDecl) -> ?*[u8] {
606 601
    let attrs = decl.attrs else { return nil; };
607 602
    if not ast::attributesContains(&attrs, ast::Attribute::Test) {
608 603
        return nil;
609 604
    }
610 605
    let case ast::NodeValue::Ident(name) = decl.name.value
612 607
613 608
    return name;
614 609
}
615 610
616 611
/// Scan a single module's AST for `@test` functions and append them to `tests`.
617 -
fn collectModuleTests(
612 +
unsafe fn collectModuleTests(
618 613
    entry: *module::ModuleEntry,
619 614
    tests: *mut [TestDesc],
620 615
    testCount: *mut u32
621 616
) {
622 617
    let modAst = entry.ast else {
640 635
        }
641 636
    }
642 637
}
643 638
644 639
/// Synthesize a `testing::test("mod", "name", mod::fn)` call for one test.
645 -
fn synthTestCall(arena: *mut ast::NodeArena, desc: *TestDesc) -> *ast::Node {
640 +
unsafe fn synthTestCall(arena: *mut ast::NodeArena, desc: *TestDesc) -> *ast::Node {
646 641
    let callee = synthScopeAccess(arena, &["testing", "test"]);
647 642
    let modStr = il::formatQualifiedName(
648 643
        &mut arena.arena,
649 644
        &desc.modPath[..desc.modPath.len - 1],
650 645
        desc.modPath[desc.modPath.len - 1]
682 677
///     ]);
683 678
/// }
684 679
/// ```
685 680
///
686 681
/// Uses `#`-prefixed names to avoid conflicts with user code.
687 -
fn generateTestRunner(
682 +
unsafe fn generateTestRunner(
688 683
    ctx: *mut CompileContext,
689 684
    arena: *mut ast::NodeArena
690 685
) throws (Error) {
691 686
    let entryPkg = try getEntryPackage(ctx);
692 687
    let root = try getRootModule(entryPkg, &ctx.graph);
712 707
713 708
    injectIntoBlock(root.ast, arena, fnDecl);
714 709
}
715 710
716 711
/// Synthesize the test entry point.
717 -
fn synthTestMainFn(arena: *mut ast::NodeArena, tests: *[TestDesc]) -> *ast::Node {
712 +
unsafe fn synthTestMainFn(arena: *mut ast::NodeArena, tests: *[TestDesc]) -> *ast::Node {
718 713
    // Build array literal: `[testing::test(...), ...]`.
719 714
    let a = alloc::arenaAllocator(&mut arena.arena);
720 715
    let mut elements = ast::nodeSlice(arena, tests.len as u32);
721 716
    for i in 0..tests.len {
722 717
        elements.append(synthTestCall(arena, &tests[i]), a);
760 755
        name: fnName, sig: fnSig, body: fnBody, attrs: fnAttrs,
761 756
    }));
762 757
}
763 758
764 759
/// Append a declaration to a block node's statement list.
765 -
fn injectIntoBlock(
760 +
unsafe fn injectIntoBlock(
766 761
    blockNode: *mut ast::Node,
767 762
    arena: *mut ast::NodeArena,
768 763
    decl: *ast::Node
769 764
) {
770 765
    let case ast::NodeValue::Block(block) = blockNode.value else {
773 768
    let stmts = block.statements.append(decl, alloc::arenaAllocator(&mut arena.arena));
774 769
    set blockNode.value = ast::NodeValue::Block(ast::Block { statements: stmts });
775 770
}
776 771
777 772
/// Write a self-contained RV64 image containing text and data sections.
778 -
fn writeImage(
773 +
unsafe fn writeImage(
779 774
    code: *[u32],
780 775
    roData: *[u8],
781 776
    rwData: *[u8],
782 777
    path: *[u8]
783 778
) -> bool {
789 784
    return unix::writeFileParts(path, &[headerBytes, codeBytes, roData, rwData]);
790 785
}
791 786
792 787
/// Write a data section to a file at `basePath` + `ext`.
793 788
/// Empty data truncates any stale sidecar left by an earlier build.
794 -
fn writeDataWithExt(
789 +
unsafe fn writeDataWithExt(
795 790
    data: *[u8],
796 791
    basePath: *[u8],
797 792
    ext: *[u8]
798 793
) throws (Error) {
799 794
    let mut path: [u8; MAX_PATH_LEN] = undefined;
809 804
}
810 805
811 806
/// Serialize debug entries and write the `.debug` file.
812 807
/// Resolves module IDs to file paths via the module graph.
813 808
/// Format per entry is `{pc: u32,  offset: u32, filePath: [u8], NULL}`.
814 -
fn writeDebugInfo(
809 +
unsafe fn writeDebugInfo(
815 810
    entries: *[types::DebugEntry],
816 811
    graph: *module::ModuleGraph,
817 812
    basePath: *[u8],
818 813
    arena: *mut alloc::Arena
819 814
) throws (Error) {
838 833
    }
839 834
    try writeDataWithExt(&buf[..pos], basePath, DEBUG_EXT);
840 835
}
841 836
842 837
/// Run the resolver on the parsed modules.
843 -
fn runResolver(ctx: *mut CompileContext, nodeCount: u32) -> resolver::Resolver throws (Error) {
838 +
unsafe fn runResolver(ctx: *mut CompileContext, nodeCount: u32) -> resolver::Resolver throws (Error) {
844 839
    let mut mainArena = alloc::new(&mut MAIN_ARENA[..]);
845 840
    let entryPkg = try getEntryPackage(ctx);
846 841
847 842
    pkgLog(entryPkg, &["resolving", ".."]);
848 843
885 880
        throw error(&["failed:", countStr, "errors"]);
886 881
    }
887 882
    return res;
888 883
}
889 884
890 -
/// Emit one lowered function to machine code and reclaim its IL arena.
891 -
fn generateLoweredFn(ctxPtr: *mut opaque, func: *il::Fn, role: lower::FnRole) {
892 -
    let ctx = ctxPtr as *mut CodegenSinkContext;
893 -
894 -
    match role {
895 -
        case lower::FnRole::Default => {
896 -
            rv64::recordFunctionAlias(ctx.generator, DEFAULT_ENTRY_SYMBOL);
897 -
            match ctx.generator.entryPatch {
898 -
                case rv64::EntryPatch::Reserved(_) => {
899 -
                    set ctx.generator.entryPatch = rv64::EntryPatch::Reserved(func.name);
900 -
                }
901 -
                // No entry jump was reserved: startup assembly calls the
902 -
                // default function through `DEFAULT_ENTRY_SYMBOL` instead.
903 -
                case rv64::EntryPatch::None => {}
904 -
            }
905 -
        }
906 -
        else => {}
907 -
    }
908 -
    rv64::generateFunction(ctx.generator, func, ctx.fnArena);
909 -
    alloc::reset(ctx.fnArena);
910 -
}
911 885
912 886
/// Assemble one `.ras` input and merge it into the active code generator.
913 887
///
914 888
/// Text symbols are appended to `generator`. Data emitted by the assembler is
915 889
/// copied into `ASM_RO_DATA_BUF` at `*asmDataLen`, and `*asmDataLen` is advanced
916 890
/// so the next assembly module receives the correct rodata base address.
917 -
fn assembleAsmModule(
891 +
unsafe fn assembleAsmModule(
918 892
    generator: *mut rv64::Generator,
919 893
    pkg: *package::Package,
920 894
    path: *[u8],
921 895
    asmDataLen: *mut u32,
922 896
    arena: *mut alloc::Arena
948 922
949 923
    rv64::addAssembly(generator, program);
950 924
}
951 925
952 926
/// Assemble all inputs collected in the package inputs.
953 -
fn assembleAsmInputs(
927 +
unsafe fn assembleAsmInputs(
954 928
    ctx: *CompileContext,
955 929
    generator: *mut rv64::Generator,
956 930
    asmDataLen: *mut u32,
957 931
    arena: *mut alloc::Arena
958 932
) -> *[u8] throws (Error) {
970 944
    }
971 945
    return &ASM_RO_DATA_BUF[..*asmDataLen];
972 946
}
973 947
974 948
/// Lower all packages while streaming each lowered function into RV64 codegen.
975 -
fn lowerAndGenerateAllPackages(
949 +
unsafe fn lowerAndGenerateAllPackages(
976 950
    ctx: *mut CompileContext,
977 951
    res: *mut resolver::Resolver,
978 952
    fnArena: *mut alloc::Arena,
979 953
    codegenOptions: CodegenOptions
980 954
) -> rv64::Program throws (Error) {
997 971
    }
998 972
    let mut generator = rv64::beginProgram(
999 973
        rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug },
1000 974
        &mut res.arena
1001 975
    );
1002 -
    let mut codegenCtx = CodegenSinkContext {
976 +
    let mut codegenCtx = codegenSink::Context {
1003 977
        generator: &mut generator,
1004 978
        fnArena,
1005 979
    };
1006 980
    let mut low = lower::lowerer(
1007 981
        res, &ctx.graph, entryPkg.name, &mut res.arena, fnArena, options
1008 982
    );
1009 983
    set low.output = lower::FnOutput::Stream(lower::FnSink {
1010 -
        ctx: &mut codegenCtx as *mut opaque,
1011 -
        emitFn: generateLoweredFn,
984 +
        ctx: codegenSink::eraseContext(&mut codegenCtx),
985 +
        emitFn: codegenSink::generate,
1012 986
    });
1013 987
    let mut asmDataLen: u32 = 0;
1014 988
    if let path = startupPath {
1015 989
        try assembleAsmModule(&mut generator, entryPkg, path, &mut asmDataLen, &mut res.arena);
1016 990
    }
1030 1004
    }
1031 1005
    return rv64::finishProgram(&mut generator, &low.data[..], storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]);
1032 1006
}
1033 1007
1034 1008
/// Lower, optionally dump, and optionally generate binary output.
1035 -
fn compile(
1009 +
unsafe fn compile(
1036 1010
    ctx: *mut CompileContext,
1037 1011
    res: *mut resolver::Resolver,
1038 1012
    fnArena: *mut alloc::Arena
1039 1013
) throws (Error) {
1040 1014
    let entryPkg = try getEntryPackage(ctx);
1086 1060
        try writeDebugInfo(result.debugEntries, &ctx.graph, outPath, &mut res.arena);
1087 1061
    }
1088 1062
    pkgLog(entryPkg, &["ok", "(", outPath, ")"]);
1089 1063
}
1090 1064
1091 -
@default fn main(env: *sys::Env) -> i32 {
1065 +
@default unsafe fn main(env: *sys::Env) -> i32 {
1092 1066
    let mut arena = ast::nodeArena(&mut TEMP_ARENA[..]);
1093 1067
    let mut ctx = try processCommand(env.args, &mut arena) catch {
1094 1068
        return 1;
1095 1069
    };
1096 1070
    match ctx.dump {
compiler/radiance/codegenSink.rad added +49 -0
1 +
//! Trusted streamed code generation implementation.
2 +
use std::lang::alloc;
3 +
use std::lang::il;
4 +
use std::lang::lower;
5 +
use std::arch::rv64;
6 +
7 +
/// Symbol name exported for startup code to call the semantic entry function.
8 +
unsafe constant DEFAULT_ENTRY_SYMBOL: *[u8] = "::default";
9 +
10 +
/// State carried by the streaming lowerer/codegen callback.
11 +
export record Context {
12 +
    /// RV64 generator receiving lowered functions.
13 +
    generator: *mut rv64::Generator,
14 +
    /// Arena holding the current function's lowered IL.
15 +
    fnArena: *mut alloc::Arena,
16 +
}
17 +
18 +
/// Erase a streamed code generation context for storage in a function sink.
19 +
///
20 +
/// The caller must keep `ctx` alive and exclusively accessible until the sink
21 +
/// stops invoking its callback.
22 +
export unsafe fn eraseContext(ctx: &mut Context) -> *unsafe mut u8 {
23 +
    return ((ctx as &mut opaque) as &mut u8) as *unsafe mut u8;
24 +
}
25 +
26 +
/// Generate machine code for a lowered function and reclaim its IL arena.
27 +
export fn generate(ctx: &mut opaque, func: *il::Fn, role: lower::FnRole) {
28 +
    generateWithContext(ctx as &mut Context, func, role);
29 +
}
30 +
31 +
/// Generate machine code with the typed streamed-lowering context.
32 +
fn generateWithContext(ctx: &mut Context, func: *il::Fn, role: lower::FnRole) {
33 +
    match role {
34 +
        case lower::FnRole::Default => {
35 +
            rv64::recordFunctionAlias(ctx.generator, DEFAULT_ENTRY_SYMBOL);
36 +
            match ctx.generator.entryPatch {
37 +
                case rv64::EntryPatch::Reserved(_) => {
38 +
                    set ctx.generator.entryPatch = rv64::EntryPatch::Reserved(func.name);
39 +
                }
40 +
                // No entry jump was reserved: startup assembly calls the
41 +
                // default function through `DEFAULT_ENTRY_SYMBOL` instead.
42 +
                case rv64::EntryPatch::None => {}
43 +
            }
44 +
        }
45 +
        else => {}
46 +
    }
47 +
    rv64::generateFunction(ctx.generator, func, ctx.fnArena);
48 +
    alloc::reset(ctx.fnArena);
49 +
}
lib/std.rad +11 -11
1 1
//! The Radiance Standard Library.
2 2
3 -
export mod io;
4 -
export mod collections;
3 +
export unsafe mod io;
4 +
export unsafe mod collections;
5 5
export mod char;
6 -
export mod lang;
7 -
export mod sys;
8 -
export mod arch;
9 -
export mod fmt;
10 -
export mod mem;
11 -
export mod vec;
12 -
export mod intrinsics;
6 +
export unsafe mod lang;
7 +
export unsafe mod sys;
8 +
export unsafe mod arch;
9 +
export unsafe mod fmt;
10 +
export unsafe mod mem;
11 +
export unsafe mod vec;
12 +
export unsafe mod intrinsics;
13 13
14 14
// Test modules.
15 -
@test export mod testing;
16 -
@test export mod tests;
15 +
@test export unsafe mod testing;
16 +
@test export unsafe mod tests;
lib/std/arch/rv64/asm.rad +4 -4
260 260
export constant SHIFT_LIMIT: i32 = 64;
261 261
/// Largest `lui` or `auipc` immediate.
262 262
export constant UPPER_IMM_MAX_VALUE: i64 = 0xFFFFF;
263 263
264 264
/// Sorted instruction descriptor table used by the assembler parser.
265 -
export constant INSTRUCTIONS: [InstructionEntry; 88] = [
265 +
export unsafe constant INSTRUCTIONS: [InstructionEntry; 88] = [
266 266
    { name: "add",    encoder: InstructionEncoder::RRR { enc: encode::add } },
267 267
    { name: "addi",   encoder: InstructionEncoder::RRI { enc: encode::addi } },
268 268
    { name: "addiw",  encoder: InstructionEncoder::RRI { enc: encode::addiw } },
269 269
    { name: "addw",   encoder: InstructionEncoder::RRR { enc: encode::addw } },
270 270
    { name: "and",    encoder: InstructionEncoder::RRR { enc: encode::and_ } },
352 352
    { name: "xor",    encoder: InstructionEncoder::RRR { enc: encode::xor } },
353 353
    { name: "xori",   encoder: InstructionEncoder::RRI { enc: encode::xori } },
354 354
];
355 355
356 356
/// Sorted directive lookup table used by the assembler parser.
357 -
export constant DIRECTIVES: [DirectiveEntry; 10] = [
357 +
export unsafe constant DIRECTIVES: [DirectiveEntry; 10] = [
358 358
    { name: "align",    kind: DirectiveKind::Align },
359 359
    { name: "ascii",    kind: DirectiveKind::Ascii },
360 360
    { name: "byte",     kind: DirectiveKind::Byte },
361 361
    { name: "constant", kind: DirectiveKind::Constant },
362 362
    { name: "data",     kind: DirectiveKind::Data },
366 366
    { name: "text",     kind: DirectiveKind::Text },
367 367
    { name: "word",     kind: DirectiveKind::Word },
368 368
];
369 369
370 370
/// Sorted register-name lookup table used by the assembler parser.
371 -
export constant REGISTERS: [RegisterEntry; 33] = [
371 +
export unsafe constant REGISTERS: [RegisterEntry; 33] = [
372 372
    { name: "a0",   reg: rv64::A0 },
373 373
    { name: "a1",   reg: rv64::A1 },
374 374
    { name: "a2",   reg: rv64::A2 },
375 375
    { name: "a3",   reg: rv64::A3 },
376 376
    { name: "a4",   reg: rv64::A4 },
403 403
    { name: "tp",   reg: rv64::TP },
404 404
    { name: "zero", reg: rv64::ZERO },
405 405
];
406 406
407 407
/// Sorted CSR-name lookup table used by the assembler parser.
408 -
export constant CSRS: [CsrEntry; 9] = [
408 +
export unsafe constant CSRS: [CsrEntry; 9] = [
409 409
    { name: "mcause",   csr: 0x342 },
410 410
    { name: "mepc",     csr: 0x341 },
411 411
    { name: "mhartid",  csr: 0xF14 },
412 412
    { name: "mie",      csr: 0x304 },
413 413
    { name: "mip",      csr: 0x344 },
lib/std/arch/rv64/asm/scanner/tests.rad +1 -1
1 1
use std::mem;
2 2
use std::testing;
3 3
use std::lang::strings;
4 4
5 5
/// String pool used by assembler scanner tests.
6 -
static TEST_STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
6 +
unsafe static TEST_STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
7 7
8 8
/// Create a scanner for test input.
9 9
fn testScanner(source: *[u8]) -> super::Scanner {
10 10
    return super::scanner(super::SourceKind::String, source, &mut TEST_STRING_POOL);
11 11
}
lib/std/arch/rv64/asm/tests.rad +6 -6
9 9
use std::arch::rv64::encode;
10 10
use std::arch::rv64::printer;
11 11
12 12
use super::scanner;
13 13
14 -
static ASM_ARENA_STORAGE: [u8; 65536] = undefined;
15 -
static ASM_TEXT_STORAGE: [u32; 256] = undefined;
16 -
static ASM_DATA_STORAGE: [u8; 1024] = undefined;
17 -
static ASM_STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
18 -
static PRINT_ARENA_STORAGE: [u8; 1024] = undefined;
19 -
static PRINT_BUFFER: [u8; 128] = undefined;
14 +
unsafe static ASM_ARENA_STORAGE: [u8; 65536] = undefined;
15 +
unsafe static ASM_TEXT_STORAGE: [u32; 256] = undefined;
16 +
unsafe static ASM_DATA_STORAGE: [u8; 1024] = undefined;
17 +
unsafe static ASM_STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
18 +
unsafe static PRINT_ARENA_STORAGE: [u8; 1024] = undefined;
19 +
unsafe static PRINT_BUFFER: [u8; 128] = undefined;
20 20
21 21
fn assembleSource(source: *[u8]) -> super::Program throws (testing::TestError) {
22 22
    let mut arena = alloc::new(&mut ASM_ARENA_STORAGE[..]);
23 23
    return try super::assemble(
24 24
        scanner::SourceKind::String,
lib/std/arch/rv64/printer.rad +1 -1
15 15
/////////////////////
16 16
// Register Names  //
17 17
/////////////////////
18 18
19 19
/// ABI register names.
20 -
constant REG_NAMES: [*[u8]; 32] = [
20 +
unsafe constant REG_NAMES: [*[u8]; 32] = [
21 21
    "%zero", "%ra", "%sp", "%gp", "%tp", "%t0", "%t1", "%t2",
22 22
    "%fp", "%s1", "%a0", "%a1", "%a2", "%a3", "%a4", "%a5",
23 23
    "%a6", "%a7", "%s2", "%s3", "%s4", "%s5", "%s6", "%s7",
24 24
    "%s8", "%s9", "%s10", "%s11", "%t3", "%t4", "%t5", "%t6"
25 25
];
lib/std/intrinsics.rad +6 -3
1 1
//! Compiler intrinsics.
2 2
3 -
/// Environment call.
3 +
/// Issue a system call with the given number and arguments.
4 4
///
5 -
/// Issues a system call with the given number and arguments.
6 5
/// Arguments and the return value are `i64` to support both 32-bit
7 6
/// emulator addresses and 64-bit native (AMD64) pointers.
8 -
@intrinsic export fn ecall(number: u32, arg1: i64, arg2: i64, arg3: i64, arg4: i64) -> i64;
7 +
///
8 +
/// The caller must satisfy the target system call's ABI. Every argument that
9 +
/// the kernel interprets as an address must remain valid and readable or
10 +
/// writable for the full extent and duration required by that system call.
11 +
@intrinsic export unsafe fn ecall(number: u32, arg1: i64, arg2: i64, arg3: i64, arg4: i64) -> i64;
9 12
10 13
/// Break out of program.
11 14
@intrinsic export fn ebreak();
12 15
13 16
/// Full acquire/release memory fence.
lib/std/io.rad +5 -3
1 1
//! Input/output utilities.
2 2
use std::fmt;
3 3
use std::intrinsics;
4 4
5 5
export fn print(str: *[u8]) {
6 -
    intrinsics::ecall(64, 1, str.ptr as i64, str.len as i64, 0);
6 +
    unsafe { intrinsics::ecall(64, 1, str.ptr as i64, str.len as i64, 0); }
7 7
}
8 8
9 9
export fn printError(str: *[u8]) {
10 -
    intrinsics::ecall(64, 2, str.ptr as i64, str.len as i64, 0);
10 +
    unsafe { intrinsics::ecall(64, 2, str.ptr as i64, str.len as i64, 0); }
11 11
}
12 12
13 13
export fn printLn(str: *[u8]) {
14 14
    print(str);
15 15
    print("\n");
32 32
    let result: *[u8] = fmt::formatBool(val, &mut buffer[..]);
33 33
    print(result);
34 34
}
35 35
36 36
export fn read(buf: *mut [u8]) -> u32 {
37 -
    return intrinsics::ecall(63, 0, buf.ptr as i64, buf.len as i64, 0) as u32;
37 +
    let mut result: i64 = 0;
38 +
    unsafe { set result = intrinsics::ecall(63, 0, buf.ptr as i64, buf.len as i64, 0); }
39 +
    return result as u32;
38 40
}
39 41
40 42
export fn readToEnd(buf: *mut [u8]) -> *[u8] {
41 43
    let mut total: u32 = 0;
42 44
lib/std/lang.rad +12 -12
1 1
//! Radiance language implementation.
2 -
export mod alloc;
3 -
export mod sexpr;
2 +
export unsafe mod alloc;
3 +
export unsafe mod sexpr;
4 4
export mod types;
5 -
export mod strings;
6 -
export mod scanner;
7 -
export mod ast;
8 -
export mod parser;
9 -
export mod module;
10 -
export mod resolver;
11 -
export mod package;
12 -
export mod il;
13 -
export mod lower;
14 -
export mod gen;
5 +
export unsafe mod strings;
6 +
export unsafe mod scanner;
7 +
export unsafe mod ast;
8 +
export unsafe mod parser;
9 +
export unsafe mod module;
10 +
export unsafe mod resolver;
11 +
export unsafe mod package;
12 +
export unsafe mod il;
13 +
export unsafe mod lower;
14 +
export unsafe mod gen;
lib/std/lang/alloc.rad +40 -15
4 4
//! byte buffer. Memory is never freed individually - the entire arena is
5 5
//! reset at once. This is ideal for compiler passes where all allocations
6 6
//! have the same lifetime.
7 7
@test mod tests;
8 8
9 -
use std::mem;
9 +
/// Largest byte extent representable by allocator APIs.
10 +
constant MAX_EXTENT: u32 = 0xFFFFFFFF;
10 11
11 12
/// Error thrown by allocator.
12 13
export union AllocError {
13 14
    /// Allocator is out of memory.
14 15
    OutOfMemory,
30 31
    return Arena { data, offset: 0 };
31 32
}
32 33
33 34
/// Allocate `size` bytes with the given alignment.
34 35
///
36 +
/// `size` must be nonzero and `alignment` must be a nonzero power of two.
35 37
/// Returns an opaque pointer to the allocated memory. Throws `AllocError` if
36 -
/// the arena is exhausted. The caller is responsible for casting to the
37 -
/// appropriate type and initializing the memory.
38 +
/// the arena is exhausted or its offset lies beyond the backing storage. The
39 +
/// caller is responsible for casting to the appropriate type and initializing
40 +
/// the memory.
38 41
export fn alloc(arena: *mut Arena, size: u32, alignment: u32) -> *mut opaque throws (AllocError) {
39 42
    assert alignment > 0;
43 +
    assert (alignment & (alignment - 1)) == 0;
40 44
    assert size > 0;
41 45
42 -
    let aligned = mem::alignUp(arena.offset, alignment);
43 -
    let newOffset = aligned + size;
44 -
45 -
    if newOffset > arena.data.len as u32 {
46 +
    let capacity = arena.data.len as u32;
47 +
    if arena.offset > capacity {
48 +
        throw AllocError::OutOfMemory;
49 +
    }
50 +
    let available = capacity - arena.offset;
51 +
    let padding = (0 - arena.offset) & (alignment - 1);
52 +
    if padding > available {
53 +
        throw AllocError::OutOfMemory;
54 +
    }
55 +
    let alignedAvailable = available - padding;
56 +
    if size > alignedAvailable {
46 57
        throw AllocError::OutOfMemory;
47 58
    }
59 +
    let aligned = arena.offset + padding;
60 +
    let newOffset = aligned + size;
48 61
    let base: *mut u8 = &mut arena.data[aligned];
49 62
    set arena.offset = newOffset;
50 63
51 64
    return base as *mut opaque;
52 65
}
53 66
54 67
/// Reset the arena, allowing all memory to be reused.
55 68
///
69 +
/// The caller must not use any allocation from this arena after the reset.
56 70
/// Does not zero the memory.
57 -
export fn reset(arena: *mut Arena) {
71 +
export unsafe fn reset(arena: *mut Arena) {
58 72
    set arena.offset = 0;
59 73
}
60 74
61 75
/// Save the current arena state for later restoration.
62 76
export fn save(arena: *Arena) -> u32 {
63 77
    return arena.offset;
64 78
}
65 79
66 80
/// Restore the arena to a previously saved state, reclaiming all
67 81
/// allocations made since that point.
68 -
export fn restore(arena: *mut Arena, savedOffset: u32) {
82 +
///
83 +
/// The caller must not use an allocation reclaimed by this operation.
84 +
export unsafe fn restore(arena: *mut Arena, savedOffset: u32) {
69 85
    set arena.offset = savedOffset;
70 86
}
71 87
72 88
/// Returns the number of bytes currently allocated.
73 89
export fn used(arena: *Arena) -> u32 {
77 93
/// Returns the number of bytes remaining in the arena.
78 94
export fn remaining(arena: *Arena) -> u32 {
79 95
    return arena.data.len as u32 - arena.offset;
80 96
}
81 97
82 -
/// Returns the remaining buffer as a mutable slice.
83 -
export fn remainingBuf(arena: *mut Arena) -> *mut [u8] {
98 +
/// Return an exclusive view of the remaining buffer.
99 +
///
100 +
/// The caller must end access through the view before another arena operation.
101 +
export unsafe fn remainingBuf(arena: *mut Arena) -> *mut [u8] {
84 102
    return &mut arena.data[arena.offset..];
85 103
}
86 104
87 -
/// Commits `size` bytes of allocation, advancing the offset.
88 -
/// Use after writing to the buffer returned by [`remainingBuf`].
89 -
export fn commit(arena: *mut Arena, size: u32) {
105 +
/// Commit `size` bytes of allocation and advance the offset.
106 +
///
107 +
/// The caller must provide a size within the view returned by [`remainingBuf`]
108 +
/// and must end access through that view before another arena operation.
109 +
export unsafe fn commit(arena: *mut Arena, size: u32) {
110 +
    assert size <= remaining(arena), "alloc::commit: size exceeds remaining capacity";
90 111
    set arena.offset += size;
91 112
}
92 113
93 114
/// Allocate a slice of `count` elements, each of `size` bytes with given alignment.
94 115
///
95 116
/// Returns a type-erased slice that should be cast to the appropriate `*[T]`.
96 117
/// The slice length is set to `count` (element count, not bytes).
97 -
/// Throws `AllocError` if the arena is exhausted.
118 +
/// Throws `AllocError` if the arena is exhausted or the total byte extent
119 +
/// cannot be represented by `u32`.
98 120
export fn allocSlice(arena: *mut Arena, size: u32, alignment: u32, count: u32) -> *mut [opaque] throws (AllocError) {
99 121
    if count == 0 {
100 122
        return &mut [];
101 123
    }
124 +
    if size > MAX_EXTENT / count {
125 +
        throw AllocError::OutOfMemory;
126 +
    }
102 127
    let ptr = try alloc(arena, size * count, alignment);
103 128
104 129
    return @sliceOf(ptr, count);
105 130
}
106 131
lib/std/lang/alloc/tests.rad +29 -0
102 102
        set failed = true;
103 103
    };
104 104
    try testing::expect(failed);
105 105
}
106 106
107 +
/// Test that a forged arena offset cannot wrap allocation arithmetic.
108 +
@test fn testAllocRejectsInvalidOffset() throws (testing::TestError) {
109 +
    static STORAGE: [u8; 16] = undefined;
110 +
    let mut arena = super::Arena { data: &mut STORAGE[..], offset: 0xFFFFFFFF };
111 +
    let mut failed = false;
112 +
113 +
    try super::alloc(&mut arena, 1, 1) catch {
114 +
        set failed = true;
115 +
    };
116 +
117 +
    try testing::expect(failed);
118 +
    try testing::expect(super::used(&arena) == 0xFFFFFFFF);
119 +
}
120 +
121 +
/// Test that slice byte extents cannot overflow u32.
122 +
@test fn testAllocSliceExtentOverflow() throws (testing::TestError) {
123 +
    static STORAGE: [u8; 16] = undefined;
124 +
    let mut arena = super::new(&mut STORAGE[..]);
125 +
    let count: u32 = 0xFFFFFFFF / 3 + 1;
126 +
    let mut failed = false;
127 +
128 +
    try super::allocSlice(&mut arena, 3, 1, count) catch {
129 +
        set failed = true;
130 +
    };
131 +
132 +
    try testing::expect(failed);
133 +
    try testing::expect(super::used(&arena) == 0);
134 +
}
135 +
107 136
/// Test the Allocator interface backed by an arena.
108 137
@test fn testAllocator() throws (testing::TestError) {
109 138
    static STORAGE: [u8; 256] = undefined;
110 139
    let mut arena = super::new(&mut STORAGE[..]);
111 140
    let a = super::arenaAllocator(&mut arena);
lib/std/lang/ast.rad +2 -0
630 630
        /// Argument list.
631 631
        args: *mut [*Node],
632 632
    },
633 633
    /// Block expression or statement body.
634 634
    Block(Block),
635 +
    /// Scoped unsafe block (`unsafe { ... }`).
636 +
    Unsafe(*Node),
635 637
    /// Call expression, eg. `f(x)`.
636 638
    Call(Call),
637 639
    /// Field access expression (e.g. `foo.bar`).
638 640
    FieldAccess(Access),
639 641
    /// Scope access expression (e.g. `foo::bar`).
lib/std/lang/ast/printer.rad +2 -0
367 367
        }
368 368
        case super::NodeValue::Block(blk) => {
369 369
            let children = nodeListToExprs(a, &blk.statements[..]);
370 370
            return sexpr::block(a, "block", &[], children);
371 371
        }
372 +
        case super::NodeValue::Unsafe(body) =>
373 +
            return sexpr::list(a, "unsafe", &[toExpr(a, body)]),
372 374
        case super::NodeValue::Let(decl) => {
373 375
            let mut head = "let";
374 376
            if decl.mutable { set head = "let-mut"; }
375 377
            return sexpr::list(a, head, &[
376 378
                toExpr(a, decl.ident),
lib/std/lang/gen/regalloc/assign.rad +22 -12
158 158
                allocatable,
159 159
                calleeSaved: config.calleeSaved,
160 160
                assignments,
161 161
                spillInfo,
162 162
            };
163 -
            il::forEachReg(instr, processInstrRegCb, &mut ctx as *mut opaque);
163 +
            il::forEachReg(instr, processInstrRegCb, &mut ctx);
164 164
165 165
            // Allocate destination.
166 166
            if let dst = il::instrDst(instr) {
167 167
                if dst.n < maxReg and not spill::isSpilled(spillInfo, dst) {
168 168
                    set assignments[dst.n] = rallocReg(&mut current, &mut usedRegs, dst.n, allocatable, config.calleeSaved, spillInfo);
278 278
    }
279 279
    panic "rallocReg: no free register, spilling fault";
280 280
}
281 281
282 282
/// Callback for [`il::forEachReg`]: free last uses and allocate missing uses.
283 -
fn processInstrRegCb(reg: il::Reg, ctxPtr: *mut opaque) {
284 -
    let ctx = ctxPtr as *mut InstrCtx;
285 -
    if not liveness::hasLaterUse(ctx.live, ctx.func, ctx.blockIdx, ctx.instrIdx, reg) {
286 -
        if let phys = rmapRemove(ctx.current, reg.n) {
287 -
            bitset::clear(ctx.usedRegs, *phys as u32);
283 +
fn processInstrRegCb(reg: il::Reg, ctx: &mut opaque) {
284 +
    if not liveness::hasLaterUse(
285 +
        (ctx as &mut InstrCtx).live,
286 +
        (ctx as &mut InstrCtx).func,
287 +
        (ctx as &mut InstrCtx).blockIdx,
288 +
        (ctx as &mut InstrCtx).instrIdx,
289 +
        reg
290 +
    ) {
291 +
        if let phys = rmapRemove((ctx as &mut InstrCtx).current, reg.n) {
292 +
            bitset::clear((ctx as &mut InstrCtx).usedRegs, *phys as u32);
288 293
        }
289 294
    }
290 -
    assert reg.n < ctx.assignments.len, "processInstrRegCb: register out of bounds";
291 -
    if spill::isSpilled(ctx.spillInfo, reg) {
295 +
    assert reg.n < (ctx as &mut InstrCtx).assignments.len,
296 +
        "processInstrRegCb: register out of bounds";
297 +
    if spill::isSpilled((ctx as &mut InstrCtx).spillInfo, reg) {
292 298
        return; // Spilled values don't get physical registers.
293 299
    }
294 -
    if ctx.assignments[reg.n] == nil {
295 -
        set ctx.assignments[reg.n] = rallocReg(
296 -
            ctx.current, ctx.usedRegs, reg.n, ctx.allocatable,
297 -
            ctx.calleeSaved, ctx.spillInfo
300 +
    if (ctx as &mut InstrCtx).assignments[reg.n] == nil {
301 +
        set (ctx as &mut InstrCtx).assignments[reg.n] = rallocReg(
302 +
            (ctx as &mut InstrCtx).current,
303 +
            (ctx as &mut InstrCtx).usedRegs,
304 +
            reg.n,
305 +
            (ctx as &mut InstrCtx).allocatable,
306 +
            (ctx as &mut InstrCtx).calleeSaved,
307 +
            (ctx as &mut InstrCtx).spillInfo
298 308
        );
299 309
    }
300 310
}
lib/std/lang/gen/regalloc/liveness.rad +16 -14
82 82
        let block = &func.blocks[b];
83 83
        for p in block.params {
84 84
            set maxReg = maxRegNum(p.value.n, maxReg);
85 85
        }
86 86
        for i in 0..block.instrs.len {
87 -
            il::forEachReg(block.instrs[i], maxRegCallback, &mut maxReg as *mut opaque);
87 +
            il::forEachReg(block.instrs[i], maxRegCallback, &mut maxReg);
88 88
            if let dst = il::instrDst(block.instrs[i]) {
89 89
                set maxReg = maxRegNum(dst.n, maxReg);
90 90
            }
91 91
        }
92 92
    }
163 163
        bitset::put(defs, p.value.n);
164 164
    }
165 165
    for i in 0..block.instrs.len {
166 166
        let instr = block.instrs[i];
167 167
        let mut ctx = DefsUses { defs, uses };
168 -
        il::forEachReg(instr, addUseCallback, &mut ctx as *mut opaque);
168 +
        il::forEachReg(instr, addUseCallback, &mut ctx);
169 169
170 170
        if let dst = il::instrDst(instr) {
171 171
            bitset::put(defs, dst.n);
172 172
        }
173 173
    }
174 174
}
175 175
176 176
/// Callback for [`il::forEachReg`]: adds register to uses if not already defined.
177 -
fn addUseCallback(reg: il::Reg, ctx: *mut opaque) {
178 -
    let c = ctx as *mut DefsUses;
179 -
    if not bitset::contains(c.defs, reg.n) {
180 -
        bitset::put(c.uses, reg.n);
177 +
fn addUseCallback(reg: il::Reg, ctx: &mut opaque) {
178 +
    if not bitset::contains((ctx as &mut DefsUses).defs, reg.n) {
179 +
        bitset::put((ctx as &mut DefsUses).uses, reg.n);
181 180
    }
182 181
}
183 182
184 183
/// Callback for [`il::forEachReg`]: updates max register number.
185 -
fn maxRegCallback(reg: il::Reg, ctx: *mut opaque) {
186 -
    let max = ctx as *mut u32;
187 -
    set *max = maxRegNum(reg.n, *max);
184 +
fn maxRegCallback(reg: il::Reg, ctx: &mut opaque) {
185 +
    set *(ctx as &mut u32) = maxRegNum(reg.n, *(ctx as &mut u32));
188 186
}
189 187
190 188
/// Return the larger of n+1 and current.
191 189
fn maxRegNum(n: u32, current: u32) -> u32 {
192 190
    if n + 1 > current {
240 238
}
241 239
242 240
/// Check if an instruction uses a specific register.
243 241
fn instrUsesReg(instr: il::Instr, reg: il::Reg) -> bool {
244 242
    let mut ctx = FindCtx { target: reg.n, found: false };
245 -
    il::forEachReg(instr, findRegCallback, &mut ctx as *mut opaque);
243 +
    il::forEachReg(instr, findRegCallback, &mut ctx);
246 244
    return ctx.found;
247 245
}
248 246
249 247
/// Callback for [`il::forEachReg`]: sets found if register matches target.
250 -
fn findRegCallback(reg: il::Reg, ctx: *mut opaque) {
251 -
    let c = ctx as *mut FindCtx;
252 -
    if reg.n == c.target {
253 -
        set c.found = true;
248 +
fn findRegCallback(reg: il::Reg, ctx: &mut opaque) {
249 +
    if reg.n == (ctx as &mut FindCtx).target {
250 +
        markFound(ctx as &mut FindCtx);
254 251
    }
255 252
}
253 +
254 +
/// Mark a register search context as found.
255 +
fn markFound(ctx: &mut FindCtx) {
256 +
    set ctx.found = true;
257 +
}
lib/std/lang/gen/regalloc/spill.rad +14 -8
140 140
            // Remove definition from live set.
141 141
            if let dst = il::instrDst(instr) {
142 142
                bitset::clear(&mut scratch, dst.n);
143 143
            }
144 144
            // Add uses to live set.
145 -
            il::forEachReg(instr, addRegToSetCallback, &mut scratch as *mut opaque);
145 +
            il::forEachReg(instr, addRegToSetCallback, &mut scratch);
146 146
        }
147 147
        // Also limit pressure at block entry.
148 148
        limitPressure(&mut scratch, &mut spilled, costs, numRegs);
149 149
    }
150 150
199 199
                    set costs[dst.n].defs = costs[dst.n].defs + weight;
200 200
                }
201 201
            }
202 202
            // Count uses.
203 203
            let mut ctx = CountCtx { costs, weight };
204 -
            il::forEachReg(instr, countRegUseCallback, &mut ctx as *mut opaque);
204 +
            il::forEachReg(instr, countRegUseCallback, &mut ctx);
205 205
        }
206 206
    }
207 207
}
208 208
209 209
/// Sort candidates by cost (ascending) using insertion sort, then spill
307 307
        }
308 308
    }
309 309
}
310 310
311 311
/// Callback for [`il::forEachReg`]: increments use count for register.
312 -
fn countRegUseCallback(reg: il::Reg, ctxPtr: *mut opaque) {
313 -
    let ctx = ctxPtr as *mut CountCtx;
314 -
    assert reg.n < ctx.costs.len, "countRegUseCallback: register out of bounds";
315 -
    set ctx.costs[reg.n].uses = ctx.costs[reg.n].uses + ctx.weight;
312 +
fn countRegUseCallback(reg: il::Reg, ctx: &mut opaque) {
313 +
    assert reg.n < (ctx as &mut CountCtx).costs.len,
314 +
        "countRegUseCallback: register out of bounds";
315 +
    set (ctx as &mut CountCtx).costs[reg.n].uses =
316 +
        (ctx as &mut CountCtx).costs[reg.n].uses + (ctx as &mut CountCtx).weight;
316 317
}
317 318
318 319
/// Callback for [`il::forEachReg`]: adds register to live set.
319 -
fn addRegToSetCallback(reg: il::Reg, ctx: *mut opaque) {
320 -
    bitset::put(ctx as *mut bitset::Bitset, reg.n);
320 +
fn addRegToSetCallback(reg: il::Reg, ctx: &mut opaque) {
321 +
    if reg.n >= (ctx as &mut bitset::Bitset).len {
322 +
        return;
323 +
    }
324 +
    let word = reg.n / 32;
325 +
    let bit = reg.n % 32;
326 +
    set (ctx as &mut bitset::Bitset).bits[word] |= (1 << bit);
321 327
}
322 328
323 329
/// Check if a register is spilled.
324 330
export fn isSpilled(info: *SpillInfo, reg: il::Reg) -> bool {
325 331
    if reg.n >= info.maxReg {
lib/std/lang/il.rad +6 -3
58 58
// TODO: Labels should have their own type.
59 59
// TODO: Blocks should have an instruction in `Instr`.
60 60
61 61
export mod printer;
62 62
63 +
/// Unit tests for intermediate language traversal.
64 +
@test mod tests;
65 +
63 66
use std::mem;
64 67
use std::lang::alloc;
65 68
66 69
/// Source location for debug info.
67 70
///
77 80
///////////////////////
78 81
// Name Formatting   //
79 82
///////////////////////
80 83
81 84
/// Separator for qualified symbol names.
82 -
export constant PATH_SEPARATOR: *[u8] = "::";
85 +
export unsafe constant PATH_SEPARATOR: *[u8] = "::";
83 86
84 87
/// Format a qualified symbol name: `pkg::mod::path::name`.
85 88
export fn formatQualifiedName(arena: *mut alloc::Arena, path: *[*[u8]], name: *[u8]) -> *[u8] {
86 89
    let mut totalLen: u32 = name.len;
87 90
    for segment in path {
425 428
    }
426 429
}
427 430
428 431
/// Call a function for each register used by an instruction.
429 432
/// This is called by the register allocator to analyze register usage.
430 -
export fn forEachReg(instr: Instr, f: fn(Reg, *mut opaque), ctx: *mut opaque) {
433 +
export fn forEachReg(instr: Instr, f: fn(Reg, &mut opaque), ctx: &mut opaque) {
431 434
    match instr {
432 435
        case Instr::Reserve { size, .. } =>
433 436
            withReg(size, f, ctx),
434 437
        case Instr::Load { src, .. } => f(src, ctx),
435 438
        case Instr::Sload { src, .. } => f(src, ctx),
503 506
             Instr::MemoryFence => {},
504 507
    }
505 508
}
506 509
507 510
/// Call callback if value is a register.
508 -
fn withReg(val: Val, callback: fn(Reg, *mut opaque), ctx: *mut opaque) {
511 +
fn withReg(val: Val, callback: fn(Reg, &mut opaque), ctx: &mut opaque) {
509 512
    if let case Val::Reg(r) = val {
510 513
        callback(r, ctx);
511 514
    }
512 515
}
lib/std/lang/il/tests.rad added +21 -0
1 +
//! Intermediate language tests.
2 +
3 +
use std::testing;
4 +
5 +
/// Add one register number through a call-scoped context loan.
6 +
fn observeReg(reg: super::Reg, ctx: &mut opaque) {
7 +
    unsafe { set *(ctx as &mut u32) += reg.n; }
8 +
}
9 +
10 +
/// Register traversal may reuse one mutable callback context loan.
11 +
@test fn testForEachRegCallbackLoan() throws (testing::TestError) {
12 +
    let mut context: u32 = 0;
13 +
    let instr = super::Instr::Store {
14 +
        typ: super::Type::W32,
15 +
        src: super::Val::Reg(super::Reg { n: 1 }),
16 +
        dst: super::Reg { n: 2 },
17 +
        offset: 0,
18 +
    };
19 +
    super::forEachReg(instr, observeReg, &mut context);
20 +
    assert context == 3;
21 +
}
lib/std/lang/lower.rad +11 -5
264 264
    Default,
265 265
}
266 266
267 267
/// Function sink used by lowerers that consume functions as they are produced.
268 268
export record FnSink {
269 -
    /// Opaque context passed to the sink callback.
270 -
    ctx: *mut opaque,
269 +
    /// Raw storage address reborrowed only for the duration of each callback.
270 +
    ctx: *unsafe mut u8,
271 271
    /// Callback invoked for each lowered function.
272 -
    emitFn: fn(*mut opaque, *il::Fn, FnRole),
272 +
    emitFn: fn(&mut opaque, *il::Fn, FnRole),
273 273
}
274 274
275 275
/// Destination for functions produced by the lowerer.
276 276
export union FnOutput {
277 277
    /// Store lowered functions in the provided slice.
363 363
            let mut fns = accumulated;
364 364
            fns.append(func, self.allocator);
365 365
            set self.output = FnOutput::Accumulate(fns);
366 366
        }
367 367
        case FnOutput::Stream(sink) => {
368 -
            sink.emitFn(sink.ctx, func, role);
368 +
            sink.emitFn(&mut *sink.ctx as &mut opaque, func, role);
369 369
        }
370 370
    }
371 371
}
372 372
373 373
/// Get the function role for a top-level function declaration.
3937 3937
    }
3938 3938
    match node.value {
3939 3939
        case ast::NodeValue::Block(_) => {
3940 3940
            try lowerBlock(self, node);
3941 3941
        }
3942 +
        case ast::NodeValue::Unsafe(body) => {
3943 +
            try lowerNode(self, body);
3944 +
        }
3942 3945
        case ast::NodeValue::Return { value } => {
3943 3946
            try lowerReturnStmt(self, node, value);
3944 3947
        }
3945 3948
        case ast::NodeValue::Throw { expr } => {
3946 3949
            try lowerThrowStmt(self, expr);
6903 6906
    } else {
6904 6907
        throw LowerError::UnknownIntrinsic;
6905 6908
    }
6906 6909
}
6907 6910
6908 -
/// Lower an ecall intrinsic: `ecall(num, a0, a1, a2, a3) -> i32`.
6911 +
/// Lower an ecall intrinsic: `ecall(num, a0, a1, a2, a3) -> i64`.
6909 6912
fn lowerEcall(self: *mut FnLowerer, call: ast::Call) -> il::Val throws (LowerError) {
6910 6913
    if call.args.len <> 5 {
6911 6914
        throw LowerError::InvalidArgCount;
6912 6915
    }
6913 6916
    let num = try lowerExpr(self, call.args[0]);
7266 7269
        }
7267 7270
        case ast::NodeValue::Block(_) => {
7268 7271
            try lowerBlock(self, node);
7269 7272
            set val = il::Val::Undef;
7270 7273
        }
7274 +
        case ast::NodeValue::Unsafe(body) => {
7275 +
            set val = try lowerExpr(self, body);
7276 +
        }
7271 7277
        case ast::NodeValue::ExprStmt(expr) => {
7272 7278
            let _ = expr;
7273 7279
            set val = il::Val::Undef;
7274 7280
        }
7275 7281
        // Lower these as statements.
lib/std/lang/module.rad +1 -1
20 20
/// Maximum number of components that make up a logical module path.
21 21
constant MAX_MODULE_PATH_DEPTH: u32 = 16;
22 22
/// Filesystem separator used when constructing child paths.
23 23
constant PATH_SEP: u8 = '/';
24 24
/// Source file extension handled by the loader.
25 -
constant SOURCE_EXT: *[u8] = ".rad";
25 +
unsafe constant SOURCE_EXT: *[u8] = ".rad";
26 26
27 27
/// Lifecycle state for modules in the dependency graph.
28 28
export union ModuleState {
29 29
    /// Slot unused or yet to be initialized.
30 30
    Vacant,
lib/std/lang/module/tests.rad +1 -1
6 6
use std::lang::strings;
7 7
8 8
/// Test arena backing storage.
9 9
static TEST_ARENA: [u8; 4096] = undefined;
10 10
/// Interned string pool.
11 -
static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
11 +
unsafe static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
12 12
13 13
fn expectSliceEq(actual: *[u8], expected: *[u8])
14 14
    throws (testing::TestError)
15 15
{
16 16
    if not mem::eq(actual, expected) {
lib/std/lang/parser.rad +31 -1
568 568
             ast::NodeValue::WhileLet(_),
569 569
             ast::NodeValue::For(_),
570 570
             ast::NodeValue::Loop { .. },
571 571
             ast::NodeValue::Match(_),
572 572
             ast::NodeValue::Block(_),
573 +
             ast::NodeValue::Unsafe(_),
573 574
             ast::NodeValue::FnDecl(_),
574 575
             ast::NodeValue::RecordDecl(_),
575 576
             ast::NodeValue::UnionDecl(_),
576 577
             ast::NodeValue::TraitDecl { .. },
577 578
             ast::NodeValue::InstanceDecl { .. },
578 579
             ast::NodeValue::MethodDecl { .. } => return false,
579 580
        else => return true,
580 581
    }
581 582
}
582 583
584 +
/// Speculatively parse a scoped unsafe block.
585 +
///
586 +
/// Restores the parser when `unsafe` starts a declaration instead.
587 +
fn tryParseUnsafeBlock(p: *mut Parser) -> ?*ast::Node
588 +
    throws (ParseError)
589 +
{
590 +
    let saved = saveState(p);
591 +
    if not consume(p, scanner::TokenKind::Unsafe) {
592 +
        return nil;
593 +
    }
594 +
    if not check(p, scanner::TokenKind::LBrace) {
595 +
        restoreState(p, &saved);
596 +
        return nil;
597 +
    }
598 +
    let body = try parseBlock(p);
599 +
    return node(p, ast::NodeValue::Unsafe(body));
600 +
}
601 +
583 602
/// Parse a primary leaf expression without postfix operators.
584 603
fn parseLeaf(p: *mut Parser) -> *ast::Node
585 604
    throws (ParseError)
586 605
{
587 606
    match p.current.kind {
591 610
        }
592 611
        case scanner::TokenKind::False => {
593 612
            advance(p);
594 613
            return nodeBool(p, false);
595 614
        }
615 +
        case scanner::TokenKind::Unsafe => {
616 +
            let unsafeBlock = try tryParseUnsafeBlock(p)
617 +
                else throw failParsing(p, "expected `{` after `unsafe`");
618 +
            return unsafeBlock;
619 +
        }
596 620
        case scanner::TokenKind::Ident => {
597 621
            advance(p);
598 622
            return node(p, ast::NodeValue::Ident(p.previous.source));
599 623
        }
600 624
        case scanner::TokenKind::Super => {
830 854
///
831 855
/// Dispatches to the appropriate statement parser based on the current token.
832 856
export fn parseStmt(p: *mut Parser) -> *ast::Node
833 857
    throws (ParseError)
834 858
{
859 +
    if let unsafeBlock = try tryParseUnsafeBlock(p) {
860 +
        return unsafeBlock;
861 +
    }
862 +
835 863
    // TODO: Why is `parseStmt` checking for attributes?
836 864
    // We should have a `parseDecl` which is top-level, and `parseStmt` which
837 865
    // is inside functions.
838 866
    let attrs = parseAttributes(p);
839 867
    if let list = attrs {
840 868
        if ast::attributesContains(&list, ast::Attribute::Unsafe)
841 869
            and p.current.kind <> scanner::TokenKind::Fn
842 870
            and p.current.kind <> scanner::TokenKind::Mod
871 +
            and p.current.kind <> scanner::TokenKind::Static
872 +
            and p.current.kind <> scanner::TokenKind::Constant
843 873
        {
844 -
            throw failParsing(p, "`unsafe` is only allowed on functions and modules");
874 +
            throw failParsing(p, "`unsafe` is only allowed on functions, modules, statics, and constants");
845 875
        }
846 876
        let allowed: bool =
847 877
            p.current.kind == scanner::TokenKind::Fn or
848 878
            p.current.kind == scanner::TokenKind::Union or
849 879
            p.current.kind == scanner::TokenKind::Record or
lib/std/lang/parser/tests.rad +63 -4
14 14
/// Unified arena size.
15 15
constant ARENA_SIZE: u32 = 2097152;
16 16
/// Unified arena storage for all AST allocations.
17 17
static ARENA_STORAGE: [u8; ARENA_SIZE] = undefined;
18 18
/// String pool.
19 -
static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
19 +
unsafe static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
20 20
21 21
/// Assert that a node is an identifier with the given name.
22 22
fn expectIdent(node: *ast::Node, name: *[u8])
23 23
    throws (testing::TestError)
24 24
{
94 94
95 95
    return root;
96 96
}
97 97
98 98
/// Parse a single expression from a string.
99 -
export fn parseExprStr(input: *[u8]) -> *ast::Node
99 +
fn parseExprStr(input: *[u8]) -> *ast::Node
100 100
    throws (super::ParseError)
101 101
{
102 102
    let mut arena = ast::nodeArena(&mut ARENA_STORAGE[..]);
103 103
    let mut parser = super::mkParser(scanner::SourceLoc::String, input, &mut arena, &mut STRING_POOL);
104 104
    super::advance(&mut parser);
270 270
    let r2 = try! parseExprStr("false");
271 271
    let case ast::NodeValue::Bool(v2) = r2.value if not v2
272 272
        else throw testing::TestError::Failed;
273 273
}
274 274
275 +
/// Verify why the static-arena expression parser stays module-private.
276 +
@test fn testParseExprStrResetsSharedArena() throws (testing::TestError) {
277 +
    let first = try! parseExprStr("first");
278 +
    let second = try! parseExprStr("second");
279 +
280 +
    try testing::expect(first == second);
281 +
    try expectIdent(first, "second");
282 +
}
283 +
275 284
/// Test parsing number literals.
276 285
@test fn testParseNumber() throws (testing::TestError) {
277 286
    let r1 = try! parseExprStr("4519");
278 287
    try expectNumber(r1, "4519");
279 288
}
542 551
    let case ast::NodeValue::ExprStmt(thirdExpr) = thirdStmt.value
543 552
        else throw testing::TestError::Failed;
544 553
    try expectIdent(thirdExpr, "third");
545 554
}
546 555
556 +
/// Test parsing an unsafe block and its child block AST.
557 +
@test fn testParseUnsafeBlock() throws (testing::TestError) {
558 +
    let root = try! parseStmtStr("unsafe { operation }");
559 +
    let case ast::NodeValue::Unsafe(body) = root.value
560 +
        else throw testing::TestError::Failed;
561 +
562 +
    try expectBlockExprStmt(body, ast::NodeValue::Ident("operation"));
563 +
}
564 +
565 +
/// Test parsing an unsafe block as one statement in an enclosing block.
566 +
@test fn testParseUnsafeBlockStatement() throws (testing::TestError) {
567 +
    let root = try! parseStmtStr("{ before; unsafe { operation; } after; }");
568 +
    let case ast::NodeValue::Block(block) = root.value
569 +
        else throw testing::TestError::Failed;
570 +
    try testing::expect(block.statements.len == 3);
571 +
572 +
    let firstStmt = block.statements[0];
573 +
    let case ast::NodeValue::ExprStmt(firstExpr) = firstStmt.value
574 +
        else throw testing::TestError::Failed;
575 +
    try expectIdent(firstExpr, "before");
576 +
577 +
    let unsafeStmt = block.statements[1];
578 +
    let case ast::NodeValue::Unsafe(body) = unsafeStmt.value
579 +
        else throw testing::TestError::Failed;
580 +
    try expectBlockExprStmt(body, ast::NodeValue::Ident("operation"));
581 +
582 +
    let lastStmt = block.statements[2];
583 +
    let case ast::NodeValue::ExprStmt(lastExpr) = lastStmt.value
584 +
        else throw testing::TestError::Failed;
585 +
    try expectIdent(lastExpr, "after");
586 +
}
587 +
547 588
/// Test parsing a block that keeps a trailing `;` delimiter.
548 589
@test fn testParseBlockTrailingSemicolon() throws (testing::TestError) {
549 590
    let root = try! parseStmtStr("if cond { only; }") catch {
550 591
        throw testing::TestError::Failed;
551 592
    };
1141 1182
1142 1183
    try testing::expect(attrs.list.len == 1);
1143 1184
    try testing::expect(ast::attributesContains(&attrs, ast::Attribute::Unsafe));
1144 1185
}
1145 1186
1187 +
/// Test parsing an unsafe static initializer.
1188 +
@test fn testParseUnsafeStaticDecl() throws (testing::TestError) {
1189 +
    let node = try! parseStmtStr("unsafe static BUFFER: [u8; 4] = undefined;");
1190 +
    let case ast::NodeValue::StaticDecl(decl) = node.value
1191 +
        else throw testing::TestError::Failed;
1192 +
    let attrs = decl.attrs
1193 +
        else throw testing::TestError::Failed;
1194 +
    try testing::expect(ast::attributesContains(&attrs, ast::Attribute::Unsafe));
1195 +
}
1196 +
1197 +
/// Test parsing an unsafe constant declaration.
1198 +
@test fn testParseUnsafeConstDecl() throws (testing::TestError) {
1199 +
    let node = try! parseStmtStr("unsafe constant TEXT: *[u8] = \"text\";");
1200 +
    let case ast::NodeValue::ConstDecl(decl) = node.value
1201 +
        else throw testing::TestError::Failed;
1202 +
    let attrs = decl.attrs
1203 +
        else throw testing::TestError::Failed;
1204 +
    try testing::expect(ast::attributesContains(&attrs, ast::Attribute::Unsafe));
1205 +
}
1206 +
1146 1207
/// Test rejecting `unsafe` on declarations where it has no semantics.
1147 1208
@test fn testParseUnsafeUnsupportedDecl() throws (testing::TestError) {
1148 1209
    let recordDecl: ?*ast::Node = try? parseStmtStr("unsafe record R {}");
1149 1210
    try testing::expect(recordDecl == nil);
1150 -
    let constDecl: ?*ast::Node = try? parseStmtStr("unsafe constant X = 1;");
1151 -
    try testing::expect(constDecl == nil);
1152 1211
}
1153 1212
1154 1213
/// Test `unsafe` on the other declaration forms that support it.
1155 1214
@test fn testParseUnsafeMethodAndModule() throws (testing::TestError) {
1156 1215
    let moduleNode = try! parseStmtStr("unsafe mod io;");
lib/std/lang/resolver.rad +469 -105
23 23
24 24
/// Maximum number of diagnostics recorded.
25 25
export constant MAX_ERRORS: u32 = 64;
26 26
27 27
/// Synthetic function name used when wrapping a bare expression for analysis.
28 -
export constant ANALYZE_EXPR_FN_NAME: *[u8] = "__expr__";
28 +
export unsafe constant ANALYZE_EXPR_FN_NAME: *[u8] = "__expr__";
29 29
/// Synthetic function name used when wrapping a block for analysis.
30 -
export constant ANALYZE_BLOCK_FN_NAME: *[u8] = "__block__";
30 +
export unsafe constant ANALYZE_BLOCK_FN_NAME: *[u8] = "__block__";
31 31
32 32
/// Maximum number of symbols stored within a module scope.
33 33
export constant MAX_MODULE_SYMBOLS: u32 = 512;
34 34
/// Maximum number of symbols stored within a local scope.
35 35
export constant MAX_LOCAL_SYMBOLS: u32 = 32;
107 107
    /// Symbol for the method.
108 108
    symbol: *mut Symbol,
109 109
}
110 110
111 111
/// Identifier for the synthetic `len` field.
112 -
export constant LEN_FIELD: *[u8] = "len";
112 +
export unsafe constant LEN_FIELD: *[u8] = "len";
113 113
/// Identifier for the synthetic `ptr` field.
114 -
export constant PTR_FIELD: *[u8] = "ptr";
114 +
export unsafe constant PTR_FIELD: *[u8] = "ptr";
115 115
/// Identifier for the synthetic `cap` field.
116 -
export constant CAP_FIELD: *[u8] = "cap";
116 +
export unsafe constant CAP_FIELD: *[u8] = "cap";
117 117
118 118
/// Maximum `u16` value.
119 119
constant U16_MAX: u16 = 0xFFFF;
120 120
/// Maximum `u8` value.
121 121
constant U8_MAX: u16 = 0xFF;
519 519
    FnMissingBody,
520 520
    /// Function body is not expected.
521 521
    FnUnexpectedBody,
522 522
    /// Intrinsic function must not have a body.
523 523
    IntrinsicUnexpectedBody,
524 +
    /// Ecall intrinsic declaration does not match the canonical unsafe ABI.
525 +
    InvalidEcallIntrinsicSignature,
524 526
    /// Encountered loop control outside of a loop construct.
525 527
    InvalidLoopControl,
526 528
    /// `try` used when the enclosing function does not declare throws.
527 529
    TryRequiresThrows,
528 530
    /// `try` used to propagate an error not declared by the enclosing function.
695 697
    constValue: ?ConstValue,
696 698
    /// Lexical scope owned by this node.
697 699
    scope: ?*mut Scope,
698 700
    /// Node-specific extra data.
699 701
    extra: NodeExtra,
702 +
    /// Whether the declaration body belongs to a trusted unsafe module.
703 +
    trustedBody: bool,
700 704
}
701 705
702 706
/// Table storing all resolver metadata indexed by node ID.
703 707
record NodeDataTable {
704 708
    entries: *mut [NodeData],
796 800
    len: u32,
797 801
    /// Whether this control-flow path has terminated.
798 802
    terminated: bool,
799 803
}
800 804
805 +
/// Loans kept alive while later call arguments are evaluated.
806 +
record LinearLoans {
807 +
    /// Enclosing call's active loans.
808 +
    parent: ?*LinearLoans,
809 +
    /// Root symbol for each active loan.
810 +
    roots: [?*mut Symbol; MAX_FN_PARAMS + 1],
811 +
    /// Whether each loan excludes every other access.
812 +
    exclusive: [bool; MAX_FN_PARAMS + 1],
813 +
    /// Number of active entries.
814 +
    len: u32,
815 +
}
816 +
801 817
/// Function-local exact-use checker state.
802 818
record LinearChecker {
803 819
    /// Resolver that owns the symbols and diagnostics.
804 820
    resolver: *mut Resolver,
821 +
    /// Active loans from enclosing and earlier call arguments.
822 +
    loans: ?*LinearLoans,
805 823
    /// Binding count at entry to each active loop.
806 824
    loopMarks: [u32; MAX_LINEAR_LOOP_DEPTH],
807 825
    /// Available bindings at entry to each active loop.
808 826
    loopAvailable: [u64; MAX_LINEAR_LOOP_DEPTH],
809 827
    /// Available bindings shared by the exits from each active loop.
981 999
            coercion: Coercion::Identity,
982 1000
            sym: nil,
983 1001
            constValue: nil,
984 1002
            scope: nil,
985 1003
            extra: NodeExtra::None,
1004 +
            trustedBody: false,
986 1005
        };
987 1006
    }
988 1007
989 1008
    let mut moduleScopes: [?*mut Scope; module::MAX_MODULES] = undefined;
990 1009
    // TODO: Simplify.
1756 1775
/// Return whether pointer classes are compatible.
1757 1776
fn pointerClassesAssignable(
1758 1777
    to: types::PointerClass,
1759 1778
    from: types::PointerClass,
1760 1779
) -> bool {
1761 -
    return to == from or (
1762 -
        to == types::PointerClass::Owned
1763 -
        and from == types::PointerClass::Ref
1764 -
    );
1780 +
    return to == from;
1765 1781
}
1766 1782
1767 1783
/// Check if the `from` type is assignable to the `to` type, and return a
1768 1784
/// coercion plan if so.
1769 1785
fn isAssignable(self: *mut Resolver, to: Type, from: Type, rval: *ast::Node) -> ?Coercion {
2679 2695
    if self.unsafeDepth == 0 {
2680 2696
        throw emitError(self, node, ErrorKind::UnsafeOperation);
2681 2697
    }
2682 2698
}
2683 2699
2700 +
/// Require unsafe context when reading an unsafe binding that is not a function declaration.
2701 +
fn checkUnsafeBindingAccess(
2702 +
    self: *mut Resolver,
2703 +
    node: *ast::Node,
2704 +
    sym: *mut Symbol,
2705 +
) throws (ResolveError) {
2706 +
    if not ast::hasAttribute(sym.attrs, ast::Attribute::Unsafe) {
2707 +
        return;
2708 +
    }
2709 +
    if let case ast::NodeValue::FnDecl(_) = sym.node.value {
2710 +
        return;
2711 +
    }
2712 +
    match sym.data {
2713 +
        case SymbolData::Value { .. },
2714 +
             SymbolData::Constant { .. } => try requireUnsafe(self, node),
2715 +
        else => {}
2716 +
    }
2717 +
}
2718 +
2684 2719
/// Reject calls from safe code through unsafe function types.
2685 2720
fn checkUnsafeCall(self: *mut Resolver, node: *ast::Node, info: *FnType)
2686 2721
    throws (ResolveError)
2687 2722
{
2688 2723
    if info.isUnsafe and self.unsafeDepth == 0 {
2809 2844
    }
2810 2845
    match node.value {
2811 2846
        case ast::NodeValue::Ident(name) => {
2812 2847
            let sym = findAnySymbol(self.scope, name)
2813 2848
                else throw emitError(self, node, ErrorKind::UnresolvedSymbol(name));
2849 +
            try checkUnsafeBindingAccess(self, node, sym);
2814 2850
            setNodeSymbol(self, node, sym);
2815 2851
            match sym.data {
2816 2852
                case SymbolData::Value { type, .. } =>
2817 2853
                    return setNodeType(self, node, type),
2818 2854
                case SymbolData::Constant { type, value } => {
2833 2869
        },
2834 2870
        case ast::NodeValue::Call(call) => return try resolveCall(self, node, call, CallCtx::Normal),
2835 2871
        case ast::NodeValue::FieldAccess(access) => return try resolveFieldAccess(self, node, access),
2836 2872
        case ast::NodeValue::BinOp(binop) => return try resolveBinOp(self, node, binop),
2837 2873
        case ast::NodeValue::Block(block) => return try resolveBlock(self, node, block),
2874 +
        case ast::NodeValue::Unsafe(body) => {
2875 +
            set self.unsafeDepth += 1;
2876 +
            let bodyTy = try visit(self, body, hint) catch e {
2877 +
                set self.unsafeDepth -= 1;
2878 +
                throw e;
2879 +
            };
2880 +
            set self.unsafeDepth -= 1;
2881 +
            return setNodeType(self, node, bodyTy);
2882 +
        },
2838 2883
        case ast::NodeValue::Let(decl) => return try resolveLet(self, node, decl),
2839 2884
        case ast::NodeValue::ConstDecl(decl) => return try resolveConstOrStatic(
2840 2885
            self, node, decl.ident, decl.type, decl.value, decl.attrs, true
2841 2886
        ),
2842 2887
        case ast::NodeValue::StaticDecl(decl) => return try resolveConstOrStatic(
2915 2960
                return setNodeType(self, node, hint);
2916 2961
            }
2917 2962
            return setNodeType(self, node, Type::Nil);
2918 2963
        },
2919 2964
        case ast::NodeValue::Undef => {
2965 +
            try requireUnsafe(self, node);
2920 2966
            return setNodeType(self, node, Type::Undefined);
2921 2967
        },
2922 2968
        case ast::NodeValue::Bool(value) => {
2923 2969
            setNodeConstValue(self, node, ConstValue::Bool(value));
2924 2970
            return setNodeType(self, node, Type::Bool);
3175 3221
        },
3176 3222
        case ast::NodeValue::As(expr) => {
3177 3223
            // Cast expressions are constant if the source value is constant.
3178 3224
            return isConstExpr(self, expr.value);
3179 3225
        },
3226 +
        case ast::NodeValue::Unsafe(body) => {
3227 +
            return isConstExpr(self, body);
3228 +
        },
3180 3229
        else => {
3181 3230
            return false;
3182 3231
        }
3183 3232
    }
3184 3233
}
3280 3329
    isConst: bool
3281 3330
) -> Type throws (ResolveError) {
3282 3331
    let attrs = resolveAttributes(self, attrList);
3283 3332
    let bindingTy = try infer(self, typeNode);
3284 3333
    try ensureStorableType(self, typeNode, bindingTy);
3285 -
    let valueTy = try checkAssignable(self, valueNode, bindingTy);
3334 +
    try ensureTypeResolved(self, bindingTy, typeNode);
3335 +
    let unsafeGlobal = ast::hasAttribute(attrs, ast::Attribute::Unsafe);
3336 +
    if isLinear(bindingTy) and not unsafeGlobal {
3337 +
        throw emitError(self, typeNode, ErrorKind::LinearDiscard);
3338 +
    }
3339 +
    let unsafeInitializer = unsafeGlobal;
3340 +
    if unsafeInitializer {
3341 +
        set self.unsafeDepth += 1;
3342 +
    }
3343 +
    let valueTy = try checkAssignable(self, valueNode, bindingTy) catch e {
3344 +
        if unsafeInitializer {
3345 +
            set self.unsafeDepth -= 1;
3346 +
        }
3347 +
        throw e;
3348 +
    };
3349 +
    if unsafeInitializer {
3350 +
        set self.unsafeDepth -= 1;
3351 +
    }
3286 3352
3287 3353
    if isConst {
3288 3354
        let mut constVal = constValueEntry(self, valueNode);
3289 3355
        if constVal == nil and not isConstExpr(self, valueNode) {
3290 3356
            throw emitError(self, valueNode, ErrorKind::ConstExprRequired);
3304 3370
    setNodeType(self, valueNode, bindingTy);
3305 3371
3306 3372
    return Type::Void;
3307 3373
}
3308 3374
3375 +
/// Return whether a function type matches the compiler's ecall ABI.
3376 +
fn isCanonicalEcallType(info: *FnType) -> bool {
3377 +
    if not info.isUnsafe
3378 +
        or info.paramTypes.len <> 5
3379 +
        or info.throwList.len <> 0
3380 +
        or *info.returnType <> Type::I64
3381 +
    {
3382 +
        return false;
3383 +
    }
3384 +
    return *info.paramTypes[0] == Type::U32
3385 +
        and *info.paramTypes[1] == Type::I64
3386 +
        and *info.paramTypes[2] == Type::I64
3387 +
        and *info.paramTypes[3] == Type::I64
3388 +
        and *info.paramTypes[4] == Type::I64;
3389 +
}
3390 +
3309 3391
/// Analyze a function declaration signature and bind the function name.
3310 3392
fn resolveFnDecl(self: *mut Resolver, node: *ast::Node, decl: ast::FnDecl) -> Type
3311 3393
    throws (ResolveError)
3312 3394
{
3395 +
    set self.nodeData.entries[node.id].trustedBody = self.unsafeDepth > 0;
3313 3396
    let attrMask = resolveAttributes(self, decl.attrs);
3314 3397
    let mut retTy = Type::Void;
3315 3398
    if let retNode = decl.sig.returnType {
3316 3399
        set retTy = try infer(self, retNode);
3317 3400
        try ensureStorableType(self, retNode, retTy);
3360 3443
        try ensureStorableType(self, throwNode, throwTy);
3361 3444
    }
3362 3445
    exitFn(self);
3363 3446
    set fnType.paramTypes = &paramTypes[..];
3364 3447
    set fnType.throwList = &throwList[..];
3448 +
    if ast::hasAttribute(attrMask, ast::Attribute::Intrinsic) {
3449 +
        let name = try nodeName(self, decl.name);
3450 +
        if mem::eq(name, "ecall") and not isCanonicalEcallType(&fnType) {
3451 +
            throw emitError(
3452 +
                self,
3453 +
                node,
3454 +
                ErrorKind::InvalidEcallIntrinsicSignature,
3455 +
            );
3456 +
        }
3457 +
    }
3365 3458
3366 3459
    // Bind the function name.
3367 3460
    let ty = Type::Fn(allocFnType(self, fnType));
3368 3461
    let sym = try bindValueIdent(self, decl.name, node, ty, false, 0, attrMask)
3369 3462
        else throw emitError(self, node, ErrorKind::ExpectedIdentifier);
3383 3476
    };
3384 3477
    let retTy = *fnType.returnType;
3385 3478
    let isExtern = ast::hasAttribute(sym.attrs, ast::Attribute::Extern);
3386 3479
    let isIntrinsic = ast::hasAttribute(sym.attrs, ast::Attribute::Intrinsic);
3387 3480
    let isUnsafe = fnType.isUnsafe;
3481 +
    let trustedBody = nodeData(self, node).trustedBody;
3388 3482
3389 3483
    if let body = decl.body {
3390 3484
        if isIntrinsic {
3391 3485
            throw emitError(self, node, ErrorKind::IntrinsicUnexpectedBody);
3392 3486
        }
3393 3487
        if isExtern {
3394 3488
            throw emitError(self, node, ErrorKind::FnUnexpectedBody);
3395 3489
        }
3396 -
        if isUnsafe {
3490 +
        if isUnsafe or trustedBody {
3397 3491
            set self.unsafeDepth += 1;
3398 3492
        }
3399 3493
        enterFn(self, node, fnType); // Enter function scope for body analysis.
3400 3494
3401 3495
        let bodyTy = try checkAssignable(self, body, Type::Void) catch e {
3402 3496
            exitFn(self);
3403 -
            if isUnsafe { set self.unsafeDepth -= 1; }
3497 +
            if isUnsafe or trustedBody { set self.unsafeDepth -= 1; }
3404 3498
            throw e;
3405 3499
        };
3406 3500
        if retTy <> Type::Void and bodyTy <> Type::Never {
3407 3501
            exitFn(self);
3408 -
            if isUnsafe { set self.unsafeDepth -= 1; }
3502 +
            if isUnsafe or trustedBody { set self.unsafeDepth -= 1; }
3409 3503
            throw emitError(self, body, ErrorKind::FnMissingReturn);
3410 3504
        }
3505 +
        if self.unsafeDepth == 0 {
3506 +
            try checkLinearFn(self, nil, decl.sig.params, body) catch e {
3507 +
                exitFn(self);
3508 +
                throw e;
3509 +
            };
3510 +
        }
3411 3511
        exitFn(self);
3412 -
        if isUnsafe {
3512 +
        if isUnsafe or trustedBody {
3413 3513
            set self.unsafeDepth -= 1;
3414 3514
        }
3415 3515
    } else if not isExtern {
3416 3516
        throw emitError(self, node, ErrorKind::FnMissingBody);
3417 3517
    }
3788 3888
    // Match each instance method to a trait method.
3789 3889
    for methodNode in methods {
3790 3890
        let case ast::NodeValue::MethodDecl {
3791 3891
            name, receiverName, receiverType, sig, body, attrs,
3792 3892
        } = methodNode.value else continue;
3893 +
        set self.nodeData.entries[methodNode.id].trustedBody = self.unsafeDepth > 0;
3793 3894
3794 3895
        let methodName = try nodeName(self, name);
3795 3896
        let attrMask = resolveAttributes(self, attrs);
3796 3897
3797 3898
        // Find the matching trait method.
3976 4077
    let sym = symbolFor(self, node)
3977 4078
        else throw emitError(self, node, ErrorKind::Internal);
3978 4079
    let case SymbolData::Value { type: Type::Fn(fnType), .. } = sym.data
3979 4080
        else panic "resolveMethodBody: expected value symbol";
3980 4081
    let isUnsafe = fnType.isUnsafe;
3981 -
    if isUnsafe {
4082 +
    let trustedBody = nodeData(self, node).trustedBody;
4083 +
    if isUnsafe or trustedBody {
3982 4084
        set self.unsafeDepth += 1;
3983 4085
    }
3984 4086
3985 4087
    // Enter function scope.
3986 4088
    enterFn(self, node, fnType);
3987 4089
3988 4090
    // Bind the receiver parameter.
3989 4091
    let receiverTy = *fnType.paramTypes[0];
3990 4092
    try bindValueIdent(self, receiverName, receiverName, receiverTy, false, 0, 0) catch e {
3991 4093
        exitFn(self);
3992 -
        if isUnsafe { set self.unsafeDepth -= 1; }
4094 +
        if isUnsafe or trustedBody { set self.unsafeDepth -= 1; }
3993 4095
        throw e;
3994 4096
    };
3995 4097
    // Bind the remaining parameters from the signature.
3996 4098
    for paramNode in sig.params {
3997 4099
        let paramTy = try infer(self, paramNode) catch e {
3998 4100
            exitFn(self);
3999 -
            if isUnsafe { set self.unsafeDepth -= 1; }
4101 +
            if isUnsafe or trustedBody { set self.unsafeDepth -= 1; }
4000 4102
            throw e;
4001 4103
        };
4002 4104
    }
4003 4105
4004 4106
    // Resolve the body.
4005 4107
    let retTy = *fnType.returnType;
4006 4108
    let bodyTy = try checkAssignable(self, body, Type::Void) catch e {
4007 4109
        exitFn(self);
4008 -
        if isUnsafe { set self.unsafeDepth -= 1; }
4110 +
        if isUnsafe or trustedBody { set self.unsafeDepth -= 1; }
4009 4111
        throw e;
4010 4112
    };
4011 4113
    if retTy <> Type::Void and bodyTy <> Type::Never {
4012 4114
        exitFn(self);
4013 -
        if isUnsafe { set self.unsafeDepth -= 1; }
4115 +
        if isUnsafe or trustedBody { set self.unsafeDepth -= 1; }
4014 4116
        throw emitError(self, body, ErrorKind::FnMissingReturn);
4015 4117
    }
4118 +
    if self.unsafeDepth == 0 {
4119 +
        try checkLinearFn(self, receiverName, sig.params, body) catch e {
4120 +
            exitFn(self);
4121 +
            throw e;
4122 +
        };
4123 +
    }
4016 4124
    exitFn(self);
4017 -
    if isUnsafe {
4125 +
    if isUnsafe or trustedBody {
4018 4126
        set self.unsafeDepth -= 1;
4019 4127
    }
4020 4128
}
4021 4129
4022 4130
/// Resolve a standalone method declaration (signature only).
4046 4154
    receiverName: *ast::Node,
4047 4155
    receiverType: *ast::Node,
4048 4156
    sig: ast::FnSig,
4049 4157
    attrs: ?ast::Attributes,
4050 4158
) throws (ResolveError) {
4159 +
    set self.nodeData.entries[node.id].trustedBody = self.unsafeDepth > 0;
4051 4160
    // Resolve the receiver type: must be `*Type` or `*mut Type` pointing to a
4052 4161
    // nominal type.
4053 4162
    let fullReceiverTy = try infer(self, receiverType);
4054 4163
    let case Type::Pointer {
4055 4164
        class: receiverClass, target: receiverTarget, mutable: receiverMut,
4293 4402
    // Find module under the current module.
4294 4403
    let modName = try nodeName(self, decl.name);
4295 4404
    let submod = try enterSubModule(self, modName, node);
4296 4405
    let case ast::NodeValue::Block(block) = submod.root.value
4297 4406
        else panic "resolveModDecl: expected block for module root";
4298 -
    try resolveModuleDecls(self, &block);
4299 -
4407 +
    let mut isUnsafe = false;
4408 +
    if let attrs = decl.attrs {
4409 +
        set isUnsafe = ast::attributesContains(&attrs, ast::Attribute::Unsafe);
4410 +
    }
4411 +
    if isUnsafe {
4412 +
        set self.unsafeDepth += 1;
4413 +
    }
4414 +
    try resolveModuleDecls(self, &block) catch e {
4415 +
        if isUnsafe { set self.unsafeDepth -= 1; }
4416 +
        exitModuleScope(self, submod);
4417 +
        throw e;
4418 +
    };
4419 +
    if isUnsafe {
4420 +
        set self.unsafeDepth -= 1;
4421 +
    }
4300 4422
    exitModuleScope(self, submod);
4301 4423
}
4302 4424
4303 4425
/// Analyze a `use` statement and create a symbol for the imported module.
4304 4426
fn resolveUse(self: *mut Resolver, node: *ast::Node, decl: ast::Use) -> Type
5323 5445
            throw emitError(self, node, ErrorKind::BuiltinArgCountMismatch(CountMismatch {
5324 5446
                expected: 2,
5325 5447
                actual: args.len as u32,
5326 5448
            }));
5327 5449
        }
5450 +
        try requireUnsafe(self, node);
5328 5451
        let ptrType = try visit(self, args[0], Type::Unknown);
5329 5452
        let case Type::Pointer { class, target, mutable } = ptrType else {
5330 5453
            throw emitError(self, node, ErrorKind::ExpectedPointer);
5331 5454
        };
5332 5455
        let _ = try checkAssignable(self, args[1], Type::U32);
5508 5631
5509 5632
    // Associate return type to call.
5510 5633
    return setNodeType(self, node, *info.returnType);
5511 5634
}
5512 5635
5636 +
/// Return whether a type has the exact allocator representation used by slice append lowering.
5637 +
fn isSliceAllocatorType(ty: Type) -> bool {
5638 +
    let case Type::Nominal(NominalType::Record(recInfo)) = ty
5639 +
        else return false;
5640 +
    if recInfo.fields.len <> 2
5641 +
        or recInfo.layout.size <> PTR_SIZE * 2
5642 +
        or recInfo.layout.alignment <> PTR_SIZE
5643 +
        or recInfo.fields[0].offset <> 0
5644 +
        or recInfo.fields[1].offset <> PTR_SIZE as i32
5645 +
    {
5646 +
        return false;
5647 +
    }
5648 +
5649 +
    let case Type::Fn(callback) = recInfo.fields[0].fieldType
5650 +
        else return false;
5651 +
    if callback.isUnsafe
5652 +
        or callback.paramTypes.len <> 3
5653 +
        or callback.throwList.len <> 0
5654 +
        or *callback.paramTypes[1] <> Type::U32
5655 +
        or *callback.paramTypes[2] <> Type::U32
5656 +
    {
5657 +
        return false;
5658 +
    }
5659 +
    let case Type::Pointer {
5660 +
        class: types::PointerClass::Owned,
5661 +
        target: callbackCtx,
5662 +
        mutable: true,
5663 +
    } = *callback.paramTypes[0] else return false;
5664 +
    if *callbackCtx <> Type::Opaque {
5665 +
        return false;
5666 +
    }
5667 +
    let case Type::Pointer {
5668 +
        class: types::PointerClass::Owned,
5669 +
        target: result,
5670 +
        mutable: true,
5671 +
    } = *callback.returnType else return false;
5672 +
    if *result <> Type::Opaque {
5673 +
        return false;
5674 +
    }
5675 +
5676 +
    return typesEqual(recInfo.fields[1].fieldType, *callback.paramTypes[0]);
5677 +
}
5678 +
5513 5679
/// Resolve `slice.append(val, allocator)`.
5514 5680
fn resolveSliceAppend(
5515 5681
    self: *mut Resolver,
5516 5682
    node: *ast::Node,
5517 5683
    parent: *ast::Node,
5527 5693
        throw emitError(self, node, ErrorKind::FnArgCountMismatch(CountMismatch {
5528 5694
            expected: 2,
5529 5695
            actual: args.len as u32,
5530 5696
        }));
5531 5697
    }
5698 +
    try requireUnsafe(self, node);
5532 5699
    // First argument must be assignable to the element type.
5533 5700
    try checkAssignable(self, args[0], *elemType);
5534 -
    // Second argument: the allocator. We accept any type -- the lowerer
5535 -
    // reads `.func` and `.ctx` at fixed offsets.
5536 -
    try visit(self, args[1], Type::Unknown);
5701 +
    // The lowerer loads the allocator callback and context from fixed offsets.
5702 +
    let allocatorType = try visit(self, args[1], Type::Unknown);
5703 +
    try ensureTypeResolved(self, allocatorType, args[1]);
5704 +
    if not isSliceAllocatorType(allocatorType) {
5705 +
        throw emitTypeMismatch(self, args[1], TypeMismatch {
5706 +
            expected: Type::Unknown,
5707 +
            actual: allocatorType,
5708 +
        });
5709 +
    }
5537 5710
    set self.nodeData.entries[node.id].extra = NodeExtra::SliceAppend { elemType };
5538 5711
5539 5712
    // Return the parent's type so the caller can rebind:
5540 5713
    return setNodeType(self, node, parentType);
5541 5714
}
6024 6197
/// Analyze a scope access expression.
6025 6198
fn resolveScopeAccess(self: *mut Resolver, node: *ast::Node, access: ast::Access) -> Type
6026 6199
    throws (ResolveError)
6027 6200
{
6028 6201
    let sym = try resolveAccess(self, node, access, self.scope);
6202 +
    try checkUnsafeBindingAccess(self, node, sym);
6029 6203
    let mut ty: Type = undefined;
6030 6204
6031 6205
    match sym.data {
6032 6206
        case SymbolData::Value { type, .. } => {
6033 6207
            setNodeSymbol(self, node, sym);
6243 6417
6244 6418
/// Analyze an address-of expression.
6245 6419
fn resolveAddressOf(self: *mut Resolver, node: *ast::Node, addr: ast::AddressOf, hint: Type) -> Type
6246 6420
    throws (ResolveError)
6247 6421
{
6248 -
    // Checked and unsafe pointer contexts require a reference source.
6249 -
    let class = types::PointerClass::Ref
6250 -
        if isRefType(hint) or isUnsafePointerType(hint)
6251 -
        else types::PointerClass::Owned;
6422 +
    // Safe address-of expressions always create call-scoped loans. Trusted
6423 +
    // implementations may create owners when no loan or raw type is required.
6424 +
    let class = types::PointerClass::Owned
6425 +
        if self.unsafeDepth > 0 and not isRefType(hint) and not isUnsafePointerType(hint)
6426 +
        else types::PointerClass::Ref;
6252 6427
    if addr.mutable {
6253 6428
        if not try canBorrowMutFrom(self, addr.target) {
6254 6429
            throw emitError(self, addr.target, ErrorKind::ImmutableBinding);
6255 6430
        }
6256 6431
    }
6450 6625
    assert sourceTy <> Type::Unknown;
6451 6626
    assert targetTy <> Type::Unknown;
6452 6627
6453 6628
    let mut valid = isValidCast(sourceTy, targetTy);
6454 6629
    if let case Type::Pointer {
6455 -
        class: sourceClass, target: sourceTarget, mutable: sourceMutable,
6630 +
        class: sourceClass, mutable: sourceMutable, ..
6456 6631
    } = sourceTy {
6457 6632
        if let case Type::Pointer {
6458 -
            class: targetClass, target: targetTarget, mutable: targetMutable,
6633 +
            class: targetClass, mutable: targetMutable, ..
6459 6634
        } = targetTy {
6460 -
            if sourceClass == types::PointerClass::Ref and
6461 -
               targetClass == types::PointerClass::Unsafe and
6462 -
               (not targetMutable or sourceMutable) and
6463 -
               isValidCast(*sourceTarget, *targetTarget)
6464 -
            {
6635 +
            let compatibleClass = sourceClass == targetClass or (
6636 +
                sourceClass == types::PointerClass::Ref and
6637 +
                targetClass == types::PointerClass::Unsafe
6638 +
            );
6639 +
            if compatibleClass and (not targetMutable or sourceMutable) {
6465 6640
                set valid = true;
6466 6641
            }
6467 6642
        }
6468 6643
    }
6469 6644
    if let case Type::Slice {
6470 -
        class: sourceClass, item: sourceItem, mutable: sourceMutable,
6645 +
        class: sourceClass, mutable: sourceMutable, ..
6471 6646
    } = sourceTy {
6472 6647
        if let case Type::Slice {
6473 -
            class: targetClass, item: targetItem, mutable: targetMutable,
6648 +
            class: targetClass, mutable: targetMutable, ..
6474 6649
        } = targetTy {
6475 -
            if sourceClass == types::PointerClass::Ref and
6476 -
               targetClass == types::PointerClass::Unsafe and
6477 -
               (not targetMutable or sourceMutable) and
6478 -
               isValidCast(*sourceItem, *targetItem)
6479 -
            {
6650 +
            let compatibleClass = sourceClass == targetClass or (
6651 +
                sourceClass == types::PointerClass::Ref and
6652 +
                targetClass == types::PointerClass::Unsafe
6653 +
            );
6654 +
            if compatibleClass and (not targetMutable or sourceMutable) {
6480 6655
                set valid = true;
6481 6656
            }
6482 6657
        }
6483 6658
    }
6484 6659
    if valid {
6660 +
        let mut changesRepresentation = false;
6661 +
        match sourceTy {
6662 +
            case Type::Pointer { target: sourceTarget, .. } => {
6663 +
                if let case Type::Pointer { target: targetTarget, .. } = targetTy {
6664 +
                    set changesRepresentation = not typesEqual(*sourceTarget, *targetTarget);
6665 +
                }
6666 +
            }
6667 +
            case Type::Slice { item: sourceItem, .. } => {
6668 +
                if let case Type::Slice { item: targetItem, .. } = targetTy {
6669 +
                    set changesRepresentation = not typesEqual(*sourceItem, *targetItem);
6670 +
                }
6671 +
            }
6672 +
            else => {}
6673 +
        }
6674 +
        if changesRepresentation {
6675 +
            try requireUnsafe(self, node);
6676 +
        }
6485 6677
        // Propagate the constant value after applying the cast's target-width
6486 6678
        // truncation and signed interpretation.
6487 6679
        if let value = constValueEntry(self, expr.value) {
6488 6680
            if let case ConstValue::Int(i) = value {
6489 6681
                setNodeConstValue(self, node, castConstInt(i, targetTy));
6967 7159
                        throw emitError(self, node, ErrorKind::OpaquePointerArithmetic);
6968 7160
                    }
6969 7161
                    if leftClass <> types::PointerClass::Ref
6970 7162
                        and isNumericType(rightTy)
6971 7163
                    {
6972 -
                        if leftClass == types::PointerClass::Unsafe {
6973 -
                            try requireUnsafe(self, node);
6974 -
                        }
7164 +
                        try requireUnsafe(self, node);
6975 7165
                        return setNodeType(self, node, leftTy);
6976 7166
                    }
6977 7167
                }
6978 7168
                if let case Type::Pointer { class: rightClass, target: rightTarget, .. } = rightTy {
6979 7169
                    if *rightTarget == Type::Opaque {
6981 7171
                    }
6982 7172
                    if binop.op == ast::BinaryOp::Add
6983 7173
                        and rightClass <> types::PointerClass::Ref
6984 7174
                        and isNumericType(leftTy)
6985 7175
                    {
6986 -
                        if rightClass == types::PointerClass::Unsafe {
6987 -
                            try requireUnsafe(self, node);
6988 -
                        }
7176 +
                        try requireUnsafe(self, node);
6989 7177
                        return setNodeType(self, node, rightTy);
6990 7178
                    }
6991 7179
                }
6992 7180
            }
6993 7181
            let leftTy = try checkNumeric(self, binop.left);
7418 7606
        }
7419 7607
    }
7420 7608
    set env.len = start;
7421 7609
}
7422 7610
7423 -
/// Consume a tracked identifier exactly once.
7424 -
fn consumeLinearIdent(
7611 +
/// Mark a tracked identifier as consumed.
7612 +
fn consumeLinearIdent(env: *mut LinearEnv, index: u32) {
7613 +
    set env.available &= ~((1 as u64) << (index as u64));
7614 +
}
7615 +
7616 +
/// Check that a tracked identifier is available for its requested use.
7617 +
fn checkLinearIdent(
7425 7618
    checker: *mut LinearChecker,
7426 7619
    env: *mut LinearEnv,
7427 7620
    node: *ast::Node,
7621 +
    usage: LinearUse,
7428 7622
) throws (ResolveError) {
7623 +
    if usage == LinearUse::Place {
7624 +
        return;
7625 +
    }
7429 7626
    let sym = symbolFor(checker.resolver, node) else return;
7430 7627
    let index = findLinearBinding(env, sym) else return;
7431 7628
    if not linearBindingAvailable(env, index) {
7432 7629
        throw emitError(
7433 7630
            checker.resolver,
7434 7631
            node,
7435 7632
            ErrorKind::LinearUseAfterConsume(sym.name),
7436 7633
        );
7437 7634
    }
7438 -
    set env.available &= ~((1 as u64) << (index as u64));
7635 +
    if usage == LinearUse::Consume {
7636 +
        consumeLinearIdent(env, index);
7637 +
    }
7439 7638
}
7440 7639
7441 7640
/// Verify that two live branches agree on every outer binding.
7442 7641
fn joinLinearBranches(
7443 7642
    checker: *mut LinearChecker,
7489 7688
        }
7490 7689
    }
7491 7690
    set env.terminated = true;
7492 7691
}
7493 7692
7494 -
/// Find the local root borrowed or consumed by an argument expression.
7693 +
/// Find the root borrowed or consumed by an argument expression.
7495 7694
fn linearRootSymbol(self: *mut Resolver, node: *ast::Node) -> ?*mut Symbol {
7496 7695
    match node.value {
7497 -
        case ast::NodeValue::Ident(_) => return symbolFor(self, node),
7696 +
        case ast::NodeValue::Ident(_),
7697 +
             ast::NodeValue::ScopeAccess(_) => return symbolFor(self, node),
7498 7698
        case ast::NodeValue::AddressOf(addr) => return linearRootSymbol(self, addr.target),
7499 7699
        case ast::NodeValue::FieldAccess(access) =>
7500 7700
            return linearRootSymbol(self, access.parent),
7501 7701
        case ast::NodeValue::Subscript { container, .. } =>
7502 7702
            return linearRootSymbol(self, container),
7503 7703
        case ast::NodeValue::Deref(target) => return linearRootSymbol(self, target),
7704 +
        case ast::NodeValue::As(expr) => return linearRootSymbol(self, expr.value),
7705 +
        case ast::NodeValue::Unsafe(body) => return linearRootSymbol(self, body),
7504 7706
        else => return nil,
7505 7707
    }
7506 7708
}
7507 7709
7710 +
/// Reject an access that overlaps a loan active in an enclosing call.
7711 +
fn checkCallLoanConflicts(
7712 +
    checker: *mut LinearChecker,
7713 +
    node: *ast::Node,
7714 +
    exclusive: bool,
7715 +
) throws (ResolveError) {
7716 +
    let root = linearRootSymbol(checker.resolver, node) else return;
7717 +
    let mut cursor = checker.loans;
7718 +
    while let loans = cursor {
7719 +
        for i in 0..loans.len {
7720 +
            if let previous = loans.roots[i];
7721 +
                previous == root and (loans.exclusive[i] or exclusive)
7722 +
            {
7723 +
                throw emitError(
7724 +
                    checker.resolver,
7725 +
                    node,
7726 +
                    ErrorKind::BorrowConflict(root.name),
7727 +
                );
7728 +
            }
7729 +
        }
7730 +
        set cursor = loans.parent;
7731 +
    }
7732 +
}
7733 +
7508 7734
/// Add the value identifiers introduced by a pattern.
7509 7735
fn addLinearPatternBindings(
7510 7736
    checker: *mut LinearChecker,
7511 7737
    env: *mut LinearEnv,
7512 7738
    pattern: *ast::Node,
7712 7938
    if haveResult {
7713 7939
        set *env = result;
7714 7940
    }
7715 7941
}
7716 7942
7943 +
/// Check one call argument and retain its loan through all later arguments.
7944 +
fn checkLinearCallArg(
7945 +
    checker: *mut LinearChecker,
7946 +
    env: *mut LinearEnv,
7947 +
    loans: *mut LinearLoans,
7948 +
    arg: *ast::Node,
7949 +
    expected: Type,
7950 +
) throws (ResolveError) {
7951 +
    let mut exclusive = isLinear(expected);
7952 +
    if let case Type::Pointer {
7953 +
        class: types::PointerClass::Ref,
7954 +
        mutable,
7955 +
        ..
7956 +
    } = expected {
7957 +
        set exclusive = mutable;
7958 +
    } else if let case Type::Slice {
7959 +
        class: types::PointerClass::Ref,
7960 +
        mutable,
7961 +
        ..
7962 +
    } = expected {
7963 +
        set exclusive = mutable;
7964 +
    } else if let case Type::TraitObject {
7965 +
        class: types::PointerClass::Ref,
7966 +
        mutable,
7967 +
        ..
7968 +
    } = expected {
7969 +
        set exclusive = mutable;
7970 +
    }
7971 +
    if not isUnsafePointerType(expected) {
7972 +
        try checkCallLoanConflicts(checker, arg, exclusive);
7973 +
    }
7974 +
    if isRefType(expected) {
7975 +
        try checkLinearNode(checker, env, arg, LinearUse::Borrow);
7976 +
    } else {
7977 +
        try checkLinearNode(checker, env, arg, LinearUse::Consume);
7978 +
    }
7979 +
    if (isRefType(expected) or isLinear(expected)) and not isUnsafePointerType(expected) {
7980 +
        if let root = linearRootSymbol(checker.resolver, arg) {
7981 +
            set loans.roots[loans.len] = root;
7982 +
            set loans.exclusive[loans.len] = exclusive;
7983 +
            set loans.len += 1;
7984 +
        }
7985 +
    }
7986 +
}
7987 +
7988 +
/// Check a syntactic slice append or delete call without a callee function type.
7989 +
fn checkLinearSliceCall(
7990 +
    checker: *mut LinearChecker,
7991 +
    env: *mut LinearEnv,
7992 +
    node: *ast::Node,
7993 +
    call: ast::Call,
7994 +
    elemType: ?*Type,
7995 +
) throws (ResolveError) {
7996 +
    let case ast::NodeValue::FieldAccess(access) = call.callee.value else {
7997 +
        throw emitError(checker.resolver, call.callee, ErrorKind::Internal);
7998 +
    };
7999 +
    let receiverTy = typeFor(checker.resolver, access.parent) else {
8000 +
        throw emitError(checker.resolver, access.parent, ErrorKind::Internal);
8001 +
    };
8002 +
    let case Type::Slice { class: receiverClass, .. } = autoDeref(receiverTy) else {
8003 +
        throw emitError(checker.resolver, access.parent, ErrorKind::Internal);
8004 +
    };
8005 +
    let mut loans = LinearLoans {
8006 +
        parent: checker.loans,
8007 +
        roots: [nil; MAX_FN_PARAMS + 1],
8008 +
        exclusive: [false; MAX_FN_PARAMS + 1],
8009 +
        len: 0,
8010 +
    };
8011 +
    set checker.loans = &loans;
8012 +
8013 +
    if receiverClass <> types::PointerClass::Unsafe {
8014 +
        try checkCallLoanConflicts(checker, access.parent, true);
8015 +
    }
8016 +
    if receiverClass == types::PointerClass::Owned and elemType <> nil {
8017 +
        try checkLinearNode(checker, env, access.parent, LinearUse::Consume);
8018 +
    } else if receiverClass <> types::PointerClass::Unsafe {
8019 +
        try checkLinearNode(checker, env, access.parent, LinearUse::Borrow);
8020 +
    } else {
8021 +
        try checkLinearNode(checker, env, access.parent, LinearUse::Observe);
8022 +
    }
8023 +
    if receiverClass <> types::PointerClass::Unsafe {
8024 +
        if let root = linearRootSymbol(checker.resolver, access.parent) {
8025 +
            set loans.roots[loans.len] = root;
8026 +
            set loans.exclusive[loans.len] = true;
8027 +
            set loans.len += 1;
8028 +
        }
8029 +
    }
8030 +
8031 +
    if let item = elemType {
8032 +
        if call.args.len <> 2 {
8033 +
            throw emitError(checker.resolver, node, ErrorKind::Internal);
8034 +
        }
8035 +
        try checkLinearCallArg(checker, env, &mut loans, call.args[0], *item);
8036 +
        let allocatorTy = typeFor(checker.resolver, call.args[1]) else {
8037 +
            throw emitError(checker.resolver, call.args[1], ErrorKind::Internal);
8038 +
        };
8039 +
        try checkLinearCallArg(
8040 +
            checker,
8041 +
            env,
8042 +
            &mut loans,
8043 +
            call.args[1],
8044 +
            allocatorTy,
8045 +
        );
8046 +
    } else {
8047 +
        if call.args.len <> 1 {
8048 +
            throw emitError(checker.resolver, node, ErrorKind::Internal);
8049 +
        }
8050 +
        try checkLinearCallArg(
8051 +
            checker,
8052 +
            env,
8053 +
            &mut loans,
8054 +
            call.args[0],
8055 +
            Type::U32,
8056 +
        );
8057 +
    }
8058 +
    set checker.loans = loans.parent;
8059 +
}
8060 +
7717 8061
/// Check call-scoped loans and argument ownership transfers.
7718 8062
fn checkLinearCall(
7719 8063
    checker: *mut LinearChecker,
7720 8064
    env: *mut LinearEnv,
7721 8065
    node: *ast::Node,
7722 8066
    call: ast::Call,
7723 8067
) throws (ResolveError) {
8068 +
    match checker.resolver.nodeData.entries[node.id].extra {
8069 +
        case NodeExtra::SliceAppend { elemType } => {
8070 +
            try checkLinearSliceCall(checker, env, node, call, elemType);
8071 +
            return;
8072 +
        }
8073 +
        case NodeExtra::SliceDelete { .. } => {
8074 +
            try checkLinearSliceCall(checker, env, node, call, nil);
8075 +
            return;
8076 +
        }
8077 +
        else => {}
8078 +
    }
7724 8079
    try checkLinearNode(checker, env, call.callee, LinearUse::Observe);
7725 8080
    let calleeTy = typeFor(checker.resolver, call.callee) else {
7726 8081
        throw emitError(checker.resolver, call.callee, ErrorKind::Internal);
7727 8082
    };
7728 8083
    let case Type::Fn(info) = calleeTy else {
7729 8084
        for arg in call.args {
7730 8085
            try checkLinearNode(checker, env, arg, LinearUse::Consume);
7731 8086
        }
7732 8087
        return;
7733 8088
    };
7734 -
    let mut roots: [?*mut Symbol; MAX_FN_PARAMS + 1] = undefined;
7735 -
    let mut exclusive: [bool; MAX_FN_PARAMS + 1] = undefined;
7736 -
    let mut rootsLen: u32 = 0;
8089 +
    let mut loans = LinearLoans {
8090 +
        parent: checker.loans,
8091 +
        roots: [nil; MAX_FN_PARAMS + 1],
8092 +
        exclusive: [false; MAX_FN_PARAMS + 1],
8093 +
        len: 0,
8094 +
    };
8095 +
    set checker.loans = &loans;
7737 8096
7738 8097
    // Method function types exclude their implicit receiver. Account for it
7739 8098
    // explicitly so owning receivers are consumed and reference receivers
7740 8099
    // participate in call-scoped loan conflict checks.
7741 8100
    if let case ast::NodeValue::FieldAccess(access) = call.callee.value {
7755 8114
                set haveReceiver = true;
7756 8115
            }
7757 8116
            else => {}
7758 8117
        }
7759 8118
        if haveReceiver {
8119 +
            let receiverExclusive =
8120 +
                receiverClass == types::PointerClass::Owned or receiverMutable;
7760 8121
            if receiverClass <> types::PointerClass::Unsafe {
7761 -
                let root = linearRootSymbol(checker.resolver, access.parent);
7762 -
                if let rootSym = root {
7763 -
                    set roots[rootsLen] = rootSym;
7764 -
                    set exclusive[rootsLen] =
7765 -
                        receiverClass == types::PointerClass::Owned or receiverMutable;
7766 -
                    set rootsLen += 1;
7767 -
                }
8122 +
                try checkCallLoanConflicts(checker, access.parent, receiverExclusive);
7768 8123
            }
7769 8124
            if receiverClass == types::PointerClass::Ref {
7770 8125
                try checkLinearNode(checker, env, access.parent, LinearUse::Borrow);
7771 8126
            } else if receiverClass == types::PointerClass::Owned {
7772 8127
                try checkLinearNode(checker, env, access.parent, LinearUse::Consume);
7773 8128
            }
8129 +
            if receiverClass <> types::PointerClass::Unsafe {
8130 +
                if let root = linearRootSymbol(checker.resolver, access.parent) {
8131 +
                    set loans.roots[loans.len] = root;
8132 +
                    set loans.exclusive[loans.len] = receiverExclusive;
8133 +
                    set loans.len += 1;
8134 +
                }
8135 +
            }
7774 8136
        }
7775 8137
    }
7776 8138
7777 8139
    for arg, i in call.args {
7778 -
        let expected = *info.paramTypes[i];
7779 -
        let root = linearRootSymbol(checker.resolver, arg);
7780 -
        let mut argExclusive = isLinear(expected);
7781 -
        if let case Type::Pointer { class: types::PointerClass::Ref, mutable, .. } = expected {
7782 -
            set argExclusive = mutable;
7783 -
        } else if let case Type::Slice { class: types::PointerClass::Ref, mutable, .. } = expected {
7784 -
            set argExclusive = mutable;
7785 -
        } else if let case Type::TraitObject {
7786 -
            class: types::PointerClass::Ref, mutable, ..
7787 -
        } = expected {
7788 -
            set argExclusive = mutable;
7789 -
        }
7790 -
        if not isUnsafePointerType(expected) {
7791 -
            if let rootSym = root {
7792 -
                for j in 0..rootsLen {
7793 -
                    if let previous = roots[j] {
7794 -
                        if previous == rootSym and (exclusive[j] or argExclusive) {
7795 -
                            throw emitError(
7796 -
                                checker.resolver,
7797 -
                                arg,
7798 -
                                ErrorKind::BorrowConflict(rootSym.name),
7799 -
                            );
7800 -
                        }
7801 -
                    }
7802 -
                }
7803 -
                set roots[rootsLen] = rootSym;
7804 -
                set exclusive[rootsLen] = argExclusive;
7805 -
                set rootsLen += 1;
7806 -
            }
7807 -
        }
7808 -
        if isRefType(expected) {
7809 -
            try checkLinearNode(checker, env, arg, LinearUse::Borrow);
7810 -
        } else {
7811 -
            try checkLinearNode(checker, env, arg, LinearUse::Consume);
7812 -
        }
8140 +
        try checkLinearCallArg(
8141 +
            checker,
8142 +
            env,
8143 +
            &mut loans,
8144 +
            arg,
8145 +
            *info.paramTypes[i],
8146 +
        );
7813 8147
    }
8148 +
    set checker.loans = loans.parent;
7814 8149
}
7815 8150
7816 8151
/// Check a pattern conditional. Linear scrutinees require an exhaustive match.
7817 8152
fn checkLinearIfLet(
7818 8153
    checker: *mut LinearChecker,
7859 8194
    usage: LinearUse,
7860 8195
) throws (ResolveError) {
7861 8196
    if env.terminated {
7862 8197
        return;
7863 8198
    }
7864 -
    match node.value {
7865 -
        case ast::NodeValue::Ident(_) => {
7866 -
            if usage == LinearUse::Consume {
7867 -
                try consumeLinearIdent(checker, env, node);
7868 -
            }
8199 +
    let mut exclusive = usage == LinearUse::Place;
8200 +
    if usage == LinearUse::Consume {
8201 +
        if let ty = typeFor(checker.resolver, node) {
8202 +
            set exclusive = isLinear(ty);
7869 8203
        }
8204 +
    }
8205 +
    if let case ast::NodeValue::AddressOf(addr) = node.value; addr.mutable {
8206 +
        set exclusive = true;
8207 +
    }
8208 +
    try checkCallLoanConflicts(checker, node, exclusive);
8209 +
    match node.value {
8210 +
        case ast::NodeValue::Ident(_) =>
8211 +
            try checkLinearIdent(checker, env, node, usage),
7870 8212
        case ast::NodeValue::ExprStmt(expr) => {
7871 8213
            if let exprTy = typeFor(checker.resolver, expr) {
7872 8214
                if isLinear(exprTy) {
7873 8215
                    throw emitError(checker.resolver, expr, ErrorKind::LinearDiscard);
7874 8216
                }
7875 8217
            }
7876 8218
            try checkLinearNode(checker, env, expr, LinearUse::Consume);
7877 8219
        }
7878 8220
        case ast::NodeValue::Block(_) => try checkLinearBlock(checker, env, node),
8221 +
        case ast::NodeValue::Unsafe(body) =>
8222 +
            try checkLinearNode(checker, env, body, usage),
7879 8223
        case ast::NodeValue::Let(binding) => {
7880 8224
            if let case ast::NodeValue::Undef = binding.value.value {
7881 8225
                if let bindingTy = typeFor(checker.resolver, binding.ident);
7882 8226
                    isLinear(bindingTy)
7883 8227
                {
7977 8321
            try checkLinearNode(checker, env, repeat.item, LinearUse::Consume);
7978 8322
            try checkLinearNode(checker, env, repeat.count, LinearUse::Consume);
7979 8323
        }
7980 8324
        case ast::NodeValue::BinOp(op) => {
7981 8325
            try checkLinearNode(checker, env, op.left, LinearUse::Consume);
7982 -
            try checkLinearNode(checker, env, op.right, LinearUse::Consume);
8326 +
            if op.op == ast::BinaryOp::And or op.op == ast::BinaryOp::Or {
8327 +
                let skipped = *env;
8328 +
                let mut evaluated = skipped;
8329 +
                try checkLinearNode(
8330 +
                    checker,
8331 +
                    &mut evaluated,
8332 +
                    op.right,
8333 +
                    LinearUse::Consume,
8334 +
                );
8335 +
                try joinLinearBranches(checker, env, skipped, evaluated, node);
8336 +
            } else {
8337 +
                try checkLinearNode(checker, env, op.right, LinearUse::Consume);
8338 +
            }
7983 8339
        }
7984 8340
        case ast::NodeValue::UnOp(op) => {
7985 8341
            try checkLinearNode(checker, env, op.value, LinearUse::Consume);
7986 8342
        }
7987 8343
        case ast::NodeValue::As(expr) => {
8072 8428
            try checkLinearMatch(checker, env, node, matchExpr);
8073 8429
        }
8074 8430
        case ast::NodeValue::Try(tryExpr) => {
8075 8431
            try checkLinearNode(checker, env, tryExpr.expr, usage);
8076 8432
            let success = *env;
8433 +
            if tryExpr.catches.len == 0
8434 +
                and not tryExpr.shouldPanic
8435 +
                and not tryExpr.returnsOptional
8436 +
            {
8437 +
                let mut errorExit = success;
8438 +
                try finishLinearExit(checker, &mut errorExit);
8439 +
            }
8077 8440
            for catchNode in tryExpr.catches {
8078 8441
                let case ast::NodeValue::CatchClause(catchClause) = catchNode.value
8079 8442
                    else panic "checkLinearNode: expected catch";
8080 8443
                let mut branch = success;
8081 8444
                let start = branch.len;
8259 8622
    params: *mut [*ast::Node],
8260 8623
    body: *ast::Node,
8261 8624
) throws (ResolveError) {
8262 8625
    let mut checker = LinearChecker {
8263 8626
        resolver: self,
8627 +
        loans: nil,
8264 8628
        loopMarks: [0; MAX_LINEAR_LOOP_DEPTH],
8265 8629
        loopAvailable: [0; MAX_LINEAR_LOOP_DEPTH],
8266 8630
        loopExitAvailable: [0; MAX_LINEAR_LOOP_DEPTH],
8267 8631
        loopHasNaturalExit: [false; MAX_LINEAR_LOOP_DEPTH],
8268 8632
        loopBreakSeen: [false; MAX_LINEAR_LOOP_DEPTH],
lib/std/lang/resolver/printer.rad +3 -0
418 418
            io::print("function body is not expected");
419 419
        }
420 420
        case super::ErrorKind::IntrinsicUnexpectedBody => {
421 421
            io::print("intrinsic function must not have a body");
422 422
        }
423 +
        case super::ErrorKind::InvalidEcallIntrinsicSignature => {
424 +
            io::print("ecall intrinsic must be unsafe and use signature (u32, i64, i64, i64, i64) -> i64");
425 +
        }
423 426
        case super::ErrorKind::InvalidLoopControl => {
424 427
            io::print("loop control outside of a loop construct");
425 428
        }
426 429
        case super::ErrorKind::TryRequiresThrows => {
427 430
            io::print("try used when function does not declare throws");
lib/std/lang/resolver/tests.rad +726 -149
9 9
use std::lang::scanner;
10 10
use std::lang::module;
11 11
use std::lang::strings;
12 12
13 13
/// Synthetic file path used for resolver tests.
14 -
constant MODULE_PATH: *[u8] = "/dev/test.rad";
14 +
unsafe constant MODULE_PATH: *[u8] = "/dev/test.rad";
15 15
16 16
/// AST arena storage used by resolver tests.
17 -
static AST_ARENA: [u8; 2097152] = undefined;
17 +
unsafe static AST_ARENA: [u8; 2097152] = undefined;
18 18
19 19
/// Resolver arena storage used by resolver tests.
20 -
static ARENA_STORAGE: [u8; 2097152] = undefined;
20 +
unsafe static ARENA_STORAGE: [u8; 2097152] = undefined;
21 21
22 22
/// Node metadata storage used by resolver tests.
23 -
static NODE_DATA_STORAGE: [super::NodeData; 256] = undefined;
23 +
unsafe static NODE_DATA_STORAGE: [super::NodeData; 256] = undefined;
24 24
25 25
/// Diagnostic storage used by resolver tests.
26 -
static ERROR_STORAGE: [super::Error; 16] = undefined;
26 +
unsafe static ERROR_STORAGE: [super::Error; 16] = undefined;
27 27
28 28
/// Package scope used by resolver tests.
29 -
static PKG_SCOPE: super::Scope = undefined;
29 +
unsafe static PKG_SCOPE: super::Scope = undefined;
30 30
31 31
/// Module entries used by resolver tests.
32 -
static MODULE_ENTRIES: [module::ModuleEntry; 8] = undefined;
32 +
unsafe static MODULE_ENTRIES: [module::ModuleEntry; 8] = undefined;
33 33
34 34
/// Module graph used by resolver tests.
35 -
static MODULE_GRAPH: module::ModuleGraph = undefined;
35 +
unsafe static MODULE_GRAPH: module::ModuleGraph = undefined;
36 36
37 37
/// Module AST arena storage used by resolver tests.
38 -
static MODULE_ARENA_STORAGE: [u8; 4096] = undefined;
38 +
unsafe static MODULE_ARENA_STORAGE: [u8; 4096] = undefined;
39 39
40 40
/// Module AST arena used by resolver tests.
41 -
static MODULE_ARENA: ast::NodeArena = undefined;
41 +
unsafe static MODULE_ARENA: ast::NodeArena = undefined;
42 42
43 43
/// Interned string pool used by resolver tests.
44 -
static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
44 +
unsafe static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
45 45
46 46
/// String literals used in tests.
47 -
constant LITERALS: [*[u8]; 15] = [
47 +
unsafe constant LITERALS: [*[u8]; 15] = [
48 48
    "Ok", "Error", "R", "S",
49 49
    "f", "Status", "Pending",
50 50
    "Some", "None", "First",
51 51
    "Second", "Opt", "x",
52 52
    "value", "idx"
344 344
    let mut a = testResolver();
345 345
    let result = try resolveProgramStr(&mut a, program);
346 346
    try expectNoErrors(&result);
347 347
}
348 348
349 +
/// Require a linear use-after-consume error at the later identifier use.
350 +
fn expectLinearUseAfterConsume(
351 +
    program: *[u8],
352 +
    name: *[u8],
353 +
    offset: u32,
354 +
) throws (testing::TestError) {
355 +
    let mut a = testResolver();
356 +
    let result = try resolveProgramStr(&mut a, program);
357 +
    let err = try expectErrorKind(
358 +
        &result,
359 +
        super::ErrorKind::LinearUseAfterConsume(name),
360 +
    );
361 +
    let node = err.node else throw testing::TestError::Failed;
362 +
    try testing::expect(node.span.offset == offset);
363 +
}
364 +
349 365
/// Require an inferred integer type mismatch.
350 366
fn expectIntMismatch(program: *[u8], expected: super::Type)
351 367
    throws (testing::TestError)
352 368
{
353 369
    let mut a = testResolver();
367 383
        throw testing::TestError::Failed;
368 384
    }
369 385
    return body.statements[index];
370 386
}
371 387
388 +
/// Retrieve a declared function body by its statement index.
389 +
fn getFnDeclBody(root: *ast::Node, index: u32) -> ast::Block
390 +
    throws (testing::TestError)
391 +
{
392 +
    let stmt = try getBlockStmt(root, index);
393 +
    let case ast::NodeValue::FnDecl(decl) = stmt.value
394 +
        else throw testing::TestError::Failed;
395 +
    let body = decl.body
396 +
        else throw testing::TestError::Failed;
397 +
    let case ast::NodeValue::Block(block) = body.value
398 +
        else throw testing::TestError::Failed;
399 +
    return block;
400 +
}
401 +
372 402
/// Retrieve a function body block by function name from the program scope.
373 403
fn getFnBody(a: *super::Resolver, root: *ast::Node, name: *[u8]) -> ast::Block
374 404
    throws (testing::TestError)
375 405
{
376 406
    let scope = super::scopeFor(a, root)
513 543
    try expectType(&a, result.root, super::Type::Bool);
514 544
}
515 545
516 546
@test fn testResolveStringLiteralType() throws (testing::TestError) {
517 547
    let mut a = testResolver();
518 -
    let result = try resolveExprStr(&mut a, "\"hello\"");
548 +
    let result = try resolveBlockStr(&mut a, "panic \"hello\";");
519 549
520 550
    try expectNoErrors(&result);
521 -
    let ty = try typeOf(&a, result.root);
551 +
    let stmt = try getBlockStmt(result.root, 0);
552 +
    let case ast::NodeValue::Panic { message } = stmt.value
553 +
        else throw testing::TestError::Failed;
554 +
    let literal = message
555 +
        else throw testing::TestError::Failed;
556 +
    let ty = try typeOf(&a, literal);
522 557
    let elemTy = try expectSliceType(ty, false);
523 558
    try testing::expect(elemTy == super::Type::U8);
524 559
}
525 560
526 561
@test fn testResolveAsNumeric() throws (testing::TestError) {
661 696
    try testing::expect(ast::hasAttribute(sym.attrs, ast::Attribute::Export));
662 697
    try testing::expect(ast::hasAttribute(sym.attrs, ast::Attribute::Default));
663 698
    try testing::expectNot(ast::hasAttribute(sym.attrs, ast::Attribute::Extern));
664 699
}
665 700
701 +
/// The canonical unsafe ecall intrinsic declaration is accepted.
702 +
@test fn testResolveEcallIntrinsicCanonicalSignature() throws (testing::TestError) {
703 +
    let mut a = testResolver();
704 +
    let program = "@intrinsic unsafe fn ecall(number: u32, arg1: i64, arg2: i64, arg3: i64, arg4: i64) -> i64;";
705 +
    let result = try resolveProgramStr(&mut a, program);
706 +
    try expectNoErrors(&result);
707 +
}
708 +
709 +
/// The ecall intrinsic cannot be exposed as a safe function.
710 +
@test fn testResolveEcallIntrinsicRequiresUnsafe() throws (testing::TestError) {
711 +
    let mut a = testResolver();
712 +
    let program = "@intrinsic fn ecall(number: u32, arg1: i64, arg2: i64, arg3: i64, arg4: i64) -> i64;";
713 +
    let result = try resolveProgramStr(&mut a, program);
714 +
    try expectErrorKind(
715 +
        &result,
716 +
        super::ErrorKind::InvalidEcallIntrinsicSignature,
717 +
    );
718 +
}
719 +
720 +
/// The ecall intrinsic declaration must use its canonical ABI.
721 +
@test fn testResolveEcallIntrinsicRequiresCanonicalAbi() throws (testing::TestError) {
722 +
    {
723 +
        let mut a = testResolver();
724 +
        let program = "@intrinsic unsafe fn ecall(number: u32, arg1: i64, arg2: i64, arg3: i64) -> i64;";
725 +
        let result = try resolveProgramStr(&mut a, program);
726 +
        try expectErrorKind(
727 +
            &result,
728 +
            super::ErrorKind::InvalidEcallIntrinsicSignature,
729 +
        );
730 +
    } {
731 +
        let mut a = testResolver();
732 +
        let program = "@intrinsic unsafe fn ecall(number: u32, arg1: u64, arg2: i64, arg3: i64, arg4: i64) -> i64;";
733 +
        let result = try resolveProgramStr(&mut a, program);
734 +
        try expectErrorKind(
735 +
            &result,
736 +
            super::ErrorKind::InvalidEcallIntrinsicSignature,
737 +
        );
738 +
    } {
739 +
        let mut a = testResolver();
740 +
        let program = "@intrinsic unsafe fn ecall(number: u32, arg1: i64, arg2: i64, arg3: i64, arg4: i64) -> i32;";
741 +
        let result = try resolveProgramStr(&mut a, program);
742 +
        try expectErrorKind(
743 +
            &result,
744 +
            super::ErrorKind::InvalidEcallIntrinsicSignature,
745 +
        );
746 +
    }
747 +
}
748 +
666 749
@test fn testSymbolStoresRecordAttributes() throws (testing::TestError) {
667 750
    let mut a = testResolver();
668 751
    let program = "export record S { value: i32 }";
669 752
    let result = try resolveProgramStr(&mut a, program);
670 753
    try expectNoErrors(&result);
843 926
    try expectExprStmtType(&a, stmt, super::Type::I32);
844 927
}
845 928
846 929
@test fn testResolveSliceIndex() throws (testing::TestError) {
847 930
    let mut a = testResolver();
848 -
    let program = "let xs: [i32; 4] = [1, 2, 3, 4]; let slice = &xs[1..]; slice[1];";
931 +
    let program = "fn run(slice: &[i32]) { slice[1]; }";
849 932
    let result = try resolveProgramStr(&mut a, program);
850 933
    try expectNoErrors(&result);
851 934
852 -
    let sliceStmt = try getBlockStmt(result.root, 1);
853 -
    let case ast::NodeValue::Let(sliceDecl) = sliceStmt.value
854 -
        else throw testing::TestError::Failed;
855 -
    let sliceTy = try typeOf(&a, sliceDecl.value);
856 -
    let elemTy = try expectSliceType(sliceTy, false);
857 -
    try testing::expect(elemTy == super::Type::I32);
858 -
859 -
    let indexStmt = try getBlockStmt(result.root, 2);
860 -
    try expectExprStmtType(&a, indexStmt, super::Type::I32);
935 +
    let body = try getFnDeclBody(result.root, 0);
936 +
    try testing::expect(body.statements.len == 1);
937 +
    try expectExprStmtType(&a, body.statements[0], super::Type::I32);
861 938
}
862 939
863 940
@test fn testResolveSliceFields() throws (testing::TestError) {
864 941
    let mut a = testResolver();
865 -
    let program = "let xs: [i32; 3] = [1, 2, 3]; let slice: *[i32] = &xs[1..]; slice.len; slice.ptr;";
942 +
    let program = "fn run(slice: &[i32]) { slice.len; slice.ptr; }";
866 943
    let result = try resolveProgramStr(&mut a, program);
867 944
    try expectNoErrors(&result);
868 945
869 -
    let lenStmt = try getBlockStmt(result.root, 2);
870 -
    let case ast::NodeValue::ExprStmt(lenExpr) = lenStmt.value
946 +
    let body = try getFnDeclBody(result.root, 0);
947 +
    try testing::expect(body.statements.len == 2);
948 +
    let case ast::NodeValue::ExprStmt(lenExpr) = body.statements[0].value
871 949
        else throw testing::TestError::Failed;
872 950
    let lenTy = try typeOf(&a, lenExpr);
873 951
    try testing::expect(lenTy == super::Type::U32);
874 952
875 -
    let ptrStmt = try getBlockStmt(result.root, 3);
876 -
    let case ast::NodeValue::ExprStmt(ptrExpr) = ptrStmt.value
953 +
    let case ast::NodeValue::ExprStmt(ptrExpr) = body.statements[1].value
877 954
        else throw testing::TestError::Failed;
878 955
    let ptrTy = try typeOf(&a, ptrExpr);
879 -
    let targetTy = try expectPointerType(ptrTy, false);
880 -
    try testing::expect(targetTy == super::Type::I32);
956 +
    let case super::Type::Pointer { class, target, mutable } = ptrTy
957 +
        else throw testing::TestError::Failed;
958 +
    try testing::expect(class == types::PointerClass::Ref);
959 +
    try testing::expect(not mutable);
960 +
    try testing::expect(*target == super::Type::I32);
881 961
}
882 962
883 963
@test fn testResolveSliceLiteralImmutable() throws (testing::TestError) {
884 964
    let mut a = testResolver();
885 -
    let program = "let slice: *[i32] = &[1, 2, 3];";
965 +
    let program = "unsafe fn run() { let slice: *[i32] = &[1, 2, 3]; }";
886 966
    let result = try resolveProgramStr(&mut a, program);
887 967
    try expectNoErrors(&result);
888 968
}
889 969
890 970
/// Empty array literal infers element type from slice annotation.
891 971
@test fn testResolveSliceLiteralEmpty() throws (testing::TestError) {
892 972
    let mut a = testResolver();
893 -
    let program = "let slice: *[i32] = &[];";
973 +
    let program = "unsafe fn run() { let slice: *[i32] = &[]; }";
894 974
    let result = try resolveProgramStr(&mut a, program);
895 975
    try expectNoErrors(&result);
896 976
}
897 977
898 978
/// Nested array literal should infer inner element type from slice annotation.
899 979
@test fn testResolveSliceLiteralNestedArray() throws (testing::TestError) {
900 980
    let mut a = testResolver();
901 -
    let program = "let slice: *[[i32; 2]] = &[[1, 2], [3, 4]];";
981 +
    let program = "unsafe fn run() { let slice: *[[i32; 2]] = &[[1, 2], [3, 4]]; }";
902 982
    let result = try resolveProgramStr(&mut a, program);
903 983
    try expectNoErrors(&result);
904 984
}
905 985
906 986
@test fn testResolveSliceFromArray() throws (testing::TestError) {
907 987
    {
908 988
        let mut a = testResolver();
909 -
        let program = "let xs: [i32; 3] = [1, 2, 3]; let slice: *[i32] = &xs[..];";
989 +
        let program = "unsafe fn run() { let xs: [i32; 3] = [1, 2, 3]; let slice: *[i32] = &xs[..]; }";
910 990
        let result = try resolveProgramStr(&mut a, program);
911 991
        try expectNoErrors(&result);
912 992
    } {
913 993
        let mut a = testResolver();
914 -
        let program = "let xs: [i32; 3] = [1, 2, 3]; let slice: *[i32] = &xs[0..3];";
994 +
        let program = "unsafe fn run() { let xs: [i32; 3] = [1, 2, 3]; let slice: *[i32] = &xs[0..3]; }";
915 995
        let result = try resolveProgramStr(&mut a, program);
916 996
        try expectNoErrors(&result);
917 997
    } {
918 998
        let mut a = testResolver();
919 -
        let program = "let xs: [i32; 3] = [1, 2, 3]; let slice: *[i32] = &xs[..3];";
999 +
        let program = "unsafe fn run() { let xs: [i32; 3] = [1, 2, 3]; let slice: *[i32] = &xs[..3]; }";
920 1000
        let result = try resolveProgramStr(&mut a, program);
921 1001
        try expectNoErrors(&result);
922 1002
    } {
923 1003
        let mut a = testResolver();
924 -
        let program = "let xs: [u8; 2] = [1, 2]; let slice = &xs[1..1];";
1004 +
        let program = "unsafe fn run() { let xs: [u8; 2] = [1, 2]; let slice = &xs[1..1]; }";
925 1005
        let result = try resolveProgramStr(&mut a, program);
926 1006
        try expectNoErrors(&result);
927 1007
    }
928 1008
}
929 1009
936 1016
        else throw testing::TestError::Failed;
937 1017
}
938 1018
939 1019
@test fn testResolveSliceLiteralMutable() throws (testing::TestError) {
940 1020
    let mut a = testResolver();
941 -
    let program = "let slice: *mut [i32] = &mut [1, 2, 3];";
1021 +
    let program = "unsafe fn run() { let slice: *mut [i32] = &mut [1, 2, 3]; }";
942 1022
    let result = try resolveProgramStr(&mut a, program);
943 1023
    try expectNoErrors(&result);
944 1024
}
945 1025
946 1026
@test fn testResolvePointerMutableAssignmentRequiresMut() throws (testing::TestError) {
952 1032
        else throw testing::TestError::Failed;
953 1033
}
954 1034
955 1035
@test fn testResolvePointerMutableToImmutableAssignment() throws (testing::TestError) {
956 1036
    let mut a = testResolver();
957 -
    let program = "let mut x: i32 = 0; let mptr: *mut i32 = &mut x; let ptr: *i32 = mptr;";
1037 +
    let program = "fn f(mptr: *mut i32) -> *i32 { return mptr; }";
958 1038
    let result = try resolveProgramStr(&mut a, program);
959 1039
    try expectNoErrors(&result);
960 1040
}
961 1041
962 1042
@test fn testResolveAddressOfRequiresMutableBinding() throws (testing::TestError) {
963 1043
    {
964 1044
        let mut a = testResolver();
965 -
        let program = "let x: i32 = 0; let ptr = &mut x;";
1045 +
        let program = "fn borrow(ptr: &mut i32) {} fn run() { let x: i32 = 0; borrow(&mut x); }";
966 1046
        let result = try resolveProgramStr(&mut a, program);
967 1047
        try expectErrorKind(&result, super::ErrorKind::ImmutableBinding);
968 1048
    } {
969 1049
        let mut a = testResolver();
970 -
        let program = "let mut x: i32 = 0; let ptr = &mut x;";
1050 +
        let program = "fn borrow(ptr: &mut i32) {} fn run() { let mut x: i32 = 0; borrow(&mut x); }";
971 1051
        let result = try resolveProgramStr(&mut a, program);
972 1052
        try expectNoErrors(&result);
973 1053
    }
974 1054
}
975 1055
976 1056
@test fn testResolveAddressOfSliceRequiresMutableBinding() throws (testing::TestError) {
977 1057
    {
978 1058
        let mut a = testResolver();
979 -
        let program = "let xs: [i32; 3] = [1, 2, 3]; let slice = &mut xs[..];";
1059 +
        let program = "fn borrow(slice: &mut [i32]) {} fn run() { let xs: [i32; 3] = [1, 2, 3]; borrow(&mut xs[..]); }";
980 1060
        let result = try resolveProgramStr(&mut a, program);
981 1061
        try expectErrorKind(&result, super::ErrorKind::ImmutableBinding);
982 1062
    } {
983 1063
        let mut a = testResolver();
984 -
        let program = "let mut xs: [i32; 3] = [1, 2, 3]; let slice = &mut xs[..];";
1064 +
        let program = "fn borrow(slice: &mut [i32]) {} fn run() { let mut xs: [i32; 3] = [1, 2, 3]; borrow(&mut xs[..]); }";
985 1065
        let result = try resolveProgramStr(&mut a, program);
986 1066
        try expectNoErrors(&result);
987 1067
    }
988 1068
}
989 1069
1804 1884
        let result = try resolveProgramStr(&mut a, program);
1805 1885
        try expectNoErrors(&result);
1806 1886
    }
1807 1887
    {
1808 1888
        let mut a = testResolver();
1809 -
        let program = "let mut xs: [u8; 2] = [0, 1]; let slice: *mut [u8] = &mut xs[..]; set slice[0] = 1;";
1889 +
        let program = "fn assign(slice: &mut [u8]) { set slice[0] = 1; } fn run() { let mut xs: [u8; 2] = [0, 1]; assign(&mut xs[..]); }";
1810 1890
        let result = try resolveProgramStr(&mut a, program);
1811 1891
        try expectNoErrors(&result);
1812 1892
    }
1813 1893
    {
1814 1894
        let mut a = testResolver();
1815 -
        let program = "let mut xs: [u8; 2] = [0, 1]; let mut slice: *[u8] = &xs[..]; set slice[0] = 1;";
1895 +
        let program = "fn f(input: *[u8]) { let mut slice: *[u8] = input; set slice[0] = 1; }";
1816 1896
        let result = try resolveProgramStr(&mut a, program);
1817 1897
        try expectErrorKind(&result, super::ErrorKind::ImmutableBinding);
1818 1898
    }
1819 1899
    {
1820 1900
        let mut a = testResolver();
1822 1902
        let result = try resolveProgramStr(&mut a, program);
1823 1903
        try expectErrorKind(&result, super::ErrorKind::ImmutableBinding);
1824 1904
    }
1825 1905
    {
1826 1906
        let mut a = testResolver();
1827 -
        let program = "let mut xs: [u8; 2] = [0, 1]; let slice: *[u8] = &xs[..]; set slice[0] = 1;";
1907 +
        let program = "fn f(slice: &[u8]) { set slice[0] = 1; }";
1828 1908
        let result = try resolveProgramStr(&mut a, program);
1829 1909
        try expectErrorKind(&result, super::ErrorKind::ImmutableBinding);
1830 1910
    }
1831 1911
}
1832 1912
2146 2226
}
2147 2227
2148 2228
@test fn testUndefinedCoercions() throws (testing::TestError) {
2149 2229
    {
2150 2230
        let mut a = testResolver();
2151 -
        let result = try resolveBlockStr(&mut a, "let count: i32 = undefined;");
2231 +
        let result = try resolveBlockStr(&mut a, "unsafe { let count: i32 = undefined; }");
2152 2232
        try expectNoErrors(&result);
2153 2233
    } {
2154 2234
        let mut a = testResolver();
2155 -
        let program = "let mut value: i32 = 0; set value = undefined;";
2235 +
        let program = "let mut value: i32 = 0; unsafe { set value = undefined; }";
2156 2236
        let result = try resolveProgramStr(&mut a, program);
2157 2237
        try expectNoErrors(&result);
2158 2238
    } {
2159 2239
        let mut a = testResolver();
2160 -
        let program = "fn f(x: i32) {} fn g() { f(undefined); }";
2240 +
        let program = "fn f(x: i32) {} fn g() { unsafe { f(undefined); } }";
2161 2241
        let result = try resolveProgramStr(&mut a, program);
2162 2242
        try expectNoErrors(&result);
2163 2243
    } {
2164 2244
        let mut a = testResolver();
2165 -
        let program = "fn fetch() -> i32 { return undefined; }";
2245 +
        let program = "fn fetch() -> i32 { unsafe { return undefined; } }";
2166 2246
        let result = try resolveProgramStr(&mut a, program);
2167 2247
        try expectNoErrors(&result);
2168 2248
    }
2169 2249
}
2170 2250
2739 2819
// Dereference tests //////////////////////////////////////////////////////////
2740 2820
2741 2821
@test fn testResolveDeref() throws (testing::TestError) {
2742 2822
    {
2743 2823
        let mut a = testResolver();
2744 -
        let result = try resolveBlockStr(&mut a, "let x: i32 = 42; let ptr: *i32 = &x; *ptr;");
2824 +
        let result = try resolveProgramStr(&mut a, "fn f(ptr: &i32) { *ptr; }");
2745 2825
        try expectNoErrors(&result);
2746 -
        let stmt = try parser::tests::getBlockLastStmt(result.root);
2826 +
        let body = try getFnBody(&a, result.root, "f");
2827 +
        if body.statements.len <> 1 {
2828 +
            throw testing::TestError::Failed;
2829 +
        }
2830 +
        let stmt = body.statements[0];
2747 2831
        try expectExprStmtType(&a, stmt, super::Type::I32);
2748 2832
    } {
2749 2833
        let mut a = testResolver();
2750 2834
        let result = try resolveExprStr(&mut a, "*42");
2751 2835
        try expectErrorKind(&result, super::ErrorKind::ExpectedPointer);
2757 2841
}
2758 2842
2759 2843
@test fn testResolveAssignDeref() throws (testing::TestError) {
2760 2844
    {
2761 2845
        let mut a = testResolver();
2762 -
        let program = "let mut x: i32 = 0; let ptr: *mut i32 = &mut x; set *ptr = 42;";
2846 +
        let program = "fn f(ptr: &mut i32) { set *ptr = 42; }";
2763 2847
        let result = try resolveProgramStr(&mut a, program);
2764 2848
        try expectNoErrors(&result);
2765 2849
    } {
2766 2850
        let mut a = testResolver();
2767 -
        let program = "let mut x: i32 = 0; let ptr: *i32 = &x; set *ptr = 42;";
2851 +
        let program = "fn f(ptr: &i32) { set *ptr = 42; }";
2768 2852
        let result = try resolveProgramStr(&mut a, program);
2769 2853
        try expectErrorKind(&result, super::ErrorKind::ImmutableBinding);
2770 2854
    } {
2771 2855
        let mut a = testResolver();
2772 -
        let program = "let mut x: i32 = 0; let mut ptr: *i32 = &x; set *ptr = 42;";
2856 +
        let program = "fn f(input: *i32) { let mut ptr: *i32 = input; set *ptr = 42; }";
2773 2857
        let result = try resolveProgramStr(&mut a, program);
2774 2858
        try expectErrorKind(&result, super::ErrorKind::ImmutableBinding);
2775 2859
    } {
2776 2860
        let mut a = testResolver();
2777 -
        let program = "let mut x: u8 = 0; let mut ptr: *mut u8 = &mut x; set *ptr = 255;";
2861 +
        let program = "fn f(ptr: &mut u8) { set *ptr = 255; }";
2778 2862
        let result = try resolveProgramStr(&mut a, program);
2779 2863
        try expectNoErrors(&result);
2780 2864
    }
2781 2865
}
2782 2866
3440 3524
}
3441 3525
3442 3526
/// Test that mutable pointer parameters can be borrowed mutably.
3443 3527
@test fn testMutableBorrowFromMutablePointer() throws (testing::TestError) {
3444 3528
    let mut a = testResolver();
3445 -
    let program = "fn f(p: *mut i32) { let x: *mut i32 = &mut *p; }";
3529 +
    let program = "fn borrow(value: &mut i32) {} fn f(p: &mut i32) { borrow(&mut *p); }";
3446 3530
    let result = try resolveProgramStr(&mut a, program);
3447 3531
    try expectNoErrors(&result);
3448 3532
}
3449 3533
3450 3534
/// Test that mutable slice parameters can be borrowed mutably.
3451 3535
@test fn testMutableBorrowFromMutableSlice() throws (testing::TestError) {
3452 3536
    let mut a = testResolver();
3453 -
    let program = "fn f(s: *mut [i32]) { let x: *mut i32 = &mut s[0]; }";
3537 +
    let program = "fn borrow(value: &mut i32) {} fn f(s: &mut [i32]) { borrow(&mut s[0]); }";
3454 3538
    let result = try resolveProgramStr(&mut a, program);
3455 3539
    try expectNoErrors(&result);
3456 3540
}
3457 3541
3458 3542
/// Test borrowing mutably from a field access on a call returning `*mut`.
3459 3543
@test fn testMutableBorrowFromCallReturningMutablePointer() throws (testing::TestError) {
3460 3544
    let mut a = testResolver();
3461 -
    let program = "record Box { x: i32 } fn idBox(b: *mut Box) -> *mut Box { return b; } fn f() { let mut b = Box { x: 1 }; let px: *mut i32 = &mut idBox(&mut b).x; }";
3545 +
    let program = "record Box { x: i32 } fn idBox(b: *mut Box) -> *mut Box { return b; } unsafe fn f() -> *mut i32 { let mut b = Box { x: 1 }; let px: *mut i32 = &mut idBox(&mut b).x; return px; }";
3462 3546
    let result = try resolveProgramStr(&mut a, program);
3463 3547
    try expectNoErrors(&result);
3464 3548
}
3465 3549
3466 3550
/// Test that calls returning immutable pointers cannot be mutably borrowed.
3467 3551
@test fn testMutableBorrowFromCallReturningImmutablePointer() throws (testing::TestError) {
3468 3552
    let mut a = testResolver();
3469 -
    let program = "record Box { x: i32 } fn idBox(b: *Box) -> *Box { return b; } fn f() { let b = Box { x: 1 }; let px: *mut i32 = &mut idBox(&b).x; }";
3553 +
    let program = "record Box { x: i32 } fn idBox(b: *Box) -> *Box { return b; } fn borrow(px: &mut i32) {} fn f(b: *Box) { borrow(&mut idBox(b).x); }";
3470 3554
    let result = try resolveProgramStr(&mut a, program);
3471 3555
    try expectErrorKind(&result, super::ErrorKind::ImmutableBinding);
3472 3556
}
3473 3557
3474 3558
/// Test borrowing mutably from a public static through scope access.
3476 3560
    let mut a = testResolver();
3477 3561
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3478 3562
3479 3563
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod statics; mod app;", &mut arena);
3480 3564
    let staticsId = try registerModule(&mut MODULE_GRAPH, rootId, "statics", "export static COUNTER: i32 = 0;", &mut arena);
3481 -
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::statics; fn main() { let p: *mut i32 = &mut statics::COUNTER; set *p = 7; }", &mut arena);
3565 +
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::statics; fn assign(p: &mut i32) { set *p = 7; } fn main() { assign(&mut statics::COUNTER); }", &mut arena);
3482 3566
3483 3567
    let result = try resolveModuleTree(&mut a, rootId);
3484 3568
    try expectNoErrors(&result);
3485 3569
}
3486 3570
3498 3582
}
3499 3583
3500 3584
/// Test that mutable bindings of immutable pointers cannot borrow mutably through the pointer.
3501 3585
@test fn testMutableBorrowFromMutableBindingOfPointer() throws (testing::TestError) {
3502 3586
    let mut a = testResolver();
3503 -
    let program = "fn f() { let mut x: i32 = 1; let p: *i32 = &x; let y = &mut *p; }";
3587 +
    let program = "fn borrow(value: &mut i32) {} fn f(input: *i32) { let mut p: *i32 = input; borrow(&mut *p); }";
3504 3588
    let result = try resolveProgramStr(&mut a, program);
3505 3589
    let err = try expectError(&result);
3506 3590
    try expectErrorKind(&result, super::ErrorKind::ImmutableBinding);
3507 3591
}
3508 3592
3520 3604
/// Test that mutable slice parameters can be assigned through index.
3521 3605
@test fn testAssignThroughMutableSliceParam() throws (testing::TestError) {
3522 3606
    {
3523 3607
        // Mutable slice param: direct assignment should work
3524 3608
        let mut a = testResolver();
3525 -
        let program = "fn f(slice: *mut [i32]) { set slice[0] = 1; }";
3609 +
        let program = "fn f(slice: &mut [i32]) { set slice[0] = 1; }";
3526 3610
        let result = try resolveProgramStr(&mut a, program);
3527 3611
        try expectNoErrors(&result);
3528 3612
    } {
3529 3613
        // Immutable slice param: direct assignment should fail
3530 3614
        let mut a = testResolver();
3531 -
        let program = "fn f(slice: *[i32]) { set slice[0] = 1; }";
3615 +
        let program = "fn f(slice: &[i32]) { set slice[0] = 1; }";
3532 3616
        let result = try resolveProgramStr(&mut a, program);
3533 3617
        try expectErrorKind(&result, super::ErrorKind::ImmutableBinding);
3534 3618
    }
3535 3619
}
3536 3620
3616 3700
}
3617 3701
3618 3702
/// Test that record fields can be assigned through a mutable pointer.
3619 3703
@test fn testMutableAssignToMutablePointerToRecord() throws (testing::TestError) {
3620 3704
    let mut a = testResolver();
3621 -
    let program = "record S { x: i32 } fn f(p: *mut S) { set p.x = 2; }";
3705 +
    let program = "record S { x: i32 } fn f(p: &mut S) { set p.x = 2; }";
3622 3706
    let result = try resolveProgramStr(&mut a, program);
3623 3707
    try expectNoErrors(&result);
3624 3708
}
3625 3709
3626 3710
/// Test that record fields cannot be assigned through an immutable pointer.
3635 3719
// Opaque pointer tests.
3636 3720
3637 3721
/// You can assign any pointer (*T) to an opaque pointer (*opaque) without a cast.
3638 3722
@test fn testOpaquePointerAutoCoercion() throws (testing::TestError) {
3639 3723
    let mut a = testResolver();
3640 -
    let result = try resolveProgramStr(&mut a, "fn f(x: i32) { let mut ptr: *i32 = &x; let o: *opaque = ptr; set ptr = o as *i32; }");
3724 +
    let result = try resolveProgramStr(&mut a, "fn f(ptr: *i32) -> *opaque { return ptr; }");
3641 3725
    try expectNoErrors(&result);
3642 3726
}
3643 3727
3644 3728
/// You cannot assign an opaque pointer to a non-opaque pointer without a cast.
3645 3729
@test fn testOpaquePointerNoReverseCoercion() throws (testing::TestError) {
3646 3730
    let mut a = testResolver();
3647 -
    let result = try resolveProgramStr(&mut a, "fn f(a: i32) { let o: *opaque = &a; let ptr: *i32 = o; }");
3731 +
    let result = try resolveProgramStr(&mut a, "fn f(o: *opaque) { let ptr: *i32 = o; }");
3648 3732
    let err = try expectError(&result);
3649 3733
    let case super::ErrorKind::TypeMismatch(mismatch) = err.kind
3650 3734
        else throw testing::TestError::Failed;
3651 3735
    let case super::Type::Pointer { target: expectedTarget, .. } = mismatch.expected
3652 3736
        else throw testing::TestError::Failed;
3664 3748
        let result = try resolveProgramStr(&mut a, "fn f(x: opaque) {}");
3665 3749
        let err = try expectError(&result);
3666 3750
        try expectErrorKind(&result, super::ErrorKind::OpaqueTypeNotAllowed);
3667 3751
    } {
3668 3752
        let mut a = testResolver();
3669 -
        let result = try resolveProgramStr(&mut a, "fn f() { let x: opaque = undefined; }");
3753 +
        let result = try resolveProgramStr(&mut a, "fn f() { unsafe { let x: opaque = undefined; } }");
3670 3754
        let err = try expectError(&result);
3671 3755
        try expectErrorKind(&result, super::ErrorKind::OpaqueTypeNotAllowed);
3672 3756
    } {
3673 3757
        let mut a = testResolver();
3674 3758
        let result = try resolveProgramStr(&mut a, "record R { x: opaque }");
3678 3762
}
3679 3763
3680 3764
/// You cannot dereference an opaque pointer, you have to cast it first.
3681 3765
@test fn testOpaquePointerNoDereference() throws (testing::TestError) {
3682 3766
    let mut a = testResolver();
3683 -
    let result = try resolveProgramStr(&mut a, "fn f(a: i32) { let o: *opaque = &a; let x = *o; }");
3767 +
    let result = try resolveProgramStr(&mut a, "fn f(o: *opaque) { let x = *o; }");
3684 3768
    let err = try expectError(&result);
3685 3769
    try expectErrorKind(&result, super::ErrorKind::OpaqueTypeDeref);
3686 3770
}
3687 3771
3688 3772
/// Test that you can dereference after casting.
3689 3773
@test fn testOpaquePointerDereferenceAfterCast() throws (testing::TestError) {
3690 3774
    let mut a = testResolver();
3691 -
    let result = try resolveProgramStr(&mut a, "fn f() { let o: *opaque = undefined; let x = *(o as *i32); }");
3775 +
    let result = try resolveProgramStr(&mut a, "unsafe fn f(o: *opaque) -> *i32 { let ptr = o as *i32; let x = *ptr; return ptr; }");
3692 3776
    try expectNoErrors(&result);
3693 3777
}
3694 3778
3695 3779
/// You cannot do pointer arithmetic with an opaque pointer.
3696 3780
@test fn testOpaquePointerNoArithmetic() throws (testing::TestError) {
3697 3781
    {
3698 3782
        let mut a = testResolver();
3699 -
        let result = try resolveProgramStr(&mut a, "fn f(a: i32) { let o: *opaque = &a; let x = o + 1; }");
3783 +
        let result = try resolveProgramStr(&mut a, "fn f(o: *opaque) { let x = o + 1; }");
3700 3784
        let err = try expectError(&result);
3701 3785
        try expectErrorKind(&result, super::ErrorKind::OpaquePointerArithmetic);
3702 3786
    } {
3703 3787
        let mut a = testResolver();
3704 -
        let result = try resolveProgramStr(&mut a, "fn f(a: i32) { let o: *opaque = &a; let x = 1 + o; }");
3788 +
        let result = try resolveProgramStr(&mut a, "fn f(o: *opaque) { let x = 1 + o; }");
3705 3789
        let err = try expectError(&result);
3706 3790
        try expectErrorKind(&result, super::ErrorKind::OpaquePointerArithmetic);
3707 3791
    } {
3708 3792
        let mut a = testResolver();
3709 -
        let result = try resolveProgramStr(&mut a, "fn f(a: i32) { let o: *opaque = &a; let x = o - 1; }");
3793 +
        let result = try resolveProgramStr(&mut a, "fn f(o: *opaque) { let x = o - 1; }");
3710 3794
        let err = try expectError(&result);
3711 3795
        try expectErrorKind(&result, super::ErrorKind::OpaquePointerArithmetic);
3712 3796
    } {
3713 3797
        let mut a = testResolver();
3714 -
        let result = try resolveProgramStr(&mut a, "fn f(a: i32) { let o: *opaque = &a; let x = 1 - o; }");
3798 +
        let result = try resolveProgramStr(&mut a, "fn f(o: *opaque) { let x = 1 - o; }");
3715 3799
        let err = try expectError(&result);
3716 3800
        try expectErrorKind(&result, super::ErrorKind::OpaquePointerArithmetic);
3717 3801
    }
3718 3802
}
3719 3803
3822 3906
/// enabling compile-time evaluation of array sizes using imported constants.
3823 3907
@test fn testImportedConstantInArraySize() throws (testing::TestError) {
3824 3908
    let mut a = testResolver();
3825 3909
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3826 3910
3827 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod consts; mod app;", &mut arena);
3911 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod consts; unsafe mod app;", &mut arena);
3828 3912
    let constsId = try registerModule(&mut MODULE_GRAPH, rootId, "consts", "export constant SIZE: u32 = 8;", &mut arena);
3829 3913
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::consts; static BUFFER: [u8; consts::SIZE] = undefined;", &mut arena);
3830 3914
3831 3915
    let result = try resolveModuleTree(&mut a, rootId);
3832 3916
    try expectNoErrors(&result);
4129 4213
/// Test @sliceOf with correct arguments succeeds.
4130 4214
@test fn testResolveSliceOfCorrect() throws (testing::TestError) {
4131 4215
    // Immutable pointer.
4132 4216
    {
4133 4217
        let mut a = testResolver();
4134 -
        let program = "fn f(ptr: *u8, len: u32) -> *[u8] { return @sliceOf(ptr, len); }";
4218 +
        let program = "fn f(ptr: *u8, len: u32) -> *[u8] { unsafe { return @sliceOf(ptr, len); } }";
4135 4219
        let result = try resolveProgramStr(&mut a, program);
4136 4220
        try expectNoErrors(&result);
4137 4221
    }
4138 4222
    // Mutable pointer produces mutable slice.
4139 4223
    {
4140 4224
        let mut a = testResolver();
4141 -
        let program = "fn f(ptr: *mut u8, len: u32) -> *mut [u8] { return @sliceOf(ptr, len); }";
4225 +
        let program = "fn f(ptr: *mut u8, len: u32) -> *mut [u8] { unsafe { return @sliceOf(ptr, len); } }";
4142 4226
        let result = try resolveProgramStr(&mut a, program);
4143 4227
        try expectNoErrors(&result);
4144 4228
    }
4145 4229
}
4146 4230
4184 4268
/// Test @sliceOf with wrong argument types produces errors.
4185 4269
@test fn testResolveSliceOfWrongArgTypes() throws (testing::TestError) {
4186 4270
    // Non-pointer first argument.
4187 4271
    {
4188 4272
        let mut a = testResolver();
4189 -
        let program = "fn f(val: u32, len: u32) -> *[u8] { return @sliceOf(val, len); }";
4273 +
        let program = "fn f(val: u32, len: u32) -> *[u8] { return unsafe { @sliceOf(val, len) }; }";
4190 4274
        let result = try resolveProgramStr(&mut a, program);
4191 4275
        let err = try expectError(&result);
4192 4276
        let case super::ErrorKind::ExpectedPointer = err.kind
4193 4277
            else throw testing::TestError::Failed;
4194 4278
    }
4195 4279
    // Array instead of pointer.
4196 4280
    {
4197 4281
        let mut a = testResolver();
4198 -
        let program = "fn f(arr: [u8; 4], len: u32) -> *[u8] { return @sliceOf(arr, len); }";
4282 +
        let program = "fn f(arr: [u8; 4], len: u32) -> *[u8] { return unsafe { @sliceOf(arr, len) }; }";
4199 4283
        let result = try resolveProgramStr(&mut a, program);
4200 4284
        let err = try expectError(&result);
4201 4285
        let case super::ErrorKind::ExpectedPointer = err.kind
4202 4286
            else throw testing::TestError::Failed;
4203 4287
    }
4204 4288
    // Non-numeric second argument.
4205 4289
    {
4206 4290
        let mut a = testResolver();
4207 -
        let program = "fn f(ptr: *u8, len: bool) -> *[u8] { return @sliceOf(ptr, len); }";
4291 +
        let program = "fn f(ptr: *u8, len: bool) -> *[u8] { return unsafe { @sliceOf(ptr, len) }; }";
4208 4292
        let result = try resolveProgramStr(&mut a, program);
4209 4293
        let err = try expectError(&result);
4210 4294
        let case super::ErrorKind::TypeMismatch(_) = err.kind
4211 4295
            else throw testing::TestError::Failed;
4212 4296
    }
4213 4297
    // Pointer second argument.
4214 4298
    {
4215 4299
        let mut a = testResolver();
4216 -
        let program = "fn f(ptr: *u8, len: *u32) -> *[u8] { return @sliceOf(ptr, len); }";
4300 +
        let program = "fn f(ptr: *u8, len: *u32) -> *[u8] { return unsafe { @sliceOf(ptr, len) }; }";
4217 4301
        let result = try resolveProgramStr(&mut a, program);
4218 4302
        let err = try expectError(&result);
4219 4303
        let case super::ErrorKind::TypeMismatch(_) = err.kind
4220 4304
            else throw testing::TestError::Failed;
4221 4305
    }
4223 4307
4224 4308
/// Test @sliceOf with 3 arguments (ptr, len, cap) succeeds.
4225 4309
@test fn testResolveSliceOfWithCap() throws (testing::TestError) {
4226 4310
    {
4227 4311
        let mut a = testResolver();
4228 -
        let program = "fn f(ptr: *u8, len: u32, cap: u32) -> *[u8] { return @sliceOf(ptr, len, cap); }";
4312 +
        let program = "fn f(ptr: *u8, len: u32, cap: u32) -> *[u8] { unsafe { return @sliceOf(ptr, len, cap); } }";
4229 4313
        let result = try resolveProgramStr(&mut a, program);
4230 4314
        try expectNoErrors(&result);
4231 4315
    }
4232 4316
    // Mutable pointer produces mutable slice.
4233 4317
    {
4234 4318
        let mut a = testResolver();
4235 -
        let program = "fn f(ptr: *mut u8, len: u32, cap: u32) -> *mut [u8] { return @sliceOf(ptr, len, cap); }";
4319 +
        let program = "fn f(ptr: *mut u8, len: u32, cap: u32) -> *mut [u8] { unsafe { return @sliceOf(ptr, len, cap); } }";
4236 4320
        let result = try resolveProgramStr(&mut a, program);
4237 4321
        try expectNoErrors(&result);
4238 4322
    }
4239 4323
}
4240 4324
4241 4325
/// Test @sliceOf with 3 arguments but wrong cap type.
4242 4326
@test fn testResolveSliceOfCapWrongType() throws (testing::TestError) {
4243 4327
    let mut a = testResolver();
4244 -
    let program = "fn f(ptr: *u8, len: u32, cap: bool) -> *[u8] { return @sliceOf(ptr, len, cap); }";
4328 +
    let program = "fn f(ptr: *u8, len: u32, cap: bool) -> *[u8] { return unsafe { @sliceOf(ptr, len, cap) }; }";
4245 4329
    let result = try resolveProgramStr(&mut a, program);
4246 4330
    let err = try expectError(&result);
4247 4331
    let case super::ErrorKind::TypeMismatch(_) = err.kind
4248 4332
        else throw testing::TestError::Failed;
4249 4333
}
4250 4334
4251 4335
/// Test .cap field access on slices resolves to u32.
4252 4336
@test fn testResolveSliceCapField() throws (testing::TestError) {
4253 4337
    let mut a = testResolver();
4254 -
    let program = "fn f(s: *[u8]) -> u32 { return s.cap; }";
4338 +
    let program = "fn f(s: &[u8]) -> u32 { return s.cap; }";
4255 4339
    let result = try resolveProgramStr(&mut a, program);
4256 4340
    try expectNoErrors(&result);
4257 4341
}
4258 4342
4343 +
/// Require a slice append allocator expression to produce a type mismatch.
4344 +
fn expectSliceAppendAllocatorError(program: *[u8]) throws (testing::TestError) {
4345 +
    let mut a = testResolver();
4346 +
    let result = try resolveProgramStr(&mut a, program);
4347 +
    let err = try expectError(&result);
4348 +
    let case super::ErrorKind::TypeMismatch(_) = err.kind
4349 +
        else throw testing::TestError::Failed;
4350 +
}
4351 +
4259 4352
/// Test `.append()` on immutable slice produces an error.
4260 4353
@test fn testResolveSliceAppendImmutable() throws (testing::TestError) {
4261 4354
    let mut a = testResolver();
4262 4355
    let program = "record A { func: fn(*mut opaque, u32, u32) -> *mut opaque, ctx: *mut opaque } fn f(s: *[i32], a: A) { s.append(1, a); }";
4263 4356
    let result = try resolveProgramStr(&mut a, program);
4292 4385
    }
4293 4386
}
4294 4387
4295 4388
/// Test `.append()` with correct arguments succeeds.
4296 4389
@test fn testResolveSliceAppendCorrect() throws (testing::TestError) {
4390 +
    // A structurally matching allocator record is accepted.
4391 +
    {
4392 +
        let mut a = testResolver();
4393 +
        let program = "record A { func: fn(*mut opaque, u32, u32) -> *mut opaque, ctx: *mut opaque } unsafe fn f(s: &mut [i32], a: A) { s.append(1, a); }";
4394 +
        let result = try resolveProgramStr(&mut a, program);
4395 +
        try expectNoErrors(&result);
4396 +
    }
4397 +
    // Function structural matching and concrete-to-opaque pointer coercion are preserved.
4398 +
    {
4399 +
        let mut a = testResolver();
4400 +
        let program = "record C { value: u32 } record A { callback: fn(*mut opaque, u32, u32) -> *mut opaque, context: *mut opaque } fn allocate(ctx: *mut opaque, size: u32, alignment: u32) -> *mut opaque { return ctx; } unsafe fn f(s: &mut [i32], ctx: *mut C) { s.append(1, A { callback: allocate, context: ctx }); }";
4401 +
        let result = try resolveProgramStr(&mut a, program);
4402 +
        try expectNoErrors(&result);
4403 +
    }
4404 +
}
4405 +
4406 +
/// Test `.append()` requires an unsafe context even with a valid allocator.
4407 +
@test fn testResolveSliceAppendRequiresUnsafe() throws (testing::TestError) {
4297 4408
    let mut a = testResolver();
4298 -
    let program = "record A { func: fn(*mut opaque, u32, u32) -> *mut opaque, ctx: *mut opaque } fn f(s: *mut [i32], a: A) { s.append(1, a); }";
4409 +
    let program = "record A { func: fn(*mut opaque, u32, u32) -> *mut opaque, ctx: *mut opaque } fn f(s: &mut [i32], a: A) { s.append(1, a); }";
4299 4410
    let result = try resolveProgramStr(&mut a, program);
4300 -
    try expectNoErrors(&result);
4411 +
    let err = try expectError(&result);
4412 +
    let case super::ErrorKind::UnsafeOperation = err.kind
4413 +
        else throw testing::TestError::Failed;
4414 +
}
4415 +
4416 +
/// Test `.append()` rejects allocator values without the exact structural contract.
4417 +
@test fn testResolveSliceAppendInvalidAllocator() throws (testing::TestError) {
4418 +
    try expectSliceAppendAllocatorError(
4419 +
        "unsafe fn f(s: *mut [i32]) { s.append(1, 0); }"
4420 +
    );
4421 +
    try expectSliceAppendAllocatorError(
4422 +
        "unsafe fn f(s: *mut [i32]) { s.append(1, undefined); }"
4423 +
    );
4424 +
    try expectSliceAppendAllocatorError(
4425 +
        "record A { func: fn(*mut opaque, u32, u32) -> *mut opaque, ctx: *mut opaque, extra: u32 } unsafe fn f(s: *mut [i32], a: A) { s.append(1, a); }"
4426 +
    );
4427 +
    try expectSliceAppendAllocatorError(
4428 +
        "record A { ctx: *mut opaque, func: fn(*mut opaque, u32, u32) -> *mut opaque } unsafe fn f(s: *mut [i32], a: A) { s.append(1, a); }"
4429 +
    );
4430 +
    try expectSliceAppendAllocatorError(
4431 +
        "record A { func: fn(*mut opaque, u64, u32) -> *mut opaque, ctx: *mut opaque } unsafe fn f(s: *mut [i32], a: A) { s.append(1, a); }"
4432 +
    );
4433 +
    try expectSliceAppendAllocatorError(
4434 +
        "record A { func: fn(*mut opaque, u32, u32) -> *opaque, ctx: *mut opaque } unsafe fn f(s: *mut [i32], a: A) { s.append(1, a); }"
4435 +
    );
4436 +
    try expectSliceAppendAllocatorError(
4437 +
        "record A { func: fn(*mut opaque, u32, u32) -> *mut opaque, ctx: *opaque } unsafe fn f(s: *mut [i32], a: A) { s.append(1, a); }"
4438 +
    );
4439 +
    try expectSliceAppendAllocatorError(
4440 +
        "record A { func: fn(*unsafe mut opaque, u32, u32) -> *unsafe mut opaque, ctx: *unsafe mut opaque } unsafe fn f(s: *mut [i32], a: A) { s.append(1, a); }"
4441 +
    );
4301 4442
}
4302 4443
4303 4444
/// Test `.append()` with wrong element type produces an error.
4304 4445
@test fn testResolveSliceAppendWrongElemType() throws (testing::TestError) {
4305 4446
    let mut a = testResolver();
4306 -
    let program = "record A { func: fn(*mut opaque, u32, u32) -> *mut opaque, ctx: *mut opaque } fn f(s: *mut [i32], a: A) { s.append(true, a); }";
4447 +
    let program = "record A { func: fn(*mut opaque, u32, u32) -> *mut opaque, ctx: *mut opaque } unsafe fn f(s: *mut [i32], a: A) { s.append(true, a); }";
4307 4448
    let result = try resolveProgramStr(&mut a, program);
4308 4449
    let err = try expectError(&result);
4309 4450
    let case super::ErrorKind::TypeMismatch(_) = err.kind
4310 4451
        else throw testing::TestError::Failed;
4311 4452
}
4347 4488
}
4348 4489
4349 4490
/// Test `.delete()` with correct arguments succeeds.
4350 4491
@test fn testResolveSliceDeleteCorrect() throws (testing::TestError) {
4351 4492
    let mut a = testResolver();
4352 -
    let program = "fn f(s: *mut [i32]) { s.delete(0); }";
4493 +
    let program = "unsafe fn f(s: &mut [i32]) { s.delete(0); }";
4353 4494
    let result = try resolveProgramStr(&mut a, program);
4354 4495
    try expectNoErrors(&result);
4355 4496
}
4356 4497
4357 4498
/// Test `.delete()` with wrong argument type produces an error.
4362 4503
    let err = try expectError(&result);
4363 4504
    let case super::ErrorKind::TypeMismatch(_) = err.kind
4364 4505
        else throw testing::TestError::Failed;
4365 4506
}
4366 4507
4508 +
/// Safe slice delete borrows its receiver for the call and leaves it available.
4509 +
@test fn testLinearSliceDeletePreservesReceiver() throws (testing::TestError) {
4510 +
    let program = "fn consumeSlice(slice: *mut [u32]) { consumeSlice(slice); } fn run(slice: *mut [u32]) { slice.delete(0); consumeSlice(slice); }";
4511 +
    try expectAnalyzeOk(program);
4512 +
}
4513 +
4514 +
/// Slice append consumes its receiver, element, and allocator before rebinding.
4515 +
@test fn testLinearSliceAppendInUnsafeBlock() throws (testing::TestError) {
4516 +
    let program = "record Token: Linear {} record Allocator { func: fn(*mut opaque, u32, u32) -> *mut opaque, ctx: *mut opaque } fn consumeSlice(slice: *mut [Token]) { consumeSlice(slice); } fn run(slice: *mut [Token], token: Token, allocator: Allocator) { unsafe { set slice = slice.append(token, allocator); } consumeSlice(slice); }";
4517 +
    try expectAnalyzeOk(program);
4518 +
}
4519 +
4367 4520
/// Test `match &opt` produces immutable pointer bindings.
4368 4521
@test fn testResolveMatchRefUnionBinding() throws (testing::TestError) {
4369 4522
    let mut a = testResolver();
4370 4523
    let program = "union Opt { Some(i32), None } fn f() { let opt = Opt::Some(42); match &opt { case Opt::Some(x) => { *x; } else => {} } }";
4371 4524
    let result = try resolveProgramStr(&mut a, program);
4480 4633
}
4481 4634
4482 4635
/// A mutable slice pointer should be assignable to an immutable slice pointer.
4483 4636
@test fn testResolveMutSliceAssignableToImmutSlice() throws (testing::TestError) {
4484 4637
    let mut a = testResolver();
4485 -
    let result = try resolveBlockStr(&mut a, "let mut arr: [i32; 3] = [1, 2, 3]; let p: *mut [i32] = &mut arr[..]; let q: *[i32] = p;");
4638 +
    let result = try resolveProgramStr(&mut a, "fn f(slice: *mut [i32]) -> *[i32] { return slice; }");
4486 4639
    try expectNoErrors(&result);
4487 4640
}
4488 4641
4489 -
/// Comprehensive tests for `as` cast expressions.
4642 +
/// Safe `as` casts preserve pointer representation or discard mutability.
4490 4643
@test fn testResolveAsCasts() throws (testing::TestError) {
4491 4644
    { // Pointer to numeric.
4492 4645
        let mut a = testResolver();
4493 -
        let result = try resolveBlockStr(&mut a, "let x: i32 = 0; let p = &x; p as u32;");
4646 +
        let result = try resolveProgramStr(&mut a, "fn f(p: *i32) { p as u64; }");
4494 4647
        try expectNoErrors(&result);
4495 4648
    } { // Function pointer to numeric.
4496 4649
        let mut a = testResolver();
4497 -
        let result = try resolveBlockStr(&mut a, "let f: fn() = undefined; f as u32;");
4650 +
        let result = try resolveProgramStr(&mut a, "fn target() {} fn f() { target as u32; }");
4498 4651
        try expectNoErrors(&result);
4499 -
    } { // *u8 to *i32 (u8 to i32 is valid).
4652 +
    } { // Identity cast: *mut [i32] to *mut [i32].
4500 4653
        let mut a = testResolver();
4501 -
        let result = try resolveBlockStr(&mut a, "let p: *u8 = undefined; p as *i32;");
4654 +
        let result = try resolveProgramStr(&mut a, "fn f(s: *mut [i32]) -> *mut [i32] { return s as *mut [i32]; }");
4502 4655
        try expectNoErrors(&result);
4503 -
    } { // **u8 to **i32 (*u8 to *i32 is valid).
4656 +
    } { // Identity cast: *i32 to *i32.
4504 4657
        let mut a = testResolver();
4505 -
        let result = try resolveBlockStr(&mut a, "let p: **u8 = undefined; p as **i32;");
4658 +
        let result = try resolveProgramStr(&mut a, "fn f(p: *i32) -> *i32 { return p as *i32; }");
4506 4659
        try expectNoErrors(&result);
4507 -
    }
4508 -
4509 -
    { // *[i32] to *[opaque].
4660 +
    } { // Identity cast: i32 to i32.
4510 4661
        let mut a = testResolver();
4511 -
        let result = try resolveBlockStr(&mut a, "let s: *[i32] = undefined; s as *[opaque];");
4662 +
        let result = try resolveBlockStr(&mut a, "let x: i32 = 0; x as i32;");
4512 4663
        try expectNoErrors(&result);
4513 -
    } { // *[opaque] to *[i32].
4664 +
    } { // Mutable pointer to immutable pointer.
4514 4665
        let mut a = testResolver();
4515 -
        let result = try resolveBlockStr(&mut a, "let s: *[opaque] = undefined; s as *[i32];");
4666 +
        let result = try resolveProgramStr(&mut a, "fn f(p: *mut i32) -> *i32 { return p as *i32; }");
4667 +
        try expectNoErrors(&result);
4668 +
    } { // Mutable slice to immutable slice.
4669 +
        let mut a = testResolver();
4670 +
        let result = try resolveProgramStr(&mut a, "fn f(s: *mut [i32]) -> *[i32] { return s as *[i32]; }");
4516 4671
        try expectNoErrors(&result);
4517 4672
    }
4673 +
}
4518 4674
4519 -
    { // *[i32] to *[u8].
4675 +
/// Representation-changing pointer and slice casts require an unsafe context.
4676 +
@test fn testResolveAsCastsRequireUnsafe() throws (testing::TestError) {
4677 +
    { // Pointer pointee representation.
4520 4678
        let mut a = testResolver();
4521 -
        let result = try resolveBlockStr(&mut a, "let s: *[i32] = undefined; s as *[u8];");
4522 -
        try expectNoErrors(&result);
4523 -
    } { // *[record] to *[u8].
4679 +
        let result = try resolveProgramStr(&mut a, "fn f(p: *u8) -> *i32 { return p as *i32; }");
4680 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
4681 +
    } { // Nested pointer pointee representation.
4524 4682
        let mut a = testResolver();
4525 -
        let result = try resolveProgramStr(&mut a, "record R { x: i32 } fn f(s: *[R]) { s as *[u8]; }");
4526 -
        try expectNoErrors(&result);
4683 +
        let result = try resolveProgramStr(&mut a, "fn f(p: **u8) -> **i32 { return p as **i32; }");
4684 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
4685 +
    } { // Concrete pointer to opaque pointer.
4686 +
        let mut a = testResolver();
4687 +
        let result = try resolveProgramStr(&mut a, "fn f(p: *i32) -> *opaque { return p as *opaque; }");
4688 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
4689 +
    } { // Opaque pointer to concrete pointer.
4690 +
        let mut a = testResolver();
4691 +
        let result = try resolveProgramStr(&mut a, "fn f(p: *opaque) -> *i32 { return p as *i32; }");
4692 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
4693 +
    } { // Concrete slice to opaque slice.
4694 +
        let mut a = testResolver();
4695 +
        let result = try resolveProgramStr(&mut a, "fn f(s: *[i32]) -> *[opaque] { return s as *[opaque]; }");
4696 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
4697 +
    } { // Opaque slice to concrete slice.
4698 +
        let mut a = testResolver();
4699 +
        let result = try resolveProgramStr(&mut a, "fn f(s: *[opaque]) -> *[i32] { return s as *[i32]; }");
4700 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
4701 +
    } { // Integer slice to byte slice.
4702 +
        let mut a = testResolver();
4703 +
        let result = try resolveProgramStr(&mut a, "fn f(s: *[i32]) -> *[u8] { return s as *[u8]; }");
4704 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
4705 +
    } { // Byte slice to integer slice.
4706 +
        let mut a = testResolver();
4707 +
        let result = try resolveProgramStr(&mut a, "fn f(s: *[u8]) -> *[i32] { return s as *[i32]; }");
4708 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
4709 +
    } { // Record slice to byte slice.
4710 +
        let mut a = testResolver();
4711 +
        let result = try resolveProgramStr(&mut a, "record R { x: i32 } fn f(s: *[R]) -> *[u8] { return s as *[u8]; }");
4712 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
4713 +
    } { // Nested slice element representation.
4714 +
        let mut a = testResolver();
4715 +
        let result = try resolveProgramStr(&mut a, "fn f(s: *[*unsafe u8]) -> *[*unsafe i32] { return s as *[*unsafe i32]; }");
4716 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
4717 +
    } { // Record pointer representation.
4718 +
        let mut a = testResolver();
4719 +
        let result = try resolveProgramStr(&mut a, "record R { x: i32 } fn f(p: *R) -> *i32 { return p as *i32; }");
4720 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
4721 +
    } { // Record slice element representation.
4722 +
        let mut a = testResolver();
4723 +
        let result = try resolveProgramStr(&mut a, "record R { x: i32 } fn f(s: *[R]) -> *[i32] { return s as *[i32]; }");
4724 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
4527 4725
    }
4726 +
}
4528 4727
4529 -
    { // *[u8] to *[i32].
4728 +
/// Unsafe blocks may reinterpret pointers and slices without changing their class.
4729 +
@test fn testResolveAsCastsInUnsafeBlock() throws (testing::TestError) {
4730 +
    { // Pointer pointee representation.
4530 4731
        let mut a = testResolver();
4531 -
        let result = try resolveBlockStr(&mut a, "let s: *[u8] = undefined; s as *[i32];");
4732 +
        let result = try resolveProgramStr(&mut a, "fn f(p: *u8) -> *i32 { unsafe { return p as *i32; } }");
4532 4733
        try expectNoErrors(&result);
4533 -
    } { // *[*u8] to *[*i32]
4734 +
    } { // Nested pointer pointee representation.
4534 4735
        let mut a = testResolver();
4535 -
        let result = try resolveBlockStr(&mut a, "let s: *[*u8] = undefined; s as *[*i32];");
4736 +
        let result = try resolveProgramStr(&mut a, "fn f(p: **u8) -> **i32 { unsafe { return p as **i32; } }");
4536 4737
        try expectNoErrors(&result);
4537 -
    }
4538 -
4539 -
    { // Identity cast: *mut [i32] to *mut [i32].
4738 +
    } { // Concrete pointer to opaque pointer.
4540 4739
        let mut a = testResolver();
4541 -
        let result = try resolveBlockStr(&mut a, "let s: *mut [i32] = undefined; s as *mut [i32];");
4740 +
        let result = try resolveProgramStr(&mut a, "fn f(p: *i32) -> *opaque { unsafe { return p as *opaque; } }");
4542 4741
        try expectNoErrors(&result);
4543 -
    } { // Identity cast: *i32 to *i32.
4742 +
    } { // Opaque pointer to concrete pointer.
4544 4743
        let mut a = testResolver();
4545 -
        let result = try resolveBlockStr(&mut a, "let p: *i32 = undefined; p as *i32;");
4744 +
        let result = try resolveProgramStr(&mut a, "fn f(p: *opaque) -> *i32 { unsafe { return p as *i32; } }");
4546 4745
        try expectNoErrors(&result);
4547 -
    } { // Identity cast: i32 to i32.
4746 +
    } { // Concrete slice to opaque slice.
4548 4747
        let mut a = testResolver();
4549 -
        let result = try resolveBlockStr(&mut a, "let x: i32 = 0; x as i32;");
4748 +
        let result = try resolveProgramStr(&mut a, "fn f(s: *[i32]) -> *[opaque] { unsafe { return s as *[opaque]; } }");
4749 +
        try expectNoErrors(&result);
4750 +
    } { // Opaque slice to concrete slice.
4751 +
        let mut a = testResolver();
4752 +
        let result = try resolveProgramStr(&mut a, "fn f(s: *[opaque]) -> *[i32] { unsafe { return s as *[i32]; } }");
4753 +
        try expectNoErrors(&result);
4754 +
    } { // Integer slice to byte slice.
4755 +
        let mut a = testResolver();
4756 +
        let result = try resolveProgramStr(&mut a, "fn f(s: *[i32]) -> *[u8] { unsafe { return s as *[u8]; } }");
4757 +
        try expectNoErrors(&result);
4758 +
    } { // Byte slice to integer slice.
4759 +
        let mut a = testResolver();
4760 +
        let result = try resolveProgramStr(&mut a, "fn f(s: *[u8]) -> *[i32] { unsafe { return s as *[i32]; } }");
4761 +
        try expectNoErrors(&result);
4762 +
    } { // Record slice to byte slice.
4763 +
        let mut a = testResolver();
4764 +
        let result = try resolveProgramStr(&mut a, "record R { x: i32 } fn f(s: *[R]) -> *[u8] { unsafe { return s as *[u8]; } }");
4765 +
        try expectNoErrors(&result);
4766 +
    } { // Nested slice element representation.
4767 +
        let mut a = testResolver();
4768 +
        let result = try resolveProgramStr(&mut a, "fn f(s: *[*unsafe u8]) -> *[*unsafe i32] { unsafe { return s as *[*unsafe i32]; } }");
4769 +
        try expectNoErrors(&result);
4770 +
    } { // Record pointer representation.
4771 +
        let mut a = testResolver();
4772 +
        let result = try resolveProgramStr(&mut a, "record R { x: i32 } fn f(p: *R) -> *i32 { unsafe { return p as *i32; } }");
4773 +
        try expectNoErrors(&result);
4774 +
    } { // Record slice element representation.
4775 +
        let mut a = testResolver();
4776 +
        let result = try resolveProgramStr(&mut a, "record R { x: i32 } fn f(s: *[R]) -> *[i32] { unsafe { return s as *[i32]; } }");
4550 4777
        try expectNoErrors(&result);
4551 4778
    }
4552 4779
}
4553 4780
4554 -
/// Tests for invalid `as` casts that should be rejected.
4781 +
/// Tests for invalid `as` casts that should be rejected even in unsafe code.
4555 4782
@test fn testResolveAsCastsInvalid() throws (testing::TestError) {
4556 4783
    { // Pointer to slice is invalid.
4557 4784
        let mut a = testResolver();
4558 -
        let result = try resolveBlockStr(&mut a, "let p: *i32 = undefined; p as *[i32];");
4785 +
        let result = try resolveBlockStr(&mut a, "unsafe { let p: *i32 = undefined; p as *[i32]; }");
4559 4786
        let err = try expectError(&result);
4560 4787
        let case super::ErrorKind::InvalidAsCast(_) = err.kind
4561 4788
            else throw testing::TestError::Failed;
4562 4789
    } { // Slice to pointer is invalid.
4563 4790
        let mut a = testResolver();
4564 -
        let result = try resolveBlockStr(&mut a, "let s: *[i32] = undefined; s as *i32;");
4791 +
        let result = try resolveBlockStr(&mut a, "unsafe { let s: *[i32] = undefined; s as *i32; }");
4565 4792
        let err = try expectError(&result);
4566 4793
        let case super::ErrorKind::InvalidAsCast(_) = err.kind
4567 4794
            else throw testing::TestError::Failed;
4568 -
    } { // *T to *i32 is invalid.
4795 +
    } { // Slice to numeric is invalid.
4569 4796
        let mut a = testResolver();
4570 -
        let result = try resolveProgramStr(&mut a, "record R { x: i32 } fn f(p: *R) { p as *i32; }");
4797 +
        let result = try resolveBlockStr(&mut a, "unsafe { let s: *[i32] = undefined; s as u32; }");
4571 4798
        let err = try expectError(&result);
4572 4799
        let case super::ErrorKind::InvalidAsCast(_) = err.kind
4573 4800
            else throw testing::TestError::Failed;
4574 -
    } { // *[T] to *[i32] is invalid.
4801 +
    } { // Integer to pointer is invalid.
4575 4802
        let mut a = testResolver();
4576 -
        let result = try resolveProgramStr(&mut a, "record R { x: i32 } fn f(s: *[R]) { s as *[i32]; }");
4803 +
        let result = try resolveBlockStr(&mut a, "let address: u64 = 0; unsafe { address as *u8; }");
4577 4804
        let err = try expectError(&result);
4578 4805
        let case super::ErrorKind::InvalidAsCast(_) = err.kind
4579 4806
            else throw testing::TestError::Failed;
4580 -
    } { // Slice to numeric is invalid.
4807 +
    } { // Pointer cast cannot add mutability.
4581 4808
        let mut a = testResolver();
4582 -
        let result = try resolveBlockStr(&mut a, "let s: *[i32] = undefined; s as u32;");
4809 +
        let result = try resolveBlockStr(&mut a, "unsafe { let p: *i32 = undefined; p as *mut i32; }");
4583 4810
        let err = try expectError(&result);
4584 4811
        let case super::ErrorKind::InvalidAsCast(_) = err.kind
4585 4812
            else throw testing::TestError::Failed;
4586 -
    } { // *record to *u8 is invalid.
4813 +
    } { // Slice cast cannot add mutability.
4587 4814
        let mut a = testResolver();
4588 -
        let result = try resolveProgramStr(&mut a, "record R { x: i32 } fn f(p: *R) { p as *u8; }");
4815 +
        let result = try resolveBlockStr(&mut a, "unsafe { let s: *[i32] = undefined; s as *mut [i32]; }");
4589 4816
        let err = try expectError(&result);
4590 4817
        let case super::ErrorKind::InvalidAsCast(_) = err.kind
4591 4818
            else throw testing::TestError::Failed;
4592 4819
    }
4593 4820
}
4815 5042
        else throw testing::TestError::Failed;
4816 5043
}
4817 5044
4818 5045
@test fn testResolveTraitMethodThrowsRequireTry() throws (testing::TestError) {
4819 5046
    let mut a = testResolver();
4820 -
    let program = "union Error { Fail } record Counter { value: i32 } trait Adder { fn (*mut Adder) add(n: i32) -> i32 throws (Error); } instance Adder for Counter { fn (c: *mut Counter) add(n: i32) -> i32 throws (Error) { throw Error::Fail; return n; } } fn caller(a: *mut opaque Adder) -> i32 { return a.add(1); }";
5047 +
    let program = "union Error { Fail } record Counter { value: i32 } trait Adder { fn (&mut Adder) add(n: i32) -> i32 throws (Error); } instance Adder for Counter { fn (c: &mut Counter) add(n: i32) -> i32 throws (Error) { throw Error::Fail; return n; } } fn caller(a: &mut opaque Adder) -> i32 { return a.add(1); }";
4821 5048
    let result = try resolveProgramStr(&mut a, program);
4822 5049
    try expectErrorKind(&result, super::ErrorKind::MissingTry);
4823 5050
}
4824 5051
4825 5052
/// Trait declares immutable receiver (*Trait) but instance uses mutable (*mut Type).
4873 5100
@test fn testResolveTraitCrossModuleCoercion() throws (testing::TestError) {
4874 5101
    let mut a = testResolver();
4875 5102
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
4876 5103
4877 5104
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod defs; mod app;", &mut arena);
4878 -
    let defsId = try registerModule(&mut MODULE_GRAPH, rootId, "defs", "export record Counter { value: i32 } export trait Adder { fn (*mut Adder) add(n: i32) -> i32; } instance Adder for Counter { fn (c: *mut Counter) add(n: i32) -> i32 { set c.value = c.value + n; return c.value; } }", &mut arena);
4879 -
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::defs; fn test() -> i32 { let mut c = defs::Counter { value: 10 }; let a: *mut opaque defs::Adder = &mut c; return a.add(5); }", &mut arena);
5105 +
    let defsId = try registerModule(&mut MODULE_GRAPH, rootId, "defs", "export record Counter { value: i32 } export trait Adder { fn (&mut Adder) add(n: i32) -> i32; } instance Adder for Counter { fn (c: &mut Counter) add(n: i32) -> i32 { set c.value = c.value + n; return c.value; } }", &mut arena);
5106 +
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::defs; fn dispatch(a: &mut opaque defs::Adder) -> i32 { return a.add(5); } fn test() -> i32 { let mut c = defs::Counter { value: 10 }; return dispatch(&mut c); }", &mut arena);
4880 5107
4881 5108
    let result = try resolveModuleTree(&mut a, rootId);
4882 5109
    try expectNoErrors(&result);
4883 5110
}
4884 5111
4886 5113
@test fn testResolveInstanceCrossModule() throws (testing::TestError) {
4887 5114
    let mut a = testResolver();
4888 5115
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
4889 5116
4890 5117
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod defs; export mod impls; mod app;", &mut arena);
4891 -
    let defsId = try registerModule(&mut MODULE_GRAPH, rootId, "defs", "export record Counter { value: i32 } export trait Adder { fn (*mut Adder) add(n: i32) -> i32; }", &mut arena);
4892 -
    let implsId = try registerModule(&mut MODULE_GRAPH, rootId, "impls", "use root::defs; instance defs::Adder for defs::Counter { fn (c: *mut defs::Counter) add(n: i32) -> i32 { set c.value = c.value + n; return c.value; } }", &mut arena);
4893 -
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::defs; fn test() -> i32 { let mut c = defs::Counter { value: 10 }; let a: *mut opaque defs::Adder = &mut c; return a.add(5); }", &mut arena);
5118 +
    let defsId = try registerModule(&mut MODULE_GRAPH, rootId, "defs", "export record Counter { value: i32 } export trait Adder { fn (&mut Adder) add(n: i32) -> i32; }", &mut arena);
5119 +
    let implsId = try registerModule(&mut MODULE_GRAPH, rootId, "impls", "use root::defs; instance defs::Adder for defs::Counter { fn (c: &mut defs::Counter) add(n: i32) -> i32 { set c.value = c.value + n; return c.value; } }", &mut arena);
5120 +
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::defs; fn dispatch(a: &mut opaque defs::Adder) -> i32 { return a.add(5); } fn test() -> i32 { let mut c = defs::Counter { value: 10 }; return dispatch(&mut c); }", &mut arena);
4894 5121
4895 5122
    let result = try resolveModuleTree(&mut a, rootId);
4896 5123
    try expectNoErrors(&result);
4897 5124
}
4898 5125
4899 5126
/// Calling a mutable-receiver trait method on an immutable trait object
4900 5127
/// must be rejected.
4901 5128
@test fn testResolveTraitMutMethodOnImmutableObject() throws (testing::TestError) {
4902 5129
    let mut a = testResolver();
4903 -
    let program = "record Counter { value: i32 } trait Adder { fn (*mut Adder) add(n: i32) -> i32; } instance Adder for Counter { fn (c: *mut Counter) add(n: i32) -> i32 { set c.value = c.value + n; return c.value; } } fn caller(a: *opaque Adder) -> i32 { return a.add(1); }";
5130 +
    let program = "record Counter { value: i32 } trait Adder { fn (&mut Adder) add(n: i32) -> i32; } instance Adder for Counter { fn (c: &mut Counter) add(n: i32) -> i32 { set c.value = c.value + n; return c.value; } } fn caller(a: &opaque Adder) -> i32 { return a.add(1); }";
4904 5131
    let result = try resolveProgramStr(&mut a, program);
4905 5132
    try expectErrorKind(&result, super::ErrorKind::ImmutableBinding);
4906 5133
}
4907 5134
4908 5135
/// Immutable methods on an immutable trait object should be accepted.
4909 5136
@test fn testResolveTraitImmutableMethodOnImmutableObject() throws (testing::TestError) {
4910 5137
    let mut a = testResolver();
4911 -
    let program = "record Counter { value: i32 } trait Reader { fn (*Reader) get() -> i32; } instance Reader for Counter { fn (c: *Counter) get() -> i32 { return c.value; } } fn caller(r: *opaque Reader) -> i32 { return r.get(); }";
5138 +
    let program = "record Counter { value: i32 } trait Reader { fn (&Reader) get() -> i32; } instance Reader for Counter { fn (c: &Counter) get() -> i32 { return c.value; } } fn caller(r: &opaque Reader) -> i32 { return r.get(); }";
4912 5139
    let result = try resolveProgramStr(&mut a, program);
4913 5140
    try expectNoErrors(&result);
4914 5141
}
4915 5142
4916 5143
/// Both mutable and immutable methods on a mutable trait object should work.
4917 5144
@test fn testResolveTraitMixedMethodsOnMutableObject() throws (testing::TestError) {
4918 5145
    let mut a = testResolver();
4919 -
    let program = "record Counter { value: i32 } trait Ops { fn (*mut Ops) inc(); fn (*Ops) get() -> i32; } instance Ops for Counter { fn (c: *mut Counter) inc() { set c.value = c.value + 1; } fn (c: *Counter) get() -> i32 { return c.value; } } fn caller(o: *mut opaque Ops) -> i32 { o.inc(); return o.get(); }";
5146 +
    let program = "record Counter { value: i32 } trait Ops { fn (&mut Ops) inc(); fn (&Ops) get() -> i32; } instance Ops for Counter { fn (c: &mut Counter) inc() { set c.value = c.value + 1; } fn (c: &Counter) get() -> i32 { return c.value; } } fn caller(o: &mut opaque Ops) -> i32 { o.inc(); return o.get(); }";
4920 5147
    let result = try resolveProgramStr(&mut a, program);
4921 5148
    try expectNoErrors(&result);
4922 5149
}
4923 5150
4924 5151
/// Instance method body type must match the trait return type.
5002 5229
}
5003 5230
5004 5231
/// Instance method correctly matches the trait's throws clause.
5005 5232
@test fn testResolveInstanceThrowsMatch() throws (testing::TestError) {
5006 5233
    let mut a = testResolver();
5007 -
    let program = "union E { Fail } record R { x: i32 } trait T { fn (*T) get() -> i32 throws (E); } instance T for R { fn (r: *R) get() -> i32 throws (E) { throw E::Fail; return r.x; } }";
5234 +
    let program = "union E { Fail } record R { x: i32 } trait T { fn (&T) get() -> i32 throws (E); } instance T for R { fn (r: &R) get() -> i32 throws (E) { throw E::Fail; return r.x; } }";
5008 5235
    let result = try resolveProgramStr(&mut a, program);
5009 5236
    try expectNoErrors(&result);
5010 5237
}
5011 5238
5012 5239
// Constant expression folding tests //////////////////////////////////////////
5089 5316
/// Test cross-module constant expression used as array size.
5090 5317
@test fn testCrossModuleConstExprArraySize() throws (testing::TestError) {
5091 5318
    let mut a = testResolver();
5092 5319
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
5093 5320
5094 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod consts; mod app;", &mut arena);
5321 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod consts; unsafe mod app;", &mut arena);
5095 5322
    let constsId = try registerModule(&mut MODULE_GRAPH, rootId, "consts", "export constant WIDTH: u32 = 8; export constant HEIGHT: u32 = 4;", &mut arena);
5096 5323
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::consts; constant TOTAL: u32 = consts::WIDTH * consts::HEIGHT; static BUF: [u8; TOTAL] = undefined;", &mut arena);
5097 5324
5098 5325
    let result = try resolveModuleTree(&mut a, rootId);
5099 5326
    try expectNoErrors(&result);
5170 5397
    let program = "record Marker: Linear {} fn bad(value: &u32) -> &u32 { return value; }";
5171 5398
    let result = try resolveProgramStr(&mut a, program);
5172 5399
    try expectErrorKind(&result, super::ErrorKind::InvalidRefPosition);
5173 5400
}
5174 5401
5402 +
/// Field observation cannot read an owner after it was moved.
5403 +
@test fn testLinearFieldObserveAfterMoveRejected() throws (testing::TestError) {
5404 +
    let program = "record Token: Linear { value: u32 } fn consume(token: Token) { consume(token); } fn run() { let token = Token { value: 1 }; consume(token); token.value; }";
5405 +
    try expectLinearUseAfterConsume(program, "token", 140);
5406 +
}
5407 +
5408 +
/// Dereferencing cannot read an owner after it was moved.
5409 +
@test fn testLinearDerefAfterMoveRejected() throws (testing::TestError) {
5410 +
    let program = "fn consume(pointer: *u32) { consume(pointer); } fn run(pointer: *u32) { consume(pointer); *pointer; }";
5411 +
    try expectLinearUseAfterConsume(program, "pointer", 91);
5412 +
}
5413 +
5414 +
/// Immutable borrowing cannot borrow an owner after it was moved.
5415 +
@test fn testLinearBorrowAfterMoveRejected() throws (testing::TestError) {
5416 +
    let program = "record Token: Linear { value: u32 } fn consume(token: Token) { consume(token); } fn inspect(token: &Token) {} fn run() { let token = Token { value: 1 }; consume(token); inspect(&token); }";
5417 +
    try expectLinearUseAfterConsume(program, "token", 178);
5418 +
}
5419 +
5420 +
/// Mutable borrowing cannot borrow an owner after it was moved.
5421 +
@test fn testLinearMutableBorrowAfterMoveRejected() throws (testing::TestError) {
5422 +
    let program = "record Token: Linear { value: u32 } fn consume(token: Token) { consume(token); } fn inspect(token: &mut Token) {} fn run() { let mut token = Token { value: 1 }; consume(token); inspect(&mut token); }";
5423 +
    try expectLinearUseAfterConsume(program, "token", 190);
5424 +
}
5425 +
5426 +
/// A non-consuming receiver cannot borrow an owner after it was moved.
5427 +
@test fn testLinearRefReceiverAfterMoveRejected() throws (testing::TestError) {
5428 +
    let program = "record Token: Linear { value: u32 } fn consume(token: Token) { consume(token); } fn (token: &Token) inspect() {} fn run() { let token = Token { value: 1 }; consume(token); token.inspect(); }";
5429 +
    try expectLinearUseAfterConsume(program, "token", 172);
5430 +
}
5431 +
5432 +
/// A branch-joined consumed owner remains unavailable afterward.
5433 +
@test fn testLinearObserveAfterBranchJoinedMoveRejected() throws (testing::TestError) {
5434 +
    let program = "record Token: Linear { value: u32 } fn consume(token: Token) { consume(token); } fn run(flag: bool) { let token = Token { value: 1 }; if flag { consume(token); } else { consume(token); } token.value; }";
5435 +
    try expectLinearUseAfterConsume(program, "token", 187);
5436 +
}
5437 +
5438 +
/// Observation before exactly one move remains valid.
5439 +
@test fn testLinearObserveBeforeMoveAllowed() throws (testing::TestError) {
5440 +
    let program = "record Token: Linear { value: u32 } fn consume(token: Token) { consume(token); } fn run() { let token = Token { value: 1 }; token.value; consume(token); }";
5441 +
    try expectAnalyzeOk(program);
5442 +
}
5443 +
5444 +
/// A short-circuit RHS cannot be the only path that consumes an owner.
5445 +
@test fn testLinearShortCircuitConsumptionRejected() throws (testing::TestError) {
5446 +
    {
5447 +
        let mut a = testResolver();
5448 +
        let program = "record Token: Linear {} fn consume(token: Token) { consume(token); } fn take(token: Token) -> bool { consume(token); return true; } fn run(flag: bool) { let token = Token {}; flag and take(token); }";
5449 +
        let result = try resolveProgramStr(&mut a, program);
5450 +
        try expectErrorKind(&result, super::ErrorKind::LinearBranchMismatch("token"));
5451 +
    } {
5452 +
        let mut a = testResolver();
5453 +
        let program = "record Token: Linear {} fn consume(token: Token) { consume(token); } fn take(token: Token) -> bool { consume(token); return true; } fn run(flag: bool) { let token = Token {}; flag or take(token); }";
5454 +
        let result = try resolveProgramStr(&mut a, program);
5455 +
        try expectErrorKind(&result, super::ErrorKind::LinearBranchMismatch("token"));
5456 +
    }
5457 +
}
5458 +
5459 +
/// Short-circuit branches with identical owner states remain valid.
5460 +
@test fn testLinearShortCircuitIdenticalStatesAllowed() throws (testing::TestError) {
5461 +
    try expectAnalyzeOk(
5462 +
        "record Token: Linear {} fn consume(token: Token) { consume(token); } fn run(flag: bool) { let token = Token {}; flag and true; consume(token); }"
5463 +
    );
5464 +
    try expectAnalyzeOk(
5465 +
        "record Token: Linear {} fn consume(token: Token) { consume(token); } fn run(flag: bool) { let token = Token {}; flag or false; consume(token); }"
5466 +
    );
5467 +
}
5468 +
5469 +
/// A propagated error exit must not leave an owner available.
5470 +
@test fn testLinearTryPropagationChecksErrorExit() throws (testing::TestError) {
5471 +
    let mut a = testResolver();
5472 +
    let program = "union Failure { Failed } record Token: Linear {} fn consume(token: Token) { consume(token); } fn fail() throws (Failure) { throw Failure::Failed; } fn run(token: Token) throws (Failure) { try fail(); consume(token); }";
5473 +
    let result = try resolveProgramStr(&mut a, program);
5474 +
    try expectErrorKind(&result, super::ErrorKind::LinearNotConsumed("token"));
5475 +
}
5476 +
5477 +
/// Propagation remains valid when the call consumes every live owner.
5478 +
@test fn testLinearTryPropagationAfterConsumptionAllowed() throws (testing::TestError) {
5479 +
    let program = "union Failure { Failed } record Token: Linear {} fn consume(token: Token) { consume(token); } fn fail(token: Token) throws (Failure) { consume(token); throw Failure::Failed; } fn run(token: Token) throws (Failure) { try fail(token); }";
5480 +
    try expectAnalyzeOk(program);
5481 +
}
5482 +
5175 5483
/// Case-pattern fallbacks must terminate instead of synthesizing bindings.
5176 5484
@test fn testCaseLetElseFallbackMustTerminate() throws (testing::TestError) {
5177 5485
    let mut a = testResolver();
5178 5486
    let program = "union Value { Item(u32) } fn run(value: Value) { let case Value::Item(item) = value else value; item; }";
5179 5487
    let result = try resolveProgramStr(&mut a, program);
5216 5524
    let program = "unsafe fn dangerous() -> u32 { return 42; } fn run() -> u32 { let alias = dangerous; return alias(); }";
5217 5525
    let result = try resolveProgramStr(&mut a, program);
5218 5526
    try expectErrorKind(&result, super::ErrorKind::UnsafeCall);
5219 5527
}
5220 5528
5529 +
/// A safe function may dereference an unsafe pointer inside an unsafe block.
5530 +
@test fn testUnsafeBlockPointerOperationAllowed() throws (testing::TestError) {
5531 +
    let program = "record Marker: Linear {} fn load(pointer: *unsafe u32) { unsafe { *pointer; } } fn run(pointer: *unsafe u32) { load(pointer); }";
5532 +
    try expectAnalyzeOk(program);
5533 +
}
5534 +
5535 +
/// An unsafe block permits calls to unsafe functions.
5536 +
@test fn testUnsafeBlockCallAllowed() throws (testing::TestError) {
5537 +
    let program = "unsafe fn dangerous() {} fn run() { unsafe { dangerous(); } }";
5538 +
    try expectAnalyzeOk(program);
5539 +
}
5540 +
5541 +
/// Unsafe context ends at the closing brace of an unsafe block.
5542 +
@test fn testUnsafeBlockContextDoesNotLeak() throws (testing::TestError) {
5543 +
    let mut a = testResolver();
5544 +
    let program = "record Marker: Linear {} fn load(pointer: *unsafe u32) { unsafe { *pointer; } *pointer; }";
5545 +
    let result = try resolveProgramStr(&mut a, program);
5546 +
    try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
5547 +
}
5548 +
5549 +
/// Leaving a nested unsafe block preserves the enclosing unsafe context.
5550 +
@test fn testNestedUnsafeBlockPreservesContext() throws (testing::TestError) {
5551 +
    let program = "record Marker: Linear {} fn load(pointer: *unsafe u32) { unsafe { unsafe { *pointer; } *pointer; } }";
5552 +
    try expectAnalyzeOk(program);
5553 +
}
5554 +
5555 +
/// Linear consumption inside an unsafe block remains visible after the block.
5556 +
@test fn testUnsafeBlockPreservesLinearConsumption() throws (testing::TestError) {
5557 +
    let mut a = testResolver();
5558 +
    let program = "union Token: Linear { Value(u32) } fn consume(token: Token) { match token { case Token::Value(value) => {} } } fn run() { let token = Token::Value(1); unsafe { consume(token); } consume(token); }";
5559 +
    let result = try resolveProgramStr(&mut a, program);
5560 +
    try expectErrorKind(
5561 +
        &result,
5562 +
        super::ErrorKind::LinearUseAfterConsume("token"),
5563 +
    );
5564 +
}
5565 +
5566 +
/// Pointer arithmetic requires an unsafe context.
5567 +
@test fn testPointerArithmeticRequiresUnsafe() throws (testing::TestError) {
5568 +
    let mut a = testResolver();
5569 +
    let program = "fn advance(pointer: *u32) -> *u32 { return pointer + 1; }";
5570 +
    let result = try resolveProgramStr(&mut a, program);
5571 +
    try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
5572 +
}
5573 +
5574 +
/// An unsafe block permits pointer arithmetic.
5575 +
@test fn testPointerArithmeticAllowedInUnsafeBlock() throws (testing::TestError) {
5576 +
    let program = "fn advance(pointer: *u32) -> *u32 { unsafe { return pointer + 1; } }";
5577 +
    try expectAnalyzeOk(program);
5578 +
}
5579 +
5580 +
/// Unchecked slice construction requires an unsafe context.
5581 +
@test fn testSliceOfRequiresUnsafe() throws (testing::TestError) {
5582 +
    let mut a = testResolver();
5583 +
    let program = "fn make(pointer: *u32) -> *[u32] { return @sliceOf(pointer, 1); }";
5584 +
    let result = try resolveProgramStr(&mut a, program);
5585 +
    try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
5586 +
}
5587 +
5588 +
/// An unsafe block permits unchecked slice construction.
5589 +
@test fn testSliceOfAllowedInUnsafeBlock() throws (testing::TestError) {
5590 +
    let program = "fn make(pointer: *u32) -> *[u32] { unsafe { return @sliceOf(pointer, 1); } }";
5591 +
    try expectAnalyzeOk(program);
5592 +
}
5593 +
5594 +
/// Undefined source values require an unsafe context.
5595 +
@test fn testUndefinedRequiresUnsafe() throws (testing::TestError) {
5596 +
    let mut a = testResolver();
5597 +
    let program = "fn value() -> u32 { return undefined; }";
5598 +
    let result = try resolveProgramStr(&mut a, program);
5599 +
    try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
5600 +
}
5601 +
5602 +
/// An unsafe block permits an undefined source value.
5603 +
@test fn testUndefinedAllowedInUnsafeBlock() throws (testing::TestError) {
5604 +
    let program = "fn value() -> u32 { unsafe { return undefined; } }";
5605 +
    try expectAnalyzeOk(program);
5606 +
}
5607 +
5608 +
/// A safe static initializer cannot use an undefined value.
5609 +
@test fn testUndefinedStaticRequiresUnsafe() throws (testing::TestError) {
5610 +
    let mut a = testResolver();
5611 +
    let result = try resolveProgramStr(&mut a, "static VALUE: u32 = undefined;");
5612 +
    try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
5613 +
}
5614 +
5615 +
/// An unsafe static initializer may use an undefined value.
5616 +
@test fn testUndefinedAllowedInUnsafeStatic() throws (testing::TestError) {
5617 +
    try expectAnalyzeOk("unsafe static VALUE: u32 = undefined;");
5618 +
}
5619 +
5221 5620
/// References cannot be embedded in aggregate fields.
5222 5621
@test fn testRefFieldRejected() throws (testing::TestError) {
5223 5622
    let mut a = testResolver();
5224 5623
    let program = "record Marker: Linear {} record Bad { value: &u32 }";
5225 5624
    let result = try resolveProgramStr(&mut a, program);
5246 5645
    let program = "record Token: Linear + Linear { value: u32 }";
5247 5646
    let result = try resolveProgramStr(&mut a, program);
5248 5647
    try expectErrorKind(&result, super::ErrorKind::DuplicateBinding("Linear"));
5249 5648
}
5250 5649
5251 -
/// A `Linear` marker does not change legacy pointer inference.
5252 -
@test fn testLinearMarkerKeepsLegacyPointerInference() throws (testing::TestError) {
5253 -
    let program = "record Marker: Linear {} fn run() { let value: u32 = 0; let pointer: *u32 = &value; pointer; }";
5254 -
    try expectAnalyzeOk(program);
5650 +
/// Address-of inference creates a loan that cannot be stored in a local.
5651 +
@test fn testAddressOfInferenceCreatesLoan() throws (testing::TestError) {
5652 +
    let mut a = testResolver();
5653 +
    let program = "fn run() { let value: u32 = 0; let pointer = &value; }";
5654 +
    let result = try resolveProgramStr(&mut a, program);
5655 +
    try expectErrorKind(&result, super::ErrorKind::RefBinding);
5656 +
}
5657 +
5658 +
/// Taking a local address cannot create an owner that escapes the function.
5659 +
@test fn testAddressOfLocalCannotCreateOwner() throws (testing::TestError) {
5660 +
    let mut a = testResolver();
5661 +
    let program = "fn bad() -> *u32 { let value: u32 = 0; return &value; }";
5662 +
    let result = try resolveProgramStr(&mut a, program);
5663 +
    let err = try expectError(&result);
5664 +
    let case super::ErrorKind::TypeMismatch(_) = err.kind
5665 +
        else throw testing::TestError::Failed;
5666 +
}
5667 +
5668 +
/// A loan cannot be converted into an owning pointer.
5669 +
@test fn testReferenceCannotBecomeOwner() throws (testing::TestError) {
5670 +
    let mut a = testResolver();
5671 +
    let program = "fn bad(value: &u32) -> *u32 { return value; }";
5672 +
    let result = try resolveProgramStr(&mut a, program);
5673 +
    let err = try expectError(&result);
5674 +
    let case super::ErrorKind::TypeMismatch(_) = err.kind
5675 +
        else throw testing::TestError::Failed;
5255 5676
}
5256 5677
5257 5678
/// References are rejected from every nested or storable type position.
5258 5679
@test fn testNestedRefPositionsRejected() throws (testing::TestError) {
5259 5680
    {
5377 5798
}
5378 5799
5379 5800
/// Recursive cast validation cannot hide a checked-to-unsafe transition.
5380 5801
@test fn testNestedUnsafePointerCastRejected() throws (testing::TestError) {
5381 5802
    let mut a = testResolver();
5382 -
    let program = "record Marker: Linear {} fn run(value: **u32) { value as **unsafe u32; }";
5803 +
    let program = "record Marker: Linear {} fn run(value: &*u32) { unsafe { value as **unsafe u32; } }";
5383 5804
    let result = try resolveProgramStr(&mut a, program);
5384 5805
    let err = try expectError(&result);
5385 5806
    let case super::ErrorKind::InvalidAsCast(_) = err.kind
5386 5807
        else throw testing::TestError::Failed;
5387 5808
}
5427 5848
    let mut a = testResolver();
5428 5849
    let program = "record Marker: Linear {} record Value { number: u32 } trait Read { unsafe fn (&Read) get() -> u32; } instance Read for Value { unsafe fn (value: &Value) get() -> u32 { return value.number; } } fn inspect(object: &opaque Read) -> u32 { return object.get(); }";
5429 5850
    let result = try resolveProgramStr(&mut a, program);
5430 5851
    try expectErrorKind(&result, super::ErrorKind::UnsafeCall);
5431 5852
}
5853 +
5854 +
/// Module-qualified statics retain one loan root across call arguments.
5855 +
@test fn testScopedStaticLoanConflicts() throws (testing::TestError) {
5856 +
    {
5857 +
        let mut a = testResolver();
5858 +
        let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
5859 +
        let rootId = try registerModule(&mut MODULE_GRAPH, nil, "loan_mut_root", "export mod values; mod app;", &mut arena);
5860 +
        let valuesId = try registerModule(&mut MODULE_GRAPH, rootId, "values", "export static X: u32 = 0;", &mut arena);
5861 +
        let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use loan_mut_root::values; fn both(left: &mut u32, right: &mut u32) {} fn run() { both(&mut values::X, &mut values::X); }", &mut arena);
5862 +
        let result = try resolveModuleTree(&mut a, rootId);
5863 +
        try expectErrorKind(&result, super::ErrorKind::BorrowConflict("X"));
5864 +
    } {
5865 +
        let mut a = testResolver();
5866 +
        let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
5867 +
        let rootId = try registerModule(&mut MODULE_GRAPH, nil, "loan_shared_root", "export mod values; mod app;", &mut arena);
5868 +
        let valuesId = try registerModule(&mut MODULE_GRAPH, rootId, "values", "export static X: u32 = 0;", &mut arena);
5869 +
        let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use loan_shared_root::values; fn both(left: &u32, right: &mut u32) {} fn run() { both(&values::X, &mut values::X); }", &mut arena);
5870 +
        let result = try resolveModuleTree(&mut a, rootId);
5871 +
        try expectErrorKind(&result, super::ErrorKind::BorrowConflict("X"));
5872 +
    }
5873 +
}
5874 +
5875 +
/// Distinct module-qualified statics do not overlap.
5876 +
@test fn testDistinctScopedStaticLoansAllowed() throws (testing::TestError) {
5877 +
    let mut a = testResolver();
5878 +
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
5879 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "loan_distinct_root", "export mod values; mod app;", &mut arena);
5880 +
    let valuesId = try registerModule(&mut MODULE_GRAPH, rootId, "values", "export static X: u32 = 0; export static Y: u32 = 0;", &mut arena);
5881 +
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use loan_distinct_root::values; fn both(left: &mut u32, right: &mut u32) {} fn run() { both(&mut values::X, &mut values::Y); }", &mut arena);
5882 +
    let result = try resolveModuleTree(&mut a, rootId);
5883 +
    try expectNoErrors(&result);
5884 +
}
5885 +
5886 +
/// Earlier argument loans remain active inside later nested calls.
5887 +
@test fn testOuterArgumentLoanConflictsWithNestedCall() throws (testing::TestError) {
5888 +
    {
5889 +
        let mut a = testResolver();
5890 +
        let program = "fn observe(value: &u32) -> u32 { return *value; } fn pair(first: &mut u32, second: u32) {} fn run() { let mut value: u32 = 0; pair(&mut value, observe(&value)); }";
5891 +
        let result = try resolveProgramStr(&mut a, program);
5892 +
        try expectErrorKind(&result, super::ErrorKind::BorrowConflict("value"));
5893 +
    } {
5894 +
        let mut a = testResolver();
5895 +
        let program = "record Token: Linear { value: u32 } fn consume(token: Token) -> u32 { return consume(token); } fn pair(first: &Token, second: u32) {} fn run() { let token = Token { value: 0 }; pair(&token, consume(token)); }";
5896 +
        let result = try resolveProgramStr(&mut a, program);
5897 +
        try expectErrorKind(&result, super::ErrorKind::BorrowConflict("token"));
5898 +
    } {
5899 +
        let mut a = testResolver();
5900 +
        let program = "fn update(value: &mut u32) -> u32 { set *value = 1; return *value; } fn pair(first: &u32, second: u32) {} fn run() { let mut value: u32 = 0; pair(&value, update(&mut value)); }";
5901 +
        let result = try resolveProgramStr(&mut a, program);
5902 +
        try expectErrorKind(&result, super::ErrorKind::BorrowConflict("value"));
5903 +
    }
5904 +
}
5905 +
5906 +
/// Nested later arguments may use a different root.
5907 +
@test fn testNonOverlappingNestedCallArgumentsAllowed() throws (testing::TestError) {
5908 +
    let program = "fn observe(value: &u32) -> u32 { return *value; } fn pair(first: &mut u32, second: u32) {} fn run() { let mut left: u32 = 0; let right: u32 = 1; pair(&mut left, observe(&right)); }";
5909 +
    try expectAnalyzeOk(program);
5910 +
}
5911 +
5912 +
/// Linear values cannot live in reusable global declarations.
5913 +
@test fn testLinearGlobalsRejected() throws (testing::TestError) {
5914 +
    {
5915 +
        let mut a = testResolver();
5916 +
        let program = "record Token: Linear { value: u32 } static TOKEN: Token = Token { value: 1 };";
5917 +
        let result = try resolveProgramStr(&mut a, program);
5918 +
        try expectErrorKind(&result, super::ErrorKind::LinearDiscard);
5919 +
    } {
5920 +
        let mut a = testResolver();
5921 +
        let program = "record Token: Linear { value: u32 } constant TOKEN: Token = Token { value: 1 };";
5922 +
        let result = try resolveProgramStr(&mut a, program);
5923 +
        try expectErrorKind(&result, super::ErrorKind::LinearDiscard);
5924 +
    }
5925 +
    {
5926 +
        let mut a = testResolver();
5927 +
        let program = "constant TEXT: *[u8] = \"text\";";
5928 +
        let result = try resolveProgramStr(&mut a, program);
5929 +
        try expectErrorKind(&result, super::ErrorKind::LinearDiscard);
5930 +
    }
5931 +
}
5932 +
5933 +
/// Function-typed unsafe statics require unsafe access like other globals.
5934 +
@test fn testUnsafeFunctionStaticAccessRequiresUnsafe() throws (testing::TestError) {
5935 +
    let mut a = testResolver();
5936 +
    let program = "fn callback() {} unsafe static CALLBACK: fn() = callback; fn run() { CALLBACK(); }";
5937 +
    let result = try resolveProgramStr(&mut a, program);
5938 +
    try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
5939 +
}
5940 +
5941 +
/// Assignments cannot overlap an earlier shared call-scoped loan.
5942 +
@test fn testOuterArgumentLoanConflictsWithLaterAssignment() throws (testing::TestError) {
5943 +
    let mut a = testResolver();
5944 +
    let program = "union Error { Fail } fn fail() -> i32 throws (Error) { return 0; } fn pair(first: &u32, second: i32) {} fn run() -> i32 { let mut value: u32 = 0; pair(&value, try fail() catch { set value = 1; return 0; }); return 0; }";
5945 +
    let result = try resolveProgramStr(&mut a, program);
5946 +
    try expectErrorKind(&result, super::ErrorKind::BorrowConflict("value"));
5947 +
}
5948 +
5949 +
/// Ordinary scalar globals remain reusable.
5950 +
@test fn testScalarGlobalsAllowed() throws (testing::TestError) {
5951 +
    try expectAnalyzeOk("static COUNT: u32 = 0; constant LIMIT: u32 = 1;");
5952 +
}
5953 +
5954 +
/// Explicit unsafe globals may hold linear values.
5955 +
@test fn testUnsafeLinearGlobalsAllowed() throws (testing::TestError) {
5956 +
    let program = "record Token: Linear { value: u32 } unsafe static TOKEN: Token = Token { value: 1 }; unsafe constant TEXT: *[u8] = \"text\";";
5957 +
    try expectAnalyzeOk(program);
5958 +
}
5959 +
5960 +
/// Reading an unsafe global requires unsafe context.
5961 +
@test fn testUnsafeGlobalAccessRequiresUnsafe() throws (testing::TestError) {
5962 +
    let mut a = testResolver();
5963 +
    let program = "unsafe static VALUE: u32 = 0; fn read() -> u32 { return VALUE; }";
5964 +
    let result = try resolveProgramStr(&mut a, program);
5965 +
    try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
5966 +
}
5967 +
5968 +
/// Unsafe context permits reading an unsafe linear global.
5969 +
@test fn testUnsafeGlobalAccessAllowedInUnsafeBlock() throws (testing::TestError) {
5970 +
    let program = "unsafe constant TEXT: *[u8] = \"text\"; fn consume(value: *[u8]) { consume(value); } fn run() { unsafe { consume(TEXT); } }";
5971 +
    try expectAnalyzeOk(program);
5972 +
}
5973 +
5974 +
/// Exported unsafe globals retain their access requirement across modules.
5975 +
@test fn testScopedUnsafeGlobalAccess() throws (testing::TestError) {
5976 +
    {
5977 +
        let mut a = testResolver();
5978 +
        let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
5979 +
        let rootId = try registerModule(&mut MODULE_GRAPH, nil, "unsafe_global_root", "export mod values; mod app;", &mut arena);
5980 +
        let valuesId = try registerModule(&mut MODULE_GRAPH, rootId, "values", "export unsafe static VALUE: u32 = 0;", &mut arena);
5981 +
        let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use unsafe_global_root::values; fn read() -> u32 { return values::VALUE; }", &mut arena);
5982 +
        let result = try resolveModuleTree(&mut a, rootId);
5983 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
5984 +
    } {
5985 +
        let mut a = testResolver();
5986 +
        let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
5987 +
        let rootId = try registerModule(&mut MODULE_GRAPH, nil, "unsafe_global_ok_root", "export mod values; mod app;", &mut arena);
5988 +
        let valuesId = try registerModule(&mut MODULE_GRAPH, rootId, "values", "export unsafe static VALUE: u32 = 0;", &mut arena);
5989 +
        let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use unsafe_global_ok_root::values; fn read() -> u32 { unsafe { return values::VALUE; } }", &mut arena);
5990 +
        let result = try resolveModuleTree(&mut a, rootId);
5991 +
        try expectNoErrors(&result);
5992 +
    }
5993 +
}
5994 +
5995 +
/// Identity casts do not hide duplicate or conflicting loans.
5996 +
@test fn testCastWrappedLoanConflicts() throws (testing::TestError) {
5997 +
    {
5998 +
        let mut a = testResolver();
5999 +
        let program = "fn both(left: &mut u32, right: &mut u32) {} fn run() { let mut value: u32 = 0; both(&mut value as &mut u32, &mut value); }";
6000 +
        let result = try resolveProgramStr(&mut a, program);
6001 +
        try expectErrorKind(&result, super::ErrorKind::BorrowConflict("value"));
6002 +
    } {
6003 +
        let mut a = testResolver();
6004 +
        let program = "fn both(left: &u32, right: &mut u32) {} fn run() { let mut value: u32 = 0; both(&value as &u32, &mut value); }";
6005 +
        let result = try resolveProgramStr(&mut a, program);
6006 +
        try expectErrorKind(&result, super::ErrorKind::BorrowConflict("value"));
6007 +
    }
6008 +
}
lib/std/lang/scanner.rad +1 -1
115 115
    /// Corresponding token.
116 116
    tok: TokenKind,
117 117
}
118 118
119 119
/// Sorted keyword table for binary search.
120 -
constant KEYWORDS: [Keyword; 52] = [
120 +
unsafe constant KEYWORDS: [Keyword; 52] = [
121 121
    { name: "align", tok: TokenKind::Align },
122 122
    { name: "and", tok: TokenKind::And },
123 123
    { name: "as", tok: TokenKind::As },
124 124
    { name: "assert", tok: TokenKind::Assert },
125 125
    { name: "bool", tok: TokenKind::Bool },
lib/std/lang/scanner/tests.rad +1 -1
1 1
use std::lang::strings;
2 2
use std::mem;
3 3
use std::testing;
4 4
5 5
/// String pool for testing.
6 -
static TEST_STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
6 +
unsafe static TEST_STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
7 7
8 8
fn testScanner(source: *[u8]) -> super::Scanner {
9 9
    return super::scanner(super::SourceLoc::File("test.r"), source, &mut TEST_STRING_POOL);
10 10
}
11 11
lib/std/sys/unix.rad +46 -20
29 29
export constant STDERR: i64 = 2;
30 30
31 31
/// Special value representing current working directory for `openat()`.
32 32
constant AT_FDCWD: i64 = -100;
33 33
34 -
/// Opens a file at the given path and returns a file descriptor.
35 -
/// Returns a negative value on error.
36 -
export fn open(path: *[u8], flags: OpenFlags) -> i64 {
34 +
/// Open a file at the given path and return a file descriptor.
35 +
/// Return a negative value on error.
36 +
///
37 +
/// The caller must ensure `path.ptr` identifies at least `path.len + 1`
38 +
/// readable bytes, that `path.ptr[path.len]` is NUL, and that those bytes
39 +
/// remain readable until the system call returns.
40 +
export unsafe fn open(path: *[u8], flags: OpenFlags) -> i64 {
37 41
    return intrinsics::ecall(56, AT_FDCWD, path.ptr as i64, *flags, 0);
38 42
}
39 43
40 -
/// Opens a file at the given path with mode, returns a file descriptor.
41 -
export fn openOpts(path: *[u8], flags: OpenFlags, mode: i64) -> i64 {
44 +
/// Open a file at the given path with mode and return a file descriptor.
45 +
///
46 +
/// The caller must ensure `path.ptr` identifies at least `path.len + 1`
47 +
/// readable bytes, that `path.ptr[path.len]` is NUL, and that those bytes
48 +
/// remain readable until the system call returns.
49 +
export unsafe fn openOpts(path: *[u8], flags: OpenFlags, mode: i64) -> i64 {
42 50
    return intrinsics::ecall(56, AT_FDCWD, path.ptr as i64, *flags, mode);
43 51
}
44 52
45 53
/// Reads from a file descriptor into the provided buffer.
46 54
/// Returns the number of bytes read, or a negative value on error.
47 55
export fn read(fd: i64, buf: *mut [u8]) -> i64 {
48 -
    return intrinsics::ecall(63, fd, buf.ptr as i64, buf.len as i64, 0);
56 +
    let mut result: i64 = 0;
57 +
    unsafe { set result = intrinsics::ecall(63, fd, buf.ptr as i64, buf.len as i64, 0); }
58 +
    return result;
49 59
}
50 60
51 61
/// Reads from a file descriptor until EOF or buffer is full.
52 62
/// Returns the total number of bytes read, or a negative value on error.
53 63
export fn readToEnd(fd: i64, buf: *mut [u8]) -> i64 {
68 78
}
69 79
70 80
/// Writes to a file descriptor from the provided buffer.
71 81
/// Returns the number of bytes written, or a negative value on error.
72 82
export fn write(fd: i64, buf: *[u8]) -> i64 {
73 -
    return intrinsics::ecall(64, fd, buf.ptr as i64, buf.len as i64, 0);
83 +
    let mut result: i64 = 0;
84 +
    unsafe { set result = intrinsics::ecall(64, fd, buf.ptr as i64, buf.len as i64, 0); }
85 +
    return result;
74 86
}
75 87
76 88
/// Writes the entire contents of a buffer to a file descriptor.
77 89
/// Returns `false` when the descriptor cannot accept the full buffer.
78 90
export fn writeAll(fd: i64, data: *[u8]) -> bool {
88 100
}
89 101
90 102
/// Closes a file descriptor.
91 103
/// Returns `0` on success, or a negative value on error.
92 104
export fn close(fd: i64) -> i64 {
93 -
    return intrinsics::ecall(57, fd, 0, 0, 0);
105 +
    let mut result: i64 = 0;
106 +
    unsafe { set result = intrinsics::ecall(57, fd, 0, 0, 0); }
107 +
    return result;
94 108
}
95 109
96 -
/// Reads the entire contents of a file at the given path into the provided buffer.
97 -
/// Returns a slice containing the data read, or `nil` on error.
98 -
export fn readFile(path: *[u8], buf: *mut [u8]) -> ?*[u8] {
110 +
/// Read the entire contents of a file at the given path into the buffer.
111 +
/// Return a slice containing the data read, or `nil` on error.
112 +
///
113 +
/// The caller must ensure `path.ptr` identifies at least `path.len + 1`
114 +
/// readable bytes, that `path.ptr[path.len]` is NUL, and that those bytes
115 +
/// remain readable until this function returns.
116 +
export unsafe fn readFile(path: *[u8], buf: *mut [u8]) -> ?*[u8] {
99 117
    let fd = open(path, O_RDONLY);
100 118
    if fd < 0 {
101 119
        return nil;
102 120
    }
103 121
    let n = readToEnd(fd, buf);
111 129
    return &buf[..n as u32];
112 130
}
113 131
114 132
/// Exit the current process with the given status code.
115 133
export fn exit(status: i64) {
116 -
    intrinsics::ecall(93, status, 0, 0, 0);
134 +
    unsafe { intrinsics::ecall(93, status, 0, 0, 0); }
117 135
}
118 136
119 -
/// Writes the entire contents of a buffer to a file at the given path.
120 -
/// Creates the file if it doesn't exist, truncates if it does.
121 -
/// Returns `true` on success.
122 -
export fn writeFile(path: *[u8], data: *[u8]) -> bool {
137 +
/// Write the entire contents of a buffer to a file at the given path.
138 +
/// Create the file if it does not exist and truncate it if it does.
139 +
/// Return `true` on success.
140 +
///
141 +
/// The caller must ensure `path.ptr` identifies at least `path.len + 1`
142 +
/// readable bytes, that `path.ptr[path.len]` is NUL, and that those bytes
143 +
/// remain readable until this function returns.
144 +
export unsafe fn writeFile(path: *[u8], data: *[u8]) -> bool {
123 145
    return writeFileParts(path, &[data]);
124 146
}
125 147
126 -
/// Writes each buffer to a file at the given path.
127 -
/// Creates the file if it doesn't exist, truncates if it does.
128 -
/// Returns `true` only when every part and the final close succeed.
129 -
export fn writeFileParts(path: *[u8], parts: *[*[u8]]) -> bool {
148 +
/// Write each buffer to a file at the given path.
149 +
/// Create the file if it does not exist and truncate it if it does.
150 +
/// Return `true` only when every part and the final close succeed.
151 +
///
152 +
/// The caller must ensure `path.ptr` identifies at least `path.len + 1`
153 +
/// readable bytes, that `path.ptr[path.len]` is NUL, and that those bytes
154 +
/// remain readable until this function returns.
155 +
export unsafe fn writeFileParts(path: *[u8], parts: *[*[u8]]) -> bool {
130 156
    let flags = OpenFlags(*O_WRONLY | *O_CREAT | *O_TRUNC);
131 157
    let fd = openOpts(path, flags, 420); // 0644 in octal.
132 158
    if fd < 0 {
133 159
        return false;
134 160
    }
lib/std/testing.rad +1 -1
25 25
export fn test(module: *[u8], name: *[u8], func: fn() throws (TestError)) -> TestInfo {
26 26
    return TestInfo { module, name, func };
27 27
}
28 28
29 29
/// Run all tests and return `0` on success or `1` if any test failed.
30 -
export fn runAllTests(tests: *[TestInfo]) -> i32 {
30 +
export fn runAllTests(tests: &[TestInfo]) -> i32 {
31 31
    let mut ctx: Ctx = Ctx { passed: 0, failed: 0 };
32 32
33 33
    io::print("Running ");
34 34
    io::printU32(tests.len);
35 35
    io::print(" test(s)...\n\n");
lib/std/tests.rad +6 -2
246 246
247 247
@test fn testWriteFileParts() throws (testing::TestError) {
248 248
    let path = "/tmp/radiance-std-write-file-parts.test";
249 249
    let mut buffer: [u8; 16] = undefined;
250 250
251 -
    if not unix::writeFileParts(path, &["hello", " ", "world"]) {
251 +
    let mut wrote = false;
252 +
    unsafe { set wrote = unix::writeFileParts(path, &["hello", " ", "world"]); }
253 +
    if not wrote {
252 254
        throw testing::TestError::Failed;
253 255
    }
254 -
    let data = unix::readFile(path, &mut buffer[..]) else {
256 +
    let mut readResult: ?*[u8] = nil;
257 +
    unsafe { set readResult = unix::readFile(path, &mut buffer[..]); }
258 +
    let data = readResult else {
255 259
        throw testing::TestError::Failed;
256 260
    };
257 261
    try testing::expectBytesEq(data, "hello world");
258 262
}
259 263
lib/std/vec.rad +30 -5
61 61
}
62 62
63 63
/// Push an element onto the end of the vector.
64 64
///
65 65
/// Returns false if the vector is at capacity.
66 -
export fn push(vec: *mut RawVec, elem: *opaque) -> bool {
66 +
///
67 +
/// The caller must preserve the metadata invariants established by [`new`],
68 +
/// including `vec.stride > 0` and `vec.len <= capacity(vec)`. When the vector
69 +
/// has spare capacity, `elem` must point to at least `vec.stride` readable,
70 +
/// initialized bytes, and the destination range beginning at
71 +
/// `vec.len * vec.stride` in `vec.data` must be writable for `vec.stride`
72 +
/// bytes. The copy is byte-wise, so `elem` has no alignment requirement and
73 +
/// the destination need not already be initialized.
74 +
export unsafe fn push(vec: *mut RawVec, elem: *opaque) -> bool {
67 75
    if vec.len >= capacity(vec) {
68 76
        return false;
69 77
    }
70 78
    let off: u32 = vec.len * vec.stride;
71 79
    let dst: *mut u8 = &mut vec.data[off];
79 87
80 88
/// Pop an element from the end of the vector.
81 89
///
82 90
/// Copies the element into the provided output pointer.
83 91
/// Returns false if the vector is empty.
84 -
export fn pop(vec: *mut RawVec, out: *mut opaque) -> bool {
92 +
///
93 +
/// The caller must preserve the metadata invariants established by [`new`],
94 +
/// including `vec.stride > 0` and `vec.len <= capacity(vec)`. When the vector
95 +
/// is non-empty, `out` must point to at least `vec.stride` writable bytes, and
96 +
/// the source range for the last logical element in `vec.data` must contain
97 +
/// `vec.stride` initialized bytes. The copy is byte-wise, so `out` has no
98 +
/// alignment requirement and its previous contents need not be initialized.
99 +
export unsafe fn pop(vec: *mut RawVec, out: *mut opaque) -> bool {
85 100
    if vec.len == 0 {
86 101
        return false;
87 102
    }
88 103
    set vec.len -= 1;
89 104
97 112
}
98 113
99 114
/// Set the element at the given index.
100 115
///
101 116
/// Returns false if index is out of bounds.
102 -
export fn put(vec: *mut RawVec, index: u32, elem: *opaque) -> bool {
117 +
///
118 +
/// The caller must preserve the metadata invariants established by [`new`],
119 +
/// including `vec.stride > 0` and `vec.len <= capacity(vec)`. When `index` is
120 +
/// in bounds, `elem` must point to at least `vec.stride` readable, initialized
121 +
/// bytes, and the destination range beginning at `index * vec.stride` in
122 +
/// `vec.data` must be writable for `vec.stride` bytes. The copy is byte-wise,
123 +
/// so `elem` has no alignment requirement.
124 +
export unsafe fn put(vec: *mut RawVec, index: u32, elem: *opaque) -> bool {
103 125
    if index >= vec.len {
104 126
        return false;
105 127
    }
106 128
    let off: u32 = index * vec.stride;
107 129
    let dst: *mut u8 = &mut vec.data[off];
110 132
    copyBytes(dst, src, vec.stride);
111 133
112 134
    return true;
113 135
}
114 136
115 -
/// Copy bytes from source to destination.
116 -
fn copyBytes(dst: *mut u8, src: *u8, count: u32) {
137 +
/// Copy `count` initialized bytes from `src` into writable storage at `dst`.
138 +
///
139 +
/// The caller must ensure both pointers are valid for `count` bytes. No
140 +
/// alignment is required because the copy operates one byte at a time.
141 +
unsafe fn copyBytes(dst: *mut u8, src: *u8, count: u32) {
117 142
    for i in 0..count {
118 143
        set *(dst + i) = *(src + i);
119 144
    }
120 145
}
seed/radiance.rv64 +0 -0

Binary file changed.

seed/radiance.rv64.git +1 -1
1 -
d024ed30c27cc671d9371988d0155b3e08ff004582cf59f1b7eac8ffdf3e3467
1 +
1e2fdea952350036a4172f567442e608555094b9e18fa69f1fb615453d5352b6
seed/update +2 -1
39 39
# ---------------------------------------------------------------------------
40 40
# Command line flags for Radiance compiler
41 41
# ---------------------------------------------------------------------------
42 42
43 43
STD_MODS="$(sed 's/^/-mod /' std.lib | tr '\n' ' ')"
44 -
OPTS="-pkg std ${STD_MODS} -pkg radiance -mod compiler/radiance.rad -entry radiance"
44 +
OPTS="-pkg std ${STD_MODS} -pkg radiance -mod compiler/radiance.rad"
45 +
OPTS="${OPTS} -mod compiler/radiance/codegenSink.rad -entry radiance"
45 46
46 47
# ---------------------------------------------------------------------------
47 48
# Emulator settings
48 49
# ---------------------------------------------------------------------------
49 50
std.lib.test +1 -0
3 3
lib/std/char/tests.rad
4 4
lib/std/arch/rv64/tests.rad
5 5
lib/std/arch/rv64/asm/tests.rad
6 6
lib/std/arch/rv64/asm/scanner/tests.rad
7 7
lib/std/lang/alloc/tests.rad
8 +
lib/std/lang/il/tests.rad
8 9
lib/std/lang/parser/tests.rad
9 10
lib/std/lang/module/tests.rad
10 11
lib/std/lang/scanner/tests.rad
11 12
lib/std/lang/resolver/tests.rad
12 13
lib/std/lang/gen/bitset/tests.rad
test/runner.rad +38 -27
28 28
/// Arena size for AST/IL allocations (512 KB).
29 29
constant ARENA_SIZE: u32 = 524288;
30 30
/// Maximum path length for expected IL file path.
31 31
constant MAX_PATH_LEN: u32 = 256;
32 32
/// Source file extension for binary tests.
33 -
constant SOURCE_EXT: *[u8] = ".rad";
33 +
unsafe constant SOURCE_EXT: *[u8] = ".rad";
34 34
/// IL snapshot file extension for binary tests.
35 -
constant SNAPSHOT_EXT: *[u8] = ".ril";
35 +
unsafe constant SNAPSHOT_EXT: *[u8] = ".ril";
36 36
37 37
/// String pool.
38 -
static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
38 +
unsafe static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
39 39
40 40
/// Maximum number of AST nodes per test file.
41 41
constant MAX_NODE_DATA: u32 = 4096;
42 42
/// Maximum number of resolver errors per test file.
43 43
constant MAX_ERRORS: u32 = 16;
46 46
/// Maximum number of data bytes in a `.ras` test binary.
47 47
constant ASM_DATA_CAPACITY: u32 = 1024;
48 48
49 49
// Static storage for large buffers to avoid stack overflow.
50 50
// Tests run serially so sharing these is safe.
51 -
static SOURCE_BUF: [u8; SOURCE_BUF_SIZE] = undefined;
52 -
static EXPECTED_BUF: [u8; EXPECTED_BUF_SIZE] = undefined;
53 -
static OUTPUT_BUF: [u8; OUTPUT_BUF_SIZE] = undefined;
54 -
static AST_ARENA_STORAGE: [u8; ARENA_SIZE] = undefined;
55 -
static IL_ARENA_STORAGE: [u8; ARENA_SIZE] = undefined;
56 -
static PRINT_ARENA_STORAGE: [u8; ARENA_SIZE] = undefined;
57 -
static RESOLVER_ARENA_STORAGE: [u8; ARENA_SIZE] = undefined;
58 -
static NODE_DATA_STORAGE: [resolver::NodeData; MAX_NODE_DATA] = undefined;
59 -
static ERROR_STORAGE: [resolver::Error; MAX_ERRORS] = undefined;
60 -
static ASM_TEXT_STORAGE: [u32; ASM_TEXT_CAPACITY] = undefined;
61 -
static ASM_DATA_STORAGE: [u8; ASM_DATA_CAPACITY] = undefined;
51 +
unsafe static SOURCE_BUF: [u8; SOURCE_BUF_SIZE] = undefined;
52 +
unsafe static EXPECTED_BUF: [u8; EXPECTED_BUF_SIZE] = undefined;
53 +
unsafe static OUTPUT_BUF: [u8; OUTPUT_BUF_SIZE] = undefined;
54 +
unsafe static AST_ARENA_STORAGE: [u8; ARENA_SIZE] = undefined;
55 +
unsafe static IL_ARENA_STORAGE: [u8; ARENA_SIZE] = undefined;
56 +
unsafe static PRINT_ARENA_STORAGE: [u8; ARENA_SIZE] = undefined;
57 +
unsafe static RESOLVER_ARENA_STORAGE: [u8; ARENA_SIZE] = undefined;
58 +
unsafe static NODE_DATA_STORAGE: [resolver::NodeData; MAX_NODE_DATA] = undefined;
59 +
unsafe static ERROR_STORAGE: [resolver::Error; MAX_ERRORS] = undefined;
60 +
unsafe static ASM_TEXT_STORAGE: [u32; ASM_TEXT_CAPACITY] = undefined;
61 +
unsafe static ASM_DATA_STORAGE: [u8; ASM_DATA_CAPACITY] = undefined;
62 62
63 63
/// Strip a `//` comment from a line, preserving `//` inside quoted strings.
64 64
/// Returns the content before the comment, trimmed of trailing whitespace.
65 -
fn stripLine(line: *[u8]) -> *[u8] {
65 +
unsafe fn stripLine(line: *[u8]) -> *[u8] {
66 66
    let mut end = line.len;
67 67
    let mut i: u32 = 0;
68 68
    let mut inString = false;
69 69
    let mut escaped = false;
70 70
93 93
    }
94 94
    return &line[..end];
95 95
}
96 96
97 97
/// Get next line from string at offset. Returns the line and updates offset past newline.
98 -
fn nextLine(s: *[u8], offset: *mut u32) -> *[u8] {
98 +
unsafe fn nextLine(s: *[u8], offset: *mut u32) -> *[u8] {
99 99
    let start = *offset;
100 100
    let mut i = start;
101 101
102 102
    while i < s.len and s[i] <> '\n' {
103 103
        set i += 1;
110 110
    }
111 111
    return line;
112 112
}
113 113
114 114
/// Compare two strings ignoring comments, line by line.
115 -
fn stringsEqual(a: *[u8], b: *[u8]) -> bool {
115 +
unsafe fn stringsEqual(a: *[u8], b: *[u8]) -> bool {
116 116
    let mut ai: u32 = 0;
117 117
    let mut bi: u32 = 0;
118 118
119 119
    while ai < a.len or bi < b.len {
120 120
        let mut aLine = "";
131 131
    }
132 132
    return true;
133 133
}
134 134
135 135
/// Print diff between expected and actual output.
136 -
fn printDiff(expected: *[u8], actual: *[u8]) {
136 +
unsafe fn printDiff(expected: *[u8], actual: *[u8]) {
137 137
    io::printLn("\n// Expected");
138 138
    io::print(expected);
139 139
    io::print("\n");
140 140
141 141
    io::printLn("// Actual");
144 144
}
145 145
146 146
/// Derive the `.ril` path from a `.rad` source path. Returns nil if the path
147 147
/// does not end in `.rad` or the buffer is too small. The result is
148 148
/// null-terminated for use with syscalls.
149 -
fn deriveRilPath(sourcePath: *[u8], buf: *mut [u8]) -> ?*[u8] {
149 +
unsafe fn deriveRilPath(sourcePath: *[u8], buf: *mut [u8]) -> ?*[u8] {
150 150
    let len = sourcePath.len;
151 151
    if len < SOURCE_EXT.len {
152 152
        return nil;
153 153
    }
154 154
    let extStart = len - SOURCE_EXT.len;
170 170
171 171
    return &buf[..len];
172 172
}
173 173
174 174
/// Write a self-contained RV64 image containing text and data sections.
175 -
fn writeImage(code: *[u32], roData: *[u8], rwData: *[u8], path: *[u8]) -> bool {
175 +
unsafe fn writeImage(code: *[u32], roData: *[u8], rwData: *[u8], path: *[u8]) -> bool {
176 176
    let mut header = rv64::imageHeader(code.len * rv64::INSTR_SIZE as u32, roData.len, rwData.len);
177 177
    let headerWords = &header[..];
178 178
    let headerBytes = @sliceOf(headerWords.ptr as *u8, headerWords.len * rv64::WORD_SIZE as u32);
179 179
    let codeBytes = @sliceOf(code.ptr as *u8, code.len * rv64::INSTR_SIZE as u32);
180 180
181 -
    return unix::writeFileParts(path, &[headerBytes, codeBytes, roData, rwData]);
181 +
    let mut success = false;
182 +
    unsafe {
183 +
        set success = unix::writeFileParts(path, &[headerBytes, codeBytes, roData, rwData]);
184 +
    }
185 +
    return success;
182 186
}
183 187
184 -
fn assembleBinary(sourcePath: *[u8], outputPath: *[u8]) -> bool {
185 -
    let source = unix::readFile(sourcePath, &mut SOURCE_BUF[..]) else {
188 +
/// Assemble one source file and write its RV64 image.
189 +
unsafe fn assembleBinary(sourcePath: *[u8], outputPath: *[u8]) -> bool {
190 +
    let mut sourceResult: ?*[u8] = nil;
191 +
    unsafe { set sourceResult = unix::readFile(sourcePath, &mut SOURCE_BUF[..]); }
192 +
    let source = sourceResult else {
186 193
        io::printError("error: could not read source: ");
187 194
        io::printError(sourcePath);
188 195
        io::printError("\n");
189 196
        return false;
190 197
    };
213 220
    }
214 221
    return true;
215 222
}
216 223
217 224
/// Run a single IL snapshot test case. Returns `true` on success.
218 -
fn runTest(sourcePath: *[u8]) -> bool {
225 +
unsafe fn runTest(sourcePath: *[u8]) -> bool {
219 226
    // Path buffer.
220 227
    let mut rilPathBuf: [u8; MAX_PATH_LEN] = undefined;
221 228
    let mut pkgScope: resolver::Scope = undefined;
222 229
223 230
    // Derive .ril path from source path.
226 233
        io::printLn(sourcePath);
227 234
        return false;
228 235
    };
229 236
230 237
    // Read expected IL.
231 -
    let expected = unix::readFile(rilPath, &mut EXPECTED_BUF[..]) else {
238 +
    let mut expectedResult: ?*[u8] = nil;
239 +
    unsafe { set expectedResult = unix::readFile(rilPath, &mut EXPECTED_BUF[..]); }
240 +
    let expected = expectedResult else {
232 241
        io::print("error: could not read expected IL: ");
233 242
        io::printLn(rilPath);
234 243
        return false;
235 244
    };
236 245
237 246
    // Read source file.
238 -
    let source = unix::readFile(sourcePath, &mut SOURCE_BUF[..]) else {
247 +
    let mut sourceResult: ?*[u8] = nil;
248 +
    unsafe { set sourceResult = unix::readFile(sourcePath, &mut SOURCE_BUF[..]); }
249 +
    let source = sourceResult else {
239 250
        io::print("error: could not read source: ");
240 251
        io::printLn(sourcePath);
241 252
        return false;
242 253
    };
243 254
289 300
290 301
    return true;
291 302
}
292 303
293 304
/// Run a single test specified as an argument.
294 -
@default fn main(env: *sys::Env) -> i32 {
305 +
@default unsafe fn main(env: *sys::Env) -> i32 {
295 306
    let args = env.args;
296 307
297 308
    if args.len == 4 and mem::eq(args[1], "assemble") {
298 309
        if assembleBinary(args[2], args[3]) {
299 310
            return 0;
test/tests/array.aggregate.stride.rad +2 -2
8 8
fn arrayOfRecords(arr: [Point; 3], idx: u32) -> i32 {
9 9
    return arr[idx].x;
10 10
}
11 11
12 12
/// Take address of record in array.
13 -
fn addressOfRecord(arr: [Point; 3], idx: u32) -> *Point {
14 -
    return &arr[idx];
13 +
unsafe fn addressOfRecord(arr: [Point; 3], idx: u32) -> *Point {
14 +
    return &arr[idx] as *Point;
15 15
}
16 16
17 17
/// Array of arrays (16 bytes per element).
18 18
fn arrayOfArrays(arr: [[i32; 4]; 3], idx: u32) -> i32 {
19 19
    return arr[idx][0];
test/tests/array.slice.empty.rad +1 -1
1 1
//! returns: 0
2 -
fn length(slice: *[u8]) -> u32 {
2 +
fn length(slice: &[u8]) -> u32 {
3 3
  return slice.len;
4 4
}
5 5
6 6
@default fn main() -> u32 {
7 7
  return length(&[]);
test/tests/array.slice.full.rad +2 -2
1 1
/// Creates a slice from an array with full range.
2 -
fn sliceFull(a: [i32; 4]) -> *[i32] {
3 -
    return &a[..];
2 +
unsafe fn sliceFull(a: [i32; 4]) -> *[i32] {
3 +
    return &a[..] as *[i32];
4 4
}
test/tests/array.slice.gen.end.rad +2 -2
1 1
//! returns: 0
2 -
@default fn main() -> i32 {
2 +
@default unsafe fn main() -> i32 {
3 3
    let mut arr: [i32; 4] = [1, 2, 3, 4];
4 -
    let mut slice: *[i32] = &arr[..2];
4 +
    let mut slice: *[i32] = &arr[..2] as *[i32];
5 5
6 6
    return 0;
7 7
}
test/tests/array.slice.gen.index.rad +2 -2
1 1
//! returns: 0
2 -
@default fn main() -> i32 {
2 +
@default unsafe fn main() -> i32 {
3 3
    let mut arr: [i32; 4] = [1, 2, 3, 4];
4 -
    let mut slice: *[i32] = &arr[1..];
4 +
    let mut slice: *[i32] = &arr[1..] as *[i32];
5 5
6 6
    return (slice[1]) - 3;
7 7
}
test/tests/array.slice.gen.open.rad +2 -2
1 1
//! returns: 0
2 -
@default fn main() -> i32 {
2 +
@default unsafe fn main() -> i32 {
3 3
    let mut arr: [i32; 4] = [1, 2, 3, 4];
4 -
    let mut slice: *[i32] = &arr[..];
4 +
    let mut slice: *[i32] = &arr[..] as *[i32];
5 5
6 6
    return 0;
7 7
}
test/tests/array.slice.gen.start.end.rad +2 -2
1 1
//! returns: 0
2 -
@default fn main() -> i32 {
2 +
@default unsafe fn main() -> i32 {
3 3
    let mut arr: [i32; 4] = [1, 2, 3, 4];
4 -
    let mut slice: *[i32] = &arr[1..3];
4 +
    let mut slice: *[i32] = &arr[1..3] as *[i32];
5 5
6 6
    return 0;
7 7
}
test/tests/array.slice.gen.start.rad +2 -2
1 1
//! returns: 0
2 -
@default fn main() -> i32 {
2 +
@default unsafe fn main() -> i32 {
3 3
    let mut arr: [i32; 4] = [1, 2, 3, 4];
4 -
    let mut slice: *[i32] = &arr[2..];
4 +
    let mut slice: *[i32] = &arr[2..] as *[i32];
5 5
6 6
    return 0;
7 7
}
test/tests/array.slice.openend.rad +2 -2
1 1
/// Creates a slice from an array with open end bound.
2 -
fn sliceOpenEnd(a: [i32; 4], start: u32) -> *[i32] {
3 -
    return &a[start..];
2 +
unsafe fn sliceOpenEnd(a: [i32; 4], start: u32) -> *[i32] {
3 +
    return &a[start..] as *[i32];
4 4
}
test/tests/array.slice.openstart.rad +2 -2
1 1
/// Creates a slice from an array with open start bound.
2 -
fn sliceOpenStart(a: [i32; 4], end: u32) -> *[i32] {
3 -
    return &a[..end];
2 +
unsafe fn sliceOpenStart(a: [i32; 4], end: u32) -> *[i32] {
3 +
    return &a[..end] as *[i32];
4 4
}
test/tests/array.slice.rad +5 -5
1 1
//! returns: 0
2 2
//! Test array slicing with various ranges.
3 3
4 -
@default fn main() -> i32 {
4 +
@default unsafe fn main() -> i32 {
5 5
    let arr: [i32; 5] = [1, 2, 3, 4, 5];
6 6
7 -
    let slice1: *[i32] = &arr[..];
8 -
    let slice2: *[i32] = &arr[1..4];
9 -
    let slice3: *[i32] = &arr[..3];
10 -
    let slice4: *[i32] = &arr[2..];
7 +
    let slice1: *[i32] = &arr[..] as *[i32];
8 +
    let slice2: *[i32] = &arr[1..4] as *[i32];
9 +
    let slice3: *[i32] = &arr[..3] as *[i32];
10 +
    let slice4: *[i32] = &arr[2..] as *[i32];
11 11
12 12
    assert slice1.len == 5;
13 13
    assert slice2.len == 3;
14 14
    assert slice3.len == 3;
15 15
    assert slice4.len == 3;
test/tests/asm.rodata.prefix.rad +1 -1
1 1
//! returns: 0
2 2
3 3
fn asmDataValue() -> i32;
4 4
5 -
@default fn main() -> i32 {
5 +
@default unsafe fn main() -> i32 {
6 6
    let s = "hello";
7 7
8 8
    assert s.len == 5;
9 9
    assert s[0] == 'h' as u8;
10 10
    assert s[4] == 'o' as u8;
test/tests/average.rad +1 -1
1 1
/// Compute the sum of a list of numbers.
2 -
fn average(numbers: *[u32]) -> u32 {
2 +
fn average(numbers: &[u32]) -> u32 {
3 3
    let mut sum: u32 = 0;
4 4
5 5
    for i in numbers {
6 6
        set sum += i;
7 7
    }
test/tests/bool.comparison.slice.rad +46 -46
1 1
//! returns: 0
2 -
fn memEq(a: *[u8], b: *[u8]) -> bool {
2 +
unsafe fn memEq(a: *[u8], b: *[u8]) -> bool {
3 3
    if a.len <> b.len {
4 4
        return false;
5 5
    }
6 6
    for i in 0..a.len {
7 7
        if a[i] <> b[i] {
17 17
18 18
fn sliceI32(input: *[i32]) -> *[i32] {
19 19
    return input;
20 20
}
21 21
22 -
fn sliceEqualU32(a: *[u32], b: *[u32]) -> bool {
22 +
unsafe fn sliceEqualU32(a: *[u32], b: *[u32]) -> bool {
23 23
    if a.len <> b.len {
24 24
        return false;
25 25
    }
26 26
    for i in 0..a.len {
27 27
        if a[i] <> b[i] {
29 29
        }
30 30
    }
31 31
    return true;
32 32
}
33 33
34 -
fn sliceEqualI32(a: *[i32], b: *[i32]) -> bool {
34 +
unsafe fn sliceEqualI32(a: *[i32], b: *[i32]) -> bool {
35 35
    if a.len <> b.len {
36 36
        return false;
37 37
    }
38 38
    for i in 0..a.len {
39 39
        if a[i] <> b[i] {
41 41
        }
42 42
    }
43 43
    return true;
44 44
}
45 45
46 -
fn sliceEqual1() -> bool {
46 +
unsafe fn sliceEqual1() -> bool {
47 47
    let a1: [u32; 3] = [9, 42, 3];
48 48
49 -
    let s1: *[u32] = &a1[..];
50 -
    let s2: *[u32] = &a1[..];
49 +
    let s1: *[u32] = &a1[..] as *[u32];
50 +
    let s2: *[u32] = &a1[..] as *[u32];
51 51
52 52
    return s1 == s2 and sliceEqualU32(s1, s2);
53 53
}
54 54
55 -
fn sliceEqual2() -> bool {
55 +
unsafe fn sliceEqual2() -> bool {
56 56
    let a1: [u32; 3] = [9, 42, 3];
57 57
58 -
    let s1: *[u32] = &a1[1..];
59 -
    let s2: *[u32] = &a1[1..];
58 +
    let s1: *[u32] = &a1[1..] as *[u32];
59 +
    let s2: *[u32] = &a1[1..] as *[u32];
60 60
61 61
    return s1 == s2 and sliceEqualU32(s1, s2);
62 62
}
63 63
64 -
fn sliceEqual3() -> bool {
64 +
unsafe fn sliceEqual3() -> bool {
65 65
    let a1: [u32; 3] = [9, 42, 3];
66 66
67 -
    let s1: *[u32] = &a1[..1];
68 -
    let s2: *[u32] = &a1[..1];
67 +
    let s1: *[u32] = &a1[..1] as *[u32];
68 +
    let s2: *[u32] = &a1[..1] as *[u32];
69 69
70 70
    return s1 == s2 and sliceEqualU32(s1, s2);
71 71
}
72 72
73 -
fn sliceEqual4() -> bool {
73 +
unsafe fn sliceEqual4() -> bool {
74 74
    let a1: [u32; 3] = [9, 42, 3];
75 75
76 -
    let s1: *[u32] = &a1[2..];
77 -
    let s2: *[u32] = &a1[2..];
76 +
    let s1: *[u32] = &a1[2..] as *[u32];
77 +
    let s2: *[u32] = &a1[2..] as *[u32];
78 78
79 79
    return s1 == s2 and sliceEqualU32(s1, s2);
80 80
}
81 81
82 -
fn sliceNotEqualSameArray1() -> bool {
82 +
unsafe fn sliceNotEqualSameArray1() -> bool {
83 83
    let a1: [u32; 3] = [42, 8, 3];
84 84
85 -
    let s1: *[u32] = &a1[..2];
86 -
    let s2: *[u32] = &a1[1..3];
85 +
    let s1: *[u32] = &a1[..2] as *[u32];
86 +
    let s2: *[u32] = &a1[1..3] as *[u32];
87 87
88 88
    return s1 <> s2 and not sliceEqualU32(s1, s2);
89 89
}
90 90
91 -
fn sliceNotEqualSameArray2() -> bool {
91 +
unsafe fn sliceNotEqualSameArray2() -> bool {
92 92
    let a1: [u32; 3] = [42, 8, 3];
93 93
94 -
    let s1: *[u32] = &a1[..2];
95 -
    let s2: *[u32] = &a1[..3];
94 +
    let s1: *[u32] = &a1[..2] as *[u32];
95 +
    let s2: *[u32] = &a1[..3] as *[u32];
96 96
97 97
    return s1 <> s2 and not sliceEqualU32(s1, s2);
98 98
}
99 99
100 -
fn sliceEqualDifferentArray() -> bool {
100 +
unsafe fn sliceEqualDifferentArray() -> bool {
101 101
    let a1: [u32; 3] = [42, 8, 3];
102 102
    let a2: [u32; 3] = [42, 8, 3];
103 103
104 -
    let s1: *[u32] = &a1[..];
105 -
    let s2: *[u32] = &a2[..];
104 +
    let s1: *[u32] = &a1[..] as *[u32];
105 +
    let s2: *[u32] = &a2[..] as *[u32];
106 106
107 107
    return sliceEqualU32(s1, s2) and s1 <> s2;
108 108
}
109 109
110 -
fn sliceEqualString1() -> bool {
110 +
unsafe fn sliceEqualString1() -> bool {
111 111
    let s1: *[u8] = "ABC";
112 112
    let s2: *[u8] = "ABC";
113 113
114 114
    return memEq(s1, s2);
115 115
}
116 116
117 -
fn sliceEqualString2() -> bool {
117 +
unsafe fn sliceEqualString2() -> bool {
118 118
    let s1: *[u8] = "ABC";
119 119
    let s2: *[u8] = "DEF";
120 120
121 121
    return not memEq(s1, s2);
122 122
}
123 123
124 -
fn sliceEqualString3() -> bool {
124 +
unsafe fn sliceEqualString3() -> bool {
125 125
    let a1: [u8; 3] = ['A', 'B', 'C'];
126 -
    let s1: *[u8] = &a1[..];
126 +
    let s1: *[u8] = &a1[..] as *[u8];
127 127
    let s2: *[u8] = "ABC";
128 128
129 129
    return memEq(s1, s2)
130 130
       and memEq(s1, "ABC")
131 -
       and memEq(&a1[..], "ABC");
131 +
       and memEq(&a1[..] as *[u8], "ABC");
132 132
}
133 133
134 -
fn sliceEqualString4() -> bool {
134 +
unsafe fn sliceEqualString4() -> bool {
135 135
    let s1: *[u8] = "ABC";
136 -
    let s2: *[u8] = &['A', 'B', 'C'];
136 +
    let s2: *[u8] = &['A', 'B', 'C'] as *[u8];
137 137
138 138
    return memEq(s1, s2);
139 139
}
140 140
141 -
fn sliceEqualString5() -> bool {
141 +
unsafe fn sliceEqualString5() -> bool {
142 142
    let s1: *[u8] = "ABC";
143 143
144 144
    // Sub-slicing a slice currently trips a separate compiler bug.
145 145
    // Keep this check focused on slice equality behavior itself.
146 146
    return memEq(s1, "ABC");
147 147
}
148 148
149 -
fn sliceNotEqualU8() -> bool {
149 +
unsafe fn sliceNotEqualU8() -> bool {
150 150
    let a: [u8; 3] = [1, 2, 3];
151 151
    let b: [u8; 3] = [1, 2, 4];
152 -
    let s: *[u8] = &[1, 2, 3];
152 +
    let s: *[u8] = &[1, 2, 3] as *[u8];
153 153
154 -
    return not memEq(s, &[1, 2, 4])
155 -
       and not memEq(s, &[1, 0, 3])
154 +
    return not memEq(s, &[1, 2, 4] as *[u8])
155 +
       and not memEq(s, &[1, 0, 3] as *[u8])
156 156
       and not memEq("ABC", "ABCD")
157 157
       and not memEq("ABC", "ABD")
158 -
       and not memEq(&a[..], &a[1..])
159 -
       and not memEq(&a[..], &b[..])
160 -
       and not memEq(&a[..], &[1, 3, 3]);
158 +
       and not memEq(&a[..] as *[u8], &a[1..] as *[u8])
159 +
       and not memEq(&a[..] as *[u8], &b[..] as *[u8])
160 +
       and not memEq(&a[..] as *[u8], &[1, 3, 3] as *[u8]);
161 161
}
162 162
163 -
fn sliceNotEqualU16() -> bool {
163 +
unsafe fn sliceNotEqualU16() -> bool {
164 164
    let a: [u16; 3] = [1, 2, 3];
165 165
    let b: [u16; 3] = [1, 2, 4];
166 -
    let s: *[u16] = &[1, 2, 3];
166 +
    let s: *[u16] = &[1, 2, 3] as *[u16];
167 167
168 -
    return s <> &[1, 2, 4]
169 -
       and s <> &[1, 0, 3]
168 +
    return s <> &[1, 2, 4] as *[u16]
169 +
       and s <> &[1, 0, 3] as *[u16]
170 170
       and &a[..] <> &a[1..]
171 171
       and &a[..] <> &b[..]
172 172
       and &a[..] <> &[1, 3, 3];
173 173
}
174 174
175 -
fn sliceReturnEqual() -> bool {
175 +
unsafe fn sliceReturnEqual() -> bool {
176 176
    return memEq(sliceU8("ABC"), "ABC")
177 -
       and sliceEqualI32(sliceI32(&[1, 2, 3]), &[1, 2, 3]);
177 +
       and sliceEqualI32(sliceI32(&[1, 2, 3] as *[i32]), &[1, 2, 3] as *[i32]);
178 178
}
179 179
180 -
@default fn main() -> i32 {
180 +
@default unsafe fn main() -> i32 {
181 181
    assert sliceEqual1();
182 182
    assert sliceEqual2();
183 183
    assert sliceEqual3();
184 184
    assert sliceEqual4();
185 185
    assert sliceEqualString1();
test/tests/bool.comparison.slice.record.gen.rad +15 -15
7 7
8 8
record Line {
9 9
    points: *[Point],
10 10
}
11 11
12 -
fn pointsEqual(a: *[Point], b: *[Point]) -> bool {
12 +
unsafe fn pointsEqual(a: *[Point], b: *[Point]) -> bool {
13 13
    if a.len <> b.len {
14 14
        return false;
15 15
    }
16 16
    for i in 0..a.len {
17 17
        if a[i].x <> b[i].x or a[i].y <> b[i].y {
19 19
        }
20 20
    }
21 21
    return true;
22 22
}
23 23
24 -
fn lineEqual(a: Line, b: Line) -> bool {
24 +
unsafe fn lineEqual(a: Line, b: Line) -> bool {
25 25
    return pointsEqual(a.points, b.points);
26 26
}
27 27
28 -
fn testSliceStructEqual() -> bool {
28 +
unsafe fn testSliceStructEqual() -> bool {
29 29
    let a: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }];
30 30
    let b: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }];
31 31
32 -
    return pointsEqual(&a[..], &b[..]);
32 +
    return pointsEqual(&a[..] as *[Point], &b[..] as *[Point]);
33 33
}
34 34
35 -
fn testSliceStructNotEqualContent() -> bool {
35 +
unsafe fn testSliceStructNotEqualContent() -> bool {
36 36
    let a: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }];
37 37
    let b: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 5 }];
38 38
39 -
    return not pointsEqual(&a[..], &b[..]);
39 +
    return not pointsEqual(&a[..] as *[Point], &b[..] as *[Point]);
40 40
}
41 41
42 -
fn testSliceStructNotEqualLength() -> bool {
42 +
unsafe fn testSliceStructNotEqualLength() -> bool {
43 43
    let a: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }];
44 44
    let b: [Point; 1] = [Point { x: 1, y: 2 }];
45 45
46 -
    return not pointsEqual(&a[..], &b[..]);
46 +
    return not pointsEqual(&a[..] as *[Point], &b[..] as *[Point]);
47 47
}
48 48
49 -
fn testStructWithSliceFieldEqual() -> bool {
49 +
unsafe fn testStructWithSliceFieldEqual() -> bool {
50 50
    let pts1: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }];
51 51
    let pts2: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }];
52 52
53 -
    let line1: Line = Line { points: &pts1[..] };
54 -
    let line2: Line = Line { points: &pts2[..] };
53 +
    let line1: Line = Line { points: &pts1[..] as *[Point] };
54 +
    let line2: Line = Line { points: &pts2[..] as *[Point] };
55 55
56 56
    return lineEqual(line1, line2);
57 57
}
58 58
59 -
fn testStructWithSliceFieldNotEqual() -> bool {
59 +
unsafe fn testStructWithSliceFieldNotEqual() -> bool {
60 60
    let pts1: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }];
61 61
    let pts2: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 5 }];
62 62
63 -
    let line1: Line = Line { points: &pts1[..] };
64 -
    let line2: Line = Line { points: &pts2[..] };
63 +
    let line1: Line = Line { points: &pts1[..] as *[Point] };
64 +
    let line2: Line = Line { points: &pts2[..] as *[Point] };
65 65
66 66
    return not lineEqual(line1, line2);
67 67
}
68 68
69 -
@default fn main() -> i32 {
69 +
@default unsafe fn main() -> i32 {
70 70
    assert testSliceStructEqual();
71 71
    assert testSliceStructNotEqualContent();
72 72
    assert testSliceStructNotEqualLength();
73 73
    assert testStructWithSliceFieldEqual();
74 74
    assert testStructWithSliceFieldNotEqual();
test/tests/bool.comparison.slice.union.gen.rad +16 -16
13 13
union Shape {
14 14
    points(*[Point]),
15 15
    sizes(*[Size]),
16 16
}
17 17
18 -
fn pointsEqual(a: *[Point], b: *[Point]) -> bool {
18 +
unsafe fn pointsEqual(a: *[Point], b: *[Point]) -> bool {
19 19
    if a.len <> b.len {
20 20
        return false;
21 21
    }
22 22
    for i in 0..a.len {
23 23
        if a[i].x <> b[i].x or a[i].y <> b[i].y {
25 25
        }
26 26
    }
27 27
    return true;
28 28
}
29 29
30 -
fn sizesEqual(a: *[Size], b: *[Size]) -> bool {
30 +
unsafe fn sizesEqual(a: *[Size], b: *[Size]) -> bool {
31 31
    if a.len <> b.len {
32 32
        return false;
33 33
    }
34 34
    for i in 0..a.len {
35 35
        if a[i].width <> b[i].width or a[i].height <> b[i].height {
37 37
        }
38 38
    }
39 39
    return true;
40 40
}
41 41
42 -
fn shapeEqual(a: Shape, b: Shape) -> bool {
42 +
unsafe fn shapeEqual(a: Shape, b: Shape) -> bool {
43 43
    if let case Shape::points(pointsA) = a {
44 44
        if let case Shape::points(pointsB) = b {
45 45
            return pointsEqual(pointsA, pointsB);
46 46
        }
47 47
        return false;
53 53
        return false;
54 54
    }
55 55
    return false;
56 56
}
57 57
58 -
fn testEnumSliceEqual() -> bool {
58 +
unsafe fn testEnumSliceEqual() -> bool {
59 59
    let pts1: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }];
60 60
    let pts2: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }];
61 61
62 -
    let s1: Shape = Shape::points(&pts1[..]);
63 -
    let s2: Shape = Shape::points(&pts2[..]);
62 +
    let s1: Shape = Shape::points(&pts1[..] as *[Point]);
63 +
    let s2: Shape = Shape::points(&pts2[..] as *[Point]);
64 64
65 65
    return shapeEqual(s1, s2);
66 66
}
67 67
68 -
fn testEnumSliceNotEqual() -> bool {
68 +
unsafe fn testEnumSliceNotEqual() -> bool {
69 69
    let pts1: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }];
70 70
    let pts2: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 5 }];
71 71
72 -
    let s1: Shape = Shape::points(&pts1[..]);
73 -
    let s2: Shape = Shape::points(&pts2[..]);
72 +
    let s1: Shape = Shape::points(&pts1[..] as *[Point]);
73 +
    let s2: Shape = Shape::points(&pts2[..] as *[Point]);
74 74
75 75
    return not shapeEqual(s1, s2);
76 76
}
77 77
78 -
fn testEnumSizesSliceEqual() -> bool {
78 +
unsafe fn testEnumSizesSliceEqual() -> bool {
79 79
    let sizes1: [Size; 2] = [Size { width: 10, height: 20 }, Size { width: 30, height: 40 }];
80 80
    let sizes2: [Size; 2] = [Size { width: 10, height: 20 }, Size { width: 30, height: 40 }];
81 81
82 -
    let s1: Shape = Shape::sizes(&sizes1[..]);
83 -
    let s2: Shape = Shape::sizes(&sizes2[..]);
82 +
    let s1: Shape = Shape::sizes(&sizes1[..] as *[Size]);
83 +
    let s2: Shape = Shape::sizes(&sizes2[..] as *[Size]);
84 84
85 85
    return shapeEqual(s1, s2);
86 86
}
87 87
88 -
fn testEnumDifferentVariants() -> bool {
88 +
unsafe fn testEnumDifferentVariants() -> bool {
89 89
    let pts: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }];
90 90
    let sizes: [Size; 2] = [Size { width: 10, height: 20 }, Size { width: 30, height: 40 }];
91 91
92 -
    let s1: Shape = Shape::points(&pts[..]);
93 -
    let s2: Shape = Shape::sizes(&sizes[..]);
92 +
    let s1: Shape = Shape::points(&pts[..] as *[Point]);
93 +
    let s2: Shape = Shape::sizes(&sizes[..] as *[Size]);
94 94
95 95
    return not shapeEqual(s1, s2);
96 96
}
97 97
98 -
@default fn main() -> i32 {
98 +
@default unsafe fn main() -> i32 {
99 99
    assert testEnumSliceEqual();
100 100
    assert testEnumSliceNotEqual();
101 101
    assert testEnumSizesSliceEqual();
102 102
    assert testEnumDifferentVariants();
103 103
    return 0;
test/tests/bool.short.circuit.rad +1 -1
1 1
//! returns: 0
2 2
//! Test short-circuiting behavior of 'and' and 'or' operators.
3 -
fn modify(counter: *mut i32, ret: bool) -> bool {
3 +
fn modify(counter: &mut i32, ret: bool) -> bool {
4 4
    set *counter += 1;
5 5
    return ret;
6 6
}
7 7
8 8
@default fn main() -> i32 {
test/tests/builtin.sliceof.invalid.cap.rad +2 -3
1 1
//! returns: 133
2 2
//! Test @sliceOf runtime validation for len > cap.
3 -
4 -
@default fn main() -> i32 {
3 +
@default unsafe fn main() -> i32 {
5 4
    let mut arr: [i32; 4] = [10, 20, 30, 40];
6 -
    let ptr: *i32 = &arr[0];
5 +
    let ptr: *i32 = &arr[0] as *i32;
7 6
    let slice: *[i32] = @sliceOf(ptr, 4, 3);
8 7
9 8
    return slice.len as i32;
10 9
}
test/tests/builtin.sliceof.mut.rad +2 -3
1 1
//! returns: 0
2 2
// Test the @sliceOf builtin with mutable slice.
3 -
4 -
@default fn main() -> i32 {
3 +
@default unsafe fn main() -> i32 {
5 4
    let mut arr: [i32; 4] = [10, 20, 30, 40];
6 5
7 6
    // Get a mutable pointer to the first element
8 -
    let ptr: *mut i32 = &mut arr[0];
7 +
    let ptr: *mut i32 = &mut arr[0] as *mut i32;
9 8
10 9
    // Create a mutable slice from the pointer and length
11 10
    let slice: *mut [i32] = @sliceOf(ptr, 4);
12 11
13 12
    // Double each element via the slice
test/tests/builtin.sliceof.rad +2 -3
1 1
//! returns: 100
2 2
// Test @sliceOf builtin: create a slice from a pointer and length.
3 -
4 -
@default fn main() -> i32 {
3 +
@default unsafe fn main() -> i32 {
5 4
    let mut arr: [i32; 4] = [10, 20, 30, 40];
6 5
7 6
    // Get a pointer to the first element
8 -
    let ptr: *i32 = &arr[0];
7 +
    let ptr: *i32 = &arr[0] as *i32;
9 8
10 9
    // Create a slice from the pointer and length
11 10
    let slice: *[i32] = @sliceOf(ptr, 4);
12 11
13 12
    // Access elements via the slice and sum them
test/tests/byte.load.store.rad +4 -4
1 1
// Test byte-level load and store operations (W8)
2 2
3 -
fn loadByte(ptr: *u8) -> u8 {
3 +
unsafe fn loadByte(ptr: *u8) -> u8 {
4 4
    return *ptr;
5 5
}
6 6
7 -
fn storeByte(ptr: *mut u8, val: u8) {
7 +
unsafe fn storeByte(ptr: *mut u8, val: u8) {
8 8
    set *ptr = val;
9 9
}
10 10
11 -
fn loadStoreByte(src: *u8, dst: *mut u8) {
11 +
unsafe fn loadStoreByte(src: *u8, dst: *mut u8) {
12 12
    set *dst = *src;
13 13
}
14 14
15 15
fn byteArrayAccess(arr: [u8; 4], idx: u32) -> u8 {
16 16
    return arr[idx];
17 17
}
18 18
19 -
fn byteSliceAccess(slice: *[u8], idx: u32) -> u8 {
19 +
unsafe fn byteSliceAccess(slice: *[u8], idx: u32) -> u8 {
20 20
    return slice[idx];
21 21
}
test/tests/call.aggregate.arg.snapshot.rad +1 -1
5 5
record Pair {
6 6
    x: i32,
7 7
    y: i32,
8 8
}
9 9
10 -
fn mutate(pair: *mut Pair) -> i32 {
10 +
fn mutate(pair: &mut Pair) -> i32 {
11 11
    set pair.x = 99;
12 12
    return 0;
13 13
}
14 14
15 15
fn first(pair: Pair, ignored: i32) -> i32 {
test/tests/call.clobber.rad +4 -4
1 1
//! returns: 0
2 2
//! Test that values live across function calls are not clobbered.
3 -
fn modify(counter: *mut i32, ret: bool) -> bool {
3 +
unsafe fn modify(counter: *mut i32, ret: bool) -> bool {
4 4
    set *counter += 1;
5 5
    return ret;
6 6
}
7 7
8 -
@default fn main() -> i32 {
8 +
@default unsafe fn main() -> i32 {
9 9
    let mut x: i32 = 0;
10 -
    let r: bool = modify(&mut x, false);
10 +
    let r: bool = modify(&mut x as *mut i32, false);
11 11
    assert x == 1;
12 12
    assert not r;
13 13
14 14
    // Pointer live across call.
15 15
    let mut y: i32 = 42;
16 -
    let p: *mut i32 = &mut y;
16 +
    let p: *mut i32 = &mut y as *mut i32;
17 17
    let r2: bool = modify(p, true);
18 18
    assert *p == 43;
19 19
    assert r2;
20 20
    return 0;
21 21
}
test/tests/cast.basic.rad +1 -1
37 37
fn i32ToI16(x: i32) -> i16 {
38 38
    return x as i16;
39 39
}
40 40
41 41
/// Cast pointer types (same size, no instruction).
42 -
fn ptrCast(x: *u8) -> *i32 {
42 +
unsafe fn ptrCast(x: *u8) -> *i32 {
43 43
    return x as *i32;
44 44
}
test/tests/compound.assign.index.once.rad +1 -1
1 1
//! returns: 0
2 2
//! Compound assignment evaluates a side-effecting lvalue exactly once.
3 3
4 -
fn nextIndex(calls: *mut u32) -> u32 {
4 +
fn nextIndex(calls: &mut u32) -> u32 {
5 5
    let index = *calls;
6 6
    set *calls += 1;
7 7
    return index;
8 8
}
9 9
test/tests/compound.assign.rad +2 -2
1 1
//! returns: 0
2 2
/// Test compound assignment operators (+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=).
3 -
@default fn main() -> i32 {
3 +
@default unsafe fn main() -> i32 {
4 4
    let mut a: i32 = 10;
5 5
6 6
    // Test +=
7 7
    set a += 5;
8 8
    assert a == 15;
50 50
    set arr[1] += 5;
51 51
    assert arr[1] == 25;
52 52
53 53
    // Test compound assignment with pointer dereference.
54 54
    let mut val: i32 = 100;
55 -
    let p: *mut i32 = &mut val;
55 +
    let p: *mut i32 = &mut val as *mut i32;
56 56
    set *p += 50;
57 57
    assert val == 150;
58 58
59 59
    // Test chained compound assignments.
60 60
    let mut x: i32 = 1;
test/tests/cond.iflet.optional.rad +1 -1
1 1
// Returns 1 when the optional pointer is present, otherwise returns 0.
2 -
fn ifLetOptional(p: ?*i32) -> i32 {
2 +
unsafe fn ifLetOptional(p: ?*i32) -> i32 {
3 3
    if let x = p {
4 4
        return 1;
5 5
    } else {
6 6
        return 0;
7 7
    }
test/tests/cond.letelse.optional.rad +1 -1
1 1
// Returns 1 when the optional pointer is present, otherwise returns 0.
2 -
fn letElseOptional(p: ?*i32) -> i32 {
2 +
unsafe fn letElseOptional(p: ?*i32) -> i32 {
3 3
    let x = p else { return 0; };
4 4
    return 1;
5 5
}
test/tests/cond.match.guard.regalloc.rad +6 -6
18 18
    span1: u32,
19 19
    span2: u32,
20 20
    kind: Kind,
21 21
}
22 22
23 -
fn getPath(node: *Node, buf: *mut [*[u8]]) -> *[*[u8]] {
24 -
    let mut out: *[*[u8]] = &[];
23 +
unsafe fn getPath(node: *Node, buf: *mut [*[u8]]) -> *[*[u8]] {
24 +
    let mut out: *[*[u8]] = &[] as *[*[u8]];
25 25
26 26
    match node.kind {
27 27
        case Kind::Name(name) if name.len > 0 => {
28 28
            set buf[0] = name;
29 -
            set out = &buf[..1];
29 +
            set out = &buf[..1] as *[*[u8]];
30 30
        }
31 31
        case Kind::Pair { a, b } => {
32 -
            set out = &buf[..1];
32 +
            set out = &buf[..1] as *[*[u8]];
33 33
        }
34 34
        else => {}
35 35
    }
36 36
    return out;
37 37
}
38 38
39 -
@default fn main() -> i32 {
39 +
@default unsafe fn main() -> i32 {
40 40
    let n = Node {
41 41
        id: 0, span1: 0, span2: 5,
42 42
        kind: Kind::Name("Hello"),
43 43
    };
44 44
    let mut buffer: [*[u8]; 4] = undefined;
45 -
    let path = getPath(&n, &mut buffer[..]);
45 +
    let path = getPath(&n as *Node, &mut buffer[..] as *mut [*[u8]]);
46 46
47 47
    let pathLen = path.len as i32;
48 48
    assert pathLen <= 1;
49 49
    assert pathLen > 0;
50 50
test/tests/const-expr-array-size.rad +2 -2
5 5
6 6
constant ROWS: u32 = 3;
7 7
constant COLS: u32 = 4;
8 8
constant TOTAL: u32 = ROWS * COLS;
9 9
10 -
static DATA: [i32; TOTAL] = undefined;
10 +
unsafe static DATA: [i32; TOTAL] = undefined;
11 11
12 -
@default fn main() -> i32 {
12 +
@default unsafe fn main() -> i32 {
13 13
    // Verify the array has the expected length.
14 14
    if DATA.len <> 12 { return 1; }
15 15
    return 0;
16 16
}
test/tests/const-expr-cast.rad +2 -2
10 10
constant SHIFTED: u64 = 4 as u64;
11 11
12 12
// Chained casts.
13 13
constant LEN: u32 = 8;
14 14
constant LEN2: u32 = (LEN as u64) as u32;
15 -
static BUF: [u8; LEN2] = undefined;
15 +
unsafe static BUF: [u8; LEN2] = undefined;
16 16
17 17
// Cast of unsuffixed literal arithmetic.
18 18
constant E: u32 = (3 + 4) as u32;
19 19
// Nested casts of literal arithmetic.
20 20
constant F: u32 = ((3 + 4) as u64) as u32;
30 30
// Typed constant * typed constant through cast.
31 31
constant X: u8 = 3;
32 32
constant Y: u8 = 4;
33 33
constant Z: i32 = (X as i32) * (Y as i32);
34 34
35 -
@default fn main() -> i32 {
35 +
@default unsafe fn main() -> i32 {
36 36
    assert BUF.len == 8;
37 37
    assert WIDE == 255;
38 38
    assert Z == 12;
39 39
    assert E == 7;
40 40
    assert F == 7;
test/tests/const-expr-literal.rad +3 -3
13 13
constant E: u32 = 2 * 3 + 4;
14 14
// Unary negation with literal.
15 15
constant F: i32 = -5;
16 16
constant G: i32 = F * 2;
17 17
18 -
static BUF: [u8; A] = undefined;
19 -
static BUF2: [u8; C] = undefined;
18 +
unsafe static BUF: [u8; A] = undefined;
19 +
unsafe static BUF2: [u8; C] = undefined;
20 20
21 -
@default fn main() -> i32 {
21 +
@default unsafe fn main() -> i32 {
22 22
    assert A == 16;
23 23
    assert BUF.len == 16;
24 24
    assert C == 20;
25 25
    assert BUF2.len == 20;
26 26
    assert D == 10;
test/tests/const.array.repeat.string.slice.rad +2 -2
1 1
//! returns: 0
2 2
/// Repeated string slices in constant arrays must emit complete slice headers.
3 -
constant WORDS: [*[u8]; 2] = ["abc"; 2];
3 +
unsafe constant WORDS: [*[u8]; 2] = ["abc"; 2];
4 4
5 -
@default fn main() -> i32 {
5 +
@default unsafe fn main() -> i32 {
6 6
    assert WORDS[0].len == 3;
7 7
    assert WORDS[1].len == 3;
8 8
    assert WORDS[0][0] == 'a';
9 9
    assert WORDS[1][2] == 'c';
10 10
    return 0;
test/tests/const.array.strings.slice.rad +2 -2
1 1
/// Const array of string slices lowers to slice headers (sym+len+padding)
2 2
/// and reuses deduplicated string storage.
3 -
constant WORDS: [*[u8]; 3] = [
3 +
unsafe constant WORDS: [*[u8]; 3] = [
4 4
    "apple",
5 5
    "pencil",
6 6
    "apple",
7 7
];
8 8
9 -
fn totalLen() -> u32 {
9 +
unsafe fn totalLen() -> u32 {
10 10
    return WORDS[0].len + WORDS[1].len + WORDS[2].len;
11 11
}
test/tests/const.record.union.rad +2 -2
9 9
record Keyword {
10 10
    name: *[u8],
11 11
    tok: TokenKind,
12 12
}
13 13
14 -
constant KEYWORDS: [Keyword; 3] = [
14 +
unsafe constant KEYWORDS: [Keyword; 3] = [
15 15
    Keyword { name: "fn", tok: TokenKind::Fn },
16 16
    Keyword { name: "let", tok: TokenKind::Let },
17 17
    Keyword { name: "if", tok: TokenKind::If },
18 18
];
19 19
20 -
fn getFirstTag() -> i32 {
20 +
unsafe fn getFirstTag() -> i32 {
21 21
    match KEYWORDS[0].tok {
22 22
        case TokenKind::Fn => return 0,
23 23
        case TokenKind::Let => return 1,
24 24
        case TokenKind::If => return 2,
25 25
    }
test/tests/const.slice.of.slices.rad +2 -2
1 1
/// Const nested slices should lower through generic constant `&[...]` support.
2 -
constant GROUPS: [*[*[u8]]; 2] = [
2 +
unsafe constant GROUPS: [*[*[u8]]; 2] = [
3 3
    &["ab", "cd"],
4 4
    &["efg"],
5 5
];
6 6
7 -
fn totalLen() -> u32 {
7 +
unsafe fn totalLen() -> u32 {
8 8
    return GROUPS[0].len + GROUPS[1].len + GROUPS[0][1].len;
9 9
}
test/tests/const.slice.param.rad +4 -5
1 1
//! returns: 36
2 2
3 3
constant DATA: [i32; 4] = [3, 5, 7, 9];
4 4
5 -
fn sumPair(slice: *[i32]) -> i32 {
5 +
fn sumPair(slice: &[i32]) -> i32 {
6 6
    return slice[0] + slice[1];
7 7
}
8 8
9 9
@default fn main() -> i32 {
10 -
    let all: *[i32] = &DATA[..];
11 -
    let head: i32 = sumPair(all);
12 -
    let tail: i32 = sumPair(&all[2..]);
13 -
    let mid: i32 = sumPair(&all[1..3]);
10 +
    let head: i32 = sumPair(&DATA[..]);
11 +
    let tail: i32 = sumPair(&DATA[2..]);
12 +
    let mid: i32 = sumPair(&DATA[1..3]);
14 13
15 14
    return head + tail + mid;
16 15
}
test/tests/const.string.rad +2 -2
1 1
/// Test string constant lowering.
2 -
constant HELLO: *[u8] = "hello";
2 +
unsafe constant HELLO: *[u8] = "hello";
3 3
4 -
fn getLen() -> u32 {
4 +
unsafe fn getLen() -> u32 {
5 5
    return HELLO.len;
6 6
}
test/tests/const.string.scoped.names.rad +4 -4
1 1
/// Backing string symbols should be declaration-scoped.
2 -
constant HELLO1: *[u8] = "hello";
3 -
constant HELLO2: *[u8] = "hello";
4 -
constant MSGS: [*[u8]; 2] = ["hello", "world"];
2 +
unsafe constant HELLO1: *[u8] = "hello";
3 +
unsafe constant HELLO2: *[u8] = "hello";
4 +
unsafe constant MSGS: [*[u8]; 2] = ["hello", "world"];
5 5
6 -
fn totalLen() -> u32 {
6 +
unsafe fn totalLen() -> u32 {
7 7
    return HELLO1.len + HELLO2.len + MSGS[0].len + MSGS[1].len;
8 8
}
test/tests/const.union.payload.record.array.rad +2 -2
11 11
record Entry {
12 12
    name: *[u8],
13 13
    spec: InstrSpec,
14 14
}
15 15
16 -
constant ENTRIES: [Entry; 4] = [
16 +
unsafe constant ENTRIES: [Entry; 4] = [
17 17
    Entry { name: "empty", spec: InstrSpec::Empty },
18 18
    Entry { name: "small", spec: InstrSpec::Small { flag: true } },
19 19
    Entry { name: "addi", spec: InstrSpec::Imm { opcode: 0x13, funct3: 0, imm: -7 } },
20 20
    Entry { name: "add", spec: InstrSpec::Reg { opcode: 0x33, funct3: 0, funct7: 0 } },
21 21
];
22 22
23 -
@default fn main() -> i32 {
23 +
@default unsafe fn main() -> i32 {
24 24
    match ENTRIES[0].spec {
25 25
        case InstrSpec::Empty => {}
26 26
        else => return 1,
27 27
    }
28 28
    assert ENTRIES[0].name[0] == 'e';
test/tests/data.simple.rad +2 -2
1 1
//! returns: 0
2 2
//! Simple test for data symbol access.
3 3
constant DATA: [u8; 4] = [10, 20, 30, 40];
4 4
5 -
@default fn main() -> i32 {
5 +
@default unsafe fn main() -> i32 {
6 6
    if DATA[0] == 10 and
7 7
       DATA[1] == 20 and
8 8
       DATA[2] == 30 and
9 9
       DATA[3] == 40 and DATA.len == 4 {
10 -
        let ptr = &DATA[..];
10 +
        let ptr = &DATA[..] as *[u8];
11 11
12 12
        if ptr[0] == 10 and
13 13
           ptr[1] == 20 and
14 14
           ptr[2] == 30 and
15 15
           ptr[3] == 40 and ptr.len == 4 {
test/tests/ecall.i64.rad +8 -2
1 1
//! returns: 0
2 2
3 -
@intrinsic fn ecall(number: u32, arg1: i64, arg2: i64, arg3: i64, arg4: i64) -> i64;
3 +
/// Issue a raw environment call for this intrinsic ABI test.
4 +
///
5 +
/// The caller must satisfy the selected system call's ABI, including every
6 +
/// memory extent represented by an integer argument.
7 +
@intrinsic unsafe fn ecall(number: u32, arg1: i64, arg2: i64, arg3: i64, arg4: i64) -> i64;
4 8
5 9
/// Test that ecall can pass and return i64 values.
6 -
@default fn main() -> i32 {
10 +
///
11 +
/// The execution environment must provide writable standard output.
12 +
@default unsafe fn main() -> i32 {
7 13
    // ecall(64, fd, buf, len, 0) = write(fd, buf, len).
8 14
    let msg: *[u8] = "ok\n";
9 15
10 16
    // Write to stdout. The pointer is passed as i64.
11 17
    let n = ecall(64, 1, msg.ptr as i64, msg.len as i64, 0);
test/tests/edge.cases.2.rad +2 -2
3 3
4 4
export record Scanner {
5 5
    source: *[u8],
6 6
}
7 7
8 -
fn peek(s: *Scanner) -> ?u8 {
8 +
fn peek(s: &Scanner) -> ?u8 {
9 9
    if 0 + 0 >= s.source.len {
10 10
        return nil;
11 11
    }
12 12
    return 'a';
13 13
}
14 14
15 -
@default fn main() -> i32 {
15 +
@default unsafe fn main() -> i32 {
16 16
    let s: Scanner = Scanner { source: "xyz" };
17 17
18 18
    if let z = peek(&s) {
19 19
        return 0;
20 20
    }
test/tests/edge.cases.3.rad +4 -4
4 4
export record Scanner {
5 5
    source: *[u8],
6 6
    cursor: u32,
7 7
}
8 8
9 -
fn isEof(s: *Scanner) -> bool {
9 +
fn isEof(s: &Scanner) -> bool {
10 10
    return s.cursor >= s.source.len;
11 11
}
12 12
13 -
export fn current(s: *Scanner) -> ?u8 {
13 +
export fn current(s: &Scanner) -> ?u8 {
14 14
    if isEof(s) {
15 15
        return nil;
16 16
    }
17 17
    return s.source[s.cursor];
18 18
}
19 19
20 -
fn peek(s: *Scanner) -> ?u8 {
20 +
fn peek(s: &Scanner) -> ?u8 {
21 21
    if s.cursor + 1 >= s.source.len {
22 22
        return nil;
23 23
    }
24 24
    return s.source[s.cursor + 1];
25 25
}
26 26
27 -
@default fn main() -> u8 {
27 +
@default unsafe fn main() -> u8 {
28 28
    let s: Scanner = Scanner { source: "abc", cursor: 0 };
29 29
    if let c = current(&s) {
30 30
        return c;
31 31
    }
32 32
    return 1;
test/tests/edge.cases.4.rad +4 -4
18 18
19 19
record Node {
20 20
    value: NodeValue,
21 21
}
22 22
23 -
fn node(nodes: *mut Node, count: *mut u32, value: NodeValue) -> *Node {
23 +
unsafe fn node(nodes: *mut Node, count: &mut u32, value: NodeValue) -> *Node {
24 24
    let index = *count;
25 25
    let slot = nodes + index;
26 26
    set *slot = Node { value };
27 27
    set *count = index + 1;
28 28
    return slot;
29 29
}
30 30
31 -
fn nodeTypeInt(nodes: *mut Node, count: *mut u32, width: u8, sign: Signedness) -> *Node {
31 +
unsafe fn nodeTypeInt(nodes: *mut Node, count: &mut u32, width: u8, sign: Signedness) -> *Node {
32 32
    return node(nodes, count, NodeValue::TypeSig(
33 33
        TypeSig::Integer { width, sign }
34 34
    ));
35 35
}
36 36
37 -
@default fn main() -> i32 {
37 +
@default unsafe fn main() -> i32 {
38 38
    let direct = TypeSig::Integer {
39 39
        width: 4,
40 40
        sign: Signedness::Signed,
41 41
    };
42 42
    let case TypeSig::Integer { width: w1, .. } = direct
43 43
        else return 50;
44 44
45 45
    assert w1 == 4;
46 46
    let mut nodes: [Node; 2] = undefined;
47 -
    let nodesPtr: *mut Node = &mut nodes[0];
47 +
    let nodesPtr: *mut Node = &mut nodes[0] as *mut Node;
48 48
    let mut count: u32 = 0;
49 49
    let typeNode = nodeTypeInt(nodesPtr, &mut count, 4, Signedness::Signed);
50 50
    let case NodeValue::TypeSig(sig) = typeNode.value
51 51
        else return 40;
52 52
test/tests/edge.cases.6.rad +8 -8
25 25
}
26 26
27 27
record Analyzer {
28 28
    pad0: u32,
29 29
    pad1: u32,
30 -
    entries: *mut [Entry],
30 +
    entries: *unsafe mut [Entry],
31 31
    len: u32,
32 32
}
33 33
34 34
static STORAGE: [Entry; 2] = [
35 35
    Entry { a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, g: 0, h: 0, i: 0, j: 0, k: 0, l: 0, m: 0, n: 0, o: 0, p: 0, q: 0, r: 0, s: 0, t: 0 },
55 55
    q: 0x1234560C,
56 56
    r: 0x1234560D,
57 57
    s: 0x1234560E,
58 58
    t: 0x1234560F,
59 59
};
60 -
static ANALYZER: Analyzer = undefined;
60 +
unsafe static ANALYZER: Analyzer = undefined;
61 61
62 -
fn fillEntry(out: *mut Entry) {
62 +
unsafe fn fillEntry(out: *mut Entry) {
63 63
    set *out = DEFAULT_ENTRY;
64 64
}
65 65
66 66
fn mkEntry() -> Entry {
67 67
    return DEFAULT_ENTRY;
72 72
        return nil;
73 73
    }
74 74
    return mkEntry();
75 75
}
76 76
77 -
fn init(entries: *mut [Entry]) {
77 +
unsafe fn init(entries: *unsafe mut [Entry]) {
78 78
    set ANALYZER = Analyzer {
79 79
        pad0: 0xDEADAAA0,
80 80
        pad1: 0xDEADAAA1,
81 81
        entries,
82 82
        len: 0,
83 83
    };
84 84
}
85 85
86 -
fn add() {
86 +
unsafe fn add() {
87 87
    let idx = ANALYZER.len;
88 88
    let entry = mkOptEntry(true) else {
89 89
        return;
90 90
    };
91 91
    set ANALYZER.entries[idx] = entry;
92 92
    set ANALYZER.len = idx + 1;
93 93
}
94 94
95 -
fn checkHeader(expected: *[Entry]) -> i32 {
95 +
unsafe fn checkHeader(expected: *unsafe [Entry]) -> i32 {
96 96
    if ANALYZER.entries.ptr <> expected.ptr or ANALYZER.entries.len <> expected.len {
97 97
        // Slice header got clobbered instead of the backing storage.
98 98
        return 1;
99 99
    }
100 100
    if ANALYZER.len <> 1 {
109 109
        return 3;
110 110
    }
111 111
    return 0;
112 112
}
113 113
114 -
@default fn main() -> i32 {
115 -
    let target = &mut STORAGE[..];
114 +
@default unsafe fn main() -> i32 {
115 +
    let target = &mut STORAGE[..] as *unsafe mut [Entry];
116 116
    init(target);
117 117
    add();
118 118
    return checkHeader(target);
119 119
}
test/tests/edge.cases.7.addr.bug.rad +4 -4
1 1
//! returns: 42
2 2
3 3
record PtrHolder {
4 -
    ptr: *mut i32,
4 +
    ptr: *unsafe mut i32,
5 5
}
6 6
7 7
static target: i32 = 10;
8 -
static holder: PtrHolder = undefined;
8 +
unsafe static holder: PtrHolder = undefined;
9 9
10 -
@default fn main() -> i32 {
11 -
    set holder.ptr = &mut target;
10 +
@default unsafe fn main() -> i32 {
11 +
    set holder.ptr = &mut target as *unsafe mut i32;
12 12
    set *holder.ptr = 42;
13 13
14 14
    return target;
15 15
}
test/tests/edge.cases.8.bug.rad +2 -2
10 10
record Outer {
11 11
    padding: i32,
12 12
    inner: Inner,
13 13
}
14 14
15 -
static global: Outer = undefined;
15 +
unsafe static global: Outer = undefined;
16 16
17 17
fn readInner(i: Inner) -> i32 {
18 18
    return i.value;
19 19
}
20 20
21 -
@default fn main() -> i32 {
21 +
@default unsafe fn main() -> i32 {
22 22
    set global = Outer { padding: 0, inner: Inner { value: 42 } };
23 23
24 24
    assert readInner(global.inner) == 42;
25 25
    return 0;
26 26
}
test/tests/edge.cases.rad +2 -2
2 2
3 3
export record Scanner {
4 4
    source: *[u8],
5 5
}
6 6
7 -
fn peek(s: *Scanner)  -> i32 {
7 +
fn peek(s: &Scanner)  -> i32 {
8 8
    assert 0 + 0 <= s.source.len;
9 9
    return 0;
10 10
}
11 11
12 -
@default fn main() -> i32 {
12 +
@default unsafe fn main() -> i32 {
13 13
    let s: Scanner = Scanner { source: "1" };
14 14
    return peek(&s);
15 15
}
test/tests/error.catch.rad +4 -4
1 1
//! returns: 0
2 2
3 3
union TestError { Fail }
4 4
static PTR_VALUE: i32 = 7;
5 5
6 -
@default fn main() -> u32 {
6 +
@default unsafe fn main() -> u32 {
7 7
    // Catch block with early return
8 8
    let val1: u32 = catchWithReturn(true);
9 9
    assert val1 == 42;
10 10
11 11
    // Catch block with early return (success case)
71 71
72 72
fn returnsErr() -> u32 throws (TestError) {
73 73
    throw TestError::Fail;
74 74
}
75 75
76 -
fn returnsPtrOk() -> *i32 throws (TestError) {
77 -
    return &PTR_VALUE;
76 +
unsafe fn returnsPtrOk() -> *i32 throws (TestError) {
77 +
    return &PTR_VALUE as *i32;
78 78
}
79 79
80 -
fn returnsPtrErr() -> *i32 throws (TestError) {
80 +
unsafe fn returnsPtrErr() -> *i32 throws (TestError) {
81 81
    throw TestError::Fail;
82 82
}
test/tests/error.slice.bounds.rad +1 -2
1 1
//! returns: 133
2 2
//! Test slice bounds checking with runtime EBREAK.
3 3
4 4
@default fn main() -> i32 {
5 5
    let arr: [i32; 3] = [1, 2, 3];
6 -
    let slice: *[i32] = &arr[..];
7 -
    let value: i32 = slice[3];
6 +
    let value: i32 = (&arr[..])[3];
8 7
9 8
    return value;
10 9
}
test/tests/error.try.rad +3 -3
70 70
        throw TestError::Bust;
71 71
    }
72 72
    return 77;
73 73
}
74 74
75 -
fn catchReturn(flag: *mut u32, fail: bool) -> bool {
75 +
fn catchReturn(flag: &mut u32, fail: bool) -> bool {
76 76
    let value: u32 = try maybeReturn(fail) catch {
77 77
        return true;
78 78
    };
79 79
    set *flag = value;
80 80
    return false;
96 96
        set idx += 1;
97 97
    }
98 98
    return total;
99 99
}
100 100
101 -
fn structSuccess(state: *mut ResultSink) -> u32 throws (TestError) {
101 +
fn structSuccess(state: &mut ResultSink) -> u32 throws (TestError) {
102 102
    let value: u32 = try returnsOk();
103 103
    set state.last = value;
104 104
    set state.count += 1;
105 105
    return value;
106 106
}
107 107
108 -
fn structFailure(state: *mut ResultSink) -> u32 throws (TestError) {
108 +
fn structFailure(state: &mut ResultSink) -> u32 throws (TestError) {
109 109
    try returnsErr(1);
110 110
    set state.last = 999;
111 111
    return 999;
112 112
}
113 113
test/tests/field.aggregate.rad +2 -2
19 19
    let r = HasUnion { maybe: MaybeInt::Some(42), z: 99 };
20 20
    return r.maybe;
21 21
}
22 22
23 23
/// Access a slice field and get its length.
24 -
fn accessSliceField() -> u32 {
24 +
unsafe fn accessSliceField() -> u32 {
25 25
    let arr: [i32; 3] = [1, 2, 3];
26 -
    let r = HasSlice { data: &arr[..], z: 77 };
26 +
    let r = HasSlice { data: &arr[..] as *[i32], z: 77 };
27 27
    return r.data.len;
28 28
}
test/tests/fn.callback.nested.rad +5 -6
15 15
        return n + 1;
16 16
    }
17 17
    return current;
18 18
}
19 19
20 -
fn maxRegCallback(reg: Reg, ctx: *mut opaque) {
21 -
    let max = ctx as *mut u32;
22 -
    set *max = maxRegNum(reg.n, *max);
20 +
fn maxRegCallback(reg: Reg, ctx: &mut opaque) {
21 +
    unsafe { set *(ctx as &mut u32) = maxRegNum(reg.n, *(ctx as &mut u32)); }
23 22
}
24 23
25 -
fn withReg(val: Val, callback: fn(Reg, *mut opaque), ctx: *mut opaque) {
24 +
fn withReg(val: Val, callback: fn(Reg, &mut opaque), ctx: &mut opaque) {
26 25
    if let case Val::Reg(r) = val {
27 26
        callback(r, ctx);
28 27
    }
29 28
}
30 29
31 -
fn forEachVal(a: Val, b: Val, c: Val, callback: fn(Reg, *mut opaque), ctx: *mut opaque) {
30 +
fn forEachVal(a: Val, b: Val, c: Val, callback: fn(Reg, &mut opaque), ctx: &mut opaque) {
32 31
    withReg(a, callback, ctx);
33 32
    withReg(b, callback, ctx);
34 33
    withReg(c, callback, ctx);
35 34
}
36 35
40 39
    forEachVal(
41 40
        Val::Reg(Reg { n: 0 }),
42 41
        Val::Reg(Reg { n: 5 }),
43 42
        Val::Reg(Reg { n: 2 }),
44 43
        maxRegCallback,
45 -
        &mut maxReg as *mut opaque
44 +
        &mut maxReg
46 45
    );
47 46
48 47
    // maxReg should be 6 (max register 5 + 1)
49 48
    if maxReg == 6 {
50 49
        return 0;
test/tests/for.else.continue.rad +5 -5
4 4
record Field {
5 5
    name: ?*[u8],
6 6
    value: i32,
7 7
}
8 8
9 -
fn findField(fields: *[Field], target: *[u8]) -> ?i32 {
9 +
unsafe fn findField(fields: *[Field], target: *[u8]) -> ?i32 {
10 10
    for i in 0..fields.len {
11 11
        let name = fields[i].name
12 12
            else continue;
13 13
        if name.len == target.len {
14 14
            let mut eq = true;
23 23
        }
24 24
    }
25 25
    return nil;
26 26
}
27 27
28 -
@default fn main() -> i32 {
28 +
@default unsafe fn main() -> i32 {
29 29
    let mut fields: [Field; 4] = undefined;
30 30
    set fields[0] = Field { name: nil, value: 10 };
31 31
    set fields[1] = Field { name: "foo", value: 20 };
32 32
    set fields[2] = Field { name: nil, value: 30 };
33 33
    set fields[3] = Field { name: "bar", value: 40 };
34 34
35 -
    let v1 = findField(&fields[..], "foo") else {
35 +
    let v1 = findField(&fields[..] as *[Field], "foo") else {
36 36
        return 1;
37 37
    };
38 38
    assert v1 == 20;
39 39
40 -
    let v2 = findField(&fields[..], "bar") else {
40 +
    let v2 = findField(&fields[..] as *[Field], "bar") else {
41 41
        return 3;
42 42
    };
43 43
    assert v2 == 40;
44 44
45 -
    if let _ = findField(&fields[..], "baz") {
45 +
    if let _ = findField(&fields[..] as *[Field], "baz") {
46 46
        return 5;
47 47
    }
48 48
49 49
    return 0;
50 50
}
test/tests/frame.large.rad +4 -4
1 1
//! returns: 0
2 2
//! Test large stack frames and addressing near top of frame.
3 -
fn bigFrame1() -> i32 {
3 +
unsafe fn bigFrame1() -> i32 {
4 4
    let pad: [u8; 2024] = undefined;
5 5
    let n: i32 = 491823;
6 6
7 7
    return n + 1;
8 8
}
9 9
10 -
fn bigFrame2() -> i32 {
10 +
unsafe fn bigFrame2() -> i32 {
11 11
    let pad: [u8; 2028] = undefined;
12 12
    let arr: [i32; 4] = [1, 2, 3, 4];
13 13
14 14
    return arr[0] + arr[1] + arr[3];
15 15
}
16 16
17 -
fn bigFrame3() -> i32 {
17 +
unsafe fn bigFrame3() -> i32 {
18 18
    let mut ary: [u8; 4096] = undefined;
19 19
    set ary[4091] = 192;
20 20
21 21
    return ary[4091] as i32;
22 22
}
23 23
24 -
@default fn main() -> i32 {
24 +
@default unsafe fn main() -> i32 {
25 25
    assert bigFrame1() == 491824;
26 26
    assert bigFrame2() == 7;
27 27
    assert bigFrame3() == 192;
28 28
    return 0;
29 29
}
test/tests/index.eval.order.rad +2 -2
2 2
//! The container expression of a subscript must run before its index expression.
3 3
4 4
static VALUES: [i32; 2] = [1, 42];
5 5
static NEXT_INDEX: u32 = 0;
6 6
7 -
fn selectValues() -> *[i32] {
7 +
unsafe fn selectValues() -> *[i32] {
8 8
    set NEXT_INDEX = 1;
9 9
    return &VALUES[..];
10 10
}
11 11
12 12
fn selectIndex() -> u32 {
13 13
    return NEXT_INDEX;
14 14
}
15 15
16 -
@default fn main() -> i32 {
16 +
@default unsafe fn main() -> i32 {
17 17
    return selectValues()[selectIndex()];
18 18
}
test/tests/index.u8.rad +1 -2
21 21
    // Unsuffixed integer literal index.
22 22
    if arr[1] <> 20 {
23 23
        return 4;
24 24
    }
25 25
    // u8 index into slice.
26 -
    let s = &arr[..];
27 26
    let si: u8 = 1;
28 -
    if s[si] <> 20 {
27 +
    if (&arr[..])[si] <> 20 {
29 28
        return 5;
30 29
    }
31 30
    return 0;
32 31
}
test/tests/intrinsic.ecall.rad +3 -3
1 1
//! Test intrinsic ecall lowering.
2 2
3 -
@intrinsic fn ecall(num: u32, a0: i32, a1: i32, a2: i32, a3: i32) -> i32;
3 +
@intrinsic unsafe fn ecall(num: u32, a0: i64, a1: i64, a2: i64, a3: i64) -> i64;
4 4
5 -
fn test() -> i32 {
6 -
    return ecall(64, 1, 100, 5, 0);
5 +
fn test() -> i64 {
6 +
    unsafe { return ecall(64, 1, 100, 5, 0); }
7 7
}
test/tests/intrinsic.ecall.ril +2 -2
1 -
extern fn w32 $ecall(w32 %0, w32 %1, w32 %2, w32 %3, w32 %4);
1 +
extern fn w64 $ecall(w32 %0, w64 %1, w64 %2, w64 %3, w64 %4);
2 2
3 -
fn w32 $test() {
3 +
fn w64 $test() {
4 4
  @entry0
5 5
    ecall %0 64 1 100 5 0;
6 6
    ret %0;
7 7
}
test/tests/large.blit.store.rad +4 -4
19 19
}
20 20
21 21
/// Store at offset 2200 (> MAX_IMM) into a record field.
22 22
/// Generates `store w32 <imm> %0 2200` in IL, which triggers the
23 23
/// SCRATCH1 aliasing bug when adjustOffset clobbers the value.
24 -
fn storeLargeOffset() -> i32 {
24 +
unsafe fn storeLargeOffset() -> i32 {
25 25
    let mut b: Big = undefined;
26 26
    set b.tag = 42;
27 27
    assert b.tag == 42;
28 28
    set b.tag = 99;
29 29
    assert b.tag == 99;
30 30
    return 0;
31 31
}
32 32
33 33
/// Copy a >2047 byte struct (triggers blit offset overflow).
34 -
fn copyBig() -> i32 {
34 +
unsafe fn copyBig() -> i32 {
35 35
    let mut src: Big = undefined;
36 36
    set src.a[0] = 10;
37 37
    set src.a[1000] = 20;
38 38
    set src.a[2199] = 30;
39 39
    set src.tag = 77;
46 46
    assert dst.tag == 77;
47 47
    return 0;
48 48
}
49 49
50 50
/// Mutate a copy to ensure the blit produced an independent copy.
51 -
fn copyIndependence() -> i32 {
51 +
unsafe fn copyIndependence() -> i32 {
52 52
    let mut a: Big = undefined;
53 53
    set a.a[0] = 1;
54 54
    set a.a[2199] = 2;
55 55
    set a.tag = 100;
56 56
64 64
    assert b.a[2199] == 2;
65 65
    assert b.tag == 200;
66 66
    return 0;
67 67
}
68 68
69 -
@default fn main() -> i32 {
69 +
@default unsafe fn main() -> i32 {
70 70
    let r1 = storeLargeOffset();
71 71
    if r1 <> 0 {
72 72
        return 10 + r1;
73 73
    }
74 74
test/tests/let.guard.rad +6 -6
27 27
    let value = opt else return true;
28 28
29 29
    return false;
30 30
}
31 31
32 -
fn caseGuardSuccess() -> bool {
32 +
unsafe fn caseGuardSuccess() -> bool {
33 33
    let resp: Response = Response::success(9);
34 34
    let case Response::success(inner) = resp else {
35 35
        return false;
36 36
    };
37 37
    return inner == 9;
38 38
}
39 39
40 -
fn caseGuardElseBlk() -> bool {
40 +
unsafe fn caseGuardElseBlk() -> bool {
41 41
    let resp: Response = Response::failure;
42 42
    let case Response::success(inner) = resp else {
43 43
        return true;
44 44
    };
45 45
    return false;
46 46
}
47 47
48 -
fn caseGuardElseStmt() -> bool {
48 +
unsafe fn caseGuardElseStmt() -> bool {
49 49
    let resp: Response = Response::failure;
50 50
    let case Response::success(inner) = resp else return true;
51 51
52 52
    return false;
53 53
}
62 62
    let b = second else return false;
63 63
64 64
    return a == 1 and b == 2;
65 65
}
66 66
67 -
fn caseGuardNoPayload() -> bool {
67 +
unsafe fn caseGuardNoPayload() -> bool {
68 68
    let resp: Response = Response::failure;
69 69
    let case Response::failure = resp else {
70 70
        return false;
71 71
    };
72 72
    return true;
73 73
}
74 74
75 -
fn caseGuardStruct() -> bool {
75 +
unsafe fn caseGuardStruct() -> bool {
76 76
    let resp: Response = Response::other { message: "ouch" };
77 77
    let case Response::other { message } = resp else {
78 78
        return false;
79 79
    };
80 80
    return message.len == 4;
81 81
}
82 82
83 -
@default fn main() -> bool {
83 +
@default unsafe fn main() -> bool {
84 84
    return
85 85
        optionGuardSuccess() and
86 86
        optionGuardElseBlk() and
87 87
        optionGuardElseStmt() and
88 88
        caseGuardSuccess() and
test/tests/let.placeholder.rad +1 -1
3 3
fn placeholder() -> i32 {
4 4
    let _: i32 = 42;
5 5
    return 1;
6 6
}
7 7
8 -
fn placeholderWithSideEffect(ptr: *mut i32) -> i32 {
8 +
fn placeholderWithSideEffect(ptr: &i32) -> i32 {
9 9
    let _ = *ptr;
10 10
    return 0;
11 11
}
12 12
13 13
fn placeholderIgnoreResult() -> i32 {
test/tests/literal.slice.bytes.rad +1 -1
1 1
/// Test byte slice literal with alignment 1.
2 -
fn byteSlice() -> u32 {
2 +
unsafe fn byteSlice() -> u32 {
3 3
    let s: *[u8] = &[65, 66, 67];
4 4
    return s.len;
5 5
}
test/tests/literal.slice.dedup.rad +1 -1
1 1
/// Test that duplicate slice literals are deduplicated into a single data entry.
2 -
fn dedupSlice() -> u32 {
2 +
unsafe fn dedupSlice() -> u32 {
3 3
    let a: *[i32] = &[1, 2, 3];
4 4
    let b: *[i32] = &[1, 2, 3];
5 5
    return a.len + b.len;
6 6
}
test/tests/literal.slice.empty.rad +1 -1
1 1
/// Test empty slice literal.
2 -
fn emptySlice() -> u32 {
2 +
unsafe fn emptySlice() -> u32 {
3 3
    let s: *[i32] = &[];
4 4
    return s.len;
5 5
}
test/tests/literal.slice.multi.rad +1 -1
1 1
/// Test multiple slice literals get unique data names.
2 -
fn multiSlice() -> u32 {
2 +
unsafe fn multiSlice() -> u32 {
3 3
    let a: *[i32] = &[1, 2];
4 4
    let b: *[i32] = &[3, 4, 5];
5 5
    return a.len + b.len;
6 6
}
test/tests/literal.slice.rad +1 -1
1 1
/// Test slice literal creates static data.
2 -
fn sliceLiteral() -> u32 {
2 +
unsafe fn sliceLiteral() -> u32 {
3 3
    let s: *[i32] = &[1, 2, 3];
4 4
    return s.len;
5 5
}
test/tests/literal.slice.record.rad +1 -1
1 1
/// Test slice literal with record elements.
2 2
record Point { x: i32, y: i32 }
3 3
4 -
fn localSliceOfRecords() -> i32 {
4 +
unsafe fn localSliceOfRecords() -> i32 {
5 5
    let s: *[Point] = &[Point { x: 5, y: 6 }, Point { x: 7, y: 8 }];
6 6
    return s[0].x + s[1].y;
7 7
}
test/tests/literal.string.dedup.rad +1 -1
1 1
/// Test that duplicate string literals are deduplicated into a single data entry.
2 -
fn dedupString() -> u32 {
2 +
unsafe fn dedupString() -> u32 {
3 3
    let a = "hello";
4 4
    let b = "hello";
5 5
    return a.len + b.len;
6 6
}
test/tests/literal.string.empty.rad +1 -1
1 1
/// Test empty string literal.
2 -
fn emptyString() -> u32 {
2 +
unsafe fn emptyString() -> u32 {
3 3
    let s = "";
4 4
    return s.len;
5 5
}
test/tests/literal.string.fns.rad +2 -2
1 1
/// Test string data names are prefixed with function name.
2 -
fn first() -> u32 {
2 +
unsafe fn first() -> u32 {
3 3
    let s = "foo";
4 4
    return s.len;
5 5
}
6 6
7 -
fn second() -> u32 {
7 +
unsafe fn second() -> u32 {
8 8
    let s = "bar";
9 9
    return s.len;
10 10
}
test/tests/literal.string.multi.rad +1 -1
1 1
/// Test multiple string literals get unique data names.
2 -
fn multiString() -> u32 {
2 +
unsafe fn multiString() -> u32 {
3 3
    let a = "hello";
4 4
    let b = "world";
5 5
    return a.len + b.len;
6 6
}
test/tests/literal.string.rad +1 -1
1 1
/// Return the length of a string literal.
2 -
fn stringLen() -> u32 {
2 +
unsafe fn stringLen() -> u32 {
3 3
    let s = "hello";
4 4
    return s.len;
5 5
}
test/tests/loc.addr.offset.bug.rad +2 -2
9 9
record Outer {
10 10
    pad: i32,      // offset 0, size 4
11 11
    inner: Inner,  // offset 4, size 4
12 12
}
13 13
14 -
static outer: Outer = undefined;
14 +
unsafe static outer: Outer = undefined;
15 15
16 -
@default fn main() -> i32 {
16 +
@default unsafe fn main() -> i32 {
17 17
    set outer.inner.value = 42;
18 18
19 19
    let ptr: *Inner = &outer.inner;
20 20
21 21
    assert (*ptr).value == 42;
test/tests/loc.addr.opt.to.opt.rad +2 -2
5 5
record Container {
6 6
    pad: i32,           // offset 0
7 7
    opt: ?i32,          // offset 4
8 8
}
9 9
10 -
static container: Container = undefined;
10 +
unsafe static container: Container = undefined;
11 11
12 -
@default fn main() -> i32 {
12 +
@default unsafe fn main() -> i32 {
13 13
    let sourceOpt: ?i32 = 42;
14 14
15 15
    set container.opt = sourceOpt;
16 16
17 17
    if let val = container.opt {
test/tests/loc.addr.optional.assign.rad +2 -2
4 4
record Container {
5 5
    pad: i32,           // offset 0, size 4
6 6
    opt: ?i32,          // offset 4, size 8 (tag + value)
7 7
}
8 8
9 -
static container: Container = undefined;
9 +
unsafe static container: Container = undefined;
10 10
11 -
@default fn main() -> i32 {
11 +
@default unsafe fn main() -> i32 {
12 12
    set container.opt = 42;
13 13
14 14
    if let val = container.opt {
15 15
        assert val == 42;
16 16
        return 0;
test/tests/loc.addr.record.assign.rad +3 -3
9 9
record Outer {
10 10
    pad: i32,      // offset 0, size 4
11 11
    inner: Inner,  // offset 4, size 4
12 12
}
13 13
14 -
static outer: Outer = undefined;
15 -
static source: Inner = undefined;
14 +
unsafe static outer: Outer = undefined;
15 +
unsafe static source: Inner = undefined;
16 16
17 -
@default fn main() -> i32 {
17 +
@default unsafe fn main() -> i32 {
18 18
    set source.value = 42;
19 19
20 20
    set outer.inner = source;
21 21
22 22
    assert outer.inner.value == 42;
test/tests/loop.for.slice.rad +1 -1
1 1
/// Iterates over a slice with index binding and sums elements.
2 -
fn forSliceIndexed(s: *[i32]) -> i32 {
2 +
fn forSliceIndexed(s: &[i32]) -> i32 {
3 3
    let mut sum: i32 = 0;
4 4
    for elem, idx in s {
5 5
        set sum += elem + idx as i32;
6 6
    }
7 7
    return sum;
test/tests/loop.mutable.rad +1 -1
5 5
    }
6 6
    return b;
7 7
}
8 8
9 9
/// Computes the maximum alignment from a slice of alignment values.
10 -
fn computeMaxAlign(items: *[u32]) -> u32 {
10 +
fn computeMaxAlign(items: &[u32]) -> u32 {
11 11
    let mut maxAlign: u32 = 1;
12 12
    for i in 0..items.len {
13 13
        set maxAlign = max(maxAlign, items[i]);
14 14
    }
15 15
    return maxAlign;
test/tests/loop.whilelet.optional.rad +1 -1
1 1
// Returns the iteration count for a single guarded loop.
2 -
fn whileLetOptional(p: ?*i32) -> i32 {
2 +
unsafe fn whileLetOptional(p: ?*i32) -> i32 {
3 3
    let mut steps: i32 = 0;
4 4
    while let _ = p {
5 5
        set steps += 1;
6 6
        break;
7 7
    }
test/tests/lower.const.record.ident.rad +2 -2
6 6
record Entry {
7 7
    name: *[u8],
8 8
    reg: Reg,
9 9
}
10 10
11 -
constant ENTRIES: [Entry; 2] = [
11 +
unsafe constant ENTRIES: [Entry; 2] = [
12 12
    { name: "a0", reg: A0 },
13 13
    { name: "a1", reg: A1 },
14 14
];
15 15
16 -
@default fn main() -> i32 {
16 +
@default unsafe fn main() -> i32 {
17 17
    return ENTRIES.len as i32;
18 18
}
test/tests/lower.private.union.const.rad +2 -2
6 6
record Entry {
7 7
    name: *[u8],
8 8
    kind: Kind,
9 9
}
10 10
11 -
constant ENTRIES: [Entry; 2] = [
11 +
unsafe constant ENTRIES: [Entry; 2] = [
12 12
    { name: "a", kind: Kind::A },
13 13
    { name: "b", kind: Kind::B },
14 14
];
15 15
16 -
@default fn main() -> i32 {
16 +
@default unsafe fn main() -> i32 {
17 17
    return ENTRIES.len as i32;
18 18
}
test/tests/lower.record.scalar.record.const.rad +2 -2
3 3
record Entry {
4 4
    name: *[u8],
5 5
    reg: Reg,
6 6
}
7 7
8 -
constant ENTRIES: [Entry; 2] = [
8 +
unsafe constant ENTRIES: [Entry; 2] = [
9 9
    { name: "a0", reg: Reg(10) },
10 10
    { name: "a1", reg: Reg(11) },
11 11
];
12 12
13 -
@default fn main() -> i32 {
13 +
@default unsafe fn main() -> i32 {
14 14
    return ENTRIES.len as i32;
15 15
}
test/tests/match.mutref.push.rad +3 -3
9 9
union Sealed {
10 10
    No { items: U32List },
11 11
    Yes,
12 12
}
13 13
14 -
fn pushItem(list: *mut U32List, value: u32) {
14 +
unsafe fn pushItem(list: &mut U32List, value: u32) {
15 15
    set list.data[list.len] = value;
16 16
    set list.len += 1;
17 17
}
18 18
19 -
fn addToUnsealedBlock(state: *mut Sealed, value: u32) -> bool {
19 +
unsafe fn addToUnsealedBlock(state: *mut Sealed, value: u32) -> bool {
20 20
    match state {
21 21
        case Sealed::No { items } => {
22 22
            pushItem(items, value);
23 23
            return true;
24 24
        },
26 26
            return false;
27 27
        },
28 28
    }
29 29
}
30 30
31 -
@default fn main() -> i32 {
31 +
@default unsafe fn main() -> i32 {
32 32
    let mut buf: [u32; 8] = undefined;
33 33
    set buf[0] = 0;
34 34
    let mut state = Sealed::No { items: U32List { data: &mut buf[0..8], len: 0 } };
35 35
36 36
    assert addToUnsealedBlock(&mut state, 42);
test/tests/match.mutref.union.rad +1 -1
10 10
record Data {
11 11
    state: State,
12 12
    value: u32,
13 13
}
14 14
15 -
fn process(d: *mut Data) -> u32 {
15 +
fn process(d: &mut Data) -> u32 {
16 16
    match &mut d.state {
17 17
        case State::A { count } => {
18 18
            let c = *count;
19 19
            set *count = c + 1;
20 20
            return c;
test/tests/match.nested.deref.rad +9 -9
10 10
    Some(*Inner),
11 11
    None,
12 12
}
13 13
14 14
/// Match nested union variant through pointer dereference in match/case.
15 -
fn matchDeref(o: Outer) -> i32 {
15 +
unsafe fn matchDeref(o: Outer) -> i32 {
16 16
    match o {
17 17
        case Outer::Some(Inner::A(x)) => {
18 18
            return x;
19 19
        }
20 20
        case Outer::Some(Inner::B) => {
25 25
        }
26 26
    }
27 27
}
28 28
29 29
/// If-let-case with auto-deref nested pattern.
30 -
fn ifLetDeref(o: Outer) -> i32 {
30 +
unsafe fn ifLetDeref(o: Outer) -> i32 {
31 31
    if let case Outer::Some(Inner::A(x)) = o {
32 32
        return x;
33 33
    }
34 34
    return 0;
35 35
}
36 36
37 37
/// Let-else with auto-deref nested pattern.
38 -
fn letElseDeref(o: Outer) -> i32 {
38 +
unsafe fn letElseDeref(o: Outer) -> i32 {
39 39
    let case Outer::Some(Inner::A(x)) = o
40 40
        else { return -1; };
41 41
    return x;
42 42
}
43 43
51 51
    Some { c: Container },
52 52
    None,
53 53
}
54 54
55 55
/// Nested record with auto-deref on a pointer field.
56 -
fn matchRecordDeref(b: Boxed) -> i32 {
56 +
unsafe fn matchRecordDeref(b: Boxed) -> i32 {
57 57
    match b {
58 58
        case Boxed::Some { c: Container { inner: Inner::A(val), tag } } => {
59 59
            return val + tag;
60 60
        }
61 61
        else => {
63 63
        }
64 64
    }
65 65
}
66 66
67 67
/// Auto-deref through pointer in if-let-case with record.
68 -
fn ifLetRecordDeref(b: Boxed) -> i32 {
68 +
unsafe fn ifLetRecordDeref(b: Boxed) -> i32 {
69 69
    if let case Boxed::Some { c: Container { inner: Inner::A(val), tag } } = b {
70 70
        return val + tag;
71 71
    }
72 72
    return 0;
73 73
}
74 74
75 75
/// Void variant through pointer deref.
76 -
fn matchDerefVoid(o: Outer) -> i32 {
76 +
unsafe fn matchDerefVoid(o: Outer) -> i32 {
77 77
    if let case Outer::Some(Inner::B) = o {
78 78
        return 1;
79 79
    }
80 80
    return 0;
81 81
}
82 82
83 83
/// Auto-deref with placeholder in nested pattern.
84 -
fn matchDerefPlaceholder(o: Outer) -> i32 {
84 +
unsafe fn matchDerefPlaceholder(o: Outer) -> i32 {
85 85
    if let case Outer::Some(Inner::A(_)) = o {
86 86
        return 1;
87 87
    }
88 88
    return 0;
89 89
}
97 97
union Holder {
98 98
    Ptr { p: *Point, z: i32 },
99 99
    Empty,
100 100
}
101 101
102 -
fn derefRecordField(h: Holder) -> i32 {
102 +
unsafe fn derefRecordField(h: Holder) -> i32 {
103 103
    if let case Holder::Ptr { p: Point { x, y }, z } = h {
104 104
        return x + y + z;
105 105
    }
106 106
    return 0;
107 107
}
108 108
109 -
@default fn main() -> i32 {
109 +
@default unsafe fn main() -> i32 {
110 110
    let innerA = Inner::A(42);
111 111
    let innerB = Inner::B;
112 112
113 113
    // matchDeref
114 114
    assert matchDeref(Outer::Some(&innerA)) == 42;
test/tests/match.nested.whilelet.rad +15 -15
9 9
union Opt {
10 10
    Some { pair: Pair },
11 11
    None,
12 12
}
13 13
14 -
fn get(items: *[Opt], idx: u32) -> Opt {
14 +
fn get(items: &[Opt], idx: u32) -> Opt {
15 15
    if idx < items.len {
16 16
        return items[idx];
17 17
    }
18 18
    return Opt::None;
19 19
}
20 20
21 21
/// While-let with nested record destructuring.
22 -
fn sumPairs(items: *[Opt]) -> i32 {
22 +
fn sumPairs(items: &[Opt]) -> i32 {
23 23
    let mut sum: i32 = 0;
24 24
    let mut i: u32 = 0;
25 25
    while let case Opt::Some { pair: Pair { a, b } } = get(items, i) {
26 26
        set sum = sum + a + b;
27 27
        set i = i + 1;
28 28
    }
29 29
    return sum;
30 30
}
31 31
32 32
/// While-let with nested record and guard.
33 -
fn sumPositive(items: *[Opt]) -> i32 {
33 +
fn sumPositive(items: &[Opt]) -> i32 {
34 34
    let mut sum: i32 = 0;
35 35
    let mut i: u32 = 0;
36 36
    while let case Opt::Some { pair: Pair { a, b } } = get(items, i); a > 0 {
37 37
        set sum = sum + a + b;
38 38
        set i = i + 1;
45 45
union Command {
46 46
    Move { dir: Dir, speed: i32 },
47 47
    Stop,
48 48
}
49 49
50 -
fn getCmd(items: *[Command], idx: u32) -> Command {
50 +
fn getCmd(items: &[Command], idx: u32) -> Command {
51 51
    if idx < items.len {
52 52
        return items[idx];
53 53
    }
54 54
    return Command::Stop;
55 55
}
56 56
57 57
/// While-let with nested union variant pattern.
58 -
fn sumNorthSpeeds(items: *[Command]) -> i32 {
58 +
fn sumNorthSpeeds(items: &[Command]) -> i32 {
59 59
    let mut sum: i32 = 0;
60 60
    let mut i: u32 = 0;
61 61
    while let case Command::Move { dir: Dir::North, speed } = getCmd(items, i) {
62 62
        set sum = sum + speed;
63 63
        set i = i + 1;
65 65
    return sum;
66 66
}
67 67
68 68
@default fn main() -> i32 {
69 69
    // sumPairs
70 -
    let items: *[Opt] = &[
70 +
    let items: [Opt; 2] = [
71 71
        Opt::Some { pair: Pair { a: 1, b: 2 } },
72 72
        Opt::Some { pair: Pair { a: 3, b: 4 } },
73 73
    ];
74 -
    assert sumPairs(items) == 10;
74 +
    assert sumPairs(&items[..]) == 10;
75 75
76 76
    // sumPairs: empty
77 -
    let empty: *[Opt] = &[];
78 -
    assert sumPairs(empty) == 0;
77 +
    let empty: [Opt; 0] = [];
78 +
    assert sumPairs(&empty[..]) == 0;
79 79
80 80
    // sumPositive: stops at first non-positive a.
81 -
    let mixed: *[Opt] = &[
81 +
    let mixed: [Opt; 3] = [
82 82
        Opt::Some { pair: Pair { a: 3, b: 4 } },
83 83
        Opt::Some { pair: Pair { a: -1, b: 5 } },
84 84
        Opt::Some { pair: Pair { a: 2, b: 6 } },
85 85
    ];
86 -
    assert sumPositive(mixed) == 7;
86 +
    assert sumPositive(&mixed[..]) == 7;
87 87
88 88
    // sumNorthSpeeds
89 -
    let cmds: *[Command] = &[
89 +
    let cmds: [Command; 2] = [
90 90
        Command::Move { dir: Dir::North, speed: 5 },
91 91
        Command::Move { dir: Dir::North, speed: 10 },
92 92
    ];
93 -
    assert sumNorthSpeeds(cmds) == 15;
93 +
    assert sumNorthSpeeds(&cmds[..]) == 15;
94 94
95 95
    // sumNorthSpeeds: stops at South.
96 -
    let cmds2: *[Command] = &[
96 +
    let cmds2: [Command; 3] = [
97 97
        Command::Move { dir: Dir::North, speed: 3 },
98 98
        Command::Move { dir: Dir::South, speed: 7 },
99 99
        Command::Move { dir: Dir::North, speed: 9 },
100 100
    ];
101 -
    assert sumNorthSpeeds(cmds2) == 3;
101 +
    assert sumNorthSpeeds(&cmds2[..]) == 3;
102 102
103 103
    return 0;
104 104
}
test/tests/match.optional.rad +1 -1
1 1
// Returns 1 when the optional pointer is present, otherwise returns 0.
2 -
fn matchOptional(p: ?*i32) -> i32 {
2 +
unsafe fn matchOptional(p: ?*i32) -> i32 {
3 3
    match p {
4 4
        case nil => return 0,
5 5
        x => return 1,
6 6
    }
7 7
}
test/tests/match.optional.ref.rad +1 -1
1 1
/// Match an optional aggregate through a reference. The value binding is a pointer.
2 -
fn matchOptionalRef(ptr: *?u32) -> u32 {
2 +
fn matchOptionalRef(ptr: &?u32) -> u32 {
3 3
    match ptr {
4 4
        value => return *value,
5 5
        case nil => return 0,
6 6
    }
7 7
}
test/tests/match.string.rad +5 -5
1 1
//! returns: 0
2 2
//! Test string literal patterns in match statements.
3 3
4 4
/// Top-level string match.
5 -
fn classify(s: *[u8]) -> i32 {
5 +
unsafe fn classify(s: *[u8]) -> i32 {
6 6
    match s {
7 7
        case "red" => {
8 8
            return 1;
9 9
        }
10 10
        case "green" => {
23 23
union Cmd {
24 24
    Say { msg: *[u8], count: i32 },
25 25
    Quit,
26 26
}
27 27
28 -
fn dispatch(c: Cmd) -> i32 {
28 +
unsafe fn dispatch(c: Cmd) -> i32 {
29 29
    match c {
30 30
        case Cmd::Say { msg: "hello", count } => {
31 31
            return count;
32 32
        }
33 33
        case Cmd::Say { msg: "bye", count } => {
38 38
        }
39 39
    }
40 40
}
41 41
42 42
/// String pattern in if-let-case.
43 -
fn isHello(c: Cmd) -> bool {
43 +
unsafe fn isHello(c: Cmd) -> bool {
44 44
    if let case Cmd::Say { msg: "hello", .. } = c {
45 45
        return true;
46 46
    }
47 47
    return false;
48 48
}
49 49
50 50
/// String pattern in let-else.
51 -
fn extractBye(c: Cmd) -> i32 {
51 +
unsafe fn extractBye(c: Cmd) -> i32 {
52 52
    let case Cmd::Say { msg: "bye", count } = c
53 53
        else { return -1; };
54 54
    return count;
55 55
}
56 56
57 -
@default fn main() -> i32 {
57 +
@default unsafe fn main() -> i32 {
58 58
    // classify
59 59
    assert classify("red") == 1;
60 60
    assert classify("green") == 2;
61 61
    assert classify("blue") == 3;
62 62
    assert classify("other") == 0;
test/tests/memzero.union.bug.rad +1 -1
10 10
    guard1: u16,
11 11
    val: Payload,
12 12
    guard2: u32,
13 13
}
14 14
15 -
@default fn main() -> i32 {
15 +
@default unsafe fn main() -> i32 {
16 16
    let mut f: Frame = Frame { guard1: 0xDEAD, val: undefined, guard2: 0xDEADBEEF };
17 17
18 18
    set f.val = Payload::Small(7);
19 19
    set f.val = Payload::Big([1, 2, 3]);
20 20
test/tests/method.basic.rad +4 -4
4 4
record Point {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 -
fn (p: *Point) sum() -> i32 {
9 +
unsafe fn (p: *Point) sum() -> i32 {
10 10
    return p.x + p.y;
11 11
}
12 12
13 -
fn (p: *mut Point) translate(dx: i32, dy: i32) {
13 +
unsafe fn (p: *mut Point) translate(dx: i32, dy: i32) {
14 14
    set p.x = p.x + dx;
15 15
    set p.y = p.y + dy;
16 16
}
17 17
18 -
@default fn main() -> i32 {
18 +
@default unsafe fn main() -> i32 {
19 19
    let mut pt = Point { x: 3, y: 4 };
20 20
21 21
    // Call immutable method.
22 22
    assert pt.sum() == 7;
23 23
25 25
    pt.translate(10, 20);
26 26
    assert pt.x == 13;
27 27
    assert pt.y == 24;
28 28
29 29
    // Call via pointer.
30 -
    let ptr = &pt;
30 +
    let ptr: *Point = &pt;
31 31
    assert ptr.sum() == 37;
32 32
33 33
    return 0;
34 34
}
test/tests/method.chain.rad +4 -4
4 4
record Builder {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 -
fn (b: *mut Builder) setX(x: i32) -> *mut Builder {
9 +
unsafe fn (b: *mut Builder) setX(x: i32) -> *mut Builder {
10 10
    set b.x = x;
11 11
    return b;
12 12
}
13 13
14 -
fn (b: *mut Builder) setY(y: i32) -> *mut Builder {
14 +
unsafe fn (b: *mut Builder) setY(y: i32) -> *mut Builder {
15 15
    set b.y = y;
16 16
    return b;
17 17
}
18 18
19 -
fn (b: *Builder) sum() -> i32 {
19 +
unsafe fn (b: *Builder) sum() -> i32 {
20 20
    return b.x + b.y;
21 21
}
22 22
23 -
@default fn main() -> i32 {
23 +
@default unsafe fn main() -> i32 {
24 24
    let mut b = Builder { x: 0, y: 0 };
25 25
26 26
    // Chain method calls.
27 27
    let p = b.setX(10).setY(20);
28 28
    assert p.sum() == 30;
test/tests/method.multiple.rad +4 -4
4 4
record Vec2 {
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 -
fn (v: *Vec2) magnitudeSq() -> i32 {
9 +
fn (v: &Vec2) magnitudeSq() -> i32 {
10 10
    return v.x * v.x + v.y * v.y;
11 11
}
12 12
13 -
fn (v: *Vec2) dot(other: *Vec2) -> i32 {
13 +
fn (v: &Vec2) dot(other: &Vec2) -> i32 {
14 14
    return v.x * other.x + v.y * other.y;
15 15
}
16 16
17 -
fn (v: *mut Vec2) add(other: *Vec2) {
17 +
fn (v: &mut Vec2) add(other: &Vec2) {
18 18
    set v.x = v.x + other.x;
19 19
    set v.y = v.y + other.y;
20 20
}
21 21
22 -
fn (v: *mut Vec2) scale(factor: i32) {
22 +
fn (v: &mut Vec2) scale(factor: i32) {
23 23
    set v.x = v.x * factor;
24 24
    set v.y = v.y * factor;
25 25
}
26 26
27 27
@default fn main() -> i32 {
test/tests/method.ptr.rad +5 -5
3 3
4 4
record Counter {
5 5
    value: i32,
6 6
}
7 7
8 -
fn (c: *Counter) get() -> i32 {
8 +
unsafe fn (c: *Counter) get() -> i32 {
9 9
    return c.value;
10 10
}
11 11
12 -
fn (c: *mut Counter) inc() {
12 +
unsafe fn (c: *mut Counter) inc() {
13 13
    set c.value = c.value + 1;
14 14
}
15 15
16 -
@default fn main() -> i32 {
16 +
@default unsafe fn main() -> i32 {
17 17
    let mut c = Counter { value: 0 };
18 18
19 19
    // Direct call on value.
20 20
    assert c.get() == 0;
21 21
22 22
    // Mutable method on value.
23 23
    c.inc();
24 24
    assert c.get() == 1;
25 25
26 26
    // Call via immutable pointer.
27 -
    let p = &c;
27 +
    let p: *Counter = &c;
28 28
    assert p.get() == 1;
29 29
30 30
    // Call via mutable pointer.
31 -
    let mp = &mut c;
31 +
    let mp: *mut Counter = &mut c;
32 32
    mp.inc();
33 33
    assert mp.get() == 2;
34 34
35 35
    // Original value also updated.
36 36
    assert c.get() == 2;
test/tests/method.pub.rad +1 -1
3 3
4 4
record Foo {
5 5
    x: i32,
6 6
}
7 7
8 -
export fn (f: *Foo) getX() -> i32 {
8 +
export fn (f: &Foo) getX() -> i32 {
9 9
    return f.x;
10 10
}
11 11
12 12
@default fn main() -> i32 {
13 13
    let f = Foo { x: 42 };
test/tests/method.return.rad +4 -4
4 4
record Pair {
5 5
    a: i32,
6 6
    b: i32,
7 7
}
8 8
9 -
fn (p: *Pair) sum() -> i32 {
9 +
fn (p: &Pair) sum() -> i32 {
10 10
    return p.a + p.b;
11 11
}
12 12
13 -
fn (p: *Pair) swapped() -> Pair {
13 +
fn (p: &Pair) swapped() -> Pair {
14 14
    return Pair { a: p.b, b: p.a };
15 15
}
16 16
17 -
fn (p: *Pair) firstPtr() -> *i32 {
17 +
unsafe fn (p: *Pair) firstPtr() -> *i32 {
18 18
    return &p.a;
19 19
}
20 20
21 -
@default fn main() -> i32 {
21 +
@default unsafe fn main() -> i32 {
22 22
    let pair = Pair { a: 10, b: 20 };
23 23
24 24
    // Scalar return.
25 25
    assert pair.sum() == 30;
26 26
test/tests/method.throws.rad +2 -2
8 8
record Parser {
9 9
    pos: i32,
10 10
    len: i32,
11 11
}
12 12
13 -
fn (p: *mut Parser) advance() throws (ParseError) {
13 +
fn (p: &mut Parser) advance() throws (ParseError) {
14 14
    if p.pos >= p.len {
15 15
        throw ParseError::Invalid;
16 16
    }
17 17
    set p.pos = p.pos + 1;
18 18
}
19 19
20 -
fn (p: *Parser) remaining() -> i32 {
20 +
fn (p: &Parser) remaining() -> i32 {
21 21
    return p.len - p.pos;
22 22
}
23 23
24 24
@default fn main() -> i32 {
25 25
    let mut parser = Parser { pos: 0, len: 2 };
test/tests/method.union.rad +1 -1
4 4
union Shape {
5 5
    Circle(i32),
6 6
    Rect { w: i32, h: i32 },
7 7
}
8 8
9 -
fn (s: *Shape) isCircle() -> bool {
9 +
fn (s: &Shape) isCircle() -> bool {
10 10
    match *s {
11 11
        case Shape::Circle(_) => return true,
12 12
        else => return false,
13 13
    }
14 14
}
test/tests/method.with.trait.rad +4 -4
5 5
    x: i32,
6 6
    y: i32,
7 7
}
8 8
9 9
// Standalone method.
10 -
fn (w: *Widget) area() -> i32 {
10 +
unsafe fn (w: *Widget) area() -> i32 {
11 11
    return w.x * w.y;
12 12
}
13 13
14 14
// Trait with its own method.
15 15
trait Printable {
16 -
    fn (*Printable) code() -> i32;
16 +
    unsafe fn (*Printable) code() -> i32;
17 17
}
18 18
19 19
instance Printable for Widget {
20 -
    fn (w: *Widget) code() -> i32 {
20 +
    unsafe fn (w: *Widget) code() -> i32 {
21 21
        return w.x + w.y;
22 22
    }
23 23
}
24 24
25 -
@default fn main() -> i32 {
25 +
@default unsafe fn main() -> i32 {
26 26
    let w = Widget { x: 3, y: 5 };
27 27
28 28
    // Standalone method call.
29 29
    assert w.area() == 15;
30 30
test/tests/mutref.call.result.rad +2 -2
3 3
4 4
record Box {
5 5
    x: i32,
6 6
}
7 7
8 -
fn idBox(b: *mut Box) -> *mut Box {
8 +
unsafe fn idBox(b: *mut Box) -> *mut Box {
9 9
    return b;
10 10
}
11 11
12 -
@default fn main() -> i32 {
12 +
@default unsafe fn main() -> i32 {
13 13
    let mut b = Box { x: 1 };
14 14
15 15
    let px: *mut i32 = &mut idBox(&mut b).x;
16 16
    set *px = 9;
17 17
test/tests/mutref.loop.bug.rad +5 -5
8 8
//!
9 9
//! The critical case is when the loop executes zero iterations: the merge
10 10
//! block tries to `load` through the initial integer value (not a valid
11 11
//! pointer), crashing the program.
12 12
13 -
fn store(ptr: *mut u32, val: u32) {
13 +
unsafe fn store(ptr: *mut u32, val: u32) {
14 14
    set *ptr = val;
15 15
}
16 16
17 17
/// Zero-iteration loop with &mut inside the body.
18 18
/// Without the fix, `val` starts as integer 42 in SSA, but the post-loop
19 19
/// merge tries to `load` through it as if it were a pointer.
20 -
fn testZeroIter(n: u32) -> u32 {
20 +
unsafe fn testZeroIter(n: u32) -> u32 {
21 21
    let mut val: u32 = 42;
22 22
    let mut i: u32 = 0;
23 23
    while i < n {
24 24
        store(&mut val, val + 1);
25 25
        set i += 1;
26 26
    }
27 27
    return val;
28 28
}
29 29
30 30
/// Multiple iterations: accumulate via &mut pointer in a loop.
31 -
fn testMultiIter() -> u32 {
31 +
unsafe fn testMultiIter() -> u32 {
32 32
    let mut acc: u32 = 0;
33 33
    let mut i: u32 = 0;
34 34
    while i < 5 {
35 35
        store(&mut acc, acc + i);
36 36
        set i += 1;
37 37
    }
38 38
    return acc;
39 39
}
40 40
41 41
/// Multiple address-taken variables in the same loop.
42 -
fn testMultipleVars() -> u32 {
42 +
unsafe fn testMultipleVars() -> u32 {
43 43
    let mut a: u32 = 0;
44 44
    let mut b: u32 = 100;
45 45
    let mut i: u32 = 0;
46 46
    while i < 3 {
47 47
        store(&mut a, a + 1);
49 49
        set i += 1;
50 50
    }
51 51
    return a + b;
52 52
}
53 53
54 -
@default fn main() -> i32 {
54 +
@default unsafe fn main() -> i32 {
55 55
    // Zero iterations: the critical regression case.
56 56
    assert testZeroIter(0) == 42;
57 57
    // Non-zero iterations still work.
58 58
    assert testZeroIter(3) == 45;
59 59
    // Accumulation: 0+0+1+2+3+4 = 10
test/tests/mutref.loop.rad +3 -4
1 -
fn callback(val: u32, ctx: *mut opaque) {
2 -
    let max = ctx as *mut u32;
3 -
    set *max = val;
1 +
fn callback(val: u32, ctx: &mut opaque) {
2 +
    unsafe { set *(ctx as &mut u32) = val; }
4 3
}
5 4
6 5
fn test() -> i32 {
7 6
    let mut maxReg: u32 = 0;
8 7
    let mut i: u32 = 0;
9 8
    while i < 3 {
10 -
        callback(i, &mut maxReg as *mut opaque);
9 +
        callback(i, &mut maxReg);
11 10
        set i += 1;
12 11
    }
13 12
    return maxReg as i32;
14 13
}
test/tests/mutref.scalar.rad +1 -1
1 -
fn modify(counter: *mut i32, ret: bool) -> bool {
1 +
fn modify(counter: &mut i32, ret: bool) -> bool {
2 2
    set *counter += 1;
3 3
    return ret;
4 4
}
5 5
6 6
fn test() -> i32 {
test/tests/nil.cmp.rad +2 -2
9 9
fn nilNeNil() -> bool {
10 10
    return nil <> nil;
11 11
}
12 12
13 13
// nil == opt (reversed order, pointer optional)
14 -
fn nilEqOptPtr(a: ?*i32) -> bool {
14 +
unsafe fn nilEqOptPtr(a: ?*i32) -> bool {
15 15
    return nil == a;
16 16
}
17 17
18 18
// nil <> opt (reversed order, pointer optional)
19 -
fn nilNeOptPtr(a: ?*i32) -> bool {
19 +
unsafe fn nilNeOptPtr(a: ?*i32) -> bool {
20 20
    return nil <> a;
21 21
}
22 22
23 23
// nil == opt (reversed order, aggregate optional)
24 24
fn nilEqOptAgg(a: ?i32) -> bool {
test/tests/opt.nil.check.rad +4 -4
2 2
//! Test nil check for optional pointers and slices.
3 3
//! Ensure that nil checks compare the full pointer width, not just the low byte.
4 4
5 5
/// Return a pointer whose low byte is zero but is non-null.
6 6
/// This tests that nil checks use W64, not W8.
7 -
fn makeAlignedPtr() -> *u8 {
7 +
unsafe fn makeAlignedPtr() -> *u8 {
8 8
    let arr: [u8; 512] = undefined;
9 9
    // Find an address within arr whose low byte is 0x00.
10 10
    let base: u64 = &arr[0] as u64;
11 11
    let offset: u64 = 256 - (base % 256);
12 12
    return &arr[offset as u32] as *u8;
13 13
}
14 14
15 15
/// Test: optional pointer nil check must use full 64-bit comparison.
16 -
fn testOptionalPtrNilCheck() -> i32 {
16 +
unsafe fn testOptionalPtrNilCheck() -> i32 {
17 17
    let p = makeAlignedPtr();
18 18
    let opt: ?*u8 = p;
19 19
20 20
    // The pointer is not nil, but its low byte is 0x00.
21 21
    // A W8 comparison would wrongly say it's nil.
26 26
    }
27 27
    return 2;
28 28
}
29 29
30 30
/// Test: optional slice nil check must use full 64-bit comparison.
31 -
fn testOptionalSliceNilCheck() -> i32 {
31 +
unsafe fn testOptionalSliceNilCheck() -> i32 {
32 32
    let arr: [u8; 512] = undefined;
33 33
    let base: u64 = &arr[0] as u64;
34 34
    let offset: u64 = 256 - (base % 256);
35 35
    // Create a slice starting at an address whose low byte is 0.
36 36
    let s: *[u8] = &arr[offset as u32 ..];
41 41
        return 0;
42 42
    }
43 43
    return 2;
44 44
}
45 45
46 -
@default fn main() -> i32 {
46 +
@default unsafe fn main() -> i32 {
47 47
    let r1 = testOptionalPtrNilCheck();
48 48
    if r1 <> 0 {
49 49
        return r1;
50 50
    }
51 51
    let r2 = testOptionalSliceNilCheck();
test/tests/opt.ptr.return.nil.rad +1 -1
1 1
// Returns a nil optional pointer.
2 -
fn optPtrReturnNil() -> ?*i32 {
2 +
unsafe fn optPtrReturnNil() -> ?*i32 {
3 3
    return nil;
4 4
}
test/tests/opt.slice.npo.rad +11 -11
1 1
//! returns: 0
2 2
//! Test null pointer optimization for optional slices (?*[T]).
3 3
//! Optional slices should have the same size as slices (16 bytes),
4 4
//! using a null data pointer to represent `nil`.
5 5
6 -
fn checkSizes() -> u8 {
6 +
unsafe fn checkSizes() -> u8 {
7 7
    // ?*[T] should be the same size as *[T] (16 bytes, not 24).
8 8
    assert @sizeOf(?*[u8]) == 16;
9 9
    assert @alignOf(?*[u8]) == 8;
10 10
    assert @sizeOf(?*[u16]) == 16;
11 11
    assert @sizeOf(?*mut [u8]) == 16;
12 12
    return 0;
13 13
}
14 14
15 -
fn checkNil() -> u8 {
15 +
unsafe fn checkNil() -> u8 {
16 16
    let x: ?*[u8] = nil;
17 17
    assert x == nil;
18 18
    return 0;
19 19
}
20 20
21 -
fn checkWrap() -> u8 {
21 +
unsafe fn checkWrap() -> u8 {
22 22
    let arr: [u8; 3] = [1, 2, 3];
23 23
    let s = &arr[..];
24 24
    let opt: ?*[u8] = s;
25 25
26 26
    assert opt <> nil;
27 27
    return 0;
28 28
}
29 29
30 -
fn checkIfLet() -> u8 {
30 +
unsafe fn checkIfLet() -> u8 {
31 31
    let arr: [u8; 3] = [10, 20, 30];
32 32
    let s = &arr[..];
33 33
    let opt: ?*[u8] = s;
34 34
35 35
    if let val = opt {
45 45
        return 35;
46 46
    }
47 47
    return 0;
48 48
}
49 49
50 -
fn checkLetElse() -> u8 {
50 +
unsafe fn checkLetElse() -> u8 {
51 51
    let arr: [u8; 3] = [10, 20, 30];
52 52
    let s = &arr[..];
53 53
    let opt: ?*[u8] = s;
54 54
55 55
    let val = opt else {
57 57
    };
58 58
    assert val.len == 3;
59 59
    return 0;
60 60
}
61 61
62 -
fn returnNil() -> ?*[u8] {
62 +
unsafe fn returnNil() -> ?*[u8] {
63 63
    return nil;
64 64
}
65 65
66 -
fn returnSome() -> ?*[u8] {
66 +
unsafe fn returnSome() -> ?*[u8] {
67 67
    let arr: [u8; 2] = [42, 99];
68 68
    return &arr[..];
69 69
}
70 70
71 -
fn checkReturn() -> u8 {
71 +
unsafe fn checkReturn() -> u8 {
72 72
    let a = returnNil();
73 73
    assert a == nil;
74 74
    let b = returnSome();
75 75
    assert b <> nil;
76 76
    if let val = b {
79 79
        return 53;
80 80
    }
81 81
    return 0;
82 82
}
83 83
84 -
fn checkMatch() -> u8 {
84 +
unsafe fn checkMatch() -> u8 {
85 85
    let arr: [u8; 2] = [5, 6];
86 86
    let s = &arr[..];
87 87
    let opt: ?*[u8] = s;
88 88
89 89
    match opt {
101 101
        }
102 102
    }
103 103
    return 0;
104 104
}
105 105
106 -
fn checkEq() -> u8 {
106 +
unsafe fn checkEq() -> u8 {
107 107
    let a: ?*[u8] = nil;
108 108
    let b: ?*[u8] = nil;
109 109
110 110
    // nil == nil
111 111
    assert a == b;
122 122
    assert c == d;
123 123
124 124
    return 0;
125 125
}
126 126
127 -
@default fn main() -> u8 {
127 +
@default unsafe fn main() -> u8 {
128 128
    let r1 = checkSizes();
129 129
    if r1 <> 0 {
130 130
        return r1;
131 131
    }
132 132
    let r2 = checkNil();
test/tests/optional.ptr.eq.rad +4 -4
1 1
// Test optional pointer equality (fast path in lowerOptionalEq)
2 2
3 -
fn optPtrEq(a: ?*i32, b: ?*i32) -> bool {
3 +
unsafe fn optPtrEq(a: ?*i32, b: ?*i32) -> bool {
4 4
    return a == b;
5 5
}
6 6
7 -
fn optPtrNeq(a: ?*i32, b: ?*i32) -> bool {
7 +
unsafe fn optPtrNeq(a: ?*i32, b: ?*i32) -> bool {
8 8
    return a <> b;
9 9
}
10 10
11 -
fn optPtrEqNil(a: ?*i32) -> bool {
11 +
unsafe fn optPtrEqNil(a: ?*i32) -> bool {
12 12
    return a == nil;
13 13
}
14 14
15 -
fn optPtrNeqNil(a: ?*i32) -> bool {
15 +
unsafe fn optPtrNeqNil(a: ?*i32) -> bool {
16 16
    return a <> nil;
17 17
}
test/tests/pointer.copy.edge.case.rad +3 -3
17 17
record Parser {
18 18
    nodes: [Node; 1],
19 19
    count: u32,
20 20
}
21 21
22 -
fn makeNode(p: *mut Parser, kind: NodeKind) -> *mut Node {
22 +
unsafe fn makeNode(p: *mut Parser, kind: NodeKind) -> *mut Node {
23 23
    let idx: u32 = p.count;
24 24
    set p.nodes[idx] = Node {
25 25
        span: Span { length: 0 },
26 26
        kind,
27 27
    };
28 28
    set p.count = idx + 1;
29 29
    return &mut p.nodes[idx];
30 30
}
31 31
32 -
fn setLen(n: *mut Node, len: u32) {
32 +
unsafe fn setLen(n: *mut Node, len: u32) {
33 33
    set n.span.length = len;
34 34
}
35 35
36 -
@default fn main() -> i32 {
36 +
@default unsafe fn main() -> i32 {
37 37
    let mut parser: Parser = Parser {
38 38
        nodes: [Node {
39 39
            span: Span { length: 0 },
40 40
            kind: NodeKind::Placeholder,
41 41
        }; 1],
test/tests/pointer.slice.index.rad +1 -1
4 4
    ptr: *[i32],
5 5
    zero: u32,
6 6
}
7 7
8 8
// Test that pointer-to-slice indexing correctly dereferences the slice header.
9 -
@default fn main() -> i32 {
9 +
@default unsafe fn main() -> i32 {
10 10
    let arr: [i32; 2] = [5, 7];
11 11
    let h = Holder { ptr: &arr[..], zero: 0 };
12 12
13 13
    return (h.ptr[1]) - 7;
14 14
}
test/tests/pointer.slice.store.rad +4 -4
16 16
17 17
record PtrBox {
18 18
    ptr: *mut *mut [Entry],
19 19
}
20 20
21 -
static STORAGE: [Entry; 2] = undefined;
22 -
static TABLE: Table = undefined;
23 -
static HOLDER: PtrBox = undefined;
21 +
unsafe static STORAGE: [Entry; 2] = undefined;
22 +
unsafe static TABLE: Table = undefined;
23 +
unsafe static HOLDER: PtrBox = undefined;
24 24
25 -
@default fn main() -> i32 {
25 +
@default unsafe fn main() -> i32 {
26 26
    set TABLE.entries = &mut STORAGE[..];
27 27
    set TABLE.len     = 0;
28 28
29 29
    set HOLDER.ptr = &mut TABLE.entries;
30 30
test/tests/pointerfn.rad +1 -1
1 -
fn takePointer(p: *i32) -> *i32 {
1 +
unsafe fn takePointer(p: *i32) -> *i32 {
2 2
    return p;
3 3
}
test/tests/prog.bignum.rad +18 -18
6 6
7 7
/// Number of limbs per big number (128 bits = 4 x 32-bit words).
8 8
constant LIMBS: u32 = 4;
9 9
10 10
/// Set a big number to a u32 value.
11 -
fn bnFromU32(dst: *mut [u32], val: u32) {
11 +
unsafe fn bnFromU32(dst: *mut [u32], val: u32) {
12 12
    set dst[0] = val;
13 13
    let mut i: u32 = 1;
14 14
    while i < LIMBS {
15 15
        set dst[i] = 0;
16 16
        set i += 1;
17 17
    }
18 18
}
19 19
20 20
/// Set a big number to zero.
21 -
fn bnZero(dst: *mut [u32]) {
21 +
unsafe fn bnZero(dst: *mut [u32]) {
22 22
    let mut i: u32 = 0;
23 23
    while i < LIMBS {
24 24
        set dst[i] = 0;
25 25
        set i += 1;
26 26
    }
27 27
}
28 28
29 29
/// Copy src to dst.
30 -
fn bnCopy(dst: *mut [u32], src: *[u32]) {
30 +
unsafe fn bnCopy(dst: *mut [u32], src: *[u32]) {
31 31
    let mut i: u32 = 0;
32 32
    while i < LIMBS {
33 33
        set dst[i] = src[i];
34 34
        set i += 1;
35 35
    }
36 36
}
37 37
38 38
/// Compare two big numbers. Returns 0 if equal, 1 if a > b, -1 if a < b.
39 -
fn bnCmp(a: *[u32], b: *[u32]) -> i32 {
39 +
unsafe fn bnCmp(a: *[u32], b: *[u32]) -> i32 {
40 40
    let mut i: i32 = LIMBS as i32 - 1;
41 41
    while i >= 0 {
42 42
        if a[i as u32] > b[i as u32] {
43 43
            return 1;
44 44
        }
52 52
    }
53 53
    return 0;
54 54
}
55 55
56 56
/// Add two big numbers: dst = a + b. Returns carry (0 or 1).
57 -
fn bnAdd(dst: *mut [u32], a: *[u32], b: *[u32]) -> u32 {
57 +
unsafe fn bnAdd(dst: *mut [u32], a: *[u32], b: *[u32]) -> u32 {
58 58
    let mut carry: u32 = 0;
59 59
    let mut i: u32 = 0;
60 60
    while i < LIMBS {
61 61
        let sumLo: u32 = a[i] + b[i];
62 62
        let mut carry1: u32 = 0;
74 74
    }
75 75
    return carry;
76 76
}
77 77
78 78
/// Subtract two big numbers: dst = a - b. Returns borrow (0 or 1).
79 -
fn bnSub(dst: *mut [u32], a: *[u32], b: *[u32]) -> u32 {
79 +
unsafe fn bnSub(dst: *mut [u32], a: *[u32], b: *[u32]) -> u32 {
80 80
    let mut borrow: u32 = 0;
81 81
    let mut i: u32 = 0;
82 82
    while i < LIMBS {
83 83
        let diff: u32 = a[i] - b[i];
84 84
        let mut borrow1: u32 = 0;
96 96
    }
97 97
    return borrow;
98 98
}
99 99
100 100
/// Multiply two LIMBS-word numbers, producing a 2*LIMBS-word result in wide.
101 -
fn bnMul(wide: *mut [u32], a: *[u32], b: *[u32]) {
101 +
unsafe fn bnMul(wide: *mut [u32], a: *[u32], b: *[u32]) {
102 102
    let mut i: u32 = 0;
103 103
    while i < LIMBS * 2 {
104 104
        set wide[i] = 0;
105 105
        set i += 1;
106 106
    }
152 152
        set i += 1;
153 153
    }
154 154
}
155 155
156 156
/// Left shift a big number by 1 bit.
157 -
fn bnShl1(dst: *mut [u32], src: *[u32]) {
157 +
unsafe fn bnShl1(dst: *mut [u32], src: *[u32]) {
158 158
    let mut carry: u32 = 0;
159 159
    let mut i: u32 = 0;
160 160
    while i < LIMBS {
161 161
        let newCarry: u32 = src[i] >> 31;
162 162
        set dst[i] = (src[i] << 1) | carry;
164 164
        set i += 1;
165 165
    }
166 166
}
167 167
168 168
/// Right shift a big number by 1 bit.
169 -
fn bnShr1(dst: *mut [u32], src: *[u32]) {
169 +
unsafe fn bnShr1(dst: *mut [u32], src: *[u32]) {
170 170
    let mut carry: u32 = 0;
171 171
    let mut i: u32 = LIMBS;
172 172
    while i > 0 {
173 173
        set i -= 1;
174 174
        let newCarry: u32 = src[i] & 1;
176 176
        set carry = newCarry;
177 177
    }
178 178
}
179 179
180 180
/// Test basic addition.
181 -
fn testAdd() -> i32 {
181 +
unsafe fn testAdd() -> i32 {
182 182
    let mut a: [u32; 4] = [0; 4];
183 183
    let mut b: [u32; 4] = [0; 4];
184 184
    let mut c: [u32; 4] = [0; 4];
185 185
    let mut d: [u32; 4] = [0; 4];
186 186
200 200
201 201
    return 0;
202 202
}
203 203
204 204
/// Test subtraction.
205 -
fn testSub() -> i32 {
205 +
unsafe fn testSub() -> i32 {
206 206
    let mut a: [u32; 4] = [0; 4];
207 207
    let mut b: [u32; 4] = [0; 4];
208 208
    let mut c: [u32; 4] = [0; 4];
209 209
210 210
    bnZero(&mut a[..]);
218 218
219 219
    return 0;
220 220
}
221 221
222 222
/// Test multiplication.
223 -
fn testMul() -> i32 {
223 +
unsafe fn testMul() -> i32 {
224 224
    let mut a: [u32; 4] = [0; 4];
225 225
    let mut b: [u32; 4] = [0; 4];
226 226
    let mut wide: [u32; 8] = [0; 8];
227 227
228 228
    bnFromU32(&mut a[..], 12345);
241 241
242 242
    return 0;
243 243
}
244 244
245 245
/// Test the identity a + b - b = a.
246 -
fn testAddSubIdentity() -> i32 {
246 +
unsafe fn testAddSubIdentity() -> i32 {
247 247
    let mut a: [u32; 4] = [0; 4];
248 248
    let mut b: [u32; 4] = [0; 4];
249 249
    let mut c: [u32; 4] = [0; 4];
250 250
    let mut d: [u32; 4] = [0; 4];
251 251
269 269
270 270
    return 0;
271 271
}
272 272
273 273
/// Test shift operations.
274 -
fn testShift() -> i32 {
274 +
unsafe fn testShift() -> i32 {
275 275
    let mut a: [u32; 4] = [0; 4];
276 276
    let mut b: [u32; 4] = [0; 4];
277 277
    let mut c: [u32; 4] = [0; 4];
278 278
279 279
    bnFromU32(&mut a[..], 1);
291 291
292 292
    return 0;
293 293
}
294 294
295 295
/// Test large multiply.
296 -
fn testLargeMultiply() -> i32 {
296 +
unsafe fn testLargeMultiply() -> i32 {
297 297
    let mut a: [u32; 4] = [0; 4];
298 298
    let mut b: [u32; 4] = [0; 4];
299 299
    let mut wide: [u32; 8] = [0; 8];
300 300
301 301
    set a[0] = 0xFFFFFFFF;
321 321
322 322
    return 0;
323 323
}
324 324
325 325
/// Test Fibonacci sequence with big numbers.
326 -
fn testFibonacci() -> i32 {
326 +
unsafe fn testFibonacci() -> i32 {
327 327
    let mut fibA: [u32; 4] = [0; 4];
328 328
    let mut fibB: [u32; 4] = [0; 4];
329 329
    let mut fibC: [u32; 4] = [0; 4];
330 330
331 331
    set fibA[0] = 0;
359 359
360 360
    return 0;
361 361
}
362 362
363 363
/// Test compare function.
364 -
fn testCompare() -> i32 {
364 +
unsafe fn testCompare() -> i32 {
365 365
    let mut a: [u32; 4] = [0; 4];
366 366
    let mut b: [u32; 4] = [0; 4];
367 367
368 368
    bnFromU32(&mut a[..], 100);
369 369
    bnFromU32(&mut b[..], 200);
381 381
    assert bnCmp(&a[..], &b[..]) == 1;
382 382
383 383
    return 0;
384 384
}
385 385
386 -
@default fn main() -> i32 {
386 +
@default unsafe fn main() -> i32 {
387 387
    let r1: i32 = testAdd();
388 388
    if r1 <> 0 {
389 389
        return 10 + r1;
390 390
    }
391 391
test/tests/prog.binsearch.rad +4 -4
1 1
//! returns: 0
2 2
//! Binary search.
3 3
//! Search a pre-sorted array for present and absent values.
4 4
5 5
/// Binary search for `target` in `data`. Returns the index if found, or nil.
6 -
fn binarySearch(data: *[i32], target: i32) -> ?u32 {
6 +
fn binarySearch(data: &[i32], target: i32) -> ?u32 {
7 7
    let mut lo: i32 = 0;
8 8
    let mut hi: i32 = data.len as i32 - 1;
9 9
10 10
    while lo <= hi {
11 11
        let mid: i32 = lo + (hi - lo) / 2;
21 21
    }
22 22
    return nil;
23 23
}
24 24
25 25
/// Test searching for elements that exist.
26 -
fn testPresent(data: *[i32]) -> i32 {
26 +
fn testPresent(data: &[i32]) -> i32 {
27 27
    // First element.
28 28
    let idx0 = binarySearch(data, 3) else {
29 29
        return 1;
30 30
    };
31 31
    assert idx0 == 0;
56 56
    assert idx16 == 16;
57 57
    return 0;
58 58
}
59 59
60 60
/// Test searching for elements that do not exist.
61 -
fn testAbsent(data: *[i32]) -> i32 {
61 +
fn testAbsent(data: &[i32]) -> i32 {
62 62
    // Below range.
63 63
    assert binarySearch(data, 1) == nil;
64 64
    // Above range.
65 65
    assert binarySearch(data, 200) == nil;
66 66
    // Between existing elements.
71 71
    assert binarySearch(data, 99) == nil;
72 72
    return 0;
73 73
}
74 74
75 75
/// Test searching for every element in the array.
76 -
fn testAllPresent(data: *[i32]) -> i32 {
76 +
fn testAllPresent(data: &[i32]) -> i32 {
77 77
    for value, index in data {
78 78
        let found = binarySearch(data, value) else {
79 79
            return index as i32 + 1;
80 80
        };
81 81
        if found <> index {
test/tests/prog.bubblesort.rad +4 -4
1 1
//! returns: 0
2 2
//! Bubble sort.
3 3
//! Sort an array of integers and verify the result.
4 4
5 5
/// Bubble sort the array in ascending order.
6 -
fn bubbleSort(data: *mut [i32]) {
6 +
fn bubbleSort(data: &mut [i32]) {
7 7
    let mut n: u32 = data.len;
8 8
    while n > 1 {
9 9
        let mut swapped: bool = false;
10 10
        let mut i: u32 = 0;
11 11
        while i < n - 1 {
24 24
        set n -= 1;
25 25
    }
26 26
}
27 27
28 28
/// Verify the array is sorted in ascending order.
29 -
fn isSorted(data: *[i32]) -> bool {
29 +
fn isSorted(data: &[i32]) -> bool {
30 30
    let mut prev: ?i32 = nil;
31 31
    for val in data {
32 32
        if let p = prev {
33 33
            if p > val {
34 34
                return false;
38 38
    }
39 39
    return true;
40 40
}
41 41
42 42
/// Compute the sum of all elements.
43 -
fn sum(data: *[i32]) -> i32 {
43 +
fn sum(data: &[i32]) -> i32 {
44 44
    let mut total: i32 = 0;
45 45
    for val in data {
46 46
        set total += val;
47 47
    }
48 48
    return total;
49 49
}
50 50
51 51
/// Verify specific positions in the sorted output.
52 -
fn verifyPositions(data: *[i32]) -> i32 {
52 +
fn verifyPositions(data: &[i32]) -> i32 {
53 53
    // Sorted: 3 5 7 12 17 19 28 31 42 50 55 66 71 80 88 93
54 54
    assert data[0] == 3;
55 55
    assert data[1] == 5;
56 56
    assert data[2] == 7;
57 57
    assert data[7] == 31;
test/tests/prog.cordic.rad +8 -8
27 27
    sin: i32,
28 28
}
29 29
30 30
/// CORDIC rotation mode: compute cos(angle) and sin(angle).
31 31
/// Input angle in Q16.16 radians, must be in [-pi/2, pi/2].
32 -
fn cordicRotate(angle: i32, atanTable: *[i32]) -> CosSin {
32 +
fn cordicRotate(angle: i32, atanTable: &[i32]) -> CosSin {
33 33
    let mut x: i32 = CORDIC_GAIN;
34 34
    let mut y: i32 = 0;
35 35
    let mut z: i32 = angle;
36 36
37 37
    let mut i: u32 = 0;
53 53
54 54
    return CosSin { cos: x, sin: y };
55 55
}
56 56
57 57
/// Compute cos and sin for any angle by reducing to [-pi/2, pi/2].
58 -
fn cosSin(angle: i32, atanTable: *[i32]) -> CosSin {
58 +
fn cosSin(angle: i32, atanTable: &[i32]) -> CosSin {
59 59
    let mut a: i32 = angle;
60 60
61 61
    // Reduce to [-pi, pi].
62 62
    while a > PI {
63 63
        set a -= 2 * PI;
120 120
    }
121 121
    return result as i32;
122 122
}
123 123
124 124
/// Test cos(0) = 1, sin(0) = 0.
125 -
fn testZero(atanTable: *[i32]) -> i32 {
125 +
fn testZero(atanTable: &[i32]) -> i32 {
126 126
    let r: CosSin = cosSin(0, atanTable);
127 127
    // cos(0) should be close to 65536 (1.0 in Q16.16).
128 128
    let cosErr: i32 = abs(r.cos - SCALE);
129 129
    let sinErr: i32 = abs(r.sin);
130 130
133 133
    assert sinErr <= 655;
134 134
    return 0;
135 135
}
136 136
137 137
/// Test cos(pi/2) = 0, sin(pi/2) = 1.
138 -
fn testHalfPi(atanTable: *[i32]) -> i32 {
138 +
fn testHalfPi(atanTable: &[i32]) -> i32 {
139 139
    let r: CosSin = cosSin(HALF_PI, atanTable);
140 140
    let cosErr: i32 = abs(r.cos);
141 141
    let sinErr: i32 = abs(r.sin - SCALE);
142 142
143 143
    assert cosErr <= 655;
144 144
    assert sinErr <= 655;
145 145
    return 0;
146 146
}
147 147
148 148
/// Test cos(pi) = -1, sin(pi) = 0.
149 -
fn testPi(atanTable: *[i32]) -> i32 {
149 +
fn testPi(atanTable: &[i32]) -> i32 {
150 150
    let r: CosSin = cosSin(PI, atanTable);
151 151
    let cosErr: i32 = abs(r.cos + SCALE);
152 152
    let sinErr: i32 = abs(r.sin);
153 153
154 154
    assert cosErr <= 655;
155 155
    assert sinErr <= 655;
156 156
    return 0;
157 157
}
158 158
159 159
/// Test Pythagorean identity: sin^2 + cos^2 = 1 for several angles.
160 -
fn testPythagorean(atanTable: *[i32]) -> i32 {
160 +
fn testPythagorean(atanTable: &[i32]) -> i32 {
161 161
    // Test at 16 evenly spaced angles from 0 to 2*pi.
162 162
    let step: i32 = PI / 8;
163 163
    let mut i: i32 = 0 - PI;
164 164
165 165
    while i <= PI {
176 176
    }
177 177
    return 0;
178 178
}
179 179
180 180
/// Test symmetry: sin(-x) = -sin(x), cos(-x) = cos(x).
181 -
fn testSymmetry(atanTable: *[i32]) -> i32 {
181 +
fn testSymmetry(atanTable: &[i32]) -> i32 {
182 182
    let angles: [i32; 5] = [16384, 32768, 51472, 65536, 81920];
183 183
184 184
    let mut i: u32 = 0;
185 185
    while i < 5 {
186 186
        let a: i32 = angles[i];
196 196
    }
197 197
    return 0;
198 198
}
199 199
200 200
/// Test specific known value: cos(pi/3) = 0.5, sin(pi/3) = 0.866.
201 -
fn testPiThird(atanTable: *[i32]) -> i32 {
201 +
fn testPiThird(atanTable: &[i32]) -> i32 {
202 202
    // pi/3 in Q16.16 = 68629.
203 203
    let piThird: i32 = 68629;
204 204
    let r: CosSin = cosSin(piThird, atanTable);
205 205
206 206
    // cos(pi/3) = 0.5 = 32768 in Q16.16.
test/tests/prog.crc32.rad +5 -5
2 2
//! CRC-32.
3 3
//! Compute CRC-32 of a byte buffer using a 256-entry lookup table.
4 4
//! The table is built at startup. Verify against known checksums.
5 5
6 6
/// Build the CRC-32 lookup table using the standard polynomial 0xEDB88320.
7 -
fn buildTable(table: *mut [u32]) {
7 +
fn buildTable(table: &mut [u32]) {
8 8
    let mut i: u32 = 0;
9 9
    while i < 256 {
10 10
        let mut crc: u32 = i;
11 11
        let mut j: u32 = 0;
12 12
        while j < 8 {
21 21
        set i += 1;
22 22
    }
23 23
}
24 24
25 25
/// Compute CRC-32 of a byte slice.
26 -
fn crc32(table: *[u32], data: *[u8]) -> u32 {
26 +
fn crc32(table: &[u32], data: &[u8]) -> u32 {
27 27
    let mut crc: u32 = 0xFFFFFFFF;
28 28
    let mut i: u32 = 0;
29 29
    while i < data.len {
30 30
        let byte: u8 = data[i];
31 31
        let index: u32 = (crc ^ byte as u32) & 0xFF;
34 34
    }
35 35
    return crc ^ 0xFFFFFFFF;
36 36
}
37 37
38 38
/// Test the lookup table has been built correctly.
39 -
fn testTable(table: *[u32]) -> i32 {
39 +
fn testTable(table: &[u32]) -> i32 {
40 40
    // TABLE[0] should be 0 (zero input, all shifts produce zero).
41 41
    assert table[0] == 0;
42 42
    // Known value: TABLE[1] = 0x77073096
43 43
    assert table[1] == 0x77073096;
44 44
    // TABLE[255] is a known value: 0x2D02EF8D
45 45
    assert table[255] == 0x2D02EF8D;
46 46
    return 0;
47 47
}
48 48
49 49
/// Test CRC-32 of known strings.
50 -
fn testKnownCRC(table: *[u32]) -> i32 {
50 +
fn testKnownCRC(table: &[u32]) -> i32 {
51 51
    // CRC-32 of empty data should be 0x00000000.
52 52
    assert crc32(table, &[]) == 0x00000000;
53 53
54 54
    // CRC-32 of "123456789" = 0xCBF43926 (the standard check value).
55 55
    let check: [u8; 9] = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39];
60 60
    assert crc32(table, &single[..]) == 0xD3D99E8B;
61 61
    return 0;
62 62
}
63 63
64 64
/// Test CRC-32 of incrementally built data.
65 -
fn testIncremental(table: *[u32]) -> i32 {
65 +
fn testIncremental(table: &[u32]) -> i32 {
66 66
    // Build a 32-byte buffer with values 0..31.
67 67
    let mut buf: [u8; 32] = [0; 32];
68 68
    let mut i: u32 = 0;
69 69
    while i < 32 {
70 70
        set buf[i] = i as u8;
test/tests/prog.dijkstra.rad +18 -18
19 19
    numNodes: u32,
20 20
    heap: *mut [HeapEntry],
21 21
    heapSize: u32,
22 22
}
23 23
24 -
fn heapSwap(g: *mut Graph, i: u32, j: u32) {
24 +
unsafe fn heapSwap(g: *mut Graph, i: u32, j: u32) {
25 25
    let tmp: HeapEntry = g.heap[i];
26 26
    set g.heap[i] = g.heap[j];
27 27
    set g.heap[j] = tmp;
28 28
}
29 29
30 -
fn siftUp(g: *mut Graph, pos: u32) {
30 +
unsafe fn siftUp(g: *mut Graph, pos: u32) {
31 31
    let mut i: u32 = pos;
32 32
    while i > 0 {
33 33
        let parent: u32 = (i - 1) / 2;
34 34
        if g.heap[i].dist < g.heap[parent].dist {
35 35
            heapSwap(g, i, parent);
38 38
            return;
39 39
        }
40 40
    }
41 41
}
42 42
43 -
fn siftDown(g: *mut Graph, pos: u32) {
43 +
unsafe fn siftDown(g: *mut Graph, pos: u32) {
44 44
    let mut i: u32 = pos;
45 45
    while true {
46 46
        let left: u32 = 2 * i + 1;
47 47
        let right: u32 = 2 * i + 2;
48 48
        let mut smallest: u32 = i;
59 59
        heapSwap(g, i, smallest);
60 60
        set i = smallest;
61 61
    }
62 62
}
63 63
64 -
fn heapPush(g: *mut Graph, dist: u32, node: u32) {
64 +
unsafe fn heapPush(g: *mut Graph, dist: u32, node: u32) {
65 65
    set g.heap[g.heapSize] = HeapEntry { dist, node };
66 66
    set g.heapSize += 1;
67 67
    siftUp(g, g.heapSize - 1);
68 68
}
69 69
70 70
/// Pop from the heap. Returns nil if the heap is empty.
71 -
fn heapPop(g: *mut Graph) -> ?HeapEntry {
71 +
unsafe fn heapPop(g: *mut Graph) -> ?HeapEntry {
72 72
    if g.heapSize == 0 {
73 73
        return nil;
74 74
    }
75 75
    let result: HeapEntry = g.heap[0];
76 76
    set g.heapSize -= 1;
79 79
        siftDown(g, 0);
80 80
    }
81 81
    return result;
82 82
}
83 83
84 -
fn addEdge(g: *mut Graph, from: u32, to: u32, weight: u32) {
84 +
unsafe fn addEdge(g: *mut Graph, from: u32, to: u32, weight: u32) {
85 85
    set g.adj[from][to] = weight;
86 86
}
87 87
88 -
fn addBidiEdge(g: *mut Graph, from: u32, to: u32, weight: u32) {
88 +
unsafe fn addBidiEdge(g: *mut Graph, from: u32, to: u32, weight: u32) {
89 89
    set g.adj[from][to] = weight;
90 90
    set g.adj[to][from] = weight;
91 91
}
92 92
93 -
fn resetGraph(g: *mut Graph, n: u32) {
93 +
unsafe fn resetGraph(g: *mut Graph, n: u32) {
94 94
    set g.numNodes = n;
95 95
    let mut i: u32 = 0;
96 96
    while i < MAX_NODES {
97 97
        let mut j: u32 = 0;
98 98
        while j < MAX_NODES {
106 106
    }
107 107
    set g.heapSize = 0;
108 108
}
109 109
110 110
/// Get the predecessor as an optional; -1 means no predecessor.
111 -
fn getPrev(g: *Graph, node: u32) -> ?u32 {
111 +
unsafe fn getPrev(g: *Graph, node: u32) -> ?u32 {
112 112
    let p = g.prev[node];
113 113
    if p < 0 {
114 114
        return nil;
115 115
    }
116 116
    return p as u32;
117 117
}
118 118
119 -
fn dijkstra(g: *mut Graph, source: u32) {
119 +
unsafe fn dijkstra(g: *mut Graph, source: u32) {
120 120
    set g.dist[source] = 0;
121 121
    heapPush(g, 0, source);
122 122
123 123
    while let entry = heapPop(g) {
124 124
        let u: u32 = entry.node;
142 142
        }
143 143
    }
144 144
}
145 145
146 146
/// Reconstruct the shortest path from source to target.
147 -
fn reconstructPath(g: *Graph, target: u32, path: *mut [u32]) -> u32 {
147 +
unsafe fn reconstructPath(g: *Graph, target: u32, path: *mut [u32]) -> u32 {
148 148
    let mut len: u32 = 0;
149 149
150 150
    set path[len] = target;
151 151
    set len += 1;
152 152
168 168
        set b -= 1;
169 169
    }
170 170
    return len;
171 171
}
172 172
173 -
fn testLinear(g: *mut Graph) -> i32 {
173 +
unsafe fn testLinear(g: *mut Graph) -> i32 {
174 174
    resetGraph(g, 5);
175 175
    addEdge(g, 0, 1, 10);
176 176
    addEdge(g, 1, 2, 10);
177 177
    addEdge(g, 2, 3, 10);
178 178
    addEdge(g, 3, 4, 10);
193 193
    assert path[4] == 4;
194 194
195 195
    return 0;
196 196
}
197 197
198 -
fn testShortcut(g: *mut Graph) -> i32 {
198 +
unsafe fn testShortcut(g: *mut Graph) -> i32 {
199 199
    resetGraph(g, 4);
200 200
    addEdge(g, 0, 1, 10);
201 201
    addEdge(g, 1, 2, 10);
202 202
    addEdge(g, 0, 3, 5);
203 203
    addEdge(g, 3, 2, 3);
212 212
    assert prev2 == 3;
213 213
214 214
    return 0;
215 215
}
216 216
217 -
fn testBidirectional(g: *mut Graph) -> i32 {
217 +
unsafe fn testBidirectional(g: *mut Graph) -> i32 {
218 218
    resetGraph(g, 5);
219 219
    addBidiEdge(g, 0, 1, 1);
220 220
    addBidiEdge(g, 1, 2, 2);
221 221
    addBidiEdge(g, 2, 3, 3);
222 222
    addBidiEdge(g, 3, 0, 4);
234 234
    }
235 235
236 236
    return 0;
237 237
}
238 238
239 -
fn testComplete(g: *mut Graph) -> i32 {
239 +
unsafe fn testComplete(g: *mut Graph) -> i32 {
240 240
    resetGraph(g, 8);
241 241
242 242
    let mut i: u32 = 0;
243 243
    while i < 8 {
244 244
        let mut j: u32 = 0;
267 267
    }
268 268
269 269
    return 0;
270 270
}
271 271
272 -
fn testDisconnected(g: *mut Graph) -> i32 {
272 +
unsafe fn testDisconnected(g: *mut Graph) -> i32 {
273 273
    resetGraph(g, 6);
274 274
    addEdge(g, 0, 1, 5);
275 275
    addEdge(g, 1, 2, 3);
276 276
    addEdge(g, 3, 4, 2);
277 277
    addEdge(g, 4, 5, 1);
290 290
    assert getPrev(g, 3) == nil;
291 291
292 292
    return 0;
293 293
}
294 294
295 -
fn testDiamond(g: *mut Graph) -> i32 {
295 +
unsafe fn testDiamond(g: *mut Graph) -> i32 {
296 296
    resetGraph(g, 5);
297 297
    addEdge(g, 0, 1, 5);
298 298
    addEdge(g, 0, 2, 5);
299 299
    addEdge(g, 1, 3, 5);
300 300
    addEdge(g, 2, 3, 5);
311 311
    assert prev3 == 1 or prev3 == 2;
312 312
313 313
    return 0;
314 314
}
315 315
316 -
@default fn main() -> i32 {
316 +
@default unsafe fn main() -> i32 {
317 317
    let mut adj: [[u32; 16]; 16] = [[0xFFFFFFFF; 16]; 16];
318 318
    let mut dist: [u32; 16] = [0xFFFFFFFF; 16];
319 319
    let mut prev: [i32; 16] = [-1; 16];
320 320
    let mut visited: [bool; 16] = [false; 16];
321 321
    let mut heap: [HeapEntry; 256] = [HeapEntry { dist: 0, node: 0 }; 256];
test/tests/prog.eval.rad +50 -30
31 31
union EvalError {
32 32
    DivByZero
33 33
}
34 34
35 35
/// Allocate a new node, returning its index.
36 -
fn newNode(pool: *mut Pool, expr: Expr) -> u32 {
36 +
fn newNode(pool: &mut Pool, expr: Expr) -> u32 {
37 37
    let idx = pool.count;
38 38
    set pool.nodes[idx] = expr;
39 39
    set pool.count += 1;
40 40
    return idx;
41 41
}
42 42
43 43
/// Convenience constructors.
44 -
fn num(pool: *mut Pool, n: i32) -> u32 {
44 +
fn num(pool: &mut Pool, n: i32) -> u32 {
45 45
    return newNode(pool, Expr::Num(n));
46 46
}
47 47
48 -
fn add(pool: *mut Pool, left: u32, right: u32) -> u32 {
48 +
fn add(pool: &mut Pool, left: u32, right: u32) -> u32 {
49 49
    return newNode(pool, Expr::Add(BinOp { left, right }));
50 50
}
51 51
52 -
fn sub(pool: *mut Pool, left: u32, right: u32) -> u32 {
52 +
fn sub(pool: &mut Pool, left: u32, right: u32) -> u32 {
53 53
    return newNode(pool, Expr::Sub(BinOp { left, right }));
54 54
}
55 55
56 -
fn mul(pool: *mut Pool, left: u32, right: u32) -> u32 {
56 +
fn mul(pool: &mut Pool, left: u32, right: u32) -> u32 {
57 57
    return newNode(pool, Expr::Mul(BinOp { left, right }));
58 58
}
59 59
60 -
fn div(pool: *mut Pool, left: u32, right: u32) -> u32 {
60 +
fn div(pool: &mut Pool, left: u32, right: u32) -> u32 {
61 61
    return newNode(pool, Expr::Div(BinOp { left, right }));
62 62
}
63 63
64 -
fn neg(pool: *mut Pool, child: u32) -> u32 {
64 +
fn neg(pool: &mut Pool, child: u32) -> u32 {
65 65
    return newNode(pool, Expr::Neg(child));
66 66
}
67 67
68 68
/// Recursively evaluate the expression tree rooted at nodes[idx].
69 -
fn eval(nodes: *[Expr], idx: u32) -> i32 throws (EvalError) {
69 +
fn eval(nodes: &[Expr], idx: u32) -> i32 throws (EvalError) {
70 70
    let node = nodes[idx];
71 71
    match node {
72 72
        case Expr::Num(n) => {
73 73
            return n;
74 74
        }
94 94
        }
95 95
    }
96 96
}
97 97
98 98
/// Count the total number of nodes in the tree rooted at idx.
99 -
fn countNodes(nodes: *[Expr], idx: u32) -> u32 {
99 +
fn countNodes(nodes: &[Expr], idx: u32) -> u32 {
100 100
    let node = nodes[idx];
101 101
    match node {
102 102
        case Expr::Num(_) => {
103 103
            return 1;
104 104
        }
119 119
        }
120 120
    }
121 121
}
122 122
123 123
/// Reset the node pool.
124 -
fn reset(pool: *mut Pool) {
124 +
fn reset(pool: &mut Pool) {
125 125
    set pool.count = 0;
126 126
}
127 127
128 128
/// Test 1: Simple addition: 3 + 4 = 7
129 -
fn testSimpleAdd(pool: *mut Pool) -> i32 {
129 +
fn testSimpleAdd(pool: &mut Pool) -> i32 {
130 130
    reset(pool);
131 -
    let root = add(pool, num(pool, 3), num(pool, 4));
131 +
    let three = num(pool, 3);
132 +
    let four = num(pool, 4);
133 +
    let root = add(pool, three, four);
132 134
    assert try! eval(&pool.nodes[..], root) == 7;
133 135
    return 0;
134 136
}
135 137
136 138
/// Test 2: Nested expression: (2 + 3) * (4 - 1) = 5 * 3 = 15
137 -
fn testNested(pool: *mut Pool) -> i32 {
139 +
fn testNested(pool: &mut Pool) -> i32 {
138 140
    reset(pool);
139 -
    let left = add(pool, num(pool, 2), num(pool, 3));
140 -
    let right = sub(pool, num(pool, 4), num(pool, 1));
141 +
    let two = num(pool, 2);
142 +
    let three = num(pool, 3);
143 +
    let left = add(pool, two, three);
144 +
    let four = num(pool, 4);
145 +
    let one = num(pool, 1);
146 +
    let right = sub(pool, four, one);
141 147
    let root = mul(pool, left, right);
142 148
    assert try! eval(&pool.nodes[..], root) == 15;
143 149
    assert countNodes(&pool.nodes[..], root) == 7;
144 150
    return 0;
145 151
}
146 152
147 153
/// Test 3: Complex expression: ((10 + 5) * 2 - 6) / 4 = (30 - 6) / 4 = 24 / 4 = 6
148 -
fn testComplex(pool: *mut Pool) -> i32 {
154 +
fn testComplex(pool: &mut Pool) -> i32 {
149 155
    reset(pool);
150 -
    let a = add(pool, num(pool, 10), num(pool, 5));
151 -
    let b = mul(pool, a, num(pool, 2));
152 -
    let c = sub(pool, b, num(pool, 6));
153 -
    let root = div(pool, c, num(pool, 4));
156 +
    let ten = num(pool, 10);
157 +
    let five = num(pool, 5);
158 +
    let a = add(pool, ten, five);
159 +
    let two = num(pool, 2);
160 +
    let b = mul(pool, a, two);
161 +
    let six = num(pool, 6);
162 +
    let c = sub(pool, b, six);
163 +
    let four = num(pool, 4);
164 +
    let root = div(pool, c, four);
154 165
    assert try! eval(&pool.nodes[..], root) == 6;
155 166
    return 0;
156 167
}
157 168
158 169
/// Test 4: Negation: -(3 + 4) = -7
159 -
fn testNeg(pool: *mut Pool) -> i32 {
170 +
fn testNeg(pool: &mut Pool) -> i32 {
160 171
    reset(pool);
161 -
    let root = neg(pool, add(pool, num(pool, 3), num(pool, 4)));
172 +
    let three = num(pool, 3);
173 +
    let four = num(pool, 4);
174 +
    let sum = add(pool, three, four);
175 +
    let root = neg(pool, sum);
162 176
    assert try! eval(&pool.nodes[..], root) == -7;
163 177
    return 0;
164 178
}
165 179
166 180
/// Test 5: Deep tree: 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 = 36
167 -
fn testDeep(pool: *mut Pool) -> i32 {
181 +
fn testDeep(pool: &mut Pool) -> i32 {
168 182
    reset(pool);
169 183
    let mut root = num(pool, 1);
170 184
    let values: [i32; 7] = [2, 3, 4, 5, 6, 7, 8];
171 185
    for v in values {
172 -
        set root = add(pool, root, num(pool, v));
186 +
        let node = num(pool, v);
187 +
        set root = add(pool, root, node);
173 188
    }
174 189
    assert try! eval(&pool.nodes[..], root) == 36;
175 190
    assert countNodes(&pool.nodes[..], root) == 15;
176 191
    return 0;
177 192
}
178 193
179 194
/// Test 6: Mixed operations: (100 - 3 * (2 + 8)) / 7 = (100 - 30) / 7 = 70 / 7 = 10
180 -
fn testMixed(pool: *mut Pool) -> i32 {
195 +
fn testMixed(pool: &mut Pool) -> i32 {
181 196
    reset(pool);
182 -
    let inner = add(pool, num(pool, 2), num(pool, 8));
183 -
    let product = mul(pool, num(pool, 3), inner);
184 -
    let diff = sub(pool, num(pool, 100), product);
185 -
    let root = div(pool, diff, num(pool, 7));
197 +
    let two = num(pool, 2);
198 +
    let eight = num(pool, 8);
199 +
    let inner = add(pool, two, eight);
200 +
    let three = num(pool, 3);
201 +
    let product = mul(pool, three, inner);
202 +
    let hundred = num(pool, 100);
203 +
    let diff = sub(pool, hundred, product);
204 +
    let seven = num(pool, 7);
205 +
    let root = div(pool, diff, seven);
186 206
    assert try! eval(&pool.nodes[..], root) == 10;
187 207
    return 0;
188 208
}
189 209
190 210
/// Test 7: Single number.
191 -
fn testSingleNum(pool: *mut Pool) -> i32 {
211 +
fn testSingleNum(pool: &mut Pool) -> i32 {
192 212
    reset(pool);
193 213
    let root = num(pool, 42);
194 214
    assert try! eval(&pool.nodes[..], root) == 42;
195 215
    assert countNodes(&pool.nodes[..], root) == 1;
196 216
    return 0;
test/tests/prog.hanoi.rad +7 -7
19 19
    moves: [Move; 63],
20 20
    count: u32,
21 21
}
22 22
23 23
/// Record a move.
24 -
fn recordMove(ml: *mut MoveLog, disk: u32, from: u32, to: u32) {
24 +
fn recordMove(ml: &mut MoveLog, disk: u32, from: u32, to: u32) {
25 25
    if ml.count < MAX_MOVES {
26 26
        set ml.moves[ml.count] = Move { disk, from, to };
27 27
        set ml.count += 1;
28 28
    }
29 29
}
30 30
31 31
/// Solve Tower of Hanoi recursively.
32 32
/// Move `n` disks from peg `from` to peg `to` using `aux` as auxiliary.
33 -
fn hanoi(ml: *mut MoveLog, n: u32, from: u32, to: u32, aux: u32) {
33 +
fn hanoi(ml: &mut MoveLog, n: u32, from: u32, to: u32, aux: u32) {
34 34
    if n == 0 {
35 35
        return;
36 36
    }
37 37
    hanoi(ml, n - 1, from, aux, to);
38 38
    recordMove(ml, n, from, to);
39 39
    hanoi(ml, n - 1, aux, to, from);
40 40
}
41 41
42 42
/// Verify total move count.
43 -
fn testMoveCount(ml: *MoveLog) -> i32 {
43 +
fn testMoveCount(ml: &MoveLog) -> i32 {
44 44
    // For N disks, there are 2^N - 1 moves.
45 45
    assert ml.count == MAX_MOVES;
46 46
    return 0;
47 47
}
48 48
49 49
/// Verify specific moves.
50 -
fn testSpecificMoves(ml: *MoveLog) -> i32 {
50 +
fn testSpecificMoves(ml: &MoveLog) -> i32 {
51 51
    // First move: smallest disk (1) from peg 0 to peg 2.
52 52
    assert ml.moves[0].disk == 1;
53 53
    assert ml.moves[0].from == 0;
54 54
    assert ml.moves[0].to == 2;
55 55
70 70
record Pegs {
71 71
    stacks: [[u32; 6]; 3],
72 72
    top: [u32; 3],
73 73
}
74 74
75 -
fn pegPush(pegs: *mut Pegs, peg: u32, disk: u32) -> bool {
75 +
fn pegPush(pegs: &mut Pegs, peg: u32, disk: u32) -> bool {
76 76
    let t = pegs.top[peg];
77 77
    if t > 0 {
78 78
        // Check that the top disk is larger than the one being placed.
79 79
        if pegs.stacks[peg][t - 1] < disk {
80 80
            return false;
83 83
    set pegs.stacks[peg][t] = disk;
84 84
    set pegs.top[peg] = t + 1;
85 85
    return true;
86 86
}
87 87
88 -
fn pegPop(pegs: *mut Pegs, peg: u32) -> u32 {
88 +
fn pegPop(pegs: &mut Pegs, peg: u32) -> u32 {
89 89
    let t = pegs.top[peg];
90 90
    if t == 0 {
91 91
        // Should not happen in a valid solution.
92 92
        return 0;
93 93
    }
97 97
98 98
/// Simulate the peg state to verify correctness.
99 99
/// Replay all moves and check:
100 100
/// 1. No larger disk is placed on a smaller disk.
101 101
/// 2. All disks end up on peg 1.
102 -
fn testSimulate(ml: *MoveLog) -> i32 {
102 +
fn testSimulate(ml: &MoveLog) -> i32 {
103 103
    let mut pegs = Pegs {
104 104
        stacks: [[0; 6]; 3],
105 105
        top: [0, 0, 0],
106 106
    };
107 107
test/tests/prog.huffman.rad +21 -21
33 33
    codeLen: *mut [u32],
34 34
    bitstream: *mut [u8],
35 35
    bitCount: u32,
36 36
}
37 37
38 -
fn newLeaf(s: *mut HuffState, freq: u32, symbol: u32) -> u32 {
38 +
unsafe fn newLeaf(s: *mut HuffState, freq: u32, symbol: u32) -> u32 {
39 39
    let idx: u32 = s.nodeCount;
40 40
    set s.nodes[idx] = HNode { freq, kind: HNodeKind::Leaf(symbol), left: NIL, right: NIL };
41 41
    set s.nodeCount += 1;
42 42
    return idx;
43 43
}
44 44
45 -
fn newInterior(s: *mut HuffState, freq: u32, left: u32, right: u32) -> u32 {
45 +
unsafe fn newInterior(s: *mut HuffState, freq: u32, left: u32, right: u32) -> u32 {
46 46
    let idx: u32 = s.nodeCount;
47 47
    set s.nodes[idx] = HNode { freq, kind: HNodeKind::Interior, left, right };
48 48
    set s.nodeCount += 1;
49 49
    return idx;
50 50
}
51 51
52 52
/// Get the symbol from a node, or nil if it's an interior node.
53 -
fn nodeSymbol(node: *HNode) -> ?u32 {
53 +
unsafe fn nodeSymbol(node: *HNode) -> ?u32 {
54 54
    match node.kind {
55 55
        case HNodeKind::Leaf(sym) => {
56 56
            return sym;
57 57
        }
58 58
        case HNodeKind::Interior => {
59 59
            return nil;
60 60
        }
61 61
    }
62 62
}
63 63
64 -
fn heapSwap(s: *mut HuffState, i: u32, j: u32) {
64 +
unsafe fn heapSwap(s: *mut HuffState, i: u32, j: u32) {
65 65
    let tmp: u32 = s.heap[i];
66 66
    set s.heap[i] = s.heap[j];
67 67
    set s.heap[j] = tmp;
68 68
}
69 69
70 -
fn heapFreq(s: *HuffState, i: u32) -> u32 {
70 +
unsafe fn heapFreq(s: *HuffState, i: u32) -> u32 {
71 71
    return s.nodes[s.heap[i]].freq;
72 72
}
73 73
74 -
fn siftUp(s: *mut HuffState, pos: u32) {
74 +
unsafe fn siftUp(s: *mut HuffState, pos: u32) {
75 75
    let mut i: u32 = pos;
76 76
    while i > 0 {
77 77
        let parent: u32 = (i - 1) / 2;
78 78
        if heapFreq(s, i) < heapFreq(s, parent) {
79 79
            heapSwap(s, i, parent);
82 82
            return;
83 83
        }
84 84
    }
85 85
}
86 86
87 -
fn siftDown(s: *mut HuffState, pos: u32) {
87 +
unsafe fn siftDown(s: *mut HuffState, pos: u32) {
88 88
    let mut i: u32 = pos;
89 89
    while true {
90 90
        let left: u32 = 2 * i + 1;
91 91
        let right: u32 = 2 * i + 2;
92 92
        let mut smallest: u32 = i;
103 103
        heapSwap(s, i, smallest);
104 104
        set i = smallest;
105 105
    }
106 106
}
107 107
108 -
fn heapPush(s: *mut HuffState, nodeIdx: u32) {
108 +
unsafe fn heapPush(s: *mut HuffState, nodeIdx: u32) {
109 109
    set s.heap[s.heapSize] = nodeIdx;
110 110
    set s.heapSize += 1;
111 111
    siftUp(s, s.heapSize - 1);
112 112
}
113 113
114 -
fn heapPop(s: *mut HuffState) -> u32 {
114 +
unsafe fn heapPop(s: *mut HuffState) -> u32 {
115 115
    let result: u32 = s.heap[0];
116 116
    set s.heapSize -= 1;
117 117
    set s.heap[0] = s.heap[s.heapSize];
118 118
    if s.heapSize > 0 {
119 119
        siftDown(s, 0);
120 120
    }
121 121
    return result;
122 122
}
123 123
124 -
fn buildTree(s: *mut HuffState, freqs: *[u32]) -> u32 {
124 +
unsafe fn buildTree(s: *mut HuffState, freqs: *[u32]) -> u32 {
125 125
    set s.nodeCount = 0;
126 126
    set s.heapSize = 0;
127 127
128 128
    for freq, sym in freqs {
129 129
        if freq > 0 {
141 141
    }
142 142
143 143
    return heapPop(s);
144 144
}
145 145
146 -
fn generateCodes(s: *mut HuffState, nodeIdx: u32, code: u32, depth: u32) {
146 +
unsafe fn generateCodes(s: *mut HuffState, nodeIdx: u32, code: u32, depth: u32) {
147 147
    match s.nodes[nodeIdx].kind {
148 148
        case HNodeKind::Leaf(sym) => {
149 149
            set s.codeBits[sym] = code;
150 150
            set s.codeLen[sym] = depth;
151 151
        }
158 158
            }
159 159
        }
160 160
    }
161 161
}
162 162
163 -
fn writeBit(s: *mut HuffState, bit: u32) {
163 +
unsafe fn writeBit(s: *mut HuffState, bit: u32) {
164 164
    let byteIdx: u32 = s.bitCount / 8;
165 165
    let bitIdx: u32 = 7 - (s.bitCount % 8);
166 166
    if bit == 1 {
167 167
        set s.bitstream[byteIdx] |= (1 as u8 << bitIdx as u8);
168 168
    }
169 169
    set s.bitCount += 1;
170 170
}
171 171
172 -
fn readBit(s: *HuffState, pos: u32) -> u32 {
172 +
unsafe fn readBit(s: *HuffState, pos: u32) -> u32 {
173 173
    let byteIdx: u32 = pos / 8;
174 174
    let bitIdx: u32 = 7 - (pos % 8);
175 175
    return (s.bitstream[byteIdx] >> bitIdx as u8) as u32 & 1;
176 176
}
177 177
178 -
fn encode(s: *mut HuffState, msg: *[u32]) {
178 +
unsafe fn encode(s: *mut HuffState, msg: *[u32]) {
179 179
    set s.bitCount = 0;
180 180
    let mut i: u32 = 0;
181 181
    while i < MAX_BITS {
182 182
        set s.bitstream[i] = 0;
183 183
        set i += 1;
193 193
            set b += 1;
194 194
        }
195 195
    }
196 196
}
197 197
198 -
fn decode(s: *HuffState, root: u32, numSymbols: u32, out: *mut [u32]) -> u32 {
198 +
unsafe fn decode(s: *HuffState, root: u32, numSymbols: u32, out: *mut [u32]) -> u32 {
199 199
    let mut bitPos: u32 = 0;
200 200
    let mut decoded: u32 = 0;
201 201
202 202
    while decoded < numSymbols {
203 203
        let mut cur: u32 = root;
218 218
        set decoded += 1;
219 219
    }
220 220
    return decoded;
221 221
}
222 222
223 -
fn resetCodes(s: *mut HuffState) {
223 +
unsafe fn resetCodes(s: *mut HuffState) {
224 224
    let mut i: u32 = 0;
225 225
    while i < MAX_SYMBOLS {
226 226
        set s.codeBits[i] = 0;
227 227
        set s.codeLen[i] = 0;
228 228
        set i += 1;
229 229
    }
230 230
}
231 231
232 -
fn testBasic(s: *mut HuffState) -> i32 {
232 +
unsafe fn testBasic(s: *mut HuffState) -> i32 {
233 233
    let freqs: [u32; 5] = [5, 9, 12, 13, 16];
234 234
    let root: u32 = buildTree(s, &freqs[..]);
235 235
236 236
    assert s.nodes[root].freq == 55;
237 237
259 259
    }
260 260
261 261
    return 0;
262 262
}
263 263
264 -
fn testRoundTrip(s: *mut HuffState) -> i32 {
264 +
unsafe fn testRoundTrip(s: *mut HuffState) -> i32 {
265 265
    let freqs: [u32; 5] = [5, 9, 12, 13, 16];
266 266
    let root: u32 = buildTree(s, &freqs[..]);
267 267
268 268
    resetCodes(s);
269 269
    generateCodes(s, root, 0, 0);
284 284
    }
285 285
286 286
    return 0;
287 287
}
288 288
289 -
fn testSkewed(s: *mut HuffState) -> i32 {
289 +
unsafe fn testSkewed(s: *mut HuffState) -> i32 {
290 290
    let freqs: [u32; 6] = [100, 1, 1, 1, 1, 1];
291 291
    let root: u32 = buildTree(s, &freqs[..]);
292 292
293 293
    resetCodes(s);
294 294
    generateCodes(s, root, 0, 0);
318 318
    }
319 319
320 320
    return 0;
321 321
}
322 322
323 -
fn testUniform(s: *mut HuffState) -> i32 {
323 +
unsafe fn testUniform(s: *mut HuffState) -> i32 {
324 324
    let freqs: [u32; 8] = [10, 10, 10, 10, 10, 10, 10, 10];
325 325
    let root: u32 = buildTree(s, &freqs[..]);
326 326
327 327
    assert s.nodes[root].freq == 80;
328 328
349 349
    }
350 350
351 351
    return 0;
352 352
}
353 353
354 -
@default fn main() -> i32 {
354 +
@default unsafe fn main() -> i32 {
355 355
    let mut nodes: [HNode; 63] = [HNode { freq: 0, kind: HNodeKind::Interior, left: 0xFFFFFFFF, right: 0xFFFFFFFF }; 63];
356 356
    let mut heap: [u32; 63] = [0; 63];
357 357
    let mut codeBits: [u32; 32] = [0; 32];
358 358
    let mut codeLen: [u32; 32] = [0; 32];
359 359
    let mut bitstream: [u8; 512] = [0; 512];
test/tests/prog.hybridsort.rad +7 -7
2 2
//! Insertion sort and selection sort.
3 3
//! Sort two copies of the same data with each algorithm, then verify
4 4
//! both produce identical sorted output.
5 5
6 6
/// Copy `src` into `dst`.
7 -
fn copy(dst: *mut [i32], src: *[i32]) {
7 +
fn copy(dst: &mut [i32], src: &[i32]) {
8 8
    for val, i in src {
9 9
        set dst[i] = val;
10 10
    }
11 11
}
12 12
13 13
/// Insertion sort on the given array.
14 -
fn insertionSort(data: *mut [i32]) {
14 +
fn insertionSort(data: &mut [i32]) {
15 15
    let mut i: u32 = 1;
16 16
    while i < data.len {
17 17
        let key = data[i];
18 18
        let mut j: i32 = i as i32 - 1;
19 19
        while j >= 0 and data[j as u32] > key {
24 24
        set i += 1;
25 25
    }
26 26
}
27 27
28 28
/// Selection sort on the given array.
29 -
fn selectionSort(data: *mut [i32]) {
29 +
fn selectionSort(data: &mut [i32]) {
30 30
    let mut i: u32 = 0;
31 31
    while i < data.len - 1 {
32 32
        let mut minIdx: u32 = i;
33 33
        let mut j: u32 = i + 1;
34 34
        while j < data.len {
45 45
        set i += 1;
46 46
    }
47 47
}
48 48
49 49
/// Check that an array is sorted in ascending order.
50 -
fn isSorted(data: *[i32]) -> bool {
50 +
fn isSorted(data: &[i32]) -> bool {
51 51
    let mut prev: ?i32 = nil;
52 52
    for val in data {
53 53
        if let p = prev {
54 54
            if p > val {
55 55
                return false;
59 59
    }
60 60
    return true;
61 61
}
62 62
63 63
/// Compute sum of all elements.
64 -
fn sum(data: *[i32]) -> i32 {
64 +
fn sum(data: &[i32]) -> i32 {
65 65
    let mut total: i32 = 0;
66 66
    for val in data {
67 67
        set total += val;
68 68
    }
69 69
    return total;
70 70
}
71 71
72 72
/// Compare two arrays element by element.
73 -
fn arraysEqual(a: *[i32], b: *[i32]) -> i32 {
73 +
fn arraysEqual(a: &[i32], b: &[i32]) -> i32 {
74 74
    for val, i in a {
75 75
        if val <> b[i] {
76 76
            return i as i32 + 1;
77 77
        }
78 78
    }
79 79
    return 0;
80 80
}
81 81
82 82
/// Verify specific positions in the sorted output.
83 -
fn verifyPositions(data: *[i32]) -> i32 {
83 +
fn verifyPositions(data: &[i32]) -> i32 {
84 84
    // Sorted: 2 3 6 8 10 14 19 25 27 33 39 41 45 48 53 56 62 67 72 74 81 88 91 97
85 85
    let expected: [i32; 6] = [2, 3, 6, 41, 91, 97];
86 86
    let indices: [u32; 6] = [0, 1, 2, 11, 22, 23];
87 87
88 88
    for exp, i in expected {
test/tests/prog.linkedlist.rad +14 -14
15 15
    free: u32,
16 16
    head: ?u32,
17 17
}
18 18
19 19
/// Allocate a node from the pool. Returns its index.
20 -
fn alloc(list: *mut List, value: i32) -> u32 {
20 +
fn alloc(list: &mut List, value: i32) -> u32 {
21 21
    let idx = list.free;
22 22
    set list.free += 1;
23 23
    set list.pool[idx] = Node { value, next: nil };
24 24
    return idx;
25 25
}
26 26
27 27
/// Push a value onto the front of the list.
28 -
fn push(list: *mut List, value: i32) {
28 +
fn push(list: &mut List, value: i32) {
29 29
    let idx = alloc(list, value);
30 30
    set list.pool[idx].next = list.head;
31 31
    set list.head = idx;
32 32
}
33 33
34 34
/// Pop a value from the front of the list. Returns nil if empty.
35 -
fn pop(list: *mut List) -> ?i32 {
35 +
fn pop(list: &mut List) -> ?i32 {
36 36
    let idx = list.head else {
37 37
        return nil;
38 38
    };
39 39
    let value = list.pool[idx].value;
40 40
    set list.head = list.pool[idx].next;
41 41
    return value;
42 42
}
43 43
44 44
/// Get the length of the list.
45 -
fn length(list: *List) -> u32 {
45 +
fn length(list: &List) -> u32 {
46 46
    let mut count: u32 = 0;
47 47
    let mut cur = list.head;
48 48
    while let idx = cur {
49 49
        set count += 1;
50 50
        set cur = list.pool[idx].next;
51 51
    }
52 52
    return count;
53 53
}
54 54
55 55
/// Find a value in the list. Returns the node index if found, or nil.
56 -
fn find(list: *List, value: i32) -> ?u32 {
56 +
fn find(list: &List, value: i32) -> ?u32 {
57 57
    let mut cur = list.head;
58 58
    while let idx = cur {
59 59
        if list.pool[idx].value == value {
60 60
            return idx;
61 61
        }
63 63
    }
64 64
    return nil;
65 65
}
66 66
67 67
/// Get the value at a given index (0-based from head).
68 -
fn valueAt(list: *List, index: u32) -> ?i32 {
68 +
fn valueAt(list: &List, index: u32) -> ?i32 {
69 69
    let mut cur = list.head;
70 70
    let mut i: u32 = 0;
71 71
    while let idx = cur {
72 72
        if i == index {
73 73
            return list.pool[idx].value;
77 77
    }
78 78
    return nil;
79 79
}
80 80
81 81
/// Reverse the linked list in-place.
82 -
fn reverse(list: *mut List) {
82 +
fn reverse(list: &mut List) {
83 83
    let mut prev: ?u32 = nil;
84 84
    let mut cur = list.head;
85 85
    while let idx = cur {
86 86
        let next = list.pool[idx].next;
87 87
        set list.pool[idx].next = prev;
90 90
    }
91 91
    set list.head = prev;
92 92
}
93 93
94 94
/// Compute the sum of all values in the list.
95 -
fn sum(list: *List) -> i32 {
95 +
fn sum(list: &List) -> i32 {
96 96
    let mut total: i32 = 0;
97 97
    let mut cur = list.head;
98 98
    while let idx = cur {
99 99
        set total += list.pool[idx].value;
100 100
        set cur = list.pool[idx].next;
101 101
    }
102 102
    return total;
103 103
}
104 104
105 105
/// Reset the list to empty.
106 -
fn reset(list: *mut List) {
106 +
fn reset(list: &mut List) {
107 107
    set list.head = nil;
108 108
    set list.free = 0;
109 109
}
110 110
111 111
/// Test basic push and length.
112 -
fn testPushLength(list: *mut List) -> i32 {
112 +
fn testPushLength(list: &mut List) -> i32 {
113 113
    push(list, 10);
114 114
    push(list, 20);
115 115
    push(list, 30);
116 116
117 117
    assert length(list) == 3;
130 130
    assert v2 == 10;
131 131
    return 0;
132 132
}
133 133
134 134
/// Test pop.
135 -
fn testPop(list: *mut List) -> i32 {
135 +
fn testPop(list: &mut List) -> i32 {
136 136
    let v = pop(list) else {
137 137
        return 1;
138 138
    };
139 139
    assert v == 30;
140 140
    assert length(list) == 2;
144 144
    assert v0 == 20;
145 145
    return 0;
146 146
}
147 147
148 148
/// Test find.
149 -
fn testFind(list: *mut List) -> i32 {
149 +
fn testFind(list: &mut List) -> i32 {
150 150
    // 20 should be found.
151 151
    assert find(list, 20) <> nil;
152 152
    // 10 should be found.
153 153
    assert find(list, 10) <> nil;
154 154
    // 30 was popped, should not be found.
157 157
    assert find(list, 99) == nil;
158 158
    return 0;
159 159
}
160 160
161 161
/// Test reverse.
162 -
fn testReverse(list: *mut List) -> i32 {
162 +
fn testReverse(list: &mut List) -> i32 {
163 163
    // Current list: 20 -> 10
164 164
    // Add more elements.
165 165
    push(list, 40);
166 166
    push(list, 50);
167 167
    // List: 50 -> 40 -> 20 -> 10
193 193
    assert sum(list) == sumBefore;
194 194
    return 0;
195 195
}
196 196
197 197
/// Test building a larger list.
198 -
fn testLargerList(list: *mut List) -> i32 {
198 +
fn testLargerList(list: &mut List) -> i32 {
199 199
    reset(list);
200 200
201 201
    // Push 32 elements.
202 202
    let mut i: u32 = 0;
203 203
    while i < 32 {
test/tests/prog.lzw.rad +18 -18
23 23
    decoded: *mut [u8],
24 24
    decLen: u32,
25 25
    temp: *mut [u8],
26 26
}
27 27
28 -
fn initEncDict(s: *mut LzwState) {
28 +
unsafe fn initEncDict(s: *mut LzwState) {
29 29
    set s.encDictSize = INIT_DICT;
30 30
    let mut i: u32 = 0;
31 31
    while i < 256 {
32 32
        set s.encDict[i] = DictEntry { prefix: 0xFFFF, suffix: i as u8 };
33 33
        set i += 1;
34 34
    }
35 35
}
36 36
37 -
fn initDecDict(s: *mut LzwState) {
37 +
unsafe fn initDecDict(s: *mut LzwState) {
38 38
    set s.decDictSize = INIT_DICT;
39 39
    let mut i: u32 = 0;
40 40
    while i < 256 {
41 41
        set s.decDict[i] = DictEntry { prefix: 0xFFFF, suffix: i as u8 };
42 42
        set i += 1;
43 43
    }
44 44
}
45 45
46 -
fn dictLookup(s: *LzwState, prefix: u32, suffix: u8) -> u32 {
46 +
unsafe fn dictLookup(s: *LzwState, prefix: u32, suffix: u8) -> u32 {
47 47
    let mut i: u32 = 0;
48 48
    while i < s.encDictSize {
49 49
        if s.encDict[i].prefix == prefix and s.encDict[i].suffix == suffix {
50 50
            return i;
51 51
        }
52 52
        set i += 1;
53 53
    }
54 54
    return 0xFFFFFFFF;
55 55
}
56 56
57 -
fn dictAdd(s: *mut LzwState, prefix: u32, suffix: u8) {
57 +
unsafe fn dictAdd(s: *mut LzwState, prefix: u32, suffix: u8) {
58 58
    if s.encDictSize < MAX_DICT {
59 59
        set s.encDict[s.encDictSize] = DictEntry { prefix, suffix };
60 60
        set s.encDictSize += 1;
61 61
    }
62 62
}
63 63
64 -
fn emitCode(s: *mut LzwState, code: u32) {
64 +
unsafe fn emitCode(s: *mut LzwState, code: u32) {
65 65
    set s.encoded[s.encLen] = code;
66 66
    set s.encLen += 1;
67 67
}
68 68
69 -
fn encode(s: *mut LzwState, data: *[u8]) {
69 +
unsafe fn encode(s: *mut LzwState, data: *[u8]) {
70 70
    initEncDict(s);
71 71
    set s.encLen = 0;
72 72
73 73
    if data.len == 0 {
74 74
        emitCode(s, EOI_CODE);
92 92
    }
93 93
    emitCode(s, w);
94 94
    emitCode(s, EOI_CODE);
95 95
}
96 96
97 -
fn decodeString(s: *mut LzwState, code: u32) -> u32 {
97 +
unsafe fn decodeString(s: *mut LzwState, code: u32) -> u32 {
98 98
    let mut len: u32 = 0;
99 99
    let mut c: u32 = code;
100 100
    while c <> 0xFFFF and c < s.decDictSize {
101 101
        set s.temp[len] = s.decDict[c].suffix;
102 102
        set len += 1;
112 112
        set b -= 1;
113 113
    }
114 114
    return len;
115 115
}
116 116
117 -
fn firstByte(s: *LzwState, code: u32) -> u8 {
117 +
unsafe fn firstByte(s: *LzwState, code: u32) -> u8 {
118 118
    let mut c: u32 = code;
119 119
    while s.decDict[c].prefix <> 0xFFFF and s.decDict[c].prefix < s.decDictSize {
120 120
        set c = s.decDict[c].prefix;
121 121
    }
122 122
    return s.decDict[c].suffix;
123 123
}
124 124
125 -
fn decDictAdd(s: *mut LzwState, prefix: u32, suffix: u8) {
125 +
unsafe fn decDictAdd(s: *mut LzwState, prefix: u32, suffix: u8) {
126 126
    if s.decDictSize < MAX_DICT {
127 127
        set s.decDict[s.decDictSize] = DictEntry { prefix, suffix };
128 128
        set s.decDictSize += 1;
129 129
    }
130 130
}
131 131
132 -
fn outputByte(s: *mut LzwState, b: u8) {
132 +
unsafe fn outputByte(s: *mut LzwState, b: u8) {
133 133
    set s.decoded[s.decLen] = b;
134 134
    set s.decLen += 1;
135 135
}
136 136
137 -
fn decode(s: *mut LzwState) {
137 +
unsafe fn decode(s: *mut LzwState) {
138 138
    initDecDict(s);
139 139
    set s.decLen = 0;
140 140
141 141
    if s.encLen == 0 {
142 142
        return;
182 182
        }
183 183
        set prevCode = code;
184 184
    }
185 185
}
186 186
187 -
fn testSimple(s: *mut LzwState) -> i32 {
187 +
unsafe fn testSimple(s: *mut LzwState) -> i32 {
188 188
    let data: *[u8] = "ABABABABAB";
189 189
    encode(s, data);
190 190
191 191
    assert s.encoded[s.encLen - 1] == EOI_CODE;
192 192
198 198
        set i += 1;
199 199
    }
200 200
    return 0;
201 201
}
202 202
203 -
fn testDistinct(s: *mut LzwState) -> i32 {
203 +
unsafe fn testDistinct(s: *mut LzwState) -> i32 {
204 204
    let data: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
205 205
    encode(s, &data[..]);
206 206
207 207
    decode(s);
208 208
    assert s.decLen == 16;
212 212
        set i += 1;
213 213
    }
214 214
    return 0;
215 215
}
216 216
217 -
fn testRepetitive(s: *mut LzwState) -> i32 {
217 +
unsafe fn testRepetitive(s: *mut LzwState) -> i32 {
218 218
    let mut data: [u8; 64] = [65; 64];
219 219
    encode(s, &data[..]);
220 220
221 221
    assert s.encLen < 32;
222 222
228 228
        set i += 1;
229 229
    }
230 230
    return 0;
231 231
}
232 232
233 -
fn testMixed(s: *mut LzwState) -> i32 {
233 +
unsafe fn testMixed(s: *mut LzwState) -> i32 {
234 234
    let data: *[u8] = "TOBEORNOTTOBEORTOBEORNOT";
235 235
    encode(s, data);
236 236
237 237
    decode(s);
238 238
    assert s.decLen == 24;
245 245
    assert s.encLen - 1 < 24;
246 246
247 247
    return 0;
248 248
}
249 249
250 -
fn testEmpty(s: *mut LzwState) -> i32 {
250 +
unsafe fn testEmpty(s: *mut LzwState) -> i32 {
251 251
    encode(s, &[]);
252 252
    assert s.encLen == 1;
253 253
    assert s.encoded[0] == EOI_CODE;
254 254
255 255
    decode(s);
256 256
    assert s.decLen == 0;
257 257
    return 0;
258 258
}
259 259
260 -
fn testSingle(s: *mut LzwState) -> i32 {
260 +
unsafe fn testSingle(s: *mut LzwState) -> i32 {
261 261
    let data: *[u8] = "*";
262 262
    encode(s, data);
263 263
264 264
    decode(s);
265 265
    assert s.decLen == 1;
266 266
    assert s.decoded[0] == 42;
267 267
    return 0;
268 268
}
269 269
270 -
@default fn main() -> i32 {
270 +
@default unsafe fn main() -> i32 {
271 271
    let mut encDict: [DictEntry; 512] = [DictEntry { prefix: 0xFFFF, suffix: 0 }; 512];
272 272
    let mut decDict: [DictEntry; 512] = [DictEntry { prefix: 0xFFFF, suffix: 0 }; 512];
273 273
    let mut encoded: [u32; 512] = [0; 512];
274 274
    let mut decoded: [u8; 256] = [0; 256];
275 275
    let mut temp: [u8; 256] = [0; 256];
test/tests/prog.matmul.rad +5 -5
9 9
record Mat4 {
10 10
    rows: [[i32; 4]; 4],
11 11
}
12 12
13 13
/// Perform matrix multiplication: c = a * b.
14 -
fn matmul(c: *mut Mat4, a: *Mat4, b: *Mat4) {
14 +
fn matmul(c: &mut Mat4, a: &Mat4, b: &Mat4) {
15 15
    let mut i: u32 = 0;
16 16
    while i < N {
17 17
        let mut j: u32 = 0;
18 18
        while j < N {
19 19
            let mut sum: i32 = 0;
28 28
        set i += 1;
29 29
    }
30 30
}
31 31
32 32
/// Verify c matches expected.
33 -
fn verifyResult(c: *Mat4, expected: *Mat4) -> i32 {
33 +
fn verifyResult(c: &Mat4, expected: &Mat4) -> i32 {
34 34
    let mut i: u32 = 0;
35 35
    while i < N {
36 36
        let mut j: u32 = 0;
37 37
        while j < N {
38 38
            if c.rows[i][j] <> expected.rows[i][j] {
44 44
    }
45 45
    return 0;
46 46
}
47 47
48 48
/// Compute the trace (sum of diagonal) of a matrix.
49 -
fn trace(m: *Mat4) -> i32 {
49 +
fn trace(m: &Mat4) -> i32 {
50 50
    let mut sum: i32 = 0;
51 51
    let mut i: u32 = 0;
52 52
    while i < N {
53 53
        set sum += m.rows[i][i];
54 54
        set i += 1;
55 55
    }
56 56
    return sum;
57 57
}
58 58
59 59
/// Zero out a matrix.
60 -
fn zero(m: *mut Mat4) {
60 +
fn zero(m: &mut Mat4) {
61 61
    let mut i: u32 = 0;
62 62
    while i < N {
63 63
        let mut j: u32 = 0;
64 64
        while j < N {
65 65
            set m.rows[i][j] = 0;
68 68
        set i += 1;
69 69
    }
70 70
}
71 71
72 72
/// Multiply matrices multiple times to test repeated computation.
73 -
fn testRepeatedMultiply(c: *mut Mat4, a: *Mat4, b: *Mat4) -> i32 {
73 +
fn testRepeatedMultiply(c: &mut Mat4, a: &Mat4, b: &Mat4) -> i32 {
74 74
    // First pass already done, trace = 13+43+43+85 = 184.
75 75
    assert trace(c) == 184;
76 76
77 77
    // Zero out c and re-multiply to ensure idempotent.
78 78
    zero(c);
test/tests/prog.mersenne.rad +10 -10
10 10
record MtState {
11 11
    mt: *mut [u32],
12 12
    mti: u32,
13 13
}
14 14
15 -
fn mtInit(s: *mut MtState, seed: u32) {
15 +
unsafe fn mtInit(s: *mut MtState, seed: u32) {
16 16
    set s.mt[0] = seed;
17 17
    let mut i: u32 = 1;
18 18
    while i < N {
19 19
        let prev: u32 = s.mt[i - 1];
20 20
        let xored: u32 = prev ^ (prev >> 30);
34 34
        set i += 1;
35 35
    }
36 36
    set s.mti = N;
37 37
}
38 38
39 -
fn generateNumbers(s: *mut MtState) {
39 +
unsafe fn generateNumbers(s: *mut MtState) {
40 40
    let mut i: u32 = 0;
41 41
42 42
    while i < N - M {
43 43
        let y: u32 = (s.mt[i] & UPPER_MASK) | (s.mt[i + 1] & LOWER_MASK);
44 44
        let mut mag: u32 = 0;
67 67
    set s.mt[N - 1] = s.mt[M - 1] ^ (y >> 1) ^ mag;
68 68
69 69
    set s.mti = 0;
70 70
}
71 71
72 -
fn mtNext(s: *mut MtState) -> u32 {
72 +
unsafe fn mtNext(s: *mut MtState) -> u32 {
73 73
    if s.mti >= N {
74 74
        generateNumbers(s);
75 75
    }
76 76
77 77
    let mut y: u32 = s.mt[s.mti];
83 83
    set y ^= (y >> 18);
84 84
85 85
    return y;
86 86
}
87 87
88 -
fn testKnownSequence(s: *mut MtState) -> i32 {
88 +
unsafe fn testKnownSequence(s: *mut MtState) -> i32 {
89 89
    mtInit(s, 1);
90 90
91 91
    let v0: u32 = mtNext(s);
92 92
    assert v0 == 1791095845;
93 93
104 104
    assert v4 == 491263;
105 105
106 106
    return 0;
107 107
}
108 108
109 -
fn testDeterminism(s: *mut MtState) -> i32 {
109 +
unsafe fn testDeterminism(s: *mut MtState) -> i32 {
110 110
    mtInit(s, 42);
111 111
112 112
    let mut first: [u32; 10] = [0; 10];
113 113
    let mut i: u32 = 0;
114 114
    while i < 10 {
126 126
    }
127 127
128 128
    return 0;
129 129
}
130 130
131 -
fn testDifferentSeeds(s: *mut MtState) -> i32 {
131 +
unsafe fn testDifferentSeeds(s: *mut MtState) -> i32 {
132 132
    mtInit(s, 1);
133 133
    let a: u32 = mtNext(s);
134 134
135 135
    mtInit(s, 2);
136 136
    let b: u32 = mtNext(s);
143 143
    assert a <> c;
144 144
145 145
    return 0;
146 146
}
147 147
148 -
fn testChiSquared(s: *mut MtState) -> i32 {
148 +
unsafe fn testChiSquared(s: *mut MtState) -> i32 {
149 149
    mtInit(s, 12345);
150 150
151 151
    constant NUM_BINS: u32 = 16;
152 152
    constant NUM_SAMPLES: u32 = 1600;
153 153
    constant EXPECTED: u32 = 100;
187 187
    }
188 188
189 189
    return 0;
190 190
}
191 191
192 -
fn testRegeneration(s: *mut MtState) -> i32 {
192 +
unsafe fn testRegeneration(s: *mut MtState) -> i32 {
193 193
    mtInit(s, 7);
194 194
195 195
    let mut last: u32 = 0;
196 196
    let mut i: u32 = 0;
197 197
    while i < 700 {
218 218
    assert more <> 0;
219 219
220 220
    return 0;
221 221
}
222 222
223 -
fn testBitCoverage(s: *mut MtState) -> i32 {
223 +
unsafe fn testBitCoverage(s: *mut MtState) -> i32 {
224 224
    mtInit(s, 999);
225 225
226 226
    let mut orAll: u32 = 0;
227 227
    let mut andAll: u32 = 0xFFFFFFFF;
228 228
239 239
    assert andAll == 0;
240 240
241 241
    return 0;
242 242
}
243 243
244 -
@default fn main() -> i32 {
244 +
@default unsafe fn main() -> i32 {
245 245
    let mut mt: [u32; 624] = [0; 624];
246 246
    let mut s: MtState = MtState {
247 247
        mt: &mut mt[..],
248 248
        mti: 625,
249 249
    };
test/tests/prog.nqueens.rad +7 -7
9 9
    queens: *mut [i32],
10 10
    solutionCount: u32,
11 11
    boardSize: u32,
12 12
}
13 13
14 -
fn isSafe(b: *Board, row: u32, col: u32) -> bool {
14 +
unsafe fn isSafe(b: *Board, row: u32, col: u32) -> bool {
15 15
    let mut i: u32 = 0;
16 16
    while i < row {
17 17
        let qcol: i32 = b.queens[i];
18 18
        if qcol == col as i32 {
19 19
            return false;
26 26
        set i += 1;
27 27
    }
28 28
    return true;
29 29
}
30 30
31 -
fn solve(b: *mut Board, row: u32) {
31 +
unsafe fn solve(b: *mut Board, row: u32) {
32 32
    if row == b.boardSize {
33 33
        set b.solutionCount += 1;
34 34
        return;
35 35
    }
36 36
    let mut col: u32 = 0;
42 42
        }
43 43
        set col += 1;
44 44
    }
45 45
}
46 46
47 -
fn resetBoard(b: *mut Board) {
47 +
unsafe fn resetBoard(b: *mut Board) {
48 48
    let mut i: u32 = 0;
49 49
    while i < MAX_N {
50 50
        set b.queens[i] = -1;
51 51
        set i += 1;
52 52
    }
53 53
    set b.solutionCount = 0;
54 54
}
55 55
56 -
fn solveNQueens(b: *mut Board, n: u32) -> u32 {
56 +
unsafe fn solveNQueens(b: *mut Board, n: u32) -> u32 {
57 57
    resetBoard(b);
58 58
    set b.boardSize = n;
59 59
    solve(b, 0);
60 60
    return b.solutionCount;
61 61
}
62 62
63 -
fn testAllSizes(b: *mut Board) -> i32 {
63 +
unsafe fn testAllSizes(b: *mut Board) -> i32 {
64 64
    let expected: [u32; 9] = [0, 1, 0, 0, 2, 10, 4, 40, 92];
65 65
    let mut n: u32 = 1;
66 66
    while n <= 8 {
67 67
        let count: u32 = solveNQueens(b, n);
68 68
        if count <> expected[n] {
101 101
    }
102 102
103 103
    return 0;
104 104
}
105 105
106 -
fn testDeterminism(b: *mut Board) -> i32 {
106 +
unsafe fn testDeterminism(b: *mut Board) -> i32 {
107 107
    let count1: u32 = solveNQueens(b, 8);
108 108
    let count2: u32 = solveNQueens(b, 8);
109 109
    let count3: u32 = solveNQueens(b, 8);
110 110
111 111
    assert count1 == 92;
129 129
    assert expected[8] > expected[7];
130 130
131 131
    return 0;
132 132
}
133 133
134 -
@default fn main() -> i32 {
134 +
@default unsafe fn main() -> i32 {
135 135
    let mut queens: [i32; 8] = [-1; 8];
136 136
    let mut b: Board = Board {
137 137
        queens: &mut queens[..],
138 138
        solutionCount: 0,
139 139
        boardSize: 0,
test/tests/prog.rbtree.rad +16 -16
22 22
    root: u32,
23 23
    inorder: *mut [i32],
24 24
    inorderCount: u32,
25 25
}
26 26
27 -
fn allocNode(t: *mut RBTree, key: i32) -> u32 {
27 +
unsafe fn allocNode(t: *mut RBTree, key: i32) -> u32 {
28 28
    let idx: u32 = t.poolNext;
29 29
    set t.poolNext += 1;
30 30
    set t.pool[idx] = RBNode { key, color: RED, left: NIL, right: NIL, parent: NIL };
31 31
    return idx;
32 32
}
33 33
34 -
fn rotateLeft(t: *mut RBTree, x: u32) {
34 +
unsafe fn rotateLeft(t: *mut RBTree, x: u32) {
35 35
    let y: u32 = t.pool[x].right;
36 36
    set t.pool[x].right = t.pool[y].left;
37 37
    if t.pool[y].left <> NIL {
38 38
        set t.pool[t.pool[y].left].parent = x;
39 39
    }
47 47
    }
48 48
    set t.pool[y].left = x;
49 49
    set t.pool[x].parent = y;
50 50
}
51 51
52 -
fn rotateRight(t: *mut RBTree, x: u32) {
52 +
unsafe fn rotateRight(t: *mut RBTree, x: u32) {
53 53
    let y: u32 = t.pool[x].left;
54 54
    set t.pool[x].left = t.pool[y].right;
55 55
    if t.pool[y].right <> NIL {
56 56
        set t.pool[t.pool[y].right].parent = x;
57 57
    }
65 65
    }
66 66
    set t.pool[y].right = x;
67 67
    set t.pool[x].parent = y;
68 68
}
69 69
70 -
fn insertFixup(t: *mut RBTree, zArg: u32) {
70 +
unsafe fn insertFixup(t: *mut RBTree, zArg: u32) {
71 71
    let mut z: u32 = zArg;
72 72
    while t.pool[t.pool[z].parent].color == RED {
73 73
        if t.pool[z].parent == t.pool[t.pool[t.pool[z].parent].parent].left {
74 74
            let y: u32 = t.pool[t.pool[t.pool[z].parent].parent].right;
75 75
            if t.pool[y].color == RED {
105 105
        }
106 106
    }
107 107
    set t.pool[t.root].color = BLACK;
108 108
}
109 109
110 -
fn insert(t: *mut RBTree, key: i32) {
110 +
unsafe fn insert(t: *mut RBTree, key: i32) {
111 111
    let z: u32 = allocNode(t, key);
112 112
    let mut y: u32 = NIL;
113 113
    let mut x: u32 = t.root;
114 114
115 115
    while x <> NIL {
131 131
    }
132 132
133 133
    insertFixup(t, z);
134 134
}
135 135
136 -
fn search(t: *RBTree, key: i32) -> bool {
136 +
unsafe fn search(t: *RBTree, key: i32) -> bool {
137 137
    let mut x: u32 = t.root;
138 138
    while x <> NIL {
139 139
        if key == t.pool[x].key {
140 140
            return true;
141 141
        } else if key < t.pool[x].key {
145 145
        }
146 146
    }
147 147
    return false;
148 148
}
149 149
150 -
fn inorderWalk(t: *mut RBTree, x: u32) {
150 +
unsafe fn inorderWalk(t: *mut RBTree, x: u32) {
151 151
    if x == NIL {
152 152
        return;
153 153
    }
154 154
    inorderWalk(t, t.pool[x].left);
155 155
    set t.inorder[t.inorderCount] = t.pool[x].key;
156 156
    set t.inorderCount += 1;
157 157
    inorderWalk(t, t.pool[x].right);
158 158
}
159 159
160 -
fn countNodes(t: *RBTree, x: u32) -> u32 {
160 +
unsafe fn countNodes(t: *RBTree, x: u32) -> u32 {
161 161
    if x == NIL {
162 162
        return 0;
163 163
    }
164 164
    return 1 + countNodes(t, t.pool[x].left) + countNodes(t, t.pool[x].right);
165 165
}
166 166
167 -
fn blackHeight(t: *RBTree, x: u32) -> i32 {
167 +
unsafe fn blackHeight(t: *RBTree, x: u32) -> i32 {
168 168
    if x == NIL {
169 169
        return 1;
170 170
    }
171 171
    let leftBH: i32 = blackHeight(t, t.pool[x].left);
172 172
    let rightBH: i32 = blackHeight(t, t.pool[x].right);
182 182
        return leftBH + 1;
183 183
    }
184 184
    return leftBH;
185 185
}
186 186
187 -
fn noRedRed(t: *RBTree, x: u32) -> bool {
187 +
unsafe fn noRedRed(t: *RBTree, x: u32) -> bool {
188 188
    if x == NIL {
189 189
        return true;
190 190
    }
191 191
    if t.pool[x].color == RED {
192 192
        if t.pool[t.pool[x].left].color == RED {
200 200
        return false;
201 201
    }
202 202
    return noRedRed(t, t.pool[x].right);
203 203
}
204 204
205 -
fn resetTree(t: *mut RBTree) {
205 +
unsafe fn resetTree(t: *mut RBTree) {
206 206
    let mut i: u32 = 0;
207 207
    while i < POOL_SIZE {
208 208
        set t.pool[i] = RBNode { key: 0, color: BLACK, left: NIL, right: NIL, parent: NIL };
209 209
        set i += 1;
210 210
    }
211 211
    set t.poolNext = 1;
212 212
    set t.root = NIL;
213 213
    set t.inorderCount = 0;
214 214
}
215 215
216 -
fn testAscending(t: *mut RBTree) -> i32 {
216 +
unsafe fn testAscending(t: *mut RBTree) -> i32 {
217 217
    resetTree(t);
218 218
219 219
    let mut i: i32 = 0;
220 220
    while i < 32 {
221 221
        insert(t, i);
240 240
    }
241 241
242 242
    return 0;
243 243
}
244 244
245 -
fn testDescending(t: *mut RBTree) -> i32 {
245 +
unsafe fn testDescending(t: *mut RBTree) -> i32 {
246 246
    resetTree(t);
247 247
248 248
    let mut i: i32 = 31;
249 249
    while i >= 0 {
250 250
        insert(t, i);
267 267
    }
268 268
269 269
    return 0;
270 270
}
271 271
272 -
fn testRandom(t: *mut RBTree) -> i32 {
272 +
unsafe fn testRandom(t: *mut RBTree) -> i32 {
273 273
    resetTree(t);
274 274
275 275
    let mut inserted: [bool; 48] = [false; 48];
276 276
    let mut seed: u32 = 42;
277 277
    let mut count: u32 = 0;
310 310
    }
311 311
312 312
    return 0;
313 313
}
314 314
315 -
fn testHeight(t: *mut RBTree) -> i32 {
315 +
unsafe fn testHeight(t: *mut RBTree) -> i32 {
316 316
    resetTree(t);
317 317
318 318
    let mut i: i32 = 0;
319 319
    while i < 63 {
320 320
        insert(t, i);
326 326
    assert bh <= 7;
327 327
328 328
    return 0;
329 329
}
330 330
331 -
@default fn main() -> i32 {
331 +
@default unsafe fn main() -> i32 {
332 332
    let mut pool: [RBNode; 128] = [RBNode { key: 0, color: 1, left: 0, right: 0, parent: 0 }; 128];
333 333
    let mut inorder: [i32; 128] = [0; 128];
334 334
335 335
    let mut t: RBTree = RBTree {
336 336
        pool: &mut pool[..],
test/tests/prog.regex.rad +21 -21
35 35
    closure: *mut [u32],
36 36
    fragStack: *mut [Frag],
37 37
    fragTop: u32,
38 38
}
39 39
40 -
fn newState(nfa: *mut NfaState) -> u32 {
40 +
unsafe fn newState(nfa: *mut NfaState) -> u32 {
41 41
    let s: u32 = nfa.stateCount;
42 42
    set nfa.stateFirst[s] = NIL;
43 43
    set nfa.stateCount += 1;
44 44
    return s;
45 45
}
46 46
47 -
fn addTrans(nfa: *mut NfaState, from: u32, kind: u32, ch: u8, to: u32) {
47 +
unsafe fn addTrans(nfa: *mut NfaState, from: u32, kind: u32, ch: u8, to: u32) {
48 48
    let idx: u32 = nfa.transCount;
49 49
    set nfa.trans[idx] = Trans { kind, ch, to };
50 50
    set nfa.transNext[idx] = nfa.stateFirst[from];
51 51
    set nfa.stateFirst[from] = idx;
52 52
    set nfa.transCount += 1;
53 53
}
54 54
55 -
fn pushFrag(nfa: *mut NfaState, f: Frag) {
55 +
unsafe fn pushFrag(nfa: *mut NfaState, f: Frag) {
56 56
    set nfa.fragStack[nfa.fragTop] = f;
57 57
    set nfa.fragTop += 1;
58 58
}
59 59
60 -
fn popFrag(nfa: *mut NfaState) -> Frag {
60 +
unsafe fn popFrag(nfa: *mut NfaState) -> Frag {
61 61
    set nfa.fragTop -= 1;
62 62
    return nfa.fragStack[nfa.fragTop];
63 63
}
64 64
65 -
fn setEmpty(s: *mut [u32]) {
65 +
unsafe fn setEmpty(s: *mut [u32]) {
66 66
    set s[0] = 0;
67 67
    set s[1] = 0;
68 68
    set s[2] = 0;
69 69
    set s[3] = 0;
70 70
}
71 71
72 -
fn setAdd(s: *mut [u32], bit: u32) {
72 +
unsafe fn setAdd(s: *mut [u32], bit: u32) {
73 73
    let word: u32 = bit / 32;
74 74
    let pos: u32 = bit % 32;
75 75
    set s[word] |= (1 << pos);
76 76
}
77 77
78 -
fn setHas(s: *[u32], bit: u32) -> bool {
78 +
unsafe fn setHas(s: *[u32], bit: u32) -> bool {
79 79
    let word: u32 = bit / 32;
80 80
    let pos: u32 = bit % 32;
81 81
    return (s[word] >> pos) & 1 == 1;
82 82
}
83 83
84 -
fn setIsEmpty(s: *[u32]) -> bool {
84 +
unsafe fn setIsEmpty(s: *[u32]) -> bool {
85 85
    return s[0] == 0 and s[1] == 0 and s[2] == 0 and s[3] == 0;
86 86
}
87 87
88 -
fn setCopy(dst: *mut [u32], src: *[u32]) {
88 +
unsafe fn setCopy(dst: *mut [u32], src: *[u32]) {
89 89
    set dst[0] = src[0];
90 90
    set dst[1] = src[1];
91 91
    set dst[2] = src[2];
92 92
    set dst[3] = src[3];
93 93
}
94 94
95 -
fn epsilonClosure(nfa: *mut NfaState, states: *mut [u32]) {
95 +
unsafe fn epsilonClosure(nfa: *mut NfaState, states: *mut [u32]) {
96 96
    setCopy(nfa.closure, states);
97 97
98 98
    let mut changed: bool = true;
99 99
    while changed {
100 100
        set changed = false;
117 117
    }
118 118
119 119
    setCopy(states, nfa.closure);
120 120
}
121 121
122 -
fn resetNFA(nfa: *mut NfaState) {
122 +
unsafe fn resetNFA(nfa: *mut NfaState) {
123 123
    set nfa.stateCount = 0;
124 124
    set nfa.transCount = 0;
125 125
    set nfa.fragTop = 0;
126 126
    let mut i: u32 = 0;
127 127
    while i < MAX_STATES {
133 133
        set nfa.transNext[i] = NIL;
134 134
        set i += 1;
135 135
    }
136 136
}
137 137
138 -
fn compile(nfa: *mut NfaState, pattern: *[u8]) -> u32 {
138 +
unsafe fn compile(nfa: *mut NfaState, pattern: *[u8]) -> u32 {
139 139
    resetNFA(nfa);
140 140
141 141
    let mut i: u32 = 0;
142 142
    while i < pattern.len {
143 143
        let ch: u8 = pattern[i];
233 233
234 234
    set nfa.acceptState = frags[numFrags - 1].endState;
235 235
    return frags[0].start;
236 236
}
237 237
238 -
fn nfaMatches(nfa: *mut NfaState, start: u32, input: *[u8]) -> bool {
238 +
unsafe fn nfaMatches(nfa: *mut NfaState, start: u32, input: *[u8]) -> bool {
239 239
    setEmpty(nfa.current);
240 240
    setAdd(nfa.current, start);
241 241
    epsilonClosure(nfa, nfa.current);
242 242
243 243
    let mut i: u32 = 0;
271 271
    }
272 272
273 273
    return setHas(nfa.current, nfa.acceptState);
274 274
}
275 275
276 -
fn testLiteral(nfa: *mut NfaState) -> i32 {
276 +
unsafe fn testLiteral(nfa: *mut NfaState) -> i32 {
277 277
    let start: u32 = compile(nfa, "abc");
278 278
279 279
    assert nfaMatches(nfa, start, "abc");
280 280
    if nfaMatches(nfa, start, "ab") { return 2; }
281 281
    if nfaMatches(nfa, start, "abcd") { return 3; }
282 282
    if nfaMatches(nfa, start, "abd") { return 4; }
283 283
284 284
    return 0;
285 285
}
286 286
287 -
fn testStar(nfa: *mut NfaState) -> i32 {
287 +
unsafe fn testStar(nfa: *mut NfaState) -> i32 {
288 288
    let start: u32 = compile(nfa, "a*");
289 289
290 290
    assert nfaMatches(nfa, start, "");
291 291
    assert nfaMatches(nfa, start, "a");
292 292
    assert nfaMatches(nfa, start, "aaa");
293 293
    if nfaMatches(nfa, start, "b") { return 4; }
294 294
295 295
    return 0;
296 296
}
297 297
298 -
fn testPlus(nfa: *mut NfaState) -> i32 {
298 +
unsafe fn testPlus(nfa: *mut NfaState) -> i32 {
299 299
    let start: u32 = compile(nfa, "a+");
300 300
301 301
    if nfaMatches(nfa, start, "") { return 1; }
302 302
    assert nfaMatches(nfa, start, "a");
303 303
    assert nfaMatches(nfa, start, "aaaaa");
304 304
305 305
    return 0;
306 306
}
307 307
308 -
fn testQuestion(nfa: *mut NfaState) -> i32 {
308 +
unsafe fn testQuestion(nfa: *mut NfaState) -> i32 {
309 309
    let start: u32 = compile(nfa, "a?");
310 310
311 311
    assert nfaMatches(nfa, start, "");
312 312
    assert nfaMatches(nfa, start, "a");
313 313
    if nfaMatches(nfa, start, "aa") { return 3; }
314 314
315 315
    return 0;
316 316
}
317 317
318 -
fn testDot(nfa: *mut NfaState) -> i32 {
318 +
unsafe fn testDot(nfa: *mut NfaState) -> i32 {
319 319
    let start: u32 = compile(nfa, "..");
320 320
321 321
    assert nfaMatches(nfa, start, "ab");
322 322
    assert nfaMatches(nfa, start, "zz");
323 323
    if nfaMatches(nfa, start, "a") { return 3; }
324 324
    if nfaMatches(nfa, start, "abc") { return 4; }
325 325
326 326
    return 0;
327 327
}
328 328
329 -
fn testComplex(nfa: *mut NfaState) -> i32 {
329 +
unsafe fn testComplex(nfa: *mut NfaState) -> i32 {
330 330
    let start: u32 = compile(nfa, "ab*c");
331 331
332 332
    assert nfaMatches(nfa, start, "ac");
333 333
    assert nfaMatches(nfa, start, "abc");
334 334
    assert nfaMatches(nfa, start, "abbc");
337 337
    if nfaMatches(nfa, start, "adc") { return 6; }
338 338
339 339
    return 0;
340 340
}
341 341
342 -
fn testDotStar(nfa: *mut NfaState) -> i32 {
342 +
unsafe fn testDotStar(nfa: *mut NfaState) -> i32 {
343 343
    let start: u32 = compile(nfa, ".*");
344 344
345 345
    assert nfaMatches(nfa, start, "");
346 346
    assert nfaMatches(nfa, start, "hello");
347 347
    assert nfaMatches(nfa, start, "x");
348 348
349 349
    return 0;
350 350
}
351 351
352 -
@default fn main() -> i32 {
352 +
@default unsafe fn main() -> i32 {
353 353
    let mut trans: [Trans; 256] = [Trans { kind: 0, ch: 0, to: 0 }; 256];
354 354
    let mut stateFirst: [u32; 128] = [0xFFFFFFFF; 128];
355 355
    let mut transNext: [u32; 256] = [0xFFFFFFFF; 256];
356 356
    let mut current: [u32; 4] = [0; 4];
357 357
    let mut nextSet: [u32; 4] = [0; 4];
test/tests/prog.sha256.rad +7 -7
50 50
fn ssig1(x: u32) -> u32 {
51 51
    return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10);
52 52
}
53 53
54 54
/// Prepare the message schedule from a 16-word (512-bit) block.
55 -
fn prepareSchedule(s: *mut Sha256, block: *[u32]) {
55 +
unsafe fn prepareSchedule(s: *mut Sha256, block: *[u32]) {
56 56
    // Copy the first 16 words directly.
57 57
    let mut i: u32 = 0;
58 58
    while i < 16 {
59 59
        set s.w[i] = block[i];
60 60
        set i += 1;
65 65
        set i += 1;
66 66
    }
67 67
}
68 68
69 69
/// Run the 64-round compression function.
70 -
fn compress(s: *mut Sha256, k: *[u32]) {
70 +
unsafe fn compress(s: *mut Sha256, k: *[u32]) {
71 71
    let mut a: u32 = s.h[0];
72 72
    let mut b: u32 = s.h[1];
73 73
    let mut c: u32 = s.h[2];
74 74
    let mut d: u32 = s.h[3];
75 75
    let mut e: u32 = s.h[4];
101 101
    set s.h[6] += g;
102 102
    set s.h[7] += hh;
103 103
}
104 104
105 105
/// Reset hash state to initial values.
106 -
fn resetHash(s: *mut Sha256) {
106 +
unsafe fn resetHash(s: *mut Sha256) {
107 107
    let mut i: u32 = 0;
108 108
    while i < 8 {
109 109
        set s.h[i] = INIT_H[i];
110 110
        set i += 1;
111 111
    }
112 112
}
113 113
114 114
/// Pad and hash a short message (up to 55 bytes, fits in one 512-bit block).
115 -
fn hashMessage(s: *mut Sha256, k: *[u32], msg: *[u8]) -> i32 {
115 +
unsafe fn hashMessage(s: *mut Sha256, k: *[u32], msg: *[u8]) -> i32 {
116 116
    // The message must fit in a single block (max 55 bytes for 1-block padding).
117 117
    if msg.len > 55 {
118 118
        return -1;
119 119
    }
120 120
157 157
    return 0;
158 158
}
159 159
160 160
/// Test SHA-256 of the empty string "".
161 161
/// Expected: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
162 -
fn testEmpty(s: *mut Sha256, k: *[u32]) -> i32 {
162 +
unsafe fn testEmpty(s: *mut Sha256, k: *[u32]) -> i32 {
163 163
    assert hashMessage(s, k, &[]) == 0;
164 164
165 165
    assert s.h[0] == 0xE3B0C442;
166 166
    assert s.h[1] == 0x98FC1C14;
167 167
    assert s.h[2] == 0x9AFBF4C8;
173 173
    return 0;
174 174
}
175 175
176 176
/// Test SHA-256 of "abc".
177 177
/// Expected: ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad
178 -
fn testAbc(s: *mut Sha256, k: *[u32]) -> i32 {
178 +
unsafe fn testAbc(s: *mut Sha256, k: *[u32]) -> i32 {
179 179
    let msg: [u8; 3] = [0x61, 0x62, 0x63];
180 180
    assert hashMessage(s, k, &msg[..]) == 0;
181 181
182 182
    assert s.h[0] == 0xBA7816BF;
183 183
    assert s.h[1] == 0x8F01CFEA;
208 208
    //   result = 0x0F ^ 0xF0 ^ 0x00 = 0xFF
209 209
    assert maj(0xFF, 0x0F, 0xF0) == 0xFF;
210 210
    return 0;
211 211
}
212 212
213 -
@default fn main() -> i32 {
213 +
@default unsafe fn main() -> i32 {
214 214
    // SHA-256 round constants (first 32 bits of fractional parts of cube roots
215 215
    // of the first 64 primes).
216 216
    let k: [u32; 64] = [
217 217
        0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5,
218 218
        0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
test/tests/prog.sieve.rad +7 -7
2 2
//! Sieve of Eratosthenes.
3 3
//! Find all primes up to 256 using a boolean array.
4 4
//! Verify the count of primes matches the known value (54 primes <= 256).
5 5
6 6
/// Mark all multiples of p as composite.
7 -
fn markMultiples(sieve: *mut [bool], p: u32) {
7 +
unsafe fn markMultiples(sieve: *mut [bool], p: u32) {
8 8
    let mut i: u32 = p * p;
9 9
    while i < sieve.len {
10 10
        set sieve[i] = true;
11 11
        set i += p;
12 12
    }
13 13
}
14 14
15 15
/// Run the sieve algorithm.
16 -
fn runSieve(sieve: *mut [bool]) {
16 +
unsafe fn runSieve(sieve: *mut [bool]) {
17 17
    // 0 and 1 are not prime.
18 18
    set sieve[0] = true;
19 19
    set sieve[1] = true;
20 20
21 21
    let mut p: u32 = 2;
26 26
        set p += 1;
27 27
    }
28 28
}
29 29
30 30
/// Count the number of primes found.
31 -
fn countPrimes(sieve: *[bool]) -> u32 {
31 +
unsafe fn countPrimes(sieve: *[bool]) -> u32 {
32 32
    let mut count: u32 = 0;
33 33
    for composite in sieve {
34 34
        if not composite {
35 35
            set count += 1;
36 36
        }
37 37
    }
38 38
    return count;
39 39
}
40 40
41 41
/// Collect primes into a slice, return count.
42 -
fn collectPrimes(sieve: *[bool], primes: *mut [u32]) -> u32 {
42 +
unsafe fn collectPrimes(sieve: *[bool], primes: *mut [u32]) -> u32 {
43 43
    let mut count: u32 = 0;
44 44
    for composite, idx in sieve {
45 45
        if not composite {
46 46
            if count < primes.len {
47 47
                set primes[count] = idx;
51 51
    }
52 52
    return count;
53 53
}
54 54
55 55
/// Check that specific known primes are marked correctly.
56 -
fn verifyKnownPrimes(sieve: *[bool]) -> i32 {
56 +
unsafe fn verifyKnownPrimes(sieve: *[bool]) -> i32 {
57 57
    // Known small primes.
58 58
    let primes: [u32; 6] = [2, 3, 5, 7, 11, 13];
59 59
    for p in primes {
60 60
        if sieve[p] {
61 61
            return 1;
78 78
    assert sieve[250];
79 79
    return 0;
80 80
}
81 81
82 82
/// Verify that collected primes are in ascending order and all valid.
83 -
fn verifyCollected(sieve: *[bool]) -> i32 {
83 +
unsafe fn verifyCollected(sieve: *[bool]) -> i32 {
84 84
    let mut primesBuf: [u32; 64] = [0; 64];
85 85
    let count = collectPrimes(sieve, &mut primesBuf[..]);
86 86
87 87
    assert count == 54;
88 88
101 101
        set prev = p;
102 102
    }
103 103
    return 0;
104 104
}
105 105
106 -
@default fn main() -> i32 {
106 +
@default unsafe fn main() -> i32 {
107 107
    let mut sieve: [bool; 256] = [false; 256];
108 108
    runSieve(&mut sieve[..]);
109 109
110 110
    let r1 = verifyKnownPrimes(&sieve[..]);
111 111
    if r1 <> 0 {
test/tests/prog.symtab.rad +15 -15
38 38
    scopeDepth: u32,
39 39
    buckets: *mut [u32],
40 40
}
41 41
42 42
/// Simple string hash function.
43 -
fn hashName(name: *[u8]) -> u32 {
43 +
unsafe fn hashName(name: *[u8]) -> u32 {
44 44
    let mut h: u32 = 5381;
45 45
    for ch in name {
46 46
        set h = ((h << 5) + h) + ch as u32;
47 47
    }
48 48
    return h;
49 49
}
50 50
51 51
/// Initialize the symbol table.
52 -
fn init(tab: *mut SymTab) {
52 +
unsafe fn init(tab: *mut SymTab) {
53 53
    set tab.symbolCount = 0;
54 54
    set tab.scopeDepth = 0;
55 55
56 56
    for i in 0..HASH_SIZE {
57 57
        set tab.buckets[i] = NIL;
58 58
    }
59 59
}
60 60
61 61
/// Push a new scope.
62 -
fn pushScope(tab: *mut SymTab) {
62 +
unsafe fn pushScope(tab: *mut SymTab) {
63 63
    set tab.scopes[tab.scopeDepth] = ScopeMarker { symbolCount: tab.symbolCount };
64 64
    set tab.scopeDepth += 1;
65 65
}
66 66
67 67
/// Pop the current scope, removing all symbols defined in it.
68 -
fn popScope(tab: *mut SymTab) {
68 +
unsafe fn popScope(tab: *mut SymTab) {
69 69
    if tab.scopeDepth == 0 {
70 70
        return;
71 71
    }
72 72
    set tab.scopeDepth -= 1;
73 73
    let marker = tab.scopes[tab.scopeDepth];
91 91
        }
92 92
    }
93 93
}
94 94
95 95
/// Define a symbol in the current scope.
96 -
fn define(tab: *mut SymTab, name: *[u8], value: i32) -> u32 {
96 +
unsafe fn define(tab: *mut SymTab, name: *[u8], value: i32) -> u32 {
97 97
    let h = hashName(name);
98 98
    let bucket = h % HASH_SIZE;
99 99
100 100
    // Check for shadowed symbol with same name.
101 101
    let mut shadowIdx: u32 = NIL;
126 126
    set tab.symbolCount += 1;
127 127
    return idx;
128 128
}
129 129
130 130
/// Look up a symbol by name. Returns the value if found.
131 -
fn lookup(tab: *SymTab, name: *[u8]) -> ?i32 {
131 +
unsafe fn lookup(tab: *SymTab, name: *[u8]) -> ?i32 {
132 132
    let h = hashName(name);
133 133
    let bucket = h % HASH_SIZE;
134 134
    let mut cur = tab.buckets[bucket];
135 135
136 136
    while cur <> NIL {
141 141
    }
142 142
    return nil;
143 143
}
144 144
145 145
/// Update a symbol's value. Returns true if the symbol was found.
146 -
fn update(tab: *mut SymTab, name: *[u8], newValue: i32) -> bool {
146 +
unsafe fn update(tab: *mut SymTab, name: *[u8], newValue: i32) -> bool {
147 147
    let h = hashName(name);
148 148
    let bucket = h % HASH_SIZE;
149 149
    let mut cur = tab.buckets[bucket];
150 150
151 151
    while cur <> NIL {
157 157
    }
158 158
    return false;
159 159
}
160 160
161 161
/// Test basic define and lookup.
162 -
fn testBasic(tab: *mut SymTab) -> i32 {
162 +
unsafe fn testBasic(tab: *mut SymTab) -> i32 {
163 163
    init(tab);
164 164
    pushScope(tab);
165 165
166 166
    define(tab, "x", 10);
167 167
    define(tab, "y", 20);
190 190
    popScope(tab);
191 191
    return 0;
192 192
}
193 193
194 194
/// Test scope shadowing.
195 -
fn testShadowing(tab: *mut SymTab) -> i32 {
195 +
unsafe fn testShadowing(tab: *mut SymTab) -> i32 {
196 196
    init(tab);
197 197
    pushScope(tab);
198 198
    define(tab, "x", 1);
199 199
200 200
    // Verify outer x.
223 223
    popScope(tab);
224 224
    return 0;
225 225
}
226 226
227 227
/// Test deep nesting with shadowing.
228 -
fn testDeepNesting(tab: *mut SymTab) -> i32 {
228 +
unsafe fn testDeepNesting(tab: *mut SymTab) -> i32 {
229 229
    init(tab);
230 230
231 231
    // Define x at each of 8 scope levels.
232 232
    let mut i: u32 = 0;
233 233
    while i < 8 {
257 257
    popScope(tab);
258 258
    return 0;
259 259
}
260 260
261 261
/// Test multiple symbols per scope.
262 -
fn testMultipleSymbols(tab: *mut SymTab) -> i32 {
262 +
unsafe fn testMultipleSymbols(tab: *mut SymTab) -> i32 {
263 263
    init(tab);
264 264
    pushScope(tab);
265 265
266 266
    // Define a bunch of symbols.
267 267
    let names: [*[u8]; 8] = ["a", "bb", "ccc", "dddd", "eeeee", "ff", "ggg", "h"];
287 287
    popScope(tab);
288 288
    return 0;
289 289
}
290 290
291 291
/// Test update functionality.
292 -
fn testUpdate(tab: *mut SymTab) -> i32 {
292 +
unsafe fn testUpdate(tab: *mut SymTab) -> i32 {
293 293
    init(tab);
294 294
    pushScope(tab);
295 295
296 296
    define(tab, "counter", 0);
297 297
318 318
    popScope(tab);
319 319
    return 0;
320 320
}
321 321
322 322
/// Test scope isolation: symbols in popped scopes are gone.
323 -
fn testScopeIsolation(tab: *mut SymTab) -> i32 {
323 +
unsafe fn testScopeIsolation(tab: *mut SymTab) -> i32 {
324 324
    init(tab);
325 325
326 326
    pushScope(tab);
327 327
    define(tab, "outer", 1);
328 328
356 356
    popScope(tab);
357 357
    return 0;
358 358
}
359 359
360 360
/// Test interleaved defines and lookups across scopes using while-let.
361 -
fn testInterleaved(tab: *mut SymTab) -> i32 {
361 +
unsafe fn testInterleaved(tab: *mut SymTab) -> i32 {
362 362
    init(tab);
363 363
    pushScope(tab);
364 364
365 365
    define(tab, "a", 100);
366 366
    define(tab, "b", 200);
400 400
    popScope(tab);
401 401
    popScope(tab);
402 402
    return 0;
403 403
}
404 404
405 -
@default fn main() -> i32 {
405 +
@default unsafe fn main() -> i32 {
406 406
    let mut symbols: [Symbol; 256] = [Symbol { nameHash: 0, value: 0, depth: 0, next: NIL, shadow: NIL }; 256];
407 407
    let mut scopes: [ScopeMarker; 16] = [ScopeMarker { symbolCount: 0 }; 16];
408 408
    let mut buckets: [u32; 64] = [NIL; 64];
409 409
410 410
    let mut tab = SymTab {
test/tests/prog.tokenizer.rad +28 -28
76 76
fn isSpace(c: u8) -> bool {
77 77
    return c == 32 or c == 9 or c == 10 or c == 13;
78 78
}
79 79
80 80
/// Peek at the current character, returning nil at end.
81 -
fn peek(lex: *Lexer) -> ?u8 {
81 +
unsafe fn peek(lex: *Lexer) -> ?u8 {
82 82
    if lex.pos < lex.source.len {
83 83
        return lex.source[lex.pos];
84 84
    }
85 85
    return nil;
86 86
}
87 87
88 88
/// Advance the lexer by one character.
89 -
fn advance(lex: *mut Lexer) {
89 +
unsafe fn advance(lex: *mut Lexer) {
90 90
    if lex.pos < lex.source.len {
91 91
        set lex.pos += 1;
92 92
    }
93 93
}
94 94
95 95
/// Skip whitespace characters.
96 -
fn skipWhitespace(lex: *mut Lexer) {
96 +
unsafe fn skipWhitespace(lex: *mut Lexer) {
97 97
    while let ch = peek(lex); isSpace(ch) {
98 98
        advance(lex);
99 99
    }
100 100
}
101 101
102 102
/// Scan a number literal.
103 -
fn scanNumber(lex: *mut Lexer) -> i32 {
103 +
unsafe fn scanNumber(lex: *mut Lexer) -> i32 {
104 104
    let mut value: i32 = 0;
105 105
    while let ch = peek(lex); isDigit(ch) {
106 106
        set value = value * 10 + (ch - 48) as i32;
107 107
        advance(lex);
108 108
    }
109 109
    return value;
110 110
}
111 111
112 112
/// Get the next token from the lexer.
113 -
fn nextToken(lex: *mut Lexer) -> Token {
113 +
unsafe fn nextToken(lex: *mut Lexer) -> Token {
114 114
    skipWhitespace(lex);
115 115
116 116
    if let ch = peek(lex) {
117 117
        if isDigit(ch) {
118 118
            return Token::Number(scanNumber(lex));
131 131
    }
132 132
    return Token::Eof;
133 133
}
134 134
135 135
/// Tokenize the entire source into a token list.
136 -
fn tokenize(lex: *mut Lexer, list: *mut TokenList) -> bool {
136 +
unsafe fn tokenize(lex: *mut Lexer, list: *mut TokenList) -> bool {
137 137
    let mut done: bool = false;
138 138
    while not done {
139 139
        let tok = nextToken(lex);
140 140
        match tok {
141 141
            case Token::Invalid(_) => {
157 157
    }
158 158
    return true;
159 159
}
160 160
161 161
/// Allocate a new expression node.
162 -
fn newExpr(pool: *mut ExprPool, expr: Expr) -> u32 {
162 +
unsafe fn newExpr(pool: *mut ExprPool, expr: Expr) -> u32 {
163 163
    let idx = pool.count;
164 164
    set pool.nodes[idx] = expr;
165 165
    set pool.count += 1;
166 166
    return idx;
167 167
}
168 168
169 169
/// Get the current token in the parser.
170 -
fn currentToken(p: *Parser) -> Token {
170 +
unsafe fn currentToken(p: *Parser) -> Token {
171 171
    if p.pos < p.tokenCount {
172 172
        return p.tokens[p.pos];
173 173
    }
174 174
    return Token::Eof;
175 175
}
176 176
177 177
/// Advance the parser to the next token.
178 -
fn advanceParser(p: *mut Parser) {
178 +
unsafe fn advanceParser(p: *mut Parser) {
179 179
    if p.pos < p.tokenCount {
180 180
        set p.pos += 1;
181 181
    }
182 182
}
183 183
184 184
/// Parse a primary expression (number, parenthesized expression, or unary minus).
185 -
fn parsePrimary(p: *mut Parser) -> ?u32 {
185 +
unsafe fn parsePrimary(p: *mut Parser) -> ?u32 {
186 186
    let tok = currentToken(p);
187 187
188 188
    match tok {
189 189
        case Token::Number(n) => {
190 190
            advanceParser(p);
220 220
        }
221 221
    }
222 222
}
223 223
224 224
/// Parse multiplication and division (higher precedence).
225 -
fn parseMulDiv(p: *mut Parser, left: u32) -> u32 {
225 +
unsafe fn parseMulDiv(p: *mut Parser, left: u32) -> u32 {
226 226
    let mut result: u32 = left;
227 227
    let mut cont: bool = true;
228 228
229 229
    while cont {
230 230
        let tok = currentToken(p);
252 252
    }
253 253
    return result;
254 254
}
255 255
256 256
/// Parse addition and subtraction (lower precedence).
257 -
fn parseAddSub(p: *mut Parser, left: u32) -> u32 {
257 +
unsafe fn parseAddSub(p: *mut Parser, left: u32) -> u32 {
258 258
    let mut result: u32 = parseMulDiv(p, left);
259 259
    let mut cont: bool = true;
260 260
261 261
    while cont {
262 262
        let tok = currentToken(p);
286 286
    }
287 287
    return result;
288 288
}
289 289
290 290
/// Parse a full expression.
291 -
fn parseExpr(p: *mut Parser) -> ?u32 {
291 +
unsafe fn parseExpr(p: *mut Parser) -> ?u32 {
292 292
    let left = parsePrimary(p) else {
293 293
        return nil;
294 294
    };
295 295
    return parseAddSub(p, left);
296 296
}
297 297
298 298
/// Evaluate an expression tree.
299 -
fn eval(nodes: *[Expr], idx: u32) -> i32 {
299 +
unsafe fn eval(nodes: *[Expr], idx: u32) -> i32 {
300 300
    let node = nodes[idx];
301 301
    match node {
302 302
        case Expr::Num(n) => {
303 303
            return n;
304 304
        }
324 324
        }
325 325
    }
326 326
}
327 327
328 328
/// Count nodes in an expression tree.
329 -
fn countNodes(nodes: *[Expr], idx: u32) -> u32 {
329 +
unsafe fn countNodes(nodes: *[Expr], idx: u32) -> u32 {
330 330
    let node = nodes[idx];
331 331
    match node {
332 332
        case Expr::Num(_) => { return 1; }
333 333
        case Expr::BinOp(data) => {
334 334
            return 1 + countNodes(nodes, data.left) + countNodes(nodes, data.right);
338 338
        }
339 339
    }
340 340
}
341 341
342 342
/// Helper: tokenize, parse, and evaluate a string expression.
343 -
fn evaluate(
343 +
unsafe fn evaluate(
344 344
    source: *[u8],
345 345
    tokenBuf: *mut [Token],
346 346
    exprBuf: *mut [Expr]
347 347
) -> ?i32 {
348 348
    let mut lex = Lexer { source, pos: 0 };
365 365
    };
366 366
    return eval(exprBuf, root);
367 367
}
368 368
369 369
/// Test simple number.
370 -
fn testNumber(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
370 +
unsafe fn testNumber(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
371 371
    let result = evaluate("42", tokenBuf, exprBuf) else {
372 372
        return 1;
373 373
    };
374 374
    assert result == 42;
375 375
    return 0;
376 376
}
377 377
378 378
/// Test addition.
379 -
fn testAdd(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
379 +
unsafe fn testAdd(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
380 380
    let result = evaluate("3 + 4", tokenBuf, exprBuf) else {
381 381
        return 1;
382 382
    };
383 383
    assert result == 7;
384 384
    return 0;
385 385
}
386 386
387 387
/// Test precedence: multiplication before addition.
388 -
fn testPrecedence(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
388 +
unsafe fn testPrecedence(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
389 389
    let result = evaluate("2 + 3 * 4", tokenBuf, exprBuf) else {
390 390
        return 1;
391 391
    };
392 392
    assert result == 14;
393 393
    return 0;
394 394
}
395 395
396 396
/// Test parenthesized expression.
397 -
fn testParens(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
397 +
unsafe fn testParens(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
398 398
    let result = evaluate("(2 + 3) * 4", tokenBuf, exprBuf) else {
399 399
        return 1;
400 400
    };
401 401
    assert result == 20;
402 402
    return 0;
403 403
}
404 404
405 405
/// Test negation.
406 -
fn testNeg(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
406 +
unsafe fn testNeg(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
407 407
    let result = evaluate("-5 + 8", tokenBuf, exprBuf) else {
408 408
        return 1;
409 409
    };
410 410
    assert result == 3;
411 411
    return 0;
412 412
}
413 413
414 414
/// Test complex expression.
415 -
fn testComplex(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
415 +
unsafe fn testComplex(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
416 416
    // (10 - 3) * (2 + 1) = 7 * 3 = 21
417 417
    let result = evaluate("(10 - 3) * (2 + 1)", tokenBuf, exprBuf) else {
418 418
        return 1;
419 419
    };
420 420
    assert result == 21;
421 421
    return 0;
422 422
}
423 423
424 424
/// Test chained operations.
425 -
fn testChained(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
425 +
unsafe fn testChained(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
426 426
    // 100 - 20 - 30 - 10 = 40
427 427
    let result = evaluate("100 - 20 - 30 - 10", tokenBuf, exprBuf) else {
428 428
        return 1;
429 429
    };
430 430
    assert result == 40;
431 431
    return 0;
432 432
}
433 433
434 434
/// Test token counting via for-in.
435 -
fn testTokenCount(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
435 +
unsafe fn testTokenCount(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
436 436
    let mut lex = Lexer { source: "1 + 2 * 3", pos: 0 };
437 437
    let mut list = TokenList { tokens: tokenBuf, count: 0 };
438 438
    assert tokenize(&mut lex, &mut list);
439 439
440 440
    // Should be: 1, +, 2, *, 3, Eof = 6 tokens
455 455
456 456
    return 0;
457 457
}
458 458
459 459
/// Test division and mixed operations.
460 -
fn testDivision(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
460 +
unsafe fn testDivision(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
461 461
    // 20 / 4 + 3 = 5 + 3 = 8
462 462
    let result = evaluate("20 / 4 + 3", tokenBuf, exprBuf) else {
463 463
        return 1;
464 464
    };
465 465
    assert result == 8;
466 466
    return 0;
467 467
}
468 468
469 469
/// Test deeply nested parentheses.
470 -
fn testDeepNesting(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
470 +
unsafe fn testDeepNesting(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
471 471
    // ((((5)))) = 5
472 472
    let result = evaluate("((((5))))", tokenBuf, exprBuf) else {
473 473
        return 1;
474 474
    };
475 475
    assert result == 5;
476 476
    return 0;
477 477
}
478 478
479 479
/// Test node count of complex expression.
480 -
fn testNodeCount(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
480 +
unsafe fn testNodeCount(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 {
481 481
    let mut lex = Lexer { source: "1 + 2 * 3", pos: 0 };
482 482
    let mut list = TokenList { tokens: tokenBuf, count: 0 };
483 483
    assert tokenize(&mut lex, &mut list);
484 484
485 485
    let mut pool = ExprPool { nodes: exprBuf, count: 0 };
498 498
    let count = countNodes(exprBuf, root);
499 499
    assert count == 5;
500 500
    return 0;
501 501
}
502 502
503 -
@default fn main() -> i32 {
503 +
@default unsafe fn main() -> i32 {
504 504
    let mut tokenBuf: [Token; 128] = [Token::Eof; 128];
505 505
    let mut exprBuf: [Expr; 128] = [Expr::Num(0); 128];
506 506
507 507
    let r1 = testNumber(&mut tokenBuf[..], &mut exprBuf[..]);
508 508
    if r1 <> 0 {
test/tests/prog.vm.rad +19 -19
81 81
    /// Too many nested calls.
82 82
    CallOverflow,
83 83
}
84 84
85 85
/// Push a value onto the stack.
86 -
fn push(vm: *mut VM, value: i32) throws (VmError) {
86 +
unsafe fn push(vm: *mut VM, value: i32) throws (VmError) {
87 87
    if vm.sp >= MAX_STACK {
88 88
        throw VmError::StackOverflow;
89 89
    }
90 90
    set vm.stack[vm.sp] = value;
91 91
    set vm.sp += 1;
92 92
}
93 93
94 94
/// Pop a value from the stack.
95 -
fn pop(vm: *mut VM) -> i32 throws (VmError) {
95 +
unsafe fn pop(vm: *mut VM) -> i32 throws (VmError) {
96 96
    if vm.sp == 0 {
97 97
        throw VmError::StackUnderflow;
98 98
    }
99 99
    set vm.sp -= 1;
100 100
    return vm.stack[vm.sp];
101 101
}
102 102
103 103
/// Peek at the top of the stack without removing.
104 -
fn peek(vm: *VM) -> i32 throws (VmError) {
104 +
unsafe fn peek(vm: *VM) -> i32 throws (VmError) {
105 105
    if vm.sp == 0 {
106 106
        throw VmError::StackUnderflow;
107 107
    }
108 108
    return vm.stack[vm.sp - 1];
109 109
}
110 110
111 111
/// Execute the bytecode program.
112 -
fn execute(vm: *mut VM) -> i32 throws (VmError) {
112 +
unsafe fn execute(vm: *mut VM) -> i32 throws (VmError) {
113 113
    while vm.pc < vm.codeLen {
114 114
        let instr = vm.code[vm.pc];
115 115
        set vm.pc += 1;
116 116
117 117
        match instr {
234 234
    }
235 235
    throw VmError::InvalidPC;
236 236
}
237 237
238 238
/// Helper to initialize VM and run a program.
239 -
fn runProgram(
239 +
unsafe fn runProgram(
240 240
    code: *[Op],
241 241
    codeLen: u32,
242 242
    stackBuf: *mut [i32],
243 243
    localsBuf: *mut [i32],
244 244
    framesBuf: *mut [Frame]
255 255
    };
256 256
    return try execute(&mut vm);
257 257
}
258 258
259 259
/// Test basic arithmetic: 3 + 4 * 2 = 11
260 -
fn testArith(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
260 +
unsafe fn testArith(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
261 261
    let mut code: [Op; 8] = [Op::Halt; 8];
262 262
    set code[0] = Op::Push(3);
263 263
    set code[1] = Op::Push(4);
264 264
    set code[2] = Op::Push(2);
265 265
    set code[3] = Op::Mul;
270 270
    assert result == 11;
271 271
    return 0;
272 272
}
273 273
274 274
/// Test local variables: x = 5, y = 7, push x + y.
275 -
fn testLocals(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
275 +
unsafe fn testLocals(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
276 276
    let mut code: [Op; 16] = [Op::Halt; 16];
277 277
    set code[0] = Op::Push(5);
278 278
    set code[1] = Op::Store(0);   // x = 5
279 279
    set code[2] = Op::Push(7);
280 280
    set code[3] = Op::Store(1);   // y = 7
287 287
    assert result == 12;
288 288
    return 0;
289 289
}
290 290
291 291
/// Test conditional jump: if 3 > 2 then push 42 else push 99.
292 -
fn testConditional(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
292 +
unsafe fn testConditional(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
293 293
    let mut code: [Op; 16] = [Op::Halt; 16];
294 294
    set code[0] = Op::Push(3);
295 295
    set code[1] = Op::Push(2);
296 296
    set code[2] = Op::Gt;            // 3 > 2 => 1
297 297
    set code[3] = Op::JumpIfZero(6); // if false, jump to 6
305 305
    return 0;
306 306
}
307 307
308 308
/// Test loop: sum 1..5 using jumps.
309 309
/// local[0] = counter (starts at 1), local[1] = sum (starts at 0).
310 -
fn testLoop(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
310 +
unsafe fn testLoop(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
311 311
    let mut code: [Op; 32] = [Op::Halt; 32];
312 312
    set code[0] = Op::Push(1);
313 313
    set code[1] = Op::Store(0);      // counter = 1
314 314
    set code[2] = Op::Push(0);
315 315
    set code[3] = Op::Store(1);      // sum = 0
335 335
    assert result == 15;
336 336
    return 0;
337 337
}
338 338
339 339
/// Test function call: call a function that computes n*2+1 for n=10.
340 -
fn testCall(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
340 +
unsafe fn testCall(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
341 341
    let mut code: [Op; 32] = [Op::Halt; 32];
342 342
343 343
    // Main: push argument on stack, call function, halt.
344 344
    set code[0] = Op::Push(10);      // push argument
345 345
    set code[1] = Op::Call(5);       // call function at 5
362 362
    assert result == 21;
363 363
    return 0;
364 364
}
365 365
366 366
/// Test division by zero detection using try...catch with error binding.
367 -
fn testDivByZero(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
367 +
unsafe fn testDivByZero(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
368 368
    let mut code: [Op; 8] = [Op::Halt; 8];
369 369
    set code[0] = Op::Push(42);
370 370
    set code[1] = Op::Push(0);
371 371
    set code[2] = Op::Div;
372 372
    set code[3] = Op::Halt;
382 382
    assert caught == 1;
383 383
    return 0;
384 384
}
385 385
386 386
/// Test negation and equality.
387 -
fn testNegAndEq(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
387 +
unsafe fn testNegAndEq(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
388 388
    let mut code: [Op; 16] = [Op::Halt; 16];
389 389
    set code[0] = Op::Push(5);
390 390
    set code[1] = Op::Neg;           // -5
391 391
    set code[2] = Op::Push(-5);
392 392
    set code[3] = Op::Eq;            // -5 == -5 => 1
396 396
    assert result == 1;
397 397
    return 0;
398 398
}
399 399
400 400
/// Test factorial using recursive calls: fact(6) = 720.
401 -
fn testFactorial(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
401 +
unsafe fn testFactorial(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
402 402
    let mut code: [Op; 32] = [Op::Halt; 32];
403 403
404 404
    // Main: push 6, call fact, halt.
405 405
    set code[0] = Op::Push(6);
406 406
    set code[1] = Op::Call(4);       // call fact at 4
432 432
    assert result == 720;
433 433
    return 0;
434 434
}
435 435
436 436
/// Test dup instruction.
437 -
fn testDup(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
437 +
unsafe fn testDup(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
438 438
    let mut code: [Op; 8] = [Op::Halt; 8];
439 439
    set code[0] = Op::Push(7);
440 440
    set code[1] = Op::Dup;
441 441
    set code[2] = Op::Add;           // 7 + 7 = 14
442 442
    set code[3] = Op::Halt;
445 445
    assert result == 14;
446 446
    return 0;
447 447
}
448 448
449 449
/// Test stack underflow detection using try...catch with error binding.
450 -
fn testStackUnderflow(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
450 +
unsafe fn testStackUnderflow(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
451 451
    let mut code: [Op; 4] = [Op::Halt; 4];
452 452
    set code[0] = Op::Add;  // nothing on stack
453 453
    set code[1] = Op::Halt;
454 454
455 455
    let mut caught: i32 = 0;
463 463
    assert caught == 1;
464 464
    return 0;
465 465
}
466 466
467 467
/// Test that try...catch on success path does not execute catch block.
468 -
fn testSuccessNoCatch(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
468 +
unsafe fn testSuccessNoCatch(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
469 469
    let mut code: [Op; 4] = [Op::Halt; 4];
470 470
    set code[0] = Op::Push(99);
471 471
    set code[1] = Op::Halt;
472 472
473 473
    let mut caught: i32 = 0;
481 481
    assert result == 99;
482 482
    return 0;
483 483
}
484 484
485 485
/// Test call overflow detection by exhausting frames.
486 -
fn testCallOverflow(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
486 +
unsafe fn testCallOverflow(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
487 487
    let mut code: [Op; 4] = [Op::Halt; 4];
488 488
    // Infinite recursion: function calls itself.
489 489
    set code[0] = Op::Call(0);
490 490
    set code[1] = Op::Halt;
491 491
500 500
    assert caught == 1;
501 501
    return 0;
502 502
}
503 503
504 504
/// Test that catch with no binding works (discard the error).
505 -
fn testCatchNoBinding(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
505 +
unsafe fn testCatchNoBinding(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 {
506 506
    let mut code: [Op; 4] = [Op::Halt; 4];
507 507
    set code[0] = Op::Pop;  // underflow
508 508
    set code[1] = Op::Halt;
509 509
510 510
    // Catch without binding - just swallow the error.
511 511
    try runProgram(&code[..], 2, stackBuf, localsBuf, framesBuf) catch {};
512 512
    return 0;
513 513
}
514 514
515 -
@default fn main() -> i32 {
515 +
@default unsafe fn main() -> i32 {
516 516
    let mut stackBuf: [i32; 64] = [0; 64];
517 517
    let mut localsBuf: [i32; 128] = [0; 128];
518 518
    let mut framesBuf: [Frame; 8] = [Frame { returnAddr: 0, localBase: 0 }; 8];
519 519
520 520
    let r1 = testArith(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]);
test/tests/ptr.addressof.field.rad +1 -1
1 1
record Point { x: i32, y: i32 }
2 2
3 3
/// Takes the address of a record field.
4 -
fn addressOfField(p: *Point) -> *i32 {
4 +
unsafe fn addressOfField(p: *Point) -> *i32 {
5 5
    return &p.y;
6 6
}
test/tests/ptr.addressof.local.rad +3 -3
1 1
record Point { x: i32, y: i32 }
2 2
3 3
/// Address of a record local (aggregate) - value is already a pointer.
4 -
fn addressOfRecordLocal() -> *Point {
4 +
unsafe fn addressOfRecordLocal() -> *Point {
5 5
    let p = Point { x: 1, y: 2 };
6 6
    return &p;
7 7
}
8 8
9 9
/// Address of a scalar local - requires stack allocation.
10 -
fn addressOfScalarLocal() -> *i32 {
10 +
unsafe fn addressOfScalarLocal() -> *i32 {
11 11
    let x: i32 = 42;
12 12
    return &x;
13 13
}
14 14
15 15
/// Address of dereference - should return the original pointer.
16 -
fn addressOfDeref(ptr: *i32) -> *i32 {
16 +
unsafe fn addressOfDeref(ptr: *i32) -> *i32 {
17 17
    return &(*ptr);
18 18
}
test/tests/ptr.addressof.rad +1 -1
1 1
/// Takes the address of an array element.
2 -
fn addressOfElem(arr: [i32; 4], idx: u32) -> *i32 {
2 +
unsafe fn addressOfElem(arr: [i32; 4], idx: u32) -> *i32 {
3 3
    return &arr[idx];
4 4
}
test/tests/ptr.assign.rad +1 -1
1 1
//! returns: 42
2 -
@default fn main() -> i32 {
2 +
@default unsafe fn main() -> i32 {
3 3
    let mut x: i32 = 1;
4 4
    let mut ptr: *mut i32 = &mut x;
5 5
6 6
    set *ptr = 42;
7 7
test/tests/ptr.deref.rad +4 -4
1 1
//! returns: 65
2 2
//! Test pointer dereference in various contexts.
3 3
4 -
fn derefArrayIndex(ary: [i32; 3]) -> i32 {
4 +
unsafe fn derefArrayIndex(ary: [i32; 3]) -> i32 {
5 5
    let x: *i32 = &ary[1];
6 6
    return *x;
7 7
}
8 8
9 -
fn derefSliceIndex(slc: *[i32]) -> i32 {
9 +
unsafe fn derefSliceIndex(slc: *[i32]) -> i32 {
10 10
    let x: *i32 = &slc[2];
11 11
    return *x;
12 12
}
13 13
14 -
fn derefBinop(x: i32, y: i32) -> i32 {
14 +
unsafe fn derefBinop(x: i32, y: i32) -> i32 {
15 15
    let px: *i32 = &x;
16 16
    let py: *i32 = &y;
17 17
18 18
    return *px + *py;
19 19
}
20 20
21 -
@default fn main() -> i32 {
21 +
@default unsafe fn main() -> i32 {
22 22
    let x: i32  = 42;
23 23
    let y: *i32 = &x;
24 24
    let z: i32  = *y; // 42
25 25
    let r: i32 = derefBinop(3, 6); // 9
26 26
    let a: i32 = derefArrayIndex([7, 8, 9]); // 8
test/tests/ptr.deref.record.rad +2 -2
1 1
record Point { x: i32, y: i32 }
2 2
3 3
/// Dereferences a record pointer and accesses a field.
4 -
fn derefField(p: *Point) -> i32 {
4 +
unsafe fn derefField(p: *Point) -> i32 {
5 5
    return (*p).x;
6 6
}
7 7
8 8
/// Dereferences a record pointer and copies to a local.
9 -
fn derefCopy(p: *Point) -> i32 {
9 +
unsafe fn derefCopy(p: *Point) -> i32 {
10 10
    let copy: Point = *p;
11 11
    return copy.y;
12 12
}
test/tests/ptr.eq.rad +4 -4
4 4
    x: i32,
5 5
    y: i32,
6 6
}
7 7
8 8
// Test that pointer equality uses address comparison, not value comparison.
9 -
fn testPtrSameAddress() -> bool {
9 +
unsafe fn testPtrSameAddress() -> bool {
10 10
    let p1 = Point { x: 1, y: 2 };
11 11
    let a = &p1;
12 12
    let b = &p1;
13 13
14 14
    return a == b;  // Same address, should be equal.
15 15
}
16 16
17 -
fn testPtrDifferentAddressSameValues() -> bool {
17 +
unsafe fn testPtrDifferentAddressSameValues() -> bool {
18 18
    let p1 = Point { x: 1, y: 2 };
19 19
    let p2 = Point { x: 1, y: 2 };  // Same values as p1, but different address.
20 20
    let a = &p1;
21 21
    let b = &p2;
22 22
23 23
    return not (a == b);  // Different addresses, should NOT be equal.
24 24
}
25 25
26 -
fn testPtrDifferentAddressDifferentValues() -> bool {
26 +
unsafe fn testPtrDifferentAddressDifferentValues() -> bool {
27 27
    let p1 = Point { x: 1, y: 2 };
28 28
    let p2 = Point { x: 3, y: 4 };
29 29
    let a = &p1;
30 30
    let b = &p2;
31 31
32 32
    return not (a == b);  // Different addresses, should NOT be equal.
33 33
}
34 34
35 -
@default fn main() -> i32 {
35 +
@default unsafe fn main() -> i32 {
36 36
    assert testPtrSameAddress();
37 37
    assert testPtrDifferentAddressSameValues();
38 38
    assert testPtrDifferentAddressDifferentValues();
39 39
    return 0;
40 40
}
test/tests/ptr.mutate.rad +3 -3
1 1
//! returns: 42
2 2
3 -
fn mutate1(ptr: *mut i32) {
3 +
unsafe fn mutate1(ptr: *mut i32) {
4 4
    set *ptr = 39;
5 5
}
6 6
7 -
fn mutate2(ptr: *mut i32) {
7 +
unsafe fn mutate2(ptr: *mut i32) {
8 8
    set *ptr += 2;
9 9
    set *ptr += 1;
10 10
}
11 11
12 -
@default fn main() -> i32 {
12 +
@default unsafe fn main() -> i32 {
13 13
    let mut ptr: i32 = 0;
14 14
15 15
    mutate1(&mut ptr);
16 16
    mutate2(&mut ptr);
17 17
test/tests/ptr.opaque.rad +7 -7
1 1
//! returns: 0
2 2
//! Test basic opaque pointer usage (automatic coercion).
3 -
fn testOpaqueCasting() -> bool {
3 +
unsafe fn testOpaqueCasting() -> bool {
4 4
    let x: u32 = 42;
5 5
    let ptr: *u8 = &x as *u8;
6 6
    let opq: *opaque = ptr;  // Automatic coercion from *u8 to *opaque.
7 7
    let back: *u8 = opq as *u8;
8 8
9 9
    return back == ptr;
10 10
}
11 11
12 12
/// Test that opaque can be used in function parameters.
13 -
fn takesOpaque(ptr: *opaque, orig: *u8) -> bool {
13 +
unsafe fn takesOpaque(ptr: *opaque, orig: *u8) -> bool {
14 14
    let back: *u8 = ptr as *u8;
15 15
    return back == orig;
16 16
}
17 17
18 -
fn testOpaqueParams() -> bool {
18 +
unsafe fn testOpaqueParams() -> bool {
19 19
    let x: u32 = 42;
20 20
    let ptr: *u8 = &x as *u8;
21 21
    return takesOpaque(ptr, ptr);  // Automatic coercion in function call.
22 22
}
23 23
24 24
/// Test that opaque can be used in return types.
25 -
fn returnsOpaque(ptr: *u8) -> *opaque {
25 +
unsafe fn returnsOpaque(ptr: *u8) -> *opaque {
26 26
    return ptr;  // Automatic coercion in return.
27 27
}
28 28
29 -
fn testOpaqueReturn() -> bool {
29 +
unsafe fn testOpaqueReturn() -> bool {
30 30
    let x: u32 = 42;
31 31
    let ptr: *u8 = &x as *u8;
32 32
    let opq: *opaque = returnsOpaque(ptr);
33 33
    return opq as *u8 == ptr;
34 34
}
35 35
36 36
/// Test nullable opaque pointers.
37 -
fn testNullableOpaque() -> bool {
37 +
unsafe fn testNullableOpaque() -> bool {
38 38
    let mut opt: ?*opaque = nil;
39 39
    if opt == nil {
40 40
        let x: u32 = 42;
41 41
        let ptr: *u8 = &x as *u8;
42 42
        set opt = ptr;  // Automatic coercion in assignment.
46 46
        }
47 47
    }
48 48
    return false;
49 49
}
50 50
51 -
@default fn main() -> u32 {
51 +
@default unsafe fn main() -> u32 {
52 52
    assert testOpaqueCasting();
53 53
    assert testOpaqueParams();
54 54
    assert testOpaqueReturn();
55 55
    assert testNullableOpaque();
56 56
    return 0;
test/tests/ptr.subscript.assign.rad +1 -1
1 1
/// Writes to a slice element through subscript.
2 -
fn subscriptAssign(arr: *mut [i32], idx: u32, val: i32) {
2 +
unsafe fn subscriptAssign(arr: *mut [i32], idx: u32, val: i32) {
3 3
    set arr[idx] = val;
4 4
}
test/tests/range.arithmetic.rad +1 -1
1 1
//! returns: 0
2 2
3 -
@default fn main() -> i32 {
3 +
@default unsafe fn main() -> i32 {
4 4
    let arr: [i32; 6] = [10, 20, 30, 40, 50, 60];
5 5
    let len: u32 = 6;
6 6
7 7
    // Arithmetic in range start (no parens needed).
8 8
    let s = &arr[len - 3..len];
test/tests/record.copy.rad +3 -3
69 69
    assert a[2].x == 561;
70 70
    assert a[3].x == 12;
71 71
    return 0;
72 72
}
73 73
74 -
fn func4(s: S) -> i32 {
74 +
unsafe fn func4(s: S) -> i32 {
75 75
    let mut a: [S; 2] = undefined;
76 76
    set a[0] = s;
77 77
78 78
    assert a[0].x == 561;
79 79
    assert a[0].y == 938;
80 80
    assert a[0].z == 102;
81 81
    return 0;
82 82
}
83 83
84 -
fn func5(s: S) -> i32 {
84 +
unsafe fn func5(s: S) -> i32 {
85 85
    let mut a: [S; 2] = undefined;
86 86
    let t: S = makeS(s.x, s.y, s.z);
87 87
    set a[0] = t;
88 88
89 89
    assert a[0].x == 561;
90 90
    assert a[0].y == 938;
91 91
    assert a[0].z == 102;
92 92
    return 0;
93 93
}
94 94
95 -
@default fn main() -> i32 {
95 +
@default unsafe fn main() -> i32 {
96 96
    let s: S = S { x: 561, y: 938, z: 102 };
97 97
98 98
    let r1: i32 = func1(s);
99 99
    if r1 <> 0 {
100 100
        return 10 + r1;
test/tests/record.ptr.access.rad +1 -1
3 3
record Point {
4 4
    x: i32,
5 5
    y: i32,
6 6
}
7 7
8 -
@default fn main() -> i32 {
8 +
@default unsafe fn main() -> i32 {
9 9
    let mut p: Point = Point { x: 11, y: 31 };
10 10
    let mut r: *Point = &p;
11 11
12 12
    let mut x: i32 = r.x;
13 13
    let mut y: i32 = r.y;
test/tests/record.ptr.mutate.rad +2 -2
3 3
record Point {
4 4
    x: i32,
5 5
    y: i32,
6 6
}
7 7
8 -
fn mutate(p: *mut Point) {
8 +
unsafe fn mutate(p: *mut Point) {
9 9
    set p.x = 4;
10 10
    set p.y = 38;
11 11
}
12 12
13 -
@default fn main() -> i32 {
13 +
@default unsafe fn main() -> i32 {
14 14
    let mut p: Point = Point { x: 0, y: 0 };
15 15
16 16
    mutate(&mut p);
17 17
18 18
    return p.x + p.y;
test/tests/ref.if.bug.rad +3 -3
2 2
//! Regression test: address-of inside an if branch.
3 3
//!
4 4
//! When `&mut var` appears in only one branch of an if/else, the merge
5 5
//! block's phi merges the original integer value with a stack pointer.
6 6
7 -
fn store(ptr: *mut u32, val: u32) {
7 +
unsafe fn store(ptr: *mut u32, val: u32) {
8 8
    set *ptr = val;
9 9
}
10 10
11 -
fn testIfBranch(cond: bool) -> u32 {
11 +
unsafe fn testIfBranch(cond: bool) -> u32 {
12 12
    let mut val: u32 = 42;
13 13
    if cond {
14 14
        store(&mut val, 99);
15 15
    }
16 16
    return val;
17 17
}
18 18
19 -
@default fn main() -> i32 {
19 +
@default unsafe fn main() -> i32 {
20 20
    assert testIfBranch(false) == 42;
21 21
    assert testIfBranch(true) == 99;
22 22
    return 0;
23 23
}
test/tests/ref.immut.loop.bug.rad +3 -3
4 4
//! Even though the variable is not `mut`, taking its address inside a
5 5
//! loop can produce the same SSA/pointer phi conflict as with mutable
6 6
//! variables: the loop header merges the original integer with a pointer
7 7
//! from the stack slot created by `&x`.
8 8
9 -
fn read(ptr: *u32) -> u32 {
9 +
unsafe fn read(ptr: *u32) -> u32 {
10 10
    return *ptr;
11 11
}
12 12
13 -
fn testZeroIter(n: u32) -> u32 {
13 +
unsafe fn testZeroIter(n: u32) -> u32 {
14 14
    let val: u32 = 42;
15 15
    let mut i: u32 = 0;
16 16
    while i < n {
17 17
        let _ = read(&val);
18 18
        set i += 1;
19 19
    }
20 20
    return val;
21 21
}
22 22
23 -
@default fn main() -> i32 {
23 +
@default unsafe fn main() -> i32 {
24 24
    assert testZeroIter(0) == 42;
25 25
    assert testZeroIter(3) == 42;
26 26
    return 0;
27 27
}
test/tests/ref.mut.ptr.rad +2 -2
1 1
//! returns: 84
2 2
//! Test mutable pointer references.
3 3
4 -
fn store(ptr: *mut i32) {
4 +
unsafe fn store(ptr: *mut i32) {
5 5
    set *ptr = 42;
6 6
}
7 7
8 -
@default fn main() -> i32 {
8 +
@default unsafe fn main() -> i32 {
9 9
    let mut a: i32 = 0;
10 10
    let p: *mut i32 = &mut a;
11 11
    store(p);
12 12
13 13
    let mut b: i32 = 0;
test/tests/reference.array.repeat.rad +1 -1
1 1
/// Taking a reference to an array repeat literal yields a static slice.
2 -
fn repeatedSlice() -> *[u32] {
2 +
unsafe fn repeatedSlice() -> *[u32] {
3 3
    return &[(7 as u32); 3];
4 4
}
test/tests/slice.alloc.loop.rad +3 -3
1 1
//! returns: 0
2 2
//! Test returning a slice from a function and iterating over it.
3 3
//! Exercises the same code path as createBlock's vars initialization.
4 4
//! The slice is returned through a return buffer (> 8 bytes).
5 5
6 -
fn makeSlice(buf: *mut [u8], count: u32) -> *mut [u8] {
6 +
unsafe fn makeSlice(buf: *mut [u8], count: u32) -> *mut [u8] {
7 7
    if count == 0 {
8 8
        return &mut [];
9 9
    }
10 10
    return @sliceOf(&mut buf[0], count);
11 11
}
12 12
13 -
fn initSlice(buf: *mut [u8], count: u32) -> i32 {
13 +
unsafe fn initSlice(buf: *mut [u8], count: u32) -> i32 {
14 14
    let s = makeSlice(buf, count);
15 15
16 16
    // This loop pattern matches createBlock's vars initialization.
17 17
    for i in 0..s.len {
18 18
        set s[i] = 0;
19 19
    }
20 20
21 21
    return s.len as i32;
22 22
}
23 23
24 -
@default fn main() -> i32 {
24 +
@default unsafe fn main() -> i32 {
25 25
    let mut buf: [u8; 64] = undefined;
26 26
27 27
    let n = initSlice(&mut buf[..], 5);
28 28
    assert n == 5;
29 29
test/tests/slice.append.rad +9 -7
5 5
record Arena {
6 6
    data: *mut [u8],
7 7
    offset: u32,
8 8
}
9 9
10 -
fn newArena(data: *mut [u8]) -> Arena {
10 +
unsafe fn newArena(data: *mut [u8]) -> Arena {
11 11
    return Arena { data, offset: 0 };
12 12
}
13 13
14 -
fn arenaAlloc(arena: *mut Arena, size: u32, al: u32) -> *mut opaque {
14 +
unsafe fn arenaAlloc(arena: *mut Arena, size: u32, al: u32) -> *mut opaque {
15 15
    let aligned = (arena.offset + al - 1) / al * al;
16 16
    let newOffset = aligned + size;
17 17
18 18
    assert newOffset <= arena.data.len as u32;
19 19
28 28
    func: fn(*mut opaque, u32, u32) -> *mut opaque,
29 29
    ctx: *mut opaque,
30 30
}
31 31
32 32
fn arenaAllocFn(ctx: *mut opaque, size: u32, al: u32) -> *mut opaque {
33 -
    let arena = ctx as *mut Arena;
34 -
    return arenaAlloc(arena, size, al);
33 +
    unsafe {
34 +
        let arena = ctx as *mut Arena;
35 +
        return arenaAlloc(arena, size, al);
36 +
    }
35 37
}
36 38
37 -
fn arenaAllocator(arena: *mut Arena) -> Allocator {
39 +
unsafe fn arenaAllocator(arena: *mut Arena) -> Allocator {
38 40
    return Allocator {
39 41
        func: arenaAllocFn,
40 42
        ctx: arena as *mut opaque,
41 43
    };
42 44
}
43 45
44 -
static BUF: [u8; 4096] = undefined;
46 +
unsafe static BUF: [u8; 4096] = undefined;
45 47
46 -
@default fn main() -> i32 {
48 +
@default unsafe fn main() -> i32 {
47 49
    let mut arena = newArena(&mut BUF[..]);
48 50
    let a = arenaAllocator(&mut arena);
49 51
50 52
    // Allocate initial capacity of 4.
51 53
    let ptr = arenaAlloc(&mut arena, @sizeOf(i32) * 4, @alignOf(i32));
test/tests/slice.assign.rad +1 -2
1 1
//! returns: 0
2 -
3 -
@default fn main() -> i32 {
2 +
@default unsafe fn main() -> i32 {
4 3
    // Fill entire array.
5 4
    let mut a: [i32; 4] = [1, 2, 3, 4];
6 5
    set a[..] = 0;
7 6
    assert a == [0, 0, 0, 0];
8 7
test/tests/slice.basic.rad +6 -6
1 1
/// Returns the length field from a slice header.
2 -
fn sliceLen(s: *[i32]) -> u32 {
2 +
unsafe fn sliceLen(s: *[i32]) -> u32 {
3 3
    return s.len;
4 4
}
5 5
6 6
/// Returns the capacity field from a slice header.
7 -
fn sliceCap(s: *[i32]) -> u32 {
7 +
unsafe fn sliceCap(s: *[i32]) -> u32 {
8 8
    return s.cap;
9 9
}
10 10
11 11
/// Returns the pointer field from a slice header.
12 -
fn slicePtr(s: *[i32]) -> *i32 {
12 +
unsafe fn slicePtr(s: *[i32]) -> *i32 {
13 13
    return s.ptr;
14 14
}
15 15
16 16
/// Copies a slice header into a new binding and returns it.
17 -
fn sliceCopy(s: *[i32]) -> *[i32] {
17 +
unsafe fn sliceCopy(s: *[i32]) -> *[i32] {
18 18
    let c: *[i32] = s;
19 19
    return c;
20 20
}
21 21
22 22
/// Builds a slice header from a pointer and length.
23 -
fn sliceOf(ptr: *i32, len: u32) -> *[i32] {
23 +
unsafe fn sliceOf(ptr: *i32, len: u32) -> *[i32] {
24 24
    return @sliceOf(ptr, len);
25 25
}
26 26
27 27
/// Builds a slice header from pointer, length, and capacity.
28 -
fn sliceOfWithCap(ptr: *i32, len: u32, cap: u32) -> *[i32] {
28 +
unsafe fn sliceOfWithCap(ptr: *i32, len: u32, cap: u32) -> *[i32] {
29 29
    return @sliceOf(ptr, len, cap);
30 30
}
test/tests/slice.cap.rad +1 -1
1 1
//! returns: 0
2 2
//! Test slice capacity field and @sliceOf builtin.
3 3
4 -
@default fn main() -> i32 {
4 +
@default unsafe fn main() -> i32 {
5 5
    // Test that regular slices have cap == len.
6 6
    let mut arr: [i32; 4] = [10, 20, 30, 40];
7 7
    let s = &mut arr[..];
8 8
9 9
    if s.cap <> 4 {
test/tests/slice.delete.rad +1 -1
1 1
//! returns: 0
2 2
//! Test slice .delete() method.
3 3
4 -
@default fn main() -> i32 {
4 +
@default unsafe fn main() -> i32 {
5 5
    let mut arr: [i32; 5] = [10, 20, 30, 40, 50];
6 6
    let mut s = &mut arr[..];
7 7
8 8
    // Delete middle element (index 2: value 30).
9 9
    s.delete(2);
test/tests/slice.empty.suffix.rad +4 -4
1 1
//! returns: 0
2 2
3 -
fn checkArraySuffix() {
3 +
unsafe fn checkArraySuffix() {
4 4
    let xs: [u8; 3] = [10, 20, 30];
5 5
    let empty = &xs[xs.len..xs.len];
6 6
7 7
    assert empty.len == 0;
8 8
}
9 9
10 -
fn checkSliceSuffix() {
10 +
unsafe fn checkSliceSuffix() {
11 11
    let xs: [u8; 3] = [10, 20, 30];
12 12
    let slice: *[u8] = &xs[..];
13 13
    let empty = &slice[slice.len..slice.len];
14 14
15 15
    assert empty.len == 0;
16 16
}
17 17
18 -
fn checkStringSuffix() {
18 +
unsafe fn checkStringSuffix() {
19 19
    let text = "abc";
20 20
    let empty = &text[text.len..text.len];
21 21
22 22
    assert empty.len == 0;
23 23
}
24 24
25 -
@default fn main() -> i32 {
25 +
@default unsafe fn main() -> i32 {
26 26
    checkArraySuffix();
27 27
    checkSliceSuffix();
28 28
    checkStringSuffix();
29 29
    return 0;
30 30
}
test/tests/slice.eq.rad +1 -1
1 1
// Returns true when the slice pointer and length both match.
2 -
fn sliceEq(a: *[i32], b: *[i32]) -> bool {
2 +
unsafe fn sliceEq(a: *[i32], b: *[i32]) -> bool {
3 3
    return a == b;
4 4
}
test/tests/slice.index.rad +1 -1
1 1
/// Returns an element from the slice.
2 -
fn sliceIndex(s: *[i32], idx: u32) -> i32 {
2 +
fn sliceIndex(s: &[i32], idx: u32) -> i32 {
3 3
    return s[idx];
4 4
}
test/tests/slice.mutable.rad +3 -3
1 1
// Test mutable slice operations
2 2
3 -
fn mutSliceStore(s: *mut [i32], idx: u32, val: i32) {
3 +
fn mutSliceStore(s: &mut [i32], idx: u32, val: i32) {
4 4
    set s[idx] = val;
5 5
}
6 6
7 -
fn mutSliceIncrement(s: *mut [i32], idx: u32) {
7 +
fn mutSliceIncrement(s: &mut [i32], idx: u32) {
8 8
    set s[idx] += 1;
9 9
}
10 10
11 -
fn mutSliceSwap(s: *mut [i32], i: u32, j: u32) {
11 +
fn mutSliceSwap(s: &mut [i32], i: u32, j: u32) {
12 12
    let tmp = s[i];
13 13
    set s[i] = s[j];
14 14
    set s[j] = tmp;
15 15
}
test/tests/slice.of.rad +2 -2
1 1
//! returns: 0
2 2
//! Test that @sliceOf produces a fat pointer with the correct length.
3 3
4 -
fn makeSlice(ptr: *mut i32, count: u32) -> *mut [i32] {
4 +
unsafe fn makeSlice(ptr: *mut i32, count: u32) -> *mut [i32] {
5 5
    return @sliceOf(ptr, count);
6 6
}
7 7
8 -
@default fn main() -> i32 {
8 +
@default unsafe fn main() -> i32 {
9 9
    let mut arr: [i32; 4] = [10, 20, 30, 40];
10 10
    let s = makeSlice(&mut arr[0], 4);
11 11
12 12
    assert s.len == 4;
13 13
    assert s[0] == 10;
test/tests/slice.range.bounds.check.rad +1 -1
3 3
4 4
fn id(n: u32) -> u32 {
5 5
    return n;
6 6
}
7 7
8 -
@default fn main() -> i32 {
8 +
@default unsafe fn main() -> i32 {
9 9
    let xs: [i32; 3] = [1, 2, 3];
10 10
    let slice: *[i32] = &xs[..];
11 11
    let bad = &slice[id(4)..id(4)];
12 12
13 13
    return bad.len as i32;
test/tests/slice.range.dynamic.rad +1 -1
2 2
3 3
fn id(n: u32) -> u32 {
4 4
    return n;
5 5
}
6 6
7 -
@default fn main() -> i32 {
7 +
@default unsafe fn main() -> i32 {
8 8
    let xs: [u8; 5] = [10, 20, 30, 40, 50];
9 9
    let slice: *[u8] = &xs[..];
10 10
11 11
    let start = id(1);
12 12
    let end = id(4);
test/tests/slice.range.order.check.rad +1 -1
3 3
4 4
fn id(n: u32) -> u32 {
5 5
    return n;
6 6
}
7 7
8 -
@default fn main() -> i32 {
8 +
@default unsafe fn main() -> i32 {
9 9
    let xs: [i32; 3] = [1, 2, 3];
10 10
    let slice: *[i32] = &xs[..];
11 11
    let bad = &slice[id(2)..id(1)];
12 12
13 13
    return bad.len as i32;
test/tests/slice.range.rad +5 -5
1 1
/// Returns a subslice with explicit bounds.
2 -
fn sliceRange(s: *[i32], start: u32, end: u32) -> *[i32] {
2 +
unsafe fn sliceRange(s: *[i32], start: u32, end: u32) -> *[i32] {
3 3
    return &s[start..end];
4 4
}
5 5
6 6
/// Returns a subslice with an open end bound.
7 -
fn sliceRangeOpenEnd(s: *[i32], start: u32) -> *[i32] {
7 +
unsafe fn sliceRangeOpenEnd(s: *[i32], start: u32) -> *[i32] {
8 8
    return &s[start..];
9 9
}
10 10
11 11
/// Returns a subslice with an open start bound.
12 -
fn sliceRangeOpenStart(s: *[i32], end: u32) -> *[i32] {
12 +
unsafe fn sliceRangeOpenStart(s: *[i32], end: u32) -> *[i32] {
13 13
    return &s[..end];
14 14
}
15 15
16 16
/// Returns a full reslice of a slice.
17 -
fn sliceRangeFull(s: *[i32]) -> *[i32] {
17 +
unsafe fn sliceRangeFull(s: *[i32]) -> *[i32] {
18 18
    return &s[..];
19 19
}
20 20
21 21
/// Returns a subslice from an array parameter.
22 -
fn sliceArray(a: [i32; 4]) -> *[i32] {
22 +
unsafe fn sliceArray(a: [i32; 4]) -> *[i32] {
23 23
    return &a[1..3];
24 24
}
test/tests/slice.runtime.i32.rad +3 -3
1 1
// Test runtime slice literal with i32 elements
2 2
3 -
fn sliceWithVar(c: i32) -> *[i32] {
3 +
unsafe fn sliceWithVar(c: i32) -> *[i32] {
4 4
    return &[c];
5 5
}
6 6
7 -
fn sliceWithVars(a: i32, b: i32) -> *[i32] {
7 +
unsafe fn sliceWithVars(a: i32, b: i32) -> *[i32] {
8 8
    return &[a, b];
9 9
}
10 10
11 -
fn sliceWithMixed(a: i32) -> *[i32] {
11 +
unsafe fn sliceWithMixed(a: i32) -> *[i32] {
12 12
    return &[a, 42, a];
13 13
}
test/tests/slice.runtime.literal.rad +2 -2
1 1
/// Create a slice literal with a runtime (non-constant) element.
2 -
fn sliceWithVar(c: u8) -> *[u8] {
2 +
unsafe fn sliceWithVar(c: u8) -> *[u8] {
3 3
    return &[c];
4 4
}
5 5
6 6
/// Create a slice literal with multiple runtime elements.
7 -
fn sliceWithVars(a: u8, b: u8) -> *[u8] {
7 +
unsafe fn sliceWithVars(a: u8, b: u8) -> *[u8] {
8 8
    return &[a, b];
9 9
}
test/tests/slice.subslice.rad +5 -5
1 1
//! returns: 42
2 2
3 -
fn testBasicSubslice() -> bool {
3 +
unsafe fn testBasicSubslice() -> bool {
4 4
    let arr: [i32; 5] = [10, 20, 30, 40, 50];
5 5
    let slice: *[i32] = &arr[..];        // Full slice [10, 20, 30, 40, 50]
6 6
    let subslice: *[i32] = &slice[1..4]; // Sub-slice [20, 30, 40]
7 7
8 8
    return subslice[0] == 20 and subslice[1] == 30 and subslice[2] == 40;
9 9
}
10 10
11 -
fn testNestedSubslice() -> bool {
11 +
unsafe fn testNestedSubslice() -> bool {
12 12
    let arr: [i32; 6] = [100, 200, 300, 400, 500, 600];
13 13
    let slice1: *[i32] = &arr[1..5];     // [200, 300, 400, 500]
14 14
    let slice2: *[i32] = &slice1[1..3];  // [300, 400]
15 15
    let slice3: *[i32] = &slice2[0..1];  // [300]
16 16
17 17
    return slice3[0] == 300;
18 18
}
19 19
20 -
fn testSubsliceLength() -> bool {
20 +
unsafe fn testSubsliceLength() -> bool {
21 21
    let arr: [i32; 4] = [1, 2, 3, 4];
22 22
    let slice: *[i32] = &arr[..];
23 23
    let subslice: *[i32] = &slice[1..3];
24 24
25 25
    return subslice.len == 2;
26 26
}
27 27
28 -
fn testEdgeCases() -> bool {
28 +
unsafe fn testEdgeCases() -> bool {
29 29
    let arr: [i32; 3] = [7, 8, 9];
30 30
    let slice: *[i32] = &arr[..];
31 31
32 32
    // Test single element subslice
33 33
    let single: *[i32] = &slice[1..2];
41 41
        return false;
42 42
    }
43 43
    return true;
44 44
}
45 45
46 -
@default fn main() -> i32 {
46 +
@default unsafe fn main() -> i32 {
47 47
    if (
48 48
        testBasicSubslice() and
49 49
        testNestedSubslice() and
50 50
        testSubsliceLength() and
51 51
        testEdgeCases()
test/tests/spill.blockarg.clobber.rad +3 -3
36 36
record State {
37 37
    h: [u32; 8],
38 38
    w: [u32; 64],
39 39
}
40 40
41 -
fn prepareSchedule(s: *mut State, block: *[u32]) {
41 +
unsafe fn prepareSchedule(s: *mut State, block: *[u32]) {
42 42
    let mut i: u32 = 0;
43 43
    while i < 16 {
44 44
        set s.w[i] = block[i];
45 45
        set i += 1;
46 46
    }
50 50
    }
51 51
}
52 52
53 53
/// SHA-256 compression: 64 rounds with 8 rotating working variables.
54 54
/// Exercises heavy register pressure and spilled block-argument shuffles.
55 -
fn compress(s: *mut State, k: *[u32]) {
55 +
unsafe fn compress(s: *mut State, k: *[u32]) {
56 56
    let mut a: u32 = s.h[0];
57 57
    let mut b: u32 = s.h[1];
58 58
    let mut c: u32 = s.h[2];
59 59
    let mut d: u32 = s.h[3];
60 60
    let mut e: u32 = s.h[4];
85 85
    set s.h[5] += f;
86 86
    set s.h[6] += g;
87 87
    set s.h[7] += hh;
88 88
}
89 89
90 -
@default fn main() -> i32 {
90 +
@default unsafe fn main() -> i32 {
91 91
    let k: [u32; 64] = [
92 92
        0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5,
93 93
        0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
94 94
        0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3,
95 95
        0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
test/tests/spill.loop.rad +1 -1
29 29
        return n;
30 30
    }
31 31
    return (n + a - 1) & (0 - a);
32 32
}
33 33
34 -
fn computeRecordLayout(tags: *[u8]) -> Layout {
34 +
fn computeRecordLayout(tags: &[u8]) -> Layout {
35 35
    let mut currentOffset: u32 = 0;
36 36
    let mut maxAlignment: u32 = 1;
37 37
    for i in 0..tags.len {
38 38
        let layout = getLayout(tags[i]);
39 39
        set currentOffset = alignUp(currentOffset, layout.alignment);
test/tests/static.slice.index.assign.rad +2 -2
7 7
record Container {
8 8
    pad: i32,
9 9
    items: [Inner; 3],
10 10
}
11 11
12 -
static container: Container = undefined;
12 +
unsafe static container: Container = undefined;
13 13
14 -
@default fn main() -> i32 {
14 +
@default unsafe fn main() -> i32 {
15 15
    set container.items[0].value = 10;
16 16
    set container.items[1].value = 20;
17 17
    set container.items[2].value = 30;
18 18
19 19
    let slice: *mut [Inner] = &mut container.items[..];
test/tests/static.slice.offset.rad +2 -2
15 15
static SCRATCH: [Entry; 1] = [Entry { a: 0, b: 0 }];
16 16
static STORAGE: [Entry; 2] = [
17 17
    Entry { a: 0, b: 0 },
18 18
    Entry { a: 0, b: 0 },
19 19
];
20 -
static TBL: Table = undefined;
20 +
unsafe static TBL: Table = undefined;
21 21
22 -
@default fn main() -> i32 {
22 +
@default unsafe fn main() -> i32 {
23 23
    set TBL.scratch = &mut SCRATCH[..];
24 24
    set TBL.entries = &mut STORAGE[..];
25 25
    set TBL.len     = 0;
26 26
27 27
    set TBL.entries[TBL.len] = Entry { a: 7, b: 9 };
test/tests/static.zero.bss.rad +2 -2
16 16
    /// Field that forces padding after `tag`.
17 17
    value: u32,
18 18
}
19 19
20 20
/// Partially initialized aggregate where all concrete bytes are zero.
21 -
static POOL: Pool = Pool { table: undefined, count: 0 };
21 +
unsafe static POOL: Pool = Pool { table: undefined, count: 0 };
22 22
23 23
/// Repeated scalar zeros.
24 24
static FLAGS: [bool; 4] = [false; 4];
25 25
26 26
/// Explicit zero fields with undefined padding.
27 27
static PADDED: Padded = Padded { tag: 0, value: 0 };
28 28
29 -
@default fn main() -> i32 {
29 +
@default unsafe fn main() -> i32 {
30 30
    assert POOL.count == 0;
31 31
    assert FLAGS[0] == false;
32 32
    assert FLAGS[3] == false;
33 33
    assert PADDED.tag == 0;
34 34
    assert PADDED.value == 0;
test/tests/string.basic.rad +1 -1
1 1
//! returns: 0
2 2
// Test string literals.
3 3
4 -
@default fn main() -> u32 {
4 +
@default unsafe fn main() -> u32 {
5 5
    let s: *[u8] = "fnord";
6 6
7 7
    // Return the length of the string.
8 8
    return (s.len) - 5;
9 9
}
test/tests/string.escape.rad +1 -1
1 1
//! returns: 0
2 2
//! Test string escape sequences.
3 -
@default fn main() -> i32 {
3 +
@default unsafe fn main() -> i32 {
4 4
    let s1: *[u8] = "Hello\tWorld!\n";
5 5
    assert s1.len == 13;
6 6
7 7
    let s2: *[u8] = "\"";
8 8
    assert s2.len == 1;
test/tests/string.index.rad +1 -1
1 1
//! returns: 0
2 2
// Test string indexing.
3 3
4 -
@default fn main() -> u8 {
4 +
@default unsafe fn main() -> u8 {
5 5
    let s: *[u8] = "fnord";
6 6
7 7
    return (s[1] + s[2]) - 221;
8 8
}
test/tests/trait.aggregate.ret.rad +23 -15
14 14
    y: i32,
15 15
    z: i32,
16 16
}
17 17
18 18
trait Geometry {
19 -
    fn (*Geometry) origin() -> Point;
20 -
    fn (*Geometry) center() -> Vec3;
21 -
    fn (*Geometry) maybe() -> ?i32;
19 +
    fn (&Geometry) origin() -> Point;
20 +
    fn (&Geometry) center() -> Vec3;
21 +
    fn (&Geometry) maybe() -> ?i32;
22 22
}
23 23
24 24
record Circle {
25 25
    cx: i32,
26 26
    cy: i32,
27 27
    radius: i32,
28 28
}
29 29
30 30
instance Geometry for Circle {
31 -
    fn (c: *Circle) origin() -> Point {
31 +
    fn (c: &Circle) origin() -> Point {
32 32
        return Point { x: c.cx, y: c.cy };
33 33
    }
34 34
35 -
    fn (c: *Circle) center() -> Vec3 {
35 +
    fn (c: &Circle) center() -> Vec3 {
36 36
        return Vec3 { x: c.cx, y: c.cy, z: 0 };
37 37
    }
38 38
39 -
    fn (c: *Circle) maybe() -> ?i32 {
39 +
    fn (c: &Circle) maybe() -> ?i32 {
40 40
        if c.radius > 0 {
41 41
            return c.radius;
42 42
        }
43 43
        return nil;
44 44
    }
45 45
}
46 46
47 -
@default fn main() -> i32 {
48 -
    let c = Circle { cx: 10, cy: 20, radius: 5 };
49 -
    let g: *opaque Geometry = &c;
50 -
47 +
fn checkCircle(g: &opaque Geometry) -> i32 {
51 48
    // Small struct return (Point, 8 bytes = pointer size).
52 49
    let p = g.origin();
53 50
    assert p.x == 10;
54 51
    assert p.y == 20;
55 52
64 61
    if let val = m {
65 62
        assert val == 5;
66 63
    } else {
67 64
        return 7;
68 65
    }
66 +
    return 0;
67 +
}
69 68
69 +
fn checkEmptyCircle(g: &opaque Geometry) -> i32 {
70 70
    // Optional return - None case.
71 -
    let c2 = Circle { cx: 0, cy: 0, radius: 0 };
72 -
    let g2: *opaque Geometry = &c2;
73 -
    let m2 = g2.maybe();
74 -
    if let _ = m2 {
71 +
    let m = g.maybe();
72 +
    if let _ = m {
75 73
        return 8;
76 74
    }
77 -
78 75
    return 0;
79 76
}
77 +
78 +
@default fn main() -> i32 {
79 +
    let c = Circle { cx: 10, cy: 20, radius: 5 };
80 +
    let result = checkCircle(&c);
81 +
    if result <> 0 {
82 +
        return result;
83 +
    }
84 +
85 +
    let c2 = Circle { cx: 0, cy: 0, radius: 0 };
86 +
    return checkEmptyCircle(&c2);
87 +
}
test/tests/trait.array.optional.rad +6 -6
11 11
record Multiplier {
12 12
    n: i32,
13 13
}
14 14
15 15
trait Transform {
16 -
    fn (*Transform) apply(x: i32) -> i32;
16 +
    unsafe fn (*Transform) apply(x: i32) -> i32;
17 17
}
18 18
19 19
instance Transform for Adder {
20 -
    fn (a: *Adder) apply(x: i32) -> i32 {
20 +
    unsafe fn (a: *Adder) apply(x: i32) -> i32 {
21 21
        return x + a.n;
22 22
    }
23 23
}
24 24
25 25
instance Transform for Multiplier {
26 -
    fn (m: *Multiplier) apply(x: i32) -> i32 {
26 +
    unsafe fn (m: *Multiplier) apply(x: i32) -> i32 {
27 27
        return x * m.n;
28 28
    }
29 29
}
30 30
31 31
/// Apply a chain of transforms to a value.
32 -
fn applyAll(transforms: *[*opaque Transform], value: i32) -> i32 {
32 +
unsafe fn applyAll(transforms: *[*opaque Transform], value: i32) -> i32 {
33 33
    let mut result = value;
34 34
    for i in 0..transforms.len {
35 35
        set result = transforms[i].apply(result);
36 36
    }
37 37
    return result;
38 38
}
39 39
40 40
/// Apply an optional transform, returning the original value if nil.
41 -
fn applyMaybe(t: ?*opaque Transform, value: i32) -> i32 {
41 +
unsafe fn applyMaybe(t: ?*opaque Transform, value: i32) -> i32 {
42 42
    if let tr = t {
43 43
        return tr.apply(value);
44 44
    }
45 45
    return value;
46 46
}
47 47
48 -
@default fn main() -> i32 {
48 +
@default unsafe fn main() -> i32 {
49 49
    let a1 = Adder { n: 10 };
50 50
    let m1 = Multiplier { n: 3 };
51 51
    let a2 = Adder { n: 5 };
52 52
53 53
    let t1: *opaque Transform = &a1;
test/tests/trait.basic.rad +8 -6
3 3
record Counter {
4 4
    value: i32,
5 5
}
6 6
7 7
trait Adder {
8 -
    fn (*mut Adder) add(n: i32) -> i32;
8 +
    fn (&mut Adder) add(n: i32) -> i32;
9 9
}
10 10
11 11
instance Adder for Counter {
12 -
    fn (c: *mut Counter) add(n: i32) -> i32 {
12 +
    fn (c: &mut Counter) add(n: i32) -> i32 {
13 13
        set c.value = c.value + n;
14 14
        return c.value;
15 15
    }
16 16
}
17 17
18 -
@default fn main() -> i32 {
19 -
    let mut c = Counter { value: 10 };
20 -
    let a: *mut opaque Adder = &mut c;
21 -
18 +
fn exerciseAdder(a: &mut opaque Adder) -> i32 {
22 19
    let result = a.add(5);
23 20
    // c.value should be 15 now.
24 21
    assert result == 15;
25 22
    let result2 = a.add(3);
26 23
    // c.value should be 18 now.
27 24
    assert result2 == 18;
28 25
    return 0;
29 26
}
27 +
28 +
@default fn main() -> i32 {
29 +
    let mut c = Counter { value: 10 };
30 +
    return exerciseAdder(&mut c);
31 +
}
test/tests/trait.control.flow.rad +10 -8
8 8
record Counter {
9 9
    value: i32,
10 10
}
11 11
12 12
trait Stepper {
13 -
    fn (*mut Stepper) step() -> i32;
14 -
    fn (*Stepper) current() -> i32;
13 +
    fn (&mut Stepper) step() -> i32;
14 +
    fn (&Stepper) current() -> i32;
15 15
}
16 16
17 17
instance Stepper for Counter {
18 -
    fn (c: *mut Counter) step() -> i32 {
18 +
    fn (c: &mut Counter) step() -> i32 {
19 19
        set c.value = c.value + 1;
20 20
        return c.value;
21 21
    }
22 22
23 -
    fn (c: *Counter) current() -> i32 {
23 +
    fn (c: &Counter) current() -> i32 {
24 24
        return c.value;
25 25
    }
26 26
}
27 27
28 -
@default fn main() -> i32 {
29 -
    let mut c = Counter { value: 0 };
30 -
    let s: *mut opaque Stepper = &mut c;
31 -
28 +
fn exerciseStepper(s: &mut opaque Stepper) -> i32 {
32 29
    // Dispatch in a while loop.
33 30
    let mut i: i32 = 0;
34 31
    while i < 5 {
35 32
        s.step();
36 33
        set i = i + 1;
52 49
    // Dispatch result used in conditional expression.
53 50
    let v = s.current();
54 51
    assert v == 10;
55 52
    return 0;
56 53
}
54 +
55 +
@default fn main() -> i32 {
56 +
    let mut c = Counter { value: 0 };
57 +
    return exerciseStepper(&mut c);
58 +
}
test/tests/trait.dispatch.rad +5 -5
1 1
record Acc {
2 2
    n: i32,
3 3
}
4 4
5 5
trait Ops {
6 -
    fn (*Ops) get() -> i32;
7 -
    fn (*mut Ops) put(n: i32);
6 +
    fn (&Ops) get() -> i32;
7 +
    fn (&mut Ops) put(n: i32);
8 8
}
9 9
10 10
instance Ops for Acc {
11 -
    fn (a: *Acc) get() -> i32 {
11 +
    fn (a: &Acc) get() -> i32 {
12 12
        return a.n;
13 13
    }
14 14
15 -
    fn (a: *mut Acc) put(n: i32) {
15 +
    fn (a: &mut Acc) put(n: i32) {
16 16
        set a.n = n;
17 17
    }
18 18
}
19 19
20 -
fn dispatch(o: *mut opaque Ops) -> i32 {
20 +
fn dispatch(o: &mut opaque Ops) -> i32 {
21 21
    o.put(42);
22 22
    return o.get();
23 23
}
test/tests/trait.fn.param.rad +12 -15
11 11
record Square {
12 12
    side: i32,
13 13
}
14 14
15 15
trait Shape {
16 -
    fn (*Shape) area() -> i32;
16 +
    fn (&Shape) area() -> i32;
17 17
}
18 18
19 19
trait Scalable {
20 -
    fn (*mut Scalable) scale(factor: i32);
20 +
    fn (&mut Scalable) scale(factor: i32);
21 21
}
22 22
23 23
instance Shape for Circle {
24 -
    fn (c: *Circle) area() -> i32 {
24 +
    fn (c: &Circle) area() -> i32 {
25 25
        return c.radius * c.radius * 3;
26 26
    }
27 27
}
28 28
29 29
instance Shape for Square {
30 -
    fn (s: *Square) area() -> i32 {
30 +
    fn (s: &Square) area() -> i32 {
31 31
        return s.side * s.side;
32 32
    }
33 33
}
34 34
35 35
instance Scalable for Circle {
36 -
    fn (c: *mut Circle) scale(factor: i32) {
36 +
    fn (c: &mut Circle) scale(factor: i32) {
37 37
        set c.radius = c.radius * factor;
38 38
    }
39 39
}
40 40
41 41
/// Accept an immutable trait object parameter.
42 -
fn getArea(s: *opaque Shape) -> i32 {
42 +
fn getArea(s: &opaque Shape) -> i32 {
43 43
    return s.area();
44 44
}
45 45
46 46
/// Accept a mutable trait object parameter.
47 -
fn doubleSize(s: *mut opaque Scalable) {
47 +
fn doubleSize(s: &mut opaque Scalable) {
48 48
    s.scale(2);
49 49
}
50 50
51 51
/// Accept two trait object parameters.
52 -
fn totalArea(a: *opaque Shape, b: *opaque Shape) -> i32 {
52 +
fn totalArea(a: &opaque Shape, b: &opaque Shape) -> i32 {
53 53
    return a.area() + b.area();
54 54
}
55 55
56 56
@default fn main() -> i32 {
57 57
    let c = Circle { radius: 5 };
58 58
    let s = Square { side: 4 };
59 59
60 60
    // Pass immutable trait objects to function.
61 -
    let cs: *opaque Shape = &c;
62 -
    let ca = getArea(cs);
61 +
    let ca = getArea(&c);
63 62
    assert ca == 75;
64 63
65 -
    let ss: *opaque Shape = &s;
66 -
    let sa = getArea(ss);
64 +
    let sa = getArea(&s);
67 65
    assert sa == 16;
68 66
69 67
    // Pass two different trait objects to same function.
70 -
    let total = totalArea(cs, ss);
68 +
    let total = totalArea(&c, &s);
71 69
    assert total == 91;
72 70
73 71
    // Pass mutable trait object to function.
74 72
    let mut c2 = Circle { radius: 3 };
75 -
    let sc: *mut opaque Scalable = &mut c2;
76 -
    doubleSize(sc);
73 +
    doubleSize(&mut c2);
77 74
    assert c2.radius == 6;
78 75
    return 0;
79 76
}
test/tests/trait.multiple.methods.rad +14 -11
8 8
record Accumulator {
9 9
    total: i32,
10 10
}
11 11
12 12
trait Collector {
13 -
    fn (*mut Collector) add(n: i32) -> i32;
14 -
    fn (*mut Collector) clear();
15 -
    fn (*mut Collector) isEmpty() -> bool;
13 +
    fn (&mut Collector) add(n: i32) -> i32;
14 +
    fn (&mut Collector) clear();
15 +
    fn (&Collector) isEmpty() -> bool;
16 16
}
17 17
18 18
instance Collector for Accumulator {
19 -
    fn (a: *mut Accumulator) add(n: i32) -> i32 {
19 +
    fn (a: &mut Accumulator) add(n: i32) -> i32 {
20 20
        set a.total = a.total + n;
21 21
        return a.total;
22 22
    }
23 23
24 -
    fn (a: *mut Accumulator) clear() {
24 +
    fn (a: &mut Accumulator) clear() {
25 25
        set a.total = 0;
26 26
    }
27 27
28 -
    fn (a: *mut Accumulator) isEmpty() -> bool {
28 +
    fn (a: &Accumulator) isEmpty() -> bool {
29 29
        return a.total == 0;
30 30
    }
31 31
}
32 32
33 -
@default fn main() -> i32 {
34 -
    let mut acc = Accumulator { total: 0 };
35 -
    let c: *mut opaque Collector = &mut acc;
36 -
33 +
fn exerciseCollector(c: &mut opaque Collector) -> i32 {
37 34
    // Initially empty.
38 35
    assert c.isEmpty();
39 36
40 37
    // Add returns running total.
41 38
    let v1 = c.add(10);
49 46
        return 4;
50 47
    }
51 48
52 49
    // Void return: clear.
53 50
    c.clear();
54 -
    assert acc.total == 0;
55 51
    assert c.isEmpty();
56 52
    return 0;
57 53
}
54 +
55 +
@default fn main() -> i32 {
56 +
    let mut acc = Accumulator { total: 0 };
57 +
    let result = exerciseCollector(&mut acc);
58 +
    assert acc.total == 0;
59 +
    return result;
60 +
}
test/tests/trait.multiple.traits.rad +24 -14
8 8
record Counter {
9 9
    value: i32,
10 10
}
11 11
12 12
trait Incrementable {
13 -
    fn (*mut Incrementable) inc() -> i32;
13 +
    fn (&mut Incrementable) inc() -> i32;
14 14
}
15 15
16 16
trait Resettable {
17 -
    fn (*mut Resettable) reset();
18 -
    fn (*mut Resettable) isZero() -> bool;
17 +
    fn (&mut Resettable) reset();
18 +
    fn (&Resettable) isZero() -> bool;
19 19
}
20 20
21 21
instance Incrementable for Counter {
22 -
    fn (c: *mut Counter) inc() -> i32 {
22 +
    fn (c: &mut Counter) inc() -> i32 {
23 23
        set c.value = c.value + 1;
24 24
        return c.value;
25 25
    }
26 26
}
27 27
28 28
instance Resettable for Counter {
29 -
    fn (c: *mut Counter) reset() {
29 +
    fn (c: &mut Counter) reset() {
30 30
        set c.value = 0;
31 31
    }
32 32
33 -
    fn (c: *mut Counter) isZero() -> bool {
33 +
    fn (c: &Counter) isZero() -> bool {
34 34
        return c.value == 0;
35 35
    }
36 36
}
37 37
38 +
fn increment(i: &mut opaque Incrementable) -> i32 {
39 +
    return i.inc();
40 +
}
41 +
42 +
fn resetCounter(r: &mut opaque Resettable) -> i32 {
43 +
    if r.isZero() {
44 +
        return 2;
45 +
    }
46 +
    r.reset();
47 +
    assert r.isZero();
48 +
    return 0;
49 +
}
50 +
38 51
@default fn main() -> i32 {
39 52
    let mut c = Counter { value: 10 };
40 53
41 54
    // Dispatch through Incrementable.
42 -
    let i: *mut opaque Incrementable = &mut c;
43 -
    let v1 = i.inc();
55 +
    let v1 = increment(&mut c);
44 56
    assert v1 == 11;
45 57
46 58
    // Dispatch through Resettable.
47 -
    let r: *mut opaque Resettable = &mut c;
48 -
    if r.isZero() {
49 -
        return 2;
59 +
    let result = resetCounter(&mut c);
60 +
    if result <> 0 {
61 +
        return result;
50 62
    }
51 -
    r.reset();
52 -
    assert r.isZero();
53 63
    assert c.value == 0;
54 64
55 65
    // Increment again after reset.
56 -
    let v2 = i.inc();
66 +
    let v2 = increment(&mut c);
57 67
    assert v2 == 1;
58 68
    return 0;
59 69
}
test/tests/trait.multiple.types.rad +18 -17
1 1
//! returns: 0
2 2
//! Test multiple types implementing the same trait, and immutable receivers.
3 3
//!
4 4
//! Exercises: two different concrete types implementing the same trait,
5 -
//! coerced to the same trait object type. Also tests immutable `*Trait`
5 +
//! coerced to the same trait object type. Also tests immutable `&Trait`
6 6
//! receivers. Verifies each type dispatches through its own vtable.
7 7
8 8
record Dog {
9 9
    age: i32,
10 10
}
12 12
record Cat {
13 13
    lives: i32,
14 14
}
15 15
16 16
trait Speaker {
17 -
    fn (*Speaker) speak() -> i32;
18 -
    fn (*Speaker) isOld() -> bool;
17 +
    fn (&Speaker) speak() -> i32;
18 +
    fn (&Speaker) isOld() -> bool;
19 19
}
20 20
21 21
instance Speaker for Dog {
22 -
    fn (d: *Dog) speak() -> i32 {
22 +
    fn (d: &Dog) speak() -> i32 {
23 23
        return d.age;
24 24
    }
25 25
26 -
    fn (d: *Dog) isOld() -> bool {
26 +
    fn (d: &Dog) isOld() -> bool {
27 27
        return d.age > 10;
28 28
    }
29 29
}
30 30
31 31
instance Speaker for Cat {
32 -
    fn (c: *Cat) speak() -> i32 {
32 +
    fn (c: &Cat) speak() -> i32 {
33 33
        return c.lives * 10;
34 34
    }
35 35
36 -
    fn (c: *Cat) isOld() -> bool {
36 +
    fn (c: &Cat) isOld() -> bool {
37 37
        return c.lives < 5;
38 38
    }
39 39
}
40 40
41 +
fn checkYoungSpeaker(s: &opaque Speaker, expected: i32) -> bool {
42 +
    return s.speak() == expected and not s.isOld();
43 +
}
44 +
45 +
fn checkOldSpeaker(s: &opaque Speaker) -> bool {
46 +
    return s.isOld();
47 +
}
48 +
41 49
@default fn main() -> i32 {
42 50
    let d = Dog { age: 5 };
43 51
    let c = Cat { lives: 9 };
44 52
45 53
    // Immutable trait object from Dog.
46 -
    let sd: *opaque Speaker = &d;
47 -
    let v1 = sd.speak();
48 -
    assert v1 == 5;
49 -
    if sd.isOld() {
54 +
    if not checkYoungSpeaker(&d, 5) {
50 55
        return 2;
51 56
    }
52 57
53 58
    // Immutable trait object from Cat.
54 -
    let sc: *opaque Speaker = &c;
55 -
    let v2 = sc.speak();
56 -
    assert v2 == 90;
57 -
    if sc.isOld() {
59 +
    if not checkYoungSpeaker(&c, 90) {
58 60
        return 4;
59 61
    }
60 62
61 63
    // Old dog.
62 64
    let oldDog = Dog { age: 15 };
63 -
    let so: *opaque Speaker = &oldDog;
64 -
    assert so.isOld();
65 +
    assert checkOldSpeaker(&oldDog);
65 66
    return 0;
66 67
}
test/tests/trait.object.rad +7 -4
1 1
record Counter {
2 2
    value: i32,
3 3
}
4 4
5 5
trait Adder {
6 -
    fn (*mut Adder) add(n: i32) -> i32;
6 +
    fn (&mut Adder) add(n: i32) -> i32;
7 7
}
8 8
9 9
instance Adder for Counter {
10 -
    fn (c: *mut Counter) add(n: i32) -> i32 {
10 +
    fn (c: &mut Counter) add(n: i32) -> i32 {
11 11
        set c.value = c.value + n;
12 12
        return c.value;
13 13
    }
14 14
}
15 15
16 +
fn addOne(a: &mut opaque Adder) -> i32 {
17 +
    return a.add(1);
18 +
}
19 +
16 20
fn use_adder() -> i32 {
17 21
    let mut c = Counter { value: 0 };
18 -
    let a: *mut opaque Adder = &mut c;
19 -
    return a.add(1);
22 +
    return addOne(&mut c);
20 23
}
test/tests/trait.object.ril +11 -5
9 9
    store w32 %3 %0 0;
10 10
    sload w32 %4 %0 0;
11 11
    ret %4;
12 12
}
13 13
14 +
fn w32 $addOne(w64 %0) {
15 +
  @entry0
16 +
    load w64 %1 %0 0;
17 +
    load w64 %2 %0 8;
18 +
    load w64 %3 %2 0;
19 +
    call w32 %4 %3(%1, 1);
20 +
    ret %4;
21 +
}
22 +
14 23
fn w32 $use_adder() {
15 24
  @entry0
16 25
    reserve %0 4 4;
17 26
    store w32 0 %0 0;
18 27
    reserve %1 16 8;
19 28
    store w64 %0 %1 0;
20 29
    store w64 $"vtable::Counter::Adder" %1 8;
21 -
    load w64 %2 %1 0;
22 -
    load w64 %3 %1 8;
23 -
    load w64 %4 %3 0;
24 -
    call w32 %5 %4(%2, 1);
25 -
    ret %5;
30 +
    call w32 %2 $addOne(%1);
31 +
    ret %2;
26 32
}
test/tests/trait.supertrait.forward.rad +10 -7
1 1
//! returns: 0
2 2
//! A supertrait declared later must contribute methods to its child trait.
3 3
4 4
trait Child: Parent {
5 -
    fn (*Child) child() -> i32;
5 +
    fn (&Child) child() -> i32;
6 6
}
7 7
8 8
trait Parent {
9 -
    fn (*Parent) parent() -> i32;
9 +
    fn (&Parent) parent() -> i32;
10 10
}
11 11
12 12
record Value {
13 13
    n: i32,
14 14
}
15 15
16 16
instance Parent for Value {
17 -
    fn (self: *Value) parent() -> i32 {
17 +
    fn (self: &Value) parent() -> i32 {
18 18
        return self.n;
19 19
    }
20 20
}
21 21
22 22
instance Child for Value {
23 -
    fn (self: *Value) child() -> i32 {
23 +
    fn (self: &Value) child() -> i32 {
24 24
        return self.n + 1;
25 25
    }
26 26
}
27 27
28 -
@default fn main() -> i32 {
29 -
    let value = Value { n: 41 };
30 -
    let object: *opaque Child = &value;
28 +
fn checkChild(object: &opaque Child) {
31 29
    assert object.parent() == 41;
32 30
    assert object.child() == 42;
31 +
}
32 +
33 +
@default fn main() -> i32 {
34 +
    let value = Value { n: 41 };
35 +
    checkChild(&value);
33 36
    return 0;
34 37
}
test/tests/trait.supertrait.rad +24 -13
2 2
//! Test supertrait bounds: a trait that requires its implementors to also
3 3
//! implement another trait.
4 4
//!
5 5
//! Exercises: trait Reader with a `read` method, trait Writer with a `write`
6 6
//! method, and trait ReadWriter: Reader + Writer that combines both.
7 -
//! A Socket type implements all three. Coercing a `*opaque ReadWriter` to
8 -
//! `*opaque Reader` or `*opaque Writer` should work, and dispatching through
7 +
//! A Socket type implements all three. Coercing an `&mut opaque ReadWriter` to
8 +
//! `&mut opaque Reader` or `&mut opaque Writer` should work, and dispatching through
9 9
//! any of the three trait objects should call the correct methods.
10 10
11 11
trait Reader {
12 -
    fn (*mut Reader) read(buf: *mut [u8]) -> i32;
12 +
    fn (&mut Reader) read(buf: &mut [u8]) -> i32;
13 13
}
14 14
15 15
trait Writer {
16 -
    fn (*mut Writer) write(data: *[u8]) -> i32;
16 +
    fn (&mut Writer) write(data: &[u8]) -> i32;
17 17
}
18 18
19 19
trait ReadWriter: Reader + Writer {
20 -
    fn (*mut ReadWriter) flush() -> i32;
20 +
    fn (&mut ReadWriter) flush() -> i32;
21 21
}
22 22
23 23
record Socket {
24 24
    rbuf: [u8; 32],
25 25
    rpos: i32,
27 27
    wbuf: [u8; 32],
28 28
    wpos: i32,
29 29
}
30 30
31 31
instance Reader for Socket {
32 -
    fn (s: *mut Socket) read(buf: *mut [u8]) -> i32 {
32 +
    fn (s: &mut Socket) read(buf: &mut [u8]) -> i32 {
33 33
        let mut i: u32 = 0;
34 34
        while i < buf.len and s.rpos < s.rlen {
35 35
            set buf[i] = s.rbuf[s.rpos as u32];
36 36
            set s.rpos = s.rpos + 1;
37 37
            set i = i + 1;
39 39
        return i as i32;
40 40
    }
41 41
}
42 42
43 43
instance Writer for Socket {
44 -
    fn (s: *mut Socket) write(data: *[u8]) -> i32 {
44 +
    fn (s: &mut Socket) write(data: &[u8]) -> i32 {
45 45
        let mut i: u32 = 0;
46 46
        while i < data.len {
47 47
            if s.wpos >= 32 {
48 48
                return s.wpos;
49 49
            }
54 54
        return s.wpos;
55 55
    }
56 56
}
57 57
58 58
instance ReadWriter for Socket {
59 -
    fn (s: *mut Socket) flush() -> i32 {
59 +
    fn (s: &mut Socket) flush() -> i32 {
60 60
        let pos = s.wpos;
61 61
        set s.wpos = 0;
62 62
        return pos;
63 63
    }
64 64
}
65 65
66 -
@default fn main() -> i32 {
66 +
fn writeThrough(rw: &mut opaque ReadWriter, data: &[u8]) -> i32 {
67 +
    return rw.write(data);
68 +
}
69 +
70 +
fn readThrough(rw: &mut opaque ReadWriter, buf: &mut [u8]) -> i32 {
71 +
    return rw.read(buf);
72 +
}
73 +
74 +
fn flushThrough(rw: &mut opaque ReadWriter) -> i32 {
75 +
    return rw.flush();
76 +
}
77 +
78 +
@default unsafe fn main() -> i32 {
67 79
    let mut sock = Socket {
68 80
        rbuf: undefined,
69 81
        rpos: 0,
70 82
        rlen: 5,
71 83
        wbuf: undefined,
77 89
    set sock.rbuf[2] = 'l' as u8;
78 90
    set sock.rbuf[3] = 'l' as u8;
79 91
    set sock.rbuf[4] = 'o' as u8;
80 92
81 93
    // Test 1: Use as ReadWriter trait object.
82 -
    let rw: *mut opaque ReadWriter = &mut sock;
83 94
84 95
    // Test 2: Write through the ReadWriter (dispatches via Writer supertrait).
85 -
    let wn = rw.write("abc");
96 +
    let wn = writeThrough(&mut sock, &"abc"[..]);
86 97
    assert wn == 3;
87 98
    assert sock.wpos == 3;
88 99
89 100
    // Test 3: Read through the ReadWriter (dispatches via Reader supertrait).
90 101
    let mut rbuf: [u8; 8] = undefined;
91 -
    let rn = rw.read(&mut rbuf[0..8]);
102 +
    let rn = readThrough(&mut sock, &mut rbuf[..]);
92 103
    assert rn == 5;
93 104
    assert rbuf[0] == 'h' as u8;
94 105
    assert rbuf[4] == 'o' as u8;
95 106
96 107
    // Test 4: Call ReadWriter's own method.
97 -
    let flushed = rw.flush();
108 +
    let flushed = flushThrough(&mut sock);
98 109
    assert flushed == 3;
99 110
    assert sock.wpos == 0;
100 111
    return 0;
101 112
}
test/tests/trait.supertrait.ril +59 -29
104 104
    sload w32 %1 %0 72;
105 105
    store w32 0 %0 72;
106 106
    ret %1;
107 107
}
108 108
109 +
fn w32 $writeThrough(w64 %0, w64 %1) {
110 +
  @entry0
111 +
    load w64 %2 %0 0;
112 +
    load w64 %3 %0 8;
113 +
    load w64 %4 %3 8;
114 +
    call w32 %5 %4(%2, %1);
115 +
    ret %5;
116 +
}
117 +
118 +
fn w32 $readThrough(w64 %0, w64 %1) {
119 +
  @entry0
120 +
    load w64 %2 %0 0;
121 +
    load w64 %3 %0 8;
122 +
    load w64 %4 %3 0;
123 +
    call w32 %5 %4(%2, %1);
124 +
    ret %5;
125 +
}
126 +
127 +
fn w32 $flushThrough(w64 %0) {
128 +
  @entry0
129 +
    load w64 %1 %0 0;
130 +
    load w64 %2 %0 8;
131 +
    load w64 %3 %2 16;
132 +
    call w32 %4 %3(%1);
133 +
    ret %4;
134 +
}
135 +
109 136
fn w32 $main() {
110 137
  @entry0
111 138
    reserve %0 76 4;
112 139
    store w32 0 %0 32;
113 140
    store w32 5 %0 36;
122 149
    add w64 %4 %0 4;
123 150
    store w8 111 %4 0;
124 151
    reserve %5 16 8;
125 152
    store w64 %0 %5 0;
126 153
    store w64 $"vtable::Socket::ReadWriter" %5 8;
127 -
    load w64 %6 %5 0;
128 -
    load w64 %7 %5 8;
129 -
    load w64 %8 %7 8;
130 -
    copy %9 $main$literal$0;
154 +
    copy %6 $main$literal$0;
155 +
    reserve %7 16 8;
156 +
    store w64 %6 %7 0;
157 +
    store w32 3 %7 8;
158 +
    store w32 3 %7 12;
159 +
    load w64 %8 %7 0;
160 +
    load w32 %9 %7 8;
131 161
    reserve %10 16 8;
132 -
    store w64 %9 %10 0;
133 -
    store w32 3 %10 8;
134 -
    store w32 3 %10 12;
135 -
    call w32 %11 %8(%6, %10);
162 +
    store w64 %8 %10 0;
163 +
    store w32 %9 %10 8;
164 +
    store w32 %9 %10 12;
165 +
    call w32 %11 $writeThrough(%5, %10);
136 166
    br.eq w32 %11 3 @assert.ok2 @assert.fail1;
137 167
  @assert.fail1
138 168
    unreachable;
139 169
  @assert.ok2
140 170
    sload w32 %12 %0 72;
141 171
    br.eq w32 %12 3 @assert.ok4 @assert.fail3;
142 172
  @assert.fail3
143 173
    unreachable;
144 174
  @assert.ok4
145 175
    reserve %13 8 1;
146 -
    load w64 %14 %5 0;
147 -
    load w64 %15 %5 8;
148 -
    load w64 %16 %15 0;
149 -
    reserve %17 16 8;
150 -
    store w64 %13 %17 0;
151 -
    store w32 8 %17 8;
152 -
    store w32 8 %17 12;
153 -
    call w32 %18 %16(%14, %17);
154 -
    br.eq w32 %18 5 @assert.ok6 @assert.fail5;
176 +
    reserve %14 16 8;
177 +
    store w64 %0 %14 0;
178 +
    store w64 $"vtable::Socket::ReadWriter" %14 8;
179 +
    reserve %15 16 8;
180 +
    store w64 %13 %15 0;
181 +
    store w32 8 %15 8;
182 +
    store w32 8 %15 12;
183 +
    call w32 %16 $readThrough(%14, %15);
184 +
    br.eq w32 %16 5 @assert.ok6 @assert.fail5;
155 185
  @assert.fail5
156 186
    unreachable;
157 187
  @assert.ok6
158 -
    load w8 %19 %13 0;
159 -
    br.eq w8 %19 104 @assert.ok8 @assert.fail7;
188 +
    load w8 %17 %13 0;
189 +
    br.eq w8 %17 104 @assert.ok8 @assert.fail7;
160 190
  @assert.fail7
161 191
    unreachable;
162 192
  @assert.ok8
163 -
    add w64 %20 %13 4;
164 -
    load w8 %21 %20 0;
165 -
    br.eq w8 %21 111 @assert.ok10 @assert.fail9;
193 +
    add w64 %18 %13 4;
194 +
    load w8 %19 %18 0;
195 +
    br.eq w8 %19 111 @assert.ok10 @assert.fail9;
166 196
  @assert.fail9
167 197
    unreachable;
168 198
  @assert.ok10
169 -
    load w64 %22 %5 0;
170 -
    load w64 %23 %5 8;
171 -
    load w64 %24 %23 16;
172 -
    call w32 %25 %24(%22);
173 -
    br.eq w32 %25 3 @assert.ok12 @assert.fail11;
199 +
    reserve %20 16 8;
200 +
    store w64 %0 %20 0;
201 +
    store w64 $"vtable::Socket::ReadWriter" %20 8;
202 +
    call w32 %21 $flushThrough(%20);
203 +
    br.eq w32 %21 3 @assert.ok12 @assert.fail11;
174 204
  @assert.fail11
175 205
    unreachable;
176 206
  @assert.ok12
177 -
    sload w32 %26 %0 72;
178 -
    br.eq w32 %26 0 @assert.ok14 @assert.fail13;
207 +
    sload w32 %22 %0 72;
208 +
    br.eq w32 %22 0 @assert.ok14 @assert.fail13;
179 209
  @assert.fail13
180 210
    unreachable;
181 211
  @assert.ok14
182 212
    ret 0;
183 213
}
test/tests/trait.throws.rad +3 -3
9 9
record StrictParser {
10 10
    limit: i32,
11 11
}
12 12
13 13
trait Parser {
14 -
    fn (*Parser) parse(n: i32) -> i32 throws (ParseError);
14 +
    unsafe fn (*Parser) parse(n: i32) -> i32 throws (ParseError);
15 15
}
16 16
17 17
instance Parser for StrictParser {
18 -
    fn (p: *StrictParser) parse(n: i32) -> i32 throws (ParseError) {
18 +
    unsafe fn (p: *StrictParser) parse(n: i32) -> i32 throws (ParseError) {
19 19
        if n < 0 {
20 20
            throw ParseError::InvalidInput;
21 21
        }
22 22
        if n > p.limit {
23 23
            throw ParseError::Overflow;
24 24
        }
25 25
        return n * 2;
26 26
    }
27 27
}
28 28
29 -
@default fn main() -> i32 {
29 +
@default unsafe fn main() -> i32 {
30 30
    let sp = StrictParser { limit: 100 };
31 31
    let p: *opaque Parser = &sp;
32 32
33 33
    // Success path.
34 34
    let r1 = try p.parse(5) catch {
test/tests/trait.writer.rad +8 -8
5 5
//! A BufferWriter writes to an in-memory buffer; a CountingWriter wraps any
6 6
//! Writer and tracks how many bytes flow through it. This tests trait objects
7 7
//! as struct fields, dispatch chains, and a real-world composition pattern.
8 8
9 9
trait Writer {
10 -
    fn (*mut Writer) write(data: *[u8]) -> i32;
11 -
    fn (*Writer) total() -> i32;
10 +
    unsafe fn (*mut Writer) write(data: *[u8]) -> i32;
11 +
    unsafe fn (*Writer) total() -> i32;
12 12
}
13 13
14 14
/// Writes bytes into a fixed-size buffer.
15 15
record BufferWriter {
16 16
    buf: [u8; 64],
17 17
    pos: i32,
18 18
}
19 19
20 20
instance Writer for BufferWriter {
21 -
    fn (w: *mut BufferWriter) write(data: *[u8]) -> i32 {
21 +
    unsafe fn (w: *mut BufferWriter) write(data: *[u8]) -> i32 {
22 22
        let mut i: u32 = 0;
23 23
        while i < data.len {
24 24
            if w.pos >= 64 {
25 25
                return w.pos;
26 26
            }
29 29
            set i = i + 1;
30 30
        }
31 31
        return w.pos;
32 32
    }
33 33
34 -
    fn (w: *BufferWriter) total() -> i32 {
34 +
    unsafe fn (w: *BufferWriter) total() -> i32 {
35 35
        return w.pos;
36 36
    }
37 37
}
38 38
39 39
/// Counts bytes written through it, forwarding to an inner writer.
41 41
    inner: *mut opaque Writer,
42 42
    count: i32,
43 43
}
44 44
45 45
instance Writer for CountingWriter {
46 -
    fn (w: *mut CountingWriter) write(data: *[u8]) -> i32 {
46 +
    unsafe fn (w: *mut CountingWriter) write(data: *[u8]) -> i32 {
47 47
        set w.count = w.count + data.len as i32;
48 48
        return w.inner.write(data);
49 49
    }
50 50
51 -
    fn (w: *CountingWriter) total() -> i32 {
51 +
    unsafe fn (w: *CountingWriter) total() -> i32 {
52 52
        return w.count;
53 53
    }
54 54
}
55 55
56 56
/// Write a slice through any Writer.
57 -
fn emit(w: *mut opaque Writer, data: *[u8]) -> i32 {
57 +
unsafe fn emit(w: *mut opaque Writer, data: *[u8]) -> i32 {
58 58
    return w.write(data);
59 59
}
60 60
61 -
@default fn main() -> i32 {
61 +
@default unsafe fn main() -> i32 {
62 62
    // Direct BufferWriter usage through trait.
63 63
    let mut buf = BufferWriter { buf: undefined, pos: 0 };
64 64
    let w: *mut opaque Writer = &mut buf;
65 65
    emit(w, "hello");
66 66
    assert w.total() == 5;
test/tests/type.unify.rad +6 -6
45 45
46 46
    return true;
47 47
}
48 48
49 49
/// Verifies assignment compatibility for pointers with identical target types.
50 -
fn testPointerUnification() -> bool {
50 +
unsafe fn testPointerUnification() -> bool {
51 51
    let x: i32 = 42;
52 52
    let y: i32 = 24;
53 53
    let ptr1: *i32 = &x;
54 54
    let ptr2: *i32 = &y;
55 55
    let mut ptrResult: *i32 = ptr1;
57 57
58 58
    return true;
59 59
}
60 60
61 61
/// Verifies conversion from array references to slices.
62 -
fn testArrayToSlice() -> bool {
62 +
unsafe fn testArrayToSlice() -> bool {
63 63
    let arr: [i32; 3] = [1, 2, 3];
64 64
    let slice: *[i32] = &arr[..];
65 65
66 66
    return true;
67 67
}
97 97
98 98
    return true;
99 99
}
100 100
101 101
/// Verifies coercion from pointer values to optional pointer values.
102 -
fn testPointerToOptional() -> bool {
102 +
unsafe fn testPointerToOptional() -> bool {
103 103
    let value: i32 = 42;
104 104
    let ptr: *i32 = &value;
105 105
    let optPtr: ?*i32 = ptr;
106 106
107 107
    return true;
108 108
}
109 109
110 110
/// Verifies creation of slices for different, but internally consistent, element types.
111 -
fn testSliceElementUnification() -> bool {
111 +
unsafe fn testSliceElementUnification() -> bool {
112 112
    let arrSmall: [i8; 3] = [1, 2, 3];
113 113
    let sliceSmall: *[i8] = &arrSmall[..];
114 114
115 115
    let arrLarge: [i32; 3] = [10, 20, 30];
116 116
    let sliceLarge: *[i32] = &arrLarge[..];
136 136
137 137
    return true;
138 138
}
139 139
140 140
/// Verifies repeated pointer assignments across multiple values of the same type.
141 -
fn testMultiplePointerAssignments() -> bool {
141 +
unsafe fn testMultiplePointerAssignments() -> bool {
142 142
    let value1: i32 = 42;
143 143
    let value2: i32 = 24;
144 144
    let value3: i32 = 100;
145 145
146 146
    let ptr1: *i32 = &value1;
162 162
    set result = b2;
163 163
164 164
    return true;
165 165
}
166 166
167 -
@default fn main() -> i32 {
167 +
@default unsafe fn main() -> i32 {
168 168
    let testResult: bool =
169 169
        testNumericUnification() and
170 170
        testOptionalUnification() and
171 171
        testArrayUnification() and
172 172
        testPointerUnification() and
test/tests/undefined.aggregate.rad +1 -1
1 1
/// Test lowering of undefined for aggregate types.
2 2
record Point { x: i32, y: i32 }
3 3
4 -
fn test() -> i32 {
4 +
unsafe fn test() -> i32 {
5 5
    let p: Point = undefined;
6 6
    return p.x;
7 7
}
test/tests/undefined.primitive.rad +1 -1
1 1
/// Test lowering of undefined for primitive types.
2 -
fn test() -> i32 {
2 +
unsafe fn test() -> i32 {
3 3
    let x: i32 = undefined;
4 4
    return x;
5 5
}
test/tests/undefined.rad +1 -1
1 1
//! returns: 0
2 2
//! Test undefined values for arrays and assignment.
3 -
@default fn main() -> i32 {
3 +
@default unsafe fn main() -> i32 {
4 4
    let mut ary: [u16; 32] = undefined;
5 5
    let x: u32 = 8;
6 6
    let y: u32 = 9;
7 7
8 8
    set ary[0] = 1;
test/tests/undefined.record.field.rad +3 -3
20 20
    len: i32,
21 21
}
22 22
23 23
/// Construct a record with one field `undefined` (named-field syntax).
24 24
/// The IL must not contain a `blit` or `store` for `x`.
25 -
fn partialInit() -> i32 {
25 +
unsafe fn partialInit() -> i32 {
26 26
    let s = Small { x: undefined, y: 42 };
27 27
    return s.y;
28 28
}
29 29
30 30
/// Construct a record with an array field `undefined` (named-field syntax).
31 31
/// The IL must not contain a `blit` for `data`.
32 -
fn arrayFieldUndef() -> i32 {
32 +
unsafe fn arrayFieldUndef() -> i32 {
33 33
    let w = WithArray { data: undefined, len: 3 };
34 34
    return w.len;
35 35
}
36 36
37 37
/// Both fields defined - normal case for comparison.
45 45
    B,
46 46
}
47 47
48 48
/// Union variant with record payload containing an `undefined` field.
49 49
/// This exercises `lowerRecordCtor` (positional constructor path).
50 -
fn unionPayloadUndef() -> i32 {
50 +
unsafe fn unionPayloadUndef() -> i32 {
51 51
    let t = Tagged::A { data: undefined, tag: 99 };
52 52
    match t {
53 53
        case Tagged::A { tag, .. } => return tag,
54 54
        else => return -1,
55 55
    }
test/tests/union-tag.rad +2 -2
23 23
    V18,
24 24
    V19,
25 25
    V20,
26 26
}
27 27
28 -
fn tag(u: *BigUnion) -> u8 {
28 +
unsafe fn tag(u: *BigUnion) -> u8 {
29 29
    let p = u as *opaque as *u8;
30 30
    return *p;
31 31
}
32 32
33 -
@default fn main() -> i32 {
33 +
@default unsafe fn main() -> i32 {
34 34
    let v0 = BigUnion::V0;
35 35
    assert tag(&v0) == 0;
36 36
    let v7 = BigUnion::V7;
37 37
    assert tag(&v7) == 7;
38 38
    let v13 = BigUnion::V13;
test/tests/union.edge.case.3.rad +3 -3
9 9
10 10
record Parser {
11 11
    root: Node,
12 12
}
13 13
14 -
fn node(p: *mut Parser, value: Node) -> *Node {
14 +
unsafe fn node(p: *mut Parser, value: Node) -> *Node {
15 15
    set p.root = value;
16 16
    return &p.root;
17 17
}
18 18
19 -
fn nodeBool(p: *mut Parser, value: bool) -> *Node {
19 +
unsafe fn nodeBool(p: *mut Parser, value: bool) -> *Node {
20 20
    return node(p, Node::Bool(value));
21 21
}
22 22
23 -
@default fn main() -> u32 {
23 +
@default unsafe fn main() -> u32 {
24 24
    let mut parser = Parser { root: undefined };
25 25
26 26
    match *nodeBool(&mut parser, true) {
27 27
        case Node::Bool(v) => {
28 28
            assert v == true;
test/tests/union.match.ref.rad +5 -5
3 3
4 4
/// Non-void union for testing ref matching.
5 5
union Option { None, Some(u32) }
6 6
7 7
/// Match on a void union reference.
8 -
fn matchColorRef(ptr: *Color) -> u32 {
8 +
fn matchColorRef(ptr: &Color) -> u32 {
9 9
    match ptr {
10 10
        case Color::Red => return 0,
11 11
        case Color::Green => return 1,
12 12
        case Color::Blue => return 2,
13 13
    }
14 14
}
15 15
16 16
/// Match on a non-void union reference. Bindings become pointers.
17 -
fn matchRef(ptr: *Option) -> u32 {
17 +
fn matchRef(ptr: &Option) -> u32 {
18 18
    match ptr {
19 19
        case Option::None => return 0,
20 20
        case Option::Some(val) => return *val,
21 21
    }
22 22
}
23 23
24 24
/// If-let on a union reference.
25 -
fn ifLetRef(ptr: *Option) -> u32 {
25 +
fn ifLetRef(ptr: &Option) -> u32 {
26 26
    if let case Option::Some(val) = ptr {
27 27
        return *val;
28 28
    }
29 29
    return 0;
30 30
}
31 31
32 32
/// While-let on a mutable union reference.
33 -
fn whileLetRef(ptr: *mut Option) -> u32 {
33 +
fn whileLetRef(ptr: &mut Option) -> u32 {
34 34
    let mut sum: u32 = 0;
35 35
    while let case Option::Some(val) = ptr {
36 36
        set sum += *val;
37 37
        set *ptr = Option::None;
38 38
    }
39 39
    return sum;
40 40
}
41 41
42 42
/// Match on an optional reference with binding pattern.
43 -
fn optionalRef(ptr: ?*u32) -> u32 {
43 +
unsafe fn optionalRef(ptr: ?*u32) -> u32 {
44 44
    if let x = ptr {
45 45
        return *x;
46 46
    }
47 47
    return 0;
48 48
}
test/tests/union.mixed.assign.rad +2 -2
10 10
11 11
record Holder {
12 12
    value: Mixed,
13 13
}
14 14
15 -
fn storePayload(holder: *mut Holder, value: i32) {
15 +
fn storePayload(holder: &mut Holder, value: i32) {
16 16
    set holder.value = Mixed::Payload(value);
17 17
}
18 18
19 -
fn storeFinal(holder: *mut Holder) {
19 +
fn storeFinal(holder: &mut Holder) {
20 20
    set holder.value = Mixed::Final;
21 21
}
22 22
23 23
fn checkIfLet(value: Mixed) -> i32 {
24 24
    if let case Mixed::Payload(v) = value {
test/tests/union.payload.mutref.rad +2 -2
17 17
    padding1: i32,
18 18
    padding2: i32,
19 19
    state: Sealed,
20 20
}
21 21
22 -
fn addItemViaMatchRef(blk: *mut Block, val: u32) -> bool {
22 +
unsafe fn addItemViaMatchRef(blk: &mut Block, val: u32) -> bool {
23 23
    match &mut blk.state {
24 24
        case Sealed::No { items } => {
25 25
            set items.data[items.len] = val;
26 26
            set items.len += 1;
27 27
            return true;
30 30
            return false;
31 31
        },
32 32
    }
33 33
}
34 34
35 -
@default fn main() -> i32 {
35 +
@default unsafe fn main() -> i32 {
36 36
    let mut buf: [u32; 8] = undefined;
37 37
    let mut blk = Block {
38 38
        padding1: 0,
39 39
        padding2: 0,
40 40
        state: Sealed::No { items: U32List { data: &mut buf[0..8], len: 0 } },