lib/std/lang/il/images.rad 10.6 KiB 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
}