//
// Copyright (c) 2008 Advanced Micro Devices, Inc. All rights reserved.
//
#ifndef ELF_HPP_
#define ELF_HPP_

#include <map>

#include "top.hpp"
#include "elf_utils.hpp"
#if !defined(WITH_LIGHTNING_COMPILER)
#include "caltarget.h" // using CALtargetEnum
#endif // !defined(WITH_LIGHTNING_COMPILER)

#include "libelf.h"
#include "gelf.h"

// Not sure where to put these in the libelf
#define AMD_BIF2 2 // AMD BIF Version 2.0
#define AMD_BIF3 3 // AMD BIF Version 3.0

// These two definitions need to stay in sync with
// the definitions elfdefinitions.h until they get
// properly upstreamed to gcc/libelf.
#ifndef EM_HSAIL
#define EM_HSAIL 0xAF5A
#endif
#ifndef EM_HSAIL_64
#define EM_HSAIL_64 0xAF5B
#endif
#ifndef EM_AMDIL
#define EM_AMDIL 0x4154
#endif
#ifndef EM_ATI_CALIMAGE_BINARY
#define EM_ATI_CALIMAGE_BINARY 125
#endif
#ifndef EM_AMDGPU
#define EM_AMDGPU 224
#endif
#ifndef ELFOSABI_AMD_OPENCL
#define ELFOSABI_AMD_OPENCL 201
#endif
#ifndef ELFOSABI_HSAIL
#define ELFOSABI_HSAIL 202
#endif
#ifndef ELFOSABI_AMDIL
#define ELFOSABI_AMDIL 203
#endif
#ifndef ELFOSABI_CALIMAGE
#define ELFOSABI_CALIMAGE 100
#endif
namespace amd {

// Test: is it ELF file (with a given bitness) ?
bool isElfHeader(const char* p, signed char ec);
bool isElfMagic(const char* p);

// Test: is it ELF for CAL ?
bool isCALTarget(const char* p, signed char ec);

// Symbol handle
typedef struct symbol_handle *Sym_Handle;

class OclElf
{
public:
    enum {
           CAL_BASE = 1001,         // A number that is not dependent on libelf.h
           CPU_BASE = 2001,
           CPU_FEATURES_FIRST = 0,  // Never generated, but keep it for simplicity.
           CPU_FEATURES_LAST  = 0xF // This should be consistent with cpudevice.hpp
        } oclElfBase;

    typedef enum {
        // NOTE!!! Never remove an entry or change the order.

#if !defined(WITH_LIGHTNING_COMPILER)
        //  All CAL targets are within [CAL_FIRST, CAL_LAST].
        CAL_FIRST      = CAL_TARGET_600  + CAL_BASE,
        CAL_LAST       = CAL_TARGET_LAST + CAL_BASE,
#endif // !defined(WITH_LIGHTNING_COMPILER)

        // All CPU targets are within [CPU_FIRST, CPU_LAST]
        CPU_FIRST      = CPU_FEATURES_FIRST + CPU_BASE,
        CPU_LAST       = CPU_FEATURES_LAST  + CPU_BASE,

        OCL_TARGETS_LAST,
    } oclElfTargets;

    typedef enum {
        CAL_PLATFORM = 0,
        CPU_PLATFORM = 1,
        COMPLIB_PLATFORM = 2,
        LC_PLATFORM = 3,
        LAST_PLATFORM = 4
    } oclElfPlatform;

    typedef enum {
        LLVMIR = 0,
        SOURCE,
        ILTEXT,
        ASTEXT,
        CAL,
        DLL,
        STRTAB,
        SYMTAB,
        RODATA,
        SHSTRTAB,
        NOTES,
        COMMENT,
        ILDEBUG,
        DEBUG_INFO,
        DEBUG_ABBREV,
        DEBUG_LINE,
        DEBUG_PUBNAMES,
        DEBUG_PUBTYPES,
        DEBUG_LOC,
        DEBUG_ARANGES,
        DEBUG_RANGES,
        DEBUG_MACINFO,
        DEBUG_STR,
        DEBUG_FRAME,
        JITBINARY,
        CODEGEN,
        TEXT,
        INTERNAL,
        SPIR,
        SPIRV,
        RUNTIME_METADATA,
        OCL_ELF_SECTIONS_LAST
    } oclElfSections;

