kernel: Create domain instances

5188225d627b2a3c6632af36400548ad937dbaa32e57a7adaf0066271077f696
Create pending domains with independent relocated package graphs,
installed Events, and initial integer contexts. Propagate only the
authorizing capability creation and allocation rights to the returned
management handle, with rollback for exhausted storage and failed
initialization.

Verify diamond graphs, selective grants, capacity failures, and native
execution of shared code with independent state. Raise the hosted
compiler persistent arena to 128 MiB for the complete binary RIL
fixture.

Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent 5400cd48
kernel/kernel.rad +2 -0
16 16
export mod registry;
17 17
export mod frames;
18 18
export mod backing;
19 19
export mod pages;
20 20
export mod loader;
21 +
export mod instances;
22 +
export mod domains;
21 23
export mod boot;
22 24
@test export mod tests;
kernel/kernel/boot.rad +2 -0
6 6
use super::limits;
7 7
use super::sync;
8 8
use super::trap;
9 9
use super::pages;
10 10
use super::registry;
11 +
use super::domains;
11 12
use std::arch::rv64::shared::catalog;
12 13
13 14
/// Platform data published by hart zero before secondary initialization.
14 15
export unsafe static PLATFORM: platform::Platform = undefined;
15 16
/// Release/acquire publication flag for PLATFORM.
68 69
        try! platform::decode(&blob[..], &mut PLATFORM);
69 70
        let treeRange = range::new(treeAddress, size as u64) else panic "FDT range";
70 71
        assert platform::inRam(&PLATFORM, treeRange);
71 72
        try! platform::protect(&mut PLATFORM, treeRange);
72 73
        try! pages::initialize(&mut pages::STORE, &PLATFORM);
74 +
        domains::initialize(&mut domains::STORE);
73 75
        assert entryCount == 2;
74 76
        registry::initialize(&mut registry::STORE);
75 77
        let bootCatalog = @sliceOf(entries, entryCount);
76 78
        try! registry::boot(&mut registry::STORE, &bootCatalog[..]);
77 79
        let library = registry::find(&registry::STORE, &"std"[..]) else panic "boot std";
kernel/kernel/domains.rad added +190 -0
1 +
//! Domain creation and ownership of initial contexts and private memory.
2 +
3 +
use super::abi;
4 +
use super::limits;
5 +
use super::slots;
6 +
use super::frames;
7 +
use super::backing;
8 +
use super::capability;
9 +
use super::registry;
10 +
use super::instances;
11 +
use super::events;
12 +
use super::trap;
13 +
14 +
/// Management rights returned to a domain's creator.
15 +
export constant MANAGEMENT_RIGHTS: u16 = abi::DESTROY | abi::EXECUTE | abi::GRANT | abi::TRANSFER | abi::WAKE;
16 +
17 +
/// Protection-domain lifetime independent of execution budgets.
18 +
export union Lifecycle: Copy {
19 +
    /// Resources can be installed before one-time activation.
20 +
    Pending,
21 +
    /// The domain can execute when a context has CPU authority.
22 +
    Active,
23 +
    /// Execution has stopped and the identity can remain as a tombstone.
24 +
    Dead,
25 +
}
26 +
27 +
/// Private domain metadata and its shared event ring.
28 +
export record Memory: Copy {
29 +
    /// Authoritative capabilities; only the kernel accesses this table.
30 +
    table: capability::Table,
31 +
    /// Event payload and consumer counters exposed to this domain.
32 +
    ring: events::Ring,
33 +
}
34 +
35 +
/// Saved integer state owned by one execution context.
36 +
export record Context: Copy {
37 +
    /// Domain that owns this context.
38 +
    owner: abi::Ref,
39 +
    /// Architectural state used when this context resumes.
40 +
    frame: trap::Frame,
41 +
}
42 +
43 +
/// Published domain resources and lifecycle metadata.
44 +
export record Domain: Copy {
45 +
    /// Activation and teardown state.
46 +
    state: Lifecycle,
47 +
    /// Historical creator generation.
48 +
    creator: abi::Ref,
49 +
    /// Current lifecycle-event receiver, if present.
50 +
    parent: ?abi::Ref,
51 +
    /// Root resident image for this domain's private graph.
52 +
    image: abi::Ref,
53 +
    /// Initial execution context, before additional contexts are created.
54 +
    initial: abi::Ref,
55 +
    /// Frames containing the capability table and event ring.
56 +
    allocation: frames::Run,
57 +
    /// Mapped domain metadata retained while the domain is live.
58 +
    memory: *unsafe mut Memory,
59 +
    /// Private package-state allocation and gp table.
60 +
    graph: instances::Instance,
61 +
    /// Nondelegable Events handle in the domain's own table.
62 +
    events: abi::Handle,
63 +
}
64 +
65 +
/// Fixed domain, context, and notification metadata under caller serialization.
66 +
export record Store: Copy {
67 +
    /// Domain generations, including retained dead identities.
68 +
    slots: [slots::Slot; limits::DOMAINS],
69 +
    /// Payload for each published domain.
70 +
    records: [Domain; limits::DOMAINS],
71 +
    /// Context generations shared by initial and additional contexts.
72 +
    contextSlots: [slots::Slot; limits::CONTEXTS],
73 +
    /// Payload for each published execution context.
74 +
    contexts: [Context; limits::CONTEXTS],
75 +
    /// Trusted event producers and terminal records.
76 +
    events: events::Store,
77 +
}
78 +
79 +
/// Global domain and execution-context metadata.
80 +
export unsafe static STORE: Store = undefined;
81 +
82 +
/// View an allocated physical extent that contains a complete Memory record.
83 +
fn memory(address: u64) -> *unsafe mut Memory;
84 +
/// Clear whole mapped physical frames before installing domain metadata.
85 +
fn zero(address: u64, count: u32);
86 +
87 +
/// Initialize fresh metadata before boot-domain publication.
88 +
export fn initialize(store: &mut Store) {
89 +
    slots::initialize(&mut store.slots[..]);
90 +
    slots::initialize(&mut store.contextSlots[..]);
91 +
    events::initialize(&mut store.events);
92 +
}
93 +
94 +
/// Read a domain record after validating its retained generation.
95 +
export fn get(store: &Store, object: abi::Ref) -> Domain throws (abi::Error) {
96 +
    if not slots::matches(&store.slots[..], object, slots::State::Live) {
97 +
        throw abi::Error::BadHandle;
98 +
    }
99 +
    return store.records[object.index];
100 +
}
101 +
102 +
/// Memory allocated before the domain and its context become observable.
103 +
record Prepared: Copy {
104 +
    /// Frames for capabilities and the event ring.
105 +
    allocation: frames::Run,
106 +
    /// Mapped metadata at the allocation's start.
107 +
    memory: *unsafe mut Memory,
108 +
    /// Independent package-state graph.
109 +
    graph: instances::Instance,
110 +
}
111 +
112 +
/// Allocate private storage and return all earlier allocations on failure.
113 +
unsafe fn prepare(pool: &mut frames::Pool, packages: &registry::Store, image: abi::Ref) -> Prepared throws (abi::Error) {
114 +
    let pending = try frames::reserve(pool, (@sizeOf(Memory) + 4095) / 4096);
115 +
    let allocation = frames::commit(pending);
116 +
    let graph = try instances::create(packages, pool, image) catch err {
117 +
        try! frames::release(pool, allocation); throw err;
118 +
    };
119 +
    let extent = try! frames::extent(pool, allocation);
120 +
    zero(extent.start, allocation.count);
121 +
    return Prepared { allocation, memory: memory(extent.start), graph };
122 +
}
123 +
124 +
/// Return storage that has never been exposed to a published domain.
125 +
fn discard(pool: &mut frames::Pool, prepared: Prepared) {
126 +
    try! frames::release(pool, prepared.graph.frames);
127 +
    try! frames::release(pool, prepared.allocation);
128 +
}
129 +
130 +
/// Create an unscheduled domain with Events and one initial integer context.
131 +
/// The caller serializes publication and keeps the authorizing domain alive.
132 +
export unsafe fn create(store: &mut Store, backings: &mut backing::Store, packages: &registry::Store,
133 +
    table: &mut capability::Table, authority: abi::Handle, image: abi::Handle) -> abi::Handle throws (abi::Error)
