Skip to content

Commit fc67ba5

Browse files
authored
Merge pull request #37938 from jckarter/async-let-multi-suspend
Handle multiple awaits and suspend-on-exit for async let tasks.
2 parents 1f23e44 + 7470155 commit fc67ba5

33 files changed

+1048
-145
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
@@ -793,18 +793,43 @@ BUILTIN_MISC_OPERATION_WITH_SILGEN(CancelAsyncTask, "cancelAsyncTask", "", Speci
793793
/// __owned @Sendable @escaping () async throws -> T
794794
/// ) -> Builtin.RawPointer
795795
///
796+
/// DEPRECATED. startAsyncLetWithLocalBuffer is used instead.
797+
///
796798
/// Create, initialize and start a new async-let and associated task.
797799
/// Returns an AsyncLet* that must be passed to endAsyncLet for destruction.
798800
BUILTIN_MISC_OPERATION(StartAsyncLet, "startAsyncLet", "", Special)
799801

800-
/// asyncLetEnd(): (Builtin.RawPointer) -> Void
802+
/// startAsyncLetWithLocalBuffer()<T>: (
803+
/// __owned @Sendable @escaping () async throws -> T,
804+
/// _ resultBuf: Builtin.RawPointer
805+
/// ) -> Builtin.RawPointer
806+
///
807+
/// Create, initialize and start a new async-let and associated task, with a
808+
/// locally-allocated buffer assigned to receive the result if the task
809+
/// completes.
810+
/// Returns an AsyncLet* that must be passed to endAsyncLetLifetime for
811+
/// destruction.
812+
BUILTIN_MISC_OPERATION(StartAsyncLetWithLocalBuffer, "startAsyncLetWithLocalBuffer", "", Special)
813+
814+
/// endAsyncLet(): (Builtin.RawPointer) -> Void
815+
///
816+
/// DEPRECATED. The swift_asyncLet_finish intrinsic and endAsyncLetLifetime
817+
/// builtin are used instead.
801818
///
802819
/// Ends and destroys an async-let.
803820
/// The ClosureLifetimeFixup pass adds a second operand to the builtin to
804821
/// ensure that optimizations keep the stack-allocated closure arguments alive
805822
/// until the endAsyncLet.
806823
BUILTIN_MISC_OPERATION_WITH_SILGEN(EndAsyncLet, "endAsyncLet", "", Special)
807824

825+
/// endAsyncLetLifetime(): (Builtin.RawPointer) -> Void
826+
///
827+
/// Marks the end of an async-let's lifetime.
828+
/// The ClosureLifetimeFixup pass adds a second operand to the builtin to
829+
/// ensure that optimizations keep the stack-allocated closure arguments alive
830+
/// until the endAsyncLet.
831+
BUILTIN_MISC_OPERATION(EndAsyncLetLifetime, "endAsyncLetLifetime", "", Special)
832+
808833
/// createAsyncTask(): (
809834
/// Int, // task-creation flags
810835
/// @escaping () async throws -> T // function

include/swift/AST/DiagnosticsSema.def

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

3714+
// Cannot capture `async let`
3715+
ERROR(capture_async_let_not_supported,none,
3716+
"capturing 'async let' variables is not supported", ())
3717+
37143718
ERROR(self_assignment_var,none,
37153719
"assigning a variable to itself", ())
37163720
ERROR(self_assignment_prop,none,

include/swift/Runtime/Concurrency.h

Lines changed: 137 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,121 @@ 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+
303440
/// Returns true if the currently executing AsyncTask has a
304441
/// 'TaskGroupTaskStatusRecord' present.
305442
///

include/swift/Runtime/RuntimeFunctions.def

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,11 +1700,32 @@ FUNCTION(AsyncLetStart,
17001700
swift_asyncLet_start, SwiftCC,
17011701
ConcurrencyAvailability,
17021702
RETURNS(VoidTy),
1703+
ARGS(SwiftAsyncLetPtrTy, // AsyncLet*
1704+
SwiftTaskOptionRecordPtrTy, // options
1705+
TypeMetadataPtrTy, // futureResultType
1706+
Int8PtrTy, // closureEntry
1707+
OpaquePtrTy // closureContext
1708+
),
1709+
ATTRS(NoUnwind, ArgMemOnly))
1710+
1711+
/// void swift_asyncLet_begin(
1712+
/// AsyncLet *alet,
1713+
/// TaskOptionRecord *options,
1714+
/// const Metadata *futureResultType,
1715+
/// void *closureEntryPoint,
1716+
/// HeapObject *closureContext,
1717+
/// void *resultBuffer
1718+
/// );
1719+
FUNCTION(AsyncLetBegin,
1720+
swift_asyncLet_begin, SwiftCC,
1721+
ConcurrencyAvailability,
1722+
RETURNS(VoidTy),
17031723
ARGS(SwiftAsyncLetPtrTy, // AsyncLet*
17041724
SwiftTaskOptionRecordPtrTy, // options
17051725
TypeMetadataPtrTy, // futureResultType
17061726
Int8PtrTy, // closureEntry
17071727
OpaquePtrTy, // closureContext
1728+
Int8PtrTy
17081729
),
17091730
ATTRS(NoUnwind, ArgMemOnly))
17101731

lib/AST/Builtins.cpp

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

28142814
case BuiltinValueKind::StartAsyncLet:
2815+
case BuiltinValueKind::StartAsyncLetWithLocalBuffer:
28152816
return getStartAsyncLet(Context, Id);
28162817

28172818
case BuiltinValueKind::EndAsyncLet:
2819+
case BuiltinValueKind::EndAsyncLetLifetime:
28182820
return getEndAsyncLet(Context, Id);
28192821

28202822
case BuiltinValueKind::CreateTaskGroup:

0 commit comments

Comments
 (0)