Skip to content

Commit 032de59

Browse files
authored
Merge pull request #38579 from jckarter/async-let-multi-suspend-5.5
[5.5] Handle multiple awaits and suspend-on-exit for async let tasks.
2 parents 158e170 + b1a9253 commit 032de59

34 files changed

+1080
-144
lines changed

include/swift/ABI/AsyncLet.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,29 @@ class alignas(Alignment_AsyncLet) AsyncLet {
3636
constexpr AsyncLet()
3737
: PrivateData{} {}
3838

39-
// FIXME: not sure how many words we should reserve
4039
void *PrivateData[NumWords_AsyncLet];
4140

4241
// TODO: we could offer a "was awaited on" check here
4342

4443
/// Returns the child task that is associated with this async let.
4544
/// The tasks completion is used to fulfil the value represented by this async let.
4645
AsyncTask *getTask() const;
47-
46+
47+
// The compiler preallocates a large fixed space for the `async let`, with the
48+
// intent that most of it be used for the child task context. The next two
49+
// methods return the address and size of that space.
50+
51+
/// Return a pointer to the unused space within the async let block.
52+
void *getPreallocatedSpace();
53+
54+
/// Return the size of the unused space within the async let block.
55+
static size_t getSizeOfPreallocatedSpace();
56+
57+
/// Was the task allocated out of the parent's allocator?
58+
bool didAllocateFromParentTask();
59+
60+
/// Flag that the task was allocated from the parent's allocator.
61+
void setDidAllocateFromParentTask(bool value = true);
4862
};
4963

5064
} // end namespace swift

include/swift/ABI/MetadataValues.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ enum {
5353
/// The number of words in a task group.
5454
NumWords_TaskGroup = 32,
5555

56-
/// The number of words in an AsyncLet (flags + task pointer)
57-
NumWords_AsyncLet = 8, // TODO: not sure how much is enough, these likely could be pretty small
56+
/// The number of words in an AsyncLet (flags + child task context & allocation)
57+
NumWords_AsyncLet = 80, // 640 bytes ought to be enough for anyone
5858
};
5959

6060
struct InProcess;
@@ -2145,8 +2145,11 @@ enum class TaskOptionRecordKind : uint8_t {
21452145
Executor = 0,
21462146
/// Request a child task to be part of a specific task group.
21472147
TaskGroup = 1,
2148+
/// DEPRECATED. AsyncLetWithBuffer is used instead.
21482149
/// Request a child task for an 'async let'.
21492150
AsyncLet = 2,
2151+
/// Request a child task for an 'async let'.
2152+
AsyncLetWithBuffer = 3,
21502153
};
21512154

21522155
/// Flags for cancellation records.

include/swift/ABI/TaskOptions.h

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,12 @@ class ExecutorTaskOptionRecord : public TaskOptionRecord {
9898
}
9999
};
100100

101+
/// DEPRECATED. AsyncLetWithBufferTaskOptionRecord is used instead.
101102
/// Task option to specify that the created task is for an 'async let'.
102103
class AsyncLetTaskOptionRecord : public TaskOptionRecord {
103104
AsyncLet *asyncLet;
104105

105-
public:
106+
public:
106107
AsyncLetTaskOptionRecord(AsyncLet *asyncLet)
107108
: TaskOptionRecord(TaskOptionRecordKind::AsyncLet),
108109
asyncLet(asyncLet) {}
@@ -116,6 +117,30 @@ class AsyncLetTaskOptionRecord : public TaskOptionRecord {
116117
}
117118
};
118119

120+
class AsyncLetWithBufferTaskOptionRecord : public TaskOptionRecord {
121+
AsyncLet *asyncLet;
122+
void *resultBuffer;
123+
124+
public:
125+
AsyncLetWithBufferTaskOptionRecord(AsyncLet *asyncLet,
126+
void *resultBuffer)
127+
: TaskOptionRecord(TaskOptionRecordKind::AsyncLetWithBuffer),
128+
asyncLet(asyncLet),
129+
resultBuffer(resultBuffer) {}
130+
131+
AsyncLet *getAsyncLet() const {
132+
return asyncLet;
133+
}
134+
135+
void *getResultBuffer() const {
136+
return resultBuffer;
137+
}
138+
139+
static bool classof(const TaskOptionRecord *record) {
140+
return record->getKind() == TaskOptionRecordKind::AsyncLetWithBuffer;
141+
}
142+
};
143+
119144
} // end namespace swift
120145

121146
#endif

