kernel: Complete domain teardown and parenting

5e7ead57981261d3095812ae7f65daf329fb70fd5cf0666c09501552b7e6c22c
Alexis Sellier committed ago 1 parent 92262c08
Makefile +10 -1
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 dispatch-test smp-test
26 +
test: emulator seed-test std-test bin-test kernel-test module-test package-test native-test shared-test sync-test kernel-boot-test trap-test page-test loader-test dispatch-test smp-test termination-test
27 27
28 28
seed-test:
29 29
	@seed/test
30 30
31 +
module-test: emulator $(RAD_BIN)
32 +
	@RAD_EMULATOR="$(EMU)" sh test/modules/run
33 +
31 34
# Emulator command check
32 35
33 36
emulator:
34 37
ifeq ($(EMU_PATH),)
35 38
	$(error Emulator not found. Install it or set RAD_EMULATOR to its path)
211 214
212 215
# Concurrent user contexts and metadata transactions on every online hart.
213 216
.PHONY: smp-test
214 217
smp-test: $(RAD_BIN) $(BIN_DIR)/kernel.build.rv64
215 218
	@RAD_EMULATOR="$(EMU)" sh test/dispatch/run smp "1 2 8" 7
219 +
220 +
# User exit, abort, and CPU faults preserve the surviving parent's execution.
221 +
termination-test: emulator $(RAD_BIN) $(BIN_DIR)/kernel.build.rv64
222 +
	@RAD_EMULATOR="$(EMU)" sh test/dispatch/run termination
223 +
224 +
.PHONY: termination-test
compiler/radiance.rad +2 -4
26 26
use std::sys;
27 27
use std::sys::unix;
28 28
use std::collections::dict;
29 29
30 30
/// Maximum number of modules we can load per package.
31 -
constant MAX_LOADED_MODULES: u32 = module::MAX_MODULES;
31 +
constant MAX_LOADED_MODULES: u32 = 128;
32 32
/// Maximum number of packages we can compile.
33 33
constant MAX_PACKAGES: u32 = 4;
34 -
/// Total module entries across all packages.
35 -
constant MAX_TOTAL_MODULES: u32 = 192;
36 34
/// Source code buffer arena (4 MB).
37 35
constant MAX_SOURCES_SIZE: u32 = 4194304;
38 36
/// Maximum number of test functions we can discover.
39 37
constant MAX_TESTS: u32 = 1024;
40 38
/// Maximum number of assembly source paths we can load per package.
56 54
static MAIN_ARENA: [u8; MAIN_ARENA_SIZE] = [0; MAIN_ARENA_SIZE];
57 55
58 56
/// Module source code.
59 57
static MODULE_SOURCES: [u8; MAX_SOURCES_SIZE] = [0; MAX_SOURCES_SIZE];
60 58
/// Module entries for all packages.
61 -
unsafe static MODULE_ENTRIES: [module::ModuleEntry; MAX_TOTAL_MODULES] = undefined;
59 +
unsafe static MODULE_ENTRIES: [module::ModuleEntry; module::MAX_MODULES] = undefined;
62 60
/// String pool.
63 61
unsafe static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 };
64 62
65 63
/// Package scope.
66 64
unsafe static RESOLVER_PKG_SCOPE: resolver::Scope = undefined;
kernel/kernel.rad +1 -0
20 20
export mod backing;
21 21
export mod pages;
22 22
export mod loader;
23 23
export mod instances;
24 24
export mod domains;
25 +
export mod lifecycle;
25 26
export mod budgets;
26 27
export mod dispatch;
27 28
export mod timers;
28 29
export mod remote;
29 30
export mod calls;
kernel/kernel/abi.rad +2 -0
133 133
export constant DESTROY: u16 = 256;
134 134
/// All defined right bits.
135 135
export constant RIGHTS_MASK: u16 = 511;
136 136
/// Cascade destruction through creation ancestry.
137 137
export constant CASCADE: u64 = 1;
138 +
/// Fault code for explicit user Abort, separate from CPU exception codes.
139 +
export constant FAULT_ABORT: u32 = 256;
138 140
139 141
/// Object kind stored in the high four handle bits.
140 142
export union Kind: Copy {
141 143
    /// Unoccupied table entry.
142 144
    Empty = 0,
kernel/kernel/backing.rad +43 -10
27 27
    pool: frames::Pool,
28 28
    /// Backing-record lifetimes.
29 29
    slots: [slots::Slot; limits::ALLOCATIONS],
30 30
    /// Payloads for live backing records.
31 31
    records: [Allocation; limits::ALLOCATIONS],
32 +
    /// Exclusive upper bound of backing slots that have been published.
33 +
    used: u32,
32 34
    /// Live domain generation, or zero after all of that domain's exposures end.
33 35
    domains: [u32; limits::DOMAINS],
36 +
    /// Zero while open; otherwise one plus the next exposure slot to inspect.
37 +
    ending: [u32; limits::DOMAINS],
34 38
}
35 39
36 40
/// Initialize fresh backing and exposure-domain tables.
37 41
export fn initialize(store: &mut Store) {
42 +
    set store.used = 0;
38 43
    set store.pool.count = 0;
44 +
    set store.pool.retiredHead = 0;
45 +
    set store.pool.retiredCount = 0;
39 46
    slots::initialize(&mut store.slots[..]);
40 -
    for i in 0..limits::DOMAINS { set store.domains[i] = 0; }
47 +
    for i in 0..limits::DOMAINS { set store.domains[i] = 0; set store.ending[i] = 0; }
41 48
}
42 49
43 50
/// Register a domain before it can receive allocation exposure.
44 51
export fn registerDomain(store: &mut Store, domain: abi::Ref) throws (abi::Error) {
45 52
    if domain.index >= limits::DOMAINS or domain.generation == 0 { throw abi::Error::InvalidArg; }
47 54
    set store.domains[domain.index] = domain.generation;
48 55
}
49 56
50 57
/// Test exposure-domain generation without reading beyond the fixed table.
51 58
export fn domainLive(store: &Store, domain: abi::Ref) -> bool {
52 -
    return domain.index < limits::DOMAINS and domain.generation <> 0 and store.domains[domain.index] == domain.generation;
59 +
    return domain.index < limits::DOMAINS and domain.generation <> 0 and store.domains[domain.index] == domain.generation and store.ending[domain.index] == 0;
53 60
}
54 61
55 62
/// Publish reserved metadata with one page object and its live origin exposure.
56 63
/// The caller owns the committed run and has validated the origin domain.
57 64
export unsafe fn publish(store: &mut Store, reservation: slots::Reservation, run: frames::Run, origin: abi::Ref) -> abi::Ref {
58 65
    assert domainLive(store, origin);
59 66
    let object = slots::reference(&reservation);
60 67
    assert slots::matches(&store.slots[..], object, slots::State::Reserved);
68 +
    if object.index >= store.used { set store.used = object.index + 1; }
61 69
    set store.records[object.index].run = run;
62 70
    set store.records[object.index].pages = 1;
63 71
    set store.records[object.index].exposed = 1;
64 72
    let mut bits = bitset::init(&mut store.records[object.index].exposures[..]);
65 73
    bitset::put(&mut bits, origin.index);
88 96
    bitset::put(&mut bits, domain.index);
89 97
    set store.records[object.index].exposed += 1;
90 98
}
91 99
92 100
/// Reclaim only when no page object and no domain exposure remains.
93 -
fn reclaim(store: &mut Store, object: abi::Ref) {
101 +
fn reclaim(store: &mut Store, object: abi::Ref, release: fn(&mut frames::Pool, frames::Run) throws (abi::Error)) {
94 102
    if store.records[object.index].pages <> 0 or store.records[object.index].exposed <> 0 { return; }
95 103
    let run = store.records[object.index].run;
96 -
    try! frames::release(&mut store.pool, run);
104 +
    try! release(&mut store.pool, run);
97 105
    try! slots::release(&mut store.slots[..], object);
98 106
}
99 107
100 108
/// Release one page object after its final handle disappears.
101 109
export fn releasePage(store: &mut Store, object: abi::Ref) throws (abi::Error) {
102 110
    try require(store, object);
103 111
    if store.records[object.index].pages == 0 { throw abi::Error::BadHandle; }
104 112
    set store.records[object.index].pages -= 1;
105 -
    reclaim(store, object);
113 +
    reclaim(store, object, frames::release);
106 114
}
107 115
108 -
/// End all exposure for one domain before its slot can be registered again.
109 -
/// Callers serialize this bounded scan with allocation and capability changes.
116 +
/// End all exposure while the caller has exclusive access to metadata.
110 117
export unsafe fn endDomain(store: &mut Store, domain: abi::Ref) throws (abi::Error) {
111 -
    if not domainLive(store, domain) { throw abi::Error::BadHandle; }
112 -
    for i in 0..limits::ALLOCATIONS {
118 +
    assert try end(store, domain, limits::ALLOCATIONS, frames::release);
119 +
}
120 +
121 +
/// Inspect at most limit backing slots and retire allocations whose lifetimes end.
122 +
/// Returns true after all exposure is clear and the domain slot can be registered again.
123 +
export unsafe fn endDomainStep(store: &mut Store, domain: abi::Ref, limit: u32) -> bool throws (abi::Error) {
124 +
    return try end(store, domain, limit, frames::retire);
125 +
}
126 +
127 +
/// Close exposure admission and advance one generation's retained scan cursor.
128 +
unsafe fn end(store: &mut Store, domain: abi::Ref, limit: u32,
129 +
    release: fn(&mut frames::Pool, frames::Run) throws (abi::Error)) -> bool throws (abi::Error)
130 +
{
131 +
    if domain.index >= limits::DOMAINS or domain.generation == 0
132 +
        or store.domains[domain.index] <> domain.generation { throw abi::Error::BadHandle; }
133 +
    if limit == 0 { throw abi::Error::InvalidArg; }
134 +
    if store.ending[domain.index] == 0 { set store.ending[domain.index] = 1; }
135 +
    let first = store.ending[domain.index] - 1;
136 +
    let mut count = store.used - first;
137 +
    if count > limit { set count = limit; }
138 +
    let next = first + count;
139 +
    for i in first..next {
113 140
        if store.slots[i].state <> slots::State::Live { continue; }
114 141
        let mut bits = bitset::new(&mut store.records[i].exposures[..]);
115 142
        if not bitset::contains(&bits, domain.index) { continue; }
116 143
        bitset::clear(&mut bits, domain.index);
117 144
        set store.records[i].exposed -= 1;
118 -
        reclaim(store, abi::Ref { index: i, generation: store.slots[i].generation });
145 +
        reclaim(store, abi::Ref { index: i, generation: store.slots[i].generation }, release);
146 +
    }
147 +
    if next < store.used {
148 +
        set store.ending[domain.index] = next + 1;
149 +
        return false;
119 150
    }
120 151
    set store.domains[domain.index] = 0;
152 +
    set store.ending[domain.index] = 0;
153 +
    return true;
121 154
}
kernel/kernel/boot.rad +6 -3
7 7
use super::sync;
8 8
use super::trap;
9 9
use super::pages;
10 10
use super::registry;
11 11
use super::domains;
12 +
use super::lifecycle;
12 13
use super::budgets;
13 14
use super::dispatch;
14 15
use super::timers;
15 16
use super::remote;
16 17
use super::interrupts;
24 25
/// Number of harts that validated their machine stack.
25 26
static ARRIVED: u64 = 0;
26 27
/// Trap anchors owned by their physical harts.
27 28
unsafe static HARTS: [trap::Hart; limits::HARTS] = undefined;
28 29
29 -
/// Handle timer boundaries and user calls, and stop on unsupported traps.
30 +
/// Dispatch interrupts, user calls, and user faults; halt on kernel faults.
30 31
unsafe fn unexpected(frame: &mut trap::Frame, hart: &mut trap::Hart) {
31 32
    let cause = trap::classify(frame.cause);
32 33
    if cause == trap::Cause::Timer or cause == trap::Cause::Software or cause == trap::Cause::External {
33 34
        dispatch::interrupt(frame, hart);
34 -
        return;
35 35
    }
36 36
    if cause == trap::Cause::UserCall and trap::fromUser(frame) {
37 37
        dispatch::call(frame, hart);
38 -
        return;
38 +
    }
39 +
    if trap::fromUser(frame) {
40 +
        dispatch::fault(frame, hart);
39 41
    }
40 42
    print("kernel: unexpected trap\n");
41 43
    trap::halt();
42 44
}
43 45
79 81
        let treeRange = range::new(treeAddress, size as u64) else panic "FDT range";
80 82
        assert platform::inRam(&PLATFORM, treeRange);
81 83
        try! platform::protect(&mut PLATFORM, treeRange);
82 84
        try! pages::initialize(&mut pages::STORE, &PLATFORM);
83 85
        domains::initialize(&mut domains::STORE);
86 +
        lifecycle::initialize(&mut lifecycle::CALLS);
84 87
        budgets::initialize(&mut budgets::STORE);
85 88
        timers::initialize(&mut timers::STORE);
86 89
        remote::initialize(&mut remote::STORE, PLATFORM.harts);
87 90
        interrupts::initialize(&mut interrupts::STORE, PLATFORM.irqSources);
88 91
        try! plic::initialize(&PLATFORM);
kernel/kernel/budgets.rad +2 -1
234 234
    let object = try owned(store, table, handle, abi::TRANSFER);
235 235
    let window = try get(store, object);
236 236
    if window.hart as u64 <> current.hart or window.context <> abi::reference(current.context)
237 237
        or now < window.start or now >= window.end { throw abi::Error::Busy; }
238 238
    let permit = try capability::lookup(table, target, abi::Kind::Domain, abi::Rights(abi::WAKE));
239 -
    let receiver = try domains::get(domainStore, permit.object) catch { throw abi::Error::Busy; };
239 +
    let receiver = try domains::get(domainStore, permit.object);
240 +
    if receiver.state == domains::Lifecycle::Dead { throw abi::Error::BadHandle; }
240 241
    if receiver.state <> domains::Lifecycle::Active or abi::id(receiver.initial) == current.context { throw abi::Error::Busy; }
241 242
    let context = try domains::context(domainStore, permit.object, receiver.initial) catch { throw abi::Error::Busy; };
242 243
    if context.state <> domains::ContextState::Ready or context.hart <> nil { throw abi::Error::Busy; }
243 244
    let result = try bind(store, domainStore, table, Binding { budget: handle, domain: target, context: receiver.initial }, now) catch error {
244 245
        if error == abi::Error::InvalidArg { throw abi::Error::Busy; }
kernel/kernel/calls.rad +114 -32
16 16
use super::timers;
17 17
use super::sync;
18 18
use super::remote;
19 19
use super::interrupts;
20 20
use super::plic;
21 +
use super::lifecycle;
21 22
22 23
/// Resolve the owning hart for an operation that can change CPU authority.
23 24
unsafe fn budgetHart(table: &capability::Table, operation: u64, arguments: &[u64]) -> ?u32 throws (abi::Error) {
24 25
    match operation {
25 26
        case 11, 12, 70, 71, 73 => {},
45 46
}
46 47
47 48
/// Validate resident object generations for capabilities without page ownership.
48 49
unsafe fn resident(entry: capability::Entry) throws (abi::Error) {
49 50
    match entry.kind {
50 -
        case abi::Kind::Domain, abi::Kind::Events => { let object = try domains::get(&domains::STORE, entry.object); },
51 +
        case abi::Kind::Domain, abi::Kind::Events => {
52 +
            let object = try domains::get(&domains::STORE, entry.object);
53 +
            if object.state == domains::Lifecycle::Dead { throw abi::Error::BadHandle; }
54 +
        },
51 55
        case abi::Kind::Image => { let object = try registry::get(&registry::STORE, entry.object); },
52 56
        else => throw abi::Error::BadHandle,
53 57
    }
54 58
}
55 59
60 +
/// Reject expired domain authority before an allocation can reserve resources.
61 +
unsafe fn allocationAuthority(table: &capability::Table, operation: u64, handle: abi::Handle) throws (abi::Error) {
62 +
    if operation <> 20 and operation <> 30 and operation <> 51 { return; }
63 +
    let rights = abi::CREATE if operation == 20 else abi::ALLOCATE;
64 +
    try resident(try capability::authority(table, handle, abi::Rights(rights)));
65 +
}
66 +
56 67
/// Grant or transfer authority while both domain tables remain serialized.
57 68
unsafe fn delegate(domain: &mut domains::Domain, handle: abi::Handle, target: abi::Handle, rights: u64, moving: bool, now: u64)
58 69
    -> abi::Handle throws (abi::Error)
59 70
{
60 71
    let owner = domain.memory.table.owner;
140 151
    }
141 152
    let output = @sliceOf(memory(arguments[1]), words.len);
142 153
    for i in 0..words.len { set output[i] = words[i]; }
143 154
}
144 155
156 +
/// Validate a termination call and return harts that must release stopped contexts.
157 +
export unsafe fn termination(owner: abi::Ref, operation: u64, arguments: &[u64]) -> u32 throws (abi::Error) {
158 +
    assert arguments.len == 4;
159 +
    let domain = try domains::get(&domains::STORE, owner);
160 +
    if domain.state <> domains::Lifecycle::Active { throw abi::Error::BadHandle; }
161 +
    if arguments[2] <> 0 or arguments[3] <> 0 { throw abi::Error::InvalidArg; }
162 +
    match operation {
163 +
        case 49 => {
164 +
            if arguments[0] > 0xffffffff or arguments[1] <> 0 { throw abi::Error::InvalidArg; }
165 +
            return try domains::terminate(&mut domains::STORE, owner, events::CHILD_EXIT, arguments[0] as u32);
166 +
        },
167 +
        case 43 => {
168 +
            if arguments[0] <> 0 or arguments[1] <> 0 { throw abi::Error::InvalidArg; }
169 +
            return try domains::terminate(&mut domains::STORE, owner, events::FAULT, abi::FAULT_ABORT);
170 +
        },
171 +
        case 22 => {
172 +
            let flags = arguments[1];
173 +
            if (flags & ~abi::CASCADE) <> 0 or (arguments[0] == 0 and flags <> 0) { throw abi::Error::InvalidArg; }
174 +
            let mut target = owner;
175 +
            if arguments[0] <> 0 {
176 +
                let entry = try capability::lookup(&domain.memory.table, abi::Handle(arguments[0]), abi::Kind::Domain, abi::Rights(abi::DESTROY));
177 +
                set target = entry.object;
178 +
                if (try domains::get(&domains::STORE, target)).state == domains::Lifecycle::Dead { throw abi::Error::BadHandle; }
179 +
            }
180 +
            if flags == abi::CASCADE { return try domains::terminateTree(&mut domains::STORE, target, events::CHILD_EXIT, 0); }
181 +
            return try domains::terminate(&mut domains::STORE, target, events::CHILD_EXIT, 0);
182 +
        },
183 +
        else => throw abi::Error::InvalidArg,
184 +
    }
185 +
}
186 +
145 187
/// Execute one operation with domain and resource metadata serialized.
146 188
export unsafe fn invoke(owner: abi::Ref, operation: u64, arguments: &[u64], now: u64) -> u64 throws (abi::Error) {
147 189
    assert arguments.len == 4;
148 190
    let mut domain = try domains::get(&domains::STORE, owner);
149 191
    let handle = abi::Handle(arguments[0]);
192 +
    try allocationAuthority(&domain.memory.table, operation, handle);
150 193
    match operation {
194 +
        case 22, 43, 49 => { let stopped = try termination(owner, operation, arguments); return 0; },
151 195
        case 10, 11 => return *try delegate(&mut domain, handle, abi::Handle(arguments[1]), arguments[2], operation == 11, now),
152 196
        case 12 => { try drop(&mut domain, handle); return 0; },
153 197
        case 20 => return *try domains::create(&mut domains::STORE, &mut pages::STORE.backings,
154 198
            &registry::STORE, &mut domain.memory.table, handle, abi::Handle(arguments[1])),
155 199
        case 21 => {
156 200
            try domains::activate(&mut domains::STORE, &pages::STORE, &domain.memory.table,
157 201
                handle, arguments[1], arguments[2], arguments[3]);
158 202
            return 0;
159 203
        },
204 +
        case 23 => {
205 +
            if arguments[2] <> 0 or arguments[3] <> 0 { throw abi::Error::InvalidArg; }
206 +
            try domains::reparent(&mut domains::STORE, &domain.memory.table, handle, abi::Handle(arguments[1]));
207 +
            return 0;
208 +
        },
160 209
        case 30 => return *try pages::allocate(&mut pages::STORE, &mut domain.memory.table, handle, arguments[1]),
161 210
        case 31 => return *try pages::split(&mut pages::STORE, &mut domain.memory.table, handle, arguments[1]),
162 211
        case 40 => {
163 212
            if arguments[1] <> 0 or arguments[2] <> 0 or arguments[3] <> 0 { throw abi::Error::InvalidArg; }
164 213
            try budgets::handoff(&mut budgets::STORE, &domains::STORE, &mut domain.memory.table, dispatch::current(), handle, now);
190 239
            return 0;
191 240
        },
192 241
        case 47 => {
193 242
            let entry = try capability::lookup(&domain.memory.table, handle, abi::Kind::Domain, abi::Rights(0));
194 243
            let target = try domains::get(&domains::STORE, entry.object);
244 +
            if target.state == domains::Lifecycle::Dead { throw abi::Error::BadHandle; }
195 245
            let mut parent: u64 = 0;
196 246
            if let object = target.parent { set parent = abi::id(object); }
197 247
            try reply(&domain, arguments, &[abi::id(entry.object), abi::id(target.creator), parent, target.state as u64]);
198 248
            return 0;
199 249
        },
200 250
        case 48 => {
201 251
            let entry = try capability::lookup(&domain.memory.table, handle, abi::Kind::Events, abi::Rights(0));
202 252
            let target = try domains::get(&domains::STORE, entry.object);
253 +
            if target.state == domains::Lifecycle::Dead { throw abi::Error::BadHandle; }
203 254
            try reply(&domain, arguments, &[events::CAPACITY as u64]);
204 255
            return 0;
205 256
        },
206 257
        case 51 => return *try loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut domain.memory.table,
207 258
            loader::Request { authority: handle, source: abi::Handle(arguments[1]), offset: arguments[2], length: arguments[3] }),
235 286
            return 0;
236 287
        },
237 288
        case 62 => {
238 289
            let permission = try capability::authority(&domain.memory.table, handle, abi::Rights(0));
239 290
            let target = try domains::get(&domains::STORE, permission.object);
291 +
            if target.state == domains::Lifecycle::Dead { throw abi::Error::BadHandle; }
240 292
            let mut object = target.initial;
241 293
            if arguments[1] <> 0 { set object = abi::reference(arguments[1]); }
242 294
            let context = try domains::context(&domains::STORE, permission.object, object);
243 295
            let mut hart: u64 = 0xffffffffffffffff;
244 296
            if let id = context.hart { set hart = id as u64; }
276 328
/// Load a package with metadata locked only around resource transactions.
277 329
unsafe fn runtimeLoad(domain: &mut domains::Domain, request: loader::Request, guard: sync::Guard) -> u64 throws (abi::Error) {
278 330
    let owner = domain.memory.table.owner;
279 331
    let pending = try loader::reserve(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, request)
280 332
        catch error { sync::leave(guard); throw error; };
333 +
    let context = abi::reference(dispatch::current().context);
334 +
    let lease = lifecycle::holdLoad(&mut lifecycle::CALLS, &mut domains::STORE, context, pending);
335 +
    let retained = lifecycle::load(&lifecycle::CALLS, &lease);
281 336
    sync::leave(guard);
282 -
    let input = try loader::decode(&pending) catch error {
337 +
    let input = try loader::decodeInput(&retained.input) catch error {
283 338
        let guard = sync::enter();
284 -
        loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending);
339 +
        lifecycle::cancelLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE,
340 +
            &mut loader::STATE, &mut registry::STORE, lease);
285 341
        sync::leave(guard); throw error;
286 342
    };
287 -
    let existing = try loader::identify(&registry::STORE, &pending, &input) catch error {
343 +
    let existing = try loader::identifyInput(&registry::STORE, &retained.input, &input) catch error {
288 344
        let guard = sync::enter();
289 -
        loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending);
345 +
        lifecycle::cancelLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE,
346 +
            &mut loader::STATE, &mut registry::STORE, lease);
290 347
        sync::leave(guard); throw error;
291 348
    };
292 349
    let allocationGuard = sync::enter();
293 350
    if not live(owner) {
294 -
        loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending);
351 +
        lifecycle::cancelLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE,
352 +
            &mut loader::STATE, &mut registry::STORE, lease);
295 353
        sync::leave(allocationGuard); throw abi::Error::BadHandle;
296 354
    }
297 355
    if let object = existing {
298 -
        let result = try loader::finish(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending, object)
356 +
        let result = try lifecycle::finishLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE,
357 +
            &mut loader::STATE, &mut registry::STORE, lease, lifecycle::LoadResult::Existing(object))
299 358
            catch error { sync::leave(allocationGuard); throw error; };
300 359
        sync::leave(allocationGuard); return *result;
301 360
    }
302 -
    let output = try loader::reserveOutput(&mut pages::STORE.backings.pool, &mut registry::STORE) catch error {
303 -
        loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending);
361 +
    try lifecycle::reserveOutput(&mut lifecycle::CALLS, &lease, &mut pages::STORE, &mut registry::STORE) catch error {
362 +
        lifecycle::cancelLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE,
363 +
            &mut loader::STATE, &mut registry::STORE, lease);
304 364
        sync::leave(allocationGuard); throw error;
305 365
    };
366 +
    let compiling = lifecycle::load(&lifecycle::CALLS, &lease);
367 +
    let output = compiling.output else panic "missing loader output";
306 368
    sync::leave(allocationGuard);
307 -
    let compiled = try loader::compile(&registry::STORE, &pending, &input, &output) catch error {
369 +
    let compiled = try loader::compileInput(&registry::STORE, &compiling.input, &input, &output) catch error {
308 370
        let guard = sync::enter();
309 -
        loader::cancelOutput(&mut pages::STORE.backings.pool, &mut registry::STORE, output);
310 -
        loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending);
371 +
        lifecycle::cancelLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE,
372 +
            &mut loader::STATE, &mut registry::STORE, lease);
311 373
        sync::leave(guard); throw error;
312 374
    };
313 375
    let publicationGuard = sync::enter();
314 -
    if not live(owner) {
315 -
        loader::cancelOutput(&mut pages::STORE.backings.pool, &mut registry::STORE, output);
316 -
        loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending);
317 -
        sync::leave(publicationGuard); throw abi::Error::BadHandle;
318 -
    }
319 -
    let object = loader::publishOutput(&mut loader::STATE, &mut pages::STORE.backings.pool, &mut registry::STORE, output, compiled);
320 -
    let result = try loader::finish(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending, object)
376 +
    let result = try lifecycle::finishLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE,
377 +
        &mut loader::STATE, &mut registry::STORE, lease, lifecycle::LoadResult::Compiled(compiled))
321 378
        catch error { sync::leave(publicationGuard); throw error; };
322 379
    sync::leave(publicationGuard);
323 380
    return *result;
