[Mesa-dev] [PATCH 07/15] clover/spirv: Add functions for parsing arguments, linking programs, etc.
Francisco Jerez
currojerez at riseup.net
Sat May 11 23:04:00 UTC 2019
Karol Herbst <kherbst at redhat.com> writes:
> From: Pierre Moreau <pierre.morrow at free.fr>
>
> v2 (Karol Herbst):
> silence warnings about unhandled enum values
> ---
> .../clover/spirv/invocation.cpp | 598 ++++++++++++++++++
> .../clover/spirv/invocation.hpp | 12 +
> 2 files changed, 610 insertions(+)
>
> diff --git a/src/gallium/state_trackers/clover/spirv/invocation.cpp b/src/gallium/state_trackers/clover/spirv/invocation.cpp
> index b874f2f061c..62886e77495 100644
> --- a/src/gallium/state_trackers/clover/spirv/invocation.cpp
> +++ b/src/gallium/state_trackers/clover/spirv/invocation.cpp
> @@ -22,10 +22,24 @@
>
> #include "invocation.hpp"
>
> +#include <stack>
You don't seem to be using this include.
> +#include <tuple>
> +#include <unordered_map>
> +#include <unordered_set>
> +#include <vector>
> +
> #ifdef CLOVER_ALLOW_SPIRV
> #include <spirv-tools/libspirv.hpp>
> +#include <spirv-tools/linker.hpp>
> #endif
>
> +#include "core/error.hpp"
> +#include "core/platform.hpp"
> +#include "invocation.hpp"
> +#include "llvm/util.hpp"
> +#include "pipe/p_state.h"
> +#include "util/algorithm.hpp"
> +#include "util/functional.hpp"
> #include "util/u_math.h"
>
> #include "compiler/spirv/spirv.h"
> @@ -34,6 +48,472 @@ using namespace clover;
>
> namespace {
>
> + template<typename T>
> + T get(const char *source, size_t index) {
> + const uint32_t *word_ptr = reinterpret_cast<const uint32_t *>(source);
> + return static_cast<T>(word_ptr[index]);
> + }
> +
> + enum module::argument::type
> + convertStorageClass(SpvStorageClass storage_class, std::string &err) {
Use underscores instead of camel case here and below.
> + switch (storage_class) {
> + case SpvStorageClassFunction:
> + return module::argument::scalar;
> + case SpvStorageClassUniformConstant:
> + return module::argument::constant;
> + case SpvStorageClassWorkgroup:
> + return module::argument::local;
> + case SpvStorageClassCrossWorkgroup:
> + return module::argument::global;
> + default:
> + err += "Invalid storage type " + std::to_string(storage_class) + "\n";
> + throw build_error();
> + }
> + }
> +
> + enum module::argument::type
> + convertImageType(SpvId id, SpvDim dim, SpvAccessQualifier access,
> + std::string &err) {
> +#define APPEND_DIM(d) \
> + switch(access) { \
> + case SpvAccessQualifierReadOnly: \
> + return module::argument::image##d##_rd; \
> + case SpvAccessQualifierWriteOnly: \
> + return module::argument::image##d##_wr; \
> + default: \
> + err += "Unsupported access qualifier " #d " for image " + \
> + std::to_string(id); \
Error message seems broken, #d is the image dimensionality, not the
access qualifier.
> + throw build_error(); \
> + }
> +
> + switch (dim) {
> + case SpvDim2D:
> + APPEND_DIM(2d)
I was shortly confused about whether this could fall through. It would
be cleaner to do this with a single if/else ladder like
clover/llvm/codegen/common.cpp. You would only be able to output a
single error message though (e.g. "Unsupported qualifiers $DIM $ACCESS
on image $ID"), but that seems good enough to me (do we even need an
error message here instead of an assertion failure?).
> + case SpvDim3D:
> + APPEND_DIM(3d)
> + default:
> + err += "Unsupported dimension " + std::to_string(dim) + " for image " +
> + std::to_string(id);
> + throw build_error();
> + }
> +
> +#undef APPEND_DIM
> + }
> +
> + module::section
> + make_text_section(const std::vector<char> &code,
> + enum module::section::type section_type) {
> + const pipe_llvm_program_header header { uint32_t(code.size()) };
> + module::section text { 0, section_type, header.num_bytes, {} };
> +
> + text.data.insert(text.data.end(), reinterpret_cast<const char *>(&header),
> + reinterpret_cast<const char *>(&header) + sizeof(header));
> + text.data.insert(text.data.end(), code.begin(), code.end());
> +
> + return text;
> + }
> +
> + module
> + create_module_from_spirv(const std::vector<char> &source,
> + size_t pointer_byte_size,
> + std::string &err) {
> + const size_t length = source.size() / sizeof(uint32_t);
> + size_t i = 5u; // Skip header
It would be nice if there was a define for this, but okay...
> +
> + std::string kernel_name;
> + size_t kernel_nb = 0u;
> + std::vector<module::argument> args;
> +
> + module m;
> +
> + std::unordered_map<SpvId, std::string> kernels;
> + std::unordered_map<SpvId, module::argument> types;
> + std::unordered_map<SpvId, SpvId> pointer_types;
> + std::unordered_map<SpvId, unsigned int> constants;
> + std::unordered_set<SpvId> packed_structures;
> + std::unordered_map<SpvId, std::vector<SpvFunctionParameterAttribute>>
> + func_param_attr_map;
> +
> +#define GET_OPERAND(type, operand_id) get<type>(source.data(), i + operand_id)
> +
> + while (i < length) {
You could declare a 'const auto inst = &source[i * sizeof(uint32_t)]'
pointer here, then do 'get<T>(inst, j)' instead of 'GET_OPERAND(T, j)',
and get rid of the macro definition.
> + const auto desc_word = get<uint32_t>(source.data(), i);
> + const auto opcode = static_cast<SpvOp>(desc_word & SpvOpCodeMask);
> + const unsigned int num_operands = desc_word >> SpvWordCountShift;
> +
> + switch (opcode) {
> + case SpvOpEntryPoint:
> + if (GET_OPERAND(SpvExecutionModel, 1) == SpvExecutionModelKernel)
> + kernels.emplace(GET_OPERAND(SpvId, 2),
> + source.data() + (i + 3u) * sizeof(uint32_t));
> + break;
> +
> + case SpvOpDecorate: {
> + const auto id = GET_OPERAND(SpvId, 1);
> + const auto decoration = GET_OPERAND(SpvDecoration, 2);
> + if (decoration == SpvDecorationCPacked)
> + packed_structures.emplace(id);
> + else if (decoration == SpvDecorationFuncParamAttr)
> + func_param_attr_map[id].push_back(GET_OPERAND(SpvFunctionParameterAttribute, 3u));
> + break;
> + }
> +
> + case SpvOpGroupDecorate: {
> + const auto group_id = GET_OPERAND(SpvId, 1);
> + if (packed_structures.count(group_id)) {
> + for (unsigned int i = 2u; i < num_operands; ++i)
> + packed_structures.emplace(GET_OPERAND(SpvId, i));
> + }
> + const auto func_param_attr_iter =
> + func_param_attr_map.find(group_id);
> + if (func_param_attr_iter != func_param_attr_map.end()) {
> + for (unsigned int i = 2u; i < num_operands; ++i)
> + func_param_attr_map.emplace(GET_OPERAND(SpvId, i),
> + func_param_attr_iter->second);
> + }
> + break;
> + }
> +
> + case SpvOpConstant:
> + // We only care about constants that represents the size of arrays.
"represent"
> + // If they are passed as argument, they will never be more than
> + // 4GB-wide, and even if they did, a clover::module::argument size
> + // is represented by an int.
> + constants[GET_OPERAND(SpvId, 2)] = GET_OPERAND(unsigned int, 3u);
> + break;
> +
> + case SpvOpTypeInt: // FALLTHROUGH
> + case SpvOpTypeFloat: {
> + const auto size = GET_OPERAND(uint32_t, 2) / 8u;
> + types[GET_OPERAND(SpvId, 1)] = { module::argument::scalar,
> + size, size, size,
> + module::argument::zero_ext };
> + break;
> + }
> +
> + case SpvOpTypeArray: {
> + const auto id = GET_OPERAND(SpvId, 1);
> + const auto type_id = GET_OPERAND(SpvId, 2);
> + const auto types_iter = types.find(type_id);
> + if (types_iter == types.end())
> + break;
> +
Shouldn't this throw an error condition? There are many similar cases
below.
Hmm, that's a lot of additional errors. Maybe you could promote the
fail() helper from llvm/util.hpp to core/compiler.hpp, and use it
everywhere you need to output an error here.
> + const auto constant_id = GET_OPERAND(SpvId, 3);
> + const auto constants_iter = constants.find(constant_id);
> + if (constants_iter == constants.end()) {
> + err += "Constant " + std::to_string(constant_id) +
> + " is missing\n";
> + throw build_error();
> + }
> + const auto elem_size = types_iter->second.size;
> + const auto elem_nbs = constants_iter->second;
> + const auto size = elem_size * elem_nbs;
> + types[id] = { module::argument::scalar, size, size,
> + types_iter->second.target_align,
> + module::argument::zero_ext };
> + break;
> + }
> +
> + case SpvOpTypeStruct: {
> + const auto id = GET_OPERAND(SpvId, 1);
> + const bool is_packed = packed_structures.count(id);
> +
> + unsigned struct_size = 0u;
> + unsigned max_elem_size = 0u;
This seems really meant to keep track of the maximum element alignment.
Maybe call it 'max_elem_align'? Would likely make sense to initialize
it to 1.
> + for (unsigned j = 2u; j < num_operands; ++j) {
> + const auto type_id = GET_OPERAND(SpvId, j);
> + const auto types_iter = types.find(type_id);
> + if (types_iter == types.end())
> + break;
> +
> + const auto alignment = is_packed ? 1u
> + : types_iter->second.target_align;
> + const auto padding = (-struct_size) & (alignment - 1u);
> + struct_size += padding + types_iter->second.target_size;
> + max_elem_size = std::max(max_elem_size,
> + types_iter->second.target_align);
If you make 'max_elem_size = std::max(max_elem_size, alignment)' you can
get rid of the is_packed ternary below.
> + }
> + if (!is_packed)
Conditional is unnecessary if you take my suggestions above.
> + struct_size += (-struct_size) & (max_elem_size - 1u);
> +
> + types[id] = { module::argument::scalar, struct_size, struct_size,
> + is_packed ? 1u
> + : max_elem_size, module::argument::zero_ext
> + };
Place the closing curly bracket on the last line of the aggregate
initializer, here and below.
> + break;
> + }
> +
> + case SpvOpTypeVector: {
> + const auto id = GET_OPERAND(SpvId, 1);
> + const auto type_id = GET_OPERAND(SpvId, 2);
> + const auto types_iter = types.find(type_id);
> + if (types_iter == types.end())
> + break;
> +
> + const auto elem_size = types_iter->second.size;
> + const auto elem_nbs = GET_OPERAND(uint32_t, 3);
> + const auto size = elem_size * elem_nbs;
> + types[id] = { module::argument::scalar, size, size, size,
> + module::argument::zero_ext };
> + break;
> + }
> +
> + case SpvOpTypeForwardPointer: // FALLTHROUGH
> + case SpvOpTypePointer: {
> + const auto id = GET_OPERAND(SpvId, 1);
> + const auto storage_class = GET_OPERAND(SpvStorageClass, 2);
> + // Input means this is for a builtin variable, which can not be
> + // passed as an argument to a kernel.
> + if (storage_class == SpvStorageClassInput)
> + break;
> + types[id] = { convertStorageClass(storage_class, err),
> + sizeof(cl_mem),
> + static_cast<module::size_t>(pointer_byte_size),
> + static_cast<module::size_t>(pointer_byte_size),
> + module::argument::zero_ext
> + };
> + if (opcode == SpvOpTypePointer)
> + pointer_types[id] = GET_OPERAND(SpvId, 3);
> + break;
> + }
> +
> + case SpvOpTypeSampler:
> + types[GET_OPERAND(SpvId, 1)] = { module::argument::sampler,
> + sizeof(cl_sampler)
> + };
> + break;
> +
> + case SpvOpTypeImage: {
> + const auto id = GET_OPERAND(SpvId, 1);
> + const auto dim = GET_OPERAND(SpvDim, 3);
> + const auto access = GET_OPERAND(SpvAccessQualifier, 9);
> + types[id] = { convertImageType(id, dim, access, err),
> + sizeof(cl_mem), sizeof(cl_mem), sizeof(cl_mem),
> + module::argument::zero_ext
> + };
> + break;
> + }
> +
> + case SpvOpTypePipe: // FALLTHROUGH
> + case SpvOpTypeQueue: {
> + err += "TypePipe and TypeQueue are valid SPIR-V 1.0 types, but are "
> + "not available in the currently supported OpenCL C version."
> + "\n";
> + throw build_error();
> + }
> +
> + case SpvOpFunction: {
> + const auto kernels_iter = kernels.find(GET_OPERAND(SpvId, 2));
> + if (kernels_iter != kernels.end())
> + kernel_name = kernels_iter->second;
> + break;
> + }
> +
> + case SpvOpFunctionParameter: {
> + if (kernel_name.empty())
> + break;
> +
> + const auto type_id = GET_OPERAND(SpvId, 1);
> + auto arg = types.find(type_id)->second;
> + const auto &func_param_attr_iter =
> + func_param_attr_map.find(GET_OPERAND(SpvId, 2));
> + if (func_param_attr_iter != func_param_attr_map.end()) {
> + for (auto &i : func_param_attr_iter->second) {
> + switch (i) {
> + case SpvFunctionParameterAttributeSext:
> + arg.ext_type = module::argument::sign_ext;
> + break;
> + case SpvFunctionParameterAttributeZext:
> + arg.ext_type = module::argument::zero_ext;
> + break;
> + case SpvFunctionParameterAttributeByVal: {
> + const SpvId ptr_type_id =
> + pointer_types.find(type_id)->second;
> + arg = types.find(ptr_type_id)->second;
> + break;
> + }
> + default:
> + break;
> + }
> + }
> + }
> + args.emplace_back(arg);
> + break;
> + }
> +
> + case SpvOpFunctionEnd:
> + if (kernel_name.empty())
> + break;
> + m.syms.emplace_back(kernel_name, 0, kernel_nb, args);
> + ++kernel_nb;
> + kernel_name.clear();
> + args.clear();
> + break;
> +
> + default:
> + break;
> + }
> +
> + i += num_operands;
> + }
> +
> +#undef GET_OPERAND
> +
> + m.secs.push_back(make_text_section(source,
> + module::section::text_intermediate));
> + return m;
> + }
> +
> + bool
> + check_capabilities(const device &dev, const std::vector<char> &source,
> + std::string &r_log) {
> + const size_t length = source.size() / sizeof(uint32_t);
> + size_t i = 5u; // Skip header
> +
> + while (i < length) {
> + const auto desc_word = get<uint32_t>(source.data(), i);
> + const auto opcode = static_cast<SpvOp>(desc_word & SpvOpCodeMask);
> + const unsigned int num_operands = desc_word >> SpvWordCountShift;
> +
> + if (opcode != SpvOpCapability)
> + break;
> +
Isn't this going to miss most of the program if there is e.g. a
SpvOpSource before the first capability?
> + const auto capability = get<SpvCapability>(source.data(), i + 1u);
> + switch (capability) {
> + // Mandatory capabilities
> + case SpvCapabilityAddresses:
> + case SpvCapabilityFloat16Buffer:
> + case SpvCapabilityGroups:
> + case SpvCapabilityInt64:
> + case SpvCapabilityInt16:
> + case SpvCapabilityInt8:
> + case SpvCapabilityKernel:
> + case SpvCapabilityLinkage:
> + case SpvCapabilityVector16:
> + break;
> + // Optional capabilities
> + case SpvCapabilityImageBasic:
> + case SpvCapabilityLiteralSampler:
> + case SpvCapabilitySampled1D:
> + case SpvCapabilityImage1D:
> + case SpvCapabilitySampledBuffer:
> + case SpvCapabilityImageBuffer:
> + if (!dev.image_support()) {
> + r_log += "Capability 'ImageBasic' is not supported.\n";
> + return false;
> + }
> + break;
> + case SpvCapabilityFloat64:
> + if (!dev.has_doubles()) {
> + r_log += "Capability 'Float64' is not supported.\n";
> + return false;
> + }
> + break;
> + // Enabled through extensions
> + case SpvCapabilityFloat16:
> + if (!dev.has_halves()) {
> + r_log += "Capability 'Float16' is not supported.\n";
> + return false;
> + }
> + break;
> + default:
> + r_log += "Capability '" + std::to_string(capability) +
> + "' is not supported.\n";
> + return false;
> + }
> +
> + i += num_operands;
> + }
> +
> + return true;
> + }
> +
> + bool
> + check_extensions(const device &dev, const std::vector<char> &source,
> + std::string &r_log) {
> + const size_t length = source.size() / sizeof(uint32_t);
> + size_t i = 5u; // Skip header
> +
> + while (i < length) {
> + const auto desc_word = get<uint32_t>(source.data(), i);
> + const auto opcode = static_cast<SpvOp>(desc_word & SpvOpCodeMask);
> + const unsigned int num_operands = desc_word >> SpvWordCountShift;
> +
> + if (opcode == SpvOpCapability) {
> + i += num_operands;
> + continue;
> + }
> + if (opcode != SpvOpExtension)
> + break;
Similar issue here.
> +
> + const char *extension = source.data() + (i + 1u) * sizeof(uint32_t);
> + const std::string device_extensions = dev.supported_extensions();
> + const std::string platform_extensions =
> + dev.platform.supported_extensions();
> + if (device_extensions.find(extension) == std::string::npos &&
> + platform_extensions.find(extension) == std::string::npos) {
> + r_log += "Extension '" + std::string(extension) +
> + "' is not supported.\n";
> + return false;
> + }
> +
> + i += num_operands;
> + }
> +
> + return true;
> + }
> +
> + bool
> + check_memory_model(const device &dev, const std::vector<char> &source,
> + std::string &r_log) {
> + const size_t length = source.size() / sizeof(uint32_t);
> + size_t i = 5u; // Skip header
> +
> + while (i < length) {
> + const auto desc_word = get<uint32_t>(source.data(), i);
> + const auto opcode = static_cast<SpvOp>(desc_word & SpvOpCodeMask);
> + const unsigned int num_operands = desc_word >> SpvWordCountShift;
> +
> + switch (opcode) {
> + case SpvOpMemoryModel:
> + switch (get<SpvAddressingModel>(source.data(), i + 1u)) {
> + case SpvAddressingModelPhysical32:
> + return dev.address_bits() == 32;
> + case SpvAddressingModelPhysical64:
> + return dev.address_bits() == 64;
> + default:
> + unreachable("Only Physical32 and Physical64 are valid for OpenCL, and the binary was already validated");
> + return false;
> + }
> + break;
> + default:
> + break;
> + }
> +
> + i += num_operands;
> + }
> +
> + return false;
> + }
> +
> + // Copies the input binary and convert it to the endianness of the host CPU.
> + std::vector<char>
> + spirv_to_cpu(const std::vector<char> &binary)
> + {
> + const uint32_t first_word = get<uint32_t>(binary.data(), 0u);
> + if (first_word == SpvMagicNumber)
> + return binary;
> +
> + std::vector<char> cpu_endianness_binary(binary.size());
> + for (size_t i = 0; i < (binary.size() / 4u); ++i) {
> + const uint32_t word = get<uint32_t>(binary.data(), i);
> + reinterpret_cast<uint32_t *>(cpu_endianness_binary.data())[i] =
> + util_bswap32(word);
> + }
> +
> + return cpu_endianness_binary;
> + }
> +
> #ifdef CLOVER_ALLOW_SPIRV
> std::string
> format_validator_msg(spv_message_level_t level, const char * /* source */,
> @@ -96,7 +576,117 @@ clover::spirv::is_binary_spirv(const char *il, size_t length)
> (util_bswap32(first_word) == SpvMagicNumber);
> }
>
> +module
> +clover::spirv::process_program(const std::vector<char> &binary,
> + const device &dev, bool validate,
> + std::string &r_log) {
We should call this function "compile_program()" for consistency with
its LLVM IR counterpart. Also the 'validate' boolean seems to be always
true.
> + std::vector<char> source = spirv_to_cpu(binary);
> +
> + if (validate && !is_valid_spirv(
> + reinterpret_cast<const uint32_t *>(source.data()),
> + source.size() / 4u, dev.device_version(),
Is there any reason not to pass a 'const std::vector<char> &' to
is_valid_spirv instead of a separate uint32_t pointer and size?
> + [&r_log](const char *log){ r_log += std::string(log); }))
> + throw build_error();
> +
> + if (!check_capabilities(dev, source, r_log))
> + throw build_error();
> + if (!check_extensions(dev, source, r_log))
> + throw build_error();
> + if (!check_memory_model(dev, source, r_log))
> + throw build_error();
> +
> + return create_module_from_spirv(source,
> + dev.address_bits() == 32 ? 4u : 8u, r_log);
> +}
> +
> #ifdef CLOVER_ALLOW_SPIRV
Shouldn't most of the code above be compiled conditionally based on this
define?
> +module
> +clover::spirv::link_program(const std::vector<module> &modules,
> + const device &dev, const std::string &opts,
> + std::string &r_log) {
> + std::vector<std::string> options = clover::llvm::tokenize(opts);
> +
> + const bool create_library = count("-create-library", options);
> + erase_if(equals("-create-library"), options);
> + erase_if(equals("-enable-link-options"), options);
> +
> + if (!options.empty()) {
> + r_log += "SPIR-V linker: Ignoring the following link options: ";
> + for (const auto &opt : options)
> + r_log += "'" + opt + "' ";
> + }
> +
This seems like a really inefficient way to check for unused options.
You could just loop over the options vector non-destructively and print
out the ones you don't know.
> + spvtools::LinkerOptions linker_options;
> + linker_options.SetCreateLibrary(create_library);
> +
> + module m;
> +
> + const auto section_type = create_library ? module::section::text_library :
> + module::section::text_executable;
> +
> + std::vector<const uint32_t *> sections;
> + sections.reserve(modules.size());
> + std::vector<size_t> lengths;
> + lengths.reserve(modules.size());
> +
> + auto const validator_consumer = [&r_log](spv_message_level_t level,
> + const char *source,
> + const spv_position_t &position,
> + const char *message) {
> + r_log += format_validator_msg(level, source, position, message);
> + };
> +
> + for (const auto &mod : modules) {
> + const module::section *msec = nullptr;
> + try {
> + msec = &find([&](const module::section &sec) {
> + return sec.type == module::section::text_intermediate ||
> + sec.type == module::section::text_library;
> + }, mod.secs);
> + } catch (const std::out_of_range &e) {
> + // We should never reach this as validate_link_devices already checked
> + // that a binary was present.
> + assert(false);
> + }
> +
Catching an exception in order to assert(0) doesn't seem useful. How
about replacing the whole block with:
| const auto &msec = find([&](const module::section &sec) {
| return sec.type == module::section::text_intermediate ||
| sec.type == module::section::text_library;
| }, mod.secs);
> + const auto c_il = msec->data.data() +
> + sizeof(struct pipe_llvm_program_header);
> + const auto length = msec->size;
> +
> + sections.push_back(reinterpret_cast<const uint32_t *>(c_il));
> + lengths.push_back(length / sizeof(uint32_t));
> + }
> +
> + std::vector<uint32_t> linked_binary;
> +
> + const std::string opencl_version = dev.device_version();
> + const spv_target_env target_env =
> + convert_opencl_str_to_target_env(opencl_version);
> +
> + const spvtools::MessageConsumer consumer = validator_consumer;
> + spvtools::Context context(target_env);
> + context.SetMessageConsumer(std::move(consumer));
> +
> + if (Link(context, sections.data(), lengths.data(), sections.size(),
> + &linked_binary, linker_options) != SPV_SUCCESS)
> + throw error(CL_LINK_PROGRAM_FAILURE);
> +
> + if (!is_valid_spirv(linked_binary.data(), linked_binary.size(),
> + opencl_version,
> + [&r_log](const char *log){ r_log += std::string(log); }))
> + throw error(CL_LINK_PROGRAM_FAILURE);
> +
> + for (const auto &mod : modules)
> + m.syms.insert(m.syms.end(), mod.syms.begin(), mod.syms.end());
> +
> + m.secs.emplace_back(make_text_section({
> + reinterpret_cast<char *>(linked_binary.data()),
> + reinterpret_cast<char *>(linked_binary.data()) +
> + linked_binary.size() * sizeof(uint32_t) }, section_type));
Simplify 'reinterpret_cast<char *>(linked_binary.data()) +
linked_binary.size() * sizeof(uint32_t)' to 'reinterpret_cast<char
*>(linked_binary.data() + linked_binary.size())'.
> +
> + return m;
> +}
> +
> bool
> clover::spirv::is_valid_spirv(const uint32_t *binary, size_t length,
> const std::string &opencl_version,
> @@ -120,6 +710,14 @@ clover::spirv::is_valid_spirv(const uint32_t *binary, size_t length,
> return spvTool.Validate(binary, length);
> }
> #else
> +module
> +clover::spirv::link_program(const std::vector<module> &/*modules*/,
> + const device &/*dev*/, const std::string &/*opts*/,
> + std::string &r_log) {
> + r_log += "SPIRV-Tools and llvm-spirv are required for linking SPIR-V binaries.\n";
> + throw error(CL_LINKER_NOT_AVAILABLE);
> +}
> +
> bool
> clover::spirv::is_valid_spirv(const uint32_t * /*binary*/, size_t /*length*/,
> const std::string &/*opencl_version*/,
> diff --git a/src/gallium/state_trackers/clover/spirv/invocation.hpp b/src/gallium/state_trackers/clover/spirv/invocation.hpp
> index 92255a68a8e..37cd1377cb2 100644
> --- a/src/gallium/state_trackers/clover/spirv/invocation.hpp
> +++ b/src/gallium/state_trackers/clover/spirv/invocation.hpp
> @@ -24,6 +24,8 @@
> #define CLOVER_SPIRV_INVOCATION_HPP
>
> #include "core/context.hpp"
> +#include "core/module.hpp"
> +#include "core/program.hpp"
>
> namespace clover {
> namespace spirv {
> @@ -41,6 +43,16 @@ namespace clover {
> bool is_valid_spirv(const uint32_t *binary, size_t length,
> const std::string &opencl_version,
> const context::notify_action ¬ify);
> +
> + // Creates a clover module out of the given SPIR-V binary.
> + module process_program(const std::vector<char> &binary,
> + const device &dev, bool validate,
> + std::string &r_log);
> +
> + // Combines multiple clover modules into a single one, resolving
> + // link dependencies between them.
> + module link_program(const std::vector<module> &modules, const device &dev,
> + const std::string &opts, std::string &r_log);
> }
> }
>
> --
> 2.21.0
>
> _______________________________________________
> mesa-dev mailing list
> mesa-dev at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/mesa-dev
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 227 bytes
Desc: not available
URL: <https://lists.freedesktop.org/archives/mesa-dev/attachments/20190511/e3cb2fed/attachment-0001.sig>
More information about the mesa-dev
mailing list