compiler: Publish checked register-allocation inputs
a359ca0dcdc69aec29168ff05130e416d91d903e38d7dae1f109353a7e693e2e
1 parent
e4713575
lib/std/arch/rv64.rad
+2 -1
| 309 | 309 | generator: &mut Generator, |
|
| 310 | 310 | func: &il::Fn, |
|
| 311 | 311 | config: ®alloc::TargetConfig, |
|
| 312 | 312 | storage: &Session 'scratch |
|
| 313 | 313 | ) throws (alloc::AllocError) { |
|
| 314 | - | let ralloc = try regalloc::allocate(func, config, storage); |
|
| 314 | + | let view = try il::published::publish(func, storage); |
|
| 315 | + | let ralloc = try regalloc::allocate(&view, config, storage); |
|
| 315 | 316 | isel::selectFn(&mut generator.e, &ralloc, func); |
|
| 316 | 317 | } |
|
| 317 | 318 | ||
| 318 | 319 | /// Record an alternate name for the next function emitted. |
|
| 319 | 320 | export fn recordFunctionAlias(generator: &mut Generator, name: *[u8]) { |
lib/std/arch/rv64/bounds.rad
+2 -1
| 158 | 158 | let func = il::Fn { |
|
| 159 | 159 | name: "p::pressure", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
|
| 160 | 160 | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut [], locs: &[], preds: &[], loopDepth: 0 }], |
|
| 161 | 161 | }; |
|
| 162 | 162 | let mut failed = false; |
|
| 163 | - | try regalloc::spill::analyze(&func, &live, 23, 11, 8, &bits) catch { |
|
| 163 | + | let view = try! il::published::publish(&func, &bits); |
|
| 164 | + | try regalloc::spill::analyze(&view, &live, 23, 11, 8, &bits) catch { |
|
| 164 | 165 | set failed = true; |
|
| 165 | 166 | }; |
|
| 166 | 167 | try testing::expect(failed); |
|
| 167 | 168 | } |
|
| 168 | 169 | } |
lib/std/lang/gen/regalloc.rad
+3 -3
| 17 | 17 | export mod liveness; |
|
| 18 | 18 | export mod flow; |
|
| 19 | 19 | export mod spill; |
|
| 20 | 20 | export mod assign; |
|
| 21 | 21 | ||
| 22 | - | use std::lang::il; |
|
| 22 | + | use std::lang::il::published; |
|
| 23 | 23 | use std::lang::alloc; |
|
| 24 | 24 | ||
| 25 | 25 | /// Target configuration for register allocation. |
|
| 26 | 26 | export record TargetConfig: Copy { |
|
| 27 | 27 | /// List of allocatable physical registers. |
| 47 | 47 | ||
| 48 | 48 | /// Run register allocation on a function. |
|
| 49 | 49 | /// |
|
| 50 | 50 | /// Returns a mapping from SSA registers to physical registers, plus |
|
| 51 | 51 | /// spill information. |
|
| 52 | - | export unsafe fn allocate 'scratch ( |
|
| 53 | - | func: &il::Fn, |
|
| 52 | + | export fn allocate 'input 'scratch ( |
|
| 53 | + | func: &published::Function 'input, |
|
| 54 | 54 | config: &TargetConfig, |
|
| 55 | 55 | storage: &Session 'scratch |
|
| 56 | 56 | ) -> AllocResult 'scratch throws (alloc::AllocError) { |
|
| 57 | 57 | // Phase 1: Liveness analysis. |
|
| 58 | 58 | let live = try liveness::analyze(func, storage); |
lib/std/lang/gen/regalloc/assign.rad
+13 -20
| 5 | 5 | //! |
|
| 6 | 6 | //! The IL is not modified. Instead, a mapping from SSA registers to physical |
|
| 7 | 7 | //! registers is produced for use by instruction selection. |
|
| 8 | 8 | ||
| 9 | 9 | use std::lang::il; |
|
| 10 | + | use std::lang::il::published; |
|
| 10 | 11 | use std::lang::alloc; |
|
| 11 | 12 | use std::lang::gen; |
|
| 12 | 13 | use std::lang::gen::bitset; |
|
| 13 | 14 | use std::lang::gen::regalloc::liveness; |
|
| 14 | 15 | use std::lang::gen::regalloc::spill; |
| 49 | 50 | assignments: &'step mut [?gen::Reg], |
|
| 50 | 51 | spillInfo: &'step spill::SpillInfo 'scratch, |
|
| 51 | 52 | } |
|
| 52 | 53 | ||
| 53 | 54 | /// Compute register assignment. |
|
| 54 | - | export unsafe fn assign 'scratch ( |
|
| 55 | - | func: &il::Fn, |
|
| 55 | + | export fn assign 'input 'scratch ( |
|
| 56 | + | func: &published::Function 'input, |
|
| 56 | 57 | live: &liveness::LiveInfo 'scratch, |
|
| 57 | 58 | spillInfo: &spill::SpillInfo 'scratch, |
|
| 58 | 59 | config: &super::TargetConfig, |
|
| 59 | 60 | storage: &Session 'scratch |
|
| 60 | 61 | ) -> AssignInfo 'scratch throws (alloc::AllocError) { |
|
| 61 | 62 | return try assignTables(func.params, func.blocks, live, spillInfo, config, storage); |
|
| 62 | 63 | } |
|
| 63 | 64 | ||
| 64 | 65 | /// Allocate register mappings for borrowed function parameter and block tables. |
|
| 65 | - | fn assignTables 'scratch ( |
|
| 66 | + | fn assignTables 'input 'scratch ( |
|
| 66 | 67 | params: &[il::Param], |
|
| 67 | - | blocks: &[il::Block], |
|
| 68 | + | blocks: &[published::Block 'input], |
|
| 68 | 69 | live: &liveness::LiveInfo 'scratch, |
|
| 69 | 70 | spillInfo: &spill::SpillInfo 'scratch, |
|
| 70 | 71 | config: &super::TargetConfig, |
|
| 71 | 72 | storage: &Session 'scratch, |
|
| 72 | 73 | ) -> AssignInfo 'scratch throws (alloc::AllocError) { |
| 119 | 120 | allocatable, |
|
| 120 | 121 | calleeSaved: config.calleeSaved, |
|
| 121 | 122 | assignments: assignmentsRef, |
|
| 122 | 123 | spillInfo: spillRef, |
|
| 123 | 124 | }; |
|
| 124 | - | unsafe { |
|
| 125 | - | assignBlock(block.params, block.instrs, &mut ctx, config); |
|
| 126 | - | } |
|
| 125 | + | assignBlock(block.params, block.instructions, &mut ctx, config); |
|
| 127 | 126 | } |
|
| 128 | 127 | } |
|
| 129 | 128 | // Compute bitmask of used callee-saved registers. |
|
| 130 | 129 | let mut usedCalleeSaved: u32 = 0; |
|
| 131 | 130 | for i in 0..maxReg { |
| 143 | 142 | usedCalleeSaved, |
|
| 144 | 143 | }; |
|
| 145 | 144 | } |
|
| 146 | 145 | ||
| 147 | 146 | /// Assign one block's source and destination registers in instruction order. |
|
| 148 | - | fn assignBlock 'scratch 'step ( |
|
| 147 | + | fn assignBlock 'input 'scratch 'step ( |
|
| 149 | 148 | params: &[il::Param], |
|
| 150 | - | instructions: &[il::Instr], |
|
| 149 | + | instructions: &[published::Instruction 'input], |
|
| 151 | 150 | ctx: &mut InstrCtx 'scratch 'step, |
|
| 152 | 151 | config: &super::TargetConfig, |
|
| 153 | 152 | ) where 'scratch: 'step { |
|
| 154 | 153 | // Record every operand before allocation. Only current-block operands |
|
| 155 | 154 | // query this table, so every read is initialized by this scan. |
|
| 156 | 155 | // Entries for other registers need not be cleared between blocks. |
|
| 157 | 156 | for i in 0..instructions.len { |
|
| 158 | 157 | let instr = &instructions[i]; |
|
| 159 | - | let mut registers = il::registers(instr); |
|
| 160 | - | unsafe { |
|
| 161 | - | while let reg = il::nextReg(&mut registers, instr) { |
|
| 162 | - | recordLastUse(reg, ctx.lastUse, i); |
|
| 163 | - | } |
|
| 158 | + | for reg in published::registers(instr) { |
|
| 159 | + | recordLastUse(reg, ctx.lastUse, i); |
|
| 164 | 160 | } |
|
| 165 | 161 | } |
|
| 166 | 162 | ||
| 167 | 163 | enterBlock(ctx.current, ctx.usedRegs, liveness::liveInRow(ctx.live, ctx.blockIdx), params, ctx.assignments, config, ctx.spillInfo); |
|
| 168 | 164 | ||
| 169 | 165 | // Process each instruction. |
|
| 170 | 166 | for i in 0..instructions.len { |
|
| 171 | 167 | let instr = &instructions[i]; |
|
| 172 | 168 | set ctx.instrIdx = i; |
|
| 173 | - | let mut registers = il::registers(instr); |
|
| 174 | - | unsafe { |
|
| 175 | - | while let reg = il::nextReg(&mut registers, instr) { |
|
| 176 | - | processInstrReg(reg, ctx); |
|
| 177 | - | } |
|
| 169 | + | for reg in published::registers(instr) { |
|
| 170 | + | processInstrReg(reg, ctx); |
|
| 178 | 171 | } |
|
| 179 | 172 | ||
| 180 | 173 | // Allocate destination. |
|
| 181 | - | if let dst = il::instrDst(*instr) { |
|
| 174 | + | if let dst = published::destination(instr) { |
|
| 182 | 175 | if dst.n < ctx.assignments.len and not spill::isSpilled(ctx.spillInfo, dst) { |
|
| 183 | 176 | set ctx.assignments[dst.n] = rallocReg(ctx.current, ctx.usedRegs, dst.n, ctx.allocatable, ctx.calleeSaved, ctx.spillInfo); |
|
| 184 | 177 | } |
|
| 185 | 178 | } |
|
| 186 | 179 | } |
lib/std/lang/gen/regalloc/flow.rad
+11 -30
| 1 | 1 | //! Owned control-flow topology for dataflow analysis. |
|
| 2 | 2 | ||
| 3 | 3 | use std::graph; |
|
| 4 | 4 | use std::lang::alloc; |
|
| 5 | - | use std::lang::il; |
|
| 6 | - | ||
| 7 | - | /// Largest successor count represented by a slice length. |
|
| 8 | - | constant MAX_SUCCESSORS: u32 = 0xffffffff; |
|
| 5 | + | use std::lang::il::published; |
|
| 9 | 6 | ||
| 10 | 7 | /// Stable block identity and private successor storage. |
|
| 11 | 8 | export opaque record Block: 's + Copy { |
|
| 12 | 9 | /// Graph capability identity. |
|
| 13 | 10 | token: graph::Node 's, |
| 23 | 20 | frozen: graph::Frozen 's, |
|
| 24 | 21 | /// Stable block handles in function order. |
|
| 25 | 22 | blocks: &'s [Block 's], |
|
| 26 | 23 | } |
|
| 27 | 24 | ||
| 28 | - | /// Copy successor topology from valid IL into session-owned storage. |
|
| 29 | - | /// Instruction and switch tables must remain valid during this call. |
|
| 30 | - | export unsafe fn snapshot 's (blocks: &[il::Block], storage: &Session 's) |
|
| 25 | + | /// Copy published successor topology into session-owned graph storage. |
|
| 26 | + | export fn snapshot 'input 's (blocks: &[published::Block 'input], storage: &Session 's) |
|
| 31 | 27 | -> Flow 's throws (alloc::AllocError) |
|
| 32 | 28 | { |
|
| 33 | 29 | let mut builder = try graph::new(storage); |
|
| 34 | 30 | let none: [Block 's; 0] = []; |
|
| 35 | 31 | let empty: &'s [Block 's] = try storage.copy(&none[..]); |
| 40 | 36 | let entries = try storage.fill(first, blocks.len); |
|
| 41 | 37 | for i in 1..blocks.len { |
|
| 42 | 38 | set entries[i] = try reserve(&mut builder, i, empty, storage); |
|
| 43 | 39 | } |
|
| 44 | 40 | for block, i in blocks { |
|
| 45 | - | let instructions: &[il::Instr] = block.instrs; |
|
| 41 | + | let instructions = block.instructions; |
|
| 46 | 42 | let mut links = empty; |
|
| 47 | 43 | if instructions.len > 0 { |
|
| 48 | - | let term = &instructions[instructions.len - 1]; |
|
| 49 | - | match term { |
|
| 50 | - | case il::Instr::Jmp { target, .. } => { |
|
| 51 | - | set links = try storage.fill(entries[*target], 1); |
|
| 52 | - | }, |
|
| 53 | - | case il::Instr::Br { thenTarget, elseTarget, .. } => { |
|
| 54 | - | let targets = try storage.fill(entries[*thenTarget], 2); |
|
| 55 | - | set targets[1] = entries[*elseTarget]; |
|
| 56 | - | set links = &targets[..]; |
|
| 57 | - | }, |
|
| 58 | - | case il::Instr::Switch { defaultTarget, cases, .. } => { |
|
| 59 | - | let cases: &[il::SwitchCase] = *cases; |
|
| 60 | - | if cases.len == MAX_SUCCESSORS { |
|
| 61 | - | throw alloc::AllocError::OutOfMemory; |
|
| 62 | - | } |
|
| 63 | - | let targets = try storage.fill(entries[*defaultTarget], cases.len + 1); |
|
| 64 | - | for item, j in cases { |
|
| 65 | - | set targets[j + 1] = entries[item.target]; |
|
| 66 | - | } |
|
| 67 | - | set links = &targets[..]; |
|
| 68 | - | }, |
|
| 69 | - | else => {}, |
|
| 44 | + | let targets = published::successors(&instructions[instructions.len - 1]); |
|
| 45 | + | if targets.len > 0 { |
|
| 46 | + | let successors = try storage.fill(entries[targets[0]], targets.len); |
|
| 47 | + | for j in 1..targets.len { |
|
| 48 | + | set successors[j] = entries[targets[j]]; |
|
| 49 | + | } |
|
| 50 | + | set links = &successors[..]; |
|
| 70 | 51 | } |
|
| 71 | 52 | } |
|
| 72 | 53 | try! define(&mut builder, entries[i], links); |
|
| 73 | 54 | } |
|
| 74 | 55 | return Flow 's { frozen: try! graph::freeze(builder), blocks: &entries[..] }; |
lib/std/lang/gen/regalloc/liveness.rad
+27 -39
| 25 | 25 | //! } |
|
| 26 | 26 | ||
| 27 | 27 | @test mod tests; |
|
| 28 | 28 | ||
| 29 | 29 | use std::lang::il; |
|
| 30 | + | use std::lang::il::published; |
|
| 30 | 31 | use std::lang::alloc; |
|
| 31 | 32 | use std::lang::gen::bitset; |
|
| 32 | 33 | use super::flow; |
|
| 33 | 34 | ||
| 34 | 35 | /// Largest register index whose required entry count fits in u32. |
| 51 | 52 | /// Maximum register number used. |
|
| 52 | 53 | maxReg: u32, |
|
| 53 | 54 | } |
|
| 54 | 55 | ||
| 55 | 56 | /// Compute liveness by growing live sets until no live-in set changes. |
|
| 56 | - | export unsafe fn analyze 'scratch (func: &il::Fn, storage: &Session 'scratch) -> LiveInfo 'scratch throws (alloc::AllocError) { |
|
| 57 | + | export fn analyze 'input 'scratch (func: &published::Function 'input, storage: &Session 'scratch) -> LiveInfo 'scratch throws (alloc::AllocError) { |
|
| 57 | 58 | return try analyzeTables(func.params, func.blocks, storage); |
|
| 58 | 59 | } |
|
| 59 | 60 | ||
| 60 | 61 | /// Allocate and compute live sets for borrowed function parameter and block tables. |
|
| 61 | - | fn analyzeTables 'scratch (params: &[il::Param], blocks: &[il::Block], storage: &Session 'scratch) -> LiveInfo 'scratch throws (alloc::AllocError) { |
|
| 62 | + | fn analyzeTables 'input 'scratch (params: &[il::Param], blocks: &[published::Block 'input], storage: &Session 'scratch) -> LiveInfo 'scratch throws (alloc::AllocError) { |
|
| 62 | 63 | let blockCount = blocks.len; |
|
| 63 | 64 | if blockCount == 0 { |
|
| 64 | 65 | let empty = try storage.fill(0 as u32, 0); |
|
| 65 | 66 | let frozen: &'scratch [u32] = &empty[..]; |
|
| 66 | 67 | return LiveInfo 'scratch { liveIn: frozen, liveOut: frozen, defs: frozen, uses: frozen, words: 0, blockCount: 0, maxReg: 0 }; |
|
| 67 | 68 | } |
|
| 68 | 69 | ||
| 69 | 70 | // Find max register number. |
|
| 70 | 71 | let mut maxReg: u32 = 0; |
|
| 71 | - | try updateExtent(params, &[], &mut maxReg); |
|
| 72 | + | for parameter in params { |
|
| 73 | + | try updateMaxReg(parameter.value, &mut maxReg); |
|
| 74 | + | } |
|
| 72 | 75 | for b in 0..blockCount { |
|
| 73 | 76 | let block = &blocks[b]; |
|
| 74 | - | unsafe { |
|
| 75 | - | try updateExtent(block.params, block.instrs, &mut maxReg); |
|
| 76 | - | } |
|
| 77 | + | try updateExtent(block.params, block.instructions, &mut maxReg); |
|
| 77 | 78 | } |
|
| 78 | 79 | // Allocate one contiguous word matrix for each set class. |
|
| 79 | 80 | let words = bitset::wordsFor(maxReg); |
|
| 80 | 81 | let count64 = blockCount as u64 * words as u64; |
|
| 81 | 82 | if count64 > 0xFFFFFFFF { |
| 88 | 89 | let uses = try storage.fill(0 as u32, count); |
|
| 89 | 90 | ||
| 90 | 91 | // Compute local defs and uses for each block. |
|
| 91 | 92 | for b in 0..blockCount { |
|
| 92 | 93 | let block = &blocks[b]; |
|
| 93 | - | unsafe { |
|
| 94 | - | computeLocalDefsUses(block.params, block.instrs, &mut defs[b * words..(b + 1) * words], &mut uses[b * words..(b + 1) * words]); |
|
| 95 | - | } |
|
| 96 | - | } |
|
| 97 | - | unsafe { |
|
| 98 | - | let topology = try flow::snapshot(blocks, storage); |
|
| 99 | - | propagateLiveness(&topology, blockCount, liveIn, liveOut, defs, uses, words); |
|
| 94 | + | computeLocalDefsUses(block.params, block.instructions, &mut defs[b * words..(b + 1) * words], &mut uses[b * words..(b + 1) * words]); |
|
| 100 | 95 | } |
|
| 96 | + | let topology = try flow::snapshot(blocks, storage); |
|
| 97 | + | propagateLiveness(&topology, blockCount, liveIn, liveOut, defs, uses, words); |
|
| 101 | 98 | return LiveInfo 'scratch { liveIn: &liveIn[..], liveOut: &liveOut[..], defs: &defs[..], uses: &uses[..], words, blockCount, maxReg }; |
|
| 102 | 99 | } |
|
| 103 | 100 | ||
| 104 | 101 | /// Propagate successor uses until every block's live-in row is stable. |
|
| 105 | 102 | fn propagateLiveness 'scratch ( |
| 151 | 148 | } |
|
| 152 | 149 | return changed; |
|
| 153 | 150 | } |
|
| 154 | 151 | ||
| 155 | 152 | /// Compute local defs and uses for a single block. |
|
| 156 | - | fn computeLocalDefsUses(params: &[il::Param], instructions: &[il::Instr], defs: &mut [u32], uses: &mut [u32]) { |
|
| 153 | + | fn computeLocalDefsUses 'input (params: &[il::Param], instructions: &[published::Instruction 'input], defs: &mut [u32], uses: &mut [u32]) { |
|
| 157 | 154 | for p in params { |
|
| 158 | 155 | bitset::put(defs, p.value.n); |
|
| 159 | 156 | } |
|
| 160 | 157 | for i in 0..instructions.len { |
|
| 161 | 158 | let instr = &instructions[i]; |
|
| 162 | - | let mut registers = il::registers(instr); |
|
| 163 | - | // Argument groups contain raw views into the function's IL storage. |
|
| 164 | - | unsafe { |
|
| 165 | - | while let reg = il::nextReg(&mut registers, instr) { |
|
| 166 | - | addUse(reg, defs, uses); |
|
| 167 | - | } |
|
| 159 | + | // Fixed operands precede the instruction's variable argument groups. |
|
| 160 | + | for reg in published::registers(instr) { |
|
| 161 | + | addUse(reg, defs, uses); |
|
| 168 | 162 | } |
|
| 169 | 163 | ||
| 170 | - | if let dst = il::instrDst(*instr) { |
|
| 164 | + | if let dst = published::destination(instr) { |
|
| 171 | 165 | bitset::put(defs, dst.n); |
|
| 172 | 166 | } |
|
| 173 | 167 | } |
|
| 174 | 168 | } |
|
| 175 | 169 |
| 179 | 173 | bitset::put(uses, reg.n); |
|
| 180 | 174 | } |
|
| 181 | 175 | } |
|
| 182 | 176 | ||
| 183 | 177 | /// Include all parameter, source, and destination registers in the entry count. |
|
| 184 | - | fn updateExtent(params: &[il::Param], instructions: &[il::Instr], max: &mut u32) throws (alloc::AllocError) { |
|
| 178 | + | fn updateExtent 'input (params: &[il::Param], instructions: &[published::Instruction 'input], max: &mut u32) throws (alloc::AllocError) { |
|
| 185 | 179 | for p in params { |
|
| 186 | 180 | try updateMaxReg(p.value, max); |
|
| 187 | 181 | } |
|
| 188 | 182 | for i in 0..instructions.len { |
|
| 189 | 183 | let instr = &instructions[i]; |
|
| 190 | - | let mut registers = il::registers(instr); |
|
| 191 | - | // Argument groups contain raw views into the function's IL storage. |
|
| 192 | - | unsafe { |
|
| 193 | - | while let reg = il::nextReg(&mut registers, instr) { |
|
| 194 | - | try updateMaxReg(reg, max); |
|
| 195 | - | } |
|
| 184 | + | // Fixed operands precede the instruction's variable argument groups. |
|
| 185 | + | for reg in published::registers(instr) { |
|
| 186 | + | try updateMaxReg(reg, max); |
|
| 196 | 187 | } |
|
| 197 | - | if let dst = il::instrDst(*instr) { |
|
| 188 | + | if let dst = published::destination(instr) { |
|
| 198 | 189 | try updateMaxReg(dst, max); |
|
| 199 | 190 | } |
|
| 200 | 191 | } |
|
| 201 | 192 | } |
|
| 202 | 193 |
| 215 | 206 | fn unionBlockLiveIn(target: u32, liveIn: &[u32], words: u32, liveOut: &mut [u32]) { |
|
| 216 | 207 | bitset::union_(liveOut, &liveIn[target * words..(target + 1) * words]); |
|
| 217 | 208 | } |
|
| 218 | 209 | ||
| 219 | 210 | /// Check if a register has any use after this instruction. |
|
| 220 | - | export unsafe fn hasLaterUse 'scratch (info: &LiveInfo 'scratch, func: &il::Fn, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool { |
|
| 211 | + | export fn hasLaterUse 'input 'scratch (info: &LiveInfo 'scratch, func: &published::Function 'input, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool { |
|
| 221 | 212 | let block = &func.blocks[blockIdx]; |
|
| 222 | 213 | ||
| 223 | 214 | if bitset::contains(liveOutRow(info, blockIdx), reg.n) { |
|
| 224 | 215 | return true; |
|
| 225 | 216 | } |
|
| 226 | - | return usesRegisterAfter(block.instrs, instrIdx, reg); |
|
| 217 | + | return usesRegisterAfter(block.instructions, instrIdx, reg); |
|
| 227 | 218 | } |
|
| 228 | 219 | ||
| 229 | 220 | /// Check if an instruction after the given index uses a specific register. |
|
| 230 | - | fn usesRegisterAfter(instructions: &[il::Instr], instrIdx: u32, reg: il::Reg) -> bool { |
|
| 221 | + | fn usesRegisterAfter 'input (instructions: &[published::Instruction 'input], instrIdx: u32, reg: il::Reg) -> bool { |
|
| 231 | 222 | for i in (instrIdx + 1)..instructions.len { |
|
| 232 | 223 | let instr = &instructions[i]; |
|
| 233 | - | let mut registers = il::registers(instr); |
|
| 234 | - | // Argument groups contain raw views into the function's IL storage. |
|
| 235 | - | unsafe { |
|
| 236 | - | while let source = il::nextReg(&mut registers, instr) { |
|
| 237 | - | if source.n == reg.n { |
|
| 238 | - | return true; |
|
| 239 | - | } |
|
| 224 | + | // Fixed operands precede the instruction's variable argument groups. |
|
| 225 | + | for source in published::registers(instr) { |
|
| 226 | + | if source.n == reg.n { |
|
| 227 | + | return true; |
|
| 240 | 228 | } |
|
| 241 | 229 | } |
|
| 242 | 230 | } |
|
| 243 | 231 | return false; |
|
| 244 | 232 | } |
lib/std/lang/gen/regalloc/liveness/tests.rad
+84 -30
| 1 | 1 | //! Tests for liveness analysis. |
|
| 2 | 2 | ||
| 3 | 3 | use std::testing; |
|
| 4 | 4 | use std::lang::alloc; |
|
| 5 | 5 | use std::lang::il; |
|
| 6 | + | use std::lang::il::published; |
|
| 6 | 7 | use std::lang::gen::bitset; |
|
| 7 | 8 | use std::lang::gen::regalloc::spill; |
|
| 8 | 9 | use std::lang::gen::regalloc::assign; |
|
| 9 | 10 | use std::lang::gen::regalloc; |
|
| 10 | 11 | use std::lang::gen::regalloc::flow; |
|
| 11 | 12 | ||
| 13 | + | /// Publish fixture inputs outside the arena whose analysis capacity is tested. |
|
| 14 | + | unsafe fn analyze 'scratch (function: &il::Fn, storage: &Session 'scratch) |
|
| 15 | + | -> super::LiveInfo 'scratch throws (alloc::AllocError) |
|
| 16 | + | { |
|
| 17 | + | static DATA: [u8; 65536] = [0; 65536]; |
|
| 18 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 19 | + | use arena as inputs in { |
|
| 20 | + | let view = try published::publish(function, &inputs); |
|
| 21 | + | return try super::analyze(&view, storage); |
|
| 22 | + | } |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// Publish fixture inputs outside the arena whose spill capacity is tested. |
|
| 26 | + | unsafe fn analyzeSpills 'scratch (function: &il::Fn, live: &super::LiveInfo 'scratch, |
|
| 27 | + | numRegs: u32, numCalleeSaved: u32, slotSize: u32, storage: &Session 'scratch) |
|
| 28 | + | -> spill::SpillInfo 'scratch throws (alloc::AllocError) |
|
| 29 | + | { |
|
| 30 | + | static DATA: [u8; 65536] = [0; 65536]; |
|
| 31 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 32 | + | use arena as inputs in { |
|
| 33 | + | let view = try published::publish(function, &inputs); |
|
| 34 | + | return try spill::analyze(&view, live, numRegs, numCalleeSaved, slotSize, storage); |
|
| 35 | + | } |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | /// Publish fixture inputs outside the arena whose assignment capacity is tested. |
|
| 39 | + | unsafe fn assignRegisters 'scratch (function: &il::Fn, live: &super::LiveInfo 'scratch, |
|
| 40 | + | spills: &spill::SpillInfo 'scratch, config: ®alloc::TargetConfig, storage: &Session 'scratch) |
|
| 41 | + | -> assign::AssignInfo 'scratch throws (alloc::AllocError) |
|
| 42 | + | { |
|
| 43 | + | static DATA: [u8; 65536] = [0; 65536]; |
|
| 44 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 45 | + | use arena as inputs in { |
|
| 46 | + | let view = try published::publish(function, &inputs); |
|
| 47 | + | return try assign::assign(&view, live, spills, config, storage); |
|
| 48 | + | } |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | /// Publish CFG fixture inputs outside the graph allocation test arena. |
|
| 52 | + | unsafe fn snapshot 'scratch (blocks: &[il::Block], storage: &Session 'scratch) |
|
| 53 | + | -> flow::Flow 'scratch throws (alloc::AllocError) |
|
| 54 | + | { |
|
| 55 | + | let function = il::Fn { name: "topology", params: &[], returnType: il::Type::W64, |
|
| 56 | + | isExtern: false, isLeaf: true, blocks: blocks as *unsafe [il::Block] }; |
|
| 57 | + | static DATA: [u8; 4096] = [0; 4096]; |
|
| 58 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 59 | + | use arena as inputs in { |
|
| 60 | + | let view = try published::publish(&function, &inputs); |
|
| 61 | + | return try flow::snapshot(view.blocks, storage); |
|
| 62 | + | } |
|
| 63 | + | } |
|
| 64 | + | ||
| 12 | 65 | /// Frozen topology owns successor tables and preserves cyclic block identities. |
|
| 13 | 66 | @test unsafe fn testOwnedFlowSnapshot() throws (testing::TestError) { |
|
| 14 | 67 | let mut cases = [il::SwitchCase { value: 1, target: 1, args: &mut [] }]; |
|
| 15 | 68 | let mut first = [il::Instr::Switch { val: il::Val::Imm(0), defaultTarget: 0, |
|
| 16 | 69 | defaultArgs: &mut [], cases: &mut cases[..] }]; |
|
| 17 | 70 | let mut second = [il::Instr::Jmp { target: 0, args: &mut [] }]; |
|
| 18 | 71 | let blocks = [block("first", &mut first[..]), block("second", &mut second[..])]; |
|
| 19 | 72 | static DATA: [u8; 2048] = [0; 2048]; |
|
| 20 | 73 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 21 | 74 | use arena as storage in { |
|
| 22 | - | let frozen = try! flow::snapshot(&blocks[..], &storage); |
|
| 75 | + | let frozen = try! snapshot(&blocks[..], &storage); |
|
| 23 | 76 | set cases[0].target = 0; |
|
| 24 | 77 | set first[0] = il::Instr::Ret { val: il::Val::Imm(0) }; |
|
| 25 | 78 | set second[0] = il::Instr::Ret { val: il::Val::Imm(0) }; |
|
| 26 | 79 | let successors = flow::successors(&frozen, 0); |
|
| 27 | 80 | assert successors.len == 2; |
| 42 | 95 | let mut succeeded = false; |
|
| 43 | 96 | for capacity in 0..257 { |
|
| 44 | 97 | static DATA: [u8; 256] = [0; 256]; |
|
| 45 | 98 | let mut arena = alloc::new(&mut DATA[..capacity]); |
|
| 46 | 99 | use arena as storage in { |
|
| 47 | - | if let frozen = try? flow::snapshot(&blocks[..], &storage) { |
|
| 100 | + | if let frozen = try? snapshot(&blocks[..], &storage) { |
|
| 48 | 101 | let links = flow::successors(&frozen, 0); |
|
| 49 | 102 | assert links.len == 1 and flow::index(links[0]) == 0; |
|
| 50 | 103 | set succeeded = true; |
|
| 51 | 104 | } else { |
|
| 52 | 105 | assert not succeeded; |
| 72 | 125 | let function = il::Fn { name: "empty", params: &[], returnType: il::Type::W64, |
|
| 73 | 126 | isExtern: false, isLeaf: true, blocks: &blocks[..count] }; |
|
| 74 | 127 | static DATA: [u8; 1024] = [0; 1024]; |
|
| 75 | 128 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 76 | 129 | use arena as analysis in { |
|
| 77 | - | let live = try! super::analyze(&function, &analysis); |
|
| 130 | + | let live = try! analyze(&function, &analysis); |
|
| 78 | 131 | assert live.blockCount == count; |
|
| 79 | 132 | assert live.maxReg == 0; |
|
| 80 | 133 | assert live.words == 0; |
|
| 81 | 134 | assert live.liveIn.len == 0 and live.liveOut.len == 0; |
|
| 82 | 135 | assert live.defs.len == 0 and live.uses.len == 0; |
|
| 83 | - | let spills = try! spill::analyze(&function, &live, 0, 0, 8, &analysis); |
|
| 136 | + | let spills = try! analyzeSpills(&function, &live, 0, 0, 8, &analysis); |
|
| 84 | 137 | assert spills.maxReg == 0; |
|
| 85 | 138 | assert spills.frameSize == 0; |
|
| 86 | 139 | assert spills.slots.len == 0 and spills.calleeClass.len == 0; |
|
| 87 | 140 | let config = regalloc::TargetConfig { allocatable: &[], argRegs: &[], calleeSaved: &[], slotSize: 8 }; |
|
| 88 | - | let assigned = try! assign::assign(&function, &live, &spills, &config, &analysis); |
|
| 141 | + | let assigned = try! assignRegisters(&function, &live, &spills, &config, &analysis); |
|
| 89 | 142 | assert assigned.assignments.len == 0; |
|
| 90 | 143 | assert assigned.usedCalleeSaved == 0; |
|
| 91 | 144 | } |
|
| 92 | 145 | } |
|
| 93 | 146 | } |
| 100 | 153 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 101 | 154 | static DATA: [u8; 16] = [0; 16]; |
|
| 102 | 155 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 103 | 156 | let mut failed = false; |
|
| 104 | 157 | use arena as analysis in { |
|
| 105 | - | try super::analyze(&function, &analysis) catch { |
|
| 158 | + | try analyze(&function, &analysis) catch { |
|
| 106 | 159 | set failed = true; |
|
| 107 | 160 | }; |
|
| 108 | 161 | } |
|
| 109 | 162 | assert failed; |
|
| 110 | 163 | } |
| 117 | 170 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 118 | 171 | static DATA: [u8; 128] = [0; 128]; |
|
| 119 | 172 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 120 | 173 | let mut failed = false; |
|
| 121 | 174 | use arena as analysis in { |
|
| 122 | - | let live = try! super::analyze(&function, &analysis); |
|
| 123 | - | try spill::analyze(&function, &live, 0, 0, 8, &analysis) catch { |
|
| 175 | + | let live = try! analyze(&function, &analysis); |
|
| 176 | + | try analyzeSpills(&function, &live, 0, 0, 8, &analysis) catch { |
|
| 124 | 177 | set failed = true; |
|
| 125 | 178 | }; |
|
| 126 | 179 | } |
|
| 127 | 180 | assert failed; |
|
| 128 | 181 | } |
| 146 | 199 | let function = il::Fn { name: "switch", params: &[], returnType: il::Type::W64, |
|
| 147 | 200 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 148 | 201 | static DATA: [u8; 8192] = [0; 8192]; |
|
| 149 | 202 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 150 | 203 | use arena as analysis in { |
|
| 151 | - | let live = try! super::analyze(&function, &analysis); |
|
| 204 | + | let live = try! analyze(&function, &analysis); |
|
| 152 | 205 | try check(super::liveOutRow(&live, 0), count > 0, count == 3, true); |
|
| 153 | 206 | try check(super::liveInRow(&live, 0), count > 0, count == 3, true); |
|
| 154 | 207 | try check(super::liveOutRow(&live, 4), false, false, false); |
|
| 155 | 208 | } |
|
| 156 | 209 | } |
| 172 | 225 | let function = il::Fn { name: "cycle", params: &[], returnType: il::Type::W64, |
|
| 173 | 226 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 174 | 227 | static DATA: [u8; 8192] = [0; 8192]; |
|
| 175 | 228 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 176 | 229 | use arena as analysis in { |
|
| 177 | - | let live = try! super::analyze(&function, &analysis); |
|
| 230 | + | let live = try! analyze(&function, &analysis); |
|
| 178 | 231 | for index in 0..blocks.len { |
|
| 179 | 232 | try check(super::liveInRow(&live, index), false, false, index > 0 and index < 4); |
|
| 180 | 233 | try check(super::liveOutRow(&live, index), false, false, index <> 1 and index < 4); |
|
| 181 | 234 | } |
|
| 182 | 235 | } |
| 202 | 255 | let function = il::Fn { name: "later", params: ¶ms[..], returnType: il::Type::W64, |
|
| 203 | 256 | isExtern: false, isLeaf: false, blocks: &blocks[..] }; |
|
| 204 | 257 | static DATA: [u8; 8192] = [0; 8192]; |
|
| 205 | 258 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 206 | 259 | use arena as analysis in { |
|
| 207 | - | let live = try! super::analyze(&function, &analysis); |
|
| 260 | + | let live = try! analyze(&function, &analysis); |
|
| 261 | + | let view = try! published::publish(&function, &analysis); |
|
| 208 | 262 | for index in 0..entry.len { |
|
| 209 | - | assert super::hasLaterUse(&live, &function, 0, index, outgoing); |
|
| 210 | - | assert super::hasLaterUse(&live, &function, 0, index, argument) == (index == 0); |
|
| 211 | - | assert super::hasLaterUse(&live, &function, 0, index, result) == (index < 2); |
|
| 212 | - | assert not super::hasLaterUse(&live, &function, 0, index, unused); |
|
| 263 | + | assert super::hasLaterUse(&live, &view, 0, index, outgoing); |
|
| 264 | + | assert super::hasLaterUse(&live, &view, 0, index, argument) == (index == 0); |
|
| 265 | + | assert super::hasLaterUse(&live, &view, 0, index, result) == (index < 2); |
|
| 266 | + | assert not super::hasLaterUse(&live, &view, 0, index, unused); |
|
| 213 | 267 | } |
|
| 214 | - | assert not super::hasLaterUse(&live, &function, 1, 0, outgoing); |
|
| 268 | + | assert not super::hasLaterUse(&live, &view, 1, 0, outgoing); |
|
| 215 | 269 | } |
|
| 216 | 270 | } |
|
| 217 | 271 | ||
| 218 | 272 | /// Local uses precede definitions, and block parameters define their registers. |
|
| 219 | 273 | @test unsafe fn testLocalDefinitionOrder() throws (testing::TestError) { |
| 233 | 287 | let function = il::Fn { name: "local", params: &[], returnType: il::Type::W64, |
|
| 234 | 288 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 235 | 289 | static DATA: [u8; 8192] = [0; 8192]; |
|
| 236 | 290 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 237 | 291 | use arena as analysis in { |
|
| 238 | - | let live = try! super::analyze(&function, &analysis); |
|
| 292 | + | let live = try! analyze(&function, &analysis); |
|
| 239 | 293 | try check(&live.defs[..live.words], true, true, true); |
|
| 240 | 294 | try check(&live.uses[..live.words], true, false, false); |
|
| 241 | 295 | try check(super::liveInRow(&live, 0), true, false, false); |
|
| 242 | 296 | try check(&live.defs[live.words..], false, false, false); |
|
| 243 | 297 | try check(&live.uses[live.words..], false, false, false); |
| 257 | 311 | let function = il::Fn { name: "extent", params: &[], returnType: il::Type::W64, |
|
| 258 | 312 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 259 | 313 | static DATA: [u8; 16384] = [0; 16384]; |
|
| 260 | 314 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 261 | 315 | use arena as analysis in { |
|
| 262 | - | let live = try super::analyze(&function, &analysis) catch { |
|
| 316 | + | let live = try analyze(&function, &analysis) catch { |
|
| 263 | 317 | throw testing::TestError::Failed; |
|
| 264 | 318 | }; |
|
| 265 | 319 | assert live.maxReg == index + 2; |
|
| 266 | 320 | assert bitset::contains(super::liveInRow(&live, 0), source.n); |
|
| 267 | 321 | assert bitset::contains(&live.defs[..], destination.n); |
| 292 | 346 | let function = il::Fn { name: "argument", params: &[], returnType: il::Type::W64, |
|
| 293 | 347 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 294 | 348 | static DATA: [u8; 32768] = [0; 32768]; |
|
| 295 | 349 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 296 | 350 | use arena as analysis in { |
|
| 297 | - | let live = try super::analyze(&function, &analysis) catch { |
|
| 351 | + | let live = try analyze(&function, &analysis) catch { |
|
| 298 | 352 | assert index == 0xffffffff; |
|
| 299 | 353 | continue; |
|
| 300 | 354 | }; |
|
| 301 | 355 | assert index <> 0xffffffff; |
|
| 302 | 356 | assert live.maxReg == index + 1; |
| 318 | 372 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 319 | 373 | static DATA: [u8; 1024] = [0; 1024]; |
|
| 320 | 374 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 321 | 375 | let mut failed = false; |
|
| 322 | 376 | use arena as analysis in { |
|
| 323 | - | try super::analyze(&function, &analysis) catch { |
|
| 377 | + | try analyze(&function, &analysis) catch { |
|
| 324 | 378 | set failed = true; |
|
| 325 | 379 | }; |
|
| 326 | 380 | } |
|
| 327 | 381 | assert failed; |
|
| 328 | 382 | assert arena.offset == 0; |
| 354 | 408 | let function = il::Fn { name: "offsets", params: &[], returnType: il::Type::W64, |
|
| 355 | 409 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 356 | 410 | static DATA: [u8; 16384] = [0; 16384]; |
|
| 357 | 411 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 358 | 412 | use arena as analysis in { |
|
| 359 | - | let live = try! super::analyze(&function, &analysis); |
|
| 360 | - | let info = try spill::analyze(&function, &live, 0, 0, size, &analysis) catch { |
|
| 413 | + | let live = try! analyze(&function, &analysis); |
|
| 414 | + | let info = try analyzeSpills(&function, &live, 0, 0, size, &analysis) catch { |
|
| 361 | 415 | assert size == 0x2aaaaaab; |
|
| 362 | 416 | continue; |
|
| 363 | 417 | }; |
|
| 364 | 418 | assert size <= 0x2aaaaaaa; |
|
| 365 | 419 | assert info.slots[0] == 0; |
| 383 | 437 | let function = il::Fn { name: "frame", params: &[], returnType: il::Type::W64, |
|
| 384 | 438 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 385 | 439 | static DATA: [u8; 4096] = [0; 4096]; |
|
| 386 | 440 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 387 | 441 | use arena as analysis in { |
|
| 388 | - | let live = try! super::analyze(&function, &analysis); |
|
| 389 | - | let info = try spill::analyze(&function, &live, 0, 0, size, &analysis) catch { |
|
| 442 | + | let live = try! analyze(&function, &analysis); |
|
| 443 | + | let info = try analyzeSpills(&function, &live, 0, 0, size, &analysis) catch { |
|
| 390 | 444 | assert size > 0x7fffffff; |
|
| 391 | 445 | continue; |
|
| 392 | 446 | }; |
|
| 393 | 447 | assert size == 0x7fffffff; |
|
| 394 | 448 | assert info.frameSize == 2147483647; |
| 419 | 473 | let function = il::Fn { name: "weighted", params: &[], returnType: il::Type::W64, |
|
| 420 | 474 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 421 | 475 | static DATA: [u8; 8192] = [0; 8192]; |
|
| 422 | 476 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 423 | 477 | use arena as analysis in { |
|
| 424 | - | let live = try! super::analyze(&function, &analysis); |
|
| 425 | - | let info = try! spill::analyze(&function, &live, 1, 0, 8, &analysis); |
|
| 478 | + | let live = try! analyze(&function, &analysis); |
|
| 479 | + | let info = try! analyzeSpills(&function, &live, 1, 0, 8, &analysis); |
|
| 426 | 480 | assert spill::isSpilled(&info, first) == (depth == 0); |
|
| 427 | 481 | assert spill::isSpilled(&info, second) == (depth > 0); |
|
| 428 | 482 | assert not spill::isSpilled(&info, temporary); |
|
| 429 | 483 | assert info.frameSize == 8; |
|
| 430 | 484 | } |
| 455 | 509 | isExtern: false, isLeaf: false, blocks: &blocks[..] }; |
|
| 456 | 510 | for capacity in [0 as u32, 1, 2] { |
|
| 457 | 511 | static DATA: [u8; 8192] = [0; 8192]; |
|
| 458 | 512 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 459 | 513 | use arena as analysis in { |
|
| 460 | - | let live = try! super::analyze(&function, &analysis); |
|
| 461 | - | let info = try! spill::analyze(&function, &live, 8, capacity, 8, &analysis); |
|
| 514 | + | let live = try! analyze(&function, &analysis); |
|
| 515 | + | let info = try! analyzeSpills(&function, &live, 8, capacity, 8, &analysis); |
|
| 462 | 516 | assert spill::isSpilled(&info, first) == (capacity < 2); |
|
| 463 | 517 | assert spill::isSpilled(&info, second) == (capacity == 0); |
|
| 464 | 518 | assert not spill::isSpilled(&info, result); |
|
| 465 | 519 | assert not spill::isSpilled(&info, sum); |
|
| 466 | 520 | assert not spill::isSpilled(&info, total); |
| 489 | 543 | set function.params = ¶meters[..]; |
|
| 490 | 544 | } |
|
| 491 | 545 | static DATA: [u8; 16384] = [0; 16384]; |
|
| 492 | 546 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 493 | 547 | use arena as analysis in { |
|
| 494 | - | let live = try super::analyze(&function, &analysis) catch { |
|
| 548 | + | let live = try analyze(&function, &analysis) catch { |
|
| 495 | 549 | assert index == 0xffffffff; |
|
| 496 | 550 | continue; |
|
| 497 | 551 | }; |
|
| 498 | 552 | assert index == 16384; |
|
| 499 | 553 | assert live.maxReg == index + 1; |
| 529 | 583 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 530 | 584 | static DATA: [u8; 8192] = [0; 8192]; |
|
| 531 | 585 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 532 | 586 | let mut answer: u32 = 0; |
|
| 533 | 587 | use arena as analysis in { |
|
| 534 | - | let live = try! super::analyze(&function, &analysis); |
|
| 588 | + | let live = try! analyze(&function, &analysis); |
|
| 535 | 589 | try testing::expect(live.blockCount == 4); |
|
| 536 | 590 | try testing::expect(live.maxReg == 67); |
|
| 537 | 591 | try testing::expect(live.words == 3); |
|
| 538 | 592 | try check(super::liveInRow(&live, 0), false, false, false); |
|
| 539 | 593 | try check(super::liveOutRow(&live, 0), true, false, false); |
lib/std/lang/gen/regalloc/spill.rad
+20 -29
| 29 | 29 | //! |
|
| 30 | 30 | //! * Spill slot assignments: SSA reg -> stack offset, `-1` if not spilled. |
|
| 31 | 31 | //! * Total frame size needed for spills. |
|
| 32 | 32 | ||
| 33 | 33 | use std::lang::il; |
|
| 34 | + | use std::lang::il::published; |
|
| 34 | 35 | use std::lang::alloc; |
|
| 35 | 36 | use std::lang::gen::bitset; |
|
| 36 | 37 | use std::lang::gen::regalloc::liveness; |
|
| 37 | 38 | ||
| 38 | 39 | /// Maximum number of candidates for spill sorting. |
| 73 | 74 | reg: u32, |
|
| 74 | 75 | cost: u32, |
|
| 75 | 76 | } |
|
| 76 | 77 | ||
| 77 | 78 | /// Analyze a function and determine which values need spill slots. |
|
| 78 | - | export unsafe fn analyze 'scratch ( |
|
| 79 | - | func: &il::Fn, |
|
| 79 | + | export fn analyze 'input 'scratch ( |
|
| 80 | + | func: &published::Function 'input, |
|
| 80 | 81 | live: &liveness::LiveInfo 'scratch, |
|
| 81 | 82 | numRegs: u32, |
|
| 82 | 83 | numCalleeSaved: u32, |
|
| 83 | 84 | slotSize: u32, |
|
| 84 | 85 | storage: &Session 'scratch |
|
| 85 | 86 | ) -> SpillInfo 'scratch throws (alloc::AllocError) { |
|
| 86 | 87 | return try analyzeBlocks(func.blocks, live, numRegs, numCalleeSaved, slotSize, storage); |
|
| 87 | 88 | } |
|
| 88 | 89 | ||
| 89 | 90 | /// Allocate spill storage and analyze pressure over a borrowed block table. |
|
| 90 | - | fn analyzeBlocks 'scratch ( |
|
| 91 | - | blocks: &[il::Block], |
|
| 91 | + | fn analyzeBlocks 'input 'scratch ( |
|
| 92 | + | blocks: &[published::Block 'input], |
|
| 92 | 93 | live: &liveness::LiveInfo 'scratch, |
|
| 93 | 94 | numRegs: u32, |
|
| 94 | 95 | numCalleeSaved: u32, |
|
| 95 | 96 | slotSize: u32, |
|
| 96 | 97 | storage: &Session 'scratch, |
| 110 | 111 | // Allocate cost array. |
|
| 111 | 112 | let costs = try storage.fill(SpillCost { defs: 0, uses: 0 }, maxReg); |
|
| 112 | 113 | // Phase 1: Calculate spill costs. |
|
| 113 | 114 | for b in 0..blocks.len { |
|
| 114 | 115 | let block = &blocks[b]; |
|
| 115 | - | unsafe { |
|
| 116 | - | fillCosts(block.params, block.instrs, block.loopDepth, costs); |
|
| 117 | - | } |
|
| 116 | + | fillCosts(block.params, block.instructions, block.loopDepth, costs); |
|
| 118 | 117 | } |
|
| 119 | 118 | ||
| 120 | 119 | // Phase 2: Find values that exceed register pressure. |
|
| 121 | 120 | let spilled = try bitset::allocate(storage, maxReg); |
|
| 122 | 121 | let calleeClass = try bitset::allocate(storage, maxReg); |
| 126 | 125 | let block = &blocks[b]; |
|
| 127 | 126 | ||
| 128 | 127 | // Start with live-out set. |
|
| 129 | 128 | bitset::copy(scratch, liveness::liveOutRow(live, b)); |
|
| 130 | 129 | ||
| 131 | - | unsafe { |
|
| 132 | - | try limitBlockPressure(block.instrs, scratch, spilled, costs, calleeClass, numRegs, numCalleeSaved); |
|
| 133 | - | } |
|
| 130 | + | try limitBlockPressure(block.instructions, scratch, spilled, costs, calleeClass, numRegs, numCalleeSaved); |
|
| 134 | 131 | } |
|
| 135 | 132 | ||
| 136 | 133 | let frameSize = try finishSpills(slots, spilled, calleeClass, numCalleeSaved, slotSize); |
|
| 137 | 134 | return SpillInfo 'scratch { slots: &slots[..], frameSize, calleeClass: &calleeClass[..], maxReg }; |
|
| 138 | 135 | } |
|
| 139 | 136 | ||
| 140 | 137 | /// Limit register pressure from the block's live-out set to its entry. |
|
| 141 | - | fn limitBlockPressure( |
|
| 142 | - | instructions: &[il::Instr], |
|
| 138 | + | fn limitBlockPressure 'input ( |
|
| 139 | + | instructions: &[published::Instruction 'input], |
|
| 143 | 140 | live: &mut [u32], |
|
| 144 | 141 | spilled: &mut [u32], |
|
| 145 | 142 | costs: &[SpillCost], |
|
| 146 | 143 | calleeClass: &mut [u32], |
|
| 147 | 144 | numRegs: u32, |
| 155 | 152 | ||
| 156 | 153 | // Limit register pressure before processing this instruction. |
|
| 157 | 154 | try limitPressure(live, spilled, costs, numRegs); |
|
| 158 | 155 | ||
| 159 | 156 | // Enforce cross-call pressure at call sites. |
|
| 160 | - | if il::isCall(*instr) { |
|
| 157 | + | if published::isCall(instr) { |
|
| 161 | 158 | try limitCrossCallPressure( |
|
| 162 | 159 | live, spilled, costs, |
|
| 163 | - | calleeClass, numCalleeSaved, il::instrDst(*instr) |
|
| 160 | + | calleeClass, numCalleeSaved, published::destination(instr) |
|
| 164 | 161 | ); |
|
| 165 | 162 | } |
|
| 166 | 163 | // Remove definition from live set. |
|
| 167 | - | if let dst = il::instrDst(*instr) { |
|
| 164 | + | if let dst = published::destination(instr) { |
|
| 168 | 165 | bitset::clear(live, dst.n); |
|
| 169 | 166 | } |
|
| 170 | 167 | // Add uses to live set. |
|
| 171 | - | let mut registers = il::registers(instr); |
|
| 172 | - | // Argument groups contain raw views into the function's IL storage. |
|
| 173 | - | unsafe { |
|
| 174 | - | while let reg = il::nextReg(&mut registers, instr) { |
|
| 175 | - | bitset::put(live, reg.n); |
|
| 176 | - | } |
|
| 168 | + | // Fixed operands precede the instruction's variable argument groups. |
|
| 169 | + | for reg in published::registers(instr) { |
|
| 170 | + | bitset::put(live, reg.n); |
|
| 177 | 171 | } |
|
| 178 | 172 | } |
|
| 179 | 173 | // Also limit pressure at block entry. |
|
| 180 | 174 | try limitPressure(live, spilled, costs, numRegs); |
|
| 181 | 175 | } |
| 219 | 213 | } |
|
| 220 | 214 | return frameSize; |
|
| 221 | 215 | } |
|
| 222 | 216 | ||
| 223 | 217 | /// Add one block's register spill costs, weighted by loop depth. |
|
| 224 | - | fn fillCosts(params: &[il::Param], instructions: &[il::Instr], loopDepth: u32, costs: &mut [SpillCost]) { |
|
| 218 | + | fn fillCosts 'input (params: &[il::Param], instructions: &[published::Instruction 'input], loopDepth: u32, costs: &mut [SpillCost]) { |
|
| 225 | 219 | // Exponential weight for loop depth, capped to avoid overflow. |
|
| 226 | 220 | let depth = MAX_LOOP_WEIGHT if loopDepth > MAX_LOOP_WEIGHT else loopDepth; |
|
| 227 | 221 | let weight: u32 = 1 << depth; |
|
| 228 | 222 | ||
| 229 | 223 | // Count block parameter definitions. |
| 235 | 229 | // Count instruction defs and uses. |
|
| 236 | 230 | for i in 0..instructions.len { |
|
| 237 | 231 | let instr = &instructions[i]; |
|
| 238 | 232 | ||
| 239 | 233 | // Count definition. |
|
| 240 | - | if let dst = il::instrDst(*instr) { |
|
| 234 | + | if let dst = published::destination(instr) { |
|
| 241 | 235 | if dst.n < costs.len { |
|
| 242 | 236 | set costs[dst.n].defs = costs[dst.n].defs + weight; |
|
| 243 | 237 | } |
|
| 244 | 238 | } |
|
| 245 | 239 | // Count uses. |
|
| 246 | - | let mut registers = il::registers(instr); |
|
| 247 | - | // Argument groups contain raw views into the function's IL storage. |
|
| 248 | - | unsafe { |
|
| 249 | - | while let reg = il::nextReg(&mut registers, instr) { |
|
| 250 | - | countRegUse(reg, &mut costs[..], weight); |
|
| 251 | - | } |
|
| 240 | + | // Fixed operands precede the instruction's variable argument groups. |
|
| 241 | + | for reg in published::registers(instr) { |
|
| 242 | + | countRegUse(reg, &mut costs[..], weight); |
|
| 252 | 243 | } |
|
| 253 | 244 | } |
|
| 254 | 245 | } |
|
| 255 | 246 | ||
| 256 | 247 | /// Sort candidates by cost (ascending) using insertion sort, then spill |
lib/std/lang/il.rad
+2 -0
| 57 | 57 | ||
| 58 | 58 | // TODO: Labels should have their own type. |
|
| 59 | 59 | // TODO: Blocks should have an instruction in `Instr`. |
|
| 60 | 60 | ||
| 61 | 61 | export mod printer; |
|
| 62 | + | export mod published; |
|
| 63 | + | @test mod publishedTests; |
|
| 62 | 64 | export mod binary; |
|
| 63 | 65 | @test mod tests; |
|
| 64 | 66 | ||
| 65 | 67 | use std::lang::alloc; |
|
| 66 | 68 |
lib/std/lang/il/published.rad
added
+157 -0
| 1 | + | //! Immutable, region-bound views of IL used by function analyses. |
|
| 2 | + | //! |
|
| 3 | + | //! Publication owns analysis tables and retains original node pointers as |
|
| 4 | + | //! identities. Analysis reads do not access the source tables. |
|
| 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 | + | /// Function parameter definitions. |
|
| 15 | + | params: &'view [il::Param], |
|
| 16 | + | /// Blocks in source order. |
|
| 17 | + | blocks: &'view [Block 'view], |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// Read-only block tables and their original identity. |
|
| 21 | + | export record Block: 'view + Copy { |
|
| 22 | + | /// Original block pointer, used only for identity comparisons. |
|
| 23 | + | identity: *unsafe il::Block, |
|
| 24 | + | /// Block parameter definitions. |
|
| 25 | + | params: &'view [il::Param], |
|
| 26 | + | /// Published instruction descriptors. |
|
| 27 | + | instructions: &'view [Instruction 'view], |
|
| 28 | + | /// Loop nesting depth for cost weighting. |
|
| 29 | + | loopDepth: u32, |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | /// An instruction's immutable register-allocation inputs. |
|
| 33 | + | export opaque record Instruction: 'view + Copy { |
|
| 34 | + | /// Original instruction pointer, used only for identity comparisons. |
|
| 35 | + | identity: *unsafe il::Instr, |
|
| 36 | + | /// Destination register, when the instruction defines a value. |
|
| 37 | + | destination: ?il::Reg, |
|
| 38 | + | /// Whether the instruction has call-clobber semantics. |
|
| 39 | + | isCall: bool, |
|
| 40 | + | /// Source registers in operand order, including repeated uses. |
|
| 41 | + | operands: &'view [il::Reg], |
|
| 42 | + | /// Successor block positions in branch order. |
|
| 43 | + | targets: &'view [u32], |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | /// Copy analysis tables from valid IL into session-owned immutable storage. |
|
| 47 | + | /// All source tables and nested arrays must remain valid during this call. |
|
| 48 | + | /// Original node pointers are identity values, not published storage borrows. |
|
| 49 | + | export unsafe fn publish 'view (function: &il::Fn, storage: &Session 'view) |
|
| 50 | + | -> Function 'view throws (alloc::AllocError) |
|
| 51 | + | { |
|
| 52 | + | let params = try storage.copy(function.params); |
|
| 53 | + | let blocks: &[il::Block] = function.blocks; |
|
| 54 | + | if blocks.len == 0 { |
|
| 55 | + | let none: [Block 'view; 0] = []; |
|
| 56 | + | return Function 'view { params, blocks: try storage.copy(&none[..]) }; |
|
| 57 | + | } |
|
| 58 | + | let first = try publishBlock(&blocks[0], storage); |
|
| 59 | + | let views = try storage.fill(first, blocks.len); |
|
| 60 | + | for i in 1..blocks.len { |
|
| 61 | + | set views[i] = try publishBlock(&blocks[i], storage); |
|
| 62 | + | } |
|
| 63 | + | return Function 'view { params, blocks: &views[..] }; |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Copy block definitions and each instruction's analysis tables. |
|
| 67 | + | unsafe fn publishBlock 'view (block: &il::Block, storage: &Session 'view) |
|
| 68 | + | -> Block 'view throws (alloc::AllocError) |
|
| 69 | + | { |
|
| 70 | + | let params = try storage.copy(block.params); |
|
| 71 | + | let instructions: &[il::Instr] = block.instrs; |
|
| 72 | + | if instructions.len == 0 { |
|
| 73 | + | let none: [Instruction 'view; 0] = []; |
|
| 74 | + | return Block 'view { identity: block as *unsafe il::Block, params, |
|
| 75 | + | instructions: try storage.copy(&none[..]), loopDepth: block.loopDepth }; |
|
| 76 | + | } |
|
| 77 | + | let first = try publishInstruction(&instructions[0], storage); |
|
| 78 | + | let views = try storage.fill(first, instructions.len); |
|
| 79 | + | for i in 1..instructions.len { |
|
| 80 | + | set views[i] = try publishInstruction(&instructions[i], storage); |
|
| 81 | + | } |
|
| 82 | + | return Block 'view { identity: block as *unsafe il::Block, params, |
|
| 83 | + | instructions: &views[..], loopDepth: block.loopDepth }; |
|
| 84 | + | } |
|
| 85 | + | ||
| 86 | + | /// Collect source registers and successor positions for one instruction. |
|
| 87 | + | unsafe fn publishInstruction 'view (source: &il::Instr, storage: &Session 'view) |
|
| 88 | + | -> Instruction 'view throws (alloc::AllocError) |
|
| 89 | + | { |
|
| 90 | + | let noTargets: [u32; 0] = []; |
|
| 91 | + | let mut targets: &'view [u32] = try storage.copy(&noTargets[..]); |
|
| 92 | + | match source { |
|
| 93 | + | case il::Instr::Jmp { target, .. } => { |
|
| 94 | + | set targets = try storage.fill(*target, 1); |
|
| 95 | + | }, |
|
| 96 | + | case il::Instr::Br { thenTarget, elseTarget, .. } => { |
|
| 97 | + | let edges = try storage.fill(*thenTarget, 2); |
|
| 98 | + | set edges[1] = *elseTarget; |
|
| 99 | + | set targets = &edges[..]; |
|
| 100 | + | }, |
|
| 101 | + | case il::Instr::Switch { defaultTarget, cases, .. } => { |
|
| 102 | + | let cases: &[il::SwitchCase] = *cases; |
|
| 103 | + | if cases.len == MAX_ELEMENTS { |
|
| 104 | + | throw alloc::AllocError::OutOfMemory; |
|
| 105 | + | } |
|
| 106 | + | let edges = try storage.fill(*defaultTarget, cases.len + 1); |
|
| 107 | + | for item, i in cases { |
|
| 108 | + | set edges[i + 1] = item.target; |
|
| 109 | + | } |
|
| 110 | + | set targets = &edges[..]; |
|
| 111 | + | }, |
|
| 112 | + | else => {}, |
|
| 113 | + | } |
|
| 114 | + | let mut cursor = il::registers(source); |
|
| 115 | + | let mut count: u32 = 0; |
|
| 116 | + | while let reg = il::nextReg(&mut cursor, source) { |
|
| 117 | + | if count == MAX_ELEMENTS { |
|
| 118 | + | throw alloc::AllocError::OutOfMemory; |
|
| 119 | + | } |
|
| 120 | + | set count += 1; |
|
| 121 | + | } |
|
| 122 | + | let operands = try storage.fill(il::Reg { n: 0 }, count); |
|
| 123 | + | set cursor = il::registers(source); |
|
| 124 | + | for i in 0..count { |
|
| 125 | + | let reg = il::nextReg(&mut cursor, source) |
|
| 126 | + | else panic "publishInstruction: operand count changed"; |
|
| 127 | + | set operands[i] = reg; |
|
| 128 | + | } |
|
| 129 | + | return Instruction 'view { identity: source as *unsafe il::Instr, |
|
| 130 | + | destination: il::instrDst(*source), isCall: il::isCall(*source), |
|
| 131 | + | operands: &operands[..], targets }; |
|
| 132 | + | } |
|
| 133 | + | ||
| 134 | + | /// Return the original instruction pointer for identity comparisons. |
|
| 135 | + | export fn identity 'view (instruction: &Instruction 'view) -> *unsafe il::Instr { |
|
| 136 | + | return instruction.identity; |
|
| 137 | + | } |
|
| 138 | + | ||
| 139 | + | /// Return an instruction's destination register. |
|
| 140 | + | export fn destination 'view (instruction: &Instruction 'view) -> ?il::Reg { |
|
| 141 | + | return instruction.destination; |
|
| 142 | + | } |
|
| 143 | + | ||
| 144 | + | /// Check whether an instruction has call-clobber semantics. |
|
| 145 | + | export fn isCall 'view (instruction: &Instruction 'view) -> bool { |
|
| 146 | + | return instruction.isCall; |
|
| 147 | + | } |
|
| 148 | + | ||
| 149 | + | /// Borrow successor positions in branch order, including repeated targets. |
|
| 150 | + | export fn successors 'view (instruction: &Instruction 'view) -> &'view [u32] { |
|
| 151 | + | return instruction.targets; |
|
| 152 | + | } |
|
| 153 | + | ||
| 154 | + | /// Borrow source registers in operand order, including repeated uses. |
|
| 155 | + | export fn registers 'view (instruction: &Instruction 'view) -> &'view [il::Reg] { |
|
| 156 | + | return instruction.operands; |
|
| 157 | + | } |
lib/std/lang/il/publishedTests.rad
added
+123 -0
| 1 | + | //! Checked traversal of published IL operand tables. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use std::lang::alloc; |
|
| 5 | + | use std::lang::il; |
|
| 6 | + | use std::lang::il::published; |
|
| 7 | + | ||
| 8 | + | /// Publish operands from source tables that end with this stack frame. |
|
| 9 | + | unsafe fn transientInput 'view (storage: &Session 'view) -> published::Function 'view |
|
| 10 | + | throws (alloc::AllocError) |
|
| 11 | + | { |
|
| 12 | + | let params = [il::Param { value: il::Reg { n: 5 }, type: il::Type::W64 }]; |
|
| 13 | + | let blockParams = [il::Param { value: il::Reg { n: 6 }, type: il::Type::W64 }]; |
|
| 14 | + | let mut instructions = [ |
|
| 15 | + | il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: il::Reg { n: 9 }, |
|
| 16 | + | a: il::Val::Reg(il::Reg { n: 5 }), b: il::Val::Reg(il::Reg { n: 6 }) }, |
|
| 17 | + | il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 9 }) }, |
|
| 18 | + | ]; |
|
| 19 | + | let blocks = [il::Block { label: "entry", params: &blockParams[..], instrs: &mut instructions[..], |
|
| 20 | + | locs: &[], preds: &[], loopDepth: 2 }]; |
|
| 21 | + | let function = il::Fn { name: "transient", params: ¶ms[..], returnType: il::Type::W64, |
|
| 22 | + | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 23 | + | return try published::publish(&function, storage); |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// Analysis tables remain valid after their source stack frame returns. |
|
| 27 | + | @test unsafe fn testTransientPublishedInput() throws (testing::TestError) { |
|
| 28 | + | static DATA: [u8; 4096] = [0; 4096]; |
|
| 29 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 30 | + | use arena as storage in { |
|
| 31 | + | let view = try! transientInput(&storage); |
|
| 32 | + | assert view.params.len == 1 and view.params[0].value.n == 5; |
|
| 33 | + | let block = &view.blocks[0]; |
|
| 34 | + | assert block.params.len == 1 and block.params[0].value.n == 6; |
|
| 35 | + | assert block.loopDepth == 2; |
|
| 36 | + | let registers = published::registers(&block.instructions[0]); |
|
| 37 | + | assert registers.len == 2 and registers[0].n == 5 and registers[1].n == 6; |
|
| 38 | + | let destination = published::destination(&block.instructions[0]) else panic; |
|
| 39 | + | assert destination.n == 9; |
|
| 40 | + | assert not published::isCall(&block.instructions[0]); |
|
| 41 | + | let returned = published::registers(&block.instructions[1]); |
|
| 42 | + | assert returned.len == 1 and returned[0].n == 9; |
|
| 43 | + | assert published::destination(&block.instructions[1]) == nil; |
|
| 44 | + | assert published::successors(&block.instructions[1]).len == 0; |
|
| 45 | + | } |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | /// Empty functions require no analysis storage. |
|
| 49 | + | @test unsafe fn testEmptyPublication() throws (testing::TestError) { |
|
| 50 | + | let function = il::Fn { name: "empty", params: &[], returnType: il::Type::W64, |
|
| 51 | + | isExtern: true, isLeaf: true, blocks: &[] }; |
|
| 52 | + | static DATA: [u8; 1] = [0; 1]; |
|
| 53 | + | let mut arena = alloc::new(&mut DATA[..0]); |
|
| 54 | + | use arena as storage in { |
|
| 55 | + | let view = try! published::publish(&function, &storage); |
|
| 56 | + | assert view.params.len == 0 and view.blocks.len == 0; |
|
| 57 | + | } |
|
| 58 | + | assert alloc::used(&arena) == 0; |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | /// Publication retains source identities and scans nested argument groups. |
|
| 62 | + | @test unsafe fn testPublishedOperands() throws (testing::TestError) { |
|
| 63 | + | let value = il::Val::Reg(il::Reg { n: 7 }); |
|
| 64 | + | let mut arguments = [il::Val::Imm(3), value, value]; |
|
| 65 | + | let mut cases = [il::SwitchCase { value: 1, target: 0, args: &mut arguments[..] }]; |
|
| 66 | + | let mut instructions = [il::Instr::Switch { val: value, defaultTarget: 0, |
|
| 67 | + | defaultArgs: &mut [], cases: &mut cases[..] }]; |
|
| 68 | + | let blocks = [il::Block { label: "entry", params: &[], instrs: &mut instructions[..], |
|
| 69 | + | locs: &[], preds: &[], loopDepth: 0 }]; |
|
| 70 | + | let function = il::Fn { name: "published", params: &[], returnType: il::Type::W64, |
|
| 71 | + | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
|
| 72 | + | static DATA: [u8; 4096] = [0; 4096]; |
|
| 73 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 74 | + | let input: 'input = &function in { |
|
| 75 | + | use arena as storage in { |
|
| 76 | + | let view = try! published::publish(input, &storage); |
|
| 77 | + | assert view.blocks.len == 1; |
|
| 78 | + | assert view.blocks[0].identity == &blocks[0]; |
|
| 79 | + | let instruction = &view.blocks[0].instructions[0]; |
|
| 80 | + | assert published::identity(instruction) == &instructions[0]; |
|
| 81 | + | set arguments[1] = il::Val::Reg(il::Reg { n: 99 }); |
|
| 82 | + | set cases[0].target = 42; |
|
| 83 | + | set instructions[0] = il::Instr::Ret { val: nil }; |
|
| 84 | + | let registers = published::registers(instruction); |
|
| 85 | + | assert registers.len == 3; |
|
| 86 | + | for reg in registers { |
|
| 87 | + | assert reg.n == 7; |
|
| 88 | + | } |
|
| 89 | + | let targets = published::successors(instruction); |
|
| 90 | + | assert targets.len == 2 and targets[0] == 0 and targets[1] == 0; |
|
| 91 | + | } |
|
| 92 | + | } |
|
| 93 | + | } |
|
| 94 | + | ||
| 95 | + | /// Allocation failures leave no borrowed source storage in published results. |
|
| 96 | + | @test unsafe fn testPublicationAllocationFailure() throws (testing::TestError) { |
|
| 97 | + | let args = [il::Val::Reg(il::Reg { n: 4 })]; |
|
| 98 | + | let mut instructions = [il::Instr::Call { retTy: il::Type::W64, dst: nil, |
|
| 99 | + | func: il::Val::FnAddr("callee"), args: &args[..] }]; |
|
| 100 | + | let blocks = [il::Block { label: "entry", params: &[], instrs: &mut instructions[..], |
|
| 101 | + | locs: &[], preds: &[], loopDepth: 0 }]; |
|
| 102 | + | let function = il::Fn { name: "allocation", params: &[], returnType: il::Type::W64, |
|
| 103 | + | isExtern: false, isLeaf: false, blocks: &blocks[..] }; |
|
| 104 | + | let mut succeeded = false; |
|
| 105 | + | for capacity in 0..513 { |
|
| 106 | + | static DATA: [u8; 512] = [0; 512]; |
|
| 107 | + | let mut arena = alloc::new(&mut DATA[..capacity]); |
|
| 108 | + | let input: 'input = &function in { |
|
| 109 | + | use arena as storage in { |
|
| 110 | + | if let view = try? published::publish(input, &storage) { |
|
| 111 | + | let registers = published::registers(&view.blocks[0].instructions[0]); |
|
| 112 | + | assert registers.len == 1 and registers[0].n == 4; |
|
| 113 | + | set succeeded = true; |
|
| 114 | + | } else { |
|
| 115 | + | assert not succeeded; |
|
| 116 | + | } |
|
| 117 | + | } |
|
| 118 | + | } |
|
| 119 | + | alloc::reset(&mut arena); |
|
| 120 | + | assert alloc::used(&arena) == 0; |
|
| 121 | + | } |
|
| 122 | + | assert succeeded; |
|
| 123 | + | } |
std.lib
+1 -0
| 30 | 30 | lib/std/lang/ast.rad |
|
| 31 | 31 | lib/std/lang/ast/printer.rad |
|
| 32 | 32 | lib/std/lang/scanner.rad |
|
| 33 | 33 | lib/std/lang/parser.rad |
|
| 34 | 34 | lib/std/lang/il.rad |
|
| 35 | + | lib/std/lang/il/published.rad |
|
| 35 | 36 | lib/std/lang/il/printer.rad |
|
| 36 | 37 | lib/std/lang/il/binary.rad |
|
| 37 | 38 | lib/std/lang/il/binary/writer.rad |
|
| 38 | 39 | lib/std/lang/il/binary/reader.rad |
|
| 39 | 40 | lib/std/lang/il/binary/program.rad |
std.lib.test
+1 -0
| 19 | 19 | lib/std/arch/rv64/shared/tests.rad |
|
| 20 | 20 | lib/std/arch/rv64/bounds.rad |
|
| 21 | 21 | lib/std/arch/rv64/atomicTests.rad |
|
| 22 | 22 | lib/std/lang/resolver/tests/regions.rad |
|
| 23 | 23 | lib/std/lang/resolver/opaqueTests.rad |
|
| 24 | + | lib/std/lang/il/publishedTests.rad |