kernel: Load trusted binary RIL
57aa2748e47298fe64332ff3b65f27a36de16871da74be817ae74c9bb005f758
Snapshot readable Page input, compile through the shared RV64 backend, retain native code and catalog metadata, and publish Image handles with rollback on failure. Verify hosted and runtime-loaded execution after compiler workspace release. Align small aggregate return loads exposed by native loader execution while preserving the return ABI. Assisted-by: Codex:gpt-6
1 parent
066b2e8c
Makefile
+7 -2
| 21 | 21 | ||
| 22 | 22 | # Verify the emulator binary exists. |
|
| 23 | 23 | EMU_PATH := $(shell command -v $(EMU) 2>/dev/null) |
|
| 24 | 24 | ||
| 25 | 25 | default: emulator $(RAD_BIN) |
|
| 26 | - | test: emulator seed-test std-test bin-test kernel-test package-test native-test shared-test sync-test kernel-boot-test trap-test page-test |
|
| 26 | + | test: emulator seed-test std-test bin-test kernel-test package-test native-test shared-test sync-test kernel-boot-test trap-test page-test loader-test |
|
| 27 | 27 | ||
| 28 | 28 | seed-test: |
|
| 29 | 29 | @seed/test |
|
| 30 | 30 | ||
| 31 | 31 | # Emulator command check |
| 66 | 66 | lib/std.test.rv64.s \ |
|
| 67 | 67 | lib/std.test.rv64.o |
|
| 68 | 68 | ||
| 69 | 69 | # Kernel modules and tests use a separate package and test entry point. |
|
| 70 | 70 | KERNEL_SRC := kernel/kernel.rad $(shell find kernel/kernel -name '*.rad' ! -name 'tests.rad' ! -path '*/tests/*' 2>/dev/null) |
|
| 71 | - | KERNEL_ASM := kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras |
|
| 71 | + | KERNEL_ASM := kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras |
|
| 72 | 72 | KERNEL_TEST_SRC := kernel/kernel/tests.rad $(shell find kernel/kernel/tests -name '*.rad' 2>/dev/null) |
|
| 73 | 73 | KERNEL := -pkg kernel $(patsubst %,-mod %,$(sort $(KERNEL_SRC))) |
|
| 74 | 74 | KERNEL_TEST := $(BIN_DIR)/kernel.test.rv64 |
|
| 75 | 75 | ||
| 76 | 76 | kernel-test: emulator $(KERNEL_TEST) |
| 196 | 196 | @RAD_EMULATOR="$(EMU)" sh test/trap/run |
|
| 197 | 197 | ||
| 198 | 198 | # Physical frame zeroing at native addresses. |
|
| 199 | 199 | page-test: $(BIN_DIR)/sync.build.rv64 |
|
| 200 | 200 | @RAD_EMULATOR="$(EMU)" sh test/pages/run |
|
| 201 | + | ||
| 202 | + | # Runtime package compilation and execution through the production kernel. |
|
| 203 | + | .PHONY: loader-test |
|
| 204 | + | loader-test: $(RAD_BIN) $(BIN_DIR)/kernel.build.rv64 |
|
| 205 | + | @RAD_EMULATOR="$(EMU)" sh test/loader/run |
kernel/kernel.rad
+1 -0
| 15 | 15 | export mod events; |
|
| 16 | 16 | export mod registry; |
|
| 17 | 17 | export mod frames; |
|
| 18 | 18 | export mod backing; |
|
| 19 | 19 | export mod pages; |
|
| 20 | + | export mod loader; |
|
| 20 | 21 | export mod boot; |
|
| 21 | 22 | @test export mod tests; |
kernel/kernel/loader.rad
added
+264 -0
| 1 | + | //! Bounded trusted-RIL loading and unpublished physical-memory ownership. |
|
| 2 | + | ||
| 3 | + | use std::mem; |
|
| 4 | + | use std::lang::alloc; |
|
| 5 | + | use std::lang::gen::data; |
|
| 6 | + | use std::lang::il::binary; |
|
| 7 | + | use std::lang::il::binary::program; |
|
| 8 | + | use std::arch::rv64; |
|
| 9 | + | use std::arch::rv64::shared; |
|
| 10 | + | use std::arch::rv64::shared::catalog; |
|
| 11 | + | use super::abi; |
|
| 12 | + | use super::limits; |
|
| 13 | + | use super::slots; |
|
| 14 | + | use super::frames; |
|
| 15 | + | use super::pages; |
|
| 16 | + | use super::backing; |
|
| 17 | + | use super::capability; |
|
| 18 | + | use super::registry; |
|
| 19 | + | use super::sync; |
|
| 20 | + | ||
| 21 | + | /// Maximum snapshotted binary RIL size. |
|
| 22 | + | export constant INPUT_BYTES: u32 = 8388608; |
|
| 23 | + | /// Maximum native output extent per load. |
|
| 24 | + | constant CODE_BYTES: u32 = 8388608; |
|
| 25 | + | /// Maximum persistent source and metadata extent per load. |
|
| 26 | + | constant METADATA_BYTES: u32 = 16777216; |
|
| 27 | + | ||
| 28 | + | /// Temporary compiler storage owned by one load. |
|
| 29 | + | export record Workspace: Copy { |
|
| 30 | + | /// Stable input bytes used throughout decoding and compilation. |
|
| 31 | + | input: [u8; INPUT_BYTES], |
|
| 32 | + | /// Decoded binary program and names. |
|
| 33 | + | decoded: [u8; 67108864], |
|
| 34 | + | /// Native emitter state and generated instructions. |
|
| 35 | + | code: [u8; 16777216], |
|
| 36 | + | /// Reusable per-function storage. |
|
| 37 | + | scratch: [u8; 16777216], |
|
| 38 | + | /// Native data layout workspace. |
|
| 39 | + | data: [data::DataSym; 4096], |
|
| 40 | + | /// Local package symbols. |
|
| 41 | + | symbols: [shared::Symbol; 4096], |
|
| 42 | + | /// Public package symbols. |
|
| 43 | + | exports: [shared::Symbol; 4096], |
|
| 44 | + | /// Admitted dependency symbols. |
|
| 45 | + | imports: [shared::Symbol; 8192], |
|
| 46 | + | /// Initialized private-state template. |
|
| 47 | + | template: [u8; 1048576], |
|
| 48 | + | /// Private-state pointer relocations. |
|
| 49 | + | relocations: [shared::Relocation; 4096], |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | /// Physical storage retained by one runtime-loaded package. |
|
| 53 | + | export record Resident: Copy { |
|
| 54 | + | /// Executable frames, including final-page padding. |
|
| 55 | + | code: frames::Run, |
|
| 56 | + | /// Packed catalog entry and retained source bytes. |
|
| 57 | + | metadata: frames::Run, |
|
| 58 | + | } |
|
| 59 | + | ||
| 60 | + | /// Single-loader ownership and persistent frame accounting. |
|
| 61 | + | export record State: Copy { |
|
| 62 | + | /// Workspace occupancy protected by the caller's metadata serialization. |
|
| 63 | + | busy: bool, |
|
| 64 | + | /// Domain generation holding the workspace while busy is true. |
|
| 65 | + | owner: abi::Ref, |
|
| 66 | + | /// True for package slots whose frames are owned by this loader. |
|
| 67 | + | live: [bool; limits::PACKAGES], |
|
| 68 | + | /// Physical extents for live runtime-loaded packages. |
|
| 69 | + | resident: [Resident; limits::PACKAGES], |
|
| 70 | + | } |
|
| 71 | + | ||
| 72 | + | /// Arguments supplied to a trusted image load. |
|
| 73 | + | export record Request: Copy { |
|
| 74 | + | /// Self-Domain allocation authority, including the zero sentinel. |
|
| 75 | + | authority: abi::Handle, |
|
| 76 | + | /// Readable page containing the binary RIL bytes. |
|
| 77 | + | source: abi::Handle, |
|
| 78 | + | /// Byte offset within the source page. |
|
| 79 | + | offset: u64, |
|
| 80 | + | /// Number of source bytes to snapshot. |
|
| 81 | + | length: u64, |
|
| 82 | + | } |
|
| 83 | + | ||
| 84 | + | /// Workspace capacity that must be returned to the same frame pool. |
|
| 85 | + | export union Lease: Once { |
|
| 86 | + | /// Private temporary frames owned by the active load. |
|
| 87 | + | Held(frames::Run), |
|
| 88 | + | } |
|
| 89 | + | ||
| 90 | + | /// Global runtime-loader ownership. |
|
| 91 | + | export unsafe static STATE: State = State { busy: false, owner: abi::Ref { index: 0, generation: 0 }, |
|
| 92 | + | live: [false; limits::PACKAGES], resident: undefined }; |
|
| 93 | + | ||
| 94 | + | /// Map a validated physical workspace extent to its compiler storage. |
|
| 95 | + | /// The caller must hold the exclusive workspace lease until the last access. |
|
| 96 | + | unsafe fn workspace(address: u64) -> *mut Workspace; |
|
| 97 | + | /// Map a validated physical byte extent. |
|
| 98 | + | fn memory(address: u64) -> *unsafe mut u8; |
|
| 99 | + | /// Read the native descriptor at a successfully packed metadata extent. |
|
| 100 | + | fn descriptor(address: u64) -> *unsafe catalog::Entry; |
|
| 101 | + | ||
| 102 | + | /// Reset loader metadata before any workspace or resident allocation exists. |
|
| 103 | + | export fn initialize(state: &mut State) { |
|
| 104 | + | set state.busy = false; |
|
| 105 | + | set state.owner = abi::Ref { index: 0, generation: 0 }; |
|
| 106 | + | for i in 0..limits::PACKAGES { set state.live[i] = false; } |
|
| 107 | + | } |
|
| 108 | + | ||
| 109 | + | /// Acquire the sole compiler workspace and reserve its private physical storage. |
|
| 110 | + | /// The caller serializes workspace ownership and frame-pool mutations. |
|
| 111 | + | export fn acquire(state: &mut State, pool: &mut frames::Pool, owner: abi::Ref) -> Lease throws (abi::Error) { |
|
| 112 | + | if owner.index >= limits::DOMAINS or owner.generation == 0 { throw abi::Error::BadHandle; } |
|
| 113 | + | if state.busy { throw abi::Error::Busy; } |
|
| 114 | + | let count = (@sizeOf(Workspace) + 4095) / 4096; |
|
| 115 | + | let reservation = try frames::reserve(pool, count); |
|
| 116 | + | set state.busy = true; |
|
| 117 | + | set state.owner = owner; |
|
| 118 | + | return Lease::Held(frames::commit(reservation)); |
|
| 119 | + | } |
|
| 120 | + | ||
| 121 | + | /// Return temporary frames and release the compiler workspace under metadata serialization. |
|
| 122 | + | export fn release(state: &mut State, pool: &mut frames::Pool, lease: Lease) { |
|
| 123 | + | match lease { |
|
| 124 | + | case Lease::Held(run) => { try! frames::release(pool, run); }, |
|
| 125 | + | } |
|
| 126 | + | set state.owner = abi::Ref { index: 0, generation: 0 }; |
|
| 127 | + | set state.busy = false; |
|
| 128 | + | } |
|
| 129 | + | ||
| 130 | + | /// Resolve the private workspace while its lease remains held. |
|
| 131 | + | unsafe fn storage(pool: &frames::Pool, lease: &Lease) -> *mut Workspace { |
|
| 132 | + | match lease { |
|
| 133 | + | case Lease::Held(run) => { |
|
| 134 | + | let extent = try! frames::extent(pool, *run); |
|
| 135 | + | assert extent.end - extent.start >= @sizeOf(Workspace) as u64; |
|
| 136 | + | return workspace(extent.start); |
|
| 137 | + | }, |
|
| 138 | + | } |
|
| 139 | + | } |
|
| 140 | + | ||
| 141 | + | /// Validate allocation authority and a readable source range before taking resources. |
|
| 142 | + | fn source(store: &pages::Store, table: &capability::Table, request: Request) -> u64 throws (abi::Error) { |
|
| 143 | + | let authority = try capability::authority(table, request.authority, abi::Rights(abi::ALLOCATE)); |
|
| 144 | + | if authority.object <> table.owner { throw abi::Error::Denied; } |
|
| 145 | + | if not backing::domainLive(&store.backings, table.owner) { throw abi::Error::BadHandle; } |
|
| 146 | + | let entry = try capability::lookup(table, request.source, abi::Kind::Page, abi::Rights(abi::READ)); |
|
| 147 | + | let page = try pages::get(store, entry.object); |
|
| 148 | + | let bytes = page.count as u64 * limits::FRAME_SIZE; |
|
| 149 | + | if request.length == 0 or request.offset > bytes or request.length > bytes - request.offset { throw abi::Error::InvalidArg; } |
|
| 150 | + | if request.length > INPUT_BYTES as u64 { throw abi::Error::Exhausted; } |
|
| 151 | + | return page.base + request.offset; |
|
| 152 | + | } |
|
| 153 | + | ||
| 154 | + | /// Translate bounded backend storage failures without validating trusted IL semantics. |
|
| 155 | + | fn backend(error: shared::Error) -> abi::Error { |
|
| 156 | + | match error { |
|
| 157 | + | case shared::Error::Capacity => return abi::Error::Exhausted, |
|
| 158 | + | case shared::Error::Codegen(inner) => { |
|
| 159 | + | if inner == rv64::Error::Allocation or inner == rv64::Error::Capacity { return abi::Error::Exhausted; } |
|
| 160 | + | return abi::Error::VerifyFailed; |
|
| 161 | + | }, |
|
| 162 | + | else => return abi::Error::VerifyFailed, |
|
| 163 | + | } |
|
| 164 | + | } |
|
| 165 | + | ||
| 166 | + | /// Used page counts for unpublished native output. |
|
| 167 | + | record OutputSize: Copy { |
|
| 168 | + | /// Pages occupied by generated code. |
|
| 169 | + | code: u32, |
|
| 170 | + | /// Pages occupied by the packed native catalog. |
|
| 171 | + | metadata: u32, |
|
| 172 | + | } |
|
| 173 | + | ||
| 174 | + | /// Compile and pack one candidate into caller-owned physical output extents. |
|
| 175 | + | unsafe fn generate(work: *mut Workspace, input: &binary::Package, packages: ®istry::Store, |
|
| 176 | + | slot: u32, code: u64, metadata: u64, length: u32) -> OutputSize throws (abi::Error) |
|
| 177 | + | { |
|
| 178 | + | let count = try registry::imports(packages, &input.dependencies[..], &mut work.imports[..]); |
|
| 179 | + | let mut arena = alloc::new(&mut work.code[..]); |
|
| 180 | + | let mut scratch = alloc::new(&mut work.scratch[..]); |
|
| 181 | + | let package = try shared::compile(&*input, slot, code, &work.imports[..count], |
|
| 182 | + | shared::Storage { |
|
| 183 | + | data: &mut work.data[..], symbols: &mut work.symbols[..], exports: &mut work.exports[..], |
|
| 184 | + | template: &mut work.template[..], relocations: &mut work.relocations[..], |
|
| 185 | + | }, &mut arena, &mut scratch) catch error { throw backend(error); }; |
|
| 186 | + | if package.code.len > CODE_BYTES / 4 { throw abi::Error::Exhausted; } |
|
| 187 | + | let codeBytes = package.code.len * 4; |
|
| 188 | + | let codeMemory = @sliceOf(memory(code), CODE_BYTES); |
|
| 189 | + | try! mem::copy(&mut codeMemory[..codeBytes], @sliceOf(package.code.ptr as *u8, codeBytes)); |
|
| 190 | + | let metadataMemory = @sliceOf(memory(metadata), METADATA_BYTES); |
|
| 191 | + | let used = try catalog::pack(&[catalog::Entry { source: &work.input[..length], package }], metadata, metadataMemory) |
|
| 192 | + | catch error { throw backend(error); }; |
|
| 193 | + | let mut codePages = (codeBytes + 4095) / 4096; |
|
| 194 | + | if codePages == 0 { set codePages = 1; } |
|
| 195 | + | return OutputSize { code: codePages, metadata: (used + 4095) / 4096 }; |
|
| 196 | + | } |
|
| 197 | + | ||
| 198 | + | /// Return unused output frames while preserving at least one frame for each extent. |
|
| 199 | + | fn trim(pool: &mut frames::Pool, run: frames::Run, count: u32) -> frames::Run { |
|
| 200 | + | assert count > 0 and count <= run.count; |
|
| 201 | + | if count < run.count { try! frames::release(pool, frames::Run { first: run.first + count, count: run.count - count }); } |
|
| 202 | + | return frames::Run { first: run.first, count }; |
|
| 203 | + | } |
|
| 204 | + | ||
| 205 | + | /// Snapshot, decode, compile, and publish a package while its caller remains live. |
|
| 206 | + | unsafe fn produce(state: &mut State, pool: &mut frames::Pool, packages: &mut registry::Store, |
|
| 207 | + | work: *mut Workspace, address: u64, length: u32) -> abi::Ref throws (abi::Error) |
|
| 208 | + | { |
|
| 209 | + | try! mem::copy(&mut work.input[..length], @sliceOf(memory(address), length)); |
|
| 210 | + | let mut decoder = alloc::new(&mut work.decoded[..]); |
|
| 211 | + | let input = try program::decode(&work.input[..length], &mut decoder, binary::Limits { registers: 8192, blocks: 4096 }) catch err { |
|
| 212 | + | if err == binary::Error::Storage { throw abi::Error::Exhausted; } |
|
| 213 | + | throw abi::Error::VerifyFailed; |
|
| 214 | + | }; |
|
| 215 | + | let existing = try registry::identify(packages, &input.name[..], &work.input[..length]); |
|
| 216 | + | if let object = existing { return object; } |
|
| 217 | + | let slot = try registry::reserve(packages); |
|
| 218 | + | let object = slots::reference(&slot); |
|
| 219 | + | let codeReservation = try frames::reserve(pool, CODE_BYTES / 4096) catch err { registry::cancel(packages, slot); throw err; }; |
|
| 220 | + | let code = frames::commit(codeReservation); |
|
| 221 | + | let metadataReservation = try frames::reserve(pool, METADATA_BYTES / 4096) catch err { |
|
| 222 | + | try! frames::release(pool, code); registry::cancel(packages, slot); throw err; |
|
| 223 | + | }; |
|
| 224 | + | let metadata = frames::commit(metadataReservation); |
|
| 225 | + | let codeRange = try! frames::extent(pool, code); |
|
| 226 | + | let metadataRange = try! frames::extent(pool, metadata); |
|
| 227 | + | let sizes = try generate(work, &input, packages, object.index, codeRange.start, metadataRange.start, length) catch err { |
|
| 228 | + | try! frames::release(pool, metadata); try! frames::release(pool, code); |
|
| 229 | + | registry::cancel(packages, slot); throw err; |
|
| 230 | + | }; |
|
| 231 | + | let retainedCode = trim(pool, code, sizes.code); |
|
| 232 | + | let retainedMetadata = trim(pool, metadata, sizes.metadata); |
|
| 233 | + | let native = descriptor(metadataRange.start); |
|
| 234 | + | sync::syncInstructions(); |
|
| 235 | + | let result = try registry::publish(packages, slot, native.source, native.package) catch err { |
|
| 236 | + | try! frames::release(pool, retainedMetadata); try! frames::release(pool, retainedCode); throw err; |
|
| 237 | + | }; |
|
| 238 | + | assert result == object; |
|
| 239 | + | set state.resident[result.index] = Resident { code: retainedCode, metadata: retainedMetadata }; |
|
| 240 | + | set state.live[result.index] = true; |
|
| 241 | + | return result; |
|
| 242 | + | } |
|
| 243 | + | ||
| 244 | + | /// Load trusted RIL and install an Image capability after complete native publication. |
|
| 245 | + | /// The caller keeps its domain alive and serializes shared metadata transactions. |
|
| 246 | + | export unsafe fn load(state: &mut State, store: &mut pages::Store, packages: &mut registry::Store, |
|
| 247 | + | table: &mut capability::Table, request: Request) -> abi::Handle throws (abi::Error) |
|
| 248 | + | { |
|
| 249 | + | let address = try source(store, table, request); |
|
| 250 | + | let handle = try slots::reserve(&mut table.slots[..]); |
|
| 251 | + | let lease = try acquire(state, &mut store.backings.pool, table.owner) catch err { |
|
| 252 | + | try! slots::cancel(&mut table.slots[..], handle); throw err; |
|
| 253 | + | }; |
|
| 254 | + | let work = storage(&store.backings.pool, &lease); |
|
| 255 | + | let object = try produce(state, &mut store.backings.pool, packages, work, address, request.length as u32) catch err { |
|
| 256 | + | release(state, &mut store.backings.pool, lease); |
|
| 257 | + | try! slots::cancel(&mut table.slots[..], handle); throw err; |
|
| 258 | + | }; |
|
| 259 | + | let result = capability::publish(table, handle, capability::Entry { |
|
| 260 | + | kind: abi::Kind::Image, object, rights: abi::Rights(registry::IMAGE_RIGHTS), |
|
| 261 | + | }); |
|
| 262 | + | release(state, &mut store.backings.pool, lease); |
|
| 263 | + | return result; |
|
| 264 | + | } |
kernel/kernel/loader.ras
added
+17 -0
| 1 | + | //! Physical views of validated loader-owned extents. |
|
| 2 | + | .text; |
|
| 3 | + | .export @kernel::loader::workspace; |
|
| 4 | + | .export @kernel::loader::memory; |
|
| 5 | + | .export @kernel::loader::descriptor; |
|
| 6 | + | ||
| 7 | + | // A validated workspace run contains one complete aligned Workspace record. |
|
| 8 | + | @kernel::loader::workspace |
|
| 9 | + | ret; |
|
| 10 | + | ||
| 11 | + | // A validated physical extent supplies the caller's mapped byte view. |
|
| 12 | + | @kernel::loader::memory |
|
| 13 | + | ret; |
|
| 14 | + | ||
| 15 | + | // Successful packing initializes one native catalog entry at this address. |
|
| 16 | + | @kernel::loader::descriptor |
|
| 17 | + | ret; |
kernel/kernel/tests.rad
+1 -0
| 11 | 11 | export mod backing; |
|
| 12 | 12 | export mod pages; |
|
| 13 | 13 | export mod transactions; |
|
| 14 | 14 | export mod events; |
|
| 15 | 15 | export mod registry; |
|
| 16 | + | export mod loader; |
kernel/kernel/tests/loader.rad
added
+207 -0
| 1 | + | //! Trusted binary loading, resident snapshots, and resource rollback. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use std::arch::rv64::shared; |
|
| 5 | + | use std::lang::il; |
|
| 6 | + | use std::lang::il::binary; |
|
| 7 | + | use std::lang::il::binary::program; |
|
| 8 | + | use kernel::abi; |
|
| 9 | + | use kernel::limits; |
|
| 10 | + | use kernel::slots; |
|
| 11 | + | use kernel::pages; |
|
| 12 | + | use kernel::frames; |
|
| 13 | + | use kernel::backing; |
|
| 14 | + | use kernel::capability; |
|
| 15 | + | use kernel::registry; |
|
| 16 | + | use kernel::loader; |
|
| 17 | + | ||
| 18 | + | /// Mapped frames for temporary compiler storage and persistent native output. |
|
| 19 | + | static RAM: [u64; 18874368] = [0; 18874368]; |
|
| 20 | + | /// Physical frame and page metadata. |
|
| 21 | + | unsafe static PAGES: pages::Store = undefined; |
|
| 22 | + | /// Resident package registry. |
|
| 23 | + | unsafe static PACKAGES: registry::Store = undefined; |
|
| 24 | + | /// Loader ownership and resident-frame accounting. |
|
| 25 | + | unsafe static LOADER: loader::State = undefined; |
|
| 26 | + | /// Calling domain's capability table. |
|
| 27 | + | unsafe static TABLE: capability::Table = undefined; |
|
| 28 | + | /// Source page allocated by the calling domain. |
|
| 29 | + | static SOURCE: abi::Handle = abi::Handle(0); |
|
| 30 | + | /// Byte offset of the first aligned test frame. |
|
| 31 | + | static OFFSET: u32 = 0; |
|
| 32 | + | ||
| 33 | + | /// Initialize a live domain and one source page over actual mapped memory. |
|
| 34 | + | unsafe fn initialize() { |
|
| 35 | + | slots::initialize(&mut PAGES.slots[..]); |
|
| 36 | + | backing::initialize(&mut PAGES.backings); |
|
| 37 | + | registry::initialize(&mut PACKAGES); |
|
| 38 | + | loader::initialize(&mut LOADER); |
|
| 39 | + | let pointer = &RAM[0]; |
|
| 40 | + | let base = pointer as u64; |
|
| 41 | + | let aligned = (base + 4095) & ~4095; |
|
| 42 | + | set OFFSET = (aligned - base) as u32; |
|
| 43 | + | set PAGES.backings.pool.count = (@sizeOf([u64; 18874368]) - OFFSET) / 4096; |
|
| 44 | + | for i in 0..PAGES.backings.pool.count { |
|
| 45 | + | set PAGES.backings.pool.addresses[i] = aligned + i as u64 * 4096; |
|
| 46 | + | set PAGES.backings.pool.free[i] = true; |
|
| 47 | + | } |
|
| 48 | + | let owner = abi::Ref { index: 0, generation: 1 }; |
|
| 49 | + | capability::initialize(&mut TABLE, owner); |
|
| 50 | + | try! backing::registerDomain(&mut PAGES.backings, owner); |
|
| 51 | + | let authority = try! capability::install(&mut TABLE, capability::Entry { |
|
| 52 | + | kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE), |
|
| 53 | + | }); |
|
| 54 | + | set SOURCE = try! pages::allocate(&mut PAGES, &mut TABLE, abi::Handle(0), 1); |
|
| 55 | + | } |
|
| 56 | + | ||
| 57 | + | /// Encode one small function into the source page, optionally naming an absent export. |
|
| 58 | + | unsafe fn input(value: i64, badExport: bool) -> loader::Request { |
|
| 59 | + | return inputWithDependencies(value, badExport, &[]); |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | /// Encode declared dependencies with a constant-return entry function. |
|
| 63 | + | unsafe fn inputWithDependencies(value: i64, badExport: bool, dependencies: *[*[u8]]) -> loader::Request { |
|
| 64 | + | let mut instructions = [il::Instr::Ret { val: il::Val::Imm(value) }]; |
|
| 65 | + | let function = il::Fn { |
|
| 66 | + | name: "loaded::main", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
|
| 67 | + | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }], |
|
| 68 | + | }; |
|
| 69 | + | let mut name: *[u8] = "loaded::main"; |
|
| 70 | + | if badExport { set name = "missing"; } |
|
| 71 | + | let package = binary::Package { |
|
| 72 | + | symbols: &["loaded", "loaded::main", "entry", "missing"], name: "loaded", dependencies: (&dependencies[..]) as *unsafe [*[u8]], |
|
| 73 | + | exports: &[binary::Export { name, kind: binary::ExportKind::Function }], entry: "loaded::main", |
|
| 74 | + | program: il::Program { data: &[], fns: &[&function] }, |
|
| 75 | + | }; |
|
| 76 | + | let bytes = @sliceOf(&mut RAM[0] as *mut u8, @sizeOf([u64; 18874368])); |
|
| 77 | + | let length = try! program::encode(&mut bytes[OFFSET..OFFSET + 4096], &package); |
|
| 78 | + | return loader::Request { authority: abi::Handle(0), source: SOURCE, offset: 0, length: length as u64 }; |
|
| 79 | + | } |
|
| 80 | + | ||
| 81 | + | /// Count available frames to detect leaked temporary or unpublished output storage. |
|
| 82 | + | unsafe fn free() -> u32 { |
|
| 83 | + | let mut count: u32 = 0; |
|
| 84 | + | for i in 0..PAGES.backings.pool.count { if PAGES.backings.pool.free[i] { set count += 1; } } |
|
| 85 | + | return count; |
|
| 86 | + | } |
|
| 87 | + | ||
| 88 | + | /// Resident source bytes survive mutation of the source page and workspace release. |
|
| 89 | + | @test unsafe fn residentSnapshot() throws (testing::TestError) { |
|
| 90 | + | initialize(); |
|
| 91 | + | let request = input(43, false); |
|
| 92 | + | let before = free(); |
|
| 93 | + | let handle = try! loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, request); |
|
| 94 | + | let object = try! registry::image(&PACKAGES, &TABLE, handle, abi::Rights(abi::EXECUTE)); |
|
| 95 | + | let package = try! registry::get(&PACKAGES, object); |
|
| 96 | + | try testing::expect(package.code.len > 0 and package.entry <> nil); |
|
| 97 | + | let retained = LOADER.resident[object.index]; |
|
| 98 | + | try testing::expect(free() + retained.code.count + retained.metadata.count == before); |
|
| 99 | + | try testing::expect(not LOADER.busy and LOADER.owner.generation == 0); |
|
| 100 | + | let bytes = @sliceOf(&mut RAM[0] as *mut u8, @sizeOf([u64; 18874368])); |
|
| 101 | + | let magic = bytes[OFFSET]; |
|
| 102 | + | set bytes[OFFSET] = 0; |
|
| 103 | + | try testing::expect(PACKAGES.entries[object.index].source[0] == magic); |
|
| 104 | + | set bytes[OFFSET] = magic; |
|
| 105 | + | let duplicate = try! loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, request); |
|
| 106 | + | try testing::expect(duplicate <> handle); |
|
| 107 | + | try testing::expect(try! registry::image(&PACKAGES, &TABLE, duplicate, abi::Rights(0)) == object); |
|
| 108 | + | try testing::expect(free() + retained.code.count + retained.metadata.count == before); |
|
| 109 | + | } |
|
| 110 | + | ||
| 111 | + | /// Decode and linking failures return all unpublished frames and capability capacity. |
|
| 112 | + | @test unsafe fn rollback() throws (testing::TestError) { |
|
| 113 | + | initialize(); |
|
| 114 | + | let mut request = input(43, false); |
|
| 115 | + | let before = free(); |
|
| 116 | + | set request.length = 3; |
|
| 117 | + | let mut failures: u32 = 0; |
|
| 118 | + | try loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, request) catch err { |
|
| 119 | + | try testing::expect(err == abi::Error::VerifyFailed); set failures += 1; |
|
| 120 | + | }; |
|
| 121 | + | set request = input(43, true); |
|
| 122 | + | try loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, request) catch err { |
|
| 123 | + | try testing::expect(err == abi::Error::VerifyFailed); set failures += 1; |
|
| 124 | + | }; |
|
| 125 | + | try testing::expect(failures == 2 and free() == before and not LOADER.busy); |
|
| 126 | + | try testing::expect(TABLE.slots[2].state == slots::State::Free and PACKAGES.slots[0].state == slots::State::Free); |
|
| 127 | + | let valid = input(43, false); |
|
| 128 | + | let handle = try! loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, valid); |
|
| 129 | + | let object = try! registry::image(&PACKAGES, &TABLE, handle, abi::Rights(0)); |
|
| 130 | + | try testing::expect(object.index == 0); |
|
| 131 | + | } |
|
| 132 | + | ||
| 133 | + | /// Contention and workspace allocation failure preserve source authority and clear workspace occupancy. |
|
| 134 | + | @test unsafe fn workspaceOwnership() throws (testing::TestError) { |
|
| 135 | + | initialize(); |
|
| 136 | + | let request = input(43, false); |
|
| 137 | + | let before = free(); |
|
| 138 | + | let lease = try! loader::acquire(&mut LOADER, &mut PAGES.backings.pool, TABLE.owner); |
|
| 139 | + | let mut busy = false; |
|
| 140 | + | try loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, request) catch err { |
|
| 141 | + | try testing::expect(err == abi::Error::Busy); set busy = true; |
|
| 142 | + | }; |
|
| 143 | + | try testing::expect(busy and LOADER.busy and LOADER.owner == TABLE.owner); |
|
| 144 | + | loader::release(&mut LOADER, &mut PAGES.backings.pool, lease); |
|
| 145 | + | try testing::expect(free() == before); |
|
| 146 | + | let count = PAGES.backings.pool.count; |
|
| 147 | + | set PAGES.backings.pool.count = 32; |
|
| 148 | + | let mut exhausted = false; |
|
| 149 | + | try loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, request) catch err { |
|
| 150 | + | try testing::expect(err == abi::Error::OutOfMemory); set exhausted = true; |
|
| 151 | + | }; |
|
| 152 | + | set PAGES.backings.pool.count = count; |
|
| 153 | + | try testing::expect(exhausted and free() == before and not LOADER.busy); |
|
| 154 | + | try testing::expect(TABLE.slots[2].state == slots::State::Free); |
|
| 155 | + | } |
|
| 156 | + | ||
| 157 | + | /// Output allocation failures cancel registry admission and return compiler frames. |
|
| 158 | + | @test unsafe fn outputAllocationRollback() throws (testing::TestError) { |
|
| 159 | + | initialize(); |
|
| 160 | + | let request = input(43, false); |
|
| 161 | + | let before = free(); |
|
| 162 | + | let workspacePages = (@sizeOf(loader::Workspace) + 4095) / 4096; |
|
| 163 | + | for extra in &[0 as u32, 2048] { |
|
| 164 | + | let reservation = try! frames::reserve(&mut PAGES.backings.pool, before - workspacePages - extra); |
|
| 165 | + | let held = frames::commit(reservation); |
|
| 166 | + | let available = free(); |
|
| 167 | + | let mut failed = false; |
|
| 168 | + | try loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, request) catch err { |
|
| 169 | + | try testing::expect(err == abi::Error::OutOfMemory); set failed = true; |
|
| 170 | + | }; |
|
| 171 | + | try testing::expect(failed and free() == available and not LOADER.busy); |
|
| 172 | + | try testing::expect(TABLE.slots[2].state == slots::State::Free); |
|
| 173 | + | try testing::expect(PACKAGES.slots[0].state == slots::State::Free); |
|
| 174 | + | try! frames::release(&mut PAGES.backings.pool, held); |
|
| 175 | + | try testing::expect(free() == before); |
|
| 176 | + | } |
|
| 177 | + | let handle = try! loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, request); |
|
| 178 | + | let object = try! registry::image(&PACKAGES, &TABLE, handle, abi::Rights(0)); |
|
| 179 | + | try testing::expect(object.index == 0); |
|
| 180 | + | } |
|
| 181 | + | ||
| 182 | + | /// Admission rejects duplicate dependencies after compilation and returns native output. |
|
| 183 | + | @test unsafe fn publicationRollback() throws (testing::TestError) { |
|
| 184 | + | initialize(); |
|
| 185 | + | let pending = try! registry::reserve(&mut PACKAGES); |
|
| 186 | + | let reference = slots::reference(&pending); |
|
| 187 | + | let dependency = shared::Package { |
|
| 188 | + | name: "missing", dependencies: &[], slot: reference.index, codeAddress: 0, |
|
| 189 | + | code: &[], exports: &[], entry: nil, template: &[], memory: 0, |
|
| 190 | + | alignment: 8, relocations: &[], |
|
| 191 | + | }; |
|
| 192 | + | let resident = try! registry::publish(&mut PACKAGES, pending, "dependency", dependency); |
|
| 193 | + | let request = inputWithDependencies(43, false, &["missing", "missing"]); |
|
| 194 | + | let before = free(); |
|
| 195 | + | let mut failed = false; |
|
| 196 | + | try loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, request) catch err { |
|
| 197 | + | try testing::expect(err == abi::Error::VerifyFailed); set failed = true; |
|
| 198 | + | }; |
|
| 199 | + | try testing::expect(failed and free() == before and not LOADER.busy); |
|
| 200 | + | try testing::expect(TABLE.slots[2].state == slots::State::Free); |
|
| 201 | + | try testing::expect(PACKAGES.slots[1].state == slots::State::Free and not LOADER.live[1]); |
|
| 202 | + | try testing::expect(slots::matches(&PACKAGES.slots[..], resident, slots::State::Live)); |
|
| 203 | + | let valid = inputWithDependencies(43, false, &["missing"]); |
|
| 204 | + | let handle = try! loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, valid); |
|
| 205 | + | let object = try! registry::image(&PACKAGES, &TABLE, handle, abi::Rights(0)); |
|
| 206 | + | try testing::expect(object.index == 1 and PACKAGES.counts[1] == 1); |
|
| 207 | + | } |
lib/std/lang/lower.rad
+11 -1
| 5773 | 5773 | else resolver::getTypeLayout(*self.fnType.returnType).size; |
|
| 5774 | 5774 | ||
| 5775 | 5775 | emit(self, il::Instr::Blit { dst: retReg, src, size: il::Val::Imm(size as i64) }); |
|
| 5776 | 5776 | emit(self, il::Instr::Ret { val: il::Val::Reg(retReg) }); |
|
| 5777 | 5777 | } else if isSmallAggregate(*self.fnType.returnType) { |
|
| 5778 | - | let src = emitValToReg(self, val); |
|
| 5778 | + | let mut src = emitValToReg(self, val); |
|
| 5779 | + | let layout = resolver::getTypeLayout(*self.fnType.returnType); |
|
| 5780 | + | if layout.alignment < resolver::PTR_SIZE or layout.size < resolver::PTR_SIZE { |
|
| 5781 | + | // The return word must not read beyond the value or its alignment. |
|
| 5782 | + | let word = emitReserveLayout(self, resolver::Layout { |
|
| 5783 | + | size: resolver::PTR_SIZE, alignment: resolver::PTR_SIZE, |
|
| 5784 | + | }); |
|
| 5785 | + | emitStoreW64At(self, il::Val::Imm(0), word, 0); |
|
| 5786 | + | emit(self, il::Instr::Blit { dst: word, src, size: il::Val::Imm(layout.size as i64) }); |
|
| 5787 | + | set src = word; |
|
| 5788 | + | } |
|
| 5779 | 5789 | let dst = nextReg(self); |
|
| 5780 | 5790 | ||
| 5781 | 5791 | emit(self, il::Instr::Load { typ: il::Type::W64, dst, src, offset: 0 }); |
|
| 5782 | 5792 | emit(self, il::Instr::Ret { val: il::Val::Reg(dst) }); |
|
| 5783 | 5793 | } else { |
test/acceptance/compile
added
+15 -0
| 1 | + | #!/bin/sh |
|
| 2 | + | # Compile std and a staged kernel fixture from the repository root. |
|
| 3 | + | # Arguments: emulator path, fixture directory. |
|
| 4 | + | set -eu |
|
| 5 | + | emulator=$1 |
|
| 6 | + | work=$2 |
|
| 7 | + | set -- -pkg std |
|
| 8 | + | while IFS= read -r source; do set -- "$@" -mod "$source"; done < std.lib |
|
| 9 | + | set -- "$@" -pkg kernel -mod "$work/kernel.rad" |
|
| 10 | + | for source in "$work"/kernel/*.rad; do |
|
| 11 | + | case "$source" in */tests.rad) continue ;; esac |
|
| 12 | + | set -- "$@" -mod "$source" |
|
| 13 | + | done |
|
| 14 | + | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 \ |
|
| 15 | + | -run bin/radiance.rv64.dev "$@" -entry kernel -ril "$work" |
test/boot/run
+2 -2
| 2 | 2 | # Boot the kernel through its production initialization on each supported hart count. |
|
| 3 | 3 | set -eu |
|
| 4 | 4 | emulator=${RAD_EMULATOR:-emulator} |
|
| 5 | 5 | work=$(mktemp -d) |
|
| 6 | 6 | trap 'rm -rf "$work"' EXIT HUP INT TERM |
|
| 7 | - | cat test/boot/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras > "$work/boot.ras" |
|
| 7 | + | cat test/boot/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras > "$work/boot.ras" |
|
| 8 | 8 | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/boot.ras" "$work/boot.rv64" |
|
| 9 | 9 | for harts in 1 2 8; do |
|
| 10 | 10 | # The deterministic emulator shares its tick budget across all harts. |
|
| 11 | 11 | steps=$((10000000 * harts)) |
|
| 12 | 12 | if ! "$emulator" -machine -max-steps="$steps" -harts="$harts" -run "$work/boot.rv64" > "$work/log" 2>&1; then |
| 28 | 28 | if [ "$status" -ne 2 ] || [ "$(grep -c 'wfi=1 mcause=0x0' "$work/log")" -ne 8 ]; then |
|
| 29 | 29 | cat "$work/log" >&2 |
|
| 30 | 30 | exit 1 |
|
| 31 | 31 | fi |
|
| 32 | 32 | printf 'kernel startup: all eight harts reached machine idle without traps\n' |
|
| 33 | - | cat test/boot/trap.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras > "$work/trap.ras" |
|
| 33 | + | cat test/boot/trap.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras > "$work/trap.ras" |
|
| 34 | 34 | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/trap.ras" "$work/trap.rv64" |
|
| 35 | 35 | status=0 |
|
| 36 | 36 | "$emulator" -machine -harts=2 -max-steps=20000000 -run "$work/trap.rv64" > "$work/log" 2>&1 || status=$? |
|
| 37 | 37 | if [ "$status" -ne 2 ] || [ "$(grep -c '^kernel: unexpected trap$' "$work/log")" -ne 1 ] \ |
|
| 38 | 38 | || [ "$(grep -c 'wfi=1' "$work/log")" -ne 2 ]; then |
test/loader/kernel/loadcheck.rad
added
+42 -0
| 1 | + | //! Execute runtime-loaded native code after releasing its compiler workspace. |
|
| 2 | + | ||
| 3 | + | use std::mem; |
|
| 4 | + | use kernel::abi; |
|
| 5 | + | use kernel::backing; |
|
| 6 | + | use kernel::capability; |
|
| 7 | + | use kernel::pages; |
|
| 8 | + | use kernel::registry; |
|
| 9 | + | use kernel::loader; |
|
| 10 | + | use kernel::loadinput; |
|
| 11 | + | ||
| 12 | + | /// Capability storage for the loading domain. |
|
| 13 | + | unsafe static TABLE: capability::Table = undefined; |
|
| 14 | + | ||
| 15 | + | /// View a validated source Page as mapped bytes. |
|
| 16 | + | fn memory(address: u64) -> *mut u8; |
|
| 17 | + | ||
| 18 | + | /// Call a published entry that does not access private package state. |
|
| 19 | + | fn invoke(address: u64) -> i64; |
|
| 20 | + | ||
| 21 | + | /// Load the hosted comparison package and execute its retained native instructions. |
|
| 22 | + | export unsafe fn run() { |
|
| 23 | + | let owner = abi::Ref { index: 0, generation: 1 }; |
|
| 24 | + | capability::initialize(&mut TABLE, owner); |
|
| 25 | + | try! backing::registerDomain(&mut pages::STORE.backings, owner); |
|
| 26 | + | let authority = try! capability::install(&mut TABLE, capability::Entry { |
|
| 27 | + | kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE), |
|
| 28 | + | }); |
|
| 29 | + | let source = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1); |
|
| 30 | + | let cap = try! capability::lookup(&TABLE, source, abi::Kind::Page, abi::Rights(abi::READ)); |
|
| 31 | + | let page = try! pages::get(&pages::STORE, cap.object); |
|
| 32 | + | let bytes = @sliceOf(memory(page.base), 4096); |
|
| 33 | + | let length = try! mem::copy(&mut bytes[..], &loadinput::INPUT[..]); |
|
| 34 | + | let handle = try! loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut TABLE, |
|
| 35 | + | loader::Request { authority, source, offset: 0, length: length as u64 }); |
|
| 36 | + | assert not loader::STATE.busy; |
|
| 37 | + | let object = try! registry::image(®istry::STORE, &TABLE, handle, abi::Rights(abi::EXECUTE)); |
|
| 38 | + | let package = try! registry::get(®istry::STORE, object); |
|
| 39 | + | let entry = package.entry else panic "loaded entry"; |
|
| 40 | + | for i in 0..length { set bytes[i] = 0; } |
|
| 41 | + | assert invoke(entry) == 43; |
|
| 42 | + | } |
test/loader/loaded.rad
added
+7 -0
| 1 | + | //! A package executed by both hosted compilation and the kernel loader. |
|
| 2 | + | ||
| 3 | + | /// Compute a result through a package-local call. |
|
| 4 | + | fn calculate(value: i64) -> i64 { return value * 6 + 1; } |
|
| 5 | + | ||
| 6 | + | /// Return the result checked by both execution paths. |
|
| 7 | + | @default fn main() -> i32 { return calculate(7) as i32; } |
test/loader/machine.ras
added
+22 -0
| 1 | + | //! Run the loader fixture after all harts complete production initialization. |
|
| 2 | + | .text; |
|
| 3 | + | call @kernel::boot::initialize; |
|
| 4 | + | beqz %a0 @wait; |
|
| 5 | + | call @kernel::loadcheck::run; |
|
| 6 | + | li %t0 0x10001000; |
|
| 7 | + | li %t1 0x5555; |
|
| 8 | + | sw %t1 0(%t0); |
|
| 9 | + | @wait |
|
| 10 | + | wfi; |
|
| 11 | + | j @wait; |
|
| 12 | + | ||
| 13 | + | .export @kernel::loadcheck::memory; |
|
| 14 | + | .export @kernel::loadcheck::invoke; |
|
| 15 | + | ||
| 16 | + | // The caller supplies an address within its validated source Page. |
|
| 17 | + | @kernel::loadcheck::memory |
|
| 18 | + | ret; |
|
| 19 | + | ||
| 20 | + | // Tail-call published code with no private-state accesses. |
|
| 21 | + | @kernel::loadcheck::invoke |
|
| 22 | + | jalr %zero %a0 0; |
test/loader/run
added
+29 -0
| 1 | + | #!/bin/sh |
|
| 2 | + | # Build a kernel fixture and call code produced by its runtime loader. |
|
| 3 | + | set -eu |
|
| 4 | + | emulator=${RAD_EMULATOR:-emulator} |
|
| 5 | + | work=$(mktemp -d) |
|
| 6 | + | trap 'rm -rf "$work"' EXIT HUP INT TERM |
|
| 7 | + | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \ |
|
| 8 | + | -run bin/radiance.rv64.dev -pkg loaded -mod test/loader/loaded.rad -entry loaded -ril "$work" |
|
| 9 | + | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \ |
|
| 10 | + | -run bin/radiance.rv64.dev -pkg loaded -mod test/loader/loaded.rad -entry loaded -o "$work/hosted.rv64" |
|
| 11 | + | status=0 |
|
| 12 | + | "$emulator" -run "$work/hosted.rv64" || status=$? |
|
| 13 | + | test "$status" -eq 43 |
|
| 14 | + | cat kernel/kernel.rad > "$work/kernel.rad" |
|
| 15 | + | printf '\nexport mod loadinput;\nexport mod loadcheck;\n' >> "$work/kernel.rad" |
|
| 16 | + | mkdir "$work/kernel" |
|
| 17 | + | cp kernel/kernel/*.rad "$work/kernel/" |
|
| 18 | + | cp test/loader/kernel/loadcheck.rad "$work/kernel/" |
|
| 19 | + | length=$(wc -c < "$work/loaded.ril") |
|
| 20 | + | printf '//! Binary package used by both execution paths.\n/// Complete trusted binary RIL input.\nexport static INPUT: [u8; %s] = [\n' "$length" > "$work/kernel/loadinput.rad" |
|
| 21 | + | od -An -v -tu1 "$work/loaded.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/loadinput.rad" |
|
| 22 | + | printf '];\n' >> "$work/kernel/loadinput.rad" |
|
| 23 | + | sh test/acceptance/compile "$emulator" "$work" |
|
| 24 | + | cat test/loader/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras \ |
|
| 25 | + | kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras > "$work/loader.ras" |
|
| 26 | + | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \ |
|
| 27 | + | -run bin/kernel.build.rv64 -- "$work/std.ril" "$work/kernel.ril" "$work/loader.ras" "$work/loader.rv64" |
|
| 28 | + | "$emulator" -machine -memory-size=262144 -max-steps=200000000 -run "$work/loader.rv64" |
|
| 29 | + | printf 'runtime loader: hosted and loaded entries agree after workspace release\n' |
test/tests/const.record.mutcopy.ril
+5 -2
| 18 | 18 | @then1 |
|
| 19 | 19 | copy %6 $SCRATCH2; |
|
| 20 | 20 | blit %2 %6 1; |
|
| 21 | 21 | jmp @merge2; |
|
| 22 | 22 | @merge2 |
|
| 23 | - | load w64 %8 %2 0; |
|
| 24 | - | ret %8; |
|
| 23 | + | reserve %8 8 8; |
|
| 24 | + | store w64 0 %8 0; |
|
| 25 | + | blit %8 %2 1; |
|
| 26 | + | load w64 %9 %8 0; |
|
| 27 | + | ret %9; |
|
| 25 | 28 | } |
test/tests/field.aggregate.ril
+5 -2
| 19 | 19 | store w8 1 %2 0; |
|
| 20 | 20 | add w64 %3 %2 4; |
|
| 21 | 21 | blit %3 %1 4; |
|
| 22 | 22 | blit %0 %2 8; |
|
| 23 | 23 | store w32 99 %0 8; |
|
| 24 | - | load w64 %4 %0 0; |
|
| 25 | - | ret %4; |
|
| 24 | + | reserve %4 8 8; |
|
| 25 | + | store w64 0 %4 0; |
|
| 26 | + | blit %4 %0 8; |
|
| 27 | + | load w64 %5 %4 0; |
|
| 28 | + | ret %5; |
|
| 26 | 29 | } |
|
| 27 | 30 | ||
| 28 | 31 | fn w32 $accessSliceField() { |
|
| 29 | 32 | @entry0 |
|
| 30 | 33 | reserve %0 12 4; |
test/tests/let.copy.semantics.ril
+5 -2
| 1 | 1 | fn w64 $makePoint() { |
|
| 2 | 2 | @entry0 |
|
| 3 | 3 | reserve %0 8 4; |
|
| 4 | 4 | store w32 1 %0 0; |
|
| 5 | 5 | store w32 2 %0 4; |
|
| 6 | - | load w64 %1 %0 0; |
|
| 7 | - | ret %1; |
|
| 6 | + | reserve %1 8 8; |
|
| 7 | + | store w64 0 %1 0; |
|
| 8 | + | blit %1 %0 8; |
|
| 9 | + | load w64 %2 %1 0; |
|
| 10 | + | ret %2; |
|
| 8 | 11 | } |
|
| 9 | 12 | ||
| 10 | 13 | fn w32 $letFromCall() { |
|
| 11 | 14 | @entry0 |
|
| 12 | 15 | call w64 %0 $makePoint(); |
test/tests/record.ctor.tuple.ril
+5 -2
| 1 | 1 | fn w64 $make() { |
|
| 2 | 2 | @entry0 |
|
| 3 | 3 | reserve %0 8 4; |
|
| 4 | 4 | store w32 1 %0 0; |
|
| 5 | 5 | store w32 2 %0 4; |
|
| 6 | - | load w64 %1 %0 0; |
|
| 7 | - | ret %1; |
|
| 6 | + | reserve %1 8 8; |
|
| 7 | + | store w64 0 %1 0; |
|
| 8 | + | blit %1 %0 8; |
|
| 9 | + | load w64 %2 %1 0; |
|
| 10 | + | ret %2; |
|
| 8 | 11 | } |
test/tests/union.ctor.ril
+10 -4
| 14 | 14 | store w32 7 %0 0; |
|
| 15 | 15 | reserve %1 8 4; |
|
| 16 | 16 | store w8 1 %1 0; |
|
| 17 | 17 | add w64 %2 %1 4; |
|
| 18 | 18 | blit %2 %0 4; |
|
| 19 | - | load w64 %3 %1 0; |
|
| 20 | - | ret %3; |
|
| 19 | + | reserve %3 8 8; |
|
| 20 | + | store w64 0 %3 0; |
|
| 21 | + | blit %3 %1 8; |
|
| 22 | + | load w64 %4 %3 0; |
|
| 23 | + | ret %4; |
|
| 21 | 24 | } |
|
| 22 | 25 | ||
| 23 | 26 | fn w64 $makeNone() { |
|
| 24 | 27 | @entry0 |
|
| 25 | 28 | reserve %0 8 4; |
|
| 26 | 29 | store w8 0 %0 0; |
|
| 27 | - | load w64 %1 %0 0; |
|
| 28 | - | ret %1; |
|
| 30 | + | reserve %1 8 8; |
|
| 31 | + | store w64 0 %1 0; |
|
| 32 | + | blit %1 %0 8; |
|
| 33 | + | load w64 %2 %1 0; |
|
| 34 | + | ret %2; |
|
| 29 | 35 | } |
test/tests/union.variant.access.ril
+15 -6
| 10 | 10 | ||
| 11 | 11 | fn w64 $makeNone() { |
|
| 12 | 12 | @entry0 |
|
| 13 | 13 | reserve %0 8 4; |
|
| 14 | 14 | store w8 0 %0 0; |
|
| 15 | - | load w64 %1 %0 0; |
|
| 16 | - | ret %1; |
|
| 15 | + | reserve %1 8 8; |
|
| 16 | + | store w64 0 %1 0; |
|
| 17 | + | blit %1 %0 8; |
|
| 18 | + | load w64 %2 %1 0; |
|
| 19 | + | ret %2; |
|
| 17 | 20 | } |
|
| 18 | 21 | ||
| 19 | 22 | fn w64 $makeSome(w32 %0) { |
|
| 20 | 23 | @entry0 |
|
| 21 | 24 | reserve %1 4 4; |
|
| 22 | 25 | store w32 %0 %1 0; |
|
| 23 | 26 | reserve %2 8 4; |
|
| 24 | 27 | store w8 1 %2 0; |
|
| 25 | 28 | add w64 %3 %2 4; |
|
| 26 | 29 | blit %3 %1 4; |
|
| 27 | - | load w64 %4 %2 0; |
|
| 28 | - | ret %4; |
|
| 30 | + | reserve %4 8 8; |
|
| 31 | + | store w64 0 %4 0; |
|
| 32 | + | blit %4 %2 8; |
|
| 33 | + | load w64 %5 %4 0; |
|
| 34 | + | ret %5; |
|
| 29 | 35 | } |
|
| 30 | 36 | ||
| 31 | 37 | fn w64 $assignNone() { |
|
| 32 | 38 | @entry0 |
|
| 33 | 39 | reserve %0 8 4; |
|
| 34 | 40 | store w8 0 %0 0; |
|
| 35 | - | load w64 %1 %0 0; |
|
| 36 | - | ret %1; |
|
| 41 | + | reserve %1 8 8; |
|
| 42 | + | store w64 0 %1 0; |
|
| 43 | + | blit %1 %0 8; |
|
| 44 | + | load w64 %2 %1 0; |
|
| 45 | + | ret %2; |
|
| 37 | 46 | } |