File: persistentsymboltable.cpp

package info (click to toggle)
kdevelop 4%3A22.12.2-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 70,096 kB
  • sloc: cpp: 284,635; javascript: 3,558; python: 3,422; sh: 1,319; ansic: 685; xml: 331; php: 95; lisp: 66; makefile: 39; sed: 12
file content (561 lines) | stat: -rw-r--r-- 21,514 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
/*
    SPDX-FileCopyrightText: 2008 David Nolden <david.nolden.kdevelop@art-master.de>

    SPDX-License-Identifier: LGPL-2.0-only
*/

#include "persistentsymboltable.h"

#include <QHash>

#include "declaration.h"
#include "declarationid.h"
#include "appendedlist.h"
#include "serialization/itemrepository.h"
#include "identifier.h"
#include "ducontext.h"
#include "topducontext.h"
#include "duchain.h"
#include "duchainlock.h"

#include <util/convenientfreelist.h>
#include <util/embeddedfreetree.h>

#include <language/util/setrepository.h>

#if defined(QT_NO_DEBUG) && !defined(QT_FORCE_ASSERTS)
#define VERIFY_VISIT_NESTING 0
#define ifVerifyVisitNesting(x)
#else
#define VERIFY_VISIT_NESTING 1
#define ifVerifyVisitNesting(x) x
#endif

namespace KDevelop {

namespace {
// For now, just _always_ use the cache
const uint MinimumCountForCache = 1;

QDebug fromTextStream(const QTextStream& out)
{
    if (out.device())
        return {out.device()};
    return {out.string()};
}

struct IndexedDeclarationHandler {
    inline static int leftChild(const IndexedDeclaration& m_data) { return ((int)(m_data.dummyData().first)) - 1; }
    inline static void setLeftChild(IndexedDeclaration& m_data, int child)
    {
        m_data.setDummyData(qMakePair((uint)(child + 1), m_data.dummyData().second));
    }
    inline static int rightChild(const IndexedDeclaration& m_data) { return ((int)m_data.dummyData().second) - 1; }
    inline static void setRightChild(IndexedDeclaration& m_data, int child)
    {
        m_data.setDummyData(qMakePair(m_data.dummyData().first, (uint)(child + 1)));
    }
    inline static void createFreeItem(IndexedDeclaration& data)
    {
        data = IndexedDeclaration();
        data.setIsDummy(true);
        data.setDummyData(qMakePair(0u, 0u)); // Since we subtract 1, this equals children -1, -1
    }
    // Copies this item into the given one
    inline static void copyTo(const IndexedDeclaration& m_data, IndexedDeclaration& data) { data = m_data; }

    inline static bool isFree(const IndexedDeclaration& m_data) { return m_data.isDummy(); }

    inline static bool equals(const IndexedDeclaration& m_data, const IndexedDeclaration& rhs) { return m_data == rhs; }
};

struct DeclarationTopContextExtractor {
    inline static IndexedTopDUContext extract(const IndexedDeclaration& decl) { return decl.indexedTopContext(); }
};

struct DUContextTopContextExtractor {
    inline static IndexedTopDUContext extract(const IndexedDUContext& ctx) { return ctx.indexedTopContext(); }
};

struct RecursiveImportCacheRepository {
    static Utils::BasicSetRepository* repository()
    {
        static QRecursiveMutex mutex;
        static Utils::BasicSetRepository recursiveImportCacheRepositoryObject(QStringLiteral("Recursive Imports Cache"),
                                                                              &mutex, nullptr, false);
        return &recursiveImportCacheRepositoryObject;
    }
};

DEFINE_LIST_MEMBER_HASH(PersistentSymbolTableItem, declarations, IndexedDeclaration)

class PersistentSymbolTableItem
{
public:
    PersistentSymbolTableItem() : centralFreeItem(-1)
    {
        initializeAppendedLists();
    }
    PersistentSymbolTableItem(const PersistentSymbolTableItem& rhs, bool dynamic = true) : id(rhs.id)
        , centralFreeItem(rhs.centralFreeItem)
    {
        initializeAppendedLists(dynamic);
        copyListsFrom(rhs);
    }

    ~PersistentSymbolTableItem()
    {
        freeAppendedLists();
    }

    PersistentSymbolTableItem& operator=(const PersistentSymbolTableItem& rhs) = delete;

