#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 $LK,"HeapLogAddrRep",A="MN:HeapLogAddrRep"$() or  $LK,"HeapLogSizeRep",A="MN: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 $LK,"HeapLog",A="MN: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 $LK,"HeapLog",A="MN: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);
}
