/*========================== begin_copyright_notice ============================

Copyright (C) 2017-2021 Intel Corporation

SPDX-License-Identifier: MIT

============================= end_copyright_notice ===========================*/

#ifndef _LOCALSCHEDULER_H_
#define _LOCALSCHEDULER_H_

#include "../Mem_Manager.h"
#include "../FlowGraph.h"
#include "../BuildIR.h" // add IR_Builder and G4_Kernel objects to support the exit code patch and combined kernel
#include "../G4_IR.hpp"
#include "../Timer.h"
#include "../BitSet.h"
#include "LatencyTable.h"
#include "Dependencies_G4IR.h"

#include <list>
#include <set>
#include <string>
#include <vector>


#define THREE_SOURCE_BLOCK_HERISTIC 0.5

namespace vISA {

class Node;
class DDD;
class G4_BB_Schedule;
class LocalScheduler;
class RPE;

class Edge {
    // The node at the end of this edge
    Node* node;
    // Type of Dependence (RAW, WAW, WAR, etc.)
    DepType type;
    // The set of variables that are live across this edge
    uint32_t latency;
public:
    Edge(Node *Node, DepType Type, uint32_t Latency) {
        node = Node;
        type = Type;
        latency = Latency;
    }
    Node *getNode() const { return node; }
    DepType getType() const { return type; }
    void setType(DepType newType) { type = newType; }
    uint32_t getLatency(void) const { return latency; }
    void setLatency(uint32_t newLatency) { latency = newLatency; }
};

typedef std_arena_based_allocator<Edge> Edge_Allocator;
typedef std::vector<Edge> EdgeVector;

class Node
{
    // Unique ID of the node.
    unsigned nodeID;

    // LIR instruction pointer
    std::list<G4_INST *> instVec;

    // Longest distance to the end of the DAG.
    int priority = PRIORITY_UNINIT;

    // Earliest time an instruction can be issue.
    uint32_t earliest = 0;

    // Number of consecutive cycles this instruction occupies in the pipeline
    // (This is typically 2 for SIMD8 and 4 for SIMD16 8 for SIMD32)
    // This is *NOT* the dependency latency. That is part of the Edge.
    uint16_t occupancy = 0;

    // Indicates whether or not this node is a barrier (NONE, SEND, CONTROL).
    DepType barrier = DepType::NODEP;

    Node *lastSchedPred = nullptr;

    // Indicates that this Node is writing to a subreg.
    // This is used to avoid WAW hazzards during scheduling.
    int wSubreg = NO_SUBREG;

    bool hasTransitiveEdgeToBarrier = false;

public:
    static const uint32_t SCHED_CYCLE_UNINIT = UINT_MAX;
    static const int NO_SUBREG = INT_MAX;
    static const int PRIORITY_UNINIT = -1;

    unsigned getNodeID() const{ return nodeID; };

    uint32_t schedTime = 0;

    // Number of predecessor nodes not scheduled
    uint16_t predsNotScheduled = 0;

    // A list of instruction's predecessors
    EdgeVector preds;

    // A list of instruction's successors
    EdgeVector succs;
    void dump();

public:
    /* Constructor */
    Node(unsigned, G4_INST*, Edge_Allocator& depEdgeAllocator, const LatencyTable &LT);
    ~Node() { }
    void *operator new(size_t sz, Mem_Manager &m) { return m.alloc(sz); }
    const std::list<G4_INST *> *getInstructions() const { return &instVec; }
    DepType isBarrier() const { return barrier; }
    void MarkAsUnresolvedIndirAddressBarrier() {
      barrier = INDIRECT_ADDR_BARRIER;
    }
    DepType isLabel() const {
        for (G4_INST *inst : instVec) {
            if (inst->isLabel()) {
                assert(instVec.size() == 1 &&
                    "Should not have label + other instr in one node");
                return DEP_LABEL;
            }
        }
        return NODEP;
    }

    uint16_t getOccupancy() { return occupancy; }
    uint32_t getEarliest() const { return earliest; }
    int getPriority() const { return priority; }
    void setWritesToSubreg(int reg) { wSubreg = reg; }
    int writesToSubreg() { return wSubreg; }
    void addPairInstr(G4_INST *inst) { instVec.push_back(inst); }
    void clear() { instVec.clear(); }
    void deletePred(Node *pred);
    //Xe check BC between adjacent instructions
    bool hasConflict(Node* node2);

