Skip to content

Commit e9d0b08

Browse files
committed
Add utilities to support OSSA update after running SSAUpdater.
This directly adds support to BasicBlockCloner for updating OSSA. It also adds a much more general-purpose GuaranteedPhiBorrowFixup utility which can be used for more complicated SSA updates, in which multiple phis need to be created. More generally, it handles adding nested borrow scopes for guaranteed phis even when that phi is used by other guaranteed phis.
1 parent 0d594fd commit e9d0b08

File tree

6 files changed

+302
-9
lines changed

6 files changed

+302
-9
lines changed

include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,19 @@ bool rotateLoop(SILLoop *loop, DominanceInfo *domInfo, SILLoopInfo *loopInfo,
8383
bool rotateSingleBlockLoops, SILBasicBlock *upToBB,
8484
bool shouldVerify);
8585

86+
//===----------------------------------------------------------------------===//
87+
// BasicBlock Cloning
88+
//===----------------------------------------------------------------------===//
89+
90+
/// Return true if the \p termInst can be cloned. If termInst produces a
91+
/// guaranteed result, then it must be possible to create a nested borrow scope
92+
/// for that result when the cloner generates a guaranteed phi.
93+
///
94+
/// Note: if all the terminators uses will also be cloned, then a guaranteed phi
95+
/// won't be necessary. This is one of the reasons that cloning a region is much
96+
/// better than cloning a single block at a time.
97+
bool canCloneTerminator(TermInst *termInst);
98+
8699
/// Sink address projections to their out-of-block uses. This is
87100
/// required after cloning a block and before calling
88101
/// updateSSAAfterCloning to avoid address-type phis.
@@ -173,6 +186,7 @@ class BasicBlockCloner : public SILCloner<BasicBlockCloner> {
173186
if (!canCloneInstruction(&inst))
174187
return false;
175188
}
189+
canCloneTerminator(origBB->getTerminator());
176190
return true;
177191
}
178192

@@ -221,17 +235,34 @@ class BasicBlockCloner : public SILCloner<BasicBlockCloner> {
221235
bi->eraseFromParent();
222236
}
223237

238+
/// Create phis and maintain OSSA invariants.
239+
///
240+
/// Note: This must be called after calling cloneBlock or cloneBranchTarget,
241+
/// before using any OSSA utilities.
242+
///
243+
/// The client may perform arbitrary branch fixups and dead block removal
244+
/// after cloning and before calling this.
245+
///
246+
/// WARNING: If client converts terminator results to phis (e.g. replaces a
247+
/// switch_enum with a branch), then it must call this before performing that
248+
/// transformation, or fix the OSSA representation of that value itself.
249+
void updateOSSAAfterCloning();
250+
224251
/// Get the newly cloned block corresponding to `origBB`.
225252
SILBasicBlock *getNewBB() {
226253
return remapBasicBlock(origBB);
227254
}
228255

229256
bool wasCloned() { return isBlockCloned(origBB); }
230257

231-
/// Helper function to perform SSA updates after calling cloneBranchTarget.
232-
void updateSSAAfterCloning();
233-
234258
protected:
259+
/// Helper function to perform SSA updates used by updateOSSAAfterCloning.
260+
void updateSSAAfterCloning(SmallVectorImpl<SILPhiArgument *> &newPhis);
261+
262+
/// Given a terminator result, either from the original or the cloned block,
263+
/// update OSSA for any phis created for the result during edge splitting.
264+
void updateOSSATerminatorResult(SILPhiArgument *termResult);
265+
235266
// MARK: CRTP overrides.
236267

237268
/// Override getMappedValue to allow values defined outside the block to be

