Skip to content

Commit 36b03a4

Browse files
authored
Merge pull request #35868 from grynspan/main
[Concurrency] Simplify the type story for `Continuation` by eliminating `Throwing` variants
2 parents e813bbe + 7e72154 commit 36b03a4

File tree

7 files changed

+207
-186
lines changed

7 files changed

+207
-186
lines changed

include/swift/AST/KnownSDKTypes.def

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ KNOWN_SDK_TYPE_DECL(ObjectiveC, ObjCBool, StructDecl, 0)
3737

3838
// TODO(async): These might move to the stdlib module when concurrency is
3939
// standardized
40-
KNOWN_SDK_TYPE_DECL(Concurrency, UnsafeContinuation, NominalTypeDecl, 1)
41-
KNOWN_SDK_TYPE_DECL(Concurrency, UnsafeThrowingContinuation, NominalTypeDecl, 1)
40+
KNOWN_SDK_TYPE_DECL(Concurrency, UnsafeContinuation, NominalTypeDecl, 2)
4241

4342
#undef KNOWN_SDK_TYPE_DECL

lib/SILGen/ResultPlan.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -487,13 +487,14 @@ class ForeignAsyncInitializationPlan final : public ResultPlan {
487487
calleeTypeInfo.substResultType, throws);
488488

