Skip to content

Commit 864c143

Browse files
committed
Devirtualizer: fix a crash due to a not supported bitcast of ABI compatible types
When devirtualizing witness method calls, it can happen that we need a cast between ABI compatible return types. We were missing supporting type casts between nominal types which are ABI compatible. This comes from whole-module reasoning of protocol conformances. If a protocol only has a single conformance where the associated type (`ID`) is some concrete type (e.g. `Int`), then the devirtualizer knows that `p.get()` can only return an `Int`: ``` public struct X2<ID> { let p: any P2<ID> public func testit(i: ID, x: ID) -> S2<ID> { return p.get(x: x) } } ``` and after devirtualizing the `get` function, its result must be cast from `Int` to `ID`. The `layoutIsTypeDependent` utility is basically only used here to assert that this cast can only happen between layout compatible types. rdar://129004015
1 parent 9cb0113 commit 864c143

File tree

2 files changed

+200
-7
lines changed

2 files changed

+200
-7
lines changed

lib/SILOptimizer/Utils/InstOptUtils.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
14+
#include "swift/AST/CanTypeVisitor.h"
1415
#include "swift/AST/GenericSignature.h"
1516
#include "swift/AST/SemanticAttrs.h"
1617
#include "swift/AST/SubstitutionMap.h"
@@ -745,6 +746,30 @@ swift::castValueToABICompatibleType(SILBuilder *builder, SILPassManager *pm,
745746
false};
746747
}
747748
}
749+
NominalTypeDecl *srcNominal = srcTy.getNominalOrBoundGenericNominal();
750+
NominalTypeDecl *destNominal = destTy.getNominalOrBoundGenericNominal();
751+
if (srcNominal && srcNominal == destNominal &&
752+
!layoutIsTypeDependent(srcNominal) &&
753+
srcTy.isObject() && destTy.isObject()) {
754+
755+
// This can be a result from whole-module reasoning of protocol conformances.
756+
// If a protocol only has a single conformance where the associated type (`ID`) is some
757+
// concrete type (e.g. `Int`), then the devirtualizer knows that `p.get()`
758+
// can only return an `Int`:
759+
// ```
760+
// public struct X2<ID> {
761+
// let p: any P2<ID>
762+
// public func testit(i: ID, x: ID) -> S2<ID> {
763+
// return p.get(x: x)
764+
// }
765+
// }
766+
// ```
767+
// and after devirtualizing the `get` function, its result must be cast from `Int` to `ID`.
768+
//
769+
// The `layoutIsTypeDependent` utility is basically only used here to assert that this
770+
// cast can only happen between layout compatible types.
771+
return {builder->createUncheckedForwardingCast(loc, value, destTy), false};
772+
}
748773

