//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

#pragma once

#if defined(ASMJS_PLAT) || defined(ENABLE_WASM)

namespace WAsmJs 
{
    static const int DOUBLE_SLOTS_SPACE = (sizeof(double) / sizeof(Js::Var)); // 2 in x86 and 1 in x64
    static const double FLOAT_SLOTS_SPACE = (sizeof(float) / (double)sizeof(Js::Var)); // 1 in x86 and 0.5 in x64
    static const double INT_SLOTS_SPACE = (sizeof(int) / (double)sizeof(Js::Var)); // 1 in x86 and 0.5 in x64
    static const double SIMD_SLOTS_SPACE = (sizeof(SIMDValue) / sizeof(Js::Var)); // 4 in x86 and 2 in x64

#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
    namespace Tracing
    {
        int GetPrintCol();
        void PrintArgSeparator();
        void PrintBeginCall();
        void PrintNewLine();
        void PrintEndCall(int hasReturn);
        template <class T> void PrintEndCall(const unaligned T* playout) { PrintEndCall(playout->I1); }
        int PrintI32(int val);
        int64 PrintI64(int64 val);
        float PrintF32(float val);
        double PrintF64(double val);
    }
#endif
    void JitFunctionIfReady(class Js::ScriptFunction* func, uint interpretedCount = 0);
    bool ShouldJitFunction(class Js::FunctionBody* body, uint interpretedCount = 0);

    typedef Js::RegSlot RegSlot;

    uint32 ConvertOffset(uint32 offset, uint32 fromSize, uint32 toSize);
    template<typename ToType> uint32 ConvertOffset(uint32 offset, uint32 fromSize)
    {
        return ConvertOffset(offset, fromSize, sizeof(ToType));
    }
    template<typename FromType, typename ToType> uint32 ConvertOffset(uint32 offset)
    {
        return ConvertOffset<ToType>(offset, sizeof(FromType));
    }

    struct EmitInfoBase
    {
        EmitInfoBase(RegSlot location_) : location(location_) {}
        EmitInfoBase() : location(Js::Constants::NoRegister) {}

        RegSlot location;
    };

    enum Types
    {
        INT32,
        INT64,
        FLOAT32,
        FLOAT64,
        SIMD,
        LIMIT
    };
    const Types FirstType = (Types)0;
    const Types LastType = (Types)(LIMIT - 1);
    uint32 GetTypeByteSize(Types type);
    Types FromIRType(IRType irType);
    template<typename T>
    Types FromPrimitiveType();

    /// Register space for const, parameters, variables and tmp values
    ///     --------------------------------------------------------
    ///     | Reserved  | Consts  | Parameters | Variables | Tmp
    ///     --------------------------------------------------------
    ///     Cannot allocate in any different order
    class RegisterSpace
    {
        // Total number of register allocated
        RegSlot   mRegisterCount;

        // location of the first temporary register and last variable + 1
        RegSlot   mFirstTmpReg;

        // Location of the next register to be allocated
        RegSlot   mNextLocation;

        // number of const, includes the reserved slots
        RegSlot    mNbConst;

    public:
        // Constructor
        RegisterSpace(RegSlot reservedSlotsCount = 0) :
            mRegisterCount( reservedSlotsCount )
            , mFirstTmpReg( reservedSlotsCount )
            , mNextLocation( reservedSlotsCount )
            , mNbConst( reservedSlotsCount )
        {
        }
        // Get the number of const allocated
        RegSlot GetConstCount() const      { return mNbConst; }
        // Get the location of the first temporary register
        RegSlot GetFirstTmpRegister() const{ return mFirstTmpReg; }
        // Get the total number of temporary register allocated
        RegSlot GetTmpCount() const        { return mRegisterCount-mFirstTmpReg; }
        // Get number of local variables
        RegSlot GetVarCount() const        { return mFirstTmpReg - mNbConst; }
        // Get the total number of variable allocated ( including temporaries )
        RegSlot GetTotalVarCount() const   { return mRegisterCount - mNbConst; }
        RegSlot GetRegisterCount() const   { return mRegisterCount; }

        // Acquire a location for a register. Use only for arguments and Variables
        RegSlot AcquireRegister()
        {
            // Makes sure no temporary register have been allocated yet
            Assert(mFirstTmpReg == mRegisterCount && mNextLocation == mFirstTmpReg);
            ++mFirstTmpReg;
            ++mRegisterCount;
            return mNextLocation++;
        }

        // Acquire a location for a constant
        RegSlot AcquireConstRegister()
        {
            ++mNbConst;
            return AcquireRegister();
        }

        // Acquire a location for a temporary register
        RegSlot AcquireTmpRegister()
        {
            // Make sure this function is called correctly
            Assert(mNextLocation <= mRegisterCount && mNextLocation >= mFirstTmpReg);

            // Allocate a new temp pseudo-register, increasing the locals count if necessary.
            if(mNextLocation == mRegisterCount)
            {
                ++mRegisterCount;
            }
#if DBG_DUMP
            PrintTmpRegisterAllocation(mNextLocation);
#endif
            return mNextLocation++;
        }