    inline unsigned int hash() const
    {
        //We only compare the declaration. This allows us implementing a map, although the item-repository
        //originally represents a set.
        return id.index();
    }

    unsigned int itemSize() const
    {
        return dynamicSize();
    }

    uint classSize() const
    {
        return sizeof(PersistentSymbolTableItem);
    }

    IndexedQualifiedIdentifier id;
    int centralFreeItem;

    START_APPENDED_LISTS(PersistentSymbolTableItem);
    APPENDED_LIST_FIRST(PersistentSymbolTableItem, IndexedDeclaration, declarations);
    END_APPENDED_LISTS(PersistentSymbolTableItem, declarations);
};

class PersistentSymbolTableRequestItem
{
public:

    PersistentSymbolTableRequestItem(const PersistentSymbolTableItem& item) : m_item(item)
    {
    }
    enum {
        AverageSize = 30 //This should be the approximate average size of an Item
    };

    unsigned int hash() const
    {
        return m_item.hash();
    }

    uint itemSize() const
    {
        return m_item.itemSize();
    }

    void createItem(PersistentSymbolTableItem* item) const
    {
        new (item) PersistentSymbolTableItem(m_item, false);
    }

    static void destroy(PersistentSymbolTableItem* item, KDevelop::AbstractItemRepository&)
    {
        item->~PersistentSymbolTableItem();
    }

    static bool persistent(const PersistentSymbolTableItem*)
    {
        return true; //Nothing to do
    }

    bool equals(const PersistentSymbolTableItem* item) const
    {
        return m_item.id == item->id;
    }

    const PersistentSymbolTableItem& m_item;
};

using CachedDeclarations = KDevVarLengthArray<IndexedDeclaration>;
using CachedDeclarationsByImports = QHash<TopDUContext::IndexedRecursiveImports, CachedDeclarations>;
using Declarations = ConstantConvenientEmbeddedSet<IndexedDeclaration, IndexedDeclarationHandler>;
using CachedIndexedRecursiveImports =
    Utils::StorableSet<IndexedTopDUContext, IndexedTopDUContextIndexConversion, RecursiveImportCacheRepository, true>;
using FilteredDeclarationIterator =
    ConvenientEmbeddedSetTreeFilterIterator<IndexedDeclaration, IndexedDeclarationHandler, IndexedTopDUContext,
                                            CachedIndexedRecursiveImports, DeclarationTopContextExtractor>;

// Maps declaration-ids to declarations, together with some caches
class PersistentSymbolTableRepo
    : public ItemRepository<PersistentSymbolTableItem, PersistentSymbolTableRequestItem, true, QRecursiveMutex>
{
    using ItemRepository::ItemRepository;

public:
    QHash<IndexedQualifiedIdentifier, CachedDeclarationsByImports> declarationsCache;

    // We cache the imports so the currently used nodes are very close in memory, which leads to much better CPU cache
    // utilization
    QHash<TopDUContext::IndexedRecursiveImports, CachedIndexedRecursiveImports> importsCache;

    /// Counts how many recursive calls to PersistentSymbolTable::visit* functions are ongoing
    /// and hold the repository's mutex lock. Is used only to assert correct API use.
    mutable uint ongoingIterations = 0;
};

#if VERIFY_VISIT_NESTING
struct IterationCounter
{
    explicit IterationCounter(const PersistentSymbolTableRepo& repo)
        : repo(repo)
    {
        ++repo.ongoingIterations;
    }

    ~IterationCounter()
    {
        --repo.ongoingIterations;
    }

private:
    Q_DISABLE_COPY_MOVE(IterationCounter)

