compiler/
lib/
examples/
std/
arch/
char/
collections/
graph/
lang/
alloc/
ast/
gen/
il/
binary/
binary.rad
5.9 KiB
printer.rad
16.0 KiB
published.rad
13.4 KiB
publishedTests.rad
8.7 KiB
tests.rad
14.7 KiB
module/
parser/
resolver/
scanner/
alloc.rad
7.1 KiB
ast.rad
26.9 KiB
gen.rad
513 B
il.rad
20.4 KiB
lower.rad
321.7 KiB
module.rad
17.3 KiB
package.rad
1.3 KiB
parser.rad
92.2 KiB
resolver.rad
511.1 KiB
scanner.rad
17.9 KiB
sexpr.rad
6.7 KiB
strings.rad
2.2 KiB
types.rad
1.6 KiB
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.3 KiB
graph.rad
4.3 KiB
intrinsics.rad
467 B
io.rad
1.7 KiB
lang.rad
276 B
mem.rad
2.3 KiB
sys.rad
179 B
testing.rad
2.4 KiB
tests.rad
15.7 KiB
vec.rad
3.2 KiB
std.rad
299 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CELL_PERMISSIONS
6.8 KiB
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
5.4 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.5 KiB
std.lib.test
808 B
lib/std/lang/il/published.rad
raw
| 1 | //! Immutable, region-bound IL for analysis and instruction selection. |
| 2 | //! |
| 3 | //! Publication owns instruction and analysis tables. Original node pointers |
| 4 | //! identify source nodes. Symbol names use immutable IL symbol storage. |
| 5 | |
| 6 | use std::lang::alloc; |
| 7 | use std::lang::il; |
| 8 | |
| 9 | /// Largest element count represented by an analysis slice. |
| 10 | constant MAX_ELEMENTS: u32 = 0xffffffff; |
| 11 | |
| 12 | /// Published function tables within one analysis lifetime. |
| 13 | export record Function: 'view + Copy { |
| 14 | /// Immutable symbol name. |
| 15 | name: *[u8], |
| 16 | /// Whether the function makes no calls. |
| 17 | isLeaf: bool, |
| 18 | /// Function parameter definitions. |
| 19 | params: &'view [il::Param], |
| 20 | /// Blocks in source order. |
| 21 | blocks: &'view [Block 'view], |
| 22 | } |
| 23 | |
| 24 | /// Read-only block tables and their original identity. |
| 25 | export record Block: 'view + Copy { |
| 26 | /// Original block pointer for imported code, used only for identity comparisons. |
| 27 | identity: ?*unsafe il::Block, |
| 28 | /// Block parameter definitions. |
| 29 | params: &'view [il::Param], |
| 30 | /// Published instruction descriptors. |
| 31 | instructions: &'view [Instruction 'view], |
| 32 | /// Source locations in instruction order, or an empty slice without debug data. |
| 33 | locations: &'view [il::SrcLoc], |
| 34 | /// Loop nesting depth for cost weighting. |
| 35 | loopDepth: u32, |
| 36 | } |
| 37 | |
| 38 | /// An instruction and its immutable register-allocation inputs. |
| 39 | export opaque record Instruction: 'view + Copy { |
| 40 | /// Original instruction pointer for imported code, used only for identity comparisons. |
| 41 | identity: ?*unsafe il::Instr, |
| 42 | /// Instruction operands with owned variable-length tables. |
| 43 | code: &'view Code 'view, |
| 44 | /// Destination register, when the instruction defines a value. |
| 45 | destination: ?il::Reg, |
| 46 | /// Whether the instruction has call-clobber semantics. |
| 47 | isCall: bool, |
| 48 | /// Source registers in operand order, including repeated uses. |
| 49 | operands: &'view [il::Reg], |
| 50 | /// Successor block positions in branch order. |
| 51 | targets: &'view [u32], |
| 52 | } |
| 53 | |
| 54 | /// Instruction data with region-bound call and branch tables. |
| 55 | export union Code: 'view + Copy { |
| 56 | /// An instruction whose operands are all inline. |
| 57 | Fixed(il::Instr), |
| 58 | /// An unconditional branch and its argument values. |
| 59 | Jmp { |
| 60 | /// Destination block position. |
| 61 | target: u32, |
| 62 | /// Values supplied to the destination parameters. |
| 63 | args: &'view [il::Val], |
| 64 | }, |
| 65 | /// A comparison and its two outgoing edges. |
| 66 | Br { |
| 67 | /// Comparison operation. |
| 68 | op: il::CmpOp, |
| 69 | /// Comparison width. |
| 70 | typ: il::Type, |
| 71 | /// Left comparison operand. |
| 72 | a: il::Val, |
| 73 | /// Right comparison operand. |
| 74 | b: il::Val, |
| 75 | /// Destination when the comparison succeeds. |
| 76 | thenTarget: u32, |
| 77 | /// Values for the successful edge. |
| 78 | thenArgs: &'view [il::Val], |
| 79 | /// Destination when the comparison fails. |
| 80 | elseTarget: u32, |
| 81 | /// Values for the failed edge. |
| 82 | elseArgs: &'view [il::Val], |
| 83 | }, |
| 84 | /// A value-based branch table. |
| 85 | Switch { |
| 86 | /// Value to compare with the cases. |
| 87 | val: il::Val, |
| 88 | /// Destination when no case matches. |
| 89 | defaultTarget: u32, |
| 90 | /// Values supplied to the default destination. |
| 91 | defaultArgs: &'view [il::Val], |
| 92 | /// Cases in comparison order. |
| 93 | cases: &'view [SwitchCase 'view], |
| 94 | }, |
| 95 | /// A function call and its argument values. |
| 96 | Call { |
| 97 | /// Return value width. |
| 98 | retTy: il::Type, |
| 99 | /// Register for the return value, if used. |
| 100 | dst: ?il::Reg, |
| 101 | /// Function symbol or address. |
| 102 | func: il::Val, |
| 103 | /// Values supplied to the function. |
| 104 | args: &'view [il::Val], |
| 105 | }, |
| 106 | } |
| 107 | |
| 108 | /// One switch comparison and its outgoing edge. |
| 109 | export record SwitchCase: 'view + Copy { |
| 110 | /// Value that selects this case. |
| 111 | value: i64, |
| 112 | /// Destination block position. |
| 113 | target: u32, |
| 114 | /// Values supplied to the destination parameters. |
| 115 | args: &'view [il::Val], |
| 116 | } |
| 117 | |
| 118 | /// Copy IL tables from valid IL into session-owned immutable storage. |
| 119 | /// All source tables and nested arrays must remain valid during this call. |
| 120 | /// Original node pointers are identity values, not published storage borrows. |
| 121 | export unsafe fn publish 'view (function: &il::Fn, storage: &Session 'view) |
| 122 | -> Function 'view throws (alloc::AllocError) |
| 123 | { |
| 124 | let params = try storage.copy(function.params); |
| 125 | let blocks: &[il::Block] = function.blocks; |
| 126 | if blocks.len == 0 { |
| 127 | let none: [Block 'view; 0] = []; |
| 128 | return Function 'view { name: function.name, isLeaf: function.isLeaf, |
| 129 | params, blocks: try storage.copy(&none[..]) }; |
| 130 | } |
| 131 | let first = try publishBlock(&blocks[0], storage); |
| 132 | let views = try storage.fill(first, blocks.len); |
| 133 | for i in 1..blocks.len { |
| 134 | set views[i] = try publishBlock(&blocks[i], storage); |
| 135 | } |
| 136 | return Function 'view { name: function.name, isLeaf: function.isLeaf, |
| 137 | params, blocks: &views[..] }; |
| 138 | } |
| 139 | |
| 140 | /// Copy block definitions and each instruction's analysis tables. |
| 141 | unsafe fn publishBlock 'view (block: &il::Block, storage: &Session 'view) |
| 142 | -> Block 'view throws (alloc::AllocError) |
| 143 | { |
| 144 | let params = try storage.copy(block.params); |
| 145 | let locations = try storage.copy(block.locs); |
| 146 | let instructions: &[il::Instr] = block.instrs; |
| 147 | if instructions.len == 0 { |
| 148 | let none: [Instruction 'view; 0] = []; |
| 149 | return Block 'view { identity: block as *unsafe il::Block, params, |
| 150 | instructions: try storage.copy(&none[..]), locations, loopDepth: block.loopDepth }; |
| 151 | } |
| 152 | let first = try publishInstruction(&instructions[0], storage); |
| 153 | let views = try storage.fill(first, instructions.len); |
| 154 | for i in 1..instructions.len { |
| 155 | set views[i] = try publishInstruction(&instructions[i], storage); |
| 156 | } |
| 157 | return Block 'view { identity: block as *unsafe il::Block, params, |
| 158 | instructions: &views[..], locations, loopDepth: block.loopDepth }; |
| 159 | } |
| 160 | |
| 161 | /// Import an instruction and construct its checked analysis tables. |
| 162 | unsafe fn publishInstruction 'view (source: &il::Instr, storage: &Session 'view) |
| 163 | -> Instruction 'view throws (alloc::AllocError) |
| 164 | { |
| 165 | let code = try storage.new(try publishCode(source, storage)); |
| 166 | return try buildInstruction(code, source as *unsafe il::Instr, storage); |
| 167 | } |
| 168 | |
| 169 | /// Construct instruction metadata from immutable, region-owned operands. |
| 170 | /// The source identity is retained as a value and is never dereferenced. |
| 171 | export fn buildInstruction 'view ( |
| 172 | code: &'view Code 'view, |
| 173 | identity: ?*unsafe il::Instr, |
| 174 | storage: &Session 'view |
| 175 | ) -> Instruction 'view throws (alloc::AllocError) { |
| 176 | let noTargets: [u32; 0] = []; |
| 177 | let mut targets: &'view [u32] = try storage.copy(&noTargets[..]); |
| 178 | let mut count: u64 = 0; |
| 179 | let mut destination: ?il::Reg = nil; |
| 180 | let mut isCall = false; |
| 181 | match code { |
| 182 | case Code::Fixed(fixed) => { |
| 183 | let cursor = il::registers(fixed); |
| 184 | assert not cursor.arguments, "buildInstruction: expected inline operands"; |
| 185 | set count = cursor.count as u64; |
| 186 | set destination = il::instrDst(*fixed); |
| 187 | set isCall = il::isCall(*fixed); |
| 188 | }, |
| 189 | case Code::Call { dst, func, args, .. } => { |
| 190 | let prefix = [*func]; |
| 191 | set count = registerCount(&prefix[..]) + registerCount(*args); |
| 192 | set destination = *dst; |
| 193 | set isCall = true; |
| 194 | }, |
| 195 | case Code::Jmp { target, args } => { |
| 196 | set count = registerCount(*args); |
| 197 | set targets = try storage.fill(*target, 1); |
| 198 | }, |
| 199 | case Code::Br { a, b, thenTarget, thenArgs, elseTarget, elseArgs, .. } => { |
| 200 | let prefix = [*a, *b]; |
| 201 | set count = registerCount(&prefix[..]) + registerCount(*thenArgs) + registerCount(*elseArgs); |
| 202 | let edges = try storage.fill(*thenTarget, 2); |
| 203 | set edges[1] = *elseTarget; |
| 204 | set targets = &edges[..]; |
| 205 | }, |
| 206 | case Code::Switch { val, defaultTarget, defaultArgs, cases } => { |
| 207 | let prefix = [*val]; |
| 208 | set count = registerCount(&prefix[..]) + registerCount(*defaultArgs); |
| 209 | if cases.len == MAX_ELEMENTS { |
| 210 | throw alloc::AllocError::OutOfMemory; |
| 211 | } |
| 212 | let edges = try storage.fill(*defaultTarget, cases.len + 1); |
| 213 | for item, i in *cases { |
| 214 | set edges[i + 1] = item.target; |
| 215 | set count += registerCount(item.args); |
| 216 | } |
| 217 | set targets = &edges[..]; |
| 218 | }, |
| 219 | } |
| 220 | if count > MAX_ELEMENTS as u64 { |
| 221 | throw alloc::AllocError::OutOfMemory; |
| 222 | } |
| 223 | let operands = try storage.fill(il::Reg { n: 0 }, count as u32); |
| 224 | let mut written: u32 = 0; |
| 225 | match code { |
| 226 | case Code::Fixed(fixed) => { |
| 227 | let cursor = il::registers(fixed); |
| 228 | for i in 0..cursor.count { |
| 229 | set operands[i] = cursor.fixed[i]; |
| 230 | } |
| 231 | set written = cursor.count; |
| 232 | }, |
| 233 | case Code::Call { func, args, .. } => { |
| 234 | let prefix = [*func]; |
| 235 | set written = copyRegisters(&prefix[..], operands, 0); |
| 236 | set written = copyRegisters(*args, operands, written); |
| 237 | }, |
| 238 | case Code::Jmp { args, .. } => set written = copyRegisters(*args, operands, 0), |
| 239 | case Code::Br { a, b, thenArgs, elseArgs, .. } => { |
| 240 | let prefix = [*a, *b]; |
| 241 | set written = copyRegisters(&prefix[..], operands, 0); |
| 242 | set written = copyRegisters(*thenArgs, operands, written); |
| 243 | set written = copyRegisters(*elseArgs, operands, written); |
| 244 | }, |
| 245 | case Code::Switch { val, defaultArgs, cases, .. } => { |
| 246 | let prefix = [*val]; |
| 247 | set written = copyRegisters(&prefix[..], operands, 0); |
| 248 | set written = copyRegisters(*defaultArgs, operands, written); |
| 249 | for item in *cases { |
| 250 | set written = copyRegisters(item.args, operands, written); |
| 251 | } |
| 252 | }, |
| 253 | } |
| 254 | assert written == operands.len, "buildInstruction: operand count changed"; |
| 255 | return Instruction 'view { identity, code, destination, isCall, |
| 256 | operands: &operands[..], targets }; |
| 257 | } |
| 258 | |
| 259 | /// Count source registers in one operand group, including repeated uses. |
| 260 | fn registerCount(values: &[il::Val]) -> u64 { |
| 261 | let mut count: u64 = 0; |
| 262 | for value in values { |
| 263 | if let case il::Val::Reg(_) = value { |
| 264 | set count += 1; |
| 265 | } |
| 266 | } |
| 267 | return count; |
| 268 | } |
| 269 | |
| 270 | /// Write source registers at an offset and return the next free position. |
| 271 | fn copyRegisters(values: &[il::Val], output: &mut [il::Reg], offset: u32) -> u32 { |
| 272 | let mut next = offset; |
| 273 | for value in values { |
| 274 | if let case il::Val::Reg(reg) = value { |
| 275 | set output[next] = reg; |
| 276 | set next += 1; |
| 277 | } |
| 278 | } |
| 279 | return next; |
| 280 | } |
| 281 | |
| 282 | /// Copy the variable-length operands of an instruction into the session. |
| 283 | unsafe fn publishCode 'view (source: &il::Instr, storage: &Session 'view) |
| 284 | -> Code 'view throws (alloc::AllocError) |
| 285 | { |
| 286 | match *source { |
| 287 | case il::Instr::Jmp { target, args } => { |
| 288 | return Code 'view::Jmp { target, args: try storage.copy(args) }; |
| 289 | }, |
| 290 | case il::Instr::Br { op, typ, a, b, thenTarget, thenArgs, elseTarget, elseArgs } => { |
| 291 | return Code 'view::Br { op, typ, a, b, thenTarget, |
| 292 | thenArgs: try storage.copy(thenArgs), elseTarget, |
| 293 | elseArgs: try storage.copy(elseArgs) }; |
| 294 | }, |
| 295 | case il::Instr::Call { retTy, dst, func, args } => { |
| 296 | return Code 'view::Call { retTy, dst, func, args: try storage.copy(args) }; |
| 297 | }, |
| 298 | case il::Instr::Switch { val, defaultTarget, defaultArgs, cases } => { |
| 299 | let defaultArgs = try storage.copy(defaultArgs); |
| 300 | let cases: &[il::SwitchCase] = cases; |
| 301 | if cases.len == 0 { |
| 302 | let none: [SwitchCase 'view; 0] = []; |
| 303 | return Code 'view::Switch { val, defaultTarget, defaultArgs, |
| 304 | cases: try storage.copy(&none[..]) }; |
| 305 | } |
| 306 | let first = SwitchCase 'view { value: cases[0].value, target: cases[0].target, |
| 307 | args: try storage.copy(cases[0].args) }; |
| 308 | let views = try storage.fill(first, cases.len); |
| 309 | for i in 1..cases.len { |
| 310 | set views[i] = SwitchCase 'view { value: cases[i].value, target: cases[i].target, |
| 311 | args: try storage.copy(cases[i].args) }; |
| 312 | } |
| 313 | return Code 'view::Switch { val, defaultTarget, defaultArgs, cases: &views[..] }; |
| 314 | }, |
| 315 | else => return Code 'view::Fixed(*source), |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | /// Return the instruction data with session-owned operand tables. |
| 320 | export fn code 'view (instruction: &Instruction 'view) -> &'view Code 'view { |
| 321 | return instruction.code; |
| 322 | } |
| 323 | |
| 324 | /// Return the original instruction pointer when the instruction was imported. |
| 325 | export fn identity 'view (instruction: &Instruction 'view) -> ?*unsafe il::Instr { |
| 326 | return instruction.identity; |
| 327 | } |
| 328 | |
| 329 | /// Return an instruction's destination register. |
| 330 | export fn destination 'view (instruction: &Instruction 'view) -> ?il::Reg { |
| 331 | return instruction.destination; |
| 332 | } |
| 333 | |
| 334 | /// Check whether an instruction has call-clobber semantics. |
| 335 | export fn isCall 'view (instruction: &Instruction 'view) -> bool { |
| 336 | return instruction.isCall; |
| 337 | } |
| 338 | |
| 339 | /// Borrow successor positions in branch order, including repeated targets. |
| 340 | export fn successors 'view (instruction: &Instruction 'view) -> &'view [u32] { |
| 341 | return instruction.targets; |
| 342 | } |
| 343 | |
| 344 | /// Borrow source registers in operand order, including repeated uses. |
| 345 | export fn registers 'view (instruction: &Instruction 'view) -> &'view [il::Reg] { |
| 346 | return instruction.operands; |
| 347 | } |