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

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

/*******************************************************************************************************
 * MEM tool
 *
 * Simple memory model - follow writes to global memory.
 *
 * NOTE: the tool callbacks might be called from different threads.
 */
#include <mutex>
#include <stdio.h>
#include <string.h>

#include "gtreplay_assert.h"
#include "gtreplay_client.h"
#include "knob_parser.h"
#include "mem_model.h"

// Global variables
uint8_t* sendDecodeData;

uint32_t   gMaxNumOfHwThreads = 0;
uint32_t   gNumOfInstructions = 0;

MemoryModel mem;
std::mutex mem_mutex;
uint32_t count = 0;
uint64_t max_addr = 0;

/*
 * BeforeInsCallback - callback called before instruction execution
 *
 * @params[in] tid - the ID of the GPU HW thread for which the callback is called
 * @params[in] ins - a handle to the current instruction
 * @params[in] state - a handle to the HW Thread state corresponding to tid
 */
void BeforeInsCallback(uint32_t tileId, uint32_t tid, GTReplayIns ins, GTReplayState state, void*)
{
    // Lock is required since this callback can be called from different threads
    std::lock_guard<std::mutex> lock(mem_mutex);

    GTREPLAY_ASSERT(GTReplay_IsMemoryWrite(ins));

    uint32_t id = GTReplay_InsId(ins);

    // Obtain attributes of access for this instruction
    uint32_t numOfAccesses = GTReplay_GetNumOfMemoryAccesses(ins); // number of accesses
    uint32_t surfaceId     = GTReplay_GetMemorySurfaceId(ins);     // surface ID (BTI)
    uint32_t memAccessSize = GTReplay_GetMemoryAccessSize(ins);    // memory access size
    
    // Obtain the dynamic execution mask - active channels
    uint32_t chan = GTReplay_DynamicExecMask(ins,  state);

    // Iterate over all accesses
    for (uint32_t i = 0; i < numOfAccesses; i++)
    {
        uint8_t data[32] = {};

        // If the current channel is active
        if (chan & (1 << i))
        {
            // Obtain the current access address
            uint64_t addr = GTReplay_GetMemoryAccessAddr(ins, state, i);
            // Obtain the current access value
            GTReplay_GetMemoryWriteValue(ins, state, i, data);

            uint32_t* d = (uint32_t*)data;

            // Write to memory in granularity of 4 bytes
            for (uint32_t e = 0; e < memAccessSize / 4; e++)
            {
                mem.Write(surfaceId, (uint32_t)addr, d[0]);

                if (addr > max_addr) max_addr = addr;

                count += 4;
                addr += 4;
                d++;
            }
        }
    }
}

/*
 * AfterInsCallback - callback called after instruction execution
 *                    An illustration - this tool doesn't need to register after instruction callbacks
 *
 * @params[in] tid - the ID of the GPU HW thread for which the callback is called
 * @params[in] ins - a handle to the current instruction
 * @params[in] state - a handle to the HW Thread state corresponding to tid
 */
void AfterInsCallback(uint32_t tileId, uint32_t tid, GTReplayIns ins, GTReplayState state, void*)
{
}

/*
 * OnKernelComplete - callback called upon kernel completion
 *
 * @params[in] kernel - a handle to the kernel
 */
void OnKernelComplete(GTReplayKernel kernel)
{
    // Print the results
    printf("\n\n=================\n"
           "MEM TOOL\n"
           "=================\n\n");

    printf("written memory = 0x%08x bytes max_addr = 0x%016llx\n", count, max_addr);

    // Print the resulting memory image
    mem.Print(true);
}

/*
 * OnKernelBuild - callback called before kernel execution
 *                 The purpose of this callback is to traverse the kernel binary and instrument callbacks
 *
 * @params[in] kernel - a handle to the kernel
 */
void OnKernelBuild(GTReplayKernel kernel)
{
    uint32_t gModelId = GTReplay_GetModel(kernel);

    gMaxNumOfHwThreads = GTReplay_MaxNumOfHWThreads(gModelId);

    gNumOfInstructions = GTReplay_NumOfInstructions(kernel);
    
    // Traverse all the basic blocks 
    for (GTReplayBbl bbl = GTReplay_BblHead(kernel); GTReplay_BblValid(bbl); bbl = GTReplay_BblNext(bbl))
    {
        // Traverse all the instruction within the basic blocks 
        for (GTReplayIns ins = GTReplay_InsHead(bbl); GTReplay_InsValid(ins); ins = GTReplay_InsNext(ins))
        {
            // Check whether the instruction is memory write
            if (!GTReplay_IsMemoryWrite(ins))
            {
                // If not, there is nothing to do
                continue;
               }
            // Register callback to be called before instruction execution
            GTReplay_RegisterCallbackBeforeIns(kernel, ins, BeforeInsCallback, NULL);
        }
    }
}

/*
 * GTReplay_Entry - tool entry point
 */
extern "C"
DLLEXP void FASTCALL GTReplay_Entry(int argc, const char *argv[])
{
    // configure GTReplay
    ConfigureGTReplay(argc, argv);
    
    // register OnKernelBuild and OnKernelComplete callbacks
    GTReplay_RegisterOnKernelBuildCallback(OnKernelBuild);
    GTReplay_RegisterOnKernelCompleteCallback(OnKernelComplete);

    // Start GTReplay
    GTReplay_Start();
}
