rv64: Generate shared package code
e237f812165afff39184f2bb816f3d0749e39bd5e954e17f381bdee98e4ffedf
1 parent
d73b8223
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 |