Add simple async drop glue generation

Explainer: https://zetanumbers.github.io/book/async-drop-design.html

https://github.com/rust-lang/rust/pull/121801
This commit is contained in:
zetanumbers 2024-02-13 12:31:41 +03:00 committed by Daria Sukhonina
parent 1dea922ea6
commit 24a24ec6ba
40 changed files with 1921 additions and 20 deletions

View File

@ -363,6 +363,24 @@ fn exported_symbols_provider_local(
},
));
}
MonoItem::Fn(Instance {
def: InstanceDef::AsyncDropGlueCtorShim(def_id, ty),
args,
}) => {
// A little sanity-check
debug_assert_eq!(
args.non_erasable_generics(tcx, def_id).skip(1).next(),
Some(GenericArgKind::Type(ty))
);
symbols.push((
ExportedSymbol::AsyncDropGlueCtorShim(ty),
SymbolExportInfo {
level: SymbolExportLevel::Rust,
kind: SymbolExportKind::Text,
used: false,
},
));
}
_ => {
// Any other symbols don't qualify for sharing
}
@ -385,6 +403,7 @@ fn upstream_monomorphizations_provider(
let mut instances: DefIdMap<UnordMap<_, _>> = Default::default();
let drop_in_place_fn_def_id = tcx.lang_items().drop_in_place_fn();
let async_drop_in_place_fn_def_id = tcx.lang_items().async_drop_in_place_fn();
for &cnum in cnums.iter() {
for (exported_symbol, _) in tcx.exported_symbols(cnum).iter() {
@ -399,6 +418,18 @@ fn upstream_monomorphizations_provider(
continue;
}
}
ExportedSymbol::AsyncDropGlueCtorShim(ty) => {
if let Some(async_drop_in_place_fn_def_id) = async_drop_in_place_fn_def_id {
(
async_drop_in_place_fn_def_id,
tcx.mk_args(&[tcx.lifetimes.re_erased.into(), ty.into()]),
)
} else {
// `drop_in_place` in place does not exist, don't try
// to use it.
continue;
}
}
ExportedSymbol::NonGeneric(..)
| ExportedSymbol::ThreadLocalShim(..)
| ExportedSymbol::NoDefId(..) => {
@ -534,6 +565,13 @@ pub fn symbol_name_for_instance_in_crate<'tcx>(
Instance::resolve_drop_in_place(tcx, ty),
instantiating_crate,
),
ExportedSymbol::AsyncDropGlueCtorShim(ty) => {
rustc_symbol_mangling::symbol_name_for_instance_in_crate(
tcx,
Instance::resolve_async_drop_in_place(tcx, ty),
instantiating_crate,
)
}
ExportedSymbol::NoDefId(symbol_name) => symbol_name.to_string(),
}
}
@ -582,6 +620,9 @@ pub fn linking_symbol_name_for_instance_in_crate<'tcx>(
// DropGlue always use the Rust calling convention and thus follow the target's default
// symbol decoration scheme.
ExportedSymbol::DropGlue(..) => None,
// AsyncDropGlueCtorShim always use the Rust calling convention and thus follow the
// target's default symbol decoration scheme.
ExportedSymbol::AsyncDropGlueCtorShim(..) => None,
// NoDefId always follow the target's default symbol decoration scheme.
ExportedSymbol::NoDefId(..) => None,
// ThreadLocalShim always follow the target's default symbol decoration scheme.

View File