134 +
{
135 +
    let permit = try capability::authority(table, authority, abi::Rights(abi::CREATE));
136 +
    if not backing::domainLive(backings, table.owner) or not backing::domainLive(backings, permit.object) {
137 +
        throw abi::Error::BadHandle;
138 +
    }
139 +
    let root = try registry::image(packages, table, image, abi::Rights(abi::EXECUTE));
140 +
    let package = try! registry::get(packages, root);
141 +
    let entry = package.entry else {
142 +
        throw abi::Error::VerifyFailed;
143 +
    };
144 +
    let handleSlot = try slots::reserve(&mut table.slots[..]);
145 +
    let domainSlot = try slots::reserve(&mut store.slots[..]) catch err {
146 +
        try! slots::cancel(&mut table.slots[..], handleSlot); throw err;
147 +
    };
148 +
    let contextSlot = try slots::reserve(&mut store.contextSlots[..]) catch err {
149 +
        try! slots::cancel(&mut store.slots[..], domainSlot);
150 +
        try! slots::cancel(&mut table.slots[..], handleSlot); throw err;
151 +
    };
152 +
    let mut prepared = try prepare(&mut backings.pool, packages, root) catch err {
153 +
        try! slots::cancel(&mut store.contextSlots[..], contextSlot);
154 +
        try! slots::cancel(&mut store.slots[..], domainSlot);
155 +
        try! slots::cancel(&mut table.slots[..], handleSlot); throw err;
156 +
    };
157 +
    let object = slots::reference(&domainSlot);
158 +
    try backing::registerDomain(backings, object) catch err {
159 +
        discard(&mut backings.pool, prepared);
160 +
        try! slots::cancel(&mut store.contextSlots[..], contextSlot);
161 +
        try! slots::cancel(&mut store.slots[..], domainSlot);
162 +
        try! slots::cancel(&mut table.slots[..], handleSlot); throw err;
163 +
    };
164 +
    capability::initialize(&mut prepared.memory.table, object);
165 +
    try events::open(&mut store.events, object, &mut prepared.memory.ring) catch err {
166 +
        try! backing::endDomain(backings, object);
167 +
        discard(&mut backings.pool, prepared);
168 +
        try! slots::cancel(&mut store.contextSlots[..], contextSlot);
169 +
        try! slots::cancel(&mut store.slots[..], domainSlot);
170 +
        try! slots::cancel(&mut table.slots[..], handleSlot); throw err;
171 +
    };
172 +
    let queue = try! capability::install(&mut prepared.memory.table, capability::Entry {
173 +
        kind: abi::Kind::Events, object, rights: abi::Rights(abi::READ | abi::WRITE),
174 +
    });
175 +
    let initial = slots::reference(&contextSlot);
176 +
    // MPIE enables interrupts after the first return into U-mode.
177 +
    let mut frame = trap::Frame { registers: [0; 32], pc: entry, status: 0x80, cause: 0, value: 0 };
178 +
    set frame.registers[3] = prepared.graph.table.ptr as u64;
179 +
    set store.contexts[initial.index] = Context { owner: object, frame };
180 +
    set store.records[object.index] = Domain {
181 +
        state: Lifecycle::Pending, creator: table.owner, parent: table.owner, image: root, initial,
182 +
        allocation: prepared.allocation, memory: prepared.memory, graph: prepared.graph, events: queue,
183 +
    };
184 +
    let context = try! slots::commit(&mut store.contextSlots[..], contextSlot);
185 +
    let domain = try! slots::commit(&mut store.slots[..], domainSlot);
186 +
    return capability::publish(table, handleSlot, capability::Entry {
187 +
        kind: abi::Kind::Domain, object: domain,
188 +
        rights: abi::Rights(MANAGEMENT_RIGHTS | (*permit.rights & (abi::CREATE | abi::ALLOCATE))),
189 +
    });
190 +
}
kernel/kernel/domains.ras added +12 -0
1 +
//! Physical domain metadata views and complete frame clearing.
2 +
.text;
3 +
.export @kernel::domains::memory;
4 +
.export @kernel::domains::zero;
5 +
6 +
// The allocated frame extent contains one complete aligned Memory record.
7 +
@kernel::domains::memory
8 +
    ret;
