diff --git a/cranelift/filetests/regalloc/multi-constraints.cton b/cranelift/filetests/regalloc/multi-constraints.cton index 80dc8f0658..ecb3aa8bad 100644 --- a/cranelift/filetests/regalloc/multi-constraints.cton +++ b/cranelift/filetests/regalloc/multi-constraints.cton @@ -27,3 +27,26 @@ ebb0(v0: i64, v1: i64, v2: i64, v3: i64): ebb1: 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 +} diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 18d141eb75..74d80e1832 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -411,7 +411,13 @@ impl<'a> Context<'a> { ®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. // We expect a quick solution in most cases. @@ -794,6 +800,8 @@ impl<'a> Context<'a> { inst: Inst, constraints: &[OperandConstraint], defs: &[LiveValue], + replace_global_defines: &mut bool, + global_regs: &AllocatableSet, ) { for (op, lv) in constraints.iter().zip(defs) { match op.kind { @@ -807,12 +815,26 @@ impl<'a> Context<'a> { // Find the input operand we're tied to. // The solver doesn't care about the output value. 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, op.regclass, self.divert.reg(arg, &self.cur.func.locations), !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; + } + } } } } diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs index c7b884ce43..b5d5aa661b 100644 --- a/lib/cretonne/src/regalloc/solver.rs +++ b/lib/cretonne/src/regalloc/solver.rs @@ -760,14 +760,22 @@ impl Solver { /// /// The output value that must have the same register as the input value is not recorded in the /// 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 { debug_assert!(self.inputs_done); // If a fixed assignment is tied, the `to` register is not available on the output side. if let Some(a) = self.assignments.get(value) { debug_assert_eq!(a.from, reg); self.regs_out.take(a.rc, a.to); - return; + return Some(a.to); } // Check if a variable was created. @@ -775,7 +783,7 @@ impl Solver { assert!(v.is_input); v.is_output = true; v.is_global = is_global; - return; + return None; } // No variable exists for `value` because its constraints are already satisfied. @@ -790,6 +798,8 @@ impl Solver { } else { self.regs_out.take(rc, reg); } + + None } /// Add a fixed output assignment.