Skip to content

Commit 4b4eb44

Browse files
committed
fix nested suspension issue with objc async calls
when two objc async functions are composed with each other, i.e., f(g()), then the clean-ups for g() would get emitted at an unexpected time, namely, during the suspension for the call to f(). This means that using a clean-up to emit the executor-hop breadcrumb was incorrect. The hop could appear between a get_async continuation and its matching await_continuation, which is an unsupported nested suspension. This commit fixes that by removing the use of the breadcrumb clean-up in favor of providing that breadcrumb directly to the result plan, so that it may be emitted later on when the result plan sees fit. Fixes rdar://91502776
1 parent 540ba2a commit 4b4eb44

File tree

7 files changed

+81
-35
lines changed

7 files changed

+81
-35
lines changed

lib/SILGen/ExecutorBreadcrumb.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
#ifndef SWIFT_SILGEN_EXECUTORBREADCRUMB_H
14+
#define SWIFT_SILGEN_EXECUTORBREADCRUMB_H
15+
1316
#include "swift/SIL/SILValue.h"
1417

1518
namespace swift {
@@ -46,3 +49,5 @@ class ExecutorBreadcrumb {
4649

4750
} // namespace Lowering
4851
} // namespace swift
52+
53+
#endif

lib/SILGen/ResultPlan.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ class ForeignAsyncInitializationPlan final : public ResultPlan {
498498
SILType opaqueResumeType;
499499
SILValue resumeBuf;
500500
SILValue continuation;
501+
ExecutorBreadcrumb breadcrumb;
501502

502503
public:
503504
ForeignAsyncInitializationPlan(SILGenFunction &SGF, SILLocation loc,
@@ -597,6 +598,10 @@ class ForeignAsyncInitializationPlan final : public ResultPlan {
597598
return ManagedValue::forUnmanaged(block);
598599
}
599600

601+
void deferExecutorBreadcrumb(ExecutorBreadcrumb &&breadcrumb) override {
602+
this->breadcrumb = breadcrumb;
603+
}
604+
600605
RValue finish(SILGenFunction &SGF, SILLocation loc, CanType substType,
601606
ArrayRef<ManagedValue> &directResults,
602607
SILValue bridgedForeignError) override {
@@ -692,6 +697,7 @@ class ForeignAsyncInitializationPlan final : public ResultPlan {
692697
// Propagate an error if we have one.
693698
if (errorBlock) {
694699
SGF.B.emitBlock(errorBlock);
700+
breadcrumb.emit(SGF, loc);
695701

696702
Scope errorScope(SGF, loc);
697703

@@ -703,6 +709,7 @@ class ForeignAsyncInitializationPlan final : public ResultPlan {
703709
}
704710

705711
SGF.B.emitBlock(resumeBlock);
712+
breadcrumb.emit(SGF, loc);
706713

707714
// The incoming value is the maximally-abstracted result type of the
708715
// continuation. Move it out of the resume buffer and reabstract it if
@@ -774,6 +781,10 @@ class ForeignErrorInitializationPlan final : public ResultPlan {
774781
AbstractionPattern(errorType), errorType);
775782
}
776783

784+
void deferExecutorBreadcrumb(ExecutorBreadcrumb &&breadcrumb) override {
785+
subPlan->deferExecutorBreadcrumb(std::move(breadcrumb));
786+
}
787+
777788
RValue finish(SILGenFunction &SGF, SILLocation loc, CanType substType,
778789
ArrayRef<ManagedValue> &directResults,
779790
SILValue bridgedForeignError) override {

lib/SILGen/ResultPlan.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#define SWIFT_SILGEN_RESULTPLAN_H
1515

1616
#include "Callee.h"
17+
#include "ExecutorBreadcrumb.h"
1718
#include "ManagedValue.h"
1819
#include "swift/AST/Types.h"
1920
#include "swift/Basic/LLVM.h"
@@ -42,6 +43,11 @@ class ResultPlan {
4243
SILValue bridgedForeignError) = 0;
4344
virtual ~ResultPlan() = default;
4445

46+
/// Defers the emission of the given breadcrumb until \p finish is invoked.
47+
virtual void deferExecutorBreadcrumb(ExecutorBreadcrumb &&breadcrumb) {
48+
llvm_unreachable("this ResultPlan does not handle deferred breadcrumbs!");
49+
}
50+
4551
virtual void
4652
gatherIndirectResultAddrs(SILGenFunction &SGF, SILLocation loc,
4753
SmallVectorImpl<SILValue> &outList) const = 0;

lib/SILGen/SILGenApply.cpp

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4427,27 +4427,6 @@ class FixLifetimeDestroyCleanup : public Cleanup {
44274427
#endif
44284428
}
44294429
};
4430-
4431-
class EmitBreadcrumbCleanup : public Cleanup {
4432-
ExecutorBreadcrumb breadcrumb;
4433-
4434-
public:
4435-
EmitBreadcrumbCleanup(ExecutorBreadcrumb &&breadcrumb)
4436-
: breadcrumb(std::move(breadcrumb)) {}
4437-
4438-
void emit(SILGenFunction &SGF, CleanupLocation l,
4439-
ForUnwind_t forUnwind) override {
4440-
breadcrumb.emit(SGF, l);
4441-
}
4442-
4443-
void dump(SILGenFunction &SGF) const override {
4444-
#ifndef NDEBUG
4445-
llvm::errs() << "EmitBreadcrumbCleanup "
4446-
<< "State:" << getState()
4447-
<< "NeedsEmit:" << breadcrumb.needsEmit();
4448-
#endif
4449-
}
4450-
};
44514430
} // end anonymous namespace
44524431

44534432
//===----------------------------------------------------------------------===//
@@ -4598,11 +4577,6 @@ RValue SILGenFunction::emitApply(
45984577
rawDirectResult = rawDirectResults[0];
45994578
}
46004579

4601-
if (!calleeTypeInfo.foreign.async) {
4602-
// hop back to the current executor
4603-
breadcrumb.emit(*this, loc);
4604-
}
4605-
46064580
// For objc async calls, lifetime extend the args until the result plan which
46074581
// generates `await_async_continuation`.
46084582
// Lifetime is extended by creating unmanaged copies here and by pushing the
@@ -4614,6 +4588,14 @@ RValue SILGenFunction::emitApply(
46144588
unmanagedCopies.push_back(arg.unmanagedCopy(*this, loc));
46154589
}
46164590
}
4591+
// similarly, we defer the emission of the breadcrumb until the result
4592+
// plan's finish method is called, because it must happen in the
4593+
// successors of the `await_async_continuation` terminator.
4594+
resultPlan->deferExecutorBreadcrumb(std::move(breadcrumb));
4595+
4596+
} else {
4597+
// In the ordinary case, we hop back to the current executor
4598+
breadcrumb.emit(*this, loc);
46174599
}
46184600

46194601
// Pop the argument scope.
@@ -4700,8 +4682,6 @@ RValue SILGenFunction::emitApply(
47004682
for (auto unmanagedCopy : unmanagedCopies) {
47014683
Cleanups.pushCleanup<FixLifetimeDestroyCleanup>(unmanagedCopy);
47024684
}
4703-
// save breadcrumb as a clean-up so it is emitted in result / throw cases.
4704-
Cleanups.pushCleanup<EmitBreadcrumbCleanup>(std::move(breadcrumb));
47054685
} else {
47064686
assert(unmanagedCopies.empty());
47074687
}

test/Inputs/clang-importer-sdk/usr/include/ObjCConcurrency.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,13 @@ struct StructWithSendableContents {
310310
SENDABLE id StructWithSendableContentsGetSendableComputed(struct StructWithSendableContents contents)
311311
__attribute__((swift_name("getter:StructWithSendableContents.sendableComputed(self:)")));
312312

313+
@interface CostcoManager : NSObject
314+
+ (instancetype)sharedManager;
315+
- (void)isCustomerEnrolledInExecutiveProgram:(NSObject *)customer completion:(void(^)(BOOL enrolled))completion;
316+
@end
317+
318+
@interface Person : NSObject
319+
+ (void)getAsCustomer:(void(^_Nonnull)(NSObject *device))completion;
320+
@end
321+
313322
#pragma clang assume_nonnull end

test/SILGen/objc_async.swift

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ func testGeneric2<T: AnyObject, U>(x: GenericObject<T>, y: U) async throws {
177177
// CHECK-LABEL: sil {{.*}}@${{.*}}22testSlowServerFromMain
178178
@MainActor
179179
func testSlowServerFromMain(slowServer: SlowServer) async throws {
180-
// CHECK: hop_to_executor %6 : $MainActor
180+
// CHECK: hop_to_executor {{%.*}} : $MainActor
181181
// CHECK: [[RESUME_BUF:%.*]] = alloc_stack $Int
182182
// CHECK: [[STRINGINIT:%.*]] = function_ref @$sSS10FoundationE19_bridgeToObjectiveCSo8NSStringCyF :
183183
// CHECK: [[ARG:%.*]] = apply [[STRINGINIT]]
@@ -194,8 +194,8 @@ func testSlowServerFromMain(slowServer: SlowServer) async throws {
194194
// CHECK: destroy_value [[ARG]]
195195
// CHECK: await_async_continuation [[CONT]] {{.*}}, resume [[RESUME:bb[0-9]+]]
196196
// CHECK: [[RESUME]]:
197+
// CHECK: hop_to_executor {{%.*}} : $MainActor
197198
// CHECK: [[RESULT:%.*]] = load [trivial] [[RESUME_BUF]]
198-
// CHECK: hop_to_executor %6 : $MainActor
199199
// CHECK: fix_lifetime [[COPY]]
200200
// CHECK: destroy_value [[COPY]]
201201
// CHECK: dealloc_stack [[RESUME_BUF]]
@@ -223,8 +223,8 @@ func testThrowingMethodFromMain(slowServer: SlowServer) async -> String {
223223
// CHECK: await_async_continuation [[RAW_CONT]] : $Builtin.RawUnsafeContinuation, resume [[RESUME:bb[0-9]+]], error [[ERROR:bb[0-9]+]]
224224

225225
// CHECK: [[RESUME]]
226-
// CHECK: {{.*}} = load [take] [[RESULT_BUF]] : $*String
227226
// CHECK: hop_to_executor {{%.*}} : $MainActor
227+
// CHECK: {{.*}} = load [take] [[RESULT_BUF]] : $*String
228228
// CHECK: fix_lifetime [[STRING_ARG_COPY]] : $NSString
229229
// CHECK: destroy_value [[STRING_ARG_COPY]] : $NSString
230230
// CHECK: dealloc_stack [[RESULT_BUF]] : $*String
@@ -241,3 +241,38 @@ func testThrowingMethodFromMain(slowServer: SlowServer) async -> String {
241241
return "none"
242242
}
243243
}
244+
245+
// rdar://91502776
246+
// CHECK-LABEL: sil hidden [ossa] @$s{{.*}}21checkCostcoMembershipSbyYaF : $@convention(thin) @async () -> Bool {
247+
// CHECK: bb0:
248+
// CHECK: hop_to_executor {{%.*}} : $Optional<Builtin.Executor>
249+
// CHECK: [[FINAL_BUF:%.*]] = alloc_stack $Bool
250+
// CHECK: [[RESULT_BUF:%.*]] = alloc_stack $NSObject
251+
// CHECK: [[METH:%.*]] = objc_method {{%.*}} : $@objc_metatype Person.Type, #Person.asCustomer!foreign
252+
// CHECK: get_async_continuation_addr NSObject, [[RESULT_BUF]] : $*NSObject
253+
// CHECK: = apply [[METH]]
254+
// CHECK: dealloc_stack {{%.*}} : $*@block_storage
255+
// CHECK: await_async_continuation {{%.*}} : $Builtin.RawUnsafeContinuation, resume bb1
256+
// CHECK: bb1:
257+
// CHECK: hop_to_executor {{%.*}} : $Optional<Builtin.Executor>
258+
// CHECK: [[RESULT:%.*]] = load [take] [[RESULT_BUF]] : $*NSObject
259+
// CHECK: objc_method {{%.*}} : $CostcoManager, #CostcoManager.isCustomerEnrolled!foreign
260+
// CHECK: get_async_continuation_addr Bool, [[FINAL_BUF]] : $*Bool
261+
// CHECK: [[BLOCK_ARG:%.*]] = init_block_storage_header [[BLOCK_STORAGE:%.*]] : $*@block_storage
262+
// CHECK: = apply {{%.*}}([[RESULT]], [[BLOCK_ARG]], [[MANAGER:%.*]]) : $@convention(objc_method)
263+
// CHECK: [[EXTEND1:%.*]] = copy_value [[RESULT]] : $NSObject
264+
// CHECK: [[EXTEND2:%.*]] = copy_value [[MANAGER]] : $CostcoManager
265+
// CHECK: dealloc_stack [[BLOCK_STORAGE]] : $*@block_storage
266+
// CHECK: await_async_continuation {{%.*}} : $Builtin.RawUnsafeContinuation, resume bb2
267+
// CHECK: bb2:
268+
// CHECK: hop_to_executor {{%.*}} : $Optional<Builtin.Executor>
269+
// CHECK: [[ANSWER:%.*]] = load [trivial] [[FINAL_BUF]] : $*Bool
270+
// CHECK: fix_lifetime [[EXTEND2]] : $CostcoManager
271+
// CHECK: destroy_value [[EXTEND2]] : $CostcoManager
272+
// CHECK: fix_lifetime [[EXTEND1]] : $NSObject
273+
// CHECK: destroy_value [[EXTEND1]] : $NSObject
274+
// CHECK: return [[ANSWER]] : $Bool
275+
// CHECK: }
276+
func checkCostcoMembership() async -> Bool {
277+
return await CostcoManager.shared().isCustomerEnrolled(inExecutiveProgram: Person.asCustomer())
278+
}

test/SILGen/objc_async_from_swift.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ func testSlowServing(p: SlowServing) async throws {
3131

3232
// CHECK: objc_method {{.*}} $@convention(objc_method) <τ_0_0 where τ_0_0 : SlowServing> (@convention(block) (Int, Optional<NSString>, Optional<NSError>) -> (), τ_0_0) -> ()
3333
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]] :
34-
// CHECK: builtin "willThrow"
35-
// CHECK-NEXT: hop_to_executor [[GENERIC_EXECUTOR]] :
34+
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]] :
35+
// CHECK-NEXT: builtin "willThrow"
3636
let _: (Int, String) = try await p.tryRequestIntAndString()
3737
}
3838

@@ -42,8 +42,8 @@ func testSlowServingAgain(p: SlowServing) async throws {
4242
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]] :
4343
// CHECK: objc_method {{.*}} $@convention(objc_method) <τ_0_0 where τ_0_0 : SlowServing> (@convention(block) (Optional<NSString>, Optional<NSError>) -> (), τ_0_0) -> ()
4444
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]] :
45-
// CHECK: builtin "willThrow"
46-
// CHECK-NEXT: hop_to_executor [[GENERIC_EXECUTOR]] :
45+
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]] :
46+
// CHECK-NEXT: builtin "willThrow"
4747
let _: String = try await p.tryRequestString()
4848
}
4949

0 commit comments

Comments
 (0)