kernel: Register global packages

066b2e8c62df3d2e5c88a352d2f681f3144543e78a850a2cf1f46e7559491de4
Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent 307f500b
compiler/radiance.rad +1 -0
1114 1114
                    set ident = decl.ident; set attrs = decl.attrs; set kind = binary::ExportKind::Data;
1115 1115
                },
1116 1116
                else => continue,
1117 1117
            }
1118 1118
            let attributes = attrs else continue;
1119 +
            if ast::attributesContains(&attributes, ast::Attribute::Intrinsic) { continue; }
1119 1120
            let isDefault = ast::attributesContains(&attributes, ast::Attribute::Default)
1120 1121
                and pkg.rootModuleId == modEntry.id;
1121 1122
            if not isDefault and not ast::attributesContains(&attributes, ast::Attribute::Export) {
1122 1123
                continue;
1123 1124
            }
kernel/kernel.rad +1 -0
11 11
export mod platform;
12 12
export mod trap;
13 13
export mod capability;
14 14
export mod transactions;
15 15
export mod events;
16 +
export mod registry;
16 17
export mod frames;
17 18
export mod backing;
18 19
export mod pages;
19 20
export mod boot;
20 21
@test export mod tests;
kernel/kernel/boot.rad +14 -2
5 5
use super::range;
6 6
use super::limits;
7 7
use super::sync;
8 8
use super::trap;
9 9
use super::pages;
10 +
use super::registry;
11 +
use std::arch::rv64::shared::catalog;
10 12
11 13
/// Platform data published by hart zero before secondary initialization.
12 14
export unsafe static PLATFORM: platform::Platform = undefined;
13 15
/// Release/acquire publication flag for PLATFORM.
14 16
static READY: u64 = 0;
46 48
    }
