/**
*
* \file Asura_Platform_MemoryManagement.cpp
*
* Created by Kevin Lea - 08/12/2001 16:05:53
*
* Copyright (C) 2001 Rebellion, All Rights Reserved.
*
* \brief
*
**/

#include "Core/Asura.h"

// Stdlib independent.
#include <malloc.h>

// Stdlib platform-specific.
#include <eekernel.h>

// Engine independent.
#include "Core/Asura_Maths.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#include "Core/Asura_Core.h"
#include "Core/Asura_Multithread.h"

// Engine platform-specific.
#include "Core/Asura_PlatformBasics.h"
#include "Core/Asura_PS2_OS.h"
#include "Core/Asura_PS2_ProjectMemoryManagement.h"

#ifndef ASURA_PS2_SONY_SUBMISSION
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_PS2_DeferredRendering.h"
#include "Rendering/Asura_PS2_GS.h"
#endif

static Asura_Mutex g_xMemoryManagementMutex;
static volatile u_int g_uLockCount;

bool Asura_PS2_MemoryManagerLocked()
{
	return g_uLockCount;
}

void Asura_MemoryManagement_System::Platform_Lock(void)
{
	if (s_bMultithreadingEnabled)
	{
#if (ASURA_PS2_MAX_THREADS > 0)
		g_xMemoryManagementMutex.Lock();
#endif
		g_uLockCount++;
	}
#ifdef ASURA_ASSERT
	else
	{
		// It is valid to turn off thread safety during a interrupt
		if (!QueryIntrContext())
		{
			Asura_Assert(Asura_Core::IsMainThread(),"Using memory management without thread saftey from outside the main thread");
		}
	}
#endif	
}

void Asura_MemoryManagement_System::Platform_Unlock(void)
{
	if (s_bMultithreadingEnabled)
	{
#if (ASURA_PS2_MAX_THREADS > 0)
		g_xMemoryManagementMutex.Unlock();
#endif
		g_uLockCount--;
	}
}

extern "C"
{

int Asura_PS2_DLMallocPreAction( void )
{
	Asura_MemoryManagement_System::Platform_Lock();
	return 0;
}

int Asura_PS2_DLMallocPostAction( void )
{
	Asura_MemoryManagement_System::Platform_Unlock();
	return 0;
}

}

