compiler/
kernel/
lib/
examples/
std/
arch/
char/
collections/
lang/
alloc/
ast/
gen/
bitset/
regalloc/
liveness/
assign.rad
10.7 KiB
liveness.rad
8.3 KiB
spill.rad
10.9 KiB
bitset.rad
4.6 KiB
data.rad
8.5 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.7 KiB
gen.rad
513 B
il.rad
19.5 KiB
lower.rad
308.1 KiB
module.rad
14.9 KiB
package.rad
1.3 KiB
parser.rad
89.5 KiB
resolver.rad
439.6 KiB
scanner.rad
18.0 KiB
sexpr.rad
6.4 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
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
281 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
9.2 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.5 KiB
std.lib.test
662 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::alloc; |
| 31 | use std::lang::gen::bitset; |
| 32 | |
| 33 | /// Maximum number of SSA registers supported. |
| 34 | export constant MAX_SSA_REGS: u32 = 8192; |
| 35 | |
| 36 | /// Liveness information for a function. |
| 37 | export record LiveInfo: 'scratch + Copy { |
| 38 | /// Per-block live-in words, stored in consecutive rows. |
| 39 | liveIn: &'scratch [u32], |
| 40 | /// Per-block live-out words, stored in consecutive rows. |
| 41 | liveOut: &'scratch [u32], |
| 42 | /// Per-block definition words, stored in consecutive rows. |
| 43 | defs: &'scratch [u32], |
| 44 | /// Per-block use-before-definition words, stored in consecutive rows. |
| 45 | uses: &'scratch [u32], |
| 46 | /// Number of words in one block row. |
| 47 | words: u32, |
| 48 | /// Number of blocks. |
| 49 | blockCount: u32, |
| 50 | /// Maximum register number used. |
| 51 | maxReg: u32, |
| 52 | } |
| 53 | |
| 54 | /// Compute liveness by growing live sets until no live-in set changes. |
| 55 | export unsafe fn analyze 'scratch (func: &il::Fn, storage: &Session 'scratch) -> LiveInfo 'scratch throws (alloc::AllocError) { |
| 56 | let blockCount = func.blocks.len; |
| 57 | if blockCount == 0 { |
| 58 | let empty = try storage.fill(0 as u32, 0); |
| 59 | let frozen: &'scratch [u32] = &empty[..]; |
| 60 | return LiveInfo 'scratch { liveIn: frozen, liveOut: frozen, defs: frozen, uses: frozen, words: 0, blockCount: 0, maxReg: 0 }; |
| 61 | } |
| 62 | |
| 63 | // Find max register number. |
| 64 | let mut maxReg: u32 = 0; |
| 65 | for p in func.params { |
| 66 | set maxReg = maxRegNum(p.value.n, maxReg); |
| 67 | } |
| 68 | for b in 0..blockCount { |
| 69 | let block = &func.blocks[b]; |
| 70 | for p in block.params { |
| 71 | set maxReg = maxRegNum(p.value.n, maxReg); |
| 72 | } |
| 73 | for instr in block.instrs { |
| 74 | let mut registers = il::registers(&instr); |
| 75 | while let reg = il::nextReg(&mut registers, &instr) { |
| 76 | updateMaxReg(reg, &mut maxReg); |
| 77 | } |
| 78 | if let dst = il::instrDst(instr) { |
| 79 | set maxReg = maxRegNum(dst.n, maxReg); |
| 80 | } |
| 81 | } |
| 82 | } |
| 83 | if maxReg > MAX_SSA_REGS { |
| 84 | throw alloc::AllocError::OutOfMemory; |
| 85 | } |
| 86 | // Allocate one contiguous word matrix for each set class. |
| 87 | let words = bitset::wordsFor(maxReg); |
| 88 | let count64 = blockCount as u64 * words as u64; |
| 89 | if count64 > 0xFFFFFFFF { |
| 90 | throw alloc::AllocError::OutOfMemory; |
| 91 | } |
| 92 | let count = count64 as u32; |
| 93 | let liveIn = try storage.fill(0 as u32, count); |
| 94 | let liveOut = try storage.fill(0 as u32, count); |
| 95 | let defs = try storage.fill(0 as u32, count); |
| 96 | let uses = try storage.fill(0 as u32, count); |
| 97 | |
| 98 | // Compute local defs and uses for each block. |
| 99 | for b in 0..blockCount { |
| 100 | computeLocalDefsUses(&func.blocks[b], &mut defs[b * words..(b + 1) * words], &mut uses[b * words..(b + 1) * words]); |
| 101 | } |
| 102 | // Live sets grow monotonically from empty sets; no scratch set is needed. |
| 103 | let mut changed = true; |
| 104 | |
| 105 | while changed { |
| 106 | set changed = false; |
| 107 | |
| 108 | // Process blocks in reverse order (approximates post-order). |
| 109 | let mut b = blockCount; |
| 110 | while b > 0 { |
| 111 | set b -= 1; |
| 112 | let block = &func.blocks[b]; |
| 113 | |
| 114 | // Add successor live-in sets directly to this block's live-out set. |
| 115 | addSuccessorLiveIn(func, block, liveIn, words, &mut liveOut[b * words..(b + 1) * words]); |
| 116 | |
| 117 | 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]) { |
| 118 | set changed = true; |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | return LiveInfo 'scratch { liveIn: &liveIn[..], liveOut: &liveOut[..], defs: &defs[..], uses: &uses[..], words, blockCount, maxReg }; |
| 123 | } |
| 124 | |
| 125 | /// Compute `liveIn = uses | (liveOut - defs)` and update `dst`. |
| 126 | /// Returns `true` if `dst` changed. Combined loop avoids multiple passes. |
| 127 | fn computeAndUpdateLiveIn( |
| 128 | dst: &mut [u32], |
| 129 | liveOut: &[u32], |
| 130 | defs: &[u32], |
| 131 | uses: &[u32] |
| 132 | ) -> bool { |
| 133 | let mut changed = false; |
| 134 | for i in 0..dst.len { |
| 135 | let newWord = uses[i] | (liveOut[i] & ~defs[i]); |
| 136 | if dst[i] <> newWord { |
| 137 | set dst[i] = newWord; |
| 138 | set changed = true; |
| 139 | } |
| 140 | } |
| 141 | return changed; |
| 142 | } |
| 143 | |
| 144 | /// Compute local defs and uses for a single block. |
| 145 | unsafe fn computeLocalDefsUses(block: &il::Block, defs: &mut [u32], uses: &mut [u32]) { |
| 146 | for p in block.params { |
| 147 | bitset::put(defs, p.value.n); |
| 148 | } |
| 149 | for instr in block.instrs { |
| 150 | let mut registers = il::registers(&instr); |
| 151 | while let reg = il::nextReg(&mut registers, &instr) { |
| 152 | addUse(reg, defs, uses); |
| 153 | } |
| 154 | |
| 155 | if let dst = il::instrDst(instr) { |
| 156 | bitset::put(defs, dst.n); |
| 157 | } |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | /// Add an undefined register to the use set. |
| 162 | fn addUse(reg: il::Reg, defs: &[u32], uses: &mut [u32]) { |
| 163 | if not bitset::contains(defs, reg.n) { |
| 164 | bitset::put(uses, reg.n); |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | /// Update the largest register number. |
| 169 | fn updateMaxReg(reg: il::Reg, max: &mut u32) { |
| 170 | set *max = maxRegNum(reg.n, *max); |
| 171 | } |
| 172 | |
| 173 | /// Return the larger of n+1 and current. |
| 174 | fn maxRegNum(n: u32, current: u32) -> u32 { |
| 175 | if n >= MAX_SSA_REGS { |
| 176 | return MAX_SSA_REGS + 1; |
| 177 | } |
| 178 | if n + 1 > current { |
| 179 | return n + 1; |
| 180 | } |
| 181 | return current; |
| 182 | } |
| 183 | |
| 184 | /// Add successor live-in sets to the block's live-out set. |
| 185 | unsafe fn addSuccessorLiveIn(func: &il::Fn, block: &il::Block, liveIn: &[u32], words: u32, liveOut: &mut [u32]) { |
| 186 | if block.instrs.len == 0 { |
| 187 | return; |
| 188 | } |
| 189 | let term = block.instrs[block.instrs.len - 1]; |
| 190 | |
| 191 | match term { |
| 192 | case il::Instr::Jmp { target, .. } => |
| 193 | unionBlockLiveIn(target, liveIn, words, liveOut), |
| 194 | case il::Instr::Br { thenTarget, elseTarget, .. } => { |
| 195 | unionBlockLiveIn(thenTarget, liveIn, words, liveOut); |
| 196 | unionBlockLiveIn(elseTarget, liveIn, words, liveOut); |
| 197 | }, |
| 198 | case il::Instr::Switch { defaultTarget, cases, .. } => { |
| 199 | unionBlockLiveIn(defaultTarget, liveIn, words, liveOut); |
| 200 | for c in cases { |
| 201 | unionBlockLiveIn(c.target, liveIn, words, liveOut); |
| 202 | } |
| 203 | }, |
| 204 | else => {}, |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | /// Union a target block's live-in set into the block's live-out set. |
| 209 | fn unionBlockLiveIn(target: u32, liveIn: &[u32], words: u32, liveOut: &mut [u32]) { |
| 210 | bitset::union_(liveOut, &liveIn[target * words..(target + 1) * words]); |
| 211 | } |
| 212 | |
| 213 | /// Check if a register has any use after this instruction. |
| 214 | export unsafe fn hasLaterUse 'scratch (info: &LiveInfo 'scratch, func: &il::Fn, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool { |
| 215 | let block = &func.blocks[blockIdx]; |
| 216 | |
| 217 | if bitset::contains(liveOutRow(info, blockIdx), reg.n) { |
| 218 | return true; |
| 219 | } |
| 220 | for i in (instrIdx + 1)..block.instrs.len { |
| 221 | if instrUsesReg(block.instrs[i], reg) { |
| 222 | return true; |
| 223 | } |
| 224 | } |
| 225 | return false; |
| 226 | } |
| 227 | |
| 228 | /// Check if an instruction uses a specific register. |
| 229 | unsafe fn instrUsesReg(instr: il::Instr, reg: il::Reg) -> bool { |
| 230 | let mut registers = il::registers(&instr); |
| 231 | while let source = il::nextReg(&mut registers, &instr) { |
| 232 | if source.n == reg.n { |
| 233 | return true; |
| 234 | } |
| 235 | } |
| 236 | return false; |
| 237 | } |
| 238 | |
| 239 | /// Borrow the live-in words for one block. |
| 240 | export fn liveInRow 'scratch (info: &LiveInfo 'scratch, block: u32) -> &'scratch [u32] { |
| 241 | assert block < info.blockCount; |
| 242 | return &info.liveIn[block * info.words..(block + 1) * info.words]; |
| 243 | } |
| 244 | |
| 245 | /// Borrow the live-out words for one block. |
| 246 | export fn liveOutRow 'scratch (info: &LiveInfo 'scratch, block: u32) -> &'scratch [u32] { |
| 247 | assert block < info.blockCount; |
| 248 | return &info.liveOut[block * info.words..(block + 1) * info.words]; |
| 249 | } |