diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml index bfeb322b..594dc6f5 100644 --- a/.github/workflows/presubmit.yml +++ b/.github/workflows/presubmit.yml @@ -61,6 +61,10 @@ jobs: cd OpenCL-Headers ln -s CL OpenCL # For OSX builds cd .. + - name: Fetch SPIR-V Headers + shell: bash + run: | + git clone https://github.com/KhronosGroup/SPIRV-Headers.git - name: Install Vulkan SDK uses: humbletim/install-vulkan-sdk@main with: @@ -160,6 +164,7 @@ jobs: -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -DCMAKE_CACHE_OPTIONS="-DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache" \ -DCL_INCLUDE_DIR='${{ github.workspace }}'/OpenCL-Headers \ + -DSPIRV_INCLUDE_DIR='${{ github.workspace }}'/SPIRV-Headers \ -DCL_LIB_DIR='${{ github.workspace }}'/OpenCL-ICD-Loader/build \ -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE} \ -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=./bin \ diff --git a/CMakeLists.txt b/CMakeLists.txt index f34ade8e..90c343fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,12 @@ else(CL_INCLUDE_DIR AND CL_LIB_DIR) message(FATAL_ERROR "Either install OpenCL or pass -DCL_INCLUDE_DIR and -DCL_LIB_DIR") endif(CL_INCLUDE_DIR AND CL_LIB_DIR) +# SPIRV_INCLUDE_DIR - path to dir with SPIR-V headers +if(NOT SPIRV_INCLUDE_DIR) + message(STATUS "SPIR-V headers haven't been found!") + message(FATAL_ERROR "Pass -DSPIRV_INCLUDE_DIR") +endif(NOT SPIRV_INCLUDE_DIR) + # CLConform_GL_LIBRARIES_DIR - path to OpenGL libraries if(GL_IS_SUPPORTED AND CLConform_GL_LIBRARIES_DIR) link_directories(${CLConform_GL_LIBRARIES_DIR}) @@ -195,6 +201,7 @@ if(APPLE) endif(APPLE) include_directories(SYSTEM ${CL_INCLUDE_DIR}) +include_directories(SYSTEM ${SPIRV_INCLUDE_DIR}/include) include_directories(${CLConform_SOURCE_DIR}/test_common/harness ${CLConform_SOURCE_DIR}/test_common/gles ${CLConform_SOURCE_DIR}/test_common/gl diff --git a/README.md b/README.md index 0cc09b1b..77e4d9a9 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ Compiling the CTS requires the following CMake configuration options to be set: * `CL_INCLUDE_DIR` Points to the unified [OpenCL-Headers](https://github.com/KhronosGroup/OpenCL-Headers). +* `SPIRV_INCLUDE_DIR` Points to the unified + [SPIRV-Headers](https://github.com/KhronosGroup/SPIRV-Headers). * `CL_LIB_DIR` Directory containing the OpenCL library to build against. * `SPIRV_TOOLS_DIR` Directory containing the `spirv-as` and `spirv-val` binaries to be used in the CTS build process. Alternatively, the location to these binaries @@ -31,6 +33,7 @@ a build, and compile. ```sh git clone https://github.com/KhronosGroup/OpenCL-CTS.git git clone https://github.com/KhronosGroup/OpenCL-Headers.git +git clone https://github.com/KhronosGroup/SPIRV-Headers.git git clone https://github.com/KhronosGroup/OpenCL-ICD-Loader.git git clone https://github.com/KhronosGroup/SPIRV-Tools.git git clone https://github.com/KhronosGroup/SPIRV-Headers.git SPIRV-Tools/external/spirv-headers @@ -50,6 +53,7 @@ cmake --build SPIRV-Tools/build --config Release mkdir OpenCL-CTS/build cmake -S OpenCL-CTS -B OpenCL-CTS/build \ -DCL_INCLUDE_DIR=$PWD/OpenCL-Headers \ + -DSPIRV_INCLUDE_DIR=$PWD/SPIRV-Headers \ -DCL_LIB_DIR=$PWD/OpenCL-ICD-Loader/build \ -DSPIRV_TOOLS_DIR=$PWD/SPIRV-Tools/build/tools/ \ -DOPENCL_LIBRARIES=OpenCL diff --git a/test_conformance/api/CMakeLists.txt b/test_conformance/api/CMakeLists.txt index f2bfac35..b781e49b 100644 --- a/test_conformance/api/CMakeLists.txt +++ b/test_conformance/api/CMakeLists.txt @@ -1,5 +1,7 @@ set(MODULE_NAME API) +find_package(Python3 COMPONENTS Interpreter QUIET) + set(${MODULE_NAME}_SOURCES main.cpp negative_platform.cpp @@ -40,6 +42,20 @@ set(${MODULE_NAME}_SOURCES test_pipe_properties_queries.cpp test_wg_suggested_local_work_size.cpp test_device_command_queue.cpp + test_spirv_queries.cpp + ${CMAKE_CURRENT_BINARY_DIR}/spirv_capability_deps.def ) +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/spirv_capability_deps.def + COMMENT "Generating spirv_capability_deps.def..." + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generate_spirv_capability_deps.py + --grammar "${SPIRV_INCLUDE_DIR}/include/spirv/unified1/spirv.core.grammar.json" + --output "${CMAKE_CURRENT_BINARY_DIR}/spirv_capability_deps.def" + DEPENDS generate_spirv_capability_deps.py "${SPIRV_INCLUDE_DIR}/include/spirv/unified1/spirv.core.grammar.json" + USES_TERMINAL + VERBATIM) + include(../CMakeCommon.txt) + +target_include_directories(${${MODULE_NAME}_OUT} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/test_conformance/api/generate_spirv_capability_deps.py b/test_conformance/api/generate_spirv_capability_deps.py new file mode 100644 index 00000000..ef59b6e8 --- /dev/null +++ b/test_conformance/api/generate_spirv_capability_deps.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 + +##################################################################### +# Copyright (c) 2025 The Khronos Group Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +##################################################################### + +""" +Generates a file describing the SPIR-V extension dependencies or SPIR-V version +dependencies for a SPIR-V capability. This can be used to ensure that if support +for a SPIR-V capability is reported, the necessary SPIR-V extensions or SPIR-V +version is also supported. +""" + +import argparse +import json + +header_text = """\ +// +// Copyright (c) 2025 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This file is generated from the SPIR-V JSON grammar file. +// Please do not edit it directly! +""" + +def main(): + parser = argparse.ArgumentParser(description='Generate SPIR-V extension and version dependencies for SPIR-V capabilities') + + parser.add_argument('--grammar', metavar='', + type=str, required=True, + help='input JSON grammar file') + parser.add_argument('--output', metavar='', + type=str, required=False, + help='output file path (default: stdout)') + args = parser.parse_args() + + dependencies = {} + capabilities = [] + with open(args.grammar) as json_file: + grammar_json = json.loads(json_file.read()) + for operand_kind in grammar_json['operand_kinds']: + if operand_kind['kind'] == 'Capability': + for cap in operand_kind['enumerants']: + capname = cap['enumerant'] + capabilities.append(capname) + dependencies[capname] = {} + dependencies[capname]['extensions'] = cap['extensions'] if 'extensions' in cap else [] + dependencies[capname]['version'] = ("SPIR-V_" + cap['version']) if 'version' in cap and cap['version'] != 'None' else "" + + capabilities.sort() + + output = [] + output.append(header_text) + output.append("// clang-format off") + if False: + for cap in capabilities: + deps = dependencies[cap] + extensions_str = ', '.join(f'"{ext}"' for ext in deps['extensions']) + + output.append('SPIRV_CAPABILITY_DEPENDENCIES( {}, {{{}}}, "{}" )'.format( + cap, extensions_str, deps['version'])) + else: + for cap in capabilities: + deps = dependencies[cap] + if deps['version'] != "": + output.append('SPIRV_CAPABILITY_VERSION_DEPENDENCY( {}, "{}" )'.format(cap, deps['version'])) + for ext in deps['extensions']: + output.append('SPIRV_CAPABILITY_EXTENSION_DEPENDENCY( {}, "{}" )'.format(cap, ext)) + output.append("// clang-format on") + + if args.output: + with open(args.output, 'w') as output_file: + output_file.write('\n'.join(output)) + else: + print('\n'.join(output)) + +if __name__ == '__main__': + main() diff --git a/test_conformance/api/test_spirv_queries.cpp b/test_conformance/api/test_spirv_queries.cpp new file mode 100644 index 00000000..720f73b1 --- /dev/null +++ b/test_conformance/api/test_spirv_queries.cpp @@ -0,0 +1,767 @@ +// +// Copyright (c) 2025 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "testBase.h" +#include +#include +#include + +#define SPV_ENABLE_UTILITY_CODE +#include + +static bool is_spirv_version_supported(cl_device_id deviceID, + const std::string& version) +{ + std::string ilVersions = get_device_il_version_string(deviceID); + return ilVersions.find(version) != std::string::npos; +} + +static int doQueries(cl_device_id device, + std::vector& extendedInstructionSets, + std::vector& extensions, + std::vector& capabilities) +{ + cl_int error = CL_SUCCESS; + + size_t size = 0; + error = + clGetDeviceInfo(device, CL_DEVICE_SPIRV_EXTENDED_INSTRUCTION_SETS_KHR, + 0, nullptr, &size); + test_error(error, + "clGetDeviceInfo failed for " + "CL_DEVICE_SPIRV_EXTENDED_INSTRUCTION_SETS_KHR size\n"); + + extendedInstructionSets.resize(size / sizeof(const char*)); + error = + clGetDeviceInfo(device, CL_DEVICE_SPIRV_EXTENDED_INSTRUCTION_SETS_KHR, + size, extendedInstructionSets.data(), nullptr); + test_error(error, + "clGetDeviceInfo failed for " + "CL_DEVICE_SPIRV_EXTENDED_INSTRUCTION_SETS_KHR\n"); + + error = clGetDeviceInfo(device, CL_DEVICE_SPIRV_EXTENSIONS_KHR, 0, nullptr, + &size); + test_error( + error, + "clGetDeviceInfo failed for CL_DEVICE_SPIRV_EXTENSIONS_KHR size\n"); + + extensions.resize(size / sizeof(const char*)); + error = clGetDeviceInfo(device, CL_DEVICE_SPIRV_EXTENSIONS_KHR, size, + extensions.data(), nullptr); + test_error(error, + "clGetDeviceInfo failed for CL_DEVICE_SPIRV_EXTENSIONS_KHR\n"); + + error = clGetDeviceInfo(device, CL_DEVICE_SPIRV_CAPABILITIES_KHR, 0, + nullptr, &size); + test_error( + error, + "clGetDeviceInfo failed for CL_DEVICE_SPIRV_CAPABILITIES_KHR size\n"); + + capabilities.resize(size / sizeof(cl_uint)); + error = clGetDeviceInfo(device, CL_DEVICE_SPIRV_CAPABILITIES_KHR, size, + capabilities.data(), nullptr); + test_error(error, + "clGetDeviceInfo failed for CL_DEVICE_SPIRV_CAPABILITIES_KHR\n"); + + return CL_SUCCESS; +} + +static int findRequirements(cl_device_id device, + std::vector& extendedInstructionSets, + std::vector& extensions, + std::vector& capabilities) +{ + cl_int error = CL_SUCCESS; + + auto version = get_device_cl_version(device); + auto ilVersions = get_device_il_version_string(device); + + // If no SPIR-V versions are supported, there are no requirements. + if (ilVersions.find("SPIR-V") == std::string::npos) + { + return CL_SUCCESS; + } + + cl_bool deviceImageSupport = CL_FALSE; + cl_bool deviceReadWriteImageSupport = CL_FALSE; + cl_bool deviceSubGroupsSupport = CL_FALSE; + cl_bool deviceGenericAddressSpaceSupport = CL_FALSE; + cl_bool deviceWorkGroupCollectiveFunctionsSupport = CL_FALSE; + cl_bool devicePipeSupport = CL_FALSE; + cl_bool deviceDeviceEnqueueSupport = CL_FALSE; + cl_device_integer_dot_product_capabilities_khr + deviceIntegerDotProductCapabilities = 0; + cl_device_fp_atomic_capabilities_ext deviceFp32AtomicCapabilities = 0; + cl_device_fp_atomic_capabilities_ext deviceFp16AtomicCapabilities = 0; + cl_device_fp_atomic_capabilities_ext deviceFp64AtomicCapabilities = 0; + + error = clGetDeviceInfo(device, CL_DEVICE_IMAGE_SUPPORT, + sizeof(deviceImageSupport), &deviceImageSupport, + nullptr); + test_error(error, "clGetDeviceInfo failed for CL_DEVICE_IMAGE_SUPPORT\n"); + + if (version >= Version(2, 0)) + { + cl_uint deviceMaxReadWriteImageArgs = 0; + error = clGetDeviceInfo(device, CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS, + sizeof(deviceMaxReadWriteImageArgs), + &deviceMaxReadWriteImageArgs, nullptr); + test_error( + error, + "clGetDeviceInfo failed for CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS\n"); + + deviceReadWriteImageSupport = + deviceMaxReadWriteImageArgs != 0 ? CL_TRUE : CL_FALSE; + } + + if (version >= Version(2, 1)) + { + cl_uint deviceMaxNumSubGroups = 0; + error = clGetDeviceInfo(device, CL_DEVICE_MAX_NUM_SUB_GROUPS, + sizeof(deviceMaxNumSubGroups), + &deviceMaxNumSubGroups, nullptr); + test_error(error, + "clGetDeviceInfo failed for CL_DEVICE_MAX_NUM_SUB_GROUPS\n"); + + deviceSubGroupsSupport = + deviceMaxNumSubGroups != 0 ? CL_TRUE : CL_FALSE; + } + else if (is_extension_available(device, "cl_khr_subgroups")) + { + deviceSubGroupsSupport = CL_TRUE; + } + + if (version >= Version(3, 0)) + { + error = clGetDeviceInfo(device, CL_DEVICE_GENERIC_ADDRESS_SPACE_SUPPORT, + sizeof(deviceGenericAddressSpaceSupport), + &deviceGenericAddressSpaceSupport, nullptr); + test_error(error, + "clGetDeviceInfo failed for " + "CL_DEVICE_GENERIC_ADDRESS_SPACE_SUPPORT\n"); + + error = clGetDeviceInfo( + device, CL_DEVICE_WORK_GROUP_COLLECTIVE_FUNCTIONS_SUPPORT, + sizeof(deviceWorkGroupCollectiveFunctionsSupport), + &deviceWorkGroupCollectiveFunctionsSupport, nullptr); + test_error(error, + "clGetDeviceInfo failed for " + "CL_DEVICE_WORK_GROUP_COLLECTIVE_FUNCTIONS_SUPPORT\n"); + + error = clGetDeviceInfo(device, CL_DEVICE_PIPE_SUPPORT, + sizeof(devicePipeSupport), &devicePipeSupport, + nullptr); + test_error(error, + "clGetDeviceInfo failed for CL_DEVICE_PIPE_SUPPORT\n"); + + cl_device_device_enqueue_capabilities deviceDeviceEnqueueCapabilities = + 0; + error = clGetDeviceInfo(device, CL_DEVICE_DEVICE_ENQUEUE_CAPABILITIES, + sizeof(deviceDeviceEnqueueCapabilities), + &deviceDeviceEnqueueCapabilities, nullptr); + test_error(error, + "clGetDeviceInfo failed for " + "CL_DEVICE_DEVICE_ENQUEUE_CAPABILITIES\n"); + + deviceDeviceEnqueueSupport = + deviceDeviceEnqueueCapabilities != 0 ? CL_TRUE : CL_FALSE; + } + else if (version >= Version(2, 0)) + { + deviceGenericAddressSpaceSupport = CL_TRUE; + deviceWorkGroupCollectiveFunctionsSupport = CL_TRUE; + devicePipeSupport = CL_TRUE; + deviceDeviceEnqueueSupport = CL_TRUE; + } + + if (is_extension_available(device, "cl_khr_integer_dot_product")) + { + error = clGetDeviceInfo(device, + CL_DEVICE_INTEGER_DOT_PRODUCT_CAPABILITIES_KHR, + sizeof(deviceIntegerDotProductCapabilities), + &deviceIntegerDotProductCapabilities, nullptr); + test_error(error, + "clGetDeviceInfo failed for " + "CL_DEVICE_INTEGER_DOT_PRODUCT_CAPABILITIES_KHR\n"); + } + + if (is_extension_available(device, "cl_ext_float_atomics")) + { + error = + clGetDeviceInfo(device, CL_DEVICE_SINGLE_FP_ATOMIC_CAPABILITIES_EXT, + sizeof(deviceFp32AtomicCapabilities), + &deviceFp32AtomicCapabilities, nullptr); + test_error(error, + "clGetDeviceInfo failed for " + "CL_DEVICE_SINGLE_FP_ATOMIC_CAPABILITIES_EXT\n"); + + error = + clGetDeviceInfo(device, CL_DEVICE_HALF_FP_ATOMIC_CAPABILITIES_EXT, + sizeof(deviceFp16AtomicCapabilities), + &deviceFp16AtomicCapabilities, nullptr); + test_error(error, + "clGetDeviceInfo failed for " + "CL_DEVICE_HALF_FP_ATOMIC_CAPABILITIES_EXT\n"); + + error = + clGetDeviceInfo(device, CL_DEVICE_DOUBLE_FP_ATOMIC_CAPABILITIES_EXT, + sizeof(deviceFp64AtomicCapabilities), + &deviceFp64AtomicCapabilities, nullptr); + test_error(error, + "clGetDeviceInfo failed for " + "CL_DEVICE_DOUBLE_FP_ATOMIC_CAPABILITIES_EXT\n"); + } + + // Required. + extendedInstructionSets.push_back("OpenCL.std"); + + capabilities.push_back(spv::CapabilityAddresses); + capabilities.push_back(spv::CapabilityFloat16Buffer); + capabilities.push_back(spv::CapabilityInt16); + capabilities.push_back(spv::CapabilityInt8); + capabilities.push_back(spv::CapabilityKernel); + capabilities.push_back(spv::CapabilityLinkage); + capabilities.push_back(spv::CapabilityVector16); + + // Required for FULL_PROFILE devices, or devices supporting + // cles_khr_int64. + if (gHasLong) + { + capabilities.push_back(spv::CapabilityInt64); + } + + // Required for devices supporting images. + if (deviceImageSupport == CL_TRUE) + { + capabilities.push_back(spv::CapabilityImage1D); + capabilities.push_back(spv::CapabilityImageBasic); + capabilities.push_back(spv::CapabilityImageBuffer); + capabilities.push_back(spv::CapabilityLiteralSampler); + capabilities.push_back(spv::CapabilitySampled1D); + capabilities.push_back(spv::CapabilitySampledBuffer); + } + + // Required for devices supporting SPIR-V 1.6. + if (ilVersions.find("SPIR-V_1.6") != std::string::npos) + { + capabilities.push_back(spv::CapabilityUniformDecoration); + } + + // Required for devices supporting read-write images. + if (deviceReadWriteImageSupport == CL_TRUE) + { + capabilities.push_back(spv::CapabilityImageReadWrite); + } + + // Required for devices supporting the generic address space. + if (deviceGenericAddressSpaceSupport == CL_TRUE) + { + capabilities.push_back(spv::CapabilityGenericPointer); + } + + // Required for devices supporting sub-groups or work-group collective + // functions. + if (deviceSubGroupsSupport == CL_TRUE + || deviceWorkGroupCollectiveFunctionsSupport == CL_TRUE) + { + capabilities.push_back(spv::CapabilityGroups); + } + + // Required for devices supporting pipes. + if (devicePipeSupport == CL_TRUE) + { + capabilities.push_back(spv::CapabilityPipes); + } + + // Required for devices supporting device-side enqueue. + if (deviceDeviceEnqueueSupport == CL_TRUE) + { + capabilities.push_back(spv::CapabilityDeviceEnqueue); + } + + // Required for devices supporting SPIR-V 1.1 and OpenCL 2.2. + if (ilVersions.find("SPIR-V_1.1") != std::string::npos + && version == Version(2, 2)) + { + capabilities.push_back(spv::CapabilityPipeStorage); + } + + // Required for devices supporting SPIR-V 1.1 and either OpenCL 2.2 or + // OpenCL 3.0 devices supporting sub-groups. + if (ilVersions.find("SPIR-V_1.1") != std::string::npos + && (version == Version(2, 2) + || (version >= Version(3, 0) && deviceSubGroupsSupport == CL_TRUE))) + { + capabilities.push_back(spv::CapabilitySubgroupDispatch); + } + + // Required for devices supporting cl_khr_expect_assume. + if (is_extension_available(device, "cl_khr_expect_assume")) + { + extensions.push_back("SPV_KHR_expect_assume"); + capabilities.push_back(spv::CapabilityExpectAssumeKHR); + } + + // Required for devices supporting cl_khr_extended_bit_ops. + if (is_extension_available(device, "cl_khr_extended_bit_ops")) + { + extensions.push_back("SPV_KHR_bit_instructions"); + capabilities.push_back(spv::CapabilityBitInstructions); + } + + // Required for devices supporting half-precision floating-point + // (cl_khr_fp16). + if (is_extension_available(device, "cl_khr_fp16")) + { + capabilities.push_back(spv::CapabilityFloat16); + } + + // Required for devices supporting double-precision floating-point + // (cl_khr_fp64). + if (is_extension_available(device, "cl_khr_fp64")) + { + capabilities.push_back(spv::CapabilityFloat64); + } + + // Required for devices supporting 64-bit atomics + // (cl_khr_int64_base_atomics or cl_khr_int64_extended_atomics). + if (is_extension_available(device, "cl_khr_int64_base_atomics") + || is_extension_available(device, "cl_khr_int64_extended_atomics")) + { + capabilities.push_back(spv::CapabilityInt64Atomics); + } + + // Required for devices supporting cl_khr_integer_dot_product. + if (is_extension_available(device, "cl_khr_integer_dot_product")) + { + extensions.push_back("SPV_KHR_integer_dot_product"); + capabilities.push_back(spv::CapabilityDotProduct); + capabilities.push_back(spv::CapabilityDotProductInput4x8BitPacked); + } + + // Required for devices supporting cl_khr_integer_dot_product and + // CL_DEVICE_INTEGER_DOT_PRODUCT_INPUT_4x8BIT_KHR. + if (is_extension_available(device, "cl_khr_integer_dot_product") + && (deviceIntegerDotProductCapabilities + & CL_DEVICE_INTEGER_DOT_PRODUCT_INPUT_4x8BIT_KHR)) + { + capabilities.push_back(spv::CapabilityDotProductInput4x8Bit); + } + + // Required for devices supporting cl_khr_kernel_clock. + if (is_extension_available(device, "cl_khr_kernel_clock")) + { + extensions.push_back("SPV_KHR_shader_clock"); + capabilities.push_back(spv::CapabilityShaderClockKHR); + } + + // Required for devices supporting both cl_khr_mipmap_image and + // cl_khr_mipmap_image_writes. + if (is_extension_available(device, "cl_khr_mipmap_image") + && is_extension_available(device, "cl_khr_mipmap_image_writes")) + { + capabilities.push_back(spv::CapabilityImageMipmap); + } + + // Required for devices supporting cl_khr_spirv_extended_debug_info. + if (is_extension_available(device, "cl_khr_spirv_extended_debug_info")) + { + extendedInstructionSets.push_back("OpenCL.DebugInfo.100"); + } + + // Required for devices supporting cl_khr_spirv_linkonce_odr. + if (is_extension_available(device, "cl_khr_spirv_linkonce_odr")) + { + extensions.push_back("SPV_KHR_linkonce_odr"); + } + + // Required for devices supporting + // cl_khr_spirv_no_integer_wrap_decoration. + if (is_extension_available(device, + "cl_khr_spirv_no_integer_wrap_decoration")) + { + extensions.push_back("SPV_KHR_no_integer_wrap_decoration"); + } + + // Required for devices supporting cl_khr_subgroup_ballot. + if (is_extension_available(device, "cl_khr_subgroup_ballot")) + { + capabilities.push_back(spv::CapabilityGroupNonUniformBallot); + } + + // Required for devices supporting cl_khr_subgroup_clustered_reduce. + if (is_extension_available(device, "cl_khr_subgroup_clustered_reduce")) + { + capabilities.push_back(spv::CapabilityGroupNonUniformClustered); + } + + // Required for devices supporting cl_khr_subgroup_named_barrier. + if (is_extension_available(device, "cl_khr_subgroup_named_barrier")) + { + capabilities.push_back(spv::CapabilityNamedBarrier); + } + + // Required for devices supporting + // cl_khr_subgroup_non_uniform_arithmetic. + if (is_extension_available(device, + "cl_khr_subgroup_non_uniform_arithmetic")) + { + capabilities.push_back(spv::CapabilityGroupNonUniformArithmetic); + } + + // Required for devices supporting cl_khr_subgroup_non_uniform_vote. + if (is_extension_available(device, "cl_khr_subgroup_non_uniform_vote")) + { + capabilities.push_back(spv::CapabilityGroupNonUniform); + capabilities.push_back(spv::CapabilityGroupNonUniformVote); + } + + // Required for devices supporting cl_khr_subgroup_rotate. + if (is_extension_available(device, "cl_khr_subgroup_rotate")) + { + extensions.push_back("SPV_KHR_subgroup_rotate"); + capabilities.push_back(spv::CapabilityGroupNonUniformRotateKHR); + } + + // Required for devices supporting cl_khr_subgroup_shuffle. + if (is_extension_available(device, "cl_khr_subgroup_shuffle")) + { + capabilities.push_back(spv::CapabilityGroupNonUniformShuffle); + } + + // Required for devices supporting cl_khr_subgroup_shuffle_relative. + if (is_extension_available(device, "cl_khr_subgroup_shuffle_relative")) + { + capabilities.push_back(spv::CapabilityGroupNonUniformShuffleRelative); + } + + // Required for devices supporting cl_khr_work_group_uniform_arithmetic. + if (is_extension_available(device, "cl_khr_work_group_uniform_arithmetic")) + { + extensions.push_back("SPV_KHR_uniform_group_instructions"); + capabilities.push_back(spv::CapabilityGroupUniformArithmeticKHR); + } + + // Required for devices supporting cl_ext_float_atomics and fp32 atomic + // adds. + if (is_extension_available(device, "cl_ext_float_atomics") + && (deviceFp32AtomicCapabilities + & (CL_DEVICE_GLOBAL_FP_ATOMIC_ADD_EXT + | CL_DEVICE_LOCAL_FP_ATOMIC_ADD_EXT))) + { + capabilities.push_back(spv::CapabilityAtomicFloat32AddEXT); + } + + // Required for devices supporting cl_ext_float_atomics and fp32 atomic + // min and max. + if (is_extension_available(device, "cl_ext_float_atomics") + && (deviceFp32AtomicCapabilities + & (CL_DEVICE_GLOBAL_FP_ATOMIC_MIN_MAX_EXT + | CL_DEVICE_LOCAL_FP_ATOMIC_MIN_MAX_EXT))) + { + capabilities.push_back(spv::CapabilityAtomicFloat32MinMaxEXT); + } + + // Required for devices supporting cl_ext_float_atomics and fp16 atomic + // adds. + if (is_extension_available(device, "cl_ext_float_atomics") + && (deviceFp16AtomicCapabilities + & (CL_DEVICE_GLOBAL_FP_ATOMIC_ADD_EXT + | CL_DEVICE_LOCAL_FP_ATOMIC_ADD_EXT))) + { + extensions.push_back("SPV_EXT_shader_atomic_float16_add"); + capabilities.push_back(spv::CapabilityAtomicFloat16AddEXT); + } + + // Required for devices supporting cl_ext_float_atomics and fp16 atomic + // min and max. + if (is_extension_available(device, "cl_ext_float_atomics") + && (deviceFp16AtomicCapabilities + & (CL_DEVICE_GLOBAL_FP_ATOMIC_MIN_MAX_EXT + | CL_DEVICE_LOCAL_FP_ATOMIC_MIN_MAX_EXT))) + { + capabilities.push_back(spv::CapabilityAtomicFloat16MinMaxEXT); + } + + // Required for devices supporting cl_ext_float_atomics and fp64 atomic + // adds. + if (is_extension_available(device, "cl_ext_float_atomics") + && (deviceFp64AtomicCapabilities + & (CL_DEVICE_GLOBAL_FP_ATOMIC_ADD_EXT + | CL_DEVICE_LOCAL_FP_ATOMIC_ADD_EXT))) + { + capabilities.push_back(spv::CapabilityAtomicFloat64AddEXT); + } + + // Required for devices supporting cl_ext_float_atomics and fp64 atomic + // min and max. + if (is_extension_available(device, "cl_ext_float_atomics") + && (deviceFp64AtomicCapabilities + & (CL_DEVICE_GLOBAL_FP_ATOMIC_MIN_MAX_EXT + | CL_DEVICE_LOCAL_FP_ATOMIC_MIN_MAX_EXT))) + { + capabilities.push_back(spv::CapabilityAtomicFloat64MinMaxEXT); + } + + // Required for devices supporting cl_ext_float_atomics and fp16, fp32, + // or fp64 atomic min or max. + if (is_extension_available(device, "cl_ext_float_atomics") + && ((deviceFp32AtomicCapabilities + & (CL_DEVICE_GLOBAL_FP_ATOMIC_MIN_MAX_EXT + | CL_DEVICE_LOCAL_FP_ATOMIC_MIN_MAX_EXT)) + || (deviceFp16AtomicCapabilities + & (CL_DEVICE_GLOBAL_FP_ATOMIC_MIN_MAX_EXT + | CL_DEVICE_LOCAL_FP_ATOMIC_MIN_MAX_EXT)) + || (deviceFp64AtomicCapabilities + & (CL_DEVICE_GLOBAL_FP_ATOMIC_MIN_MAX_EXT + | CL_DEVICE_LOCAL_FP_ATOMIC_MIN_MAX_EXT)))) + { + extensions.push_back("SPV_EXT_shader_atomic_float_min_max"); + } + + // Required for devices supporting cl_ext_float_atomics and fp32 or fp64 + // atomic adds. + if (is_extension_available(device, "cl_ext_float_atomics") + && ((deviceFp32AtomicCapabilities + & (CL_DEVICE_GLOBAL_FP_ATOMIC_ADD_EXT + | CL_DEVICE_LOCAL_FP_ATOMIC_ADD_EXT)) + || (deviceFp64AtomicCapabilities + & (CL_DEVICE_GLOBAL_FP_ATOMIC_ADD_EXT + | CL_DEVICE_LOCAL_FP_ATOMIC_ADD_EXT)))) + { + extensions.push_back("SPV_EXT_shader_atomic_float_add"); + } + + // Required for devices supporting cl_intel_bfloat16_conversions. + if (is_extension_available(device, "cl_intel_bfloat16_conversions")) + { + extensions.push_back("SPV_INTEL_bfloat16_conversion"); + capabilities.push_back(spv::CapabilityBFloat16ConversionINTEL); + } + + // Required for devices supporting + // cl_intel_spirv_device_side_avc_motion_estimation. + if (is_extension_available( + device, "cl_intel_spirv_device_side_avc_motion_estimation")) + { + extensions.push_back("SPV_INTEL_device_side_avc_motion_estimation"); + capabilities.push_back( + spv::CapabilitySubgroupAvcMotionEstimationChromaINTEL); + capabilities.push_back(spv::CapabilitySubgroupAvcMotionEstimationINTEL); + capabilities.push_back( + spv::CapabilitySubgroupAvcMotionEstimationIntraINTEL); + } + + // Required for devices supporting cl_intel_spirv_media_block_io. + if (is_extension_available(device, "cl_intel_spirv_media_block_io")) + { + extensions.push_back("SPV_INTEL_media_block_io"); + capabilities.push_back(spv::CapabilitySubgroupImageMediaBlockIOINTEL); + } + + // Required for devices supporting cl_intel_spirv_subgroups. + if (is_extension_available(device, "cl_intel_spirv_subgroups")) + { + extensions.push_back("SPV_INTEL_subgroups"); + capabilities.push_back(spv::CapabilitySubgroupBufferBlockIOINTEL); + capabilities.push_back(spv::CapabilitySubgroupImageBlockIOINTEL); + capabilities.push_back(spv::CapabilitySubgroupShuffleINTEL); + } + + // Required for devices supporting cl_intel_split_work_group_barrier. + if (is_extension_available(device, "cl_intel_split_work_group_barrier")) + { + extensions.push_back("SPV_INTEL_split_barrier"); + capabilities.push_back(spv::CapabilitySplitBarrierINTEL); + } + + // Required for devices supporting cl_intel_subgroup_buffer_prefetch. + if (is_extension_available(device, "cl_intel_subgroup_buffer_prefetch")) + { + extensions.push_back("SPV_INTEL_subgroup_buffer_prefetch"); + capabilities.push_back(spv::CapabilitySubgroupBufferPrefetchINTEL); + } + + return CL_SUCCESS; +} + +REGISTER_TEST(spirv_query_requirements) +{ + if (!is_extension_available(device, "cl_khr_spirv_queries")) + { + log_info("cl_khr_spirv_queries is not supported; skipping test.\n"); + return TEST_SKIPPED_ITSELF; + } + + cl_int error; + + std::vector queriedExtendedInstructionSets; + std::vector queriedExtensions; + std::vector queriedCapabilities; + + error = doQueries(device, queriedExtendedInstructionSets, queriedExtensions, + queriedCapabilities); + test_error_fail(error, "Unable to perform SPIR-V queries"); + + std::vector requiredExtendedInstructionSets; + std::vector requiredExtensions; + std::vector requiredCapabilities; + error = findRequirements(device, requiredExtendedInstructionSets, + requiredExtensions, requiredCapabilities); + test_error_fail(error, "Unable to find SPIR-V requirements"); + + for (auto check : requiredExtendedInstructionSets) + { + auto cmp = [=](const char* queried) { + return strcmp(check, queried) == 0; + }; + auto it = std::find_if(queriedExtendedInstructionSets.begin(), + queriedExtendedInstructionSets.end(), cmp); + if (it == queriedExtendedInstructionSets.end()) + { + test_fail("Missing required extended instruction set: %s\n", check); + } + } + + for (auto check : requiredExtensions) + { + auto cmp = [=](const char* queried) { + return strcmp(check, queried) == 0; + }; + auto it = std::find_if(queriedExtensions.begin(), + queriedExtensions.end(), cmp); + if (it == queriedExtensions.end()) + { + test_fail("Missing required extension: %s\n", check); + } + } + + for (auto check : requiredCapabilities) + { + if (std::find(queriedCapabilities.begin(), queriedCapabilities.end(), + check) + == queriedCapabilities.end()) + { + test_fail( + "Missing required capability: %s\n", + spv::CapabilityToString(static_cast(check))); + } + } + + // Find any extraneous capabilities (informational): + for (auto check : queriedCapabilities) + { + if (std::find(requiredCapabilities.begin(), requiredCapabilities.end(), + check) + == requiredCapabilities.end()) + { + log_info( + "Found non-required capability: %s\n", + spv::CapabilityToString(static_cast(check))); + } + } + + return TEST_PASS; +} + +REGISTER_TEST(spirv_query_dependencies) +{ + if (!is_extension_available(device, "cl_khr_spirv_queries")) + { + log_info("cl_khr_spirv_queries is not supported; skipping test.\n"); + return TEST_SKIPPED_ITSELF; + } + + cl_int error; + + std::vector queriedExtendedInstructionSets; + std::vector queriedExtensions; + std::vector queriedCapabilities; + + error = doQueries(device, queriedExtendedInstructionSets, queriedExtensions, + queriedCapabilities); + test_error_fail(error, "Unable to perform SPIR-V queries"); + + struct CapabilityDependencies + { + std::vector extensions; + std::string version; + }; + + std::map dependencies; + +#define SPIRV_CAPABILITY_VERSION_DEPENDENCY(_cap, _ver) \ + dependencies[spv::Capability##_cap].version = _ver; +#define SPIRV_CAPABILITY_EXTENSION_DEPENDENCY(_cap, _ext) \ + dependencies[spv::Capability##_cap].extensions.push_back(_ext); +#include "spirv_capability_deps.def" + + // For each queried SPIR-V capability, ensure that either that any SPIR-V + // version dependencies or SPIR-V extension dependencies are satisfied. + + for (auto check : queriedCapabilities) + { + // Log and skip any unknown capabilities + auto it = dependencies.find(static_cast(check)); + if (it == dependencies.end()) + { + log_info( + "No known dependencies for queried capability %s!\n", + spv::CapabilityToString(static_cast(check))); + continue; + } + + // Check if a SPIR-V version dependency is satisfied + const auto& version_dep = it->second.version; + if (!version_dep.empty() + && is_spirv_version_supported(device, version_dep)) + { + continue; + } + + // Check if a SPIR-V extension dependency is satisfied + bool found = false; + for (const auto& extension_dep : it->second.extensions) + { + if (std::find(queriedExtensions.begin(), queriedExtensions.end(), + extension_dep) + != queriedExtensions.end()) + { + found = true; + break; + } + } + if (found) + { + continue; + } + + // If we get here then the capability has an unsatisfied dependency. + log_error("Couldn't find a dependency for queried capability %s!\n", + spv::CapabilityToString(static_cast(check))); + if (!version_dep.empty()) + { + log_error("Checked for SPIR-V version %s.\n", version_dep.c_str()); + } + for (const auto& extension_dep : it->second.extensions) + { + log_error("Checked for SPIR-V extension %s.n", + extension_dep.c_str()); + } + return TEST_FAIL; + } + + return TEST_PASS; +}