kernel: Manage additional execution contexts

638682ed119287b1bee3d78a46682b2af6ec8c72173c39cfa348db3454e8bf52
Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent 4296f82d
kernel/kernel/abi.rad +30 -0
55 55
    codeBytes: u64,
56 56
    /// Number of private state bytes per instance.
57 57
    dataBytes: u64,
58 58
}
59 59
60 +
/// Validated entry, stack, and arguments for an additional context.
61 +
export record ContextStart: Copy {
62 +
    /// Exported native entry in the target domain's package graph.
63 +
    entry: u64,
64 +
    /// Aligned top of writable user stack storage.
65 +
    stack: u64,
66 +
    /// Readable startup argument address passed in a0.
67 +
    args: u64,
68 +
    /// Startup argument byte count passed in a1.
69 +
    size: u64,
70 +
}
71 +
72 +
/// Identity, owner, and user stack bounds of a live context.
73 +
export record ContextInfo: Copy {
74 +
    /// Generation-bearing context identity.
75 +
    context: u64,
76 +
    /// Owning domain identity.
77 +
    domain: u64,
78 +
    /// First byte of the reserved user stack.
79 +
    stackBase: u64,
80 +
    /// Exclusive upper bound of the reserved user stack.
81 +
    stackTop: u64,
82 +
    /// Ready or Stopped encoded as zero or one.
83 +
    state: u64,
84 +
    /// Executing hart, or all ones while no hart owns execution.
85 +
    hart: u64,
86 +
}
87 +
60 88
/// Identity and CPU authority of the calling execution context.
61 89
export record CurrentContextInfo: Copy {
62 90
    /// Generation-bearing context identity.
63 91
    context: u64,
64 92
    /// Physical hart executing this call.
196 224
    ContextDestroy = 61,
197 225
    /// Query an execution context.
198 226
    QueryContext = 62,
199 227
    /// Query the current context and execution window.
200 228
    CurrentContext = 63,
229 +
    /// Report normal return from the calling execution context.
230 +
    ContextReturn = 64,
201 231
    /// Partition a CPU time window.
202 232
    BudgetSplit = 70,
203 233
    /// Combine adjacent CPU time windows.
204 234
    BudgetMerge = 71,
205 235
    /// Query a CPU time window.
kernel/kernel/budgets.rad +3 -0
241 241
    }
242 242
    if not slots::matches(&domainStore.contextSlots[..], request.context, slots::State::Live)
243 243
        or domainStore.contexts[request.context.index].owner <> permission.object {
244 244
            throw abi::Error::BadHandle;
245 245
        }
246 +
    if domainStore.contexts[request.context.index].state == domains::ContextState::Stopped {
247 +
        throw abi::Error::InvalidArg;
248 +
    }
246 249
    if not available(store, object, candidate, request.context) {
247 250
        throw abi::Error::Busy;
248 251
    }
249 252
    let mut result = request.budget;
250 253
    if permission.object <> table.owner {
kernel/kernel/calls.rad +40 -6
80 80
            let removed = try! capability::invalidate(table, handle);
81 81
        },
82 82
    }
