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