/*
 * $Id: allocator.c 1.9 1998/04/13 08:33:48 olsen Exp olsen $
 *
 * :ts=4
 *
 * Wipeout -- Traces and munges memory and detects memory trashing
 *
 * Written by Olaf `Olsen' Barthel <olsen@sourcery.han.de>
 * Public Domain
 */

#ifndef _GLOBAL_H
#include "global.h"
#endif	/* _GLOBAL_H */

/******************************************************************************/

#include "installpatches.h"

/******************************************************************************/

STATIC struct MinList AllocationList;

/******************************************************************************/

STATIC ULONG
CalculateChecksum(const ULONG * mem,ULONG memSize)
{
	ULONG tmp,sum;
	int i;

	/* memSize must be a multiple of 4. */
	ASSERT((memSize % 4) == 0);

	/* Calculate the "additive carry wraparound" checksum
	 * for the given memory area. The Kickstart and the boot block
	 * checksums are calculated using the same technique.
	 */
	sum = 0;
	for(i = 0 ; i < memSize / 4 ; i++)
	{
		tmp = sum + mem[i];
		if(tmp < sum)
			tmp++;

		sum = tmp;
	}

	return(sum);
}

/******************************************************************************/

VOID
PerformDeallocation(struct TrackHeader * th)
{
	LONG allocationSize;

	th->th_Magic = 0;
	allocationSize = th->th_NameLen + sizeof(*th) + PreWallSize + th->th_Size + th->th_PostSize;

	/* It is quasi-legal to release and reuse memory whilst under
	 * Forbid(). If this is the case, we will not stomp on the allocation
	 * body and leave the contents of the buffer unmodified. Note that
	 * while in Disable() state, multitasking will be halted, just as whilst
	 * in Forbid() state. But as there is no safe way to track whether the
	 * system actually has the interrupts disabled and the memory allocator
	 * is documented to operate under Forbid() conditions only, we just
	 * consider the Forbid() state.
	 */
	if(SysBase->TDNestCnt == 0 || NoReuse) /* not -1 because we always run under Forbid() */
	{
		UBYTE * mem;

		mem = ((UBYTE *)(th + 1)) + PreWallSize;

		MungMem((ULONG *)mem,th->th_Size,DEADBEEF);
	}

	switch(th->th_Type)
	{
		case ALLOCATIONTYPE_AllocMem:
		case ALLOCATIONTYPE_AllocVec:

			RemoveAllocation(th);

			if(th->th_NameLen > 0)
			{
				th = (struct TrackHeader *)(((ULONG)th) - th->th_NameLen);
			}

			(*OldFreeMem)(th,allocationSize,SysBase);
			break;

		case ALLOCATIONTYPE_AllocPooled:

			RemovePuddle(th->th_PoolHeader,th);

			if(th->th_NameLen > 0)
			{
				th = (struct TrackHeader *)(((ULONG)th) - th->th_NameLen);
			}

			(*OldFreePooled)(th->th_PoolHeader->ph_PoolHeader,th,allocationSize,SysBase);
			break;
	}
}

/******************************************************************************/