@ -558,6 +558,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
| ty::InstanceDef::CloneShim(..)
| ty::InstanceDef::FnPtrAddrShim(..)
| ty::InstanceDef::ThreadLocalShim(..)
| ty::InstanceDef::AsyncDropGlueCtorShim(..)
| ty::InstanceDef::Item(_) => {
// We need MIR for this fn
let Some((body, instance)) = M::find_mir_or_eval_fn(

View File

@ -162,6 +162,18 @@ language_item_table! {
Drop, sym::drop, drop_trait, Target::Trait, GenericRequirement::None;
Destruct, sym::destruct, destruct_trait, Target::Trait, GenericRequirement::None;
AsyncDrop, sym::async_drop, async_drop_trait, Target::Trait, GenericRequirement::Exact(0);
AsyncDestruct, sym::async_destruct, async_destruct_trait, Target::Trait, GenericRequirement::Exact(0);
AsyncDropInPlace, sym::async_drop_in_place, async_drop_in_place_fn, Target::Fn, GenericRequirement::Exact(1);
SurfaceAsyncDropInPlace, sym::surface_async_drop_in_place, surface_async_drop_in_place_fn, Target::Fn, GenericRequirement::Exact(1);
AsyncDropSurfaceDropInPlace, sym::async_drop_surface_drop_in_place, async_drop_surface_drop_in_place_fn, Target::Fn, GenericRequirement::Exact(1);
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);
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);
CoerceUnsized, sym::coerce_unsized, coerce_unsized_trait, Target::Trait, GenericRequirement::Minimum(1);
DispatchFromDyn, sym::dispatch_from_dyn, dispatch_from_dyn_trait, Target::Trait, GenericRequirement::Minimum(1);
@ -281,6 +293,7 @@ language_item_table! {
ExchangeMalloc, sym::exchange_malloc, exchange_malloc_fn, Target::Fn, GenericRequirement::None;
DropInPlace, sym::drop_in_place, drop_in_place_fn, Target::Fn, GenericRequirement::Minimum(1);
FallbackSurfaceDrop, sym::fallback_surface_drop, fallback_surface_drop_fn, Target::Fn, GenericRequirement::None;
AllocLayout, sym::alloc_layout, alloc_layout, Target::Struct, GenericRequirement::None;
Start, sym::start, start_fn, Target::Fn, GenericRequirement::Exact(1);

View File

@ -38,8 +38,11 @@ pub fn check_legal_trait_for_method_call(
receiver: Option<Span>,
expr_span: Span,
trait_id: DefId,
body_id: DefId,
) -> Result<(), ErrorGuaranteed> {
if tcx.lang_items().drop_trait() == Some(trait_id) {
if tcx.lang_items().drop_trait() == Some(trait_id)
&& tcx.lang_items().fallback_surface_drop_fn() != Some(body_id)
{
let sugg = if let Some(receiver) = receiver.filter(|s| !s.is_empty()) {
errors::ExplicitDestructorCallSugg::Snippet {
lo: expr_span.shrink_to_lo(),

View File

@ -1118,6 +1118,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
None,
span,
container_id,
self.body_id.to_def_id(),
) {
self.set_tainted_by_errors(e);
}

View File

@ -628,6 +628,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> {
Some(self.self_expr.span),
self.call_expr.span,
trait_def_id,
self.body_id.to_def_id(),
) {
self.set_tainted_by_errors(e);
}

View File

@ -43,6 +43,7 @@ pub enum ExportedSymbol<'tcx> {
NonGeneric(DefId),
Generic(DefId, GenericArgsRef<'tcx>),
DropGlue(Ty<'tcx>),
AsyncDropGlueCtorShim(Ty<'tcx>),
ThreadLocalShim(DefId),
NoDefId(ty::SymbolName<'tcx>),
}
@ -59,6 +60,9 @@ impl<'tcx> ExportedSymbol<'tcx> {
ExportedSymbol::DropGlue(ty) => {
tcx.symbol_name(ty::Instance::resolve_drop_in_place(tcx, ty))
}
ExportedSymbol::AsyncDropGlueCtorShim(ty) => {
tcx.symbol_name(ty::Instance::resolve_async_drop_in_place(tcx, ty))
}
ExportedSymbol::ThreadLocalShim(def_id) => tcx.symbol_name(ty::Instance {
def: ty::InstanceDef::ThreadLocalShim(def_id),
args: ty::GenericArgs::empty(),

View File

@ -406,7 +406,8 @@ impl<'tcx> CodegenUnit<'tcx> {
| InstanceDef::DropGlue(..)
| InstanceDef::CloneShim(..)
| InstanceDef::ThreadLocalShim(..)
| InstanceDef::FnPtrAddrShim(..) => None,
| InstanceDef::FnPtrAddrShim(..)
| InstanceDef::AsyncDropGlueCtorShim(..) => None,
}
}
MonoItem::Static(def_id) => def_id.as_local().map(Idx::index),

View File

@ -187,6 +187,17 @@ fn dump_path<'tcx>(
}));
s
}
ty::InstanceDef::AsyncDropGlueCtorShim(_, ty) => {
// Unfortunately, pretty-printed typed are not very filename-friendly.
// We dome some filtering.
let mut s = ".".to_owned();
s.extend(ty.to_string().chars().filter_map(|c| match c {
' ' => None,
':' | '<' | '>' => Some('_'),
c => Some(c),
}));
s
}
_ => String::new(),
};

View File

@ -355,7 +355,8 @@ macro_rules! make_mir_visitor {
ty::InstanceDef::FnPtrShim(_def_id, ty) |
ty::InstanceDef::DropGlue(_def_id, Some(ty)) |
ty::InstanceDef::CloneShim(_def_id, ty) |
ty::InstanceDef::FnPtrAddrShim(_def_id, ty) => {
ty::InstanceDef::FnPtrAddrShim(_def_id, ty) |
ty::InstanceDef::AsyncDropGlueCtorShim(_def_id, ty) => {
// FIXME(eddyb) use a better `TyContext` here.
self.visit_ty($(& $mutability)? *ty, TyContext::Location(location));
}

View File

@ -1344,6 +1344,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 }

View File

@ -168,6 +168,12 @@ pub enum InstanceDef<'tcx> {
///
/// The `DefId` is for `FnPtr::addr`, the `Ty` is the type `T`.
FnPtrAddrShim(DefId, Ty<'tcx>),
/// `core::future::async_drop::async_drop_in_place::<'_, T>`.
///
/// The `DefId` is for `core::future::async_drop::async_drop_in_place`, the `Ty`
/// is the type `T`.
AsyncDropGlueCtorShim(DefId, Ty<'tcx>),
}
impl<'tcx> Instance<'tcx> {
@ -210,7 +216,9 @@ impl<'tcx> Instance<'tcx> {
InstanceDef::Item(def) => tcx
.upstream_monomorphizations_for(def)
.and_then(|monos| monos.get(&self.args).cloned()),
InstanceDef::DropGlue(_, Some(_)) => tcx.upstream_drop_glue_for(self.args),
InstanceDef::DropGlue(_, Some(_)) | InstanceDef::AsyncDropGlueCtorShim(_, _) => {
tcx.upstream_drop_glue_for(self.args)
}
_ => None,
}
}
@ -235,7 +243,8 @@ impl<'tcx> InstanceDef<'tcx> {
| ty::InstanceDef::CoroutineKindShim { coroutine_def_id: def_id }
| InstanceDef::DropGlue(def_id, _)
| InstanceDef::CloneShim(def_id, _)
| InstanceDef::FnPtrAddrShim(def_id, _) => def_id,
| InstanceDef::FnPtrAddrShim(def_id, _)
| InstanceDef::AsyncDropGlueCtorShim(def_id, _) => def_id,
}
}
@ -243,9 +252,9 @@ impl<'tcx> InstanceDef<'tcx> {
pub fn def_id_if_not_guaranteed_local_codegen(self) -> Option<DefId> {
match self {
ty::InstanceDef::Item(def) => Some(def),
ty::InstanceDef::DropGlue(def_id, Some(_)) | InstanceDef::ThreadLocalShim(def_id) => {
Some(def_id)
}
ty::InstanceDef::DropGlue(def_id, Some(_))
| InstanceDef::AsyncDropGlueCtorShim(def_id, _)
| InstanceDef::ThreadLocalShim(def_id) => Some(def_id),
InstanceDef::VTableShim(..)
| InstanceDef::ReifyShim(..)
| InstanceDef::FnPtrShim(..)
@ -347,7 +356,8 @@ impl<'tcx> InstanceDef<'tcx> {
| InstanceDef::ThreadLocalShim(..)
| InstanceDef::FnPtrAddrShim(..)
| InstanceDef::FnPtrShim(..)
| InstanceDef::DropGlue(_, Some(_)) => false,
| InstanceDef::DropGlue(_, Some(_))
| InstanceDef::AsyncDropGlueCtorShim(..) => false,
InstanceDef::ClosureOnceShim { .. }
| InstanceDef::ConstructCoroutineInClosureShim { .. }
| InstanceDef::CoroutineKindShim { .. }
@ -396,6 +406,7 @@ fn fmt_instance(
InstanceDef::DropGlue(_, Some(ty)) => write!(f, " - shim(Some({ty}))"),
InstanceDef::CloneShim(_, ty) => write!(f, " - shim({ty})"),
InstanceDef::FnPtrAddrShim(_, ty) => write!(f, " - shim({ty})"),
InstanceDef::AsyncDropGlueCtorShim(_, ty) => write!(f, " - shim({ty})"),
}
}
@ -638,6 +649,12 @@ impl<'tcx> Instance<'tcx> {
Instance::expect_resolve(tcx, ty::ParamEnv::reveal_all(), def_id, args)
}
pub fn resolve_async_drop_in_place(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> ty::Instance<'tcx> {
let def_id = tcx.require_lang_item(LangItem::AsyncDropInPlace, None);
let args = tcx.mk_args(&[ty.into()]);
Instance::expect_resolve(tcx, ty::ParamEnv::reveal_all(), def_id, args)
}
#[instrument(level = "debug", skip(tcx), ret)]
pub fn fn_once_adapter_instance(
tcx: TyCtxt<'tcx>,

View File

@ -1797,7 +1797,8 @@ impl<'tcx> TyCtxt<'tcx> {
| ty::InstanceDef::DropGlue(..)
| ty::InstanceDef::CloneShim(..)
| ty::InstanceDef::ThreadLocalShim(..)
| ty::InstanceDef::FnPtrAddrShim(..) => self.mir_shims(instance),
| ty::InstanceDef::FnPtrAddrShim(..)
| ty::InstanceDef::AsyncDropGlueCtorShim(..) => self.mir_shims(instance),
}
}

View File

@ -24,6 +24,7 @@ use rustc_target::abi::{FieldIdx, VariantIdx, FIRST_VARIANT};
use rustc_target::spec::abi::{self, Abi};
use std::assert_matches::debug_assert_matches;
use std::borrow::Cow;
use std::iter;
use std::ops::{ControlFlow, Deref, Range};
use ty::util::IntTypeExt;
@ -2316,6 +2317,152 @@ 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> {
match *self.kind() {
ty::Param(_) | ty::Alias(..) | ty::Infer(ty::TyVar(_)) => {
let assoc_items = tcx
.associated_item_def_ids(tcx.require_lang_item(LangItem::AsyncDestruct, None));
Ty::new_projection(tcx, assoc_items[0], [self])
}
ty::Array(elem_ty, _) | ty::Slice(elem_ty) => {
let dtor = Ty::async_destructor_combinator(tcx, LangItem::AsyncDropSlice)
.instantiate(tcx, &[elem_ty.into()]);
Ty::async_destructor_combinator(tcx, LangItem::AsyncDropFuse)
.instantiate(tcx, &[dtor.into()])
}
ty::Adt(adt_def, _) if adt_def.is_manually_drop() => {
Ty::async_destructor_combinator(tcx, LangItem::AsyncDropNoop).instantiate_identity()
}
ty::Adt(adt_def, args) if adt_def.is_enum() || adt_def.is_struct() => self
.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::Adt(adt_def, _) => {
assert!(adt_def.is_union());
match self.surface_async_dropper_ty(tcx, param_env) {
None => Ty::async_destructor_combinator(tcx, LangItem::AsyncDropNoop)
.instantiate_identity(),
Some(surface_drop) => {
Ty::async_destructor_combinator(tcx, LangItem::AsyncDropFuse)
.instantiate(tcx, &[surface_drop.into()])
}
}
}
ty::Never
| ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Str
| ty::RawPtr(_, _)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::Infer(ty::IntVar(_) | ty::FloatVar(_))
| ty::Error(_) => {
Ty::async_destructor_combinator(tcx, LangItem::AsyncDropNoop).instantiate_identity()
}
ty::Dynamic(..) | ty::CoroutineWitness(..) | ty::Coroutine(..) | ty::Pat(..) => {
bug!("`async_destructor_ty` is not yet implemented for type: {self:?}")
}
ty::Bound(..)
| ty::Foreign(_)
| ty::Placeholder(_)
| ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
bug!("`async_destructor_ty` applied to unexpected type: {self:?}")
}
}
}
fn adt_async_destructor_ty<I>(
self,
tcx: TyCtxt<'tcx>,
variants: I,
param_env: ParamEnv<'tcx>,
) -> Ty<'tcx>
where
I: Iterator + ExactSizeIterator,
I::Item: IntoIterator<Item = Ty<'tcx>>,
{
let defer = Ty::async_destructor_combinator(tcx, LangItem::AsyncDropDefer);
let chain = Ty::async_destructor_combinator(tcx, LangItem::AsyncDropChain);
let noop =
Ty::async_destructor_combinator(tcx, LangItem::AsyncDropNoop).instantiate_identity();
let either = Ty::async_destructor_combinator(tcx, LangItem::AsyncDropEither);
let variants_dtor = variants
.into_iter()
.map(|variant| {
variant
.into_iter()
.map(|ty| defer.instantiate(tcx, &[ty.into()]))
.reduce(|acc, next| chain.instantiate(tcx, &[acc.into(), next.into()]))
.unwrap_or(noop)
})
.reduce(|other, matched| {
either.instantiate(tcx, &[other.into(), matched.into(), self.into()])
})
.unwrap_or(noop);
let dtor = if let Some(dropper_ty) = self.surface_async_dropper_ty(tcx, param_env) {
Ty::async_destructor_combinator(tcx, LangItem::AsyncDropChain)
.instantiate(tcx, &[dropper_ty.into(), variants_dtor.into()])
} else {
variants_dtor
};
Ty::async_destructor_combinator(tcx, LangItem::AsyncDropFuse)
.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 async_destructor_combinator(
tcx: TyCtxt<'tcx>,
lang_item: LangItem,
) -> ty::EarlyBinder<Ty<'tcx>> {
tcx.fn_sig(tcx.require_lang_item(lang_item, None))
.map_bound(|fn_sig| fn_sig.output().no_bound_vars().unwrap())
}
/// Returns the type of metadata for (potentially fat) pointers to this type,
/// or the struct tail if the metadata type cannot be determined.
pub fn ptr_metadata_ty_or_tail(

View File

@ -1303,6 +1303,97 @@ 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.trivially_has_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 trivially_has_surface_async_drop(self) -> bool {
match self.kind() {
ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Bool
| ty::Char
| ty::Str
| ty::Never
| ty::Ref(..)
| ty::RawPtr(_, _)
| ty::FnDef(..)
| ty::FnPtr(_)
| ty::Error(_)
| ty::Tuple(_)
| ty::Slice(_)
| ty::Array(_, _)
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::Coroutine(..)
| ty::CoroutineWitness(..)
| ty::Pat(..) => false,
ty::Adt(..)
| ty::Bound(..)
| ty::Dynamic(..)
| ty::Foreign(_)
| ty::Infer(_)
| ty::Alias(..)
| ty::Param(_)
| ty::Placeholder(_) => true,
}
}
/// Checks whether values of this type `T` implements the `AsyncDrop`
/// trait.
pub fn has_surface_drop(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> bool {
self.trivially_has_surface_drop() && tcx.has_surface_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 trivially_has_surface_drop(self) -> bool {
match self.kind() {
ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Bool
| ty::Char
| ty::Str
| ty::Never
| ty::Ref(..)
| ty::RawPtr(_, _)
| ty::FnDef(..)
| ty::FnPtr(_)
| ty::Error(_)
| ty::Tuple(_)
| ty::Slice(_)
| ty::Array(_, _)
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::Coroutine(..)
| ty::CoroutineWitness(..)
| ty::Pat(..) => false,
ty::Adt(..)
| ty::Bound(..)
| ty::Dynamic(..)
| ty::Foreign(_)
| ty::Infer(_)
| ty::Alias(..)
| ty::Param(_)
| ty::Placeholder(_) => true,
}
}
/// If `ty.needs_drop(...)` returns `true`, then `ty` is definitely
/// non-copy and *might* have a destructor attached; if it returns
/// `false`, then `ty` definitely has no destructor (i.e., no drop glue).

View File

@ -332,7 +332,8 @@ impl<'tcx> Inliner<'tcx> {
| InstanceDef::DropGlue(..)
| InstanceDef::CloneShim(..)
| InstanceDef::ThreadLocalShim(..)
| InstanceDef::FnPtrAddrShim(..) => return Ok(()),
| InstanceDef::FnPtrAddrShim(..)
| InstanceDef::AsyncDropGlueCtorShim(..) => return Ok(()),
}
if self.tcx.is_constructor(callee_def_id) {
@ -1071,7 +1072,8 @@ fn try_instance_mir<'tcx>(
tcx: TyCtxt<'tcx>,
instance: InstanceDef<'tcx>,
) -> Result<&'tcx Body<'tcx>, &'static str> {
if let ty::InstanceDef::DropGlue(_, Some(ty)) = instance
if let ty::InstanceDef::DropGlue(_, Some(ty)) | ty::InstanceDef::AsyncDropGlueCtorShim(_, ty) =
instance
&& let ty::Adt(def, args) = ty.kind()
{
let fields = def.all_fields();

View File

@ -94,8 +94,10 @@ pub(crate) fn mir_callgraph_reachable<'tcx>(
| InstanceDef::CloneShim(..) => {}
// This shim does not call any other functions, thus there can be no recursion.
InstanceDef::FnPtrAddrShim(..) => continue,
InstanceDef::DropGlue(..) => {
InstanceDef::FnPtrAddrShim(..) => {
continue;
}
InstanceDef::DropGlue(..) | InstanceDef::AsyncDropGlueCtorShim(..) => {
// FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to
// have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this
// needs some more analysis.

View File

@ -22,6 +22,8 @@ use crate::{
use rustc_middle::mir::patch::MirPatch;
use rustc_mir_dataflow::elaborate_drops::{self, DropElaborator, DropFlagMode, DropStyle};
mod async_destructor_ctor;
pub fn provide(providers: &mut Providers) {
providers.mir_shims = make_shim;
}
@ -127,6 +129,9 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> Body<'
ty::InstanceDef::ThreadLocalShim(..) => build_thread_local_shim(tcx, instance),
ty::InstanceDef::CloneShim(def_id, ty) => build_clone_shim(tcx, def_id, ty),
ty::InstanceDef::FnPtrAddrShim(def_id, ty) => build_fn_ptr_addr_shim(tcx, def_id, ty),
ty::InstanceDef::AsyncDropGlueCtorShim(def_id, ty) => {
async_destructor_ctor::build_async_destructor_ctor_shim(tcx, def_id, ty)
}
ty::InstanceDef::Virtual(..) => {
bug!("InstanceDef::Virtual ({:?}) is for direct calls only", instance)
}

View File

@ -0,0 +1,612 @@
use std::iter;
use itertools::Itertools;
use rustc_ast::Mutability;
use rustc_const_eval::interpret;
use rustc_hir::def_id::DefId;
use rustc_hir::lang_items::LangItem;
use rustc_index::{Idx, IndexVec};
use rustc_middle::mir::{
BasicBlock, BasicBlockData, Body, CallSource, CastKind, Const, ConstOperand, ConstValue, Local,
LocalDecl, MirSource, Operand, Place, PlaceElem, Rvalue, SourceInfo, Statement, StatementKind,
Terminator, TerminatorKind, UnwindAction, UnwindTerminateReason,
};
use rustc_middle::ty::adjustment::PointerCoercion;
use rustc_middle::ty::util::Discr;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_span::source_map::respan;
use rustc_span::{Span, Symbol};
use rustc_target::abi::{FieldIdx, VariantIdx};
use rustc_target::spec::PanicStrategy;
use super::{local_decls_for_sig, new_body};
pub fn build_async_destructor_ctor_shim<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
self_ty: Ty<'tcx>,
) -> Body<'tcx> {
AsyncDestructorCtorShimBuilder::new(tcx, def_id, self_ty).build()
}
/// Builder for async_drop_in_place shim. Functions as a stack machine
/// to build up an expression using combinators. Stack contains pairs
/// of locals and types. Combinator is a not yet instantiated pair of a
/// function and a type, is considered to be an operator which consumes
/// operands from the stack by instantiating its function and its type
/// with operand types and moving locals into the function call. Top
/// pair is considered to be the last operand.
// FIXME: add mir-opt tests
struct AsyncDestructorCtorShimBuilder<'tcx> {
tcx: TyCtxt<'tcx>,
def_id: DefId,
self_ty: Ty<'tcx>,
span: Span,
source_info: SourceInfo,
param_env: ty::ParamEnv<'tcx>,
stack: Vec<Operand<'tcx>>,
last_bb: BasicBlock,
top_cleanup_bb: Option<BasicBlock>,
locals: IndexVec<Local, LocalDecl<'tcx>>,
bbs: IndexVec<BasicBlock, BasicBlockData<'tcx>>,
}
#[derive(Clone, Copy)]
enum SurfaceDropKind {
Async,
Sync,
}
impl<'tcx> AsyncDestructorCtorShimBuilder<'tcx> {
const SELF_PTR: Local = Local::from_u32(1);
const INPUT_COUNT: usize = 1;
const MAX_STACK_LEN: usize = 2;
fn new(tcx: TyCtxt<'tcx>, def_id: DefId, self_ty: Ty<'tcx>) -> Self {
let span = tcx.def_span(def_id);
let Some(sig) = tcx.fn_sig(def_id).instantiate(tcx, &[self_ty.into()]).no_bound_vars()
else {
span_bug!(span, "async_drop_in_place_raw with bound vars for `{self_ty}`");
};
let source_info = SourceInfo::outermost(span);
debug_assert_eq!(sig.inputs().len(), Self::INPUT_COUNT);
let locals = local_decls_for_sig(&sig, span);
// Usual case: noop() + unwind resume + return
let mut bbs = IndexVec::with_capacity(3);
let param_env = tcx.param_env_reveal_all_normalized(def_id);
AsyncDestructorCtorShimBuilder {
tcx,
def_id,
self_ty,
span,
source_info,
param_env,
stack: Vec::with_capacity(Self::MAX_STACK_LEN),
last_bb: bbs.push(BasicBlockData::new(None)),
top_cleanup_bb: match tcx.sess.panic_strategy() {
PanicStrategy::Unwind => {
// Don't drop input arg because it's just a pointer
Some(bbs.push(BasicBlockData {
statements: Vec::new(),
terminator: Some(Terminator {
source_info,
kind: TerminatorKind::UnwindResume,
}),
is_cleanup: true,
}))
}
PanicStrategy::Abort => None,
},
locals,
bbs,
}
}
fn build(self) -> Body<'tcx> {
let (tcx, def_id, self_ty) = (self.tcx, self.def_id, self.self_ty);
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) {
Some(SurfaceDropKind::Async)
} else if self_ty.has_surface_drop(tcx, param_env) {
Some(SurfaceDropKind::Sync)
} else {
None
}
};
match self_ty.kind() {
ty::Array(elem_ty, _) => self.build_slice(true, *elem_ty),
ty::Slice(elem_ty) => self.build_slice(false, *elem_ty),
ty::Adt(adt_def, _) if adt_def.is_manually_drop() => self.build_noop(),
ty::Tuple(elem_tys) => self.build_chain(None, elem_tys.iter()),
ty::Adt(adt_def, args) if adt_def.is_struct() => {
let field_tys = adt_def.non_enum_variant().fields.iter().map(|f| f.ty(tcx, args));
self.build_chain(surface_drop_kind(), field_tys)
}
ty::Closure(_, args) => self.build_chain(None, args.as_closure().upvar_tys().iter()),
ty::CoroutineClosure(_, args) => {
self.build_chain(None, args.as_coroutine_closure().upvar_tys().iter())
}
ty::Adt(adt_def, args) if adt_def.is_enum() => {
self.build_enum(*adt_def, *args, surface_drop_kind())
}
ty::Never
| ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Str
| ty::RawPtr(_, _)
| ty::Ref(_, _, _)
| ty::FnDef(_, _)
| ty::FnPtr(_)
| ty::Infer(ty::IntVar(_) | ty::FloatVar(_))
| ty::Error(_) => self.build_noop(),
ty::Adt(adt_def, _) => {
assert!(adt_def.is_union());
match surface_drop_kind() {
Some(SurfaceDropKind::Async) => self.build_fused_async_surface(),
Some(SurfaceDropKind::Sync) => self.build_fused_sync_surface(),
None => self.build_noop(),
}
}
ty::Dynamic(..) | ty::CoroutineWitness(..) | ty::Coroutine(..) | ty::Pat(..) => {
bug!(
"Building async destructor constructor shim is not yet implemented for type: {self_ty:?}"
)
}
ty::Bound(..)
| ty::Foreign(_)
| ty::Placeholder(_)
| ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_) | ty::TyVar(_))
| ty::Param(_)
| ty::Alias(..) => {
bug!("Building async destructor for unexpected type: {self_ty:?}")
}
}
}
fn build_enum(
mut self,
adt_def: ty::AdtDef<'tcx>,
args: ty::GenericArgsRef<'tcx>,
surface_drop: Option<SurfaceDropKind>,
) -> Body<'tcx> {
let tcx = self.tcx;
let surface = match surface_drop {
None => None,
Some(kind) => {
self.put_self();
Some(match kind {
SurfaceDropKind::Async => self.combine_async_surface(),
SurfaceDropKind::Sync => self.combine_sync_surface(),
})
}
};
let mut other = None;
for (variant_idx, discr) in adt_def.discriminants(tcx) {
let variant = adt_def.variant(variant_idx);
let mut chain = None;
for (field_idx, field) in variant.fields.iter_enumerated() {
let field_ty = field.ty(tcx, args);
self.put_variant_field(variant.name, variant_idx, field_idx, field_ty);
let defer = self.combine_defer(field_ty);
chain = Some(match chain {
None => defer,
Some(chain) => self.combine_chain(chain, defer),
})
}
let variant_dtor = chain.unwrap_or_else(|| self.put_noop());
other = Some(match other {
None => variant_dtor,
Some(other) => {
self.put_self();
self.put_discr(discr);
self.combine_either(other, variant_dtor)
}
});
}
let variants_dtor = other.unwrap_or_else(|| self.put_noop());
let dtor = match surface {
None => variants_dtor,
Some(surface) => self.combine_chain(surface, variants_dtor),
};
self.combine_fuse(dtor);
self.return_()
}
fn build_chain<I>(mut self, surface_drop: Option<SurfaceDropKind>, elem_tys: I) -> Body<'tcx>
where
I: Iterator<Item = Ty<'tcx>> + ExactSizeIterator,
{
let surface = match surface_drop {
None => None,
Some(kind) => {
self.put_self();
Some(match kind {
SurfaceDropKind::Async => self.combine_async_surface(),
SurfaceDropKind::Sync => self.combine_sync_surface(),
})
}
};
let mut chain = None;
for (field_idx, field_ty) in elem_tys.enumerate().map(|(i, ty)| (FieldIdx::new(i), ty)) {
self.put_field(field_idx, field_ty);
let defer = self.combine_defer(field_ty);
chain = Some(match chain {
None => defer,
Some(chain) => self.combine_chain(chain, defer),
})
}
let chain = chain.unwrap_or_else(|| self.put_noop());
let dtor = match surface {
None => chain,
Some(surface) => self.combine_chain(surface, chain),
};
self.combine_fuse(dtor);
self.return_()
}
fn build_noop(mut self) -> Body<'tcx> {
self.put_noop();
self.return_()
}
fn build_fused_async_surface(mut self) -> Body<'tcx> {
self.put_self();
let surface = self.combine_async_surface();
self.combine_fuse(surface);
self.return_()
}
fn build_fused_sync_surface(mut self) -> Body<'tcx> {
self.put_self();
let surface = self.combine_sync_surface();
self.combine_fuse(surface);
self.return_()
}
fn build_slice(mut self, is_array: bool, elem_ty: Ty<'tcx>) -> Body<'tcx> {
if is_array {
self.put_array_as_slice(elem_ty)
} else {
self.put_self()
}
let dtor = self.combine_slice(elem_ty);
self.combine_fuse(dtor);
self.return_()
}
/// Puts `to_drop: *mut Self` on top of the stack.
fn put_self(&mut self) {
self.put_operand(Operand::Copy(Self::SELF_PTR.into()))
}
/// Given that `Self is [ElemTy; N]` puts `to_drop: *mut [ElemTy]`
/// on top of the stack.
fn put_array_as_slice(&mut self, elem_ty: Ty<'tcx>) {
let slice_ptr_ty = Ty::new_mut_ptr(self.tcx, Ty::new_slice(self.tcx, elem_ty));
self.put_temp_rvalue(Rvalue::Cast(
CastKind::PointerCoercion(PointerCoercion::Unsize),
Operand::Copy(Self::SELF_PTR.into()),
slice_ptr_ty,
))
}
/// If given Self is a struct puts `to_drop: *mut FieldTy` on top
/// of the stack.
fn put_field(&mut self, field: FieldIdx, field_ty: Ty<'tcx>) {
let place = Place {
local: Self::SELF_PTR,
projection: self
.tcx
.mk_place_elems(&[PlaceElem::Deref, PlaceElem::Field(field, field_ty)]),
};
self.put_temp_rvalue(Rvalue::AddressOf(Mutability::Mut, place))
}
/// If given Self is an enum puts `to_drop: *mut FieldTy` on top of
/// the stack.
fn put_variant_field(
&mut self,
variant_sym: Symbol,
variant: VariantIdx,
field: FieldIdx,
field_ty: Ty<'tcx>,
) {
let place = Place {
local: Self::SELF_PTR,
projection: self.tcx.mk_place_elems(&[
PlaceElem::Deref,
PlaceElem::Downcast(Some(variant_sym), variant),
PlaceElem::Field(field, field_ty),
]),
};
self.put_temp_rvalue(Rvalue::AddressOf(Mutability::Mut, place))
}
/// If given Self is an enum puts `to_drop: *mut FieldTy` on top of
/// the stack.
fn put_discr(&mut self, discr: Discr<'tcx>) {
let (size, _) = discr.ty.int_size_and_signed(self.tcx);
self.put_operand(Operand::const_from_scalar(
self.tcx,
discr.ty,
interpret::Scalar::from_uint(discr.val, size),
self.span,
));
}
/// Puts `x: RvalueType` on top of the stack.
fn put_temp_rvalue(&mut self, rvalue: Rvalue<'tcx>) {
let last_bb = &mut self.bbs[self.last_bb];
debug_assert!(last_bb.terminator.is_none());
let source_info = self.source_info;
let local_ty = rvalue.ty(&self.locals, self.tcx);
// We need to create a new local to be able to "consume" it with
// a combinator
let local = self.locals.push(LocalDecl::with_source_info(local_ty, source_info));
last_bb.statements.extend_from_slice(&[
Statement { source_info, kind: StatementKind::StorageLive(local) },
Statement {
source_info,
kind: StatementKind::Assign(Box::new((local.into(), rvalue))),
},
]);
self.put_operand(Operand::Move(local.into()));
}
/// Puts operand on top of the stack.
fn put_operand(&mut self, operand: Operand<'tcx>) {
if let Some(top_cleanup_bb) = &mut self.top_cleanup_bb {
let source_info = self.source_info;
match &operand {
Operand::Copy(_) | Operand::Constant(_) => {
*top_cleanup_bb = self.bbs.push(BasicBlockData {
statements: Vec::new(),
terminator: Some(Terminator {
source_info,
kind: TerminatorKind::Goto { target: *top_cleanup_bb },
}),
is_cleanup: true,
});
}
Operand::Move(place) => {
let local = place.as_local().unwrap();
*top_cleanup_bb = self.bbs.push(BasicBlockData {
statements: Vec::new(),
terminator: Some(Terminator {
source_info,
kind: if self.locals[local].ty.needs_drop(self.tcx, self.param_env) {
TerminatorKind::Drop {
place: local.into(),
target: *top_cleanup_bb,
unwind: UnwindAction::Terminate(
UnwindTerminateReason::InCleanup,
),
replace: false,
}
} else {
TerminatorKind::Goto { target: *top_cleanup_bb }
},
}),
is_cleanup: true,
});
}
};
}
self.stack.push(operand);
}
/// Puts `noop: async_drop::Noop` on top of the stack
fn put_noop(&mut self) -> Ty<'tcx> {
self.apply_combinator(0, LangItem::AsyncDropNoop, &[])
}
fn combine_async_surface(&mut self) -> Ty<'tcx> {
self.apply_combinator(1, LangItem::SurfaceAsyncDropInPlace, &[self.self_ty.into()])
}
fn combine_sync_surface(&mut self) -> Ty<'tcx> {
self.apply_combinator(1, LangItem::AsyncDropSurfaceDropInPlace, &[self.self_ty.into()])
}
fn combine_fuse(&mut self, inner_future_ty: Ty<'tcx>) -> Ty<'tcx> {
self.apply_combinator(1, LangItem::AsyncDropFuse, &[inner_future_ty.into()])
}
fn combine_slice(&mut self, elem_ty: Ty<'tcx>) -> Ty<'tcx> {
self.apply_combinator(1, LangItem::AsyncDropSlice, &[elem_ty.into()])
}
fn combine_defer(&mut self, to_drop_ty: Ty<'tcx>) -> Ty<'tcx> {
self.apply_combinator(1, LangItem::AsyncDropDefer, &[to_drop_ty.into()])
}
fn combine_chain(&mut self, first: Ty<'tcx>, second: Ty<'tcx>) -> Ty<'tcx> {
self.apply_combinator(2, LangItem::AsyncDropChain, &[first.into(), second.into()])
}
fn combine_either(&mut self, other: Ty<'tcx>, matched: Ty<'tcx>) -> Ty<'tcx> {
self.apply_combinator(
4,
LangItem::AsyncDropEither,
&[other.into(), matched.into(), self.self_ty.into()],
)
}
fn return_(mut self) -> Body<'tcx> {
let last_bb = &mut self.bbs[self.last_bb];
debug_assert!(last_bb.terminator.is_none());
let source_info = self.source_info;
let (1, Some(output)) = (self.stack.len(), self.stack.pop()) else {
span_bug!(
self.span,
"async destructor ctor shim builder finished with invalid number of stack items: expected 1 found {}",
self.stack.len(),
)
};
const RETURN_LOCAL: Local = Local::from_u32(0);
debug_assert_eq!(
output.ty(&self.locals, self.tcx),
self.self_ty.async_destructor_ty(self.tcx, self.param_env),
);
let dead_storage = match &output {
Operand::Move(place) => Some(Statement {
source_info,
kind: StatementKind::StorageDead(place.as_local().unwrap()),
}),
_ => None,
};
last_bb.statements.extend(
iter::once(Statement {
source_info,
kind: StatementKind::Assign(Box::new((RETURN_LOCAL.into(), Rvalue::Use(output)))),
})
.chain(dead_storage),
);
last_bb.terminator = Some(Terminator { source_info, kind: TerminatorKind::Return });
let source = MirSource::from_instance(ty::InstanceDef::AsyncDropGlueCtorShim(
self.def_id,
self.self_ty,
));
new_body(source, self.bbs, self.locals, Self::INPUT_COUNT, self.span)
}
fn apply_combinator(
&mut self,
arity: usize,
function: LangItem,
args: &[ty::GenericArg<'tcx>],
) -> Ty<'tcx> {
let function = self.tcx.require_lang_item(function, Some(self.span));
let operands_split = self
.stack
.len()
.checked_sub(arity)
.expect("async destructor ctor shim combinator tried to consume too many items");
let operands = &self.stack[operands_split..];
let func_ty = Ty::new_fn_def(self.tcx, function, args.iter().copied());
let func_sig = func_ty.fn_sig(self.tcx).no_bound_vars().unwrap();
#[cfg(debug_assertions)]
operands.iter().zip(func_sig.inputs()).for_each(|(operand, expected_ty)| {
let operand_ty = operand.ty(&self.locals, self.tcx);
if operand_ty == *expected_ty {
return;
}
// If projection of Discriminant then compare with `Ty::discriminant_ty`
if let ty::Alias(ty::AliasKind::Projection, ty::AliasTy { args, def_id, .. }) =
expected_ty.kind()
&& Some(*def_id) == self.tcx.lang_items().discriminant_type()
&& args.first().unwrap().as_type().unwrap().discriminant_ty(self.tcx) == operand_ty
{
return;
}
span_bug!(
self.span,
"Operand type and combinator argument type are not equal.
operand_ty: {:?}
argument_ty: {:?}
",
operand_ty,
expected_ty
);
});
let target = self.bbs.push(BasicBlockData {
statements: operands
.iter()
.rev()
.filter_map(|o| {
if let Operand::Move(Place { local, projection }) = o {
assert!(projection.is_empty());
Some(Statement {
source_info: self.source_info,
kind: StatementKind::StorageDead(*local),
})
} else {
None
}
})
.collect(),
terminator: None,
is_cleanup: false,
});
let dest_ty = func_sig.output();
let dest =
self.locals.push(LocalDecl::with_source_info(dest_ty, self.source_info).immutable());
let unwind = if let Some(top_cleanup_bb) = &mut self.top_cleanup_bb {
for _ in 0..arity {
*top_cleanup_bb =
self.bbs[*top_cleanup_bb].terminator().successors().exactly_one().ok().unwrap();
}
UnwindAction::Cleanup(*top_cleanup_bb)
} else {
UnwindAction::Unreachable
};
let last_bb = &mut self.bbs[self.last_bb];
debug_assert!(last_bb.terminator.is_none());
last_bb.statements.push(Statement {
source_info: self.source_info,
kind: StatementKind::StorageLive(dest),
});
last_bb.terminator = Some(Terminator {
source_info: self.source_info,
kind: TerminatorKind::Call {
func: Operand::Constant(Box::new(ConstOperand {
span: self.span,
user_ty: None,
const_: Const::Val(ConstValue::ZeroSized, func_ty),
})),
destination: dest.into(),
target: Some(target),
unwind,
call_source: CallSource::Misc,
fn_span: self.span,
args: self.stack.drain(operands_split..).map(|o| respan(self.span, o)).collect(),
},
});
self.put_operand(Operand::Move(dest.into()));
self.last_bb = target;
dest_ty
}
}

View File

@ -973,6 +973,7 @@ fn visit_instance_use<'tcx>(
}
}
ty::InstanceDef::DropGlue(_, Some(_))
| ty::InstanceDef::AsyncDropGlueCtorShim(..)
| ty::InstanceDef::VTableShim(..)
| ty::InstanceDef::ReifyShim(..)
| ty::InstanceDef::ClosureOnceShim { .. }

