/*========================== begin_copyright_notice ============================
Copyright (C) 2018-2025 Intel Corporation

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

/*!
 * @file Implementation of the Perftrace tool
 */

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

using std::list;
using std::vector;
using std::string;
using std::map;
using std::ofstream;

// Profiling Mode enum
enum PERFTRACE_MODE
{
    PERFTRACE_FUNTIME,
    PERFTRACE_MEMLATENCY,
    PERFTRACE_OCCUPANCY,
};

// globals
Knob<int>  gKnobMode("mode", 0, "Trace instrumentation scope\n { 0 - funtime, 1 - memory read instructions, 2 - occupancy} ");
Knob<int>  gKnobPhase("phase", 0, "tracing tool - processing phase\n { 1 - pre-processing, 2 - processing - trace gathering} ");
Knob<int>  gKnobMaxTraceBufferInMB("max_buffer_mb", 300, "perftrace - the max allowed size of the trace buffer per kernel in MB\n");


/* ============================================================================================= */
// PerfTraceDispatch implementation
/* ============================================================================================= */
bool PerfTraceDispatch::ReadTrace(const GtProfileTrace& traceAccessor, const IGtProfileBuffer& profileBuffer)
{
    uint32_t traceSize = traceAccessor.Size(profileBuffer);
    _rawTrace.resize(traceSize);
    _isTrimmed = traceAccessor.IsTruncated(profileBuffer);
    return traceAccessor.Read(profileBuffer, _rawTrace.data(), 0, traceSize);
}

bool PerfTraceDispatch::IsEmpty() const
{
    return _rawTrace.size() == 0;
}

/* ============================================================================================= */
// PerfTraceKernel implementation
/* ============================================================================================= */
PerfTraceKernel::PerfTraceKernel(const IGtKernelInstrument& kernelInstrument, const uint32_t recordSize, uint32_t numTiles) : _numTiles(numTiles)
{
    const IGtKernel& kernel = kernelInstrument.Kernel();
    const IGtCfg&    cfg    = kernelInstrument.Cfg();

    _name       = GlueString(kernel.Name());
    _extName    = ExtendedKernelName(kernel);
    _platform   = kernel.GpuPlatform();
    _genId      = kernel.GenModel().Id();
    _asmText    = CfgAsmText(cfg);
    _uniqueName = kernel.UniqueName();

    // Initialize trace accessor. The trace capacity is expected to be computed during the preprocessing phase.
    uint64_t traceCapacity =  PerfTracePreProcessor::Instance()->TraceSize(_extName);
    if (traceCapacity == 0)
    {
        // Unknown trace capacity
        GTPIN_WARNING("PERFTRACE: unknown trace capacity for kernel " + _name + ". Assuming the kernel is filtered out. "
            "Allocating a buffer of 8KB size. If the kernel is supposed to run, expect buffer overflow. "
            "In this case, please re-run phase 1 and make sure the kernel is not filtered out.");
        traceCapacity = 0x2000;
    }
    else
    {
        traceCapacity += 0x2000; // Add some space to account for possible fluctuation of trace sizes between phases
        if (traceCapacity > UINT32_MAX)
        {
            GTPIN_WARNING("PERFTRACE: The kernel " + _name + " exceedeed maximum trace capacity.");
            traceCapacity = UINT32_MAX;
        }
    }
    if (traceCapacity > (uint64_t(gKnobMaxTraceBufferInMB) * 0x100000))
    {
        GTPIN_WARNING("PERFTRACE: required capacity (" + DecStr(traceCapacity) + ") for kernel " + _name + " is too big - cut to " + DecStr(gKnobMaxTraceBufferInMB) + "MB. "
                      "Expect the final trace to contain partial data.");
        traceCapacity = uint64_t(gKnobMaxTraceBufferInMB) * 0x100000;
    }
    _traceAccessor = GtProfileTrace((uint32_t)traceCapacity, recordSize);
    _traceAccessor.Allocate(kernelInstrument.ProfileBufferAllocator());
}

