rv64: Generate shared package code
7b0dbbf9f1ad606c201e3ee0cabca79ad157e15c6a710746ab3c0744fda40fb8
Assisted-by: Codex:gpt-6
1 parent
b83ecdff
Makefile
+10 -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 |
|
| 26 | + | test: emulator seed-test std-test bin-test kernel-test package-test native-test shared-test |
|
| 27 | 27 | ||
| 28 | 28 | seed-test: |
|
| 29 | 29 | @seed/test |
|
| 30 | 30 | ||
| 31 | 31 | # Emulator command check |
| 94 | 94 | @$(RADIANCE) $(STD) -pkg build -mod $< -entry build -o $@ |
|
| 95 | 95 | ||
| 96 | 96 | $(BIN_DIR)/native.fixture.rv64: $(BIN_DIR)/native.build.rv64 |
|
| 97 | 97 | @$(EMU) -run $< -- $@ |
|
| 98 | 98 | ||
| 99 | + | # Shared package execution |
|
| 100 | + | ||
| 101 | + | shared-test: $(BIN_DIR)/shared.build.rv64 |
|
| 102 | + | @RAD_EMULATOR="$(EMU)" test/shared/run |
|
| 103 | + | ||
| 104 | + | $(BIN_DIR)/shared.build.rv64: test/shared/build.rad $(STD_LIB) $(RAD_BIN) |
|
| 105 | + | @$(RADIANCE) $(STD) -pkg build -mod $< -entry build -o $@ |
|
| 106 | + | ||
| 99 | 107 | # Binary Tests |
|
| 100 | 108 | ||
| 101 | 109 | BIN_TEST_DIR := test/tests |
|
| 102 | 110 | # Only tests with `//! returns:` are compiled to binaries and executed. |
|
| 103 | 111 | BIN_TEST_EXE_SRC := $(shell grep -rl '^//! returns:' $(BIN_TEST_DIR)) |
| 151 | 159 | clean: clean-std-test clean-bin-test clean-rad |
|
| 152 | 160 | ||
| 153 | 161 | t: test |
|
| 154 | 162 | c: clean |
|
| 155 | 163 | ||
| 156 | - | .PHONY: test clean default seed-test std-test bin-test kernel-test package-test native-test seed \ |
|
| 164 | + | .PHONY: test clean default seed-test std-test bin-test kernel-test package-test native-test shared-test seed \ |
|
| 157 | 165 | clean-std-test clean-bin-test clean-rad emulator |
|
| 158 | 166 | .SUFFIXES: |
|
| 159 | 167 | .DELETE_ON_ERROR: |
|
| 160 | 168 | .SILENT: |
lib/std/arch/rv64.rad
+1 -0
| 9 | 9 | //! * emit: Binary emission context and branch patching |
|
| 10 | 10 | //! * isel: Instruction selection (IL to RV64 instructions) |
|
| 11 | 11 | //! * printer: Assembly text output |
|
| 12 | 12 | ||
| 13 | 13 | export mod image; |
|
| 14 | + | export mod shared; |
|
| 14 | 15 | export mod encode; |
|
| 15 | 16 | export mod decode; |
|
| 16 | 17 | export mod emit; |
|
| 17 | 18 | export mod isel; |
|
| 18 | 19 | export mod printer; |
lib/std/arch/rv64/emit.rad
+7 -0
| 96 | 96 | ||
| 97 | 97 | /// Emission context. Tracks state during code generation. |
|
| 98 | 98 | export record Emitter { |
|
| 99 | 99 | /// Allocator for growing append-backed emitter lists. |
|
| 100 | 100 | allocator: alloc::Allocator, |
|
| 101 | + | /// Data address loads use the current domain package-state table. |
|
| 102 | + | sharedData: bool, |
|
| 101 | 103 | /// Emitted instructions storage. |
|
| 102 | 104 | code: *mut [u32], |
|
| 103 | 105 | /// Current number of emitted instructions. |
|
| 104 | 106 | codeLen: u32, |
|
| 105 | 107 | /// Local branches needing offset patching. |
| 201 | 203 | let pendingJumpsBuf = pendingJumps as *mut [PendingJump]; |
|
| 202 | 204 | let pendingAddrLoadsBuf = pendingAddrLoads as *mut [PendingAddrLoad]; |
|
| 203 | 205 | let funcsBuf = funcs as *mut [types::FuncAddr]; |
|
| 204 | 206 | return Emitter { |
|
| 205 | 207 | allocator: alloc::arenaAllocator(arena), |
|
| 208 | + | sharedData: false, |
|
| 206 | 209 | code: code as *mut [u32], |
|
| 207 | 210 | codeLen: 0, |
|
| 208 | 211 | pendingBranches: &mut pendingBranchesBuf[..0], |
|
| 209 | 212 | pendingCalls: &mut pendingCallsBuf[..0], |
|
| 210 | 213 | pendingJumps: &mut pendingJumpsBuf[..0], |
| 338 | 341 | isData: true, |
|
| 339 | 342 | }, e.allocator); |
|
| 340 | 343 | ||
| 341 | 344 | emit(e, encode::nop()); // Address-load upper instruction. |
|
| 342 | 345 | emit(e, encode::nop()); // Address-load lower instruction. |
|
| 346 | + | if e.sharedData { |
|
| 347 | + | emit(e, encode::nop()); // Package offset addition. |
|
| 348 | + | emit(e, encode::nop()); // Package offset low bits. |
|
| 349 | + | } |
|
| 343 | 350 | } |
|
| 344 | 351 | ||
| 345 | 352 | /// Patch local branches and clear the pending list. |
|
| 346 | 353 | /// |
|
| 347 | 354 | /// Called after each function. |
lib/std/arch/rv64/shared.rad
added
+332 -0
| 1 | + | //! Shared package code, private data templates, and qualified symbol linking. |
|
| 2 | + | ||
| 3 | + | @test export mod tests; |
|
| 4 | + | ||
| 5 | + | use std::mem; |
|
| 6 | + | use std::lang::alloc; |
|
| 7 | + | use std::lang::il; |
|
| 8 | + | use std::lang::il::binary; |
|
| 9 | + | use std::lang::gen; |
|
| 10 | + | use std::lang::gen::data; |
|
| 11 | + | use super::emit; |
|
| 12 | + | use super::encode; |
|
| 13 | + | use super::image; |
|
| 14 | + | ||
| 15 | + | /// Maximum number of resident package slots in a domain state table. |
|
| 16 | + | export constant MAX_PACKAGES: u32 = 256; |
|
| 17 | + | ||
| 18 | + | /// Package linking or instance storage failure. |
|
| 19 | + | export union Error: Copy { |
|
| 20 | + | /// A caller-supplied output buffer is full. |
|
| 21 | + | Capacity, |
|
| 22 | + | /// A symbol is absent, duplicated, or has the wrong kind. |
|
| 23 | + | Symbol, |
|
| 24 | + | /// An address or data extent is outside the supported range. |
|
| 25 | + | Range, |
|
| 26 | + | /// A data or code address has invalid alignment. |
|
| 27 | + | Alignment, |
|
| 28 | + | /// A referenced package has no private instance. |
|
| 29 | + | Instance, |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | /// Address of data within a domain's package graph. |
|
| 33 | + | export record DataRef: Copy { |
|
| 34 | + | /// Stable global package slot. |
|
| 35 | + | slot: u32, |
|
| 36 | + | /// Byte offset in the package's private state. |
|
| 37 | + | offset: u32, |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | /// Resolved function or domain-relative data address. |
|
| 41 | + | export union Target: Copy { |
|
| 42 | + | /// Absolute address of shared executable code. |
|
| 43 | + | Function(u64), |
|
| 44 | + | /// Private data location selected through gp. |
|
| 45 | + | Data(DataRef), |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | /// Resolved qualified symbol. |
|
| 49 | + | export record Symbol: Copy { |
|
| 50 | + | /// Fully qualified symbol name. |
|
| 51 | + | name: *[u8], |
|
| 52 | + | /// Code address or private data location. |
|
| 53 | + | target: Target, |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// Private pointer fixup applied when a domain is instantiated. |
|
| 57 | + | export record Relocation: Copy { |
|
| 58 | + | /// Byte offset of the first pointer in the private template. |
|
| 59 | + | offset: u32, |
|
| 60 | + | /// Number of consecutive 64-bit pointers. |
|
| 61 | + | count: u32, |
|
| 62 | + | /// Target data location in the domain graph. |
|
| 63 | + | target: DataRef, |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Caller-owned storage for one package's persistent link results. |
|
| 67 | + | export record Storage { |
|
| 68 | + | /// Local data layout workspace. |
|
| 69 | + | data: *mut [data::DataSym], |
|
| 70 | + | /// Local function and data definitions. |
|
| 71 | + | symbols: *mut [Symbol], |
|
| 72 | + | /// Public symbols for dependent packages. |
|
| 73 | + | exports: *mut [Symbol], |
|
| 74 | + | /// Private data initializer bytes. |
|
| 75 | + | template: *mut [u8], |
|
| 76 | + | /// Private pointer fixups. |
|
| 77 | + | relocations: *mut [Relocation], |
|
| 78 | + | } |
|
| 79 | + | ||
| 80 | + | /// A resident package entry used by boot catalogs and runtime loading. |
|
| 81 | + | export record Package: Copy { |
|
| 82 | + | /// Immutable package identity. |
|
| 83 | + | name: *[u8], |
|
| 84 | + | /// Required package names, in binary package order. |
|
| 85 | + | dependencies: *unsafe [*[u8]], |
|
| 86 | + | /// Stable slot in each domain's package-state table. |
|
| 87 | + | slot: u32, |
|
| 88 | + | /// Address at which the shared instructions execute. |
|
| 89 | + | codeAddress: u64, |
|
| 90 | + | /// Generated shared instructions. |
|
| 91 | + | code: *[u32], |
|
| 92 | + | /// Public function and data definitions. |
|
| 93 | + | exports: *[Symbol], |
|
| 94 | + | /// Exported default entry, if present. |
|
| 95 | + | entry: ?u64, |
|
| 96 | + | /// Initialized private bytes. The rest of memory must be zero-filled. |
|
| 97 | + | template: *[u8], |
|
| 98 | + | /// Total private state extent. |
|
| 99 | + | memory: u32, |
|
| 100 | + | /// Required private state base alignment. |
|
| 101 | + | alignment: u32, |
|
| 102 | + | /// Private pointers to resolve after all graph instances have storage. |
|
| 103 | + | relocations: *[Relocation], |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | /// Find exactly one qualified symbol in a bounded table. |
|
| 107 | + | export fn lookup(symbols: &[Symbol], name: &[u8]) -> ?Target { |
|
| 108 | + | for symbol in symbols { |
|
| 109 | + | if mem::eq(symbol.name, name) { return symbol.target; } |
|
| 110 | + | } |
|
| 111 | + | return nil; |
|
| 112 | + | } |
|
| 113 | + | ||
| 114 | + | /// Resolve a local or imported symbol. |
|
| 115 | + | fn resolve(local: &[Symbol], imports: &[Symbol], name: *[u8]) -> Target throws (Error) { |
|
| 116 | + | if let target = lookup(local, name) { return target; } |
|
| 117 | + | let target = lookup(imports, name) else { throw Error::Symbol; }; |
|
| 118 | + | return target; |
|
| 119 | + | } |
|
| 120 | + | ||
| 121 | + | /// Append a unique local definition to bounded symbol storage. |
|
| 122 | + | fn define(symbols: &mut [Symbol], count: &mut u32, name: *[u8], target: Target) throws (Error) { |
|
| 123 | + | if lookup(&symbols[..*count], name) <> nil { throw Error::Symbol; } |
|
| 124 | + | if *count == symbols.len { throw Error::Capacity; } |
|
| 125 | + | set symbols[*count] = Symbol { name, target }; |
|
| 126 | + | set *count += 1; |
|
| 127 | + | } |
|
| 128 | + | ||
| 129 | + | /// Resolve a shared function address with its required kind. |
|
| 130 | + | fn function(local: &[Symbol], imports: &[Symbol], name: *[u8]) -> u64 throws (Error) { |
|
| 131 | + | match try resolve(local, imports, name) { |
|
| 132 | + | case Target::Function(address) => return address, |
|
| 133 | + | else => throw Error::Symbol, |
|
| 134 | + | } |
|
| 135 | + | } |
|
| 136 | + | ||
| 137 | + | /// Require a private data target. |
|
| 138 | + | fn dataRef(target: Target) -> DataRef throws (Error) { |
|
| 139 | + | match target { |
|
| 140 | + | case Target::Data(location) => return location, |
|
| 141 | + | else => throw Error::Symbol, |
|
| 142 | + | } |
|
| 143 | + | } |
|
| 144 | + | ||
| 145 | + | /// Patch a PC-relative call or function address load. |
|
| 146 | + | unsafe fn relative(e: &mut emit::Emitter, index: u32, base: u64, target: u64, rd: gen::Reg, call: bool) |
|
| 147 | + | throws (Error) |
|
| 148 | + | { |
|
| 149 | + | let delta = image::displacement(base + index as u64 * 4, target) else { throw Error::Range; }; |
|
| 150 | + | let parts = emit::splitImm(delta); |
|
| 151 | + | emit::patch(e, index, encode::auipc(rd, parts.hi)); |
|
| 152 | + | if call { |
|
| 153 | + | emit::patch(e, index + 1, encode::jalr(super::RA, rd, parts.lo)); |
|
| 154 | + | } else { |
|
| 155 | + | emit::patch(e, index + 1, encode::addi(rd, rd, parts.lo)); |
|
| 156 | + | } |
|
| 157 | + | } |
|
| 158 | + | ||
| 159 | + | /// Link shared code against local definitions and dependency exports. |
|
| 160 | + | unsafe fn link(e: &mut emit::Emitter, base: u64, local: &[Symbol], imports: &[Symbol]) throws (Error) { |
|
| 161 | + | for i in 0..e.pendingCalls.len { |
|
| 162 | + | let pending = e.pendingCalls[i]; |
|
| 163 | + | let address = try function(local, imports, pending.target); |
|
| 164 | + | try relative(e, pending.index, base, address, super::SCRATCH1, true); |
|
| 165 | + | } |
|
| 166 | + | for i in 0..e.pendingJumps.len { |
|
| 167 | + | let pending = e.pendingJumps[i]; |
|
| 168 | + | let address = try function(local, imports, pending.target); |
|
| 169 | + | let delta = image::displacement(base + pending.index as u64 * 4, address) else { throw Error::Range; }; |
|
| 170 | + | if not encode::isJumpImm(delta) { throw Error::Range; } |
|
| 171 | + | emit::patch(e, pending.index, encode::jal(pending.rd, delta)); |
|
| 172 | + | } |
|
| 173 | + | for i in 0..e.pendingAddrLoads.len { |
|
| 174 | + | let pending = e.pendingAddrLoads[i]; |
|
| 175 | + | if not pending.isData { |
|
| 176 | + | try relative(e, pending.index, base, try function(local, imports, pending.target), pending.rd, false); |
|
| 177 | + | continue; |
|
| 178 | + | } |
|
| 179 | + | let target = try resolve(local, imports, pending.target); |
|
| 180 | + | let location = try dataRef(target); |
|
| 181 | + | if location.slot >= MAX_PACKAGES or location.offset > 0x7ffff7ff { throw Error::Range; } |
|
| 182 | + | if pending.rd == super::ADDR_SCRATCH or pending.rd == super::GP or pending.rd == super::ZERO { |
|
| 183 | + | throw Error::Symbol; |
|
| 184 | + | } |
|
| 185 | + | let parts = emit::splitImm(location.offset as i32); |
|
| 186 | + | emit::patch(e, pending.index, encode::ld(pending.rd, super::GP, location.slot as i32 * 8)); |
|
| 187 | + | emit::patch(e, pending.index + 1, encode::lui(super::ADDR_SCRATCH, parts.hi)); |
|
| 188 | + | emit::patch(e, pending.index + 2, encode::add(pending.rd, pending.rd, super::ADDR_SCRATCH)); |
|
| 189 | + | emit::patch(e, pending.index + 3, encode::addi(pending.rd, pending.rd, parts.lo)); |
|
| 190 | + | } |
|
| 191 | + | } |
|
| 192 | + | ||
| 193 | + | /// Write a little-endian integer into a validated byte extent. |
|
| 194 | + | fn integer(bytes: &mut [u8], offset: u32, value: u64, width: u32) { |
|
| 195 | + | for i in 0..width { set bytes[offset + i] = (value >> (i as u64 * 8)) as u8; } |
|
| 196 | + | } |
|
| 197 | + | ||
| 198 | + | /// Initialized output extents. |
|
| 199 | + | record TemplateSize: Copy { |
|
| 200 | + | /// Initialized byte count. |
|
| 201 | + | bytes: u32, |
|
| 202 | + | /// Private pointer fixup count. |
|
| 203 | + | relocations: u32, |
|
| 204 | + | } |
|
| 205 | + | ||
| 206 | + | /// Build initialized bytes and compact private pointer fixups. |
|
| 207 | + | unsafe fn template(items: *[il::Data], local: &[Symbol], imports: &[Symbol], bytes: &mut [u8], relocs: &mut [Relocation]) |
|
| 208 | + | -> TemplateSize throws (Error) |
|
| 209 | + | { |
|
| 210 | + | let mut initialized: u32 = 0; |
|
| 211 | + | let mut count: u32 = 0; |
|
| 212 | + | for item in items { |
|
| 213 | + | if item.isZeroInit { continue; } |
|
| 214 | + | let location = try dataRef(try resolve(local, &[], item.name)); |
|
| 215 | + | if location.offset > bytes.len or item.size > bytes.len - location.offset { throw Error::Capacity; } |
|
| 216 | + | let end = location.offset + item.size; |
|
| 217 | + | if end > initialized { set initialized = end; } |
|
| 218 | + | } |
|
| 219 | + | for i in 0..initialized { set bytes[i] = 0; } |
|
| 220 | + | for item in items { |
|
| 221 | + | if item.isZeroInit { continue; } |
|
| 222 | + | let location = try dataRef(try resolve(local, &[], item.name)); |
|
| 223 | + | let mut offset = location.offset; |
|
| 224 | + | let end = offset + item.size; |
|
| 225 | + | for value in item.values { |
|
| 226 | + | let mut width: u32 = 1; |
|
| 227 | + | let mut number: u64 = 0; |
|
| 228 | + | match value.item { |
|
| 229 | + | case il::DataItem::Val { typ, val } => { set width = il::typeSize(typ); set number = val as u64; }, |
|
| 230 | + | case il::DataItem::Fn(name) => { set width = 8; set number = try function(local, imports, name); }, |
|
| 231 | + | case il::DataItem::Sym(name) => { |
|
| 232 | + | set width = 8; |
|
| 233 | + | let target = try dataRef(try resolve(local, imports, name)); |
|
| 234 | + | if value.count > 0 { |
|
| 235 | + | if count == relocs.len { throw Error::Capacity; } |
|
| 236 | + | set relocs[count] = Relocation { offset, count: value.count, target }; |
|
| 237 | + | set count += 1; |
|
| 238 | + | } |
|
| 239 | + | }, |
|
| 240 | + | case il::DataItem::Str(s) => { set width = s.len; }, |
|
| 241 | + | case il::DataItem::Undef => {}, |
|
| 242 | + | } |
|
| 243 | + | if width > 0 and value.count > (end - offset) / width { throw Error::Range; } |
|
| 244 | + | for _ in 0..value.count { |
|
| 245 | + | match value.item { |
|
| 246 | + | case il::DataItem::Str(s) => { try! mem::copy(&mut bytes[offset..offset + width], s); }, |
|
| 247 | + | else => { integer(bytes, offset, number, width); }, |
|
| 248 | + | } |
|
| 249 | + | set offset += width; |
|
| 250 | + | } |
|
| 251 | + | } |
|
| 252 | + | } |
|
| 253 | + | return TemplateSize { bytes: initialized, relocations: count }; |
|
| 254 | + | } |
|
| 255 | + | ||
| 256 | + | /// Compile one package into caller-owned code, symbol, and private template storage. |
|
| 257 | + | /// Imports must contain unique exports from the package's admitted dependencies. |
|
| 258 | + | /// Arena storage and binary package names must outlive the returned catalog entry. |
|
| 259 | + | export unsafe fn compile(input: &binary::Package, slot: u32, codeAddress: u64, imports: &[Symbol], |
|
| 260 | + | storage: Storage, arena: &mut alloc::Arena, scratch: &mut alloc::Arena) -> Package throws (Error) |
|
| 261 | + | { |
|
| 262 | + | let case Storage { data: dataStorage, symbols: symbolStorage, exports: exportStorage, template: templateStorage, relocations: relocationStorage } = storage |
|
| 263 | + | else panic "expected shared linker storage"; |
|
| 264 | + | if slot >= MAX_PACKAGES { throw Error::Range; } |
|
| 265 | + | if (codeAddress & 3) <> 0 { throw Error::Alignment; } |
|
| 266 | + | for imported, i in imports { |
|
| 267 | + | if lookup(&imports[..i], imported.name) <> nil { throw Error::Symbol; } |
|
| 268 | + | } |
|
| 269 | + | let mut generator = super::beginProgram(super::ProgramOptions { |
|
| 270 | + | entryPatch: super::EntryPatch::None, debug: false, placement: image::Placement::Hosted, |
|
| 271 | + | }, arena); |
|
| 272 | + | set generator.e.sharedData = true; |
|
| 273 | + | for func in input.program.fns { super::generateFunction(&mut generator, func, scratch); } |
|
| 274 | + | if codeAddress > 0xffffffffffffffff - generator.e.codeLen as u64 * 4 { throw Error::Range; } |
|
| 275 | + | let mut symbols: u32 = 0; |
|
| 276 | + | for func in &generator.e.funcs[..] { |
|
| 277 | + | try define(symbolStorage, &mut symbols, func.name, Target::Function(codeAddress + func.index as u64 * 4)); |
|
| 278 | + | } |
|
| 279 | + | let mut dataCount: u32 = 0; |
|
| 280 | + | let roSize = try data::layoutSection(input.program.data, dataStorage, &mut dataCount, 0, true) |
|
| 281 | + | catch { throw Error::Range; }; |
|
| 282 | + | let memory = try data::layoutSectionAtOffset(input.program.data, dataStorage, &mut dataCount, 0, roSize, false) |
|
| 283 | + | catch { throw Error::Range; }; |
|
| 284 | + | if memory > 0x7ffff7ff { throw Error::Range; } |
|
| 285 | + | for item in &dataStorage[..dataCount] { |
|
| 286 | + | try define(symbolStorage, &mut symbols, item.name, Target::Data(DataRef { slot, offset: item.addr as u32 })); |
|
| 287 | + | } |
|
| 288 | + | let local = &symbolStorage[..symbols]; |
|
| 289 | + | for symbol in local { if lookup(imports, symbol.name) <> nil { throw Error::Symbol; } } |
|
| 290 | + | try link(&mut generator.e, codeAddress, local, imports); |
|
| 291 | + | let size = try template(input.program.data, local, imports, templateStorage, relocationStorage); |
|
| 292 | + | if input.exports.len > exportStorage.len { throw Error::Capacity; } |
|
| 293 | + | for exported, i in input.exports { |
|
| 294 | + | let target = try resolve(local, &[], exported.name); |
|
| 295 | + | match exported.kind { |
|
| 296 | + | case binary::ExportKind::Function => { try function(local, &[], exported.name); }, |
|
| 297 | + | case binary::ExportKind::Data => { try dataRef(target); }, |
|
| 298 | + | } |
|
| 299 | + | if lookup(&exportStorage[..i], exported.name) <> nil { throw Error::Symbol; } |
|
| 300 | + | set exportStorage[i] = Symbol { name: exported.name, target }; |
|
| 301 | + | } |
|
| 302 | + | let mut entry: ?u64 = nil; |
|
| 303 | + | if let name = input.entry { set entry = try function(&exportStorage[..input.exports.len], &[], name); } |
|
| 304 | + | let mut alignment: u32 = 8; |
|
| 305 | + | for item in input.program.data { if item.alignment > alignment { set alignment = item.alignment; } } |
|
| 306 | + | return Package { |
|
| 307 | + | name: input.name, dependencies: input.dependencies, slot, codeAddress, |
|
| 308 | + | code: emit::getCode(&generator.e), exports: &exportStorage[..input.exports.len], entry, |
|
| 309 | + | template: &templateStorage[..size.bytes], memory, alignment, |
|
| 310 | + | relocations: &relocationStorage[..size.relocations], |
|
| 311 | + | }; |
|
| 312 | + | } |
|
| 313 | + | ||
| 314 | + | /// Initialize private package memory after all graph bases have been assigned. |
|
| 315 | + | /// Validate every fixup before writing bytes so a rejected instance stays intact. |
|
| 316 | + | export fn instantiate(package: &Package, bases: &[u64], memory: &mut [u8]) throws (Error) { |
|
| 317 | + | if package.slot >= bases.len or bases[package.slot] == 0 { throw Error::Instance; } |
|
| 318 | + | if memory.len < package.memory or package.template.len > package.memory { throw Error::Capacity; } |
|
| 319 | + | if (bases[package.slot] & (package.alignment as u64 - 1)) <> 0 { throw Error::Alignment; } |
|
| 320 | + | if bases[package.slot] > 0xffffffffffffffff - package.memory as u64 { throw Error::Range; } |
|
| 321 | + | for fixup in package.relocations { |
|
| 322 | + | if fixup.target.slot >= bases.len or bases[fixup.target.slot] == 0 { throw Error::Instance; } |
|
| 323 | + | if bases[fixup.target.slot] > 0xffffffffffffffff - fixup.target.offset as u64 { throw Error::Range; } |
|
| 324 | + | if fixup.offset > package.memory or fixup.count > (package.memory - fixup.offset) / 8 { throw Error::Range; } |
|
| 325 | + | } |
|
| 326 | + | for i in 0..package.memory { set memory[i] = 0; } |
|
| 327 | + | try! mem::copy(memory, package.template); |
|
| 328 | + | for fixup in package.relocations { |
|
| 329 | + | let address = bases[fixup.target.slot] + fixup.target.offset as u64; |
|
| 330 | + | for i in 0..fixup.count { integer(memory, fixup.offset + i * 8, address, 8); } |
|
| 331 | + | } |
|
| 332 | + | } |
lib/std/arch/rv64/shared/tests.rad
added
+135 -0
| 1 | + | //! Private template relocation and bounded linking checks. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use std::lang::alloc; |
|
| 5 | + | use std::lang::il; |
|
| 6 | + | use std::lang::il::binary; |
|
| 7 | + | use std::lang::gen::data; |
|
| 8 | + | ||
| 9 | + | /// Code generation workspace reused between checks. |
|
| 10 | + | static ARENA: [u8; 16777216] = [0; 16777216]; |
|
| 11 | + | /// Function allocation workspace. |
|
| 12 | + | static SCRATCH: [u8; 16777216] = [0; 16777216]; |
|
| 13 | + | /// Persistent catalog symbols. |
|
| 14 | + | unsafe static SYMBOLS: [super::Symbol; 8] = undefined; |
|
| 15 | + | /// Persistent exported symbols. |
|
| 16 | + | unsafe static EXPORTS: [super::Symbol; 8] = undefined; |
|
| 17 | + | /// Data layout workspace. |
|
| 18 | + | unsafe static DATA: [data::DataSym; 8] = undefined; |
|
| 19 | + | /// Persistent initialized bytes. |
|
| 20 | + | static TEMPLATE: [u8; 64] = [0; 64]; |
|
| 21 | + | /// Persistent private data relocations. |
|
| 22 | + | unsafe static RELOCS: [super::Relocation; 8] = undefined; |
|
| 23 | + | ||
| 24 | + | /// Build a package with repeated dependency pointers and a high code pointer. |
|
| 25 | + | unsafe fn input() -> binary::Package { |
|
| 26 | + | return binary::Package { |
|
| 27 | + | symbols: &[], name: "p", dependencies: &["dep"], |
|
| 28 | + | exports: &[binary::Export { name: "p::refs", kind: binary::ExportKind::Data }], |
|
| 29 | + | entry: nil, |
|
| 30 | + | program: il::Program { |
|
| 31 | + | data: &[ |
|
| 32 | + | il::Data { |
|
| 33 | + | name: "p::refs", size: 32, alignment: 8, readOnly: true, isZeroInit: false, |
|
| 34 | + | values: &[ |
|
| 35 | + | il::DataValue { item: il::DataItem::Sym("dep::value"), count: 2 }, |
|
| 36 | + | il::DataValue { item: il::DataItem::Fn("dep::call"), count: 1 }, |
|
| 37 | + | ], |
|
| 38 | + | }, |
|
| 39 | + | il::Data { |
|
| 40 | + | name: "p::zero", size: 16, alignment: 16, readOnly: false, isZeroInit: true, values: &[], |
|
| 41 | + | }, |
|
| 42 | + | ], |
|
| 43 | + | fns: &[], |
|
| 44 | + | }, |
|
| 45 | + | }; |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | /// Compile into small persistent tables. |
|
| 49 | + | unsafe fn compile(imports: &[super::Symbol], capacity: u32) -> super::Package throws (super::Error) { |
|
| 50 | + | let mut arena = alloc::new(&mut ARENA[..]); |
|
| 51 | + | let mut scratch = alloc::new(&mut SCRATCH[..]); |
|
| 52 | + | let package = input(); |
|
| 53 | + | return try super::compile(&package, 2, 0x180000000, imports, |
|
| 54 | + | super::Storage { |
|
| 55 | + | data: &mut DATA[..], symbols: &mut SYMBOLS[..], exports: &mut EXPORTS[..], |
|
| 56 | + | template: &mut TEMPLATE[..capacity], relocations: &mut RELOCS[..], |
|
| 57 | + | }, &mut arena, &mut scratch); |
|
| 58 | + | } |
|
| 59 | + | ||
| 60 | + | /// Dependency exports with a full-width function pointer and a large data offset. |
|
| 61 | + | fn imports() -> *[super::Symbol] { |
|
| 62 | + | return &[ |
|
| 63 | + | super::Symbol { name: "dep::value", target: super::Target::Data(super::DataRef { slot: 1, offset: 8192 }) }, |
|
| 64 | + | super::Symbol { name: "dep::call", target: super::Target::Function(0x180001234) }, |
|
| 65 | + | ]; |
|
| 66 | + | } |
|
| 67 | + | ||
| 68 | + | /// Read a little-endian pointer without requiring buffer alignment. |
|
| 69 | + | fn pointer(bytes: &[u8], offset: u32) -> u64 { |
|
| 70 | + | let mut value: u64 = 0; |
|
| 71 | + | for i in 0..8 { set value |= bytes[offset + i] as u64 << (i as u64 * 8); } |
|
| 72 | + | return value; |
|
| 73 | + | } |
|
| 74 | + | ||
| 75 | + | /// Check repeated pointers, full-width code addresses, padding, and zero-filled tails. |
|
| 76 | + | @test unsafe fn privateRelocations() throws (testing::TestError) { |
|
| 77 | + | let package = try compile(imports(), 64) catch { throw testing::TestError::Failed; }; |
|
| 78 | + | try testing::expect(package.template.len == 32 and package.memory == 48 and package.alignment == 16); |
|
| 79 | + | try testing::expect(package.relocations.len == 1 and package.relocations[0].count == 2); |
|
| 80 | + | try testing::expect(pointer(package.template, 16) == 0x180001234); |
|
| 81 | + | let mut first: [u8; 48] = [255; 48]; |
|
| 82 | + | let mut second: [u8; 48] = [255; 48]; |
|
| 83 | + | try super::instantiate(&package, &[0, 0x180002000, 0x180008000], &mut first[..]) |
|
| 84 | + | catch { throw testing::TestError::Failed; }; |
|
| 85 | + | try super::instantiate(&package, &[0, 0x280002000, 0x280008000], &mut second[..]) |
|
| 86 | + | catch { throw testing::TestError::Failed; }; |
|
| 87 | + | try testing::expect(pointer(&first[..], 0) == 0x180004000 and pointer(&first[..], 8) == 0x180004000); |
|
| 88 | + | try testing::expect(pointer(&second[..], 0) == 0x280004000 and pointer(&second[..], 8) == 0x280004000); |
|
| 89 | + | try testing::expect(pointer(&first[..], 16) == pointer(&second[..], 16)); |
|
| 90 | + | for i in 24..48 { try testing::expect(first[i] == 0 and second[i] == 0); } |
|
| 91 | + | } |
|
| 92 | + | ||
| 93 | + | /// A missing instance, bad base, or short buffer leaves instance bytes untouched. |
|
| 94 | + | @test unsafe fn instanceFailures() throws (testing::TestError) { |
|
| 95 | + | let package = try compile(imports(), 64) catch { throw testing::TestError::Failed; }; |
|
| 96 | + | let mut bytes: [u8; 48] = [255; 48]; |
|
| 97 | + | let mut failed: u32 = 0; |
|
| 98 | + | try super::instantiate(&package, &[0, 0, 0x8000], &mut bytes[..]) catch err { |
|
| 99 | + | try testing::expect(err == super::Error::Instance); set failed += 1; |
|
| 100 | + | }; |
|
| 101 | + | try super::instantiate(&package, &[0, 0x2000, 0x8008], &mut bytes[..]) catch err { |
|
| 102 | + | try testing::expect(err == super::Error::Alignment); set failed += 1; |
|
| 103 | + | }; |
|
| 104 | + | try super::instantiate(&package, &[0, 0x2000, 0x8000], &mut bytes[..47]) catch err { |
|
| 105 | + | try testing::expect(err == super::Error::Capacity); set failed += 1; |
|
| 106 | + | }; |
|
| 107 | + | try super::instantiate(&package, &[0, 0xffffffffffffffff, 0x8000], &mut bytes[..]) catch err { |
|
| 108 | + | try testing::expect(err == super::Error::Range); set failed += 1; |
|
| 109 | + | }; |
|
| 110 | + | try testing::expect(failed == 4); |
|
| 111 | + | for byte in &bytes[..] { try testing::expect(byte == 255); } |
|
| 112 | + | } |
|
| 113 | + | ||
| 114 | + | /// Missing symbols, duplicate imports, kind mismatches, and short output fail explicitly. |
|
| 115 | + | @test unsafe fn linkFailures() throws (testing::TestError) { |
|
| 116 | + | let mut failed: u32 = 0; |
|
| 117 | + | try compile(&[], 64) catch err { |
|
| 118 | + | try testing::expect(err == super::Error::Symbol); set failed += 1; |
|
| 119 | + | }; |
|
| 120 | + | let names = imports(); |
|
| 121 | + | try compile(&[names[0], names[0]], 64) catch err { |
|
| 122 | + | try testing::expect(err == super::Error::Symbol); set failed += 1; |
|
| 123 | + | }; |
|
| 124 | + | try compile(&[ |
|
| 125 | + | super::Symbol { name: "dep::value", target: super::Target::Function(0x8000) }, names[1], |
|
| 126 | + | ], 64) catch err { |
|
| 127 | + | try testing::expect(err == super::Error::Symbol); set failed += 1; |
|
| 128 | + | }; |
|
| 129 | + | try compile(names, 31) catch err { |
|
| 130 | + | try testing::expect(err == super::Error::Capacity); set failed += 1; |
|
| 131 | + | }; |
|
| 132 | + | try testing::expect(failed == 4); |
|
| 133 | + | let package = try compile(names, 64) catch { throw testing::TestError::Failed; }; |
|
| 134 | + | try testing::expect(package.exports.len == 1); |
|
| 135 | + | } |
std.lib
+1 -0
| 50 | 50 | lib/std/lang/gen/types.rad |
|
| 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 | + | lib/std/arch/rv64/shared.rad |
std.lib.test
+1 -0
| 11 | 11 | lib/std/lang/resolver/tests.rad |
|
| 12 | 12 | lib/std/lang/gen/bitset/tests.rad |
|
| 13 | 13 | lib/std/lang/il/binary/tests.rad |
|
| 14 | 14 | lib/std/lang/il/binary/decodeTests.rad |
|
| 15 | 15 | lib/std/arch/rv64/image/tests.rad |
|
| 16 | + | lib/std/arch/rv64/shared/tests.rad |
test/shared/app.rad
added
+16 -0
| 1 | + | //! Shared entry with callbacks, initialized data pointers, and private counters. |
|
| 2 | + | ||
| 3 | + | use support; |
|
| 4 | + | ||
| 5 | + | /// Callback table contains a linked dependency function address. |
|
| 6 | + | static CALLBACKS: [fn() -> i32; 1] = [support::read]; |
|
| 7 | + | /// The slice contains a pointer into this domain's string storage. |
|
| 8 | + | static TEXT: *[u8] = "xy"; |
|
| 9 | + | /// Number of calls in the current domain. |
|
| 10 | + | static calls: i32 = 0; |
|
| 11 | + | ||
| 12 | + | /// Read dependency state through calls and data, then advance private state. |
|
| 13 | + | @default export fn main() -> i32 { |
|
| 14 | + | set calls += 1; |
|
| 15 | + | return support::read() + CALLBACKS[0]() + support::value + TEXT[0] as i32 + calls; |
|
| 16 | + | } |
test/shared/build.rad
added
+148 -0
| 1 | + | //! Build a machine fixture from separately decoded and linked packages. |
|
| 2 | + | ||
| 3 | + | use std::sys; |
|
| 4 | + | use std::sys::unix; |
|
| 5 | + | use std::mem; |
|
| 6 | + | use std::lang::alloc; |
|
| 7 | + | use std::lang::il::binary; |
|
| 8 | + | use std::lang::il::binary::program; |
|
| 9 | + | use std::lang::gen::data; |
|
| 10 | + | use std::arch::rv64; |
|
| 11 | + | use std::arch::rv64::shared; |
|
| 12 | + | use std::arch::rv64::emit; |
|
| 13 | + | use std::arch::rv64::encode; |
|
| 14 | + | use std::arch::rv64::image; |
|
| 15 | + | ||
| 16 | + | /// Separate code arenas preserve the boot catalog's package records. |
|
| 17 | + | unsafe static CODE: [[u8; 16777216]; 3] = undefined; |
|
| 18 | + | /// Reusable function workspace. |
|
| 19 | + | static SCRATCH: [u8; 16777216] = [0; 16777216]; |
|
| 20 | + | /// Decoder arena shared by the two input packages. |
|
| 21 | + | static DECODE: [u8; 1048576] = [0; 1048576]; |
|
| 22 | + | /// Input file buffer. |
|
| 23 | + | static INPUT: [u8; 65536] = [0; 65536]; |
|
| 24 | + | /// Persistent private templates. |
|
| 25 | + | unsafe static TEMPLATES: [[u8; 16384]; 2] = undefined; |
|
| 26 | + | /// Local symbol workspaces. |
|
| 27 | + | unsafe static SYMBOLS: [[shared::Symbol; 128]; 2] = undefined; |
|
| 28 | + | /// Package exports. |
|
| 29 | + | unsafe static EXPORTS: [[shared::Symbol; 128]; 2] = undefined; |
|
| 30 | + | /// Private pointer fixups. |
|
| 31 | + | unsafe static RELOCS: [[shared::Relocation; 128]; 2] = undefined; |
|
| 32 | + | /// Data layout workspaces. |
|
| 33 | + | unsafe static DATA: [[data::DataSym; 128]; 2] = undefined; |
|
| 34 | + | /// Native instruction image, with fixed package placements. |
|
| 35 | + | static TEXT: [u32; 4096] = [0; 4096]; |
|
| 36 | + | /// Two private graphs followed by their package-state tables. |
|
| 37 | + | static STATE: [u8; 69632] = [0; 69632]; |
|
| 38 | + | ||
| 39 | + | /// Decode trusted package IL into persistent storage. |
|
| 40 | + | unsafe fn load(path: *[u8], arena: &mut alloc::Arena) -> binary::Package { |
|
| 41 | + | let length = unix::readFile(path, &mut INPUT[..]) else panic "missing package"; |
|
| 42 | + | return try! program::decode(&INPUT[..length], arena, binary::Limits { registers: 8192, blocks: 4096 }); |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | /// Compile through the same entry used for boot catalogs and runtime loading. |
|
| 46 | + | unsafe fn compile(input: &binary::Package, index: u32, imports: &[shared::Symbol]) -> shared::Package throws (shared::Error) { |
|
| 47 | + | let mut arena = alloc::new(&mut CODE[index][..]); |
|
| 48 | + | let mut scratch = alloc::new(&mut SCRATCH[..]); |
|
| 49 | + | return try shared::compile(input, index, 0x80011000 + index as u64 * 4096, imports, |
|
| 50 | + | shared::Storage { |
|
| 51 | + | data: &mut DATA[index][..], symbols: &mut SYMBOLS[index][..], exports: &mut EXPORTS[index][..], |
|
| 52 | + | template: &mut TEMPLATES[index][..], relocations: &mut RELOCS[index][..], |
|
| 53 | + | }, &mut arena, &mut scratch); |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// Append a call result check and return its branch patch location. |
|
| 57 | + | fn check(e: &mut emit::Emitter, entry: u64, expected: i64) -> u32 { |
|
| 58 | + | let delta = entry - (0x80010000 + e.codeLen as u64 * 4); |
|
| 59 | + | emit::emit(e, encode::jal(rv64::RA, delta as i32)); |
|
| 60 | + | emit::loadImm(e, rv64::T0, expected); |
|
| 61 | + | let index = e.codeLen; |
|
| 62 | + | emit::emit(e, encode::nop()); |
|
| 63 | + | return index; |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Emit an explicit platform completion status. |
|
| 67 | + | fn finish(e: &mut emit::Emitter, status: i64) { |
|
| 68 | + | emit::loadImm(e, rv64::T0, 0x10001000); |
|
| 69 | + | emit::loadImm(e, rv64::T1, status); |
|
| 70 | + | emit::emit(e, encode::sw(rv64::T1, rv64::T0, 0)); |
|
| 71 | + | emit::emit(e, encode::ebreak()); |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | /// Verify catalog linking, then execute one code copy against two private graphs. |
|
| 75 | + | @default unsafe fn main(env: *sys::Env) -> i32 { |
|
| 76 | + | assert env.args.len == 4; |
|
| 77 | + | let mut decoder = alloc::new(&mut DECODE[..]); |
|
| 78 | + | let supportInput = load(env.args[1], &mut decoder); |
|
| 79 | + | let appInput = load(env.args[2], &mut decoder); |
|
| 80 | + | let support = try! compile(&supportInput, 0, &[]); |
|
| 81 | + | let mut far: [shared::Symbol; 128] = undefined; |
|
| 82 | + | for symbol, i in support.exports { |
|
| 83 | + | set far[i] = symbol; |
|
| 84 | + | match symbol.target { |
|
| 85 | + | case shared::Target::Function(_) => { set far[i].target = shared::Target::Function(0x180000000); }, |
|
| 86 | + | else => {}, |
|
| 87 | + | } |
|
| 88 | + | } |
|
| 89 | + | let mut rejected = false; |
|
| 90 | + | try compile(&appInput, 1, &far[..support.exports.len]) catch err { |
|
| 91 | + | assert err == shared::Error::Range; |
|
| 92 | + | set rejected = true; |
|
| 93 | + | }; |
|
| 94 | + | assert rejected; |
|
| 95 | + | let app = try! compile(&appInput, 1, support.exports); |
|
| 96 | + | let catalog = [support, app]; |
|
| 97 | + | assert app.relocations.len > 0; |
|
| 98 | + | let entry = app.entry else panic "missing entry"; |
|
| 99 | + | for i in 0..STATE.len { set STATE[i] = 0; } |
|
| 100 | + | for domain in 0..2 { |
|
| 101 | + | let mut bases: [u64; 256] = [0; 256]; |
|
| 102 | + | for package, i in &catalog[..] { |
|
| 103 | + | let offset = domain * 32768 + i * 16384; |
|
| 104 | + | set bases[i] = 0x80020000 + offset as u64; |
|
| 105 | + | } |
|
| 106 | + | for package, i in &catalog[..] { |
|
| 107 | + | let offset = domain * 32768 + i * 16384; |
|
| 108 | + | try! shared::instantiate(&package, &bases[..], &mut STATE[offset..offset + 16384]); |
|
| 109 | + | } |
|
| 110 | + | let table = @sliceOf(&bases[0] as *unsafe u8, @sizeOf([u64; 256])); |
|
| 111 | + | let tableOffset: u32 = 65536 + domain * 2048; |
|
| 112 | + | try! mem::copy(&mut STATE[tableOffset..], table); |
|
| 113 | + | } |
|
| 114 | + | let mut arena = alloc::new(&mut CODE[2][..]); |
|
| 115 | + | let mut e = try! emit::emitter(&mut arena, false); |
|
| 116 | + | emit::loadImm(&mut e, rv64::GP, 0x80030000); |
|
| 117 | + | let first = check(&mut e, entry, 142); |
|
| 118 | + | let second = check(&mut e, entry, 143); |
|
| 119 | + | emit::loadImm(&mut e, rv64::GP, 0x80030800); |
|
| 120 | + | let other = check(&mut e, entry, 142); |
|
| 121 | + | emit::loadImm(&mut e, rv64::GP, 0x80030000); |
|
| 122 | + | let resumed = check(&mut e, entry, 144); |
|
| 123 | + | finish(&mut e, 0x5555); |
|
| 124 | + | let failure = e.codeLen; |
|
| 125 | + | finish(&mut e, 0x13333); |
|
| 126 | + | for index in &[first, second, other, resumed] { |
|
| 127 | + | emit::patch(&mut e, index, encode::bne(rv64::A0, rv64::T0, (failure - index) as i32 * 4)); |
|
| 128 | + | } |
|
| 129 | + | for i in 0..TEXT.len { set TEXT[i] = encode::nop(); } |
|
| 130 | + | assert e.codeLen <= 1024; |
|
| 131 | + | for word, i in emit::getCode(&e) { set TEXT[i] = word; } |
|
| 132 | + | for package, i in &catalog[..] { |
|
| 133 | + | assert package.code.len <= 1024; |
|
| 134 | + | for word, j in package.code { set TEXT[1024 + i * 1024 + j] = word; } |
|
| 135 | + | } |
|
| 136 | + | let header = try! image::header(image::Layout { |
|
| 137 | + | entry: 0x80010000, |
|
| 138 | + | code: image::Segment { address: 0x80010000, initialized: 16384, memory: 16384 }, |
|
| 139 | + | roData: image::Segment { address: 0, initialized: 0, memory: 0 }, |
|
| 140 | + | rwData: image::Segment { address: 0x80020000, initialized: STATE.len, memory: STATE.len }, |
|
| 141 | + | }); |
|
| 142 | + | let fd = unix::openOpts(env.args[3], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420); |
|
| 143 | + | assert fd >= 0; |
|
| 144 | + | let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, @sliceOf(&TEXT[0] as *u8, 16384)) and unix::writeAll(fd, &STATE[..]); |
|
| 145 | + | let closed = unix::close(fd) == 0; |
|
| 146 | + | assert written and closed; |
|
| 147 | + | return 0; |
|
| 148 | + | } |
test/shared/run
added
+13 -0
| 1 | + | #!/bin/sh |
|
| 2 | + | # Compile two packages once and execute them with two private state graphs. |
|
| 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 \ |
|
| 9 | + | -pkg support -mod test/shared/support.rad \ |
|
| 10 | + | -pkg app -mod test/shared/app.rad -entry app -ril "$work" |
|
| 11 | + | "$emulator" -run bin/shared.build.rv64 -- "$work/support.ril" "$work/app.ril" "$work/native.rv64" |
|
| 12 | + | "$emulator" -machine -run "$work/native.rv64" |
|
| 13 | + | printf 'shared packages: calls, callbacks, private pointers, and independent state passed\n' |
test/shared/support.rad
added
+15 -0
| 1 | + | //! Shared dependency with private writable state. |
|
| 2 | + | ||
| 3 | + | /// Value read by dependent packages. |
|
| 4 | + | export static value: i32 = 7; |
|
| 5 | + | ||
| 6 | + | /// Large private allocation before the final zero-initialized scalar. |
|
| 7 | + | static padding: [u8; 8192] = [0; 8192]; |
|
| 8 | + | /// Zero-initialized value at a large package offset. |
|
| 9 | + | static tail: i32 = 0; |
|
| 10 | + | ||
| 11 | + | /// Read this domain's value. |
|
| 12 | + | export fn read() -> i32 { |
|
| 13 | + | set padding[8191] = 0; |
|
| 14 | + | return value + tail; |
|
| 15 | + | } |