#ifdef ASURA_MEMORYMANAGEMENT_PROCESS_ALLOCATED_MEM

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Asura_PS2_EnsureCacheAlignedMemoryIsNotInCache().
// PARAMETERS   pMem      - Pointer to region of memory.
//              uSize      - Size of region of memory in bytes.
//              uAlignment - Alignment. This should be either 0 (ie. no
//                           alignment) or a value from the ASURA_ALIGN_ID
//                           enumeration.
// RETURNS      None.
// DESCRIPTION  If the specified region of memory is indeed cache-aligned,
//              this function invalidates all cache entries for it. Note the
//              word invalidates there; it does not write them back to RAM.
// AUTHOR       Morgan Parry - 02.08.2002.
//
//////////////////////////////////////////////////////////////////////////////
void Asura_PS2_EnsureCacheAlignedMemoryIsNotInCache( void* pMem, u_int uSize, u_int uAlignment )
{
    // Don't care unless the region of memory is on a 64 byte boundary and is
    // a multiple of 64 bytes in size.
    if( !uAlignment || (uAlignment & 63) || (uSize & 63) )
    {
        return;
    }

    // We require additional data if the memory is aligned.
    Asura_Assert( pMem != NULL, "" );
    Asura_Assert( uSize > 0, "" );

    // Verify that the aligned region is really allocated on a 64 byte
    // boundary.
    Asura_Assert( !(reinterpret_cast<u_int>(pMem) & 63), "Specified region of memory is not cache-algined." );
    
    // Verify that the specified address does not have any of the upper
    // nybble's bits set. This will upset cop0.
    Asura_Assert( !(reinterpret_cast<u_int>(pMem) & 0xf0000000), "Address should not have uncached, uncached accelerated or SPR bits set." );

    // Ensure the aligned region is not in the cache. This code is equivalent
    // to an InvalidDCache() call but should be quicker, as we know we're
    // dealing with 64 byte aligned blocks of memory. It's in assembly
    // because the debug build constantly moves local variables on and off
    // the stack and this causes major problems with the CACHE DHIN
    // instructions. Note that we need to use a multi-line ASM statement to
    // ensure the compiler doesn't insert instructions in the middle of this
    // code and this means we have to ensure VisualC doesn't see the code.
#ifndef _MSC_VER
#ifdef __SNC__
	InvalidDCache( pMem, reinterpret_cast<void*>(reinterpret_cast<char*>(pMem) + uSize - 1 ) );
#else
	{
        u_char* pucCacheEntry;
        u_int   uNumEntries;
        int     iMinusOne;

        asm __volatile__(
                
            // Setup ptr to first cache entry.
            "    add %0, %3, $0                                    \n"

            // Num cache entries = size of aligned area / 64.
            "    srl %1, %4, 6                                     \n"

            // Prepare -1 in a register ready for decrementing the loop
            // counter.
            "    lui %2, 0xffff                                    \n"
            "    ori %2, %2, 0xffff                                \n"

            // The CACHE DHIN documentation states that a sync.l is
            // necessary. Note that we can't perform any cached memory
            // accesses now until we've finished issuing the CACHE DHIN
            // instructions.
            "    sync.l                                            \n"

            // Iterate through the 64 byte blocks, issuing a CACHE
            // DHIN for each. Note that the assembler will insert NOPs
            // as necessary here (ie. to pad the loop to the safe
            // minimum number of cycles and for the branch delay
            // slot.)
            "__label_BeginCacheEntryInvalidateLoop:                \n"
            "    cache 0x1a, 0(%0)                                 \n"
            "    addiu %0, %0, 64                                  \n"
            "    add %1, %1, %2                                    \n"
            "    bne $0, %1, __label_BeginCacheEntryInvalidateLoop \n"
            
            :
        
                "=r" (pucCacheEntry), // %0
                "=r" (uNumEntries),   // %1
                "=r" (iMinusOne)      // %2

            :
            
                "r" (pMem),          // %3
                "r" (uSize)           // %4

            );
    }
#endif // #else not #ifdef __SNC__
#endif // #ifndef _MSC_VER
}
// End function Asura_PS2_EnsureCacheAlignedMemoryIsNotInCache().

#endif // #ifdef ASURA_MEMORYMANAGEMENT_PROCESS_ALLOCATED_MEM

typedef int __attribute__ ((mode (TI))) heap_size_type __attribute__((aligned(16)));
extern "C" heap_size_type _end;
extern "C" heap_size_type _stack_size;
extern "C" heap_size_type _stack;
extern "C" heap_size_type _fbss;

static int g_iPrevNumFreeBlocks = 1;