83 83
}
84 84
85 -
/// Map a checked, aligned result address to writable words.
85 +
/// Map a checked, aligned caller record address to words.
86 86
fn memory(address: u64) -> *mut u64;
87 87
88 88
/// Test whether one nonwrapping extent contains the complete result range.
89 89
fn contains(base: u64, size: u64, address: u64, length: u64) -> bool {
90 90
    return address >= base and address - base <= size and length <= size - (address - base);
91 91
}
92 92
93 -
/// Validate writable Page or private package-data storage for a result record.
94 -
/// Package-state tables and kernel metadata are not writable result storage.
95 -
export unsafe fn resultBuffer(domain: &domains::Domain, address: u64, size: u64) -> bool {
93 +
/// Validate aligned Page or private package-data storage with the given rights.
94 +
/// Package-state tables and kernel metadata cannot hold caller-supplied records.
95 +
unsafe fn buffer(domain: &domains::Domain, address: u64, size: u64, rights: abi::Rights) -> bool {
96 96
    if (address & 7) <> 0 or size > 0xffffffffffffffff - address {
97 97
        return false;
98 98
    }
99 -
    if pages::accessible(&pages::STORE, &domain.memory.table, address, size, abi::Rights(abi::WRITE)) {
99 +
    if pages::accessible(&pages::STORE, &domain.memory.table, address, size, rights) {
100 100
        return true;
101 101
    }
102 102
    for i in 0..limits::PACKAGES {
103 103
        if registry::STORE.slots[i].state <> slots::State::Live {
104 104
            continue;
116 116
}
117 117
118 118
/// Copy a fixed query record only after validating its exact buffer size.
119 119
unsafe fn reply(domain: &domains::Domain, arguments: &[u64], words: &[u64]) throws (abi::Error) {
120 120
    let size = words.len as u64 * 8;
121 -
    if arguments[2] <> size or arguments[3] <> 0 or not resultBuffer(domain, arguments[1], size) {
121 +
    if arguments[2] <> size or arguments[3] <> 0 or not buffer(domain, arguments[1], size, abi::Rights(abi::WRITE)) {
122 122
        throw abi::Error::InvalidArg;
123 123
    }
124 124
    let output = @sliceOf(memory(arguments[1]), words.len);
125 125
    for i in 0..words.len {
126 126
        set output[i] = words[i];
179 179
                set entry = address;
180 180
            }
181 181
            try reply(&domain, arguments, &[abi::id(object), entry, package.codeAddress, package.code.len as u64 * 4, package.memory as u64]);
182 182
            return 0;
183 183
        },
184 +
        case 60 => {
185 +
            if arguments[2] <> @sizeOf(abi::ContextStart) as u64 or arguments[3] <> 0
186 +
                or not buffer(&domain, arguments[1], arguments[2], abi::Rights(abi::READ)) {
187 +
                throw abi::Error::InvalidArg;
188 +
            }
189 +
            let words = @sliceOf(memory(arguments[1]), 4);
190 +
            let start = abi::ContextStart { entry: words[0], stack: words[1], args: words[2], size: words[3] };
191 +
            return abi::id(try domains::contextCreate(&mut domains::STORE, &mut pages::STORE, &registry::STORE,
192 +
                &domain.memory.table, handle, start));
193 +
        },
194 +
        case 61 => {
195 +
            if arguments[2] <> 0 or arguments[3] <> 0 {
196 +
                throw abi::Error::InvalidArg;
197 +
            }
198 +
            try domains::contextDestroy(&mut domains::STORE, &mut pages::STORE, &domain.memory.table,
199 +
                handle, abi::reference(arguments[1]));
200 +
            return 0;
201 +
        },
202 +
        case 62 => {
203 +
            let permission = try capability::authority(&domain.memory.table, handle, abi::Rights(0));
204 +
            let target = try domains::get(&domains::STORE, permission.object);
205 +
            let mut object = target.initial;
206 +
            if arguments[1] <> 0 {
207 +
                set object = abi::reference(arguments[1]);
208 +
            }
209 +
            let context = try domains::context(&domains::STORE, permission.object, object);
210 +
            let mut hart: u64 = 0xffffffffffffffff;
211 +
            if let id = context.hart {
212 +
                set hart = id as u64;
213 +
            }
214 +
            try reply(&domain, &[0, arguments[2], arguments[3], 0],
215 +
                &[abi::id(object), abi::id(permission.object), context.userStack.start, context.userStack.end, context.state as u64, hart]);
216 +
            return 0;
217 +
        },
184 218
        case 63 => {
185 219
            if arguments[0] <> 0 {
186 220
                throw abi::Error::InvalidArg;
187 221
            }
188 222
            let current = dispatch::current();
kernel/kernel/calls.ras +2 -2
1 -
//! Typed access to a checked user result buffer.
1 +
//! Typed access to a checked user record buffer.
2 2
.text;
3 3
.export @kernel::calls::memory;
4 4
5 -
// The caller checked alignment, extent, and writable domain ownership.
5 +
// The caller checked alignment, extent, and the required domain access rights.
6 6
@kernel::calls::memory
7 7
    ret;
kernel/kernel/dispatch.rad +10 -2
89 89
    let context = HARTS[id].state.current else {
90 90
        trap::halt();
91 91
        return;
92 92
    };
93 93
    assert slots::matches(&domains::STORE.contextSlots[..], context, slots::State::Live);
94 -
    calls::handle(domains::STORE.contexts[context.index].owner, frame, now(HARTS[id].timer));
94 +
    let owner = domains::STORE.contexts[context.index].owner;
95 +
    if frame.registers[17] == abi::Operation::ContextReturn as u64 {
96 +
        assert trap::advanceCall(frame);
97 +
        try! domains::returned(&mut domains::STORE, owner, context);
98 +
    } else {
99 +
        calls::handle(owner, frame, now(HARTS[id].timer));
100 +
    }
95 101
    interrupt(frame, anchor);
96 102
}
97 103
98 104
/// Enter the first authorized context or the hart's retained idle frame.
99 105
export unsafe fn start(anchor: &mut trap::Hart) -> ! {
162 168
        return false;
163 169
    }
164 170
    if not slots::matches(&store.contextSlots[..], context, slots::State::Live) {
165 171
        return false;
166 172
    }
167 -
    return store.contexts[context.index].owner == owner;
173 +
    return store.contexts[context.index].owner == owner and store.contexts[context.index].state == domains::ContextState::Ready;
168 174
}
169 175
170 176
/// Select the unique runnable window and the earliest relevant timer boundary.
171 177
/// The caller serializes budget and domain metadata while taking this snapshot.
172 178
export fn select(windows: &budgets::Store, contexts: &domains::Store, hart: u32, now: u64)
213 219
{
214 220
    let choice = try select(windows, contexts, state.hart, now);
215 221
    if let current = state.current {
216 222
        assert slots::matches(&contexts.contextSlots[..], current, slots::State::Live);
217 223
        set contexts.contexts[current.index].frame = *frame;
224 +
        set contexts.contexts[current.index].hart = nil;
218 225
    }
219 226
    if let next = choice.context {
220 227
        let context = contexts.contexts[next.index];
221 228
        assert context.kernelStack.start < context.kernelStack.end;
222 229
        set *frame = context.frame;
223 230
        set anchor.stackBottom = context.kernelStack.start;
224 231
        set anchor.stackTop = context.kernelStack.end;
232 +
        set contexts.contexts[next.index].hart = state.hart;
225 233
    } else {
226 234
        set *frame = state.idle;
227 235
        set anchor.stackBottom = state.stack.start;
228 236
        set anchor.stackTop = state.stack.end;
229 237
    }
kernel/kernel/domains.rad +161 -5
10 10
use super::instances;
11 11
use super::events;
12 12
use super::trap;
13 13
use super::range;
14 14
use super::pages;
15 +
use std::arch::rv64::shared;
15 16
16 17
/// Retained kernel stack capacity for one suspended execution context.
17 18
export constant KERNEL_STACK_PAGES: u32 = 64;
18 19
19 20
/// Management rights returned to a domain's creator.
37 38
    ring: events::Ring,
38 39
    /// Startup values exposed read-only to this domain.
39 40
    env: abi::Env,
40 41
}
41 42
43 +
/// Eligibility to resume a live execution context.
44 +
export union ContextState: Copy {
45 +
    /// The context can execute with CPU authority in an active domain.
46 +
    Ready,
47 +
    /// The context has returned and cannot execute again.
48 +
    Stopped,
49 +
}
50 +
42 51
/// Saved integer state owned by one execution context.
43 52
export record Context: Copy {
44 53
    /// Domain that owns this context.
45 54
    owner: abi::Ref,
55 +
    /// Whether this context can resume.
56 +
    state: ContextState,
57 +
    /// Hart retaining this context as its current execution owner.
58 +
    hart: ?u32,
46 59
    /// Physical frames retained until this context stops.
47 60
    kernelFrames: frames::Run,
48 61
    /// Exclusive stack bounds used by trap entry and suspended M-mode calls.
49 62
    kernelStack: range::Range,
63 +
    /// Exclusive user stack extent; empty until initial activation.
64 +
    userStack: range::Range,
50 65
    /// Architectural state used when this context resumes.
51 66
    frame: trap::Frame,
52 67
}
53 68
54 69
/// Published domain resources and lifecycle metadata.
92 107
93 108
/// View an allocated physical extent that contains a complete Memory record.
94 109
fn memory(address: u64) -> *unsafe mut Memory;
95 110
/// Clear whole mapped physical frames before installing domain metadata.
96 111
fn zero(address: u64, count: u32);
112 +
/// Address of the U-mode normal-return call sequence.
113 +
fn returnAddress() -> u64;
97 114
98 115
/// Initialize fresh metadata before boot-domain publication.
99 116
export fn initialize(store: &mut Store) {
100 117
    slots::initialize(&mut store.slots[..]);
101 118
    slots::initialize(&mut store.contextSlots[..]);
108 125
        throw abi::Error::BadHandle;
109 126
    }
110 127
    return store.records[object.index];
111 128
}
112 129
130 +
/// Read a live context after checking its generation and owning domain.
131 +
export fn context(store: &Store, owner: abi::Ref, object: abi::Ref) -> Context throws (abi::Error) {
132 +
    let domain = try get(store, owner);
133 +
    if not slots::matches(&store.contextSlots[..], object, slots::State::Live)
134 +
        or store.contexts[object.index].owner <> owner {
135 +
            throw abi::Error::BadHandle;
136 +
        }
137 +
    return store.contexts[object.index];
138 +
}
139 +
140 +
/// Stop a returned context; initial return ends execution of its whole domain.
141 +
export fn returned(store: &mut Store, owner: abi::Ref, object: abi::Ref) throws (abi::Error) {
142 +
    let current = try context(store, owner, object);
143 +
    if store.records[owner.index].initial == object {
144 +
        set store.records[owner.index].state = Lifecycle::Dead;
145 +
        for i in 0..limits::CONTEXTS {
146 +
            if store.contextSlots[i].state == slots::State::Live and store.contexts[i].owner == owner {
147 +
                set store.contexts[i].state = ContextState::Stopped;
148 +
            }
149 +
        }
150 +
    } else {
151 +
        set store.contexts[object.index].state = ContextState::Stopped;
152 +
    }
153 +
}
154 +
155 +
/// Reclaim an additional context after all harts have released execution ownership.
156 +
export fn contextDestroy(store: &mut Store, memory: &mut pages::Store, table: &capability::Table,
157 +
    authority: abi::Handle, object: abi::Ref) throws (abi::Error)
158 +
{
159 +
    let permit = try capability::authority(table, authority, abi::Rights(abi::DESTROY));
160 +
    let target = try context(store, permit.object, object);
161 +
    if store.records[permit.object.index].initial == object {
162 +
        throw abi::Error::Denied;
163 +
    }
164 +
    if target.hart <> nil {
165 +
        throw abi::Error::Busy;
166 +
    }
167 +
    try! frames::release(&mut memory.backings.pool, target.kernelFrames);
168 +
    try! slots::release(&mut store.contextSlots[..], object);
169 +
}
170 +
171 +
/// Derive a descending stack extent from one writable Page capability.
172 +
fn stackRange(memory: &pages::Store, table: &capability::Table, top: u64) -> range::Range throws (abi::Error) {
173 +
    if top < 16 or (top & 15) <> 0 {
174 +
        throw abi::Error::InvalidArg;
175 +
    }
176 +
    for i in 0..limits::HANDLES {
177 +
        if table.slots[i].state <> slots::State::Live {
178 +
            continue;
179 +
        }
180 +
        let entry = table.entries[i];
181 +
        if entry.kind <> abi::Kind::Page or not abi::permits(entry.rights, abi::Rights(abi::WRITE)) {
182 +
            continue;
183 +
        }
184 +
        let page = try pages::get(memory, entry.object) catch {
185 +
            continue;
186 +
        };
187 +
        if page.base <= top - 16 and top - page.base <= page.count as u64 * limits::FRAME_SIZE {
188 +
            return range::Range { start: page.base, end: top };
189 +
        }
190 +
    }
191 +
    throw abi::Error::InvalidArg;
192 +
}
193 +
194 +
/// Reject physical overlap with any live context's reserved user stack.
195 +
fn stackAvailable(store: &Store, candidate: range::Range) throws (abi::Error) {
196 +
    for i in 0..limits::CONTEXTS {
197 +
        if store.contextSlots[i].state <> slots::State::Live {
198 +
            continue;
199 +
        }
200 +
        let used = store.contexts[i].userStack;
201 +
        if used.start < used.end and candidate.start < used.end and used.start < candidate.end {
202 +
            throw abi::Error::Busy;
203 +
        }
204 +
    }
205 +
}
206 +
113 207
/// Activate a pending domain after checking its stack and argument authority.
114 208
/// The caller serializes metadata until the initial frame and state are published.
115 209
export unsafe fn activate(store: &mut Store, memory: &pages::Store, table: &capability::Table,
116 210
    handle: abi::Handle, stack: u64, args: u64, size: u64) throws (abi::Error)
117 211
{
118 212
    let entry = try capability::lookup(table, handle, abi::Kind::Domain, abi::Rights(abi::EXECUTE));
119 213
    let mut domain = try get(store, entry.object);
120 214
    if domain.state <> Lifecycle::Pending {
121 215
        throw abi::Error::NotPending;
122 216
    }
123 -
    if (stack & 15) <> 0 or stack < 16
124 -
        or not pages::accessible(memory, &domain.memory.table, stack - 16, 16, abi::Rights(abi::WRITE)) {
125 -
        throw abi::Error::InvalidArg;
126 -
    }
217 +
    let userStack = try stackRange(memory, &domain.memory.table, stack);
127 218
    if size <> 0 and not pages::accessible(memory, &domain.memory.table, args, size, abi::Rights(abi::READ)) {
128 219
        throw abi::Error::InvalidArg;
129 220
    }
130 221
    assert slots::matches(&store.contextSlots[..], domain.initial, slots::State::Live);
222 +
    try stackAvailable(store, userStack);
131 223
    set domain.memory.env = abi::Env {
132 224
        argsPointer: args, argsSize: size, eventsHandle: *domain.events,
133 225
        eventsPointer: (&domain.memory.ring) as u64,
134 226
    };
135 227
    set store.contexts[domain.initial.index].frame.registers[2] = stack;
136 228
    set store.contexts[domain.initial.index].frame.registers[10] = (&domain.memory.env) as u64;
229 +
    set store.contexts[domain.initial.index].userStack = userStack;
137 230
    set store.records[entry.object.index].state = Lifecycle::Active;
138 231
}
139 232
233 +
/// Check an entry against the target's resident package graph exports.
234 +
unsafe fn exportedEntry(domain: &Domain, packages: &registry::Store, address: u64) -> bool {
235 +
    for i in 0..limits::PACKAGES {
236 +
        if domain.graph.table[i] == 0 or packages.slots[i].state <> slots::State::Live {
237 +
            continue;
238 +
        }
239 +
        let package = try! registry::get(packages, abi::Ref { index: i, generation: packages.slots[i].generation });
240 +
        if package.entry == address {
241 +
            return true;
242 +
        }
243 +
        for symbol in package.exports {
244 +
            match symbol.target {
245 +
                case shared::Target::Function(entry) => {
246 +
                    if entry == address {
247 +
                        return true;
248 +
                    }
249 +
                },
250 +
                else => {
251 +
                },
252 +
            }
253 +
        }
254 +
    }
255 +
    return false;
256 +
}
257 +
258 +
/// Create an additional context with private kernel storage and shared domain state.
259 +
/// The caller serializes domain, capability, and physical allocation metadata.
260 +
export unsafe fn contextCreate(store: &mut Store, memory: &mut pages::Store, packages: &registry::Store,
261 +
    table: &capability::Table, authority: abi::Handle, start: abi::ContextStart) -> abi::Ref throws (abi::Error)
262 +
{
263 +
    let permission = try capability::authority(table, authority, abi::Rights(abi::EXECUTE));
264 +
    let domain = try get(store, permission.object);
265 +
    if domain.state == Lifecycle::Dead {
266 +
        throw abi::Error::BadHandle;
267 +
    }
268 +
    if start.entry == 0 or not exportedEntry(&domain, packages, start.entry)
269 +
        or (start.size <> 0 and not pages::accessible(memory, &domain.memory.table, start.args, start.size, abi::Rights(abi::READ))) {
270 +
        throw abi::Error::InvalidArg;
271 +
    }
272 +
    let userStack = try stackRange(memory, &domain.memory.table, start.stack);
273 +
    try stackAvailable(store, userStack);
274 +
    let slot = try slots::reserve(&mut store.contextSlots[..]);
275 +
    let pending = try frames::reserve(&mut memory.backings.pool, KERNEL_STACK_PAGES) catch error {
276 +
        try! slots::cancel(&mut store.contextSlots[..], slot); throw error;
277 +
    };
278 +
    let kernelFrames = frames::commit(pending);
279 +
    let kernelStack = try! frames::extent(&memory.backings.pool, kernelFrames);
280 +
    zero(kernelStack.start, kernelFrames.count);
281 +
    let mut frame = trap::Frame { registers: [0; 32], pc: start.entry, status: 0x80, cause: 0, value: 0 };
282 +
    set frame.registers[1] = returnAddress();
283 +
    set frame.registers[2] = start.stack;
284 +
    set frame.registers[3] = domain.graph.table.ptr as u64;
285 +
    set frame.registers[10] = start.args;
286 +
    set frame.registers[11] = start.size;
287 +
    let object = slots::reference(&slot);
288 +
    set store.contexts[object.index] = Context {
289 +
        owner: permission.object, state: ContextState::Ready, hart: nil, kernelFrames, kernelStack, userStack, frame,
290 +
    };
291 +
    return try! slots::commit(&mut store.contextSlots[..], slot);
292 +
}
293 +
140 294
/// Memory allocated before the domain and its context become observable.
141 295
record Prepared: Copy {
142 296
    /// Frames for capabilities and the event ring.
143 297
    allocation: frames::Run,
144 298
    /// Mapped metadata at the allocation's start.
223 377
        kind: abi::Kind::Events, object, rights: abi::Rights(abi::READ | abi::WRITE),
224 378
    });
225 379
    let initial = slots::reference(&contextSlot);
226 380
    // MPIE enables interrupts after the first return into U-mode.
227 381
    let mut frame = trap::Frame { registers: [0; 32], pc: entry, status: 0x80, cause: 0, value: 0 };
382 +
    set frame.registers[1] = returnAddress();
228 383
    set frame.registers[3] = prepared.graph.table.ptr as u64;
229 384
    set store.contexts[initial.index] = Context {
230 -
        owner: object, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack, frame,
385 +
        owner: object, state: ContextState::Ready, hart: nil, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack,
386 +
        userStack: range::Range { start: 0, end: 0 }, frame,
231 387
    };
232 388
    set store.records[object.index] = Domain {
233 389
        state: Lifecycle::Pending, creator: table.owner, parent: table.owner, image: root, initial,
234 390
        allocation: prepared.allocation, memory: prepared.memory, graph: prepared.graph, events: queue,
235 391
    };
kernel/kernel/domains.ras +10 -0
1 1
//! Physical domain metadata views and complete frame clearing.
2 2
.text;
3 3
.export @kernel::domains::memory;
4 4
.export @kernel::domains::zero;
5 +
.export @kernel::domains::returnAddress;
6 +
7 +
// Native entries return to a gp-independent user call sequence.
8 +
@kernel::domains::returnAddress
9 +
    la %a0 @contextReturn;
10 +
    ret;
11 +
@contextReturn
12 +
    li %a7 64;
13 +
    ecall;
14 +
    ebreak;
5 15
6 16
// The allocated frame extent contains one complete aligned Memory record.
7 17
@kernel::domains::memory
8 18
    ret;
9 19
kernel/kernel/sys.rad +21 -0
122 122
        try! result(ecall(abi::Operation::QueryEvents as u32, *handle as i64, (&mut info) as i64, @sizeOf(abi::EventsInfo) as i64, 0));
123 123
        return info;
124 124
    }
125 125
}
126 126
127 +
/// Create an additional context with Execute authority over its domain.
128 +
export fn contextCreate(domain: abi::Handle, start: abi::ContextStart) -> u64 throws (abi::Error) {
129 +
    return try result(ecall(abi::Operation::ContextCreate as u32, *domain as i64,
130 +
        (&start) as i64, @sizeOf(abi::ContextStart) as i64, 0));
131 +
}
132 +
133 +
/// Reclaim an additional context that no hart is executing.
134 +
export fn contextDestroy(domain: abi::Handle, context: u64) throws (abi::Error) {
135 +
    try result(ecall(abi::Operation::ContextDestroy as u32, *domain as i64, context as i64, 0, 0));
136 +
}
137 +
138 +
/// Read a context's owner, stack, and state. Zero selects the initial context.
139 +
export fn queryContext(domain: abi::Handle, context: u64) -> abi::ContextInfo throws (abi::Error) {
140 +
    unsafe {
141 +
        let mut info: abi::ContextInfo = undefined;
142 +
        try result(ecall(abi::Operation::QueryContext as u32, *domain as i64, context as i64,
143 +
            (&mut info) as i64, @sizeOf(abi::ContextInfo) as i64));
144 +
        return info;
145 +
    }
146 +
}
147 +
127 148
/// Read the current extent and binding of a held CPU window.
128 149
export fn queryBudget(handle: abi::Handle) -> abi::BudgetInfo throws (abi::Error) {
129 150
    unsafe {
130 151
        let mut info: abi::BudgetInfo = undefined;
131 152
        try result(ecall(abi::Operation::QueryBudget as u32, *handle as i64, (&mut info) as i64, @sizeOf(abi::BudgetInfo) as i64, 0));
kernel/kernel/tests/budgets.rad +3 -1
132 132
    let child = slots::reference(&childSlot);
133 133
    capability::initialize(&mut MEMORY.table, child);
134 134
    let contextSlot = try! slots::reserve(&mut DOMAINS.contextSlots[..]);
135 135
    let context = slots::reference(&contextSlot);
136 136
    set DOMAINS.contexts[context.index] = domains::Context {
137 -
        owner: child, kernelFrames: frames::Run { first: 0, count: 0 }, kernelStack: range::Range { start: 0, end: 0 },
137 +
        owner: child, state: domains::ContextState::Ready, hart: nil,
138 +
        kernelFrames: frames::Run { first: 0, count: 0 }, kernelStack: range::Range { start: 0, end: 0 },
139 +
        userStack: range::Range { start: 0, end: 0 },
138 140
        frame: trap::Frame { registers: [0; 32], pc: 4, status: 0x80, cause: 0, value: 0 },
139 141
    };
140 142
    set DOMAINS.records[child.index] = domains::Domain {
141 143
        state: domains::Lifecycle::Pending, creator: root, parent: root,
142 144
        image: abi::Ref { index: 0, generation: 1 }, initial: context,
kernel/kernel/tests/calls.rad +20 -0
141 141
        try testing::expect(error == abi::Error::InvalidArg); set rejected += 1;
142 142
    };
143 143
    try testing::expect(rejected == 6 and RAM[OFFSET + 2] == 0x12345678);
144 144
}
145 145
146 +
/// Context calls validate descriptor storage and preserve capacity on failure.
147 +
@test unsafe fn contextBuffers() throws (testing::TestError) {
148 +
    let owner = initialize();
149 +
    let page = try! calls::invoke(owner, 30, &[0, 1, 0, 0], 0);
150 +
    let base = (&RAM[OFFSET]) as u64;
151 +
    let mut rejected: u32 = 0;
152 +
    for args in &[[0 as u64, 0, 32, 0], [0, base, 24, 0], [0, base, 32, 1], [0, base + 1, 32, 0]] {
153 +
        try calls::invoke(owner, 60, &args[..], 0) catch error {
154 +
            try testing::expect(error == abi::Error::InvalidArg); set rejected += 1;
155 +
        };
156 +
    }
157 +
    try calls::invoke(owner, 60, &[0, base, 32, 0], 0) catch error {
158 +
        try testing::expect(error == abi::Error::Denied); set rejected += 1;
159 +
    };
160 +
    try calls::invoke(owner, 62, &[0, 0, base, 32], 0) catch error {
161 +
        try testing::expect(error == abi::Error::BadHandle); set rejected += 1;
162 +
    };
163 +
    try testing::expect(rejected == 6 and domains::STORE.contextSlots[0].state == slots::State::Free);
164 +
}
165 +
146 166
/// An unknown user operation advances PC and returns only a negative a0 error.
147 167
@test unsafe fn trapReply() throws (testing::TestError) {
148 168
    let owner = initialize();
149 169
    let mut frame = trap::Frame { registers: [123; 32], pc: 0x80000000, status: 0x80, cause: 8, value: 0 };
150 170
    set frame.registers[17] = 0xffffffffffffffff;
kernel/kernel/tests/dispatch.rad +10 -0
29 29
    capability::initialize(&mut TABLE, owner);
30 30
    set DOMAINS.slots[0] = slots::Slot { generation: 1, state: slots::State::Live };
31 31
    set DOMAINS.records[0].state = domains::Lifecycle::Active;
32 32
    set DOMAINS.contextSlots[0] = slots::Slot { generation: 1, state: slots::State::Live };
33 33
    set DOMAINS.contexts[0].owner = owner;
34 +
    set DOMAINS.contexts[0].state = domains::ContextState::Ready;
35 +
    set DOMAINS.contexts[0].hart = nil;
34 36
    let first = try! budgets::seed(&mut BUDGETS, &mut TABLE, 0, 10, 40);
35 37
    let gap = try! budgets::split(&mut BUDGETS, &mut TABLE, first, 20, 0);
36 38
    let last = try! budgets::split(&mut BUDGETS, &mut TABLE, gap, 30, 0);
37 39
    let a = try! capability::get(&TABLE, first);
38 40
    let b = try! capability::get(&TABLE, last);
65 67
    try testing::expect(pending.context == nil);
66 68
    set DOMAINS.records[0].state = domains::Lifecycle::Active;
67 69
    set DOMAINS.contextSlots[0].generation = 2;
68 70
    let stale = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10);
69 71
    try testing::expect(stale.context == nil);
72 +
    set DOMAINS.contextSlots[0].generation = 1;
73 +
    set DOMAINS.contexts[0].state = domains::ContextState::Stopped;
74 +
    let stopped = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10);
