compiler/
kernel/
lib/
examples/
std/
arch/
char/
collections/
lang/
alloc/
ast/
gen/
il/
binary/
images/
binary.rad
13.9 KiB
images.rad
10.6 KiB
printer.rad
14.7 KiB
module/
parser/
resolver/
scanner/
alloc.rad
4.3 KiB
ast.rad
23.3 KiB
gen.rad
513 B
il.rad
16.1 KiB
lower.rad
272.5 KiB
module.rad
13.2 KiB
package.rad
1.3 KiB
parser.rad
77.8 KiB
resolver.rad
308.6 KiB
scanner.rad
17.5 KiB
sexpr.rad
6.3 KiB
strings.rad
2.2 KiB
types.rad
286 B
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.1 KiB
intrinsics.rad
467 B
io.rad
1.3 KiB
lang.rad
276 B
mem.rad
2.2 KiB
sys.rad
179 B
testing.rad
2.4 KiB
tests.rad
15.2 KiB
vec.rad
3.2 KiB
std.rad
281 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
351 B
.gitsigners
112 B
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
3.8 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.4 KiB
std.lib.test
380 B
lib/std/lang/il/images.rad
raw
| 1 | //! Freestanding compilation and placement of trusted binary RIL images. |
| 2 | //! |
| 3 | //! Structural and backend-capacity checks do not establish memory safety, |
| 4 | //! pointer provenance, or authority confinement of generated native code. |
| 5 | |
| 6 | export mod graph; |
| 7 | export mod native; |
| 8 | mod layout; |
| 9 | |
| 10 | use std::mem; |
| 11 | use std::collections::dict; |
| 12 | use std::lang::alloc; |
| 13 | use std::lang::strings; |
| 14 | use std::lang::il; |
| 15 | use std::arch::rv64; |
| 16 | use std::arch::rv64::emit; |
| 17 | |
| 18 | /// One explicitly permitted native function in the trusted execution environment. |
| 19 | export record Binding: Copy { |
| 20 | /// Exact qualified external declaration name. |
| 21 | name: *[u8], |
| 22 | /// Callable native address using the image's register and stack ABI. |
| 23 | address: u64, |
| 24 | } |
| 25 | |
| 26 | /// Immutable native image entry and separately instantiated private state. |
| 27 | export record Descriptor: Copy { |
| 28 | /// Native entry receiving the domain Env pointer in a0. |
| 29 | entry: u64, |
| 30 | /// Read-only bytes copied into each domain's private allocation. |
| 31 | initial: u64, |
| 32 | /// Number of private state bytes required by one domain. |
| 33 | stateSize: u32, |
| 34 | /// Power-of-two alignment required by the private allocation. |
| 35 | stateAlignment: u32, |
| 36 | /// Repair private pointers after copying the initializer to its new base. |
| 37 | relocate: fn(u64), |
| 38 | } |
| 39 | |
| 40 | /// Prepared native output borrowing caller-owned compiler storage until finish. |
| 41 | /// Its metadata must not be mutated by the caller; only size and alignment are |
| 42 | /// needed to obtain a destination allocation. No compiler storage escapes finish. |
| 43 | export record Prepared: Copy { |
| 44 | /// Minimum destination capacity in bytes. |
| 45 | size: u32, |
| 46 | /// Required power-of-two destination base alignment. |
| 47 | alignment: u32, |
| 48 | /// Final position-independent native instruction words. |
| 49 | code: *[u32], |
| 50 | /// Immutable initializer bytes containing allocation-relative pointers. |
| 51 | data: *[u8], |
| 52 | /// Flattened initializer declarations used to identify pointer slots. |
| 53 | items: *[il::Data], |
| 54 | /// Allocation-relative byte offset of native code. |
| 55 | codeOffset: u32, |
| 56 | /// Allocation-relative byte offset of the trusted entry wrapper. |
| 57 | entryOffset: u32, |
| 58 | /// Allocation-relative byte offset of the private initializer. |
| 59 | initialOffset: u32, |
| 60 | /// Allocation-relative byte offset of the private pointer repair helper. |
| 61 | relocateOffset: u32, |
| 62 | /// Number of separately allocated private state bytes. |
| 63 | stateSize: u32, |
| 64 | /// Alignment required by separately allocated private state. |
| 65 | stateAlignment: u32, |
| 66 | /// Immutable source range excluded from destination placement. |
| 67 | source: *[u8], |
| 68 | /// Persistent compiler arena range excluded from destination placement. |
| 69 | arena: *[u8], |
| 70 | /// Temporary compiler arena range excluded from destination placement. |
| 71 | scratch: *[u8], |
| 72 | /// Caller-owned interning pool range excluded from destination placement. |
| 73 | pool: *[u8], |
| 74 | } |
| 75 | |
| 76 | /// Reject wrapping memory ranges before overlap or placement arithmetic. |
| 77 | fn range(bytes: *[u8]) throws (il::binary::Error) { |
| 78 | let base = bytes.ptr as u64; |
| 79 | if bytes.len as u64 > 0xFFFFFFFFFFFFFFFF - base { |
| 80 | throw il::binary::error(0, "image memory range wraps address space"); |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | /// Detect overlap between already checked byte ranges without pointer ordering. |
| 85 | fn overlap(a: *[u8], b: *[u8]) -> bool { |
| 86 | if a.len == 0 or b.len == 0 { return false; } |
| 87 | let first = a.ptr as u64; |
| 88 | let second = b.ptr as u64; |
| 89 | return first < second + b.len as u64 and second < first + a.len as u64; |
| 90 | } |
| 91 | |
| 92 | /// Require aligned, disjoint compiler storage and a caller-initialized empty pool. |
| 93 | fn storage(source: *[u8], arena: *alloc::Arena, scratch: *alloc::Arena, pool: *strings::Pool) throws (il::binary::Error) { |
| 94 | try range(source); try range(arena.data); try range(scratch.data); |
| 95 | let poolBytes = @sliceOf(pool as *opaque as *u8, @sizeOf(strings::Pool)); |
| 96 | try range(poolBytes); |
| 97 | if arena.offset > arena.data.len or scratch.offset > scratch.data.len |
| 98 | or arena.data.len > 0x7FFFFFFF or scratch.data.len > 0x7FFFFFFF |
| 99 | or arena.data.ptr as u64 % 8 <> 0 or scratch.data.ptr as u64 % 8 <> 0 { |
| 100 | throw il::binary::error(0, "invalid image compiler arena"); |
| 101 | } |
| 102 | if overlap(arena.data, scratch.data) or overlap(source, arena.data) or overlap(source, scratch.data) |
| 103 | or overlap(poolBytes, arena.data) or overlap(poolBytes, scratch.data) or overlap(poolBytes, source) { |
| 104 | throw il::binary::error(0, "image compiler storage overlaps"); |
| 105 | } |
| 106 | if pool.count <> 0 { throw il::binary::error(0, "image compiler requires an empty string pool"); } |
| 107 | for i in 0..pool.table.len { |
| 108 | if pool.table[i].len <> 0 { throw il::binary::error(0, "image compiler requires an empty string pool"); } |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | /// Decode and compile a closed trusted image using the shared RV64 backend. |
| 113 | /// Source stays immutable through finish. Arena, scratch, and empty pool must |
| 114 | /// be disjoint and live through finish; arenas require eight-byte aligned bases. |
| 115 | /// The emitter reserves an eight-MiB code buffer plus fixed symbol/relocation |
| 116 | /// tables in arena. Remaining arena use depends on names and initialized data; |
| 117 | /// scratch holds the decoded graph and one function's register allocation at a |
| 118 | /// time. Every exhaustion is reported as a binary Error. Failed preparation may |
| 119 | /// consume both arenas and pool; reclaim them together before the next attempt. |
| 120 | export fn prepare(source: *[u8], bindings: *[Binding], arena: *mut alloc::Arena, |
| 121 | scratch: *mut alloc::Arena, pool: *mut strings::Pool) -> Prepared throws (il::binary::Error) { |
| 122 | if source.len > 16 * 1024 * 1024 { throw il::binary::error(0, "binary image exceeds capacity"); } |
| 123 | try storage(source, arena, scratch, pool); |
| 124 | if bindings.len > 4096 { throw il::binary::error(0, "too many native image bindings"); } |
| 125 | let mut bindingIndex = try il::binary::dictionary(scratch, bindings.len, 0); |
| 126 | for binding, i in bindings { |
| 127 | if binding.name.len <= 11 or not mem::eq(&binding.name[..11], "user::sys::") |
| 128 | or binding.address == 0 or binding.address % 4 <> 0 { |
| 129 | throw il::binary::error(0, "invalid native image binding"); |
| 130 | } |
| 131 | if dict::get(&bindingIndex, binding.name) <> nil { throw il::binary::error(0, "duplicate native image binding"); } |
| 132 | dict::insert(&mut bindingIndex, binding.name, i as i32); |
| 133 | } |
| 134 | let image = try il::binary::decode(source, scratch, pool); |
| 135 | let plan = try graph::plan(&image, scratch); |
| 136 | let names = try graph::names(&image.program, 0, pool, arena); |
| 137 | let stateName = try graph::name("images::state_", 0, "", pool, arena); |
| 138 | let initial = try native::initializer(&plan, &names, stateName, arena, scratch); |
| 139 | let emitter = try emit::emitter(arena, false) catch { throw il::binary::error(0, "native image emitter arena exhausted"); }; |
| 140 | let mut generator = rv64::Generator { e: emitter, entryPatch: rv64::EntryPatch::None }; |
| 141 | for function, i in image.program.fns { |
| 142 | if not function.isExtern { continue; } |
| 143 | if function.params.len > rv64::ARG_REGS.len { throw il::binary::error(0, "image native declaration has too many arguments"); } |
| 144 | let binding = dict::get(&bindingIndex, function.name) else { |
| 145 | throw il::binary::error(0, "unresolved image sys native declaration"); |
| 146 | }; |
| 147 | try native::trampoline(&mut generator, names.functions[i], bindings[binding as u32].address); |
| 148 | } |
| 149 | let mut offsets = try il::binary::dictionary(scratch, plan.order.len, 0); |
| 150 | for item, i in plan.items { |
| 151 | if not item.readOnly { dict::insert(&mut offsets, names.data[i], plan.offsets[i] as i32); } |
| 152 | } |
| 153 | set generator.e.instanceData = &offsets; |
| 154 | for function, i in image.program.fns { |
| 155 | if function.isExtern { continue; } |
| 156 | let renamed = try graph::function(function, i, &plan, &names, scratch); |
| 157 | try native::function(&mut generator, &renamed, scratch); |
| 158 | } |
| 159 | set generator.e.instanceData = nil; |
| 160 | try native::helpers(&mut generator, 0, &plan, &names, &initial, pool, arena); |
| 161 | let placed = try layout::prepare(&mut generator, initial.items, arena, scratch); |
| 162 | let entryName = try graph::name("images::native_", 0, "", pool, arena); |
| 163 | let relocateName = try graph::name("images::relocate_", 0, "", pool, arena); |
| 164 | let entryOffset = placed.codeOffset + (try layout::functionOffset(&generator.e, entryName)); |
| 165 | let relocateOffset = placed.codeOffset + (try layout::functionOffset(&generator.e, relocateName)); |
| 166 | let initialOffset = dict::get(&placed.symbols.dict, stateName) else { |
| 167 | throw il::binary::error(0, "unresolved native image initializer"); |
| 168 | }; |
| 169 | return Prepared { size: placed.size, alignment: placed.alignment, code: emit::getCode(&generator.e), |
| 170 | data: placed.bytes, items: initial.items, codeOffset: placed.codeOffset, entryOffset, |
| 171 | initialOffset: initialOffset as u32, relocateOffset, stateSize: plan.size, stateAlignment: plan.alignment, |
| 172 | source, arena: arena.data, scratch: scratch.data, pool: @sliceOf(pool as *opaque as *u8, @sizeOf(strings::Pool)) }; |
| 173 | } |
| 174 | |
| 175 | /// Copy prepared native bytes and relocate persistent pointers at destination. |
| 176 | /// Destination must be aligned, large enough, and disjoint from compiler storage |
| 177 | /// and source. No allocation or compilation occurs here. The returned descriptor |
| 178 | /// borrows only destination, which must remain immutable for its execution life. |
| 179 | /// The caller must synchronize instruction fetch before any hart enters the code. |
| 180 | export fn finish(prepared: *mut Prepared, destination: *mut [u8]) -> Descriptor throws (il::binary::Error) { |
| 181 | try range(destination); |
| 182 | let base = destination.ptr as u64; |
| 183 | if base == 0 or prepared.alignment == 0 or prepared.alignment & (prepared.alignment - 1) <> 0 |
| 184 | or base % prepared.alignment as u64 <> 0 or destination.len < prepared.size { |
| 185 | throw il::binary::error(0, "invalid native image destination"); |
| 186 | } |
| 187 | let output = &mut destination[..prepared.size]; |
| 188 | if overlap(output, prepared.source) or overlap(output, prepared.arena) |
| 189 | or overlap(output, prepared.scratch) or overlap(output, prepared.pool) { |
| 190 | throw il::binary::error(0, "native image destination overlaps compiler storage"); |
| 191 | } |
| 192 | try! mem::copy(output, prepared.data); |
| 193 | for i in prepared.data.len..prepared.codeOffset { set output[i] = 0; } |
| 194 | let code = @sliceOf(prepared.code.ptr as *u8, prepared.code.len * rv64::INSTR_SIZE as u32); |
| 195 | try! mem::copy(&mut output[prepared.codeOffset..], code); |
| 196 | layout::rebase(prepared.items, output, base); |
| 197 | let relocateAddress = base + prepared.relocateOffset as u64; |
| 198 | let relocate = *(&relocateAddress as *opaque as *fn(u64)); |
| 199 | return Descriptor { entry: base + prepared.entryOffset as u64, initial: base + prepared.initialOffset as u64, |
| 200 | stateSize: prepared.stateSize, stateAlignment: prepared.stateAlignment, |
| 201 | relocate }; |
| 202 | } |