kernel: Dispatch contexts and enforce timers

03e8c2d1f892e10e886e35649fcb0a4b95a3cdfe5fed136c563aa7dab8fee713
Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent 35dcc69b
Makefile +7 -2
21 21
22 22
# Verify the emulator binary exists.
23 23
EMU_PATH := $(shell command -v $(EMU) 2>/dev/null)
24 24
25 25
default: emulator $(RAD_BIN)
26 -
test: emulator seed-test std-test bin-test kernel-test package-test native-test shared-test sync-test kernel-boot-test trap-test page-test loader-test
26 +
test: emulator seed-test std-test bin-test kernel-test package-test native-test shared-test sync-test kernel-boot-test trap-test page-test loader-test dispatch-test
27 27
28 28
seed-test:
29 29
	@seed/test
30 30
31 31
# Emulator command check
66 66
		lib/std.test.rv64.s \
67 67
		lib/std.test.rv64.o
68 68
69 69
# Kernel modules and tests use a separate package and test entry point.
70 70
KERNEL_SRC := kernel/kernel.rad $(shell find kernel/kernel -name '*.rad' ! -name 'tests.rad' ! -path '*/tests/*' 2>/dev/null)
71 -
KERNEL_ASM := kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras
71 +
KERNEL_ASM := kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras kernel/kernel/dispatch.ras
72 72
KERNEL_TEST_SRC := kernel/kernel/tests.rad $(shell find kernel/kernel/tests -name '*.rad' 2>/dev/null)
73 73
KERNEL := -pkg kernel $(patsubst %,-mod %,$(sort $(KERNEL_SRC)))
74 74
KERNEL_TEST := $(BIN_DIR)/kernel.test.rv64
75 75
76 76
kernel-test: emulator $(KERNEL_TEST)
201 201
202 202
# Runtime package compilation and execution through the production kernel.
203 203
.PHONY: loader-test
204 204
loader-test: $(RAD_BIN) $(BIN_DIR)/kernel.build.rv64
205 205
	@RAD_EMULATOR="$(EMU)" sh test/loader/run
206 +
207 +
# Native preemption across user code, idle, and retained machine work.
208 +
.PHONY: dispatch-test
209 +
dispatch-test: $(RAD_BIN) $(BIN_DIR)/kernel.build.rv64
210 +
	@RAD_EMULATOR="$(EMU)" sh test/dispatch/run
kernel/kernel.rad +1 -0
19 19
export mod pages;
20 20
export mod loader;
21 21
export mod instances;
22 22
export mod domains;
23 23
export mod budgets;
24 +
export mod dispatch;
24 25
export mod boot;
25 26
@test export mod tests;
kernel/kernel/boot.rad +10 -1
8 8
use super::trap;
9 9
use super::pages;
10 10
use super::registry;
11 11
use super::domains;
12 12
use super::budgets;
13 +
use super::dispatch;
13 14
use std::arch::rv64::shared::catalog;
14 15
15 16
/// Platform data published by hart zero before secondary initialization.
16 17
export unsafe static PLATFORM: platform::Platform = undefined;
17 18
/// Release/acquire publication flag for PLATFORM.
19 20
/// Number of harts that validated their machine stack.
20 21
static ARRIVED: u64 = 0;
21 22
/// Trap anchors owned by their physical harts.
22 23
unsafe static HARTS: [trap::Hart; limits::HARTS] = undefined;
23 24
24 -
/// Stop on a trap before a domain or interrupt dispatcher is installed.
25 +
/// Handle timer boundaries and stop on unsupported traps.
25 26
unsafe fn unexpected(frame: &mut trap::Frame, hart: &mut trap::Hart) {
27 +
    if trap::classify(frame.cause) == trap::Cause::Timer {
28 +
        dispatch::interrupt(frame, hart);
29 +
        return;
30 +
    }
26 31
    print("kernel: unexpected trap\n");
27 32
    trap::halt();
28 33
}
29 34
30 35
/// Read a byte from a device register with I/O ordering.
89 94
    set HARTS[hart as u32] = trap::Hart {
90 95
        stackTop: stack.end, stackBottom: stack.start, kernelGp: stateTable as u64, handler: unexpected,
91 96
        savedT0: 0, savedT1: 0, savedSp: 0,
92 97
    };
93 98
    trap::install(&mut HARTS[hart as u32]);
99 +
    try! dispatch::initialize(&PLATFORM, hart as u32, stateTable as u64);
94 100
    let mut count: u64 = 0;
95 101
    for id in 0..limits::HARTS { if (PLATFORM.harts & (1 << id)) <> 0 { set count += 1; } }
96 102
    return sync::fetchAdd(&mut ARRIVED, 1) + 1 == count;
97 103
}
104 +
105 +
/// Enter dispatch after this hart completes platform initialization.
106 +
export unsafe fn run() { dispatch::start(&mut HARTS[dispatch::hart()]); }
kernel/kernel/boot.ras +2 -3
5 5
.export @kernel::boot::read8;
6 6
.export @kernel::boot::write8;
7 7
8 8
@kernel::boot::start
9 9
    call @kernel::boot::initialize;
10 -
@idle
11 -
    wfi;
12 -
    j @idle;
10 +
    call @kernel::boot::run;
11 +
    ebreak;
13 12
14 13
// Initialize machine CSRs and preserve the firmware stack across Radiance entry.
15 14
@kernel::boot::initialize
16 15
    csrw mie %zero;
17 16
    csrw mstatus %zero;
