Add a FixedTied constraint kind for operand constraints.

Fixes #175.

The Intel division instructions have fixed input operands that are
clobbered by fixed output operands, so the value passed as an input will
be clobbered just like a tied operand.

The FixedTied operand constraint is used to indicate a fixed input
operand that has a corresponding output operand with the same fixed
register.

Teach the spiller to teach a FixedTied operand the same as a Tied
operand constraint and make sure that the input value is killed by the
instruction.
This commit is contained in:
Jakob Stoklund Olesen
2017-10-25 11:14:11 -07:00
parent 2932a9314d
commit e8ecf1f809
7 changed files with 72 additions and 18 deletions

View File

@@ -0,0 +1,15 @@
test compile
set is_64bit=1
isa intel haswell
function %test(i64) -> i64 native {
ebb0(v0: i64):
v2 = iconst.i64 12
; This division clobbers two of its fixed input registers on Intel.
; These are FixedTied constraints that the spiller needs to resolve.
v5 = udiv v0, v2
v6 = iconst.i64 13
v9 = udiv v0, v6
v10 = iadd v5, v9
return v10
}

View File

@@ -394,6 +394,16 @@ class EncRecipe(object):
o2i[o] = i o2i[o] = i
return (i2o, o2i) return (i2o, o2i)
def fixed_ops(self):
# type: () -> Tuple[Set[Register], Set[Register]]
"""
Return two sets of registers representing the fixed input and output
operands.
"""
i = set(r for r in self.ins if isinstance(r, Register))
o = set(r for r in self.outs if isinstance(r, Register))
return (i, o)
def recipe_pred(self): def recipe_pred(self):
# type: () -> RecipePred # type: () -> RecipePred
""" """

View File

@@ -286,7 +286,10 @@ class RegClass(object):
For example: `GPR.r5`. For example: `GPR.r5`.
""" """
return Register(self, self.bank.unit_by_name(attr)) reg = Register(self, self.bank.unit_by_name(attr))
# Save this register so we won't have to create it again.
setattr(self, attr, reg)
return reg
def mask(self): def mask(self):
# type: () -> List[int] # type: () -> List[int]

View File

