Const dropping

This commit is contained in:
Deadbeef 2021-09-01 11:06:15 +00:00
parent d9797d23d5
commit 104e40fb74
No known key found for this signature in database
GPG key ID: 027DF9338862ADDD
9 changed files with 141 additions and 28 deletions

View file

@ -22,7 +22,7 @@ use std::mem;
use std::ops::Deref;
use super::ops::{self, NonConstOp, Status};
use super::qualifs::{self, CustomEq, HasMutInterior, NeedsDrop};
use super::qualifs::{self, CustomEq, HasMutInterior, NeedsNonConstDrop};
use super::resolver::FlowSensitiveAnalysis;
use super::{is_lang_panic_fn, ConstCx, Qualif};
use crate::const_eval::is_unstable_const_fn;
@ -39,7 +39,7 @@ type QualifResults<'mir, 'tcx, Q> =
#[derive(Default)]
pub struct Qualifs<'mir, 'tcx> {
has_mut_interior: Option<QualifResults<'mir, 'tcx, HasMutInterior>>,
needs_drop: Option<QualifResults<'mir, 'tcx, NeedsDrop>>,
needs_drop: Option<QualifResults<'mir, 'tcx, NeedsNonConstDrop>>,
indirectly_mutable: Option<IndirectlyMutableResults<'mir, 'tcx>>,
}
@ -80,14 +80,14 @@ impl Qualifs<'mir, 'tcx> {
location: Location,
) -> bool {
let ty = ccx.body.local_decls[local].ty;
if !NeedsDrop::in_any_value_of_ty(ccx, ty) {
if !NeedsNonConstDrop::in_any_value_of_ty(ccx, ty) {
return false;
}
let needs_drop = self.needs_drop.get_or_insert_with(|| {
let ConstCx { tcx, body, .. } = *ccx;
FlowSensitiveAnalysis::new(NeedsDrop, ccx)
FlowSensitiveAnalysis::new(NeedsNonConstDrop, ccx)
.into_engine(tcx, &body)
.iterate_to_fixpoint()
.into_results_cursor(&body)
@ -991,7 +991,7 @@ impl Visitor<'tcx> for Checker<'mir, 'tcx> {
// Check to see if the type of this place can ever have a drop impl. If not, this
// `Drop` terminator is frivolous.
let ty_needs_drop =
dropped_place.ty(self.body, self.tcx).ty.needs_drop(self.tcx, self.param_env);
dropped_place.ty(self.body, self.tcx).ty.needs_non_const_drop(self.tcx, self.param_env);
if !ty_needs_drop {
return;

View file

@ -5,7 +5,7 @@ use rustc_span::Span;
use super::check::Qualifs;
use super::ops::{self, NonConstOp};
use super::qualifs::{NeedsDrop, Qualif};
use super::qualifs::{NeedsNonConstDrop, Qualif};
use super::ConstCx;
/// Returns `true` if we should use the more precise live drop checker that runs after drop
@ -78,7 +78,7 @@ impl Visitor<'tcx> for CheckLiveDrops<'mir, 'tcx> {
match &terminator.kind {
mir::TerminatorKind::Drop { place: dropped_place, .. } => {
let dropped_ty = dropped_place.ty(self.body, self.tcx).ty;
if !NeedsDrop::in_any_value_of_ty(self.ccx, dropped_ty) {
if !NeedsNonConstDrop::in_any_value_of_ty(self.ccx, dropped_ty) {
bug!(
"Drop elaboration left behind a Drop for a type that does not need dropping"
);

View file

@ -17,7 +17,7 @@ pub fn in_any_value_of_ty(
) -> ConstQualifs {
ConstQualifs {
has_mut_interior: HasMutInterior::in_any_value_of_ty(cx, ty),
needs_drop: NeedsDrop::in_any_value_of_ty(cx, ty),
needs_drop: NeedsNonConstDrop::in_any_value_of_ty(cx, ty),
custom_eq: CustomEq::in_any_value_of_ty(cx, ty),
error_occured,
}
@ -97,10 +97,10 @@ impl Qualif for HasMutInterior {
/// This must be ruled out (a) because we cannot run `Drop` during compile-time
/// as that might not be a `const fn`, and (b) because implicit promotion would
/// remove side-effects that occur as part of dropping that value.
pub struct NeedsDrop;
pub struct NeedsNonConstDrop;
impl Qualif for NeedsDrop {
const ANALYSIS_NAME: &'static str = "flow_needs_drop";
impl Qualif for NeedsNonConstDrop {
const ANALYSIS_NAME: &'static str = "flow_needs_nonconst_drop";
const IS_CLEARED_ON_MOVE: bool = true;
fn in_qualifs(qualifs: &ConstQualifs) -> bool {
@ -112,7 +112,7 @@ impl Qualif for NeedsDrop {
}
fn in_adt_inherently(cx: &ConstCx<'_, 'tcx>, adt: &'tcx AdtDef, _: SubstsRef<'tcx>) -> bool {
adt.has_dtor(cx.tcx)
adt.has_non_const_dtor(cx.tcx)
}
}

View file

@ -231,7 +231,7 @@ impl<'tcx> Validator<'_, 'tcx> {
// We cannot promote things that need dropping, since the promoted value
// would not get dropped.
if self.qualif_local::<qualifs::NeedsDrop>(place.local) {
if self.qualif_local::<qualifs::NeedsNonConstDrop>(place.local) {
return Err(Unpromotable);
}

View file

@ -1077,6 +1077,10 @@ rustc_queries! {
query needs_drop_raw(env: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
desc { "computing whether `{}` needs drop", env.value }
}
/// Query backing `Tys::needs_non_const_drop`.
query needs_non_const_drop_raw(env: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
desc { "computing whether `{}` needs non-const drop", env.value }
}
/// Query backing `TyS::has_significant_drop_raw`.
query has_significant_drop_raw(env: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
desc { "computing whether `{}` has a significant drop", env.value }
@ -1101,6 +1105,14 @@ rustc_queries! {
cache_on_disk_if { true }
}
/// A list of types where the ADT requires drop if and only if any of
/// those types require non-const drop. If the ADT is known to always need
/// non-const drop then `Err(AlwaysRequiresDrop)` is returned.
query adt_drop_tys_non_const(def_id: DefId) -> Result<&'tcx ty::List<Ty<'tcx>>, AlwaysRequiresDrop> {
desc { |tcx| "computing when `{}` needs non-const drop", tcx.def_path_str(def_id) }
cache_on_disk_if { true }
}
/// A list of types where the ADT requires drop if and only if any of those types
/// has significant drop. A type marked with the attribute `rustc_insignificant_dtor`
/// is considered to not be significant. A drop is significant if it is implemented

View file

@ -7,6 +7,7 @@ use rustc_data_structures::fingerprint::Fingerprint;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_errors::ErrorReported;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_index::vec::{Idx, IndexVec};
@ -288,6 +289,10 @@ impl<'tcx> AdtDef {
self.destructor(tcx).is_some()
}
pub fn has_non_const_dtor(&self, tcx: TyCtxt<'tcx>) -> bool {
matches!(self.destructor(tcx), Some(Destructor { constness: hir::Constness::NotConst, .. }))
}
/// Asserts this is a struct or union and returns its unique variant.
pub fn non_enum_variant(&self) -> &VariantDef {
assert!(self.is_struct() || self.is_union());

View file

@ -1377,6 +1377,8 @@ where
pub struct Destructor {
/// The `DefId` of the destructor method
pub did: DefId,
/// The constness of the destructor method
pub constness: hir::Constness,
}
bitflags! {

View file

@ -336,16 +336,16 @@ impl<'tcx> TyCtxt<'tcx> {
self.ensure().coherent_trait(drop_trait);
let ty = self.type_of(adt_did);
let dtor_did = self.find_map_relevant_impl(drop_trait, ty, |impl_did| {
let (did, constness) = self.find_map_relevant_impl(drop_trait, ty, |impl_did| {
if let Some(item) = self.associated_items(impl_did).in_definition_order().next() {
if validate(self, impl_did).is_ok() {
return Some(item.def_id);
return Some((item.def_id, self.impl_constness(impl_did)));
}
}
None
});
})?;
Some(ty::Destructor { did: dtor_did? })
Some(ty::Destructor { did, constness })
}
/// Returns the set of types that are required to be alive in
@ -792,6 +792,35 @@ impl<'tcx> ty::TyS<'tcx> {
}
}
}
/// If `ty.needs_non_const_drop(...)` returns true, then `ty` is definitely
/// non-copy and *might* have a non-const destructor attached; if it returns
/// `false`, then `ty` definitely has a const destructor or no destructor at all.
///
/// (Note that this implies that if `ty` has a non-const destructor attached,
/// then `needs_non_const_drop` will definitely return `true` for `ty`.)
pub fn needs_non_const_drop(
&'tcx self,
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> bool {
// Avoid querying in simple cases.
match needs_drop_components(self, &tcx.data_layout) {
Err(AlwaysRequiresDrop) => true,
Ok(components) => {
let query_ty = match *components {
[] => return false,
// if we've got a single component, call the query with that
// to increase the chance that we hit the query cache.
[component_ty] => component_ty,
_ => self,
};
// This doesn't depend on regions, so try to minimize distinct
// query keys used.
let erased = tcx.normalize_erasing_regions(param_env, query_ty);
tcx.needs_non_const_drop_raw(param_env.and(erased))
}
}
}
/// Checks if `ty` has has a significant drop.
///

View file

@ -1,22 +1,41 @@
//! Check whether a type has (potentially) non-trivial drop glue.
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::ty::subst::Subst;
use rustc_middle::ty::util::{needs_drop_components, AlwaysRequiresDrop};
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_session::Limit;
use rustc_span::{sym, DUMMY_SP};
use rustc_trait_selection::traits::{Obligation, ObligationCause, SelectionContext};
type NeedsDropResult<T> = Result<T, AlwaysRequiresDrop>;
fn needs_drop_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
let adt_fields =
move |adt_def: &ty::AdtDef| tcx.adt_drop_tys(adt_def.did).map(|tys| tys.iter());
fn needs_drop_raw<'tcx>(
tcx: TyCtxt<'tcx>,
query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>,
needs_non_const_drop: bool,
) -> bool {
// If we don't know a type doesn't need drop, for example if it's a type
// parameter without a `Copy` bound, then we conservatively return that it
// needs drop.
let res = NeedsDropTypes::new(tcx, query.param_env, query.value, adt_fields).next().is_some();
let res = if needs_non_const_drop {
let adt_components = move |adt_def: &ty::AdtDef| {
tcx.adt_drop_tys_non_const(adt_def.did).map(|tys| tys.iter())
};
NeedsDropTypes::new(tcx, query.param_env, query.value, adt_components, needs_non_const_drop)
.next()
.is_some()
} else {
let adt_components =
move |adt_def: &ty::AdtDef| tcx.adt_drop_tys(adt_def.did).map(|tys| tys.iter());
NeedsDropTypes::new(tcx, query.param_env, query.value, adt_components, needs_non_const_drop)
.next()
.is_some()
};
debug!("needs_drop_raw({:?}) = {:?}", query, res);
res
}
@ -27,9 +46,10 @@ fn has_significant_drop_raw<'tcx>(
) -> bool {
let significant_drop_fields =
move |adt_def: &ty::AdtDef| tcx.adt_significant_drop_tys(adt_def.did).map(|tys| tys.iter());
let res = NeedsDropTypes::new(tcx, query.param_env, query.value, significant_drop_fields)
.next()
.is_some();
let res =
NeedsDropTypes::new(tcx, query.param_env, query.value, significant_drop_fields, false)
.next()
.is_some();
debug!("has_significant_drop_raw({:?}) = {:?}", query, res);
res
}
@ -46,6 +66,7 @@ struct NeedsDropTypes<'tcx, F> {
unchecked_tys: Vec<(Ty<'tcx>, usize)>,
recursion_limit: Limit,
adt_components: F,
needs_non_const_drop: bool,
}
impl<'tcx, F> NeedsDropTypes<'tcx, F> {
@ -54,6 +75,7 @@ impl<'tcx, F> NeedsDropTypes<'tcx, F> {
param_env: ty::ParamEnv<'tcx>,
ty: Ty<'tcx>,
adt_components: F,
needs_non_const_drop: bool,
) -> Self {
let mut seen_tys = FxHashSet::default();
seen_tys.insert(ty);
@ -65,6 +87,7 @@ impl<'tcx, F> NeedsDropTypes<'tcx, F> {
unchecked_tys: vec![(ty, 0)],
recursion_limit: tcx.recursion_limit(),
adt_components,
needs_non_const_drop,
}
}
}
@ -147,6 +170,35 @@ where
queue_type(self, subst_ty);
}
}
ty::Param(_)
if self.needs_non_const_drop && self.tcx.features().const_trait_impl =>
{
// Check if the param is bounded to have a `~const Drop` impl.
let drop_trait = self.tcx.require_lang_item(hir::LangItem::Drop, None);
let trait_ref = ty::TraitRef {
def_id: drop_trait,
substs: self.tcx.mk_substs_trait(component, &[]),
};
let obligation = Obligation::new(
ObligationCause::dummy(),
self.param_env,
ty::Binder::dummy(ty::TraitPredicate {
trait_ref,
constness: ty::BoundConstness::ConstIfConst,
}),
);
let implsrc = tcx.infer_ctxt().enter(|infcx| {
let mut selcx =
SelectionContext::with_constness(&infcx, hir::Constness::Const);
selcx.select(&obligation)
});
if let Ok(Some(_)) = implsrc {
return None;
}
}
ty::Array(..) | ty::Opaque(..) | ty::Projection(..) | ty::Param(_) => {
if ty == component {
// Return the type to the caller: they may be able
@ -176,6 +228,7 @@ fn adt_drop_tys_helper(
tcx: TyCtxt<'_>,
def_id: DefId,
adt_has_dtor: impl Fn(&ty::AdtDef) -> bool,
needs_non_const_drop: bool,
) -> Result<&ty::List<Ty<'_>>, AlwaysRequiresDrop> {
let adt_components = move |adt_def: &ty::AdtDef| {
if adt_def.is_manually_drop() {
@ -194,7 +247,7 @@ fn adt_drop_tys_helper(
let adt_ty = tcx.type_of(def_id);
let param_env = tcx.param_env(def_id);
let res: Result<Vec<_>, _> =
NeedsDropTypes::new(tcx, param_env, adt_ty, adt_components).collect();
NeedsDropTypes::new(tcx, param_env, adt_ty, adt_components, needs_non_const_drop).collect();
debug!("adt_drop_tys(`{}`) = `{:?}`", tcx.def_path_str(def_id), res);
res.map(|components| tcx.intern_type_list(&components))
@ -202,7 +255,17 @@ fn adt_drop_tys_helper(
fn adt_drop_tys(tcx: TyCtxt<'_>, def_id: DefId) -> Result<&ty::List<Ty<'_>>, AlwaysRequiresDrop> {
let adt_has_dtor = |adt_def: &ty::AdtDef| adt_def.destructor(tcx).is_some();
adt_drop_tys_helper(tcx, def_id, adt_has_dtor)
adt_drop_tys_helper(tcx, def_id, adt_has_dtor, false)
}
fn adt_drop_tys_non_const(
tcx: TyCtxt<'_>,
def_id: DefId,
) -> Result<&ty::List<Ty<'_>>, AlwaysRequiresDrop> {
let adt_has_dtor = |adt_def: &ty::AdtDef| {
adt_def.destructor(tcx).map(|d| d.constness) == Some(hir::Constness::NotConst)
};
adt_drop_tys_helper(tcx, def_id, adt_has_dtor, true)
}
fn adt_significant_drop_tys(
@ -215,14 +278,16 @@ fn adt_significant_drop_tys(
.map(|dtor| !tcx.has_attr(dtor.did, sym::rustc_insignificant_dtor))
.unwrap_or(false)
};
adt_drop_tys_helper(tcx, def_id, adt_has_dtor)
adt_drop_tys_helper(tcx, def_id, adt_has_dtor, false)
}
pub(crate) fn provide(providers: &mut ty::query::Providers) {
*providers = ty::query::Providers {
needs_drop_raw,
needs_drop_raw: |tcx, query| needs_drop_raw(tcx, query, false),
needs_non_const_drop_raw: |tcx, query| needs_drop_raw(tcx, query, true),
has_significant_drop_raw,
adt_drop_tys,
adt_drop_tys_non_const,
adt_significant_drop_tys,
..*providers
};