Skip to content

Commit 543973b

Browse files
committed
Track multiple vregs per assignment in regalloc verifier
Previously, the regalloc verifier was stricter than necessary in its handling of block exit ghost copies: whenever it encountered one, it would forget the vreg currently attached to the indicated assignment and track just the new one. In principle, that assignment could be used as either of the vregs until overwritten, but the verifier accepted only the destination vreg. This strict behavior ended up working with assignments produced by the allocator because it always placed block param copies in predecessors, which meant the assignment referenced by the ghost copy was always a freshly-overwritten one. We'll soon want to sink those param copies into the successor where possible, though, and at that point it will be possible for several vregs to share a physical assignment. Prepare for that now in the verifier by tracking lists of vregs per assignment instead of just a single one. This ended up propagating all the way to the public API because errors about unexpected values in registers now report the full list of tracked vregs.
1 parent 40d2e4e commit 543973b

File tree

1 file changed

+76
-39
lines changed

1 file changed

+76
-39
lines changed

crates/codegen/src/regalloc/verify.rs

Lines changed: 76 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
use core::fmt;
22

3-
use alloc::{borrow::ToOwned, collections::VecDeque, string::ToString};
3+
use alloc::{
4+
borrow::ToOwned,
5+
boxed::Box,
6+
collections::VecDeque,
7+
format,
8+
string::{String, ToString},
9+
};
410

511
use cranelift_entity::SecondaryMap;
612
use fx_utils::FxHashMap;
7-
use itertools::izip;
13+
use itertools::{izip, Itertools};
814
use log::trace;
15+
use smallvec::{smallvec, SmallVec};
916

