kernel: reclaim domain resources after lifecycle quiescence

dde478e3e6cbc3a2b9189e616fa46f9e2d3375fcd92c13204421190f93d54b21
Verified: make -C kernel check; lifecycle transitions, terminal events, surviving Page recipients, and native instance reclamation pass.
Alexis Sellier committed ago 1 parent 04ed5958
kernel/NOTES.md +23 -2
1 1
# Kernel implementation decisions
2 2
3 3
The specification at https://radiant.computer/system/kernel takes precedence
4 4
for fixed call numbers, handle layout, rights, and object behavior. These notes
5 -
record the contracts established through step 18 of the 22-step plan.
5 +
record the contracts established through step 19 of the 22-step plan.
6 6
7 7
## Source and trust boundary
8 8
9 9
- Kernel mechanisms use freestanding Radiance; RAS owns machine entry, register
10 10
  state, atomics, and MMIO. Hosted checks exercise the same mechanism modules.
257 257
  old incarnation's ancestry bit without erasing surviving ancestors. Private
258 258
  frame ownership prevents reuse before reclamation.
259 259
- The eighth native image checks instance state: initial value 41 becomes 42
260 260
  and 44 across two contexts in one domain, while another domain reports 42.
261 261
262 +
## Termination and reclamation
263 +
264 +
- The first accepted Exit or Fault fixes the terminal report. Dying domains
265 +
  immediately lose lookup, activation, and dispatch authority. Retain physical
266 +
  state until every context has returned its architectural frame and completed
267 +
  budget accounting.
268 +
- Destruction may select creation descendants independently of notification
269 +
  parents. Reparent requires Destroy on the child and Wake on the new parent;
270 +
  reserve the new terminal credit before releasing the old one. Queued terminal
271 +
  reports keep their original receiver.
272 +
- Reclamation releases context bindings, private stacks, installed capabilities,
273 +
  and lifetime Page pins. Surviving recipients retain Page memory. Domain,
274 +
  Events, handle, and context incarnations prevent stale-name reuse.
275 +
- Root termination obeys the same lifecycle rules. Surviving children become
276 +
  parentless and retain independent authority. Domain exit is not machine
277 +
  shutdown.
278 +
- Checks cover running-context retention, cascades through dead creators,
279 +
  exhausted reparenting credits, stale names, and malformed parent progress.
280 +
  Destroying an image instance must preserve its sibling and immutable initializer
281 +
  so a newly created instance starts from the same value.
