///////////////////////////////////////////////////////////////////////////////
// EAGeneralAllocatorDebugPS3
//
// Copyright (c) 2005, Electronic Arts. All Rights Reserved.
// By Paul Pedriana, Maxis
//
// This module implements a platform-specific call stack functionality. 
///////////////////////////////////////////////////////////////////////////////


#include <EABase/eabase.h>
#include <PPMalloc/internal/config.h>
#include <PPMalloc/internal/callstack.h>

#if (defined(EA_PLATFORM_PS3) || defined(EA_PLATFORM_PS3_SPU)) && !PPM_USE_EATHREAD_CALLSTACK

#include <stddef.h>
#include <stdio.h>


namespace EA
{
    namespace Allocator
    {
        PPM_API void InitCallstack()
        {
            // Empty
        }


        PPM_API void ShutdownCallstack()
        {
            // Empty
        }


        /// GetCallStack
        /// Returns number of non-zero entries written. The return value may be zero.
        /// Note that the memory size of the entries is thus the return value * sizeof(void).
        /// The first entry in the returned array is the return address of the calling function;
        /// the second entry is the returned addess of the first entry's calling function, etc.
        /// Due to the complications that are often involved in doing stack traces, this function
        /// may return addresses that are invalid (NULL pointers or otherwise). The user should 
        /// be prepared for this and gracefully deal with such addresses.
        ///
        #if defined(EA_PLATFORM_PS3)
        
            PPM_API size_t GetCallStack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity)
            {
                size_t    nEntryIndex(0);
                uint64_t* pFrame;           // The PS3 with 32 bit pointers still uses 64 bit pointers in its stack frames.
                void*     pInstruction;

                // General PowerPC asm for reading the instruction pointer (program counter).
                // mflr r4                  // Save link register
                // bl NextLine              // Jump to the next line. Instruction pointer will be in the link register (LR)
                // NextLine:                //
                // mflr r3                  // Copy LR to some other register.
                // mtlr r4                  // Restore the original LR.
                // std r4, pInstruction     // std for 64 bit pointers, stw for 32 bit pointers

                #if defined(__SNC__)
                    // We make a bogus function call to ourselves that we know will have no effect. We do this because we 
                    // need to force the generation of a stack frame for this function. Otherwise the optimizer might 
                    // realize this function calls no others and eliminate the stack frame.
                    if(nReturnAddressArrayCapacity == 0)
                        return 0;
                    else                    
                        GetCallStack(pReturnAddressArray, 0);  // This will do nothing but return via the 'return 0' above.

                    pFrame = (uint64_t*)__builtin_frame_address();
                #else
                    // The following statement causes the compiler to generate a stack frame
                    // for this function, as if this function were going to need to call 
                    // another function. We could call some arbitrary function here, but the
                    // statement below has the same effect under GCC.
                    __asm__ __volatile__("" : "=l" (pFrame) : );

                    // Load into pFrame (%0) the contents of what is pointed to by register 1 with 0 offset.
                    #if (EA_PLATFORM_PTR_SIZE == 8)
                        __asm__ __volatile__("ld %0, 0(1)" : "=r" (pFrame) : );
                    #else
                        uint64_t  temp;
                        __asm__ __volatile__("ld %0, 0(1)" : "=r" (temp) : );
                        pFrame = (uint64_t*)(uintptr_t)temp;
                    #endif
                #endif

                pInstruction = (void*)(uintptr_t)pFrame[2];

                while(pFrame && (nEntryIndex < (nReturnAddressArrayCapacity - 1)))
                {
                    // The LR save area of a stack frame holds the return address, and what
                    // we really want is the address of the calling instruction, which is in
                    // many cases the instruction located just before the return address.
                    // On PPC, this is simple to calculate due to every instruction being 
                    // four bytes in length. To do: Only do the -4 adjustment below if the 
                    // instruction before pInstruction is a function-calling instruction.
                    pInstruction = (void*)((uintptr_t)pInstruction - 4);

                    pReturnAddressArray[nEntryIndex++] = pInstruction;

                    const uint64_t* const pPrevFrame = pFrame;

                    pFrame = (uint64_t*)(uintptr_t)*pFrame;

                    // If pFrame is 0 or is not aligned as expected (16 bytes) or is 
                    // a pointer to an address that's before pPrevFrame, we quit.
                    if(!pFrame || ((uintptr_t)pFrame & 15) || (pFrame < pPrevFrame))
                        break;

                    pInstruction = (void*)(uintptr_t)pFrame[2];
                }

                pReturnAddressArray[nEntryIndex] = 0;
                return nEntryIndex;
            }

