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

class ExprAttributes
{
protected:
    uint32 attributes;

public:
    ExprAttributes(const uint32 attributes = 0) : attributes(attributes)
    {
    }

    uint32 Attributes() const
    {
        return attributes;
    }

private:
    static uint32 BitMask(const uint index)
    {
        return 1u << index;
    }

protected:
    void SetBitAttribute(const uint index, const bool bit)
    {
        if(bit)
        {
            attributes |= BitMask(index);
        }
        else
        {
            attributes &= ~BitMask(index);
        }
    }
};

class ExprHash
{
public:
    ExprHash() { this->opcode = 0; }
    ExprHash(int init) { Assert(init == 0); this->opcode = 0; }

    void Init(Js::OpCode opcode, ValueNumber src1Val, ValueNumber src2Val, ExprAttributes exprAttributes)
    {
        extern uint8 OpCodeToHash[(int)Js::OpCode::Count];

        uint32 opCodeHash = OpCodeToHash[(int)opcode];
        this->opcode = opCodeHash;
        this->src1Val = src1Val;
        this->src2Val = src2Val;
        this->attributes = exprAttributes.Attributes();

        // Assert too many opcodes...
        AssertMsg(this->opcode == (uint32)opCodeHash, "Opcode value too large for CSEs");
        AssertMsg(this->attributes == exprAttributes.Attributes(), "Not enough bits for expr attributes");

        // If value numbers are too large, just give up
        if (this->src1Val != src1Val || this->src2Val != src2Val)
        {
            this->opcode = 0;
            this->src1Val = 0;
            this->src2Val = 0;
            this->attributes = 0;
        }
    }

    Js::OpCode  GetOpcode()             { return (Js::OpCode)this->opcode; }
    ValueNumber GetSrc1ValueNumber()    { return this->src1Val; }
    ValueNumber GetSrc2ValueNumber()    { return this->src2Val; }
    ExprAttributes GetExprAttributes()  { return this->attributes; }
    bool        IsValid()               { return this->opcode != 0; }

    operator    uint()                  { return *(uint*)this; }

private:
    uint32  opcode: 8;
    uint32  src1Val: 11;
    uint32  src2Val: 11;
    uint32  attributes: 2;
};

#include "GlobHashTable.h"

typedef ValueHashTable<Sym *, Value *> GlobHashTable;
typedef HashBucket<Sym *, Value *> GlobHashBucket;

typedef ValueHashTable<ExprHash, Value *> ExprHashTable;
typedef HashBucket<ExprHash, Value *> ExprHashBucket;

typedef JsUtil::BaseHashSet<Value *, JitArenaAllocator> ValueSet;

struct StackLiteralInitFldData
{
    const Js::PropertyIdArray * propIds;
    uint currentInitFldCount;
};

typedef JsUtil::BaseDictionary<StackSym *, StackLiteralInitFldData, JitArenaAllocator> StackLiteralInitFldDataMap;

typedef SList<GlobHashBucket*, JitArenaAllocator> PRECandidatesList;
class PRECandidates
{
public:
    PRECandidates() : candidatesToProcess(nullptr), candidatesBv(nullptr), candidatesList(nullptr) {}

public:
    BVSparse<JitArenaAllocator> * candidatesToProcess;
    BVSparse<JitArenaAllocator> * candidatesBv;
    PRECandidatesList * candidatesList;
};


class GlobOptBlockData
{
    friend class BasicBlock;
public:
    GlobOptBlockData(Func *func) :
        symToValueMap(nullptr),
        exprToValueMap(nullptr),
        liveFields(nullptr),
        liveArrayValues(nullptr),
        maybeWrittenTypeSyms(nullptr),
        liveVarSyms(nullptr),
        liveInt32Syms(nullptr),
        liveLossyInt32Syms(nullptr),
        liveFloat64Syms(nullptr),
        argObjSyms(nullptr),
        maybeTempObjectSyms(nullptr),
        canStoreTempObjectSyms(nullptr),
        valuesToKillOnCalls(nullptr),
        inductionVariables(nullptr),
        availableIntBoundChecks(nullptr),
        startCallCount(0),
        argOutCount(0),
        totalOutParamCount(0),
        callSequence(nullptr),
        capturedValuesCandidate(nullptr),
        capturedValues(nullptr),
        changedSyms(nullptr),
        hasCSECandidates(false),
        curFunc(func),
        hasDataRef(nullptr),
        stackLiteralInitFldDataMap(nullptr),
        globOpt(nullptr)
    {
    }