324 381
}
325 382
326 383
/// Execute a call with short metadata transactions and private bulk initialization.
327 384
/// Context ownership and pending calls retain storage across dispatch boundaries.
328 385
export unsafe fn synchronized(owner: abi::Ref, operation: u64, arguments: &[u64], now: u64) -> u64 throws (abi::Error) {
329 386
    assert arguments.len == 4;
330 -
    if operation == 21 or operation == 50 { return try runtimeReady(owner, operation, arguments, now); }
387 +
    if operation == 21 or operation == 50 { return try runtimeReady(owner, operation, arguments); }
331 388
    let guard = sync::enter();
332 389
    let mut domain = try domains::get(&domains::STORE, owner) catch error { sync::leave(guard); throw error; };
333 390
    if domain.state <> domains::Lifecycle::Active { sync::leave(guard); throw abi::Error::BadHandle; }
391 +
    try allocationAuthority(&domain.memory.table, operation, abi::Handle(arguments[0])) catch error {
392 +
        sync::leave(guard); throw error;
393 +
    };
394 +
    if operation == 22 or operation == 43 or operation == 49 {
395 +
        let stopped = try termination(owner, operation, arguments) catch error { sync::leave(guard); throw error; };
396 +
        dispatch::reschedule(stopped);
397 +
        sync::leave(guard); return 0;
398 +
    }
334 399
    let target = try budgetHart(&domain.memory.table, operation, arguments) catch error { sync::leave(guard); throw error; };
335 400
    if let destination = target {
336 401
        let source = dispatch::hart();
337 402
        if destination <> source {
338 403
            let call = remote::Call { owner, context: abi::reference(dispatch::current().context), operation,
349 414
    let handle = abi::Handle(arguments[0]);
350 415
    match operation {
351 416
        case 20 => {
352 417
            let pending = try domains::reserve(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE,
353 418
                &mut domain.memory.table, handle, abi::Handle(arguments[1])) catch error { sync::leave(guard); throw error; };
419 +
            let context = abi::reference(dispatch::current().context);
420 +
            let lease = lifecycle::holdDomain(&mut lifecycle::CALLS, &mut domains::STORE, context, pending);
354 421
            sync::leave(guard);
355 -
            try domains::prepare(&registry::STORE, &pending) catch error {
422 +
            try lifecycle::prepareDomain(&lifecycle::CALLS, &registry::STORE, &lease) catch error {
356 423
                let guard = sync::enter();
357 -
                domains::cancel(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, pending);
424 +
                let failed = lifecycle::takeDomain(&mut lifecycle::CALLS, &mut domains::STORE, lease);
425 +
                domains::cancel(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, failed);
358 426
                sync::leave(guard); throw error;
359 427
            };
360 428
            let guard = sync::enter();
429 +
            let ready = lifecycle::takeDomain(&mut lifecycle::CALLS, &mut domains::STORE, lease);
361 430
            if not live(owner) {
362 -
                domains::cancel(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, pending);
431 +
                domains::cancel(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, ready);
363 432
                sync::leave(guard); throw abi::Error::BadHandle;
364 433
            }
365 -
            let result = try domains::publish(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, pending)
434 +
            let result = try domains::publish(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, ready)
366 435
                catch error { sync::leave(guard); throw error; };
367 436
            sync::leave(guard); return *result;
368 437
        },
369 438
        case 30 => {
370 439
            let pending = try pages::reserve(&mut pages::STORE, &mut domain.memory.table, handle, arguments[1])
371 440
                catch error { sync::leave(guard); throw error; };
441 +
            let context = abi::reference(dispatch::current().context);
442 +
            let lease = lifecycle::holdPage(&mut lifecycle::CALLS, &mut domains::STORE, context, pending);
372 443
            sync::leave(guard);
373 -
            pages::clear(&pending);
444 +
            lifecycle::clearPage(&lifecycle::CALLS, &lease);
374 445
            let guard = sync::enter();
446 +
            let ready = lifecycle::takePage(&mut lifecycle::CALLS, &mut domains::STORE, lease);
375 447
            if not live(owner) {
376 -
                pages::cancel(&mut pages::STORE, &mut domain.memory.table, pending);
448 +
                pages::cancel(&mut pages::STORE, &mut domain.memory.table, ready);
377 449
                sync::leave(guard); throw abi::Error::BadHandle;
378 450
            }
379 -
            let result = pages::publish(&mut pages::STORE, &mut domain.memory.table, pending);
451 +
            let result = pages::publish(&mut pages::STORE, &mut domain.memory.table, ready);
380 452
            sync::leave(guard); return *result;
381 453
        },
382 454
        case 51 => return try runtimeLoad(&mut domain, loader::Request {
383 455
            authority: handle, source: abi::Handle(arguments[1]), offset: arguments[2], length: arguments[3],
384 456
        }, guard),
391 463
            let start = abi::ContextStart { entry: words[0], stack: words[1], args: words[2], size: words[3] };
392 464
            let pending = try domains::contextReserve(&mut domains::STORE, &mut pages::STORE, &registry::STORE,
393 465
                &domain.memory.table, handle, start) catch error { sync::leave(guard); throw error; };
394 466
            let mut target: abi::Ref = undefined;
395 467
            match &pending { case domains::ContextReservation::Held(allocation) => { set target = allocation.owner; }, }
468 +
            let context = abi::reference(dispatch::current().context);
469 +
            let lease = lifecycle::holdContext(&mut lifecycle::CALLS, &mut domains::STORE, context, pending);
396 470
            sync::leave(guard);
397 -
            domains::contextClear(&pending);
471 +
            lifecycle::clearContext(&lifecycle::CALLS, &lease);
398 472
            let guard = sync::enter();
473 +
            let ready = lifecycle::takeContext(&mut lifecycle::CALLS, &mut domains::STORE, lease);
399 474
            if not live(owner) or not live(target) {
400 -
                domains::contextCancel(&mut domains::STORE, &mut pages::STORE, pending);
475 +
                domains::contextCancel(&mut domains::STORE, &mut pages::STORE, ready);
401 476
                sync::leave(guard); throw abi::Error::BadHandle;
402 477
            }
403 -
            let result = domains::contextPublish(&mut domains::STORE, pending);
478 +
            let result = domains::contextPublish(&mut domains::STORE, ready);
404 479
            sync::leave(guard); return abi::id(result);
405 480
        },
406 481
        else => {
407 -
            let result = try invoke(owner, operation, arguments, now) catch error { sync::leave(guard); throw error; };
482 +
            let result = try invoke(owner, operation, arguments, dispatch::clock()) catch error { sync::leave(guard); throw error; };
408 483
            sync::leave(guard); return result;
409 484
        },
410 485
    }
411 486
}
412 487
413 488
/// Reserve remote dispatch capacity before activation or event publication.
414 -
unsafe fn runtimeReady(owner: abi::Ref, operation: u64, arguments: &[u64], now: u64) -> u64 throws (abi::Error) {
489 +
unsafe fn runtimeReady(owner: abi::Ref, operation: u64, arguments: &[u64]) -> u64 throws (abi::Error) {
415 490
    if operation == 50 and (arguments[1] > 0xffffffff or arguments[2] <> 0 or arguments[3] <> 0) { throw abi::Error::InvalidArg; }
416 491
    let guard = sync::enter();
417 492
    let domain = try domains::get(&domains::STORE, owner) catch error { sync::leave(guard); throw error; };
418 493
    if domain.state <> domains::Lifecycle::Active { sync::leave(guard); throw abi::Error::BadHandle; }
419 494
    let handle = abi::Handle(arguments[0]);
421 496
    let permit = try capability::lookup(&domain.memory.table, handle, abi::Kind::Domain, abi::Rights(rights)) catch error {
422 497
        sync::leave(guard); throw error;
423 498
    };
424 499
    let source = dispatch::hart();
425 500
    let context = abi::reference(dispatch::current().context);
501 +
    let now = dispatch::clock();
426 502
    let targets = budgets::wakeTargets(&budgets::STORE, &domains::STORE, permit.object, now) & ~(1 << source);
427 503
    let pending = try remote::reserve(&mut remote::STORE, source, targets) catch error { sync::leave(guard); throw error; };
428 504
    let result = try invoke(owner, operation, arguments, now) catch error {
429 505
        remote::cancelReservation(&mut remote::STORE, pending);
430 506
        sync::leave(guard); throw error;
442 518
{
443 519
    assert trap::fromUser(frame) and frame.cause == 8;
444 520
    assert trap::advanceCall(frame);
445 521
    let args = [frame.registers[10], frame.registers[11], frame.registers[12], frame.registers[13]];
446 522
    let result = try execute(owner, frame.registers[17], &args[..], now) catch error {
523 +
        let operation = frame.registers[17];
524 +
        if operation >= 44 and operation <= 48 and error == abi::Error::BadHandle {
525 +
            try execute(owner, 43, &[0, 0, 0, 0], now) catch stopped {
526 +
                assert stopped == abi::Error::BadHandle;
527 +
            };
528 +
        }
447 529
        set frame.registers[10] = (0 as u64) - error as u64;
448 530
        return;
449 531
    };
450 532
    set frame.registers[10] = result;
451 533
}
kernel/kernel/dispatch.rad +59 -7
3 3
use super::abi;
4 4
use super::limits;
5 5
use super::slots;
6 6
use super::budgets;
7 7
use super::domains;
8 +
use super::lifecycle;
9 +
use super::loader;
10 +
use super::registry;
11 +
use super::frames;
12 +
use super::interrupts;
13 +
use super::events;
14 +
use super::pages;
8 15
use super::platform;
9 16
use super::trap;
10 17
use super::range;
11 18
use super::calls;
12 19
use super::timers;
42 49
record Runtime: Copy {
43 50
    /// Current context and retained idle frame.
44 51
    state: State,
45 52
    /// Validated timer registers for this hart.
46 53
    timer: Timer,
54 +
    /// Minimum ticks between scheduled maintenance passes.
55 +
    maintenance: u64,
56 +
    /// Earliest tick for another background maintenance pass.
57 +
    nextMaintenance: u64,
47 58
}
48 59
49 60
/// Runtime slots initialized before each hart enables timer interrupts.
50 61
unsafe static HARTS: [Runtime; limits::HARTS] = undefined;
51 62
65 76
    let device = try timer(machine, id);
66 77
    let stack = machine.stacks[id];
67 78
    let mut idle = trap::Frame { registers: [0; 32], pc: idleAddress(), status: 0x1880, cause: 0, value: 0 };
68 79
    set idle.registers[2] = stack.end;
69 80
    set idle.registers[3] = kernelGp;
81 +
    let mut maintenance = machine.timebase as u64 / 100;
82 +
    if maintenance == 0 { set maintenance = 1; }
70 83
    set HARTS[id] = Runtime {
71 -
        state: State { hart: id, current: nil, budget: nil, idle, stack }, timer: device,
84 +
        state: State { hart: id, current: nil, budget: nil, idle, stack }, timer: device, maintenance, nextMaintenance: 0,
72 85
    };
73 86
    arm(device, 0xffffffffffffffff);
74 87
}
75 88
76 89
/// Tail-enter dispatch on the firmware stack and resume the selected frame.
103 116
        set deliveries[count] = delivery;
104 117
        set count += 1;
105 118
    }
106 119
    let clock = now(HARTS[id].timer);
107 120
    let timeout = try! timers::service(&mut timers::STORE, &mut domains::STORE, &budgets::STORE, id, clock);
108 -
    let notified = timeout.harts | plic::service(clock);
121 +
    let notified = timeout.harts | plic::service(clock) | lifecycle::service(&mut domains::STORE, &budgets::STORE, clock);
109 122
    let choice = try! exchange(&mut HARTS[id].state, &budgets::STORE, &mut domains::STORE, frame, anchor, clock);
110 123
    let mut deadline = choice.deadline;
111 124
    if timeout.deadline < deadline { set deadline = timeout.deadline; }
112 -
    for target in 0..limits::HARTS {
113 -
        if target <> id and (notified & (1 << target)) <> 0 { signal(target); }
114 -
    }
115 -
    arm(HARTS[id].timer, deadline);
125 +
    reschedule(notified);
116 126
    if previous <> choice.context {
117 127
        if let context = previous { signalContext(context, clock); }
118 128
    }
119 129
    for delivery in &deliveries[..count] {
120 130
        assert remote::acknowledge(&mut remote::STORE, id, delivery.id);
121 131
        if delivery.request.source <> id { signal(delivery.request.source); }
122 132
        if let case remote::Action::Budget(call) = delivery.request.action { signalContext(call.context, clock); }
123 133
        if let case remote::Action::Ready(context) = delivery.request.action { signalContext(context, clock); }
124 134
    }
135 +
    lifecycle::cancelRequests(&mut domains::STORE, &mut remote::STORE);
136 +
    let cancelled = lifecycle::cancelAllocations(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE, &mut loader::STATE, &mut registry::STORE);
137 +
    let maintenanceClock = now(HARTS[id].timer);
138 +
    let mut maintenance = cancelled or domains::STORE.dead > 0 or pages::STORE.backings.pool.retiredCount > 0;
139 +
    if maintenance and maintenanceClock >= HARTS[id].nextMaintenance {
140 +
        let reaped = lifecycle::reapNext(&mut domains::STORE, &mut pages::STORE, &mut budgets::STORE, &mut interrupts::STORE);
141 +
        let reclaimed = frames::reclaim(&mut pages::STORE.backings.pool, 256);
142 +
        set maintenance = cancelled or reaped or pages::STORE.backings.pool.retiredCount > 0;
143 +
        let after = now(HARTS[id].timer);
144 +
        let interval = HARTS[id].maintenance;
145 +
        set HARTS[id].nextMaintenance = 0xffffffffffffffff;
146 +
        if interval < 0xffffffffffffffff - after { set HARTS[id].nextMaintenance = after + interval; }
147 +
    }
148 +
    if maintenance and HARTS[id].nextMaintenance < deadline { set deadline = HARTS[id].nextMaintenance; }
149 +
    arm(HARTS[id].timer, deadline);
125 150
    sync::leave(guard);
126 151
}
127 152
128 153
/// Signal an initialized online hart while request metadata is serialized.
129 154
unsafe fn signal(target: u32) { writeSoftware(HARTS[target].timer.software, 1); }
130 155
156 +
/// Signal other harts that retain stopped contexts under the metadata lock.
157 +
export unsafe fn reschedule(harts: u32) {
158 +
    let source = hart();
159 +
    for target in 0..limits::HARTS {
160 +
        if target <> source and (harts & (1 << target)) <> 0 { signal(target); }
161 +
    }
162 +
}
163 +
131 164
/// Notify the current owner or the eligible hart after a context is released.
132 165
unsafe fn signalContext(context: abi::Ref, now: u64) {
133 166
    if not slots::matches(&domains::STORE.contextSlots[..], context, slots::State::Live) { return; }
134 167
    if let owner = domains::STORE.contexts[context.index].hart { signal(owner); return; }
135 168
    for i in 0..limits::BUDGETS {
215 248
    };
216 249
    releaseRequest(source, request);
217 250
    sync::leave(guard); return result;
218 251
}
219 252
253 +
/// Read mtime through the executing hart's validated timer mapping.
254 +
export unsafe fn clock() -> u64 { return now(HARTS[hart()].timer); }
255 +
220 256
/// Read the running context's identity and domain-relative CPU authority.
221 257
export unsafe fn current() -> abi::CurrentContextInfo {
222 258
    let id = hart();
223 259
    assert id < limits::HARTS;
224 260
    let context = HARTS[id].state.current else panic "no current context";
235 271
    let guard = sync::enter();
236 272
    assert slots::matches(&domains::STORE.contextSlots[..], context, slots::State::Live);
237 273
    let owner = domains::STORE.contexts[context.index].owner;
238 274
    if frame.registers[17] == abi::Operation::ContextReturn as u64 {
239 275
        assert trap::advanceCall(frame);
240 -
        try! domains::returned(&mut domains::STORE, owner, context);
276 +
        let stopped = try! domains::returned(&mut domains::STORE, owner, context);
277 +
        reschedule(stopped);
241 278
        sync::leave(guard);
242 279
    } else {
243 280
        sync::leave(guard);
281 +
        sync::restoreInterrupts(8);
244 282
        calls::handle(owner, frame, now(HARTS[id].timer), calls::synchronized);
283 +
        let interrupts = sync::maskInterrupts();
245 284
    }
246 285
    interrupt(frame, &mut *localAnchor());
247 286
}
248 287
288 +
/// Stop a faulting user domain and resume another authorized context.
289 +
export unsafe fn fault(frame: &mut trap::Frame, anchor: &mut trap::Hart) -> ! {
290 +
    assert trap::fromUser(frame);
291 +
    let guard = sync::enter();
292 +
    let context = HARTS[hart()].state.current else panic "fault without current context";
293 +
    assert slots::matches(&domains::STORE.contextSlots[..], context, slots::State::Live);
294 +
    let owner = domains::STORE.contexts[context.index].owner;
295 +
    let stopped = try! domains::terminate(&mut domains::STORE, owner, events::FAULT, frame.cause as u32);
296 +
    reschedule(stopped);
297 +
    sync::leave(guard);
298 +
    interrupt(frame, &mut *localAnchor());
299 +
}
300 +
249 301
/// Enter the first authorized context or the hart's retained idle frame.
250 302
export unsafe fn start(anchor: &mut trap::Hart) -> ! {
251 303
    let id = hart();
252 304
    assert id < limits::HARTS;
253 305
    let mut frame = HARTS[id].state.idle;
kernel/kernel/domains.rad +191 -32
18 18
export constant KERNEL_STACK_PAGES: u32 = 64;
19 19
20 20
/// Management rights returned to a domain's creator.
21 21
export constant MANAGEMENT_RIGHTS: u16 = abi::DESTROY | abi::EXECUTE | abi::GRANT | abi::TRANSFER | abi::WAKE;
22 22
23 +
/// Words needed to retain one ancestry bit per domain slot.
24 +
constant ANCESTRY_WORDS: u32 = (limits::DOMAINS + 31) / 32;
25 +
23 26
/// Protection-domain lifetime independent of execution budgets.
24 27
export union Lifecycle: Copy {
25 28
    /// Resources can be installed before one-time activation.
26 29
    Pending,
27 30
    /// The domain can execute when a context has CPU authority.
28 31
    Active,
29 -
    /// Execution has stopped and the identity can remain as a tombstone.
32 +
    /// Contexts cannot be selected; hart release and resource cleanup can remain.
30 33
    Dead,
31 34
}
32 35
33 36
/// Private domain metadata and its shared event ring.
34 37
export record Memory: Copy {
58 61
    state: ContextState,
59 62
    /// Hart retaining this context as its current execution owner.
60 63
    hart: ?u32,
61 64
    /// Remote call whose retained continuation requires this context's storage.
62 65
    pending: ?abi::Ref,
66 +
    /// A kernel call retains unpublished resources and requires this context's stack.
67 +
    reservation: bool,
63 68
    /// Physical frames retained until this context stops.
64 69
    kernelFrames: frames::Run,
65 70
    /// Exclusive stack bounds used by trap entry and suspended M-mode calls.
66 71
    kernelStack: range::Range,
67 72
    /// Exclusive user stack extent; empty until initial activation.
96 101
export record Store: Copy {
97 102
    /// Domain generations, including retained dead identities.
98 103
    slots: [slots::Slot; limits::DOMAINS],
99 104
    /// Payload for each published domain.
100 105
    records: [Domain; limits::DOMAINS],
106 +
    /// True after private storage is retired, while terminal identity can remain.
107 +
    reclaimed: [bool; limits::DOMAINS],
108 +
    /// True after quiescence admits a domain to resource teardown.
109 +
    reaping: [bool; limits::DOMAINS],
110 +
    /// Number of dead domain identities retained by the store.
111 +
    dead: u32,
112 +
    /// First domain slot considered by the next reaper pass.
113 +
    reapCursor: u32,
114 +
    /// Creation ancestors by domain slot. Clear a column before its ancestor slot is reused.
115 +
    ancestors: [[u32; ANCESTRY_WORDS]; limits::DOMAINS],
101 116
    /// Context generations shared by initial and additional contexts.
102 117
    contextSlots: [slots::Slot; limits::CONTEXTS],
103 118
    /// Payload for each published execution context.
104 119
    contexts: [Context; limits::CONTEXTS],
105 120
    /// Trusted event producers and terminal records.
116 131
/// Address of the U-mode normal-return call sequence.
117 132
fn returnAddress() -> u64;
118 133
119 134
/// Initialize fresh metadata before boot-domain publication.
120 135
export fn initialize(store: &mut Store) {
136 +
    set store.dead = 0;
137 +
    set store.reapCursor = 0;
121 138
    slots::initialize(&mut store.slots[..]);
122 139
    slots::initialize(&mut store.contextSlots[..]);
140 +
    for i in 0..limits::CONTEXTS { set store.contexts[i].reservation = false; }
141 +
    for i in 0..limits::DOMAINS { set store.ancestors[i] = [0; ANCESTRY_WORDS]; set store.reclaimed[i] = false; set store.reaping[i] = false; }
123 142
    events::initialize(&mut store.events);
124 143
}
125 144
126 145
/// Read a domain record after validating its retained generation.
127 146
export fn get(store: &Store, object: abi::Ref) -> Domain throws (abi::Error) {
128 -
    if not slots::matches(&store.slots[..], object, slots::State::Live) { throw abi::Error::BadHandle; }
147 +
    if not slots::matches(&store.slots[..], object, slots::State::Live) or store.reclaimed[object.index] { throw abi::Error::BadHandle; }
129 148
    return store.records[object.index];
130 149
}
131 150
151 +
/// Test whether all published and unpublished execution ownership has ended.
152 +
export fn quiescent(store: &Store, owner: abi::Ref) -> bool throws (abi::Error) {
153 +
    let domain = try get(store, owner);
154 +
    if domain.state <> Lifecycle::Dead { return false; }
155 +
    for i in 0..limits::CONTEXTS {
156 +
        let state = store.contextSlots[i].state;
157 +
        if state <> slots::State::Live and state <> slots::State::Reserved { continue; }
158 +
        if store.contexts[i].owner <> owner { continue; }
159 +
        if state == slots::State::Reserved or store.contexts[i].state <> ContextState::Stopped
160 +
            or store.contexts[i].hart <> nil or store.contexts[i].pending <> nil or store.contexts[i].reservation { return false; }
161 +
    }
162 +
    return true;
163 +
}
164 +
165 +
/// Retire at most one stopped context after all domain execution ownership ends.
166 +
export fn retireContext(store: &mut Store, pool: &mut frames::Pool, owner: abi::Ref) -> bool throws (abi::Error) {
167 +
    if not try quiescent(store, owner) { throw abi::Error::Busy; }
168 +
    for i in 0..limits::CONTEXTS {
169 +
        if store.contextSlots[i].state <> slots::State::Live or store.contexts[i].owner <> owner { continue; }
170 +
        let object = abi::Ref { index: i, generation: store.contextSlots[i].generation };
171 +
        try! frames::retire(pool, store.contexts[i].kernelFrames);
172 +
        try! slots::release(&mut store.contextSlots[..], object);
173 +
        return true;
174 +
    }
175 +
    return false;
176 +
}
177 +
178 +
/// Retire private domain storage after contexts, handles, and exposures are gone.
179 +
export unsafe fn retireStorage(store: &mut Store, backings: &mut backing::Store, owner: abi::Ref) throws (abi::Error) {
180 +
    let domain = try get(store, owner);
181 +
    if not try quiescent(store, owner) { throw abi::Error::Busy; }
182 +
    for i in 0..limits::CONTEXTS {
183 +
        if store.contextSlots[i].state == slots::State::Live and store.contexts[i].owner == owner { throw abi::Error::Busy; }
184 +
    }
185 +
    for i in 0..limits::HANDLES {
186 +
        let state = domain.memory.table.slots[i].state;
187 +
        if state == slots::State::Live or state == slots::State::Reserved { throw abi::Error::Busy; }
188 +
    }
189 +
    if backings.domains[owner.index] <> 0 or store.events.queues[owner.index].generation <> 0 { throw abi::Error::Busy; }
190 +
    try! frames::retire(&mut backings.pool, domain.graph.frames);
191 +
    try! frames::retire(&mut backings.pool, domain.allocation);
192 +
    set store.reclaimed[owner.index] = true;
193 +
}
194 +
195 +
/// Release an acknowledged terminal identity and clear its creation-ancestry column.
196 +
export fn releaseIdentity(store: &mut Store, owner: abi::Ref) throws (abi::Error) {
197 +
    if not slots::matches(&store.slots[..], owner, slots::State::Live) { throw abi::Error::BadHandle; }
198 +
    if not store.reclaimed[owner.index] or not try events::acknowledged(&store.events, owner) { throw abi::Error::Busy; }
199 +
    let word = owner.index / 32;
200 +
    let bit = 1 << (owner.index % 32);
201 +
    for i in 0..limits::DOMAINS { set store.ancestors[i][word] &= ~bit; }
202 +
    try! events::release(&mut store.events, owner);
203 +
    try! slots::release(&mut store.slots[..], owner);
204 +
    assert store.dead > 0;
205 +
    set store.dead -= 1;
206 +
}
207 +
132 208
/// Read a live context after checking its generation and owning domain.
133 209
export fn context(store: &Store, owner: abi::Ref, object: abi::Ref) -> Context throws (abi::Error) {
134 210
    let domain = try get(store, owner);
135 211
    if not slots::matches(&store.contextSlots[..], object, slots::State::Live)
136 212
        or store.contexts[object.index].owner <> owner { throw abi::Error::BadHandle; }
137 213
    return store.contexts[object.index];
138 214
}
139 215
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 -
            }
216 +
/// Change the live lifecycle receiver under shared metadata serialization.
217 +
export fn reparent(store: &mut Store, table: &capability::Table, child: abi::Handle, parent: abi::Handle)
218 +
    throws (abi::Error)
219 +
{
220 +
    let target = try capability::lookup(table, child, abi::Kind::Domain, abi::Rights(abi::DESTROY));
221 +
    let receiver = try capability::lookup(table, parent, abi::Kind::Domain, abi::Rights(abi::WAKE));
222 +
    let domain = try get(store, target.object);
223 +
    let destination = try get(store, receiver.object);
224 +
    if domain.state == Lifecycle::Dead or destination.state == Lifecycle::Dead { throw abi::Error::BadHandle; }
225 +
    set store.records[target.object.index].parent = receiver.object;
226 +
}
227 +
228 +
/// Retain a terminal event, stop execution, and report harts that must release contexts.
229 +
export fn terminate(store: &mut Store, owner: abi::Ref, kind: u16, code: u32) -> u32 throws (abi::Error) {
230 +
    return try stop(store, owner, false, kind, code);
231 +
}
232 +
233 +
/// Stop a domain and its creation descendants without changing creation ancestry.
234 +
export fn terminateTree(store: &mut Store, owner: abi::Ref, kind: u16, code: u32) -> u32 throws (abi::Error) {
235 +
    return try stop(store, owner, true, kind, code);
236 +
}
237 +
238 +
/// Commit terminal state for a selected set before releasing the metadata lock.
239 +
fn stop(store: &mut Store, owner: abi::Ref, tree: bool, kind: u16, code: u32) -> u32 throws (abi::Error) {
240 +
    if kind <> events::CHILD_EXIT and kind <> events::FAULT { throw abi::Error::InvalidArg; }
241 +
    let domain = try get(store, owner);
242 +
    let mut generations: [u32; limits::DOMAINS] = [0; limits::DOMAINS];
243 +
    for i in 0..limits::DOMAINS {
244 +
        if store.slots[i].state <> slots::State::Live or store.records[i].state == Lifecycle::Dead { continue; }
245 +
        if i <> owner.index and (not tree or (store.ancestors[i][owner.index / 32] & (1 << (owner.index % 32))) == 0) { continue; }
246 +
        if store.events.terminals[i].state <> events::State::Free { throw abi::Error::Busy; }
247 +
        if store.events.queues[i].generation <> store.slots[i].generation { throw abi::Error::BadHandle; }
248 +
        set generations[i] = store.slots[i].generation;
249 +
    }
250 +
    for i in 0..limits::DOMAINS {
251 +
        if generations[i] == 0 { continue; }
252 +
        let object = abi::Ref { index: i, generation: generations[i] };
253 +
        let terminal = try! events::reserve(&mut store.events, object);
254 +
        let mut receiver = abi::Ref { index: 0, generation: 0 };
255 +
        if let parent = store.records[i].parent { set receiver = parent; }
256 +
        events::finish(&mut store.events, terminal, receiver, kind, code);
257 +
        set store.records[i].state = Lifecycle::Dead;
258 +
        set store.dead += 1;
259 +
    }
260 +
    try! events::closeSet(&mut store.events, &generations[..]);
261 +
    let mut harts: u32 = 0;
262 +
    for i in 0..limits::CONTEXTS {
263 +
        if store.contextSlots[i].state <> slots::State::Live { continue; }
264 +
        let target = store.contexts[i].owner;
265 +
        if generations[target.index] == target.generation {
266 +
            set store.contexts[i].state = ContextState::Stopped;
267 +
            if let hart = store.contexts[i].hart { set harts |= 1 << hart; }
149 268
        }
150 -
    } else {
151 -
        set store.contexts[object.index].state = ContextState::Stopped;
152 269
    }
270 +
    for i in 0..limits::DOMAINS {
271 +
        if store.slots[i].state <> slots::State::Live { continue; }
272 +
        if let parent = store.records[i].parent {
273 +
            if generations[parent.index] == parent.generation { set store.records[i].parent = nil; }
274 +
        }
275 +
    }
276 +
    return harts;
277 +
}
278 +
279 +
/// Stop a returned context; initial return ends execution of its whole domain.
280 +
export fn returned(store: &mut Store, owner: abi::Ref, object: abi::Ref) -> u32 throws (abi::Error) {
281 +
    let current = try context(store, owner, object);
282 +
    if store.records[owner.index].initial == object { return try terminate(store, owner, events::CHILD_EXIT, 0); }
283 +
    set store.contexts[object.index].state = ContextState::Stopped;
284 +
    return 0;
153 285
}
154 286
155 287
/// Atomically check notifications and suspend the caller if its queue is empty.
156 288
/// The caller serializes this check with notification publication.
157 289
export unsafe fn wait(store: &mut Store, owner: abi::Ref, object: abi::Ref) throws (abi::Error) {
191 323
export fn contextDestroy(store: &mut Store, memory: &mut pages::Store, table: &capability::Table,
192 324
    authority: abi::Handle, object: abi::Ref) throws (abi::Error)
193 325
{
194 326
    let permit = try capability::authority(table, authority, abi::Rights(abi::DESTROY));
195 327
    let target = try context(store, permit.object, object);
328 +
    if store.records[permit.object.index].state == Lifecycle::Dead { throw abi::Error::BadHandle; }
196 329
    if store.records[permit.object.index].initial == object { throw abi::Error::Denied; }
197 -
    if target.hart <> nil or target.pending <> nil { throw abi::Error::Busy; }
330 +
    if target.hart <> nil or target.pending <> nil or target.reservation { throw abi::Error::Busy; }
198 331
    try! frames::release(&mut memory.backings.pool, target.kernelFrames);
199 332
    try! slots::release(&mut store.contextSlots[..], object);
200 333
}
201 334
202 335
/// Derive a descending stack extent from one writable Page capability.
230 363
export unsafe fn activate(store: &mut Store, memory: &pages::Store, table: &capability::Table,
231 364
    handle: abi::Handle, stack: u64, args: u64, size: u64) throws (abi::Error)
232 365
{
233 366
    let entry = try capability::lookup(table, handle, abi::Kind::Domain, abi::Rights(abi::EXECUTE));
234 367
    let mut domain = try get(store, entry.object);
368 +
    if domain.state == Lifecycle::Dead { throw abi::Error::BadHandle; }
235 369
    if domain.state <> Lifecycle::Pending { throw abi::Error::NotPending; }
236 370
    let userStack = try stackRange(memory, &domain.memory.table, stack);
237 371
    if size <> 0 and not pages::accessible(memory, &domain.memory.table, args, size, abi::Rights(abi::READ)) {
238 372
        throw abi::Error::InvalidArg;
239 373
    }
308 442
    set frame.registers[3] = domain.graph.table.ptr as u64;
309 443
    set frame.registers[10] = start.args;
310 444
    set frame.registers[11] = start.size;
311 445
    let object = slots::reference(&slot);
312 446
    set store.contexts[object.index] = Context {
313 -
        owner: permission.object, state: ContextState::Ready, hart: nil, pending: nil, kernelFrames, kernelStack, userStack, frame,
447 +
        owner: permission.object, state: ContextState::Ready, hart: nil, pending: nil, reservation: false, kernelFrames, kernelStack, userStack, frame,
314 448
    };
315 449
    match slot {
316 450
        case slots::Reservation::Held(object) => return ContextReservation::Held(ContextAllocation {
317 451
            object, owner: permission.object, frames: kernelFrames, base: kernelStack.start,
318 452
        }),
319 453
    }
320 454
}
321 455
322 456
/// Clear an unpublished context's private kernel stack.
323 457
export fn contextClear(reservation: &ContextReservation) {
324 -
    match reservation { case ContextReservation::Held(allocation) => zero(allocation.base, allocation.frames.count), }
458 +
    match reservation { case ContextReservation::Held(allocation) => contextClearAllocation(allocation), }
459 +
}
460 +
461 +
/// Clear retained private stack frames outside the metadata lock.
462 +
export fn contextClearAllocation(allocation: &ContextAllocation) {
463 +
    zero(allocation.base, allocation.frames.count);
325 464
}
326 465
327 466
/// Check the reserved context and its retained physical allocation.
328 467
fn contextRequired(store: &Store, allocation: &ContextAllocation) {
329 468
    assert slots::matches(&store.contextSlots[..], allocation.object, slots::State::Reserved);
343 482
    }
344 483
}
345 484
346 485
/// Return an unpublished context's stack and slot under metadata serialization.
347 486
export fn contextCancel(store: &mut Store, memory: &mut pages::Store, reservation: ContextReservation) {
487 +
    contextCancelUsing(store, memory, reservation, frames::release);
488 +
}
489 +
490 +
/// Cancel a context slot and transfer its private stack to the release operation.
491 +
export fn contextCancelUsing(store: &mut Store, memory: &mut pages::Store, reservation: ContextReservation,
492 +
    release: fn(&mut frames::Pool, frames::Run) throws (abi::Error))
493 +
{
348 494
    match reservation {
349 495
        case ContextReservation::Held(allocation) => {
350 496
            contextRequired(store, &allocation);
351 -
            try! frames::release(&mut memory.backings.pool, allocation.frames);
497 +
            try! release(&mut memory.backings.pool, allocation.frames);
352 498
            try! slots::cancel(&mut store.contextSlots[..], slots::Reservation::Held(allocation.object));
353 499
        },
354 500
    }
355 501
}
356 502
397 543
        },
398 544
    }
399 545
}
400 546
401 547
/// Return storage that has never been exposed to a published domain.
402 -
fn discard(pool: &mut frames::Pool, prepared: Prepared) {
403 -
    try! frames::release(pool, prepared.kernelFrames);
404 -
    try! frames::release(pool, prepared.graph.frames);
405 -
    try! frames::release(pool, prepared.allocation);
548 +
fn discard(pool: &mut frames::Pool, prepared: Prepared, release: fn(&mut frames::Pool, frames::Run) throws (abi::Error)) {
549 +
    try! release(pool, prepared.kernelFrames);
550 +
    try! release(pool, prepared.graph.frames);
551 +
    try! release(pool, prepared.allocation);
406 552
}
407 553
408 554
/// Unpublished domain metadata retained during private initialization.
409 555
export record Creation: Copy {
410 556
    /// Calling domain kept live until publication or cancellation.
449 595
    let contextSlot = try slots::reserve(&mut store.contextSlots[..]) catch err {
450 596
        try! slots::cancel(&mut store.slots[..], domainSlot);
451 597
        try! slots::cancel(&mut table.slots[..], handleSlot); throw err;
452 598
    };
453 599
    let initialSlot = slots::reference(&contextSlot);
600 +
    set store.contexts[initialSlot.index].owner = slots::reference(&domainSlot);
454 601
    set store.contexts[initialSlot.index].userStack = range::Range { start: 0, end: 0 };
455 602
    let prepared = try allocate(&mut backings.pool, packages, root) catch err {
456 603
        try! slots::cancel(&mut store.contextSlots[..], contextSlot);
457 604
        try! slots::cancel(&mut store.slots[..], domainSlot);
458 605
        try! slots::cancel(&mut table.slots[..], handleSlot); throw err;
470 617
    }
471 618
}
472 619
473 620
/// Initialize private storage from resident packages while metadata can change.
474 621
export unsafe fn prepare(packages: &registry::Store, reservation: &Reservation) throws (abi::Error) {
475 -
    match reservation {
476 -
        case Reservation::Held(creation) => {
477 -
            let mut storage = creation.storage;
478 -
            zero(storage.memory as u64, storage.allocation.count);
479 -
            zero(storage.kernelStack.start, storage.kernelFrames.count);
480 -
            try instances::fill(packages, &storage.graph.graph, storage.graph.base);
481 -
            capability::initialize(&mut storage.memory.table, creation.domain);
482 -
        },
483 -
    }
622 +
    match reservation { case Reservation::Held(creation) => try prepareCreation(packages, creation), }
623 +
}
624 +
625 +
/// Initialize retained domain storage outside the metadata lock.
626 +
export unsafe fn prepareCreation(packages: &registry::Store, creation: &Creation) throws (abi::Error) {
627 +
    let mut storage = creation.storage;
628 +
    zero(storage.memory as u64, storage.allocation.count);
629 +
    zero(storage.kernelStack.start, storage.kernelFrames.count);
630 +
    try instances::fill(packages, &storage.graph.graph, storage.graph.base);
631 +
    capability::initialize(&mut storage.memory.table, creation.domain);
484 632
}
485 633
486 634
/// Check the unpublished capacity retained by one creation transaction.
487 635
fn require(store: &Store, table: &capability::Table, creation: &Creation) {
488 636
    assert table.owner == creation.owner;
491 639
    assert slots::matches(&store.contextSlots[..], creation.context, slots::State::Reserved);
492 640
}
493 641
494 642
/// Return private domain storage and reserved slots under metadata serialization.
495 643
export fn cancel(store: &mut Store, backings: &mut backing::Store, table: &mut capability::Table, reservation: Reservation) {
644 +
    cancelUsing(store, backings, table, reservation, frames::release);
645 +
}
646 +
647 +
/// Cancel private domain slots and transfer their storage to the release operation.
648 +
export fn cancelUsing(store: &mut Store, backings: &mut backing::Store, table: &mut capability::Table, reservation: Reservation,
649 +
    release: fn(&mut frames::Pool, frames::Run) throws (abi::Error))
650 +
{
496 651
    match reservation {
497 652
        case Reservation::Held(creation) => {
498 653
            require(store, table, &creation);
499 -
            discard(&mut backings.pool, creation.storage);
654 +
            discard(&mut backings.pool, creation.storage, release);
500 655
            try! slots::cancel(&mut store.contextSlots[..], slots::Reservation::Held(creation.context));
501 656
            try! slots::cancel(&mut store.slots[..], slots::Reservation::Held(creation.domain));
502 657
            try! slots::cancel(&mut table.slots[..], slots::Reservation::Held(creation.handle));
503 658
        },
504 659
    }
532 687
            // MPIE enables interrupts after the first return into U-mode.
533 688
            let mut frame = trap::Frame { registers: [0; 32], pc: creation.entry, status: 0x80, cause: 0, value: 0 };
534 689
            set frame.registers[1] = returnAddress();
535 690
            set frame.registers[3] = graph.table.ptr as u64;
536 691
            set store.contexts[initial.index] = Context {
537 -
                owner: object, state: ContextState::Ready, hart: nil, pending: nil, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack,
692 +
                owner: object, state: ContextState::Ready, hart: nil, pending: nil, reservation: false, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack,
538 693
                userStack: range::Range { start: 0, end: 0 }, frame,
539 694
            };
695 +
            set store.reclaimed[object.index] = false;
696 +
            set store.reaping[object.index] = false;
540 697
            set store.records[object.index] = Domain {
541 698
                state: Lifecycle::Pending, creator: creation.owner, parent: creation.owner, image: creation.image, initial,
542 699
                allocation: prepared.allocation, memory: prepared.memory, graph, events: queue,
543 700
            };
701 +
            set store.ancestors[object.index] = store.ancestors[creation.owner.index];
702 +
            set store.ancestors[object.index][creation.owner.index / 32] |= 1 << (creation.owner.index % 32);
544 703
            let context = try! slots::commit(&mut store.contextSlots[..], slots::Reservation::Held(initial));
545 704
            let domain = try! slots::commit(&mut store.slots[..], slots::Reservation::Held(object));
546 705
            return capability::publish(table, slots::Reservation::Held(creation.handle), capability::Entry {
547 706
                kind: abi::Kind::Domain, object: domain, rights: creation.rights,
548 707
            });
kernel/kernel/events.rad +41 -7
83 83
    state: State,
84 84
}
85 85
86 86
/// Fixed event metadata. Callers serialize all kernel-side mutations.
87 87
export record Store: Copy {
88 +
    /// Terminal events retained for publication into receiver rings.
89 +
    pending: u32,
88 90
    /// Producer metadata indexed by receiver domain.
89 91
    queues: [Queue; limits::DOMAINS],
90 92
    /// Terminal storage indexed by the terminating domain.
91 93
    terminals: [Terminal; limits::DOMAINS],
92 94
}
105 107
/// Advance a shared counter with explicit modulo arithmetic.
106 108
fn next(value: u32) -> u32 { return ((value as u64 + 1) & 0xffffffff) as u32; }
107 109
108 110
/// Initialize metadata before any queue or terminal record exists.
109 111
export fn initialize(store: &mut Store) {
112 +
    set store.pending = 0;
110 113
    for i in 0..limits::DOMAINS {
111 114
        set store.queues[i].generation = 0;
112 115
        set store.terminals[i].state = State::Free;
113 116
    }
114 117
}
227 230
        case Reservation::Held(subject) => {
228 231
            let terminal = &mut store.terminals[subject.index];
229 232
            assert terminal.subject == subject and terminal.state == State::Reserved;
230 233
            set terminal.receiver = receiver;
231 234
            set terminal.event = Event { kind, reserved: 0, code, value: abi::id(subject) };
232 -
            if active { set terminal.state = State::Pending; }
235 +
            if active { set terminal.state = State::Pending; set store.pending += 1; }
233 236
            else { set terminal.state = State::Acknowledged; }
234 237
        },
235 238
    }
236 239
}
237 240
241 +
/// Publish one retained terminal event when its receiver has ring capacity.
242 +
export fn deliver(store: &mut Store, subject: abi::Ref, ring: &mut Ring) -> bool throws (abi::Error) {
243 +
    if subject.index >= limits::DOMAINS or store.terminals[subject.index].subject <> subject
244 +
        or store.terminals[subject.index].state == State::Free { throw abi::Error::BadHandle; }
245 +
    let terminal = store.terminals[subject.index];
246 +
    if terminal.state <> State::Pending { return false; }
247 +
    try refresh(store, terminal.receiver, ring);
248 +
    let queue = &mut store.queues[terminal.receiver.index];
249 +
    if distance(queue.tail, queue.head) == CAPACITY { return false; }
250 +
    set store.terminals[subject.index].state = State::Queued;
251 +
    assert store.pending > 0;
252 +
    set store.pending -= 1;
253 +
    publish(queue, ring, terminal.event, subject);
254 +
    return true;
255 +
}
256 +
238 257
/// Publish retained terminal events in domain-index order as ring capacity permits.
239 258
export fn flush(store: &mut Store, receiver: abi::Ref, ring: &mut Ring) -> u32 throws (abi::Error) {
240 259
    try refresh(store, receiver, ring);
241 260
    let mut count: u32 = 0;
242 261
    for i in 0..limits::DOMAINS {
243 262
        if distance(store.queues[receiver.index].tail, store.queues[receiver.index].head) == CAPACITY { break; }
244 263
        if store.terminals[i].state <> State::Pending or store.terminals[i].receiver <> receiver { continue; }
245 -
        set store.terminals[i].state = State::Queued;
246 -
        let event = store.terminals[i].event;
247 264
        let subject = store.terminals[i].subject;
248 -
        publish(&mut store.queues[receiver.index], ring, event, subject);
249 -
        set count += 1;
265 +
        if try deliver(store, subject, ring) { set count += 1; }
250 266
    }
251 267
    return count;
252 268
}
253 269
254 270
/// Report whether a retained terminal identity can be released.
265 281
}
266 282
267 283
/// Close a stopped domain's queue and end all terminal references held by that receiver.
268 284
export fn close(store: &mut Store, receiver: abi::Ref) throws (abi::Error) {
269 285
    if not live(store, receiver) { throw abi::Error::BadHandle; }
270 -
    set store.queues[receiver.index].generation = 0;
286 +
    let mut generations: [u32; limits::DOMAINS] = [0; limits::DOMAINS];
287 +
    set generations[receiver.index] = receiver.generation;
288 +
    try! closeSet(store, &generations[..]);
289 +
}
290 +
291 +
/// Close selected receiver generations and acknowledge their retained terminal events.
292 +
/// Zero entries leave a receiver open. Validate all selected generations before mutation.
293 +
export fn closeSet(store: &mut Store, generations: &[u32]) throws (abi::Error) {
294 +
    if generations.len <> limits::DOMAINS { throw abi::Error::InvalidArg; }
295 +
    for i in 0..limits::DOMAINS {
296 +
        if generations[i] <> 0 and store.queues[i].generation <> generations[i] { throw abi::Error::BadHandle; }
297 +
    }
298 +
    for i in 0..limits::DOMAINS {
299 +
        if generations[i] <> 0 { set store.queues[i].generation = 0; }
300 +
    }
271 301
    for i in 0..limits::DOMAINS {
272 -
        if (store.terminals[i].state == State::Pending or store.terminals[i].state == State::Queued) and store.terminals[i].receiver == receiver {
302 +
        let terminal = store.terminals[i];
303 +
        if terminal.state <> State::Pending and terminal.state <> State::Queued { continue; }
304 +
        let receiver = terminal.receiver;
305 +
        if generations[receiver.index] == receiver.generation {
306 +
            if store.terminals[i].state == State::Pending { assert store.pending > 0; set store.pending -= 1; }
273 307
            set store.terminals[i].state = State::Acknowledged;
274 308
        }
275 309
    }
276 310
}
kernel/kernel/frames.rad +37 -0
25 25
    addresses: [u64; limits::FRAMES],
26 26
    /// True when the frame is available for reservation.
27 27
    free: [bool; limits::FRAMES],
28 28
    /// Number of valid frame-map entries.
29 29
    count: u32,
30 +
    /// Retired runs whose frames remain unavailable until reclamation.
31 +
    retired: [Run; limits::FRAMES],
32 +
    /// Index of the next retired run to reclaim.
33 +
    retiredHead: u32,
34 +
    /// Number of occupied retirement entries.
35 +
    retiredCount: u32,
30 36
}
31 37
32 38
/// Insert one range into an address-sorted prefix of fixed storage.
33 39
fn insert(ranges: &mut [range::Range], count: u32, value: range::Range) {
34 40
    let mut at = count;
40 46
41 47
/// Build a fresh frame map from complete RAM pages with no reserved byte.
42 48
/// The pool has zero valid entries if initialization fails.
43 49
export unsafe fn initialize(pool: &mut Pool, machine: &platform::Platform) throws (abi::Error) {
44 50
    set pool.count = 0;
51 +
    set pool.retiredHead = 0;
52 +
    set pool.retiredCount = 0;
45 53
    if machine.ramCount > platform::RAM_BANKS or machine.reservedCount > platform::RESERVATIONS {
46 54
        throw abi::Error::InvalidArg;
47 55
    }
48 56
    let mut reserved: [range::Range; platform::RESERVATIONS] = undefined;
49 57
    for i in 0..machine.reservedCount {
123 131
    let memory = try extent(pool, run);
124 132
    for i in run.first..run.first + run.count { if pool.free[i] { throw abi::Error::BadHandle; } }
125 133
    for i in run.first..run.first + run.count { set pool.free[i] = true; }
126 134
}
127 135
136 +
/// Transfer exclusive ownership of a committed run to bounded reclamation.
137 +
/// The caller must not release or retire this run again.
138 +
export fn retire(pool: &mut Pool, run: Run) throws (abi::Error) {
139 +
    let memory = try extent(pool, run);
140 +
    assert pool.retiredCount < pool.count;
141 +
    let tail = (pool.retiredHead + pool.retiredCount) % limits::FRAMES;
142 +
    set pool.retired[tail] = run;
143 +
    set pool.retiredCount += 1;
144 +
}
145 +
146 +
/// Return at most limit retired frames while metadata is serialized.
147 +
export fn reclaim(pool: &mut Pool, limit: u32) -> u32 {
148 +
    let mut returned: u32 = 0;
149 +
    while pool.retiredCount > 0 and returned < limit {
150 +
        let mut count = pool.retired[pool.retiredHead].count;
151 +
        if count > limit - returned { set count = limit - returned; }
152 +
        let batch = Run { first: pool.retired[pool.retiredHead].first, count };
153 +
        try! release(pool, batch);
154 +
        set pool.retired[pool.retiredHead].first += count;
155 +
        set pool.retired[pool.retiredHead].count -= count;
156 +
        set returned += count;
157 +
        if pool.retired[pool.retiredHead].count == 0 {
158 +
            set pool.retiredHead = (pool.retiredHead + 1) % limits::FRAMES;
159 +
            set pool.retiredCount -= 1;
160 +
        }
161 +
    }
162 +
    return returned;
163 +
}
164 +
128 165
/// Cancel unpublished frame ownership and restore its capacity.
129 166
export fn cancel(pool: &mut Pool, reservation: Reservation) throws (abi::Error) {
130 167
    match reservation { case Reservation::Held(run) => try release(pool, run), }
131 168
}
kernel/kernel/lifecycle.rad added +468 -0
1 +
//! Terminal notification delivery and execution eligibility.
2 +
use super::limits;
3 +
use super::slots;
4 +
use super::events;
5 +
use super::domains;
6 +
use super::budgets;
7 +
use super::abi;
8 +
use super::remote;
9 +
use super::pages;
10 +
use super::registry;
11 +
use super::loader;
12 +
use super::capability;
13 +
use super::frames;
14 +
use super::backing;
15 +
use super::interrupts;
16 +
use super::plic;
17 +
18 +
/// Words needed to track request ownership for every context slot.
19 +
constant CONTEXT_WORDS: u32 = (limits::CONTEXTS + 31) / 32;
20 +
21 +
/// Private input and optional native output retained by a loader call.
22 +
export record Loading: Copy {
23 +
    /// Workspace and reserved caller handle.
24 +
    input: loader::Input,
25 +
    /// Output frames and package slot, when compilation has been admitted.
26 +
    output: ?loader::Output,
27 +
}
28 +
29 +
/// Successful loader work ready for publication under the metadata lock.
30 +
export union LoadResult: Copy {
31 +
    /// Matching immutable package already present in the registry.
32 +
    Existing(abi::Ref),
33 +
    /// Generated code and metadata in the retained output allocation.
34 +
    Compiled(loader::Compiled),
35 +
}
36 +
37 +
/// Private storage retained by an unfinished syscall.
38 +
export union Allocation: Copy {
39 +
    /// Page frames and unpublished capability metadata.
40 +
    Page(pages::Allocation),
41 +
    /// Additional context slot and private kernel stack.
42 +
    Context(domains::ContextAllocation),
43 +
    /// Reserved domain generation with its retained creation payload.
44 +
    Domain(abi::Ref),
45 +
    /// Private compiler workspace and unpublished output.
46 +
    Load(Loading),
47 +
}
48 +
49 +
/// One unfinished allocation retained by a caller generation.
50 +
export record HeldAllocation: Copy {
51 +
    /// Context whose continuation owns this allocation.
52 +
    context: abi::Ref,
53 +
    /// Calling domain, which can differ from the allocation target.
54 +
    owner: abi::Ref,
55 +
    /// Frames and metadata slots excluded from allocation.
56 +
    allocation: Allocation,
57 +
}
58 +
59 +
/// Allocations retained while their contexts initialize private frames.
60 +
export record Calls: Copy {
61 +
    /// One validity bit per occupied context slot.
62 +
    active: [u32; CONTEXT_WORDS],
63 +
    /// Reservation payloads indexed by context slot.
64 +
    records: [HeldAllocation; limits::CONTEXTS],
65 +
    /// Creation payloads valid while their domain slots are reserved.
66 +
    creations: [domains::Creation; limits::DOMAINS],
67 +
}
68 +
69 +
/// A continuation's exclusive claim to its retained reservation.
70 +
export union Lease: Once {
71 +
    /// Context generation that must take back the reservation before completion.
72 +
    Held(abi::Ref),
73 +
}
74 +
75 +
/// Reservations serialized with context and physical-memory metadata.
76 +
export unsafe static CALLS: Calls = undefined;
77 +
78 +
/// Clear allocation ownership before contexts start.
79 +
export fn initialize(calls: &mut Calls) {
80 +
    for i in 0..CONTEXT_WORDS { set calls.active[i] = 0; }
81 +
}
82 +
83 +
/// Retain private storage and pin the caller's kernel stack under the lock.
84 +
fn hold(calls: &mut Calls, store: &mut domains::Store, context: abi::Ref, allocation: Allocation) -> Lease {
85 +
    assert context.index < limits::CONTEXTS and context.generation <> 0;
86 +
    assert slots::matches(&store.contextSlots[..], context, slots::State::Live);
87 +
    assert not store.contexts[context.index].reservation;
88 +
    let bit = 1 << (context.index % 32);
89 +
    assert (calls.active[context.index / 32] & bit) == 0;
90 +
    set calls.records[context.index] = HeldAllocation { context, owner: store.contexts[context.index].owner, allocation };
91 +
    set calls.active[context.index / 32] |= bit;
92 +
    set store.contexts[context.index].reservation = true;
93 +
    return Lease::Held(context);
94 +
}
95 +
96 +
/// Transfer a page reservation into context-owned storage under the metadata lock.
97 +
export fn holdPage(calls: &mut Calls, store: &mut domains::Store, context: abi::Ref, reservation: pages::Reservation) -> Lease {
98 +
    match reservation {
99 +
        case pages::Reservation::Held(allocation) => {
100 +
            assert store.contexts[context.index].owner == allocation.owner;
101 +
            return hold(calls, store, context, Allocation::Page(allocation));
102 +
        },
103 +
    }
104 +
}
105 +
106 +
/// Transfer an unpublished context into its caller's retained storage.
107 +
export fn holdContext(calls: &mut Calls, store: &mut domains::Store, context: abi::Ref, reservation: domains::ContextReservation) -> Lease {
108 +
    match reservation {
109 +
        case domains::ContextReservation::Held(allocation) => return hold(calls, store, context, Allocation::Context(allocation)),
110 +
    }
111 +
}
112 +
113 +
/// Retain unpublished domain storage under its reserved domain generation.
114 +
export fn holdDomain(calls: &mut Calls, store: &mut domains::Store, context: abi::Ref, reservation: domains::Reservation) -> Lease {
115 +
    match reservation {
116 +
        case domains::Reservation::Held(creation) => {
117 +
            assert slots::matches(&store.slots[..], creation.domain, slots::State::Reserved);
118 +
            assert store.contexts[context.index].owner == creation.owner;
119 +
            set calls.creations[creation.domain.index] = creation;
120 +
            return hold(calls, store, context, Allocation::Domain(creation.domain));
121 +
        },
122 +
    }
123 +
}
124 +
125 +
/// Read the private payload for an exact reserved domain generation.
126 +
fn creation(calls: &Calls, object: abi::Ref) -> domains::Creation {
127 +
    assert object.index < limits::DOMAINS;
128 +
    assert calls.creations[object.index].domain == object;
129 +
    return calls.creations[object.index];
130 +
}
131 +
132 +
/// Initialize domain storage while the caller retains execution ownership.
133 +
export unsafe fn prepareDomain(calls: &Calls, packages: &registry::Store, lease: &Lease) throws (abi::Error) {
134 +
    match lease {
135 +
        case Lease::Held(context) => match held(calls, *context) {
136 +
            case Allocation::Domain(object) => {
137 +
                let retained = creation(calls, object);
138 +
                try domains::prepareCreation(packages, &retained);
139 +
            },
140 +
            else => panic "domain reservation required",
141 +
        },
142 +
    }
143 +
}
144 +
145 +
/// Retain private loader input and pin its caller's kernel stack.
146 +
export fn holdLoad(calls: &mut Calls, store: &mut domains::Store, context: abi::Ref, reservation: loader::Reservation) -> Lease {
147 +
    match reservation {
148 +
        case loader::Reservation::Held(input) => {
149 +
            assert store.contexts[context.index].owner == input.owner;
150 +
            return hold(calls, store, context, Allocation::Load(Loading { input, output: nil }));
151 +
        },
152 +
    }
153 +
}
154 +
155 +
/// Read the private loader allocations through their caller's lease.
156 +
export fn load(calls: &Calls, lease: &Lease) -> Loading {
157 +
    match lease {
158 +
        case Lease::Held(context) => match held(calls, *context) {
159 +
            case Allocation::Load(loading) => return loading,
160 +
            else => panic "loader reservation required",
161 +
        },
162 +
    }
163 +
}
164 +
165 +
/// Attach reserved native output to its caller before compilation can start.
166 +
export fn reserveOutput(calls: &mut Calls, lease: &Lease, memory: &mut pages::Store, packages: &mut registry::Store) throws (abi::Error) {
167 +
    let mut loading = load(calls, lease);
168 +
    assert loading.output == nil;
169 +
    let pending = try loader::reserveOutput(&mut memory.backings.pool, packages);
170 +
    match pending { case loader::OutputReservation::Held(output) => { set loading.output = output; }, }
171 +
    match lease { case Lease::Held(context) => { set calls.records[context.index].allocation = Allocation::Load(loading); }, }
172 +
}
173 +
174 +
/// Return all unpublished loader capacity under the metadata lock.
175 +
fn discardLoad(memory: &mut pages::Store, state: &mut loader::State, packages: &mut registry::Store,
176 +
    table: &mut capability::Table, loading: Loading, release: fn(&mut frames::Pool, frames::Run) throws (abi::Error))
177 +
{
178 +
    if let output = loading.output { loader::cancelOutputUsing(&mut memory.backings.pool, packages, loader::OutputReservation::Held(output), release); }
179 +
    loader::cancelUsing(state, memory, table, loader::Reservation::Held(loading.input), release);
180 +
}
181 +
182 +
/// Cancel a live continuation's load and release its stack pin under the lock.
183 +
export unsafe fn cancelLoad(calls: &mut Calls, store: &mut domains::Store, memory: &mut pages::Store,
184 +
    state: &mut loader::State, packages: &mut registry::Store, lease: Lease)
185 +
{
186 +
    match take(calls, store, lease) {
187 +
        case Allocation::Load(loading) => {
188 +
            let mut owner = try! domains::get(store, loading.input.owner);
189 +
            discardLoad(memory, state, packages, &mut owner.memory.table, loading, frames::release);
190 +
        },
191 +
        else => panic "loader reservation required",
192 +
    }
193 +
}
194 +
195 +
/// Publish successful loader work and release caller-owned temporary storage.
196 +
export unsafe fn finishLoad(calls: &mut Calls, store: &mut domains::Store, memory: &mut pages::Store,
197 +
    state: &mut loader::State, packages: &mut registry::Store, lease: Lease, result: LoadResult) -> abi::Handle throws (abi::Error)
198 +
{
199 +
    match take(calls, store, lease) {
200 +
        case Allocation::Load(loading) => {
201 +
            let mut owner = try! domains::get(store, loading.input.owner);
202 +
            if owner.state == domains::Lifecycle::Dead {
203 +
                discardLoad(memory, state, packages, &mut owner.memory.table, loading, frames::release);
204 +
                throw abi::Error::BadHandle;
205 +
            }
206 +
            let mut object: abi::Ref = undefined;
207 +
            match result {
208 +
                case LoadResult::Existing(existing) => { assert loading.output == nil; set object = existing; },
209 +
                case LoadResult::Compiled(compiled) => {
210 +
                    let output = loading.output else panic "missing loader output";
211 +
                    set object = loader::publishOutput(state, &mut memory.backings.pool, packages,
212 +
                        loader::OutputReservation::Held(output), compiled);
213 +
                },
214 +
            }
215 +
            return try loader::finish(state, memory, &mut owner.memory.table, loader::Reservation::Held(loading.input), object);
216 +
        },
217 +
        else => panic "loader reservation required",
218 +
    }
219 +
}
220 +
221 +
/// Read retained storage through its exact caller generation.
222 +
fn held(calls: &Calls, context: abi::Ref) -> Allocation {
223 +
    assert context.index < limits::CONTEXTS;
224 +
    assert (calls.active[context.index / 32] & (1 << (context.index % 32))) <> 0;
225 +
    assert calls.records[context.index].context == context;
226 +
    return calls.records[context.index].allocation;
227 +
}
228 +
229 +
/// Clear private page frames while the executing context retains hart ownership.
230 +
export fn clearPage(calls: &Calls, lease: &Lease) {
231 +
    match lease {
232 +
        case Lease::Held(context) => match held(calls, *context) {
233 +
            case Allocation::Page(allocation) => pages::clearAllocation(&allocation),
234 +
            else => panic "page reservation required",
235 +
        },
236 +
    }
237 +
}
238 +
239 +
/// Clear a private kernel stack while the caller retains hart ownership.
240 +
export fn clearContext(calls: &Calls, lease: &Lease) {
241 +
    match lease {
242 +
        case Lease::Held(context) => match held(calls, *context) {
243 +
            case Allocation::Context(allocation) => domains::contextClearAllocation(&allocation),
244 +
            else => panic "context reservation required",
245 +
        },
246 +
    }
247 +
}
248 +
249 +
/// Remove retained ownership before serialized publication or cancellation.
250 +
fn take(calls: &mut Calls, store: &mut domains::Store, lease: Lease) -> Allocation {
251 +
    match lease {
252 +
        case Lease::Held(context) => {
253 +
            let allocation = held(calls, context);
254 +
            set calls.active[context.index / 32] &= ~(1 << (context.index % 32));
255 +
            assert slots::matches(&store.contextSlots[..], context, slots::State::Live);
256 +
            set store.contexts[context.index].reservation = false;
257 +
            return allocation;
258 +
        },
259 +
    }
260 +
}
261 +
262 +
/// Return a page reservation for publication or cancellation under the lock.
263 +
export fn takePage(calls: &mut Calls, store: &mut domains::Store, lease: Lease) -> pages::Reservation {
264 +
    match take(calls, store, lease) {
265 +
        case Allocation::Page(allocation) => return pages::Reservation::Held(allocation),
266 +
        else => panic "page reservation required",
267 +
    }
268 +
}
269 +
270 +
/// Return a context reservation for publication or cancellation under the lock.
271 +
export fn takeContext(calls: &mut Calls, store: &mut domains::Store, lease: Lease) -> domains::ContextReservation {
272 +
    match take(calls, store, lease) {
273 +
        case Allocation::Context(allocation) => return domains::ContextReservation::Held(allocation),
274 +
        else => panic "context reservation required",
275 +
    }
276 +
}
277 +
278 +
/// Return domain storage for publication or cancellation under the lock.
279 +
export fn takeDomain(calls: &mut Calls, store: &mut domains::Store, lease: Lease) -> domains::Reservation {
280 +
    match take(calls, store, lease) {
281 +
        case Allocation::Domain(object) => return domains::Reservation::Held(creation(calls, object)),
282 +
        else => panic "domain reservation required",
283 +
    }
284 +
}
285 +
286 +
/// Retire one dead caller's private storage after execution ownership ends.
287 +
export unsafe fn cancelAllocations(calls: &mut Calls, store: &mut domains::Store, memory: &mut pages::Store,
288 +
    state: &mut loader::State, packages: &mut registry::Store) -> bool
289 +
{
290 +
    for word in 0..CONTEXT_WORDS {
291 +
        if calls.active[word] == 0 { continue; }
292 +
        for offset in 0..32 as u32 {
293 +
            let bit: u32 = 1 << offset;
294 +
            if (calls.active[word] & bit) == 0 { continue; }
295 +
            let index = word * 32 + offset;
296 +
            let retained = calls.records[index];
297 +
            let context = try domains::context(store, retained.owner, retained.context) catch { continue; };
298 +
            if context.hart <> nil or context.state <> domains::ContextState::Stopped { continue; }
299 +
            let mut owner = try! domains::get(store, retained.owner);
300 +
            if owner.state <> domains::Lifecycle::Dead { continue; }
301 +
            set calls.active[word] &= ~bit;
302 +
            match retained.allocation {
303 +
                case Allocation::Load(loading) => discardLoad(memory, state, packages, &mut owner.memory.table, loading, frames::retire),
304 +
                case Allocation::Page(allocation) => pages::cancelUsing(memory, &mut owner.memory.table, pages::Reservation::Held(allocation), frames::retire),
305 +
                case Allocation::Context(allocation) => domains::contextCancelUsing(store, memory, domains::ContextReservation::Held(allocation), frames::retire),
306 +
                case Allocation::Domain(object) => domains::cancelUsing(store, &mut memory.backings,
307 +
                    &mut owner.memory.table, domains::Reservation::Held(creation(calls, object)), frames::retire),
308 +
            }
309 +
            set store.contexts[index].reservation = false;
310 +
            return true;
311 +
        }
312 +
    }
313 +
    return false;
314 +
}
315 +
316 +
/// Release stopped callers' remote work after hart ownership ends.
317 +
/// Taken requests keep the context pinned until the target acknowledges them.
318 +
export fn cancelRequests(store: &mut domains::Store, requests: &mut remote::Store) {
319 +
    let mut touched: [u32; CONTEXT_WORDS] = [0; CONTEXT_WORDS];
320 +
    let mut retained: [u32; CONTEXT_WORDS] = [0; CONTEXT_WORDS];
321 +
    let mut cancelled = false;
322 +
    for i in 0..limits::REMOTE_REQUESTS {
323 +
        if requests.slots[i].state <> slots::State::Live { continue; }
324 +
        let request = requests.requests[i];
325 +
        let mut caller: ?abi::Ref = nil;
326 +
        match request.action {
327 +
            case remote::Action::Budget(call) => { set caller = call.context; },
328 +
            case remote::Action::Ready(context) => { set caller = context; },
329 +
            else => continue,
330 +
        }
331 +
        let object = caller else { continue; };
332 +
        if not slots::matches(&store.contextSlots[..], object, slots::State::Live) { continue; }
333 +
        if store.contexts[object.index].hart <> nil or store.contexts[object.index].state <> domains::ContextState::Stopped { continue; }
334 +
        if not slots::matches(&store.slots[..], store.contexts[object.index].owner, slots::State::Live)
335 +
            or store.records[store.contexts[object.index].owner.index].state <> domains::Lifecycle::Dead { continue; }
336 +
        let word = object.index / 32;
337 +
        let bit = 1 << (object.index % 32);
338 +
        set touched[word] |= bit;
339 +
        if request.state == remote::State::Delivered {
340 +
            set retained[word] |= bit;
341 +
            set store.contexts[object.index].pending = abi::Ref { index: i, generation: requests.slots[i].generation };
342 +
            continue;
343 +
        }
344 +
        try! remote::cancel(requests, request.source, abi::Ref { index: i, generation: requests.slots[i].generation });
345 +
        set cancelled = true;
346 +
    }
347 +
    if not cancelled { return; }
348 +
    for i in 0..limits::CONTEXTS {
349 +
        let bit = 1 << (i % 32);
350 +
        if (touched[i / 32] & bit) <> 0 and (retained[i / 32] & bit) == 0 {
351 +
            set store.contexts[i].pending = nil;
352 +
        }
353 +
    }
354 +
}
355 +
356 +
/// Advance one eligible domain from a rotating slot cursor.
357 +
export unsafe fn reapNext(store: &mut domains::Store, memory: &mut pages::Store, windows: &mut budgets::Store,
358 +
    irqs: &mut interrupts::Store) -> bool
359 +
{
360 +
    if store.dead == 0 { return false; }
361 +
    let mut checked = false;
362 +
    let mut blocked: [bool; limits::DOMAINS] = [false; limits::DOMAINS];
363 +
    for offset in 0..limits::DOMAINS {
364 +
        let index = (store.reapCursor + offset) % limits::DOMAINS;
365 +
        if store.slots[index].state <> slots::State::Live or store.records[index].state <> domains::Lifecycle::Dead { continue; }
366 +
        let owner = abi::Ref { index, generation: store.slots[index].generation };
367 +
        if store.reclaimed[index] {
368 +
            if not try! events::acknowledged(&store.events, owner) { continue; }
369 +
        } else if not store.reaping[index] {
370 +
            if not checked {
371 +
                for i in 0..limits::CONTEXTS {
372 +
                    let state = store.contextSlots[i].state;
373 +
                    if state <> slots::State::Live and state <> slots::State::Reserved { continue; }
374 +
                    if state == slots::State::Reserved or store.contexts[i].state <> domains::ContextState::Stopped
375 +
                        or store.contexts[i].hart <> nil or store.contexts[i].pending <> nil or store.contexts[i].reservation {
376 +
                        set blocked[store.contexts[i].owner.index] = true;
377 +
                    }
378 +
                }
379 +
                set checked = true;
380 +
            }
381 +
            if blocked[index] { continue; }
382 +
        }
383 +
        if try! reap(store, memory, windows, irqs, owner) {
384 +
            set store.reapCursor = (index + 1) % limits::DOMAINS;
385 +
            return true;
386 +
        }
387 +
    }
388 +
    return false;
389 +
}
390 +
391 +
/// Advance one dead domain's resource teardown under the metadata lock.
392 +
/// Returns false while execution ownership or terminal acknowledgement is pending.
393 +
export unsafe fn reap(store: &mut domains::Store, memory: &mut pages::Store, windows: &mut budgets::Store,
394 +
    irqs: &mut interrupts::Store, owner: abi::Ref) -> bool throws (abi::Error)
395 +
{
396 +
    if not slots::matches(&store.slots[..], owner, slots::State::Live) { throw abi::Error::BadHandle; }
397 +
    if store.reclaimed[owner.index] {
398 +
        if not try events::acknowledged(&store.events, owner) { return false; }
399 +
        try! domains::releaseIdentity(store, owner);
400 +
        return true;
401 +
    }
402 +
    if not store.reaping[owner.index] {
403 +
        if not try domains::quiescent(store, owner) { return false; }
404 +
        set store.reaping[owner.index] = true;
405 +
    }
406 +
    let mut domain = try! domains::get(store, owner);
407 +
    for i in 0..limits::HANDLES {
408 +
        let slot = domain.memory.table.slots[i];
409 +
        if slot.state == slots::State::Reserved { return false; }
410 +
        if slot.state <> slots::State::Live { continue; }
411 +
        let entry = domain.memory.table.entries[i];
412 +
        let handle = try! abi::handle(entry.kind, abi::Ref { index: i, generation: slot.generation });
413 +
        match entry.kind {
414 +
            case abi::Kind::Page => try! pages::drop(memory, &mut domain.memory.table, handle),
415 +
            case abi::Kind::Budget => try! budgets::drop(windows, &mut domain.memory.table, handle),
416 +
            case abi::Kind::Interrupt => {
417 +
                let number = try! interrupts::query(irqs, &domain.memory.table, handle);
418 +
                plic::mask(number);
419 +
                try! interrupts::drop(irqs, &mut domain.memory.table, handle);
420 +
            },
421 +
            else => { let removed = try! capability::invalidate(&mut domain.memory.table, handle); },
422 +
        }
423 +
        return true;
424 +
    }
425 +
    if memory.backings.domains[owner.index] <> 0 {
426 +
        let complete = try! backing::endDomainStep(&mut memory.backings, owner, 64);
427 +
        return true;
428 +
    }
429 +
    if try! domains::retireContext(store, &mut memory.backings.pool, owner) { return true; }
430 +
    try! domains::retireStorage(store, &mut memory.backings, owner);
431 +
    return true;
432 +
}
433 +
434 +
/// Publish retained terminal events and report receiver harts under the metadata lock.
435 +
export unsafe fn service(store: &mut domains::Store, windows: &budgets::Store, now: u64) -> u32 {
436 +
    if store.events.pending == 0 { return 0; }
437 +
    let mut notified: [bool; limits::DOMAINS] = [false; limits::DOMAINS];
438 +
    let mut changed = false;
439 +
    for i in 0..limits::DOMAINS {
440 +
        let terminal = store.events.terminals[i];
441 +
        if terminal.state <> events::State::Pending { continue; }
442 +
        let mut receiver = try domains::get(store, terminal.receiver) catch { continue; };
443 +
        if receiver.state == domains::Lifecycle::Dead { continue; }
444 +
        let delivered = try events::deliver(&mut store.events, terminal.subject, &mut receiver.memory.ring) catch { false };
445 +
        if delivered { set notified[terminal.receiver.index] = true; set changed = true; }
446 +
    }
447 +
    if not changed { return 0; }
448 +
    for i in 0..limits::CONTEXTS {
449 +
        if store.contextSlots[i].state <> slots::State::Live { continue; }
450 +
        if notified[store.contexts[i].owner.index] and store.contexts[i].state == domains::ContextState::Waiting {
451 +
            set store.contexts[i].state = domains::ContextState::Ready;
452 +
        }
453 +
    }
454 +
    let mut harts: u32 = 0;
455 +
    for i in 0..limits::BUDGETS {
456 +
        if windows.slots[i].state <> slots::State::Live { continue; }
457 +
        let window = windows.windows[i];
458 +
        if not slots::matches(&store.slots[..], window.owner, slots::State::Live)
459 +
            or not notified[window.owner.index] or window.end <= now { continue; }
460 +
        let context = window.context else { continue; };
461 +
        if not slots::matches(&store.contextSlots[..], context, slots::State::Live) { continue; }
462 +
        if store.contexts[context.index].owner == window.owner
463 +
            and store.contexts[context.index].state <> domains::ContextState::Stopped {
464 +
            set harts |= 1 << window.hart;
465 +
        }
466 +
    }
467 +
    return harts;
468 +
}
kernel/kernel/loader.rad +52 -18
140 140
    return Lease::Held(frames::commit(reservation));
141 141
}
142 142
143 143
/// Return temporary frames and release the compiler workspace under metadata serialization.
144 144
export fn release(state: &mut State, pool: &mut frames::Pool, lease: Lease) {
145 +
    releaseUsing(state, pool, lease, frames::release);
146 +
}
147 +
148 +
/// Transfer temporary frames to their release operation and clear workspace ownership.
149 +
fn releaseUsing(state: &mut State, pool: &mut frames::Pool, lease: Lease,
150 +
    release: fn(&mut frames::Pool, frames::Run) throws (abi::Error))
151 +
{
145 152
    match lease {
146 -
        case Lease::Held(run) => { try! frames::release(pool, run); },
153 +
        case Lease::Held(run) => { try! release(pool, run); },
147 154
    }
148 155
    set state.owner = abi::Ref { index: 0, generation: 0 };
149 156
    set state.busy = false;
150 157
}
151 158
229 236
}
230 237
231 238
/// Snapshot and decode trusted input into the held private workspace.
232 239
/// The decoded package stays valid until this reservation is completed or cancelled.
233 240
export unsafe fn decode(reservation: &Reservation) -> binary::Package throws (abi::Error) {
234 -
    match reservation {
235 -
        case Reservation::Held(input) => {
236 -
            let mut work = workspace(input.work);
237 -
            try! mem::copy(&mut work.input[..input.length], @sliceOf(memory(input.address), input.length));
238 -
            let mut decoder = alloc::new(&mut work.decoded[..]);
239 -
            return try program::decode(&work.input[..input.length], &mut decoder, binary::Limits { registers: 8192, blocks: 4096 }) catch err {
240 -
                if err == binary::Error::Storage { throw abi::Error::Exhausted; }
241 -
                throw abi::Error::VerifyFailed;
242 -
            };
243 -
        },
244 -
    }
241 +
    match reservation { case Reservation::Held(input) => return try decodeInput(input), }
242 +
}
243 +
244 +
/// Snapshot retained input and decode it in its private workspace.
245 +
export unsafe fn decodeInput(input: &Input) -> binary::Package throws (abi::Error) {
246 +
    let mut work = workspace(input.work);
247 +
    try! mem::copy(&mut work.input[..input.length], @sliceOf(memory(input.address), input.length));
248 +
    let mut decoder = alloc::new(&mut work.decoded[..]);
249 +
    return try program::decode(&work.input[..input.length], &mut decoder, binary::Limits { registers: 8192, blocks: 4096 }) catch err {
250 +
        if err == binary::Error::Storage { throw abi::Error::Exhausted; }
251 +
        throw abi::Error::VerifyFailed;
252 +
    };
245 253
}
246 254
247 255
/// Reserved native output and its immutable physical extents.
248 256
export record Output: Copy {
249 257
    /// Reserved package slot selected for generated state references.
288 296
    -> Compiled throws (abi::Error)
289 297
{
290 298
    match reservation {
291 299
        case Reservation::Held(source) => match output {
292 300
            case OutputReservation::Held(target) => {
293 -
                let work = workspace(source.work);
294 -
                return try generate(work, input, packages, target.object, target.codeBase, target.metadataBase, source.length);
301 +
                return try compileInput(packages, source, input, target);
295 302
            },
296 303
        },
297 304
    }
298 305
}
299 306
307 +
/// Generate native code into retained output while the caller owns its workspace.
308 +
export unsafe fn compileInput(packages: &registry::Store, source: &Input, input: &binary::Package, target: &Output)
309 +
    -> Compiled throws (abi::Error)
310 +
{
311 +
    let work = workspace(source.work);
312 +
    return try generate(work, input, packages, target.object, target.codeBase, target.metadataBase, source.length);
313 +
}
314 +
300 315
/// Return unpublished output frames and its package slot under serialization.
301 316
export fn cancelOutput(pool: &mut frames::Pool, packages: &mut registry::Store, reservation: OutputReservation) {
317 +
    cancelOutputUsing(pool, packages, reservation, frames::release);
318 +
}
319 +
320 +
/// Cancel a package slot and transfer private output to the release operation.
321 +
export fn cancelOutputUsing(pool: &mut frames::Pool, packages: &mut registry::Store, reservation: OutputReservation,
322 +
    release: fn(&mut frames::Pool, frames::Run) throws (abi::Error))
323 +
{
302 324
    match reservation {
303 325
        case OutputReservation::Held(output) => {
304 326
            assert slots::matches(&packages.slots[..], output.object, slots::State::Reserved);
305 -
            try! frames::release(pool, output.metadata);
306 -
            try! frames::release(pool, output.code);
327 +
            try! release(pool, output.metadata);
328 +
            try! release(pool, output.code);
307 329
            registry::cancel(packages, slots::Reservation::Held(output.object));
308 330
        },
309 331
    }
310 332
}
311 333
329 351
}
330 352
331 353
/// Compare the snapshot with immutable resident content while the workspace is held.
332 354
export unsafe fn identify(packages: &registry::Store, reservation: &Reservation, input: &binary::Package) -> ?abi::Ref throws (abi::Error) {
333 355
    match reservation {
334 -
        case Reservation::Held(source) => return try registry::identify(packages, &input.name[..], &workspace(source.work).input[..source.length]),
356 +
        case Reservation::Held(source) => return try identifyInput(packages, source, input),
335 357
    }
336 358
}
337 359
360 +
/// Compare retained source bytes with immutable resident package content.
361 +
export unsafe fn identifyInput(packages: &registry::Store, source: &Input, input: &binary::Package) -> ?abi::Ref throws (abi::Error) {
362 +
    return try registry::identify(packages, &input.name[..], &workspace(source.work).input[..source.length]);
363 +
}
364 +
338 365
/// Reserve an Image handle and exclusive workspace under metadata serialization.
339 366
export unsafe fn reserve(state: &mut State, store: &mut pages::Store,
340 367
    table: &mut capability::Table, request: Request) -> Reservation throws (abi::Error)
