Skip to content

[5.5] Handle multiple awaits and suspend-on-exit for async let tasks. #38579

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

Conversation

jckarter
Copy link
Contributor

Description: Implements a new ABI for async let that can support multiple awaits of the same binding, cancelling and suspending the task when it is still running on scope exit, and efficiently consuming and deallocating the task in one step in the common case.

Scope: Completes the implementation of async let.

Issues: rdar://77855176, rdar://80043610

Reviewed by: @ktoso

Risk: Moderate; it's a substantial reimplementation of the feature. However, impact on the rest of the compiler and runtime should be minimal.

@jckarter jckarter requested a review from a team as a code owner July 22, 2021 21:32
@jckarter
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 5c50ed13aa24b3b5f507a01cf242a498b57ed59f

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 5c50ed13aa24b3b5f507a01cf242a498b57ed59f

@jckarter jckarter force-pushed the async-let-multi-suspend-5.5 branch from 5c50ed1 to e47ab71 Compare July 23, 2021 02:29
@jckarter
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - e47ab715da8a37b2f20a683e684c819856021248

jckarter added 2 commits July 23, 2021 07:58
Change the code generation patterns for `async let` bindings to use an ABI based on the following
functions:

- `swift_asyncLet_begin`, which starts an `async let` child task, but which additionally
  now associates the `async let` with a caller-owned buffer to receive the result of the task.
  This is intended to allow the task to emplace its result in caller-owned memory, allowing the
  child task to be deallocated after completion without invalidating the result buffer.
- `swift_asyncLet_get[_throwing]`, which replaces `swift_asyncLet_wait[_throwing]`. Instead of
  returning a copy of the value, this entry point concerns itself with populating the local buffer.
  If the buffer hasn't been populated, then it awaits completion of the task and emplaces the
  result in the buffer; otherwise, it simply returns. The caller can then read the result out of
  its owned memory. These entry points are intended to be used before every read from the
  `async let` binding, after which point the local buffer is guaranteed to contain an initialized
  value.
- `swift_asyncLet_finish`, which replaces `swift_asyncLet_end`. Unlike `_end`, this variant
  is async and will suspend the parent task after cancelling the child to ensure it finishes
  before cleaning up. The local buffer will also be deinitialized if necessary. This is intended
  to be used on exit from an `async let` scope, to handle cleaning up the local buffer if necessary
  as well as cancelling, awaiting, and deallocating the child task.
- `swift_asyncLet_consume[_throwing]`, which combines `get` and `finish`. This will await completion
  of the task, leaving the result value in the result buffer (or propagating the error, if it
  throws), while destroying and deallocating the child task. This is intended as an optimization
  for reading `async let` variables that are read exactly once by their parent task.

To avoid an epoch break with existing swiftinterfaces and ABI clients, the old builtins and entry
points are kept intact for now, but SILGen now only generates code using the new interface.

This new interface fixes several issues with the old async let codegen, including use-after-free
crashes if the `async let` was never awaited, and the inability to read from an `async let` variable
more than once.

rdar://77855176
This isn't implemented. It would never make sense for escaping captures of
`async let` values, because doing so would either involve an implicit awaiting
of the value on capture, or else stretching the lifetime of the task past its
scope. In nonescaping contexts, and in nested `async let` expressions, it might
be implementable in the future. rdar://80043610
@jckarter jckarter force-pushed the async-let-multi-suspend-5.5 branch from e47ab71 to b1a9253 Compare July 23, 2021 15:01
@jckarter
Copy link
Contributor Author

@swift-ci Please test

@jckarter
Copy link
Contributor Author

@swift-ci Please nominate

@DougGregor DougGregor merged commit 032de59 into swiftlang:release/5.5 Jul 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants