compiler/
kernel/
lib/
examples/
std/
arch/
char/
collections/
lang/
alloc/
ast/
gen/
il/
binary/
images/
graph.rad
15.5 KiB
layout.rad
7.4 KiB
native.rad
18.5 KiB
binary.rad
13.9 KiB
images.rad
10.6 KiB
printer.rad
14.7 KiB
module/
parser/
resolver/
scanner/
alloc.rad
4.3 KiB
ast.rad
23.3 KiB
gen.rad
513 B
il.rad
16.1 KiB
lower.rad
272.5 KiB
module.rad
13.2 KiB
package.rad
1.3 KiB
parser.rad
77.8 KiB
resolver.rad
308.6 KiB
scanner.rad
17.5 KiB
sexpr.rad
6.3 KiB
strings.rad
2.2 KiB
types.rad
286 B
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.1 KiB
intrinsics.rad
467 B
io.rad
1.3 KiB
lang.rad
276 B
mem.rad
2.2 KiB
sys.rad
179 B
testing.rad
2.4 KiB
tests.rad
15.2 KiB
vec.rad
3.2 KiB
std.rad
281 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
351 B
.gitsigners
112 B
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
3.8 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.4 KiB
std.lib.test
380 B
lib/std/lang/il/images/graph.rad
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 | } |