Files
swift-mirror/test/SILOptimizer/optimize_hop_to_executor.sil
Michael Gottesman 734f057ce8 [concurrency] Fix optimize_hop_to_executor so that we take advantage of the new nonisolated(nonsending) ABI in post 6.2.
Specifically:

1. We assume in nonisolated(nonsending) that we are already on the relevant
actor. This lets us always eliminate the initial hop_to_executor.

2. We stopped treating nonisolated(nonsending) functions as suspension points
since we are guaranteed to always be on the same actor when we enter/return.

3. Now that nonisolated(nonsending) is no longer a suspension point, I could
sink the needs executor nonisolated(nonsending) specific code into the needs
executor code. For those unfamiliar it is that we: a. treat a
nonisolated(nonsending) callee as a needs executor since we are no longer
guaranteed to hop in callees and b. treat returns from nonisolated(nonsending)
functions as being a needs executor instruction since we are no longer
guaranteed to hop in the caller after such a function returns.

rdar://155465878
2025-08-10 14:58:25 -07:00

440 lines
19 KiB
Plaintext

// RUN: %target-sil-opt -sil-print-function-isolation-info -enable-sil-verify-all %s -optimize-hop-to-executor | %FileCheck %s
// REQUIRES: concurrency
// REQUIRES: asserts
sil_stage canonical
import Builtin
import Swift
actor MyActor {
@_hasStorage private var p: Int { get set }
}
sil [ossa] @requiredToRunOnActor : $@convention(method) (@guaranteed MyActor) -> ()
sil [ossa] @anotherAsyncFunction : $@convention(thin) @async () -> ()
sil [ossa] @syncFunction : $@convention(thin) () -> ()
// CHECK-LABEL: sil [ossa] @simpleRedundantActor : $@convention(method) @async (@guaranteed MyActor) -> () {
// CHECK: bb0(%0 : @guaranteed $MyActor):
// CHECK-NEXT: hop_to_executor %0
// CHECK-NOT: hop_to_executor
// CHECK: } // end sil function 'simpleRedundantActor'
sil [ossa] @simpleRedundantActor : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor %0 : $MyActor
%f = function_ref @requiredToRunOnActor : $@convention(method) (@guaranteed MyActor) -> ()
apply %f(%0) : $@convention(method) (@guaranteed MyActor) -> ()
hop_to_executor %0 : $MyActor
apply %f(%0) : $@convention(method) (@guaranteed MyActor) -> ()
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @redundantActorAndControlFlow : $@convention(method) @async (@guaranteed MyActor) -> () {
// CHECK: bb0(%0 : @guaranteed $MyActor):
// CHECK-NEXT: hop_to_executor %0
// CHECK-NOT: hop_to_executor
// CHECK: } // end sil function 'redundantActorAndControlFlow'
sil [ossa] @redundantActorAndControlFlow : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor %0 : $MyActor
%f = function_ref @requiredToRunOnActor : $@convention(method) (@guaranteed MyActor) -> ()
apply %f(%0) : $@convention(method) (@guaranteed MyActor) -> ()
cond_br undef, bb1, bb2
bb1:
hop_to_executor %0 : $MyActor
br bb3
bb2:
br bb3
bb3:
hop_to_executor %0 : $MyActor
apply %f(%0) : $@convention(method) (@guaranteed MyActor) -> ()
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @notRedundantActor : $@convention(method) @async (@guaranteed MyActor) -> () {
// CHECK: bb1:
// CHECK-NEXT: hop_to_executor %0
// CHECK: bb3:
// CHECK-NEXT: hop_to_executor %0
// CHECK: } // end sil function 'notRedundantActor'
sil [ossa] @notRedundantActor : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
%f = function_ref @requiredToRunOnActor : $@convention(method) (@guaranteed MyActor) -> ()
cond_br undef, bb1, bb2
bb1:
hop_to_executor %0 : $MyActor
apply %f(%0) : $@convention(method) (@guaranteed MyActor) -> ()
br bb3
bb2:
br bb3
bb3:
hop_to_executor %0 : $MyActor
apply %f(%0) : $@convention(method) (@guaranteed MyActor) -> ()
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @mismatchingActor : $@convention(method) @async (@guaranteed MyActor, @guaranteed MyActor) -> () {
// CHECK: hop_to_executor %0
// CHECK: apply
// CHECK: hop_to_executor %1
// CHECK: apply
// CHECK: } // end sil function 'mismatchingActor'
sil [ossa] @mismatchingActor : $@convention(method) @async (@guaranteed MyActor, @guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor, %1 : @guaranteed $MyActor):
%f = function_ref @requiredToRunOnActor : $@convention(method) (@guaranteed MyActor) -> ()
hop_to_executor %0 : $MyActor
apply %f(%0) : $@convention(method) (@guaranteed MyActor) -> ()
hop_to_executor %1 : $MyActor
apply %f(%1) : $@convention(method) (@guaranteed MyActor) -> ()
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @simpleDeadHopElimination : $@convention(method) @async (@guaranteed MyActor) -> () {
// CHECK-NOT: hop_to_executor
// CHECK: } // end sil function 'simpleDeadHopElimination'
sil [ossa] @simpleDeadHopElimination : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor %0 : $MyActor
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @doubleDeadHopElimination : $@convention(method) @async (@guaranteed MyActor) -> () {
// CHECK: bb0(%0 : @guaranteed $MyActor):
// CHECK-NEXT: hop_to_executor %0
// CHECK-NOT: hop_to_executor
// CHECK: } // end sil function 'doubleDeadHopElimination'
sil [ossa] @doubleDeadHopElimination : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor %0 : $MyActor
hop_to_executor %0 : $MyActor
%f = function_ref @requiredToRunOnActor : $@convention(method) (@guaranteed MyActor) -> ()
apply %f(%0) : $@convention(method) (@guaranteed MyActor) -> ()
hop_to_executor %0 : $MyActor
apply %f(%0) : $@convention(method) (@guaranteed MyActor) -> ()
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @deadHopAndControlFlow : $@convention(method) @async (@guaranteed MyActor) -> () {
// CHECK-NOT: hop_to_executor
// CHECK: } // end sil function 'deadHopAndControlFlow'
sil [ossa] @deadHopAndControlFlow : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor %0 : $MyActor
cond_br undef, bb1, bb2
bb1:
br bb3
bb2:
br bb3
bb3:
%f = function_ref @anotherAsyncFunction : $@convention(thin) @async () -> ()
apply %f() : $@convention(thin) @async () -> ()
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @partialAliveHop : $@convention(method) @async (@guaranteed MyActor) -> () {
// CHECK: bb0(%0 : @guaranteed $MyActor):
// CHECK-NEXT: hop_to_executor %0
// CHECK-NOT: hop_to_executor
// CHECK: } // end sil function 'partialAliveHop'
sil [ossa] @partialAliveHop : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor %0 : $MyActor
cond_br undef, bb1, bb2
bb1:
%f = function_ref @requiredToRunOnActor : $@convention(method) (@guaranteed MyActor) -> ()
apply %f(%0) : $@convention(method) (@guaranteed MyActor) -> ()
br bb3
bb2:
br bb3
bb3:
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @handleUnreachable1 : $@convention(method) @async (@guaranteed MyActor) -> () {
// CHECK: bb0(%0 : @guaranteed $MyActor):
// CHECK-NOT: hop_to_executor
// CHECK: } // end sil function 'handleUnreachable1'
sil [ossa] @handleUnreachable1 : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor %0 : $MyActor
cond_br undef, bb1, bb2
bb1:
unreachable
bb2:
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @handleUnreachable2 : $@convention(method) @async (@guaranteed MyActor) -> () {
// CHECK: bb0(%0 : @guaranteed $MyActor):
// CHECK-NOT: hop_to_executor
// CHECK: } // end sil function 'handleUnreachable2'
sil [ossa] @handleUnreachable2 : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
cond_br undef, bb1, bb2
bb1:
hop_to_executor %0 : $MyActor
unreachable
bb2:
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @handleUnreachableBlock : $@convention(method) @async (@guaranteed MyActor) -> () {
// CHECK: bb0(%0 : @guaranteed $MyActor):
// CHECK-NEXT: hop_to_executor
// CHECK: bb2:
// CHECK-NOT: hop_to_executor
// CHECK: } // end sil function 'handleUnreachableBlock'
sil [ossa] @handleUnreachableBlock : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor %0 : $MyActor
%f = function_ref @requiredToRunOnActor : $@convention(method) (@guaranteed MyActor) -> ()
apply %f(%0) : $@convention(method) (@guaranteed MyActor) -> ()
br bb2
bb1:
br bb2
bb2:
hop_to_executor %0 : $MyActor
%r = tuple ()
return %r : $()
}
// This pattern is definitely optimizable, but it's *not* supposed to
// be optimized by simply removing the hop before the builtin.
// CHECK-LABEL: sil [ossa] @handleGetCurrentExecutor : $@convention(method) @async (@guaranteed MyActor, @guaranteed MyActor) -> () {
// CHECK: bb0(%0 : @guaranteed $MyActor, %1 : @guaranteed $MyActor):
// CHECK-NEXT: // function_ref
// CHECK-NEXT: function_ref
// CHECK-NEXT: apply
// CHECK-NEXT: hop_to_executor
// CHECK-NEXT: builtin
// CHECK-NEXT: hop_to_executor
// CHECK-NEXT: // function_ref
// CHECK-NEXT: function_ref
// CHECK-NEXT: apply
// CHECK-NEXT: tuple
// CHECK-NEXT: return
// CHECK: } // end sil function 'handleGetCurrentExecutor'
sil [ossa] @handleGetCurrentExecutor : $@convention(method) @async (@guaranteed MyActor, @guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor, %1 : @guaranteed $MyActor):
hop_to_executor %0 : $MyActor
%async_f = function_ref @anotherAsyncFunction : $@convention(thin) @async () -> ()
apply %async_f() : $@convention(thin) @async () -> ()
hop_to_executor %0 : $MyActor
%curExec = builtin "getCurrentExecutor"() : $Optional<Builtin.Executor>
hop_to_executor %1 : $MyActor
%f = function_ref @requiredToRunOnActor : $@convention(method) (@guaranteed MyActor) -> ()
apply %f(%1) : $@convention(method) (@guaranteed MyActor) -> ()
hop_to_executor %0 : $MyActor
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @redundantMandatoryHop : $@convention(method) @async (@guaranteed MyActor) -> () {
// CHECK: bb0(%0 : @guaranteed $MyActor):
// CHECK-NEXT: hop_to_executor %0
// CHECK: apply
// CHECK: hop_to_executor [mandatory] %0
// CHECK: } // end sil function 'redundantMandatoryHop'
sil [ossa] @redundantMandatoryHop : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor %0 : $MyActor
%f = function_ref @requiredToRunOnActor : $@convention(method) (@guaranteed MyActor) -> ()
apply %f(%0) : $@convention(method) (@guaranteed MyActor) -> ()
hop_to_executor [mandatory] %0 : $MyActor
apply %f(%0) : $@convention(method) (@guaranteed MyActor) -> ()
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @simpleMandatoryHop : $@convention(method) @async (@guaranteed MyActor) -> () {
// CHECK: hop_to_executor [mandatory]
// CHECK: } // end sil function 'simpleMandatoryHop'
sil [ossa] @simpleMandatoryHop : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor [mandatory] %0 : $MyActor
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @simpleMandatoryHopAndSuspend : $@convention(method) @async (@guaranteed MyActor) -> () {
// CHECK: hop_to_executor [mandatory]
// CHECK: apply
// CHECK: } // end sil function 'simpleMandatoryHopAndSuspend'
sil [ossa] @simpleMandatoryHopAndSuspend : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor [mandatory] %0 : $MyActor
%async_f = function_ref @anotherAsyncFunction : $@convention(thin) @async () -> ()
apply %async_f() : $@convention(thin) @async () -> ()
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @handleBeginBorrow : {{.*}} {
// CHECK-NOT: hop_to_executor
// CHECK-LABEL: } // end sil function 'handleBeginBorrow'
sil [ossa] @handleBeginBorrow : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor %0 : $MyActor
%b = begin_borrow %0 : $MyActor
end_borrow %b : $MyActor
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @handleEndBorrow : {{.*}} {
// CHECK-NOT: hop_to_executor
// CHECK-LABEL: } // end sil function 'handleEndBorrow'
sil [ossa] @handleEndBorrow : $@convention(method) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
%b = begin_borrow %0 : $MyActor
hop_to_executor %0 : $MyActor
end_borrow %b : $MyActor
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @simpleDCEAsync : $@convention(thin) @async (@guaranteed MyActor) -> () {
// CHECK-NOT: hop_to_executor
// CHECK: } // end sil function 'simpleDCEAsync'
sil [ossa] @simpleDCEAsync : $@convention(thin) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor %0
%f = function_ref @anotherAsyncFunction : $@convention(thin) @async () -> ()
apply %f() : $@convention(thin) @async () -> ()
%9999 = tuple ()
return %9999 : $()
}
// CHECK-LABEL: sil [ossa] @simpleCallerIsolationInheritingStopsDCE : $@convention(thin) @async (@guaranteed MyActor) -> () {
// CHECK: hop_to_executor
// CHECK: } // end sil function 'simpleCallerIsolationInheritingStopsDCE'
sil [ossa] @simpleCallerIsolationInheritingStopsDCE : $@convention(thin) @async (@guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor %0
%f = function_ref @anotherAsyncFunction : $@convention(thin) @async () -> ()
apply [callee_isolation=caller_isolation_inheriting] [caller_isolation=caller_isolation_inheriting] %f() : $@convention(thin) @async () -> ()
%9999 = tuple ()
return %9999 : $()
}
// We should eliminate none of the hop_to_executor here since
// caller_isolation_inheriting @async apply sites do not cross isolation
// boundaries.
//
// CHECK-LABEL: sil [ossa] @simpleCallerIsolationInheritingStopsDCE2 : $@convention(thin) @async (@guaranteed MyActor, @guaranteed MyActor, @guaranteed MyActor) -> () {
// CHECK: hop_to_executor
// CHECK: hop_to_executor
// CHECK: hop_to_executor
// CHECK: } // end sil function 'simpleCallerIsolationInheritingStopsDCE2'
sil [ossa] @simpleCallerIsolationInheritingStopsDCE2 : $@convention(thin) @async (@guaranteed MyActor, @guaranteed MyActor, @guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor, %1 : @guaranteed $MyActor, %2 : @guaranteed $MyActor):
hop_to_executor %0
%f = function_ref @anotherAsyncFunction : $@convention(thin) @async () -> ()
apply [callee_isolation=caller_isolation_inheriting] [caller_isolation=caller_isolation_inheriting] %f() : $@convention(thin) @async () -> ()
hop_to_executor %1
%f2 = function_ref @syncFunction : $@convention(thin) () -> ()
apply %f2() : $@convention(thin) () -> ()
apply [callee_isolation=caller_isolation_inheriting] [caller_isolation=caller_isolation_inheriting] %f() : $@convention(thin) @async () -> ()
hop_to_executor %2
apply %f2() : $@convention(thin) () -> ()
apply [callee_isolation=caller_isolation_inheriting] [caller_isolation=caller_isolation_inheriting] %f() : $@convention(thin) @async () -> ()
%9999 = tuple ()
return %9999 : $()
}
// CHECK-LABEL: sil [ossa] @simpleWithoutCallerIsolationInheritingHaveDCE : $@convention(thin) @async (@guaranteed MyActor, @guaranteed MyActor, @guaranteed MyActor) -> () {
// CHECK: bb0([[ARG1:%.*]] : @guaranteed $MyActor, [[ARG2:%.*]] : @guaranteed $MyActor, [[ARG3:%.*]] : @guaranteed $MyActor):
// CHECK-NEXT: // function_ref anotherAsyncFunction
// CHECK-NEXT: [[FUNC:%.*]] = function_ref @anotherAsyncFunction : $@convention(thin) @async () -> ()
// CHECK-NEXT: apply [[FUNC]]() : $@convention(thin) @async () -> ()
// CHECK-NEXT: hop_to_executor [[ARG2]]
// CHECK-NEXT: // function_ref syncFunction
// CHECK-NEXT: [[FUNC2:%.*]] = function_ref @syncFunction : $@convention(thin) () -> ()
// CHECK-NEXT: apply [[FUNC2]]() : $@convention(thin) () -> ()
// CHECK-NEXT: apply [[FUNC]]() : $@convention(thin) @async () -> ()
// CHECK-NEXT: hop_to_executor [[ARG3]]
// CHECK-NEXT: apply [[FUNC2]]() : $@convention(thin) () -> ()
// CHECK-NEXT: apply [[FUNC]]() : $@convention(thin) @async () -> ()
// CHECK-NEXT: apply [[FUNC]]() : $@convention(thin) @async () -> ()
// CHECK: } // end sil function 'simpleWithoutCallerIsolationInheritingHaveDCE'
sil [ossa] @simpleWithoutCallerIsolationInheritingHaveDCE : $@convention(thin) @async (@guaranteed MyActor, @guaranteed MyActor, @guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor, %1 : @guaranteed $MyActor, %2 : @guaranteed $MyActor):
hop_to_executor %0
%f = function_ref @anotherAsyncFunction : $@convention(thin) @async () -> ()
apply %f() : $@convention(thin) @async () -> ()
hop_to_executor %1
%f2 = function_ref @syncFunction : $@convention(thin) () -> ()
apply %f2() : $@convention(thin) () -> ()
apply %f() : $@convention(thin) @async () -> ()
hop_to_executor %2
apply %f2() : $@convention(thin) () -> ()
apply %f() : $@convention(thin) @async () -> ()
hop_to_executor %2
apply %f() : $@convention(thin) @async () -> ()
%9999 = tuple ()
return %9999 : $()
}
// We do not allow for these to be propagated yet through caller isolation
// inheriting, so we should have all of the hop_to_executor.
//
// CHECK-LABEL: sil [ossa] @callerIsolationInheritingStopsAllowsPropagating : $@convention(thin) @async (@guaranteed MyActor, @guaranteed MyActor, @guaranteed MyActor) -> () {
// CHECK: bb0(
// CHECK: hop_to_executor
// CHECK-NOT: hop_to_executor
// CHECK: } // end sil function 'callerIsolationInheritingStopsAllowsPropagating'
sil [ossa] @callerIsolationInheritingStopsAllowsPropagating : $@convention(thin) @async (@guaranteed MyActor, @guaranteed MyActor, @guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor, %1 : @guaranteed $MyActor, %2 : @guaranteed $MyActor):
hop_to_executor %0
%f = function_ref @anotherAsyncFunction : $@convention(thin) @async () -> ()
apply [callee_isolation=caller_isolation_inheriting] [caller_isolation=caller_isolation_inheriting] %f() : $@convention(thin) @async () -> ()
hop_to_executor %0
%f2 = function_ref @syncFunction : $@convention(thin) () -> ()
apply %f2() : $@convention(thin) () -> ()
apply [callee_isolation=caller_isolation_inheriting] [caller_isolation=caller_isolation_inheriting] %f() : $@convention(thin) @async () -> ()
hop_to_executor %0
apply %f2() : $@convention(thin) () -> ()
apply [callee_isolation=caller_isolation_inheriting] [caller_isolation=caller_isolation_inheriting] %f() : $@convention(thin) @async () -> ()
%9999 = tuple ()
return %9999 : $()
}
// Since we are caller isolation inheriting, make sure that we leave around the
// hop_to_executor due to ehre elase. We do eliminate one of the hop_to_executor
// though.
//
// CHECK: sil [isolation "caller_isolation_inheriting"] [ossa] @callerIsolationInheritingStopsReturnDeadEnd : $@convention(thin) @async (@sil_isolated @guaranteed MyActor) -> () {
// CHECK: bb0(
// CHECK-NEXT: tuple
// CHECK-NEXT: return
// CHECK: } // end sil function 'callerIsolationInheritingStopsReturnDeadEnd'
sil [isolation "caller_isolation_inheriting"] [ossa] @callerIsolationInheritingStopsReturnDeadEnd : $@convention(thin) @async (@sil_isolated @guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor):
hop_to_executor %0
hop_to_executor %0
%9999 = tuple ()
return %9999 : $()
}
// CHECK-LABEL: sil [ossa] @noIsolationStillRemoves : $@convention(thin) @async (@guaranteed MyActor, @guaranteed MyActor, @guaranteed MyActor) -> () {
// CHECK-NOT: hop_to_executor
// CHECK: } // end sil function 'noIsolationStillRemoves'
sil [ossa] @noIsolationStillRemoves : $@convention(thin) @async (@guaranteed MyActor, @guaranteed MyActor, @guaranteed MyActor) -> () {
bb0(%0 : @guaranteed $MyActor, %1 : @guaranteed $MyActor, %2 : @guaranteed $MyActor):
hop_to_executor %0
%9999 = tuple ()
return %9999 : $()
}