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

Copyright (C) 2020-2023 Intel Corporation

SPDX-License-Identifier: MIT

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

//===- ZEELFObjectBuilder.hpp -----------------------------------*- C++ -*-===//
// ZE Binary Utilities
//
// \file
// This file declares ZEELFObjectBuilder for building an ZE Binary object
//===----------------------------------------------------------------------===//

#ifndef ZE_ELF_OBJECT_BUILDER_HPP
#define ZE_ELF_OBJECT_BUILDER_HPP

#include <ZEELF.h>
#include <ZEInfo.hpp>
#include "inc/common/igfxfmid.h"

#ifndef ZEBinStandAloneBuild
#include "common/LLVMWarningsPush.hpp"
#endif

#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Support/raw_ostream.h"

#ifndef ZEBinStandAloneBuild
#include "common/LLVMWarningsPop.hpp"
#endif

#include <map>
#include <memory>
#include <string>
#include <vector>

namespace llvm {
    class raw_pwrite_stream;
}

namespace zebin {

/// ZEELFObjectBuilder - Build an ELF Object for ZE binary format
class ZEELFObjectBuilder {
    friend class ELFWriter;
public:
    // The valid SectionID must be 0 or positive value
    typedef int32_t SectionID;

public:
    ZEELFObjectBuilder(bool is64Bit) : m_is64Bit(is64Bit)
    {
        m_metadata.packed = 0;
    }

    ~ZEELFObjectBuilder() {}
    ZEELFObjectBuilder(const ZEELFObjectBuilder&) = delete;
    ZEELFObjectBuilder& operator=(const ZEELFObjectBuilder&) = delete;

    void setProductFamily(PRODUCT_FAMILY family) { m_productFamily = family; }
    PRODUCT_FAMILY getProductFamily() const { return m_productFamily; }

    void setGfxCoreFamily(GFXCORE_FAMILY family) { m_gfxCoreFamily = family; }
    GFXCORE_FAMILY getGfxCoreFamily() const { return m_gfxCoreFamily; }

    void setTargetMetadata(TargetMetadata metadata) { m_metadata = metadata; }
    TargetMetadata getTargetMetadata() const { return m_metadata; }

    void setVISAABIVersion(unsigned int version) { m_VISAABIVersion = version; }
    unsigned int getVISAABIVersion() const { return m_VISAABIVersion; }

    void setGmdID(GFX_GMD_ID gmdID) { m_gmdID = gmdID; }
    GFX_GMD_ID setGmdID() const { return m_gmdID; }

    // add a text section contains gen binary
    // - name: section name. This is usually the kernel or function name of
    //         this text section. Do not includes leading .text in given
    //         name. For example, giving "kernel", the section name will be
    //         .text.kernel
    // - data: memory buffer of the section contents. This buffer must be live
    //         through this ZEELFObjectBuilder
    // - size: input buffer size in byte
    // - padding: required padding at the end
    // - align: alignment requirement in byte
    // - return a unique id for referencing in addSymbol
    // Currently we assume there is only one text section that'll be added
    SectionID addSectionText(
        const std::string& name, const uint8_t* data, uint64_t size, uint32_t padding, uint32_t align);

    // add a data section contains raw data, such as constant or global buffer.
    // - name: section name. Do not includes leading .data in given
    //         name. For example, giving "const", the section name will be
    //         .data.const
    // - data: memory buffer of the section contents. This buffer must be live through
    //         this ZEELFObjectBuilder
    // - size: input buffer size in byte
    // - padding: required padding at the end
    // - align: alignment requirement in byte
    // - rodata: mark section as read-only
    // - alloc: mark section as allocatable
    // - return a unique id for referencing in addSymbol
    SectionID addSectionData(
        const std::string& name, const uint8_t* data, uint64_t size, uint32_t padding = 0, uint32_t align = 0, bool rodata = false, bool alloc = true);

