regalloc: Grow liveness sets monotonically
2c74c0a9818708dfff9a32e527ddac87b405b62c51e88101a7f631eb5238adb1
Liveness sets start empty and only grow while the graph is fixed. Accumulate successor live-in sets directly into live-out to avoid scratch allocation, clearing, comparison, and copying. Assisted-by: Codex:gpt-6-astra
1 parent
02871810
lib/std/lang/gen/regalloc/liveness.rad
+20 -25
| 2 | 2 | //! |
|
| 3 | 3 | //! Computes live-in and live-out sets for each basic block. A register is |
|
| 4 | 4 | //! "live" at a program point if its value may be used on some path from that |
|
| 5 | 5 | //! point to program exit. |
|
| 6 | 6 | //! |
|
| 7 | - | //! Uses iterative dataflow analysis: |
|
| 7 | + | //! Uses monotone iterative dataflow analysis. All sets start empty and grow: |
|
| 8 | 8 | //! |
|
| 9 | 9 | //! REPEAT UNTIL changed == false |
|
| 10 | 10 | //! changed = false |
|
| 11 | 11 | //! FOR EACH BLOCK b IN POST-ORDER DO |
|
| 12 | - | //! newOut = UNION(liveIn[succ] FOR EACH SUCCESSOR OF b) |
|
| 13 | - | //! newIn = uses[b] | (newOut - defs[b]) |
|
| 14 | - | //! IF newIn <> liveIn[b] OR newOut <> liveOut[b] THEN |
|
| 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 | 15 | //! changed = true |
|
| 16 | 16 | //! liveIn[b] = newIn |
|
| 17 | - | //! liveOut[b] = newOut |
|
| 17 | + | //! |
|
| 18 | + | //! Only live-in changes require another pass, since predecessors read live-in. |
|
| 18 | 19 | //! |
|
| 19 | 20 | //! Usage: |
|
| 20 | 21 | //! |
|
| 21 | 22 | //! let live = liveness::analyze(func, ...); |
|
| 22 | 23 | //! if not liveness::hasLaterUse(&live, func, blockIdx, instrIdx, reg) { |
| 57 | 58 | record FindCtx: Copy { |
|
| 58 | 59 | target: u32, |
|
| 59 | 60 | found: bool, |
|
| 60 | 61 | } |
|
| 61 | 62 | ||
| 62 | - | /// Compute liveness information for a function. |
|
| 63 | + | /// Compute liveness by growing live sets until no live-in set changes. |
|
| 63 | 64 | export fn analyze(func: *il::Fn, arena: *mut alloc::Arena) -> LiveInfo throws (alloc::AllocError) { |
|
| 64 | 65 | let blockCount = func.blocks.len; |
|
| 65 | 66 | if blockCount == 0 { |
|
| 66 | 67 | return LiveInfo { |
|
| 67 | 68 | liveIn: &mut [], |
| 106 | 107 | ||
| 107 | 108 | // Compute local defs and uses for each block. |
|
| 108 | 109 | for b in 0..blockCount { |
|
| 109 | 110 | computeLocalDefsUses(&func.blocks[b], &mut defs[b], &mut uses[b]); |
|
| 110 | 111 | } |
|
| 111 | - | // Iterative dataflow analysis. |
|
| 112 | + | // Live sets grow monotonically from empty sets; no scratch set is needed. |
|
| 112 | 113 | let mut changed = true; |
|
| 113 | - | let mut scratch = try bitset::allocate(arena, maxReg); |
|
| 114 | 114 | ||
| 115 | 115 | while changed { |
|
| 116 | 116 | set changed = false; |
|
| 117 | 117 | ||
| 118 | 118 | // Process blocks in reverse order (approximates post-order). |
|
| 119 | 119 | let mut b = blockCount; |
|
| 120 | 120 | while b > 0 { |
|
| 121 | 121 | set b -= 1; |
|
| 122 | 122 | let block = &func.blocks[b]; |
|
| 123 | 123 | ||
| 124 | - | // Compute new `liveOut` as union of successor `liveIn` sets. |
|
| 125 | - | bitset::clearAll(&mut scratch); |
|
| 126 | - | addSuccessorLiveIn(func, block, liveIn, &mut scratch); |
|
| 124 | + | // Add successor live-in sets directly to this block's live-out set. |
|
| 125 | + | addSuccessorLiveIn(func, block, liveIn, &mut liveOut[b]); |
|
| 127 | 126 | ||
| 128 | - | if not bitset::eq(&liveOut[b], &scratch) { |
|
| 129 | - | bitset::copy(&mut liveOut[b], &scratch); |
|
| 130 | - | set changed = true; |
|
| 131 | - | } |
|
| 132 | 127 | if computeAndUpdateLiveIn(&mut liveIn[b], &liveOut[b], &defs[b], &uses[b]) { |
|
| 133 | 128 | set changed = true; |
|
| 134 | 129 | } |
|
| 135 | 130 | } |
|
| 136 | 131 | } |
| 193 | 188 | return n + 1; |
|
| 194 | 189 | } |
|
| 195 | 190 | return current; |
|
| 196 | 191 | } |
|
| 197 | 192 | ||
| 198 | - | /// Add successor "live in" sets to the scratch bitset. |
|
| 199 | - | fn addSuccessorLiveIn(func: *il::Fn, block: *il::Block, liveIn: *[bitset::Bitset], scratch: *mut bitset::Bitset) { |
|
| 193 | + | /// Add successor live-in sets to the block's live-out set. |
|
| 194 | + | fn addSuccessorLiveIn(func: *il::Fn, block: *il::Block, liveIn: *[bitset::Bitset], liveOut: *mut bitset::Bitset) { |
|
| 200 | 195 | if block.instrs.len == 0 { |
|
| 201 | 196 | return; |
|
| 202 | 197 | } |
|
| 203 | 198 | let term = block.instrs[block.instrs.len - 1]; |
|
| 204 | 199 | ||
| 205 | 200 | match term { |
|
| 206 | 201 | case il::Instr::Jmp { target, .. } => |
|
| 207 | - | unionBlockLiveIn(target, liveIn, scratch), |
|
| 202 | + | unionBlockLiveIn(target, liveIn, liveOut), |
|
| 208 | 203 | case il::Instr::Br { thenTarget, elseTarget, .. } => { |
|
| 209 | - | unionBlockLiveIn(thenTarget, liveIn, scratch); |
|
| 210 | - | unionBlockLiveIn(elseTarget, liveIn, scratch); |
|
| 204 | + | unionBlockLiveIn(thenTarget, liveIn, liveOut); |
|
| 205 | + | unionBlockLiveIn(elseTarget, liveIn, liveOut); |
|
| 211 | 206 | }, |
|
| 212 | 207 | case il::Instr::Switch { defaultTarget, cases, .. } => { |
|
| 213 | - | unionBlockLiveIn(defaultTarget, liveIn, scratch); |
|
| 208 | + | unionBlockLiveIn(defaultTarget, liveIn, liveOut); |
|
| 214 | 209 | for c in cases { |
|
| 215 | - | unionBlockLiveIn(c.target, liveIn, scratch); |
|
| 210 | + | unionBlockLiveIn(c.target, liveIn, liveOut); |
|
| 216 | 211 | } |
|
| 217 | 212 | }, |
|
| 218 | 213 | else => {}, |
|
| 219 | 214 | } |
|
| 220 | 215 | } |
|
| 221 | 216 | ||
| 222 | - | /// Union a target block's "live in" set into scratch. |
|
| 223 | - | fn unionBlockLiveIn(target: u32, liveIn: *[bitset::Bitset], scratch: *mut bitset::Bitset) { |
|
| 224 | - | bitset::union_(scratch, &liveIn[target]); |
|
| 217 | + | /// Union a target block's live-in set into the block's live-out set. |
|
| 218 | + | fn unionBlockLiveIn(target: u32, liveIn: *[bitset::Bitset], liveOut: *mut bitset::Bitset) { |
|
| 219 | + | bitset::union_(liveOut, &liveIn[target]); |
|
| 225 | 220 | } |
|
| 226 | 221 | ||
| 227 | 222 | /// Check if a register has any use after this instruction. |
|
| 228 | 223 | export fn hasLaterUse(info: *LiveInfo, func: *il::Fn, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool { |
|
| 229 | 224 | let block = &func.blocks[blockIdx]; |