SwitchInt over Switch

This removes another special case of Switch by replacing it with the more general SwitchInt. While
this is more clunky currently, there’s no reason we can’t make it nice (and efficient) to use.
This commit is contained in:
Simonas Kazlauskas 2017-02-02 02:05:56 +02:00
parent 5d70a7fbe4
commit aac82d9b13
19 changed files with 105 additions and 130 deletions

View file

@ -14,6 +14,7 @@ use std::rc::Rc;
use hir::def_id::DefId;
use rustc_const_math::*;
use self::ConstVal::*;
pub use rustc_const_math::ConstInt;
use std::collections::BTreeMap;

View file

@ -453,13 +453,6 @@ pub enum TerminatorKind<'tcx> {
target: BasicBlock,
},
/// lvalue evaluates to some enum; jump depending on the branch
Switch {
discr: Lvalue<'tcx>,
adt_def: &'tcx AdtDef,
targets: Vec<BasicBlock>,
},
/// operand evaluates to an integer; jump depending on its value
/// to one of the targets, and otherwise fallback to `otherwise`
SwitchInt {
@ -471,6 +464,7 @@ pub enum TerminatorKind<'tcx> {
/// Possible values. The locations to branch to in each case
/// are found in the corresponding indices from the `targets` vector.
// FIXME: ConstVal doesnt quite make any sense here? Its a Switch*Int*.
values: Vec<ConstVal>,
/// Possible branch sites. The length of this vector should be
@ -544,7 +538,6 @@ impl<'tcx> TerminatorKind<'tcx> {
use self::TerminatorKind::*;
match *self {
Goto { target: ref b } => slice::ref_slice(b).into_cow(),
Switch { targets: ref b, .. } => b[..].into_cow(),
SwitchInt { targets: ref b, .. } => b[..].into_cow(),
Resume => (&[]).into_cow(),
Return => (&[]).into_cow(),
@ -573,7 +566,6 @@ impl<'tcx> TerminatorKind<'tcx> {
use self::TerminatorKind::*;
match *self {
Goto { target: ref mut b } => vec![b],
Switch { targets: ref mut b, .. } => b.iter_mut().collect(),
SwitchInt { targets: ref mut b, .. } => b.iter_mut().collect(),
Resume => Vec::new(),
Return => Vec::new(),
@ -651,7 +643,6 @@ impl<'tcx> TerminatorKind<'tcx> {
use self::TerminatorKind::*;
match *self {
Goto { .. } => write!(fmt, "goto"),
Switch { discr: ref lv, .. } => write!(fmt, "switch({:?})", lv),
SwitchInt { discr: ref lv, .. } => write!(fmt, "switchInt({:?})", lv),
Return => write!(fmt, "return"),
Resume => write!(fmt, "resume"),
@ -701,12 +692,6 @@ impl<'tcx> TerminatorKind<'tcx> {
match *self {
Return | Resume | Unreachable => vec![],
Goto { .. } => vec!["".into()],
Switch { ref adt_def, .. } => {
adt_def.variants
.iter()
.map(|variant| variant.name.to_string().into())
.collect()
}
SwitchInt { ref values, .. } => {
values.iter()
.map(|const_val| {

View file

@ -17,6 +17,7 @@ use mir::*;
use ty::subst::{Subst, Substs};
use ty::{self, AdtDef, Ty, TyCtxt};
use ty::fold::{TypeFoldable, TypeFolder, TypeVisitor};
use syntax::attr;
use hir;
#[derive(Copy, Clone, Debug)]
@ -170,9 +171,14 @@ impl<'tcx> Rvalue<'tcx> {
Some(operand.ty(mir, tcx))
}
Rvalue::Discriminant(ref lval) => {
if let ty::TyAdt(_, _) = lval.ty(mir, tcx).to_ty(tcx).sty {
// TODO
None
if let ty::TyAdt(adt_def, _) = lval.ty(mir, tcx).to_ty(tcx).sty {
// FIXME: Why this does not work?
// Some(adt_def.discr_ty.to_ty(tcx))
let ty = match adt_def.discr_ty {
attr::SignedInt(i) => tcx.mk_mach_int(i),
attr::UnsignedInt(i) => tcx.mk_mach_uint(i),
};
Some(ty)
} else {
None
}

View file

@ -362,15 +362,6 @@ macro_rules! make_mir_visitor {
self.visit_branch(block, target);
}
TerminatorKind::Switch { ref $($mutability)* discr,
adt_def: _,
ref targets } => {
self.visit_lvalue(discr, LvalueContext::Inspect, source_location);
for &target in targets {
self.visit_branch(block, target);
}
}
TerminatorKind::SwitchInt { ref $($mutability)* discr,
ref $($mutability)* switch_ty,
ref $($mutability)* values,

View file

@ -39,27 +39,17 @@ use rustc_i128::i128;
use hir;
pub trait IntTypeExt {
fn to_ty<'a, 'tcx>(&self, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> Ty<'tcx>;
fn to_ty<'a, 'tcx>(self, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> Ty<'tcx>;
fn disr_incr<'a, 'tcx>(&self, tcx: TyCtxt<'a, 'tcx, 'tcx>, val: Option<Disr>)
-> Option<Disr>;
fn initial_discriminant<'a, 'tcx>(&self, _: TyCtxt<'a, 'tcx, 'tcx>) -> Disr;
}
impl IntTypeExt for attr::IntType {
fn to_ty<'a, 'tcx>(&self, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> Ty<'tcx> {
match *self {
SignedInt(ast::IntTy::I8) => tcx.types.i8,
SignedInt(ast::IntTy::I16) => tcx.types.i16,
SignedInt(ast::IntTy::I32) => tcx.types.i32,
SignedInt(ast::IntTy::I64) => tcx.types.i64,
SignedInt(ast::IntTy::I128) => tcx.types.i128,
SignedInt(ast::IntTy::Is) => tcx.types.isize,
UnsignedInt(ast::UintTy::U8) => tcx.types.u8,
UnsignedInt(ast::UintTy::U16) => tcx.types.u16,
UnsignedInt(ast::UintTy::U32) => tcx.types.u32,
UnsignedInt(ast::UintTy::U64) => tcx.types.u64,
UnsignedInt(ast::UintTy::U128) => tcx.types.u128,
UnsignedInt(ast::UintTy::Us) => tcx.types.usize,
fn to_ty<'a, 'gcx, 'tcx>(self, tcx: TyCtxt<'a, 'gcx, 'tcx>) -> Ty<'tcx> {
match self {
SignedInt(i) => tcx.mk_mach_int(i),
UnsignedInt(i) => tcx.mk_mach_uint(i),
}
}

View file

@ -454,7 +454,6 @@ impl<'a, 'tcx: 'a, D> DataflowAnalysis<'a, 'tcx, D>
self.propagate_bits_into_entry_set_for(in_out, changed, target);
self.propagate_bits_into_entry_set_for(in_out, changed, unwind);
}
mir::TerminatorKind::Switch { ref targets, .. } |
mir::TerminatorKind::SwitchInt { ref targets, .. } => {
for target in targets {
self.propagate_bits_into_entry_set_for(in_out, changed, target);

View file

@ -17,9 +17,10 @@ use super::{DropFlagState, MoveDataParamEnv};
use super::patch::MirPatch;
use rustc::ty::{self, Ty, TyCtxt};
use rustc::ty::subst::{Kind, Subst, Substs};
use rustc::ty::util::IntTypeExt;
use rustc::mir::*;
use rustc::mir::transform::{Pass, MirPass, MirSource};
use rustc::middle::const_val::ConstVal;
use rustc::middle::const_val::{ConstVal, ConstInt};
use rustc::middle::lang_items;
use rustc::util::nodemap::FxHashMap;
use rustc_data_structures::indexed_set::IdxSetBuf;
@ -672,12 +673,15 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
self.drop_ladder(c, fields)
}
_ => {
let variant_drops : Vec<BasicBlock> =
(0..adt.variants.len()).map(|i| {
self.open_drop_for_variant(c, &mut drop_block,
adt, substs, i)
}).collect();
let mut values = Vec::with_capacity(adt.variants.len());
let mut blocks = Vec::with_capacity(adt.variants.len() + 1);
for (idx, variant) in adt.variants.iter().enumerate() {
let discr = ConstInt::new_inttype(variant.disr_val, adt.discr_ty,
self.tcx.sess.target.uint_type,
self.tcx.sess.target.int_type).unwrap();
values.push(ConstVal::Integral(discr));
blocks.push(self.open_drop_for_variant(c, &mut drop_block, adt, substs, idx));
}
// If there are multiple variants, then if something
// is present within the enum the discriminant, tracked
// by the rest path, must be initialized.
@ -685,14 +689,29 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
// Additionally, we do not want to switch on the
// discriminant after it is free-ed, because that
// way lies only trouble.
let switch_block = self.new_block(
c, c.is_cleanup, TerminatorKind::Switch {
discr: c.lvalue.clone(),
adt_def: adt,
targets: variant_drops
});
let discr_ty = adt.discr_ty.to_ty(self.tcx);
let discr = Lvalue::Local(self.patch.new_temp(discr_ty));
let switch_block = self.patch.new_block(BasicBlockData {
statements: vec![
Statement {
source_info: c.source_info,
kind: StatementKind::Assign(discr.clone(),
Rvalue::Discriminant(c.lvalue.clone()))
}
],
terminator: Some(Terminator {
source_info: c.source_info,
kind: TerminatorKind::SwitchInt {
discr: Operand::Consume(discr),
switch_ty: discr_ty,
values: values,
targets: blocks,
// adt_def: adt,
// targets: variant_drops
}
}),
is_cleanup: c.is_cleanup,
});
self.drop_flag_test_block(c, switch_block)
}
}

View file

@ -465,8 +465,7 @@ impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
}
TerminatorKind::Assert { .. } |
TerminatorKind::SwitchInt { .. } |
TerminatorKind::Switch { .. } => {
TerminatorKind::SwitchInt { .. } => {
// branching terminators - these don't move anything
}

View file

@ -77,6 +77,14 @@ mod ibounds {
}
impl ConstInt {
pub fn new_inttype(val: u128, ty: IntType, usize_ty: UintTy, isize_ty: IntTy)
-> Option<ConstInt> {
match ty {
IntType::SignedInt(i) => ConstInt::new_signed(val as i128, i, isize_ty),
IntType::UnsignedInt(i) => ConstInt::new_unsigned(val, i, usize_ty),
}
}
/// Creates a new unsigned ConstInt with matching type while also checking that overflow does
/// not happen.
pub fn new_unsigned(val: u128, ty: UintTy, usize_ty: UintTy) -> Option<ConstInt> {

View file

@ -30,6 +30,10 @@ impl BitVector {
}
}
pub fn count(&self) -> usize {
self.data.iter().map(|e| e.count_ones() as usize).sum()
}
#[inline]
pub fn contains(&self, bit: usize) -> bool {
let (word, mask) = word_mask(bit);

View file

@ -20,11 +20,12 @@ use build::matches::{Candidate, MatchPair, Test, TestKind};
use hair::*;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::bitvec::BitVector;
use rustc::middle::const_val::ConstVal;
use rustc::middle::const_val::{ConstVal, ConstInt};
use rustc::ty::{self, Ty};
use rustc::mir::*;
use rustc::hir::RangeEnd;
use syntax_pos::Span;
use syntax::attr;
use std::cmp::Ordering;
impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
@ -182,24 +183,51 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
let source_info = self.source_info(test.span);
match test.kind {
TestKind::Switch { adt_def, ref variants } => {
// Variants is a BitVec of indexes into adt_def.variants.
let num_enum_variants = self.hir.num_variants(adt_def);
let used_variants = variants.count();
let mut otherwise_block = None;
let target_blocks: Vec<_> = (0..num_enum_variants).map(|i| {
if variants.contains(i) {
self.cfg.start_new_block()
let mut target_blocks = Vec::with_capacity(num_enum_variants);
let mut targets = Vec::with_capacity(used_variants + 1);
let mut values = Vec::with_capacity(used_variants);
let tcx = self.hir.tcx();
for (idx, variant) in adt_def.variants.iter().enumerate() {
target_blocks.place_back() <- if variants.contains(idx) {
let discr = ConstInt::new_inttype(variant.disr_val, adt_def.discr_ty,
tcx.sess.target.uint_type,
tcx.sess.target.int_type).unwrap();
values.push(ConstVal::Integral(discr));
*(targets.place_back() <- self.cfg.start_new_block())
} else {
if otherwise_block.is_none() {
otherwise_block = Some(self.cfg.start_new_block());
}
otherwise_block.unwrap()
}
}).collect();
debug!("num_enum_variants: {}, num tested variants: {}, variants: {:?}",
num_enum_variants, variants.iter().count(), variants);
self.cfg.terminate(block, source_info, TerminatorKind::Switch {
discr: lvalue.clone(),
adt_def: adt_def,
targets: target_blocks.clone()
};
}
if let Some(otherwise_block) = otherwise_block {
targets.push(otherwise_block);
} else {
values.pop();
}
debug!("num_enum_variants: {}, tested variants: {:?}, variants: {:?}",
num_enum_variants, values, variants);
// FIXME: WHY THIS DOES NOT WORK?!
// let discr_ty = adt_def.discr_ty.to_ty(tcx);
let discr_ty = match adt_def.discr_ty {
attr::SignedInt(i) => tcx.mk_mach_int(i),
attr::UnsignedInt(i) => tcx.mk_mach_uint(i),
};
let discr = self.temp(discr_ty);
self.cfg.push_assign(block, source_info, &discr,
Rvalue::Discriminant(lvalue.clone()));
assert_eq!(values.len() + 1, targets.len());
self.cfg.terminate(block, source_info, TerminatorKind::SwitchInt {
discr: Operand::Consume(discr),
switch_ty: discr_ty,
values: values,
targets: targets
});
target_blocks
}

View file

@ -26,6 +26,8 @@ Rust MIR: a lowered representation of Rust. Also: an experiment!
#![feature(rustc_diagnostic_macros)]
#![feature(rustc_private)]
#![feature(staged_api)]
#![feature(placement_in_syntax)]
#![feature(collection_placement)]
#[macro_use] extern crate log;
extern crate graphviz as dot;

View file

@ -28,7 +28,6 @@ impl<'tcx> MutVisitor<'tcx> for NoLandingPads {
TerminatorKind::Resume |
TerminatorKind::Return |
TerminatorKind::Unreachable |
TerminatorKind::Switch { .. } |
TerminatorKind::SwitchInt { .. } => {
/* nothing to do */
},

View file

@ -394,7 +394,6 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
return Qualif::empty();
}
TerminatorKind::Switch {..} |
TerminatorKind::SwitchInt {..} |
TerminatorKind::DropAndReplace { .. } |
TerminatorKind::Resume |

View file

@ -209,7 +209,6 @@ impl<'a, 'tcx: 'a> CfgSimplifier<'a, 'tcx> {
// turn a branch with all successors identical to a goto
fn simplify_branch(&mut self, terminator: &mut Terminator<'tcx>) -> bool {
match terminator.kind {
TerminatorKind::Switch { .. } |
TerminatorKind::SwitchInt { .. } => {},
_ => return false
};

View file

@ -436,19 +436,6 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
}
// FIXME: check the values
}
TerminatorKind::Switch { ref discr, adt_def, ref targets } => {
let discr_ty = discr.ty(mir, tcx).to_ty(tcx);
match discr_ty.sty {
ty::TyAdt(def, _) if def.is_enum() &&
def == adt_def &&
adt_def.variants.len() == targets.len()
=> {},
_ => {
span_mirbug!(self, term, "bad Switch ({:?} on {:?})",
adt_def, discr_ty);
}
}
}
TerminatorKind::Call { ref func, ref args, ref destination, .. } => {
let func_ty = func.ty(mir, tcx);
debug!("check_terminator: call, func_ty={:?}", func_ty);
@ -593,7 +580,6 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
match block.terminator().kind {
TerminatorKind::Goto { target } =>
self.assert_iscleanup(mir, block, target, is_cleanup),
TerminatorKind::Switch { ref targets, .. } |
TerminatorKind::SwitchInt { ref targets, .. } => {
for target in targets {
self.assert_iscleanup(mir, block, *target, is_cleanup);

View file

@ -148,7 +148,6 @@ impl<'a, 'tcx> mir_visit::Visitor<'tcx> for StatCollector<'a, 'tcx> {
self.record("TerminatorKind", kind);
self.record(match *kind {
TerminatorKind::Goto { .. } => "TerminatorKind::Goto",
TerminatorKind::Switch { .. } => "TerminatorKind::Switch",
TerminatorKind::SwitchInt { .. } => "TerminatorKind::SwitchInt",
TerminatorKind::Resume => "TerminatorKind::Resume",
TerminatorKind::Return => "TerminatorKind::Return",

View file

@ -204,7 +204,6 @@ pub fn cleanup_kinds<'a, 'tcx>(mir: &mir::Mir<'tcx>) -> IndexVec<mir::BasicBlock
TerminatorKind::Resume |
TerminatorKind::Return |
TerminatorKind::Unreachable |
TerminatorKind::Switch { .. } |
TerminatorKind::SwitchInt { .. } => {
/* nothing to do */
}

View file

@ -14,14 +14,12 @@ use rustc::middle::lang_items;
use rustc::ty::{self, layout, TypeFoldable};
use rustc::mir;
use abi::{Abi, FnType, ArgType};
use adt;
use base::{self, Lifetime};
use callee::{Callee, CalleeData, Fn, Intrinsic, NamedTupleConstructor, Virtual};
use builder::Builder;
use common::{self, Funclet};
use common::{C_bool, C_str_slice, C_struct, C_u32, C_undef};
use consts;
use Disr;
use machine::{llalign_of_min, llbitsize_of_real};
use meth;
use type_of::{self, align_of};
@ -29,7 +27,6 @@ use glue;
use type_::Type;
use rustc_data_structures::indexed_vec::IndexVec;
use rustc_data_structures::fx::FxHashMap;
use syntax::symbol::Symbol;
use std::cmp;
@ -136,41 +133,6 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> {
funclet_br(self, bcx, target);
}
mir::TerminatorKind::Switch { ref discr, ref adt_def, ref targets } => {
let discr_lvalue = self.trans_lvalue(&bcx, discr);
let ty = discr_lvalue.ty.to_ty(bcx.tcx());
let discr = adt::trans_get_discr(
&bcx, ty, discr_lvalue.llval, discr_lvalue.alignment,
None, true);
let mut bb_hist = FxHashMap();
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), llblock(self, bb))
}
// 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, targets.len());
assert_eq!(adt_def.variants.len(), targets.len());
for (adt_variant, &target) in adt_def.variants.iter().zip(targets) {
if default_bb != Some(target) {
let llbb = llblock(self, target);
let llval = adt::trans_case(&bcx, ty, Disr::from(adt_variant.disr_val));
bcx.add_case(switch, llval, llbb)
}
}
}
mir::TerminatorKind::SwitchInt { ref discr, switch_ty, ref values, ref targets } => {
// TODO: cond_br if only 1 value
let (otherwise, targets) = targets.split_last().unwrap();