View File

@ -625,7 +625,8 @@ fn characteristic_def_id_of_mono_item<'tcx>(
| ty::InstanceDef::Virtual(..)
| ty::InstanceDef::CloneShim(..)
| ty::InstanceDef::ThreadLocalShim(..)
| ty::InstanceDef::FnPtrAddrShim(..) => return None,
| ty::InstanceDef::FnPtrAddrShim(..)
| ty::InstanceDef::AsyncDropGlueCtorShim(..) => return None,
};
// If this is a method, we want to put it into the same module as
@ -769,7 +770,9 @@ fn mono_item_visibility<'tcx>(
};
let def_id = match instance.def {
InstanceDef::Item(def_id) | InstanceDef::DropGlue(def_id, Some(_)) => def_id,
InstanceDef::Item(def_id)
| InstanceDef::DropGlue(def_id, Some(_))
| InstanceDef::AsyncDropGlueCtorShim(def_id, _) => def_id,
// We match the visibility of statics here
InstanceDef::ThreadLocalShim(def_id) => {

View File

@ -807,7 +807,10 @@ impl<'tcx> Stable<'tcx> for ty::Instance<'tcx> {
| ty::InstanceDef::ThreadLocalShim(..)
| ty::InstanceDef::DropGlue(..)
| ty::InstanceDef::CloneShim(..)
| ty::InstanceDef::FnPtrShim(..) => stable_mir::mir::mono::InstanceKind::Shim,
| ty::InstanceDef::FnPtrShim(..)
| ty::InstanceDef::AsyncDropGlueCtorShim(..) => {
stable_mir::mir::mono::InstanceKind::Shim
}
};
stable_mir::mir::mono::Instance { def, kind }
}