    const PersistentSymbolTableRepo& repo;
};
#endif
}

template<>
class ItemRepositoryFor<PersistentSymbolTable>
{
    friend struct LockedItemRepository;
    static PersistentSymbolTableRepo& repo()
    {
        static QRecursiveMutex mutex;
        static PersistentSymbolTableRepo repo { QStringLiteral("Persistent Declaration Table"), &mutex };
        return repo;
    }
};

void PersistentSymbolTable::clearCache()
{
    LockedItemRepository::write<PersistentSymbolTable>([](PersistentSymbolTableRepo& repo) {
        Q_ASSERT_X(repo.ongoingIterations == 0, Q_FUNC_INFO, "don't call clearCache directly from a visitor");

        repo.importsCache.clear();
        repo.declarationsCache.clear();
    });
}

PersistentSymbolTable::PersistentSymbolTable()
{
    // PersistentSymbolTableRepo::importsCache uses RecursiveImportCacheRepository, so the cache repository must be
    // destroyed after and therefore created before the persistent symbol table repository.
    RecursiveImportCacheRepository::repository();
    LockedItemRepository::initialize<PersistentSymbolTable>();
}

PersistentSymbolTable::~PersistentSymbolTable() = default;

void PersistentSymbolTable::addDeclaration(const IndexedQualifiedIdentifier& id, const IndexedDeclaration& declaration)
{
    ENSURE_CHAIN_WRITE_LOCKED

    PersistentSymbolTableItem item;
    item.id = id;

    LockedItemRepository::write<PersistentSymbolTable>([&item, &declaration](PersistentSymbolTableRepo& repo) {
        Q_ASSERT_X(repo.ongoingIterations == 0, Q_FUNC_INFO, "don't call addDeclaration directly from a visitor");

        repo.declarationsCache.remove(item.id);

        uint index = repo.findIndex(item);

        if (index) {
            // Check whether the item is already in the mapped list, else copy the list into the new created item
            const PersistentSymbolTableItem* oldItem = repo.itemFromIndex(index);

            EmbeddedTreeAlgorithms<IndexedDeclaration, IndexedDeclarationHandler> alg(
                oldItem->declarations(), oldItem->declarationsSize(), oldItem->centralFreeItem);

            if (alg.indexOf(declaration) != -1)
                return;

            DynamicItem<PersistentSymbolTableItem, true> editableItem = repo.dynamicItemFromIndex(index);

            EmbeddedTreeAddItem<IndexedDeclaration, IndexedDeclarationHandler> add(
                const_cast<IndexedDeclaration*>(editableItem->declarations()), editableItem->declarationsSize(),
                editableItem->centralFreeItem, declaration);

            uint newSize = add.newItemCount();
            if (newSize != editableItem->declarationsSize()) {
                // We need to resize. Update and fill the new item, and delete the old item.
                item.declarationsList().resize(newSize);
                add.transferData(item.declarationsList().data(), newSize, &item.centralFreeItem);

                repo.deleteItem(index);
                Q_ASSERT(!repo.findIndex(item));
            } else {
                // We're fine, the item could be added to the existing list
                return;
            }
        } else {
            item.declarationsList().append(declaration);
        }

        // This inserts the changed item
        repo.index(item);
    });
}

void PersistentSymbolTable::removeDeclaration(const IndexedQualifiedIdentifier& id,
                                              const IndexedDeclaration& declaration)
{
    ENSURE_CHAIN_WRITE_LOCKED

    PersistentSymbolTableItem item;
    item.id = id;

    LockedItemRepository::write<PersistentSymbolTable>([&item, &declaration](PersistentSymbolTableRepo& repo) {
        Q_ASSERT_X(repo.ongoingIterations == 0, Q_FUNC_INFO, "don't call removeDeclaration directly from a visitor");

        repo.declarationsCache.remove(item.id);

        uint index = repo.findIndex(item);

        if (index) {
            // Check whether the item is already in the mapped list, else copy the list into the new created item
            const PersistentSymbolTableItem* oldItem = repo.itemFromIndex(index);

            EmbeddedTreeAlgorithms<IndexedDeclaration, IndexedDeclarationHandler> alg(
                oldItem->declarations(), oldItem->declarationsSize(), oldItem->centralFreeItem);

            if (alg.indexOf(declaration) == -1)
                return;

            DynamicItem<PersistentSymbolTableItem, true> editableItem = repo.dynamicItemFromIndex(index);

            EmbeddedTreeRemoveItem<IndexedDeclaration, IndexedDeclarationHandler> remove(
                const_cast<IndexedDeclaration*>(editableItem->declarations()), editableItem->declarationsSize(),
                editableItem->centralFreeItem, declaration);

            uint newSize = remove.newItemCount();
            if (newSize != editableItem->declarationsSize()) {
                // We need to resize. Update and fill the new item, and delete the old item.
                item.declarationsList().resize(newSize);
                remove.transferData(item.declarationsList().data(), newSize, &item.centralFreeItem);

                repo.deleteItem(index);
                Q_ASSERT(!repo.findIndex(item));
            } else {
                // We're fine, the item could be added to the existing list
                return;
            }
        }

        // This inserts the changed item
        if (item.declarationsSize())
            repo.index(item);
    });
}

void PersistentSymbolTable::visitDeclarations(const IndexedQualifiedIdentifier& id,
                                              const DeclarationVisitor& visitor) const
{
    ENSURE_CHAIN_READ_LOCKED

    PersistentSymbolTableItem item;
    item.id = id;

    LockedItemRepository::read<PersistentSymbolTable>([&item, &visitor](const PersistentSymbolTableRepo& repo) {
        ifVerifyVisitNesting(const auto guard = IterationCounter(repo);)

        uint index = repo.findIndex(item);

        if (!index) {
            return;
        }

        const PersistentSymbolTableItem* repositoryItem = repo.itemFromIndex(index);

        const auto numDeclarations = repositoryItem->declarationsSize();
        const auto declarations = repositoryItem->declarations();
        for (uint i = 0; i < numDeclarations; ++i) {
            if (visitor(declarations[i]) == VisitorState::Break) {
                break;
            }
        }
    });
}

void PersistentSymbolTable::visitFilteredDeclarations(const IndexedQualifiedIdentifier& id,
                                                      const TopDUContext::IndexedRecursiveImports& visibility,
                                                      const DeclarationVisitor& visitor) const
{
    ENSURE_CHAIN_READ_LOCKED

    PersistentSymbolTableItem item;
    item.id = id;

    // This function does not modify the item repository. write() rather than read() is called only in order to modify
    // declarationsCache and importsCache. Making these two cache data members mutable would allow to call read() here.
    LockedItemRepository::write<PersistentSymbolTable>([&](PersistentSymbolTableRepo& repo) {
        ifVerifyVisitNesting(const auto guard = IterationCounter(repo);)

        uint index = repo.findIndex(item);
        if (!index) {
            return;
        }

        const PersistentSymbolTableItem* repositoryItem = repo.itemFromIndex(index);
        const auto declarations = Declarations(repositoryItem->declarations(), repositoryItem->declarationsSize(),
                                               repositoryItem->centralFreeItem);

        // TODO Qt6: revisit when porting as this code - when called recursively - relies on QHash reference stability:
        // 1. `cachedImports` is a copy of a value inside importsCache, so it doesn't rely on QHash reference stability.
        // 2. `cached` is a reference to a value inside declarationsCache, but it is not used after the visitor is
        //    called, so it does not rely on QHash reference stability either.
        // 3. `cache` is a reference to a value inside a value inside declarationsCache. `cache` is filled before
        //    `filterIterator` is constructed, so it is recursion-safe. `cache` is not used after the visitor is called,
        //    so it also does not rely on QHash reference stability.
        // 4. `filterIterator` contains a copy of the constData() pointer of a KDevVarLengthArray value inside a QHash.
        //    If QVarLengthArray::size() is less or equal to its Prealloc, constData() points to an array within the
        //    QVarLengthArray object. A next recursive call to visitFilteredDeclarations() may insert an id into
        //    declarationsCache or a visibility into a value of declarationsCache. Such an insertion can move the
        //    QVarLengthArray object in memory and make the copy of constData() pointer in `filterIterator` dangling.
        //    A possible fix is to store raw pointers KDevVarLengthArray* as values of values of declarationsCache;
        //    delete the pointers in ~PersistentSymbolTableRepo() and before removing values or values of values of
        //    declarationsCache. An alternative fix is to create a local copy of the KDevVarLengthArray and pass the
        //    copy's constData() to filterIterator. Better yet, make CachedDeclarations = QVector<IndexedDeclaration>
        //    instead of KDevVarLengthArray<IndexedDeclaration>: the QVector::constData() pointer does not change until
        //    the QVector is modified, which it isn't in this case.
        CachedIndexedRecursiveImports cachedImports;
        auto it = repo.importsCache.constFind(visibility);
        if (it != repo.importsCache.constEnd()) {
            cachedImports = *it;
        } else {
            cachedImports = CachedIndexedRecursiveImports(visibility.set().stdSet());
            repo.importsCache.insert(visibility, cachedImports);
        }

        auto filterIterator = [&]() {
            if (declarations.dataSize() > MinimumCountForCache) {
                // Do visibility caching
                auto& cached = repo.declarationsCache[id];
                auto cacheIt = cached.constFind(visibility);
                if (cacheIt != cached.constEnd()) {
                    return FilteredDeclarationIterator(
                        Declarations::Iterator(cacheIt->constData(), cacheIt->size(), -1), cachedImports);
                }

                auto insertIt = cached.insert(visibility, {});
                auto& cache = *insertIt;
                {
                    auto cacheVisitor = [&cache](const IndexedDeclaration& decl) {
                        cache.append(decl);
                    };

                    using FilteredDeclarationCacheVisitor =
                        ConvenientEmbeddedSetTreeFilterVisitor<IndexedDeclaration, IndexedDeclarationHandler,
                                                               IndexedTopDUContext, CachedIndexedRecursiveImports,
                                                               DeclarationTopContextExtractor, decltype(cacheVisitor)>;

                    // The visitor visits all the declarations from within its constructor
                    FilteredDeclarationCacheVisitor visitor(cacheVisitor, declarations.iterator(), cachedImports);
                }

                return FilteredDeclarationIterator(Declarations::Iterator(cache.constData(), cache.size(), -1),
                                                   cachedImports, true);
            } else {
                return FilteredDeclarationIterator(declarations.iterator(), cachedImports);
            }
        }();

        for (; filterIterator; ++filterIterator) {
            if (visitor(*filterIterator) == VisitorState::Break) {
                break;
            }
        }
    });
}

struct DebugVisitor
{
    explicit DebugVisitor(const QTextStream& _out)
        : out(_out)
    {
    }