1017
use crate::{
1118
cfg::{Block, CfgContext},
@@ -18,7 +25,7 @@ use crate::{
1825

1926
use super::{Assignment, AssignmentCopy, OperandAssignment};
2027

21-
#[derive(Clone, Copy)]
28+
#[derive(Clone)]
2229
pub enum VerifierError {
2330
BadUseCount(Instr),
2431
BadDefCount(Instr),
@@ -33,7 +40,7 @@ pub enum VerifierError {
3340
BadUse {
3441
instr: Instr,
3542
op: u32,
36-
found_vreg: Option<VirtReg>,
43+
found_vregs: Option<Box<[VirtReg]>>,
3744
},
3845
IllegalSpillCopy {
3946
copy_idx: u32,
@@ -49,7 +56,7 @@ pub enum VerifierError {
4956
},
5057
BadGhostCopySource {
5158
ghost_copy_idx: u32,
52-
found_vreg: Option<VirtReg>,
59+
found_vregs: Option<Box<[VirtReg]>>,
5360
},
5461
}
5562

@@ -111,15 +118,13 @@ impl<M: MachineCore> fmt::Display for DisplayVerifierError<'_, M> {
111118
VerifierError::BadUse {
112119
instr,
113120
op,
114-
found_vreg,
121+
ref found_vregs,
115122
} => {
116-
let found_vreg = found_vreg.map_or("garbage".to_owned(), |found_vreg: VirtReg| {
117-
found_vreg.to_string()
118-
});
123+
let found_vregs = display_found_vregs(found_vregs.as_deref());
119124
let expected_vreg = self.lir.instr_uses(instr)[op as usize].reg();
120125
write!(
121126
f,
122-
"expected {instr} use {op} to be of {expected_vreg}, found {found_vreg}"
127+
"expected {instr} use {op} to be of {expected_vreg}, found {found_vregs}"
123128
)
124129
}
125130
VerifierError::IllegalSpillCopy { copy_idx } => {
@@ -158,16 +163,15 @@ impl<M: MachineCore> fmt::Display for DisplayVerifierError<'_, M> {
158163
}
159164
VerifierError::BadGhostCopySource {
160165
ghost_copy_idx,
161-
found_vreg,
166+
ref found_vregs,
162167
} => {
163-
let found_vreg =
164-
found_vreg.map_or("garbage".to_owned(), |found_vreg| found_vreg.to_string());
168+
let found_vregs = display_found_vregs(found_vregs.as_deref());
165169
let ghost_copy = &self.assignment.block_exit_ghost_copies[ghost_copy_idx as usize];
166170
let block = ghost_copy.block;
167171
let expected_vreg = ghost_copy.from_vreg;
168172
write!(
169173
f,
170-
"expected ghost copy for '{}' at end of '{block}' to be from {expected_vreg}, found {found_vreg}",
174+
"expected ghost copy for '{}' at end of '{block}' to be from {expected_vreg}, found {found_vregs}",
171175
ghost_copy.assignment.display::<M>(),
172176
)
173177
}
@@ -183,9 +187,10 @@ pub fn verify<M: MachineCore>(
183187
let mut block_entry_reg_states = BlockKnownRegState::with_capacity(cfg_ctx.block_order.len());
184188

185189
let entry = cfg_ctx.block_order[0];
190+
186191
let mut reg_state = KnownRegState::default();
187192
for (&live_in, &preg) in lir.block_params(entry).iter().zip(lir.live_in_regs()) {
188-
reg_state.insert(OperandAssignment::Reg(preg), live_in);
193+
reg_state.insert(OperandAssignment::Reg(preg), smallvec![live_in]);
189194
}
190195

191196
let mut worklist = VecDeque::new();
@@ -212,7 +217,17 @@ pub fn verify<M: MachineCore>(
212217
Ok(())
213218
}
214219

215-
type KnownRegState = FxHashMap<OperandAssignment, VirtReg>;
220+
fn display_found_vregs(found_vregs: Option<&[VirtReg]>) -> String {
221+
match found_vregs {
222+
Some(&[found_vreg]) => found_vreg.to_string(),
223+
Some(found_vregs) => {
224+
format!("{{{}}}", found_vregs.iter().format(", "))
225+
}
226+
None => "garbage".to_owned(),
227+
}
228+
}
229+
230+
type KnownRegState = FxHashMap<OperandAssignment, SmallVec<[VirtReg; 2]>>;
216231
type BlockKnownRegState = SecondaryMap<Block, Option<KnownRegState>>;
217232

218233
fn verify_block_ghost_copies(
@@ -229,13 +244,20 @@ fn verify_block_ghost_copies(
229244
for ghost_copy_idx in ghost_copy_range {
230245
let ghost_copy = &assignment.block_exit_ghost_copies[ghost_copy_idx];
231246
let operand = ghost_copy.assignment;
232-
check_assigned_vreg(operand, ghost_copy.from_vreg, &*reg_state).map_err(|found_vreg| {
247+
check_assigned_vreg(operand, ghost_copy.from_vreg, reg_state).map_err(|found_vregs| {
233248
VerifierError::BadGhostCopySource {
234249
ghost_copy_idx: ghost_copy_idx.try_into().unwrap(),
235-
found_vreg,
250+
found_vregs,
236251
}
237252
})?;
238-
reg_state.insert(operand, ghost_copy.to_vreg);
253+
254+
// Ghost copies are special in that they copy between vregs in the same physical assignment
255+
// rather than between physical assignments in the same vreg. That means the same physical
256+
// assignment may now simultaneously house an additional vreg.
257+
let vregs = reg_state.get_mut(&operand).unwrap();
258+
if !vregs.contains(&ghost_copy.to_vreg) {
259+
vregs.push(ghost_copy.to_vreg);
260+
}
239261
}
240262

241263
Ok(())
@@ -273,10 +295,13 @@ fn verify_copy<M: MachineCore>(
273295
return Err(VerifierError::IllegalSpillCopy { copy_idx });
274296
}
275297

276-
let from_vreg = match copy.from {
277-
CopySourceAssignment::Operand(from) => *reg_state
278-
.get(&from)
279-
.ok_or(VerifierError::UndefCopySource { copy_idx })?,
298+
match copy.from {
299+
CopySourceAssignment::Operand(from) => {
300+
let from = reg_state
301+
.get(&from)
302+
.ok_or(VerifierError::UndefCopySource { copy_idx })?;
303+
reg_state.insert(copy.to, from.clone());
304+
}
280305
CopySourceAssignment::Remat(instr) => {
281306
if !lir.instr_uses(instr).is_empty() {
282307
return Err(VerifierError::BadRematOperands { copy_idx });
@@ -293,12 +318,10 @@ fn verify_copy<M: MachineCore>(
293318
return Err(VerifierError::BadRematOperands { copy_idx });
294319
}
295320

296-
def.reg()
321+
reg_state.insert(copy.to, smallvec![def.reg()]);
297322
}
298323
};
299324

300-
reg_state.insert(copy.to, from_vreg);
301-
302325
Ok(())
303326
}
304327

@@ -324,11 +347,11 @@ fn verify_instr<M: MachineCore>(
324347
if !use_matches_constraint(use_op, use_assignment, def_assignments) {
325348
return Err(VerifierError::UseConstraintViolation { instr, op: i });
326349
}
327-
check_use_assignment(use_op, use_assignment, reg_state).map_err(|found_vreg| {
350+
check_use_assignment(use_op, use_assignment, reg_state).map_err(|found_vregs| {
328351
VerifierError::BadUse {
329352
instr,
330353
op: i,
331-
found_vreg,
354+
found_vregs,
332355
}
333356
})?;
334357
}
@@ -341,7 +364,7 @@ fn verify_instr<M: MachineCore>(
341364
if !def_matches_constraint(def_op, def_assignment) {
342365
return Err(VerifierError::DefConstraintViolation { instr, op: i });
343366
}
344-
reg_state.insert(def_assignment, def_op.reg());
367+
reg_state.insert(def_assignment, smallvec![def_op.reg()]);
345368
}
346369

347370
Ok(())
@@ -351,20 +374,25 @@ fn check_use_assignment(
351374
use_op: UseOperand,
352375
use_assignment: OperandAssignment,
353376
reg_state: &KnownRegState,
354-
) -> Result<(), Option<VirtReg>> {
377+
) -> Result<(), Option<Box<[VirtReg]>>> {
355378
check_assigned_vreg(use_assignment, use_op.reg(), reg_state)
356379
}
357380

358381
fn check_assigned_vreg(
359382
assignment: OperandAssignment,
360383
expected_vreg: VirtReg,
361384
reg_state: &KnownRegState,
362-
) -> Result<(), Option<VirtReg>> {
363-
let found_vreg = reg_state.get(&assignment).copied();
364-
if found_vreg != Some(expected_vreg) {
365-
return Err(found_vreg);
385+
) -> Result<(), Option<Box<[VirtReg]>>> {
386+
match reg_state.get(&assignment) {
387+
Some(found_vregs) => {
388+
if !found_vregs.contains(&expected_vreg) {
389+
return Err(Some(found_vregs.as_slice().into()));
390+
}
391+
392+
Ok(())
393+
}
394+
None => Err(None),
366395
}
367-
Ok(())
368396
}
369397

370398
fn use_matches_constraint(
@@ -415,10 +443,19 @@ fn merge_reg_state(
415443
) -> bool {
416444
let mut changed = false;
417445

418-
existing_reg_state.retain(|operand, known_reg| {
419-
let retain = incoming_reg_state
420-
.get(operand)
421-
.is_some_and(|incoming| incoming == known_reg);
446+
existing_reg_state.retain(|operand, known_regs| {
447+
let retain = match incoming_reg_state.get(operand) {
448+
Some(incoming) => {
449+
// This can end up quadratic in the worst case, but these lists will almost always
450+
// have 1 or 2 elements in practice. We also don't care that much about the
451+
// performance of the verifier as long as it isn't unbearably slow - it's just a
452+
// debugging aid.
453+
known_regs.retain(|reg| incoming.contains(reg));
454+
!known_regs.is_empty()
455+
}
456+
None => false,
457+
};
458+
422459
if !retain {
423460
changed = true;
424461
}

0 commit comments

Comments
 (0)