Make regalloc visit fallthrough_return instructions.
Add an explicit "is_ghost" property to selected instructions, and use that to determine whether reload and coloring should visit instructions. This allows them to visit fallthrough_return instructions and insert fills and register moves as needed.
This commit is contained in:
committed by
Benjamin Bouvier
parent
681cb5e20a
commit
88bbbca6cd
23
cranelift/filetests/regalloc/fallthrough-return.clif
Normal file
23
cranelift/filetests/regalloc/fallthrough-return.clif
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
test regalloc
|
||||||
|
target x86_64
|
||||||
|
|
||||||
|
; Test that fallthrough returns are visited by reload and coloring.
|
||||||
|
|
||||||
|
function %foo() -> f64 {
|
||||||
|
fn0 = %bar()
|
||||||
|
|
||||||
|
ebb0:
|
||||||
|
v0 = f64const 0.0
|
||||||
|
call fn0()
|
||||||
|
fallthrough_return v0
|
||||||
|
}
|
||||||
|
; check: fill v0
|
||||||
|
|
||||||
|
function %foo() -> f64 {
|
||||||
|
fn0 = %bar() -> f64, f64
|
||||||
|
|
||||||
|
ebb0:
|
||||||
|
v0, v1 = call fn0()
|
||||||
|
fallthrough_return v1
|
||||||
|
}
|
||||||
|
; check: regmove v1, %xmm1 -> %xmm0
|
||||||
@@ -844,7 +844,7 @@ vsplit = Instruction(
|
|||||||
the lanes from ``x``. The result may be two scalars if ``x`` only had
|
the lanes from ``x``. The result may be two scalars if ``x`` only had
|
||||||
two lanes.
|
two lanes.
|
||||||
""",
|
""",
|
||||||
ins=x, outs=(lo, hi))
|
ins=x, outs=(lo, hi), is_ghost=True)
|
||||||
|
|
||||||
Any128 = TypeVar(
|
Any128 = TypeVar(
|
||||||
'Any128', 'Any scalar or vector type with as most 128 lanes',
|
'Any128', 'Any scalar or vector type with as most 128 lanes',
|
||||||
@@ -864,7 +864,7 @@ vconcat = Instruction(
|
|||||||
|
|
||||||
It is possible to form a vector by concatenating two scalars.
|
It is possible to form a vector by concatenating two scalars.
|
||||||
""",
|
""",
|
||||||
ins=(x, y), outs=a)
|
ins=(x, y), outs=a, is_ghost=True)
|
||||||
|
|
||||||
c = Operand('c', TxN.as_bool(), doc='Controlling vector')
|
c = Operand('c', TxN.as_bool(), doc='Controlling vector')
|
||||||
x = Operand('x', TxN, doc='Value to use where `c` is true')
|
x = Operand('x', TxN, doc='Value to use where `c` is true')
|
||||||
@@ -2009,7 +2009,7 @@ isplit = Instruction(
|
|||||||
Returns the low half of `x` and the high half of `x` as two independent
|
Returns the low half of `x` and the high half of `x` as two independent
|
||||||
values.
|
values.
|
||||||
""",
|
""",
|
||||||
ins=x, outs=(lo, hi))
|
ins=x, outs=(lo, hi), is_ghost=True)
|
||||||
|
|
||||||
|
|
||||||
NarrowInt = TypeVar(
|
NarrowInt = TypeVar(
|
||||||
@@ -2029,6 +2029,6 @@ iconcat = Instruction(
|
|||||||
the same number of lanes as the inputs, but the lanes are twice the
|
the same number of lanes as the inputs, but the lanes are twice the
|
||||||
size.
|
size.
|
||||||
""",
|
""",
|
||||||
ins=(lo, hi), outs=a)
|
ins=(lo, hi), outs=a, is_ghost=True)
|
||||||
|
|
||||||
GROUP.close()
|
GROUP.close()
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ class Instruction(object):
|
|||||||
:param is_indirect_branch: This is an indirect branch instruction.
|
:param is_indirect_branch: This is an indirect branch instruction.
|
||||||
:param is_call: This is a call instruction.
|
:param is_call: This is a call instruction.
|
||||||
:param is_return: This is a return instruction.
|
:param is_return: This is a return instruction.
|
||||||
|
:param is_ghost: This is a ghost instruction.
|
||||||
:param can_trap: This instruction can trap.
|
:param can_trap: This instruction can trap.
|
||||||
:param can_load: This instruction can load from memory.
|
:param can_load: This instruction can load from memory.
|
||||||
:param can_store: This instruction can store to memory.
|
:param can_store: This instruction can store to memory.
|
||||||
@@ -108,6 +109,7 @@ class Instruction(object):
|
|||||||
'True for all indirect branch or jump instructions.',
|
'True for all indirect branch or jump instructions.',
|
||||||
'is_call': 'Is this a call instruction?',
|
'is_call': 'Is this a call instruction?',
|
||||||
'is_return': 'Is this a return instruction?',
|
'is_return': 'Is this a return instruction?',
|
||||||
|
'is_ghost': 'Is this a ghost instruction?',
|
||||||
'can_load': 'Can this instruction read from memory?',
|
'can_load': 'Can this instruction read from memory?',
|
||||||
'can_store': 'Can this instruction write to memory?',
|
'can_store': 'Can this instruction write to memory?',
|
||||||
'can_trap': 'Can this instruction cause a trap?',
|
'can_trap': 'Can this instruction cause a trap?',
|
||||||
|
|||||||
@@ -165,8 +165,9 @@ impl<'a> Context<'a> {
|
|||||||
self.cur.goto_top(ebb);
|
self.cur.goto_top(ebb);
|
||||||
while let Some(inst) = self.cur.next_inst() {
|
while let Some(inst) = self.cur.next_inst() {
|
||||||
self.cur.use_srcloc(inst);
|
self.cur.use_srcloc(inst);
|
||||||
let enc = self.cur.func.encodings[inst];
|
if !self.cur.func.dfg[inst].opcode().is_ghost() {
|
||||||
if let Some(constraints) = self.encinfo.operand_constraints(enc) {
|
let enc = self.cur.func.encodings[inst];
|
||||||
|
let constraints = self.encinfo.operand_constraints(enc);
|
||||||
if self.visit_inst(inst, constraints, tracker, &mut regs) {
|
if self.visit_inst(inst, constraints, tracker, &mut regs) {
|
||||||
self.replace_global_defines(inst, tracker);
|
self.replace_global_defines(inst, tracker);
|
||||||
// Restore cursor location after `replace_global_defines` moves it.
|
// Restore cursor location after `replace_global_defines` moves it.
|
||||||
@@ -290,7 +291,7 @@ impl<'a> Context<'a> {
|
|||||||
fn visit_inst(
|
fn visit_inst(
|
||||||
&mut self,
|
&mut self,
|
||||||
inst: Inst,
|
inst: Inst,
|
||||||
constraints: &RecipeConstraints,
|
constraints: Option<&RecipeConstraints>,
|
||||||
tracker: &mut LiveValueTracker,
|
tracker: &mut LiveValueTracker,
|
||||||
regs: &mut AvailableRegs,
|
regs: &mut AvailableRegs,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
@@ -306,7 +307,9 @@ impl<'a> Context<'a> {
|
|||||||
|
|
||||||
// Program the solver with register constraints for the input side.
|
// Program the solver with register constraints for the input side.
|
||||||
self.solver.reset(®s.input);
|
self.solver.reset(®s.input);
|
||||||
self.program_input_constraints(inst, constraints.ins);
|
if let Some(constraints) = constraints {
|
||||||
|
self.program_input_constraints(inst, constraints.ins);
|
||||||
|
}
|
||||||
let call_sig = self.cur.func.dfg.call_signature(inst);
|
let call_sig = self.cur.func.dfg.call_signature(inst);
|
||||||
if let Some(sig) = call_sig {
|
if let Some(sig) = call_sig {
|
||||||
program_input_abi(
|
program_input_abi(
|
||||||
@@ -390,14 +393,16 @@ impl<'a> Context<'a> {
|
|||||||
// Program the fixed output constraints before the general defines. This allows us to
|
// 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
|
// detect conflicts between fixed outputs and tied operands where the input value hasn't
|
||||||
// been converted to a solver variable.
|
// been converted to a solver variable.
|
||||||
if constraints.fixed_outs {
|
if let Some(constraints) = constraints {
|
||||||
self.program_fixed_outputs(
|
if constraints.fixed_outs {
|
||||||
constraints.outs,
|
self.program_fixed_outputs(
|
||||||
defs,
|
constraints.outs,
|
||||||
throughs,
|
defs,
|
||||||
&mut replace_global_defines,
|
throughs,
|
||||||
®s.global,
|
&mut replace_global_defines,
|
||||||
);
|
®s.global,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(sig) = call_sig {
|
if let Some(sig) = call_sig {
|
||||||
self.program_output_abi(
|
self.program_output_abi(
|
||||||
@@ -408,13 +413,15 @@ impl<'a> Context<'a> {
|
|||||||
®s.global,
|
®s.global,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.program_output_constraints(
|
if let Some(constraints) = constraints {
|
||||||
inst,
|
self.program_output_constraints(
|
||||||
constraints.outs,
|
inst,
|
||||||
defs,
|
constraints.outs,
|
||||||
&mut replace_global_defines,
|
defs,
|
||||||
®s.global,
|
&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.
|
||||||
@@ -440,12 +447,14 @@ impl<'a> Context<'a> {
|
|||||||
|
|
||||||
// Tied defs are not part of the solution above.
|
// Tied defs are not part of the solution above.
|
||||||
// Copy register assignments from tied inputs to tied outputs.
|
// Copy register assignments from tied inputs to tied outputs.
|
||||||
if constraints.tied_ops {
|
if let Some(constraints) = constraints {
|
||||||
for (op, lv) in constraints.outs.iter().zip(defs) {
|
if constraints.tied_ops {
|
||||||
if let ConstraintKind::Tied(num) = op.kind {
|
for (op, lv) in constraints.outs.iter().zip(defs) {
|
||||||
let arg = self.cur.func.dfg.inst_args(inst)[num as usize];
|
if let ConstraintKind::Tied(num) = op.kind {
|
||||||
let reg = self.divert.reg(arg, &self.cur.func.locations);
|
let arg = self.cur.func.dfg.inst_args(inst)[num as usize];
|
||||||
self.cur.func.locations[lv.value] = ValueLoc::Reg(reg);
|
let reg = self.divert.reg(arg, &self.cur.func.locations);
|
||||||
|
self.cur.func.locations[lv.value] = ValueLoc::Reg(reg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,8 +125,8 @@ impl<'a> Context<'a> {
|
|||||||
|
|
||||||
// visit_ebb_header() places us at the first interesting instruction in the EBB.
|
// visit_ebb_header() places us at the first interesting instruction in the EBB.
|
||||||
while let Some(inst) = self.cur.current_inst() {
|
while let Some(inst) = self.cur.current_inst() {
|
||||||
let encoding = self.cur.func.encodings[inst];
|
if !self.cur.func.dfg[inst].opcode().is_ghost() {
|
||||||
if encoding.is_legal() {
|
let encoding = self.cur.func.encodings[inst];
|
||||||
self.visit_inst(ebb, inst, encoding, tracker);
|
self.visit_inst(ebb, inst, encoding, tracker);
|
||||||
tracker.drop_dead(inst);
|
tracker.drop_dead(inst);
|
||||||
} else {
|
} else {
|
||||||
@@ -200,10 +200,7 @@ impl<'a> Context<'a> {
|
|||||||
self.cur.use_srcloc(inst);
|
self.cur.use_srcloc(inst);
|
||||||
|
|
||||||
// Get the operand constraints for `inst` that we are trying to satisfy.
|
// Get the operand constraints for `inst` that we are trying to satisfy.
|
||||||
let constraints = self
|
let constraints = self.encinfo.operand_constraints(encoding);
|
||||||
.encinfo
|
|
||||||
.operand_constraints(encoding)
|
|
||||||
.expect("Missing instruction encoding");
|
|
||||||
|
|
||||||
// Identify reload candidates.
|
// Identify reload candidates.
|
||||||
debug_assert!(self.candidates.is_empty());
|
debug_assert!(self.candidates.is_empty());
|
||||||
@@ -240,27 +237,32 @@ impl<'a> Context<'a> {
|
|||||||
// v2 = spill v7
|
// v2 = spill v7
|
||||||
//
|
//
|
||||||
// That way, we don't need to rewrite all future uses of v2.
|
// That way, we don't need to rewrite all future uses of v2.
|
||||||
for (lv, op) in defs.iter().zip(constraints.outs) {
|
if let Some(constraints) = constraints {
|
||||||
if lv.affinity.is_stack() && op.kind != ConstraintKind::Stack {
|
for (lv, op) in defs.iter().zip(constraints.outs) {
|
||||||
if let InstructionData::Unary {
|
if lv.affinity.is_stack() && op.kind != ConstraintKind::Stack {
|
||||||
opcode: Opcode::Copy,
|
if let InstructionData::Unary {
|
||||||
arg,
|
opcode: Opcode::Copy,
|
||||||
} = self.cur.func.dfg[inst]
|
arg,
|
||||||
{
|
} = self.cur.func.dfg[inst]
|
||||||
self.cur.func.dfg.replace(inst).spill(arg);
|
{
|
||||||
let ok = self.cur.func.update_encoding(inst, self.cur.isa).is_ok();
|
self.cur.func.dfg.replace(inst).spill(arg);
|
||||||
debug_assert!(ok);
|
let ok = self.cur.func.update_encoding(inst, self.cur.isa).is_ok();
|
||||||
} else {
|
debug_assert!(ok);
|
||||||
let value_type = self.cur.func.dfg.value_type(lv.value);
|
} else {
|
||||||
let reg = self.cur.func.dfg.replace_result(lv.value, value_type);
|
let value_type = self.cur.func.dfg.value_type(lv.value);
|
||||||
self.liveness.create_dead(reg, inst, Affinity::new(op));
|
let reg = self.cur.func.dfg.replace_result(lv.value, value_type);
|
||||||
self.insert_spill(ebb, lv.value, reg);
|
self.liveness.create_dead(reg, inst, Affinity::new(op));
|
||||||
|
self.insert_spill(ebb, lv.value, reg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same thing for spilled call return values.
|
// Same thing for spilled call return values.
|
||||||
let retvals = &defs[constraints.outs.len()..];
|
let retvals = &defs[self.cur.func.dfg[inst]
|
||||||
|
.opcode()
|
||||||
|
.constraints()
|
||||||
|
.fixed_results()..];
|
||||||
if !retvals.is_empty() {
|
if !retvals.is_empty() {
|
||||||
let sig = self
|
let sig = self
|
||||||
.cur
|
.cur
|
||||||
@@ -342,21 +344,26 @@ impl<'a> Context<'a> {
|
|||||||
// Find reload candidates for `inst` and add them to `self.candidates`.
|
// Find reload candidates for `inst` and add them to `self.candidates`.
|
||||||
//
|
//
|
||||||
// These are uses of spilled values where the operand constraint requires a register.
|
// These are uses of spilled values where the operand constraint requires a register.
|
||||||
fn find_candidates(&mut self, inst: Inst, constraints: &RecipeConstraints) {
|
fn find_candidates(&mut self, inst: Inst, constraints: Option<&RecipeConstraints>) {
|
||||||
let args = self.cur.func.dfg.inst_args(inst);
|
let args = self.cur.func.dfg.inst_args(inst);
|
||||||
|
|
||||||
for (argidx, (op, &arg)) in constraints.ins.iter().zip(args).enumerate() {
|
if let Some(constraints) = constraints {
|
||||||
if op.kind != ConstraintKind::Stack && self.liveness[arg].affinity.is_stack() {
|
for (argidx, (op, &arg)) in constraints.ins.iter().zip(args).enumerate() {
|
||||||
self.candidates.push(ReloadCandidate {
|
if op.kind != ConstraintKind::Stack && self.liveness[arg].affinity.is_stack() {
|
||||||
argidx,
|
self.candidates.push(ReloadCandidate {
|
||||||
value: arg,
|
argidx,
|
||||||
regclass: op.regclass,
|
value: arg,
|
||||||
})
|
regclass: op.regclass,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we only have the fixed arguments, we're done now.
|
// If we only have the fixed arguments, we're done now.
|
||||||
let offset = constraints.ins.len();
|
let offset = self.cur.func.dfg[inst]
|
||||||
|
.opcode()
|
||||||
|
.constraints()
|
||||||
|
.fixed_value_arguments();
|
||||||
if args.len() == offset {
|
if args.len() == offset {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user