        RegSlot PeekNextTmpRegister()
        {
            // Make sure this function is called correctly
            Assert(mNextLocation <= mRegisterCount && mNextLocation >= mFirstTmpReg);
            return mNextLocation;
        }

        // Release a location for a temporary register, must be the last location acquired
        void ReleaseTmpRegister( RegSlot tmpReg )
        {
            // make sure the location released is valid
            Assert(tmpReg != Js::Constants::NoRegister);

            // Put this reg back on top of the temp stack (if it's a temp).
            if( this->IsTmpReg( tmpReg ) )
            {
                Assert( tmpReg == this->mNextLocation - 1 );
#if DBG_DUMP
                PrintTmpRegisterAllocation(mNextLocation - 1, true);
#endif
                mNextLocation--;
            }
        }

        // Checks if the register is a temporary register
        bool IsTmpReg(RegSlot tmpReg)
        {
            Assert(mFirstTmpReg != Js::Constants::NoRegister);
            return !IsConstReg(tmpReg) && tmpReg >= mFirstTmpReg;
        }

        // Checks if the register is a const register
        bool IsConstReg(RegSlot reg)
        {
            // a register is const if it is between the first register and the end of consts
            return reg < mNbConst && reg != 0;
        }

        // Checks if the register is a variable register
        bool IsVarReg(RegSlot reg)
        {
            // a register is a var if it is between the last const and the end
            // equivalent to  reg>=mNbConst && reg<mRegisterCount
            // forcing unsigned, if reg < mNbConst then reg-mNbConst = 0xFFFFF..
            return (uint32)(reg - mNbConst) < (uint32)(mRegisterCount - mNbConst);
        }

        // Releases a location if its a temporary, safe to call with any expression
        void ReleaseLocation(const EmitInfoBase *pnode)
        {
            // Release the temp assigned to this expression so it can be re-used.
            if(pnode && pnode->location != Js::Constants::NoRegister)
            {
                ReleaseTmpRegister(pnode->location);
            }
        }

        // Checks if the location points to a temporary register
        bool IsTmpLocation(const EmitInfoBase* pnode)
        {
            if(pnode && pnode->location != Js::Constants::NoRegister)
            {
                return IsTmpReg(pnode->location);
            }
            return false;
        }

        // Checks if the location points to a constant register
        bool IsConstLocation(const EmitInfoBase* pnode)
        {
            if(pnode && pnode->location != Js::Constants::NoRegister)
            {
                return IsConstReg(pnode->location);
            }
            return false;
        }

        // Checks if the location points to a variable register
        bool IsVarLocation(const EmitInfoBase* pnode)
        {
            if(pnode && pnode->location != Js::Constants::NoRegister)
            {
                return IsVarReg(pnode->location);
            }
            return false;
        }

        // Checks if the location is valid (within bounds of already allocated registers)
        bool IsValidLocation(const EmitInfoBase* pnode)
        {
            if(pnode && pnode->location != Js::Constants::NoRegister)
            {
                return pnode->location < mRegisterCount;
            }
            return false;
        }

#if DBG_DUMP
        // Used for debugging
        Types mType;
        static void GetTypeDebugName(Types type, char16* buf, uint bufsize, bool shortName = false);
        void PrintTmpRegisterAllocation(RegSlot loc, bool deallocation = false);
#endif
    };

    struct TypedConstSourcesInfo
    {
        uint32 srcByteOffsets[WAsmJs::LIMIT];
        uint32 bytesUsed;
    };

    struct TypedSlotInfo
    {
        TypedSlotInfo(): constCount(0), varCount(0), tmpCount(0), byteOffset(0), constSrcByteOffset(0) { }
        Field(uint32) constCount;
        Field(uint32) varCount;
        Field(uint32) tmpCount;
        // Offset in bytes from the start of InterpreterStack::m_localSlot
        Field(uint32) byteOffset;
        // Offset in bytes from the start of the const table before shuffling (InterpreterStackFrame::AlignMemoryForAsmJs())
        Field(uint32) constSrcByteOffset;
    };

    typedef RegisterSpace*(*AllocateRegisterSpaceFunc)(ArenaAllocator*, WAsmJs::Types);
    class TypedRegisterAllocator
    {
        uint32 mExcludedMask;
        RegisterSpace* mTypeSpaces[WAsmJs::LIMIT];
    public:
        TypedRegisterAllocator(ArenaAllocator* allocator, AllocateRegisterSpaceFunc allocateFunc, uint32 excludedMask = 0);

        void CommitToFunctionInfo(Js::AsmJsFunctionInfo* funcInfo, Js::FunctionBody* body) const;
        void CommitToFunctionBody(Js::FunctionBody* body);
        TypedConstSourcesInfo GetConstSourceInfos() const;

        bool IsTypeExcluded(Types type) const;
#if DBG_DUMP
        void DumpLocalsInfo() const;
        // indexes' array size must be WAsmJs::RegisterSpace::LIMIT
        void GetArgumentStartIndex(uint32* indexes) const;
#endif

        RegisterSpace* GetRegisterSpace(Types type) const;
    private:
        bool IsValidType(Types type) const;
    };
};

#endif
