/*
 * 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
 */

#include "arch/hsail/operand.hh"

using namespace Brig;

bool
BaseRegOperand::init(unsigned opOffset, const BrigObject *obj,
                     unsigned &maxRegIdx, char _regFileChar)
{
    regFileChar = _regFileChar;
    const BrigOperand *brigOp = obj->getOperand(opOffset);

    if (brigOp->kind != BRIG_KIND_OPERAND_REGISTER)
        return false;

    const BrigOperandRegister *brigRegOp = (const BrigOperandRegister*)brigOp;

    regIdx = brigRegOp->regNum;

    DPRINTF(GPUReg, "Operand: regNum: %d, kind: %d\n", regIdx,
            brigRegOp->regKind);

    maxRegIdx = std::max(maxRegIdx, regIdx);

    return true;
}

void
ListOperand::init(unsigned opOffset, const BrigObject *obj)
{
    const BrigOperand *brigOp = (const BrigOperand*)obj->getOperand(opOffset);

    switch (brigOp->kind) {
      case BRIG_KIND_OPERAND_CODE_LIST:
        {
            const BrigOperandCodeList *opList =
                (const BrigOperandCodeList*)brigOp;

            const Brig::BrigData *oprnd_data =
                obj->getBrigBaseData(opList->elements);

            // Note: for calls Dest list of operands could be size of 0.
            elementCount = oprnd_data->byteCount / 4;

            DPRINTF(GPUReg, "Operand Code List: # elements: %d\n",
                    elementCount);

            for (int i = 0; i < elementCount; ++i) {
                unsigned *data_offset =
                    (unsigned*)obj->getData(opList->elements + 4 * (i + 1));

                const BrigDirectiveVariable *p =
                    (const BrigDirectiveVariable*)obj->
                    getCodeSectionEntry(*data_offset);

                StorageElement *se = obj->currentCode->storageMap->
                    findSymbol(BRIG_SEGMENT_ARG, p);

                assert(se);
                callArgs.push_back(se);
            }
        }
        break;
      default:
        fatal("ListOperand: bad operand kind %d\n", brigOp->kind);
    }
}

std::string
ListOperand::disassemble()
{
    std::string res_str("");

    for (auto it : callArgs) {
        res_str += csprintf("%s ", it->name.c_str());
    }

    return res_str;
}

void
FunctionRefOperand::init(unsigned opOffset, const BrigObject *obj)
{
    const BrigOperand *baseOp = obj->getOperand(opOffset);

    if (baseOp->kind != BRIG_KIND_OPERAND_CODE_REF) {
        fatal("FunctionRefOperand: bad operand kind %d\n", baseOp->kind);
    }

    const BrigOperandCodeRef *brigOp = (const BrigOperandCodeRef*)baseOp;

    const BrigDirectiveExecutable *p =
        (const BrigDirectiveExecutable*)obj->getCodeSectionEntry(brigOp->ref);

    func_name = obj->getString(p->name);
}

std::string
FunctionRefOperand::disassemble()
{
    DPRINTF(GPUReg, "Operand Func-ref name: %s\n", func_name);

    return csprintf("%s", func_name);
}

bool
BaseRegOperand::init_from_vect(unsigned opOffset, const BrigObject *obj,
                               int at, unsigned &maxRegIdx, char _regFileChar)
{
    regFileChar = _regFileChar;
    const BrigOperand *brigOp = obj->getOperand(opOffset);

    if (brigOp->kind != BRIG_KIND_OPERAND_OPERAND_LIST)
        return false;


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

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

    const BrigOperand *p =
        (const BrigOperand*)obj->getOperand(*data_offset);
    if (p->kind != BRIG_KIND_OPERAND_REGISTER) {
        return false;
    }

    const BrigOperandRegister *brigRegOp =(const BrigOperandRegister*)p;

    regIdx = brigRegOp->regNum;

    DPRINTF(GPUReg, "Operand: regNum: %d, kind: %d \n", regIdx,
            brigRegOp->regKind);

    maxRegIdx = std::max(maxRegIdx, regIdx);

    return true;
}

void
BaseRegOperand::initWithStrOffset(unsigned strOffset, const BrigObject *obj,
                     unsigned &maxRegIdx, char _regFileChar)
{
    const char *name = obj->getString(strOffset);
    char *endptr;
    regIdx = strtoul(name + 2, &endptr, 10);

    if (name[0] != '$' || name[1] != _regFileChar) {
        fatal("register operand parse error on \"%s\"\n", name);
    }

    maxRegIdx = std::max(maxRegIdx, regIdx);
}

