kernel: admit and compile trusted RIL at runtime

6e82279532a864c3ed91e0ace91cf1865b5dcc39135f52ff748805d5f3dfe8fb
Alexis Sellier committed ago 1 parent 408a1843
compiler/radiance/images.rad +2 -2
1 1
//! Binary RIL image catalogs and native image append through the shared backend.
2 2
3 -
mod graph;
4 -
mod native;
3 +
use std::lang::il::images::graph;
4 +
use std::lang::il::images::native;
5 5
6 6
use std::mem;
7 7
use std::fmt;
8 8
use std::sys::unix;
9 9
use std::collections::dict;
kernel/Makefile +39 -14
2 2
EMU ?= $(or $(RAD_EMULATOR),emulator)
3 3
HOST_EMU ?= $(EMU)
4 4
COMPILER := ../bin/radiance.rv64.dev
5 5
COMPILE := $(HOST_EMU) -memory-size=385024 -data-size=348160 -stack-size=512 -run $(COMPILER)
6 6
MODULES := $(wildcard core/*.rad)
7 -
CORE_ASM := arch/atomic.ras arch/context.ras arch/clock.ras arch/mmio.ras arch/physical.ras arch/smp.ras
8 -
CORE := -pkg core -mod core.rad $(addprefix -mod ,$(MODULES) $(CORE_ASM))
7 +
STD_FILES := $(addprefix ../,$(shell cat ../std.lib))
8 +
STD := -pkg std $(addprefix -mod ,$(STD_FILES))
9 +
CORE_ASM := arch/atomic.ras arch/context.ras arch/clock.ras arch/mmio.ras arch/physical.ras arch/smp.ras arch/loading.ras
10 +
CORE := $(STD) -pkg core -mod core.rad $(addprefix -mod ,$(MODULES) $(CORE_ASM))
9 11
CHECK_MODULES := $(wildcard check/*.rad)
10 12
USER_INPUTS := user.rad $(wildcard user/*.rad user/*/*.rad) core/abi.rad
11 -
USER_BASE := -pkg abi -mod core/abi.rad -pkg user -mod user.rad -mod user/sys.rad -pkg probe -mod user/probe.rad
13 +
USER_BASE := -pkg abi -mod core/abi.rad -pkg user -mod user.rad -mod user/sys.rad -mod user/launch.rad -pkg probe -mod user/probe.rad
12 14
BASE_IMAGES := sample_root sample_control sample_scalars sample_memory sample_overflow sample_events sample_alias sample_instance
13 15
CONTROL_IMAGES := control flow exiting faulting spinning admin remote
14 16
BASE_RIL := $(addprefix build/,$(addsuffix .ril,$(BASE_IMAGES)))
15 17
CONTROL_RIL := $(addprefix build/,$(addsuffix .ril,$(CONTROL_IMAGES)))
16 18
IMAGE_MODULES_sample_control := user/sample_control/math.rad
17 19
IMAGE_MODULES_control := user/control/lifecycle.rad user/control/authority.rad
20 +
ROOT_CATALOG := -zero-bss -pkg images -mod build/root/images.rad -mod user/sys.ras -image build/loader.ril
21 +
LOADING_CATALOG := -zero-bss -pkg images -mod build/loading/images.rad -mod user/sys.ras -image build/load_check.ril
18 22
BASE_CATALOG := -zero-bss -pkg images -mod build/baseline/images.rad -mod user/sys.ras $(addprefix -image ,$(BASE_RIL))
19 23
CONTROL_CATALOG := -zero-bss -pkg images -mod build/control/images.rad -mod user/sys.ras $(addprefix -image ,$(CONTROL_RIL))
20 -
MACHINE := $(EMU) -machine -no-guard-stack -max-steps=1000000000 -count-instructions
24 +
MACHINE := $(EMU) -machine -memory-size=196608 -no-guard-stack -max-steps=1000000000 -count-instructions
21 25
22 26
.PHONY: all check clean compiler-check
23 27
.DELETE_ON_ERROR:
24 28
all: kernel.rv64
25 29
40 44
41 45
build/control/images.rad: $(CONTROL_RIL) $(COMPILER)
42 46
	mkdir -p $(@D)
43 47
	$(COMPILE) -catalog $@ $(addprefix -image ,$(CONTROL_RIL))
44 48
45 -
kernel.rv64: main.rad arch/start.ras core.rad $(MODULES) $(CORE_ASM) build/control/images.rad user/sys.ras $(COMPILER)
46 -
	$(COMPILE) $(CONTROL_CATALOG) $(CORE) -pkg kernel -start arch/start.ras -mod main.rad -entry kernel -o $@
49 +
build/root/images.rad: build/loader.ril $(COMPILER)
50 +
	mkdir -p $(@D)
51 +
	$(COMPILE) -catalog $@ -image $<
52 +
53 +
build/loading/images.rad: build/load_check.ril $(COMPILER)
54 +
	mkdir -p $(@D)
55 +
	$(COMPILE) -catalog $@ -image $<
47 56
48 -
check.rv64: check.rad core.rad $(MODULES) $(CORE_ASM) $(CHECK_MODULES) build/baseline/images.rad user/sys.ras $(COMPILER)
57 +
# Keep the admission test input as raw RIL bytes.
58 +
build/loading/input.ras: build/runtime.ril
59 +
	mkdir -p $(@D)
60 +
	{ printf '.text;\n.export @"loading::input";\n@"loading::input"\nla %%a0 @input;\nret;\n'; \
61 +
	  printf '.export @"loading::inputSize";\n@"loading::inputSize"\nli %%a0 %s;\nret;\n.data;\n@input\n' "$$(wc -c < $<)"; \
62 +
	  od -An -v -tu1 $< | sed -e 's/^ *//' -e 's/  */, /g' -e 's/^/.byte /' -e 's/$$/;/'; \
63 +
	} > $@
64 +
65 +
kernel.rv64: main.rad arch/start.ras core.rad $(MODULES) $(CORE_ASM) build/root/images.rad user/sys.ras $(STD_FILES) $(COMPILER)
66 +
	$(COMPILE) $(ROOT_CATALOG) $(CORE) -pkg kernel -start arch/start.ras -mod main.rad -entry kernel -o $@
67 +
68 +
check.rv64: check.rad core.rad $(MODULES) $(CORE_ASM) $(CHECK_MODULES) build/baseline/images.rad user/sys.ras $(STD_FILES) $(COMPILER)
49 69
	$(COMPILE) $(BASE_CATALOG) $(CORE) -pkg check -mod check.rad $(addprefix -mod ,$(CHECK_MODULES)) -entry check -o $@
50 70
51 -
context.rv64: context.rad context/wait.rad check/context.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER)
71 +
context.rv64: context.rad context/wait.rad check/context.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(STD_FILES) $(COMPILER)
52 72
	$(COMPILE) $(BASE_CATALOG) $(CORE) -pkg context -start arch/entry.ras -mod context.rad -mod context/wait.rad -mod check/context.ras -entry context -o $@
53 73
54 -
interrupt.rv64: interrupt.rad check/interrupt.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER)
74 +
interrupt.rv64: interrupt.rad check/interrupt.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(STD_FILES) $(COMPILER)
55 75
	$(COMPILE) $(BASE_CATALOG) $(CORE) -pkg interrupt -start arch/entry.ras -mod interrupt.rad -mod check/interrupt.ras -entry interrupt -o $@
56 76
57 -
native.rv64: native.rad native/instances.rad arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER)
77 +
native.rv64: native.rad native/instances.rad arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(STD_FILES) $(COMPILER)
58 78
	$(COMPILE) $(BASE_CATALOG) $(CORE) -pkg native -start arch/entry.ras -mod native.rad -mod native/instances.rad -entry native -o $@
59 79
60 -
parallel.rv64: parallel.rad parallel/mailboxes.rad arch/start.ras core.rad $(MODULES) $(CORE_ASM) build/control/images.rad user/sys.ras $(COMPILER)
80 +
parallel.rv64: parallel.rad parallel/mailboxes.rad arch/start.ras core.rad $(MODULES) $(CORE_ASM) build/control/images.rad user/sys.ras $(STD_FILES) $(COMPILER)
61 81
	$(COMPILE) $(CONTROL_CATALOG) $(CORE) -pkg parallel -start arch/start.ras -mod parallel.rad -mod parallel/mailboxes.rad -entry parallel -o $@
62 82
63 -
check: all check.rv64 context.rv64 interrupt.rv64 native.rv64 parallel.rv64
83 +
loading.rv64: loading.rad arch/start.ras core.rad $(MODULES) $(CORE_ASM) build/loading/images.rad build/loading/input.ras user/sys.ras $(STD_FILES) $(COMPILER)
84 +
	$(COMPILE) $(LOADING_CATALOG) $(CORE) -pkg loading -start arch/start.ras -mod loading.rad -mod build/loading/input.ras -entry loading -o $@
85 +
86 +
check: all check.rv64 context.rv64 interrupt.rv64 native.rv64 parallel.rv64 loading.rv64
64 87
	$(HOST_EMU) -run check.rv64
65 88
	$(MACHINE) -max-steps=1000000 -run context.rv64
66 89
	$(MACHINE) -harts=2 -irq=3 -irq-at=4000000 -uart-rx=52 -uart-rx-at=8000000 -run interrupt.rv64
67 90
	$(MACHINE) -run native.rv64
68 91
	$(MACHINE) -harts=1 -run parallel.rv64
69 92
	$(MACHINE) -harts=2 -run parallel.rv64
70 93
	$(MACHINE) -harts=8 -run parallel.rv64
94 +
	$(MACHINE) -harts=1 -run loading.rv64
95 +
	$(MACHINE) -harts=2 -run loading.rv64
71 96
	@output="$$( $(MACHINE) -harts=2 -run kernel.rv64 2>build/boot.log )"; status=$$?; \
72 -
	if test "$$status" -ne 2 || test "$$output" != CONTROL-OK; then cat build/boot.log; exit 1; fi
97 +
	if test "$$status" -ne 2 || test "$$output" != RIL-READY; then cat build/boot.log; exit 1; fi
73 98
74 99
clean:
75 100
	rm -rf build
76 -
	rm -f kernel.rv64 check.rv64 context.rv64 interrupt.rv64 native.rv64 parallel.rv64
101 +
	rm -f kernel.rv64 check.rv64 context.rv64 interrupt.rv64 native.rv64 parallel.rv64 loading.rv64
kernel/arch/context.ras +2 -0
36 36
	mret;
37 37
@idleLoop
38 38
	wfi;
39 39
	j      @idleLoop;
40 40
@enterUser
41 +
	// Synchronize this hart with native images published under the kernel lock.
42 +
	fence.i;
41 43
	mv     %t6    %a0;
42 44
	ld     %t0    256(%t6);
43 45
	csrw   mepc   %t0;
44 46
	li     %t0    0x80;
45 47
	csrw   mstatus %t0;
kernel/arch/entry.ras +2 -1
2 2
.text;
3 3
	la     %t0    @bootFault;
4 4
	csrw   mtvec  %t0;
5 5
	csrr   %a0    mhartid;
6 6
	bnez   %a0    @bootIdle;
7 -
	call   @"::default";
7 +
	la     %t0    @"::default";
8 +
	jalr   %ra    %t0    0;
8 9
	li     %t1    0x5555;
9 10
	j      @bootFinish;
10 11
@bootFault
11 12
	csrr   %t1    mcause;
12 13
	slli   %t1    %t1    16;
kernel/arch/loading.ras added +40 -0
1 +
// Exact user::sys native allowlist; index is below 14.
2 +
// Each table entry is a two-instruction la followed by ret.
3 +
.text;
4 +
.export @"core::loading::bindingAddress";
5 +
@"core::loading::bindingAddress"
6 +
    slli %t1 %a0 1;
7 +
    add %a0 %a0 %t1;
8 +
    slli %a0 %a0 2;
9 +
    la %t0 @bindings;
10 +
    add %t0 %t0 %a0;
11 +
    jalr %zero %t0 0;
12 +
@bindings
13 +
    la %a0 @"user::sys::rawCall";
14 +
    ret;
15 +
    la %a0 @"user::sys::pointer";
16 +
    ret;
17 +
    la %a0 @"user::sys::loadAcquire";
18 +
    ret;
19 +
    la %a0 @"user::sys::storeRelease";
20 +
    ret;
21 +
    la %a0 @"user::sys::lockAcquire";
22 +
    ret;
23 +
    la %a0 @"user::sys::lockRelease";
24 +
    ret;
25 +
    la %a0 @"user::sys::read8";
26 +
    ret;
27 +
    la %a0 @"user::sys::read16";
28 +
    ret;
29 +
    la %a0 @"user::sys::read32";
30 +
    ret;
31 +
    la %a0 @"user::sys::read64";
32 +
    ret;
33 +
    la %a0 @"user::sys::write8";
34 +
    ret;
35 +
    la %a0 @"user::sys::write16";
36 +
    ret;
37 +
    la %a0 @"user::sys::write32";
38 +
    ret;
39 +
    la %a0 @"user::sys::write64";
40 +
    ret;
kernel/arch/start.ras +2 -1
12 12
    bgeu %a0 %t0 @bootFault;
13 13
    la %sp @hartStacks;
14 14
    addi %t0 %a0 1;
15 15
    slli %t0 %t0 16;
16 16
    add %sp %sp %t0;
17 -
    call @"::default";
17 +
    la %t0 @"::default";
18 +
    jalr %ra %t0 0;
18 19
    csrr %t0 mhartid;
19 20
    bnez %t0 @bootIdle;
20 21
    li %t1 0x5555;
21 22
    j @bootFinish;