        #else

            struct SPUStackFrame
            {
                const SPUStackFrame* pNext;
                uint32_t             availableStackSize;
                uint32_t             unused1;
                uint32_t             unused2;
                const char*          pReturnAddress;
            } __attribute__((aligned(16)));


            // Declare a global register variable for the current compilation unit. This allows us 
            // to use the given register safely within the compilation unit. You can see what this 
            // is about at:
            //     http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Global-Reg-Vars.html#Global-Reg-Vars
            // This is not portable to SNC. It's probably best to use __builtin_frame_address() in SNC.
            #if defined(__SNC__)
                #define spRegister ((SPUStackFrame*)__builtin_frame_address())
                #define lrRegister ((char*)__builtin_return_address())
            #else
                register SPUStackFrame* spRegister __asm__("$sp");  // sp is the same as r1.word[0]. 
                register char*          lrRegister __asm__("$lr");  // lr is the same as r0.word[0].
            #endif


            ///////////////////////////////////////////////////////////////////////////////
            // GetCallStack
            //
            // Note that in optimized builds some functions may not have a stack frame.
            // This is typically so when the function has no local variables or has local
            // variables which can be somehow 'optimized away' by the optimizer.
            //
            // We have the following snippet which deals with the case of relocatable code.
            // It works by loading the link-time-specified address of the label "1:" into 
            // linkTimeAddress via the ila instruction. Then it loads the actual runtime 
            // address into the actualAddress variable via the brsl (branch-relative) 
            // instruction which simply branches to the next instruction and stores the 
            // the address of that instruction into actualAccress. If the code has been 
            // relocated upon load then relocationDistances will be non-zero. If you want
            // to represent the addresses in their original link-time form then you 
            // need to subtract relocationDistance from all values.
            //
            //    uint32_t linkTimeAddress, actualAddress, relocationDistance;
            //    __asm__ ("ila %0,1f\n\tbrsl\t%1,1f\n1:" : "=r" (linkTimeAddress), "=r" (actualAddress));
            //    relocationDistance = actualAddress - linkTimeAddress;
            //
            // In our case here, we report the link-time addresses, as the user usually 
            // wants to look these up in a symbol reader. 
            //
            // This function is about 270 bytes in size in a debug build and 170 in an optimized build.
            // 
            // If the pContext argument is non-NULL, then it must refer to a currently valid 
            // stack context. If pContext is non-NULL, then if its context or the current context
            // has been relocated then the resulting addresses may be an an incorrect offset.
            //
            PPM_API size_t GetCallStack(void* pAddressArray[], size_t nAddressArrayCapacity, const CallstackContext* pContext)
            {
                size_t nEntryIndex = 0;
                const SPUStackFrame* pFrame = (pContext ? (const SPUStackFrame*)pContext->mGPR1 : spRegister);
                const char* pReturnAddress = (pContext ? (const char*)pContext->mGPR0 : lrRegister);

                uint32_t linkTimeAddress, actualAddress, relocationDistance;
                __asm__ ("ila %0,1f\n\tbrsl\t%1,1f\n1:" : "=r" (linkTimeAddress), "=r" (actualAddress));
                relocationDistance = actualAddress - linkTimeAddress;

                for(pFrame = pFrame->pNext;
                    pFrame && (pFrame < (SPUStackFrame*)0x0003fff0) && (nEntryIndex < (nAddressArrayCapacity - 2)); 
                    pFrame = pFrame->pNext, pReturnAddress = pFrame->pReturnAddress)
                {
                    // The call address is often a single 32 bit instruction prior to the return address.
                    // Sometimes it is more instructions prior; uncommonly it can be instructions later.
                    pAddressArray[nEntryIndex++] = const_cast<char*>((pReturnAddress - 4) - relocationDistance);
                }

                pAddressArray[nEntryIndex++] = (void*)relocationDistance; // We store the relocation distance as the last entry, so the user can reference it if needed.
                pAddressArray[nEntryIndex]   = 0;

                return nEntryIndex;
            }
        #endif
    } // namespace Allocator

} // namespace EA


#endif // (platform checks)