    // add a bss section which occupies no space in the ELF, but with size and other section information
    // The bss sections could be used for zero-initialized variables.
    // - name: section name. Do not includes leading .bss in given name.
    //         For example, giving "const", the section name will be .bss.const
    // - size: input buffer size in byte
    // - padding: required padding at the end
    // - align: alignment requirement in byte
    // - return a unique id for referencing in addSymbol
    SectionID addSectionBss(
        const std::string& name, uint64_t size, uint32_t padding = 0, uint32_t align = 0);

    // add a spv section contains the spv source. This section will be set to SHT_ZEBIN_SPIRV type
    // - name: section name. The default name is .spv
    // - size in byte
    // - Note that the alignment requirement of the section should be satisfied
    //   by the given data and size
    // - Note that the given data buffer have to be alive through
    //   ZEELFObjectBuilder
    void addSectionSpirv(const std::string& name, const uint8_t* data, uint64_t size);

    // add .gtpin_info section
    // - name: section name. Do not includes leading .gtpin_info in the given
    //         name. For example, giving "func", the section name will be
    //         ".gtpin_info.func". The default name is .gtpin_fino. It'll be apply
    //         if the given name is empty.
    // - size in byte
    // - Note that the alignment requirement of the section should be satisfied
    //   by the given data and size
    // - Note that the given data buffer have to be alive through
    //   ZEELFObjectBuilder
    void addSectionGTPinInfo(const std::string& name, const uint8_t* data, uint64_t size);

    // add .visaasm section
    // - name: section name. Do not includes leading .visaasm in the given
    //         name. For example, giving "func", the section name will be
    //         ".visaasm.func". The default name is .visaasm. It'll be apply
    //         if the given name is empty.
    // - size in byte
    // - Note that the alignment requirement of the section should be satisfied
    //   by the given data and size
    // - Note that the given data buffer have to be alive through ZEELFObjectBuilder
    void addSectionVISAAsm(const std::string& name, const uint8_t* data, uint64_t size);

    // add .misc section
    // - name: section name. Do not includes leading .misc in the given
    //         name. For example, giving "func", the section name will be
    //         ".misc.func". The default name is .misc. It'll be apply
    //         if the given name is empty.
    // - size in byte
    // - Note that the alignment requirement of the section should be satisfied
    //   by the given data and size
    // - Note that the given data buffer have to be alive through ZEELFObjectBuilder
    void addSectionMisc(const std::string& name, const uint8_t* data, uint64_t size);

    // add .note.intelgt.metrics section
    // - name: .note.intelgt.metrics. Do not includes leading .note.intelgt.metrics
    //         in the given name. For example, giving "func", the section name will be
    //         ".note.intelgt.metrics.func". The default name is .note.intelgt.metrics.
    //         It'll be applied if the given name is empty.
    // - size in byte
    // - Note that the alignment requirement of the section should be satisfied
    //   by the given data and size
    // - Note that the given data buffer have to be alive through ZEELFObjectBuilder
    void addSectionMetrics(const std::string& name, const uint8_t* data, uint64_t size);

    // .debug_info section in DWARF format
    // - name: section name. The default name is .debug_info
    // - size in byte
    // - Note that the alignment requirement of the section should be satisfied
    //   by the given data and size
    // - Note that the given data buffer have to be alive through
    //   ZEELFObjectBuilder
    // - return a unique id for referencing in addSymbol
    SectionID addSectionDebug(const std::string& name, const uint8_t* data, uint64_t size);

    // add ze_info section
    void addSectionZEInfo(zeInfoContainer& zeInfo);

    // add a symbol
    // - name    : symbol's name
    // - addr    : symbol's address. The binary offset of where this symbol is
    //             defined in the section
    // - size    : symbol size
    // - binding : symbol binding. The value is defined in ELF standard ST_BIND
    // - type    : symbol type. The value is defined in ELF standard ST_TYPE
    // - sectionId : the section id of which this symbol is defined in. Giving
    //               -1 if this is an UNDEFINED symbol
    void addSymbol(const std::string& name, uint64_t addr, uint64_t size,
        uint8_t binding, uint8_t type, SectionID sectionId);

