regalloc: Index last operand uses per block

95ea08b12d856da1d042dcbb32db0513e4183cf719e317e756e86ffa0aa206d2
Repeated searches through later instructions duplicate operand work.
Record last-use indexes once per block and combine them with live-out
membership during allocation. Keep the public liveness query API.

Assisted-by: Codex:gpt-6-astra
Alexis Sellier committed ago 1 parent 7c9a7f91
lib/std/lang/gen/regalloc/assign.rad +29 -4
37 37
38 38
/// Per-instruction context for freeing and allocating register uses.
39 39
record InstrCtx: Copy {
40 40
    current: *mut RegMap,
41 41
    usedRegs: *mut bitset::Bitset,
42 -
    func: *il::Fn,
42 +
    /// Last operand-use index for each register used in the current block.
43 +
    lastUse: *[u32],
43 44
    live: *liveness::LiveInfo,
44 45
    blockIdx: u32,
45 46
    instrIdx: u32,
46 47
    allocatable: *[gen::Reg],
47 48
    calleeSaved: *[gen::Reg],
48 49
    assignments: *mut [?gen::Reg],
49 50
    spillInfo: *spill::SpillInfo,
50 51
}
51 52
53 +
/// Context for recording the last operand-use index in a block.
54 +
record LastUseCtx: Copy {
55 +
    /// Per-register indices, shared by all blocks in the function.
56 +
    lastUse: *mut [u32],
57 +
    /// Index of the instruction whose operands are being recorded.
58 +
    index: u32,
59 +
}
60 +
52 61
/// Compute register assignment.
53 62
export fn assign(
54 63
    func: *il::Fn,
55 64
    live: *liveness::LiveInfo,
56 65
    spillInfo: *spill::SpillInfo,
71 80
    // Allocate output structures.
72 81
    let assignments = try alloc::allocSlice(arena, @sizeOf(?gen::Reg), @alignOf(?gen::Reg), maxReg) as *mut [?gen::Reg];
73 82
    for i in 0..maxReg {
74 83
        set assignments[i] = nil;
75 84
    }
85 +
    // Reuse one last-use table for all blocks in the function.
86 +
    let lastUse = try alloc::allocSlice(arena, @sizeOf(u32), @alignOf(u32), maxReg) as *mut [u32];
76 87
    // Pre-assign function parameters to argument registers.
77 88
    // Cross-call params are NOT pre-assigned here; they will be allocated
78 89
    // to callee-saved registers by the normal path, and isel emits moves
79 90
    // from the arg register to the assigned register at function entry.
80 91
    for param, i in func.params {
93 104
94 105
    // Phase 2: Linear scan allocation.
95 106
    for b in 0..blockCount {
96 107
        let block = &func.blocks[b];
97 108
109 +
        // Record every operand before allocation. Only current-block operands
110 +
        // query this table, so every read is initialized by this scan.
111 +
        // Entries for other registers need not be cleared between blocks.
112 +
        for instr, i in block.instrs {
113 +
            let mut ctx = LastUseCtx { lastUse, index: i };
114 +
            il::forEachReg(instr, recordLastUseCb, &mut ctx as *mut opaque);
115 +
        }
116 +
98 117
        // Reset for new block.
99 118
        set current.n = 0;
100 119
        bitset::clearAll(&mut usedRegs);
101 120
102 121
        // Mark all live-in values' registers as used.
128 147
        // Process each instruction.
129 148
        for instr, i in block.instrs {
130 149
            let mut ctx = InstrCtx {
131 150
                current: &mut current,
132 151
                usedRegs: &mut usedRegs,
133 -
                func,
152 +
                lastUse,
134 153
                live,
135 154
                blockIdx: b,
136 155
                instrIdx: i,
137 156
                allocatable,
138 157
                calleeSaved: config.calleeSaved,
250 269
        return r;
251 270
    }
252 271
    panic "rallocReg: no free register, spilling fault";
253 272
}
254 273
255 -
/// Callback for [`il::forEachReg`]: free last uses and allocate missing uses.
274 +
/// Record the current index; forward traversal leaves the last operand use.
275 +
fn recordLastUseCb(reg: il::Reg, ctxPtr: *mut opaque) {
276 +
    let ctx = ctxPtr as *mut LastUseCtx;
277 +
    set ctx.lastUse[reg.n] = ctx.index;
278 +
}
279 +
280 +
/// Free operands with no later block use or live-out use, then allocate missing uses.
256 281
fn processInstrRegCb(reg: il::Reg, ctxPtr: *mut opaque) {
257 282
    let ctx = ctxPtr as *mut InstrCtx;
258 -
    if not liveness::hasLaterUse(ctx.live, ctx.func, ctx.blockIdx, ctx.instrIdx, reg) {
283 +
    if not (bitset::contains(&ctx.live.liveOut[ctx.blockIdx], reg.n) or ctx.lastUse[reg.n] > ctx.instrIdx) {
259 284
        if let phys = rmapRemove(ctx.current, reg.n) {
260 285
            bitset::clear(ctx.usedRegs, *phys as u32);
261 286
        }
262 287
    }
263 288
    assert reg.n < ctx.assignments.len, "processInstrRegCb: register out of bounds";