Skip to content

Commit 6d4f2d9

Browse files
authored
Merge pull request #40800 from apple/maxd/release-devirtualizer
libswift: implement `ReleaseDevirtualizer` in Swift
2 parents ce813f7 + 1f53563 commit 6d4f2d9

File tree

21 files changed

+688
-38
lines changed

21 files changed

+688
-38
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ swift_compiler_sources(Optimizer
1010
AssumeSingleThreaded.swift
1111
SILPrinter.swift
1212
MergeCondFails.swift
13+
ReleaseDevirtualizer.swift
1314
)
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
//===--- ReleaseDevirtualizer.swift - Devirtualizes release-instructions --===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 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 release instructions which are known to destruct the object.
16+
///
17+
/// This means, it replaces a sequence of
18+
/// %x = alloc_ref [stack] $X
19+
/// ...
20+
/// strong_release %x
21+
/// dealloc_stack_ref %x
22+
/// with
23+
/// %x = alloc_ref [stack] $X
24+
/// ...
25+
/// set_deallocating %x
26+
/// %d = function_ref @dealloc_of_X
27+
/// %a = apply %d(%x)
28+
/// dealloc_stack_ref %x
29+
///
30+
/// The optimization is only done for stack promoted objects because they are
31+
/// known to have no associated objects (which are not explicitly released
32+
/// in the deinit method).
33+
let releaseDevirtualizerPass = FunctionPass(
34+
name: "release-devirtualizer", { function, context in
35+
for block in function.blocks {
36+
// The last `release_value`` or `strong_release`` instruction before the
37+
// deallocation.
38+
var lastRelease: RefCountingInst?
39+
40+
for instruction in block.instructions {
41+
if let release = lastRelease {
42+
// We only do the optimization for stack promoted object, because for
43+
// these we know that they don't have associated objects, which are
44+
// _not_ released by the deinit method.
45+
if let deallocStackRef = instruction as? DeallocStackRefInst {
46+
tryDevirtualizeReleaseOfObject(context, release, deallocStackRef)
47+
lastRelease = nil
48+
continue
49+
}
50+
}
51+
52+
if instruction is ReleaseValueInst || instruction is StrongReleaseInst {
53+
lastRelease = instruction as? RefCountingInst
54+
} else if instruction.mayRelease {
55+
lastRelease = nil
56+
}
57+
}
58+
}
59+
}
60+
)
61+
62+
/// Tries to de-virtualize the final release of a stack-promoted object.
63+
private func tryDevirtualizeReleaseOfObject(
64+
_ context: PassContext,
65+
_ release: RefCountingInst,
66+
_ deallocStackRef: DeallocStackRefInst
67+
) {
68+
let allocRefInstruction = deallocStackRef.allocRef
69+
var root = release.operands[0].value
70+
while let newRoot = stripRCIdentityPreservingInsts(root) {
71+
root = newRoot
72+
}
73+
74+
if root != allocRefInstruction {
75+
return
76+
}
77+
78+
let type = allocRefInstruction.type
79+
80+
guard let dealloc = context.getDestructor(ofClass: type) else {
81+
return
82+
}
83+
84+
let builder = Builder(at: release, location: release.location, context)
85+
86+
var object: Value = allocRefInstruction
87+
if object.type != type {
88+
object = builder.createUncheckedRefCast(object: object, type: type)
89+
}
90+
91+
// Do what a release would do before calling the deallocator: set the object
92+
// in deallocating state, which means set the RC_DEALLOCATING_FLAG flag.
93+
builder.createSetDeallocating(operand: object, isAtomic: release.isAtomic)
94+
95+
// Create the call to the destructor with the allocated object as self
96+
// argument.
97+
let functionRef = builder.createFunctionRef(dealloc)
98+
99+
let substitutionMap = context.getContextSubstitutionMap(for: type)
100+
builder.createApply(function: functionRef, substitutionMap, arguments: [object])
101+
context.erase(instruction: release)
102+
}
103+
104+
private func stripRCIdentityPreservingInsts(_ value: Value) -> Value? {
105+
guard let inst = value as? Instruction else { return nil }
106+
107+
switch inst {
108+
// First strip off RC identity preserving casts.
109+
case is UpcastInst,
110+
is UncheckedRefCastInst,
111+
is InitExistentialRefInst,
112+
is OpenExistentialRefInst,
113+
is RefToBridgeObjectInst,
114+
is BridgeObjectToRefInst,
115+
is ConvertFunctionInst,
116+
is UncheckedEnumDataInst:
117+
return inst.operands[0].value
118+
119+
// Then if we have a struct_extract that is extracting a non-trivial member
120+
// from a struct with no other non-trivial members, a ref count operation on
121+
// the struct is equivalent to a ref count operation on the extracted
122+
// member. Strip off the extract.
123+
case let sei as StructExtractInst where sei.isFieldOnlyNonTrivialField:
124+
return sei.operand
125+
126+
// If we have a struct or tuple instruction with only one non-trivial operand, the
127+
// only reference count that can be modified is the non-trivial operand. Return
128+
// the non-trivial operand.
129+
case is StructInst, is TupleInst:
130+
return inst.uniqueNonTrivialOperand
131+
132+
// If we have an enum instruction with a payload, strip off the enum to
133+
// expose the enum's payload.
134+
case let ei as EnumInst where !ei.operands.isEmpty:
135+
return ei.operand
136+
137+
// If we have a tuple_extract that is extracting the only non trivial member
138+
// of a tuple, a retain_value on the tuple is equivalent to a retain_value on
139+
// the extracted value.
140+
case let tei as TupleExtractInst where tei.isEltOnlyNonTrivialElt:
141+
return tei.operand
142+
143+
default:
144+
return nil
145+
}
146+
}
147+
148+
private extension Instruction {
149+
/// Search the operands of this tuple for a unique non-trivial elt. If we find
150+
/// it, return it. Otherwise return `nil`.
151+
var uniqueNonTrivialOperand: Value? {
152+
var candidateElt: Value?
153+
let function = self.function
154+
155+
for op in operands {
156+
if !op.value.type.isTrivial(in: function) {
157+
if candidateElt == nil {
158+
candidateElt = op.value
159+
continue
160+
}
161+
162+
// Otherwise, we have two values that are non-trivial. Bail.
163+
return nil
164+
}
165+
}
166+
167+
return candidateElt
168+
}
169+
}
170+
171+
private extension TupleExtractInst {
172+
var isEltOnlyNonTrivialElt: Bool {
173+
let function = self.function
174+
175+
if type.isTrivial(in: function) {
176+
return false
177+
}
178+
179+
let opType = operand.type
180+
181+
var nonTrivialEltsCount = 0
182+
for elt in opType.tupleElements {
183+
if elt.isTrivial(in: function) {
184+
nonTrivialEltsCount += 1
185+
}
186+
187+
if nonTrivialEltsCount > 1 {
188+
return false
189+
}
190+
}
191+
192+
return true
193+
}
194+
}
195+
196+
private extension StructExtractInst {
197+
var isFieldOnlyNonTrivialField: Bool {
198+
let function = self.function
199+
200+
if type.isTrivial(in: function) {
201+
return false
202+
}
203+
204+
let structType = operand.type
205+
206+
var nonTrivialFieldsCount = 0
207+
for field in structType.getStructFields(in: function) {
208+
if field.isTrivial(in: function) {
209+
nonTrivialFieldsCount += 1
210+
}
211+
212+
if nonTrivialFieldsCount > 1 {
213+
return false
214+
}
215+
}
216+
217+
return true
218+
}
219+
}

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,5 @@ private func registerSwiftPasses() {
5151
registerPass(simplifyStrongRetainPass, { simplifyStrongRetainPass.run($0) })
5252
registerPass(simplifyStrongReleasePass, { simplifyStrongReleasePass.run($0) })
5353
registerPass(assumeSingleThreadedPass, { assumeSingleThreadedPass.run($0) })
54+
registerPass(releaseDevirtualizerPass, { releaseDevirtualizerPass.run($0) })
5455
}