    // add a relocation with rel format
    // This function will create a corresponding .rel.{targetSectionName} section if
    // not exist
    // - offset    : the binary offset of the section where the relocation
    //               will apply to. The section is denoted by sectionId
    // - symName   : the target symbol's name
    // - type      : the relocation name
    // - sectionId : the section id where the relocation is apply to
    void addRelRelocation(
        uint64_t offset, const std::string& symName, R_TYPE_ZEBIN type, SectionID sectionId);

    // add a relocation with rela format
    // This function will create a corresponding .rela.{targetSectionName} section if
    // not exist
    // - offset    : the binary offset of the section where the relocation
    //               will apply to. The section is denoted by sectionId
    // - symName   : the target symbol's name
    // - type      : the relocation name
    // - addend    : the addend value
    // - sectionId : the section id where the relocation is apply to
    void addRelaRelocation(
        uint64_t offset, const std::string& symName, R_TYPE_ZEBIN type, uint64_t addend, SectionID sectionId);

    // finalize - Finalize the ELF Object, write ELF file into given os
    // return number of written bytes
    uint64_t finalize(llvm::raw_pwrite_stream& os);

    // get an ID of a section
    // - name  : section name
    SectionID getSectionIDBySectionName(const char* name);

private:
    class Section {
    public:
        enum Kind {STANDARD, RELOC, ZEINFO};
        virtual Kind getKind() const = 0;

        SectionID id() const { return m_id; }

    protected:
        Section(uint32_t id) : m_id(id) {}
        virtual ~Section() {}
        Section(const Section&) = default;
        Section& operator=(const Section&) = default;

    protected:
        SectionID m_id;
    };

    class StandardSection : public Section {
    public:
        StandardSection(const std::string& sectName, const uint8_t* data, uint64_t size,
            unsigned type, unsigned flags, uint32_t padding, uint32_t id)
            : Section(id), m_sectName(sectName), m_data(data), m_size(size), m_type(type),
              m_flags(flags), m_padding(padding)
        {}

        Kind getKind() const { return STANDARD; }

        // m_sectName - the final name presented in ELF section header
        // This field is required as the place holder for StringTable construction
        std::string m_sectName;
        const uint8_t* m_data;
        uint64_t m_size;
        // section type
        unsigned m_type;
        unsigned m_flags = 0;
        uint32_t m_padding;
    };

    class ZEInfoSection : public Section {
    public:
        ZEInfoSection(zeInfoContainer& zeinfo, uint32_t id)
            : Section(id), m_zeinfo(zeinfo)
        {}

        Kind getKind() const { return ZEINFO; }

        zeInfoContainer& getZeInfo()
        { return m_zeinfo; }

    private:
        zeInfoContainer& m_zeinfo;
    };

    class Symbol {
    public:
        Symbol(const std::string& name, uint64_t addr, uint64_t size, uint8_t binding,
            uint8_t type, SectionID sectionId)
            : m_name(name), m_addr(addr), m_size(size), m_binding(binding),
            m_type(type), m_sectionId(sectionId)
        {}

        std::string& name()            { return m_name;      }
        uint64_t     addr()      const { return m_addr;      }
        uint64_t     size()      const { return m_size;      }
        uint8_t      binding()   const { return m_binding;   }
        uint8_t      type()      const { return m_type;      }
        SectionID    sectionId() const { return m_sectionId; }

    private:
        std::string m_name;
        uint64_t m_addr;
        uint64_t m_size;
        uint8_t m_binding;
        uint8_t m_type;
        SectionID m_sectionId;
    };