    friend class DDD;
    friend class G4_BB_Schedule;
};

using NODE_VECT = std::vector<Node *>;
using NODE_VECT_ITER = NODE_VECT::iterator;
using NODE_LIST = std::list<Node *>;
using NODE_LIST_ITER = NODE_LIST::iterator;

// The mask describes the range of data touched by a bucket.
struct Mask {
    unsigned short LeftB;
    unsigned short RightB;
    bool nonContiguousStride = false;
    G4_AccRegSel specialAcc = G4_AccRegSel::ACC_UNDEFINED;

    Mask() : LeftB(0), RightB(0) {}
    Mask(unsigned short LB, unsigned short RB, bool NCS, G4_AccRegSel Acc)
        : LeftB(LB), RightB(RB), nonContiguousStride(NCS), specialAcc(Acc) {
    }

    bool killsBucket(unsigned int bucket) const {
        unsigned short bucketLB = bucket * numEltPerGRF<Type_UB>();
        unsigned short bucketRB = (bucket + 1) * numEltPerGRF<Type_UB>() - 1;
        return !nonContiguousStride && !withSpecialAcc() &&
               LeftB <= bucketLB && RightB >= bucketRB;
    }

    bool kills(const Mask &mask2) const {
        return LeftB <= mask2.LeftB && RightB >= mask2.RightB &&
               specialAcc == mask2.specialAcc;
    }

    bool withSpecialAcc() const {
        return specialAcc != G4_AccRegSel::ACC_UNDEFINED;
    }

    bool hasOverlap(const Mask &mask2) const {
        return (LeftB <= mask2.RightB && RightB >= mask2.LeftB) ||
               (mask2.withSpecialAcc() && mask2.specialAcc == specialAcc);
    }
};

// This is a live node hanging from the bucket array.
// It represents an access to the bucket of type described by opndNum (R/W)
struct BucketNode {
    // The DAG node that this access belongs to
    Node *node;
    // Used to track whether it is read/write
    Gen4_Operand_Number opndNum;
    // The mask helps us track the dependences at a byte granularity.
    Mask mask;
    BucketNode(Node *node1, const Mask &mask1, Gen4_Operand_Number opndNum1)
        : node(node1), opndNum(opndNum1), mask(mask1) {}
};

typedef std::vector<BucketNode *> BUCKET_VECTOR;
typedef BUCKET_VECTOR::iterator BUCKET_VECTOR_ITER;

// This is the head node from which the list of live nodes hangs from.
// There is a single head node per bucket.
struct BucketHeadNode {
    // The list of live nodes hanging from this head node.
    BUCKET_VECTOR *bucketVec;
    // This is for future use. We can use it as an aggregate mask to avoid
    // searching through the list.
    Mask mask;
};

// Describes a single bucket access
struct BucketDescr {
    // This is the index into the bucket array. For GRFs it is the GRF num.
    int bucket;
    // This is used to tell whether this bucket is read/written
    Gen4_Operand_Number operand;
    // The mask helps us track the dependences at a byte granularity.
    Mask mask;
    BucketDescr(int Bucket, const Mask &Mask, Gen4_Operand_Number Operand)
        : bucket(Bucket), operand(Operand), mask(Mask) { ; }
};

class DDD {
    std::vector<Node *> allNodes;
    Mem_Manager &mem;
    Edge_Allocator depEdgeAllocator;
    int HWthreadsPerEU;
    bool useMTLatencies;
    bool isThreeSouceBlock;
    const LatencyTable LT;

    int GRF_BUCKET;
    int ACC_BUCKET;
    int FLAG0_BUCKET;
    int FLAG1_BUCKET;
    int A0_BUCKET;
    int SEND_BUCKET;
    int SCRATCH_SEND_BUCKET;
    int OTHER_ARF_BUCKET;
    int TOTAL_BUCKETS;
    int totalGRFNum;
    G4_Kernel* kernel;

    // Gather all initial ready nodes.
    void collectRoots();

public:
    typedef std::pair<Node *, Node *> instrPair_t;
    typedef std::vector<instrPair_t> instrPairVec_t;
    NODE_LIST Nodes, Roots;
    void moveDeps(Node *fromNode, Node *toNode);
    void pairTypedWriteOrURBWriteNodes(G4_BB *bb);

    bool hasReadSuppression(G4_INST *curInst, G4_INST *nextInst, BitSet &liveDst, BitSet &liveSrc);
    bool hasReadSuppression(G4_INST* prevInst, G4_INST* nextInst, bool multipSuppression);
    bool hasSameSourceOneDPAS(G4_INST * curInst, G4_INST * nextInst, BitSet & liveDst, BitSet & liveSrc);

