#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
        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", MSize(hl.addr), hl.addr;
                        for (j = 0; 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);

        for (i = 0; i < HL_HASH_SIZE; i++)
        {
                tmphls = size_head[i].next;
                while (tmphls != &size_head[i])
                {
                        tmphls1 = tmphls->next;
                        "%08X*%08X=%08X", tmphls->size, tmphls->count, tmphls->size * tmphls->count;
                        for (j = 0; 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);
}