Skip to content

Commit 15aa088

Browse files
committed
silgen test coverage for async thunks of non-async funcs
In light of SE-338, you'd think all functions converted to be `async` would have hops in their reabstraction thunks. But that's not the case. A non-async function that is not otherwise isolated according to its type, when converted to become an async function, should not have hops in its reabstraction thunk; not even to the generic executor. That was not intuitive for me, so I thought I'd add extra test coverage to ensure we maintain that existing behavior, since we rely on it when non-async functions have their isolation casted away when in an isolated context (only allowed when it's not also Sendable). Thus, when it's called later on, it needs to inherit the executor, as its guaranteed to only be called while on a matching one. For example: ``` @mainactor func caller() async { let f: () -> () = mainActorFn let g: () async -> () = f await g() // g cannot be hopping to a different executor, it must inherit! } ```
1 parent ac10f2a commit 15aa088

File tree

1 file changed

+54
-1
lines changed

1 file changed

+54
-1
lines changed

test/SILGen/async_conversion.swift

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -emit-silgen %s -module-name test -swift-version 5 -disable-availability-checking | %FileCheck %s
1+
// RUN: %target-swift-frontend -emit-silgen %s -module-name test -swift-version 5 -disable-availability-checking | %FileCheck --implicit-check-not hop_to_executor %s
22
// REQUIRES: concurrency
33

44
func f(_: Int, _: String) -> String? { nil }
@@ -33,3 +33,56 @@ actor A: P2 {
3333
// CHECK-NEXT: apply
3434
// CHECK: return
3535
}
36+
37+
// It's important that async thunks generated for conversions
38+
// from a nonisolated, non-async function to an async function
39+
// do _not_ contain hops to any executor, including the generic executor (!!),
40+
// because these specific async functions derived from non-async ones
41+
// must inherit their executor to make conversion sequences such as these, safe:
42+
43+
@MainActor func mainActorFn() {}
44+
45+
// CHECK-LABEL: sil hidden [ossa] @$s4test7caller1yyYaF : $@convention(thin) @async () -> () {
46+
@MainActor func caller1() async {
47+
// CHECK: hop_to_executor {{.*}} : $MainActor
48+
// CHECK: [[F:%.*]] = function_ref @$s4test11mainActorFnyyF : $@convention(thin) () -> ()
49+
// CHECK: [[THICK_F:%.*]] = thin_to_thick_function [[F]] : $@convention(thin) () -> () to $@callee_guaranteed () -> ()
50+
// CHECK: [[THUNK:%.*]] = function_ref @$sIeg_IegH_TR : $@convention(thin) @async (@guaranteed @callee_guaranteed () -> ()) -> ()
51+
// CHECK: = partial_apply [callee_guaranteed] [[THUNK]]([[THICK_F]]) : $@convention(thin) @async (@guaranteed @callee_guaranteed () -> ()) -> ()
52+
// ... after applying ...
53+
// CHECK: hop_to_executor {{.*}} : $MainActor
54+
let f: () -> () = mainActorFn
55+
let g: () async -> () = f
56+
await g() // g cannot be hopping to a different executor, it must inherit!
57+
}
58+
// CHECK: end sil function '$s4test7caller1yyYaF'
59+
60+
// CHECK-LABEL: sil shared [transparent] [serialized] [reabstraction_thunk] [ossa] @$sIeg_IegH_TR : $@convention(thin) @async (@guaranteed @callee_guaranteed () -> ()) -> () {
61+
// CHECK-NOT: hop_to_executor
62+
// CHECK: end sil function '$sIeg_IegH_TR'
63+
64+
65+
actor AnActor {
66+
func isolatedMethod(_ i: Int) {}
67+
68+
// CHECK-LABEL: sil hidden [ossa] @$s4test7AnActorC6calleryyYaF : $@convention(method) @async (@guaranteed AnActor) -> () {
69+
func caller() async {
70+
// CHECK: hop_to_executor {{..*}} : $AnActor
71+
// [[F:%.*]] = function_ref @$s4test7AnActorC6calleryyYaFySicACYicfu_ : $@convention(thin) (@guaranteed AnActor) -> @owned @callee_guaranteed (Int) -> ()
72+
// [[APPLIED_F:%.*]] = apply [[F]]({{.*}}) : $@convention(thin) (@guaranteed AnActor) -> @owned @callee_guaranteed (Int) -> ()
73+
// [[BORROWED_F:%.*]] = begin_borrow [lexical] [[APPLIED_F]] : $@callee_guaranteed (Int) -> ()
74+
// [[COPIED_F:%.*]] = copy_value [[BORROWED_F]] : $@callee_guaranteed (Int) -> ()
75+
// [[THUNK:%.*]] = function_ref @$sSiIegy_SiIegHy_TR : $@convention(thin) @async (Int, @guaranteed @callee_guaranteed (Int) -> ()) -> ()
76+
// = partial_apply [callee_guaranteed] [[THUNK]]([[COPIED_F]]) : $@convention(thin) @async (Int, @guaranteed @callee_guaranteed (Int) -> ()) -> ()
77+
// ... after applying ...
78+
// CHECK: hop_to_executor {{.*}} : $AnActor
79+
let f: (Int) -> () = self.isolatedMethod
80+
let g: (Int) async -> () = f
81+
await g(0)
82+
}
83+
} // CHECK: end sil function '$s4test7AnActorC6calleryyYaF'
84+
85+
// CHECK-LABEL: sil shared [transparent] [serialized] [reabstraction_thunk] [ossa] @$sSiIegy_SiIegHy_TR : $@convention(thin) @async (Int, @guaranteed @callee_guaranteed (Int) -> ()) -> () {
86+
// CHECK-NOT: hop_to_executor
87+
// CHECK: end sil function '$sSiIegy_SiIegHy_TR'
88+

0 commit comments

Comments
 (0)