kernel: Complete preemptible runtime loading
407a0145ce370ff9fde042af1b3b1ecd075db506943939f32d16545bf4b6708a
1 parent
60847643
kernel/kernel/frames.rad
+89 -21
| 21 | 21 | ||
| 22 | 22 | /// Physical addresses and occupancy of allocatable frames, sorted by address. |
|
| 23 | 23 | export record Pool: Copy { |
|
| 24 | 24 | /// Physical base address of each eligible frame. |
|
| 25 | 25 | addresses: [u64; limits::FRAMES], |
|
| 26 | - | /// True when the frame is available for reservation. |
|
| 27 | - | free: [bool; limits::FRAMES], |
|
| 26 | + | /// One set bit per available frame, indexed from the low bit of each word. |
|
| 27 | + | free: [u64; limits::FRAMES / 64], |
|
| 28 | 28 | /// Number of valid frame-map entries. |
|
| 29 | 29 | count: u32, |
|
| 30 | 30 | /// Retired runs whose frames remain unavailable until reclamation. |
|
| 31 | 31 | retired: [Run; limits::FRAMES], |
|
| 32 | 32 | /// Index of the next retired run to reclaim. |
| 46 | 46 | ||
| 47 | 47 | /// Build a fresh frame map from complete RAM pages with no reserved byte. |
|
| 48 | 48 | /// The pool has zero valid entries if initialization fails. |
|
| 49 | 49 | export unsafe fn initialize(pool: &mut Pool, machine: &platform::Platform) throws (abi::Error) { |
|
| 50 | 50 | set pool.count = 0; |
|
| 51 | + | set pool.free = [0; limits::FRAMES / 64]; |
|
| 51 | 52 | set pool.retiredHead = 0; |
|
| 52 | 53 | set pool.retiredCount = 0; |
|
| 53 | 54 | if machine.ramCount > platform::RAM_BANKS or machine.reservedCount > platform::RESERVATIONS { |
|
| 54 | 55 | throw abi::Error::InvalidArg; |
|
| 55 | 56 | } |
| 95 | 96 | } |
|
| 96 | 97 | if count == limits::FRAMES { |
|
| 97 | 98 | throw abi::Error::Exhausted; |
|
| 98 | 99 | } |
|
| 99 | 100 | set pool.addresses[count] = address; |
|
| 100 | - | set pool.free[count] = true; |
|
| 101 | + | set pool.free[count / 64] |= 1 << (count % 64) as u64; |
|
| 101 | 102 | set count += 1; |
|
| 102 | 103 | set address += limits::FRAME_SIZE; |
|
| 103 | 104 | } |
|
| 104 | 105 | } |
|
| 105 | 106 | set pool.count = count; |
|
| 106 | 107 | } |
|
| 107 | 108 | ||
| 108 | - | /// Reserve the first physically contiguous free run with a bounded scan. |
|
| 109 | + | /// Test one valid frame's availability without exposing unused bitmap bits. |
|
| 110 | + | export fn available(pool: &Pool, index: u32) -> bool { |
|
| 111 | + | return index < pool.count and (pool.free[index / 64] & (1 << (index % 64) as u64)) <> 0; |
|
| 112 | + | } |
|
| 113 | + | ||
| 114 | + | /// Low bits selected by a nonzero word-sized span. |
|
| 115 | + | fn mask(count: u32) -> u64 { |
|
| 116 | + | assert count > 0 and count <= 64; |
|
| 117 | + | if count == 64 { |
|
| 118 | + | return 0xffffffffffffffff; |
|
| 119 | + | } |
|
| 120 | + | return (1 << count as u64) - 1; |
|
| 121 | + | } |
|
| 122 | + | ||
| 123 | + | /// Change a validated run's occupancy one bitmap word at a time. |
|
| 124 | + | fn assign(pool: &mut Pool, run: Run, free: bool) { |
|
| 125 | + | let mut first = run.first; |
|
| 126 | + | let end = first + run.count; |
|
| 127 | + | while first < end { |
|
| 128 | + | let offset = first % 64; |
|
| 129 | + | let mut count = 64 - offset; |
|
| 130 | + | if count > end - first { |
|
| 131 | + | set count = end - first; |
|
| 132 | + | } |
|
| 133 | + | let bits = mask(count) << offset as u64; |
|
| 134 | + | if free { |
|
| 135 | + | set pool.free[first / 64] |= bits; |
|
| 136 | + | } |
|
| 137 | + | else { |
|
| 138 | + | set pool.free[first / 64] &= ~bits; |
|
| 139 | + | } |
|
| 140 | + | set first += count; |
|
| 141 | + | } |
|
| 142 | + | } |
|
| 143 | + | ||
| 144 | + | /// Reserve the first contiguous run, scanning uniform bitmap words together. |
|
| 109 | 145 | export fn reserve(pool: &mut Pool, count: u32) -> Reservation throws (abi::Error) { |
|
| 110 | 146 | if count == 0 { |
|
| 111 | 147 | throw abi::Error::InvalidArg; |
|
| 112 | 148 | } |
|
| 113 | 149 | let mut first: u32 = 0; |
|
| 114 | 150 | let mut found: u32 = 0; |
|
| 115 | 151 | let mut previous: u64 = 0; |
|
| 116 | - | for i in 0..pool.count { |
|
| 117 | - | if not pool.free[i] { |
|
| 118 | - | set found = 0; |
|
| 119 | - | continue; |
|
| 152 | + | for word in 0..(pool.count + 63) / 64 { |
|
| 153 | + | let base = word * 64; |
|
| 154 | + | let mut length = pool.count - base; |
|
| 155 | + | if length > 64 { |
|
| 156 | + | set length = 64; |
|
| 120 | 157 | } |
|
| 121 | - | if found == 0 or pool.addresses[i] <> previous + limits::FRAME_SIZE { |
|
| 122 | - | set first = i; |
|
| 158 | + | let full = mask(length); |
|
| 159 | + | let bits = pool.free[word] & full; |
|
| 160 | + | if bits == 0 { |
|
| 123 | 161 | set found = 0; |
|
| 162 | + | continue; |
|
| 124 | 163 | } |
|
| 125 | - | set found += 1; |
|
| 126 | - | set previous = pool.addresses[i]; |
|
| 127 | - | if found == count { |
|
| 128 | - | for j in first..first + count { |
|
| 129 | - | set pool.free[j] = false; |
|
| 164 | + | let address = pool.addresses[base]; |
|
| 165 | + | if bits == full and pool.addresses[base + length - 1] - address == (length - 1) as u64 * limits::FRAME_SIZE { |
|
| 166 | + | if found == 0 or address <> previous + limits::FRAME_SIZE { |
|
| 167 | + | set first = base; |
|
| 168 | + | set found = 0; |
|
| 130 | 169 | } |
|
| 131 | - | return Reservation::Held(Run { first, count }); |
|
| 170 | + | set found += length; |
|
| 171 | + | set previous = pool.addresses[base + length - 1]; |
|
| 172 | + | } else { |
|
| 173 | + | for offset in 0..length { |
|
| 174 | + | let i = base + offset; |
|
| 175 | + | if (bits & (1 << offset as u64)) == 0 { |
|
| 176 | + | set found = 0; |
|
| 177 | + | continue; |
|
| 178 | + | } |
|
| 179 | + | if found == 0 or pool.addresses[i] <> previous + limits::FRAME_SIZE { |
|
| 180 | + | set first = i; |
|
| 181 | + | set found = 0; |
|
| 182 | + | } |
|
| 183 | + | set found += 1; |
|
| 184 | + | set previous = pool.addresses[i]; |
|
| 185 | + | if found >= count { |
|
| 186 | + | break; |
|
| 187 | + | } |
|
| 188 | + | } |
|
| 189 | + | } |
|
| 190 | + | if found >= count { |
|
| 191 | + | let run = Run { first, count }; |
|
| 192 | + | assign(pool, run, false); |
|
| 193 | + | return Reservation::Held(run); |
|
| 132 | 194 | } |
|
| 133 | 195 | } |
|
| 134 | 196 | throw abi::Error::OutOfMemory; |
|
| 135 | 197 | } |
|
| 136 | 198 |
| 157 | 219 | ||
| 158 | 220 | /// Return a backing allocation's frames after all handles and exposures end. |
|
| 159 | 221 | /// Only the owner of a committed run may release it. |
|
| 160 | 222 | export fn release(pool: &mut Pool, run: Run) throws (abi::Error) { |
|
| 161 | 223 | let memory = try extent(pool, run); |
|
| 162 | - | for i in run.first..run.first + run.count { |
|
| 163 | - | if pool.free[i] { |
|
| 224 | + | let mut first = run.first; |
|
| 225 | + | let end = first + run.count; |
|
| 226 | + | while first < end { |
|
| 227 | + | let offset = first % 64; |
|
| 228 | + | let mut count = 64 - offset; |
|
| 229 | + | if count > end - first { |
|
| 230 | + | set count = end - first; |
|
| 231 | + | } |
|
| 232 | + | if (pool.free[first / 64] & (mask(count) << offset as u64)) <> 0 { |
|
| 164 | 233 | throw abi::Error::BadHandle; |
|
| 165 | 234 | } |
|
| 235 | + | set first += count; |
|
| 166 | 236 | } |
|
| 167 | - | for i in run.first..run.first + run.count { |
|
| 168 | - | set pool.free[i] = true; |
|
| 169 | - | } |
|
| 237 | + | assign(pool, run, true); |
|
| 170 | 238 | } |
|
| 171 | 239 | ||
| 172 | 240 | /// Transfer exclusive ownership of a committed run to bounded reclamation. |
|
| 173 | 241 | /// The caller must not release or retire this run again. |
|
| 174 | 242 | export fn retire(pool: &mut Pool, run: Run) throws (abi::Error) { |
kernel/kernel/tests/backing.rad
+13 -13
| 15 | 15 | try! backing::registerDomain(&mut STORE, abi::Ref { index: 0, generation: 1 }); |
|
| 16 | 16 | try! backing::registerDomain(&mut STORE, abi::Ref { index: 1, generation: 1 }); |
|
| 17 | 17 | set STORE.pool.count = 4; |
|
| 18 | 18 | for i in 0..4 { |
|
| 19 | 19 | set STORE.pool.addresses[i] = 0x80000000 + i as u64 * 4096; |
|
| 20 | - | set STORE.pool.free[i] = true; |
|
| 20 | + | set STORE.pool.free[i / 64] |= 1 << (i % 64) as u64; |
|
| 21 | 21 | } |
|
| 22 | 22 | } |
|
| 23 | 23 | ||
| 24 | 24 | /// Publish one backing record with its initial page and origin exposure. |
|
| 25 | 25 | unsafe fn allocate() -> abi::Ref { |
| 50 | 50 | }; |
|
| 51 | 51 | assert invalid and backing::domainLive(&STORE, owner); |
|
| 52 | 52 | assert not try! backing::endDomainStep(&mut STORE, owner, 1); |
|
| 53 | 53 | assert not backing::domainLive(&STORE, owner); |
|
| 54 | 54 | assert STORE.slots[objects[0].index].state == slots::State::Free; |
|
| 55 | - | assert STORE.pool.retiredCount == 1 and not STORE.pool.free[0]; |
|
| 55 | + | assert STORE.pool.retiredCount == 1 and not frames::available(&STORE.pool, 0); |
|
| 56 | 56 | assert STORE.records[objects[1].index].exposed == 2; |
|
| 57 | 57 | let mut busy = false; |
|
| 58 | 58 | try backing::registerDomain(&mut STORE, abi::Ref { index: 0, generation: 2 }) catch error { |
|
| 59 | 59 | assert error == abi::Error::Busy; set busy = true; |
|
| 60 | 60 | }; |
| 69 | 69 | assert STORE.records[objects[2].index].exposed == 1; |
|
| 70 | 70 | while not try! backing::endDomainStep(&mut STORE, owner, 64) { |
|
| 71 | 71 | } |
|
| 72 | 72 | assert STORE.records[objects[2].index].exposed == 0 and STORE.records[objects[2].index].pages == 1; |
|
| 73 | 73 | assert STORE.pool.retiredCount == 1; |
|
| 74 | - | assert frames::reclaim(&mut STORE.pool, 1) == 1 and STORE.pool.free[0]; |
|
| 75 | - | assert not STORE.pool.free[1] and not STORE.pool.free[2]; |
|
| 74 | + | assert frames::reclaim(&mut STORE.pool, 1) == 1 and frames::available(&STORE.pool, 0); |
|
| 75 | + | assert not frames::available(&STORE.pool, 1) and not frames::available(&STORE.pool, 2); |
|
| 76 | 76 | let next = abi::Ref { index: 0, generation: 2 }; |
|
| 77 | 77 | try! backing::registerDomain(&mut STORE, next); |
|
| 78 | 78 | assert backing::domainLive(&STORE, next); |
|
| 79 | 79 | let mut stale = false; |
|
| 80 | 80 | try backing::endDomainStep(&mut STORE, owner, 64) catch error { |
|
| 81 | 81 | assert error == abi::Error::BadHandle; set stale = true; |
|
| 82 | 82 | }; |
|
| 83 | 83 | assert stale and backing::domainLive(&STORE, next); |
|
| 84 | 84 | try! backing::releasePage(&mut STORE, objects[2]); |
|
| 85 | - | assert STORE.pool.free[2]; |
|
| 85 | + | assert frames::available(&STORE.pool, 2); |
|
| 86 | 86 | while not try! backing::endDomainStep(&mut STORE, receiver, 64) { |
|
| 87 | 87 | } |
|
| 88 | - | assert STORE.pool.retiredCount == 1 and not STORE.pool.free[1]; |
|
| 89 | - | assert frames::reclaim(&mut STORE.pool, 1) == 1 and STORE.pool.free[1]; |
|
| 88 | + | assert STORE.pool.retiredCount == 1 and not frames::available(&STORE.pool, 1); |
|
| 89 | + | assert frames::reclaim(&mut STORE.pool, 1) == 1 and frames::available(&STORE.pool, 1); |
|
| 90 | 90 | } |
|
| 91 | 91 | ||
| 92 | 92 | /// Dropping all page handles preserves capacity while an exposed domain lives. |
|
| 93 | 93 | @test unsafe fn exposures() throws (testing::TestError) { |
|
| 94 | 94 | initialize(); |
| 97 | 97 | try! backing::expose(&mut STORE, object, receiver); |
|
| 98 | 98 | try! backing::expose(&mut STORE, object, receiver); |
|
| 99 | 99 | try testing::expect(STORE.records[object.index].exposed == 2); |
|
| 100 | 100 | try! backing::releasePage(&mut STORE, object); |
|
| 101 | 101 | try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 }); |
|
| 102 | - | try testing::expect(not STORE.pool.free[0] and slots::matches(&STORE.slots[..], object, slots::State::Live)); |
|
| 102 | + | assert not frames::available(&STORE.pool, 0) and slots::matches(&STORE.slots[..], object, slots::State::Live); |
|
| 103 | 103 | try! backing::endDomain(&mut STORE, receiver); |
|
| 104 | 104 | for i in 0..4 { |
|
| 105 | - | try testing::expect(STORE.pool.free[i]); |
|
| 105 | + | assert frames::available(&STORE.pool, i); |
|
| 106 | 106 | } |
|
| 107 | 107 | try testing::expect(not slots::matches(&STORE.slots[..], object, slots::State::Live)); |
|
| 108 | 108 | } |
|
| 109 | 109 | ||
| 110 | 110 | /// Splits keep their common backing alive until every page object is gone. |
| 112 | 112 | initialize(); |
|
| 113 | 113 | let object = allocate(); |
|
| 114 | 114 | try! backing::retainPage(&mut STORE, object); |
|
| 115 | 115 | try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 }); |
|
| 116 | 116 | try! backing::releasePage(&mut STORE, object); |
|
| 117 | - | try testing::expect(not STORE.pool.free[0] and STORE.records[object.index].pages == 1); |
|
| 117 | + | assert not frames::available(&STORE.pool, 0) and STORE.records[object.index].pages == 1; |
|
| 118 | 118 | try! backing::releasePage(&mut STORE, object); |
|
| 119 | - | try testing::expect(STORE.pool.free[0]); |
|
| 119 | + | assert frames::available(&STORE.pool, 0); |
|
| 120 | 120 | let mut stale = false; |
|
| 121 | 121 | try backing::releasePage(&mut STORE, object) catch err { |
|
| 122 | 122 | try testing::expect(err == abi::Error::BadHandle); set stale = true; |
|
| 123 | 123 | }; |
|
| 124 | 124 | try testing::expect(stale); |
| 137 | 137 | try testing::expect(err == abi::Error::BadHandle); set stale = true; |
|
| 138 | 138 | }; |
|
| 139 | 139 | try testing::expect(stale and STORE.records[object.index].exposed == 1); |
|
| 140 | 140 | try! backing::releasePage(&mut STORE, object); |
|
| 141 | 141 | try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 }); |
|
| 142 | - | try testing::expect(STORE.pool.free[0]); |
|
| 142 | + | assert frames::available(&STORE.pool, 0); |
|
| 143 | 143 | } |
|
| 144 | 144 | ||
| 145 | 145 | /// Retained backing metadata cannot create new page authority after the last page ends. |
|
| 146 | 146 | @test unsafe fn noResurrection() throws (testing::TestError) { |
|
| 147 | 147 | initialize(); |
| 154 | 154 | try backing::expose(&mut STORE, object, abi::Ref { index: 1, generation: 1 }) catch err { |
|
| 155 | 155 | try testing::expect(err == abi::Error::BadHandle); set failures += 1; |
|
| 156 | 156 | }; |
|
| 157 | 157 | try testing::expect(failures == 2 and STORE.records[object.index].exposed == 1); |
|
| 158 | 158 | try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 }); |
|
| 159 | - | try testing::expect(STORE.pool.free[0]); |
|
| 159 | + | assert frames::available(&STORE.pool, 0); |
|
| 160 | 160 | } |
kernel/kernel/tests/calls.rad
+2 -1
| 1 | 1 | //! Direct-call scalar results, checked record copies, and architectural replies. |
|
| 2 | 2 | ||
| 3 | 3 | use std::testing; |
|
| 4 | 4 | use kernel::abi; |
|
| 5 | + | use kernel::frames; |
|
| 5 | 6 | use kernel::calls; |
|
| 6 | 7 | use kernel::domains; |
|
| 7 | 8 | use kernel::pages; |
|
| 8 | 9 | use kernel::backing; |
|
| 9 | 10 | use kernel::capability; |
| 302 | 303 | let aligned = (base + 4095) & ~4095; |
|
| 303 | 304 | set OFFSET = ((aligned - base) / 8) as u32; |
|
| 304 | 305 | set pages::STORE.backings.pool.count = 4; |
|
| 305 | 306 | for i in 0..4 { |
|
| 306 | 307 | set pages::STORE.backings.pool.addresses[i] = aligned + i as u64 * 4096; |
|
| 307 | - | set pages::STORE.backings.pool.free[i] = true; |
|
| 308 | + | set pages::STORE.backings.pool.free[i / 64] |= 1 << (i % 64) as u64; |
|
| 308 | 309 | } |
|
| 309 | 310 | let pending = try! slots::reserve(&mut domains::STORE.slots[..]); |
|
| 310 | 311 | let owner = try! slots::commit(&mut domains::STORE.slots[..], pending); |
|
| 311 | 312 | try! backing::registerDomain(&mut pages::STORE.backings, owner); |
|
| 312 | 313 | capability::initialize(&mut MEMORY.table, owner); |
kernel/kernel/tests/domains.rad
+15 -15
| 49 | 49 | let base = pointer as u64; |
|
| 50 | 50 | let aligned = (base + 4095) & ~4095; |
|
| 51 | 51 | set BACKINGS.pool.count = (@sizeOf([u64; 131072]) - (aligned - base) as u32) / 4096; |
|
| 52 | 52 | for i in 0..BACKINGS.pool.count { |
|
| 53 | 53 | set BACKINGS.pool.addresses[i] = aligned + i as u64 * 4096; |
|
| 54 | - | set BACKINGS.pool.free[i] = true; |
|
| 54 | + | set BACKINGS.pool.free[i / 64] |= 1 << (i % 64) as u64; |
|
| 55 | 55 | } |
|
| 56 | 56 | let pending = try! slots::reserve(&mut DOMAINS.slots[..]); |
|
| 57 | 57 | let owner = try! slots::commit(&mut DOMAINS.slots[..], pending); |
|
| 58 | 58 | set DOMAINS.records[owner.index].state = domains::Lifecycle::Active; |
|
| 59 | 59 | set DOMAINS.records[owner.index].parent = nil; |
| 102 | 102 | set DOMAINS.records[permit.object.index].state = domains::Lifecycle::Dead; |
|
| 103 | 103 | set DOMAINS.contexts[caller.initial.index].state = domains::ContextState::Stopped; |
|
| 104 | 104 | set DOMAINS.contexts[caller.initial.index].hart = 1; |
|
| 105 | 105 | lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut loader::STATE, &mut registry::STORE); |
|
| 106 | 106 | assert DOMAINS.contexts[caller.initial.index].reservation; |
|
| 107 | - | assert not PAGES.backings.pool.free[creation.storage.allocation.first]; |
|
| 107 | + | assert not frames::available(&PAGES.backings.pool, creation.storage.allocation.first); |
|
| 108 | 108 | match lease { |
|
| 109 | 109 | case lifecycle::Lease::Held(context) => { |
|
| 110 | 110 | assert context == caller.initial; |
|
| 111 | 111 | }, |
|
| 112 | 112 | } |
| 121 | 121 | let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256); |
|
| 122 | 122 | assert reclaimed > 0 and reclaimed <= 256; |
|
| 123 | 123 | } |
|
| 124 | 124 | for run in [creation.storage.allocation, creation.storage.kernelFrames, creation.storage.graph.frames] { |
|
| 125 | 125 | for i in run.first..run.first + run.count { |
|
| 126 | - | assert PAGES.backings.pool.free[i]; |
|
| 126 | + | assert frames::available(&PAGES.backings.pool, i); |
|
| 127 | 127 | } |
|
| 128 | 128 | } |
|
| 129 | 129 | set DOMAINS.records[permit.object.index].state = domains::Lifecycle::Active; |
|
| 130 | 130 | let next = try! domains::reserve(&mut DOMAINS, &mut PAGES.backings, &PACKAGES, &mut caller.memory.table, authority, image); |
|
| 131 | 131 | let active = lifecycle::holdDomain(&mut CALLS, &mut DOMAINS, caller.initial, next); |
| 277 | 277 | assert not lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS); |
|
| 278 | 278 | while PAGES.backings.pool.retiredCount > 0 { |
|
| 279 | 279 | let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256); |
|
| 280 | 280 | assert reclaimed > 0 and reclaimed <= 256; |
|
| 281 | 281 | } |
|
| 282 | - | assert not PAGES.backings.pool.free[run.first]; |
|
| 282 | + | assert not frames::available(&PAGES.backings.pool, run.first); |
|
| 283 | 283 | assert PAGES.backings.records[allocation.index].exposed == 1; |
|
| 284 | 284 | assert (try! pages::get(&PAGES, (try! capability::get(&parent.memory.table, shared)).object)).count == 1; |
|
| 285 | 285 | assert try! events::flush(&mut DOMAINS.events, receiver, &mut parent.memory.ring) == 1; |
|
| 286 | 286 | let event = try! events::pop(&mut parent.memory.ring) else panic "missing reaped child event"; |
|
| 287 | 287 | assert event.value == abi::id(target) and event.code == 19; |
| 299 | 299 | while PAGES.backings.pool.retiredCount > 0 { |
|
| 300 | 300 | let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256); |
|
| 301 | 301 | assert reclaimed > 0 and reclaimed <= 256; |
|
| 302 | 302 | } |
|
| 303 | 303 | for i in 0..PAGES.backings.pool.count { |
|
| 304 | - | assert PAGES.backings.pool.free[i]; |
|
| 304 | + | assert frames::available(&PAGES.backings.pool, i); |
|
| 305 | 305 | } |
|
| 306 | 306 | } |
|
| 307 | 307 | ||
| 308 | 308 | /// Initial return reports to the current parent and retains its terminal identity. |
|
| 309 | 309 | @test unsafe fn terminalParent() throws (testing::TestError) { |
| 496 | 496 | assert overlap; |
|
| 497 | 497 | let reservedFrames = DOMAINS.contexts[index].kernelFrames; |
|
| 498 | 498 | domains::contextCancel(&mut DOMAINS, &mut PAGES, reserved); |
|
| 499 | 499 | assert DOMAINS.contextSlots[index].state == slots::State::Free; |
|
| 500 | 500 | for i in reservedFrames.first..reservedFrames.first + reservedFrames.count { |
|
| 501 | - | assert PAGES.backings.pool.free[i]; |
|
| 501 | + | assert frames::available(&PAGES.backings.pool, i); |
|
| 502 | 502 | } |
|
| 503 | 503 | let pendingContext = try! domains::contextReserve(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start); |
|
| 504 | 504 | let stackBase = DOMAINS.contexts[index].kernelStack.start; |
|
| 505 | 505 | let base = (&RAM[0]) as u64; |
|
| 506 | 506 | let firstWord = ((stackBase - base) / 8) as u32; |
| 580 | 580 | try domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context) catch error { |
|
| 581 | 581 | assert error == abi::Error::Busy; set retained = true; |
|
| 582 | 582 | }; |
|
| 583 | 583 | assert retained and DOMAINS.contextSlots[context.index].state == slots::State::Live; |
|
| 584 | 584 | for i in created.kernelFrames.first..created.kernelFrames.first + created.kernelFrames.count { |
|
| 585 | - | assert not PAGES.backings.pool.free[i]; |
|
| 585 | + | assert not frames::available(&PAGES.backings.pool, i); |
|
| 586 | 586 | } |
|
| 587 | 587 | set DOMAINS.contexts[context.index].pending = nil; |
|
| 588 | 588 | set DOMAINS.contexts[context.index].reservation = true; |
|
| 589 | 589 | set retained = false; |
|
| 590 | 590 | try domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context) catch error { |
| 592 | 592 | }; |
|
| 593 | 593 | assert retained and DOMAINS.contextSlots[context.index].state == slots::State::Live; |
|
| 594 | 594 | set DOMAINS.contexts[context.index].reservation = false; |
|
| 595 | 595 | try! domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context); |
|
| 596 | 596 | for i in created.kernelFrames.first..created.kernelFrames.first + created.kernelFrames.count { |
|
| 597 | - | try testing::expect(PAGES.backings.pool.free[i]); |
|
| 597 | + | assert frames::available(&PAGES.backings.pool, i); |
|
| 598 | 598 | } |
|
| 599 | 599 | try domains::context(&DOMAINS, authority.object, context) catch error { |
|
| 600 | 600 | try testing::expect(error == abi::Error::BadHandle); set rejected += 1; |
|
| 601 | 601 | }; |
|
| 602 | 602 | let next = try! domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start); |
| 673 | 673 | domains::cancel(&mut DOMAINS, &mut BACKINGS, &mut TABLE, cancelled); |
|
| 674 | 674 | assert DOMAINS.slots[1].state == slots::State::Free; |
|
| 675 | 675 | assert DOMAINS.contextSlots[0].state == slots::State::Free; |
|
| 676 | 676 | assert TABLE.slots[2].state == slots::State::Free; |
|
| 677 | 677 | for i in 0..BACKINGS.pool.count { |
|
| 678 | - | assert BACKINGS.pool.free[i]; |
|
| 678 | + | assert frames::available(&BACKINGS.pool, i); |
|
| 679 | 679 | } |
|
| 680 | 680 | set PACKAGES.entries[0].package.relocations = &[shared::Relocation { |
|
| 681 | 681 | offset: 0, count: 1, target: shared::DataRef { slot: 5, offset: 0 }, |
|
| 682 | 682 | }]; |
|
| 683 | 683 | let mut invalid = false; |
|
| 684 | 684 | try domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE) catch error { |
|
| 685 | 685 | assert error == abi::Error::VerifyFailed; set invalid = true; |
|
| 686 | 686 | }; |
|
| 687 | 687 | assert invalid and DOMAINS.slots[1].state == slots::State::Free; |
|
| 688 | 688 | for i in 0..BACKINGS.pool.count { |
|
| 689 | - | assert BACKINGS.pool.free[i]; |
|
| 689 | + | assert frames::available(&BACKINGS.pool, i); |
|
| 690 | 690 | } |
|
| 691 | 691 | initialize(abi::CREATE); |
|
| 692 | 692 | let abandoned = try! domains::reserve(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 693 | 693 | try! domains::prepare(&PACKAGES, &abandoned); |
|
| 694 | 694 | try! backing::endDomain(&mut BACKINGS, TABLE.owner); |
| 697 | 697 | assert error == abi::Error::BadHandle; set dead = true; |
|
| 698 | 698 | }; |
|
| 699 | 699 | assert dead and DOMAINS.slots[1].state == slots::State::Free; |
|
| 700 | 700 | assert TABLE.slots[2].state == slots::State::Free; |
|
| 701 | 701 | for i in 0..BACKINGS.pool.count { |
|
| 702 | - | assert BACKINGS.pool.free[i]; |
|
| 702 | + | assert frames::available(&BACKINGS.pool, i); |
|
| 703 | 703 | } |
|
| 704 | 704 | } |
|
| 705 | 705 | ||
| 706 | 706 | /// Creation installs only Events in the child and preserves selected authorizing rights. |
|
| 707 | 707 | @test unsafe fn pending() throws (testing::TestError) { |
| 734 | 734 | try domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE) catch err { |
|
| 735 | 735 | try testing::expect(err == expected); set rejected = true; |
|
| 736 | 736 | }; |
|
| 737 | 737 | try testing::expect(rejected); |
|
| 738 | 738 | for i in 0..BACKINGS.pool.count { |
|
| 739 | - | try testing::expect(BACKINGS.pool.free[i]); |
|
| 739 | + | assert frames::available(&BACKINGS.pool, i); |
|
| 740 | 740 | } |
|
| 741 | 741 | try testing::expect(DOMAINS.slots[1].state == slots::State::Free); |
|
| 742 | 742 | try testing::expect(DOMAINS.contextSlots[0].state == slots::State::Free); |
|
| 743 | 743 | try testing::expect(TABLE.slots[2].state == slots::State::Free); |
|
| 744 | 744 | try testing::expect(BACKINGS.domains[1] == 0); |
| 775 | 775 | try testing::expect(err == abi::Error::Exhausted); set rejected = true; |
|
| 776 | 776 | }; |
|
| 777 | 777 | try testing::expect(rejected and DOMAINS.slots[1].state == slots::State::Free); |
|
| 778 | 778 | try testing::expect(TABLE.slots[2].state == slots::State::Free); |
|
| 779 | 779 | for i in 0..BACKINGS.pool.count { |
|
| 780 | - | try testing::expect(BACKINGS.pool.free[i]); |
|
| 780 | + | assert frames::available(&BACKINGS.pool, i); |
|
| 781 | 781 | } |
|
| 782 | 782 | } |
|
| 783 | 783 | ||
| 784 | 784 | /// A creator can grant Create without Allocate and the next creation preserves that choice. |
|
| 785 | 785 | @test unsafe fn selectiveAuthority() throws (testing::TestError) { |
| 831 | 831 | try domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE) catch err { |
|
| 832 | 832 | try testing::expect(err == abi::Error::Exhausted); set failures += 1; |
|
| 833 | 833 | }; |
|
| 834 | 834 | try testing::expect(DOMAINS.slots[1].state == slots::State::Free); |
|
| 835 | 835 | for i in 0..BACKINGS.pool.count { |
|
| 836 | - | try testing::expect(BACKINGS.pool.free[i]); |
|
| 836 | + | assert frames::available(&BACKINGS.pool, i); |
|
| 837 | 837 | } |
|
| 838 | 838 | initialize(abi::CREATE); |
|
| 839 | 839 | for i in 1..DOMAINS.slots.len { |
|
| 840 | 840 | set DOMAINS.slots[i].state = slots::State::Retired; |
|
| 841 | 841 | } |
| 843 | 843 | try testing::expect(err == abi::Error::Exhausted); set failures += 1; |
|
| 844 | 844 | }; |
|
| 845 | 845 | try testing::expect(failures == 2 and TABLE.slots[2].state == slots::State::Free); |
|
| 846 | 846 | try testing::expect(DOMAINS.contextSlots[0].state == slots::State::Free); |
|
| 847 | 847 | for i in 0..BACKINGS.pool.count { |
|
| 848 | - | try testing::expect(BACKINGS.pool.free[i]); |
|
| 848 | + | assert frames::available(&BACKINGS.pool, i); |
|
| 849 | 849 | } |
|
| 850 | 850 | } |
kernel/kernel/tests/frames.rad
+81 -6
| 18 | 18 | set machine.reservedCount = 1; |
|
| 19 | 19 | set machine.reserved[0] = range::Range { start: 0x80003000, end: 0x80005000 }; |
|
| 20 | 20 | try! frames::initialize(&mut POOL, &machine); |
|
| 21 | 21 | } |
|
| 22 | 22 | ||
| 23 | + | /// Bitmap allocation agrees with a scalar scan across holes and word boundaries. |
|
| 24 | + | @test unsafe fn bitmapRuns() throws (testing::TestError) { |
|
| 25 | + | let mut machine: platform::Platform = undefined; |
|
| 26 | + | set machine.ramCount = 1; |
|
| 27 | + | set machine.ram[0] = range::Range { start: 0x80000000, end: 0x800c2000 }; |
|
| 28 | + | set machine.reservedCount = 1; |
|
| 29 | + | set machine.reserved[0] = range::Range { start: 0x8001f000, end: 0x80020000 }; |
|
| 30 | + | for pattern in 0..5 { |
|
| 31 | + | for count in [1 as u32, 2, 31, 32, 63, 64, 65, 127, 128, 129, 193, 194] { |
|
| 32 | + | try! frames::initialize(&mut POOL, &machine); |
|
| 33 | + | assert POOL.count == 193; |
|
| 34 | + | for i in 0..POOL.count { |
|
| 35 | + | if pattern > 0 and i % (pattern * 32) == 0 { |
|
| 36 | + | set POOL.free[i / 64] &= ~(1 << (i % 64) as u64); |
|
| 37 | + | } |
|
| 38 | + | } |
|
| 39 | + | // Padding bits are outside the pool even when their values are set. |
|
| 40 | + | set POOL.free[3] |= 0xfffffffffffffffe; |
|
| 41 | + | let before = [POOL.free[0], POOL.free[1], POOL.free[2], POOL.free[3]]; |
|
| 42 | + | let mut expected: ?u32 = nil; |
|
| 43 | + | let mut found: u32 = 0; |
|
| 44 | + | for i in 0..POOL.count { |
|
| 45 | + | if not frames::available(&POOL, i) { |
|
| 46 | + | set found = 0; |
|
| 47 | + | continue; |
|
| 48 | + | } |
|
| 49 | + | if found > 0 and POOL.addresses[i] <> POOL.addresses[i - 1] + 4096 { |
|
| 50 | + | set found = 0; |
|
| 51 | + | } |
|
| 52 | + | set found += 1; |
|
| 53 | + | if found == count { |
|
| 54 | + | set expected = i + 1 - count; |
|
| 55 | + | break; |
|
| 56 | + | } |
|
| 57 | + | } |
|
| 58 | + | let reservation = try frames::reserve(&mut POOL, count) catch error { |
|
| 59 | + | assert expected == nil and error == abi::Error::OutOfMemory; |
|
| 60 | + | for i in 0..4 { |
|
| 61 | + | assert POOL.free[i] == before[i]; |
|
| 62 | + | } |
|
| 63 | + | continue; |
|
| 64 | + | }; |
|
| 65 | + | let first = expected else panic "unexpected frame reservation"; |
|
| 66 | + | let run = frames::commit(reservation); |
|
| 67 | + | assert run.first == first and run.count == count; |
|
| 68 | + | for i in 0..POOL.count { |
|
| 69 | + | if i >= first and i - first < count { |
|
| 70 | + | assert not frames::available(&POOL, i); |
|
| 71 | + | } |
|
| 72 | + | else { |
|
| 73 | + | assert frames::available(&POOL, i) == ((before[i / 64] & (1 << (i % 64) as u64)) <> 0); |
|
| 74 | + | } |
|
| 75 | + | } |
|
| 76 | + | try! frames::release(&mut POOL, run); |
|
| 77 | + | for i in 0..4 { |
|
| 78 | + | assert POOL.free[i] == before[i]; |
|
| 79 | + | } |
|
| 80 | + | } |
|
| 81 | + | } |
|
| 82 | + | assert not frames::available(&POOL, POOL.count); |
|
| 83 | + | try! frames::initialize(&mut POOL, &machine); |
|
| 84 | + | let run = frames::commit(try! frames::reserve(&mut POOL, 128)); |
|
| 85 | + | assert run.first == 31; |
|
| 86 | + | let before = [POOL.free[0], POOL.free[1], POOL.free[2], POOL.free[3]]; |
|
| 87 | + | let mut invalid = false; |
|
| 88 | + | try frames::release(&mut POOL, frames::Run { first: run.first, count: 129 }) catch error { |
|
| 89 | + | assert error == abi::Error::BadHandle; set invalid = true; |
|
| 90 | + | }; |
|
| 91 | + | assert invalid; |
|
| 92 | + | for i in 0..4 { |
|
| 93 | + | assert POOL.free[i] == before[i]; |
|
| 94 | + | } |
|
| 95 | + | try! frames::release(&mut POOL, run); |
|
| 96 | + | } |
|
| 97 | + | ||
| 23 | 98 | /// Retired frames stay unavailable until bounded reclamation returns them. |
|
| 24 | 99 | @test unsafe fn retirement() throws (testing::TestError) { |
|
| 25 | 100 | initialize(); |
|
| 26 | 101 | set POOL.retiredHead = limits::FRAMES - 1; |
|
| 27 | 102 | let left = frames::commit(try! frames::reserve(&mut POOL, 3)); |
|
| 28 | 103 | let right = frames::commit(try! frames::reserve(&mut POOL, 5)); |
|
| 29 | 104 | try! frames::retire(&mut POOL, left); |
|
| 30 | 105 | try! frames::retire(&mut POOL, right); |
|
| 31 | 106 | assert POOL.retiredCount == 2; |
|
| 32 | 107 | for i in 0..8 { |
|
| 33 | - | assert not POOL.free[i]; |
|
| 108 | + | assert not frames::available(&POOL, i); |
|
| 34 | 109 | } |
|
| 35 | 110 | assert frames::reclaim(&mut POOL, 0) == 0; |
|
| 36 | 111 | assert frames::reclaim(&mut POOL, 2) == 2; |
|
| 37 | - | assert POOL.free[0] and POOL.free[1] and not POOL.free[2]; |
|
| 112 | + | assert frames::available(&POOL, 0) and frames::available(&POOL, 1) and not frames::available(&POOL, 2); |
|
| 38 | 113 | let reused = frames::commit(try! frames::reserve(&mut POOL, 2)); |
|
| 39 | 114 | assert reused.first == 0; |
|
| 40 | 115 | assert frames::reclaim(&mut POOL, 3) == 3; |
|
| 41 | - | assert POOL.retiredCount == 1 and not POOL.free[5]; |
|
| 42 | - | assert not POOL.free[0] and not POOL.free[1]; |
|
| 116 | + | assert POOL.retiredCount == 1 and not frames::available(&POOL, 5); |
|
| 117 | + | assert not frames::available(&POOL, 0) and not frames::available(&POOL, 1); |
|
| 43 | 118 | try! frames::retire(&mut POOL, reused); |
|
| 44 | 119 | assert frames::reclaim(&mut POOL, 20) == 5; |
|
| 45 | 120 | assert POOL.retiredCount == 0; |
|
| 46 | 121 | for i in 0..8 { |
|
| 47 | - | assert POOL.free[i]; |
|
| 122 | + | assert frames::available(&POOL, i); |
|
| 48 | 123 | } |
|
| 49 | 124 | assert frames::reclaim(&mut POOL, 20) == 0; |
|
| 50 | 125 | } |
|
| 51 | 126 | ||
| 52 | 127 | /// Adjacent physical frames form runs; a reserved hole cannot be crossed. |
| 193 | 268 | try! frames::initialize(&mut POOL, &machine); |
|
| 194 | 269 | let mut count: u32 = 0; |
|
| 195 | 270 | for i in 0..96 { |
|
| 196 | 271 | let start = 0x80000000 + i as u64 * 4096; |
|
| 197 | 272 | if platform::available(&machine, range::Range { start, end: start + 4096 }) { |
|
| 198 | - | try testing::expect(count < POOL.count and POOL.addresses[count] == start and POOL.free[count]); |
|
| 273 | + | assert count < POOL.count and POOL.addresses[count] == start and frames::available(&POOL, count); |
|
| 199 | 274 | set count += 1; |
|
| 200 | 275 | } |
|
| 201 | 276 | } |
|
| 202 | 277 | try testing::expect(count == POOL.count); |
|
| 203 | 278 | } |
kernel/kernel/tests/instances.rad
+4 -4
| 21 | 21 | let base = pointer as u64; |
|
| 22 | 22 | let aligned = (base + 4095) & ~4095; |
|
| 23 | 23 | set POOL.count = (@sizeOf([u64; 131072]) - (aligned - base) as u32) / 4096; |
|
| 24 | 24 | for i in 0..POOL.count { |
|
| 25 | 25 | set POOL.addresses[i] = aligned + i as u64 * 4096; |
|
| 26 | - | set POOL.free[i] = true; |
|
| 26 | + | set POOL.free[i / 64] |= 1 << (i % 64) as u64; |
|
| 27 | 27 | } |
|
| 28 | 28 | registry::initialize(&mut PACKAGES); |
|
| 29 | 29 | } |
|
| 30 | 30 | ||
| 31 | 31 | /// Publish a package with one private word and optional pointer relocation. |
| 67 | 67 | try! frames::release(&mut POOL, first.frames); |
|
| 68 | 68 | try! frames::release(&mut POOL, other.frames); |
|
| 69 | 69 | let cancelled = try! instances::reserve(&PACKAGES, &mut POOL, root); |
|
| 70 | 70 | instances::cancel(&mut POOL, cancelled); |
|
| 71 | 71 | for i in 0..POOL.count { |
|
| 72 | - | assert POOL.free[i]; |
|
| 72 | + | assert frames::available(&POOL, i); |
|
| 73 | 73 | } |
|
| 74 | 74 | } |
|
| 75 | 75 | ||
| 76 | 76 | /// A diamond has one dependency instance per domain and independent relocated pointers. |
|
| 77 | 77 | @test unsafe fn diamond() throws (testing::TestError) { |
| 93 | 93 | try testing::expect(*word(second.table[base.index]) == 7); |
|
| 94 | 94 | try testing::expect(first.table[4] == 0 and second.table[4] == 0); |
|
| 95 | 95 | try! frames::release(&mut POOL, first.frames); |
|
| 96 | 96 | try! frames::release(&mut POOL, second.frames); |
|
| 97 | 97 | for i in 0..POOL.count { |
|
| 98 | - | try testing::expect(POOL.free[i]); |
|
| 98 | + | assert frames::available(&POOL, i); |
|
| 99 | 99 | } |
|
| 100 | 100 | } |
|
| 101 | 101 | ||
| 102 | 102 | /// State alignment can exceed a physical frame and zero-fill follows the template. |
|
| 103 | 103 | @test unsafe fn alignment() throws (testing::TestError) { |
| 130 | 130 | try instances::create(&PACKAGES, &mut POOL, abi::Ref { index: root.index, generation: 2 }) catch err { |
|
| 131 | 131 | try testing::expect(err == abi::Error::BadHandle); set failures += 1; |
|
| 132 | 132 | }; |
|
| 133 | 133 | try testing::expect(failures == 3); |
|
| 134 | 134 | for i in 0..POOL.count { |
|
| 135 | - | try testing::expect(POOL.free[i]); |
|
| 135 | + | assert frames::available(&POOL, i); |
|
| 136 | 136 | } |
|
| 137 | 137 | let retry = try! instances::create(&PACKAGES, &mut POOL, root); |
|
| 138 | 138 | try testing::expect(*word(retry.table[root.index]) == 7); |
|
| 139 | 139 | try! frames::release(&mut POOL, retry.frames); |
|
| 140 | 140 | } |
kernel/kernel/tests/lifecycle.rad
+11 -11
| 80 | 80 | }); |
|
| 81 | 81 | let base = ((&RAM[0]) as u64 + 4095) & ~4095; |
|
| 82 | 82 | set MEMORY.backings.pool.count = 2; |
|
| 83 | 83 | for i in 0..2 { |
|
| 84 | 84 | set MEMORY.backings.pool.addresses[i] = base + i as u64 * 4096; |
|
| 85 | - | set MEMORY.backings.pool.free[i] = true; |
|
| 85 | + | set MEMORY.backings.pool.free[i / 64] |= 1 << (i % 64) as u64; |
|
| 86 | 86 | } |
|
| 87 | 87 | let reservation = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 2); |
|
| 88 | 88 | let held = lifecycle::holdPage(&mut CALLS, &mut STORE, owner, reservation); |
|
| 89 | 89 | assert STORE.contexts[0].reservation; |
|
| 90 | 90 | lifecycle::clearPage(&CALLS, &held); |
|
| 91 | 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]; |
|
| 92 | + | assert not frames::available(&MEMORY.backings.pool, 0) and not frames::available(&MEMORY.backings.pool, 1); |
|
| 93 | 93 | set STORE.records[0].state = domains::Lifecycle::Dead; |
|
| 94 | 94 | set STORE.contexts[0].state = domains::ContextState::Stopped; |
|
| 95 | 95 | set STORE.contexts[0].hart = 0; |
|
| 96 | 96 | lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 97 | - | assert not MEMORY.backings.pool.free[0]; |
|
| 97 | + | assert not frames::available(&MEMORY.backings.pool, 0); |
|
| 98 | 98 | // Model the end of the stopped continuation before hart release. |
|
| 99 | 99 | match held { |
|
| 100 | 100 | case lifecycle::Lease::Held(context) => { |
|
| 101 | 101 | assert context == owner; |
|
| 102 | 102 | }, |
|
| 103 | 103 | } |
|
| 104 | 104 | set STORE.contexts[0].hart = nil; |
|
| 105 | 105 | lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 106 | - | assert MEMORY.backings.pool.retiredCount == 1 and not MEMORY.backings.pool.free[0]; |
|
| 106 | + | assert MEMORY.backings.pool.retiredCount == 1 and not frames::available(&MEMORY.backings.pool, 0); |
|
| 107 | 107 | assert frames::reclaim(&mut MEMORY.backings.pool, 256) == 2; |
|
| 108 | - | assert MEMORY.backings.pool.free[0] and MEMORY.backings.pool.free[1]; |
|
| 108 | + | assert frames::available(&MEMORY.backings.pool, 0) and frames::available(&MEMORY.backings.pool, 1); |
|
| 109 | 109 | assert MEMORY.slots[0].state == slots::State::Free; |
|
| 110 | 110 | assert MEMORY.backings.slots[0].state == slots::State::Free; |
|
| 111 | 111 | assert FIRST.table.slots[1].state == slots::State::Free and CALLS.active[0] == 0; |
|
| 112 | 112 | assert not STORE.contexts[0].reservation; |
|
| 113 | 113 | set STORE.records[0].state = domains::Lifecycle::Active; |
| 137 | 137 | }); |
|
| 138 | 138 | let base = ((&RAM[0]) as u64 + 4095) & ~4095; |
|
| 139 | 139 | set MEMORY.backings.pool.count = 2; |
|
| 140 | 140 | for i in 0..2 { |
|
| 141 | 141 | set MEMORY.backings.pool.addresses[i] = base + i as u64 * 4096; |
|
| 142 | - | set MEMORY.backings.pool.free[i] = true; |
|
| 142 | + | set MEMORY.backings.pool.free[i / 64] |= 1 << (i % 64) as u64; |
|
| 143 | 143 | } |
|
| 144 | 144 | let firstPage = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 1); |
|
| 145 | 145 | let first = lifecycle::holdPage(&mut CALLS, &mut STORE, owner, firstPage); |
|
| 146 | 146 | let secondPage = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 1); |
|
| 147 | 147 | let other = lifecycle::holdPage(&mut CALLS, &mut STORE, second, secondPage); |
| 162 | 162 | assert CALLS.active[0] == 2 and MEMORY.backings.pool.retiredCount == 1; |
|
| 163 | 163 | assert not STORE.contexts[0].reservation and STORE.contexts[1].reservation; |
|
| 164 | 164 | assert lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 165 | 165 | assert CALLS.active[0] == 0 and MEMORY.backings.pool.retiredCount == 2; |
|
| 166 | 166 | assert not lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 167 | - | assert not MEMORY.backings.pool.free[0] and not MEMORY.backings.pool.free[1]; |
|
| 167 | + | assert not frames::available(&MEMORY.backings.pool, 0) and not frames::available(&MEMORY.backings.pool, 1); |
|
| 168 | 168 | assert frames::reclaim(&mut MEMORY.backings.pool, 1) == 1; |
|
| 169 | 169 | assert frames::reclaim(&mut MEMORY.backings.pool, 1) == 1; |
|
| 170 | 170 | assert MEMORY.backings.pool.retiredCount == 0; |
|
| 171 | 171 | } |
|
| 172 | 172 |
| 178 | 178 | let caller = abi::Ref { index: 0, generation: 1 }; |
|
| 179 | 179 | let target = abi::Ref { index: 1, generation: 1 }; |
|
| 180 | 180 | let base = ((&RAM[0]) as u64 + 4095) & ~4095; |
|
| 181 | 181 | set MEMORY.backings.pool.count = 1; |
|
| 182 | 182 | set MEMORY.backings.pool.addresses[0] = base; |
|
| 183 | - | set MEMORY.backings.pool.free[0] = true; |
|
| 183 | + | set MEMORY.backings.pool.free[0] |= 1; |
|
| 184 | 184 | let slot = try! slots::reserve(&mut STORE.contextSlots[..]); |
|
| 185 | 185 | let object = slots::reference(&slot); |
|
| 186 | 186 | let run = frames::commit(try! frames::reserve(&mut MEMORY.backings.pool, 1)); |
|
| 187 | 187 | set STORE.contexts[object.index].owner = target; |
|
| 188 | 188 | set STORE.contexts[object.index].kernelFrames = run; |
| 197 | 197 | }); |
|
| 198 | 198 | let lease = lifecycle::holdContext(&mut CALLS, &mut STORE, caller, pending); |
|
| 199 | 199 | lifecycle::clearContext(&CALLS, &lease); |
|
| 200 | 200 | set STORE.records[1].state = domains::Lifecycle::Dead; |
|
| 201 | 201 | lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 202 | - | assert STORE.contexts[0].reservation and not MEMORY.backings.pool.free[0]; |
|
| 202 | + | assert STORE.contexts[0].reservation and not frames::available(&MEMORY.backings.pool, 0); |
|
| 203 | 203 | set STORE.records[0].state = domains::Lifecycle::Dead; |
|
| 204 | 204 | set STORE.contexts[0].state = domains::ContextState::Stopped; |
|
| 205 | 205 | set STORE.contexts[0].hart = 1; |
|
| 206 | 206 | lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 207 | 207 | assert STORE.contextSlots[object.index].state == slots::State::Reserved; |
| 210 | 210 | assert id == caller; |
|
| 211 | 211 | }, |
|
| 212 | 212 | } |
|
| 213 | 213 | set STORE.contexts[0].hart = nil; |
|
| 214 | 214 | lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 215 | - | assert MEMORY.backings.pool.retiredCount == 1 and not MEMORY.backings.pool.free[0]; |
|
| 215 | + | assert MEMORY.backings.pool.retiredCount == 1 and not frames::available(&MEMORY.backings.pool, 0); |
|
| 216 | 216 | assert frames::reclaim(&mut MEMORY.backings.pool, 256) == 1; |
|
| 217 | - | assert MEMORY.backings.pool.free[0]; |
|
| 217 | + | assert frames::available(&MEMORY.backings.pool, 0); |
|
| 218 | 218 | assert STORE.contextSlots[object.index].state == slots::State::Free; |
|
| 219 | 219 | assert not STORE.contexts[0].reservation and CALLS.active[0] == 0; |
|
| 220 | 220 | } |
|
| 221 | 221 | ||
| 222 | 222 | /// Dead contexts retain requests until hart release and completion of taken work. |
kernel/kernel/tests/loader.rad
+2 -2
| 50 | 50 | let aligned = (base + 4095) & ~4095; |
|
| 51 | 51 | set OFFSET = (aligned - base) as u32; |
|
| 52 | 52 | set PAGES.backings.pool.count = (@sizeOf([u64; 18874368]) - OFFSET) / 4096; |
|
| 53 | 53 | for i in 0..PAGES.backings.pool.count { |
|
| 54 | 54 | set PAGES.backings.pool.addresses[i] = aligned + i as u64 * 4096; |
|
| 55 | - | set PAGES.backings.pool.free[i] = true; |
|
| 55 | + | set PAGES.backings.pool.free[i / 64] |= 1 << (i % 64) as u64; |
|
| 56 | 56 | } |
|
| 57 | 57 | let owner = abi::Ref { index: 0, generation: 1 }; |
|
| 58 | 58 | capability::initialize(&mut TABLE, owner); |
|
| 59 | 59 | try! backing::registerDomain(&mut PAGES.backings, owner); |
|
| 60 | 60 | let authority = try! capability::install(&mut TABLE, capability::Entry { |
| 91 | 91 | ||
| 92 | 92 | /// Count available frames to detect leaked temporary or unpublished output storage. |
|
| 93 | 93 | unsafe fn free() -> u32 { |
|
| 94 | 94 | let mut count: u32 = 0; |
|
| 95 | 95 | for i in 0..PAGES.backings.pool.count { |
|
| 96 | - | if PAGES.backings.pool.free[i] { |
|
| 96 | + | if frames::available(&PAGES.backings.pool, i) { |
|
| 97 | 97 | set count += 1; |
|
| 98 | 98 | } |
|
| 99 | 99 | } |
|
| 100 | 100 | return count; |
|
| 101 | 101 | } |
kernel/kernel/tests/pages.rad
+16 -15
| 1 | 1 | //! Page allocation, zeroing, split atomicity, and retained-pointer lifetimes. |
|
| 2 | 2 | ||
| 3 | 3 | use std::testing; |
|
| 4 | 4 | use kernel::abi; |
|
| 5 | + | use kernel::frames; |
|
| 5 | 6 | use kernel::capability; |
|
| 6 | 7 | use kernel::pages; |
|
| 7 | 8 | use kernel::backing; |
|
| 8 | 9 | use kernel::slots; |
|
| 9 | 10 | use kernel::limits; |
| 31 | 32 | slots::initialize(&mut STORE.slots[..]); |
|
| 32 | 33 | backing::initialize(&mut STORE.backings); |
|
| 33 | 34 | set STORE.backings.pool.count = 8; |
|
| 34 | 35 | for i in 0..8 { |
|
| 35 | 36 | set STORE.backings.pool.addresses[i] = aligned + i as u64 * 4096; |
|
| 36 | - | set STORE.backings.pool.free[i] = true; |
|
| 37 | + | set STORE.backings.pool.free[i / 64] |= 1 << (i % 64) as u64; |
|
| 37 | 38 | } |
|
| 38 | 39 | let owner = abi::Ref { index: 0, generation: 1 }; |
|
| 39 | 40 | capability::initialize(&mut TABLE, owner); |
|
| 40 | 41 | try! backing::registerDomain(&mut STORE.backings, owner); |
|
| 41 | 42 | let authority = try! capability::install(&mut TABLE, capability::Entry { |
| 52 | 53 | @test unsafe fn stagedAllocation() throws (testing::TestError) { |
|
| 53 | 54 | initialize(); |
|
| 54 | 55 | let pending = try! pages::reserve(&mut STORE, &mut TABLE, abi::Handle(0), 2); |
|
| 55 | 56 | assert STORE.slots[0].state == slots::State::Reserved; |
|
| 56 | 57 | assert TABLE.slots[1].state == slots::State::Reserved; |
|
| 57 | - | assert not STORE.backings.pool.free[0] and not STORE.backings.pool.free[1]; |
|
| 58 | + | assert not frames::available(&STORE.backings.pool, 0) and not frames::available(&STORE.backings.pool, 1); |
|
| 58 | 59 | assert RAM[OFFSET] == 0xa5; |
|
| 59 | 60 | let other = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 6); |
|
| 60 | 61 | set RAM[OFFSET + 8192] = 0x7b; |
|
| 61 | 62 | pages::clear(&pending); |
|
| 62 | 63 | for i in OFFSET..OFFSET + 8192 { |
| 68 | 69 | let page = try! pages::get(&STORE, entry.object); |
|
| 69 | 70 | assert page.count == 2 and page.origin == TABLE.owner; |
|
| 70 | 71 | drop(handle); drop(other); |
|
| 71 | 72 | try! backing::endDomain(&mut STORE.backings, TABLE.owner); |
|
| 72 | 73 | for i in 0..8 { |
|
| 73 | - | assert STORE.backings.pool.free[i]; |
|
| 74 | + | assert frames::available(&STORE.backings.pool, i); |
|
| 74 | 75 | } |
|
| 75 | 76 | } |
|
| 76 | 77 | ||
| 77 | 78 | /// Cancelling unpublished storage returns all capacity without exposing its bytes. |
|
| 78 | 79 | @test unsafe fn cancelAllocation() throws (testing::TestError) { |
| 81 | 82 | pages::cancel(&mut STORE, &mut TABLE, pending); |
|
| 82 | 83 | assert STORE.slots[0].state == slots::State::Free; |
|
| 83 | 84 | assert STORE.backings.slots[0].state == slots::State::Free; |
|
| 84 | 85 | assert TABLE.slots[1].state == slots::State::Free; |
|
| 85 | 86 | for i in 0..8 { |
|
| 86 | - | assert STORE.backings.pool.free[i]; |
|
| 87 | + | assert frames::available(&STORE.backings.pool, i); |
|
| 87 | 88 | } |
|
| 88 | 89 | assert RAM[OFFSET] == 0xa5; |
|
| 89 | 90 | let full = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 8); |
|
| 90 | 91 | let entry = try! capability::get(&TABLE, full); |
|
| 91 | 92 | assert (try! pages::get(&STORE, entry.object)).count == 8; |
| 104 | 105 | try testing::expect(page.handles == 1 and STORE.backings.records[page.backing.index].exposed == 2); |
|
| 105 | 106 | let actual = try! capability::get(&TARGET, moved); |
|
| 106 | 107 | try testing::expect(*actual.rights == abi::READ); |
|
| 107 | 108 | try! pages::drop(&mut STORE, &mut TARGET, moved); |
|
| 108 | 109 | try! backing::endDomain(&mut STORE.backings, receiver); |
|
| 109 | - | try testing::expect(not STORE.backings.pool.free[0]); |
|
| 110 | + | assert not frames::available(&STORE.backings.pool, 0); |
|
| 110 | 111 | try! backing::endDomain(&mut STORE.backings, TABLE.owner); |
|
| 111 | - | try testing::expect(STORE.backings.pool.free[0]); |
|
| 112 | + | assert frames::available(&STORE.backings.pool, 0); |
|
| 112 | 113 | } |
|
| 113 | 114 | ||
| 114 | 115 | /// Grants acquire references only after capacity and receiver checks succeed. |
|
| 115 | 116 | @test unsafe fn grantRollback() throws (testing::TestError) { |
|
| 116 | 117 | initialize(); |
| 152 | 153 | try testing::expect(STORE.records[entry.object.index].handles == 2); |
|
| 153 | 154 | try testing::expect(STORE.backings.records[page.backing.index].exposed == 2); |
|
| 154 | 155 | drop(original); |
|
| 155 | 156 | try! pages::drop(&mut STORE, &mut TARGET, granted); |
|
| 156 | 157 | try! backing::endDomain(&mut STORE.backings, TABLE.owner); |
|
| 157 | - | try testing::expect(not STORE.backings.pool.free[0]); |
|
| 158 | + | assert not frames::available(&STORE.backings.pool, 0); |
|
| 158 | 159 | try! backing::endDomain(&mut STORE.backings, receiver); |
|
| 159 | - | try testing::expect(STORE.backings.pool.free[0]); |
|
| 160 | + | assert frames::available(&STORE.backings.pool, 0); |
|
| 160 | 161 | } |
|
| 161 | 162 | ||
| 162 | 163 | /// Self-attenuation adds one reference, preserves authority, and prevents splitting. |
|
| 163 | 164 | @test unsafe fn selfAttenuation() throws (testing::TestError) { |
|
| 164 | 165 | initialize(); |
| 171 | 172 | try testing::expect(*none.rights == 0); |
|
| 172 | 173 | drop(narrowed); drop(copy); |
|
| 173 | 174 | let right = try! pages::split(&mut STORE, &mut TABLE, original, 1); |
|
| 174 | 175 | drop(original); drop(right); |
|
| 175 | 176 | try! backing::endDomain(&mut STORE.backings, TABLE.owner); |
|
| 176 | - | try testing::expect(STORE.backings.pool.free[0] and STORE.backings.pool.free[1]); |
|
| 177 | + | assert frames::available(&STORE.backings.pool, 0) and frames::available(&STORE.backings.pool, 1); |
|
| 177 | 178 | } |
|
| 178 | 179 | ||
| 179 | 180 | /// New allocations are zeroed completely and carry the documented default rights. |
|
| 180 | 181 | @test unsafe fn allocation() throws (testing::TestError) { |
|
| 181 | 182 | initialize(); |
| 196 | 197 | for count in &[65537 as u64, 0xffffffffffffffff] { |
|
| 197 | 198 | let mut exhausted = false; |
|
| 198 | 199 | try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), count) catch err { |
|
| 199 | 200 | try testing::expect(err == abi::Error::OutOfMemory); set exhausted = true; |
|
| 200 | 201 | }; |
|
| 201 | - | try testing::expect(exhausted and STORE.backings.pool.free[2]); |
|
| 202 | + | assert exhausted and frames::available(&STORE.backings.pool, 2); |
|
| 202 | 203 | } |
|
| 203 | 204 | } |
|
| 204 | 205 | ||
| 205 | 206 | /// Splitting preserves contents and backing identity while changing only page extents. |
|
| 206 | 207 | @test unsafe fn split() throws (testing::TestError) { |
| 242 | 243 | try! pages::retain(&mut STORE, entry.object, receiver); |
|
| 243 | 244 | set RAM[OFFSET] = 99; |
|
| 244 | 245 | drop(handle); |
|
| 245 | 246 | try! pages::release(&mut STORE, entry.object); |
|
| 246 | 247 | try! backing::endDomain(&mut STORE.backings, TABLE.owner); |
|
| 247 | - | try testing::expect(not STORE.backings.pool.free[0] and RAM[OFFSET] == 99); |
|
| 248 | + | assert not frames::available(&STORE.backings.pool, 0) and RAM[OFFSET] == 99; |
|
| 248 | 249 | try! backing::endDomain(&mut STORE.backings, receiver); |
|
| 249 | - | try testing::expect(STORE.backings.pool.free[0]); |
|
| 250 | + | assert frames::available(&STORE.backings.pool, 0); |
|
| 250 | 251 | let owner = abi::Ref { index: 0, generation: 2 }; |
|
| 251 | 252 | capability::initialize(&mut TABLE, owner); |
|
| 252 | 253 | try! backing::registerDomain(&mut STORE.backings, owner); |
|
| 253 | 254 | let authority = try! capability::install(&mut TABLE, capability::Entry { |
|
| 254 | 255 | kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE), |
| 274 | 275 | }; |
|
| 275 | 276 | try pages::split(&mut STORE, &mut TABLE, handle, 2) catch err { |
|
| 276 | 277 | try testing::expect(err == abi::Error::Exhausted); set failed += 1; |
|
| 277 | 278 | }; |
|
| 278 | 279 | let page = try! pages::get(&STORE, entry.object); |
|
| 279 | - | try testing::expect(failed == 2 and page.count == 4 and STORE.backings.pool.free[4]); |
|
| 280 | + | assert failed == 2 and page.count == 4 and frames::available(&STORE.backings.pool, 4); |
|
| 280 | 281 | } |
|
| 281 | 282 | ||
| 282 | 283 | /// Failure at each storage stage releases all earlier reservations. |
|
| 283 | 284 | @test unsafe fn rollback() throws (testing::TestError) { |
|
| 284 | 285 | initialize(); |
| 302 | 303 | } |
|
| 303 | 304 | let mut exhausted = false; |
|
| 304 | 305 | try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 1) catch err { |
|
| 305 | 306 | try testing::expect(err == abi::Error::Exhausted); set exhausted = true; |
|
| 306 | 307 | }; |
|
| 307 | - | try testing::expect(exhausted and TABLE.slots[1].state == slots::State::Free and STORE.backings.pool.free[0]); |
|
| 308 | + | assert exhausted and TABLE.slots[1].state == slots::State::Free and frames::available(&STORE.backings.pool, 0); |
|
| 308 | 309 | if stage == 0 { |
|
| 309 | 310 | try testing::expect(STORE.slots[0].state == slots::State::Free); |
|
| 310 | 311 | } |
|
| 311 | 312 | } |
|
| 312 | 313 | } |
| 319 | 320 | }); |
|
| 320 | 321 | let mut denied = false; |
|
| 321 | 322 | try pages::allocate(&mut STORE, &mut TABLE, other, 1) catch err { |
|
| 322 | 323 | try testing::expect(err == abi::Error::Denied); set denied = true; |
|
| 323 | 324 | }; |
|
| 324 | - | try testing::expect(denied and STORE.backings.pool.free[0] and TABLE.slots[2].state == slots::State::Free); |
|
| 325 | + | assert denied and frames::available(&STORE.backings.pool, 0) and TABLE.slots[2].state == slots::State::Free; |
|
| 325 | 326 | } |
|
| 326 | 327 | ||
| 327 | 328 | /// Buffer authority covers complete ranges and rejects overflow and stale objects. |
|
| 328 | 329 | @test unsafe fn bufferAuthority() throws (testing::TestError) { |
|
| 329 | 330 | initialize(); |
test/runtime/kernel/dispatchcheck.rad
added
+249 -0
| 1 | + | //! Runtime compiler ownership across scheduled windows and domain destruction. |
|
| 2 | + | use std::mem; |
|
| 3 | + | use kernel::abi; |
|
| 4 | + | use kernel::frames; |
|
| 5 | + | use kernel::slots; |
|
| 6 | + | use kernel::backing; |
|
| 7 | + | use kernel::pages; |
|
| 8 | + | use kernel::capability; |
|
| 9 | + | use kernel::registry; |
|
| 10 | + | use kernel::loader; |
|
| 11 | + | use kernel::domains; |
|
| 12 | + | use kernel::budgets; |
|
| 13 | + | use kernel::dispatch; |
|
| 14 | + | use kernel::boot; |
|
| 15 | + | use kernel::sync; |
|
| 16 | + | use kernel::calls; |
|
| 17 | + | use kernel::lifecycle; |
|
| 18 | + | use kernel::remote; |
|
| 19 | + | use kernel::dispatchinput; |
|
| 20 | + | ||
| 21 | + | /// Bootstrap resource capabilities. |
|
| 22 | + | unsafe static TABLE: capability::Table = undefined; |
|
| 23 | + | /// First loader, retry loader, witness, and controller domain handles. |
|
| 24 | + | unsafe static HANDLES: [abi::Handle; 4] = undefined; |
|
| 25 | + | /// Generation-bearing domain identities for each actor. |
|
| 26 | + | unsafe static ACTORS: [abi::Ref; 4] = undefined; |
|
| 27 | + | /// Initial context identities for each actor. |
|
| 28 | + | unsafe static CONTEXTS: [abi::Ref; 4] = undefined; |
|
| 29 | + | /// Controller capability for the first loader. |
|
| 30 | + | unsafe static VICTIM: abi::Handle = undefined; |
|
| 31 | + | /// Controller capability for the binary input page. |
|
| 32 | + | unsafe static SOURCE: abi::Handle = undefined; |
|
| 33 | + | /// Shared user progress and published package identity. |
|
| 34 | + | unsafe static PROGRESS: *mut u64 = undefined; |
|
| 35 | + | /// Start of the controller's final execution window. |
|
| 36 | + | static FINAL: u64 = 0; |
|
| 37 | + | /// Write one diagnostic byte to the fixture UART. |
|
| 38 | + | fn put(byte: u8); |
|
| 39 | + | /// Write a fixed-width hexadecimal diagnostic value. |
|
| 40 | + | fn number(value: u64) { |
|
| 41 | + | let digits = "0123456789abcdef"; |
|
| 42 | + | for i in 0..16 { |
|
| 43 | + | put(digits[((value >> ((15 - i) as u64 * 4)) & 15) as u32]); |
|
| 44 | + | } |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | /// Map a validated physical page. |
|
| 48 | + | fn memory(address: u64) -> *mut u8; |
|
| 49 | + | /// Current kernel package-state base. |
|
| 50 | + | fn kernelGp() -> u64; |
|
| 51 | + | /// Native controller entry address. |
|
| 52 | + | fn completion() -> u64; |
|
| 53 | + | /// Execute published code with one private package-state table. |
|
| 54 | + | fn invoke(address: u64, table: u64) -> i64; |
|
| 55 | + | /// Signal native fixture completion. |
|
| 56 | + | fn finish(); |
|
| 57 | + | ||
| 58 | + | /// Copy trusted package bytes into a bootstrap-owned page allocation. |
|
| 59 | + | unsafe fn input(authority: abi::Handle, bytes: &[u8]) -> abi::Handle { |
|
| 60 | + | let handle = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, (bytes.len as u64 + 4095) / 4096); |
|
| 61 | + | let page = try! pages::get(&pages::STORE, (try! capability::get(&TABLE, handle)).object); |
|
| 62 | + | let copied = try! mem::copy(@sliceOf(memory(page.base), page.count * 4096), bytes); |
|
| 63 | + | assert copied == bytes.len; |
|
| 64 | + | return handle; |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | /// Install user actors and disjoint windows that interrupt two compiler calls. |
|
| 68 | + | export unsafe fn setup() { |
|
| 69 | + | let pending = try! slots::reserve(&mut domains::STORE.slots[..]); |
|
| 70 | + | let owner = try! slots::commit(&mut domains::STORE.slots[..], pending); |
|
| 71 | + | capability::initialize(&mut TABLE, owner); |
|
| 72 | + | try! backing::registerDomain(&mut pages::STORE.backings, owner); |
|
| 73 | + | let authority = try! capability::install(&mut TABLE, capability::Entry { |
|
| 74 | + | kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::CREATE | abi::ALLOCATE), |
|
| 75 | + | }); |
|
| 76 | + | let source = input(authority, &dispatchinput::INPUT[..]); |
|
| 77 | + | let image = try! loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut TABLE, |
|
| 78 | + | loader::Request { authority, source, offset: 0, length: dispatchinput::INPUT.len as u64 }); |
|
| 79 | + | let payload = input(authority, &dispatchinput::PAYLOAD[..]); |
|
| 80 | + | let progress = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1); |
|
| 81 | + | let shared = try! pages::get(&pages::STORE, (try! capability::get(&TABLE, progress)).object); |
|
| 82 | + | set PROGRESS = memory(shared.base) as *mut u64; |
|
| 83 | + | for i in 0..4 { |
|
| 84 | + | let handle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, ®istry::STORE, &mut TABLE, authority, image); |
|
| 85 | + | let object = (try! capability::get(&TABLE, handle)).object; |
|
| 86 | + | let mut child = try! domains::get(&domains::STORE, object); |
|
| 87 | + | let mut rights = abi::ALLOCATE; |
|
| 88 | + | if i == 3 { |
|
| 89 | + | set rights |= abi::CREATE; |
|
| 90 | + | } |
|
| 91 | + | let self = try! capability::install(&mut child.memory.table, capability::Entry { |
|
| 92 | + | kind: abi::Kind::Domain, object, rights: abi::Rights(rights), |
|
| 93 | + | }); |
|
| 94 | + | set HANDLES[i] = handle; set ACTORS[i] = object; set CONTEXTS[i] = child.initial; |
|
| 95 | + | let readable = try! pages::grant(&mut pages::STORE, &TABLE, &mut child.memory.table, payload, abi::READ as u64); |
|
| 96 | + | let writable = try! pages::grant(&mut pages::STORE, &TABLE, &mut child.memory.table, progress, (abi::READ | abi::WRITE) as u64); |
|
| 97 | + | if i == 3 { |
|
| 98 | + | set SOURCE = readable; |
|
| 99 | + | set VICTIM = try! capability::install(&mut child.memory.table, capability::Entry { |
|
| 100 | + | kind: abi::Kind::Domain, object: ACTORS[0], rights: abi::Rights(abi::DESTROY), |
|
| 101 | + | }); |
|
| 102 | + | set domains::STORE.records[object.index].state = domains::Lifecycle::Active; |
|
| 103 | + | let frame = &mut domains::STORE.contexts[child.initial.index].frame; |
|
| 104 | + | set frame.pc = completion(); set frame.status = 0x1880; |
|
| 105 | + | set frame.registers[2] = domains::STORE.contexts[child.initial.index].kernelStack.end; |
|
| 106 | + | set frame.registers[3] = kernelGp(); |
|
| 107 | + | continue; |
|
| 108 | + | } |
|
| 109 | + | let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1); |
|
| 110 | + | let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut child.memory.table, stack, (abi::READ | abi::WRITE) as u64); |
|
| 111 | + | let storage = try! pages::get(&pages::STORE, (try! capability::get(&TABLE, stack)).object); |
|
| 112 | + | let args = @sliceOf(memory(storage.base) as *mut u64, 3); |
|
| 113 | + | set args[0] = *readable; set args[1] = dispatchinput::PAYLOAD.len as u64; set args[2] = shared.base; |
|
| 114 | + | try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, handle, storage.base + 4096, storage.base, i as u64); |
|
| 115 | + | } |
|
| 116 | + | for byte in "runtime actors ready\n" { |
|
| 117 | + | put(byte); |
|
| 118 | + | } |
|
| 119 | + | let now = dispatch::clock(); |
|
| 120 | + | let start = now + 1000000; |
|
| 121 | + | set FINAL = start + 300000000; |
|
| 122 | + | let mut remaining = try! budgets::seed(&mut budgets::STORE, &mut TABLE, 0, start, FINAL + 10000000); |
|
| 123 | + | let actors = [0 as u32, 3, 1, 2, 1]; |
|
| 124 | + | let ends = [10000000 as u64, 50000000, 60000000, 62000000, 300000000]; |
|
| 125 | + | for i in 0..5 { |
|
| 126 | + | let next = try! budgets::split(&mut budgets::STORE, &mut TABLE, remaining, start + ends[i], now); |
|
| 127 | + | let actor = actors[i]; |
|
| 128 | + | let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE, |
|
| 129 | + | budgets::Binding { budget: remaining, domain: HANDLES[actor], context: CONTEXTS[actor] }, now); |
|
| 130 | + | set remaining = next; |
|
| 131 | + | } |
|
| 132 | + | let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE, |
|
| 133 | + | budgets::Binding { budget: remaining, domain: HANDLES[3], context: CONTEXTS[3] }, now); |
|
| 134 | + | } |
|
| 135 | + | ||
| 136 | + | /// Verify cancellation, competing admission, progress, and resident code reuse. |
|
| 137 | + | export unsafe fn verify() { |
|
| 138 | + | for byte in "runtime admission metadata instructions: " { |
|
| 139 | + | put(byte); |
|
| 140 | + | } |
|
| 141 | + | number(sync::maximum()); put(10); |
|
| 142 | + | let progress = @sliceOf(PROGRESS, 4); |
|
| 143 | + | let guard = sync::enter(); |
|
| 144 | + | let first = CONTEXTS[0]; |
|
| 145 | + | for byte in "runtime first progress and owner: " { |
|
| 146 | + | put(byte); |
|
| 147 | + | } |
|
| 148 | + | number(progress[0]); put(32); number(abi::id(loader::STATE.owner)); put(10); |
|
| 149 | + | assert progress[0] == 1 and loader::STATE.busy and loader::STATE.owner == ACTORS[0]; |
|
| 150 | + | assert domains::STORE.contexts[first.index].reservation; |
|
| 151 | + | assert (domains::STORE.contexts[first.index].frame.status & 0x1800) == 0x1800; |
|
| 152 | + | let allocation = lifecycle::CALLS.records[first.index].allocation; |
|
| 153 | + | let mut loading: lifecycle::Loading = undefined; |
|
| 154 | + | match allocation { |
|
| 155 | + | case lifecycle::Allocation::Load(value) => { |
|
| 156 | + | set loading = value; |
|
| 157 | + | }, |
|
| 158 | + | else => panic "loader reservation required", |
|
| 159 | + | } |
|
| 160 | + | let output = loading.output else panic "compilation output required"; |
|
| 161 | + | sync::leave(guard); |
|
| 162 | + | let mut busy = false; |
|
| 163 | + | try calls::synchronized(ACTORS[3], 51, &[0, *SOURCE, 0, dispatchinput::PAYLOAD.len as u64], dispatch::clock()) catch error { |
|
| 164 | + | assert error == abi::Error::Busy; set busy = true; |
|
| 165 | + | }; |
|
| 166 | + | assert busy; |
|
| 167 | + | for byte in "runtime competing load rejected\n" { |
|
| 168 | + | put(byte); |
|
| 169 | + | } |
|
| 170 | + | let destroyed = try! calls::synchronized(ACTORS[3], 22, &[*VICTIM, 0, 0, 0], dispatch::clock()); |
|
| 171 | + | assert destroyed == 0; |
|
| 172 | + | let checking = sync::enter(); |
|
| 173 | + | let request = try! dispatch::request(0, remote::Action::Reschedule); |
|
| 174 | + | sync::leave(checking); |
|
| 175 | + | let result = try! dispatch::awaitRequest(0, request); |
|
| 176 | + | assert result == 0; |
|
| 177 | + | let cancelled = sync::enter(); |
|
| 178 | + | assert not loader::STATE.busy; |
|
| 179 | + | assert (lifecycle::CALLS.active[first.index / 32] & (1 << (first.index % 32))) == 0; |
|
| 180 | + | assert registry::STORE.slots[output.object.index].state == slots::State::Free; |
|
| 181 | + | sync::leave(cancelled); |
|
| 182 | + | let mut reclaimed = false; |
|
| 183 | + | for attempt in 0..10000 { |
|
| 184 | + | let guard = sync::enter(); |
|
| 185 | + | set reclaimed = pages::STORE.backings.pool.retiredCount == 0; |
|
| 186 | + | if reclaimed { |
|
| 187 | + | sync::leave(guard); |
|
| 188 | + | break; |
|
| 189 | + | } |
|
| 190 | + | let request = try! dispatch::request(0, remote::Action::Reschedule); |
|
| 191 | + | sync::leave(guard); |
|
| 192 | + | let result = try! dispatch::awaitRequest(0, request); |
|
| 193 | + | assert result == 0; |
|
| 194 | + | } |
|
| 195 | + | assert reclaimed; |
|
| 196 | + | for run in [loading.input.frames, output.code, output.metadata] { |
|
| 197 | + | let mut offset: u32 = 0; |
|
| 198 | + | while offset < run.count { |
|
| 199 | + | let mut count = run.count - offset; |
|
| 200 | + | if count > 256 { |
|
| 201 | + | set count = 256; |
|
| 202 | + | } |
|
| 203 | + | let recovered = sync::enter(); |
|
| 204 | + | for i in run.first + offset..run.first + offset + count { |
|
| 205 | + | assert frames::available(&pages::STORE.backings.pool, i); |
|
| 206 | + | } |
|
| 207 | + | sync::leave(recovered); |
|
| 208 | + | set offset += count; |
|
| 209 | + | } |
|
| 210 | + | } |
|
| 211 | + | for byte in "runtime cancellation passed\n" { |
|
| 212 | + | put(byte); |
|
| 213 | + | } |
|
| 214 | + | while dispatch::clock() < FINAL { |
|
| 215 | + | } |
|
| 216 | + | let complete = sync::enter(); |
|
| 217 | + | for byte in "runtime final progress: " { |
|
| 218 | + | put(byte); |
|
| 219 | + | } |
|
| 220 | + | for i in 0..4 { |
|
| 221 | + | number(progress[i]); |
|
| 222 | + | put(32); |
|
| 223 | + | } |
|
| 224 | + | put(10); |
|
| 225 | + | assert progress[0] == 1 and progress[1] == 2 and progress[2] == 1; |
|
| 226 | + | assert not loader::STATE.busy; |
|
| 227 | + | let package = try! registry::get(®istry::STORE, abi::reference(progress[3])); |
|
| 228 | + | let entry = package.entry else panic "published entry required"; |
|
| 229 | + | let mut controller = try! domains::get(&domains::STORE, ACTORS[3]); |
|
| 230 | + | let image = try! registry::install(®istry::STORE, &mut controller.memory.table, abi::reference(progress[3])); |
|
| 231 | + | sync::leave(complete); |
|
| 232 | + | let handle = abi::Handle(try! calls::synchronized(ACTORS[3], 20, &[0, *image, 0, 0], dispatch::clock())); |
|
| 233 | + | let created = sync::enter(); |
|
| 234 | + | let object = (try! capability::get(&controller.memory.table, handle)).object; |
|
| 235 | + | let domain = try! domains::get(&domains::STORE, object); |
|
| 236 | + | sync::leave(created); |
|
| 237 | + | assert invoke(entry, domain.graph.table.ptr as u64) == 43; |
|
| 238 | + | assert invoke(entry, domain.graph.table.ptr as u64) == 85; |
|
| 239 | + | for byte in "runtime retry published executable code\n" { |
|
| 240 | + | put(byte); |
|
| 241 | + | } |
|
| 242 | + | let maximum = sync::maximum(); |
|
| 243 | + | for byte in "runtime metadata instructions: " { |
|
| 244 | + | put(byte); |
|
| 245 | + | } |
|
| 246 | + | number(maximum); put(10); |
|
| 247 | + | assert maximum > 0 and maximum < 1000000; |
|
| 248 | + | finish(); |
|
| 249 | + | } |
test/runtime/machine.ras
added
+44 -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; |
|
| 26 | + | ||
| 27 | + | .export @kernel::dispatchcheck::put; |
|
| 28 | + | @kernel::dispatchcheck::put |
|
| 29 | + | li %t0 0x10000000; |
|
| 30 | + | sb %a0 0(%t0); |
|
| 31 | + | ret; |
|
| 32 | + | ||
| 33 | + | .export @kernel::dispatchcheck::invoke; |
|
| 34 | + | // Preserve kernel state while calling one private instance of shared code. |
|
| 35 | + | @kernel::dispatchcheck::invoke |
|
| 36 | + | addi %sp %sp -16; |
|
| 37 | + | sd %ra 8(%sp); |
|
| 38 | + | sd %gp 0(%sp); |
|
| 39 | + | mv %gp %a1; |
|
| 40 | + | jalr %ra %a0 0; |
|
| 41 | + | ld %gp 0(%sp); |
|
| 42 | + | ld %ra 8(%sp); |
|
| 43 | + | addi %sp %sp 16; |
|
| 44 | + | ret; |
test/runtime/run
added
+39 -0
| 1 | + | #!/bin/sh |
|
| 2 | + | # Compile, cancel, and retry a package through scheduled user calls. |
|
| 3 | + | set -eu |
|
| 4 | + | emulator=${RAD_EMULATOR:-emulator} |
|
| 5 | + | fixture=runtime |
|
| 6 | + | work=$(mktemp -d) |
|
| 7 | + | trap 'rm -rf "$work"' EXIT HUP INT TERM |
|
| 8 | + | mkdir "$work/spin" |
|
| 9 | + | cp "test/$fixture/spin.rad" "$work/spin.rad" |
|
| 10 | + | cp kernel/kernel/abi.rad kernel/kernel/sys.rad "$work/spin/" |
|
| 11 | + | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/radiance.rv64.dev \ |
|
| 12 | + | -pkg spin -mod "$work/spin.rad" -mod "$work/spin/abi.rad" -mod "$work/spin/sys.rad" -entry spin -ril "$work" |
|
| 13 | + | cp test/loader/loaded.rad "$work/loaded.rad" |
|
| 14 | + | index=0 |
|
| 15 | + | while [ "$index" -lt 128 ]; do |
|
| 16 | + | printf '\n/// Compute a distinct exported value.\nexport fn value%s(input: u64) -> u64 { return input + %s; }\n' "$index" "$index" >> "$work/loaded.rad" |
|
| 17 | + | index=$((index + 1)) |
|
| 18 | + | done |
|
| 19 | + | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/radiance.rv64.dev \ |
|
| 20 | + | -pkg loaded -mod "$work/loaded.rad" -entry loaded -ril "$work" |
|
| 21 | + | cp kernel/kernel.rad "$work/kernel.rad" |
|
| 22 | + | printf '\nexport mod dispatchinput;\nexport mod dispatchcheck;\n' >> "$work/kernel.rad" |
|
| 23 | + | mkdir "$work/kernel" |
|
| 24 | + | cp kernel/kernel/*.rad "$work/kernel/" |
|
| 25 | + | cp "test/$fixture/kernel/dispatchcheck.rad" "$work/kernel/" |
|
| 26 | + | length=$(wc -c < "$work/spin.ril") |
|
| 27 | + | printf '//! Binary user loop.\n/// Complete trusted input package.\nexport static INPUT: [u8; %s] = [\n' "$length" > "$work/kernel/dispatchinput.rad" |
|
| 28 | + | od -An -v -tu1 "$work/spin.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/dispatchinput.rad" |
|
| 29 | + | printf '];\n' >> "$work/kernel/dispatchinput.rad" |
|
| 30 | + | length=$(wc -c < "$work/loaded.ril") |
|
| 31 | + | printf '\n/// Package compiled by scheduled user calls.\nexport static PAYLOAD: [u8; %s] = [\n' "$length" >> "$work/kernel/dispatchinput.rad" |
|
| 32 | + | od -An -v -tu1 "$work/loaded.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/dispatchinput.rad" |
|
| 33 | + | printf '];\n' >> "$work/kernel/dispatchinput.rad" |
|
| 34 | + | sh test/acceptance/compile "$emulator" "$work" |
|
| 35 | + | cat "test/$fixture/machine.ras" kernel/kernel/*.ras > "$work/dispatch.ras" |
|
| 36 | + | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 \ |
|
| 37 | + | -- "$work/std.ril" "$work/kernel.ril" "$work/dispatch.ras" "$work/dispatch.rv64" |
|
| 38 | + | "$emulator" -machine -harts=1 -memory-size=262144 -max-steps=2000000000 -run "$work/dispatch.rv64" |
|
| 39 | + | printf 'runtime: scheduled cancellation, retry, and publication passed\n' |
test/runtime/spin.rad
added
+53 -0
| 1 | + | //! Scheduled runtime compilation and an independent window witness. |
|
| 2 | + | export mod abi; |
|
| 3 | + | export mod sys; |
|
| 4 | + | ||
| 5 | + | /// Startup fields used by the scheduled fixture. |
|
| 6 | + | record Request: Copy { |
|
| 7 | + | /// Readable binary package capability. |
|
| 8 | + | source: u64, |
|
| 9 | + | /// Complete binary package byte count. |
|
| 10 | + | length: u64, |
|
| 11 | + | /// Shared progress counters. |
|
| 12 | + | progress: *unsafe mut u64, |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | /// Values supplied at domain activation. |
|
| 16 | + | record Env: Copy { |
|
| 17 | + | /// Input handle, byte count, and shared progress address. |
|
| 18 | + | argsPointer: *Request, |
|
| 19 | + | /// Actor index selected by the parent. |
|
| 20 | + | argsSize: u64, |
|
| 21 | + | /// Event capability installed at creation. |
|
| 22 | + | eventsHandle: u64, |
|
| 23 | + | /// Shared event queue address. |
|
| 24 | + | eventsPointer: u64, |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | /// Compile a package twice or record progress during another domain's load. |
|
| 28 | + | @default unsafe fn main(env: *Env) { |
|
| 29 | + | let args = env.argsPointer; |
|
| 30 | + | let progress = @sliceOf(args.progress, 4); |
|
| 31 | + | let actor = env.argsSize as u32; |
|
| 32 | + | if actor == 2 { |
|
| 33 | + | assert progress[1] == 1; |
|
| 34 | + | let mut busy = false; |
|
| 35 | + | try sys::imageLoad(abi::Handle(0), abi::Handle(args.source), 0, args.length) catch error { |
|
| 36 | + | assert error == abi::Error::Busy; set busy = true; |
|
| 37 | + | }; |
|
| 38 | + | assert busy; |
|
| 39 | + | set progress[2] = 1; |
|
| 40 | + | return; |
|
| 41 | + | } |
|
| 42 | + | set progress[actor] = 1; |
|
| 43 | + | let image = try sys::imageLoad(abi::Handle(0), abi::Handle(args.source), 0, args.length) catch error { |
|
| 44 | + | set progress[actor] = 100 + error as u64; return; |
|
| 45 | + | }; |
|
| 46 | + | let info = try! sys::queryImage(image); |
|
| 47 | + | let repeated = try sys::imageLoad(abi::Handle(0), abi::Handle(args.source), 0, args.length) catch error { |
|
| 48 | + | set progress[actor] = 200 + error as u64; return; |
|
| 49 | + | }; |
|
| 50 | + | assert (try! sys::queryImage(repeated)).id == info.id; |
|
| 51 | + | set progress[3] = info.id; |
|
| 52 | + | set progress[actor] = 2; |
|
| 53 | + | } |
test/smp/kernel/dispatchcheck.rad
+2 -1
| 1 | 1 | //! One domain executing separate user contexts on every online hart. |
|
| 2 | 2 | use std::mem; |
|
| 3 | 3 | use std::arch::rv64::shared; |
|
| 4 | 4 | use kernel::abi; |
|
| 5 | + | use kernel::frames; |
|
| 5 | 6 | use kernel::slots; |
|
| 6 | 7 | use kernel::backing; |
|
| 7 | 8 | use kernel::pages; |
|
| 8 | 9 | use kernel::capability; |
|
| 9 | 10 | use kernel::registry; |
| 642 | 643 | let guard = sync::enter(); |
|
| 643 | 644 | assert (lifecycle::CALLS.active[IDLE_CONTEXT.index / 32] & (1 << (IDLE_CONTEXT.index % 32))) == 0; |
|
| 644 | 645 | assert pages::STORE.slots[allocation.page.index].state == slots::State::Free; |
|
| 645 | 646 | assert pages::STORE.backings.slots[allocation.backing.index].state == slots::State::Free; |
|
| 646 | 647 | for i in allocation.run.first..allocation.run.first + allocation.run.count { |
|
| 647 | - | assert pages::STORE.backings.pool.free[i]; |
|
| 648 | + | assert frames::available(&pages::STORE.backings.pool, i); |
|
| 648 | 649 | } |
|
| 649 | 650 | sync::leave(guard); |
|
| 650 | 651 | for byte in "smp cancelled preempted page allocation\n" { |
|
| 651 | 652 | put(byte); |
|
| 652 | 653 | } |