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

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

/*!
 * @file Library of High-Level Instrumentation (HLI) functions used by the cachelineprof tool
 */

#include "hlif_basic_defs.h"
#include "cachelineprof.h"


#define OPTIMIZED

static inline uint32_t InsertCacheline32(uint32_t* ids, uint32_t cachelineId, uint32_t nextFreeIndex)
{
    if (nextFreeIndex >= 63)
    {
        return nextFreeIndex;
    }
    
    for (uint32_t i = 0; i < nextFreeIndex; i++)
    {
        if (ids[i] == cachelineId)
        {
            return nextFreeIndex;
        }
    }
    ids[nextFreeIndex] = cachelineId;
    
    return nextFreeIndex + 1;
} 

static inline uint32_t InsertCacheline64(uint64_t* ids, uint64_t cachelineId, uint32_t nextFreeIndex)
{
    if (nextFreeIndex > 63)
    {
        return nextFreeIndex;
    }
    
    for (uint32_t i = 0; i < nextFreeIndex; i++)
    {
        if (ids[i] == cachelineId)
        {
            return nextFreeIndex;
        }
    }
    ids[nextFreeIndex] = cachelineId;
    
    return nextFreeIndex + 1;
} 


/*!
 * @brief HLI function that detects amount of cachelines accessed by a scatter SEND instruction to global or local memory done in A32 or BTS modes
 * @param[in]       addresses           Array of 32-bit addresses/offsets
 * @param[in]       accessMask          Per-channel mask of memory accesses
 * @param[in][out]  CachelineProfArgs   Information about memory access instruction and the resulting cachelines histogram
 */
IGC_STACK_CALL void CheckScatterA32Access(__global const uint32_t* addresses,
                                          uint32_t accessMask,
                                          __global CachelineProfArgs* cachelineProfArgs)
{
    uint32_t cacheLineIds[MAX_SUPPORTED_CACHE_LINE_HISTOGRAM_SIZE];
    uint32_t detectedCachelines = 0;

    if (accessMask != 0)
    {
        uint32_t numOfElements = cachelineProfArgs->in.numAccesses;
        uint32_t dataSize      = cachelineProfArgs->in.dataSize;
        uint32_t shiftBits     = cachelineProfArgs->in.log2CachelineSize;
        uint32_t CACHELINE_SIZE_IN_BYTES = 1 << shiftBits;

        for (uint32_t eIndx = 0; eIndx != numOfElements; ++eIndx)
        {
            if ((accessMask & (0x1 << eIndx)) != 0)
            {
                uint32_t firstByteAddr = addresses[eIndx];
                uint32_t lastByteAddr  = firstByteAddr + dataSize - 1;

                for (; firstByteAddr < lastByteAddr; firstByteAddr += CACHELINE_SIZE_IN_BYTES)
                {
                    detectedCachelines = InsertCacheline32(cacheLineIds, firstByteAddr >> shiftBits, detectedCachelines);
                }
                detectedCachelines = InsertCacheline32(cacheLineIds, lastByteAddr >> shiftBits, detectedCachelines);
            }
            if (detectedCachelines == MAX_SUPPORTED_CACHE_LINE_HISTOGRAM_SIZE)
            {
                atomic_inc(&(cachelineProfArgs->out.outOfHistogramValues));
                return;
            }
        }
        atomic_inc(&(cachelineProfArgs->out.cachelineHistogram[detectedCachelines]));
    }
}

/*!
 * @brief HLI function that detects amount of cachelines accessed by a scatter SEND instruction to global memory done in A64 mode
 * @param[in]       addresses           Array of 64-bit addresses
 * @param[in]       accessMask          Per-channel mask of memory accesses
 * @param[in][out]  CachelineProfArgs   Information about memory access instruction and the resulting cachelines histogram
 */
IGC_STACK_CALL void CheckScatterA64Access(__global const uint64_t* addresses,
                                          uint32_t accessMask,
                                          __global CachelineProfArgs* cachelineProfArgs)
{
    uint64_t cacheLineIds[MAX_SUPPORTED_CACHE_LINE_HISTOGRAM_SIZE];
    uint32_t detectedCachelines = 0;

    if (accessMask != 0)
    {
        uint32_t numOfElements = cachelineProfArgs->in.numAccesses;
        uint32_t dataSize      = cachelineProfArgs->in.dataSize;
        uint32_t shiftBits     = cachelineProfArgs->in.log2CachelineSize;
        uint32_t CACHELINE_SIZE_IN_BYTES = 1 << shiftBits;

        for (uint32_t eIndx = 0; eIndx != numOfElements; ++eIndx)
        {
            if ((accessMask & (0x1 << eIndx)) != 0)
            {
                uint64_t firstByteAddr = addresses[eIndx];
                uint64_t lastByteAddr  = firstByteAddr + dataSize - 1;

                for (; firstByteAddr < lastByteAddr; firstByteAddr += CACHELINE_SIZE_IN_BYTES)
                {
                    detectedCachelines = InsertCacheline32(cacheLineIds, firstByteAddr >> shiftBits, detectedCachelines);
                }
                detectedCachelines = InsertCacheline32(cacheLineIds, lastByteAddr >> shiftBits, detectedCachelines);
            }
            if (detectedCachelines == MAX_SUPPORTED_CACHE_LINE_HISTOGRAM_SIZE)
            {
                atomic_inc(&(cachelineProfArgs->out.outOfHistogramValues));
                return;
            }
        }
        atomic_inc(&(cachelineProfArgs->out.cachelineHistogram[detectedCachelines]));
    }
}