9 +
10 +
// The physical page implementation clears whole mapped frames.
11 +
@kernel::domains::zero
12 +
    j @kernel::pages::zero;
kernel/kernel/instances.rad added +99 -0
1 +
//! Private package-state allocation and relocation for one domain graph.
2 +
3 +
use std::arch::rv64::shared;
4 +
use super::abi;
5 +
use super::limits;
6 +
use super::frames;
7 +
use super::registry;
8 +
9 +
/// Physical storage owned by one private package graph.
10 +
export record Instance: Copy {
11 +
    /// Complete allocation retained until the domain stops.
12 +
    frames: frames::Run,
13 +
    /// Package bases indexed by resident slot; absent packages have zero bases.
14 +
    table: *unsafe [u64],
15 +
}
16 +
17 +
/// Bounded traversal and conservative storage requirement for a package graph.
18 +
record Graph: Copy {
19 +
    /// Reachable package references, each present once.
20 +
    packages: [abi::Ref; limits::PACKAGES],
21 +
    /// Number of initialized package references.
22 +
    count: u32,
23 +
    /// Bytes for the base table, private state, and worst-case alignment padding.
24 +
    bytes: u64,
25 +
}
26 +
27 +
/// Map an allocated physical extent to typed storage at the call site.
28 +
fn memory(address: u64) -> *unsafe mut opaque;
29 +
30 +
/// Collect the reachable immutable packages with a bounded queue.
31 +
unsafe fn graph(store: &registry::Store, root: abi::Ref) -> Graph throws (abi::Error) {
32 +
    let package = try registry::get(store, root);
33 +
    let mut result = Graph { packages: undefined, count: 1, bytes: limits::PACKAGES as u64 * 8 };
34 +
    let mut seen = [false; limits::PACKAGES];
35 +
    set result.packages[0] = root;
36 +
    set seen[root.index] = true;
37 +
    let mut next: u32 = 0;
38 +
    while next < result.count {
39 +
        let object = result.packages[next];
40 +
        let package = try registry::get(store, object);
41 +
        if package.alignment == 0 or (package.alignment & (package.alignment - 1)) <> 0 {
42 +
            throw abi::Error::VerifyFailed;
43 +
        }
44 +
        set result.bytes += package.memory as u64 + package.alignment as u64 - 1;
45 +
        if result.bytes > limits::FRAMES as u64 * limits::FRAME_SIZE {
46 +
            throw abi::Error::OutOfMemory;
47 +
        }
48 +
        for i in 0..store.counts[object.index] {
49 +
            let dependency = store.dependencies[object.index][i];
50 +
            let target = try registry::get(store, dependency);
51 +
            if seen[dependency.index] {
52 +
                continue;
53 +
            }
54 +
            set seen[dependency.index] = true;
55 +
            set result.packages[result.count] = dependency;
56 +
            set result.count += 1;
57 +
        }
58 +
        set next += 1;
59 +
    }
60 +
    return result;
61 +
}
62 +
63 +
/// Assign every base before initializing templates and their private pointers.
64 +
unsafe fn initialize(store: &registry::Store, graph: &Graph, base: u64) -> *unsafe [u64] throws (abi::Error) {
65 +
    let table = @sliceOf(memory(base) as *unsafe mut u64, limits::PACKAGES);
66 +
    for i in 0..table.len {
67 +
        set table[i] = 0;
68 +
    }
69 +
    let mut next = base + limits::PACKAGES as u64 * 8;
70 +
    for object in &graph.packages[..graph.count] {
71 +
        let package = try! registry::get(store, object);
72 +
        let mask = package.alignment as u64 - 1;
73 +
        set next = (next + mask) & ~mask;
74 +
        set table[object.index] = next;
75 +
        set next += package.memory as u64;
76 +
    }
77 +
    for object in &graph.packages[..graph.count] {
78 +
        let package = try! registry::get(store, object);
79 +
        let bytes = @sliceOf(memory(table[object.index]) as *unsafe mut u8, package.memory);
80 +
        try shared::instantiate(&package, &table[..], bytes) catch {
81 +
            throw abi::Error::VerifyFailed;
82 +
        };
83 +
    }
84 +
    return &table[..];
85 +
}
86 +
87 +
/// Allocate and initialize one private graph without copying resident code.
88 +
/// The caller serializes frame reservations and keeps the registry immutable.
89 +
export unsafe fn create(store: &registry::Store, pool: &mut frames::Pool, root: abi::Ref) -> Instance throws (abi::Error) {
90 +
    let layout = try graph(store, root);
91 +
    let count = ((layout.bytes + limits::FRAME_SIZE - 1) / limits::FRAME_SIZE) as u32;
92 +
    let pending = try frames::reserve(pool, count);
93 +
    let run = frames::commit(pending);
94 +
    let extent = try! frames::extent(pool, run);
95 +
    let table = try initialize(store, &layout, extent.start) catch err {
96 +
        try! frames::release(pool, run); throw err;
97 +
    };
98 +
    return Instance { frames: run, table };
99 +
}
kernel/kernel/instances.ras added +7 -0
1 +
//! Physical views of allocated private package-state storage.
2 +
.text;
3 +
.export @kernel::instances::memory;
4 +
5 +
// The caller owns a complete validated physical frame extent.
6 +
@kernel::instances::memory
7 +
    ret;
