compiler: Check spill class and stack-slot finalization

ccb91cef1699b4d7ab09b1ef73f7d2c6d43973b68c0037537d6ec2e8e188a368
Alexis Sellier committed ago 1 parent 9ab18d59
lib/std/lang/gen/regalloc/liveness/tests.rad +34 -0
97 97
    try testing::expect(bitset::contains(words, 0) == first);
98 98
    try testing::expect(bitset::contains(words, 33) == second);
99 99
    try testing::expect(bitset::contains(words, 66) == third);
100 100
}
101 101
102 +
/// Multiple spill slots preserve register order and reject total-frame overflow.
103 +
@test unsafe fn testMultipleSpillOffsets() throws (testing::TestError) {
104 +
    for size in [0 as u32, 8, 0x2aaaaaaa, 0x2aaaaaab] {
105 +
        let first = il::Reg { n: 0 };
106 +
        let second = il::Reg { n: 33 };
107 +
        let result = il::Reg { n: 66 };
108 +
        let mut instructions = [
109 +
            il::Instr::Copy { dst: first, val: il::Val::Imm(1) },
110 +
            il::Instr::Copy { dst: second, val: il::Val::Imm(2) },
111 +
            il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64,
112 +
                dst: result, a: il::Val::Reg(first), b: il::Val::Reg(second) },
113 +
            il::Instr::Ret { val: il::Val::Reg(result) },
114 +
        ];
115 +
        let blocks = [block("entry", &mut instructions[..])];
116 +
        let function = il::Fn { name: "offsets", params: &[], returnType: il::Type::W64,
117 +
            isExtern: false, isLeaf: true, blocks: &blocks[..] };
118 +
        static DATA: [u8; 16384] = [0; 16384];
119 +
        let mut arena = alloc::new(&mut DATA[..]);
120 +
        use arena as analysis in {
121 +
            let live = try! super::analyze(&function, &analysis);
122 +
            let info = try spill::analyze(&function, &live, 0, 0, size, &analysis) catch {
123 +
                assert size == 0x2aaaaaab;
124 +
                continue;
125 +
            };
126 +
            assert size <= 0x2aaaaaaa;
127 +
            assert info.slots[0] == 0;
128 +
            assert info.slots[33] == size as i32;
129 +
            assert info.slots[66] == (size * 2) as i32;
130 +
            assert info.slots[1] == -1;
131 +
            assert info.frameSize == (size * 3) as i32;
132 +
        }
133 +
    }
134 +
}
135 +
102 136
/// Spill frame sizes must fit their signed byte-offset representation.
103 137
@test unsafe fn testSpillFrameExtent() throws (testing::TestError) {
104 138
    for size in [0x7fffffff as u32, 0x80000000, 0xffffffff] {
105 139
        let value = il::Reg { n: 0 };
106 140
        let mut instructions = [
lib/std/lang/gen/regalloc/spill.rad +13 -1
139 139
        }
140 140
        // Also limit pressure at block entry.
141 141
        try limitPressure(scratch, spilled, costs, numRegs);
142 142
    }
143 143
144 +
    let frameSize = try finishSpills(slots, spilled, calleeClass, numCalleeSaved, slotSize);
145 +
    return SpillInfo 'scratch { slots: &slots[..], frameSize, calleeClass: &calleeClass[..], maxReg };
146 +
}
147 +
148 +
/// Enforce the global register-class limit and assign bounded stack offsets.
149 +
fn finishSpills(
150 +
    slots: &mut [i32],
151 +
    spilled: &mut [u32],
152 +
    calleeClass: &mut [u32],
153 +
    numCalleeSaved: u32,
154 +
    slotSize: u32,
155 +
) -> i32 throws (alloc::AllocError) {
144 156
    // Phase 3: Enforce global callee-class limit.
145 157
    // The per-call-site limit may leave the callee-class set larger than
146 158
    // `numCalleeSaved` when different call sites keep different subsets.
147 159
    // Spill excess values to guarantee the assignment phase always finds
148 160
    // a callee-saved register for cross-call values.
168 180
            throw alloc::AllocError::OutOfMemory;
169 181
        }
170 182
        set slots[n] = frameSize;
171 183
        set frameSize += slotSize as i32;
172 184
    }
173 -
    return SpillInfo 'scratch { slots: &slots[..], frameSize, calleeClass: &calleeClass[..], maxReg };
185 +
    return frameSize;
174 186
}
175 187
176 188
/// Calculate spill costs for all registers, weighted by loop depth.
177 189
unsafe fn fillCosts(func: &il::Fn, costs: &mut [SpillCost]) {
178 190
    for b in 0..func.blocks.len {