compiler/
lib/
examples/
std/
arch/
char/
collections/
graph/
lang/
alloc/
ast/
gen/
bitset/
regalloc/
liveness/
assign.rad
11.7 KiB
flow.rad
4.1 KiB
liveness.rad
9.0 KiB
spill.rad
12.4 KiB
bitset.rad
4.6 KiB
data.rad
8.6 KiB
labels.rad
2.4 KiB
regalloc.rad
2.4 KiB
types.rad
594 B
il/
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/gen/regalloc/liveness.rad
raw
| 1 | //! Backward dataflow liveness analysis. |
| 2 | //! |
| 3 | //! Computes live-in and live-out sets for each basic block. A register is |
| 4 | //! "live" at a program point if its value may be used on some path from that |
| 5 | //! point to program exit. |
| 6 | //! |
| 7 | //! Uses monotone iterative dataflow analysis. All sets start empty and grow: |
| 8 | //! |
| 9 | //! REPEAT UNTIL changed == false |
| 10 | //! changed = false |
| 11 | //! FOR EACH BLOCK b IN POST-ORDER DO |
| 12 | //! liveOut[b] |= UNION(liveIn[succ] FOR EACH SUCCESSOR OF b) |
| 13 | //! newIn = uses[b] | (liveOut[b] - defs[b]) |
| 14 | //! IF newIn <> liveIn[b] THEN |
| 15 | //! changed = true |
| 16 | //! liveIn[b] = newIn |
| 17 | //! |
| 18 | //! Only live-in changes require another pass, since predecessors read live-in. |
| 19 | //! |
| 20 | //! Usage: |
| 21 | //! |
| 22 | //! let live = liveness::analyze(func, ...); |
| 23 | //! if not liveness::hasLaterUse(&live, func, blockIdx, instrIdx, reg) { |
| 24 | //! // `reg` dies at this instruction. |
| 25 | //! } |
| 26 | |
| 27 | @test mod tests; |
| 28 | |
| 29 | use std::lang::il; |
| 30 | use std::lang::il::published; |
| 31 | use std::lang::alloc; |
| 32 | use std::lang::gen::bitset; |
| 33 | use super::flow; |
| 34 | |
| 35 | /// Largest register index whose required entry count fits in u32. |
| 36 | constant MAX_REGISTER_INDEX: u32 = 0xfffffffe; |
| 37 | |
| 38 | /// Liveness information for a function. |
| 39 | export record LiveInfo: 'scratch + Copy { |
| 40 | /// Per-block live-in words, stored in consecutive rows. |
| 41 | liveIn: &'scratch [u32], |
| 42 | /// Per-block live-out words, stored in consecutive rows. |
| 43 | liveOut: &'scratch [u32], |
| 44 | /// Per-block definition words, stored in consecutive rows. |
| 45 | defs: &'scratch [u32], |
| 46 | /// Per-block use-before-definition words, stored in consecutive rows. |
| 47 | uses: &'scratch [u32], |
| 48 | /// Number of words in one block row. |
| 49 | words: u32, |
| 50 | /// Number of blocks. |
| 51 | blockCount: u32, |
| 52 | /// Maximum register number used. |
| 53 | maxReg: u32, |
| 54 | } |
| 55 | |
| 56 | /// Compute liveness by growing live sets until no live-in set changes. |
| 57 | export fn analyze 'input 'scratch (func: &published::Function 'input, storage: &Session 'scratch) -> LiveInfo 'scratch throws (alloc::AllocError) { |
| 58 | return try analyzeTables(func.params, func.blocks, storage); |
| 59 | } |
| 60 | |
| 61 | /// Allocate and compute live sets for borrowed function parameter and block tables. |
| 62 | fn analyzeTables 'input 'scratch (params: &[il::Param], blocks: &[published::Block 'input], storage: &Session 'scratch) -> LiveInfo 'scratch throws (alloc::AllocError) { |
| 63 | let blockCount = blocks.len; |
| 64 | if blockCount == 0 { |
| 65 | let empty = try storage.fill(0 as u32, 0); |
| 66 | let frozen: &'scratch [u32] = &empty[..]; |
| 67 | return LiveInfo 'scratch { liveIn: frozen, liveOut: frozen, defs: frozen, uses: frozen, words: 0, blockCount: 0, maxReg: 0 }; |
| 68 | } |
| 69 | |
| 70 | // Find max register number. |
| 71 | let mut maxReg: u32 = 0; |
| 72 | for parameter in params { |
| 73 | try updateMaxReg(parameter.value, &mut maxReg); |
| 74 | } |
| 75 | for b in 0..blockCount { |
| 76 | let block = &blocks[b]; |
| 77 | try updateExtent(block.params, block.instructions, &mut maxReg); |
| 78 | } |
| 79 | // Allocate one contiguous word matrix for each set class. |
| 80 | let words = bitset::wordsFor(maxReg); |
| 81 | let count64 = blockCount as u64 * words as u64; |
| 82 | if count64 > 0xFFFFFFFF { |
| 83 | throw alloc::AllocError::OutOfMemory; |
| 84 | } |
| 85 | let count = count64 as u32; |
| 86 | let liveIn = try storage.fill(0 as u32, count); |
| 87 | let liveOut = try storage.fill(0 as u32, count); |
| 88 | let defs = try storage.fill(0 as u32, count); |
| 89 | let uses = try storage.fill(0 as u32, count); |
| 90 | |
| 91 | // Compute local defs and uses for each block. |
| 92 | for b in 0..blockCount { |
| 93 | let block = &blocks[b]; |
| 94 | computeLocalDefsUses(block.params, block.instructions, &mut defs[b * words..(b + 1) * words], &mut uses[b * words..(b + 1) * words]); |
| 95 | } |
| 96 | let mut owner = flow::Permission {}; |
| 97 | let permission: 'permission = &mut owner in { |
| 98 | let topology = try flow::snapshot(blocks, storage, permission); |
| 99 | propagateLiveness(&topology, permission, blockCount, liveIn, liveOut, defs, uses, words); |
| 100 | } |
| 101 | return LiveInfo 'scratch { liveIn: &liveIn[..], liveOut: &liveOut[..], defs: &defs[..], uses: &uses[..], words, blockCount, maxReg }; |
| 102 | } |
| 103 | |
| 104 | /// Propagate successor uses until every block's live-in row is stable. |
| 105 | fn propagateLiveness 'scratch 'permission ( |
| 106 | topology: &flow::Flow 'scratch 'permission, |
| 107 | permission: &'permission mut flow::Permission, |
| 108 | blockCount: u32, |
| 109 | liveIn: &mut [u32], |
| 110 | liveOut: &mut [u32], |
| 111 | defs: &[u32], |
| 112 | uses: &[u32], |
| 113 | words: u32, |
| 114 | ) { |
| 115 | // Live sets grow monotonically from empty sets; no scratch set is needed. |
| 116 | let mut changed = true; |
| 117 | |
| 118 | while changed { |
| 119 | set changed = false; |
| 120 | |
| 121 | // Process blocks in reverse order (approximates post-order). |
| 122 | let mut b = blockCount; |
| 123 | while b > 0 { |
| 124 | set b -= 1; |
| 125 | // Add successor live-in sets directly to this block's live-out set. |
| 126 | for successor in flow::successors(topology, b, permission) { |
| 127 | unionBlockLiveIn(flow::index(successor), liveIn, words, &mut liveOut[b * words..(b + 1) * words]); |
| 128 | } |
| 129 | |
| 130 | if computeAndUpdateLiveIn(&mut liveIn[b * words..(b + 1) * words], &liveOut[b * words..(b + 1) * words], &defs[b * words..(b + 1) * words], &uses[b * words..(b + 1) * words]) { |
| 131 | set changed = true; |
| 132 | } |
| 133 | } |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | /// Compute `liveIn = uses | (liveOut - defs)` and update `dst`. |
| 138 | /// Returns `true` if `dst` changed. Combined loop avoids multiple passes. |
| 139 | fn computeAndUpdateLiveIn( |
| 140 | dst: &mut [u32], |
| 141 | liveOut: &[u32], |
| 142 | defs: &[u32], |
| 143 | uses: &[u32] |
| 144 | ) -> bool { |
| 145 | let mut changed = false; |
| 146 | for i in 0..dst.len { |
| 147 | let newWord = uses[i] | (liveOut[i] & ~defs[i]); |
| 148 | if dst[i] <> newWord { |
| 149 | set dst[i] = newWord; |
| 150 | set changed = true; |
| 151 | } |
| 152 | } |
| 153 | return changed; |
| 154 | } |
| 155 | |
| 156 | /// Compute local defs and uses for a single block. |
| 157 | fn computeLocalDefsUses 'input (params: &[il::Param], instructions: &[published::Instruction 'input], defs: &mut [u32], uses: &mut [u32]) { |
| 158 | for p in params { |
| 159 | bitset::put(defs, p.value.n); |
| 160 | } |
| 161 | for i in 0..instructions.len { |
| 162 | let instr = &instructions[i]; |
| 163 | // Fixed operands precede the instruction's variable argument groups. |
| 164 | for reg in published::registers(instr) { |
| 165 | addUse(reg, defs, uses); |
| 166 | } |
| 167 | |
| 168 | if let dst = published::destination(instr) { |
| 169 | bitset::put(defs, dst.n); |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | /// Add an undefined register to the use set. |
| 175 | fn addUse(reg: il::Reg, defs: &[u32], uses: &mut [u32]) { |
| 176 | if not bitset::contains(defs, reg.n) { |
| 177 | bitset::put(uses, reg.n); |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | /// Include all parameter, source, and destination registers in the entry count. |
| 182 | fn updateExtent 'input (params: &[il::Param], instructions: &[published::Instruction 'input], max: &mut u32) throws (alloc::AllocError) { |
| 183 | for p in params { |
| 184 | try updateMaxReg(p.value, max); |
| 185 | } |
| 186 | for i in 0..instructions.len { |
| 187 | let instr = &instructions[i]; |
| 188 | // Fixed operands precede the instruction's variable argument groups. |
| 189 | for reg in published::registers(instr) { |
| 190 | try updateMaxReg(reg, max); |
| 191 | } |
| 192 | if let dst = published::destination(instr) { |
| 193 | try updateMaxReg(dst, max); |
| 194 | } |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | /// Retain the larger of the current entry count and the register index plus one. |
| 199 | fn updateMaxReg(reg: il::Reg, max: &mut u32) throws (alloc::AllocError) { |
| 200 | if reg.n > MAX_REGISTER_INDEX { |
| 201 | throw alloc::AllocError::OutOfMemory; |
| 202 | } |
| 203 | let count = reg.n + 1; |
| 204 | if count > *max { |
| 205 | set *max = count; |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | /// Union a target block's live-in set into the block's live-out set. |
| 210 | fn unionBlockLiveIn(target: u32, liveIn: &[u32], words: u32, liveOut: &mut [u32]) { |
| 211 | bitset::union_(liveOut, &liveIn[target * words..(target + 1) * words]); |
| 212 | } |
| 213 | |
| 214 | /// Check if a register has any use after this instruction. |
| 215 | export fn hasLaterUse 'input 'scratch (info: &LiveInfo 'scratch, func: &published::Function 'input, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool { |
| 216 | let block = &func.blocks[blockIdx]; |
| 217 | |
| 218 | if bitset::contains(liveOutRow(info, blockIdx), reg.n) { |
| 219 | return true; |
| 220 | } |
| 221 | return usesRegisterAfter(block.instructions, instrIdx, reg); |
| 222 | } |
| 223 | |
| 224 | /// Check if an instruction after the given index uses a specific register. |
| 225 | fn usesRegisterAfter 'input (instructions: &[published::Instruction 'input], instrIdx: u32, reg: il::Reg) -> bool { |
| 226 | for i in (instrIdx + 1)..instructions.len { |
| 227 | let instr = &instructions[i]; |
| 228 | // Fixed operands precede the instruction's variable argument groups. |
| 229 | for source in published::registers(instr) { |
| 230 | if source.n == reg.n { |
| 231 | return true; |
| 232 | } |
| 233 | } |
| 234 | } |
| 235 | return false; |
| 236 | } |
| 237 | |
| 238 | /// Borrow the live-in words for one block. |
| 239 | export fn liveInRow 'scratch (info: &LiveInfo 'scratch, block: u32) -> &'scratch [u32] { |
| 240 | assert block < info.blockCount; |
| 241 | return &info.liveIn[block * info.words..(block + 1) * info.words]; |
| 242 | } |
| 243 | |
| 244 | /// Borrow the live-out words for one block. |
| 245 | export fn liveOutRow 'scratch (info: &LiveInfo 'scratch, block: u32) -> &'scratch [u32] { |
| 246 | assert block < info.blockCount; |
| 247 | return &info.liveOut[block * info.words..(block + 1) * info.words]; |
| 248 | } |