View File

@ -424,6 +424,16 @@ symbols! {
async_call_mut,
async_call_once,
async_closure,
async_destruct,
async_drop,
async_drop_chain,
async_drop_defer,
async_drop_either,
async_drop_fuse,
async_drop_in_place,
async_drop_noop,
async_drop_slice,
async_drop_surface_drop_in_place,
async_fn,
async_fn_in_trait,
async_fn_kind_helper,
@ -825,6 +835,7 @@ symbols! {
fadd_fast,
fake_variadic,
fallback,
fallback_surface_drop,
fdiv_algebraic,
fdiv_fast,
feature,
@ -1786,6 +1797,7 @@ symbols! {
sub_assign,
sub_with_overflow,
suggestion,
surface_async_drop_in_place,
sym,
sync,
synthetic,

View File

@ -55,7 +55,9 @@ pub(super) fn mangle<'tcx>(
printer
.print_def_path(
def_id,
if let ty::InstanceDef::DropGlue(_, _) = instance.def {
if let ty::InstanceDef::DropGlue(_, _) | ty::InstanceDef::AsyncDropGlueCtorShim(_, _) =
instance.def
{
// Add the name of the dropped type to the symbol name
&*instance.args
} else {

View File

@ -240,6 +240,11 @@ pub(super) trait GoalKind<'tcx>:
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx>;
fn consider_builtin_async_destruct_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx>;
fn consider_builtin_destruct_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
@ -520,6 +525,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
G::consider_builtin_coroutine_candidate(self, goal)
} else if lang_items.discriminant_kind_trait() == Some(trait_def_id) {
G::consider_builtin_discriminant_kind_candidate(self, goal)
} else if lang_items.async_destruct_trait() == Some(trait_def_id) {
G::consider_builtin_async_destruct_candidate(self, goal)
} else if lang_items.destruct_trait() == Some(trait_def_id) {
G::consider_builtin_destruct_candidate(self, goal)
} else if lang_items.transmute_trait() == Some(trait_def_id) {

View File

@ -814,6 +814,59 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> {
})
}
fn consider_builtin_async_destruct_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx> {
let self_ty = goal.predicate.self_ty();
let discriminant_ty = match *self_ty.kind() {
ty::Bool
| ty::Char
| ty::Int(..)
| ty::Uint(..)
| ty::Float(..)
| ty::Array(..)
| ty::RawPtr(..)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::Infer(ty::IntVar(..) | ty::FloatVar(..))
| ty::Coroutine(..)
| ty::CoroutineWitness(..)
| ty::Pat(..)
| ty::Never
| ty::Adt(_, _)
| ty::Str
| ty::Slice(_)
| ty::Dynamic(_, _, _)
| ty::Tuple(_)
| ty::Error(_) => self_ty.async_destructor_ty(ecx.tcx(), goal.param_env),
// We do not call `Ty::discriminant_ty` on alias, param, or placeholder
// types, which return `<self_ty as DiscriminantKind>::Discriminant`
// (or ICE in the case of placeholders). Projecting a type to itself
// is never really productive.
ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
return Err(NoSolution);
}
ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_))
| ty::Foreign(..)
| ty::Bound(..) => bug!(
"unexpected self ty `{:?}` when normalizing `<T as AsyncDestruct>::AsyncDestructor`",
goal.predicate.self_ty()
),
};
ecx.probe_misc_candidate("builtin async destruct").enter(|ecx| {
ecx.eq(goal.param_env, goal.predicate.term, discriminant_ty.into())
.expect("expected goal term to be fully unconstrained");
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}
fn consider_builtin_destruct_candidate(
_ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,

View File

@ -544,6 +544,18 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
fn consider_builtin_async_destruct_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
// `AsyncDestruct` is automatically implemented for every type.
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
fn consider_builtin_destruct_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,

View File

@ -1074,6 +1074,42 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
| ty::Infer(..)
| ty::Error(_) => false,
}
} else if lang_items.async_destruct_trait() == Some(trait_ref.def_id) {
match self_ty.kind() {
ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Adt(..)
| ty::Str
| ty::Array(..)
| ty::Slice(_)
| ty::RawPtr(..)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::Dynamic(..)
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::Coroutine(..)
| ty::CoroutineWitness(..)
| ty::Pat(..)
| ty::Never
| ty::Tuple(..)
| ty::Infer(ty::InferTy::IntVar(_) | ty::InferTy::FloatVar(..)) => true,
// type parameters, opaques, and unnormalized projections don't have
// a known discriminant and may need to be normalized further or rely
// on param env for async destructor projections
ty::Param(_)
| ty::Foreign(_)
| ty::Alias(..)
| ty::Bound(..)
| ty::Placeholder(..)
| ty::Infer(..)
| ty::Error(_) => false,
}
} else if lang_items.pointee_trait() == Some(trait_ref.def_id) {
let tail = selcx.tcx().struct_tail_with_normalize(
self_ty,
@ -1488,15 +1524,20 @@ fn confirm_builtin_candidate<'cx, 'tcx>(
) -> Progress<'tcx> {
let tcx = selcx.tcx();
let self_ty = obligation.predicate.self_ty();
let args = tcx.mk_args(&[self_ty.into()]);
let lang_items = tcx.lang_items();
let item_def_id = obligation.predicate.def_id;
let trait_def_id = tcx.trait_of_item(item_def_id).unwrap();
let args = tcx.mk_args(&[self_ty.into()]);
let (term, obligations) = if lang_items.discriminant_kind_trait() == Some(trait_def_id) {
let discriminant_def_id = tcx.require_lang_item(LangItem::Discriminant, None);
assert_eq!(discriminant_def_id, item_def_id);
(self_ty.discriminant_ty(tcx).into(), Vec::new())
} else if lang_items.async_destruct_trait() == Some(trait_def_id) {
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())
} 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);

View File

@ -81,6 +81,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
} else if lang_items.discriminant_kind_trait() == Some(def_id) {
// `DiscriminantKind` is automatically implemented for every type.
candidates.vec.push(BuiltinCandidate { has_nested: false });
} else if lang_items.async_destruct_trait() == Some(def_id) {
// `AsyncDestruct` is automatically implemented for every type.
candidates.vec.push(BuiltinCandidate { has_nested: false });
} else if lang_items.pointee_trait() == Some(def_id) {
// `Pointee` is automatically implemented for every type.
candidates.vec.push(BuiltinCandidate { has_nested: false });

View File

@ -22,6 +22,17 @@ 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>>,
@ -34,5 +45,13 @@ 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, ..*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
};
}

