Also consider fixed outputs for replace_global_defines.

Fixes #178.

When an instruction with a fixed output operand defines a globally live
SSA value, we need to check if the fixed register is available in the
`regs.global` set of registers that can be used across EBB boundaries.

If the fixed output register is not available in regs.global, set the
replace_global_defines flag so the output operands are rewritten as
local values.
This commit is contained in:
Jakob Stoklund Olesen
2017-10-25 14:28:30 -07:00
parent 1b71285b34
commit d37126565e
2 changed files with 63 additions and 8 deletions

View File

@@ -0,0 +1,17 @@
test compile
set is_64bit=1
isa intel haswell
function %foo() native {
ebb4:
v3 = iconst.i32 0
jump ebb3
ebb3:
v9 = udiv v3, v3
jump ebb1
ebb1:
v19 = iadd.i32 v9, v9
jump ebb3
}

View File

@@ -377,23 +377,34 @@ impl<'a> Context<'a> {
// This aligns with the " from" line at the top of the function.
dbg!(" glob {}", regs.global.display(&self.reginfo));
// This flag is set when the solver failed to find a solution for the global defines that
// doesn't interfere with `regs.global`. We need to rewrite all of `inst`s global defines
// as local defines followed by copies.
let mut replace_global_defines = false;
// Program the fixed output constraints before the general defines. This allows us to
// detect conflicts between fixed outputs and tied operands where the input value hasn't
// been converted to a solver variable.
if constraints.fixed_outs {
self.program_fixed_outputs(constraints.outs, defs, throughs);
self.program_fixed_outputs(
constraints.outs,
defs,
throughs,
&mut replace_global_defines,
&regs.global,
);
}
if let Some(sig) = call_sig {
self.program_output_abi(sig, defs, throughs);
self.program_output_abi(
sig,
defs,
throughs,
&mut replace_global_defines,
&regs.global,
);
}
self.program_output_constraints(inst, constraints.outs, defs);
// This flag is set when the solver failed to find a solution for the global defines that
// doesn't interfere with `regs.global`. We need to rewrite all of `inst`s global defines
// as local defines followed by copies.
let mut replace_global_defines = false;
// Finally, we've fully programmed the constraint solver.
// We expect a quick solution in most cases.
let output_regs = self.solver.quick_solve(&regs.global).unwrap_or_else(|_| {
@@ -675,12 +686,23 @@ impl<'a> Context<'a> {
constraints: &[OperandConstraint],
defs: &[LiveValue],
throughs: &[LiveValue],
replace_global_defines: &mut bool,
global_regs: &AllocatableSet,
) {
for (op, lv) in constraints.iter().zip(defs) {
match op.kind {
ConstraintKind::FixedReg(reg) |
ConstraintKind::FixedTied(reg) => {
self.add_fixed_output(lv.value, op.regclass, reg, throughs);
if !lv.is_local && !global_regs.is_avail(op.regclass, reg) {
dbg!(
"Fixed output {} in {}:{} is not available in global regs",
lv.value,
op.regclass,
self.reginfo.display_regunit(reg)
);
*replace_global_defines = true;
}
}
ConstraintKind::Reg |
ConstraintKind::Tied(_) |
@@ -693,7 +715,14 @@ impl<'a> Context<'a> {
/// Program the output-side ABI constraints for `inst` into the constraint solver.
///
/// That means return values for a call instruction.
fn program_output_abi(&mut self, sig: SigRef, defs: &[LiveValue], throughs: &[LiveValue]) {
fn program_output_abi(
&mut self,
sig: SigRef,
defs: &[LiveValue],
throughs: &[LiveValue],
replace_global_defines: &mut bool,
global_regs: &AllocatableSet,
) {
// It's technically possible for a call instruction to have fixed results before the
// variable list of results, but we have no known instances of that.
// Just assume all results are variable return values.
@@ -704,6 +733,15 @@ impl<'a> Context<'a> {
if let Affinity::Reg(rci) = lv.affinity {
let rc = self.reginfo.rc(rci);
self.add_fixed_output(lv.value, rc, reg, throughs);
if !lv.is_local && !global_regs.is_avail(rc, reg) {
dbg!(
"ABI output {} in {}:{} is not available in global regs",
lv.value,
rc,
self.reginfo.display_regunit(reg)
);
*replace_global_defines = true;
}
} else {
panic!("ABI argument {} should be in a register", lv.value);
}