Skip to content

Commit d222cf2

Browse files
committed
MandatoryPerformanceOptimizations: support default methods for class existentials
For example: ``` protocol P: AnyObject { func foo() } extension P { func foo() {} } class C: P {} let e: any P = C() ``` Such default methods are SILGen'd with a generic self argument. Therefore we need to specialize such witness methods, even if the conforming type is not generic. rdar://145855851
1 parent 7a8a50a commit d222cf2

File tree

6 files changed

+242
-66
lines changed

6 files changed

+242
-66
lines changed

SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift

+20-47
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import AST
1314
import SIL
1415

1516
/// Performs mandatory optimizations for performance-annotated functions, and global
@@ -39,11 +40,6 @@ let mandatoryPerformanceOptimizations = ModulePass(name: "mandatory-performance-
3940
}
4041

4142
optimizeFunctionsTopDown(using:&worklist, moduleContext)
42-
43-
if moduleContext.options.enableEmbeddedSwift {
44-
// Print errors for generic functions in vtables, which is not allowed in embedded Swift.
45-
checkVTablesForGenericFunctions(moduleContext)
46-
}
4743
}
4844

4945
privatefunc optimizeFunctionsTopDown(using worklist:inoutFunctionWorklist,
@@ -131,9 +127,27 @@ private func optimize(function: Function, _ context: FunctionPassContext, _ modu
131127

132128
caseletinitExRef as InitExistentialRefInst:
133129
if context.options.enableEmbeddedSwift {
134-
specializeWitnessTables(for: initExRef, moduleContext,&worklist)
130+
forcin initExRef.conformances where c.isConcrete {
131+
specializeWitnessTable(for: c, moduleContext){
132+
worklist.addWitnessMethods(of: $0)
133+
}
134+
}
135135
}
136136

137+
caseletbi as BuiltinInst:
138+
switch bi.id {
139+
case.BuildOrdinaryTaskExecutorRef,
140+
.BuildOrdinarySerialExecutorRef,
141+
.BuildComplexEqualitySerialExecutorRef:
142+
specializeWitnessTable(for: bi.substitutionMap.conformances[0], moduleContext){
143+
worklist.addWitnessMethods(of: $0)
144+
}
145+
146+
default:
147+
break
148+
}
149+
150+
137151
// We need to de-virtualize deinits of non-copyable types to be able to specialize the deinitializers.
138152
caseletdestroyValue as DestroyValueInst:
139153
if !devirtualizeDeinits(of: destroyValue, simplifyCtxt){
@@ -282,47 +296,6 @@ private func shouldInline(apply: FullApplySite, callee: Function, alreadyInlined
282296
returnfalse
283297
}
284298

285-
privatefunc specializeWitnessTables(for initExRef:InitExistentialRefInst, _ context:ModulePassContext,
286-
_ worklist:inoutFunctionWorklist)
287-
{
288-
forcin initExRef.conformances where c.isConcrete {
289-
letconformance= c.isInherited ? c.inheritedConformance : c
290-
letorigWitnessTable= context.lookupWitnessTable(for: conformance)
291-
if conformance.isSpecialized {
292-
if origWitnessTable ==nil{
293-
specializeWitnessTable(forConformance: conformance, errorLocation: initExRef.location, context){
294-
worklist.addWitnessMethods(of: $0)
295-
}
296-
}
297-
}elseiflet origWitnessTable {
298-
checkForGenericMethods(in: origWitnessTable, errorLocation: initExRef.location, context)
299-
}
300-
}
301-
}
302-
303-
privatefunc checkForGenericMethods(in witnessTable:WitnessTable,
304-
errorLocation:Location,
305-
_ context:ModulePassContext)
306-
{
307-
forentryin witnessTable.entries {
308-
if case .method(let requirement,let witness)= entry,
309-
let witness,
310-
witness.isGeneric
311-
{
312-
context.diagnosticEngine.diagnose(.cannot_specialize_witness_method, requirement, at: errorLocation)
313-
return
314-
}
315-
}
316-
}
317-
318-
privatefunc checkVTablesForGenericFunctions(_ context:ModulePassContext){
319-
forvTablein context.vTables where !vTable.class.isGenericAtAnyLevel {
320-
forentryin vTable.entries where entry.implementation.isGeneric {
321-
context.diagnosticEngine.diagnose(.non_final_generic_class_function, at: entry.methodDecl.location)
322-
}
323-
}
324-
}
325-
326299
privateextensionFullApplySite{
327300
func resultIsUsedInGlobalInitialization()->SmallProjectionPath?{
328301
guard parentFunction.isGlobalInitOnceFunction,

SwiftCompilerSources/Sources/Optimizer/PassManager/ModulePassContext.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,14 @@ struct ModulePassContext : Context, CustomStringConvertible {
144144
}
145145

146146
@discardableResult
147-
funccreateWitnessTable(entries:[WitnessTable.Entry],
147+
funccreateSpecializedWitnessTable(entries:[WitnessTable.Entry],
148148
conformance:Conformance,
149149
linkage:Linkage,
150150
serialized:Bool)->WitnessTable
151151
{
152152
letbridgedEntries= entries.map{ $0.bridged }
153153
letbridgedWitnessTable= bridgedEntries.withBridgedArrayRef{
154-
_bridged.createWitnessTable(linkage.bridged, serialized, conformance.bridged, $0)
154+
_bridged.createSpecializedWitnessTable(linkage.bridged, serialized, conformance.bridged, $0)
155155
}
156156
returnWitnessTable(bridged: bridgedWitnessTable)
157157
}

SwiftCompilerSources/Sources/Optimizer/Utilities/GenericSpecialization.swift

+117-15
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ private struct VTableSpecializer {
7979
}
8080

8181
privatefunc specializeEntries(of vTable:VTable, _ notifyNewFunction:(Function)->())->[VTable.Entry]{
82-
return vTable.entries.compactMap{ entry in
82+
return vTable.entries.map{ entry in
8383
if !entry.implementation.isGeneric {
8484
return entry
8585
}
@@ -91,8 +91,7 @@ private struct VTableSpecializer {
9191
context.loadFunction(function: entry.implementation, loadCalleesRecursively:true),
9292
let specializedMethod = context.specialize(function: entry.implementation, for: methodSubs)else
9393
{
94-
context.diagnosticEngine.diagnose(.non_final_generic_class_function, at: entry.methodDecl.location)
95-
returnnil
94+
return entry
9695
}
9796
notifyNewFunction(specializedMethod)
9897

@@ -106,16 +105,29 @@ private struct VTableSpecializer {
106105
}
107106
}
108107

109-
func specializeWitnessTable(forConformance conformance:Conformance,
110-
errorLocation:Location,
108+
/// Specializes a witness table of `conformance` for the concrete type of the conformance.
109+
func specializeWitnessTable(for conformance:Conformance,
111110
_ context:ModulePassContext,
112111
_ notifyNewWitnessTable:(WitnessTable)->())
113112
{
114-
letgenericConformance= conformance.genericConformance
115-
guardlet witnessTable = context.lookupWitnessTable(for: genericConformance)else{
113+
iflet existingSpecialization = context.lookupWitnessTable(for: conformance),
114+
existingSpecialization.isSpecialized
115+
{
116+
return
117+
}
118+
119+
letbaseConf= conformance.isInherited ? conformance.inheritedConformance: conformance
120+
if !baseConf.isSpecialized {
121+
varvisited=Set<Conformance>()
122+
specializeDefaultMethods(for: conformance, visited:&visited, context, notifyNewWitnessTable)
123+
return
124+
}
125+
126+
guardlet witnessTable = context.lookupWitnessTable(for: baseConf.genericConformance)else{
116127
fatalError("no witness table found")
117128
}
118129
assert(witnessTable.isDefinition,"No witness table available")
130+
letsubstitutions= baseConf.specializedSubstitutions
119131

120132
letnewEntries= witnessTable.entries.map{ origEntry in
121133
switch origEntry {
@@ -125,21 +137,22 @@ func specializeWitnessTable(forConformance conformance: Conformance,
125137
guardlet origMethod = witness else{
126138
return origEntry
127139
}
128-
letmethodSubs= conformance.specializedSubstitutions.getMethodSubstitutions(for: origMethod)
140+
letmethodSubs= substitutions.getMethodSubstitutions(for: origMethod,
141+
// Generic self types need to be handled specially (see `getMethodSubstitutions`)
142+
selfType: origMethod.hasGenericSelf(context)? conformance.type.canonical :nil)
129143

130144
guard !methodSubs.conformances.contains(where:{!$0.isValid}),
131145
context.loadFunction(function: origMethod, loadCalleesRecursively:true),
132146
let specializedMethod = context.specialize(function: origMethod, for: methodSubs)else
133147
{
134-
context.diagnosticEngine.diagnose(.cannot_specialize_witness_method, requirement, at: errorLocation)
135148
return origEntry
136149
}
137150
return.method(requirement: requirement, witness: specializedMethod)
138151
case.baseProtocol(let requirement,let witness):
139152
letbaseConf= context.getSpecializedConformance(of: witness,
140153
for: conformance.type,
141154
substitutions: conformance.specializedSubstitutions)
142-
specializeWitnessTable(forConformance: baseConf, errorLocation: errorLocation, context, notifyNewWitnessTable)
155+
specializeWitnessTable(for: baseConf, context, notifyNewWitnessTable)
143156
return.baseProtocol(requirement: requirement, witness: baseConf)
144157
case.associatedType(let requirement,let witness):
145158
letsubstType= witness.subst(with: conformance.specializedSubstitutions)
@@ -150,15 +163,104 @@ func specializeWitnessTable(forConformance conformance: Conformance,
150163
letconcreteAssociateConf= conformance.getAssociatedConformance(ofAssociatedType: requirement.rawType,
151164
to: assocConf.protocol)
152165
if concreteAssociateConf.isSpecialized {
153-
specializeWitnessTable(forConformance: concreteAssociateConf,
154-
errorLocation: errorLocation,
155-
context, notifyNewWitnessTable)
166+
specializeWitnessTable(for: concreteAssociateConf, context, notifyNewWitnessTable)
156167
}
157168
return.associatedConformance(requirement: requirement,
158169
witness: concreteAssociateConf)
159170
}
160171
}
161-
letnewWT= context.createWitnessTable(entries: newEntries,conformance: conformance,
162-
linkage:.shared, serialized:false)
172+
letnewWT= context.createSpecializedWitnessTable(entries: newEntries,conformance: conformance,
173+
linkage:.shared, serialized:false)
163174
notifyNewWitnessTable(newWT)
164175
}
176+
177+
/// Specializes the default methods of a non-generic witness table.
178+
/// Default implementations (in protocol extentions) of non-generic protocol methods have a generic
179+
/// self argument. Specialize such methods with the concrete type. Note that it is important to also
180+
/// specialize inherited conformances so that the concrete self type is correct, even for derived classes.
181+
privatefunc specializeDefaultMethods(for conformance:Conformance,
182+
visited:inoutSet<Conformance>,
183+
_ context:ModulePassContext,
184+
_ notifyNewWitnessTable:(WitnessTable)->())
185+
{
186+
// Avoid infinite recursion, which may happen if an associated conformance is the conformance itself.
187+
guard visited.insert(conformance).inserted,
188+
let witnessTable = context.lookupWitnessTable(for: conformance.rootConformance)
189+
else{
190+
return
191+
}
192+
193+
assert(witnessTable.isDefinition,"No witness table available")
194+
195+
varspecialized=false
196+
197+
letnewEntries= witnessTable.entries.map{ origEntry in
198+
switch origEntry {
199+
case.invalid:
200+
returnWitnessTable.Entry.invalid
201+
case.method(let requirement,let witness):
202+
guardlet origMethod = witness,
203+
// Is it a generic method where only self is generic (= a default witness method)?
204+
origMethod.isGeneric, origMethod.isNonGenericWitnessMethod(context)
205+
else{
206+
return origEntry
207+
}
208+
// Replace the generic self type with the concrete type.
209+
letmethodSubs=SubstitutionMap(genericSignature: origMethod.genericSignature,
210+
replacementTypes:[conformance.type])
211+
212+
guard !methodSubs.conformances.contains(where:{!$0.isValid}),
213+
context.loadFunction(function: origMethod, loadCalleesRecursively:true),
214+
let specializedMethod = context.specialize(function: origMethod, for: methodSubs)else
215+
{
216+
return origEntry
217+
}
218+
specialized =true
219+
return.method(requirement: requirement, witness: specializedMethod)
220+
case.baseProtocol(_,let witness):
221+
specializeDefaultMethods(for: witness, visited:&visited, context, notifyNewWitnessTable)
222+
return origEntry
223+
case.associatedType:
224+
return origEntry
225+
case.associatedConformance(_,let assocConf):
226+
specializeDefaultMethods(for: assocConf, visited:&visited, context, notifyNewWitnessTable)
227+
return origEntry
228+
}
229+
}
230+
// If the witness table does not contain any default methods, there is no need to create a
231+
// specialized witness table.
232+
if specialized {
233+
letnewWT= context.createSpecializedWitnessTable(entries: newEntries,conformance: conformance,
234+
linkage:.shared, serialized:false)
235+
notifyNewWitnessTable(newWT)
236+
}
237+
}
238+
239+
privateextensionFunction{
240+
// True, if this is a non-generic method which might have a generic self argument.
241+
// Default implementations (in protocol extentions) of non-generic protocol methods have a generic
242+
// self argument.
243+
func isNonGenericWitnessMethod(_ context:someContext)->Bool{
244+
switch loweredFunctionType.invocationGenericSignatureOfFunction.genericParameters.count {
245+
case0:
246+
returntrue
247+
case1:
248+
returnhasGenericSelf(context)
249+
default:
250+
returnfalse
251+
}
252+
}
253+
254+
// True, if the self argument is a generic parameter.
255+
func hasGenericSelf(_ context:someContext)->Bool{
256+
letconvention=FunctionConvention(for: loweredFunctionType,
257+
hasLoweredAddresses: context.moduleHasLoweredAddresses)
258+
if convention.hasSelfParameter,
259+
let selfParam = convention.parameters.last,
260+
selfParam.type.isGenericTypeParameter
261+
{
262+
returntrue
263+
}
264+
returnfalse
265+
}
266+
}

include/swift/SILOptimizer/OptimizerBridging.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ struct BridgedPassContext {
328328
BridgedSubstitutionMap substitutions) const;
329329
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE
330330
OptionalBridgedWitnessTable lookupWitnessTable(BridgedConformance conformance) const;
331-
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedWitnessTable createWitnessTable(BridgedLinkage linkage,
331+
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedWitnessTable createSpecializedWitnessTable(BridgedLinkage linkage,
332332
bool serialized,
333333
BridgedConformance conformance,
334334
BridgedArrayRef bridgedEntries) const;

include/swift/SILOptimizer/OptimizerBridgingImpl.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ OptionalBridgedWitnessTable BridgedPassContext::lookupWitnessTable(BridgedConfor
445445
return {mod->lookUpWitnessTable(ref.getConcrete())};
446446
}
447447

448-
BridgedWitnessTable BridgedPassContext::createWitnessTable(BridgedLinkage linkage,
448+
BridgedWitnessTable BridgedPassContext::createSpecializedWitnessTable(BridgedLinkage linkage,
449449
bool serialized,
450450
BridgedConformance conformance,
451451
BridgedArrayRef bridgedEntries) const {

0 commit comments

Comments
 (0)
close