Generate MIR thats easier for llvm for str matches

LLVM appears to prefer (spend less time optimizing) long if chains if
it receives them in approzimately source order.
This fixes a ~10% regression for optimized builds of the encoding benchmark
on perf.rlo due to the changes to decision tree construction.
This commit is contained in:
Matthew Jasper 2019-05-25 17:11:27 +01:00
parent ef1fc86b52
commit a1d0266878
2 changed files with 82 additions and 68 deletions

View File

@ -1198,51 +1198,58 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
debug!("tested_candidates: {}", total_candidate_count - candidates.len()); debug!("tested_candidates: {}", total_candidate_count - candidates.len());
debug!("untested_candidates: {}", candidates.len()); debug!("untested_candidates: {}", candidates.len());
// For each outcome of test, process the candidates that still // HACK(matthewjasper) This is a closure so that we can let the test
// apply. Collect a list of blocks where control flow will // create its blocks before the rest of the match. This currently
// branch if one of the `target_candidate` sets is not // improves the speed of llvm when optimizing long string literal
// exhaustive. // matches
if !candidates.is_empty() { let make_target_blocks = move |this: &mut Self| -> Vec<BasicBlock> {
let remainder_start = &mut None; // For each outcome of test, process the candidates that still
self.match_candidates( // apply. Collect a list of blocks where control flow will
span, // branch if one of the `target_candidate` sets is not
remainder_start, // exhaustive.
otherwise_block, if !candidates.is_empty() {
candidates, let remainder_start = &mut None;
fake_borrows, this.match_candidates(
);
otherwise_block = Some(remainder_start.unwrap());
};
let target_blocks: Vec<_> = target_candidates.into_iter().map(|mut candidates| {
if candidates.len() != 0 {
let candidate_start = &mut None;
self.match_candidates(
span, span,
candidate_start, remainder_start,
otherwise_block, otherwise_block,
&mut *candidates, candidates,
fake_borrows, fake_borrows,
); );
candidate_start.unwrap() otherwise_block = Some(remainder_start.unwrap());
} else { };
*otherwise_block.get_or_insert_with(|| {
let unreachable = self.cfg.start_new_block(); target_candidates.into_iter().map(|mut candidates| {
let source_info = self.source_info(span); if candidates.len() != 0 {
self.cfg.terminate( let candidate_start = &mut None;
unreachable, this.match_candidates(
source_info, span,
TerminatorKind::Unreachable, candidate_start,
otherwise_block,
&mut *candidates,
fake_borrows,
); );
unreachable candidate_start.unwrap()
}) } else {
} *otherwise_block.get_or_insert_with(|| {
}).collect(); let unreachable = this.cfg.start_new_block();
let source_info = this.source_info(span);
this.cfg.terminate(
unreachable,
source_info,
TerminatorKind::Unreachable,
);
unreachable
})
}
}).collect()
};
self.perform_test( self.perform_test(
block, block,
&match_place, &match_place,
&test, &test,
target_blocks, make_target_blocks,
); );
} }

View File

@ -168,7 +168,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
block: BasicBlock, block: BasicBlock,
place: &Place<'tcx>, place: &Place<'tcx>,
test: &Test<'tcx>, test: &Test<'tcx>,
target_blocks: Vec<BasicBlock>, make_target_blocks: impl FnOnce(&mut Self) -> Vec<BasicBlock>,
) { ) {
debug!("perform_test({:?}, {:?}: {:?}, {:?})", debug!("perform_test({:?}, {:?}: {:?}, {:?})",
block, block,
@ -179,6 +179,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
let source_info = self.source_info(test.span); let source_info = self.source_info(test.span);
match test.kind { match test.kind {
TestKind::Switch { adt_def, ref variants } => { TestKind::Switch { adt_def, ref variants } => {
let target_blocks = make_target_blocks(self);
// Variants is a BitVec of indexes into adt_def.variants. // Variants is a BitVec of indexes into adt_def.variants.
let num_enum_variants = adt_def.variants.len(); let num_enum_variants = adt_def.variants.len();
let used_variants = variants.count(); let used_variants = variants.count();
@ -223,6 +224,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
} }
TestKind::SwitchInt { switch_ty, ref options, indices: _ } => { TestKind::SwitchInt { switch_ty, ref options, indices: _ } => {
let target_blocks = make_target_blocks(self);
let terminator = if switch_ty.sty == ty::Bool { let terminator = if switch_ty.sty == ty::Bool {
assert!(options.len() > 0 && options.len() <= 2); assert!(options.len() > 0 && options.len() <= 2);
if let [first_bb, second_bb] = *target_blocks { if let [first_bb, second_bb] = *target_blocks {
@ -254,38 +256,38 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
} }
TestKind::Eq { value, ty } => { TestKind::Eq { value, ty } => {
if let [success, fail] = *target_blocks { if !ty.is_scalar() {
if !ty.is_scalar() { // Use `PartialEq::eq` instead of `BinOp::Eq`
// Use `PartialEq::eq` instead of `BinOp::Eq` // (the binop can only handle primitives)
// (the binop can only handle primitives) self.non_scalar_compare(
self.non_scalar_compare( block,
block, make_target_blocks,
success, source_info,
fail, value,
source_info, place,
value, ty,
place, );
ty, } else {
); if let [success, fail] = *make_target_blocks(self) {
} else {
let val = Operand::Copy(place.clone()); let val = Operand::Copy(place.clone());
let expect = self.literal_operand(test.span, ty, value); let expect = self.literal_operand(test.span, ty, value);
self.compare(block, success, fail, source_info, BinOp::Eq, expect, val); self.compare(block, success, fail, source_info, BinOp::Eq, expect, val);
} else {
bug!("`TestKind::Eq` should have two target blocks");
} }
} else { }
bug!("`TestKind::Eq` should have two target blocks")
};
} }
TestKind::Range(PatternRange { ref lo, ref hi, ty, ref end }) => { TestKind::Range(PatternRange { ref lo, ref hi, ty, ref end }) => {
let lower_bound_success = self.cfg.start_new_block();
let target_blocks = make_target_blocks(self);
// Test `val` by computing `lo <= val && val <= hi`, using primitive comparisons. // Test `val` by computing `lo <= val && val <= hi`, using primitive comparisons.
let lo = self.literal_operand(test.span, ty, lo); let lo = self.literal_operand(test.span, ty, lo);
let hi = self.literal_operand(test.span, ty, hi); let hi = self.literal_operand(test.span, ty, hi);
let val = Operand::Copy(place.clone()); let val = Operand::Copy(place.clone());
if let [success, fail] = *target_blocks { if let [success, fail] = *target_blocks {
let lower_bound_success = self.cfg.start_new_block();
self.compare( self.compare(
block, block,
lower_bound_success, lower_bound_success,
@ -306,6 +308,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
} }
TestKind::Len { len, op } => { TestKind::Len { len, op } => {
let target_blocks = make_target_blocks(self);
let usize_ty = self.hir.usize_ty(); let usize_ty = self.hir.usize_ty();
let actual = self.temp(usize_ty, test.span); let actual = self.temp(usize_ty, test.span);
@ -374,8 +378,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
fn non_scalar_compare( fn non_scalar_compare(
&mut self, &mut self,
block: BasicBlock, block: BasicBlock,
success_block: BasicBlock, make_target_blocks: impl FnOnce(&mut Self) -> Vec<BasicBlock>,
fail_block: BasicBlock,
source_info: SourceInfo, source_info: SourceInfo,
value: &'tcx ty::Const<'tcx>, value: &'tcx ty::Const<'tcx>,
place: &Place<'tcx>, place: &Place<'tcx>,
@ -461,17 +464,21 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
from_hir_call: false, from_hir_call: false,
}); });
// check the result if let [success_block, fail_block] = *make_target_blocks(self) {
self.cfg.terminate( // check the result
eq_block, self.cfg.terminate(
source_info, eq_block,
TerminatorKind::if_( source_info,
self.hir.tcx(), TerminatorKind::if_(
Operand::Move(eq_result), self.hir.tcx(),
success_block, Operand::Move(eq_result),
fail_block, success_block,
), fail_block,
); ),
);
} else {
bug!("`TestKind::Eq` should have two target blocks")
}
} }
/// Given that we are performing `test` against `test_place`, this job /// Given that we are performing `test` against `test_place`, this job