rv64: Generate shared package code

7b0dbbf9f1ad606c201e3ee0cabca79ad157e15c6a710746ab3c0744fda40fb8
Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent b83ecdff
Makefile +10 -2
21 21
22 22
# Verify the emulator binary exists.
23 23
EMU_PATH := $(shell command -v $(EMU) 2>/dev/null)
24 24
25 25
default: emulator $(RAD_BIN)
26 -
test: emulator seed-test std-test bin-test kernel-test package-test native-test
26 +
test: emulator seed-test std-test bin-test kernel-test package-test native-test shared-test
27 27
28 28
seed-test:
29 29
	@seed/test
30 30
31 31
# Emulator command check
94 94
	@$(RADIANCE) $(STD) -pkg build -mod $< -entry build -o $@
95 95
96 96
$(BIN_DIR)/native.fixture.rv64: $(BIN_DIR)/native.build.rv64
97 97
	@$(EMU) -run $< -- $@
98 98
99 +
# Shared package execution
100 +
101 +
shared-test: $(BIN_DIR)/shared.build.rv64
102 +
	@RAD_EMULATOR="$(EMU)" test/shared/run
103 +
104 +
$(BIN_DIR)/shared.build.rv64: test/shared/build.rad $(STD_LIB) $(RAD_BIN)
105 +
	@$(RADIANCE) $(STD) -pkg build -mod $< -entry build -o $@
106 +
99 107
# Binary Tests
100 108
101 109
BIN_TEST_DIR := test/tests
102 110
# Only tests with `//! returns:` are compiled to binaries and executed.
103 111
BIN_TEST_EXE_SRC := $(shell grep -rl '^//! returns:' $(BIN_TEST_DIR))
151 159
clean: clean-std-test clean-bin-test clean-rad
152 160
153 161
t: test
154 162
c: clean
155 163
156 -
.PHONY: test clean default seed-test std-test bin-test kernel-test package-test native-test seed \
164 +
.PHONY: test clean default seed-test std-test bin-test kernel-test package-test native-test shared-test seed \
157 165
	clean-std-test clean-bin-test clean-rad emulator
