Skip to content

Commit a5a826c

Browse files
authored
Merge pull request swiftlang#35064 from atrick/respecialize-existential
Fix ExistentialSpecializer to detect repeated specialization.
2 parents 332569a + f24658f commit a5a826c

File tree

2 files changed

+126
-23
lines changed

2 files changed

+126
-23
lines changed

lib/SILOptimizer/FunctionSignatureTransforms/ExistentialTransform.cpp

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,7 @@ std::string ExistentialTransform::createExistentialSpecializedFunctionName() {
292292
int Idx = IdxIt.first;
293293
Mangler.setArgumentExistentialToGeneric(Idx);
294294
}
295-
auto MangledName = Mangler.mangle();
296-
assert(!F->getModule().hasFunction(MangledName));
297-
return MangledName;
295+
return Mangler.mangle();
298296
}
299297

300298
/// Convert all existential argument types to generic argument type.
@@ -642,41 +640,47 @@ void ExistentialTransform::populateThunkBody() {
642640
/// its inline strategy as always inline.
643641
void ExistentialTransform::createExistentialSpecializedFunction() {
644642
std::string Name = createExistentialSpecializedFunctionName();
645-
SILLinkage linkage = getSpecializedLinkage(F, F->getLinkage());
646643

647-
/// Create devirtualized function type.
644+
/// Create devirtualized function type and populate ArgToGenericTypeMap.
648645
auto NewFTy = createExistentialSpecializedFunctionType();
649646

650-
auto NewFGenericSig = NewFTy->getInvocationGenericSignature();
651-
auto NewFGenericEnv = NewFGenericSig->getGenericEnvironment();
652-
653647
/// Step 1: Create the new protocol constrained generic function.
654-
NewF = FunctionBuilder.createFunction(
648+
if (auto *CachedFn = F->getModule().lookUpFunction(Name)) {
649+
// The specialized body still exists (because it is now called directly),
650+
// but the thunk has been dead-code eliminated.
651+
assert(CachedFn->getLoweredFunctionType() == NewFTy);
652+
NewF = CachedFn;
653+
} else {
654+
auto NewFGenericSig = NewFTy->getInvocationGenericSignature();
655+
auto NewFGenericEnv = NewFGenericSig->getGenericEnvironment();
656+
SILLinkage linkage = getSpecializedLinkage(F, F->getLinkage());
657+
658+
NewF = FunctionBuilder.createFunction(
655659
linkage, Name, NewFTy, NewFGenericEnv, F->getLocation(), F->isBare(),
656660
F->isTransparent(), F->isSerialized(), IsNotDynamic, F->getEntryCount(),
657661
F->isThunk(), F->getClassSubclassScope(), F->getInlineStrategy(),
658662
F->getEffectsKind(), nullptr, F->getDebugScope());
659-
/// Set the semantics attributes for the new function.
660-
for (auto &Attr : F->getSemanticsAttrs())
661-
NewF->addSemanticsAttr(Attr);
662663

663-
/// Set Unqualified ownership, if any.
664-
if (!F->hasOwnership()) {
665-
NewF->setOwnershipEliminated();
666-
}
664+
/// Set the semantics attributes for the new function.
665+
for (auto &Attr : F->getSemanticsAttrs())
666+
NewF->addSemanticsAttr(Attr);
667667

668-
/// Step 1a: Populate the body of NewF.
669-
SubstitutionMap Subs = SubstitutionMap::get(
668+
/// Set Unqualified ownership, if any.
669+
if (!F->hasOwnership()) {
670+
NewF->setOwnershipEliminated();
671+
}
672+
/// Step 1a: Populate the body of NewF.
673+
SubstitutionMap Subs = SubstitutionMap::get(
670674
NewFGenericSig,
671675
[&](SubstitutableType *type) -> Type {
672676
return NewFGenericEnv->mapTypeIntoContext(type);
673677
},
674678
LookUpConformanceInModule(F->getModule().getSwiftModule()));
675-
ExistentialSpecializerCloner cloner(F, NewF, Subs, ArgumentDescList,
676-
ArgToGenericTypeMap,
677-
ExistentialArgDescriptor);
678-
cloner.cloneAndPopulateFunction();
679-
679+
ExistentialSpecializerCloner cloner(F, NewF, Subs, ArgumentDescList,
680+
ArgToGenericTypeMap,
681+
ExistentialArgDescriptor);
682+
cloner.cloneAndPopulateFunction();
683+
}
680684
/// Step 2: Create the thunk with always_inline and populate its body.
681685
populateThunkBody();
682686

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// RUN: %target-sil-opt -enable-sil-verify-all %s -existential-specializer | %FileCheck %s
2+
3+
import Builtin
4+
import Swift
5+
import SwiftShims
6+
7+
// -----------------------------------------------------------------------------
8+
// Test respecialization after the original thunk has been dead-code-eliminated.
9+
//
10+
// Consider the pipeline:
11+
// - GenericSpecializer
12+
// - ExistentialSpecializer
13+
// - Inliner
14+
// - DeadFunctionElimination
15+
// - GenericSpecializer
16+
// - ExistentialSpecializer
17+
//
18+
// This test models the pipeline just before the final invocation of
19+
// ExistentialSpecializer. The SIL now contains a function "wrapFoo"
20+
// that has already been specialized (with the suffix
21+
// "Tf4ee_n"). However, the thunk for that specialization does not
22+
// exist (it would have been inlined and dead-code eliminated by the
23+
// pipeline above). This allows the ExistentialSpecializer to kick in
24+
// again on a call to the same function.
25+
//
26+
// Here we reinvoke ExistentialSpecializer to ensure that it reuses
27+
// the existing specialization of wrapFoo (with the suffix "Tf4ee_n").
28+
// "callPrespecializedWrapFoo" is what drives the optimization this
29+
// time around by calling into the original, unspecialized wrapFoo.
30+
31+
internal protocol SomeProtocol : AnyObject {
32+
func foo() -> Int
33+
}
34+
35+
@_hasMissingDesignatedInitializers public class SomeClass : SomeProtocol {
36+
@_hasStorage @_hasInitialValue var x: Int { get set }
37+
init()
38+
@inline(never) func foo() -> Int
39+
}
40+
41+
// SomeClass.foo()
42+
sil [noinline] @$s1t9SomeClassC3fooSiyF : $@convention(method) (@guaranteed SomeClass) -> Int
43+
44+
// The original "wrapFoo" function prior to existential specialization.
45+
//
46+
// This will be replaced with a thunk by existential specialization.
47+
// CHECK-LABEL: sil hidden [signature_optimized_thunk] [always_inline] @$s1t7wrapFoo1a1bSi_SitAA12SomeProtocol_p_AaE_ptF : $@convention(thin) (@guaranteed SomeProtocol, @guaranteed SomeProtocol) -> (Int, Int) {
48+
sil hidden [noinline] @$s1t7wrapFoo1a1bSi_SitAA12SomeProtocol_p_AaE_ptF : $@convention(thin) (@guaranteed SomeProtocol, @guaranteed SomeProtocol) -> (Int, Int) {
49+
bb0(%0 : $SomeProtocol, %1 : $SomeProtocol):
50+
debug_value %0 : $SomeProtocol, let, name "a", argno 1
51+
debug_value %1 : $SomeProtocol, let, name "b", argno 2
52+
%4 = open_existential_ref %0 : $SomeProtocol to $@opened("15146E00-3B09-11EB-9A47-D0817AD9F6DD") SomeProtocol
53+
%5 = unchecked_ref_cast %4 : $@opened("15146E00-3B09-11EB-9A47-D0817AD9F6DD") SomeProtocol to $SomeClass
54+
// function_ref SomeClass.foo()
55+
%6 = function_ref @$s1t9SomeClassC3fooSiyF : $@convention(method) (@guaranteed SomeClass) -> Int
56+
%7 = apply %6(%5) : $@convention(method) (@guaranteed SomeClass) -> Int
57+
%8 = open_existential_ref %1 : $SomeProtocol to $@opened("15147238-3B09-11EB-9A47-D0817AD9F6DD") SomeProtocol
58+
%9 = unchecked_ref_cast %8 : $@opened("15147238-3B09-11EB-9A47-D0817AD9F6DD") SomeProtocol to $SomeClass
59+
%10 = apply %6(%9) : $@convention(method) (@guaranteed SomeClass) -> Int
60+
%11 = tuple (%7 : $Int, %10 : $Int)
61+
return %11 : $(Int, Int)
62+
}
63+
64+
// CHECK-LABEL: sil @callPrespecializedWrapFoo : $@convention(thin) (@guaranteed SomeClass) -> (Int, Int) {
65+
sil @callPrespecializedWrapFoo : $@convention(thin) (@guaranteed SomeClass) -> (Int, Int) {
66+
bb0(%0 : $SomeClass):
67+
debug_value %0 : $SomeClass, let, name "a", argno 1
68+
%2 = init_existential_ref %0 : $SomeClass : $SomeClass, $SomeProtocol
69+
%3 = init_existential_ref %0 : $SomeClass : $SomeClass, $SomeProtocol
70+
// function_ref wrapFoo(a:b:)
71+
%4 = function_ref @$s1t7wrapFoo1a1bSi_SitAA12SomeProtocol_p_AaE_ptF : $@convention(thin) (@guaranteed SomeProtocol, @guaranteed SomeProtocol) -> (Int, Int)
72+
%5 = apply %4(%2, %3) : $@convention(thin) (@guaranteed SomeProtocol, @guaranteed SomeProtocol) -> (Int, Int)
73+
%6 = tuple_extract %5 : $(Int, Int), 0
74+
%7 = tuple_extract %5 : $(Int, Int), 1
75+
%8 = tuple (%6 : $Int, %7 : $Int)
76+
return %8 : $(Int, Int)
77+
}
78+
79+
// wrapFoo after existential specialization AND thunk inlining.
80+
// After thunk inlining, the original thunk may be dead, but the specialized function may still be called.
81+
//
82+
// CHECK-LABEL: sil shared [noinline] @$s1t7wrapFoo1a1bSi_SitAA12SomeProtocol_p_AaE_ptFTf4ee_n : $@convention(thin) <τ_0_0, τ_0_1 where τ_0_0 : SomeProtocol, τ_0_1 : SomeProtocol> (@guaranteed τ_0_0, @guaranteed τ_0_1) -> (Int, Int) {
83+
sil shared [noinline] @$s1t7wrapFoo1a1bSi_SitAA12SomeProtocol_p_AaE_ptFTf4ee_n : $@convention(thin) <τ_0_0, τ_0_1 where τ_0_0 : SomeProtocol, τ_0_1 : SomeProtocol> (@guaranteed τ_0_0, @guaranteed τ_0_1) -> (Int, Int) {
84+
bb0(%0 : $τ_0_0, %1 : $τ_0_1):
85+
%2 = init_existential_ref %0 : $τ_0_0 : $τ_0_0, $SomeProtocol
86+
%3 = init_existential_ref %1 : $τ_0_1 : $τ_0_1, $SomeProtocol
87+
debug_value %2 : $SomeProtocol, let, name "a", argno 1
88+
debug_value %3 : $SomeProtocol, let, name "b", argno 2
89+
%6 = open_existential_ref %2 : $SomeProtocol to $@opened("47F4DDE6-3B09-11EB-9A47-D0817AD9F6DD") SomeProtocol
90+
%7 = unchecked_ref_cast %6 : $@opened("47F4DDE6-3B09-11EB-9A47-D0817AD9F6DD") SomeProtocol to $SomeClass
91+
// function_ref SomeClass.foo()
92+
%8 = function_ref @$s1t9SomeClassC3fooSiyF : $@convention(method) (@guaranteed SomeClass) -> Int
93+
%9 = apply %8(%7) : $@convention(method) (@guaranteed SomeClass) -> Int
94+
%10 = open_existential_ref %3 : $SomeProtocol to $@opened("47F4E1A6-3B09-11EB-9A47-D0817AD9F6DD") SomeProtocol
95+
%11 = unchecked_ref_cast %10 : $@opened("47F4E1A6-3B09-11EB-9A47-D0817AD9F6DD") SomeProtocol to $SomeClass
96+
%12 = apply %8(%11) : $@convention(method) (@guaranteed SomeClass) -> Int
97+
%13 = tuple (%9 : $Int, %12 : $Int)
98+
return %13 : $(Int, Int)
99+
}

0 commit comments

Comments
 (0)