compiler: Check reverse block pressure traversal

a000c3ac545ea1e2807fcef5de886bf1886471708b54be259e87a28e94078ad8
Alexis Sellier committed ago 1 parent 5d3e0414
lib/std/lang/gen/regalloc/liveness/tests.rad +41 -0
186 186
            assert info.slots[0] == 0;
187 187
        }
188 188
    }
189 189
}
190 190
191 +
/// Cross-call pressure excludes the result and spills equal-cost values in order.
192 +
@test unsafe fn testCrossCallPressure() throws (testing::TestError) {
193 +
    let first = il::Reg { n: 0 };
194 +
    let second = il::Reg { n: 33 };
195 +
    let result = il::Reg { n: 66 };
196 +
    let sum = il::Reg { n: 67 };
197 +
    let total = il::Reg { n: 68 };
198 +
    let params = [il::Param { value: first, type: il::Type::W64 },
199 +
        il::Param { value: second, type: il::Type::W64 }];
200 +
    let args = [il::Val::Reg(first), il::Val::Reg(second)];
201 +
    let mut instructions = [
202 +
        il::Instr::Call { retTy: il::Type::W64, dst: result,
203 +
            func: il::Val::FnAddr("callee"), args: &args[..] },
204 +
        il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: sum,
205 +
            a: il::Val::Reg(first), b: il::Val::Reg(second) },
206 +
        il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: total,
207 +
            a: il::Val::Reg(sum), b: il::Val::Reg(result) },
208 +
        il::Instr::Ret { val: il::Val::Reg(total) },
209 +
    ];
210 +
    let blocks = [block("entry", &mut instructions[..]), block("empty", &mut [])];
211 +
    let function = il::Fn { name: "crossing", params: &params[..], returnType: il::Type::W64,
212 +
        isExtern: false, isLeaf: false, blocks: &blocks[..] };
213 +
    for capacity in [0 as u32, 1, 2] {
214 +
        static DATA: [u8; 8192] = [0; 8192];
215 +
        let mut arena = alloc::new(&mut DATA[..]);
216 +
        use arena as analysis in {
217 +
            let live = try! super::analyze(&function, &analysis);
218 +
            let info = try! spill::analyze(&function, &live, 8, capacity, 8, &analysis);
219 +
            assert spill::isSpilled(&info, first) == (capacity < 2);
220 +
            assert spill::isSpilled(&info, second) == (capacity == 0);
221 +
            assert not spill::isSpilled(&info, result);
222 +
            assert not spill::isSpilled(&info, sum);
223 +
            assert not spill::isSpilled(&info, total);
224 +
            assert bitset::contains(info.calleeClass, first.n) == (capacity == 2);
225 +
            assert bitset::contains(info.calleeClass, second.n) == (capacity > 0);
226 +
            assert not bitset::contains(info.calleeClass, result.n);
227 +
            assert info.frameSize == ((2 - capacity) * 8) as i32;
228 +
        }
229 +
    }
230 +
}
231 +
191 232
/// Function and block parameter extents receive the same overflow checks.
192 233
@test unsafe fn testParameterRegisterExtent() throws (testing::TestError) {
193 234
    for index in [16384 as u32, 0xffffffff] {
194 235
        for isFunction in [false, true] {
195 236
            let parameters = [il::Param { value: il::Reg { n: index }, type: il::Type::W64 }];
lib/std/lang/gen/regalloc/spill.rad +43 -27
109 109
        let block = &func.blocks[b];
110 110
111 111
        // Start with live-out set.
112 112
        bitset::copy(scratch, liveness::liveOutRow(live, b));
113 113
114 -
        // Walk instructions backwards.
115 -
        let mut i = block.instrs.len;
116 -
        while i > 0 {
117 -
            set i -= 1;
118 -
            let instr = block.instrs[i];
114 +
        try limitBlockPressure(block.instrs, scratch, spilled, costs, calleeClass, numRegs, numCalleeSaved);
115 +
    }
119 116
120 -
            // Limit register pressure before processing this instruction.
121 -
            try limitPressure(scratch, spilled, costs, numRegs);
117 +
    let frameSize = try finishSpills(slots, spilled, calleeClass, numCalleeSaved, slotSize);
118 +
    return SpillInfo 'scratch { slots: &slots[..], frameSize, calleeClass: &calleeClass[..], maxReg };
119 +
}
122 120
123 -
            // Enforce cross-call pressure at call sites.
124 -
            if il::isCall(instr) {
125 -
                try limitCrossCallPressure(
126 -
                    scratch, spilled, costs,
127 -
                    calleeClass, numCalleeSaved, il::instrDst(instr)
128 -
                );
129 -
            }
130 -
            // Remove definition from live set.
131 -
            if let dst = il::instrDst(instr) {
132 -
                bitset::clear(scratch, dst.n);
133 -
            }
134 -
            // Add uses to live set.
135 -
            let mut registers = il::registers(&instr);
136 -
            while let reg = il::nextReg(&mut registers, &instr) {
137 -
                bitset::put(scratch, reg.n);
121 +
/// Limit register pressure from the block's live-out set to its entry.
122 +
fn limitBlockPressure(
123 +
    instructions: &[il::Instr],
124 +
    live: &mut [u32],
125 +
    spilled: &mut [u32],
126 +
    costs: &[SpillCost],
127 +
    calleeClass: &mut [u32],
128 +
    numRegs: u32,
129 +
    numCalleeSaved: u32,
130 +
) throws (alloc::AllocError) {
131 +
    // Walk instructions backwards.
132 +
    let mut i = instructions.len;
133 +
    while i > 0 {
134 +
        set i -= 1;
135 +
        let instr = &instructions[i];
136 +
137 +
        // Limit register pressure before processing this instruction.
138 +
        try limitPressure(live, spilled, costs, numRegs);
139 +
140 +
        // Enforce cross-call pressure at call sites.
141 +
        if il::isCall(*instr) {
142 +
            try limitCrossCallPressure(
143 +
                live, spilled, costs,
144 +
                calleeClass, numCalleeSaved, il::instrDst(*instr)
145 +
            );
146 +
        }
147 +
        // Remove definition from live set.
148 +
        if let dst = il::instrDst(*instr) {
149 +
            bitset::clear(live, dst.n);
150 +
        }
151 +
        // Add uses to live set.
152 +
        let mut registers = il::registers(instr);
153 +
        // Argument groups contain raw views into the function's IL storage.
154 +
        unsafe {
155 +
            while let reg = il::nextReg(&mut registers, instr) {
156 +
                bitset::put(live, reg.n);
138 157
            }
139 158
        }
140 -
        // Also limit pressure at block entry.
141 -
        try limitPressure(scratch, spilled, costs, numRegs);
142 159
    }
143 -
144 -
    let frameSize = try finishSpills(slots, spilled, calleeClass, numCalleeSaved, slotSize);
145 -
    return SpillInfo 'scratch { slots: &slots[..], frameSize, calleeClass: &calleeClass[..], maxReg };
160 +
    // Also limit pressure at block entry.
161 +
    try limitPressure(live, spilled, costs, numRegs);
146 162
}
147 163
148 164
/// Enforce the global register-class limit and assign bounded stack offsets.
149 165
fn finishSpills(
150 166
    slots: &mut [i32],