Skip to content

Commit f24658f

Browse files
committed
Fix ExistentialSpecializer to detect repeated specialization.
Add handling for repeated specialization and remove an incorrect assertion in ExistentialTransform::createExistentialSpecializedFunctionName(): assert(!F->getModule().hasFunction(MangledName)); The ExistentialSpecializer replaces the original function with a thunk to a newly specialized function. Repeated attempts to specialize the same function bail out because the pass avoids reoptimizing thunks. Ultimately, the intention is that the thunks will all be inlined into their callers. Dead function elimination will then remove the thunks. If the original function was itself a specialization, then the GenericSpecialize may regenerate the original again in non-thunk form. Consider the pipeline: - GenericSpecializer - ExistentialSpecializer - Inliner - DeadFunctionElimination - GenericSpecializer - ExistentialSpecializer This is not a problem with the ExistentialSpecializer itself. In fact, it may respecialize the same function in different ways, for example specializing more of the arguments each time. Each different specialization transforms the original function into a thunk, that thunk is inlined, and the newly specialized code is called directly. Of course, the ExistentialSpecializer may also decide to respecialize a function the same way as before. When doing this, it still needs to produce the thunk, which was dead function eliminated since last specialization of the same function. However, it can simply reuse the previous specialization by performing a name lookup first. The design problem is that the SILModule makes assumptions about duplicate symbols when managing symbol memory but does not provide a robust way to protect against such duplicate symbols. That will be improved in a separate commit. Minimal fix for: rdar://72135512 The ExistentialSpecializer crashes
1 parent c65a206 commit f24658f

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)