This is an archive of the discontinued LLVM Phabricator instance.

[ADT] Fix SmallDenseMap assertion with large InlineBuckets
ClosedPublic

Authored by nikic on Jan 8 2019, 2:24 PM.

Details

Summary

Fixes issue encountered in D56362, where I tried to use an 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. Alternatively, if we do want to switch to the large rep in this case, would be to relax the assertion in allocateBuckets().

Diff Detail

Event Timeline

nikic created this revision.Jan 8 2019, 2:24 PM

I should probably mention that this also changes the behavior for smaller inline buckets. If say we are in the small rep with InlineBuckets=16 and call grow(16), then before this change it would switch it to the large rep with 64 buckets, while after this change it will stay in the small rep with 16 buckets. If we want to keep it this way, then an alternative fix here would be to simply adjust the assertion and allow creating a large rep with InlineBuckets buckets.

Herald added a project: Restricted Project. · View Herald TranscriptJun 22 2019, 4:23 AM
atrick added a subscriber: atrick.Sep 6 2019, 10:18 AM

Can this problem be fixed without losing the invariant that memory is never allocated until NumBuckets > InlineBuckets, regardless of the load? I don't like the removal of the fast path if (AtLeast < InlineBuckets) and the load doesn't matter at all in small mode!
e.g. If I ask to reserve 100 out of 128 entries, it should not attempt to copy all the entries into temporary storage.

nikic added a comment.Sep 6 2019, 12:54 PM

@atrick That invariant doesn't exist currently. If a SmallDenseMap with less than InlineBuckets elements is compacted, it will change to the large rep (with an allocation). After this change it will instead stay in the small rep (without an allocation).

The short-circuit condition AtLeast < InlineBuckets is dead code right now, I think, because NumBuckets is >= InlineBuckets and AtLeast is either NumBuckets or NumBuckets*2.

atrick accepted this revision.Sep 9 2019, 6:45 PM

Hmm... I guess the intent of the original design is not to overload the table even if it's in small mode. I'm not sure that makes sense, but I'll accept it as status quo. Anyway, it does sort of make sense that we can compress the small table by removing tombstones.

Now that I fully understand what's happening, this looks like a good fix!

This revision is now accepted and ready to land.Sep 9 2019, 6:45 PM
This revision was automatically updated to reflect the committed changes.