282 +
262 283
## Validation
263 284
264 285
Use the current machine-capable sibling emulator. Set `RAD_EMULATOR`, pass
265 286
`EMU` to the kernel Make invocation, or put `emulator` on PATH. The kernel build
266 287
checks compiler dependencies. From the repository root, run:
267 288
268 289
```sh
269 290
make -C kernel check
270 291
```
271 292
272 -
Run the eight-image native workload and hosted activation checks. Check complete argument/stack coverage, independent private instances, and two contexts sharing one instance with reported values 42 and 44 versus 42 in another.
293 +
Exercise deferred resource retention, creation cascades, reparenting capacity, first terminal reports, surviving Page recipients, and stale identities. Destroy an instance, preserve its sibling, then create and execute a fresh instance from the unchanged initializer.
273 294
274 295
Run the context reservation probe with an emulator that retains LR/SC
275 296
reservations across traps. This checks the kernel's reservation invalidation.
kernel/check.rad +2 -0
12 12
mod budget_caps;
13 13
mod timers;
14 14
mod notifications;
15 15
mod devices;
16 16
mod activation;
17 +
mod lifecycle;
17 18
18 19
/// Run the available kernel mechanism checks.
19 20
@default fn main() -> u32 {
20 21
    frames::run();
21 22
    boot::run();
28 29
    budget_caps::run();
29 30
    timers::run();
30 31
    notifications::run();
31 32
    devices::run();
32 33
    activation::run();
34 +
    lifecycle::run();
33 35
    return 0;
34 36
}
kernel/check/lifecycle.rad added +285 -0
1 +
//! Incarnation invalidation, creation ancestry, credits, and quiescent teardown.
2 +
3 +
use core::abi;
4 +
use core::budget_caps;
5 +
use core::budgets;
6 +
use core::capabilities;
7 +
use core::contexts;
8 +
use core::domains;
9 +
use core::events;
10 +
use core::fdt;
11 +
use core::frames;
12 +
use core::handles;
13 +
use core::lifecycle;
14 +
use core::memory;
15 +
use core::pages;
16 +
use core::platform;
17 +
use core::resources;
18 +
use core::state;
19 +
20 +
/// Simulated physical backing, never dereferenced as a host physical address.
21 +
static RAM: [u8; 64 * frames::PAGE_SIZE] = undefined;
22 +
/// Enough slots for a creator chain, survivors, and an unrelated replacement.
23 +
static DOMAINS: [domains::Domain; 8] = undefined;
24 +
/// Page and retained-budget identities.
25 +
static OBJECTS: [resources::Slot; 8] = undefined;
26 +
/// Both contexts must quiesce before either private stack is reclaimed.
27 +
static CONTEXTS: [contexts::Context; 2] = undefined;
28 +
/// Frame availability and lifetime accounting.
29 +
static POOL: frames::Pool = undefined;
30 +
static PINS: [u16; 64] = undefined;
31 +
static ASSIGNED: [bool; 64] = undefined;
32 +
static GRANTS: [u64; 8] = undefined;
33 +
/// Public mechanism state shared by each isolated scenario.
34 +
static KERNEL: state::State = undefined;
35 +
36 +
/// Clear only the allocator's simulated physical range.
37 +
fn clear(base: u64, size: u32) {
38 +
    assert base <= RAM.len as u64 and size as u64 <= RAM.len as u64 - base;
39 +
    for i in base as u32..base as u32 + size { set RAM[i] = 0; }
40 +
}
41 +
42 +
/// Reset boot-owned storage between behavior scenarios.
43 +
fn setup() -> abi::Object {
44 +
    let mut machine: platform::Platform = undefined;
45 +
    set machine.memoryCount = 1;
46 +
    set machine.memory[0] = fdt::Range { base: 0, size: RAM.len as u64 };
47 +
    set machine.reservedCount = 0;
48 +
    try! frames::init(&mut POOL, &machine);
49 +
    let mut ram: memory::Memory = undefined;
50 +
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], DOMAINS.len);
51 +
    domains::init(&mut DOMAINS[..]);
52 +
    resources::init(&mut OBJECTS[..]);
53 +
    for i in 0..CONTEXTS.len { contexts::init(&mut CONTEXTS[i], i); }
54 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
55 +
    let root = domains::root(&mut DOMAINS[..], 0);
56 +
    return try! domains::resolve(&DOMAINS[..], 0, root, abi::CREATE);
