kernel: Register global packages

3d8abc9f3daccb5c40a83734ea3544635438bd6b44275a4d9269379482ae745c
Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent 1db0e99f
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;
52 54
    }
53 55
}
54 56
55 57
/// Initialize one hart from firmware arguments. Return true for the last hart.
56 58
/// Firmware supplies a mapped FDT and a disjoint, reserved stack for each hart.
57 -
export unsafe fn enter(hart: u64, tree: *u8, stackTop: u64) -> bool {
59 +
export unsafe fn enter(hart: u64, tree: *u8, stackTop: u64, entries: *catalog::Entry, entryCount: u32, stateTable: *u64) -> bool {
58 60
    let treeAddress = tree as u64;
59 61
    assert hart < limits::HARTS as u64;
60 62
    if hart == 0 {
61 63
        assert (treeAddress & 7) == 0 and treeAddress <= 0xffffffffffffffff - 65536;
62 64
        let prefix = @sliceOf(tree, 40);
66 68
        try! platform::decode(&blob[..], &mut PLATFORM);
67 69
        let treeRange = range::new(treeAddress, size as u64) else panic "FDT range";
68 70
        assert platform::inRam(&PLATFORM, treeRange);
69 71
        try! platform::protect(&mut PLATFORM, treeRange);
70 72
        try! pages::initialize(&mut pages::STORE, &PLATFORM);
73 +
        assert entryCount == 2;
74 +
        registry::initialize(&mut registry::STORE);
75 +
        let bootCatalog = @sliceOf(entries, entryCount);
76 +
        try! registry::boot(&mut registry::STORE, &bootCatalog[..]);
77 +
        let library = registry::find(&registry::STORE, &"std"[..]) else panic "boot std";
78 +
        let kernel = registry::find(&registry::STORE, &"kernel"[..]) else panic "boot kernel";
79 +
        assert library.index == 0 and kernel.index == 1;
80 +
        let bases = @sliceOf(stateTable, limits::PACKAGES);
81 +
        assert bases[library.index] <> 0 and bases[kernel.index] <> 0;
82 +
        print("kernel: boot catalog ready\n");
71 83
        print("kernel: platform ready\n");
72 84
        sync::storeRelease(&mut READY, 1);
73 85
    } else {
74 86
        while sync::loadAcquire(&READY) == 0 {
75 87
        }
76 88
    }
77 89
    assert (PLATFORM.harts & (1 << hart as u32)) <> 0;
78 90
    assert stackTop == PLATFORM.stacks[hart as u32].end and (stackTop & 15) == 0;
79 91
    let stack = PLATFORM.stacks[hart as u32];
80 92
    set HARTS[hart as u32] = trap::Hart {
81 -
        stackTop: stack.end, stackBottom: stack.start, kernelGp: 0, handler: unexpected,
93 +
        stackTop: stack.end, stackBottom: stack.start, kernelGp: stateTable as u64, handler: unexpected,
82 94
        savedT0: 0, savedT1: 0, savedSp: 0,
83 95
    };
84 96
    trap::install(&mut HARTS[hart as u32]);
85 97
    let mut count: u64 = 0;
86 98
    for id in 0..limits::HARTS {
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 +177 -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 {
36 +
            continue;
37 +
        }
38 +
        if mem::eq(store.entries[i].package.name, &name[..]) {
39 +
            return abi::Ref { index: i, generation: store.slots[i].generation };
40 +
        }
41 +
    }
42 +
    return nil;
43 +
}
44 +
45 +
/// Reuse identical content and reject conflicting content under a resident name.
46 +
export fn identify(store: &Store, name: &[u8], source: &[u8]) -> ?abi::Ref throws (abi::Error) {
47 +
    let object = find(store, name) else {
48 +
        return nil;
49 +
    };
50 +
    if not mem::eq(store.entries[object.index].source, &source[..]) {
51 +
        throw abi::Error::VerifyFailed;
52 +
    }
53 +
    return object;
54 +
}
55 +
56 +
/// Reserve the stable package-state slot before compiling its native code.
57 +
export fn reserve(store: &mut Store) -> slots::Reservation throws (abi::Error) {
58 +
    return try slots::reserve(&mut store.slots[..]);
59 +
}
60 +
61 +
/// Return unpublished capacity after a failed load.
62 +
export fn cancel(store: &mut Store, reservation: slots::Reservation) {
63 +
    try! slots::cancel(&mut store.slots[..], reservation);
64 +
}
65 +
66 +
/// Validate dependencies without changing registry payloads.
67 +
unsafe fn validate(store: &Store, object: abi::Ref, package: &shared::Package) throws (abi::Error) {
68 +
    if package.name.len == 0 or package.slot <> object.index or package.dependencies.len > limits::PACKAGES {
69 +
        throw abi::Error::VerifyFailed;
70 +
    }
71 +
    for dependency, i in package.dependencies {
72 +
        let target = find(store, &dependency[..]) else {
73 +
            throw abi::Error::VerifyFailed;
74 +
        };
75 +
        for prior in &package.dependencies[..i] {
76 +
            if mem::eq(prior, dependency) {
77 +
                throw abi::Error::VerifyFailed;
78 +
            }
79 +
        }
80 +
    }
81 +
}
82 +
83 +
/// Publish persistent compiler output after successful admission checks.
84 +
/// All descriptor pointers and source bytes must remain immutable and resident.
85 +
/// Failure cancels the reservation; duplicate content returns its existing slot.
86 +
export unsafe fn publish(store: &mut Store, reservation: slots::Reservation, source: *[u8], package: shared::Package)
87 +
    -> abi::Ref throws (abi::Error)
88 +
{
89 +
    let existing = try identify(store, &package.name[..], &source[..]) catch err {
90 +
        cancel(store, reservation); throw err;
91 +
    };
92 +
    if let object = existing {
93 +
        cancel(store, reservation);
94 +
        return object;
95 +
    }
96 +
    let object = slots::reference(&reservation);
97 +
    try validate(store, object, &package) catch err {
98 +
        cancel(store, reservation);
99 +
        throw err;
100 +
    };
101 +
    for dependency, i in package.dependencies {
102 +
        let target = find(store, &dependency[..]) else panic "resident dependency";
103 +
        set store.dependencies[object.index][i] = target;
104 +
    }
105 +
    set store.counts[object.index] = package.dependencies.len;
106 +
    set store.entries[object.index] = catalog::Entry { source, package };
107 +
    return try! slots::commit(&mut store.slots[..], reservation);
108 +
}
109 +
110 +
/// Read resident metadata through a generation-bearing package reference.
111 +
export fn get(store: &Store, object: abi::Ref) -> shared::Package throws (abi::Error) {
112 +
    if not slots::matches(&store.slots[..], object, slots::State::Live) {
113 +
        throw abi::Error::BadHandle;
114 +
    }
115 +
    return store.entries[object.index].package;
116 +
}
117 +
118 +
/// Resolve one public symbol without searching packages outside the named image.
119 +
export fn exported(store: &Store, object: abi::Ref, name: &[u8]) -> shared::Target throws (abi::Error) {
120 +
    let package = try get(store, object);
121 +
    let target = shared::lookup(package.exports, &name[..]) else {
122 +
        throw abi::Error::VerifyFailed;
123 +
    };
124 +
    return target;
125 +
}
126 +
127 +
/// Collect exports from declared dependencies into caller-owned compiler storage.
128 +
export fn imports(store: &Store, dependencies: &[*[u8]], output: &mut [shared::Symbol]) -> u32 throws (abi::Error) {
129 +
    let mut count: u32 = 0;
130 +
    for name in dependencies {
131 +
        let object = find(store, &name[..]) else {
132 +
            throw abi::Error::VerifyFailed;
133 +
        };
134 +
        let package = try! get(store, object);
135 +
        if package.exports.len > output.len - count {
136 +
            throw abi::Error::Exhausted;
137 +
        }
138 +
        set count += package.exports.len;
139 +
    }
140 +
    set count = 0;
141 +
    for name in dependencies {
142 +
        let object = find(store, &name[..]) else panic "resident dependency";
143 +
        let package = try! get(store, object);
144 +
        for symbol in package.exports {
145 +
            set output[count] = symbol;
146 +
            set count += 1;
147 +
        }
148 +
    }
149 +
    return count;
150 +
}
151 +
152 +
/// Register a persistent boot catalog in dependency order through normal admission.
153 +
export unsafe fn boot(store: &mut Store, catalog: &[catalog::Entry]) throws (abi::Error) {
154 +
    for entry in catalog {
155 +
        let existing = try identify(store, &entry.package.name[..], &entry.source[..]);
156 +
        if existing <> nil {
157 +
            continue;
158 +
        }
159 +
        let reservation = try reserve(store);
160 +
        let object = try publish(store, reservation, entry.source, entry.package);
161 +
    }
162 +
}
163 +
164 +
/// Install an Image handle for a resident package without duplicating its code.
165 +
export fn install(store: &Store, table: &mut capability::Table, object: abi::Ref) -> abi::Handle throws (abi::Error) {
166 +
    let package = try get(store, object);
167 +
    return try capability::install(table, capability::Entry {
168 +
        kind: abi::Kind::Image, object, rights: abi::Rights(IMAGE_RIGHTS),
169 +
    });
170 +
}
171 +
172 +
/// Resolve image authority and validate its resident package generation.
173 +
export fn image(store: &Store, table: &capability::Table, handle: abi::Handle, rights: abi::Rights) -> abi::Ref throws (abi::Error) {
174 +
    let entry = try capability::lookup(table, handle, abi::Kind::Image, rights);
175 +
    let package = try get(store, entry.object);
176 +
    return entry.object;
177 +
}
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 +138 -84
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) {
98 +
        set NATIVE[i] = word;
99 +
    }
