Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions src/ion/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,124 @@ pub fn run<F: Function>(

Ok(())
}

#[cfg(test)]
mod tests {
use crate::{
Algorithm, Block, Function, Inst, InstRange, MachineEnv, Operand, OperandConstraint,
OperandKind, OperandPos, PReg, PRegSet, RegAllocError, RegClass, RegallocOptions, VReg,
Vec,
};
use alloc::vec;

struct ConflictFunc {
operands: Vec<Vec<Operand>>,
clobbers: Vec<PRegSet>,
}

impl Function for ConflictFunc {
fn num_insts(&self) -> usize {
self.operands.len()
}
fn num_blocks(&self) -> usize {
1
}
fn entry_block(&self) -> Block {
Block::new(0)
}
fn block_insns(&self, _: Block) -> InstRange {
InstRange::new(Inst::new(0), Inst::new(self.operands.len()))
}
fn block_succs(&self, _: Block) -> &[Block] {
&[]
}
fn block_preds(&self, _: Block) -> &[Block] {
&[]
}
fn block_params(&self, _: Block) -> &[VReg] {
&[]
}
fn is_ret(&self, insn: Inst) -> bool {
insn.index() + 1 == self.operands.len()
}
fn is_branch(&self, _: Inst) -> bool {
false
}
fn branch_blockparams(&self, _: Block, _: Inst, _: usize) -> &[VReg] {
&[]
}
fn inst_operands(&self, insn: Inst) -> &[Operand] {
&self.operands[insn.index()]
}
fn inst_clobbers(&self, insn: Inst) -> PRegSet {
self.clobbers[insn.index()]
}
fn num_vregs(&self) -> usize {
2
}
fn spillslot_size(&self, _: RegClass) -> usize {
1
}
}

fn int_env(nregs: usize) -> MachineEnv {
let mut regs = PRegSet::empty();
for hw in 0..nregs {
regs.add(PReg::new(hw, RegClass::Int));
}
MachineEnv {
preferred_regs_by_class: [regs, PRegSet::empty(), PRegSet::empty()],
non_preferred_regs_by_class: [PRegSet::empty(); 3],
scratch_by_class: [None, None, None],
fixed_stack_slots: vec![],
}
}

fn int_op(vreg: usize, constraint: OperandConstraint, kind: OperandKind) -> Operand {
Operand::new(
VReg::new(vreg, RegClass::Int),
constraint,
kind,
match kind {
OperandKind::Use => OperandPos::Early,
OperandKind::Def => OperandPos::Late,
},
)
}

#[test]
fn fixed_def_conflicting_with_clobber_is_an_error() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this test: we don't have any other unit tests like this, it's quite verbose, and we can instead test by modifying the fuzzer to generate such cases.

Can you update the fuzzer to generate these cases?

let p = |hw| PReg::new(hw, RegClass::Int);
let mut clobber = PRegSet::empty();
for hw in 0..3 {
clobber.add(p(hw));
}
let func = ConflictFunc {
operands: vec![
vec![int_op(0, OperandConstraint::Any, OperandKind::Def)],
vec![
int_op(1, OperandConstraint::FixedReg(p(0)), OperandKind::Def),
int_op(0, OperandConstraint::FixedReg(p(1)), OperandKind::Use),
],
vec![int_op(
1,
OperandConstraint::FixedReg(p(0)),
OperandKind::Use,
)],
],
clobbers: vec![PRegSet::empty(), clobber, PRegSet::empty()],
};
let env = int_env(3);
let opts = RegallocOptions {
verbose_log: false,
validate_ssa: true,
algorithm: Algorithm::Ion,
};
let err = crate::run(&func, &env, &opts).unwrap_err();
assert!(
matches!(err, RegAllocError::TooManyLiveRegs),
"expected TooManyLiveRegs, got {:?}",
err
);
}
}
10 changes: 10 additions & 0 deletions src/ion/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,16 @@ impl<'a, F: Function> Env<'a, F> {
|| lowest_cost_evict_conflict_cost.is_none()
|| lowest_cost_evict_conflict_cost.unwrap() >= our_spill_weight)
{
// A minimal bundle pinned to one physical register cannot
// move, and a fixed reservation on that register (a clobber
// is modeled as one) cannot be evicted. The overlap is
// illegal: a clobber must not collide with a fixed def or
// late use. Reject it instead of panicking.
if matches!(req, Requirement::FixedReg(_))
&& lowest_cost_evict_conflict_cost.is_none()
{
return Err(RegAllocError::TooManyLiveRegs);
}
if matches!(req, Requirement::Register | Requirement::Limit(_)) {
// Check if this is a too-many-live-registers situation.
let range = self.ctx.bundles[bundle].ranges[0].range;
Expand Down
Loading