Skip to content

Commit cdbc917

Browse files
authored
Merge pull request #61648 from atrick/jump-thread
[NFC] OSSA jump-threading support
2 parents 927b846 + 13e032a commit cdbc917

File tree

5 files changed

+418
-94
lines changed

5 files changed

+418
-94
lines changed

include/swift/SILOptimizer/Utils/OwnershipOptUtils.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,23 @@ struct OwnershipFixupContext {
220220
class OwnershipRAUWHelper {
221221
public:
222222
/// Return true if \p oldValue can be replaced with \p newValue in terms of
223-
/// their value ownership. This ignores any current uses of \p oldValue. To
224-
/// determine whether \p oldValue can be replaced as-is with it's existing
225-
/// uses, create an instance of OwnershipRAUWHelper and check its validity.
223+
/// their value ownership. This checks current uses of \p oldValue for
224+
/// satisfying lexical lifetimes. To completely determine whether \p oldValue
225+
/// can be replaced as-is with it's existing uses, create an instance of
226+
/// OwnershipRAUWHelper and check its validity.
226227
static bool hasValidRAUWOwnership(SILValue oldValue, SILValue newValue,
227228
ArrayRef<Operand *> oldUses);
228229

230+
static bool hasValidNonLexicalRAUWOwnership(SILValue oldValue,
231+
SILValue newValue) {
232+
if (oldValue->isLexical() || newValue->isLexical())
233+
return false;
234+
235+
// Pretend that we have no uses since they are only used to check lexical
236+
// lifetimes.
237+
return hasValidRAUWOwnership(oldValue, newValue, {});
238+
}
239+
229240
private:
230241
OwnershipFixupContext *ctx;
231242
SILValue oldValue;

lib/SILOptimizer/Transforms/SpeculativeDevirtualizer.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,21 +146,21 @@ static FullApplySite speculateMonomorphicTarget(FullApplySite AI,
146146
SILFunction *F = AI.getFunction();
147147
SILBasicBlock *Entry = AI.getParent();
148148

149+
ClassMethodInst *CMI = cast<ClassMethodInst>(AI.getCallee());
150+
149151
// Iden is the basic block containing the direct call.
150152
SILBasicBlock *Iden = F->createBasicBlock();
151153
// Virt is the block containing the slow virtual call.
152154
SILBasicBlock *Virt = F->createBasicBlock();
153155
Iden->createPhiArgument(SILType::getPrimitiveObjectType(SubType),
154-
OwnershipKind::Owned);
156+
CMI->getOperand()->getOwnershipKind());
155157

156158
SILBasicBlock *Continue = Entry->split(It);
157159

158160
SILBuilderWithScope Builder(Entry, AI.getInstruction());
159161
// Create the checked_cast_branch instruction that checks at runtime if the
160162
// class instance is identical to the SILType.
161163

162-
ClassMethodInst *CMI = cast<ClassMethodInst>(AI.getCallee());
163-
164164
CCBI = Builder.createCheckedCastBranch(AI.getLoc(), /*exact*/ true,
165165
CMI->getOperand(),
166166
SILType::getPrimitiveObjectType(SubType),

lib/SILOptimizer/Utils/CheckedCastBrJumpThreading.cpp

Lines changed: 150 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
1919
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
2020
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
21+
#include "swift/SILOptimizer/Utils/OwnershipOptUtils.h"
2122
#include "swift/SILOptimizer/Utils/SILInliner.h"
2223
#include "llvm/ADT/SmallPtrSet.h"
2324
#include "llvm/ADT/SmallVector.h"
@@ -52,6 +53,11 @@ class CheckedCastBrJumpThreading {
5253
// Enable non-trivial terminator rewriting in OSSA.
5354
bool EnableOSSARewriteTerminator;
5455

56+
InstModCallbacks callbacks;
57+
58+
// Shared data structures across OwnershipRAUWHelper instances.
59+
OwnershipFixupContext rauwContext;
60+
5561
// List of predecessors.
5662
typedef SmallVector<SILBasicBlock *, 8> PredList;
5763

@@ -82,8 +88,10 @@ class CheckedCastBrJumpThreading {
8288
// Copy of CheckedCastBrJumpThreading::FailurePreds.
8389
PredList FailurePreds;
8490
// The argument of the dominating checked_cast_br's successor block.
85-
SILValue SuccessArg;
91+
SILPhiArgument *SuccessArg;
8692

93+
// True if the dominating check is inverted AND all the predecessors are on
94+
// the dominating check's success path.
8795
bool InvertSuccess;
8896

8997
// True if CheckedCastBrJumpThreading::numUnknownPreds is not 0.
@@ -92,13 +100,16 @@ class CheckedCastBrJumpThreading {
92100
Edit(SILBasicBlock *CCBBlock, bool InvertSuccess,
93101
const PredList &SuccessPreds,
94102
const PredList &FailurePreds,
95-
bool hasUnknownPreds, SILValue SuccessArg) :
103+
bool hasUnknownPreds, SILPhiArgument *SuccessArg) :
96104
CCBBlock(CCBBlock), SuccessPreds(SuccessPreds), FailurePreds(FailurePreds),
97105
SuccessArg(SuccessArg), InvertSuccess(InvertSuccess),
98106
hasUnknownPreds(hasUnknownPreds) { }
99107

108+
bool canRAUW(OwnershipFixupContext &rauwContext);
109+
100110
void modifyCFGForFailurePreds(BasicBlockCloner &Cloner);
101-
void modifyCFGForSuccessPreds(BasicBlockCloner &Cloner);
111+
void modifyCFGForSuccessPreds(BasicBlockCloner &Cloner,
112+
OwnershipFixupContext &rauwContext);
102113
};
103114

104115
// Contains an entry for each checked_cast_br to be optimized.
@@ -135,6 +146,7 @@ class CheckedCastBrJumpThreading {
135146
bool EnableOSSARewriteTerminator)
136147
: Fn(Fn), DT(DT), deBlocks(deBlocks),
137148
EnableOSSARewriteTerminator(EnableOSSARewriteTerminator),
149+
rauwContext(callbacks, *deBlocks),
138150
BlocksForWorklist(BlocksForWorklist), BlocksToEdit(Fn),
139151
BlocksToClone(Fn) {}
140152

@@ -238,8 +250,54 @@ SILValue CheckedCastBrJumpThreading::isArgValueEquivalentToCondition(
238250
}
239251
}
240252

241-
/// Create a copy of the BB as a landing BB
242-
/// for all FailurePreds.
253+
// Return false if an ownership RAUW is necessary but cannot be performed.
254+
bool CheckedCastBrJumpThreading::Edit::
255+
canRAUW(OwnershipFixupContext &rauwContext) {
256+
if (InvertSuccess || SuccessPreds.empty())
257+
return true;
258+
259+
auto *ccbi = cast<CheckedCastBranchInst>(CCBBlock->getTerminator());
260+
auto *oldSuccessArg = ccbi->getSuccessBB()->getArgument(0);
261+
// Check the ownership validity of the RAUW transformation that will replace
262+
// oldSuccessArg with SuccessArg. This is valid iff it will be valid to
263+
// replace the new checked_cast_br. The new checked_cast_br will be in a
264+
// cloned block reachable from a subset of the original block's predecessors,
265+
// it will have equivalent operands. Checking the current uses is unnecessary,
266+
// because after cloning, the only use of the cloned checked_cast_br will be
267+
// a phi in the successor. It is always valid to replace a phi use, because
268+
// phi itself already guarantees that lifetime extends over its own uses.
269+
return OwnershipRAUWHelper::hasValidNonLexicalRAUWOwnership(oldSuccessArg,
270+
SuccessArg);
271+
}
272+
273+
// Erase the checked_cast_br that terminates this block. The caller must replace
274+
// and erase the successful cast result.
275+
//
276+
// The checked_cast_br failure result's uses are replaced with the cast's
277+
// operand, and the block argument representing that result is deleted. Since
278+
// the checked_cast's uses now use its forwarded operand, they are still in
279+
// valid OSSA form, so this can be done before updateOSSAAfterCloning, which
280+
// doesn't need to know about the erased checked_cast.
281+
static void eraseCheckedCastBr(
282+
CheckedCastBranchInst *checkedCastBr,
283+
CheckedCastBranchInst::SuccessorPath successorIdx) {
284+
285+
SILBuilderWithScope Builder(checkedCastBr);
286+
Builder.createBranch(checkedCastBr->getLoc(),
287+
checkedCastBr->getSuccessors()[successorIdx]);
288+
auto *successBB = checkedCastBr->getSuccessBB();
289+
assert(successBB->getNumArguments() == 1);
290+
assert(successBB->getArgument(0)->use_empty());
291+
successBB->eraseArgument(0);
292+
if (checkedCastBr->getFunction()->hasOwnership()) {
293+
auto *failureBB = checkedCastBr->getFailureBB();
294+
assert(failureBB->getNumArguments() == 1);
295+
failureBB->getArgument(0)->replaceAllUsesWith(checkedCastBr->getOperand());
296+
failureBB->eraseArgument(0);
297+
}
298+
checkedCastBr->eraseFromParent();
299+
}
300+
243301
void CheckedCastBrJumpThreading::Edit::modifyCFGForFailurePreds(
244302
BasicBlockCloner &Cloner) {
245303
if (FailurePreds.empty())
@@ -248,12 +306,13 @@ void CheckedCastBrJumpThreading::Edit::modifyCFGForFailurePreds(
248306
assert(!Cloner.wasCloned());
249307
Cloner.cloneBlock();
250308
SILBasicBlock *TargetFailureBB = Cloner.getNewBB();
309+
// This cloned block branches to the FailureBB, so just delete the cast and
310+
// ignore the success target which will keep it's original predecessor.
251311
auto *clonedCCBI =
252-
cast<CheckedCastBranchInst>(TargetFailureBB->getTerminator());
253-
SILBuilderWithScope Builder(clonedCCBI);
254-
// This BB copy branches to the FailureBB.
255-
Builder.createBranch(clonedCCBI->getLoc(), clonedCCBI->getFailureBB());
256-
clonedCCBI->eraseFromParent();
312+
cast<CheckedCastBranchInst>(TargetFailureBB->getTerminator());
313+
auto *clonedSuccessArg = clonedCCBI->getSuccessBB()->getArgument(0);
314+
clonedSuccessArg->replaceAllUsesWithUndef();
315+
eraseCheckedCastBr(clonedCCBI, CheckedCastBranchInst::FailIdx);
257316

258317
// Redirect all FailurePreds to the copy of BB.
259318
for (auto *Pred : FailurePreds) {
@@ -262,62 +321,93 @@ void CheckedCastBrJumpThreading::Edit::modifyCFGForFailurePreds(
262321
replaceBranchTarget(TI, CCBBlock, TargetFailureBB,
263322
/*PreserveArgs=*/true);
264323
}
324+
Cloner.updateOSSAAfterCloning();
265325
}
266326

267-
/// Create a copy of the BB or reuse BB as
268-
/// a landing basic block for all FailurePreds.
327+
/// Create a copy of the BB or reuse BB as a landing basic block for all
328+
/// FailurePreds.
329+
///
330+
/// Note: must be called after modifyCFGForFailurePreds and
331+
/// before modifyCFGForUnknownPreds.
269332
void CheckedCastBrJumpThreading::Edit::modifyCFGForSuccessPreds(
270-
BasicBlockCloner &Cloner) {
333+
BasicBlockCloner &Cloner, OwnershipFixupContext &rauwContext) {
271334

335+
auto *checkedCastBr = cast<CheckedCastBranchInst>(CCBBlock->getTerminator());
336+
auto *oldSuccessArg = checkedCastBr->getSuccessBB()->getArgument(0);
272337
if (InvertSuccess) {
273-
auto *CCBI = cast<CheckedCastBranchInst>(CCBBlock->getTerminator());
274-
SILBuilderWithScope(CCBI).createBranch(CCBI->getLoc(),
275-
CCBI->getFailureBB());
276-
CCBI->eraseFromParent();
338+
assert(!hasUnknownPreds && "is not handled, should have been checked");
339+
// This success path is unused, so undef its uses and delete the cast.
340+
oldSuccessArg->replaceAllUsesWithUndef();
341+
eraseCheckedCastBr(checkedCastBr, CheckedCastBranchInst::FailIdx);
277342
return;
278343
}
279-
if (hasUnknownPreds) {
280-
if (!SuccessPreds.empty()) {
281-
// Create a copy of the BB as a landing BB.
282-
// for all SuccessPreds.
283-
assert(!Cloner.wasCloned());
284-
Cloner.cloneBlock();
285-
SILBasicBlock *TargetSuccessBB = Cloner.getNewBB();
286-
auto *clonedCCBI =
287-
cast<CheckedCastBranchInst>(TargetSuccessBB->getTerminator());
288-
SILBuilderWithScope Builder(clonedCCBI);
289-
// This BB copy branches to SuccessBB.
290-
// Take argument value from the dominating BB.
291-
Builder.createBranch(clonedCCBI->getLoc(), clonedCCBI->getSuccessBB(),
292-
{SuccessArg});
293-
clonedCCBI->eraseFromParent();
294-
295-
// Redirect all SuccessPreds to the copy of BB.
296-
for (auto *Pred : SuccessPreds) {
297-
TermInst *TI = Pred->getTerminator();
298-
// Replace branch to BB by branch to TargetSuccessBB.
299-
replaceBranchTarget(TI, CCBBlock, TargetSuccessBB, /*PreserveArgs=*/true);
300-
}
301-
}
344+
if (!hasUnknownPreds) {
345+
// All predecessors are dominated by a successful cast. So the current BB
346+
// can be re-used instead as their target.
347+
//
348+
// NOTE: Assumes that failure predecessors have already been processed and
349+
// removed from the current block's predecessors.
350+
351+
// Replace uses with SuccessArg from the dominating BB. Do this while it is
352+
// still a valid terminator result, before erasing the cast.
353+
OwnershipRAUWHelper rauwTransform(rauwContext, oldSuccessArg, SuccessArg);
354+
assert(rauwTransform.isValid() && "sufficiently checked by canRAUW");
355+
rauwTransform.perform();
356+
357+
eraseCheckedCastBr(checkedCastBr, CheckedCastBranchInst::SuccessIdx);
302358
return;
303359
}
304-
// There are no predecessors where it is not clear
305-
// if they are dominated by a success or failure branch
306-
// of DomBB. Therefore, there is no need to clone
307-
// the BB for SuccessPreds. Current BB can be re-used
308-
// instead as their target.
309-
310-
// Add an unconditional jump at the end of the block.
311-
// Take argument value from the dominating BB
312-
auto *CCBI = cast<CheckedCastBranchInst>(CCBBlock->getTerminator());
313-
SILBuilderWithScope(CCBI).createBranch(CCBI->getLoc(), CCBI->getSuccessBB(),
314-
{SuccessArg});
315-
CCBI->eraseFromParent();
360+
// Only clone if there are preds on the success path.
361+
if (SuccessPreds.empty())
362+
return;
363+
364+
// Create a copy of the BB as a landing BB.
365+
// for all SuccessPreds.
366+
assert(!Cloner.wasCloned());
367+
Cloner.cloneBlock();
368+
SILBasicBlock *clonedCCBBlock = Cloner.getNewBB();
369+
// Redirect all SuccessPreds to the copy of BB.
370+
for (auto *Pred : SuccessPreds) {
371+
TermInst *TI = Pred->getTerminator();
372+
// Replace branch to BB by branch to TargetSuccessBB.
373+
replaceBranchTarget(TI, CCBBlock, clonedCCBBlock,
374+
/*PreserveArgs=*/true);
375+
}
376+
// Remove the unreachable checked_cast_br target.
377+
auto *clonedCCBI =
378+
cast<CheckedCastBranchInst>(clonedCCBBlock->getTerminator());
379+
auto *successBB = clonedCCBI->getSuccessBB();
380+
// This cloned block branches to the successBB.
381+
// The checked_cast_br uses are replaced with SuccessArg.
382+
if (!CCBBlock->getParent()->hasOwnership()) {
383+
SILBuilderWithScope Builder(clonedCCBI);
384+
Builder.createBranch(clonedCCBI->getLoc(), successBB, {SuccessArg});
385+
clonedCCBI->eraseFromParent();
386+
Cloner.updateOSSAAfterCloning();
387+
return;
388+
}
389+
// Remove all uses from the failure path so RAUW can erase the
390+
// terminator after replacing the successor argument.
391+
auto *failureBB = clonedCCBI->getFailureBB();
392+
assert(failureBB->getNumArguments() == 1 && "expecting term result");
393+
failureBB->getArgument(0)->replaceAllUsesWithUndef();
394+
395+
// Create nested borrow scopes for new phis either created for the
396+
// checked_cast's results or during SSA update. This puts the SIL in
397+
// valid OSSA form before calling OwnershipRAUWHelper.
398+
Cloner.updateOSSAAfterCloning();
399+
400+
auto *clonedSuccessArg = successBB->getArgument(0);
401+
OwnershipRAUWHelper rauwUtil(rauwContext, clonedSuccessArg, SuccessArg);
402+
assert(rauwUtil.isValid() && "sufficiently checked by canRAUW");
403+
rauwUtil.perform();
404+
405+
eraseCheckedCastBr(clonedCCBI, CheckedCastBranchInst::SuccessIdx);
316406
}
317407

318408
/// Handle a special case, where ArgBB is the entry block.
319-
bool CheckedCastBrJumpThreading::handleArgBBIsEntryBlock(SILBasicBlock *ArgBB,
320-
CheckedCastBranchInst *DomCCBI) {
409+
bool CheckedCastBrJumpThreading::handleArgBBIsEntryBlock(
410+
SILBasicBlock *ArgBB, CheckedCastBranchInst *DomCCBI) {
321411
if (!ArgBB->pred_empty())
322412
return false;
323413

@@ -644,7 +734,8 @@ bool CheckedCastBrJumpThreading::trySimplify(CheckedCastBranchInst *CCBI) {
644734
// Record what we want to change.
645735
Edit *edit = new (EditAllocator.Allocate())
646736
Edit(BB, InvertSuccess, SuccessPreds, FailurePreds,
647-
numUnknownPreds != 0, DomCCBI->getSuccessBB()->getArgument(0));
737+
numUnknownPreds != 0,
738+
cast<SILPhiArgument>(DomCCBI->getSuccessBB()->getArgument(0)));
648739
Edits.push_back(edit);
649740

650741
return true;
@@ -684,12 +775,15 @@ void CheckedCastBrJumpThreading::optimizeFunction() {
684775
if (!Cloner.canCloneBlock())
685776
continue;
686777

778+
if (Fn->hasOwnership() && !edit->canRAUW(rauwContext))
779+
continue;
780+
687781
// Create a copy of the BB as a landing BB
688782
// for all FailurePreds.
689783
edit->modifyCFGForFailurePreds(Cloner);
690784
// Create a copy of the BB or reuse BB as
691785
// a landing basic block for all SuccessPreds.
692-
edit->modifyCFGForSuccessPreds(Cloner);
786+
edit->modifyCFGForSuccessPreds(Cloner, rauwContext);
693787

694788
if (Cloner.wasCloned()) {
695789
Cloner.updateOSSAAfterCloning();

lib/SILOptimizer/Utils/OwnershipOptUtils.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,8 +1464,13 @@ OwnershipRAUWHelper::perform(SILValue replacementValue) {
14641464
// Make sure to always clear our context after we transform.
14651465
SWIFT_DEFER { ctx->clear(); };
14661466

1467-
auto *svi = dyn_cast<SingleValueInstruction>(oldValue);
1468-
return replaceAllUsesAndErase(svi, replacementValue, ctx->callbacks);
1467+
if (auto *svi = dyn_cast<SingleValueInstruction>(oldValue))
1468+
return replaceAllUsesAndErase(svi, replacementValue, ctx->callbacks);
1469+
1470+
// The caller must rewrite the terminator after RAUW.
1471+
auto *term = cast<SILPhiArgument>(oldValue)->getTerminatorForResult();
1472+
auto nextII = term->getParent()->end();
1473+
return replaceAllUses(oldValue, replacementValue, nextII, ctx->callbacks);
14691474
}
14701475

14711476
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)