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
@test mod tests;
28
29
use std::lang::il;
30
use std::lang::alloc;
31
use std::lang::gen::bitset;
32
33
/// Maximum number of SSA registers supported.
34
export constant MAX_SSA_REGS: u32 = 8192;
35
36
/// Liveness information for a function.
37
export record LiveInfo: 'scratch + Copy {
38
    /// Per-block live-in words, stored in consecutive rows.
39
    liveIn: &'scratch [u32],
40
    /// Per-block live-out words, stored in consecutive rows.
41
    liveOut: &'scratch [u32],
42
    /// Per-block definition words, stored in consecutive rows.
43
    defs: &'scratch [u32],
44
    /// Per-block use-before-definition words, stored in consecutive rows.
45
    uses: &'scratch [u32],
46
    /// Number of words in one block row.
47
    words: u32,
48
    /// Number of blocks.
49
    blockCount: u32,
50
    /// Maximum register number used.
51
    maxReg: u32,
52
}
53
54
/// Compute liveness by growing live sets until no live-in set changes.
55
export unsafe fn analyze 'scratch (func: &il::Fn, storage: &Session 'scratch) -> LiveInfo 'scratch throws (alloc::AllocError) {
56
    let blockCount = func.blocks.len;
57
    if blockCount == 0 {
58
        let empty = try storage.fill(0 as u32, 0);
59
        let frozen: &'scratch [u32] = &empty[..];
60
        return LiveInfo 'scratch { liveIn: frozen, liveOut: frozen, defs: frozen, uses: frozen, words: 0, blockCount: 0, maxReg: 0 };
61
    }
62
63
    // Find max register number.
64
    let mut maxReg: u32 = 0;
65
    for p in func.params {
66
        set maxReg = maxRegNum(p.value.n, maxReg);
67
    }
68
    for b in 0..blockCount {
69
        let block = &func.blocks[b];
70
        for p in block.params {
71
            set maxReg = maxRegNum(p.value.n, maxReg);
72
        }
73
        for instr in block.instrs {
74
            let mut registers = il::registers(&instr);
75
            while let reg = il::nextReg(&mut registers, &instr) {
76
                updateMaxReg(reg, &mut maxReg);
77
            }
78
            if let dst = il::instrDst(instr) {
79
                set maxReg = maxRegNum(dst.n, maxReg);
80
            }
81
        }
82
    }
83
    if maxReg > MAX_SSA_REGS {
84
        throw alloc::AllocError::OutOfMemory;
85
    }
86
    // Allocate one contiguous word matrix for each set class.
87
    let words = bitset::wordsFor(maxReg);
88
    let count64 = blockCount as u64 * words as u64;
89
    if count64 > 0xFFFFFFFF {
90
        throw alloc::AllocError::OutOfMemory;
91
    }
92
    let count = count64 as u32;
93
    let liveIn = try storage.fill(0 as u32, count);
94
    let liveOut = try storage.fill(0 as u32, count);
95
    let defs = try storage.fill(0 as u32, count);
96
    let uses = try storage.fill(0 as u32, count);
97
98
    // Compute local defs and uses for each block.
99
    for b in 0..blockCount {
100
        computeLocalDefsUses(&func.blocks[b], &mut defs[b * words..(b + 1) * words], &mut uses[b * words..(b + 1) * words]);
101
    }
102
    // Live sets grow monotonically from empty sets; no scratch set is needed.
103
    let mut changed = true;
104
105
    while changed {
106
        set changed = false;
107
108
        // Process blocks in reverse order (approximates post-order).
109
        let mut b = blockCount;
110
        while b > 0 {
111
            set b -= 1;
112
            let block = &func.blocks[b];
113
114
            // Add successor live-in sets directly to this block's live-out set.
115
            addSuccessorLiveIn(func, block, liveIn, words, &mut liveOut[b * words..(b + 1) * words]);
116
117
            if computeAndUpdateLiveIn(&mut liveIn[b * words..(b + 1) * words], &liveOut[b * words..(b + 1) * words], &defs[b * words..(b + 1) * words], &uses[b * words..(b + 1) * words]) {
118
                set changed = true;
119
            }
120
        }
121
    }
122
    return LiveInfo 'scratch { liveIn: &liveIn[..], liveOut: &liveOut[..], defs: &defs[..], uses: &uses[..], words, blockCount, maxReg };
