mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2025-12-24 17:30:12 +01:00
Reviewed-by: Serge Martin <edb@sigluy.net> Reviewed-by: Francisco Jerez <currojerez@riseup.net> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/4974>
901 lines
32 KiB
C++
901 lines
32 KiB
C++
//
|
|
// Copyright 2018 Pierre Moreau
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the "Software"),
|
|
// to deal in the Software without restriction, including without limitation
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
// and/or sell copies of the Software, and to permit persons to whom the
|
|
// Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
// OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
#include "invocation.hpp"
|
|
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#ifdef HAVE_CLOVER_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"
|
|
|
|
#define SPIRV_HEADER_WORD_SIZE 5
|
|
|
|
using namespace clover;
|
|
|
|
#ifdef HAVE_CLOVER_SPIRV
|
|
namespace {
|
|
|
|
uint32_t
|
|
make_spirv_version(uint8_t major, uint8_t minor) {
|
|
return (static_cast<uint32_t>(major) << 16u) |
|
|
(static_cast<uint32_t>(minor) << 8u);
|
|
}
|
|
|
|
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
|
|
convert_storage_class(SpvStorageClass storage_class, std::string &err) {
|
|
switch (storage_class) {
|
|
case SpvStorageClassFunction:
|
|
return module::argument::scalar;
|
|
case SpvStorageClassUniformConstant:
|
|
return module::argument::global;
|
|
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();
|
|
}
|
|
}
|
|
|
|
cl_kernel_arg_address_qualifier
|
|
convert_storage_class_to_cl(SpvStorageClass storage_class) {
|
|
switch (storage_class) {
|
|
case SpvStorageClassUniformConstant:
|
|
return CL_KERNEL_ARG_ADDRESS_CONSTANT;
|
|
case SpvStorageClassWorkgroup:
|
|
return CL_KERNEL_ARG_ADDRESS_LOCAL;
|
|
case SpvStorageClassCrossWorkgroup:
|
|
return CL_KERNEL_ARG_ADDRESS_GLOBAL;
|
|
case SpvStorageClassFunction:
|
|
default:
|
|
return CL_KERNEL_ARG_ADDRESS_PRIVATE;
|
|
}
|
|
}
|
|
|
|
enum module::argument::type
|
|
convert_image_type(SpvId id, SpvDim dim, SpvAccessQualifier access,
|
|
std::string &err) {
|
|
if (dim == SpvDim2D && access == SpvAccessQualifierReadOnly)
|
|
return module::argument::image2d_rd;
|
|
else if (dim == SpvDim2D && access == SpvAccessQualifierWriteOnly)
|
|
return module::argument::image2d_wr;
|
|
else if (dim == SpvDim3D && access == SpvAccessQualifierReadOnly)
|
|
return module::argument::image3d_rd;
|
|
else if (dim == SpvDim3D && access == SpvAccessQualifierWriteOnly)
|
|
return module::argument::image3d_wr;
|
|
else {
|
|
err += "Unknown access qualifier " + std::to_string(access)
|
|
+ " or dimension " + std::to_string(dim) + " for image "
|
|
+ std::to_string(id) + ".\n";
|
|
throw build_error();
|
|
}
|
|
}
|
|
|
|
module::section
|
|
make_text_section(const std::vector<char> &code,
|
|
enum module::section::type section_type) {
|
|
const pipe_binary_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 = SPIRV_HEADER_WORD_SIZE; // Skip header
|
|
|
|
std::string kernel_name;
|
|
size_t kernel_nb = 0u;
|
|
std::vector<module::argument> args;
|
|
std::vector<size_t> req_local_size;
|
|
|
|
module m;
|
|
|
|
std::unordered_map<SpvId, std::vector<size_t> > req_local_sizes;
|
|
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;
|
|
std::unordered_map<SpvId, std::string> names;
|
|
std::unordered_map<SpvId, cl_kernel_arg_type_qualifier> qualifiers;
|
|
std::unordered_map<std::string, std::vector<std::string> > param_type_names;
|
|
|
|
while (i < length) {
|
|
const auto inst = &source[i * sizeof(uint32_t)];
|
|
const auto desc_word = get<uint32_t>(inst, 0);
|
|
const auto opcode = static_cast<SpvOp>(desc_word & SpvOpCodeMask);
|
|
const unsigned int num_operands = desc_word >> SpvWordCountShift;
|
|
|
|
switch (opcode) {
|
|
case SpvOpName: {
|
|
names.emplace(get<SpvId>(inst, 1),
|
|
source.data() + (i + 2u) * sizeof(uint32_t));
|
|
break;
|
|
}
|
|
|
|
case SpvOpString: {
|
|
// SPIRV-LLVM-Translator stores param type names as OpStrings
|
|
std::string str(source.data() + (i + 2u) * sizeof(uint32_t));
|
|
if (str.find("kernel_arg_type.") != 0)
|
|
break;
|
|
|
|
std::string line;
|
|
std::istringstream istream(str.substr(16));
|
|
|
|
std::getline(istream, line, '.');
|
|
|
|
std::string k = line;
|
|
while (std::getline(istream, line, ','))
|
|
param_type_names[k].push_back(line);
|
|
break;
|
|
}
|
|
|
|
case SpvOpEntryPoint:
|
|
if (get<SpvExecutionModel>(inst, 1) == SpvExecutionModelKernel)
|
|
kernels.emplace(get<SpvId>(inst, 2),
|
|
source.data() + (i + 3u) * sizeof(uint32_t));
|
|
break;
|
|
|
|
case SpvOpExecutionMode:
|
|
switch (get<SpvExecutionMode>(inst, 2)) {
|
|
case SpvExecutionModeLocalSize:
|
|
req_local_sizes[get<SpvId>(inst, 1)] = {
|
|
get<uint32_t>(inst, 3),
|
|
get<uint32_t>(inst, 4),
|
|
get<uint32_t>(inst, 5)
|
|
};
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
case SpvOpDecorate: {
|
|
const auto id = get<SpvId>(inst, 1);
|
|
const auto decoration = get<SpvDecoration>(inst, 2);
|
|
switch (decoration) {
|
|
case SpvDecorationCPacked:
|
|
packed_structures.emplace(id);
|
|
break;
|
|
case SpvDecorationFuncParamAttr: {
|
|
const auto attribute =
|
|
get<SpvFunctionParameterAttribute>(inst, 3u);
|
|
func_param_attr_map[id].push_back(attribute);
|
|
break;
|
|
}
|
|
case SpvDecorationVolatile:
|
|
qualifiers[id] |= CL_KERNEL_ARG_TYPE_VOLATILE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SpvOpGroupDecorate: {
|
|
const auto group_id = get<SpvId>(inst, 1);
|
|
if (packed_structures.count(group_id)) {
|
|
for (unsigned int i = 2u; i < num_operands; ++i)
|
|
packed_structures.emplace(get<SpvId>(inst, 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) {
|
|
auto &attrs = func_param_attr_map[get<SpvId>(inst, i)];
|
|
attrs.insert(attrs.begin(),
|
|
func_param_attr_iter->second.begin(),
|
|
func_param_attr_iter->second.end());
|
|
}
|
|
}
|
|
if (qualifiers.count(group_id)) {
|
|
for (unsigned int i = 2u; i < num_operands; ++i)
|
|
qualifiers[get<SpvId>(inst, i)] |= qualifiers[group_id];
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SpvOpConstant:
|
|
// We only care about constants that represent the size of arrays.
|
|
// 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<SpvId>(inst, 2)] = get<unsigned int>(inst, 3u);
|
|
break;
|
|
|
|
case SpvOpTypeInt:
|
|
case SpvOpTypeFloat: {
|
|
const auto size = get<uint32_t>(inst, 2) / 8u;
|
|
const auto id = get<SpvId>(inst, 1);
|
|
types[id] = { module::argument::scalar, size, size, size,
|
|
module::argument::zero_ext };
|
|
types[id].info.address_qualifier = CL_KERNEL_ARG_ADDRESS_PRIVATE;
|
|
break;
|
|
}
|
|
|
|
case SpvOpTypeArray: {
|
|
const auto id = get<SpvId>(inst, 1);
|
|
const auto type_id = get<SpvId>(inst, 2);
|
|
const auto types_iter = types.find(type_id);
|
|
if (types_iter == types.end())
|
|
break;
|
|
|
|
const auto constant_id = get<SpvId>(inst, 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<SpvId>(inst, 1);
|
|
const bool is_packed = packed_structures.count(id);
|
|
|
|
unsigned struct_size = 0u;
|
|
unsigned struct_align = 1u;
|
|
for (unsigned j = 2u; j < num_operands; ++j) {
|
|
const auto type_id = get<SpvId>(inst, j);
|
|
const auto types_iter = types.find(type_id);
|
|
|
|
// If a type was not found, that means it is not one of the
|
|
// types allowed as kernel arguments. And since the module has
|
|
// been validated, this means this type is not used for kernel
|
|
// arguments, and therefore can be ignored.
|
|
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;
|
|
struct_align = std::max(struct_align, alignment);
|
|
}
|
|
struct_size += (-struct_size) & (struct_align - 1u);
|
|
types[id] = { module::argument::scalar, struct_size, struct_size,
|
|
struct_align, module::argument::zero_ext };
|
|
break;
|
|
}
|
|
|
|
case SpvOpTypeVector: {
|
|
const auto id = get<SpvId>(inst, 1);
|
|
const auto type_id = get<SpvId>(inst, 2);
|
|
const auto types_iter = types.find(type_id);
|
|
|
|
// If a type was not found, that means it is not one of the
|
|
// types allowed as kernel arguments. And since the module has
|
|
// been validated, this means this type is not used for kernel
|
|
// arguments, and therefore can be ignored.
|
|
if (types_iter == types.end())
|
|
break;
|
|
|
|
const auto elem_size = types_iter->second.size;
|
|
const auto elem_nbs = get<uint32_t>(inst, 3);
|
|
const auto size = elem_size * elem_nbs;
|
|
const auto align = elem_size * util_next_power_of_two(elem_nbs);
|
|
types[id] = { module::argument::scalar, size, size, align,
|
|
module::argument::zero_ext };
|
|
types[id].info.address_qualifier = CL_KERNEL_ARG_ADDRESS_PRIVATE;
|
|
break;
|
|
}
|
|
|
|
case SpvOpTypeForwardPointer: // FALLTHROUGH
|
|
case SpvOpTypePointer: {
|
|
const auto id = get<SpvId>(inst, 1);
|
|
const auto storage_class = get<SpvStorageClass>(inst, 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;
|
|
|
|
if (opcode == SpvOpTypePointer)
|
|
pointer_types[id] = get<SpvId>(inst, 3);
|
|
|
|
types[id] = { convert_storage_class(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 };
|
|
types[id].info.address_qualifier = convert_storage_class_to_cl(storage_class);
|
|
break;
|
|
}
|
|
|
|
case SpvOpTypeSampler:
|
|
types[get<SpvId>(inst, 1)] = { module::argument::sampler,
|
|
sizeof(cl_sampler) };
|
|
break;
|
|
|
|
case SpvOpTypeImage: {
|
|
const auto id = get<SpvId>(inst, 1);
|
|
const auto dim = get<SpvDim>(inst, 3);
|
|
const auto access = get<SpvAccessQualifier>(inst, 9);
|
|
types[id] = { convert_image_type(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: {
|
|
auto id = get<SpvId>(inst, 2);
|
|
const auto kernels_iter = kernels.find(id);
|
|
if (kernels_iter != kernels.end())
|
|
kernel_name = kernels_iter->second;
|
|
|
|
const auto req_local_size_iter = req_local_sizes.find(id);
|
|
if (req_local_size_iter != req_local_sizes.end())
|
|
req_local_size = (*req_local_size_iter).second;
|
|
else
|
|
req_local_size = { 0, 0, 0 };
|
|
|
|
break;
|
|
}
|
|
|
|
case SpvOpFunctionParameter: {
|
|
if (kernel_name.empty())
|
|
break;
|
|
|
|
const auto id = get<SpvId>(inst, 2);
|
|
const auto type_id = get<SpvId>(inst, 1);
|
|
auto arg = types.find(type_id)->second;
|
|
const auto &func_param_attr_iter =
|
|
func_param_attr_map.find(get<SpvId>(inst, 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;
|
|
}
|
|
case SpvFunctionParameterAttributeNoAlias:
|
|
arg.info.type_qualifier |= CL_KERNEL_ARG_TYPE_RESTRICT;
|
|
break;
|
|
case SpvFunctionParameterAttributeNoWrite:
|
|
arg.info.type_qualifier |= CL_KERNEL_ARG_TYPE_CONST;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto name_it = names.find(id);
|
|
if (name_it != names.end())
|
|
arg.info.arg_name = (*name_it).second;
|
|
|
|
arg.info.type_qualifier |= qualifiers[id];
|
|
arg.info.address_qualifier = types[type_id].info.address_qualifier;
|
|
arg.info.access_qualifier = CL_KERNEL_ARG_ACCESS_NONE;
|
|
args.emplace_back(arg);
|
|
break;
|
|
}
|
|
|
|
case SpvOpFunctionEnd:
|
|
if (kernel_name.empty())
|
|
break;
|
|
|
|
for (size_t i = 0; i < param_type_names[kernel_name].size(); i++)
|
|
args[i].info.type_name = param_type_names[kernel_name][i];
|
|
|
|
m.syms.emplace_back(kernel_name, std::string(),
|
|
req_local_size, 0, kernel_nb, args);
|
|
++kernel_nb;
|
|
kernel_name.clear();
|
|
args.clear();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
i += num_operands;
|
|
}
|
|
|
|
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 = SPIRV_HEADER_WORD_SIZE; // 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;
|
|
|
|
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;
|
|
case SpvCapabilityInt64Atomics:
|
|
if (!dev.has_int64_atomics()) {
|
|
r_log += "Capability 'Int64Atomics' 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 = SPIRV_HEADER_WORD_SIZE; // Skip header
|
|
const auto spirv_extensions = spirv::supported_extensions();
|
|
|
|
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;
|
|
|
|
const std::string extension = source.data() + (i + 1u) * sizeof(uint32_t);
|
|
if (spirv_extensions.count(extension) == 0) {
|
|
r_log += "Extension '" + 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 = SPIRV_HEADER_WORD_SIZE; // 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 HAVE_CLOVER_SPIRV
|
|
std::string
|
|
format_validator_msg(spv_message_level_t level, const char * /* source */,
|
|
const spv_position_t &position, const char *message) {
|
|
std::string level_str;
|
|
switch (level) {
|
|
case SPV_MSG_FATAL:
|
|
level_str = "Fatal";
|
|
break;
|
|
case SPV_MSG_INTERNAL_ERROR:
|
|
level_str = "Internal error";
|
|
break;
|
|
case SPV_MSG_ERROR:
|
|
level_str = "Error";
|
|
break;
|
|
case SPV_MSG_WARNING:
|
|
level_str = "Warning";
|
|
break;
|
|
case SPV_MSG_INFO:
|
|
level_str = "Info";
|
|
break;
|
|
case SPV_MSG_DEBUG:
|
|
level_str = "Debug";
|
|
break;
|
|
}
|
|
return "[" + level_str + "] At word No." +
|
|
std::to_string(position.index) + ": \"" + message + "\"\n";
|
|
}
|
|
|
|
spv_target_env
|
|
convert_opencl_str_to_target_env(const std::string &opencl_version) {
|
|
if (opencl_version == "2.2") {
|
|
return SPV_ENV_OPENCL_2_2;
|
|
} else if (opencl_version == "2.1") {
|
|
return SPV_ENV_OPENCL_2_1;
|
|
} else if (opencl_version == "2.0") {
|
|
return SPV_ENV_OPENCL_2_0;
|
|
} else if (opencl_version == "1.2" ||
|
|
opencl_version == "1.1" ||
|
|
opencl_version == "1.0") {
|
|
// SPIR-V is only defined for OpenCL >= 1.2, however some drivers
|
|
// might use it with OpenCL 1.0 and 1.1.
|
|
return SPV_ENV_OPENCL_1_2;
|
|
} else {
|
|
throw build_error("Invalid OpenCL version");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
module
|
|
clover::spirv::compile_program(const std::vector<char> &binary,
|
|
const device &dev, std::string &r_log,
|
|
bool validate) {
|
|
std::vector<char> source = spirv_to_cpu(binary);
|
|
|
|
if (!is_valid_spirv(source, dev.device_version(), r_log, validate))
|
|
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);
|
|
}
|
|
|
|
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);
|
|
|
|
bool create_library = false;
|
|
|
|
std::string ignored_options;
|
|
for (const std::string &option : options) {
|
|
if (option == "-create-library") {
|
|
create_library = true;
|
|
} else {
|
|
ignored_options += "'" + option + "' ";
|
|
}
|
|
}
|
|
if (!ignored_options.empty()) {
|
|
r_log += "Ignoring the following link options: " + ignored_options
|
|
+ "\n";
|
|
}
|
|
|
|
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 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 = ((struct pipe_binary_program_header*)msec.data.data())->blob;
|
|
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);
|
|
|
|
std::vector<char> final_binary{
|
|
reinterpret_cast<char *>(linked_binary.data()),
|
|
reinterpret_cast<char *>(linked_binary.data() +
|
|
linked_binary.size()) };
|
|
if (!is_valid_spirv(final_binary, opencl_version, r_log))
|
|
throw error(CL_LINK_PROGRAM_FAILURE);
|
|
|
|
if (has_flag(llvm::debug::spirv))
|
|
llvm::debug::log(".spvasm", spirv::print_module(final_binary, dev.device_version()));
|
|
|
|
for (const auto &mod : modules)
|
|
m.syms.insert(m.syms.end(), mod.syms.begin(), mod.syms.end());
|
|
|
|
m.secs.emplace_back(make_text_section(final_binary, section_type));
|
|
|
|
return m;
|
|
}
|
|
|
|
bool
|
|
clover::spirv::is_valid_spirv(const std::vector<char> &binary,
|
|
const std::string &opencl_version,
|
|
std::string &r_log,
|
|
bool validate) {
|
|
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);
|
|
};
|
|
|
|
const spv_target_env target_env =
|
|
convert_opencl_str_to_target_env(opencl_version);
|
|
spvtools::SpirvTools spvTool(target_env);
|
|
spvTool.SetMessageConsumer(validator_consumer);
|
|
|
|
if (!validate)
|
|
return true;
|
|
return spvTool.Validate(reinterpret_cast<const uint32_t *>(binary.data()),
|
|
binary.size() / 4u);
|
|
}
|
|
|
|
std::string
|
|
clover::spirv::print_module(const std::vector<char> &binary,
|
|
const std::string &opencl_version) {
|
|
const spv_target_env target_env =
|
|
convert_opencl_str_to_target_env(opencl_version);
|
|
spvtools::SpirvTools spvTool(target_env);
|
|
spv_context spvContext = spvContextCreate(target_env);
|
|
if (!spvContext)
|
|
return "Failed to create an spv_context for disassembling the module.";
|
|
|
|
spv_text disassembly;
|
|
spvBinaryToText(spvContext,
|
|
reinterpret_cast<const uint32_t *>(binary.data()),
|
|
binary.size() / 4u, SPV_BINARY_TO_TEXT_OPTION_NONE,
|
|
&disassembly, nullptr);
|
|
spvContextDestroy(spvContext);
|
|
|
|
const std::string disassemblyStr = disassembly->str;
|
|
spvTextDestroy(disassembly);
|
|
|
|
return disassemblyStr;
|
|
}
|
|
|
|
std::unordered_set<std::string>
|
|
clover::spirv::supported_extensions() {
|
|
return {
|
|
/* this is only a hint so all devices support that */
|
|
"SPV_KHR_no_integer_wrap_decoration"
|
|
};
|
|
}
|
|
|
|
std::vector<uint32_t>
|
|
clover::spirv::supported_versions() {
|
|
return { make_spirv_version(1u, 0u) };
|
|
}
|
|
|
|
#else
|
|
bool
|
|
clover::spirv::is_valid_spirv(const std::vector<char> &/*binary*/,
|
|
const std::string &/*opencl_version*/,
|
|
std::string &/*r_log*/, bool /*validate*/) {
|
|
return false;
|
|
}
|
|
|
|
module
|
|
clover::spirv::compile_program(const std::vector<char> &binary,
|
|
const device &dev, std::string &r_log,
|
|
bool validate) {
|
|
r_log += "SPIR-V support in clover is not enabled.\n";
|
|
throw build_error();
|
|
}
|
|
|
|
module
|
|
clover::spirv::link_program(const std::vector<module> &/*modules*/,
|
|
const device &/*dev*/, const std::string &/*opts*/,
|
|
std::string &r_log) {
|
|
r_log += "SPIR-V support in clover is not enabled.\n";
|
|
throw error(CL_LINKER_NOT_AVAILABLE);
|
|
}
|
|
|
|
std::string
|
|
clover::spirv::print_module(const std::vector<char> &binary,
|
|
const std::string &opencl_version) {
|
|
return std::string();
|
|
}
|
|
|
|
std::unordered_set<std::string>
|
|
clover::spirv::supported_extensions() {
|
|
return {};
|
|
}
|
|
|
|
std::vector<uint32_t>
|
|
clover::spirv::supported_versions() {
|
|
return {};
|
|
}
|
|
#endif
|
|
|
|
module
|
|
clover::spirv::load_clc(const device &dev)
|
|
{
|
|
std::vector<char> ilfile;
|
|
std::ifstream file;
|
|
std::string name32 = "spirv-mesa3d-.spv";
|
|
std::string name64 = "spirv64-mesa3d-.spv";
|
|
file.open(LIBCLC_LIBEXECDIR + (dev.address_bits() == 64 ? name64 : name32), std::ifstream::in | std::ifstream::binary);
|
|
if (!file.good())
|
|
throw error(CL_COMPILER_NOT_AVAILABLE);
|
|
|
|
file.seekg(0, std::ios::end);
|
|
std::streampos length(file.tellg());
|
|
if (length) {
|
|
file.seekg(0, std::ios::beg);
|
|
ilfile.resize(static_cast<std::size_t>(length));
|
|
file.read(&ilfile.front(), static_cast<std::size_t>(length));
|
|
}
|
|
|
|
std::string log;
|
|
return spirv::compile_program(ilfile, dev, log, false);
|
|
}
|