22 23
@bootFault
kernel/check.rad +1 -1
16 16
mod activation;
17 17
mod lifecycle;
18 18
mod calls;
19 19
20 20
/// Run the available kernel mechanism checks.
21 -
@default fn main() -> u32 {
21 +
@default unsafe fn main() -> u32 {
22 22
    frames::run();
23 23
    boot::run();
24 24
    handles::run();
25 25
    domains::run();
26 26
    capabilities::run();
kernel/check/activation.rad +15 -12
12 12
use core::memory;
13 13
use core::pages;
14 14
use core::platform;
15 15
use core::resources;
16 16
use core::state;
17 -
use images;
17 +
use core::images;
18 18
19 19
/// Test-owned physical memory for Page and private-stack allocations.
20 20
static RAM: [u8; 64 * frames::PAGE_SIZE] = undefined;
21 21
/// Domain slots include spare creation capacity unless explicitly exhausted.
22 22
static DOMAINS: [domains::Domain; 3] = undefined;
29 29
static PINS: [u16; 64] = undefined;
30 30
static ASSIGNED: [bool; 64] = undefined;
31 31
static GRANTS: [u64; 3] = undefined;
32 32
/// Shared state for the exercised public operations.
33 33
static KERNEL: state::State = undefined;
34 +
/// Image identities owned by this isolated kernel state.
35 +
static IMAGES: images::Registry = undefined;
34 36
35 37
/// Zero only the supplied simulated physical range.
36 38
fn clear(base: u64, size: u32) {
37 39
    assert base <= RAM.len as u64 and size as u64 <= RAM.len as u64 - base;
38 40
    for i in base as u32..base as u32 + size { set RAM[i] = 0; }
53 55
    let mut ram: memory::Memory = undefined;
54 56
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 3);
55 57
    domains::init(&mut DOMAINS[..]);
56 58
    resources::init(&mut OBJECTS[..]);
57 59
    for i in 0..CONTEXTS.len { contexts::init(&mut CONTEXTS[i], i); }
58 -
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
59 -
    let handle = domains::root(&mut DOMAINS[..], images::ROOT);
60 +
    images::init(&mut IMAGES);
61 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram, images: &mut IMAGES };
62 +
    let handle = domains::root(&mut DOMAINS[..], KERNEL.images.root);
60 63
    let root = try! domains::resolve(&DOMAINS[..], 0, handle, abi::CREATE);
61 -
    let child = try! domains::create(&mut DOMAINS[..], root, images::ROOT);
64 +
    let child = try! domains::create(&mut DOMAINS[..], root, KERNEL.images.root);
62 65
    return handles::install(&mut DOMAINS[0].handles, 2, child, abi::DOMAIN_RIGHTS);
63 66
}
64 67
65 68
/// Read a rejected call's public error without accepting accidental success.
66 69
fn createError(authority: abi::Handle, image: u64) -> abi::Error {
86 89
87 90
/// A rejected image/capacity request cannot consume authority or an incarnation.
88 91
fn creation() {
89 92
    let target = setup();
90 93
    let self = abi::Handle { bits: 0 };
91 -
    assert createError(self, images::COUNT as u64) == abi::Error::VerifyFailed;
94 +
    assert createError(self, KERNEL.images.count as u64) == abi::Error::VerifyFailed;
92 95
    assert createError(self, 0x100000000) == abi::Error::VerifyFailed;
93 -
    assert createError(target, images::ROOT as u64) == abi::Error::Denied;
96 +
    assert createError(target, KERNEL.images.root as u64) == abi::Error::Denied;
94 97
    let child = try! domains::resolve(&DOMAINS[..], 0, target, abi::EXECUTE);
95 98
    let foreign = handles::install(&mut DOMAINS[0].handles, 3, child, abi::CREATE);
96 -
    assert createError(foreign, images::ROOT as u64) == abi::Error::Denied;
99 +
    assert createError(foreign, KERNEL.images.root as u64) == abi::Error::Denied;
97 100
    set DOMAINS[0].events.reserved = events::CRITICAL;
98 -
    assert createError(self, images::ROOT as u64) == abi::Error::Exhausted;
101 +
    assert createError(self, KERNEL.images.root as u64) == abi::Error::Exhausted;
99 102
    assert DOMAINS[0].events.reserved == events::CRITICAL;
100 103
    set DOMAINS[0].events.reserved = 1;
101 104
    let root = try! domains::authority(&DOMAINS[..], 0, self, abi::CREATE);
102 105
    for i in 4..abi::MAX_HANDLES {
103 106
        let _handle = handles::install(&mut DOMAINS[0].handles, i, root, abi::EXECUTE);
104 107
    }
105 -
    assert createError(self, images::ROOT as u64) == abi::Error::Exhausted;
108 +
    assert createError(self, KERNEL.images.root as u64) == abi::Error::Exhausted;
106 109
    for i in 4..abi::MAX_HANDLES { let _entry = handles::remove(&mut DOMAINS[0].handles, i); }
107 110
    assert DOMAINS[2].epoch == 0 and DOMAINS[2].state == domains::Lifecycle::Dead;
108 -
    let _last = try! domains::create(&mut DOMAINS[..], root, images::ROOT);
109 -
    assert createError(self, images::ROOT as u64) == abi::Error::Exhausted;
111 +
    let _last = try! domains::create(&mut DOMAINS[..], root, KERNEL.images.root);
112 +
    assert createError(self, KERNEL.images.root as u64) == abi::Error::Exhausted;
110 113
    assert POOL.available == 64;
111 114
    assert domains::live(&DOMAINS[..], try! domains::resolve(&DOMAINS[..], 0, target, abi::EXECUTE));
112 115
}
113 116
114 117
/// Verify stack edges, adjacent args, allocation rollback, and context isolation.
150 153
    assert DOMAINS[1].env.context == first;
151 154
    assert CONTEXTS[a].env.stackBase == 0 and CONTEXTS[a].env.stackTop == 8192;
152 155
    assert CONTEXTS[a].env.argsPointer == 4096 and CONTEXTS[a].env.argsSize == 8192;
153 156
    assert CONTEXTS[a].env.eventsPointer == &DOMAINS[1].events.ring as u64;
154 157
    assert CONTEXTS[a].env.privateStackTop - CONTEXTS[a].env.privateStackBase == activation::PRIVATE_STACK_SIZE as u64;
155 -
    assert CONTEXTS[a].frame.pc == images::get(images::ROOT).entry;
158 +
    assert CONTEXTS[a].frame.pc == images::get(KERNEL.images, KERNEL.images.root).entry;
156 159
    assert startError(target, 8192, 0, 0, true) == abi::Error::NotPending;
157 160
    // Empty args need no Page, even for an otherwise invalid pointer.
158 161
    let second = try! activation::context(&mut KERNEL, 0, target, 4096, 0xffffffffffffffff, 0, clear);
159 162
    let b = try! contexts::resolve(&CONTEXTS[..], second);
160 163
    assert first <> second and a <> b;
kernel/check/budget_caps.rad +5 -1
10 10
use core::handles;
11 11
use core::memory;
12 12
use core::platform;
13 13
use core::resources;
14 14
use core::state;
15 +
use core::images;
15 16
16 17
/// Two independent protection domains for authority checks.
17 18
static DOMAINS: [domains::Domain; 2] = undefined;
18 19
/// Exactly enough resource slots for a reserve, a loan, and its replacement.
19 20
static OBJECTS: [resources::Slot; 3] = undefined;
27 28
static ASSIGNED: [bool; 1] = undefined;
28 29
/// Empty-pool persistent domain grants.
29 30
static GRANTS: [u64; 2] = undefined;
30 31
/// Shared kernel mechanism storage.
31 32
static KERNEL: state::State = undefined;
33 +
/// Image identities owned by this isolated kernel state.
34 +
static IMAGES: images::Registry = undefined;
32 35
33 36
/// Reject a split without consuming any authority from the source.
34 37
fn reject(source: abi::Handle, expected: abi::Error) {
35 38
    let _handle = try budget_caps::split(&mut KERNEL, 0, source, 1) catch error {
36 39
        assert error == expected;
57 60
    set machine.memoryCount = 0;
58 61
    set machine.reservedCount = 0;
59 62
    try! frames::init(&mut POOL, &machine);
60 63
    let mut ram: memory::Memory = undefined;
61 64
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 2);
62 -
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
65 +
    images::init(&mut IMAGES);
66 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram, images: &mut IMAGES };
63 67
    let rootHandle = domains::root(&mut DOMAINS[..], 0);
64 68
    let root = try! domains::resolve(&DOMAINS[..], 0, rootHandle, abi::CREATE);
65 69
    let child = try! domains::create(&mut DOMAINS[..], root, 0);
66 70
    let target = handles::install(&mut DOMAINS[0].handles, 2, child, abi::DOMAIN_RIGHTS);
67 71
    set DOMAINS[0].state = domains::Lifecycle::Active;
kernel/check/calls.rad +25 -6
13 13
use core::lifecycle;
14 14
use core::memory;
15 15
use core::platform;
16 16
use core::resources;
17 17
use core::state;
18 +
use core::images;
19 +
use core::loading;
18 20
19 21
static DOMAINS: [domains::Domain; 2] = undefined;
20 22
static OBJECTS: [resources::Slot; 2] = undefined;
21 23
static CONTEXTS: [contexts::Context; 1] = undefined;
22 24
static POOL: frames::Pool = undefined;
23 25
static PINS: [u16; 1] = undefined;
24 26
static ASSIGNED: [bool; 1] = undefined;
25 27
static GRANTS: [u64; 2] = undefined;
26 28
static KERNEL: state::State = undefined;
29 +
/// Image identities owned by this isolated kernel state.
30 +
static IMAGES: images::Registry = undefined;
27 31
28 32
/// Exercise a returning operation through exactly the public seven-word input.
29 -
fn reply(caller: u32, number: u64, args: [u64; 7], now: u64) -> [u64; 4] {
33 +
unsafe fn reply(caller: u32, number: u64, args: [u64; 7], now: u64) -> [u64; 4] {
30 34
    let result = try! calls::dispatch(&mut KERNEL, caller, number, &args[..], now);
31 35
    match result {
32 36
        case calls::Outcome::Reply { values } => return values,
33 37
        else => panic "reply: expected completed direct call",
34 38
    }
35 39
}
36 40
37 41
/// Observe a boundary rejection without substituting a lower-level helper.
38 -
fn reject(caller: u32, number: u64, args: [u64; 7], expected: abi::Error) {
42 +
unsafe fn reject(caller: u32, number: u64, args: [u64; 7], expected: abi::Error) {
39 43
    let _result = try calls::dispatch(&mut KERNEL, caller, number, &args[..], 100) catch error {
40 44
        assert error == expected;
41 45
        return;
42 46
    };
43 47
    panic "reject: invalid direct call succeeded";
44 48
}
45 49
46 50
/// Slot kinds, reserved bits, and MMIO widths/rights remain full-width inputs.
47 -
fn materialization(root: abi::Handle) {
51 +
unsafe fn materialization(root: abi::Handle) {
48 52
    let device = try! resources::create(&mut OBJECTS[..], resources::Value::Device(fdt::Range { base: 0x1000, size: 8 }));
49 53
    resources::retain(&mut OBJECTS[..], device);
50 54
    let handle = handles::install(&mut DOMAINS[0].handles, 3, device, abi::READ);
51 55
    let readonly = reply(0, 10, [handle.bits, 0, 0, 0, 0, 0, 0], 0)[0];
52 56
    let info = reply(0, 45, [readonly, 0, 0, 0, 0, 0, 0], 0);
71 75
    reject(0, 62, [3, 3, 0, 0, 0, 0, 0], abi::Error::BadHandle);
72 76
    set OBJECTS[device.index].epoch = device.epoch;
73 77
}
74 78
75 79
/// Running metadata is conservatively debited; Run keeps admission transactional.
76 -
fn execution(target: abi::Handle, child: abi::Object) {
80 +
unsafe fn execution(target: abi::Handle, child: abi::Object) {
77 81
    set DOMAINS[child.index].state = domains::Lifecycle::Active;
78 82
    try! contexts::prepare(&mut CONTEXTS[0], child, 0, 0, DOMAINS[child.index].env);
79 83
    let id = contexts::identity(&CONTEXTS[0]);
80 84
    set DOMAINS[child.index].env.context = id;
81 85
    let resource = try! resources::create(&mut OBJECTS[..], resources::Value::Budget(try! budgets::init(0, 1000)));
105 109
    reject(0, 73, args, abi::Error::Busy);
106 110
    set CONTEXTS[0].status = contexts::Status::Stopped;
107 111
    reject(0, 75, [target.bits, id + (1 as u64 << 32), 0, 0, 0, 0, 0], abi::Error::InvalidArg);
108 112
}
109 113
114 +
/// Admission rejects bad authority and byte ranges before touching physical RAM.
115 +
unsafe fn admission(root: abi::Handle, target: abi::Handle) {
116 +
    let count = KERNEL.images.count;
117 +
    let available = POOL.available;
118 +
    reject(1, 77, [0, 4096, 4, 0, 0, 0, 0], abi::Error::Denied);
119 +
    reject(0, 77, [target.bits, 4096, 4, 0, 0, 0, 0], abi::Error::Denied);
120 +
    reject(0, 77, [root.bits, 4096, 0, 0, 0, 0, 0], abi::Error::InvalidArg);
121 +
    reject(0, 77, [root.bits, 4096, loading::MAX_BYTES as u64 + 1, 0, 0, 0, 0], abi::Error::InvalidArg);
122 +
    reject(0, 77, [root.bits, 0xfffffffffffffffe, 4, 0, 0, 0, 0], abi::Error::InvalidArg);
123 +
    reject(0, 77, [root.bits, 4096, 4, 0, 0, 0, 0], abi::Error::Denied);
124 +
    assert KERNEL.images.count == count and POOL.available == available;
125 +
}
126 +
110 127
/// Independent target-local administrative authority survives source death.
111 -
export fn run() {
128 +
export unsafe fn run() {
112 129
    domains::init(&mut DOMAINS[..]);
113 130
    contexts::init(&mut CONTEXTS[0], 0);
114 131
    resources::init(&mut OBJECTS[..]);
115 132
    let mut machine: platform::Platform = undefined;
116 133
    set machine.memoryCount = 0;
117 134
    set machine.reservedCount = 0;
118 135
    try! frames::init(&mut POOL, &machine);
119 136
    let mut ram: memory::Memory = undefined;
120 137
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 2);
121 -
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
138 +
    images::init(&mut IMAGES);
139 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram, images: &mut IMAGES };
122 140
    let rootHandle = domains::root(&mut DOMAINS[..], 0);
123 141
    let root = try! domains::resolve(&DOMAINS[..], 0, rootHandle, abi::CREATE);
124 142
    let child = try! domains::create(&mut DOMAINS[..], root, 0);
125 143
    let target = handles::install(&mut DOMAINS[0].handles, 2, child, abi::DOMAIN_RIGHTS);
144 +
    admission(rootHandle, target);
126 145
    materialization(rootHandle);
127 146
    execution(target, child);
128 147
    reject(0, 76, [rootHandle.bits, target.bits, 0x100000020, 0, 0, 0, 0], abi::Error::InvalidArg);
129 148
    reject(0, 76, [rootHandle.bits, target.bits, abi::DESTROY as u64, 0, 0, 0, 0], abi::Error::InvalidArg);
130 149
    let createGrant = (abi::CREATE | abi::GRANT) as u64;
kernel/check/capabilities.rad +5 -1
9 9
use core::resources;
10 10
use core::frames;
11 11
use core::memory;
12 12
use core::platform;
13 13
use core::state;
14 +
use core::images;
14 15
15 16
/// Domain storage for capability transactions.
16 17
static DOMAINS: [domains::Domain; 3] = undefined;
17 18
/// Physical resources used by the transactions.
18 19
static OBJECTS: [resources::Slot; 4] = undefined;
26 27
static ASSIGNED: [bool; 1] = undefined;
27 28
/// Per-domain grant backing storage.
28 29
static GRANTS: [u64; 3] = undefined;
29 30
/// Shared mechanism state for each transaction.
30 31
static KERNEL: state::State = undefined;
32 +
/// Image identities owned by this isolated kernel state.
33 +
static IMAGES: images::Registry = undefined;
31 34
32 35
/// Return a grant error as a test value.
33 36
fn grantError(source: abi::Handle, target: abi::Handle) -> abi::Error {
34 37
    let _handle = try capabilities::grant(&mut KERNEL, 0, source, target, abi::RIGHTS as u64) catch error {
35 38
        return error;
46 49
    set machine.memoryCount = 0;
47 50
    set machine.reservedCount = 0;
48 51
    try! frames::init(&mut POOL, &machine);
49 52
    let mut ram: memory::Memory = undefined;
50 53
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 3);
51 -
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
54 +
    images::init(&mut IMAGES);
55 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram, images: &mut IMAGES };
52 56
    let rootHandle = domains::root(&mut DOMAINS[..], 0);
53 57
    let root = try! domains::resolve(&DOMAINS[..], 0, rootHandle, abi::CREATE);
54 58
    let child = try! domains::create(&mut DOMAINS[..], root, 1);
55 59
    let childHandle = handles::install(&mut DOMAINS[0].handles, 2, child, abi::DOMAIN_RIGHTS);
56 60
    let device = try! resources::create(&mut OBJECTS[..], resources::Value::Device(fdt::Range { base: 0x10000000, size: 256 }));
kernel/check/devices.rad +5 -1
10 10
use core::handles;
11 11
use core::memory;
12 12
use core::platform;
13 13
use core::resources;
14 14
use core::state;
15 +
use core::images;
15 16
16 17
/// One caller and its device authority.
17 18
static DOMAINS: [domains::Domain; 1] = undefined;
18 19
/// Device windows, including deliberately invalid physical bounds.
19 20
static OBJECTS: [resources::Slot; 4] = undefined;
27 28
static ASSIGNED: [bool; 1] = undefined;
28 29
/// Domain grant backing.
29 30
static GRANTS: [u64; 1] = undefined;
30 31
/// Shared state for the checked materialization entry point.
31 32
static KERNEL: state::State = undefined;
33 +
/// Image identities owned by this isolated kernel state.
34 +
static IMAGES: images::Registry = undefined;
32 35
33 36
/// Install a real device resource and read-only capability without touching MMIO.
34 37
fn device(base: u64, size: u64) -> abi::Handle {
35 38
    let object = try! resources::create(&mut OBJECTS[..], resources::Value::Device(fdt::Range { base, size }));
36 39
    resources::retain(&mut OBJECTS[..], object);
55 58
    set machine.memoryCount = 0;
56 59
    set machine.reservedCount = 0;
57 60
    try! frames::init(&mut POOL, &machine);
58 61
    let mut ram: memory::Memory = undefined;
59 62
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 1);
60 -
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
63 +
    images::init(&mut IMAGES);
64 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram, images: &mut IMAGES };
61 65
    let root = domains::root(&mut DOMAINS[..], 0);
62 66
    let registers = device(0x1000, 16);
63 67
    assert try! devices::access(&KERNEL, 0, registers, 8, 8, abi::READ) == 0x1008;
64 68
    assert error(registers, 16, 1, abi::READ) == abi::Error::InvalidArg;
65 69
    assert error(registers, 12, 8, abi::READ) == abi::Error::InvalidArg;
kernel/check/lifecycle.rad +5 -1
14 14
use core::memory;
15 15
use core::pages;
16 16
use core::platform;
17 17
use core::resources;
18 18
use core::state;
19 +
use core::images;
19 20
20 21
/// Simulated physical backing, never dereferenced as a host physical address.
21 22
static RAM: [u8; 64 * frames::PAGE_SIZE] = undefined;
22 23
/// Enough slots for a creator chain, survivors, and an unrelated replacement.
23 24
static DOMAINS: [domains::Domain; 8] = undefined;
30 31
static PINS: [u16; 64] = undefined;
31 32
static ASSIGNED: [bool; 64] = undefined;
32 33
static GRANTS: [u64; 8] = undefined;
33 34
/// Public mechanism state shared by each isolated scenario.
34 35
static KERNEL: state::State = undefined;
36 +
/// Image identities owned by this isolated kernel state.
37 +
static IMAGES: images::Registry = undefined;
35 38
36 39
/// Clear only the allocator's simulated physical range.
37 40
fn clear(base: u64, size: u32) {
38 41
    assert base <= RAM.len as u64 and size as u64 <= RAM.len as u64 - base;
39 42
    for i in base as u32..base as u32 + size { set RAM[i] = 0; }
49 52
    let mut ram: memory::Memory = undefined;
50 53
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], DOMAINS.len);
51 54
    domains::init(&mut DOMAINS[..]);
52 55
    resources::init(&mut OBJECTS[..]);
53 56
    for i in 0..CONTEXTS.len { contexts::init(&mut CONTEXTS[i], i); }
54 -
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
57 +
    images::init(&mut IMAGES);
58 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram, images: &mut IMAGES };
55 59
    let root = domains::root(&mut DOMAINS[..], 0);
56 60
    return try! domains::resolve(&DOMAINS[..], 0, root, abi::CREATE);
57 61
}
58 62
59 63
/// Install test-owned authority through the real handle generation mechanism.
kernel/check/notifications.rad +5 -1
13 13
use core::memory;
14 14
use core::notifications;
15 15
use core::platform;
16 16
use core::resources;
17 17
use core::state;
18 +
use core::images;
18 19
19 20
/// Independent sender and receiver domain storage.
20 21
static DOMAINS: [domains::Domain; 2] = undefined;
21 22
/// Reserve and execution-loan resource slots.
22 23
static OBJECTS: [resources::Slot; 3] = undefined;
30 31
static ASSIGNED: [bool; 1] = undefined;
31 32
/// Empty-pool grants.
32 33
static GRANTS: [u64; 2] = undefined;
33 34
/// Kernel mechanism storage.
34 35
static KERNEL: state::State = undefined;
36 +
/// Image identities owned by this isolated kernel state.
37 +
static IMAGES: images::Registry = undefined;
35 38
36 39
/// Consume the receiver's next Wakeup with acquire/release progress.
37 40
fn consume(token: u32, sender: u32) {
38 41
    let queue = &mut DOMAINS[1].events;
39 42
    let head = atomic::load(&queue.ring.head);
61 64
    set machine.memoryCount = 0;
62 65
    set machine.reservedCount = 0;
63 66
    try! frames::init(&mut POOL, &machine);
64 67
    let mut ram: memory::Memory = undefined;
65 68
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 2);
66 -
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
69 +
    images::init(&mut IMAGES);
70 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram, images: &mut IMAGES };
67 71
    let rootHandle = domains::root(&mut DOMAINS[..], 0);