SwiftCompilerSources/Sources/Optimizer/PassManager/PassUtils.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ struct PassContext {
115115
func fixStackNesting(function: Function) {
116116
PassContext_fixStackNesting(_bridged, function.bridged)
117117
}
118+
119+
func getDestructor(ofClass type: Type) -> Function? {
120+
PassContext_getDestructor(_bridged, type.bridged).function
121+
}
122+
123+
func getContextSubstitutionMap(for type: Type) -> SubstitutionMap {
124+
SubstitutionMap(PassContext_getContextSubstitutionMap(_bridged, type.bridged))
125+
}
118126
}
119127

120128
struct FunctionPass {

SwiftCompilerSources/Sources/SIL/Builder.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,44 @@ public struct Builder {
7373
let dr = SILBuilder_createDeallocStackRef(bridgedInsPoint, location.bridgedLocation, operand.bridged)
7474
return dr.getAs(DeallocStackRefInst.self)
7575
}
76+
77+
public func createUncheckedRefCast(object: Value, type: Type) -> UncheckedRefCastInst {
78+
notifyInstructionsChanged()
79+
let object = SILBuilder_createUncheckedRefCast(
80+
bridgedInsPoint, location.bridgedLocation, object.bridged, type.bridged)
81+
return object.getAs(UncheckedRefCastInst.self)
82+
}
83+
84+
@discardableResult
85+
public func createSetDeallocating(operand: Value, isAtomic: Bool) -> SetDeallocatingInst {
86+
notifyInstructionsChanged()
87+
let setDeallocating = SILBuilder_createSetDeallocating(
88+
bridgedInsPoint, location.bridgedLocation, operand.bridged, isAtomic)
89+
return setDeallocating.getAs(SetDeallocatingInst.self)
90+
}
91+
92+
public func createFunctionRef(_ function: Function) -> FunctionRefInst {
93+
notifyInstructionsChanged()
94+
let functionRef = SILBuilder_createFunctionRef(
95+
bridgedInsPoint, location.bridgedLocation, function.bridged)
96+
return functionRef.getAs(FunctionRefInst.self)
97+
}
98+
99+
@discardableResult
100+
public func createApply(
101+
function: Value,
102+
_ substitutionMap: SubstitutionMap,
103+
arguments: [Value]
104+
) -> ApplyInst {
105+
notifyInstructionsChanged()
106+
notifyCallsChanged()
107+
108+
let apply = arguments.withBridgedValues { valuesRef in
109+
SILBuilder_createApply(
110+
bridgedInsPoint, location.bridgedLocation, function.bridged,
111+
substitutionMap.bridged, valuesRef
112+
)
113+
}
114+
return apply.getAs(ApplyInst.self)
115+
}
76116
}