include/swift/AST/Builtins.def

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -782,18 +782,43 @@ BUILTIN_MISC_OPERATION_WITH_SILGEN(CancelAsyncTask, "cancelAsyncTask", "", Speci
782782
/// __owned @Sendable @escaping () async throws -> T
783783
/// ) -> Builtin.RawPointer
784784
///
785+
/// DEPRECATED. startAsyncLetWithLocalBuffer is used instead.
786+
///
785787
/// Create, initialize and start a new async-let and associated task.
786788
/// Returns an AsyncLet* that must be passed to endAsyncLet for destruction.
787789
BUILTIN_MISC_OPERATION(StartAsyncLet, "startAsyncLet", "", Special)
788790

789-
/// asyncLetEnd(): (Builtin.RawPointer) -> Void
791+
/// startAsyncLetWithLocalBuffer()<T>: (
792+
/// __owned @Sendable @escaping () async throws -> T,
793+
/// _ resultBuf: Builtin.RawPointer
794+
/// ) -> Builtin.RawPointer
795+
///
796+
/// Create, initialize and start a new async-let and associated task, with a
797+
/// locally-allocated buffer assigned to receive the result if the task
798+
/// completes.
799+
/// Returns an AsyncLet* that must be passed to endAsyncLetLifetime for
800+
/// destruction.
801+
BUILTIN_MISC_OPERATION(StartAsyncLetWithLocalBuffer, "startAsyncLetWithLocalBuffer", "", Special)
802+
803+
/// endAsyncLet(): (Builtin.RawPointer) -> Void
804+
///
805+
/// DEPRECATED. The swift_asyncLet_finish intrinsic and endAsyncLetLifetime
806+
/// builtin are used instead.
790807
///
791808
/// Ends and destroys an async-let.
792809
/// The ClosureLifetimeFixup pass adds a second operand to the builtin to
793810
/// ensure that optimizations keep the stack-allocated closure arguments alive
794811
/// until the endAsyncLet.
795812
BUILTIN_MISC_OPERATION_WITH_SILGEN(EndAsyncLet, "endAsyncLet", "", Special)
796813

814+
/// endAsyncLetLifetime(): (Builtin.RawPointer) -> Void
815+
///
816+
/// Marks the end of an async-let's lifetime.
817+
/// The ClosureLifetimeFixup pass adds a second operand to the builtin to
818+
/// ensure that optimizations keep the stack-allocated closure arguments alive
819+
/// until the endAsyncLet.
820+
BUILTIN_MISC_OPERATION(EndAsyncLetLifetime, "endAsyncLetLifetime", "", Special)
821+
797822
/// createAsyncTask(): (
798823
/// Int, // task-creation flags
799824
/// @escaping () async throws -> T // function

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3700,6 +3700,10 @@ WARNING(partial_application_of_function_invalid_swift4,none,
37003700
"be an error in future Swift versions",
37013701
(unsigned))
37023702

3703+
// Cannot capture `async let`
3704+
ERROR(capture_async_let_not_supported,none,
3705+
"capturing 'async let' variables is not supported", ())
3706+
37033707
ERROR(self_assignment_var,none,
37043708
"assigning a variable to itself", ())
37053709
ERROR(self_assignment_prop,none,

include/swift/Runtime/Concurrency.h

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ bool swift_taskGroup_isCancelled(TaskGroup *group);
240240
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
241241
bool swift_taskGroup_isEmpty(TaskGroup *group);
242242

243+
/// DEPRECATED. swift_asyncLet_begin is used instead.
243244
/// Its Swift signature is
244245
///
245246
/// \code
@@ -255,12 +256,31 @@ void swift_asyncLet_start(AsyncLet *alet,
255256
const Metadata *futureResultType,
256257
void *closureEntryPoint, HeapObject *closureContext);
257258

259+
/// Begin an async let child task.
260+
/// Its Swift signature is
261+
///
262+
/// \code
263+
/// func swift_asyncLet_start<T>(
264+
/// asyncLet: Builtin.RawPointer,
265+
/// options: Builtin.RawPointer?,
266+
/// operation: __owned @Sendable () async throws -> T,
267+
/// resultBuffer: Builtin.RawPointer
268+
/// )
269+
/// \endcode
270+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
271+
void swift_asyncLet_begin(AsyncLet *alet,
272+
TaskOptionRecord *options,
273+
const Metadata *futureResultType,
274+
void *closureEntryPoint, HeapObject *closureContext,
275+
void *resultBuffer);
276+
258277
/// This matches the ABI of a closure `<T>(Builtin.RawPointer) async -> T`
259278
using AsyncLetWaitSignature =
260279
SWIFT_CC(swiftasync)
261280
void(OpaqueValue *,
262281
SWIFT_ASYNC_CONTEXT AsyncContext *, AsyncTask *, Metadata *);
263282

283+
/// DEPRECATED. swift_asyncLet_get is used instead.
264284
/// Wait for a non-throwing async-let to complete.
265285
///
266286
/// This can be called from any thread. Its Swift signature is
@@ -276,6 +296,7 @@ void swift_asyncLet_wait(OpaqueValue *,
276296
AsyncLet *, TaskContinuationFunction *,
277297
AsyncContext *);
278298

299+
/// DEPRECATED. swift_asyncLet_get_throwing is used instead.
279300
/// Wait for a potentially-throwing async-let to complete.
280301
///
281302
/// This can be called from any thread. Its Swift signature is
@@ -292,6 +313,7 @@ void swift_asyncLet_wait_throwing(OpaqueValue *,
292313
ThrowingTaskFutureWaitContinuationFunction *,
293314
AsyncContext *);
294315

316+
/// DEPRECATED. swift_asyncLet_finish is used instead.
295317
/// Its Swift signature is
296318
///
297319
/// \code
@@ -300,6 +322,134 @@ void swift_asyncLet_wait_throwing(OpaqueValue *,
300322
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
301323
void swift_asyncLet_end(AsyncLet *alet);
302324

325+
/// Get the value of a non-throwing async-let, awaiting the result if necessary.
326+
///
327+
/// This can be called from any thread. Its Swift signature is
328+
///
329+
/// \code
330+
/// func swift_asyncLet_get(
331+
/// _ asyncLet: Builtin.RawPointer,
332+
/// _ resultBuffer: Builtin.RawPointer
333+
/// ) async
334+
/// \endcode
335+
///
336+
/// \c result points at the variable storage for the binding. It is
337+
/// uninitialized until the first call to \c swift_asyncLet_get or
338+
/// \c swift_asyncLet_get_throwing. That first call initializes the storage
339+
/// with the result of the child task. Subsequent calls do nothing and leave
340+
/// the value in place.
341+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync)
342+
void swift_asyncLet_get(SWIFT_ASYNC_CONTEXT AsyncContext *,
343+
AsyncLet *,
344+
void *,
345+
TaskContinuationFunction *,
346+
AsyncContext *);
347+
348+
/// Get the value of a throwing async-let, awaiting the result if necessary.
349+
///
350+
/// This can be called from any thread. Its Swift signature is
351+
///
352+
/// \code
353+
/// func swift_asyncLet_get_throwing(
354+
/// _ asyncLet: Builtin.RawPointer,
355+
/// _ resultBuffer: Builtin.RawPointer
356+
/// ) async throws
357+
/// \endcode
358+
///
359+
/// \c result points at the variable storage for the binding. It is
360+
/// uninitialized until the first call to \c swift_asyncLet_get or
361+
/// \c swift_asyncLet_get_throwing. That first call initializes the storage
362+
/// with the result of the child task. Subsequent calls do nothing and leave
363+
/// the value in place. A pointer to the storage inside the child task is
364+
/// returned if the task completes successfully, otherwise the error from the
365+
/// child task is thrown.
366+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync)
367+
void swift_asyncLet_get_throwing(SWIFT_ASYNC_CONTEXT AsyncContext *,
368+
AsyncLet *,
369+
void *,
370+
ThrowingTaskFutureWaitContinuationFunction *,
371+
AsyncContext *);
372+
373+
/// Exit the scope of an async-let binding. If the task is still running, it
374+
/// is cancelled, and we await its completion; otherwise, we destroy the
375+
/// value in the variable storage.
376+
///
377+
/// Its Swift signature is
378+
///
379+
/// \code
380+
/// func swift_asyncLet_finish(_ asyncLet: Builtin.RawPointer,
381+
/// _ resultBuffer: Builtin.RawPointer) async
382+
/// \endcode
383+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync)
384+
void swift_asyncLet_finish(SWIFT_ASYNC_CONTEXT AsyncContext *,
385+
AsyncLet *,
386+
void *,
387+
TaskContinuationFunction *,
388+
AsyncContext *);
389+
390+
/// Get the value of a non-throwing async-let, awaiting the result if necessary,
391+
/// and then destroy the child task. The result buffer is left initialized after
392+
/// returning.
393+
///
394+
/// This can be called from any thread. Its Swift signature is
395+
///
396+
/// \code
397+
/// func swift_asyncLet_get(
398+
/// _ asyncLet: Builtin.RawPointer,
399+
/// _ resultBuffer: Builtin.RawPointer
400+
/// ) async
401+
/// \endcode
402+
///
403+
/// \c result points at the variable storage for the binding. It is
404+
/// uninitialized until the first call to \c swift_asyncLet_get or
405+
/// \c swift_asyncLet_get_throwing. The child task will be invalidated after
406+
/// this call, so the `async let` can not be gotten or finished afterward.
407+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync)
408+
void swift_asyncLet_consume(SWIFT_ASYNC_CONTEXT AsyncContext *,
409+
AsyncLet *,
410+
void *,
411+
TaskContinuationFunction *,
412+
AsyncContext *);
413+
414+
/// Get the value of a throwing async-let, awaiting the result if necessary,
415+
/// and then destroy the child task. The result buffer is left initialized after
416+
/// returning.
417+
///
418+
/// This can be called from any thread. Its Swift signature is
419+
///
420+
/// \code
421+
/// func swift_asyncLet_get_throwing(
422+
/// _ asyncLet: Builtin.RawPointer,
423+
/// _ resultBuffer: Builtin.RawPointer
424+
/// ) async throws
425+
/// \endcode
426+
///
427+
/// \c result points at the variable storage for the binding. It is
428+
/// uninitialized until the first call to \c swift_asyncLet_get or
429+
/// \c swift_asyncLet_get_throwing. That first call initializes the storage
430+
/// with the result of the child task. Subsequent calls do nothing and leave
431+
/// the value in place. The child task will be invalidated after
432+
/// this call, so the `async let` can not be gotten or finished afterward.
433+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync)
434+
void swift_asyncLet_consume_throwing(SWIFT_ASYNC_CONTEXT AsyncContext *,
435+
AsyncLet *,
436+
void *,
437+
ThrowingTaskFutureWaitContinuationFunction *,
438+
AsyncContext *);
439+
440+
/// Returns true if the currently executing AsyncTask has a
441+
/// 'TaskGroupTaskStatusRecord' present.
442+
///
443+
/// This can be called from any thread.
444+
///
445+
/// Its Swift signature is
446+
///
447+
/// \code
448+
/// func swift_taskGroup_hasTaskGroupRecord()
449+
/// \endcode
450+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
451+
bool swift_taskGroup_hasTaskGroupRecord();
452+
303453
/// Add a status record to a task. The record should not be
304454
/// modified while it is registered with a task.
305455
///