68 72
    let root = try! domains::resolve(&DOMAINS[..], 0, rootHandle, abi::CREATE);
69 73
    let child = try! domains::create(&mut DOMAINS[..], root, 0);
70 74
    let target = handles::install(&mut DOMAINS[0].handles, 2, child, abi::DOMAIN_RIGHTS);
71 75
    set DOMAINS[0].state = domains::Lifecycle::Active;
kernel/check/pages.rad +5 -1
10 10
use core::memory;
11 11
use core::pages;
12 12
use core::platform;
13 13
use core::resources;
14 14
use core::state;
15 +
use core::images;
15 16
16 17
/// Test-owned physical RAM.
17 18
static RAM: [u8; 16 * frames::PAGE_SIZE] = undefined;
18 19
/// Domain storage for allocator and recipient authority.
19 20
static DOMAINS: [domains::Domain; 3] = undefined;
29 30
static ASSIGNED: [bool; 16] = undefined;
30 31
/// Per-domain persistent grant sets.
31 32
static GRANTS: [u64; 3] = undefined;
32 33
/// State shared by all tested control-plane operations.
33 34
static KERNEL: state::State = undefined;
35 +
/// Image identities owned by this isolated kernel state.
36 +
static IMAGES: images::Registry = undefined;
34 37
35 38
/// Clear a physical allocation before it is exposed through a handle.
36 39
fn clear(base: u64, size: u32) {
37 40
    assert base <= RAM.len as u64 and size as u64 <= RAM.len as u64 - base;
38 41
    for i in base as u32..base as u32 + size { set RAM[i] = 0; }
48 51
    let mut ram: memory::Memory = undefined;
49 52
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 3);
50 53
    domains::init(&mut DOMAINS[..]);
51 54
    resources::init(&mut OBJECTS[..]);
52 55
    contexts::init(&mut CONTEXTS[0], 0);
53 -
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
56 +
    images::init(&mut IMAGES);
57 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram, images: &mut IMAGES };
54 58
    let rootHandle = domains::root(&mut DOMAINS[..], 0);
55 59
    let root = try! domains::resolve(&DOMAINS[..], 0, rootHandle, abi::CREATE);
56 60
    let child = try! domains::create(&mut DOMAINS[..], root, 1);
57 61
    let childHandle = handles::install(&mut DOMAINS[0].handles, 2, child, abi::DOMAIN_RIGHTS);
58 62
    let left = try! pages::allocate(&mut KERNEL, 0, abi::Handle { bits: 0 }, 4, clear);
kernel/context/wait.rad +5 -1
14 14
use core::notifications;
15 15
use core::platform;
16 16
use core::resources;
17 17
use core::state;
18 18
use core::timers;
19 +
use core::images;
19 20
20 21
/// Trusted test program that requests a timeout and consumes its event after Wait.
21 22
fn entry() -> u64;
22 23
/// One complete domain and event queue.
23 24
static DOMAINS: [domains::Domain; 1] = undefined;
35 36
static GRANTS: [u64; 1] = undefined;
36 37
/// Private one-shot timer storage.
37 38
static TIMERS: timers::Store = undefined;
38 39
/// Shared kernel mechanisms.
39 40
static KERNEL: state::State = undefined;
41 +
/// Image identities owned by this isolated kernel state.
42 +
static IMAGES: images::Registry = undefined;
40 43
41 44
/// Resume one authorized user interval until a real trap and charge it once.
42 45
unsafe fn step() -> u64 {
43 46
    let context = &mut CONTEXTS[0];
44 47
    let budget = budget_caps::value(&mut KERNEL, context.budget);
66 69
    set machine.memoryCount = 0;
67 70
    set machine.reservedCount = 0;
68 71
    try! frames::init(&mut POOL, &machine);
69 72
    let mut ram: memory::Memory = undefined;
70 73
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 1);
71 -
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
74 +
    images::init(&mut IMAGES);
75 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram, images: &mut IMAGES };
72 76
    let handle = domains::root(&mut DOMAINS[..], 0);
73 77
    let root = try! domains::resolve(&DOMAINS[..], 0, handle, abi::EXECUTE);
74 78
    set DOMAINS[0].state = domains::Lifecycle::Active;
75 79
    set DOMAINS[0].env.eventsPointer = &DOMAINS[0].events.ring as u64;
76 80
    try! contexts::prepare(&mut CONTEXTS[0], root, entry(), 0, DOMAINS[0].env);
kernel/core.rad +2 -0
31 31
export mod calls;
32 32
export mod execution;
33 33
export mod control;
34 34
export mod teardown;
35 35
export mod runtime;
36 +
export mod images;
37 +
export mod loading;
kernel/core/activation.rad +13 -10
12 12
use core::frames;
13 13
use core::handles;
14 14
use core::pages;
15 15
use core::resources;
16 16
use core::state;
17 -
use images;
17 +
use core::images;
18 +
use core::smp;
18 19
19 20
/// Fixed private native stack per context: 64 KiB for locals and saved registers.
20 21
/// The authorized Page stack has separate bounds in Env.
21 22
export constant PRIVATE_STACK_SIZE: u32 = 64 * 1024;
22 23
26 27
    run: frames::Run,
27 28
    /// Aligned mutable-state base within the owned run.
28 29
    base: u64,
29 30
}
30 31
31 -
/// Allocate and copy a checked catalog initializer before domain installation.
32 +
/// Allocate and copy a published image initializer before domain installation.
32 33
/// The caller installs the returned ownership or reclaims it on failure.
33 -
export fn initialize(pool: *mut frames::Pool, image: u32, clear: fn(u64, u32), copy: fn(u64, u64, u32)) -> ImageState throws (abi::Error) {
34 -
    let descriptor = images::get(image);
34 +
export fn initialize(pool: *mut frames::Pool, registry: *images::Registry, image: u32, clear: fn(u64, u32), copy: fn(u64, u64, u32)) -> ImageState throws (abi::Error) {
35 +
    let descriptor = images::get(registry, image);
35 36
    let mut run = frames::Run { first: 0, count: 0 };
36 37
    let mut base: u64 = 0;
37 38
    if descriptor.stateSize <> 0 {
38 39
        let alignment = descriptor.stateAlignment as u64;
39 40
        // These are trusted compiler facts, not caller-provided metadata.
51 52
        };
52 53
        set run = frames::install(allocation);
53 54
        let first = run.first as u64 * frames::PAGE_SIZE as u64;
54 55
        set base = (first + alignment - 1) & ~(alignment - 1);
55 56
        copy(base, descriptor.initial, descriptor.stateSize);
57 +
        // The immutable relocation helper may have been compiled on another hart.
58 +
        smp::syncInstructions();
56 59
        descriptor.relocate(base);
57 60
    }
58 61
    return ImageState { run, base };
59 62
}
60 63
61 -
/// Instantiate an admitted catalog graph under the caller's Create authority.
62 -
/// image is only a catalog index, never an entry address or runtime descriptor.
64 +
/// Instantiate an admitted image graph under the caller's Create authority.
65 +
/// image is only a stable image ID, never an entry address or runtime descriptor.
63 66
/// clear(base, size) zeros exactly the supplied physical allocation;
64 67
/// copy(destination, source, size) copies exactly the immutable initializer.
65 68
/// Both callbacks obey the serialization and lifetime rules of this module.
66 69
/// All capacity checks precede allocation; failure installs no authority or
67 70
/// domain incarnation and returns any provisionally allocated private frames.
68 71
export fn create(kernel: *mut state::State, caller: u32, authority: abi::Handle, image: u64, clear: fn(u64, u32), copy: fn(u64, u64, u32)) -> abi::Handle throws (abi::Error) {
69 72
    let creator = try domains::authority(kernel.domains, caller, authority, abi::CREATE);
70 73
    if creator.index <> caller { throw abi::Error::Denied; }
71 -
    if image >= images::COUNT as u64 { throw abi::Error::VerifyFailed; }
74 +
    if image >= kernel.images.count as u64 { throw abi::Error::VerifyFailed; }
72 75
    let slot = try handles::vacant(&kernel.domains[caller].handles);
73 76
    let _domainSlot = try domains::vacant(kernel.domains);
74 77
    // Refresh only consumed event credits; do not reserve a child until commit.
75 78
    try events::refresh(&mut kernel.domains[caller].events);
76 79
    if kernel.domains[caller].events.reserved == events::CRITICAL {
77 80
        throw abi::Error::Exhausted;
78 81
    }
79 -
    let initial = try initialize(kernel.memory.pool, image as u32, clear, copy);
82 +
    let initial = try initialize(kernel.memory.pool, kernel.images, image as u32, clear, copy);
80 83
    // The consumer can publish another head during allocation. Revalidate it
81 84
    // when reserving the terminal event; even that late failure returns RAM.
82 85
    let object = try domains::create(kernel.domains, creator, image as u32) catch error {
83 86
        if initial.run.count <> 0 { frames::reclaim(kernel.memory.pool, initial.run); }
84 87
        throw error;
124 127
            break;
125 128
        }
126 129
    }
