/*========================== begin_copyright_notice ============================
Copyright (C) 2021-2026 Intel Corporation

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

/*!
 * @file Declaration of the GEN code generation interface
 */

#ifndef IGT_GEN_CODER_H_
#define IGT_GEN_CODER_H_

#include "gt_basic_defs.h"
#include "igt_gen_arch.h"
#include "igt_gen_procedure.h"
#include "igt_ins.h"
#include "igt_ins_factory.h"
#include "igt_vreg_factory.h"
#include "igt_reg_allocator.h"
#include "igt_iarg.h"

namespace gtpin
{
#pragma pack(push, 8)

/* ===================================================================================================== */
// Class IGtGenCoder
/* ===================================================================================================== */
/*! @ingroup GTPIN_CODEGEN
 * @brief The interface that facilitates the process of generating code for instrumentation procedures
 * 
 * GTPin tools mainly use IGtGenCoder interface to generate predefined inline subroutines and insert them
 * into instrumentation procedures. They receive the IGtGenCoder object along with the IGtKernelInstrument
 * object when GTPin invokes the IGtTool::OnKernelBuild function (@see IGtKernelInstrument::Coder()).
 * 
 * @par Availability:
 * - IGtTool::OnKernelBuild
 */
class IGtGenCoder
{
public:
    /*!
     * @brief Get IGtInsFactory interface that can be used to create instructions in the instrumentation procedures
     * @return Instruction factory associated with this object
     */
    virtual IGtInsFactory& InstructionFactory() const = 0;

    /*!
     * @brief Get IGtVregFactory interface to the container of virtual registers in the instrumentation procedures
     * @return Factory of virtual registers associated with this object
     */
    virtual IGtVregFactory& VregFactory() const = 0;

    /*!
     * @brief Get IGtRegAllocator interface to the allocator of virtual registers in the instrumentation procedures
     * @return Register allocator associated with this object
     */
    virtual IGtRegAllocator& RegAllocator() const = 0;

    /// Return GEN model of the code generated by this object
    virtual const IGtGenModel& GenModel() const = 0;

