compiler/
lib/
examples/
std/
arch/
char/
collections/
lang/
alloc/
ast/
gen/
bitset/
regalloc/
liveness/
assign.rad
11.8 KiB
liveness.rad
10.0 KiB
spill.rad
12.5 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.7 KiB
gen.rad
513 B
il.rad
20.3 KiB
lower.rad
315.6 KiB
module.rad
14.9 KiB
package.rad
1.3 KiB
parser.rad
91.2 KiB
resolver.rad
452.9 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
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
5.4 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 | /// Largest register index whose required entry count fits in u32. |
| 34 | constant MAX_REGISTER_INDEX: u32 = 0xfffffffe; |
| 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 | return try analyzeTables(func.params, func.blocks, storage); |
| 57 | } |
| 58 | |
| 59 | /// Allocate and compute live sets for borrowed function parameter and block tables. |
| 60 | fn analyzeTables 'scratch (params: &[il::Param], blocks: &[il::Block], storage: &Session 'scratch) -> LiveInfo 'scratch throws (alloc::AllocError) { |
| 61 | let blockCount = blocks.len; |
| 62 | if blockCount == 0 { |
| 63 | let empty = try storage.fill(0 as u32, 0); |
| 64 | let frozen: &'scratch [u32] = &empty[..]; |
| 65 | return LiveInfo 'scratch { liveIn: frozen, liveOut: frozen, defs: frozen, uses: frozen, words: 0, blockCount: 0, maxReg: 0 }; |
| 66 | } |
| 67 | |
| 68 | // Find max register number. |
| 69 | let mut maxReg: u32 = 0; |
| 70 | try updateExtent(params, &[], &mut maxReg); |
| 71 | for b in 0..blockCount { |
| 72 | let block = &blocks[b]; |
| 73 | unsafe { |
| 74 | try updateExtent(block.params, block.instrs, &mut maxReg); |
| 75 | } |
| 76 | } |
| 77 | // Allocate one contiguous word matrix for each set class. |
| 78 | let words = bitset::wordsFor(maxReg); |
| 79 | let count64 = blockCount as u64 * words as u64; |
| 80 | if count64 > 0xFFFFFFFF { |
| 81 | throw alloc::AllocError::OutOfMemory; |
| 82 | } |
| 83 | let count = count64 as u32; |
| 84 | let liveIn = try storage.fill(0 as u32, count); |
| 85 | let liveOut = try storage.fill(0 as u32, count); |
| 86 | let defs = try storage.fill(0 as u32, count); |
| 87 | let uses = try storage.fill(0 as u32, count); |
| 88 | |
| 89 | // Compute local defs and uses for each block. |
| 90 | for b in 0..blockCount { |
| 91 | let block = &blocks[b]; |
| 92 | unsafe { |
| 93 | computeLocalDefsUses(block.params, block.instrs, &mut defs[b * words..(b + 1) * words], &mut uses[b * words..(b + 1) * words]); |
| 94 | } |
| 95 | } |
| 96 | propagateLiveness(blocks, liveIn, liveOut, defs, uses, words); |
| 97 | return LiveInfo 'scratch { liveIn: &liveIn[..], liveOut: &liveOut[..], defs: &defs[..], uses: &uses[..], words, blockCount, maxReg }; |
| 98 | } |
| 99 | |
| 100 | /// Propagate successor uses until every block's live-in row is stable. |
| 101 | fn propagateLiveness( |
| 102 | blocks: &[il::Block], |
| 103 | liveIn: &mut [u32], |
| 104 | liveOut: &mut [u32], |
| 105 | defs: &[u32], |
| 106 | uses: &[u32], |
| 107 | words: u32, |
| 108 | ) { |
| 109 | // Live sets grow monotonically from empty sets; no scratch set is needed. |
| 110 | let mut changed = true; |
| 111 | |
| 112 | while changed { |
| 113 | set changed = false; |
| 114 | |
| 115 | // Process blocks in reverse order (approximates post-order). |
| 116 | let mut b = blocks.len; |
| 117 | while b > 0 { |
| 118 | set b -= 1; |
| 119 | let block = &blocks[b]; |
| 120 | |
| 121 | // Add successor live-in sets directly to this block's live-out set. |
| 122 | unsafe { |
| 123 | addSuccessorLiveIn(block.instrs, liveIn, words, &mut liveOut[b * words..(b + 1) * words]); |
| 124 | } |
| 125 | |
| 126 | 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]) { |
| 127 | set changed = true; |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | /// Compute `liveIn = uses | (liveOut - defs)` and update `dst`. |
| 134 | /// Returns `true` if `dst` changed. Combined loop avoids multiple passes. |
| 135 | fn computeAndUpdateLiveIn( |
| 136 | dst: &mut [u32], |
| 137 | liveOut: &[u32], |
| 138 | defs: &[u32], |
| 139 | uses: &[u32] |
| 140 | ) -> bool { |
| 141 | let mut changed = false; |
| 142 | for i in 0..dst.len { |
| 143 | let newWord = uses[i] | (liveOut[i] & ~defs[i]); |
| 144 | if dst[i] <> newWord { |
| 145 | set dst[i] = newWord; |
| 146 | set changed = true; |
| 147 | } |
| 148 | } |
| 149 | return changed; |
| 150 | } |
| 151 | |
| 152 | /// Compute local defs and uses for a single block. |
| 153 | fn computeLocalDefsUses(params: &[il::Param], instructions: &[il::Instr], defs: &mut [u32], uses: &mut [u32]) { |
| 154 | for p in params { |
| 155 | bitset::put(defs, p.value.n); |
| 156 | } |
| 157 | for i in 0..instructions.len { |
| 158 | let instr = &instructions[i]; |
| 159 | let mut registers = il::registers(instr); |
| 160 | // Argument groups contain raw views into the function's IL storage. |
| 161 | unsafe { |
| 162 | while let reg = il::nextReg(&mut registers, instr) { |
| 163 | addUse(reg, defs, uses); |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | if let dst = il::instrDst(*instr) { |
| 168 | bitset::put(defs, dst.n); |
| 169 | } |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | /// Add an undefined register to the use set. |
| 174 | fn addUse(reg: il::Reg, defs: &[u32], uses: &mut [u32]) { |
| 175 | if not bitset::contains(defs, reg.n) { |
| 176 | bitset::put(uses, reg.n); |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | /// Include all parameter, source, and destination registers in the entry count. |
| 181 | fn updateExtent(params: &[il::Param], instructions: &[il::Instr], max: &mut u32) throws (alloc::AllocError) { |
| 182 | for p in params { |
| 183 | try updateMaxReg(p.value, max); |
| 184 | } |
| 185 | for i in 0..instructions.len { |
| 186 | let instr = &instructions[i]; |
| 187 | let mut registers = il::registers(instr); |
| 188 | // Argument groups contain raw views into the function's IL storage. |
| 189 | unsafe { |
| 190 | while let reg = il::nextReg(&mut registers, instr) { |
| 191 | try updateMaxReg(reg, max); |
| 192 | } |
| 193 | } |
| 194 | if let dst = il::instrDst(*instr) { |
| 195 | try updateMaxReg(dst, max); |
| 196 | } |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | /// Retain the larger of the current entry count and the register index plus one. |
| 201 | fn updateMaxReg(reg: il::Reg, max: &mut u32) throws (alloc::AllocError) { |
| 202 | if reg.n > MAX_REGISTER_INDEX { |
| 203 | throw alloc::AllocError::OutOfMemory; |
| 204 | } |
| 205 | let count = reg.n + 1; |
| 206 | if count > *max { |
| 207 | set *max = count; |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | /// Add successor live-in sets to the block's live-out set. |
| 212 | fn addSuccessorLiveIn(instructions: &[il::Instr], liveIn: &[u32], words: u32, liveOut: &mut [u32]) { |
| 213 | if instructions.len == 0 { |
| 214 | return; |
| 215 | } |
| 216 | let term = &instructions[instructions.len - 1]; |
| 217 | if let case il::Instr::Switch { cases, .. } = term { |
| 218 | unsafe { |
| 219 | mergeSuccessorLiveIn(term, *cases, liveIn, words, liveOut); |
| 220 | } |
| 221 | } else { |
| 222 | mergeSuccessorLiveIn(term, &[], liveIn, words, liveOut); |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | /// Merge live-in rows selected by a terminator and its borrowed switch cases. |
| 227 | fn mergeSuccessorLiveIn(term: &il::Instr, cases: &[il::SwitchCase], liveIn: &[u32], words: u32, liveOut: &mut [u32]) { |
| 228 | match term { |
| 229 | case il::Instr::Jmp { target, .. } => |
| 230 | unionBlockLiveIn(*target, liveIn, words, liveOut), |
| 231 | case il::Instr::Br { thenTarget, elseTarget, .. } => { |
| 232 | unionBlockLiveIn(*thenTarget, liveIn, words, liveOut); |
| 233 | unionBlockLiveIn(*elseTarget, liveIn, words, liveOut); |
| 234 | }, |
| 235 | case il::Instr::Switch { defaultTarget, .. } => { |
| 236 | unionBlockLiveIn(*defaultTarget, liveIn, words, liveOut); |
| 237 | for c in cases { |
| 238 | unionBlockLiveIn(c.target, liveIn, words, liveOut); |
| 239 | } |
| 240 | }, |
| 241 | else => {}, |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | /// Union a target block's live-in set into the block's live-out set. |
| 246 | fn unionBlockLiveIn(target: u32, liveIn: &[u32], words: u32, liveOut: &mut [u32]) { |
| 247 | bitset::union_(liveOut, &liveIn[target * words..(target + 1) * words]); |
| 248 | } |
| 249 | |
| 250 | /// Check if a register has any use after this instruction. |
| 251 | export unsafe fn hasLaterUse 'scratch (info: &LiveInfo 'scratch, func: &il::Fn, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool { |
| 252 | let block = &func.blocks[blockIdx]; |
| 253 | |
| 254 | if bitset::contains(liveOutRow(info, blockIdx), reg.n) { |
| 255 | return true; |
| 256 | } |
| 257 | return usesRegisterAfter(block.instrs, instrIdx, reg); |
| 258 | } |
| 259 | |
| 260 | /// Check if an instruction after the given index uses a specific register. |
| 261 | fn usesRegisterAfter(instructions: &[il::Instr], instrIdx: u32, reg: il::Reg) -> bool { |
| 262 | for i in (instrIdx + 1)..instructions.len { |
| 263 | let instr = &instructions[i]; |
| 264 | let mut registers = il::registers(instr); |
| 265 | // Argument groups contain raw views into the function's IL storage. |
| 266 | unsafe { |
| 267 | while let source = il::nextReg(&mut registers, instr) { |
| 268 | if source.n == reg.n { |
| 269 | return true; |
| 270 | } |
| 271 | } |
| 272 | } |
| 273 | } |
| 274 | return false; |
| 275 | } |
| 276 | |
| 277 | /// Borrow the live-in words for one block. |
| 278 | export fn liveInRow 'scratch (info: &LiveInfo 'scratch, block: u32) -> &'scratch [u32] { |
| 279 | assert block < info.blockCount; |
| 280 | return &info.liveIn[block * info.words..(block + 1) * info.words]; |
| 281 | } |
| 282 | |
| 283 | /// Borrow the live-out words for one block. |
| 284 | export fn liveOutRow 'scratch (info: &LiveInfo 'scratch, block: u32) -> &'scratch [u32] { |
| 285 | assert block < info.blockCount; |
| 286 | return &info.liveOut[block * info.words..(block + 1) * info.words]; |
| 287 | } |