Skip to content

Commit 28ee477

Browse files
authored
Merge pull request #62523 from eeckstein/improve-devirtualizer
Devirtualizer: don't de-virtualize witness calls to non-generic thunks which call a generic function.
2 parents 4cf6f79 + 21b4004 commit 28ee477

File tree

4 files changed

+140
-7
lines changed

4 files changed

+140
-7
lines changed

lib/SILOptimizer/Utils/Devirtualize.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,24 @@ devirtualizeWitnessMethod(ApplySite applySite, SILFunction *f,
11221122
return {newApplySite, changedCFG};
11231123
}
11241124

1125+
static bool isNonGenericThunkOfGenericExternalFunction(SILFunction *thunk) {
1126+
if (!thunk->isThunk())
1127+
return false;
1128+
if (thunk->getGenericSignature())
1129+
return false;
1130+
for (SILBasicBlock &block : *thunk) {
1131+
for (SILInstruction &inst : block) {
1132+
if (FullApplySite fas = FullApplySite::isa(&inst)) {
1133+
if (SILFunction *calledFunc = fas.getReferencedFunctionOrNull()) {
1134+
if (fas.hasSubstitutions() && !calledFunc->isDefinition())
1135+
return true;
1136+
}
1137+
}
1138+
}
1139+
}
1140+
return false;
1141+
}
1142+
11251143
static bool canDevirtualizeWitnessMethod(ApplySite applySite) {
11261144
SILFunction *f;
11271145
SILWitnessTable *wt;
@@ -1150,6 +1168,25 @@ static bool canDevirtualizeWitnessMethod(ApplySite applySite) {
11501168
return false;
11511169
}
11521170

1171+
// The following check is for performance reasons: if `f` is a non-generic thunk
1172+
// which calls a (not inlinable) generic function in the defining module, it's
1173+
// more efficient to not devirtualize, but call the non-generic thunk - even though
1174+
// it's done through the witness table.
1175+
// Example:
1176+
// ```
1177+
// protocol P {
1178+
// func f(x: [Int]) // not generic
1179+
// }
1180+
// struct S: P {
1181+
// func f(x: some RandomAccessCollection<Int>) { ... } // generic
1182+
// }
1183+
// ```
1184+
// In the defining module, the generic conformance can be specialized (which is not
1185+
// possible in the client module, because it's not inlinable).
1186+
if (isNonGenericThunkOfGenericExternalFunction(f)) {
1187+
return false;
1188+
}
1189+
11531190
// FIXME: devirtualizeWitnessMethod does not support cases with covariant
11541191
// 'Self'-rooted type parameters nested inside a collection type, like
11551192
// '[Self]' or '[* : Self.A]', because it doesn't know how to deal with

test/ModuleInterface/Conformances.swiftinterface

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
// RUN: %empty-directory(%t)
55
// RUN: %target-swift-frontend -emit-module-path %t/Conformances.swiftmodule -enable-library-evolution -emit-sil -o %t/Conformances.sil -enable-objc-interop -disable-objc-attr-requires-foundation-module %s
6-
// RUN: %FileCheck -check-prefix CHECK-MODULE %s < %t/Conformances.sil
7-
// RUN: %FileCheck -check-prefix NEGATIVE-MODULE %s < %t/Conformances.sil
86
// RUN: %target-swift-frontend -enable-objc-interop -emit-sil -I %t %S/Inputs/ConformancesUser.swift -O | %FileCheck %s
97

108
public protocol MyProto {
@@ -41,11 +39,11 @@ public struct FullStructImpl: MyProto {
4139
public struct OpaqueStructImpl: MyProto {}
4240

4341
// CHECK-LABEL: sil @$s16ConformancesUser10testOpaqueSiyF
44-
// CHECK: function_ref @$s12Conformances7MyProtoPxycfC
42+
// CHECK: witness_method $OpaqueStructImpl, #MyProto.init!allocator
4543
// Note the default implementation is filled in here.
46-
// CHECK: function_ref @$s12Conformances7MyProtoPAAE6methodyyF
47-
// CHECK: function_ref @$s12Conformances7MyProtoP4propSivs
48-
// CHECK: function_ref @$s12Conformances7MyProtoPyS2icig
44+
// CHECK: witness_method $OpaqueStructImpl, #MyProto.method
45+
// CHECK: witness_method $OpaqueStructImpl, #MyProto.prop!setter
46+
// CHECK: witness_method $OpaqueStructImpl, #MyProto.subscript!getter
4947
// CHECK: end sil function '$s16ConformancesUser10testOpaqueSiyF'
5048

5149

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// First test: without cross-module-optimization
4+
5+
// RUN: %target-build-swift -O -wmo -disable-cmo -parse-as-library -DMODULE -emit-module -emit-module-path=%t/Module.swiftmodule -module-name=Module %s
6+
// RUN: %target-build-swift -O -wmo -module-name=Main -I%t %s -c -emit-sil | %FileCheck --check-prefix=CHECK --check-prefix=CHECK-NOCMO %s
7+
8+
// Second test: with cross-module-optimization (which is the default)
9+
10+
// RUN: %target-build-swift -O -wmo -Xfrontend -enable-default-cmo -parse-as-library -DMODULE -emit-module -emit-module-path=%t/Module.swiftmodule -module-name=Module %s
11+
// RUN: %target-build-swift -O -wmo -module-name=Main -I%t %s -c -emit-sil | %FileCheck --check-prefix=CHECK --check-prefix=CHECK-CMO %s
12+
13+
#if MODULE
14+
15+
public protocol P {
16+
func foo(x: [Int])
17+
func bar(x: [Int])
18+
func baz(x: some RandomAccessCollection<Int>)
19+
}
20+
21+
public struct S: P {
22+
public init() {}
23+
24+
@inline(never)
25+
public func foo(x: some RandomAccessCollection<Int>) { }
26+
@inline(never)
27+
public func bar(x: [Int]) { }
28+
@inline(never)
29+
public func baz(x: some RandomAccessCollection<Int>) { }
30+
}
31+
32+
#else
33+
34+
import Module
35+
36+
public struct Local: P {
37+
@inline(never)
38+
public func foo(x: some RandomAccessCollection<Int>) { }
39+
@inline(never)
40+
public func bar(x: [Int]) { }
41+
@inline(never)
42+
public func baz(x: some RandomAccessCollection<Int>) { }
43+
}
44+
45+
// Don't devirtualize in this case because it's better to call the non-generic function
46+
// (which can be fully specialized in the module) via the witness table than the generic
47+
// de-virtualized function.
48+
49+
// CHECK-LABEL: sil @$s4Main24testGenericInOtherModuleyyF
50+
// CHECK-NOCMO: [[F:%[0-9]+]] = witness_method $S, #P.foo
51+
// CHECK-CMO: [[F:%[0-9]+]] = function_ref @$s6Module1SV3foo1xyx_tSkRzSi7ElementRtzlFSaySiG_Tgq5{{.*}}
52+
// CHECK: apply [[F]]
53+
// CHECK: } // end sil function '$s4Main24testGenericInOtherModuleyyF'
54+
public func testGenericInOtherModule() {
55+
let s = S()
56+
callFoo(s, x: [])
57+
}
58+
59+
// CHECK-LABEL: sil @$s4Main27testNonGenericInOtherModuleyyF
60+
// CHECK: [[F:%[0-9]+]] = function_ref @$s6Module1SV3bar1xySaySiG_tF
61+
// CHECK: apply [[F]]
62+
// CHECK: } // end sil function '$s4Main27testNonGenericInOtherModuleyyF'
63+
public func testNonGenericInOtherModule() {
64+
let s = S()
65+
callBar(s, x: [])
66+
}
67+
68+
// CHECK-LABEL: sil @$s4Main35testGenericRequirementInOtherModuleyyF
69+
// CHECK: [[F:%[0-9]+]] = function_ref @$s6Module1SV3baz1xyx_tSkRzSi7ElementRtzlF{{.*}}
70+
// CHECK: apply [[F]]
71+
// CHECK: } // end sil function '$s4Main35testGenericRequirementInOtherModuleyyF'
72+
public func testGenericRequirementInOtherModule() {
73+
let s = S()
74+
callBaz(s, x: [])
75+
}
76+
77+
// CHECK-LABEL: sil @$s4Main23testGenericInSameModuleyyF
78+
// CHECK: [[F:%[0-9]+]] = function_ref @$s4Main5LocalV3foo1xyx_tSkRzSi7ElementRtzlFSaySiG_Tg5
79+
// CHECK: apply [[F]]
80+
// CHECK: } // end sil function '$s4Main23testGenericInSameModuleyyF'
81+
public func testGenericInSameModule() {
82+
let l = Local()
83+
callFoo(l, x: [])
84+
}
85+
86+
func callFoo<T: P>(_ f: T, x: [Int]) {
87+
f.foo(x: x)
88+
}
89+
90+
func callBar<T: P>(_ f: T, x: [Int]) {
91+
f.bar(x: x)
92+
}
93+
94+
func callBaz<T: P>(_ f: T, x: [Int]) {
95+
f.baz(x: x)
96+
}
97+
98+
#endif

test/Serialization/sil-imported-enums.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ import UsesImportedEnums
1616
// CHECK-LABEL: sil hidden @$s4main4test1eSbSo13NSRuncingModeV_tF
1717
func test(e: NSRuncingMode) -> Bool {
1818
// CHECK-NOT: return
19-
// CHECK: $ss2eeoiySbx_xtSYRzSQ8RawValueRpzlF
19+
// CHECK: witness_method $NSRuncingMode, #Equatable."=="
2020
return compareImportedEnumToSelf(e)
2121
}

0 commit comments

Comments
 (0)