Skip to content

Commit d4837c4

Browse files
authored
Merge pull request #29703 from eeckstein/fix-partial-apply-combine
SILOptimizer: restructure the apply(partial_apply) peephole and the dead partial_apply elimination optimizations
2 parents d246512 + 8578936 commit d4837c4

18 files changed

+470
-595
lines changed

include/swift/SILOptimizer/Analysis/ARCAnalysis.h

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -392,47 +392,6 @@ class ConsumedArgToEpilogueReleaseMatcher {
392392
void processMatchingReleases();
393393
};
394394

395-
class ReleaseTracker {
396-
llvm::SmallSetVector<SILInstruction *, 4> TrackedUsers;
397-
llvm::SmallSetVector<SILInstruction *, 4> FinalReleases;
398-
std::function<bool(SILInstruction *)> AcceptableUserQuery;
399-
std::function<bool(SILInstruction *)> TransitiveUserQuery;
400-
401-
public:
402-
ReleaseTracker(std::function<bool(SILInstruction *)> AcceptableUserQuery,
403-
std::function<bool(SILInstruction *)> TransitiveUserQuery)
404-
: TrackedUsers(), FinalReleases(),
405-
AcceptableUserQuery(AcceptableUserQuery),
406-
TransitiveUserQuery(TransitiveUserQuery) {}
407-
408-
void trackLastRelease(SILInstruction *Inst) { FinalReleases.insert(Inst); }
409-
410-
bool isUserAcceptable(SILInstruction *User) const {
411-
return AcceptableUserQuery(User);
412-
}
413-
bool isUserTransitive(SILInstruction *User) const {
414-
return TransitiveUserQuery(User);
415-
}
416-
417-
bool isUser(SILInstruction *User) { return TrackedUsers.count(User); }
418-
419-
void trackUser(SILInstruction *User) { TrackedUsers.insert(User); }
420-
421-
using range = iterator_range<llvm::SmallSetVector<SILInstruction *, 4>::iterator>;
422-
423-
// An ordered list of users, with "casts" before their transitive uses.
424-
range getTrackedUsers() { return {TrackedUsers.begin(), TrackedUsers.end()}; }
425-
426-
range getFinalReleases() {
427-
return {FinalReleases.begin(), FinalReleases.end()};
428-
}
429-
};
430-
431-
/// Return true if we can find a set of post-dominating final releases. Returns
432-
/// false otherwise. The FinalRelease set is placed in the out parameter
433-
/// FinalRelease.
434-
bool getFinalReleasesForValue(SILValue Value, ReleaseTracker &Tracker);
435-
436395
/// Match a call to a trap BB with no ARC relevant side effects.
437396
bool isARCInertTrapBB(const SILBasicBlock *BB);
438397

include/swift/SILOptimizer/Utils/InstOptUtils.h

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,16 +304,37 @@ struct InstModCallbacks {
304304
InstModCallbacks(InstModCallbacks &&) = default;
305305
};
306306

307+
/// Get all consumed arguments of a partial_apply.
308+
///
309+
/// These are basically all arguments, except inout arguments and arguments
310+
/// of trivial type.
311+
/// If \p includeTrivialAddrArgs is true, also trivial address-type arguments
312+
/// are included.
313+
void getConsumedPartialApplyArgs(PartialApplyInst *pai,
314+
SmallVectorImpl<Operand *> &argOperands,
315+
bool includeTrivialAddrArgs);
316+
317+
/// Collect all (transitive) users of \p inst which just copy or destroy \p
318+
/// inst.
319+
///
320+
/// In other words: all users which do not prevent \p inst from being considered
321+
/// as "dead".
322+
/// Returns true, if there are no other users beside those collected in \p
323+
/// destroys, i.e. if \p inst can be considered as "dead".
324+
bool collectDestroys(SingleValueInstruction *inst,
325+
SmallVectorImpl<SILInstruction *> &destroys);
307326
/// If Closure is a partial_apply or thin_to_thick_function with only local
308327
/// ref count users and a set of post-dominating releases:
309328
///
310329
/// 1. Remove all ref count operations and the closure.
311-
/// 2. Add each one of the last release locations insert releases for the
312-
/// captured args if we have a partial_apply.
330+
/// 2. At each one of the last release locations insert releases for the
331+
/// captured args if we have a partial_apply (except \p needKeepArgsAlive is
332+
/// false).
313333
///
314334
/// In the future this should be extended to be less conservative with users.
315335
bool tryDeleteDeadClosure(SingleValueInstruction *closure,
316-
InstModCallbacks callbacks = InstModCallbacks());
336+
InstModCallbacks callbacks = InstModCallbacks(),
337+
bool needKeepArgsAlive = true);
317338