100 +
}
101 +
102 +
/// Link shared boot packages, retain their catalog, and instantiate the kernel graph.
67 103
@default unsafe fn main(env: *sys::Env) -> i32 {
68 104
    assert env.args.len == 5;
69 105
    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 {
106 +
    let libraryBytesLength = unix::readFile(env.args[1], &mut INPUT[0][..]) else panic "std RIL";
107 +
    let libraryBytes = &INPUT[0][..libraryBytesLength];
108 +
    let libraryInput = try! program::decode(libraryBytes, &mut decoder, binary::Limits { registers: 8192, blocks: 4096 });
109 +
    let kernelBytesLength = unix::readFile(env.args[2], &mut INPUT[1][..]) else panic "kernel RIL";
110 +
    let kernelBytes = &INPUT[1][..kernelBytesLength];
111 +
    let kernelInput = try! program::decode(kernelBytes, &mut decoder, binary::Limits { registers: 8192, blocks: 4096 });
112 +
    assert mem::eq(libraryInput.name, "std") and mem::eq(kernelInput.name, "kernel");
113 +
    assert libraryInput.dependencies.len == 0;
114 +
    for dependency in kernelInput.dependencies {
75 115
        assert mem::eq(dependency, "std");
76 116
    }
77 117
    let sourceLength = unix::readFile(env.args[3], &mut SOURCE[..]) else panic "kernel assembly";
78 -
    let mut assembly = alloc::new(&mut ASSEMBLY[..]);
118 +
    let mut assemblyArena = alloc::new(&mut ASSEMBLY[..]);
79 119
    let empty: *mut [u8] = &mut [];
80 -
    let startup = try! asm::assemble(asm::scanner::SourceKind::String, &SOURCE[..sourceLength],
81 -
        &mut TEXT[..], &mut empty[..], &mut assembly, &mut STRINGS, 0);
82 -
    let mut arena = alloc::new(&mut CODE[..]);
83 -
    let mut scratch = alloc::new(&mut SCRATCH[..]);
84 -
    let mut generator = try! rv64::beginProgram(rv64::ProgramOptions {
85 -
        entryPatch: rv64::EntryPatch::None, debug: false,
86 -
        placement: image::Placement::Physical {
87 -
            code: CODE_ADDRESS, roData: 0, rwData: 0, entry: CODE_ADDRESS,
88 -
        },
89 -
    }, &mut arena);
90 -
    rv64::addAssembly(&mut generator, startup);
91 -
    let mut dataCount: u32 = 0;
92 -
    for package in &[library, kernel] {
93 -
        for item in package.program.data {
94 -
            assert dataCount < GLOBALS.len;
95 -
            set GLOBALS[dataCount] = item; set dataCount += 1;
96 -
        }
97 -
        for func in package.program.fns {
98 -
            rv64::generateFunction(&mut generator, func, &mut scratch);
99 -
            if let error = generator.e.error {
100 -
                report(error); io::printLn(func.name); return 1;
101 -
            }
102 -
            alloc::reset(&mut scratch);
103 -
        }
104 -
    }
105 -
    for call in &generator.e.pendingCalls[..] {
106 -
        if dict::get(&generator.e.labels.funcs, call.target) == nil {
107 -
            io::print("undefined kernel function: "); io::printLn(call.target); return 1;
108 -
        }
109 -
    }
110 -
    let roAddress = (CODE_ADDRESS + generator.e.codeLen as u64 * 4 + 4095) & ~4095;
111 -
    let mut symbols: u32 = 0;
112 -
    let roSize = try! data::layoutSection(&GLOBALS[..dataCount], &mut SYMBOLS[..], &mut symbols, roAddress, true);
113 -
    let rwAddress = (roAddress + roSize as u64 + 4095) & ~4095;
114 -
    set generator.placement = image::Placement::Physical {
115 -
        code: CODE_ADDRESS, roData: roAddress, rwData: rwAddress, entry: CODE_ADDRESS,
116 -
    };
117 -
    let output = try rv64::finishProgram(&mut generator, &GLOBALS[..dataCount],
118 -
        rv64::Storage { dataSyms: &mut SYMBOLS[..], dataSymEntries: &mut ENTRIES[..] },
119 -
        &[], &mut RO[..], &mut RW[..]) catch error {
120 +
    let assembly = try! asm::assemble(asm::scanner::SourceKind::String, &SOURCE[..sourceLength],
121 +
        &mut TEXT[..], &mut empty[..], &mut assemblyArena, &mut STRINGS, 0);
122 +
    let library = try compile(&libraryInput, 0, CODE_ADDRESS + 4096, &[],
123 +
        asm::Program { text: &[], data: &[], symbols: &[], externalFixups: &[] }) catch error {
120 124
            report(error);
121 125
            return 1;
122 126
        };
123 -
    let header = try! image::header(output.layout);
127 +
    let kernelAddress = aligned(library.codeAddress + library.code.len as u64 * 4, 4096);
128 +
    let kernel = try compile(&kernelInput, 1, kernelAddress, library.exports, assembly) catch error {
129 +
        report(error);
130 +
        return 1;
131 +
    };
132 +
    let entries = [
133 +
        catalog::Entry { source: libraryBytes, package: library },
134 +
        catalog::Entry { source: kernelBytes, package: kernel },
135 +
    ];
136 +
    let codeEnd = kernel.codeAddress + kernel.code.len as u64 * 4;
137 +
    let codeSize = (codeEnd - CODE_ADDRESS) as u32;
138 +
    assert codeEnd - CODE_ADDRESS <= @sizeOf([u32; 4194304]) as u64;
139 +
    let roAddress = aligned(codeEnd, 4096);
140 +
    let roSize = try catalog::pack(&entries[..], roAddress, &mut RO[..]) catch error {
141 +
        report(error);
142 +
        return 1;
143 +
    };
144 +
    let rwAddress = aligned(roAddress + roSize as u64, 4096);
145 +
    let mut bases: [u64; shared::MAX_PACKAGES] = [0; shared::MAX_PACKAGES];
146 +
    let mut rwSize: u32 = @sizeOf([u64; shared::MAX_PACKAGES]);
147 +
    for entry in &entries[..] {
148 +
        let at = aligned(rwAddress + rwSize as u64, entry.package.alignment);
149 +
        assert at - rwAddress <= RW.len as u64 and entry.package.memory <= RW.len - (at - rwAddress) as u32;
150 +
        set bases[entry.package.slot] = at;
151 +
        set rwSize = (at - rwAddress) as u32 + entry.package.memory;
152 +
    }
153 +
    try! mem::copy(&mut RW[..@sizeOf([u64; shared::MAX_PACKAGES])],
154 +
        @sliceOf(&bases[0] as *unsafe u8, @sizeOf([u64; shared::MAX_PACKAGES])));
155 +
    for entry in &entries[..] {
156 +
        let at = (bases[entry.package.slot] - rwAddress) as u32;
157 +
        try shared::instantiate(&entry.package, &bases[..], &mut RW[at..at + entry.package.memory])
158 +
            catch error {
159 +
                report(error);
160 +
                return 1;
161 +
            };
162 +
    }
163 +
    for i in 0..codeSize / 4 {
164 +
        set NATIVE[i] = encode::nop();
165 +
    }
166 +
    for entry in &entries[..] {
167 +
        let at = ((entry.package.codeAddress - CODE_ADDRESS) / 4) as u32;
168 +
        for word, i in entry.package.code {
169 +
            set NATIVE[at + i] = word;
170 +
        }
171 +
    }
172 +
    trampoline(kernelAddress, roAddress, rwAddress);
173 +
    let header = try! image::header(image::Layout {
174 +
        entry: CODE_ADDRESS,
175 +
        code: image::Segment { address: CODE_ADDRESS, initialized: codeSize, memory: codeSize },
176 +
        roData: image::Segment { address: roAddress, initialized: roSize, memory: roSize },
177 +
        rwData: image::Segment { address: rwAddress, initialized: rwSize, memory: rwSize },
178 +
    });
124 179
    let fd = unix::openOpts(env.args[4], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420);
125 180
    assert fd >= 0;
126 -
    let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, @sliceOf(output.code.ptr as *u8, output.code.len * 4))
127 -
        and unix::writeAll(fd, &RO[..output.roDataSize]) and unix::writeAll(fd, &RW[..output.rwDataSize]);
181 +
    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]);
128 182
    let closed = unix::close(fd) == 0;
129 183
    assert written and closed;
130 184
    return 0;
131 185
}
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 +14 -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 {
51 +
                set fenceFound = true;
52 +
            }
53 +
        }
54 +
    }
55 +
    assert fenceFound, "main: intrinsic instruction";
45 56
    assert app.exports.len == 1, "main: app.exports.len == 1";
46 57
    assert app.exports[0].kind == binary::ExportKind::Function, "main: app.exports[0].kind == binary::ExportKind::Function";
47 58
    let entry = app.entry else panic "main: missing package entry";
48 59
    assert mem::eq(entry, "app::main"), "main: mem::eq(entry, \"app::main\")";
49 60
    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();