kernel/kernel/dispatch.rad added +179 -0
1 +
//! Hart-local selection of runnable CPU authority and timer boundaries.
2 +
3 +
use super::abi;
4 +
use super::limits;
5 +
use super::slots;
6 +
use super::budgets;
7 +
use super::domains;
8 +
use super::platform;
9 +
use super::trap;
10 +
use super::range;
11 +
12 +
/// Dispatch ownership and idle state retained by one physical hart.
13 +
export record State: Copy {
14 +
    /// Physical hart whose windows this state selects.
15 +
    hart: u32,
16 +
    /// Context currently executing or interrupted on this hart.
17 +
    current: ?abi::Ref,
18 +
    /// Budget selected for the current context.
19 +
    budget: ?abi::Ref,
20 +
    /// M-mode frame that enters the hart's idle loop.
21 +
    idle: trap::Frame,
22 +
    /// Firmware stack reserved for idle trap handling.
23 +
    stack: range::Range,
24 +
}
25 +
26 +
/// Validated RV64 CLINT registers owned by one hart.
27 +
export record Timer: Copy {
28 +
    /// Shared monotonic mtime register.
29 +
    clock: u64,
30 +
    /// This hart's mtimecmp register.
31 +
    compare: u64,
32 +
}
33 +
34 +
/// Timer and dispatch state owned by one physical hart.
35 +
record Runtime: Copy {
36 +
    /// Current context and retained idle frame.
37 +
    state: State,
38 +
    /// Validated timer registers for this hart.
39 +
    timer: Timer,
40 +
}
41 +
42 +
/// Runtime slots initialized before each hart enables timer interrupts.
43 +
unsafe static HARTS: [Runtime; limits::HARTS] = undefined;
44 +
45 +
/// Read the executing physical hart identifier.
46 +
export fn hart() -> u32;
47 +
/// Address of the M-mode idle loop.
48 +
fn idleAddress() -> u64;
49 +
/// Enable machine timer delivery while global interrupts remain disabled.
50 +
fn enable();
51 +
52 +
/// Initialize this hart's idle frame and validated timer before installing dispatch.
53 +
export unsafe fn initialize(machine: &platform::Platform, id: u32, kernelGp: u64) throws (abi::Error) {
54 +
    let device = try timer(machine, id);
55 +
    let stack = machine.stacks[id];
56 +
    let mut idle = trap::Frame { registers: [0; 32], pc: idleAddress(), status: 0x1880, cause: 0, value: 0 };
57 +
    set idle.registers[2] = stack.end;
58 +
    set idle.registers[3] = kernelGp;
59 +
    set HARTS[id] = Runtime {
60 +
        state: State { hart: id, current: nil, budget: nil, idle, stack }, timer: device,
61 +
    };
62 +
    arm(device, 0xffffffffffffffff);
63 +
}
64 +
65 +
/// Reevaluate authority after a timer interrupt with shared metadata serialized.
66 +
export unsafe fn interrupt(frame: &mut trap::Frame, anchor: &mut trap::Hart) {
67 +
    let id = hart();
68 +
    assert id < limits::HARTS;
69 +
    let clock = now(HARTS[id].timer);
70 +
    let choice = try! exchange(&mut HARTS[id].state, &budgets::STORE, &mut domains::STORE, frame, anchor, clock);
71 +
    arm(HARTS[id].timer, choice.deadline);
72 +
}
73 +
74 +
/// Enter the first authorized context or the hart's retained idle frame.
75 +
export unsafe fn start(anchor: &mut trap::Hart) -> ! {
76 +
    let id = hart();
77 +
    assert id < limits::HARTS;
78 +
    let mut frame = HARTS[id].state.idle;
79 +
    interrupt(&mut frame, anchor);
80 +
    enable();
81 +
    trap::resume(&frame);
82 +
}
83 +
84 +
/// Read one validated, naturally aligned 64-bit device register.
85 +
fn read(address: u64) -> u64;
86 +
/// Write one validated, naturally aligned 64-bit device register.
87 +
fn write(address: u64, value: u64);
88 +
89 +
/// Select one CLINT extent and derive the selected hart's timer registers.
90 +
export fn timer(machine: &platform::Platform, hart: u32) -> Timer throws (abi::Error) {
91 +
    if hart >= limits::HARTS or (machine.harts & (1 << hart)) == 0 { throw abi::Error::InvalidArg; }
92 +
    let mut result: ?Timer = nil;
93 +
    for i in 0..machine.deviceCount {
94 +
        let device = machine.devices[i];
95 +
        if device.kind <> platform::Kind::Clint { continue; }
96 +
        if result <> nil or device.memory.end < device.memory.start
97 +
            or device.memory.end - device.memory.start < 0xc000 or (device.memory.start & 7) <> 0 {
98 +
            throw abi::Error::InvalidArg;
99 +
        }
100 +
        set result = Timer { clock: device.memory.start + 0xbff8, compare: device.memory.start + 0x4000 + hart as u64 * 8 };
101 +
    }
102 +
    let selected = result else { throw abi::Error::InvalidArg; };
103 +
    return selected;
104 +
}
105 +
106 +
/// Read the platform's monotonic clock in mtime ticks.
107 +
export fn now(timer: Timer) -> u64 { return read(timer.clock); }
108 +
109 +
/// Program the next boundary while this hart's timer handling is serialized.
110 +
export fn arm(timer: Timer, deadline: u64) { write(timer.compare, deadline); }
111 +
112 +
/// Runnable context and the next boundary that requires a dispatch decision.
113 +
export record Choice: Copy {
114 +
    /// Current exclusive interval, or nil while idle.
115 +
    budget: ?abi::Ref,
116 +
    /// Context authorized to execute now, or nil while idle.
117 +
    context: ?abi::Ref,
118 +
    /// Platform mtime tick for the next timer interrupt.
119 +
    deadline: u64,
120 +
}
121 +
122 +
/// Test whether a live context belongs to the active domain owning its budget.
123 +
fn runnable(store: &domains::Store, owner: abi::Ref, context: abi::Ref) -> bool {
124 +
    if not slots::matches(&store.slots[..], owner, slots::State::Live) { return false; }
125 +
    if store.records[owner.index].state <> domains::Lifecycle::Active { return false; }
126 +
    if not slots::matches(&store.contextSlots[..], context, slots::State::Live) { return false; }
127 +
    return store.contexts[context.index].owner == owner;
128 +
}
129 +
130 +
/// Select the unique runnable window and the earliest relevant timer boundary.
131 +
/// The caller serializes budget and domain metadata while taking this snapshot.
132 +
export fn select(windows: &budgets::Store, contexts: &domains::Store, hart: u32, now: u64)
133 +
    -> Choice throws (abi::Error)
