Skip to content

Commit 0fc5035

Browse files
authored
Merge pull request #74574 from eeckstein/fix-devirtualizer
2 parents a03fcb5 + 864c143 commit 0fc5035

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
@@ -198,6 +198,20 @@ castValueToABICompatibleType(SILBuilder *builder, SILPassManager *pm,
198198
SILLocation Loc,
199199
SILValue value, SILType srcTy, SILType destTy,
200200
ArrayRef<SILInstruction *> usePoints);
201+
202+
/// Returns true if the layout of a generic nominal type is dependent on its generic parameters.
203+
/// This is usually the case. Some examples, where they layout is _not_ dependent:
204+
/// ```
205+
/// struct S<T> {
206+
/// var x: Int // no members which depend on T
207+
/// }
208+
///
209+
/// struct S<T> {
210+
/// var c: SomeClass<T> // a class reference does not depend on the layout of the class
211+
/// }
212+
/// ```
213+
bool layoutIsTypeDependent(NominalTypeDecl *decl);
214+
201215
/// Peek through trivial Enum initialization, typically for pointless
202216
/// Optionals.
203217
///

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"
@@ -745,12 +746,133 @@ 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";
751776
llvm_unreachable("Unknown combination of types for casting");
752777
}
753778

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