341 368
{
342 369
    let address = try source(store, table, request);
360 387
    assert slots::matches(&table.slots[..], input.handle, slots::State::Reserved);
361 388
}
362 389
363 390
/// Return the workspace and reserved handle under metadata serialization.
364 391
export fn cancel(state: &mut State, store: &mut pages::Store, table: &mut capability::Table, reservation: Reservation) {
392 +
    cancelUsing(state, store, table, reservation, frames::release);
393 +
}
394 +
395 +
/// Cancel a reserved Image handle and transfer its workspace to the release operation.
396 +
export fn cancelUsing(state: &mut State, store: &mut pages::Store, table: &mut capability::Table, reservation: Reservation,
397 +
    release: fn(&mut frames::Pool, frames::Run) throws (abi::Error))
398 +
{
365 399
    match reservation {
366 400
        case Reservation::Held(input) => {
367 401
            require(state, table, &input);
368 -
            release(state, &mut store.backings.pool, Lease::Held(input.frames));
402 +
            releaseUsing(state, &mut store.backings.pool, Lease::Held(input.frames), release);
369 403
            try! slots::cancel(&mut table.slots[..], slots::Reservation::Held(input.handle));
370 404
        },
371 405
    }
372 406
}
373 407
kernel/kernel/pages.rad +12 -2
140 140
    }