134 +
{
135 +
    if hart >= limits::HARTS { throw abi::Error::InvalidArg; }
136 +
    let mut result = Choice { budget: nil, context: nil, deadline: 0xffffffffffffffff };
137 +
    for i in 0..limits::BUDGETS {
138 +
        if windows.slots[i].state <> slots::State::Live { continue; }
139 +
        let window = windows.windows[i];
140 +
        if window.hart <> hart or window.end <= now { continue; }
141 +
        let context = window.context else { continue; };
142 +
        if not runnable(contexts, window.owner, context) { continue; }
143 +
        if window.start > now {
144 +
            if window.start < result.deadline { set result.deadline = window.start; }
145 +
            continue;
146 +
        }
147 +
        assert result.budget == nil;
148 +
        set result.budget = abi::Ref { index: i, generation: windows.slots[i].generation };
149 +
        set result.context = context;
150 +
        if window.end < result.deadline { set result.deadline = window.end; }
151 +
    }
152 +
    return result;
153 +
}
154 +
155 +
/// Save the interrupted context and install the selected frame and kernel stack.
156 +
/// Interrupts remain disabled and shared metadata stays serialized through exchange.
157 +
export fn exchange(state: &mut State, windows: &budgets::Store, contexts: &mut domains::Store,
158 +
    frame: &mut trap::Frame, anchor: &mut trap::Hart, now: u64) -> Choice throws (abi::Error)
159 +
{
160 +
    let choice = try select(windows, contexts, state.hart, now);
161 +
    if let current = state.current {
162 +
        assert slots::matches(&contexts.contextSlots[..], current, slots::State::Live);
163 +
        set contexts.contexts[current.index].frame = *frame;
164 +
    }
165 +
    if let next = choice.context {
166 +
        let context = contexts.contexts[next.index];
167 +
        assert context.kernelStack.start < context.kernelStack.end;
168 +
        set *frame = context.frame;
169 +
        set anchor.stackBottom = context.kernelStack.start;
170 +
        set anchor.stackTop = context.kernelStack.end;
171 +
    } else {
172 +
        set *frame = state.idle;
173 +
        set anchor.stackBottom = state.stack.start;
174 +
        set anchor.stackTop = state.stack.end;
175 +
    }
176 +
    set state.current = choice.context;
177 +
    set state.budget = choice.budget;
178 +
    return choice;
179 +
}
kernel/kernel/dispatch.ras added +41 -0
1 +
//! Naturally aligned RV64 CLINT access with device ordering.
2 +
.text;
3 +
.export @kernel::dispatch::read;
4 +
.export @kernel::dispatch::write;
5 +
.export @kernel::dispatch::hart;
6 +
.export @kernel::dispatch::idleAddress;
7 +
.export @kernel::dispatch::enable;
8 +
9 +
// The executing hart selects its private dispatch runtime slot.
10 +
@kernel::dispatch::hart
11 +
    csrr %a0 mhartid;
12 +
    ret;
13 +
14 +
// Saved idle frames enter this loop with machine interrupts enabled by mret.
15 +
@kernel::dispatch::idleAddress
16 +
    la %a0 @dispatchIdle;
17 +
    ret;
18 +
@dispatchIdle
19 +
    wfi;
20 +
    j @dispatchIdle;
21 +
22 +
// Global interrupt enable is restored from the selected frame by mret.
23 +
@kernel::dispatch::enable
24 +
    csrr %t0 mie;
25 +
    ori %t0 %t0 128;
26 +
    csrw mie %t0;
27 +
    ret;
28 +
29 +
// The validated CLINT mapping contains a naturally aligned mtime register.
30 +
@kernel::dispatch::read
31 +
    fence iorw iorw;
32 +
    ld %a0 0(%a0);
33 +
    fence iorw iorw;
34 +
    ret;
35 +
36 +
// One RV64 store updates the owning hart's complete mtimecmp value.
37 +
@kernel::dispatch::write
38 +
    fence iorw iorw;
39 +
    sd %a1 0(%a0);
40 +
    fence iorw iorw;
41 +
    ret;
kernel/kernel/domains.rad +24 -2
8 8
use super::capability;
9 9
use super::registry;
10 10
use super::instances;
11 11
use super::events;
12 12
use super::trap;
13 +
use super::range;
14 +
15 +
/// Retained kernel stack capacity for one suspended execution context.
16 +
export constant KERNEL_STACK_PAGES: u32 = 64;
13 17
14 18
/// Management rights returned to a domain's creator.
15 19
export constant MANAGEMENT_RIGHTS: u16 = abi::DESTROY | abi::EXECUTE | abi::GRANT | abi::TRANSFER | abi::WAKE;
16 20
17 21
/// Protection-domain lifetime independent of execution budgets.
34 38
35 39
/// Saved integer state owned by one execution context.
36 40
export record Context: Copy {
37 41
    /// Domain that owns this context.
38 42
    owner: abi::Ref,
43 +
    /// Physical frames retained until this context stops.
44 +
    kernelFrames: frames::Run,
45 +
    /// Exclusive stack bounds used by trap entry and suspended M-mode calls.
46 +
    kernelStack: range::Range,
39 47
    /// Architectural state used when this context resumes.
40 48
    frame: trap::Frame,
41 49
}
42 50
43 51
/// Published domain resources and lifecycle metadata.
103 111
    allocation: frames::Run,
104 112
    /// Mapped metadata at the allocation's start.
105 113
    memory: *unsafe mut Memory,
106 114
    /// Independent package-state graph.
107 115
    graph: instances::Instance,
116 +
    /// Retained stack allocation for the initial context.
117 +
    kernelFrames: frames::Run,
118 +
    /// Initial context's mapped stack bounds.
119 +
    kernelStack: range::Range,
108 120
}
109 121
110 122
/// Allocate private storage and return all earlier allocations on failure.
111 123
unsafe fn prepare(pool: &mut frames::Pool, packages: &registry::Store, image: abi::Ref) -> Prepared throws (abi::Error) {
112 124
    let pending = try frames::reserve(pool, (@sizeOf(Memory) + 4095) / 4096);
113 125
    let allocation = frames::commit(pending);
114 126
    let graph = try instances::create(packages, pool, image) catch err {
115 127
        try! frames::release(pool, allocation); throw err;
116 128
    };
129 +
    let pendingStack = try frames::reserve(pool, KERNEL_STACK_PAGES) catch err {
130 +
        try! frames::release(pool, graph.frames);
131 +
        try! frames::release(pool, allocation); throw err;
132 +
    };
133 +
    let kernelFrames = frames::commit(pendingStack);
134 +
    let kernelStack = try! frames::extent(pool, kernelFrames);
117 135
    let extent = try! frames::extent(pool, allocation);
118 136
    zero(extent.start, allocation.count);
119 -
    return Prepared { allocation, memory: memory(extent.start), graph };
137 +
    zero(kernelStack.start, kernelFrames.count);
138 +
    return Prepared { allocation, memory: memory(extent.start), graph, kernelFrames, kernelStack };
120 139
}
121 140
122 141
/// Return storage that has never been exposed to a published domain.
123 142
fn discard(pool: &mut frames::Pool, prepared: Prepared) {
143 +
    try! frames::release(pool, prepared.kernelFrames);
124 144
    try! frames::release(pool, prepared.graph.frames);
125 145
    try! frames::release(pool, prepared.allocation);
126 146
}
127 147
128 148
/// Create an unscheduled domain with Events and one initial integer context.
170 190
    });