unsigned SRegOperand::maxRegIdx;
unsigned DRegOperand::maxRegIdx;
unsigned CRegOperand::maxRegIdx;

std::string
SRegOperand::disassemble()
{
    return csprintf("$s%d", regIdx);
}

std::string
DRegOperand::disassemble()
{
    return csprintf("$d%d", regIdx);
}

std::string
CRegOperand::disassemble()
{
    return csprintf("$c%d", regIdx);
}

BrigRegOperandInfo
findRegDataType(unsigned opOffset, const BrigObject *obj)
{
    const BrigOperand *baseOp = obj->getOperand(opOffset);

    switch (baseOp->kind) {
      case BRIG_KIND_OPERAND_REGISTER:
        {
            const BrigOperandRegister *op = (BrigOperandRegister*)baseOp;

            return BrigRegOperandInfo((BrigKind16_t)baseOp->kind,
                                      (BrigRegisterKind)op->regKind);
        }
        break;

      case BRIG_KIND_OPERAND_WAVESIZE:
        {
            BrigRegisterKind reg_kind = BRIG_REGISTER_KIND_DOUBLE;
            return BrigRegOperandInfo((BrigKind16_t)baseOp->kind, reg_kind);
        }

      case BRIG_KIND_OPERAND_OPERAND_LIST:
        {
            const BrigOperandOperandList *op =
               (BrigOperandOperandList*)baseOp;
            const BrigData *data_p = (BrigData*)obj->getData(op->elements);


            int num_operands = 0;
            BrigRegisterKind reg_kind = (BrigRegisterKind)0;
            for (int offset = 0; offset < data_p->byteCount; offset += 4) {
                const BrigOperand *op_p = (const BrigOperand *)
                   obj->getOperand(((int *)data_p->bytes)[offset/4]);

                if (op_p->kind == BRIG_KIND_OPERAND_REGISTER) {
                    const BrigOperandRegister *brigRegOp =
                       (const BrigOperandRegister*)op_p;
                    reg_kind = (BrigRegisterKind)brigRegOp->regKind;
                } else if (op_p->kind == BRIG_KIND_OPERAND_CONSTANT_BYTES) {
                    uint16_t num_bytes =
                       ((Brig::BrigOperandConstantBytes*)op_p)->base.byteCount
                           - sizeof(BrigBase);
                    if (num_bytes == sizeof(uint32_t)) {
                        reg_kind = BRIG_REGISTER_KIND_SINGLE;
                    } else if (num_bytes == sizeof(uint64_t)) {
                        reg_kind = BRIG_REGISTER_KIND_DOUBLE;
                    } else {
                        fatal("OperandList: bad operand size %d\n", num_bytes);
                    }
                } else if (op_p->kind == BRIG_KIND_OPERAND_WAVESIZE) {
                    reg_kind = BRIG_REGISTER_KIND_DOUBLE;
                } else {
                    fatal("OperandList: bad operand kind %d\n", op_p->kind);
                }

                num_operands++;
            }
            assert(baseOp->kind == BRIG_KIND_OPERAND_OPERAND_LIST);

            return BrigRegOperandInfo((BrigKind16_t)baseOp->kind, reg_kind);
        }
        break;

      case BRIG_KIND_OPERAND_ADDRESS:
        {
            const BrigOperandAddress *op = (BrigOperandAddress*)baseOp;

            if (!op->reg) {
                BrigType type = BRIG_TYPE_NONE;

                if (op->symbol) {
                    const BrigDirective *dir = (BrigDirective*)
                        obj->getCodeSectionEntry(op->symbol);

                    assert(dir->kind == BRIG_KIND_DIRECTIVE_VARIABLE);

                    const BrigDirectiveVariable *sym =
                       (const BrigDirectiveVariable*)dir;

                    type = (BrigType)sym->type;
                }
                return BrigRegOperandInfo(BRIG_KIND_OPERAND_ADDRESS,
                                          (BrigType)type);
            } else {
                const BrigOperandAddress *b = (const BrigOperandAddress*)baseOp;
                const BrigOperand *reg = obj->getOperand(b->reg);
                const BrigOperandRegister *rop = (BrigOperandRegister*)reg;

                return BrigRegOperandInfo(BRIG_KIND_OPERAND_REGISTER,
                                          (BrigRegisterKind)rop->regKind);
            }
        }
        break;

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

void
AddrOperandBase::parseAddr(const BrigOperandAddress *op, const BrigObject *obj)
{
    assert(op->base.kind == BRIG_KIND_OPERAND_ADDRESS);

    const BrigDirective *d =
        (BrigDirective*)obj->getCodeSectionEntry(op->symbol);

    /**
     * HSAIL does not properly handle immediate offsets for instruction types
     * that utilize them. It currently only supports instructions that use
     * variables instead. Again, these pop up in code that is never executed
     * (i.e. the HCC AMP codes) so we just hack it here to let us pass through
     * the HSAIL object initialization. If such code is ever called, we would
     * have to implement this properly.
     */
    if (d->kind != BRIG_KIND_DIRECTIVE_VARIABLE) {
        warn("HSAIL implementation does not support instructions with "
             "address calculations where the operand is not a variable\n");
    }

    const BrigDirectiveVariable *sym = (BrigDirectiveVariable*)d;
    name = obj->getString(sym->name);

    if (sym->segment != BRIG_SEGMENT_ARG) {
        storageElement =
            obj->currentCode->storageMap->findSymbol(sym->segment, name);
        offset = 0;
    } else {
        // sym->name does not work for BRIG_SEGMENT_ARG for the following case:
        //
        //     void foo(int a);
        //     void bar(double a);
        //
        //     foo(...) --> arg_u32 %param_p0;
        //                  st_arg_u32 $s0, [%param_p0];
        //                  call &foo (%param_p0);
        //     bar(...) --> arg_f64 %param_p0;
        //                  st_arg_u64 $d0, [%param_p0];
        //                  call &foo (%param_p0);
        //
        //  Both functions use the same variable name (param_p0)!!!
        //
        //  Maybe this is a bug in the compiler (I don't know).
        //
        // Solution:
        // Use directive pointer (BrigDirectiveVariable) to differentiate 2
        // versions of param_p0.
        //
        // Note this solution is kind of stupid, because we are pulling stuff
        // out of the brig binary via the directive pointer and putting it into
        // the symbol table, but now we are indexing the symbol table by the
        // brig directive pointer! It makes the symbol table sort of pointless.
        // But I don't want to mess with the rest of the infrastructure, so
        // let's go with this for now.
        //
        // When we update the compiler again, we should see if this problem goes
        // away. If so, we can fold some of this functionality into the code for
        // kernel arguments. If not, maybe we can index the symbol name on a
        // hash of the variable AND function name
        storageElement = obj->currentCode->
                 storageMap->findSymbol((Brig::BrigSegment)sym->segment, sym);

        assert(storageElement);
    }
}

uint64_t
AddrOperandBase::calcUniformBase()
{
    // start with offset, will be 0 if not specified
    uint64_t address = offset;

    // add in symbol value if specified
    if (storageElement) {
        address += storageElement->offset;
    }

    return address;
}

std::string
AddrOperandBase::disassemble(std::string reg_disassembly)
{
    std::string disasm;

    if (offset || reg_disassembly != "") {
        disasm += "[";

        if (reg_disassembly != "") {
            disasm += reg_disassembly;

            if (offset > 0) {
                disasm += "+";
            }
        }

        if (offset) {
            disasm += csprintf("%d", offset);
        }

        disasm += "]";
    } else if (name) {
        disasm += csprintf("[%s]", name);
    }

    return disasm;
}

void
NoRegAddrOperand::init(unsigned opOffset, const BrigObject *obj)
{
    const BrigOperand *baseOp = obj->getOperand(opOffset);

    if (baseOp->kind == BRIG_KIND_OPERAND_ADDRESS) {
        BrigOperandAddress *addrOp = (BrigOperandAddress*)baseOp;
        parseAddr(addrOp, obj);
        offset = (uint64_t(addrOp->offset.hi) << 32) |
                  uint64_t(addrOp->offset.lo);
    } else {
        fatal("NoRegAddrOperand: bad operand kind %d\n", baseOp->kind);
    }

}

std::string
NoRegAddrOperand::disassemble()
{
    return AddrOperandBase::disassemble(std::string(""));
}

void
LabelOperand::init(unsigned opOffset, const BrigObject *obj)
{
    const BrigOperandCodeRef *op =
        (const BrigOperandCodeRef*)obj->getOperand(opOffset);

    assert(op->base.kind == BRIG_KIND_OPERAND_CODE_REF);

    const BrigDirective *dir =
        (const BrigDirective*)obj->getCodeSectionEntry(op->ref);

    assert(dir->kind == BRIG_KIND_DIRECTIVE_LABEL);
    label = obj->currentCode->refLabel((BrigDirectiveLabel*)dir, obj);
}

uint32_t
LabelOperand::getTarget(Wavefront *w, int lane)
{
    return label->get();
}

std::string
LabelOperand::disassemble()
{
    return label->name;
}
