Skip to content

Commit 3dd3255

Browse files
authored
Merge pull request #30741 from gottesmm/pr-961dc8ea62390b2c78cdfafd88f3d04ebfcb7ade
[arc-opts] Teach IsAddressWrittenToDefUseAnalysis how to track "well behaved" writes but don't use it.
2 parents f4006c3 + 3eda1ea commit 3dd3255

File tree

3 files changed

+89
-77
lines changed

3 files changed

+89
-77
lines changed

include/swift/Basic/MultiMapCache.h

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,40 +18,35 @@
1818

1919
namespace swift {
2020

21-
/// A CRTP write-once multi-map cache that can be small. It uses a DenseMap
21+
/// A write-once multi-map cache that can be small. It uses a DenseMap
2222
/// internally, so it can be used as a cache without needing to be frozen like
23-
/// FrozenMultiMap (which uses only a vector internally). The Impl class
24-
/// implements the method constructValuesForKey that is used to compute the
25-
/// actual cache value.
26-
///
27-
/// NOTE: constructValuesForKeys is assumed to take a KeyTy and a
28-
/// SmallVectorImpl<ValueTy>. It must append all results to that accumulator and
29-
/// not read any contents of the accumulator.
23+
/// FrozenMultiMap (which uses only a vector internally). The cached value is
24+
/// computed by a passed in std::function. The std::function is able to map
25+
/// multiple values to a specific key via the out array.
3026
///
3127
/// NOTE: We store the (size, length) of each ArrayRef<ValueTy> instead of
3228
/// storing the ArrayRef to avoid data invalidation issues caused by SmallVector
3329
/// switching from small to large representations.
3430
///
3531
/// For an example of a subclass implementation see:
3632
/// unittests/Basic/MultiMapCacheTest.cpp.
37-
template <typename ImplType, typename KeyTy, typename ValueTy,
33+
template <typename KeyTy, typename ValueTy,
3834
typename MapTy =
3935
llvm::DenseMap<KeyTy, Optional<std::tuple<unsigned, unsigned>>>,
4036
typename VectorTy = std::vector<ValueTy>,
4137
typename VectorTyImpl = VectorTy>
4238
class MultiMapCache {
39+
std::function<bool(const KeyTy &, VectorTyImpl &)> function;
4340
MapTy valueToDataOffsetIndexMap;
4441
VectorTy data;
4542

4643
constexpr static unsigned ArrayStartOffset = 0;
4744
constexpr static unsigned ArrayLengthOffset = 1;
4845

49-
constexpr ImplType &asImpl() const {
50-
auto *self = const_cast<MultiMapCache *>(this);
51-
return reinterpret_cast<ImplType &>(*self);
52-
}
53-
5446
public:
47+
MultiMapCache(std::function<bool(const KeyTy &, VectorTyImpl &)> function)
48+
: function(function) {}
49+
5550
void clear() {
5651
valueToDataOffsetIndexMap.clear();
5752
data.clear();
@@ -81,7 +76,7 @@ class MultiMapCache {
8176

8277
// We assume that constructValuesForKey /only/ inserts to the end of data
8378
// and does not inspect any other values in the data array.
84-
if (!asImpl().constructValuesForKey(key, data)) {
79+
if (!function(key, data)) {
8580
return None;
8681
}
8782

@@ -94,9 +89,9 @@ class MultiMapCache {
9489
}
9590
};
9691

97-
template <typename ImplType, typename KeyTy, typename ValueTy>
92+
template <typename KeyTy, typename ValueTy>
9893
using SmallMultiMapCache = MultiMapCache<
99-
ImplType, KeyTy, ValueTy,
94+
KeyTy, ValueTy,
10095
llvm::SmallDenseMap<KeyTy, Optional<std::tuple<unsigned, unsigned>>, 8>,
10196
SmallVector<ValueTy, 32>, SmallVectorImpl<ValueTy>>;
10297

lib/SILOptimizer/Transforms/SemanticARCOpts.cpp

Lines changed: 37 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#define DEBUG_TYPE "sil-semantic-arc-opts"
1414
#include "swift/Basic/BlotSetVector.h"
1515
#include "swift/Basic/FrozenMultiMap.h"
16+
#include "swift/Basic/MultiMapCache.h"
1617
#include "swift/Basic/STLExtras.h"
1718
#include "swift/SIL/BasicBlockUtils.h"
1819
#include "swift/SIL/DebugUtils.h"
@@ -477,35 +478,13 @@ LiveRange::hasUnknownConsumingUse(bool assumingAtFixPoint) const {
477478
// Address Written To Analysis
478479
//===----------------------------------------------------------------------===//
479480

480-
namespace {
481-
482-
/// A simple analysis that checks if a specific def (in our case an inout
483-
/// argument) is ever written to. This is conservative, local and processes
484-
/// recursively downwards from def->use.
485-
struct IsAddressWrittenToDefUseAnalysis {
486-
llvm::SmallDenseMap<SILValue, bool, 8> isWrittenToCache;
487-
488-
bool operator()(SILValue value) {
489-
auto iter = isWrittenToCache.try_emplace(value, true);
490-
491-
// If we are already in the map, just return that.
492-
if (!iter.second)
493-
return iter.first->second;
494-
495-
// Otherwise, compute our value, cache it and return.
496-
bool result = isWrittenToHelper(value);
497-
iter.first->second = result;
498-
return result;
499-
}
500-
501-
private:
502-
bool isWrittenToHelper(SILValue value);
503-
};
504-
505-
} // end anonymous namespace
506-
507-
bool IsAddressWrittenToDefUseAnalysis::isWrittenToHelper(SILValue initialValue) {
481+
/// Returns true if we were able to ascertain that either the initialValue has
482+
/// no write uses or all of the write uses were writes that we could understand.
483+
static bool
484+
constructCacheValue(SILValue initialValue,
485+
SmallVectorImpl<Operand *> &wellBehavedWriteAccumulator) {
508486
SmallVector<Operand *, 8> worklist(initialValue->getUses());
487+
509488
while (!worklist.empty()) {
510489
auto *op = worklist.pop_back_val();
511490
SILInstruction *user = op->getUser();
@@ -521,35 +500,41 @@ bool IsAddressWrittenToDefUseAnalysis::isWrittenToHelper(SILValue initialValue)
521500
if (auto *oeai = dyn_cast<OpenExistentialAddrInst>(user)) {
522501
// Mutable access!
523502
if (oeai->getAccessKind() != OpenedExistentialAccess::Immutable) {
524-
return true;
503+
wellBehavedWriteAccumulator.push_back(op);
525504
}
526505

527506
// Otherwise, look through it and continue.
528507
llvm::copy(oeai->getUses(), std::back_inserter(worklist));
529508
continue;
530509
}
531510

511+
// Add any destroy_addrs to the resultAccumulator.
512+
if (isa<DestroyAddrInst>(user)) {
513+
wellBehavedWriteAccumulator.push_back(op);
514+
continue;
515+
}
516+
532517
// load_borrow and incidental uses are fine as well.
533518
if (isa<LoadBorrowInst>(user) || isIncidentalUse(user)) {
534519
continue;
535520
}
536521

537522
// Look through immutable begin_access.
538523
if (auto *bai = dyn_cast<BeginAccessInst>(user)) {
539-
// If we do not have a read, return true.
524+
// If we do not have a read, mark this as a write.
540525
if (bai->getAccessKind() != SILAccessKind::Read) {
541-
return true;
526+
wellBehavedWriteAccumulator.push_back(op);
542527
}
543528

544529
// Otherwise, add the users to the worklist and continue.
545530
llvm::copy(bai->getUses(), std::back_inserter(worklist));
546531
continue;
547532
}
548533

549-
// As long as we do not have a load [take], we are fine.
534+
// If we have a load, we just need to mark the load [take] as a write.
550535
if (auto *li = dyn_cast<LoadInst>(user)) {
551536
if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Take) {
552-
return true;
537+
wellBehavedWriteAccumulator.push_back(op);
553538
}
554539
continue;
555540
}
@@ -559,41 +544,45 @@ bool IsAddressWrittenToDefUseAnalysis::isWrittenToHelper(SILValue initialValue)
559544
// interprocedural analysis that we do not perform here.
560545
if (auto fas = FullApplySite::isa(user)) {
561546
if (fas.getArgumentConvention(*op) ==
562-
SILArgumentConvention::Indirect_In_Guaranteed)
547+
SILArgumentConvention::Indirect_In_Guaranteed) {
563548
continue;
549+
}
564550

565-
// Otherwise, be conservative and return true.
566-
return true;
551+
// Otherwise, be conservative and return that we had a write that we did
552+
// not understand.
553+
return false;
567554
}
568555

569556
// Copy addr that read are just loads.
570557
if (auto *cai = dyn_cast<CopyAddrInst>(user)) {
571558
// If our value is the destination, this is a write.
572559
if (cai->getDest() == op->get()) {
573-
return true;
560+
wellBehavedWriteAccumulator.push_back(op);
561+
continue;
574562
}
575563

576564
// Ok, so we are Src by process of elimination. Make sure we are not being
577565
// taken.
578566
if (cai->isTakeOfSrc()) {
579-
return true;
567+
wellBehavedWriteAccumulator.push_back(op);
568+
continue;
580569
}
581570

582571
// Otherwise, we are safe and can continue.
583572
continue;
584573
}
585574

586575
// If we did not recognize the user, just return conservatively that it was
587-
// written to.
576+
// written to in a way we did not understand.
588577
LLVM_DEBUG(llvm::dbgs()
589578
<< "Function: " << user->getFunction()->getName() << "\n");
590579
LLVM_DEBUG(llvm::dbgs() << "Value: " << op->get());
591580
LLVM_DEBUG(llvm::dbgs() << "Unknown instruction!: " << *user);
592-
return true;
581+
return false;
593582
}
594583

595584
// Ok, we finished our worklist and this address is not being written to.
596-
return false;
585+
return true;
597586
}
598587

599588
//===----------------------------------------------------------------------===//
@@ -623,7 +612,7 @@ struct SemanticARCOptVisitor
623612
SILFunction &F;
624613
Optional<DeadEndBlocks> TheDeadEndBlocks;
625614
ValueLifetimeAnalysis::Frontier lifetimeFrontier;
626-
IsAddressWrittenToDefUseAnalysis isAddressWrittenToDefUseAnalysis;
615+
SmallMultiMapCache<SILValue, Operand *> addressToExhaustiveWriteListCache;
627616

628617
/// Are we assuming that we reached a fix point and are re-processing to
629618
/// prepare to use the phiToIncomingValueMultiMap.
@@ -658,7 +647,8 @@ struct SemanticARCOptVisitor
658647
using FrozenMultiMapRange =
659648
decltype(joinedOwnedIntroducerToConsumedOperands)::PairToSecondEltRange;
660649

661-
explicit SemanticARCOptVisitor(SILFunction &F) : F(F) {}
650+
explicit SemanticARCOptVisitor(SILFunction &F)
651+
: F(F), addressToExhaustiveWriteListCache(constructCacheValue) {}
662652

663653
DeadEndBlocks &getDeadEndBlocks() {
664654
if (!TheDeadEndBlocks)
@@ -1663,7 +1653,8 @@ class StorageGuaranteesLoadVisitor
16631653

16641654
// If we have a modify, check if our value is /ever/ written to. If it is
16651655
// never actually written to, then we convert to a load_borrow.
1666-
return answer(ARCOpt.isAddressWrittenToDefUseAnalysis(access));
1656+
auto result = ARCOpt.addressToExhaustiveWriteListCache.get(access);
1657+
return answer(!result.hasValue() || result.getValue().size());
16671658
}
16681659

16691660
void visitArgumentAccess(SILFunctionArgument *arg) {
@@ -1676,7 +1667,8 @@ class StorageGuaranteesLoadVisitor
16761667
// If we have an inout parameter that isn't ever actually written to, return
16771668
// false.
16781669
if (arg->getKnownParameterInfo().isIndirectMutating()) {
1679-
return answer(ARCOpt.isAddressWrittenToDefUseAnalysis(arg));
1670+
auto result = ARCOpt.addressToExhaustiveWriteListCache.get(arg);
1671+
return answer(!result.hasValue() || result.getValue().size());
16801672
}
16811673

16821674
// TODO: This should be extended:

unittests/Basic/MultiMapCacheTest.cpp

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,49 @@
1717

1818
using namespace swift;
1919

20-
namespace {
21-
22-
/// A multimap cache that caches the initial 4 powers of each key.
23-
struct PowerMultiMapCache
24-
: public MultiMapCache<PowerMultiMapCache, unsigned, unsigned> {
25-
bool constructValuesForKey(unsigned key, std::vector<unsigned> &data) {
26-
// Construct the first 3 powers of key.
27-
data.push_back(key);
28-
data.push_back(key * key);
29-
data.push_back(key * key * key);
30-
return true;
20+
TEST(MultiMapCache, powersTest) {
21+
std::function<bool(unsigned, std::vector<unsigned> &)> cacheCompute =
22+
[&](unsigned key, std::vector<unsigned> &outArray) {
23+
outArray.push_back(key);
24+
outArray.push_back(key * key);
25+
outArray.push_back(key * key * key);
26+
return true;
27+
};
28+
MultiMapCache<unsigned, unsigned> cache(cacheCompute);
29+
30+
EXPECT_TRUE(cache.empty());
31+
EXPECT_EQ(cache.size(), 0u);
32+
for (unsigned index : range(1, 256)) {
33+
auto array = *cache.get(index);
34+
for (unsigned power : array) {
35+
EXPECT_EQ(power % index, 0);
36+
}
37+
}
38+
EXPECT_FALSE(cache.empty());
39+
EXPECT_EQ(cache.size(), 255);
40+
for (unsigned index : range(1, 256)) {
41+
auto array = *cache.get(index);
42+
for (unsigned power : array) {
43+
EXPECT_EQ(power % index, 0);
44+
}
3145
}
32-
};
46+
EXPECT_FALSE(cache.empty());
47+
EXPECT_EQ(cache.size(), 255);
3348

34-
} // end anonymous namespace
49+
cache.clear();
50+
EXPECT_TRUE(cache.empty());
51+
EXPECT_EQ(cache.size(), 0);
52+
}
3553

36-
TEST(MultiMapCache, powersTest) {
37-
PowerMultiMapCache cache;
54+
TEST(MultiMapCache, smallTest) {
55+
std::function<bool(unsigned, SmallVectorImpl<unsigned> &)> cacheCompute =
56+
[&](unsigned key, SmallVectorImpl<unsigned> &outArray) {
57+
outArray.push_back(key);
58+
outArray.push_back(key * key);
59+
outArray.push_back(key * key * key);
60+
return true;
61+
};
62+
SmallMultiMapCache<unsigned, unsigned> cache(cacheCompute);
3863

3964
EXPECT_TRUE(cache.empty());
4065
EXPECT_EQ(cache.size(), 0u);

0 commit comments

Comments
 (0)