75 +
    try testing::expect(stopped.context == nil);
70 76
}
71 77
72 78
/// Each online hart has a separate comparison word and shares one clock register.
73 79
@test unsafe fn timerRegisters() throws (testing::TestError) {
74 80
    set PLATFORM.harts = 0x81;
100 106
    let mut frame = trap::Frame { registers: [0; 32], pc: 0x40, status: 0x80, cause: 0, value: 0 };
101 107
    set DOMAINS.contexts[0].frame = frame;
102 108
    set DOMAINS.contexts[0].kernelStack = range::Range { start: 0x8000, end: 0xc000 };
103 109
    set DOMAINS.contextSlots[1] = slots::Slot { generation: 1, state: slots::State::Live };
104 110
    set DOMAINS.contexts[1].owner = TABLE.owner;
111 +
    set DOMAINS.contexts[1].state = domains::ContextState::Ready;
112 +
    set DOMAINS.contexts[1].hart = nil;
105 113
    set DOMAINS.contexts[1].frame = frame;
106 114
    set DOMAINS.contexts[1].kernelStack = range::Range { start: 0xc000, end: 0x10000 };
107 115
    set BUDGETS.windows[1].context = abi::Ref { index: 1, generation: 1 };
108 116
    let mut idle = frame;
109 117
    set idle.pc = 0x100;
117 125
        stackTop: 0x4000, stackBottom: 0x2000, kernelGp: 0x20000, handler,
118 126
        savedT0: 0, savedT1: 0, savedSp: 0,
119 127
    };
