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 not liveness::hasLaterUse(&live, func, blockIdx, instrIdx, reg) {
23
//!       // `reg` dies 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
export constant MAX_SSA_REGS: u32 = 8192;
33
34
/// Liveness information for a function.
35
export 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
export 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
        set 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
            set maxReg = maxRegNum(p.value.n, maxReg);
85
        }
86
        for i in 0..block.instrs.len {
87
            il::forEachReg(block.instrs[i], maxRegCallback, &mut maxReg);
88
            if let dst = il::instrDst(block.instrs[i]) {
89
                set 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
        set liveIn[b] = try bitset::allocate(arena, maxReg);
102
        set liveOut[b] = try bitset::allocate(arena, maxReg);
103
        set defs[b] = try bitset::allocate(arena, maxReg);
104
        set 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
        set changed = false;
117
118
        // Process blocks in reverse order (approximates post-order).
119
        let mut b = blockCount;
120
        while b > 0 {
121
            set 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
                set changed = true;
131
            }
132
            if computeAndUpdateLiveIn(&mut liveIn[b], &liveOut[b], &defs[b], &uses[b]) {
133
                set 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
            set dst.bits[i] = newWord;
154
            set 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::put(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);
169
170
        if let dst = il::instrDst(instr) {
171
            bitset::put(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
    if not bitset::contains((ctx as &mut DefsUses).defs, reg.n) {
179
        bitset::put((ctx as &mut DefsUses).uses, reg.n);
180
    }
181
}
182
183
/// Callback for [`il::forEachReg`]: updates max register number.
184
fn maxRegCallback(reg: il::Reg, ctx: &mut opaque) {
185
    set *(ctx as &mut u32) = maxRegNum(reg.n, *(ctx as &mut u32));
186
}
187
188
/// Return the larger of n+1 and current.
189
fn maxRegNum(n: u32, current: u32) -> u32 {
190
    if n + 1 > current {
191
        return n + 1;
192
    }
193
    return current;
194
}
195
196
/// Add successor "live in" sets to the scratch bitset.
197
fn addSuccessorLiveIn(func: *il::Fn, block: *il::Block, liveIn: *[bitset::Bitset], scratch: *mut bitset::Bitset) {
198
    if block.instrs.len == 0 {
199
        return;
200
    }
201
    let term = block.instrs[block.instrs.len - 1];
202
203
    match term {
204
        case il::Instr::Jmp { target, .. } =>
205
            unionBlockLiveIn(target, liveIn, scratch),
206
        case il::Instr::Br { thenTarget, elseTarget, .. } => {
207
            unionBlockLiveIn(thenTarget, liveIn, scratch);
208
            unionBlockLiveIn(elseTarget, liveIn, scratch);
209
        },
210
        case il::Instr::Switch { defaultTarget, cases, .. } => {
211
            unionBlockLiveIn(defaultTarget, liveIn, scratch);
212
            for c in cases {
213
                unionBlockLiveIn(c.target, liveIn, scratch);
214
            }
215
        },
216
        else => {},
217
    }
218
}
219
220
/// Union a target block's "live in" set into scratch.
221
fn unionBlockLiveIn(target: u32, liveIn: *[bitset::Bitset], scratch: *mut bitset::Bitset) {
222
    bitset::union_(scratch, &liveIn[target]);
223
}
224
225
/// Check if a register has any use after this instruction.
226
export fn hasLaterUse(info: *LiveInfo, func: *il::Fn, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool {
227
    let block = &func.blocks[blockIdx];
228
229
    if bitset::contains(&info.liveOut[blockIdx], reg.n) {
230
        return true;
231
    }
232
    for i in (instrIdx + 1)..block.instrs.len {
233
        if instrUsesReg(block.instrs[i], reg) {
234
            return true;
235
        }
236
    }
237
    return false;
238
}
239
240
/// Check if an instruction uses a specific register.
241
fn instrUsesReg(instr: il::Instr, reg: il::Reg) -> bool {
242
    let mut ctx = FindCtx { target: reg.n, found: false };
243
    il::forEachReg(instr, findRegCallback, &mut ctx);
244
    return ctx.found;
245
}
246
247
/// Callback for [`il::forEachReg`]: sets found if register matches target.
248
fn findRegCallback(reg: il::Reg, ctx: &mut opaque) {
249
    if reg.n == (ctx as &mut FindCtx).target {
250
        markFound(ctx as &mut FindCtx);
251
    }
252
}
253
254
/// Mark a register search context as found.
255
fn markFound(ctx: &mut FindCtx) {
256
    set ctx.found = true;
257
}