]> git.lizzy.rs Git - rust.git/commitdiff
[MIR trans] Optimize trans for biased switches
authorBjörn Steinbrink <bsteinbr@gmail.com>
Wed, 11 May 2016 19:31:19 +0000 (21:31 +0200)
committerBjörn Steinbrink <bsteinbr@gmail.com>
Wed, 11 May 2016 20:18:46 +0000 (22:18 +0200)
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

src/librustc_trans/mir/block.rs

index e1318396e317d98aa4066eda7c19e5abec60c03f..4e3386bc736775440d22bb3f1fd6d015dbe9c392 100644 (file)
@@ -24,6 +24,7 @@
 use type_of;
 use glue;
 use type_::Type;
+use rustc_data_structures::fnv::FnvHashMap;
 
 use super::{MirContext, TempRef, drop};
 use super::constant::Const;
@@ -95,17 +96,32 @@ pub fn trans_block(&mut self, bb: mir::BasicBlock) {
                     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)
+                    }
                 }
             }