#help_index "Debugging/Heap;Memory/Debugging"
#help_file "::/Doc/HeapDebug"

#define HL_CALLER_DEPTH     5 //Feel free to change this.
#define HL_HASH_SIZE        0x1000

class CHeapLog
{
    CHeapLog    *next, *last;
    union
    {
        U8 *addr;
        I64 size;
    }
    I64          count;
    U8          *caller[HL_CALLER_DEPTH];
};

class CHeapLogHash
{
    CHeapLog *next, *last;
};

CHeapCtrl    *heaplog_hc_watched, *heaplog_hc = NULL;
CHeapLogHash *heaplog_head = NULL;

U0 HeapLogMAlloc(U8 *addr)
{
    CHeapLog    *tmphl;
    I64          i;

    if (MHeapCtrl(addr) == heaplog_hc_watched)
    {
        tmphl = MAlloc(sizeof(CHeapLog), heaplog_hc);
        tmphl->addr = addr;
        for (i = 0; i < HL_CALLER_DEPTH; i++)
            tmphl->caller[i] = Caller(i + 2);
        i = addr >> 3 & (HL_HASH_SIZE - 1);

        PUSHFD
        CLI
        while (LBts(&sys_semas[SEMA_HEAPLOG_LOCK], 0))
            PAUSE
        QueueInsert(tmphl, heaplog_head[i].last);
        LBtr(&sys_semas[SEMA_HEAPLOG_LOCK], 0);
        POPFD
    }
}

U0 HeapLogFree(U8 *addr)
{
    I64          i;
    CHeapLog    *tmphl;

    if (!addr)
        return;
    if (MHeapCtrl(addr) == heaplog_hc_watched)
    {
        i = addr >> 3 & (HL_HASH_SIZE - 1);

        PUSHFD
        CLI
        while (LBts(&sys_semas[SEMA_HEAPLOG_LOCK], 0))
            PAUSE
        tmphl = heaplog_head[i].next;
        while (tmphl != &heaplog_head[i])
        {
            if (addr == tmphl->addr)
            {
                QueueRemove(tmphl);
                LBtr(&sys_semas[SEMA_HEAPLOG_LOCK], 0);
                POPFD
                Free(tmphl);
                return;
            }
            tmphl = tmphl->next;
        }
        LBtr(&sys_semas[SEMA_HEAPLOG_LOCK], 0);
        POPFD
    }
}

public Bool HeapLog(Bool val=ON, CTask *task=NULL)
{//Turn on.  Collect data.  Call HeapLogAddrRep() or  HeapLogSizeRep().
    I64 i;

    if (val)
    {
        if (Bt(&sys_semas[SEMA_HEAPLOG_ACTIVE], 0))
        {
            "HeapLog Already Active\n";
            return TRUE;
        }
        else
        {
            if (!task)
                task = Fs;
            if (TaskValidate(task))
                heaplog_hc_watched = task->data_heap;
            else
                heaplog_hc_watched = task;//Actually, not a task, must be a HeapCtrl.

            PUSHFD
            CLI
            while (LBts(&sys_semas[SEMA_HEAPLOG_LOCK], 0))
                PAUSE
            heaplog_hc = HeapCtrlInit(,, sys_data_bp);
            ext[EXT_HEAPLOG_MALLOC] = &HeapLogMAlloc;
            ext[EXT_HEAPLOG_FREE]   = &HeapLogFree;
            heaplog_head = MAlloc(sizeof(CHeapLogHash) * HL_HASH_SIZE, heaplog_hc);
            for (i = 0; i < HL_HASH_SIZE; i++)
                QueueInit(&heaplog_head[i]);
            LBtr(&sys_semas[SEMA_HEAPLOG_LOCK], 0);
            POPFD
            LBts(&sys_semas[SEMA_HEAPLOG_ACTIVE], 0);

            return FALSE;
        }
    }
    else
    {
        if (!LBtr(&sys_semas[SEMA_HEAPLOG_ACTIVE], 0))
        {
            "HeapLog Not Active\n";
            return FALSE;
        }
        else
        {
            HeapCtrlDel(heaplog_hc);
            heaplog_head = heaplog_hc = NULL;
            ext[EXT_HEAPLOG_MALLOC] = NULL;
            ext[EXT_HEAPLOG_FREE]   = NULL;

            return TRUE;
        }
    }
}

