/////////////////////////////////////////////////////////////////////////////
// EAStompAllocator.cpp
//
// Copyright (c) 2006 Electronic Arts. All Rights Reserved.
///////////////////////////////////////////////////////////////////////////////


#include "EAStompAllocator.h"
#include <PPMalloc/internal/shared.h>
#include <stdlib.h>
#include <stdio.h>


#if defined(EA_PLATFORM_XENON)
    #pragma warning(push, 0)
    #pragma warning(disable: 4311 4312)
    #include <xtl.h>
    #pragma warning(pop)
#elif defined(EA_PLATFORM_PS3)
    #include <sys/memory.h>
#endif


namespace EA
{
    namespace Allocator
    {
        namespace StompAllocatorLocal
        {
            #if defined(EA_PLATFORM_XENON) || defined(EA_PLATFORM_WINDOWS)
                const uint32_t kPageSize     = 0x00001000;   // 4K

            #elif defined(EA_PLATFORM_PS3)
                const uint32_t kPageSize     = 0x00010000;   // 64K
                const uint32_t kScanSize     = 0x00020000;   // 128K
                const uint32_t kReserveSize  = 0x10000000;   // 256MB

                // These are values that are shared between multiple instances of StompAllocator.
                sys_addr_t mBaseAddress      = 0;
                sys_addr_t mNextAllocAddress = 0;
            #endif
        }


        StompAllocator::StompAllocator()
          : mAllocCount(0)
        {
        }


        StompAllocator::~StompAllocator()
        {
            Shutdown();
        }


        void StompAllocator::Init()
        {
            #if defined(EA_PLATFORM_PS3)
                using namespace StompAllocatorLocal;

                if(sys_mmapper_allocate_address(kReserveSize, SYS_MEMORY_PAGE_SIZE_64K | SYS_MEMORY_ACCESS_RIGHT_PPU_THR, kReserveSize, &mBaseAddress) != CELL_OK)
                {
                    printf("StompAllocator::Init: Can't initialize address space.\n");
                    PPM_DebugBreak();
                }

                mNextAllocAddress = mBaseAddress;
            #endif
        }


        void StompAllocator::Shutdown()
        {
            // Nothing to do.
        }


        void* StompAllocator::Malloc(size_t nSize, int nAllocationFlags)
        {
            return MallocDebug(nSize, nAllocationFlags, NULL, NULL, 0);
        }


        void* StompAllocator::MallocAligned(size_t nSize, size_t nAlignment, size_t nAlignmentOffset, int nAllocationFlags)
        {
            return MallocAlignedDebug(nSize, nAlignment, nAlignmentOffset, nAllocationFlags, NULL, NULL, 0);
        }


        void* StompAllocator::MallocDebug(size_t nSize, int nAllocationFlags, const char* pName, const char* pFile, int nLine)
        {
            return MallocAlignedDebug(nSize, kMinAlignment, 0, nAllocationFlags, NULL, NULL, 0);
        }


