Skip to content

Commit 7fd9607

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 7d83764 commit 7fd9607

File tree

4 files changed

+1432
-6
lines changed

4 files changed

+1432
-6
lines changed

include/swift/SIL/FieldSensitivePrunedLiveness.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ struct TypeTreeLeafTypeRange {
350350
// projections of a single address.
351351
// Note: The new length may be less than the original lengths sums because
352352
// they might overlap.
353+
// rdar://110676577
353354
assert(newLength <= copyLength + otherLength &&
354355
"extended range into non-contiguous range!?");
355356
#endif

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 315 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,246 @@ 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+
originalLiveBlocks.insert(block);
2795+
for (auto *predecessor : block->getPredecessorBlocks()) {
2796+
// If the block was discovered by liveness, we already added it to the
2797+
// set.
2798+
if (originalLiveBlocks.contains(predecessor))
2799+
continue;
2800+
worklist.pushIfNotVisited(predecessor);
2801+
}
2802+
}
2803+
}
2804+
2805+
// Second, collect the blocks which occur after a consuming use.
2806+
BasicBlockSet consumedAtExitBlocks(currentDef->getFunction());
2807+
BasicBlockSetVector consumedAtEntryBlocks(currentDef->getFunction());
2808+
{
2809+
// Start the forward walk from blocks which contain non-destroy consumes not
2810+
// followed by defs.
2811+
//
2812+
// Because they contain a consume not followed by a def, these are
2813+
// consumed-at-exit.
2814+
BasicBlockWorklist worklist(currentDef->getFunction());
2815+
for (auto iterator : boundary.getLastUsers()) {
2816+
if (!iterator.second.test(element))
2817+
continue;
2818+
auto *instruction = iterator.first;
2819+
// Skip over destroys on the boundary.
2820+
auto iter = destroys.find(instruction);
2821+
if (iter != destroys.end() && iter->second != DestroyKind::Take) {
2822+
continue;
2823+
}
2824+
// Skip over non-consuming users.
2825+
auto pair = liveness.isInterestingUser(instruction);
2826+
assert(pair.first !=
2827+
FieldSensitivePrunedLiveness::IsInterestingUser::NonUser);
2828+
if (pair.first !=
2829+
FieldSensitivePrunedLiveness::IsInterestingUser::LifetimeEndingUse) {
2830+
continue;
2831+
}
2832+
// Skip over users that aren't users of this element.
2833+
assert(pair.second.has_value());
2834+
if (!pair.second->contains(element)) {
2835+
continue;
2836+
}
2837+
// Skip over local live ranges.
2838+
if (hasDefAfter(instruction, element))
2839+
continue;
2840+
worklist.push(instruction->getParent());
2841+
}
2842+
while (auto *block = worklist.pop()) {
2843+
consumedAtExitBlocks.insert(block);
2844+
for (auto *successor : block->getSuccessorBlocks()) {
2845+
if (!originalLiveBlocks.contains(successor))
2846+
continue;
2847+
worklist.pushIfNotVisited(successor);
2848+
consumedAtEntryBlocks.insert(successor);
2849+
}
2850+
}
2851+
}
2852+
2853+
// Third, find the blocks on the boundary between the originally-live blocks
2854+
// and the originally-live-but-consumed blocks. Extend liveness "to the end"
2855+
// of these blocks.
2856+
for (auto *block : consumedAtEntryBlocks) {
2857+
for (auto *predecessor : block->getPredecessorBlocks()) {
2858+
if (consumedAtExitBlocks.contains(predecessor))
2859+
continue;
2860+
// Add "the instruction(s) before the terminator" of the predecessor to
2861+
// liveness.
2862+
addPreviousInstructionToLiveness(predecessor->getTerminator(), element,
2863+
/*lifetimeEnding*/ false);
2864+
}
2865+
}
2866+
2867+
// Finally, preserve the destroys which weren't in the consumed region in
2868+
// place: hoisting such destroys would not avoid copies.
2869+
for (auto pair : destroys) {
2870+
auto *destroy = pair.first;
2871+
if (!shouldAddDestroyToLiveness(destroy, element, consumedAtExitBlocks,
2872+
consumedAtEntryBlocks))
2873+
continue;
2874+
addPreviousInstructionToLiveness(destroy, element,
2875+
/*lifetimeEnding*/ false);
2876+
}
2877+
}
2878+
2879+
bool ExtendUnconsumedLiveness::shouldAddDestroyToLiveness(
2880+
SILInstruction *destroy, unsigned element,
2881+
BasicBlockSet const &consumedAtExitBlocks,
2882+
BasicBlockSetVector const &consumedAtEntryBlocks) {
2883+
auto *block = destroy->getParent();
2884+
bool followedByDef = hasDefAfter(destroy, element);
2885+
if (!followedByDef) {
2886+
// This destroy is the last write to the field in the block.
2887+
//
2888+
// If the block is consumed-at-exit, then there is some other consuming use
2889+
// before this destroy. Liveness can't be extended.
2890+
return !consumedAtExitBlocks.contains(block);
2891+
}
2892+
for (auto *inst = destroy->getPreviousInstruction(); inst;
2893+
inst = inst->getPreviousInstruction()) {
2894+
if (liveness.isDef(inst, element)) {
2895+
// Found the corresponding def with no intervening users. Liveness
2896+
// can be extended to the destroy.
2897+
return true;
2898+
}
2899+
auto pair = liveness.isInterestingUser(inst);
2900+
switch (pair.first) {
2901+
case FieldSensitivePrunedLiveness::IsInterestingUser::NonUser:
2902+
break;
2903+
case FieldSensitivePrunedLiveness::IsInterestingUser::NonLifetimeEndingUse:
2904+
if (pair.second.has_value() && pair.second->contains(element)) {
2905+
// The first use seen is non-consuming. Liveness can be extended to the
2906+
// destroy.
2907+
return true;
2908+
}
2909+
break;
2910+
case FieldSensitivePrunedLiveness::IsInterestingUser::LifetimeEndingUse:
2911+
if (pair.second.has_value() && pair.second->contains(element)) {
2912+
// Found a consuming use. Liveness can't be extended to the destroy
2913+
// (without creating a copy and triggering a diagnostic).
2914+
return false;
2915+
}
2916+
break;
2917+
}
2918+
}
2919+
// Found no uses or defs between the destroy and the top of the block. If the
2920+
// block was not consumed at entry, liveness can be extended to the destroy.
2921+
return !consumedAtEntryBlocks.contains(block);
2922+
}
2923+
2924+
bool ExtendUnconsumedLiveness::hasDefAfter(SILInstruction *start,
2925+
unsigned element) {
2926+
// NOTE: Start iteration at \p start, not its sequel, because
2927+
// it might be both a consuming use and a def.
2928+
for (auto *inst = start; inst; inst = inst->getNextInstruction()) {
2929+
if (liveness.isDef(inst, element))
2930+
return true;
2931+
}
2932+
return false;
2933+
}
2934+
2935+
void ExtendUnconsumedLiveness::addPreviousInstructionToLiveness(
2936+
SILInstruction *inst, unsigned element, bool lifetimeEnding) {
2937+
auto range = TypeTreeLeafTypeRange(element);
2938+
if (auto *previous = inst->getPreviousInstruction()) {
2939+
liveness.updateForUse(previous, range, lifetimeEnding);
2940+
} else {
2941+
for (auto *predecessor : inst->getParent()->getPredecessorBlocks()) {
2942+
liveness.updateForUse(predecessor->getTerminator(), range,
2943+
lifetimeEnding);
2944+
}
2945+
}
2946+
}
2947+
26372948
bool MoveOnlyAddressCheckerPImpl::performSingleCheck(
26382949
MarkMustCheckInst *markedAddress) {
26392950
SWIFT_DEFER { diagnosticEmitter.clearUsesWithDiagnostic(); };
@@ -2728,6 +3039,10 @@ bool MoveOnlyAddressCheckerPImpl::performSingleCheck(
27283039

27293040
FieldSensitivePrunedLivenessBoundary boundary(liveness.getNumSubElements());
27303041
liveness.computeBoundary(boundary);
3042+
ExtendUnconsumedLiveness extension(addressUseState, liveness, boundary);
3043+
extension.run();
3044+
boundary.clear();
3045+
liveness.computeBoundary(boundary);
27313046
insertDestroysOnBoundary(markedAddress, liveness, boundary);
27323047
rewriteUses(markedAddress, liveness, boundary);
27333048

0 commit comments

Comments
 (0)