/*
 * Copyright (c) 2012-2015 Advanced Micro Devices, Inc.
 * All rights reserved.
 *
 * For use for simulation and test purposes only
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Author: Steve Reinhardt
 */

#ifndef __ARCH_HSAIL_OPERAND_HH__
#define __ARCH_HSAIL_OPERAND_HH__

/**
 *  @file operand.hh
 *
 *  Defines classes encapsulating HSAIL instruction operands.
 */

#include <limits>
#include <string>

#include "arch/hsail/Brig.h"
#include "base/trace.hh"
#include "base/types.hh"
#include "debug/GPUReg.hh"
#include "enums/RegisterType.hh"
#include "gpu-compute/brig_object.hh"
#include "gpu-compute/compute_unit.hh"
#include "gpu-compute/hsail_code.hh"
#include "gpu-compute/shader.hh"
#include "gpu-compute/vector_register_file.hh"
#include "gpu-compute/wavefront.hh"

class Label;
class StorageElement;

class BaseOperand
{
  public:
    Enums::RegisterType registerType;
    uint32_t regOperandSize;
    BaseOperand() { registerType = Enums::RT_NONE; regOperandSize = 0; }
    bool isVectorRegister() { return registerType == Enums::RT_VECTOR; }
    bool isScalarRegister() { return registerType == Enums::RT_SCALAR; }
    bool isCondRegister() { return registerType == Enums::RT_CONDITION; }
    unsigned int regIndex() { return 0; }
    uint32_t opSize() { return regOperandSize; }
    virtual ~BaseOperand() { }
};

class BrigRegOperandInfo
{
  public:
    Brig::BrigKind16_t kind;
    Brig::BrigType type;
    Brig::BrigRegisterKind regKind;

    BrigRegOperandInfo(Brig::BrigKind16_t _kind,
                       Brig::BrigRegisterKind _regKind)
        : kind(_kind), regKind(_regKind)
    {
    }

    BrigRegOperandInfo(Brig::BrigKind16_t _kind, Brig::BrigType _type)
        : kind(_kind), type(_type)
    {
    }

    BrigRegOperandInfo() : kind(Brig::BRIG_KIND_OPERAND_CONSTANT_BYTES),
                           type(Brig::BRIG_TYPE_NONE)
    {
    }
};

BrigRegOperandInfo findRegDataType(unsigned opOffset, const BrigObject *obj);

class BaseRegOperand : public BaseOperand
{
  public:
    unsigned regIdx;
    char regFileChar;

    bool init(unsigned opOffset, const BrigObject *obj,
              unsigned &maxRegIdx, char _regFileChar);

    bool init_from_vect(unsigned opOffset, const BrigObject *obj, int at,
                        unsigned &maxRegIdx, char _regFileChar);

    void initWithStrOffset(unsigned strOffset, const BrigObject *obj,
                           unsigned &maxRegIdx, char _regFileChar);
    unsigned int regIndex() { return regIdx; }
};

class SRegOperand : public BaseRegOperand
{
  public:
    static unsigned maxRegIdx;

    bool
    init(unsigned opOffset, const BrigObject *obj)
    {
        regOperandSize = sizeof(uint32_t);
        registerType = Enums::RT_VECTOR;

        return BaseRegOperand::init(opOffset, obj, maxRegIdx, 's');
    }

    bool
    init_from_vect(unsigned opOffset, const BrigObject *obj, int at)
    {
        regOperandSize = sizeof(uint32_t);
        registerType = Enums::RT_VECTOR;

        return BaseRegOperand::init_from_vect(opOffset, obj, at, maxRegIdx,
                                              's');
    }

    void
    initWithStrOffset(unsigned strOffset, const BrigObject *obj)
    {
        regOperandSize = sizeof(uint32_t);
        registerType = Enums::RT_VECTOR;

        return BaseRegOperand::initWithStrOffset(strOffset, obj, maxRegIdx,
                                                 's');
    }

    template<typename OperandType>
    OperandType
    get(Wavefront *w, int lane)
    {
        assert(sizeof(OperandType) <= sizeof(uint32_t));
        assert(regIdx < w->maxSpVgprs);
        // if OperandType is smaller than 32-bit, we truncate the value
        OperandType ret;
        uint32_t vgprIdx;

        switch (sizeof(OperandType)) {
          case 1: // 1 byte operand
              vgprIdx = w->remap(regIdx, 1, 1);
              ret = (w->computeUnit->vrf[w->simdId]->
                      read<uint32_t>(vgprIdx, lane)) & 0xff;
            break;
          case 2: // 2 byte operand
              vgprIdx = w->remap(regIdx, 2, 1);
              ret = (w->computeUnit->vrf[w->simdId]->
                      read<uint32_t>(vgprIdx, lane)) & 0xffff;
            break;
          case 4: // 4 byte operand
              vgprIdx = w->remap(regIdx,sizeof(OperandType), 1);
              ret = w->computeUnit->vrf[w->simdId]->
                  read<OperandType>(vgprIdx, lane);
            break;
          default:
            panic("Bad OperandType\n");
            break;
        }

        return (OperandType)ret;
    }

