compiler: Return register allocation capacity failures
cdfa85525d05e864a901c473b9234ed3580269cec20655bc5ee14d5e00c33e23
1 parent
873a9fac
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; |