Skip to content

Commit 04da864

Browse files
committed
[semantic-arc] Split out owned -> guaranteed phi opt into its own file.
1 parent f4fcb40 commit 04da864

File tree

4 files changed

+265
-233
lines changed

4 files changed

+265
-233
lines changed

lib/SILOptimizer/SemanticARC/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ target_sources(swiftSILOptimizer PRIVATE
33
OwnershipLiveRange.cpp
44
LoadCopyToLoadBorrowOpt.cpp
55
BorrowScopeOpts.cpp
6-
CopyValueOpts.cpp)
6+
CopyValueOpts.cpp
7+
OwnedToGuaranteedPhiOpt.cpp)
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
//===--- OwnedToGuaranteedPhiOpt.cpp --------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 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+
/// \file
14+
///
15+
/// Late optimization that eliminates webs of owned phi nodes.
16+
///
17+
//===----------------------------------------------------------------------===//
18+
19+
#include "OwnershipPhiOperand.h"
20+
#include "SemanticARCOptVisitor.h"
21+
22+
using namespace swift;
23+
using namespace swift::semanticarc;
24+
25+
static bool canEliminatePhi(
26+
SemanticARCOptVisitor::FrozenMultiMapRange optimizableIntroducerRange,
27+
ArrayRef<OwnershipPhiOperand> incomingValueOperandList,
28+
SmallVectorImpl<OwnedValueIntroducer> &ownedValueIntroducerAccumulator) {
29+
for (auto incomingValueOperand : incomingValueOperandList) {
30+
SILValue incomingValue = incomingValueOperand.getValue();
31+
32+
// Before we do anything, see if we have an incoming value with trivial
33+
// ownership. This can occur in the case where we are working with enums due
34+
// to trivial non-payloaded cases. Skip that.
35+
if (incomingValue.getOwnershipKind() == ValueOwnershipKind::None) {
36+
continue;
37+
}
38+
39+
// Then see if this is an introducer that we actually saw as able to be
40+
// optimized if we could flip this joined live range.
41+
//
42+
// NOTE: If this linear search is too slow, we can change the multimap to
43+
// sort the mapped to list by pointer instead of insertion order. In such a
44+
// case, we could then bisect.
45+
if (llvm::find(optimizableIntroducerRange,
46+
incomingValueOperand.getOperand()) ==
47+
optimizableIntroducerRange.end()) {
48+
return false;
49+
}
50+
51+
// Now that we know it is an owned value that we saw before, check for
52+
// introducers of the owned value which are the copies that we may be able
53+
// to eliminate. Since we do not look through joined live ranges, we must
54+
// only have a single introducer. So look for that one and if not, bail.
55+
auto singleIntroducer = getSingleOwnedValueIntroducer(incomingValue);
56+
if (!singleIntroducer.hasValue()) {
57+
return false;
58+
}
59+
60+
// Then make sure that our owned value introducer is able to be converted to
61+
// guaranteed and that we found it to have a LiveRange that we could have
62+
// eliminated /if/ we were to get rid of this phi.
63+
if (!singleIntroducer->isConvertableToGuaranteed()) {
64+
return false;
65+
}
66+
67+
// Otherwise, add the introducer to our result array.
68+
ownedValueIntroducerAccumulator.push_back(*singleIntroducer);
69+
}
70+
71+
#ifndef NDEBUG
72+
// Other parts of the pass ensure that we only add values to the list if their
73+
// owned value introducer is not used by multiple live ranges. That being
74+
// said, lets assert that.
75+
{
76+
SmallVector<OwnedValueIntroducer, 32> uniqueCheck;
77+
llvm::copy(ownedValueIntroducerAccumulator,
78+
std::back_inserter(uniqueCheck));
79+
sortUnique(uniqueCheck);
80+
assert(
81+
uniqueCheck.size() == ownedValueIntroducerAccumulator.size() &&
82+
"multiple joined live range operands are from the same live range?!");
83+
}
84+
#endif
85+
86+
return true;
87+
}
88+
89+
static bool getIncomingJoinedLiveRangeOperands(
90+
SILValue joinedLiveRange,
91+
SmallVectorImpl<OwnershipPhiOperand> &resultingOperands) {
92+
if (auto *phi = dyn_cast<SILPhiArgument>(joinedLiveRange)) {
93+
return phi->visitIncomingPhiOperands([&](Operand *op) {
94+
if (auto phiOp = OwnershipPhiOperand::get(op)) {
95+
resultingOperands.push_back(*phiOp);
96+
return true;
97+
}
98+
return false;
99+
});
100+
}
101+
102+
if (auto *svi = dyn_cast<SingleValueInstruction>(joinedLiveRange)) {
103+
return llvm::all_of(svi->getAllOperands(), [&](const Operand &op) {
104+
// skip type dependent operands.
105+
if (op.isTypeDependent())
106+
return true;
107+
108+
auto phiOp = OwnershipPhiOperand::get(&op);
109+
if (!phiOp)
110+
return false;
111+
resultingOperands.push_back(*phiOp);
112+
return true;
113+
});
114+
}
115+
116+
llvm_unreachable("Unhandled joined live range?!");
117+
}
118+
119+
//===----------------------------------------------------------------------===//
120+
// Top Level Entrypoint
121+
//===----------------------------------------------------------------------===//
122+
123+
bool SemanticARCOptVisitor::performPostPeepholeOwnedArgElimination() {
124+
bool madeChange = false;
125+
126+
// First freeze our multi-map so we can use it for map queries. Also, setup a
127+
// defer of the reset so we do not forget to reset the map when we are done.
128+
joinedOwnedIntroducerToConsumedOperands.setFrozen();
129+
SWIFT_DEFER { joinedOwnedIntroducerToConsumedOperands.reset(); };
130+
131+
// Now for each phi argument that we have in our multi-map...
132+
SmallVector<OwnershipPhiOperand, 4> incomingValueOperandList;
133+
SmallVector<OwnedValueIntroducer, 4> ownedValueIntroducers;
134+
for (auto pair : joinedOwnedIntroducerToConsumedOperands.getRange()) {
135+
SWIFT_DEFER {
136+
incomingValueOperandList.clear();
137+
ownedValueIntroducers.clear();
138+
};
139+
140+
// First compute the LiveRange for ownershipPhi value. For simplicity, we
141+
// only handle cases now where the result does not have any additional
142+
// ownershipPhi uses.
143+
SILValue joinedIntroducer = pair.first;
144+
OwnershipLiveRange joinedLiveRange(joinedIntroducer);
145+
if (bool(joinedLiveRange.hasUnknownConsumingUse())) {
146+
continue;
147+
}
148+
149+
// Ok, we know that our phi argument /could/ be converted to guaranteed if
150+
// our incoming values are able to be converted to guaranteed. Now for each
151+
// incoming value, compute the incoming values ownership roots and see if
152+
// all of the ownership roots are in our owned incoming value array.
153+
if (!getIncomingJoinedLiveRangeOperands(joinedIntroducer,
154+
incomingValueOperandList)) {
155+
continue;
156+
}
157+
158+
// Grab our list of introducer values paired with this SILArgument. See if
159+
// all of these introducer values were ones that /could/ have been
160+
// eliminated if it was not for the given phi. If all of them are, we can
161+
// optimize!
162+
{
163+
auto rawFoundOptimizableIntroducerArray = pair.second;
164+
if (!canEliminatePhi(rawFoundOptimizableIntroducerArray,
165+
incomingValueOperandList, ownedValueIntroducers)) {
166+
continue;
167+
}
168+
}
169+
170+
// Ok, at this point we know that we can eliminate this phi. First go
171+
// through the list of incomingValueOperandList and stash the value/set the
172+
// operand's stored value to undef. We will hook them back up later.
173+
SmallVector<SILValue, 8> originalIncomingValues;
174+
for (auto &incomingValueOperand : incomingValueOperandList) {
175+
originalIncomingValues.push_back(incomingValueOperand.getValue());
176+
incomingValueOperand.markUndef();
177+
}
178+
179+
// Then go through all of our owned value introducers, compute their live
180+
// ranges, and eliminate them. We know it is safe to remove them from our
181+
// previous proofs.
182+
//
183+
// NOTE: If our introducer is a copy_value that is one of our
184+
// originalIncomingValues, we need to update the originalIncomingValue array
185+
// with that value since we are going to delete the copy_value here. This
186+
// creates a complication since we want to binary_search on
187+
// originalIncomingValues to detect this same condition! So, we create a
188+
// list of updates that we apply after we no longer need to perform
189+
// binary_search, but before we start RAUWing things.
190+
SmallVector<std::pair<SILValue, unsigned>, 8> incomingValueUpdates;
191+
for (auto introducer : ownedValueIntroducers) {
192+
SILValue v = introducer.value;
193+
OwnershipLiveRange lr(v);
194+
195+
// For now, we only handle copy_value for simplicity.
196+
//
197+
// TODO: Add support for load [copy].
198+
if (introducer.kind == OwnedValueIntroducerKind::Copy) {
199+
auto *cvi = cast<CopyValueInst>(v);
200+
// Before we convert from owned to guaranteed, we need to first see if
201+
// cvi is one of our originalIncomingValues. If so, we need to set
202+
// originalIncomingValues to be cvi->getOperand(). Otherwise, weirdness
203+
// results since we are deleting one of our stashed values.
204+
auto iter = find(originalIncomingValues, cvi);
205+
if (iter != originalIncomingValues.end()) {
206+
// We use an auxillary array here so we can continue to bisect on
207+
// original incoming values. Once we are done processing here, we will
208+
// not need that property anymore.
209+
unsigned updateOffset =
210+
std::distance(originalIncomingValues.begin(), iter);
211+
incomingValueUpdates.emplace_back(cvi->getOperand(), updateOffset);
212+
}
213+
std::move(lr).convertToGuaranteedAndRAUW(cvi->getOperand(),
214+
getCallbacks());
215+
continue;
216+
}
217+
llvm_unreachable("Unhandled optimizable introducer!");
218+
}
219+
220+
// Now go through and update our original incoming value array now that we
221+
// do not need it to be sorted for bisection purposes.
222+
while (!incomingValueUpdates.empty()) {
223+
auto pair = incomingValueUpdates.pop_back_val();
224+
originalIncomingValues[pair.second] = pair.first;
225+
}
226+
227+
// Then convert the phi's live range to be guaranteed.
228+
std::move(joinedLiveRange)
229+
.convertJoinedLiveRangePhiToGuaranteed(
230+
getDeadEndBlocks(), lifetimeFrontier, getCallbacks());
231+
232+
// Now if our phi operand consumes/forwards its guaranteed input, insert a
233+
// begin_borrow along the incoming value edges. We have to do this after
234+
// converting the incoming values to be guaranteed to avoid tripping
235+
// SILBuilder checks around simple ownership invariants (namely that def/use
236+
// line up) when creating instructions.
237+
assert(incomingValueOperandList.size() == originalIncomingValues.size());
238+
while (!incomingValueOperandList.empty()) {
239+
auto incomingValueOperand = incomingValueOperandList.pop_back_val();
240+
SILValue originalValue = originalIncomingValues.pop_back_val();
241+
if (incomingValueOperand.isGuaranteedConsuming() &&
242+
originalValue.getOwnershipKind() != ValueOwnershipKind::None) {
243+
auto loc = RegularLocation::getAutoGeneratedLocation();
244+
SILBuilderWithScope builder(incomingValueOperand.getInst());
245+
originalValue = builder.createBeginBorrow(loc, originalValue);
246+
}
247+
incomingValueOperand.getOperand()->set(originalValue);
248+
}
249+
250+
madeChange = true;
251+
if (VerifyAfterTransform) {
252+
F.verify();
253+
}
254+
}
255+
256+
return madeChange;
257+
}

lib/SILOptimizer/SemanticARC/SemanticARCOptVisitor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
namespace swift {
2828
namespace semanticarc {
2929

30+
extern bool VerifyAfterTransform;
31+
3032
bool constructCacheValue(
3133
SILValue initialValue,
3234
SmallVectorImpl<Operand *> &wellBehavedWriteAccumulator);

0 commit comments

Comments
 (0)