        void* StompAllocator::MallocAlignedDebug(size_t nSize, size_t nAlignment, size_t /*nAlignmentOffset*/, int nAllocationFlags, 
                                                 const char* pName, const char* pFile, int nLine)
        {
            // To do: Implement support for alignment offset.

            if(nAlignment < kMinAlignment) 
                nAlignment = kMinAlignment;

            // Round aligned nSize up to alignment. This works for power of 2 alignments up to 4K.
            size_t aligned_size = (size_t)((nSize + (nAlignment - 1)) & -(intptr_t)(uintptr_t)nAlignment);
            size_t size_alloc   = aligned_size + sizeof(MemInfo);

            #if defined(EA_PLATFORM_XENON) || defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_PS3)
                using namespace StompAllocatorLocal;

                #if defined(EA_PLATFORM_XENON) || defined(EA_PLATFORM_WINDOWS)
                    // Round the maximum we need up to a multiple of 4K.
                    size_alloc = (size_t)((size_alloc + (kPageSize - 1)) & -(intptr_t)(uintptr_t)kPageSize);

                    // Reserve the memory + guard in the virtual address space.
                    // Add 4K for the guard page.
                    // nAllocationFlags
                    #if defined(EA_PLATFORM_XENON)
                        void* p = (nAllocationFlags & kAllocationFlagPhysical) ? 
                                  XPhysicalAlloc(size_alloc + kPageSize, MAXULONG_PTR, 0, PAGE_READONLY) : 
                                  VirtualAlloc(NULL, size_alloc + kPageSize, MEM_RESERVE, PAGE_READWRITE);
                    #else
                        void* p = VirtualAlloc(NULL, size_alloc + kPageSize, MEM_RESERVE, PAGE_READWRITE);
                    #endif

                    if(p)
                    {
                        // Mark all but the last page as READWRITE.
                        #if defined(EA_PLATFORM_XENON)
                            if(nAllocationFlags & kAllocationFlagPhysical)
                                XPhysicalProtect(p, size_alloc, PAGE_READWRITE);
                            else
                                p = VirtualAlloc(p, size_alloc, MEM_COMMIT, PAGE_READWRITE);
                        #else
                            p = VirtualAlloc(p, size_alloc, MEM_COMMIT, PAGE_READWRITE);
                        #endif

                        if(p)
                        {
                            // Squish the allocation up against the end of the page.
                            void* const pReturnValue = (char*)p + size_alloc - aligned_size;

                            // Record what we need for the rest of the functions.
                            MemInfo* const pMemInfo = (MemInfo*)pReturnValue - 1;

                            memset(pMemInfo, 0, sizeof(MemInfo));
                            pMemInfo->mAvailable       = aligned_size;
                            pMemInfo->mSize            = nSize;
                            pMemInfo->mAllocationFlags = nAllocationFlags;
                            pMemInfo->mBase            = (uintptr_t)p;
                            pMemInfo->mSentinel        = kSentinelValue;

                            // Set up the stack info.
                            #if PPM_DEBUG_CALLSTACK_AVAILABLE
                                const size_t kStackCapacity = sizeof(pMemInfo->mStack) / sizeof(pMemInfo->mStack[0]);
                                GetCallStack(pMemInfo->mStack, kStackCapacity);
                            #endif

                            // Set up the description info
                            if(!pName)
                                pName = "No Name";
                            const size_t kDescriptionCapacity = sizeof(pMemInfo->mDescription) / sizeof(pMemInfo->mDescription[0]);
                            strncpy(pMemInfo->mDescription, pName, kDescriptionCapacity);
                            pMemInfo->mDescription[kDescriptionCapacity - 1] = NULL;

                            // Set up the MemInfo pointer. To consider: Do we actually need to do this?
                            *(uintptr_t**)p = (uintptr_t*)pMemInfo;

                            mAllocCount++;
                            return pReturnValue;
                        }
                    }

                    return NULL;

                #elif defined(EA_PLATFORM_PS3)
                    // Round up to kPageSize
                    size_alloc = ((size_alloc + (kPageSize - 1)) & -kPageSize);

                    // Make sure that we will be able to keep a consistent page-sized gap between allocs.
                    if((size_alloc % kScanSize) == 0)
                        size_alloc += kPageSize;

                    sys_memory_t pAddress;
                    int result = sys_mmapper_allocate_memory(size_alloc, SYS_MEMORY_GRANULARITY_64K, &pAddress);

                    if(result == CELL_OK)
                    {
                        // Make sure we don't get caught in the loop infinitely
                        sys_addr_t startAddress = mNextAllocAddress;

                        // To do: Find a way to make this loop more elegant.
                        while(1)
                        {
                            if(((sys_addr_t)((uint8_t*)mNextAllocAddress + size_alloc + kPageSize) > (sys_addr_t)((uint8_t*)mBaseAddress + kReserveSize)))
                                mNextAllocAddress = mBaseAddress;

                            result = sys_mmapper_map_memory(mNextAllocAddress, pAddress, SYS_MEMORY_PROT_READ_WRITE);

                            if(result != CELL_OK)
                            {
                                // Scan by 8K
                                mNextAllocAddress = (sys_addr_t)((uint8_t*)mNextAllocAddress + kScanSize);

                                if(startAddress == mNextAllocAddress)
                                {
                                    printf("StompAllocator::Malloc: We have ran out of Memory when cycling!");
                                    PPM_DebugBreak();
                                    return NULL;
                                }
                            }
                            else
                                break;
                        }

                        if(result == CELL_OK)
                        {
                            // Squish the allocation up against the end of the page.
                            void* const pReturnValue = (void*)((uint8_t *)mNextAllocAddress + size_alloc - aligned_size);

                            // Record what we need for the rest of the functions.
                            MemInfo* const pMemInfo = (MemInfo*)pReturnValue - 1;

                            pMemInfo->mAvailable       = aligned_size;
                            pMemInfo->mSize            = nSize;
                            pMemInfo->mAllocationFlags = nAllocationFlags;
                            pMemInfo->mBase            = mNextAllocAddress;
                            pMemInfo->mSentinel        = kSentinelValue;

                            // Set up the stack info.
                            #if PPM_DEBUG_CALLSTACK_AVAILABLE
                                const size_t kStackCapacity = sizeof(pMemInfo->mStack) / sizeof(pMemInfo->mStack[0]);
                                GetCallStack(pMemInfo->mStack, kStackCapacity);
                            #endif

                            // Set up the description info.
                            if(!pName)
                                pName = "No Name";
                            const size_t kDescriptionCapacity = sizeof(pMemInfo->mDescription) / sizeof(pMemInfo->mDescription[0]);
                            strncpy(pMemInfo->mDescription, pName, kDescriptionCapacity);
                            pMemInfo->mDescription[kDescriptionCapacity - 1] = NULL;

                            // Set up the MemInfo pointer.
                            // *(uintptr_t**)p = (uintptr_t*)pMemInfo;

                            // Move up our next allocation address and create a 4k gap between next allocation.
                            mNextAllocAddress = (sys_addr_t)((uint8_t*)mNextAllocAddress + size_alloc + kPageSize);

                            mAllocCount++;
                            return pReturnValue;
                        }
                    }
                    else
                    {
                        printf("StompAllocator::Malloc: Can't allocate memory!\n");
                        PPM_DebugBreak();
                    }

                    return NULL;
                #endif
            #else
                return malloc(nSize);
            #endif
        }