#ifdef OPTIMIZED
static inline void SetBits(__global uint32_t* dwords, uint64_t fromAddr, uint64_t toAddr)
{
    uint64_t fromDword = fromAddr >> 5;
    uint64_t toDword   = toAddr >> 5;

    if (fromDword == toDword)
    {
        uint32_t from = fromAddr & 0x1F;
        uint32_t to   = toAddr & 0x1F;
        uint32_t bits = (((uint32_t)1 << (to - from + 1)) - 1) << to;
        atomic_or(&dwords[fromDword], bits);
        return;
    }

    // Set the first dword
    {
        uint32_t bit = fromAddr & 0x1F;
        atomic_or(&dwords[fromDword], (uint32_t)0xFFFFFFFF << bit);
    }

    // Set the middle dwords
    for (uint64_t dword = fromDword + 1; dword < toDword; dword++)
    {
        atomic_or(&dwords[dword], (uint32_t)0xFFFFFFFF);
    }

    // Set the last dword
    {
        uint32_t bit = toAddr & 0x1F;
        atomic_or(&dwords[toDword], (uint32_t)0xFFFFFFFF >> (31 - bit));
    }
}
#else
static inline void SetBits(__global uint32_t* dwords, uint64_t fromAddr, uint64_t toAddr)
{
    for (uint64_t line = fromAddr; line <= toAddr; line++)
    {
        uint64_t dword = line >> 5;
        uint32_t bit   = line & 0x1F;
        atomic_or(&dwords[dword], (uint32_t)1 << bit);
    }
}
#endif

/*!
 * @brief HLI function sets bits within a bitvector that correspond to accessed cachelines
 * @param[in]       addresses            Array of 64-bit addresses
 * @param[in]       accessMask           Per-channel mask of memory accesses
 * @param[in,out]   memoryFootprintArgs  Information about memory access
 * @param[out]      bitvector            Resulting per-cacheline bitvector
 */
IGC_STACK_CALL void GlobalMemoryFootprintA64Access(__global MemoryFootprintArgsCombo* memoryFootprintArgsCombo)
{
    __global uint64_t*            addresses             = memoryFootprintArgsCombo->addresses;
    uint32_t                      accessMask            = memoryFootprintArgsCombo->accessMask;
    uint32_t                      dataSize              = memoryFootprintArgsCombo->dataSize;
    __global MemoryFootprintArgs* memoryFootprintArgs   = memoryFootprintArgsCombo->memoryFootprintArgs;
    __global uint32_t*            bitvector             = (__global uint32_t*)memoryFootprintArgsCombo->bitvector;
    __global uint32_t*            bitvector4Reads       = bitvector;
    __global uint32_t*            bitvector4Writes      = (__global uint32_t*)((__global uint8_t*)bitvector + (memoryFootprintArgs->in.bitVectorSizeInBytes >> 1));

    if (accessMask != 0)
    {
        uint32_t shiftBits                  = memoryFootprintArgs->in.log2CachelineSize;
        uint64_t supportedAddressBitsMask   = (((uint64_t)1 << memoryFootprintArgs->in.supportedAddressBits) - 1);
        uint64_t unsupportedAddressBitsMask = ~supportedAddressBitsMask;
        uint64_t upperAddressBits = 0;

        for (uint32_t eIndx = 0; eIndx != 32; ++eIndx)
        {
            if ((accessMask & (0x1 << eIndx)) != 0)
            {
                uint64_t firstByteAddr        = addresses[eIndx];
                uint64_t lastByteAddr         = firstByteAddr + dataSize - 1;

                upperAddressBits = (firstByteAddr & unsupportedAddressBitsMask);

                firstByteAddr &= supportedAddressBitsMask;
                lastByteAddr  &= supportedAddressBitsMask;
                
                uint64_t firstByteCacheLineId = firstByteAddr >> shiftBits;
                uint64_t lastByteCacheLineId  = lastByteAddr >> shiftBits;

                if (memoryFootprintArgsCombo->accessType == Store)
                {
                    SetBits(bitvector4Writes, firstByteCacheLineId, lastByteCacheLineId);
                }
                else
                {
                    SetBits(bitvector4Reads, firstByteCacheLineId, lastByteCacheLineId);
                }

                if (memoryFootprintArgsCombo->accessType == ReadModifyWrite)
                {
                    SetBits(bitvector4Writes, firstByteCacheLineId, lastByteCacheLineId);
                }
            }
        }

        memoryFootprintArgs->out.upperAddressBits = upperAddressBits;
    }
}

