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

Copyright (C) 2017-2021 Intel Corporation

SPDX-License-Identifier: MIT

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

#ifndef _IGA_OPERAND_HPP_
#define _IGA_OPERAND_HPP_

#include "ImmVal.hpp"
#include "Types.hpp"

namespace iga {
class Block;

class Operand {
public:
    enum class Kind {
        INVALID,   // an invalid or uninitialized operand
        DIRECT,    // direct register reference
        MACRO,     // madm or math.invm or math.rsqrtm
        INDIRECT,  // register-indriect access
        IMMEDIATE, // immediate value
        LABEL,     // block target (can be numeric label/m_immVal)
    };

    Operand()
        : m_kind(Kind::INVALID)
        , m_lblBlock(nullptr)
        , m_type(Type::INVALID)
        , m_regOpSrcMod(SrcModifier::NONE) { }

    // direct destination constructor (for constants etc)
    Operand(
        DstModifier dstMod,
        RegName rType,
        const RegRef &reg,
        const Region::Horz &rgnHz,
        Type type)
        : m_lblBlock(nullptr)
    {
        setDirectDestination(dstMod, rType, reg, rgnHz, type);
    }

    // direct source constructor (for constants etc)
    Operand(
        SrcModifier srcMod,
        RegName rType,
        const RegRef &reg,
        const Region &rgn,
        Type type)
        : m_lblBlock(nullptr)
    {
        setDirectSource(srcMod, rType, reg, rgn, type);
    }

    // generally you want a reference to an operand, not a copy
    // this prevents:
    //    auto o = i->getSource(...); // copies all state
    // you usually want
    //    const auto &o = ...
    // but in rare cases maybe you want a copy
    //    Operand copy(i->getSource(...));
    explicit Operand(const Operand &) = default;

    // describes if the operand is direct (Operand::Kind::DIRECT),
    // indirect (Operand::Kind::INDIRECT) or immediate
    // (Operand::Kind::IMMEDIATE)
    Kind getKind() const {return m_kind;}