    /// Relocation - present the relocation information of each entry.
    /// The relocation itself doesn't know if it's in rel or rela format.
    /// It's rel or rela depends on it's in RelocSection or RelaRelocSection
    class Relocation {
    public:
        Relocation(uint64_t offset, const std::string& symName, R_TYPE_ZEBIN type, uint64_t addend = 0)
            : m_offset(offset), m_symName(symName), m_type(type), m_addend(addend)
        {}

        uint64_t            offset()  const { return m_offset;  }
        const std::string&  symName() const { return m_symName; }
        R_TYPE_ZEBIN        type()    const { return m_type;    }
        uint64_t            addend()  const { return m_addend;  }

    private:
        uint64_t m_offset;
        std::string m_symName;
        R_TYPE_ZEBIN m_type;
        uint64_t m_addend;
    };

    typedef std::vector<StandardSection> StandardSectionListTy;
    typedef std::vector<Symbol> SymbolListTy;
    typedef std::vector<Relocation> RelocationListTy;

    class RelocSection : public Section {
    public:
        RelocSection(SectionID myID, SectionID targetID, std::string sectName, bool isRelFormat) :
            Section(myID), m_TargetID(targetID), m_sectName(sectName), m_isRelFormat (isRelFormat)
        {}

        Kind getKind() const { return RELOC; }
        bool isRelFormat() const { return m_isRelFormat; }

    public:
        // target section's id that this relocation section apply to
        SectionID m_TargetID;
        std::string m_sectName;
        RelocationListTy m_Relocations;

        // This is a rel or rela relocation format
        bool m_isRelFormat;
    };
    typedef std::vector<RelocSection> RelocSectionListTy;

private:
    Section& addStandardSection(
        const std::string& sectName, const uint8_t* data, uint64_t size, unsigned type,
        unsigned flags, uint32_t padding, uint32_t align, StandardSectionListTy& sections);

    // isRelFormat - rel or rela relocation format
    RelocSection& getOrCreateRelocSection(SectionID targetSectId, bool isRelFormat);

    std::string getSectionNameBySectionID(SectionID id);

private:
    // place holder for section default name
    const std::string m_TextName        = ".text";
    const std::string m_DataName        = ".data";
    const std::string m_BssName         = ".bss";
    const std::string m_SymTabName      = ".symtab";
    const std::string m_RelName         = ".rel";
    const std::string m_RelaName        = ".rela";
    const std::string m_SpvName         = ".spv";
    const std::string m_VISAAsmName     = ".visaasm";
    const std::string m_DebugName       = ".debug_info";
    const std::string m_ZEInfoName      = ".ze_info";
    const std::string m_GTPinInfoName   = ".gtpin_info";
    const std::string m_MiscName        = ".misc";
    const std::string m_CompatNoteName  = ".note.intelgt.compat";
    const std::string m_MetricsNoteName = ".note.intelgt.metrics";
    const std::string m_StrTabName      = ".strtab";

private:
    // 32 or 64 bit object
    bool m_is64Bit;

    // information used to generate .note.intelgt.compat
    PRODUCT_FAMILY m_productFamily = IGFX_UNKNOWN;
    GFXCORE_FAMILY m_gfxCoreFamily = IGFX_UNKNOWN_CORE;
    TargetMetadata m_metadata;
    unsigned int m_VISAABIVersion = 0;
    GFX_GMD_ID m_gmdID;

    StandardSectionListTy m_textSections;
    StandardSectionListTy m_dataAndbssSections; // data and bss sections
    StandardSectionListTy m_otherStdSections;
    RelocSectionListTy    m_relocSections; // rel and rela reloc sections

    // current section id
    SectionID m_sectionIdCount = 0;

    // every ze object contains at most one ze_info section
    std::unique_ptr<ZEInfoSection> m_zeInfoSection;
    SymbolListTy m_localSymbols;
    SymbolListTy m_globalSymbols;

};

/// ZEInfoBuilder - Build a zeInfoContainer for .ze_info section
class ZEInfoBuilder {
public:
    ZEInfoBuilder()
    {
        mContainer.version = PreDefinedAttrGetter::getVersionNumber();
    }