PerfTraceDispatch& PerfTraceKernel::AddPerfTrace(IGtKernelDispatch& kernelDispatch)
{
    // Create a new PerfTraceDispatch object and store the entire trace within this object
    _traces.emplace_back(kernelDispatch);
    PerfTraceDispatch& perfTraceDispatch  = _traces.back();
    if (!perfTraceDispatch.ReadTrace(_traceAccessor, *kernelDispatch.GetProfileBuffer()))
    {
        GTPIN_ERROR_MSG("PERFTRACE: Failed to read profile buffer for kernel " + _name);
    }
    return perfTraceDispatch;
}

void PerfTraceKernel::DumpAsm() const
{
    DumpKernelAsmText(_name, _uniqueName, _asmText);
}

/* ============================================================================================= */
// PerfTrace implementation
/* ============================================================================================= */
void PerfTrace::OnKernelBuild(IGtKernelInstrument& instrumentor)
{
    const IGtKernel& kernel = instrumentor.Kernel();
    uint32_t numTiles = (instrumentor.Coder().IsTileIdSupported()) ? GTPin_GetCore()->GenArch().MaxTiles(kernel.GpuPlatform()) : 1;

    // Create new KernelData object and add it to the data base
    auto ret = _kernels.emplace(std::piecewise_construct, std::forward_as_tuple(kernel.Id()), std::forward_as_tuple(instrumentor, RecordSize(), numTiles));
    if (ret.second)
    {
        PerfTraceKernel& perfTraceKernel = (*ret.first).second;
        if (!perfTraceKernel.IsEnabled())
        {
            GTPIN_WARNING("PERFTRACE: The trace won't be generated for kernel " + perfTraceKernel.Name());
            return;
        }
        Instrument(instrumentor, perfTraceKernel);
    }
}

void PerfTrace::OnKernelRun(IGtKernelDispatch& dispatcher)
{
    bool isProfileEnabled = false;

    const IGtKernel& kernel = dispatcher.Kernel();
    GtKernelExecDesc execDesc; dispatcher.GetExecDescriptor(execDesc);
    if (kernel.IsInstrumented() && IsKernelExecProfileEnabled(execDesc, kernel.GpuPlatform(), kernel.Name().Get()))
    {
        auto it = _kernels.find(kernel.Id());
        if (it != _kernels.end())
        {
            const PerfTraceKernel&  perfTraceKernel = it->second;
            if (perfTraceKernel.IsEnabled())
            {
                IGtProfileBuffer*     buffer        = dispatcher.CreateProfileBuffer(); GTPIN_ASSERT(buffer);
                const GtProfileTrace& traceAccessor = perfTraceKernel.TraceAccessor();
                if (traceAccessor.Initialize(*buffer))
                {
                    isProfileEnabled = true;
                }
                else
                {
                    GTPIN_ERROR_MSG("PERFTRACE: Failed to write into memory buffer for kernel " + string(kernel.Name()));
                }
            }
        }
    }
    dispatcher.SetProfilingMode(isProfileEnabled);
}

void PerfTrace::OnKernelComplete(IGtKernelDispatch& dispatcher)
{
    if (!dispatcher.IsProfilingEnabled())
    {
        return; // Do nothing if kernel profiling has not been applied/failed
    }

    const IGtKernel& kernel = dispatcher.Kernel();
    auto it = _kernels.find(kernel.Id());
    if (it != _kernels.end())
    {
        // Read the trace from the profile buffer
        PerfTraceKernel&  perfTraceKernel = it->second;
        perfTraceKernel.AddPerfTrace(dispatcher);
    }
}

void PerfTraceFuntime::GeneratePreCode(GtGenProcedure& proc, const IGtGenCoder& coder)
{
    IGtInsFactory&  insF = coder.InstructionFactory();

    proc += insF.MakeMov(GtDstRegion(_timeReg, 1, GED_DATA_TYPE_ud),
                         GtRegRegion(TimeStampReg(), GtStride(2, 2, 1), GED_DATA_TYPE_ud), { 2 });

    if (!proc.empty()) { proc.front()->AppendAnnotation(__func__); }
}

void PerfTraceMemoryLatency::GeneratePreCode(GtGenProcedure& proc, const IGtGenCoder& coder)
{
    coder.StartTimer(proc, _timeReg);
    if (!proc.empty()) { proc.front()->AppendAnnotation(__func__); }
}

