mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-25 11:56:08 +00:00

This mitigates a regression introduced in #87824. The mitigation here is to store pointers the deduplicated AST nodes, rather than copies of the nodes themselves. This allows a pointer-optimized set to be used and saves a lot of memory because `clang::DynTypedNode` is ~5 times larger than a pointer. Fixes #129808.
490 lines
17 KiB
C++
490 lines
17 KiB
C++
//===- ParentMapContext.cpp - Map of parents using DynTypedNode -*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Similar to ParentMap.cpp, but generalizes to non-Stmt nodes, which can have
|
|
// multiple parents.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/AST/ParentMapContext.h"
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/AST/Expr.h"
|
|
#include "clang/AST/RecursiveASTVisitor.h"
|
|
#include "clang/AST/TemplateBase.h"
|
|
#include "llvm/ADT/SmallPtrSet.h"
|
|
|
|
using namespace clang;
|
|
|
|
ParentMapContext::ParentMapContext(ASTContext &Ctx) : ASTCtx(Ctx) {}
|
|
|
|
ParentMapContext::~ParentMapContext() = default;
|
|
|
|
void ParentMapContext::clear() { Parents.reset(); }
|
|
|
|
const Expr *ParentMapContext::traverseIgnored(const Expr *E) const {
|
|
return traverseIgnored(const_cast<Expr *>(E));
|
|
}
|
|
|
|
Expr *ParentMapContext::traverseIgnored(Expr *E) const {
|
|
if (!E)
|
|
return nullptr;
|
|
|
|
switch (Traversal) {
|
|
case TK_AsIs:
|
|
return E;
|
|
case TK_IgnoreUnlessSpelledInSource:
|
|
return E->IgnoreUnlessSpelledInSource();
|
|
}
|
|
llvm_unreachable("Invalid Traversal type!");
|
|
}
|
|
|
|
DynTypedNode ParentMapContext::traverseIgnored(const DynTypedNode &N) const {
|
|
if (const auto *E = N.get<Expr>()) {
|
|
return DynTypedNode::create(*traverseIgnored(E));
|
|
}
|
|
return N;
|
|
}
|
|
|
|
template <typename T, typename... U>
|
|
static std::tuple<bool, DynTypedNodeList, const T *, const U *...>
|
|
matchParents(const DynTypedNodeList &NodeList,
|
|
ParentMapContext::ParentMap *ParentMap);
|
|
|
|
template <typename, typename...> struct MatchParents;
|
|
|
|
class ParentMapContext::ParentMap {
|
|
|
|
template <typename, typename...> friend struct ::MatchParents;
|
|
|
|
/// Contains parents of a node.
|
|
class ParentVector {
|
|
public:
|
|
ParentVector() = default;
|
|
explicit ParentVector(size_t N, const DynTypedNode &Value) {
|
|
Items.reserve(N);
|
|
for (; N > 0; --N)
|
|
push_back(Value);
|
|
}
|
|
bool contains(const DynTypedNode &Value) const {
|
|
const void *Identity = Value.getMemoizationData();
|
|
assert(Identity);
|
|
return Dedup.contains(Identity);
|
|
}
|
|
void push_back(const DynTypedNode &Value) {
|
|
const void *Identity = Value.getMemoizationData();
|
|
if (!Identity || Dedup.insert(Identity).second) {
|
|
Items.push_back(Value);
|
|
}
|
|
}
|
|
llvm::ArrayRef<DynTypedNode> view() const { return Items; }
|
|
private:
|
|
llvm::SmallVector<DynTypedNode, 1> Items;
|
|
llvm::SmallPtrSet<const void *, 2> Dedup;
|
|
};
|
|
|
|
/// Maps from a node to its parents. This is used for nodes that have
|
|
/// pointer identity only, which are more common and we can save space by
|
|
/// only storing a unique pointer to them.
|
|
using ParentMapPointers =
|
|
llvm::DenseMap<const void *,
|
|
llvm::PointerUnion<const Decl *, const Stmt *,
|
|
DynTypedNode *, ParentVector *>>;
|
|
|
|
/// Parent map for nodes without pointer identity. We store a full
|
|
/// DynTypedNode for all keys.
|
|
using ParentMapOtherNodes =
|
|
llvm::DenseMap<DynTypedNode,
|
|
llvm::PointerUnion<const Decl *, const Stmt *,
|
|
DynTypedNode *, ParentVector *>>;
|
|
|
|
ParentMapPointers PointerParents;
|
|
ParentMapOtherNodes OtherParents;
|
|
class ASTVisitor;
|
|
|
|
static DynTypedNode
|
|
getSingleDynTypedNodeFromParentMap(ParentMapPointers::mapped_type U) {
|
|
if (const auto *D = dyn_cast<const Decl *>(U))
|
|
return DynTypedNode::create(*D);
|
|
if (const auto *S = dyn_cast<const Stmt *>(U))
|
|
return DynTypedNode::create(*S);
|
|
return *cast<DynTypedNode *>(U);
|
|
}
|
|
|
|
template <typename NodeTy, typename MapTy>
|
|
static DynTypedNodeList getDynNodeFromMap(const NodeTy &Node,
|
|
const MapTy &Map) {
|
|
auto I = Map.find(Node);
|
|
if (I == Map.end()) {
|
|
return llvm::ArrayRef<DynTypedNode>();
|
|
}
|
|
if (const auto *V = dyn_cast<ParentVector *>(I->second)) {
|
|
return V->view();
|
|
}
|
|
return getSingleDynTypedNodeFromParentMap(I->second);
|
|
}
|
|
|
|
public:
|
|
ParentMap(ASTContext &Ctx);
|
|
~ParentMap() {
|
|
for (const auto &Entry : PointerParents) {
|
|
if (auto *DTN = dyn_cast<DynTypedNode *>(Entry.second)) {
|
|
delete DTN;
|
|
} else if (auto *PV = dyn_cast<ParentVector *>(Entry.second)) {
|
|
delete PV;
|
|
}
|
|
}
|
|
for (const auto &Entry : OtherParents) {
|
|
if (auto *DTN = dyn_cast<DynTypedNode *>(Entry.second)) {
|
|
delete DTN;
|
|
} else if (auto *PV = dyn_cast<ParentVector *>(Entry.second)) {
|
|
delete PV;
|
|
}
|
|
}
|
|
}
|
|
|
|
DynTypedNodeList getParents(TraversalKind TK, const DynTypedNode &Node) {
|
|
if (Node.getNodeKind().hasPointerIdentity()) {
|
|
auto ParentList =
|
|
getDynNodeFromMap(Node.getMemoizationData(), PointerParents);
|
|
if (ParentList.size() > 0 && TK == TK_IgnoreUnlessSpelledInSource) {
|
|
|
|
const auto *ChildExpr = Node.get<Expr>();
|
|
|
|
{
|
|
// Don't match explicit node types because different stdlib
|
|
// implementations implement this in different ways and have
|
|
// different intermediate nodes.
|
|
// Look up 4 levels for a cxxRewrittenBinaryOperator as that is
|
|
// enough for the major stdlib implementations.
|
|
auto RewrittenBinOpParentsList = ParentList;
|
|
int I = 0;
|
|
while (ChildExpr && RewrittenBinOpParentsList.size() == 1 &&
|
|
I++ < 4) {
|
|
const auto *S = RewrittenBinOpParentsList[0].get<Stmt>();
|
|
if (!S)
|
|
break;
|
|
|
|
const auto *RWBO = dyn_cast<CXXRewrittenBinaryOperator>(S);
|
|
if (!RWBO) {
|
|
RewrittenBinOpParentsList = getDynNodeFromMap(S, PointerParents);
|
|
continue;
|
|
}
|
|
if (RWBO->getLHS()->IgnoreUnlessSpelledInSource() != ChildExpr &&
|
|
RWBO->getRHS()->IgnoreUnlessSpelledInSource() != ChildExpr)
|
|
break;
|
|
return DynTypedNode::create(*RWBO);
|
|
}
|
|
}
|
|
|
|
const auto *ParentExpr = ParentList[0].get<Expr>();
|
|
if (ParentExpr && ChildExpr)
|
|
return AscendIgnoreUnlessSpelledInSource(ParentExpr, ChildExpr);
|
|
|
|
{
|
|
auto AncestorNodes =
|
|
matchParents<DeclStmt, CXXForRangeStmt>(ParentList, this);
|
|
if (std::get<bool>(AncestorNodes) &&
|
|
std::get<const CXXForRangeStmt *>(AncestorNodes)
|
|
->getLoopVarStmt() ==
|
|
std::get<const DeclStmt *>(AncestorNodes))
|
|
return std::get<DynTypedNodeList>(AncestorNodes);
|
|
}
|
|
{
|
|
auto AncestorNodes = matchParents<VarDecl, DeclStmt, CXXForRangeStmt>(
|
|
ParentList, this);
|
|
if (std::get<bool>(AncestorNodes) &&
|
|
std::get<const CXXForRangeStmt *>(AncestorNodes)
|
|
->getRangeStmt() ==
|
|
std::get<const DeclStmt *>(AncestorNodes))
|
|
return std::get<DynTypedNodeList>(AncestorNodes);
|
|
}
|
|
{
|
|
auto AncestorNodes =
|
|
matchParents<CXXMethodDecl, CXXRecordDecl, LambdaExpr>(ParentList,
|
|
this);
|
|
if (std::get<bool>(AncestorNodes))
|
|
return std::get<DynTypedNodeList>(AncestorNodes);
|
|
}
|
|
{
|
|
auto AncestorNodes =
|
|
matchParents<FunctionTemplateDecl, CXXRecordDecl, LambdaExpr>(
|
|
ParentList, this);
|
|
if (std::get<bool>(AncestorNodes))
|
|
return std::get<DynTypedNodeList>(AncestorNodes);
|
|
}
|
|
}
|
|
return ParentList;
|
|
}
|
|
return getDynNodeFromMap(Node, OtherParents);
|
|
}
|
|
|
|
DynTypedNodeList AscendIgnoreUnlessSpelledInSource(const Expr *E,
|
|
const Expr *Child) {
|
|
|
|
auto ShouldSkip = [](const Expr *E, const Expr *Child) {
|
|
if (isa<ImplicitCastExpr>(E))
|
|
return true;
|
|
|
|
if (isa<FullExpr>(E))
|
|
return true;
|
|
|
|
if (isa<MaterializeTemporaryExpr>(E))
|
|
return true;
|
|
|
|
if (isa<CXXBindTemporaryExpr>(E))
|
|
return true;
|
|
|
|
if (isa<ParenExpr>(E))
|
|
return true;
|
|
|
|
if (isa<ExprWithCleanups>(E))
|
|
return true;
|
|
|
|
auto SR = Child->getSourceRange();
|
|
|
|
if (const auto *C = dyn_cast<CXXFunctionalCastExpr>(E)) {
|
|
if (C->getSourceRange() == SR)
|
|
return true;
|
|
}
|
|
|
|
if (const auto *C = dyn_cast<CXXConstructExpr>(E)) {
|
|
if (C->getSourceRange() == SR || C->isElidable())
|
|
return true;
|
|
}
|
|
|
|
if (const auto *C = dyn_cast<CXXMemberCallExpr>(E)) {
|
|
if (C->getSourceRange() == SR)
|
|
return true;
|
|
}
|
|
|
|
if (const auto *C = dyn_cast<MemberExpr>(E)) {
|
|
if (C->getSourceRange() == SR)
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
while (ShouldSkip(E, Child)) {
|
|
auto It = PointerParents.find(E);
|
|
if (It == PointerParents.end())
|
|
break;
|
|
const auto *S = dyn_cast<const Stmt *>(It->second);
|
|
if (!S) {
|
|
if (auto *Vec = dyn_cast<ParentVector *>(It->second))
|
|
return Vec->view();
|
|
return getSingleDynTypedNodeFromParentMap(It->second);
|
|
}
|
|
const auto *P = dyn_cast<Expr>(S);
|
|
if (!P)
|
|
return DynTypedNode::create(*S);
|
|
Child = E;
|
|
E = P;
|
|
}
|
|
return DynTypedNode::create(*E);
|
|
}
|
|
};
|
|
|
|
template <typename T, typename... U> struct MatchParents {
|
|
static std::tuple<bool, DynTypedNodeList, const T *, const U *...>
|
|
match(const DynTypedNodeList &NodeList,
|
|
ParentMapContext::ParentMap *ParentMap) {
|
|
if (const auto *TypedNode = NodeList[0].get<T>()) {
|
|
auto NextParentList =
|
|
ParentMap->getDynNodeFromMap(TypedNode, ParentMap->PointerParents);
|
|
if (NextParentList.size() == 1) {
|
|
auto TailTuple = MatchParents<U...>::match(NextParentList, ParentMap);
|
|
if (std::get<bool>(TailTuple)) {
|
|
return std::apply(
|
|
[TypedNode](bool, DynTypedNodeList NodeList, auto... TupleTail) {
|
|
return std::make_tuple(true, NodeList, TypedNode, TupleTail...);
|
|
},
|
|
TailTuple);
|
|
}
|
|
}
|
|
}
|
|
return std::tuple_cat(std::make_tuple(false, NodeList),
|
|
std::tuple<const T *, const U *...>());
|
|
}
|
|
};
|
|
|
|
template <typename T> struct MatchParents<T> {
|
|
static std::tuple<bool, DynTypedNodeList, const T *>
|
|
match(const DynTypedNodeList &NodeList,
|
|
ParentMapContext::ParentMap *ParentMap) {
|
|
if (const auto *TypedNode = NodeList[0].get<T>()) {
|
|
auto NextParentList =
|
|
ParentMap->getDynNodeFromMap(TypedNode, ParentMap->PointerParents);
|
|
if (NextParentList.size() == 1)
|
|
return std::make_tuple(true, NodeList, TypedNode);
|
|
}
|
|
return std::make_tuple(false, NodeList, nullptr);
|
|
}
|
|
};
|
|
|
|
template <typename T, typename... U>
|
|
std::tuple<bool, DynTypedNodeList, const T *, const U *...>
|
|
matchParents(const DynTypedNodeList &NodeList,
|
|
ParentMapContext::ParentMap *ParentMap) {
|
|
return MatchParents<T, U...>::match(NodeList, ParentMap);
|
|
}
|
|
|
|
/// Template specializations to abstract away from pointers and TypeLocs.
|
|
/// @{
|
|
template <typename T> static DynTypedNode createDynTypedNode(const T &Node) {
|
|
return DynTypedNode::create(*Node);
|
|
}
|
|
template <> DynTypedNode createDynTypedNode(const TypeLoc &Node) {
|
|
return DynTypedNode::create(Node);
|
|
}
|
|
template <>
|
|
DynTypedNode createDynTypedNode(const NestedNameSpecifierLoc &Node) {
|
|
return DynTypedNode::create(Node);
|
|
}
|
|
template <> DynTypedNode createDynTypedNode(const ObjCProtocolLoc &Node) {
|
|
return DynTypedNode::create(Node);
|
|
}
|
|
/// @}
|
|
|
|
/// A \c RecursiveASTVisitor that builds a map from nodes to their
|
|
/// parents as defined by the \c RecursiveASTVisitor.
|
|
///
|
|
/// Note that the relationship described here is purely in terms of AST
|
|
/// traversal - there are other relationships (for example declaration context)
|
|
/// in the AST that are better modeled by special matchers.
|
|
class ParentMapContext::ParentMap::ASTVisitor
|
|
: public RecursiveASTVisitor<ASTVisitor> {
|
|
public:
|
|
ASTVisitor(ParentMap &Map) : Map(Map) {}
|
|
|
|
private:
|
|
friend class RecursiveASTVisitor<ASTVisitor>;
|
|
|
|
using VisitorBase = RecursiveASTVisitor<ASTVisitor>;
|
|
|
|
bool shouldVisitTemplateInstantiations() const { return true; }
|
|
|
|
bool shouldVisitImplicitCode() const { return true; }
|
|
|
|
/// Record the parent of the node we're visiting.
|
|
/// MapNode is the child, the parent is on top of ParentStack.
|
|
/// Parents is the parent storage (either PointerParents or OtherParents).
|
|
template <typename MapNodeTy, typename MapTy>
|
|
void addParent(MapNodeTy MapNode, MapTy *Parents) {
|
|
if (ParentStack.empty())
|
|
return;
|
|
|
|
// FIXME: Currently we add the same parent multiple times, but only
|
|
// when no memoization data is available for the type.
|
|
// For example when we visit all subexpressions of template
|
|
// instantiations; this is suboptimal, but benign: the only way to
|
|
// visit those is with hasAncestor / hasParent, and those do not create
|
|
// new matches.
|
|
// The plan is to enable DynTypedNode to be storable in a map or hash
|
|
// map. The main problem there is to implement hash functions /
|
|
// comparison operators for all types that DynTypedNode supports that
|
|
// do not have pointer identity.
|
|
auto &NodeOrVector = (*Parents)[MapNode];
|
|
if (NodeOrVector.isNull()) {
|
|
if (const auto *D = ParentStack.back().get<Decl>())
|
|
NodeOrVector = D;
|
|
else if (const auto *S = ParentStack.back().get<Stmt>())
|
|
NodeOrVector = S;
|
|
else
|
|
NodeOrVector = new DynTypedNode(ParentStack.back());
|
|
} else {
|
|
if (!isa<ParentVector *>(NodeOrVector)) {
|
|
auto *Vector = new ParentVector(
|
|
1, getSingleDynTypedNodeFromParentMap(NodeOrVector));
|
|
delete dyn_cast<DynTypedNode *>(NodeOrVector);
|
|
NodeOrVector = Vector;
|
|
}
|
|
|
|
auto *Vector = cast<ParentVector *>(NodeOrVector);
|
|
// Skip duplicates for types that have memoization data.
|
|
// We must check that the type has memoization data before calling
|
|
// llvm::is_contained() because DynTypedNode::operator== can't compare all
|
|
// types.
|
|
bool Found = ParentStack.back().getMemoizationData() &&
|
|
llvm::is_contained(*Vector, ParentStack.back());
|
|
if (!Found)
|
|
Vector->push_back(ParentStack.back());
|
|
}
|
|
}
|
|
|
|
template <typename T> static bool isNull(T Node) { return !Node; }
|
|
static bool isNull(ObjCProtocolLoc Node) { return false; }
|
|
|
|
template <typename T, typename MapNodeTy, typename BaseTraverseFn,
|
|
typename MapTy>
|
|
bool TraverseNode(T Node, MapNodeTy MapNode, BaseTraverseFn BaseTraverse,
|
|
MapTy *Parents) {
|
|
if (isNull(Node))
|
|
return true;
|
|
addParent(MapNode, Parents);
|
|
ParentStack.push_back(createDynTypedNode(Node));
|
|
bool Result = BaseTraverse();
|
|
ParentStack.pop_back();
|
|
return Result;
|
|
}
|
|
|
|
bool TraverseDecl(Decl *DeclNode) {
|
|
return TraverseNode(
|
|
DeclNode, DeclNode, [&] { return VisitorBase::TraverseDecl(DeclNode); },
|
|
&Map.PointerParents);
|
|
}
|
|
bool TraverseTypeLoc(TypeLoc TypeLocNode) {
|
|
return TraverseNode(
|
|
TypeLocNode, DynTypedNode::create(TypeLocNode),
|
|
[&] { return VisitorBase::TraverseTypeLoc(TypeLocNode); },
|
|
&Map.OtherParents);
|
|
}
|
|
bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc NNSLocNode) {
|
|
return TraverseNode(
|
|
NNSLocNode, DynTypedNode::create(NNSLocNode),
|
|
[&] { return VisitorBase::TraverseNestedNameSpecifierLoc(NNSLocNode); },
|
|
&Map.OtherParents);
|
|
}
|
|
bool TraverseAttr(Attr *AttrNode) {
|
|
return TraverseNode(
|
|
AttrNode, AttrNode, [&] { return VisitorBase::TraverseAttr(AttrNode); },
|
|
&Map.PointerParents);
|
|
}
|
|
bool TraverseObjCProtocolLoc(ObjCProtocolLoc ProtocolLocNode) {
|
|
return TraverseNode(
|
|
ProtocolLocNode, DynTypedNode::create(ProtocolLocNode),
|
|
[&] { return VisitorBase::TraverseObjCProtocolLoc(ProtocolLocNode); },
|
|
&Map.OtherParents);
|
|
}
|
|
|
|
// Using generic TraverseNode for Stmt would prevent data-recursion.
|
|
bool dataTraverseStmtPre(Stmt *StmtNode) {
|
|
addParent(StmtNode, &Map.PointerParents);
|
|
ParentStack.push_back(DynTypedNode::create(*StmtNode));
|
|
return true;
|
|
}
|
|
bool dataTraverseStmtPost(Stmt *StmtNode) {
|
|
ParentStack.pop_back();
|
|
return true;
|
|
}
|
|
|
|
ParentMap ⤅
|
|
llvm::SmallVector<DynTypedNode, 16> ParentStack;
|
|
};
|
|
|
|
ParentMapContext::ParentMap::ParentMap(ASTContext &Ctx) {
|
|
ASTVisitor(*this).TraverseAST(Ctx);
|
|
}
|
|
|
|
DynTypedNodeList ParentMapContext::getParents(const DynTypedNode &Node) {
|
|
if (!Parents)
|
|
// We build the parent map for the traversal scope (usually whole TU), as
|
|
// hasAncestor can escape any subtree.
|
|
Parents = std::make_unique<ParentMap>(ASTCtx);
|
|
return Parents->getParents(getTraversalKind(), Node);
|
|
}
|