Skip to content

Commit

Permalink
Make iteration over the DeclContext::lookup_result safe.
Browse files Browse the repository at this point in the history
The idiom:
```
DeclContext::lookup_result R = DeclContext::lookup(Name);
for (auto *D : R) {...}
```

is not safe when in the loop body we trigger deserialization from an AST file.
The deserialization can insert new declarations in the StoredDeclsList whose
underlying type is a vector. When the vector decides to reallocate its storage
the pointer we hold becomes invalid.

This patch replaces a SmallVector with an singly-linked list. The current
approach stores a SmallVector<NamedDecl*, 4> which is around 8 pointers.
The linked list is 3, 5, or 7. We do better in terms of memory usage for small
cases (and worse in terms of locality -- the linked list entries won't be near
each other, but will be near their corresponding declarations, and we were going
to fetch those memory pages anyway). For larger cases: the vector uses a
doubling strategy for reallocation, so will generally be between half-full and
full. Let's say it's 75% full on average, so there's N * 4/3 + 4 pointers' worth
of space allocated currently and will be 2N pointers with the linked list. So we
break even when there are N=6 entries and slightly lose in terms of memory usage
after that. We suspect that's still a win on average.

Thanks to @rsmith!

Differential revision: https://reviews.llvm.org/D91524
  • Loading branch information
vgvassilev committed Mar 17, 2021
1 parent 3b8b5d1 commit 0cb7e7c
Show file tree
Hide file tree
Showing 30 changed files with 458 additions and 384 deletions.
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/unittests/TestTU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ const NamedDecl &findDecl(ParsedAST &AST, llvm::StringRef QName) {
llvm::StringRef Name) -> const NamedDecl & {
auto LookupRes = Scope.lookup(DeclarationName(&Ctx.Idents.get(Name)));
assert(!LookupRes.empty() && "Lookup failed");
assert(LookupRes.size() == 1 && "Lookup returned multiple results");
assert(LookupRes.isSingleResult() && "Lookup returned multiple results");
return *LookupRes.front();
};

Expand Down
21 changes: 21 additions & 0 deletions clang/include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
std::unique_ptr<interp::Context> InterpContext;
std::unique_ptr<ParentMapContext> ParentMapCtx;

/// Keeps track of the deallocated DeclListNodes for future reuse.
DeclListNode *ListNodeFreeList = nullptr;

public:
IdentifierTable &Idents;
SelectorTable &Selectors;
Expand Down Expand Up @@ -655,6 +658,24 @@ class ASTContext : public RefCountedBase<ASTContext> {
}
void Deallocate(void *Ptr) const {}

/// Allocates a \c DeclListNode or returns one from the \c ListNodeFreeList
/// pool.
DeclListNode *AllocateDeclListNode(clang::NamedDecl *ND) {
if (DeclListNode *Alloc = ListNodeFreeList) {
ListNodeFreeList = Alloc->Rest.dyn_cast<DeclListNode*>();
Alloc->D = ND;
Alloc->Rest = nullptr;
return Alloc;
}
return new (*this) DeclListNode(ND);
}
/// Deallcates a \c DeclListNode by returning it to the \c ListNodeFreeList
/// pool.
void DeallocateDeclListNode(DeclListNode *N) {
N->Rest = ListNodeFreeList;
ListNodeFreeList = N;
}

