lib/std/lang/gen/regalloc/assign.rad 10.4 KiB raw
1
//! Register assignment.
2
//!
3
//! This pass assigns physical registers to SSA values. It runs after spilling
4
//! has ensured register pressure never exceeds available registers.
5
//!
6
//! The IL is not modified. Instead, a mapping from SSA registers to physical
7
//! registers is produced for use by instruction selection.
8
9
use std::lang::il;
10
use std::lang::alloc;
11
use std::lang::gen::bitset;
12
use std::lang::gen::regalloc::liveness;
13
use std::lang::gen::regalloc::spill;
14
15
/// Maximum number of active register mappings.
16
const MAX_ACTIVE: u32 = 64;
17
18
/// Register mapping at a program point.
19
/// Maps SSA registers to physical registers.
20
pub record RegMap {
21
    /// SSA (virtual) registers that have mappings.
22
    virtRegs: *mut [u32],
23
    /// Physical register for each virtual register.
24
    physRegs: *mut [u8],
25
    /// Number of active mappings.
26
    n: u32,
27
}
28
29
/// Register assignment result, per function.
30
pub record AssignInfo {
31
    /// SSA register -> physical register mapping.
32
    assignments: *mut [?u8],
33
    /// Bitmask of used callee-saved registers.
34
    usedCalleeSaved: u32,
35
}
36
37
/// Per-instruction context for freeing and allocating register uses.
38
record InstrCtx {
39
    current: *mut RegMap,
40
    usedRegs: *mut bitset::Bitset,
41
    func: *il::Fn,
42
    live: *liveness::LiveInfo,
43
    blockIdx: u32,
44
    instrIdx: u32,
45
    allocatable: *[u8],
46
    calleeSaved: *[u8],
47
    assignments: *mut [?u8],
48
    spillInfo: *spill::SpillInfo,
49
}
50
51
/// Compute register assignment.
52
pub fn assign(
53
    func: *il::Fn,
54
    live: *liveness::LiveInfo,
55
    spillInfo: *spill::SpillInfo,
56
    config: *super::TargetConfig,
57
    arena: *mut alloc::Arena
58
) -> AssignInfo throws (alloc::AllocError) {
59
    let maxReg = live.maxReg;
60
    let blockCount = func.blocks.len;
61
    let allocatable = config.allocatable;
62
63
    if maxReg == 0 or blockCount == 0 {
64
        return AssignInfo {
65
            assignments: &mut [],
66
            usedCalleeSaved: 0,
67
        };
68
    }
69
70
    // Allocate output structures.
71
    let assignments = try alloc::allocSlice(arena, @sizeOf(?u8), @alignOf(?u8), maxReg) as *mut [?u8];
72
    for i in 0..maxReg {
73
        assignments[i] = nil;
74
    }
75
    // Pre-assign function parameters to argument registers.
76
    // Cross-call params are NOT pre-assigned here; they will be allocated
77
    // to callee-saved registers by the normal path, and isel emits moves
78
    // from the arg register to the assigned register at function entry.
79
    for param, i in func.params {
80
        if i < config.argRegs.len {
81
            if not bitset::contains(&spillInfo.calleeClass, param.value.n) {
82
                assignments[param.value.n] = config.argRegs[i];
83
            }
84
        }
85
    }
86
    let blkEnd = try alloc::allocSlice(arena, @sizeOf(RegMap), @alignOf(RegMap), blockCount) as *mut [RegMap];
87
    for i in 0..blockCount {
88
        blkEnd[i] = try createRegMap(arena);
89
    }
90
    // Allocate used registers bitset (32 physical registers per 32-bit word).
91
    let usedRegsBits = try alloc::allocSlice(arena, @sizeOf(u32), @alignOf(u32), 1) as *mut [u32];
92
    let mut usedRegs = bitset::init(usedRegsBits);
93
94
    // Current register mapping.
95
    let mut current = try createRegMap(arena);
96
97
    // Phase 2: Linear scan allocation.
98
    for b in 0..blockCount {
99
        let block = &func.blocks[b];
100
101
        // Reset for new block.
102
        current.n = 0;
103
        bitset::clearAll(&mut usedRegs);
104
105
        // Initialize from predecessor if single predecessor.
106
        // Only copy values that are live-in to this block.
107
        if block.preds.len == 1 {
108
            let pred = &blkEnd[block.preds[0]];
109
            for k in 0..pred.n {
110
                if bitset::contains(&live.liveIn[b], pred.virtRegs[k]) {
111
                    current.virtRegs[current.n] = pred.virtRegs[k];
112
                    current.physRegs[current.n] = pred.physRegs[k];
113
                    current.n += 1;
114
                    bitset::set(&mut usedRegs, pred.physRegs[k] as u32);
115
                }
116
            }
117
        }
118
119
        // Mark all live-in values' registers as used.
120
        // This ensures we don't reuse registers for values that flow in
121
        // from predecessors, even at merge points with multiple predecessors.
122
        // Values that are live-in but have no assignment yet (e.g. callee-saved
123
        // function parameters not used before a phi block) are allocated now to
124
        // prevent conflicts with block parameters.
125
        let mut liveInIter = bitset::iter(&live.liveIn[b]);
126
        while let ssaReg = bitset::iterNext(&mut liveInIter) {
127
            let reg = il::Reg { n: ssaReg };
128
            if not spill::isSpilled(spillInfo, reg) {
129
                if let phys = assignments[ssaReg] {
130
                    bitset::set(&mut usedRegs, phys as u32);
131
                    // Also track in current mapping for block-end state.
132
                    if rmapFind(&current, ssaReg) == nil {
133
                        rmapSet(&mut current, ssaReg, phys);
134
                    }
135
                } else {
136
                    assignments[ssaReg] = rallocReg(&mut current, &mut usedRegs, ssaReg, allocatable, config.calleeSaved, spillInfo);
137
                }
138
            }
139
        }
140
141
        // Allocate block parameters.
142
        for p in block.params {
143
            if p.value.n < maxReg and not spill::isSpilled(spillInfo, p.value) {
144
                assignments[p.value.n] = rallocReg(&mut current, &mut usedRegs, p.value.n, allocatable, config.calleeSaved, spillInfo);
145
            }
146
        }
147
148
        // Process each instruction.
149
        for instr, i in block.instrs {
150
            let mut ctx = InstrCtx {
151
                current: &mut current,
152
                usedRegs: &mut usedRegs,
153
                func,
154
                live,
155
                blockIdx: b,
156
                instrIdx: i,
157
                allocatable,
158
                calleeSaved: config.calleeSaved,
159
                assignments,
160
                spillInfo,
161
            };
162
            il::forEachReg(instr, processInstrRegCb, &mut ctx as *mut opaque);
163
164
            // Allocate destination.
165
            if let dst = il::instrDst(instr) {
166
                if dst.n < maxReg and not spill::isSpilled(spillInfo, dst) {
167
                    assignments[dst.n] = rallocReg(&mut current, &mut usedRegs, dst.n, allocatable, config.calleeSaved, spillInfo);
168
                }
169
            }
170
        }
171
        // Save block-end state.
172
        rmapCopy(&mut blkEnd[b], &current);
173
    }
174
    // Compute bitmask of used callee-saved registers.
175
    let mut usedCalleeSaved: u32 = 0;
176
    for i in 0..maxReg {
177
        if let phys = assignments[i] {
178
            for saved, j in config.calleeSaved {
179
                if phys == saved {
180
                    usedCalleeSaved |= (1 << j);
181
                }
182
            }
183
        }
184
    }
185
186
    return AssignInfo {
187
        assignments,
188
        usedCalleeSaved,
189
    };
190
}
191
192
/// Create an empty register map.
193
fn createRegMap(arena: *mut alloc::Arena) -> RegMap throws (alloc::AllocError) {
194
    let virtRegs = try alloc::allocSlice(arena, @sizeOf(u32), @alignOf(u32), MAX_ACTIVE) as *mut [u32];
195
    let physRegs = try alloc::allocSlice(arena, @sizeOf(u8), @alignOf(u8), MAX_ACTIVE) as *mut [u8];
196
197
    return RegMap { virtRegs, physRegs, n: 0 };
198
}
199
200
/// Copy a register map.
201
fn rmapCopy(dst: *mut RegMap, src: *RegMap) {
202
    dst.n = src.n;
203
    for i in 0..src.n {
204
        dst.virtRegs[i] = src.virtRegs[i];
205
        dst.physRegs[i] = src.physRegs[i];
206
    }
207
}
208
209
/// Find physical register for a virtual register in RegMap.
210
fn rmapFind(rmap: *RegMap, virtReg: u32) -> ?u8 {
211
    for i in 0..rmap.n {
212
        if rmap.virtRegs[i] == virtReg {
213
            return rmap.physRegs[i];
214
        }
215
    }
216
    return nil;
217
}
218
219
/// Add a mapping to the register map.
220
fn rmapSet(rmap: *mut RegMap, virtReg: u32, physReg: u8) {
221
    if rmap.n >= MAX_ACTIVE {
222
        panic "rmapSet: register map overflow";
223
    }
224
    rmap.virtRegs[rmap.n] = virtReg;
225
    rmap.physRegs[rmap.n] = physReg;
226
    rmap.n += 1;
227
}
228
229
/// Remove a mapping from register map.
230
fn rmapRemove(rmap: *mut RegMap, virtReg: u32) {
231
    for i in 0..rmap.n {
232
        if rmap.virtRegs[i] == virtReg {
233
            // Swap with last and decrement.
234
            rmap.n -= 1;
235
            if i < rmap.n {
236
                rmap.virtRegs[i] = rmap.virtRegs[rmap.n];
237
                rmap.physRegs[i] = rmap.physRegs[rmap.n];
238
            }
239
            return;
240
        }
241
    }
242
    panic "rmapRemove: register not found";
243
}
244
245
/// Find first free register in pool, allocate it, return it.
246
fn findFreeInPool(usedRegs: *mut bitset::Bitset, current: *mut RegMap, ssaReg: u32, pool: *[u8]) -> ?u8 {
247
    for i in 0..pool.len {
248
        let r = pool[i];
249
        if not bitset::contains(usedRegs, r as u32) {
250
            bitset::set(usedRegs, r as u32);
251
            rmapSet(current, ssaReg, r);
252
            return r;
253
        }
254
    }
255
    return nil;
256
}
257
258
/// Allocate a physical register for an SSA register.
259
/// Cross-call values are steered to callee-saved registers.
260
fn rallocReg(
261
    current: *mut RegMap,
262
    usedRegs: *mut bitset::Bitset,
263
    ssaReg: u32,
264
    allocatable: *[u8],
265
    calleeSaved: *[u8],
266
    spillInfo: *spill::SpillInfo
267
) -> u8 {
268
    // Check if already assigned.
269
    if let phys = rmapFind(current, ssaReg) {
270
        return phys;
271
    }
272
    let crossCall = bitset::contains(&spillInfo.calleeClass, ssaReg);
273
    // Allocate from appropriate pool. Cross-call values must use callee-saved
274
    // registers since they are live across function calls.
275
    if crossCall {
276
        if let r = findFreeInPool(usedRegs, current, ssaReg, calleeSaved) {
277
            return r;
278
        }
279
        panic "rallocReg: no callee-saved register for cross-call value";
280
    }
281
    if let r = findFreeInPool(usedRegs, current, ssaReg, allocatable) {
282
        return r;
283
    }
284
    panic "rallocReg: no free register, spilling fault";
285
}
286
287
/// Callback for [`il::forEachReg`]: free last uses and allocate missing uses.
288
fn processInstrRegCb(reg: il::Reg, ctxPtr: *mut opaque) {
289
    let ctx = ctxPtr as *mut InstrCtx;
290
    if liveness::isLastUse(ctx.live, ctx.func, ctx.blockIdx, ctx.instrIdx, reg) {
291
        if let phys = rmapFind(ctx.current, reg.n) {
292
            bitset::clear(ctx.usedRegs, phys as u32);
293
            rmapRemove(ctx.current, reg.n);
294
        }
295
    }
296
    if reg.n >= ctx.assignments.len {
297
        panic "processInstrRegCb: register out of bounds";
298
    }
299
    if spill::isSpilled(ctx.spillInfo, reg) {
300
        return; // Spilled values don't get physical registers.
301
    }
302
    if ctx.assignments[reg.n] == nil {
303
        ctx.assignments[reg.n] = rallocReg(
304
            ctx.current, ctx.usedRegs, reg.n, ctx.allocatable,
305
            ctx.calleeSaved, ctx.spillInfo
306
        );
307
    }
308
}