Skip to content

Commit 4d0e4a8

Browse files
authored
Merge pull request #36465 from atrick/ossa-guaranteed-phi
2 parents dbea482 + e9d0b08 commit 4d0e4a8

File tree

10 files changed

+381
-20
lines changed

10 files changed

+381
-20
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,31 @@ inline bool isForwardingConsume(SILValue value) {
7575
return canOpcodeForwardOwnedValues(value);
7676
}
7777

78+
/// Find all "use points" of \p guaranteedValue that determine its lifetime
79+
/// requirement.
80+
///
81+
/// Precondition: \p guaranteedValue is not a BorrowedValue.
82+
///
83+
/// In general, if the client does not know whether \p guaranteed value
84+
/// introduces a borrow scope or not, it should instead call
85+
/// findTransitiveGuaranteedUses() which efficiently gathers use
86+
/// points for arbitrary guaranteed values, including those that introduce a
87+
/// borrow scope and may be reborrowed.
88+
///
89+
/// In valid OSSA, this should never be called on values that introduce a new
90+
/// scope (doing so would be extremely innefficient). The lifetime of a borrow
91+
/// introducing instruction is always determined by its direct EndBorrow uses
92+
/// (see BorrowedValue::visitLocalScopeEndingUses). None of the non-scope-ending
93+
/// uses are relevant, and there's no need to transively follow forwarding
94+
/// uses. However, this utility may be used on borrow-introducing values when
95+
/// updating OSSA form to place EndBorrow uses after introducing new phis.
96+
///
97+
/// When this is called on a value that does not introduce a new scope, none of
98+
/// the use points can be EndBorrows or Reborrows. Those uses are only allowed
99+
/// on borrow-introducing values.
100+
bool findInnerTransitiveGuaranteedUses(SILValue guaranteedValue,
101+
SmallVectorImpl<Operand *> &usePoints);
102+
78103
/// Find all "use points" of a guaranteed value within its enclosing borrow
79104
/// scope (without looking through reborrows). To find the use points of the
80105
/// extended borrow scope, after looking through reborrows, use
@@ -101,7 +126,7 @@ inline bool isForwardingConsume(SILValue value) {
101126
/// 2. If \p guaranteedValue does not introduce a borrow scope (it is not a
102127
/// valid BorrowedValue), then its uses are discovered transitively by looking
103128
/// through forwarding operations. If any use is a PointerEscape, then this
104-
/// returns false without adding more uses--the guaranteed values lifetime is
129+
/// returns false without adding more uses--the guaranteed value's lifetime is
105130
/// indeterminite. If a use introduces a nested borrow scope, it creates use
106131
/// points where the "extended" borrow scope ends. An extended borrow
107132
/// scope is found by looking through any reborrows that end the nested

include/swift/SIL/SILArgument.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ class SILArgument : public ValueBase {
117117
/// opposed to a cast or projection.
118118
bool isPhiArgument() const;
119119

120+
/// Return true if this block argument is a terminator result.
121+
bool isTerminatorResult() const;
122+
120123
/// If this argument is a phi, return the incoming phi value for the given
121124
/// predecessor BB. If this argument is not a phi, return an invalid SILValue.
122125
SILValue getIncomingPhiValue(SILBasicBlock *predBlock) const;
@@ -178,6 +181,10 @@ class SILArgument : public ValueBase {
178181
/// terminator has a single operand, return that terminator.
179182
TermInst *getSingleTerminator() const;
180183

184+
/// Return the terminator instruction for which this argument is a result,
185+
/// otherwise return nullptr.
186+
TermInst *getTerminatorForResultArg() const;
187+
181188
/// Return the SILArgumentKind of this argument.
182189
SILArgumentKind getKind() const {
183190
return SILArgumentKind(ValueBase::getKind());
@@ -208,6 +215,9 @@ class SILPhiArgument : public SILArgument {
208215
/// opposed to a cast or projection.
209216
bool isPhiArgument() const;
210217

218+
/// Return true if this block argument is a terminator result.
219+
bool isTerminatorResult() const { return !isPhiArgument(); }
220+
211221
/// If this argument is a phi, return the incoming phi value for the given
212222
/// predecessor BB. If this argument is not a phi, return an invalid SILValue.
213223
///
@@ -283,6 +293,10 @@ class SILPhiArgument : public SILArgument {
283293
/// terminator has a single operand, return that terminator.
284294
TermInst *getSingleTerminator() const;
285295

296+
/// Return the terminator instruction for which this argument is a result,
297+
/// otherwise return nullptr.
298+
TermInst *getTerminatorForResultArg() const;
299+
286300
static bool classof(const SILInstruction *) = delete;
287301
static bool classof(const SILUndef *) = delete;
288302
static bool classof(SILNodePointer node) {
@@ -347,6 +361,16 @@ inline bool SILArgument::isPhiArgument() const {
347361
llvm_unreachable("Covered switch is not covered?!");
348362
}
349363

364+
inline bool SILArgument::isTerminatorResult() const {
365+
switch (getKind()) {
366+
case SILArgumentKind::SILPhiArgument:
367+
return cast<SILPhiArgument>(this)->isTerminatorResult();
368+
case SILArgumentKind::SILFunctionArgument:
369+
return false;
370+
}
371+
llvm_unreachable("Covered switch is not covered?!");
372+
}
373+
350374
inline SILValue
351375
SILArgument::getIncomingPhiValue(SILBasicBlock *predBlock) const {
352376
switch (getKind()) {
@@ -417,6 +441,16 @@ inline TermInst *SILArgument::getSingleTerminator() const {
417441
llvm_unreachable("Covered switch is not covered?!");
418442
}
419443

444+
inline TermInst *SILArgument::getTerminatorForResultArg() const {
445+
switch (getKind()) {
446+
case SILArgumentKind::SILPhiArgument:
447+
return cast<SILPhiArgument>(this)->getTerminatorForResultArg();
448+
case SILArgumentKind::SILFunctionArgument:
449+
return nullptr;
450+
}
451+
llvm_unreachable("Covered switch is not covered?!");
452+
}
453+
420454
inline bool SILArgument::getIncomingPhiOperands(
421455
SmallVectorImpl<Operand *> &returnedPhiOperands) const {
422456
switch (getKind()) {

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/SIL/IR/SILArgument.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,14 @@ TermInst *SILPhiArgument::getSingleTerminator() const {
316316
return const_cast<SILBasicBlock *>(predBlock)->getTerminator();
317317
}
318318

319+
TermInst *SILPhiArgument::getTerminatorForResultArg() const {
320+
if (auto *termInst = getSingleTerminator()) {
321+
if (!isa<BranchInst>(termInst) && !isa<CondBranchInst>(termInst))
322+
return termInst;
323+
}
324+
return nullptr;
325+
}
326+
319327
SILPhiArgument *BranchInst::getArgForOperand(const Operand *oper) {
320328
assert(oper->getUser() == this);
321329
return cast<SILPhiArgument>(

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -147,21 +147,18 @@ bool swift::canOpcodeForwardOwnedValues(Operand *use) {
147147
// Guaranteed Use-Point (Lifetime) Discovery
148148
//===----------------------------------------------------------------------===//
149149

150-
// Find all use points of \p guaranteedValue within its borrow scope where \p
151-
// guaranteedValue is not itself a BorrowedValue (it does not introduce a borrow
152-
// scope). This means there is no need to consider reborrows, and all uses are
153-
// naturally dominated by \p guaranteedValue. On the other hand, if a
154-
// PointerEscape is found, then no assumption can be made about \p
155-
// guaranteedValue's lifetime. Therefore the use points are incomplete and this
156-
// returns false.
150+
// Find all use points of \p guaranteedValue within its borrow scope. All uses
151+
// are naturally dominated by \p guaranteedValue. If a PointerEscape is found,
152+
// then no assumption can be made about \p guaranteedValue's lifetime. Therefore
153+
// the use points are incomplete and this returns false.
157154
//
158155
// Accumulate results in \p usePoints, ignoring existing elements.
159156
//
160157
// Skip over nested borrow scopes. Their scope-ending instructions are their use
161158
// points. Transitively find all nested scope-ending instructions by looking
162159
// through nested reborrows. Nested reborrows are not use points and \p
163160
// visitReborrow is not called for them.
164-
static bool
161+
bool swift::
165162
findInnerTransitiveGuaranteedUses(SILValue guaranteedValue,
166163
SmallVectorImpl<Operand *> &usePoints) {
167164
// Push the value's immediate uses.
@@ -195,7 +192,6 @@ findInnerTransitiveGuaranteedUses(SILValue guaranteedValue,
195192
case OperandOwnership::TrivialUse:
196193
case OperandOwnership::ForwardingConsume:
197194
case OperandOwnership::DestroyingConsume:
198-
case OperandOwnership::Reborrow:
199195
llvm_unreachable("this operand cannot handle an inner guaranteed use");
200196

201197
case OperandOwnership::ForwardingUnowned:
@@ -205,7 +201,12 @@ findInnerTransitiveGuaranteedUses(SILValue guaranteedValue,
205201
case OperandOwnership::InstantaneousUse:
206202
case OperandOwnership::UnownedInstantaneousUse:
207203
case OperandOwnership::BitwiseEscape:
208-
// An end_borrow may be pushed as a use when processing a nested borrow.
204+
// Reborrow only happens when this is called on a value that creates a
205+
// borrow scope.
206+
case OperandOwnership::Reborrow:
207+
// EndBorrow either happens when this is called on a value that creates a
208+
// borrow scope, or when it is pushed as a use when processing a nested
209+
// borrow.
209210
case OperandOwnership::EndBorrow:
210211
break;
211212

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

0 commit comments

Comments
 (0)