749774
llvm::errs() << "Source type: " << srcTy << "\n";
750775
llvm::errs() << "Destination type: " << destTy << "\n";
Lines changed: 175 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,187 @@
1-
// RUN: %target-swift-frontend %s -O -emit-sil | %FileCheck %s
1+
// RUN: %target-swift-frontend %s -O -Xllvm -sil-disable-pass=FunctionSignatureOpts -module-name=test -emit-sil | %FileCheck %s
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: %target-build-swift -wmo -O -Xllvm -sil-disable-pass=FunctionSignatureOpts -module-name=test %s -o %t/a.out
5+
// RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT
6+
// REQUIRES: executable_test
7+
8+
// Test conversions of return types.
9+
10+
public struct S1<ID> {
11+
var x: Int
12+
}
13+
14+
protocol P1<ID> {
15+
associatedtype ID
16+
17+
func get(x: ID) -> S1<ID>
18+
}
19+
20+
struct Y1: P1 {
21+
func get(x: Int) -> S1<Int> {
22+
return S1(x: 27)
23+
}
24+
}
25+
26+
public struct X1<ID> {
27+
let p: any P1<ID>
28+
29+
// CHECK-LABEL: sil {{.*}} @$s4test2X1V6testit1i1xAA2S1VyxGx_xtF :
30+
// CHECK: unchecked_trivial_bit_cast
31+
// CHECK: } // end sil function '$s4test2X1V6testit1i1xAA2S1VyxGx_xtF'
32+
@_semantics("optimize.sil.specialize.generic.never")
33+
@inline(never)
34+
public func testit(i: ID, x: ID) -> S1<ID> {
35+
return p.get(x: x)
36+
}
37+
}
38+
39+
public struct S2<ID> {
40+
var x: String
41+
}
42+
43+
protocol P2<ID> {
44+
associatedtype ID
45+
46+
func get(x: ID) -> S2<ID>
47+
}
48+
49+
struct Y2: P2 {
50+
func get(x: Int) -> S2<Int> {
51+
return S2(x: "27")
52+
}
53+
}
54+
55+
public struct X2<ID> {
56+
let p: any P2<ID>
57+
58+
// CHECK-LABEL: sil {{.*}} @$s4test2X2V6testit1i1xAA2S2VyxGx_xtF :
59+
// CHECK: unchecked_bitwise_cast
60+
// CHECK: } // end sil function '$s4test2X2V6testit1i1xAA2S2VyxGx_xtF'
61+
@_semantics("optimize.sil.specialize.generic.never")
62+
@inline(never)
63+
public func testit(i: ID, x: ID) -> S2<ID> {
64+
return p.get(x: x)
65+
}
66+
}
67+
68+
69+
class C3<T> {}
70+
71+
public struct S3<ID> {
72+
var x: C3<ID>
73+
}
74+
75+
protocol P3<ID> {
76+
associatedtype ID
77+
78+
func get(x: ID) -> S3<ID>
79+
}
80+
81+
struct Y3: P3 {
82+
func get(x: Int) -> S3<Int> {
83+
return S3(x: C3<Int>())
84+
}
85+
}
86+
87+
public struct X3<ID> {
88+
let p: any P3<ID>
89+
90+
// CHECK-LABEL: sil {{.*}} @$s4test2X3V6testit1i1xAA2S3VyxGx_xtF :
91+
// CHECK: unchecked_bitwise_cast
92+
// CHECK: } // end sil function '$s4test2X3V6testit1i1xAA2S3VyxGx_xtF'
93+
@_semantics("optimize.sil.specialize.generic.never")
94+
@inline(never)
95+
public func testit(i: ID, x: ID) -> S3<ID> {
96+
return p.get(x: x)
97+
}
98+
}
99+
100+
101+
public class C4<T> {}
102+
103+
protocol P4<ID> {
104+
associatedtype ID
105+
106+
func get(x: ID) -> C4<ID>
107+
}
108+
109+
struct Y4: P4 {
110+
func get(x: Int) -> C4<Int> {
111+
return C4()
112+
}
113+
}
114+
115+
public struct X4<ID> {
116+
let p: any P4<ID>
117+
118+
// CHECK-LABEL: sil {{.*}} @$s4test2X4V6testit1i1xAA2C4CyxGx_xtF :
119+
// CHECK: unchecked_ref_cast
120+
// CHECK: } // end sil function '$s4test2X4V6testit1i1xAA2C4CyxGx_xtF'
121+
@_semantics("optimize.sil.specialize.generic.never")
122+
@inline(never)
123+
public func testit(i: ID, x: ID) -> C4<ID> {
124+
return p.get(x: x)
125+
}
126+
}
127+
128+
129+
public struct S5<ID> {
130+
var x: (Int, C4<ID>)
131+
}
132+
133+
protocol P5<ID> {
134+
associatedtype ID
135+
136+
func get(x: ID) -> S5<ID>
137+
}
138+
139+
struct Y5: P5 {
140+
func get(x: Int) -> S5<Int> {
141+
return S5(x: (27, C4<Int>()))
142+
}
143+
}
144+
145+
public struct X5<ID> {
146+
let p: any P5<ID>
147+
148+
// CHECK-LABEL: sil {{.*}} @$s4test2X5V6testit1i1xAA2S5VyxGx_xtF :
149+
// CHECK: unchecked_bitwise_cast
150+
// CHECK: } // end sil function '$s4test2X5V6testit1i1xAA2S5VyxGx_xtF'
151+
@_semantics("optimize.sil.specialize.generic.never")
152+
@inline(never)
153+
public func testit(i: ID, x: ID) -> S5<ID> {
154+
return p.get(x: x)
155+
}
156+
}
157+
158+
// Basic test
2159

3160
protocol Pingable {
4161
func ping(_ x : Int);
5162
}
6163
class Foo : Pingable {
7-
func ping(_ x : Int) { var t : Int }
164+
func ping(_ x : Int) { _ = 1 }
8165
}
9166

10167
// Everything gets devirtualized, inlined, and promoted to the stack.
11-
//CHECK: @$s24devirtualize_existential17interesting_stuffyyF
12-
//CHECK-NOT: init_existential_addr
13-
//CHECK-NOT: apply
14-
//CHECK: return
168+
//CHECK-LABEL: sil @$s4test17interesting_stuffyyF :
169+
//CHECK-NOT: init_existential_addr
170+
//CHECK-NOT: apply
171+
//CHECK: } // end sil function '$s4test17interesting_stuffyyF'
15172
public func interesting_stuff() {
16-
var x : Pingable = Foo()
173+
let x : Pingable = Foo()
17174
x.ping(1)
18175
}
19176

177+
// CHECK-OUTPUT: S1<Int>(x: 27)
178+
print(X1<Int>(p: Y1()).testit(i: 1, x: 2))
179+
// CHECK-OUTPUT: S2<Int>(x: "27")
180+
print(X2<Int>(p: Y2()).testit(i: 1, x: 2))
181+
// CHECK-OUTPUT: S3<Int>(x: test.C3<Swift.Int>)
182+
print(X3<Int>(p: Y3()).testit(i: 1, x: 2))
183+
// CHECK-OUTPUT: test.C4<Swift.Int>
184+
print(X4<Int>(p: Y4()).testit(i: 1, x: 2))
185+
// CHECK-OUTPUT: S5<Int>(x: (27, test.C4<Swift.Int>))
186+
print(X5<Int>(p: Y5()).testit(i: 1, x: 2))
187+

0 commit comments

Comments
 (0)