171 191
    let initial = slots::reference(&contextSlot);
172 192
    // MPIE enables interrupts after the first return into U-mode.
173 193
    let mut frame = trap::Frame { registers: [0; 32], pc: entry, status: 0x80, cause: 0, value: 0 };
174 194
    set frame.registers[3] = prepared.graph.table.ptr as u64;
175 -
    set store.contexts[initial.index] = Context { owner: object, frame };
195 +
    set store.contexts[initial.index] = Context {
196 +
        owner: object, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack, frame,
197 +
    };
176 198
    set store.records[object.index] = Domain {
177 199
        state: Lifecycle::Pending, creator: table.owner, parent: table.owner, image: root, initial,
178 200
        allocation: prepared.allocation, memory: prepared.memory, graph: prepared.graph, events: queue,
179 201
    };
180 202
    let context = try! slots::commit(&mut store.contextSlots[..], contextSlot);
kernel/kernel/tests.rad +1 -0
15 15
export mod registry;
16 16
export mod loader;
17 17
export mod instances;
18 18
export mod domains;
19 19
export mod budgets;
20 +
export mod dispatch;
kernel/kernel/tests/budgets.rad +3 -1
7 7
use kernel::slots;
8 8
use kernel::domains;
9 9
use kernel::frames;
10 10
use kernel::instances;
11 11
use kernel::trap;
12 +
use kernel::range;
12 13
13 14
/// Exclusive CPU-window metadata.
14 15
unsafe static STORE: budgets::Store = undefined;
15 16
/// Capability owner for the test windows.
16 17
unsafe static TABLE: capability::Table = undefined;
125 126
    let child = slots::reference(&childSlot);
126 127
    capability::initialize(&mut MEMORY.table, child);
127 128
    let contextSlot = try! slots::reserve(&mut DOMAINS.contextSlots[..]);
128 129
    let context = slots::reference(&contextSlot);
129 130
    set DOMAINS.contexts[context.index] = domains::Context {
130 -
        owner: child, frame: trap::Frame { registers: [0; 32], pc: 4, status: 0x80, cause: 0, value: 0 },
131 +
        owner: child, kernelFrames: frames::Run { first: 0, count: 0 }, kernelStack: range::Range { start: 0, end: 0 },
132 +
        frame: trap::Frame { registers: [0; 32], pc: 4, status: 0x80, cause: 0, value: 0 },
131 133
    };
132 134
    set DOMAINS.records[child.index] = domains::Domain {
133 135
        state: domains::Lifecycle::Pending, creator: root, parent: root,
134 136
        image: abi::Ref { index: 0, generation: 1 }, initial: context,
135 137
        allocation: frames::Run { first: 0, count: 0 }, memory: &mut MEMORY,
kernel/kernel/tests/dispatch.rad added +136 -0
1 +
//! Dispatch selection at CPU-window boundaries and idle gaps.
2 +
3 +
use std::testing;
4 +
use kernel::abi;
5 +
use kernel::slots;
6 +
use kernel::domains;
7 +
use kernel::budgets;
8 +
use kernel::capability;
9 +
use kernel::dispatch;
10 +
use kernel::platform;
11 +
use kernel::range;
12 +
use kernel::trap;
13 +
14 +
/// Platform resources used to validate CLINT address selection.
15 +
unsafe static PLATFORM: platform::Platform = undefined;
16 +
17 +
/// Domain lifetimes and saved contexts used for selection.
18 +
unsafe static DOMAINS: domains::Store = undefined;
19 +
/// Exclusive intervals presented to dispatch.
20 +
unsafe static BUDGETS: budgets::Store = undefined;
21 +
/// Owner of the test CPU capabilities.
22 +
unsafe static TABLE: capability::Table = undefined;
23 +
24 +
/// Install one live context and two separated windows on hart zero.
25 +
unsafe fn initialize() {
26 +
    domains::initialize(&mut DOMAINS);
27 +
    budgets::initialize(&mut BUDGETS);
28 +
    let owner = abi::Ref { index: 0, generation: 1 };
29 +
    capability::initialize(&mut TABLE, owner);
30 +
    set DOMAINS.slots[0] = slots::Slot { generation: 1, state: slots::State::Live };
31 +
    set DOMAINS.records[0].state = domains::Lifecycle::Active;
32 +
    set DOMAINS.contextSlots[0] = slots::Slot { generation: 1, state: slots::State::Live };
33 +
    set DOMAINS.contexts[0].owner = owner;
34 +
    let first = try! budgets::seed(&mut BUDGETS, &mut TABLE, 0, 10, 40);
35 +
    let gap = try! budgets::split(&mut BUDGETS, &mut TABLE, first, 20, 0);
36 +
    let last = try! budgets::split(&mut BUDGETS, &mut TABLE, gap, 30, 0);
37 +
    let a = try! capability::get(&TABLE, first);
38 +
    let b = try! capability::get(&TABLE, last);
39 +
    set BUDGETS.windows[a.object.index].context = owner;
40 +
    set BUDGETS.windows[b.object.index].context = owner;
41 +
}
42 +
43 +
/// Dispatch selects only the half-open interval and wakes at the next runnable start.
44 +
@test unsafe fn boundaries() throws (testing::TestError) {
45 +
    initialize();
46 +
    let before = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 9);
47 +
    try testing::expect(before.context == nil and before.deadline == 10);
48 +
    let first = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10);
49 +
    try testing::expect(first.context == TABLE.owner and first.deadline == 20);
