Skip to content

Commit 8f80374

Browse files
committed
libswift: implement ReleaseDevirtualizer in Swift
1 parent 0b7b4bf commit 8f80374

File tree

20 files changed

+243
-62
lines changed

20 files changed

+243
-62
lines changed

SwiftCompilerSources/Sources/AST/CMakeLists.txt

Lines changed: 0 additions & 11 deletions
This file was deleted.

SwiftCompilerSources/Sources/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,5 @@
99
if(SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING)
1010
add_subdirectory(ExperimentalRegex)
1111
endif()
12-
add_subdirectory(AST)
1312
add_subdirectory(SIL)
1413
add_subdirectory(Optimizer)

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: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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+
devirtualizeReleaseOfObject(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.mayReleaseOrReadRefCount {
55+
lastRelease = nil
56+
}
57+
}
58+
}
59+
}
60+
)
61+
62+
/// Devirtualize releases of array buffers.
63+
private func devirtualizeReleaseOfObject(
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+
guard root === allocRefInstruction else {
75+
return
76+
}
77+
78+
createDeallocCall(context, allocRefInstruction.type, release, allocRefInstruction)
79+
}
80+
81+
/// Replaces the release-instruction `release` with an explicit call to
82+
/// the deallocating destructor of `type` for `object`.
83+
private func createDeallocCall(
84+
_ context: PassContext,
85+
_ type: Type,
86+
_ release: RefCountingInst,
87+
_ object: Value
88+
) {
89+
guard let dealloc = context.getDestructor(ofClass: type) else {
90+
return
91+
}
92+
93+
let substitutionMap = context.getContextSubstitutionMap(for: type)
94+
95+
let builder = Builder(at: release, location: release.location, context)
96+
97+
var object = object
98+
if object.type != type {
99+
object = builder.createUncheckedRefCast(object: object, type: type)
100+
}
101+
102+
// Do what a release would do before calling the deallocator: set the object
103+
// in deallocating state, which means set the RC_DEALLOCATING_FLAG flag.
104+
builder.createSetDeallocating(operand: object, isAtomic: release.isAtomic)
105+
106+
// Create the call to the destructor with the allocated object as self
107+
// argument.
108+
let functionRef = builder.createFunctionRef(dealloc)
109+
110+
builder.createApply(function: functionRef, substitutionMap, arguments: [object])
111+
context.erase(instruction: release)
112+
}
113+
114+
private func stripRCIdentityPreservingInsts(_ value: Value) -> Value? {
115+
switch value {
116+
// First strip off RC identity preserving casts.
117+
case let inst as Instruction where inst is UpcastInst ||
118+
inst is UncheckedRefCastInst ||
119+
inst is InitExistentialRefInst ||
120+
inst is OpenExistentialRefInst ||
121+
inst is RefToBridgeObjectInst ||
122+
inst is BridgeObjectToRefInst ||
123+
inst is ConvertFunctionInst ||
124+
inst is UncheckedEnumDataInst:
125+
return inst.operands[0].value
126+
127+
// Then if we have a struct_extract that is extracting a non-trivial member
128+
// from a struct with no other non-trivial members, a ref count operation on
129+
// the struct is equivalent to a ref count operation on the extracted
130+
// member. Strip off the extract.
131+
case let sei as StructExtractInst where sei.isFieldOnlyNonTrivialField:
132+
return sei.operands[0].value
133+
134+
// If we have a struct instruction with only one non-trivial stored field, the
135+
// only reference count that can be modified is the non-trivial field. Return
136+
// the non-trivial field.
137+
case let si as StructInst:
138+
return si.uniqueNonTrivialFieldValue
139+
140+
// If we have an enum instruction with a payload, strip off the enum to
141+
// expose the enum's payload.
142+
case let ei as EnumInst where !ei.operands.isEmpty:
143+
return ei.operands[0].value
144+
145+
// If we have a tuple_extract that is extracting the only non trivial member
146+
// of a tuple, a retain_value on the tuple is equivalent to a retain_value on
147+
// the extracted value.
148+
case let tei as TupleExtractInst where tei.isEltOnlyNonTrivialElt:
149+
return tei.operands[0].value
150+
151+
// If we are forming a tuple and the tuple only has one element with reference
152+
// semantics, a retain_value on the tuple is equivalent to a retain value on
153+
// the tuple operand.
154+
case let ti as TupleInst:
155+
return ti.uniqueNonTrivialElt
156+
157+
default:
158+
return nil
159+
}
160+
}

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: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import AST
1413
import SIL
1514
import OptimizerBridging
1615

@@ -117,8 +116,8 @@ struct PassContext {
117116
PassContext_fixStackNesting(_bridged, function.bridged)
118117
}
119118

