lib/std/lang/il/images/graph.rad 15.5 KiB raw
1
//! Closed binary image graphs, private-state placement, and symbol rewriting.
2
3
use std::mem;
4
use std::fmt;
5
use std::collections::dict;
6
use std::lang::alloc;
7
use std::lang::strings;
8
use std::lang::il;
9
use std::lang::gen::data;
10
11
/// Maximum bytes in an image's private state or one native data section.
12
export constant MAX_DATA: u32 = 4 * 1024 * 1024;
13
14
/// One reverse initializer edge used to promote address-bearing constants.
15
record Edge: Copy {
16
    /// Declaration containing the pointer initializer.
17
    owner: u32,
18
    /// Next reverse edge for the same target.
19
    next: ?u32,
20
}
21
22
/// Shared placement facts used by catalog generation and native emission.
23
export record Plan: Copy {
24
    /// Original data declaration indexes.
25
    dataIndex: dict::Dict,
26
    /// Original function declaration indexes.
27
    fnIndex: dict::Dict,
28
    /// Data declarations classified for the ordinary data layout routine.
29
    items: *mut [il::Data],
30
    /// Private byte offsets, indexed by the original data declaration.
31
    offsets: *mut [u32],
32
    /// Native layout order of the private declarations.
33
    order: *[data::DataSym],
34
    /// Number of private bytes.
35
    size: u32,
36
    /// Required private allocation alignment.
37
    alignment: u32,
38
    /// Entry function declaration index.
39
    entry: u32,
40
    /// The entry returns a status word.
41
    returnsStatus: bool,
42
}
43
44
/// Persistent names corresponding to the declaration indexes in Plan.
45
export record Names: Copy {
46
    /// Namespaced data declaration names.
47
    data: *[*[u8]],
48
    /// Namespaced function names and permitted native external names.
49
    functions: *[*[u8]],
50
}
51
52
/// Allocate through the binary codec's checked public arena interface.
53
export fn storage(arena: *mut alloc::Arena, size: u32, alignment: u32, count: u32) -> *mut [opaque] throws (il::binary::Error) {
54
    return try il::binary::storage(arena, size, alignment, count, 0);
55
}
56
57
/// Copy bytes whose lifetime would otherwise end with the decoded image.
58
export fn copy(arena: *mut alloc::Arena, bytes: *[u8]) -> *[u8] throws (il::binary::Error) {
59
    let result = try storage(arena, 1, 1, bytes.len) as *mut [u8];
60
    try! mem::copy(result, bytes);
61
    return result;
62
}
63
64
/// Intern persistent names without overflowing the caller's shared pool.
65
fn intern(pool: *mut strings::Pool, arena: *mut alloc::Arena, bytes: *[u8]) -> *[u8] throws (il::binary::Error) {
66
    if let existing = strings::find(pool, bytes) { return existing; }
67
    if pool.count >= pool.table.len / 2 { throw il::binary::error(0, "image string pool exhausted"); }
68
    return strings::intern(pool, try copy(arena, bytes));
69
}
70
71
/// Construct a trusted helper or namespace name with a decimal image ID.
72
export fn name(prefix: *[u8], id: u32, suffix: *[u8], pool: *mut strings::Pool, arena: *mut alloc::Arena) -> *[u8] throws (il::binary::Error) {
73
    let mut digits: [u8; 10] = undefined;
74
    let number = fmt::formatU32(id, &mut digits[..]);
75
    let size = prefix.len as u64 + number.len as u64 + suffix.len as u64;
76
    if size > 0x7FFFFFFF { throw il::binary::error(0, "image symbol is too long"); }
77
    let saved = alloc::save(arena);
78
    let bytes = try storage(arena, 1, 1, size as u32) as *mut [u8];
79
    let mut pos = try! mem::copy(bytes, prefix);
80
    set pos += try! mem::copy(&mut bytes[pos..], number);
81
    try! mem::copy(&mut bytes[pos..], suffix);
82
    if let existing = strings::find(pool, bytes) {
83
        alloc::restore(arena, saved);
84
        return existing;
85
    }
86
    if pool.count >= pool.table.len / 2 { throw il::binary::error(0, "image string pool exhausted"); }
87
    return strings::intern(pool, bytes);
88
}
89
90
/// Decode already established declaration references into an index.
91
export fn index(map: *dict::Dict, symbol: *[u8]) -> u32 throws (il::binary::Error) {
92
    let value = dict::get(map, symbol) else { throw il::binary::error(0, "unresolved image graph reference"); };
93
    return value as u32;
94
}
95
96
/// Width of one repetition, matching the shared data emitter.
97
export fn width(item: il::DataItem) -> u32 {
98
    match item {
99
        case il::DataItem::Val { typ, .. } => return il::typeSize(typ),
100
        case il::DataItem::Sym(_), il::DataItem::Fn(_) => return 8,
101
        case il::DataItem::Str(bytes) => return bytes.len,
102
        case il::DataItem::Undef => return 1,
103
    }
104
}
105
106
/// Classify transitive private pointers, then use the ordinary data layout order.
107
export fn plan(image: *il::binary::Image, arena: *mut alloc::Arena) -> Plan throws (il::binary::Error) {
108
    let program = &image.program;
109
    if program.data.len >= data::MAX_DATA_SYMS { throw il::binary::error(0, "too many image data symbols"); }
110
    if program.fns.len > 8192 { throw il::binary::error(0, "too many image functions"); }
111
    let mut dataIndex = try il::binary::dictionary(arena, program.data.len, 0);
112
    let mut fnIndex = try il::binary::dictionary(arena, program.fns.len, 0);
113
    let items = try storage(arena, @sizeOf(il::Data), @alignOf(il::Data), program.data.len) as *mut [il::Data];
114
    let offsets = try storage(arena, @sizeOf(u32), @alignOf(u32), items.len) as *mut [u32];
115
    let heads = try storage(arena, @sizeOf(?u32), @alignOf(?u32), items.len) as *mut [?u32];
116
    let queue = try storage(arena, @sizeOf(u32), @alignOf(u32), items.len) as *mut [u32];
117
    let mut queued: u32 = 0;
118
    let mut edgeCount: u64 = 0;
119
    for item, i in program.data {
120
        dict::insert(&mut dataIndex, item.name, i as i32);
121
        set items[i] = item;
122
        set offsets[i] = 0;
123
        set heads[i] = nil;
124
        if not item.readOnly { set queue[queued] = i; set queued += 1; }
125
        for value in item.values {
126
            if value.count == 0 { continue; }
127
            if let case il::DataItem::Sym(_) = value.item { set edgeCount += 1; }
128
        }
129
    }
130
    for function, i in program.fns {
131
        dict::insert(&mut fnIndex, function.name, i as i32);
132
        if function.isExtern and (function.name.len <= 11 or not mem::eq(&function.name[..11], "user::sys::")) {
133
            throw il::binary::error(0, "image extern must be a declared sys native function");
134
        }
135
    }
136
    if edgeCount > 0x7FFFFFFF { throw il::binary::error(0, "too many image initializer references"); }
137
    let edges = try storage(arena, @sizeOf(Edge), @alignOf(Edge), edgeCount as u32) as *mut [Edge];
138
    let mut edgeIndex: u32 = 0;
139
    for item, i in items {
140
        for value in item.values {
141
            if value.count == 0 { continue; }
142
            match value.item {
143
                case il::DataItem::Sym(symbol) => {
144
                    let target = try index(&dataIndex, symbol);
145
                    set edges[edgeIndex] = Edge { owner: i, next: heads[target] };
146
                    set heads[target] = edgeIndex;
147
                    set edgeIndex += 1;
148
                }
149
                case il::DataItem::Fn(symbol) => { let _ = try index(&fnIndex, symbol); }
150
                else => {},
151
            }
152
        }
153
    }
154
    let mut cursor: u32 = 0;
155
    while cursor < queued {
156
        let mut next = heads[queue[cursor]];
157
        while let e = next {
158
            let edge = edges[e];
159
            if items[edge.owner].readOnly {
160
                set items[edge.owner].readOnly = false;
161
                set queue[queued] = edge.owner;
162
                set queued += 1;
163
            }
164
            set next = edge.next;
165
        }
166
        set cursor += 1;
167
    }
168
    let mut alignment: u32 = 8;
169
    let mut expected: u64 = 0;
170
    for pass in 0..2 {
171
        for item in items {
172
            if item.readOnly or item.isZeroInit <> (pass == 1) { continue; }
173
            let a = item.alignment as u64;
174
            if a == 0 or a & (a - 1) <> 0 or a > MAX_DATA as u64 {
175
                throw il::binary::error(0, "image state alignment exceeds capacity");
176
            }
177
            if item.alignment > alignment { set alignment = item.alignment; }
178
            set expected = ((expected + a - 1) & ~(a - 1)) + item.size as u64;
179
            if expected > MAX_DATA as u64 { throw il::binary::error(0, "image private state exceeds capacity"); }
180
        }
181
    }
182
    let symbols = try storage(arena, @sizeOf(data::DataSym), @alignOf(data::DataSym), queued) as *mut [data::DataSym];
183
    let mut symbolCount: u32 = 0;
184
    let size = data::layoutSection(items, symbols, &mut symbolCount, 0, false);
185
    for symbol in symbols { set offsets[try index(&dataIndex, symbol.name)] = symbol.addr; }
186
    let entryName = image.entry else { throw il::binary::error(0, "image has no entry function"); };
187
    let entry = try index(&fnIndex, entryName);
188
    let function = program.fns[entry];
189
    if function.isExtern or function.params.len <> 1 or il::typeSize(function.params[0].type) <> 8 {
190
        throw il::binary::error(0, "image entry must receive one Env pointer");
191
    }
192
    let mut returnsStatus = false;
193
    let mut returnsVoid = false;
194
    for block in function.blocks {
195
        for instruction in block.instrs {
196
            if let case il::Instr::Ret { val } = instruction {
197
                if let value = val {
198
                    match value {
199
                        case il::Val::Undef => set returnsVoid = true,
200
                        else => set returnsStatus = true,
201
                    }
202
                } else { set returnsVoid = true; }
203
            }
204
        }
205
    }
206
    if returnsStatus and (returnsVoid or il::typeSize(function.returnType) <> 4) {
207
        throw il::binary::error(0, "image entry must return void or u32");
208
    }
209
    return Plan { dataIndex, fnIndex, items, offsets, order: symbols, size, alignment, entry, returnsStatus };
210
}
211
212
/// Allocate all names once; native externs alone retain their original spelling.
213
export fn names(program: *il::Program, id: u32, pool: *mut strings::Pool, arena: *mut alloc::Arena) -> Names throws (il::binary::Error) {
214
    let dataNames = try storage(arena, @sizeOf(*[u8]), @alignOf(*[u8]), program.data.len) as *mut [*[u8]];
215
    let fnNames = try storage(arena, @sizeOf(*[u8]), @alignOf(*[u8]), program.fns.len) as *mut [*[u8]];
216
    let prefix = try name("images::image_", id, "::", pool, arena);
217
    for item, i in program.data { set dataNames[i] = try qualified(prefix, item.name, pool, arena); }
218
    for function, i in program.fns {
219
        set fnNames[i] = try intern(pool, arena, function.name) if function.isExtern
220
            else try qualified(prefix, function.name, pool, arena);
221
    }
222
    return Names { data: dataNames, functions: fnNames };
223
}
224
225
/// Prefix an original whole-graph name, retaining its complete qualified path.
226
fn qualified(prefix: *[u8], suffix: *[u8], pool: *mut strings::Pool, arena: *mut alloc::Arena) -> *[u8] throws (il::binary::Error) {
227
    let size = prefix.len as u64 + suffix.len as u64;
228
    if size > 0x7FFFFFFF { throw il::binary::error(0, "image symbol is too long"); }
229
    let saved = alloc::save(arena);
230
    let bytes = try storage(arena, 1, 1, size as u32) as *mut [u8];
231
    let pos = try! mem::copy(bytes, prefix);
232
    try! mem::copy(&mut bytes[pos..], suffix);
233
    if let existing = strings::find(pool, bytes) { alloc::restore(arena, saved); return existing; }
234
    if pool.count >= pool.table.len / 2 { throw il::binary::error(0, "image string pool exhausted"); }
235
    return strings::intern(pool, bytes);
236
}
237
238
/// Rewrite a typed symbol reference, rejecting unresolved graph references.
239
fn val(value: il::Val, plan: *Plan, names: *Names) -> il::Val throws (il::binary::Error) {
240
    match value {
241
        case il::Val::DataSym(symbol) => return il::Val::DataSym(names.data[try index(&plan.dataIndex, symbol)]),
242
        case il::Val::FnAddr(symbol) => return il::Val::FnAddr(names.functions[try index(&plan.fnIndex, symbol)]),
243
        else => return value,
244
    }
245
}
246
247
/// Rewrite mutable block-edge arguments in their decoder-owned storage.
248
fn args(values: *mut [il::Val], plan: *Plan, names: *Names) throws (il::binary::Error) {
249
    for value, i in values { set values[i] = try val(value, plan, names); }
250
}
251
252
/// Copy immutable call arguments only when they contain a symbol to rename.
253
fn callArgs(values: *[il::Val], plan: *Plan, names: *Names, arena: *mut alloc::Arena) -> *[il::Val] throws (il::binary::Error) {
254
    for value in values {
255
        match value {
256
            case il::Val::DataSym(_), il::Val::FnAddr(_) => {
257
                let copied = try storage(arena, @sizeOf(il::Val), @alignOf(il::Val), values.len) as *mut [il::Val];
258
                for argument, i in values { set copied[i] = try val(argument, plan, names); }
259
                return copied;
260
            },
261
            else => {},
262
        }
263
    }
264
    return values;
265
}
266
267
/// Rewrite every symbolic operand without reconstructing any source-language types.
268
fn instruction(item: il::Instr, plan: *Plan, names: *Names, arena: *mut alloc::Arena) -> il::Instr throws (il::binary::Error) {
269
    match item {
270
        case il::Instr::Reserve { dst, size, alignment } => return il::Instr::Reserve { dst, size: try val(size, plan, names), alignment },
271
        case il::Instr::Store { typ, src, dst, offset } => return il::Instr::Store { typ, src: try val(src, plan, names), dst, offset },
272
        case il::Instr::Blit { dst, src, size, alignment } => return il::Instr::Blit { dst, src, size: try val(size, plan, names), alignment },
273
        case il::Instr::Copy { dst, val: value } => return il::Instr::Copy { dst, val: try val(value, plan, names) },
274
        case il::Instr::BinOp { op, typ, dst, a, b } => return il::Instr::BinOp { op, typ, dst, a: try val(a, plan, names), b: try val(b, plan, names) },
275
        case il::Instr::UnOp { op, typ, dst, a } => return il::Instr::UnOp { op, typ, dst, a: try val(a, plan, names) },
276
        case il::Instr::Zext { typ, dst, val: value } => return il::Instr::Zext { typ, dst, val: try val(value, plan, names) },
277
        case il::Instr::Sext { typ, dst, val: value } => return il::Instr::Sext { typ, dst, val: try val(value, plan, names) },
278
        case il::Instr::Call { retTy, dst, func, args: values } => {
279
            return il::Instr::Call { retTy, dst, func: try val(func, plan, names), args: try callArgs(values, plan, names, arena) };
280
        }
281
        case il::Instr::Ret { val: value } => {
282
            if let v = value { return il::Instr::Ret { val: try val(v, plan, names) }; }
283
            return item;
284
        }
285
        case il::Instr::Jmp { args: values, .. } => { try args(values, plan, names); return item; }
286
        case il::Instr::Br { op, typ, a, b, thenTarget, thenArgs, elseTarget, elseArgs } => {
287
            try args(thenArgs, plan, names); try args(elseArgs, plan, names);
288
            return il::Instr::Br { op, typ, a: try val(a, plan, names), b: try val(b, plan, names), thenTarget, thenArgs, elseTarget, elseArgs };
289
        }
290
        case il::Instr::Switch { val: value, defaultTarget, defaultArgs, cases } => {
291
            try args(defaultArgs, plan, names);
292
            for c in cases { try args(c.args, plan, names); }
293
            return il::Instr::Switch { val: try val(value, plan, names), defaultTarget, defaultArgs, cases };
294
        }
295
        case il::Instr::Ecall { dst, num, a0, a1, a2, a3 } => return il::Instr::Ecall {
296
            dst, num: try val(num, plan, names), a0: try val(a0, plan, names), a1: try val(a1, plan, names),
297
            a2: try val(a2, plan, names), a3: try val(a3, plan, names),
298
        },
299
        case il::Instr::Load { .. }, il::Instr::Sload { .. }, il::Instr::Unreachable,
300
             il::Instr::Ebreak, il::Instr::MemoryFence => return item,
301
    }
302
}
303
304
/// Rename a decoder-owned body in place; its storage survives until emission ends.
305
export fn function(function: *il::Fn, index: u32, plan: *Plan, names: *Names, arena: *mut alloc::Arena) -> il::Fn throws (il::binary::Error) {
306
    let mut result = *function;
307
    set result.name = names.functions[index];
308
    for block in result.blocks {
309
        for item, i in block.instrs { set block.instrs[i] = try instruction(item, plan, names, arena); }
310
    }
311
    return result;
312
}