Skip to content

Commit 048624f

Browse files
committed
Optimizer: de-virtualize deinits of non-copyable types
In regular swift this is a nice optimization. In embedded swift it's a requirement, because the compiler needs to be able to specialize generic deinits of non-copyable types. The new de-virtualization utilities are called from two places: * from the new DeinitDevirtualizer pass. It replaces the old MoveOnlyDeinitDevirtualization, which is very basic and does not fulfill the needs for embedded swift. * from MandatoryPerformanceOptimizations for embedded swift
1 parent 79a4162 commit 048624f

File tree

16 files changed

+802
-71
lines changed

16 files changed

+802
-71
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ swift_compiler_sources(Optimizer
1313
ComputeEscapeEffects.swift
1414
ComputeSideEffects.swift
1515
DeadStoreElimination.swift
16+
DeinitDevirtualizer.swift
1617
InitializeStaticGlobals.swift
1718
LetPropertyLowering.swift
1819
ObjectOutliner.swift
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//===--- DeinitDevirtualizer.swift ----------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SIL
14+
15+
/// Devirtualizes destroys of non-copyable values.
16+
///
17+
let deinitDevirtualizer = FunctionPass(name: "deinit-devirtualizer") {
18+
(function: Function, context: FunctionPassContext) in
19+
20+
for inst in function.instructions {
21+
switch inst {
22+
case let destroyValue as DestroyValueInst:
23+
_ = devirtualizeDeinits(of: destroyValue, context)
24+
case let destroyAddr as DestroyAddrInst:
25+
_ = devirtualizeDeinits(of: destroyAddr, context)
26+
default:
27+
break
28+
}
29+
}
30+
}

SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ private func optimize(function: Function, _ context: FunctionPassContext, _ work
9090
_ = context.specializeClassMethodInst(classMethod)
9191
}
9292

93+
// We need to de-virtualize deinits of non-copyable types to be able to specialize the deinitializers.
94+
case let destroyValue as DestroyValueInst:
95+
if !devirtualizeDeinits(of: destroyValue, simplifyCtxt) {
96+
context.diagnosticEngine.diagnose(destroyValue.location.sourceLoc, .deinit_not_visible)
97+
}
98+
case let destroyAddr as DestroyAddrInst:
99+
if !devirtualizeDeinits(of: destroyAddr, simplifyCtxt) {
100+
context.diagnosticEngine.diagnose(destroyAddr.location.sourceLoc, .deinit_not_visible)
101+
}
102+
93103
default:
94104
break
95105
}

SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ extension Context {
4242
default: fatalError("unhandled SILStage case")
4343
}
4444
}
45+
46+
func lookupDeinit(ofNominal: NominalTypeDecl) -> Function? {
47+
_bridged.lookUpNominalDeinitFunction(ofNominal.bridged).function
48+
}
4549
}
4650