    // special get method for compatibility with LabelOperand
    uint32_t
    getTarget(Wavefront *w, int lane)
    {
        return get<uint32_t>(w, lane);
    }

    template<typename OperandType>
    void set(Wavefront *w, int lane, OperandType &val);
    std::string disassemble();
};

template<typename OperandType>
void
SRegOperand::set(Wavefront *w, int lane, OperandType &val)
{
    DPRINTF(GPUReg, "CU%d, WF[%d][%d], lane %d: $s%d <- %d\n",
            w->computeUnit->cu_id, w->simdId, w->wfSlotId, lane, regIdx, val);

    assert(sizeof(OperandType) == sizeof(uint32_t));
    assert(regIdx < w->maxSpVgprs);
    uint32_t vgprIdx = w->remap(regIdx, sizeof(OperandType), 1);
    w->computeUnit->vrf[w->simdId]->write<OperandType>(vgprIdx,val,lane);
}

template<>
inline void
SRegOperand::set(Wavefront *w, int lane, uint64_t &val)
{
    DPRINTF(GPUReg, "CU%d, WF[%d][%d], lane %d: $s%d <- %d\n",
            w->computeUnit->cu_id, w->simdId, w->wfSlotId, lane, regIdx, val);

    assert(regIdx < w->maxSpVgprs);
    uint32_t vgprIdx = w->remap(regIdx, sizeof(uint32_t), 1);
    w->computeUnit->vrf[w->simdId]->write<uint32_t>(vgprIdx, val, lane);
}

class DRegOperand : public BaseRegOperand
{
  public:
    static unsigned maxRegIdx;

    bool
    init(unsigned opOffset, const BrigObject *obj)
    {
        regOperandSize = sizeof(uint64_t);
        registerType = Enums::RT_VECTOR;

        return BaseRegOperand::init(opOffset, obj, maxRegIdx, 'd');
    }

    bool
    init_from_vect(unsigned opOffset, const BrigObject *obj, int at)
    {
        regOperandSize = sizeof(uint64_t);
        registerType = Enums::RT_VECTOR;

        return BaseRegOperand::init_from_vect(opOffset, obj, at, maxRegIdx,
                                              'd');
    }

    void
    initWithStrOffset(unsigned strOffset, const BrigObject *obj)
    {
        regOperandSize = sizeof(uint64_t);
        registerType = Enums::RT_VECTOR;

        return BaseRegOperand::initWithStrOffset(strOffset, obj, maxRegIdx,
                                                 'd');
    }

    template<typename OperandType>
    OperandType
    get(Wavefront *w, int lane)
    {
        assert(sizeof(OperandType) <= sizeof(uint64_t));
        // TODO: this check is valid only for HSAIL
        assert(regIdx < w->maxDpVgprs);
        uint32_t vgprIdx = w->remap(regIdx, sizeof(OperandType), 1);

        return w->computeUnit->vrf[w->simdId]->read<OperandType>(vgprIdx,lane);
    }

    template<typename OperandType>
    void
    set(Wavefront *w, int lane, OperandType &val)
    {
        DPRINTF(GPUReg, "CU%d, WF[%d][%d], lane %d: $d%d <- %d\n",
                w->computeUnit->cu_id, w->simdId, w->wfSlotId, lane, regIdx,
                val);

        assert(sizeof(OperandType) <= sizeof(uint64_t));
        // TODO: this check is valid only for HSAIL
        assert(regIdx < w->maxDpVgprs);
        uint32_t vgprIdx = w->remap(regIdx, sizeof(OperandType), 1);
        w->computeUnit->vrf[w->simdId]->write<OperandType>(vgprIdx,val,lane);
    }

    std::string disassemble();
};

class CRegOperand : public BaseRegOperand
{
  public:
    static unsigned maxRegIdx;

    bool
    init(unsigned opOffset, const BrigObject *obj)
    {
        regOperandSize = sizeof(uint8_t);
        registerType = Enums::RT_CONDITION;

        return BaseRegOperand::init(opOffset, obj, maxRegIdx, 'c');
    }

