rv64: Support explicit native image placement
b83ecdff6f09f82e6f3c4f4d64d59074655215404202bbc5e48bb0f94eea57b5
Assisted-by: Codex:gpt-6
1 parent
b06f8930
Makefile
+12 -2
| 21 | 21 | ||
| 22 | 22 | # Verify the emulator binary exists. |
|
| 23 | 23 | EMU_PATH := $(shell command -v $(EMU) 2>/dev/null) |
|
| 24 | 24 | ||
| 25 | 25 | default: emulator $(RAD_BIN) |
|
| 26 | - | test: emulator seed-test std-test bin-test kernel-test package-test |
|
| 26 | + | test: emulator seed-test std-test bin-test kernel-test package-test native-test |
|
| 27 | 27 | ||
| 28 | 28 | seed-test: |
|
| 29 | 29 | @seed/test |
|
| 30 | 30 | ||
| 31 | 31 | # Emulator command check |
| 84 | 84 | @sh test/packages/run |
|
| 85 | 85 | ||
| 86 | 86 | $(BIN_DIR)/packages.check.rv64: test/packages/check.rad $(STD_LIB) $(RAD_BIN) |
|
| 87 | 87 | @$(RADIANCE) $(STD) -pkg check -mod $< -entry check -o $@ |
|
| 88 | 88 | ||
| 89 | + | # Native image execution through the configured machine-capable emulator. |
|
| 90 | + | native-test: emulator $(BIN_DIR)/native.fixture.rv64 |
|
| 91 | + | @$(EMU) -machine -run $(BIN_DIR)/native.fixture.rv64 |
|
| 92 | + | ||
| 93 | + | $(BIN_DIR)/native.build.rv64: test/native/build.rad $(STD_LIB) $(RAD_BIN) |
|
| 94 | + | @$(RADIANCE) $(STD) -pkg build -mod $< -entry build -o $@ |
|
| 95 | + | ||
| 96 | + | $(BIN_DIR)/native.fixture.rv64: $(BIN_DIR)/native.build.rv64 |
|
| 97 | + | @$(EMU) -run $< -- $@ |
|
| 98 | + | ||
| 89 | 99 | # Binary Tests |
|
| 90 | 100 | ||
| 91 | 101 | BIN_TEST_DIR := test/tests |
|
| 92 | 102 | # Only tests with `//! returns:` are compiled to binaries and executed. |
|
| 93 | 103 | BIN_TEST_EXE_SRC := $(shell grep -rl '^//! returns:' $(BIN_TEST_DIR)) |
| 141 | 151 | clean: clean-std-test clean-bin-test clean-rad |
|
| 142 | 152 | ||
| 143 | 153 | t: test |
|
| 144 | 154 | c: clean |
|
| 145 | 155 | ||
| 146 | - | .PHONY: test clean default seed-test std-test bin-test kernel-test package-test seed \ |
|
| 156 | + | .PHONY: test clean default seed-test std-test bin-test kernel-test package-test native-test seed \ |
|
| 147 | 157 | clean-std-test clean-bin-test clean-rad emulator |
|
| 148 | 158 | .SUFFIXES: |
|
| 149 | 159 | .DELETE_ON_ERROR: |
|
| 150 | 160 | .SILENT: |
compiler/radiance.rad
+4 -2
| 1014 | 1014 | set entryPatch = rv64::EntryPatch::Reserved(nil); |
|
| 1015 | 1015 | } |
|
| 1016 | 1016 | else => {} |
|
| 1017 | 1017 | } |
|
| 1018 | 1018 | let mut generator = rv64::beginProgram( |
|
| 1019 | - | rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug }, |
|
| 1019 | + | rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug, placement: rv64::image::Placement::Hosted }, |
|
| 1020 | 1020 | &mut res.arena |
|
| 1021 | 1021 | ); |
|
| 1022 | 1022 | let mut codegenCtx = codegen::Context { |
|
| 1023 | 1023 | generator: &mut generator, |
|
| 1024 | 1024 | fnArena: (&mut *fnArena) as *unsafe mut alloc::Arena, |
| 1046 | 1046 | else => {} |
|
| 1047 | 1047 | } |
|
| 1048 | 1048 | if let path = codegenOptions.logPath { |
|
| 1049 | 1049 | pkgLog(entryPkg, &["generating code", "(", path, ")", ".."]); |
|
| 1050 | 1050 | } |
|
| 1051 | - | return rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]); |
|
| 1051 | + | return try rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]) catch { |
|
| 1052 | + | throw error(&["invalid native image layout"]); |
|
| 1053 | + | }; |
|
| 1052 | 1054 | } |
|
| 1053 | 1055 | ||
| 1054 | 1056 | /// Source exports selected for one binary RIL package. |
|
| 1055 | 1057 | record PackageExports: Copy { |
|
| 1056 | 1058 | /// Number of initialized entries in the caller's export table. |
lib/std/arch/rv64.rad
+47 -7
| 8 | 8 | //! * decode: Instruction decoding (for disassembly/printing) |
|
| 9 | 9 | //! * emit: Binary emission context and branch patching |
|
| 10 | 10 | //! * isel: Instruction selection (IL to RV64 instructions) |
|
| 11 | 11 | //! * printer: Assembly text output |
|
| 12 | 12 | ||
| 13 | + | export mod image; |
|
| 13 | 14 | export mod encode; |
|
| 14 | 15 | export mod decode; |
|
| 15 | 16 | export mod emit; |
|
| 16 | 17 | export mod isel; |
|
| 17 | 18 | export mod printer; |
| 178 | 179 | roDataSize: u32, |
|
| 179 | 180 | /// Number of read-write data bytes emitted. |
|
| 180 | 181 | rwDataSize: u32, |
|
| 181 | 182 | /// Debug entries mapping PCs to source locations. Empty when debug is off. |
|
| 182 | 183 | debugEntries: *[types::DebugEntry], |
|
| 184 | + | /// Physical segment addresses and initialized and total memory sizes. |
|
| 185 | + | layout: image::Layout, |
|
| 183 | 186 | } |
|
| 184 | 187 | ||
| 185 | 188 | /// Entry jump patching requested for the generated program. |
|
| 186 | 189 | export union EntryPatch: Copy { |
|
| 187 | 190 | /// No entry jump is emitted. |
| 194 | 197 | export record ProgramOptions: Copy { |
|
| 195 | 198 | /// Entry jump patching mode. |
|
| 196 | 199 | entryPatch: EntryPatch, |
|
| 197 | 200 | /// Whether to emit debug source locations. |
|
| 198 | 201 | debug: bool, |
|
| 202 | + | /// Address policy for code and data. |
|
| 203 | + | placement: image::Placement, |
|
| 199 | 204 | } |
|
| 200 | 205 | ||
| 201 | 206 | /// State for incremental RV64 program generation. |
|
| 202 | 207 | /// |
|
| 203 | 208 | /// The generator owns global codegen state that must survive across function |
| 206 | 211 | export record Generator { |
|
| 207 | 212 | /// Binary emitter and relocation state. |
|
| 208 | 213 | e: emit::Emitter, |
|
| 209 | 214 | /// Entry jump patching state. |
|
| 210 | 215 | entryPatch: EntryPatch, |
|
| 216 | + | /// Address policy for code and data. |
|
| 217 | + | placement: image::Placement, |
|
| 211 | 218 | } |
|
| 212 | 219 | ||
| 213 | 220 | /// Begin RV64 code generation for a program's global state. |
|
| 214 | 221 | export unsafe fn beginProgram( |
|
| 215 | 222 | options: ProgramOptions, |
| 228 | 235 | } |
|
| 229 | 236 | ||
| 230 | 237 | return Generator { |
|
| 231 | 238 | e, |
|
| 232 | 239 | entryPatch: options.entryPatch, |
|
| 240 | + | placement: options.placement, |
|
| 233 | 241 | }; |
|
| 234 | 242 | } |
|
| 235 | 243 | ||
| 236 | 244 | /// Generate code for one IL function. |
|
| 237 | 245 | export unsafe fn generateFunction( |
| 304 | 312 | globalData: &[il::Data], |
|
| 305 | 313 | storage: Storage, |
|
| 306 | 314 | roDataPrefix: *[u8], |
|
| 307 | 315 | roDataBuf: &mut [u8], |
|
| 308 | 316 | rwDataBuf: &mut [u8] |
|
| 309 | - | ) -> Program { |
|
| 317 | + | ) -> Program throws (image::Error) { |
|
| 318 | + | let mut roBase: u64 = RO_DATA_BASE as u64; |
|
| 319 | + | let mut rwBase: u64 = RW_DATA_BASE as u64; |
|
| 320 | + | match generator.placement { |
|
| 321 | + | case image::Placement::Physical { roData, rwData, .. } => { |
|
| 322 | + | set roBase = roData; |
|
| 323 | + | set rwBase = rwData; |
|
| 324 | + | }, |
|
| 325 | + | else => {}, |
|
| 326 | + | } |
|
| 310 | 327 | // Build data map after function lowering. Function-local literals can add |
|
| 311 | 328 | // global data while functions are lowered, so final layout belongs here. |
|
| 312 | 329 | let case Storage { dataSyms: symbolBuf, dataSymEntries } = storage |
|
| 313 | 330 | else panic "expected code generation storage"; |
|
| 314 | 331 | let mut dataSymCount: u32 = 0; |
|
| 315 | - | let roLayoutSize = data::layoutSectionAtOffset( |
|
| 316 | - | globalData, symbolBuf, &mut dataSymCount, RO_DATA_BASE, roDataPrefix.len, true |
|
| 317 | - | ); |
|
| 318 | - | data::layoutSection(globalData, symbolBuf, &mut dataSymCount, RW_DATA_BASE, false); |
|
| 332 | + | let roLayoutSize = try data::layoutSectionAtOffset( |
|
| 333 | + | globalData, symbolBuf, &mut dataSymCount, roBase, roDataPrefix.len, true |
|
| 334 | + | ) catch { throw image::Error::Size; }; |
|
| 335 | + | let rwLayoutSize = try data::layoutSection(globalData, symbolBuf, &mut dataSymCount, rwBase, false) catch { throw image::Error::Size; }; |
|
| 319 | 336 | ||
| 320 | 337 | let dataSyms = &symbolBuf[..dataSymCount]; |
|
| 321 | 338 | let dataSymMap = data::buildMap(dataSyms, dataSymEntries); |
|
| 322 | - | let codeBase = mem::alignUp(RO_DATA_BASE + roLayoutSize, DWORD_SIZE as u32); |
|
| 339 | + | if roBase > 0xffffffffffffffff - roLayoutSize as u64 - 7 { |
|
| 340 | + | throw image::Error::Overflow; |
|
| 341 | + | } |
|
| 342 | + | let mut codeBase: u64 = (roBase + roLayoutSize as u64 + 7) & ~7; |
|
| 343 | + | let mut entry = codeBase; |
|
| 344 | + | match generator.placement { |
|
| 345 | + | case image::Placement::Physical { code, entry: address, .. } => { |
|
| 346 | + | set codeBase = code; |
|
| 347 | + | set entry = address; |
|
| 348 | + | }, |
|
| 349 | + | else => {}, |
|
| 350 | + | } |
|
| 351 | + | let codeBytes = generator.e.codeLen * 4; |
|
| 352 | + | let mut layout = image::Layout { |
|
| 353 | + | entry, |
|
| 354 | + | code: image::Segment { address: codeBase, initialized: codeBytes, memory: codeBytes }, |
|
| 355 | + | roData: image::Segment { address: roBase, initialized: 0, memory: roLayoutSize }, |
|
| 356 | + | rwData: image::Segment { address: rwBase, initialized: 0, memory: rwLayoutSize }, |
|
| 357 | + | }; |
|
| 358 | + | try image::validate(layout); |
|
| 323 | 359 | ||
| 324 | 360 | match generator.entryPatch { |
|
| 325 | 361 | case EntryPatch::Reserved(targetName) => { |
|
| 326 | 362 | let target = targetName else { |
|
| 327 | 363 | panic "finishProgram: entry jump reserved without default function"; |
| 335 | 371 | else => {} |
|
| 336 | 372 | } |
|
| 337 | 373 | // Patch function calls and address loads now that all functions are emitted. |
|
| 338 | 374 | emit::patchJumps(&mut generator.e); |
|
| 339 | 375 | emit::patchCalls(&mut generator.e); |
|
| 340 | - | emit::patchAddrLoads(&mut generator.e, &dataSymMap); |
|
| 376 | + | try emit::patchAddrLoads(&mut generator.e, &dataSymMap, codeBase); |
|
| 341 | 377 | ||
| 342 | 378 | // Emit data sections. |
|
| 343 | 379 | assert roDataPrefix.len <= roDataBuf.len, "finishProgram: rodata prefix buffer overflow"; |
|
| 344 | 380 | try! mem::copy(roDataBuf, roDataPrefix); |
|
| 345 | 381 |
| 347 | 383 | globalData, &dataSymMap, &generator.e.labels, codeBase, roDataBuf, true, roDataPrefix.len |
|
| 348 | 384 | ); |
|
| 349 | 385 | let rwDataSize = data::emitSection( |
|
| 350 | 386 | globalData, &dataSymMap, &generator.e.labels, codeBase, rwDataBuf, false |
|
| 351 | 387 | ); |
|
| 388 | + | set layout.roData.initialized = roDataSize; |
|
| 389 | + | set layout.rwData.initialized = rwDataSize; |
|
| 390 | + | try image::validate(layout); |
|
| 352 | 391 | return Program { |
|
| 353 | 392 | code: emit::getCode(&generator.e), |
|
| 354 | 393 | funcs: &generator.e.funcs[..], |
|
| 355 | 394 | roDataSize, |
|
| 356 | 395 | rwDataSize, |
|
| 357 | 396 | debugEntries: emit::getDebugEntries(&generator.e), |
|
| 397 | + | layout, |
|
| 358 | 398 | }; |
|
| 359 | 399 | } |
lib/std/arch/rv64/emit.rad
+10 -10
| 15 | 15 | ||
| 16 | 16 | /// Maximum number of instructions in code buffer. |
|
| 17 | 17 | constant MAX_INSTRS: u32 = 2097152; |
|
| 18 | 18 | /// Maximum code length before byte offset overflows signed 32-bits. |
|
| 19 | 19 | constant MAX_CODE_LEN: u32 = 0x7FFFFFFF / super::INSTR_SIZE as u32; |
|
| 20 | - | /// Maximum positive value encodable by a signed 32-bit address calculation. |
|
| 21 | - | constant MAX_I32_ADDR: u32 = 0x7FFFFFFF; |
|
| 22 | 20 | /// Maximum number of pending branches awaiting patching. |
|
| 23 | 21 | constant MAX_PENDING: u32 = 65536; |
|
| 24 | 22 | /// Maximum number of function entries. |
|
| 25 | 23 | constant MAX_FUNCS: u32 = 4096; |
|
| 26 | 24 | /// Maximum number of debug entries. |
| 329 | 327 | isData: false, |
|
| 330 | 328 | }, e.allocator); |
|
| 331 | 329 | } |
|
| 332 | 330 | ||
| 333 | 331 | /// Record a data address load needing later patching. |
|
| 334 | - | /// Uses an absolute 32-bit load sequence matching the current data memory map. |
|
| 332 | + | /// Reserves two instructions for a PC-relative address load. |
|
| 335 | 333 | export fn recordDataAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) { |
|
| 336 | 334 | e.pendingAddrLoads.append(PendingAddrLoad { |
|
| 337 | 335 | index: e.codeLen, |
|
| 338 | 336 | target, |
|
| 339 | 337 | rd: rd, |
|
| 340 | 338 | isData: true, |
|
| 341 | 339 | }, e.allocator); |
|
| 342 | 340 | ||
| 343 | - | emit(e, encode::nop()); // Placeholder for LUI. |
|
| 344 | - | emit(e, encode::nop()); // Placeholder for ADDIW. |
|
| 341 | + | emit(e, encode::nop()); // Address-load upper instruction. |
|
| 342 | + | emit(e, encode::nop()); // Address-load lower instruction. |
|
| 345 | 343 | } |
|
| 346 | 344 | ||
| 347 | 345 | /// Patch local branches and clear the pending list. |
|
| 348 | 346 | /// |
|
| 349 | 347 | /// Called after each function. |
| 431 | 429 | } |
|
| 432 | 430 | } |
|
| 433 | 431 | ||
| 434 | 432 | /// Patch all pending function and data address loads. |
|
| 435 | 433 | /// Called after all functions have been generated and data layout is known. |
|
| 436 | - | export fn patchAddrLoads(e: &mut Emitter, dataSymMap: &data::DataSymMap) { |
|
| 434 | + | export fn patchAddrLoads(e: &mut Emitter, dataSymMap: &data::DataSymMap, codeBase: u64) throws (super::image::Error) { |
|
| 437 | 435 | for i in 0..e.pendingAddrLoads.len { |
|
| 438 | 436 | let p = e.pendingAddrLoads[i]; |
|
| 439 | 437 | if p.isData { |
|
| 440 | 438 | let addr = data::lookupAddr(dataSymMap, p.target) else { |
|
| 441 | 439 | panic "patchAddrLoads: data symbol not found"; |
|
| 442 | 440 | }; |
|
| 443 | - | assert addr <= MAX_I32_ADDR, "patchAddrLoads: data address too large"; |
|
| 444 | - | let s = splitImm(addr as i32); |
|
| 441 | + | let offset = super::image::displacement(codeBase + p.index as u64 * 4, addr) else { |
|
| 442 | + | throw super::image::Error::Relocation; |
|
| 443 | + | }; |
|
| 444 | + | let s = splitImm(offset); |
|
| 445 | 445 | ||
| 446 | - | patch(e, p.index, encode::lui(p.rd, s.hi)); |
|
| 447 | - | patch(e, p.index + 1, encode::addiw(p.rd, p.rd, s.lo)); |
|
| 446 | + | patch(e, p.index, encode::auipc(p.rd, s.hi)); |
|
| 447 | + | patch(e, p.index + 1, encode::addi(p.rd, p.rd, s.lo)); |
|
| 448 | 448 | ||
| 449 | 449 | continue; |
|
| 450 | 450 | } |
|
| 451 | 451 | ||
| 452 | 452 | let offset = branchOffsetToFunc(e, p.index, p.target); |
lib/std/arch/rv64/image.rad
added
+136 -0
| 1 | + | //! Native image segment placement and versioned wire headers. |
|
| 2 | + | ||
| 3 | + | @test export mod tests; |
|
| 4 | + | ||
| 5 | + | /// Native image signature, encoded as RAD0 in little-endian order. |
|
| 6 | + | export constant MAGIC: u32 = 0x30444152; |
|
| 7 | + | /// Native image format with explicit segment addresses and memory extents. |
|
| 8 | + | export constant VERSION: u32 = 2; |
|
| 9 | + | /// Fixed header size: signature, version, entry, and three segment records. |
|
| 10 | + | export constant HEADER_SIZE: u32 = 64; |
|
| 11 | + | ||
| 12 | + | /// Invalid native image placement. |
|
| 13 | + | export union Error: Copy { |
|
| 14 | + | /// A size or end address cannot be represented. |
|
| 15 | + | Overflow, |
|
| 16 | + | /// A segment or entry violates its alignment requirement. |
|
| 17 | + | Alignment, |
|
| 18 | + | /// Segment memory intervals overlap. |
|
| 19 | + | Overlap, |
|
| 20 | + | /// An initialized extent exceeds its memory extent. |
|
| 21 | + | Size, |
|
| 22 | + | /// The entry is outside initialized code. |
|
| 23 | + | Entry, |
|
| 24 | + | /// A PC-relative address load exceeds its instruction range. |
|
| 25 | + | Relocation, |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// File and memory extents for one image segment. |
|
| 29 | + | export record Segment: Copy { |
|
| 30 | + | /// Physical start address. |
|
| 31 | + | address: u64, |
|
| 32 | + | /// Number of bytes stored in the image file. |
|
| 33 | + | initialized: u32, |
|
| 34 | + | /// Total memory size, including the zero-filled tail. |
|
| 35 | + | memory: u32, |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | /// Explicit native image layout. File payload order is code, rodata, rwdata. |
|
| 39 | + | export record Layout: Copy { |
|
| 40 | + | /// Physical entry address. |
|
| 41 | + | entry: u64, |
|
| 42 | + | /// Executable instructions. |
|
| 43 | + | code: Segment, |
|
| 44 | + | /// Read-only data. |
|
| 45 | + | roData: Segment, |
|
| 46 | + | /// Writable data. |
|
| 47 | + | rwData: Segment, |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// Check one segment's extent and required base alignment. |
|
| 51 | + | fn segment(item: Segment, alignment: u64) throws (Error) { |
|
| 52 | + | if item.initialized > item.memory { |
|
| 53 | + | throw Error::Size; |
|
| 54 | + | } |
|
| 55 | + | if item.address > 0xffffffffffffffff - item.memory as u64 { |
|
| 56 | + | throw Error::Overflow; |
|
| 57 | + | } |
|
| 58 | + | if item.memory > 0 and (item.address & (alignment - 1)) <> 0 { |
|
| 59 | + | throw Error::Alignment; |
|
| 60 | + | } |
|
| 61 | + | } |
|
| 62 | + | ||
| 63 | + | /// Check that two nonempty memory intervals are disjoint. |
|
| 64 | + | fn disjoint(a: Segment, b: Segment) throws (Error) { |
|
| 65 | + | if a.memory > 0 and b.memory > 0 and |
|
| 66 | + | a.address < b.address + b.memory as u64 and b.address < a.address + a.memory as u64 |
|
| 67 | + | { |
|
| 68 | + | throw Error::Overlap; |
|
| 69 | + | } |
|
| 70 | + | } |
|
| 71 | + | ||
| 72 | + | /// Validate segment extents, alignment, overlap, and the executable entry. |
|
| 73 | + | export fn validate(layout: Layout) throws (Error) { |
|
| 74 | + | try segment(layout.code, 4); |
|
| 75 | + | try segment(layout.roData, 8); |
|
| 76 | + | try segment(layout.rwData, 8); |
|
| 77 | + | if (layout.code.initialized & 3) <> 0 or (layout.entry & 3) <> 0 { |
|
| 78 | + | throw Error::Alignment; |
|
| 79 | + | } |
|
| 80 | + | if layout.entry < layout.code.address or |
|
| 81 | + | layout.entry - layout.code.address >= layout.code.initialized as u64 |
|
| 82 | + | { |
|
| 83 | + | throw Error::Entry; |
|
| 84 | + | } |
|
| 85 | + | try disjoint(layout.code, layout.roData); |
|
| 86 | + | try disjoint(layout.code, layout.rwData); |
|
| 87 | + | try disjoint(layout.roData, layout.rwData); |
|
| 88 | + | } |
|
| 89 | + | ||
| 90 | + | /// Encode a validated native header in little-endian order. |
|
| 91 | + | export fn header(layout: Layout) -> [u8; 64] throws (Error) { |
|
| 92 | + | try validate(layout); |
|
| 93 | + | let words: [u64; 8] = [ |
|
| 94 | + | MAGIC as u64 | (VERSION as u64 << 32), layout.entry, |
|
| 95 | + | layout.code.address, layout.code.initialized as u64 | (layout.code.memory as u64 << 32), |
|
| 96 | + | layout.roData.address, layout.roData.initialized as u64 | (layout.roData.memory as u64 << 32), |
|
| 97 | + | layout.rwData.address, layout.rwData.initialized as u64 | (layout.rwData.memory as u64 << 32), |
|
| 98 | + | ]; |
|
| 99 | + | let mut bytes: [u8; HEADER_SIZE] = [0; HEADER_SIZE]; |
|
| 100 | + | for word, i in &words[..] { |
|
| 101 | + | for j in 0..8 { |
|
| 102 | + | set bytes[i * 8 + j] = (word >> (j as u64 * 8)) as u8; |
|
| 103 | + | } |
|
| 104 | + | } |
|
| 105 | + | return bytes; |
|
| 106 | + | } |
|
| 107 | + | ||
| 108 | + | /// Address policy for generated code and data. |
|
| 109 | + | export union Placement: Copy { |
|
| 110 | + | /// Hosted layout with code after read-only data. |
|
| 111 | + | Hosted, |
|
| 112 | + | /// Fixed physical segment addresses and entry. |
|
| 113 | + | Physical { |
|
| 114 | + | /// Executable segment base. |
|
| 115 | + | code: u64, |
|
| 116 | + | /// Read-only segment base. |
|
| 117 | + | roData: u64, |
|
| 118 | + | /// Writable segment base. |
|
| 119 | + | rwData: u64, |
|
| 120 | + | /// First instruction to execute. |
|
| 121 | + | entry: u64, |
|
| 122 | + | }, |
|
| 123 | + | } |
|
| 124 | + | ||
| 125 | + | /// Compute a signed displacement for a two-instruction AUIPC/ADDI load. |
|
| 126 | + | export fn displacement(source: u64, target: u64) -> ?i32 { |
|
| 127 | + | if target >= source { |
|
| 128 | + | let distance = target - source; |
|
| 129 | + | // AUIPC sign-extends its upper immediate before ADDI applies the low part. |
|
| 130 | + | if distance > 0x7ffff7ff { return nil; } |
|
| 131 | + | return distance as i32; |
|
| 132 | + | } |
|
| 133 | + | let distance = source - target; |
|
| 134 | + | if distance > 0x80000000 { return nil; } |
|
| 135 | + | return (-(distance as i64)) as i32; |
|
| 136 | + | } |
lib/std/arch/rv64/image/tests.rad
added
+183 -0
| 1 | + | //! Native image placement, relocation, and wire-header checks. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use std::lang::il; |
|
| 5 | + | use std::lang::alloc; |
|
| 6 | + | use std::lang::gen::data; |
|
| 7 | + | use std::collections::dict; |
|
| 8 | + | use std::arch::rv64; |
|
| 9 | + | use std::arch::rv64::image; |
|
| 10 | + | use std::arch::rv64::emit; |
|
| 11 | + | use std::arch::rv64::encode; |
|
| 12 | + | ||
| 13 | + | /// Arena for one emitter and its bounded relocation tables. |
|
| 14 | + | static MEMORY: [u8; 16777216] = [0; 16777216]; |
|
| 15 | + | /// Data symbol lookup workspace. |
|
| 16 | + | unsafe static ENTRIES: [dict::Entry; data::DATA_SYM_TABLE_SIZE] = undefined; |
|
| 17 | + | ||
| 18 | + | /// Construct a small image with three disjoint high-address segments. |
|
| 19 | + | fn layout() -> image::Layout { |
|
| 20 | + | return image::Layout { |
|
| 21 | + | entry: 0x80000004, |
|
| 22 | + | code: image::Segment { address: 0x80000000, initialized: 8, memory: 8 }, |
|
| 23 | + | roData: image::Segment { address: 0x80001000, initialized: 3, memory: 16 }, |
|
| 24 | + | rwData: image::Segment { address: 0x80002000, initialized: 4, memory: 4096 }, |
|
| 25 | + | }; |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// Check the exact 64-byte little-endian header. |
|
| 29 | + | @test fn header() throws (testing::TestError) { |
|
| 30 | + | let bytes = try image::header(layout()) catch { throw testing::TestError::Failed; }; |
|
| 31 | + | try testing::expectBytesEq(&bytes[..], &[ |
|
| 32 | + | 82, 65, 68, 48, 2, 0, 0, 0, 4, 0, 0, 128, 0, 0, 0, 0, |
|
| 33 | + | 0, 0, 0, 128, 0, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, |
|
| 34 | + | 0, 16, 0, 128, 0, 0, 0, 0, 3, 0, 0, 0, 16, 0, 0, 0, |
|
| 35 | + | 0, 32, 0, 128, 0, 0, 0, 0, 4, 0, 0, 0, 0, 16, 0, 0, |
|
| 36 | + | ]); |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | /// Check a rejected image layout. |
|
| 40 | + | fn invalid(item: image::Layout, expected: image::Error) throws (testing::TestError) { |
|
| 41 | + | let mut failed = false; |
|
| 42 | + | try image::validate(item) catch err { |
|
| 43 | + | try testing::expect(err == expected); |
|
| 44 | + | set failed = true; |
|
| 45 | + | }; |
|
| 46 | + | try testing::expect(failed); |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | /// Check overlap, alignment, size, entry, and address overflow failures. |
|
| 50 | + | @test fn validation() throws (testing::TestError) { |
|
| 51 | + | let mut item = layout(); |
|
| 52 | + | set item.rwData.address = item.roData.address + 8; |
|
| 53 | + | try invalid(item, image::Error::Overlap); |
|
| 54 | + | set item = layout(); |
|
| 55 | + | set item.roData.address = item.code.address; |
|
| 56 | + | try invalid(item, image::Error::Overlap); |
|
| 57 | + | set item = layout(); |
|
| 58 | + | set item.rwData.address += 1; |
|
| 59 | + | try invalid(item, image::Error::Alignment); |
|
| 60 | + | set item = layout(); |
|
| 61 | + | set item.entry += 1; |
|
| 62 | + | try invalid(item, image::Error::Alignment); |
|
| 63 | + | set item = layout(); |
|
| 64 | + | set item.entry = item.code.address + item.code.initialized as u64; |
|
| 65 | + | try invalid(item, image::Error::Entry); |
|
| 66 | + | set item = layout(); |
|
| 67 | + | set item.roData.initialized = 17; |
|
| 68 | + | try invalid(item, image::Error::Size); |
|
| 69 | + | set item = layout(); |
|
| 70 | + | set item.rwData.address = 0xfffffffffffffff8; |
|
| 71 | + | try invalid(item, image::Error::Overflow); |
|
| 72 | + | set item = layout(); |
|
| 73 | + | set item.roData.address = item.code.address + 8; |
|
| 74 | + | try image::validate(item) catch { throw testing::TestError::Failed; }; |
|
| 75 | + | } |
|
| 76 | + | ||
| 77 | + | /// Check AUIPC/ADDI limits without overflowing unsigned address arithmetic. |
|
| 78 | + | @test fn displacements() throws (testing::TestError) { |
|
| 79 | + | try testing::expect(image::displacement(0x180000000, 0x180002000) == 8192); |
|
| 80 | + | try testing::expect(image::displacement(0x180002000, 0x180000000) == -8192); |
|
| 81 | + | try testing::expect(image::displacement(0, 0x7ffff7ff) == 0x7ffff7ff); |
|
| 82 | + | try testing::expect(image::displacement(0, 0x7ffff800) == nil); |
|
| 83 | + | try testing::expect(image::displacement(0x80000000, 0) == -2147483648); |
|
| 84 | + | try testing::expect(image::displacement(0x80000001, 0) == nil); |
|
| 85 | + | try testing::expect(image::displacement(0, 0xffffffffffffffff) == nil); |
|
| 86 | + | try testing::expect(image::displacement(0xffffffffffffffff, 0) == nil); |
|
| 87 | + | } |
|
| 88 | + | ||
| 89 | + | /// Check high physical placement, zero-fill extents, and 64-bit data relocations. |
|
| 90 | + | @test unsafe fn nativeProgram() throws (testing::TestError) { |
|
| 91 | + | let mut arena = alloc::new(&mut MEMORY[..]); |
|
| 92 | + | let mut generator = rv64::beginProgram(rv64::ProgramOptions { |
|
| 93 | + | entryPatch: rv64::EntryPatch::None, debug: false, |
|
| 94 | + | placement: image::Placement::Physical { |
|
| 95 | + | code: 0x180000000, roData: 0x180001000, rwData: 0x180002000, entry: 0x180000000, |
|
| 96 | + | }, |
|
| 97 | + | }, &mut arena); |
|
| 98 | + | emit::recordFunc(&mut generator.e, "p::entry"); |
|
| 99 | + | emit::recordFuncOffset(&mut generator.e, "p::entry"); |
|
| 100 | + | emit::recordDataAddrLoad(&mut generator.e, "p::pointer", rv64::A0); |
|
| 101 | + | let globals = &[ |
|
| 102 | + | il::Data { |
|
| 103 | + | name: "p::pointer", size: 8, alignment: 8, readOnly: false, isZeroInit: false, |
|
| 104 | + | values: &[il::DataValue { item: il::DataItem::Fn("p::entry"), count: 1 }], |
|
| 105 | + | }, |
|
| 106 | + | il::Data { |
|
| 107 | + | name: "p::zero", size: 4096, alignment: 8, readOnly: false, isZeroInit: true, values: &[], |
|
| 108 | + | }, |
|
| 109 | + | ]; |
|
| 110 | + | unsafe static syms: [data::DataSym; 2] = undefined; |
|
| 111 | + | let mut ro: [u8; 8] = [0; 8]; |
|
| 112 | + | let mut rw: [u8; 8] = [0; 8]; |
|
| 113 | + | let result = try rv64::finishProgram(&mut generator, globals, |
|
| 114 | + | rv64::Storage { dataSyms: &mut syms[..], dataSymEntries: &mut ENTRIES[..] }, |
|
| 115 | + | &[], &mut ro[..], &mut rw[..] |
|
| 116 | + | ) catch { throw testing::TestError::Failed; }; |
|
| 117 | + | try testing::expect(result.layout.code.address == 0x180000000); |
|
| 118 | + | try testing::expect(result.layout.rwData.initialized == 8); |
|
| 119 | + | try testing::expect(result.layout.rwData.memory == 4104); |
|
| 120 | + | try testing::expect(syms[1].addr == 0x180002008); |
|
| 121 | + | try testing::expectBytesEq(&rw[..], &[0, 0, 0, 128, 1, 0, 0, 0]); |
|
| 122 | + | try testing::expect(result.code[0] == encode::auipc(rv64::A0, 2)); |
|
| 123 | + | try testing::expect(result.code[1] == encode::addi(rv64::A0, rv64::A0, 0)); |
|
| 124 | + | } |
|
| 125 | + | ||
| 126 | + | /// Check bounded data-layout failure without wrapped addresses or symbol writes. |
|
| 127 | + | @test unsafe fn dataLayout() throws (testing::TestError) { |
|
| 128 | + | let item = il::Data { |
|
| 129 | + | name: "p::data", size: 16, alignment: 8, readOnly: false, isZeroInit: true, values: &[], |
|
| 130 | + | }; |
|
| 131 | + | unsafe static syms: [data::DataSym; 1] = undefined; |
|
| 132 | + | let mut count: u32 = 0; |
|
| 133 | + | let mut failed = false; |
|
| 134 | + | try data::layoutSection(&[item], &mut syms[..], &mut count, 0xfffffffffffffff8, false) catch err { |
|
| 135 | + | try testing::expect(err == data::Error::Overflow); |
|
| 136 | + | set failed = true; |
|
| 137 | + | }; |
|
| 138 | + | try testing::expect(failed and count == 0); |
|
| 139 | + | set failed = false; |
|
| 140 | + | try data::layoutSection(&[item], &mut syms[..0], &mut count, 0x80000000, false) catch err { |
|
| 141 | + | try testing::expect(err == data::Error::Capacity); |
|
| 142 | + | set failed = true; |
|
| 143 | + | }; |
|
| 144 | + | try testing::expect(failed and count == 0); |
|
| 145 | + | set failed = false; |
|
| 146 | + | try data::layoutSectionAtOffset(&[item], &mut syms[..], &mut count, 0, 0xfffffff8, false) catch err { |
|
| 147 | + | try testing::expect(err == data::Error::Overflow); |
|
| 148 | + | set failed = true; |
|
| 149 | + | }; |
|
| 150 | + | try testing::expect(failed and count == 0); |
|
| 151 | + | } |
|
| 152 | + | ||
| 153 | + | /// Keep declared symbol extents and alignment gaps in initialized data. |
|
| 154 | + | @test unsafe fn initializedExtents() throws (testing::TestError) { |
|
| 155 | + | let mut arena = alloc::new(&mut MEMORY[..]); |
|
| 156 | + | let mut generator = rv64::beginProgram(rv64::ProgramOptions { |
|
| 157 | + | entryPatch: rv64::EntryPatch::None, debug: false, |
|
| 158 | + | placement: image::Placement::Physical { |
|
| 159 | + | code: 0x80000000, roData: 0x80001000, rwData: 0x80002000, entry: 0x80000000, |
|
| 160 | + | }, |
|
| 161 | + | }, &mut arena); |
|
| 162 | + | emit::recordDataAddrLoad(&mut generator.e, "p::second", rv64::A0); |
|
| 163 | + | let globals = &[ |
|
| 164 | + | il::Data { |
|
| 165 | + | name: "p::first", size: 5, alignment: 1, readOnly: false, isZeroInit: false, |
|
| 166 | + | values: &[il::DataValue { item: il::DataItem::Str("a"), count: 1 }], |
|
| 167 | + | }, |
|
| 168 | + | il::Data { |
|
| 169 | + | name: "p::second", size: 8, alignment: 8, readOnly: false, isZeroInit: false, |
|
| 170 | + | values: &[il::DataValue { item: il::DataItem::Str("b"), count: 1 }], |
|
| 171 | + | }, |
|
| 172 | + | ]; |
|
| 173 | + | unsafe static syms: [data::DataSym; 2] = undefined; |
|
| 174 | + | let mut ro: [u8; 0] = []; |
|
| 175 | + | let mut rw: [u8; 16] = [255; 16]; |
|
| 176 | + | let result = try rv64::finishProgram(&mut generator, globals, |
|
| 177 | + | rv64::Storage { dataSyms: &mut syms[..], dataSymEntries: &mut ENTRIES[..] }, |
|
| 178 | + | &[], &mut ro[..], &mut rw[..] |
|
| 179 | + | ) catch { throw testing::TestError::Failed; }; |
|
| 180 | + | try testing::expect(result.layout.rwData.initialized == 16); |
|
| 181 | + | try testing::expect(syms[1].addr == 0x80002008); |
|
| 182 | + | try testing::expectBytesEq(&rw[..], &[97, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0]); |
|
| 183 | + | } |
lib/std/arch/rv64/tests.rad
+1 -1
| 36 | 36 | offset: super::INSTR_SIZE, |
|
| 37 | 37 | isExported: true, |
|
| 38 | 38 | }; |
|
| 39 | 39 | ||
| 40 | 40 | let mut generator = super::beginProgram( |
|
| 41 | - | super::ProgramOptions { entryPatch: super::EntryPatch::None, debug: false }, |
|
| 41 | + | super::ProgramOptions { entryPatch: super::EntryPatch::None, debug: false, placement: super::image::Placement::Hosted }, |
|
| 42 | 42 | &mut arena |
|
| 43 | 43 | ); |
|
| 44 | 44 | super::addAssembly( |
|
| 45 | 45 | &mut generator, |
|
| 46 | 46 | asm::Program { |
lib/std/lang/gen/data.rad
+49 -21
| 13 | 13 | ||
| 14 | 14 | /// Size of the data symbol hash table. Must be a power of two |
|
| 15 | 15 | /// and at least twice the size of [`MAX_DATA_SYMS`]. |
|
| 16 | 16 | export constant DATA_SYM_TABLE_SIZE: u32 = MAX_DATA_SYMS * 2; |
|
| 17 | 17 | ||
| 18 | + | /// A data section cannot fit its address or symbol storage. |
|
| 19 | + | export union Error: Copy { |
|
| 20 | + | /// The symbol array is full. |
|
| 21 | + | Capacity, |
|
| 22 | + | /// A section size or address would overflow. |
|
| 23 | + | Overflow, |
|
| 24 | + | /// A data alignment is zero or is not a power of two. |
|
| 25 | + | Alignment, |
|
| 26 | + | } |
|
| 27 | + | ||
| 18 | 28 | /// Data symbol entry mapping name to address. |
|
| 19 | 29 | export record DataSym: Copy { |
|
| 20 | 30 | /// Symbol name. |
|
| 21 | 31 | name: *[u8], |
|
| 22 | 32 | /// Absolute address, including data base address. |
|
| 23 | - | addr: u32, |
|
| 33 | + | addr: u64, |
|
| 24 | 34 | } |
|
| 25 | 35 | ||
| 26 | 36 | /// Hash-indexed data symbol map. |
|
| 27 | 37 | export record DataSymMap { |
|
| 28 | 38 | /// Underlying hash table. |
| 37 | 47 | /// Returns the updated offset past all placed symbols. |
|
| 38 | 48 | export fn layoutSection( |
|
| 39 | 49 | items: &[il::Data], |
|
| 40 | 50 | syms: &mut [DataSym], |
|
| 41 | 51 | count: &mut u32, |
|
| 42 | - | base: u32, |
|
| 52 | + | base: u64, |
|
| 43 | 53 | readOnly: bool |
|
| 44 | - | ) -> u32 { |
|
| 45 | - | return layoutSectionAtOffset(items, syms, count, base, 0, readOnly); |
|
| 54 | + | ) -> u32 throws (Error) { |
|
| 55 | + | return try layoutSectionAtOffset(items, syms, count, base, 0, readOnly); |
|
| 46 | 56 | } |
|
| 47 | 57 | ||
| 48 | 58 | /// Lay out data symbols for a single section starting at [`startOffset`]. |
|
| 49 | 59 | export fn layoutSectionAtOffset( |
|
| 50 | 60 | items: &[il::Data], |
|
| 51 | 61 | syms: &mut [DataSym], |
|
| 52 | 62 | count: &mut u32, |
|
| 53 | - | base: u32, |
|
| 63 | + | base: u64, |
|
| 54 | 64 | startOffset: u32, |
|
| 55 | 65 | readOnly: bool |
|
| 56 | - | ) -> u32 { |
|
| 66 | + | ) -> u32 throws (Error) { |
|
| 57 | 67 | let mut offset: u32 = startOffset; |
|
| 58 | 68 | ||
| 59 | 69 | // Data requiring sidecar image bytes first. |
|
| 60 | 70 | for i in 0..items.len { |
|
| 61 | 71 | let data = items[i]; |
|
| 62 | 72 | if data.readOnly == readOnly and not data.isZeroInit { |
|
| 63 | - | set offset = mem::alignUp(offset, data.alignment); |
|
| 64 | - | set syms[*count] = DataSym { name: data.name, addr: base + offset }; |
|
| 65 | - | set *count += 1; |
|
| 66 | - | set offset += data.size; |
|
| 73 | + | try place(&data, syms, count, base, &mut offset); |
|
| 67 | 74 | } |
|
| 68 | 75 | } |
|
| 69 | 76 | // Zero-initialized data after. |
|
| 70 | 77 | for i in 0..items.len { |
|
| 71 | 78 | let data = items[i]; |
|
| 72 | 79 | if data.readOnly == readOnly and data.isZeroInit { |
|
| 73 | - | set offset = mem::alignUp(offset, data.alignment); |
|
| 74 | - | set syms[*count] = DataSym { name: data.name, addr: base + offset }; |
|
| 75 | - | set *count += 1; |
|
| 76 | - | set offset += data.size; |
|
| 80 | + | try place(&data, syms, count, base, &mut offset); |
|
| 77 | 81 | } |
|
| 78 | 82 | } |
|
| 79 | 83 | return offset; |
|
| 80 | 84 | } |
|
| 81 | 85 | ||
| 86 | + | /// Place a data symbol with checked offset and address arithmetic. |
|
| 87 | + | fn place(item: &il::Data, syms: &mut [DataSym], count: &mut u32, base: u64, offset: &mut u32) |
|
| 88 | + | throws (Error) |
|
| 89 | + | { |
|
| 90 | + | if item.alignment == 0 or (item.alignment & (item.alignment - 1)) <> 0 { |
|
| 91 | + | throw Error::Alignment; |
|
| 92 | + | } |
|
| 93 | + | if (base & (item.alignment as u64 - 1)) <> 0 { throw Error::Alignment; } |
|
| 94 | + | let aligned = (*offset as u64 + item.alignment as u64 - 1) & ~(item.alignment as u64 - 1); |
|
| 95 | + | let end = aligned + item.size as u64; |
|
| 96 | + | if end > 0xffffffff or base > 0xffffffffffffffff - end { |
|
| 97 | + | throw Error::Overflow; |
|
| 98 | + | } |
|
| 99 | + | if *count >= syms.len { throw Error::Capacity; } |
|
| 100 | + | set syms[*count] = DataSym { name: item.name, addr: base + aligned }; |
|
| 101 | + | set *count += 1; |
|
| 102 | + | set *offset = end as u32; |
|
| 103 | + | } |
|
| 104 | + | ||
| 82 | 105 | /// Emit data bytes for a single section (read-only or read-write) into `buf`. |
|
| 83 | 106 | /// Iterates data requiring sidecar image bytes, serializing each data item. |
|
| 84 | 107 | /// Returns the total number of bytes written. |
|
| 85 | 108 | export unsafe fn emitSection( |
|
| 86 | 109 | items: &[il::Data], |
|
| 87 | 110 | dataSymMap: &DataSymMap, |
|
| 88 | 111 | fnLabels: &labels::Labels, |
|
| 89 | - | codeBase: u32, |
|
| 112 | + | codeBase: u64, |
|
| 90 | 113 | buf: &mut [u8], |
|
| 91 | 114 | readOnly: bool |
|
| 92 | 115 | ) -> u32 { |
|
| 93 | 116 | return emitSectionAtOffset(items, dataSymMap, fnLabels, codeBase, buf, readOnly, 0); |
|
| 94 | 117 | } |
| 96 | 119 | /// Emit data bytes for a single section starting at `startOffset`. |
|
| 97 | 120 | export unsafe fn emitSectionAtOffset( |
|
| 98 | 121 | items: &[il::Data], |
|
| 99 | 122 | dataSymMap: &DataSymMap, |
|
| 100 | 123 | fnLabels: &labels::Labels, |
|
| 101 | - | codeBase: u32, |
|
| 124 | + | codeBase: u64, |
|
| 102 | 125 | buf: &mut [u8], |
|
| 103 | 126 | readOnly: bool, |
|
| 104 | 127 | startOffset: u32 |
|
| 105 | 128 | ) -> u32 { |
|
| 106 | 129 | let mut offset: u32 = startOffset; |
|
| 107 | 130 | ||
| 108 | 131 | for i in 0..items.len { |
|
| 109 | 132 | let data = items[i]; |
|
| 110 | 133 | if data.readOnly == readOnly and not data.isZeroInit { |
|
| 134 | + | let start = offset; |
|
| 111 | 135 | set offset = mem::alignUp(offset, data.alignment); |
|
| 112 | - | assert offset + data.size <= buf.len, "emitSectionAtOffset: buffer overflow"; |
|
| 136 | + | assert offset <= buf.len and data.size <= buf.len - offset, "emitSectionAtOffset: buffer overflow"; |
|
| 137 | + | let end = offset + data.size; |
|
| 138 | + | for j in start..end { set buf[j] = 0; } |
|
| 113 | 139 | for j in 0..data.values.len { |
|
| 114 | 140 | let v = &data.values[j]; |
|
| 115 | 141 | for _ in 0..v.count { |
|
| 116 | 142 | match v.item { |
|
| 117 | 143 | case il::DataItem::Val { typ, val } => { |
| 128 | 154 | try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, 8)); |
|
| 129 | 155 | ||
| 130 | 156 | set offset += @sizeOf(u64); |
|
| 131 | 157 | }, |
|
| 132 | 158 | case il::DataItem::Fn(name) => { |
|
| 133 | - | let addr = codeBase + labels::funcOffset(fnLabels, name) as u32; |
|
| 159 | + | let addr = codeBase + labels::funcOffset(fnLabels, name) as u64; |
|
| 134 | 160 | let addr64: u64 = addr as u64; |
|
| 135 | 161 | try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, 8)); |
|
| 136 | 162 | ||
| 137 | 163 | set offset += @sizeOf(*u8); |
|
| 138 | 164 | }, |
| 145 | 171 | set offset += 1; |
|
| 146 | 172 | }, |
|
| 147 | 173 | } |
|
| 148 | 174 | } |
|
| 149 | 175 | } |
|
| 176 | + | assert offset <= end, "emitSectionAtOffset: initializer exceeds symbol size"; |
|
| 177 | + | set offset = end; |
|
| 150 | 178 | } |
|
| 151 | 179 | } |
|
| 152 | 180 | return offset; |
|
| 153 | 181 | } |
|
| 154 | 182 | ||
| 155 | 183 | /// Build a hash-indexed data symbol map from the laid-out symbols. |
|
| 156 | 184 | /// The `entries` slice must have length `DATA_SYM_TABLE_SIZE`. |
|
| 157 | 185 | export fn buildMap(syms: *[DataSym], entries: *mut [dict::Entry]) -> DataSymMap { |
|
| 158 | 186 | let mut d = dict::init(entries); |
|
| 159 | 187 | for i in 0..syms.len { |
|
| 160 | - | dict::insert(&mut d, syms[i].name, syms[i].addr as i32); |
|
| 188 | + | dict::insert(&mut d, syms[i].name, i as i32); |
|
| 161 | 189 | } |
|
| 162 | 190 | return DataSymMap { dict: d, syms }; |
|
| 163 | 191 | } |
|
| 164 | 192 | ||
| 165 | 193 | /// Resolve a data symbol to its final absolute address using the hash map. |
|
| 166 | - | export fn lookupAddr(m: &DataSymMap, name: *[u8]) -> ?u32 { |
|
| 194 | + | export fn lookupAddr(m: &DataSymMap, name: *[u8]) -> ?u64 { |
|
| 167 | 195 | if let v = dict::get(&m.dict, name) { |
|
| 168 | - | return v as u32; |
|
| 196 | + | return m.syms[v as u32].addr; |
|
| 169 | 197 | } |
|
| 170 | 198 | return nil; |
|
| 171 | 199 | } |
std.lib
+1 -0
| 9 | 9 | lib/std/collections.rad |
|
| 10 | 10 | lib/std/collections/dict.rad |
|
| 11 | 11 | lib/std/sys/unix.rad |
|
| 12 | 12 | lib/std/arch.rad |
|
| 13 | 13 | lib/std/arch/rv64.rad |
|
| 14 | + | lib/std/arch/rv64/image.rad |
|
| 14 | 15 | lib/std/arch/rv64/encode.rad |
|
| 15 | 16 | lib/std/arch/rv64/decode.rad |
|
| 16 | 17 | lib/std/arch/rv64/emit.rad |
|
| 17 | 18 | lib/std/arch/rv64/isel.rad |
|
| 18 | 19 | lib/std/arch/rv64/printer.rad |
std.lib.test
+1 -0
| 10 | 10 | lib/std/lang/scanner/tests.rad |
|
| 11 | 11 | lib/std/lang/resolver/tests.rad |
|
| 12 | 12 | lib/std/lang/gen/bitset/tests.rad |
|
| 13 | 13 | lib/std/lang/il/binary/tests.rad |
|
| 14 | 14 | lib/std/lang/il/binary/decodeTests.rad |
|
| 15 | + | lib/std/arch/rv64/image/tests.rad |
test/native/build.rad
added
+90 -0
| 1 | + | //! Build a native machine fixture with the reusable RV64 backend. |
|
| 2 | + | ||
| 3 | + | use std::sys; |
|
| 4 | + | use std::sys::unix; |
|
| 5 | + | use std::lang::il; |
|
| 6 | + | use std::lang::alloc; |
|
| 7 | + | use std::lang::gen::data; |
|
| 8 | + | use std::collections::dict; |
|
| 9 | + | use std::arch::rv64; |
|
| 10 | + | use std::arch::rv64::image; |
|
| 11 | + | ||
| 12 | + | /// Persistent code generation arena. |
|
| 13 | + | static MEMORY: [u8; 16777216] = [0; 16777216]; |
|
| 14 | + | /// Function lowering and allocation workspace. |
|
| 15 | + | static SCRATCH: [u8; 16777216] = [0; 16777216]; |
|
| 16 | + | /// Data symbol lookup workspace. |
|
| 17 | + | unsafe static ENTRIES: [dict::Entry; data::DATA_SYM_TABLE_SIZE] = undefined; |
|
| 18 | + | ||
| 19 | + | /// Compile data accesses and conditional finish writes into a native image. |
|
| 20 | + | @default unsafe fn main(env: *sys::Env) -> i32 { |
|
| 21 | + | assert env.args.len == 2; |
|
| 22 | + | let mut arena = alloc::new(&mut MEMORY[..]); |
|
| 23 | + | let mut scratch = alloc::new(&mut SCRATCH[..]); |
|
| 24 | + | let mut generator = rv64::beginProgram(rv64::ProgramOptions { |
|
| 25 | + | entryPatch: rv64::EntryPatch::None, debug: false, |
|
| 26 | + | placement: image::Placement::Physical { |
|
| 27 | + | code: 0x80010000, roData: 0x80018000, rwData: 0x80020000, entry: 0x80010000, |
|
| 28 | + | }, |
|
| 29 | + | }, &mut arena); |
|
| 30 | + | let mut args: [il::Val; 0] = []; |
|
| 31 | + | let mut entry = [ |
|
| 32 | + | il::Instr::Copy { dst: il::Reg { n: 0 }, val: il::Val::DataSym("fixture::value") }, |
|
| 33 | + | il::Instr::Load { typ: il::Type::W64, dst: il::Reg { n: 1 }, src: il::Reg { n: 0 }, offset: 0 }, |
|
| 34 | + | il::Instr::Br { |
|
| 35 | + | op: il::CmpOp::Eq, typ: il::Type::W64, a: il::Val::Reg(il::Reg { n: 1 }), b: il::Val::Imm(42), |
|
| 36 | + | thenTarget: 1, thenArgs: &mut args[..], elseTarget: 3, elseArgs: &mut args[..], |
|
| 37 | + | }, |
|
| 38 | + | ]; |
|
| 39 | + | let mut zero = [ |
|
| 40 | + | il::Instr::Copy { dst: il::Reg { n: 2 }, val: il::Val::DataSym("fixture::zero") }, |
|
| 41 | + | il::Instr::Load { typ: il::Type::W64, dst: il::Reg { n: 3 }, src: il::Reg { n: 2 }, offset: 0 }, |
|
| 42 | + | il::Instr::Br { |
|
| 43 | + | op: il::CmpOp::Eq, typ: il::Type::W64, a: il::Val::Reg(il::Reg { n: 3 }), b: il::Val::Imm(0), |
|
| 44 | + | thenTarget: 2, thenArgs: &mut args[..], elseTarget: 3, elseArgs: &mut args[..], |
|
| 45 | + | }, |
|
| 46 | + | ]; |
|
| 47 | + | let mut success = [ |
|
| 48 | + | il::Instr::Copy { dst: il::Reg { n: 4 }, val: il::Val::Imm(0x10001000) }, |
|
| 49 | + | il::Instr::Store { typ: il::Type::W32, src: il::Val::Imm(0x5555), dst: il::Reg { n: 4 }, offset: 0 }, |
|
| 50 | + | il::Instr::Unreachable, |
|
| 51 | + | ]; |
|
| 52 | + | let mut failure = [ |
|
| 53 | + | il::Instr::Copy { dst: il::Reg { n: 5 }, val: il::Val::Imm(0x10001000) }, |
|
| 54 | + | il::Instr::Store { typ: il::Type::W32, src: il::Val::Imm(0x13333), dst: il::Reg { n: 5 }, offset: 0 }, |
|
| 55 | + | il::Instr::Unreachable, |
|
| 56 | + | ]; |
|
| 57 | + | let func = il::Fn { |
|
| 58 | + | name: "fixture::entry", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
|
| 59 | + | blocks: &[ |
|
| 60 | + | il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 }, |
|
| 61 | + | il::Block { label: "zero", params: &[], instrs: &mut zero[..], locs: &[], preds: &[0], loopDepth: 0 }, |
|
| 62 | + | il::Block { label: "success", params: &[], instrs: &mut success[..], locs: &[], preds: &[1], loopDepth: 0 }, |
|
| 63 | + | il::Block { label: "failure", params: &[], instrs: &mut failure[..], locs: &[], preds: &[0, 1], loopDepth: 0 }, |
|
| 64 | + | ], |
|
| 65 | + | }; |
|
| 66 | + | rv64::generateFunction(&mut generator, &func, &mut scratch); |
|
| 67 | + | let globals = &[ |
|
| 68 | + | il::Data { |
|
| 69 | + | name: "fixture::value", size: 8, alignment: 8, readOnly: false, isZeroInit: false, |
|
| 70 | + | values: &[il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: 42 }, count: 1 }], |
|
| 71 | + | }, |
|
| 72 | + | il::Data { |
|
| 73 | + | name: "fixture::zero", size: 4096, alignment: 8, readOnly: false, isZeroInit: true, values: &[], |
|
| 74 | + | }, |
|
| 75 | + | ]; |
|
| 76 | + | unsafe static symbols: [data::DataSym; 2] = undefined; |
|
| 77 | + | let mut ro: [u8; 8] = [0; 8]; |
|
| 78 | + | let mut rw: [u8; 8] = [0; 8]; |
|
| 79 | + | let result = try! rv64::finishProgram(&mut generator, globals, |
|
| 80 | + | rv64::Storage { dataSyms: &mut symbols[..], dataSymEntries: &mut ENTRIES[..] }, |
|
| 81 | + | &[], &mut ro[..], &mut rw[..]); |
|
| 82 | + | let header = try! image::header(result.layout); |
|
| 83 | + | let code = @sliceOf(result.code.ptr as *u8, result.code.len * 4); |
|
| 84 | + | let fd = unix::openOpts(env.args[1], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420); |
|
| 85 | + | assert fd >= 0; |
|
| 86 | + | let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, code) and unix::writeAll(fd, &ro[..result.roDataSize]) and unix::writeAll(fd, &rw[..result.rwDataSize]); |
|
| 87 | + | let closed = unix::close(fd) == 0; |
|
| 88 | + | assert written and closed; |
|
| 89 | + | return 0; |
|
| 90 | + | } |