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