    typedef struct {
        char*     sec_name;    //!   section name
        char*     sec_addr;    //!   section address
        uint64_t  sec_size;    //!   section size
        char*     sym_name;    //!   symbol name
        char*     address;     //!   address of corresponding to symbol data
        uint64_t  size;        //!   size of data corresponding to symbol
    } SymbolInfo;

private:

    // file descriptor 
    int        _fd;

    // file name
    const char* _fname;

    // pointer to libelf structure
    ::Elf*     _e;

    // Error Object
    mutable OclElfErr  _err;

    // Bitness of the Elf object.
    unsigned char _eclass;

    // Raw ELF bytes in memory from which Elf object is initialized
    // The memory is owned by the client, not this OclElf object !
    const char* _rawElfBytes;
    uint64_t    _rawElfSize;

    // Read, write, or read and write for this Elf object
    const Elf_Cmd  _elfCmd;

    // Memory management
    typedef std::map<void*, size_t> EMemory;
    EMemory  _elfMemory;

    // Indexes of .shstrtab and .strtab (for convenience)
    Elf64_Word    _shstrtab_ndx;
    Elf64_Word    _strtab_ndx;

public: 

    /*
       OclElf object can be created for reading or writing (it could be created for
       both reading and writing, which is not supported yet at this time). Currently,
       it has two forms:

        1)  OclElf(eclass, rawElfBytes, rawElfSize, 0, ELF_C_READ)

            To load ELF from raw bytes in memory and generate OclElf object. And this
            object is for reading only.

        2)  OclElf(eclass,  NULL, 0, elfFileName|NULL, ELF_C_WRITE)

            To create an ELF for writing and save it into a file 'elfFileName' (if it
            is NULL, the OclElf will create a temporary file and set it to 'elfFileName'.

            Since we need to read the ELF into memory, this file 'elfFileName' is created
            with both read and write, so that the runtime can use dumpImage() to get ELF
            raw bytes by reading this file.

        'eclass' is ELF's bitness and it must be the same as the eclass of ELF to
        be loaded (for example, rawElfBytes).


        Return values of all public APIs with bool return type
           true  : on success;
           false : on error.
     */
    OclElf (
        unsigned char eclass,       // eclass for this ELF
        const char*   rawElfBytes,  // raw ELF bytes to be loaded
        uint64_t      rawElfSize,   // size of the ELF raw bytes
        const char*   elfFileName,  // File to save this ELF.
        Elf_Cmd       elfcmd        // ELF_C_READ/ELF_C_WRITE
        );

    ~OclElf ();

    /*
       dumpImage() will finalize the ELF and write it into the file. It then reads
       it into the memory; and returns it via <buff, len>.

       The memory pointed by buff is owned by OclElf object.
     */
    bool dumpImage(char** buff, size_t* len);

    /*
       addSection() is used to create a single ELF section with data <d_buf, d_size>. If
       do_copy is true, the OclElf object will make a copy of d_buf and uses that copy to
       create an ELF section.

       When setting do_copy = false,  the caller should make sure that <d_buf, d_size> will
       be unchanged and available during the lifetime of this OclElf object; ie before
       calling dumpImage().
     */
    bool addSection (
        oclElfSections id,
        const void*    d_buf,
        size_t         d_size,
        bool           do_copy = true
        );

    /*
       getSection() will return the whole section in <dst, sz>.

       The memory pointed by <dst, sz> is owned by the OclElf object.
     */
    bool getSection(oclElfSections id, char** dst, size_t* sz) const;


    /*
        addSymbol() adds a symbol with name 'symbolName' and data <buffer, size>
        into the ELF.  'id' indicates which section  <buffer, size> will go
        into.  The meaning of 'do_copy' is the same as addSection().
     */
    bool addSymbol(
        oclElfSections id,             // Section in which symbol is added
        const char*    symbolName,     // Name of symbol
        const void*    buffer,         // Symbol's data
        size_t         size,           // Symbol's size
        bool           do_copy = true  // If true, add a copy of buffer into the section
        );       