/*!
 * @brief HLI function sets bits within a bitvector that correspond to accessed cachelines
 * @param[in]       addresses            Array of 64-bit addresses
 * @param[in]       accessMask           Per-channel mask of memory accesses
 * @param[in,out]   memoryFootprintArgs  Information about memory access
 * @param[out]      bitvector            Resulting per-cacheline bitvector
 */
IGC_STACK_CALL void GlobalMemoryFootprintBlock2DAccess(__global MemoryFootprintArgsCombo* memoryFootprintArgsCombo)
{
    if ((memoryFootprintArgsCombo->accessMask & 0x1) == 0)
    {
        return;
    }

    // exec_size is ignored for this message

    __global Block2D*             block2d               = memoryFootprintArgsCombo->addresses;
    uint32_t                      dataSize              = memoryFootprintArgsCombo->dataSize;
    __global MemoryFootprintArgs* memoryFootprintArgs   = memoryFootprintArgsCombo->memoryFootprintArgs;
    __global uint32_t*            bitvector             = (__global uint32_t*)memoryFootprintArgsCombo->bitvector;
    __global uint32_t*            bitvector4Reads       = bitvector;
    __global uint32_t*            bitvector4Writes      = (__global uint32_t*)((__global uint8_t*)bitvector + (memoryFootprintArgs->in.bitVectorSizeInBytes >> 1));

    uint32_t shiftBits                  = memoryFootprintArgs->in.log2CachelineSize;
    uint64_t supportedAddressBitsMask   = (((uint64_t)1 << memoryFootprintArgs->in.supportedAddressBits) - 1);
    uint64_t unsupportedAddressBitsMask = ~supportedAddressBitsMask;
    uint64_t upperAddressBits = 0;

    uint64_t surfaceBaseAddress = block2d->surface_base_address;
    uint32_t surfaceHeight      = (block2d->surface_height & 0x00FFFFFF) + 1;  // value -1-encoded
    uint32_t surfaceWidth       = (block2d->surface_width & 0x00FFFFFF) + 1;   // value -1-encoded
    uint32_t surfacePitch       = (block2d->surface_pitch & 0x00FFFFFF) + 1;   // value -1-encoded

    uint32_t numBlocks          = (block2d->array_length + 1);                 // value -1-encoded
    uint32_t blockHeight        = (block2d->block_height + 1);                 // value -1-encoded
    uint32_t blockWidth         = (block2d->block_width + 1);                  // value -1-encoded
    int32_t  blockStartX        = block2d->block_start_x;
    int32_t  blockStartY        = block2d->block_start_y;

    uint32_t lastY = blockStartY + blockHeight - 1;
    if (lastY >= surfaceHeight)
    {
        lastY = surfaceHeight - 1;
    }

    uint32_t lastX = blockStartX + numBlocks * blockWidth - 1;
    if (lastX * dataSize >= surfaceWidth)
    {
        lastX = (surfaceWidth / dataSize) - 1;
    }

    for (uint32_t y = blockStartY; y <= lastY; y++)
    {
        uint64_t firstByteAddr = surfaceBaseAddress + y * surfacePitch + blockStartX * dataSize;
        uint64_t lastByteAddr  = firstByteAddr + dataSize * lastX - 1;

        upperAddressBits = (firstByteAddr & unsupportedAddressBitsMask);

        firstByteAddr &= supportedAddressBitsMask;
        lastByteAddr  &= supportedAddressBitsMask;
            
        uint64_t firstByteCacheLineId = firstByteAddr >> shiftBits;
        uint64_t lastByteCacheLineId  = lastByteAddr >> shiftBits;

        if (memoryFootprintArgsCombo->accessType == Store)
        {
            SetBits(bitvector4Writes, firstByteCacheLineId, lastByteCacheLineId);
        }
        else
        {
            SetBits(bitvector4Reads, firstByteCacheLineId, lastByteCacheLineId);
        }

        if (memoryFootprintArgsCombo->accessType == ReadModifyWrite)
        {
            SetBits(bitvector4Writes, firstByteCacheLineId, lastByteCacheLineId);
        }
    }

    memoryFootprintArgs->out.upperAddressBits = upperAddressBits;
}


static inline uint32_t CacheIndexHash(uint64_t lineId, uint64_t hashMask, uint32_t shift)
{
    return (lineId & (hashMask << shift)) >> shift;
}

