diff --git a/src/librustc_mir/transform/promote_consts.rs b/src/librustc_mir/transform/promote_consts.rs index 3090b63a7e993..324bc043eb9c8 100644 --- a/src/librustc_mir/transform/promote_consts.rs +++ b/src/librustc_mir/transform/promote_consts.rs @@ -12,13 +12,21 @@ //! initialization and can otherwise silence errors, if //! move analysis runs after promotion on broken MIR. +use rustc::hir; +use rustc::hir::def_id::DefId; use rustc::mir::*; +use rustc::mir::interpret::ConstValue; use rustc::mir::visit::{PlaceContext, MutatingUseContext, MutVisitor, Visitor}; use rustc::mir::traversal::ReversePostorder; -use rustc::ty::TyCtxt; -use syntax_pos::Span; +use rustc::ty::{self, TyCtxt}; +use rustc::ty::cast::CastTy; +use syntax::ast::LitKind; +use syntax::symbol::sym; +use syntax_pos::{Span, DUMMY_SP}; +use rustc_data_structures::bit_set::BitSet; use rustc_data_structures::indexed_vec::{IndexVec, Idx}; +use rustc_target::spec::abi::Abi; use std::{iter, mem, usize}; @@ -55,7 +63,7 @@ impl TempState { /// A "root candidate" for promotion, which will become the /// returned value in a promoted MIR, unless it's a subset /// of a larger candidate. -#[derive(Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum Candidate { /// Borrow of a constant temporary. Ref(Location), @@ -71,13 +79,28 @@ pub enum Candidate { Argument { bb: BasicBlock, index: usize }, } -struct TempCollector<'tcx> { +fn args_required_const(tcx: TyCtxt<'_>, def_id: DefId) -> Option> { + let attrs = tcx.get_attrs(def_id); + let attr = attrs.iter().find(|a| a.check_name(sym::rustc_args_required_const))?; + let mut ret = vec![]; + for meta in attr.meta_item_list()? { + match meta.literal()?.node { + LitKind::Int(a, _) => { ret.push(a as usize); } + _ => return None, + } + } + Some(ret) +} + +struct Collector<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + body: &'a Body<'tcx>, temps: IndexVec, + candidates: Vec, span: Span, - body: &'tcx Body<'tcx>, } -impl<'tcx> Visitor<'tcx> for TempCollector<'tcx> { +impl<'tcx> Visitor<'tcx> for Collector<'_, 'tcx> { fn visit_local(&mut self, &index: &Local, context: PlaceContext, @@ -132,22 +155,603 @@ impl<'tcx> Visitor<'tcx> for TempCollector<'tcx> { *temp = TempState::Unpromotable; } + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + self.super_rvalue(rvalue, location); + + match *rvalue { + Rvalue::Ref(..) => { + self.candidates.push(Candidate::Ref(location)); + } + Rvalue::Repeat(..) if self.tcx.features().const_in_array_repeat_expressions => { + // FIXME(#49147) only promote the element when it isn't `Copy` + // (so that code that can copy it at runtime is unaffected). + self.candidates.push(Candidate::Repeat(location)); + } + _ => {} + } + } + + fn visit_terminator_kind(&mut self, + kind: &TerminatorKind<'tcx>, + location: Location) { + self.super_terminator_kind(kind, location); + + if let TerminatorKind::Call { ref func, .. } = *kind { + if let ty::FnDef(def_id, _) = func.ty(self.body, self.tcx).sty { + let fn_sig = self.tcx.fn_sig(def_id); + if let Abi::RustIntrinsic | Abi::PlatformIntrinsic = fn_sig.abi() { + let name = self.tcx.item_name(def_id); + // FIXME(eddyb) use `#[rustc_args_required_const(2)]` for shuffles. + if name.as_str().starts_with("simd_shuffle") { + self.candidates.push(Candidate::Argument { + bb: location.block, + index: 2, + }); + } + } + + if let Some(constant_args) = args_required_const(self.tcx, def_id) { + for index in constant_args { + self.candidates.push(Candidate::Argument { bb: location.block, index }); + } + } + } + } + } + fn visit_source_info(&mut self, source_info: &SourceInfo) { self.span = source_info.span; } } -pub fn collect_temps(body: &Body<'_>, - rpo: &mut ReversePostorder<'_, '_>) -> IndexVec { - let mut collector = TempCollector { +pub fn collect_temps_and_candidates( + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + rpo: &mut ReversePostorder<'_, 'tcx>, +) -> (IndexVec, Vec) { + let mut collector = Collector { + tcx, + body, temps: IndexVec::from_elem(TempState::Undefined, &body.local_decls), + candidates: vec![], span: body.span, - body, }; for (bb, data) in rpo { collector.visit_basic_block_data(bb, data); } - collector.temps + (collector.temps, collector.candidates) +} + +struct Validator<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + body: &'a Body<'tcx>, + is_static: bool, + is_static_mut: bool, + is_non_const_fn: bool, + temps: &'a IndexVec, + has_mut_interior: &'a BitSet, + needs_drop: &'a BitSet, + + /// Explicit promotion happens e.g. for constant arguments declared via + /// `rustc_args_required_const`. + /// Implicit promotion has almost the same rules, except that disallows `const fn` + /// except for those marked `#[rustc_promotable]`. This is to avoid changing + /// a legitimate run-time operation into a failing compile-time operation + /// e.g. due to addresses being compared inside the function. + explicit: bool, +} + +struct Unpromotable; + +impl<'tcx> Validator<'_, 'tcx> { + fn is_const_panic_fn(&self, def_id: DefId) -> bool { + Some(def_id) == self.tcx.lang_items().panic_fn() || + Some(def_id) == self.tcx.lang_items().begin_panic_fn() + } + + fn validate_candidate(&self, candidate: Candidate) -> Result<(), Unpromotable> { + match candidate { + Candidate::Ref(loc) => { + assert!(!self.explicit); + + let statement = &self.body[loc.block].statements[loc.statement_index]; + match &statement.kind { + StatementKind::Assign(_, box Rvalue::Ref(_, kind, place)) => { + match kind { + BorrowKind::Shared | BorrowKind::Mut { .. } => {} + + // FIXME(eddyb) these aren't promoted here but *could* + // be promoted as part of a larger value because + // `validate_rvalue` doesn't check them, need to + // figure out what is the intended behavior. + BorrowKind::Shallow | BorrowKind::Unique => return Err(Unpromotable), + } + + // We can only promote interior borrows of promotable temps (non-temps + // don't get promoted anyway). + let base = match place.base { + PlaceBase::Local(local) => local, + _ => return Err(Unpromotable), + }; + // HACK(eddyb) this should compute the same thing as + // `::in_projection` from + // `qualify_consts` but without recursion. + let mut has_mut_interior = self.has_mut_interior.contains(base); + { + // This allows borrowing fields which don't have + // `HasMutInterior`, from a type that does, e.g.: + // `let _: &'static _ = &(Cell::new(1), 2).1;` + let mut place_projection = &place.projection; + while let Some(proj) = place_projection { + // FIXME(eddyb) maybe this should have its own loop? + if let ProjectionElem::Deref = proj.elem { + return Err(Unpromotable); + } + + // FIXME(eddyb) this is probably excessive, with + // the exception of `union` member accesses. + if has_mut_interior { + let ty = + Place::ty_from(&place.base, &proj.base, self.body, self.tcx) + .projection_ty(self.tcx, &proj.elem) + .ty; + if ty.is_freeze(self.tcx, self.param_env, DUMMY_SP) { + has_mut_interior = false; + } + } + + place_projection = &proj.base; + } + } + + // FIXME(eddyb) this duplicates part of `validate_rvalue`. + if has_mut_interior { + return Err(Unpromotable); + } + if self.needs_drop.contains(base) { + return Err(Unpromotable); + } + if let BorrowKind::Mut { .. } = kind { + let ty = place.ty(self.body, self.tcx).ty; + + // In theory, any zero-sized value could be borrowed + // mutably without consequences. However, only &mut [] + // is allowed right now, and only in functions. + if self.is_static_mut { + // Inside a `static mut`, &mut [...] is also allowed. + match ty.sty { + ty::Array(..) | ty::Slice(_) => {} + _ => return Err(Unpromotable), + } + } else if let ty::Array(_, len) = ty.sty { + // FIXME(eddyb) the `self.is_non_const_fn` condition + // seems unnecessary, given that this is merely a ZST. + match len.try_eval_usize(self.tcx, self.param_env) { + Some(0) if self.is_non_const_fn => {}, + _ => return Err(Unpromotable), + } + } else { + return Err(Unpromotable); + } + } + + self.validate_local(base) + } + _ => bug!() + } + } + Candidate::Repeat(loc) => { + assert!(!self.explicit); + + let statement = &self.body[loc.block].statements[loc.statement_index]; + match &statement.kind { + StatementKind::Assign(_, box Rvalue::Repeat(ref operand, _)) => { + if !self.tcx.features().const_in_array_repeat_expressions { + return Err(Unpromotable); + } + + self.validate_operand(operand) + } + _ => bug!() + } + }, + Candidate::Argument { bb, index } => { + assert!(self.explicit); + + let terminator = self.body[bb].terminator(); + match &terminator.kind { + TerminatorKind::Call { args, .. } => { + self.validate_operand(&args[index]) + } + _ => bug!() + } + } + } + } + + fn validate_local(&self, local: Local) -> Result<(), Unpromotable> { + if let TempState::Defined { location: loc, .. } = self.temps[local] { + let num_stmts = self.body[loc.block].statements.len(); + + if loc.statement_index < num_stmts { + let statement = &self.body[loc.block].statements[loc.statement_index]; + match &statement.kind { + StatementKind::Assign(_, rhs) => self.validate_rvalue(rhs), + _ => { + span_bug!(statement.source_info.span, "{:?} is not an assignment", + statement); + } + } + } else { + let terminator = self.body[loc.block].terminator(); + match &terminator.kind { + TerminatorKind::Call { func, args, .. } => self.validate_call(func, args), + kind => { + span_bug!(terminator.source_info.span, "{:?} not promotable", kind); + } + } + } + } else { + Err(Unpromotable) + } + } + + fn validate_place(&self, place: PlaceRef<'_, 'tcx>) -> Result<(), Unpromotable> { + match place { + PlaceRef { + base: PlaceBase::Local(local), + projection: None, + } => self.validate_local(*local), + PlaceRef { + base: PlaceBase::Static(box Static { + kind: StaticKind::Promoted(_), + .. + }), + projection: None, + } => bug!("qualifying already promoted MIR"), + PlaceRef { + base: PlaceBase::Static(static_), + projection: None, + } => match static_.kind { + StaticKind::Promoted(_) => unreachable!(), + StaticKind::Static(def_id) => { + // Only allow statics (not consts) to refer to other statics. + // FIXME(eddyb) does this matter at all for promotion? + let allowed = self.is_static || self.is_static_mut; + if !allowed { + return Err(Unpromotable); + } + + let is_thread_local = self.tcx.get_attrs(def_id).iter().any( + |attr| attr.check_name(sym::thread_local) + ); + if is_thread_local { + return Err(Unpromotable); + } + + Ok(()) + } + }, + PlaceRef { + base: _, + projection: Some(proj), + } => { + match proj.elem { + ProjectionElem::Deref | + ProjectionElem::Downcast(..) => return Err(Unpromotable), + + ProjectionElem::ConstantIndex {..} | + ProjectionElem::Subslice {..} => {} + + ProjectionElem::Index(local) => { + self.validate_local(local)?; + } + + ProjectionElem::Field(..) => { + if self.is_non_const_fn { + let base_ty = + Place::ty_from(place.base, &proj.base, self.body, self.tcx).ty; + if let Some(def) = base_ty.ty_adt_def() { + // No promotion of union field accesses. + if def.is_union() { + return Err(Unpromotable); + } + } + } + } + } + + self.validate_place(PlaceRef { + base: place.base, + projection: &proj.base, + }) + } + } + } + + fn validate_operand(&self, operand: &Operand<'tcx>) -> Result<(), Unpromotable> { + match operand { + Operand::Copy(place) | + Operand::Move(place) => self.validate_place(place.as_ref()), + + Operand::Constant(constant) => { + if let ConstValue::Unevaluated(def_id, _) = constant.literal.val { + if self.tcx.trait_of_item(def_id).is_some() { + // Don't peek inside trait associated constants. + } else { + // HACK(eddyb) ensure that errors propagate correctly. + let (bits, _) = self.tcx.at(constant.span).mir_const_qualif(def_id); + if bits == (1 << 2) { + self.tcx.sess.delay_span_bug( + constant.span, + "promote_consts: MIR had errors", + ); + return Err(Unpromotable); + } + } + } + + Ok(()) + } + } + } + + fn validate_rvalue(&self, rvalue: &Rvalue<'tcx>) -> Result<(), Unpromotable> { + match *rvalue { + Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) if self.is_non_const_fn => { + let operand_ty = operand.ty(self.body, self.tcx); + let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast"); + let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast"); + match (cast_in, cast_out) { + (CastTy::Ptr(_), CastTy::Int(_)) | + (CastTy::FnPtr, CastTy::Int(_)) => { + // in normal functions, mark such casts as not promotable + return Err(Unpromotable); + } + _ => {} + } + } + + Rvalue::BinaryOp(op, ref lhs, _) if self.is_non_const_fn => { + if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.body, self.tcx).sty { + assert!(op == BinOp::Eq || op == BinOp::Ne || + op == BinOp::Le || op == BinOp::Lt || + op == BinOp::Ge || op == BinOp::Gt || + op == BinOp::Offset); + + // raw pointer operations are not allowed inside promoteds + return Err(Unpromotable); + } + } + + Rvalue::NullaryOp(NullOp::Box, _) => return Err(Unpromotable), + + _ => {} + } + + match rvalue { + Rvalue::NullaryOp(..) => Ok(()), + + Rvalue::Discriminant(place) | + Rvalue::Len(place) => self.validate_place(place.as_ref()), + + Rvalue::Use(operand) | + Rvalue::Repeat(operand, _) | + Rvalue::UnaryOp(_, operand) | + Rvalue::Cast(_, operand, _) => self.validate_operand(operand), + + Rvalue::BinaryOp(_, lhs, rhs) | + Rvalue::CheckedBinaryOp(_, lhs, rhs) => { + self.validate_operand(lhs)?; + self.validate_operand(rhs) + } + + Rvalue::Ref(_, kind, place) => { + if let BorrowKind::Mut { .. } = kind { + let ty = place.ty(self.body, self.tcx).ty; + + // In theory, any zero-sized value could be borrowed + // mutably without consequences. However, only &mut [] + // is allowed right now, and only in functions. + if self.is_static_mut { + // Inside a `static mut`, &mut [...] is also allowed. + match ty.sty { + ty::Array(..) | ty::Slice(_) => {} + _ => return Err(Unpromotable), + } + } else if let ty::Array(_, len) = ty.sty { + // FIXME(eddyb) the `self.is_non_const_fn` condition + // seems unnecessary, given that this is merely a ZST. + match len.try_eval_usize(self.tcx, self.param_env) { + Some(0) if self.is_non_const_fn => {}, + _ => return Err(Unpromotable), + } + } else { + return Err(Unpromotable); + } + } + + // Special-case reborrows to be more like a copy of the reference. + let mut place = place.as_ref(); + if let Some(proj) = &place.projection { + if let ProjectionElem::Deref = proj.elem { + let base_ty = + Place::ty_from(&place.base, &proj.base, self.body, self.tcx).ty; + if let ty::Ref(..) = base_ty.sty { + place = PlaceRef { + base: &place.base, + projection: &proj.base, + }; + } + } + } + + // HACK(eddyb) this should compute the same thing as + // `::in_projection` from + // `qualify_consts` but without recursion. + let mut has_mut_interior = match place.base { + PlaceBase::Local(local) => self.has_mut_interior.contains(*local), + PlaceBase::Static(_) => false, + }; + { + let mut place_projection = place.projection; + while let Some(proj) = place_projection { + // FIXME(eddyb) this is probably excessive, with + // the exception of `union` member accesses. + if has_mut_interior { + let ty = Place::ty_from(place.base, &proj.base, self.body, self.tcx) + .projection_ty(self.tcx, &proj.elem) + .ty; + if ty.is_freeze(self.tcx, self.param_env, DUMMY_SP) { + has_mut_interior = false; + } + } + + place_projection = &proj.base; + } + } + if has_mut_interior { + return Err(Unpromotable); + } + + self.validate_place(place) + } + + Rvalue::Aggregate(_, ref operands) => { + for o in operands { + self.validate_operand(o)?; + } + + Ok(()) + } + } + } + + fn validate_call( + &self, + callee: &Operand<'tcx>, + args: &[Operand<'tcx>], + ) -> Result<(), Unpromotable> { + if !self.explicit && self.is_non_const_fn { + if let ty::FnDef(def_id, _) = callee.ty(self.body, self.tcx).sty { + // Never promote runtime `const fn` calls of + // functions without `#[rustc_promotable]`. + if !self.tcx.is_promotable_const_fn(def_id) { + return Err(Unpromotable); + } + } + } + + let fn_ty = callee.ty(self.body, self.tcx); + match fn_ty.sty { + ty::FnDef(def_id, _) => { + match self.tcx.fn_sig(def_id).abi() { + Abi::RustIntrinsic | + Abi::PlatformIntrinsic => { + assert!(!self.tcx.is_const_fn(def_id)); + match &self.tcx.item_name(def_id).as_str()[..] { + | "size_of" + | "min_align_of" + | "needs_drop" + | "type_id" + | "bswap" + | "bitreverse" + | "ctpop" + | "cttz" + | "cttz_nonzero" + | "ctlz" + | "ctlz_nonzero" + | "wrapping_add" + | "wrapping_sub" + | "wrapping_mul" + | "unchecked_shl" + | "unchecked_shr" + | "rotate_left" + | "rotate_right" + | "add_with_overflow" + | "sub_with_overflow" + | "mul_with_overflow" + | "saturating_add" + | "saturating_sub" + | "transmute" + => return Err(Unpromotable), + + _ => {} + } + } + _ => { + let is_const_fn = + self.tcx.is_const_fn(def_id) || + self.tcx.is_unstable_const_fn(def_id).is_some() || + self.is_const_panic_fn(def_id); + if !is_const_fn { + return Err(Unpromotable); + } + } + } + } + _ => return Err(Unpromotable), + } + + self.validate_operand(callee)?; + for arg in args { + self.validate_operand(arg)?; + } + + Ok(()) + } +} + +pub fn validate_candidates( + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + def_id: DefId, + temps: &IndexVec, + has_mut_interior: &BitSet, + needs_drop: &BitSet, + candidates: &[Candidate], +) -> Vec { + let mut validator = Validator { + tcx, + param_env: tcx.param_env(def_id), + body, + is_static: false, + is_static_mut: false, + is_non_const_fn: false, + temps, + has_mut_interior, + needs_drop, + + explicit: false, + }; + + // FIXME(eddyb) remove the distinctions that make this necessary. + let id = tcx.hir().as_local_hir_id(def_id).unwrap(); + match tcx.hir().body_owner_kind(id) { + hir::BodyOwnerKind::Closure => validator.is_non_const_fn = true, + hir::BodyOwnerKind::Fn => { + if !tcx.is_const_fn(def_id) { + validator.is_non_const_fn = true; + } + }, + hir::BodyOwnerKind::Static(hir::MutImmutable) => validator.is_static = true, + hir::BodyOwnerKind::Static(hir::MutMutable) => validator.is_static_mut = true, + _ => {} + } + + candidates.iter().copied().filter(|&candidate| { + validator.explicit = match candidate { + Candidate::Ref(_) | + Candidate::Repeat(_) => false, + Candidate::Argument { .. } => true, + }; + + // FIXME(eddyb) also emit the errors for shuffle indices + // and `#[rustc_args_required_const]` arguments here. + + validator.validate_candidate(candidate).is_ok() + }).collect() } struct Promoter<'a, 'tcx> { @@ -209,17 +813,17 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { self.temps[temp] = TempState::PromotedOut; } - let no_stmts = self.source[loc.block].statements.len(); + let num_stmts = self.source[loc.block].statements.len(); let new_temp = self.promoted.local_decls.push( LocalDecl::new_temp(self.source.local_decls[temp].ty, self.source.local_decls[temp].source_info.span)); debug!("promote({:?} @ {:?}/{:?}, {:?})", - temp, loc, no_stmts, self.keep_original); + temp, loc, num_stmts, self.keep_original); // First, take the Rvalue or Call out of the source MIR, // or duplicate it, depending on keep_original. - if loc.statement_index < no_stmts { + if loc.statement_index < num_stmts { let (rvalue, source_info) = { let statement = &mut self.source[loc.block].statements[loc.statement_index]; let rhs = match statement.kind { diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 649cccc36c346..257eca71fed35 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -203,6 +203,9 @@ trait Qualif { ProjectionElem::ConstantIndex { .. } | ProjectionElem::Downcast(..) => qualif, + // FIXME(eddyb) shouldn't this be masked *after* including the + // index local? Then again, it's `usize` which is neither + // `HasMutInterior` nor `NeedsDrop`. ProjectionElem::Index(local) => qualif || Self::in_local(cx, local), } } @@ -436,6 +439,7 @@ impl Qualif for IsNotPromotable { StaticKind::Promoted(_) => unreachable!(), StaticKind::Static(def_id) => { // Only allow statics (not consts) to refer to other statics. + // FIXME(eddyb) does this matter at all for promotion? let allowed = cx.mode == Mode::Static || cx.mode == Mode::StaticMut; !allowed || @@ -664,6 +668,7 @@ struct Checker<'a, 'tcx> { temp_promotion_state: IndexVec, promotion_candidates: Vec, + unchecked_promotion_candidates: Vec, } macro_rules! unleash_miri { @@ -687,7 +692,8 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { fn new(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>, mode: Mode) -> Self { assert!(def_id.is_local()); let mut rpo = traversal::reverse_postorder(body); - let temps = promote_consts::collect_temps(body, &mut rpo); + let (temps, unchecked_promotion_candidates) = + promote_consts::collect_temps_and_candidates(tcx, body, &mut rpo); rpo.reset(); let param_env = tcx.param_env(def_id); @@ -725,7 +731,8 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { def_id, rpo, temp_promotion_state: temps, - promotion_candidates: vec![] + promotion_candidates: vec![], + unchecked_promotion_candidates, } } @@ -799,6 +806,10 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { } else if let BorrowKind::Mut { .. } | BorrowKind::Shared = kind { // Don't promote BorrowKind::Shallow borrows, as they don't // reach codegen. + // FIXME(eddyb) the two other kinds of borrow (`Shallow` and `Unique`) + // aren't promoted here but *could* be promoted as part of a larger + // value because `IsNotPromotable` isn't being set for them, + // need to figure out what is the intended behavior. // We might have a candidate for promotion. let candidate = Candidate::Ref(location); @@ -927,6 +938,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { let mut seen_blocks = BitSet::new_empty(body.basic_blocks().len()); let mut bb = START_BLOCK; + let mut has_controlflow_error = false; loop { seen_blocks.insert(bb.index()); @@ -966,6 +978,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { bb = target; } _ => { + has_controlflow_error = true; self.not_const(); break; } @@ -976,9 +989,17 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { // Collect all the temps we need to promote. let mut promoted_temps = BitSet::new_empty(self.temp_promotion_state.len()); - debug!("qualify_const: promotion_candidates={:?}", self.promotion_candidates); - for candidate in &self.promotion_candidates { - match *candidate { + // HACK(eddyb) don't try to validate promotion candidates if any + // parts of the control-flow graph were skipped due to an error. + let promotion_candidates = if has_controlflow_error { + self.tcx.sess.delay_span_bug(body.span, "check_const: expected control-flow error(s)"); + self.promotion_candidates.clone() + } else { + self.valid_promotion_candidates() + }; + debug!("qualify_const: promotion_candidates={:?}", promotion_candidates); + for candidate in promotion_candidates { + match candidate { Candidate::Repeat(Location { block: bb, statement_index: stmt_idx }) => { if let StatementKind::Assign(_, box Rvalue::Repeat( Operand::Move(Place { @@ -1015,6 +1036,51 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { (qualifs.encode_to_bits(), self.tcx.arena.alloc(promoted_temps)) } + + /// Get the subset of `unchecked_promotion_candidates` that are eligible + /// for promotion. + // FIXME(eddyb) replace the old candidate gathering with this. + fn valid_promotion_candidates(&self) -> Vec { + // Sanity-check the promotion candidates. + let candidates = promote_consts::validate_candidates( + self.tcx, + self.body, + self.def_id, + &self.temp_promotion_state, + &self.per_local.0[HasMutInterior::IDX], + &self.per_local.0[NeedsDrop::IDX], + &self.unchecked_promotion_candidates, + ); + + if candidates != self.promotion_candidates { + let report = |msg, candidate| { + let span = match candidate { + Candidate::Ref(loc) | + Candidate::Repeat(loc) => self.body.source_info(loc).span, + Candidate::Argument { bb, .. } => { + self.body[bb].terminator().source_info.span + } + }; + self.tcx.sess.span_err(span, &format!("{}: {:?}", msg, candidate)); + }; + + for &c in &self.promotion_candidates { + if !candidates.contains(&c) { + report("invalidated old candidate", c); + } + } + + for &c in &candidates { + if !self.promotion_candidates.contains(&c) { + report("extra new candidate", c); + } + } + + bug!("promotion candidate validation mismatches (see above)"); + } + + candidates + } } impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { @@ -1611,9 +1677,10 @@ impl MirPass for QualifyAndPromoteConstants { let (temps, candidates) = { let mut checker = Checker::new(tcx, def_id, body, mode); if mode == Mode::ConstFn { - if tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you { - checker.check_const(); - } else if tcx.is_min_const_fn(def_id) { + let use_min_const_fn_checks = + !tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you && + tcx.is_min_const_fn(def_id); + if use_min_const_fn_checks { // enforce `min_const_fn` for stable const fns use super::qualify_min_const_fn::is_min_const_fn; if let Err((span, err)) = is_min_const_fn(tcx, def_id, body) { @@ -1630,22 +1697,25 @@ impl MirPass for QualifyAndPromoteConstants { "add `#![feature(const_fn)]` to the crate attributes to enable", ); diag.emit(); - } else { - // this should not produce any errors, but better safe than sorry - // FIXME(#53819) - checker.check_const(); + return; } - } else { - // Enforce a constant-like CFG for `const fn`. - checker.check_const(); + + // `check_const` should not produce any errors, but better safe than sorry + // FIXME(#53819) + // NOTE(eddyb) `check_const` is actually needed for promotion inside + // `min_const_fn` functions. } + + // Enforce a constant-like CFG for `const fn`. + checker.check_const(); } else { while let Some((bb, data)) = checker.rpo.next() { checker.visit_basic_block_data(bb, data); } } - (checker.temp_promotion_state, checker.promotion_candidates) + let promotion_candidates = checker.valid_promotion_candidates(); + (checker.temp_promotion_state, promotion_candidates) }; // Do the actual promotion, now that we know what's viable.