extern "C" 
{
// global to store end of main mem
static char* g_pcEndOfMainMem = 0;

static u_int Asura_PS2_GetHeapStart()
{
	// align heap start to page size (4096)
	unsigned int uHeapStart = (unsigned int)&_end;
	uHeapStart = (uHeapStart + 4095) & ~4095;

	return uHeapStart;
}

// accessor to get / init it
static char* Asura_PS2_GetEndOfMainMem()
{
    if (0 == g_pcEndOfMainMem)
    {
        Asura_Assert( !(ASURA_PS2_MEMORYMANAGEMENT_HEAP_SIZE & 3), "Heap size must be word aligned." );
        int iOurHeapSize = ASURA_PS2_MEMORYMANAGEMENT_HEAP_SIZE;

        int HeapStart =  (int)Asura_PS2_GetHeapStart();
		//Asura_OutputToDebugger_Retail(("heap start = %p.\n", HeapStart));
        int StackStart = (int)&_stack;
        int StackSize = (int)&_stack_size;
        if ( StackStart == -1 )
        {
            StackStart = 128*1024*1024 - StackSize;
        }
        int HeapSize = StackStart - HeapStart;

#ifndef ASURA_PS2_CDEMUL
#ifndef _DEBUG
        // (MH - 31.12.2002) hack because this won't display in release build
        void attySetMode( int iMode );
        attySetMode(0);
#endif
#endif
        if (HeapSize < iOurHeapSize)
        {
            Asura_OutputToDebugger_Retail(( "HeapSize < iOurHeapSize; short of %d bytes.\n", iOurHeapSize - HeapSize ));
            iOurHeapSize = HeapSize;
        }
#ifndef _DEBUG
		else
		{
			Asura_OutputToDebugger_Retail(( "%d bytes spare compared to debug build.\n", HeapSize - iOurHeapSize ));
		}
#endif
		
#ifndef ASURA_PS2_CDEMUL
#ifndef _DEBUG
        attySetMode(1);
#endif
#endif

        g_pcEndOfMainMem = (char*) (HeapStart + iOurHeapSize);
	}
	return g_pcEndOfMainMem;
}

#define MORECORE_FAILURE    ((void*)(-1))
void* Asura_PS2_GetHeapMem(int iSize)
{
	static bool bFirstTime = true;
    if (bFirstTime && (iSize > 0))
	{
		bFirstTime = false;
		return (void*)Asura_PS2_GetHeapStart();
	}

    if (iSize == 0)
    {
        // return end of mem
        return Asura_PS2_GetEndOfMainMem();
    }
    else
    {
        return MORECORE_FAILURE;
    }
}

void* Asura_PS2_GetDebugHeapMem(int iSize)
{
    static char* pcEndOfDebugMem = 0;
    if (0 == pcEndOfDebugMem)
    {
        char* pcStartOfDebugMem = Asura_PS2_GetEndOfMainMem();
        int iDebugHeapSize = 0x04000000; // 64Mb
        pcEndOfDebugMem = (pcStartOfDebugMem + iDebugHeapSize);
        if (iSize > 0)
        {
            unsigned int uHeapStart = (unsigned int)pcStartOfDebugMem;
			uHeapStart = (uHeapStart + 4095) & ~4095;
			return (void*)uHeapStart;
        }
    }

    if (iSize == 0)
    {
        // return end of mem
        return pcEndOfDebugMem;
    }
    else
    {
        return MORECORE_FAILURE;
    }
}


void Asura_PS2_OutOfMem()
{
    // Handy for debugging purposes to have mallinfo available.
    const struct mallinfo xMallInfo = mallinfo();

#ifdef ASURA_ASSERT
	Asura_Assert(false, "out of memory");
#else
	Asura_OutputToDebugger_Retail(( "out of memory\n" ));
#endif

#ifndef ASURA_PS2_SONY_SUBMISSION

	Asura_PS2_DeferredRendering::WaitForDeferredRendering();
	sceGsSyncV( 0 );

	bool bTrue = true;
	while(bTrue)
	{
		// Start a new one
		Asura_PS2_GS::EnableDrawToFrontBfr( true );
		Asura_PS2_GS::DebugClearFrame( 255, 64, 64 );
		sceGsSyncV( 0 );

		// If there's no debugger running then breaking is a really bad idea.
		// currently SNIsDebuggerRunning is broken so do it based on whether it's a DEBUG build.
#if _DEBUG
		bool bCanBreak = true;//( 0 != SNIsDebuggerRunning() );
#else
		bool bCanBreak = false;
#endif
		if(bCanBreak)
		{
			// Do another frame in case we're syncing on begin instead of end.
			Asura_PS2_GS::EnableDrawToFrontBfr( true );
			Asura_PS2_GS::DebugClearFrame( 255, 64, 64 );
			sceGsSyncV( 0 );

			// Now break so we can see what caused the error in the debugger.
			asm("break 1"); asm("nop");
		}
	}
#endif
}

}	// extern "C" 

#ifdef ASURA_MEMORYMANAGEMENT_ENABLED