static inline void SingleLineAccess(uint64_t lineId, uint64_t hashMask, uint32_t shift, __global uint64_t* tags, __global uint64_t* misses, __global uint64_t* hits)
{
    uint32_t bucket = CacheIndexHash(lineId, hashMask, shift);
    uint64_t prevLineId = atom_xchg(&tags[bucket], lineId);
    if (prevLineId == lineId)
    {
        atom_inc(hits);
    }
    else
    {
        atom_inc(misses);
    }
}

/*!
 * @brief HLI function that models cache and reports the number of hits and misses
 * @param[in]       addresses            Array of 64-bit addresses
 * @param[in]       accessMask           Per-channel mask of memory accesses
 * @param[in]       dataSize             Data size per single access
 * @param[in,out]   MemoryCacheModelArgs Information about cache model
 * @param[in]       cacheTagsVector      Vector of cache buckets tags
 */
IGC_STACK_CALL void GlobalMemoryCacheModelA64Access(__global MemoryCacheModelArgsCombo* memoryCacheModelArgsCombo)
{
    __global uint64_t*             addresses             = memoryCacheModelArgsCombo->addresses;
    uint32_t                       accessMask            = memoryCacheModelArgsCombo->accessMask;
    uint32_t                       dataSize              = memoryCacheModelArgsCombo->dataSize;
    __global MemoryCacheModelArgs* memoryCacheModelArgs  = memoryCacheModelArgsCombo->memoryCacheModelArgs;
    __global uint64_t*             tags                  = (__global uint64_t*)memoryCacheModelArgsCombo->cacheTagsVector;
    uint32_t                       shiftBits             = memoryCacheModelArgs->in.log2CachelineSize;
    uint64_t                       numOfBuckets          = memoryCacheModelArgs->in.cacheSize >> shiftBits;
    uint64_t                       hashMask              = (numOfBuckets - 1);
    uint32_t                       shiftHashMaskBits     = memoryCacheModelArgs->in.hashMaskShift;
    __global uint64_t*             misses                = (__global uint64_t*)&memoryCacheModelArgs->out.misses;
    __global uint64_t*             hits                  = (__global uint64_t*)&memoryCacheModelArgs->out.hits;

    if (accessMask != 0)
    {
        for (uint32_t eIndx = 0; eIndx != 32; ++eIndx)
        {
            if ((accessMask & (0x1 << eIndx)) != 0)
            {
                uint64_t firstByteAddr        = addresses[eIndx];
                uint64_t lastByteAddr         = firstByteAddr + dataSize - 1;

                uint64_t firstByteCacheLineId = firstByteAddr >> shiftBits;
                uint64_t lastByteCacheLineId  = lastByteAddr >> shiftBits;

                for (uint64_t lineId = firstByteCacheLineId; lineId <= lastByteCacheLineId; lineId++)
                {
                    SingleLineAccess(lineId, hashMask, shiftHashMaskBits, tags, misses, hits);
                }
            }
        }
    }
}

/*!
 * @brief HLI function that models cache and reports the number of hits and misses
 * @param[in]       addresses            Array of 64-bit addresses
 * @param[in]       accessMask           Per-channel mask of memory accesses
 * @param[in]       dataSize             Data size per single access
 * @param[in,out]   MemoryCacheModelArgs Information about cache model
 * @param[in]       cacheTagsVector      Vector of cache buckets tags
 */