    bool
    init_from_vect(unsigned opOffset, const BrigObject *obj, int at)
    {
        regOperandSize = sizeof(uint8_t);
        registerType = Enums::RT_CONDITION;

        return BaseRegOperand::init_from_vect(opOffset, obj, at, maxRegIdx,
                                              'c');
    }

    void
    initWithStrOffset(unsigned strOffset, const BrigObject *obj)
    {
        regOperandSize = sizeof(uint8_t);
        registerType = Enums::RT_CONDITION;

        return BaseRegOperand::initWithStrOffset(strOffset, obj, maxRegIdx,
                                                 'c');
    }

    template<typename OperandType>
    OperandType
    get(Wavefront *w, int lane)
    {
        assert(regIdx < w->condRegState->numRegs());

        return w->condRegState->read<OperandType>((int)regIdx, lane);
    }

    template<typename OperandType>
    void
    set(Wavefront *w, int lane, OperandType &val)
    {
        DPRINTF(GPUReg, "CU%d, WF[%d][%d], lane %d: $c%d <- %d\n",
                w->computeUnit->cu_id, w->simdId, w->wfSlotId, lane, regIdx,
                val);

        assert(regIdx < w->condRegState->numRegs());
        w->condRegState->write<OperandType>(regIdx,lane,val);
    }

    std::string disassemble();
};

template<typename T>
class ImmOperand : public BaseOperand
{
  private:
    uint16_t kind;
  public:
    T bits;

    bool init(unsigned opOffset, const BrigObject *obj);
    bool init_from_vect(unsigned opOffset, const BrigObject *obj, int at);
    std::string disassemble();

    template<typename OperandType>
    OperandType
    get(Wavefront *w)
    {
        assert(sizeof(OperandType) <= sizeof(T));
        panic_if(w == nullptr, "WF pointer needs to be set");

        switch (kind) {
          // immediate operand is WF size
          case Brig::BRIG_KIND_OPERAND_WAVESIZE:
            return (OperandType)w->computeUnit->wfSize();
            break;

          default:
            return *(OperandType*)&bits;
            break;
        }
    }

    // This version of get() takes a WF* and a lane id for
    // compatibility with the register-based get() methods.
    template<typename OperandType>
    OperandType
    get(Wavefront *w, int lane)
    {
        return get<OperandType>(w);
    }
};

template<typename T>
bool
ImmOperand<T>::init(unsigned opOffset, const BrigObject *obj)
{
    const Brig::BrigOperand *brigOp = obj->getOperand(opOffset);

    switch (brigOp->kind) {
      // this is immediate operand
      case Brig::BRIG_KIND_OPERAND_CONSTANT_BYTES:
        {
            DPRINTF(GPUReg, "sizeof(T): %lu, byteCount: %d\n", sizeof(T),
                    brigOp->byteCount);

            auto cbptr = (Brig::BrigOperandConstantBytes*)brigOp;

            bits = *((T*)(obj->getData(cbptr->bytes + 4)));
            kind = brigOp->kind;
            return true;
        }
        break;

      case Brig::BRIG_KIND_OPERAND_WAVESIZE:
        kind = brigOp->kind;
        bits = std::numeric_limits<unsigned long long>::digits;
        return true;

      default:
        kind = Brig::BRIG_KIND_NONE;
        return false;
    }
}

template <typename T>
bool
ImmOperand<T>::init_from_vect(unsigned opOffset, const BrigObject *obj, int at)
{
    const Brig::BrigOperand *brigOp = obj->getOperand(opOffset);

    if (brigOp->kind != Brig::BRIG_KIND_OPERAND_OPERAND_LIST) {
        kind = Brig::BRIG_KIND_NONE;
        return false;
    }


    const Brig::BrigOperandOperandList *brigVecOp =
         (const Brig::BrigOperandOperandList *)brigOp;

    unsigned *data_offset =
        (unsigned *)obj->getData(brigVecOp->elements + 4 * (at + 1));

    const Brig::BrigOperand *p =
        (const Brig::BrigOperand *)obj->getOperand(*data_offset);

    if (p->kind != Brig::BRIG_KIND_OPERAND_CONSTANT_BYTES) {
        kind = Brig::BRIG_KIND_NONE;
        return false;
    }

    return init(*data_offset, obj);
}
template<typename T>
std::string
ImmOperand<T>::disassemble()
{
    return csprintf("0x%08x", bits);
}

template<typename RegOperand, typename T>
class RegOrImmOperand : public BaseOperand
{
  private:
    bool is_imm;

  public:
    void setImm(const bool value) { is_imm = value; }

    ImmOperand<T> imm_op;
    RegOperand reg_op;