120 128
    let first = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 10);
121 129
    try testing::expect(frame.pc == 0x40 and anchor.stackTop == 0xc000);
130 +
    try testing::expect(DOMAINS.contexts[0].hart == 0);
122 131
    set frame.pc = 0x88;
123 132
    set frame.status = 0x1880;
124 133
    set frame.registers[2] = 0xb000;
125 134
    set frame.registers[5] = 43;
126 135
    let second = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 20);
127 136
    try testing::expect(anchor.stackBottom == 0xc000 and anchor.stackTop == 0x10000);
137 +
    try testing::expect(DOMAINS.contexts[0].hart == nil and DOMAINS.contexts[1].hart == 0);
128 138
    set frame.pc = 0x77;
129 139
    let resumed = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 30);
130 140
    try testing::expect(frame.pc == 0x88 and frame.status == 0x1880);
131 141
    try testing::expect(frame.registers[2] == 0xb000 and frame.registers[5] == 43);
132 142
    try testing::expect(DOMAINS.contexts[1].frame.pc == 0x77 and anchor.stackTop == 0xc000);
kernel/kernel/tests/domains.rad +105 -0
101 101
        try testing::expect(err == abi::Error::NotPending); set failures += 1;
102 102
    };
103 103
    try testing::expect(failures == 5);
