Skip to content

Commit 714cc00

Browse files
committed
[silgen] When emitting a foreign async completion handler for a method, use merge isolation region to tie self and the block storage into the same region.
This is an extension of a similar problem that I had fixed earlier where due to the usage of intermediate Sendable types we do not propagate regions correctly. The previous issue I fixed was that we were not properly tieing the result of a foreign async completion handler to the block storage since we used an intervening UnsafeContinuation (which is Sendable) to propagate the result into the block storage. I fixed this by changing SILGen to insert a merge_isolation_region that explicitly ties the result to the block storage. This new issue is that the block that we create and then pass as the completion handler is an @sendable block. Thus when we call the actual objc_method, the block storage and self are not viewed as being in the same region. In this PR, I change it so that we add a merge_isolation_region from self onto the block storage. The end result of this is that we have that self, the result of the call, and the block storage are all in the same region meaning that we properly diagnose that returning an NSObject from the imported Objective-C function is task isolated and thus we cannot return it as a sending result. rdar://131422332 (cherry picked from commit 227ab37)
1 parent 6f696a4 commit 714cc00

File tree

5 files changed

+171
-13
lines changed

5 files changed

+171
-13
lines changed

lib/SILGen/ResultPlan.cpp

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -792,10 +792,9 @@ class ForeignAsyncInitializationPlan final : public ResultPlan {
792792
return std::make_tuple(blockStorage, blockStorageTy, continuationTy);
793793
}
794794

