| // Copyright 2018 The clvk authors. |
| // |
| // 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 <algorithm> |
| #include <cstring> |
| #include <filesystem> |
| #include <iostream> |
| #include <map> |
| #include <sstream> |
| #include <system_error> |
| #include <utility> |
| #include <vector> |
| |
| #include <vulkan/vulkan.h> |
| |
| #include "clspv/Sampler.h" |
| #include "utils.hpp" |
| |
| #ifdef CLSPV_ONLINE_COMPILER |
| #ifdef ENABLE_SPIRV_IL |
| #include "LLVMSPIRVLib.h" |
| #include "llvm/Bitcode/BitcodeWriter.h" |
| #include "llvm/Support/CommandLine.h" // FIXME(#380) remove |
| #include "llvm/Support/raw_ostream.h" |
| #endif |
| #include "clspv/Compiler.h" |
| #endif |
| |
| #include "spirv-tools/linker.hpp" |
| #include "spirv-tools/optimizer.hpp" |
| #include "spirv/unified1/NonSemanticClspvReflection.h" |
| #include "spirv/unified1/spirv.hpp" |
| |
| #include "config.hpp" |
| #include "init.hpp" |
| #include "log.hpp" |
| #include "program.hpp" |
| #include "tracing.hpp" |
| |
| struct membuf : public std::streambuf { |
| membuf(const unsigned char* begin, const unsigned char* end) { |
| auto sbegin = |
| reinterpret_cast<char*>(const_cast<unsigned char*>(begin)); |
| auto send = reinterpret_cast<char*>(const_cast<unsigned char*>(end)); |
| setg(sbegin, sbegin, send); |
| } |
| membuf(unsigned char* begin, unsigned char* end) { |
| auto sbegin = reinterpret_cast<char*>(begin); |
| auto send = reinterpret_cast<char*>(end); |
| setp(sbegin, send); |
| } |
| pos_type seekoff(off_type off, std::ios_base::seekdir dir, |
| std::ios_base::openmode which) override { |
| UNUSED(which); |
| char* whence = eback(); |
| if (dir == std::ios_base::cur) { |
| whence = gptr(); |
| } else if (dir == std::ios_base::end) { |
| whence = egptr(); |
| } |
| char* to = whence + off; |
| if (to >= eback() && to <= egptr()) { |
| setg(eback(), to, egptr()); |
| return gptr() - eback(); |
| } |
| return -1; |
| } |
| |
| pos_type seekpos(pos_type pos, std::ios_base::openmode which) override { |
| UNUSED(which); |
| char* to = eback() + pos; |
| if (to >= eback() && to <= egptr()) { |
| setg(eback(), to, egptr()); |
| return gptr() - eback(); |
| } |
| return -1; |
| } |
| }; |
| |
| struct reflection_parse_data { |
| uint32_t uint_id = 0; |
| std::unordered_map<uint32_t, uint32_t> constants; |
| std::unordered_map<uint32_t, std::string> strings; |
| spir_binary* binary; |
| std::unordered_map<uint32_t, kernel_argument_info> arg_infos; |
| }; |
| |
| spv_result_t parse_reflection(void* user_data, |
| const spv_parsed_instruction_t* inst) { |
| // Helper function to map instruction to argument type. |
| auto inst_to_arg_kind = [](uint32_t inst) { |
| switch (static_cast<NonSemanticClspvReflectionInstructions>(inst)) { |
| case NonSemanticClspvReflectionArgumentStorageBuffer: |
| return kernel_argument_kind::buffer; |
| case NonSemanticClspvReflectionArgumentUniform: |
| return kernel_argument_kind::buffer_ubo; |
| case NonSemanticClspvReflectionArgumentPodStorageBuffer: |
| return kernel_argument_kind::pod; |
| case NonSemanticClspvReflectionArgumentPodUniform: |
| return kernel_argument_kind::pod_ubo; |
| case NonSemanticClspvReflectionArgumentPodPushConstant: |
| return kernel_argument_kind::pod_pushconstant; |
| case NonSemanticClspvReflectionArgumentPointerUniform: |
| return kernel_argument_kind::pointer_ubo; |
| case NonSemanticClspvReflectionArgumentPointerPushConstant: |
| return kernel_argument_kind::pointer_pushconstant; |
| case NonSemanticClspvReflectionArgumentSampledImage: |
| return kernel_argument_kind::sampled_image; |
| case NonSemanticClspvReflectionArgumentStorageImage: |
| return kernel_argument_kind::storage_image; |
| case NonSemanticClspvReflectionArgumentStorageTexelBuffer: |
| return kernel_argument_kind::storage_texel_buffer; |
| case NonSemanticClspvReflectionArgumentUniformTexelBuffer: |
| return kernel_argument_kind::uniform_texel_buffer; |
| case NonSemanticClspvReflectionArgumentSampler: |
| return kernel_argument_kind::sampler; |
| case NonSemanticClspvReflectionArgumentWorkgroup: |
| return kernel_argument_kind::local; |
| default: |
| cvk_error_fn("Unhandled reflection instruction for arg kind"); |
| break; |
| } |
| return kernel_argument_kind::buffer; |
| }; |
| |
| // Helper function to map instruction to push constant type. |
| auto inst_to_push_constant = [](uint32_t inst) { |
| switch (static_cast<NonSemanticClspvReflectionInstructions>(inst)) { |
| case NonSemanticClspvReflectionPushConstantGlobalOffset: |
| return pushconstant::global_offset; |
| case NonSemanticClspvReflectionPushConstantEnqueuedLocalSize: |
| return pushconstant::enqueued_local_size; |
| case NonSemanticClspvReflectionPushConstantGlobalSize: |
| return pushconstant::global_size; |
| case NonSemanticClspvReflectionPushConstantRegionOffset: |
| return pushconstant::region_offset; |
| case NonSemanticClspvReflectionPushConstantNumWorkgroups: |
| return pushconstant::num_workgroups; |
| case NonSemanticClspvReflectionPushConstantRegionGroupOffset: |
| return pushconstant::region_group_offset; |
| case NonSemanticClspvReflectionImageArgumentInfoChannelOrderPushConstant: |
| return pushconstant::image_metadata; |
| case NonSemanticClspvReflectionImageArgumentInfoChannelDataTypePushConstant: |
| return pushconstant::image_metadata; |
| case NonSemanticClspvReflectionConstantDataPointerPushConstant: |
| return pushconstant::module_constants_pointer; |
| case NonSemanticClspvReflectionPrintfBufferPointerPushConstant: |
| return pushconstant::printf_buffer_pointer; |
| case NonSemanticClspvReflectionNormalizedSamplerMaskPushConstant: |
| return pushconstant::normalized_sampler_mask; |
| default: |
| cvk_error_fn("Unhandled reflection instruction for push constant"); |
| break; |
| } |
| return pushconstant::global_offset; |
| }; |
| |
| auto* parse_data = reinterpret_cast<reflection_parse_data*>(user_data); |
| switch (inst->opcode) { |
| case spv::OpTypeInt: |
| if (inst->words[2] == 32 && inst->words[3] == 0) { |
| parse_data->uint_id = inst->result_id; |
| } |
| break; |
| case spv::OpConstant: |
| if (inst->words[1] == parse_data->uint_id) { |
| parse_data->constants[inst->result_id] = inst->words[3]; |
| } |
| break; |
| case spv::OpString: |
| parse_data->strings[inst->result_id] = |
| std::string(reinterpret_cast<const char*>(&inst->words[2])); |
| break; |
| case spv::OpExtInst: |
| if (inst->ext_inst_type == |
| SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION) { |
| auto ext_inst = inst->words[4]; |
| switch (ext_inst) { |
| case NonSemanticClspvReflectionKernel: { |
| // Record the kernel name. |
| const auto& name = parse_data->strings[inst->words[6]]; |
| |
| const auto& num_args = parse_data->constants[inst->words[7]]; |
| const auto& flags = parse_data->constants[inst->words[8]]; |
| const auto& attributes = parse_data->strings[inst->words[9]]; |
| |
| parse_data->strings[inst->result_id] = name; |
| parse_data->binary->add_kernel(name, num_args, attributes, |
| flags); |
| break; |
| } |
| case NonSemanticClspvReflectionArgumentInfo: { |
| // Record the argument info. |
| kernel_argument_info info; |
| info.name = parse_data->strings[inst->words[5]]; |
| if (inst->num_operands > 6) { |
| info.type_name = parse_data->strings[inst->words[6]]; |
| info.address_qualifier = |
| parse_data->constants[inst->words[7]]; |
| info.access_qualifier = |
| parse_data->constants[inst->words[8]]; |
| info.type_qualifier = parse_data->constants[inst->words[9]]; |
| info.extended_valid = true; |
| } |
| parse_data->arg_infos[inst->result_id] = info; |
| break; |
| } |
| case NonSemanticClspvReflectionNormalizedSamplerMaskPushConstant: { |
| auto kernel = parse_data->strings[inst->words[5]]; |
| auto ordinal = parse_data->constants[inst->words[6]]; |
| auto offset = parse_data->constants[inst->words[7]]; |
| auto size = parse_data->constants[inst->words[8]]; |
| parse_data->binary->add_sampler_metadata(kernel, ordinal, |
| offset); |
| auto pc = inst_to_push_constant(ext_inst); |
| parse_data->binary->add_push_constant(pc, {offset, size}); |
| break; |
| } |
| case NonSemanticClspvReflectionImageArgumentInfoChannelOrderPushConstant: { |
| auto kernel = parse_data->strings[inst->words[5]]; |
| auto ordinal = parse_data->constants[inst->words[6]]; |
| auto offset = parse_data->constants[inst->words[7]]; |
| auto size = parse_data->constants[inst->words[8]]; |
| parse_data->binary->add_image_channel_order_metadata( |
| kernel, ordinal, offset); |
| auto pc = inst_to_push_constant(ext_inst); |
| parse_data->binary->add_push_constant(pc, {offset, size}); |
| break; |
| } |
| case NonSemanticClspvReflectionImageArgumentInfoChannelDataTypePushConstant: { |
| auto kernel = parse_data->strings[inst->words[5]]; |
| auto ordinal = parse_data->constants[inst->words[6]]; |
| auto offset = parse_data->constants[inst->words[7]]; |
| auto size = parse_data->constants[inst->words[8]]; |
| parse_data->binary->add_image_channel_data_type_metadata( |
| kernel, ordinal, offset); |
| auto pc = inst_to_push_constant(ext_inst); |
| parse_data->binary->add_push_constant(pc, {offset, size}); |
| break; |
| } |
| case NonSemanticClspvReflectionArgumentStorageBuffer: |
| case NonSemanticClspvReflectionArgumentUniform: |
| case NonSemanticClspvReflectionArgumentSampledImage: |
| case NonSemanticClspvReflectionArgumentStorageImage: |
| case NonSemanticClspvReflectionArgumentStorageTexelBuffer: |
| case NonSemanticClspvReflectionArgumentUniformTexelBuffer: |
| case NonSemanticClspvReflectionArgumentSampler: { |
| // These arguments have descriptor set, binding and an optional |
| // arg info. |
| auto kernel = parse_data->strings[inst->words[5]]; |
| auto ordinal = parse_data->constants[inst->words[6]]; |
| auto descriptor_set = parse_data->constants[inst->words[7]]; |
| if (descriptor_set >= spir_binary::MAX_DESCRIPTOR_SETS) |
| return SPV_ERROR_INVALID_DATA; |
| auto binding = parse_data->constants[inst->words[8]]; |
| kernel_argument_info arg_info; |
| if (inst->num_operands == 9) { |
| arg_info = parse_data->arg_infos[inst->words[9]]; |
| } |
| auto kind = inst_to_arg_kind(ext_inst); |
| kernel_argument arg = {arg_info, ordinal, descriptor_set, |
| binding, 0, 0, |
| kind, 0, 0}; |
| parse_data->binary->add_kernel_argument(kernel, std::move(arg)); |
| break; |
| } |
| case NonSemanticClspvReflectionArgumentPodStorageBuffer: |
| case NonSemanticClspvReflectionArgumentPodUniform: |
| case NonSemanticClspvReflectionArgumentPointerUniform: { |
| // These arguments have descriptor set, binding, offset, size |
| // and an optional arg info. |
| auto kernel = parse_data->strings[inst->words[5]]; |
| auto ordinal = parse_data->constants[inst->words[6]]; |
| auto descriptor_set = parse_data->constants[inst->words[7]]; |
| if (descriptor_set >= spir_binary::MAX_DESCRIPTOR_SETS) |
| return SPV_ERROR_INVALID_DATA; |
| auto binding = parse_data->constants[inst->words[8]]; |
| auto offset = parse_data->constants[inst->words[9]]; |
| auto size = parse_data->constants[inst->words[10]]; |
| kernel_argument_info arg_info; |
| if (inst->num_operands == 11) { |
| arg_info = parse_data->arg_infos[inst->words[11]]; |
| } |
| auto kind = inst_to_arg_kind(ext_inst); |
| kernel_argument arg = {arg_info, ordinal, descriptor_set, |
| binding, offset, size, |
| kind, 0, 0}; |
| parse_data->binary->add_kernel_argument(kernel, std::move(arg)); |
| break; |
| } |
| case NonSemanticClspvReflectionArgumentPodPushConstant: |
| case NonSemanticClspvReflectionArgumentPointerPushConstant: { |
| // These arguments have offset, size and an optional arg info. |
| auto kernel = parse_data->strings[inst->words[5]]; |
| auto ordinal = parse_data->constants[inst->words[6]]; |
| auto offset = parse_data->constants[inst->words[7]]; |
| auto size = parse_data->constants[inst->words[8]]; |
| kernel_argument_info arg_info; |
| if (inst->num_operands == 9) { |
| arg_info = parse_data->arg_infos[inst->words[9]]; |
| } |
| auto kind = inst_to_arg_kind(ext_inst); |
| kernel_argument arg = {arg_info, ordinal, 0, 0, offset, |
| size, kind, 0, 0}; |
| parse_data->binary->add_kernel_argument(kernel, std::move(arg)); |
| break; |
| } |
| case NonSemanticClspvReflectionWorkgroupVariableSize: { |
| auto size = parse_data->constants[inst->words[6]]; |
| parse_data->binary->add_workgroup_variable_size(size); |
| break; |
| } |
| case NonSemanticClspvReflectionArgumentWorkgroup: { |
| // These arguments have spec id, elem size and an optional arg |
| // info. |
| auto kernel = parse_data->strings[inst->words[5]]; |
| auto ordinal = parse_data->constants[inst->words[6]]; |
| auto spec_id = parse_data->constants[inst->words[7]]; |
| auto size = parse_data->constants[inst->words[8]]; |
| kernel_argument_info arg_info; |
| if (inst->num_operands == 9) { |
| arg_info = parse_data->arg_infos[inst->words[9]]; |
| } |
| auto kind = inst_to_arg_kind(ext_inst); |
| kernel_argument arg = {arg_info, ordinal, 0, 0, 0, |
| 0, kind, spec_id, size}; |
| parse_data->binary->add_kernel_argument(kernel, std::move(arg)); |
| break; |
| } |
| case NonSemanticClspvReflectionSpecConstantWorkgroupSize: { |
| // Reflection encodes all three spec ids in a single |
| // instruction. |
| auto x_id = parse_data->constants[inst->words[5]]; |
| auto y_id = parse_data->constants[inst->words[6]]; |
| auto z_id = parse_data->constants[inst->words[7]]; |
| parse_data->binary->add_spec_constant( |
| spec_constant::workgroup_size_x, x_id); |
| parse_data->binary->add_spec_constant( |
| spec_constant::workgroup_size_y, y_id); |
| parse_data->binary->add_spec_constant( |
| spec_constant::workgroup_size_z, z_id); |
| break; |
| } |
| case NonSemanticClspvReflectionSpecConstantGlobalOffset: { |
| // Reflection encodes all three spec ids in a single |
| // instruction. |
| auto x_id = parse_data->constants[inst->words[5]]; |
| auto y_id = parse_data->constants[inst->words[6]]; |
| auto z_id = parse_data->constants[inst->words[7]]; |
| parse_data->binary->add_spec_constant( |
| spec_constant::global_offset_x, x_id); |
| parse_data->binary->add_spec_constant( |
| spec_constant::global_offset_y, y_id); |
| parse_data->binary->add_spec_constant( |
| spec_constant::global_offset_z, z_id); |
| break; |
| } |
| case NonSemanticClspvReflectionSpecConstantWorkDim: { |
| auto dim_id = parse_data->constants[inst->words[5]]; |
| parse_data->binary->add_spec_constant(spec_constant::work_dim, |
| dim_id); |
| break; |
| } |
| case NonSemanticClspvReflectionSpecConstantSubgroupMaxSize: { |
| auto size_id = parse_data->constants[inst->words[5]]; |
| parse_data->binary->add_spec_constant( |
| spec_constant::subgroup_max_size, size_id); |
| break; |
| } |
| case NonSemanticClspvReflectionPushConstantGlobalOffset: |
| case NonSemanticClspvReflectionPushConstantEnqueuedLocalSize: |
| case NonSemanticClspvReflectionPushConstantGlobalSize: |
| case NonSemanticClspvReflectionPushConstantRegionOffset: |
| case NonSemanticClspvReflectionPushConstantNumWorkgroups: |
| case NonSemanticClspvReflectionPushConstantRegionGroupOffset: { |
| auto offset = parse_data->constants[inst->words[5]]; |
| auto size = parse_data->constants[inst->words[6]]; |
| auto pc = inst_to_push_constant(ext_inst); |
| parse_data->binary->add_push_constant(pc, {offset, size}); |
| break; |
| } |
| case NonSemanticClspvReflectionLiteralSampler: { |
| // Track descriptor set and binding. Decode the sampler mask. |
| auto descriptor_set = parse_data->constants[inst->words[5]]; |
| if (descriptor_set >= spir_binary::MAX_DESCRIPTOR_SETS) |
| return SPV_ERROR_INVALID_DATA; |
| auto binding = parse_data->constants[inst->words[6]]; |
| auto mask = parse_data->constants[inst->words[7]]; |
| uint32_t coords = mask & clspv::kSamplerNormalizedCoordsMask; |
| bool normalized_coords = |
| coords == clspv::CLK_NORMALIZED_COORDS_TRUE; |
| cl_addressing_mode addressing; |
| switch (mask & clspv::kSamplerAddressMask) { |
| case clspv::CLK_ADDRESS_NONE: |
| default: |
| addressing = CL_ADDRESS_NONE; |
| break; |
| case clspv::CLK_ADDRESS_CLAMP_TO_EDGE: |
| addressing = CL_ADDRESS_CLAMP_TO_EDGE; |
| break; |
| case clspv::CLK_ADDRESS_CLAMP: |
| addressing = CL_ADDRESS_CLAMP; |
| break; |
| case clspv::CLK_ADDRESS_MIRRORED_REPEAT: |
| addressing = CL_ADDRESS_MIRRORED_REPEAT; |
| break; |
| case clspv::CLK_ADDRESS_REPEAT: |
| addressing = CL_ADDRESS_REPEAT; |
| break; |
| } |
| cl_filter_mode filter; |
| switch (mask & clspv::kSamplerFilterMask) { |
| case clspv::CLK_FILTER_NEAREST: |
| default: |
| filter = CL_FILTER_NEAREST; |
| break; |
| case clspv::CLK_FILTER_LINEAR: |
| filter = CL_FILTER_LINEAR; |
| break; |
| } |
| parse_data->binary->add_literal_sampler( |
| {descriptor_set, binding, normalized_coords, addressing, |
| filter}); |
| break; |
| } |
| case NonSemanticClspvReflectionPropertyRequiredWorkgroupSize: { |
| auto kernel = parse_data->strings[inst->words[5]]; |
| auto x = parse_data->constants[inst->words[6]]; |
| auto y = parse_data->constants[inst->words[7]]; |
| auto z = parse_data->constants[inst->words[8]]; |
| parse_data->binary->set_required_work_group_size(kernel, x, y, |
| z); |
| break; |
| } |
| case NonSemanticClspvReflectionConstantDataStorageBuffer: |
| case NonSemanticClspvReflectionConstantDataPointerPushConstant: { |
| |
| auto char2int = [](char c) { |
| if (c >= '0' && c <= '9') |
| return c - '0'; |
| if (c >= 'A' && c <= 'F') |
| return c - 'A' + 10; |
| if (c >= 'a' && c <= 'f') |
| return c - 'a' + 10; |
| return 0; |
| }; |
| |
| auto hex2bin = [&char2int](const char* str, char* bin) { |
| while (str[0] && str[1]) { |
| *(bin++) = char2int(str[0]) * 16 + char2int(str[1]); |
| str += 2; |
| } |
| }; |
| |
| auto data = parse_data->strings[inst->words[7]]; |
| if (data.size() & 1) { |
| cvk_error_fn("invalid constant data buffer string (odd " |
| "number of digits)"); |
| return SPV_ERROR_INVALID_DATA; |
| } |
| constant_data_buffer_info binfo{}; |
| auto data_size = data.size() / 2; |
| binfo.data.resize(data_size); |
| hex2bin(data.c_str(), binfo.data.data()); |
| if (ext_inst == |
| NonSemanticClspvReflectionConstantDataStorageBuffer) { |
| binfo.type = module_buffer_type::storage_buffer; |
| binfo.set = parse_data->constants[inst->words[5]]; |
| if (binfo.set >= spir_binary::MAX_DESCRIPTOR_SETS) |
| return SPV_ERROR_INVALID_DATA; |
| binfo.binding = parse_data->constants[inst->words[6]]; |
| |
| } else { |
| binfo.type = module_buffer_type::pointer_push_constant; |
| binfo.pc_offset = parse_data->constants[inst->words[5]]; |
| parse_data->binary->add_push_constant( |
| pushconstant::module_constants_pointer, |
| {binfo.pc_offset, 8u}); |
| } |
| parse_data->binary->set_constant_data_buffer(binfo); |
| break; |
| } |
| case NonSemanticClspvReflectionPrintfInfo: { |
| uint32_t printf_id = parse_data->constants[inst->words[5]]; |
| std::string printf_string = parse_data->strings[inst->words[6]]; |
| std::vector<uint32_t> printf_arg_sizes; |
| for (int i = 6; i < inst->num_operands; i++) { |
| printf_arg_sizes.push_back( |
| parse_data |
| ->constants[inst->words[inst->operands[i].offset]]); |
| } |
| parse_data->binary->add_printf_descriptor( |
| {printf_id, printf_string, printf_arg_sizes}); |
| break; |
| } |
| case NonSemanticClspvReflectionPrintfBufferStorageBuffer: { |
| printf_buffer_desc_info binfo; |
| binfo.type = module_buffer_type::storage_buffer; |
| binfo.set = parse_data->constants[inst->words[5]]; |
| binfo.binding = parse_data->constants[inst->words[6]]; |
| binfo.size = parse_data->constants[inst->words[7]]; |
| parse_data->binary->set_printf_buffer_info(binfo); |
| break; |
| } |
| case NonSemanticClspvReflectionPrintfBufferPointerPushConstant: { |
| printf_buffer_desc_info binfo; |
| binfo.type = module_buffer_type::pointer_push_constant; |
| binfo.pc_offset = parse_data->constants[inst->words[5]]; |
| binfo.size = parse_data->constants[inst->words[7]]; |
| parse_data->binary->set_printf_buffer_info(binfo); |
| parse_data->binary->add_push_constant( |
| pushconstant::printf_buffer_pointer, {binfo.pc_offset, 8u}); |
| break; |
| } |
| default: |
| return SPV_ERROR_INVALID_DATA; |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return SPV_SUCCESS; |
| } |
| |
| bool spir_binary::load(const char* fname) { |
| std::ifstream ifile; |
| |
| ifile.open(fname, std::ios::in | std::ios::binary); |
| |
| if (!ifile.is_open()) { |
| cvk_error("Failed to open %s", fname); |
| return false; |
| } |
| |
| ifile.seekg(0, std::ios::end); |
| uint32_t size = ifile.tellg(); |
| ifile.seekg(0, std::ios::beg); |
| |
| return load(ifile, size); |
| } |
| |
| bool spir_binary::load(std::istream& istream, uint32_t size) { |
| m_code.assign(size / SPIR_WORD_SIZE, 0); |
| istream.read(reinterpret_cast<char*>(m_code.data()), size); |
| |
| if (!istream.good()) { |
| cvk_warn("Failed to load SPIR-V (size: %u)", size); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool spir_binary::read(const unsigned char* src, size_t size) { |
| m_loaded_from_binary = true; |
| membuf bufview(src, src + size); |
| std::istream istream(&bufview); |
| return load(istream, size); |
| } |
| |
| bool spir_binary::save(std::ostream& ostream) const { |
| ostream.write(reinterpret_cast<const char*>(m_code.data()), size()); |
| return ostream.good(); |
| } |
| |
| bool spir_binary::save(const char* fname) const { |
| std::ofstream ofile; |
| |
| ofile.open(fname, std::ios::out | std::ios::binary); |
| |
| if (!ofile.is_open()) { |
| return false; |
| } |
| |
| return save(ofile); |
| } |
| |
| size_t spir_binary::size() const { return m_code.size() * SPIR_WORD_SIZE; } |
| |
| bool spir_binary::write(unsigned char* dst) const { |
| membuf bufview(dst, dst + size()); |
| std::ostream ostream(&bufview); |
| return save(ostream); |
| } |
| |
| void spir_binary::use(std::vector<uint32_t>&& src) { m_code = std::move(src); } |
| |
| void spir_binary::set_target_env(spv_target_env env) { |
| spvContextDestroy(m_context); |
| m_context = spvContextCreate(env); |
| } |
| |
| bool spir_binary::validate(const spirv_validation_options& val_options) const { |
| spv_diagnostic diag; |
| spv_validator_options options = spvValidatorOptionsCreate(); |
| spvValidatorOptionsSetUniformBufferStandardLayout( |
| options, val_options.uniform_buffer_std_layout); |
| spv_const_binary_t binary{m_code.data(), m_code.size()}; |
| spv_result_t res = |
| spvValidateWithOptions(m_context, options, &binary, &diag); |
| spvDiagnosticPrint(diag); |
| spvDiagnosticDestroy(diag); |
| spvValidatorOptionsDestroy(options); |
| return res == SPV_SUCCESS; |
| } |
| |
| bool spir_binary::strip_reflection(std::vector<uint32_t>* stripped) { |
| const spvtools::MessageConsumer consumer = |
| [](spv_message_level_t level, const char*, |
| const spv_position_t& position, const char* message) { |
| |
| #define msgtpl "spvtools says '%s' at position %zu" |
| switch (level) { |
| case SPV_MSG_FATAL: |
| case SPV_MSG_INTERNAL_ERROR: |
| case SPV_MSG_ERROR: |
| cvk_error(msgtpl, message, position.index); |
| break; |
| case SPV_MSG_WARNING: |
| cvk_warn(msgtpl, message, position.index); |
| break; |
| case SPV_MSG_INFO: |
| cvk_info(msgtpl, message, position.index); |
| break; |
| case SPV_MSG_DEBUG: |
| cvk_debug(msgtpl, message, position.index); |
| break; |
| } |
| #undef msgtpl |
| }; |
| |
| #if COMPILER_AVAILABLE || USING_SWIFTSHADER |
| spvtools::Optimizer opt(m_target_env); |
| opt.SetMessageConsumer(consumer); |
| opt.RegisterPass(spvtools::CreateStripReflectInfoPass()); |
| spvtools::OptimizerOptions options; |
| options.set_run_validator(false); |
| if (!opt.Run(m_code.data(), m_code.size(), stripped, options)) { |
| return false; |
| } |
| #else |
| *stripped = m_code; |
| #endif |
| return true; |
| } |
| |
| bool spir_binary::load_descriptor_map() { |
| reflection_parse_data parse_data; |
| parse_data.binary = this; |
| |
| // TODO: The parser assumes a valid SPIR-V module, but validation is not |
| // run until later. |
| auto result = |
| spvBinaryParse(m_context, &parse_data, m_code.data(), m_code.size(), |
| nullptr, parse_reflection, nullptr); |
| if (result != SPV_SUCCESS) { |
| cvk_error_fn("Parsing SPIR-V module reflection failed: %d", result); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool spir_binary::get_capabilities( |
| std::vector<spv::Capability>& capabilities) const { |
| // Callback for receiving parsed instructions. |
| // The `user_data` parameter will be a pointer to the vector of |
| // capabilities (we cannot use a lambda capture for this as it prevents the |
| // lambda from being able to be converted to a function pointer). |
| auto parse_inst = [](void* user_data, |
| const spv_parsed_instruction_t* inst) { |
| // Stop parsing at first instruction that is not an OpCapability. |
| if (inst->opcode != spv::Op::OpCapability) { |
| return SPV_END_OF_STREAM; |
| } |
| |
| // Add the capability to the list. |
| uint32_t capability = inst->words[inst->operands[0].offset]; |
| auto capabilities = |
| reinterpret_cast<std::vector<spv::Capability>*>(user_data); |
| capabilities->push_back(static_cast<spv::Capability>(capability)); |
| |
| return SPV_SUCCESS; |
| }; |
| |
| // Parse the SPIR-V binary to build the list of required capabilities. |
| spv_result_t result = |
| spvBinaryParse(m_context, &capabilities, m_code.data(), m_code.size(), |
| nullptr, parse_inst, nullptr); |
| if (result != SPV_SUCCESS && result != SPV_END_OF_STREAM) { |
| cvk_error_fn("Parsing SPIR-V module failed: %d", result); |
| return false; |
| } |
| return true; |
| } |
| |
| namespace { |
| |
| #if COMPILER_AVAILABLE |
| bool save_cstring_to_file( |
| const std::string& fname, const char* data, size_t size, |
| std::ios_base::openmode open_mode = std::ios_base::out) { |
| std::filesystem::path ofname(fname); |
| if (ofname.has_parent_path()) { |
| std::error_code error; |
| std::filesystem::create_directories(ofname.parent_path(), error); |
| if (error) { |
| cvk_error_fn("create_directories failed: '%s' (%u)", |
| error.message().c_str(), error.value()); |
| return false; |
| } |
| } |
| std::ofstream ofile{fname, open_mode}; |
| |
| if (!ofile.is_open()) { |
| return false; |
| } |
| |
| ofile.write(data, size); |
| ofile.close(); |
| |
| return ofile.good(); |
| } |
| |
| bool save_string_to_file(const std::string& fname, const std::string& text) { |
| return save_cstring_to_file(fname, text.c_str(), text.size()); |
| } |
| |
| #ifndef CLSPV_ONLINE_COMPILER |
| bool save_il_to_file(const std::string& fname, const std::vector<uint8_t>& il) { |
| return save_cstring_to_file(fname, reinterpret_cast<const char*>(il.data()), |
| il.size(), std::ios::binary); |
| } |
| #endif // CLSPV_ONLINE_COMPILER |
| #endif // COMPILER_AVAILABLE |
| |
| struct temp_folder_deletion { |
| temp_folder_deletion(const std::string& path) : m_path(path) {} |
| ~temp_folder_deletion() { |
| if (!config.keep_temporaries && !m_path.empty()) |
| std::filesystem::remove_all(m_path.c_str()); |
| } |
| |
| private: |
| std::string m_path; |
| }; |
| |
| enum class spirv_validation_level |
| { |
| skip, |
| warn, |
| error, |
| }; |
| |
| bool validate_binary(spir_binary const& binary, |
| spirv_validation_options const& val_options) { |
| spirv_validation_level level = spirv_validation_level::error; |
| if (config.spirv_validation.set) { |
| if (config.spirv_validation == 0) { |
| level = spirv_validation_level::skip; |
| } else if (config.spirv_validation == 1) { |
| level = spirv_validation_level::warn; |
| } |
| } |
| |
| if (level == spirv_validation_level::skip) { |
| cvk_info("Skipping validation of SPIR-V binary."); |
| return true; |
| } |
| |
| if (binary.validate(val_options)) { |
| cvk_info("SPIR-V binary is valid."); |
| return true; |
| } |
| |
| if (level == spirv_validation_level::warn) { |
| cvk_warn("SPIR-V binary is invalid."); |
| return true; |
| } |
| |
| cvk_error("SPIR-V binary is invalid."); |
| return false; |
| } |
| |
| const uint32_t clvk_binary_magic = |
| 0x6B766C63; // "clvk" in ASCII in little-endian |
| const uint32_t clvk_binary_version = 1; |
| struct clvk_binary_header { |
| uint32_t magic; |
| uint32_t version; |
| uint32_t binary_type; |
| }; |
| #define COPY_WORD(dst, src) \ |
| do { \ |
| ((unsigned char*)dst)[0] = ((unsigned char*)(src))[0]; \ |
| ((unsigned char*)dst)[1] = ((unsigned char*)(src))[1]; \ |
| ((unsigned char*)dst)[2] = ((unsigned char*)(src))[2]; \ |
| ((unsigned char*)dst)[3] = ((unsigned char*)(src))[3]; \ |
| } while (0) |
| |
| } // namespace |
| |
| bool cvk_program::read_llvm_bitcode(const unsigned char* src, size_t size) { |
| if (size >= 4 && |
| (src[0] == 'B' && src[1] == 'C' && src[2] == 0xc0 && src[3] == 0xde)) { |
| m_ir.resize(size); |
| memcpy(m_ir.data(), src, size); |
| m_dev_status[m_context->device()] = CL_BUILD_SUCCESS; |
| return true; |
| } |
| return false; |
| } |
| |
| void cvk_program::write_binary_header(unsigned char* dst) const { |
| struct clvk_binary_header* header = (struct clvk_binary_header*)dst; |
| COPY_WORD(&header->magic, &clvk_binary_magic); |
| COPY_WORD(&header->version, &clvk_binary_version); |
| COPY_WORD(&header->binary_type, &m_binary_type); |
| } |
| |
| cl_program_binary_type cvk_program::read_binary_header(const unsigned char* src, |
| size_t size) { |
| struct clvk_binary_header* header = (struct clvk_binary_header*)src; |
| if (size < sizeof(header)) { |
| return CL_PROGRAM_BINARY_TYPE_NONE; |
| } |
| uint32_t magic, version, binary_type; |
| COPY_WORD(&magic, &header->magic); |
| COPY_WORD(&version, &header->version); |
| COPY_WORD(&binary_type, &header->binary_type); |
| if (magic != clvk_binary_magic) { |
| cvk_info_fn("magic not found"); |
| return CL_PROGRAM_BINARY_TYPE_NONE; |
| } |
| if (version != clvk_binary_version) { |
| cvk_warn_fn("wrong version"); |
| return CL_PROGRAM_BINARY_TYPE_NONE; |
| } |
| return binary_type; |
| } |
| |
| bool cvk_program::read(const unsigned char* src, size_t size) { |
| bool success = false; |
| auto binary_type = read_binary_header(src, size); |
| // if the binary does not have a clvk binary header, let's try to read |
| // it first as a llvm ir buffer, then as a vulkan spirv buffer. |
| if (binary_type == CL_PROGRAM_BINARY_TYPE_NONE) { |
| cvk_info_fn("no clvk binary header found, looking for llvm bitcode"); |
| if (read_llvm_bitcode(src, size)) { |
| m_binary_type = CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT; |
| cvk_info_fn("llvm bitcode compiled object found"); |
| return true; |
| } |
| cvk_info_fn("llvm bitcode not found, looking for Vulkan SPIR-V binary"); |
| if (m_binary.read(src, size)) { |
| m_binary_type = CL_PROGRAM_BINARY_TYPE_EXECUTABLE; |
| cvk_info_fn("Vulkan SPIR-V binary executable found"); |
| return true; |
| } |
| cvk_error_fn("unable to read binary"); |
| return success; |
| } |
| |
| auto header_size = sizeof(struct clvk_binary_header); |
| src += header_size; |
| size -= header_size; |
| |
| switch (binary_type) { |
| case CL_PROGRAM_BINARY_TYPE_LIBRARY: |
| case CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT: |
| success = read_llvm_bitcode(src, size); |
| break; |
| case CL_PROGRAM_BINARY_TYPE_EXECUTABLE: |
| success = m_binary.read(src, size); |
| break; |
| } |
| if (success) { |
| m_binary_type = binary_type; |
| } |
| return success; |
| } |
| |
| bool cvk_program::write(unsigned char* dst) const { |
| write_binary_header(dst); |
| dst += sizeof(struct clvk_binary_header); |
| |
| switch (m_binary_type) { |
| case CL_PROGRAM_BINARY_TYPE_LIBRARY: |
| case CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT: |
| memcpy(dst, m_ir.data(), m_ir.size()); |
| return true; |
| case CL_PROGRAM_BINARY_TYPE_EXECUTABLE: |
| return m_binary.write(dst); |
| } |
| return false; |
| } |
| |
| size_t cvk_program::binary_size() const { |
| auto header_size = sizeof(struct clvk_binary_header); |
| switch (m_binary_type) { |
| case CL_PROGRAM_BINARY_TYPE_LIBRARY: |
| case CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT: |
| return header_size + m_ir.size(); |
| case CL_PROGRAM_BINARY_TYPE_EXECUTABLE: |
| return header_size + m_binary.size(); |
| } |
| return 0; |
| } |
| |
| std::string cvk_program::prepare_build_options(const cvk_device* device) const { |
| // Strip off a few options we can't handle |
| std::string options; |
| if (m_build_options.size() > 0) { |
| options += " "; |
| options += m_build_options; |
| } |
| std::vector<std::pair<std::string, std::string>> option_substitutions = { |
| // FIXME The 1.2 conformance tests shouldn't pass this option. |
| // It doesn't exist after OpenCL 1.0. |
| {"-cl-strict-aliasing", ""}, |
| // clspv require entrypoint inlining for OpenCL 2.0 and OpenCL 3.0 (for |
| // generic addrspace for example). |
| {"-cl-std=CL2.0", "-cl-std=CL2.0 -inline-entry-points"}, |
| {"-cl-std=CL3.0", "-cl-std=CL3.0 -inline-entry-points"}, |
| {"-create-library", ""}, |
| }; |
| |
| for (auto& subst : option_substitutions) { |
| size_t loc = options.find(subst.first); |
| if (loc != std::string::npos) { |
| options.replace(loc, subst.first.length(), subst.second); |
| } |
| } |
| |
| // Check for some options we need and add them if not present. |
| std::string necessary_options[] = { |
| "-cl-single-precision-constant", |
| "-cl-kernel-arg-info", |
| }; |
| for (std::string option : necessary_options) { |
| if (options.find(option) == std::string::npos) { |
| options += " " + option + " "; |
| } |
| } |
| |
| // The device sets up some compiler options based on its capabilities. |
| options += " " + device->get_device_specific_compile_options() + " "; |
| |
| // Features |
| if (options.find("-cl-std=CL3.0") != std::string::npos) { |
| auto features = device->opencl_c_features(); |
| if (!features.empty()) { |
| options += "-enable-feature-macros="; |
| for (auto& feature : features) { |
| options += feature.name; |
| options += ','; |
| } |
| options.back() = ' '; // replace the final comma |
| } |
| } |
| |
| auto buff_size = m_context->get_printf_buffersize(); |
| |
| options += " -enable-printf "; |
| options += " -printf-buffer-size=" + std::to_string(buff_size) + " "; |
| |
| #if COMPILER_AVAILABLE |
| options += " " + config.clspv_options() + " "; |
| #endif |
| // split options into a vector |
| std::istringstream iss(options); |
| std::vector<std::string> vector_options; |
| std::string token; |
| while (std::getline(iss, token, ' ')) { |
| vector_options.push_back(token); |
| } |
| |
| // loop through the options and quote the ones that need it |
| std::string quoted_options; |
| for (size_t i = 0; i < vector_options.size(); i++) { |
| if (vector_options[i].empty()) { |
| continue; |
| } |
| if (vector_options[i].find("-") == 0) { |
| quoted_options += vector_options[i]; |
| } else { |
| quoted_options += "\"" + vector_options[i] + "\""; |
| } |
| quoted_options += " "; |
| } |
| |
| return quoted_options; |
| } |
| |
| cl_int cvk_program::parse_user_spec_constants() { |
| #if COMPILER_AVAILABLE && ENABLE_SPIRV_IL |
| #ifndef CLSPV_ONLINE_COMPILER |
| // We'll need to go through the whole temp folder rigamarole to query the |
| // spec constant info with the command line tool. |
| std::filesystem::path tmp_prefix(config.compiler_temp_dir()); |
| std::filesystem::path tmp_suffix("clvk-XXXXXX"); |
| std::string tmp_template = (tmp_prefix / tmp_suffix).string(); |
| const char* tmp = cvk_mkdtemp(tmp_template); |
| if (tmp == nullptr) { |
| cvk_error_fn("Could not create temporary folder \"%s\"", |
| tmp_template.c_str()); |
| return CL_INVALID_VALUE; |
| } |
| std::string tmp_folder = tmp; |
| cvk_info("Created temporary folder \"%s\"", tmp_folder.c_str()); |
| |
| std::string llvmspirv_input_file = tmp_folder + "/source.spv"; |
| |
| if (!save_il_to_file(llvmspirv_input_file, m_il)) { |
| cvk_error_fn("Couldn't save IL to file!"); |
| return CL_INVALID_VALUE; |
| } |
| |
| std::string cmd_spv{config.llvmspirv_bin()}; |
| cmd_spv += " --spec-const-info "; |
| cmd_spv += llvmspirv_input_file; |
| |
| std::string output = ""; |
| cvk_exec(cmd_spv, &output); |
| auto output_stream = std::istringstream(output); |
| |
| for (std::string line; std::getline(output_stream, line);) { |
| if (line.find("Spec const id") == std::string::npos) { |
| continue; |
| } |
| |
| auto delim = line.find(","); |
| auto id_string = line.substr(0, delim); |
| line.erase(0, delim + 1); |
| id_string = id_string.substr(id_string.find("=") + 1); |
| auto id = static_cast<uint32_t>(atoi(id_string.c_str())); |
| |
| delim = line.find(","); |
| auto size_string = line.substr(0, delim); |
| line.erase(0, delim + 1); |
| size_string = size_string.substr(size_string.find("=") + 1); |
| auto size = static_cast<uint32_t>(atoi(size_string.c_str())); |
| |
| // We need to consume the "=" and the " " here since we aren't |
| // converting the value from a string. |
| auto type_string = line.substr(line.find("=") + 2); |
| m_user_spec_constants.emplace( |
| id, user_spec_constant_data{type_string, size}); |
| } |
| std::filesystem::remove_all(tmp_folder.c_str()); |
| return CL_SUCCESS; |
| #else |
| auto m_il_start = reinterpret_cast<const unsigned char*>(m_il.data()); |
| membuf m_il_buf(m_il_start, m_il_start + m_il.size()); |
| std::istream m_il_stream(&m_il_buf); |
| SPIRV::TranslatorOpts translator_opts; |
| // We don't need llvm-spirv to validate extensions for us. |
| translator_opts.enableAllExtensions(); |
| static std::mutex llvmspirv_compile_mutex; |
| std::lock_guard<std::mutex> llvmspirv_compile_lock(llvmspirv_compile_mutex); |
| |
| std::vector<llvm::SpecConstInfoTy> spec_const_info; |
| |
| if (!getSpecConstInfo(m_il_stream, spec_const_info)) { |
| cvk_error_fn("Failed to parse spec constants"); |
| return CL_INVALID_VALUE; |
| } |
| |
| for (const auto& spec_const : spec_const_info) { |
| m_user_spec_constants.emplace( |
| spec_const.ID, |
| user_spec_constant_data{spec_const.Type, spec_const.Size}); |
| } |
| return CL_SUCCESS; |
| #endif // CLSPV_ONLINE_COMPILER |
| #else |
| #if !COMPILER_AVAILABLE |
| cvk_error_fn("Could not parse user spec constants because clvk has been " |
| "built with CLVK_COMPILER_AVAILABLE=OFF"); |
| #elif !ENABLE_SPIRV_IL |
| cvk_error_fn("Could not parse user spec constants because clvk has been " |
| "built with CLVK_ENABLE_SPIRV_IL=OFF"); |
| #endif |
| return CL_INVALID_OPERATION; |
| #endif // COMPILER_AVAILABLE |
| } |
| |
| #if COMPILER_AVAILABLE |
| #ifndef CLSPV_ONLINE_COMPILER |
| cl_build_status cvk_program::do_build_inner_offline(bool build_to_ir, |
| bool build_from_il, |
| std::string& build_options, |
| std::string& tmp_folder) { |
| TRACE_FUNCTION("build_to_ir", build_to_ir, "build_from_il", build_from_il, |
| "build_options", TRACE_STRING(build_options.c_str())); |
| // Compose clspv command-line |
| std::string cmd{config.clspv_path}; |
| cmd += " "; |
| |
| std::string clspv_input_file{tmp_folder + "/source"}; |
| // Save input program to a file |
| if (build_from_il) { |
| #ifndef ENABLE_SPIRV_IL |
| cvk_error_fn("Could not build from il because clvk has been built with " |
| "CLVK_ENABLE_SPIRV_IL=OFF"); |
| return CL_BUILD_ERROR; |
| #else // ENABLE_SPIRV_IL |
| std::string llvmspirv_input_file{tmp_folder + "/source.spv"}; |
| clspv_input_file += ".bc"; |
| if (!save_il_to_file(llvmspirv_input_file, m_il)) { |
| cvk_error_fn("Couldn't save IL to file!"); |
| return CL_BUILD_ERROR; |
| } |
| // Compose llvm-spirv command-line |
| std::string cmd_spv{config.llvmspirv_bin}; |
| |
| if (!m_user_spec_constants.empty()) { |
| std::string spec_constant_flag = " --spec-const="; |
| for (const auto& spec_const : m_user_spec_constants) { |
| if (!spec_const.second.set) { |
| continue; |
| } |
| const auto& type_string = spec_const.second.type; |
| spec_constant_flag += |
| std::to_string(spec_const.first) + ":" + type_string + ":"; |
| |
| if (type_string.find("i32") != std::string::npos) { |
| spec_constant_flag += |
| std::to_string(spec_const.second.data.i32); |
| } else if (type_string.find("i16") != std::string::npos) { |
| spec_constant_flag += |
| std::to_string(spec_const.second.data.i16); |
| } else if (type_string.find("i8") != std::string::npos || |
| type_string.find("i1") != std::string::npos) { |
| spec_constant_flag += |
| std::to_string(spec_const.second.data.i8); |
| } else if (type_string.find("i64") != std::string::npos) { |
| spec_constant_flag += |
| std::to_string(spec_const.second.data.i64); |
| } else if (type_string.find("f16") != std::string::npos) { |
| // At most we should need seven (0xFFFF\0) |
| char buf[7]; |
| std::snprintf(buf, 7, "0x%04X", spec_const.second.data.i16); |
| spec_constant_flag += std::string(buf); |
| } else if (type_string.find("f32") != std::string::npos) { |
| float spec_value; |
| std::memcpy(&spec_value, &spec_const.second.data.i32, |
| spec_const.second.size); |
| spec_constant_flag += std::to_string(spec_value); |
| } else if (type_string.find("f64") != std::string::npos) { |
| double spec_value; |
| std::memcpy(&spec_value, &spec_const.second.data.i64, |
| spec_const.second.size); |
| spec_constant_flag += std::to_string(spec_value); |
| } |
| spec_constant_flag += " "; |
| } |
| cmd_spv += spec_constant_flag; |
| } |
| |
| cmd_spv += " -r "; |
| cmd_spv += " -o "; |
| cmd_spv += clspv_input_file; |
| cmd_spv += " "; |
| cmd_spv += llvmspirv_input_file; |
| |
| // Call the translator |
| int status = cvk_exec(cmd_spv); |
| |
| if (status != 0) { |
| cvk_error_fn("failed to translate SPIR-V to LLVM IR"); |
| return CL_BUILD_ERROR; |
| } |
| |
| cmd += clspv_input_file; |
| cmd += " "; |
| #endif // ENABLE_SPIRV_IL |
| } else if (m_operation == build_operation::link) { |
| for (auto input_program : m_input_programs) { |
| if (input_program->m_binary_type != |
| CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT && |
| input_program->m_binary_type != |
| CL_PROGRAM_BINARY_TYPE_LIBRARY) { |
| return CL_BUILD_ERROR; |
| } |
| std::string input_file = clspv_input_file + "_" + |
| std::to_string((uintptr_t)input_program) + |
| ".bc"; |
| if (!save_il_to_file(input_file, input_program->m_ir)) { |
| cvk_error_fn("Couldn't save source to file!"); |
| return CL_BUILD_ERROR; |
| } |
| cmd += input_file; |
| cmd += " "; |
| } |
| } else { |
| if (m_source.empty() && !m_ir.empty()) { |
| clspv_input_file += ".bc"; |
| if (!save_il_to_file(clspv_input_file, m_ir)) { |
| cvk_error_fn("Couldn't save source to file!"); |
| return CL_BUILD_ERROR; |
| } |
| cmd += clspv_input_file; |
| cmd += " "; |
| } else { |
| clspv_input_file += ".cl"; |
| if (!save_string_to_file(clspv_input_file, m_source)) { |
| cvk_error_fn("Couldn't save source to file!"); |
| return CL_BUILD_ERROR; |
| } |
| cmd += clspv_input_file; |
| cmd += " "; |
| } |
| } |
| |
| std::string clspv_output_file{tmp_folder + "/compiled"}; |
| if (build_to_ir) { |
| clspv_output_file += ".bc"; |
| } else { |
| clspv_output_file += ".spv"; |
| } |
| |
| cmd += build_options; |
| cmd += " -o "; |
| cmd += clspv_output_file; |
| |
| // Call clspv |
| int status = cvk_exec(cmd, &m_build_log); |
| if (status != 0) { |
| cvk_error_fn("failed to compile the program"); |
| cvk_debug_fn("%s", m_build_log.c_str()); |
| return CL_BUILD_ERROR; |
| } |
| |
| // Load output from clspv |
| if (build_to_ir) { |
| std::ifstream stream(clspv_output_file, |
| std::ios::in | std::ios::binary); |
| m_ir.assign((std::istreambuf_iterator<char>(stream)), |
| std::istreambuf_iterator<char>()); |
| if (!stream.good()) { |
| return CL_BUILD_ERROR; |
| } |
| } else { |
| const char* filename = clspv_output_file.c_str(); |
| if (!m_binary.load(filename)) { |
| cvk_error("Could not load SPIR-V binary from \"%s\"", filename); |
| return CL_BUILD_ERROR; |
| } |
| } |
| |
| cvk_info("Loaded %s binary from \"%s\", size = %zu %s", |
| build_to_ir ? "IR" : "SPIR-V", clspv_output_file.c_str(), |
| build_to_ir ? m_ir.size() : m_binary.code().size(), |
| build_to_ir ? "bytes" : "words"); |
| |
| return CL_BUILD_SUCCESS; |
| } |
| |
| #else // #ifndef CLSPV_ONLINE_COMPILER |
| |
| cl_build_status cvk_program::do_build_inner_online(bool build_to_ir, |
| bool build_from_il, |
| std::string& build_options) { |
| TRACE_FUNCTION("build_to_ir", build_to_ir, "build_from_il", build_from_il, |
| "build_options", TRACE_STRING(build_options.c_str())); |
| cvk_info_fn("build_from_il %u - build_to_ir %u", build_from_il, |
| build_to_ir); |
| if (build_from_il) { |
| #ifndef ENABLE_SPIRV_IL |
| cvk_error_fn("Could not build from il because clvk has been built with " |
| "CLVK_ENABLE_SPIRV_IL=OFF"); |
| return CL_BUILD_ERROR; |
| #else // ENABLE_SPIRV_IL |
| llvm::LLVMContext llvm_context; |
| llvm::Module* llvm_module; |
| std::string err; |
| auto m_il_start = reinterpret_cast<const unsigned char*>(m_il.data()); |
| membuf m_il_buf(m_il_start, m_il_start + m_il.size()); |
| std::istream m_il_stream(&m_il_buf); |
| SPIRV::TranslatorOpts translator_opts; |
| // We don't need llvm-spirv to validate extensions for us. |
| translator_opts.enableAllExtensions(); |
| |
| for (const auto& spec_const : m_user_spec_constants) { |
| if (!spec_const.second.set) { |
| continue; |
| } |
| uint64_t spec_const_value = 0; |
| if (spec_const.second.type == "i1" || |
| spec_const.second.type == "i8") { |
| spec_const_value = spec_const.second.data.i8; |
| } else if (spec_const.second.type == "i16" || |
| spec_const.second.type == "f16") { |
| spec_const_value = spec_const.second.data.i16; |
| } else if (spec_const.second.type == "i32" || |
| spec_const.second.type == "f32") { |
| spec_const_value = spec_const.second.data.i32; |
| } else if (spec_const.second.type == "i64" || |
| spec_const.second.type == "f64") { |
| spec_const_value = spec_const.second.data.i64; |
| } |
| translator_opts.setSpecConst(spec_const.first, spec_const_value); |
| } |
| |
| // llvm-spirv is based on LLVM. LLVM options parsing is done using |
| // global variable that are not thread safe. Thus, we need to lock call |
| // to llvm-spirv in order to ensure a thread safe execution. |
| { |
| static std::mutex llvmspirv_compile_mutex; |
| std::lock_guard<std::mutex> llvmspirv_compile_lock( |
| llvmspirv_compile_mutex); |
| std::vector<const char*> llvmArgv{ |
| "llvm-spirv(online)", |
| }; |
| llvm::cl::ResetAllOptionOccurrences(); |
| llvm::cl::ParseCommandLineOptions(llvmArgv.size(), llvmArgv.data()); |
| if (!llvm::readSpirv(llvm_context, translator_opts, m_il_stream, |
| llvm_module, err)) { |
| cvk_error_fn("Fails to load SPIR-V as LLVM Module: %s", |
| err.c_str()); |
| return CL_BUILD_ERROR; |
| } |
| } |
| |
| m_source.clear(); |
| llvm::raw_string_ostream spirv_stream(m_source); |
| llvm::WriteBitcodeToFile(*llvm_module, spirv_stream); |
| #endif // ENABLE_SPIRV_IL |
| } |
| cvk_info("About to compile \"%s\"", build_options.c_str()); |
| int status; |
| // clspv is based on LLVM. LLVM options parsing is done using global |
| // variable that are not thread safe. Thus, we need to lock call to clspv in |
| // order to ensure a thread safe execution. |
| { |
| static std::mutex clspv_compile_mutex; |
| std::lock_guard<std::mutex> clspv_compile_lock(clspv_compile_mutex); |
| std::vector<std::string> programs; |
| if (m_operation == build_operation::link) { |
| for (auto input_program : m_input_programs) { |
| if (input_program->m_binary_type != |
| CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT && |
| input_program->m_binary_type != |
| CL_PROGRAM_BINARY_TYPE_LIBRARY) { |
| return CL_BUILD_ERROR; |
| } |
| programs.emplace_back(std::string{input_program->m_ir.begin(), |
| input_program->m_ir.end()}); |
| } |
| } else { |
| if (m_source.empty() && !m_ir.empty()) { |
| programs.emplace_back(std::string{m_ir.begin(), m_ir.end()}); |
| } else { |
| programs.emplace_back(m_source); |
| } |
| } |
| m_build_log.clear(); |
| if (build_to_ir) { |
| std::vector<uint32_t> ir; |
| status = clspv::CompileFromSourcesString(programs, build_options, |
| &ir, &m_build_log); |
| m_ir.clear(); |
| auto size = ir.size() * sizeof(uint32_t); |
| m_ir.resize(size); |
| memcpy(m_ir.data(), ir.data(), size); |
| } else { |
| status = clspv::CompileFromSourcesString( |
| programs, build_options, m_binary.raw_binary(), &m_build_log); |
| } |
| } |
| if (status != 0) { |
| cvk_error_fn("failed to compile the program"); |
| cvk_debug_fn("%s", m_build_log.c_str()); |
| return CL_BUILD_ERROR; |
| } |
| return CL_BUILD_SUCCESS; |
| } |
| |
| #endif // #ifndef CLSPV_ONLINE_COMPILER |
| #endif // #if COMPILER_AVAILABLE |
| |
| cl_build_status cvk_program::do_build_inner(const cvk_device* device) { |
| #if !COMPILER_AVAILABLE |
| UNUSED(device); |
| #else |
| // m_il and m_source cannot be set together. |
| // They can both be empty when loading from binary. |
| CVK_ASSERT(m_il.empty() || m_source.empty()); |
| |
| bool build_from_il = |
| m_il.size() > 0 && m_operation != build_operation::link; |
| bool use_tmp_folder = true; |
| #ifdef CLSPV_ONLINE_COMPILER |
| use_tmp_folder = |
| m_operation == build_operation::compile && m_num_input_programs > 0; |
| #endif |
| |
| std::string tmp_folder; |
| if (use_tmp_folder) { |
| // Create temporary folder |
| std::filesystem::path tmp_prefix(config.compiler_temp_dir()); |
| std::filesystem::path tmp_suffix("clvk-XXXXXX"); |
| std::string tmp_template = (tmp_prefix / tmp_suffix).string(); |
| const char* tmp = cvk_mkdtemp(tmp_template); |
| if (tmp == nullptr) { |
| cvk_error_fn("Could not create temporary folder \"%s\"", |
| tmp_template.c_str()); |
| return CL_BUILD_ERROR; |
| } |
| tmp_folder = tmp; |
| cvk_info("Created temporary folder \"%s\"", tmp_folder.c_str()); |
| } |
| temp_folder_deletion temp(tmp_folder); |
| |
| // Prepare build options |
| bool create_library = |
| m_build_options.find("create-library") != std::string::npos; |
| auto build_options = prepare_build_options(device); |
| |
| // Add options to specify input/output types |
| if (m_source.empty() || m_operation == build_operation::link) { |
| build_options += " -x ir "; |
| } |
| bool build_to_ir = |
| m_operation == build_operation::compile || create_library; |
| if (build_to_ir) { |
| build_options += " --output-format=bc "; |
| } |
| |
| // Save headers |
| if (use_tmp_folder && m_operation == build_operation::compile) { |
| build_options += "-I" + tmp_folder; |
| for (cl_uint i = 0; i < m_num_input_programs; i++) { |
| std::string fname{tmp_folder + "/" + m_header_include_names[i]}; |
| if (!save_string_to_file(fname, m_input_programs[i]->source())) { |
| cvk_error_fn("Couldn't save header to file!"); |
| return CL_BUILD_ERROR; |
| } |
| } |
| } |
| |
| cl_build_status build_status; |
| #ifdef CLSPV_ONLINE_COMPILER |
| build_status = |
| do_build_inner_online(build_to_ir, build_from_il, build_options); |
| #else |
| build_status = do_build_inner_offline(build_to_ir, build_from_il, |
| build_options, tmp_folder); |
| #endif // CLSPV_ONLINE_COMPILER |
| if (build_status != CL_BUILD_SUCCESS) { |
| return build_status; |
| } |
| |
| // Select operation |
| if (m_operation == build_operation::compile) { |
| m_binary_type = CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT; |
| } else if (m_operation == build_operation::link && create_library) { |
| m_binary_type = CL_PROGRAM_BINARY_TYPE_LIBRARY; |
| } else { |
| m_binary_type = CL_PROGRAM_BINARY_TYPE_EXECUTABLE; |
| } |
| |
| #endif // #if !COMPILER_AVAILABLE |
| return CL_BUILD_SUCCESS; |
| } |
| |
| void cvk_program::prepare_push_constant_range() { |
| auto& pcs = m_binary.push_constants(); |
| |
| uint32_t min_offset = UINT32_MAX; |
| uint32_t max_offset = 0, max_offset_size = 0; |
| |
| for (auto& pc_pcd : pcs) { |
| auto pcd = pc_pcd.second; |
| min_offset = std::min(min_offset, pcd.offset); |
| if (pcd.offset >= max_offset) { |
| max_offset = pcd.offset; |
| max_offset_size = pcd.size; |
| } |
| } |
| |
| m_push_constant_range = {VK_SHADER_STAGE_COMPUTE_BIT, min_offset, |
| max_offset + max_offset_size}; |
| } |
| |
| bool cvk_program::check_capabilities(const cvk_device* device) { |
| // Get list of required SPIR-V capabilities. |
| std::vector<spv::Capability> capabilities; |
| if (!m_binary.get_capabilities(capabilities)) { |
| cvk_error("Failed to get required SPIR-V capabilities."); |
| return false; |
| } |
| |
| // Check that each required capability is supported by the device. |
| for (auto c : capabilities) { |
| cvk_info_fn("Program requires SPIR-V capability %d (%s).", c, |
| spirv_capability_to_string(c)); |
| if (!device->supports_capability(c) |
| #ifdef CLVK_UNIT_TESTING_ENABLED |
| || config.force_check_capabilities_error() |
| #endif |
| ) { |
| std::stringstream error_message; |
| error_message << "Device does not support SPIR-V capability " << c |
| << " (" << spirv_capability_to_string(c) << ")."; |
| cvk_error_fn("%s", error_message.str().c_str()); |
| m_build_log += error_message.str(); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void cvk_program::do_build() { |
| // Destroy entry points from previous build |
| m_entry_points.clear(); |
| |
| auto device = m_context->device(); |
| |
| if (m_operation != build_operation::build_binary) { |
| cl_build_status status = do_build_inner(device); |
| |
| if ((m_binary_type != CL_PROGRAM_BINARY_TYPE_EXECUTABLE) || |
| (status != CL_BUILD_SUCCESS)) { |
| complete_operation(device, status); |
| return; |
| } |
| } |
| |
| // Load descriptor map |
| if (!m_binary.load_descriptor_map()) { |
| cvk_error("Could not load descriptor map for SPIR-V binary."); |
| complete_operation(device, CL_BUILD_ERROR); |
| return; |
| } |
| |
| if (!create_module_constant_data_buffer()) { |
| complete_operation(device, CL_BUILD_ERROR); |
| return; |
| } |
| |
| prepare_push_constant_range(); |
| |
| bool cache_hit = |
| device->get_pipeline_cache(m_binary.code(), m_pipeline_cache); |
| if (m_pipeline_cache == VK_NULL_HANDLE) { |
| complete_operation(device, CL_BUILD_ERROR); |
| return; |
| } |
| |
| if (!cache_hit) { |
| // Validate |
| // TODO validate with different rules depending on the binary type |
| if (m_binary_type == CL_PROGRAM_BINARY_TYPE_EXECUTABLE) { |
| spirv_validation_options validation_options{}; |
| validation_options.uniform_buffer_std_layout = |
| m_context->device()->supports_ubo_stdlayout(); |
| if (!validate_binary(m_binary, validation_options)) { |
| complete_operation(device, CL_BUILD_ERROR); |
| return; |
| } |
| } |
| } |
| |
| // Check capabilities against the device. |
| if ((m_binary_type == CL_PROGRAM_BINARY_TYPE_EXECUTABLE) && |
| !config.skip_spirv_capability_check && !check_capabilities(device)) { |
| cvk_error("Missing support for required SPIR-V capabilities."); |
| complete_operation(device, CL_BUILD_ERROR); |
| return; |
| } |
| |
| // Create literal samplers |
| for (auto const& desc : literal_sampler_descs()) { |
| auto sampler = |
| cvk_sampler::create(context(), desc.normalized_coords, |
| desc.addressing_mode, desc.filter_mode); |
| if (sampler == nullptr) { |
| complete_operation(device, CL_BUILD_ERROR); |
| return; |
| } |
| m_literal_samplers.emplace_back(sampler); |
| } |
| |
| // Strip the reflection information if non-semantic info is not supported |
| // by the Vulkan implementation. This stripped binary is stored separately |
| // from |m_binary| because clvk needs to be able to provide the binary with |
| // reflection information for clGetProgramInfo. |
| const uint32_t* spir_data = m_binary.spir_data(); |
| size_t spir_size = m_binary.spir_size(); |
| const bool should_strip_reflection = |
| !device->is_vulkan_extension_enabled( |
| VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME) |
| #ifdef USING_SWIFTSHADER |
| || true |
| #endif |
| ; |
| if (should_strip_reflection) { |
| if (!m_binary.strip_reflection(&m_stripped_binary)) { |
| cvk_error_fn("couldn't strip reflection from SPIR-V module"); |
| complete_operation(device, CL_BUILD_ERROR); |
| return; |
| } |
| spir_data = m_stripped_binary.data(); |
| spir_size = m_stripped_binary.size() * sizeof(uint32_t); |
| } |
| |
| // Create a shader module |
| VkDevice dev = device->vulkan_device(); |
| |
| VkShaderModuleCreateInfo moduleCreateInfo = { |
| VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, // sType |
| nullptr, // pNext |
| 0, // flags |
| spir_size, // codeSize |
| spir_data // pCode |
| }; |
| |
| VkResult res = |
| vkCreateShaderModule(dev, &moduleCreateInfo, nullptr, &m_shader_module); |
| |
| if (res != VK_SUCCESS) { |
| cvk_error("vkCreateShaderModule returned %d", res); |
| complete_operation(device, CL_BUILD_ERROR); |
| return; |
| } |
| |
| complete_operation(device, CL_BUILD_SUCCESS); |
| } |
| |
| cl_int cvk_program::build(build_operation operation, cl_uint num_devices, |
| const cl_device_id* device_list, const char* options, |
| cl_uint num_input_programs, |
| const cl_program* input_programs, |
| const char** header_include_names, |
| cvk_program_callback cb, void* data) { |
| std::lock_guard<std::mutex> lock(m_lock); |
| |
| // Check if there is already a build in progress |
| // TODO: Allow concurrent builds targeting different devices |
| if (std::count_if(m_dev_status.begin(), m_dev_status.end(), |
| [](auto& status) { |
| return status.second == CL_BUILD_IN_PROGRESS; |
| })) { |
| return CL_INVALID_OPERATION; |
| } |
| |
| retain(); |
| |
| for (cl_uint i = 0; i < num_input_programs; i++) { |
| cvk_program* iprog = |
| const_cast<cvk_program*>(icd_downcast(input_programs[i])); |
| iprog->retain(); |
| m_input_programs.push_back(iprog); |
| if (header_include_names != nullptr) { |
| m_header_include_names.push_back(header_include_names[i]); |
| } |
| } |
| |
| // Mark build in-progress and save devices |
| if (num_devices == 0) { |
| m_num_devices = 1u; |
| m_dev_status[m_context->device()] = CL_BUILD_IN_PROGRESS; |
| } else { |
| m_num_devices = num_devices; |
| for (cl_uint i = 0; i < num_devices; i++) { |
| m_dev_status[icd_downcast(device_list[i])] = CL_BUILD_IN_PROGRESS; |
| } |
| } |
| |
| if (options != nullptr) { |
| m_build_options = options; |
| } |
| m_num_input_programs = num_input_programs; |
| m_operation = operation; |
| m_operation_callback = cb; |
| m_operation_callback_data = data; |
| |
| cl_int ret = CL_SUCCESS; |
| bool build_in_separate_thread = config.build_in_separate_thread() || cb; |
| bool wait_for_completion = !cb; |
| if (build_in_separate_thread) { |
| // Kick off build |
| m_thread = std::make_unique<std::thread>( |
| &cvk_program::do_build_in_separate_thread, this); |
| if (!wait_for_completion) { |
| m_thread->detach(); |
| } |
| } else { |
| do_build(); |
| } |
| |
| if (wait_for_completion) { |
| if (build_in_separate_thread) { |
| CVK_ASSERT(m_thread->joinable()); |
| m_thread->join(); |
| } |
| if (build_status() != CL_BUILD_SUCCESS) { |
| switch (operation) { |
| case build_operation::link: |
| ret = CL_LINK_PROGRAM_FAILURE; |
| break; |
| case build_operation::build: |
| case build_operation::build_binary: |
| ret = CL_BUILD_PROGRAM_FAILURE; |
| break; |
| case build_operation::compile: |
| ret = CL_COMPILE_PROGRAM_FAILURE; |
| break; |
| } |
| CVK_ASSERT(ret != CL_SUCCESS); |
| } |
| } |
| return ret; |
| } |
| |
| cvk_entry_point::cvk_entry_point(cvk_device* dev, cvk_program* program, |
| const std::string& name) |
| : m_device(dev), m_context(program->context()), m_program(program), |
| m_name(name), m_pod_descriptor_type(VK_DESCRIPTOR_TYPE_MAX_ENUM), |
| m_pod_buffer_size(0u), m_has_pod_arguments(false), |
| m_has_pod_buffer_arguments(false), m_sampler_metadata(nullptr), |
| m_image_metadata(nullptr), m_descriptor_pool(VK_NULL_HANDLE), |
| m_pipeline_layout(VK_NULL_HANDLE), m_nb_descriptor_set_allocated(0), |
| m_first_allocation_failure(true) { |
| TRACE_CNT_VAR_INIT(descriptor_set_allocated_counter, |
| "clvk-entry_point_" + std::to_string((uintptr_t)this), |
| dev->track()); |
| TRACE_CNT(descriptor_set_allocated_counter, 0); |
| } |
| |
| std::shared_ptr<cvk_entry_point> |
| cvk_program::get_entry_point(std::string& name, cl_int* errcode_ret) { |
| std::lock_guard<std::mutex> lock(m_lock); |
| |
| // Check for existing entry point in cache |
| if (m_entry_points.count(name)) { |
| *errcode_ret = CL_SUCCESS; |
| return m_entry_points.at(name); |
| } |
| |
| // Create and initialize entry point |
| std::shared_ptr<cvk_entry_point> entry_point = |
| std::make_shared<cvk_entry_point>(m_context->device(), this, name); |
| *errcode_ret = entry_point->init(); |
| if (*errcode_ret != CL_SUCCESS) { |
| return nullptr; |
| } |
| |
| // Add to cache for reuse by other kernels |
| m_entry_points.insert({name, entry_point}); |
| |
| return entry_point; |
| } |
| |
| bool cvk_entry_point::build_descriptor_set_layout( |
| const std::vector<VkDescriptorSetLayoutBinding>& bindings) { |
| VkDescriptorSetLayoutCreateInfo createInfo = { |
| VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, |
| 0, // flags |
| static_cast<uint32_t>(bindings.size()), // bindingCount |
| bindings.data() // pBindings |
| }; |
| |
| VkResult res; |
| if (bindings.size() > 0) { |
| VkDescriptorSetLayout setLayout; |
| res = vkCreateDescriptorSetLayout(m_device->vulkan_device(), |
| &createInfo, 0, &setLayout); |
| if (res != VK_SUCCESS) { |
| cvk_error("Could not create descriptor set layout"); |
| return false; |
| } |
| m_descriptor_set_layouts.push_back(setLayout); |
| } |
| |
| return true; |
| } |
| |
| bool cvk_entry_point::build_descriptor_sets_layout_bindings_for_arguments( |
| binding_stat_map& smap, uint32_t& num_resource_slots) { |
| bool pod_found = false; |
| |
| uint32_t highest_binding = 0; |
| |
| std::vector<VkDescriptorSetLayoutBinding> layoutBindings; |
| for (auto& arg : m_args) { |
| VkDescriptorType dt = VK_DESCRIPTOR_TYPE_MAX_ENUM; |
| |
| switch (arg.kind) { |
| case kernel_argument_kind::buffer: |
| dt = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; |
| break; |
| case kernel_argument_kind::buffer_ubo: |
| dt = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; |
| break; |
| case kernel_argument_kind::sampled_image: |
| dt = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; |
| break; |
| case kernel_argument_kind::storage_image: |
| dt = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; |
| break; |
| case kernel_argument_kind::storage_texel_buffer: |
| dt = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER; |
| break; |
| case kernel_argument_kind::uniform_texel_buffer: |
| dt = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; |
| break; |
| case kernel_argument_kind::sampler: |
| dt = VK_DESCRIPTOR_TYPE_SAMPLER; |
| break; |
| case kernel_argument_kind::local: |
| continue; |
| case kernel_argument_kind::pod: |
| case kernel_argument_kind::pod_ubo: |
| case kernel_argument_kind::pointer_ubo: |
| if (!pod_found) { |
| if (arg.kind == kernel_argument_kind::pod) { |
| dt = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; |
| } else if (arg.kind == kernel_argument_kind::pod_ubo || |
| arg.kind == kernel_argument_kind::pointer_ubo) { |
| dt = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; |
| } |
| |
| m_pod_descriptor_type = dt; |
| |
| pod_found = true; |
| } else { |
| continue; |
| } |
| break; |
| case kernel_argument_kind::pod_pushconstant: |
| case kernel_argument_kind::pointer_pushconstant: |
| case kernel_argument_kind::unused: |
| continue; |
| } |
| |
| VkDescriptorSetLayoutBinding binding = { |
| arg.binding, // binding |
| dt, // descriptorType |
| 1, // decriptorCount |
| VK_SHADER_STAGE_COMPUTE_BIT, // stageFlags |
| nullptr // pImmutableSamplers |
| }; |
| |
| highest_binding = std::max(arg.binding, highest_binding); |
| |
| layoutBindings.push_back(binding); |
| smap[binding.descriptorType]++; |
| } |
| |
| num_resource_slots = highest_binding + 1; |
| |
| if (!build_descriptor_set_layout(layoutBindings)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool cvk_entry_point:: |
| build_descriptor_sets_layout_bindings_for_literal_samplers( |
| binding_stat_map& smap) { |
| |
| std::vector<VkDescriptorSetLayoutBinding> layoutBindings; |
| for (auto& desc : m_program->literal_sampler_descs()) { |
| VkDescriptorSetLayoutBinding binding = { |
| desc.binding, // binding |
| VK_DESCRIPTOR_TYPE_SAMPLER, // descriptorType |
| 1, // decriptorCount |
| VK_SHADER_STAGE_COMPUTE_BIT, // stageFlags |
| nullptr // pImmutableSamplers |
| }; |
| layoutBindings.push_back(binding); |
| smap[binding.descriptorType]++; |
| } |
| |
| if (!build_descriptor_set_layout(layoutBindings)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool cvk_entry_point:: |
| build_descriptor_sets_layout_bindings_for_program_scope_buffers( |
| binding_stat_map& smap) { |
| |
| std::vector<VkDescriptorSetLayoutBinding> layoutBindings; |
| if (m_program->module_constant_data_buffer() != nullptr) { |
| auto info = m_program->module_constant_data_buffer_info(); |
| // If the program scope buffer isn't passed as a storage buffer (i.e. |
| // it is passed a pointer push constant), there is nothing to bind here |
| if (info->type != module_buffer_type::storage_buffer) { |
| return true; |
| } |
| VkDescriptorSetLayoutBinding binding = { |
| info->binding, // binding |
| VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // descriptorType |
| 1, // decriptorCount |
| VK_SHADER_STAGE_COMPUTE_BIT, // stageFlags |
| nullptr // pImmutableSamplers |
| }; |
| layoutBindings.push_back(binding); |
| smap[binding.descriptorType]++; |
| } |
| |
| if (!build_descriptor_set_layout(layoutBindings)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool cvk_entry_point::build_descriptor_sets_layout_bindings_for_printf_buffer( |
| binding_stat_map& smap) { |
| std::vector<VkDescriptorSetLayoutBinding> layoutBindings; |
| if (m_program->printf_buffer_info().size > 0 && (uses_printf())) { |
| auto info = m_program->printf_buffer_info(); |
| if (info.type != module_buffer_type::storage_buffer) { |
| return true; |
| } |
| VkDescriptorSetLayoutBinding binding = { |
| info.binding, // binding |
| VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // descriptorType |
| 1, // decriptorCount |
| VK_SHADER_STAGE_COMPUTE_BIT, // stageFlags |
| nullptr // pImmutableSamplers |
| }; |
| layoutBindings.push_back(binding); |
| smap[binding.descriptorType]++; |
| } |
| |
| if (!build_descriptor_set_layout(layoutBindings)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| cl_int cvk_entry_point::init() { |
| VkResult res; |
| |
| // Get the image metadata for the this entry point |
| if (auto* md = m_program->image_metadata(m_name)) { |
| m_image_metadata = md; |
| } |
| |
| // Get the sampler metadata for this entry point |
| if (auto* md = m_program->sampler_metadata(m_name)) { |
| m_sampler_metadata = md; |
| } |
| |
| // Get a pointer to the arguments from the program |
| auto args = m_program->args_for_kernel(m_name); |
| |
| if (args == nullptr) { |
| cvk_error("Kernel %s doesn't exist in program", m_name.c_str()); |
| return CL_INVALID_KERNEL_NAME; |
| } |
| |
| // Store a sorted copy of the arguments |
| m_args = *args; |
| std::sort( |
| m_args.begin(), m_args.end(), |
| [](kernel_argument a, kernel_argument b) { return a.pos < b.pos; }); |
| |
| // Create Descriptor Sets Layout |
| std::unordered_map<VkDescriptorType, uint32_t> bindingTypes; |
| if (!build_descriptor_sets_layout_bindings_for_literal_samplers( |
| bindingTypes)) { |
| return CL_INVALID_VALUE; |
| } |
| if (!build_descriptor_sets_layout_bindings_for_arguments( |
| bindingTypes, m_num_resource_slots)) { |
| return CL_INVALID_VALUE; |
| } |
| if (!build_descriptor_sets_layout_bindings_for_program_scope_buffers( |
| bindingTypes)) { |
| return CL_INVALID_VALUE; |
| } |
| if (!build_descriptor_sets_layout_bindings_for_printf_buffer( |
| bindingTypes)) { |
| return CL_INVALID_VALUE; |
| } |
| |
| // Do we have POD arguments? |
| for (auto& arg : m_args) { |
| if (arg.is_pod()) { |
| m_has_pod_arguments = true; |
| if (arg.is_pod_buffer()) { |
| m_has_pod_buffer_arguments = true; |
| } |
| } |
| } |
| |
| // Calculate POD buffer size and update the push constant range. |
| VkPushConstantRange push_constant_range = m_program->push_constant_range(); |
| if (m_has_pod_arguments) { |
| // Check we know the POD buffer's descriptor type |
| if (m_has_pod_buffer_arguments && |
| m_pod_descriptor_type == VK_DESCRIPTOR_TYPE_MAX_ENUM) { |
| return CL_INVALID_PROGRAM; |
| } |
| |
| // Find how big the POD buffer should be |
| uint32_t max_offset = 0; |
| uint32_t max_offset_arg_size = 0; |
| |
| for (auto& arg : m_args) { |
| if (arg.is_pod()) { |
| if (arg.offset >= max_offset) { |
| max_offset = arg.offset; |
| max_offset_arg_size = arg.size; |
| } |
| if (!arg.is_pod_buffer()) { |
| if (arg.offset < push_constant_range.offset) { |
| push_constant_range.offset = arg.offset; |
| } |
| |
| if (arg.offset + arg.size > |
| push_constant_range.offset + push_constant_range.size) { |
| push_constant_range.size = |
| arg.offset + arg.size - push_constant_range.offset; |
| } |
| } |
| } |
| } |
| |
| m_pod_buffer_size = max_offset + max_offset_arg_size; |
| m_pod_buffer_size = round_up(m_pod_buffer_size, 4); |
| } |
| |
| // Take the size of image & sampler metadata into account for the pod buffer |
| // size |
| { |
| uint32_t max_offset = 0; |
| if (m_image_metadata) { |
| // Find how big the POD buffer should be |
| for (const auto& md : *m_image_metadata) { |
| auto order_offset = md.second.order_offset; |
| auto data_type_offset = md.second.data_type_offset; |
| if (md.second.has_valid_order()) { |
| max_offset = std::max(order_offset, max_offset); |
| push_constant_range.offset = |
| std::min(order_offset, push_constant_range.offset); |
| if (order_offset + sizeof(uint32_t) > |
| push_constant_range.offset + push_constant_range.size) { |
| push_constant_range.size = order_offset + |
| sizeof(uint32_t) - |
| push_constant_range.offset; |
| } |
| } |
| if (md.second.has_valid_data_type()) { |
| max_offset = std::max(data_type_offset, max_offset); |
| push_constant_range.offset = |
| std::min(data_type_offset, push_constant_range.offset); |
| if (data_type_offset + sizeof(uint32_t) > |
| push_constant_range.offset + push_constant_range.size) { |
| push_constant_range.size = data_type_offset + |
| sizeof(uint32_t) - |
| push_constant_range.offset; |
| } |
| } |
| } |
| } |
| if (m_sampler_metadata) { |
| for (const auto& md : *m_sampler_metadata) { |
| auto offset = md.second; |
| max_offset = std::max(offset, max_offset); |
| push_constant_range.offset = |
| std::min(offset, push_constant_range.offset); |
| if (offset + sizeof(uint32_t) > |
| push_constant_range.offset + push_constant_range.size) { |
| push_constant_range.size = |
| offset + sizeof(uint32_t) - push_constant_range.offset; |
| } |
| } |
| } |
| if (max_offset + sizeof(uint32_t) > m_pod_buffer_size) { |
| m_pod_buffer_size = round_up(max_offset + sizeof(uint32_t), 4); |
| } |
| } |
| |
| // Don't pass the range at pipeline layout creation time if no push |
| // constants are used |
| uint32_t num_push_constant_ranges = 1; |
| if (push_constant_range.offset == UINT32_MAX) { |
| num_push_constant_ranges = 0; |
| } |
| |
| // The size of the range must be a multiple of 4, round up to guarantee this |
| push_constant_range.size = round_up(push_constant_range.size, 4); |
| |
| // Its offset must be a multiple of 4, round down to guarantee this |
| push_constant_range.offset &= ~0x3U; |
| |
| if (push_constant_range.size > |
| m_context->device()->vulkan_max_push_constants_size()) { |
| cvk_error("Not enough space for push constants"); |
| return CL_INVALID_VALUE; |
| } |
| |
| // Create pipeline layout |
| cvk_debug("about to create pipeline layout, number of descriptor set " |
| "layouts: %zu", |
| m_descriptor_set_layouts.size()); |
| |
| VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = { |
| VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, |
| 0, |
| 0, |
| static_cast<uint32_t>(m_descriptor_set_layouts.size()), |
| m_descriptor_set_layouts.data(), |
| num_push_constant_ranges, |
| &push_constant_range}; |
| |
| res = vkCreatePipelineLayout(m_device->vulkan_device(), |
| &pipelineLayoutCreateInfo, 0, |
| &m_pipeline_layout); |
| if (res != VK_SUCCESS) { |
| cvk_error("Could not create pipeline layout."); |
| return CL_INVALID_VALUE; |
| } |
| |
| // Determine number and types of bindings |
| std::vector<VkDescriptorPoolSize> poolSizes(bindingTypes.size()); |
| |
| int bidx = 0; |
| for (auto& bt : bindingTypes) { |
| poolSizes[bidx].type = bt.first; |
| poolSizes[bidx].descriptorCount = bt.second * MAX_INSTANCES; |
| bidx++; |
| } |
| |
| // Create descriptor pool |
| if (poolSizes.size() > 0) { |
| VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = { |
| VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, |
| nullptr, |
| VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, // flags |
| MAX_INSTANCES * spir_binary::MAX_DESCRIPTOR_SETS, // maxSets |
| static_cast<uint32_t>(poolSizes.size()), // poolSizeCount |
| poolSizes.data(), // pPoolSizes |
| }; |
| |
| res = vkCreateDescriptorPool(m_device->vulkan_device(), |
| &descriptorPoolCreateInfo, 0, |
| &m_descriptor_pool); |
| |
| if (res != VK_SUCCESS) { |
| cvk_error("Could not create descriptor pool."); |
| return CL_INVALID_VALUE; |
| } |
| } |
| |
| return CL_SUCCESS; |
| } |
| |
| VkPipeline |
| cvk_entry_point::create_pipeline(const cvk_spec_constant_map& spec_constants) { |
| std::lock_guard<std::mutex> lock(m_pipeline_cache_lock); |
| |
| // Check for a cached pipeline using the same specialization constants |
| if (m_pipelines.count(spec_constants)) { |
| VkPipeline pipeline = m_pipelines.at(spec_constants); |
| cvk_info("reusing pipeline %p for kernel %s", pipeline, m_name.c_str()); |
| return pipeline; |
| } |
| |
| std::vector<VkSpecializationMapEntry> mapEntries; |
| std::vector<uint32_t> specConstantData; |
| uint32_t constantDataOffset = 0; |
| for (auto& spec_const : spec_constants) { |
| VkSpecializationMapEntry entry = {spec_const.first, constantDataOffset, |
| sizeof(uint32_t)}; |
| mapEntries.push_back(entry); |
| specConstantData.push_back(spec_const.second); |
| constantDataOffset += sizeof(uint32_t); |
| } |
| |
| VkSpecializationInfo specializationInfo = { |
| static_cast<uint32_t>(mapEntries.size()), |
| mapEntries.data(), |
| specConstantData.size() * sizeof(uint32_t), |
| specConstantData.data(), |
| }; |
| |
| void* pipelineShaderStageCreateInfoPNext = nullptr; |
| VkPipelineShaderStageRequiredSubgroupSizeCreateInfo |
| reqdSubgroupSizeCreateInfo; |
| if (m_device->supports_subgroup_size_selection()) { |
| auto reqdSubgroupSize = m_program->required_sub_group_size(m_name); |
| if (reqdSubgroupSize > m_device->max_sub_group_size() || |
| reqdSubgroupSize < m_device->min_sub_group_size()) { |
| if (reqdSubgroupSize != 0) { |
| cvk_error_fn("required subgroup size '%u' for '%s' is out of " |
| "the supported range [%u, %u]", |
| reqdSubgroupSize, m_name.c_str(), |
| m_device->min_sub_group_size(), |
| m_device->max_sub_group_size()); |
| return VK_NULL_HANDLE; |
| } |
| reqdSubgroupSize = m_device->sub_group_size(); |
| } |
| reqdSubgroupSizeCreateInfo.sType = |
| VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO; |
| reqdSubgroupSizeCreateInfo.pNext = nullptr; |
| reqdSubgroupSizeCreateInfo.requiredSubgroupSize = reqdSubgroupSize; |
| pipelineShaderStageCreateInfoPNext = &reqdSubgroupSizeCreateInfo; |
| } |
| |
| const VkComputePipelineCreateInfo createInfo = { |
| VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, // sType |
| nullptr, // pNext |
| 0, // flags |
| { |
| VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, // sType |
| pipelineShaderStageCreateInfoPNext, // pNext |
| 0, // flags |
| VK_SHADER_STAGE_COMPUTE_BIT, // stage |
| m_program->shader_module(), // module |
| m_name.c_str(), |
| &specializationInfo // pSpecializationInfo |
| }, // stage |
| m_pipeline_layout, // layout |
| VK_NULL_HANDLE, // basePipelineHandle |
| 0 // basePipelineIndex |
| }; |
| |
| VkPipeline pipeline; |
| VkResult res = vkCreateComputePipelines(m_device->vulkan_device(), |
| m_program->pipeline_cache(), 1, |
| &createInfo, nullptr, &pipeline); |
| |
| if (res != VK_SUCCESS) { |
| cvk_error_fn("Could not create compute pipeline for kernel %s: %s", |
| vulkan_error_string(res), m_name.c_str()); |
| return VK_NULL_HANDLE; |
| } |
| |
| // Add to pipeline cache |
| m_pipelines[spec_constants] = pipeline; |
| |
| cvk_info("created pipeline %p for kernel %s", pipeline, m_name.c_str()); |
| |
| return pipeline; |
| } |
| |
| bool cvk_entry_point::allocate_descriptor_sets(VkDescriptorSet* ds) { |
| TRACE_FUNCTION(); |
| |
| if (m_descriptor_set_layouts.size() == 0) { |
| return true; |
| } |
| |
| std::lock_guard<std::mutex> lock(m_descriptor_pool_lock); |
| |
| #if CLVK_UNIT_TESTING_ENABLED |
| if (config.force_descriptor_set_allocation_failure() && |
| m_nb_descriptor_set_allocated + m_descriptor_set_layouts.size() > |
| config.max_entry_points_instances()) { |
| return false; |
| } |
| #endif |
| |
| VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = { |
| VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, nullptr, |
| m_descriptor_pool, |
| static_cast<uint32_t>( |
| m_descriptor_set_layouts.size()), // descriptorSetCount |
| m_descriptor_set_layouts.data()}; |
| |
| VkResult res = vkAllocateDescriptorSets(m_device->vulkan_device(), |
| &descriptorSetAllocateInfo, ds); |
| |
| if (res != VK_SUCCESS) { |
| if (config.enqueue_command_retry_sleep_us == UINT32_MAX) { |
| cvk_error_fn("could not allocate descriptor sets: %s", |
| vulkan_error_string(res)); |
| } else if (m_first_allocation_failure) { |
| cvk_warn_fn( |
| "could not allocate descriptor sets: %s, retry in %u us", |
| vulkan_error_string(res), |
| config.enqueue_command_retry_sleep_us()); |
| } else { |
| cvk_info_fn("could not allocate descriptor sets: %s", |
| vulkan_error_string(res)); |
| } |
| m_first_allocation_failure = false; |
| return false; |
| } |
| |
| m_nb_descriptor_set_allocated += m_descriptor_set_layouts.size(); |
| TRACE_CNT(descriptor_set_allocated_counter, m_nb_descriptor_set_allocated); |
| |
| return true; |
| } |
| |
| std::unique_ptr<cvk_buffer> cvk_entry_point::allocate_pod_buffer() { |
| cl_int err; |
| auto buffer = |
| cvk_buffer::create(m_context, 0, m_pod_buffer_size, nullptr, &err); |
| if (err != CL_SUCCESS) { |
| return nullptr; |
| } |
| |
| return buffer; |
| } |
| |
| bool cvk_entry_point::uses_printf() const { |
| return m_program->kernel_flags(m_name) & |
| NonSemanticClspvReflectionMayUsePrintf; |
| } |