2 // Copyright 2018 Pierre Moreau
4 // Permission is hereby granted, free of charge, to any person obtaining a
5 // copy of this software and associated documentation files (the "Software"),
6 // to deal in the Software without restriction, including without limitation
7 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 // and/or sell copies of the Software, and to permit persons to whom the
9 // Software is furnished to do so, subject to the following conditions:
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
18 // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19 // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20 // OTHER DEALINGS IN THE SOFTWARE.
23 #include "invocation.hpp"
25 #include <unordered_map>
26 #include <unordered_set>
29 #ifdef HAVE_CLOVER_SPIRV
30 #include <spirv-tools/libspirv.hpp>
31 #include <spirv-tools/linker.hpp>
34 #include "core/error.hpp"
35 #include "core/platform.hpp"
36 #include "invocation.hpp"
37 #include "llvm/util.hpp"
38 #include "pipe/p_state.h"
39 #include "util/algorithm.hpp"
40 #include "util/functional.hpp"
41 #include "util/u_math.h"
43 #include "compiler/spirv/spirv.h"
45 #define SPIRV_HEADER_WORD_SIZE 5
47 using namespace clover
;
49 #ifdef HAVE_CLOVER_SPIRV
53 T
get(const char *source
, size_t index
) {
54 const uint32_t *word_ptr
= reinterpret_cast<const uint32_t *>(source
);
55 return static_cast<T
>(word_ptr
[index
]);
58 enum module::argument::type
59 convert_storage_class(SpvStorageClass storage_class
, std::string
&err
) {
60 switch (storage_class
) {
61 case SpvStorageClassFunction
:
62 return module::argument::scalar
;
63 case SpvStorageClassUniformConstant
:
64 return module::argument::global
;
65 case SpvStorageClassWorkgroup
:
66 return module::argument::local
;
67 case SpvStorageClassCrossWorkgroup
:
68 return module::argument::global
;
70 err
+= "Invalid storage type " + std::to_string(storage_class
) + "\n";
75 enum module::argument::type
76 convert_image_type(SpvId id
, SpvDim dim
, SpvAccessQualifier access
,
78 if (dim
== SpvDim2D
&& access
== SpvAccessQualifierReadOnly
)
79 return module::argument::image2d_rd
;
80 else if (dim
== SpvDim2D
&& access
== SpvAccessQualifierWriteOnly
)
81 return module::argument::image2d_wr
;
82 else if (dim
== SpvDim3D
&& access
== SpvAccessQualifierReadOnly
)
83 return module::argument::image3d_rd
;
84 else if (dim
== SpvDim3D
&& access
== SpvAccessQualifierWriteOnly
)
85 return module::argument::image3d_wr
;
87 err
+= "Unknown access qualifier " + std::to_string(access
)
88 + " or dimension " + std::to_string(dim
) + " for image "
89 + std::to_string(id
) + ".\n";
95 make_text_section(const std::vector
<char> &code
,
96 enum module::section::type section_type
) {
97 const pipe_binary_program_header header
{ uint32_t(code
.size()) };
98 module::section text
{ 0, section_type
, header
.num_bytes
, {} };
100 text
.data
.insert(text
.data
.end(), reinterpret_cast<const char *>(&header
),
101 reinterpret_cast<const char *>(&header
) + sizeof(header
));
102 text
.data
.insert(text
.data
.end(), code
.begin(), code
.end());
108 create_module_from_spirv(const std::vector
<char> &source
,
109 size_t pointer_byte_size
,
111 const size_t length
= source
.size() / sizeof(uint32_t);
112 size_t i
= SPIRV_HEADER_WORD_SIZE
; // Skip header
114 std::string kernel_name
;
115 size_t kernel_nb
= 0u;
116 std::vector
<module::argument
> args
;
120 std::unordered_map
<SpvId
, std::string
> kernels
;
121 std::unordered_map
<SpvId
, module::argument
> types
;
122 std::unordered_map
<SpvId
, SpvId
> pointer_types
;
123 std::unordered_map
<SpvId
, unsigned int> constants
;
124 std::unordered_set
<SpvId
> packed_structures
;
125 std::unordered_map
<SpvId
, std::vector
<SpvFunctionParameterAttribute
>>
129 const auto inst
= &source
[i
* sizeof(uint32_t)];
130 const auto desc_word
= get
<uint32_t>(inst
, 0);
131 const auto opcode
= static_cast<SpvOp
>(desc_word
& SpvOpCodeMask
);
132 const unsigned int num_operands
= desc_word
>> SpvWordCountShift
;
135 case SpvOpEntryPoint
:
136 if (get
<SpvExecutionModel
>(inst
, 1) == SpvExecutionModelKernel
)
137 kernels
.emplace(get
<SpvId
>(inst
, 2),
138 source
.data() + (i
+ 3u) * sizeof(uint32_t));
141 case SpvOpDecorate
: {
142 const auto id
= get
<SpvId
>(inst
, 1);
143 const auto decoration
= get
<SpvDecoration
>(inst
, 2);
144 if (decoration
== SpvDecorationCPacked
)
145 packed_structures
.emplace(id
);
146 else if (decoration
== SpvDecorationFuncParamAttr
) {
147 const auto attribute
=
148 get
<SpvFunctionParameterAttribute
>(inst
, 3u);
149 func_param_attr_map
[id
].push_back(attribute
);
154 case SpvOpGroupDecorate
: {
155 const auto group_id
= get
<SpvId
>(inst
, 1);
156 if (packed_structures
.count(group_id
)) {
157 for (unsigned int i
= 2u; i
< num_operands
; ++i
)
158 packed_structures
.emplace(get
<SpvId
>(inst
, i
));
160 const auto func_param_attr_iter
=
161 func_param_attr_map
.find(group_id
);
162 if (func_param_attr_iter
!= func_param_attr_map
.end()) {
163 for (unsigned int i
= 2u; i
< num_operands
; ++i
)
164 func_param_attr_map
.emplace(get
<SpvId
>(inst
, i
),
165 func_param_attr_iter
->second
);
171 // We only care about constants that represent the size of arrays.
172 // If they are passed as argument, they will never be more than
173 // 4GB-wide, and even if they did, a clover::module::argument size
174 // is represented by an int.
175 constants
[get
<SpvId
>(inst
, 2)] = get
<unsigned int>(inst
, 3u);
178 case SpvOpTypeInt
: // FALLTHROUGH
179 case SpvOpTypeFloat
: {
180 const auto size
= get
<uint32_t>(inst
, 2) / 8u;
181 types
[get
<SpvId
>(inst
, 1)] = { module::argument::scalar
, size
,
183 module::argument::zero_ext
};
187 case SpvOpTypeArray
: {
188 const auto id
= get
<SpvId
>(inst
, 1);
189 const auto type_id
= get
<SpvId
>(inst
, 2);
190 const auto types_iter
= types
.find(type_id
);
191 if (types_iter
== types
.end())
194 const auto constant_id
= get
<SpvId
>(inst
, 3);
195 const auto constants_iter
= constants
.find(constant_id
);
196 if (constants_iter
== constants
.end()) {
197 err
+= "Constant " + std::to_string(constant_id
) +
201 const auto elem_size
= types_iter
->second
.size
;
202 const auto elem_nbs
= constants_iter
->second
;
203 const auto size
= elem_size
* elem_nbs
;
204 types
[id
] = { module::argument::scalar
, size
, size
,
205 types_iter
->second
.target_align
,
206 module::argument::zero_ext
};
210 case SpvOpTypeStruct
: {
211 const auto id
= get
<SpvId
>(inst
, 1);
212 const bool is_packed
= packed_structures
.count(id
);
214 unsigned struct_size
= 0u;
215 unsigned struct_align
= 1u;
216 for (unsigned j
= 2u; j
< num_operands
; ++j
) {
217 const auto type_id
= get
<SpvId
>(inst
, j
);
218 const auto types_iter
= types
.find(type_id
);
220 // If a type was not found, that means it is not one of the
221 // types allowed as kernel arguments. And since the module has
222 // been validated, this means this type is not used for kernel
223 // arguments, and therefore can be ignored.
224 if (types_iter
== types
.end())
227 const auto alignment
= is_packed
? 1u
228 : types_iter
->second
.target_align
;
229 const auto padding
= (-struct_size
) & (alignment
- 1u);
230 struct_size
+= padding
+ types_iter
->second
.target_size
;
231 struct_align
= std::max(struct_align
, alignment
);
233 struct_size
+= (-struct_size
) & (struct_align
- 1u);
234 types
[id
] = { module::argument::scalar
, struct_size
, struct_size
,
235 struct_align
, module::argument::zero_ext
};
239 case SpvOpTypeVector
: {
240 const auto id
= get
<SpvId
>(inst
, 1);
241 const auto type_id
= get
<SpvId
>(inst
, 2);
242 const auto types_iter
= types
.find(type_id
);
244 // If a type was not found, that means it is not one of the
245 // types allowed as kernel arguments. And since the module has
246 // been validated, this means this type is not used for kernel
247 // arguments, and therefore can be ignored.
248 if (types_iter
== types
.end())
251 const auto elem_size
= types_iter
->second
.size
;
252 const auto elem_nbs
= get
<uint32_t>(inst
, 3);
253 const auto size
= elem_size
* elem_nbs
;
254 types
[id
] = { module::argument::scalar
, size
, size
, size
,
255 module::argument::zero_ext
};
259 case SpvOpTypeForwardPointer
: // FALLTHROUGH
260 case SpvOpTypePointer
: {
261 const auto id
= get
<SpvId
>(inst
, 1);
262 const auto storage_class
= get
<SpvStorageClass
>(inst
, 2);
263 // Input means this is for a builtin variable, which can not be
264 // passed as an argument to a kernel.
265 if (storage_class
== SpvStorageClassInput
)
267 types
[id
] = { convert_storage_class(storage_class
, err
),
269 static_cast<module::size_t>(pointer_byte_size
),
270 static_cast<module::size_t>(pointer_byte_size
),
271 module::argument::zero_ext
};
272 if (opcode
== SpvOpTypePointer
)
273 pointer_types
[id
] = get
<SpvId
>(inst
, 3);
277 case SpvOpTypeSampler
:
278 types
[get
<SpvId
>(inst
, 1)] = { module::argument::sampler
,
279 sizeof(cl_sampler
) };
282 case SpvOpTypeImage
: {
283 const auto id
= get
<SpvId
>(inst
, 1);
284 const auto dim
= get
<SpvDim
>(inst
, 3);
285 const auto access
= get
<SpvAccessQualifier
>(inst
, 9);
286 types
[id
] = { convert_image_type(id
, dim
, access
, err
),
287 sizeof(cl_mem
), sizeof(cl_mem
), sizeof(cl_mem
),
288 module::argument::zero_ext
};
292 case SpvOpTypePipe
: // FALLTHROUGH
293 case SpvOpTypeQueue
: {
294 err
+= "TypePipe and TypeQueue are valid SPIR-V 1.0 types, but are "
295 "not available in the currently supported OpenCL C version."
300 case SpvOpFunction
: {
301 const auto kernels_iter
= kernels
.find(get
<SpvId
>(inst
, 2));
302 if (kernels_iter
!= kernels
.end())
303 kernel_name
= kernels_iter
->second
;
307 case SpvOpFunctionParameter
: {
308 if (kernel_name
.empty())
311 const auto type_id
= get
<SpvId
>(inst
, 1);
312 auto arg
= types
.find(type_id
)->second
;
313 const auto &func_param_attr_iter
=
314 func_param_attr_map
.find(get
<SpvId
>(inst
, 2));
315 if (func_param_attr_iter
!= func_param_attr_map
.end()) {
316 for (auto &i
: func_param_attr_iter
->second
) {
318 case SpvFunctionParameterAttributeSext
:
319 arg
.ext_type
= module::argument::sign_ext
;
321 case SpvFunctionParameterAttributeZext
:
322 arg
.ext_type
= module::argument::zero_ext
;
324 case SpvFunctionParameterAttributeByVal
: {
325 const SpvId ptr_type_id
=
326 pointer_types
.find(type_id
)->second
;
327 arg
= types
.find(ptr_type_id
)->second
;
335 args
.emplace_back(arg
);
339 case SpvOpFunctionEnd
:
340 if (kernel_name
.empty())
342 m
.syms
.emplace_back(kernel_name
, 0, kernel_nb
, args
);
355 m
.secs
.push_back(make_text_section(source
,
356 module::section::text_intermediate
));
361 check_capabilities(const device
&dev
, const std::vector
<char> &source
,
362 std::string
&r_log
) {
363 const size_t length
= source
.size() / sizeof(uint32_t);
364 size_t i
= SPIRV_HEADER_WORD_SIZE
; // Skip header
367 const auto desc_word
= get
<uint32_t>(source
.data(), i
);
368 const auto opcode
= static_cast<SpvOp
>(desc_word
& SpvOpCodeMask
);
369 const unsigned int num_operands
= desc_word
>> SpvWordCountShift
;
371 if (opcode
!= SpvOpCapability
)
374 const auto capability
= get
<SpvCapability
>(source
.data(), i
+ 1u);
375 switch (capability
) {
376 // Mandatory capabilities
377 case SpvCapabilityAddresses
:
378 case SpvCapabilityFloat16Buffer
:
379 case SpvCapabilityGroups
:
380 case SpvCapabilityInt64
:
381 case SpvCapabilityInt16
:
382 case SpvCapabilityInt8
:
383 case SpvCapabilityKernel
:
384 case SpvCapabilityLinkage
:
385 case SpvCapabilityVector16
:
387 // Optional capabilities
388 case SpvCapabilityImageBasic
:
389 case SpvCapabilityLiteralSampler
:
390 case SpvCapabilitySampled1D
:
391 case SpvCapabilityImage1D
:
392 case SpvCapabilitySampledBuffer
:
393 case SpvCapabilityImageBuffer
:
394 if (!dev
.image_support()) {
395 r_log
+= "Capability 'ImageBasic' is not supported.\n";
399 case SpvCapabilityFloat64
:
400 if (!dev
.has_doubles()) {
401 r_log
+= "Capability 'Float64' is not supported.\n";
405 // Enabled through extensions
406 case SpvCapabilityFloat16
:
407 if (!dev
.has_halves()) {
408 r_log
+= "Capability 'Float16' is not supported.\n";
412 case SpvCapabilityInt64Atomics
:
413 if (!dev
.has_int64_atomics()) {
414 r_log
+= "Capability 'Int64Atomics' is not supported.\n";
419 r_log
+= "Capability '" + std::to_string(capability
) +
420 "' is not supported.\n";
431 check_extensions(const device
&dev
, const std::vector
<char> &source
,
432 std::string
&r_log
) {
433 const size_t length
= source
.size() / sizeof(uint32_t);
434 size_t i
= SPIRV_HEADER_WORD_SIZE
; // Skip header
435 const auto spirv_extensions
= spirv::supported_extensions();
438 const auto desc_word
= get
<uint32_t>(source
.data(), i
);
439 const auto opcode
= static_cast<SpvOp
>(desc_word
& SpvOpCodeMask
);
440 const unsigned int num_operands
= desc_word
>> SpvWordCountShift
;
442 if (opcode
== SpvOpCapability
) {
446 if (opcode
!= SpvOpExtension
)
449 const std::string extension
= source
.data() + (i
+ 1u) * sizeof(uint32_t);
450 if (spirv_extensions
.count(extension
) == 0) {
451 r_log
+= "Extension '" + extension
+ "' is not supported.\n";
462 check_memory_model(const device
&dev
, const std::vector
<char> &source
,
463 std::string
&r_log
) {
464 const size_t length
= source
.size() / sizeof(uint32_t);
465 size_t i
= SPIRV_HEADER_WORD_SIZE
; // Skip header
468 const auto desc_word
= get
<uint32_t>(source
.data(), i
);
469 const auto opcode
= static_cast<SpvOp
>(desc_word
& SpvOpCodeMask
);
470 const unsigned int num_operands
= desc_word
>> SpvWordCountShift
;
473 case SpvOpMemoryModel
:
474 switch (get
<SpvAddressingModel
>(source
.data(), i
+ 1u)) {
475 case SpvAddressingModelPhysical32
:
476 return dev
.address_bits() == 32;
477 case SpvAddressingModelPhysical64
:
478 return dev
.address_bits() == 64;
480 unreachable("Only Physical32 and Physical64 are valid for OpenCL, and the binary was already validated");
494 // Copies the input binary and convert it to the endianness of the host CPU.
496 spirv_to_cpu(const std::vector
<char> &binary
)
498 const uint32_t first_word
= get
<uint32_t>(binary
.data(), 0u);
499 if (first_word
== SpvMagicNumber
)
502 std::vector
<char> cpu_endianness_binary(binary
.size());
503 for (size_t i
= 0; i
< (binary
.size() / 4u); ++i
) {
504 const uint32_t word
= get
<uint32_t>(binary
.data(), i
);
505 reinterpret_cast<uint32_t *>(cpu_endianness_binary
.data())[i
] =
509 return cpu_endianness_binary
;
512 #ifdef HAVE_CLOVER_SPIRV
514 format_validator_msg(spv_message_level_t level
, const char * /* source */,
515 const spv_position_t
&position
, const char *message
) {
516 std::string level_str
;
521 case SPV_MSG_INTERNAL_ERROR
:
522 level_str
= "Internal error";
527 case SPV_MSG_WARNING
:
528 level_str
= "Warning";
537 return "[" + level_str
+ "] At word No." +
538 std::to_string(position
.index
) + ": \"" + message
+ "\"\n";
542 convert_opencl_str_to_target_env(const std::string
&opencl_version
) {
543 if (opencl_version
== "2.2") {
544 return SPV_ENV_OPENCL_2_2
;
545 } else if (opencl_version
== "2.1") {
546 return SPV_ENV_OPENCL_2_1
;
547 } else if (opencl_version
== "2.0") {
548 return SPV_ENV_OPENCL_2_0
;
549 } else if (opencl_version
== "1.2" ||
550 opencl_version
== "1.1" ||
551 opencl_version
== "1.0") {
552 // SPIR-V is only defined for OpenCL >= 1.2, however some drivers
553 // might use it with OpenCL 1.0 and 1.1.
554 return SPV_ENV_OPENCL_1_2
;
556 throw build_error("Invalid OpenCL version");
564 clover::spirv::compile_program(const std::vector
<char> &binary
,
565 const device
&dev
, std::string
&r_log
) {
566 std::vector
<char> source
= spirv_to_cpu(binary
);
568 if (!is_valid_spirv(source
, dev
.device_version(), r_log
))
571 if (!check_capabilities(dev
, source
, r_log
))
573 if (!check_extensions(dev
, source
, r_log
))
575 if (!check_memory_model(dev
, source
, r_log
))
578 return create_module_from_spirv(source
,
579 dev
.address_bits() == 32 ? 4u : 8u, r_log
);
583 clover::spirv::link_program(const std::vector
<module
> &modules
,
584 const device
&dev
, const std::string
&opts
,
585 std::string
&r_log
) {
586 std::vector
<std::string
> options
= clover::llvm::tokenize(opts
);
588 bool create_library
= false;
590 std::string ignored_options
;
591 for (const std::string
&option
: options
) {
592 if (option
== "-create-library") {
593 create_library
= true;
595 ignored_options
+= "'" + option
+ "' ";
598 if (!ignored_options
.empty()) {
599 r_log
+= "Ignoring the following link options: " + ignored_options
603 spvtools::LinkerOptions linker_options
;
604 linker_options
.SetCreateLibrary(create_library
);
608 const auto section_type
= create_library
? module::section::text_library
:
609 module::section::text_executable
;
611 std::vector
<const uint32_t *> sections
;
612 sections
.reserve(modules
.size());
613 std::vector
<size_t> lengths
;
614 lengths
.reserve(modules
.size());
616 auto const validator_consumer
= [&r_log
](spv_message_level_t level
,
618 const spv_position_t
&position
,
619 const char *message
) {
620 r_log
+= format_validator_msg(level
, source
, position
, message
);
623 for (const auto &mod
: modules
) {
624 const auto &msec
= find([](const module::section
&sec
) {
625 return sec
.type
== module::section::text_intermediate
||
626 sec
.type
== module::section::text_library
;
629 const auto c_il
= ((struct pipe_binary_program_header
*)msec
.data
.data())->blob
;
630 const auto length
= msec
.size
;
632 sections
.push_back(reinterpret_cast<const uint32_t *>(c_il
));
633 lengths
.push_back(length
/ sizeof(uint32_t));
636 std::vector
<uint32_t> linked_binary
;
638 const std::string opencl_version
= dev
.device_version();
639 const spv_target_env target_env
=
640 convert_opencl_str_to_target_env(opencl_version
);
642 const spvtools::MessageConsumer consumer
= validator_consumer
;
643 spvtools::Context
context(target_env
);
644 context
.SetMessageConsumer(std::move(consumer
));
646 if (Link(context
, sections
.data(), lengths
.data(), sections
.size(),
647 &linked_binary
, linker_options
) != SPV_SUCCESS
)
648 throw error(CL_LINK_PROGRAM_FAILURE
);
650 std::vector
<char> final_binary
{
651 reinterpret_cast<char *>(linked_binary
.data()),
652 reinterpret_cast<char *>(linked_binary
.data() +
653 linked_binary
.size()) };
654 if (!is_valid_spirv(final_binary
, opencl_version
, r_log
))
655 throw error(CL_LINK_PROGRAM_FAILURE
);
657 if (has_flag(llvm::debug::spirv
))
658 llvm::debug::log(".spvasm", spirv::print_module(final_binary
, dev
.device_version()));
660 for (const auto &mod
: modules
)
661 m
.syms
.insert(m
.syms
.end(), mod
.syms
.begin(), mod
.syms
.end());
663 m
.secs
.emplace_back(make_text_section(final_binary
, section_type
));
669 clover::spirv::is_valid_spirv(const std::vector
<char> &binary
,
670 const std::string
&opencl_version
,
671 std::string
&r_log
) {
672 auto const validator_consumer
=
673 [&r_log
](spv_message_level_t level
, const char *source
,
674 const spv_position_t
&position
, const char *message
) {
675 r_log
+= format_validator_msg(level
, source
, position
, message
);
678 const spv_target_env target_env
=
679 convert_opencl_str_to_target_env(opencl_version
);
680 spvtools::SpirvTools
spvTool(target_env
);
681 spvTool
.SetMessageConsumer(validator_consumer
);
683 return spvTool
.Validate(reinterpret_cast<const uint32_t *>(binary
.data()),
688 clover::spirv::print_module(const std::vector
<char> &binary
,
689 const std::string
&opencl_version
) {
690 const spv_target_env target_env
=
691 convert_opencl_str_to_target_env(opencl_version
);
692 spvtools::SpirvTools
spvTool(target_env
);
693 spv_context spvContext
= spvContextCreate(target_env
);
695 return "Failed to create an spv_context for disassembling the module.";
697 spv_text disassembly
;
698 spvBinaryToText(spvContext
,
699 reinterpret_cast<const uint32_t *>(binary
.data()),
700 binary
.size() / 4u, SPV_BINARY_TO_TEXT_OPTION_NONE
,
701 &disassembly
, nullptr);
702 spvContextDestroy(spvContext
);
704 const std::string disassemblyStr
= disassembly
->str
;
705 spvTextDestroy(disassembly
);
707 return disassemblyStr
;
710 std::unordered_set
<std::string
>
711 clover::spirv::supported_extensions() {
713 /* this is only a hint so all devices support that */
714 "SPV_KHR_no_integer_wrap_decoration"
720 clover::spirv::is_valid_spirv(const std::vector
<char> &/*binary*/,
721 const std::string
&/*opencl_version*/,
722 std::string
&/*r_log*/) {
727 clover::spirv::compile_program(const std::vector
<char> &binary
,
728 const device
&dev
, std::string
&r_log
) {
729 r_log
+= "SPIR-V support in clover is not enabled.\n";
734 clover::spirv::link_program(const std::vector
<module
> &/*modules*/,
735 const device
&/*dev*/, const std::string
&/*opts*/,
736 std::string
&r_log
) {
737 r_log
+= "SPIR-V support in clover is not enabled.\n";
738 throw error(CL_LINKER_NOT_AVAILABLE
);
742 clover::spirv::print_module(const std::vector
<char> &binary
,
743 const std::string
&opencl_version
) {
744 return std::string();
747 std::unordered_set
<std::string
>
748 clover::spirv::supported_extensions() {