    /// Return address model of profile buffer accesses
    virtual GtMemoryAddrModel ProfileBufferAddrModel() const = 0;

public:
    /*!
     * @brief Generate code that stores current value of TimeStampReg in the specified GRF register
     * 
     * @param[in]   proc            Procedure, the generated code is appended to
     * @param[in]   timerReg        Register that receives the TimeStampReg value
     * @param[in]   perfReg         Register that receives the PerfMonReg value
     * @param[in]   predicate       Predicate of the generated code. Invalid predicate is ignored
     */
    virtual void StartTimer(IGtGenProcedure& proc, const GtReg& timerReg, const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Generate code that stores current value of PerfMonReg in the specified GRF register
     * @copydetails StartTimer()
     */
    virtual void StartPerfmon(IGtGenProcedure& proc, const GtReg& perfReg, const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Generate code that computes elapsed time - difference between current value of
     * TimeStampReg and value stored in the specified GRF register.
     * 
     * @param[in]   proc            Procedure, the generated code is appended to
     * @param[in]   timerReg        Register that receives the (TimeStampReg - timerReg) value
     * @param[in]   perfReg         Register that receives the (PerfMonReg - perfReg) value
     * @param[in]   predicate       Predicate of the generated code. Invalid predicate is ignored
     */
    virtual void StopTimer(IGtGenProcedure& proc, const GtReg& timerReg, const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
    * @brief Generate code that computes elapsed time - difference between current value of
    * PerfMonReg and value stored in the specified GRF register.
    * @copydetails StopTimer
    */
    virtual void StopPerfmon(IGtGenProcedure& proc, const GtReg& perfReg, const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Similar to StopTimer, but additionally checks for the time counter overflow.
     * 
     * In case of overflow, the generated code sets f0.0:uw bits on, and assigns zero value to timerReg/perfReg.
     * Otherwise, the f0.0:uw bits are cleared off, and the elapsed time is stored in timerReg/perfReg.
     */
    virtual void StopTimerExt(IGtGenProcedure& proc, const GtReg& timerReg) const = 0;

    /*!
     * @brief Similar to StopPerfmon, but additionally checks for the perfomon counter overflow.
     * @copydetails StopTimerExt
     */
    virtual void StopPerfmonExt(IGtGenProcedure& proc, const GtReg& perfReg) const = 0;

    /*!
     * @brief Given offset within a profile buffer, generate code that computes address payload for instruction
     * that accesses the corresponding location
     * 
     * @param[in]   proc            Procedure, the generated code is appended to
     * @param[in]   addrReg         Register that receives the calculated address
     * @param[in]   offset          32-bit offset within the profile buffer
     * @param[in]   predicate       Predicate of the generated code. Invalid predicate is ignored
     */
    virtual void ComputeAddress(IGtGenProcedure& proc, const GtReg& addrReg, uint32_t offset,
                                const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Given offset within a profile buffer, generate code that computes address payload for instruction
     * that accesses the corresponding location
     * 
     * @param[in]   proc            Procedure, the generated code is appended to
     * @param[in]   addrReg         Register that receives the calculated address
     * @param[in]   offsetReg       32-bit register that holds offset within the buffer
     * @param[in]   predicate       Predicate of the generated code. Invalid predicate is ignored
     */
    virtual void ComputeAddress(IGtGenProcedure& proc, const GtReg& addrReg, const GtReg& offsetReg,
                                const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Generate code that computes the sum of the specified base address and the specified offset
     * 
     * @param[in]   proc            Procedure, the generated code is appended to
     * @param[in]   addrReg         Register that receives the calculated address
     * @param[in]   baseReg         Register that holds an address within the profile buffer
     * @param[in]   offset          32-bit offset relative to the base address
     * @param[in]   predicate       Predicate of the generated code. Invalid predicate is ignored
     */
    virtual void ComputeRelAddress(IGtGenProcedure& proc, const GtReg& addrReg, const GtReg& baseReg,
                                   int32_t offset, const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Generate code that computes the sum of the specified base address and the specified offset
     * 
     * @param[in]   proc            Procedure, the generated code is appended to
     * @param[in]   addrReg         Register that receives the calculated address
     * @param[in]   baseReg         Register that holds an address within the profile buffer
     * @param[in]   offsetReg       32-bit register that holds offset relative to the base address
     * @param[in]   predicate       Predicate of the generated code. Invalid predicate is ignored
     */
    virtual void ComputeRelAddress(IGtGenProcedure& proc, const GtReg& addrReg, const GtReg& baseReg,
                                   const GtReg& offsetReg, const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Generate code that computes offset within a memory chunk of the current thread bucket (@see GtProfileArray)
     * 
     * @note If offsetInChunk = 0, the generated code computes offset/address of the memory chunk associated
     * with the currently running HW thread, relative to the profile buffer base.
     * 
     * @param[in]   proc                Procedure, the generated code is appended to
     * @param[in]   offsetReg           32-bit register that receives the calculated offset within the profile buffer
     * @param[in]   addrReg             Register that receives the calculated address
     * @param[in]   numThreadBuckets    Number of thread buckets
     * @param[in]   chunkSize           Size of the per-bucket chunk, in bytes
     * @param[in]   baseOffset          Offset of the array of chunks within the profile buffer
     * @param[in]   offsetInChunk       Offset of the location within the chunk
     * @param[in]   predicate           Predicate of the generated code. Invalid predicate is ignored
     * 
     * @pre IsPower2(numThreadBuckets) && IsPower2(chunkSize)
     */
    virtual void ComputeThreadChunkOffset(IGtGenProcedure& proc, const GtReg& offsetReg, uint32_t numThreadBuckets,
                                          uint32_t chunkSize, uint32_t baseOffset = 0, uint32_t offsetInChunk = 0,
                                          const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Generate code that computes address within a memory chunk of the current thread bucket (@see GtProfileArray)
     * @copydetails ComputeThreadChunkOffset
     */
    virtual void ComputeThreadChunkAddress(IGtGenProcedure& proc, const GtReg& addrReg, uint32_t numThreadBuckets,
                                           uint32_t chunkSize, uint32_t baseOffset = 0, uint32_t offsetInChunk = 0,
                                           const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Generate code that computes the mask of effective SIMD channels at the point where the procedure is executed
     * 
     *          ceMask      = ignoreCeMask ? 0xffffffff : ChannelEnableReg()
     *          simdMaskReg = ceMask & DispatchMaskReg() & execMask & Mask(predicate)
     * 
     * @param[in]       proc            Procedure, the generated code is appended to
     * @param[in]       simdMaskReg     32-bit register that receives the mask of effective SIMD channels
     * @param[in]       ignoreCeMask    Flag that indicates whether the mask in ChannelEnableReg should be applied (false)
     *                                  or ignored (true) in the computation of effective SIMD channels
     * @param[in]       execMask        Execution mask to be used in the computation of effective SIMD channels.
     *                                  Normally, this mask is produced by the GtExecMask::Bits() function.
     * @param[in]       predicate       Predicate to be used in the computation. Invalid predicate is ignored
     */
    virtual void ComputeSimdMask(IGtGenProcedure& proc, const GtReg& simdMaskReg, bool ignoreCeMask, uint32_t execMask,
                                 const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Generate code that does not change the architectural state but consumes (reads) source registers of the
     * specified instruction.
     * 
     * This functions can be used to expose latency of instructions waiting for register values produced by other instructions.
     * 
     * @param[in]   proc            Procedure, the generated code is appended to
     * @param[in]   ins             Instruction whose register operands are consumed
     * @param[in]   predicate       Predicate of the generated code. Invalid predicate is ignored
     */
    virtual void GenerateFakeSrcConsumers(IGtGenProcedure& proc, const IGtIns& ins,
                                          const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Generate code that does not change the architectural state but consumes (reads) destination registers of the
     * specified instruction.
     * @copydetails GenerateFakeSrcConsumers
     */
    virtual void GenerateFakeDstConsumers(IGtGenProcedure& proc, const IGtIns& ins,
                                          const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Generate code that stores block of data to the profile buffer
     * 
     * @param[in]   proc            Procedure, the generated code is appended to
     * @param[in]   addrReg         Register that specifies memory address to be accessed
     * @param[in]   baseDataReg     The base of the register range that receives/holds the data being read/written
     * @param[in]   dataSize        Data size in bytes
     * @param[in]   predicate       Predicate of the generated code. Invalid predicate is ignored
     * 
     * @pre dataSize is a power-of-2 integer in the interval [16, MaxMemBlockSize()]
     * @pre addrReg and baseDataReg registers are zero-aligned:
     *      GtReg::SubRegNum() == 0 for physical register, and IGtVreg::RequiredSubregNum() == 0 for virtual register.
     *      Virtual registers created by MakeMsgAddrScratch() and MakeMsgDataScratch() satisfy this requirement.
     */
    virtual void StoreMemBlock(IGtGenProcedure& proc, const GtReg& addrReg, const GtReg& baseDataReg, uint32_t dataSize,
                               const GtPredicate& predicate = GtPredicate(), uint32_t execSize = 1) const = 0;

    /*!
     * @brief Generate code that loads block of data from the profile buffer
     * @copydetails LoadMemBlock
     */
    virtual void LoadMemBlock(IGtGenProcedure& proc, const GtReg& addrReg, const GtReg& baseDataReg, uint32_t dataSize,
                               const GtPredicate& predicate = GtPredicate(), uint32_t execSize = 1) const = 0;

    virtual void StoreMemBlockWithChannels(IGtGenProcedure& proc, const GtReg& addrReg, const GtReg& baseDataReg, uint32_t dataSize, const GtMemoryAccessChannelType mask,
                                           const GtPredicate& predicate = GtPredicate(), uint32_t execSize = 1) const = 0;

    virtual void LoadMemBlockWithChannels(IGtGenProcedure& proc, const GtReg& addrReg, const GtReg& baseDataReg, uint32_t dataSize, const GtMemoryAccessChannelType mask,
                                          const GtPredicate& predicate = GtPredicate(), uint32_t execSize = 1) const = 0;

    virtual void StoreMemBlockToStateSurface(IGtGenProcedure& proc, const GtReg& ssOffset, const GtReg& addrReg, const GtReg& baseDataReg,
        uint32_t dataSize, const GtPredicate& predicate = GtPredicate(), uint32_t execSize = 1) const = 0;

    virtual void LoadMemBlockFromStateSurface(IGtGenProcedure& proc, const GtReg& ssOffset, const GtReg& addrReg, const GtReg& baseDataReg,
                                              uint32_t dataSize, const GtPredicate& predicate = GtPredicate(), uint32_t execSize = 1) const = 0;

    virtual void StoreMemBlockWithChannelsToStateSurface(IGtGenProcedure& proc, const GtReg& ssOffset, const GtReg& addrReg, const GtReg& baseDataReg,
                                                         uint32_t dataSize, const GtMemoryAccessChannelType mask, const GtPredicate& predicate = GtPredicate(),
                                                         uint32_t execSize = 1) const = 0;

    virtual void LoadMemBlockWithChannelsFromStateSurface(IGtGenProcedure& proc, const GtReg& ssOffset, const GtReg& addrReg, const GtReg& baseDataReg,
                                                          uint32_t dataSize, const GtMemoryAccessChannelType mask, const GtPredicate& predicate = GtPredicate(),
                                                          uint32_t execSize = 1) const = 0;

    /*!
     * @brief Get maximum size of the load/store block, in bytes
     * 
     * @return Maximum number of bytes in the memory block that can be accessed by the StoreMemBlock/LoadMemBlock procedure
     * @post The returned value is a power-of-2
     */
    virtual uint32_t MaxMemBlockSize() const = 0;

    /*!
     * @brief Generate code that assigns sub-device (tile) ID to the specified register
     * 
     * If SW platform associated with this code generator does not support tile identification (see IsTileIdSupported()),
     * the register is zero-initialized.
     * 
     * @note Since tile ID is a per-thread constant, its value can be loaded in a dedicated virtual register once
     *       (at kernel entry), and then, shared by multiple instrumentation procedures without re-loading. 
     *
     * @param[in]   proc            Procedure, the generated code is appended to
     * @param[in]   tileIdReg       Register that receives tile ID
     * @param[in]   predicate       Predicate of the generated code. Invalid predicate is ignored
     */
    virtual void LoadTileId(IGtGenProcedure& proc, const GtReg& tileIdReg,
                            const GtPredicate& predicate = GtPredicate()) const = 0;

    /// Return true if SW platform associated with this code generator supports tile identification
    virtual bool IsTileIdSupported() const = 0;

    /*!
     * Generate code that calls the specified HLI function with the specified argument values
     *
     * @param[in]   proc            Procedure, the generated code is appended to
     * @param[in]   funcName        The name of the HLI function 
     * @param[in]   ret             Descriptor of the return value of the HLI function, or nullptr
     * @param[in]   args            Span of pointers to IGtIarg objects that represent arguments of the HLI function
     * @param[in]   callStd         The calling convention of the HLI function
     * @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
     * @param[in]   predicate       Predicate of the generated code. Invalid predicate is ignored
     */
    virtual void GenerateHliCall(IGtGenProcedure& proc, const char* funcName, const IGtIret* ret, const GtIargSpan& args,
                                 GtHliCallStd callStd = GtHliCallStd::IGC_STACK, bool hasComboParam = false,
                                 const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * Generate code that assigns current Thread ID (TID) to the specified register
     * 
     * @param[in]   proc        Procedure, the generated code is appended to
     * @param[in]   tidReg      Register that receives the TID value
     * @param[in]   predicate   Predicate of the generated code. Invalid predicate is ignored
     */
    virtual void GetTid(IGtGenProcedure& proc, const GtReg& tidReg,
                        const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * Generate code that assigns current global DualSubSliceID
     *
     * @param[in]   proc        Procedure, the generated code is appended to
     * @param[in]   dssReg      Register that receives the global DSS value
     * @param[in]   predicate   Predicate of the generated code. Invalid predicate is ignored
     */
    virtual void GetDssId(IGtGenProcedure& proc, const GtReg& dssReg,
                         const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * Generate code that assigns current Fixed Function Thread ID (FFTID) to the specified register
     * 
     * @param[in]   proc            Procedure, the generated code is appended to
     * @param[in]   ffTidReg        Register that receives the FFTID value
     * @param[in]   predicate       Predicate of the generated code. Invalid predicate is ignored
     */
    virtual void GetFfTid(IGtGenProcedure& proc, const GtReg& ffTidReg,
                          const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Generate code that stores the specified range of GRF registers in the specified memory buffer.
     * 
     * The code generated by this function advances the addrReg by numRegs*GrfRegSize(), so that addrReg will
     * point to the location just after the stored memory range
     * 
     * @param[in]   proc            Procedure, the generated code is appended to
     * @param[in]   addrReg         Register that specifies base address of the memory buffer
     * @param[in]   firstRegNum     First GRF register to be stored
     * @param[in]   numRegs         Number of registers to be stored
     * @param[in]   predicate       Predicate of the generated code. Invalid predicate is ignored
    */
    virtual void StoreRegRange(IGtGenProcedure& proc, const GtReg& addrReg, GtRegNum firstReg, uint32_t numRegs,
                               const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * @brief Generate code that loads the specified range of GRF registers from the specified memory buffer.
     * 
     * The code generated by this function advances the addrReg by numRegs*GrfRegSize(), so that addrReg will
     * point to the location just after the loaded memory range
     * 
     * @copydetails StoreRegRange
     */
    virtual void LoadRegRange(IGtGenProcedure& proc, const GtReg& addrReg, GtRegNum firstReg, uint32_t numRegs,
                              const GtPredicate& predicate = GtPredicate()) const = 0;

    /*!
     * Generate code that stores address payload of the specified SEND instruction in the specified buffer.
     * 
     * The code generated by this function advances the addrReg by numRegs*GrfRegSize(), so that addrReg will
     * point to the location just after the stored memory range.
     * 
     * @note The address payload includes all addresses of the SEND message, even those that could be disabled
     * by the channel execution mask.
     * 
     * @param[in]   proc            Procedure, the generated code is appended to
     * @param[in]   bufferPtrReg    Register that points to the output buffer
     * @param[in]   sendIns         SEND instruction whose address payload is queried
     * @param[in]   predicate       Predicate of the generated code. Invalid predicate is ignored
     */
    virtual void StorePayloadAddresses(IGtGenProcedure& proc, const GtReg& bufferPtrReg,
                                       const IGtIns& sendIns, const GtPredicate& predicate) const = 0;

    /*!
     *  Convert a given procedure into a conditional procedure that will be executed based
     *  on the value of the specified register.
     *
     * @note If 'proc' consists of the following code:
     *           ins1
     *           ins2
     *           ...
     *           insN
     *
     *        Then the resulted procedure will be:
     *           pred = condReg;
     *           (pred) ins1
     *           (pred) ins2
     *           ...
     *           (pred) insN
     *
     * @param[in]   proc            Procedure that holds the code that will be converted to conditional
     * @param[out]  condProc        Procedure, the generated conditional code will be appended to
     * @param[in]   condReg         Register that holds the value for the predicate
     *
     * @pre 'proc' does not contain an instruction that is using ARF register f1.0.
     */
    virtual void ConvertToConditionalProcedure(const IGtGenProcedure& proc, IGtGenProcedure& condProc,
                                              const GtReg& condReg) const = 0;

    /*!
     * @brief Generate procedure that will calculate a condition between two virtual registers
     *        and convert the given procedure into conditional based of the condition's value.
     *        The condition that will be calculated depends on the condition modifier.
     *
     * @note If 'proc' consists of the following code:
     *           ins1
     *           ins2
     *           ...
     *           insN
     *
     *        Then the resulted procedure will be:
     *           pred = srcReg0 COND srcReg1;
     *           (pred) ins1
     *           (pred) ins2
     *           ...
     *           (pred) insN
     *
     *        Where 'condMode' can be one of the following:
     *           GED_COND_MODIFIER_z  : srcReg0 == srcReg1
     *           GED_COND_MODIFIER_nz : srcReg0 != srcReg1
     *           GED_COND_MODIFIER_g  : srcReg0 >  srcReg1
     *           GED_COND_MODIFIER_ge : srcReg0 >= srcReg1
     *           GED_COND_MODIFIER_l  : srcReg0 <  srcReg1
     *           GED_COND_MODIFIER_le : srcReg0 <= srcReg1
     *
     * @param[in]   proc            Procedure that holds the code that will be converted to conditional
     * @param[out]  condProc        Procedure, the generated code will be appended to
     * @param[in]   condMode        Condition modifier for the CMP instruction
     * @param[in]   srcReg0         virtual register that will be used for the condition
     * @param[in]   srcReg1         virtual register that will be used for the condition
     *
     * @pre 'proc' does not contain an instruction that is using ARF register f1.0.
     */
    virtual void GenerateConditionalProcedure(const IGtGenProcedure& proc, IGtGenProcedure& condProc,
                        const GtCondModifier& condMode, const GtReg& srcReg0, const GtReg& srcReg1) const = 0;

    /*!
     * @brief Generate procedure that will calculate a condition between a virtual registers and an immediate
     *        and convert the given procedure into conditional based of the condition's value.
     *        The condition that will be calculated depends on the condition modifier.
     *
     * @note If 'proc' consists of the following code:
     *           ins1
     *           ins2
     *           ...
     *           insN
     *
     *        Then the resulted procedure will be:
     *           pred = srcReg COND val;
     *           (pred) ins1
     *           (pred) ins2
     *           ...
     *           (pred) insN
     *
     *        Where 'condMode' can be one of the following:
     *           GED_COND_MODIFIER_z  : srcReg == val
     *           GED_COND_MODIFIER_nz : srcReg != val
     *           GED_COND_MODIFIER_g  : srcReg >  val
     *           GED_COND_MODIFIER_ge : srcReg >= val
     *           GED_COND_MODIFIER_l  : srcReg <  val
     *           GED_COND_MODIFIER_le : srcReg <= val
     *
     * @param[in]   proc            Procedure that holds the code that will be converted to conditional
     * @param[out]  condProc        Procedure, the generated code will be appended to
     * @param[in]   condMode        Condition modifier for the CMP instruction
     * @param[in]   srcReg          Virtual register that will be used for the condition
     * @param[in]   val             Immediate that will be used for the condition
     *
     * @pre 'proc' does not contain an instruction that is using ARF register f1.0.
     */
    virtual void GenerateConditionalProcedure(const IGtGenProcedure& proc, IGtGenProcedure& condProc,
                                    const GtCondModifier& condMode, const GtReg& srcReg, uint32_t val) const = 0;

protected:
    /// @internal Protected destructor
    virtual ~IGtGenCoder() = default;
};
GTPIN_ASSERT_PURE_ABSTRACT(IGtGenCoder);

#pragma pack(pop)
}
#endif
