Skip to content
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

Add simple async drop glue generation #121801

Merged
merged 6 commits into from
Apr 23, 2024
Merged

Conversation

zetanumbers
Copy link
Contributor

@zetanumbers zetanumbers commented Feb 29, 2024

This is a prototype of the async drop glue generation for some simple types. Async drop glue is intended to behave very similar to the regular drop glue except for being asynchronous. Currently it does not execute synchronous drops but only calls user implementations of AsyncDrop::async_drop associative function and awaits the returned future. It is not complete as it only recurses into arrays, slices, tuples, and structs and does not have same sensible restrictions as the old Drop trait implementation like having the same bounds as the type definition, while code assumes their existence (requires a future work).

This current design uses a workaround as it does not create any custom async destructor state machine types for ADTs, but instead uses types defined in the std library called future combinators (deferred_async_drop, chain, ready_unit).

Also I recommend reading my explainer.

This is a part of the MCP: Low level components for async drop work.

Feature completeness:

  • AsyncDrop trait
  • async_drop_in_place_raw/async drop glue generation support for
    • Trivially destructible types (integers, bools, floats, string slices, pointers, references, etc.)
    • Arrays and slices (array pointer is unsized into slice pointer)
    • ADTs (enums, structs, unions)
    • tuple-like types (tuples, closures)
    • Dynamic types (dyn Trait, see explainer's proposed design)
    • coroutines (Async drop codegen #123948)
  • Async drop glue includes sync drop glue code
  • Cleanup branch generation for async_drop_in_place_raw
  • Union rejects non-trivially async destructible fields
  • AsyncDrop implementation requires same bounds as type definition
  • Skip trivially destructible fields (optimization)
  • New TyKind::AdtAsyncDestructor and get rid of combinators
  • Synchronously undroppable types
  • Automatic async drop at the end of the scope in async context

@rustbot
Copy link
Collaborator

rustbot commented Feb 29, 2024

r? @michaelwoerister

rustbot has assigned @michaelwoerister.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver) labels Feb 29, 2024
@rust-log-analyzer

This comment has been minimized.

@zetanumbers zetanumbers marked this pull request as ready for review February 29, 2024 14:36
@rustbot
Copy link
Collaborator

rustbot commented Feb 29, 2024

The Miri subtree was changed

cc @rust-lang/miri

Some changes occurred to the CTFE / Miri engine

cc @rust-lang/miri

Some changes occurred in src/tools/rustfmt

cc @rust-lang/rustfmt

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

Some changes occurred to the core trait solver

cc @rust-lang/initiative-trait-system-refactor

@rust-log-analyzer

This comment has been minimized.

@oli-obk oli-obk self-assigned this Mar 1, 2024
@michaelwoerister michaelwoerister removed their assignment Mar 1, 2024
@petrochenkov
Copy link
Contributor

Some high level comments:

This PR adds many lang items, it would be good to document not just what they do, but why are they added

  • to avoid generating MIR manually and call something from the library instead (not strictly necessary, but makes life easier)
  • to get some type information that is not easily calculatable otherwise (?), e.g. why AsyncDestruct and its associated type exist in particular.
  • to execute something user-defined from the compiler (strictly necessary, like trait AsyncDrop)
  • etc

It would also be good to add test cases that are not yet supported in a test, to see what happens when they are encountered and add corresponding FIXMEs for people to see.
Preferable with some short comments explaining whether they are trivial to support or not, and why.
E.g. what if AsyncDrop is implemented or called for enums, closures, or generic parameters.

Also add a test for a struct (or tuple) with 2 fields, one with non-trivial async drop, and one with non-trivial sync drop.
This case is not yet supported, but it would be good to cover it with a failing test for visibility.

@petrochenkov
Copy link
Contributor

This needs a review from someone who maintains MIR building/passes/transforms.
It's quite possible that some things here are not done idiomatically, and could be done simpler/better.
I oversaw this work, but neither @zetanumbers nor me had a previous experience with these parts of the compiler.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@RalfJung
Copy link
Member

RalfJung commented Mar 2, 2024

Great to see Miri tests already in the initial PR, that's much appreciated. :)

@zetanumbers
Copy link
Contributor Author

I would say there's one specific problem about this API. async_drop_in_place gets a pointer to some object to construct object's async destructor. This async destructor has a lifetime, that comes from the T: 'a bound of async_drop_in_place, which may not be meaningfull. For example if T: 'static is true, then it may be that 'a = 'static even tho we probably meant lifetime of a reference to this object instead. This 'a argument came from the trivial implementation of async drop purely within the library so we would able to write <T as AsyncDrop>::Dropper<'a> within the implementation of impl<'a, T: AsyncDrop> AsyncDestruct<'a> for T {...}.

To move forward I can either remove this 'a parameter or change function signature to something like a fn async_drop_in_place<'a, T>(to_drop: &'a mut MaybeUninit<T>) .... Second one has the problem with MaybeUninit<T> requiring T: Sized, which is already bad for slices, but perhaps this could be replaced with ManuallyDrop<T>. First variant has been done once, and i'm actually surprised lifetime punning kinda works here, however there could still be a hypothetical problem with making some coroutines Unpin cause technically we still don't borrow anything there thus possibly allowing compiler to mark this coroutine as Unpin, while having a self-referencial pointer.

For now I've decided to stick with removing 'a since Unpin coroutines don't seem to be close in the future.

@oli-obk
Copy link
Contributor

oli-obk commented Mar 4, 2024

Did a partial review to get an overview. I think the main comment of this PR should mention that this does not yet do

Component 2: Calling async drop glue at the end of scope

or any other automatic drop glue handling. All that we get is that a type gets dropped asynchronously plus the glue needed for field types and builtin types

compiler/rustc_middle/src/ty/instance.rs Outdated Show resolved Hide resolved
library/core/src/future/async_drop.rs Outdated Show resolved Hide resolved
library/core/src/future/async_drop.rs Outdated Show resolved Hide resolved
compiler/rustc_ty_utils/src/instance.rs Outdated Show resolved Hide resolved
compiler/rustc_trait_selection/src/traits/project.rs Outdated Show resolved Hide resolved
Comment on lines 1073 to 1078
/// Empty strategy should generate this:
///
/// ```ignore (illustrative)
/// ready_unit()
/// ```
Empty,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we avoid this variant by never requesting the shim for these at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would still need to be able to directly call async_drop_in_place(_raw)::<()> so we cannot "never request". This is true for regular drop glue too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be relevant: #121801 (comment)

compiler/rustc_mir_transform/src/shim.rs Outdated Show resolved Hide resolved
}
}