    bool operator()(const PersistentSymbolTableItem* item)
    {
        QDebug qout = fromTextStream(out);
        QualifiedIdentifier id(item->id.identifier());
        if (identifiers.contains(id)) {
            qout << "identifier" << id.toString() << "appears for" << identifiers[id] << "th time";
        }

        ++identifiers[id];

        for (uint a = 0; a < item->declarationsSize(); ++a) {
            IndexedDeclaration decl(item->declarations()[a]);
            if (!decl.isDummy()) {
                if (declarations.contains(decl)) {
                    qout << "declaration found for multiple identifiers. Previous identifier:"
                         << declarations[decl].toString() << "current identifier:" << id.toString() << Qt::endl;
                } else {
                    declarations.insert(decl, id);
                }
            }
            if (decl.data() && decl.data()->qualifiedIdentifier() != item->id.identifier()) {
                qout << decl.data()->url().str() << "declaration" << decl.data()->qualifiedIdentifier()
                     << "is registered as" << item->id.identifier() << Qt::endl;
            }

            const QString url = IndexedTopDUContext(decl.topContextIndex()).url().str();
            if (!decl.data() && !decl.isDummy()) {
                qout << "Item in symbol-table is invalid:" << id.toString() << "- localIndex:" << decl.localIndex()
                     << "- url:" << url << Qt::endl;
            } else {
                qout << "Item in symbol-table:" << id.toString() << "- localIndex:" << decl.localIndex() << "- url:" <<
                    url;
                if (auto d = decl.data()) {
                    qout << "- range:" << d->range();
                } else {
                    qout << "- null declaration";
                }
                qout << Qt::endl;
            }
        }

        return true;
    }

    const QTextStream& out;
    QHash<QualifiedIdentifier, uint> identifiers;
    QHash<IndexedDeclaration, QualifiedIdentifier> declarations;
};

void PersistentSymbolTable::dump(const QTextStream& out)
{
    QDebug qout = fromTextStream(out);
    DebugVisitor v(out);

    LockedItemRepository::read<PersistentSymbolTable>([&](const PersistentSymbolTableRepo& repo) {
        Q_ASSERT_X(repo.ongoingIterations == 0, Q_FUNC_INFO, "don't call dump directly from a visitor");

        repo.visitAllItems(v);

        qout << "Statistics:" << Qt::endl;
        qout << repo.statistics() << Qt::endl;
    });
}

PersistentSymbolTable& PersistentSymbolTable::self()
{
    static PersistentSymbolTable ret;
    return ret;
}
}