include/swift/SILOptimizer/Utils/OwnershipOptUtils.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ inline bool requiresOSSACleanup(SILValue v) {
3737
// Defined in BasicBlockUtils.h
3838
struct JointPostDominanceSetComputer;
3939

40+
/// Given a new phi that may use a guaranteed value, create nested borrow scopes
41+
/// for its incoming operands and end_borrows that cover the phi's extended
42+
/// borrow scope, which transitively includes any phis that use this phi.
43+
///
44+
/// Returns true if any changes were made.
45+
///
46+
/// Note: \p newPhi itself might not have Guaranteed ownership. A phi that
47+
/// converts Guaranteed to None ownership still needs nested borrows.
48+
///
49+
/// Note: This may be called on partially invalid OSSA form, where multiple
50+
/// newly created phis do not yet have a borrow scope.
51+
bool createBorrowScopeForPhiOperands(SILPhiArgument *newPhi);
52+
4053
/// A struct that contains context shared in between different operation +
4154
/// "ownership fixup" utilities. Please do not put actual methods on this, it is
4255
/// meant to be composed with.

lib/SILOptimizer/Transforms/SimplifyCFG.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ bool SimplifyCFG::threadEdge(const ThreadInfo &ti) {
382382
dyn_cast<BranchInst>(ThreadedSuccessorBlock->getTerminator())) {
383383
simplifyBranchBlock(branchInst);
384384
}
385-
Cloner.updateSSAAfterCloning();
385+
Cloner.updateOSSAAfterCloning();
386386
return true;
387387
}
388388

