Skip to content

Commit 4bacb7f

Browse files
authored
Merge pull request #66376 from eeckstein/simplify-retain-release-value
Optimizer: implement the SILCombine peephole optimizations for retain_value and release_value in Swift
2 parents 23e2f34 + 50c23a1 commit 4bacb7f

File tree

18 files changed

+653
-162
lines changed

18 files changed

+653
-162
lines changed

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ swift_compiler_sources(Optimizer
1919
SimplifyInitEnumDataAddr.swift
2020
SimplifyLoad.swift
2121
SimplifyPartialApply.swift
22+
SimplifyRetainReleaseValue.swift
2223
SimplifyStrongRetainRelease.swift
2324
SimplifyStructExtract.swift
2425
SimplifyTupleExtract.swift
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
//===--- SimplifyRetainReleaseValue.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+
extension RetainValueInst : Simplifyable, SILCombineSimplifyable {
16+
func simplify(_ context: SimplifyContext) {
17+
18+
// Remove pairs of
19+
// ```
20+
// release_value %0 // the release is before the retain!
21+
// retain_value %0
22+
// ```
23+
// which sometimes the ARC optimizations cannot do.
24+
//
25+
if optimizeReleaseRetainPair(context) {
26+
return
27+
}
28+
29+
// Replace
30+
// ```
31+
// %1 = enum #E.A, %0
32+
// retain_value %1
33+
// ```
34+
// with
35+
// ```
36+
// %1 = enum #E.A, %0 // maybe dead
37+
// retain_value %0
38+
// ```
39+
replaceOperandWithPayloadOfEnum(context)
40+
41+
// Remove if the operand is trivial (but not necessarily its type), e.g.
42+
// ```
43+
// %1 = value_to_bridge_object %0 : $UInt64
44+
// retain_value %1 : $Builtin.BridgeObject
45+
// ```
46+
if removeIfOperandIsTrivial(context) {
47+
return
48+
}
49+
50+
// Replace e.g.
51+
// ```
52+
// retain_value %1 : $SomeClass
53+
// ```
54+
// with
55+
// ```
56+
// strong_retain %1 : $SomeClass
57+
// ```
58+
replaceWithStrongOrUnownedRetain(context)
59+
}
60+
}
61+
62+
extension ReleaseValueInst : Simplifyable, SILCombineSimplifyable {
63+
func simplify(_ context: SimplifyContext) {
64+
65+
// Replace
66+
// ```
67+
// %1 = enum #E.A, %0
68+
// release_value %1
69+
// ```
70+
// with
71+
// ```
72+
// %1 = enum #E.A, %0 // maybe dead
73+
// release_value %0
74+
// ```
75+
replaceOperandWithPayloadOfEnum(context)
76+
77+
// Remove if the operand is trivial (but not necessarily its type), e.g.
78+
// ```
79+
// %1 = value_to_bridge_object %0 : $UInt64
80+
// release_value %1 : $Builtin.BridgeObject
81+
// ```
82+
if removeIfOperandIsTrivial(context) {
83+
return
84+
}
85+
86+
// Replace e.g.
87+
// ```
88+
// release_value %1 : $SomeClass
89+
// ```
90+
// with
91+
// ```
92+
// release_value %1 : $SomeClass
93+
// ```
94+
replaceWithStrongOrUnownedRelease(context)
95+
}
96+
}
97+
98+
private extension RetainValueInst {
99+
func optimizeReleaseRetainPair(_ context: SimplifyContext) -> Bool {
100+
if let prevInst = self.previous,
101+
let release = prevInst as? ReleaseValueInst,
102+
release.value == self.value {
103+
context.erase(instruction: release)
104+
context.erase(instruction: self)
105+
return true
106+
}
107+
return false
108+
}
109+
110+
func replaceWithStrongOrUnownedRetain(_ context: SimplifyContext) {
111+
if value.type.isReferenceCounted(in: parentFunction) {
112+
let builder = Builder(before: self, context)
113+
if value.type.isUnownedStorageType {
114+
builder.createUnownedRetain(operand: value)
115+
} else {
116+
builder.createStrongRetain(operand: value)
117+
}
118+
context.erase(instruction: self)
119+
}
120+
}
121+
}
122+
123+
private extension ReleaseValueInst {
124+
func replaceWithStrongOrUnownedRelease(_ context: SimplifyContext) {
125+
if value.type.isReferenceCounted(in: parentFunction) {
126+
let builder = Builder(before: self, context)
127+
if value.type.isUnownedStorageType {
128+
builder.createUnownedRelease(operand: value)
129+
} else {
130+
builder.createStrongRelease(operand: value)
131+
}
132+
context.erase(instruction: self)
133+
}
134+
}
135+
}
136+
137+
private extension UnaryInstruction {
138+
func replaceOperandWithPayloadOfEnum(_ context: SimplifyContext) {
139+
if let e = operand.value as? EnumInst,
140+
!e.type.isValueTypeWithDeinit,
141+
let payload = e.payload {
142+
operand.set(to: payload, context)
143+
}
144+
}
145+
146+
func removeIfOperandIsTrivial(_ context: SimplifyContext) -> Bool {
147+
if operand.value.isTrivial(context) {
148+
context.erase(instruction: self)
149+
return true
150+
}
151+
return false
152+
}
153+
}

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ private func registerSwiftPasses() {
8787
registerForSILCombine(GlobalValueInst.self, { run(GlobalValueInst.self, $0) })
8888
registerForSILCombine(StrongRetainInst.self, { run(StrongRetainInst.self, $0) })
8989
registerForSILCombine(StrongReleaseInst.self, { run(StrongReleaseInst.self, $0) })
90+
registerForSILCombine(RetainValueInst.self, { run(RetainValueInst.self, $0) })
91+
registerForSILCombine(ReleaseValueInst.self, { run(ReleaseValueInst.self, $0) })
9092
registerForSILCombine(LoadInst.self, { run(LoadInst.self, $0) })
9193

9294
// Test passes

SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,43 @@ extension Value {
1616
var nonDebugUses: LazyFilterSequence<UseList> {
1717
uses.lazy.filter { !($0.instruction is DebugValueInst) }
1818
}
19+
20+
/// Walks over all fields of an aggregate and checks if a reference count
21+
/// operation for this value is required. This differs from a simple `Type.isTrivial`
22+
/// check, because it treats a value_to_bridge_object instruction as "trivial".
23+
/// It can also handle non-trivial enums with trivial cases.
24+
func isTrivial(_ context: some Context) -> Bool {
25+
if self is Undef {
26+
return true
27+
}
28+
var worklist = ValueWorklist(context)
29+
defer { worklist.deinitialize() }
30+
31+
worklist.pushIfNotVisited(self)
32+
while let v = worklist.pop() {
33+
if v.type.isTrivial(in: parentFunction) {
34+
continue
35+
}
36+
if v.type.isValueTypeWithDeinit {
37+
return false
38+
}
39+
switch v {
40+
case is ValueToBridgeObjectInst:
41+
break
42+
case is StructInst, is TupleInst:
43+
let inst = (v as! SingleValueInstruction)
44+
worklist.pushIfNotVisited(contentsOf: inst.operands.values.filter { !($0 is Undef) })
45+
case let en as EnumInst:
46+
if let payload = en.payload,
47+
!(payload is Undef) {
48+
worklist.pushIfNotVisited(payload)
49+
}
50+
default:
51+
return false
52+
}
53+
}
54+
return true
55+
}
1956
}
2057

2158
extension Builder {

SwiftCompilerSources/Sources/SIL/ApplySite.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ extension ApplySite {
8080
///
8181
/// This does not include the callee function operand.
8282
public var arguments: LazyMapSequence<OperandArray, Value> {
83-
argumentOperands.lazy.map { $0.value }
83+
argumentOperands.values
8484
}
8585

8686
public var substitutionMap: SubstitutionMap {

SwiftCompilerSources/Sources/SIL/Builder.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,18 @@ public struct Builder {
136136
return notifyNew(release.getAs(StrongReleaseInst.self))
137137
}
138138

139+
@discardableResult
140+
public func createUnownedRetain(operand: Value) -> UnownedRetainInst {
141+
let retain = bridged.createUnownedRetain(operand.bridged)
142+
return notifyNew(retain.getAs(UnownedRetainInst.self))
143+
}
144+
145+
@discardableResult
146+
public func createUnownedRelease(operand: Value) -> UnownedReleaseInst {
147+
let release = bridged.createUnownedRelease(operand.bridged)
148+
return notifyNew(release.getAs(UnownedReleaseInst.self))
149+
}
150+
139151
public func createFunctionRef(_ function: Function) -> FunctionRefInst {
140152
let functionRef = bridged.createFunctionRef(function.bridged)
141153
return notifyNew(functionRef.getAs(FunctionRefInst.self))

SwiftCompilerSources/Sources/SIL/Instruction.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ public class MultipleValueInstruction : Instruction {
197197
}
198198

199199
/// Instructions, which have a single operand.
200-
public protocol UnaryInstruction : AnyObject {
200+
public protocol UnaryInstruction : Instruction {
201201
var operands: OperandArray { get }
202202
var operand: Operand { get }
203203
}
@@ -306,14 +306,24 @@ final public class StrongRetainInst : RefCountingInst {
306306
public var instance: Value { operand.value }
307307
}
308308

309+
final public class UnownedRetainInst : RefCountingInst {
310+
public var instance: Value { operand.value }
311+
}
312+
309313
final public class RetainValueInst : RefCountingInst {
314+
public var value: Value { return operand.value }
310315
}
311316

312317
final public class StrongReleaseInst : RefCountingInst {
313318
public var instance: Value { operand.value }
314319
}
315320

321+
final public class UnownedReleaseInst : RefCountingInst {
322+
public var instance: Value { operand.value }
323+
}
324+
316325
final public class ReleaseValueInst : RefCountingInst {
326+
public var value: Value { return operand.value }
317327
}
318328

319329
final public class DestroyValueInst : Instruction, UnaryInstruction {

SwiftCompilerSources/Sources/SIL/Operand.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ public struct OperandArray : RandomAccessCollection, CustomReflectable {
7676
base: OptionalBridgedOperand(op: base.advancedBy(bounds.lowerBound).op),
7777
count: bounds.upperBound - bounds.lowerBound)
7878
}
79+
80+
public var values: LazyMapSequence<LazySequence<OperandArray>.Elements, Value> {
81+
self.lazy.map { $0.value }
82+
}
7983
}
8084

8185
public struct UseList : CollectionLikeSequence {

SwiftCompilerSources/Sources/SIL/Registration.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ public func registerSILClasses() {
5454
register(EndApplyInst.self)
5555
register(AbortApplyInst.self)
5656
register(StrongRetainInst.self)
57+
register(UnownedRetainInst.self)
5758
register(RetainValueInst.self)
5859
register(StrongReleaseInst.self)
60+
register(UnownedReleaseInst.self)
5961
register(ReleaseValueInst.self)
6062
register(DestroyValueInst.self)
6163
register(DestroyAddrInst.self)

SwiftCompilerSources/Sources/SIL/Type.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ public struct Type : CustomStringConvertible, NoReflectionChildren {
3131
return !bridged.isNonTrivialOrContainsRawPointer(function.bridged.getFunction())
3232
}
3333

34+
/// True if this type is a value type (struct/enum) that requires deinitialization beyond
35+
/// destruction of its members.
36+
public var isValueTypeWithDeinit: Bool { bridged.isValueTypeWithDeinit() }
37+
3438
public func isLoadable(in function: Function) -> Bool {
3539
return bridged.isLoadable(function.bridged.getFunction())
3640
}
@@ -39,6 +43,10 @@ public struct Type : CustomStringConvertible, NoReflectionChildren {
3943
return bridged.isReferenceCounted(function.bridged.getFunction())
4044
}
4145

46+
public var isUnownedStorageType: Bool {
47+
return bridged.isUnownedStorageType()
48+
}
49+
4250
public var hasArchetype: Bool { bridged.hasArchetype() }
4351

4452
public var isNominal: Bool { bridged.getNominalOrBoundGenericNominal() != nil }

include/swift/SIL/SILBridging.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,18 @@ struct BridgedBuilder{
11051105
return {b.createStrongRelease(regularLoc(), op.getSILValue(), b.getDefaultAtomicity())};
11061106
}
11071107

1108+
SWIFT_IMPORT_UNSAFE
1109+
BridgedInstruction createUnownedRetain(BridgedValue op) const {
1110+
auto b = builder();
1111+
return {b.createUnownedRetain(regularLoc(), op.getSILValue(), b.getDefaultAtomicity())};
1112+
}
1113+
1114+
SWIFT_IMPORT_UNSAFE
1115+
BridgedInstruction createUnownedRelease(BridgedValue op) const {
1116+
auto b = builder();
1117+
return {b.createUnownedRelease(regularLoc(), op.getSILValue(), b.getDefaultAtomicity())};
1118+
}
1119+
11081120
SWIFT_IMPORT_UNSAFE
11091121
BridgedInstruction createFunctionRef(BridgedFunction function) const {
11101122
return {builder().createFunctionRef(regularLoc(), function.getFunction())};

include/swift/SIL/SILType.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,8 @@ class SILType {
394394

395395
bool isReferenceCounted(SILFunction *f) const;
396396

397+
bool isUnownedStorageType() const { return is<UnownedStorageType>(); }
398+
397399
/// Returns true if the referenced type is a function type that never
398400
/// returns.
399401
bool isNoReturnFunction(SILModule &M, TypeExpansionContext context) const;
@@ -745,6 +747,10 @@ class SILType {
745747
/// for a move only wrapped type.
746748
bool isPureMoveOnly() const;
747749

750+
/// Return true if this is a value type (struct/enum) that requires
751+
/// deinitialization beyond destruction of its members.
752+
bool isValueTypeWithDeinit() const;
753+
748754
/// Returns true if and only if this type is a first class move only
749755
/// type. NOTE: Returns false if the type is a move only wrapped type.
750756
bool isMoveOnlyNominalType() const;

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,8 @@ SWIFT_SILCOMBINE_PASS(BeginCOWMutationInst)
482482
SWIFT_SILCOMBINE_PASS(GlobalValueInst)
483483
SWIFT_SILCOMBINE_PASS(StrongRetainInst)
484484
SWIFT_SILCOMBINE_PASS(StrongReleaseInst)
485+
SWIFT_SILCOMBINE_PASS(RetainValueInst)
486+
SWIFT_SILCOMBINE_PASS(ReleaseValueInst)
485487
SWIFT_SILCOMBINE_PASS(LoadInst)
486488

487489
#undef IRGEN_PASS

lib/SIL/IR/SILType.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,15 @@ bool SILType::isPureMoveOnly() const {
10481048
return false;
10491049
}
10501050

1051+
bool SILType::isValueTypeWithDeinit() const {
1052+
// Do not look inside an aggregate type that has a user-deinit, for which
1053+
// memberwise-destruction is not equivalent to aggregate destruction.
1054+
if (auto *nominal = getNominalOrBoundGenericNominal()) {
1055+
return nominal->getValueTypeDestructor() != nullptr;
1056+
}
1057+
return false;
1058+
}
1059+
10511060
SILType SILType::getInstanceTypeOfMetatype(SILFunction *function) const {
10521061
auto metaType = castTo<MetatypeType>();
10531062
CanType instanceTy = metaType.getInstanceType();

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,6 @@ class SILCombiner :
234234
SILInstruction *visitSILInstruction(SILInstruction *I) { return nullptr; }
235235

236236
/// Instruction visitors.
237-
SILInstruction *visitReleaseValueInst(ReleaseValueInst *DI);
238-
SILInstruction *visitRetainValueInst(RetainValueInst *CI);
239237
SILInstruction *visitPartialApplyInst(PartialApplyInst *AI);
240238
SILInstruction *visitApplyInst(ApplyInst *AI);
241239
SILInstruction *visitBeginApplyInst(BeginApplyInst *BAI);

0 commit comments

Comments
 (0)