IGC_STACK_CALL void GlobalMemoryCacheModelBlock2DAccess(__global MemoryCacheModelArgsCombo* memoryCacheModelArgsCombo)
{
    if ((memoryCacheModelArgsCombo->accessMask & 0x1) == 0)
    {
        return;
    }

    // exec_size is ignored for this message

    __global Block2D*              block2d               = memoryCacheModelArgsCombo->addresses;
    uint32_t                       dataSize              = memoryCacheModelArgsCombo->dataSize;
    __global MemoryCacheModelArgs* memoryCacheModelArgs  = memoryCacheModelArgsCombo->memoryCacheModelArgs;
    __global uint64_t*             tags                  = (__global uint64_t*)memoryCacheModelArgsCombo->cacheTagsVector;
    uint32_t                       shiftBits             = memoryCacheModelArgs->in.log2CachelineSize;
    uint64_t                       numOfBuckets          = memoryCacheModelArgs->in.cacheSize >> shiftBits;
    uint64_t                       hashMask              = (numOfBuckets - 1);
    uint32_t                       shiftHashMaskBits     = memoryCacheModelArgs->in.hashMaskShift;
    __global uint64_t*             misses                = (__global uint64_t*)&memoryCacheModelArgs->out.misses;
    __global uint64_t*             hits                  = (__global uint64_t*)&memoryCacheModelArgs->out.hits;

    uint64_t surfaceBaseAddress = block2d->surface_base_address;
    uint32_t surfaceHeight      = (block2d->surface_height & 0x00FFFFFF) + 1;  // value -1-encoded
    uint32_t surfaceWidth       = (block2d->surface_width & 0x00FFFFFF) + 1;   // value -1-encoded
    uint32_t surfacePitch       = (block2d->surface_pitch & 0x00FFFFFF) + 1;   // value -1-encoded

    uint32_t numBlocks          = (block2d->array_length + 1);                 // value -1-encoded
    uint32_t blockHeight        = (block2d->block_height + 1);                 // value -1-encoded
    uint32_t blockWidth         = (block2d->block_width + 1);                  // value -1-encoded
    int32_t  blockStartX        = block2d->block_start_x;
    int32_t  blockStartY        = block2d->block_start_y;

    uint32_t lastY = blockStartY + blockHeight - 1;
    if (lastY >= surfaceHeight)
    {
        lastY = surfaceHeight - 1;
    }

    uint32_t lastX = blockStartX + numBlocks * blockWidth - 1;
    if (lastX * dataSize >= surfaceWidth)
    {
        lastX = (surfaceWidth / dataSize) - 1;
    }

    for (uint32_t y = blockStartY; y <= lastY; y++)
    {
        uint64_t firstByteAddr = surfaceBaseAddress + y * surfacePitch + blockStartX * dataSize;
        uint64_t lastByteAddr  = firstByteAddr + dataSize * lastX - 1;

        uint64_t firstByteCacheLineId = firstByteAddr >> shiftBits;
        uint64_t lastByteCacheLineId  = lastByteAddr >> shiftBits;

        for (uint64_t lineId = firstByteCacheLineId; lineId <= lastByteCacheLineId; lineId++)
        {
            SingleLineAccess(lineId, hashMask, shiftHashMaskBits, tags, misses, hits);
        }
    }
}

/*!
 * @brief HLI function checks type of accesses (RaR, WaR, WaW, RaW) 
 * @param[in]       addresses                      Array of 64-bit addresses
 * @param[in]       accessMask                     Per-channel mask of memory accesses
 * @param[in]       dataSize                       Data size per single access
 * @param[in]       accessType                     Type of access (load, store, atimic)
 * @param[in,out]   memoryAccessAnalysisArgsCombo  Information about memory access
 * @param[out]      bitvector                      Resulting per-cacheline bitvector
 */          
IGC_STACK_CALL void GlobalMemoryAccessAnalysisA64Access(__global MemoryAccessAnalysisArgsCombo* memoryAccessAnalysisArgsCombo)
{
    __global uint64_t*                 addresses                = memoryAccessAnalysisArgsCombo->addresses;
    uint32_t                           accessMask               = memoryAccessAnalysisArgsCombo->accessMask;
    uint32_t                           dataSize                 = memoryAccessAnalysisArgsCombo->dataSize;
    uint32_t                           accessType               = memoryAccessAnalysisArgsCombo->accessType;
    __global MemoryAccessAnalysisArgs* memoryAccessAnalysisArgs = memoryAccessAnalysisArgsCombo->memoryAccessAnalysisArgs;
    __global uint32_t*                 bitvector                = (__global uint32_t*)memoryAccessAnalysisArgsCombo->bitvector;
    __global uint64_t*                 rarCount                 = (__global uint64_t*)&memoryAccessAnalysisArgs->out.rarCount;
    __global uint64_t*                 rawCount                 = (__global uint64_t*)&memoryAccessAnalysisArgs->out.rawCount;
    __global uint64_t*                 warCount                 = (__global uint64_t*)&memoryAccessAnalysisArgs->out.warCount;
    __global uint64_t*                 wawCount                 = (__global uint64_t*)&memoryAccessAnalysisArgs->out.wawCount;

    if (accessMask != 0)
    {
        uint32_t shiftBits                  = memoryAccessAnalysisArgs->in.log2CachelineSize;
        uint64_t supportedAddressBitsMask   = (((uint64_t)1 << memoryAccessAnalysisArgs->in.supportedAddressBits) - 1);

        for (uint32_t eIndx = 0; eIndx != 32; ++eIndx)
        {
            if ((accessMask & (0x1 << eIndx)) != 0)
            {
                uint64_t firstByteAddr = addresses[eIndx];
                uint64_t lastByteAddr  = firstByteAddr + dataSize - 1;

                firstByteAddr &= supportedAddressBitsMask;
                lastByteAddr  &= supportedAddressBitsMask;
                
                uint64_t firstByteCacheLineId = firstByteAddr >> shiftBits;
                uint64_t lastByteCacheLineId  = lastByteAddr >> shiftBits;

                for (uint64_t lineId = firstByteCacheLineId; lineId <= lastByteCacheLineId; lineId++)
                {
                    uint64_t dword = lineId >> 5;
                    uint32_t bit   = lineId & 0x1F;

                    uint32_t bitMask = (uint32_t)1 << bit;
                    uint32_t prev = (accessType == Store || accessType == ReadModifyWrite) ? atomic_or(&bitvector[dword], bitMask) : atomic_and(&bitvector[dword], ~bitMask);

                    uint32_t prevBit = prev & bitMask;

                    if (accessType == Store)
                    {
                        if (prevBit == 0)
                        {
                            atom_inc(warCount);
                        }
                        else
                        {
                            atom_inc(wawCount);
                        }
                    }
                    else
                    {
                        if (prevBit == 0)
                        {
                            atom_inc(rarCount);
                        }
                        else
                        {
                            atom_inc(rawCount);
                        }
                    }
                }

                if (accessType == ReadModifyWrite)
                {
                    atom_add(warCount, (lastByteCacheLineId - firstByteCacheLineId + 1));
                }
            }
        }
    }
}