        void StompAllocator::Free(void* p)
        {
            if(p)
            {
                #if defined(EA_PLATFORM_XENON) || defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_PS3)
                    MemInfo* const pMemInfo = (MemInfo*)p - 1;

                    if(pMemInfo->mSentinel != kSentinelValue)
                        PPM_DebugBreak();

                    #if defined(EA_PLATFORM_XENON)
                        if(pMemInfo->mAllocationFlags & kAllocationFlagPhysical)
                            XPhysicalFree((void*)pMemInfo->mBase); 
                        else
                            VirtualFree((void*)pMemInfo->mBase, 0, MEM_RELEASE);

                    #elif defined(EA_PLATFORM_WINDOWS)
                        VirtualFree((void*)pMemInfo->mBase, 0, MEM_RELEASE);

                    #elif defined(EA_PLATFORM_PS3)
                        sys_memory_t pMemBase;
                        int result = sys_mmapper_unmap_memory((sys_addr_t)pMemInfo->mBase, &pMemBase);

                        if(result == CELL_OK)
                        {
                            // Give back the memory, but not the address space.
                            result = sys_mmapper_free_memory(pMemBase);

                            if(result != CELL_OK)
                            {
                                printf("StompAllocator::Free: sys_mmapper_free_memory() failed\n");
                                PPM_DebugBreak();
                            }
                        }
                        else
                        {
                            printf("StompAllocator::Free: sys_mmapper_unmap_memory() failed\n");
                            PPM_DebugBreak();
                        }
                    #endif
                #else
                    free(p);
                #endif
            }
        }


        size_t StompAllocator::GetSize(const void* p) const
        {
            const MemInfo* const pMemInfo = (MemInfo*)p - 1;

            return pMemInfo->mSize;
        }


        size_t StompAllocator::GetUsableSize(const void* p) const
        {
            const MemInfo* const pMemInfo = (MemInfo*)p - 1;

            return pMemInfo->mAvailable;
        }


    } // namespace Allocator

} // namespace EA