GlueStrategy::Chain { has_surface_async_drop, elem_tys } => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we write most of this one in surface Rust somehow? Maybe we could generate a tuple of the various types that should be dropped and recursively invoke drop on them?

Copy link
Contributor Author

@zetanumbers zetanumbers Mar 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry but I don't understand what you are describing. If you imagine a handwritten async destructor future for a tuple it would be an enum, for which each variant of it represents running an async destructor for some field, sequentially ordered one after the other + variants like Unresumed and Done.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 5, 2024
@oli-obk
Copy link
Contributor

oli-obk commented Mar 5, 2024

I have not reviewed the shim impls yet, I'd prefer it if we could avoid such a complex shim

@zetanumbers
Copy link
Contributor Author

zetanumbers commented Mar 5, 2024

I have not reviewed the shim impls yet, I'd prefer it if we could avoid such a complex shim

When it comes to GlueStrategy::Chain it shouldn't have been complex like that, but since I haven't yet managed to generate async destructor ADTs inside of the compiler I've used nested library types, which is suboptimal.

EDIT: However it will instead introduce ADT async destructors Future::poll shim.

@zetanumbers
Copy link
Contributor Author

zetanumbers commented Mar 5, 2024

To reduce complexity of shim generation code I can try modeling it as nested expressions instead of directly creating basic blocks. Yeah, I should try doing that.

EDIT: going to prioritize this for now since making changes became a bit unwieldy.

EDIT2: Done in 16010ba :)

@zetanumbers
Copy link
Contributor Author

Also this "constructor of async destructor" is confusing to say. Not sure what naming convention I should use instead.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@traviscross
Copy link
Contributor

@rustbot labels +T-lang +I-lang-nominated

Let's discuss approval of this as an experiment.

@zetanumbers / @petrochenkov: If you could perhaps write up further details for the lang team to review as part of this nomination about the nature of this part of the experiment, what the motivation for it is, and how this fits into the larger body of work, that work be helpful.

@zetanumbers zetanumbers marked this pull request as ready for review April 18, 2024 12:21
@rustbot
Copy link
Collaborator

rustbot commented Apr 18, 2024

This PR changes Stable MIR

cc @oli-obk, @celinval, @ouz-a

@zetanumbers
Copy link
Contributor Author

zetanumbers commented Apr 18, 2024

Uh-oh

This PR changes Stable MIR

@zetanumbers
Copy link
Contributor Author

Sorry for adding changes last moment.

@celinval
Copy link
Contributor

Uh-oh

This PR changes Stable MIR

The StableMIR change looks fine. Thanks for addressing it.

@oli-obk
Copy link
Contributor

oli-obk commented Apr 19, 2024

Made most matches on TyKind to be non exhaustive in 80c0b7e.

