
/*
    SuperCollider real time audio synthesis system
    Copyright (c) 2002 James McCartney. All rights reserved.
    http://www.audiosynth.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/
/*
This is based on Doug Lea's allocator but rewritten so I can read and understand it...
also features of free all-at-once pools are added.
Now uses 16 byte alignment, which does increase the minimum allocation size to 32 bytes
including the overhead.
Improved bit block scanning by using a count leading zeroes instruction.

*/

#pragma once

#include "SC_List.h"
#include "clz.h"
#include "function_attributes.h"
#include <stdlib.h>

const int kNumAllocBins = 128;
const int kNumSmallBins = 64;
const int kMaxSmallBin = kNumSmallBins - 1;
const int kBinWidth = 8;
const int kMaxSmallBinSize = kNumSmallBins * kBinWidth;
const int kBinBlockWidth = 4;
const int kBinBlockMask = kBinBlockWidth - 1;

const size_t kAlign = 64;
const size_t kAlignMask = kAlign - 1;
const size_t kChunkFree = 0;
const size_t kChunkInUse = 1;
const size_t kSizeBits = ~kChunkInUse;

class AllocChunk;
class AllocPool;
typedef AllocChunk* AllocChunkPtr;
typedef Link<AllocChunk> AllocBin;
typedef AllocBin* AllocBinPtr;

class AllocChunk : public Link<AllocChunk> {
    friend class AllocPool;

    size_t Size() { return mSize & kSizeBits; }

    size_t PrevSize() { return mPrevSize & kSizeBits; }

    AllocChunkPtr ChunkAtOffset(long inSize) { return AllocChunkPtr((char*)this + inSize); }

    AllocChunkPtr NextChunk() { return ChunkAtOffset(Size()); }

    AllocChunkPtr PrevChunk() { return ChunkAtOffset(-(long)PrevSize()); }

    bool InUse() { return (bool)(mSize & kChunkInUse); }

    bool PrevInUse() { return (bool)(mPrevSize & kChunkInUse); }

    void SetSizeFree(size_t inSize) { mSize = ChunkAtOffset(inSize)->mPrevSize = inSize; }

    void SetSizeInUse(size_t inSize) { mSize = ChunkAtOffset(inSize)->mPrevSize = inSize | kChunkInUse; }

    void SetNeighborsInUse(size_t inOffset) { mPrevSize = ChunkAtOffset(inOffset)->mSize = kChunkInUse; }

    bool IsArea() { return mPrevSize == kChunkInUse && NextChunk()->mSize == kChunkInUse; }

    void* ToPtr() { return (void*)((char*)this + sizeof(AllocChunk)); }

    size_t mPrevSize;
    size_t mSize;
};

class AllocArea;
typedef AllocArea* AllocAreaPtr;

class AllocAreaHdr /* for size calculations */
{
protected:
    friend class AllocPool;

    AllocAreaPtr mPrev, mNext;
    size_t mSize;
    void* mUnalignedPointerToThis;
};

class AllocArea : public AllocAreaHdr {
public:
    void SanityCheck();

private:
    friend class AllocPool;

    AllocChunk mChunk;
};

const size_t kMinAllocSize = 2 * kAlign;
const size_t kAreaOverhead = sizeof(AllocAreaHdr) + 2 * sizeof(AllocChunk) + kAlign;

typedef void* (*NewAreaFunc)(size_t size);
typedef void (*FreeAreaFunc)(void*);

class AllocPool {
public:
    AllocPool(NewAreaFunc allocArea, FreeAreaFunc freeArea, size_t areaInitSize, size_t areaMoreSize);
    ~AllocPool();

    void Reinit();

    MALLOC void* Alloc(size_t inBytes);
    MALLOC void* Realloc(void* inPtr, size_t inBytes);
    void Free(void* inPtr);
    void FreeAll();
    void FreeAllInternal();

    // debugging
    size_t TotalFree();
    size_t LargestFreeChunk();

    void DoCheckPool();
    void DoCheckInUseChunk(AllocChunkPtr p);

    static AllocChunkPtr MemToChunk(void* inPtr) { return (AllocChunkPtr)((char*)(inPtr) - sizeof(AllocChunk)); }

private:
    void InitAlloc();
    void InitBins();
    AllocAreaPtr NewArea(size_t inAreaSize);
    void FreeArea(AllocChunkPtr chunk);