@@ -1149,7 +1149,7 @@ bool SimplifyCFG::tryJumpThreading(BranchInst *BI) {
11491149
// Duplicate the destination block into this one, rewriting uses of the BBArgs
11501150
// to use the branch arguments as we go.
11511151
Cloner.cloneBranchTarget(BI);
1152-
Cloner.updateSSAAfterCloning();
1152+
Cloner.updateOSSAAfterCloning();
11531153

11541154
// Once all the instructions are copied, we can nuke BI itself. We also add
11551155
// the threaded and edge block to the worklist now that they (likely) can be
@@ -3027,7 +3027,7 @@ bool SimplifyCFG::tailDuplicateObjCMethodCallSuccessorBlocks() {
30273027
continue;
30283028

30293029
Cloner.cloneBranchTarget(Branch);
3030-
Cloner.updateSSAAfterCloning();
3030+
Cloner.updateOSSAAfterCloning();
30313031

30323032
Changed = true;
30333033
// Simplify the cloned block and continue tail duplicating through its new

lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
1414
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
1515
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
16+
#include "swift/SILOptimizer/Utils/OwnershipOptUtils.h"
1617
#include "swift/SILOptimizer/Utils/SILSSAUpdater.h"
1718

1819
using namespace swift;
@@ -87,7 +88,99 @@ bool swift::removeUnreachableBlocks(SILFunction &f) {
8788
return changed;
8889
}
8990

90-
void BasicBlockCloner::updateSSAAfterCloning() {
91+
//===----------------------------------------------------------------------===//
92+
// BasicBlock Cloning
93+
//===----------------------------------------------------------------------===//
94+
95+
// Return true if a guaranteed terminator result can be borrowed such that the
96+
// nested borrow scope covers all its uses.
97+
static bool canBorrowGuaranteedResult(SILValue guaranteedResult) {
98+
if (guaranteedResult.getOwnershipKind() != OwnershipKind::Guaranteed) {
99+
// Either this terminator forwards an owned value, or it is some legal
100+
// conversion to a non-guaranteed value. Either way, not interesting.
101+
return true;
102+
}
103+
SmallVector<Operand *, 16> usePoints;
104+
return findInnerTransitiveGuaranteedUses(guaranteedResult, usePoints);
105+
}
106+
107+
bool swift::canCloneTerminator(TermInst *termInst) {
108+
// TODO: this is an awkward way to check for guaranteed terminator results.
109+
for (Operand &oper : termInst->getAllOperands()) {
110+
if (oper.getOperandOwnership() != OperandOwnership::ForwardingBorrow)
111+
continue;
112+
113+
if (!ForwardingOperand(&oper).visitForwardedValues(
114+
[&](SILValue termResult) {
115+
return canBorrowGuaranteedResult(termResult);
116+
})) {
117+
return false;
118+
}
119+
}
120+
return true;
121+
}
122+
123+
/// Given a terminator result, either from the original or the cloned block,
124+
/// update OSSA for any phis created for the result during edge splitting.
125+
void BasicBlockCloner::updateOSSATerminatorResult(SILPhiArgument *termResult) {
126+
assert(termResult->isTerminatorResult() && "precondition");
127+
128+
// If the terminator result is used by a phi, then it is invalid OSSA
129+
// which was created by edge splitting.
130+
for (Operand *termUse : termResult->getUses()) {
131+
if (auto phiOper = PhiOperand(termUse)) {
132+
createBorrowScopeForPhiOperands(phiOper.getValue());
133+
}
134+
}
135+
}
136+
137+
// Cloning does not invalidate ownership lifetime. When it clones values, it
138+
// also either clones the consumes, or creates the necessary phis that consume
139+
// the new values on all paths. However, cloning may create new phis of
140+
// inner guaranteed values. Since phis are reborrows, they are only allowed to
141+
// use BorrowedValues. Therefore, we must create nested borrow scopes for any
142+
// new phis whose arguments aren't BorrowedValues. Note that other newly created
143+
// phis are themselves BorrowedValues, so only one level of nested borrow is
144+
// needed per value, per new phi that the value reaches.
145+
void BasicBlockCloner::updateOSSAAfterCloning() {
146+
SmallVector<SILPhiArgument *, 4> updateSSAPhis;
147+
if (!origBB->getParent()->hasOwnership()) {
148+
updateSSAAfterCloning(updateSSAPhis);
149+
return;
150+
}
151+
152+
// If the original basic block has terminator results, then all phis in the
153+
// exit blocks are new phis that used to be terminator results.
154+
//
155+
// Create nested borrow scopes for terminator results that were converted to
156+
// phis during edge splitting. This is simpler to check before SSA update.
157+
//
158+
// This assumes that the phis introduced by update-SSA below cannot be users
159+
// of the phis that were created in exitBBs during block cloning. Otherwise
160+
// borrowPhiArguments would handle them twice.
161+
auto *termInst = origBB->getTerminator();
162+
// FIXME: cond_br args should not exist in OSSA
163+
if (!isa<BranchInst>(termInst) && !isa<CondBranchInst>(termInst)) {
164+
// Update all of the terminator results.
165+
for (auto *succBB : origBB->getSuccessorBlocks()) {
166+
for (SILArgument *termResult : succBB->getArguments()) {
167+
updateOSSATerminatorResult(cast<SILPhiArgument>(termResult));
168+
}
169+
}
170+
}
171+
172+
// Update SSA form before calling OSSA update utilities to maintain a layering
173+
// of SIL invariants.
174+
updateSSAAfterCloning(updateSSAPhis);
175+
176+
// Create nested borrow scopes for phis created during SSA update.
177+
for (auto *phi : updateSSAPhis) {
178+
createBorrowScopeForPhiOperands(phi);
179+
}
180+
}
181+
182+
void BasicBlockCloner::updateSSAAfterCloning(
183+
SmallVectorImpl<SILPhiArgument *> &newPhis) {
91184
// All instructions should have been checked by canCloneInstruction. But we
92185
// still need to check the arguments.
93186
for (auto arg : origBB->getArguments()) {
@@ -98,7 +191,7 @@ void BasicBlockCloner::updateSSAAfterCloning() {
98191
if (!needsSSAUpdate)
99192
return;
100193

101-
SILSSAUpdater ssaUpdater;
194+
SILSSAUpdater ssaUpdater(&newPhis);
102195
for (auto availValPair : availVals) {
103196
auto inst = availValPair.first;
104197
if (inst->use_empty())

lib/SILOptimizer/Utils/CheckedCastBrJumpThreading.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ void CheckedCastBrJumpThreading::optimizeFunction() {
673673
edit->modifyCFGForSuccessPreds(Cloner);
674674

675675
if (Cloner.wasCloned()) {
676-
Cloner.updateSSAAfterCloning();
676+
Cloner.updateOSSAAfterCloning();
677677

678678
if (!Cloner.getNewBB()->pred_empty())
679679
BlocksForWorklist.push_back(Cloner.getNewBB());

lib/SILOptimizer/Utils/OwnershipOptUtils.cpp

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,3 +1225,159 @@ SILBasicBlock::iterator OwnershipReplaceSingleUseHelper::perform() {
12251225
SingleUseReplacementUtility utility{use, newValue, *ctx};
12261226
return utility.perform();
12271227
}
1228+
1229+
//===----------------------------------------------------------------------===//
1230+
// createBorrowScopeForPhiOperands
1231+
//===----------------------------------------------------------------------===//
1232+
1233+
/// Given a phi that has been newly created or converted from terminator
1234+
/// results, check for inner guaranteed operands (which do not introduce a
1235+
/// borrow scope). This is invalid OSSA because the phi is a reborrow, and all
1236+
/// borrow-scope-ending instructions must directly use the BorrowedValue that
1237+
/// introduces the scope.
1238+
///
1239+
/// Create nested borrow scopes for its operands.
1240+
///
1241+
/// Transitively follow its phi uses.
1242+
///
1243+
/// Create end_borrows at all points that cover the inner uses.
1244+
///
1245+
/// The client must check canCloneTerminator() first to make sure that the
1246+
/// search for transitive uses does not encouter a PointerEscape.
1247+
class GuaranteedPhiBorrowFixup {
1248+
// A phi in mustConvertPhis has already been determined to be part of this
1249+
// new nested borrow scope.
1250+
SmallSetVector<SILPhiArgument *, 8> mustConvertPhis;
1251+
1252+
// Phi operands that are already within the new nested borrow scope.
1253+
llvm::SmallDenseSet<PhiOperand, 8> nestedPhiOperands;
1254+
1255+
public:
1256+
/// Return true if an extended nested borrow scope was created.
1257+
bool createExtendedNestedBorrowScope(SILPhiArgument *newPhi);
1258+
1259+
protected:
1260+
bool phiOperandNeedsBorrow(Operand *operand) {
1261+
SILValue inVal = operand->get();
1262+
if (inVal.getOwnershipKind() != OwnershipKind::Guaranteed) {
1263+
assert(inVal.getOwnershipKind() == OwnershipKind::None);
1264+
return false;
1265+
}
1266+
// This operand needs a nested borrow if inVal is not a BorrowedValue.
1267+
return !bool(BorrowedValue(inVal));
1268+
}
1269+
1270+
void borrowPhiOperand(Operand *oper) {
1271+
// Begin the borrow just before the branch.
1272+
SILInstruction *borrowPoint = oper->getUser();
1273+
auto loc = RegularLocation::getAutoGeneratedLocation(borrowPoint->getLoc());
1274+
auto *borrow =
1275+
SILBuilderWithScope(borrowPoint).createBeginBorrow(loc, oper->get());
1276+
oper->set(borrow);
1277+
}
1278+
1279+
EndBorrowInst *createEndBorrow(SILValue guaranteedValue,
1280+
SILBasicBlock::iterator borrowPoint) {
1281+
auto loc = borrowPoint->getLoc();
1282+
return SILBuilderWithScope(borrowPoint)
1283+
.createEndBorrow(loc, guaranteedValue);
1284+
}
1285+
1286+
void insertEndBorrowsAndFindPhis(SILPhiArgument *phi);
1287+
};
1288+
1289+
void GuaranteedPhiBorrowFixup::insertEndBorrowsAndFindPhis(
1290+
SILPhiArgument *phi) {
1291+
// Scope ending instructions are only needed for nontrivial results.
1292+
if (phi->getOwnershipKind() != OwnershipKind::Guaranteed) {
1293+
assert(phi->getOwnershipKind() == OwnershipKind::None);
1294+
return;
1295+
}
1296+
SmallVector<Operand *, 16> usePoints;
1297+
bool result = findInnerTransitiveGuaranteedUses(phi, usePoints);
1298+
assert(result && "should be checked by canCloneTerminator");
1299+
(void)result;
1300+
1301+
// Add usePoints to a set for phi membership checking.
1302+
//
1303+
// FIXME: consider integrating with ValueLifetimeBoundary instead.
1304+
SmallPtrSet<Operand *, 16> useSet(usePoints.begin(), usePoints.end());
1305+
1306+
auto phiUsers = llvm::map_range(usePoints, ValueBase::UseToUser());
1307+
ValueLifetimeAnalysis lifetimeAnalysis(phi, phiUsers);
1308+
ValueLifetimeBoundary boundary;
1309+
lifetimeAnalysis.computeLifetimeBoundary(boundary);
1310+
1311+
for (auto *boundaryEdge : boundary.boundaryEdges) {
1312+
createEndBorrow(phi, boundaryEdge->begin());
1313+
}
1314+
1315+
for (SILInstruction *lastUser : boundary.lastUsers) {
1316+
// If the last use is a branch, transitively process the phi.
1317+
if (isa<BranchInst>(lastUser)) {
1318+
for (Operand &oper : lastUser->getAllOperands()) {
1319+
if (!useSet.count(&oper))
1320+
continue;
1321+
1322+
PhiOperand phiOper(&oper);
1323+
nestedPhiOperands.insert(phiOper);
1324+
mustConvertPhis.insert(phiOper.getValue());
1325+
continue;
1326+
}
1327+
}
1328+
// If the last user is a terminator, add the successors as boundary edges.
1329+
if (isa<TermInst>(lastUser)) {
1330+
for (auto *succBB : lastUser->getParent()->getSuccessorBlocks()) {
1331+
// succBB cannot already be in boundaryEdges. It has a
1332+
// single predecessor with liveness ending at the terminator, which
1333+
// means it was not live into any successor blocks.
1334+
createEndBorrow(phi, succBB->begin());
1335+
}
1336+
continue;
1337+
}
1338+
// Otherwise, just plop down an end_borrow after the last use.
1339+
createEndBorrow(phi, std::next(lastUser->getIterator()));
1340+
}
1341+
};
1342+
1343+
// For each phi that transitively uses an inner guaranteed value, create nested
1344+
// borrow scopes so that it is a well-formed reborrow.
1345+
bool GuaranteedPhiBorrowFixup::
1346+
createExtendedNestedBorrowScope(SILPhiArgument *newPhi) {
1347+
// Determine if this new phi needs a nested borrow scope. If so, seed the
1348+
// Visit phi operands, returning false as soon as one needs a borrow.
1349+
if (!newPhi->visitIncomingPhiOperands(
1350+
[&](Operand *op) { return !phiOperandNeedsBorrow(op); })) {
1351+
mustConvertPhis.insert(newPhi);
1352+
}
1353+
if (mustConvertPhis.empty())
1354+
return false;
1355+
1356+
// mustConvertPhis grows in this loop.
1357+
for (unsigned mustConvertIdx = 0; mustConvertIdx < mustConvertPhis.size();
1358+
++mustConvertIdx) {
1359+
SILPhiArgument *phi = mustConvertPhis[mustConvertIdx];
1360+
insertEndBorrowsAndFindPhis(phi);
1361+
}
1362+
// To handle recursive phis, first discover all phis before attempting to
1363+
// borrow any phi operands.
1364+
for (SILPhiArgument *phi : mustConvertPhis) {
1365+
phi->visitIncomingPhiOperands([&](Operand *op) {
1366+
if (!nestedPhiOperands.count(op))
1367+
borrowPhiOperand(op);
1368+
return true;
1369+
});
1370+
}
1371+
return true;
1372+
}
1373+
1374+
// Note: \p newPhi itself might not have Guaranteed ownership. A phi that
1375+
// converts Guaranteed to None ownership still needs nested borrows.
1376+
//
1377+
// Note: This may be called on partially invalid OSSA form, where multiple
1378+
// newly created phis do not yet have a borrow scope. The implementation
1379+
// assumes that this API will eventually be called for all such new phis until
1380+
// OSSA is fully valid.
1381+
bool swift::createBorrowScopeForPhiOperands(SILPhiArgument *newPhi) {
1382+
return GuaranteedPhiBorrowFixup().createExtendedNestedBorrowScope(newPhi);
1383+
}

0 commit comments

Comments
 (0)