@@ -4126,6 +4126,91 @@ pipeline.
41264126
41274127The operand is a guaranteed operand, i.e. not consumed.
41284128
4129+ merge_isolation_region
4130+ ``````````````````````
4131+
4132+ ::
4133+
4134+ sil-instruction :: 'merge_isolation_region' (sil-operand ',')+ sil-operand
4135+
4136+ %2 = merge_isolation_region %first : $*T, %second : $U
4137+ %2 = merge_isolation_region %first : $*T, %second : $U, %third : $H
4138+
4139+ Instruction that is only valid in Ownership SSA.
4140+
4141+ This instruction informs region isolation that all of the operands should be
4142+ considered to be artificially apart of the same region. It is intended to be
4143+ used to express region dependency when due to unsafe codegen we have to traffic
4144+ a non-Sendable value through computations with Sendable values (causing us to
4145+ not track the non-Sendable value) but have to later express that a non-Sendable
4146+ result of using the Sendable value needs to be in the same region as the
4147+ original non-Sendable value. As an example of where this comes up, consider the
4148+ following code::
4149+
4150+ // objc code
4151+ @interface CallbackData : NSObject
4152+ @end
4153+
4154+ @interface Klass : NSObject
4155+
4156+ - (void)loadDataWithCompletionHandler:(void (^)(CallbackData * _Nullable, NSError * _Nullable))completionHandler;
4157+
4158+ @end
4159+
4160+ // swift code
4161+ extension Klass {
4162+ func loadCallbackData() async throws -> sending CallbackData {
4163+ try await loadData()
4164+ }
4165+ }
4166+
4167+ This lowers to::
4168+
4169+ %5 = alloc_stack $CallbackData // users: %26, %25, %31, %16, %7
4170+ %6 = objc_method %0 : $Klass, #Klass.loadData!foreign : (Klass) -> () async throws -> CallbackData, $@convention(objc_method) (Optional<@convention(block) (Optional<CallbackData>, Optional<NSError>) -> ()>, Klass) -> () // user: %20
4171+ %7 = get_async_continuation_addr [throws] CallbackData, %5 : $*CallbackData // users: %23, %8
4172+ %8 = struct $UnsafeContinuation<CallbackData, any Error> (%7 : $Builtin.RawUnsafeContinuation) // user: %14
4173+ %9 = alloc_stack $@block_storage Any // users: %22, %16, %10
4174+ %10 = project_block_storage %9 : $*@block_storage Any // user: %11
4175+ %11 = init_existential_addr %10 : $*Any, $CheckedContinuation<CallbackData, any Error> // user: %15
4176+ // function_ref _createCheckedThrowingContinuation<A>(_:)
4177+ %12 = function_ref @$ss34_createCheckedThrowingContinuationyScCyxs5Error_pGSccyxsAB_pGnlF : $@convention(thin) <τ_0_0> (UnsafeContinuation<τ_0_0, any Error>) -> @out CheckedContinuation<τ_0_0, any Error> // user: %14
4178+ %13 = alloc_stack $CheckedContinuation<CallbackData, any Error> // users: %21, %15, %14
4179+ %14 = apply %12<CallbackData>(%13, %8) : $@convention(thin) <τ_0_0> (UnsafeContinuation<τ_0_0, any Error>) -> @out CheckedContinuation<τ_0_0, any Error>
4180+ copy_addr [take] %13 to [init] %11 : $*CheckedContinuation<CallbackData, any Error> // id: %15
4181+ merge_isolation_region %9 : $*@block_storage Any, %5 : $*CallbackData // id: %16
4182+ // function_ref @objc completion handler block implementation for @escaping @callee_unowned @convention(block) (@unowned CallbackData?, @unowned NSError?) -> () with result type CallbackData
4183+ %17 = function_ref @$sSo12CallbackDataCSgSo7NSErrorCSgIeyByy_ABTz_ : $@convention(c) (@inout_aliasable @block_storage Any, Optional<CallbackData>, Optional<NSError>) -> () // user: %18
4184+ %18 = init_block_storage_header %9 : $*@block_storage Any, invoke %17 : $@convention(c) (@inout_aliasable @block_storage Any, Optional<CallbackData>, Optional<NSError>) -> (), type $@convention(block) (Optional<CallbackData>, Optional<NSError>) -> () // user: %19
4185+ %19 = enum $Optional<@convention(block) (Optional<CallbackData>, Optional<NSError>) -> ()>, #Optional.some!enumelt, %18 : $@convention(block) (Optional<CallbackData>, Optional<NSError>) -> () // user: %20
4186+ %20 = apply %6(%19, %0) : $@convention(objc_method) (Optional<@convention(block) (Optional<CallbackData>, Optional<NSError>) -> ()>, Klass) -> ()
4187+
4188+ Notice how without the `merge_isolation_region `_ instruction (%16) there is no
4189+ non-Sendable def-use chain from %5, the indirect return value of the block, to
4190+ the actual non-Sendable block storage %9. This can result in region isolation
4191+ not propagating restrictions on usage from %9 onto %5 risking the creation of
4192+ races.
4193+
4194+ Applying the previous discussion to this specific example, self (%0) is
4195+ non-Sendable and is bound to the current task. If we did not have the
4196+ `merge_isolation_region `_ instruction here, we would not tie the return value %5
4197+ to %0 via %9. This would cause %5 to be treated as a disconnected value and thus
4198+ be a valid sending return value potentially allowing for %5 in the caller of the
4199+ function to be sent to another isolation domain and introduce a race.
4200+
4201+ .. note ::
4202+ This is effectively the same purpose that `mark_dependence `_ plays for memory
4203+ dependence (expressing memory dependence that the compiler cannot infer)
4204+ except in the world of region isolation. We purposely use a different
4205+ instruction since `mark_dependence `_ is often times used to create a
4206+ temporary dependence in between two values via the return value of
4207+ `mark_dependence `_. If `mark_dependence `_ had the semantics of acting like a
4208+ region merge we would in contrast have from that point on a region dependence
4209+ in between the base and value of the `mark_dependence `_ causing the
4210+ `mark_dependence `_ to have a less "local" effect since all paths through that
4211+ program point would have to maintain that region dependence until the end of
4212+ the function.
4213+
41294214dealloc_stack
41304215`````````````
41314216::
@@ -9049,7 +9134,6 @@ NOTE: From the perspective of the address checker, a trivial `load`_ with a
90499134`moveonlywrapper_to_copyable_addr `_ operand is considered to be a use of a
90509135noncopyable type.
90519136
9052-
90539137Assertion configuration
90549138~~~~~~~~~~~~~~~~~~~~~~~
90559139
0 commit comments