57 +
}
58 +
59 +
/// Install test-owned authority through the real handle generation mechanism.
60 +
fn handle(caller: u32, object: abi::Object, rights: u16) -> abi::Handle {
61 +
    resources::retain(&mut OBJECTS[..], object);
62 +
    return handles::install(&mut DOMAINS[caller].handles, try! handles::vacant(&DOMAINS[caller].handles), object, rights);
63 +
}
64 +
65 +
/// A healthy consumer permits one complete bounded reclamation pass.
66 +
fn poll() {
67 +
    if let bad = lifecycle::poll(&mut KERNEL) { panic "poll: unexpected invalid consumer"; }
68 +
}
69 +
70 +
/// Reject stale authority through consumer-visible lookup, not table inspection.
71 +
fn badHandle(caller: u32, name: abi::Handle) {
72 +
    let _entry = try capabilities::lookup(&KERNEL, caller, name) catch error {
73 +
        assert error == abi::Error::BadHandle;
74 +
        return;
75 +
    };
76 +
    panic "badHandle: stale capability regained authority";
77 +
}
78 +
79 +
/// Require rejected destroy requests to report an error rather than succeed idle.
80 +
fn badRequest(caller: u32, name: abi::Handle, flags: u64, expected: abi::Error) {
81 +
    let _owner = try lifecycle::request(&mut KERNEL, caller, name, flags) catch error {
82 +
        assert error == expected;
83 +
        return;
84 +
    };
85 +
    panic "badRequest: invalid destroy succeeded";
86 +
}
87 +
88 +
/// Old execution identities remain invalid after their slots are prepared again.
89 +
fn badContext(id: u64) {
90 +
    let _index = try contexts::resolve(&CONTEXTS[..], id) catch error {
91 +
        assert error == abi::Error::InvalidArg;
92 +
        return;
93 +
    };
94 +
    panic "badContext: stale context resolved";
95 +
}
96 +
97 +
/// Preserve physical ownership until all charged execution has returned.
98 +
fn quiescence() {
99 +
    let root = setup();
100 +
    let child = try! domains::create(&mut DOMAINS[..], root, 0);
101 +
    let target = handle(0, child, abi::DOMAIN_RIGHTS);
102 +
    let targetSlot = try! handles::slot(&DOMAINS[0].handles, target);
103 +
    let self = handle(child.index, child, abi::ROOT_RIGHTS);
104 +
    let parent = handle(child.index, root, abi::WAKE);
105 +
    let oldEvents = abi::Handle { bits: DOMAINS[child.index].env.eventsHandle };
106 +
    let eventsAlias = try! capabilities::grant(&mut KERNEL, child.index, oldEvents, abi::Handle { bits: 0 }, abi::READ as u64);
107 +
    let shared = try! pages::allocate(&mut KERNEL, child.index, self, 2, clear);
108 +
    let external = try! capabilities::grant(&mut KERNEL, child.index, shared, parent, abi::PAGE_RIGHTS as u64);
109 +
    let dropped = try! pages::allocate(&mut KERNEL, child.index, self, 1, clear);
110 +
    try! capabilities::drop(&mut KERNEL, child.index, dropped);
111 +
    let _exclusive = try! pages::allocate(&mut KERNEL, child.index, self, 1, clear);
112 +
    set DOMAINS[child.index].private = frames::install(try! frames::allocate(&mut POOL, 2, clear));
113 +
    set DOMAINS[child.index].state = domains::Lifecycle::Active;
114 +
    for i in 0..CONTEXTS.len {
115 +
        try! contexts::prepare(&mut CONTEXTS[i], child, 32, 4096, DOMAINS[child.index].env);
116 +
        set CONTEXTS[i].private = frames::install(try! frames::allocate(&mut POOL, 1, clear));
117 +
    }
118 +
    let id = contexts::identity(&CONTEXTS[0]);
119 +
    let second = contexts::identity(&CONTEXTS[1]);
120 +
    let budget = try! resources::create(&mut OBJECTS[..], resources::Value::Budget(try! budgets::init(0, 100)));
121 +
    let budgetHandle = handle(0, budget, abi::EXECUTE);
122 +
    try! budget_caps::bind(&mut KERNEL, 0, budgetHandle, target, id);
123 +
    try! capabilities::drop(&mut KERNEL, 0, budgetHandle);
124 +
    let running = try! contexts::begin(&mut CONTEXTS[0], budget_caps::value(&mut KERNEL, budget), 10, 0);
125 +
    let owner = try! lifecycle::request(&mut KERNEL, 0, target, 0);
126 +
    badHandle(0, target);
127 +
    badHandle(child.index, oldEvents);
128 +
    badHandle(child.index, eventsAlias);
129 +
    badRequest(0, target, 0, abi::Error::BadHandle);
130 +
    poll();
131 +
    assert not lifecycle::completed(&DOMAINS[..], owner);
132 +
    assert POOL.available == 56;
133 +
    assert try! contexts::resolve(&CONTEXTS[..], second) == 1;
134 +
    assert DOMAINS[0].events.ring.tail == 0;
135 +
    assert budget_caps::value(&mut KERNEL, budget).context == id;
136 +
    assert contexts::finish(&mut CONTEXTS[0], budget_caps::value(&mut KERNEL, budget), running, 15) == 95;
137 +
    poll();
138 +
    assert lifecycle::completed(&DOMAINS[..], owner);
139 +
    badContext(id);
140 +
    badContext(second);
141 +
    assert POOL.available == 62;
142 +
    assert resources::kind(OBJECTS[budget.index].value) == abi::Kind::Empty;
143 +
    assert (try! pages::access(&KERNEL, 0, external, abi::READ)).size == 2 * frames::PAGE_SIZE as u64;
144 +
    assert DOMAINS[0].events.ring.tail == 1;
145 +
    assert DOMAINS[0].events.ring.data[0].kind == 4 and DOMAINS[0].events.ring.data[0].value == child.index as u64;
146 +
    poll();
147 +
    assert DOMAINS[0].events.ring.tail == 1;
148 +
    let replacement = try! domains::create(&mut DOMAINS[..], root, 0);
149 +
    assert replacement.index == child.index and replacement.epoch <> child.epoch;
150 +
    let fresh = handles::install(&mut DOMAINS[0].handles, targetSlot, replacement, abi::DOMAIN_RIGHTS);
151 +
    assert fresh.bits <> target.bits;
152 +
    badHandle(0, target);
153 +
    badHandle(replacement.index, oldEvents);
154 +
    badHandle(replacement.index, eventsAlias);
155 +
    try! contexts::prepare(&mut CONTEXTS[0], replacement, 64, 0, DOMAINS[replacement.index].env);
156 +
    badContext(id);
157 +
    assert contexts::identity(&CONTEXTS[0]) <> id;
158 +
    try! capabilities::drop(&mut KERNEL, 0, external);
159 +
    assert POOL.available == 62;
160 +
    lifecycle::terminate(&mut KERNEL, root, 4, 0);
161 +
    poll();
162 +
    assert POOL.available == 64;
163 +
    assert domains::live(&DOMAINS[..], replacement);
164 +
    assert DOMAINS[replacement.index].parent.kind == abi::Kind::Empty;
165 +
}
166 +
167 +
/// An exited intermediate creator cannot sever a surviving ancestor's cascade.
168 +
fn ancestry() {
169 +
    let root = setup();
170 +
    let ancestor = try! domains::create(&mut DOMAINS[..], root, 0);
171 +
    let middle = try! domains::create(&mut DOMAINS[..], ancestor, 0);
172 +
    let leaf = try! domains::create(&mut DOMAINS[..], middle, 0);
173 +
    let target = handle(0, ancestor, abi::DESTROY);
174 +
    lifecycle::terminate(&mut KERNEL, middle, 4, 17);
175 +
    poll();
176 +
    assert domains::live(&DOMAINS[..], leaf);
177 +
    assert DOMAINS[leaf.index].parent.kind == abi::Kind::Empty;
178 +
    let replacement = try! domains::create(&mut DOMAINS[..], root, 0);
179 +
    assert replacement.index == middle.index and replacement.epoch <> middle.epoch;
180 +
    let unrelated = handle(0, replacement, abi::DESTROY);
181 +
    let _replacement = try! lifecycle::request(&mut KERNEL, 0, unrelated, 1);
182 +
    assert domains::live(&DOMAINS[..], leaf);
183 +
    poll();
184 +
    let _ancestor = try! lifecycle::request(&mut KERNEL, 0, target, 1);
185 +
    assert not domains::live(&DOMAINS[..], ancestor) and not domains::live(&DOMAINS[..], leaf);
186 +
    poll();
187 +
    assert lifecycle::completed(&DOMAINS[..], ancestor) and lifecycle::completed(&DOMAINS[..], leaf);
188 +
    assert domains::live(&DOMAINS[..], root);
189 +
}
190 +
191 +
/// Noncascade destruction leaves creation descendants alive and parentless.
192 +
fn survival() {
193 +
    let root = setup();
194 +
    let child = try! domains::create(&mut DOMAINS[..], root, 0);
195 +
    let leaf = try! domains::create(&mut DOMAINS[..], child, 0);
196 +
    let denied = handle(0, child, abi::WAKE);
197 +
    badRequest(0, denied, 0, abi::Error::Denied);
198 +
    badRequest(child.index, abi::Handle { bits: 0 }, 1, abi::Error::InvalidArg);
199 +
    badRequest(0, denied, 2, abi::Error::InvalidArg);
200 +
    let _owner = try! lifecycle::request(&mut KERNEL, child.index, abi::Handle { bits: 0 }, 0);
201 +
    lifecycle::terminate(&mut KERNEL, child, 3, 99);
202 +
    poll();
203 +
    assert domains::live(&DOMAINS[..], leaf);
204 +
    assert DOMAINS[leaf.index].parent.kind == abi::Kind::Empty;
205 +
    assert DOMAINS[0].events.ring.data[0].kind == 4 and DOMAINS[0].events.ring.data[0].code == 0;
206 +
    lifecycle::terminate(&mut KERNEL, leaf, 3, 42);
207 +
    poll();
208 +
    assert lifecycle::completed(&DOMAINS[..], leaf);
209 +
    assert DOMAINS[0].events.ring.tail == 1;
210 +
}
211 +
212 +
/// Require failed reparent operations to leave their relationship usable.
213 +
fn badReparent(child: abi::Handle, parent: abi::Handle, expected: abi::Error) {
214 +
    try lifecycle::reparent(&mut KERNEL, 0, child, parent) catch error {
215 +
        assert error == expected;
216 +
        return;
217 +
    };
218 +
    panic "badReparent: invalid parent accepted";
219 +
}
220 +
221 +
/// Credit exhaustion is transactional; queued notifications never change owner.
222 +
fn parenting() {
223 +
    let root = setup();
224 +
    let child = try! domains::create(&mut DOMAINS[..], root, 0);
225 +
    let receiver = try! domains::create(&mut DOMAINS[..], root, 0);
226 +
    let prior = try! domains::create(&mut DOMAINS[..], root, 0);
227 +
    lifecycle::terminate(&mut KERNEL, prior, 4, 13);
228 +
    poll();
229 +
    let target = handle(0, child, abi::DESTROY);
230 +
    let parent = handle(0, receiver, abi::WAKE);
231 +
    let weakParent = handle(0, receiver, abi::DESTROY);
232 +
    badReparent(target, weakParent, abi::Error::Denied);
233 +
    badReparent(target, abi::Handle { bits: 0 }, abi::Error::BadHandle);
234 +
    badReparent(target, handle(0, child, abi::WAKE), abi::Error::InvalidArg);
235 +
    for i in 0..events::CRITICAL {
236 +
        try! events::reserve(&mut DOMAINS[receiver.index].events);
237 +
        try! events::push(&mut DOMAINS[receiver.index].events,
238 +
            events::Event { kind: 4, reserved: 0, code: i, value: 99 }, events::Class::Critical);
239 +
    }
240 +
    badReparent(target, parent, abi::Error::Exhausted);
241 +
    assert DOMAINS[child.index].parent.index == root.index;
242 +
    assert DOMAINS[0].events.reserved == 3;
243 +
    set DOMAINS[receiver.index].events.ring.head = 1;
244 +
    try! lifecycle::reparent(&mut KERNEL, 0, target, parent);
245 +
    assert DOMAINS[0].events.reserved == 2;
246 +
    // Same-parent no-op must not require a second delivery credit.
247 +
    try! lifecycle::reparent(&mut KERNEL, 0, target, parent);
248 +
    assert DOMAINS[receiver.index].events.reserved == events::CRITICAL;
249 +
    lifecycle::terminate(&mut KERNEL, child, 3, 77);
250 +
    lifecycle::terminate(&mut KERNEL, child, 4, 12);
251 +
    poll();
252 +
    assert DOMAINS[0].events.ring.tail == 1;
253 +
    assert DOMAINS[0].events.ring.data[0].code == 13 and DOMAINS[0].events.ring.data[0].value == prior.index as u64;
254 +
    let event = DOMAINS[receiver.index].events.ring.data[events::CRITICAL];
255 +
    assert event.kind == 3 and event.code == 77 and event.value == child.index as u64;
256 +
    assert DOMAINS[receiver.index].events.ring.data[1].value == 99;
257 +
    set DOMAINS[receiver.index].events.ring.head = events::CRITICAL + 1;
258 +
    try! events::refresh(&mut DOMAINS[receiver.index].events);
259 +
    assert DOMAINS[receiver.index].events.reserved == 0;
260 +
}
261 +
262 +
/// Malformed parent progress cannot drop a child's reserved terminal report.
263 +
fn invalidParent() {
264 +
    let root = setup();
265 +
    let child = try! domains::create(&mut DOMAINS[..], root, 0);
266 +
    lifecycle::terminate(&mut KERNEL, child, 4, 5);
267 +
    set DOMAINS[0].events.ring.head = 1;
268 +
    if let bad = lifecycle::poll(&mut KERNEL) {
269 +
        assert bad.index == root.index and bad.epoch == root.epoch;
270 +
        assert not lifecycle::completed(&DOMAINS[..], child);
271 +
        assert DOMAINS[0].events.reserved == 1;
272 +
        lifecycle::terminate(&mut KERNEL, bad, 3, 1);
273 +
    } else { panic "invalidParent: invalid consumer not reported"; }
274 +
    poll();
275 +
    assert lifecycle::completed(&DOMAINS[..], root) and lifecycle::completed(&DOMAINS[..], child);
276 +
}
277 +
278 +
/// Exercise lifecycle through real authority, accounting, and event mechanisms.
279 +
export fn run() {
280 +
    quiescence();
281 +
    ancestry();
282 +
    survival();
283 +
    parenting();
284 +
    invalidParent();
285 +
}
kernel/core.rad +1 -0
23 23
export mod devices;
24 24
export mod interrupts;
25 25
export mod mmio;
26 26
export mod physical;
27 27
export mod activation;
28 +
export mod lifecycle;
kernel/core/lifecycle.rad added +192 -0
1 +
//! Serialized domain termination, parent credits, and quiescent reclamation.
2 +
3 +
use core::abi;
4 +
use core::budget_caps;
5 +
use core::contexts;
6 +
use core::cpu;
7 +
use core::domains;
8 +
use core::events;
9 +
use core::frames;
10 +
use core::handles;
11 +
use core::memory;
12 +
use core::resources;
13 +
use core::state;
14 +
15 +
/// Compare incarnation-qualified identities, including their object kind.
16 +
fn same(left: abi::Object, right: abi::Object) -> bool {
17 +
    return left.kind == right.kind and left.index == right.index and left.epoch == right.epoch;
18 +
}
19 +
20 +
/// Record the first trusted Exit (4) or Fault (3), without creation cascade.
21 +
/// Hold the global state lock. Running harts must finish their charged intervals
22 +
/// and relinquish architectural frames before poll can reclaim this owner.
23 +
/// A stale owner or an already terminal incarnation cannot rewrite the cause.
24 +
export fn terminate(kernel: *mut state::State, owner: abi::Object, kind: u16, code: u32) {
25 +
    assert kind == 3 or kind == 4;
26 +
    if not domains::live(kernel.domains, owner) { return; }
27 +
    let domain = &mut kernel.domains[owner.index];
28 +
    set domain.terminalKind = kind;
29 +
    set domain.terminalCode = code;
30 +
    set domain.state = domains::Lifecycle::Dying;
31 +
}
32 +
33 +
/// Accept teardown under the global lock and return its named incarnation.
34 +
/// Zero names self only with zero flags; explicit handles require Destroy.
35 +
/// Bit zero selects creation descendants, independently of current parents.
36 +
/// Acceptance immediately denies new activation, dispatch, and object lookup;
37 +
/// the caller must drive hart quiescence and poll before reporting completion.
38 +
export fn request(kernel: *mut state::State, caller: u32, handle: abi::Handle, flags: u64) -> abi::Object throws (abi::Error) {
39 +
    if flags & ~1 <> 0 or (handle.bits == 0 and flags <> 0) { throw abi::Error::InvalidArg; }
40 +
    assert caller < kernel.domains.len;
41 +
    let mut owner = abi::Object { kind: abi::Kind::Domain, index: caller, epoch: kernel.domains[caller].epoch };
42 +
    if not domains::live(kernel.domains, owner) { throw abi::Error::BadHandle; }
43 +
    if handle.bits <> 0 { set owner = try domains::resolve(kernel.domains, caller, handle, abi::DESTROY); }
44 +
    terminate(kernel, owner, 4, 0);
45 +
    if flags & 1 <> 0 {
46 +
        let word = owner.index / 64;
47 +
        let bit = 1 as u64 << (owner.index as u64 & 63);
48 +
        for i in 0..kernel.domains.len {
49 +
            let domain = &kernel.domains[i];
50 +
            if domain.ancestors[word] & bit <> 0 {
51 +
                terminate(kernel, abi::Object { kind: abi::Kind::Domain, index: i, epoch: domain.epoch }, 4, 0);
52 +
            }
53 +
        }
54 +
    }
55 +
    return owner;
56 +
}
57 +
58 +
/// Cancel only the unfulfilled reservation owned by a current relationship.
59 +
/// Queued terminal entries no longer have a relationship and are never moved
60 +
/// or canceled here. A matching Dying parent still owns its private queue.
61 +
fn detach(table: *mut [domains::Domain], index: u32) {
62 +
    let parent = table[index].parent;
63 +
    if parent.kind == abi::Kind::Domain and parent.index < table.len {
64 +
        let receiver = &mut table[parent.index];
65 +
        if receiver.epoch == parent.epoch and receiver.state <> domains::Lifecycle::Dead {
66 +
            assert receiver.events.reserved > 0;
67 +
            set receiver.events.reserved -= 1;
68 +
        }
69 +
    }
70 +
    set table[index].parent = domains::none();
71 +
}
72 +
73 +
/// Atomically change terminal receiver under the global lock, not ancestry.
74 +
/// Destroy on the child and Wake on the new parent are both required.
75 +
/// Reserve before canceling the old unfulfilled credit; failure keeps the old
76 +
/// relationship intact. Already queued events stay in their original queue.
77 +
export fn reparent(kernel: *mut state::State, caller: u32, childHandle: abi::Handle, parentHandle: abi::Handle) throws (abi::Error) {
78 +
    let child = try domains::resolve(kernel.domains, caller, childHandle, abi::DESTROY);
79 +
    let parent = try domains::resolve(kernel.domains, caller, parentHandle, abi::WAKE);
80 +
    if same(child, parent) { throw abi::Error::InvalidArg; }
81 +
    if same(kernel.domains[child.index].parent, parent) { return; }
82 +
    try events::reserve(&mut kernel.domains[parent.index].events);
83 +
    detach(kernel.domains, child.index);
84 +
    set kernel.domains[child.index].parent = parent;
85 +
}
86 +
87 +
/// Test completion of the named incarnation, not merely loss of authority.
88 +
/// Cascade callers must also wait for their selected descendant incarnations;
89 +
/// capture that bounded set under the same lock as request, before slot reuse.
90 +
export fn completed(table: *[domains::Domain], owner: abi::Object) -> bool {
91 +
    assert owner.kind == abi::Kind::Domain and owner.epoch <> 0 and owner.index < table.len;
92 +
    return table[owner.index].epoch <> owner.epoch or table[owner.index].state == domains::Lifecycle::Dead;
93 +
}
94 +
95 +
/// All physical addresses are discarded only after architectural ownership ends.
96 +
fn emptyEnv() -> domains::Env {
97 +
    return domains::Env {
98 +
        argsPointer: 0, argsSize: 0, eventsHandle: 0, eventsPointer: 0,
99 +
        stateBase: 0, stackBase: 0, stackTop: 0, context: 0,
100 +
        privateStackBase: 0, privateStackTop: 0,
101 +
    };
102 +
}
103 +
104 +
/// Remove trusted authority, including installed Events, and release Page claims.
105 +
fn remove(kernel: *mut state::State, owner: u32, slot: u32) {
106 +
    let entry = handles::remove(&mut kernel.domains[owner].handles, slot);
107 +
    if let run = resources::release(kernel.resources, entry.object) {
108 +
        memory::release(&mut kernel.memory, run);
109 +
    }
110 +
}
111 +
112 +
/// Reclaim a quiescent owner after terminal publication or loss of its parent.
113 +
/// Retained budget bindings precede private-stack release and slot invalidation.
114 +
fn reclaim(kernel: *mut state::State, owner: abi::Object) {
115 +
    for i in 0..kernel.contexts.len {
116 +
        let context = &mut kernel.contexts[i];
117 +
        if context.status == contexts::Status::Vacant or not same(context.owner, owner) { continue; }
118 +
        assert context.status <> contexts::Status::Running;
119 +
        budget_caps::unbind(kernel, context);
120 +
        if context.private.count > 0 { frames::reclaim(kernel.memory.pool, context.private); }
121 +
        set context.private = frames::Run { first: 0, count: 0 };
122 +
        set context.status = contexts::Status::Vacant;
123 +
        set context.owner = domains::none();
124 +
        set context.hart = contexts::NO_HART;
125 +
        set context.continuation = 0;
126 +
        set context.deadline = 0;
127 +
        set context.env = emptyEnv();
128 +
        cpu::init(&mut context.frame, 0, 0, 0, 0);
129 +
    }
130 +
    for slot in 0..abi::MAX_HANDLES {
131 +
        if kernel.domains[owner.index].handles.entries[slot].object.kind <> abi::Kind::Empty {
132 +
            remove(kernel, owner.index, slot);
133 +
        }
134 +
    }
135 +
    // Removing dead Domain/Events entries advances packed-name generations.
136 +
    // These objects carry no physical-resource reference counts.
137 +
    for i in 0..kernel.domains.len {
138 +
        let domain = &kernel.domains[i];
139 +
        if domain.state <> domains::Lifecycle::Pending and domain.state <> domains::Lifecycle::Active { continue; }
140 +
        for slot in 0..abi::MAX_HANDLES {
141 +
            let object = domain.handles.entries[slot].object;
142 +
            if (object.kind == abi::Kind::Domain or object.kind == abi::Kind::Events)
143 +
                and not resources::live(kernel.resources, kernel.domains, object) {
144 +
                remove(kernel, i, slot);
145 +
            }
146 +
        }
147 +
    }
148 +
    memory::retire(&mut kernel.memory, owner.index);
149 +
    let domain = &mut kernel.domains[owner.index];
150 +
    if domain.private.count > 0 { frames::reclaim(kernel.memory.pool, domain.private); }
151 +
    set domain.private = frames::Run { first: 0, count: 0 };
152 +
    set domain.env = emptyEnv();
153 +
    events::init(&mut domain.events);
154 +
    set domain.state = domains::Lifecycle::Dead;
155 +
}
156 +
157 +
/// Make one bounded reclamation pass under the global lock; never stop a hart.
158 +
/// Orphan relationships to nonlive parents, including children still Running.
159 +
/// No owner is reclaimed while any of its contexts is Running. An invalid
160 +
/// live parent consumer returns that identity without reclaiming its child:
161 +
/// apply trusted Fault termination to the returned parent, then poll again.
162 +
/// Terminal values use the public domain index; private epochs protect reuse.
163 +
export fn poll(kernel: *mut state::State) -> ?abi::Object {
164 +
    for i in 0..kernel.domains.len {
165 +
        let domain = &kernel.domains[i];
166 +
        if domain.state == domains::Lifecycle::Dead { continue; }
167 +
        if domain.parent.kind == abi::Kind::Domain and not domains::live(kernel.domains, domain.parent) {
168 +
            detach(kernel.domains, i);
169 +
        }
170 +
    }
171 +
    for i in 0..kernel.domains.len {
172 +
        let domain = &mut kernel.domains[i];
173 +
        if domain.state <> domains::Lifecycle::Dying { continue; }
174 +
        let owner = abi::Object { kind: abi::Kind::Domain, index: i, epoch: domain.epoch };
175 +
        let mut running = false;
176 +
        for c in 0..kernel.contexts.len {
177 +
            let context = &kernel.contexts[c];
178 +
            if context.status == contexts::Status::Running and same(context.owner, owner) { set running = true; }
179 +
        }
180 +
        if running { continue; }
181 +
        let parent = domain.parent;
182 +
        if domains::live(kernel.domains, parent) {
183 +
            try events::push(&mut kernel.domains[parent.index].events, events::Event {
184 +
                kind: domain.terminalKind, reserved: 0, code: domain.terminalCode, value: i as u64,
185 +
            }, events::Class::Critical) catch { return parent; };
186 +
            // Publication consumes the relationship, not its queued reservation.
187 +
            set domain.parent = domains::none();
188 +
        }
189 +
        reclaim(kernel, owner);
190 +
    }
191 +
    return nil;
192 +
}
kernel/native/instances.rad +11 -2
6 6
use core::budgets;
7 7
use core::capabilities;
8 8
use core::clock;
9 9
use core::contexts;
10 10
use core::cpu;
11 -
11 +
use core::domains;
12 12
use core::handles;
13 13
use core::pages;
14 -
14 +
use core::lifecycle;
15 15
use core::physical;
16 16
use core::resources;
17 17
use core::state;
18 18
19 19
/// Give one explicit context its own finite budget and observe its native Exit.
56 56
    let extraId = try! activation::context(kernel, 0, first, top, range.base + 4, 4, physical::clear);
57 57
    let secondId = try! activation::activate(kernel, 0, second, top, range.base, 4, physical::clear);
58 58
    execute(kernel, first, firstId, clint, 42);
59 59
    execute(kernel, first, extraId, clint, 44);
60 60
    execute(kernel, second, secondId, clint, 42);
61 +
    let secondOwner = try! domains::resolve(kernel.domains, 0, second, 0);
62 +
    let retired = try! lifecycle::request(kernel, 0, first, 0);
63 +
    if let _bad = lifecycle::poll(kernel) { panic "run: invalid lifecycle consumer"; }
64 +
    assert lifecycle::completed(kernel.domains, retired);
65 +
    assert domains::live(kernel.domains, secondOwner);
66 +
    let fresh = try! activation::create(kernel, 0, own, image, physical::clear, physical::copy);
67 +
    let _page = try! capabilities::grant(kernel, 0, page, fresh, (abi::READ | abi::WRITE) as u64);
68 +
    let freshId = try! activation::activate(kernel, 0, fresh, top, range.base, 4, physical::clear);
69 +
    execute(kernel, fresh, freshId, clint, 42);
61 70
}