141 141
}
142 142
143 143
/// Clear private frames while shared metadata can be used by other harts.
144 144
export fn clear(reservation: &Reservation) {
145 -
    match reservation { case Reservation::Held(allocation) => zero(allocation.base, allocation.run.count), }
145 +
    match reservation { case Reservation::Held(allocation) => clearAllocation(allocation), }
146 146
}
147 147
148 +
/// Clear frames held by an unpublished allocation's exclusive owner.
149 +
export fn clearAllocation(allocation: &Allocation) { zero(allocation.base, allocation.run.count); }
150 +
148 151
/// Check reserved generations before publishing or cancelling an allocation.
149 152
fn require(store: &Store, table: &capability::Table, allocation: &Allocation) {
150 153
    assert table.owner == allocation.owner;
151 154
    assert slots::matches(&table.slots[..], allocation.handle, slots::State::Reserved);
152 155
    assert slots::matches(&store.slots[..], allocation.page, slots::State::Reserved);
171 174
    }
172 175
}
173 176
174 177
/// Return private allocation capacity under metadata serialization.
175 178
export fn cancel(store: &mut Store, table: &mut capability::Table, reservation: Reservation) {
179 +
    cancelUsing(store, table, reservation, frames::release);
180 +
}
181 +
182 +
/// Cancel unpublished slots and transfer their frames to the selected release operation.
183 +
export fn cancelUsing(store: &mut Store, table: &mut capability::Table, reservation: Reservation,
184 +
    release: fn(&mut frames::Pool, frames::Run) throws (abi::Error))
185 +
{
176 186
    match reservation {
177 187
        case Reservation::Held(allocation) => {
178 188
            require(store, table, &allocation);
179 -
            try! frames::release(&mut store.backings.pool, allocation.run);
189 +
            try! release(&mut store.backings.pool, allocation.run);
180 190
            try! slots::cancel(&mut store.backings.slots[..], slots::Reservation::Held(allocation.backing));
181 191
            try! slots::cancel(&mut store.slots[..], slots::Reservation::Held(allocation.page));
182 192
            try! slots::cancel(&mut table.slots[..], slots::Reservation::Held(allocation.handle));
183 193
        },
184 194
    }
kernel/kernel/sys.rad +22 -0
24 24
/// Copy authority to a domain with optional rights narrowing.
25 25
export fn capabilityGrant(handle: abi::Handle, target: abi::Handle, rights: u64) -> abi::Handle throws (abi::Error) {
26 26
    return abi::Handle(try result(ecall(abi::Operation::CapabilityGrant as u32, *handle as i64, *target as i64, rights as i64, 0)));
27 27
}
28 28
29 +
/// Set a child's lifecycle receiver with Destroy and Wake authority.
30 +
export fn domainReparent(domain: abi::Handle, parent: abi::Handle) throws (abi::Error) {
31 +
    try result(ecall(abi::Operation::DomainReparent as u32, *domain as i64, *parent as i64, 0, 0));
32 +
}
33 +
29 34
/// Load a trusted binary package from a readable Page range.
30 35
export fn imageLoad(authority: abi::Handle, source: abi::Handle, offset: u64, length: u64) -> abi::Handle throws (abi::Error) {
31 36
    return abi::Handle(try result(ecall(abi::Operation::ImageLoad as u32, *authority as i64, *source as i64, offset as i64, length as i64)));
32 37
}
33 38
39 +
/// Destroy the named domain or the calling domain through the zero sentinel.
40 +
export fn domainDestroy(handle: abi::Handle, flags: u64) throws (abi::Error) {
41 +
    try result(ecall(abi::Operation::DomainDestroy as u32, *handle as i64, flags as i64, 0, 0));
42 +
}
43 +
44 +
/// End the calling domain and report its normal exit status to its parent.
45 +
export fn exit(status: u32) -> ! {
46 +
    ecall(abi::Operation::Exit as u32, status as i64, 0, 0, 0);
47 +
    panic "exit returned";
48 +
}
49 +
50 +
/// End the calling domain and report an explicit-abort fault to its parent.
51 +
export fn abort() -> ! {
52 +
    ecall(abi::Operation::Abort as u32, 0, 0, 0, 0);
53 +
    panic "abort returned";
54 +
}
55 +
34 56
/// Read resident package metadata through a readable Image capability.
35 57
export fn queryImage(handle: abi::Handle) -> abi::ImageInfo throws (abi::Error) {
36 58
    unsafe {
37 59
        let mut info: abi::ImageInfo = undefined;
38 60
        try result(ecall(abi::Operation::QueryImage as u32, *handle as i64, (&mut info) as i64, @sizeOf(abi::ImageInfo) as i64, 0));
kernel/kernel/tests.rad +1 -0
15 15
export mod interrupts;
16 16
export mod registry;
17 17
export mod loader;
18 18
export mod instances;
19 19
export mod domains;
20 +
export mod lifecycle;
20 21
export mod budgets;
21 22
export mod dispatch;
22 23
export mod calls;
23 24
export mod timers;
24 25
export mod remote;
kernel/kernel/tests/backing.rad +58 -0
24 24
    let reservation = try! frames::reserve(&mut STORE.pool, 4);
25 25
    let run = frames::commit(reservation);
26 26
    return backing::publish(&mut STORE, slot, run, abi::Ref { index: 0, generation: 1 });
27 27
}
28 28
29 +
/// Exposure teardown advances in bounded steps and retains shared allocations.
30 +
@test unsafe fn boundedEnd() throws (testing::TestError) {
31 +
    initialize();
32 +
    let owner = abi::Ref { index: 0, generation: 1 };
33 +
    let receiver = abi::Ref { index: 1, generation: 1 };
34 +
    let mut objects: [abi::Ref; 3] = undefined;
35 +
    for i in 0..3 {
36 +
        let slot = try! slots::reserve(&mut STORE.slots[..]);
37 +
        let run = frames::commit(try! frames::reserve(&mut STORE.pool, 1));
38 +
        set objects[i] = backing::publish(&mut STORE, slot, run, owner);
39 +
    }
40 +
    assert STORE.used == 3;
41 +
    try! backing::expose(&mut STORE, objects[1], receiver);
42 +
    try! backing::releasePage(&mut STORE, objects[0]);
43 +
    try! backing::releasePage(&mut STORE, objects[1]);
44 +
    let mut invalid = false;
45 +
    try backing::endDomainStep(&mut STORE, owner, 0) catch error {
46 +
        assert error == abi::Error::InvalidArg; set invalid = true;
47 +
    };
48 +
    assert invalid and backing::domainLive(&STORE, owner);
49 +
    assert not try! backing::endDomainStep(&mut STORE, owner, 1);
50 +
    assert not backing::domainLive(&STORE, owner);
51 +
    assert STORE.slots[objects[0].index].state == slots::State::Free;
52 +
    assert STORE.pool.retiredCount == 1 and not STORE.pool.free[0];
53 +
    assert STORE.records[objects[1].index].exposed == 2;
54 +
    let mut busy = false;
55 +
    try backing::registerDomain(&mut STORE, abi::Ref { index: 0, generation: 2 }) catch error {
56 +
        assert error == abi::Error::Busy; set busy = true;
57 +
    };
58 +
    assert busy;
59 +
    let mut denied = false;
60 +
    try backing::expose(&mut STORE, objects[2], owner) catch error {
61 +
        assert error == abi::Error::BadHandle; set denied = true;
62 +
    };
63 +
    assert denied;
64 +
    assert not try! backing::endDomainStep(&mut STORE, owner, 1);
65 +
    assert STORE.records[objects[1].index].exposed == 1;
66 +
    assert STORE.records[objects[2].index].exposed == 1;
67 +
    while not try! backing::endDomainStep(&mut STORE, owner, 64) {}
68 +
    assert STORE.records[objects[2].index].exposed == 0 and STORE.records[objects[2].index].pages == 1;
69 +
    assert STORE.pool.retiredCount == 1;
70 +
    assert frames::reclaim(&mut STORE.pool, 1) == 1 and STORE.pool.free[0];
71 +
    assert not STORE.pool.free[1] and not STORE.pool.free[2];
72 +
    let next = abi::Ref { index: 0, generation: 2 };
73 +
    try! backing::registerDomain(&mut STORE, next);
74 +
    assert backing::domainLive(&STORE, next);
75 +
    let mut stale = false;
76 +
    try backing::endDomainStep(&mut STORE, owner, 64) catch error {
77 +
        assert error == abi::Error::BadHandle; set stale = true;
78 +
    };
79 +
    assert stale and backing::domainLive(&STORE, next);
80 +
    try! backing::releasePage(&mut STORE, objects[2]);
81 +
    assert STORE.pool.free[2];
82 +
    while not try! backing::endDomainStep(&mut STORE, receiver, 64) {}
83 +
    assert STORE.pool.retiredCount == 1 and not STORE.pool.free[1];
84 +
    assert frames::reclaim(&mut STORE.pool, 1) == 1 and STORE.pool.free[1];
85 +
}
86 +
29 87
/// Dropping all page handles preserves capacity while an exposed domain lives.
30 88
@test unsafe fn exposures() throws (testing::TestError) {
31 89
    initialize();
32 90
    let object = allocate();
33 91
    let receiver = abi::Ref { index: 1, generation: 1 };
kernel/kernel/tests/budgets.rad +15 -2
33 33
    let source = abi::Ref { index: 7, generation: 1 };
34 34
    set STORE.windows[entry.object.index].context = source;
35 35
    let current = abi::CurrentContextInfo { context: abi::id(source), hart: 0, budget: *budget };
36 36
    let before = STORE.windows[entry.object.index];
37 37
    let mut rejected: u32 = 0;
38 +
    set DOMAINS.records[permission.object.index].state = domains::Lifecycle::Dead;
39 +
    for phase in 0..3 {
40 +
        if phase == 1 { set DOMAINS.reclaimed[permission.object.index] = true; }
41 +
        if phase == 2 { set DOMAINS.slots[permission.object.index].state = slots::State::Free; }
42 +
        try budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30) catch error {
43 +
            assert error == abi::Error::BadHandle; set rejected += 1;
44 +
        };
45 +
        assert STORE.windows[entry.object.index] == before;
46 +
        assert try! capability::get(&TABLE, budget) == entry;
47 +
    }
48 +
    set DOMAINS.slots[permission.object.index].state = slots::State::Live;
49 +
    set DOMAINS.reclaimed[permission.object.index] = false;
50 +
    set DOMAINS.records[permission.object.index].state = domains::Lifecycle::Pending;
38 51
    try budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30) catch error {
39 52
        assert (error == abi::Error::Busy); set rejected += 1;
40 53
    };
41 54
    set DOMAINS.records[permission.object.index].state = domains::Lifecycle::Active;
42 55
    set DOMAINS.contexts[initial.index].state = domains::ContextState::Waiting;
66 79
    set TABLE.entries[targetSlot.object.index].rights = permission.rights;
67 80
    for i in 0..MEMORY.table.slots.len { set MEMORY.table.slots[i].state = slots::State::Retired; }
68 81
    try budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30) catch error {
69 82
        assert (error == abi::Error::Busy); set rejected += 1;
70 83
    };
