compiler: Check block-entry register assignment

972c0b107bf4c4063a1c065e6aaddf1449f91138ebbac9a1643e1f4d57d309a5
Alexis Sellier committed ago 1 parent ccb91cef
lib/std/lang/gen/regalloc/assign.rad +42 -29
102 102
            while let reg = il::nextReg(&mut registers, &instr) {
103 103
                recordLastUse(reg, &mut lastUse[..], i);
104 104
            }
105 105
        }
106 106
107 -
        // Reset for new block.
108 -
        set current.n = 0;
109 -
        bitset::clearAll(usedRegs);
110 -
111 -
        // Mark all live-in values' registers as used.
112 -
        // This ensures we don't reuse registers for values that flow in
113 -
        // from predecessors, even at merge points with multiple predecessors.
114 -
        // Values that are live-in but have no assignment yet (e.g. callee-saved
115 -
        // function parameters not used before a phi block) are allocated now to
116 -
        // prevent conflicts with block parameters.
117 -
        let mut liveInIter = bitset::iter(liveness::liveInRow(live, b));
118 -
        while let ssaReg = bitset::iterNext(&mut liveInIter, liveness::liveInRow(live, b)) {
119 -
            let reg = il::Reg { n: ssaReg };
120 -
            if not spill::isSpilled(spillInfo, reg) {
121 -
                if let phys = assignments[ssaReg] {
122 -
                    bitset::put(usedRegs, *phys as u32);
123 -
                    rmapSet(&mut current, ssaReg, phys);
124 -
                } else {
125 -
                    set assignments[ssaReg] = rallocReg(&mut current, usedRegs, ssaReg, allocatable, config.calleeSaved, spillInfo);
126 -
                }
127 -
            }
128 -
        }
129 -
130 -
        // Allocate block parameters.
131 -
        for p in block.params {
132 -
            if p.value.n < maxReg and not spill::isSpilled(spillInfo, p.value) {
133 -
                set assignments[p.value.n] = rallocReg(&mut current, usedRegs, p.value.n, allocatable, config.calleeSaved, spillInfo);
134 -
            }
135 -
        }
107 +
        enterBlock(&mut current, usedRegs, liveness::liveInRow(live, b), block.params, assignments, config, spillInfo);
136 108
137 109
        // Process each instruction.
138 110
        for instr, i in block.instrs {
139 111
            let currentRef: 'step = &mut current, usedRef = &mut usedRegs[..],
140 112
                lastUseRef = &lastUse[..], liveRef = &*live,
182 154
        assignments: &assignments[..],
183 155
        usedCalleeSaved,
184 156
    };
185 157
}
186 158
159 +
/// Reserve live-in registers before assigning the block's parameter registers.
160 +
fn enterBlock 'scratch (
161 +
    current: &mut RegMap 'scratch,
162 +
    usedRegs: &mut [u32],
163 +
    liveIn: &[u32],
164 +
    params: &[il::Param],
165 +
    assignments: &mut [?gen::Reg],
166 +
    config: &super::TargetConfig,
167 +
    spillInfo: &spill::SpillInfo 'scratch,
168 +
) {
169 +
    // Reset for new block.
170 +
    set current.n = 0;
171 +
    bitset::clearAll(usedRegs);
172 +
173 +
    // Mark all live-in values' registers as used.
174 +
    // This ensures we don't reuse registers for values that flow in
175 +
    // from predecessors, even at merge points with multiple predecessors.
176 +
    // Values that are live-in but have no assignment yet (e.g. callee-saved
177 +
    // function parameters not used before a phi block) are allocated now to
178 +
    // prevent conflicts with block parameters.
179 +
    let mut liveInIter = bitset::iter(liveIn);
180 +
    while let ssaReg = bitset::iterNext(&mut liveInIter, liveIn) {
181 +
        let reg = il::Reg { n: ssaReg };
182 +
        if not spill::isSpilled(spillInfo, reg) {
183 +
            if let phys = assignments[ssaReg] {
184 +
                bitset::put(usedRegs, *phys as u32);
185 +
                rmapSet(current, ssaReg, phys);
186 +
            } else {
187 +
                set assignments[ssaReg] = rallocReg(current, usedRegs, ssaReg, config.allocatable, config.calleeSaved, spillInfo);
188 +
            }
189 +
        }
190 +
    }
191 +
192 +
    // Allocate block parameters.
193 +
    for p in params {
194 +
        if p.value.n < assignments.len and not spill::isSpilled(spillInfo, p.value) {
195 +
            set assignments[p.value.n] = rallocReg(current, usedRegs, p.value.n, config.allocatable, config.calleeSaved, spillInfo);
196 +
        }
197 +
    }
198 +
}
199 +
187 200
/// Create an empty register map.
188 201
fn createRegMap 'scratch (storage: &Session 'scratch) -> RegMap 'scratch throws (alloc::AllocError) {
189 202
    let virtRegs = try storage.fill(0 as u32, MAX_ACTIVE);
190 203
    let physRegs = try storage.fill(gen::Reg(0), MAX_ACTIVE);
191 204
    return RegMap 'scratch { virtRegs, physRegs, n: 0 };
test/tests/regalloc.block.entry.rad added +29 -0
1 +
//! returns: 0
2 +
3 +
/// Compute a full-width value across a call boundary.
4 +
fn step(value: u64) -> u64 {
5 +
    return value * 3 + 1;
6 +
}
7 +
8 +
/// Keep both input parameters live beside a merged block parameter.
9 +
fn combine(first: u64, second: u64, selected: bool) -> u64 {
10 +
    let mut value: u64 = 0;
11 +
    if selected {
12 +
        set value = step(first) + second;
13 +
    } else {
14 +
        set value = step(second) + first;
15 +
    }
16 +
    let result = step(value);
17 +
    return result + first + second;
18 +
}
19 +
20 +
/// Check both predecessor paths and repeated block-entry register allocation.
21 +
@default fn main() -> u32 {
22 +
    for index in 0..32 {
23 +
        let first = 0x100000000 + index as u64;
24 +
        let second = 0x200000000 + 2 * index as u64;
25 +
        assert combine(first, second, true) == 10 * first + 4 * second + 4;
26 +
        assert combine(first, second, false) == 4 * first + 10 * second + 4;
27 +
    }
28 +
    return 0;
29 +
}