/*========================== begin_copyright_notice ============================
Copyright (C) 2023-2024 Intel Corporation

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

/*!
 * @file Interface for generating High-Level Instrumentation (HLI) function calls
 */

#ifndef GT_HLI_FUNCTION_H_
#define GT_HLI_FUNCTION_H_

#include "gt_basic_defs.h"
#include "igt_kernel.h"
#include "igt_gen_coder.h"
#include "igt_iarg.h"
#include "igt_iarg_factory.h"
#include "gt_iarg_defs.h"
#include "gt_gpu_defs.h"
#include "gt_gen_procedure.h"

namespace gtpin
{
#pragma pack(push, 8)

/*! @addtogroup GTPIN_HLI_FUNCTION
@{
*/

/* ===================================================================================================== */
// Class GtIargList
/* ===================================================================================================== */
/*! @internal
 * @brief A list of pointers to IGtIarg objects that represent arguments of an HLI function.
 * 
 * This class provides methods that construct a list of IGtIarg objects from the variadic list of Iarg*
 * values as defined in the @ref GTPIN_HLI_ARGUMENT section.
 * 
 * IGtIarg objects in the list follow the order of the formal paramters in the function definition.
 * 
 * @param PRMS  List of valid HLI parameter types
 * 
 * @verbatim
 * ========================================================================================================*
 *                                          Examples                                                       *
 * ========================================================================================================*
    IGtIargFactory& f;

    GtIargList<uint32_t*> args { f, &myVar };
            // args[0]: Host pointer converted into device pointer
    
    GtIargList<float> args { f, 1.2 };
            // args[0]: 32-bit floating point constant 1.2
    
    GtIargList<uint32_t, uint8_t*> args { f, IargFfTid(), IargDevicePtr(vreg) };
            // args[0]: Fixed Function thread ID
            // args[1]: Device pointer passed in a virtual register
    
    GtIargList<uint64_t, uint32_t, MyType*>  args { f, vreg, IargTid(), &myVar };
            // args[0]: 64-bit integer, passed in a virtual register
            // args[1]: Current Thread ID
            // args[2]: Host pointer converted to device pointer
 
     GtIargList<uint64_t, uint32_t, MyType*>  args(f);
     args.Assign(vreg, IargTid(), &myVar);               // same as above

     GtIargList<uint64_t, uint32_t, MyType*>  args(f);
     args.Set<0>(vreg).Set<1>(IargTid()).Set<2>(&myVar); // same as above

 * ========================================================================================================*
 * @endverbatim
 */
template <typename... PRMS> class GtIargList
{
public:
    /// Constructor
    GtIargList(IGtIargFactory& factory) : _factory(&factory)
    {
        _args.fill(nullptr);
    }

    /*!
     * @brief Construct a list of pointers to IGtIarg objects
     * 
     * @param[in] argValues     List of argument values
     * @pre sizeof... (ARGS) == sizeof... (PRMS)
     * @pre ARGS types are compatible with the corresponding PRMS types. See @ref GtIargType::IsCompatible() 
     */
    template <typename... ARGS> GtIargList(IGtIargFactory& factory, const ARGS&... argValues) :
        _factory(&factory), _args({ CreateIarg<PRMS>(factory, argValues)... }) {}

    /// Return Number of function arguments
    constexpr uint32_t Size() const  { return sizeof... (PRMS); }

    /*!
     * @return Pointer to the IGtIarg object specified by the ordinal number of the argument
     * @param[in]   idx    Ordinal number of the argument, starting from zero
     * @pre idx < sizeof... (PRMS)
     */
    const           IargCptr& At(uint32_t idx)            const { GTPIN_ASSERT(idx < Size()); return _args[idx]; }
    constexpr const IargCptr& operator [] (uint32_t idx)  const { return _args[idx]; }
    IargCptr&                 At(uint32_t idx)                  { GTPIN_ASSERT(idx < Size()); return _args[idx]; }
    IargCptr&                 operator [] (uint32_t idx)        { return _args[idx]; }

    /// Get span of IGtIarg objects in the list
    constexpr GtIargSpan GetSpan() const { return GtIargSpan(_args); }

    /*!
     * @brief Assign specified values to function arguments
     * 
     * @param[in] argValues     List of argument values
     * @pre sizeof... (ARGS) == sizeof... (PRMS)
     * @pre ARGS types are compatible with the corresponding PRMS types. See @ref GtIargType::IsCompatible()
     */
    template <typename... ARGS> GtIargList& Assign(const ARGS&... argValues)
    {
        _args = { CreateIarg<PRMS>(*_factory, argValues)... };
        return *this;
    }

    /*!
     * @brief Set the specified value to the specified function argument
     * @param[in]   argVal  The value to be set
     * @param       IDX     Ordinal number of the argument, starting from zero
     * @pre IDX < sizeof... (PRMS)
     * @pre ARGT type is compatible with the corresponding type in the PRMS list. See See @ref GtIargType::IsCompatible()
     */
    template <uint32_t IDX, typename ARGT> GtIargList& Set(ARGT argVal)
    {
        GTPIN_STATIC_ASSERT(IDX < (sizeof... (PRMS)));
        _args[IDX] = CreateIarg<typename VariadicElement<IDX, PRMS...>::type>(*_factory, argVal);
        return *this;
    }

private:
    IGtIargFactory*                   _factory;
    Array<IargCptr, sizeof... (PRMS)> _args;
};

/* ===================================================================================================== */
// Class GtHliFunction
/* ===================================================================================================== */
/*!
 * @brief Class that represents an HLI function and provides convenient interface for generating HLI
 * function calls
 * 
 * The template arguments of this class resemble signature of the function, as declared in the source
 * code of the corresponding function, e.g. in the OpenCL C function declaration.
 * 
 * @note To overcome ABI limitations, a tool can define HLI function so that it receives all argument
 * values on stack, rather than in registers. For this purpose, the tool should\n
 *  a) Pass the [hasComboParam = true] value to the GtHliFunction constructor\n
 *  b) Define the HLI function with the single parameter - pointer to the array of argument values passed
 *     on the stack. The order of arguments values follows the order of template parameters in the
 *     GtHliFunction definition. Each argument value is aligned to 8-byte boundary, at minimum.
 *     See Example 3 below.
 * 
 * @param RET   Type of the return value of the function, either valid HLI parameter type or void
 * @param PRMS  List of valid HLI parameter types that correspond to the types of the function's parameters
 *
@verbatim
 * ========================================================================================================*
 *                                          Examples                                                       *
 * ========================================================================================================*

      // ------------------ Example 1 -----------------------
     bool MyFunc1(uint64_t, uint32_t, MyType*);

     #if defined(__cplusplus)
     GtHliFunction<bool, uint64_t, uint32_t, MyType*> myFunc1 { "MyFunc1" };

     myFunc1.InsertCallAtInstruction(instrumentor, ins, GT_IPOINT_BEFORE,
                                     NullReg(), // [bool]     retVal: unused return value
                                     vreg,      // [uint64_t] arg0:   64-bit integer, passed in a virtual register
                                     IargTid(), // [uint32_t] arg1:   Current Thread ID
                                     &myVar     // [MyType*]  arg2:   Host pointer converted to device pointer
                                     );
     #endif

      // ------------------ Example 2 -----------------------
      uint64_t MyFunc2(uint32_t, uint8_t*);

      #if defined(__cplusplus)
      GtHliFunction<uint64_t, uint32_t, uint8_t*> myFunc2 { "MyFunc2" };

      myFunc2.InsertCallAtBbl(instrumentor, bbl, GT_IPOINT_BEFORE,
                              myRetReg,             // [uint64_t] retVal: Return value passed in myRetReg
                              IargFfTid(),          // [uint32_t] arg0:   Fixed Function thread ID
                              IargDevicePtr(vreg),  // [uint32_t] arg1:   Device pointer passed in a virtual register
                              );
      #endif

      // ------------------ Example 3 -----------------------
      // Enforce all function arguments to be passed on stack

      #if defined(__cplusplus)
      #define HLIF_ALIGNED alignas(8)
      #else
      #define HLIF_ALIGNED __attribute__ ((aligned (8)))
      #endif

      typedef struct MyArgsOnStack
      {
        uint32_t    arg0 HLIF_ALIGNED;
        uint32_t    arg1 HLIF_ALIGNED;
        uint8_t*    arg2 HLIF_ALIGNED;
        MyType*     arg3 HLIF_ALIGNED;
      } MyArgsOnStack;

      void MyFunc3(const MyArgsOnStack*);

      #if defined(__cplusplus)
      bool hasComboParam = true;
      GtHliFunction<void, uint32_t, uint32_t, uint8_t*, MyType*> myFunc3 { "MyFunc3", GtHliCallStd::IGC_STACK, hasComboParam };

      myFunc3.InsertCallAtBbl(instrumentor, bbl, GT_IPOINT_BEFORE,
                              NullReg(),                // [void]     retVal: no return value
                              IargTid(),                // [uint32_t] arg0:   Current Thread ID
                              IargInsOpMask(ins),       // [uint32_t] arg1:   Mask of scalar operations
                              IargConstGrfRange(10, 5), // [uint8_t*] arg2:   Array of GRF registers [r10 - r14]
                              &myVar                    // [MyType*]  arg3:   Host pointer converted to device pointer
                              );
      #endif
                              
* ========================================================================================================*
@endverbatim
 *
 * @par Availability:
 * - IGtTool::OnKernelBuild
 */
template <typename RET, typename... PRMS> class GtHliFunction
{
public:
    /*!
     * Constructor
     * @param[in]   funcName        The name of the HLI function in the module compiled from the corresponding
     *                              source, e.g. the name of the OpenCL C function
     * @param[in]   callStd         The calling convention of the HLI function.
     *                              Currently, the only supported calling convention is IGC ABI for compilers
     *                              targeting Gen, aka stack-call ABI.
     * @param[in]   hasComboParam   true  - the function has a single parameter pointing to the array of argument values on stack
     *                              false - the function has a dedicated parameter for each argument value
     */
    GtHliFunction(const char * funcName, GtHliCallStd callStd = GtHliCallStd::IGC_STACK, bool hasComboParam = false) :
        _funcName(funcName), _callStd(callStd), _hasComboParam(hasComboParam) {}

    /// Return name of the function
    std::string Name() const { return _funcName; }

    /// Return calling convention of the function
    GtHliCallStd  CallStd() const { return _callStd; }

    /// Return number of function arguments
    constexpr uint32_t NumArgs() const  { return sizeof... (PRMS); }

    /// Return true if the function has a non-void return value, false - otherwise
    constexpr bool HasReturnValue() const  { return !IsVoidType<RET>::value; }

    /*!
     * Generate a procedure that calls this HLI function with the specified list of argument values
     *
     * @param[in]       instrumentor  Object that implements kernel instrumentation interface
     * @param[in]       proc          Procedure, the generated code is appended to
     * @param[in]       predicate     Predicate of the generated code. Invalid predicate is ignored
     * @param[in]       retReg        Register that receives return value.
     *                                NULL register indicates that the return value is unused
     * @param[in]       argValues     List of argument values of the function call.
     *                                Objects in the list follow the order of the formal function
     *                                paramters to which they will be assigned
     * 
     * <b>Supported types of argument values</b>\n
     * - <b>Any type ARGT that provides a method GtIargType ARGT::Type()</b>\n
     *        E.g. IargPrime, IargHostPtr, IargDevicePtr, IargTid, and other Iarg* types defined 
     *        in the @ref GTPIN_HLI_ARGUMENT section.\n
     *        For these types, the corresponding IGtIarg object is generated by the ARGT::Make() method\n\n
     * 
     * - <b>Any valid prime type ARGT (IsValidHliPrimeType<ARGT>)</b>\n
     *        These types are treated as IargPrime<ARGT>, e.g.
     *        int32_t => IargPrime<int32_t>; float => IargPrime<float>; enum E => IargPrime<enum E>; etc.\n\n
     * 
     * - <b>The GtReg type</b>\n
     *        This type is treated as IargPrime<GtReg>\n\n
     * 
     * - <b>Any pointer pointer type ARGT (IsPointer<ARGT>)</b>\n
     *        This type is treated as IargHostPtr, e.g. MyType* => IargHostPtr\n\n
     * 
     * @pre sizeof... (ARGS) == sizeof... (PRMS)
     * @pre ARGS types are compatible with the corresponding PRMS types. See @ref GtIargType::IsCompatible()
     */
    template <typename... ARGS>
    void GeneratePredicatedCall(IGtKernelInstrument& instrumentor, IGtGenProcedure& proc,
                                const GtPredicate& predicate, const GtReg& retReg, const ARGS&... argValues)
    {
        IGtIargFactory& factory = instrumentor.IargFactory();
        IretCptr ret = CreateIret<RET>(factory, retReg);
        GtIargList<PRMS...> argList(factory, argValues...);
        instrumentor.Coder().GenerateHliCall(proc, _funcName.c_str(), ret, argList.GetSpan(), _callStd, _hasComboParam,
                                             predicate);
    }

    /*! @copydoc GeneratePredicatedCall */
    template <typename... ARGS>
    void GenerateCall(IGtKernelInstrument& instrumentor, IGtGenProcedure& proc,
                      const GtReg& retReg, const ARGS&... argValues)
    {
        GeneratePredicatedCall(instrumentor, proc, GtPredicate(), retReg, argValues...);
    }

    /*!
     * @brief Insert a call to this HLI function with the specified argument values before/after
     * the specified instruction
     *
     * @param[in]       instrumentor  Object that implements kernel instrumentation interface
     * @param[in]       ins           Instruction in the original code of the kernel
     * @param[in]       bbl           BBL in the original code of the kernel
     * @param[in]       ipoint        Relative location of the instrumentation point in the original code
     * @param[in]       predicate     Predicate of the generated code. Invalid predicate is ignored
     * @param[in]       retReg        Register that receives return value.
     *                                NULL register indicates that the return value is unused
     * @param[in]       argValues     List of argument values of the function call
     * 
     * @see @ref GeneratePredicatedCall for the list of supported types of argument values
     * 
     * @return true - success, false - failure
     *         In case of error, the IGtCore::LastError() function provides the specific failure reason
     * 
     * @pre sizeof... (ARGS) == sizeof... (PRMS)
     * @pre ARGS types are compatible with the corresponding PRMS types. See @ref GtIargType::IsCompatible()
     */
    template <typename... ARGS>
    bool InsertPredicatedCallAtInstruction(IGtKernelInstrument& instrumentor, const IGtIns& ins, GtIpoint ipoint,
                                           const GtPredicate& predicate, const GtReg& retReg, const ARGS&... argValues)
    {
        GtGenProcedure proc;
        GeneratePredicatedCall(instrumentor, proc, predicate, retReg, argValues...);
        return instrumentor.InstrumentInstruction(ins, ipoint, proc);
    }

    /*! @copydoc InsertPredicatedCallAtInstruction */
    template <typename... ARGS>
    bool InsertCallAtInstruction(IGtKernelInstrument& instrumentor, const IGtIns& ins, GtIpoint ipoint,
                                 const GtReg& retReg, const ARGS&... argValues)
    {
        return InsertPredicatedCallAtInstruction(instrumentor, ins, ipoint, GtPredicate(), retReg, argValues...);
    }

    /*!
     * @brief Insert call to this HLI function with the specified argument values at the beginning/end of
     * the specified BBL
     *
     * @copydetails InsertCallAtInstruction
     */
    template <typename... ARGS>
    bool InsertPredicatedCallAtBbl(IGtKernelInstrument& instrumentor, const IGtBbl& bbl, GtIpoint ipoint,
                                   const GtPredicate& predicate, const GtReg& retReg, const ARGS&... argValues)
    {
        GtGenProcedure proc;
        GeneratePredicatedCall(instrumentor, proc, predicate, retReg, argValues...);
        return instrumentor.InstrumentBbl(bbl, ipoint, proc);
    }

    /*! @copydoc InsertPredicatedCallAtBbl */
    template <typename... ARGS>
    bool InsertCallAtBbl(IGtKernelInstrument& instrumentor, const IGtBbl& bbl, GtIpoint ipoint,
                         const GtReg& retReg, const ARGS&... argValues)
    {
        return InsertPredicatedCallAtBbl(instrumentor, bbl, ipoint, GtPredicate(), retReg, argValues...);
    }

    /*!
     * @brief Insert call to this HLI function with the specified argument values at all entries to the kernel
     *
     * @copydetails InsertCallAtInstruction
     */
    template <typename... ARGS>
    bool InsertPredicatedCallAtKernelEntries(IGtKernelInstrument& instrumentor,
                                             const GtPredicate& predicate, const GtReg& retReg, const ARGS&... argValues)
    {
        GtGenProcedure proc;
        GeneratePredicatedCall(instrumentor, proc, predicate, retReg, argValues...);
        return instrumentor.InstrumentEntries(proc);
    }

    /*! @copydoc InsertPredicatedCallAtKernelEntries */
    template <typename... ARGS>
    bool InsertCallAtKernelEntries(IGtKernelInstrument& instrumentor,
                                   const GtReg& retReg, const ARGS&... argValues)
    {
        return InsertPredicatedCallAtKernelEntries(instrumentor, GtPredicate(), retReg, argValues...);
    }

    /*!
     * @brief Insert call to this HLI function with the specified argument values at all exits of the kernel
     *
     * @copydetails InsertCallAtInstruction
     */
    template <typename... ARGS>
    bool InsertPredicatedCallAtKernelExits(IGtKernelInstrument& instrumentor,
                                           const GtPredicate& predicate, const GtReg& retReg, const ARGS&... argValues)
    {
        GtGenProcedure proc;
        GeneratePredicatedCall(instrumentor, proc, predicate, retReg, argValues...);
        return instrumentor.InstrumentExits(proc);
    }

    /*! @copydoc InsertPredicatedCallAtKernelExits */
    template <typename... ARGS>
    bool InsertCallAtKernelExits(IGtKernelInstrument& instrumentor,
                                 const GtReg& retReg, const ARGS&... argValues)
    {
        return InsertPredicatedCallAtKernelExits(instrumentor, GtPredicate(), retReg, argValues...);
    }

private:
    std::string     _funcName;
    GtHliCallStd    _callStd;
    bool            _hasComboParam;
};

/*! @} */

#pragma pack(pop)
}
#endif