71 -
    assert (rejected == 7 and STORE.windows[entry.object.index] == before);
84 +
    assert (rejected == 10 and STORE.windows[entry.object.index] == before);
72 85
    assert (try! capability::get(&TABLE, budget) == entry);
73 86
    capability::initialize(&mut MEMORY.table, permission.object);
74 87
    try! budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30);
75 88
    let after = STORE.windows[entry.object.index];
76 89
    assert (after.start == 30 and after.end == 100 and after.hart == 0);
185 198
    let child = slots::reference(&childSlot);
186 199
    capability::initialize(&mut MEMORY.table, child);
187 200
    let contextSlot = try! slots::reserve(&mut DOMAINS.contextSlots[..]);
188 201
    let context = slots::reference(&contextSlot);
189 202
    set DOMAINS.contexts[context.index] = domains::Context {
190 -
        owner: child, state: domains::ContextState::Ready, hart: nil, pending: nil,
203 +
        owner: child, state: domains::ContextState::Ready, hart: nil, pending: nil, reservation: false,
191 204
        kernelFrames: frames::Run { first: 0, count: 0 }, kernelStack: range::Range { start: 0, end: 0 },
192 205
        userStack: range::Range { start: 0, end: 0 },
193 206
        frame: trap::Frame { registers: [0; 32], pc: 4, status: 0x80, cause: 0, value: 0 },
194 207
    };
195 208
    set DOMAINS.records[child.index] = domains::Domain {
kernel/kernel/tests/calls.rad +206 -0
12 12
use kernel::limits;
13 13
use kernel::trap;
14 14
use kernel::budgets;
15 15
use kernel::remote;
16 16
use kernel::interrupts;
17 +
use kernel::events;
18 +
19 +
/// Event storage for administrative destruction targets.
20 +
unsafe static TARGETS: [domains::Memory; 2] = undefined;
21 +
22 +
/// Retained dead-domain records cannot be queried or delegated through handles.
23 +
@test unsafe fn deadHandles() throws (testing::TestError) {
24 +
    let owner = initialize();
25 +
    let pending = try! slots::reserve(&mut domains::STORE.slots[..]);
26 +
    let object = try! slots::commit(&mut domains::STORE.slots[..], pending);
27 +
    try! backing::registerDomain(&mut pages::STORE.backings, object);
28 +
    set domains::STORE.records[object.index].state = domains::Lifecycle::Dead;
29 +
    set domains::STORE.records[object.index].creator = owner;
30 +
    set domains::STORE.records[object.index].parent = nil;
31 +
    set domains::STORE.records[object.index].initial = object;
32 +
    set domains::STORE.contextSlots[object.index] = slots::Slot { generation: object.generation, state: slots::State::Live };
33 +
    set domains::STORE.contexts[object.index].owner = object;
34 +
    set domains::STORE.contexts[object.index].state = domains::ContextState::Stopped;
35 +
    set domains::STORE.contexts[object.index].hart = nil;
36 +
    set domains::STORE.contexts[object.index].userStack = { start: 0, end: 0 };
37 +
    for operation in [47 as u64, 48, 62, 10, 12, 21, 61, 20, 30, 51] {
38 +
        let mut kind = abi::Kind::Domain;
39 +
        if operation == 48 { set kind = abi::Kind::Events; }
40 +
        let handle = try! capability::install(&mut MEMORY.table, capability::Entry {
41 +
            kind: kind, object: object,
42 +
            rights: abi::Rights(abi::GRANT | abi::EXECUTE | abi::DESTROY | abi::CREATE | abi::ALLOCATE),
43 +
        });
44 +
        let mut rejected = false;
45 +
        let mut argument: u64 = 0;
46 +
        if operation == 61 { set argument = abi::id(object); }
47 +
        try calls::invoke(owner, operation, &[*handle, argument, 0, 0], 0) catch error {
48 +
            assert error == abi::Error::BadHandle;
49 +
            set rejected = true;
50 +
        };
51 +
        assert rejected;
52 +
        assert (try! capability::get(&MEMORY.table, handle)).object == object;
53 +
    }
54 +
}
55 +
56 +
/// Administrative destruction checks authority and follows creation ancestry only on request.
57 +
@test unsafe fn administrativeTermination() throws (testing::TestError) {
58 +
    for flags in [0 as u64, abi::CASCADE] {
59 +
        let owner = initialize();
60 +
        try! events::open(&mut domains::STORE.events, owner, &mut MEMORY.ring);
61 +
        let mut objects: [abi::Ref; 2] = undefined;
62 +
        for i in 0..2 {
63 +
            let pending = try! slots::reserve(&mut domains::STORE.slots[..]);
64 +
            let object = try! slots::commit(&mut domains::STORE.slots[..], pending);
65 +
            set objects[i] = object;
66 +
            set domains::STORE.records[object.index].state = domains::Lifecycle::Pending;
67 +
            set domains::STORE.records[object.index].creator = owner;
68 +
            set domains::STORE.records[object.index].parent = owner;
69 +
            set domains::STORE.records[object.index].memory = &mut TARGETS[i];
70 +
            try! events::open(&mut domains::STORE.events, object, &mut TARGETS[i].ring);
71 +
            set domains::STORE.contextSlots[object.index] = slots::Slot { generation: 1, state: slots::State::Live };
72 +
            set domains::STORE.contexts[object.index].owner = object;
73 +
            set domains::STORE.contexts[object.index].state = domains::ContextState::Ready;
74 +
            set domains::STORE.contexts[object.index].hart = 1 + 2 * i;
75 +
        }
76 +
        set domains::STORE.records[objects[1].index].creator = objects[0];
77 +
        set domains::STORE.records[objects[1].index].parent = objects[0];
78 +
        set domains::STORE.ancestors[objects[1].index][0] = 1 << objects[0].index;
79 +
        let handle = try! capability::install(&mut MEMORY.table, capability::Entry {
80 +
            kind: abi::Kind::Domain, object: objects[0], rights: abi::Rights(0),
81 +
        });
82 +
        let mut denied = false;
83 +
        try calls::termination(owner, 22, &[*handle, flags, 0, 0]) catch error {
84 +
            assert error == abi::Error::Denied; set denied = true;
85 +
        };
86 +
        assert denied and domains::STORE.dead == 0;
87 +
        let decoded = try! abi::decode(handle);
88 +
        set MEMORY.table.entries[decoded.object.index].rights = abi::Rights(abi::DESTROY);
89 +
        let mut invalid = false;
90 +
        try calls::termination(owner, 22, &[*handle, 2, 0, 0]) catch error {
91 +
            assert error == abi::Error::InvalidArg; set invalid = true;
92 +
        };
93 +
        assert invalid and domains::STORE.dead == 0;
94 +
        let stopped = try! calls::termination(owner, 22, &[*handle, flags, 0, 0]);
95 +
        assert domains::STORE.events.terminals[objects[0].index].event.code == 0;
96 +
        if flags == 0 {
97 +
            assert stopped == 2 and domains::STORE.records[objects[1].index].state == domains::Lifecycle::Pending;
98 +
            assert domains::STORE.records[objects[1].index].parent == nil;
99 +
        } else {
100 +
            assert stopped == 10 and domains::STORE.records[objects[1].index].state == domains::Lifecycle::Dead;
101 +
        }
102 +
        let mut dead = false;
103 +
        try calls::termination(owner, 22, &[*handle, 0, 0, 0]) catch error {
104 +
            assert error == abi::Error::BadHandle; set dead = true;
105 +
        };
106 +
        assert dead;
107 +
    }
108 +
}
109 +
110 +
/// Self termination validates arguments and reports the requested terminal kind.
111 +
@test unsafe fn terminationCalls() throws (testing::TestError) {
112 +
    for operation in [22 as u64, 43, 49] {
113 +
        let owner = initialize();
114 +
        try! events::open(&mut domains::STORE.events, owner, &mut MEMORY.ring);
115 +
        let pending = try! slots::reserve(&mut domains::STORE.contextSlots[..]);
116 +
        let context = try! slots::commit(&mut domains::STORE.contextSlots[..], pending);
117 +
        set domains::STORE.contexts[context.index].owner = owner;
118 +
        set domains::STORE.contexts[context.index].state = domains::ContextState::Ready;
119 +
        set domains::STORE.contexts[context.index].hart = 2;
120 +
        let mut invalid = false;
121 +
        try calls::termination(owner, operation, &[0, 0, 1, 0]) catch error {
122 +
            assert error == abi::Error::InvalidArg; set invalid = true;
123 +
        };
124 +
        assert invalid and domains::STORE.records[owner.index].state == domains::Lifecycle::Active;
125 +
        let mut code: u64 = 0;
126 +
        if operation == 49 { set code = 0xffffffff; }
127 +
        assert try! calls::termination(owner, operation, &[code, 0, 0, 0]) == 4;
128 +
        let terminal = domains::STORE.events.terminals[owner.index];
129 +
        assert terminal.subject == owner;
130 +
        if operation == 43 {
131 +
            assert terminal.event.kind == events::FAULT and terminal.event.code == abi::FAULT_ABORT;
132 +
        } else {
133 +
            assert terminal.event.kind == events::CHILD_EXIT and terminal.event.code == code as u32;
134 +
        }
135 +
        assert domains::STORE.contexts[context.index].state == domains::ContextState::Stopped;
136 +
        assert domains::STORE.contexts[context.index].hart == 2;
137 +
    }
138 +
    let owner = initialize();
139 +
    for arguments in &[[0 as u64, 1, 0, 0], [0, 2, 0, 0]] {
140 +
        let mut invalid = false;
141 +
        try calls::termination(owner, 22, &arguments[..]) catch error {
142 +
            assert error == abi::Error::InvalidArg; set invalid = true;
143 +
        };
144 +
        assert invalid;
145 +
    }
146 +
    let mut overflow = false;
147 +
    try calls::termination(owner, 49, &[0x100000000, 0, 0, 0]) catch error {
148 +
        assert error == abi::Error::InvalidArg; set overflow = true;
149 +
    };
150 +
    assert overflow;
151 +
}
152 +
153 +
/// Parenting requires both capabilities and preserves creation ancestry.
154 +
@test unsafe fn reparent() throws (testing::TestError) {
155 +
    let owner = initialize();
156 +
    let childSlot = try! slots::reserve(&mut domains::STORE.slots[..]);
157 +
    let child = try! slots::commit(&mut domains::STORE.slots[..], childSlot);
158 +
    let parentSlot = try! slots::reserve(&mut domains::STORE.slots[..]);
159 +
    let parent = try! slots::commit(&mut domains::STORE.slots[..], parentSlot);
160 +
    set domains::STORE.records[child.index].state = domains::Lifecycle::Pending;
161 +
    set domains::STORE.records[child.index].creator = owner;
162 +
    set domains::STORE.records[child.index].parent = owner;
163 +
    set domains::STORE.records[parent.index].state = domains::Lifecycle::Active;
164 +
    let childHandle = try! capability::install(&mut MEMORY.table, capability::Entry {
165 +
        kind: abi::Kind::Domain, object: child, rights: abi::Rights(abi::DESTROY),
166 +
    });
167 +
    let parentHandle = try! capability::install(&mut MEMORY.table, capability::Entry {
168 +
        kind: abi::Kind::Domain, object: parent, rights: abi::Rights(abi::WAKE),
169 +
    });
170 +
    let changed = try! calls::invoke(owner, 23, &[*childHandle, *parentHandle, 0, 0], 0);
171 +
    assert changed == 0 and domains::STORE.records[child.index].parent == parent;
172 +
    assert domains::STORE.records[child.index].creator == owner;
173 +
    let repeated = try! calls::invoke(owner, 23, &[*childHandle, *parentHandle, 0, 0], 0);
174 +
    assert repeated == 0;
175 +
    let childCap = try! abi::decode(childHandle);
176 +
    let parentCap = try! abi::decode(parentHandle);
177 +
    let mut denied: u32 = 0;
178 +
    for index in &[childCap.object.index, parentCap.object.index] {
179 +
        let rights = MEMORY.table.entries[index].rights;
180 +
        set MEMORY.table.entries[index].rights = abi::Rights(0);
181 +
        try calls::invoke(owner, 23, &[*childHandle, *parentHandle, 0, 0], 0) catch error {
182 +
            assert error == abi::Error::Denied; set denied += 1;
183 +
        };
184 +
        set MEMORY.table.entries[index].rights = rights;
185 +
    }
186 +
    assert denied == 2 and domains::STORE.records[child.index].parent == parent;
187 +
    let mut invalid: u32 = 0;
188 +
    for arguments in &[[*childHandle, *parentHandle, 1, 0], [*childHandle, *parentHandle, 0, 1]] {
189 +
        try calls::invoke(owner, 23, &arguments[..], 0) catch error {
190 +
            assert error == abi::Error::InvalidArg; set invalid += 1;
191 +
        };
192 +
    }
193 +
    assert invalid == 2;
194 +
    let mut dead: u32 = 0;
195 +
    for object in &[child, parent] {
196 +
        set domains::STORE.records[object.index].state = domains::Lifecycle::Dead;
197 +
        try calls::invoke(owner, 23, &[*childHandle, *parentHandle, 0, 0], 0) catch error {
198 +
            assert error == abi::Error::BadHandle; set dead += 1;
199 +
        };
200 +
        set domains::STORE.records[object.index].state = domains::Lifecycle::Active;
201 +
    }
202 +
    assert dead == 2 and domains::STORE.records[child.index].parent == parent;
203 +
    try! slots::release(&mut domains::STORE.slots[..], parent);
204 +
    try calls::invoke(owner, 23, &[*childHandle, *parentHandle, 0, 0], 0) catch error {
205 +
        assert error == abi::Error::BadHandle; set dead += 1;
206 +
    };
207 +
    assert dead == 3 and domains::STORE.records[child.index].creator == owner;
208 +
}
17 209
18 210
/// Interrupt queries write exactly four bytes at four-byte-aligned addresses.
19 211
@test unsafe fn interruptRecord() throws (testing::TestError) {
20 212
    let owner = initialize();
21 213
    interrupts::initialize(&mut interrupts::STORE, 8);
228 420
        try testing::expect(error == abi::Error::BadHandle); set rejected += 1;
229 421
    };
230 422
    try testing::expect(rejected == 6 and domains::STORE.contextSlots[0].state == slots::State::Free);
231 423
}
232 424
425 +
/// Invalid infallible queries terminate the caller at the syscall boundary.
426 +
@test unsafe fn invalidQueryAborts() throws (testing::TestError) {
427 +
    for operation in [44 as u64, 46, 47, 48] {
428 +
        let owner = initialize();
429 +
        try! events::open(&mut domains::STORE.events, owner, &mut MEMORY.ring);
430 +
        let mut frame = trap::Frame { registers: [0; 32], pc: 0x80000000, status: 0x80, cause: 8, value: 0 };
431 +
        set frame.registers[17] = operation;
432 +
        calls::handle(owner, &mut frame, 0, calls::invoke);
433 +
        assert domains::STORE.records[owner.index].state == domains::Lifecycle::Dead;
434 +
        assert domains::STORE.events.terminals[owner.index].event.code == abi::FAULT_ABORT;
435 +
        assert domains::STORE.events.terminals[owner.index].event.kind == events::FAULT;
436 +
    }
437 +
}
438 +
233 439
/// An unknown user operation advances PC and returns only a negative a0 error.
234 440
@test unsafe fn trapReply() throws (testing::TestError) {
235 441
    let owner = initialize();
236 442
    let mut frame = trap::Frame { registers: [123; 32], pc: 0x80000000, status: 0x80, cause: 8, value: 0 };
237 443
    set frame.registers[17] = 0xffffffffffffffff;
kernel/kernel/tests/domains.rad +357 -4
6 6
use kernel::slots;
7 7
use kernel::backing;
8 8
use kernel::capability;
9 9
use kernel::registry;
10 10
use kernel::domains;
11 +
use kernel::lifecycle;
12 +
use kernel::loader;
13 +
use kernel::frames;
14 +
use kernel::budgets;
15 +
use kernel::interrupts;
11 16
use kernel::transactions;
12 17
use kernel::pages;
13 18
use kernel::events;
14 19
20 +
/// Exclusive resources released by the domain reaper.
21 +
unsafe static WINDOWS: budgets::Store = undefined;
22 +
/// Interrupt ownership released after hardware masking.
23 +
unsafe static IRQS: interrupts::Store = undefined;
24 +
25 +
/// Caller-owned storage used while a context stack is initialized.
26 +
unsafe static CALLS: lifecycle::Calls = undefined;
27 +
15 28
/// Mapped physical memory for child metadata and package instances.
16 29
static RAM: [u64; 131072] = [0; 131072];
17 30
/// Domain and context metadata under test.
18 31
unsafe static DOMAINS: domains::Store = undefined;
19 32
/// Physical allocation and exposure state.
40 53
        set BACKINGS.pool.addresses[i] = aligned + i as u64 * 4096;
41 54
        set BACKINGS.pool.free[i] = true;
42 55
    }
43 56
    let pending = try! slots::reserve(&mut DOMAINS.slots[..]);
44 57
    let owner = try! slots::commit(&mut DOMAINS.slots[..], pending);
58 +
    set DOMAINS.records[owner.index].state = domains::Lifecycle::Active;
59 +
    set DOMAINS.records[owner.index].parent = nil;
60 +
    set DOMAINS.records[owner.index].creator = owner;
45 61
    try! backing::registerDomain(&mut BACKINGS, owner);
46 62
    capability::initialize(&mut TABLE, owner);
47 63
    let authority = try! capability::install(&mut TABLE, capability::Entry {
48 64
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(rights),
49 65
    });
55 71
        template: &[7], memory: 8, alignment: 8, relocations: &[],
56 72
    });
57 73
    set IMAGE = try! registry::install(&PACKAGES, &mut TABLE, object);
58 74
}
59 75
76 +
/// Interrupted creation returns all private storage after its caller releases its hart.
77 +
@test unsafe fn cancelledCreation() throws (testing::TestError) {
78 +
    initialize(abi::CREATE);
79 +
    lifecycle::initialize(&mut CALLS);
80 +
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
81 +
    let permit = try! capability::get(&TABLE, handle);
82 +
    let mut caller = try! domains::get(&DOMAINS, permit.object);
83 +
    set DOMAINS.records[permit.object.index].state = domains::Lifecycle::Active;
84 +
    let authority = try! capability::install(&mut caller.memory.table, capability::Entry {
85 +
        kind: abi::Kind::Domain, object: permit.object, rights: abi::Rights(abi::CREATE),
86 +
    });
87 +
    let root = caller.image;
88 +
    let image = try! registry::install(&PACKAGES, &mut caller.memory.table, root);
89 +
    set PAGES.backings = BACKINGS;
90 +
    let pending = try! domains::reserve(&mut DOMAINS, &mut PAGES.backings, &PACKAGES, &mut caller.memory.table, authority, image);
91 +
    let mut creation: domains::Creation = undefined;
92 +
    match &pending { case domains::Reservation::Held(value) => { set creation = *value; }, }
93 +
    assert DOMAINS.contexts[creation.context.index].owner == creation.domain;
94 +
    let lease = lifecycle::holdDomain(&mut CALLS, &mut DOMAINS, caller.initial, pending);
95 +
    try! lifecycle::prepareDomain(&CALLS, &PACKAGES, &lease);
96 +
    lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut loader::STATE, &mut registry::STORE);
97 +
    assert DOMAINS.slots[creation.domain.index].state == slots::State::Reserved;
98 +
    set DOMAINS.records[permit.object.index].state = domains::Lifecycle::Dead;
99 +
    set DOMAINS.contexts[caller.initial.index].state = domains::ContextState::Stopped;
100 +
    set DOMAINS.contexts[caller.initial.index].hart = 1;
101 +
    lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut loader::STATE, &mut registry::STORE);
102 +
    assert DOMAINS.contexts[caller.initial.index].reservation;
103 +
    assert not PAGES.backings.pool.free[creation.storage.allocation.first];
104 +
    match lease { case lifecycle::Lease::Held(context) => { assert context == caller.initial; }, }
105 +
    set DOMAINS.contexts[caller.initial.index].hart = nil;
106 +
    lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut loader::STATE, &mut registry::STORE);
107 +
    assert not DOMAINS.contexts[caller.initial.index].reservation;
108 +
    assert DOMAINS.slots[creation.domain.index].state == slots::State::Free;
109 +
    assert DOMAINS.contextSlots[creation.context.index].state == slots::State::Free;
110 +
    assert caller.memory.table.slots[creation.handle.index].state == slots::State::Free;
111 +
    assert PAGES.backings.pool.retiredCount == 3;
112 +
    while PAGES.backings.pool.retiredCount > 0 {
113 +
        let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256);
114 +
        assert reclaimed > 0 and reclaimed <= 256;
115 +
    }
116 +
    for run in [creation.storage.allocation, creation.storage.kernelFrames, creation.storage.graph.frames] {
117 +
        for i in run.first..run.first + run.count { assert PAGES.backings.pool.free[i]; }
118 +
    }
119 +
    set DOMAINS.records[permit.object.index].state = domains::Lifecycle::Active;
120 +
    let next = try! domains::reserve(&mut DOMAINS, &mut PAGES.backings, &PACKAGES, &mut caller.memory.table, authority, image);
121 +
    let active = lifecycle::holdDomain(&mut CALLS, &mut DOMAINS, caller.initial, next);
122 +
    try! lifecycle::prepareDomain(&CALLS, &PACKAGES, &active);
123 +
    let ready = lifecycle::takeDomain(&mut CALLS, &mut DOMAINS, active);
124 +
    let published = try! domains::publish(&mut DOMAINS, &mut PAGES.backings, &mut caller.memory.table, ready);
125 +
    let entry = try! capability::get(&caller.memory.table, published);
126 +
    assert entry.object.index == creation.domain.index and entry.object.generation <> creation.domain.generation;
127 +
    assert (try! domains::get(&DOMAINS, entry.object)).state == domains::Lifecycle::Pending;
128 +
    assert not DOMAINS.contexts[caller.initial.index].reservation;
129 +
}
130 +
131 +
/// Private storage and terminal identity have separate reclamation boundaries.
132 +
@test unsafe fn reclaimDomain() throws (testing::TestError) {
133 +
    initialize(abi::CREATE);
134 +
    let parentHandle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
135 +
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
136 +
    let entry = try! capability::get(&TABLE, handle);
137 +
    let child = try! domains::get(&DOMAINS, entry.object);
138 +
    try! domains::reparent(&mut DOMAINS, &TABLE, handle, parentHandle);
139 +
    let mut private = child.memory;
140 +
    let authority = try! capability::install(&mut private.table, capability::Entry {
141 +
        kind: abi::Kind::Domain, object: entry.object, rights: abi::Rights(abi::CREATE),
142 +
    });
143 +
    let image = try! registry::install(&PACKAGES, &mut private.table, child.image);
144 +
    let descendantHandle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut private.table, authority, image);
145 +
    let descendant = try! capability::get(&private.table, descendantHandle);
146 +
    let word = entry.object.index / 32;
147 +
    let bit = 1 << (entry.object.index % 32);
148 +
    assert (DOMAINS.ancestors[descendant.object.index][word] & bit) <> 0;
149 +
    assert not try! domains::quiescent(&DOMAINS, entry.object);
150 +
    set DOMAINS.contexts[child.initial.index].hart = 1;
151 +
    assert try! domains::terminate(&mut DOMAINS, entry.object, events::CHILD_EXIT, 17) == 2;
152 +
    assert not try! domains::quiescent(&DOMAINS, entry.object);
153 +
    set DOMAINS.contexts[child.initial.index].hart = nil;
154 +
    set DOMAINS.contexts[child.initial.index].pending = abi::Ref { index: 0, generation: 1 };
155 +
    assert not try! domains::quiescent(&DOMAINS, entry.object);
156 +
    set DOMAINS.contexts[child.initial.index].pending = nil;
157 +
    set DOMAINS.contexts[child.initial.index].reservation = true;
158 +
    assert not try! domains::quiescent(&DOMAINS, entry.object);
159 +
    set DOMAINS.contexts[child.initial.index].reservation = false;
160 +
    let reserved = try! slots::reserve(&mut DOMAINS.contextSlots[..]);
161 +
    let context = slots::reference(&reserved);
162 +
    set DOMAINS.contexts[context.index].owner = entry.object;
163 +
    assert not try! domains::quiescent(&DOMAINS, entry.object);
164 +
    let mut busy = false;
165 +
    try domains::retireContext(&mut DOMAINS, &mut BACKINGS.pool, entry.object) catch error {
166 +
        assert error == abi::Error::Busy; set busy = true;
167 +
    };
168 +
    assert busy and BACKINGS.pool.retiredCount == 0;
169 +
    try! slots::cancel(&mut DOMAINS.contextSlots[..], reserved);
170 +
    assert try! domains::quiescent(&DOMAINS, entry.object);
171 +
    assert try! domains::retireContext(&mut DOMAINS, &mut BACKINGS.pool, entry.object);
172 +
    assert not try! domains::retireContext(&mut DOMAINS, &mut BACKINGS.pool, entry.object);
173 +
    let mut handles = false;
174 +
    try domains::retireStorage(&mut DOMAINS, &mut BACKINGS, entry.object) catch error {
175 +
        assert error == abi::Error::Busy; set handles = true;
176 +
    };
177 +
    assert handles;
178 +
    let mut memory = child.memory;
179 +
    for cap in [child.events, authority, image, descendantHandle] {
180 +
        let removed = try! capability::invalidate(&mut memory.table, cap);
181 +
    }
182 +
    let mut exposed = false;
183 +
    try domains::retireStorage(&mut DOMAINS, &mut BACKINGS, entry.object) catch error {
184 +
        assert error == abi::Error::Busy; set exposed = true;
185 +
    };
186 +
    assert exposed;
187 +
    while not try! backing::endDomainStep(&mut BACKINGS, entry.object, 64) {}
188 +
    try! domains::retireStorage(&mut DOMAINS, &mut BACKINGS, entry.object);
189 +
    assert BACKINGS.pool.retiredCount == 3;
190 +
    let mut stale = false;
191 +
    try domains::get(&DOMAINS, entry.object) catch error {
192 +
        assert error == abi::Error::BadHandle; set stale = true;
193 +
    };
194 +
    assert stale and DOMAINS.slots[entry.object.index].state == slots::State::Live;
195 +
    while BACKINGS.pool.retiredCount > 0 {
196 +
        let reclaimed = frames::reclaim(&mut BACKINGS.pool, 256);
197 +
        assert reclaimed > 0 and reclaimed <= 256;
198 +
    }
199 +
    assert not try! events::acknowledged(&DOMAINS.events, entry.object);
200 +
    let mut retained = false;