318339
/// Given a SILValue argument to a partial apply \p Arg and the associated
319340
/// parameter info for that argument, perform the necessary cleanups to Arg when
@@ -522,9 +543,9 @@ findLocalApplySites(FunctionRefBaseInst *fri);
522543
/// Gets the base implementation of a method.
523544
AbstractFunctionDecl *getBaseMethod(AbstractFunctionDecl *FD);
524545

525-
SILInstruction *
526-
tryOptimizeApplyOfPartialApply(PartialApplyInst *pai, SILBuilder &builder,
527-
InstModCallbacks callbacks = InstModCallbacks());
546+
bool tryOptimizeApplyOfPartialApply(
547+
PartialApplyInst *pai, SILBuilderContext &builderCtxt,
548+
InstModCallbacks callbacks = InstModCallbacks());
528549

529550
} // end namespace swift
530551

include/swift/SILOptimizer/Utils/ValueLifetime.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ class ValueLifetimeAnalysis {
7979
/// instructions of the frontier that are not in the critical edges. Note that
8080
/// the method getCriticalEdges can be used to retrieve the critical edges.
8181
///
82+
/// An edge is also considered as "critical" if it has a single precedessor
83+
/// but the predecessor's terminal instruction is a user of the value.
84+
///
8285
/// If \p deBlocks is provided, all dead-end blocks are ignored. This
8386
/// prevents unreachable-blocks to be included in the frontier.
8487
bool computeFrontier(Frontier &frontier, Mode mode,
@@ -132,6 +135,15 @@ class ValueLifetimeAnalysis {
132135
SILInstruction *findLastUserInBlock(SILBasicBlock *bb);
133136
};
134137

138+
/// Destroys \p valueOrStackLoc at \p frontier.
139+
///
140+
/// If \p valueOrStackLoc is an alloc_stack, inserts destroy_addr and
141+
/// dealloc_stack at each instruction of the \p frontier.
142+
/// Otherwise \p valueOrStackLoc must be a value type and in this case, inserts
143+
/// destroy_value at each instruction of the \p frontier.
144+
void endLifetimeAtFrontier(SILValue valueOrStackLoc,
145+
const ValueLifetimeAnalysis::Frontier &frontier,
146+
SILBuilderContext &builderCtxt);
135147

136148
} // end namespace swift
137149

lib/SILOptimizer/Analysis/ARCAnalysis.cpp

Lines changed: 0 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -987,131 +987,6 @@ findMatchingReleases(SILBasicBlock *BB) {
987987
processMatchingReleases();
988988
}
989989

990-
//===----------------------------------------------------------------------===//
991-
// Code for Determining Final Releases
992-
//===----------------------------------------------------------------------===//
993-
994-
// Propagate liveness backwards from an initial set of blocks in our
995-
// LiveIn set.
996-
static void propagateLiveness(llvm::SmallPtrSetImpl<SILBasicBlock *> &LiveIn,
997-
SILBasicBlock *DefBB) {
998-
// First populate a worklist of predecessors.
999-
llvm::SmallVector<SILBasicBlock *, 64> Worklist;
1000-
for (auto *BB : LiveIn)
1001-
for (auto Pred : BB->getPredecessorBlocks())
1002-
Worklist.push_back(Pred);
1003-
1004-
// Now propagate liveness backwards until we hit the alloc_box.
1005-
while (!Worklist.empty()) {
1006-
auto *BB = Worklist.pop_back_val();
1007-
1008-
// If it's already in the set, then we've already queued and/or
1009-
// processed the predecessors.
1010-
if (BB == DefBB || !LiveIn.insert(BB).second)
1011-
continue;
1012-
1013-
for (auto Pred : BB->getPredecessorBlocks())
1014-
Worklist.push_back(Pred);
1015-
}
1016-
}
1017-
1018-
// Is any successor of BB in the LiveIn set?
1019-
static bool successorHasLiveIn(SILBasicBlock *BB,
1020-
llvm::SmallPtrSetImpl<SILBasicBlock *> &LiveIn) {
1021-
for (auto &Succ : BB->getSuccessors())
1022-
if (LiveIn.count(Succ))
1023-
return true;
1024-
1025-
return false;
1026-
}
1027-
1028-
// Walk backwards in BB looking for the last use of a given
1029-
// value, and add it to the set of release points.
1030-
static bool addLastUse(SILValue V, SILBasicBlock *BB,
1031-
ReleaseTracker &Tracker) {
1032-
for (auto I = BB->rbegin(); I != BB->rend(); ++I) {
1033-
if (Tracker.isUser(&*I)) {
1034-
Tracker.trackLastRelease(&*I);
1035-
return true;
1036-
}
1037-
}
1038-
1039-
llvm_unreachable("BB is expected to have a use of a closure");
1040-
return false;
1041-
}
1042-
1043-
/// TODO: Refactor this code so the decision on whether or not to accept an
1044-
/// instruction.
1045-
bool swift::getFinalReleasesForValue(SILValue V, ReleaseTracker &Tracker) {
1046-
llvm::SmallPtrSet<SILBasicBlock *, 16> LiveIn;
1047-
llvm::SmallPtrSet<SILBasicBlock *, 16> UseBlocks;
1048-
1049-
// First attempt to get the BB where this value resides.
1050-
auto *DefBB = V->getParentBlock();
1051-
if (!DefBB)
1052-
return false;
1053-
1054-
bool seenRelease = false;
1055-
SILInstruction *OneRelease = nullptr;
1056-
1057-
// We'll treat this like a liveness problem where the value is the def. Each
1058-
// block that has a use of the value has the value live-in unless it is the
1059-
// block with the value.
1060-
SmallVector<Operand *, 8> Uses(V->getUses());
1061-
while (!Uses.empty()) {
1062-
auto *Use = Uses.pop_back_val();
1063-
auto *User = Use->getUser();
1064-
auto *BB = User->getParent();
1065-
1066-
if (Tracker.isUserTransitive(User)) {
1067-
Tracker.trackUser(User);
1068-
auto *CastInst = cast<SingleValueInstruction>(User);
1069-
Uses.append(CastInst->getUses().begin(), CastInst->getUses().end());
1070-
continue;
1071-
}
1072-
1073-
if (!Tracker.isUserAcceptable(User))
1074-
return false;
1075-
1076-
Tracker.trackUser(User);
1077-
1078-
if (BB != DefBB)
1079-
LiveIn.insert(BB);
1080-
1081-
// Also keep track of the blocks with uses.
1082-
UseBlocks.insert(BB);
1083-
1084-
// Try to speed up the trivial case of single release/dealloc.
1085-
if (isa<StrongReleaseInst>(User) || isa<DeallocBoxInst>(User) ||
1086-
isa<DestroyValueInst>(User) || isa<ReleaseValueInst>(User)) {
1087-
if (!seenRelease)
1088-
OneRelease = User;
1089-
else
1090-
OneRelease = nullptr;
1091-
1092-
seenRelease = true;
1093-
}
1094-
}
1095-
1096-
// Only a single release/dealloc? We're done!
1097-
if (OneRelease) {
1098-
Tracker.trackLastRelease(OneRelease);
1099-
return true;
1100-
}
1101-
1102-
propagateLiveness(LiveIn, DefBB);
1103-
1104-
// Now examine each block we saw a use in. If it has no successors
1105-
// that are in LiveIn, then the last use in the block is the final
1106-
// release/dealloc.
1107-
for (auto *BB : UseBlocks)
1108-
if (!successorHasLiveIn(BB, LiveIn))
1109-
if (!addLastUse(V, BB, Tracker))
1110-
return false;
1111-
1112-
return true;
1113-
}
1114-
1115990
//===----------------------------------------------------------------------===//
1116991
// Leaking BB Analysis
1117992
//===----------------------------------------------------------------------===//

lib/SILOptimizer/IPO/ClosureSpecializer.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,8 @@ void SILClosureSpecializerTransform::run() {
971971
// specialized all of their uses.
972972
LLVM_DEBUG(llvm::dbgs() << "Trying to remove dead closures!\n");
973973
sortUnique(PropagatedClosures);
974+
bool needUpdateStackNesting = false;
975+
974976
for (auto *Closure : PropagatedClosures) {
975977
LLVM_DEBUG(llvm::dbgs() << " Visiting: " << *Closure);
976978
if (!tryDeleteDeadClosure(Closure)) {
@@ -981,6 +983,11 @@ void SILClosureSpecializerTransform::run() {
981983

982984
LLVM_DEBUG(llvm::dbgs() << " Deleted closure!\n");
983985
++NumPropagatedClosuresEliminated;
986+
needUpdateStackNesting = true;
987+
}
988+
989+
if (needUpdateStackNesting) {
990+
StackNesting().correctStackNesting(F);
984991
}
985992
}
986993

lib/SILOptimizer/Mandatory/MandatoryCombine.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "swift/SILOptimizer/PassManager/Passes.h"
3333
#include "swift/SILOptimizer/PassManager/Transforms.h"
3434
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
35+
#include "swift/SILOptimizer/Utils/StackNesting.h"
3536
#include "llvm/ADT/STLExtras.h"
3637
#include "llvm/ADT/Statistic.h"
3738
#include "llvm/Support/raw_ostream.h"
@@ -69,6 +70,10 @@ class MandatoryCombiner final
6970
/// Whether any changes have been made.
7071
bool madeChange;
7172

73+
/// Set to true if some alloc/dealloc_stack instruction are inserted and at
74+
/// the end of the run stack nesting needs to be corrected.
75+
bool needUpdateStackNesting;
76+
7277
/// The number of times that the worklist has been processed.
7378
unsigned iteration;
7479

@@ -114,6 +119,10 @@ class MandatoryCombiner final
114119
++iteration;
115120
}
116121

122+
if (needUpdateStackNesting) {
123+
StackNesting().correctStackNesting(&function);
124+
}
125+
117126
return changed;
118127
}
119128

@@ -269,7 +278,9 @@ SILInstruction *MandatoryCombiner::visitApplyInst(ApplyInst *instruction) {
269278
/*instructionDescription=*/""
270279
#endif
271280
);
272-
tryDeleteDeadClosure(partialApply, instModCallbacks);
281+
if (tryDeleteDeadClosure(partialApply, instModCallbacks)) {
282+
needUpdateStackNesting = true;
283+
}
273284
return nullptr;
274285
}
275286