why? The equivalent matches on Drop things elsewhere are exhaustive

@zetanumbers
Copy link
Contributor Author

zetanumbers commented Apr 19, 2024

Made most matches on TyKind to be non exhaustive in 80c0b7e.

why? The equivalent matches on Drop things elsewhere are exhaustive

Some TyKind variants are not supported yet, so I've made this branch to accept anything that either I didn't account for or is know but should be added in the future. Anyway people would have to figure out what case they should put their type in and it would be most probably the "todo" branch, so I would like to think I've made this choice myself not to spend everyone's time on those. But of course the "todo" branch should be removed in the future.

@@ -2319,6 +2319,10 @@ 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(_)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer self.references_error() over matching for ty::Error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither drop glue nor discriminant kind do this. I will have to switch every match on ty::Error otherwise compiler may emit type errors.

Copy link
Contributor Author

@zetanumbers zetanumbers Apr 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I cannot add this case to be a part of is_async_destructor_noop because of this line, which differs from other similar types like ints:

But I'm not sure, maybe I should include it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... I guess we can look if there's a general thing to improve here everywhere

compiler/rustc_middle/src/ty/util.rs Outdated Show resolved Hide resolved
compiler/rustc_middle/src/ty/util.rs Show resolved Hide resolved
/// 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this relate to is_trivially_const_drop and can we deduplicate some code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_trivially_const_drop recurses into array and tuple elements to determine if type can be trivially destructed, while could_have_surface_drop only checks if there's possible impl Drop for T. This function would return false for a tuple with any collection of types, while !is_trivially_const_drop for the same tuple may be true because of some element type could be an ADT. I could use could_have_surface_drop from is_trivially_const_drop since the former's negative implies the latter if it's ok.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, in contrast is_trivially_const_drop does not check/intend to check ManuallyDrop, so those could mean a bit different things after all.

@rustbot rustbot added the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Apr 19, 2024
@oli-obk
Copy link
Contributor

oli-obk commented Apr 22, 2024

@bors r+

@bors
Copy link
Contributor

bors commented Apr 22, 2024

📌 Commit 67980dd has been approved by oli-obk

It is now in the queue for this repository.

@bors bors removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Apr 22, 2024
@compiler-errors
Copy link
Member

@bors rollup=never

@bors
Copy link
Contributor

bors commented Apr 23, 2024

⌛ Testing commit 67980dd with merge aca749e...

@bors
Copy link
Contributor

bors commented Apr 23, 2024

☀️ Test successful - checks-actions
Approved by: oli-obk
Pushing aca749e to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Apr 23, 2024
@bors bors merged commit aca749e into rust-lang:master Apr 23, 2024
13 checks passed
@rustbot rustbot added this to the 1.79.0 milestone Apr 23, 2024
@rust-timer
Copy link
Collaborator

Finished benchmarking commit (aca749e): comparison URL.

Overall result: ❌ regressions - no action needed

@rustbot label: -perf-regression

Instruction count

This is a highly reliable metric that was used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
0.1% [0.1%, 0.1%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) - - 0

Max RSS (memory usage)

Results

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
-2.5% [-2.5%, -2.5%] 1
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) -2.5% [-2.5%, -2.5%] 1

Cycles

This benchmark run did not return any relevant results for this metric.

Binary size

This benchmark run did not return any relevant results for this metric.

Bootstrap: 672.713s -> 674.396s (0.25%)
Artifact size: 316.06 MiB -> 315.53 MiB (-0.17%)

@zetanumbers zetanumbers deleted the async_drop_glue branch April 23, 2024 08:57
@traviscross traviscross removed the I-lang-nominated Nominated for discussion during a lang team meeting. label Apr 25, 2024
bors added a commit to rust-lang-ci/rust that referenced this pull request May 31, 2024
Implement `needs_async_drop` in rustc and optimize async drop glue

This PR expands on rust-lang#121801 and implements `Ty::needs_async_drop` which works almost exactly the same as `Ty::needs_drop`, which is needed for rust-lang#123948.

Also made compiler's async drop code to look more like compiler's regular drop code, which enabled me to write an optimization where types which do not use `AsyncDrop` can simply forward async drop glue to `drop_in_place`. This made size of the async block from the [async_drop test](https://github.com/zetanumbers/rust/blob/67980dd6fb11917d23d01a19c2cf4cfc3978aac8/tests/ui/async-await/async-drop.rs) to decrease by 12%.
@petrochenkov petrochenkov added the F-async_drop Async drop label Jun 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-async-await Area: Async & Await AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. F-async_drop Async drop merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. WG-async Working group: Async & await WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver)
Projects
None yet
Development

Successfully merging this pull request may close these issues.