kernel: Register global packages
066b2e8c62df3d2e5c88a352d2f681f3144543e78a850a2cf1f46e7559491de4
Assisted-by: Codex:gpt-6
1 parent
307f500b
compiler/radiance.rad
+1 -0
| 1114 | 1114 | set ident = decl.ident; set attrs = decl.attrs; set kind = binary::ExportKind::Data; |
|
| 1115 | 1115 | }, |
|
| 1116 | 1116 | else => continue, |
|
| 1117 | 1117 | } |
|
| 1118 | 1118 | let attributes = attrs else continue; |
|
| 1119 | + | if ast::attributesContains(&attributes, ast::Attribute::Intrinsic) { continue; } |
|
| 1119 | 1120 | let isDefault = ast::attributesContains(&attributes, ast::Attribute::Default) |
|
| 1120 | 1121 | and pkg.rootModuleId == modEntry.id; |
|
| 1121 | 1122 | if not isDefault and not ast::attributesContains(&attributes, ast::Attribute::Export) { |
|
| 1122 | 1123 | continue; |
|
| 1123 | 1124 | } |
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; |
| 46 | 48 | } |
|
| 47 | 49 | } |
|
| 48 | 50 | ||
| 49 | 51 | /// Initialize one hart from firmware arguments. Return true for the last hart. |
|
| 50 | 52 | /// Firmware supplies a mapped FDT and a disjoint, reserved stack for each hart. |
|
| 51 | - | export unsafe fn enter(hart: u64, tree: *u8, stackTop: u64) -> bool { |
|
| 53 | + | export unsafe fn enter(hart: u64, tree: *u8, stackTop: u64, entries: *catalog::Entry, entryCount: u32, stateTable: *u64) -> bool { |
|
| 52 | 54 | let treeAddress = tree as u64; |
|
| 53 | 55 | assert hart < limits::HARTS as u64; |
|
| 54 | 56 | if hart == 0 { |
|
| 55 | 57 | assert (treeAddress & 7) == 0 and treeAddress <= 0xffffffffffffffff - 65536; |
|
| 56 | 58 | let prefix = @sliceOf(tree, 40); |
| 60 | 62 | try! platform::decode(&blob[..], &mut PLATFORM); |
|
| 61 | 63 | let treeRange = range::new(treeAddress, size as u64) else panic "FDT range"; |
|
| 62 | 64 | assert platform::inRam(&PLATFORM, treeRange); |
|
| 63 | 65 | try! platform::protect(&mut PLATFORM, treeRange); |
|
| 64 | 66 | try! pages::initialize(&mut pages::STORE, &PLATFORM); |
|
| 67 | + | assert entryCount == 2; |
|
| 68 | + | registry::initialize(&mut registry::STORE); |
|
| 69 | + | let bootCatalog = @sliceOf(entries, entryCount); |
|
| 70 | + | try! registry::boot(&mut registry::STORE, &bootCatalog[..]); |
|
| 71 | + | let library = registry::find(®istry::STORE, &"std"[..]) else panic "boot std"; |
|
| 72 | + | let kernel = registry::find(®istry::STORE, &"kernel"[..]) else panic "boot kernel"; |
|
| 73 | + | assert library.index == 0 and kernel.index == 1; |
|
| 74 | + | let bases = @sliceOf(stateTable, limits::PACKAGES); |
|
| 75 | + | assert bases[library.index] <> 0 and bases[kernel.index] <> 0; |
|
| 76 | + | print("kernel: boot catalog ready\n"); |
|
| 65 | 77 | print("kernel: platform ready\n"); |
|
| 66 | 78 | sync::storeRelease(&mut READY, 1); |
|
| 67 | 79 | } else { |
|
| 68 | 80 | while sync::loadAcquire(&READY) == 0 {} |
|
| 69 | 81 | } |
|
| 70 | 82 | assert (PLATFORM.harts & (1 << hart as u32)) <> 0; |
|
| 71 | 83 | assert stackTop == PLATFORM.stacks[hart as u32].end and (stackTop & 15) == 0; |
|
| 72 | 84 | let stack = PLATFORM.stacks[hart as u32]; |
|
| 73 | 85 | set HARTS[hart as u32] = trap::Hart { |
|
| 74 | - | stackTop: stack.end, stackBottom: stack.start, kernelGp: 0, handler: unexpected, |
|
| 86 | + | stackTop: stack.end, stackBottom: stack.start, kernelGp: stateTable as u64, handler: unexpected, |
|
| 75 | 87 | savedT0: 0, savedT1: 0, savedSp: 0, |
|
| 76 | 88 | }; |
|
| 77 | 89 | trap::install(&mut HARTS[hart as u32]); |
|
| 78 | 90 | let mut count: u64 = 0; |
|
| 79 | 91 | for id in 0..limits::HARTS { if (PLATFORM.harts & (1 << id)) <> 0 { set count += 1; } } |
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
+148 -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 { continue; } |
|
| 36 | + | if mem::eq(store.entries[i].package.name, &name[..]) { |
|
| 37 | + | return abi::Ref { index: i, generation: store.slots[i].generation }; |
|
| 38 | + | } |
|
| 39 | + | } |
|
| 40 | + | return nil; |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | /// Reuse identical content and reject conflicting content under a resident name. |
|
| 44 | + | export fn identify(store: &Store, name: &[u8], source: &[u8]) -> ?abi::Ref throws (abi::Error) { |
|
| 45 | + | let object = find(store, name) else { return nil; }; |
|
| 46 | + | if not mem::eq(store.entries[object.index].source, &source[..]) { throw abi::Error::VerifyFailed; } |
|
| 47 | + | return object; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// Reserve the stable package-state slot before compiling its native code. |
|
| 51 | + | export fn reserve(store: &mut Store) -> slots::Reservation throws (abi::Error) { |
|
| 52 | + | return try slots::reserve(&mut store.slots[..]); |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | /// Return unpublished capacity after a failed load. |
|
| 56 | + | export fn cancel(store: &mut Store, reservation: slots::Reservation) { |
|
| 57 | + | try! slots::cancel(&mut store.slots[..], reservation); |
|
| 58 | + | } |
|
| 59 | + | ||
| 60 | + | /// Validate dependencies without changing registry payloads. |
|
| 61 | + | unsafe fn validate(store: &Store, object: abi::Ref, package: &shared::Package) throws (abi::Error) { |
|
| 62 | + | if package.name.len == 0 or package.slot <> object.index or package.dependencies.len > limits::PACKAGES { |
|
| 63 | + | throw abi::Error::VerifyFailed; |
|
| 64 | + | } |
|
| 65 | + | for dependency, i in package.dependencies { |
|
| 66 | + | let target = find(store, &dependency[..]) else { throw abi::Error::VerifyFailed; }; |
|
| 67 | + | for prior in &package.dependencies[..i] { |
|
| 68 | + | if mem::eq(prior, dependency) { throw abi::Error::VerifyFailed; } |
|
| 69 | + | } |
|
| 70 | + | } |
|
| 71 | + | } |
|
| 72 | + | ||
| 73 | + | /// Publish persistent compiler output after successful admission checks. |
|
| 74 | + | /// All descriptor pointers and source bytes must remain immutable and resident. |
|
| 75 | + | /// Failure cancels the reservation; duplicate content returns its existing slot. |
|
| 76 | + | export unsafe fn publish(store: &mut Store, reservation: slots::Reservation, source: *[u8], package: shared::Package) |
|
| 77 | + | -> abi::Ref throws (abi::Error) |
|
| 78 | + | { |
|
| 79 | + | let existing = try identify(store, &package.name[..], &source[..]) catch err { |
|
| 80 | + | cancel(store, reservation); throw err; |
|
| 81 | + | }; |
|
| 82 | + | if let object = existing { cancel(store, reservation); return object; } |
|
| 83 | + | let object = slots::reference(&reservation); |
|
| 84 | + | try validate(store, object, &package) catch err { cancel(store, reservation); throw err; }; |
|
| 85 | + | for dependency, i in package.dependencies { |
|
| 86 | + | let target = find(store, &dependency[..]) else panic "resident dependency"; |
|
| 87 | + | set store.dependencies[object.index][i] = target; |
|
| 88 | + | } |
|
| 89 | + | set store.counts[object.index] = package.dependencies.len; |
|
| 90 | + | set store.entries[object.index] = catalog::Entry { source, package }; |
|
| 91 | + | return try! slots::commit(&mut store.slots[..], reservation); |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | /// Read resident metadata through a generation-bearing package reference. |
|
| 95 | + | export fn get(store: &Store, object: abi::Ref) -> shared::Package throws (abi::Error) { |
|
| 96 | + | if not slots::matches(&store.slots[..], object, slots::State::Live) { throw abi::Error::BadHandle; } |
|
| 97 | + | return store.entries[object.index].package; |
|
| 98 | + | } |
|
| 99 | + | ||
| 100 | + | /// Resolve one public symbol without searching packages outside the named image. |
|
| 101 | + | export fn exported(store: &Store, object: abi::Ref, name: &[u8]) -> shared::Target throws (abi::Error) { |
|
| 102 | + | let package = try get(store, object); |
|
| 103 | + | let target = shared::lookup(package.exports, &name[..]) else { throw abi::Error::VerifyFailed; }; |
|
| 104 | + | return target; |
|
| 105 | + | } |
|
| 106 | + | ||
| 107 | + | /// Collect exports from declared dependencies into caller-owned compiler storage. |
|
| 108 | + | export fn imports(store: &Store, dependencies: &[*[u8]], output: &mut [shared::Symbol]) -> u32 throws (abi::Error) { |
|
| 109 | + | let mut count: u32 = 0; |
|
| 110 | + | for name in dependencies { |
|
| 111 | + | let object = find(store, &name[..]) else { throw abi::Error::VerifyFailed; }; |
|
| 112 | + | let package = try! get(store, object); |
|
| 113 | + | if package.exports.len > output.len - count { throw abi::Error::Exhausted; } |
|
| 114 | + | set count += package.exports.len; |
|
| 115 | + | } |
|
| 116 | + | set count = 0; |
|
| 117 | + | for name in dependencies { |
|
| 118 | + | let object = find(store, &name[..]) else panic "resident dependency"; |
|
| 119 | + | let package = try! get(store, object); |
|
| 120 | + | for symbol in package.exports { set output[count] = symbol; set count += 1; } |
|
| 121 | + | } |
|
| 122 | + | return count; |
|
| 123 | + | } |
|
| 124 | + | ||
| 125 | + | /// Register a persistent boot catalog in dependency order through normal admission. |
|
| 126 | + | export unsafe fn boot(store: &mut Store, catalog: &[catalog::Entry]) throws (abi::Error) { |
|
| 127 | + | for entry in catalog { |
|
| 128 | + | let existing = try identify(store, &entry.package.name[..], &entry.source[..]); |
|
| 129 | + | if existing <> nil { continue; } |
|
| 130 | + | let reservation = try reserve(store); |
|
| 131 | + | let object = try publish(store, reservation, entry.source, entry.package); |
|
| 132 | + | } |
|
| 133 | + | } |
|
| 134 | + | ||
| 135 | + | /// Install an Image handle for a resident package without duplicating its code. |
|
| 136 | + | export fn install(store: &Store, table: &mut capability::Table, object: abi::Ref) -> abi::Handle throws (abi::Error) { |
|
| 137 | + | let package = try get(store, object); |
|
| 138 | + | return try capability::install(table, capability::Entry { |
|
| 139 | + | kind: abi::Kind::Image, object, rights: abi::Rights(IMAGE_RIGHTS), |
|
| 140 | + | }); |
|
| 141 | + | } |
|
| 142 | + | ||
| 143 | + | /// Resolve image authority and validate its resident package generation. |
|
| 144 | + | export fn image(store: &Store, table: &capability::Table, handle: abi::Handle, rights: abi::Rights) -> abi::Ref throws (abi::Error) { |
|
| 145 | + | let entry = try capability::lookup(table, handle, abi::Kind::Image, rights); |
|
| 146 | + | let package = try get(store, entry.object); |
|
| 147 | + | return entry.object; |
|
| 148 | + | } |
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
+121 -82
| 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) { set NATIVE[i] = word; } |
|
| 98 | + | } |
|
| 99 | + | ||
| 100 | + | /// Link shared boot packages, retain their catalog, and instantiate the kernel graph. |
|
| 67 | 101 | @default unsafe fn main(env: *sys::Env) -> i32 { |
|
| 68 | 102 | assert env.args.len == 5; |
|
| 69 | 103 | 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 { assert mem::eq(dependency, "std"); } |
|
| 104 | + | let libraryBytesLength = unix::readFile(env.args[1], &mut INPUT[0][..]) else panic "std RIL"; |
|
| 105 | + | let libraryBytes = &INPUT[0][..libraryBytesLength]; |
|
| 106 | + | let libraryInput = try! program::decode(libraryBytes, &mut decoder, binary::Limits { registers: 8192, blocks: 4096 }); |
|
| 107 | + | let kernelBytesLength = unix::readFile(env.args[2], &mut INPUT[1][..]) else panic "kernel RIL"; |
|
| 108 | + | let kernelBytes = &INPUT[1][..kernelBytesLength]; |
|
| 109 | + | let kernelInput = try! program::decode(kernelBytes, &mut decoder, binary::Limits { registers: 8192, blocks: 4096 }); |
|
| 110 | + | assert mem::eq(libraryInput.name, "std") and mem::eq(kernelInput.name, "kernel"); |
|
| 111 | + | assert libraryInput.dependencies.len == 0; |
|
| 112 | + | for dependency in kernelInput.dependencies { assert mem::eq(dependency, "std"); } |
|
| 75 | 113 | let sourceLength = unix::readFile(env.args[3], &mut SOURCE[..]) else panic "kernel assembly"; |
|
| 76 | - | let mut assembly = alloc::new(&mut ASSEMBLY[..]); |
|
| 114 | + | let mut assemblyArena = alloc::new(&mut ASSEMBLY[..]); |
|
| 77 | 115 | let empty: *mut [u8] = &mut []; |
|
| 78 | - | let startup = try! asm::assemble(asm::scanner::SourceKind::String, &SOURCE[..sourceLength], |
|
| 79 | - | &mut TEXT[..], &mut empty[..], &mut assembly, &mut STRINGS, 0); |
|
| 80 | - | let mut arena = alloc::new(&mut CODE[..]); |
|
| 81 | - | let mut scratch = alloc::new(&mut SCRATCH[..]); |
|
| 82 | - | let mut generator = try! rv64::beginProgram(rv64::ProgramOptions { |
|
| 83 | - | entryPatch: rv64::EntryPatch::None, debug: false, |
|
| 84 | - | placement: image::Placement::Physical { |
|
| 85 | - | code: CODE_ADDRESS, roData: 0, rwData: 0, entry: CODE_ADDRESS, |
|
| 86 | - | }, |
|
| 87 | - | }, &mut arena); |
|
| 88 | - | rv64::addAssembly(&mut generator, startup); |
|
| 89 | - | let mut dataCount: u32 = 0; |
|
| 90 | - | for package in &[library, kernel] { |
|
| 91 | - | for item in package.program.data { |
|
| 92 | - | assert dataCount < GLOBALS.len; |
|
| 93 | - | set GLOBALS[dataCount] = item; set dataCount += 1; |
|
| 94 | - | } |
|
| 95 | - | for func in package.program.fns { |
|
| 96 | - | rv64::generateFunction(&mut generator, func, &mut scratch); |
|
| 97 | - | if let error = generator.e.error { |
|
| 98 | - | report(error); io::printLn(func.name); return 1; |
|
| 99 | - | } |
|
| 100 | - | alloc::reset(&mut scratch); |
|
| 101 | - | } |
|
| 116 | + | let assembly = try! asm::assemble(asm::scanner::SourceKind::String, &SOURCE[..sourceLength], |
|
| 117 | + | &mut TEXT[..], &mut empty[..], &mut assemblyArena, &mut STRINGS, 0); |
|
| 118 | + | let library = try compile(&libraryInput, 0, CODE_ADDRESS + 4096, &[], |
|
| 119 | + | asm::Program { text: &[], data: &[], symbols: &[], externalFixups: &[] }) catch error { report(error); return 1; }; |
|
| 120 | + | let kernelAddress = aligned(library.codeAddress + library.code.len as u64 * 4, 4096); |
|
| 121 | + | let kernel = try compile(&kernelInput, 1, kernelAddress, library.exports, assembly) catch error { report(error); return 1; }; |
|
| 122 | + | let entries = [ |
|
| 123 | + | catalog::Entry { source: libraryBytes, package: library }, |
|
| 124 | + | catalog::Entry { source: kernelBytes, package: kernel }, |
|
| 125 | + | ]; |
|
| 126 | + | let codeEnd = kernel.codeAddress + kernel.code.len as u64 * 4; |
|
| 127 | + | let codeSize = (codeEnd - CODE_ADDRESS) as u32; |
|
| 128 | + | assert codeEnd - CODE_ADDRESS <= @sizeOf([u32; 4194304]) as u64; |
|
| 129 | + | let roAddress = aligned(codeEnd, 4096); |
|
| 130 | + | let roSize = try catalog::pack(&entries[..], roAddress, &mut RO[..]) catch error { report(error); return 1; }; |
|
| 131 | + | let rwAddress = aligned(roAddress + roSize as u64, 4096); |
|
| 132 | + | let mut bases: [u64; shared::MAX_PACKAGES] = [0; shared::MAX_PACKAGES]; |
|
| 133 | + | let mut rwSize: u32 = @sizeOf([u64; shared::MAX_PACKAGES]); |
|
| 134 | + | for entry in &entries[..] { |
|
| 135 | + | let at = aligned(rwAddress + rwSize as u64, entry.package.alignment); |
|
| 136 | + | assert at - rwAddress <= RW.len as u64 and entry.package.memory <= RW.len - (at - rwAddress) as u32; |
|
| 137 | + | set bases[entry.package.slot] = at; |
|
| 138 | + | set rwSize = (at - rwAddress) as u32 + entry.package.memory; |
|
| 139 | + | } |
|
| 140 | + | try! mem::copy(&mut RW[..@sizeOf([u64; shared::MAX_PACKAGES])], |
|
| 141 | + | @sliceOf(&bases[0] as *unsafe u8, @sizeOf([u64; shared::MAX_PACKAGES]))); |
|
| 142 | + | for entry in &entries[..] { |
|
| 143 | + | let at = (bases[entry.package.slot] - rwAddress) as u32; |
|
| 144 | + | try shared::instantiate(&entry.package, &bases[..], &mut RW[at..at + entry.package.memory]) |
|
| 145 | + | catch error { report(error); return 1; }; |
|
| 102 | 146 | } |
|
| 103 | - | for call in &generator.e.pendingCalls[..] { |
|
| 104 | - | if dict::get(&generator.e.labels.funcs, call.target) == nil { |
|
| 105 | - | io::print("undefined kernel function: "); io::printLn(call.target); return 1; |
|
| 106 | - | } |
|
| 147 | + | for i in 0..codeSize / 4 { set NATIVE[i] = encode::nop(); } |
|
| 148 | + | for entry in &entries[..] { |
|
| 149 | + | let at = ((entry.package.codeAddress - CODE_ADDRESS) / 4) as u32; |
|
| 150 | + | for word, i in entry.package.code { set NATIVE[at + i] = word; } |
|
| 107 | 151 | } |
|
| 108 | - | let roAddress = (CODE_ADDRESS + generator.e.codeLen as u64 * 4 + 4095) & ~4095; |
|
| 109 | - | let mut symbols: u32 = 0; |
|
| 110 | - | let roSize = try! data::layoutSection(&GLOBALS[..dataCount], &mut SYMBOLS[..], &mut symbols, roAddress, true); |
|
| 111 | - | let rwAddress = (roAddress + roSize as u64 + 4095) & ~4095; |
|
| 112 | - | set generator.placement = image::Placement::Physical { |
|
| 113 | - | code: CODE_ADDRESS, roData: roAddress, rwData: rwAddress, entry: CODE_ADDRESS, |
|
| 114 | - | }; |
|
| 115 | - | let output = try rv64::finishProgram(&mut generator, &GLOBALS[..dataCount], |
|
| 116 | - | rv64::Storage { dataSyms: &mut SYMBOLS[..], dataSymEntries: &mut ENTRIES[..] }, |
|
| 117 | - | &[], &mut RO[..], &mut RW[..]) catch error { report(error); return 1; }; |
|
| 118 | - | let header = try! image::header(output.layout); |
|
| 152 | + | trampoline(kernelAddress, roAddress, rwAddress); |
|
| 153 | + | let header = try! image::header(image::Layout { |
|
| 154 | + | entry: CODE_ADDRESS, |
|
| 155 | + | code: image::Segment { address: CODE_ADDRESS, initialized: codeSize, memory: codeSize }, |
|
| 156 | + | roData: image::Segment { address: roAddress, initialized: roSize, memory: roSize }, |
|
| 157 | + | rwData: image::Segment { address: rwAddress, initialized: rwSize, memory: rwSize }, |
|
| 158 | + | }); |
|
| 119 | 159 | let fd = unix::openOpts(env.args[4], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420); |
|
| 120 | 160 | assert fd >= 0; |
|
| 121 | - | let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, @sliceOf(output.code.ptr as *u8, output.code.len * 4)) |
|
| 122 | - | and unix::writeAll(fd, &RO[..output.roDataSize]) and unix::writeAll(fd, &RW[..output.rwDataSize]); |
|
| 161 | + | 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]); |
|
| 123 | 162 | let closed = unix::close(fd) == 0; |
|
| 124 | 163 | assert written and closed; |
|
| 125 | 164 | return 0; |
|
| 126 | 165 | } |
lib/std/arch/rv64.rad
+1 -1
| 10 | 10 | //! * isel: Instruction selection (IL to RV64 instructions) |
|
| 11 | 11 | //! * printer: Assembly text output |
|
| 12 | 12 | ||
| 13 | 13 | export mod image; |
|
| 14 | 14 | export mod atomics; |
|
| 15 | - | export mod shared; |
|
| 16 | 15 | export mod encode; |
|
| 17 | 16 | export mod decode; |
|
| 18 | 17 | export mod emit; |
|
| 19 | 18 | export mod isel; |
|
| 20 | 19 | export mod printer; |
|
| 21 | 20 | export mod asm; |
|
| 21 | + | export mod shared; |
|
| 22 | 22 | ||
| 23 | 23 | @test mod tests; |
|
| 24 | 24 | @test mod bounds; |
|
| 25 | 25 | @test mod atomicTests; |
|
| 26 | 26 |
lib/std/arch/rv64/shared.rad
+24 -0
| 1 | 1 | //! Shared package code, private data templates, and qualified symbol linking. |
|
| 2 | 2 | ||
| 3 | 3 | @test export mod tests; |
|
| 4 | + | export mod catalog; |
|
| 4 | 5 | ||
| 5 | 6 | use std::mem; |
|
| 6 | 7 | use std::lang::alloc; |
|
| 7 | 8 | use std::lang::il; |
|
| 8 | 9 | use std::lang::il::binary; |
|
| 9 | 10 | use std::lang::gen; |
|
| 10 | 11 | use std::lang::gen::data; |
|
| 11 | 12 | use super::emit; |
|
| 12 | 13 | use super::encode; |
|
| 13 | 14 | use super::image; |
|
| 15 | + | use super::asm; |
|
| 14 | 16 | ||
| 15 | 17 | /// Maximum number of resident package slots in a domain state table. |
|
| 16 | 18 | export constant MAX_PACKAGES: u32 = 256; |
|
| 17 | 19 | ||
| 18 | 20 | /// Package linking or instance storage failure. |
| 254 | 256 | } |
|
| 255 | 257 | } |
|
| 256 | 258 | return TemplateSize { bytes: initialized, relocations: count }; |
|
| 257 | 259 | } |
|
| 258 | 260 | ||
| 261 | + | /// Package definitions and their native assembly boundaries. |
|
| 262 | + | export record AssemblyInput: Copy { |
|
| 263 | + | /// Trusted binary RIL definitions that remain valid during compilation. |
|
| 264 | + | package: *unsafe binary::Package, |
|
| 265 | + | /// Text-only assembly prefix with exported native boundaries. |
|
| 266 | + | assembly: asm::Program, |
|
| 267 | + | } |
|
| 268 | + | ||
| 259 | 269 | /// Compile one package into caller-owned code, symbol, and private template storage. |
|
| 260 | 270 | /// Imports must contain unique exports from the package's admitted dependencies. |
|
| 261 | 271 | /// Arena storage and binary package names must outlive the returned catalog entry. |
|
| 262 | 272 | export unsafe fn compile(input: &binary::Package, slot: u32, codeAddress: u64, imports: &[Symbol], |
|
| 263 | 273 | storage: Storage, arena: &mut alloc::Arena, scratch: &mut alloc::Arena) -> Package throws (Error) |
|
| 274 | + | { |
|
| 275 | + | return try compileAssembly(AssemblyInput { package: input as *unsafe binary::Package, |
|
| 276 | + | assembly: asm::Program { text: &[], data: &[], symbols: &[], externalFixups: &[] } }, |
|
| 277 | + | slot, codeAddress, imports, storage, arena, scratch); |
|
| 278 | + | } |
|
| 279 | + | ||
| 280 | + | /// Compile a package with a text-only assembly prefix and its exported boundaries. |
|
| 281 | + | /// Assembly and RIL definitions share one native code extent and package-state slot. |
|
| 282 | + | export unsafe fn compileAssembly(source: AssemblyInput, slot: u32, codeAddress: u64, imports: &[Symbol], |
|
| 283 | + | storage: Storage, arena: &mut alloc::Arena, scratch: &mut alloc::Arena) -> Package throws (Error) |
|
| 264 | 284 | { |
|
| 265 | 285 | let case Storage { data: dataStorage, symbols: symbolStorage, exports: exportStorage, template: templateStorage, relocations: relocationStorage } = storage |
|
| 266 | 286 | else panic "expected shared linker storage"; |
|
| 287 | + | let input = source.package; |
|
| 288 | + | let assembly = source.assembly; |
|
| 289 | + | if assembly.data.len <> 0 { throw Error::Range; } |
|
| 267 | 290 | if slot >= MAX_PACKAGES { throw Error::Range; } |
|
| 268 | 291 | if (codeAddress & 3) <> 0 { throw Error::Alignment; } |
|
| 269 | 292 | for imported, i in imports { |
|
| 270 | 293 | if lookup(&imports[..i], imported.name) <> nil { throw Error::Symbol; } |
|
| 271 | 294 | } |
|
| 272 | 295 | let mut generator = try super::beginProgram(super::ProgramOptions { |
|
| 273 | 296 | entryPatch: super::EntryPatch::None, debug: false, placement: image::Placement::Hosted, |
|
| 274 | 297 | }, arena) catch err { throw Error::Codegen(err); }; |
|
| 275 | 298 | set generator.e.sharedData = true; |
|
| 299 | + | super::addAssembly(&mut generator, assembly); |
|
| 276 | 300 | for func in input.program.fns { super::generateFunction(&mut generator, func, scratch); } |
|
| 277 | 301 | try emit::check(&generator.e) catch err { throw Error::Codegen(err); }; |
|
| 278 | 302 | if codeAddress > 0xffffffffffffffff - generator.e.codeLen as u64 * 4 { throw Error::Range; } |
|
| 279 | 303 | let mut symbols: u32 = 0; |
|
| 280 | 304 | for func in &generator.e.funcs[..] { |
lib/std/arch/rv64/shared/catalog.rad
added
+111 -0
| 1 | + | //! Persistent native boot catalogs for the RV64 record ABI. |
|
| 2 | + | ||
| 3 | + | use std::mem; |
|
| 4 | + | use std::arch::rv64::shared; |
|
| 5 | + | ||
| 6 | + | /// Binary package identity and its resident native descriptor. |
|
| 7 | + | export record Entry: Copy { |
|
| 8 | + | /// Complete immutable binary RIL bytes. |
|
| 9 | + | source: *[u8], |
|
| 10 | + | /// Shared code, exports, and private-state template. |
|
| 11 | + | package: shared::Package, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | /// Reserve aligned bytes in the native catalog output. |
|
| 15 | + | fn reserve(output: &mut [u8], used: &mut u32, size: u32, alignment: u32) -> u32 throws (shared::Error) { |
|
| 16 | + | let padding = (alignment - (*used & (alignment - 1))) & (alignment - 1); |
|
| 17 | + | if padding > output.len - *used { throw shared::Error::Capacity; } |
|
| 18 | + | let offset = *used + padding; |
|
| 19 | + | if size > output.len - offset { throw shared::Error::Capacity; } |
|
| 20 | + | for i in *used..offset { set output[i] = 0; } |
|
| 21 | + | set *used = offset + size; |
|
| 22 | + | return offset; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// View the native bytes of a caller-owned record or array. |
|
| 26 | + | unsafe fn raw(value: &opaque, size: u32) -> *unsafe [u8] { return @sliceOf((value as &u8) as *unsafe u8, size); } |
|
| 27 | + | ||
| 28 | + | /// Obtain a field's byte offset from the compiler's native record layout. |
|
| 29 | + | unsafe fn field(container: &opaque, value: &opaque) -> u32 { return (value as u64 - container as u64) as u32; } |
|
| 30 | + | ||
| 31 | + | /// Write one little-endian integer into a reserved native field. |
|
| 32 | + | fn integer(output: &mut [u8], offset: u32, value: u64, width: u32) { |
|
| 33 | + | for i in 0..width { set output[offset + i] = (value >> (i as u64 * 8)) as u8; } |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | /// Write the RV64 pointer, length, and capacity of a native immutable slice. |
|
| 37 | + | fn slice(output: &mut [u8], offset: u32, address: u64, length: u32) { |
|
| 38 | + | integer(output, offset, address, 8); |
|
| 39 | + | integer(output, offset + 8, length as u64, 4); |
|
| 40 | + | integer(output, offset + 12, length as u64, 4); |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | /// Copy bytes into the catalog and return their target-relative offset. |
|
| 44 | + | fn bytes(input: *[u8], output: &mut [u8], used: &mut u32) -> u32 throws (shared::Error) { |
|
| 45 | + | let offset = try reserve(output, used, input.len, 1); |
|
| 46 | + | try! mem::copy(&mut output[offset..offset + input.len], input); |
|
| 47 | + | return offset; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// Copy a dependency list and each name into native catalog storage. |
|
| 51 | + | unsafe fn dependencies(input: *unsafe [*[u8]], base: u64, output: &mut [u8], used: &mut u32) -> u32 throws (shared::Error) { |
|
| 52 | + | if input.len > output.len / @sizeOf(*[u8]) { throw shared::Error::Capacity; } |
|
| 53 | + | let offset = try reserve(output, used, input.len * @sizeOf(*[u8]), @alignOf(*[u8])); |
|
| 54 | + | for name, i in input { |
|
| 55 | + | let at = try bytes(name, output, used); |
|
| 56 | + | slice(output, offset + i * @sizeOf(*[u8]), base + at as u64, name.len); |
|
| 57 | + | } |
|
| 58 | + | return offset; |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | /// Copy public symbols and their names into native catalog storage. |
|
| 62 | + | unsafe fn symbols(input: *[shared::Symbol], base: u64, output: &mut [u8], used: &mut u32) -> u32 throws (shared::Error) { |
|
| 63 | + | if input.len > output.len / @sizeOf(shared::Symbol) { throw shared::Error::Capacity; } |
|
| 64 | + | let offset = try reserve(output, used, input.len * @sizeOf(shared::Symbol), @alignOf(shared::Symbol)); |
|
| 65 | + | for symbol, i in input { |
|
| 66 | + | let at = offset + i * @sizeOf(shared::Symbol); |
|
| 67 | + | try! mem::copy(&mut output[at..at + @sizeOf(shared::Symbol)], raw(&symbol, @sizeOf(shared::Symbol))); |
|
| 68 | + | let name = try bytes(symbol.name, output, used); |
|
| 69 | + | slice(output, at + field(&symbol, &symbol.name), base + name as u64, symbol.name.len); |
|
| 70 | + | } |
|
| 71 | + | return offset; |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | /// Copy private-state relocation records into native catalog storage. |
|
| 75 | + | unsafe fn relocations(input: *[shared::Relocation], output: &mut [u8], used: &mut u32) -> u32 throws (shared::Error) { |
|
| 76 | + | if input.len > output.len / @sizeOf(shared::Relocation) { throw shared::Error::Capacity; } |
|
| 77 | + | let size = input.len * @sizeOf(shared::Relocation); |
|
| 78 | + | let offset = try reserve(output, used, size, @alignOf(shared::Relocation)); |
|
| 79 | + | try! mem::copy(&mut output[offset..offset + size], raw(input.ptr, size)); |
|
| 80 | + | return offset; |
|
| 81 | + | } |
|
| 82 | + | ||
| 83 | + | /// Pack native Entry records followed by all referenced metadata and source bytes. |
|
| 84 | + | /// Code is placed separately at each package's codeAddress. The target base must |
|
| 85 | + | /// have Entry alignment. Return the used extent. Failure leaves partial output. |
|
| 86 | + | export unsafe fn pack(entries: &[Entry], base: u64, output: &mut [u8]) -> u32 throws (shared::Error) { |
|
| 87 | + | assert @sizeOf(*[u8]) == 16; |
|
| 88 | + | if (base & (@alignOf(Entry) as u64 - 1)) <> 0 { throw shared::Error::Alignment; } |
|
| 89 | + | if base > 0xffffffffffffffff - output.len as u64 { throw shared::Error::Range; } |
|
| 90 | + | if entries.len > output.len / @sizeOf(Entry) { throw shared::Error::Capacity; } |
|
| 91 | + | let mut used: u32 = 0; |
|
| 92 | + | let start = try reserve(output, &mut used, entries.len * @sizeOf(Entry), @alignOf(Entry)); |
|
| 93 | + | for entry, i in entries { |
|
| 94 | + | let at = i * @sizeOf(Entry); |
|
| 95 | + | try! mem::copy(&mut output[at..at + @sizeOf(Entry)], raw(&entry, @sizeOf(Entry))); |
|
| 96 | + | let source = try bytes(entry.source, output, &mut used); |
|
| 97 | + | let name = try bytes(entry.package.name, output, &mut used); |
|
| 98 | + | let required = try dependencies(entry.package.dependencies, base, output, &mut used); |
|
| 99 | + | let exports = try symbols(entry.package.exports, base, output, &mut used); |
|
| 100 | + | let template = try bytes(entry.package.template, output, &mut used); |
|
| 101 | + | let fixups = try relocations(entry.package.relocations, output, &mut used); |
|
| 102 | + | slice(output, at + field(&entry, &entry.source), base + source as u64, entry.source.len); |
|
| 103 | + | slice(output, at + field(&entry, &entry.package.name), base + name as u64, entry.package.name.len); |
|
| 104 | + | slice(output, at + field(&entry, &entry.package.dependencies), base + required as u64, entry.package.dependencies.len); |
|
| 105 | + | slice(output, at + field(&entry, &entry.package.code), entry.package.codeAddress, entry.package.code.len); |
|
| 106 | + | slice(output, at + field(&entry, &entry.package.exports), base + exports as u64, entry.package.exports.len); |
|
| 107 | + | slice(output, at + field(&entry, &entry.package.template), base + template as u64, entry.package.template.len); |
|
| 108 | + | slice(output, at + field(&entry, &entry.package.relocations), base + fixups as u64, entry.package.relocations.len); |
|
| 109 | + | } |
|
| 110 | + | return used; |
|
| 111 | + | } |
lib/std/arch/rv64/shared/tests.rad
+75 -0
| 1 | 1 | //! Private template relocation and bounded linking checks. |
|
| 2 | 2 | ||
| 3 | 3 | use std::testing; |
|
| 4 | + | use std::mem; |
|
| 5 | + | use std::arch::rv64::shared::catalog; |
|
| 6 | + | use std::arch::rv64::asm; |
|
| 4 | 7 | use std::lang::alloc; |
|
| 5 | 8 | use std::lang::il; |
|
| 6 | 9 | use std::lang::il::binary; |
|
| 7 | 10 | use std::lang::gen::data; |
|
| 8 | 11 |
| 18 | 21 | unsafe static DATA: [data::DataSym; 8] = undefined; |
|
| 19 | 22 | /// Persistent initialized bytes. |
|
| 20 | 23 | static TEMPLATE: [u8; 64] = [0; 64]; |
|
| 21 | 24 | /// Persistent private data relocations. |
|
| 22 | 25 | unsafe static RELOCS: [super::Relocation; 8] = undefined; |
|
| 26 | + | /// Aligned storage for a native boot catalog and its retained payloads. |
|
| 27 | + | static CATALOG: [u64; 512] = [0; 512]; |
|
| 28 | + | ||
| 29 | + | /// Exported assembly boundaries occupy the same code extent as their package. |
|
| 30 | + | @test unsafe fn assemblyPrefix() throws (testing::TestError) { |
|
| 31 | + | let input = binary::Package { |
|
| 32 | + | symbols: &[], name: "p", dependencies: &[], |
|
| 33 | + | exports: &[binary::Export { name: "p::entry", kind: binary::ExportKind::Function }], |
|
| 34 | + | entry: "p::entry", program: il::Program { data: &[], fns: &[] }, |
|
| 35 | + | }; |
|
| 36 | + | let assembly = asm::Program { |
|
| 37 | + | text: &[0x02a00513 as u32, 0x00008067], data: &[], |
|
| 38 | + | symbols: &[asm::Symbol { name: "p::entry", section: asm::Section::Text, offset: 0, isExported: true }], |
|
| 39 | + | externalFixups: &[], |
|
| 40 | + | }; |
|
| 41 | + | let mut arena = alloc::new(&mut ARENA[..]); |
|
| 42 | + | let mut scratch = alloc::new(&mut SCRATCH[..]); |
|
| 43 | + | let package = try super::compileAssembly(super::AssemblyInput { package: &input, assembly }, 0, 0x180000000, &[], |
|
| 44 | + | super::Storage { |
|
| 45 | + | data: &mut DATA[..], symbols: &mut SYMBOLS[..], exports: &mut EXPORTS[..], |
|
| 46 | + | template: &mut TEMPLATE[..], relocations: &mut RELOCS[..], |
|
| 47 | + | }, &mut arena, &mut scratch) catch { throw testing::TestError::Failed; }; |
|
| 48 | + | let entry = package.entry else { throw testing::TestError::Failed; }; |
|
| 49 | + | try testing::expect(entry == 0x180000000 and package.code.len == 2); |
|
| 50 | + | try testing::expect(package.code[0] == 0x02a00513 and package.exports[0].target == super::Target::Function(entry)); |
|
| 51 | + | } |
|
| 52 | + | ||
| 53 | + | /// Packed catalog pointers resolve to retained metadata at the supplied native base. |
|
| 54 | + | @test unsafe fn nativeCatalog() throws (testing::TestError) { |
|
| 55 | + | let package = try compile(imports(), 64) catch { throw testing::TestError::Failed; }; |
|
| 56 | + | let output = @sliceOf(&mut CATALOG[0] as *mut u8, @sizeOf([u64; 512])); |
|
| 57 | + | let base = output.ptr as u64; |
|
| 58 | + | let used = try catalog::pack(&[catalog::Entry { source: "trusted binary", package }], base, output) |
|
| 59 | + | catch { throw testing::TestError::Failed; }; |
|
| 60 | + | let storage = &CATALOG[0] as *opaque; |
|
| 61 | + | let entry = storage as *catalog::Entry; |
|
| 62 | + | try testing::expect(used > @sizeOf(catalog::Entry) and used <= output.len); |
|
| 63 | + | try testing::expect(mem::eq(entry.source, "trusted binary") and mem::eq(entry.package.name, "p")); |
|
| 64 | + | try testing::expect(mem::eq(entry.package.dependencies[0], "dep")); |
|
| 65 | + | try testing::expect(entry.package.code.ptr as u64 == package.codeAddress and entry.package.code.len == package.code.len); |
|
| 66 | + | try testing::expect(entry.source.ptr as u64 >= base and entry.source.ptr as u64 < base + used as u64); |
|
| 67 | + | set TEMPLATE[0] = 91; |
|
| 68 | + | set EXPORTS[0].name = "changed"; |
|
| 69 | + | try testing::expect(entry.package.template[0] == 0 and mem::eq(entry.package.exports[0].name, "p::refs")); |
|
| 70 | + | let mut state: [u8; 48] = [255; 48]; |
|
| 71 | + | try super::instantiate(&entry.package, &[0, 0x180002000, 0x180008000], &mut state[..]) |
|
| 72 | + | catch { throw testing::TestError::Failed; }; |
|
| 73 | + | try testing::expect(pointer(&state[..], 0) == 0x180004000 and pointer(&state[..], 16) == 0x180001234); |
|
| 74 | + | let relocated = try catalog::pack(&[catalog::Entry { source: "trusted binary", package }], 0x180000000, output) |
|
| 75 | + | catch { throw testing::TestError::Failed; }; |
|
| 76 | + | try testing::expect(entry.source.ptr as u64 >= 0x180000000 and entry.source.ptr as u64 < 0x180000000 + relocated as u64); |
|
| 77 | + | } |
|
| 78 | + | ||
| 79 | + | /// Catalog bounds and native alignment failures are recoverable. |
|
| 80 | + | @test unsafe fn nativeCatalogBounds() throws (testing::TestError) { |
|
| 81 | + | let package = try compile(imports(), 64) catch { throw testing::TestError::Failed; }; |
|
| 82 | + | let entries = [catalog::Entry { source: "binary", package }]; |
|
| 83 | + | let output = @sliceOf(&mut CATALOG[0] as *mut u8, @sizeOf([u64; 512])); |
|
| 84 | + | let mut failures: u32 = 0; |
|
| 85 | + | try catalog::pack(&entries[..], 0x80000000, &mut output[..@sizeOf(catalog::Entry)]) catch err { |
|
| 86 | + | try testing::expect(err == super::Error::Capacity); set failures += 1; |
|
| 87 | + | }; |
|
| 88 | + | try catalog::pack(&entries[..], 0x80000001, output) catch err { |
|
| 89 | + | try testing::expect(err == super::Error::Alignment); set failures += 1; |
|
| 90 | + | }; |
|
| 91 | + | try catalog::pack(&entries[..], 0xfffffffffffffff8, output) catch err { |
|
| 92 | + | try testing::expect(err == super::Error::Range); set failures += 1; |
|
| 93 | + | }; |
|
| 94 | + | try testing::expect(failures == 3); |
|
| 95 | + | let used = try catalog::pack(&entries[..], 0x80000000, output) catch { throw testing::TestError::Failed; }; |
|
| 96 | + | try testing::expect(used > @sizeOf(catalog::Entry)); |
|
| 97 | + | } |
|
| 23 | 98 | ||
| 24 | 99 | /// Build a package with repeated dependency pointers and a high code pointer. |
|
| 25 | 100 | unsafe fn input() -> binary::Package { |
|
| 26 | 101 | return binary::Package { |
|
| 27 | 102 | symbols: &[], name: "p", dependencies: &["dep"], |
lib/std/lang/lower.rad
+7 -2
| 300 | 300 | /// Current module being lowered. |
|
| 301 | 301 | currentMod: ?u16, |
|
| 302 | 302 | /// Global data items (string literals, constants, static arrays). |
|
| 303 | 303 | /// These become the data sections in the final binary. |
|
| 304 | 304 | data: *mut [il::Data], |
|
| 305 | + | /// First data entry owned by the package currently being lowered. |
|
| 306 | + | packageDataStart: u32, |
|
| 305 | 307 | /// Destination for lowered functions. |
|
| 306 | 308 | output: FnOutput, |
|
| 307 | 309 | /// Map of function symbols to qualified names. |
|
| 308 | 310 | fnSyms: *mut [FnSymEntry], |
|
| 309 | 311 | /// Global error type tag table. Maps nominal types to unique tags. |
| 760 | 762 | resolver: res as *unsafe resolver::Resolver, |
|
| 761 | 763 | moduleGraph: nil, |
|
| 762 | 764 | pkgName, |
|
| 763 | 765 | currentMod: nil, |
|
| 764 | 766 | data: &mut [], |
|
| 767 | + | packageDataStart: 0, |
|
| 765 | 768 | output: FnOutput::Accumulate(&mut []), |
|
| 766 | 769 | fnSyms: &mut [], |
|
| 767 | 770 | errTags: &mut [], |
|
| 768 | 771 | errTagCounter: 1, |
|
| 769 | 772 | options: LowerOptions { debug: false, buildTest: false }, |
| 794 | 797 | resolver: res, |
|
| 795 | 798 | moduleGraph: graph as *unsafe module::ModuleGraph, |
|
| 796 | 799 | pkgName, |
|
| 797 | 800 | currentMod: nil, |
|
| 798 | 801 | data: &mut [], |
|
| 802 | + | packageDataStart: 0, |
|
| 799 | 803 | output: FnOutput::Accumulate(&mut []), |
|
| 800 | 804 | fnSyms: &mut [], |
|
| 801 | 805 | errTags: &mut [], |
|
| 802 | 806 | errTagCounter: 1, |
|
| 803 | 807 | options, |
| 926 | 930 | /// The graph must outlive later uses of the lowerer. |
|
| 927 | 931 | export unsafe fn setPackage(self: &mut Lowerer, graph: &module::ModuleGraph, pkgName: *[u8]) { |
|
| 928 | 932 | set self.moduleGraph = graph as *unsafe module::ModuleGraph; |
|
| 929 | 933 | set self.pkgName = pkgName; |
|
| 930 | 934 | set self.currentMod = nil; |
|
| 935 | + | set self.packageDataStart = self.data.len; |
|
| 931 | 936 | } |
|
| 932 | 937 | ||
| 933 | 938 | /// Create a new function lowerer for a given function type and name. |
|
| 934 | 939 | unsafe fn fnLowerer( |
|
| 935 | 940 | self: &mut Lowerer, |
| 1689 | 1694 | } |
|
| 1690 | 1695 | ||
| 1691 | 1696 | /// Find an existing string data entry with matching content. |
|
| 1692 | 1697 | // TODO: Optimize with hash table or remove? |
|
| 1693 | 1698 | fn findStringData(self: &Lowerer, s: *[u8]) -> ?*[u8] { |
|
| 1694 | - | for d in &self.data[..] { |
|
| 1699 | + | for d in &self.data[self.packageDataStart..] { |
|
| 1695 | 1700 | if d.values.len == 1 { |
|
| 1696 | 1701 | if let case il::DataItem::Str(existing) = d.values[0].item { |
|
| 1697 | 1702 | if mem::eq(existing, s) { |
|
| 1698 | 1703 | return d.name; |
|
| 1699 | 1704 | } |
| 1846 | 1851 | } |
|
| 1847 | 1852 | ||
| 1848 | 1853 | /// Find an existing read-only slice data entry with matching values. |
|
| 1849 | 1854 | // TODO: Optimize with hash table or remove? |
|
| 1850 | 1855 | fn findSliceData(self: &Lowerer, values: *[il::DataValue], alignment: u32) -> ?*[u8] { |
|
| 1851 | - | for d in &self.data[..] { |
|
| 1856 | + | for d in &self.data[self.packageDataStart..] { |
|
| 1852 | 1857 | if d.alignment == alignment and d.readOnly and dataValuesEq(d.values, values) { |
|
| 1853 | 1858 | return d.name; |
|
| 1854 | 1859 | } |
|
| 1855 | 1860 | } |
|
| 1856 | 1861 | return nil; |
std.lib
+1 -0
| 51 | 51 | lib/std/lang/gen/regalloc.rad |
|
| 52 | 52 | lib/std/lang/gen/regalloc/liveness.rad |
|
| 53 | 53 | lib/std/lang/gen/regalloc/spill.rad |
|
| 54 | 54 | lib/std/lang/gen/regalloc/assign.rad |
|
| 55 | 55 | lib/std/arch/rv64/shared.rad |
|
| 56 | + | lib/std/arch/rv64/shared/catalog.rad |
|
| 56 | 57 | lib/std/arch/rv64/atomics.rad |
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
+12 -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 { set fenceFound = true; } |
|
| 51 | + | } |
|
| 52 | + | } |
|
| 53 | + | assert fenceFound, "main: intrinsic instruction"; |
|
| 45 | 54 | assert app.exports.len == 1, "main: app.exports.len == 1"; |
|
| 46 | 55 | assert app.exports[0].kind == binary::ExportKind::Function, "main: app.exports[0].kind == binary::ExportKind::Function"; |
|
| 47 | 56 | let entry = app.entry else panic "main: missing package entry"; |
|
| 48 | 57 | assert mem::eq(entry, "app::main"), "main: mem::eq(entry, \"app::main\")"; |
|
| 49 | 58 | 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(); |