    RegOrImmOperand() { is_imm = false; }
    void init(unsigned opOffset, const BrigObject *obj);
    void init_from_vect(unsigned opOffset, const BrigObject *obj, int at);
    std::string disassemble();

    template<typename OperandType>
    OperandType
    get(Wavefront *w, int lane)
    {
        return is_imm ?  imm_op.template get<OperandType>(w) :
                         reg_op.template get<OperandType>(w, lane);
    }

    uint32_t
    opSize()
    {
        if (!is_imm) {
            return reg_op.opSize();
        }

        return 0;
    }

    bool
    isVectorRegister()
    {
        if (!is_imm) {
            return reg_op.registerType == Enums::RT_VECTOR;
        }
        return false;
    }

    bool
    isCondRegister()
    {
        if (!is_imm) {
            return reg_op.registerType == Enums::RT_CONDITION;
        }

        return false;
    }

    bool
    isScalarRegister()
    {
        if (!is_imm) {
            return reg_op.registerType == Enums::RT_SCALAR;
        }

        return false;
    }

    unsigned int
    regIndex()
    {
        if (!is_imm) {
            return reg_op.regIndex();
        }
        return 0;
    }
};

template<typename RegOperand, typename T>
void
RegOrImmOperand<RegOperand, T>::init(unsigned opOffset, const BrigObject *obj)
{
    is_imm = false;

    if (reg_op.init(opOffset, obj)) {
        return;
    }

    if (imm_op.init(opOffset, obj)) {
        is_imm = true;
        return;
    }

    fatal("RegOrImmOperand::init(): bad operand kind %d\n",
          obj->getOperand(opOffset)->kind);
}

template<typename RegOperand, typename T>
void
RegOrImmOperand<RegOperand, T>::init_from_vect(unsigned opOffset,
                                               const BrigObject *obj, int at)
{
    if (reg_op.init_from_vect(opOffset, obj, at)) {
        is_imm = false;

        return;
    }

    if (imm_op.init_from_vect(opOffset, obj, at)) {
        is_imm = true;

        return;
    }

    fatal("RegOrImmOperand::init(): bad operand kind %d\n",
          obj->getOperand(opOffset)->kind);
}

template<typename RegOperand, typename T>
std::string
RegOrImmOperand<RegOperand, T>::disassemble()
{
    return is_imm ? imm_op.disassemble() : reg_op.disassemble();
}

typedef RegOrImmOperand<SRegOperand, uint32_t> SRegOrImmOperand;
typedef RegOrImmOperand<DRegOperand, uint64_t> DRegOrImmOperand;
typedef RegOrImmOperand<CRegOperand, bool> CRegOrImmOperand;

class AddrOperandBase : public BaseOperand
{
  protected:
    // helper function for init()
    void parseAddr(const Brig::BrigOperandAddress *op, const BrigObject *obj);

    // helper function for disassemble()
    std::string disassemble(std::string reg_disassembly);
    uint64_t calcUniformBase();

  public:
    virtual void calcVector(Wavefront *w, std::vector<Addr> &addrVec) = 0;
    virtual uint64_t calcLane(Wavefront *w, int lane=0) = 0;

    int64_t offset;
    const char *name = nullptr;
    StorageElement *storageElement;
};

template<typename RegOperandType>
class RegAddrOperand : public AddrOperandBase
{
  public:
    RegOperandType reg;
    void init(unsigned opOffset, const BrigObject *obj);
    uint64_t calcUniform();
    void calcVector(Wavefront *w, std::vector<Addr> &addrVec);
    uint64_t calcLane(Wavefront *w, int lane=0);
    uint32_t opSize() { return reg.opSize(); }
    bool isVectorRegister() { return reg.registerType == Enums::RT_VECTOR; }
    bool isCondRegister() { return reg.registerType == Enums::RT_CONDITION; }
    bool isScalarRegister() { return reg.registerType == Enums::RT_SCALAR; }
    unsigned int regIndex() { return reg.regIndex(); }
    std::string disassemble();
};