50 +
    let gap = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 20);
51 +
    try testing::expect(gap.context == nil and gap.deadline == 30);
52 +
    let last = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 30);
53 +
    try testing::expect(last.context == TABLE.owner and last.deadline == 40);
54 +
    let ended = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 40);
55 +
    try testing::expect(ended.context == nil and ended.deadline == 0xffffffffffffffff);
56 +
    let other = try! dispatch::select(&BUDGETS, &DOMAINS, 1, 10);
57 +
    try testing::expect(other.context == nil and other.deadline == 0xffffffffffffffff);
58 +
}
59 +
60 +
/// Pending domains and stale context generations cannot become runnable through a budget.
61 +
@test unsafe fn lifetimes() throws (testing::TestError) {
62 +
    initialize();
63 +
    set DOMAINS.records[0].state = domains::Lifecycle::Pending;
64 +
    let pending = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10);
65 +
    try testing::expect(pending.context == nil);
66 +
    set DOMAINS.records[0].state = domains::Lifecycle::Active;
67 +
    set DOMAINS.contextSlots[0].generation = 2;
68 +
    let stale = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10);
69 +
    try testing::expect(stale.context == nil);
70 +
}
71 +
72 +
/// Each online hart has a separate comparison word and shares one clock register.
73 +
@test unsafe fn timerRegisters() throws (testing::TestError) {
74 +
    set PLATFORM.harts = 0x81;
75 +
    set PLATFORM.deviceCount = 1;
76 +
    set PLATFORM.devices[0] = platform::Device {
77 +
        kind: platform::Kind::Clint, memory: range::Range { start: 0x2000000, end: 0x2010000 },
78 +
    };
79 +
    let first = try! dispatch::timer(&PLATFORM, 0);
80 +
    let last = try! dispatch::timer(&PLATFORM, 7);
81 +
    try testing::expect(first.clock == 0x200bff8 and first.compare == 0x2004000);
82 +
    try testing::expect(last.clock == first.clock and last.compare == first.compare + 56);
83 +
    let mut failures: u32 = 0;
84 +
    try dispatch::timer(&PLATFORM, 1) catch err {
85 +
        try testing::expect(err == abi::Error::InvalidArg); set failures += 1;
86 +
    };
87 +
    set PLATFORM.devices[0].memory.end = 0x200bff8;
88 +
    try dispatch::timer(&PLATFORM, 0) catch err {
89 +
        try testing::expect(err == abi::Error::InvalidArg); set failures += 1;
90 +
    };
91 +
    try testing::expect(failures == 2);
92 +
}
93 +
94 +
/// Trap callback supplied to the frame-exchange fixture.
95 +
fn handler(frame: &mut trap::Frame, hart: &mut trap::Hart) {}
96 +
97 +
/// Exchange preserves an M-mode continuation while another context uses its own stack.
98 +
@test unsafe fn continuations() throws (testing::TestError) {
99 +
    initialize();
100 +
    let mut frame = trap::Frame { registers: [0; 32], pc: 0x40, status: 0x80, cause: 0, value: 0 };
101 +
    set DOMAINS.contexts[0].frame = frame;
102 +
    set DOMAINS.contexts[0].kernelStack = range::Range { start: 0x8000, end: 0xc000 };
103 +
    set DOMAINS.contextSlots[1] = slots::Slot { generation: 1, state: slots::State::Live };
104 +
    set DOMAINS.contexts[1].owner = TABLE.owner;
105 +
    set DOMAINS.contexts[1].frame = frame;
106 +
    set DOMAINS.contexts[1].kernelStack = range::Range { start: 0xc000, end: 0x10000 };
107 +
    set BUDGETS.windows[1].context = abi::Ref { index: 1, generation: 1 };
108 +
    let mut idle = frame;
109 +
    set idle.pc = 0x100;
110 +
    set idle.status = 0x1880;
111 +
    set idle.registers[2] = 0x4000;
112 +
    let mut state = dispatch::State {
113 +
        hart: 0, current: nil, budget: nil, idle,
114 +
        stack: range::Range { start: 0x2000, end: 0x4000 },
115 +
    };
116 +
    let mut anchor = trap::Hart {
117 +
        stackTop: 0x4000, stackBottom: 0x2000, kernelGp: 0x20000, handler,
118 +
        savedT0: 0, savedT1: 0, savedSp: 0,
119 +
    };
120 +
    let first = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 10);
121 +
    try testing::expect(frame.pc == 0x40 and anchor.stackTop == 0xc000);
122 +
    set frame.pc = 0x88;
123 +
    set frame.status = 0x1880;
124 +
    set frame.registers[2] = 0xb000;
125 +
    set frame.registers[5] = 43;
126 +
    let second = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 20);
127 +
    try testing::expect(anchor.stackBottom == 0xc000 and anchor.stackTop == 0x10000);
128 +
    set frame.pc = 0x77;
129 +
    let resumed = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 30);
130 +
    try testing::expect(frame.pc == 0x88 and frame.status == 0x1880);
131 +
    try testing::expect(frame.registers[2] == 0xb000 and frame.registers[5] == 43);
132 +
    try testing::expect(DOMAINS.contexts[1].frame.pc == 0x77 and anchor.stackTop == 0xc000);
133 +
    let ended = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 40);
134 +
    try testing::expect(ended.context == nil and frame.pc == idle.pc and frame.registers[2] == 0x4000);
135 +
    try testing::expect(anchor.stackTop == 0x4000 and anchor.stackBottom == 0x2000);
136 +
}
kernel/kernel/tests/domains.rad +5 -0
69 69
    try testing::expect(child.memory.table.slots[1].state == slots::State::Free);
70 70
    let context = DOMAINS.contexts[child.initial.index];
71 71
    try testing::expect(context.owner == authority.object and context.frame.pc == 0x80000000);
72 72
    try testing::expect(context.frame.registers[3] == child.graph.table.ptr as u64);
73 73
    try testing::expect(context.frame.registers[2] == 0 and context.frame.status == 0x80);
74 +
    try testing::expect(context.kernelFrames.count == domains::KERNEL_STACK_PAGES);
75 +
    try testing::expect(context.kernelStack.end - context.kernelStack.start == domains::KERNEL_STACK_PAGES as u64 * 4096);
76 +
    try testing::expect((context.kernelStack.end & 15) == 0);
