Skip to content

Implement needs_async_drop in rustc and optimize async drop glue #124662

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 31, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
@@ -176,6 +176,7 @@ language_item_table! {
AsyncDropSlice, sym::async_drop_slice, async_drop_slice_fn, Target::Fn, GenericRequirement::Exact(1);
AsyncDropChain, sym::async_drop_chain, async_drop_chain_fn, Target::Fn, GenericRequirement::Exact(2);
AsyncDropNoop, sym::async_drop_noop, async_drop_noop_fn, Target::Fn, GenericRequirement::Exact(0);
AsyncDropDeferredDropInPlace, sym::async_drop_deferred_drop_in_place, async_drop_deferred_drop_in_place_fn, Target::Fn, GenericRequirement::Exact(1);
AsyncDropFuse, sym::async_drop_fuse, async_drop_fuse_fn, Target::Fn, GenericRequirement::Exact(1);
AsyncDropDefer, sym::async_drop_defer, async_drop_defer_fn, Target::Fn, GenericRequirement::Exact(1);
AsyncDropEither, sym::async_drop_either, async_drop_either_fn, Target::Fn, GenericRequirement::Exact(3);
5 changes: 5 additions & 0 deletions compiler/rustc_hir_analysis/src/check/mod.rs
Original file line number Diff line number Diff line change
@@ -112,6 +112,7 @@ pub fn provide(providers: &mut Providers) {
wfcheck::provide(providers);
*providers = Providers {
adt_destructor,
adt_async_destructor,
region_scope_tree,
collect_return_position_impl_trait_in_trait_tys,
compare_impl_const: compare_impl_item::compare_impl_const_raw,
@@ -124,6 +125,10 @@ fn adt_destructor(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<ty::Destructor>
tcx.calculate_dtor(def_id.to_def_id(), dropck::check_drop_impl)
}

fn adt_async_destructor(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<ty::AsyncDestructor> {
tcx.calculate_async_dtor(def_id.to_def_id(), dropck::check_drop_impl)
}

/// Given a `DefId` for an opaque type in return position, find its parent item's return
/// expressions.
fn get_owner_return_paths(
4 changes: 4 additions & 0 deletions compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
Original file line number Diff line number Diff line change
@@ -287,6 +287,10 @@ provide! { tcx, def_id, other, cdata,
let _ = cdata;
tcx.calculate_dtor(def_id, |_,_| Ok(()))
}
adt_async_destructor => {
let _ = cdata;
tcx.calculate_async_dtor(def_id, |_,_| Ok(()))
}
associated_item_def_ids => {
tcx.arena.alloc_from_iter(cdata.get_associated_item_or_field_def_ids(def_id.index))
}
2 changes: 2 additions & 0 deletions compiler/rustc_middle/src/query/erase.rs
Original file line number Diff line number Diff line change
@@ -238,6 +238,7 @@ trivial! {
Option<rustc_hir::CoroutineKind>,
Option<rustc_hir::HirId>,
Option<rustc_middle::middle::stability::DeprecationEntry>,
Option<rustc_middle::ty::AsyncDestructor>,
Option<rustc_middle::ty::Destructor>,
Option<rustc_middle::ty::ImplTraitInTraitData>,
Option<rustc_middle::ty::ScalarInt>,
@@ -295,6 +296,7 @@ trivial! {
rustc_middle::ty::AssocItem,
rustc_middle::ty::AssocItemContainer,
rustc_middle::ty::Asyncness,
rustc_middle::ty::AsyncDestructor,
rustc_middle::ty::BoundVariableKind,
rustc_middle::ty::DeducedParamAttrs,
rustc_middle::ty::Destructor,
17 changes: 9 additions & 8 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
@@ -703,6 +703,11 @@ rustc_queries! {
cache_on_disk_if { key.is_local() }
separate_provide_extern
}
query adt_async_destructor(key: DefId) -> Option<ty::AsyncDestructor> {
desc { |tcx| "computing `AsyncDrop` impl for `{}`", tcx.def_path_str(key) }
cache_on_disk_if { key.is_local() }
separate_provide_extern
}

query adt_sized_constraint(key: DefId) -> Option<ty::EarlyBinder<'tcx, Ty<'tcx>>> {
desc { |tcx| "computing the `Sized` constraint for `{}`", tcx.def_path_str(key) }
@@ -1343,18 +1348,14 @@ rustc_queries! {
query is_unpin_raw(env: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
desc { "computing whether `{}` is `Unpin`", env.value }
}
/// Query backing `Ty::has_surface_async_drop`.
query has_surface_async_drop_raw(env: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
desc { "computing whether `{}` has `AsyncDrop` implementation", env.value }
}
/// Query backing `Ty::has_surface_drop`.
query has_surface_drop_raw(env: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
desc { "computing whether `{}` has `Drop` implementation", env.value }
}
/// Query backing `Ty::needs_drop`.
query needs_drop_raw(env: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
desc { "computing whether `{}` needs drop", env.value }
}
/// Query backing `Ty::needs_async_drop`.
query needs_async_drop_raw(env: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
desc { "computing whether `{}` needs async drop", env.value }
}
/// Query backing `Ty::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 }
10 changes: 9 additions & 1 deletion compiler/rustc_middle/src/ty/adt.rs
Original file line number Diff line number Diff line change
@@ -24,7 +24,9 @@ use std::hash::{Hash, Hasher};
use std::ops::Range;
use std::str;

use super::{Destructor, FieldDef, GenericPredicates, Ty, TyCtxt, VariantDef, VariantDiscr};
use super::{
AsyncDestructor, Destructor, FieldDef, GenericPredicates, Ty, TyCtxt, VariantDef, VariantDiscr,
};

#[derive(Clone, Copy, PartialEq, Eq, Hash, HashStable, TyEncodable, TyDecodable)]
pub struct AdtFlags(u16);
@@ -577,6 +579,12 @@ impl<'tcx> AdtDef<'tcx> {
tcx.adt_destructor(self.did())
}

// FIXME: consider combining this method with `AdtDef::destructor` and removing
// this version
pub fn async_destructor(self, tcx: TyCtxt<'tcx>) -> Option<AsyncDestructor> {
tcx.adt_async_destructor(self.did())
}

/// Returns a type such that `Self: Sized` if and only if that type is `Sized`,
/// or `None` if the type is always sized.
pub fn sized_constraint(self, tcx: TyCtxt<'tcx>) -> Option<ty::EarlyBinder<'tcx, Ty<'tcx>>> {
9 changes: 9 additions & 0 deletions compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
@@ -1172,6 +1172,15 @@ pub struct Destructor {
pub constness: hir::Constness,
}

// FIXME: consider combining this definition with regular `Destructor`
#[derive(Copy, Clone, Debug, HashStable, Encodable, Decodable)]
pub struct AsyncDestructor {
/// The `DefId` of the async destructor future constructor
pub ctor: DefId,
/// The `DefId` of the async destructor future type
pub future: DefId,
}

#[derive(Clone, Copy, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
pub struct VariantFlags(u8);
bitflags::bitflags! {
74 changes: 33 additions & 41 deletions compiler/rustc_middle/src/ty/sty.rs
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ use std::assert_matches::debug_assert_matches;
use std::borrow::Cow;
use std::iter;
use std::ops::{ControlFlow, Range};
use ty::util::IntTypeExt;
use ty::util::{AsyncDropGlueMorphology, IntTypeExt};

use rustc_type_ir::TyKind::*;
use rustc_type_ir::{self as ir, BoundVar, CollectAndApply, DynKind};
@@ -1951,11 +1951,22 @@ impl<'tcx> Ty<'tcx> {
}

/// Returns the type of the async destructor of this type.
pub fn async_destructor_ty(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Ty<'tcx> {
if self.is_async_destructor_noop(tcx, param_env) || matches!(self.kind(), ty::Error(_)) {
return Ty::async_destructor_combinator(tcx, LangItem::AsyncDropNoop)
.instantiate_identity();
pub fn async_destructor_ty(self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
match self.async_drop_glue_morphology(tcx) {
AsyncDropGlueMorphology::Noop => {
return Ty::async_destructor_combinator(tcx, LangItem::AsyncDropNoop)
.instantiate_identity();
}
AsyncDropGlueMorphology::DeferredDropInPlace => {
let drop_in_place =
Ty::async_destructor_combinator(tcx, LangItem::AsyncDropDeferredDropInPlace)
.instantiate(tcx, &[self.into()]);
return Ty::async_destructor_combinator(tcx, LangItem::AsyncDropFuse)
.instantiate(tcx, &[drop_in_place.into()]);
}
AsyncDropGlueMorphology::Custom => (),
}

match *self.kind() {
ty::Param(_) | ty::Alias(..) | ty::Infer(ty::TyVar(_)) => {
let assoc_items = tcx
@@ -1974,24 +1985,18 @@ impl<'tcx> Ty<'tcx> {
.adt_async_destructor_ty(
tcx,
adt_def.variants().iter().map(|v| v.fields.iter().map(|f| f.ty(tcx, args))),
param_env,
),
ty::Tuple(tys) => self.adt_async_destructor_ty(tcx, iter::once(tys), param_env),
ty::Closure(_, args) => self.adt_async_destructor_ty(
tcx,
iter::once(args.as_closure().upvar_tys()),
param_env,
),
ty::CoroutineClosure(_, args) => self.adt_async_destructor_ty(
tcx,
iter::once(args.as_coroutine_closure().upvar_tys()),
param_env,
),
ty::Tuple(tys) => self.adt_async_destructor_ty(tcx, iter::once(tys)),
ty::Closure(_, args) => {
self.adt_async_destructor_ty(tcx, iter::once(args.as_closure().upvar_tys()))
}
ty::CoroutineClosure(_, args) => self
.adt_async_destructor_ty(tcx, iter::once(args.as_coroutine_closure().upvar_tys())),

ty::Adt(adt_def, _) => {
assert!(adt_def.is_union());

let surface_drop = self.surface_async_dropper_ty(tcx, param_env).unwrap();
let surface_drop = self.surface_async_dropper_ty(tcx).unwrap();

Ty::async_destructor_combinator(tcx, LangItem::AsyncDropFuse)
.instantiate(tcx, &[surface_drop.into()])
@@ -2008,17 +2013,12 @@ impl<'tcx> Ty<'tcx> {
}
}

fn adt_async_destructor_ty<I>(
self,
tcx: TyCtxt<'tcx>,
variants: I,
param_env: ParamEnv<'tcx>,
) -> Ty<'tcx>
fn adt_async_destructor_ty<I>(self, tcx: TyCtxt<'tcx>, variants: I) -> Ty<'tcx>
where
I: Iterator + ExactSizeIterator,
I::Item: IntoIterator<Item = Ty<'tcx>>,
{
debug_assert!(!self.is_async_destructor_noop(tcx, param_env));
debug_assert_eq!(self.async_drop_glue_morphology(tcx), AsyncDropGlueMorphology::Custom);

let defer = Ty::async_destructor_combinator(tcx, LangItem::AsyncDropDefer);
let chain = Ty::async_destructor_combinator(tcx, LangItem::AsyncDropChain);
@@ -2041,7 +2041,7 @@ impl<'tcx> Ty<'tcx> {
})
.unwrap();

let dtor = if let Some(dropper_ty) = self.surface_async_dropper_ty(tcx, param_env) {
let dtor = if let Some(dropper_ty) = self.surface_async_dropper_ty(tcx) {
Ty::async_destructor_combinator(tcx, LangItem::AsyncDropChain)
.instantiate(tcx, &[dropper_ty.into(), variants_dtor.into()])
} else {
@@ -2052,21 +2052,13 @@ impl<'tcx> Ty<'tcx> {
.instantiate(tcx, &[dtor.into()])
}

fn surface_async_dropper_ty(
self,
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
) -> Option<Ty<'tcx>> {
if self.has_surface_async_drop(tcx, param_env) {
Some(LangItem::SurfaceAsyncDropInPlace)
} else if self.has_surface_drop(tcx, param_env) {
Some(LangItem::AsyncDropSurfaceDropInPlace)
} else {
None
}
.map(|dropper| {
Ty::async_destructor_combinator(tcx, dropper).instantiate(tcx, &[self.into()])
})
fn surface_async_dropper_ty(self, tcx: TyCtxt<'tcx>) -> Option<Ty<'tcx>> {
let adt_def = self.ty_adt_def()?;
let dropper = adt_def
.async_destructor(tcx)
.map(|_| LangItem::SurfaceAsyncDropInPlace)
.or_else(|| adt_def.destructor(tcx).map(|_| LangItem::AsyncDropSurfaceDropInPlace))?;
Some(Ty::async_destructor_combinator(tcx, dropper).instantiate(tcx, &[self.into()]))
}

fn async_destructor_combinator(
265 changes: 183 additions & 82 deletions compiler/rustc_middle/src/ty/util.rs
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use crate::query::{IntoQueryParam, Providers};
use crate::ty::layout::{FloatExt, IntegerExt};
use crate::ty::{
self, FallibleTypeFolder, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
self, Asyncness, FallibleTypeFolder, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
TypeVisitableExt, Upcast,
};
use crate::ty::{GenericArgKind, GenericArgsRef};
@@ -382,6 +382,64 @@ impl<'tcx> TyCtxt<'tcx> {
Some(ty::Destructor { did, constness })
}

/// Calculate the async destructor of a given type.
pub fn calculate_async_dtor(
self,
adt_did: DefId,
validate: impl Fn(Self, DefId) -> Result<(), ErrorGuaranteed>,
) -> Option<ty::AsyncDestructor> {
let async_drop_trait = self.lang_items().async_drop_trait()?;
self.ensure().coherent_trait(async_drop_trait).ok()?;

let ty = self.type_of(adt_did).instantiate_identity();
let mut dtor_candidate = None;
self.for_each_relevant_impl(async_drop_trait, ty, |impl_did| {
if validate(self, impl_did).is_err() {
// Already `ErrorGuaranteed`, no need to delay a span bug here.
return;
}

let [future, ctor] = self.associated_item_def_ids(impl_did) else {
self.dcx().span_delayed_bug(
self.def_span(impl_did),
"AsyncDrop impl without async_drop function or Dropper type",
);
return;
};

if let Some((_, _, old_impl_did)) = dtor_candidate {
self.dcx()
.struct_span_err(self.def_span(impl_did), "multiple async drop impls found")
.with_span_note(self.def_span(old_impl_did), "other impl here")
.delay_as_bug();
}

dtor_candidate = Some((*future, *ctor, impl_did));
});

let (future, ctor, _) = dtor_candidate?;
Some(ty::AsyncDestructor { future, ctor })
}

/// Returns async drop glue morphology for a definition. To get async drop
/// glue morphology for a type see [`Ty::async_drop_glue_morphology`].
//
// FIXME: consider making this a query
pub fn async_drop_glue_morphology(self, did: DefId) -> AsyncDropGlueMorphology {
let ty: Ty<'tcx> = self.type_of(did).instantiate_identity();

// Async drop glue morphology is an internal detail, so reveal_all probably
// should be fine
let param_env = ty::ParamEnv::reveal_all();
if ty.needs_async_drop(self, param_env) {
AsyncDropGlueMorphology::Custom
} else if ty.needs_drop(self, param_env) {
AsyncDropGlueMorphology::DeferredDropInPlace
} else {
AsyncDropGlueMorphology::Noop
}
}

/// Returns the set of types that are required to be alive in
/// order to run the destructor of `def` (see RFCs 769 and
/// 1238).
@@ -1138,6 +1196,18 @@ impl<'tcx> TypeFolder<TyCtxt<'tcx>> for WeakAliasTypeExpander<'tcx> {
}
}

/// Indicates the form of `AsyncDestruct::Destructor`. Used to simplify async
/// drop glue for types not using async drop.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum AsyncDropGlueMorphology {
/// Async destructor simply does nothing
Noop,
/// Async destructor simply runs `drop_in_place`
DeferredDropInPlace,
/// Async destructor has custom logic
Custom,
}

impl<'tcx> Ty<'tcx> {
/// Returns the `Size` for primitive types (bool, uint, int, char, float).
pub fn primitive_size(self, tcx: TyCtxt<'tcx>) -> Size {
@@ -1303,80 +1373,16 @@ impl<'tcx> Ty<'tcx> {
}
}

/// Checks whether values of this type `T` implements the `AsyncDrop`
/// trait.
pub fn has_surface_async_drop(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> bool {
self.could_have_surface_async_drop() && tcx.has_surface_async_drop_raw(param_env.and(self))
}

/// Fast path helper for testing if a type has `AsyncDrop`
/// implementation.
///
/// Returning `false` means the type is known to not have `AsyncDrop`
/// implementation. Returning `true` means nothing -- could be
/// `AsyncDrop`, might not be.
fn could_have_surface_async_drop(self) -> bool {
!self.is_async_destructor_trivially_noop()
&& !matches!(
self.kind(),
ty::Tuple(_)
| ty::Slice(_)
| ty::Array(_, _)
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::Coroutine(..)
)
}

/// Checks whether values of this type `T` implements the `Drop`
/// trait.
pub fn has_surface_drop(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> bool {
self.could_have_surface_drop() && tcx.has_surface_drop_raw(param_env.and(self))
}

/// Fast path helper for testing if a type has `Drop` implementation.
///
/// Returning `false` means the type is known to not have `Drop`
/// implementation. Returning `true` means nothing -- could be
/// `Drop`, might not be.
fn could_have_surface_drop(self) -> bool {
!self.is_async_destructor_trivially_noop()
&& !matches!(
self.kind(),
ty::Tuple(_)
| ty::Slice(_)
| ty::Array(_, _)
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::Coroutine(..)
)
}

/// Checks whether values of this type `T` implement has noop async destructor.
/// Get morphology of the async drop glue, needed for types which do not
/// use async drop. To get async drop glue morphology for a definition see
/// [`TyCtxt::async_drop_glue_morphology`]. Used for `AsyncDestruct::Destructor`
/// type construction.
//
// FIXME: implement optimization to make ADTs, which do not need drop,
// to skip fields or to have noop async destructor.
pub fn is_async_destructor_noop(
self,
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> bool {
self.is_async_destructor_trivially_noop()
|| if let ty::Adt(adt_def, _) = self.kind() {
(adt_def.is_union() || adt_def.is_payloadfree())
&& !self.has_surface_async_drop(tcx, param_env)
&& !self.has_surface_drop(tcx, param_env)
} else {
false
}
}

/// Fast path helper for testing if a type has noop async destructor.
///
/// Returning `true` means the type is known to have noop async destructor
/// implementation. Returning `true` means nothing -- could be
/// `Drop`, might not be.
fn is_async_destructor_trivially_noop(self) -> bool {
// FIXME: implement optimization to not instantiate a certain morphology of
// async drop glue too soon to allow per type optimizations, see array case
// for more info. Perhaps then remove this method and use `needs_(async_)drop`
// instead.
pub fn async_drop_glue_morphology(self, tcx: TyCtxt<'tcx>) -> AsyncDropGlueMorphology {
match self.kind() {
ty::Int(_)
| ty::Uint(_)
@@ -1388,10 +1394,43 @@ impl<'tcx> Ty<'tcx> {
| ty::Ref(..)
| ty::RawPtr(..)
| ty::FnDef(..)
| ty::FnPtr(_) => true,
ty::Tuple(tys) => tys.is_empty(),
ty::Adt(adt_def, _) => adt_def.is_manually_drop(),
_ => false,
| ty::FnPtr(_)
| ty::Infer(ty::FreshIntTy(_))
| ty::Infer(ty::FreshFloatTy(_)) => AsyncDropGlueMorphology::Noop,

ty::Tuple(tys) if tys.is_empty() => AsyncDropGlueMorphology::Noop,
ty::Adt(adt_def, _) if adt_def.is_manually_drop() => AsyncDropGlueMorphology::Noop,

// Foreign types can never have destructors.
ty::Foreign(_) => AsyncDropGlueMorphology::Noop,

// FIXME: implement dynamic types async drops
ty::Error(_) | ty::Dynamic(..) => AsyncDropGlueMorphology::DeferredDropInPlace,

ty::Tuple(_) | ty::Array(_, _) | ty::Slice(_) => {
// Assume worst-case scenario, because we can instantiate async
// destructors in different orders:
//
// 1. Instantiate [T; N] with T = String and N = 0
// 2. Instantiate <[String; 0] as AsyncDestruct>::Destructor
//
// And viceversa, thus we cannot rely on String not using async
// drop or array having zero (0) elements
AsyncDropGlueMorphology::Custom
}
ty::Pat(ty, _) => ty.async_drop_glue_morphology(tcx),

ty::Adt(adt_def, _) => tcx.async_drop_glue_morphology(adt_def.did()),

ty::Closure(did, _)
| ty::CoroutineClosure(did, _)
| ty::Coroutine(did, _)
| ty::CoroutineWitness(did, _) => tcx.async_drop_glue_morphology(*did),

ty::Alias(..) | ty::Param(_) | ty::Bound(..) | ty::Placeholder(..) | ty::Infer(_) => {
// No specifics, but would usually mean forwarding async drop glue
AsyncDropGlueMorphology::Custom
}
}
}

@@ -1430,6 +1469,46 @@ impl<'tcx> Ty<'tcx> {
}
}

/// If `ty.needs_async_drop(...)` returns `true`, then `ty` is definitely
/// non-copy and *might* have a async destructor attached; if it returns
/// `false`, then `ty` definitely has no async destructor (i.e., no async
/// drop glue).
///
/// (Note that this implies that if `ty` has an async destructor attached,
/// then `needs_async_drop` will definitely return `true` for `ty`.)
///
/// When constructing `AsyncDestruct::Destructor` type, use
/// [`Ty::async_drop_glue_morphology`] instead.
//
// FIXME(zetanumbers): Note that this method is used to check eligible types
// in unions.
#[inline]
pub fn needs_async_drop(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> bool {
// Avoid querying in simple cases.
match needs_drop_components(tcx, self) {
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.
// If normalization fails, we just use `query_ty`.
debug_assert!(!param_env.has_infer());
let query_ty = tcx
.try_normalize_erasing_regions(param_env, query_ty)
.unwrap_or_else(|_| tcx.erase_regions(query_ty));

tcx.needs_async_drop_raw(param_env.and(query_ty))
}
}
}

/// Checks if `ty` has a significant drop.
///
/// Note that this method can return false even if `ty` has a destructor
@@ -1601,9 +1680,24 @@ impl<'tcx> ExplicitSelf<'tcx> {
/// Returns a list of types such that the given type needs drop if and only if
/// *any* of the returned types need drop. Returns `Err(AlwaysRequiresDrop)` if
/// this type always needs drop.
//
// FIXME(zetanumbers): consider replacing this with only
// `needs_drop_components_with_async`
#[inline]
pub fn needs_drop_components<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
) -> Result<SmallVec<[Ty<'tcx>; 2]>, AlwaysRequiresDrop> {
needs_drop_components_with_async(tcx, ty, Asyncness::No)
}

/// Returns a list of types such that the given type needs drop if and only if
/// *any* of the returned types need drop. Returns `Err(AlwaysRequiresDrop)` if
/// this type always needs drop.
pub fn needs_drop_components_with_async<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
asyncness: Asyncness,
) -> Result<SmallVec<[Ty<'tcx>; 2]>, AlwaysRequiresDrop> {
match *ty.kind() {
ty::Infer(ty::FreshIntTy(_))
@@ -1623,11 +1717,18 @@ pub fn needs_drop_components<'tcx>(
// Foreign types can never have destructors.
ty::Foreign(..) => Ok(SmallVec::new()),

ty::Dynamic(..) | ty::Error(_) => Err(AlwaysRequiresDrop),
// FIXME(zetanumbers): Temporary workaround for async drop of dynamic types
ty::Dynamic(..) | ty::Error(_) => {
if asyncness.is_async() {
Ok(SmallVec::new())
} else {
Err(AlwaysRequiresDrop)
}
}

ty::Pat(ty, _) | ty::Slice(ty) => needs_drop_components(tcx, ty),
ty::Pat(ty, _) | ty::Slice(ty) => needs_drop_components_with_async(tcx, ty, asyncness),
ty::Array(elem_ty, size) => {
match needs_drop_components(tcx, elem_ty) {
match needs_drop_components_with_async(tcx, elem_ty, asyncness) {
Ok(v) if v.is_empty() => Ok(v),
res => match size.try_to_target_usize(tcx) {
// Arrays of size zero don't need drop, even if their element
@@ -1643,7 +1744,7 @@ pub fn needs_drop_components<'tcx>(
}
// If any field needs drop, then the whole tuple does.
ty::Tuple(fields) => fields.iter().try_fold(SmallVec::new(), move |mut acc, elem| {
acc.extend(needs_drop_components(tcx, elem)?);
acc.extend(needs_drop_components_with_async(tcx, elem, asyncness)?);
Ok(acc)
}),

37 changes: 31 additions & 6 deletions compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ use rustc_middle::mir::{
Terminator, TerminatorKind, UnwindAction, UnwindTerminateReason, RETURN_PLACE,
};
use rustc_middle::ty::adjustment::PointerCoercion;
use rustc_middle::ty::util::Discr;
use rustc_middle::ty::util::{AsyncDropGlueMorphology, Discr};
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::{bug, span_bug};
use rustc_span::source_map::respan;
@@ -116,15 +116,25 @@ impl<'tcx> AsyncDestructorCtorShimBuilder<'tcx> {
}

fn build(self) -> Body<'tcx> {
let (tcx, def_id, Some(self_ty)) = (self.tcx, self.def_id, self.self_ty) else {
let (tcx, Some(self_ty)) = (self.tcx, self.self_ty) else {
return self.build_zst_output();
};
match self_ty.async_drop_glue_morphology(tcx) {
AsyncDropGlueMorphology::Noop => span_bug!(
self.span,
"async drop glue shim generator encountered type with noop async drop glue morphology"
),
AsyncDropGlueMorphology::DeferredDropInPlace => {
return self.build_deferred_drop_in_place();
}
AsyncDropGlueMorphology::Custom => (),
}

let surface_drop_kind = || {
let param_env = tcx.param_env_reveal_all_normalized(def_id);
if self_ty.has_surface_async_drop(tcx, param_env) {
let adt_def = self_ty.ty_adt_def()?;
if adt_def.async_destructor(tcx).is_some() {
Some(SurfaceDropKind::Async)
} else if self_ty.has_surface_drop(tcx, param_env) {
} else if adt_def.destructor(tcx).is_some() {
Some(SurfaceDropKind::Sync)
} else {
None
@@ -267,6 +277,13 @@ impl<'tcx> AsyncDestructorCtorShimBuilder<'tcx> {
self.return_()
}

fn build_deferred_drop_in_place(mut self) -> Body<'tcx> {
self.put_self();
let deferred = self.combine_deferred_drop_in_place();
self.combine_fuse(deferred);
self.return_()
}

fn build_fused_async_surface(mut self) -> Body<'tcx> {
self.put_self();
let surface = self.combine_async_surface();
@@ -441,6 +458,14 @@ impl<'tcx> AsyncDestructorCtorShimBuilder<'tcx> {
)
}

fn combine_deferred_drop_in_place(&mut self) -> Ty<'tcx> {
self.apply_combinator(
1,
LangItem::AsyncDropDeferredDropInPlace,
&[self.self_ty.unwrap().into()],
)
}

fn combine_fuse(&mut self, inner_future_ty: Ty<'tcx>) -> Ty<'tcx> {
self.apply_combinator(1, LangItem::AsyncDropFuse, &[inner_future_ty.into()])
}
@@ -481,7 +506,7 @@ impl<'tcx> AsyncDestructorCtorShimBuilder<'tcx> {
if let Some(ty) = self.self_ty {
debug_assert_eq!(
output.ty(&self.locals, self.tcx),
ty.async_destructor_ty(self.tcx, self.param_env),
ty.async_destructor_ty(self.tcx),
"output async destructor types did not match for type: {ty:?}",
);
}
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
@@ -430,6 +430,7 @@ symbols! {
async_drop,
async_drop_chain,
async_drop_defer,
async_drop_deferred_drop_in_place,
async_drop_either,
async_drop_fuse,
async_drop_in_place,
Original file line number Diff line number Diff line change
@@ -839,7 +839,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> {
| ty::Str
| ty::Slice(_)
| ty::Tuple(_)
| ty::Error(_) => self_ty.async_destructor_ty(ecx.interner(), goal.param_env),
| ty::Error(_) => self_ty.async_destructor_ty(ecx.interner()),

// We do not call `Ty::async_destructor_ty` on alias, param, or placeholder
// types, which return `<self_ty as AsyncDestruct>::AsyncDestructor`
2 changes: 1 addition & 1 deletion compiler/rustc_trait_selection/src/traits/project.rs
Original file line number Diff line number Diff line change
@@ -1560,7 +1560,7 @@ fn confirm_builtin_candidate<'cx, 'tcx>(
let destructor_def_id = tcx.associated_item_def_ids(trait_def_id)[0];
assert_eq!(destructor_def_id, item_def_id);

(self_ty.async_destructor_ty(tcx, obligation.param_env).into(), Vec::new())
(self_ty.async_destructor_ty(tcx).into(), Vec::new())
} else if lang_items.pointee_trait() == Some(trait_def_id) {
let metadata_def_id = tcx.require_lang_item(LangItem::Metadata, None);
assert_eq!(metadata_def_id, item_def_id);
21 changes: 1 addition & 20 deletions compiler/rustc_ty_utils/src/common_traits.rs
Original file line number Diff line number Diff line change
@@ -22,17 +22,6 @@ fn is_unpin_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>)
is_item_raw(tcx, query, LangItem::Unpin)
}

fn has_surface_async_drop_raw<'tcx>(
tcx: TyCtxt<'tcx>,
query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>,
) -> bool {
is_item_raw(tcx, query, LangItem::AsyncDrop)
}

fn has_surface_drop_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
is_item_raw(tcx, query, LangItem::Drop)
}

fn is_item_raw<'tcx>(
tcx: TyCtxt<'tcx>,
query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>,
@@ -45,13 +34,5 @@ fn is_item_raw<'tcx>(
}

pub(crate) fn provide(providers: &mut Providers) {
*providers = Providers {
is_copy_raw,
is_sized_raw,
is_freeze_raw,
is_unpin_raw,
has_surface_async_drop_raw,
has_surface_drop_raw,
..*providers
};
*providers = Providers { is_copy_raw, is_sized_raw, is_freeze_raw, is_unpin_raw, ..*providers };
}
3 changes: 2 additions & 1 deletion compiler/rustc_ty_utils/src/instance.rs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::bug;
use rustc_middle::query::Providers;
use rustc_middle::traits::{BuiltinImplSource, CodegenObligationError};
use rustc_middle::ty::util::AsyncDropGlueMorphology;
use rustc_middle::ty::GenericArgsRef;
use rustc_middle::ty::{self, Instance, TyCtxt, TypeVisitableExt};
use rustc_span::sym;
@@ -59,7 +60,7 @@ fn resolve_instance<'tcx>(
} else if Some(def_id) == tcx.lang_items().async_drop_in_place_fn() {
let ty = args.type_at(0);

if !ty.is_async_destructor_noop(tcx, param_env) {
if ty.async_drop_glue_morphology(tcx) != AsyncDropGlueMorphology::Noop {
match *ty.kind() {
ty::Closure(..)
| ty::CoroutineClosure(..)
16 changes: 16 additions & 0 deletions compiler/rustc_ty_utils/src/needs_drop.rs
Original file line number Diff line number Diff line change
@@ -30,6 +30,21 @@ fn needs_drop_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, Ty<'tcx>
res
}

fn needs_async_drop_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
// If we don't know a type doesn't need async drop, for example if it's a
// type parameter without a `Copy` bound, then we conservatively return that
// it needs async drop.
let adt_has_async_dtor =
|adt_def: ty::AdtDef<'tcx>| adt_def.async_destructor(tcx).map(|_| DtorType::Significant);
let res = drop_tys_helper(tcx, query.value, query.param_env, adt_has_async_dtor, false)
.filter(filter_array_elements(tcx, query.param_env))
.next()
.is_some();

debug!("needs_drop_raw({:?}) = {:?}", query, res);
res
}

/// HACK: in order to not mistakenly assume that `[PhantomData<T>; N]` requires drop glue
/// we check the element type for drop glue. The correct fix would be looking at the
/// entirety of the code around `needs_drop_components` and this file and come up with
@@ -389,6 +404,7 @@ fn adt_significant_drop_tys(
pub(crate) fn provide(providers: &mut Providers) {
*providers = Providers {
needs_drop_raw,
needs_async_drop_raw,
has_significant_drop_raw,
adt_drop_tys,
adt_significant_drop_tys,
13 changes: 13 additions & 0 deletions library/core/src/future/async_drop.rs
Original file line number Diff line number Diff line change
@@ -161,6 +161,11 @@ async unsafe fn surface_drop_in_place<T: Drop + ?Sized>(ptr: *mut T) {
/// wrapped future completes by returning `Poll::Ready(())` on poll. This
/// is useful for constructing async destructors to guarantee this
/// "fuse" property
//
// FIXME: Consider optimizing combinators to not have to use fuse in majority
// of cases, perhaps by adding `#[(rustc_)idempotent(_future)]` attribute for
// async functions and blocks with the unit return type. However current layout
// optimizations currently encode `None` case into the async block's discriminant.
struct Fuse<T> {
inner: Option<T>,
}
@@ -251,6 +256,14 @@ async unsafe fn either<O: IntoFuture<Output = ()>, M: IntoFuture<Output = ()>, T
}
}

#[cfg(not(bootstrap))]
#[lang = "async_drop_deferred_drop_in_place"]
async unsafe fn deferred_drop_in_place<T>(to_drop: *mut T) {
// SAFETY: same safety requirements as with drop_in_place (implied by
// function's name)
unsafe { crate::ptr::drop_in_place(to_drop) }
}

/// Used for noop async destructors. We don't use [`core::future::Ready`]
/// because it panics after its second poll, which could be potentially
/// bad if that would happen during the cleanup.
5 changes: 5 additions & 0 deletions src/tools/miri/tests/pass/async-drop.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
//@revisions: stack tree
//@compile-flags: -Zmiri-strict-provenance
//@[tree]compile-flags: -Zmiri-tree-borrows

// WARNING: If you would ever want to modify this test,
// please consider modifying rustc's async drop test at
// `tests/ui/async-await/async-drop.rs`.

#![feature(async_drop, impl_trait_in_assoc_type, noop_waker, async_closure)]
#![allow(incomplete_features, dead_code)]

95 changes: 59 additions & 36 deletions tests/ui/async-await/async-drop.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
//@ run-pass
//@ check-run-results

// WARNING: If you would ever want to modify this test,
// please consider modifying miri's async drop test at
// `src/tools/miri/tests/pass/async-drop.rs`.

#![feature(async_drop, impl_trait_in_assoc_type, noop_waker, async_closure)]
#![allow(incomplete_features, dead_code)]

@@ -13,9 +17,21 @@ use core::mem::{self, ManuallyDrop};
use core::pin::{pin, Pin};
use core::task::{Context, Poll, Waker};

async fn test_async_drop<T>(x: T) {
async fn test_async_drop<T>(x: T, _size: usize) {
let mut x = mem::MaybeUninit::new(x);
let dtor = pin!(unsafe { async_drop_in_place(x.as_mut_ptr()) });

// FIXME(zetanumbers): This check fully depends on the layout of
// the coroutine state, since async destructor combinators are just
// async functions.
#[cfg(target_pointer_width = "64")]
assert_eq!(
mem::size_of_val(&*dtor),
_size,
"sizes did not match for async destructor of type {}",
core::any::type_name::<T>(),
);

test_idempotency(dtor).await;
}

@@ -36,51 +52,58 @@ fn main() {

let i = 13;
let fut = pin!(async {
test_async_drop(Int(0)).await;
test_async_drop(AsyncInt(0)).await;
test_async_drop([AsyncInt(1), AsyncInt(2)]).await;
test_async_drop((AsyncInt(3), AsyncInt(4))).await;
test_async_drop(5).await;
test_async_drop(Int(0), 0).await;
test_async_drop(AsyncInt(0), 104).await;
test_async_drop([AsyncInt(1), AsyncInt(2)], 152).await;
test_async_drop((AsyncInt(3), AsyncInt(4)), 488).await;
test_async_drop(5, 0).await;
let j = 42;
test_async_drop(&i).await;
test_async_drop(&j).await;
test_async_drop(AsyncStruct { b: AsyncInt(8), a: AsyncInt(7), i: 6 }).await;
test_async_drop(ManuallyDrop::new(AsyncInt(9))).await;
test_async_drop(&i, 0).await;
test_async_drop(&j, 0).await;
test_async_drop(AsyncStruct { b: AsyncInt(8), a: AsyncInt(7), i: 6 }, 1688).await;
test_async_drop(ManuallyDrop::new(AsyncInt(9)), 0).await;

let foo = AsyncInt(10);
test_async_drop(AsyncReference { foo: &foo }).await;
test_async_drop(AsyncReference { foo: &foo }, 104).await;

let foo = AsyncInt(11);
test_async_drop(|| {
black_box(foo);
let foo = AsyncInt(10);
foo
}).await;

test_async_drop(AsyncEnum::A(AsyncInt(12))).await;
test_async_drop(AsyncEnum::B(SyncInt(13))).await;

test_async_drop(SyncInt(14)).await;
test_async_drop(SyncThenAsync {
i: 15,
a: AsyncInt(16),
b: SyncInt(17),
c: AsyncInt(18),
}).await;
test_async_drop(
|| {
black_box(foo);
let foo = AsyncInt(10);
foo
},
120,
)
.await;

test_async_drop(AsyncEnum::A(AsyncInt(12)), 680).await;
test_async_drop(AsyncEnum::B(SyncInt(13)), 680).await;

test_async_drop(SyncInt(14), 16).await;
test_async_drop(
SyncThenAsync { i: 15, a: AsyncInt(16), b: SyncInt(17), c: AsyncInt(18) },
3064,
)
.await;

let async_drop_fut = pin!(core::future::async_drop(AsyncInt(19)));
test_idempotency(async_drop_fut).await;

let foo = AsyncInt(20);
test_async_drop(async || {
black_box(foo);
let foo = AsyncInt(19);
// Await point there, but this is async closure so it's fine
black_box(core::future::ready(())).await;
foo
}).await;

test_async_drop(AsyncUnion { signed: 21 }).await;
test_async_drop(
async || {
black_box(foo);
let foo = AsyncInt(19);
// Await point there, but this is async closure so it's fine
black_box(core::future::ready(())).await;
foo
},
120,
)
.await;

test_async_drop(AsyncUnion { signed: 21 }, 32).await;
});
let res = fut.poll(&mut cx);
assert_eq!(res, Poll::Ready(()));