void PerfTraceFuntime::GeneratePostCode(GtGenProcedure& proc, const IGtGenCoder& coder, const PerfTraceKernel& perfTraceKernel)
{
    // Initialize virtual registers
    IGtVregFactory&   vregs = coder.VregFactory();
    GtReg addrReg   = vregs.MakeMsgAddrScratch();
    GtReg dataReg   = vregs.MakeMsgDataScratch(VREG_TYPE_HWORD);
    GtReg offsetReg = vregs.MakeScratch(VREG_TYPE_DWORD);
    GtReg tileIdReg = vregs.MakeScratch(VREG_TYPE_DWORD);

    auto fieldReg = [&](uint32_t fieldOffset) -> GtReg { return GtReg(dataReg, sizeof(uint32_t), fieldOffset / sizeof(uint32_t)); };

    uint32_t recordSize = RecordSize();

    GtReg sr0FieldReg    = fieldReg(offsetof(PerfTraceFuntimeRecord, sr0));
    GtReg tileIdFieldReg = fieldReg(offsetof(PerfTraceFuntimeRecord, tileId));
    GtReg timeStartReg   = fieldReg(offsetof(PerfTraceFuntimeRecord, timeStart));
    GtReg timeEndReg     = fieldReg(offsetof(PerfTraceFuntimeRecord, timeEnd));

    IGtInsFactory&  insF = coder.InstructionFactory();
    GtPredicate     predicate(FlagReg(0));

    // dataReg[1-4] = { preTm[0-1], postTm[0-1] }
    proc += insF.MakeMov(timeEndReg, GtRegRegion(TimeStampReg(), GtStride(2, 2, 1), GED_DATA_TYPE_ud), { 2 });
    proc += insF.MakeMov(timeStartReg, GtRegRegion(_timeReg, GtStride(2, 2, 1), GED_DATA_TYPE_ud), { 2 });

    // Set values of PerfTraceRecordHeader fields in dataReg
    proc += insF.MakeMov(sr0FieldReg, StateReg(0)); // sr0.0

    coder.LoadTileId(proc, tileIdReg);
    proc += insF.MakeMov(tileIdFieldReg, tileIdReg); // tile ID

    // Allocate new record in the trace.
    // Set offsetReg = offset of the allocated record in the profile buffer, addrReg = address of the allocated record
    perfTraceKernel.TraceAccessor().ComputeNewRecordOffset(coder, proc, recordSize, offsetReg);
    coder.ComputeAddress(proc, addrReg, offsetReg);

    //if (!predicate) { STORE buffer[offsetReg] = dataReg;
    coder.StoreMemBlock(proc, addrReg, dataReg, recordSize, !predicate);

    if (!proc.empty()) { proc.front()->AppendAnnotation(__func__); }
}

void PerfTraceMemoryLatency::GeneratePostCode(GtGenProcedure& proc, const IGtGenCoder& coder)
{
    IGtInsFactory&  insF  = coder.InstructionFactory();

    coder.StopTimer(proc, _timeReg);
    proc += insF.MakeAdd(_cyclesAccumReg, _cyclesAccumReg, _timeReg);
    
    if (!proc.empty()) { proc.front()->AppendAnnotation(__func__); }
}

