Optimize immediates and compare and branch sequences (#286)

* Add a pre-opt optimization to change constants into immediates.

This converts 'iadd' + 'iconst' into 'iadd_imm', and so on.

* Optimize away redundant `bint` instructions.

Cretonne has a concept of "Testable" values, which can be either boolean
or integer. When the an instruction needing a "Testable" value receives
the result of a `bint`, converting boolean to integer, eliminate the
`bint`, as it's redundant.

* Postopt: Optimize using CPU flags.

This introduces a post-legalization optimization pass which converts
compare+branch sequences to use flags values on CPUs which support it.

* Define a form of x86's `urm` that doesn't clobber FLAGS.

movzbl/movsbl/etc. don't clobber FLAGS; define a form of the `urm`
recipe that represents this.

* Implement a DCE pass.

This pass deletes instructions with no side effects and no results that
are used.

* Clarify ambiguity about "32-bit" and "64-bit" in comments.

* Add x86 encodings for icmp_imm.

* Add a testcase for postopt CPU flags optimization.

This covers the basic functionality of transforming compare+branch
sequences to use CPU flags.

* Pattern-match irsub_imm in preopt.
This commit is contained in:
Dan Gohman
2018-03-30 12:30:07 -07:00
committed by GitHub
parent 5377092e5b
commit 6606b88136
22 changed files with 921 additions and 109 deletions

View File

@@ -127,28 +127,6 @@ fn get_div_info(inst: Inst, dfg: &DataFlowGraph) -> Option<DivRemByConstInfo> {
return package_up_divrem_info(arg, argL_ty, imm.into(), isSigned, isRem);
}
// TODO: should we actually bother to do this (that is, manually match
// the case that the second argument is an iconst)? Or should we assume
// that some previous constant propagation pass has pushed all such
// immediates to their use points, creating BinaryImm instructions
// instead? For now we take the conservative approach.
if let InstructionData::Binary { opcode, args } = *idata {
let (isSigned, isRem) = match opcode {
Opcode::Udiv => (false, false),
Opcode::Urem => (false, true),
Opcode::Sdiv => (true, false),
Opcode::Srem => (true, true),
_other => return None,
};
let argR: Value = args[1];
if let Some(simm64) = get_const(argR, dfg) {
let argL: Value = args[0];
// Pull the operation size (type) from the left arg
let argL_ty = dfg.value_type(argL);
return package_up_divrem_info(argL, argL_ty, simm64, isSigned, isRem);
}
}
None
}
@@ -473,25 +451,106 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
}
}
//----------------------------------------------------------------------
//
// General pattern-match helpers.
/// Find out if `value` actually resolves to a constant, and if so what its
/// value is.
fn get_const(value: Value, dfg: &DataFlowGraph) -> Option<i64> {
match dfg.value_def(value) {
ValueDef::Result(definingInst, resultNo) => {
let definingIData: &InstructionData = &dfg[definingInst];
if let InstructionData::UnaryImm { opcode, imm } = *definingIData {
if opcode == Opcode::Iconst && resultNo == 0 {
return Some(imm.into());
/// Apply basic simplifications.
///
/// This folds constants with arithmetic to form `_imm` instructions, and other
/// minor simplifications.
fn simplify(pos: &mut FuncCursor, inst: Inst) {
match pos.func.dfg[inst] {
InstructionData::Binary { opcode, args } => {
if let ValueDef::Result(iconst_inst, _) = pos.func.dfg.value_def(args[1]) {
if let InstructionData::UnaryImm {
opcode: Opcode::Iconst,
mut imm,
} = pos.func.dfg[iconst_inst]
{
let new_opcode = match opcode {
Opcode::Iadd => Opcode::IaddImm,
Opcode::Imul => Opcode::ImulImm,
Opcode::Sdiv => Opcode::SdivImm,
Opcode::Udiv => Opcode::UdivImm,
Opcode::Srem => Opcode::SremImm,
Opcode::Urem => Opcode::UremImm,
Opcode::Band => Opcode::BandImm,
Opcode::Bor => Opcode::BorImm,
Opcode::Bxor => Opcode::BxorImm,
Opcode::Rotl => Opcode::RotlImm,
Opcode::Rotr => Opcode::RotrImm,
Opcode::Ishl => Opcode::IshlImm,
Opcode::Ushr => Opcode::UshrImm,
Opcode::Sshr => Opcode::SshrImm,
Opcode::Isub => {
imm = imm.wrapping_neg();
Opcode::IaddImm
}
_ => return,
};
let ty = pos.func.dfg.ctrl_typevar(inst);
pos.func.dfg.replace(inst).BinaryImm(
new_opcode,
ty,
imm,
args[0],
);
}
} else if let ValueDef::Result(iconst_inst, _) = pos.func.dfg.value_def(args[0]) {
if let InstructionData::UnaryImm {
opcode: Opcode::Iconst,
mut imm,
} = pos.func.dfg[iconst_inst]
{
let new_opcode = match opcode {
Opcode::Isub => Opcode::IrsubImm,
_ => return,
};
let ty = pos.func.dfg.ctrl_typevar(inst);
pos.func.dfg.replace(inst).BinaryImm(
new_opcode,
ty,
imm,
args[0],
);
}
}
None
}
ValueDef::Param(_definingEbb, _paramNo) => None,
InstructionData::IntCompare { opcode, cond, args } => {
debug_assert_eq!(opcode, Opcode::Icmp);
if let ValueDef::Result(iconst_inst, _) = pos.func.dfg.value_def(args[1]) {
if let InstructionData::UnaryImm {
opcode: Opcode::Iconst,
imm,
} = pos.func.dfg[iconst_inst]
{
pos.func.dfg.replace(inst).icmp_imm(cond, args[0], imm);
}
}
}
InstructionData::CondTrap { .. } |
InstructionData::Branch { .. } |
InstructionData::Ternary { opcode: Opcode::Select, .. } => {
// Fold away a redundant `bint`.
let maybe = {
let args = pos.func.dfg.inst_args(inst);
if let ValueDef::Result(def_inst, _) = pos.func.dfg.value_def(args[0]) {
if let InstructionData::Unary {
opcode: Opcode::Bint,
arg: bool_val,
} = pos.func.dfg[def_inst]
{
Some(bool_val)
} else {
None
}
} else {
None
}
};
if let Some(bool_val) = maybe {
let args = pos.func.dfg.inst_args_mut(inst);
args[0] = bool_val;
}
}
_ => {}
}
}
@@ -503,6 +562,8 @@ pub fn do_preopt(func: &mut Function) {
while let Some(_ebb) = pos.next_ebb() {
while let Some(inst) = pos.next_inst() {
// Apply basic simplifications.
simplify(&mut pos, inst);
//-- BEGIN -- division by constants ----------------