//
// Copyright 2021 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// CLDevice.cpp: Implements the cl::Device class.

#include "libANGLE/CLDevice.h"

#include "libANGLE/CLPlatform.h"

#include <cstring>

namespace cl
{

cl_int Device::getInfo(DeviceInfo name, size_t valueSize, void *value, size_t *valueSizeRet) const
{
    static_assert(std::is_same<cl_uint, cl_bool>::value &&
                      std::is_same<cl_uint, cl_device_mem_cache_type>::value &&
                      std::is_same<cl_uint, cl_device_local_mem_type>::value &&
                      std::is_same<cl_uint, cl_version>::value &&
                      std::is_same<cl_ulong, cl_device_type>::value &&
                      std::is_same<cl_ulong, cl_device_fp_config>::value &&
                      std::is_same<cl_ulong, cl_device_exec_capabilities>::value &&
                      std::is_same<cl_ulong, cl_command_queue_properties>::value &&
                      std::is_same<cl_ulong, cl_device_affinity_domain>::value &&
                      std::is_same<cl_ulong, cl_device_svm_capabilities>::value &&
                      std::is_same<cl_ulong, cl_device_atomic_capabilities>::value &&
                      std::is_same<cl_ulong, cl_device_device_enqueue_capabilities>::value,
                  "OpenCL type mismatch");

    cl_uint valUInt   = 0u;
    cl_ulong valULong = 0u;
    size_t valSizeT   = 0u;
    void *valPointer  = nullptr;
    std::vector<char> valString;

    const void *copyValue = nullptr;
    size_t copySize       = 0u;
    cl_int result         = CL_SUCCESS;

    // The info names are sorted within their type group in the order they appear in the OpenCL
    // specification, so it is easier to compare them side-by-side when looking for changes.
    // https://www.khronos.org/registry/OpenCL/specs/3.0-unified/html/OpenCL_API.html#clGetDeviceInfo
    switch (name)
    {
        // Handle all cl_uint and aliased types
        case DeviceInfo::VendorID:
        case DeviceInfo::MaxComputeUnits:
        case DeviceInfo::PreferredVectorWidthChar:
        case DeviceInfo::PreferredVectorWidthShort:
        case DeviceInfo::PreferredVectorWidthInt:
        case DeviceInfo::PreferredVectorWidthLong:
        case DeviceInfo::PreferredVectorWidthFloat:
        case DeviceInfo::PreferredVectorWidthDouble:
        case DeviceInfo::PreferredVectorWidthHalf:
        case DeviceInfo::NativeVectorWidthChar:
        case DeviceInfo::NativeVectorWidthShort:
        case DeviceInfo::NativeVectorWidthInt:
        case DeviceInfo::NativeVectorWidthLong:
        case DeviceInfo::NativeVectorWidthFloat:
        case DeviceInfo::NativeVectorWidthDouble:
        case DeviceInfo::NativeVectorWidthHalf:
        case DeviceInfo::MaxClockFrequency:
        case DeviceInfo::AddressBits:
        case DeviceInfo::MaxReadImageArgs:
        case DeviceInfo::MaxWriteImageArgs:
        case DeviceInfo::MaxReadWriteImageArgs:
        case DeviceInfo::MaxSamplers:
        case DeviceInfo::MaxPipeArgs:
        case DeviceInfo::PipeMaxActiveReservations:
        case DeviceInfo::PipeMaxPacketSize:
        case DeviceInfo::MinDataTypeAlignSize:
        case DeviceInfo::GlobalMemCacheType:
        case DeviceInfo::GlobalMemCachelineSize:
        case DeviceInfo::MaxConstantArgs:
        case DeviceInfo::LocalMemType:
        case DeviceInfo::ErrorCorrectionSupport:
        case DeviceInfo::HostUnifiedMemory:
        case DeviceInfo::EndianLittle:
        case DeviceInfo::Available:
        case DeviceInfo::CompilerAvailable:
        case DeviceInfo::LinkerAvailable:
        case DeviceInfo::QueueOnDevicePreferredSize:
        case DeviceInfo::MaxOnDeviceQueues:
        case DeviceInfo::MaxOnDeviceEvents:
        case DeviceInfo::PreferredInteropUserSync:
        case DeviceInfo::PartitionMaxSubDevices:
        case DeviceInfo::PreferredPlatformAtomicAlignment:
        case DeviceInfo::PreferredGlobalAtomicAlignment:
        case DeviceInfo::PreferredLocalAtomicAlignment:
        case DeviceInfo::MaxNumSubGroups:
        case DeviceInfo::SubGroupIndependentForwardProgress:
        case DeviceInfo::NonUniformWorkGroupSupport:
        case DeviceInfo::WorkGroupCollectiveFunctionsSupport:
        case DeviceInfo::GenericAddressSpaceSupport:
        case DeviceInfo::PipeSupport:
            result    = mImpl->getInfoUInt(name, &valUInt);
            copyValue = &valUInt;
            copySize  = sizeof(valUInt);
            break;

        // Handle all cl_ulong and aliased types
        case DeviceInfo::SingleFpConfig:
        case DeviceInfo::DoubleFpConfig:
        case DeviceInfo::GlobalMemCacheSize:
        case DeviceInfo::GlobalMemSize:
        case DeviceInfo::MaxConstantBufferSize:
        case DeviceInfo::LocalMemSize:
        case DeviceInfo::QueueOnHostProperties:
        case DeviceInfo::QueueOnDeviceProperties:
        case DeviceInfo::PartitionAffinityDomain:
        case DeviceInfo::SVM_Capabilities:
        case DeviceInfo::AtomicMemoryCapabilities:
        case DeviceInfo::AtomicFenceCapabilities:
        case DeviceInfo::DeviceEnqueueCapabilities:
        case DeviceInfo::HalfFpConfig:
            result    = mImpl->getInfoULong(name, &valULong);
            copyValue = &valULong;
            copySize  = sizeof(valULong);
            break;

        // Handle all size_t and aliased types
        case DeviceInfo::MaxWorkGroupSize:
        case DeviceInfo::MaxParameterSize:
        case DeviceInfo::MaxGlobalVariableSize:
        case DeviceInfo::GlobalVariablePreferredTotalSize:
        case DeviceInfo::ProfilingTimerResolution:
        case DeviceInfo::PrintfBufferSize:
        case DeviceInfo::PreferredWorkGroupSizeMultiple:
            result    = mImpl->getInfoSizeT(name, &valSizeT);
            copyValue = &valSizeT;
            copySize  = sizeof(valSizeT);
            break;

        // Handle all string types
        case DeviceInfo::Name:
        case DeviceInfo::Vendor:
        case DeviceInfo::DriverVersion:
        case DeviceInfo::Profile:
        case DeviceInfo::OpenCL_C_Version:
        case DeviceInfo::LatestConformanceVersionPassed:
            result = mImpl->getInfoStringLength(name, &copySize);
            if (result != CL_SUCCESS)
            {
                return result;
            }
            valString.resize(copySize, '\0');
            result    = mImpl->getInfoString(name, copySize, valString.data());
            copyValue = valString.data();
            break;

        // Handle all cached values
        case DeviceInfo::Type:
            copyValue = &mInfo.mType;
            copySize  = sizeof(mInfo.mType);
            break;
        case DeviceInfo::MaxWorkItemDimensions:
            valUInt   = static_cast<cl_uint>(mInfo.mMaxWorkItemSizes.size());
            copyValue = &valUInt;
            copySize  = sizeof(valUInt);
            break;
        case DeviceInfo::MaxWorkItemSizes:
            copyValue = mInfo.mMaxWorkItemSizes.data();
            copySize  = mInfo.mMaxWorkItemSizes.size() *
                       sizeof(decltype(mInfo.mMaxWorkItemSizes)::value_type);
            break;
        case DeviceInfo::MaxMemAllocSize:
            copyValue = &mInfo.mMaxMemAllocSize;
            copySize  = sizeof(mInfo.mMaxMemAllocSize);
            break;
        case DeviceInfo::ImageSupport:
            copyValue = &mInfo.mImageSupport;
            copySize  = sizeof(mInfo.mImageSupport);
            break;
        case DeviceInfo::IL_Version:
            copyValue = mInfo.mIL_Version.c_str();
            copySize  = mInfo.mIL_Version.length() + 1u;
            break;
        case DeviceInfo::ILsWithVersion:
            copyValue = mInfo.mILsWithVersion.data();
            copySize =
                mInfo.mILsWithVersion.size() * sizeof(decltype(mInfo.mILsWithVersion)::value_type);
            break;
        case DeviceInfo::Image2D_MaxWidth:
            copyValue = &mInfo.mImage2D_MaxWidth;
            copySize  = sizeof(mInfo.mImage2D_MaxWidth);
            break;
        case DeviceInfo::Image2D_MaxHeight:
            copyValue = &mInfo.mImage2D_MaxHeight;
            copySize  = sizeof(mInfo.mImage2D_MaxHeight);
            break;
        case DeviceInfo::Image3D_MaxWidth:
            copyValue = &mInfo.mImage3D_MaxWidth;
            copySize  = sizeof(mInfo.mImage3D_MaxWidth);
            break;
        case DeviceInfo::Image3D_MaxHeight:
            copyValue = &mInfo.mImage3D_MaxHeight;
            copySize  = sizeof(mInfo.mImage3D_MaxHeight);
            break;
        case DeviceInfo::Image3D_MaxDepth:
            copyValue = &mInfo.mImage3D_MaxDepth;
            copySize  = sizeof(mInfo.mImage3D_MaxDepth);
            break;
        case DeviceInfo::ImageMaxBufferSize:
            copyValue = &mInfo.mImageMaxBufferSize;
            copySize  = sizeof(mInfo.mImageMaxBufferSize);
            break;
        case DeviceInfo::ImageMaxArraySize:
            copyValue = &mInfo.mImageMaxArraySize;
            copySize  = sizeof(mInfo.mImageMaxArraySize);
            break;
        case DeviceInfo::ImagePitchAlignment:
            copyValue = &mInfo.mImagePitchAlignment;
            copySize  = sizeof(mInfo.mImagePitchAlignment);
            break;
        case DeviceInfo::ImageBaseAddressAlignment:
            copyValue = &mInfo.mImageBaseAddressAlignment;
            copySize  = sizeof(mInfo.mImageBaseAddressAlignment);
            break;
        case DeviceInfo::MemBaseAddrAlign:
            copyValue = &mInfo.mMemBaseAddrAlign;
            copySize  = sizeof(mInfo.mMemBaseAddrAlign);
            break;
        case DeviceInfo::ExecutionCapabilities:
            copyValue = &mInfo.mExecCapabilities;
            copySize  = sizeof(mInfo.mExecCapabilities);
            break;
        case DeviceInfo::QueueOnDeviceMaxSize:
            copyValue = &mInfo.mQueueOnDeviceMaxSize;
            copySize  = sizeof(mInfo.mQueueOnDeviceMaxSize);
            break;
        case DeviceInfo::BuiltInKernels:
            copyValue = mInfo.mBuiltInKernels.c_str();
            copySize  = mInfo.mBuiltInKernels.length() + 1u;
            break;
        case DeviceInfo::BuiltInKernelsWithVersion:
            copyValue = mInfo.mBuiltInKernelsWithVersion.data();
            copySize  = mInfo.mBuiltInKernelsWithVersion.size() *
                       sizeof(decltype(mInfo.mBuiltInKernelsWithVersion)::value_type);
            break;
        case DeviceInfo::Version:
            copyValue = mInfo.mVersionStr.c_str();
            copySize  = mInfo.mVersionStr.length() + 1u;
            break;
        case DeviceInfo::NumericVersion:
            copyValue = &mInfo.mVersion;
            copySize  = sizeof(mInfo.mVersion);
            break;
        case DeviceInfo::OpenCL_C_AllVersions:
            copyValue = mInfo.mOpenCL_C_AllVersions.data();
            copySize  = mInfo.mOpenCL_C_AllVersions.size() *
                       sizeof(decltype(mInfo.mOpenCL_C_AllVersions)::value_type);
            break;
        case DeviceInfo::OpenCL_C_Features:
            copyValue = mInfo.mOpenCL_C_Features.data();
            copySize  = mInfo.mOpenCL_C_Features.size() *
                       sizeof(decltype(mInfo.mOpenCL_C_Features)::value_type);
            break;
        case DeviceInfo::Extensions:
            copyValue = mInfo.mExtensions.c_str();
            copySize  = mInfo.mExtensions.length() + 1u;
            break;
        case DeviceInfo::ExtensionsWithVersion:
            copyValue = mInfo.mExtensionsWithVersion.data();
            copySize  = mInfo.mExtensionsWithVersion.size() *
                       sizeof(decltype(mInfo.mExtensionsWithVersion)::value_type);
            break;
        case DeviceInfo::PartitionProperties:
            copyValue = mInfo.mPartitionProperties.data();
            copySize  = mInfo.mPartitionProperties.size() *
                       sizeof(decltype(mInfo.mPartitionProperties)::value_type);
            break;
        case DeviceInfo::PartitionType:
            copyValue = mInfo.mPartitionType.data();
            copySize =
                mInfo.mPartitionType.size() * sizeof(decltype(mInfo.mPartitionType)::value_type);
            break;

        // Handle all mapped values
        case DeviceInfo::Platform:
            valPointer = mPlatform.getNative();
            copyValue  = &valPointer;
            copySize   = sizeof(valPointer);
            break;
        case DeviceInfo::ParentDevice:
            valPointer = mParent->getNative();
            copyValue  = &valPointer;
            copySize   = sizeof(valPointer);
            break;
        case DeviceInfo::ReferenceCount:
            valUInt   = isRoot() ? 1u : getRefCount();
            copyValue = &valUInt;
            copySize  = sizeof(valUInt);
            break;

        default:
            ASSERT(false);
            return CL_INVALID_VALUE;
    }

    if (result != CL_SUCCESS)
    {
        return result;
    }
    if (value != nullptr)
    {
        // CL_INVALID_VALUE if size in bytes specified by param_value_size is < size of return
        // type as specified in the Device Queries table and param_value is not a NULL value
        if (valueSize < copySize)
        {
            return CL_INVALID_VALUE;
        }
        if (copyValue != nullptr)
        {
            std::memcpy(value, copyValue, copySize);
        }
    }
    if (valueSizeRet != nullptr)
    {
        *valueSizeRet = copySize;
    }
    return CL_SUCCESS;
}

cl_int Device::createSubDevices(const cl_device_partition_property *properties,
                                cl_uint numDevices,
                                cl_device_id *subDevices,
                                cl_uint *numDevicesRet)
{
    if (subDevices == nullptr)
    {
        numDevices = 0u;
    }
    rx::CLDeviceImpl::CreateFuncs subDeviceCreateFuncs;
    const cl_int errorCode =
        mImpl->createSubDevices(properties, numDevices, subDeviceCreateFuncs, numDevicesRet);
    if (errorCode == CL_SUCCESS)
    {
        cl::DeviceType type = mInfo.mType;
        type.clear(CL_DEVICE_TYPE_DEFAULT);
        DevicePtrs devices;
        devices.reserve(subDeviceCreateFuncs.size());
        while (!subDeviceCreateFuncs.empty())
        {
            devices.emplace_back(new Device(mPlatform, this, type, subDeviceCreateFuncs.front()));
            if (!devices.back()->mInfo.isValid())
            {
                return CL_INVALID_VALUE;
            }
            subDeviceCreateFuncs.pop_front();
        }
        for (DevicePtr &subDevice : devices)
        {
            *subDevices++ = subDevice.release();
        }
    }
    return errorCode;
}

Device::~Device() = default;

bool Device::supportsBuiltInKernel(const std::string &name) const
{
    if (name.empty() || mInfo.mBuiltInKernels.empty())
    {
        return false;
    }
    // Compare kernel name with all sub-strings terminated by semi-colon or end of string
    std::string::size_type start = 0u;
    do
    {
        std::string::size_type end = mInfo.mBuiltInKernels.find(';', start);
        if (end == std::string::npos)
        {
            end = mInfo.mBuiltInKernels.length();
        }
        const std::string::size_type length = end - start;
        if (length == name.length() && mInfo.mBuiltInKernels.compare(start, length, name) == 0)
        {
            return true;
        }
        start = end + 1u;
    } while (start < mInfo.mBuiltInKernels.size());
    return false;
}

bool Device::supportsNativeImageDimensions(const cl_image_desc &desc) const
{
    switch (desc.image_type)
    {
        case CL_MEM_OBJECT_IMAGE1D:
            return desc.image_width <= mInfo.mImage2D_MaxWidth;
        case CL_MEM_OBJECT_IMAGE2D:
            return desc.image_width <= mInfo.mImage2D_MaxWidth &&
                   desc.image_height <= mInfo.mImage2D_MaxHeight;
        case CL_MEM_OBJECT_IMAGE3D:
            return desc.image_width <= mInfo.mImage3D_MaxWidth &&
                   desc.image_height <= mInfo.mImage3D_MaxHeight &&
                   desc.image_depth <= mInfo.mImage3D_MaxDepth;
        case CL_MEM_OBJECT_IMAGE1D_ARRAY:
            return desc.image_width <= mInfo.mImage2D_MaxWidth &&
                   desc.image_array_size <= mInfo.mImageMaxArraySize;
        case CL_MEM_OBJECT_IMAGE2D_ARRAY:
            return desc.image_width <= mInfo.mImage2D_MaxWidth &&
                   desc.image_height <= mInfo.mImage2D_MaxHeight &&
                   desc.image_array_size <= mInfo.mImageMaxArraySize;
        case CL_MEM_OBJECT_IMAGE1D_BUFFER:
            return desc.image_width <= mInfo.mImageMaxBufferSize;
        default:
            ASSERT(false);
            break;
    }
    return false;
}

bool Device::supportsImageDimensions(const ImageDescriptor &desc) const
{
    switch (desc.type)
    {
        case CL_MEM_OBJECT_IMAGE1D:
            return desc.width <= mInfo.mImage2D_MaxWidth;
        case CL_MEM_OBJECT_IMAGE2D:
            return desc.width <= mInfo.mImage2D_MaxWidth && desc.height <= mInfo.mImage2D_MaxHeight;
        case CL_MEM_OBJECT_IMAGE3D:
            return desc.width <= mInfo.mImage3D_MaxWidth &&
                   desc.height <= mInfo.mImage3D_MaxHeight && desc.depth <= mInfo.mImage3D_MaxDepth;
        case CL_MEM_OBJECT_IMAGE1D_ARRAY:
            return desc.width <= mInfo.mImage2D_MaxWidth &&
                   desc.arraySize <= mInfo.mImageMaxArraySize;
        case CL_MEM_OBJECT_IMAGE2D_ARRAY:
            return desc.width <= mInfo.mImage2D_MaxWidth &&
                   desc.height <= mInfo.mImage2D_MaxHeight &&
                   desc.arraySize <= mInfo.mImageMaxArraySize;
        case CL_MEM_OBJECT_IMAGE1D_BUFFER:
            return desc.width <= mInfo.mImageMaxBufferSize;
        default:
            ASSERT(false);
            break;
    }
    return false;
}

Device::Device(Platform &platform,
               Device *parent,
               DeviceType type,
               const rx::CLDeviceImpl::CreateFunc &createFunc)
    : mPlatform(platform), mParent(parent), mImpl(createFunc(*this)), mInfo(mImpl->createInfo(type))
{}

}  // namespace cl