74 77
    try testing::expect(child.memory.ring.head == 0 and child.memory.ring.tail == 0);
75 78
    try testing::expect(child.memory.ring.mask == 255);
76 79
}
77 80
78 81
/// Check that a failed creation has returned all unpublished resources.
95 98
    let count = BACKINGS.pool.count;
96 99
    set BACKINGS.pool.count = 0;
97 100
    try failed(abi::Error::OutOfMemory);
98 101
    set BACKINGS.pool.count = (@sizeOf(domains::Memory) + 4095) / 4096;
99 102
    try failed(abi::Error::OutOfMemory);
103 +
    set BACKINGS.pool.count += 1;
104 +
    try failed(abi::Error::OutOfMemory);
100 105
    set BACKINGS.pool.count = count;
101 106
    set DOMAINS.events.queues[1].generation = 99;
102 107
    try failed(abi::Error::Busy);
103 108
    try testing::expect(DOMAINS.events.queues[1].generation == 99);
104 109
    set DOMAINS.events.queues[1].generation = 0;
kernel/kernel/trap.rad +1 -1
86 86
87 87
/// Install trap entry for the current hart with machine interrupts disabled.
88 88
export fn install(hart: *mut Hart);
89 89
/// Restore a complete integer context through mret. The current mscratch must
90 90
/// name an installed Hart, and the frame must have machine interrupts disabled.
91 -
export fn resume(frame: *Frame) -> !;
91 +
export fn resume(frame: &Frame) -> !;
92 92
93 93
/// Disable machine interrupts and stop the current hart.
94 94
export fn halt() -> !;
lib/std/arch/rv64/asm.rad +14 -1
490 490
    dataBuf: *mut [u8],
491 491
    arena: &mut alloc::Arena,
492 492
    pool: *unsafe mut strings::Pool,
493 493
    dataBase: u32
494 494
) -> Program throws (Error) {
495 -
    let slotCap = source.len + SOURCE_CAP_PADDING;
495 +
    let slotCap = tokenCapacity(sourceKind, source, pool);
496 496
    let tableCap = nextPowerOfTwo(slotCap * TABLE_CAPACITY_SCALE);
497 497
498 498
    let symbols = try! alloc::allocSlice(arena, @sizeOf(Symbol), @alignOf(Symbol), slotCap);
499 499
    let fixups = try! alloc::allocSlice(arena, @sizeOf(Fixup), @alignOf(Fixup), slotCap);
500 500
    let externalFixups = try! alloc::allocSlice(arena, @sizeOf(Fixup), @alignOf(Fixup), slotCap);
527 527
    let case Assembler { text, data, symbols: definedSymbols, externalFixups: pendingFixups, .. } = a
528 528
        else panic "expected assembler state";
529 529
    return Program { text, data, symbols: definedSymbols, externalFixups: pendingFixups };
530 530
}
531 531
532 +
/// Bound symbol and fixup counts by lexical tokens, including one spare slot.
533 +
unsafe fn tokenCapacity(kind: scanner::SourceKind, source: *[u8], pool: *unsafe mut strings::Pool) -> u32 {
534 +
    let mut scan = scanner::scanner(kind, source, pool);
535 +
    let mut count = SOURCE_CAP_PADDING;
536 +
    while true {
537 +
        let token = scanner::next(&mut scan);
538 +
        if token.kind == scanner::TokenKind::Eof { break; }
539 +
        set count += 1;
540 +
        if token.kind == scanner::TokenKind::Invalid { break; }
541 +
    }
542 +
    return count;
543 +
}
544 +
532 545
/// Return the next power of two at least as large as `value`.
533 546
fn nextPowerOfTwo(value: u32) -> u32 {
534 547
    let mut n: u32 = MIN_TABLE_CAPACITY;
535 548
    while n < value {
536 549
        set n <<= 1;
lib/std/arch/rv64/asm/tests.rad +14 -0
16 16
static ASM_DATA_STORAGE: [u8; 1024] = [0; 1024];
17 17
unsafe static ASM_STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
18 18
static PRINT_ARENA_STORAGE: [u8; 1024] = [0; 1024];
19 19
static PRINT_BUFFER: [u8; 128] = [0; 128];
20 20
21 +
/// Source buffer for comment capacity tests.
22 +
static SOURCE: [u8; 4096] = [0; 4096];
23 +
24 +
/// Long comments consume source space while leaving symbol and fixup demand small.
25 +
@test unsafe fn commentStorage() throws (testing::TestError) {
26 +
    let source = &mut SOURCE[..];
27 +
    for i in 0..source.len { set source[i] = 32; }
28 +
    set source[0] = '/'; set source[1] = '/';
29 +
    let text = "\n.text;\nret;\n";
30 +
    for byte, i in text { set source[source.len - text.len + i] = byte; }
31 +
    let program = try assembleSource(&SOURCE[..]);
32 +
    try testing::expect(program.text.len == 1 and program.text[0] == encode::ret());
33 +
}
34 +
21 35
unsafe fn assembleSource(source: *[u8]) -> super::Program throws (testing::TestError) {
22 36
    let mut arena = alloc::new(&mut ASM_ARENA_STORAGE[..]);
23 37
    return try super::assemble(
24 38
        scanner::SourceKind::String,
25 39
        source,
test/boot/run +2 -2
2 2
# Boot the kernel through its production initialization on each supported hart count.
3 3
set -eu
4 4
emulator=${RAD_EMULATOR:-emulator}
5 5
work=$(mktemp -d)
6 6
trap 'rm -rf "$work"' EXIT HUP INT TERM
7 -
cat test/boot/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras > "$work/boot.ras"
7 +
cat test/boot/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras kernel/kernel/dispatch.ras > "$work/boot.ras"
8 8
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/boot.ras" "$work/boot.rv64"
9 9
for harts in 1 2 8; do
10 10
    # The deterministic emulator shares its tick budget across all harts.
11 11
    steps=$((10000000 * harts))
12 12
    if ! "$emulator" -machine -max-steps="$steps" -harts="$harts" -run "$work/boot.rv64" > "$work/log" 2>&1; then
28 28
if [ "$status" -ne 2 ] || [ "$(grep -c 'wfi=1 mcause=0x0' "$work/log")" -ne 8 ]; then
29 29
    cat "$work/log" >&2
30 30
    exit 1
31 31
fi
32 32
printf 'kernel startup: all eight harts reached machine idle without traps\n'
33 -
cat test/boot/trap.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras > "$work/trap.ras"
33 +
cat test/boot/trap.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras kernel/kernel/dispatch.ras > "$work/trap.ras"
34 34
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/trap.ras" "$work/trap.rv64"
35 35
status=0
36 36
"$emulator" -machine -harts=2 -max-steps=20000000 -run "$work/trap.rv64" > "$work/log" 2>&1 || status=$?
37 37
if [ "$status" -ne 2 ] || [ "$(grep -c '^kernel: unexpected trap$' "$work/log")" -ne 1 ] \
38 38
    || [ "$(grep -c 'wfi=1' "$work/log")" -ne 2 ]; then
test/dispatch/kernel/dispatchcheck.rad added +104 -0
1 +
//! Native timer preemption across user execution, idle, and machine continuations.
2 +
use std::mem;
3 +
use std::arch::rv64::shared;
4 +
use kernel::abi;
5 +
use kernel::slots;
6 +
use kernel::backing;
7 +
use kernel::pages;
8 +
use kernel::capability;
9 +
use kernel::registry;
10 +
use kernel::loader;
11 +
use kernel::domains;
12 +
use kernel::budgets;
13 +
use kernel::dispatch;
14 +
use kernel::boot;
15 +
use kernel::dispatchinput;
16 +
17 +
/// Bootstrap authority used to create fixture domains and CPU windows.
18 +
unsafe static TABLE: capability::Table = undefined;
19 +
/// Domain handles used for budget binding.
20 +
unsafe static HANDLES: [abi::Handle; 3] = undefined;
21 +
/// Initial contexts for the user, machine worker, and completion check.
22 +
unsafe static CONTEXTS: [abi::Ref; 3] = undefined;
23 +
/// View a validated allocated source page.
24 +
fn memory(address: u64) -> *mut u8;
25 +
/// Current kernel package-state base.
26 +
fn kernelGp() -> u64;
27 +
/// Native entry for retained M-mode work.
28 +
fn worker() -> u64;
29 +
/// Native entry for the final assertion callback.
30 +
fn completion() -> u64;
31 +
/// Complete the machine fixture with a passing status.
32 +
fn finish();
33 +
34 +
/// Prepare real private domains before enabling dispatch on hart zero.
35 +
export unsafe fn setup() {
36 +
    let pending = try! slots::reserve(&mut domains::STORE.slots[..]);
37 +
    let owner = try! slots::commit(&mut domains::STORE.slots[..], pending);
38 +
    capability::initialize(&mut TABLE, owner);
39 +
    try! backing::registerDomain(&mut pages::STORE.backings, owner);
40 +
    let authority = try! capability::install(&mut TABLE, capability::Entry {
41 +
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::CREATE | abi::ALLOCATE),
42 +
    });
43 +
    let source = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1);
44 +
    let sourceEntry = try! capability::get(&TABLE, source);
45 +
    let page = try! pages::get(&pages::STORE, sourceEntry.object);
46 +
    let bytes = @sliceOf(memory(page.base), 4096);
47 +
    let length = try! mem::copy(&mut bytes[..], &dispatchinput::INPUT[..]);
48 +
    let image = try! loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut TABLE,
49 +
        loader::Request { authority, source, offset: 0, length: length as u64 });
50 +
    for i in 0..3 {
51 +
        set HANDLES[i] = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE, &mut TABLE, authority, image);
52 +
        let cap = try! capability::get(&TABLE, HANDLES[i]);
53 +
        let child = try! domains::get(&domains::STORE, cap.object);
54 +
        set CONTEXTS[i] = child.initial;
55 +
        set domains::STORE.records[cap.object.index].state = domains::Lifecycle::Active;
56 +
        if i > 0 {
57 +
            set domains::STORE.contexts[child.initial.index].frame.status = 0x1880;
58 +
            set domains::STORE.contexts[child.initial.index].frame.registers[2] = domains::STORE.contexts[child.initial.index].kernelStack.end;
59 +
            set domains::STORE.contexts[child.initial.index].frame.registers[3] = kernelGp();
60 +
        }
61 +
    }
62 +
    let cap = try! capability::get(&TABLE, HANDLES[0]);
63 +
    let mut user = try! domains::get(&domains::STORE, cap.object);
64 +
    let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1);
