compiler/
kernel/
lib/
examples/
std/
arch/
rv64/
asm/
image/
shared/
asm.rad
24.5 KiB
atomicTests.rad
5.0 KiB
atomics.rad
3.6 KiB
bounds.rad
8.7 KiB
decode.rad
15.2 KiB
emit.rad
33.2 KiB
encode.rad
22.0 KiB
image.rad
4.5 KiB
isel.rad
51.0 KiB
printer.rad
14.2 KiB
shared.rad
16.8 KiB
tests.rad
17.2 KiB
rv64.rad
17.1 KiB
char/
collections/
lang/
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.3 KiB
intrinsics.rad
467 B
io.rad
1.7 KiB
lang.rad
276 B
mem.rad
2.3 KiB
sys.rad
179 B
testing.rad
2.4 KiB
tests.rad
15.7 KiB
vec.rad
3.2 KiB
std.rad
281 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
9.2 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.5 KiB
std.lib.test
662 B
lib/std/arch/rv64/shared.rad
raw
| 1 | //! Shared package code, private data templates, and qualified symbol linking. |
| 2 | |
| 3 | @test export mod tests; |
| 4 | export mod catalog; |
| 5 | |
| 6 | use std::mem; |
| 7 | use std::lang::alloc; |
| 8 | use std::lang::il; |
| 9 | use std::lang::il::binary; |
| 10 | use std::lang::gen; |
| 11 | use std::lang::gen::data; |
| 12 | use super::emit; |
| 13 | use super::encode; |
| 14 | use super::image; |
| 15 | use super::asm; |
| 16 | |
| 17 | /// Maximum number of resident package slots in a domain state table. |
| 18 | export constant MAX_PACKAGES: u32 = 256; |
| 19 | |
| 20 | /// Package linking or instance storage failure. |
| 21 | export union Error: Copy { |
| 22 | /// Reusable backend generation failed. |
| 23 | Codegen(super::Error), |
| 24 | /// A caller-supplied output buffer is full. |
| 25 | Capacity, |
| 26 | /// A symbol is absent, duplicated, or has the wrong kind. |
| 27 | Symbol, |
| 28 | /// An address or data extent is outside the supported range. |
| 29 | Range, |
| 30 | /// A data or code address has invalid alignment. |
| 31 | Alignment, |
| 32 | /// A referenced package has no private instance. |
| 33 | Instance, |
| 34 | } |
| 35 | |
| 36 | /// Address of data within a domain's package graph. |
| 37 | export record DataRef: Copy { |
| 38 | /// Stable global package slot. |
| 39 | slot: u32, |
| 40 | /// Byte offset in the package's private state. |
| 41 | offset: u32, |
| 42 | } |
| 43 | |
| 44 | /// Resolved function or domain-relative data address. |
| 45 | export union Target: Copy { |
| 46 | /// Absolute address of shared executable code. |
| 47 | Function(u64), |
| 48 | /// Private data location selected through gp. |
| 49 | Data(DataRef), |
| 50 | } |
| 51 | |
| 52 | /// Resolved qualified symbol. |
| 53 | export record Symbol: Copy { |
| 54 | /// Fully qualified symbol name. |
| 55 | name: *[u8], |
| 56 | /// Code address or private data location. |
| 57 | target: Target, |
| 58 | } |
| 59 | |
| 60 | /// Private pointer fixup applied when a domain is instantiated. |
| 61 | export record Relocation: Copy { |
| 62 | /// Byte offset of the first pointer in the private template. |
| 63 | offset: u32, |
| 64 | /// Number of consecutive 64-bit pointers. |
| 65 | count: u32, |
| 66 | /// Target data location in the domain graph. |
| 67 | target: DataRef, |
| 68 | } |
| 69 | |
| 70 | /// Caller-owned storage for one package's persistent link results. |
| 71 | export record Storage { |
| 72 | /// Local data layout workspace. |
| 73 | data: *mut [data::DataSym], |
| 74 | /// Local function and data definitions. |
| 75 | symbols: *mut [Symbol], |
| 76 | /// Public symbols for dependent packages. |
| 77 | exports: *mut [Symbol], |
| 78 | /// Private data initializer bytes. |
| 79 | template: *mut [u8], |
| 80 | /// Private pointer fixups. |
| 81 | relocations: *mut [Relocation], |
| 82 | } |
| 83 | |
| 84 | /// A resident package entry used by boot catalogs and runtime loading. |
| 85 | export record Package: Copy { |
| 86 | /// Immutable package identity. |
| 87 | name: *[u8], |
| 88 | /// Required package names, in binary package order. |
| 89 | dependencies: *unsafe [*[u8]], |
| 90 | /// Stable slot in each domain's package-state table. |
| 91 | slot: u32, |
| 92 | /// Address at which the shared instructions execute. |
| 93 | codeAddress: u64, |
| 94 | /// Generated shared instructions. |
| 95 | code: *[u32], |
| 96 | /// Public function and data definitions. |
| 97 | exports: *[Symbol], |
| 98 | /// Exported default entry, if present. |
| 99 | entry: ?u64, |
| 100 | /// Initialized private bytes. The rest of memory must be zero-filled. |
| 101 | template: *[u8], |
| 102 | /// Total private state extent. |
| 103 | memory: u32, |
| 104 | /// Required private state base alignment. |
| 105 | alignment: u32, |
| 106 | /// Private pointers to resolve after all graph instances have storage. |
| 107 | relocations: *[Relocation], |
| 108 | } |
| 109 | |
| 110 | /// Find exactly one qualified symbol in a bounded table. |
| 111 | export fn lookup(symbols: &[Symbol], name: &[u8]) -> ?Target { |
| 112 | for symbol in symbols { |
| 113 | if mem::eq(symbol.name, name) { |
| 114 | return symbol.target; |
| 115 | } |
| 116 | } |
| 117 | return nil; |
| 118 | } |
| 119 | |
| 120 | /// Resolve a local or imported symbol. |
| 121 | fn resolve(local: &[Symbol], imports: &[Symbol], name: *[u8]) -> Target throws (Error) { |
| 122 | if let target = lookup(local, name) { |
| 123 | return target; |
| 124 | } |
| 125 | let target = lookup(imports, name) else { |
| 126 | throw Error::Symbol; |
| 127 | }; |
| 128 | return target; |
| 129 | } |
| 130 | |
| 131 | /// Append a unique local definition to bounded symbol storage. |
| 132 | fn define(symbols: &mut [Symbol], count: &mut u32, name: *[u8], target: Target) throws (Error) { |
| 133 | if lookup(&symbols[..*count], name) <> nil { |
| 134 | throw Error::Symbol; |
| 135 | } |
| 136 | if *count == symbols.len { |
| 137 | throw Error::Capacity; |
| 138 | } |
| 139 | set symbols[*count] = Symbol { name, target }; |
| 140 | set *count += 1; |
| 141 | } |
| 142 | |
| 143 | /// Resolve a shared function address with its required kind. |
| 144 | fn function(local: &[Symbol], imports: &[Symbol], name: *[u8]) -> u64 throws (Error) { |
| 145 | match try resolve(local, imports, name) { |
| 146 | case Target::Function(address) => return address, |
| 147 | else => throw Error::Symbol, |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | /// Require a private data target. |
| 152 | fn dataRef(target: Target) -> DataRef throws (Error) { |
| 153 | match target { |
| 154 | case Target::Data(location) => return location, |
| 155 | else => throw Error::Symbol, |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | /// Patch a PC-relative call or function address load. |
| 160 | unsafe fn relative(e: &mut emit::Emitter, index: u32, base: u64, target: u64, rd: gen::Reg, call: bool) |
| 161 | throws (Error) |
| 162 | { |
| 163 | let delta = image::displacement(base + index as u64 * 4, target) else { |
| 164 | throw Error::Range; |
| 165 | }; |
| 166 | let parts = emit::splitImm(delta); |
| 167 | emit::patch(e, index, encode::auipc(rd, parts.hi)); |
| 168 | if call { |
| 169 | emit::patch(e, index + 1, encode::jalr(super::RA, rd, parts.lo)); |
| 170 | } else { |
| 171 | emit::patch(e, index + 1, encode::addi(rd, rd, parts.lo)); |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | /// Link shared code against local definitions and dependency exports. |
| 176 | unsafe fn link(e: &mut emit::Emitter, base: u64, local: &[Symbol], imports: &[Symbol]) throws (Error) { |
| 177 | for i in 0..e.pendingCallsLen { |
| 178 | let pending = e.pendingCalls[i]; |
| 179 | let address = try function(local, imports, pending.target); |
| 180 | try relative(e, pending.index, base, address, super::SCRATCH1, true); |
| 181 | } |
| 182 | for i in 0..e.pendingJumpsLen { |
| 183 | let pending = e.pendingJumps[i]; |
| 184 | let address = try function(local, imports, pending.target); |
| 185 | let delta = image::displacement(base + pending.index as u64 * 4, address) else { |
| 186 | throw Error::Range; |
| 187 | }; |
| 188 | if not encode::isJumpImm(delta) { |
| 189 | throw Error::Range; |
| 190 | } |
| 191 | emit::patch(e, pending.index, encode::jal(pending.rd, delta)); |
| 192 | } |
| 193 | for i in 0..e.pendingAddrLoadsLen { |
| 194 | let pending = e.pendingAddrLoads[i]; |
| 195 | if not pending.isData { |
| 196 | try relative(e, pending.index, base, try function(local, imports, pending.target), pending.rd, false); |
| 197 | continue; |
| 198 | } |
| 199 | let target = try resolve(local, imports, pending.target); |
| 200 | let location = try dataRef(target); |
| 201 | if location.slot >= MAX_PACKAGES or location.offset > 0x7ffff7ff { |
| 202 | throw Error::Range; |
| 203 | } |
| 204 | if pending.rd == super::ADDR_SCRATCH or pending.rd == super::GP or pending.rd == super::ZERO { |
| 205 | throw Error::Symbol; |
| 206 | } |
| 207 | let parts = emit::splitImm(location.offset as i32); |
| 208 | emit::patch(e, pending.index, encode::ld(pending.rd, super::GP, location.slot as i32 * 8)); |
| 209 | emit::patch(e, pending.index + 1, encode::lui(super::ADDR_SCRATCH, parts.hi)); |
| 210 | emit::patch(e, pending.index + 2, encode::add(pending.rd, pending.rd, super::ADDR_SCRATCH)); |
| 211 | emit::patch(e, pending.index + 3, encode::addi(pending.rd, pending.rd, parts.lo)); |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | /// Write a little-endian integer into a validated byte extent. |
| 216 | fn integer(bytes: &mut [u8], offset: u32, value: u64, width: u32) { |
| 217 | for i in 0..width { |
| 218 | set bytes[offset + i] = (value >> (i as u64 * 8)) as u8; |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | /// Initialized output extents. |
| 223 | record TemplateSize: Copy { |
| 224 | /// Initialized byte count. |
| 225 | bytes: u32, |
| 226 | /// Private pointer fixup count. |
| 227 | relocations: u32, |
| 228 | } |
| 229 | |
| 230 | /// Build initialized bytes and compact private pointer fixups. |
| 231 | unsafe fn template(items: *[il::Data], local: &[Symbol], imports: &[Symbol], bytes: &mut [u8], relocs: &mut [Relocation]) |
| 232 | -> TemplateSize throws (Error) |
| 233 | { |
| 234 | let mut initialized: u32 = 0; |
| 235 | let mut count: u32 = 0; |
| 236 | for item in items { |
| 237 | if item.isZeroInit { |
| 238 | continue; |
| 239 | } |
| 240 | let location = try dataRef(try resolve(local, &[], item.name)); |
| 241 | if location.offset > bytes.len or item.size > bytes.len - location.offset { |
| 242 | throw Error::Capacity; |
| 243 | } |
| 244 | let end = location.offset + item.size; |
| 245 | if end > initialized { |
| 246 | set initialized = end; |
| 247 | } |
| 248 | } |
| 249 | for i in 0..initialized { |
| 250 | set bytes[i] = 0; |
| 251 | } |
| 252 | for item in items { |
| 253 | if item.isZeroInit { |
| 254 | continue; |
| 255 | } |
| 256 | let location = try dataRef(try resolve(local, &[], item.name)); |
| 257 | let mut offset = location.offset; |
| 258 | let end = offset + item.size; |
| 259 | for value in item.values { |
| 260 | let mut width: u32 = 1; |
| 261 | let mut number: u64 = 0; |
| 262 | match value.item { |
| 263 | case il::DataItem::Val { typ, val } => { |
| 264 | set width = il::typeSize(typ); |
| 265 | set number = val as u64; |
| 266 | }, |
| 267 | case il::DataItem::Fn(name) => { |
| 268 | set width = 8; |
| 269 | set number = try function(local, imports, name); |
| 270 | }, |
| 271 | case il::DataItem::Sym(name) => { |
| 272 | set width = 8; |
| 273 | let target = try dataRef(try resolve(local, imports, name)); |
| 274 | if value.count > 0 { |
| 275 | if count == relocs.len { |
| 276 | throw Error::Capacity; |
| 277 | } |
| 278 | set relocs[count] = Relocation { offset, count: value.count, target }; |
| 279 | set count += 1; |
| 280 | } |
| 281 | }, |
| 282 | case il::DataItem::Str(s) => { |
| 283 | set width = s.len; |
| 284 | }, |
| 285 | case il::DataItem::Undef => { |
| 286 | }, |
| 287 | } |
| 288 | if width > 0 and value.count > (end - offset) / width { |
| 289 | throw Error::Range; |
| 290 | } |
| 291 | if width == 0 { |
| 292 | continue; |
| 293 | } |
| 294 | for _ in 0..value.count { |
| 295 | match value.item { |
| 296 | case il::DataItem::Str(s) => { |
| 297 | try! mem::copy(&mut bytes[offset..offset + width], s); |
| 298 | }, |
| 299 | else => { |
| 300 | integer(bytes, offset, number, width); |
| 301 | }, |
| 302 | } |
| 303 | set offset += width; |
| 304 | } |
| 305 | } |
| 306 | } |
| 307 | return TemplateSize { bytes: initialized, relocations: count }; |
| 308 | } |
| 309 | |
| 310 | /// Package definitions and their native assembly boundaries. |
| 311 | export record AssemblyInput: Copy { |
| 312 | /// Trusted binary RIL definitions that remain valid during compilation. |
| 313 | package: *unsafe binary::Package, |
| 314 | /// Text-only assembly prefix with exported native boundaries. |
| 315 | assembly: asm::Program, |
| 316 | } |
| 317 | |
| 318 | /// Compile one package into caller-owned code, symbol, and private template storage. |
| 319 | /// Imports must contain unique exports from the package's admitted dependencies. |
| 320 | /// Arena storage and binary package names must outlive the returned catalog entry. |
| 321 | export unsafe fn compile(input: &binary::Package, slot: u32, codeAddress: u64, imports: &[Symbol], |
| 322 | storage: Storage, arena: &mut alloc::Arena, scratch: &mut alloc::Arena) -> Package throws (Error) |
| 323 | { |
| 324 | return try compileAssembly(AssemblyInput { package: input as *unsafe binary::Package, |
| 325 | assembly: asm::Program { text: &[], data: &[], symbols: &[], externalFixups: &[] } }, |
| 326 | slot, codeAddress, imports, storage, arena, scratch); |
| 327 | } |
| 328 | |
| 329 | /// Compile a package with a text-only assembly prefix and its exported boundaries. |
| 330 | /// Assembly and RIL definitions share one native code extent and package-state slot. |
| 331 | export unsafe fn compileAssembly(source: AssemblyInput, slot: u32, codeAddress: u64, imports: &[Symbol], |
| 332 | storage: Storage, arena: &mut alloc::Arena, scratch: &mut alloc::Arena) -> Package throws (Error) |
| 333 | { |
| 334 | let case Storage { data: dataStorage, symbols: symbolStorage, exports: exportStorage, template: templateStorage, relocations: relocationStorage } = storage |
| 335 | else panic "expected shared linker storage"; |
| 336 | let input = source.package; |
| 337 | let assembly = source.assembly; |
| 338 | if assembly.data.len <> 0 { |
| 339 | throw Error::Range; |
| 340 | } |
| 341 | if slot >= MAX_PACKAGES { |
| 342 | throw Error::Range; |
| 343 | } |
| 344 | if (codeAddress & 3) <> 0 { |
| 345 | throw Error::Alignment; |
| 346 | } |
| 347 | for imported, i in imports { |
| 348 | if lookup(&imports[..i], imported.name) <> nil { |
| 349 | throw Error::Symbol; |
| 350 | } |
| 351 | } |
| 352 | let mut generator = try super::beginProgram(super::ProgramOptions { |
| 353 | entryPatch: super::EntryPatch::None, debug: false, placement: image::Placement::Hosted, |
| 354 | }, arena) catch err { |
| 355 | throw Error::Codegen(err); |
| 356 | }; |
| 357 | set generator.e.sharedData = true; |
| 358 | super::addAssembly(&mut generator, assembly); |
| 359 | for func in input.program.fns { |
| 360 | super::generateFunction(&mut generator, func, scratch); |
| 361 | } |
| 362 | try emit::check(&generator.e) catch err { |
| 363 | throw Error::Codegen(err); |
| 364 | }; |
| 365 | if codeAddress > 0xffffffffffffffff - generator.e.codeLen as u64 * 4 { |
| 366 | throw Error::Range; |
| 367 | } |
| 368 | let mut symbols: u32 = 0; |
| 369 | for func in &generator.e.funcs[..generator.e.funcsLen] { |
| 370 | try define(symbolStorage, &mut symbols, func.name, Target::Function(codeAddress + func.index as u64 * 4)); |
| 371 | } |
| 372 | let mut dataCount: u32 = 0; |
| 373 | let roSize = try data::layoutSection(input.program.data, dataStorage, &mut dataCount, 0, true) |
| 374 | catch { |
| 375 | throw Error::Range; |
| 376 | }; |
| 377 | let memory = try data::layoutSectionAtOffset(input.program.data, dataStorage, &mut dataCount, 0, roSize, false) |
| 378 | catch { |
| 379 | throw Error::Range; |
| 380 | }; |
| 381 | if memory > 0x7ffff7ff { |
| 382 | throw Error::Range; |
| 383 | } |
| 384 | for item in &dataStorage[..dataCount] { |
| 385 | try define(symbolStorage, &mut symbols, item.name, Target::Data(DataRef { slot, offset: item.addr as u32 })); |
| 386 | } |
| 387 | let local = &symbolStorage[..symbols]; |
| 388 | for symbol in local { |
| 389 | if lookup(imports, symbol.name) <> nil { |
| 390 | throw Error::Symbol; |
| 391 | } |
| 392 | } |
| 393 | try link(&mut generator.e, codeAddress, local, imports); |
| 394 | try emit::check(&generator.e) catch err { |
| 395 | throw Error::Codegen(err); |
| 396 | }; |
| 397 | let size = try template(input.program.data, local, imports, templateStorage, relocationStorage); |
| 398 | if input.exports.len > exportStorage.len { |
| 399 | throw Error::Capacity; |
| 400 | } |
| 401 | for exported, i in input.exports { |
| 402 | let target = try resolve(local, &[], exported.name); |
| 403 | match exported.kind { |
| 404 | case binary::ExportKind::Function => { |
| 405 | try function(local, &[], exported.name); |
| 406 | }, |
| 407 | case binary::ExportKind::Data => { |
| 408 | try dataRef(target); |
| 409 | }, |
| 410 | } |
| 411 | if lookup(&exportStorage[..i], exported.name) <> nil { |
| 412 | throw Error::Symbol; |
| 413 | } |
| 414 | set exportStorage[i] = Symbol { name: exported.name, target }; |
| 415 | } |
| 416 | let mut entry: ?u64 = nil; |
| 417 | if let name = input.entry { |
| 418 | set entry = try function(&exportStorage[..input.exports.len], &[], name); |
| 419 | } |
| 420 | let mut alignment: u32 = 8; |
| 421 | for item in input.program.data { |
| 422 | if item.alignment > alignment { |
| 423 | set alignment = item.alignment; |
| 424 | } |
| 425 | } |
| 426 | let case super::Generator { e, .. } = generator else panic "expected package generator"; |
| 427 | let case emit::Emitter { code, codeLen, .. } = e else panic "expected package emitter"; |
| 428 | return Package { |
| 429 | name: input.name, dependencies: input.dependencies, slot, codeAddress, |
| 430 | code: &code[..codeLen], exports: &exportStorage[..input.exports.len], entry, |
| 431 | template: &templateStorage[..size.bytes], memory, alignment, |
| 432 | relocations: &relocationStorage[..size.relocations], |
| 433 | }; |
| 434 | } |
| 435 | |
| 436 | /// Initialize private package memory after all graph bases have been assigned. |
| 437 | /// Validate every fixup before writing bytes so a rejected instance stays intact. |
| 438 | export fn instantiate(package: &Package, bases: &[u64], memory: &mut [u8]) throws (Error) { |
| 439 | if package.slot >= bases.len or bases[package.slot] == 0 { |
| 440 | throw Error::Instance; |
| 441 | } |
| 442 | if memory.len < package.memory or package.template.len > package.memory { |
| 443 | throw Error::Capacity; |
| 444 | } |
| 445 | if (bases[package.slot] & (package.alignment as u64 - 1)) <> 0 { |
| 446 | throw Error::Alignment; |
| 447 | } |
| 448 | if bases[package.slot] > 0xffffffffffffffff - package.memory as u64 { |
| 449 | throw Error::Range; |
| 450 | } |
| 451 | for fixup in package.relocations { |
| 452 | if fixup.target.slot >= bases.len or bases[fixup.target.slot] == 0 { |
| 453 | throw Error::Instance; |
| 454 | } |
| 455 | if bases[fixup.target.slot] > 0xffffffffffffffff - fixup.target.offset as u64 { |
| 456 | throw Error::Range; |
| 457 | } |
| 458 | if fixup.offset > package.memory or fixup.count > (package.memory - fixup.offset) / 8 { |
| 459 | throw Error::Range; |
| 460 | } |
| 461 | } |
| 462 | for i in 0..package.memory { |
| 463 | set memory[i] = 0; |
| 464 | } |
| 465 | try! mem::copy(memory, package.template); |
| 466 | for fixup in package.relocations { |
| 467 | let address = bases[fixup.target.slot] + fixup.target.offset as u64; |
| 468 | for i in 0..fixup.count { |
| 469 | integer(memory, fixup.offset + i * 8, address, 8); |
| 470 | } |
| 471 | } |
| 472 | } |