kernel/kernel/tests.rad +2 -0
12 12
export mod pages;
13 13
export mod transactions;
14 14
export mod events;
15 15
export mod registry;
16 16
export mod loader;
17 +
export mod instances;
18 +
export mod domains;
kernel/kernel/tests/domains.rad added +195 -0
1 +
//! Pending domain creation, initial context state, and creation authority.
2 +
3 +
use std::testing;
4 +
use std::arch::rv64::shared;
5 +
use kernel::abi;
6 +
use kernel::slots;
7 +
use kernel::backing;
8 +
use kernel::capability;
9 +
use kernel::registry;
10 +
use kernel::domains;
11 +
use kernel::transactions;
12 +
13 +
/// Mapped physical memory for child metadata and package instances.
14 +
static RAM: [u64; 131072] = [0; 131072];
15 +
/// Domain and context metadata under test.
16 +
unsafe static DOMAINS: domains::Store = undefined;
17 +
/// Physical allocation and exposure state.
18 +
unsafe static BACKINGS: backing::Store = undefined;
19 +
/// Shared resident image registry.
20 +
unsafe static PACKAGES: registry::Store = undefined;
21 +
/// Calling bootstrap domain's capability table.
22 +
unsafe static TABLE: capability::Table = undefined;
23 +
/// Executable image capability in the calling table.
24 +
static IMAGE: abi::Handle = abi::Handle(0);
25 +
26 +
/// Initialize bootstrap authority and one executable image over mapped frames.
27 +
unsafe fn initialize(rights: u16) {
28 +
    domains::initialize(&mut DOMAINS);
29 +
    backing::initialize(&mut BACKINGS);
30 +
    registry::initialize(&mut PACKAGES);
31 +
    let pointer = &RAM[0];
32 +
    let base = pointer as u64;
33 +
    let aligned = (base + 4095) & ~4095;
34 +
    set BACKINGS.pool.count = (@sizeOf([u64; 131072]) - (aligned - base) as u32) / 4096;
35 +
    for i in 0..BACKINGS.pool.count {
36 +
        set BACKINGS.pool.addresses[i] = aligned + i as u64 * 4096;
37 +
        set BACKINGS.pool.free[i] = true;
38 +
    }
39 +
    let pending = try! slots::reserve(&mut DOMAINS.slots[..]);
40 +
    let owner = try! slots::commit(&mut DOMAINS.slots[..], pending);
41 +
    try! backing::registerDomain(&mut BACKINGS, owner);
42 +
    capability::initialize(&mut TABLE, owner);
43 +
    let authority = try! capability::install(&mut TABLE, capability::Entry {
44 +
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(rights),
45 +
    });
46 +
    let imageSlot = try! registry::reserve(&mut PACKAGES);
47 +
    let image = slots::reference(&imageSlot);
48 +
    let object = try! registry::publish(&mut PACKAGES, imageSlot, "program", shared::Package {
49 +
        name: "program", dependencies: &[], slot: image.index, codeAddress: 0x80000000,
50 +
        code: &[0x00008067 as u32], exports: &[], entry: 0x80000000,
51 +
        template: &[7], memory: 8, alignment: 8, relocations: &[],
52 +
    });
53 +
    set IMAGE = try! registry::install(&PACKAGES, &mut TABLE, object);
54 +
}
55 +
56 +
/// Creation installs only Events in the child and preserves selected authorizing rights.
57 +
@test unsafe fn pending() throws (testing::TestError) {
58 +
    initialize(abi::CREATE | abi::ALLOCATE);
59 +
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
60 +
    let authority = try! capability::get(&TABLE, handle);
61 +
    try testing::expect(*authority.rights == domains::MANAGEMENT_RIGHTS | abi::CREATE | abi::ALLOCATE);
62 +
    let child = try! domains::get(&DOMAINS, authority.object);
63 +
    try testing::expect(child.state == domains::Lifecycle::Pending);
64 +
    try testing::expect(child.creator == TABLE.owner and child.parent == TABLE.owner);
65 +
    try testing::expect(backing::domainLive(&BACKINGS, authority.object));
66 +
    let events = try! capability::get(&child.memory.table, child.events);
67 +
    try testing::expect(events.kind == abi::Kind::Events and events.object == authority.object);
68 +
    try testing::expect(*events.rights == abi::READ | abi::WRITE);
69 +
    try testing::expect(child.memory.table.slots[1].state == slots::State::Free);
70 +
    let context = DOMAINS.contexts[child.initial.index];
71 +
    try testing::expect(context.owner == authority.object and context.frame.pc == 0x80000000);
72 +
    try testing::expect(context.frame.registers[3] == child.graph.table.ptr as u64);
73 +
    try testing::expect(context.frame.registers[2] == 0 and context.frame.status == 0x80);
74 +
    try testing::expect(child.memory.ring.head == 0 and child.memory.ring.tail == 0);
75 +
    try testing::expect(child.memory.ring.mask == 255);
76 +
}
77 +
78 +
/// Check that a failed creation has returned all unpublished resources.
79 +
unsafe fn failed(expected: abi::Error) throws (testing::TestError) {
80 +
    let mut rejected = false;
81 +
    try domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE) catch err {
82 +
        try testing::expect(err == expected); set rejected = true;
83 +
    };