public U0 HeapLogAddrRep(Bool leave_it=OFF)
{//Call HeapLog() first and collect data.
    I64          i, j, total = 0;
    CHeapLog    *tmphl, hl;

    if (!LBtr(&sys_semas[SEMA_HEAPLOG_ACTIVE], 0))
    {
        "HeapLog Not Active\n";
        return;
    }
    "$WW,0$";
    while (LBts(&sys_semas[SEMA_HEAPLOG_LOCK], 0))
        PAUSE
    "$LTCYAN$  Size    Address$FG$\n";
    for (i = 0; i < HL_HASH_SIZE; i++)
    {
        tmphl = heaplog_head[i].next;
        while (tmphl != &heaplog_head[i])
        {
//Take snapshot in case modified. (while we work)
            MemCopy(&hl, tmphl, sizeof(CHeapLog));
            "$PURPLE$%08X$FG$ %08X %P", MSize(hl.addr), hl.addr, hl.caller[0];
            for (j = 1; j < HL_CALLER_DEPTH; j++)
                " . %P", hl.caller[j];
            '\n';
            total += MSize(hl.addr);
            tmphl = hl.next;
        }
    }
    LBtr(&sys_semas[SEMA_HEAPLOG_LOCK], 0);
    "\n$LTRED$Total:%08X$FG$\n", total;
    LBts(&sys_semas[SEMA_HEAPLOG_ACTIVE], 0);
    if (!leave_it)
        HeapLog(OFF);
}

public U0 HeapLogSizeRep(Bool leave_it=OFF)
{//Call HeapLog() first and collect data.
    I64              i, j, k, total = 0;
    CHeapLog        *tmphla, hla, *tmphls, *tmphls1;
    CHeapLogHash    *size_head;

    if (!LBtr(&sys_semas[SEMA_HEAPLOG_ACTIVE], 0))
    {
        "HeapLog Not Active\n";
        return;
    }

    size_head = MAlloc(sizeof(CHeapLogHash) * HL_HASH_SIZE, heaplog_hc);
    for (i = 0; i < HL_HASH_SIZE; i++)
        QueueInit(&size_head[i]);

    "$WW,0$";
    while (LBts(&sys_semas[SEMA_HEAPLOG_LOCK], 0))
        PAUSE
    for (i = 0; i < HL_HASH_SIZE; i++)
    {
        tmphla = heaplog_head[i].next;
        while (tmphla != &heaplog_head[i])
        {
//Take snapshot in case modified. (while we work)
            MemCopy(&hla, tmphla, sizeof(CHeapLog));
            k = (MSize(hla.addr) >> 3 + hla.caller[0]) & (HL_HASH_SIZE - 1);
            tmphls = size_head[k].next;
            while (tmphls != &size_head[k])
            {
                if (MSize(hla.addr) == tmphls->size)
                {
                    for (j = 0; j < HL_CALLER_DEPTH; j++)
                        if (hla.caller[j] != tmphls->caller[j])
                            goto hl_next;
                    tmphls->count++;
                    goto hl_found;
                }
hl_next:
                tmphls = tmphls->next;
            }
            tmphls = MAlloc(sizeof(CHeapLog), heaplog_hc);
            MemCopy(tmphls, &hla, sizeof(CHeapLog));
            tmphls->count = 1;
            tmphls->size  = MSize(hla.addr);
            QueueInsert(tmphls, size_head[k].last);
hl_found:
            tmphla = hla.next;
        }
    }
    LBtr(&sys_semas[SEMA_HEAPLOG_LOCK], 0);

    "$LTCYAN$  Size  * Count  = Total$FG$\n";
    for (i = 0; i < HL_HASH_SIZE; i++)
    {
        tmphls = size_head[i].next;
        while (tmphls != &size_head[i])
        {
            tmphls1 = tmphls->next;
            "%08X*%08X=%08X %P", tmphls->size, tmphls->count, tmphls->size * tmphls->count, tmphls->caller[0];
            for (j = 1; j < HL_CALLER_DEPTH; j++)
                " . %P", tmphls->caller[j];
            '\n';
            total += tmphls->size * tmphls->count;
            Free(tmphls);
            tmphls = tmphls1;
        }
    }
    Free(size_head);

    "\n$LTRED$Total:%08X$FG$\n", total;
    LBts(&sys_semas[SEMA_HEAPLOG_ACTIVE], 0);
    if (!leave_it)
        HeapLog(OFF);
}