regalloc: Avoid rescanning current operands
80166a832bc5984877f11eb326ffff6d40a6ef5baf5b3f294b4ae192c7223112
`isLastUse` searched the current instruction even though its only caller invoked it while visiting an operand of that instruction. Express the query as `hasLaterUse` and inspect only later instructions and live-out state. This removes one complete operand traversal from every last-use query. Assisted-by: Codex:gpt-5.6-sol
1 parent
abd19748
lib/std/lang/gen/regalloc/assign.rad
+1 -1
| 279 | 279 | } |
|
| 280 | 280 | ||
| 281 | 281 | /// Callback for [`il::forEachReg`]: free last uses and allocate missing uses. |
|
| 282 | 282 | fn processInstrRegCb(reg: il::Reg, ctxPtr: *mut opaque) { |
|
| 283 | 283 | let ctx = ctxPtr as *mut InstrCtx; |
|
| 284 | - | if liveness::isLastUse(ctx.live, ctx.func, ctx.blockIdx, ctx.instrIdx, reg) { |
|
| 284 | + | if not liveness::hasLaterUse(ctx.live, ctx.func, ctx.blockIdx, ctx.instrIdx, reg) { |
|
| 285 | 285 | if let phys = rmapFind(ctx.current, reg.n) { |
|
| 286 | 286 | bitset::clear(ctx.usedRegs, *phys as u32); |
|
| 287 | 287 | rmapRemove(ctx.current, reg.n); |
|
| 288 | 288 | } |
|
| 289 | 289 | } |
lib/std/lang/gen/regalloc/liveness.rad
+7 -11
| 17 | 17 | //! liveOut[b] = newOut |
|
| 18 | 18 | //! |
|
| 19 | 19 | //! Usage: |
|
| 20 | 20 | //! |
|
| 21 | 21 | //! let live = liveness::analyze(func, ...); |
|
| 22 | - | //! if liveness::isLastUse(&live, func, blockIdx, instrIdx, reg) { |
|
| 23 | - | //! // `reg` last use is at this instruction. |
|
| 22 | + | //! if not liveness::hasLaterUse(&live, func, blockIdx, instrIdx, reg) { |
|
| 23 | + | //! // `reg` dies at this instruction. |
|
| 24 | 24 | //! } |
|
| 25 | 25 | ||
| 26 | 26 | use std::mem; |
|
| 27 | 27 | use std::lang::il; |
|
| 28 | 28 | use std::lang::alloc; |
| 222 | 222 | /// Union a target block's "live in" set into scratch. |
|
| 223 | 223 | fn unionBlockLiveIn(target: u32, liveIn: *[bitset::Bitset], scratch: *mut bitset::Bitset) { |
|
| 224 | 224 | bitset::union_(scratch, &liveIn[target]); |
|
| 225 | 225 | } |
|
| 226 | 226 | ||
| 227 | - | /// Check if this is the last use of a register at this instruction. |
|
| 228 | - | export fn isLastUse(info: *LiveInfo, func: *il::Fn, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool { |
|
| 227 | + | /// Check if a register has any use after this instruction. |
|
| 228 | + | export fn hasLaterUse(info: *LiveInfo, func: *il::Fn, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool { |
|
| 229 | 229 | let block = &func.blocks[blockIdx]; |
|
| 230 | - | let instr = block.instrs[instrIdx]; |
|
| 231 | 230 | ||
| 232 | - | if not instrUsesReg(instr, reg) { |
|
| 233 | - | return false; |
|
| 234 | - | } |
|
| 235 | 231 | if bitset::contains(&info.liveOut[blockIdx], reg.n) { |
|
| 236 | - | return false; |
|
| 232 | + | return true; |
|
| 237 | 233 | } |
|
| 238 | 234 | for i in (instrIdx + 1)..block.instrs.len { |
|
| 239 | 235 | if instrUsesReg(block.instrs[i], reg) { |
|
| 240 | - | return false; |
|
| 236 | + | return true; |
|
| 241 | 237 | } |
|
| 242 | 238 | } |
|
| 243 | - | return true; |
|
| 239 | + | return false; |
|
| 244 | 240 | } |
|
| 245 | 241 | ||
| 246 | 242 | /// Check if an instruction uses a specific register. |
|
| 247 | 243 | fn instrUsesReg(instr: il::Instr, reg: il::Reg) -> bool { |
|
| 248 | 244 | let mut ctx = FindCtx { target: reg.n, found: false }; |