template<typename RegOperandType>
void
RegAddrOperand<RegOperandType>::init(unsigned opOffset, const BrigObject *obj)
{
    using namespace Brig;

    const BrigOperand *baseOp = obj->getOperand(opOffset);

    switch (baseOp->kind) {
      case BRIG_KIND_OPERAND_ADDRESS:
        {
            const BrigOperandAddress *op = (BrigOperandAddress*)baseOp;
            storageElement = nullptr;

            reg.init(op->reg, obj);

            if (reg.regFileChar == 's') {
                // if the address expression is 32b, then the hi
                // bits of the offset must be set to 0 in the BRIG
                assert(!op->offset.hi);
                /**
                 * the offset field of an HSAIL instruction may be negative
                 * so here we cast the raw bits we get from the BRIG file to
                 * a signed type to avoid address calculation errors
                 */
                offset = (int32_t)(op->offset.lo);
                reg.regOperandSize = sizeof(uint32_t);
                registerType = Enums::RT_VECTOR;
            }
            else if (reg.regFileChar == 'd') {
                offset = (int64_t)(((uint64_t)(op->offset.hi) << 32)
                    | (uint64_t)(op->offset.lo));
                reg.regOperandSize = sizeof(uint64_t);
                registerType = Enums::RT_VECTOR;
            }
        }
        break;

      default:
        fatal("RegAddrOperand: bad operand kind %d\n", baseOp->kind);
        break;
    }
}

template<typename RegOperandType>
uint64_t
RegAddrOperand<RegOperandType>::calcUniform()
{
    fatal("can't do calcUniform() on register-based address\n");

    return 0;
}

template<typename RegOperandType>
void
RegAddrOperand<RegOperandType>::calcVector(Wavefront *w,
                                           std::vector<Addr> &addrVec)
{
    Addr address = calcUniformBase();

    for (int lane = 0; lane < w->computeUnit->wfSize(); ++lane) {
        if (w->execMask(lane)) {
            if (reg.regFileChar == 's') {
                addrVec[lane] = address + reg.template get<uint32_t>(w, lane);
            } else {
                addrVec[lane] = address + reg.template get<Addr>(w, lane);
            }
        }
    }
}

template<typename RegOperandType>
uint64_t
RegAddrOperand<RegOperandType>::calcLane(Wavefront *w, int lane)
{
    Addr address = calcUniformBase();

    return address + reg.template get<Addr>(w, lane);
}

template<typename RegOperandType>
std::string
RegAddrOperand<RegOperandType>::disassemble()
{
    return AddrOperandBase::disassemble(reg.disassemble());
}

typedef RegAddrOperand<SRegOperand> SRegAddrOperand;
typedef RegAddrOperand<DRegOperand> DRegAddrOperand;

class NoRegAddrOperand : public AddrOperandBase
{
  public:
    void init(unsigned opOffset, const BrigObject *obj);
    uint64_t calcUniform();
    void calcVector(Wavefront *w, std::vector<Addr> &addrVec);
    uint64_t calcLane(Wavefront *w, int lane=0);
    std::string disassemble();
};

inline uint64_t
NoRegAddrOperand::calcUniform()
{
    return AddrOperandBase::calcUniformBase();
}

inline uint64_t
NoRegAddrOperand::calcLane(Wavefront *w, int lane)
{
    return calcUniform();
}

inline void
NoRegAddrOperand::calcVector(Wavefront *w, std::vector<Addr> &addrVec)
{
    uint64_t address = calcUniformBase();

    for (int lane = 0; lane < w->computeUnit->wfSize(); ++lane)
        addrVec[lane] = address;
}

class LabelOperand : public BaseOperand
{
  public:
    Label *label;

    void init(unsigned opOffset, const BrigObject *obj);
    std::string disassemble();

    // special get method for compatibility with SRegOperand
    uint32_t getTarget(Wavefront *w, int lane);

};

class ListOperand : public BaseOperand
{
  public:
    int elementCount;
    std::vector<StorageElement*> callArgs;

    int
    getSrcOperand(int idx)
    {
        DPRINTF(GPUReg, "getSrcOperand, idx: %d, sz_args: %d\n", idx,
                callArgs.size());

        return callArgs.at(idx)->offset;
    }

    void init(unsigned opOffset, const BrigObject *obj);

    std::string disassemble();

    template<typename OperandType>
    OperandType
    get(Wavefront *w, int lane, int arg_idx)
    {
        return w->readCallArgMem<OperandType>(lane, getSrcOperand(arg_idx));
    }

    template<typename OperandType>
    void
    set(Wavefront *w, int lane, OperandType val)
    {
        w->writeCallArgMem<OperandType>(lane, getSrcOperand(0), val);
        DPRINTF(GPUReg, "CU%d, WF[%d][%d], lane %d: arg[%d] <- %d\n",
                w->computeUnit->cu_id, w->simdId, w->wfSlotId, lane,
                getSrcOperand(0), val);
    }
};

class FunctionRefOperand : public BaseOperand
{
  public:
    const char *func_name;

    void init(unsigned opOffset, const BrigObject *obj);
    std::string disassemble();
};

#endif // __ARCH_HSAIL_OPERAND_HH__