4751
/// A context which allows mutation of a function's SIL.

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ private func registerSwiftPasses() {
8686
registerPass(deadStoreElimination, { deadStoreElimination.run($0) })
8787
registerPass(redundantLoadElimination, { redundantLoadElimination.run($0) })
8888
registerPass(earlyRedundantLoadElimination, { earlyRedundantLoadElimination.run($0) })
89+
registerPass(deinitDevirtualizer, { deinitDevirtualizer.run($0) })
8990

9091
// Instruction passes
9192
registerForSILCombine(BeginCOWMutationInst.self, { run(BeginCOWMutationInst.self, $0) })

SwiftCompilerSources/Sources/Optimizer/Utilities/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
swift_compiler_sources(Optimizer
1010
DiagnosticEngine.swift
11+
Devirtualization.swift
1112
EscapeUtils.swift
1213
OptUtils.swift
1314
ForwardingUtils.swift
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
//===--- Devirtualization.swift -------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SIL
14+
15+
/// Devirtualizes all value-type deinitializers of a `destroy_value`.
16+
///
17+
/// This may be a no-op if the destroy doesn't call any deinitializers.
18+
/// Returns true if all deinitializers could be devirtualized.
19+
func devirtualizeDeinits(of destroy: DestroyValueInst, _ context: some MutatingContext) -> Bool {
20+
return devirtualize(destroy: destroy, context)
21+
}
22+
23+
/// Devirtualizes all value-type deinitializers of a `destroy_addr`.
24+
///
25+
/// This may be a no-op if the destroy doesn't call any deinitializers.
26+
/// Returns true if all deinitializers could be devirtualized.
27+
func devirtualizeDeinits(of destroy: DestroyAddrInst, _ context: some MutatingContext) -> Bool {
28+
return devirtualize(destroy: destroy, context)
29+
}
30+
31+
private func devirtualize(destroy: some DevirtualizableDestroy, _ context: some MutatingContext) -> Bool {
32+
let type = destroy.type
33+
guard type.isMoveOnly && type.selfOrAnyFieldHasValueDeinit(in: destroy.parentFunction) else {
34+
return true
35+
}
36+
precondition(type.isNominal, "non-copyable non-nominal types not supported, yet")
37+
38+
let result: Bool
39+
if type.nominal.hasValueDeinit && !destroy.hasDropDeinit {
40+
guard let deinitFunc = context.lookupDeinit(ofNominal: type.nominal) else {
41+
return false
42+
}
43+
destroy.createDeinitCall(to: deinitFunc, context)
44+
result = true
45+
} else {
46+
// If there is no deinit to be called for the original type we have to recursively visit
47+
// the struct fields or enum cases.
48+
if type.isStruct {
49+
result = destroy.devirtualizeStructFields(context)
50+
} else {
51+
precondition(type.isEnum, "unknown nominal value type")
52+
result = destroy.devirtualizeEnumPayloads(context)
53+
}
54+
}
55+
context.erase(instruction: destroy)
56+
return result
57+
}
58+
59+
// Used to dispatch devirtualization tasks to `destroy_value` and `destroy_addr`.
60+
private protocol DevirtualizableDestroy : UnaryInstruction {
61+
func createDeinitCall(to deinitializer: Function, _ context: some MutatingContext)
62+
func devirtualizeStructFields(_ context: some MutatingContext) -> Bool
63+
func devirtualizeEnumPayload(enumCase: EnumCase, in block: BasicBlock, _ context: some MutatingContext) -> Bool
64+
func createSwitchEnum(atEndOf block: BasicBlock, cases: [(Int, BasicBlock)], _ context: some MutatingContext)
65+
}
66+
67+
private extension DevirtualizableDestroy {
68+
var type: Type { operand.value.type }
69+
70+
var hasDropDeinit: Bool { operand.value.lookThoughOwnershipInstructions is DropDeinitInst }
71+
72+
func devirtualizeEnumPayloads(_ context: some MutatingContext) -> Bool {
73+
guard let cases = type.getEnumCases(in: parentFunction) else {
74+
return false
75+
}
76+
if cases.allPayloadsAreTrivial(in: parentFunction) {
77+
let builder = Builder(before: self, context)
78+
builder.createEndLifetime(of: operand.value)
79+
return true
80+
}
81+
82+
var caseBlocks: [(caseIndex: Int, targetBlock: BasicBlock)] = []
83+
let switchBlock = parentBlock
84+
let endBlock = context.splitBlock(before: self)
85+
var result = true
86+
87+
for enumCase in cases {
88+
let caseBlock = context.createBlock(after: switchBlock)
89+
caseBlocks.append((enumCase.index, caseBlock))
90+
let builder = Builder(atEndOf: caseBlock, location: location, context)
91+
builder.createBranch(to: endBlock)
92+
if !devirtualizeEnumPayload(enumCase: enumCase, in: caseBlock, context) {
93+
result = false
94+
}
95+
}
96+
createSwitchEnum(atEndOf: switchBlock, cases: caseBlocks, context)
97+
return result
98+
}
99+
}
100+
101+
extension DestroyValueInst : DevirtualizableDestroy {
102+
fileprivate func createDeinitCall(to deinitializer: Function, _ context: some MutatingContext) {
103+
let builder = Builder(before: self, context)
104+
let subs = context.getContextSubstitutionMap(for: type)
105+
let deinitRef = builder.createFunctionRef(deinitializer)
106+
if deinitializer.getArgumentConvention(for: deinitializer.selfArgumentIndex).isIndirect {
107+
let allocStack = builder.createAllocStack(type)
108+
builder.createStore(source: destroyedValue, destination: allocStack, ownership: .initialize)
109+
builder.createApply(function: deinitRef, subs, arguments: [allocStack])
110+
builder.createDeallocStack(allocStack)
111+
} else {
112+
builder.createApply(function: deinitRef, subs, arguments: [destroyedValue])
113+
}
114+
}
115+
116+
fileprivate func devirtualizeStructFields(_ context: some MutatingContext) -> Bool {
117+
let builder = Builder(before: self, context)
118+
119+
guard let fields = type.getNominalFields(in: parentFunction) else {
120+
return false
121+
}
122+
if fields.allFieldsAreTrivial(in: parentFunction) {
123+
builder.createEndLifetime(of: operand.value)
124+
return true
125+
}
126+
let destructure = builder.createDestructureStruct(struct: destroyedValue)
127+
var result = true
128+
129+
for fieldValue in destructure.results where !fieldValue.type.isTrivial(in: parentFunction) {
130+
let destroyField = builder.createDestroyValue(operand: fieldValue)
131+
if !devirtualizeDeinits(of: destroyField, context) {
132+
result = false
133+
}
134+
}
135+
return result
136+
}
137+
138+
fileprivate func devirtualizeEnumPayload(
139+
enumCase: EnumCase,
140+
in block: BasicBlock,
141+
_ context: some MutatingContext
142+
) -> Bool {
143+
let builder = Builder(atBeginOf: block, location: location, context)
144+
if let payloadTy = enumCase.payload {
145+
let payload = block.addArgument(type: payloadTy, ownership: .owned, context)
146+
if !payloadTy.isTrivial(in: parentFunction) {
147+
let destroyPayload = builder.createDestroyValue(operand: payload)
148+
return devirtualizeDeinits(of: destroyPayload, context)
149+
}
150+
}
151+
return true
152+
}
153+
154+
fileprivate func createSwitchEnum(
155+
atEndOf block: BasicBlock,
156+
cases: [(Int, BasicBlock)],
157+
_ context: some MutatingContext
158+
) {
159+
let builder = Builder(atEndOf: block, location: location, context)
160+
builder.createSwitchEnum(enum: destroyedValue, cases: cases)
161+
}
162+
}
163+
164+
extension DestroyAddrInst : DevirtualizableDestroy {
165+
fileprivate func createDeinitCall(to deinitializer: Function, _ context: some MutatingContext) {
166+
let builder = Builder(before: self, context)
167+
let subs = context.getContextSubstitutionMap(for: destroyedAddress.type)
168+
let deinitRef = builder.createFunctionRef(deinitializer)
169+
if !deinitializer.getArgumentConvention(for: deinitializer.selfArgumentIndex).isIndirect {
170+
let value = builder.createLoad(fromAddress: destroyedAddress, ownership: .take)
171+
builder.createApply(function: deinitRef, subs, arguments: [value])
172+
} else {
173+
builder.createApply(function: deinitRef, subs, arguments: [destroyedAddress])
174+
}
175+
}
176+
177+
fileprivate func devirtualizeStructFields(_ context: some MutatingContext) -> Bool {
178+
let builder = Builder(before: self, context)
179+
180+
guard let fields = type.getNominalFields(in: parentFunction) else {
181+
return false
182+
}
183+
if fields.allFieldsAreTrivial(in: parentFunction) {
184+
builder.createEndLifetime(of: operand.value)
185+
return true
186+
}
187+
var result = true
188+
for (fieldIdx, fieldTy) in fields.enumerated()
189+
where !fieldTy.isTrivial(in: parentFunction)
190+
{
191+
let fieldAddr = builder.createStructElementAddr(structAddress: destroyedAddress, fieldIndex: fieldIdx)
192+
let destroyField = builder.createDestroyAddr(address: fieldAddr)
193+
if !devirtualizeDeinits(of: destroyField, context) {
194+
result = false
195+
}
196+
}
197+
return result
198+
}
199+
200+
fileprivate func devirtualizeEnumPayload(
201+
enumCase: EnumCase,
202+
in block: BasicBlock,
203+
_ context: some MutatingContext
204+
) -> Bool {
205+
let builder = Builder(atBeginOf: block, location: location, context)
206+
if let payloadTy = enumCase.payload,
207+
!payloadTy.isTrivial(in: parentFunction)
208+
{
209+
let caseAddr = builder.createUncheckedTakeEnumDataAddr(enumAddress: destroyedAddress, caseIndex: enumCase.index)
210+
let destroyPayload = builder.createDestroyAddr(address: caseAddr)
211+
return devirtualizeDeinits(of: destroyPayload, context)
212+
}
213+
return true
214+
}
215+
216+
fileprivate func createSwitchEnum(
217+
atEndOf block: BasicBlock,
218+
cases: [(Int, BasicBlock)],
219+
_ context: some MutatingContext
220+
) {
221+
let builder = Builder(atEndOf: block, location: location, context)
222+
builder.createSwitchEnumAddr(enumAddress: destroyedAddress, cases: cases)
223+
}
224+
}
225+
226+
private extension EnumCases {
227+
func allPayloadsAreTrivial(in function: Function) -> Bool {
228+
allSatisfy({ $0.payload?.isTrivial(in: function) ?? true })
229+
}
230+
}
231+
232+
private extension NominalFieldsArray {
233+
func allFieldsAreTrivial(in function: Function) -> Bool {
234+
allSatisfy({ $0.isTrivial(in: function)})
235+
}
236+
}

include/swift/AST/DiagnosticsSIL.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,5 +888,9 @@ WARNING(call_site_transfer_yields_race, none,
888888
NOTE(possible_racy_access_site, none,
889889
"access here could race", ())
890890

891+
// TODO: print the name of the nominal type
892+
ERROR(deinit_not_visible, none,
893+
"deinit of non-copyable type not visible in the current module", ())
894+
891895
#define UNDEFINE_DIAGNOSTIC_MACROS
892896
#include "DefineDiagnosticMacros.h"

include/swift/SILOptimizer/OptimizerBridging.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ struct BridgedPassContext {
268268
bool loadCalleesRecursively) const;
269269
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE void loadFunction(BridgedFunction function, bool loadCalleesRecursively) const;
270270
SWIFT_IMPORT_UNSAFE OptionalBridgedFunction lookupStdlibFunction(BridgedStringRef name) const;
271+
SWIFT_IMPORT_UNSAFE OptionalBridgedFunction lookUpNominalDeinitFunction(BridgedNominalTypeDecl nominal) const;
271272
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedSubstitutionMap getContextSubstitutionMap(BridgedType type) const;
272273

273274
// Passmanager housekeeping

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,8 @@ PASS(RedundantPhiElimination, "redundant-phi-elimination",
341341
"Redundant Phi Block Argument Elimination")
342342
PASS(PhiExpansion, "phi-expansion",
343343
"Replace Phi arguments by their only used field")
344+
SWIFT_FUNCTION_PASS(DeinitDevirtualizer, "deinit-devirtualizer",
345+
"Devirtualizes destroys of non-copyable values")
344346
SWIFT_FUNCTION_PASS(ReleaseDevirtualizer, "release-devirtualizer",
345347
"SIL release Devirtualization")
346348
PASS(RetainSinking, "retain-sinking",

lib/SILOptimizer/PassManager/PassManager.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,6 +1638,11 @@ OptionalBridgedFunction BridgedPassContext::lookupStdlibFunction(BridgedStringRe
16381638
return {funcBuilder.getOrCreateFunction(SILLocation(decl), declRef, NotForDefinition)};
16391639
}
16401640

1641+
OptionalBridgedFunction BridgedPassContext::lookUpNominalDeinitFunction(BridgedNominalTypeDecl nominal) const {
1642+
swift::SILModule *mod = invocation->getPassManager()->getModule();
1643+
return {mod->lookUpMoveOnlyDeinitFunction(nominal.unbridged())};
1644+
}
1645+
16411646
bool BridgedPassContext::enableSimplificationFor(BridgedInstruction inst) const {
16421647
// Fast-path check.
16431648
if (SimplifyInstructionTest.empty() && SILDisablePass.empty())

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ llvm::cl::opt<bool>
7171
EnableDestroyHoisting("enable-destroy-hoisting", llvm::cl::init(false),
7272
llvm::cl::desc("Enable the DestroyHoisting pass."));
7373

74+
llvm::cl::opt<bool>
75+
EnableDeinitDevirtualizer("enable-deinit-devirtualizer", llvm::cl::init(false),
76+
llvm::cl::desc("Enable the DestroyHoisting pass."));
77+
78+
7479
//===----------------------------------------------------------------------===//
7580
// Diagnostic Pass Pipeline
7681
//===----------------------------------------------------------------------===//
@@ -162,6 +167,10 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
162167

163168
// Check noImplicitCopy and move only types for objects and addresses.
164169
P.addMoveOnlyChecker();
170+
171+
if (EnableDeinitDevirtualizer)
172+
P.addDeinitDevirtualizer();
173+
165174
// Lower move only wrapped trivial types.
166175
P.addTrivialMoveOnlyTypeEliminator();
167176
// Check no uses after consume operator of a value in an address.

0 commit comments

Comments
 (0)