795-
ManagedValue
796-
emitForeignAsyncCompletionHandler(SILGenFunction &SGF,
797-
AbstractionPattern origFormalType,
798-
SILLocation loc) override {
795+
ManagedValue emitForeignAsyncCompletionHandler(
796+
SILGenFunction &SGF, AbstractionPattern origFormalType, ManagedValue self,
797+
SILLocation loc) override {
799798
// Get the current continuation for the task.
800799
bool throws =
801800
calleeTypeInfo.foreign.async->completionHandlerErrorParamIndex()
@@ -845,7 +844,14 @@ class ForeignAsyncInitializationPlan final : public ResultPlan {
845844
SILValue block = SGF.B.createInitBlockStorageHeader(loc, blockStorage,
846845
impRef, SILType::getPrimitiveObjectType(impFnTy),
847846
SGF.getForwardingSubstitutionMap());
848-
847+
848+
// If our block is Sendable, we have lost the connection in between self and
849+
// blockStorage. We need to restore that connection by using a merge
850+
// isolation region.
851+
if (self && block->getType().isSendable(&SGF.F)) {
852+
SGF.B.createMergeIsolationRegion(loc, {self.getValue(), blockStorage});
853+
}
854+
849855
// Wrap it in optional if the callee expects it.
850856
if (handlerIsOptional) {
851857
block = SGF.B.createOptionalSome(loc, block, impTy);
@@ -1091,11 +1097,11 @@ class ForeignErrorInitializationPlan final : public ResultPlan {
10911097
subPlan->gatherIndirectResultAddrs(SGF, loc, outList);
10921098
}
10931099

1094-
ManagedValue
1095-
emitForeignAsyncCompletionHandler(SILGenFunction &SGF,
1096-
AbstractionPattern origFormalType,
1097-
SILLocation loc) override {
1098-
return subPlan->emitForeignAsyncCompletionHandler(SGF, origFormalType, loc);
1100+
ManagedValue emitForeignAsyncCompletionHandler(
1101+
SILGenFunction &SGF, AbstractionPattern origFormalType, ManagedValue self,
1102+
SILLocation loc) override {
1103+
return subPlan->emitForeignAsyncCompletionHandler(SGF, origFormalType, self,
1104+
loc);
10991105
}
11001106

11011107
std::optional<std::pair<ManagedValue, ManagedValue>>

lib/SILGen/ResultPlan.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@ class ResultPlan {
6565
return std::nullopt;
6666
}
6767

68-
virtual ManagedValue emitForeignAsyncCompletionHandler(
69-
SILGenFunction &SGF, AbstractionPattern origFormalType, SILLocation loc) {
68+
virtual ManagedValue
69+
emitForeignAsyncCompletionHandler(SILGenFunction &SGF,
70+
AbstractionPattern origFormalType,
71+
ManagedValue self, SILLocation loc) {
7072
return {};
7173
}
7274
};

lib/SILGen/SILGenApply.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5819,9 +5819,16 @@ RValue SILGenFunction::emitApply(
58195819
// we left during the first pass.
58205820
auto &completionArgSlot = const_cast<ManagedValue &>(args[completionIndex]);
58215821

5822+
// We have already lowered foreign self/moved it into position at this
5823+
// point, so we know that self will be back.
5824+
ManagedValue self;
5825+
if (substFnType->hasSelfParam()) {
5826+
self = args.back();
5827+
}
5828+
58225829
auto origFormalType = *calleeTypeInfo.origFormalType;
58235830
completionArgSlot = resultPlan->emitForeignAsyncCompletionHandler(
5824-
*this, origFormalType, loc);
5831+
*this, origFormalType, self, loc);
58255832
}
58265833
if (auto foreignError = calleeTypeInfo.foreign.error) {
58275834
unsigned errorParamIndex =
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
#import <Foundation/Foundation.h>
3+
4+
NS_ASSUME_NONNULL_BEGIN
5+
6+
@interface ObjCObject : NSObject
7+
8+
- (void)loadObjectsWithCompletionHandler:
9+
(void (^NS_SWIFT_SENDABLE)(NSArray<NSObject *> *_Nullable,
10+
NSError *_Nullable))completionHandler;
11+
12+
- (void)loadObjects2WithCompletionHandler:
13+
(void (^)(NSArray<NSObject *> *_Nullable,
14+
NSError *_Nullable))completionHandler;
15+
16+
@end
17+
18+
NS_ASSUME_NONNULL_END
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// RUN: %target-swift-frontend %s -import-objc-header %S/Inputs/regionbasedisolation.h -verify -c -swift-version 6
2+
// RUN: %target-swift-frontend %s -import-objc-header %S/Inputs/regionbasedisolation.h -emit-silgen -swift-version 6 | %FileCheck %s
3+
4+
// REQUIRES: objc_interop
5+
6+
extension ObjCObject {
7+
// CHECK-LABEL: sil hidden [ossa] @$sSo10ObjCObjectC20regionbasedisolationE11sendObjectsSaySo8NSObjectCGyYaKF : $@convention(method) @async (@guaranteed ObjCObject) -> (@sil_sending @owned Array<NSObject>, @error any Error) {
8+
// CHECK: bb0([[SELF:%.*]] : @guaranteed $ObjCObject):
9+
10+
// Our result.
11+
// CHECK: [[RESULT:%.*]] = alloc_stack $Array<NSObject>
12+
13+
// Our method.
14+
// CHECK: [[METHOD:%.*]] = objc_method [[SELF]], #ObjCObject.loadObjects!foreign : (ObjCObject) -> () async throws -> [NSObject], $@convention(objc_method) (@convention(block) @Sendable (Optional<NSArray>, Optional<NSError>) -> (), ObjCObject) -> ()
15+
16+
// Begin setting up the unsafe continuation for our method. Importantly note
17+
// that [[UNSAFE_CONT]] is Sendable, so we lose any connection from the
18+
// continuation addr to any uses of the UnsafeContinuation.
19+
//
20+
// CHECK: [[CONT:%.*]] = get_async_continuation_addr [throws] Array<NSObject>, [[RESULT]]
21+
// CHECK: [[UNSAFE_CONT:%.*]] = struct $UnsafeContinuation<Array<NSObject>, any Error> ([[CONT]])
22+
23+
// Then prepare the block storage.
24+
// CHECK: [[BLOCK_STORAGE:%.*]] = alloc_stack $@block_storage Any
25+
// CHECK: [[PROJECT_BLOCK_STORAGE:%.*]] = project_block_storage [[BLOCK_STORAGE]]
26+
// CHECK: [[EXISTENTIAL_BLOCK_STORAGE:%.*]] = init_existential_addr [[PROJECT_BLOCK_STORAGE]]
27+
28+
// Then create a checked continuation from the unsafe continuation.
29+
//
30+
// CHECK: [[CREATE_CHECKED_CONT:%.*]] = function_ref @$ss34_createCheckedThrowingContinuationyScCyxs5Error_pGSccyxsAB_pGnlF : $@convention(thin) <τ_0_0> (UnsafeContinuation<τ_0_0, any Error>) -> @out CheckedContinuation<τ_0_0, any Error>
31+
// CHECK: [[CHECKED_CONT:%.*]] = alloc_stack $CheckedContinuation<Array<NSObject>, any Error>
32+
// CHECK: apply [[CREATE_CHECKED_CONT]]<Array<NSObject>>([[CHECKED_CONT]], [[UNSAFE_CONT]])
33+
34+
// Then place the checked continuation into the block storage and perform a
35+
// merge_isolation_region in between the block storage and the result to
36+
// propagate that the result and the block storage are apart of the same
37+
// region despite the UnsafeContinuation blocking the relation in between
38+
// them.
39+
//
40+
// CHECK: copy_addr [take] [[CHECKED_CONT]] to [init] [[EXISTENTIAL_BLOCK_STORAGE]]
41+
// CHECK: merge_isolation_region [[BLOCK_STORAGE]], [[RESULT]]
42+
43+
// Then create the actual block. NOTE: Since the block is @Sendable, the block
44+
// does not propagate regions.
45+
//
46+
// CHECK: [[COMPLETION_HANDLER_BLOCK:%.*]] = function_ref @$sSo7NSArrayCSgSo7NSErrorCSgIeyBhyy_SaySo8NSObjectCGTz_ : $@convention(c) @Sendable (@inout_aliasable @block_storage Any, Optional<NSArray>, Optional<NSError>) -> ()
47+
// CHECK: [[COMPLETION_BLOCK:%.*]] = init_block_storage_header [[BLOCK_STORAGE]], invoke [[COMPLETION_HANDLER_BLOCK]]
48+
//
49+
// Since the block is @Sendable, it does not propagate the connection in
50+
// between self and the block storage when we just call the method. Thus we
51+
// need to perform a merge_isolation_region to communicate that the block
52+
// storage and self are part of the same region.
53+
//
54+
// CHECK: merge_isolation_region [[SELF]], [[BLOCK_STORAGE]]
55+
//
56+
// Then call the method.
57+
// CHECK: apply [[METHOD]]([[COMPLETION_BLOCK]], [[SELF]])
58+
// CHECK: } // end sil function '$sSo10ObjCObjectC20regionbasedisolationE11sendObjectsSaySo8NSObjectCGyYaKF'
59+
60+
func sendObjects() async throws -> sending [NSObject] {
61+
// We emit an error since loadObjects just returns an [NSObject], not a
62+
// sending [NSObject].
63+
try await loadObjects()
64+
} // expected-error {{task or actor isolated value cannot be sent}}
65+
66+
// Check if we do not mark the block as NS_SWIFT_SENDABLE
67+
//
68+
// CHECK-LABEL: sil hidden [ossa] @$sSo10ObjCObjectC20regionbasedisolationE12sendObjects2SaySo8NSObjectCGyYaKF : $@convention(method) @async (@guaranteed ObjCObject) -> (@sil_sending @owned Array<NSObject>, @error any Error) {
69+
// CHECK: bb0([[SELF:%.*]] : @guaranteed $ObjCObject):
70+
71+
// Our result.
72+
// CHECK: [[RESULT:%.*]] = alloc_stack $Array<NSObject>
73+
74+
// Our method.
75+
// CHECK: [[METHOD:%.*]] = objc_method [[SELF]], #ObjCObject.loadObjects2!foreign : (ObjCObject) -> () async throws -> [NSObject], $@convention(objc_method) (@convention(block) @Sendable (Optional<NSArray>, Optional<NSError>) -> (), ObjCObject) -> ()
76+
77+
// Begin setting up the unsafe continuation for our method. Importantly note
78+
// that [[UNSAFE_CONT]] is Sendable, so we lose any connection from the
79+
// continuation addr to any uses of the UnsafeContinuation.
80+
//
81+
// CHECK: [[CONT:%.*]] = get_async_continuation_addr [throws] Array<NSObject>, [[RESULT]]
82+
// CHECK: [[UNSAFE_CONT:%.*]] = struct $UnsafeContinuation<Array<NSObject>, any Error> ([[CONT]])
83+
84+
// Then prepare the block storage.
85+
// CHECK: [[BLOCK_STORAGE:%.*]] = alloc_stack $@block_storage Any
86+
// CHECK: [[PROJECT_BLOCK_STORAGE:%.*]] = project_block_storage [[BLOCK_STORAGE]]
87+
// CHECK: [[EXISTENTIAL_BLOCK_STORAGE:%.*]] = init_existential_addr [[PROJECT_BLOCK_STORAGE]]
88+
89+
// Then create a checked continuation from the unsafe continuation.
90+
//
91+
// CHECK: [[CREATE_CHECKED_CONT:%.*]] = function_ref @$ss34_createCheckedThrowingContinuationyScCyxs5Error_pGSccyxsAB_pGnlF : $@convention(thin) <τ_0_0> (UnsafeContinuation<τ_0_0, any Error>) -> @out CheckedContinuation<τ_0_0, any Error>
92+
// CHECK: [[CHECKED_CONT:%.*]] = alloc_stack $CheckedContinuation<Array<NSObject>, any Error>
93+
// CHECK: apply [[CREATE_CHECKED_CONT]]<Array<NSObject>>([[CHECKED_CONT]], [[UNSAFE_CONT]])
94+
95+
// Then place the checked continuation into the block storage and perform a
96+
// merge_isolation_region in between the block storage and the result to
97+
// propagate that the result and the block storage are apart of the same
98+
// region despite the UnsafeContinuation blocking the relation in between
99+
// them.
100+
//
101+
// CHECK: copy_addr [take] [[CHECKED_CONT]] to [init] [[EXISTENTIAL_BLOCK_STORAGE]]
102+
// CHECK: merge_isolation_region [[BLOCK_STORAGE]], [[RESULT]]
103+
104+
// Then create the actual block. NOTE: Since the block is @Sendable, the block
105+
// does not propagate regions.
106+
//
107+
// CHECK: [[COMPLETION_HANDLER_BLOCK:%.*]] = function_ref @$sSo7NSArrayCSgSo7NSErrorCSgIeyBhyy_SaySo8NSObjectCGTz_ : $@convention(c) @Sendable (@inout_aliasable @block_storage Any, Optional<NSArray>, Optional<NSError>) -> ()
108+
// CHECK: [[COMPLETION_BLOCK:%.*]] = init_block_storage_header [[BLOCK_STORAGE]], invoke [[COMPLETION_HANDLER_BLOCK]]
109+
//
110+
// Since the block is @Sendable, it does not propagate the connection in
111+
// between self and the block storage when we just call the method. Thus we
112+
// need to perform a merge_isolation_region to communicate that the block
113+
// storage and self are part of the same region.
114+
//
115+
// CHECK: merge_isolation_region [[SELF]], [[BLOCK_STORAGE]]
116+
//
117+
// Then call the method.
118+
// CHECK: apply [[METHOD]]([[COMPLETION_BLOCK]], [[SELF]])
119+
// CHECK: } // end sil function '$sSo10ObjCObjectC20regionbasedisolationE12sendObjects2SaySo8NSObjectCGyYaKF'
120+
func sendObjects2() async throws -> sending [NSObject] {
121+
// We emit an error since loadObjects just returns an [NSObject], not a
122+
// sending [NSObject].
123+
try await loadObjects2()
124+
} // expected-error {{task or actor isolated value cannot be sent}}
125+
}

0 commit comments

Comments
 (0)