120-
func getDeallocRef(for type: Type) -> Function? {
121-
PassContext_getDeallocRef(_bridged, type.bridged).function
119+
func getDestructor(ofClass type: Type) -> Function? {
120+
PassContext_getDestructor(_bridged, type.bridged).function
122121
}
123122

124123
func getContextSubstitutionMap(for type: Type) -> SubstitutionMap {

SwiftCompilerSources/Sources/SIL/Builder.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import AST
1413
import SILBridging
1514

1615
/// A utility to create new instructions at a given insertion point.
@@ -102,14 +101,16 @@ public struct Builder {
102101
function: Value,
103102
_ substitutionMap: SubstitutionMap,
104103
arguments: [Value]
105-
) -> FunctionRefInst {
104+
) -> ApplyInst {
106105
notifyInstructionsChanged()
107-
let functionRef = arguments.withBridgedValues { valuesRef in
106+
notifyCallsChanged()
107+
108+
let apply = arguments.withBridgedValues { valuesRef in
108109
SILBuilder_createApply(
109110
bridgedInsPoint, location.bridgedLocation, function.bridged,
110111
substitutionMap.bridged, valuesRef
111112
)
112113
}
113-
return functionRef.getAs(FunctionRefInst.self)
114+
return apply.getAs(ApplyInst.self)
114115
}
115116
}

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/Instruction.swift

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ public class Instruction : ListNode, CustomStringConvertible, Hashable {
9898
}
9999
}
100100

101+
final public var mayReleaseOrReadRefCount: Bool {
102+
return SILInstruction_mayReleaseOrReadRefCount(bridged)
103+
}
104+
101105
public static func ==(lhs: Instruction, rhs: Instruction) -> Bool {
102106
lhs === rhs
103107
}
@@ -237,7 +241,9 @@ final public class SetDeallocatingInst : Instruction, UnaryInstruction {}
237241

238242
final public class DeallocRefInst : Instruction, UnaryInstruction {}
239243

240-
public class RefCountingInst : Instruction, UnaryInstruction {}
244+
public class RefCountingInst : Instruction, UnaryInstruction {
245+
public var isAtomic: Bool { RefCountingInst_getIsAtomic(bridged) }
246+
}
241247

242248
final public class StrongRetainInst : RefCountingInst {
243249
}
@@ -356,10 +362,14 @@ final public class GlobalValueInst : GlobalAccessInst {}
356362
final public class IntegerLiteralInst : SingleValueInstruction {}
357363

358364
final public class TupleInst : SingleValueInstruction {
365+
public var uniqueNonTrivialElt: Value? {
366+
TupleInst_getUniqueNonTrivialElt(bridged).value
367+
}
359368
}
360369

361370
final public class TupleExtractInst : SingleValueInstruction, UnaryInstruction {
362371
public var fieldIndex: Int { TupleExtractInst_fieldIndex(bridged) }
372+
public var isEltOnlyNonTrivialElt: Bool { TupleExtractInst_isEltOnlyNonTrivialElt(bridged) }
363373
}
364374

365375
final public
@@ -368,10 +378,16 @@ class TupleElementAddrInst : SingleValueInstruction, UnaryInstruction {
368378
}
369379

370380
final public class StructInst : SingleValueInstruction {
381+
public var uniqueNonTrivialFieldValue: Value? {
382+
StructInst_getUniqueNonTrivialFieldValue(bridged).value
383+
}
371384
}
372385

373386
final public class StructExtractInst : SingleValueInstruction, UnaryInstruction {
374387
public var fieldIndex: Int { StructExtractInst_fieldIndex(bridged) }
388+
public var isFieldOnlyNonTrivialField: Bool {
389+
StructExtractInst_isFieldOnlyNonTrivialField(bridged)
390+
}
375391
}
376392

377393
final public
@@ -515,6 +531,9 @@ final public class BeginApplyInst : MultipleValueInstruction, FullApplySite {
515531
public var singleDirectResult: Value? { nil }
516532
}
517533

534+
final public class RefToBridgeObjectInst: MultipleValueInstruction {
535+
}
536+
518537
//===----------------------------------------------------------------------===//
519538
// terminator instructions
520539
//===----------------------------------------------------------------------===//

SwiftCompilerSources/Sources/SIL/Type.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,9 @@ public struct Type {
2222
return SILType_isTrivial(bridged, function.bridged) != 0
2323
}
2424
}
25+
26+
extension Type: Equatable {
27+
public static func == (lhs: Type, rhs: Type) -> Bool {
28+
lhs.bridged.typePtr == rhs.bridged.typePtr
29+
}
30+
}

