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