lib/std/lang/gen/regalloc/assign.rad 10.5 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: Copy {
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: *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: *mut RegMap,
41
    usedRegs: *mut bitset::Bitset,
42
    /// Last operand-use index for each register used in the current block.
43
    lastUse: *[u32],
44
    live: *liveness::LiveInfo,
45
    blockIdx: u32,
46
    instrIdx: u32,
47
    allocatable: *[gen::Reg],
48
    calleeSaved: *[gen::Reg],
49
    assignments: *mut [?gen::Reg],
50
    spillInfo: *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: *mut [u32],
57
    /// Index of the instruction whose operands are being recorded.
58
    index: u32,
59
}
60
61
/// Compute register assignment.
62
export fn assign(
63
    func: *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::allocSlice(arena, @sizeOf(?gen::Reg), @alignOf(?gen::Reg), maxReg) as *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::allocSlice(arena, @sizeOf(u32), @alignOf(u32), maxReg) as *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::allocSlice(arena, @sizeOf(u32), @alignOf(u32), 1) as *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,
154
                blockIdx: b,
155
                instrIdx: i,
156
                allocatable,
157
                calleeSaved: config.calleeSaved,
158
                assignments,
159
                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
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
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
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
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.
281
fn processInstrRegCb(reg: il::Reg, ctxPtr: *mut opaque) {
282
    let ctx = ctxPtr as *mut InstrCtx;
283
    if not (bitset::contains(&ctx.live.liveOut[ctx.blockIdx], reg.n) or ctx.lastUse[reg.n] > ctx.instrIdx) {
284
        if let phys = rmapRemove(ctx.current, reg.n) {
285
            bitset::clear(ctx.usedRegs, *phys as u32);
286
        }
287
    }
288
    assert reg.n < ctx.assignments.len, "processInstrRegCb: register out of bounds";
289
    if spill::isSpilled(ctx.spillInfo, reg) {
290
        return; // Spilled values don't get physical registers.
291
    }
292
    if ctx.assignments[reg.n] == nil {
293
        set ctx.assignments[reg.n] = rallocReg(
294
            ctx.current, ctx.usedRegs, reg.n, ctx.allocatable,
295
            ctx.calleeSaved, ctx.spillInfo
296
        );
297
    }
298
}