-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Handle multiple awaits and suspend-on-exit for async let tasks. #37938
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,15 +36,29 @@ class alignas(Alignment_AsyncLet) AsyncLet { | |
constexpr AsyncLet() | ||
: PrivateData{} {} | ||
|
||
// FIXME: not sure how many words we should reserve | ||
void *PrivateData[NumWords_AsyncLet]; | ||
|
||
// TODO: we could offer a "was awaited on" check here | ||
|
||
/// Returns the child task that is associated with this async let. | ||
/// The tasks completion is used to fulfil the value represented by this async let. | ||
AsyncTask *getTask() const; | ||
|
||
|
||
// The compiler preallocates a large fixed space for the `async let`, with the | ||
// intent that most of it be used for the child task context. The next two | ||
// methods return the address and size of that space. | ||
|
||
/// Return a pointer to the unused space within the async let block. | ||
void *getPreallocatedSpace(); | ||
|
||
/// Return the size of the unused space within the async let block. | ||
static size_t getSizeOfPreallocatedSpace(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❤️ |
||
|
||
/// Was the task allocated out of the parent's allocator? | ||
bool didAllocateFromParentTask(); | ||
|
||
/// Flag that the task was allocated from the parent's allocator. | ||
void setDidAllocateFromParentTask(bool value = true); | ||
}; | ||
|
||
} // end namespace swift | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,8 +53,8 @@ enum { | |
/// The number of words in a task group. | ||
NumWords_TaskGroup = 32, | ||
|
||
/// The number of words in an AsyncLet (flags + task pointer) | ||
NumWords_AsyncLet = 8, // TODO: not sure how much is enough, these likely could be pretty small | ||
/// The number of words in an AsyncLet (flags + child task context & allocation) | ||
NumWords_AsyncLet = 80, // 640 bytes ought to be enough for anyone | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious why so much, is it because the task context and place where we'll store the result basically ends up statically as large as the result struct as well, so we effectively have a lot of bytes to use without additional alloc? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We use the same block for the child task's initial slab, and the previous code gave the child 512 bytes of slab to begin with. 640 seemed like a nice round number to allow for some growth in the task context header overhead and still ensure at least 512 bytes still remain for the child context to start with. |
||
}; | ||
|
||
struct InProcess; | ||
|
@@ -2145,8 +2145,11 @@ enum class TaskOptionRecordKind : uint8_t { | |
Executor = 0, | ||
/// Request a child task to be part of a specific task group. | ||
TaskGroup = 1, | ||
/// DEPRECATED. AsyncLetWithBuffer is used instead. | ||
/// Request a child task for an 'async let'. | ||
AsyncLet = 2, | ||
/// Request a child task for an 'async let'. | ||
AsyncLetWithBuffer = 3, | ||
}; | ||
|
||
/// Flags for cancellation records. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -240,6 +240,7 @@ bool swift_taskGroup_isCancelled(TaskGroup *group); | |
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) | ||
bool swift_taskGroup_isEmpty(TaskGroup *group); | ||
|
||
/// DEPRECATED. swift_asyncLet_begin is used instead. | ||
/// Its Swift signature is | ||
/// | ||
/// \code | ||
|
@@ -255,12 +256,31 @@ void swift_asyncLet_start(AsyncLet *alet, | |
const Metadata *futureResultType, | ||
void *closureEntryPoint, HeapObject *closureContext); | ||
|
||
/// Begin an async let child task. | ||
/// Its Swift signature is | ||
/// | ||
/// \code | ||
/// func swift_asyncLet_start<T>( | ||
/// asyncLet: Builtin.RawPointer, | ||
/// options: Builtin.RawPointer?, | ||
/// operation: __owned @Sendable () async throws -> T, | ||
/// resultBuffer: Builtin.RawPointer | ||
/// ) | ||
/// \endcode | ||
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) | ||
void swift_asyncLet_begin(AsyncLet *alet, | ||
TaskOptionRecord *options, | ||
const Metadata *futureResultType, | ||
void *closureEntryPoint, HeapObject *closureContext, | ||
void *resultBuffer); | ||
|
||
/// This matches the ABI of a closure `<T>(Builtin.RawPointer) async -> T` | ||
using AsyncLetWaitSignature = | ||
SWIFT_CC(swiftasync) | ||
void(OpaqueValue *, | ||
SWIFT_ASYNC_CONTEXT AsyncContext *, AsyncTask *, Metadata *); | ||
|
||
/// DEPRECATED. swift_asyncLet_get is used instead. | ||
/// Wait for a non-throwing async-let to complete. | ||
/// | ||
/// This can be called from any thread. Its Swift signature is | ||
|
@@ -276,6 +296,7 @@ void swift_asyncLet_wait(OpaqueValue *, | |
AsyncLet *, TaskContinuationFunction *, | ||
AsyncContext *); | ||
|
||
/// DEPRECATED. swift_asyncLet_get_throwing is used instead. | ||
/// Wait for a potentially-throwing async-let to complete. | ||
/// | ||
/// This can be called from any thread. Its Swift signature is | ||
|
@@ -292,6 +313,7 @@ void swift_asyncLet_wait_throwing(OpaqueValue *, | |
ThrowingTaskFutureWaitContinuationFunction *, | ||
AsyncContext *); | ||
|
||
/// DEPRECATED. swift_asyncLet_finish is used instead. | ||
/// Its Swift signature is | ||
/// | ||
/// \code | ||
|
@@ -300,6 +322,121 @@ void swift_asyncLet_wait_throwing(OpaqueValue *, | |
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) | ||
void swift_asyncLet_end(AsyncLet *alet); | ||
|
||
/// Get the value of a non-throwing async-let, awaiting the result if necessary. | ||
/// | ||
/// This can be called from any thread. Its Swift signature is | ||
/// | ||
/// \code | ||
/// func swift_asyncLet_get( | ||
/// _ asyncLet: Builtin.RawPointer, | ||
/// _ resultBuffer: Builtin.RawPointer | ||
/// ) async | ||
/// \endcode | ||
/// | ||
/// \c result points at the variable storage for the binding. It is | ||
/// uninitialized until the first call to \c swift_asyncLet_get or | ||
/// \c swift_asyncLet_get_throwing. That first call initializes the storage | ||
/// with the result of the child task. Subsequent calls do nothing and leave | ||
/// the value in place. | ||
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync) | ||
void swift_asyncLet_get(SWIFT_ASYNC_CONTEXT AsyncContext *, | ||
AsyncLet *, | ||
void *, | ||
TaskContinuationFunction *, | ||
AsyncContext *); | ||
|
||
/// Get the value of a throwing async-let, awaiting the result if necessary. | ||
/// | ||
/// This can be called from any thread. Its Swift signature is | ||
/// | ||
/// \code | ||
/// func swift_asyncLet_get_throwing( | ||
/// _ asyncLet: Builtin.RawPointer, | ||
/// _ resultBuffer: Builtin.RawPointer | ||
/// ) async throws | ||
/// \endcode | ||
/// | ||
/// \c result points at the variable storage for the binding. It is | ||
/// uninitialized until the first call to \c swift_asyncLet_get or | ||
/// \c swift_asyncLet_get_throwing. That first call initializes the storage | ||
/// with the result of the child task. Subsequent calls do nothing and leave | ||
/// the value in place. A pointer to the storage inside the child task is | ||
/// returned if the task completes successfully, otherwise the error from the | ||
/// child task is thrown. | ||
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync) | ||
void swift_asyncLet_get_throwing(SWIFT_ASYNC_CONTEXT AsyncContext *, | ||
AsyncLet *, | ||
void *, | ||
ThrowingTaskFutureWaitContinuationFunction *, | ||
AsyncContext *); | ||
|
||
/// Exit the scope of an async-let binding. If the task is still running, it | ||
/// is cancelled, and we await its completion; otherwise, we destroy the | ||
/// value in the variable storage. | ||
/// | ||
/// Its Swift signature is | ||
/// | ||
/// \code | ||
/// func swift_asyncLet_finish(_ asyncLet: Builtin.RawPointer, | ||
/// _ resultBuffer: Builtin.RawPointer) async | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor nitpick: use same formatting as other docs? (cut down at the parameters) |
||
/// \endcode | ||
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync) | ||
void swift_asyncLet_finish(SWIFT_ASYNC_CONTEXT AsyncContext *, | ||
AsyncLet *, | ||
void *, | ||
TaskContinuationFunction *, | ||
AsyncContext *); | ||
|
||
/// Get the value of a non-throwing async-let, awaiting the result if necessary, | ||
/// and then destroy the child task. The result buffer is left initialized after | ||
/// returning. | ||
/// | ||
/// This can be called from any thread. Its Swift signature is | ||
/// | ||
/// \code | ||
/// func swift_asyncLet_get( | ||
/// _ asyncLet: Builtin.RawPointer, | ||
/// _ resultBuffer: Builtin.RawPointer | ||
/// ) async | ||
/// \endcode | ||
/// | ||
/// \c result points at the variable storage for the binding. It is | ||
/// uninitialized until the first call to \c swift_asyncLet_get or | ||
/// \c swift_asyncLet_get_throwing. The child task will be invalidated after | ||
/// this call, so the `async let` can not be gotten or finished afterward. | ||
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync) | ||
void swift_asyncLet_consume(SWIFT_ASYNC_CONTEXT AsyncContext *, | ||
AsyncLet *, | ||
void *, | ||
TaskContinuationFunction *, | ||
AsyncContext *); | ||
|
||
/// Get the value of a throwing async-let, awaiting the result if necessary, | ||
/// and then destroy the child task. The result buffer is left initialized after | ||
/// returning. | ||
/// | ||
/// This can be called from any thread. Its Swift signature is | ||
/// | ||
/// \code | ||
/// func swift_asyncLet_get_throwing( | ||
/// _ asyncLet: Builtin.RawPointer, | ||
/// _ resultBuffer: Builtin.RawPointer | ||
/// ) async throws | ||
/// \endcode | ||
/// | ||
/// \c result points at the variable storage for the binding. It is | ||
/// uninitialized until the first call to \c swift_asyncLet_get or | ||
/// \c swift_asyncLet_get_throwing. That first call initializes the storage | ||
/// with the result of the child task. Subsequent calls do nothing and leave | ||
/// the value in place. The child task will be invalidated after | ||
/// this call, so the `async let` can not be gotten or finished afterward. | ||
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync) | ||
void swift_asyncLet_consume_throwing(SWIFT_ASYNC_CONTEXT AsyncContext *, | ||
AsyncLet *, | ||
void *, | ||
ThrowingTaskFutureWaitContinuationFunction *, | ||
AsyncContext *); | ||
|
||
/// Returns true if the currently executing AsyncTask has a | ||
/// 'TaskGroupTaskStatusRecord' present. | ||
/// | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -1682,11 +1682,32 @@ FUNCTION(AsyncLetStart, | |||||
swift_asyncLet_start, SwiftCC, | ||||||
ConcurrencyAvailability, | ||||||
RETURNS(VoidTy), | ||||||
ARGS(SwiftAsyncLetPtrTy, // AsyncLet* | ||||||
SwiftTaskOptionRecordPtrTy, // options | ||||||
TypeMetadataPtrTy, // futureResultType | ||||||
Int8PtrTy, // closureEntry | ||||||
OpaquePtrTy // closureContext | ||||||
), | ||||||
ATTRS(NoUnwind, ArgMemOnly)) | ||||||
|
||||||
/// void swift_asyncLet_begin( | ||||||
/// AsyncLet *alet, | ||||||
/// TaskOptionRecord *options, | ||||||
/// const Metadata *futureResultType, | ||||||
/// void *closureEntryPoint, | ||||||
/// HeapObject *closureContext, | ||||||
/// void *resultBuffer | ||||||
/// ); | ||||||
FUNCTION(AsyncLetBegin, | ||||||
swift_asyncLet_begin, SwiftCC, | ||||||
ConcurrencyAvailability, | ||||||
RETURNS(VoidTy), | ||||||
ARGS(SwiftAsyncLetPtrTy, // AsyncLet* | ||||||
SwiftTaskOptionRecordPtrTy, // options | ||||||
TypeMetadataPtrTy, // futureResultType | ||||||
Int8PtrTy, // closureEntry | ||||||
OpaquePtrTy, // closureContext | ||||||
Int8PtrTy | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
? |
||||||
), | ||||||
ATTRS(NoUnwind, ArgMemOnly)) | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is solved now by this PR! :-)
specifically by:
llvm::PointerIntPair<AsyncTask *, 1, bool> taskAndHasResult;
that boolean is exactly the flag this was alluding to.