158 166
.SUFFIXES:
159 167
.DELETE_ON_ERROR:
160 168
.SILENT:
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
test/shared/app.rad added +16 -0
1 +
//! Shared entry with callbacks, initialized data pointers, and private counters.
2 +
3 +
use support;
4 +
5 +
/// Callback table contains a linked dependency function address.
6 +
static CALLBACKS: [fn() -> i32; 1] = [support::read];
7 +
/// The slice contains a pointer into this domain's string storage.
8 +
static TEXT: *[u8] = "xy";
9 +
/// Number of calls in the current domain.
10 +
static calls: i32 = 0;
11 +
12 +
/// Read dependency state through calls and data, then advance private state.
13 +
@default export fn main() -> i32 {
14 +
    set calls += 1;
15 +
    return support::read() + CALLBACKS[0]() + support::value + TEXT[0] as i32 + calls;
16 +
}
test/shared/build.rad added +148 -0
1 +
//! Build a machine fixture from separately decoded and linked packages.
2 +
3 +
use std::sys;
4 +
use std::sys::unix;
5 +
use std::mem;
6 +
use std::lang::alloc;
7 +
use std::lang::il::binary;
8 +
use std::lang::il::binary::program;
9 +
use std::lang::gen::data;
10 +
use std::arch::rv64;
11 +
use std::arch::rv64::shared;
12 +
use std::arch::rv64::emit;
13 +
use std::arch::rv64::encode;
14 +
use std::arch::rv64::image;
15 +
16 +
/// Separate code arenas preserve the boot catalog's package records.
17 +
unsafe static CODE: [[u8; 16777216]; 3] = undefined;
18 +
/// Reusable function workspace.
19 +
static SCRATCH: [u8; 16777216] = [0; 16777216];
20 +
/// Decoder arena shared by the two input packages.
21 +
static DECODE: [u8; 1048576] = [0; 1048576];
22 +
/// Input file buffer.
23 +
static INPUT: [u8; 65536] = [0; 65536];
24 +
/// Persistent private templates.
25 +
unsafe static TEMPLATES: [[u8; 16384]; 2] = undefined;
26 +
/// Local symbol workspaces.
27 +
unsafe static SYMBOLS: [[shared::Symbol; 128]; 2] = undefined;
28 +
/// Package exports.
29 +
unsafe static EXPORTS: [[shared::Symbol; 128]; 2] = undefined;
30 +
/// Private pointer fixups.
31 +
unsafe static RELOCS: [[shared::Relocation; 128]; 2] = undefined;
32 +
/// Data layout workspaces.
33 +
unsafe static DATA: [[data::DataSym; 128]; 2] = undefined;
34 +
/// Native instruction image, with fixed package placements.
35 +
static TEXT: [u32; 4096] = [0; 4096];
36 +
/// Two private graphs followed by their package-state tables.
37 +
static STATE: [u8; 69632] = [0; 69632];
38 +
39 +
/// Decode trusted package IL into persistent storage.
40 +
unsafe fn load(path: *[u8], arena: &mut alloc::Arena) -> binary::Package {
41 +
    let length = unix::readFile(path, &mut INPUT[..]) else panic "missing package";
42 +
    return try! program::decode(&INPUT[..length], arena, binary::Limits { registers: 8192, blocks: 4096 });
43 +
}
44 +
45 +
/// Compile through the same entry used for boot catalogs and runtime loading.
46 +
unsafe fn compile(input: &binary::Package, index: u32, imports: &[shared::Symbol]) -> shared::Package throws (shared::Error) {
47 +
    let mut arena = alloc::new(&mut CODE[index][..]);
48 +
    let mut scratch = alloc::new(&mut SCRATCH[..]);
49 +
    return try shared::compile(input, index, 0x80011000 + index as u64 * 4096, imports,
50 +
        shared::Storage {
51 +
            data: &mut DATA[index][..], symbols: &mut SYMBOLS[index][..], exports: &mut EXPORTS[index][..],
52 +
            template: &mut TEMPLATES[index][..], relocations: &mut RELOCS[index][..],
53 +
        }, &mut arena, &mut scratch);
54 +
}
55 +
56 +
/// Append a call result check and return its branch patch location.
57 +
fn check(e: &mut emit::Emitter, entry: u64, expected: i64) -> u32 {
58 +
    let delta = entry - (0x80010000 + e.codeLen as u64 * 4);
59 +
    emit::emit(e, encode::jal(rv64::RA, delta as i32));
60 +
    emit::loadImm(e, rv64::T0, expected);
61 +
    let index = e.codeLen;
62 +
    emit::emit(e, encode::nop());
63 +
    return index;
64 +
}
65 +
66 +
/// Emit an explicit platform completion status.
67 +
fn finish(e: &mut emit::Emitter, status: i64) {
68 +
    emit::loadImm(e, rv64::T0, 0x10001000);
69 +
    emit::loadImm(e, rv64::T1, status);
70 +
    emit::emit(e, encode::sw(rv64::T1, rv64::T0, 0));
71 +
    emit::emit(e, encode::ebreak());
72 +
}
73 +
74 +
/// Verify catalog linking, then execute one code copy against two private graphs.
75 +
@default unsafe fn main(env: *sys::Env) -> i32 {
76 +
    assert env.args.len == 4;
77 +
    let mut decoder = alloc::new(&mut DECODE[..]);
78 +
    let supportInput = load(env.args[1], &mut decoder);
79 +
    let appInput = load(env.args[2], &mut decoder);
80 +
    let support = try! compile(&supportInput, 0, &[]);
81 +
    let mut far: [shared::Symbol; 128] = undefined;
82 +
    for symbol, i in support.exports {
83 +
        set far[i] = symbol;
84 +
        match symbol.target {
85 +
            case shared::Target::Function(_) => { set far[i].target = shared::Target::Function(0x180000000); },
86 +
            else => {},
87 +
        }
88 +
    }
89 +
    let mut rejected = false;
90 +
    try compile(&appInput, 1, &far[..support.exports.len]) catch err {
91 +
        assert err == shared::Error::Range;
92 +
        set rejected = true;
93 +
    };
94 +
    assert rejected;
95 +
    let app = try! compile(&appInput, 1, support.exports);
96 +
    let catalog = [support, app];
97 +
    assert app.relocations.len > 0;
98 +
    let entry = app.entry else panic "missing entry";
99 +
    for i in 0..STATE.len { set STATE[i] = 0; }
100 +
    for domain in 0..2 {
101 +
        let mut bases: [u64; 256] = [0; 256];
102 +
        for package, i in &catalog[..] {
103 +
            let offset = domain * 32768 + i * 16384;
104 +
            set bases[i] = 0x80020000 + offset as u64;
105 +
        }
106 +
        for package, i in &catalog[..] {
107 +
            let offset = domain * 32768 + i * 16384;
108 +
            try! shared::instantiate(&package, &bases[..], &mut STATE[offset..offset + 16384]);
109 +
        }
110 +
        let table = @sliceOf(&bases[0] as *unsafe u8, @sizeOf([u64; 256]));
111 +
        let tableOffset: u32 = 65536 + domain * 2048;
112 +
        try! mem::copy(&mut STATE[tableOffset..], table);
113 +
    }
114 +
    let mut arena = alloc::new(&mut CODE[2][..]);
115 +
    let mut e = try! emit::emitter(&mut arena, false);
116 +
    emit::loadImm(&mut e, rv64::GP, 0x80030000);
117 +
    let first = check(&mut e, entry, 142);
118 +
    let second = check(&mut e, entry, 143);
119 +
    emit::loadImm(&mut e, rv64::GP, 0x80030800);
120 +
    let other = check(&mut e, entry, 142);
121 +
    emit::loadImm(&mut e, rv64::GP, 0x80030000);
122 +
    let resumed = check(&mut e, entry, 144);
123 +
    finish(&mut e, 0x5555);
124 +
    let failure = e.codeLen;
125 +
    finish(&mut e, 0x13333);
126 +
    for index in &[first, second, other, resumed] {
127 +
        emit::patch(&mut e, index, encode::bne(rv64::A0, rv64::T0, (failure - index) as i32 * 4));
128 +
    }
129 +
    for i in 0..TEXT.len { set TEXT[i] = encode::nop(); }
130 +
    assert e.codeLen <= 1024;
131 +
    for word, i in emit::getCode(&e) { set TEXT[i] = word; }
132 +
    for package, i in &catalog[..] {
133 +
        assert package.code.len <= 1024;
134 +
        for word, j in package.code { set TEXT[1024 + i * 1024 + j] = word; }
135 +
    }
136 +
    let header = try! image::header(image::Layout {
137 +
        entry: 0x80010000,
138 +
        code: image::Segment { address: 0x80010000, initialized: 16384, memory: 16384 },
139 +
        roData: image::Segment { address: 0, initialized: 0, memory: 0 },
140 +
        rwData: image::Segment { address: 0x80020000, initialized: STATE.len, memory: STATE.len },
141 +
    });
142 +
    let fd = unix::openOpts(env.args[3], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420);
143 +
    assert fd >= 0;
144 +
    let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, @sliceOf(&TEXT[0] as *u8, 16384)) and unix::writeAll(fd, &STATE[..]);
145 +
    let closed = unix::close(fd) == 0;
146 +
    assert written and closed;
147 +
    return 0;
148 +
}
test/shared/run added +13 -0
1 +
#!/bin/sh
2 +
# Compile two packages once and execute them with two private state graphs.
3 +
set -eu
4 +
emulator=${RAD_EMULATOR:-emulator}
5 +
work=$(mktemp -d)
6 +
trap 'rm -rf "$work"' EXIT HUP INT TERM
7 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
8 +
    -run bin/radiance.rv64.dev \
9 +
    -pkg support -mod test/shared/support.rad \
10 +
    -pkg app -mod test/shared/app.rad -entry app -ril "$work"
11 +
"$emulator" -run bin/shared.build.rv64 -- "$work/support.ril" "$work/app.ril" "$work/native.rv64"
12 +
"$emulator" -machine -run "$work/native.rv64"
13 +
printf 'shared packages: calls, callbacks, private pointers, and independent state passed\n'
test/shared/support.rad added +15 -0
1 +
//! Shared dependency with private writable state.
2 +
3 +
/// Value read by dependent packages.
4 +
export static value: i32 = 7;
5 +
6 +
/// Large private allocation before the final zero-initialized scalar.
7 +
static padding: [u8; 8192] = [0; 8192];
8 +
/// Zero-initialized value at a large package offset.
9 +
static tail: i32 = 0;
10 +
11 +
/// Read this domain's value.
12 +
export fn read() -> i32 {
13 +
    set padding[8191] = 0;
14 +
    return value + tail;
15 +
}