Skip to content

Commit fe593fe

Browse files
committed
[ADT] Fix SmallDenseMap assertion with large InlineBuckets
Fixes issue encountered in D56362, where I tried to use a SmallSetVector<Instruction*, 128> with an excessively large number of inline elements. This triggers an "Must allocate more buckets than are inline" assertion inside allocateBuckets() under certain usage patterns. The issue is as follows: The grow() method is used either to grow the map, or to rehash it and remove tombstones. The latter is done if the fraction of empty (non-used, non-tombstone) elements is below 1/8. In this case grow() is invoked with the current number of buckets. This is currently incorrectly handled for dense maps using the small rep. The current implementation will switch them over to the large rep, which violates the invariant that the large rep is only used if there are more than InlineBuckets buckets. This patch fixes the issue by staying in the small rep and only moving the buckets. An alternative, if we do want to switch to the large rep in this case, would be to relax the assertion in allocateBuckets(). Differential Revision: https://reviews.llvm.org/D56455
1 parent d23c614 commit fe593fe

File tree

2 files changed

+26
-8
lines changed

2 files changed

+26
-8
lines changed

llvm/include/llvm/ADT/DenseMap.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,13 +1006,10 @@ class SmallDenseMap
10061006
}
10071007

10081008
void grow(unsigned AtLeast) {
1009-
if (AtLeast >= InlineBuckets)
1009+
if (AtLeast > InlineBuckets)
10101010
AtLeast = std::max<unsigned>(64, NextPowerOf2(AtLeast-1));
10111011

10121012
if (Small) {
1013-
if (AtLeast < InlineBuckets)
1014-
return; // Nothing to do.
1015-
10161013
// First move the inline buckets into a temporary storage.
10171014
AlignedCharArrayUnion<BucketT[InlineBuckets]> TmpStorage;
10181015
BucketT *TmpBegin = reinterpret_cast<BucketT *>(TmpStorage.buffer);
@@ -1035,10 +1032,13 @@ class SmallDenseMap
10351032
P->getFirst().~KeyT();
10361033
}
10371034

1038-
// Now make this map use the large rep, and move all the entries back
1039-
// into it.
1040-
Small = false;
1041-
new (getLargeRep()) LargeRep(allocateBuckets(AtLeast));
1035+
// AtLeast == InlineBuckets can happen if there are many tombstones,
1036+
// and grow() is used to remove them. Usually we always switch to the
1037+
// large rep here.
1038+
if (AtLeast > InlineBuckets) {
1039+
Small = false;
1040+
new (getLargeRep()) LargeRep(allocateBuckets(AtLeast));
1041+
}
10421042
this->moveFromOldBuckets(TmpBegin, TmpEnd);
10431043
return;
10441044
}

llvm/unittests/ADT/DenseMapTest.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,24 @@ TEST(DenseMapCustomTest, SmallDenseMapGrowTest) {
580580
EXPECT_TRUE(map.find(32) == map.end());
581581
}
582582

583+
TEST(DenseMapCustomTest, LargeSmallDenseMapCompaction) {
584+
SmallDenseMap<unsigned, unsigned, 128, ContiguousDenseMapInfo> map;
585+
// Fill to < 3/4 load.
586+
for (unsigned i = 0; i < 95; ++i)
587+
map[i] = i;
588+
// And erase, leaving behind tombstones.
589+
for (unsigned i = 0; i < 95; ++i)
590+
map.erase(i);
591+
// Fill further, so that less than 1/8 are empty, but still below 3/4 load.
592+
for (unsigned i = 95; i < 128; ++i)
593+
map[i] = i;
594+
595+
EXPECT_EQ(33u, map.size());
596+
// Similar to the previous test, check for a non-existing element, as an
597+
// indirect check that tombstones have been removed.
598+
EXPECT_TRUE(map.find(0) == map.end());
599+
}
600+
583601
TEST(DenseMapCustomTest, TryEmplaceTest) {
584602
DenseMap<int, std::unique_ptr<int>> Map;
585603
std::unique_ptr<int> P(new int(2));

0 commit comments

Comments
 (0)