123
}
124
125
/// Compute `liveIn = uses | (liveOut - defs)` and update `dst`.
126
/// Returns `true` if `dst` changed. Combined loop avoids multiple passes.
127
fn computeAndUpdateLiveIn(
128
    dst: &mut [u32],
129
    liveOut: &[u32],
130
    defs: &[u32],
131
    uses: &[u32]
132
) -> bool {
133
    let mut changed = false;
134
    for i in 0..dst.len {
135
        let newWord = uses[i] | (liveOut[i] & ~defs[i]);
136
        if dst[i] <> newWord {
137
            set dst[i] = newWord;
138
            set changed = true;
139
        }
140
    }
141
    return changed;
142
}
143
144
/// Compute local defs and uses for a single block.
145
unsafe fn computeLocalDefsUses(block: &il::Block, defs: &mut [u32], uses: &mut [u32]) {
146
    for p in block.params {
147
        bitset::put(defs, p.value.n);
148
    }
149
    for instr in block.instrs {
150
        let mut registers = il::registers(&instr);
151
        while let reg = il::nextReg(&mut registers, &instr) {
152
            addUse(reg, defs, uses);
153
        }
154
155
        if let dst = il::instrDst(instr) {
156
            bitset::put(defs, dst.n);
157
        }
158
    }
159
}
160
161
/// Add an undefined register to the use set.
162
fn addUse(reg: il::Reg, defs: &[u32], uses: &mut [u32]) {
163
    if not bitset::contains(defs, reg.n) {
164
        bitset::put(uses, reg.n);
165
    }
166
}
167
168
/// Update the largest register number.
169
fn updateMaxReg(reg: il::Reg, max: &mut u32) {
170
    set *max = maxRegNum(reg.n, *max);
171
}
172
173
/// Return the larger of n+1 and current.
174
fn maxRegNum(n: u32, current: u32) -> u32 {
175
    if n >= MAX_SSA_REGS {
176
        return MAX_SSA_REGS + 1;
177
    }
178
    if n + 1 > current {
179
        return n + 1;
180
    }
181
    return current;
182
}
183
184
/// Add successor live-in sets to the block's live-out set.
185
unsafe fn addSuccessorLiveIn(func: &il::Fn, block: &il::Block, liveIn: &[u32], words: u32, liveOut: &mut [u32]) {
186
    if block.instrs.len == 0 {
187
        return;
188
    }
189
    let term = block.instrs[block.instrs.len - 1];
190
191
    match term {
192
        case il::Instr::Jmp { target, .. } =>
193
            unionBlockLiveIn(target, liveIn, words, liveOut),
194
        case il::Instr::Br { thenTarget, elseTarget, .. } => {
195
            unionBlockLiveIn(thenTarget, liveIn, words, liveOut);
196
            unionBlockLiveIn(elseTarget, liveIn, words, liveOut);
197
        },
198
        case il::Instr::Switch { defaultTarget, cases, .. } => {
199
            unionBlockLiveIn(defaultTarget, liveIn, words, liveOut);
200
            for c in cases {
201
                unionBlockLiveIn(c.target, liveIn, words, liveOut);
202
            }
203
        },
204
        else => {},
205
    }
206
}
207
208
/// Union a target block's live-in set into the block's live-out set.
209
fn unionBlockLiveIn(target: u32, liveIn: &[u32], words: u32, liveOut: &mut [u32]) {
210
    bitset::union_(liveOut, &liveIn[target * words..(target + 1) * words]);
211
}
212
213
/// Check if a register has any use after this instruction.
214
export unsafe fn hasLaterUse 'scratch (info: &LiveInfo 'scratch, func: &il::Fn, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool {
215
    let block = &func.blocks[blockIdx];
216
217
    if bitset::contains(liveOutRow(info, blockIdx), reg.n) {
218
        return true;
219
    }
220
    for i in (instrIdx + 1)..block.instrs.len {
221
        if instrUsesReg(block.instrs[i], reg) {
222
            return true;
223
        }
224
    }
225
    return false;
226
}
227
228
/// Check if an instruction uses a specific register.
229
unsafe fn instrUsesReg(instr: il::Instr, reg: il::Reg) -> bool {
230
    let mut registers = il::registers(&instr);
231
    while let source = il::nextReg(&mut registers, &instr) {
232
        if source.n == reg.n {
233
            return true;
234
        }
235
    }
236
    return false;
237
}
238
239
/// Borrow the live-in words for one block.
240
export fn liveInRow 'scratch (info: &LiveInfo 'scratch, block: u32) -> &'scratch [u32] {
241
    assert block < info.blockCount;
242
    return &info.liveIn[block * info.words..(block + 1) * info.words];
243
}
244
245
/// Borrow the live-out words for one block.
246
export fn liveOutRow 'scratch (info: &LiveInfo 'scratch, block: u32) -> &'scratch [u32] {
247
    assert block < info.blockCount;
248
    return &info.liveOut[block * info.words..(block + 1) * info.words];
249
}