Skip to content

Commit 50c23a1

Browse files
committed
Optimizer: implement the SILCombine peephole optimizations for retain_value and release_value in Swift
1 parent 5325a4f commit 50c23a1

File tree

9 files changed

+588
-160
lines changed

9 files changed

+588
-160
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 {

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/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);

lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp

Lines changed: 0 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -811,164 +811,6 @@ SILInstruction *SILCombiner::visitIndexAddrInst(IndexAddrInst *IA) {
811811
IA->needsStackProtection() || cast<IndexAddrInst>(base)->needsStackProtection());
812812
}
813813

814-
/// Walks over all fields of an aggregate and checks if a reference count
815-
/// operation for \p value is required. This differs from a simple `isTrivial`
816-
/// check, because it treats a value_to_bridge_object instruction as "trivial".
817-
/// It can also handle non-trivial enums with trivial cases.
818-
///
819-
/// TODO: Define this as a top-level SILValue API. It needs to be updated
820-
/// whenever we refine the specification of isTrivial. For example, in the
821-
/// future we will check for the existence of a user-defined deinitializer and
822-
/// handle C++ types specially.
823-
static bool isValueTrivial(SILValue value, SILFunction *function) {
824-
SmallVector<ValueBase *, 32> workList;
825-
SmallPtrSet<ValueBase *, 16> visited;
826-
workList.push_back(value);
827-
while (!workList.empty()) {
828-
SILValue v = workList.pop_back_val();
829-
if (v->getType().isTrivial(*function))
830-
continue;
831-
if (isa<ValueToBridgeObjectInst>(v))
832-
continue;
833-
834-
// MoveOnly types may have a user-defined deinit.
835-
if (hasValueDeinit(v))
836-
return false;
837-
838-
if (isa<StructInst>(v) || isa<TupleInst>(v)) {
839-
for (SILValue op : cast<SingleValueInstruction>(v)->getOperandValues()) {
840-
if (visited.insert(op).second)
841-
workList.push_back(op);
842-
}
843-
continue;
844-
}
845-
if (auto *en = dyn_cast<EnumInst>(v)) {
846-
if (en->hasOperand() && visited.insert(en->getOperand()).second)
847-
workList.push_back(en->getOperand());
848-
continue;
849-
}
850-
return false;
851-
}
852-
return true;
853-
}
854-
855-
SILInstruction *SILCombiner::visitReleaseValueInst(ReleaseValueInst *RVI) {
856-
assert(!RVI->getFunction()->hasOwnership());
857-
858-
SILValue Operand = RVI->getOperand();
859-
SILType OperandTy = Operand->getType();
860-
861-
// Do not remove a release that calls a value deinit.
862-
if (hasValueDeinit(OperandTy))
863-
return nullptr;
864-
865-
// Destroy value of an enum with a trivial payload or no-payload is a no-op.
866-
if (auto *EI = dyn_cast<EnumInst>(Operand)) {
867-
if (!EI->hasOperand() ||
868-
EI->getOperand()->getType().isTrivial(*EI->getFunction()))
869-
return eraseInstFromFunction(*RVI);
870-
871-
// retain_value of an enum_inst where we know that it has a payload can be
872-
// reduced to a retain_value on the payload.
873-
if (EI->hasOperand()) {
874-
return Builder.createReleaseValue(RVI->getLoc(), EI->getOperand(),
875-
RVI->getAtomicity());
876-
}
877-
}
878-
879-
// ReleaseValueInst of a loadable reference storage type needs the
880-
// corresponding release instruction.
881-
#define ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
882-
if (OperandTy.is<Name##StorageType>()) \
883-
return Builder.create##Name##Release(RVI->getLoc(), Operand, \
884-
RVI->getAtomicity());
885-
#include "swift/AST/ReferenceStorage.def"
886-
887-
// ReleaseValueInst of a reference type is a strong_release.
888-
if (OperandTy.isReferenceCounted(RVI->getModule()))
889-
return Builder.createStrongRelease(RVI->getLoc(), Operand,
890-
RVI->getAtomicity());
891-
892-
// ReleaseValueInst of a trivial type is a no-op.
893-
if (isValueTrivial(Operand, RVI->getFunction()))
894-
return eraseInstFromFunction(*RVI);
895-
896-
// Do nothing for non-trivial non-reference types.
897-
return nullptr;
898-
}
899-
900-
SILInstruction *SILCombiner::visitRetainValueInst(RetainValueInst *RVI) {
901-
assert(!RVI->getFunction()->hasOwnership());
902-
903-
SILValue Operand = RVI->getOperand();
904-
SILType OperandTy = Operand->getType();
905-
906-
// retain_value of an enum with a trivial payload or no-payload is a no-op +
907-
// RAUW.
908-
if (auto *EI = dyn_cast<EnumInst>(Operand)) {
909-
if (!EI->hasOperand() ||
910-
EI->getOperand()->getType().isTrivial(*RVI->getFunction())) {
911-
return eraseInstFromFunction(*RVI);
912-
}
913-
914-
// retain_value of an enum_inst where we know that it has a payload can be
915-
// reduced to a retain_value on the payload.
916-
if (EI->hasOperand()) {
917-
return Builder.createRetainValue(RVI->getLoc(), EI->getOperand(),
918-
RVI->getAtomicity());
919-
}
920-
}
921-
922-
// RetainValueInst of a loadable reference storage type needs the
923-
// corresponding retain instruction.
924-
#define ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
925-
if (OperandTy.is<Name##StorageType>()) \
926-
return Builder.create##Name##Retain(RVI->getLoc(), Operand, \
927-
RVI->getAtomicity());
928-
#include "swift/AST/ReferenceStorage.def"
929-
930-
// RetainValueInst of a reference type is a strong_release.
931-
if (OperandTy.isReferenceCounted(RVI->getModule())) {
932-
return Builder.createStrongRetain(RVI->getLoc(), Operand,
933-
RVI->getAtomicity());
934-
}
935-
936-
// RetainValueInst of a trivial type is a no-op + use propagation.
937-
if (OperandTy.isTrivial(*RVI->getFunction())) {
938-
return eraseInstFromFunction(*RVI);
939-
}
940-
941-
// Sometimes in the stdlib due to hand offs, we will see code like:
942-
//
943-
// release_value %0
944-
// retain_value %0
945-
//
946-
// with the matching retain_value to the release_value in a predecessor basic
947-
// block and the matching release_value for the retain_value_retain in a
948-
// successor basic block.
949-
//
950-
// Due to the matching pairs being in different basic blocks, the ARC
951-
// Optimizer (which is currently local to one basic block does not handle
952-
// it). But that does not mean that we cannot eliminate this pair with a
953-
// peephole.
954-
955-
// If we are not the first instruction in this basic block...
956-
if (RVI != &*RVI->getParent()->begin()) {
957-
SILBasicBlock::iterator Pred = std::prev(RVI->getIterator());
958-
959-
// ...and the predecessor instruction is a release_value on the same value
960-
// as our retain_value...
961-
if (auto *Release = dyn_cast<ReleaseValueInst>(&*Pred))
962-
// Remove them...
963-
if (Release->getOperand() == RVI->getOperand()) {
964-
eraseInstFromFunction(*Release);
965-
return eraseInstFromFunction(*RVI);
966-
}
967-
}
968-
969-
return nullptr;
970-
}
971-
972814
SILInstruction *SILCombiner::visitCondFailInst(CondFailInst *CFI) {
973815
// Remove runtime asserts such as overflow checks and bounds checks.
974816
if (RemoveCondFails)

0 commit comments

Comments
 (0)