Skip to content

Commit e99bb26

Browse files
Reduce memory usage in AST parent map generation by partially reverting quadratic slowdown mitigation
The use of parent maps (hasParent(), hasAncestor(), etc.) in Clang AST matchers is no longer guaranteed to avoid quadratic slowdown, but will only do so in pathological cases.
1 parent c017cdf commit e99bb26

File tree

1 file changed

+47
-3
lines changed

1 file changed

+47
-3
lines changed

clang/lib/AST/ParentMapContext.cpp

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,29 @@ class ParentMapContext::ParentMap {
6060

6161
template <typename, typename...> friend struct ::MatchParents;
6262

63+
template <class T> struct IndirectDenseMapInfo {
64+
using Ptr = T *;
65+
using Base = llvm::DenseMapInfo<std::remove_cv_t<T>>;
66+
static inline Ptr getEmptyKey() {
67+
return static_cast<Ptr>(llvm::DenseMapInfo<void *>::getEmptyKey());
68+
}
69+
static inline Ptr getTombstoneKey() {
70+
return static_cast<Ptr>(llvm::DenseMapInfo<void *>::getTombstoneKey());
71+
}
72+
static unsigned getHashValue(Ptr Val) {
73+
return Val == getEmptyKey() || Val == getTombstoneKey()
74+
? 0
75+
: Base::getHashValue(*Val);
76+
}
77+
static bool isEqual(Ptr LHS, Ptr RHS) {
78+
if (LHS == getEmptyKey() || LHS == getTombstoneKey() ||
79+
RHS == getEmptyKey() || RHS == getTombstoneKey()) {
80+
return LHS == RHS;
81+
}
82+
return Base::isEqual(*LHS, *RHS);
83+
}
84+
};
85+
6386
/// Contains parents of a node.
6487
class ParentVector {
6588
public:
@@ -70,16 +93,37 @@ class ParentMapContext::ParentMap {
7093
push_back(Value);
7194
}
7295
bool contains(const DynTypedNode &Value) {
73-
return Seen.contains(Value);
96+
assert(Value.getMemoizationData());
97+
bool found = FragileLazySeenCache.contains(&Value);
98+
while (!found && ItemsProcessed < Items.size()) {
99+
found |= FragileLazySeenCache.insert(&Items[ItemsProcessed]).second;
100+
++ItemsProcessed;
101+
}
102+
return found;
74103
}
75104
void push_back(const DynTypedNode &Value) {
76-
if (!Value.getMemoizationData() || Seen.insert(Value).second)
105+
if (!Value.getMemoizationData() || !contains(Value)) {
106+
const size_t OldCapacity = Items.capacity();
77107
Items.push_back(Value);
108+
if (OldCapacity != Items.capacity()) {
109+
// Pointers are invalidated; remove them.
110+
ItemsProcessed = 0;
111+
// Free memory to avoid doubling peak memory usage during rehashing
112+
FragileLazySeenCache.clear();
113+
}
114+
}
78115
}
79116
llvm::ArrayRef<DynTypedNode> view() const { return Items; }
80117
private:
118+
// BE CAREFUL. Pointers into this container are stored in the cache.
81119
llvm::SmallVector<DynTypedNode, 2> Items;
82-
llvm::SmallDenseSet<DynTypedNode, 2> Seen;
120+
// This cache is fragile because it contains pointers that are invalidated
121+
// when the vector capacity changes.
122+
llvm::SmallDenseSet<const DynTypedNode *, 2,
123+
IndirectDenseMapInfo<const DynTypedNode>>
124+
FragileLazySeenCache;
125+
// Lazily tracks which items have been processed for the cache.
126+
size_t ItemsProcessed = 0;
83127
};
84128

85129
/// Maps from a node to its parents. This is used for nodes that have

0 commit comments

Comments
 (0)