/*!
 * @brief HLI function checks type of access (RaR, WaR, WaW, RaW) for load_block2d/store_block2d messages
 * @param[in]       addresses            Array of 64-bit addresses
 * @param[in]       accessMask           Per-channel mask of memory accesses
 * @param[in]       dataSize             Data size
 * @param[in]       accessType           Type of access
 * @param[in,out]   memoryFootprintArgs  Information about memory access
 * @param[out]      bitvector            Resulting per-cacheline bitvector
 */
IGC_STACK_CALL void GlobalMemoryAccessAnalysisBlock2DAccess(__global MemoryAccessAnalysisArgsCombo* memoryAccessAnalysisArgsCombo)
{
    if ((memoryAccessAnalysisArgsCombo->accessMask & 0x1) == 0)
    {
        return;
    }

    // exec_size is ignored for this message

    __global Block2D*                  block2d                  = memoryAccessAnalysisArgsCombo->addresses;
    uint32_t                           dataSize                 = memoryAccessAnalysisArgsCombo->dataSize;
    uint32_t                           accessType               = memoryAccessAnalysisArgsCombo->accessType;
    __global MemoryAccessAnalysisArgs* memoryAccessAnalysisArgs = memoryAccessAnalysisArgsCombo->memoryAccessAnalysisArgs;
    __global uint32_t*                 bitvector                = (__global uint32_t*)memoryAccessAnalysisArgsCombo->bitvector;
    __global uint64_t*                 rarCount                 = (__global uint64_t*)&memoryAccessAnalysisArgs->out.rarCount;
    __global uint64_t*                 rawCount                 = (__global uint64_t*)&memoryAccessAnalysisArgs->out.rawCount;
    __global uint64_t*                 warCount                 = (__global uint64_t*)&memoryAccessAnalysisArgs->out.warCount;
    __global uint64_t*                 wawCount                 = (__global uint64_t*)&memoryAccessAnalysisArgs->out.wawCount;

    uint32_t shiftBits                = memoryAccessAnalysisArgs->in.log2CachelineSize;
    uint64_t supportedAddressBitsMask = (((uint64_t)1 << memoryAccessAnalysisArgs->in.supportedAddressBits) - 1);

    uint64_t surfaceBaseAddress       = block2d->surface_base_address;
    uint32_t surfaceHeight            = (block2d->surface_height & 0x00FFFFFF) + 1;  // value -1-encoded
    uint32_t surfaceWidth             = (block2d->surface_width & 0x00FFFFFF) + 1;   // value -1-encoded
    uint32_t surfacePitch             = (block2d->surface_pitch & 0x00FFFFFF) + 1;   // value -1-encoded
                                      
    uint32_t numBlocks                = (block2d->array_length + 1);                 // value -1-encoded
    uint32_t blockHeight              = (block2d->block_height + 1);                 // value -1-encoded
    uint32_t blockWidth               = (block2d->block_width + 1);                  // value -1-encoded
    int32_t  blockStartX              = block2d->block_start_x;
    int32_t  blockStartY              = block2d->block_start_y;

    uint32_t lastY = blockStartY + blockHeight - 1;
    if (lastY >= surfaceHeight)
    {
        lastY = surfaceHeight - 1;
    }

    uint32_t lastX = blockStartX + numBlocks * blockWidth - 1;
    if (lastX * dataSize >= surfaceWidth)
    {
        lastX = (surfaceWidth / dataSize) - 1;
    }

    for (uint32_t y = blockStartY; y <= lastY; y++)
    {
        uint64_t firstByteAddr = surfaceBaseAddress + y * surfacePitch + blockStartX * dataSize;
        uint64_t lastByteAddr  = firstByteAddr + dataSize * lastX - 1;

        firstByteAddr &= supportedAddressBitsMask;
        lastByteAddr  &= supportedAddressBitsMask;
            
        uint64_t firstByteCacheLineId = firstByteAddr >> shiftBits;
        uint64_t lastByteCacheLineId  = lastByteAddr >> shiftBits;

        for (uint64_t lineId = firstByteCacheLineId; lineId <= lastByteCacheLineId; lineId++)
        {
            uint64_t dword = lineId >> 5;
            uint32_t bit   = lineId & 0x1F;

            uint32_t bitMask = (uint32_t)1 << bit;
            uint32_t prev = (accessType == Store || accessType == ReadModifyWrite) ? atomic_or(&bitvector[dword], bitMask) : atomic_and(&bitvector[dword], ~bitMask);

            uint32_t prevBit = prev & bitMask;

            if (accessType == Store)
            {
                if (prevBit == 0)
                {
                    atom_inc(warCount);
                }
                else
                {
                    atom_inc(wawCount);
                }
            }
            else
            {
                if (prevBit == 0)
                {
                    atom_inc(rarCount);
                }
                else
                {
                    atom_inc(rawCount);
                }
            }
        }

        if (accessType == ReadModifyWrite)
        {
            atom_add(warCount, (lastByteCacheLineId - firstByteCacheLineId + 1));
        }
    }
}