    // Data
    GlobHashTable *                         symToValueMap;
    ExprHashTable *                         exprToValueMap;
    BVSparse<JitArenaAllocator> *           liveFields;
    BVSparse<JitArenaAllocator> *           liveArrayValues;
    BVSparse<JitArenaAllocator> *           maybeWrittenTypeSyms;
    BVSparse<JitArenaAllocator> *           isTempSrc;
    BVSparse<JitArenaAllocator> *           liveVarSyms;
    BVSparse<JitArenaAllocator> *           liveInt32Syms;
    // 'liveLossyInt32Syms' includes only syms that contain an int value that may not fully represent the value of the
    // equivalent var sym. The set (liveInt32Syms - liveLossyInt32Syms) includes only syms that contain an int value that fully
    // represents the value of the equivalent var sym, such as when (a + 1) is type-specialized. Among other things, this
    // bit-vector is used, based on the type of conversion that is needed, to determine whether conversion is necessary, and if
    // so, whether a bailout is needed. For instance, after type-specializing (a | 0), the int32 sym of 'a' cannot be reused in
    // (a + 1) during type-specialization. It needs to be converted again using a lossless conversion with a bailout.
    // Conversely, a lossless int32 sym can be reused to avoid a lossy conversion.
    BVSparse<JitArenaAllocator> *           liveLossyInt32Syms;
    BVSparse<JitArenaAllocator> *           liveFloat64Syms;
    BVSparse<JitArenaAllocator> *           argObjSyms;
    BVSparse<JitArenaAllocator> *           maybeTempObjectSyms;
    BVSparse<JitArenaAllocator> *           canStoreTempObjectSyms;

    // This is the func that this block comes from, so the inlinee if from an inlined function
    Func *                                  curFunc;

    // 'valuesToKillOnCalls' includes values whose value types need to be killed upon a call. Upon a call, the value types of
    // values in the set are updated and removed from the set as appropriate.
    ValueSet *                              valuesToKillOnCalls;

    InductionVariableSet *                  inductionVariables;
    IntBoundCheckSet *                      availableIntBoundChecks;

    // Bailout data
    uint                                    startCallCount;
    uint                                    argOutCount;
    uint                                    totalOutParamCount;
    SListBase<IR::Opnd *> *                 callSequence;
    StackLiteralInitFldDataMap *            stackLiteralInitFldDataMap;

    CapturedValues *                        capturedValuesCandidate;
    CapturedValues *                        capturedValues;
    BVSparse<JitArenaAllocator> *           changedSyms;

    uint                                    inlinedArgOutSize;

    bool                                    hasCSECandidates;

private:
    bool *                                  hasDataRef;

    GlobOpt *                               globOpt;

public:
    void OnDataInitialized(JitArenaAllocator *const allocator)
    {
        Assert(allocator);

        hasDataRef = JitAnew(allocator, bool, true);
    }

    void OnDataReused(GlobOptBlockData *const fromData)
    {
        // If a block's data is deleted, *hasDataRef will be set to false. Since these two blocks are pointing to the same data,
        // they also need to point to the same has-data info.
        hasDataRef = fromData->hasDataRef;
    }

    void OnDataUnreferenced()
    {
        // Other blocks may still be using the data, we should only un-reference the previous data
        hasDataRef = nullptr;
    }

    void OnDataDeleted()
    {
        if (hasDataRef)
            *hasDataRef = false;
        OnDataUnreferenced();
    }

    bool HasData()
    {
        if (!hasDataRef)
            return false;
        if (*hasDataRef)
            return true;
        OnDataUnreferenced();
        return false;
    }

    // Functions pulled out of GlobOpt.cpp

    // Initialization/copying/moving
public:
    void                    NullOutBlockData(GlobOpt* globOpt, Func* func);
    void                    InitBlockData(GlobOpt* globOpt, Func* func);
    void                    ReuseBlockData(GlobOptBlockData *fromData);
    void                    CopyBlockData(GlobOptBlockData *fromData);
    void                    DeleteBlockData();
    void                    CloneBlockData(BasicBlock *const toBlock, BasicBlock *const fromBlock);

public:
    void                    RemoveUnavailableCandidates(PRECandidates *candidates);