void PerfTraceMemoryLatency::GenerateFiniCode(GtGenProcedure& proc, const IGtGenCoder& coder, const PerfTraceKernel& perfTraceKernel)
{
    // Initialize virtual registers
    IGtVregFactory&   vregs = coder.VregFactory();
    GtReg addrReg   = vregs.MakeMsgAddrScratch();
    GtReg dataReg   = vregs.MakeMsgDataScratch(VREG_TYPE_HWORD);
    GtReg offsetReg = vregs.MakeScratch(VREG_TYPE_DWORD);
    GtReg tileIdReg = vregs.MakeScratch(VREG_TYPE_DWORD);

    auto fieldReg = [&](uint32_t fieldOffset) -> GtReg { return GtReg(dataReg, sizeof(uint32_t), fieldOffset / sizeof(uint32_t)); };
    GtReg sr0FieldReg    = fieldReg(offsetof(PerfTraceMemoryLatencyRecord, sr0));
    GtReg tileIdFieldReg = fieldReg(offsetof(PerfTraceMemoryLatencyRecord, tileId));
    GtReg cyclesReg      = fieldReg(offsetof(PerfTraceMemoryLatencyRecord, cycles));

    uint32_t recordSize = RecordSize();

    IGtInsFactory&  insF = coder.InstructionFactory();
    GtPredicate     predicate(FlagReg(0));

    // Set values of PerfTraceRecordHeader fields in dataReg
    proc += insF.MakeMov(sr0FieldReg, StateReg(0));   // sr0.0
    coder.LoadTileId(proc, tileIdReg);
    proc += insF.MakeMov(tileIdFieldReg, tileIdReg);  // tile ID
    proc += insF.MakeMov(cyclesReg, _cyclesAccumReg); // accumReg

    // Allocate new record in the trace.
    // Set offsetReg = offset of the allocated record in the profile buffer, addrReg = address of the allocated record
    perfTraceKernel.TraceAccessor().ComputeNewRecordOffset(coder, proc, recordSize, offsetReg);
    coder.ComputeAddress(proc, addrReg, offsetReg);

    //if (!predicate) { STORE buffer[offsetReg] = dataReg;
    coder.StoreMemBlock(proc, addrReg, dataReg, recordSize, !predicate);

    if (!proc.empty()) { proc.front()->AppendAnnotation(__func__); }
}

void PerfTraceFuntime::Instrument(IGtKernelInstrument& instrumentor, const PerfTraceKernel& perfTraceKernel)
{
    const IGtCfg&      cfg   = instrumentor.Cfg();
    const IGtGenCoder& coder = instrumentor.Coder();
    IGtVregFactory&    vregs = coder.VregFactory();

    _timeReg = vregs.Make(VREG_TYPE_QWORD);

    // Generate code that starts/stops timer at entry/exit of the kernel
    GtGenProcedure preCode;  GeneratePreCode(preCode, coder);
    GtGenProcedure postCode; GeneratePostCode(postCode, coder, perfTraceKernel);

    // Instrument kernel entries
    instrumentor.InstrumentEntries(preCode);

    // Instrument kernel exits
    for (auto bblPtr : cfg.ExitBbls())
    {
        const IGtIns& ins = bblPtr->LastIns(); GTPIN_ASSERT(ins.IsEot());
        GtGenProcedure fakeConsumers;
        coder.GenerateFakeSrcConsumers(fakeConsumers, ins);
        instrumentor.InstrumentInstruction(ins, GtIpoint::Before(), fakeConsumers);
        instrumentor.InstrumentInstruction(ins, GtIpoint::Before(), postCode);
    }
}

void PerfTraceMemoryLatency::Instrument(IGtKernelInstrument& instrumentor, const PerfTraceKernel& perfTraceKernel)
{
    const IGtCfg&     cfg = instrumentor.Cfg();
    IGtVregFactory& vregs = instrumentor.Coder().VregFactory();

    _cyclesAccumReg = vregs.Make(VREG_TYPE_DWORD);

    // Instrument kernel exits
    for (auto bblPtr : cfg.Bbls())
    {
        InstrumentBbl(instrumentor, *bblPtr, perfTraceKernel);
    }
}

void PerfTraceMemoryLatency::InstrumentBbl(IGtKernelInstrument& instrumentor, const IGtBbl& bbl, const PerfTraceKernel& perfTraceKernel)
{
    const IGtGenCoder& coder = instrumentor.Coder();

    for (auto& insPtr : bbl.Instructions())
    {
        const IGtIns& ins = *insPtr;

        if (ins.IsEot())
        {
            GtGenProcedure finiCode; GenerateFiniCode(finiCode, coder, perfTraceKernel);
            instrumentor.InstrumentInstruction(ins, GtIpoint::Before(), finiCode);
        }
        if (ins.IsMemRead())
        {
            GtGenProcedure preCode;  GeneratePreCode(preCode, coder);
            GtGenProcedure postCode; GeneratePostCode(postCode, coder);
            GtGenProcedure fakeConsumers;
            coder.GenerateFakeDstConsumers(fakeConsumers, ins);

            instrumentor.InstrumentInstruction(ins, GtIpoint::Before(), preCode);
            instrumentor.InstrumentInstruction(ins, GtIpoint::After(), fakeConsumers);
            instrumentor.InstrumentInstruction(ins, GtIpoint::After(), postCode);
        }
    }
}

