rv64: Support explicit native image placement

d73b8223074614949fdb7547600b0a4651fd1ed7e330cb167ff806713ce06c39
Alexis Sellier committed ago 1 parent d1be47b5
compiler/radiance.rad +4 -2
1012 1012
            set entryPatch = rv64::EntryPatch::Reserved(nil);
1013 1013
        }
1014 1014
        else => {}
1015 1015
    }
1016 1016
    let mut generator = rv64::beginProgram(
1017 -
        rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug },
1017 +
        rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug, placement: rv64::image::Placement::Hosted },
1018 1018
        &mut res.arena
1019 1019
    );
1020 1020
    let mut codegenCtx = codegen::Context {
1021 1021
        generator: &mut generator,
1022 1022
        fnArena: (&mut *fnArena) as *unsafe mut alloc::Arena,
1044 1044
        else => {}
1045 1045
    }
1046 1046
    if let path = codegenOptions.logPath {
1047 1047
        pkgLog(entryPkg, &["generating code", "(", path, ")", ".."]);
1048 1048
    }
1049 -
    return rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]);
1049 +
    return try rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]) catch {
1050 +
        throw error(&["invalid native image layout"]);
1051 +
    };
1050 1052
}
1051 1053
1052 1054
/// Source exports selected for one binary RIL package.
1053 1055
record PackageExports: Copy {
1054 1056
    /// 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