/*========================== begin_copyright_notice ============================
Copyright (C) 2017-2022 Intel Corporation

SPDX-License-Identifier: MIT
============================= end_copyright_notice ===========================*/

/*!
 * @file Latency tool definitions
 */

#ifndef LATENCY_H_
#define LATENCY_H_

#include <list>
#include <map>
#include <set>
#include <string>

#include "gtpin_api.h"
#include "gtpin_tool_utils.h"

using namespace gtpin;

/*!
 * Identifier of the instrumentation site; depends on the latency measurment mode:
 *  - Basic block latency : ID of the first instruction of the basic block
 *  - Memory read latency : ID of the memory instruction
 */
using InstrumentSiteId = InsId;

/// Array of instrumentation site IDs, indexed by ordinal numbers of corresponding records in the profile buffer
using InstrumentSites = std::vector<InstrumentSiteId>;

/* ============================================================================================= */
// Struct LatencyRecord
/* ============================================================================================= */
/*!
 * Layout of records collected in profile buffer by the Latency tool
 */
struct alignas(uint64_t) LatencyRecord
{
    uint64_t cycles;    ///< Total number of cycles
    uint32_t freq;      ///< Total number of executions
};

/* ============================================================================================= */
// Class LatencyDispatchProfile
/* ============================================================================================= */
/*!
 * Profiling data collected during a single kernel dispatch per a single instrumentation site
 */
struct LatencyDispatchProfile
{
    explicit LatencyDispatchProfile(const IGtKernelDispatch& kernelDispatch, InstrumentSiteId siteId);
    void Accumulate(const LatencyRecord& record);

    GtKernelExecDesc  kernelExecDesc; ///< Kernel execution descriptor
    InstrumentSiteId  siteId;         ///< Identifier of the instrumentation site: BBL ID or instruction ID
    uint64_t          cycles;         ///< Total number of cycles
    uint64_t          freq;           ///< Total number of executions
};

/* ============================================================================================= */
// Class LatencyKernelProfile
/* ============================================================================================= */
/*!
 * Aggregated profile of all instrumented kernel dispatches
 */
class LatencyKernelProfile
{
public:
    LatencyKernelProfile(const IGtKernel& kernel, const IGtCfg& cfg, const GtProfileArray& profileArray,
                         InstrumentSites&& instrumentSites);

    /// Add the specified profile of a kernel dispatch
    LatencyDispatchProfile& AddDispatchProfile(const LatencyDispatchProfile& dispathProfile);

    std::string           ToString()        const;                          ///< @return Text representation of the profile data
    void                  DumpAsm()         const;                          ///< Dump kernel's assembly text to file
    std::string           GetName()         const { return _name; }         ///< @return  Kernel's name
    const GtProfileArray& GetProfileArray() const { return _profileArray; } ///< @return Profile buffer accessor

    /// Given a record number in the profile buffer, return corresponding ID of the instrumentation site   
    InstrumentSiteId GetSiteId(uint32_t recordNum)  const;

    /// @return Collection of profiles per kernel dispatch
    typedef std::list<LatencyDispatchProfile>  DispatchProfiles;
    const DispatchProfiles& GetDispatchProfiles() const { return _dispatchProfiles; }

private:
    std::string                         _name;              ///< Kernel's name
    std::string                         _uniqueName;        ///< Kernel's unique name (IGC style)
    GtKernelType                        _type;              ///< Kernel's type
    GtGpuPlatform                       _platform;          ///< Kernel's platform
    uint64_t                            _hashId;            ///< Kernel's hash identifier
    GtSimdWidth                         _simd;              ///< Kernel's SIMD width
    std::string                         _asmText;           ///< Kernel's assembly text
    GtProfileArray                      _profileArray;      ///< Profile buffer accessor
    InstrumentSites                     _instrumentSites;   ///< Array of instrumentation site IDs
    DispatchProfiles                    _dispatchProfiles;  ///< Profiles per kernel dispatch
    std::map<std::string, uint64_t>     _execTotalCycles;   ///< Total number of cycles per execution descriptor
};

/* ============================================================================================= */
// Class Latency
/* ============================================================================================= */
/*!
 * Implementation of the IGtTool interface for the Latency tool
 */
class Latency : public GtTool
{
public:
    /// Implementation of the IGtTool interface
    const char* Name() const { return "latency"; }

    void OnKernelBuild(IGtKernelInstrument& instrumentor);
    void OnKernelRun(IGtKernelDispatch& dispatcher);
    void OnKernelComplete(IGtKernelDispatch& dispatcher);

public:
    std::string ToString() const;                ///< @return Text representation of the profile data
    void        DumpAsm()  const;                ///< Dump assembly text of profiled kernels to files

    static Latency* Instance();                  ///< @return Single instance of this class
    static void OnFini() { Instance()->Fini(); } ///< Callback function registered with atexit()
protected:
    Latency() = default;
    Latency(const Latency&) = delete;
    Latency& operator = (const Latency&) = delete;
    ~Latency() = default;

    /// @return Collection of kernel profiles
    typedef std::map<GtKernelId, LatencyKernelProfile>  KernelProfiles;
    const KernelProfiles& GetKernelProfiles() const { return _kernels; }

    /// Post process and dump profiling data
    void Fini();
private:
    /// Generate latency instrumentation for basic blocks
    void InstrumentBasicBlocks(IGtKernelInstrument& instrumentor);

    /// Generate latency instrumentation for memory instructions
    void InstrumentMemoryInstructions(IGtKernelInstrument& instrumentor);

    /// Generate instrumentation to be inserted before the code fragment whose latency is measured
    void GeneratePreCode(GtGenProcedure& proc, const IGtGenCoder& coder);

    /*!
     * Generate instrumentation that computes latency and stores the result in the profile buffer.
     *  - GeneratePostCodeMemBound  - Generate memory-bound instrumentation
     *  - GeneratePostCodeRegBound  - Generate register-bound instrumentation. Return success/failure status
     * 
     * @param[in, out] postProc         Procedure to be inserted after the code fragment whose latency is measured
     * @param[in, out] finiProc         Procedure to be inserted before each EOT
     * @param[in]      coder            GEN code generator
     * @param[in]      profileArray     Array of 'LatencyRecord's in the profile buffer
     * @param[in]      recordNum        Index of the record in 'profileArray' associated with the instrumentation site
     */
    void GeneratePostCodeMemBound(GtGenProcedure& postProc, const IGtGenCoder& coder,
                                  const GtProfileArray& profileArray, uint32_t recordNum);
    bool GeneratePostCodeRegBound(GtGenProcedure& postProc, GtGenProcedure& finiProc, const IGtGenCoder& coder,
                                  const GtProfileArray& profileArray, uint32_t recordNum);

    /// @return true/false - use 64-bit/32-bit integer for the cycle counter
    static bool Use64BitCounters(const IGtGenCoder& coder);

private:
    KernelProfiles  _kernels;   ///< Collection of kernel profiles

    GtReg _addrReg;     ///< Virtual register that holds address within profile buffer
    GtReg _dataReg;     ///< Virtual register that holds data to be read from/written to profile buffer 
    GtReg _timeReg;     ///< Virtual timer register
};

#endif