104 104
}
105 105
106 +
/// Additional contexts share private package state and reserve distinct kernel stacks.
107 +
@test unsafe fn additionalContext() throws (testing::TestError) {
108 +
    initialize(abi::CREATE);
109 +
    slots::initialize(&mut PAGES.slots[..]);
110 +
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
111 +
    let authority = try! capability::get(&TABLE, handle);
112 +
    let mut child = try! domains::get(&DOMAINS, authority.object);
113 +
    // Move the fixture's physical allocation state into its Page store.
114 +
    set PAGES.backings = BACKINGS;
115 +
    let pending = try! slots::reserve(&mut PAGES.slots[..]);
116 +
    let object = slots::reference(&pending);
117 +
    set PAGES.records[object.index] = pages::Page {
118 +
        backing: object, base: 0x90000000, count: 2, origin: authority.object, handles: 1,
119 +
    };
120 +
    let published = try! slots::commit(&mut PAGES.slots[..], pending);
121 +
    let memory = try! capability::install(&mut child.memory.table, capability::Entry {
122 +
        kind: abi::Kind::Page, object: published, rights: abi::Rights(abi::READ | abi::WRITE),
123 +
    });
124 +
    let start = abi::ContextStart { entry: 0x80000000, stack: 0x90002000, args: 0x90000000, size: 16 };
125 +
    let count = PAGES.backings.pool.count;
126 +
    set PAGES.backings.pool.count = 0;
127 +
    let mut exhausted = false;
128 +
    try domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start) catch error {
129 +
        try testing::expect(error == abi::Error::OutOfMemory); set exhausted = true;
130 +
    };