84 +
    try testing::expect(rejected);
85 +
    for i in 0..BACKINGS.pool.count {
86 +
        try testing::expect(BACKINGS.pool.free[i]);
87 +
    }
88 +
    try testing::expect(DOMAINS.slots[1].state == slots::State::Free);
89 +
    try testing::expect(DOMAINS.contextSlots[0].state == slots::State::Free);
90 +
    try testing::expect(TABLE.slots[2].state == slots::State::Free);
91 +
    try testing::expect(BACKINGS.domains[1] == 0);
92 +
}
93 +
94 +
/// Failure at each memory allocation or event opening returns every reservation.
95 +
@test unsafe fn rollback() throws (testing::TestError) {
96 +
    initialize(abi::CREATE);
97 +
    let count = BACKINGS.pool.count;
98 +
    set BACKINGS.pool.count = 0;
99 +
    try failed(abi::Error::OutOfMemory);
100 +
    set BACKINGS.pool.count = (@sizeOf(domains::Memory) + 4095) / 4096;
101 +
    try failed(abi::Error::OutOfMemory);
102 +
    set BACKINGS.pool.count = count;
103 +
    set DOMAINS.events.queues[1].generation = 99;
104 +
    try failed(abi::Error::Busy);
105 +
    try testing::expect(DOMAINS.events.queues[1].generation == 99);
106 +
    set DOMAINS.events.queues[1].generation = 0;
107 +
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
108 +
    let entry = try! capability::get(&TABLE, handle);
109 +
    try testing::expect(*entry.rights == domains::MANAGEMENT_RIGHTS | abi::CREATE);
110 +
}
111 +
112 +
/// Context exhaustion does not consume a domain slot, handle, or physical frame.
113 +
@test unsafe fn contextCapacity() throws (testing::TestError) {
114 +
    initialize(abi::CREATE);
115 +
    for i in 0..DOMAINS.contextSlots.len {
116 +
        set DOMAINS.contextSlots[i].state = slots::State::Retired;
117 +
    }
118 +
    let mut rejected = false;
119 +
    try domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE) catch err {
120 +
        try testing::expect(err == abi::Error::Exhausted); set rejected = true;
121 +
    };
122 +
    try testing::expect(rejected and DOMAINS.slots[1].state == slots::State::Free);
123 +
    try testing::expect(TABLE.slots[2].state == slots::State::Free);
124 +
    for i in 0..BACKINGS.pool.count {
125 +
        try testing::expect(BACKINGS.pool.free[i]);
126 +
    }
127 +
}
128 +
129 +
/// A creator can grant Create without Allocate and the next creation preserves that choice.
130 +
@test unsafe fn selectiveAuthority() throws (testing::TestError) {
131 +
    initialize(abi::CREATE | abi::ALLOCATE);
132 +
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
133 +
    let entry = try! capability::get(&TABLE, handle);
134 +
    let mut child = try! domains::get(&DOMAINS, entry.object);
135 +
    let owner = TABLE.owner;
136 +
    let grant = try! transactions::reserve(&mut child.memory.table, owner, handle, entry, abi::CREATE as u64, false);
137 +
    let authority = transactions::grant(&mut child.memory.table, grant);
138 +
    let image = try! capability::get(&TABLE, IMAGE);
139 +
    let imageGrant = try! transactions::reserve(&mut child.memory.table, owner, IMAGE, image, abi::EXECUTE as u64, false);
140 +
    let childImage = transactions::grant(&mut child.memory.table, imageGrant);
141 +
    let grandchild = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut child.memory.table,
142 +
        abi::Handle(0), childImage);
143 +
    let returned = try! capability::get(&child.memory.table, grandchild);
144 +
    try testing::expect(*returned.rights == domains::MANAGEMENT_RIGHTS | abi::CREATE);
145 +
    let created = try! domains::get(&DOMAINS, returned.object);
146 +
    try testing::expect(created.creator == entry.object and created.parent == entry.object);
147 +
    let mut denied = false;
148 +
    try capability::authority(&child.memory.table, abi::Handle(0), abi::Rights(abi::ALLOCATE)) catch err {
149 +
        try testing::expect(err == abi::Error::Denied); set denied = true;
150 +
    };
151 +
    try testing::expect(denied);