lib/SILOptimizer/Mandatory/MandatoryInlining.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,8 @@ static SILValue cleanupLoadedCalleeValue(SILValue calleeValue, LoadInst *li) {
342342

343343
/// Removes instructions that create the callee value if they are no
344344
/// longer necessary after inlining.
345-
static void cleanupCalleeValue(SILValue calleeValue) {
345+
static void cleanupCalleeValue(SILValue calleeValue,
346+
bool &needUpdateStackNesting) {
346347
// Handle the case where the callee of the apply is a load instruction. If we
347348
// fail to optimize, return. Otherwise, see if we can look through other
348349
// abstractions on our callee.
@@ -382,6 +383,7 @@ static void cleanupCalleeValue(SILValue calleeValue) {
382383
return;
383384
calleeValue = callee;
384385
}
386+
needUpdateStackNesting = true;
385387

386388
calleeValue = stripCopiesAndBorrows(calleeValue);
387389

@@ -446,6 +448,10 @@ class ClosureCleanup {
446448
SmallBlotSetVector<SILInstruction *, 4> deadFunctionVals;
447449

448450
public:
451+
/// Set to true if some alloc/dealloc_stack instruction are inserted and at
452+
/// the end of the run stack nesting needs to be corrected.
453+
bool needUpdateStackNesting = false;
454+
449455
/// This regular instruction deletion callback checks for any function-type
450456
/// values that may be unused after deleting the given instruction.
451457
void recordDeadFunction(SILInstruction *deletedInst) {
@@ -475,7 +481,7 @@ class ClosureCleanup {
475481
continue;
476482

477483
if (auto *SVI = dyn_cast<SingleValueInstruction>(I.getValue()))
478-
cleanupCalleeValue(SVI);
484+
cleanupCalleeValue(SVI, needUpdateStackNesting);
479485
}
480486
}
481487
};
@@ -938,6 +944,7 @@ runOnFunctionRecursively(SILOptFunctionBuilder &FuncBuilder,
938944
// we may be able to remove dead callee computations (e.g. dead
939945
// partial_apply closures).
940946
closureCleanup.cleanupDeadClosures(F);
947+
needUpdateStackNesting |= closureCleanup.needUpdateStackNesting;
941948

942949
// Resume inlining within nextBB, which contains only the inlined
943950
// instructions and possibly instructions in the original call block that

lib/SILOptimizer/SILCombiner/SILCombine.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "swift/SILOptimizer/Utils/CanonicalizeInstruction.h"
3131
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
3232
#include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h"
33+
#include "swift/SILOptimizer/Utils/StackNesting.h"
3334
#include "llvm/ADT/SmallPtrSet.h"
3435
#include "llvm/ADT/SmallVector.h"
3536
#include "llvm/ADT/Statistic.h"
@@ -229,6 +230,10 @@ bool SILCombiner::runOnFunction(SILFunction &F) {
229230
Iteration++;
230231
}
231232

233+
if (needUpdateStackNesting) {
234+
StackNesting().correctStackNesting(&F);
235+
}
236+
232237
// Cleanup the builder and return whether or not we made any changes.
233238
return Changed;
234239
}

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ class SILCombiner :
6565
/// If set to true then the optimizer is free to erase cond_fail instructions.
6666
bool RemoveCondFails;
6767

68+
/// Set to true if some alloc/dealloc_stack instruction are inserted and at
69+
/// the end of the run stack nesting needs to be corrected.
70+
bool needUpdateStackNesting = false;
71+
6872
/// The current iteration of the SILCombine.
6973
unsigned Iteration;
7074

0 commit comments

Comments
 (0)