    /*
     * getSymbol() will return the data associated with
     * the symbol from the Elf.
     *
     * The memory pointed by <buffer, size> is owned by the OclElf object
     */
    bool getSymbol(
        oclElfSections id,        // Section in which symbol is in
        const char* symbolName,   // Name of the symbol to retrieve
        char** buffer,            // Symbol's data
        size_t* size              // Symbol's size
        ) const;

    /*
       nextSymbol() and getSymbolInfo() use the symbol handle to access symbols

       For example:
          for( Sym_Handle s = nextSymbol(NULL); s ; s = nextSymbol(s)) {
              SymbolInfo si;
              if (!getSymbolInfo(s, &si)) {
                  Error;
              }
              use si
          }

          where nextSymbol(NULL) will return the first symbol.
    
       Note that memory space pointed to by si is owned by OclElf.
     */
    bool getSymbolInfo(Sym_Handle sym, SymbolInfo* symInfo) const;
    Sym_Handle nextSymbol(Sym_Handle symhandle) const;

    /*
        Adds a note with name 'noteName' and description "noteDesc"
        into the .note section of ELF. Length of note name is 'nameSize'.
        Length of note description is "descSize'.
    */
    bool addNote(const char* noteName, const char* noteDesc,
                 size_t nameSize, size_t descSize);

    /*
        Returns the description of a note whose name is 'noteName'
        in 'noteDesc'.
        Returns the length of the description in 'descSize'.
    */
    bool getNote(const char* noteName, char** noteDesc, size_t *descSize);


    /*
       Get/set machine and platform (target) for which elf is built.
     */
    bool getTarget(uint16_t& machine, oclElfPlatform& platform);
    bool setTarget(uint16_t machine, oclElfPlatform platform);

    /*
       Get/set elf type field from header
     */
    bool getType(uint16_t &type);
    bool setType(uint16_t  type);

    /*
       Get/set elf flag field from header.
     */
    bool getFlags(uint32_t &flag);
    bool setFlags(uint32_t  flag);

    /*
       Clear() will return the status of OclElf to just after ctor() is invoked.
       However, it will not regenerate a temporary file name like ctor() does. 

       It is useful when the ELF content needs to be discarded for some reason.
     */
    bool Clear();

    bool              hasError() { return (_err.getOclElfError())[0] != 0; }
    const char*      getErrMsg() { return _err.getOclElfError(); }
    unsigned char  getELFClass() { return _eclass; }

private:

    /* Initialization */
    bool Init();

    /*
       Initialize ELF object by creating ELF header and key sections such as
      .shstrtab, .strtab, and .symtab.
     */
    bool InitElf ();

    // Wraper for creating a section header and Elf_Data
    bool createShdr (
        oclElfSections id,
        Elf_Scn*&      scn,
        Elf64_Word     shname,
        Elf64_Word     shlink = 0
        );

    Elf_Data* createElfData(
        Elf_Scn*&      scn,
        oclElfSections id,
        void*          d_buf,
        uint64_t       d_size,
        bool           do_copy
        );


    /*
       Create a new section (id) with data <d_buf, d_size>. If do_copy is true,
       make a copy of d_buf and create a new section with that copy.

       Return the valid Elf_Scn on success; return NULL on error.

       Note that newSection() uses Section Header's size, so make sure elf_update()
       is invoked properly before invoking newSection().
     */
    Elf_Scn* newSection (
        oclElfSections id,
        const void*    d_buf,
        size_t         d_size,
        bool           do_copy = true  // if true, add a copy of d_buf
        );

    /*
        Add a new data into a section by creating a new data descriptor.
        And the new data's offset is returned in 'outOffset'.
     */
    bool addSectionData(
        Elf64_Xword&   outOffset,
        oclElfSections id,
        const void*    buffer,
        size_t         size,
        bool           do_copy=true  // if true, add a copy of buffer
        );

    // Return Elf_Data for this section 'id'
    bool getSectionData(Elf_Data*& data, oclElfSections id) const;

    // Return Elf_Scn for this section 'id'
    bool getSectionDesc(Elf_Scn*& scn, oclElfSections id) const;

    // 
    bool getShstrtabNdx(Elf64_Word& outNdx, const char*);

    void* oclelf_allocAndCopy(void* p, size_t sz);
    void* oclelf_calloc(size_t sz);

    void elfMemoryRelease();
};

} // namespace amd

#endif