65 +
    let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut user.memory.table, stack, (abi::READ | abi::WRITE) as u64);
66 +
    let stackCap = try! capability::get(&TABLE, stack);
67 +
    let stackPage = try! pages::get(&pages::STORE, stackCap.object);
68 +
    set domains::STORE.contexts[CONTEXTS[0].index].frame.registers[2] = stackPage.base + 4096;
69 +
    let imageRef = try! registry::image(&registry::STORE, &TABLE, image, abi::Rights(abi::EXECUTE));
70 +
    let target = try! registry::exported(&registry::STORE, imageRef, &"spin::count"[..]);
71 +
    let case shared::Target::Data(data) = target else panic "counter";
72 +
    let timer = try! dispatch::timer(&boot::PLATFORM, 0);
73 +
    let clock = dispatch::now(timer);
74 +
    let start = clock + 1000000;
75 +
    let span: u64 = 1000000;
76 +
    set domains::STORE.contexts[CONTEXTS[1].index].frame.pc = worker();
77 +
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[10] = user.graph.table[data.slot] + data.offset as u64;
78 +
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[11] = timer.clock;
79 +
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[12] = start + 3 * span;
80 +
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[13] = start + 4 * span;
81 +
    set domains::STORE.contexts[CONTEXTS[2].index].frame.pc = completion();
82 +
    let mut windows: [abi::Handle; 6] = undefined;
83 +
    let mut remaining = try! budgets::seed(&mut budgets::STORE, &mut TABLE, 0, start, start + 6 * span);
84 +
    for i in 0..6 {
85 +
        set windows[i] = remaining;
86 +
        if i < 5 { set remaining = try! budgets::split(&mut budgets::STORE, &mut TABLE, remaining, start + (i as u64 + 1) * span, clock); }
87 +
    }
88 +
    let owners = &[0 as u32, 0, 1, 0, 1, 2];
