lib/std/lang/gen/regalloc/liveness.rad 10.0 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
/// Largest register index whose required entry count fits in u32.
34
constant MAX_REGISTER_INDEX: u32 = 0xfffffffe;
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
    return try analyzeTables(func.params, func.blocks, storage);
57
}
58
59
/// Allocate and compute live sets for borrowed function parameter and block tables.
60
fn analyzeTables 'scratch (params: &[il::Param], blocks: &[il::Block], storage: &Session 'scratch) -> LiveInfo 'scratch throws (alloc::AllocError) {
61
    let blockCount = blocks.len;
62
    if blockCount == 0 {
63
        let empty = try storage.fill(0 as u32, 0);
64
        let frozen: &'scratch [u32] = &empty[..];
65
        return LiveInfo 'scratch { liveIn: frozen, liveOut: frozen, defs: frozen, uses: frozen, words: 0, blockCount: 0, maxReg: 0 };
66
    }
67
68
    // Find max register number.
69
    let mut maxReg: u32 = 0;
70
    try updateExtent(params, &[], &mut maxReg);
71
    for b in 0..blockCount {
72
        let block = &blocks[b];
73
        unsafe {
74
            try updateExtent(block.params, block.instrs, &mut maxReg);
75
        }
76
    }
77
    // Allocate one contiguous word matrix for each set class.
78
    let words = bitset::wordsFor(maxReg);
79
    let count64 = blockCount as u64 * words as u64;
80
    if count64 > 0xFFFFFFFF {
81
        throw alloc::AllocError::OutOfMemory;
82
    }
83
    let count = count64 as u32;
84
    let liveIn = try storage.fill(0 as u32, count);
85
    let liveOut = try storage.fill(0 as u32, count);
86
    let defs = try storage.fill(0 as u32, count);
87
    let uses = try storage.fill(0 as u32, count);
88
89
    // Compute local defs and uses for each block.
90
    for b in 0..blockCount {
91
        let block = &blocks[b];
92
        unsafe {
93
            computeLocalDefsUses(block.params, block.instrs, &mut defs[b * words..(b + 1) * words], &mut uses[b * words..(b + 1) * words]);
94
        }
95
    }
96
    propagateLiveness(blocks, liveIn, liveOut, defs, uses, words);
97
    return LiveInfo 'scratch { liveIn: &liveIn[..], liveOut: &liveOut[..], defs: &defs[..], uses: &uses[..], words, blockCount, maxReg };
98
}
99
100
/// Propagate successor uses until every block's live-in row is stable.
101
fn propagateLiveness(
102
    blocks: &[il::Block],
103
    liveIn: &mut [u32],
104
    liveOut: &mut [u32],
105
    defs: &[u32],
106
    uses: &[u32],
107
    words: u32,
108
) {
109
    // Live sets grow monotonically from empty sets; no scratch set is needed.
110
    let mut changed = true;
111
112
    while changed {
113
        set changed = false;
114
115
        // Process blocks in reverse order (approximates post-order).
116
        let mut b = blocks.len;
117
        while b > 0 {
118
            set b -= 1;
119
            let block = &blocks[b];
120
121
            // Add successor live-in sets directly to this block's live-out set.
122
            unsafe {
123
                addSuccessorLiveIn(block.instrs, liveIn, words, &mut liveOut[b * words..(b + 1) * words]);
124
            }
125
126
            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]) {
127
                set changed = true;
128
            }
129
        }
130
    }