    // Merging functions
public:
    void                    MergeBlockData(BasicBlock *toBlockContext, BasicBlock *fromBlock, BVSparse<JitArenaAllocator> *const symsRequiringCompensation, BVSparse<JitArenaAllocator> *const symsCreatedForMerge, bool forceTypeSpecOnLoopHeader);
private:
    template <typename CaptureList, typename CapturedItemsAreEqual>
    void                    MergeCapturedValues(SListBase<CaptureList>* toList, SListBase<CaptureList> * fromList, CapturedItemsAreEqual itemsAreEqual);
    void                    MergeValueMaps(BasicBlock *toBlock, BasicBlock *fromBlock, BVSparse<JitArenaAllocator> *const symsRequiringCompensation, BVSparse<JitArenaAllocator> *const symsCreatedForMerge);
    Value *                 MergeValues(Value *toDataValue, Value *fromDataValue, Sym *fromDataSym, bool isLoopBackEdge, bool forceUniqueValue, BVSparse<JitArenaAllocator> *const symsRequiringCompensation, BVSparse<JitArenaAllocator> *const symsCreatedForMerge);
    ValueInfo *             MergeValueInfo(Value *toDataVal, Value *fromDataVal, Sym *fromDataSym, bool isLoopBackEdge, bool sameValueNumber, BVSparse<JitArenaAllocator> *const symsRequiringCompensation, BVSparse<JitArenaAllocator> *const symsCreatedForMerge);
    JsTypeValueInfo *       MergeJsTypeValueInfo(JsTypeValueInfo * toValueInfo, JsTypeValueInfo * fromValueInfo, bool isLoopBackEdge, bool sameValueNumber);
    ValueInfo *             MergeArrayValueInfo(const ValueType mergedValueType, const ArrayValueInfo *const toDataValueInfo, const ArrayValueInfo *const fromDataValueInfo, Sym *const arraySym, BVSparse<JitArenaAllocator> *const symsRequiringCompensation, BVSparse<JitArenaAllocator> *const symsCreatedForMerge, bool isLoopBackEdge);

    // Argument Tracking
public:
    void                    TrackArgumentsSym(IR::RegOpnd const* opnd);
    void                    ClearArgumentsSym(IR::RegOpnd const* opnd);
    BOOL                    TestAnyArgumentsSym() const;
    BOOL                    IsArgumentsSymID(SymID id) const;
    BOOL                    IsArgumentsOpnd(IR::Opnd const* opnd) const;
private:

    // Value Tracking
public:
    Value *                 FindValue(Sym *sym);
    Value *                 FindValueFromMapDirect(SymID symId);
    Value *                 FindPropertyValue(SymID symId);
    Value *                 FindObjectTypeValue(StackSym* typeSym);
    Value *                 FindObjectTypeValue(SymID typeSymId);
    Value *                 FindObjectTypeValueNoLivenessCheck(StackSym* typeSym);
    Value *                 FindObjectTypeValueNoLivenessCheck(SymID typeSymId);
    Value *                 FindFuturePropertyValue(PropertySym *const propertySym);

    StackSym *              GetCopyPropSym(Sym * sym, Value * val);

    Value *                 InsertNewValue(Value *val, IR::Opnd *opnd);
    Value *                 SetValue(Value *val, IR::Opnd *opnd);
    void                    SetValue(Value *val, Sym * sym);

    void                    ClearSymValue(Sym *sym);

private:
    void                    SetValueToHashTable(GlobHashTable * valueNumberMap, Value *val, Sym *sym);

    // Temp Tracking
public:
    void                    MarkTempLastUse(IR::Instr *instr, IR::RegOpnd *regOpnd);
private:

    // Liveness Tracking
public:
    void                    MakeLive(StackSym *sym, const bool lossy);

    // Checks if the symbol is live in this block under any type
    bool                    IsLive(Sym const * sym) const;
    bool                    IsTypeSpecialized(Sym const * sym) const;
    bool                    IsSwitchInt32TypeSpecialized(IR::Instr const * instr) const;
    bool                    IsInt32TypeSpecialized(Sym const * sym) const;
    bool                    IsFloat64TypeSpecialized(Sym const * sym) const;

    // Changed Symbol Tracking
public:
    void                    SetChangedSym(SymID symId);
    void                    SetChangedSym(Sym* sym);
private:

    // Other
    void                    KillSymToValueMapForGeneratorYield();
public:
    void                    KillStateForGeneratorYield(IR::Instr *yieldInstr);

    // Debug
public:
    void                    DumpSymToValueMap() const;
private:
    static void             DumpSym(Sym *sym);
};