152 +
}
153 +
154 +
/// Missing creation rights, stale authority, and non-executable images allocate nothing.
155 +
@test unsafe fn authority() throws (testing::TestError) {
156 +
    initialize(abi::ALLOCATE);
157 +
    try failed(abi::Error::Denied);
158 +
    initialize(abi::CREATE);
159 +
    set BACKINGS.domains[0] = 0;
160 +
    try failed(abi::Error::BadHandle);
161 +
    initialize(abi::CREATE);
162 +
    set TABLE.entries[1].rights = abi::Rights(abi::READ);
163 +
    try failed(abi::Error::Denied);
164 +
    initialize(abi::CREATE);
165 +
    set PACKAGES.entries[0].package.entry = nil;
166 +
    try failed(abi::Error::VerifyFailed);
167 +
}
168 +
169 +
/// Handle and domain table exhaustion preserve all physical capacity.
170 +
@test unsafe fn capacity() throws (testing::TestError) {
171 +
    initialize(abi::CREATE);
172 +
    for i in 2..TABLE.slots.len {
173 +
        set TABLE.slots[i].state = slots::State::Retired;
174 +
    }
175 +
    let mut failures: u32 = 0;
176 +
    try domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE) catch err {
177 +
        try testing::expect(err == abi::Error::Exhausted); set failures += 1;
178 +
    };
179 +
    try testing::expect(DOMAINS.slots[1].state == slots::State::Free);
180 +
    for i in 0..BACKINGS.pool.count {
181 +
        try testing::expect(BACKINGS.pool.free[i]);
182 +
    }
183 +
    initialize(abi::CREATE);
184 +
    for i in 1..DOMAINS.slots.len {
185 +
        set DOMAINS.slots[i].state = slots::State::Retired;
186 +
    }
187 +
    try domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE) catch err {
188 +
        try testing::expect(err == abi::Error::Exhausted); set failures += 1;
189 +
    };
190 +
    try testing::expect(failures == 2 and TABLE.slots[2].state == slots::State::Free);
191 +
    try testing::expect(DOMAINS.contextSlots[0].state == slots::State::Free);
192 +
    for i in 0..BACKINGS.pool.count {
193 +
        try testing::expect(BACKINGS.pool.free[i]);
194 +
    }