201 +
    try domains::releaseIdentity(&mut DOMAINS, entry.object) catch error {
202 +
        assert error == abi::Error::Busy; set retained = true;
203 +
    };
204 +
    assert retained;
205 +
    let parentEntry = try! capability::get(&TABLE, parentHandle);
206 +
    let mut parent = try! domains::get(&DOMAINS, parentEntry.object);
207 +
    assert try! events::flush(&mut DOMAINS.events, parentEntry.object, &mut parent.memory.ring) == 1;
208 +
    let event = try! events::pop(&mut parent.memory.ring) else panic "missing terminal event";
209 +
    assert event.value == abi::id(entry.object) and event.code == 17;
210 +
    try! events::refresh(&mut DOMAINS.events, parentEntry.object, &parent.memory.ring);
211 +
    try! domains::releaseIdentity(&mut DOMAINS, entry.object);
212 +
    assert DOMAINS.slots[entry.object.index].state == slots::State::Free;
213 +
    assert (DOMAINS.ancestors[descendant.object.index][word] & bit) == 0;
214 +
    let next = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
215 +
    let replacement = try! capability::get(&TABLE, next);
216 +
    assert replacement.object.index == entry.object.index and replacement.object.generation <> entry.object.generation;
217 +
    assert (try! domains::get(&DOMAINS, replacement.object)).state == domains::Lifecycle::Pending;
218 +
    assert try! domains::terminateTree(&mut DOMAINS, replacement.object, events::CHILD_EXIT, 0) == 0;
219 +
    assert (try! domains::get(&DOMAINS, descendant.object)).state == domains::Lifecycle::Pending;
220 +
}
221 +
222 +
/// Reaping releases private authority while surviving shared pages retain their frames.
223 +
@test unsafe fn reaper() throws (testing::TestError) {
224 +
    initialize(abi::CREATE | abi::ALLOCATE);
225 +
    budgets::initialize(&mut WINDOWS);
226 +
    interrupts::initialize(&mut IRQS, 8);
227 +
    let parentHandle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
228 +
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
229 +
    let target = (try! capability::get(&TABLE, handle)).object;
230 +
    let receiver = (try! capability::get(&TABLE, parentHandle)).object;
231 +
    let mut child = try! domains::get(&DOMAINS, target);
232 +
    let mut parent = try! domains::get(&DOMAINS, receiver);
233 +
    try! domains::reparent(&mut DOMAINS, &TABLE, handle, parentHandle);
234 +
    set PAGES.backings = BACKINGS;
235 +
    slots::initialize(&mut PAGES.slots[..]);
236 +
    let authority = try! capability::install(&mut child.memory.table, capability::Entry {
237 +
        kind: abi::Kind::Domain, object: target, rights: abi::Rights(abi::ALLOCATE),
238 +
    });
239 +
    let page = try! pages::allocate(&mut PAGES, &mut child.memory.table, authority, 1);
240 +
    let shared = try! pages::grant(&mut PAGES, &child.memory.table, &mut parent.memory.table, page, abi::READ as u64);
241 +
    let allocation = (try! pages::get(&PAGES, (try! capability::get(&child.memory.table, page)).object)).backing;
242 +
    let run = PAGES.backings.records[allocation.index].run;
243 +
    let budget = try! budgets::seed(&mut WINDOWS, &mut child.memory.table, 0, 0, 1000);
244 +
    let window = (try! capability::get(&child.memory.table, budget)).object;
245 +
    let irq = try! interrupts::seed(&mut IRQS, &mut child.memory.table, 7);
246 +
    let dangling = try! capability::install(&mut child.memory.table, capability::Entry {
247 +
        kind: abi::Kind::Domain, object: abi::Ref { index: 511, generation: 99 }, rights: abi::Rights(0),
248 +
    });
249 +
    set DOMAINS.contexts[child.initial.index].hart = 0;
250 +
    assert try! domains::terminate(&mut DOMAINS, target, events::CHILD_EXIT, 19) == 1;
251 +
    assert not lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS);
252 +
    assert (try! capability::get(&child.memory.table, page)).kind == abi::Kind::Page;
253 +
    set DOMAINS.contexts[child.initial.index].hart = nil;
254 +
    assert lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS);
255 +
    assert child.memory.table.slots[0].state == slots::State::Free;
256 +
    assert (try! capability::get(&child.memory.table, page)).kind == abi::Kind::Page;
257 +
    for step in 0..2000 {
258 +
        if DOMAINS.reclaimed[target.index] { break; }
259 +
        assert lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS);
260 +
    }
261 +
    assert DOMAINS.reclaimed[target.index];
262 +
    assert WINDOWS.slots[window.index].state == slots::State::Free;
263 +
    assert IRQS.slots[6].state == slots::State::Free;
264 +
    assert not lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS);
265 +
    while PAGES.backings.pool.retiredCount > 0 {
266 +
        let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256);
267 +
        assert reclaimed > 0 and reclaimed <= 256;
268 +
    }
269 +
    assert not PAGES.backings.pool.free[run.first];
270 +
    assert PAGES.backings.records[allocation.index].exposed == 1;
271 +
    assert (try! pages::get(&PAGES, (try! capability::get(&parent.memory.table, shared)).object)).count == 1;
272 +
    assert try! events::flush(&mut DOMAINS.events, receiver, &mut parent.memory.ring) == 1;
273 +
    let event = try! events::pop(&mut parent.memory.ring) else panic "missing reaped child event";
274 +
    assert event.value == abi::id(target) and event.code == 19;
275 +
    try! events::refresh(&mut DOMAINS.events, receiver, &parent.memory.ring);
276 +
    assert lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS);
277 +
    assert DOMAINS.slots[target.index].state == slots::State::Free;
278 +
    assert try! domains::terminate(&mut DOMAINS, receiver, events::CHILD_EXIT, 0) == 0;
279 +
    for step in 0..2000 {
280 +
        if DOMAINS.slots[receiver.index].state == slots::State::Free { break; }
281 +
        assert lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS);
282 +
    }
283 +
    assert DOMAINS.slots[receiver.index].state == slots::State::Free;
284 +
    while PAGES.backings.pool.retiredCount > 0 {
285 +
        let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256);
286 +
        assert reclaimed > 0 and reclaimed <= 256;
287 +
    }
288 +
    for i in 0..PAGES.backings.pool.count { assert PAGES.backings.pool.free[i]; }
289 +
}
290 +
291 +
/// Initial return reports to the current parent and retains its terminal identity.
292 +
@test unsafe fn terminalParent() throws (testing::TestError) {
293 +
    initialize(abi::CREATE);
294 +
    let first = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
295 +
    let second = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
296 +
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
297 +
    let target = try! capability::get(&TABLE, handle);
298 +
    let destination = try! capability::get(&TABLE, second);
299 +
    try! domains::reparent(&mut DOMAINS, &TABLE, handle, first);
300 +
    try! domains::reparent(&mut DOMAINS, &TABLE, handle, second);
301 +
    let child = try! domains::get(&DOMAINS, target.object);
302 +
    set DOMAINS.contexts[child.initial.index].hart = 3;
303 +
    set DOMAINS.contexts[child.initial.index].pending = abi::Ref { index: 7, generation: 1 };
304 +
    let stopped = try! domains::returned(&mut DOMAINS, target.object, child.initial);
305 +
    assert stopped == 8 and DOMAINS.contexts[child.initial.index].hart == 3;
306 +
    assert DOMAINS.contexts[child.initial.index].pending <> nil;
307 +
    let repeated = try! domains::terminate(&mut DOMAINS, target.object, events::FAULT, 99);
308 +
    assert repeated == 0;
309 +
    assert DOMAINS.records[target.object.index].state == domains::Lifecycle::Dead;
310 +
    assert DOMAINS.events.terminals[target.object.index].state == events::State::Pending;
311 +
    assert DOMAINS.events.terminals[target.object.index].receiver == destination.object;
312 +
    assert DOMAINS.events.queues[target.object.index].generation == 0;
313 +
    let mut rejected = false;
314 +
    try domains::reparent(&mut DOMAINS, &TABLE, handle, first) catch error {
315 +
        assert error == abi::Error::BadHandle; set rejected = true;
316 +
    };
317 +
    assert rejected and DOMAINS.records[target.object.index].creator == TABLE.owner;
318 +
    let mut parent = try! domains::get(&DOMAINS, destination.object);
319 +
    assert try! events::flush(&mut DOMAINS.events, destination.object, &mut parent.memory.ring) == 1;
320 +
    assert not try! events::acknowledged(&DOMAINS.events, target.object);
321 +
    let event = try! events::pop(&mut parent.memory.ring) else panic "missing child exit";
322 +
    assert event.kind == events::CHILD_EXIT and event.code == 0 and event.value == abi::id(target.object);
323 +
    try! events::refresh(&mut DOMAINS.events, destination.object, &parent.memory.ring);
324 +
    assert try! events::acknowledged(&DOMAINS.events, target.object);
325 +
}
326 +
327 +
/// Parent exit leaves surviving children parentless without changing their creator.
328 +
@test unsafe fn parentExit() throws (testing::TestError) {
329 +
    initialize(abi::CREATE);
330 +
    let parentHandle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
331 +
    let childHandle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
332 +
    let parentCap = try! capability::get(&TABLE, parentHandle);
333 +
    let childCap = try! capability::get(&TABLE, childHandle);
334 +
    try! domains::reparent(&mut DOMAINS, &TABLE, childHandle, parentHandle);
335 +
    let parent = try! domains::get(&DOMAINS, parentCap.object);
336 +
    try! domains::returned(&mut DOMAINS, parentCap.object, parent.initial);
337 +
    let child = try! domains::get(&DOMAINS, childCap.object);
338 +
    assert child.state == domains::Lifecycle::Pending and child.parent == nil and child.creator == TABLE.owner;
339 +
    try! domains::returned(&mut DOMAINS, childCap.object, child.initial);
340 +
    assert try! events::acknowledged(&DOMAINS.events, childCap.object);
341 +
}
342 +
343 +
/// Create a child through its creator's own authority and resident image.
344 +
unsafe fn descendant(table: &mut capability::Table, image: abi::Ref) -> abi::Ref {
345 +
    let owner = table.owner;
346 +
    let authority = try! capability::install(table, capability::Entry {
347 +
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::CREATE),
348 +
    });
349 +
    let executable = try! registry::install(&PACKAGES, table, image);
350 +
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, table, authority, executable);
351 +
    return (try! capability::get(table, handle)).object;
352 +
}
353 +
354 +
/// Cascade retains creation ancestry after reparenting and an intermediate exit.
355 +
@test unsafe fn cascadeAncestry() throws (testing::TestError) {
356 +
    initialize(abi::CREATE);
357 +
    let package = try! capability::get(&TABLE, IMAGE);
358 +
    let first = descendant(&mut TABLE, package.object);
359 +
    let mut ancestor = try! domains::get(&DOMAINS, first);
360 +
    let second = descendant(&mut ancestor.memory.table, package.object);
361 +
    let mut middle = try! domains::get(&DOMAINS, second);
362 +
    let third = descendant(&mut middle.memory.table, package.object);
363 +
    let child = try! capability::install(&mut TABLE, capability::Entry {
364 +
        kind: abi::Kind::Domain, object: third, rights: abi::Rights(abi::DESTROY),
365 +
    });
366 +
    let root = TABLE.owner;
367 +
    let receiver = try! capability::install(&mut TABLE, capability::Entry {
368 +
        kind: abi::Kind::Domain, object: root, rights: abi::Rights(abi::WAKE),
369 +
    });
370 +
    try! domains::reparent(&mut DOMAINS, &TABLE, child, receiver);
371 +
    let reserved = try! events::reserve(&mut DOMAINS.events, third);
372 +
    let mut busy = false;
373 +
    try domains::terminateTree(&mut DOMAINS, first, events::CHILD_EXIT, 7) catch error {
374 +
        assert error == abi::Error::Busy; set busy = true;
375 +
    };
376 +
    assert busy and DOMAINS.records[first.index].state == domains::Lifecycle::Pending;
377 +
    assert DOMAINS.records[second.index].state == domains::Lifecycle::Pending;
378 +
    assert DOMAINS.events.terminals[first.index].state == events::State::Free;
379 +
    events::cancel(&mut DOMAINS.events, reserved);
380 +
    let stopped = try! domains::terminate(&mut DOMAINS, second, events::CHILD_EXIT, 12);
381 +
    assert stopped == 0 and DOMAINS.records[third.index].state == domains::Lifecycle::Pending;
382 +
    assert DOMAINS.records[third.index].parent == TABLE.owner;
383 +
    set DOMAINS.contexts[ancestor.initial.index].hart = 1;
384 +
    let last = try! domains::get(&DOMAINS, third);
385 +
    set DOMAINS.contexts[last.initial.index].hart = 3;
386 +
    let harts = try! domains::terminateTree(&mut DOMAINS, first, events::CHILD_EXIT, 7);
387 +
    assert harts == 10;
388 +
    assert DOMAINS.records[first.index].state == domains::Lifecycle::Dead;
389 +
    assert DOMAINS.records[second.index].state == domains::Lifecycle::Dead;
390 +
    assert DOMAINS.records[third.index].state == domains::Lifecycle::Dead;
391 +
    assert DOMAINS.records[TABLE.owner.index].state == domains::Lifecycle::Active;
392 +
    assert DOMAINS.events.terminals[second.index].event.code == 12;
393 +
    assert DOMAINS.events.terminals[third.index].event.code == 7;
394 +
    assert DOMAINS.records[third.index].creator == second;
395 +
    assert DOMAINS.contexts[last.initial.index].hart == 3;
396 +
}
397 +
60 398
/// Activation validates child authority before publishing its initial state.
61 399
@test unsafe fn activation() throws (testing::TestError) {
62 400
    initialize(abi::CREATE);
63 401
    slots::initialize(&mut PAGES.slots[..]);
64 402
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
147 485
    let stackBase = DOMAINS.contexts[index].kernelStack.start;
148 486
    let base = (&RAM[0]) as u64;
149 487
    let firstWord = ((stackBase - base) / 8) as u32;
150 488
    let lastWord = firstWord + domains::KERNEL_STACK_PAGES * 512 - 1;
151 489
    set RAM[firstWord] = 123; set RAM[lastWord] = 456;
152 -
    domains::contextClear(&pendingContext);
490 +
    lifecycle::initialize(&mut CALLS);
491 +
    let lease = lifecycle::holdContext(&mut CALLS, &mut DOMAINS, child.initial, pendingContext);
492 +
    assert DOMAINS.contexts[child.initial.index].reservation;
493 +
    lifecycle::clearContext(&CALLS, &lease);
153 494
    assert RAM[firstWord] == 0 and RAM[lastWord] == 0;
154 -
    let context = domains::contextPublish(&mut DOMAINS, pendingContext);
495 +
    let ready = lifecycle::takeContext(&mut CALLS, &mut DOMAINS, lease);
496 +
    assert not DOMAINS.contexts[child.initial.index].reservation;
497 +
    let context = domains::contextPublish(&mut DOMAINS, ready);
155 498
    let created = try! domains::context(&DOMAINS, authority.object, context);
156 499
    let initial = DOMAINS.contexts[child.initial.index];
157 500
    try testing::expect(context <> child.initial and created.owner == authority.object);
158 501
    try testing::expect(created.frame.registers[3] == initial.frame.registers[3]);
159 502
    try testing::expect(created.kernelStack.start >= initial.kernelStack.end or created.kernelStack.end <= initial.kernelStack.start);
221 564
    assert retained and DOMAINS.contextSlots[context.index].state == slots::State::Live;
222 565
    for i in created.kernelFrames.first..created.kernelFrames.first + created.kernelFrames.count {
223 566
        assert not PAGES.backings.pool.free[i];
224 567
    }
225 568
    set DOMAINS.contexts[context.index].pending = nil;
569 +
    set DOMAINS.contexts[context.index].reservation = true;
570 +
    set retained = false;
571 +
    try domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context) catch error {
572 +
        assert error == abi::Error::Busy; set retained = true;
573 +
    };
574 +
    assert retained and DOMAINS.contextSlots[context.index].state == slots::State::Live;
575 +
    set DOMAINS.contexts[context.index].reservation = false;
226 576
    try! domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context);
227 577
    for i in created.kernelFrames.first..created.kernelFrames.first + created.kernelFrames.count {
228 578
        try testing::expect(PAGES.backings.pool.free[i]);
229 579
    }
230 580
    try domains::context(&DOMAINS, authority.object, context) catch error {
233 583
    let next = try! domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start);
234 584
    try testing::expect(next.index == context.index and next.generation <> context.generation);
235 585
    try! domains::returned(&mut DOMAINS, authority.object, child.initial);
236 586
    try testing::expect(DOMAINS.records[authority.object.index].state == domains::Lifecycle::Dead);
237 587
    try testing::expect(DOMAINS.contexts[next.index].state == domains::ContextState::Stopped);
238 -
    try! domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, next);
239 -
    try testing::expect(rejected == 11);
588 +
    try domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, next) catch error {
589 +
        assert error == abi::Error::BadHandle; set rejected += 1;
590 +
    };
591 +
    assert DOMAINS.contextSlots[next.index].state == slots::State::Live;
592 +
    assert rejected == 12;
240 593
}
241 594
242 595
/// Both serialized wait/wakeup orders preserve notification and runnable state.
243 596
@test unsafe fn waitWakeup() throws (testing::TestError) {
244 597
    initialize(abi::CREATE);
kernel/kernel/tests/frames.rad +26 -0
1 1
//! Contiguous frame reservation and reserved-memory exclusion.
2 2
3 3
use std::testing;
4 4
use kernel::abi;
5 5
use kernel::frames;
6 +
use kernel::limits;
6 7
use kernel::platform;
7 8
use kernel::range;
8 9
9 10
/// Frame-map workspace is static because its size exceeds a small kernel stack.
10 11
unsafe static POOL: frames::Pool = undefined;
17 18
    set machine.reservedCount = 1;
18 19
    set machine.reserved[0] = range::Range { start: 0x80003000, end: 0x80005000 };
19 20
    try! frames::initialize(&mut POOL, &machine);
20 21
}
21 22
23 +
/// Retired frames stay unavailable until bounded reclamation returns them.
24 +
@test unsafe fn retirement() throws (testing::TestError) {
25 +
    initialize();
26 +
    set POOL.retiredHead = limits::FRAMES - 1;
27 +
    let left = frames::commit(try! frames::reserve(&mut POOL, 3));
28 +
    let right = frames::commit(try! frames::reserve(&mut POOL, 5));
29 +
    try! frames::retire(&mut POOL, left);
30 +
    try! frames::retire(&mut POOL, right);
31 +
    assert POOL.retiredCount == 2;
32 +
    for i in 0..8 { assert not POOL.free[i]; }
33 +
    assert frames::reclaim(&mut POOL, 0) == 0;
34 +
    assert frames::reclaim(&mut POOL, 2) == 2;
35 +
    assert POOL.free[0] and POOL.free[1] and not POOL.free[2];
36 +
    let reused = frames::commit(try! frames::reserve(&mut POOL, 2));
37 +
    assert reused.first == 0;
38 +
    assert frames::reclaim(&mut POOL, 3) == 3;
39 +
    assert POOL.retiredCount == 1 and not POOL.free[5];
40 +
    assert not POOL.free[0] and not POOL.free[1];
41 +
    try! frames::retire(&mut POOL, reused);
42 +
    assert frames::reclaim(&mut POOL, 20) == 5;
43 +
    assert POOL.retiredCount == 0;
44 +
    for i in 0..8 { assert POOL.free[i]; }
45 +
    assert frames::reclaim(&mut POOL, 20) == 0;
46 +
}
47 +
22 48
/// Adjacent physical frames form runs; a reserved hole cannot be crossed.
23 49
@test unsafe fn fragmentation() throws (testing::TestError) {
24 50
    initialize();
25 51
    try testing::expect(POOL.count == 8);
26 52
    let left = try! frames::reserve(&mut POOL, 3);
kernel/kernel/tests/lifecycle.rad added +307 -0
1 +
//! Terminal delivery, receiver eligibility, and retained full-queue events.
2 +
use std::testing;
3 +
use kernel::abi;
4 +
use kernel::slots;
5 +
use kernel::domains;
6 +
use kernel::budgets;
7 +
use kernel::events;
8 +
use kernel::lifecycle;
9 +
use kernel::registry;
10 +
use kernel::loader;
11 +
use kernel::limits;
12 +
use kernel::remote;
13 +
use kernel::pages;
14 +
use kernel::backing;
15 +
use kernel::capability;
16 +
use kernel::frames;
17 +
18 +
/// Receiver generations, contexts, and terminal events.
19 +
unsafe static STORE: domains::Store = undefined;
20 +
/// CPU authority retained by receivers.
21 +
unsafe static WINDOWS: budgets::Store = undefined;
22 +
/// Queued calls and acknowledgements retained by stopped contexts.
23 +
unsafe static REQUESTS: remote::Store = undefined;
24 +
/// Physical allocation metadata used for interrupted page calls.
25 +
unsafe static MEMORY: pages::Store = undefined;
26 +
/// Mapped frames available to interrupted page calls.
27 +
static RAM: [u64; 4096] = [0; 4096];
28 +
/// Reservations retained across private initialization.
29 +
unsafe static CALLS: lifecycle::Calls = undefined;
30 +
/// Shared queue storage for the first receiver.
31 +
unsafe static FIRST: domains::Memory = undefined;
32 +
/// Shared queue storage for the second receiver.
33 +
unsafe static SECOND: domains::Memory = undefined;
34 +
35 +
/// Open one receiver with a waiting initial context.
36 +
unsafe fn open(index: u32, memory: &mut domains::Memory) {
37 +
    let owner = abi::Ref { index, generation: 1 };
38 +
    set STORE.slots[index] = slots::Slot { generation: 1, state: slots::State::Live };
39 +
    set STORE.records[index].state = domains::Lifecycle::Active;
40 +
    set STORE.records[index].memory = (&mut *memory) as *unsafe mut domains::Memory;
41 +
    set STORE.contextSlots[index] = slots::Slot { generation: 1, state: slots::State::Live };
42 +
    set STORE.contexts[index].owner = owner;
43 +
    set STORE.contexts[index].state = domains::ContextState::Waiting;
44 +
    set STORE.contexts[index].hart = nil;
45 +
    set STORE.contexts[index].pending = nil;
46 +
    try! events::open(&mut STORE.events, owner, &mut memory.ring);
47 +
}
48 +
49 +
/// Initialize two independent receivers and one future window on hart one.
50 +
unsafe fn initialize() {
51 +
    domains::initialize(&mut STORE);
52 +
    budgets::initialize(&mut WINDOWS);
53 +
    remote::initialize(&mut REQUESTS, 3);
54 +
    open(0, &mut FIRST);
55 +
    open(1, &mut SECOND);
56 +
    let owner = abi::Ref { index: 0, generation: 1 };
57 +
    set WINDOWS.slots[0] = slots::Slot { generation: 1, state: slots::State::Live };
58 +
    set WINDOWS.windows[0] = budgets::Window {
59 +
        hart: 1, start: 30, end: 100, owner, handle: abi::Handle(0), context: owner,
60 +
    };
61 +
}
62 +
63 +
/// Retain a normal exit for a chosen receiver.
64 +
unsafe fn retain(index: u32, receiver: u32) {
65 +
    let pending = try! events::reserve(&mut STORE.events, abi::Ref { index, generation: 1 });
66 +
    events::finish(&mut STORE.events, pending, abi::Ref { index: receiver, generation: 1 }, events::CHILD_EXIT, index);
67 +
}
68 +
69 +
/// Dead page callers release unpublished capacity only after hart ownership ends.
70 +
@test unsafe fn cancelPage() throws (testing::TestError) {
71 +
    initialize();
72 +
    lifecycle::initialize(&mut CALLS);
73 +
    backing::initialize(&mut MEMORY.backings);
74 +
    slots::initialize(&mut MEMORY.slots[..]);
75 +
    let owner = abi::Ref { index: 0, generation: 1 };
76 +
    try! backing::registerDomain(&mut MEMORY.backings, owner);
77 +
    capability::initialize(&mut FIRST.table, owner);
78 +
    let authority = try! capability::install(&mut FIRST.table, capability::Entry {
79 +
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE),
80 +
    });
81 +
    let base = ((&RAM[0]) as u64 + 4095) & ~4095;
82 +
    set MEMORY.backings.pool.count = 2;
83 +
    for i in 0..2 {
84 +
        set MEMORY.backings.pool.addresses[i] = base + i as u64 * 4096;
85 +
        set MEMORY.backings.pool.free[i] = true;
86 +
    }
87 +
    let reservation = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 2);
88 +
    let held = lifecycle::holdPage(&mut CALLS, &mut STORE, owner, reservation);
89 +
    assert STORE.contexts[0].reservation;
90 +
    lifecycle::clearPage(&CALLS, &held);
91 +
    lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
92 +
    assert not MEMORY.backings.pool.free[0] and not MEMORY.backings.pool.free[1];
93 +
    set STORE.records[0].state = domains::Lifecycle::Dead;
94 +
    set STORE.contexts[0].state = domains::ContextState::Stopped;
95 +
    set STORE.contexts[0].hart = 0;
96 +
    lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
97 +
    assert not MEMORY.backings.pool.free[0];
98 +
    // Model the end of the stopped continuation before hart release.
99 +
    match held { case lifecycle::Lease::Held(context) => { assert context == owner; }, }
100 +
    set STORE.contexts[0].hart = nil;
101 +
    lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
102 +
    assert MEMORY.backings.pool.retiredCount == 1 and not MEMORY.backings.pool.free[0];
103 +
    assert frames::reclaim(&mut MEMORY.backings.pool, 256) == 2;
104 +
    assert MEMORY.backings.pool.free[0] and MEMORY.backings.pool.free[1];
105 +
    assert MEMORY.slots[0].state == slots::State::Free;
106 +
    assert MEMORY.backings.slots[0].state == slots::State::Free;
107 +
    assert FIRST.table.slots[1].state == slots::State::Free and CALLS.active[0] == 0;
108 +
    assert not STORE.contexts[0].reservation;
109 +
    set STORE.records[0].state = domains::Lifecycle::Active;
110 +
    let next = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 1);
111 +
    let lease = lifecycle::holdPage(&mut CALLS, &mut STORE, owner, next);
112 +
    lifecycle::clearPage(&CALLS, &lease);
113 +
    let ready = lifecycle::takePage(&mut CALLS, &mut STORE, lease);
114 +
    let handle = pages::publish(&mut MEMORY, &mut FIRST.table, ready);
115 +
    assert CALLS.active[0] == 0;
116 +
    let entry = try! capability::get(&FIRST.table, handle);
117 +
    assert (try! pages::get(&MEMORY, entry.object)).count == 1;
118 +
}
119 +
120 +
/// One cancellation pass retires at most one caller's private allocation.
121 +
@test unsafe fn cancellationBatch() throws (testing::TestError) {
122 +
    initialize();
123 +
    lifecycle::initialize(&mut CALLS);
124 +
    backing::initialize(&mut MEMORY.backings);
125 +
    slots::initialize(&mut MEMORY.slots[..]);
126 +
    let owner = abi::Ref { index: 0, generation: 1 };
127 +
    let second = abi::Ref { index: 1, generation: 1 };
128 +
    set STORE.contexts[1].owner = owner;
129 +
    try! backing::registerDomain(&mut MEMORY.backings, owner);
130 +
    capability::initialize(&mut FIRST.table, owner);
131 +
    let authority = try! capability::install(&mut FIRST.table, capability::Entry {
132 +
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE),
133 +
    });
134 +
    let base = ((&RAM[0]) as u64 + 4095) & ~4095;
135 +
    set MEMORY.backings.pool.count = 2;
136 +
    for i in 0..2 {
137 +
        set MEMORY.backings.pool.addresses[i] = base + i as u64 * 4096;
138 +
        set MEMORY.backings.pool.free[i] = true;
139 +
    }
140 +
    let firstPage = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 1);
141 +
    let first = lifecycle::holdPage(&mut CALLS, &mut STORE, owner, firstPage);
142 +
    let secondPage = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 1);
143 +
    let other = lifecycle::holdPage(&mut CALLS, &mut STORE, second, secondPage);
144 +
    match first { case lifecycle::Lease::Held(context) => { assert context == owner; }, }
145 +
    match other { case lifecycle::Lease::Held(context) => { assert context == second; }, }
146 +
    set STORE.records[0].state = domains::Lifecycle::Dead;
147 +
    set STORE.contexts[0].state = domains::ContextState::Stopped;
148 +
    set STORE.contexts[1].state = domains::ContextState::Stopped;
149 +
    assert lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
150 +
    assert CALLS.active[0] == 2 and MEMORY.backings.pool.retiredCount == 1;
151 +
    assert not STORE.contexts[0].reservation and STORE.contexts[1].reservation;
152 +
    assert lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
153 +
    assert CALLS.active[0] == 0 and MEMORY.backings.pool.retiredCount == 2;
154 +
    assert not lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
155 +
    assert not MEMORY.backings.pool.free[0] and not MEMORY.backings.pool.free[1];
156 +
    assert frames::reclaim(&mut MEMORY.backings.pool, 1) == 1;
157 +
    assert frames::reclaim(&mut MEMORY.backings.pool, 1) == 1;
158 +
    assert MEMORY.backings.pool.retiredCount == 0;
