compiler/
lib/
examples/
std/
arch/
rv64/
asm/
image/
shared/
catalog.rad
6.1 KiB
tests.rad
10.8 KiB
asm.rad
24.8 KiB
atomicTests.rad
5.0 KiB
atomics.rad
3.6 KiB
bounds.rad
33.4 KiB
decode.rad
15.2 KiB
emit.rad
33.2 KiB
encode.rad
22.0 KiB
image.rad
4.5 KiB
isel.rad
52.6 KiB
printer.rad
13.9 KiB
shared.rad
16.8 KiB
tests.rad
17.2 KiB
rv64.rad
17.5 KiB
char/
collections/
graph/
lang/
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.3 KiB
graph.rad
4.3 KiB
intrinsics.rad
467 B
io.rad
1.7 KiB
lang.rad
276 B
mem.rad
2.3 KiB
sys.rad
179 B
testing.rad
2.4 KiB
tests.rad
15.7 KiB
vec.rad
3.2 KiB
std.rad
299 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CELL_PERMISSIONS
6.8 KiB
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
5.4 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.5 KiB
std.lib.test
808 B
lib/std/arch/rv64/shared/tests.rad
raw
| 1 | //! Private template relocation and bounded linking checks. |
| 2 | |
| 3 | use std::testing; |
| 4 | use std::mem; |
| 5 | use std::arch::rv64::shared::catalog; |
| 6 | use std::arch::rv64::asm; |
| 7 | use std::lang::alloc; |
| 8 | use std::lang::il; |
| 9 | use std::lang::il::binary; |
| 10 | use std::lang::gen::data; |
| 11 | |
| 12 | /// Code generation workspace reused between checks. |
| 13 | static ARENA: [u8; 16777216] = [0; 16777216]; |
| 14 | /// Function allocation workspace. |
| 15 | static SCRATCH: [u8; 16777216] = [0; 16777216]; |
| 16 | /// Persistent catalog symbols. |
| 17 | unsafe static SYMBOLS: [super::Symbol; 8] = undefined; |
| 18 | /// Persistent exported symbols. |
| 19 | unsafe static EXPORTS: [super::Symbol; 8] = undefined; |
| 20 | /// Data layout workspace. |
| 21 | unsafe static DATA: [data::DataSym; 8] = undefined; |
| 22 | /// Persistent initialized bytes. |
| 23 | static TEMPLATE: [u8; 64] = [0; 64]; |
| 24 | /// Persistent private data relocations. |
| 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 { |
| 48 | throw testing::TestError::Failed; |
| 49 | }; |
| 50 | let entry = package.entry else { |
| 51 | throw testing::TestError::Failed; |
| 52 | }; |
| 53 | try testing::expect(entry == 0x180000000 and package.code.len == 2); |
| 54 | try testing::expect(package.code[0] == 0x02a00513 and package.exports[0].target == super::Target::Function(entry)); |
| 55 | } |
| 56 | |
| 57 | /// Packed catalog pointers resolve to retained metadata at the supplied native base. |
| 58 | @test unsafe fn nativeCatalog() throws (testing::TestError) { |
| 59 | let package = try compile(imports(), 64) catch { |
| 60 | throw testing::TestError::Failed; |
| 61 | }; |
| 62 | let output = @sliceOf(&mut CATALOG[0] as *mut u8, @sizeOf([u64; 512])); |
| 63 | let base = output.ptr as u64; |
| 64 | let used = try catalog::pack(&[catalog::Entry { source: "trusted binary", package }], base, output) |
| 65 | catch { |
| 66 | throw testing::TestError::Failed; |
| 67 | }; |
| 68 | let storage = &CATALOG[0] as *opaque; |
| 69 | let entry = storage as *catalog::Entry; |
| 70 | try testing::expect(used > @sizeOf(catalog::Entry) and used <= output.len); |
| 71 | try testing::expect(mem::eq(entry.source, "trusted binary") and mem::eq(entry.package.name, "p")); |
| 72 | try testing::expect(mem::eq(entry.package.dependencies[0], "dep")); |
| 73 | try testing::expect(entry.package.code.ptr as u64 == package.codeAddress and entry.package.code.len == package.code.len); |
| 74 | try testing::expect(entry.source.ptr as u64 >= base and entry.source.ptr as u64 < base + used as u64); |
| 75 | set TEMPLATE[0] = 91; |
| 76 | set EXPORTS[0].name = "changed"; |
| 77 | try testing::expect(entry.package.template[0] == 0 and mem::eq(entry.package.exports[0].name, "p::refs")); |
| 78 | let mut state: [u8; 48] = [255; 48]; |
| 79 | try super::instantiate(&entry.package, &[0, 0x180002000, 0x180008000], &mut state[..]) |
| 80 | catch { |
| 81 | throw testing::TestError::Failed; |
| 82 | }; |
| 83 | try testing::expect(pointer(&state[..], 0) == 0x180004000 and pointer(&state[..], 16) == 0x180001234); |
| 84 | let relocated = try catalog::pack(&[catalog::Entry { source: "trusted binary", package }], 0x180000000, output) |
| 85 | catch { |
| 86 | throw testing::TestError::Failed; |
| 87 | }; |
| 88 | try testing::expect(entry.source.ptr as u64 >= 0x180000000 and entry.source.ptr as u64 < 0x180000000 + relocated as u64); |
| 89 | } |
| 90 | |
| 91 | /// Catalog bounds and native alignment failures are recoverable. |
| 92 | @test unsafe fn nativeCatalogBounds() throws (testing::TestError) { |
| 93 | let package = try compile(imports(), 64) catch { |
| 94 | throw testing::TestError::Failed; |
| 95 | }; |
| 96 | let entries = [catalog::Entry { source: "binary", package }]; |
| 97 | let output = @sliceOf(&mut CATALOG[0] as *mut u8, @sizeOf([u64; 512])); |
| 98 | let mut failures: u32 = 0; |
| 99 | try catalog::pack(&entries[..], 0x80000000, &mut output[..@sizeOf(catalog::Entry)]) catch err { |
| 100 | try testing::expect(err == super::Error::Capacity); set failures += 1; |
| 101 | }; |
| 102 | try catalog::pack(&entries[..], 0x80000001, output) catch err { |
| 103 | try testing::expect(err == super::Error::Alignment); set failures += 1; |
| 104 | }; |
| 105 | try catalog::pack(&entries[..], 0xfffffffffffffff8, output) catch err { |
| 106 | try testing::expect(err == super::Error::Range); set failures += 1; |
| 107 | }; |
| 108 | try testing::expect(failures == 3); |
| 109 | let used = try catalog::pack(&entries[..], 0x80000000, output) catch { |
| 110 | throw testing::TestError::Failed; |
| 111 | }; |
| 112 | try testing::expect(used > @sizeOf(catalog::Entry)); |
| 113 | } |
| 114 | |
| 115 | /// Build a package with repeated dependency pointers and a high code pointer. |
| 116 | unsafe fn input() -> binary::Package { |
| 117 | return binary::Package { |
| 118 | symbols: &[], name: "p", dependencies: &["dep"], |
| 119 | exports: &[binary::Export { name: "p::refs", kind: binary::ExportKind::Data }], |
| 120 | entry: nil, |
| 121 | program: il::Program { |
| 122 | data: &[ |
| 123 | il::Data { |
| 124 | name: "p::refs", size: 32, alignment: 8, readOnly: true, isZeroInit: false, |
| 125 | values: &[ |
| 126 | il::DataValue { item: il::DataItem::Sym("dep::value"), count: 2 }, |
| 127 | il::DataValue { item: il::DataItem::Fn("dep::call"), count: 1 }, |
| 128 | ], |
| 129 | }, |
| 130 | il::Data { |
| 131 | name: "p::zero", size: 16, alignment: 16, readOnly: false, isZeroInit: true, values: &[], |
| 132 | }, |
| 133 | ], |
| 134 | fns: &[], |
| 135 | }, |
| 136 | }; |
| 137 | } |
| 138 | |
| 139 | /// Compile into small persistent tables. |
| 140 | unsafe fn compile(imports: &[super::Symbol], capacity: u32) -> super::Package throws (super::Error) { |
| 141 | let mut arena = alloc::new(&mut ARENA[..]); |
| 142 | let mut scratch = alloc::new(&mut SCRATCH[..]); |
| 143 | let package = input(); |
| 144 | return try super::compile(&package, 2, 0x180000000, imports, |
| 145 | super::Storage { |
| 146 | data: &mut DATA[..], symbols: &mut SYMBOLS[..], exports: &mut EXPORTS[..], |
| 147 | template: &mut TEMPLATE[..capacity], relocations: &mut RELOCS[..], |
| 148 | }, &mut arena, &mut scratch); |
| 149 | } |
| 150 | |
| 151 | /// Dependency exports with a full-width function pointer and a large data offset. |
| 152 | fn imports() -> *[super::Symbol] { |
| 153 | return &[ |
| 154 | super::Symbol { name: "dep::value", target: super::Target::Data(super::DataRef { slot: 1, offset: 8192 }) }, |
| 155 | super::Symbol { name: "dep::call", target: super::Target::Function(0x180001234) }, |
| 156 | ]; |
| 157 | } |
| 158 | |
| 159 | /// Read a little-endian pointer without requiring buffer alignment. |
| 160 | fn pointer(bytes: &[u8], offset: u32) -> u64 { |
| 161 | let mut value: u64 = 0; |
| 162 | for i in 0..8 { |
| 163 | set value |= bytes[offset + i] as u64 << (i as u64 * 8); |
| 164 | } |
| 165 | return value; |
| 166 | } |
| 167 | |
| 168 | /// Check repeated pointers, full-width code addresses, padding, and zero-filled tails. |
| 169 | @test unsafe fn privateRelocations() throws (testing::TestError) { |
| 170 | let package = try compile(imports(), 64) catch { |
| 171 | throw testing::TestError::Failed; |
| 172 | }; |
| 173 | try testing::expect(package.template.len == 32 and package.memory == 48 and package.alignment == 16); |
| 174 | try testing::expect(package.relocations.len == 1 and package.relocations[0].count == 2); |
| 175 | try testing::expect(pointer(package.template, 16) == 0x180001234); |
| 176 | let mut first: [u8; 48] = [255; 48]; |
| 177 | let mut second: [u8; 48] = [255; 48]; |
| 178 | try super::instantiate(&package, &[0, 0x180002000, 0x180008000], &mut first[..]) |
| 179 | catch { |
| 180 | throw testing::TestError::Failed; |
| 181 | }; |
| 182 | try super::instantiate(&package, &[0, 0x280002000, 0x280008000], &mut second[..]) |
| 183 | catch { |
| 184 | throw testing::TestError::Failed; |
| 185 | }; |
| 186 | try testing::expect(pointer(&first[..], 0) == 0x180004000 and pointer(&first[..], 8) == 0x180004000); |
| 187 | try testing::expect(pointer(&second[..], 0) == 0x280004000 and pointer(&second[..], 8) == 0x280004000); |
| 188 | try testing::expect(pointer(&first[..], 16) == pointer(&second[..], 16)); |
| 189 | for i in 24..48 { |
| 190 | try testing::expect(first[i] == 0 and second[i] == 0); |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | /// A missing instance, bad base, or short buffer leaves instance bytes untouched. |
| 195 | @test unsafe fn instanceFailures() throws (testing::TestError) { |
| 196 | let package = try compile(imports(), 64) catch { |
| 197 | throw testing::TestError::Failed; |
| 198 | }; |
| 199 | let mut bytes: [u8; 48] = [255; 48]; |
| 200 | let mut failed: u32 = 0; |
| 201 | try super::instantiate(&package, &[0, 0, 0x8000], &mut bytes[..]) catch err { |
| 202 | try testing::expect(err == super::Error::Instance); set failed += 1; |
| 203 | }; |
| 204 | try super::instantiate(&package, &[0, 0x2000, 0x8008], &mut bytes[..]) catch err { |
| 205 | try testing::expect(err == super::Error::Alignment); set failed += 1; |
| 206 | }; |
| 207 | try super::instantiate(&package, &[0, 0x2000, 0x8000], &mut bytes[..47]) catch err { |
| 208 | try testing::expect(err == super::Error::Capacity); set failed += 1; |
| 209 | }; |
| 210 | try super::instantiate(&package, &[0, 0xffffffffffffffff, 0x8000], &mut bytes[..]) catch err { |
| 211 | try testing::expect(err == super::Error::Range); set failed += 1; |
| 212 | }; |
| 213 | try testing::expect(failed == 4); |
| 214 | for byte in &bytes[..] { |
| 215 | try testing::expect(byte == 255); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | /// Missing symbols, duplicate imports, kind mismatches, and short output fail explicitly. |
| 220 | @test unsafe fn linkFailures() throws (testing::TestError) { |
| 221 | let mut failed: u32 = 0; |
| 222 | try compile(&[], 64) catch err { |
| 223 | try testing::expect(err == super::Error::Symbol); set failed += 1; |
| 224 | }; |
| 225 | let names = imports(); |
| 226 | try compile(&[names[0], names[0]], 64) catch err { |
| 227 | try testing::expect(err == super::Error::Symbol); set failed += 1; |
| 228 | }; |
| 229 | try compile(&[ |
| 230 | super::Symbol { name: "dep::value", target: super::Target::Function(0x8000) }, names[1], |
| 231 | ], 64) catch err { |
| 232 | try testing::expect(err == super::Error::Symbol); set failed += 1; |
| 233 | }; |
| 234 | try compile(names, 31) catch err { |
| 235 | try testing::expect(err == super::Error::Capacity); set failed += 1; |
| 236 | }; |
| 237 | try testing::expect(failed == 4); |
| 238 | let package = try compile(names, 64) catch { |
| 239 | throw testing::TestError::Failed; |
| 240 | }; |
| 241 | try testing::expect(package.exports.len == 1); |
| 242 | } |