@@ -754,17 +754,14 @@ def emit_recipe_constraints(isa, fmt):
for r in isa.all_recipes: for r in isa.all_recipes:
fmt.comment(r.name) fmt.comment(r.name)
tied_i2o, tied_o2i = r.ties() tied_i2o, tied_o2i = r.ties()
fixed_ins, fixed_outs = r.fixed_ops()
with fmt.indented('RecipeConstraints {', '},'): with fmt.indented('RecipeConstraints {', '},'):
emit_operand_constraints(r, r.ins, 'ins', tied_i2o, fmt) emit_operand_constraints(
emit_operand_constraints(r, r.outs, 'outs', tied_o2i, fmt) r, r.ins, 'ins', tied_i2o, fixed_outs, fmt)
fmt.format( emit_operand_constraints(
'fixed_ins: {},', r, r.outs, 'outs', tied_o2i, fixed_ins, fmt)
str(any(isinstance(c, Register) fmt.format('fixed_ins: {},', str(bool(fixed_ins)).lower())
for c in r.ins)).lower()) fmt.format('fixed_outs: {},', str(bool(fixed_outs)).lower())
fmt.format(
'fixed_outs: {},',
str(any(isinstance(c, Register)
for c in r.outs)).lower())
fmt.format('tied_ops: {},', str(bool(tied_i2o)).lower()) fmt.format('tied_ops: {},', str(bool(tied_i2o)).lower())
fmt.format( fmt.format(
'clobbers_flags: {},', 'clobbers_flags: {},',
@@ -776,11 +773,16 @@ def emit_operand_constraints(
seq, # type: Sequence[OperandConstraint] seq, # type: Sequence[OperandConstraint]
field, # type: str field, # type: str
tied, # type: Dict[int, int] tied, # type: Dict[int, int]
fixops, # type: Set[Register]
fmt # type: srcgen.Formatter fmt # type: srcgen.Formatter
): ):
# type: (...) -> None # type: (...) -> None
""" """
Emit a struct field initializer for an array of operand constraints. Emit a struct field initializer for an array of operand constraints.
:param field: The name of the struct field to emit.
:param tied: Map of tied opnums to counterparts.
:param fix_ops: Set of fixed operands on the other side of the inst.
""" """
if len(seq) == 0: if len(seq) == 0:
fmt.line('{}: &[],'.format(field)) fmt.line('{}: &[],'.format(field))
@@ -796,8 +798,9 @@ def emit_operand_constraints(
fmt.format('regclass: &{}_DATA,', cons) fmt.format('regclass: &{}_DATA,', cons)
elif isinstance(cons, Register): elif isinstance(cons, Register):
assert n not in tied, "Can't tie fixed register operand" assert n not in tied, "Can't tie fixed register operand"
fmt.format( # See if this fixed register is also on the other side.
'kind: ConstraintKind::FixedReg({}),', cons.unit) t = 'FixedTied' if cons in fixops else 'FixedReg'
fmt.format('kind: ConstraintKind::{}({}),', t, cons.unit)
fmt.format('regclass: &{}_DATA,', cons.regclass) fmt.format('regclass: &{}_DATA,', cons.regclass)
elif isinstance(cons, int): elif isinstance(cons, int):
# This is a tied output constraint. It should never happen # This is a tied output constraint. It should never happen

View File

@@ -37,7 +37,8 @@ impl OperandConstraint {
false false
} }
} }
ConstraintKind::FixedReg(reg) => { ConstraintKind::FixedReg(reg) |
ConstraintKind::FixedTied(reg) => {
loc == ValueLoc::Reg(reg) && self.regclass.contains(reg) loc == ValueLoc::Reg(reg) && self.regclass.contains(reg)
} }
ConstraintKind::Stack => { ConstraintKind::Stack => {
@@ -73,6 +74,13 @@ pub enum ConstraintKind {
/// the out operand is `Tied(in)`. /// the out operand is `Tied(in)`.
Tied(u8), Tied(u8),
/// This operand must be a fixed register, and it has a tied counterpart.
///
/// This works just like `FixedReg`, but additionally indicates that there are identical
/// input/output operands for this fixed register. For an input operand, this means that the
/// value will be clobbered by the instruction
FixedTied(RegUnit),
/// This operand must be a value in a stack slot. /// This operand must be a value in a stack slot.
/// ///
/// The constraint's `regclass` field is the register class that would normally be used to load /// The constraint's `regclass` field is the register class that would normally be used to load

View File

@@ -478,7 +478,8 @@ impl<'a> Context<'a> {
// already in a register. // already in a register.
let cur_reg = self.divert.reg(value, &self.cur.func.locations); let cur_reg = self.divert.reg(value, &self.cur.func.locations);
match op.kind { match op.kind {
ConstraintKind::FixedReg(regunit) => { ConstraintKind::FixedReg(regunit) |
ConstraintKind::FixedTied(regunit) => {
if regunit != cur_reg { if regunit != cur_reg {
self.solver.reassign_in( self.solver.reassign_in(
value, value,
@@ -532,7 +533,9 @@ impl<'a> Context<'a> {
} }
} }
} }
_ => {} ConstraintKind::FixedReg(_) |
ConstraintKind::FixedTied(_) |
ConstraintKind::Stack => {}
} }
} }
} }
@@ -674,8 +677,15 @@ impl<'a> Context<'a> {
throughs: &[LiveValue], throughs: &[LiveValue],
) { ) {
for (op, lv) in constraints.iter().zip(defs) { for (op, lv) in constraints.iter().zip(defs) {
if let ConstraintKind::FixedReg(reg) = op.kind { match op.kind {
self.add_fixed_output(lv.value, op.regclass, reg, throughs); ConstraintKind::FixedReg(reg) |
ConstraintKind::FixedTied(reg) => {
self.add_fixed_output(lv.value, op.regclass, reg, throughs);
}
ConstraintKind::Reg |
ConstraintKind::Tied(_) |
ConstraintKind::Stack => {}
} }
} }
} }
@@ -741,6 +751,7 @@ impl<'a> Context<'a> {
for (op, lv) in constraints.iter().zip(defs) { for (op, lv) in constraints.iter().zip(defs) {
match op.kind { match op.kind {
ConstraintKind::FixedReg(_) | ConstraintKind::FixedReg(_) |
ConstraintKind::FixedTied(_) |
ConstraintKind::Stack => continue, ConstraintKind::Stack => continue,
ConstraintKind::Reg => { ConstraintKind::Reg => {
self.solver.add_def(lv.value, op.regclass, !lv.is_local); self.solver.add_def(lv.value, op.regclass, !lv.is_local);

View File

@@ -301,6 +301,10 @@ impl<'a> Context<'a> {
// A tied operand must kill the used value. // A tied operand must kill the used value.
reguse.tied = !lr.killed_at(inst, ebb, &self.cur.func.layout); reguse.tied = !lr.killed_at(inst, ebb, &self.cur.func.layout);
} }
ConstraintKind::FixedTied(_) => {
reguse.fixed = true;
reguse.tied = !lr.killed_at(inst, ebb, &self.cur.func.layout);
}
ConstraintKind::Reg => {} ConstraintKind::Reg => {}
} }
if lr.affinity.is_stack() { if lr.affinity.is_stack() {