/*
 * 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 __HSAIL_CODE_HH__
#define __HSAIL_CODE_HH__

#include <cassert>
#include <list>
#include <map>
#include <string>
#include <vector>

#include "arch/gpu_decoder.hh"
#include "arch/hsail/Brig.h"
#include "base/addr_range_map.hh"
#include "base/intmath.hh"
#include "config/the_gpu_isa.hh"
#include "gpu-compute/hsa_code.hh"
#include "gpu-compute/hsa_kernel_info.hh"
#include "gpu-compute/misc.hh"

class BrigObject;
class GPUStaticInst;

inline int
popcount(uint64_t src, int sz)
{
    int cnt = 0;

    for (int i = 0; i < sz; ++i) {
        if (src & 1)
            ++cnt;
        src >>= 1;
    }

    return cnt;
}

inline int
firstbit(uint64_t src, int sz)
{
    int i;

    for (i = 0; i < sz; ++i) {
        if (src & 1)
            break;
        src >>= 1;
    }

    return i;
}

inline int
lastbit(uint64_t src, int sz)
{
    int i0 = -1;

    for (int i = 0; i < sz; ++i) {
        if (src & 1)
            i0 = i;
        src >>= 1;
    }

    return i0;
}

inline int
signbit(uint64_t src, int sz)
{
    int i0 = -1;

    if (src & (1 << (sz - 1))) {
        for (int i = 0; i < sz - 1; ++i) {
            if (!(src & 1))
                i0 = i;
            src >>= 1;
        }
    } else {
        for (int i = 0; i < sz - 1; ++i) {
            if (src & 1)
                i0 = i;
            src >>= 1;
        }
    }

    return i0;
}

inline uint64_t
bitrev(uint64_t src, int sz)
{
    uint64_t r = 0;

    for (int i = 0; i < sz; ++i) {
        r <<= 1;
        if (src & 1)
            r |= 1;
        src >>= 1;
    }

    return r;
}

inline uint64_t
mul_hi(uint32_t a, uint32_t b)
{
    return ((uint64_t)a * (uint64_t)b) >> 32;
}

inline uint64_t
mul_hi(int32_t a, int32_t b)
{
    return ((int64_t)a * (int64_t)b) >> 32;
}

inline uint64_t
mul_hi(uint64_t a, uint64_t b)
{
    return ((uint64_t)a * (uint64_t)b) >> 32;
}

inline uint64_t
mul_hi(int64_t a, int64_t b)
{
    return ((int64_t)a * (int64_t)b) >> 32;
}

inline uint64_t
mul_hi(double a, double b)
{
    return 0;
}

class Label
{
  public:
    std::string name;
    int value;

    Label() : value(-1)
    {
    }

    bool defined() { return value != -1; }

    void
    checkName(std::string &_name)
    {
        if (name.empty()) {
            name = _name;
        } else {
            assert(name == _name);
        }
    }

    void
    define(std::string &_name, int _value)
    {
        assert(!defined());
        assert(_value != -1);
        value = _value;
        checkName(_name);
    }

    int
    get()
    {
        assert(defined());
        return value;
    }
};

class LabelMap
{
    std::map<std::string, Label> map;

  public:
    LabelMap() { }

    void addLabel(const Brig::BrigDirectiveLabel *lbl, int inst_index,
                  const BrigObject *obj);

    Label *refLabel(const Brig::BrigDirectiveLabel *lbl,
                    const BrigObject *obj);
};

const int NumSegments = Brig::BRIG_SEGMENT_AMD_GCN;

extern const char *segmentNames[];

class StorageElement
{
  public:
    std::string name;
    uint64_t offset;

    uint64_t size;
    const Brig::BrigDirectiveVariable *brigSymbol;
    StorageElement(const char *_name, uint64_t _offset, int _size,
                   const Brig::BrigDirectiveVariable *sym)
        : name(_name), offset(_offset), size(_size), brigSymbol(sym)
    {
    }
};

class StorageSpace
{
    typedef std::map<const Brig::BrigDirectiveVariable*, StorageElement*>
            DirVarToSE_map;

    std::list<StorageElement*> elements;
    AddrRangeMap<StorageElement*> elements_by_addr;
    DirVarToSE_map elements_by_brigptr;

    uint64_t nextOffset;
    Brig::BrigSegment segment;

  public:
    StorageSpace(Brig::BrigSegment _class)
        : nextOffset(0), segment(_class)
    {
    }

    StorageElement *addSymbol(const Brig::BrigDirectiveVariable *sym,
                              const BrigObject *obj);

    StorageElement* findSymbol(std::string name);
    StorageElement* findSymbol(uint64_t addr);
    StorageElement* findSymbol(const Brig::BrigDirectiveVariable *brigptr);

    int getSize() { return nextOffset; }
    void resetOffset() { nextOffset = 0; }
};

class StorageMap
{
    StorageMap *outerScopeMap;
    StorageSpace *space[NumSegments];

  public:
    StorageMap(StorageMap *outerScope = nullptr);

    StorageElement *addSymbol(const Brig::BrigDirectiveVariable *sym,
                              const BrigObject *obj);

    StorageElement* findSymbol(Brig::BrigSegment segment, std::string name);
    StorageElement* findSymbol(Brig::BrigSegment segment, uint64_t addr);

    StorageElement* findSymbol(Brig::BrigSegment segment,
                               const Brig::BrigDirectiveVariable *brigptr);

    // overloaded version to avoid casting
    StorageElement*
    findSymbol(Brig::BrigSegment8_t segment, std::string name)
    {
        return findSymbol((Brig::BrigSegment)segment, name);
    }

    int getSize(Brig::BrigSegment segment);
    void resetOffset(Brig::BrigSegment segment);
};

typedef enum
{
    BT_DEFAULT,
    BT_B8,
    BT_U8,
    BT_U16,
    BT_U32,
    BT_U64,
    BT_S8,
    BT_S16,
    BT_S32,
    BT_S64,
    BT_F16,
    BT_F32,
    BT_F64,
    BT_NULL
} base_type_e;

/* @class HsailCode
 * the HsailCode class is used to store information
 * about HSA kernels stored in the BRIG format. it holds
 * all information about a kernel, function, or variable
 * symbol and provides methods for accessing that
 * information.
 */