489489
// Wrap the Builtin.RawUnsafeContinuation in an
490-
// Unsafe[Throwing]Continuation<T>.
491-
auto continuationDecl = throws
492-
? SGF.getASTContext().getUnsafeThrowingContinuationDecl()
493-
: SGF.getASTContext().getUnsafeContinuationDecl();
494-
490+
// UnsafeContinuation<T, E>.
491+
auto continuationDecl = SGF.getASTContext().getUnsafeContinuationDecl();
492+
493+
auto errorTy = throws
494+
? SGF.getASTContext().getExceptionType()
495+
: SGF.getASTContext().getNeverType();
495496
auto continuationTy = BoundGenericType::get(continuationDecl, Type(),
496-
calleeTypeInfo.substResultType)
497+
{ calleeTypeInfo.substResultType, errorTy })
497498
->getCanonicalType();
498499
auto wrappedContinuation =
499500
SGF.B.createStruct(loc,

lib/SILGen/SILGenThunk.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,9 @@ SILGenModule::getOrCreateForeignAsyncCompletionHandlerImplFunction(
186186
CanSILFunctionType blockType,
187187
CanType continuationTy,
188188
ForeignAsyncConvention convention) {
189-
// Extract the result type from the continuation type.
189+
// Extract the result and error types from the continuation type.
190190
auto resumeType = cast<BoundGenericType>(continuationTy).getGenericArgs()[0];
191-
191+
192192
// Build up the implementation function type, which matches the
193193
// block signature with an added block storage argument that points at the
194194
// block buffer. The block storage holds the continuation we feed the
@@ -249,13 +249,14 @@ SILGenModule::getOrCreateForeignAsyncCompletionHandlerImplFunction(
249249

250250
// Check for an error if the convention includes one.
251251
auto errorIndex = convention.completionHandlerErrorParamIndex();
252-
253-
FuncDecl *resumeIntrinsic, *errorIntrinsic;
252+
253+
FuncDecl *resumeIntrinsic;
254+
Type replacementTypes[] = {resumeType};
254255

255256
SILBasicBlock *returnBB = nullptr;
256257
if (errorIndex) {
257258
resumeIntrinsic = getResumeUnsafeThrowingContinuation();
258-
errorIntrinsic = getResumeUnsafeThrowingContinuationWithError();
259+
auto errorIntrinsic = getResumeUnsafeThrowingContinuationWithError();
259260

260261
auto errorArgument = params[*errorIndex + 1];
261262
auto someErrorBB = SGF.createBasicBlock(FunctionSection::Postmatter);
@@ -281,7 +282,6 @@ SILGenModule::getOrCreateForeignAsyncCompletionHandlerImplFunction(
281282
// Resume the continuation as throwing the given error, bridged to a
282283
// native Swift error.
283284
auto nativeError = SGF.emitBridgedToNativeError(loc, matchedError);
284-
Type replacementTypes[] = {resumeType};
285285
auto subs = SubstitutionMap::get(errorIntrinsic->getGenericSignature(),
286286
replacementTypes,
287287
ArrayRef<ProtocolConformanceRef>{});
@@ -343,7 +343,6 @@ SILGenModule::getOrCreateForeignAsyncCompletionHandlerImplFunction(
343343

344344
// Resume the continuation with the composed bridged result.
345345
ManagedValue resumeArg = SGF.emitManagedBufferWithCleanup(resumeArgBuf);
346-
Type replacementTypes[] = {resumeType};
347346
auto subs = SubstitutionMap::get(resumeIntrinsic->getGenericSignature(),
348347
replacementTypes,
349348
ArrayRef<ProtocolConformanceRef>{});

stdlib/public/Concurrency/CheckedContinuation.swift

Lines changed: 66 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,7 @@ internal final class CheckedContinuationCanary {
3636
return functionPtr.assumingMemoryBound(to: String.self)
3737
}
3838

39-
internal static func create<T>(continuation: UnsafeContinuation<T>,
40-
function: String) -> Self {
41-
return _create(
42-
continuation: unsafeBitCast(continuation, to: UnsafeRawPointer.self),
43-
function: function)
44-
}
45-
46-
internal static func create<T>(continuation: UnsafeThrowingContinuation<T>,
39+
internal static func create<T, E>(continuation: UnsafeContinuation<T, E>,
4740
function: String) -> Self {
4841
return _create(
4942
continuation: unsafeBitCast(continuation, to: UnsafeRawPointer.self),
@@ -56,23 +49,14 @@ internal final class CheckedContinuationCanary {
5649

5750
// Take the continuation away from the container, or return nil if it's
5851
// already been taken.
59-
private func _takeContinuation() -> UnsafeRawPointer? {
52+
internal func takeContinuation<T, E>() -> UnsafeContinuation<T, E>? {
6053
// Atomically exchange the current continuation value with a null pointer.
6154
let rawContinuationPtr = unsafeBitCast(_continuationPtr,
6255
to: Builtin.RawPointer.self)
6356
let rawOld = Builtin.atomicrmw_xchg_seqcst_Word(rawContinuationPtr,
6457
0._builtinWordValue)
6558

66-
return unsafeBitCast(rawOld, to: UnsafeRawPointer?.self)
67-
}
68-
69-
internal func takeContinuation<T>() -> UnsafeContinuation<T>? {
70-
return unsafeBitCast(_takeContinuation(),
71-
to: UnsafeContinuation<T>?.self)
72-
}
73-
internal func takeThrowingContinuation<T>() -> UnsafeThrowingContinuation<T>? {
74-
return unsafeBitCast(_takeContinuation(),
75-
to: UnsafeThrowingContinuation<T>?.self)
59+
return unsafeBitCast(rawOld, to: UnsafeContinuation<T, E>?.self)
7660
}
7761

7862
deinit {
@@ -97,27 +81,30 @@ internal final class CheckedContinuationCanary {
9781
/// is abandoned without resuming the task, then the task will be stuck in
9882
/// the suspended state forever, and conversely, if the same continuation is
9983
/// resumed multiple times, it will put the task in an undefined state.
84+
///
10085
/// `UnsafeContinuation` avoids enforcing these invariants at runtime because
10186
/// it aims to be a low-overhead mechanism for interfacing Swift tasks with
10287
/// event loops, delegate methods, callbacks, and other non-`async` scheduling
10388
/// mechanisms. However, during development, being able to verify that the
10489
/// invariants are being upheld in testing is important.
10590
///
10691
/// `CheckedContinuation` is designed to be a drop-in API replacement for
107-
/// `UnsafeContinuation` that can be used for testing purposes, at the cost
108-
/// of an extra allocation and indirection for the wrapper object.
109-
/// Changing a call of `withUnsafeContinuation` into a call of
110-
/// `withCheckedContinuation` should be enough to obtain the extra checking
111-
/// without further source modification in most circumstances.
112-
public struct CheckedContinuation<T> {
113-
let canary: CheckedContinuationCanary
92+
/// `UnsafeContinuation` that can be used for testing purposes, at the cost of
93+
/// an extra allocation and indirection for the wrapper object. Changing a call
94+
/// of `withUnsafeContinuation` or `withUnsafeThrowingContinuation` into a call
95+
/// of `withCheckedContinuation` or `withCheckedThrowingContinuation` should be
96+
/// enough to obtain the extra checking without further source modification in
97+
/// most circumstances.
98+
public struct CheckedContinuation<T, E: Error> {
99+
private let canary: CheckedContinuationCanary
114100

115101
/// Initialize a `CheckedContinuation` wrapper around an
116102
/// `UnsafeContinuation`.
117103
///
118-
/// In most cases, you should use `withCheckedContinuation` instead.
119-
/// You only need to initialize your own `CheckedContinuation<T>` if you
120-
/// already have an `UnsafeContinuation` you want to impose checking on.
104+
/// In most cases, you should use `withCheckedContinuation` or
105+
/// `withCheckedThrowingContinuation` instead. You only need to initialize
106+
/// your own `CheckedContinuation<T, E>` if you already have an
107+
/// `UnsafeContinuation` you want to impose checking on.
121108
///
122109
/// - Parameters:
123110
/// - continuation: a fresh `UnsafeContinuation` that has not yet
@@ -126,7 +113,7 @@ public struct CheckedContinuation<T> {
126113
/// - function: a string identifying the declaration that is the notional
127114
/// source for the continuation, used to identify the continuation in
128115
/// runtime diagnostics related to misuse of this continuation.
129-
public init(continuation: UnsafeContinuation<T>, function: String = #function) {
116+
public init(continuation: UnsafeContinuation<T, E>, function: String = #function) {
130117
canary = CheckedContinuationCanary.create(
131118
continuation: continuation,
132119
function: function)
@@ -135,6 +122,8 @@ public struct CheckedContinuation<T> {
135122
/// Resume the task awaiting the continuation by having it return normally
136123
/// from its suspension point.
137124
///
125+
/// - Parameter value: The value to return from the continuation.
126+
///
138127
/// A continuation must be resumed exactly once. If the continuation has
139128
/// already been resumed through this object, then the attempt to resume
140129
/// the continuation again will trap.
@@ -143,107 +132,41 @@ public struct CheckedContinuation<T> {
143132
/// the caller. The task will continue executing when its executor is
144133
/// able to reschedule it.
145134
public func resume(returning x: __owned T) {
146-
if let c: UnsafeContinuation<T> = canary.takeContinuation() {
135+
if let c: UnsafeContinuation<T, E> = canary.takeContinuation() {
147136
c.resume(returning: x)
148137
} else {
149138
fatalError("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, returning \(x)!\n")
150139
}
151140
}
152-
}
153-
154-
extension CheckedContinuation where T == Void {
155-
/// Resume the task awaiting the continuation by having it return normally
156-
/// from its suspension point.
157-
///
158-
/// A continuation must be resumed exactly once. If the continuation has
159-
/// already been resumed through this object, then the attempt to resume
160-
/// the continuation again will trap.
161-
///
162-
/// After `resume` enqueues the task, control is immediately returned to
163-
/// the caller. The task will continue executing when its executor is
164-
/// able to reschedule it.
165-
@inlinable
166-
public func resume() {
167-
self.resume(returning: ())
168-
}
169-
}
170-
171-
public func withCheckedContinuation<T>(
172-
function: String = #function,
173-
_ body: (CheckedContinuation<T>) -> Void
174-
) async -> T {
175-
return await withUnsafeContinuation {
176-
body(CheckedContinuation(continuation: $0, function: function))
177-
}
178-
}
179-
180-
/// A wrapper class for `UnsafeThrowingContinuation` that logs misuses of the
181-
/// continuation, logging a message if the continuation is resumed
182-
/// multiple times, or if an object is destroyed without its continuation
183-
/// ever being resumed.
184-
///
185-
/// Raw `UnsafeThrowingContinuation`, like other unsafe constructs, requires the
186-
/// user to apply it correctly in order to maintain invariants. The key
187-
/// invariant is that the continuation must be resumed exactly once,
188-
/// and bad things happen if this invariant is not upheld--if a continuation
189-
/// is abandoned without resuming the task, then the task will be stuck in
190-
/// the suspended state forever, and conversely, if the same continuation is
191-
/// resumed multiple times, it will put the task in an undefined state.
192-
/// `UnsafeThrowingContinuation` avoids enforcing these invariants at runtime because
193-
/// it aims to be a low-overhead mechanism for interfacing Swift tasks with
194-
/// event loops, delegate methods, callbacks, and other non-`async` scheduling
195-
/// mechanisms. However, during development, being able to verify that the
196-
/// invariants are being upheld in testing is important.
197-
///
198-
/// `CheckedThrowingContinuation` is designed to be a drop-in API replacement for
199-
/// `UnsafeThrowingContinuation` that can be used for testing purposes, at the cost
200-
/// of an extra allocation and indirection for the wrapper object.
201-
/// Changing a call of `withUnsafeThrowingContinuation` into a call of
202-
/// `withCheckedThrowingContinuation` should be enough to obtain the extra checking
203-
/// without further source modification in most circumstances.
204-
public struct CheckedThrowingContinuation<T> {
205-
let canary: CheckedContinuationCanary
206141

207-
/// Initialize a `CheckedThrowingContinuation` wrapper around an
208-
/// `UnsafeThrowingContinuation`.
209-
///
210-
/// In most cases, you should use `withCheckedThrowingContinuation` instead.
211-
/// You only need to initialize your own `CheckedThrowingContinuation<T>` if you
212-
/// already have an `UnsafeThrowingContinuation` you want to impose checking on.
213-
///
214-
/// - Parameters:
215-
/// - continuation: a fresh `UnsafeThrowingContinuation` that has not yet
216-
/// been resumed. The `UnsafeThrowingContinuation` must not be used outside of
217-
/// this object once it's been given to the new object.
218-
/// - function: a string identifying the declaration that is the notional
219-
/// source for the continuation, used to identify the continuation in
220-
/// runtime diagnostics related to misuse of this continuation.
221-
public init(continuation: UnsafeThrowingContinuation<T>, function: String = #function) {
222-
canary = CheckedContinuationCanary.create(
223-
continuation: continuation,
224-
function: function)
225-
}
226-
227-
/// Resume the task awaiting the continuation by having it return normally
142+
/// Resume the task awaiting the continuation by having it throw an error
228143
/// from its suspension point.
229144
///
145+
/// - Parameter error: The error to throw from the continuation.
146+
///
230147
/// A continuation must be resumed exactly once. If the continuation has
231148
/// already been resumed through this object, then the attempt to resume
232149
/// the continuation again will trap.
233150
///
234151
/// After `resume` enqueues the task, control is immediately returned to
235152
/// the caller. The task will continue executing when its executor is
236153
/// able to reschedule it.
237-
public func resume(returning x: __owned T) {
238-
if let c: UnsafeThrowingContinuation<T> = canary.takeThrowingContinuation() {
239-
c.resume(returning: x)
154+
public func resume(throwing x: __owned E) {
155+
if let c: UnsafeContinuation<T, E> = canary.takeContinuation() {
156+
c.resume(throwing: x)
240157
} else {
241-
fatalError("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, returning \(x)!\n")
158+
fatalError("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, throwing \(x)!\n")
242159
}
243160
}
244-
245-
/// Resume the task awaiting the continuation by having it throw an error
246-
/// from its suspension point.
161+
}
162+
163+
extension CheckedContinuation {
164+
/// Resume the task awaiting the continuation by having it either
165+
/// return normally or throw an error based on the state of the given
166+
/// `Result` value.
167+
///
168+
/// - Parameter result: A value to either return or throw from the
169+
/// continuation.
247170
///
248171
/// A continuation must be resumed exactly once. If the continuation has
249172
/// already been resumed through this object, then the attempt to resume
@@ -252,36 +175,40 @@ public struct CheckedThrowingContinuation<T> {
252175
/// After `resume` enqueues the task, control is immediately returned to
253176
/// the caller. The task will continue executing when its executor is
254177
/// able to reschedule it.
255-
public func resume(throwing x: __owned Error) {
256-
if let c: UnsafeThrowingContinuation<T> = canary.takeThrowingContinuation() {
257-
c.resume(throwing: x)
258-
} else {
259-
fatalError("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, throwing \(x)!\n")
178+
@_alwaysEmitIntoClient
179+
public func resume<Er: Error>(with result: Result<T, Er>) where E == Error {
180+
switch result {
181+
case .success(let val):
182+
self.resume(returning: val)
183+
case .failure(let err):
184+
self.resume(throwing: err)
260185
}
261186
}
262187

263188
/// Resume the task awaiting the continuation by having it either
264189
/// return normally or throw an error based on the state of the given
265190
/// `Result` value.
266191
///
192+
/// - Parameter result: A value to either return or throw from the
193+
/// continuation.
194+
///
267195
/// A continuation must be resumed exactly once. If the continuation has
268196
/// already been resumed through this object, then the attempt to resume
269197
/// the continuation again will trap.
270198
///
271199
/// After `resume` enqueues the task, control is immediately returned to
272200
/// the caller. The task will continue executing when its executor is
273201
/// able to reschedule it.
274-
public func resume<E: Error>(with x: __owned Result<T, E>) {
275-
switch x {
276-
case .success(let s):
277-
return resume(returning: s)
278-
case .failure(let e):
279-
return resume(throwing: e)
202+
@_alwaysEmitIntoClient
203+
public func resume(with result: Result<T, E>) {
204+
switch result {
205+
case .success(let val):
206+
self.resume(returning: val)
207+
case .failure(let err):
208+
self.resume(throwing: err)
280209
}
281210
}
282-
}
283211

284-
extension CheckedThrowingContinuation where T == Void {
285212
/// Resume the task awaiting the continuation by having it return normally
286213
/// from its suspension point.
287214
///
@@ -292,18 +219,27 @@ extension CheckedThrowingContinuation where T == Void {
292219
/// After `resume` enqueues the task, control is immediately returned to
293220
/// the caller. The task will continue executing when its executor is
294221
/// able to reschedule it.
295-
@inlinable
296-
public func resume() {
222+
@_alwaysEmitIntoClient
223+
public func resume() where T == Void {
297224
self.resume(returning: ())
298225
}
299226
}
300227

228+
public func withCheckedContinuation<T>(
229+
function: String = #function,
230+
_ body: (CheckedContinuation<T, Never>) -> Void
231+
) async -> T {
232+
return await withUnsafeContinuation {
233+
body(CheckedContinuation(continuation: $0, function: function))
234+
}
235+
}
236+
301237
public func withCheckedThrowingContinuation<T>(
302238
function: String = #function,
303-
_ body: (CheckedThrowingContinuation<T>) -> Void
239+
_ body: (CheckedContinuation<T, Error>) -> Void
304240
) async throws -> T {
305241
return try await withUnsafeThrowingContinuation {
306-
body(CheckedThrowingContinuation(continuation: $0, function: function))
242+
body(CheckedContinuation(continuation: $0, function: function))
307243
}
308244
}
309245

0 commit comments

Comments
 (0)