    zeInfoContainer& getZEInfoContainer()             { return mContainer; }
    const zeInfoContainer& getZEInfoContainer() const { return mContainer; }

    // empty - return true if there is no kernel/function info in it
    bool empty() const;

    /// --------- Helper functions for setup zeinfo contents -------------- ///
    // createKernel - create a zeInfoKernel and add it into zeInfoContainer
    zeInfoKernel& createKernel(const std::string& name);

    // createFunction - create a zeInfoFunction and add it into zeInfoContainer
    zeInfoFunction& createFunction(const std::string& name);

    // createKernelMiscInfo - create a zeKernelMiscInfo and add it into zeInfoContainer
    zeInfoKernelMiscInfo& createKernelMiscInfo(const std::string& name);

    // createKernelCostInfo - create a zeKernelCostInfo and add it into zeInfoContainer
    // zeKernelCostInfo: experimental kernel cost expressions.
    zeInfoKernelCostInfo& createKernelCostInfo(const std::string &name);

    // addGlobalHostAccessSymbol - create a zeInfo global_host_access_table section
    // which is used by Runtime to identify a global variable based on host name
    void addGlobalHostAccessSymbol(const std::string& device_name, const std::string& host_name);

    // addPayloadArgumentByPointer - add explicit kernel argument with pointer
    // type into given arg_list
    static zeInfoPayloadArgument& addPayloadArgumentByPointer(
        PayloadArgumentsTy& arg_list,
        int32_t offset,
        int32_t size,
        int32_t arg_index,
        PreDefinedAttrGetter::ArgAddrMode addrmode,
        PreDefinedAttrGetter::ArgAddrSpace addrspace,
        PreDefinedAttrGetter::ArgAccessType access_type,
        int32_t alignment = 0);

    // addPayloadArgumentByValue - add explicit kernel argument with pass by
    // value type into given arg_list
    static zeInfoPayloadArgument& addPayloadArgumentByValue(
        PayloadArgumentsTy& arg_list,
        int32_t offset,
        int32_t size,
        int32_t arg_index,
        int32_t source_offset,
        bool is_ptr);

    // addPayloadArgumentImage - add explicit kernel argument for image
    // into given arg_list
    // The argument type will be set to by_pointer, and addr_space will be set to image
    static zeInfoPayloadArgument& addPayloadArgumentImage(
        PayloadArgumentsTy& arg_list,
        int32_t offset,
        int32_t size,
        int32_t arg_index,
        PreDefinedAttrGetter::ArgAddrMode addrmode,
        PreDefinedAttrGetter::ArgAccessType access_type,
        PreDefinedAttrGetter::ArgImageType image_type);

    // addPayloadArgumentSampler - add explicit kernel argument for sampler
    // into given arg_list
    // The argument type will be set to by_pointer, and addr_space will be set to sampler
    static zeInfoPayloadArgument& addPayloadArgumentSampler(
        PayloadArgumentsTy& arg_list,
        int32_t offset,
        int32_t size,
        int32_t arg_index,
        int32_t sampler_index,
        PreDefinedAttrGetter::ArgAddrMode addrmode,
        PreDefinedAttrGetter::ArgAccessType access_type,
        PreDefinedAttrGetter::ArgSamplerType sampler_type);

    // addInlineSampler - add inline sampler into given inline_sampler_list.
    static zeInfoInlineSampler& addInlineSampler(
        InlineSamplersTy& inline_sampler_list,
        int32_t sampler_index,
        PreDefinedAttrGetter::ArgSamplerAddrMode addr_mode,
        PreDefinedAttrGetter::ArgSamplerFilterMode filter_mode,
        bool normalized);

    // addPayloadArgumentImplicit - add non-user argument (implicit argument)
    // into given arg_list. The type must be local_size, group_size,
    // global_id_offset or private_base_stateless
    static zeInfoPayloadArgument& addPayloadArgumentImplicit(
        PayloadArgumentsTy& arg_list,
        PreDefinedAttrGetter::ArgType type,
        int32_t offset,
        int32_t size,
        bool has_ptr = false);