BOOL
PerformAllocation(
	ULONG					pc,
	struct PoolHeader *		poolHeader,
	ULONG					memSize,
	ULONG					attributes,
	UBYTE					type,
	APTR *					resultPtr)
{
	struct TrackHeader * th;
	ULONG preSize;
	ULONG allocationRemainder;
	ULONG postSize;
	LONG nameLen;
	APTR result = NULL;
	BOOL success = FALSE;

	nameLen = 0;

	/* Get the name of the current task, if this is necessary. */
	if(NameTag)
	{
		if(GetTaskName(NULL,GlobalNameBuffer,sizeof(GlobalNameBuffer)))
		{
			nameLen = ((strlen(GlobalNameBuffer)+1) + 3) & ~3;
		}
	}

	/* If the allocation is not a multiple of the memory granularity
	 * block size, increase the post memory wall by the remaining
	 * few bytes of padding.
	 */
	allocationRemainder = (memSize % MEM_BLOCKSIZE);
	if(allocationRemainder > 0)
	{
		allocationRemainder = MEM_BLOCKSIZE - allocationRemainder;
	}

	preSize = PreWallSize;
	postSize = allocationRemainder + PostWallSize;

	switch(type)
	{
		case ALLOCATIONTYPE_AllocMem:

			th = (*OldAllocMem)(nameLen + sizeof(*th) + preSize + memSize + postSize,attributes & (~MEMF_CLEAR),SysBase);
			if(th != NULL)
			{
				/* Store the name in front of the header, then
				 * adjust the header address.
				 */
				if(nameLen > 0)
				{
					strcpy((char *)th,GlobalNameBuffer);
					th = (struct TrackHeader *)(((ULONG)th) + nameLen);
				}

				AddAllocation(th);
			}

			break;

		case ALLOCATIONTYPE_AllocVec:

			/* This will later contain the length long word. */
			memSize += sizeof(ULONG);

			th = (*OldAllocMem)(nameLen + sizeof(*th) + preSize + memSize + postSize,attributes & (~MEMF_CLEAR),SysBase);
			if(th != NULL)
			{
				/* Store the name in front of the header, then
				 * adjust the header address.
				 */
				if(nameLen > 0)
				{
					strcpy((char *)th,GlobalNameBuffer);
					th = (struct TrackHeader *)(((ULONG)th) + nameLen);
				}

				AddAllocation(th);
			}

			break;

		case ALLOCATIONTYPE_AllocPooled:

			th = (*OldAllocPooled)(poolHeader->ph_PoolHeader,nameLen + sizeof(*th) + preSize + memSize + postSize,SysBase);
			if(th != NULL)
			{
				/* Store the name in front of the header, then
				 * adjust the header address.
				 */
				if(nameLen > 0)
				{
					strcpy((char *)th,GlobalNameBuffer);
					th = (struct TrackHeader *)(((ULONG)th) + nameLen);
				}

				AddPuddle(poolHeader,th);
			}

			break;

		default:

			th = NULL;
			break;
	}

	if(th != NULL)
	{
		UBYTE * mem;

		/* Fill in the regular header data. */
		th->th_Magic		= BASEBALL;
		th->th_PointBack	= th;
		th->th_PC			= pc;
		th->th_Owner		= FindTask(NULL);
		th->th_OwnerType	= GetTaskType(NULL);
		th->th_NameLen		= nameLen;

		GetSysTime(&th->th_Time);

		th->th_Size			= memSize;
		th->th_Checksum		= 0;
		th->th_PoolHeader	= poolHeader;
		th->th_Type			= type;
		th->th_FillChar		= NewFillChar();
		th->th_PostSize		= postSize;
		th->th_Marked		= FALSE;

		/* Protect everything but the MinNode at the beginning
		 * with a checksum.
		 */
		th->th_Checksum = ~CalculateChecksum((ULONG *)&th->th_PoolHeader,
		                                     sizeof(*th) - offsetof(struct TrackHeader,th_PoolHeader));

		/* Fill in the preceding memory wall. */
		mem = (UBYTE *)(th + 1);

		memset(mem,th->th_FillChar,preSize);
		mem += preSize;

		/* Fill the memory allocation body either with
		 * junk or with zeroes.
		 */
		if(FLAG_IS_CLEAR(attributes,MEMF_CLEAR))
		{
			MungMem((ULONG *)mem,memSize,DEADFOOD);
		}
		else
		{
			memset(mem,0,memSize);
		}

		mem += memSize;

		/* Fill in the following memory wall. */
		memset(mem,th->th_FillChar,postSize);

		mem = (UBYTE *)(th + 1);
		mem += preSize;

		/* AllocVec()'ed allocations are special in that
		 * the size of the allocation precedes the header.
		 */
		if(type == ALLOCATIONTYPE_AllocVec)
		{
			/* Size of the allocation must include the
			 * size long word.
			 */
			(*(ULONG *)mem) = memSize + sizeof(ULONG);

			result = (APTR)(mem + sizeof(ULONG));
		}
		else
		{
			result = (APTR)mem;
		}

		success = TRUE;
	}

	(*resultPtr) = result;

	return(success);
}

/******************************************************************************/

BOOL
IsTrackedAllocation(
	ULONG					address,
	struct TrackHeader **	resultPtr)
{
	struct TrackHeader * result = NULL;
	struct TrackHeader * th;
	BOOL valid = FALSE;