class HsailCode final : public HsaCode
{
  public:
    TheGpuISA::Decoder decoder;

    StorageMap *storageMap;
    LabelMap labelMap;
    uint32_t kernarg_start;
    uint32_t kernarg_end;
    int32_t private_size;

    int32_t readonly_size;

    // We track the maximum register index used for each register
    // class when we load the code so we can size the register files
    // appropriately (i.e., one more than the max index).
    uint32_t max_creg;    // maximum c-register index
    uint32_t max_sreg;    // maximum s-register index
    uint32_t max_dreg;    // maximum d-register index

    HsailCode(const std::string &name_str,
              const Brig::BrigDirectiveExecutable *code_dir,
              const BrigObject *obj,
              StorageMap *objStorageMap);

    // this version is used to create a placeholder when
    // we encounter a kernel-related directive before the
    // kernel itself
    HsailCode(const std::string &name_str);

    void init(const Brig::BrigDirectiveExecutable *code_dir,
              const BrigObject *obj, StorageMap *objStorageMap);

    void
    generateHsaKernelInfo(HsaKernelInfo *hsaKernelInfo) const
    {
        hsaKernelInfo->sRegCount = max_sreg + 1;
        hsaKernelInfo->dRegCount = max_dreg + 1;
        hsaKernelInfo->cRegCount = max_creg + 1;

        hsaKernelInfo->static_lds_size = getSize(Brig::BRIG_SEGMENT_GROUP);

        hsaKernelInfo->private_mem_size =
            roundUp(getSize(Brig::BRIG_SEGMENT_PRIVATE), 8);

        hsaKernelInfo->spill_mem_size =
            roundUp(getSize(Brig::BRIG_SEGMENT_SPILL), 8);
    }

    int
    getSize(MemorySegment segment) const
    {
        Brig::BrigSegment brigSeg;

        switch (segment) {
          case MemorySegment::NONE:
            brigSeg = Brig::BRIG_SEGMENT_NONE;
            break;
          case MemorySegment::FLAT:
            brigSeg = Brig::BRIG_SEGMENT_FLAT;
            break;
          case MemorySegment::GLOBAL:
            brigSeg = Brig::BRIG_SEGMENT_GLOBAL;
            break;
          case MemorySegment::READONLY:
            brigSeg = Brig::BRIG_SEGMENT_READONLY;
            break;
          case MemorySegment::KERNARG:
            brigSeg = Brig::BRIG_SEGMENT_KERNARG;
            break;
          case MemorySegment::GROUP:
            brigSeg = Brig::BRIG_SEGMENT_GROUP;
            break;
          case MemorySegment::PRIVATE:
            brigSeg = Brig::BRIG_SEGMENT_PRIVATE;
            break;
          case MemorySegment::SPILL:
            brigSeg = Brig::BRIG_SEGMENT_SPILL;
            break;
          case MemorySegment::ARG:
            brigSeg = Brig::BRIG_SEGMENT_ARG;
            break;
          case MemorySegment::EXTSPACE0:
            brigSeg = Brig::BRIG_SEGMENT_AMD_GCN;
            break;
          default:
            fatal("Unknown BrigSegment type.\n");
        }

        return getSize(brigSeg);
    }

  private:
    int
    getSize(Brig::BrigSegment segment) const
    {
        if (segment == Brig::BRIG_SEGMENT_PRIVATE) {
            // with the code generated by new HSA compiler the assertion
            // does not hold anymore..
            //assert(private_size != -1);
            return private_size;
        } else {
            return storageMap->getSize(segment);
        }
    }

  public:
    StorageElement*
    findSymbol(Brig::BrigSegment segment, uint64_t addr)
    {
        return storageMap->findSymbol(segment, addr);
    }

    void
    setPrivateSize(int32_t _private_size)
    {
        private_size = _private_size;
    }

    Label*
    refLabel(const Brig::BrigDirectiveLabel *lbl, const BrigObject *obj)
    {
        return labelMap.refLabel(lbl, obj);
    }
};

#endif // __HSAIL_CODE_HH__