    // addPayloadArgumentImplicit - add non-user argument (implicit argument)
    // into given arg_list. The type must be local_size, group_size,
    // global_id_offset or private_base_stateless
    static zeInfoPayloadArgument& addPayloadArgumentImplicitInlineSampler(
        PayloadArgumentsTy& arg_list,
        PreDefinedAttrGetter::ArgType type,
        int32_t offset,
        int32_t size,
        int32_t sampler_index);

    // addPerThreadPayloadArgument - add a per-thread payload argument into
    // arg_list. Currently we only support local id as per-thread argument.
    // The given type must be packed_local_ids or local_id
    static zeInfoPerThreadPayloadArgument& addPerThreadPayloadArgument(
        PerThreadPayloadArgumentsTy& arg_list,
        PreDefinedAttrGetter::ArgType type,
        int32_t offset,
        int32_t size);

    // addBindingTableIndex - add a binding table index into given bti_list, with
    // corresponding kernel argument index
    static zeInfoBindingTableIndex& addBindingTableIndex(
        BindingTableIndicesTy& bti_list,
        int32_t bti_value,
        int32_t arg_index);

    // addPerThreadMemoryBuffer - add a memory buffer info with global or slm type
    // If adding buffer with "global" type, this API assume it is allocated per-hardware-thread
    // Use below addPerSIMTThreadGlobalMemoryBuffer API if attempting to add per-simt-thread global buffer
    static zeInfoPerThreadMemoryBuffer& addPerThreadMemoryBuffer(
        PerThreadMemoryBuffersTy& mem_buff_list,
        PreDefinedAttrGetter::MemBufferType type,
        PreDefinedAttrGetter::MemBufferUsage usage,
        int32_t size);

    // addScratchPerThreadMemoryBuffer - add a memory buffer info for scratch buffer
    // per_thread_memory_buffers::type set to scratch
    static zeInfoPerThreadMemoryBuffer& addScratchPerThreadMemoryBuffer(
        PerThreadMemoryBuffersTy& mem_buff_list,
        PreDefinedAttrGetter::MemBufferUsage usage,
        int32_t slot_id,
        int32_t size);

    // addPerSIMTThreadGlobalMemoryBuffer - add a memory buffer info
    // for global memory buffer with
    // per_thread_memory_buffers::type set to global
    // per_thread_memory_buffers::is_simt_thread set to true
    // Use addPerThreadMemoryBuffer if adding per-hardware-thread global memory buffer
    static zeInfoPerThreadMemoryBuffer& addPerSIMTThreadGlobalMemoryBuffer(
        PerThreadMemoryBuffersTy& mem_buff_list,
        PreDefinedAttrGetter::MemBufferUsage usage,
        int32_t size);

    // addExpPropertiesHasNonKernelArgLdSt - add experimental_properties for has-non-kernel-arg-load-store analysis
    // add experimental_properties::has_non_kernel_arg_load, experimental_properties::has_non_kernel_arg_store
    // and experimental_properties::has_non_kernel_arg_atomic
    // Note that zeInfoExperimentalProperties is made as a vector under kernel in the spec, because we want it only
    // present when needed. If it's not a vector, the attribute name will always present in final output even if
    // all of its sub-attributes are default and are not shown.
    static void addExpPropertiesHasNonKernelArgLdSt(zeInfoKernel& zekernel,
        bool hasNonKernelArgLoad, bool hasNonKernelArgStore, bool hasNonKernelArgAtomic);

    // dump mContainer in yaml format into given raw_ostream
    void printZEInfoInYaml(llvm::raw_ostream &OS);

private:
    zeInfoContainer mContainer;
};

} // end namespace zebin

#endif // ZE_ELF_OBJECT_BUILDER_HPP