89 +
    for i in 0..6 {
90 +
        if i == 1 { continue; }
91 +
        let owner = owners[i];
92 +
        let handle = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE,
93 +
            budgets::Binding { budget: windows[i], domain: HANDLES[owner], context: CONTEXTS[owner] }, clock);
94 +
    }
95 +
}
96 +
97 +
/// Check state captured by production timer handling after both resumptions.
98 +
export unsafe fn verify() {
99 +
    let user = domains::STORE.contexts[CONTEXTS[0].index].frame;
100 +
    let machine = domains::STORE.contexts[CONTEXTS[1].index].frame;
101 +
    assert (user.status & 0x1800) == 0 and (machine.status & 0x1800) == 0x1800;
102 +
    assert machine.registers[8] > 0 and machine.registers[9] == 1 and machine.registers[18] > 0;
103 +
    finish();
104 +
}
test/dispatch/machine.ras added +53 -0
1 +
//! Exercise real timer-driven switching on hart zero.
2 +
.text;
3 +
    call @kernel::boot::initialize;
4 +
    call @kernel::dispatchcheck::setup;
5 +
    call @kernel::boot::run;
6 +
@fail
7 +
    li %t0 0x10001000;
8 +
    li %t1 0x13333;
9 +
    sw %t1 0(%t0);
10 +
.export @kernel::dispatchcheck::memory;
11 +
.export @kernel::dispatchcheck::kernelGp;
12 +
.export @kernel::dispatchcheck::worker;
13 +
.export @kernel::dispatchcheck::completion;
14 +
.export @kernel::dispatchcheck::finish;
15 +
@kernel::dispatchcheck::memory
16 +
    ret;
17 +
@kernel::dispatchcheck::kernelGp
18 +
    mv %a0 %gp;
19 +
    ret;
20 +
@kernel::dispatchcheck::worker
21 +
    la %a0 @machineWork;
22 +
    ret;
23 +
@kernel::dispatchcheck::completion
24 +
    la %a0 @complete;
25 +
    ret;
26 +
@machineWork
27 +
    ld %t0 0(%a1);
28 +
    bgeu %t0 %a2 @fail;
29 +
    ld %s2 0(%a0);
30 +
    beqz %s2 @fail;
31 +
    addi %sp %sp -16;
32 +
    li %s3 1234567;
33 +
    sd %s3 0(%sp);
34 +
    li %s0 0;
35 +
    li %s1 0;
36 +
@workLoop
37 +
    addi %s0 %s0 1;
38 +
    ld %t0 0(%sp);
39 +
    bne %t0 %s3 @fail;
40 +
    ld %t0 0(%a1);
41 +
    bltu %t0 %a3 @workLoop;
42 +
    ld %t0 0(%a0);
43 +
    bgeu %s2 %t0 @fail;
44 +
    li %s1 1;
45 +
    j @workLoop;
46 +
@complete
47 +
    call @kernel::dispatchcheck::verify;
48 +
    j @fail;
49 +
@kernel::dispatchcheck::finish
50 +
    li %t0 0x10001000;
51 +
    li %t1 0x5555;
52 +
    sw %t1 0(%t0);
53 +
    j @fail;
test/dispatch/run added +23 -0
1 +
#!/bin/sh
2 +
# Preempt and resume U-mode and M-mode work through production dispatch.
3 +
set -eu
4 +
emulator=${RAD_EMULATOR:-emulator}
5 +
work=$(mktemp -d)
6 +
trap 'rm -rf "$work"' EXIT HUP INT TERM
7 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/radiance.rv64.dev \
8 +
    -pkg spin -mod test/dispatch/spin.rad -entry spin -ril "$work"
9 +
cp kernel/kernel.rad "$work/kernel.rad"
10 +
printf '\nexport mod dispatchinput;\nexport mod dispatchcheck;\n' >> "$work/kernel.rad"
11 +
mkdir "$work/kernel"
12 +
cp kernel/kernel/*.rad "$work/kernel/"
13 +
cp test/dispatch/kernel/dispatchcheck.rad "$work/kernel/"
14 +
length=$(wc -c < "$work/spin.ril")
15 +
printf '//! Binary user loop.\n/// Complete trusted input package.\nexport static INPUT: [u8; %s] = [\n' "$length" > "$work/kernel/dispatchinput.rad"
16 +
od -An -v -tu1 "$work/spin.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/dispatchinput.rad"
17 +
printf '];\n' >> "$work/kernel/dispatchinput.rad"
18 +
sh test/acceptance/compile "$emulator" "$work"
19 +
cat test/dispatch/machine.ras kernel/kernel/*.ras > "$work/dispatch.ras"
20 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 \
21 +
    -- "$work/std.ril" "$work/kernel.ril" "$work/dispatch.ras" "$work/dispatch.rv64"
22 +
"$emulator" -machine -memory-size=262144 -max-steps=80000000 -run "$work/dispatch.rv64"
23 +
printf 'dispatch: user preemption, idle gap, and retained machine continuation passed\n'
test/dispatch/spin.rad added +5 -0
1 +
//! Non-yielding user code preempted by hardware timer boundaries.
2 +
/// Private progress counter observed by the machine fixture.
3 +
export static count: u64 = 0;
4 +
/// Consume each assigned window without making a kernel call.
5 +
@default fn main() { while true { set count += 1; } }
test/loader/run +1 -1
20 20
printf '//! Binary package used by both execution paths.\n/// Complete trusted binary RIL input.\nexport static INPUT: [u8; %s] = [\n' "$length" > "$work/kernel/loadinput.rad"
21 21
od -An -v -tu1 "$work/loaded.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/loadinput.rad"
22 22
printf '];\n' >> "$work/kernel/loadinput.rad"
23 23
sh test/acceptance/compile "$emulator" "$work"
24 24
cat test/loader/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras \
25 -
    kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras > "$work/loader.ras"
25 +
    kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras kernel/kernel/dispatch.ras > "$work/loader.ras"
26 26
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
27 27
    -run bin/kernel.build.rv64 -- "$work/std.ril" "$work/kernel.ril" "$work/loader.ras" "$work/loader.rv64"
28 28
"$emulator" -machine -memory-size=262144 -max-steps=200000000 -run "$work/loader.rv64"
29 29
printf 'runtime loader: hosted and loaded entries agree after workspace release\n'