File indexing completed on 2024-05-12 04:38:03
0001 /* 0002 SPDX-FileCopyrightText: 2008 David Nolden <david.nolden.kdevelop@art-master.de> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only 0005 */ 0006 0007 #include "persistentsymboltable.h" 0008 0009 #include <QHash> 0010 0011 #include "declaration.h" 0012 #include "declarationid.h" 0013 #include "appendedlist.h" 0014 #include "serialization/itemrepository.h" 0015 #include "identifier.h" 0016 #include "ducontext.h" 0017 #include "topducontext.h" 0018 #include "duchain.h" 0019 #include "duchainlock.h" 0020 0021 #include <util/convenientfreelist.h> 0022 #include <util/embeddedfreetree.h> 0023 0024 #include <language/util/setrepository.h> 0025 0026 #if defined(QT_NO_DEBUG) && !defined(QT_FORCE_ASSERTS) 0027 #define VERIFY_VISIT_NESTING 0 0028 #define ifVerifyVisitNesting(x) 0029 #else 0030 #define VERIFY_VISIT_NESTING 1 0031 #define ifVerifyVisitNesting(x) x 0032 #endif 0033 0034 namespace KDevelop { 0035 0036 namespace { 0037 // For now, just _always_ use the cache 0038 const uint MinimumCountForCache = 1; 0039 0040 QDebug fromTextStream(const QTextStream& out) 0041 { 0042 if (out.device()) 0043 return {out.device()}; 0044 return {out.string()}; 0045 } 0046 0047 struct IndexedDeclarationHandler { 0048 inline static int leftChild(const IndexedDeclaration& m_data) { return ((int)(m_data.dummyData().first)) - 1; } 0049 inline static void setLeftChild(IndexedDeclaration& m_data, int child) 0050 { 0051 m_data.setDummyData(qMakePair((uint)(child + 1), m_data.dummyData().second)); 0052 } 0053 inline static int rightChild(const IndexedDeclaration& m_data) { return ((int)m_data.dummyData().second) - 1; } 0054 inline static void setRightChild(IndexedDeclaration& m_data, int child) 0055 { 0056 m_data.setDummyData(qMakePair(m_data.dummyData().first, (uint)(child + 1))); 0057 } 0058 inline static void createFreeItem(IndexedDeclaration& data) 0059 { 0060 data = IndexedDeclaration(); 0061 data.setIsDummy(true); 0062 data.setDummyData(qMakePair(0u, 0u)); // Since we subtract 1, this equals children -1, -1 0063 } 0064 // Copies this item into the given one 0065 inline static void copyTo(const IndexedDeclaration& m_data, IndexedDeclaration& data) { data = m_data; } 0066 0067 inline static bool isFree(const IndexedDeclaration& m_data) { return m_data.isDummy(); } 0068 0069 inline static bool equals(const IndexedDeclaration& m_data, const IndexedDeclaration& rhs) { return m_data == rhs; } 0070 }; 0071 0072 struct DeclarationTopContextExtractor { 0073 inline static IndexedTopDUContext extract(const IndexedDeclaration& decl) { return decl.indexedTopContext(); } 0074 }; 0075 0076 struct DUContextTopContextExtractor { 0077 inline static IndexedTopDUContext extract(const IndexedDUContext& ctx) { return ctx.indexedTopContext(); } 0078 }; 0079 0080 struct RecursiveImportCacheRepository { 0081 static Utils::BasicSetRepository* repository() 0082 { 0083 static QRecursiveMutex mutex; 0084 static Utils::BasicSetRepository recursiveImportCacheRepositoryObject(QStringLiteral("Recursive Imports Cache"), 0085 &mutex, nullptr, false); 0086 return &recursiveImportCacheRepositoryObject; 0087 } 0088 }; 0089 0090 DEFINE_LIST_MEMBER_HASH(PersistentSymbolTableItem, declarations, IndexedDeclaration) 0091 0092 class PersistentSymbolTableItem 0093 { 0094 public: 0095 PersistentSymbolTableItem() : centralFreeItem(-1) 0096 { 0097 initializeAppendedLists(); 0098 } 0099 PersistentSymbolTableItem(const PersistentSymbolTableItem& rhs, bool dynamic = true) : id(rhs.id) 0100 , centralFreeItem(rhs.centralFreeItem) 0101 { 0102 initializeAppendedLists(dynamic); 0103 copyListsFrom(rhs); 0104 } 0105 0106 ~PersistentSymbolTableItem() 0107 { 0108 freeAppendedLists(); 0109 } 0110 0111 PersistentSymbolTableItem& operator=(const PersistentSymbolTableItem& rhs) = delete; 0112 0113 inline unsigned int hash() const 0114 { 0115 //We only compare the declaration. This allows us implementing a map, although the item-repository 0116 //originally represents a set. 0117 return id.index(); 0118 } 0119 0120 unsigned int itemSize() const 0121 { 0122 return dynamicSize(); 0123 } 0124 0125 uint classSize() const 0126 { 0127 return sizeof(PersistentSymbolTableItem); 0128 } 0129 0130 IndexedQualifiedIdentifier id; 0131 int centralFreeItem; 0132 0133 START_APPENDED_LISTS(PersistentSymbolTableItem); 0134 APPENDED_LIST_FIRST(PersistentSymbolTableItem, IndexedDeclaration, declarations); 0135 END_APPENDED_LISTS(PersistentSymbolTableItem, declarations); 0136 }; 0137 0138 class PersistentSymbolTableRequestItem 0139 { 0140 public: 0141 0142 PersistentSymbolTableRequestItem(const PersistentSymbolTableItem& item) : m_item(item) 0143 { 0144 } 0145 enum { 0146 AverageSize = 30 //This should be the approximate average size of an Item 0147 }; 0148 0149 unsigned int hash() const 0150 { 0151 return m_item.hash(); 0152 } 0153 0154 uint itemSize() const 0155 { 0156 return m_item.itemSize(); 0157 } 0158 0159 void createItem(PersistentSymbolTableItem* item) const 0160 { 0161 new (item) PersistentSymbolTableItem(m_item, false); 0162 } 0163 0164 static void destroy(PersistentSymbolTableItem* item, KDevelop::AbstractItemRepository&) 0165 { 0166 item->~PersistentSymbolTableItem(); 0167 } 0168 0169 static bool persistent(const PersistentSymbolTableItem*) 0170 { 0171 return true; //Nothing to do 0172 } 0173 0174 bool equals(const PersistentSymbolTableItem* item) const 0175 { 0176 return m_item.id == item->id; 0177 } 0178 0179 const PersistentSymbolTableItem& m_item; 0180 }; 0181 0182 using CachedDeclarations = KDevVarLengthArray<IndexedDeclaration>; 0183 using CachedDeclarationsByImports = QHash<TopDUContext::IndexedRecursiveImports, CachedDeclarations>; 0184 using Declarations = ConstantConvenientEmbeddedSet<IndexedDeclaration, IndexedDeclarationHandler>; 0185 using CachedIndexedRecursiveImports = 0186 Utils::StorableSet<IndexedTopDUContext, IndexedTopDUContextIndexConversion, RecursiveImportCacheRepository, true>; 0187 using FilteredDeclarationIterator = 0188 ConvenientEmbeddedSetTreeFilterIterator<IndexedDeclaration, IndexedDeclarationHandler, IndexedTopDUContext, 0189 CachedIndexedRecursiveImports, DeclarationTopContextExtractor>; 0190 0191 // Maps declaration-ids to declarations, together with some caches 0192 class PersistentSymbolTableRepo 0193 : public ItemRepository<PersistentSymbolTableItem, PersistentSymbolTableRequestItem, true, QRecursiveMutex> 0194 { 0195 using ItemRepository::ItemRepository; 0196 0197 public: 0198 QHash<IndexedQualifiedIdentifier, CachedDeclarationsByImports> declarationsCache; 0199 0200 // We cache the imports so the currently used nodes are very close in memory, which leads to much better CPU cache 0201 // utilization 0202 QHash<TopDUContext::IndexedRecursiveImports, CachedIndexedRecursiveImports> importsCache; 0203 0204 /// Counts how many recursive calls to PersistentSymbolTable::visit* functions are ongoing 0205 /// and hold the repository's mutex lock. Is used only to assert correct API use. 0206 mutable uint ongoingIterations = 0; 0207 }; 0208 0209 #if VERIFY_VISIT_NESTING 0210 struct IterationCounter 0211 { 0212 explicit IterationCounter(const PersistentSymbolTableRepo& repo) 0213 : repo(repo) 0214 { 0215 ++repo.ongoingIterations; 0216 } 0217 0218 ~IterationCounter() 0219 { 0220 --repo.ongoingIterations; 0221 } 0222 0223 private: 0224 Q_DISABLE_COPY_MOVE(IterationCounter) 0225 0226 const PersistentSymbolTableRepo& repo; 0227 }; 0228 #endif 0229 } 0230 0231 template<> 0232 class ItemRepositoryFor<PersistentSymbolTable> 0233 { 0234 friend struct LockedItemRepository; 0235 static PersistentSymbolTableRepo& repo() 0236 { 0237 static QRecursiveMutex mutex; 0238 static PersistentSymbolTableRepo repo { QStringLiteral("Persistent Declaration Table"), &mutex }; 0239 return repo; 0240 } 0241 }; 0242 0243 void PersistentSymbolTable::clearCache() 0244 { 0245 LockedItemRepository::write<PersistentSymbolTable>([](PersistentSymbolTableRepo& repo) { 0246 Q_ASSERT_X(repo.ongoingIterations == 0, Q_FUNC_INFO, "don't call clearCache directly from a visitor"); 0247 0248 repo.importsCache.clear(); 0249 repo.declarationsCache.clear(); 0250 }); 0251 } 0252 0253 PersistentSymbolTable::PersistentSymbolTable() 0254 { 0255 // PersistentSymbolTableRepo::importsCache uses RecursiveImportCacheRepository, so the cache repository must be 0256 // destroyed after and therefore created before the persistent symbol table repository. 0257 RecursiveImportCacheRepository::repository(); 0258 LockedItemRepository::initialize<PersistentSymbolTable>(); 0259 } 0260 0261 PersistentSymbolTable::~PersistentSymbolTable() = default; 0262 0263 void PersistentSymbolTable::addDeclaration(const IndexedQualifiedIdentifier& id, const IndexedDeclaration& declaration) 0264 { 0265 ENSURE_CHAIN_WRITE_LOCKED 0266 0267 PersistentSymbolTableItem item; 0268 item.id = id; 0269 0270 LockedItemRepository::write<PersistentSymbolTable>([&item, &declaration](PersistentSymbolTableRepo& repo) { 0271 Q_ASSERT_X(repo.ongoingIterations == 0, Q_FUNC_INFO, "don't call addDeclaration directly from a visitor"); 0272 0273 repo.declarationsCache.remove(item.id); 0274 0275 uint index = repo.findIndex(item); 0276 0277 if (index) { 0278 // Check whether the item is already in the mapped list, else copy the list into the new created item 0279 const PersistentSymbolTableItem* oldItem = repo.itemFromIndex(index); 0280 0281 EmbeddedTreeAlgorithms<IndexedDeclaration, IndexedDeclarationHandler> alg( 0282 oldItem->declarations(), oldItem->declarationsSize(), oldItem->centralFreeItem); 0283 0284 if (alg.indexOf(declaration) != -1) 0285 return; 0286 0287 DynamicItem<PersistentSymbolTableItem, true> editableItem = repo.dynamicItemFromIndex(index); 0288 0289 EmbeddedTreeAddItem<IndexedDeclaration, IndexedDeclarationHandler> add( 0290 const_cast<IndexedDeclaration*>(editableItem->declarations()), editableItem->declarationsSize(), 0291 editableItem->centralFreeItem, declaration); 0292 0293 uint newSize = add.newItemCount(); 0294 if (newSize != editableItem->declarationsSize()) { 0295 // We need to resize. Update and fill the new item, and delete the old item. 0296 item.declarationsList().resize(newSize); 0297 add.transferData(item.declarationsList().data(), newSize, &item.centralFreeItem); 0298 0299 repo.deleteItem(index); 0300 Q_ASSERT(!repo.findIndex(item)); 0301 } else { 0302 // We're fine, the item could be added to the existing list 0303 return; 0304 } 0305 } else { 0306 item.declarationsList().append(declaration); 0307 } 0308 0309 // This inserts the changed item 0310 repo.index(item); 0311 }); 0312 } 0313 0314 void PersistentSymbolTable::removeDeclaration(const IndexedQualifiedIdentifier& id, 0315 const IndexedDeclaration& declaration) 0316 { 0317 ENSURE_CHAIN_WRITE_LOCKED 0318 0319 PersistentSymbolTableItem item; 0320 item.id = id; 0321 0322 LockedItemRepository::write<PersistentSymbolTable>([&item, &declaration](PersistentSymbolTableRepo& repo) { 0323 Q_ASSERT_X(repo.ongoingIterations == 0, Q_FUNC_INFO, "don't call removeDeclaration directly from a visitor"); 0324 0325 repo.declarationsCache.remove(item.id); 0326 0327 uint index = repo.findIndex(item); 0328 0329 if (index) { 0330 // Check whether the item is already in the mapped list, else copy the list into the new created item 0331 const PersistentSymbolTableItem* oldItem = repo.itemFromIndex(index); 0332 0333 EmbeddedTreeAlgorithms<IndexedDeclaration, IndexedDeclarationHandler> alg( 0334 oldItem->declarations(), oldItem->declarationsSize(), oldItem->centralFreeItem); 0335 0336 if (alg.indexOf(declaration) == -1) 0337 return; 0338 0339 DynamicItem<PersistentSymbolTableItem, true> editableItem = repo.dynamicItemFromIndex(index); 0340 0341 EmbeddedTreeRemoveItem<IndexedDeclaration, IndexedDeclarationHandler> remove( 0342 const_cast<IndexedDeclaration*>(editableItem->declarations()), editableItem->declarationsSize(), 0343 editableItem->centralFreeItem, declaration); 0344 0345 uint newSize = remove.newItemCount(); 0346 if (newSize != editableItem->declarationsSize()) { 0347 // We need to resize. Update and fill the new item, and delete the old item. 0348 item.declarationsList().resize(newSize); 0349 remove.transferData(item.declarationsList().data(), newSize, &item.centralFreeItem); 0350 0351 repo.deleteItem(index); 0352 Q_ASSERT(!repo.findIndex(item)); 0353 } else { 0354 // We're fine, the item could be added to the existing list 0355 return; 0356 } 0357 } 0358 0359 // This inserts the changed item 0360 if (item.declarationsSize()) 0361 repo.index(item); 0362 }); 0363 } 0364 0365 void PersistentSymbolTable::visitDeclarations(const IndexedQualifiedIdentifier& id, 0366 const DeclarationVisitor& visitor) const 0367 { 0368 ENSURE_CHAIN_READ_LOCKED 0369 0370 PersistentSymbolTableItem item; 0371 item.id = id; 0372 0373 LockedItemRepository::read<PersistentSymbolTable>([&item, &visitor](const PersistentSymbolTableRepo& repo) { 0374 ifVerifyVisitNesting(const auto guard = IterationCounter(repo);) 0375 0376 uint index = repo.findIndex(item); 0377 0378 if (!index) { 0379 return; 0380 } 0381 0382 const PersistentSymbolTableItem* repositoryItem = repo.itemFromIndex(index); 0383 0384 const auto numDeclarations = repositoryItem->declarationsSize(); 0385 const auto declarations = repositoryItem->declarations(); 0386 for (uint i = 0; i < numDeclarations; ++i) { 0387 if (visitor(declarations[i]) == VisitorState::Break) { 0388 break; 0389 } 0390 } 0391 }); 0392 } 0393 0394 void PersistentSymbolTable::visitFilteredDeclarations(const IndexedQualifiedIdentifier& id, 0395 const TopDUContext::IndexedRecursiveImports& visibility, 0396 const DeclarationVisitor& visitor) const 0397 { 0398 ENSURE_CHAIN_READ_LOCKED 0399 0400 PersistentSymbolTableItem item; 0401 item.id = id; 0402 0403 // This function does not modify the item repository. write() rather than read() is called only in order to modify 0404 // declarationsCache and importsCache. Making these two cache data members mutable would allow to call read() here. 0405 LockedItemRepository::write<PersistentSymbolTable>([&](PersistentSymbolTableRepo& repo) { 0406 ifVerifyVisitNesting(const auto guard = IterationCounter(repo);) 0407 0408 uint index = repo.findIndex(item); 0409 if (!index) { 0410 return; 0411 } 0412 0413 const PersistentSymbolTableItem* repositoryItem = repo.itemFromIndex(index); 0414 const auto declarations = Declarations(repositoryItem->declarations(), repositoryItem->declarationsSize(), 0415 repositoryItem->centralFreeItem); 0416 0417 // TODO Qt6: revisit when porting as this code - when called recursively - relies on QHash reference stability: 0418 // 1. `cachedImports` is a copy of a value inside importsCache, so it doesn't rely on QHash reference stability. 0419 // 2. `cached` is a reference to a value inside declarationsCache, but it is not used after the visitor is 0420 // called, so it does not rely on QHash reference stability either. 0421 // 3. `cache` is a reference to a value inside a value inside declarationsCache. `cache` is filled before 0422 // `filterIterator` is constructed, so it is recursion-safe. `cache` is not used after the visitor is called, 0423 // so it also does not rely on QHash reference stability. 0424 // 4. `filterIterator` contains a copy of the constData() pointer of a KDevVarLengthArray value inside a QHash. 0425 // If QVarLengthArray::size() is less or equal to its Prealloc, constData() points to an array within the 0426 // QVarLengthArray object. A next recursive call to visitFilteredDeclarations() may insert an id into 0427 // declarationsCache or a visibility into a value of declarationsCache. Such an insertion can move the 0428 // QVarLengthArray object in memory and make the copy of constData() pointer in `filterIterator` dangling. 0429 // A possible fix is to store raw pointers KDevVarLengthArray* as values of values of declarationsCache; 0430 // delete the pointers in ~PersistentSymbolTableRepo() and before removing values or values of values of 0431 // declarationsCache. An alternative fix is to create a local copy of the KDevVarLengthArray and pass the 0432 // copy's constData() to filterIterator. Better yet, make CachedDeclarations = QVector<IndexedDeclaration> 0433 // instead of KDevVarLengthArray<IndexedDeclaration>: the QVector::constData() pointer does not change until 0434 // the QVector is modified, which it isn't in this case. 0435 CachedIndexedRecursiveImports cachedImports; 0436 auto it = repo.importsCache.constFind(visibility); 0437 if (it != repo.importsCache.constEnd()) { 0438 cachedImports = *it; 0439 } else { 0440 cachedImports = CachedIndexedRecursiveImports(visibility.set().stdSet()); 0441 repo.importsCache.insert(visibility, cachedImports); 0442 } 0443 0444 auto filterIterator = [&]() { 0445 if (declarations.dataSize() > MinimumCountForCache) { 0446 // Do visibility caching 0447 auto& cached = repo.declarationsCache[id]; 0448 auto cacheIt = cached.constFind(visibility); 0449 if (cacheIt != cached.constEnd()) { 0450 return FilteredDeclarationIterator( 0451 Declarations::Iterator(cacheIt->constData(), cacheIt->size(), -1), cachedImports); 0452 } 0453 0454 auto insertIt = cached.insert(visibility, {}); 0455 auto& cache = *insertIt; 0456 { 0457 auto cacheVisitor = [&cache](const IndexedDeclaration& decl) { 0458 cache.append(decl); 0459 }; 0460 0461 using FilteredDeclarationCacheVisitor = 0462 ConvenientEmbeddedSetTreeFilterVisitor<IndexedDeclaration, IndexedDeclarationHandler, 0463 IndexedTopDUContext, CachedIndexedRecursiveImports, 0464 DeclarationTopContextExtractor, decltype(cacheVisitor)>; 0465 0466 // The visitor visits all the declarations from within its constructor 0467 FilteredDeclarationCacheVisitor visitor(cacheVisitor, declarations.iterator(), cachedImports); 0468 } 0469 0470 return FilteredDeclarationIterator(Declarations::Iterator(cache.constData(), cache.size(), -1), 0471 cachedImports, true); 0472 } else { 0473 return FilteredDeclarationIterator(declarations.iterator(), cachedImports); 0474 } 0475 }(); 0476 0477 for (; filterIterator; ++filterIterator) { 0478 if (visitor(*filterIterator) == VisitorState::Break) { 0479 break; 0480 } 0481 } 0482 }); 0483 } 0484 0485 struct DebugVisitor 0486 { 0487 explicit DebugVisitor(const QTextStream& _out) 0488 : out(_out) 0489 { 0490 } 0491 0492 bool operator()(const PersistentSymbolTableItem* item) 0493 { 0494 QDebug qout = fromTextStream(out); 0495 QualifiedIdentifier id(item->id.identifier()); 0496 if (identifiers.contains(id)) { 0497 qout << "identifier" << id.toString() << "appears for" << identifiers[id] << "th time"; 0498 } 0499 0500 ++identifiers[id]; 0501 0502 for (uint a = 0; a < item->declarationsSize(); ++a) { 0503 IndexedDeclaration decl(item->declarations()[a]); 0504 if (!decl.isDummy()) { 0505 if (declarations.contains(decl)) { 0506 qout << "declaration found for multiple identifiers. Previous identifier:" 0507 << declarations[decl].toString() << "current identifier:" << id.toString() << Qt::endl; 0508 } else { 0509 declarations.insert(decl, id); 0510 } 0511 } 0512 if (decl.data() && decl.data()->qualifiedIdentifier() != item->id.identifier()) { 0513 qout << decl.data()->url().str() << "declaration" << decl.data()->qualifiedIdentifier() 0514 << "is registered as" << item->id.identifier() << Qt::endl; 0515 } 0516 0517 const QString url = IndexedTopDUContext(decl.topContextIndex()).url().str(); 0518 if (!decl.data() && !decl.isDummy()) { 0519 qout << "Item in symbol-table is invalid:" << id.toString() << "- localIndex:" << decl.localIndex() 0520 << "- url:" << url << Qt::endl; 0521 } else { 0522 qout << "Item in symbol-table:" << id.toString() << "- localIndex:" << decl.localIndex() << "- url:" << 0523 url; 0524 if (auto d = decl.data()) { 0525 qout << "- range:" << d->range(); 0526 } else { 0527 qout << "- null declaration"; 0528 } 0529 qout << Qt::endl; 0530 } 0531 } 0532 0533 return true; 0534 } 0535 0536 const QTextStream& out; 0537 QHash<QualifiedIdentifier, uint> identifiers; 0538 QHash<IndexedDeclaration, QualifiedIdentifier> declarations; 0539 }; 0540 0541 void PersistentSymbolTable::dump(const QTextStream& out) 0542 { 0543 QDebug qout = fromTextStream(out); 0544 DebugVisitor v(out); 0545 0546 LockedItemRepository::read<PersistentSymbolTable>([&](const PersistentSymbolTableRepo& repo) { 0547 Q_ASSERT_X(repo.ongoingIterations == 0, Q_FUNC_INFO, "don't call dump directly from a visitor"); 0548 0549 repo.visitAllItems(v); 0550 0551 qout << "Statistics:" << Qt::endl; 0552 qout << repo.statistics() << Qt::endl; 0553 }); 0554 } 0555 0556 PersistentSymbolTable& PersistentSymbolTable::self() 0557 { 0558 static PersistentSymbolTable ret; 0559 return ret; 0560 } 0561 }