    // debugging
    void DoCheckArea(AllocAreaPtr area);
    void DoCheckBin(AllocChunkPtr bin, long idx);
    void DoCheckChunk(AllocChunkPtr p);
    void DoCheckFreeChunk(AllocChunkPtr p);
    void DoCheckAllocedChunk(AllocChunkPtr p, size_t s);
    void DoGarbageFill(AllocChunkPtr p);
    void DoGarbageFill(AllocChunkPtr p, long size);

    // inlines

    static size_t RequestToSize(size_t inReqSize) {
        size_t sizePlusOverhead = inReqSize + sizeof(AllocChunk);
        if (sizePlusOverhead <= kMinAllocSize)
            return kMinAllocSize;
        else
            return (sizePlusOverhead + kAlignMask) & ~kAlignMask;
    }

    static size_t SmallBinIndex(size_t inSize) { return inSize >> 4; }

    static size_t BinIndex2(size_t inSize) {
        return ((inSize < 1024) ? (inSize >> 4)
                                : (inSize < 2048)
                        ? 56 + (inSize >> 7)
                        : (inSize < 4096) ? 64 + (inSize >> 8)
                                          : (inSize < 8192) ? 72 + (inSize >> 9)
                                                            : (inSize < 16384) ? 80 + (inSize >> 10)
                                                                               : (inSize < 32768) ? 88 + (inSize >> 11)
                                                                                                  : (inSize < 65536)
                                            ? 96 + (inSize >> 12)
                                            : (inSize < 131072) ? 104 + (inSize >> 13)
                                                                : (inSize < 262144) ? 112 + (inSize >> 14) : 127);
    }

    static size_t BinIndex(size_t inSize) {
        if (inSize < 1024)
            return inSize >> 4;
        if (inSize >= 262144)
            return 127;
        int bits = 28 - CLZ(inSize);
        return (bits << 3) + (inSize >> bits);
    }

    void MarkBinBlock(size_t inIndex) {
        size_t word = inIndex >> 5;
        size_t bitPosition = inIndex & 31;
        size_t bitValue = 1L << bitPosition;
        mBinBlocks[word] |= bitValue;
    }

    void ClearBinBlock(size_t inIndex) {
        size_t word = inIndex >> 5;
        size_t bitPosition = inIndex & 31;
        size_t bitValue = 1L << bitPosition;
        mBinBlocks[word] &= ~bitValue;
    }

    int NextFullBin(int inStartingBinIndex) {
        if (inStartingBinIndex >= 128)
            return -1;
        int word = inStartingBinIndex >> 5;
        int bitPosition = inStartingBinIndex & 31;
        unsigned long bitValue = 1L << bitPosition;
        unsigned long binBits = mBinBlocks[word];

        if (binBits >= bitValue) {
            binBits = (~(bitValue - 1) & binBits);
        } else {
            for (++word; word < 4 && !mBinBlocks[word]; ++word) {
            }
            if (word == 4)
                return -1;
            binBits = mBinBlocks[word];
        }
        bitPosition = CTZ((int32)binBits);

        return (word << 5) + bitPosition;
    }

    void LinkFree(AllocChunkPtr inChunk);
    /*
        {
            size_t size = inChunk->Size();
            int index = BinIndex(size);

            AllocChunkPtr bin = mBins + index;

            if (index < kNumSmallBins || bin->IsEmpty()) {
                inChunk->InsertAfter(bin);
                MarkBinBlock(index);
            } else {
                AllocChunkPtr link = bin->Next();
                while (link != bin && size < link->Size()) link = link->Next();
                inChunk->InsertBefore(link);
            }
        }
    */

    void UnlinkFree(AllocChunkPtr inChunk) {
        inChunk->RemoveLeaveDangling();
        size_t size = inChunk->Size();
        size_t index = BinIndex(size);
        AllocChunkPtr bin = mBins + index;
        if (bin->IsEmpty())
            ClearBinBlock(index);
    }

    AllocChunk mBins[kNumAllocBins];
    AllocAreaPtr mAreas;
    NewAreaFunc mAllocArea;
    FreeAreaFunc mFreeArea;
    size_t mAreaInitSize, mAreaMoreSize;
    unsigned long mBinBlocks[4];
};