    DDD(Mem_Manager& m, G4_BB* bb, const LatencyTable& lt, G4_Kernel* k);
    ~DDD()
    {
        if (Nodes.size())
        {
            for (NODE_LIST_ITER nIter = Nodes.begin(); nIter != Nodes.end(); nIter++)
            {
                Node *n = (*nIter);
                n->~Node();
            }
            Nodes.clear();
        }
    }
    void *operator new(size_t sz, Mem_Manager &m) { return m.alloc(sz); }
    void InsertNode(Node *node) { Nodes.push_back(node); }
    void dumpNodes(G4_BB *bb);
    void dumpDagDot(G4_BB *bb);
    uint32_t listScheduleForSuppression(G4_BB_Schedule* schedule);
    uint32_t listSchedule(G4_BB_Schedule*);
    void setPriority(Node *pred, const Edge &edge);
    void createAddEdge(Node* pred, Node* succ, DepType d);
    void DumpDotFile(const char*, const char*);

    void getBucketsForOperand(G4_INST *inst, Gen4_Operand_Number opnd_num,
                              G4_Operand *opnd,
                              std::vector<BucketDescr> &buckets);
    // Returns true if instruction has any indirect operands (dst or src)
    bool getBucketDescrs(Node *inst, std::vector<BucketDescr> &bucketDescrs);


    uint32_t getEdgeLatency_old(Node *node, DepType depT);
    uint32_t getEdgeLatency(Node *node, DepType depT);
    Mem_Manager* get_mem() { return &mem; }
    IR_Builder* getBuilder() const { return kernel->fg.builder; }
    const Options* getOptions() const { return kernel->getOptions(); }
    bool getIsThreeSourceBlock() { return isThreeSouceBlock; }
};

class G4_BB_Schedule {
    Mem_Manager &mem;
    G4_BB *bb;
    DDD *ddd;
    G4_Kernel *kernel;

public:
    std::vector<Node *> scheduledNodes;
    unsigned lastCycle = 0;
    unsigned sendStallCycle = 0;
    unsigned sequentialCycle  = 0;

    // Constructor
    G4_BB_Schedule(G4_Kernel* kernel, Mem_Manager& m, G4_BB* bb,
        const LatencyTable& LT);
    void *operator new(size_t sz, Mem_Manager &m){ return m.alloc(sz); }
    // Dumps the schedule
    void emit(std::ostream &);
    void dumpSchedule(G4_BB *bb);
    G4_BB *getBB() const { return bb; };
    G4_Kernel *getKernel() const { return kernel; }
    IR_Builder *getBuilder() const { return kernel->fg.builder; }
    const Options* getOptions() const { return kernel->getOptions(); }
};

class LocalScheduler {
    FlowGraph &fg;
    Mem_Manager &mem;

    // send latencies are now defined in FFLatency in LIR.cpp
    void EmitNode(Node *);

public:
    LocalScheduler(FlowGraph &flowgraph, Mem_Manager &m)
        : fg(flowgraph), mem(m) {}
    void localScheduling();
};

class preRA_Scheduler {
public:
    preRA_Scheduler(G4_Kernel& k, Mem_Manager& m, RPE* rpe);
    ~preRA_Scheduler();
    bool run();

private:
    G4_Kernel& kernel;
    Mem_Manager& mem;
    RPE* rpe;
    Options* m_options;
};

class GRFMode
{
public:
    GRFMode();
    void setCurrentMode(unsigned newMode) { currentMode = newMode; }
    unsigned getNumThreads() const { return configurations[currentMode].second; }
    unsigned getMinGRF() const { return configurations[0].first; }
    unsigned getMaxGRF() const { return configurations[configurations.size() - 1].first; }
    unsigned getDefaultGRF() const { return configurations[defaultMode].first; }
    unsigned getMinNumThreads() const { return configurations[configurations.size() - 1].second; }
    unsigned getMaxNumThreads() const { return configurations[0].second; }
    unsigned getDefaultNumThreads() const { return configurations[defaultMode].second; }

private:
    // Store all configurations <GRF, numThreads> for current platform
    std::vector<std::pair<unsigned, unsigned>> configurations;
    unsigned defaultMode;
    unsigned currentMode;
};

class preRA_RegSharing
{
public:
    preRA_RegSharing(G4_Kernel& k, Mem_Manager& m, RPE* rpe);
    ~preRA_RegSharing();
    bool run();

private:
    G4_Kernel& kernel;
    Mem_Manager& mem;
    RPE* rpe;

};
} // namespace vISA

#endif // _LOCALSCHEDULER_H_
