Skip to content

Commit a823cad

Browse files
authored
Merge pull request #74653 from eeckstein/fix-devirtualizer-6.0
[6.0] Devirtualizer: fix a crash due to a not supported bitcast of ABI compatible types
2 parents 54898ce + a56657e commit a823cad

File tree

3 files changed

+311
-7
lines changed

3 files changed

+311
-7
lines changed

include/swift/SILOptimizer/Utils/InstOptUtils.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,20 @@ std::pair<SILValue, bool /* changedCFG */>
197197
castValueToABICompatibleType(SILBuilder *builder, SILLocation Loc,
198198
SILValue value, SILType srcTy, SILType destTy,
199199
ArrayRef<SILInstruction *> usePoints);
200+
201+
/// Returns true if the layout of a generic nominal type is dependent on its generic parameters.
202+
/// This is usually the case. Some examples, where they layout is _not_ dependent:
203+
/// ```
204+
/// struct S<T> {
205+
/// var x: Int // no members which depend on T
206+
/// }
207+
///
208+
/// struct S<T> {
209+
/// var c: SomeClass<T> // a class reference does not depend on the layout of the class
210+
/// }
211+
/// ```
212+
bool layoutIsTypeDependent(NominalTypeDecl *decl);
213+
200214
/// Peek through trivial Enum initialization, typically for pointless
201215
/// Optionals.
202216
///

lib/SILOptimizer/Utils/InstOptUtils.cpp

Lines changed: 122 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"
@@ -736,12 +737,133 @@ swift::castValueToABICompatibleType(SILBuilder *builder, SILLocation loc,
736737
false};
737738
}
738739
}
740+
NominalTypeDecl *srcNominal = srcTy.getNominalOrBoundGenericNominal();
741+
NominalTypeDecl *destNominal = destTy.getNominalOrBoundGenericNominal();
742+
if (srcNominal && srcNominal == destNominal &&
743+
!layoutIsTypeDependent(srcNominal) &&
744+
srcTy.isObject() && destTy.isObject()) {
745+
746+
// This can be a result from whole-module reasoning of protocol conformances.
747+
// If a protocol only has a single conformance where the associated type (`ID`) is some
748+
// concrete type (e.g. `Int`), then the devirtualizer knows that `p.get()`
749+
// can only return an `Int`:
750+
// ```
751+
// public struct X2<ID> {
752+
// let p: any P2<ID>
753+
// public func testit(i: ID, x: ID) -> S2<ID> {
754+
// return p.get(x: x)
755+
// }
756+
// }
757+
// ```
758+
// and after devirtualizing the `get` function, its result must be cast from `Int` to `ID`.
759+
//
760+
// The `layoutIsTypeDependent` utility is basically only used here to assert that this
761+
// cast can only happen between layout compatible types.
762+
return {builder->createUncheckedForwardingCast(loc, value, destTy), false};
763+
}
739764

740765
llvm::errs() << "Source type: " << srcTy << "\n";
741766
llvm::errs() << "Destination type: " << destTy << "\n";
742767
llvm_unreachable("Unknown combination of types for casting");
743768
}
744769

770+
namespace {
771+
class TypeDependentVisitor : public CanTypeVisitor<TypeDependentVisitor, bool> {
772+
public:
773+
// If the type isn't actually dependent, we're okay.
774+
bool visit(CanType type) {
775+
if (!type->hasArchetype() && !type->hasTypeParameter())
776+
return false;
777+
return CanTypeVisitor::visit(type);
778+
}
779+
780+
bool visitStructType(CanStructType type) {
781+
return visitStructDecl(type->getDecl());
782+
}
783+
bool visitBoundGenericStructType(CanBoundGenericStructType type) {
784+
return visitStructDecl(type->getDecl());
785+
}
786+
bool visitStructDecl(StructDecl *decl) {
787+
auto rawLayout = decl->getAttrs().getAttribute<RawLayoutAttr>();
788+
if (rawLayout) {
789+
if (auto likeType = rawLayout->getResolvedScalarLikeType(decl)) {
790+
return visit((*likeType)->getCanonicalType());
791+
} else if (auto likeArray = rawLayout->getResolvedArrayLikeTypeAndCount(decl)) {
792+
return visit(likeArray->first->getCanonicalType());
793+
}
794+
}
795+
796+
for (auto field : decl->getStoredProperties()) {
797+
if (visit(field->getInterfaceType()->getCanonicalType()))
798+
return true;
799+
}
800+
return false;
801+
}
802+
803+
bool visitEnumType(CanEnumType type) {
804+
return visitEnumDecl(type->getDecl());
805+
}
806+
bool visitBoundGenericEnumType(CanBoundGenericEnumType type) {
807+
return visitEnumDecl(type->getDecl());
808+
}
809+
bool visitEnumDecl(EnumDecl *decl) {
810+
if (decl->isIndirect())
811+
return false;
812+
813+
for (auto elt : decl->getAllElements()) {
814+
if (!elt->hasAssociatedValues() || elt->isIndirect())
815+
continue;
816+
817+
if (visit(elt->getArgumentInterfaceType()->getCanonicalType()))
818+
return true;
819+
}
820+
return false;
821+
}
822+
823+
bool visitTupleType(CanTupleType type) {
824+
for (auto eltTy : type.getElementTypes()) {
825+
if (visit(eltTy->getCanonicalType()))
826+
return true;
827+
}
828+
return false;
829+
}
830+
831+
// A class reference does not depend on the layout of the class.
832+
bool visitClassType(CanClassType type) {
833+
return false;
834+
}
835+
bool visitBoundGenericClassType(CanBoundGenericClassType type) {
836+
return false;
837+
}
838+
839+
// The same for non-strong references.
840+
bool visitReferenceStorageType(CanReferenceStorageType type) {
841+
return false;
842+
}
843+
844+
// All function types have the same layout.
845+
bool visitAnyFunctionType(CanAnyFunctionType type) {
846+
return false;
847+
}
848+
849+
// The safe default for types we didn't handle above.
850+
bool visitType(CanType type) {
851+
return true;
852+
}
853+
};
854+
} // end anonymous namespace
855+
856+
bool swift::layoutIsTypeDependent(NominalTypeDecl *decl) {
857+
if (auto *classDecl = dyn_cast<ClassDecl>(decl)) {
858+
return false;
859+
} else if (auto *structDecl = dyn_cast<StructDecl>(decl)) {
860+
return TypeDependentVisitor().visitStructDecl(structDecl);
861+
} else {
862+
auto *enumDecl = cast<EnumDecl>(decl);
863+
return TypeDependentVisitor().visitEnumDecl(enumDecl);
864+
}
865+
}
866+
745867
ProjectBoxInst *swift::getOrCreateProjectBox(AllocBoxInst *abi,
746868
unsigned index) {
747869
SILBasicBlock::iterator iter(abi);
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)