195 +
}
kernel/kernel/tests/instances.rad added +117 -0
1 +
//! Private package-state graphs over shared resident code.
2 +
3 +
use std::testing;
4 +
use std::arch::rv64::shared;
5 +
use kernel::abi;
6 +
use kernel::frames;
7 +
use kernel::slots;
8 +
use kernel::registry;
9 +
use kernel::instances;
10 +
11 +
/// Mapped storage for independent private graphs.
12 +
static RAM: [u64; 131072] = [0; 131072];
13 +
/// Physical allocation metadata.
14 +
unsafe static POOL: frames::Pool = undefined;
15 +
/// Immutable package metadata shared by all instances.
16 +
unsafe static PACKAGES: registry::Store = undefined;
17 +
18 +
/// Initialize a frame pool over aligned mapped memory.
19 +
unsafe fn initialize() {
20 +
    let pointer = &RAM[0];
21 +
    let base = pointer as u64;
22 +
    let aligned = (base + 4095) & ~4095;
23 +
    set POOL.count = (@sizeOf([u64; 131072]) - (aligned - base) as u32) / 4096;
24 +
    for i in 0..POOL.count {
25 +
        set POOL.addresses[i] = aligned + i as u64 * 4096;
26 +
        set POOL.free[i] = true;
27 +
    }
28 +
    registry::initialize(&mut PACKAGES);
29 +
}
30 +
31 +
/// Publish a package with one private word and optional pointer relocation.
32 +
unsafe fn register(name: *[u8], dependencies: *[*[u8]], relocations: *[shared::Relocation]) -> abi::Ref {
33 +
    return registerLayout(name, dependencies, relocations, 8, 8);
34 +
}
35 +
36 +
/// Publish explicit private-state bounds for alignment and allocation checks.
37 +
unsafe fn registerLayout(name: *[u8], dependencies: *[*[u8]], relocations: *[shared::Relocation], size: u32, alignment: u32) -> abi::Ref {
38 +
    let pending = try! registry::reserve(&mut PACKAGES);
39 +
    let reference = slots::reference(&pending);
40 +
    return try! registry::publish(&mut PACKAGES, pending, name, shared::Package {
41 +
        name, dependencies: (&dependencies[..]) as *unsafe [*[u8]], slot: reference.index, codeAddress: 0x80000000,
42 +
        code: &[0x00008067 as u32], exports: &[], entry: 0x80000000,
43 +
        template: &[7, 0, 0, 0, 0, 0, 0, 0], memory: size, alignment, relocations,
44 +
    });
45 +
}
46 +
47 +
/// Map an instance's private word through the original test allocation.
48 +
fn word(address: u64) -> *mut u64 {
49 +
    let pointer = &RAM[0];
50 +
    return &mut RAM[((address - pointer as u64) / 8) as u32];
51 +
}
52 +
53 +
/// A diamond has one dependency instance per domain and independent relocated pointers.
54 +
@test unsafe fn diamond() throws (testing::TestError) {
55 +
    initialize();
56 +
    let base = register("base", &[], &[]);
57 +
    unsafe static relocation: [shared::Relocation; 1] = undefined;
58 +
    set relocation = [shared::Relocation { offset: 0, count: 1, target: shared::DataRef { slot: base.index, offset: 0 } }];
59 +
    let left = register("left", &["base"], &relocation[..]);
60 +
    let right = register("right", &["base"], &relocation[..]);
61 +
    let root = register("root", &["left", "right"], &[]);
62 +
    let first = try! instances::create(&PACKAGES, &mut POOL, root);
63 +
    let second = try! instances::create(&PACKAGES, &mut POOL, root);
64 +
    try testing::expect(first.table.len == 256 and second.table.len == 256);
65 +
    try testing::expect(first.table[base.index] <> second.table[base.index]);
66 +
    try testing::expect(*word(first.table[left.index]) == first.table[base.index]);
67 +
    try testing::expect(*word(first.table[right.index]) == first.table[base.index]);
68 +
    try testing::expect(*word(second.table[left.index]) == second.table[base.index]);
69 +
    set *word(first.table[base.index]) = 99;
70 +
    try testing::expect(*word(second.table[base.index]) == 7);
71 +
    try testing::expect(first.table[4] == 0 and second.table[4] == 0);
72 +
    try! frames::release(&mut POOL, first.frames);
73 +
    try! frames::release(&mut POOL, second.frames);
74 +
    for i in 0..POOL.count {
75 +
        try testing::expect(POOL.free[i]);
76 +
    }
77 +
}
78 +
79 +
/// State alignment can exceed a physical frame and zero-fill follows the template.
80 +
@test unsafe fn alignment() throws (testing::TestError) {
81 +
    initialize();
82 +
    let root = registerLayout("aligned", &[], &[], 16, 8192);
83 +
    let state = try! instances::create(&PACKAGES, &mut POOL, root);
84 +
    try testing::expect((state.table[root.index] & 8191) == 0);
85 +
    try testing::expect(*word(state.table[root.index]) == 7);
86 +
    try testing::expect(*word(state.table[root.index] + 8) == 0);
87 +
    try! frames::release(&mut POOL, state.frames);
88 +
}
89 +
90 +
/// Failed allocation and private relocation leave no retained instance frames.
91 +
@test unsafe fn rollback() throws (testing::TestError) {
92 +
    initialize();
93 +
    let root = register("valid", &[], &[]);
94 +
    let count = POOL.count;
95 +
    set POOL.count = 0;
96 +
    let mut failures: u32 = 0;
97 +
    try instances::create(&PACKAGES, &mut POOL, root) catch err {
98 +
        try testing::expect(err == abi::Error::OutOfMemory); set failures += 1;
99 +
    };
100 +
    set POOL.count = count;
101 +
    let bad = register("bad", &[], &[shared::Relocation {
102 +
        offset: 0, count: 1, target: shared::DataRef { slot: 5, offset: 0 },
103 +
    }]);
104 +
    try instances::create(&PACKAGES, &mut POOL, bad) catch err {
105 +
        try testing::expect(err == abi::Error::VerifyFailed); set failures += 1;
106 +
    };
107 +
    try instances::create(&PACKAGES, &mut POOL, abi::Ref { index: root.index, generation: 2 }) catch err {
108 +
        try testing::expect(err == abi::Error::BadHandle); set failures += 1;
109 +
    };
110 +
    try testing::expect(failures == 3);
111 +
    for i in 0..POOL.count {
112 +
        try testing::expect(POOL.free[i]);
113 +
    }
114 +
    let retry = try! instances::create(&PACKAGES, &mut POOL, root);
115 +
    try testing::expect(*word(retry.table[root.index]) == 7);
116 +
    try! frames::release(&mut POOL, retry.frames);
117 +
}
test/boot/run +2 -2
2 2
# Boot the kernel through its production initialization on each supported hart count.
3 3
set -eu
4 4
emulator=${RAD_EMULATOR:-emulator}
5 5
work=$(mktemp -d)
6 6
trap 'rm -rf "$work"' EXIT HUP INT TERM
7 -
cat test/boot/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras > "$work/boot.ras"
7 +
cat test/boot/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras > "$work/boot.ras"
8 8
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/boot.ras" "$work/boot.rv64"
9 9
for harts in 1 2 8; do
10 10
    # The deterministic emulator shares its tick budget across all harts.
11 11
    steps=$((10000000 * harts))
12 12
    if ! "$emulator" -machine -max-steps="$steps" -harts="$harts" -run "$work/boot.rv64" > "$work/log" 2>&1; then
28 28
if [ "$status" -ne 2 ] || [ "$(grep -c 'wfi=1 mcause=0x0' "$work/log")" -ne 8 ]; then
29 29
    cat "$work/log" >&2
30 30
    exit 1
31 31
fi
32 32
printf 'kernel startup: all eight harts reached machine idle without traps\n'
33 -
cat test/boot/trap.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras > "$work/trap.ras"
33 +
cat test/boot/trap.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras > "$work/trap.ras"
34 34
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/trap.ras" "$work/trap.rv64"
35 35
status=0
36 36
"$emulator" -machine -harts=2 -max-steps=20000000 -run "$work/trap.rv64" > "$work/log" 2>&1 || status=$?
37 37
if [ "$status" -ne 2 ] || [ "$(grep -c '^kernel: unexpected trap$' "$work/log")" -ne 1 ] \
38 38
    || [ "$(grep -c 'wfi=1' "$work/log")" -ne 2 ]; then