/// Return the total amount of physical memory allocated for representing
/// AST nodes and type information.
size_t getASTAllocatedMemory() const {
Expand Down
5 changes: 2 additions & 3 deletions clang/include/clang/AST/CXXInheritance.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,8 @@ class CXXBasePath : public SmallVector<CXXBasePathElement, 4> {

CXXBasePath() = default;

/// The set of declarations found inside this base class
/// subobject.
DeclContext::lookup_result Decls;
/// The declarations found inside this base class subobject.
DeclContext::lookup_iterator Decls;

void clear() {
SmallVectorImpl<CXXBasePathElement>::clear();
Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,16 @@ class NamespaceDecl : public NamedDecl, public DeclContext,
AnonOrFirstNamespaceAndInline.setInt(Inline);
}

/// Returns true if the inline qualifier for \c Name is redundant.
bool isRedundantInlineQualifierFor(DeclarationName Name) const {
if (!isInline())
return false;
auto X = lookup(Name);
auto Y = getParent()->lookup(Name);
return std::distance(X.begin(), X.end()) ==
std::distance(Y.begin(), Y.end());
}

/// Get the original (first) namespace declaration.
NamespaceDecl *getOriginalNamespace();

Expand Down
137 changes: 91 additions & 46 deletions clang/include/clang/AST/DeclBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -1220,65 +1220,110 @@ class PrettyStackTraceDecl : public llvm::PrettyStackTraceEntry {

void print(raw_ostream &OS) const override;
};
} // namespace clang

/// The results of name lookup within a DeclContext. This is either a
/// single result (with no stable storage) or a collection of results (with
/// stable storage provided by the lookup table).
class DeclContextLookupResult {
using ResultTy = ArrayRef<NamedDecl *>;

ResultTy Result;

// If there is only one lookup result, it would be invalidated by
// reallocations of the name table, so store it separately.
NamedDecl *Single = nullptr;

static NamedDecl *const SingleElementDummyList;
// Required to determine the layout of the PointerUnion<NamedDecl*> before
// seeing the NamedDecl definition being first used in DeclListNode::operator*.
namespace llvm {
template <> struct PointerLikeTypeTraits<::clang::NamedDecl *> {
static inline void *getAsVoidPointer(::clang::NamedDecl *P) { return P; }
static inline ::clang::NamedDecl *getFromVoidPointer(void *P) {
return static_cast<::clang::NamedDecl *>(P);
}
static constexpr int NumLowBitsAvailable = 3;
};
}

namespace clang {
/// A list storing NamedDecls in the lookup tables.
class DeclListNode {
friend class ASTContext; // allocate, deallocate nodes.
friend class StoredDeclsList;
public:
DeclContextLookupResult() = default;
DeclContextLookupResult(ArrayRef<NamedDecl *> Result)
: Result(Result) {}
DeclContextLookupResult(NamedDecl *Single)
: Result(SingleElementDummyList), Single(Single) {}

class iterator;

using IteratorBase =
llvm::iterator_adaptor_base<iterator, ResultTy::iterator,
std::random_access_iterator_tag, NamedDecl *>;

class iterator : public IteratorBase {
value_type SingleElement;
using Decls = llvm::PointerUnion<NamedDecl*, DeclListNode*>;
class iterator {
friend class DeclContextLookupResult;
friend class StoredDeclsList;

Decls Ptr;
iterator(Decls Node) : Ptr(Node) { }
public:
explicit iterator(pointer Pos, value_type Single = nullptr)
: IteratorBase(Pos), SingleElement(Single) {}
using difference_type = ptrdiff_t;
using value_type = NamedDecl*;
using pointer = void;
using reference = value_type;
using iterator_category = std::forward_iterator_tag;

iterator() = default;

reference operator*() const {
return SingleElement ? SingleElement : IteratorBase::operator*();
assert(Ptr && "dereferencing end() iterator");
if (DeclListNode *CurNode = Ptr.dyn_cast<DeclListNode*>())
return CurNode->D;
return Ptr.get<NamedDecl*>();
}
void operator->() const { } // Unsupported.
bool operator==(const iterator &X) const { return Ptr == X.Ptr; }
bool operator!=(const iterator &X) const { return Ptr != X.Ptr; }
inline iterator &operator++() { // ++It
assert(!Ptr.isNull() && "Advancing empty iterator");

if (DeclListNode *CurNode = Ptr.dyn_cast<DeclListNode*>())
Ptr = CurNode->Rest;
else
Ptr = nullptr;
return *this;
}
iterator operator++(int) { // It++
iterator temp = *this;
++(*this);
return temp;
}
// Enables the pattern for (iterator I =..., E = I.end(); I != E; ++I)
iterator end() { return iterator(); }
};
private:
NamedDecl *D = nullptr;
Decls Rest = nullptr;
DeclListNode(NamedDecl *ND) : D(ND) {}
};

/// The results of name lookup within a DeclContext.
class DeclContextLookupResult {
using Decls = DeclListNode::Decls;

/// When in collection form, this is what the Data pointer points to.
Decls Result;

public:
DeclContextLookupResult() = default;
DeclContextLookupResult(Decls Result) : Result(Result) {}

using iterator = DeclListNode::iterator;
using const_iterator = iterator;
using pointer = iterator::pointer;
using reference = iterator::reference;

iterator begin() const { return iterator(Result.begin(), Single); }
iterator end() const { return iterator(Result.end(), Single); }

bool empty() const { return Result.empty(); }
pointer data() const { return Single ? &Single : Result.data(); }
size_t size() const { return Single ? 1 : Result.size(); }
reference front() const { return Single ? Single : Result.front(); }
reference back() const { return Single ? Single : Result.back(); }
reference operator[](size_t N) const { return Single ? Single : Result[N]; }

// FIXME: Remove this from the interface
DeclContextLookupResult slice(size_t N) const {
DeclContextLookupResult Sliced = Result.slice(N);
Sliced.Single = Single;
return Sliced;
iterator begin() { return iterator(Result); }
iterator end() { return iterator(); }
const_iterator begin() const {
return const_cast<DeclContextLookupResult*>(this)->begin();
}
const_iterator end() const { return iterator(); }

bool empty() const { return Result.isNull(); }
bool isSingleResult() const { return Result.dyn_cast<NamedDecl*>(); }
reference front() const { return *begin(); }

// Find the first declaration of the given type in the list. Note that this
// is not in general the earliest-declared declaration, and should only be
// used when it's not possible for there to be more than one match or where
// it doesn't matter which one is found.
template<class T> T *find_first() const {
for (auto *D : *this)
if (T *Decl = dyn_cast<T>(D))
return Decl;

return nullptr;
}
};

Expand Down
Loading

0 comments on commit 0cb7e7c

Please sign in to comment.