void PerfTraceFuntime::OnFini(void)
{
    PerfTraceFuntime& me = *Instance();
    IGtCore* gtpinCore = GTPin_GetCore();
    for (auto& ref : me._kernels)
    {
        const PerfTraceKernel&  perfTraceKernel = ref.second;
        PerfTracePostProcessorFuntime(*gtpinCore, perfTraceKernel)();
    }
}

void PerfTraceMemoryLatency::OnFini(void)
{
    PerfTraceMemoryLatency& me = *Instance();
    IGtCore* gtpinCore = GTPin_GetCore();
    for (auto& ref : me._kernels)
    {
        const PerfTraceKernel&  perfTraceKernel = ref.second;
        PerfTracePostProcessorMemoryLatency(*gtpinCore, perfTraceKernel)();
    }
}

PerfTraceFuntime* PerfTraceFuntime::Instance()
{
    static PerfTraceFuntime perfTrace;
    return &perfTrace;
}

PerfTraceMemoryLatency* PerfTraceMemoryLatency::Instance()
{
    static PerfTraceMemoryLatency perfTrace;
    return &perfTrace;
}

/* ============================================================================================= */
// PerfTracePreProcessor implementation
/* ============================================================================================= */
const char* PerfTracePreProcessor::_kernelPreProcessFileName   = "perftrace_pre_process.txt";
const char* PerfTracePreProcessor::_dispatchPreProcessFileName = "perftrace_pre_process_dispatch.txt";

PerfTracePreProcessor::PerfTracePreProcessor()
{
    if (gKnobPhase == 2)
    {
        // Read the data collected during the preprocessing phase
        std::ifstream is(_kernelPreProcessFileName);
        GTPIN_ASSERT_MSG(is, string("File ") + _kernelPreProcessFileName + " does not exist. The trace won't be generated");
        is >> _kernelCounters;
    }
    else if (gKnobPhase == 1)
    {
        // Create pre_process files or remove old pre_process files's content if they exist
        CreateCleanFile(_kernelPreProcessFileName);
        CreateCleanFile(_dispatchPreProcessFileName);
    }

    if (gKnobMode == PERFTRACE_FUNTIME || gKnobMode == PERFTRACE_OCCUPANCY)
    {
        _perfTrace = PerfTraceFuntime::Instance();
    }
    if (gKnobMode == PERFTRACE_MEMLATENCY)
    {
        _perfTrace = PerfTraceMemoryLatency::Instance();
    }
}

PerfTracePreProcessor* PerfTracePreProcessor::Instance()
{
    static PerfTracePreProcessor instance;
    return &instance;
}

void PerfTracePreProcessor::OnFini()
{
    PerfTracePreProcessor&  tool = *Instance();
    tool.DumpKernelProfiles(_kernelPreProcessFileName);
    tool.DumpDispatchProfiles(_dispatchPreProcessFileName);
}

uint64_t PerfTracePreProcessor::TraceSize(const string& extKernelName) const
{
    auto it = _kernelCounters.find(extKernelName);
    return ((it == _kernelCounters.end()) ? 0 : it->second.weight);
}

uint32_t PerfTracePreProcessor::GetBblWeight(IGtKernelInstrument&, const IGtBbl& bbl) const
{
    return bbl.IsEot() ? _perfTrace->RecordSize() : 0;
}

void PerfTracePreProcessor::AggregateDispatchCounters(KernelWeightCounters& kc, KernelWeightCounters dc) const
{
    kc.weight = std::max(kc.weight, dc.weight);
    kc.freq += dc.freq;
}

/* ============================================================================================= */
// PerfTracePostProcessor implementation
/* ============================================================================================= */
const char* PerfTracePostProcessor::_traceFileName = "perftrace_compressed.bin";

PerfTracePostProcessor::PerfTracePostProcessor(const IGtCore& gtpinCore, const PerfTraceKernel& perfTraceKernel, const PerfTrace* perfTrace) :
    _kernel(&perfTraceKernel), _kernelDir(JoinPath(string(gtpinCore.ProfileDir()), perfTraceKernel.UniqueName())), _perfTrace(perfTrace) 
{
    GTPIN_ASSERT(_perfTrace);
}

