Fix coloring bug with combined constraints and global values.
The Intel instruction "v1 = ushr v2, v2" will implicitly fix the output register for v2 to %rcx because the output is tied to the first input operand and the second input operand is fixed to %rcx. Make sure we handle this transitive constraint when checking for interference with the globally live registers. Fixes #218
This commit is contained in:
@@ -27,3 +27,26 @@ ebb0(v0: i64, v1: i64, v2: i64, v3: i64):
|
|||||||
ebb1:
|
ebb1:
|
||||||
return v4
|
return v4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
; Found by the Binaryen fuzzer in PR218.
|
||||||
|
;
|
||||||
|
; This is a similar situation involving combined constraints on the ushr instruction:
|
||||||
|
;
|
||||||
|
; - The %rcx register is already in use by a globally live value.
|
||||||
|
; - The ushr x, x result is also a globally live value.
|
||||||
|
;
|
||||||
|
; Since the ushr x, x result is forced to be placed in %rcx, we must set the replace_global_defines
|
||||||
|
; flag so it can be reassigned to a different global register.
|
||||||
|
function %pr218(i64 [%rdi], i64 [%rsi], i64 [%rdx], i64 [%rcx]) -> i64 [%rax] {
|
||||||
|
ebb0(v0: i64, v1: i64, v2: i64, v3: i64):
|
||||||
|
; check: regmove $v3, %rcx ->
|
||||||
|
v4 = ushr v0, v0
|
||||||
|
; check: $v4 = copy
|
||||||
|
jump ebb1
|
||||||
|
|
||||||
|
ebb1:
|
||||||
|
; v3 is globally live in %rcx.
|
||||||
|
; v4 is also globally live. Needs to be assigned something else for the trip across the CFG edge.
|
||||||
|
v5 = iadd v3, v4
|
||||||
|
return v5
|
||||||
|
}
|
||||||
|
|||||||
@@ -411,7 +411,13 @@ impl<'a> Context<'a> {
|
|||||||
®s.global,
|
®s.global,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.program_output_constraints(inst, constraints.outs, defs);
|
self.program_output_constraints(
|
||||||
|
inst,
|
||||||
|
constraints.outs,
|
||||||
|
defs,
|
||||||
|
&mut replace_global_defines,
|
||||||
|
®s.global,
|
||||||
|
);
|
||||||
|
|
||||||
// Finally, we've fully programmed the constraint solver.
|
// Finally, we've fully programmed the constraint solver.
|
||||||
// We expect a quick solution in most cases.
|
// We expect a quick solution in most cases.
|
||||||
@@ -794,6 +800,8 @@ impl<'a> Context<'a> {
|
|||||||
inst: Inst,
|
inst: Inst,
|
||||||
constraints: &[OperandConstraint],
|
constraints: &[OperandConstraint],
|
||||||
defs: &[LiveValue],
|
defs: &[LiveValue],
|
||||||
|
replace_global_defines: &mut bool,
|
||||||
|
global_regs: &AllocatableSet,
|
||||||
) {
|
) {
|
||||||
for (op, lv) in constraints.iter().zip(defs) {
|
for (op, lv) in constraints.iter().zip(defs) {
|
||||||
match op.kind {
|
match op.kind {
|
||||||
@@ -807,12 +815,26 @@ impl<'a> Context<'a> {
|
|||||||
// Find the input operand we're tied to.
|
// Find the input operand we're tied to.
|
||||||
// The solver doesn't care about the output value.
|
// The solver doesn't care about the output value.
|
||||||
let arg = self.cur.func.dfg.inst_args(inst)[num as usize];
|
let arg = self.cur.func.dfg.inst_args(inst)[num as usize];
|
||||||
self.solver.add_tied_input(
|
if let Some(reg) = self.solver.add_tied_input(
|
||||||
arg,
|
arg,
|
||||||
op.regclass,
|
op.regclass,
|
||||||
self.divert.reg(arg, &self.cur.func.locations),
|
self.divert.reg(arg, &self.cur.func.locations),
|
||||||
!lv.is_local,
|
!lv.is_local,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// The value we're tied to has been assigned to a fixed register.
|
||||||
|
// We need to make sure that fixed output register is compatible with the
|
||||||
|
// global register set.
|
||||||
|
if !lv.is_local && !global_regs.is_avail(op.regclass, reg) {
|
||||||
|
dbg!(
|
||||||
|
"Tied output {} in {}:{} is not available in global regs",
|
||||||
|
lv.value,
|
||||||
|
op.regclass,
|
||||||
|
self.reginfo.display_regunit(reg)
|
||||||
);
|
);
|
||||||
|
*replace_global_defines = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -760,14 +760,22 @@ impl Solver {
|
|||||||
///
|
///
|
||||||
/// The output value that must have the same register as the input value is not recorded in the
|
/// The output value that must have the same register as the input value is not recorded in the
|
||||||
/// solver.
|
/// solver.
|
||||||
pub fn add_tied_input(&mut self, value: Value, rc: RegClass, reg: RegUnit, is_global: bool) {
|
///
|
||||||
|
/// If the value has already been assigned to a fixed register, return that.
|
||||||
|
pub fn add_tied_input(
|
||||||
|
&mut self,
|
||||||
|
value: Value,
|
||||||
|
rc: RegClass,
|
||||||
|
reg: RegUnit,
|
||||||
|
is_global: bool,
|
||||||
|
) -> Option<RegUnit> {
|
||||||
debug_assert!(self.inputs_done);
|
debug_assert!(self.inputs_done);
|
||||||
|
|
||||||
// If a fixed assignment is tied, the `to` register is not available on the output side.
|
// If a fixed assignment is tied, the `to` register is not available on the output side.
|
||||||
if let Some(a) = self.assignments.get(value) {
|
if let Some(a) = self.assignments.get(value) {
|
||||||
debug_assert_eq!(a.from, reg);
|
debug_assert_eq!(a.from, reg);
|
||||||
self.regs_out.take(a.rc, a.to);
|
self.regs_out.take(a.rc, a.to);
|
||||||
return;
|
return Some(a.to);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a variable was created.
|
// Check if a variable was created.
|
||||||
@@ -775,7 +783,7 @@ impl Solver {
|
|||||||
assert!(v.is_input);
|
assert!(v.is_input);
|
||||||
v.is_output = true;
|
v.is_output = true;
|
||||||
v.is_global = is_global;
|
v.is_global = is_global;
|
||||||
return;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No variable exists for `value` because its constraints are already satisfied.
|
// No variable exists for `value` because its constraints are already satisfied.
|
||||||
@@ -790,6 +798,8 @@ impl Solver {
|
|||||||
} else {
|
} else {
|
||||||
self.regs_out.take(rc, reg);
|
self.regs_out.take(rc, reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a fixed output assignment.
|
/// Add a fixed output assignment.
|
||||||
|
|||||||
Reference in New Issue
Block a user