159 +
}
160 +
161 +
/// A context call belongs to its caller even when its target domain dies.
162 +
@test unsafe fn cancelContext() throws (testing::TestError) {
163 +
    initialize();
164 +
    lifecycle::initialize(&mut CALLS);
165 +
    backing::initialize(&mut MEMORY.backings);
166 +
    let caller = abi::Ref { index: 0, generation: 1 };
167 +
    let target = abi::Ref { index: 1, generation: 1 };
168 +
    let base = ((&RAM[0]) as u64 + 4095) & ~4095;
169 +
    set MEMORY.backings.pool.count = 1;
170 +
    set MEMORY.backings.pool.addresses[0] = base;
171 +
    set MEMORY.backings.pool.free[0] = true;
172 +
    let slot = try! slots::reserve(&mut STORE.contextSlots[..]);
173 +
    let object = slots::reference(&slot);
174 +
    let run = frames::commit(try! frames::reserve(&mut MEMORY.backings.pool, 1));
175 +
    set STORE.contexts[object.index].owner = target;
176 +
    set STORE.contexts[object.index].kernelFrames = run;
177 +
    set STORE.contexts[object.index].hart = nil;
178 +
    match slot { case slots::Reservation::Held(id) => { assert id == object; }, }
179 +
    let pending = domains::ContextReservation::Held(domains::ContextAllocation {
180 +
        object, owner: target, frames: run, base,
181 +
    });
182 +
    let lease = lifecycle::holdContext(&mut CALLS, &mut STORE, caller, pending);
183 +
    lifecycle::clearContext(&CALLS, &lease);
184 +
    set STORE.records[1].state = domains::Lifecycle::Dead;
185 +
    lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
186 +
    assert STORE.contexts[0].reservation and not MEMORY.backings.pool.free[0];
187 +
    set STORE.records[0].state = domains::Lifecycle::Dead;
188 +
    set STORE.contexts[0].state = domains::ContextState::Stopped;
189 +
    set STORE.contexts[0].hart = 1;
190 +
    lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
191 +
    assert STORE.contextSlots[object.index].state == slots::State::Reserved;
192 +
    match lease { case lifecycle::Lease::Held(id) => { assert id == caller; }, }
193 +
    set STORE.contexts[0].hart = nil;
194 +
    lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
195 +
    assert MEMORY.backings.pool.retiredCount == 1 and not MEMORY.backings.pool.free[0];
196 +
    assert frames::reclaim(&mut MEMORY.backings.pool, 256) == 1;
197 +
    assert MEMORY.backings.pool.free[0];
198 +
    assert STORE.contextSlots[object.index].state == slots::State::Free;
199 +
    assert not STORE.contexts[0].reservation and CALLS.active[0] == 0;
200 +
}
201 +
202 +
/// Dead contexts retain requests until hart release and completion of taken work.
203 +
@test unsafe fn cancelRequests() throws (testing::TestError) {
204 +
    initialize();
205 +
    let context = abi::Ref { index: 0, generation: 1 };
206 +
    let pending = try! remote::send(&mut REQUESTS, 0, 1, remote::Action::Ready(context));
207 +
    let other = try! remote::send(&mut REQUESTS, 0, 1, remote::Action::Ready(context));
208 +
    let delivered = try! remote::take(&mut REQUESTS, 1) else panic "missing readiness request";
209 +
    assert delivered.id == pending;
210 +
    set STORE.contexts[0].pending = other;
211 +
    lifecycle::cancelRequests(&mut STORE, &mut REQUESTS);
212 +
    assert REQUESTS.slots[other.index].state == slots::State::Live;
213 +
    set STORE.records[0].state = domains::Lifecycle::Dead;
214 +
    set STORE.contexts[0].state = domains::ContextState::Stopped;
215 +
    set STORE.contexts[0].hart = 0;
216 +
    lifecycle::cancelRequests(&mut STORE, &mut REQUESTS);
217 +
    assert REQUESTS.slots[other.index].state == slots::State::Live;
218 +
    set STORE.contexts[0].hart = nil;
219 +
    lifecycle::cancelRequests(&mut STORE, &mut REQUESTS);
220 +
    assert REQUESTS.slots[other.index].state == slots::State::Free;
221 +
    assert REQUESTS.slots[pending.index].state == slots::State::Live;
222 +
    assert STORE.contexts[0].pending == pending;
223 +
    assert remote::acknowledge(&mut REQUESTS, 1, pending);
224 +
    lifecycle::cancelRequests(&mut STORE, &mut REQUESTS);
225 +
    assert STORE.contexts[0].pending == nil and REQUESTS.slots[pending.index].state == slots::State::Free;
226 +
    assert try! remote::take(&mut REQUESTS, 1) == nil;
227 +
    assert REQUESTS.queues[1].count == 0;
228 +
    let budget = try! remote::send(&mut REQUESTS, 0, 1, remote::Action::Budget(remote::Call {
229 +
        owner: context, context, operation: 70, arguments: [0; 4],
230 +
    }));
231 +
    set STORE.contexts[0].pending = budget;
232 +
    let work = try! remote::take(&mut REQUESTS, 1) else panic "missing budget request";
233 +
    remote::respond(&mut REQUESTS, 1, work.id, remote::Reply::Error(abi::Error::BadHandle));
234 +
    assert remote::acknowledge(&mut REQUESTS, 1, work.id);
235 +
    lifecycle::cancelRequests(&mut STORE, &mut REQUESTS);
236 +
    assert STORE.contexts[0].pending == nil and REQUESTS.slots[budget.index].state == slots::State::Free;
237 +
}
238 +
239 +
/// Closing multiple receivers validates all generations before changing any queue.
240 +
@test unsafe fn closeReceivers() throws (testing::TestError) {
241 +
    initialize();
242 +
    retain(2, 0);
243 +
    retain(3, 1);
244 +
    let mut generations: [u32; limits::DOMAINS] = [0; limits::DOMAINS];
245 +
    set generations[0] = 1;
246 +
    set generations[1] = 2;
247 +
    let mut rejected = false;
248 +
    try events::closeSet(&mut STORE.events, &generations[..]) catch error {
249 +
        assert error == abi::Error::BadHandle; set rejected = true;
250 +
    };
251 +
    assert rejected and STORE.events.queues[0].generation == 1 and STORE.events.queues[1].generation == 1;
252 +
    assert STORE.events.pending == 2;
253 +
    set generations[1] = 1;
254 +
    try! events::closeSet(&mut STORE.events, &generations[..]);
255 +
    assert STORE.events.pending == 0;
256 +
    assert STORE.events.queues[0].generation == 0 and STORE.events.queues[1].generation == 0;
257 +
    assert try! events::acknowledged(&STORE.events, abi::Ref { index: 2, generation: 1 });
258 +
    assert try! events::acknowledged(&STORE.events, abi::Ref { index: 3, generation: 1 });
259 +
}
260 +
261 +
/// Events ready both receivers but signal only existing CPU authority.
262 +
@test unsafe fn receivers() throws (testing::TestError) {
263 +
    initialize();
264 +
    retain(2, 0);
265 +
    retain(3, 1);
266 +
    assert STORE.events.pending == 2;
267 +
    assert lifecycle::service(&mut STORE, &WINDOWS, 20) == 2;
268 +
    assert STORE.events.pending == 0;
269 +
    assert STORE.contexts[0].state == domains::ContextState::Ready;
270 +
    assert STORE.contexts[1].state == domains::ContextState::Ready;
271 +
    assert lifecycle::service(&mut STORE, &WINDOWS, 20) == 0;
272 +
    assert STORE.events.queues[0].tail == 1 and STORE.events.queues[1].tail == 1;
273 +
    let first = try! events::pop(&mut FIRST.ring) else panic "first exit";
274 +
    let second = try! events::pop(&mut SECOND.ring) else panic "second exit";
275 +
    assert first.kind == events::CHILD_EXIT and first.code == 2;
276 +
    assert second.kind == events::CHILD_EXIT and second.code == 3;
277 +
    retain(4, 0);
278 +
    assert lifecycle::service(&mut STORE, &WINDOWS, 100) == 0;
279 +
    assert STORE.events.terminals[4].state == events::State::Queued;
280 +
}
281 +
282 +
/// Full receivers retain terminal events while other receivers make progress.
283 +
@test unsafe fn saturation() throws (testing::TestError) {
284 +
    initialize();
285 +
    let receiver = abi::Ref { index: 0, generation: 1 };
286 +
    for i in 0..events::ORDINARY {
287 +
        assert try! events::send(&mut STORE.events, receiver, &mut FIRST.ring,
288 +
            events::Event { kind: events::WAKEUP, reserved: 0, code: i, value: 0 });
289 +
    }
290 +
    for i in 64..128 { retain(i, 0); }
291 +
    assert try! events::flush(&mut STORE.events, receiver, &mut FIRST.ring) == 64;
292 +
    retain(2, 0);
293 +
    retain(3, 1);
294 +
    assert lifecycle::service(&mut STORE, &WINDOWS, 20) == 0;
295 +
    assert STORE.events.terminals[2].state == events::State::Pending;
296 +
    assert STORE.events.pending == 1;
297 +
    assert STORE.events.terminals[3].state == events::State::Queued;
298 +
    let consumed = try! events::pop(&mut FIRST.ring) else panic "full queue";
299 +
    assert consumed.kind == events::WAKEUP;
300 +
    assert lifecycle::service(&mut STORE, &WINDOWS, 20) == 2;
301 +
    assert STORE.events.terminals[2].state == events::State::Queued;
302 +
    assert STORE.events.queues[0].tail == events::CAPACITY + 1;
303 +
    retain(4, 0);
304 +
    assert STORE.events.pending == 1;
305 +
    try! events::close(&mut STORE.events, receiver);
306 +
    assert STORE.events.pending == 0;
307 +
}
kernel/kernel/tests/loader.rad +81 -0
12 12
use kernel::frames;
13 13
use kernel::backing;
14 14
use kernel::capability;
15 15
use kernel::registry;
16 16
use kernel::loader;
17 +
use kernel::domains;
18 +
use kernel::lifecycle;
19 +
20 +
/// Caller metadata used for interrupted loader calls.
21 +
unsafe static DOMAINS: domains::Store = undefined;
22 +
/// Caller-owned private reservations.
23 +
unsafe static CALLS: lifecycle::Calls = undefined;
24 +
/// Capability storage retained until the stopped caller is reclaimed.
25 +
unsafe static MEMORY: domains::Memory = undefined;
17 26
18 27
/// Mapped frames for temporary compiler storage and persistent native output.
19 28
static RAM: [u64; 18874368] = [0; 18874368];
20 29
/// Physical frame and page metadata.
21 30
unsafe static PAGES: pages::Store = undefined;
83 92
    let mut count: u32 = 0;
84 93
    for i in 0..PAGES.backings.pool.count { if PAGES.backings.pool.free[i] { set count += 1; } }
85 94
    return count;
86 95
}
87 96
97 +
/// Dead loaders retain private resources until hart release, then permit a retry.
98 +
@test unsafe fn cancelledCall() throws (testing::TestError) {
99 +
    for stage in 0..3 {
100 +
        initialize();
101 +
        domains::initialize(&mut DOMAINS);
102 +
        lifecycle::initialize(&mut CALLS);
103 +
        let request = input(43, false);
104 +
        let before = free();
105 +
        let owner = TABLE.owner;
106 +
        set MEMORY.table = TABLE;
107 +
        set DOMAINS.slots[0] = slots::Slot { generation: 1, state: slots::State::Live };
108 +
        set DOMAINS.records[0].state = domains::Lifecycle::Active;
109 +
        set DOMAINS.records[0].memory = &mut MEMORY;
110 +
        set DOMAINS.contextSlots[0] = slots::Slot { generation: 1, state: slots::State::Live };
111 +
        set DOMAINS.contexts[0].owner = owner;
112 +
        set DOMAINS.contexts[0].state = domains::ContextState::Ready;
113 +
        set DOMAINS.contexts[0].hart = nil;
114 +
        let pending = try! loader::reserve(&mut LOADER, &mut PAGES, &mut MEMORY.table, request);
115 +
        let lease = lifecycle::holdLoad(&mut CALLS, &mut DOMAINS, owner, pending);
116 +
        if stage > 0 {
117 +
            try! lifecycle::reserveOutput(&mut CALLS, &lease, &mut PAGES, &mut PACKAGES);
118 +
        }
119 +
        if stage == 2 {
120 +
            let retained = lifecycle::load(&CALLS, &lease);
121 +
            let decoded = try! loader::decodeInput(&retained.input);
122 +
            let output = retained.output else panic "missing output";
123 +
            let compiled = try! loader::compileInput(&PACKAGES, &retained.input, &decoded, &output);
124 +
            assert compiled.code > 0;
125 +
        }
126 +
        lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut LOADER, &mut PACKAGES);
127 +
        assert LOADER.busy and DOMAINS.contexts[0].reservation;
128 +
        set DOMAINS.records[0].state = domains::Lifecycle::Dead;
129 +
        set DOMAINS.contexts[0].state = domains::ContextState::Stopped;
130 +
        set DOMAINS.contexts[0].hart = 1;
131 +
        lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut LOADER, &mut PACKAGES);
132 +
        assert LOADER.busy and free() < before;
133 +
        match lease { case lifecycle::Lease::Held(context) => { assert context == owner; }, }
134 +
        set DOMAINS.contexts[0].hart = nil;
135 +
        lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut LOADER, &mut PACKAGES);
136 +
        assert not LOADER.busy and not DOMAINS.contexts[0].reservation;
137 +
        assert free() < before and PAGES.backings.pool.retiredCount > 0;
138 +
        while PAGES.backings.pool.retiredCount > 0 {
139 +
            let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256);
140 +
            assert reclaimed > 0 and reclaimed <= 256;
141 +
        }
142 +
        assert free() == before and MEMORY.table.slots[2].state == slots::State::Free;
143 +
        assert PACKAGES.slots[0].state == slots::State::Free and not LOADER.live[0];
144 +
        set DOMAINS.records[0].state = domains::Lifecycle::Active;
145 +
        let retry = try! loader::reserve(&mut LOADER, &mut PAGES, &mut MEMORY.table, request);
146 +
        let active = lifecycle::holdLoad(&mut CALLS, &mut DOMAINS, owner, retry);
147 +
        try! lifecycle::reserveOutput(&mut CALLS, &active, &mut PAGES, &mut PACKAGES);
148 +
        let retained = lifecycle::load(&CALLS, &active);
149 +
        let decoded = try! loader::decodeInput(&retained.input);
150 +
        let output = retained.output else panic "missing retry output";
151 +
        let compiled = try! loader::compileInput(&PACKAGES, &retained.input, &decoded, &output);
152 +
        let handle = try! lifecycle::finishLoad(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut LOADER,
153 +
            &mut PACKAGES, active, lifecycle::LoadResult::Compiled(compiled));
154 +
        let object = try! registry::image(&PACKAGES, &MEMORY.table, handle, abi::Rights(0));
155 +
        assert object.index == 0 and not DOMAINS.contexts[0].reservation;
156 +
        let available = free();
157 +
        let duplicate = try! loader::reserve(&mut LOADER, &mut PAGES, &mut MEMORY.table, request);
158 +
        let reuse = lifecycle::holdLoad(&mut CALLS, &mut DOMAINS, owner, duplicate);
159 +
        let source = lifecycle::load(&CALLS, &reuse);
160 +
        let program = try! loader::decodeInput(&source.input);
161 +
        assert try! loader::identifyInput(&PACKAGES, &source.input, &program) == object;
162 +
        let same = try! lifecycle::finishLoad(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut LOADER,
163 +
            &mut PACKAGES, reuse, lifecycle::LoadResult::Existing(object));
164 +
        assert try! registry::image(&PACKAGES, &MEMORY.table, same, abi::Rights(0)) == object;
165 +
        assert free() == available and not LOADER.busy;
166 +
    }
167 +
}
168 +
88 169
/// Reserved input stays private across allocation, source mutation, and contention.
89 170
@test unsafe fn stagedInput() throws (testing::TestError) {
90 171
    initialize();
91 172
    let request = input(43, false);
92 173
    let before = free();
lib/std/lang/module.rad +1 -1
12 12
use std::lang::alloc;
13 13
use std::lang::ast;
14 14
use std::lang::strings;
15 15
16 16
/// Maximum number of modules tracked in a single compilation graph.
17 -
export constant MAX_MODULES: u32 = 128;
17 +
export constant MAX_MODULES: u32 = 192;
18 18
/// Maximum number of characters for a module path.
19 19
constant MAX_PATH_LEN: u32 = 256;
20 20
/// Maximum number of components that make up a logical module path.
21 21
constant MAX_MODULE_PATH_DEPTH: u32 = 16;
22 22
/// Filesystem separator used when constructing child paths.
test/dispatch/kernel/dispatchcheck.rad +23 -3
11 11
use kernel::domains;
12 12
use kernel::budgets;
13 13
use kernel::dispatch;
14 14
use kernel::boot;
15 15
use kernel::events;
16 +
use kernel::sync;
16 17
use kernel::dispatchinput;
17 18
18 19
/// Bootstrap authority used to create fixture domains and CPU windows.
19 20
unsafe static TABLE: capability::Table = undefined;
20 21
/// Domain handles used for budget binding.
71 72
    let executable = try! registry::install(&registry::STORE, &mut user.memory.table, root);
72 73
    let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1);
73 74
    let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut user.memory.table, stack, (abi::READ | abi::WRITE) as u64);
74 75
    let stackCap = try! capability::get(&TABLE, stack);
75 76
    let stackPage = try! pages::get(&pages::STORE, stackCap.object);
76 -
    let args = @sliceOf(memory(stackPage.base), 32);
77 +
    let parent = try! capability::install(&mut user.memory.table, capability::Entry {
78 +
        kind: abi::Kind::Domain, object: domains::STORE.contexts[CONTEXTS[2].index].owner, rights: abi::Rights(abi::WAKE),
79 +
    });
80 +
    let args = @sliceOf(memory(stackPage.base), 40);
77 81
    let words = [*granted, *executable, *selfAuthority];
78 82
    for word in 0..3 {
79 83
        for byte in 0..8 { set args[word * 8 + byte] = (words[word] >> (byte as u64 * 8)) as u8; }
80 84
    }
81 -
    try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, HANDLES[0], stackPage.base + 4096, stackPage.base, 32);
85 +
    for byte in 0..8 { set args[32 + byte] = (*parent >> (byte as u64 * 8)) as u8; }
86 +
    try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, HANDLES[0], stackPage.base + 4096, stackPage.base, 40);
82 87
    let imageRef = try! registry::image(&registry::STORE, &TABLE, image, abi::Rights(abi::EXECUTE));
83 88
    let target = try! registry::exported(&registry::STORE, imageRef, &"spin::count"[..]);
84 89
    let case shared::Target::Data(data) = target else panic "counter";
85 90
    let timer = try! dispatch::timer(&boot::PLATFORM, 0);
86 91
    let clock = dispatch::now(timer);
87 92
    let start = clock + 1000000;
88 -
    let span: u64 = 4000000;
93 +
    let span: u64 = 8000000;
89 94
    set domains::STORE.contexts[CONTEXTS[1].index].frame.pc = worker();
90 95
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[10] = user.graph.table[data.slot] + data.offset as u64;
91 96
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[11] = timer.clock;
92 97
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[12] = start + 3 * span;
93 98
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[13] = start + 4 * span;
124 129
    for byte in 0..8 { set progress |= bytes[byte] as u64 << (byte as u64 * 8); }
125 130
    if progress == 0 { failed(6); }
126 131
    let checked = try! registry::exported(&registry::STORE, domain.image, &"spin::destroyed"[..]);
127 132
    let case shared::Target::Data(flag) = checked else panic "destruction flag";
128 133
    if *memory(domain.graph.table[flag.slot] + flag.offset as u64) <> 1 { failed(7); }
134 +
    let child = try! registry::exported(&registry::STORE, domain.image, &"spin::childId"[..]);
135 +
    let case shared::Target::Data(identity) = child else panic "child identity";
136 +
    let idBytes = @sliceOf(memory(domain.graph.table[identity.slot] + identity.offset as u64), 8);
137 +
    let mut childId: u64 = 0;
138 +
    for byte in 0..8 { set childId |= idBytes[byte] as u64 << (byte as u64 * 8); }
139 +
    let owner = domains::STORE.contexts[CONTEXTS[2].index].owner;
140 +
    let guard = sync::enter();
141 +
    let mut checker = try! domains::get(&domains::STORE, owner);
142 +
    assert try! events::flush(&mut domains::STORE.events, owner, &mut checker.memory.ring) == 0;
143 +
    assert not try! events::acknowledged(&domains::STORE.events, abi::reference(childId));
144 +
    let exited = try! events::pop(&mut checker.memory.ring) else panic "child exit";
145 +
    assert exited.kind == events::CHILD_EXIT and exited.code == 0 and exited.value == childId;
146 +
    try! events::refresh(&mut domains::STORE.events, owner, &checker.memory.ring);
147 +
    assert try! events::acknowledged(&domains::STORE.events, abi::reference(childId));
148 +
    sync::leave(guard);
129 149
    let wake = try! events::pop(&mut domain.memory.ring) else panic "wake token";
130 150
    assert wake.kind == events::WAKEUP and wake.code == 123;
131 151
    let timeout = try! events::pop(&mut domain.memory.ring) else panic "timeout token";
132 152
    assert timeout.kind == events::TIMEOUT and timeout.code == 77 and timeout.value == 0;
133 153
    assert try! events::pop(&mut domain.memory.ring) == nil;
test/dispatch/spin.rad +10 -5
11 11
    image: u64,
12 12
    /// Local self-Domain capability with allocation authority.
13 13
    domain: u64,
14 14
    /// Future window that the user can partition between its contexts.
15 15
    future: u64,
16 +
    /// Completion checker with authority to receive child lifecycle events.
17 +
    parent: u64,
16 18
}
17 19
18 20
/// Startup environment supplied by the kernel.
19 21
record Env: Copy {
20 22
    /// Readable startup capabilities.
32 34
export static count: u64 = 0;
33 35
/// Additional-context progress in the same private package state.
34 36
export static extraCount: u64 = 0;
35 37
/// Successful user-side return and destruction checks.
36 38
export static destroyed: u64 = 0;
39 +
/// Generation-bearing identity of the child that returns normally.
40 +
export static childId: u64 = 0;
37 41
/// Local self-Domain authority shared by both user contexts.
38 42
static SELF: abi::Handle = abi::Handle(0);
39 43
40 44
/// Update shared private state and return from an additional context.
41 45
export fn worker(args: *opaque, size: u64) {
42 46
    assert size == 0;
43 47
    set extraCount += 1;
44 -
    try! sys::timeout(1000000, 77);
48 +
    try! sys::timeout(2000000, 77);
45 49
    sys::wait();
46 50
    set extraCount += 1;
47 51
    set count += 1;
48 52
}
49 53
/// Check direct calls and handoff, then exercise independently bound contexts.
51 55
    if env.argsSize == 0 {
52 56
        try! sys::timeout(10000, 88);
53 57
        sys::wait();
54 58
        return;
55 59
    }
56 -
    assert env.argsSize == 32;
60 +
    assert env.argsSize == 40;
57 61
    set SELF = abi::Handle(env.argsPointer.domain);
58 62
    let handle = env.argsPointer.stack as i64;
59 63
    let info = sys::queryPage(abi::Handle(handle as u64));
60 64
    assert info.base <> 0 and info.count == 1;
61 65
    let mut rejected = false;
73 77
    let domain = sys::queryDomain(abi::Handle(env.argsPointer.domain));
74 78
    assert domain.id <> 0 and domain.state == 1;
75 79
    let image = try! sys::queryImage(abi::Handle(env.argsPointer.image));
76 80
    assert image.entry <> 0 and image.codeBase <> 0 and image.codeBytes > 0 and image.dataBytes > 0;
77 81
    let child = try! sys::domainCreate(abi::Handle(0), abi::Handle(env.argsPointer.image));
82 +
    set childId = sys::queryDomain(child).id;
83 +
    try! sys::domainReparent(child, abi::Handle(env.argsPointer.parent));
78 84
    let stack = try! sys::pageAllocate(abi::Handle(0), 1);
79 85
    let granted = try! sys::capabilityGrant(stack, child, (abi::READ | abi::WRITE) as u64);
80 86
    let childStack = sys::queryPage(stack);
81 87
    try! sys::domainActivate(child, childStack.base + 4096, (&*env) as *unsafe opaque, 0);
82 88
    assert sys::queryDomain(child).state == 1;
130 136
    assert rejected and sys::currentContext().budget == current.budget;
131 137
    set count = 1;
132 138
    try! sys::yield(child);
133 139
    let continued = sys::currentContext();
134 140
    assert continued.context == current.context and continued.budget == *future;
135 -
    assert sys::queryDomain(child).state == 2;
136 141
    while extraCount == 0 { set count += 1; }
137 142
    let waiting = try! sys::queryContext(SELF, extra);
138 143
    assert waiting.state == 2 and waiting.hart == 0xffffffffffffffff;
139 144
    try! sys::wakeup(SELF, 123);
140 -
    assert extraCount == 1;
145 +
    assert extraCount == 1 or extraCount == 2;
141 146
    while extraCount == 1 { set count += 1; }
142 147
    let stopped = try! sys::queryContext(abi::Handle(env.argsPointer.domain), extra);
143 148
    assert stopped.state == 1 and stopped.hart == 0xffffffffffffffff;
144 149
    try! sys::contextDestroy(abi::Handle(env.argsPointer.domain), extra);
145 150
    set rejected = false;
146 151
    try sys::queryContext(abi::Handle(env.argsPointer.domain), extra) catch error {
147 152
        assert error == abi::Error::BadHandle;
148 153
        set rejected = true;
149 154
    };
150 -
    assert rejected and sys::queryDomain(child).state == 2;
155 +
    assert rejected;
151 156
    set destroyed = 1;
152 157
    while true { set count += 1; }
153 158
}
test/modules/run added +21 -0
1 +
#!/bin/sh
2 +
# Resolve and execute references beyond module index 127.
3 +
set -eu
4 +
emulator=${RAD_EMULATOR:-emulator}
5 +
work=$(mktemp -d)
6 +
trap 'rm -rf "$work"' EXIT HUP INT TERM
7 +
mkdir "$work/capacity"
8 +
set -- -pkg capacity -mod "$work/capacity.rad"
9 +
index=0
10 +
while [ "$index" -lt 127 ]; do
11 +
    printf 'export mod m%s;\n' "$index" >> "$work/capacity.rad"
12 +
    printf '/// Return this module index.\nexport fn value() -> u64 { return %s; }\n' "$index" > "$work/capacity/m$index.rad"
13 +
    set -- "$@" -mod "$work/capacity/m$index.rad"
14 +
    index=$((index + 1))
15 +
done
16 +
printf 'use capacity::m126;\n/// Check a package root beyond module index 127.\n@default fn main() -> u64 { assert m126::value() == 126; return 0; }\n' > "$work/entry.rad"
17 +
set -- "$@" -pkg entry -mod "$work/entry.rad"
18 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
19 +
    -run bin/radiance.rv64.dev "$@" -entry entry -o "$work/capacity.rv64"
20 +
"$emulator" -run "$work/capacity.rv64"
21 +
printf 'module scopes: references beyond index 127 passed\n'
test/smp/kernel/dispatchcheck.rad +207 -5
17 17
use kernel::calls;
18 18
use kernel::events;
19 19
use kernel::timers;
20 20
use kernel::interrupts;
21 21
use kernel::plic;
22 +
use kernel::lifecycle;
22 23
use kernel::dispatchinput;
23 24
24 25
/// Bootstrap resource authority.
25 26
unsafe static TABLE: capability::Table = undefined;
26 27
/// Release/acquire barrier after fixture publication.
27 28
static READY: u64 = 0;
28 29
/// Shared user domain generation.
29 30
unsafe static USER: abi::Ref = undefined;
31 +
/// Pending domain in the user's creation subtree.
32 +
unsafe static DESCENDANT: abi::Ref = undefined;
30 33
/// Per-hart user execution contexts.
31 34
unsafe static CONTEXTS: [abi::Ref; 8] = undefined;
32 35
/// Mapped progress counters in the user's private state.
33 36
static COUNTERS: u64 = 0;
34 37
/// Per-context migration observations in the user's private state.
43 46
static IDLE_STACK: u64 = 0;
44 47
/// Generation of the idle-wakeup domain.
45 48
unsafe static IDLE_DOMAIN: abi::Ref = undefined;
46 49
/// Completion checker's live domain generation.
47 50
unsafe static CHECKER: abi::Ref = undefined;
51 +
/// Unbound window that expires before the checker starts.
52 +
unsafe static EXPIRED: abi::Handle = undefined;
48 53
/// Checker's authority to activate and notify the idle-wakeup domain.
49 54
unsafe static RECEIVER: abi::Handle = undefined;
50 55
/// Unbound authority reserved for the idle-wakeup check on hart one.
51 56
unsafe static IDLE_WINDOW: abi::Handle = undefined;
52 57
/// Validated physical memory mapping.
85 90
    let image = try! loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut TABLE,
86 91
        loader::Request { authority, source, offset: 0, length: length as u64 });
87 92
    let handle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE, &mut TABLE, authority, image);