127 130
    if slot == kernel.contexts.len { throw abi::Error::Exhausted; }
128 131
    let domain = &kernel.domains[owner.index];
129 -
    assert domain.image < images::COUNT;
130 -
    let descriptor = images::get(domain.image);
132 +
    assert domain.image < kernel.images.count;
133 +
    let descriptor = images::get(kernel.images, domain.image);
131 134
    let allocation = try frames::allocate(kernel.memory.pool, PRIVATE_STACK_SIZE / frames::PAGE_SIZE, clear) catch {
132 135
        throw abi::Error::OutOfMemory;
133 136
    };
134 137
    let private = frames::install(allocation);
135 138
    let privateBase = private.first as u64 * frames::PAGE_SIZE as u64;
kernel/core/boot.rad +10 -7
20 20
use core::physical;
21 21
use core::platform;
22 22
use core::resources;
23 23
use core::smp;
24 24
use core::state;
25 -
use images;
25 +
use core::images;
26 26
27 27
/// Space for one Page per tracked frame plus all bounded non-Page resources.
28 28
constant RESOURCE_CAPACITY: u32 = frames::MAX_FRAMES + 512 + platform::MAX_DEVICES + platform::MAX_IRQS;
29 29
/// Root's authorized Page stack; activation allocates a separate native stack.
30 30
constant ROOT_STACK_FRAMES: u32 = 16;
93 93
    let contextStore = storage(pool, contexts::CAPACITY, @sizeOf(contexts::Context), @alignOf(contexts::Context));
94 94
    let pinStore = storage(pool, frames::MAX_FRAMES, @sizeOf(u16), @alignOf(u16));
95 95
    let objectStore = storage(pool, frames::MAX_FRAMES, @sizeOf(bool), @alignOf(bool));
96 96
    let grantCount = frames::MAX_FRAMES / 64 * abi::MAX_DOMAINS;
97 97
    let grantStore = storage(pool, grantCount, @sizeOf(u64), @alignOf(u64));
98 +
    let imageStore = storage(pool, 1, @sizeOf(images::Registry), @alignOf(images::Registry));
99 +
    set kernel.images = imageStore as *mut images::Registry;
100 +
    images::init(kernel.images);
98 101
    set kernel.domains = @sliceOf(domainStore as *mut domains::Domain, abi::MAX_DOMAINS);
99 102
    set kernel.resources = @sliceOf(resourceStore as *mut resources::Slot, RESOURCE_CAPACITY);
100 103
    set kernel.contexts = @sliceOf(contextStore as *mut contexts::Context, contexts::CAPACITY);
101 104
    domains::init(kernel.domains);
102 105
    resources::init(kernel.resources);
