kernel: Register global packages
3d8abc9f3daccb5c40a83734ea3544635438bd6b44275a4d9269379482ae745c
Assisted-by: Codex:gpt-6
1 parent
1db0e99f
kernel/kernel.rad
+1 -0
| 11 | 11 | export mod platform; |
|
| 12 | 12 | export mod trap; |
|
| 13 | 13 | export mod capability; |
|
| 14 | 14 | export mod transactions; |
|
| 15 | 15 | export mod events; |
|
| 16 | + | export mod registry; |
|
| 16 | 17 | export mod frames; |
|
| 17 | 18 | export mod backing; |
|
| 18 | 19 | export mod pages; |
|
| 19 | 20 | export mod boot; |
|
| 20 | 21 | @test export mod tests; |
kernel/kernel/boot.rad
+14 -2
| 5 | 5 | use super::range; |
|
| 6 | 6 | use super::limits; |
|
| 7 | 7 | use super::sync; |
|
| 8 | 8 | use super::trap; |
|
| 9 | 9 | use super::pages; |
|
| 10 | + | use super::registry; |
|
| 11 | + | use std::arch::rv64::shared::catalog; |
|
| 10 | 12 | ||
| 11 | 13 | /// Platform data published by hart zero before secondary initialization. |
|
| 12 | 14 | export unsafe static PLATFORM: platform::Platform = undefined; |
|
| 13 | 15 | /// Release/acquire publication flag for PLATFORM. |
|
| 14 | 16 | static READY: u64 = 0; |
| 52 | 54 | } |
|
| 53 | 55 | } |
|
| 54 | 56 | ||
| 55 | 57 | /// Initialize one hart from firmware arguments. Return true for the last hart. |
|
| 56 | 58 | /// Firmware supplies a mapped FDT and a disjoint, reserved stack for each hart. |
|
| 57 | - | export unsafe fn enter(hart: u64, tree: *u8, stackTop: u64) -> bool { |
|
| 59 | + | export unsafe fn enter(hart: u64, tree: *u8, stackTop: u64, entries: *catalog::Entry, entryCount: u32, stateTable: *u64) -> bool { |
|
| 58 | 60 | let treeAddress = tree as u64; |
|
| 59 | 61 | assert hart < limits::HARTS as u64; |
|
| 60 | 62 | if hart == 0 { |
|
| 61 | 63 | assert (treeAddress & 7) == 0 and treeAddress <= 0xffffffffffffffff - 65536; |
|
| 62 | 64 | let prefix = @sliceOf(tree, 40); |
| 66 | 68 | try! platform::decode(&blob[..], &mut PLATFORM); |
|
| 67 | 69 | let treeRange = range::new(treeAddress, size as u64) else panic "FDT range"; |
|
| 68 | 70 | assert platform::inRam(&PLATFORM, treeRange); |
|
| 69 | 71 | try! platform::protect(&mut PLATFORM, treeRange); |
|
| 70 | 72 | try! pages::initialize(&mut pages::STORE, &PLATFORM); |
|
| 73 | + | assert entryCount == 2; |
|
| 74 | + | registry::initialize(&mut registry::STORE); |
|
| 75 | + | let bootCatalog = @sliceOf(entries, entryCount); |
|
| 76 | + | try! registry::boot(&mut registry::STORE, &bootCatalog[..]); |
|
| 77 | + | let library = registry::find(®istry::STORE, &"std"[..]) else panic "boot std"; |
|
| 78 | + | let kernel = registry::find(®istry::STORE, &"kernel"[..]) else panic "boot kernel"; |
|
| 79 | + | assert library.index == 0 and kernel.index == 1; |
|
| 80 | + | let bases = @sliceOf(stateTable, limits::PACKAGES); |
|
| 81 | + | assert bases[library.index] <> 0 and bases[kernel.index] <> 0; |
|
| 82 | + | print("kernel: boot catalog ready\n"); |
|
| 71 | 83 | print("kernel: platform ready\n"); |
|
| 72 | 84 | sync::storeRelease(&mut READY, 1); |
|
| 73 | 85 | } else { |
|
| 74 | 86 | while sync::loadAcquire(&READY) == 0 { |
|
| 75 | 87 | } |
|
| 76 | 88 | } |
|
| 77 | 89 | assert (PLATFORM.harts & (1 << hart as u32)) <> 0; |
|
| 78 | 90 | assert stackTop == PLATFORM.stacks[hart as u32].end and (stackTop & 15) == 0; |
|
| 79 | 91 | let stack = PLATFORM.stacks[hart as u32]; |
|
| 80 | 92 | set HARTS[hart as u32] = trap::Hart { |
|
| 81 | - | stackTop: stack.end, stackBottom: stack.start, kernelGp: 0, handler: unexpected, |
|
| 93 | + | stackTop: stack.end, stackBottom: stack.start, kernelGp: stateTable as u64, handler: unexpected, |
|
| 82 | 94 | savedT0: 0, savedT1: 0, savedSp: 0, |
|
| 83 | 95 | }; |
|
| 84 | 96 | trap::install(&mut HARTS[hart as u32]); |
|
| 85 | 97 | let mut count: u64 = 0; |
|
| 86 | 98 | for id in 0..limits::HARTS { |
kernel/kernel/boot.ras
+1 -0
| 15 | 15 | @kernel::boot::initialize |
|
| 16 | 16 | csrw mie %zero; |
|
| 17 | 17 | csrw mstatus %zero; |
|
| 18 | 18 | csrw mscratch %sp; |
|
| 19 | 19 | mv %a2 %sp; |
|
| 20 | + | mv %a5 %gp; |
|
| 20 | 21 | addi %sp %sp -16; |
|
| 21 | 22 | sd %ra 0(%sp); |
|
| 22 | 23 | la %t0 @kernel::boot::enter; |
|
| 23 | 24 | jalr %ra %t0 0; |
|
| 24 | 25 | ld %ra 0(%sp); |
kernel/kernel/registry.rad
added
+177 -0
| 1 | + | //! Immutable resident packages shared by boot and runtime loading. |
|
| 2 | + | ||
| 3 | + | use std::mem; |
|
| 4 | + | use std::arch::rv64::shared; |
|
| 5 | + | use std::arch::rv64::shared::catalog; |
|
| 6 | + | use super::abi; |
|
| 7 | + | use super::limits; |
|
| 8 | + | use super::slots; |
|
| 9 | + | use super::capability; |
|
| 10 | + | ||
| 11 | + | /// Rights installed on a new Image capability. |
|
| 12 | + | export constant IMAGE_RIGHTS: u16 = abi::READ | abi::EXECUTE | abi::GRANT | abi::TRANSFER; |
|
| 13 | + | ||
| 14 | + | /// Fixed package registry. Callers serialize admission and handle publication. |
|
| 15 | + | export record Store: Copy { |
|
| 16 | + | /// Package generations; published slots remain live until shutdown. |
|
| 17 | + | slots: [slots::Slot; limits::PACKAGES], |
|
| 18 | + | /// Descriptors whose backing storage remains resident until shutdown. |
|
| 19 | + | entries: [catalog::Entry; limits::PACKAGES], |
|
| 20 | + | /// Resolved dependencies in each package's declared order. |
|
| 21 | + | dependencies: [[abi::Ref; limits::PACKAGES]; limits::PACKAGES], |
|
| 22 | + | /// Number of valid resolved dependencies per live package. |
|
| 23 | + | counts: [u32; limits::PACKAGES], |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// Global resident package registry shared by all domains. |
|
| 27 | + | export unsafe static STORE: Store = undefined; |
|
| 28 | + | ||
| 29 | + | /// Initialize an empty registry before loading any native package. |
|
| 30 | + | export fn initialize(store: &mut Store) { slots::initialize(&mut store.slots[..]); } |
|
| 31 | + | ||
| 32 | + | /// Find a resident package by its immutable name. |
|
| 33 | + | export fn find(store: &Store, name: &[u8]) -> ?abi::Ref { |
|
| 34 | + | for i in 0..limits::PACKAGES { |
|
| 35 | + | if store.slots[i].state <> slots::State::Live { |
|
| 36 | + | continue; |
|
| 37 | + | } |
|
| 38 | + | if mem::eq(store.entries[i].package.name, &name[..]) { |
|
| 39 | + | return abi::Ref { index: i, generation: store.slots[i].generation }; |
|
| 40 | + | } |
|
| 41 | + | } |
|
| 42 | + | return nil; |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | /// Reuse identical content and reject conflicting content under a resident name. |
|
| 46 | + | export fn identify(store: &Store, name: &[u8], source: &[u8]) -> ?abi::Ref throws (abi::Error) { |
|
| 47 | + | let object = find(store, name) else { |
|
| 48 | + | return nil; |
|
| 49 | + | }; |
|
| 50 | + | if not mem::eq(store.entries[object.index].source, &source[..]) { |
|
| 51 | + | throw abi::Error::VerifyFailed; |
|
| 52 | + | } |
|
| 53 | + | return object; |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// Reserve the stable package-state slot before compiling its native code. |
|
| 57 | + | export fn reserve(store: &mut Store) -> slots::Reservation throws (abi::Error) { |
|
| 58 | + | return try slots::reserve(&mut store.slots[..]); |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | /// Return unpublished capacity after a failed load. |
|
| 62 | + | export fn cancel(store: &mut Store, reservation: slots::Reservation) { |
|
| 63 | + | try! slots::cancel(&mut store.slots[..], reservation); |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Validate dependencies without changing registry payloads. |
|
| 67 | + | unsafe fn validate(store: &Store, object: abi::Ref, package: &shared::Package) throws (abi::Error) { |
|
| 68 | + | if package.name.len == 0 or package.slot <> object.index or package.dependencies.len > limits::PACKAGES { |
|
| 69 | + | throw abi::Error::VerifyFailed; |
|
| 70 | + | } |
|
| 71 | + | for dependency, i in package.dependencies { |
|
| 72 | + | let target = find(store, &dependency[..]) else { |
|
| 73 | + | throw abi::Error::VerifyFailed; |
|
| 74 | + | }; |
|
| 75 | + | for prior in &package.dependencies[..i] { |
|
| 76 | + | if mem::eq(prior, dependency) { |
|
| 77 | + | throw abi::Error::VerifyFailed; |
|
| 78 | + | } |
|
| 79 | + | } |
|
| 80 | + | } |
|
| 81 | + | } |
|
| 82 | + | ||
| 83 | + | /// Publish persistent compiler output after successful admission checks. |
|
| 84 | + | /// All descriptor pointers and source bytes must remain immutable and resident. |
|
| 85 | + | /// Failure cancels the reservation; duplicate content returns its existing slot. |
|
| 86 | + | export unsafe fn publish(store: &mut Store, reservation: slots::Reservation, source: *[u8], package: shared::Package) |
|
| 87 | + | -> abi::Ref throws (abi::Error) |
|
| 88 | + | { |
|
| 89 | + | let existing = try identify(store, &package.name[..], &source[..]) catch err { |
|
| 90 | + | cancel(store, reservation); throw err; |
|
| 91 | + | }; |
|
| 92 | + | if let object = existing { |
|
| 93 | + | cancel(store, reservation); |
|
| 94 | + | return object; |
|
| 95 | + | } |
|
| 96 | + | let object = slots::reference(&reservation); |
|
| 97 | + | try validate(store, object, &package) catch err { |
|
| 98 | + | cancel(store, reservation); |
|
| 99 | + | throw err; |
|
| 100 | + | }; |
|
| 101 | + | for dependency, i in package.dependencies { |
|
| 102 | + | let target = find(store, &dependency[..]) else panic "resident dependency"; |
|
| 103 | + | set store.dependencies[object.index][i] = target; |
|
| 104 | + | } |
|
| 105 | + | set store.counts[object.index] = package.dependencies.len; |
|
| 106 | + | set store.entries[object.index] = catalog::Entry { source, package }; |
|
| 107 | + | return try! slots::commit(&mut store.slots[..], reservation); |
|
| 108 | + | } |
|
| 109 | + | ||
| 110 | + | /// Read resident metadata through a generation-bearing package reference. |
|
| 111 | + | export fn get(store: &Store, object: abi::Ref) -> shared::Package throws (abi::Error) { |
|
| 112 | + | if not slots::matches(&store.slots[..], object, slots::State::Live) { |
|
| 113 | + | throw abi::Error::BadHandle; |
|
| 114 | + | } |
|
| 115 | + | return store.entries[object.index].package; |
|
| 116 | + | } |
|
| 117 | + | ||
| 118 | + | /// Resolve one public symbol without searching packages outside the named image. |
|
| 119 | + | export fn exported(store: &Store, object: abi::Ref, name: &[u8]) -> shared::Target throws (abi::Error) { |
|
| 120 | + | let package = try get(store, object); |
|
| 121 | + | let target = shared::lookup(package.exports, &name[..]) else { |
|
| 122 | + | throw abi::Error::VerifyFailed; |
|
| 123 | + | }; |
|
| 124 | + | return target; |
|
| 125 | + | } |
|
| 126 | + | ||
| 127 | + | /// Collect exports from declared dependencies into caller-owned compiler storage. |
|
| 128 | + | export fn imports(store: &Store, dependencies: &[*[u8]], output: &mut [shared::Symbol]) -> u32 throws (abi::Error) { |
|
| 129 | + | let mut count: u32 = 0; |
|
| 130 | + | for name in dependencies { |
|
| 131 | + | let object = find(store, &name[..]) else { |
|
| 132 | + | throw abi::Error::VerifyFailed; |
|
| 133 | + | }; |
|
| 134 | + | let package = try! get(store, object); |
|
| 135 | + | if package.exports.len > output.len - count { |
|
| 136 | + | throw abi::Error::Exhausted; |
|
| 137 | + | } |
|
| 138 | + | set count += package.exports.len; |
|
| 139 | + | } |
|
| 140 | + | set count = 0; |
|
| 141 | + | for name in dependencies { |
|
| 142 | + | let object = find(store, &name[..]) else panic "resident dependency"; |
|
| 143 | + | let package = try! get(store, object); |
|
| 144 | + | for symbol in package.exports { |
|
| 145 | + | set output[count] = symbol; |
|
| 146 | + | set count += 1; |
|
| 147 | + | } |
|
| 148 | + | } |
|
| 149 | + | return count; |
|
| 150 | + | } |
|
| 151 | + | ||
| 152 | + | /// Register a persistent boot catalog in dependency order through normal admission. |
|
| 153 | + | export unsafe fn boot(store: &mut Store, catalog: &[catalog::Entry]) throws (abi::Error) { |
|
| 154 | + | for entry in catalog { |
|
| 155 | + | let existing = try identify(store, &entry.package.name[..], &entry.source[..]); |
|
| 156 | + | if existing <> nil { |
|
| 157 | + | continue; |
|
| 158 | + | } |
|
| 159 | + | let reservation = try reserve(store); |
|
| 160 | + | let object = try publish(store, reservation, entry.source, entry.package); |
|
| 161 | + | } |
|
| 162 | + | } |
|
| 163 | + | ||
| 164 | + | /// Install an Image handle for a resident package without duplicating its code. |
|
| 165 | + | export fn install(store: &Store, table: &mut capability::Table, object: abi::Ref) -> abi::Handle throws (abi::Error) { |
|
| 166 | + | let package = try get(store, object); |
|
| 167 | + | return try capability::install(table, capability::Entry { |
|
| 168 | + | kind: abi::Kind::Image, object, rights: abi::Rights(IMAGE_RIGHTS), |
|
| 169 | + | }); |
|
| 170 | + | } |
|
| 171 | + | ||
| 172 | + | /// Resolve image authority and validate its resident package generation. |
|
| 173 | + | export fn image(store: &Store, table: &capability::Table, handle: abi::Handle, rights: abi::Rights) -> abi::Ref throws (abi::Error) { |
|
| 174 | + | let entry = try capability::lookup(table, handle, abi::Kind::Image, rights); |
|
| 175 | + | let package = try get(store, entry.object); |
|
| 176 | + | return entry.object; |
|
| 177 | + | } |
kernel/kernel/tests.rad
+1 -0
| 10 | 10 | export mod frames; |
|
| 11 | 11 | export mod backing; |
|
| 12 | 12 | export mod pages; |
|
| 13 | 13 | export mod transactions; |
|
| 14 | 14 | export mod events; |
|
| 15 | + | export mod registry; |
kernel/kernel/tests/registry.rad
added
+139 -0
| 1 | + | //! Resident package identity, dependency admission, and image capabilities. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use std::arch::rv64::shared; |
|
| 5 | + | use std::arch::rv64::shared::catalog; |
|
| 6 | + | use kernel::abi; |
|
| 7 | + | use kernel::slots; |
|
| 8 | + | use kernel::capability; |
|
| 9 | + | use kernel::registry; |
|
| 10 | + | ||
| 11 | + | /// Resident registry metadata workspace. |
|
| 12 | + | unsafe static STORE: registry::Store = undefined; |
|
| 13 | + | /// Persistent distinct names for complete registry exhaustion. |
|
| 14 | + | unsafe static NAMES: [[u8; 4]; 256] = undefined; |
|
| 15 | + | ||
| 16 | + | /// Construct persistent compiled metadata for registry contract tests. |
|
| 17 | + | fn package(name: *[u8], dependencies: *unsafe [*[u8]], slot: u32) -> shared::Package { |
|
| 18 | + | return shared::Package { |
|
| 19 | + | name, dependencies, slot, codeAddress: 0x80000000 + slot as u64 * 4096, |
|
| 20 | + | code: &[0x00008067 as u32], exports: &[], entry: nil, |
|
| 21 | + | template: &[], memory: 0, alignment: 8, relocations: &[], |
|
| 22 | + | }; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// Admit persistent package bytes and metadata through a reserved registry slot. |
|
| 26 | + | unsafe fn register(name: *[u8], dependencies: *unsafe [*[u8]], bytes: *[u8]) -> abi::Ref throws (abi::Error) { |
|
| 27 | + | let pending = try registry::reserve(&mut STORE); |
|
| 28 | + | let slot = slots::reference(&pending); |
|
| 29 | + | return try registry::publish(&mut STORE, pending, bytes, package(name, dependencies, slot.index)); |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | /// Identical bytes reuse one package; a name cannot identify different content. |
|
| 33 | + | @test unsafe fn identity() throws (testing::TestError) { |
|
| 34 | + | registry::initialize(&mut STORE); |
|
| 35 | + | let first = try! register("a", &[], "binary a"); |
|
| 36 | + | let second = try! register("a", &[], "binary a"); |
|
| 37 | + | try testing::expect(first == second and STORE.slots[1].state == slots::State::Free); |
|
| 38 | + | let found = try! registry::identify(&STORE, &"a"[..], &"binary a"[..]); |
|
| 39 | + | try testing::expect(found == first); |
|
| 40 | + | let mut conflict = false; |
|
| 41 | + | try register("a", &[], "different") catch err { |
|
| 42 | + | try testing::expect(err == abi::Error::VerifyFailed); set conflict = true; |
|
| 43 | + | }; |
|
| 44 | + | try testing::expect(conflict and STORE.slots[1].state == slots::State::Free); |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | /// Dependencies must already be resident and retain shared identity in a diamond. |
|
| 48 | + | @test unsafe fn dependencyGraph() throws (testing::TestError) { |
|
| 49 | + | registry::initialize(&mut STORE); |
|
| 50 | + | let mut missing = false; |
|
| 51 | + | try register("left", &["base"], "left") catch err { |
|
| 52 | + | try testing::expect(err == abi::Error::VerifyFailed); set missing = true; |
|
| 53 | + | }; |
|
| 54 | + | try testing::expect(missing and STORE.slots[0].state == slots::State::Free); |
|
| 55 | + | let base = try! register("base", &[], "base"); |
|
| 56 | + | let left = try! register("left", &["base"], "left"); |
|
| 57 | + | let right = try! register("right", &["base"], "right"); |
|
| 58 | + | let root = try! register("root", &["left", "right"], "root"); |
|
| 59 | + | try testing::expect(STORE.dependencies[left.index][0] == base and STORE.dependencies[right.index][0] == base); |
|
| 60 | + | try testing::expect(STORE.dependencies[root.index][0] == left and STORE.dependencies[root.index][1] == right); |
|
| 61 | + | } |
|
| 62 | + | ||
| 63 | + | /// Image handles name resident packages and dropping a handle does not unload code. |
|
| 64 | + | @test unsafe fn images() throws (testing::TestError) { |
|
| 65 | + | registry::initialize(&mut STORE); |
|
| 66 | + | let object = try! register("image", &[], "image"); |
|
| 67 | + | let mut table: capability::Table = undefined; |
|
| 68 | + | capability::initialize(&mut table, abi::Ref { index: 0, generation: 1 }); |
|
| 69 | + | let handle = try! registry::install(&STORE, &mut table, object); |
|
| 70 | + | let found = try! registry::image(&STORE, &table, handle, abi::Rights(abi::EXECUTE)); |
|
| 71 | + | try testing::expect(found == object); |
|
| 72 | + | let removed = try! capability::invalidate(&mut table, handle); |
|
| 73 | + | let resident = try! registry::get(&STORE, object); |
|
| 74 | + | try testing::expect(resident.slot == object.index); |
|
| 75 | + | let mut stale = false; |
|
| 76 | + | try registry::image(&STORE, &table, handle, abi::Rights(0)) catch err { |
|
| 77 | + | try testing::expect(err == abi::Error::BadHandle); set stale = true; |
|
| 78 | + | }; |
|
| 79 | + | try testing::expect(stale); |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | /// Boot admission uses the same identity and dependency records as runtime publication. |
|
| 83 | + | @test unsafe fn bootCatalog() throws (testing::TestError) { |
|
| 84 | + | registry::initialize(&mut STORE); |
|
| 85 | + | let catalog = [ |
|
| 86 | + | catalog::Entry { source: "base", package: package("base", &[], 0) }, |
|
| 87 | + | catalog::Entry { source: "app", package: package("app", &["base"], 1) }, |
|
| 88 | + | ]; |
|
| 89 | + | try! registry::boot(&mut STORE, &catalog[..]); |
|
| 90 | + | try! registry::boot(&mut STORE, &catalog[..]); |
|
| 91 | + | try testing::expect(STORE.counts[1] == 1 and STORE.dependencies[1][0].index == 0); |
|
| 92 | + | try testing::expect(STORE.slots[2].state == slots::State::Free); |
|
| 93 | + | } |
|
| 94 | + | ||
| 95 | + | /// Export lookup and dependency import collection preserve code and private-data targets. |
|
| 96 | + | @test unsafe fn exports() throws (testing::TestError) { |
|
| 97 | + | registry::initialize(&mut STORE); |
|
| 98 | + | let pending = try! registry::reserve(&mut STORE); |
|
| 99 | + | let mut native = package("base", &[], 0); |
|
| 100 | + | set native.exports = &[ |
|
| 101 | + | shared::Symbol { name: "base::run", target: shared::Target::Function(0x80000000) }, |
|
| 102 | + | shared::Symbol { name: "base::state", target: shared::Target::Data(shared::DataRef { slot: 0, offset: 8 }) }, |
|
| 103 | + | ]; |
|
| 104 | + | let object = try! registry::publish(&mut STORE, pending, "base", native); |
|
| 105 | + | let target = try! registry::exported(&STORE, object, &"base::run"[..]); |
|
| 106 | + | try testing::expect(target == shared::Target::Function(0x80000000)); |
|
| 107 | + | let mut output: [shared::Symbol; 2] = undefined; |
|
| 108 | + | let count = try! registry::imports(&STORE, &["base"], &mut output[..]); |
|
| 109 | + | try testing::expect(count == 2 and output[1].target == shared::Target::Data(shared::DataRef { slot: 0, offset: 8 })); |
|
| 110 | + | let saved = output[0]; |
|
| 111 | + | let mut failures: u32 = 0; |
|
| 112 | + | try registry::imports(&STORE, &["base"], &mut output[..1]) catch err { |
|
| 113 | + | try testing::expect(err == abi::Error::Exhausted); set failures += 1; |
|
| 114 | + | }; |
|
| 115 | + | try registry::exported(&STORE, object, &"base::private"[..]) catch err { |
|
| 116 | + | try testing::expect(err == abi::Error::VerifyFailed); set failures += 1; |
|
| 117 | + | }; |
|
| 118 | + | try registry::imports(&STORE, &["absent"], &mut output[..]) catch err { |
|
| 119 | + | try testing::expect(err == abi::Error::VerifyFailed); set failures += 1; |
|
| 120 | + | }; |
|
| 121 | + | try testing::expect(failures == 3 and output[0] == saved); |
|
| 122 | + | } |
|
| 123 | + | ||
| 124 | + | /// Full registry exhaustion leaves existing packages available for duplicate lookup. |
|
| 125 | + | @test unsafe fn capacity() throws (testing::TestError) { |
|
| 126 | + | registry::initialize(&mut STORE); |
|
| 127 | + | for i in 0..256 { |
|
| 128 | + | set NAMES[i] = [112 as u8, (48 + i / 100) as u8, (48 + (i / 10) % 10) as u8, (48 + i % 10) as u8]; |
|
| 129 | + | let object = try! register(&NAMES[i][..], &[], &NAMES[i][..]); |
|
| 130 | + | try testing::expect(object.index == i); |
|
| 131 | + | } |
|
| 132 | + | let mut full = false; |
|
| 133 | + | try register("extra", &[], "extra") catch err { |
|
| 134 | + | try testing::expect(err == abi::Error::Exhausted); set full = true; |
|
| 135 | + | }; |
|
| 136 | + | try testing::expect(full); |
|
| 137 | + | let found = try! registry::identify(&STORE, &NAMES[255][..], &NAMES[255][..]) else panic "resident package"; |
|
| 138 | + | try testing::expect(found.index == 255); |
|
| 139 | + | } |
kernel/tools/build.rad
+138 -84
| 1 | - | //! Build a physical kernel image from trusted binary RIL and startup assembly. |
|
| 1 | + | //! Build a native kernel image with shared packages and a persistent boot catalog. |
|
| 2 | 2 | ||
| 3 | 3 | use std::sys; |
|
| 4 | 4 | use std::io; |
|
| 5 | 5 | use std::mem; |
|
| 6 | - | use std::lang::il; |
|
| 7 | 6 | use std::sys::unix; |
|
| 8 | 7 | use std::lang::alloc; |
|
| 9 | 8 | use std::lang::strings; |
|
| 10 | 9 | use std::lang::il::binary; |
|
| 11 | 10 | use std::lang::il::binary::program; |
|
| 12 | 11 | use std::lang::gen::data; |
|
| 13 | - | use std::collections::dict; |
|
| 14 | 12 | use std::arch::rv64; |
|
| 15 | 13 | use std::arch::rv64::asm; |
|
| 14 | + | use std::arch::rv64::emit; |
|
| 15 | + | use std::arch::rv64::encode; |
|
| 16 | 16 | use std::arch::rv64::image; |
|
| 17 | + | use std::arch::rv64::shared; |
|
| 18 | + | use std::arch::rv64::shared::catalog; |
|
| 17 | 19 | ||
| 18 | - | /// Code segment base for the native platform profile. |
|
| 20 | + | /// Native entry page, above the platform firmware data. |
|
| 19 | 21 | constant CODE_ADDRESS: u64 = 0x81000000; |
|
| 20 | - | ||
| 21 | - | /// Persistent native code-generation workspace. |
|
| 22 | - | static CODE: [u8; 16777216] = [0; 16777216]; |
|
| 22 | + | /// Package code arenas retained until image output completes. |
|
| 23 | + | unsafe static CODE: [[u8; 16777216]; 2] = undefined; |
|
| 23 | 24 | /// Reusable function workspace. |
|
| 24 | 25 | static SCRATCH: [u8; 16777216] = [0; 16777216]; |
|
| 25 | - | /// Decoded package storage. |
|
| 26 | + | /// Persistent decoded RIL storage. |
|
| 26 | 27 | static DECODE: [u8; 67108864] = [0; 67108864]; |
|
| 27 | - | /// Binary package input. |
|
| 28 | - | static INPUT: [u8; 8388608] = [0; 8388608]; |
|
| 29 | - | /// Combined package data descriptors. |
|
| 30 | - | unsafe static GLOBALS: [il::Data; 4096] = undefined; |
|
| 31 | - | /// Combined startup and boundary assembly. |
|
| 28 | + | /// Exact package bytes retained in the native catalog. |
|
| 29 | + | unsafe static INPUT: [[u8; 8388608]; 2] = undefined; |
|
| 30 | + | /// Combined startup and boundary assembly source. |
|
| 32 | 31 | static SOURCE: [u8; 65536] = [0; 65536]; |
|
| 33 | 32 | /// Assembler workspace. |
|
| 34 | 33 | static ASSEMBLY: [u8; 4194304] = [0; 4194304]; |
|
| 35 | 34 | /// Assembled startup words. |
|
| 36 | 35 | static TEXT: [u32; 16384] = [0; 16384]; |
|
| 37 | - | /// Assembly identifiers. |
|
| 36 | + | /// Assembly identifiers retained through package linking. |
|
| 38 | 37 | unsafe static STRINGS: strings::Pool = strings::Pool { table: undefined, count: 0 }; |
|
| 39 | - | /// Data symbol placement workspace. |
|
| 40 | - | unsafe static SYMBOLS: [data::DataSym; 4096] = undefined; |
|
| 41 | - | /// Data name lookup workspace. |
|
| 42 | - | unsafe static ENTRIES: [dict::Entry; data::DATA_SYM_TABLE_SIZE] = undefined; |
|
| 43 | - | /// Initialized read-only bytes. |
|
| 44 | - | static RO: [u8; 1048576] = [0; 1048576]; |
|
| 45 | - | /// Initialized writable bytes. |
|
| 46 | - | static RW: [u8; 1048576] = [0; 1048576]; |
|
| 38 | + | /// Per-package data layout workspaces. |
|
| 39 | + | unsafe static DATA: [[data::DataSym; 4096]; 2] = undefined; |
|
| 40 | + | /// Local function and data definitions. |
|
| 41 | + | unsafe static SYMBOLS: [[shared::Symbol; 4096]; 2] = undefined; |
|
| 42 | + | /// Public package symbols. |
|
| 43 | + | unsafe static EXPORTS: [[shared::Symbol; 4096]; 2] = undefined; |
|
| 44 | + | /// Private-state relocation records. |
|
| 45 | + | unsafe static RELOCS: [[shared::Relocation; 4096]; 2] = undefined; |
|
| 46 | + | /// Initialized package templates. |
|
| 47 | + | unsafe static TEMPLATES: [[u8; 1048576]; 2] = undefined; |
|
| 48 | + | /// Read-only catalog and retained package payloads. |
|
| 49 | + | static RO: [u8; 16777216] = [0; 16777216]; |
|
| 50 | + | /// Kernel state table and initialized private package graph. |
|
| 51 | + | static RW: [u8; 33554432] = [0; 33554432]; |
|
| 52 | + | /// Contiguous native code segment, including entry trampoline and alignment padding. |
|
| 53 | + | static NATIVE: [u32; 4194304] = [0; 4194304]; |
|
| 47 | 54 | ||
| 48 | - | /// Decode one package into persistent storage shared by the native link. |
|
| 49 | - | unsafe fn load(path: &[u8], arena: &mut alloc::Arena) -> binary::Package { |
|
| 50 | - | let inputLength = unix::readFile(path, &mut INPUT[..]) else panic "boot RIL"; |
|
| 51 | - | return try! program::decode(&INPUT[..inputLength], arena, binary::Limits { registers: 8192, blocks: 4096 }); |
|
| 55 | + | /// Round an extent up to a power-of-two alignment. |
|
| 56 | + | fn aligned(value: u64, alignment: u32) -> u64 { |
|
| 57 | + | assert alignment > 0 and (alignment & (alignment - 1)) == 0; |
|
| 58 | + | assert value <= 0xffffffffffffffff - alignment as u64 + 1; |
|
| 59 | + | return (value + alignment as u64 - 1) & ~(alignment as u64 - 1); |
|
| 52 | 60 | } |
|
| 53 | 61 | ||
| 54 | - | /// Print the bounded backend failure reported by a native build. |
|
| 55 | - | fn report(error: rv64::Error) { |
|
| 62 | + | /// Print a bounded shared-backend failure class. |
|
| 63 | + | fn report(error: shared::Error) { |
|
| 56 | 64 | match error { |
|
| 57 | - | case rv64::Error::Allocation => io::printLn("native build: allocation exhausted"), |
|
| 58 | - | case rv64::Error::Capacity => io::printLn("native build: output capacity exhausted"), |
|
| 59 | - | case rv64::Error::Symbol => io::printLn("native build: unresolved symbol"), |
|
| 60 | - | case rv64::Error::Relocation => io::printLn("native build: relocation out of range"), |
|
| 61 | - | case rv64::Error::Image(_) => io::printLn("native build: invalid image layout"), |
|
| 62 | - | case rv64::Error::Data(_) => io::printLn("native build: invalid data layout"), |
|
| 65 | + | case shared::Error::Codegen(_) => io::printLn("native build: code generation failed"), |
|
| 66 | + | case shared::Error::Capacity => io::printLn("native build: output capacity exhausted"), |
|
| 67 | + | case shared::Error::Symbol => io::printLn("native build: unresolved or duplicate symbol"), |
|
| 68 | + | case shared::Error::Range => io::printLn("native build: address out of range"), |
|
| 69 | + | case shared::Error::Alignment => io::printLn("native build: invalid alignment"), |
|
| 70 | + | case shared::Error::Instance => io::printLn("native build: missing package instance"), |
|
| 63 | 71 | } |
|
| 64 | 72 | } |
|
| 65 | 73 | ||
| 66 | - | /// Assemble entry code and lower the kernel at explicit physical addresses. |
|
| 74 | + | /// Compile one boot package into resident code and private-state metadata. |
|
| 75 | + | unsafe fn compile(input: &binary::Package, slot: u32, address: u64, imports: &[shared::Symbol], assembly: asm::Program) |
|
| 76 | + | -> shared::Package throws (shared::Error) |
|
| 77 | + | { |
|
| 78 | + | let mut arena = alloc::new(&mut CODE[slot][..]); |
|
| 79 | + | let mut scratch = alloc::new(&mut SCRATCH[..]); |
|
| 80 | + | return try shared::compileAssembly(shared::AssemblyInput { package: input as *unsafe binary::Package, assembly }, slot, address, imports, |
|
| 81 | + | shared::Storage { |
|
| 82 | + | data: &mut DATA[slot][..], symbols: &mut SYMBOLS[slot][..], exports: &mut EXPORTS[slot][..], |
|
| 83 | + | template: &mut TEMPLATES[slot][..], relocations: &mut RELOCS[slot][..], |
|
| 84 | + | }, &mut arena, &mut scratch); |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | /// Emit a trampoline that preserves firmware arguments and supplies the native catalog. |
|
| 88 | + | unsafe fn trampoline(kernel: u64, ro: u64, rw: u64) { |
|
| 89 | + | let mut arena = alloc::new(&mut SCRATCH[..]); |
|
| 90 | + | let mut e = try! emit::emitter(&mut arena, false); |
|
| 91 | + | emit::loadImm(&mut e, rv64::GP, rw as i64); |
|
| 92 | + | emit::loadImm(&mut e, rv64::A3, ro as i64); |
|
| 93 | + | emit::loadImm(&mut e, rv64::A4, 2); |
|
| 94 | + | emit::loadImm(&mut e, rv64::T0, kernel as i64); |
|
| 95 | + | emit::emit(&mut e, encode::jalr(rv64::ZERO, rv64::T0, 0)); |
|
| 96 | + | assert e.codeLen <= 1024; |
|
| 97 | + | for word, i in emit::getCode(&e) { |
|
| 98 | + | set NATIVE[i] = word; |
|
| 99 | + | } |
|
| 100 | + | } |
|
| 101 | + | ||
| 102 | + | /// Link shared boot packages, retain their catalog, and instantiate the kernel graph. |
|
| 67 | 103 | @default unsafe fn main(env: *sys::Env) -> i32 { |
|
| 68 | 104 | assert env.args.len == 5; |
|
| 69 | 105 | let mut decoder = alloc::new(&mut DECODE[..]); |
|
| 70 | - | let library = load(env.args[1], &mut decoder); |
|
| 71 | - | let kernel = load(env.args[2], &mut decoder); |
|
| 72 | - | assert mem::eq(library.name, "std") and mem::eq(kernel.name, "kernel"); |
|
| 73 | - | assert library.dependencies.len == 0; |
|
| 74 | - | for dependency in kernel.dependencies { |
|
| 106 | + | let libraryBytesLength = unix::readFile(env.args[1], &mut INPUT[0][..]) else panic "std RIL"; |
|
| 107 | + | let libraryBytes = &INPUT[0][..libraryBytesLength]; |
|
| 108 | + | let libraryInput = try! program::decode(libraryBytes, &mut decoder, binary::Limits { registers: 8192, blocks: 4096 }); |
|
| 109 | + | let kernelBytesLength = unix::readFile(env.args[2], &mut INPUT[1][..]) else panic "kernel RIL"; |
|
| 110 | + | let kernelBytes = &INPUT[1][..kernelBytesLength]; |
|
| 111 | + | let kernelInput = try! program::decode(kernelBytes, &mut decoder, binary::Limits { registers: 8192, blocks: 4096 }); |
|
| 112 | + | assert mem::eq(libraryInput.name, "std") and mem::eq(kernelInput.name, "kernel"); |
|
| 113 | + | assert libraryInput.dependencies.len == 0; |
|
| 114 | + | for dependency in kernelInput.dependencies { |
|
| 75 | 115 | assert mem::eq(dependency, "std"); |
|
| 76 | 116 | } |
|
| 77 | 117 | let sourceLength = unix::readFile(env.args[3], &mut SOURCE[..]) else panic "kernel assembly"; |
|
| 78 | - | let mut assembly = alloc::new(&mut ASSEMBLY[..]); |
|
| 118 | + | let mut assemblyArena = alloc::new(&mut ASSEMBLY[..]); |
|
| 79 | 119 | let empty: *mut [u8] = &mut []; |
|
| 80 | - | let startup = try! asm::assemble(asm::scanner::SourceKind::String, &SOURCE[..sourceLength], |
|
| 81 | - | &mut TEXT[..], &mut empty[..], &mut assembly, &mut STRINGS, 0); |
|
| 82 | - | let mut arena = alloc::new(&mut CODE[..]); |
|
| 83 | - | let mut scratch = alloc::new(&mut SCRATCH[..]); |
|
| 84 | - | let mut generator = try! rv64::beginProgram(rv64::ProgramOptions { |
|
| 85 | - | entryPatch: rv64::EntryPatch::None, debug: false, |
|
| 86 | - | placement: image::Placement::Physical { |
|
| 87 | - | code: CODE_ADDRESS, roData: 0, rwData: 0, entry: CODE_ADDRESS, |
|
| 88 | - | }, |
|
| 89 | - | }, &mut arena); |
|
| 90 | - | rv64::addAssembly(&mut generator, startup); |
|
| 91 | - | let mut dataCount: u32 = 0; |
|
| 92 | - | for package in &[library, kernel] { |
|
| 93 | - | for item in package.program.data { |
|
| 94 | - | assert dataCount < GLOBALS.len; |
|
| 95 | - | set GLOBALS[dataCount] = item; set dataCount += 1; |
|
| 96 | - | } |
|
| 97 | - | for func in package.program.fns { |
|
| 98 | - | rv64::generateFunction(&mut generator, func, &mut scratch); |
|
| 99 | - | if let error = generator.e.error { |
|
| 100 | - | report(error); io::printLn(func.name); return 1; |
|
| 101 | - | } |
|
| 102 | - | alloc::reset(&mut scratch); |
|
| 103 | - | } |
|
| 104 | - | } |
|
| 105 | - | for call in &generator.e.pendingCalls[..] { |
|
| 106 | - | if dict::get(&generator.e.labels.funcs, call.target) == nil { |
|
| 107 | - | io::print("undefined kernel function: "); io::printLn(call.target); return 1; |
|
| 108 | - | } |
|
| 109 | - | } |
|
| 110 | - | let roAddress = (CODE_ADDRESS + generator.e.codeLen as u64 * 4 + 4095) & ~4095; |
|
| 111 | - | let mut symbols: u32 = 0; |
|
| 112 | - | let roSize = try! data::layoutSection(&GLOBALS[..dataCount], &mut SYMBOLS[..], &mut symbols, roAddress, true); |
|
| 113 | - | let rwAddress = (roAddress + roSize as u64 + 4095) & ~4095; |
|
| 114 | - | set generator.placement = image::Placement::Physical { |
|
| 115 | - | code: CODE_ADDRESS, roData: roAddress, rwData: rwAddress, entry: CODE_ADDRESS, |
|
| 116 | - | }; |
|
| 117 | - | let output = try rv64::finishProgram(&mut generator, &GLOBALS[..dataCount], |
|
| 118 | - | rv64::Storage { dataSyms: &mut SYMBOLS[..], dataSymEntries: &mut ENTRIES[..] }, |
|
| 119 | - | &[], &mut RO[..], &mut RW[..]) catch error { |
|
| 120 | + | let assembly = try! asm::assemble(asm::scanner::SourceKind::String, &SOURCE[..sourceLength], |
|
| 121 | + | &mut TEXT[..], &mut empty[..], &mut assemblyArena, &mut STRINGS, 0); |
|
| 122 | + | let library = try compile(&libraryInput, 0, CODE_ADDRESS + 4096, &[], |
|
| 123 | + | asm::Program { text: &[], data: &[], symbols: &[], externalFixups: &[] }) catch error { |
|
| 120 | 124 | report(error); |
|
| 121 | 125 | return 1; |
|
| 122 | 126 | }; |
|
| 123 | - | let header = try! image::header(output.layout); |
|
| 127 | + | let kernelAddress = aligned(library.codeAddress + library.code.len as u64 * 4, 4096); |
|
| 128 | + | let kernel = try compile(&kernelInput, 1, kernelAddress, library.exports, assembly) catch error { |
|
| 129 | + | report(error); |
|
| 130 | + | return 1; |
|
| 131 | + | }; |
|
| 132 | + | let entries = [ |
|
| 133 | + | catalog::Entry { source: libraryBytes, package: library }, |
|
| 134 | + | catalog::Entry { source: kernelBytes, package: kernel }, |
|
| 135 | + | ]; |
|
| 136 | + | let codeEnd = kernel.codeAddress + kernel.code.len as u64 * 4; |
|
| 137 | + | let codeSize = (codeEnd - CODE_ADDRESS) as u32; |
|
| 138 | + | assert codeEnd - CODE_ADDRESS <= @sizeOf([u32; 4194304]) as u64; |
|
| 139 | + | let roAddress = aligned(codeEnd, 4096); |
|
| 140 | + | let roSize = try catalog::pack(&entries[..], roAddress, &mut RO[..]) catch error { |
|
| 141 | + | report(error); |
|
| 142 | + | return 1; |
|
| 143 | + | }; |
|
| 144 | + | let rwAddress = aligned(roAddress + roSize as u64, 4096); |
|
| 145 | + | let mut bases: [u64; shared::MAX_PACKAGES] = [0; shared::MAX_PACKAGES]; |
|
| 146 | + | let mut rwSize: u32 = @sizeOf([u64; shared::MAX_PACKAGES]); |
|
| 147 | + | for entry in &entries[..] { |
|
| 148 | + | let at = aligned(rwAddress + rwSize as u64, entry.package.alignment); |
|
| 149 | + | assert at - rwAddress <= RW.len as u64 and entry.package.memory <= RW.len - (at - rwAddress) as u32; |
|
| 150 | + | set bases[entry.package.slot] = at; |
|
| 151 | + | set rwSize = (at - rwAddress) as u32 + entry.package.memory; |
|
| 152 | + | } |
|
| 153 | + | try! mem::copy(&mut RW[..@sizeOf([u64; shared::MAX_PACKAGES])], |
|
| 154 | + | @sliceOf(&bases[0] as *unsafe u8, @sizeOf([u64; shared::MAX_PACKAGES]))); |
|
| 155 | + | for entry in &entries[..] { |
|
| 156 | + | let at = (bases[entry.package.slot] - rwAddress) as u32; |
|
| 157 | + | try shared::instantiate(&entry.package, &bases[..], &mut RW[at..at + entry.package.memory]) |
|
| 158 | + | catch error { |
|
| 159 | + | report(error); |
|
| 160 | + | return 1; |
|
| 161 | + | }; |
|
| 162 | + | } |
|
| 163 | + | for i in 0..codeSize / 4 { |
|
| 164 | + | set NATIVE[i] = encode::nop(); |
|
| 165 | + | } |
|
| 166 | + | for entry in &entries[..] { |
|
| 167 | + | let at = ((entry.package.codeAddress - CODE_ADDRESS) / 4) as u32; |
|
| 168 | + | for word, i in entry.package.code { |
|
| 169 | + | set NATIVE[at + i] = word; |
|
| 170 | + | } |
|
| 171 | + | } |
|
| 172 | + | trampoline(kernelAddress, roAddress, rwAddress); |
|
| 173 | + | let header = try! image::header(image::Layout { |
|
| 174 | + | entry: CODE_ADDRESS, |
|
| 175 | + | code: image::Segment { address: CODE_ADDRESS, initialized: codeSize, memory: codeSize }, |
|
| 176 | + | roData: image::Segment { address: roAddress, initialized: roSize, memory: roSize }, |
|
| 177 | + | rwData: image::Segment { address: rwAddress, initialized: rwSize, memory: rwSize }, |
|
| 178 | + | }); |
|
| 124 | 179 | let fd = unix::openOpts(env.args[4], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420); |
|
| 125 | 180 | assert fd >= 0; |
|
| 126 | - | let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, @sliceOf(output.code.ptr as *u8, output.code.len * 4)) |
|
| 127 | - | and unix::writeAll(fd, &RO[..output.roDataSize]) and unix::writeAll(fd, &RW[..output.rwDataSize]); |
|
| 181 | + | let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, @sliceOf(&NATIVE[0] as *u8, codeSize)) and unix::writeAll(fd, &RO[..roSize]) and unix::writeAll(fd, &RW[..rwSize]); |
|
| 128 | 182 | let closed = unix::close(fd) == 0; |
|
| 129 | 183 | assert written and closed; |
|
| 130 | 184 | return 0; |
|
| 131 | 185 | } |
test/boot/run
+14 -0
| 15 | 15 | fi |
|
| 16 | 16 | if ! grep -q '^kernel: platform ready$' "$work/log"; then |
|
| 17 | 17 | cat "$work/log" >&2 |
|
| 18 | 18 | exit 1 |
|
| 19 | 19 | fi |
|
| 20 | + | if ! grep -q '^kernel: boot catalog ready$' "$work/log"; then |
|
| 21 | + | cat "$work/log" >&2 |
|
| 22 | + | exit 1 |
|
| 23 | + | fi |
|
| 20 | 24 | printf 'kernel boot: %s harts passed\n' "$harts" |
|
| 21 | 25 | done |
|
| 22 | 26 | status=0 |
|
| 23 | 27 | "$emulator" -machine -harts=8 -max-steps=80000000 -run bin/kernel.rv64 > "$work/log" 2>&1 || status=$? |
|
| 24 | 28 | if [ "$status" -ne 2 ] || [ "$(grep -c 'wfi=1 mcause=0x0' "$work/log")" -ne 8 ]; then |
|
| 25 | 29 | cat "$work/log" >&2 |
|
| 26 | 30 | exit 1 |
|
| 27 | 31 | fi |
|
| 28 | 32 | printf 'kernel startup: all eight harts reached machine idle without traps\n' |
|
| 33 | + | cat test/boot/trap.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras > "$work/trap.ras" |
|
| 34 | + | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/trap.ras" "$work/trap.rv64" |
|
| 35 | + | status=0 |
|
| 36 | + | "$emulator" -machine -harts=2 -max-steps=20000000 -run "$work/trap.rv64" > "$work/log" 2>&1 || status=$? |
|
| 37 | + | if [ "$status" -ne 2 ] || [ "$(grep -c '^kernel: unexpected trap$' "$work/log")" -ne 1 ] \ |
|
| 38 | + | || [ "$(grep -c 'wfi=1' "$work/log")" -ne 2 ]; then |
|
| 39 | + | cat "$work/log" >&2 |
|
| 40 | + | exit 1 |
|
| 41 | + | fi |
|
| 42 | + | printf 'kernel trap: shared kernel state restored from the hart anchor\n' |
test/boot/trap.ras
added
+12 -0
| 1 | + | //! Enter a kernel handler after an interrupted context changes gp. |
|
| 2 | + | .text; |
|
| 3 | + | call @kernel::boot::initialize; |
|
| 4 | + | beqz %a0 @wait; |
|
| 5 | + | li %gp 123; |
|
| 6 | + | ecall; |
|
| 7 | + | li %t0 0x10001000; |
|
| 8 | + | li %t1 0x13333; |
|
| 9 | + | sw %t1 0(%t0); |
|
| 10 | + | @wait |
|
| 11 | + | wfi; |
|
| 12 | + | j @wait; |
test/packages/app.rad
+5 -1
| 1 | 1 | //! Application package fixture. |
|
| 2 | 2 | use support; |
|
| 3 | 3 | /// Return the shared package's state. |
|
| 4 | - | @default fn main() -> u64 { return support::read() + support::value; } |
|
| 4 | + | @default fn main() -> u64 { |
|
| 5 | + | let label = "package literal"; |
|
| 6 | + | let values = &[11 as u64, 13]; |
|
| 7 | + | return support::read() + support::value + label.len as u64 - 15 + values[0] - 11; |
|
| 8 | + | } |
test/packages/check.rad
+14 -3
| 32 | 32 | assert mem::eq(support.name, "support"), "main: mem::eq(support.name, \"support\")"; |
|
| 33 | 33 | assert mem::eq(app.name, "app"), "main: mem::eq(app.name, \"app\")"; |
|
| 34 | 34 | assert support.dependencies.len == 0, "main: support.dependencies.len == 0"; |
|
| 35 | 35 | assert app.dependencies.len == 1, "main: app.dependencies.len == 1"; |
|
| 36 | 36 | assert mem::eq(app.dependencies[0], "support"), "main: mem::eq(app.dependencies[0], \"support\")"; |
|
| 37 | - | assert support.program.fns.len == 1, "main: support.program.fns.len == 1"; |
|
| 38 | - | assert support.program.data.len == 1, "main: support.program.data.len == 1"; |
|
| 37 | + | assert support.program.fns.len == 2, "main: support.program.fns.len == 2"; |
|
| 38 | + | assert support.program.data.len == 3, "main: support.program.data.len == 3"; |
|
| 39 | 39 | assert mem::eq(support.program.fns[0].name, "support::read"), "main: mem::eq(support.program.fns[0].name, \"support::read\")"; |
|
| 40 | 40 | assert mem::eq(support.program.data[0].name, "support::value"), "main: mem::eq(support.program.data[0].name, \"support::value\")"; |
|
| 41 | 41 | assert app.program.fns.len == 1, "main: app.program.fns.len == 1"; |
|
| 42 | - | assert app.program.data.len == 0, "main: app.program.data.len == 0"; |
|
| 42 | + | assert app.program.data.len == 2, "main: app.program.data.len == 2"; |
|
| 43 | + | assert not mem::eq(support.program.data[1].name, app.program.data[0].name), "main: package-local literals"; |
|
| 44 | + | assert not mem::eq(support.program.data[2].name, app.program.data[1].name), "main: package-local arrays"; |
|
| 43 | 45 | assert mem::eq(app.program.fns[0].name, "app::main"), "main: mem::eq(app.program.fns[0].name, \"app::main\")"; |
|
| 44 | 46 | assert support.exports.len == 2, "main: support.exports.len == 2"; |
|
| 47 | + | let mut fenceFound = false; |
|
| 48 | + | for block in support.program.fns[0].blocks { |
|
| 49 | + | for instr in block.instrs { |
|
| 50 | + | if let case il::Instr::MemoryFence = instr { |
|
| 51 | + | set fenceFound = true; |
|
| 52 | + | } |
|
| 53 | + | } |
|
| 54 | + | } |
|
| 55 | + | assert fenceFound, "main: intrinsic instruction"; |
|
| 45 | 56 | assert app.exports.len == 1, "main: app.exports.len == 1"; |
|
| 46 | 57 | assert app.exports[0].kind == binary::ExportKind::Function, "main: app.exports[0].kind == binary::ExportKind::Function"; |
|
| 47 | 58 | let entry = app.entry else panic "main: missing package entry"; |
|
| 48 | 59 | assert mem::eq(entry, "app::main"), "main: mem::eq(entry, \"app::main\")"; |
|
| 49 | 60 | let mut callFound = false; |
test/packages/support.rad
+8 -1
| 1 | 1 | //! Shared package fixture. |
|
| 2 | 2 | /// Shared mutable data. |
|
| 3 | 3 | export static value: u64 = 7; |
|
| 4 | 4 | /// Read shared mutable data. |
|
| 5 | - | export fn read() -> u64 { return value; } |
|
| 5 | + | export fn read() -> u64 { |
|
| 6 | + | memoryFence(); |
|
| 7 | + | let label = "package literal"; |
|
| 8 | + | let values = &[11 as u64, 13]; |
|
| 9 | + | return value + label.len as u64 - 15 + values[0] - 11; |
|
| 10 | + | } |
|
| 11 | + | /// Order shared-memory accesses through a compiler instruction. |
|
| 12 | + | @intrinsic export fn memoryFence(); |