131 +
    try testing::expect(exhausted and DOMAINS.contextSlots[child.initial.index + 1].state == slots::State::Free);
132 +
    set PAGES.backings.pool.count = count;
133 +
    let context = try! domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start);
134 +
    let created = try! domains::context(&DOMAINS, authority.object, context);
135 +
    let initial = DOMAINS.contexts[child.initial.index];
136 +
    try testing::expect(context <> child.initial and created.owner == authority.object);
137 +
    try testing::expect(created.frame.registers[3] == initial.frame.registers[3]);
138 +
    try testing::expect(created.kernelStack.start >= initial.kernelStack.end or created.kernelStack.end <= initial.kernelStack.start);
139 +
    try testing::expect(created.frame.pc == start.entry and created.frame.registers[2] == start.stack);
140 +
    try testing::expect(created.frame.registers[10] == start.args and created.frame.registers[11] == start.size);
141 +
    try testing::expect(created.userStack.start == 0x90000000 and created.userStack.end == start.stack);
142 +
    let mut invalid = start;
143 +
    set invalid.entry += 4;
144 +
    let mut rejected: u32 = 0;
145 +
    try domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, invalid) catch error {
146 +
        try testing::expect(error == abi::Error::InvalidArg); set rejected += 1;
147 +
    };
148 +
    set invalid = start;
149 +
    set invalid.stack -= 1;
150 +
    try domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, invalid) catch error {
151 +
        try testing::expect(error == abi::Error::InvalidArg); set rejected += 1;
152 +
    };
153 +
    set invalid = start;
154 +
    set invalid.size = 0xffffffffffffffff;
155 +
    try domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, invalid) catch error {
156 +
        try testing::expect(error == abi::Error::InvalidArg); set rejected += 1;
157 +
    };
158 +
    try testing::expect(rejected == 3 and DOMAINS.contextSlots[context.index + 1].state == slots::State::Free);
159 +
    try domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start) catch error {
160 +
        try testing::expect(error == abi::Error::Busy); set rejected += 1;
161 +
    };
162 +
    try testing::expect(rejected == 4 and DOMAINS.contextSlots[context.index + 1].state == slots::State::Free);
163 +
    try domains::activate(&mut DOMAINS, &PAGES, &TABLE, handle, start.stack, 0, 0) catch error {
164 +
        try testing::expect(error == abi::Error::Busy); set rejected += 1;
165 +
    };
166 +
    try testing::expect(rejected == 5 and DOMAINS.records[authority.object.index].state == domains::Lifecycle::Pending);
167 +
    let other = try! domains::create(&mut DOMAINS, &mut PAGES.backings, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
168 +
    let otherEntry = try! capability::get(&TABLE, other);
169 +
    let mut receiver = try! domains::get(&DOMAINS, otherEntry.object);
170 +
    let shared = try! capability::install(&mut receiver.memory.table, capability::Entry {
171 +
        kind: abi::Kind::Page, object: published, rights: abi::Rights(abi::WRITE),
172 +
    });
173 +
    try domains::activate(&mut DOMAINS, &PAGES, &TABLE, other, start.stack, 0, 0) catch error {
174 +
        try testing::expect(error == abi::Error::Busy); set rejected += 1;
175 +
    };
176 +
    try testing::expect(rejected == 6 and DOMAINS.records[otherEntry.object.index].state == domains::Lifecycle::Pending);
177 +
    try domains::context(&DOMAINS, otherEntry.object, context) catch error {
178 +
        try testing::expect(error == abi::Error::BadHandle); set rejected += 1;
179 +
    };
180 +
    try domains::context(&DOMAINS, authority.object, abi::Ref { index: context.index, generation: context.generation + 1 }) catch error {
181 +
        try testing::expect(error == abi::Error::BadHandle); set rejected += 1;
182 +
    };
183 +
    try testing::expect(rejected == 8);
184 +
    try domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, child.initial) catch error {
185 +
        try testing::expect(error == abi::Error::Denied); set rejected += 1;
186 +
    };
187 +
    set DOMAINS.contexts[context.index].hart = 0;
188 +
    try domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context) catch error {
189 +
        try testing::expect(error == abi::Error::Busy); set rejected += 1;
190 +
    };
191 +
    try! domains::returned(&mut DOMAINS, authority.object, context);
192 +
    try testing::expect(DOMAINS.contexts[context.index].state == domains::ContextState::Stopped);
193 +
    try testing::expect(DOMAINS.records[authority.object.index].state == domains::Lifecycle::Pending);
194 +
    set DOMAINS.contexts[context.index].hart = nil;
195 +
    try! domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context);
196 +
    for i in created.kernelFrames.first..created.kernelFrames.first + created.kernelFrames.count {
197 +
        try testing::expect(PAGES.backings.pool.free[i]);
198 +
    }
199 +
    try domains::context(&DOMAINS, authority.object, context) catch error {
200 +
        try testing::expect(error == abi::Error::BadHandle); set rejected += 1;
201 +
    };