View File

@ -54,6 +54,47 @@ fn resolve_instance<'tcx>(
debug!(" => trivial drop glue");
ty::InstanceDef::DropGlue(def_id, None)
}
} else if Some(def_id) == tcx.lang_items().async_drop_in_place_fn() {
let ty = args.type_at(0);
match *ty.kind() {
ty::Array(..)
| ty::Slice(_)
| ty::Tuple(_)
| ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Str
| ty::RawPtr(_, _)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::Infer(ty::IntVar(_) | ty::FloatVar(_))
| ty::Adt(..)
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::CoroutineWitness(..)
| ty::Pat(..)
| ty::Never
| ty::Coroutine(..) => {}
ty::Param(_)
| ty::Error(_)
| ty::Dynamic(..)
| ty::Alias(..)
| ty::Infer(ty::TyVar(_))
| ty::Bound(..)
| ty::Foreign(_)
| ty::Placeholder(_)
| ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
return Ok(None);
}
}
debug!(" => async drop glue ctor");
ty::InstanceDef::AsyncDropGlueCtorShim(def_id, ty)
} else {
debug!(" => free item");
// FIXME(effects): we may want to erase the effect param if that is present on this item.

View File

@ -0,0 +1,271 @@
#![unstable(feature = "async_drop", issue = "none")]
use crate::fmt;
use crate::future::{Future, IntoFuture};
use crate::intrinsics::discriminant_value;
use crate::marker::{DiscriminantKind, PhantomPinned};
use crate::mem::MaybeUninit;
use crate::pin::Pin;
use crate::task::{ready, Context, Poll};
/// Asynchronously drops a value by running `AsyncDrop::async_drop`
/// on a value and its fields recursively.
#[unstable(feature = "async_drop", issue = "none")]
pub fn async_drop<T>(value: T) -> AsyncDropOwning<T> {
AsyncDropOwning { value: MaybeUninit::new(value), dtor: None, _pinned: PhantomPinned }
}
/// A future returned by the [`async_drop`].
#[unstable(feature = "async_drop", issue = "none")]
pub struct AsyncDropOwning<T> {
value: MaybeUninit<T>,
dtor: Option<AsyncDropInPlace<T>>,
_pinned: PhantomPinned,
}
#[unstable(feature = "async_drop", issue = "none")]
impl<T> fmt::Debug for AsyncDropOwning<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AsyncDropOwning").finish_non_exhaustive()
}
}
#[unstable(feature = "async_drop", issue = "none")]
impl<T> Future for AsyncDropOwning<T> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// SAFETY: Self is pinned thus it is ok to store references to self
unsafe {
let this = self.get_unchecked_mut();
let dtor = Pin::new_unchecked(
this.dtor.get_or_insert_with(|| async_drop_in_place(this.value.as_mut_ptr())),
);
// AsyncDestuctors are idempotent so Self gets idempotency as well
dtor.poll(cx)
}
}
}
#[lang = "async_drop_in_place"]
#[allow(unconditional_recursion)]
// FIXME: Consider if `#[rustc_diagnostic_item = "ptr_drop_in_place"]` is needed?
unsafe fn async_drop_in_place_raw<T: ?Sized>(
to_drop: *mut T,
) -> <T as AsyncDestruct>::AsyncDestructor {
// Code here does not matter - this is replaced by the
// real async drop glue constructor by the compiler.
// SAFETY: see comment above
unsafe { async_drop_in_place_raw(to_drop) }
}
/// Creates the asynchronous destructor of the pointed-to value.
///
/// # Safety
///
/// Behavior is undefined if any of the following conditions are violated:
///
/// * `to_drop` must be [valid](crate::ptr#safety) for both reads and writes.
///
/// * `to_drop` must be properly aligned, even if `T` has size 0.
///
/// * `to_drop` must be nonnull, even if `T` has size 0.
///
/// * The value `to_drop` points to must be valid for async dropping,
/// which may mean it must uphold additional invariants. These
/// invariants depend on the type of the value being dropped. For
/// instance, when dropping a Box, the box's pointer to the heap must
/// be valid.
///
/// * While `async_drop_in_place` is executing or the returned async
/// destructor is alive, the only way to access parts of `to_drop`
/// is through the `self: Pin<&mut Self>` references supplied to
/// the `AsyncDrop::async_drop` methods that `async_drop_in_place`
/// or `AsyncDropInPlace<T>::poll` invokes. This usually means the
/// returned future stores the `to_drop` pointer and user is required
/// to guarantee that dropped value doesn't move.
///
#[unstable(feature = "async_drop", issue = "none")]
pub unsafe fn async_drop_in_place<T: ?Sized>(to_drop: *mut T) -> AsyncDropInPlace<T> {
// SAFETY: `async_drop_in_place_raw` has the same safety requirements
unsafe { AsyncDropInPlace(async_drop_in_place_raw(to_drop)) }
}
/// A future returned by the [`async_drop_in_place`].
#[unstable(feature = "async_drop", issue = "none")]
pub struct AsyncDropInPlace<T: ?Sized>(<T as AsyncDestruct>::AsyncDestructor);
#[unstable(feature = "async_drop", issue = "none")]
impl<T: ?Sized> fmt::Debug for AsyncDropInPlace<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AsyncDropInPlace").finish_non_exhaustive()
}
}
#[unstable(feature = "async_drop", issue = "none")]
impl<T: ?Sized> Future for AsyncDropInPlace<T> {
type Output = ();
#[inline(always)]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// SAFETY: This code simply forwards poll call to the inner future
unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) }.poll(cx)
}
}
// FIXME(zetanumbers): Add same restrictions on AsyncDrop impls as
// with Drop impls
/// Custom code within the asynchronous destructor.
#[unstable(feature = "async_drop", issue = "none")]
#[lang = "async_drop"]
pub trait AsyncDrop {
/// A future returned by the [`AsyncDrop::async_drop`] to be part
/// of the async destructor.
#[unstable(feature = "async_drop", issue = "none")]
type Dropper<'a>: Future<Output = ()>
where
Self: 'a;
/// Constructs the asynchronous destructor for this type.
#[unstable(feature = "async_drop", issue = "none")]
fn async_drop(self: Pin<&mut Self>) -> Self::Dropper<'_>;
}
#[lang = "async_destruct"]
#[rustc_deny_explicit_impl(implement_via_object = false)]
trait AsyncDestruct {
type AsyncDestructor: Future<Output = ()>;
}
/// Basically calls `AsyncDrop::async_drop` with pointer. Used to simplify
/// generation of the code for `async_drop_in_place_raw`
#[lang = "surface_async_drop_in_place"]
async unsafe fn surface_async_drop_in_place<T: AsyncDrop + ?Sized>(ptr: *mut T) {
// SAFETY: We call this from async drop `async_drop_in_place_raw`
// which has the same safety requirements
unsafe { <T as AsyncDrop>::async_drop(Pin::new_unchecked(&mut *ptr)).await }
}
/// Basically calls `Drop::drop` with pointer. Used to simplify generation
/// of the code for `async_drop_in_place_raw`
#[allow(drop_bounds)]
#[lang = "async_drop_surface_drop_in_place"]
async unsafe fn surface_drop_in_place<T: Drop + ?Sized>(ptr: *mut T) {
// SAFETY: We call this from async drop `async_drop_in_place_raw`
// which has the same safety requirements
unsafe { crate::ops::fallback_surface_drop(&mut *ptr) }
}
/// Wraps a future to continue outputing `Poll::Ready(())` once after
/// wrapped future completes by returning `Poll::Ready(())` on poll. This
/// is useful for constructing async destructors to guarantee this
/// "fuse" property
struct Fuse<T> {
inner: Option<T>,
}
#[lang = "async_drop_fuse"]
fn fuse<T>(inner: T) -> Fuse<T> {
Fuse { inner: Some(inner) }
}
impl<T> Future for Fuse<T>
where
T: Future<Output = ()>,
{
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// SAFETY: pin projection into `self.inner`
unsafe {
let this = self.get_unchecked_mut();
if let Some(inner) = &mut this.inner {
ready!(Pin::new_unchecked(inner).poll(cx));
this.inner = None;
}
}
Poll::Ready(())
}
}
/// Async destructor for arrays and slices.
#[lang = "async_drop_slice"]
async unsafe fn slice<T>(s: *mut [T]) {
let len = s.len();
let ptr = s.as_mut_ptr();
for i in 0..len {
// SAFETY: we iterate over elements of `s` slice
unsafe { async_drop_in_place_raw(ptr.add(i)).await }
}
}
/// Construct a chain of two futures, which awaits them sequentially as
/// a future.
#[lang = "async_drop_chain"]
async fn chain<F, G>(first: F, last: G)
where
F: IntoFuture<Output = ()>,
G: IntoFuture<Output = ()>,
{
first.await;
last.await;
}
/// Basically a lazy version of `async_drop_in_place`. Returns a future
/// that would call `AsyncDrop::async_drop` on a first poll.
///
/// # Safety
///
/// Same as `async_drop_in_place` except is lazy to avoid creating
/// multiple mutable refernces.
#[lang = "async_drop_defer"]
async unsafe fn defer<T: ?Sized>(to_drop: *mut T) {
// SAFETY: same safety requirements as `async_drop_in_place`
unsafe { async_drop_in_place(to_drop) }.await
}
/// If `T`'s discriminant is equal to the stored one then awaits `M`
/// otherwise awaits the `O`.
///
/// # Safety
///
/// User should carefully manage returned future, since it would
/// try creating an immutable referece from `this` and get pointee's
/// discriminant.
// FIXME(zetanumbers): Send and Sync impls
#[lang = "async_drop_either"]
async unsafe fn either<O: IntoFuture<Output = ()>, M: IntoFuture<Output = ()>, T>(
other: O,
matched: M,
this: *mut T,
discr: <T as DiscriminantKind>::Discriminant,
) {
// SAFETY: Guaranteed by the safety section of this funtion's documentation
if unsafe { discriminant_value(&*this) } == discr {
drop(other);
matched.await
} else {
drop(matched);
other.await
}
}
/// 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.
#[derive(Clone, Copy)]
struct Noop;
#[lang = "async_drop_noop"]
fn noop() -> Noop {
Noop
}
impl Future for Noop {
type Output = ();
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

View File

@ -12,6 +12,8 @@
use crate::ptr::NonNull;
use crate::task::Context;
#[cfg(not(bootstrap))]
mod async_drop;
mod future;
mod into_future;
mod join;
@ -36,6 +38,10 @@ pub use ready::{ready, Ready};
#[stable(feature = "future_poll_fn", since = "1.64.0")]
pub use poll_fn::{poll_fn, PollFn};
#[cfg(not(bootstrap))]
#[unstable(feature = "async_drop", issue = "none")]
pub use async_drop::{async_drop, async_drop_in_place, AsyncDrop, AsyncDropInPlace};
/// This type is needed because:
///
/// a) Coroutines cannot implement `for<'a, 'b> Coroutine<&'a mut Context<'b>>`, so we need to pass

View File

@ -238,3 +238,11 @@ pub trait Drop {
#[stable(feature = "rust1", since = "1.0.0")]
fn drop(&mut self);
}
/// Fallback function to call surface level `Drop::drop` function
#[cfg(not(bootstrap))]
#[allow(drop_bounds)]
#[lang = "fallback_surface_drop"]
pub(crate) fn fallback_surface_drop<T: Drop + ?Sized>(x: &mut T) {
<T as Drop>::drop(x)
}

View File

@ -174,6 +174,9 @@ pub use self::deref::Receiver;
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::drop::Drop;
#[cfg(not(bootstrap))]
pub(crate) use self::drop::fallback_surface_drop;
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::function::{Fn, FnMut, FnOnce};

View File

@ -0,0 +1,191 @@
//@revisions: stack tree
//@compile-flags: -Zmiri-strict-provenance
//@[tree]compile-flags: -Zmiri-tree-borrows
#![feature(async_drop, impl_trait_in_assoc_type, noop_waker, async_closure)]
#![allow(incomplete_features, dead_code)]
// FIXME(zetanumbers): consider AsyncDestruct::async_drop cleanup tests
use core::future::{async_drop_in_place, AsyncDrop, Future};
use core::hint::black_box;
use core::mem::{self, ManuallyDrop};
use core::pin::{pin, Pin};
use core::task::{Context, Poll, Waker};
async fn test_async_drop<T>(x: T) {
let mut x = mem::MaybeUninit::new(x);
let dtor = pin!(unsafe { async_drop_in_place(x.as_mut_ptr()) });
test_idempotency(dtor).await;
}
fn test_idempotency<T>(mut x: Pin<&mut T>) -> impl Future<Output = ()> + '_
where
T: Future<Output = ()>,
{
core::future::poll_fn(move |cx| {
assert_eq!(x.as_mut().poll(cx), Poll::Ready(()));
assert_eq!(x.as_mut().poll(cx), Poll::Ready(()));
Poll::Ready(())
})
}
fn main() {
let waker = Waker::noop();
let mut cx = Context::from_waker(&waker);
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;
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;
let foo = AsyncInt(10);
test_async_drop(AsyncReference { foo: &foo }).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;
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;
});
let res = fut.poll(&mut cx);
assert_eq!(res, Poll::Ready(()));
}
struct AsyncInt(i32);
impl AsyncDrop for AsyncInt {
type Dropper<'a> = impl Future<Output = ()>;
fn async_drop(self: Pin<&mut Self>) -> Self::Dropper<'_> {
async move {
println!("AsyncInt::Dropper::poll: {}", self.0);
}
}
}
struct SyncInt(i32);
impl Drop for SyncInt {
fn drop(&mut self) {
println!("SyncInt::drop: {}", self.0);
}
}
struct SyncThenAsync {
i: i32,
a: AsyncInt,
b: SyncInt,
c: AsyncInt,
}
impl Drop for SyncThenAsync {
fn drop(&mut self) {
println!("SyncThenAsync::drop: {}", self.i);
}
}
struct AsyncReference<'a> {
foo: &'a AsyncInt,
}
impl AsyncDrop for AsyncReference<'_> {
type Dropper<'a> = impl Future<Output = ()> where Self: 'a;
fn async_drop(self: Pin<&mut Self>) -> Self::Dropper<'_> {
async move {
println!("AsyncReference::Dropper::poll: {}", self.foo.0);
}
}
}
struct Int(i32);
struct AsyncStruct {
i: i32,
a: AsyncInt,
b: AsyncInt,
}
impl AsyncDrop for AsyncStruct {
type Dropper<'a> = impl Future<Output = ()>;
fn async_drop(self: Pin<&mut Self>) -> Self::Dropper<'_> {
async move {
println!("AsyncStruct::Dropper::poll: {}", self.i);
}
}
}
enum AsyncEnum {
A(AsyncInt),
B(SyncInt),
}
impl AsyncDrop for AsyncEnum {
type Dropper<'a> = impl Future<Output = ()>;
fn async_drop(mut self: Pin<&mut Self>) -> Self::Dropper<'_> {
async move {
let new_self = match &*self {
AsyncEnum::A(foo) => {
println!("AsyncEnum(A)::Dropper::poll: {}", foo.0);
AsyncEnum::B(SyncInt(foo.0))
}
AsyncEnum::B(foo) => {
println!("AsyncEnum(B)::Dropper::poll: {}", foo.0);
AsyncEnum::A(AsyncInt(foo.0))
}
};
mem::forget(mem::replace(&mut *self, new_self));
}
}
}
// FIXME(zetanumbers): Disallow types with `AsyncDrop` in unions
union AsyncUnion {
signed: i32,
unsigned: u32,
}
impl AsyncDrop for AsyncUnion {
type Dropper<'a> = impl Future<Output = ()>;
fn async_drop(self: Pin<&mut Self>) -> Self::Dropper<'_> {
async move {
println!("AsyncUnion::Dropper::poll: {}, {}", unsafe { self.signed }, unsafe {
self.unsigned
});
}
}
}