SwiftCompilerSources/Sources/SIL/Value.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,7 @@ final class Undef : Value {
5858
final class PlaceholderValue : Value {
5959
public var definingInstruction: Instruction? { nil }
6060
}
61+
62+
extension OptionalBridgedValue {
63+
var value: Value? { obj.getAs(AnyObject.self) as? Value }
64+
}

include/swift/SIL/SILBridging.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ typedef struct {
118118
SwiftObject obj;
119119
} BridgedValue;
120120

121+
typedef struct {
122+
OptionalSwiftObject obj;
123+
} OptionalBridgedValue;
124+
121125
typedef struct {
122126
SwiftObject obj;
123127
} BridgedInstruction;
@@ -211,6 +215,7 @@ void SILInstruction_setOperand(BridgedInstruction inst, SwiftInt index,
211215
BridgedValue value);
212216
BridgedLocation SILInstruction_getLocation(BridgedInstruction inst);
213217
BridgedMemoryBehavior SILInstruction_getMemBehavior(BridgedInstruction inst);
218+
bool SILInstruction_mayReleaseOrReadRefCount(BridgedInstruction inst);
214219

215220
BridgedInstruction MultiValueInstResult_getParent(BridgedMultiValueResult result);
216221
SwiftInt MultipleValueInstruction_getNumResults(BridgedInstruction inst);
@@ -224,8 +229,12 @@ BridgedBuiltinID BuiltinInst_getID(BridgedInstruction bi);
224229
BridgedGlobalVar GlobalAccessInst_getGlobal(BridgedInstruction globalInst);
225230
BridgedFunction FunctionRefInst_getReferencedFunction(BridgedInstruction fri);
226231
SwiftInt TupleExtractInst_fieldIndex(BridgedInstruction tei);
232+
bool TupleExtractInst_isEltOnlyNonTrivialElt(BridgedInstruction tei);
227233
SwiftInt TupleElementAddrInst_fieldIndex(BridgedInstruction teai);
234+
OptionalBridgedValue TupleInst_getUniqueNonTrivialElt(BridgedInstruction ti);
228235
SwiftInt StructExtractInst_fieldIndex(BridgedInstruction sei);
236+
bool StructExtractInst_isFieldOnlyNonTrivialField(BridgedInstruction sei);
237+
OptionalBridgedValue StructInst_getUniqueNonTrivialFieldValue(BridgedInstruction si);
229238
SwiftInt StructElementAddrInst_fieldIndex(BridgedInstruction seai);
230239
SwiftInt EnumInst_caseIndex(BridgedInstruction ei);
231240
SwiftInt UncheckedEnumDataInst_caseIndex(BridgedInstruction uedi);
@@ -241,6 +250,7 @@ SwiftInt SwitchEnumInst_getNumCases(BridgedInstruction se);
241250
SwiftInt SwitchEnumInst_getCaseIndex(BridgedInstruction se, SwiftInt idx);
242251
SwiftInt StoreInst_getStoreOwnership(BridgedInstruction store);
243252
void RefCountingInst_setIsAtomic(BridgedInstruction rc, bool isAtomic);
253+
bool RefCountingInst_getIsAtomic(BridgedInstruction rc);
244254

245255
BridgedInstruction SILBuilder_createBuiltinBinaryFunction(
246256
BridgedInstruction insertionPoint,

include/swift/SILOptimizer/OptimizerBridging.h

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -141,17 +141,8 @@ void BasicBlockSet_erase(BridgedBasicBlockSet set, BridgedBasicBlock block);
141141
BridgedFunction BasicBlockSet_getFunction(BridgedBasicBlockSet set);
142142

143143
void AllocRefInstBase_setIsStackAllocatable(BridgedInstruction arb);
144-
BridgedRCIdentityAnalysis
145-
PassContext_getRCIdentityAnalysis(BridgedPassContext context);
146144

147-
BridgedRCIdentityFunctionInfo
148-
RCIdentityAnalysis_getFunctionInfo(BridgedRCIdentityAnalysis bridgedAnalysis,
149-
BridgedFunction function);
150-
151-
BridgedValue RCIdentityFunctionInfo_getRCIdentityRoot(
152-
BridgedRCIdentityFunctionInfo bridgedInfo, BridgedValue value);
153-
154-
OptionalBridgedFunction PassContext_getDeallocRef(BridgedPassContext context,
145+
OptionalBridgedFunction PassContext_getDestructor(BridgedPassContext context,
155146
BridgedType type);
156147

157148
BridgedSubstitutionMap

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ PASS(RedundantPhiElimination, "redundant-phi-elimination",
336336
"Redundant Phi Block Argument Elimination")
337337
PASS(PhiExpansion, "phi-expansion",
338338
"Replace Phi arguments by their only used field")
339-
PASS(ReleaseDevirtualizer, "release-devirtualizer",
339+
SWIFT_FUNCTION_PASS_WITH_LEGACY(ReleaseDevirtualizer, "release-devirtualizer",
340340
"SIL release Devirtualization")
341341
PASS(RetainSinking, "retain-sinking",
342342
"SIL retain Sinking")

0 commit comments

Comments
 (0)