test/loader/kernel/loadcheck.rad +21 -5
6 6
use kernel::capability;
7 7
use kernel::pages;
8 8
use kernel::registry;
9 9
use kernel::loader;
10 10
use kernel::loadinput;
11 +
use kernel::domains;
12 +
use kernel::slots;
11 13
12 14
/// Capability storage for the loading domain.
13 15
unsafe static TABLE: capability::Table = undefined;
14 16
15 17
/// View a validated source Page as mapped bytes.
16 18
fn memory(address: u64) -> *mut u8;
17 19
18 -
/// Call a published entry that does not access private package state.
19 -
fn invoke(address: u64) -> i64;
20 +
/// Call a published entry with one instance's package-state table.
21 +
fn invoke(address: u64, table: u64) -> i64;
20 22
21 23
/// Load the hosted comparison package and execute its retained native instructions.
22 24
export unsafe fn run() {
23 -
    let owner = abi::Ref { index: 0, generation: 1 };
25 +
    let pending = try! slots::reserve(&mut domains::STORE.slots[..]);
26 +
    let owner = try! slots::commit(&mut domains::STORE.slots[..], pending);
24 27
    capability::initialize(&mut TABLE, owner);
25 28
    try! backing::registerDomain(&mut pages::STORE.backings, owner);
26 29
    let authority = try! capability::install(&mut TABLE, capability::Entry {
27 -
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE),
30 +
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::CREATE | abi::ALLOCATE),
28 31
    });
29 32
    let source = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1);
30 33
    let cap = try! capability::lookup(&TABLE, source, abi::Kind::Page, abi::Rights(abi::READ));
31 34
    let page = try! pages::get(&pages::STORE, cap.object);
32 35
    let bytes = @sliceOf(memory(page.base), 4096);
38 41
    let package = try! registry::get(&registry::STORE, object);
39 42
    let entry = package.entry else panic "loaded entry";
40 43
    for i in 0..length {
41 44
        set bytes[i] = 0;
42 45
    }
43 -
    assert invoke(entry) == 43;
46 +
    assert package.relocations.len > 0;
47 +
    let firstHandle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE, &mut TABLE, authority, handle);
48 +
    let secondHandle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE, &mut TABLE, authority, handle);
49 +
    let firstEntry = try! capability::get(&TABLE, firstHandle);
50 +
    let secondEntry = try! capability::get(&TABLE, secondHandle);
51 +
    let first = try! domains::get(&domains::STORE, firstEntry.object);
52 +
    let second = try! domains::get(&domains::STORE, secondEntry.object);
53 +
    assert first.state == domains::Lifecycle::Pending and second.state == domains::Lifecycle::Pending;
54 +
    assert domains::STORE.contexts[first.initial.index].frame.pc == entry;
55 +
    assert domains::STORE.contexts[second.initial.index].frame.pc == entry;
56 +
    assert first.memory.ring.mask == 255 and second.memory.ring.mask == 255;
57 +
    assert invoke(entry, first.graph.table.ptr as u64) == 43;
58 +
    assert invoke(entry, first.graph.table.ptr as u64) == 85;
59 +
    assert invoke(entry, second.graph.table.ptr as u64) == 43;
44 60
}
test/loader/loaded.rad +9 -1
1 1
//! A package executed by both hosted compilation and the kernel loader.
2 2
3 +
/// Counter owned by each package instance.
4 +
static counter: i64 = 1;
5 +
/// Relocated slice pointing into this instance's initialized data.
6 +
static factor: *[u8] = "6";
7 +
3 8
/// Compute a result through a package-local call.
4 -
fn calculate(value: i64) -> i64 { return value * 6 + 1; }
9 +
fn calculate(value: i64) -> i64 {
10 +
    set counter += value * (factor[0] as i64 - 48);
11 +
    return counter;
12 +
}
5 13
6 14
/// Return the result checked by both execution paths.
7 15
@default fn main() -> i32 { return calculate(7) as i32; }
test/loader/machine.ras +10 -2
15 15
16 16
// The caller supplies an address within its validated source Page.
17 17
@kernel::loadcheck::memory
18 18
    ret;
19 19
20 -
// Tail-call published code with no private-state accesses.
20 +
// Preserve kernel state while calling one private instance of shared code.
21 21
@kernel::loadcheck::invoke
22 -
    jalr %zero %a0 0;
22 +
    addi %sp %sp -16;
23 +
    sd %ra 8(%sp);
24 +
    sd %gp 0(%sp);
25 +
    mv %gp %a1;
26 +
    jalr %ra %a0 0;
27 +
    ld %gp 0(%sp);
28 +
    ld %ra 8(%sp);
29 +
    addi %sp %sp 16;
30 +
    ret;
test/loader/run +1 -1
20 20
printf '//! Binary package used by both execution paths.\n/// Complete trusted binary RIL input.\nexport static INPUT: [u8; %s] = [\n' "$length" > "$work/kernel/loadinput.rad"
21 21
od -An -v -tu1 "$work/loaded.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/loadinput.rad"
22 22
printf '];\n' >> "$work/kernel/loadinput.rad"
23 23
sh test/acceptance/compile "$emulator" "$work"
24 24
cat test/loader/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras \
25 -
    kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras > "$work/loader.ras"
25 +
    kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras > "$work/loader.ras"
26 26
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
27 27
    -run bin/kernel.build.rv64 -- "$work/std.ril" "$work/kernel.ril" "$work/loader.ras" "$work/loader.rv64"
28 28
"$emulator" -machine -memory-size=262144 -max-steps=200000000 -run "$work/loader.rv64"
29 29
printf 'runtime loader: hosted and loaded entries agree after workspace release\n'