131
}
132
133
/// Compute `liveIn = uses | (liveOut - defs)` and update `dst`.
134
/// Returns `true` if `dst` changed. Combined loop avoids multiple passes.
135
fn computeAndUpdateLiveIn(
136
    dst: &mut [u32],
137
    liveOut: &[u32],
138
    defs: &[u32],
139
    uses: &[u32]
140
) -> bool {
141
    let mut changed = false;
142
    for i in 0..dst.len {
143
        let newWord = uses[i] | (liveOut[i] & ~defs[i]);
144
        if dst[i] <> newWord {
145
            set dst[i] = newWord;
146
            set changed = true;
147
        }
148
    }
149
    return changed;
150
}
151
152
/// Compute local defs and uses for a single block.
153
fn computeLocalDefsUses(params: &[il::Param], instructions: &[il::Instr], defs: &mut [u32], uses: &mut [u32]) {
154
    for p in params {
155
        bitset::put(defs, p.value.n);
156
    }
157
    for i in 0..instructions.len {
158
        let instr = &instructions[i];
159
        let mut registers = il::registers(instr);
160
        // Argument groups contain raw views into the function's IL storage.
161
        unsafe {
162
            while let reg = il::nextReg(&mut registers, instr) {
163
                addUse(reg, defs, uses);
164
            }
165
        }
166
167
        if let dst = il::instrDst(*instr) {
168
            bitset::put(defs, dst.n);
169
        }
170
    }
171
}
172
173
/// Add an undefined register to the use set.
174
fn addUse(reg: il::Reg, defs: &[u32], uses: &mut [u32]) {
175
    if not bitset::contains(defs, reg.n) {
176
        bitset::put(uses, reg.n);
177
    }
178
}
179
180
/// Include all parameter, source, and destination registers in the entry count.
181
fn updateExtent(params: &[il::Param], instructions: &[il::Instr], max: &mut u32) throws (alloc::AllocError) {
182
    for p in params {
183
        try updateMaxReg(p.value, max);
184
    }
185
    for i in 0..instructions.len {
186
        let instr = &instructions[i];
187
        let mut registers = il::registers(instr);
188
        // Argument groups contain raw views into the function's IL storage.
189
        unsafe {
190
            while let reg = il::nextReg(&mut registers, instr) {
191
                try updateMaxReg(reg, max);
192
            }
193
        }
194
        if let dst = il::instrDst(*instr) {
195
            try updateMaxReg(dst, max);
196
        }
197
    }
198
}
199
200
/// Retain the larger of the current entry count and the register index plus one.
201
fn updateMaxReg(reg: il::Reg, max: &mut u32) throws (alloc::AllocError) {
202
    if reg.n > MAX_REGISTER_INDEX {
203
        throw alloc::AllocError::OutOfMemory;
204
    }
205
    let count = reg.n + 1;
206
    if count > *max {
207
        set *max = count;
208
    }
209
}
210
211
/// Add successor live-in sets to the block's live-out set.
212
fn addSuccessorLiveIn(instructions: &[il::Instr], liveIn: &[u32], words: u32, liveOut: &mut [u32]) {
213
    if instructions.len == 0 {
214
        return;
215
    }
216
    let term = &instructions[instructions.len - 1];
217
    if let case il::Instr::Switch { cases, .. } = term {
218
        unsafe {
219
            mergeSuccessorLiveIn(term, *cases, liveIn, words, liveOut);
220
        }
221
    } else {
222
        mergeSuccessorLiveIn(term, &[], liveIn, words, liveOut);
223
    }
224
}
225
226
/// Merge live-in rows selected by a terminator and its borrowed switch cases.
227
fn mergeSuccessorLiveIn(term: &il::Instr, cases: &[il::SwitchCase], liveIn: &[u32], words: u32, liveOut: &mut [u32]) {
228
    match term {
229
        case il::Instr::Jmp { target, .. } =>
230
            unionBlockLiveIn(*target, liveIn, words, liveOut),
231
        case il::Instr::Br { thenTarget, elseTarget, .. } => {
232
            unionBlockLiveIn(*thenTarget, liveIn, words, liveOut);
233
            unionBlockLiveIn(*elseTarget, liveIn, words, liveOut);
234
        },
235
        case il::Instr::Switch { defaultTarget, .. } => {
236
            unionBlockLiveIn(*defaultTarget, liveIn, words, liveOut);
237
            for c in cases {
238
                unionBlockLiveIn(c.target, liveIn, words, liveOut);
239
            }
240
        },
241
        else => {},
242
    }
243
}
244
245
/// Union a target block's live-in set into the block's live-out set.
246
fn unionBlockLiveIn(target: u32, liveIn: &[u32], words: u32, liveOut: &mut [u32]) {
247
    bitset::union_(liveOut, &liveIn[target * words..(target + 1) * words]);
248
}
249
250
/// Check if a register has any use after this instruction.
251
export unsafe fn hasLaterUse 'scratch (info: &LiveInfo 'scratch, func: &il::Fn, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool {
252
    let block = &func.blocks[blockIdx];
253
254
    if bitset::contains(liveOutRow(info, blockIdx), reg.n) {
255
        return true;
256
    }
257
    return usesRegisterAfter(block.instrs, instrIdx, reg);
258
}
259
260
/// Check if an instruction after the given index uses a specific register.
261
fn usesRegisterAfter(instructions: &[il::Instr], instrIdx: u32, reg: il::Reg) -> bool {
262
    for i in (instrIdx + 1)..instructions.len {
263
        let instr = &instructions[i];
264
        let mut registers = il::registers(instr);
265
        // Argument groups contain raw views into the function's IL storage.
266
        unsafe {
267
            while let source = il::nextReg(&mut registers, instr) {
268
                if source.n == reg.n {
269
                    return true;
270
                }
271
            }
272
        }
273
    }
274
    return false;
275
}
276
277
/// Borrow the live-in words for one block.
278
export fn liveInRow 'scratch (info: &LiveInfo 'scratch, block: u32) -> &'scratch [u32] {
279
    assert block < info.blockCount;
280
    return &info.liveIn[block * info.words..(block + 1) * info.words];
281
}
282
283
/// Borrow the live-out words for one block.
284
export fn liveOutRow 'scratch (info: &LiveInfo 'scratch, block: u32) -> &'scratch [u32] {
285
    assert block < info.blockCount;
286
    return &info.liveOut[block * info.words..(block + 1) * info.words];
287
}