47 49
}
48 50
49 51
/// Initialize one hart from firmware arguments. Return true for the last hart.
50 52
/// Firmware supplies a mapped FDT and a disjoint, reserved stack for each hart.
51 -
export unsafe fn enter(hart: u64, tree: *u8, stackTop: u64) -> bool {
53 +
export unsafe fn enter(hart: u64, tree: *u8, stackTop: u64, entries: *catalog::Entry, entryCount: u32, stateTable: *u64) -> bool {
52 54
    let treeAddress = tree as u64;
53 55
    assert hart < limits::HARTS as u64;
54 56
    if hart == 0 {
55 57
        assert (treeAddress & 7) == 0 and treeAddress <= 0xffffffffffffffff - 65536;
56 58
        let prefix = @sliceOf(tree, 40);
60 62
        try! platform::decode(&blob[..], &mut PLATFORM);
61 63
        let treeRange = range::new(treeAddress, size as u64) else panic "FDT range";
62 64
        assert platform::inRam(&PLATFORM, treeRange);
63 65
        try! platform::protect(&mut PLATFORM, treeRange);
64 66
        try! pages::initialize(&mut pages::STORE, &PLATFORM);
67 +
        assert entryCount == 2;
68 +
        registry::initialize(&mut registry::STORE);
69 +
        let bootCatalog = @sliceOf(entries, entryCount);
70 +
        try! registry::boot(&mut registry::STORE, &bootCatalog[..]);
71 +
        let library = registry::find(&registry::STORE, &"std"[..]) else panic "boot std";
72 +
        let kernel = registry::find(&registry::STORE, &"kernel"[..]) else panic "boot kernel";
73 +
        assert library.index == 0 and kernel.index == 1;
74 +
        let bases = @sliceOf(stateTable, limits::PACKAGES);
75 +
        assert bases[library.index] <> 0 and bases[kernel.index] <> 0;
76 +
        print("kernel: boot catalog ready\n");
65 77
        print("kernel: platform ready\n");
66 78
        sync::storeRelease(&mut READY, 1);
67 79
    } else {
68 80
        while sync::loadAcquire(&READY) == 0 {}
69 81
    }
70 82
    assert (PLATFORM.harts & (1 << hart as u32)) <> 0;
71 83
    assert stackTop == PLATFORM.stacks[hart as u32].end and (stackTop & 15) == 0;
72 84
    let stack = PLATFORM.stacks[hart as u32];
73 85
    set HARTS[hart as u32] = trap::Hart {
74 -
        stackTop: stack.end, stackBottom: stack.start, kernelGp: 0, handler: unexpected,
86 +
        stackTop: stack.end, stackBottom: stack.start, kernelGp: stateTable as u64, handler: unexpected,
75 87
        savedT0: 0, savedT1: 0, savedSp: 0,
76 88
    };
77 89
    trap::install(&mut HARTS[hart as u32]);
78 90
    let mut count: u64 = 0;
79 91
    for id in 0..limits::HARTS { if (PLATFORM.harts & (1 << id)) <> 0 { set count += 1; } }
kernel/kernel/boot.ras +1 -0
15 15
@kernel::boot::initialize
16 16
    csrw mie %zero;
17 17
    csrw mstatus %zero;
18 18
    csrw mscratch %sp;
19 19
    mv %a2 %sp;
20 +
    mv %a5 %gp;
20 21
    addi %sp %sp -16;
21 22
    sd %ra 0(%sp);
22 23
    la %t0 @kernel::boot::enter;
23 24
    jalr %ra %t0 0;
24 25
    ld %ra 0(%sp);
kernel/kernel/registry.rad added +148 -0
1 +
//! Immutable resident packages shared by boot and runtime loading.
2 +
3 +
use std::mem;
4 +
use std::arch::rv64::shared;
5 +
use std::arch::rv64::shared::catalog;
6 +
use super::abi;
7 +
use super::limits;
8 +
use super::slots;
9 +
use super::capability;
10 +
11 +
/// Rights installed on a new Image capability.
12 +
export constant IMAGE_RIGHTS: u16 = abi::READ | abi::EXECUTE | abi::GRANT | abi::TRANSFER;
13 +
14 +
/// Fixed package registry. Callers serialize admission and handle publication.
15 +
export record Store: Copy {
16 +
    /// Package generations; published slots remain live until shutdown.
17 +
    slots: [slots::Slot; limits::PACKAGES],
18 +
    /// Descriptors whose backing storage remains resident until shutdown.
19 +
    entries: [catalog::Entry; limits::PACKAGES],
20 +
    /// Resolved dependencies in each package's declared order.
21 +
    dependencies: [[abi::Ref; limits::PACKAGES]; limits::PACKAGES],
22 +
    /// Number of valid resolved dependencies per live package.
23 +
    counts: [u32; limits::PACKAGES],
24 +
}
25 +
26 +
/// Global resident package registry shared by all domains.
27 +
export unsafe static STORE: Store = undefined;
28 +
29 +
/// Initialize an empty registry before loading any native package.
30 +
export fn initialize(store: &mut Store) { slots::initialize(&mut store.slots[..]); }
31 +
32 +
/// Find a resident package by its immutable name.
33 +
export fn find(store: &Store, name: &[u8]) -> ?abi::Ref {
34 +
    for i in 0..limits::PACKAGES {
35 +
        if store.slots[i].state <> slots::State::Live { continue; }
36 +
        if mem::eq(store.entries[i].package.name, &name[..]) {
37 +
            return abi::Ref { index: i, generation: store.slots[i].generation };
38 +
        }
39 +
    }
40 +
    return nil;
41 +
}
42 +
43 +
/// Reuse identical content and reject conflicting content under a resident name.
44 +
export fn identify(store: &Store, name: &[u8], source: &[u8]) -> ?abi::Ref throws (abi::Error) {
45 +
    let object = find(store, name) else { return nil; };
46 +
    if not mem::eq(store.entries[object.index].source, &source[..]) { throw abi::Error::VerifyFailed; }
47 +
    return object;
48 +
}
49 +
50 +
/// Reserve the stable package-state slot before compiling its native code.
51 +
export fn reserve(store: &mut Store) -> slots::Reservation throws (abi::Error) {
52 +
    return try slots::reserve(&mut store.slots[..]);
53 +
}
54 +
55 +
/// Return unpublished capacity after a failed load.
56 +
export fn cancel(store: &mut Store, reservation: slots::Reservation) {
57 +
    try! slots::cancel(&mut store.slots[..], reservation);
58 +
}
59 +
60 +
/// Validate dependencies without changing registry payloads.
61 +
unsafe fn validate(store: &Store, object: abi::Ref, package: &shared::Package) throws (abi::Error) {
62 +
    if package.name.len == 0 or package.slot <> object.index or package.dependencies.len > limits::PACKAGES {
63 +
        throw abi::Error::VerifyFailed;
64 +
    }
65 +
    for dependency, i in package.dependencies {
66 +
        let target = find(store, &dependency[..]) else { throw abi::Error::VerifyFailed; };
67 +
        for prior in &package.dependencies[..i] {
68 +
            if mem::eq(prior, dependency) { throw abi::Error::VerifyFailed; }
69 +
        }
70 +
    }
71 +
}
72 +
73 +
/// Publish persistent compiler output after successful admission checks.
74 +
/// All descriptor pointers and source bytes must remain immutable and resident.
75 +
/// Failure cancels the reservation; duplicate content returns its existing slot.
76 +
export unsafe fn publish(store: &mut Store, reservation: slots::Reservation, source: *[u8], package: shared::Package)
77 +
    -> abi::Ref throws (abi::Error)
78 +
{
79 +
    let existing = try identify(store, &package.name[..], &source[..]) catch err {
80 +
        cancel(store, reservation); throw err;
81 +
    };
82 +
    if let object = existing { cancel(store, reservation); return object; }
83 +
    let object = slots::reference(&reservation);
84 +
    try validate(store, object, &package) catch err { cancel(store, reservation); throw err; };
85 +
    for dependency, i in package.dependencies {
86 +
        let target = find(store, &dependency[..]) else panic "resident dependency";
87 +
        set store.dependencies[object.index][i] = target;
88 +
    }
89 +
    set store.counts[object.index] = package.dependencies.len;
90 +
    set store.entries[object.index] = catalog::Entry { source, package };
91 +
    return try! slots::commit(&mut store.slots[..], reservation);
92 +
}
93 +
94 +
/// Read resident metadata through a generation-bearing package reference.
95 +
export fn get(store: &Store, object: abi::Ref) -> shared::Package throws (abi::Error) {
96 +
    if not slots::matches(&store.slots[..], object, slots::State::Live) { throw abi::Error::BadHandle; }
97 +
    return store.entries[object.index].package;
98 +
}
99 +
100 +
/// Resolve one public symbol without searching packages outside the named image.
101 +
export fn exported(store: &Store, object: abi::Ref, name: &[u8]) -> shared::Target throws (abi::Error) {
102 +
    let package = try get(store, object);
103 +
    let target = shared::lookup(package.exports, &name[..]) else { throw abi::Error::VerifyFailed; };
104 +
    return target;
105 +
}
106 +
107 +
/// Collect exports from declared dependencies into caller-owned compiler storage.
108 +
export fn imports(store: &Store, dependencies: &[*[u8]], output: &mut [shared::Symbol]) -> u32 throws (abi::Error) {
109 +
    let mut count: u32 = 0;
110 +
    for name in dependencies {
111 +
        let object = find(store, &name[..]) else { throw abi::Error::VerifyFailed; };
112 +
        let package = try! get(store, object);
113 +
        if package.exports.len > output.len - count { throw abi::Error::Exhausted; }
114 +
        set count += package.exports.len;
115 +
    }
116 +
    set count = 0;
117 +
    for name in dependencies {
118 +
        let object = find(store, &name[..]) else panic "resident dependency";
119 +
        let package = try! get(store, object);
120 +
        for symbol in package.exports { set output[count] = symbol; set count += 1; }
121 +
    }
122 +
    return count;
123 +
}
124 +
125 +
/// Register a persistent boot catalog in dependency order through normal admission.
126 +
export unsafe fn boot(store: &mut Store, catalog: &[catalog::Entry]) throws (abi::Error) {
127 +
    for entry in catalog {
128 +
        let existing = try identify(store, &entry.package.name[..], &entry.source[..]);
129 +
        if existing <> nil { continue; }
130 +
        let reservation = try reserve(store);
131 +
        let object = try publish(store, reservation, entry.source, entry.package);
132 +
    }
133 +
}
134 +
135 +
/// Install an Image handle for a resident package without duplicating its code.
136 +
export fn install(store: &Store, table: &mut capability::Table, object: abi::Ref) -> abi::Handle throws (abi::Error) {
137 +
    let package = try get(store, object);
138 +
    return try capability::install(table, capability::Entry {
139 +
        kind: abi::Kind::Image, object, rights: abi::Rights(IMAGE_RIGHTS),
140 +
    });
141 +
}
142 +
143 +
/// Resolve image authority and validate its resident package generation.
144 +
export fn image(store: &Store, table: &capability::Table, handle: abi::Handle, rights: abi::Rights) -> abi::Ref throws (abi::Error) {
145 +
    let entry = try capability::lookup(table, handle, abi::Kind::Image, rights);
146 +
    let package = try get(store, entry.object);
147 +
    return entry.object;
148 +
}
kernel/kernel/tests.rad +1 -0
10 10
export mod frames;
11 11
export mod backing;
12 12
export mod pages;
13 13
export mod transactions;
14 14
export mod events;
15 +
export mod registry;
kernel/kernel/tests/registry.rad added +139 -0
1 +
//! Resident package identity, dependency admission, and image capabilities.
2 +
3 +
use std::testing;
4 +
use std::arch::rv64::shared;
5 +
use std::arch::rv64::shared::catalog;
6 +
use kernel::abi;
7 +
use kernel::slots;
8 +
use kernel::capability;
9 +
use kernel::registry;
10 +
11 +
/// Resident registry metadata workspace.
12 +
unsafe static STORE: registry::Store = undefined;
13 +
/// Persistent distinct names for complete registry exhaustion.
14 +
unsafe static NAMES: [[u8; 4]; 256] = undefined;
15 +
16 +
/// Construct persistent compiled metadata for registry contract tests.
17 +
fn package(name: *[u8], dependencies: *unsafe [*[u8]], slot: u32) -> shared::Package {
18 +
    return shared::Package {
19 +
        name, dependencies, slot, codeAddress: 0x80000000 + slot as u64 * 4096,
20 +
        code: &[0x00008067 as u32], exports: &[], entry: nil,
21 +
        template: &[], memory: 0, alignment: 8, relocations: &[],
22 +
    };
23 +
}
24 +
25 +
/// Admit persistent package bytes and metadata through a reserved registry slot.
26 +
unsafe fn register(name: *[u8], dependencies: *unsafe [*[u8]], bytes: *[u8]) -> abi::Ref throws (abi::Error) {
27 +
    let pending = try registry::reserve(&mut STORE);
28 +
    let slot = slots::reference(&pending);
29 +
    return try registry::publish(&mut STORE, pending, bytes, package(name, dependencies, slot.index));
30 +
}
31 +
32 +
/// Identical bytes reuse one package; a name cannot identify different content.
33 +
@test unsafe fn identity() throws (testing::TestError) {
34 +
    registry::initialize(&mut STORE);
35 +
    let first = try! register("a", &[], "binary a");
36 +
    let second = try! register("a", &[], "binary a");
37 +
    try testing::expect(first == second and STORE.slots[1].state == slots::State::Free);
38 +
    let found = try! registry::identify(&STORE, &"a"[..], &"binary a"[..]);
39 +
    try testing::expect(found == first);
40 +
    let mut conflict = false;
41 +
    try register("a", &[], "different") catch err {
42 +
        try testing::expect(err == abi::Error::VerifyFailed); set conflict = true;
43 +
    };
44 +
    try testing::expect(conflict and STORE.slots[1].state == slots::State::Free);
45 +
}
46 +
47 +
/// Dependencies must already be resident and retain shared identity in a diamond.
48 +
@test unsafe fn dependencyGraph() throws (testing::TestError) {
49 +
    registry::initialize(&mut STORE);
50 +
    let mut missing = false;
51 +
    try register("left", &["base"], "left") catch err {
52 +
        try testing::expect(err == abi::Error::VerifyFailed); set missing = true;
53 +
    };
54 +
    try testing::expect(missing and STORE.slots[0].state == slots::State::Free);
55 +
    let base = try! register("base", &[], "base");
56 +
    let left = try! register("left", &["base"], "left");
57 +
    let right = try! register("right", &["base"], "right");
58 +
    let root = try! register("root", &["left", "right"], "root");
59 +
    try testing::expect(STORE.dependencies[left.index][0] == base and STORE.dependencies[right.index][0] == base);
60 +
    try testing::expect(STORE.dependencies[root.index][0] == left and STORE.dependencies[root.index][1] == right);
61 +
}
62 +
63 +
/// Image handles name resident packages and dropping a handle does not unload code.
64 +
@test unsafe fn images() throws (testing::TestError) {
65 +
    registry::initialize(&mut STORE);
66 +
    let object = try! register("image", &[], "image");
67 +
    let mut table: capability::Table = undefined;
68 +
    capability::initialize(&mut table, abi::Ref { index: 0, generation: 1 });
69 +
    let handle = try! registry::install(&STORE, &mut table, object);
70 +
    let found = try! registry::image(&STORE, &table, handle, abi::Rights(abi::EXECUTE));
71 +
    try testing::expect(found == object);
72 +
    let removed = try! capability::invalidate(&mut table, handle);
73 +
    let resident = try! registry::get(&STORE, object);
74 +
    try testing::expect(resident.slot == object.index);
75 +
    let mut stale = false;
76 +
    try registry::image(&STORE, &table, handle, abi::Rights(0)) catch err {
77 +
        try testing::expect(err == abi::Error::BadHandle); set stale = true;
78 +
    };
79 +
    try testing::expect(stale);
80 +
}
81 +
82 +
/// Boot admission uses the same identity and dependency records as runtime publication.
83 +
@test unsafe fn bootCatalog() throws (testing::TestError) {
84 +
    registry::initialize(&mut STORE);
85 +
    let catalog = [
86 +
        catalog::Entry { source: "base", package: package("base", &[], 0) },
87 +
        catalog::Entry { source: "app", package: package("app", &["base"], 1) },
88 +
    ];
89 +
    try! registry::boot(&mut STORE, &catalog[..]);
90 +
    try! registry::boot(&mut STORE, &catalog[..]);
91 +
    try testing::expect(STORE.counts[1] == 1 and STORE.dependencies[1][0].index == 0);
92 +
    try testing::expect(STORE.slots[2].state == slots::State::Free);
93 +
}
94 +
95 +
/// Export lookup and dependency import collection preserve code and private-data targets.
96 +
@test unsafe fn exports() throws (testing::TestError) {
97 +
    registry::initialize(&mut STORE);
98 +
    let pending = try! registry::reserve(&mut STORE);
99 +
    let mut native = package("base", &[], 0);
100 +
    set native.exports = &[
101 +
        shared::Symbol { name: "base::run", target: shared::Target::Function(0x80000000) },
102 +
        shared::Symbol { name: "base::state", target: shared::Target::Data(shared::DataRef { slot: 0, offset: 8 }) },
103 +
    ];
104 +
    let object = try! registry::publish(&mut STORE, pending, "base", native);
105 +
    let target = try! registry::exported(&STORE, object, &"base::run"[..]);
106 +
    try testing::expect(target == shared::Target::Function(0x80000000));
107 +
    let mut output: [shared::Symbol; 2] = undefined;
108 +
    let count = try! registry::imports(&STORE, &["base"], &mut output[..]);
109 +
    try testing::expect(count == 2 and output[1].target == shared::Target::Data(shared::DataRef { slot: 0, offset: 8 }));
110 +
    let saved = output[0];
111 +
    let mut failures: u32 = 0;
112 +
    try registry::imports(&STORE, &["base"], &mut output[..1]) catch err {
113 +
        try testing::expect(err == abi::Error::Exhausted); set failures += 1;
114 +
    };
115 +
    try registry::exported(&STORE, object, &"base::private"[..]) catch err {
116 +
        try testing::expect(err == abi::Error::VerifyFailed); set failures += 1;
117 +
    };
118 +
    try registry::imports(&STORE, &["absent"], &mut output[..]) catch err {
119 +
        try testing::expect(err == abi::Error::VerifyFailed); set failures += 1;
120 +
    };
121 +
    try testing::expect(failures == 3 and output[0] == saved);
122 +
}
123 +
124 +
/// Full registry exhaustion leaves existing packages available for duplicate lookup.
125 +
@test unsafe fn capacity() throws (testing::TestError) {
126 +
    registry::initialize(&mut STORE);
127 +
    for i in 0..256 {
128 +
        set NAMES[i] = [112 as u8, (48 + i / 100) as u8, (48 + (i / 10) % 10) as u8, (48 + i % 10) as u8];
129 +
        let object = try! register(&NAMES[i][..], &[], &NAMES[i][..]);
130 +
        try testing::expect(object.index == i);
131 +
    }
132 +
    let mut full = false;
133 +
    try register("extra", &[], "extra") catch err {
134 +
        try testing::expect(err == abi::Error::Exhausted); set full = true;
135 +
    };
136 +
    try testing::expect(full);
137 +
    let found = try! registry::identify(&STORE, &NAMES[255][..], &NAMES[255][..]) else panic "resident package";
138 +
    try testing::expect(found.index == 255);
139 +
}
kernel/tools/build.rad +121 -82
1 -
//! Build a physical kernel image from trusted binary RIL and startup assembly.
1 +
//! Build a native kernel image with shared packages and a persistent boot catalog.
2 2
3 3
use std::sys;
4 4
use std::io;
5 5
use std::mem;
6 -
use std::lang::il;
7 6
use std::sys::unix;
8 7
use std::lang::alloc;
9 8
use std::lang::strings;
10 9
use std::lang::il::binary;
11 10
use std::lang::il::binary::program;
12 11
use std::lang::gen::data;
13 -
use std::collections::dict;
14 12
use std::arch::rv64;
15 13
use std::arch::rv64::asm;
14 +
use std::arch::rv64::emit;
15 +
use std::arch::rv64::encode;
16 16
use std::arch::rv64::image;
17 +
use std::arch::rv64::shared;
18 +
use std::arch::rv64::shared::catalog;
17 19
18 -
/// Code segment base for the native platform profile.
20 +
/// Native entry page, above the platform firmware data.
19 21
constant CODE_ADDRESS: u64 = 0x81000000;
20 -
21 -
/// Persistent native code-generation workspace.
22 -
static CODE: [u8; 16777216] = [0; 16777216];
22 +
/// Package code arenas retained until image output completes.
23 +
unsafe static CODE: [[u8; 16777216]; 2] = undefined;
23 24
/// Reusable function workspace.
24 25
static SCRATCH: [u8; 16777216] = [0; 16777216];
25 -
/// Decoded package storage.
26 +
/// Persistent decoded RIL storage.
26 27
static DECODE: [u8; 67108864] = [0; 67108864];
27 -
/// Binary package input.
28 -
static INPUT: [u8; 8388608] = [0; 8388608];
29 -
/// Combined package data descriptors.
30 -
unsafe static GLOBALS: [il::Data; 4096] = undefined;
31 -
/// Combined startup and boundary assembly.
28 +
/// Exact package bytes retained in the native catalog.
29 +
unsafe static INPUT: [[u8; 8388608]; 2] = undefined;
30 +
/// Combined startup and boundary assembly source.
32 31
static SOURCE: [u8; 65536] = [0; 65536];
33 32
/// Assembler workspace.
34 33
static ASSEMBLY: [u8; 4194304] = [0; 4194304];
35 34
/// Assembled startup words.
36 35
static TEXT: [u32; 16384] = [0; 16384];
37 -
/// Assembly identifiers.
36 +
/// Assembly identifiers retained through package linking.
38 37
unsafe static STRINGS: strings::Pool = strings::Pool { table: undefined, count: 0 };
39 -
/// Data symbol placement workspace.
40 -
unsafe static SYMBOLS: [data::DataSym; 4096] = undefined;
41 -
/// Data name lookup workspace.
42 -
unsafe static ENTRIES: [dict::Entry; data::DATA_SYM_TABLE_SIZE] = undefined;
43 -
/// Initialized read-only bytes.
44 -
static RO: [u8; 1048576] = [0; 1048576];
45 -
/// Initialized writable bytes.
46 -
static RW: [u8; 1048576] = [0; 1048576];
38 +
/// Per-package data layout workspaces.
39 +
unsafe static DATA: [[data::DataSym; 4096]; 2] = undefined;
40 +
/// Local function and data definitions.
41 +
unsafe static SYMBOLS: [[shared::Symbol; 4096]; 2] = undefined;
42 +
/// Public package symbols.
43 +
unsafe static EXPORTS: [[shared::Symbol; 4096]; 2] = undefined;
44 +
/// Private-state relocation records.
45 +
unsafe static RELOCS: [[shared::Relocation; 4096]; 2] = undefined;
46 +
/// Initialized package templates.
47 +
unsafe static TEMPLATES: [[u8; 1048576]; 2] = undefined;
48 +
/// Read-only catalog and retained package payloads.
49 +
static RO: [u8; 16777216] = [0; 16777216];
50 +
/// Kernel state table and initialized private package graph.
51 +
static RW: [u8; 33554432] = [0; 33554432];
52 +
/// Contiguous native code segment, including entry trampoline and alignment padding.
53 +
static NATIVE: [u32; 4194304] = [0; 4194304];
47 54
48 -
/// Decode one package into persistent storage shared by the native link.
49 -
unsafe fn load(path: &[u8], arena: &mut alloc::Arena) -> binary::Package {
50 -
    let inputLength = unix::readFile(path, &mut INPUT[..]) else panic "boot RIL";
51 -
    return try! program::decode(&INPUT[..inputLength], arena, binary::Limits { registers: 8192, blocks: 4096 });
55 +
/// Round an extent up to a power-of-two alignment.
56 +
fn aligned(value: u64, alignment: u32) -> u64 {
57 +
    assert alignment > 0 and (alignment & (alignment - 1)) == 0;
58 +
    assert value <= 0xffffffffffffffff - alignment as u64 + 1;
59 +
    return (value + alignment as u64 - 1) & ~(alignment as u64 - 1);
52 60
}
53 61
54 -
/// Print the bounded backend failure reported by a native build.
55 -
fn report(error: rv64::Error) {
62 +
/// Print a bounded shared-backend failure class.
63 +
fn report(error: shared::Error) {
56 64
    match error {
57 -
        case rv64::Error::Allocation => io::printLn("native build: allocation exhausted"),
58 -
        case rv64::Error::Capacity => io::printLn("native build: output capacity exhausted"),
59 -
        case rv64::Error::Symbol => io::printLn("native build: unresolved symbol"),
60 -
        case rv64::Error::Relocation => io::printLn("native build: relocation out of range"),
61 -
        case rv64::Error::Image(_) => io::printLn("native build: invalid image layout"),
62 -
        case rv64::Error::Data(_) => io::printLn("native build: invalid data layout"),
65 +
        case shared::Error::Codegen(_) => io::printLn("native build: code generation failed"),
66 +
        case shared::Error::Capacity => io::printLn("native build: output capacity exhausted"),
67 +
        case shared::Error::Symbol => io::printLn("native build: unresolved or duplicate symbol"),
68 +
        case shared::Error::Range => io::printLn("native build: address out of range"),
69 +
        case shared::Error::Alignment => io::printLn("native build: invalid alignment"),
70 +
        case shared::Error::Instance => io::printLn("native build: missing package instance"),
63 71
    }
64 72
}
65 73
66 -
/// Assemble entry code and lower the kernel at explicit physical addresses.
74 +
/// Compile one boot package into resident code and private-state metadata.
75 +
unsafe fn compile(input: &binary::Package, slot: u32, address: u64, imports: &[shared::Symbol], assembly: asm::Program)
76 +
    -> shared::Package throws (shared::Error)
77 +
{
78 +
    let mut arena = alloc::new(&mut CODE[slot][..]);
79 +
    let mut scratch = alloc::new(&mut SCRATCH[..]);
80 +
    return try shared::compileAssembly(shared::AssemblyInput { package: input as *unsafe binary::Package, assembly }, slot, address, imports,
81 +
        shared::Storage {
82 +
            data: &mut DATA[slot][..], symbols: &mut SYMBOLS[slot][..], exports: &mut EXPORTS[slot][..],
83 +
            template: &mut TEMPLATES[slot][..], relocations: &mut RELOCS[slot][..],
84 +
        }, &mut arena, &mut scratch);
85 +
}
86 +
87 +
/// Emit a trampoline that preserves firmware arguments and supplies the native catalog.
88 +
unsafe fn trampoline(kernel: u64, ro: u64, rw: u64) {
89 +
    let mut arena = alloc::new(&mut SCRATCH[..]);
90 +
    let mut e = try! emit::emitter(&mut arena, false);
91 +
    emit::loadImm(&mut e, rv64::GP, rw as i64);
92 +
    emit::loadImm(&mut e, rv64::A3, ro as i64);
93 +
    emit::loadImm(&mut e, rv64::A4, 2);
94 +
    emit::loadImm(&mut e, rv64::T0, kernel as i64);
95 +
    emit::emit(&mut e, encode::jalr(rv64::ZERO, rv64::T0, 0));
96 +
    assert e.codeLen <= 1024;
97 +
    for word, i in emit::getCode(&e) { set NATIVE[i] = word; }
98 +
}
99 +
100 +
/// Link shared boot packages, retain their catalog, and instantiate the kernel graph.
67 101
@default unsafe fn main(env: *sys::Env) -> i32 {
68 102
    assert env.args.len == 5;
69 103
    let mut decoder = alloc::new(&mut DECODE[..]);
70 -
    let library = load(env.args[1], &mut decoder);
71 -
    let kernel = load(env.args[2], &mut decoder);
72 -
    assert mem::eq(library.name, "std") and mem::eq(kernel.name, "kernel");
73 -
    assert library.dependencies.len == 0;
74 -
    for dependency in kernel.dependencies { assert mem::eq(dependency, "std"); }
104 +
    let libraryBytesLength = unix::readFile(env.args[1], &mut INPUT[0][..]) else panic "std RIL";
105 +
    let libraryBytes = &INPUT[0][..libraryBytesLength];
106 +
    let libraryInput = try! program::decode(libraryBytes, &mut decoder, binary::Limits { registers: 8192, blocks: 4096 });
107 +
    let kernelBytesLength = unix::readFile(env.args[2], &mut INPUT[1][..]) else panic "kernel RIL";
108 +
    let kernelBytes = &INPUT[1][..kernelBytesLength];
109 +
    let kernelInput = try! program::decode(kernelBytes, &mut decoder, binary::Limits { registers: 8192, blocks: 4096 });
110 +
    assert mem::eq(libraryInput.name, "std") and mem::eq(kernelInput.name, "kernel");
111 +
    assert libraryInput.dependencies.len == 0;
112 +
    for dependency in kernelInput.dependencies { assert mem::eq(dependency, "std"); }
75 113
    let sourceLength = unix::readFile(env.args[3], &mut SOURCE[..]) else panic "kernel assembly";
76 -
    let mut assembly = alloc::new(&mut ASSEMBLY[..]);
114 +
    let mut assemblyArena = alloc::new(&mut ASSEMBLY[..]);
77 115
    let empty: *mut [u8] = &mut [];
78 -
    let startup = try! asm::assemble(asm::scanner::SourceKind::String, &SOURCE[..sourceLength],
79 -
        &mut TEXT[..], &mut empty[..], &mut assembly, &mut STRINGS, 0);
80 -
    let mut arena = alloc::new(&mut CODE[..]);
81 -
    let mut scratch = alloc::new(&mut SCRATCH[..]);
82 -
    let mut generator = try! rv64::beginProgram(rv64::ProgramOptions {
83 -
        entryPatch: rv64::EntryPatch::None, debug: false,
84 -
        placement: image::Placement::Physical {
85 -
            code: CODE_ADDRESS, roData: 0, rwData: 0, entry: CODE_ADDRESS,
86 -
        },
87 -
    }, &mut arena);
88 -
    rv64::addAssembly(&mut generator, startup);
89 -
    let mut dataCount: u32 = 0;
90 -
    for package in &[library, kernel] {
91 -
        for item in package.program.data {
92 -
            assert dataCount < GLOBALS.len;
93 -
            set GLOBALS[dataCount] = item; set dataCount += 1;
94 -
        }
95 -
        for func in package.program.fns {
96 -
            rv64::generateFunction(&mut generator, func, &mut scratch);
97 -
            if let error = generator.e.error {
98 -
                report(error); io::printLn(func.name); return 1;
99 -
            }
100 -
            alloc::reset(&mut scratch);
101 -
        }
116 +
    let assembly = try! asm::assemble(asm::scanner::SourceKind::String, &SOURCE[..sourceLength],
117 +
        &mut TEXT[..], &mut empty[..], &mut assemblyArena, &mut STRINGS, 0);
118 +
    let library = try compile(&libraryInput, 0, CODE_ADDRESS + 4096, &[],
119 +
        asm::Program { text: &[], data: &[], symbols: &[], externalFixups: &[] }) catch error { report(error); return 1; };
120 +
    let kernelAddress = aligned(library.codeAddress + library.code.len as u64 * 4, 4096);
121 +
    let kernel = try compile(&kernelInput, 1, kernelAddress, library.exports, assembly) catch error { report(error); return 1; };
122 +
    let entries = [
123 +
        catalog::Entry { source: libraryBytes, package: library },
124 +
        catalog::Entry { source: kernelBytes, package: kernel },
125 +
    ];
126 +
    let codeEnd = kernel.codeAddress + kernel.code.len as u64 * 4;
127 +
    let codeSize = (codeEnd - CODE_ADDRESS) as u32;
128 +
    assert codeEnd - CODE_ADDRESS <= @sizeOf([u32; 4194304]) as u64;
129 +
    let roAddress = aligned(codeEnd, 4096);
130 +
    let roSize = try catalog::pack(&entries[..], roAddress, &mut RO[..]) catch error { report(error); return 1; };
131 +
    let rwAddress = aligned(roAddress + roSize as u64, 4096);
132 +
    let mut bases: [u64; shared::MAX_PACKAGES] = [0; shared::MAX_PACKAGES];
133 +
    let mut rwSize: u32 = @sizeOf([u64; shared::MAX_PACKAGES]);
134 +
    for entry in &entries[..] {
135 +
        let at = aligned(rwAddress + rwSize as u64, entry.package.alignment);
136 +
        assert at - rwAddress <= RW.len as u64 and entry.package.memory <= RW.len - (at - rwAddress) as u32;
137 +
        set bases[entry.package.slot] = at;
138 +
        set rwSize = (at - rwAddress) as u32 + entry.package.memory;
139 +
    }
140 +
    try! mem::copy(&mut RW[..@sizeOf([u64; shared::MAX_PACKAGES])],
141 +
        @sliceOf(&bases[0] as *unsafe u8, @sizeOf([u64; shared::MAX_PACKAGES])));
142 +
    for entry in &entries[..] {
143 +
        let at = (bases[entry.package.slot] - rwAddress) as u32;
144 +
        try shared::instantiate(&entry.package, &bases[..], &mut RW[at..at + entry.package.memory])
145 +
            catch error { report(error); return 1; };
102 146
    }
103 -
    for call in &generator.e.pendingCalls[..] {
104 -
        if dict::get(&generator.e.labels.funcs, call.target) == nil {
105 -
            io::print("undefined kernel function: "); io::printLn(call.target); return 1;
106 -
        }
147 +
    for i in 0..codeSize / 4 { set NATIVE[i] = encode::nop(); }
148 +
    for entry in &entries[..] {
149 +
        let at = ((entry.package.codeAddress - CODE_ADDRESS) / 4) as u32;
150 +
        for word, i in entry.package.code { set NATIVE[at + i] = word; }
107 151
    }
108 -
    let roAddress = (CODE_ADDRESS + generator.e.codeLen as u64 * 4 + 4095) & ~4095;
109 -
    let mut symbols: u32 = 0;
110 -
    let roSize = try! data::layoutSection(&GLOBALS[..dataCount], &mut SYMBOLS[..], &mut symbols, roAddress, true);
111 -
    let rwAddress = (roAddress + roSize as u64 + 4095) & ~4095;
112 -
    set generator.placement = image::Placement::Physical {
113 -
        code: CODE_ADDRESS, roData: roAddress, rwData: rwAddress, entry: CODE_ADDRESS,
114 -
    };
115 -
    let output = try rv64::finishProgram(&mut generator, &GLOBALS[..dataCount],
116 -
        rv64::Storage { dataSyms: &mut SYMBOLS[..], dataSymEntries: &mut ENTRIES[..] },
117 -
        &[], &mut RO[..], &mut RW[..]) catch error { report(error); return 1; };
118 -
    let header = try! image::header(output.layout);
152 +
    trampoline(kernelAddress, roAddress, rwAddress);
153 +
    let header = try! image::header(image::Layout {
154 +
        entry: CODE_ADDRESS,
155 +
        code: image::Segment { address: CODE_ADDRESS, initialized: codeSize, memory: codeSize },
156 +
        roData: image::Segment { address: roAddress, initialized: roSize, memory: roSize },
157 +
        rwData: image::Segment { address: rwAddress, initialized: rwSize, memory: rwSize },
158 +
    });
119 159
    let fd = unix::openOpts(env.args[4], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420);
120 160
    assert fd >= 0;
121 -
    let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, @sliceOf(output.code.ptr as *u8, output.code.len * 4))
122 -
        and unix::writeAll(fd, &RO[..output.roDataSize]) and unix::writeAll(fd, &RW[..output.rwDataSize]);
161 +
    let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, @sliceOf(&NATIVE[0] as *u8, codeSize)) and unix::writeAll(fd, &RO[..roSize]) and unix::writeAll(fd, &RW[..rwSize]);
123 162
    let closed = unix::close(fd) == 0;
124 163
    assert written and closed;
125 164
    return 0;
126 165
}
lib/std/arch/rv64.rad +1 -1
10 10
//! * isel: Instruction selection (IL to RV64 instructions)
11 11
//! * printer: Assembly text output
12 12
13 13
export mod image;
14 14
export mod atomics;
15 -
export mod shared;
16 15
export mod encode;
17 16
export mod decode;
18 17
export mod emit;
19 18
export mod isel;
20 19
export mod printer;
21 20
export mod asm;
21 +
export mod shared;
22 22
23 23
@test mod tests;
24 24
@test mod bounds;
25 25
@test mod atomicTests;
26 26
lib/std/arch/rv64/shared.rad +24 -0
1 1
//! Shared package code, private data templates, and qualified symbol linking.
2 2
3 3
@test export mod tests;
4 +
export mod catalog;
4 5
5 6
use std::mem;
6 7
use std::lang::alloc;
7 8
use std::lang::il;
8 9
use std::lang::il::binary;
9 10
use std::lang::gen;
10 11
use std::lang::gen::data;
11 12
use super::emit;
12 13
use super::encode;
13 14
use super::image;
15 +
use super::asm;
14 16
15 17
/// Maximum number of resident package slots in a domain state table.
16 18
export constant MAX_PACKAGES: u32 = 256;
17 19
18 20
/// Package linking or instance storage failure.
254 256
        }
255 257
    }
256 258
    return TemplateSize { bytes: initialized, relocations: count };
257 259
}
258 260
261 +
/// Package definitions and their native assembly boundaries.
262 +
export record AssemblyInput: Copy {
263 +
    /// Trusted binary RIL definitions that remain valid during compilation.
264 +
    package: *unsafe binary::Package,
265 +
    /// Text-only assembly prefix with exported native boundaries.
266 +
    assembly: asm::Program,
267 +
}
268 +
259 269
/// Compile one package into caller-owned code, symbol, and private template storage.
260 270
/// Imports must contain unique exports from the package's admitted dependencies.
261 271
/// Arena storage and binary package names must outlive the returned catalog entry.
262 272
export unsafe fn compile(input: &binary::Package, slot: u32, codeAddress: u64, imports: &[Symbol],
263 273
    storage: Storage, arena: &mut alloc::Arena, scratch: &mut alloc::Arena) -> Package throws (Error)
274 +
{
275 +
    return try compileAssembly(AssemblyInput { package: input as *unsafe binary::Package,
276 +
        assembly: asm::Program { text: &[], data: &[], symbols: &[], externalFixups: &[] } },
277 +
        slot, codeAddress, imports, storage, arena, scratch);
278 +
}
279 +
280 +
/// Compile a package with a text-only assembly prefix and its exported boundaries.
281 +
/// Assembly and RIL definitions share one native code extent and package-state slot.
282 +
export unsafe fn compileAssembly(source: AssemblyInput, slot: u32, codeAddress: u64, imports: &[Symbol],
283 +
    storage: Storage, arena: &mut alloc::Arena, scratch: &mut alloc::Arena) -> Package throws (Error)
264 284
{
265 285
    let case Storage { data: dataStorage, symbols: symbolStorage, exports: exportStorage, template: templateStorage, relocations: relocationStorage } = storage
266 286
        else panic "expected shared linker storage";
287 +
    let input = source.package;
288 +
    let assembly = source.assembly;
289 +
    if assembly.data.len <> 0 { throw Error::Range; }
267 290
    if slot >= MAX_PACKAGES { throw Error::Range; }
268 291
    if (codeAddress & 3) <> 0 { throw Error::Alignment; }
269 292
    for imported, i in imports {
270 293
        if lookup(&imports[..i], imported.name) <> nil { throw Error::Symbol; }
271 294
    }
272 295
    let mut generator = try super::beginProgram(super::ProgramOptions {
273 296
        entryPatch: super::EntryPatch::None, debug: false, placement: image::Placement::Hosted,
274 297
    }, arena) catch err { throw Error::Codegen(err); };
275 298
    set generator.e.sharedData = true;
299 +
    super::addAssembly(&mut generator, assembly);
276 300
    for func in input.program.fns { super::generateFunction(&mut generator, func, scratch); }
277 301
    try emit::check(&generator.e) catch err { throw Error::Codegen(err); };
278 302
    if codeAddress > 0xffffffffffffffff - generator.e.codeLen as u64 * 4 { throw Error::Range; }
279 303
    let mut symbols: u32 = 0;
280 304
    for func in &generator.e.funcs[..] {
lib/std/arch/rv64/shared/catalog.rad added +111 -0
1 +
//! Persistent native boot catalogs for the RV64 record ABI.
2 +
3 +
use std::mem;
4 +
use std::arch::rv64::shared;
5 +
6 +
/// Binary package identity and its resident native descriptor.
7 +
export record Entry: Copy {
8 +
    /// Complete immutable binary RIL bytes.
9 +
    source: *[u8],
10 +
    /// Shared code, exports, and private-state template.
11 +
    package: shared::Package,
12 +
}
13 +
14 +
/// Reserve aligned bytes in the native catalog output.
15 +
fn reserve(output: &mut [u8], used: &mut u32, size: u32, alignment: u32) -> u32 throws (shared::Error) {
16 +
    let padding = (alignment - (*used & (alignment - 1))) & (alignment - 1);
17 +
    if padding > output.len - *used { throw shared::Error::Capacity; }
18 +
    let offset = *used + padding;
19 +
    if size > output.len - offset { throw shared::Error::Capacity; }
20 +
    for i in *used..offset { set output[i] = 0; }
21 +
    set *used = offset + size;
22 +
    return offset;
23 +
}
24 +
25 +
/// View the native bytes of a caller-owned record or array.
26 +
unsafe fn raw(value: &opaque, size: u32) -> *unsafe [u8] { return @sliceOf((value as &u8) as *unsafe u8, size); }
27 +
28 +
/// Obtain a field's byte offset from the compiler's native record layout.
29 +
unsafe fn field(container: &opaque, value: &opaque) -> u32 { return (value as u64 - container as u64) as u32; }
30 +
31 +
/// Write one little-endian integer into a reserved native field.
32 +
fn integer(output: &mut [u8], offset: u32, value: u64, width: u32) {
33 +
    for i in 0..width { set output[offset + i] = (value >> (i as u64 * 8)) as u8; }
34 +
}
35 +
36 +
/// Write the RV64 pointer, length, and capacity of a native immutable slice.
37 +
fn slice(output: &mut [u8], offset: u32, address: u64, length: u32) {
38 +
    integer(output, offset, address, 8);
39 +
    integer(output, offset + 8, length as u64, 4);
40 +
    integer(output, offset + 12, length as u64, 4);
41 +
}
42 +
43 +
/// Copy bytes into the catalog and return their target-relative offset.
44 +
fn bytes(input: *[u8], output: &mut [u8], used: &mut u32) -> u32 throws (shared::Error) {
45 +
    let offset = try reserve(output, used, input.len, 1);
46 +
    try! mem::copy(&mut output[offset..offset + input.len], input);
47 +
    return offset;
48 +
}
49 +
50 +
/// Copy a dependency list and each name into native catalog storage.
51 +
unsafe fn dependencies(input: *unsafe [*[u8]], base: u64, output: &mut [u8], used: &mut u32) -> u32 throws (shared::Error) {
52 +
    if input.len > output.len / @sizeOf(*[u8]) { throw shared::Error::Capacity; }
53 +
    let offset = try reserve(output, used, input.len * @sizeOf(*[u8]), @alignOf(*[u8]));
54 +
    for name, i in input {
55 +
        let at = try bytes(name, output, used);
56 +
        slice(output, offset + i * @sizeOf(*[u8]), base + at as u64, name.len);
57 +
    }
58 +
    return offset;
59 +
}
60 +
61 +
/// Copy public symbols and their names into native catalog storage.
62 +
unsafe fn symbols(input: *[shared::Symbol], base: u64, output: &mut [u8], used: &mut u32) -> u32 throws (shared::Error) {
63 +
    if input.len > output.len / @sizeOf(shared::Symbol) { throw shared::Error::Capacity; }
64 +
    let offset = try reserve(output, used, input.len * @sizeOf(shared::Symbol), @alignOf(shared::Symbol));
65 +
    for symbol, i in input {
66 +
        let at = offset + i * @sizeOf(shared::Symbol);
67 +
        try! mem::copy(&mut output[at..at + @sizeOf(shared::Symbol)], raw(&symbol, @sizeOf(shared::Symbol)));
68 +
        let name = try bytes(symbol.name, output, used);
69 +
        slice(output, at + field(&symbol, &symbol.name), base + name as u64, symbol.name.len);
70 +
    }
71 +
    return offset;
72 +
}
73 +
74 +
/// Copy private-state relocation records into native catalog storage.
75 +
unsafe fn relocations(input: *[shared::Relocation], output: &mut [u8], used: &mut u32) -> u32 throws (shared::Error) {
76 +
    if input.len > output.len / @sizeOf(shared::Relocation) { throw shared::Error::Capacity; }
77 +
    let size = input.len * @sizeOf(shared::Relocation);
78 +
    let offset = try reserve(output, used, size, @alignOf(shared::Relocation));
79 +
    try! mem::copy(&mut output[offset..offset + size], raw(input.ptr, size));
80 +
    return offset;
81 +
}
82 +
83 +
/// Pack native Entry records followed by all referenced metadata and source bytes.
84 +
/// Code is placed separately at each package's codeAddress. The target base must
85 +
/// have Entry alignment. Return the used extent. Failure leaves partial output.
86 +
export unsafe fn pack(entries: &[Entry], base: u64, output: &mut [u8]) -> u32 throws (shared::Error) {
87 +
    assert @sizeOf(*[u8]) == 16;
88 +
    if (base & (@alignOf(Entry) as u64 - 1)) <> 0 { throw shared::Error::Alignment; }
89 +
    if base > 0xffffffffffffffff - output.len as u64 { throw shared::Error::Range; }
90 +
    if entries.len > output.len / @sizeOf(Entry) { throw shared::Error::Capacity; }
91 +
    let mut used: u32 = 0;
92 +
    let start = try reserve(output, &mut used, entries.len * @sizeOf(Entry), @alignOf(Entry));
93 +
    for entry, i in entries {
94 +
        let at = i * @sizeOf(Entry);
95 +
        try! mem::copy(&mut output[at..at + @sizeOf(Entry)], raw(&entry, @sizeOf(Entry)));
96 +
        let source = try bytes(entry.source, output, &mut used);
97 +
        let name = try bytes(entry.package.name, output, &mut used);
98 +
        let required = try dependencies(entry.package.dependencies, base, output, &mut used);
99 +
        let exports = try symbols(entry.package.exports, base, output, &mut used);
100 +
        let template = try bytes(entry.package.template, output, &mut used);
101 +
        let fixups = try relocations(entry.package.relocations, output, &mut used);
102 +
        slice(output, at + field(&entry, &entry.source), base + source as u64, entry.source.len);
103 +
        slice(output, at + field(&entry, &entry.package.name), base + name as u64, entry.package.name.len);
104 +
        slice(output, at + field(&entry, &entry.package.dependencies), base + required as u64, entry.package.dependencies.len);
105 +
        slice(output, at + field(&entry, &entry.package.code), entry.package.codeAddress, entry.package.code.len);
106 +
        slice(output, at + field(&entry, &entry.package.exports), base + exports as u64, entry.package.exports.len);
107 +
        slice(output, at + field(&entry, &entry.package.template), base + template as u64, entry.package.template.len);
108 +
        slice(output, at + field(&entry, &entry.package.relocations), base + fixups as u64, entry.package.relocations.len);
109 +
    }
110 +
    return used;
111 +
}
lib/std/arch/rv64/shared/tests.rad +75 -0
1 1
//! Private template relocation and bounded linking checks.
2 2
3 3
use std::testing;
4 +
use std::mem;
5 +
use std::arch::rv64::shared::catalog;
6 +
use std::arch::rv64::asm;
4 7
use std::lang::alloc;
5 8
use std::lang::il;
6 9
use std::lang::il::binary;
7 10
use std::lang::gen::data;
8 11
18 21
unsafe static DATA: [data::DataSym; 8] = undefined;
19 22
/// Persistent initialized bytes.
20 23
static TEMPLATE: [u8; 64] = [0; 64];
21 24
/// Persistent private data relocations.
22 25
unsafe static RELOCS: [super::Relocation; 8] = undefined;
26 +
/// Aligned storage for a native boot catalog and its retained payloads.
27 +
static CATALOG: [u64; 512] = [0; 512];
28 +
29 +
/// Exported assembly boundaries occupy the same code extent as their package.
30 +
@test unsafe fn assemblyPrefix() throws (testing::TestError) {
31 +
    let input = binary::Package {
32 +
        symbols: &[], name: "p", dependencies: &[],
33 +
        exports: &[binary::Export { name: "p::entry", kind: binary::ExportKind::Function }],
34 +
        entry: "p::entry", program: il::Program { data: &[], fns: &[] },
35 +
    };
36 +
    let assembly = asm::Program {
37 +
        text: &[0x02a00513 as u32, 0x00008067], data: &[],
38 +
        symbols: &[asm::Symbol { name: "p::entry", section: asm::Section::Text, offset: 0, isExported: true }],
39 +
        externalFixups: &[],
40 +
    };
41 +
    let mut arena = alloc::new(&mut ARENA[..]);
42 +
    let mut scratch = alloc::new(&mut SCRATCH[..]);
43 +
    let package = try super::compileAssembly(super::AssemblyInput { package: &input, assembly }, 0, 0x180000000, &[],
44 +
        super::Storage {
45 +
            data: &mut DATA[..], symbols: &mut SYMBOLS[..], exports: &mut EXPORTS[..],
46 +
            template: &mut TEMPLATE[..], relocations: &mut RELOCS[..],
47 +
        }, &mut arena, &mut scratch) catch { throw testing::TestError::Failed; };
48 +
    let entry = package.entry else { throw testing::TestError::Failed; };
49 +
    try testing::expect(entry == 0x180000000 and package.code.len == 2);
50 +
    try testing::expect(package.code[0] == 0x02a00513 and package.exports[0].target == super::Target::Function(entry));
51 +
}
52 +
53 +
/// Packed catalog pointers resolve to retained metadata at the supplied native base.
54 +
@test unsafe fn nativeCatalog() throws (testing::TestError) {
55 +
    let package = try compile(imports(), 64) catch { throw testing::TestError::Failed; };
56 +
    let output = @sliceOf(&mut CATALOG[0] as *mut u8, @sizeOf([u64; 512]));
57 +
    let base = output.ptr as u64;
58 +
    let used = try catalog::pack(&[catalog::Entry { source: "trusted binary", package }], base, output)
59 +
        catch { throw testing::TestError::Failed; };
60 +
    let storage = &CATALOG[0] as *opaque;
61 +
    let entry = storage as *catalog::Entry;
62 +
    try testing::expect(used > @sizeOf(catalog::Entry) and used <= output.len);
63 +
    try testing::expect(mem::eq(entry.source, "trusted binary") and mem::eq(entry.package.name, "p"));
64 +
    try testing::expect(mem::eq(entry.package.dependencies[0], "dep"));
65 +
    try testing::expect(entry.package.code.ptr as u64 == package.codeAddress and entry.package.code.len == package.code.len);
66 +
    try testing::expect(entry.source.ptr as u64 >= base and entry.source.ptr as u64 < base + used as u64);
67 +
    set TEMPLATE[0] = 91;
68 +
    set EXPORTS[0].name = "changed";
69 +
    try testing::expect(entry.package.template[0] == 0 and mem::eq(entry.package.exports[0].name, "p::refs"));
70 +
    let mut state: [u8; 48] = [255; 48];
71 +
    try super::instantiate(&entry.package, &[0, 0x180002000, 0x180008000], &mut state[..])
72 +
        catch { throw testing::TestError::Failed; };
73 +
    try testing::expect(pointer(&state[..], 0) == 0x180004000 and pointer(&state[..], 16) == 0x180001234);
74 +
    let relocated = try catalog::pack(&[catalog::Entry { source: "trusted binary", package }], 0x180000000, output)
75 +
        catch { throw testing::TestError::Failed; };
76 +
    try testing::expect(entry.source.ptr as u64 >= 0x180000000 and entry.source.ptr as u64 < 0x180000000 + relocated as u64);
77 +
}
78 +
79 +
/// Catalog bounds and native alignment failures are recoverable.
80 +
@test unsafe fn nativeCatalogBounds() throws (testing::TestError) {
81 +
    let package = try compile(imports(), 64) catch { throw testing::TestError::Failed; };
82 +
    let entries = [catalog::Entry { source: "binary", package }];
83 +
    let output = @sliceOf(&mut CATALOG[0] as *mut u8, @sizeOf([u64; 512]));
84 +
    let mut failures: u32 = 0;
85 +
    try catalog::pack(&entries[..], 0x80000000, &mut output[..@sizeOf(catalog::Entry)]) catch err {
86 +
        try testing::expect(err == super::Error::Capacity); set failures += 1;
87 +
    };
88 +
    try catalog::pack(&entries[..], 0x80000001, output) catch err {
89 +
        try testing::expect(err == super::Error::Alignment); set failures += 1;
90 +
    };
91 +
    try catalog::pack(&entries[..], 0xfffffffffffffff8, output) catch err {
92 +
        try testing::expect(err == super::Error::Range); set failures += 1;
93 +
    };
94 +
    try testing::expect(failures == 3);
95 +
    let used = try catalog::pack(&entries[..], 0x80000000, output) catch { throw testing::TestError::Failed; };
96 +
    try testing::expect(used > @sizeOf(catalog::Entry));
97 +
}
23 98
24 99
/// Build a package with repeated dependency pointers and a high code pointer.
25 100
unsafe fn input() -> binary::Package {
26 101
    return binary::Package {
27 102
        symbols: &[], name: "p", dependencies: &["dep"],
lib/std/lang/lower.rad +7 -2
300 300
    /// Current module being lowered.
301 301
    currentMod: ?u16,
302 302
    /// Global data items (string literals, constants, static arrays).
303 303
    /// These become the data sections in the final binary.
304 304
    data: *mut [il::Data],
305 +
    /// First data entry owned by the package currently being lowered.
306 +
    packageDataStart: u32,
305 307
    /// Destination for lowered functions.
306 308
    output: FnOutput,
307 309
    /// Map of function symbols to qualified names.
308 310
    fnSyms: *mut [FnSymEntry],
309 311
    /// Global error type tag table. Maps nominal types to unique tags.
760 762
        resolver: res as *unsafe resolver::Resolver,
761 763
        moduleGraph: nil,
762 764
        pkgName,
763 765
        currentMod: nil,
764 766
        data: &mut [],
767 +
        packageDataStart: 0,
765 768
        output: FnOutput::Accumulate(&mut []),
766 769
        fnSyms: &mut [],
767 770
        errTags: &mut [],
768 771
        errTagCounter: 1,
769 772
        options: LowerOptions { debug: false, buildTest: false },
794 797
        resolver: res,
795 798
        moduleGraph: graph as *unsafe module::ModuleGraph,
796 799
        pkgName,
797 800
        currentMod: nil,
798 801
        data: &mut [],
802 +
        packageDataStart: 0,
799 803
        output: FnOutput::Accumulate(&mut []),
800 804
        fnSyms: &mut [],
801 805
        errTags: &mut [],
802 806
        errTagCounter: 1,
803 807
        options,
926 930
/// The graph must outlive later uses of the lowerer.
927 931
export unsafe fn setPackage(self: &mut Lowerer, graph: &module::ModuleGraph, pkgName: *[u8]) {
928 932
    set self.moduleGraph = graph as *unsafe module::ModuleGraph;
929 933
    set self.pkgName = pkgName;
930 934
    set self.currentMod = nil;
935 +
    set self.packageDataStart = self.data.len;
931 936
}
932 937
933 938
/// Create a new function lowerer for a given function type and name.
934 939
unsafe fn fnLowerer(
935 940
    self: &mut Lowerer,
1689 1694
}
1690 1695
1691 1696
/// Find an existing string data entry with matching content.
1692 1697
// TODO: Optimize with hash table or remove?
1693 1698
fn findStringData(self: &Lowerer, s: *[u8]) -> ?*[u8] {
1694 -
    for d in &self.data[..] {
1699 +
    for d in &self.data[self.packageDataStart..] {
1695 1700
        if d.values.len == 1 {
1696 1701
            if let case il::DataItem::Str(existing) = d.values[0].item {
1697 1702
                if mem::eq(existing, s) {
1698 1703
                    return d.name;
1699 1704
                }
1846 1851
}
1847 1852
1848 1853
/// Find an existing read-only slice data entry with matching values.
1849 1854
// TODO: Optimize with hash table or remove?
1850 1855
fn findSliceData(self: &Lowerer, values: *[il::DataValue], alignment: u32) -> ?*[u8] {
1851 -
    for d in &self.data[..] {
1856 +
    for d in &self.data[self.packageDataStart..] {
1852 1857
        if d.alignment == alignment and d.readOnly and dataValuesEq(d.values, values) {
1853 1858
            return d.name;
1854 1859
        }
1855 1860
    }
1856 1861
    return nil;
std.lib +1 -0
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 55
lib/std/arch/rv64/shared.rad
56 +
lib/std/arch/rv64/shared/catalog.rad
56 57
lib/std/arch/rv64/atomics.rad
test/boot/run +14 -0
15 15
    fi
16 16
    if ! grep -q '^kernel: platform ready$' "$work/log"; then
17 17
        cat "$work/log" >&2
18 18
        exit 1
19 19
    fi
20 +
    if ! grep -q '^kernel: boot catalog ready$' "$work/log"; then
21 +
        cat "$work/log" >&2
22 +
        exit 1
23 +
    fi
20 24
    printf 'kernel boot: %s harts passed\n' "$harts"
21 25
done
22 26
status=0
23 27
"$emulator" -machine -harts=8 -max-steps=80000000 -run bin/kernel.rv64 > "$work/log" 2>&1 || status=$?
24 28
if [ "$status" -ne 2 ] || [ "$(grep -c 'wfi=1 mcause=0x0' "$work/log")" -ne 8 ]; then
25 29
    cat "$work/log" >&2
26 30
    exit 1
27 31
fi
28 32
printf 'kernel startup: all eight harts reached machine idle without traps\n'
33 +
cat test/boot/trap.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras > "$work/trap.ras"
34 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/trap.ras" "$work/trap.rv64"
35 +
status=0
36 +
"$emulator" -machine -harts=2 -max-steps=20000000 -run "$work/trap.rv64" > "$work/log" 2>&1 || status=$?
37 +
if [ "$status" -ne 2 ] || [ "$(grep -c '^kernel: unexpected trap$' "$work/log")" -ne 1 ] \
38 +
    || [ "$(grep -c 'wfi=1' "$work/log")" -ne 2 ]; then
39 +
    cat "$work/log" >&2
40 +
    exit 1
41 +
fi
42 +
printf 'kernel trap: shared kernel state restored from the hart anchor\n'
test/boot/trap.ras added +12 -0
1 +
//! Enter a kernel handler after an interrupted context changes gp.
2 +
.text;
3 +
    call @kernel::boot::initialize;
4 +
    beqz %a0 @wait;
5 +
    li %gp 123;
6 +
    ecall;
7 +
    li %t0 0x10001000;
8 +
    li %t1 0x13333;
9 +
    sw %t1 0(%t0);
10 +
@wait
11 +
    wfi;
12 +
    j @wait;
test/packages/app.rad +5 -1
1 1
//! Application package fixture.
2 2
use support;
3 3
/// Return the shared package's state.
4 -
@default fn main() -> u64 { return support::read() + support::value; }
4 +
@default fn main() -> u64 {
5 +
    let label = "package literal";
6 +
    let values = &[11 as u64, 13];
7 +
    return support::read() + support::value + label.len as u64 - 15 + values[0] - 11;
8 +
}
test/packages/check.rad +12 -3
32 32
    assert mem::eq(support.name, "support"), "main: mem::eq(support.name, \"support\")";
33 33
    assert mem::eq(app.name, "app"), "main: mem::eq(app.name, \"app\")";
34 34
    assert support.dependencies.len == 0, "main: support.dependencies.len == 0";
35 35
    assert app.dependencies.len == 1, "main: app.dependencies.len == 1";
36 36
    assert mem::eq(app.dependencies[0], "support"), "main: mem::eq(app.dependencies[0], \"support\")";
37 -
    assert support.program.fns.len == 1, "main: support.program.fns.len == 1";
38 -
    assert support.program.data.len == 1, "main: support.program.data.len == 1";
37 +
    assert support.program.fns.len == 2, "main: support.program.fns.len == 2";
38 +
    assert support.program.data.len == 3, "main: support.program.data.len == 3";
39 39
    assert mem::eq(support.program.fns[0].name, "support::read"), "main: mem::eq(support.program.fns[0].name, \"support::read\")";
40 40
    assert mem::eq(support.program.data[0].name, "support::value"), "main: mem::eq(support.program.data[0].name, \"support::value\")";
41 41
    assert app.program.fns.len == 1, "main: app.program.fns.len == 1";
42 -
    assert app.program.data.len == 0, "main: app.program.data.len == 0";
42 +
    assert app.program.data.len == 2, "main: app.program.data.len == 2";
43 +
    assert not mem::eq(support.program.data[1].name, app.program.data[0].name), "main: package-local literals";
44 +
    assert not mem::eq(support.program.data[2].name, app.program.data[1].name), "main: package-local arrays";
43 45
    assert mem::eq(app.program.fns[0].name, "app::main"), "main: mem::eq(app.program.fns[0].name, \"app::main\")";
44 46
    assert support.exports.len == 2, "main: support.exports.len == 2";
47 +
    let mut fenceFound = false;
48 +
    for block in support.program.fns[0].blocks {
49 +
        for instr in block.instrs {
50 +
            if let case il::Instr::MemoryFence = instr { set fenceFound = true; }
51 +
        }
52 +
    }
53 +
    assert fenceFound, "main: intrinsic instruction";
45 54
    assert app.exports.len == 1, "main: app.exports.len == 1";
46 55
    assert app.exports[0].kind == binary::ExportKind::Function, "main: app.exports[0].kind == binary::ExportKind::Function";
47 56
    let entry = app.entry else panic "main: missing package entry";
48 57
    assert mem::eq(entry, "app::main"), "main: mem::eq(entry, \"app::main\")";
49 58
    let mut callFound = false;
test/packages/support.rad +8 -1
1 1
//! Shared package fixture.
2 2
/// Shared mutable data.
3 3
export static value: u64 = 7;
4 4
/// Read shared mutable data.
5 -
export fn read() -> u64 { return value; }
5 +
export fn read() -> u64 {
6 +
    memoryFence();
7 +
    let label = "package literal";
8 +
    let values = &[11 as u64, 13];
9 +
    return value + label.len as u64 - 15 + values[0] - 11;
10 +
}
11 +
/// Order shared-memory accesses through a compiler instruction.
12 +
@intrinsic export fn memoryFence();