File indexing completed on 2024-04-28 03:53:10

0001 /*
0002     This file is part of the KDE libraries
0003 
0004     SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
0005     SPDX-FileCopyrightText: 2002 Michael Matz <matz@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 /* Fast zone memory allocator with deallocation support, for use as obstack
0011    or as general purpose allocator.  It does no compaction.  If the usage
0012    pattern is non-optimal it might waste some memory while running.  E.g.
0013    allocating many small things at once, and then deallocating only every
0014    second one, there is a high chance, that actually no memory is freed.
0015  */
0016 
0017 #include "kzoneallocator_p.h"
0018 
0019 #include <kcompletion_debug.h>
0020 
0021 #include <QList>
0022 
0023 #include <stdio.h>
0024 
0025 class KZoneAllocator::MemBlock
0026 {
0027 public:
0028     MemBlock(size_t s)
0029         : size(s)
0030         , ref(0)
0031         , older(nullptr)
0032         , newer(nullptr)
0033     {
0034         begin = new char[s];
0035     }
0036     ~MemBlock()
0037     {
0038         delete[] begin;
0039     }
0040     MemBlock(const MemBlock &) = delete;
0041     MemBlock &operator=(const MemBlock &) = delete;
0042     bool is_in(void *ptr) const
0043     {
0044         return !(begin > (char *)ptr || (begin + size) <= (char *)ptr);
0045     }
0046     size_t size;
0047     unsigned int ref;
0048     char *begin;
0049     MemBlock *older;
0050     MemBlock *newer;
0051 };
0052 
0053 class KZoneAllocator::Private
0054 {
0055 public:
0056     Private()
0057         : currentBlock(nullptr)
0058         , blockSize(1)
0059         , blockOffset(0)
0060         , log2(0)
0061         , num_blocks(0)
0062         , hashList(nullptr)
0063         , hashSize(0)
0064         , hashDirty(true)
0065     {
0066     }
0067 
0068     /** One block is 'current' to satisfy requests. @internal */
0069     MemBlock *currentBlock;
0070     /** Store block size from constructor. @internal */
0071     quintptr blockSize;
0072     /** Store offset into current block; size-offset is free. @internal */
0073     quintptr blockOffset;
0074     /** base-2 log of the block size. @internal */
0075     unsigned int log2;
0076     /** Count total number of allocated blocks. @internal */
0077     unsigned int num_blocks;
0078     /** Collection of lists of blocks, for lookups. @internal */
0079     MemList **hashList;
0080     /** Count of hashes. @internal */
0081     unsigned int hashSize;
0082     /** Flag the hashes as in need of reorganization. @internal */
0083     bool hashDirty;
0084 };
0085 
0086 KZoneAllocator::KZoneAllocator(unsigned long _blockSize)
0087     : d(new Private)
0088 {
0089     while (d->blockSize < _blockSize) {
0090         d->blockSize <<= 1;
0091         d->log2++;
0092     }
0093 
0094     /* Make sure, that a block is allocated at the first time allocate()
0095        is called (even with a 0 size).  */
0096     d->blockOffset = d->blockSize + 1;
0097 }
0098 
0099 KZoneAllocator::~KZoneAllocator()
0100 {
0101     unsigned int count = 0;
0102     if (d->hashList) {
0103         /* No need to maintain the different lists in d->hashList[] anymore.
0104            I.e. no need to use delBlock().  */
0105         for (unsigned int i = 0; i < d->hashSize; i++) {
0106             delete d->hashList[i];
0107         }
0108         delete[] d->hashList;
0109         d->hashList = nullptr;
0110     }
0111     MemBlock *next;
0112     for (; d->currentBlock; d->currentBlock = next) {
0113         next = d->currentBlock->older;
0114         delete d->currentBlock;
0115         count++;
0116     }
0117 #ifndef NDEBUG // as this is called quite late in the app, we don't care
0118     // to use qDebug
0119     if (count > 1) {
0120         fprintf(stderr, "zone still contained %u blocks", count);
0121     }
0122 #endif
0123     delete d;
0124 }
0125 
0126 void KZoneAllocator::insertHash(MemBlock *b)
0127 {
0128     quintptr adr = ((quintptr)b->begin) & (~(d->blockSize - 1));
0129     quintptr end = ((quintptr)b->begin) + d->blockSize;
0130     while (adr < end) {
0131         quintptr key = adr >> d->log2;
0132         key = key & (d->hashSize - 1);
0133         if (!d->hashList[key]) {
0134             d->hashList[key] = new QList<MemBlock *>;
0135         }
0136         d->hashList[key]->append(b);
0137         adr += d->blockSize;
0138     }
0139 }
0140 
0141 /** Add a new memory block to the pool of blocks,
0142     and reorganize the hash lists if needed.
0143     @param b block to add
0144     @internal
0145 */
0146 void KZoneAllocator::addBlock(MemBlock *b)
0147 {
0148     b->newer = nullptr;
0149     b->older = d->currentBlock;
0150     if (d->currentBlock) {
0151         b->older->newer = b;
0152     }
0153     d->currentBlock = b;
0154     d->num_blocks++;
0155     /* If we either have no d->hashList at all, or since it's last construction
0156        there are now many more blocks we reconstruct the list.  But don't
0157        make it larger than a certain maximum.  */
0158     if (d->hashList && ((d->num_blocks / 4) > d->hashSize && d->hashSize < 64 * 1024)) {
0159         d->hashDirty = true;
0160     }
0161     /* Only insert this block into the hashlists, if we aren't going to
0162        reconstruct them anyway.  */
0163     if (d->hashList && !d->hashDirty) {
0164         insertHash(b);
0165     }
0166 }
0167 
0168 /** Reinitialize hash list. @internal */
0169 void KZoneAllocator::initHash()
0170 {
0171     if (d->hashList) {
0172         for (unsigned int i = 0; i < d->hashSize; i++) {
0173             delete d->hashList[i];
0174         }
0175         delete[] d->hashList;
0176         d->hashList = nullptr;
0177     }
0178     d->hashSize = 1;
0179     while (d->hashSize < d->num_blocks) {
0180         d->hashSize <<= 1;
0181     }
0182     if (d->hashSize < 1024) {
0183         d->hashSize = 1024;
0184     }
0185     if (d->hashSize > 64 * 1024) {
0186         d->hashSize = 64 * 1024;
0187     }
0188     d->hashList = new QList<MemBlock *> *[d->hashSize];
0189     memset(d->hashList, 0, sizeof(QList<MemBlock *> *) * d->hashSize);
0190     d->hashDirty = false;
0191     for (MemBlock *b = d->currentBlock; b; b = b->older) {
0192         insertHash(b);
0193     }
0194 }
0195 
0196 /** Delete a memory block. This @em really returns the memory to the heap.
0197     @param b block to delete
0198     @internal
0199 */
0200 void KZoneAllocator::delBlock(MemBlock *b)
0201 {
0202     /* Update also the hashlists if we aren't going to reconstruct them
0203        soon.  */
0204     if (d->hashList && !d->hashDirty) {
0205         quintptr adr = ((quintptr)b->begin) & (~(d->blockSize - 1));
0206         quintptr end = ((quintptr)b->begin) + d->blockSize;
0207         while (adr < end) {
0208             quintptr key = adr >> d->log2;
0209             key = key & (d->hashSize - 1);
0210             if (d->hashList[key]) {
0211                 QList<MemBlock *> *list = d->hashList[key];
0212                 QList<MemBlock *>::Iterator it = list->begin();
0213                 QList<MemBlock *>::Iterator endit = list->end();
0214                 for (; it != endit; ++it) {
0215                     if (*it == b) {
0216                         list->erase(it);
0217                         break;
0218                     }
0219                 }
0220             }
0221             adr += d->blockSize;
0222         }
0223     }
0224     if (b->older) {
0225         b->older->newer = b->newer;
0226     }
0227     if (b->newer) {
0228         b->newer->older = b->older;
0229     }
0230     if (b == d->currentBlock) {
0231         d->currentBlock = nullptr;
0232         d->blockOffset = d->blockSize;
0233     }
0234     delete b;
0235     d->num_blocks--;
0236 }
0237 
0238 void *KZoneAllocator::allocate(size_t _size)
0239 {
0240     // Use the size of (void *) as alignment
0241     const size_t alignment = sizeof(void *) - 1;
0242     _size = (_size + alignment) & ~alignment;
0243 
0244     if ((unsigned long)_size + d->blockOffset > d->blockSize) {
0245         if (_size > d->blockSize) {
0246             qCDebug(KCOMPLETION_LOG, "KZoneAllocator: allocating more than %zu bytes", (size_t)d->blockSize);
0247             return nullptr;
0248         }
0249         addBlock(new MemBlock(d->blockSize));
0250         d->blockOffset = 0;
0251         // qDebug ("Allocating block #%d (%x)\n", d->num_blocks, d->currentBlock->begin);
0252     }
0253     void *result = (void *)(d->currentBlock->begin + d->blockOffset);
0254     d->currentBlock->ref++;
0255     d->blockOffset += _size;
0256     return result;
0257 }
0258 
0259 void KZoneAllocator::deallocate(void *ptr)
0260 {
0261     if (d->hashDirty) {
0262         initHash();
0263     }
0264 
0265     quintptr key = (((quintptr)ptr) >> d->log2) & (d->hashSize - 1);
0266     const QList<MemBlock *> *list = d->hashList[key];
0267     if (!list) {
0268         /* Can happen with certain usage pattern of intermixed free_since()
0269            and deallocate().  */
0270         // qDebug("Uhoh");
0271         return;
0272     }
0273     QList<MemBlock *>::ConstIterator it = list->begin();
0274     QList<MemBlock *>::ConstIterator endit = list->end();
0275     for (; it != endit; ++it) {
0276         MemBlock *cur = *it;
0277         if (cur->is_in(ptr)) {
0278             if (!--cur->ref) {
0279                 if (cur != d->currentBlock) {
0280                     delBlock(cur);
0281                 } else {
0282                     d->blockOffset = 0;
0283                 }
0284             }
0285             return;
0286         }
0287     }
0288     /* Can happen with certain usage pattern of intermixed free_since()
0289        and deallocate().  */
0290     // qDebug("Uhoh2");
0291 }
0292 
0293 void KZoneAllocator::free_since(void *ptr)
0294 {
0295     /* If we have a d->hashList and it's not yet dirty, see, if we will dirty
0296        it by removing too many blocks.  This will make the below delBlock()s
0297        faster.  */
0298     if (d->hashList && !d->hashDirty) {
0299         const MemBlock *b;
0300         unsigned int removed = 0;
0301         for (b = d->currentBlock; b; b = b->older, removed++) {
0302             if (b->is_in(ptr)) {
0303                 break;
0304             }
0305         }
0306         if (d->hashSize >= 4 * (d->num_blocks - removed)) {
0307             d->hashDirty = true;
0308         }
0309     }
0310     while (d->currentBlock && !d->currentBlock->is_in(ptr)) {
0311         d->currentBlock = d->currentBlock->older;
0312         delBlock(d->currentBlock->newer);
0313     }
0314     d->blockOffset = ((char *)ptr) - d->currentBlock->begin;
0315 }