lib/std/lang/gen/regalloc/liveness.rad 8.4 KiB raw
1
//! Backward dataflow liveness analysis.
2
//!
3
//! Computes live-in and live-out sets for each basic block. A register is
4
//! "live" at a program point if its value may be used on some path from that
5
//! point to program exit.
6
//!
7
//! Uses iterative dataflow analysis:
8
//!
9
//!   REPEAT UNTIL changed == false
10
//!     changed = false
11
//!     FOR EACH BLOCK b IN POST-ORDER DO
12
//!       newOut = UNION(liveIn[succ] FOR EACH SUCCESSOR OF b)
13
//!       newIn = uses[b] | (newOut - defs[b])
14
//!       IF newIn != liveIn[b] OR newOut != liveOut[b] THEN
15
//!         changed = true
16
//!         liveIn[b] = newIn
17
//!         liveOut[b] = newOut
18
//!
19
//! Usage:
20
//!
21
//!   let live = liveness::analyze(func, ...);
22
//!   if liveness::isLastUse(&live, func, blockIdx, instrIdx, reg) {
23
//!       // `reg` last use is at this instruction.
24
//!   }
25
26
use std::mem;
27
use std::lang::il;
28
use std::lang::alloc;
29
use std::lang::gen::bitset;
30
31
/// Maximum number of SSA registers supported.
32
pub const MAX_SSA_REGS: u32 = 8192;
33
34
/// Liveness information for a function.
35
pub record LiveInfo {
36
    /// Per-block live-in sets (indexed by block index).
37
    liveIn: *mut [bitset::Bitset],
38
    /// Per-block live-out sets (indexed by block index).
39
    liveOut: *mut [bitset::Bitset],
40
    /// Per-block defs sets (registers defined in block).
41
    defs: *mut [bitset::Bitset],
42
    /// Per-block uses sets (registers used before defined in block).
43
    uses: *mut [bitset::Bitset],
44
    /// Number of blocks.
45
    blockCount: u32,
46
    /// Maximum register number used.
47
    maxReg: u32,
48
}
49
50
/// Context for collecting defs and uses during block analysis.
51
record DefsUses {
52
    defs: *bitset::Bitset,
53
    uses: *mut bitset::Bitset,
54
}
55
56
/// Context for searching for a specific register in an instruction.
57
record FindCtx {
58
    target: u32,
59
    found: bool,
60
}
61
62
/// Compute liveness information for a function.
63
pub fn analyze(func: *il::Fn, arena: *mut alloc::Arena) -> LiveInfo throws (alloc::AllocError) {
64
    let blockCount = func.blocks.len;
65
    if blockCount == 0 {
66
        return LiveInfo {
67
            liveIn: &mut [],
68
            liveOut: &mut [],
69
            defs: &mut [],
70
            uses: &mut [],
71
            blockCount: 0,
72
            maxReg: 0,
73
        };
74
    }
75
76
    // Find max register number.
77
    let mut maxReg: u32 = 0;
78
    for p in func.params {
79
        maxReg = maxRegNum(p.value.n, maxReg);
80
    }
81
    for b in 0..blockCount {
82
        let block = &func.blocks[b];
83
        for p in block.params {
84
            maxReg = maxRegNum(p.value.n, maxReg);
85
        }
86
        for i in 0..block.instrs.len {
87
            il::forEachReg(block.instrs[i], maxRegCallback, &mut maxReg as *mut opaque);
88
            if let dst = il::instrDst(block.instrs[i]) {
89
                maxReg = maxRegNum(dst.n, maxReg);
90
            }
91
        }
92
    }
93
    assert maxReg <= MAX_SSA_REGS, "analyze: maximum SSA registers exceeded";
94
    // Allocate per-block bitsets.
95
    let liveIn = try alloc::allocSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *mut [bitset::Bitset];
96
    let liveOut = try alloc::allocSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *mut [bitset::Bitset];
97
    let defs = try alloc::allocSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *mut [bitset::Bitset];
98
    let uses = try alloc::allocSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *mut [bitset::Bitset];
99
100
    for b in 0..blockCount {
101
        liveIn[b] = try bitset::allocate(arena, maxReg);
102
        liveOut[b] = try bitset::allocate(arena, maxReg);
103
        defs[b] = try bitset::allocate(arena, maxReg);
104
        uses[b] = try bitset::allocate(arena, maxReg);
105
    }
106
107
    // Compute local defs and uses for each block.
108
    for b in 0..blockCount {
109
        computeLocalDefsUses(&func.blocks[b], &mut defs[b], &mut uses[b]);
110
    }
111
    // Iterative dataflow analysis.
112
    let mut changed = true;
113
    let mut scratch = try bitset::allocate(arena, maxReg);
114
115
    while changed {
116
        changed = false;
117
118
        // Process blocks in reverse order (approximates post-order).
119
        let mut b = blockCount;
120
        while b > 0 {
121
            b -= 1;
122
            let block = &func.blocks[b];
123
124
            // Compute new `liveOut` as union of successor `liveIn` sets.
125
            bitset::clearAll(&mut scratch);
126
            addSuccessorLiveIn(func, block, liveIn, &mut scratch);
127
128
            if not bitset::eq(&liveOut[b], &scratch) {
129
                bitset::copy(&mut liveOut[b], &scratch);
130
                changed = true;
131
            }
132
            if computeAndUpdateLiveIn(&mut liveIn[b], &liveOut[b], &defs[b], &uses[b]) {
133
                changed = true;
134
            }
135
        }
136
    }
137
    return LiveInfo { liveIn, liveOut, defs, uses, blockCount, maxReg };
138
}
139
140
/// Compute `liveIn = uses | (liveOut - defs)` and update `dst`.
141
/// Returns `true` if `dst` changed. Combined loop avoids multiple passes.
142
fn computeAndUpdateLiveIn(
143
    dst: *mut bitset::Bitset,
144
    liveOut: *bitset::Bitset,
145
    defs: *bitset::Bitset,
146
    uses: *bitset::Bitset
147
) -> bool {
148
    let numWords = dst.bits.len;
149
    let mut changed = false;
150
    for i in 0..numWords {
151
        let newWord = uses.bits[i] | (liveOut.bits[i] & ~defs.bits[i]);
152
        if dst.bits[i] != newWord {
153
            dst.bits[i] = newWord;
154
            changed = true;
155
        }
156
    }
157
    return changed;
158
}
159
160
/// Compute local defs and uses for a single block.
161
fn computeLocalDefsUses(block: *il::Block, defs: *mut bitset::Bitset, uses: *mut bitset::Bitset) {
162
    for p in block.params {
163
        bitset::set(defs, p.value.n);
164
    }
165
    for i in 0..block.instrs.len {
166
        let instr = block.instrs[i];
167
        let mut ctx = DefsUses { defs, uses };
168
        il::forEachReg(instr, addUseCallback, &mut ctx as *mut opaque);
169
170
        if let dst = il::instrDst(instr) {
171
            bitset::set(defs, dst.n);
172
        }
173
    }
174
}
175
176
/// Callback for [`il::forEachReg`]: adds register to uses if not already defined.
177
fn addUseCallback(reg: il::Reg, ctx: *mut opaque) {
178
    let c = ctx as *mut DefsUses;
179
    if not bitset::contains(c.defs, reg.n) {
180
        bitset::set(c.uses, reg.n);
181
    }
182
}
183
184
/// Callback for [`il::forEachReg`]: updates max register number.
185
fn maxRegCallback(reg: il::Reg, ctx: *mut opaque) {
186
    let max = ctx as *mut u32;
187
    *max = maxRegNum(reg.n, *max);
188
}
189
190
/// Return the larger of n+1 and current.
191
fn maxRegNum(n: u32, current: u32) -> u32 {
192
    if n + 1 > current {
193
        return n + 1;
194
    }
195
    return current;
196
}
197
198
/// Add successor "live in" sets to the scratch bitset.
199
fn addSuccessorLiveIn(func: *il::Fn, block: *il::Block, liveIn: *[bitset::Bitset], scratch: *mut bitset::Bitset) {
200
    if block.instrs.len == 0 {
201
        return;
202
    }
203
    let term = block.instrs[block.instrs.len - 1];
204
205
    match term {
206
        case il::Instr::Jmp { target, .. } =>
207
            unionBlockLiveIn(target, liveIn, scratch),
208
        case il::Instr::Br { thenTarget, elseTarget, .. } => {
209
            unionBlockLiveIn(thenTarget, liveIn, scratch);
210
            unionBlockLiveIn(elseTarget, liveIn, scratch);
211
        },
212
        case il::Instr::Switch { defaultTarget, cases, .. } => {
213
            unionBlockLiveIn(defaultTarget, liveIn, scratch);
214
            for c in cases {
215
                unionBlockLiveIn(c.target, liveIn, scratch);
216
            }
217
        },
218
        else => {},
219
    }
220
}
221
222
/// Union a target block's "live in" set into scratch.
223
fn unionBlockLiveIn(target: u32, liveIn: *[bitset::Bitset], scratch: *mut bitset::Bitset) {
224
    bitset::union_(scratch, &liveIn[target]);
225
}
226
227
/// Check if this is the last use of a register at this instruction.
228
pub fn isLastUse(info: *LiveInfo, func: *il::Fn, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool {
229
    let block = &func.blocks[blockIdx];
230
    let instr = block.instrs[instrIdx];
231
232
    if not instrUsesReg(instr, reg) {
233
        return false;
234
    }
235
    if bitset::contains(&info.liveOut[blockIdx], reg.n) {
236
        return false;
237
    }
238
    for i in (instrIdx + 1)..block.instrs.len {
239
        if instrUsesReg(block.instrs[i], reg) {
240
            return false;
241
        }
242
    }
243
    return true;
244
}
245
246
/// Check if an instruction uses a specific register.
247
fn instrUsesReg(instr: il::Instr, reg: il::Reg) -> bool {
248
    let mut ctx = FindCtx { target: reg.n, found: false };
249
    il::forEachReg(instr, findRegCallback, &mut ctx as *mut opaque);
250
    return ctx.found;
251
}
252
253
/// Callback for [`il::forEachReg`]: sets found if register matches target.
254
fn findRegCallback(reg: il::Reg, ctx: *mut opaque) {
255
    let c = ctx as *mut FindCtx;
256
    if reg.n == c.target {
257
        c.found = true;
258
    }
259
}