kernel: Add ABI types and bounded object storage
1873ef7c9d924b8588c54be9c171cc659592bceee55f9965a7ad39914730076d
Assisted-by: Codex:gpt-6
1 parent
fc84a1d3
kernel/kernel.rad
+3 -0
| 1 | 1 | //! Kernel resource management and machine execution. |
|
| 2 | 2 | ||
| 3 | 3 | use std::testing; |
|
| 4 | 4 | ||
| 5 | + | export mod abi; |
|
| 6 | + | export mod limits; |
|
| 7 | + | export mod slots; |
|
| 5 | 8 | export mod range; |
|
| 6 | 9 | export mod sync; |
|
| 7 | 10 | @test export mod tests; |
kernel/kernel/abi.rad
added
+201 -0
| 1 | + | //! Kernel handle representation, rights, errors, and operation numbers. |
|
| 2 | + | ||
| 3 | + | /// Domain-relative capability value. Zero is the self sentinel where allowed. |
|
| 4 | + | export record Handle: Copy(u64); |
|
| 5 | + | /// Rights granted by a live capability-table entry. |
|
| 6 | + | export record Rights: Copy(u16); |
|
| 7 | + | ||
| 8 | + | /// Read object contents or metadata. |
|
| 9 | + | export constant READ: u16 = 1; |
|
| 10 | + | /// Modify object contents. |
|
| 11 | + | export constant WRITE: u16 = 2; |
|
| 12 | + | /// Execute an image or entry. |
|
| 13 | + | export constant EXECUTE: u16 = 4; |
|
| 14 | + | /// Grant an attenuated capability. |
|
| 15 | + | export constant GRANT: u16 = 8; |
|
| 16 | + | /// Transfer a capability or CPU authority. |
|
| 17 | + | export constant TRANSFER: u16 = 16; |
|
| 18 | + | /// Create a domain. |
|
| 19 | + | export constant CREATE: u16 = 32; |
|
| 20 | + | /// Allocate physical memory. |
|
| 21 | + | export constant ALLOCATE: u16 = 64; |
|
| 22 | + | /// Wake or manage the parent relationship of a domain. |
|
| 23 | + | export constant WAKE: u16 = 128; |
|
| 24 | + | /// Destroy a domain. |
|
| 25 | + | export constant DESTROY: u16 = 256; |
|
| 26 | + | /// All defined right bits. |
|
| 27 | + | export constant RIGHTS_MASK: u16 = 511; |
|
| 28 | + | /// Cascade destruction through creation ancestry. |
|
| 29 | + | export constant CASCADE: u64 = 1; |
|
| 30 | + | ||
| 31 | + | /// Object kind stored in the high four handle bits. |
|
| 32 | + | export union Kind: Copy { |
|
| 33 | + | /// Unoccupied table entry. |
|
| 34 | + | Empty = 0, |
|
| 35 | + | /// Contiguous physical memory. |
|
| 36 | + | Page = 1, |
|
| 37 | + | /// Interrupt source ownership. |
|
| 38 | + | Interrupt = 2, |
|
| 39 | + | /// Device memory region. |
|
| 40 | + | Device = 3, |
|
| 41 | + | /// Domain notification queue. |
|
| 42 | + | Events = 4, |
|
| 43 | + | /// Domain management authority. |
|
| 44 | + | Domain = 5, |
|
| 45 | + | /// Exclusive CPU time window. |
|
| 46 | + | Budget = 6, |
|
| 47 | + | /// Resident executable package graph entry. |
|
| 48 | + | Image = 7, |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | /// Scalar error codes; the syscall ABI returns their negatives. |
|
| 52 | + | export union Error: Copy { |
|
| 53 | + | /// Successful operation. |
|
| 54 | + | Ok = 0, |
|
| 55 | + | /// Missing, stale, or incorrectly typed handle. |
|
| 56 | + | BadHandle = 1, |
|
| 57 | + | /// Required rights are absent. |
|
| 58 | + | Denied = 2, |
|
| 59 | + | /// Physical memory is exhausted. |
|
| 60 | + | OutOfMemory = 3, |
|
| 61 | + | /// Invalid argument, alignment, or reserved bits. |
|
| 62 | + | InvalidArg = 4, |
|
| 63 | + | /// The operation cannot proceed in the current state. |
|
| 64 | + | Busy = 5, |
|
| 65 | + | /// The supplied executable image is invalid. |
|
| 66 | + | VerifyFailed = 6, |
|
| 67 | + | /// The domain is not pending activation. |
|
| 68 | + | NotPending = 7, |
|
| 69 | + | /// Fixed kernel storage is exhausted. |
|
| 70 | + | Exhausted = 8, |
|
| 71 | + | } |
|
| 72 | + | ||
| 73 | + | /// Operation identifier passed in a7. |
|
| 74 | + | export union Operation: Copy { |
|
| 75 | + | /// Grant capability rights. |
|
| 76 | + | CapabilityGrant = 10, |
|
| 77 | + | /// Transfer a capability. |
|
| 78 | + | CapabilityTransfer = 11, |
|
| 79 | + | /// Drop a capability. |
|
| 80 | + | CapabilityDrop = 12, |
|
| 81 | + | /// Create a pending domain from an image. |
|
| 82 | + | DomainCreate = 20, |
|
| 83 | + | /// Activate a pending domain. |
|
| 84 | + | DomainActivate = 21, |
|
| 85 | + | /// Destroy a domain. |
|
| 86 | + | DomainDestroy = 22, |
|
| 87 | + | /// Select a new lifecycle-event parent. |
|
| 88 | + | DomainReparent = 23, |
|
| 89 | + | /// Allocate physical frames. |
|
| 90 | + | PageAllocate = 30, |
|
| 91 | + | /// Split an exclusive page object. |
|
| 92 | + | PageSplit = 31, |
|
| 93 | + | /// Transfer the remaining active window to a target domain. |
|
| 94 | + | Yield = 40, |
|
| 95 | + | /// Request a timeout event. |
|
| 96 | + | Timeout = 41, |
|
| 97 | + | /// Wait for an event. |
|
| 98 | + | Wait = 42, |
|
| 99 | + | /// Terminate with an abort report. |
|
| 100 | + | Abort = 43, |
|
| 101 | + | /// Query page metadata. |
|
| 102 | + | QueryPage = 44, |
|
| 103 | + | /// Query device metadata. |
|
| 104 | + | QueryDevice = 45, |
|
| 105 | + | /// Query interrupt metadata. |
|
| 106 | + | QueryInterrupt = 46, |
|
| 107 | + | /// Query domain metadata. |
|
| 108 | + | QueryDomain = 47, |
|
| 109 | + | /// Query event queue metadata. |
|
| 110 | + | QueryEvents = 48, |
|
| 111 | + | /// Terminate normally. |
|
| 112 | + | Exit = 49, |
|
| 113 | + | /// Deliver a wake token. |
|
| 114 | + | Wakeup = 50, |
|
| 115 | + | /// Load a trusted binary RIL package. |
|
| 116 | + | ImageLoad = 51, |
|
| 117 | + | /// Query a resident image. |
|
| 118 | + | QueryImage = 52, |
|
| 119 | + | /// Create an additional execution context. |
|
| 120 | + | ContextCreate = 60, |
|
| 121 | + | /// Destroy an additional execution context. |
|
| 122 | + | ContextDestroy = 61, |
|
| 123 | + | /// Query an execution context. |
|
| 124 | + | QueryContext = 62, |
|
| 125 | + | /// Query the current context and execution window. |
|
| 126 | + | CurrentContext = 63, |
|
| 127 | + | /// Partition a CPU time window. |
|
| 128 | + | BudgetSplit = 70, |
|
| 129 | + | /// Combine adjacent CPU time windows. |
|
| 130 | + | BudgetMerge = 71, |
|
| 131 | + | /// Query a CPU time window. |
|
| 132 | + | QueryBudget = 72, |
|
| 133 | + | /// Bind and transfer a CPU time window to a context. |
|
| 134 | + | BudgetBind = 73, |
|
| 135 | + | } |
|
| 136 | + | ||
| 137 | + | /// Generation-bearing reference within one object table. |
|
| 138 | + | export record Ref: Copy { |
|
| 139 | + | /// Table index. |
|
| 140 | + | index: u32, |
|
| 141 | + | /// Generation of the indexed slot. |
|
| 142 | + | generation: u32, |
|
| 143 | + | } |
|
| 144 | + | ||
| 145 | + | /// Structurally valid handle fields. Lookup must also check the live table entry. |
|
| 146 | + | export record Decoded: Copy { |
|
| 147 | + | /// Encoded object kind. |
|
| 148 | + | kind: Kind, |
|
| 149 | + | /// Table slot and generation. |
|
| 150 | + | object: Ref, |
|
| 151 | + | } |
|
| 152 | + | ||
| 153 | + | /// Pack kind, 32-bit generation, and 24-bit index with zero reserved bits. |
|
| 154 | + | export fn handle(kind: Kind, object: Ref) -> Handle throws (Error) { |
|
| 155 | + | if kind == Kind::Empty or object.index > 0xffffff { |
|
| 156 | + | throw Error::InvalidArg; |
|
| 157 | + | } |
|
| 158 | + | return Handle((kind as u64 << 60) | (object.generation as u64 << 28) | object.index as u64); |
|
| 159 | + | } |
|
| 160 | + | ||
| 161 | + | /// Decode a nonzero handle and reject reserved bits or unsupported kinds. |
|
| 162 | + | export fn decode(value: Handle) -> Decoded throws (Error) { |
|
| 163 | + | let bits = *value; |
|
| 164 | + | if bits == 0 or (bits & 0x0f000000) <> 0 { |
|
| 165 | + | throw Error::BadHandle; |
|
| 166 | + | } |
|
| 167 | + | let mut kind = Kind::Empty; |
|
| 168 | + | match bits >> 60 { |
|
| 169 | + | case 1 => set kind = Kind::Page, |
|
| 170 | + | case 2 => set kind = Kind::Interrupt, |
|
| 171 | + | case 3 => set kind = Kind::Device, |
|
| 172 | + | case 4 => set kind = Kind::Events, |
|
| 173 | + | case 5 => set kind = Kind::Domain, |
|
| 174 | + | case 6 => set kind = Kind::Budget, |
|
| 175 | + | case 7 => set kind = Kind::Image, |
|
| 176 | + | else => throw Error::BadHandle, |
|
| 177 | + | } |
|
| 178 | + | return Decoded { kind, object: Ref { index: (bits & 0xffffff) as u32, generation: (bits >> 28) as u32 } }; |
|
| 179 | + | } |
|
| 180 | + | ||
| 181 | + | /// Validate a caller-supplied rights mask. |
|
| 182 | + | export fn rights(bits: u64) -> Rights throws (Error) { |
|
| 183 | + | if (bits & ~(RIGHTS_MASK as u64)) <> 0 { |
|
| 184 | + | throw Error::InvalidArg; |
|
| 185 | + | } |
|
| 186 | + | return Rights(bits as u16); |
|
| 187 | + | } |
|
| 188 | + | ||
| 189 | + | /// Check that all requested rights are present. |
|
| 190 | + | export fn permits(granted: Rights, requested: Rights) -> bool { |
|
| 191 | + | return (*granted & *requested) == *requested; |
|
| 192 | + | } |
|
| 193 | + | ||
| 194 | + | /// Encode a generation-bearing internal ID without reserved bits. |
|
| 195 | + | export fn id(object: Ref) -> u64 { |
|
| 196 | + | return (object.generation as u64 << 32) | object.index as u64; |
|
| 197 | + | } |
|
| 198 | + | /// Decode an internal ID. Object lookup validates its scope and liveness. |
|
| 199 | + | export fn reference(value: u64) -> Ref { |
|
| 200 | + | return Ref { index: value as u32, generation: (value >> 32) as u32 }; |
|
| 201 | + | } |
kernel/kernel/limits.rad
added
+28 -0
| 1 | + | //! Fixed kernel storage profile. |
|
| 2 | + | ||
| 3 | + | /// Maximum resident domains, including retained tombstones. |
|
| 4 | + | export constant DOMAINS: u32 = 512; |
|
| 5 | + | /// Per-domain capability table capacity. |
|
| 6 | + | export constant HANDLES: u32 = 512; |
|
| 7 | + | /// Maximum physical frames tracked by this build. |
|
| 8 | + | export constant FRAMES: u32 = 65536; |
|
| 9 | + | /// Physical frame size in bytes. |
|
| 10 | + | export constant FRAME_SIZE: u64 = 4096; |
|
| 11 | + | /// Maximum page objects, including split allocation segments. |
|
| 12 | + | export constant PAGES: u32 = 65536; |
|
| 13 | + | /// Maximum backing allocation records. |
|
| 14 | + | export constant ALLOCATIONS: u32 = 65536; |
|
| 15 | + | /// Maximum interrupt objects. |
|
| 16 | + | export constant INTERRUPTS: u32 = 128; |
|
| 17 | + | /// Maximum device memory regions. |
|
| 18 | + | export constant DEVICES: u32 = 128; |
|
| 19 | + | /// Maximum budget objects. |
|
| 20 | + | export constant BUDGETS: u32 = 512; |
|
| 21 | + | /// Maximum execution contexts across all domains. |
|
| 22 | + | export constant CONTEXTS: u32 = 2048; |
|
| 23 | + | /// Maximum hardware threads. |
|
| 24 | + | export constant HARTS: u32 = 8; |
|
| 25 | + | /// Maximum resident package records and gp table entries. |
|
| 26 | + | export constant PACKAGES: u32 = 256; |
|
| 27 | + | /// Generation values never wrap. |
|
| 28 | + | export constant LAST_GENERATION: u32 = 0xffffffff; |
kernel/kernel/slots.rad
added
+103 -0
| 1 | + | //! Bounded slot lifetimes and linear reservations. |
|
| 2 | + | ||
| 3 | + | use super::abi; |
|
| 4 | + | use super::limits; |
|
| 5 | + | ||
| 6 | + | /// Slot lifetime state. |
|
| 7 | + | export union State: Copy { |
|
| 8 | + | /// Available for reservation. |
|
| 9 | + | Free, |
|
| 10 | + | /// Owned by an uncommitted reservation. |
|
| 11 | + | Reserved, |
|
| 12 | + | /// Published object. |
|
| 13 | + | Live, |
|
| 14 | + | /// Generation space is exhausted; the slot cannot be reused. |
|
| 15 | + | Retired, |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | /// Metadata stored beside an object table's payload array. |
|
| 19 | + | export record Slot: Copy { |
|
| 20 | + | /// Current nonzero generation. |
|
| 21 | + | generation: u32, |
|
| 22 | + | /// Current lifetime state. |
|
| 23 | + | state: State, |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// A reserved slot that must be committed or cancelled in the same table. |
|
| 27 | + | export union Reservation: Once { |
|
| 28 | + | /// Unpublished object slot. |
|
| 29 | + | Held(abi::Ref), |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | /// Initialize fresh metadata. No live references may name this table. |
|
| 33 | + | export fn initialize(slots: &mut [Slot]) { |
|
| 34 | + | for i in 0..slots.len { |
|
| 35 | + | set slots[i] = Slot { generation: 1, state: State::Free }; |
|
| 36 | + | } |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | /// Reserve the first available slot with a bounded scan. |
|
| 40 | + | export fn reserve(slots: &mut [Slot]) -> Reservation throws (abi::Error) { |
|
| 41 | + | for i in 0..slots.len { |
|
| 42 | + | if slots[i].state == State::Free { |
|
| 43 | + | set slots[i].state = State::Reserved; |
|
| 44 | + | return Reservation::Held(abi::Ref { index: i, generation: slots[i].generation }); |
|
| 45 | + | } |
|
| 46 | + | } |
|
| 47 | + | throw abi::Error::Exhausted; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// Check an internal reference against its table and required lifetime state. |
|
| 51 | + | export fn matches(slots: &[Slot], object: abi::Ref, state: State) -> bool { |
|
| 52 | + | return object.index < slots.len and slots[object.index].generation == object.generation |
|
| 53 | + | and slots[object.index].state == state; |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// Inspect a reservation's unpublished reference without consuming it. |
|
| 57 | + | export fn reference(reservation: &Reservation) -> abi::Ref { |
|
| 58 | + | match reservation { case Reservation::Held(object) => return *object, } |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | /// Publish a reserved slot. The caller must initialize its payload first. |
|
| 62 | + | export fn commit(slots: &mut [Slot], reservation: Reservation) -> abi::Ref throws (abi::Error) { |
|
| 63 | + | match reservation { |
|
| 64 | + | case Reservation::Held(object) => { |
|
| 65 | + | if not matches(slots, object, State::Reserved) { |
|
| 66 | + | throw abi::Error::BadHandle; |
|
| 67 | + | } |
|
| 68 | + | set slots[object.index].state = State::Live; |
|
| 69 | + | return object; |
|
| 70 | + | }, |
|
| 71 | + | } |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | /// Advance the slot generation or retire it permanently. |
|
| 75 | + | fn advance(slot: &mut Slot) { |
|
| 76 | + | if slot.generation == limits::LAST_GENERATION { |
|
| 77 | + | set slot.state = State::Retired; |
|
| 78 | + | } |
|
| 79 | + | else { |
|
| 80 | + | set slot.generation += 1; |
|
| 81 | + | set slot.state = State::Free; |
|
| 82 | + | } |
|
| 83 | + | } |
|
| 84 | + | ||
| 85 | + | /// Cancel a reservation and invalidate its generation. |
|
| 86 | + | export fn cancel(slots: &mut [Slot], reservation: Reservation) throws (abi::Error) { |
|
| 87 | + | match reservation { |
|
| 88 | + | case Reservation::Held(object) => { |
|
| 89 | + | if not matches(slots, object, State::Reserved) { |
|
| 90 | + | throw abi::Error::BadHandle; |
|
| 91 | + | } |
|
| 92 | + | advance(&mut slots[object.index]); |
|
| 93 | + | }, |
|
| 94 | + | } |
|
| 95 | + | } |
|
| 96 | + | ||
| 97 | + | /// Invalidate a live object after all payload-specific teardown is complete. |
|
| 98 | + | export fn release(slots: &mut [Slot], object: abi::Ref) throws (abi::Error) { |
|
| 99 | + | if not matches(slots, object, State::Live) { |
|
| 100 | + | throw abi::Error::BadHandle; |
|
| 101 | + | } |
|
| 102 | + | advance(&mut slots[object.index]); |
|
| 103 | + | } |
kernel/kernel/tests.rad
+2 -0
| 1 | 1 | //! Kernel unit tests. Machine execution tests have separate entry points. |
|
| 2 | 2 | ||
| 3 | 3 | export mod range; |
|
| 4 | + | export mod abi; |
|
| 5 | + | export mod slots; |
kernel/kernel/tests/abi.rad
added
+60 -0
| 1 | + | //! Handle bit boundaries and stable ABI values. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use kernel::abi; |
|
| 5 | + | ||
| 6 | + | /// Check every kind at the index and generation boundaries. |
|
| 7 | + | @test fn handles() throws (testing::TestError) { |
|
| 8 | + | for kind in &[abi::Kind::Page, abi::Kind::Interrupt, abi::Kind::Device, |
|
| 9 | + | abi::Kind::Events, abi::Kind::Domain, abi::Kind::Budget, abi::Kind::Image] |
|
| 10 | + | { |
|
| 11 | + | for index in &[0 as u32, 0xffffff as u32] { |
|
| 12 | + | for generation in &[0 as u32, 1 as u32, 0xffffffff as u32] { |
|
| 13 | + | let object = abi::Ref { index, generation }; |
|
| 14 | + | let value = try! abi::handle(kind, object); |
|
| 15 | + | let decoded = try! abi::decode(value); |
|
| 16 | + | try testing::expect(decoded.kind == kind and decoded.object == object); |
|
| 17 | + | try testing::expect((*value & 0x0f000000) == 0); |
|
| 18 | + | try testing::expect(abi::reference(abi::id(object)) == object); |
|
| 19 | + | } |
|
| 20 | + | } |
|
| 21 | + | } |
|
| 22 | + | let maximum = try! abi::handle(abi::Kind::Image, abi::Ref { index: 0xffffff, generation: 0xffffffff }); |
|
| 23 | + | try testing::expect(*maximum == 0x7ffffffff0ffffff); |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// Reject self, empty/unsupported kinds, reserved bits, and oversized indices. |
|
| 27 | + | @test fn invalidHandles() throws (testing::TestError) { |
|
| 28 | + | let mut failed: u32 = 0; |
|
| 29 | + | for bits in &[0 as u64, 1 as u64, 0x8000000000000000, 0xf000000000000000, |
|
| 30 | + | 0x1000000001000000, 0x1000000002000000, 0x1000000004000000, 0x1000000008000000] |
|
| 31 | + | { |
|
| 32 | + | try abi::decode(abi::Handle(bits)) catch err { |
|
| 33 | + | try testing::expect(err == abi::Error::BadHandle); set failed += 1; |
|
| 34 | + | }; |
|
| 35 | + | } |
|
| 36 | + | try testing::expect(failed == 8); |
|
| 37 | + | try abi::handle(abi::Kind::Page, abi::Ref { index: 0x1000000, generation: 1 }) catch err { |
|
| 38 | + | try testing::expect(err == abi::Error::InvalidArg); set failed += 1; |
|
| 39 | + | }; |
|
| 40 | + | try testing::expect(failed == 9); |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | /// Check rights validation and the specified error/operation numbers. |
|
| 44 | + | @test fn numbersAndRights() throws (testing::TestError) { |
|
| 45 | + | let all = try! abi::rights(511); |
|
| 46 | + | let read = try! abi::rights(abi::READ as u64); |
|
| 47 | + | try testing::expect(abi::permits(all, read) and not abi::permits(read, all)); |
|
| 48 | + | let mut failed = false; |
|
| 49 | + | try abi::rights(512) catch err { |
|
| 50 | + | try testing::expect(err == abi::Error::InvalidArg); |
|
| 51 | + | set failed = true; |
|
| 52 | + | }; |
|
| 53 | + | try testing::expect(failed); |
|
| 54 | + | try testing::expect(abi::Operation::CapabilityGrant as u32 == 10); |
|
| 55 | + | try testing::expect(abi::Operation::DomainReparent as u32 == 23); |
|
| 56 | + | try testing::expect(abi::Operation::PageAllocate as u32 == 30); |
|
| 57 | + | try testing::expect(abi::Operation::Yield as u32 == 40); |
|
| 58 | + | try testing::expect(abi::Operation::Wakeup as u32 == 50); |
|
| 59 | + | try testing::expect(abi::Error::BadHandle as u32 == 1 and abi::Error::Exhausted as u32 == 8); |
|
| 60 | + | } |
kernel/kernel/tests/slots.rad
added
+63 -0
| 1 | + | //! Slot reservation, publication, cancellation, and generation retirement. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use kernel::abi; |
|
| 5 | + | use kernel::slots; |
|
| 6 | + | ||
| 7 | + | /// Reservations occupy capacity before publication and must be consumed once. |
|
| 8 | + | @test unsafe fn lifetime() throws (testing::TestError) { |
|
| 9 | + | let mut table: [slots::Slot; 2] = undefined; |
|
| 10 | + | slots::initialize(&mut table[..]); |
|
| 11 | + | let first = try! slots::reserve(&mut table[..]); |
|
| 12 | + | let pending = slots::reference(&first); |
|
| 13 | + | let reserved = slots::matches(&table[..], pending, slots::State::Reserved); |
|
| 14 | + | let second = try! slots::reserve(&mut table[..]); |
|
| 15 | + | let left = try! slots::commit(&mut table[..], first); |
|
| 16 | + | let right = try! slots::commit(&mut table[..], second); |
|
| 17 | + | try testing::expect(reserved and left.index == 0 and right.index == 1); |
|
| 18 | + | let mut exhausted = false; |
|
| 19 | + | try reserveAndCancel(&mut table[..]) catch err { |
|
| 20 | + | try testing::expect(err == abi::Error::Exhausted); set exhausted = true; |
|
| 21 | + | }; |
|
| 22 | + | try testing::expect(exhausted); |
|
| 23 | + | try! slots::release(&mut table[..], left); |
|
| 24 | + | try testing::expect(not slots::matches(&table[..], left, slots::State::Live)); |
|
| 25 | + | let replacement = try! slots::reserve(&mut table[..]); |
|
| 26 | + | let next = try! slots::commit(&mut table[..], replacement); |
|
| 27 | + | try testing::expect(next.index == left.index and next.generation == left.generation + 1); |
|
| 28 | + | let mut stale = false; |
|
| 29 | + | try slots::release(&mut table[..], left) catch err { |
|
| 30 | + | try testing::expect(err == abi::Error::BadHandle); set stale = true; |
|
| 31 | + | }; |
|
| 32 | + | try testing::expect(stale and slots::matches(&table[..], next, slots::State::Live)); |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | /// Cancellation invalidates references and exhausted generations never wrap. |
|
| 36 | + | @test unsafe fn cancellationAndRetirement() throws (testing::TestError) { |
|
| 37 | + | let mut table: [slots::Slot; 1] = undefined; |
|
| 38 | + | slots::initialize(&mut table[..]); |
|
| 39 | + | let token = try! slots::reserve(&mut table[..]); |
|
| 40 | + | let old = slots::reference(&token); |
|
| 41 | + | try! slots::cancel(&mut table[..], token); |
|
| 42 | + | try testing::expect(table[0].state == slots::State::Free and table[0].generation == old.generation + 1); |
|
| 43 | + | set table[0].generation = 0xffffffff; |
|
| 44 | + | let last = try! slots::reserve(&mut table[..]); |
|
| 45 | + | let object = try! slots::commit(&mut table[..], last); |
|
| 46 | + | try! slots::release(&mut table[..], object); |
|
| 47 | + | try testing::expect(table[0].state == slots::State::Retired and table[0].generation == 0xffffffff); |
|
| 48 | + | let mut failed = false; |
|
| 49 | + | try reserveAndCancel(&mut table[..]) catch err { |
|
| 50 | + | try testing::expect(err == abi::Error::Exhausted); set failed = true; |
|
| 51 | + | }; |
|
| 52 | + | try testing::expect(failed); |
|
| 53 | + | set table[0].state = slots::State::Free; |
|
| 54 | + | let cancelled = try! slots::reserve(&mut table[..]); |
|
| 55 | + | try! slots::cancel(&mut table[..], cancelled); |
|
| 56 | + | try testing::expect(table[0].state == slots::State::Retired); |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | /// Consume a reservation when a capacity check unexpectedly succeeds. |
|
| 60 | + | fn reserveAndCancel(table: &mut [slots::Slot]) throws (abi::Error) { |
|
| 61 | + | let token = try slots::reserve(table); |
|
| 62 | + | try slots::cancel(table, token); |
|
| 63 | + | } |
test/slots/abandon.rad
added
+11 -0
| 1 | + | //! Compile-failure fixture for an unconsumed reservation. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use kernel::slots; |
|
| 5 | + | ||
| 6 | + | /// A reservation must be consumed before its scope ends. |
|
| 7 | + | @test unsafe fn abandon() throws (testing::TestError) { |
|
| 8 | + | let mut table: [slots::Slot; 1] = undefined; |
|
| 9 | + | slots::initialize(&mut table[..]); |
|
| 10 | + | let token = try! slots::reserve(&mut table[..]); |
|
| 11 | + | } |
test/slots/duplicate.rad
added
+13 -0
| 1 | + | //! Compile-failure fixture for repeated reservation consumption. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use kernel::slots; |
|
| 5 | + | ||
| 6 | + | /// A reservation cannot be cancelled twice. |
|
| 7 | + | @test unsafe fn duplicate() throws (testing::TestError) { |
|
| 8 | + | let mut table: [slots::Slot; 1] = undefined; |
|
| 9 | + | slots::initialize(&mut table[..]); |
|
| 10 | + | let token = try! slots::reserve(&mut table[..]); |
|
| 11 | + | try! slots::cancel(&mut table[..], token); |
|
| 12 | + | try! slots::cancel(&mut table[..], token); |
|
| 13 | + | } |
test/slots/run
added
+25 -0
| 1 | + | #!/bin/sh |
|
| 2 | + | # Check that reservation misuse fails during compilation. |
|
| 3 | + | set -eu |
|
| 4 | + | emulator=$1 |
|
| 5 | + | shift |
|
| 6 | + | work=$(mktemp -d) |
|
| 7 | + | trap 'rm -rf "$work"' EXIT HUP INT TERM |
|
| 8 | + | for name in duplicate abandon; do |
|
| 9 | + | case "$name" in |
|
| 10 | + | duplicate) diagnostic='linear value used after consumption' ;; |
|
| 11 | + | abandon) diagnostic='linear value is not consumed' ;; |
|
| 12 | + | esac |
|
| 13 | + | if "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \ |
|
| 14 | + | -run bin/radiance.rv64.dev "$@" -test \ |
|
| 15 | + | -pkg "$name" -mod "test/slots/$name.rad" -entry "$name" \ |
|
| 16 | + | -o "$work/test.rv64" > "$work/log" 2>&1; then |
|
| 17 | + | printf 'reservation misuse compiled: %s\n' "$name" >&2 |
|
| 18 | + | exit 1 |
|
| 19 | + | fi |
|
| 20 | + | if ! grep -q "$diagnostic" "$work/log"; then |
|
| 21 | + | cat "$work/log" >&2 |
|
| 22 | + | exit 1 |
|
| 23 | + | fi |
|
| 24 | + | printf 'reservation rejection: %s passed\n' "$name" |
|
| 25 | + | done |