compiler: Return register allocation capacity failures

bf4dc3d25168e960f07a6df619c0e81e51b2494d1acf649d2fc0cc9beb5eee83
Alexis Sellier committed ago 1 parent c9134fc1
lib/std/arch/rv64/tests.rad +48 -0
3 3
//! These tests verify that instruction encodings match the RISC-V specification
4 4
//! by comparing against known-good values.
5 5
6 6
use std::testing;
7 7
use std::lang::alloc;
8 +
use std::lang::il;
9 +
use std::lang::gen::bitset;
10 +
use std::lang::gen::regalloc;
8 11
use std::collections::dict;
9 12
10 13
use super::encode;
11 14
use super::asm;
12 15
551 554
    try testing::expect(encode::isJumpImm(1048574));    // Max positive even
552 555
    try testing::expect(encode::isJumpImm(-1048576));   // Min negative
553 556
    try testing::expectNot(encode::isJumpImm(1));       // Must be even
554 557
    try testing::expectNot(encode::isJumpImm(1048576)); // Out of range
555 558
}
559 +
560 +
/// Register limit checks use bounded scratch storage.
561 +
static REGISTER_SCRATCH: [u8; 65536] = [0; 65536];
562 +
563 +
/// Register numbers at or above the supported count return allocation errors.
564 +
@test unsafe fn testRegisterLimit() throws (testing::TestError) {
565 +
    for number in [8191, 8192, 0xffffffff] {
566 +
        let mut arena = alloc::new(&mut REGISTER_SCRATCH[..]);
567 +
        let mut instructions = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: number }) }];
568 +
        let func = il::Fn {
569 +
            name: "limit", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
570 +
            blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }],
571 +
        };
572 +
        let mut failed = false;
573 +
        try regalloc::liveness::analyze(&func, &mut arena) catch {
574 +
            set failed = true;
575 +
        };
576 +
        assert failed == (number >= 8192);
577 +
        if failed {
578 +
            assert arena.offset == 0;
579 +
        }
580 +
    }
581 +
}
582 +
583 +
/// Excess live values return an error before the spill candidate table overflows.
584 +
@test unsafe fn testSpillCandidateLimit() throws (testing::TestError) {
585 +
    let mut arena = alloc::new(&mut REGISTER_SCRATCH[..]);
586 +
    let mut liveSet = try! bitset::allocate(&mut arena, 257);
587 +
    for i in 0..257 {
588 +
        bitset::put(&mut liveSet, i);
589 +
    }
590 +
    let mut out = [liveSet];
591 +
    let live = regalloc::liveness::LiveInfo {
592 +
        liveIn: &mut [], liveOut: &mut out[..], defs: &mut [], uses: &mut [], blockCount: 1, maxReg: 257,
593 +
    };
594 +
    let func = il::Fn {
595 +
        name: "pressure", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
596 +
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut [], locs: &[], preds: &[], loopDepth: 0 }],
597 +
    };
598 +
    let mut failed = false;
599 +
    try regalloc::spill::analyze(&func, &live, 23, 11, 8, &mut arena) catch {
600 +
        set failed = true;
601 +
    };
602 +
    assert failed;
603 +
}
lib/std/lang/gen/regalloc/liveness.rad +6 -1
89 89
            if let dst = il::instrDst(block.instrs[i]) {
90 90
                set maxReg = maxRegNum(dst.n, maxReg);
91 91
            }
92 92
        }
93 93
    }
94 -
    assert maxReg <= MAX_SSA_REGS, "analyze: maximum SSA registers exceeded";
94 +
    if maxReg > MAX_SSA_REGS {
95 +
        throw alloc::AllocError::OutOfMemory;
96 +
    }
95 97
    // Allocate per-block bitsets.
96 98
    let liveIn = try alloc::allocRawSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *unsafe mut [bitset::Bitset];
97 99
    let liveOut = try alloc::allocRawSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *unsafe mut [bitset::Bitset];
98 100
    let defs = try alloc::allocRawSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *unsafe mut [bitset::Bitset];
99 101
    let uses = try alloc::allocRawSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *unsafe mut [bitset::Bitset];
190 192
    set *max = maxRegNum(reg.n, *max);