88 93
    let cap = try! capability::get(&TABLE, handle);
89 94
    set USER = cap.object;
95 +
    createDescendant();
90 96
    let mut user = try! domains::get(&domains::STORE, USER);
91 97
    let self = try! capability::install(&mut user.memory.table, capability::Entry {
92 98
        kind: abi::Kind::Domain, object: USER, rights: abi::Rights(abi::ALLOCATE | abi::WAKE),
93 99
    });
94 100
    assert boot::PLATFORM.irqSources == 31;
116 122
                &TABLE, handle, abi::ContextStart { entry, stack: page.base + 4096, args: page.base, size: 8 });
117 123
        }
118 124
    }
119 125
    let checkerHandle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE, &mut TABLE, authority, image);
120 126
    let checkerCap = try! capability::get(&TABLE, checkerHandle);
121 -
    let checker = try! domains::get(&domains::STORE, checkerCap.object);
127 +
    let mut checker = try! domains::get(&domains::STORE, checkerCap.object);
122 128
    set CHECKER = checkerCap.object;
123 129
    set domains::STORE.records[checkerCap.object.index].state = domains::Lifecycle::Active;
124 130
    set domains::STORE.contexts[checker.initial.index].frame.pc = completion();
125 131
    set domains::STORE.contexts[checker.initial.index].frame.status = 0x1880;
126 132
    set domains::STORE.contexts[checker.initial.index].frame.registers[2] = domains::STORE.contexts[checker.initial.index].kernelStack.end;
133 139
    let end = start + 10000000 * harts * harts;
134 140
    let migrationEnd = end + 10000000 * harts * harts;
135 141
    let windowBase = dataAddress(&user, &"spin::windows"[..]);
136 142
    for hart in 0..8 {
137 143
        if (boot::PLATFORM.harts & (1 << hart)) == 0 { continue; }
138 -
        let budget = try! budgets::seed(&mut budgets::STORE, &mut TABLE, hart, start, 0xffffffffffffffff);
144 +
        let begin = clock if hart == 0 else start;
145 +
        let mut budget = try! budgets::seed(&mut budgets::STORE, &mut TABLE, hart, begin, 0xffffffffffffffff);
146 +
        if hart == 0 {
147 +
            let next = try! budgets::split(&mut budgets::STORE, &mut TABLE, budget, start, clock);
148 +
            set EXPIRED = try! budgets::transfer(&mut budgets::STORE, &mut TABLE, &mut checker.memory.table,
149 +
                budget, budgets::DEFAULT_RIGHTS as u64, clock);
150 +
            set budget = next;
151 +
        }
139 152
        let final = try! budgets::split(&mut budgets::STORE, &mut TABLE, budget, end, clock);
140 153
        let checking = try! budgets::split(&mut budgets::STORE, &mut TABLE, final, migrationEnd, clock);
141 154
        let future = try! budgets::split(&mut budgets::STORE, &mut TABLE, checking, migrationEnd + 10000000000, clock);
142 155
        let transferred = try! budgets::transfer(&mut budgets::STORE, &mut TABLE, &mut user.memory.table,
143 156
            future, budgets::DEFAULT_RIGHTS as u64, clock);
155 168
        }
156 169
        if hart == 1 { set IDLE_WINDOW = checking; }
157 170
    }
158 171
}
159 172
173 +
/// Create an actual pending descendant before secondary harts enter dispatch.
174 +
unsafe fn createDescendant() {
175 +
    let mut user = try! domains::get(&domains::STORE, USER);
176 +
    let root = user.image;
177 +
    let authority = try! capability::install(&mut user.memory.table, capability::Entry {
178 +
        kind: abi::Kind::Domain, object: USER, rights: abi::Rights(abi::CREATE),
179 +
    });
180 +
    let image = try! registry::install(&registry::STORE, &mut user.memory.table, root);
181 +
    let handle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings,
182 +
        &registry::STORE, &mut user.memory.table, authority, image);
183 +
    set DESCENDANT = (try! capability::get(&user.memory.table, handle)).object;
184 +
}
185 +
160 186
/// Resolve one exported private-data address in the fixture's user instance.
161 187
unsafe fn dataAddress(user: &domains::Domain, name: &[u8]) -> u64 {
162 188
    let target = try! registry::exported(&registry::STORE, user.image, name);
163 189
    let case shared::Target::Data(data) = target else panic "fixture data export";
164 190
    return user.graph.table[data.slot] + data.offset as u64;
169 195
    set IDLE_HANDLE = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings,
170 196
        &registry::STORE, &mut TABLE, authority, image);
171 197
    let permit = try! capability::get(&TABLE, IDLE_HANDLE);
172 198
    set IDLE_DOMAIN = permit.object;
173 199
    let mut domain = try! domains::get(&domains::STORE, IDLE_DOMAIN);
200 +
    let allocation = try! capability::install(&mut domain.memory.table, capability::Entry {
201 +
        kind: abi::Kind::Domain, object: IDLE_DOMAIN, rights: abi::Rights(abi::ALLOCATE),
202 +
    });
174 203
    set IDLE_CONTEXT = domain.initial;
175 204
    set WAKE_STATE = dataAddress(&domain, &"spin::wakeState"[..]);
176 205
    sync::storeRelease(memory(dataAddress(&domain, &"spin::idle"[..])) as *mut u64, 1);
177 206
    let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1);
178 207
    let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut domain.memory.table, stack, (abi::READ | abi::WRITE) as u64);
179 208
    let permission = try! capability::get(&TABLE, stack);
180 209
    let page = try! pages::get(&pages::STORE, permission.object);
181 210
    set IDLE_STACK = page.base + 4096;
182 211
    let mut checker = try! domains::get(&domains::STORE, CHECKER);
183 212
    set RECEIVER = try! capability::install(&mut checker.memory.table, capability::Entry {
184 -
        kind: abi::Kind::Domain, object: IDLE_DOMAIN, rights: abi::Rights(abi::WAKE | abi::EXECUTE),
213 +
        kind: abi::Kind::Domain, object: IDLE_DOMAIN, rights: abi::Rights(abi::WAKE | abi::EXECUTE | abi::DESTROY),
185 214
    });
186 215
}
187 216
188 217
/// Check shared progress and context isolation after the user windows finish.
189 218
export unsafe fn verify() {
190 219
    let guard = sync::enter();
220 +
    let user = try! domains::get(&domains::STORE, USER);
191 221
    let mut counts: [u64; 8] = [0; 8];
192 222
    let mut notifications: u32 = 0;
193 223
    for hart in 0..8 { if (boot::PLATFORM.harts & (1 << hart)) <> 0 { set notifications += 1; } }
194 224
    for hart in 0..8 {
195 225
        if (boot::PLATFORM.harts & (1 << hart)) == 0 { continue; }
196 226
        let value = sync::loadAcquire(memory(COUNTERS + hart as u64 * 8) as *u64);
197 227
        set counts[hart] = value;
198 228
        let context = try! domains::context(&domains::STORE, USER, CONTEXTS[hart]);
199 -
        assert context.owner == USER and (context.frame.status & 0x1800) == 0;
229 +
        assert context.owner == USER;
230 +
        let mode = context.frame.status & 0x1800;
231 +
        assert mode == 0 or mode == 0x1800;
232 +
        if mode == 0x1800 {
233 +
            assert context.frame.registers[3] == kernelGp();
234 +
            assert context.frame.registers[2] >= context.kernelStack.start and context.frame.registers[2] <= context.kernelStack.end;
235 +
        } else {
236 +
            assert context.frame.registers[3] == user.graph.table.ptr as u64;
237 +
        }
200 238
        let destination = (hart + notifications - 1) % notifications;
201 239
        assert context.hart == nil or context.hart == destination;
202 240
        assert sync::loadAcquire(memory(MIGRATIONS + hart as u64 * 8) as *u64) == destination as u64 + 1;
203 241
        assert context.pending == nil;
204 242
    }
205 243
    assert domains::STORE.events.queues[USER.index].tail == notifications + 1;
206 -
    let user = try! domains::get(&domains::STORE, USER);
207 244
    assert user.memory.ring.data[0].kind == events::INTERRUPT and user.memory.ring.data[0].code == 7;
208 245
    sync::leave(guard);
209 246
    for byte in "smp completed transactions:" { put(byte); }
210 247
    for hart in 0..8 {
211 248
        if (boot::PLATFORM.harts & (1 << hart)) == 0 { continue; }
233 270
            }
234 271
            assert acknowledged;
235 272
        }
236 273
    }
237 274
    for byte in "smp request acknowledgements passed\n" { put(byte); }
275 +
    expiredCall();
238 276
    if (boot::PLATFORM.harts & 2) <> 0 { idleWakeup(); }
239 277
    interruptOwnership();
240 278
    interruptStorm();
279 +
    if (boot::PLATFORM.harts & 2) <> 0 { terminalWakeup(); cancelAllocation(); reclaimedDomains(); }
241 280
    let maximum = sync::maximum();
242 281
    assert maximum > 0 and maximum < 1000000;
243 282
    for byte in "smp metadata instructions: 0x" { put(byte); }
244 283
    number(maximum);
245 284
    put(10);
246 285
    finish();
247 286
}
248 287
288 +
/// Validate elapsed authority against the current clock at the serialized boundary.
289 +
unsafe fn expiredCall() {
290 +
    let guard = sync::enter();
291 +
    let checker = try! domains::get(&domains::STORE, CHECKER);
292 +
    let before = try! budgets::query(&budgets::STORE, &checker.memory.table, EXPIRED);
293 +
    assert before.end < dispatch::clock();
294 +
    sync::leave(guard);
295 +
    let mut rejected = false;
296 +
    try calls::synchronized(CHECKER, 70, &[*EXPIRED, before.start + (before.end - before.start) / 2, 0, 0], 0) catch error {
297 +
        assert error == abi::Error::Busy; set rejected = true;
298 +
    };
299 +
    assert rejected;
300 +
    let checking = sync::enter();
301 +
    let after = try! budgets::query(&budgets::STORE, &checker.memory.table, EXPIRED);
302 +
    assert after.start == before.start and after.end == before.end;
303 +
    sync::leave(checking);
304 +
    for byte in "smp resumed call rejected expired authority\n" { put(byte); }
305 +
}
306 +
249 307
/// Transfer a queued IRQ, rearm after consumption, and mask it before capability drop.
250 308
unsafe fn interruptOwnership() {
251 309
    let guard = sync::enter();
252 310
    let mut user = try! domains::get(&domains::STORE, USER);
253 311
    let mut checker = try! domains::get(&domains::STORE, CHECKER);
405 463
    }
406 464
    assert resumed;
407 465
    for byte in "smp remote timeout passed\n" { put(byte); }
408 466
}
409 467
468 +
/// Wake a parent waiting on hart one through terminal delivery on hart zero.
469 +
unsafe fn terminalWakeup() {
470 +
    let guard = sync::enter();
471 +
    let mut receiver = try! domains::get(&domains::STORE, IDLE_DOMAIN);
472 +
    let timeout = try! events::pop(&mut receiver.memory.ring) else panic "missing idle timeout";
473 +
    assert timeout.kind == events::TIMEOUT and timeout.code == 0x66;
474 +
    sync::storeRelease(memory(WAKE_STATE) as *mut u64, 7);
475 +
    sync::leave(guard);
476 +
    assert waiting();
477 +
    let stopping = sync::enter();
478 +
    let mut checker = try! domains::get(&domains::STORE, CHECKER);
479 +
    let child = try! capability::install(&mut checker.memory.table, capability::Entry {
480 +
        kind: abi::Kind::Domain, object: USER, rights: abi::Rights(abi::DESTROY),
481 +
    });
482 +
    let parent = try! calls::invoke(CHECKER, 23, &[*child, *RECEIVER, 0, 0], 0);
483 +
    assert parent == 0;
484 +
    let cancelled = try! dispatch::request(1, remote::Action::Budget(remote::Call {
485 +
        owner: USER, context: CONTEXTS[0], operation: 70, arguments: [0; 4],
486 +
    }));
487 +
    set domains::STORE.contexts[CONTEXTS[0].index].pending = cancelled;
488 +
    let readiness = try! remote::reserve(&mut remote::STORE, 0, 2);
489 +
    let batch = remote::publish(&mut remote::STORE, readiness, remote::Action::Ready(CONTEXTS[1]));
490 +
    dispatch::startReady(CONTEXTS[1], &batch);
491 +
    let stopped = try! domains::terminateTree(&mut domains::STORE, USER, events::CHILD_EXIT, 23);
492 +
    assert stopped == 0;
493 +
    assert domains::STORE.records[DESCENDANT.index].state == domains::Lifecycle::Dead;
494 +
    assert try! events::acknowledged(&domains::STORE.events, DESCENDANT);
495 +
    let request = try! dispatch::request(0, remote::Action::Reschedule);
496 +
    sync::leave(stopping);
497 +
    let acknowledged = try! dispatch::awaitRequest(0, request);
498 +
    assert acknowledged == 0;
499 +
    let mut resumed = false;
500 +
    for attempt in 0..100000 {
501 +
        if sync::loadAcquire(memory(WAKE_STATE) as *u64) == 9 { set resumed = true; break; }
502 +
    }
503 +
    assert resumed;
504 +
    let checking = sync::enter();
505 +
    assert remote::STORE.slots[cancelled.index].state == slots::State::Free;
506 +
    assert remote::STORE.slots[batch.ids[1].index].state == slots::State::Free;
507 +
    assert domains::STORE.contexts[CONTEXTS[0].index].pending == nil;
508 +
    assert domains::STORE.contexts[CONTEXTS[1].index].pending == nil;
509 +
    let mut domain = try! domains::get(&domains::STORE, IDLE_DOMAIN);
510 +
    let event = try! events::pop(&mut domain.memory.ring) else panic "missing remote exit";
511 +
    assert event.kind == events::CHILD_EXIT and event.code == 23 and event.value == abi::id(USER);
512 +
    sync::leave(checking);
513 +
    for byte in "smp cascade cancelled calls and woke remote parent\n" { put(byte); }
514 +
}
515 +
516 +
/// Stop a real page-clear continuation and recover its unpublished frames.
517 +
unsafe fn cancelAllocation() {
518 +
    sync::storeRelease(memory(WAKE_STATE) as *mut u64, 10);
519 +
    let mut allocation: pages::Allocation = undefined;
520 +
    let mut request: ?abi::Ref = nil;
521 +
    for attempt in 0..100000 {
522 +
        let guard = sync::enter();
523 +
        let index = IDLE_CONTEXT.index;
524 +
        if (lifecycle::CALLS.active[index / 32] & (1 << (index % 32))) <> 0 {
525 +
            match lifecycle::CALLS.records[index].allocation {
526 +
                case lifecycle::Allocation::Page(value) => { set allocation = value; },
527 +
                else => panic "page allocation required",
528 +
            }
529 +
            assert allocation.owner == IDLE_DOMAIN and allocation.run.count == 4096;
530 +
            sync::leave(guard);
531 +
            let result = try! calls::synchronized(CHECKER, 22, &[*RECEIVER, 0, 0, 0], dispatch::clock());
532 +
            assert result == 0;
533 +
            let checking = sync::enter();
534 +
            assert domains::STORE.records[IDLE_DOMAIN.index].state == domains::Lifecycle::Dead;
535 +
            set request = try! dispatch::request(1, remote::Action::Reschedule);
536 +
            sync::leave(checking);
537 +
            break;
538 +
        }
539 +
        sync::leave(guard);
540 +
    }
541 +
    let stopped = request else panic "page call did not reserve frames";
542 +
    let acknowledged = try! dispatch::awaitRequest(0, stopped);
543 +
    assert acknowledged == 0;
544 +
    let snapshot = sync::enter();
545 +
    let context = try! domains::context(&domains::STORE, IDLE_DOMAIN, IDLE_CONTEXT);
546 +
    for byte in "smp cancelled page status and progress: " { put(byte); }
547 +
    number(context.frame.status); put(32);
548 +
    number(sync::loadAcquire(memory(WAKE_STATE) as *u64)); put(10);
549 +
    assert context.hart == nil and context.state == domains::ContextState::Stopped;
550 +
    assert (context.frame.status & 0x1800) == 0x1800;
551 +
    assert sync::loadAcquire(memory(WAKE_STATE) as *u64) == 11;
552 +
    sync::leave(snapshot);
553 +
    let mut reclaimed = false;
554 +
    for attempt in 0..1000 {
555 +
        let checking = sync::enter();
556 +
        set reclaimed = pages::STORE.backings.pool.retiredCount == 0;
557 +
        if reclaimed { sync::leave(checking); break; }
558 +
        let progress = try! dispatch::request(0, remote::Action::Reschedule);
559 +
        sync::leave(checking);
560 +
        let result = try! dispatch::awaitRequest(0, progress);
561 +
        assert result == 0;
562 +
    }
563 +
    assert reclaimed;
564 +
    let guard = sync::enter();
565 +
    assert (lifecycle::CALLS.active[IDLE_CONTEXT.index / 32] & (1 << (IDLE_CONTEXT.index % 32))) == 0;
566 +
    assert pages::STORE.slots[allocation.page.index].state == slots::State::Free;
567 +
    assert pages::STORE.backings.slots[allocation.backing.index].state == slots::State::Free;
568 +
    for i in allocation.run.first..allocation.run.first + allocation.run.count {
569 +
        assert pages::STORE.backings.pool.free[i];
570 +
    }
571 +
    sync::leave(guard);
572 +
    for byte in "smp cancelled preempted page allocation\n" { put(byte); }
573 +
}
574 +
575 +
/// Wait for dispatch-driven teardown and verify all dead execution ownership is gone.
576 +
unsafe fn reclaimedDomains() {
577 +
    for attempt in 0..100000 {
578 +
        let guard = sync::enter();
579 +
        let mut done = pages::STORE.backings.pool.retiredCount == 0;
580 +
        for owner in [USER, IDLE_DOMAIN, DESCENDANT] {
581 +
            if slots::matches(&domains::STORE.slots[..], owner, slots::State::Live) { set done = false; }
582 +
        }
583 +
        if done {
584 +
            assert domains::STORE.dead == 0;
585 +
            for owner in [USER, IDLE_DOMAIN, DESCENDANT] {
586 +
                assert domains::STORE.slots[owner.index].state == slots::State::Free;
587 +
                assert pages::STORE.backings.domains[owner.index] == 0;
588 +
                for i in 0..domains::STORE.contextSlots.len {
589 +
                    if domains::STORE.contextSlots[i].state == slots::State::Live {
590 +
                        assert domains::STORE.contexts[i].owner <> owner;
591 +
                    }
592 +
                }
593 +
                for i in 0..budgets::STORE.slots.len {
594 +
                    if budgets::STORE.slots[i].state == slots::State::Live { assert budgets::STORE.windows[i].owner <> owner; }
595 +
                }
596 +
                for i in 0..interrupts::STORE.slots.len {
597 +
                    if interrupts::STORE.slots[i].state == slots::State::Live { assert interrupts::STORE.records[i].owner <> owner; }
598 +
                }
599 +
            }
600 +
            sync::leave(guard);
601 +
            for byte in "smp reaped dead domains and recovered ownership\n" { put(byte); }
602 +
            return;
603 +
        }
604 +
        let progress = try! dispatch::request(0, remote::Action::Reschedule);
605 +
        sync::leave(guard);
606 +
        let result = try! dispatch::awaitRequest(0, progress);
607 +
        assert result == 0;
608 +
    }
609 +
    panic "domain reaper did not finish";
610 +
}
611 +
410 612
/// Confirm the waiting context has released hart ownership before its wakeup.
411 613
unsafe fn waiting() -> bool {
412 614
    for attempt in 0..100000 {
413 615
        let guard = sync::enter();
414 616
        let context = try! domains::context(&domains::STORE, IDLE_DOMAIN, IDLE_CONTEXT);
test/smp/spin.rad +9 -1
15 15
export static interrupt: u64 = 0;
16 16
17 17
/// Observed destination hart plus one after each context changes budget.
18 18
export static migrations: [u64; 8] = [0; 8];
19 19
20 -
/// Wait handshake: 2/3 bracket wakeup; 4 requests a second wait, bracketed by 5/6.
20 +
/// Progress shared with the machine fixture for waits and interrupted allocation.
21 21
export static wakeState: u64 = 0;
22 22
23 23
/// Nonzero selects the independently activated idle-wakeup context.
24 24
export static idle: u64 = 0;
25 25
72 72
        set wakeState = 3;
73 73
        while wakeState <> 4 {}
74 74
        set wakeState = 5;
75 75
        sys::wait();
76 76
        set wakeState = 6;
77 +
        while wakeState <> 7 {}
78 +
        set wakeState = 8;
79 +
        sys::wait();
80 +
        set wakeState = 9;
81 +
        while wakeState <> 10 {}
82 +
        set wakeState = 11;
83 +
        let allocation = try! sys::pageAllocate(abi::Handle(0), 4096);
84 +
        set wakeState = 12;
77 85
        while true {}
78 86
    }
79 87
    run(0);
80 88
}
test/termination/kernel/dispatchcheck.rad added +101 -0
1 +
//! Native exit and fault reporting to a surviving parent.
2 +
use std::mem;
3 +
use kernel::abi;
4 +
use kernel::slots;
5 +
use kernel::backing;
6 +
use kernel::pages;
7 +
use kernel::capability;
8 +
use kernel::registry;
9 +
use kernel::loader;
10 +
use kernel::domains;
11 +
use kernel::budgets;
12 +
use kernel::dispatch;
13 +
use kernel::boot;
14 +
use kernel::events;
15 +
use kernel::sync;
16 +
use kernel::dispatchinput;
17 +
18 +
/// Bootstrap creation, allocation, and budget authority.
19 +
unsafe static TABLE: capability::Table = undefined;
20 +
/// Child domain identities retained by terminal events.
21 +
static CHILDREN: [abi::Ref; 5] = undefined;
22 +
/// Surviving event receiver and native completion checker.
23 +
unsafe static PARENT: abi::Ref = undefined;
24 +
/// Map a validated physical page.
25 +
fn memory(address: u64) -> *mut u8;
26 +
/// Current kernel package-state base.
27 +
fn kernelGp() -> u64;
28 +
/// Native entry for the completion checker.
29 +
fn completion() -> u64;
30 +
/// Signal successful completion to the machine fixture.
31 +
fn finish();
32 +
33 +
/// Give four children disjoint windows, followed by the surviving parent.
34 +
export unsafe fn setup() {
35 +
    let pending = try! slots::reserve(&mut domains::STORE.slots[..]);
36 +
    let owner = try! slots::commit(&mut domains::STORE.slots[..], pending);
37 +
    capability::initialize(&mut TABLE, owner);
38 +
    try! backing::registerDomain(&mut pages::STORE.backings, owner);
39 +
    let authority = try! capability::install(&mut TABLE, capability::Entry {
40 +
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::CREATE | abi::ALLOCATE),
41 +
    });
42 +
    let source = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, (dispatchinput::INPUT.len as u64 + 4095) / 4096);
43 +
    let page = try! pages::get(&pages::STORE, (try! capability::get(&TABLE, source)).object);
44 +
    let bytes = @sliceOf(memory(page.base), page.count * 4096);
45 +
    let length = try! mem::copy(&mut bytes[..], &dispatchinput::INPUT[..]);
46 +
    let image = try! loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut TABLE,
47 +
        loader::Request { authority, source, offset: 0, length: length as u64 });
48 +
    let parentHandle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE, &mut TABLE, authority, image);
49 +
    set PARENT = (try! capability::get(&TABLE, parentHandle)).object;
50 +
    let parent = try! domains::get(&domains::STORE, PARENT);
51 +
    set domains::STORE.records[PARENT.index].state = domains::Lifecycle::Active;
52 +
    set domains::STORE.contexts[parent.initial.index].frame.pc = completion();
53 +
    set domains::STORE.contexts[parent.initial.index].frame.status = 0x1880;
54 +
    set domains::STORE.contexts[parent.initial.index].frame.registers[2] = domains::STORE.contexts[parent.initial.index].kernelStack.end;
55 +
    set domains::STORE.contexts[parent.initial.index].frame.registers[3] = kernelGp();
56 +
    let mut handles: [abi::Handle; 5] = undefined;
57 +
    let mut contexts: [abi::Ref; 5] = undefined;
58 +
    for i in 0..5 {
59 +
        let handle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE, &mut TABLE, authority, image);
60 +
        let object = (try! capability::get(&TABLE, handle)).object;
61 +
        let mut child = try! domains::get(&domains::STORE, object);
62 +
        set CHILDREN[i] = object;
63 +
        set handles[i] = handle;
64 +
        set contexts[i] = child.initial;
65 +
        try! domains::reparent(&mut domains::STORE, &TABLE, handle, parentHandle);
66 +
        let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1);
67 +
        let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut child.memory.table, stack, (abi::READ | abi::WRITE) as u64);
68 +
        let storage = try! pages::get(&pages::STORE, (try! capability::get(&TABLE, stack)).object);
69 +
        try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, handle, storage.base + 4096, storage.base, i as u64);
70 +
    }
71 +
    let timer = try! dispatch::timer(&boot::PLATFORM, 0);
72 +
    let clock = dispatch::now(timer);
73 +
    let start = clock + 1000000;
74 +
    let mut remaining = try! budgets::seed(&mut budgets::STORE, &mut TABLE, 0, start, start + 12000000);
75 +
    for i in 0..5 {
76 +
        let next = try! budgets::split(&mut budgets::STORE, &mut TABLE, remaining, start + (i as u64 + 1) * 2000000, clock);
77 +
        let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE,
78 +
            budgets::Binding { budget: remaining, domain: handles[i], context: contexts[i] }, clock);
79 +
        set remaining = next;
80 +
    }
81 +
    let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE,
82 +
        budgets::Binding { budget: remaining, domain: parentHandle, context: parent.initial }, clock);
83 +
}
84 +
85 +
/// Check each terminal kind, status, and generation while the parent continues to run.
86 +
export unsafe fn verify() {
87 +
    let guard = sync::enter();
88 +
    let mut parent = try! domains::get(&domains::STORE, PARENT);
89 +
    let codes = [37 as u32, abi::FAULT_ABORT, 3, 0, abi::FAULT_ABORT];
90 +
    for i in 0..5 {
91 +
        let event = try! events::pop(&mut parent.memory.ring) else panic "missing termination event";
92 +
        assert event.value == abi::id(CHILDREN[i]) and event.code == codes[i];
93 +
        if i == 0 or i == 3 { assert event.kind == events::CHILD_EXIT; }
94 +
        else { assert event.kind == events::FAULT; }
95 +
        assert domains::STORE.records[CHILDREN[i].index].state == domains::Lifecycle::Dead;
96 +
    }
97 +
    try! events::refresh(&mut domains::STORE.events, PARENT, &parent.memory.ring);
98 +
    assert try! events::pop(&mut parent.memory.ring) == nil;
99 +
    sync::leave(guard);
100 +
    finish();
101 +
}
test/termination/machine.ras added +25 -0
1 +
//! Boot one hart and check user termination through production dispatch.
2 +
.text;
3 +
    call @kernel::boot::initialize;
4 +
    call @kernel::dispatchcheck::setup;
5 +
    call @kernel::boot::run;
6 +
.export @kernel::dispatchcheck::memory;
7 +
.export @kernel::dispatchcheck::kernelGp;
8 +
.export @kernel::dispatchcheck::completion;
9 +
.export @kernel::dispatchcheck::finish;
10 +
@kernel::dispatchcheck::memory
11 +
    ret;
12 +
@kernel::dispatchcheck::kernelGp
13 +
    mv %a0 %gp;
14 +
    ret;
15 +
@kernel::dispatchcheck::completion
16 +
    la %a0 @complete;
17 +
    ret;
18 +
@complete
19 +
    call @kernel::dispatchcheck::verify;
20 +
    ebreak;
21 +
@kernel::dispatchcheck::finish
22 +
    li %t0 0x10001000;
23 +
    li %t1 0x5555;
24 +
    sw %t1 0(%t0);
25 +
    ebreak;
test/termination/spin.rad added +30 -0
1 +
//! User termination and CPU-fault entry paths.
2 +
export mod abi;
3 +
export mod sys;
4 +
5 +
/// Issue a raw call to test the kernel error boundary.
6 +
@intrinsic fn ecall(operation: u32, a0: i64, a1: i64, a2: i64, a3: i64) -> i64;
7 +
8 +
/// Startup values used to select the termination operation.
9 +
record Env: Copy {
10 +
    /// Optional argument bytes supplied by the parent.
11 +
    argsPointer: *u8,
12 +
    /// Termination case selected by the parent.
13 +
    argsSize: u64,
14 +
    /// Installed event handle.
15 +
    eventsHandle: u64,
16 +
    /// Shared event-ring address.
17 +
    eventsPointer: u64,
18 +
}
19 +
20 +
/// Exercise an explicit exit, explicit abort, or a user breakpoint fault.
21 +
@default fn main(env: *Env) {
22 +
    if env.argsSize == 0 { sys::exit(37); }
23 +
    if env.argsSize == 1 { sys::abort(); }
24 +
    if env.argsSize == 3 { try! sys::domainDestroy(abi::Handle(0), 0); }
25 +
    if env.argsSize == 4 {
26 +
        let result = ecall(47, 0, 0, 0, 0);
27 +
        sys::exit(99);
28 +
    }
29 +
    assert false;
30 +
}