    // both labels and true immediates are considered immediates
    // for encoding and decoding sake
    bool isImm() const {
        switch (getKind()) {
        case Operand::Kind::IMMEDIATE:
        case Operand::Kind::LABEL:
            return true;
        default:
            return false;
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // derived accessors

    // returns true if a direct register reference to the null register
    bool isNull() const {
        return getKind() == Kind::DIRECT &&
            getDirRegName() == RegName::ARF_NULL;
    }

    ///////////////////////////////////////////////////////////////////////////
    // other accessors
    DstModifier getDstModifier() const { return m_regOpDstMod; }
    SrcModifier getSrcModifier() const { return m_regOpSrcMod; }

    // Applies to Operand::Kind::DIRECT
    RegName getDirRegName() const { return m_regOpName; }
    const RegRef &getDirRegRef() const { return m_regOpReg; }

    // Applies to Operand::Kind::INDIRECT only
    const RegRef &getIndAddrReg() const { return m_regOpReg; }
    int16_t getIndImmAddr() const { return m_regOpIndOff; }

    // Applies to Operand::Kind::DIRECT and Operand::Kind::INDIRECT
    Region getRegion() const { return m_regOpRgn; }
    MathMacroExt getMathMacroExt() const { return m_regMathMacro; }
    // Defined if the value is immediate
    const ImmVal getImmediateValue() const { return m_immValue; }
    // if this operand corresponds to a label, this is the target block
    // nullptr if we are using numeric labels
    const Block *getTargetBlock() const { return m_lblBlock; }

    // the operand type (as in :f, :d, ...)
    Type getType() const { return m_type; }

    // re-initializes this operand as an direct destination register operand
    void setDirectDestination(
        DstModifier dstMod,
        RegName rName,
        const RegRef &reg,
        const Region::Horz &rgnHz,
        Type type);
    // re-initializes this operand as a destination register operand using
    // a math macro register access
    void setMacroDestination(
        DstModifier dstMod,
        RegName rName,
        const RegRef &reg,
        MathMacroExt acc,
        Type type)
    {
        setMacroDestination(dstMod,rName,reg,acc,Region::Horz::HZ_1,type);
    }
    void setMacroDestination(
        DstModifier dstMod,
        RegName rName,
        const RegRef &reg,
        MathMacroExt acc,
        Region::Horz rgnHz,
        Type type);
    // re-initializes this operand as an indirect destination register operand
    void setInidirectDestination(
        DstModifier dstMod,
        const RegRef &reg,
        int16_t immediateOffset,
        const Region::Horz &rgnHz,
        Type type);

    // re-initializes this operand as an immeidate value with a given type
    void setImmediateSource(const ImmVal &val, Type type);
    // re-initializes this operand as a direct source register
    void setDirectSource(
        SrcModifier srcMod,
        RegName rName,
        const RegRef &reg,
        const Region &rgn,
        Type type);
    // re-initializes this operand as a source register operand using
    // an math macro register access
    void setMacroSource(
        SrcModifier srcMod,
        RegName rName,
        const RegRef &reg,
        Region /* rgn */,
        MathMacroExt mme,
        Type type)
    {
        setMacroSource(srcMod, rName, reg, mme, Region::SRC110, type);
    }
    void setMacroSource(
        SrcModifier srcMod,
        RegName rName,
        const RegRef &reg,
        MathMacroExt mme,
        Region rgn,
        Type type);

    // re-initializes this operand as an indirect register operand
    void setInidirectSource(
        SrcModifier srcMod,
        RegName regName,
        const RegRef &reg,
        int16_t addrImmOff,
        const Region &rgn,
        Type type);
    // re-initializes this operand as an immediate branch target
    void setLabelSource(Block *blk, Type type);
    void setLabelSource(int32_t jipOrUip, Type type);
    // set sthe operand region
    void setRegion(const Region &rgn) { m_regOpRgn = rgn; }
    // sets the operand type
    void setType(Type type) { m_type = type; }
    // set the operand reg RegRef directly
    void setRegRef(const RegRef& regRef)
    {
        m_regOpReg.regNum = regRef.regNum;
        m_regOpReg.subRegNum = regRef.subRegNum;
    }

  private:
    Operand::Kind m_kind;

    // direct/indirect register information
    union {
        SrcModifier m_regOpSrcMod;
        DstModifier m_regOpDstMod;
    };
    RegName m_regOpName = RegName::INVALID;
    MathMacroExt m_regMathMacro = MathMacroExt::INVALID;
    Region  m_regOpRgn = Region::INVALID;

    // direct/indirect register
    RegRef m_regOpReg;

    // indirect register offset
    int16_t m_regOpIndOff = 0;

    // Imm field information
    ImmVal m_immValue;
    // for resolved labels
    // the literal value (for immediate data)
    // also set before labels are resolved (label is .s32)
    // for numeric labels the value is normalized as bytes from
    // the pre-increment PC of the instruction.  Hence,
    //  * Pre-BDW branches which use units of QWORDS in the
    //    encoding will be converted to bytes.
    //  * JMPI uses the pos-increment PC; we normalize that here
    //    as well to a pre-increment value.
    Block* m_lblBlock = nullptr;

    // the operand type (e.g. :d, :f, etc...)
    Type m_type;

  public:
    // useful constants (reusable operands to streamline codegen)
    static const Operand DST_REG_IP_D; // brd/brc use this
    static const Operand SRC_REG_IP_D;
    static const Operand DST_REG_IP_UD; // jmpi Dst and Src0
    static const Operand SRC_REG_IP_UD;
    static const Operand DST_REG_NULL_UD; // e.g. while.Dst
    static const Operand SRC_REG_NULL_UD;
    static const Operand SRC_REG_NULL_UB;
}; // class Operand
} // namespace iga
#endif // _IGA_OPERAND_HPP_