191 193
}
192 194
193 195
/// Return the larger of n+1 and current.
194 196
fn maxRegNum(n: u32, current: u32) -> u32 {
197 +
    if n >= MAX_SSA_REGS {
198 +
        return MAX_SSA_REGS + 1;
199 +
    }
195 200
    if n + 1 > current {
196 201
        return n + 1;
197 202
    }
198 203
    return current;
199 204
}
lib/std/lang/gen/regalloc/spill.rad +13 -8
126 126
        while i > 0 {
127 127
            set i -= 1;
128 128
            let instr = block.instrs[i];
129 129
130 130
            // Limit register pressure before processing this instruction.
131 -
            limitPressure(&mut scratch, &mut spilled, costs, numRegs);
131 +
            try limitPressure(&mut scratch, &mut spilled, costs, numRegs);
132 132
133 133
            // Enforce cross-call pressure at call sites.
134 134
            if il::isCall(instr) {
135 -
                limitCrossCallPressure(
135 +
                try limitCrossCallPressure(
136 136
                    &mut scratch, &mut spilled, costs,
137 137
                    &mut calleeClass, numCalleeSaved, il::instrDst(instr)
138 138
                );
139 139
            }
140 140
            // Remove definition from live set.
143 143
            }
144 144
            // Add uses to live set.
145 145
            il::forEachReg(instr, addRegToSetCallback, &mut scratch as &mut opaque);
146 146
        }
147 147
        // Also limit pressure at block entry.
148 -
        limitPressure(&mut scratch, &mut spilled, costs, numRegs);
148 +
        try limitPressure(&mut scratch, &mut spilled, costs, numRegs);
149 149
    }
150 150
151 151
    // Phase 3: Enforce global callee-class limit.
152 152
    // The per-call-site limit may leave the callee-class set larger than
153 153
    // `numCalleeSaved` when different call sites keep different subsets.
233 233
        bitset::clear(source, c.entries[i].reg);
234 234
    }
235 235
}
236 236
237 237
/// Collect all values from a bitset into a candidates buffer with their costs.
238 -
unsafe fn collectCandidates(bs: &bitset::Bitset, costs: *unsafe [SpillCost]) -> Candidates {
238 +
unsafe fn collectCandidates(bs: &bitset::Bitset, costs: *unsafe [SpillCost]) -> Candidates throws (alloc::AllocError) {
239 239
    let mut c = Candidates { entries: undefined, n: 0 };
240 240
    let mut it = bitset::iter(bs);
241 241
    while let reg = bitset::iterNext(&mut it) {
242 -
        assert c.n < MAX_CANDIDATES, "collectCandidates: too many live values";
242 +
        if c.n == MAX_CANDIDATES {
243 +
            throw alloc::AllocError::OutOfMemory;
244 +
        }
243 245
        if reg < costs.len {
244 246
            set c.entries[c.n] = CostEntry { reg, cost: costs[reg].defs + costs[reg].uses };
245 247
            set c.n += 1;
246 248
        }
247 249
    }
252 254
unsafe fn limitPressure(
253 255
    live: &mut bitset::Bitset,
254 256
    spilled: &mut bitset::Bitset,
255 257
    costs: *unsafe [SpillCost],
256 258
    numRegs: u32
257 -
) {
259 +
) throws (alloc::AllocError) {
258 260
    let liveCount = bitset::count(live);
259 261
    if liveCount <= numRegs {
260 262
        return;
261 263
    }
262 -
    let mut c = collectCandidates(live, costs);
264 +
    let mut c = try collectCandidates(live, costs);
263 265
    spillCheapest(&mut c, liveCount - numRegs, live, spilled);
264 266
}
265 267
266 268
/// Check whether an SSA register is the destination of a call instruction.
267 269
fn isCallDst(callDst: ?il::Reg, n: u32) -> bool {
281 283
    spilled: &mut bitset::Bitset,
282 284
    costs: *unsafe [SpillCost],
283 285
    calleeClass: &mut bitset::Bitset,
284 286
    numCalleeSaved: u32,
285 287
    callDst: ?il::Reg
286 -
) {
288 +
) throws (alloc::AllocError) {
287 289
    // Collect crossing candidates: live values excluding the call destination.
288 290
    let mut candidates: [CostEntry; 256] = undefined;
289 291
    let mut numCandidates: u32 = 0;
290 292
    let mut it = bitset::iter(live);
291 293
    while let n = bitset::iterNext(&mut it) {
292 294
        if not isCallDst(callDst, n) and n < costs.len {
295 +
            if numCandidates == MAX_CANDIDATES {
296 +
                throw alloc::AllocError::OutOfMemory;
297 +
            }
293 298
            set candidates[numCandidates] = CostEntry {
294 299
                reg: n,
295 300
                cost: costs[n].defs + costs[n].uses,
296 301
            };
297 302
            set numCandidates += 1;