bool PerfTracePostProcessor::operator()()
{
    if (!MakeDirectory(_kernelDir))
    {
        GTPIN_WARNING("PERFTRACE: Could not create directory " + _kernelDir);
        return false;
    }

    // Process traces recorded in kernel dispatches
    for (const auto& trace : _kernel->GetTraces())
    {
        if (!trace.IsEmpty())
        {
            if (trace.IsTrimmed())
            {
                GTPIN_WARNING("PERFTRACE: Detected trace buffer overflow in kernel " + _kernel->Name());
            }

            string subdir   = trace.KernelExecDesc().ToString(_kernel->Platform(), ExecDescFileNameFormat());
            string dir      = MakeSubDirectory(_kernelDir, subdir);
            string filePath = JoinPath(dir, _traceFileName);

            ofstream fs(filePath, std::ios::binary);
            if (!fs)
            {
                GTPIN_WARNING("PERFTRACE: Could not create file " + filePath);
                continue;
            }
            StoreTrace(trace, fs);
        }
    }
    return true;
}

void PerfTracePostProcessor::StoreTrace(const PerfTraceDispatch& trace, std::ofstream& fs)
{
    const uint8_t* traceData  = trace.Data();
    uint32_t       traceSize  = trace.Size();

    uint32_t       recordSize = _perfTrace->RecordSize();

    // Associate trace records with threads - populate _threadTraceRecords array
    uint32_t maxThreads = _kernel->GenModel().MaxThreads(); // Max number of HW threads
    _threadTraceRecords.resize(_kernel->NumTiles());

    for (uint32_t tile = 0; tile < _kernel->NumTiles(); tile++)
    {
        _threadTraceRecords[tile].clear();
        _threadTraceRecords[tile].resize(maxThreads);
    }

    std::vector<uint32_t> numProfiledThreads; // Number of profiled (active) threads
    numProfiledThreads.resize(_kernel->NumTiles(), 0);

    for (uint32_t recordOffset = 0; recordOffset + recordSize <= traceSize;)
    {
        const uint8_t* recordPtr = traceData + recordOffset;
        // Retrive thread ID from the record
        uint32_t tid = GetGlobalTid(recordPtr);
        uint32_t tileId = GetTileId(recordPtr); GTPIN_ASSERT(tileId < _kernel->NumTiles());
        if (recordOffset + recordSize > traceSize)
        {
            break; // end of trace
        }

        auto& tileThreadRecords = _threadTraceRecords[tileId];
        auto& threadTraceRecords = tileThreadRecords[tid];

        // Add a new trace record reference to _threadTraceRecords
        if (threadTraceRecords.empty()) { ++numProfiledThreads[tileId]; } // Increment thread count on the first relevant record
        threadTraceRecords.emplace_back(TraceRecord{ recordPtr, recordSize });

        recordOffset += recordSize;
    }

    uint32_t mode = gKnobMode;
    Store(mode, fs);               // Store profiling mode

    // Compute and store the number of involved tiles
    uint32_t numOfTiles = 0;
    for (uint32_t i = 0; i < numProfiledThreads.size(); i++)
    {
        numOfTiles += (numProfiledThreads[i] == 0) ? 0 : 1;
    }
    Store(numOfTiles, fs);

    for (uint32_t tileId = 0; tileId < _threadTraceRecords.size(); tileId++)
    {
        if (numProfiledThreads[tileId] == 0) { continue; }

        Store(tileId, fs);

        // Store the number of profiled threads
        Store(numProfiledThreads[tileId], fs);

        // Store per-thread traces
        for (uint32_t tid = 0; tid < maxThreads; tid++)
        {
            const auto& tileThreadRecords = _threadTraceRecords[tileId];
            const auto& traceRecordList = tileThreadRecords[tid];

            if (traceRecordList.empty()) { continue; }

            StoreGlobalTid(tid, fs);    // Store Global Thread Identifier

            uint32_t numRecords = (uint32_t)traceRecordList.size();
            Store(numRecords, fs);      // Store #records collected in the thread

            // Store trace records
            for (const auto& r : traceRecordList)
            {
                StoreTraceRecordData((const uint8_t*)r.record, fs);
            }
        }
    }
}

void PerfTracePostProcessor::StoreGlobalTid(uint32_t gtid, std::ofstream& fs)
{
    const GtStateRegAccessor& sra = _kernel->GenModel().StateRegAccessor();
    uint32_t sr0 = sra.SetGlobalTid(0, gtid);

    auto storeSr0Field = [&](const ScatteredBitFieldU32& sbf)
    {
        uint32_t val = (sbf.IsEmpty() ? UINT32_MAX : sbf.GetValue(sr0));
        Store(val, fs);
    };

    storeSr0Field(sra.SliceIdField());
    storeSr0Field(sra.DualSubSliceIdField());
    storeSr0Field(sra.SubSliceIdField());
    storeSr0Field(sra.EuIdField());
    storeSr0Field(sra.ThreadSlotField());
}

uint32_t PerfTracePostProcessorFuntime::GetGlobalTid(const uint8_t* traceRecord) const
{
    return _kernel->GenModel().StateRegAccessor().GetGlobalTid((((const PerfTraceFuntimeRecord*)traceRecord)->sr0) & 0xFFFF);
}

uint32_t PerfTracePostProcessorFuntime::GetTileId(const uint8_t* traceRecord) const
{
    return ((const PerfTraceFuntimeRecord*)traceRecord)->tileId;
}

void PerfTracePostProcessorFuntime::StoreTraceRecordData(const uint8_t* traceRecord, std::ofstream& fs) const
{
    const PerfTraceFuntimeRecord* record = (const PerfTraceFuntimeRecord*)traceRecord;
    fs.write((const char*)&record->timeStart, sizeof(PerfTraceFuntimeRecord::timeStart));
    fs.write((const char*)&record->timeEnd, sizeof(PerfTraceFuntimeRecord::timeEnd));
}

uint32_t PerfTracePostProcessorMemoryLatency::GetGlobalTid(const uint8_t* traceRecord) const
{
    return _kernel->GenModel().StateRegAccessor().GetGlobalTid((((const PerfTraceMemoryLatencyRecord*)traceRecord)->sr0) & 0xFFFF);
}

uint32_t PerfTracePostProcessorMemoryLatency::GetTileId(const uint8_t* traceRecord) const
{
    return ((const PerfTraceMemoryLatencyRecord*)traceRecord)->tileId;
}

void PerfTracePostProcessorMemoryLatency::StoreTraceRecordData(const uint8_t* traceRecord, std::ofstream& fs) const
{
    const PerfTraceMemoryLatencyRecord* record = (const PerfTraceMemoryLatencyRecord*)traceRecord;
    fs.write((const char*)&record->cycles, sizeof(PerfTraceMemoryLatencyRecord::cycles));
}

/* ============================================================================================= */
// GTPin_Entry
/* ============================================================================================= */
EXPORT_C_FUNC void GTPin_Entry(int argc, const char *argv[])
{
    ConfigureGTPin(argc, argv);
    GTPIN_ASSERT_MSG((gKnobMode == 0 || gKnobMode == 1 || gKnobMode == 2), "PERFTRACE: Invalid mode value. Should be 0, 1 or 2, provided " + std::to_string(gKnobMode));

    if (gKnobPhase == 1)
    {
        PerfTracePreProcessor::Instance()->Register();
        atexit(PerfTracePreProcessor::OnFini);
    }
    else
    {
        GTPIN_ASSERT_MSG((gKnobPhase == 2), "PERFTRACE: Invalid phase value. Should be 1 or 2, provided " + std::to_string(gKnobPhase));

        if (gKnobMode == PERFTRACE_FUNTIME || gKnobMode == PERFTRACE_OCCUPANCY)
        {
            PerfTraceFuntime::Instance()->Register();
            atexit(PerfTraceFuntime::OnFini);
        }
        else if (gKnobMode == PERFTRACE_MEMLATENCY)
        {
            PerfTraceMemoryLatency::Instance()->Register();
            atexit(PerfTraceMemoryLatency::OnFini);
        }
        else
        {
            GTPIN_ASSERT(0);
        }
    }
}
