diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs index ae9def8a5b595..ae65a32e60295 100644 --- a/src/librustc_mir/transform/mod.rs +++ b/src/librustc_mir/transform/mod.rs @@ -45,6 +45,7 @@ pub mod generator; pub mod inline; pub mod lower_128bit; pub mod uniform_array_move_out; +pub mod split_local_fields; pub(crate) fn provide(providers: &mut Providers) { self::qualify_consts::provide(providers); @@ -265,6 +266,7 @@ fn optimized_mir<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> &'tcx instcombine::InstCombine, deaggregator::Deaggregator, + split_local_fields::SplitLocalFields, copy_prop::CopyPropagation, remove_noop_landing_pads::RemoveNoopLandingPads, simplify::SimplifyCfg::new("final"), diff --git a/src/librustc_mir/transform/split_local_fields.rs b/src/librustc_mir/transform/split_local_fields.rs new file mode 100644 index 0000000000000..b0d596f81bdc4 --- /dev/null +++ b/src/librustc_mir/transform/split_local_fields.rs @@ -0,0 +1,429 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use rustc::hir; +use rustc::ty::{self, TyCtxt, Ty}; +use rustc::ty::util::IntTypeExt; +use rustc::middle::const_val::ConstVal; +use rustc::mir::*; +use rustc::mir::visit::{PlaceContext, MutVisitor, Visitor}; +use rustc::session::config::FullDebugInfo; +use rustc_const_math::ConstInt; +use rustc_data_structures::indexed_vec::{Idx, IndexVec}; +use std::collections::BTreeMap; +use syntax_pos::Span; +use transform::{MirPass, MirSource}; + +pub struct SplitLocalFields; + +impl MirPass for SplitLocalFields { + fn run_pass<'a, 'tcx>(&self, + tcx: TyCtxt<'a, 'tcx, 'tcx>, + source: MirSource, + mir: &mut Mir<'tcx>) { + // Don't run on constant MIR, because trans might not be able to + // evaluate the modified MIR. + // FIXME(eddyb) Remove check after miri is merged. + let id = tcx.hir.as_local_node_id(source.def_id).unwrap(); + match (tcx.hir.body_owner_kind(id), source.promoted) { + (_, Some(_)) | + (hir::BodyOwnerKind::Const, _) | + (hir::BodyOwnerKind::Static(_), _) => return, + + (hir::BodyOwnerKind::Fn, _) => { + if tcx.is_const_fn(source.def_id) { + // Don't run on const functions, as, again, trans might not be able to evaluate + // the optimized IR. + return + } + } + } + + let mut collector = LocalPathCollector { + locals: mir.local_decls.iter().map(|decl| { + LocalPath::new(decl.ty) + }).collect() + }; + + // Can't split return and arguments. + collector.locals[RETURN_PLACE].make_opaque(); + for arg in mir.args_iter() { + collector.locals[arg].make_opaque(); + } + + // We need to keep user variables intact for debuginfo. + if tcx.sess.opts.debuginfo == FullDebugInfo { + for local in mir.vars_iter() { + collector.locals[local].make_opaque(); + } + } + + collector.visit_mir(mir); + + let replacements = collector.locals.iter_enumerated_mut().map(|(local, root)| { + // Don't rename locals that are entirely opaque. + match root.interior { + LocalPathInterior::Opaque { .. } => local.index()..local.index()+1, + LocalPathInterior::Split { .. } => { + let orig_decl = mir.local_decls[local].clone(); + let first = mir.local_decls.len(); + root.split_into_locals(tcx, &mut mir.local_decls, &orig_decl); + first..mir.local_decls.len() + } + } + }).collect::>(); + + // Expand `Storage{Live,Dead}` statements to refer to the replacement locals. + for bb in mir.basic_blocks_mut() { + bb.expand_statements(|stmt| { + let (local, is_live) = match stmt.kind { + StatementKind::StorageLive(local) => (local, true), + StatementKind::StorageDead(local) => (local, false), + _ => return None + }; + let range = replacements[local].clone(); + let source_info = stmt.source_info; + Some(range.map(move |i| { + let new_local = Local::new(i); + Statement { + source_info, + kind: if is_live { + StatementKind::StorageLive(new_local) + } else { + StatementKind::StorageDead(new_local) + } + } + })) + }); + } + drop(replacements); + + // Lastly, replace all the opaque paths with their new locals. + let mut replacer = LocalPathReplacer { + tcx, + span: mir.span, + locals: collector.locals + }; + replacer.visit_mir(mir); + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +struct VariantField { + variant_index: usize, + field: u64 +} + +struct LocalPath<'tcx> { + ty: Ty<'tcx>, + interior: LocalPathInterior<'tcx> +} + +enum LocalPathInterior<'tcx> { + /// This path needs to remain self-contained, e.g. due to accesses / borrows. + Opaque { + replacement_local: Option + }, + + /// This path' can be split into separate locals for its fields. + Split { + discr_local: Option, + fields: BTreeMap> + } +} + +impl<'a, 'tcx> LocalPath<'tcx> { + fn new(ty: Ty<'tcx>) -> Self { + let mut path = LocalPath { + ty, + interior: LocalPathInterior::Split { + discr_local: None, + fields: BTreeMap::new() + } + }; + + if let ty::TyAdt(adt_def, _) = ty.sty { + // Unions have (observably) overlapping members, so don't split them. + if adt_def.is_union() { + path.make_opaque(); + } + } + + path + } + + fn make_opaque(&mut self) { + if let LocalPathInterior::Split { .. } = self.interior { + self.interior = LocalPathInterior::Opaque { + replacement_local: None + }; + } + } + + fn project(&mut self, elem: &PlaceElem<'tcx>, variant_index: usize) -> Option<&mut Self> { + match *elem { + ProjectionElem::Field(f, ty) => { + if let LocalPathInterior::Split { ref mut fields, .. } = self.interior { + let field = VariantField { + variant_index, + field: f.index() as u64 + }; + return Some(fields.entry(field).or_insert(LocalPath::new(ty))); + } + } + ProjectionElem::Downcast(..) => { + bug!("should be handled by the caller of `LocalPath::project`"); + } + // FIXME(eddyb) support indexing by constants. + ProjectionElem::ConstantIndex { .. } | + ProjectionElem::Subslice { .. } => {} + // Can't support without alias analysis. + ProjectionElem::Index(_) | + ProjectionElem::Deref => {} + } + + // If we can't project, we must be opaque. + self.make_opaque(); + None + } + + fn split_into_locals(&mut self, + tcx: TyCtxt<'a, 'tcx, 'tcx>, + local_decls: &mut IndexVec>, + base_decl: &LocalDecl<'tcx>) { + match self.interior { + LocalPathInterior::Opaque { ref mut replacement_local } => { + let mut decl = base_decl.clone(); + decl.ty = self.ty; + decl.name = None; + decl.is_user_variable = false; + *replacement_local = Some(local_decls.push(decl)); + } + LocalPathInterior::Split { + ref mut discr_local, + ref mut fields + } => { + if let ty::TyAdt(adt_def, _) = self.ty.sty { + if adt_def.is_enum() { + let discr_ty = adt_def.repr.discr_type().to_ty(tcx); + let mut decl = base_decl.clone(); + decl.ty = discr_ty; + decl.name = None; + decl.is_user_variable = false; + *discr_local = Some(local_decls.push(decl)); + } + } + for field in fields.values_mut() { + field.split_into_locals(tcx, local_decls, base_decl); + } + } + } + } +} + +struct LocalPathCollector<'tcx> { + locals: IndexVec> +} + +impl<'tcx> LocalPathCollector<'tcx> { + fn place_path(&mut self, place: &Place<'tcx>) -> Option<&mut LocalPath<'tcx>> { + match *place { + Place::Local(local) => Some(&mut self.locals[local]), + Place::Static(_) => None, + Place::Projection(box ref proj) => { + let (base, variant_index) = match proj.base { + Place::Projection(box Projection { + ref base, + elem: ProjectionElem::Downcast(_, variant_index) + }) => (base, variant_index), + ref base => (base, 0), + }; + + // Locals used as indices shouldn't be optimized. + if let ProjectionElem::Index(i) = proj.elem { + self.locals[i].make_opaque(); + } + + self.place_path(base)?.project(&proj.elem, variant_index) + } + } + } +} + +impl<'tcx> Visitor<'tcx> for LocalPathCollector<'tcx> { + fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _: Location) { + if context.is_use() { + if let Some(path) = self.place_path(place) { + path.make_opaque(); + } + } + } + + // Special-case `(Set)Discriminant(place)` to not mark `place` as opaque. + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + if let Rvalue::Discriminant(ref place) = *rvalue { + self.place_path(place); + return; + } + self.super_rvalue(rvalue, location); + } + + fn visit_statement(&mut self, + block: BasicBlock, + statement: &Statement<'tcx>, + location: Location) { + if let StatementKind::SetDiscriminant { ref place, .. } = statement.kind { + self.place_path(place); + return; + } + self.super_statement(block, statement, location); + } +} + +struct LocalPathReplacer<'a, 'tcx: 'a> { + tcx: TyCtxt<'a, 'tcx, 'tcx>, + span: Span, + locals: IndexVec> +} + +impl<'a, 'tcx> LocalPathReplacer<'a, 'tcx> { + fn replace(&mut self, place: &mut Place<'tcx>) -> Option<&mut LocalPath<'tcx>> { + let path = match *place { + Place::Local(ref mut local) => { + let path = &mut self.locals[*local]; + match path.interior { + LocalPathInterior::Opaque { replacement_local } => { + *local = replacement_local.unwrap_or(*local); + } + _ => {} + } + return Some(path); + } + Place::Static(_) => return None, + Place::Projection(box ref mut proj) => { + let (base, variant_index) = match proj.base { + Place::Projection(box Projection { + ref mut base, + elem: ProjectionElem::Downcast(_, variant_index) + }) => (base, variant_index), + ref mut base => (base, 0) + }; + self.replace(base)?.project(&proj.elem, variant_index)? + } + }; + match path.interior { + LocalPathInterior::Opaque { replacement_local } => { + *place = Place::Local(replacement_local.expect("missing replacement")); + } + _ => {} + } + Some(path) + } +} + +impl<'a, 'tcx> MutVisitor<'tcx> for LocalPathReplacer<'a, 'tcx> { + fn visit_place(&mut self, place: &mut Place<'tcx>, _: PlaceContext, _: Location) { + self.replace(place); + } + + // Special-case `(Set)Discriminant(place)` to use `discr_local` for `place`. + fn visit_rvalue(&mut self, rvalue: &mut Rvalue<'tcx>, location: Location) { + let tcx = self.tcx; + let span = self.span; + + let mut replacement = None; + if let Rvalue::Discriminant(ref mut place) = rvalue { + if let Some(path) = self.replace(place) { + if let LocalPathInterior::Split { discr_local, .. } = path.interior { + if let ty::TyAdt(adt_def, _) = path.ty.sty { + if adt_def.is_enum() { + let discr_place = + Place::Local(discr_local.expect("missing discriminant")); + replacement = Some(Rvalue::Use(Operand::Copy(discr_place))); + } + } + + // Non-enums don't have discriminants other than `0u8`. + if replacement.is_none() { + let discr = tcx.mk_const(ty::Const { + val: ConstVal::Integral(ConstInt::U8(0)), + ty: tcx.types.u8 + }); + replacement = Some(Rvalue::Use(Operand::Constant(box Constant { + span, + ty: discr.ty, + literal: Literal::Value { + value: discr + }, + }))); + } + } + } + } + // HACK(eddyb) clean this double matching post-NLL. + if let Rvalue::Discriminant(_) = rvalue { + if let Some(replacement) = replacement { + *rvalue = replacement; + } + return; + } + self.super_rvalue(rvalue, location); + } + + fn visit_statement(&mut self, + block: BasicBlock, + statement: &mut Statement<'tcx>, + location: Location) { + self.span = statement.source_info.span; + + let tcx = self.tcx; + let span = self.span; + + let mut replacement = None; + if let StatementKind::SetDiscriminant { ref mut place, variant_index } = statement.kind { + if let Some(path) = self.replace(place) { + if let LocalPathInterior::Split { discr_local, .. } = path.interior { + if let ty::TyAdt(adt_def, _) = path.ty.sty { + if adt_def.is_enum() { + let discr_place = + Place::Local(discr_local.expect("missing discriminant")); + let discr = adt_def.discriminant_for_variant(tcx, variant_index); + let discr = tcx.mk_const(ty::Const { + val: ConstVal::Integral(discr), + ty: adt_def.repr.discr_type().to_ty(tcx) + }); + let discr = Rvalue::Use(Operand::Constant(box Constant { + span, + ty: discr.ty, + literal: Literal::Value { + value: discr + }, + })); + replacement = Some(StatementKind::Assign(discr_place, discr)); + } + } + + // Non-enums don't have discriminants to set. + if replacement.is_none() { + replacement = Some(StatementKind::Nop); + } + } + } + } + // HACK(eddyb) clean this double matching post-NLL. + if let StatementKind::SetDiscriminant { .. } = statement.kind { + if let Some(replacement) = replacement { + statement.kind = replacement; + } + return; + } + self.super_statement(block, statement, location); + } +} diff --git a/src/librustc_trans/mir/analyze.rs b/src/librustc_trans/mir/analyze.rs index f683703ce6d53..43612f4fa8532 100644 --- a/src/librustc_trans/mir/analyze.rs +++ b/src/librustc_trans/mir/analyze.rs @@ -177,35 +177,42 @@ impl<'mir, 'a, 'tcx> Visitor<'tcx> for LocalAnalyzer<'mir, 'a, 'tcx> { } fn visit_local(&mut self, - &index: &mir::Local, + &local: &mir::Local, context: PlaceContext<'tcx>, _: Location) { match context { PlaceContext::Call => { - self.mark_assigned(index); + self.mark_assigned(local); } PlaceContext::StorageLive | PlaceContext::StorageDead | - PlaceContext::Validate | + PlaceContext::Validate => {} + PlaceContext::Copy | - PlaceContext::Move => {} + PlaceContext::Move => { + // Reads from uninitialized variables (e.g. in dead code, after + // optimizations) require locals to be in (uninitialized) memory. + if !self.seen_assigned.contains(local.index()) { + self.mark_as_memory(local); + } + } PlaceContext::Inspect | PlaceContext::Store | PlaceContext::AsmOutput | PlaceContext::Borrow { .. } | PlaceContext::Projection(..) => { - self.mark_as_memory(index); + self.mark_as_memory(local); } PlaceContext::Drop => { - let ty = mir::Place::Local(index).ty(self.fx.mir, self.fx.cx.tcx); + let ty = mir::Place::Local(local).ty(self.fx.mir, self.fx.cx.tcx); let ty = self.fx.monomorphize(&ty.to_ty(self.fx.cx.tcx)); // Only need the place if we're actually dropping it. if self.fx.cx.type_needs_drop(ty) { - self.mark_as_memory(index); + self.mark_as_memory(local); } } } diff --git a/src/test/compile-fail/huge-enum.rs b/src/test/compile-fail/huge-enum.rs index 6e7c05370b99d..410b797c0948e 100644 --- a/src/test/compile-fail/huge-enum.rs +++ b/src/test/compile-fail/huge-enum.rs @@ -15,9 +15,11 @@ #[cfg(target_pointer_width = "32")] fn main() { let big: Option<[u32; (1<<29)-1]> = None; + std::mem::drop(&big); // HACK(eddyb) avoid `big` being optimized away. } #[cfg(target_pointer_width = "64")] fn main() { let big: Option<[u32; (1<<45)-1]> = None; + std::mem::drop(&big); // HACK(eddyb) avoid `big` being optimized away. } diff --git a/src/test/compile-fail/huge-struct.rs b/src/test/compile-fail/huge-struct.rs index a10c61d6606d0..59c712211b0ce 100644 --- a/src/test/compile-fail/huge-struct.rs +++ b/src/test/compile-fail/huge-struct.rs @@ -50,5 +50,6 @@ struct S1k { val: S32> } struct S1M { val: S1k> } fn main() { - let fat: Option>>> = None; + let big: Option>>> = None; + std::mem::drop(&big); // HACK(eddyb) avoid `big` being optimized away. } diff --git a/src/test/run-pass/mir_raw_fat_ptr.rs b/src/test/run-pass/mir_raw_fat_ptr.rs index 846318ec4fd34..0daaf56be769f 100644 --- a/src/test/run-pass/mir_raw_fat_ptr.rs +++ b/src/test/run-pass/mir_raw_fat_ptr.rs @@ -156,6 +156,7 @@ fn main() { S(10, [11, 12, 13]), S(4, [5, 6]) ); + mem::drop(&ss); // HACK(eddyb) avoid optimizations splitting `ss.{0,1,2}`. assert_inorder(&[ &ss.0 as *const S<[u8]>, &ss.1 as *const S<[u8]>, diff --git a/src/test/run-pass/raw-fat-ptr.rs b/src/test/run-pass/raw-fat-ptr.rs index b4572f4577133..62f02395d64ba 100644 --- a/src/test/run-pass/raw-fat-ptr.rs +++ b/src/test/run-pass/raw-fat-ptr.rs @@ -119,6 +119,7 @@ fn main() { S(10, [11, 12, 13]), S(4, [5, 6]) ); + mem::drop(&ss); // HACK(eddyb) avoid optimizations splitting `ss.{0,1,2}`. assert_inorder(&[ &ss.0 as *const S<[u8]>, &ss.1 as *const S<[u8]>,