SwiftCompilerSources/Sources/SIL/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ add_swift_compiler_module(SIL
1717
Location.swift
1818
Operand.swift
1919
Registration.swift
20+
SubstitutionMap.swift
2021
Type.swift
2122
Utils.swift
2223
Value.swift)

SwiftCompilerSources/Sources/SIL/Function.swift

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,47 @@ final public class Function : CustomStringConvertible, HasName {
3333
public var arguments: LazyMapSequence<ArgumentArray, FunctionArgument> {
3434
entryBlock.arguments.lazy.map { $0 as! FunctionArgument }
3535
}
36-
36+
3737
public var numIndirectResultArguments: Int {
3838
SILFunction_numIndirectResultArguments(bridged)
3939
}
40-
40+
4141
public var hasSelfArgument: Bool {
4242
SILFunction_getSelfArgumentIndex(bridged) >= 0
4343
}
44-
44+
4545
public var selfArgumentIndex: Int {
4646
let selfIdx = SILFunction_getSelfArgumentIndex(bridged)
4747
assert(selfIdx >= 0)
4848
return selfIdx
4949
}
50+
51+
public var argumentTypes: ArgumentTypeArray { ArgumentTypeArray(function: self) }
52+
public var resultType: Type { SILFunction_getSILResultType(bridged).type }
5053

5154
public var bridged: BridgedFunction { BridgedFunction(obj: SwiftObject(self)) }
5255
}
5356

5457
public func == (lhs: Function, rhs: Function) -> Bool { lhs === rhs }
5558
public func != (lhs: Function, rhs: Function) -> Bool { lhs !== rhs }
5659

60+
public struct ArgumentTypeArray : RandomAccessCollection, FormattedLikeArray {
61+
fileprivate let function: Function
62+
63+
public var startIndex: Int { return 0 }
64+
public var endIndex: Int { SILFunction_getNumSILArguments(function.bridged) }
65+
66+
public subscript(_ index: Int) -> Type {
67+
SILFunction_getSILArgumentType(function.bridged, index).type
68+
}
69+
}
70+
5771
// Bridging utilities
5872

5973
extension BridgedFunction {
6074
public var function: Function { obj.getAs(Function.self) }
6175
}
76+
77+
extension OptionalBridgedFunction {
78+
public var function: Function? { obj.getAs(Function.self) }
79+
}

0 commit comments

Comments
 (0)