#ifdef ASURA_MEMORYMANAGEMENT_PROCESS_ALLOCATED_MEM

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Asura_MemoryManagement_System::Platform_ProcessAllocatedMem().
// PARAMETERS   pAddr         - Address, or actual address if aligned.
//              uSize          - Size, or actual size if aligned.
//              eEventID       - Type of memory management event.
//              uAlignment     - Alignment type, or 0 if no alignment.
//              pReportedAddr - Reported address if memory is aligned.
//              uReportedSize  - Reported size if memory is aligned.
// RETURNS      None.
// DESCRIPTION  
// AUTHOR       Morgan Parry - 31.07.2002.
//
//////////////////////////////////////////////////////////////////////////////
void Asura_MemoryManagement_System::Platform_ProcessAllocatedMem( void* pAddr,
                                                                  u_int uSize,
                                                                  ASURA_MM_EVENT_ID eEventID,
                                                                  u_int uAlignment,
                                                                  void* pReportedAddr /*= NULL*/,
                                                                  u_int uReportedSize /*= 0*/ )
{
    // The compiler should inline this since we've defined it above.
    Asura_PS2_EnsureCacheAlignedMemoryIsNotInCache( pReportedAddr, uReportedSize, uAlignment );
}
// End function Asura_MemoryManagement_System::Platform_ProcessAllocatedMem().

#endif // #ifdef ASURA_MEMORYMANAGEMENT_PROCESS_ALLOCATED_MEM

#ifdef ASURA_MEMORYMANAGEMENT_FILL

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Asura_MemoryManagement_System::Platform_FillMem().
// PARAMETERS   pAddr          - Address, or actual address if aligned.
//              uSize          - Size, or actual size if aligned.
//              eEventID       - Type of memory management event.
//              uAlignment     - Alignment type, or 0 if no alignment.
//              pReportedAddr  - Reported address if memory is aligned.
//              uReportedSize  - Reported size if memory is aligned.
// RETURNS      None.
// DESCRIPTION  Fills specified region of memory. If the caller requested
//              memory aligned on a 64-byte boundary and a multiple of 64
//              bytes in size then we ensure the returned memory is not
//              present in the cache.
// AUTHOR       Morgan Parry - 29.07.2002.
//
//////////////////////////////////////////////////////////////////////////////
void Asura_MemoryManagement_System::Platform_FillMem( void* pAddr,
                                                      u_int uSize,
                                                      ASURA_MM_EVENT_ID eEventID,
                                                      u_int uAlignment,
                                                      void* pReportedAddr /*= NULL*/,
                                                      u_int uReportedSize /*= 0*/ )
{
    if( uAlignment )
    {
        Asura_Assert( pReportedAddr != NULL, "" );
        Asura_Assert( uReportedSize > 0, "" );
    }

    u_char ucFill;
	switch( eEventID )
    {
        case ASURA_NEW:
        case ASURA_NEWALIGN:
        case ASURA_NEWARRAY:
        case ASURA_ALLOC:
            ucFill = ucFillValueAllocate;           
            break;

        case ASURA_DELETE:
        case ASURA_DELETEALIGN:
        case ASURA_DELETEARRAY:
        case ASURA_DEALLOC:
            ucFill = ucFillValueFree;           
            break;

        default:
            Asura_Assert( false, "Invalid memory manager event ID." );
            return;
    }
    
    // Determine whether we need to take the cache into consideration when
    // filling memory. There's no need to bother if we're deallocating the
    // memory because we'll deal with it when reallocating.
    bool bCareAboutCache = ( uAlignment && ucFillValueAllocate == ucFill && !(uAlignment & 63) && !(uReportedSize & 63) );

    // Fill.
    u_char* pucOut = reinterpret_cast<u_char*>( pAddr );
    if( bCareAboutCache )
    {
        u_char* const pucAlignedStart = reinterpret_cast<u_char*>( pReportedAddr );

        // Fill bytes before start of aligned area.
        {
            u_int uNumBytesBeforeAligned = pucAlignedStart - pucOut;
            while( uNumBytesBeforeAligned-- )
            {
                *pucOut++ = ucFill;
            }
        }

        Asura_Assert( pucAlignedStart == pucOut, "" );

        // Bypass cache for aligned area. We know this region of memory will
        // not be in the cache; Platform_ProcessAllocatedMem() will have 
        // ensured that.
        {
            Asura_Assert( !(reinterpret_cast<u_int>(pucOut) & 63), "" );
            Asura_Assert( !(uReportedSize & 63), "" );

            pucOut = Asura_PS2_UncachedAcceleratedMem( pucOut );

            u_int uNumAlignedBytes = uReportedSize;
            while( uNumAlignedBytes-- )
            {
                *pucOut++ = ucFill;
            }

            pucOut = Asura_PS2_CachedMem( pucOut );
        }

        Asura_Assert( pucAlignedStart + uReportedSize == pucOut, "" );

        // Fill bytes after end of aligned area.
        {
            u_int uNumBytesAfterAligned = (reinterpret_cast<u_char*>(pAddr) + uSize) - pucOut;
            while( uNumBytesAfterAligned-- )
            {
                *pucOut++ = ucFill;
            }
        }

        Asura_Assert( reinterpret_cast<u_char*>(pAddr) + uSize == pucOut, "" );
    }
    else
    {
        while( uSize-- ) { *pucOut++ = ucFill; }
    }
}
// End function Asura_MemoryManagement_System::Platform_FillMem().

