lib/std/lang/il/images/layout.rad 7.4 KiB raw
1
//! Position-independent native layout and persistent pointer initialization.
2
3
use std::mem;
4
use std::collections::dict;
5
use std::lang::alloc;
6
use std::lang::il;
7
use std::lang::gen::data;
8
use std::arch::rv64;
9
use std::arch::rv64::emit;
10
use std::arch::rv64::encode;
11
use super::graph;
12
13
/// Native bytes and offsets, with data addresses relative to the allocation.
14
export record Layout: Copy {
15
    /// Fully initialized read-only bytes with allocation-relative pointers.
16
    bytes: *[u8],
17
    /// Allocation-relative immutable symbol addresses.
18
    symbols: data::DataSymMap,
19
    /// Byte offset of the first native instruction.
20
    codeOffset: u32,
21
    /// Complete allocation size including native instructions.
22
    size: u32,
23
    /// Required power-of-two allocation alignment.
24
    alignment: u32,
25
}
26
27
/// Resolve a function before invoking backend relocation routines.
28
export fn functionOffset(e: *emit::Emitter, name: *[u8]) -> u32 throws (il::binary::Error) {
29
    let offset = dict::get(&e.labels.funcs, name) else {
30
        throw il::binary::error(0, "unresolved native image function");
31
    };
32
    if offset < 0 or offset as u32 % 4 <> 0 or offset as u32 / 4 >= e.codeLen {
33
        throw il::binary::error(0, "native image function offset exceeds code");
34
    }
35
    return offset as u32;
36
}
37
38
/// Require every reserved relocation instruction to belong to emitted code.
39
fn slots(e: *emit::Emitter, index: u32, count: u32) throws (il::binary::Error) {
40
    if index > e.codeLen or count > e.codeLen - index {
41
        throw il::binary::error(0, "native image relocation exceeds code");
42
    }
43
}
44
45
/// Check all references before the shared emitter's infallible final patching.
46
fn references(e: *emit::Emitter, items: *[il::Data], symbols: *data::DataSymMap) throws (il::binary::Error) {
47
    if e.pendingBranches.len <> 0 { throw il::binary::error(0, "unresolved native image block branch"); }
48
    for pending in e.pendingCalls {
49
        try slots(e, pending.index, 2);
50
        let _ = try functionOffset(e, pending.target);
51
    }
52
    for pending in e.pendingJumps {
53
        try slots(e, pending.index, 1);
54
        let offset = try functionOffset(e, pending.target);
55
        if not encode::isJumpImm(offset as i32 - pending.index as i32 * rv64::INSTR_SIZE) {
56
            throw il::binary::error(0, "native image jump exceeds reach");
57
        }
58
    }
59
    for pending in e.pendingAddrLoads {
60
        try slots(e, pending.index, 2);
61
        if pending.isData {
62
            if data::lookupAddr(symbols, pending.target) == nil {
63
                throw il::binary::error(0, "unresolved native image data address");
64
            }
65
        } else { let _ = try functionOffset(e, pending.target); }
66
    }
67
    for item in items {
68
        for value in item.values {
69
            if value.count == 0 { continue; }
70
            match value.item {
71
                case il::DataItem::Sym(name) => {
72
                    if data::lookupAddr(symbols, name) == nil {
73
                        throw il::binary::error(0, "unresolved native image data initializer");
74
                    }
75
                }
76
                case il::DataItem::Fn(name) => { let _ = try functionOffset(e, name); }
77
                else => {},
78
            }
79
        }
80
    }
81
}
82
83
/// Lay out flattened immutable data using the shared data placement and emitter.
84
/// Code and data address instructions use allocation-local PC-relative offsets.
85
export fn prepare(generator: *mut rv64::Generator, items: *[il::Data], arena: *mut alloc::Arena,
86
    scratch: *mut alloc::Arena) -> Layout throws (il::binary::Error) {
87
    let mut alignment: u32 = 8;
88
    let mut extent: u64 = 0;
89
    for item in items {
90
        let a = item.alignment as u64;
91
        if not item.readOnly or item.isZeroInit or a == 0 or a & (a - 1) <> 0 or a > graph::MAX_DATA as u64 {
92
            throw il::binary::error(0, "invalid flattened native image data");
93
        }
94
        if item.alignment > alignment { set alignment = item.alignment; }
95
        set extent = ((extent + a - 1) & ~(a - 1)) + item.size as u64;
96
        if extent > graph::MAX_DATA as u64 { throw il::binary::error(0, "native image data exceeds capacity"); }
97
        let mut size: u64 = 0;
98
        for value in item.values {
99
            set size += graph::width(value.item) as u64 * value.count as u64;
100
            if size > item.size as u64 { throw il::binary::error(0, "native image initializer exceeds data"); }
101
        }
102
        if size <> item.size as u64 { throw il::binary::error(0, "native image initializer size mismatch"); }
103
    }
104
    let syms = try graph::storage(scratch, @sizeOf(data::DataSym), @alignOf(data::DataSym), items.len) as *mut [data::DataSym];
105
    let mut count: u32 = 0;
106
    let dataSize = data::layoutSection(items, syms, &mut count, 0, true);
107
    let mut map = try il::binary::dictionary(scratch, count, 0);
108
    for symbol in syms {
109
        if dict::get(&map, symbol.name) <> nil { throw il::binary::error(0, "duplicate native image data symbol"); }
110
        dict::insert(&mut map, symbol.name, symbol.addr as i32);
111
    }
112
    let symbols = data::DataSymMap { dict: map, syms };
113
    let e = &mut generator.e;
114
    let codeOffset = mem::alignUp(dataSize, 8);
115
    let total = codeOffset as u64 + e.codeLen as u64 * rv64::INSTR_SIZE as u64;
116
    // Keep every allocation-local AUIPC displacement strictly inside signed reach.
117
    if total > 0x7FFFF000 { throw il::binary::error(0, "native image exceeds relative address reach"); }
118
    try references(e, items, &symbols);
119
    let bytes = try graph::storage(arena, 1, 1, dataSize) as *mut [u8];
120
    for i in 0..bytes.len { set bytes[i] = 0; }
121
    emit::patchCalls(e);
122
    emit::patchJumps(e);
123
    for pending in e.pendingAddrLoads {
124
        let mut target: u32 = 0;
125
        if pending.isData {
126
            let address = data::lookupAddr(&symbols, pending.target) else {
127
                throw il::binary::error(0, "unresolved native image data address");
128
            };
129
            set target = address;
130
        } else { set target = codeOffset + (try functionOffset(e, pending.target)); }
131
        let pc = codeOffset + pending.index * rv64::INSTR_SIZE as u32;
132
        let split = emit::splitImm(target as i32 - pc as i32);
133
        emit::patch(e, pending.index, encode::auipc(pending.rd, split.hi));
134
        emit::patch(e, pending.index + 1, encode::addi(pending.rd, pending.rd, split.lo));
135
    }
136
    data::emitSection(items, &symbols, &e.labels, codeOffset, bytes, true);
137
    return Layout { bytes, symbols, codeOffset, size: total as u32, alignment };
138
}
139
140
/// Convert allocation-relative initializer pointers to actual native addresses.
141
/// Scalar private offsets remain unchanged for the per-instance relocation helper.
142
export fn rebase(items: *[il::Data], destination: *mut [u8], base: u64) {
143
    let mut offset: u32 = 0;
144
    for item in items {
145
        set offset = mem::alignUp(offset, item.alignment);
146
        for value in item.values {
147
            match value.item {
148
                case il::DataItem::Sym(_), il::DataItem::Fn(_) => {
149
                    for _ in 0..value.count {
150
                        let mut relative: u64 = 0;
151
                        for byte in 0..8 { set relative |= (destination[offset + byte] as u64) << (byte as u64 * 8); }
152
                        let address = base + relative;
153
                        for byte in 0..8 { set destination[offset + byte] = (address >> (byte as u64 * 8)) as u8; }
154
                        set offset += 8;
155
                    }
156
                }
157
                else => set offset += graph::width(value.item) * value.count,
158
            }
159
        }
160
    }
161
}