rv64: Generate shared package code

e237f812165afff39184f2bb816f3d0749e39bd5e954e17f381bdee98e4ffedf
Alexis Sellier committed ago 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