View File

@ -0,0 +1,22 @@
AsyncInt::Dropper::poll: 0
AsyncInt::Dropper::poll: 1
AsyncInt::Dropper::poll: 2
AsyncInt::Dropper::poll: 3
AsyncInt::Dropper::poll: 4
AsyncStruct::Dropper::poll: 6
AsyncInt::Dropper::poll: 7
AsyncInt::Dropper::poll: 8
AsyncReference::Dropper::poll: 10
AsyncInt::Dropper::poll: 11
AsyncEnum(A)::Dropper::poll: 12
SyncInt::drop: 12
AsyncEnum(B)::Dropper::poll: 13
AsyncInt::Dropper::poll: 13
SyncInt::drop: 14
SyncThenAsync::drop: 15
AsyncInt::Dropper::poll: 16
SyncInt::drop: 17
AsyncInt::Dropper::poll: 18
AsyncInt::Dropper::poll: 19
AsyncInt::Dropper::poll: 20
AsyncUnion::Dropper::poll: 21, 21

View File

@ -0,0 +1,22 @@
AsyncInt::Dropper::poll: 0
AsyncInt::Dropper::poll: 1
AsyncInt::Dropper::poll: 2
AsyncInt::Dropper::poll: 3
AsyncInt::Dropper::poll: 4
AsyncStruct::Dropper::poll: 6
AsyncInt::Dropper::poll: 7
AsyncInt::Dropper::poll: 8
AsyncReference::Dropper::poll: 10
AsyncInt::Dropper::poll: 11
AsyncEnum(A)::Dropper::poll: 12
SyncInt::drop: 12
AsyncEnum(B)::Dropper::poll: 13
AsyncInt::Dropper::poll: 13
SyncInt::drop: 14
SyncThenAsync::drop: 15
AsyncInt::Dropper::poll: 16
SyncInt::drop: 17
AsyncInt::Dropper::poll: 18
AsyncInt::Dropper::poll: 19
AsyncInt::Dropper::poll: 20
AsyncUnion::Dropper::poll: 21, 21

