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

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

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

#include "hlif_basic_defs.h"
#include "bounds_check.h"

/*!
 * @brief HLI function that detects OOB violations in the FLAT A64 memory access
 * @see bounds_check.h for details
 */
IGC_STACK_CALL void CheckA64Access(__global const MemBounds* allocatedBuffers,
                                   uint64_t addr,
                                   uint32_t accessMask,
                                   __global BoundsCheckArgs* boundsCheckArgs)
{
    if ((accessMask & 0x1) != 0)
    {
        uint32_t numRanges = allocatedBuffers->numRanges;
        uint32_t dataSize  = boundsCheckArgs->in.dataSize;

        uint32_t rIdx = 0;
        for (; rIdx != numRanges; ++rIdx)
        {
            __global const MemRange* range = &(allocatedBuffers->ranges[rIdx]);
            uintptr_t base = range->base;
            uintptr_t end  = base + range->size;
            if ((addr >= base) && (addr + dataSize <= end))
            {
                return;
            }
        }
        atomic_inc(&(boundsCheckArgs->out.oobCount));
    }
}

/*!
 * @brief HLI function that detects OOB violations in the FLAT A32 memory access
 * @see bounds_check.h for details
 */
IGC_STACK_CALL void CheckA32Access(__global const MemBounds* allocatedBuffers,
                                   uint32_t addr,
                                   uint32_t accessMask,
                                   __global BoundsCheckArgs* boundsCheckArgs)
{
    if ((accessMask & 0x1) != 0)
    {
        uint32_t numRanges = allocatedBuffers->numRanges;
        uint32_t dataSize  = boundsCheckArgs->in.dataSize;

        uint32_t rIdx = 0;
        for (; rIdx != numRanges; ++rIdx)
        {
            __global const MemRange* range = &(allocatedBuffers->ranges[rIdx]);
            uintptr_t base = range->base;
            uintptr_t end  = base + range->size;
            if ((addr >= base) && (addr + dataSize <= end))
            {
                return;
            }
        }
        atomic_inc(&(boundsCheckArgs->out.oobCount));
    }
}

/*!
 * @brief HLI function that detects OOB violations in the surface (BTS/SLM/scratch) memory access
 * @see bounds_check.h for details
 */
IGC_STACK_CALL void CheckSurfaceAccess(uint32_t offset, uint32_t accessMask, __global BoundsCheckArgs* boundsCheckArgs)
{
    if ((accessMask & 0x1) != 0)
    {
        uint32_t dataSize    = boundsCheckArgs->in.dataSize;
        uint32_t surfaceSize = boundsCheckArgs->in.surfaceSize;
        if (offset + dataSize > surfaceSize)
        {
            atomic_inc(&(boundsCheckArgs->out.oobCount));
        }
    }
}

/*!
 * @brief HLI function that detects OOB violations in the scattered FLAT A64 memory access
 * @see bounds_check.h for details
 */
IGC_STACK_CALL void CheckA64ScatteredAccess(__global const MemBounds* allocatedBuffers,
                                            __global const uint64_t* addrPayload,
                                            uint32_t accessMask,
                                            __global BoundsCheckArgs* boundsCheckArgs)
{
    if (accessMask != 0)
    {
        uint32_t numRanges    = allocatedBuffers->numRanges;
        uint32_t numAddresses = boundsCheckArgs->in.numAddresses;
        uint32_t dataSize     = boundsCheckArgs->in.dataSize;

        uint32_t oobCount = 0;

        for (uint32_t aIdx = 0; aIdx != numAddresses; ++aIdx)
        {
            if ((accessMask & (0x1 << aIdx)) != 0)
            {
                uint64_t addr = addrPayload[aIdx];
                uint32_t rIdx = 0;
                for (; rIdx != numRanges; ++rIdx)
                {
                    __global const MemRange* range = &(allocatedBuffers->ranges[rIdx]);
                    uintptr_t base = range->base;
                    uintptr_t end  = base + range->size;
                    if ((addr >= base) && (addr + dataSize <= end))
                    {
                        break;
                    }
                }
                if (rIdx == numRanges)
                {
                    oobCount++;
                }
            }
        }
        if (oobCount)
        {
            atomic_add(&(boundsCheckArgs->out.oobCount), oobCount);
        }
    }
}

