Skip to content

Commit 53c6173

Browse files
[SILGen] Create thunks for non-throwing -> indirect error result conversions
When a non-throwing function is passed to a function that expects a throwing function with an indirect error result, we need to create a thunk because the indirect error result pointer is passed as an extra trailing parameter. Extra trailing parameters except for `swifterror` and `swiftself` are not allowed on some platforms like WebAssembly, so they need to be called as exactly the same function signature. ```swift func passThrough<X>(_ c: () throws(X) -> Void) throws(X) { try c() } func neverThrow() {} passThrough(neverThrow) ```
1 parent 8afcdff commit 53c6173

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed

lib/SIL/IR/TypeLowering.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4664,6 +4664,12 @@ TypeConverter::checkFunctionForABIDifferences(SILModule &M,
46644664
return ABIDifference::NeedsThunk;
46654665
}
46664666

4667+
// Non-throwing functions and functions with indirect error result need thunk
4668+
// because of the trailing error result storage parameter.
4669+
if (fnTy1->hasIndirectErrorResult() != fnTy2->hasIndirectErrorResult()) {
4670+
return ABIDifference::NeedsThunk;
4671+
}
4672+
46674673
// Asynchronous functions require a thunk if they differ in whether they
46684674
// have an error result.
46694675
if (fnTy1->hasErrorResult() != fnTy2->hasErrorResult() &&

test/SILGen/typed_throws_thunk.swift

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// RUN: %target-swift-emit-silgen %s | %FileCheck %s
2+
3+
struct ConcreteError: Error {}
4+
5+
func existentialPassThrough(_ body: () throws(Error) -> Void) throws(Error) {
6+
try body()
7+
}
8+
9+
func concretePassThrough(_ body: () throws(ConcreteError) -> Void) throws(ConcreteError) {
10+
try body()
11+
}
12+
13+
func genericPassThrough<X>(_ body: () throws(X) -> Void) throws(X) {
14+
try body()
15+
}
16+
17+
func neverThrow() {}
18+
func throwConcrete() throws(ConcreteError) {}
19+
func throwExistential() throws {}
20+
21+
// CHECK-LABEL: sil{{.*}} @$s18typed_throws_thunk5test1yyKF
22+
func test1() throws {
23+
// No thunk needed here because no indirect error result
24+
// CHECK-NOT: function_ref thunk for
25+
// CHECK: [[FN:%[0-9]+]] = function_ref @$s18typed_throws_thunk22existentialPassThroughyyyyKXEKF
26+
// CHECK-NEXT: try_apply [[FN]]
27+
try existentialPassThrough(neverThrow)
28+
}
29+
30+
// CHECK-LABEL: sil{{.*}} @$s18typed_throws_thunk5test2yyKF
31+
func test2() throws {
32+
// No thunk needed here because no indirect error result
33+
// CHECK-NOT: function_ref thunk for
34+
// CHECK: [[FN:%[0-9]+]] = function_ref @$s18typed_throws_thunk19concretePassThroughyyyyAA13ConcreteErrorVYKXEADYKF
35+
// CHECK-NEXT: try_apply [[FN]]
36+
try concretePassThrough(neverThrow)
37+
}
38+
39+
// CHECK-LABEL: sil{{.*}} @$s18typed_throws_thunk5test3yyF
40+
func test3() {
41+
// CHECK: [[T1:%[0-9]+]] = function_ref @$s18typed_throws_thunk10neverThrowyyF
42+
// CHECK-NEXT: [[T2:%[0-9]+]] = thin_to_thick_function [[T1]]
43+
// CHECK-NEXT: [[T3:%[0-9]+]] = convert_escape_to_noescape [not_guaranteed] [[T2]]
44+
// CHECK-NEXT: function_ref thunk for
45+
// CHECK-NEXT: [[THUNK:%[0-9]+]] = function_ref @$sIg_s5NeverOIegzr_TR
46+
// CHECK-NEXT: [[T4:%[0-9]+]] = partial_apply [callee_guaranteed] [[THUNK]]([[T3]])
47+
// CHECK-NEXT: [[T5:%[0-9]+]] = convert_function [[T4]]
48+
// CHECK-NEXT: [[T6:%[0-9]+]] = convert_escape_to_noescape [not_guaranteed] [[T5]]
49+
// CHECK-NEXT: // function_ref genericPassThrough<A>(_:)
50+
// CHECK-NEXT: [[T7:%[0-9]+]] = function_ref @$s18typed_throws_thunk18genericPassThroughyyyyxYKXExYKs5ErrorRzlF
51+
// CHECK-NEXT: [[T8:%[0-9]+]] = alloc_stack $Never
52+
// CHECK-NEXT: try_apply [[T7]]<Never>([[T8]], [[T6]])
53+
genericPassThrough(neverThrow)
54+
}
55+
56+
// CHECK-LABEL: sil{{.*}} @$s18typed_throws_thunk5test4yyyyxYKXEKs5ErrorRzlF
57+
// Indirect -> Indirect should not require a thunk
58+
func test4<T: Error>(_ x: () throws(T) -> Void) throws {
59+
// CHECK-NOT: function_ref thunk for
60+
// CHECK: [[FN:%[0-9]+]] = function_ref @$s18typed_throws_thunk18genericPassThroughyyyyxYKXExYKs5ErrorRzlF
61+
// CHECK: try_apply [[FN]]
62+
try genericPassThrough(x)
63+
}
64+
65+
// CHECK-LABEL: sil{{.*}} @$s18typed_throws_thunk5test5yyyyxYKXEKs5ErrorRzlF
66+
// Indirect -> Existential should require a thunk
67+
func test5<T: Error>(_ x: () throws(T) -> Void) throws {
68+
// CHECK: bb0([[T0:%[0-9]+]]
69+
// CHECK: [[T3:%[0-9]+]] = copy_value [[T0]]
70+
// CHECK-NEXT: [[T4:%[0-9]+]] = convert_function [[T3]]
71+
// CHECK-NEXT: // function_ref thunk for @callee_guaranteed () -> (@error @out A)
72+
// CHECK-NEXT: [[T5:%[0-9]+]] = function_ref @$sxIgzr_s5Error_pIegzo_sAARzlTR
73+
// CHECK-NEXT: [[T6:%[0-9]+]] = partial_apply [callee_guaranteed] [[T5]]<T>([[T4]])
74+
// CHECK-NEXT: [[T7:%[0-9]+]] = convert_escape_to_noescape [not_guaranteed] [[T6]]
75+
// CHECK-NEXT: // function_ref existentialPassThrough(_:)
76+
// CHECK-NEXT: [[T8:%[0-9]+]] = function_ref @$s18typed_throws_thunk22existentialPassThroughyyyyKXEKF
77+
// CHECK-NEXT: try_apply [[T8]]([[T7]])
78+
try existentialPassThrough(x)
79+
}
80+
81+
// CHECK-LABEL: sil{{.*}} @$s18typed_throws_thunk5test6yyKF
82+
// Concrete -> Existential should require a thunk
83+
func test6() throws {
84+
// CHECK: [[T1:%[0-9]+]] = function_ref @$s18typed_throws_thunk13throwConcreteyyAA0E5ErrorVYKF
85+
// CHECK-NEXT: [[T2:%[0-9]+]] = thin_to_thick_function [[T1]]
86+
// CHECK-NEXT: // function_ref thunk for @escaping @callee_guaranteed () -> (@error @owned ConcreteError)
87+
// CHECK-NEXT: [[T3:%[0-9]+]] = function_ref @$s18typed_throws_thunk13ConcreteErrorVIegzo_s0E0_pIegzo_TR
88+
// CHECK-NEXT: [[T4:%[0-9]+]] = partial_apply [callee_guaranteed] [[T3]]([[T2]])
89+
// CHECK-NEXT: [[T5:%[0-9]+]] = convert_escape_to_noescape [not_guaranteed] [[T4]]
90+
// CHECK-NEXT: // function_ref existentialPassThrough(_:)
91+
// CHECK-NEXT: [[T6:%[0-9]+]] = function_ref @$s18typed_throws_thunk22existentialPassThroughyyyyKXEKF
92+
// CHECK-NEXT: try_apply [[T6]]([[T5]])
93+
try existentialPassThrough(throwConcrete)
94+
}
95+
96+
// CHECK-LABEL: sil{{.*}} @$s18typed_throws_thunk5test7yyKF
97+
// Concrete -> Concrete should not require a thunk
98+
func test7() throws {
99+
// CHECK-NOT: function_ref thunk for
100+
// CHECK: [[FN:%[0-9]+]] = function_ref @$s18typed_throws_thunk19concretePassThroughyyyyAA13ConcreteErrorVYKXEADYKF
101+
// CHECK: try_apply [[FN]]
102+
try concretePassThrough(throwConcrete)
103+
}
104+
105+
// CHECK-LABEL: sil{{.*}} @$s18typed_throws_thunk5test8yyKF
106+
// Concrete -> Indirect should require a thunk
107+
func test8() throws {
108+
// CHECK: [[T1:%[0-9]+]] = function_ref @$s18typed_throws_thunk13throwConcreteyyAA0E5ErrorVYKF
109+
// CHECK-NEXT: [[T2:%[0-9]+]] = thin_to_thick_function [[T1]]
110+
// CHECK-NEXT: [[T3:%[0-9]+]] = convert_escape_to_noescape [not_guaranteed] [[T2]]
111+
// CHECK-NEXT: // function_ref thunk for @callee_guaranteed () -> (@error @owned ConcreteError)
112+
// CHECK-NEXT: [[T4:%[0-9]+]] = function_ref @$s18typed_throws_thunk13ConcreteErrorVIgzo_ACIegzr_TR
113+
// CHECK-NEXT: [[T5:%[0-9]+]] = partial_apply [callee_guaranteed] [[T4]]([[T3]])
114+
// CHECK-NEXT: [[T6:%[0-9]+]] = convert_function [[T5]]
115+
// CHECK-NEXT: [[T7:%[0-9]+]] = convert_escape_to_noescape [not_guaranteed] [[T6]]
116+
// CHECK-NEXT: // function_ref genericPassThrough<A>(_:)
117+
// CHECK-NEXT: [[T8:%[0-9]+]] = function_ref @$s18typed_throws_thunk18genericPassThroughyyyyxYKXExYKs5ErrorRzlF
118+
// CHECK-NEXT: [[T9:%[0-9]+]] = alloc_stack $ConcreteError
119+
// CHECK-NEXT: try_apply [[T8]]<ConcreteError>([[T9]], [[T7]])
120+
try genericPassThrough(throwConcrete)
121+
}
122+
123+
// CHECK-LABEL: sil{{.*}} @$s18typed_throws_thunk5test9yyKF
124+
// Existential -> Indirect should require a thunk
125+
func test9() throws {
126+
// CHECK: [[T1:%[0-9]+]] = function_ref @$s18typed_throws_thunk16throwExistentialyyKF
127+
// CHECK-NEXT: [[T2:%[0-9]+]] = thin_to_thick_function [[T1]]
128+
// CHECK-NEXT: [[T3:%[0-9]+]] = convert_escape_to_noescape [not_guaranteed] [[T2]]
129+
// CHECK-NEXT: // function_ref thunk for @callee_guaranteed () -> (@error @owned Error)
130+
// CHECK-NEXT: [[T4:%[0-9]+]] = function_ref @$ss5Error_pIgzo_sAA_pIegzr_TR
131+
// CHECK-NEXT: [[T5:%[0-9]+]] = partial_apply [callee_guaranteed] [[T4]]([[T3]])
132+
// CHECK-NEXT: [[T6:%[0-9]+]] = convert_function [[T5]]
133+
// CHECK-NEXT: [[T7:%[0-9]+]] = convert_escape_to_noescape [not_guaranteed] [[T6]]
134+
// CHECK-NEXT: // function_ref genericPassThrough<A>(_:)
135+
// CHECK-NEXT: [[T8:%[0-9]+]] = function_ref @$s18typed_throws_thunk18genericPassThroughyyyyxYKXExYKs5ErrorRzlF
136+
// CHECK-NEXT: [[T9:%[0-9]+]] = alloc_stack $any Error
137+
// CHECK-NEXT: try_apply [[T8]]<any Error>([[T9]], [[T7]])
138+
try genericPassThrough(throwExistential)
139+
}

0 commit comments

Comments
 (0)