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:
Jakob Stoklund Olesen
2018-01-17 15:17:34 -08:00
parent 0a6500c99a
commit dcad3fa339
3 changed files with 61 additions and 6 deletions

View File

@@ -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
}

View File

@@ -411,7 +411,13 @@ impl<'a> Context<'a> {
&regs.global, &regs.global,
); );
} }
self.program_output_constraints(inst, constraints.outs, defs); self.program_output_constraints(
inst,
constraints.outs,
defs,
&mut replace_global_defines,
&regs.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;
}
}
} }
} }
} }

View File

@@ -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.