139 142
export unsafe fn init(kernel: *mut state::State, pool: *mut frames::Pool, machine: *platform::Platform, controller: *mut interrupts::Controller) -> u64 {
140 143
    assert pool.limit > 0 and pool.limit <= frames::MAX_FRAMES;
141 144
    assert machine.deviceCount <= platform::MAX_DEVICES;
142 145
    assert machine.irqCount > 0 and machine.irqCount < platform::MAX_IRQS;
143 146
    assert machine.hartCount > 0 and machine.hartCount <= platform::MAX_HARTS;
144 -
    assert machine.frequency > 0 and images::ROOT < images::COUNT;
147 +
    assert machine.frequency > 0;
145 148
    let budgetBaseSlot = 2 + machine.deviceCount + machine.irqCount;
146 149
    let bootstrapBudgetSlot = budgetBaseSlot + machine.hartCount;
147 150
    assert bootstrapBudgetSlot + 3 <= abi::MAX_HANDLES;
148 151
    let mut bootHartIndex = machine.hartCount;
149 152
    for i in 0..machine.hartCount {
151 154
        if machine.harts[i].id == 0 { set bootHartIndex = i; }
152 155
    }
153 156
    assert bootHartIndex < machine.hartCount;
154 157
    tables(kernel, pool);
155 158
    interrupts::init(controller, machine, bootHartIndex);
156 -
    let root = domains::root(kernel.domains, images::ROOT);
159 +
    let root = domains::root(kernel.domains, kernel.images.root);
157 160
    let owner = try! domains::resolve(kernel.domains, 0, root, 0);
158 -
    let initial = try! activation::initialize(pool, images::ROOT, physical::clear, physical::copy);
161 +
    let initial = try! activation::initialize(pool, kernel.images, kernel.images.root, physical::clear, physical::copy);
159 162
    set kernel.domains[0].private = initial.run;
160 163
    set kernel.domains[0].env.stateBase = initial.base;
161 164
162 165
    // Fixed slots contain no implicit discovery or ambient resource authority.
163 166
    for i in 0..machine.deviceCount {
179 182
    let bootstrap = try! budgets::init(0, frequency * 60);
180 183
    let bootBudget = install(kernel, bootstrapBudgetSlot, resources::Value::Budget(bootstrap), BUDGET_RIGHTS);
181 184
182 185
    // These two Page objects follow all fixed authority slots: startup, then stack.
183 186
    // Large catalogs may span several frames but still occupy one startup Page.
184 -
    let wordCount = 8 as u64 + machine.hartCount as u64 * 2 + images::COUNT as u64;
187 +
    let wordCount = 8 as u64 + machine.hartCount as u64 * 2 + kernel.images.count as u64;
185 188
    let byteCount = wordCount * 8;
186 189
    let startupFrames = (byteCount + frames::PAGE_SIZE as u64 - 1) / frames::PAGE_SIZE as u64;
187 190
    let startup = try! pages::allocate(kernel, 0, root, startupFrames, physical::clear);
188 191
    let args = try! pages::access(kernel, 0, startup, abi::READ | abi::WRITE);
189 192
    let words = @sliceOf(physical::bytes(args.base, byteCount as u32).ptr as *mut u64, wordCount as u32);
192 195
    set words[2] = machine.irqCount as u64;
193 196
    set words[3] = machine.hartCount as u64;
194 197
    set words[4] = budgetBaseSlot as u64;
195 198
    set words[5] = bootstrapBudgetSlot as u64;
196 199
    set words[6] = frequency;
197 -
    set words[7] = images::COUNT as u64;
200 +
    set words[7] = kernel.images.count as u64;
198 201
    for i in 0..machine.hartCount {
199 202
        set words[8 + i * 2] = machine.harts[i].id as u64;
200 203
        set words[9 + i * 2] = (budgetBaseSlot + i) as u64;
201 204
    }
202 -
    for i in 0..images::COUNT {
205 +
    for i in 0..kernel.images.count {
203 206
        set words[8 + machine.hartCount * 2 + i] = i as u64;
204 207
    }
205 208
    let stack = try! pages::allocate(kernel, 0, root, ROOT_STACK_FRAMES as u64, physical::clear);
206 209
    let stackRange = try! pages::access(kernel, 0, stack, abi::WRITE);
207 210
    let id = try! activation::activate(kernel, 0, root, stackRange.base + stackRange.size,
kernel/core/calls.rad +4 -1
10 10
use core::domains;
11 11
use core::events;
12 12
use core::frames;
13 13
use core::handles;
14 14
use core::lifecycle;
15 +
use core::loading;
15 16
use core::notifications;
16 17
use core::pages;
17 18
use core::physical;
18 19
use core::platform;
19 20
use core::resources;
167 168
/// Returning effects finish here. Runtime completes control obligations before
168 169
/// replying, advances returning ecalls, and faults query/slot errors (44..48,
169 170
/// 62, 74, 75) and undefined calls rather than exposing their error return.
170 171
/// Calls 60/61/62 support trusted runtime wrappers: Page bounds, one transient
171 172
/// Device access, and a typed existing-slot lookup. RIL verification is deferred.
172 -
export fn dispatch(kernel: *mut state::State, caller: u32, number: u64, arguments: *[u64], now: u64) -> Outcome throws (abi::Error) {
173 +
/// The caller holds the kernel lock; available frames must name mapped free RAM.
174 +
export unsafe fn dispatch(kernel: *mut state::State, caller: u32, number: u64, arguments: *[u64], now: u64) -> Outcome throws (abi::Error) {
173 175
    assert caller < kernel.domains.len and arguments.len >= 7;
174 176
    let first = abi::Handle { bits: arguments[0] };
175 177
    let second = abi::Handle { bits: arguments[1] };
176 178
    let mut values: [u64; 4] = [0; 4];
177 179
    match number {
264 266
            set values[1] = kernel.contexts[index].hart as u64;
265 267
            set values[2] = kernel.contexts[index].status as u64;
266 268
            set values[3] = try contextRemaining(kernel, index, now);
267 269
        },
268 270
        case 76 => set values[0] = (try delegate(kernel, caller, first, second, arguments[2])).bits,
271 +
        case 77 => set values[0] = try loading::load(kernel, caller, first, arguments[1], arguments[2]),
269 272
        else => throw abi::Error::InvalidArg,
270 273
    }
271 274
    return Outcome::Reply { values };
272 275
}
kernel/core/control.rad +1 -1
70 70
71 71
/// Distinguish undefined calls from errors returned by defined control operations.
72 72
fn defined(number: u64) -> bool {
73 73
    return (number >= 10 and number <= 12) or (number >= 20 and number <= 23)
74 74
        or number == 30 or number == 31 or (number >= 40 and number <= 50)
75 -
        or (number >= 60 and number <= 62) or (number >= 70 and number <= 76);
75 +
        or (number >= 60 and number <= 62) or (number >= 70 and number <= 77);
76 76
}
77 77
78 78
/// Commit one call. Architectural return and budget charging must precede this.
79 79
unsafe fn apply(runtime: *mut execution::State, index: u32, now: u64) throws (abi::Error) {
80 80
    let kernel = runtime.kernel;
kernel/core/images.rad added +78 -0
1 +
//! Machine-lifetime native images, serialized by the kernel critical section.
2 +
//!
3 +
//! IDs are monotonically published and never reused. Registry allocations are
4 +
//! not Page objects or domain private state and survive every domain teardown.
5 +
6 +
use core::abi;
7 +
use core::frames;
8 +
use images;
9 +
10 +
/// Maximum admitted images, including immutable bootstrap descriptors.
11 +
export constant CAPACITY: u32 = 64;
12 +
13 +
/// Shared native code and initializer with separately allocated private state.
14 +
export record Descriptor: Copy {
15 +
    /// Native entry accepting a context Env pointer.
16 +
    entry: u64,
17 +
    /// Immutable bytes copied into each new domain's private state.
18 +
    initial: u64,
19 +
    /// Bytes required for each domain's private image state.
20 +
    stateSize: u32,
21 +
    /// Power-of-two alignment of private image state.
22 +
    stateAlignment: u32,
23 +
    /// Repair private pointers after copying initializer bytes to base.
24 +
    relocate: fn(u64),
25 +
}
26 +
27 +
/// Stable image storage borrowed by one kernel state for machine lifetime.
28 +
export record Registry: Copy {
29 +
    /// Published descriptors; only indices below count may be read.
30 +
    descriptors: [Descriptor; CAPACITY],
31 +
    /// Exclusive native allocations; bootstrap images use empty runs.
32 +
    native: [frames::Run; CAPACITY],
33 +
    /// Next image ID and number of fully published descriptors.
34 +
    count: u32,
35 +
    /// Bootstrap root image ID, retained independently of later admissions.
36 +
    root: u32,
37 +
}
38 +
39 +
/// Initialize fresh registry storage from the linked bootstrap package once.
40 +
/// No live registry may be reset because its native allocations are permanent.
41 +
export fn init(registry: *mut Registry) {
42 +
    assert images::COUNT > 0 and images::COUNT <= CAPACITY;
43 +
    assert images::ROOT < images::COUNT;
44 +
    set registry.count = 0;
45 +
    set registry.root = images::ROOT;
46 +
    for i in 0..images::COUNT {
47 +
        let source = images::get(i);
48 +
        set registry.descriptors[i] = Descriptor {
49 +
            entry: source.entry, initial: source.initial,
50 +
            stateSize: source.stateSize, stateAlignment: source.stateAlignment,
51 +
            relocate: source.relocate,
52 +
        };
53 +
        set registry.native[i] = frames::Run { first: 0, count: 0 };
54 +
    }
55 +
    set registry.count = images::COUNT;
56 +
}
57 +
58 +
/// Preflight the next stable ID without publishing or reserving a descriptor.
59 +
export fn vacant(registry: *Registry) -> u32 throws (abi::Error) {
60 +
    if registry.count == CAPACITY { throw abi::Error::Exhausted; }
61 +
    return registry.count;
62 +
}
63 +
64 +
/// Read a published immutable descriptor while holding the kernel lock.
65 +
export fn get(registry: *Registry, image: u32) -> Descriptor {
66 +
    assert image < registry.count;
67 +
    return registry.descriptors[image];
68 +
}
69 +
70 +
/// Publish a finished native allocation under the previously checked next ID.
71 +
/// Caller holds the global lock across preflight, compilation and publication.
72 +
/// Every architectural user entry instruction-fences its hart before execution.
73 +
export fn publish(registry: *mut Registry, image: u32, descriptor: Descriptor, run: frames::Run) {
74 +
    assert image == registry.count and image < CAPACITY and run.count > 0;
75 +
    set registry.descriptors[image] = descriptor;
76 +
    set registry.native[image] = run;
77 +
    set registry.count = image + 1;
78 +
}
kernel/core/loading.rad added +120 -0
1 +
//! Trusted binary RIL admission under the global kernel critical section.
2 +
//!
3 +
//! Decoding and native compilation do not constitute a safety or provenance
4 +
//! proof. The input snapshot, string pool and bounded compiler arenas occupy
5 +
//! temporary physical frames; no compiler workspace lives in kernel BSS.
6 +
7 +
use core::abi;
8 +
use core::domains;
9 +
use core::frames;
10 +
use core::images;
11 +
use core::pages;
12 +
use core::physical;
13 +
use core::state;
14 +
use std::lang::alloc;
15 +
use std::lang::il;
16 +
use std::lang::strings;
17 +
18 +
/// Largest accepted binary RIL byte range.
19 +
export constant MAX_BYTES: u32 = 16 * 1024 * 1024;
20 +
/// Temporary contiguous storage reclaimed after every admission attempt.
21 +
export constant WORKSPACE_BYTES: u32 = 96 * 1024 * 1024;
22 +
/// Persistent compiler metadata and emitted code before final placement.
23 +
constant ARENA_BYTES: u32 = 32 * 1024 * 1024;
24 +
/// Number of permitted native user::sys primitives, in address-table order.
25 +
constant BINDING_COUNT: u32 = 14;
26 +
27 +
/// Address of one exact user::sys RAS primitive; index is below BINDING_COUNT.
28 +
/// The native table contains no privileged kernel exports.
29 +
fn bindingAddress(index: u32) -> u64;
30 +
31 +
/// Fill the complete runtime native allowlist without exposing kernel symbols.
32 +
fn bindings(destination: *mut [il::images::Binding]) {
33 +
    let names: [*[u8]; BINDING_COUNT] = [
34 +
        "user::sys::rawCall", "user::sys::pointer",
35 +
        "user::sys::loadAcquire", "user::sys::storeRelease",
36 +
        "user::sys::lockAcquire", "user::sys::lockRelease",
37 +
        "user::sys::read8", "user::sys::read16",
38 +
        "user::sys::read32", "user::sys::read64",
39 +
        "user::sys::write8", "user::sys::write16",
40 +
        "user::sys::write32", "user::sys::write64",
41 +
    ];
42 +
    for i in 0..BINDING_COUNT {
43 +
        set destination[i] = il::images::Binding { name: names[i], address: bindingAddress(i) };
44 +
    }
45 +
}
46 +
47 +
/// Compile a private immutable snapshot and retain only a completed allocation.
48 +
/// The caller owns workspace and reclaims it on both success and failure.
49 +
unsafe fn compile(kernel: *mut state::State, image: u32, source: u64, size: u32, workspace: frames::Run) -> u64 throws (abi::Error) {
50 +
    let base = workspace.first as u64 * frames::PAGE_SIZE as u64;
51 +
    physical::copy(base, source, size);
52 +
    let snapshot = physical::bytes(base, size);
53 +
    let poolBase = base + MAX_BYTES as u64;
54 +
    let pool = physical::bytes(poolBase, @sizeOf(strings::Pool)).ptr as *mut opaque as *mut strings::Pool;
55 +
    // Frame allocation cleared every pool slot and count without a large value copy.
56 +
    let poolBytes = (@sizeOf(strings::Pool) + frames::PAGE_SIZE - 1) & ~(frames::PAGE_SIZE - 1);
57 +
    let arenaBase = poolBase + poolBytes as u64;
58 +
    let mut arena = alloc::new(physical::bytes(arenaBase, ARENA_BYTES));
59 +
    let scratchBase = arenaBase + ARENA_BYTES as u64;
60 +
    let scratchBytes = WORKSPACE_BYTES - MAX_BYTES - poolBytes - ARENA_BYTES;
61 +
    let mut scratch = alloc::new(physical::bytes(scratchBase, scratchBytes));
62 +
    let mut allowed: [il::images::Binding; BINDING_COUNT] = undefined;
63 +
    bindings(&mut allowed[..]);
64 +
    let mut prepared = try il::images::prepare(snapshot, &allowed[..], &mut arena, &mut scratch, pool) catch {
65 +
        throw abi::Error::VerifyFailed;
66 +
    };
67 +
    let alignment = prepared.alignment as u64;
68 +
    if prepared.size == 0 or alignment == 0 or alignment & (alignment - 1) <> 0 {
69 +
        throw abi::Error::VerifyFailed;
70 +
    }
71 +
    let mut padding: u64 = 0;
72 +
    if alignment > frames::PAGE_SIZE as u64 { set padding = alignment - frames::PAGE_SIZE as u64; }
73 +
    let bytes = prepared.size as u64 + padding;
74 +
    let count = (bytes + frames::PAGE_SIZE as u64 - 1) / frames::PAGE_SIZE as u64;
75 +
    if count > frames::MAX_FRAMES as u64 { throw abi::Error::OutOfMemory; }
76 +
    let allocation = try frames::allocate(kernel.memory.pool, count as u32, physical::clear) catch {
77 +
        throw abi::Error::OutOfMemory;
78 +
    };
79 +
    let native = frames::install(allocation);
80 +
    let first = native.first as u64 * frames::PAGE_SIZE as u64;
81 +
    let destination = (first + alignment - 1) & ~(alignment - 1);
82 +
    let compiled = try il::images::finish(&mut prepared, physical::bytes(destination, prepared.size)) catch {
83 +
        frames::reclaim(kernel.memory.pool, native);
84 +
        throw abi::Error::VerifyFailed;
85 +
    };
86 +
    images::publish(kernel.images, image, images::Descriptor {
87 +
        entry: compiled.entry, initial: compiled.initial,
88 +
        stateSize: compiled.stateSize, stateAlignment: compiled.stateAlignment,
89 +
        relocate: compiled.relocate,
90 +
    }, native);
91 +
    return image as u64;
92 +
}
93 +
94 +
/// Admit trusted binary RIL without consuming or changing any input Page rights.
95 +
/// Requires live caller-self Create|Allocate authority and complete Read coverage.
96 +
/// All range, capacity and authority failures precede allocation. Compiler errors
97 +
/// publish nothing; workspace and provisional native frames are always reclaimed.
98 +
/// The global lock remains held through snapshot, compilation and publication.
99 +
/// The frame pool must contain only mapped RAM and exclude all live kernel data.
100 +
export unsafe fn load(kernel: *mut state::State, caller: u32, authority: abi::Handle, source: u64, size: u64) -> u64 throws (abi::Error) {
101 +
    let owner = try domains::authority(kernel.domains, caller, authority, abi::CREATE | abi::ALLOCATE) catch {
102 +
        throw abi::Error::Denied;
103 +
    };
104 +
    if owner.index <> caller { throw abi::Error::Denied; }
105 +
    if size == 0 or size > MAX_BYTES as u64 or source > 0xffffffffffffffff - size {
106 +
        throw abi::Error::InvalidArg;
107 +
    }
108 +
    if not pages::covers(kernel, caller, source, size, abi::READ) { throw abi::Error::Denied; }
109 +
    let image = try images::vacant(kernel.images);
110 +
    let allocation = try frames::allocate(kernel.memory.pool, WORKSPACE_BYTES / frames::PAGE_SIZE, physical::clear) catch {
111 +
        throw abi::Error::OutOfMemory;
112 +
    };
113 +
    let workspace = frames::install(allocation);
114 +
    let admitted = try compile(kernel, image, source, size as u32, workspace) catch error {
115 +
        frames::reclaim(kernel.memory.pool, workspace);
116 +
        throw error;
117 +
    };
118 +
    frames::reclaim(kernel.memory.pool, workspace);
119 +
    return admitted;
120 +
}
kernel/core/smp.rad +1 -1
157 157
/// clearing after an unlocked empty observation can lose a concurrent kick.
158 158
export unsafe fn acknowledge(clint: u64, bank: u32);
159 159
160 160
/// Synchronize this hart's instruction fetch after observing published code.
161 161
/// Each executing hart must call this before entering newly published code.
162 -
export unsafe fn syncInstructions();
162 +
export fn syncInstructions();
kernel/core/state.rad +3 -0
2 2
3 3
use core::domains;
4 4
use core::contexts;
5 5
use core::memory;
6 6
use core::resources;
7 +
use core::images;
7 8
8 9
/// Kernel-owned state accessed while the machine critical section is held.
9 10
export record State: Copy {
10 11
    /// Protection-domain slots and their capability tables.
11 12
    domains: *mut [domains::Domain],
13 14
    resources: *mut [resources::Slot],
14 15
    /// Execution identities and protected architectural frames.
15 16
    contexts: *mut [contexts::Context],
16 17
    /// Physical allocations and persistent domain-lifetime pins.
17 18
    memory: memory::Memory,
19 +
    /// Borrowed machine-lifetime image descriptors and native frame ownership.
20 +
    images: *mut images::Registry,
18 21
}
kernel/interrupt.rad +5 -1
16 16
use core::memory;
17 17
use core::mmio;
18 18
use core::platform;
19 19
use core::resources;
20 20
use core::state;
21 +
use core::images;
21 22
22 23
/// One ordered byte access to an already checked device address.
23 24
unsafe fn read8(address: u64) -> u8;
24 25
/// One ordered byte write to an already checked device address.
25 26
unsafe fn write8(address: u64, value: u8);
39 40
static GRANTS: [u64; 2] = undefined;
40 41
/// Private PLIC ownership and coalescing state.
41 42
static PLIC: interrupts::Controller = undefined;
42 43
/// Serialized control-plane state.
43 44
static KERNEL: state::State = undefined;
45 +
/// Image identities owned by this isolated kernel state.
46 +
static IMAGES: images::Registry = undefined;
44 47
45 48
/// Install exclusive authority for one real PLIC source.
46 49
fn interrupt(source: u32, root: abi::Object) -> abi::Handle {
47 50
    let object = try! resources::create(&mut OBJECTS[..], resources::Value::Interrupt(resources::Interrupt { number: source, target: root }));
48 51
    resources::retain(&mut OBJECTS[..], object);
93 96
    set empty.memoryCount = 0;
94 97
    set empty.reservedCount = 0;
95 98
    try! frames::init(&mut POOL, &empty);
96 99
    let mut ram: memory::Memory = undefined;
97 100
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 2);
98 -
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
101 +
    images::init(&mut IMAGES);
102 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram, images: &mut IMAGES };
99 103
    let own = domains::root(&mut DOMAINS[..], 0);
100 104
    let root = try! domains::resolve(&DOMAINS[..], 0, own, abi::CREATE);
101 105
    let child = try! domains::create(&mut DOMAINS[..], root, 0);
102 106
    let target = handles::install(&mut DOMAINS[0].handles, 2, child, abi::DOMAIN_RIGHTS);
103 107
    let device = try! resources::create(&mut OBJECTS[..], resources::Value::Device(machine.devices[0].region));
kernel/loading.rad added +76 -0
1 +
//! Runtime RIL admission and execution through the actual user syscall path.
2 +
use core::abi;
3 +
use core::boot;
4 +
use core::contexts;
5 +
use core::domains;
6 +
use core::handles;
7 +
use core::pages;
8 +
use core::physical;
9 +
use core::runtime;
10 +
use core::smp;
11 +
use core::execution;
12 +
13 +
/// Embedded binary RIL fixture, with no linked native implementation.
14 +
fn input() -> u64;
15 +
/// Exact binary fixture byte extent.
16 +
fn inputSize() -> u64;
17 +
18 +
/// Supply a root-owned input Page after kernel boot has completed.
19 +
unsafe fn arguments(execution: *mut execution::State) {
20 +
    let kernel = execution.kernel;
21 +
    let root = try! handles::get(&kernel.domains[0].handles, 0, abi::Kind::Domain);
22 +
    let context = try! contexts::resolve(kernel.contexts, kernel.domains[0].env.context);
23 +
    let env = &mut kernel.contexts[context].env;
24 +
    let boot = @sliceOf(physical::bytes(env.argsPointer, env.argsSize as u32).ptr as *u64, env.argsSize as u32 / 8);
25 +
    let localReserve = boot[9];
26 +
    let frequency = boot[6];
27 +
    let mut remoteReserve: u64 = 0;
28 +
    if boot[3] > 1 { set remoteReserve = boot[11]; }
29 +
    let mut output: u64 = 0;
30 +
    for i in 0..execution.machine.deviceCount {
31 +
        if execution.machine.devices[i].region.base == 0x10000000 {
32 +
            set output = (try! handles::get(&kernel.domains[0].handles, i + 2, abi::Kind::Device)).bits;
33 +
        }
34 +
    }
35 +
    assert output <> 0;
36 +
    let size = inputSize();
37 +
    assert size > 0 and size <= 16 * 1024 * 1024;
38 +
    let source = try! pages::allocate(kernel, 0, root, (size + 4095) / 4096, physical::clear);
39 +
    let sourceRange = try! pages::access(kernel, 0, source, abi::WRITE);
40 +
    physical::copy(sourceRange.base, input(), size as u32);
41 +
    let args = try! pages::allocate(kernel, 0, root, 1, physical::clear);
42 +
    let range = try! pages::access(kernel, 0, args, abi::WRITE);
43 +
    let words = @sliceOf(physical::bytes(range.base, 48).ptr as *mut u64, 6);
44 +
    set words[0] = localReserve;
45 +
    set words[1] = frequency;
46 +
    set words[2] = source.bits;
47 +
    set words[3] = size;
48 +
    set words[4] = remoteReserve;
49 +
    set words[5] = output;
50 +
    set env.argsPointer = range.base;
51 +
    set env.argsSize = 48;
52 +
}
53 +
54 +
@default unsafe fn main(hardware: u64, description: *u8) -> u32 {
55 +
    let execution = boot::start(hardware, description);
56 +
    let hart = hardware as u32;
57 +
    if hart == 0 {
58 +
        let guard = smp::acquire(&mut execution.lock);
59 +
        arguments(execution);
60 +
        smp::release(guard);
61 +
    }
62 +
    runtime::initializeHart(execution, hart);
63 +
    loop {
64 +
        runtime::step(execution, hart);
65 +
        if hart == 0 {
66 +
            let guard = smp::acquire(&mut execution.lock);
67 +
            let root = &execution.kernel.domains[0];
68 +
            let finished = root.state == domains::Lifecycle::Dead;
69 +
            if finished {
70 +
                assert root.terminalKind == 4 and root.terminalCode == 0;
71 +
            }
72 +
            smp::release(guard);
73 +
            if finished { return 0; }
74 +
        }
75 +
    }
76 +
}
kernel/native.rad +7 -4
13 13
use core::pages;
14 14
use core::physical;
15 15
use core::platform;
16 16
use core::resources;
17 17
use core::state;
18 -
use images;
18 +
use core::images;
19 19
20 20
mod instances;
21 21
22 22
/// Isolated consumer used by the native graph regressions.
23 23
static DOMAINS: [domains::Domain; 3] = undefined;
33 33
static CLAIMS: [bool; frames::MAX_FRAMES] = undefined;
34 34
/// Domain-lifetime frame bitmap.
35 35
static GRANTS: [u64; 3 * frames::MAX_FRAMES / 64] = undefined;
36 36
/// Actual capability and Page mechanisms used by the fixture calls.
37 37
static KERNEL: state::State = undefined;
38 +
/// Image identities owned by this isolated kernel state.
39 +
static IMAGES: images::Registry = undefined;
38 40
39 41
/// Run one graph against actual Page authority and check its terminal call.
40 42
unsafe fn run(image: u32, expected: u32, own: abi::Handle, queued: bool) {
41 -
    let descriptor = images::get(image);
43 +
    let descriptor = images::get(KERNEL.images, image);
42 44
    let stack = try! pages::allocate(&mut KERNEL, 0, own, 1, physical::clear);
43 45
    let range = try! pages::access(&KERNEL, 0, stack, abi::READ | abi::WRITE);
44 46
    let args = physical::bytes(range.base, 8).ptr as *mut u64;
45 47
    set *args = stack.bits if image == 6 else own.bits;
46 48
    let private = frames::install(try! frames::allocate(&mut POOL, 16, physical::clear));
110 112
    domains::init(&mut DOMAINS[..]);
111 113
    resources::init(&mut OBJECTS[..]);
112 114
    for i in 0..CONTEXTS.len { contexts::init(&mut CONTEXTS[i], i); }
113 115
    let mut ram: memory::Memory = undefined;
114 116
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut CLAIMS[..], &mut GRANTS[..], 3);
115 -
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
116 -
    let own = domains::root(&mut DOMAINS[..], images::ROOT);
117 +
    images::init(&mut IMAGES);
118 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram, images: &mut IMAGES };
119 +
    let own = domains::root(&mut DOMAINS[..], KERNEL.images.root);
117 120
    let expected: [u32; 7] = [42, 21, 73, 77, 0, 42, 81];
118 121
    for i in 0..expected.len { run(i, expected[i], own, i == 5); }
119 122
    run(5, 42, own, false);
120 123
    instances::run(&mut KERNEL, own, machine.clint.base);
121 124
    return 0;
kernel/user.rad +1 -0
1 1
//! User bindings for the kernel's capability-checked direct-call ABI.
2 2
3 3
export unsafe mod sys;
4 +
export mod launch;
kernel/user/launch.rad added +56 -0
1 +
//! Sequential child execution with explicit stack and finite CPU authority.
2 +
use abi;
3 +
use user::sys;
4 +
5 +
/// Consume terminal reports for one child; the caller owns the event consumer.
6 +
fn terminal(env: *sys::Env, child: u16) -> ?sys::Event {
7 +
    while let event = sys::eventsPop(env) {
8 +
        if (event.kind == 3 or event.kind == 4) and event.value == child as u64 {
9 +
            return event;
10 +
        }
11 +
    }
12 +
    return nil;
13 +
}
14 +
15 +
/// Activate one child and resume voluntary yields until its terminal report.
16 +
fn execute(env: *sys::Env, child: abi::Handle, budget: abi::Handle, stack: abi::Handle) -> sys::Event throws (abi::Error) {
17 +
    let id = sys::queryDomain(child).id;
18 +
    let bytes = try sys::pageSliceMut(stack, 0, 8192);
19 +
    for i in 0..bytes.len { set bytes[i] = 0; }
20 +
    let _stack = try sys::capabilityGrant(stack, child, (abi::READ | abi::WRITE) as u64);
21 +
    let top = sys::queryPage(stack).base + 8192;
22 +
    try sys::domainActivate(child, top, &bytes[..0]);
23 +
    let context = sys::queryContext(child, 0).id;
24 +
    try sys::budgetBind(budget, child, context);
25 +
    loop {
26 +
        try sys::contextRun(child, context);
27 +
        if let event = terminal(env, id) { return event; }
28 +
        let info = sys::queryContext(child, context);
29 +
        if info.remaining == 0 { throw abi::Error::Exhausted; }
30 +
        while sys::queryContext(child, context).status == 3 {
31 +
            try sys::wait();
32 +
            if let event = terminal(env, id) { return event; }
33 +
        }
34 +
    }
35 +
}
36 +
37 +
/// Run a fresh image instance with an empty argument range and an 8 KiB Page stack.
38 +
/// The caller retains stack for reuse. Each invocation consumes at most ticks
39 +
/// from reserve. The image remains admitted after the child terminates.
40 +
export fn run(env: *sys::Env, authority: abi::Handle, reserve: abi::Handle,
41 +
    stack: abi::Handle, image: u64, ticks: u64) -> sys::Event throws (abi::Error) {
42 +
    let budget = try sys::budgetSplit(reserve, ticks);
43 +
    let child = try sys::domainCreate(authority, image) catch error {
44 +
        try! sys::capabilityDrop(budget);
45 +
        throw error;
46 +
    };
47 +
    let id = sys::queryDomain(child).id;
48 +
    let event = try execute(env, child, budget, stack) catch error {
49 +
        try! sys::domainDestroy(child, 0);
50 +
        while terminal(env, id) == nil { try! sys::wait(); }
51 +
        try! sys::capabilityDrop(budget);
52 +
        throw error;
53 +
    };
54 +
    try sys::capabilityDrop(budget);
55 +
    return event;
56 +
}
kernel/user/load_check.rad added +91 -0
1 +
//! Machine checks for runtime admission, rejected input, and image lifetime.
2 +
use abi;
3 +
use user::sys;
4 +
use user::launch;
5 +
6 +
/// Admission must reject malformed or unsupported input without publishing it.
7 +
fn rejected(root: abi::Handle, bytes: *[u8]) {
8 +
    let _image = try sys::imageLoad(root, bytes) catch error {
9 +
        assert error == abi::Error::VerifyFailed;
10 +
        return;
11 +
    };
12 +
    sys::abort();
13 +
}
14 +
15 +
/// Locate an encoded native declaration without altering the binary layout.
16 +
fn nativeName(bytes: *[u8]) -> u32 {
17 +
    let name = "user::sys::rawCall";
18 +
    assert bytes.len >= name.len;
19 +
    for i in 0..bytes.len - name.len + 1 {
20 +
        let mut matches = true;
21 +
        for j in 0..name.len {
22 +
            if bytes[i + j] <> name[j] { set matches = false; break; }
23 +
        }
24 +
        if matches { return i + 11; }
25 +
    }
26 +
    sys::abort();
27 +
    return 0;
28 +
}
29 +
30 +
/// Run a fresh instance on another hart and await its terminal report.
31 +
fn remote(env: *sys::Env, root: abi::Handle, reserve: abi::Handle,
32 +
    stack: abi::Handle, image: u64, ticks: u64) {
33 +
    let child = try! sys::domainCreate(root, image);
34 +
    let id = sys::queryDomain(child).id;
35 +
    let _page = try! sys::capabilityGrant(stack, child, (abi::READ | abi::WRITE) as u64);
36 +
    let args = try! sys::pageSlice(stack, 0, 0);
37 +
    try! sys::domainActivate(child, sys::queryPage(stack).base + 8192, args);
38 +
    let context = sys::queryContext(child, 0).id;
39 +
    let budget = try! sys::budgetSplit(reserve, ticks);
40 +
    try! sys::budgetBind(budget, child, context);
41 +
    try! sys::contextRun(child, context);
42 +
    loop {
43 +
        if let event = sys::eventsPop(env) {
44 +
            assert event.kind == 4 and event.code == 42 and event.value == id as u64;
45 +
            break;
46 +
        }
47 +
        try! sys::wait();
48 +
    }
49 +
    try! sys::capabilityDrop(budget);
50 +
}
51 +
52 +
@default fn main(env: *sys::Env) -> u32 {
53 +
    let args = sys::envArgs(env);
54 +
    assert args.len == 48;
55 +
    let words = @sliceOf(args.ptr as *u64, 6);
56 +
    let root = sys::handleSlot(0, abi::Kind::Domain);
57 +
    let reserve = sys::handleSlot(words[0], abi::Kind::Budget);
58 +
    let ticks = words[1] * 5;
59 +
    let input = abi::Handle { bits: words[2] };
60 +
    let bytes = try! sys::pageSliceMut(input, 0, words[3] as u32);
61 +
    assert bytes.len > 4;
62 +
    let stack = try! sys::pageAllocate(root, 2);
63 +
    let name = nativeName(bytes);
64 +
    let original = bytes[name];
65 +
    set bytes[name] = 120;
66 +
    rejected(root, bytes);
67 +
    set bytes[name] = original;
68 +
    rejected(root, &bytes[..bytes.len - 1]);
69 +
    let first = try! sys::imageLoad(root, bytes);
70 +
    let firstRun = try! launch::run(env, root, reserve, stack, first, ticks);
71 +
    assert firstRun.kind == 4 and firstRun.code == 42;
72 +
    let second = try! sys::imageLoad(root, bytes);
73 +
    assert first <> second;
74 +
    // Later compilation and source writes must not change an admitted image.
75 +
    for i in 0..bytes.len { set bytes[i] = 0; }
76 +
    try! sys::capabilityDrop(input);
77 +
    let nextRun = try! launch::run(env, root, reserve, stack, second, ticks);
78 +
    assert nextRun.kind == 4 and nextRun.code == 42;
79 +
    let independent = try! launch::run(env, root, reserve, stack, first, ticks);
80 +
    assert independent.kind == 4 and independent.code == 42;
81 +
    if words[4] <> 0 {
82 +
        remote(env, root, sys::handleSlot(words[4], abi::Kind::Budget), stack, first, ticks);
83 +
    }
84 +
    let device = abi::Handle { bits: words[5] };
85 +
    let message = "LOADING-OK\n";
86 +
    for byte in message {
87 +
        while (try! sys::deviceRead8(device, 5)) & 32 == 0 {}
88 +
        try! sys::deviceWrite8(device, 0, byte);
89 +
    }
90 +
    return 0;
91 +
}
kernel/user/loader.rad added +95 -0
1 +
//! Trusted RIL admission over the reference machine UART.
2 +
//! Wait for RIL-READY, send a little-endian u32 length, wait for RIL-DATA,
3 +
//! then send exactly that many bytes. A zero length exits the root domain.
4 +
//! Programs run sequentially with empty arguments and finite CPU budgets.
5 +
use abi;
6 +
use user::sys;
7 +
use user::launch;
8 +
9 +
/// Maximum binary RIL request size.
10 +
constant MAX_IMAGE: u32 = 16 * 1024 * 1024;
11 +
12 +
/// Select the reference UART from the installed boot Device capabilities.
13 +
fn uart(count: u64) -> abi::Handle {
14 +
    for i in 0..count {
15 +
        let device = sys::handleSlot(i + 2, abi::Kind::Device);
16 +
        let info = sys::queryDevice(device);
17 +
        if info.base == 0x10000000 and info.size >= 8 { return device; }
18 +
    }
19 +
    sys::abort();
20 +
    return sys::domainSelf();
21 +
}
22 +
23 +
/// Write complete text through individually authorized UART accesses.
24 +
fn print(device: abi::Handle, text: *[u8]) {
25 +
    for byte in text {
26 +
        while (try! sys::deviceRead8(device, 5)) & 32 == 0 {}
27 +
        try! sys::deviceWrite8(device, 0, byte);
28 +
    }
29 +
}
30 +
31 +
/// Write a stable eight-digit hexadecimal status or image identifier.
32 +
fn number(device: abi::Handle, value: u32) {
33 +
    let digits = "0123456789abcdef";
34 +
    let mut bytes: [u8; 8] = undefined;
35 +
    for i in 0..8 { set bytes[i] = digits[((value >> ((7 - i) * 4)) & 15)]; }
36 +
    print(device, &bytes[..]);
37 +
}
38 +
39 +
/// Receive one byte, blocking only after draining the UART and its IRQ event.
40 +
fn receive(env: *sys::Env, device: abi::Handle) -> u8 throws (abi::Error) {
41 +
    loop {
42 +
        let status = try sys::deviceRead8(device, 5);
43 +
        if status & 2 <> 0 { throw abi::Error::InvalidArg; }
44 +
        if status & 1 <> 0 { return try sys::deviceRead8(device, 0); }
45 +
        while let _event = sys::eventsPop(env) {}
46 +
        // Recheck after consuming an IRQ so a newly arrived byte cannot be lost.
47 +
        let ready = try sys::deviceRead8(device, 5);
48 +
        if ready & 2 <> 0 { throw abi::Error::InvalidArg; }
49 +
        if ready & 1 == 0 { try sys::wait(); }
50 +
    }
51 +
}
52 +
53 +
/// Read a request length without alignment or host-endian assumptions.
54 +
fn length(env: *sys::Env, device: abi::Handle) -> u32 throws (abi::Error) {
55 +
    let mut size: u32 = 0;
56 +
    for i in 0..4 { set size |= (try receive(env, device)) as u32 << (i * 8); }
57 +
    return size;
58 +
}
59 +
60 +
@default fn main(env: *sys::Env) -> u32 {
61 +
    let args = sys::envArgs(env);
62 +
    assert args.len >= 80 and args.ptr as u64 & 7 == 0;
63 +
    let words = @sliceOf(args.ptr as *u64, args.len / 8);
64 +
    assert words[0] == 1 and words[3] > 0 and words[7] == 1;
65 +
    let root = sys::handleSlot(0, abi::Kind::Domain);
66 +
    let reserve = sys::handleSlot(words[9], abi::Kind::Budget);
67 +
    let ticks = words[6] * 5;
68 +
    let device = uart(words[1]);
69 +
    let input = try! sys::pageAllocate(root, MAX_IMAGE as u64 / 4096);
70 +
    let bytes = try! sys::pageSliceMut(input, 0, MAX_IMAGE);
71 +
    let stack = try! sys::pageAllocate(root, 2);
72 +
    try! sys::deviceWrite8(device, 2, 7);
73 +
    try! sys::deviceWrite8(device, 1, 1);
74 +
    loop {
75 +
        print(device, "RIL-READY\n");
76 +
        let size = try length(env, device) catch { print(device, "RIL-TRANSPORT-ERROR\n"); return 1; };
77 +
        if size == 0 { return 0; }
78 +
        if size > MAX_IMAGE { print(device, "RIL-SIZE-ERROR\n"); return 1; }
79 +
        print(device, "RIL-DATA\n");
80 +
        for i in 0..size {
81 +
            set bytes[i] = try receive(env, device) catch { print(device, "RIL-TRANSPORT-ERROR\n"); return 1; };
82 +
        }
83 +
        let image = try sys::imageLoad(root, &bytes[..size]) catch error {
84 +
            print(device, "RIL-ERROR "); number(device, error as u32); print(device, "\n");
85 +
            continue;
86 +
        };
87 +
        print(device, "RIL-IMAGE "); number(device, image as u32); print(device, "\n");
88 +
        let event = try launch::run(env, root, reserve, stack, image, ticks) catch error {
89 +
            print(device, "RIL-RUN-ERROR "); number(device, error as u32); print(device, "\n");
90 +
            continue;
91 +
        };
92 +
        print(device, "RIL-TERMINAL "); number(device, event.kind as u32);
93 +
        print(device, " "); number(device, event.code); print(device, "\n");
94 +
    }
95 +
}
kernel/user/runtime.rad added +26 -0
1 +
//! Runtime image state, repeated function pointers, and native syscall bindings.
2 +
use user::sys;
3 +
4 +
/// Per-domain counter copied from the immutable image initializer.
5 +
static COUNT: u32 = 41;
6 +
7 +
/// Update the counter and read it through its instance-relative address.
8 +
fn increment(value: *u32) -> u32 {
9 +
    set COUNT += 1;
10 +
    return *value;
11 +
}
12 +
13 +
/// Repeated native function pointers in private writable state.
14 +
static LOCAL: [fn(*u32) -> u32; 2] = [increment; 2];
15 +
/// Repeated native function pointers in immutable shared data.
16 +
constant SHARED: [fn(*u32) -> u32; 2] = [increment; 2];
17 +
18 +
@default fn main(env: *sys::Env) -> u32 {
19 +
    let result = LOCAL[0](&COUNT);
20 +
    assert SHARED[1](&COUNT) == 43;
21 +
    try! sys::timeout(1, 0x52494c);
22 +
    try! sys::wait();
23 +
    let event = sys::eventsPop(env) else { sys::abort(); return 1; };
24 +
    assert event.kind == 2 and event.code == 0x52494c;
25 +
    return result;
26 +
}
kernel/user/sys.rad +13 -3
1 1
//! Capability-checked user calls and views of kernel-authorized memory.
2 -
//! The image catalog links the native primitives in user/sys.ras.
2 +
//! Native primitives in user/sys.ras are the runtime image binding boundary.
3 3
4 4
use abi;
5 5
6 6
/// Startup ABI: exactly the ten u64 fields of core::domains::Env, in order.
7 7
/// Each entry receives its own immutable Env pointer in a0. Retain that pointer
115 115
/// Drop only this local capability, not another domain's copy or its object.
116 116
export fn capabilityDrop(handle: abi::Handle) throws (abi::Error) {
117 117
    try check(invoke(12, handle.bits, 0, 0, 0).error);
118 118
}
119 119
120 -
/// Create a pending domain using explicit Create authority and a catalog index.
121 -
/// image is a u64 image-catalog index, not a handle or a source-code address.
120 +
/// Create a pending domain using explicit Create authority and an admitted ID.
121 +
/// image is a stable u64 image ID, not a handle or a source-code address.
122 122
export fn domainCreate(authority: abi::Handle, image: u64) -> abi::Handle throws (abi::Error) {
123 123
    let reply = invoke(20, authority.bits, image, 0, 0);
124 124
    try check(reply.error);
125 125
    return abi::Handle { bits: reply.values[0] };
126 126
}
127 127
128 +
/// Admit trusted binary RIL using caller-self Create and Allocate authority.
129 +
/// Every byte must have live readable Page coverage. The kernel snapshots and
130 +
/// compiles the input; the returned stable image ID outlives all domain instances.
131 +
/// This admission does not provide a safety or provenance verification proof.
132 +
export fn imageLoad(authority: abi::Handle, ril: *[u8]) -> u64 throws (abi::Error) {
133 +
    let reply = invoke(77, authority.bits, ril.ptr as u64, ril.len as u64, 0);
134 +
    try check(reply.error);
135 +
    return reply.values[0];
136 +
}
137 +
128 138
/// Activate at the admitted entry; args is the exact readable byte range.
129 139
/// The kernel checks Page authority and stack ownership. Pass args.ptr, NEVER
130 140
/// the address of its fat-slice descriptor. Subslice first to pass fewer bytes.
131 141
export fn domainActivate(domain: abi::Handle, stackTop: u64, args: *[u8]) throws (abi::Error) {
132 142
    try check(invoke(21, domain.bits, stackTop, args.ptr as u64, args.len as u64).error);
lib/std/lang/il.rad +1 -0
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
export mod binary;
63 +
export mod images;
63 64
64 65
use std::mem;
65 66
use std::lang::alloc;
66 67
67 68
/// Source location for debug info.
lib/std/lang/il/images.rad added +202 -0
1 +
//! Freestanding compilation and placement of trusted binary RIL images.
2 +
//!
3 +
//! Structural and backend-capacity checks do not establish memory safety,
4 +
//! pointer provenance, or authority confinement of generated native code.
5 +
6 +
export mod graph;
7 +
export mod native;
8 +
mod layout;
9 +
10 +
use std::mem;
11 +
use std::collections::dict;
12 +
use std::lang::alloc;
13 +
use std::lang::strings;
14 +
use std::lang::il;
15 +
use std::arch::rv64;
16 +
use std::arch::rv64::emit;
17 +
18 +
/// One explicitly permitted native function in the trusted execution environment.
19 +
export record Binding: Copy {
20 +
    /// Exact qualified external declaration name.
21 +
    name: *[u8],
22 +
    /// Callable native address using the image's register and stack ABI.
23 +
    address: u64,
24 +
}
25 +
26 +
/// Immutable native image entry and separately instantiated private state.
27 +
export record Descriptor: Copy {
28 +
    /// Native entry receiving the domain Env pointer in a0.
29 +
    entry: u64,
30 +
    /// Read-only bytes copied into each domain's private allocation.
31 +
    initial: u64,
32 +
    /// Number of private state bytes required by one domain.
33 +
    stateSize: u32,
34 +
    /// Power-of-two alignment required by the private allocation.
35 +
    stateAlignment: u32,
36 +
    /// Repair private pointers after copying the initializer to its new base.
37 +
    relocate: fn(u64),
38 +
}
39 +
40 +
/// Prepared native output borrowing caller-owned compiler storage until finish.
41 +
/// Its metadata must not be mutated by the caller; only size and alignment are
42 +
/// needed to obtain a destination allocation. No compiler storage escapes finish.
43 +
export record Prepared: Copy {
44 +
    /// Minimum destination capacity in bytes.
45 +
    size: u32,
46 +
    /// Required power-of-two destination base alignment.
47 +
    alignment: u32,
48 +
    /// Final position-independent native instruction words.
49 +
    code: *[u32],
50 +
    /// Immutable initializer bytes containing allocation-relative pointers.
51 +
    data: *[u8],
52 +
    /// Flattened initializer declarations used to identify pointer slots.
53 +
    items: *[il::Data],
54 +
    /// Allocation-relative byte offset of native code.
55 +
    codeOffset: u32,
56 +
    /// Allocation-relative byte offset of the trusted entry wrapper.
57 +
    entryOffset: u32,
58 +
    /// Allocation-relative byte offset of the private initializer.
59 +
    initialOffset: u32,
60 +
    /// Allocation-relative byte offset of the private pointer repair helper.
61 +
    relocateOffset: u32,
62 +
    /// Number of separately allocated private state bytes.
63 +
    stateSize: u32,
64 +
    /// Alignment required by separately allocated private state.
65 +
    stateAlignment: u32,
66 +
    /// Immutable source range excluded from destination placement.
67 +
    source: *[u8],
68 +
    /// Persistent compiler arena range excluded from destination placement.
69 +
    arena: *[u8],
70 +
    /// Temporary compiler arena range excluded from destination placement.
71 +
    scratch: *[u8],
72 +
    /// Caller-owned interning pool range excluded from destination placement.
73 +
    pool: *[u8],
74 +
}
75 +
76 +
/// Reject wrapping memory ranges before overlap or placement arithmetic.
77 +
fn range(bytes: *[u8]) throws (il::binary::Error) {
78 +
    let base = bytes.ptr as u64;
79 +
    if bytes.len as u64 > 0xFFFFFFFFFFFFFFFF - base {
80 +
        throw il::binary::error(0, "image memory range wraps address space");
81 +
    }
82 +
}
83 +
84 +
/// Detect overlap between already checked byte ranges without pointer ordering.
85 +
fn overlap(a: *[u8], b: *[u8]) -> bool {
86 +
    if a.len == 0 or b.len == 0 { return false; }
87 +
    let first = a.ptr as u64;
88 +
    let second = b.ptr as u64;
89 +
    return first < second + b.len as u64 and second < first + a.len as u64;
90 +
}
91 +
92 +
/// Require aligned, disjoint compiler storage and a caller-initialized empty pool.
93 +
fn storage(source: *[u8], arena: *alloc::Arena, scratch: *alloc::Arena, pool: *strings::Pool) throws (il::binary::Error) {
94 +
    try range(source); try range(arena.data); try range(scratch.data);
95 +
    let poolBytes = @sliceOf(pool as *opaque as *u8, @sizeOf(strings::Pool));
96 +
    try range(poolBytes);
97 +
    if arena.offset > arena.data.len or scratch.offset > scratch.data.len
98 +
        or arena.data.len > 0x7FFFFFFF or scratch.data.len > 0x7FFFFFFF
99 +
        or arena.data.ptr as u64 % 8 <> 0 or scratch.data.ptr as u64 % 8 <> 0 {
100 +
        throw il::binary::error(0, "invalid image compiler arena");
101 +
    }
102 +
    if overlap(arena.data, scratch.data) or overlap(source, arena.data) or overlap(source, scratch.data)
103 +
        or overlap(poolBytes, arena.data) or overlap(poolBytes, scratch.data) or overlap(poolBytes, source) {
104 +
        throw il::binary::error(0, "image compiler storage overlaps");
105 +
    }
106 +
    if pool.count <> 0 { throw il::binary::error(0, "image compiler requires an empty string pool"); }
107 +
    for i in 0..pool.table.len {
108 +
        if pool.table[i].len <> 0 { throw il::binary::error(0, "image compiler requires an empty string pool"); }
109 +
    }
110 +
}
111 +
112 +
/// Decode and compile a closed trusted image using the shared RV64 backend.
113 +
/// Source stays immutable through finish. Arena, scratch, and empty pool must
114 +
/// be disjoint and live through finish; arenas require eight-byte aligned bases.
115 +
/// The emitter reserves an eight-MiB code buffer plus fixed symbol/relocation
116 +
/// tables in arena. Remaining arena use depends on names and initialized data;
117 +
/// scratch holds the decoded graph and one function's register allocation at a
118 +
/// time. Every exhaustion is reported as a binary Error. Failed preparation may
119 +
/// consume both arenas and pool; reclaim them together before the next attempt.
120 +
export fn prepare(source: *[u8], bindings: *[Binding], arena: *mut alloc::Arena,
121 +
    scratch: *mut alloc::Arena, pool: *mut strings::Pool) -> Prepared throws (il::binary::Error) {
122 +
    if source.len > 16 * 1024 * 1024 { throw il::binary::error(0, "binary image exceeds capacity"); }
123 +
    try storage(source, arena, scratch, pool);
124 +
    if bindings.len > 4096 { throw il::binary::error(0, "too many native image bindings"); }
125 +
    let mut bindingIndex = try il::binary::dictionary(scratch, bindings.len, 0);
126 +
    for binding, i in bindings {
127 +
        if binding.name.len <= 11 or not mem::eq(&binding.name[..11], "user::sys::")
128 +
            or binding.address == 0 or binding.address % 4 <> 0 {
129 +
            throw il::binary::error(0, "invalid native image binding");
130 +
        }
131 +
        if dict::get(&bindingIndex, binding.name) <> nil { throw il::binary::error(0, "duplicate native image binding"); }
132 +
        dict::insert(&mut bindingIndex, binding.name, i as i32);
133 +
    }
134 +
    let image = try il::binary::decode(source, scratch, pool);
135 +
    let plan = try graph::plan(&image, scratch);
136 +
    let names = try graph::names(&image.program, 0, pool, arena);
137 +
    let stateName = try graph::name("images::state_", 0, "", pool, arena);
138 +
    let initial = try native::initializer(&plan, &names, stateName, arena, scratch);
139 +
    let emitter = try emit::emitter(arena, false) catch { throw il::binary::error(0, "native image emitter arena exhausted"); };
140 +
    let mut generator = rv64::Generator { e: emitter, entryPatch: rv64::EntryPatch::None };
141 +
    for function, i in image.program.fns {
142 +
        if not function.isExtern { continue; }
143 +
        if function.params.len > rv64::ARG_REGS.len { throw il::binary::error(0, "image native declaration has too many arguments"); }
144 +
        let binding = dict::get(&bindingIndex, function.name) else {
145 +
            throw il::binary::error(0, "unresolved image sys native declaration");
146 +
        };
147 +
        try native::trampoline(&mut generator, names.functions[i], bindings[binding as u32].address);
148 +
    }
149 +
    let mut offsets = try il::binary::dictionary(scratch, plan.order.len, 0);
150 +
    for item, i in plan.items {
151 +
        if not item.readOnly { dict::insert(&mut offsets, names.data[i], plan.offsets[i] as i32); }
152 +
    }
153 +
    set generator.e.instanceData = &offsets;
154 +
    for function, i in image.program.fns {
155 +
        if function.isExtern { continue; }
156 +
        let renamed = try graph::function(function, i, &plan, &names, scratch);
157 +
        try native::function(&mut generator, &renamed, scratch);
158 +
    }
159 +
    set generator.e.instanceData = nil;
160 +
    try native::helpers(&mut generator, 0, &plan, &names, &initial, pool, arena);
161 +
    let placed = try layout::prepare(&mut generator, initial.items, arena, scratch);
162 +
    let entryName = try graph::name("images::native_", 0, "", pool, arena);
163 +
    let relocateName = try graph::name("images::relocate_", 0, "", pool, arena);
164 +
    let entryOffset = placed.codeOffset + (try layout::functionOffset(&generator.e, entryName));
165 +
    let relocateOffset = placed.codeOffset + (try layout::functionOffset(&generator.e, relocateName));
166 +
    let initialOffset = dict::get(&placed.symbols.dict, stateName) else {
167 +
        throw il::binary::error(0, "unresolved native image initializer");
168 +
    };
169 +
    return Prepared { size: placed.size, alignment: placed.alignment, code: emit::getCode(&generator.e),
170 +
        data: placed.bytes, items: initial.items, codeOffset: placed.codeOffset, entryOffset,
171 +
        initialOffset: initialOffset as u32, relocateOffset, stateSize: plan.size, stateAlignment: plan.alignment,
172 +
        source, arena: arena.data, scratch: scratch.data, pool: @sliceOf(pool as *opaque as *u8, @sizeOf(strings::Pool)) };
173 +
}
174 +
175 +
/// Copy prepared native bytes and relocate persistent pointers at destination.
176 +
/// Destination must be aligned, large enough, and disjoint from compiler storage
177 +
/// and source. No allocation or compilation occurs here. The returned descriptor
178 +
/// borrows only destination, which must remain immutable for its execution life.
179 +
/// The caller must synchronize instruction fetch before any hart enters the code.
180 +
export fn finish(prepared: *mut Prepared, destination: *mut [u8]) -> Descriptor throws (il::binary::Error) {
181 +
    try range(destination);
182 +
    let base = destination.ptr as u64;
183 +
    if base == 0 or prepared.alignment == 0 or prepared.alignment & (prepared.alignment - 1) <> 0
184 +
        or base % prepared.alignment as u64 <> 0 or destination.len < prepared.size {
185 +
        throw il::binary::error(0, "invalid native image destination");
186 +
    }
187 +
    let output = &mut destination[..prepared.size];
188 +
    if overlap(output, prepared.source) or overlap(output, prepared.arena)
189 +
        or overlap(output, prepared.scratch) or overlap(output, prepared.pool) {
190 +
        throw il::binary::error(0, "native image destination overlaps compiler storage");
191 +
    }
192 +
    try! mem::copy(output, prepared.data);
193 +
    for i in prepared.data.len..prepared.codeOffset { set output[i] = 0; }
194 +
    let code = @sliceOf(prepared.code.ptr as *u8, prepared.code.len * rv64::INSTR_SIZE as u32);
195 +
    try! mem::copy(&mut output[prepared.codeOffset..], code);
196 +
    layout::rebase(prepared.items, output, base);
197 +
    let relocateAddress = base + prepared.relocateOffset as u64;
198 +
    let relocate = *(&relocateAddress as *opaque as *fn(u64));
199 +
    return Descriptor { entry: base + prepared.entryOffset as u64, initial: base + prepared.initialOffset as u64,
200 +
        stateSize: prepared.stateSize, stateAlignment: prepared.stateAlignment,
201 +
        relocate };
202 +
}
compiler/radiance/images/graph.rad → lib/std/lang/il/images/graph.rad renamed +0 -0
lib/std/lang/il/images/layout.rad added +161 -0
1 +
//! Position-independent native layout and persistent pointer initialization.
2 +
3 +
use std::mem;
4 +
use std::collections::dict;
5 +
use std::lang::alloc;
6 +
use std::lang::il;
7 +
use std::lang::gen::data;
8 +
use std::arch::rv64;
9 +
use std::arch::rv64::emit;
10 +
use std::arch::rv64::encode;
11 +
use super::graph;
12 +
13 +
/// Native bytes and offsets, with data addresses relative to the allocation.
14 +
export record Layout: Copy {
15 +
    /// Fully initialized read-only bytes with allocation-relative pointers.
16 +
    bytes: *[u8],
17 +
    /// Allocation-relative immutable symbol addresses.
18 +
    symbols: data::DataSymMap,
19 +
    /// Byte offset of the first native instruction.
20 +
    codeOffset: u32,
21 +
    /// Complete allocation size including native instructions.
22 +
    size: u32,
23 +
    /// Required power-of-two allocation alignment.
24 +
    alignment: u32,
25 +
}
26 +
27 +
/// Resolve a function before invoking backend relocation routines.
28 +
export fn functionOffset(e: *emit::Emitter, name: *[u8]) -> u32 throws (il::binary::Error) {
29 +
    let offset = dict::get(&e.labels.funcs, name) else {
30 +
        throw il::binary::error(0, "unresolved native image function");
31 +
    };
32 +
    if offset < 0 or offset as u32 % 4 <> 0 or offset as u32 / 4 >= e.codeLen {
33 +
        throw il::binary::error(0, "native image function offset exceeds code");
34 +
    }
35 +
    return offset as u32;
36 +
}
37 +
38 +
/// Require every reserved relocation instruction to belong to emitted code.
39 +
fn slots(e: *emit::Emitter, index: u32, count: u32) throws (il::binary::Error) {
40 +
    if index > e.codeLen or count > e.codeLen - index {
41 +
        throw il::binary::error(0, "native image relocation exceeds code");
42 +
    }
43 +
}
44 +
45 +
/// Check all references before the shared emitter's infallible final patching.
46 +
fn references(e: *emit::Emitter, items: *[il::Data], symbols: *data::DataSymMap) throws (il::binary::Error) {
47 +
    if e.pendingBranches.len <> 0 { throw il::binary::error(0, "unresolved native image block branch"); }
48 +
    for pending in e.pendingCalls {
49 +
        try slots(e, pending.index, 2);
50 +
        let _ = try functionOffset(e, pending.target);
51 +
    }
52 +
    for pending in e.pendingJumps {
53 +
        try slots(e, pending.index, 1);
54 +
        let offset = try functionOffset(e, pending.target);
55 +
        if not encode::isJumpImm(offset as i32 - pending.index as i32 * rv64::INSTR_SIZE) {
56 +
            throw il::binary::error(0, "native image jump exceeds reach");
57 +
        }
58 +
    }
59 +
    for pending in e.pendingAddrLoads {
60 +
        try slots(e, pending.index, 2);
61 +
        if pending.isData {
62 +
            if data::lookupAddr(symbols, pending.target) == nil {
63 +
                throw il::binary::error(0, "unresolved native image data address");
64 +
            }
65 +
        } else { let _ = try functionOffset(e, pending.target); }
66 +
    }
67 +
    for item in items {
68 +
        for value in item.values {
69 +
            if value.count == 0 { continue; }
70 +
            match value.item {
71 +
                case il::DataItem::Sym(name) => {
72 +
                    if data::lookupAddr(symbols, name) == nil {
73 +
                        throw il::binary::error(0, "unresolved native image data initializer");
74 +
                    }
75 +
                }
76 +
                case il::DataItem::Fn(name) => { let _ = try functionOffset(e, name); }
77 +
                else => {},
78 +
            }
79 +
        }
80 +
    }
81 +
}
82 +
83 +
/// Lay out flattened immutable data using the shared data placement and emitter.
84 +
/// Code and data address instructions use allocation-local PC-relative offsets.
85 +
export fn prepare(generator: *mut rv64::Generator, items: *[il::Data], arena: *mut alloc::Arena,
86 +
    scratch: *mut alloc::Arena) -> Layout throws (il::binary::Error) {
87 +
    let mut alignment: u32 = 8;
88 +
    let mut extent: u64 = 0;
89 +
    for item in items {
90 +
        let a = item.alignment as u64;
91 +
        if not item.readOnly or item.isZeroInit or a == 0 or a & (a - 1) <> 0 or a > graph::MAX_DATA as u64 {
92 +
            throw il::binary::error(0, "invalid flattened native image data");
93 +
        }
94 +
        if item.alignment > alignment { set alignment = item.alignment; }
95 +
        set extent = ((extent + a - 1) & ~(a - 1)) + item.size as u64;
96 +
        if extent > graph::MAX_DATA as u64 { throw il::binary::error(0, "native image data exceeds capacity"); }
97 +
        let mut size: u64 = 0;
98 +
        for value in item.values {
99 +
            set size += graph::width(value.item) as u64 * value.count as u64;
100 +
            if size > item.size as u64 { throw il::binary::error(0, "native image initializer exceeds data"); }
101 +
        }
102 +
        if size <> item.size as u64 { throw il::binary::error(0, "native image initializer size mismatch"); }
103 +
    }
104 +
    let syms = try graph::storage(scratch, @sizeOf(data::DataSym), @alignOf(data::DataSym), items.len) as *mut [data::DataSym];
105 +
    let mut count: u32 = 0;
106 +
    let dataSize = data::layoutSection(items, syms, &mut count, 0, true);
107 +
    let mut map = try il::binary::dictionary(scratch, count, 0);
108 +
    for symbol in syms {
109 +
        if dict::get(&map, symbol.name) <> nil { throw il::binary::error(0, "duplicate native image data symbol"); }
110 +
        dict::insert(&mut map, symbol.name, symbol.addr as i32);
111 +
    }
112 +
    let symbols = data::DataSymMap { dict: map, syms };
113 +
    let e = &mut generator.e;
114 +
    let codeOffset = mem::alignUp(dataSize, 8);
115 +
    let total = codeOffset as u64 + e.codeLen as u64 * rv64::INSTR_SIZE as u64;
116 +
    // Keep every allocation-local AUIPC displacement strictly inside signed reach.
117 +
    if total > 0x7FFFF000 { throw il::binary::error(0, "native image exceeds relative address reach"); }
118 +
    try references(e, items, &symbols);
119 +
    let bytes = try graph::storage(arena, 1, 1, dataSize) as *mut [u8];
120 +
    for i in 0..bytes.len { set bytes[i] = 0; }
121 +
    emit::patchCalls(e);
122 +
    emit::patchJumps(e);
123 +
    for pending in e.pendingAddrLoads {
124 +
        let mut target: u32 = 0;
125 +
        if pending.isData {
126 +
            let address = data::lookupAddr(&symbols, pending.target) else {
127 +
                throw il::binary::error(0, "unresolved native image data address");
128 +
            };
129 +
            set target = address;
130 +
        } else { set target = codeOffset + (try functionOffset(e, pending.target)); }
131 +
        let pc = codeOffset + pending.index * rv64::INSTR_SIZE as u32;
132 +
        let split = emit::splitImm(target as i32 - pc as i32);
133 +
        emit::patch(e, pending.index, encode::auipc(pending.rd, split.hi));
134 +
        emit::patch(e, pending.index + 1, encode::addi(pending.rd, pending.rd, split.lo));
135 +
    }
136 +
    data::emitSection(items, &symbols, &e.labels, codeOffset, bytes, true);
137 +
    return Layout { bytes, symbols, codeOffset, size: total as u32, alignment };
138 +
}
139 +
140 +
/// Convert allocation-relative initializer pointers to actual native addresses.
141 +
/// Scalar private offsets remain unchanged for the per-instance relocation helper.
142 +
export fn rebase(items: *[il::Data], destination: *mut [u8], base: u64) {
143 +
    let mut offset: u32 = 0;
144 +
    for item in items {
145 +
        set offset = mem::alignUp(offset, item.alignment);
146 +
        for value in item.values {
147 +
            match value.item {
148 +
                case il::DataItem::Sym(_), il::DataItem::Fn(_) => {
149 +
                    for _ in 0..value.count {
150 +
                        let mut relative: u64 = 0;
151 +
                        for byte in 0..8 { set relative |= (destination[offset + byte] as u64) << (byte as u64 * 8); }
152 +
                        let address = base + relative;
153 +
                        for byte in 0..8 { set destination[offset + byte] = (address >> (byte as u64 * 8)) as u8; }
154 +
                        set offset += 8;
155 +
                    }
156 +
                }
157 +
                else => set offset += graph::width(value.item) * value.count,
158 +
            }
159 +
        }
160 +
    }
161 +
}
compiler/radiance/images/native.rad → lib/std/lang/il/images/native.rad renamed +23 -4
41 41
        }
42 42
        case il::DataItem::Fn(symbol) => set item = il::DataItem::Fn(names.functions[try graph::index(&plan.fnIndex, symbol)]),
43 43
        case il::DataItem::Str(bytes) => set item = il::DataItem::Str(try graph::copy(arena, bytes)),
44 44
        else => {},
45 45
    }
46 -
    return il::DataValue { item, count: value.count };
46 +
    return il::DataValue { item, count: 0 if graph::width(item) == 0 else value.count };
47 47
}
48 48
49 49
/// Flatten private declarations into one RO initializer using their native layout.
50 50
export fn initializer(plan: *graph::Plan, names: *graph::Names, stateName: *[u8], arena: *mut alloc::Arena, scratch: *mut alloc::Arena) -> Initializer throws (il::binary::Error) {
51 51
    let mut shared: u32 = 0;
129 129
    for item in items { slice.append(item, allocator); }
130 130
    set *target = slice;
131 131
}
132 132
133 133
/// Check hard backend limits and a conservative expansion bound for one function.
134 -
fn bounds(e: *emit::Emitter, function: *il::Fn) throws (il::binary::Error) {
134 +
fn bounds(e: *emit::Emitter, function: *il::Fn) -> u32 throws (il::binary::Error) {
135 135
    if function.params.len > rv64::ARG_REGS.len { throw il::binary::error(0, "image function has too many arguments"); }
136 136
    let mut blocks = function.blocks.len as u64 + 1;
137 137
    let mut words: u64 = 256;
138 138
    let mut reserve: u64 = 0;
139 139
    let mut operands: u64 = 0;
144 144
        for item in block.instrs {
145 145
            set words += 64;
146 146
            set operands += 5;
147 147
            set branches += 2;
148 148
            match item {
149 -
                case il::Instr::Call { args, .. } => {
149 +
                case il::Instr::Call { func, args, .. } => {
150 +
                    match func {
151 +
                        case il::Val::FnAddr(_), il::Val::Reg(_) => {},
152 +
                        else => throw il::binary::error(0, "invalid image native call target"),
153 +
                    }
150 154
                    if args.len > rv64::ARG_REGS.len { throw il::binary::error(0, "image call has too many arguments"); }
151 155
                    set words += args.len as u64 * 32;
152 156
                    set operands += args.len as u64;
153 157
                    set calls += 1;
154 158
                }
210 214
    if e.funcs.len >= e.funcs.cap or operands + e.pendingAddrLoads.len as u64 > e.pendingAddrLoads.cap as u64
211 215
        or calls + e.pendingCalls.len as u64 > e.pendingCalls.cap as u64
212 216
        or branches + e.pendingBranches.len as u64 > e.pendingBranches.cap as u64 {
213 217
        throw il::binary::error(0, "native image relocation capacity exceeded");
214 218
    }
219 +
    return reserve as u32;
215 220
}
216 221
217 222
/// Shared liveness state used to bound the spill-candidate buffer.
218 223
record Pressure: Copy {
219 224
    /// Registers live at the inspected instruction.
265 270
    return regalloc::AllocResult { assignments: assignment.assignments, spill, usedCalleeSaved: assignment.usedCalleeSaved };
266 271
}
267 272
268 273
/// Emit through the existing allocator and selector, propagating scratch exhaustion.
269 274
export fn function(generator: *mut rv64::Generator, function: *il::Fn, scratch: *mut alloc::Arena) throws (il::binary::Error) {
270 -
    try bounds(&generator.e, function);
275 +
    let reserve = try bounds(&generator.e, function);
271 276
    let saved = alloc::save(scratch);
272 277
    let allocation = try allocate(function, scratch) catch error {
273 278
        alloc::restore(scratch, saved);
274 279
        throw error;
275 280
    };
281 +
    let frame = emit::computeFrame(allocation.spill.frameSize + reserve as i32, allocation.usedCalleeSaved,
282 +
        function.blocks.len, function.isLeaf, false);
283 +
    if frame.totalSize > 64 * 1024 {
284 +
        alloc::restore(scratch, saved);
285 +
        throw il::binary::error(0, "image function frame exceeds native stack");
286 +
    }
276 287
    rv64::isel::selectFn(&mut generator.e, &allocation, function);
277 288
    alloc::restore(scratch, saved);
278 289
}
279 290
280 291
/// Reserve a trusted leaf helper in the same global symbol and relocation tables.
288 299
    }
289 300
    emit::recordFunc(e, name);
290 301
    emit::recordFuncOffset(e, name);
291 302
}
292 303
304 +
/// Tail-call one explicitly bound primitive without limiting its physical address.
305 +
export fn trampoline(generator: *mut rv64::Generator, name: *[u8], address: u64) throws (il::binary::Error) {
306 +
    let e = &mut generator.e;
307 +
    try helper(e, name, 9);
308 +
    emit::loadImm(e, rv64::SCRATCH1, address as i64);
309 +
    emit::emit(e, encode::jalr(rv64::ZERO, rv64::SCRATCH1, 0));
310 +
}
311 +
293 312
/// Build trusted startup, address getters, and private-pointer relocation routines.
294 313
export fn helpers(generator: *mut rv64::Generator, id: u32, plan: *graph::Plan, names: *graph::Names,
295 314
    initial: *Initializer, pool: *mut strings::Pool, arena: *mut alloc::Arena) throws (il::binary::Error) {
296 315
    let stateName = initial.items[0].name;
297 316
    let entry = try graph::name("images::native_", id, "", pool, arena);
std.lib +4 -0
33 33
lib/std/lang/il/printer.rad
34 34
lib/std/lang/il/binary.rad
35 35
lib/std/lang/il/binary/instructions.rad
36 36
lib/std/lang/il/binary/records.rad
37 37
lib/std/lang/il/binary/structure.rad
38 +
lib/std/lang/il/images.rad
39 +
lib/std/lang/il/images/graph.rad
40 +
lib/std/lang/il/images/native.rad
41 +
lib/std/lang/il/images/layout.rad
38 42
lib/std/lang/resolver.rad
39 43
lib/std/lang/resolver/printer.rad
40 44
lib/std/lang/lower.rad
41 45
lib/std/lang/module.rad
42 46
lib/std/lang/module/printer.rad