Skip to content

Commit

Permalink
[MIR trans] Optimize trans for biased switches
Browse files Browse the repository at this point in the history
Currently, all switches in MIR are exhausitive, meaning that we can have
a lot of arms that all go to the same basic block, the extreme case
being an if-let expression which results in just 2 possible cases, be
might end up with hundreds of arms for large enums.

To improve this situation and give LLVM less code to chew on, we can
detect whether there's a pre-dominant target basic block in a switch
and then promote this to be the default target, not translating the
corresponding arms at all.

In combination with #33544 this makes unoptimized MIR trans of
nickel.rs as fast as using old trans and greatly improves the times for
optimized builds, which are only 30-40% slower instead of ~300%.

cc #33111
  • Loading branch information
dotdash committed May 11, 2016
1 parent c049541 commit 49b2cdf
Showing 1 changed file with 26 additions and 10 deletions.
36 changes: 26 additions & 10 deletions src/librustc_trans/mir/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use meth;
use type_of;
use glue;
use type_::Type;
use rustc_data_structures::fnv::FnvHashMap;

use super::{MirContext, TempRef, drop};
use super::constant::Const;
Expand Down Expand Up @@ -95,17 +96,32 @@ impl<'bcx, 'tcx> MirContext<'bcx, 'tcx> {
adt::trans_get_discr(bcx, &repr, discr_lvalue.llval, None, true)
);

// The else branch of the Switch can't be hit, so branch to an unreachable
// instruction so LLVM knows that
let unreachable_blk = self.unreachable_block();
let switch = bcx.switch(discr, unreachable_blk.llbb, targets.len());
let mut bb_hist = FnvHashMap();
for target in targets {
*bb_hist.entry(target).or_insert(0) += 1;
}
let (default_bb, default_blk) = match bb_hist.iter().max_by_key(|&(_, c)| c) {
// If a single target basic blocks is predominant, promote that to be the
// default case for the switch instruction to reduce the size of the generated
// code. This is especially helpful in cases like an if-let on a huge enum.
// Note: This optimization is only valid for exhaustive matches.
Some((&&bb, &c)) if c > targets.len() / 2 => {
(Some(bb), self.blocks[bb.index()])
}
// We're generating an exhaustive switch, so the else branch
// can't be hit. Branching to an unreachable instruction
// lets LLVM know this
_ => (None, self.unreachable_block())
};
let switch = bcx.switch(discr, default_blk.llbb, targets.len());
assert_eq!(adt_def.variants.len(), targets.len());
for (adt_variant, target) in adt_def.variants.iter().zip(targets) {
let llval = bcx.with_block(|bcx|
adt::trans_case(bcx, &repr, Disr::from(adt_variant.disr_val))
);
let llbb = self.llblock(*target);
build::AddCase(switch, llval, llbb)
for (adt_variant, &target) in adt_def.variants.iter().zip(targets) {
if default_bb != Some(target) {
let llbb = self.llblock(target);
let llval = bcx.with_block(|bcx| adt::trans_case(
bcx, &repr, Disr::from(adt_variant.disr_val)));
build::AddCase(switch, llval, llbb)
}
}
}

Expand Down

0 comments on commit 49b2cdf

Please sign in to comment.