/*!
 * @brief HLI function checks for WaW accesses from different HW threads for A64 access
 * @param[in]       addresses                      Array of 64-bit addresses
 * @param[in]       accessMask                     Per-channel mask of memory accesses
 * @param[in]       dataSize                       Data size per single access
 * @param[in]       accessType                     Type of access
 * @param[in,out]   memoryAccessAnalysisArgsCombo  Information about memory access
 * @param[out]      bitvector                      Resulting per-cacheline bitvector
 */
IGC_STACK_CALL void GlobalMemoryRaceDetectionA64Access(__global MemoryRaceDetectionArgsCombo* memoryRaceDetectionArgsCombo)
{
    __global uint64_t*                addresses                = memoryRaceDetectionArgsCombo->addresses;
    uint32_t                          accessMask               = memoryRaceDetectionArgsCombo->accessMask;
    uint32_t                          dataSize                 = memoryRaceDetectionArgsCombo->dataSize;
    uint32_t                          accessType               = memoryRaceDetectionArgsCombo->accessType;
    uint32_t                          hwThreadId               = memoryRaceDetectionArgsCombo->hwThreadId;
    __global MemoryRaceDetectionArgs* memoryRaceDetectionArgs  = memoryRaceDetectionArgsCombo->memoryRaceDetectionArgs;
    __global uint32_t*                raceDataVector           = (__global uint32_t*)memoryRaceDetectionArgsCombo->raceDataVector;
    __global uint32_t*                bitvector                = (__global uint32_t*)memoryRaceDetectionArgsCombo->bitvector;
    __global uint64_t*                raceCount                = (__global uint64_t*)&memoryRaceDetectionArgs->out.raceCount;

    if (accessMask != 0)
    {
        uint32_t shiftBits                  = memoryRaceDetectionArgs->in.log2CachelineSize;
        uint64_t supportedAddressBitsMask   = (((uint64_t)1 << memoryRaceDetectionArgs->in.supportedAddressBits) - 1);
        uint64_t unsupportedAddressBitsMask = ~supportedAddressBitsMask;
        uint64_t upperAddressBits = 0;

        for (uint32_t eIndx = 0; eIndx != 32; ++eIndx)
        {
            if ((accessMask & (0x1 << eIndx)) != 0)
            {
                uint64_t firstByteAddr = addresses[eIndx];
                uint64_t lastByteAddr  = firstByteAddr + dataSize - 1;

                upperAddressBits = (firstByteAddr & unsupportedAddressBitsMask);

                firstByteAddr &= supportedAddressBitsMask;
                lastByteAddr  &= supportedAddressBitsMask;

                uint64_t firstByteCacheLineId = firstByteAddr >> shiftBits;
                uint64_t lastByteCacheLineId  = lastByteAddr >> shiftBits;

                for (uint64_t lineId = firstByteCacheLineId; lineId <= lastByteCacheLineId; lineId++)
                {
                    uint32_t currHwThreadId = hwThreadId & 0xFFFF;
                    uint32_t mask           = currHwThreadId | ((accessType != Load) ? 0x80000000 : 0);
                    uint32_t prevMask       = atomic_xchg(&raceDataVector[lineId], mask);
                    uint32_t prevBit        = (prevMask == 0xFFFFFFFF) ? 0 : (prevMask & 0x80000000);
                    uint32_t prevHwThreadId = prevMask & 0xFFFF;

                    if (accessType == Store)
                    {
                        if ((prevBit != 0) && prevHwThreadId != currHwThreadId)
                        {
                            // if got here a race condition was detected
                            atom_inc(raceCount);
                            atomic_or(&bitvector[lineId >> 5], (1 << (lineId & 0x1F)));
                        }
                    }

                }
            }
        }

        memoryRaceDetectionArgs->out.upperAddressBits = upperAddressBits;

    }
}

