Skip to content

Commit 6c2ba68

Browse files
committed
[MoveOnlyAddressChecker] Maximize lifetimes.
Previously, the checker inserted destroys after each last use. Here, extend the lifetimes of fields as far as possible within their original (unchecked) limits. rdar://99681073
1 parent 36c28cc commit 6c2ba68

7 files changed

+1815
-28
lines changed

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,15 @@ namespace {
634634
struct UseState {
635635
MarkMustCheckInst *address;
636636

637+
/// The number of fields in the exploded type. Set in initializeLiveness.
638+
unsigned fieldCount = UINT_MAX;
639+
640+
/// The blocks that consume fields of the value.
641+
///
642+
/// A map from blocks to a bit vector recording which fields were destroyed
643+
/// in each.
644+
llvm::SmallMapVector<SILBasicBlock *, SmallBitVector, 8> consumingBlocks;
645+
637646
/// A map from destroy_addr to the part of the type that it destroys.
638647
llvm::SmallMapVector<SILInstruction *, TypeTreeLeafTypeRange, 4> destroys;
639648

@@ -742,6 +751,7 @@ struct UseState {
742751

743752
void clear() {
744753
address = nullptr;
754+
consumingBlocks.clear();
745755
destroys.clear();
746756
livenessUses.clear();
747757
borrows.clear();
@@ -798,6 +808,16 @@ struct UseState {
798808
}
799809
}
800810

811+
void recordConsumingBlock(SILBasicBlock *block, TypeTreeLeafTypeRange range) {
812+
if (consumingBlocks.find(block) == consumingBlocks.end()) {
813+
consumingBlocks.insert({block, SmallBitVector(fieldCount)});
814+
}
815+
auto &vector = consumingBlocks[block];
816+
for (auto field : range.getRange()) {
817+
vector.set(field);
818+
}
819+
}
820+
801821
void
802822
initializeLiveness(FieldSensitiveMultiDefPrunedLiveRange &prunedLiveness);
803823

@@ -899,6 +919,8 @@ struct UseState {
899919

900920
void UseState::initializeLiveness(
901921
FieldSensitiveMultiDefPrunedLiveRange &liveness) {
922+
fieldCount = liveness.getNumSubElements();
923+
902924
// We begin by initializing all of our init uses.
903925
for (auto initInstAndValue : initInsts) {
904926
LLVM_DEBUG(llvm::dbgs() << "Found def: " << *initInstAndValue.first);
@@ -1005,6 +1027,8 @@ void UseState::initializeLiveness(
10051027
if (!isReinitToInitConvertibleInst(reinitInstAndValue.first)) {
10061028
liveness.updateForUse(reinitInstAndValue.first, reinitInstAndValue.second,
10071029
false /*lifetime ending*/);
1030+
recordConsumingBlock(reinitInstAndValue.first->getParent(),
1031+
reinitInstAndValue.second);
10081032
LLVM_DEBUG(llvm::dbgs() << "Added liveness for reinit: "
10091033
<< *reinitInstAndValue.first;
10101034
liveness.print(llvm::dbgs()));
@@ -1016,18 +1040,27 @@ void UseState::initializeLiveness(
10161040
for (auto takeInstAndValue : takeInsts) {
10171041
liveness.updateForUse(takeInstAndValue.first, takeInstAndValue.second,
10181042
true /*lifetime ending*/);
1043+
recordConsumingBlock(takeInstAndValue.first->getParent(),
1044+
takeInstAndValue.second);
10191045
LLVM_DEBUG(llvm::dbgs()
10201046
<< "Added liveness for take: " << *takeInstAndValue.first;
10211047
liveness.print(llvm::dbgs()));
10221048
}
10231049
for (auto copyInstAndValue : copyInsts) {
10241050
liveness.updateForUse(copyInstAndValue.first, copyInstAndValue.second,
10251051
true /*lifetime ending*/);
1052+
recordConsumingBlock(copyInstAndValue.first->getParent(),
1053+
copyInstAndValue.second);
10261054
LLVM_DEBUG(llvm::dbgs()
10271055
<< "Added liveness for copy: " << *copyInstAndValue.first;
10281056
liveness.print(llvm::dbgs()));
10291057
}
10301058

1059+
for (auto destroyInstAndValue : destroys) {
1060+
recordConsumingBlock(destroyInstAndValue.first->getParent(),
1061+
destroyInstAndValue.second);
1062+
}
1063+
10311064
// Do the same for our borrow and liveness insts.
10321065
for (auto livenessInstAndValue : borrows) {
10331066
liveness.updateForUse(livenessInstAndValue.first,
@@ -1303,6 +1336,44 @@ struct MoveOnlyAddressCheckerPImpl {
13031336
void handleSingleBlockDestroy(SILInstruction *destroy, bool isReinit);
13041337
};
13051338

1339+
class ExtendUnconsumedLiveness {
1340+
UseState addressUseState;
1341+
FieldSensitiveMultiDefPrunedLiveRange &liveness;
1342+
FieldSensitivePrunedLivenessBoundary &boundary;
1343+
1344+
enum class DestroyKind {
1345+
Destroy,
1346+
Take,
1347+
Reinit,
1348+
};
1349+
using DestroysCollection =
1350+
llvm::SmallMapVector<SILInstruction *, DestroyKind, 8>;
1351+
using ConsumingBlocksCollection = SmallPtrSetVector<SILBasicBlock *, 8>;
1352+
1353+
public:
1354+
ExtendUnconsumedLiveness(UseState addressUseState,
1355+
FieldSensitiveMultiDefPrunedLiveRange &liveness,
1356+
FieldSensitivePrunedLivenessBoundary &boundary)
1357+
: addressUseState(addressUseState), liveness(liveness),
1358+
boundary(boundary) {}
1359+
1360+
void run();
1361+
1362+
void runOnField(unsigned element, DestroysCollection &destroys,
1363+
ConsumingBlocksCollection &consumingBlocks);
1364+
1365+
private:
1366+
bool hasDefAfter(SILInstruction *inst, unsigned element);
1367+
1368+
bool
1369+
shouldAddDestroyToLiveness(SILInstruction *destroy, unsigned element,
1370+
BasicBlockSet const &consumedAtExitBlocks,
1371+
BasicBlockSetVector const &consumedAtEntryBlocks);
1372+
1373+
void addPreviousInstructionToLiveness(SILInstruction *inst, unsigned element,
1374+
bool lifetimeEnding);
1375+
};
1376+
13061377
} // namespace
13071378

13081379
//===----------------------------------------------------------------------===//
@@ -2634,6 +2705,249 @@ void MoveOnlyAddressCheckerPImpl::rewriteUses(
26342705
#endif
26352706
}
26362707

2708+
void ExtendUnconsumedLiveness::run() {
2709+
ConsumingBlocksCollection consumingBlocks;
2710+
DestroysCollection destroys;
2711+
for (unsigned element = 0, count = liveness.getNumSubElements();
2712+
element < count; ++element) {
2713+
2714+
for (auto pair : addressUseState.consumingBlocks) {
2715+
if (pair.second.test(element)) {
2716+
consumingBlocks.insert(pair.first);
2717+
}
2718+
}
2719+
2720+
for (auto pair : addressUseState.destroys) {
2721+
if (pair.second.contains(element)) {
2722+
destroys[pair.first] = DestroyKind::Destroy;
2723+
}
2724+
}
2725+
for (auto pair : addressUseState.takeInsts) {
2726+
if (pair.second.contains(element)) {
2727+
destroys[pair.first] = DestroyKind::Take;
2728+
}
2729+
}
2730+
for (auto pair : addressUseState.reinitInsts) {
2731+
if (pair.second.contains(element)) {
2732+
destroys[pair.first] = DestroyKind::Reinit;
2733+
}
2734+
}
2735+
2736+
runOnField(element, destroys, consumingBlocks);
2737+
2738+
consumingBlocks.clear();
2739+
destroys.clear();
2740+
}
2741+
}
2742+
2743+
/// Extend liveness of each field as far as possible within the original live
2744+
/// range as far as possible without incurring any copies.
2745+
///
2746+
/// The strategy has two parts.
2747+
///
2748+
/// (1) The global analysis:
2749+
/// - Collect the blocks in which the field was live before canonicalization.
2750+
/// These are the "original" live blocks (originalLiveBlocks).
2751+
/// [Color these blocks green.]
2752+
/// - From within that collection, collect the blocks which contain a _final_
2753+
/// consuming, non-destroy use, and their iterative successors.
2754+
/// These are the "consumed" blocks (consumedAtExitBlocks).
2755+
/// [Color these blocks red.]
2756+
/// - Extend liveness down to the boundary between originalLiveBlocks and
2757+
/// consumedAtExitBlocks blocks.
2758+
/// [Extend liveness down to the boundary between green blocks and red.]
2759+
/// - In particular, in regions of originalLiveBlocks which have no boundary
2760+
/// with consumedAtExitBlocks, liveness should be extended to its original
2761+
/// extent.
2762+
/// [Extend liveness down to the boundary between green blocks and uncolored.]
2763+
///
2764+
/// (2) The local analysis:
2765+
/// - For in-block lifetimes, extend liveness forward from non-consuming uses
2766+
/// and dead defs to the original destroy.
2767+
void ExtendUnconsumedLiveness::runOnField(
2768+
unsigned element, DestroysCollection &destroys,
2769+
ConsumingBlocksCollection &consumingBlocks) {
2770+
SILValue currentDef = addressUseState.address;
2771+
2772+
// First, collect the blocks that were _originally_ live. We can't use
2773+
// liveness here because it doesn't include blocks that occur before a
2774+
// destroy_addr.
2775+
BasicBlockSet originalLiveBlocks(currentDef->getFunction());
2776+
{
2777+
// Some of the work here was already done by initializeLiveness.
2778+
// Specifically, it already discovered all blocks containing (transitive)
2779+
// uses and blocks that appear between them and the def.
2780+
//
2781+
// Seed the set with what it already discovered.
2782+
for (auto *discoveredBlock : liveness.getDiscoveredBlocks())
2783+
originalLiveBlocks.insert(discoveredBlock);
2784+
2785+
// Start the walk from the consuming blocks (which includes destroys as well
2786+
// as the other consuming uses).
2787+
BasicBlockWorklist worklist(currentDef->getFunction());
2788+
for (auto *consumingBlock : consumingBlocks) {
2789+
worklist.push(consumingBlock);
2790+
}
2791+
2792+
// Walk backwards from consuming blocks.
2793+
while (auto *block = worklist.pop()) {
2794+
if (!originalLiveBlocks.insert(block)) {
2795+
continue;
2796+
}
2797+
for (auto *predecessor : block->getPredecessorBlocks()) {
2798+
// If the block was discovered by liveness, we already added it to the
2799+
// set.
2800+
if (originalLiveBlocks.contains(predecessor))
2801+
continue;
2802+
worklist.pushIfNotVisited(predecessor);
2803+
}
2804+
}
2805+
}
2806+
2807+
// Second, collect the blocks which occur after a consuming use.
2808+
BasicBlockSet consumedAtExitBlocks(currentDef->getFunction());
2809+
BasicBlockSetVector consumedAtEntryBlocks(currentDef->getFunction());
2810+
{
2811+
// Start the forward walk from blocks which contain non-destroy consumes not
2812+
// followed by defs.
2813+
//
2814+
// Because they contain a consume not followed by a def, these are
2815+
// consumed-at-exit.
2816+
BasicBlockWorklist worklist(currentDef->getFunction());
2817+
for (auto iterator : boundary.getLastUsers()) {
2818+
if (!iterator.second.test(element))
2819+
continue;
2820+
auto *instruction = iterator.first;
2821+
// Skip over destroys on the boundary.
2822+
auto iter = destroys.find(instruction);
2823+
if (iter != destroys.end() && iter->second != DestroyKind::Take) {
2824+
continue;
2825+
}
2826+
// Skip over non-consuming users.
2827+
auto pair = liveness.isInterestingUser(instruction);
2828+
assert(pair.first !=
2829+
FieldSensitivePrunedLiveness::IsInterestingUser::NonUser);
2830+
if (pair.first !=
2831+
FieldSensitivePrunedLiveness::IsInterestingUser::LifetimeEndingUse) {
2832+
continue;
2833+
}
2834+
// Skip over users that aren't users of this element.
2835+
assert(pair.second.has_value());
2836+
if (!pair.second->contains(element)) {
2837+
continue;
2838+
}
2839+
// A consume with a subsequent def doesn't cause the block to be
2840+
// consumed-at-exit.
2841+
if (hasDefAfter(instruction, element))
2842+
continue;
2843+
worklist.push(instruction->getParent());
2844+
}
2845+
while (auto *block = worklist.pop()) {
2846+
consumedAtExitBlocks.insert(block);
2847+
for (auto *successor : block->getSuccessorBlocks()) {
2848+
if (!originalLiveBlocks.contains(successor))
2849+
continue;
2850+
worklist.pushIfNotVisited(successor);
2851+
consumedAtEntryBlocks.insert(successor);
2852+
}
2853+
}
2854+
}
2855+
2856+
// Third, find the blocks on the boundary between the originally-live blocks
2857+
// and the originally-live-but-consumed blocks. Extend liveness "to the end"
2858+
// of these blocks.
2859+
for (auto *block : consumedAtEntryBlocks) {
2860+
for (auto *predecessor : block->getPredecessorBlocks()) {
2861+
if (consumedAtExitBlocks.contains(predecessor))
2862+
continue;
2863+
// Add "the instruction(s) before the terminator" of the predecessor to
2864+
// liveness.
2865+
addPreviousInstructionToLiveness(predecessor->getTerminator(), element,
2866+
/*lifetimeEnding*/ false);
2867+
}
2868+
}
2869+
2870+
// Finally, preserve the destroys which weren't in the consumed region in
2871+
// place: hoisting such destroys would not avoid copies.
2872+
for (auto pair : destroys) {
2873+
auto *destroy = pair.first;
2874+
if (!shouldAddDestroyToLiveness(destroy, element, consumedAtExitBlocks,
2875+
consumedAtEntryBlocks))
2876+
continue;
2877+
addPreviousInstructionToLiveness(destroy, element,
2878+
/*lifetimeEnding*/ false);
2879+
}
2880+
}
2881+
2882+
bool ExtendUnconsumedLiveness::shouldAddDestroyToLiveness(
2883+
SILInstruction *destroy, unsigned element,
2884+
BasicBlockSet const &consumedAtExitBlocks,
2885+
BasicBlockSetVector const &consumedAtEntryBlocks) {
2886+
auto *block = destroy->getParent();
2887+
bool followedByDef = hasDefAfter(destroy, element);
2888+
if (!followedByDef) {
2889+
// This destroy is the last write to the field in the block.
2890+
//
2891+
// If the block is consumed-at-exit, then there is some other consuming use
2892+
// before this destroy. Liveness can't be extended.
2893+
return !consumedAtExitBlocks.contains(block);
2894+
}
2895+
for (auto *inst = destroy->getPreviousInstruction(); inst;
2896+
inst = inst->getPreviousInstruction()) {
2897+
if (liveness.isDef(inst, element)) {
2898+
// Found the corresponding def with no intervening users. Liveness
2899+
// can be extended to the destroy.
2900+
return true;
2901+
}
2902+
auto pair = liveness.isInterestingUser(inst);
2903+
switch (pair.first) {
2904+
case FieldSensitivePrunedLiveness::IsInterestingUser::NonUser:
2905+
break;
2906+
case FieldSensitivePrunedLiveness::IsInterestingUser::NonLifetimeEndingUse:
2907+
if (pair.second.has_value() && pair.second->contains(element)) {
2908+
// The first use seen is non-consuming. Liveness can be extended to the
2909+
// destroy.
2910+
return true;
2911+
}
2912+
break;
2913+
case FieldSensitivePrunedLiveness::IsInterestingUser::LifetimeEndingUse:
2914+
if (pair.second.has_value() && pair.second->contains(element)) {
2915+
// Found a consuming use. Liveness can't be extended to the destroy
2916+
// (without creating a copy and triggering a diagnostic).
2917+
return false;
2918+
}
2919+
break;
2920+
}
2921+
}
2922+
// Found no uses or defs between the destroy and the top of the block. If the
2923+
// block was not consumed at entry, liveness can be extended to the destroy.
2924+
return !consumedAtEntryBlocks.contains(block);
2925+
}
2926+
2927+
bool ExtendUnconsumedLiveness::hasDefAfter(SILInstruction *start,
2928+
unsigned element) {
2929+
// NOTE: Start iteration at \p start, not its sequel, because
2930+
// it might be both a consuming use and a def.
2931+
for (auto *inst = start; inst; inst = inst->getNextInstruction()) {
2932+
if (liveness.isDef(inst, element))
2933+
return true;
2934+
}
2935+
return false;
2936+
}
2937+
2938+
void ExtendUnconsumedLiveness::addPreviousInstructionToLiveness(
2939+
SILInstruction *inst, unsigned element, bool lifetimeEnding) {
2940+
auto range = TypeTreeLeafTypeRange(element);
2941+
if (auto *previous = inst->getPreviousInstruction()) {
2942+
liveness.updateForUse(previous, range, lifetimeEnding);
2943+
} else {
2944+
for (auto *predecessor : inst->getParent()->getPredecessorBlocks()) {
2945+
liveness.updateForUse(predecessor->getTerminator(), range,
2946+
lifetimeEnding);
2947+
}
2948+
}
2949+
}
2950+
26372951
bool MoveOnlyAddressCheckerPImpl::performSingleCheck(
26382952
MarkMustCheckInst *markedAddress) {
26392953
SWIFT_DEFER { diagnosticEmitter.clearUsesWithDiagnostic(); };
@@ -2728,6 +3042,10 @@ bool MoveOnlyAddressCheckerPImpl::performSingleCheck(
27283042

27293043
FieldSensitivePrunedLivenessBoundary boundary(liveness.getNumSubElements());
27303044
liveness.computeBoundary(boundary);
3045+
ExtendUnconsumedLiveness extension(addressUseState, liveness, boundary);
3046+
extension.run();
3047+
boundary.clear();
3048+
liveness.computeBoundary(boundary);
27313049
insertDestroysOnBoundary(markedAddress, liveness, boundary);
27323050
rewriteUses(markedAddress, liveness, boundary);
27333051

0 commit comments

Comments
 (0)