#endif // #ifdef ASURA_MEMORYMANAGEMENT_FILL  

// (MP - 07.07.2003) Good idea to turn dbg_bDoConsolidateBeforeGivingMallinfo
// on as well if you're using this.
DEBUGVAR bool dbg_bCheckForFragmentation = false;

void Asura_MemoryManagement_System::Platform_PostDeallocate( void )
{
    // (MP - 07.07.2003) Check that number of free blocks hasn't increased.
    // If stuff is being deallocated correctly, the stuff just freed should
    // have been joined on to one of our previously free blocks. (One obvious
    // but unlikely exception: if we've consumed all available memory and
    // there are no free blocks.)
    if( dbg_bCheckForFragmentation )
    {
        const struct mallinfo xMallInfo = mallinfo();
        Asura_Assert( xMallInfo.ordblks <= g_iPrevNumFreeBlocks, "This deallocation caused memory fragmentation." );
        g_iPrevNumFreeBlocks = xMallInfo.ordblks;
    }
}



void Asura_MemoryManagement_System::Platform_Initialise( void )
{
	g_uLockCount = 0;
}

void Asura_MemoryManagement_System::Platform_Shutdown( void )
{
	Asura_Assert(g_uLockCount == 0,"Memory management should not have any locks at shutdown");
}

#else // not def ASURA_MEMORYMANAGEMENT_ENABLED

void* Asura_PS2_AllocateAlign(unsigned int bytes, enum ASURA_ALIGN_ID alignment)
{
    int iTotalBytes = bytes + alignment + 4;    // 4 bytes before reported address is ptr to start of block

    char* const pcMem = (char*)Asura_Allocate(iTotalBytes);
    
    if (!pcMem) 
    {
        return NULL;
    }

    unsigned int offset = (alignment - ((unsigned int)(pcMem + 4)) & (alignment-1));
	char* const pcReportedMem = (char*)((unsigned char*)(pcMem + 4) + offset);
    u_int* const pcActualAddressStore = (u_int*)(pcReportedMem - 4);
    *pcActualAddressStore = (u_int)pcMem;
    //printf("Asura_PS2_AllocateAlign: %d bytes, %d align, addr = 0x%8p\n", bytes, alignment, pcReportedMem);
    return pcReportedMem;
}

void Asura_PS2_DeallocateAlign(void* address)
{
    if (!address) return;

    u_int* puActualAddress = ((u_int*)address) - 1;
    u_int uActualAddress = *puActualAddress;
    //printf("Asura_PS2_DeallocateAlign: addr = 0x%8p, actual = 0x%8x\n", address, uActualAddress);
    Asura_Deallocate((void*)uActualAddress);
}
#endif // else not def ASURA_MEMORYMANAGEMENT_ENABLED

#if defined ASURA_MEMORYMANAGEMENT_ENABLED

extern "C" {
void* dlmalloc(int size);
void dlfree(void* PTR);
};

void Asura_MemoryUtility::Platform_Initialise(void)
{
}

void Asura_MemoryUtility::Platform_Shutdown(void)
{
}

void* Asura_MemoryUtility::Platform_PreAllocate(/*const*/ size_t size)
{
	return dlmalloc(size);
}

void* Asura_MemoryUtility::Platform_PostAllocate(void* /*const*/ pAddr)
{
	return pAddr;
}

bool Asura_MemoryUtility::Platform_PreDeallocate(void* /*const*/ pAddr)
{
	dlfree(pAddr);
	return true;
}

void Asura_MemoryUtility::Platform_PostDeallocate(void* /*const*/ pAddr)
{
}

#endif