	/* Move back to the memory tracking header. */
	th = (struct TrackHeader *)(address - PreWallSize - sizeof(*th));

	/* Check whether the calculated address looks good enough. */
	if(NOT IsInvalidAddress((ULONG)th) && NOT IsOddAddress((ULONG)th))
	{
		/* Check for the unique identifiers. */
		if(th->th_Magic == BASEBALL && th->th_PointBack == th)
		{
			/* For extra safety, also take a look at the
			 * checksum.
			 */
			if(CalculateChecksum((ULONG *)&th->th_PoolHeader,
			                     sizeof(*th) - offsetof(struct TrackHeader,th_PoolHeader)) == (ULONG)-1)
			{
				result = th;
				valid = TRUE;
			}
		}
	}

	(*resultPtr) = result;

	return(valid);
}

/******************************************************************************/

VOID
AddAllocation(struct TrackHeader * th)
{
	/* Register the new regular memory allocation. */
	AddTail((struct List *)&AllocationList,(struct Node *)th);
}

VOID
RemoveAllocation(struct TrackHeader * th)
{
	/* Unregister the regular memory allocation. */
	Remove((struct Node *)th);
}

/******************************************************************************/

VOID
SetupAllocationList(VOID)
{
	/* Initialize the list of regular memory allocations.
	 * Pooled allocations will be stored elsewhere.
	 */
	NewList((struct List *)&AllocationList);
}

/******************************************************************************/

VOID
CheckAllocatedMemory(VOID)
{
	struct TrackHeader * th;
	ULONG totalBytes;
	ULONG totalAllocations;

	/* Check and count all regular memory allocations. We look for
	 * trashed memory walls and orphaned memory.
	 */

	totalBytes = 0;
	totalAllocations = 0;

	Forbid();

	for(th = (struct TrackHeader *)AllocationList.mlh_Head ;
	    th->th_MinNode.mln_Succ != NULL ;
	    th = (struct TrackHeader *)th->th_MinNode.mln_Succ)
	{
		/* A magic value of 0 indicates a "dead" allocation
		 * that we left to its own devices. We don't want it
		 * to show up in our list.
		 */
		if(th->th_Magic != 0)
		{
			/* Check for trashed memory walls. */
			CheckStomping(NULL,th);

			/* Check if its creator is still with us. */
			if(NOT IsTaskStillAround(th->th_Owner))
			{
				VoiceComplaint(NULL,th,"Orphaned allocation?");
			}

			totalBytes += th->th_Size;
			totalAllocations++;
		}
	}

	Permit();

	DPrintf("%ld byte(s) in %ld single allocation(s).\n",totalBytes,totalAllocations);
}

/******************************************************************************/

VOID
ShowUnmarkedMemory(VOID)
{
	struct TrackHeader * th;
	ULONG totalBytes;
	ULONG totalAllocations;

	/* Show and count all unmarked regular memory allocations. */

	totalBytes = 0;
	totalAllocations = 0;

	Forbid();

	for(th = (struct TrackHeader *)AllocationList.mlh_Head ;
	    th->th_MinNode.mln_Succ != NULL ;
	    th = (struct TrackHeader *)th->th_MinNode.mln_Succ)
	{
		/* A magic value of 0 indicates a "dead" allocation
		 * that we left to its own devices. We don't want it
		 * to show up in our list.
		 */
		if(th->th_Magic != 0)
		{
			if(NOT th->th_Marked)
			{
				VoiceComplaint(NULL,th,NULL);
			}

			totalBytes += th->th_Size;
			totalAllocations++;
		}
	}

	Permit();

	DPrintf("%ld byte(s) in %ld single allocation(s).\n",totalBytes,totalAllocations);
}

/******************************************************************************/

VOID
ChangeMemoryMarks(BOOL markSet)
{
	struct TrackHeader * th;

	/* Mark or unmark all memory puddles. */

	Forbid();

	for(th = (struct TrackHeader *)AllocationList.mlh_Head ;
	    th->th_MinNode.mln_Succ != NULL ;
	    th = (struct TrackHeader *)th->th_MinNode.mln_Succ)
	{
		/* A magic value of 0 indicates a "dead" allocation
		 * that we left to its own devices.
		 */
		if(th->th_Magic != 0)
		{
			th->th_Marked = markSet;
		}
	}

	Permit();
}
