From f04424acd1bf894d1dc930c2a347871ea8b96dfa Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Wed, 6 Feb 2019 17:18:42 +0200 Subject: [PATCH] rustc_mir: compute all the qualification bits separately in qualify_consts. --- Cargo.lock | 1 - src/librustc_mir/Cargo.toml | 1 - src/librustc_mir/transform/qualify_consts.rs | 867 +++++++++++-------- 3 files changed, 504 insertions(+), 365 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a36df24359d..963bbcf0f04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2822,7 +2822,6 @@ name = "rustc_mir" version = "0.0.0" dependencies = [ "arena 0.0.0", - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "graphviz 0.0.0", diff --git a/src/librustc_mir/Cargo.toml b/src/librustc_mir/Cargo.toml index 44a6b41cdfe..c32bafa9920 100644 --- a/src/librustc_mir/Cargo.toml +++ b/src/librustc_mir/Cargo.toml @@ -11,7 +11,6 @@ crate-type = ["dylib"] [dependencies] arena = { path = "../libarena" } -bitflags = "1.0" either = "1.5.0" dot = { path = "../libgraphviz", package = "graphviz" } log = "0.4" diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 3a103e62297..51168f650ae 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -25,67 +25,12 @@ use syntax_pos::{Span, DUMMY_SP}; use std::fmt; -use std::ops::Deref; +use std::ops::{Deref, Index, IndexMut}; use std::usize; use crate::transform::{MirPass, MirSource}; use super::promote_consts::{self, Candidate, TempState}; -bitflags::bitflags! { - // Borrows of temporaries can be promoted only if - // they have none of these qualifications, with - // the exception of `STATIC_REF` (in statics only). - struct Qualif: u8 { - // Constant containing interior mutability (UnsafeCell). - const MUTABLE_INTERIOR = 1 << 0; - - // Constant containing an ADT that implements Drop. - const NEEDS_DROP = 1 << 1; - - // Not constant at all - non-`const fn` calls, asm!, - // pointer comparisons, ptr-to-int casts, etc. - const NOT_CONST = 1 << 2; - - // Refers to temporaries which cannot be promoted as - // promote_consts decided they weren't simple enough. - const NOT_PROMOTABLE = 1 << 3; - } -} - -impl<'a, 'tcx> Qualif { - /// Compute the qualifications for the given type. - fn any_value_of_ty( - ty: Ty<'tcx>, - tcx: TyCtxt<'a, 'tcx, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - ) -> Self { - let mut qualif = Self::empty(); - if !ty.is_freeze(tcx, param_env, DUMMY_SP) { - qualif = qualif | Qualif::MUTABLE_INTERIOR; - } - if ty.needs_drop(tcx, param_env) { - qualif = qualif | Qualif::NEEDS_DROP; - } - qualif - } - - /// Remove flags which are impossible for the given type. - fn restrict( - &mut self, - ty: Ty<'tcx>, - tcx: TyCtxt<'a, 'tcx, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - ) { - let ty_qualif = Self::any_value_of_ty(ty, tcx, param_env); - if !ty_qualif.contains(Qualif::MUTABLE_INTERIOR) { - *self = *self - Qualif::MUTABLE_INTERIOR; - } - if !ty_qualif.contains(Qualif::NEEDS_DROP) { - *self = *self - Qualif::NEEDS_DROP; - } - } -} - /// What kind of item we are in. #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum Mode { @@ -107,264 +52,404 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } } +const QUALIF_COUNT: usize = 4; + +// FIXME(eddyb) once we can use const generics, replace this array with +// something like `IndexVec` but for fixed-size arrays (`IndexArray`?). +#[derive(Copy, Clone, Default)] +struct PerQualif([T; QUALIF_COUNT]); + +impl PerQualif { + fn new(x: T) -> Self { + PerQualif([x.clone(), x.clone(), x.clone(), x]) + } +} + +impl PerQualif { + fn as_mut(&mut self) -> PerQualif<&mut T> { + let [x0, x1, x2, x3] = &mut self.0; + PerQualif([x0, x1, x2, x3]) + } + + fn zip(self, other: PerQualif) -> PerQualif<(T, U)> { + let [x0, x1, x2, x3] = self.0; + let [y0, y1, y2, y3] = other.0; + PerQualif([(x0, y0), (x1, y1), (x2, y2), (x3, y3)]) + } +} + +impl PerQualif { + fn encode_to_bits(self) -> u8 { + self.0.iter().enumerate().fold(0, |bits, (i, &qualif)| { + bits | ((qualif as u8) << i) + }) + } + + fn decode_from_bits(bits: u8) -> Self { + let mut qualifs = Self::default(); + for (i, qualif) in qualifs.0.iter_mut().enumerate() { + *qualif = (bits & (1 << i)) != 0; + } + qualifs + } +} + +impl Index for PerQualif { + type Output = T; + + fn index(&self, _: Q) -> &T { + &self.0[Q::IDX] + } +} + +impl IndexMut for PerQualif { + fn index_mut(&mut self, _: Q) -> &mut T { + &mut self.0[Q::IDX] + } +} + struct ConstCx<'a, 'tcx> { tcx: TyCtxt<'a, 'tcx, 'tcx>, param_env: ty::ParamEnv<'tcx>, mode: Mode, mir: &'a Mir<'tcx>, - local_qualif: IndexVec, + per_local: PerQualif>, } impl<'a, 'tcx> ConstCx<'a, 'tcx> { - fn qualify_any_value_of_ty(&self, ty: Ty<'tcx>) -> Qualif { - Qualif::any_value_of_ty(ty, self.tcx, self.param_env) + 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 qualify_local(&self, local: Local) -> Qualif { - self.local_qualif[local] +#[derive(Copy, Clone, Debug)] +enum ValueSource<'a, 'tcx> { + Rvalue(&'a Rvalue<'tcx>), + Call { + callee: &'a Operand<'tcx>, + args: &'a [Operand<'tcx>], + return_ty: Ty<'tcx>, + }, +} + +trait Qualif { + const IDX: usize; + + /// Return the qualification that is (conservatively) correct for any value + /// of the type, or `None` if the qualification is not value/type-based. + fn in_any_value_of_ty(_cx: &ConstCx<'_, 'tcx>, _ty: Ty<'tcx>) -> Option { + None + } + + /// Return a mask for the qualification, given a type. This is `false` iff + /// no value of that type can have the qualification. + fn mask_for_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool { + Self::in_any_value_of_ty(cx, ty).unwrap_or(true) } - fn qualify_projection_elem(&self, proj: &PlaceElem<'tcx>) -> Qualif { - match *proj { + fn in_local(cx: &ConstCx<'_, '_>, local: Local) -> bool { + cx.per_local.0[Self::IDX].contains(local) + } + + fn in_static(_cx: &ConstCx<'_, 'tcx>, _static: &Static<'tcx>) -> bool { + // FIXME(eddyb) should we do anything here for value properties? + false + } + + fn in_projection_structurally( + cx: &ConstCx<'_, 'tcx>, + proj: &PlaceProjection<'tcx>, + ) -> bool { + let base_qualif = Self::in_place(cx, &proj.base); + let qualif = base_qualif && Self::mask_for_ty( + cx, + proj.base.ty(cx.mir, cx.tcx) + .projection_ty(cx.tcx, &proj.elem) + .to_ty(cx.tcx), + ); + match proj.elem { ProjectionElem::Deref | ProjectionElem::Subslice { .. } | ProjectionElem::Field(..) | ProjectionElem::ConstantIndex { .. } | - ProjectionElem::Downcast(..) => Qualif::empty(), + ProjectionElem::Downcast(..) => qualif, - ProjectionElem::Index(local) => self.qualify_local(local), + ProjectionElem::Index(local) => qualif || Self::in_local(cx, local), } } - fn qualify_place(&self, place: &Place<'tcx>) -> Qualif { + fn in_projection(cx: &ConstCx<'_, 'tcx>, proj: &PlaceProjection<'tcx>) -> bool { + Self::in_projection_structurally(cx, proj) + } + + fn in_place(cx: &ConstCx<'_, 'tcx>, place: &Place<'tcx>) -> bool { match *place { - Place::Local(local) => self.qualify_local(local), + Place::Local(local) => Self::in_local(cx, local), Place::Promoted(_) => bug!("qualifying already promoted MIR"), - Place::Static(ref global) => { - if self.tcx - .get_attrs(global.def_id) - .iter() - .any(|attr| attr.check_name("thread_local")) { - return Qualif::NOT_CONST; - } - - // Only allow statics (not consts) to refer to other statics. - if self.mode == Mode::Static || self.mode == Mode::StaticMut { - Qualif::empty() - } else { - Qualif::NOT_CONST - } - } - Place::Projection(ref proj) => { - let mut qualif = - self.qualify_place(&proj.base) | - self.qualify_projection_elem(&proj.elem); - match proj.elem { - ProjectionElem::Deref | - ProjectionElem::Downcast(..) => qualif | Qualif::NOT_CONST, - - ProjectionElem::ConstantIndex {..} | - ProjectionElem::Subslice {..} | - ProjectionElem::Field(..) | - ProjectionElem::Index(_) => { - let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx); - if let Some(def) = base_ty.ty_adt_def() { - if def.is_union() { - match self.mode { - Mode::Fn => qualif = qualif | Qualif::NOT_CONST, - - Mode::ConstFn | - Mode::Static | - Mode::StaticMut | - Mode::Const => {} - } - } - } - - let ty = place.ty(self.mir, self.tcx).to_ty(self.tcx); - qualif.restrict(ty, self.tcx, self.param_env); - qualif - } - } - } + Place::Static(ref static_) => Self::in_static(cx, static_), + Place::Projection(ref proj) => Self::in_projection(cx, proj), } } - fn qualify_operand(&self, operand: &Operand<'tcx>) -> Qualif { + fn in_operand(cx: &ConstCx<'_, 'tcx>, operand: &Operand<'tcx>) -> bool { match *operand { Operand::Copy(ref place) | - Operand::Move(ref place) => self.qualify_place(place), + Operand::Move(ref place) => Self::in_place(cx, place), Operand::Constant(ref constant) => { if let ty::LazyConst::Unevaluated(def_id, _) = constant.literal { // Don't peek inside trait associated constants. - if self.tcx.trait_of_item(*def_id).is_some() { - self.qualify_any_value_of_ty(constant.ty) + if cx.tcx.trait_of_item(*def_id).is_some() { + Self::in_any_value_of_ty(cx, constant.ty).unwrap_or(false) } else { - let (bits, _) = self.tcx.at(constant.span).mir_const_qualif(*def_id); + let (bits, _) = cx.tcx.at(constant.span).mir_const_qualif(*def_id); - let mut qualif = Qualif::from_bits(bits).expect("invalid mir_const_qualif"); + let qualif = PerQualif::decode_from_bits(bits).0[Self::IDX]; // Just in case the type is more specific than // the definition, e.g., impl associated const // with type parameters, take it into account. - qualif.restrict(constant.ty, self.tcx, self.param_env); - qualif + qualif && Self::mask_for_ty(cx, constant.ty) } } else { - Qualif::empty() + false } } } } - fn qualify_rvalue(&self, rvalue: &Rvalue<'tcx>) -> Qualif { + fn in_rvalue_structurally(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool { match *rvalue { - Rvalue::NullaryOp(NullOp::SizeOf, _) => Qualif::empty(), + Rvalue::NullaryOp(..) => false, + + Rvalue::Discriminant(ref place) | + Rvalue::Len(ref place) => Self::in_place(cx, place), Rvalue::Use(ref operand) | Rvalue::Repeat(ref operand, _) | - Rvalue::UnaryOp(UnOp::Neg, ref operand) | - Rvalue::UnaryOp(UnOp::Not, ref operand) | - Rvalue::Cast(CastKind::ReifyFnPointer, ref operand, _) | - Rvalue::Cast(CastKind::UnsafeFnPointer, ref operand, _) | - Rvalue::Cast(CastKind::ClosureFnPointer, ref operand, _) | - Rvalue::Cast(CastKind::Unsize, ref operand, _) => { - self.qualify_operand(operand) - } + Rvalue::UnaryOp(_, ref operand) | + Rvalue::Cast(_, ref operand, _) => Self::in_operand(cx, operand), + Rvalue::BinaryOp(_, ref lhs, ref rhs) | Rvalue::CheckedBinaryOp(_, ref lhs, ref rhs) => { - self.qualify_operand(lhs) | self.qualify_operand(rhs) + Self::in_operand(cx, lhs) || Self::in_operand(cx, rhs) } - Rvalue::Discriminant(ref place) | - Rvalue::Len(ref place) => self.qualify_place(place), - - Rvalue::Ref(_, kind, ref place) => { - let mut reborrow_qualif = None; + Rvalue::Ref(_, _, ref place) => { + // Special-case reborrows to be more like a copy of the reference. if let Place::Projection(ref proj) = *place { if let ProjectionElem::Deref = proj.elem { - let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx); + let base_ty = proj.base.ty(cx.mir, cx.tcx).to_ty(cx.tcx); if let ty::Ref(..) = base_ty.sty { - reborrow_qualif = Some(self.qualify_place(&proj.base)); + return Self::in_place(cx, &proj.base); } } } - let mut qualif = reborrow_qualif.unwrap_or_else(|| { - self.qualify_place(place) - }); + Self::in_place(cx, place) + } - let ty = place.ty(self.mir, self.tcx).to_ty(self.tcx); + Rvalue::Aggregate(_, ref operands) => { + operands.iter().any(|o| Self::in_operand(cx, o)) + } + } + } + + fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool { + Self::in_rvalue_structurally(cx, rvalue) + } + + fn in_call( + cx: &ConstCx<'_, 'tcx>, + _callee: &Operand<'tcx>, + _args: &[Operand<'tcx>], + return_ty: Ty<'tcx>, + ) -> bool { + // Be conservative about the returned value of a const fn. + Self::in_any_value_of_ty(cx, return_ty).unwrap_or(false) + } + + fn in_value(cx: &ConstCx<'_, 'tcx>, source: ValueSource<'_, 'tcx>) -> bool { + match source { + ValueSource::Rvalue(rvalue) => Self::in_rvalue(cx, rvalue), + ValueSource::Call { callee, args, return_ty } => { + Self::in_call(cx, callee, args, return_ty) + } + } + } +} + +// Constant containing interior mutability (UnsafeCell). +struct HasMutInterior; + +impl Qualif for HasMutInterior { + const IDX: usize = 0; + + fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> Option { + Some(!ty.is_freeze(cx.tcx, cx.param_env, DUMMY_SP)) + } + + fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool { + match *rvalue { + // Returning `true` for `Rvalue::Ref` indicates the borrow isn't + // allowed in constants (and the `Checker` will error), and/or it + // won't be promoted, due to `&mut ...` or interior mutability. + Rvalue::Ref(_, kind, ref place) => { + let ty = place.ty(cx.mir, cx.tcx).to_ty(cx.tcx); if let BorrowKind::Mut { .. } = kind { // In theory, any zero-sized value could be borrowed // mutably without consequences. However, only &mut [] // is allowed right now, and only in functions. - let allowed = if self.mode == Mode::StaticMut { + if cx.mode == Mode::StaticMut { // Inside a `static mut`, &mut [...] is also allowed. match ty.sty { - ty::Array(..) | ty::Slice(_) => true, - _ => false + ty::Array(..) | ty::Slice(_) => {} + _ => return true, } } else if let ty::Array(_, len) = ty.sty { - // FIXME(eddyb) the `self.mode == Mode::Fn` condition + // FIXME(eddyb) the `cx.mode == Mode::Fn` condition // seems unnecessary, given that this is merely a ZST. - len.unwrap_usize(self.tcx) == 0 && self.mode == Mode::Fn + if !(len.unwrap_usize(cx.tcx) == 0 && cx.mode == Mode::Fn) { + return true; + } } else { - false - }; + return true; + } + } + } - if !allowed { - qualif = qualif | Qualif::MUTABLE_INTERIOR; + Rvalue::Aggregate(ref kind, _) => { + if let AggregateKind::Adt(def, ..) = **kind { + if Some(def.did) == cx.tcx.lang_items().unsafe_cell_type() { + let ty = rvalue.ty(cx.mir, cx.tcx); + assert_eq!(Self::in_any_value_of_ty(cx, ty), Some(true)); + return true; } } + } + + _ => {} + } + + Self::in_rvalue_structurally(cx, rvalue) + } +} + +// Constant containing an ADT that implements Drop. +struct NeedsDrop; + +impl Qualif for NeedsDrop { + const IDX: usize = 1; + + fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> Option { + Some(ty.needs_drop(cx.tcx, cx.param_env)) + } + + fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool { + if let Rvalue::Aggregate(ref kind, _) = *rvalue { + if let AggregateKind::Adt(def, ..) = **kind { + if def.has_dtor(cx.tcx) { + return true; + } + } + } - qualif + Self::in_rvalue_structurally(cx, rvalue) + } +} + +// Not constant at all - non-`const fn` calls, asm!, +// pointer comparisons, ptr-to-int casts, etc. +struct IsNotConst; + +impl Qualif for IsNotConst { + const IDX: usize = 2; + + fn in_static(cx: &ConstCx<'_, 'tcx>, static_: &Static<'tcx>) -> bool { + // Only allow statics (not consts) to refer to other statics. + let allowed = cx.mode == Mode::Static || cx.mode == Mode::StaticMut; + + !allowed || + cx.tcx.get_attrs(static_.def_id).iter().any(|attr| attr.check_name("thread_local")) + } + + fn in_projection(cx: &ConstCx<'_, 'tcx>, proj: &PlaceProjection<'tcx>) -> bool { + match proj.elem { + ProjectionElem::Deref | + ProjectionElem::Downcast(..) => return true, + + ProjectionElem::ConstantIndex {..} | + ProjectionElem::Subslice {..} | + ProjectionElem::Index(_) => {} + + ProjectionElem::Field(..) => { + if cx.mode == Mode::Fn { + let base_ty = proj.base.ty(cx.mir, cx.tcx).to_ty(cx.tcx); + if let Some(def) = base_ty.ty_adt_def() { + if def.is_union() { + return true; + } + } + } } + } - Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) => { - let mut qualif = self.qualify_operand(operand); + Self::in_projection_structurally(cx, proj) + } - let operand_ty = operand.ty(self.mir, self.tcx); + fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool { + match *rvalue { + Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) if cx.mode == Mode::Fn => { + let operand_ty = operand.ty(cx.mir, cx.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(_)) => { - if let Mode::Fn = self.mode { - // in normal functions, mark such casts as not promotable - qualif = qualif | Qualif::NOT_CONST; - } + // in normal functions, mark such casts as not promotable + return true; } _ => {} } - - qualif } - Rvalue::BinaryOp(op, ref lhs, ref rhs) => { - let mut qualif = self.qualify_operand(lhs) | self.qualify_operand(rhs); - - if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.mir, self.tcx).sty { + Rvalue::BinaryOp(op, ref lhs, _) if cx.mode == Mode::Fn => { + if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(cx.mir, cx.tcx).sty { assert!(op == BinOp::Eq || op == BinOp::Ne || op == BinOp::Le || op == BinOp::Lt || op == BinOp::Ge || op == BinOp::Gt || op == BinOp::Offset); - if let Mode::Fn = self.mode { - // raw pointer operations are not allowed inside promoteds - qualif = qualif | Qualif::NOT_CONST; - } + // raw pointer operations are not allowed inside promoteds + return true; } - - qualif } - Rvalue::NullaryOp(NullOp::Box, _) => Qualif::NOT_CONST, - - Rvalue::Aggregate(ref kind, ref operands) => { - let mut qualif = operands.iter().map(|o| self.qualify_operand(o)) - .fold(Qualif::empty(), |a, b| a | b); - - if let AggregateKind::Adt(def, ..) = **kind { - if Some(def.did) == self.tcx.lang_items().unsafe_cell_type() { - let ty = rvalue.ty(self.mir, self.tcx); - qualif = qualif | self.qualify_any_value_of_ty(ty); - assert!(qualif.contains(Qualif::MUTABLE_INTERIOR)); - } - - if def.has_dtor(self.tcx) { - qualif = qualif | Qualif::NEEDS_DROP; - } - } + Rvalue::NullaryOp(NullOp::Box, _) => return true, - qualif - } + _ => {} } - } - 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() + Self::in_rvalue_structurally(cx, rvalue) } - fn qualify_call( - &self, + fn in_call( + cx: &ConstCx<'_, 'tcx>, callee: &Operand<'tcx>, args: &[Operand<'tcx>], - return_ty: Ty<'tcx>, - ) -> Qualif { - let fn_ty = callee.ty(self.mir, self.tcx); - let mut is_promotable_const_fn = false; - let is_const_fn = match fn_ty.sty { + _return_ty: Ty<'tcx>, + ) -> bool { + let fn_ty = callee.ty(cx.mir, cx.tcx); + match fn_ty.sty { ty::FnDef(def_id, _) => { - match self.tcx.fn_sig(def_id).abi() { + match cx.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()[..] { + assert!(!cx.tcx.is_const_fn(def_id)); + match &cx.tcx.item_name(def_id).as_str()[..] { | "size_of" | "min_align_of" | "needs_drop" @@ -389,50 +474,100 @@ fn qualify_call( | "saturating_add" | "saturating_sub" | "transmute" - => true, + => return true, - _ => false, + _ => {} } } _ => { - // Never promote runtime `const fn` calls of - // functions without `#[rustc_promotable]`. - if self.tcx.is_promotable_const_fn(def_id) { - is_promotable_const_fn = true; - } - - if self.mode == Mode::Fn { - self.tcx.is_const_fn(def_id) - } else { - self.tcx.is_const_fn(def_id) || - self.is_const_panic_fn(def_id) || - self.tcx.is_unstable_const_fn(def_id).is_some() + let is_const_fn = + cx.tcx.is_const_fn(def_id) || + cx.tcx.is_unstable_const_fn(def_id).is_some() || + cx.is_const_panic_fn(def_id); + if !is_const_fn { + return true; } } } } - _ => false, - }; - - // Bail out on oon-`const fn` calls or if the callee had errors. - if !is_const_fn || self.qualify_operand(callee).intersects(Qualif::NOT_CONST) { - return Qualif::NOT_CONST; + _ => return true, } - // Bail out if any arguments had errors. - for arg in args { - if self.qualify_operand(arg).intersects(Qualif::NOT_CONST) { - return Qualif::NOT_CONST; + Self::in_operand(cx, callee) || args.iter().any(|arg| Self::in_operand(cx, arg)) + } +} + +// Refers to temporaries which cannot be promoted as +// promote_consts decided they weren't simple enough. +struct IsNotPromotable; + +impl Qualif for IsNotPromotable { + const IDX: usize = 3; + + fn in_call( + cx: &ConstCx<'_, 'tcx>, + callee: &Operand<'tcx>, + _args: &[Operand<'tcx>], + _return_ty: Ty<'tcx>, + ) -> bool { + if cx.mode == Mode::Fn { + if let ty::FnDef(def_id, _) = callee.ty(cx.mir, cx.tcx).sty { + // Never promote runtime `const fn` calls of + // functions without `#[rustc_promotable]`. + if !cx.tcx.is_promotable_const_fn(def_id) { + return true; + } } } - // Be conservative about the returned value of a const fn. - let qualif = self.qualify_any_value_of_ty(return_ty); - if !is_promotable_const_fn && self.mode == Mode::Fn { - qualif | Qualif::NOT_PROMOTABLE - } else { - qualif - } + // FIXME(eddyb) do we need "not promotable" in anything + // other than `Mode::Fn` by any chance? + + false + } +} + +// Ensure the `IDX` values are sequential (`0..QUALIF_COUNT`). +macro_rules! static_assert_seq_qualifs { + ($i:expr => $first:ident $(, $rest:ident)*) => { + static_assert!(SEQ_QUALIFS: { + static_assert_seq_qualifs!($i + 1 => $($rest),*); + + $first::IDX == $i + }); + }; + ($i:expr =>) => { + static_assert!(SEQ_QUALIFS: QUALIF_COUNT == $i); + }; +} +static_assert_seq_qualifs!(0 => HasMutInterior, NeedsDrop, IsNotConst, IsNotPromotable); + +impl ConstCx<'_, 'tcx> { + fn qualifs_in_any_value_of_ty(&self, ty: Ty<'tcx>) -> PerQualif { + let mut qualifs = PerQualif::default(); + qualifs[HasMutInterior] = HasMutInterior::in_any_value_of_ty(self, ty).unwrap_or(false); + qualifs[NeedsDrop] = NeedsDrop::in_any_value_of_ty(self, ty).unwrap_or(false); + qualifs[IsNotConst] = IsNotConst::in_any_value_of_ty(self, ty).unwrap_or(false); + qualifs[IsNotPromotable] = IsNotPromotable::in_any_value_of_ty(self, ty).unwrap_or(false); + qualifs + } + + fn qualifs_in_local(&self, local: Local) -> PerQualif { + let mut qualifs = PerQualif::default(); + qualifs[HasMutInterior] = HasMutInterior::in_local(self, local); + qualifs[NeedsDrop] = NeedsDrop::in_local(self, local); + qualifs[IsNotConst] = IsNotConst::in_local(self, local); + qualifs[IsNotPromotable] = IsNotPromotable::in_local(self, local); + qualifs + } + + fn qualifs_in_value(&self, source: ValueSource<'_, 'tcx>) -> PerQualif { + let mut qualifs = PerQualif::default(); + qualifs[HasMutInterior] = HasMutInterior::in_value(self, source); + qualifs[NeedsDrop] = NeedsDrop::in_value(self, source); + qualifs[IsNotConst] = IsNotConst::in_value(self, source); + qualifs[IsNotPromotable] = IsNotPromotable::in_value(self, source); + qualifs } } @@ -477,31 +612,40 @@ fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, let param_env = tcx.param_env(def_id); - let local_qualif = mir.local_decls.iter_enumerated().map(|(local, decl)| { + let mut cx = ConstCx { + tcx, + param_env, + mode, + mir, + per_local: PerQualif::new(BitSet::new_empty(mir.local_decls.len())), + }; + + for (local, decl) in mir.local_decls.iter_enumerated() { match mir.local_kind(local) { LocalKind::Arg => { - Qualif::any_value_of_ty(decl.ty, tcx, param_env) | - Qualif::NOT_PROMOTABLE + let qualifs = cx.qualifs_in_any_value_of_ty(decl.ty); + for (per_local, qualif) in &mut cx.per_local.as_mut().zip(qualifs).0 { + if *qualif { + per_local.insert(local); + } + } + cx.per_local[IsNotPromotable].insert(local); } - LocalKind::Var if mode == Mode::Fn => Qualif::NOT_CONST, + LocalKind::Var if mode == Mode::Fn => { + cx.per_local[IsNotConst].insert(local); + } LocalKind::Temp if !temps[local].is_promotable() => { - Qualif::NOT_PROMOTABLE + cx.per_local[IsNotPromotable].insert(local); } - _ => Qualif::empty(), + _ => {} } - }).collect(); + } Checker { - cx: ConstCx { - tcx, - param_env, - mode, - mir, - local_qualif, - }, + cx, span: mir.span, def_id, rpo, @@ -534,23 +678,76 @@ fn not_const(&mut self) { } /// Assigns an rvalue/call qualification to the given destination. - fn assign(&mut self, dest: &Place<'tcx>, qualif: Qualif, location: Location) { - trace!("assign: {:?} <- {:?}", dest, qualif); + fn assign(&mut self, dest: &Place<'tcx>, source: ValueSource<'_, 'tcx>, location: Location) { + trace!("assign: {:?} <- {:?}", dest, source); - // Only handle promotable temps in non-const functions. - if self.mode == Mode::Fn { - if let Place::Local(index) = *dest { - if self.mir.local_kind(index) == LocalKind::Temp - && self.temp_promotion_state[index].is_promotable() { - debug!("store to promotable temp {:?} ({:?})", index, qualif); - let slot = &mut self.cx.local_qualif[index]; - if !slot.is_empty() { - span_bug!(self.span, "multiple assignments to {:?}", dest); + let mut qualifs = self.qualifs_in_value(source); + + if let ValueSource::Rvalue(&Rvalue::Ref(_, kind, ref place)) = source { + // Getting `true` from `HasMutInterior::in_rvalue` means + // the borrowed place is disallowed from being borrowed, + // due to either a mutable borrow (with some exceptions), + // or an shared borrow of a value with interior mutability. + // Then `HasMutInterior` is replaced with `IsNotConst`, + // to avoid duplicate errors (e.g. from reborrowing). + if qualifs[HasMutInterior] { + qualifs[HasMutInterior] = false; + qualifs[IsNotConst] = true; + + if self.mode != Mode::Fn { + if let BorrowKind::Mut { .. } = kind { + let mut err = struct_span_err!(self.tcx.sess, self.span, E0017, + "references in {}s may only refer \ + to immutable values", self.mode); + err.span_label(self.span, format!("{}s require immutable values", + self.mode)); + if self.tcx.sess.teach(&err.get_code().unwrap()) { + err.note("References in statics and constants may only refer to \ + immutable values.\n\n\ + Statics are shared everywhere, and if they refer to \ + mutable data one might violate memory safety since \ + holding multiple mutable references to shared data is \ + not allowed.\n\n\ + If you really want global mutable state, try using \ + static mut or a global UnsafeCell."); + } + err.emit(); + } else { + span_err!(self.tcx.sess, self.span, E0492, + "cannot borrow a constant which may contain \ + interior mutability, create a static instead"); + } + } + } else { + // We might have a candidate for promotion. + let candidate = Candidate::Ref(location); + // We can only promote interior borrows of promotable temps. + let mut place = place; + while let Place::Projection(ref proj) = *place { + if proj.elem == ProjectionElem::Deref { + break; + } + place = &proj.base; + } + debug!("qualify_consts: promotion candidate: place={:?}", place); + if let Place::Local(local) = *place { + if self.mir.local_kind(local) == LocalKind::Temp { + debug!("qualify_consts: promotion candidate: local={:?}", local); + // The borrowed place doesn't have `HasMutInterior` + // (from `in_rvalue`), so we can safely ignore + // `HasMutInterior` from the local's qualifications. + // 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 local_qualifs = self.qualifs_in_local(local); + local_qualifs[HasMutInterior] = false; + if !local_qualifs.0.iter().any(|&qualif| qualif) { + debug!("qualify_consts: promotion candidate: {:?}", candidate); + self.promotion_candidates.push(candidate); + } } - *slot = qualif; } } - return; } let mut dest = dest; @@ -588,27 +785,39 @@ fn assign(&mut self, dest: &Place<'tcx>, qualif: Qualif, location: Location) { let kind = self.mir.local_kind(index); debug!("store to {:?} {:?}", kind, index); + // Only handle promotable temps in non-const functions. + if self.mode == Mode::Fn { + if kind != LocalKind::Temp || + !self.temp_promotion_state[index].is_promotable() { + return; + } + } + // this is overly restrictive, because even full assignments do not clear the qualif // While we could special case full assignments, this would be inconsistent with // aggregates where we overwrite all fields via assignments, which would not get // that feature. - let slot = &mut self.cx.local_qualif[index]; - *slot = *slot | qualif; + for (per_local, qualif) in &mut self.cx.per_local.as_mut().zip(qualifs).0 { + if *qualif { + per_local.insert(index); + } + } - // Ensure we keep the `NOT_PROMOTABLE` flag is preserved. + // Ensure the `IsNotPromotable` qualification is preserved. // NOTE(eddyb) this is actually unnecessary right now, as - // we never replace the local's qualif (but we might in - // the future) - also, if `NOT_PROMOTABLE` only matters - // for `Mode::Fn`, then this is also pointless. + // we never replace the local's qualif, but we might in + // the future, and so it serves to catch changes that unset + // important bits (in which case, asserting `contains` could + // be replaced with calling `insert` to re-set the bit). if kind == LocalKind::Temp { if !self.temp_promotion_state[index].is_promotable() { - *slot = *slot | Qualif::NOT_PROMOTABLE; + assert!(self.cx.per_local[IsNotPromotable].contains(index)); } } } /// Check a whole const, static initializer or const fn. - fn check_const(&mut self) -> (Qualif, Lrc>) { + fn check_const(&mut self) -> (u8, Lrc>) { debug!("const-checking {} {:?}", self.mode, self.def_id); let mir = self.mir; @@ -660,14 +869,6 @@ fn check_const(&mut self) -> (Qualif, Lrc>) { } } - let mut qualif = self.local_qualif[RETURN_PLACE]; - - // Account for errors in consts by using the - // conservative type qualification instead. - if qualif.intersects(Qualif::NOT_CONST) { - qualif = self.qualify_any_value_of_ty(mir.return_ty()); - } - // Collect all the temps we need to promote. let mut promoted_temps = BitSet::new_empty(self.temp_promotion_state.len()); @@ -687,7 +888,17 @@ fn check_const(&mut self) -> (Qualif, Lrc>) { } } - (qualif, Lrc::new(promoted_temps)) + let promoted_temps = Lrc::new(promoted_temps); + + let mut qualifs = self.qualifs_in_local(RETURN_PLACE); + + // Account for errors in consts by using the + // conservative type qualification instead. + if qualifs[IsNotConst] { + qualifs = self.qualifs_in_any_value_of_ty(mir.return_ty()); + } + + (qualifs.encode_to_bits(), promoted_temps) } } @@ -822,8 +1033,7 @@ fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { Operand::Move(ref place) => { // Mark the consumed locals to indicate later drops are noops. if let Place::Local(local) = *place { - let slot = &mut self.cx.local_qualif[local]; - *slot = *slot - Qualif::NEEDS_DROP; + self.cx.per_local[NeedsDrop].remove(local); } } Operand::Copy(_) | @@ -960,9 +1170,11 @@ fn visit_terminator_kind(&mut self, debug!("visit_terminator_kind: bb={:?} kind={:?} location={:?}", bb, kind, location); if let TerminatorKind::Call { ref func, ref args, ref destination, .. } = *kind { if let Some((ref dest, _)) = *destination { - let ty = dest.ty(self.mir, self.tcx).to_ty(self.tcx); - let qualif = self.qualify_call(func, args, ty); - self.assign(dest, qualif, location); + self.assign(dest, ValueSource::Call { + callee: func, + args, + return_ty: dest.ty(self.mir, self.tcx).to_ty(self.tcx), + }, location); } let fn_ty = func.ty(self.mir, self.tcx); @@ -1094,8 +1306,7 @@ fn visit_terminator_kind(&mut self, // which happens even without the user requesting it. // We can error out with a hard error if the argument is not // constant here. - let arg_qualif = self.qualify_operand(arg); - if (arg_qualif - Qualif::NOT_PROMOTABLE).is_empty() { + if !IsNotConst::in_operand(self, arg) { debug!("visit_terminator_kind: candidate={:?}", candidate); self.promotion_candidates.push(candidate); } else { @@ -1125,7 +1336,7 @@ fn visit_terminator_kind(&mut self, // HACK(eddyb): emulate a bit of dataflow analysis, // conservatively, that drop elaboration will do. let needs_drop = if let Place::Local(local) = *place { - if self.local_qualif[local].contains(Qualif::NEEDS_DROP) { + if NeedsDrop::in_local(self, local) { Some(self.mir.local_decls[local].source_info.span) } else { None @@ -1158,75 +1369,7 @@ fn visit_assign(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { debug!("visit_assign: dest={:?} rvalue={:?} location={:?}", dest, rvalue, location); - let mut qualif = self.qualify_rvalue(rvalue); - - if let Rvalue::Ref(_, kind, ref place) = *rvalue { - // Getting `MUTABLE_INTERIOR` from `qualify_rvalue` means - // the borrowed place is disallowed from being borrowed, - // due to either a mutable borrow (with some exceptions), - // or an shared borrow of a value with interior mutability. - // Then `MUTABLE_INTERIOR` is replaced with `NOT_CONST`, - // to avoid duplicate errors (e.g. from reborrowing). - if qualif.contains(Qualif::MUTABLE_INTERIOR) { - qualif = (qualif - Qualif::MUTABLE_INTERIOR) | Qualif::NOT_CONST; - - if self.mode != Mode::Fn { - if let BorrowKind::Mut { .. } = kind { - let mut err = struct_span_err!(self.tcx.sess, self.span, E0017, - "references in {}s may only refer \ - to immutable values", self.mode); - err.span_label(self.span, format!("{}s require immutable values", - self.mode)); - if self.tcx.sess.teach(&err.get_code().unwrap()) { - err.note("References in statics and constants may only refer to \ - immutable values.\n\n\ - Statics are shared everywhere, and if they refer to \ - mutable data one might violate memory safety since \ - holding multiple mutable references to shared data is \ - not allowed.\n\n\ - If you really want global mutable state, try using \ - static mut or a global UnsafeCell."); - } - err.emit(); - } else { - span_err!(self.tcx.sess, self.span, E0492, - "cannot borrow a constant which may contain \ - interior mutability, create a static instead"); - } - } - } else { - // We might have a candidate for promotion. - let candidate = Candidate::Ref(location); - // We can only promote interior borrows of promotable temps. - let mut place = place; - while let Place::Projection(ref proj) = *place { - if proj.elem == ProjectionElem::Deref { - break; - } - place = &proj.base; - } - debug!("qualify_consts: promotion candidate: place={:?}", place); - if let Place::Local(local) = *place { - if self.mir.local_kind(local) == LocalKind::Temp { - debug!("qualify_consts: promotion candidate: local={:?}", local); - let qualif = self.local_qualif[local]; - // The borrowed place doesn't have `MUTABLE_INTERIOR` - // (from `qualify_rvalue`), so we can safely ignore - // `MUTABLE_INTERIOR` from the local's qualifications. - // This allows borrowing fields which don't have - // `MUTABLE_INTERIOR`, from a type that does, e.g.: - // `let _: &'static _ = &(Cell::new(1), 2).1;` - debug!("qualify_consts: promotion candidate: qualif={:?}", qualif); - if (qualif - Qualif::MUTABLE_INTERIOR).is_empty() { - debug!("qualify_consts: promotion candidate: {:?}", candidate); - self.promotion_candidates.push(candidate); - } - } - } - } - } - - self.assign(dest, qualif, location); + self.assign(dest, ValueSource::Rvalue(rvalue), location); self.visit_rvalue(rvalue, location); } @@ -1281,12 +1424,10 @@ fn mir_const_qualif<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, if mir.return_ty().references_error() { tcx.sess.delay_span_bug(mir.span, "mir_const_qualif: Mir had errors"); - return (Qualif::NOT_CONST.bits(), Lrc::new(BitSet::new_empty(0))); + return (1 << IsNotConst::IDX, Lrc::new(BitSet::new_empty(0))); } - let mut checker = Checker::new(tcx, def_id, mir, Mode::Const); - let (qualif, promoted_temps) = checker.check_const(); - (qualif.bits(), promoted_temps) + Checker::new(tcx, def_id, mir, Mode::Const).check_const() } pub struct QualifyAndPromoteConstants; -- 2.44.0