View File

@ -0,0 +1,197 @@
//@ run-pass
//@ check-run-results
#![feature(async_drop, impl_trait_in_assoc_type, noop_waker, async_closure)]
#![allow(incomplete_features, dead_code)]
//@ edition: 2021
// FIXME(zetanumbers): consider AsyncDestruct::async_drop cleanup tests
use core::future::{async_drop_in_place, AsyncDrop, Future};
use core::hint::black_box;
use core::mem::{self, ManuallyDrop};
use core::pin::{pin, Pin};
use core::task::{Context, Poll, Waker};
async fn test_async_drop<T>(x: T) {
let mut x = mem::MaybeUninit::new(x);
let dtor = pin!(unsafe { async_drop_in_place(x.as_mut_ptr()) });
test_idempotency(dtor).await;
}
fn test_idempotency<T>(mut x: Pin<&mut T>) -> impl Future<Output = ()> + '_
where
T: Future<Output = ()>,
{
core::future::poll_fn(move |cx| {
assert_eq!(x.as_mut().poll(cx), Poll::Ready(()));
assert_eq!(x.as_mut().poll(cx), Poll::Ready(()));
Poll::Ready(())
})
}
fn main() {
let waker = Waker::noop();
let mut cx = Context::from_waker(&waker);
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;
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;
let foo = AsyncInt(10);
test_async_drop(AsyncReference { foo: &foo }).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;
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;
});
let res = fut.poll(&mut cx);
assert_eq!(res, Poll::Ready(()));
}
struct AsyncInt(i32);
impl AsyncDrop for AsyncInt {
type Dropper<'a> = impl Future<Output = ()>;
fn async_drop(self: Pin<&mut Self>) -> Self::Dropper<'_> {
async move {
println!("AsyncInt::Dropper::poll: {}", self.0);
}
}
}
struct SyncInt(i32);
impl Drop for SyncInt {
fn drop(&mut self) {
println!("SyncInt::drop: {}", self.0);
}
}
struct SyncThenAsync {
i: i32,
a: AsyncInt,
b: SyncInt,
c: AsyncInt,
}
impl Drop for SyncThenAsync {
fn drop(&mut self) {
println!("SyncThenAsync::drop: {}", self.i);
}
}
struct AsyncReference<'a> {
foo: &'a AsyncInt,
}
impl AsyncDrop for AsyncReference<'_> {
type Dropper<'a> = impl Future<Output = ()> where Self: 'a;
fn async_drop(self: Pin<&mut Self>) -> Self::Dropper<'_> {
async move {
println!("AsyncReference::Dropper::poll: {}", self.foo.0);
}
}
}
struct Int(i32);
struct AsyncStruct {
i: i32,
a: AsyncInt,
b: AsyncInt,
}
impl AsyncDrop for AsyncStruct {
type Dropper<'a> = impl Future<Output = ()>;
fn async_drop(self: Pin<&mut Self>) -> Self::Dropper<'_> {
async move {
println!("AsyncStruct::Dropper::poll: {}", self.i);
}
}
}
enum AsyncEnum {
A(AsyncInt),
B(SyncInt),
}
impl AsyncDrop for AsyncEnum {
type Dropper<'a> = impl Future<Output = ()>;
fn async_drop(mut self: Pin<&mut Self>) -> Self::Dropper<'_> {
async move {
let new_self = match &*self {
AsyncEnum::A(foo) => {
println!("AsyncEnum(A)::Dropper::poll: {}", foo.0);
AsyncEnum::B(SyncInt(foo.0))
}
AsyncEnum::B(foo) => {
println!("AsyncEnum(B)::Dropper::poll: {}", foo.0);
AsyncEnum::A(AsyncInt(foo.0))
}
};
mem::forget(mem::replace(&mut *self, new_self));
}
}
}
// FIXME(zetanumbers): Disallow types with `AsyncDrop` in unions
union AsyncUnion {
signed: i32,
unsigned: u32,
}
impl AsyncDrop for AsyncUnion {
type Dropper<'a> = impl Future<Output = ()>;
fn async_drop(self: Pin<&mut Self>) -> Self::Dropper<'_> {
async move {
println!(
"AsyncUnion::Dropper::poll: {}, {}",
unsafe { self.signed },
unsafe { self.unsigned },
);
}
}
}

View File

@ -0,0 +1,22 @@
AsyncInt::Dropper::poll: 0
AsyncInt::Dropper::poll: 1
AsyncInt::Dropper::poll: 2
AsyncInt::Dropper::poll: 3
AsyncInt::Dropper::poll: 4
AsyncStruct::Dropper::poll: 6
AsyncInt::Dropper::poll: 7
AsyncInt::Dropper::poll: 8
AsyncReference::Dropper::poll: 10
AsyncInt::Dropper::poll: 11
AsyncEnum(A)::Dropper::poll: 12
SyncInt::drop: 12
AsyncEnum(B)::Dropper::poll: 13
AsyncInt::Dropper::poll: 13
SyncInt::drop: 14
SyncThenAsync::drop: 15
AsyncInt::Dropper::poll: 16
SyncInt::drop: 17
AsyncInt::Dropper::poll: 18
AsyncInt::Dropper::poll: 19
AsyncInt::Dropper::poll: 20
AsyncUnion::Dropper::poll: 21, 21