/*!
 * @brief HLI function that detects OOB violations in the scattered FLAT A32 memory access
 * @see bounds_check.h for details
 */
IGC_STACK_CALL void CheckA32ScatteredAccess(__global const MemBounds* allocatedBuffers,
                                            __global const uint32_t* addrPayload,
                                            uint32_t accessMask,
                                            __global BoundsCheckArgs* boundsCheckArgs)
{
    if (accessMask != 0)
    {
        uint32_t numRanges    = allocatedBuffers->numRanges;
        uint32_t numAddresses = boundsCheckArgs->in.numAddresses;
        uint32_t dataSize     = boundsCheckArgs->in.dataSize;

        uint32_t oobCount = 0;

        for (uint32_t aIdx = 0; aIdx != numAddresses; ++aIdx)
        {
            if ((accessMask & (0x1 << aIdx)) != 0)
            {
                uint32_t addr = addrPayload[aIdx];
                uint32_t rIdx = 0;
                for (; rIdx != numRanges; ++rIdx)
                {
                    __global const MemRange* range = &(allocatedBuffers->ranges[rIdx]);
                    uintptr_t base = range->base;
                    uintptr_t end  = base + range->size;
                    if ((addr >= base) && (addr + dataSize <= end))
                    {
                        break;
                    }
                }
                if (rIdx == numRanges)
                {
                    oobCount++;
                }
            }
        }
        if (oobCount)
        {
            atomic_add(&(boundsCheckArgs->out.oobCount), oobCount);
        }
    }
}

/*!
 * @brief HLI function that detects OOB violations in the scattered surface (BTS/SLM/scratch) memory access
 * @see bounds_check.h for details
 */
IGC_STACK_CALL void CheckSurfaceScatteredAccess( __global const uint32_t* offsets,
                                                 uint32_t accessMask,
                                                 __global BoundsCheckArgs* boundsCheckArgs)
{
    if (accessMask != 0)
    {
        uint32_t numAddresses = boundsCheckArgs->in.numAddresses;
        uint32_t dataSize     = boundsCheckArgs->in.dataSize;
        uint32_t surfaceSize  = boundsCheckArgs->in.surfaceSize;

        for (uint32_t aIdx = 0; aIdx != numAddresses; ++aIdx)
        {
            if (((accessMask & (0x1 << aIdx)) != 0) && (offsets[aIdx] + dataSize > surfaceSize))
            {
                atomic_inc(&(boundsCheckArgs->out.oobCount));
                return;
            }
        }
    }
}

/*!
 * @brief HLI function that detects OOB violations in 2D block memory access
 *
 * @param[in]       allocatedBuffers Pointer to the memory bounds structure containing allocated ranges
 * @param[in]       block            Pointer to the 2D block structure containing block dimensions and surface details
 * @param[in]       accessMask Mask  Per-channel mask of memory accesses
 * @param[in,out]   boundsCheckArgs  Pointer to the bounds check arguments structure
 *
 */
IGC_STACK_CALL void CheckBlock2DAccess(__global const MemBounds* allocatedBuffers,
                                       __global const Block2D* block,
                                       uint32_t accessMask,
                                       __global BoundsCheckArgs* boundsCheckArgs)
{
    if ((accessMask & 0x1) == 0)
    {
        return;
    }

    uint32_t numRanges          = allocatedBuffers->numRanges;

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

    uint32_t dataSize           = boundsCheckArgs->in.dataSize;

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

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

    // Calculate the first and last byte address of the blocks being accessed
    uint64_t firstByteAddr = surfaceBaseAddress + blockStartY * surfacePitch + blockStartX * dataSize;
    uint64_t lastByteAddr  = surfaceBaseAddress + lastY * surfacePitch + lastX * dataSize -1;

    // Check if the access is within any single range
    for (uint32_t rIdx = 0; rIdx < numRanges; ++rIdx)
    {
        __global const MemRange* range = &(allocatedBuffers->ranges[rIdx]);
        uintptr_t rangeBase = range->base;
        uintptr_t rangeEnd  = rangeBase + range->size; 

        if ((firstByteAddr >= rangeBase) && (lastByteAddr < rangeEnd))
        {
            return;
        }
    }
    atomic_inc(&(boundsCheckArgs->out.oobCount));
}