include/swift/Runtime/RuntimeFunctions.def

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,11 +1674,32 @@ FUNCTION(AsyncLetStart,
16741674
swift_asyncLet_start, SwiftCC,
16751675
ConcurrencyAvailability,
16761676
RETURNS(VoidTy),
1677+
ARGS(SwiftAsyncLetPtrTy, // AsyncLet*
1678+
SwiftTaskOptionRecordPtrTy, // options
1679+
TypeMetadataPtrTy, // futureResultType
1680+
Int8PtrTy, // closureEntry
1681+
OpaquePtrTy // closureContext
1682+
),
1683+
ATTRS(NoUnwind, ArgMemOnly))
1684+
1685+
/// void swift_asyncLet_begin(
1686+
/// AsyncLet *alet,
1687+
/// TaskOptionRecord *options,
1688+
/// const Metadata *futureResultType,
1689+
/// void *closureEntryPoint,
1690+
/// HeapObject *closureContext,
1691+
/// void *resultBuffer
1692+
/// );
1693+
FUNCTION(AsyncLetBegin,
1694+
swift_asyncLet_begin, SwiftCC,
1695+
ConcurrencyAvailability,
1696+
RETURNS(VoidTy),
16771697
ARGS(SwiftAsyncLetPtrTy, // AsyncLet*
16781698
SwiftTaskOptionRecordPtrTy, // options
16791699
TypeMetadataPtrTy, // futureResultType
16801700
Int8PtrTy, // closureEntry
16811701
OpaquePtrTy, // closureContext
1702+
Int8PtrTy
16821703
),
16831704
ATTRS(NoUnwind, ArgMemOnly))
16841705

lib/AST/Builtins.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2790,9 +2790,11 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
27902790
return getDefaultActorInitDestroy(Context, Id);
27912791

27922792
case BuiltinValueKind::StartAsyncLet:
2793+
case BuiltinValueKind::StartAsyncLetWithLocalBuffer:
27932794
return getStartAsyncLet(Context, Id);
27942795

27952796
case BuiltinValueKind::EndAsyncLet:
2797+
case BuiltinValueKind::EndAsyncLetLifetime:
27962798
return getEndAsyncLet(Context, Id);
27972799

27982800
case BuiltinValueKind::CreateTaskGroup:

0 commit comments

Comments
 (0)