/*!
 * @brief HLI function that counts memory access patterns (RaR, RaW, WaR, WaW) and checks whether WaW accesses from different HW threads for A64 access
 * @param[in]       addresses                      Array of 64-bit addresses
 * @param[in]       accessMask                     Per-channel mask of memory accesses
 * @param[in]       dataSize                       Data size per single access
 * @param[in]       accessType                     Type of access
 * @param[in,out]   memoryAccessAnalysisArgsCombo  Information about memory access
 * @param[out]      bitvector                      Resulting per-cacheline bitvector
 */
 IGC_STACK_CALL void GlobalMemoryAccessAnalysisAndRaceDetectionA64Access(__global MemoryRaceDetectionArgsCombo* memoryRaceDetectionArgsCombo)
{
    __global uint64_t*                addresses                = memoryRaceDetectionArgsCombo->addresses;
    uint32_t                          accessMask               = memoryRaceDetectionArgsCombo->accessMask;
    uint32_t                          dataSize                 = memoryRaceDetectionArgsCombo->dataSize;
    uint32_t                          accessType               = memoryRaceDetectionArgsCombo->accessType;
    uint32_t                          hwThreadId               = memoryRaceDetectionArgsCombo->hwThreadId;
    __global MemoryRaceDetectionArgs* memoryRaceDetectionArgs  = memoryRaceDetectionArgsCombo->memoryRaceDetectionArgs;
    __global uint32_t*                raceDataVector           = (__global uint32_t*)memoryRaceDetectionArgsCombo->raceDataVector;
    __global uint32_t*                bitvector                = (__global uint32_t*)memoryRaceDetectionArgsCombo->bitvector;
    __global uint64_t*                raceCount                = (__global uint64_t*)&memoryRaceDetectionArgs->out.raceCount;
    __global uint64_t*                rarCount                = (__global uint64_t*)&memoryRaceDetectionArgs->out.rarCount;
    __global uint64_t*                rawCount                = (__global uint64_t*)&memoryRaceDetectionArgs->out.rawCount;
    __global uint64_t*                warCount                = (__global uint64_t*)&memoryRaceDetectionArgs->out.warCount;
    __global uint64_t*                wawCount                = (__global uint64_t*)&memoryRaceDetectionArgs->out.wawCount;

    if (accessMask != 0)
    {
        uint32_t shiftBits                  = memoryRaceDetectionArgs->in.log2CachelineSize;
        uint64_t supportedAddressBitsMask   = (((uint64_t)1 << memoryRaceDetectionArgs->in.supportedAddressBits) - 1);
        uint64_t unsupportedAddressBitsMask = ~supportedAddressBitsMask;
        uint64_t upperAddressBits = 0;

        for (uint32_t eIndx = 0; eIndx != 32; ++eIndx)
        {
            if ((accessMask & (0x1 << eIndx)) != 0)
            {
                uint64_t firstByteAddr = addresses[eIndx];
                uint64_t lastByteAddr  = firstByteAddr + dataSize - 1;

                upperAddressBits = (firstByteAddr & unsupportedAddressBitsMask);

                firstByteAddr &= supportedAddressBitsMask;
                lastByteAddr  &= supportedAddressBitsMask;

                uint64_t firstByteCacheLineId = firstByteAddr >> shiftBits;
                uint64_t lastByteCacheLineId  = lastByteAddr >> shiftBits;

                for (uint64_t lineId = firstByteCacheLineId; lineId <= lastByteCacheLineId; lineId++)
                {
                    uint32_t currHwThreadId = hwThreadId & 0xFFFF;
                    uint32_t mask           = currHwThreadId | ((accessType != Load) ? 0x80000000 : 0);
                    uint32_t prevMask       = atomic_xchg(&raceDataVector[lineId], mask);
                    uint32_t prevBit        = (prevMask == 0xFFFFFFFF) ? 0 : (prevMask & 0x80000000);
                    uint32_t prevHwThreadId = prevMask & 0xFFFF;

                    if (accessType == Store)
                    {
                        if ((prevBit != 0))
                        {
                            if (prevHwThreadId != currHwThreadId)
                            {
                                // if got here a race condition was detected
                                atom_inc(raceCount);
                                atomic_or(&bitvector[lineId >> 5], (1 << (lineId & 0x1F)));
                            }
                            atom_inc(wawCount);
                        }
                        else
                        {
                            atom_inc(warCount);
                        }
                    }
                    else
                    {
                        if (prevBit != 0)
                        {
                            atom_inc(rawCount);
                        }
                        else
                        {
                            atom_inc(rarCount);
                        }
                    }
                }

                if (accessType == ReadModifyWrite)
                {
                    atom_add(warCount, (lastByteCacheLineId - firstByteCacheLineId + 1));
                }
            }
        }

        memoryRaceDetectionArgs->out.upperAddressBits = upperAddressBits;

    }
}