202 +
    let next = try! domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start);
203 +
    try testing::expect(next.index == context.index and next.generation <> context.generation);
204 +
    try! domains::returned(&mut DOMAINS, authority.object, child.initial);
205 +
    try testing::expect(DOMAINS.records[authority.object.index].state == domains::Lifecycle::Dead);
206 +
    try testing::expect(DOMAINS.contexts[next.index].state == domains::ContextState::Stopped);
207 +
    try! domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, next);
208 +
    try testing::expect(rejected == 11);
209 +
}
210 +
106 211
/// Creation installs only Events in the child and preserves selected authorizing rights.
107 212
@test unsafe fn pending() throws (testing::TestError) {
108 213
    initialize(abi::CREATE | abi::ALLOCATE);
109 214
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
110 215
    let authority = try! capability::get(&TABLE, handle);
test/dispatch/kernel/dispatchcheck.rad +21 -4
60 60
        }
61 61
    }
62 62
    let cap = try! capability::get(&TABLE, HANDLES[0]);
63 63
    let mut user = try! domains::get(&domains::STORE, cap.object);
64 64
    let selfAuthority = try! capability::install(&mut user.memory.table, capability::Entry {
65 -
        kind: abi::Kind::Domain, object: cap.object, rights: abi::Rights(abi::CREATE | abi::ALLOCATE),
65 +
        kind: abi::Kind::Domain, object: cap.object, rights: abi::Rights(abi::CREATE | abi::ALLOCATE | abi::EXECUTE | abi::WAKE | abi::DESTROY),
66 66
    });
67 67
    let root = user.image;
68 68
    let executable = try! registry::install(&registry::STORE, &mut user.memory.table, root);
69 69
    let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1);
70 70
    let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut user.memory.table, stack, (abi::READ | abi::WRITE) as u64);
71 71
    let stackCap = try! capability::get(&TABLE, stack);
72 72
    let stackPage = try! pages::get(&pages::STORE, stackCap.object);
73 -
    let args = @sliceOf(memory(stackPage.base), 24);
73 +
    let args = @sliceOf(memory(stackPage.base), 32);
74 74
    let words = [*granted, *executable, *selfAuthority];
75 75
    for word in 0..3 {
76 76
        for byte in 0..8 {
77 77
            set args[word * 8 + byte] = (words[word] >> (byte as u64 * 8)) as u8;
78 78
        }
79 79
    }
80 -
    try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, HANDLES[0], stackPage.base + 4096, stackPage.base, 24);
80 +
    try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, HANDLES[0], stackPage.base + 4096, stackPage.base, 32);
81 81
    let imageRef = try! registry::image(&registry::STORE, &TABLE, image, abi::Rights(abi::EXECUTE));
82 82
    let target = try! registry::exported(&registry::STORE, imageRef, &"spin::count"[..]);
83 83
    let case shared::Target::Data(data) = target else panic "counter";
84 84
    let timer = try! dispatch::timer(&boot::PLATFORM, 0);
85 85
    let clock = dispatch::now(timer);
86 86
    let start = clock + 1000000;
87 -
    let span: u64 = 1000000;
87 +
    let span: u64 = 4000000;
88 88
    set domains::STORE.contexts[CONTEXTS[1].index].frame.pc = worker();
89 89
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[10] = user.graph.table[data.slot] + data.offset as u64;
90 90
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[11] = timer.clock;
91 91
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[12] = start + 3 * span;
92 92
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[13] = start + 4 * span;
105 105
            continue;
106 106
        }
107 107
        let owner = owners[i];
108 108
        let handle = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE,
109 109
            budgets::Binding { budget: windows[i], domain: HANDLES[owner], context: CONTEXTS[owner] }, clock);
110 +
        if i == 3 {
111 +
            for byte in 0..8 {
112 +
                set args[24 + byte] = (*handle >> (byte as u64 * 8)) as u8;
113 +
            }
114 +
        }
110 115
    }
111 116
}
112 117
113 118
/// Check state captured by production timer handling after both resumptions.
114 119
export unsafe fn verify() {
115 120
    let user = domains::STORE.contexts[CONTEXTS[0].index].frame;
116 121
    let machine = domains::STORE.contexts[CONTEXTS[1].index].frame;
117 122
    assert (user.status & 0x1800) == 0 and (machine.status & 0x1800) == 0x1800;
118 123
    assert machine.registers[8] > 0 and machine.registers[9] == 1 and machine.registers[18] > 0;
124 +
    let domain = try! domains::get(&domains::STORE, domains::STORE.contexts[CONTEXTS[0].index].owner);
125 +
    let target = try! registry::exported(&registry::STORE, domain.image, &"spin::extraCount"[..]);
126 +
    let case shared::Target::Data(data) = target else panic "extra counter";
127 +
    let bytes = @sliceOf(memory(domain.graph.table[data.slot] + data.offset as u64), 8);
128 +
    let mut progress: u64 = 0;
129 +
    for byte in 0..8 {
130 +
        set progress |= bytes[byte] as u64 << (byte as u64 * 8);
131 +
    }
132 +
    assert progress > 0;
133 +
    let checked = try! registry::exported(&registry::STORE, domain.image, &"spin::destroyed"[..]);
134 +
    let case shared::Target::Data(flag) = checked else panic "destruction flag";
135 +
    assert *memory(domain.graph.table[flag.slot] + flag.offset as u64) == 1;
119 136
    finish();
120 137
}
test/dispatch/machine.ras +21 -5
2 2
.text;
3 3
    call @kernel::boot::initialize;
4 4
    call @kernel::dispatchcheck::setup;
5 5
    call @kernel::boot::run;
6 6
@fail
7 +
    li %a0 1;
8 +
    j @failure;
9 +
@late
10 +
    li %a0 2;
11 +
    j @failure;
12 +
@noProgress
13 +
    li %a0 3;
14 +
    j @failure;
15 +
@noResumption
16 +
    li %a0 4;
17 +
    j @failure;
18 +
@stackCorruption
19 +
    li %a0 5;
20 +
@failure
7 21
    li %t0 0x10001000;
8 -
    li %t1 0x13333;
22 +
    slli %t1 %a0 16;
23 +
    li %t2 0x3333;
24 +
    or %t1 %t1 %t2;
9 25
    sw %t1 0(%t0);
10 26
.export @kernel::dispatchcheck::memory;
11 27
.export @kernel::dispatchcheck::kernelGp;
12 28
.export @kernel::dispatchcheck::worker;
13 29
.export @kernel::dispatchcheck::completion;
23 39
@kernel::dispatchcheck::completion
24 40
    la %a0 @complete;
25 41
    ret;
26 42
@machineWork
27 43
    ld %t0 0(%a1);
28 -
    bgeu %t0 %a2 @fail;
44 +
    bgeu %t0 %a2 @late;
29 45
    ld %s2 0(%a0);
30 -
    beqz %s2 @fail;
46 +
    beqz %s2 @noProgress;
31 47
    addi %sp %sp -16;
32 48
    li %s3 1234567;
33 49
    sd %s3 0(%sp);
34 50
    li %s0 0;
35 51
    li %s1 0;
36 52
@workLoop
37 53
    addi %s0 %s0 1;
38 54
    ld %t0 0(%sp);
39 -
    bne %t0 %s3 @fail;
55 +
    bne %t0 %s3 @stackCorruption;
40 56
    ld %t0 0(%a1);
41 57
    bltu %t0 %a3 @workLoop;
42 58
    ld %t0 0(%a0);
43 -
    bgeu %s2 %t0 @fail;
59 +
    bgeu %s2 %t0 @noResumption;
44 60
    li %s1 1;
45 61
    j @workLoop;
46 62
@complete
47 63
    call @kernel::dispatchcheck::verify;
48 64
    j @fail;
test/dispatch/run +1 -1
20 20
printf '];\n' >> "$work/kernel/dispatchinput.rad"
21 21
sh test/acceptance/compile "$emulator" "$work"
22 22
cat test/dispatch/machine.ras kernel/kernel/*.ras > "$work/dispatch.ras"
23 23
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 \
24 24
    -- "$work/std.ril" "$work/kernel.ril" "$work/dispatch.ras" "$work/dispatch.rv64"
25 -
"$emulator" -machine -memory-size=262144 -max-steps=80000000 -run "$work/dispatch.rv64"
25 +
"$emulator" -machine -memory-size=262144 -max-steps=200000000 -run "$work/dispatch.rv64"
26 26
printf 'dispatch: user calls, preemption, idle gap, and retained machine continuation passed\n'
test/dispatch/spin.rad +46 -1
9 9
    stack: u64,
10 10
    /// Local capability for the resident user package.
11 11
    image: u64,
12 12
    /// Local self-Domain capability with allocation authority.
13 13
    domain: u64,
14 +
    /// Future window that the user can partition between its contexts.
15 +
    future: u64,
14 16
}
15 17
16 18
/// Startup environment supplied by the kernel.
17 19
record Env: Copy {
18 20
    /// Readable startup capabilities.
26 28
}
27 29
/// Private query output used to check writable package-state authority.
28 30
static INFO: [u64; 2] = [0; 2];
29 31
/// Private progress counter observed by the machine fixture.
30 32
export static count: u64 = 0;
33 +
/// Additional-context progress in the same private package state.
34 +
export static extraCount: u64 = 0;
35 +
/// Successful user-side return and destruction checks.
36 +
export static destroyed: u64 = 0;
37 +
38 +
/// Update shared private state and return from an additional context.
39 +
export fn worker(args: *opaque, size: u64) {
40 +
    assert size == 0;
41 +
    set extraCount += 1;
42 +
    set count += 1;
43 +
}
31 44
/// Check direct calls, then consume each window without yielding.
32 45
@default unsafe fn main(env: *Env) {
33 -
    assert env.argsSize == 24;
46 +
    if env.argsSize == 0 {
47 +
        return;
48 +
    }
49 +
    assert env.argsSize == 32;
34 50
    let handle = env.argsPointer.stack as i64;
35 51
    let info = sys::queryPage(abi::Handle(handle as u64));
36 52
    assert info.base <> 0 and info.count == 1;
37 53
    let mut rejected = false;
38 54
    try sys::pageAllocate(abi::Handle(0), 0) catch error {
70 86
    assert rejected;
71 87
    let current = sys::currentContext();
72 88
    assert current.context <> 0 and current.hart == 0 and current.budget <> 0;
73 89
    let window = try! sys::queryBudget(abi::Handle(current.budget));
74 90
    assert window.context == current.context and window.hart == current.hart and window.start < window.end;
91 +
    let extraStack = try! sys::pageAllocate(abi::Handle(0), 1);
92 +
    let extraMemory = sys::queryPage(extraStack);
93 +
    let extra = try! sys::contextCreate(abi::Handle(env.argsPointer.domain), abi::ContextStart {
94 +
        entry: worker as u64, stack: extraMemory.base + 4096, args: 0, size: 0,
95 +
    });
96 +
    let context = try! sys::queryContext(abi::Handle(env.argsPointer.domain), extra);
97 +
    assert context.context == extra and context.domain == domain.id;
98 +
    assert context.stackBase == extraMemory.base and context.stackTop == extraMemory.base + 4096;
99 +
    let future = abi::Handle(env.argsPointer.future);
100 +
    let later = try! sys::queryBudget(future);
101 +
    let tail = try! sys::budgetSplit(future, later.start + (later.end - later.start) / 2);
102 +
    let childWindow = try! sys::budgetSplit(tail, later.start + (later.end - later.start) * 3 / 4);
103 +
    let resumption = try! sys::budgetSplit(childWindow, later.start + (later.end - later.start) * 7 / 8);
104 +
    let bound = try! sys::budgetBind(tail, abi::Handle(env.argsPointer.domain), extra);
105 +
    let initial = try! sys::queryContext(child, 0);
106 +
    let childBound = try! sys::budgetBind(childWindow, child, initial.context);
75 107
    let mut local: [u64; 2] = [0; 2];
76 108
    assert ecall(44, handle, (&mut local[0]) as i64, 16, 0) == 0;
77 109
    assert local[0] <> 0 and local[1] == 1;
78 110
    assert ecall(44, handle, (&mut INFO[0]) as i64, 16, 0) == 0;
79 111
    assert INFO[0] == local[0] and INFO[1] == local[1];
80 112
    assert ecall(44, handle, 0, 16, 0) == -4;
81 113
    assert ecall(0xffffffff, 0, 0, 0, 0) == -4;
114 +
    while extraCount == 0 {
115 +
        set count += 1;
116 +
    }
117 +
    let stopped = try! sys::queryContext(abi::Handle(env.argsPointer.domain), extra);
118 +
    assert stopped.state == 1 and stopped.hart == 0xffffffffffffffff;
119 +
    try! sys::contextDestroy(abi::Handle(env.argsPointer.domain), extra);
120 +
    set rejected = false;
121 +
    try sys::queryContext(abi::Handle(env.argsPointer.domain), extra) catch error {
122 +
        assert error == abi::Error::BadHandle;
123 +
        set rejected = true;
124 +
    };
125 +
    assert rejected and sys::queryDomain(child).state == 2;
126 +
    set destroyed = 1;
82 127
    while true {
83 128
        set count += 1;
84 129
    }
85 130
}