mesa/src/compiler/clc/clc_helpers.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1487 lines
49 KiB
C++
Raw Normal View History

//
// Copyright 2012-2016 Francisco Jerez
// Copyright 2012-2016 Advanced Micro Devices, Inc.
// Copyright 2014-2016 Jan Vesely
// Copyright 2014-2015 Serge Martin
// Copyright 2015 Zoltan Gilian
//
// 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 <cstdlib>
#include <filesystem>
#include <sstream>
#include <mutex>
#include "util/ralloc.h"
#include "util/set.h"
#include <llvm/ADT/ArrayRef.h>
#include <llvm/IR/DiagnosticPrinter.h>
#include <llvm/IR/DiagnosticInfo.h>
#include <llvm/IR/LegacyPassManager.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Type.h>
#include <llvm/MC/TargetRegistry.h>
#include <llvm/Target/TargetMachine.h>
#include <llvm/Support/raw_ostream.h>
#include <llvm/Bitcode/BitcodeWriter.h>
#include <llvm/Bitcode/BitcodeReader.h>
#include <llvm-c/Core.h>
#include <llvm-c/Target.h>
#include <LLVMSPIRVLib/LLVMSPIRVLib.h>
#include <clang/Config/config.h>
#include <clang/Driver/Driver.h>
#include <clang/CodeGen/CodeGenAction.h>
#include <clang/Lex/PreprocessorOptions.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/TextDiagnosticBuffer.h>
#include <clang/Frontend/TextDiagnosticPrinter.h>
#include <clang/Frontend/Utils.h>
#include <clang/Basic/TargetInfo.h>
#include <spirv-tools/libspirv.h>
#include <spirv-tools/libspirv.hpp>
#include <spirv-tools/linker.hpp>
#include <spirv-tools/optimizer.hpp>
#if LLVM_VERSION_MAJOR >= 16
#include <llvm/TargetParser/Triple.h>
#endif
#if LLVM_VERSION_MAJOR >= 20
#include <llvm/Support/VirtualFileSystem.h>
#endif
#include "util/macros.h"
#include "util/u_dl.h"
#include "glsl_types.h"
#include "spirv.h"
#if DETECT_OS_POSIX
#include <dlfcn.h>
#endif
#ifdef USE_STATIC_OPENCL_C_H
#include "opencl-c-base.h.h"
#include "opencl-c.h.h"
#endif
#include "clc_helpers.h"
namespace fs = std::filesystem;
/* Use the highest version of SPIRV supported by SPIRV-Tools. */
constexpr spv_target_env spirv_target = SPV_ENV_UNIVERSAL_1_6;
constexpr SPIRV::VersionNumber invalid_spirv_trans_version = static_cast<SPIRV::VersionNumber>(0);
using ::llvm::Function;
using ::llvm::legacy::PassManager;
using ::llvm::LLVMContext;
using ::llvm::Module;
using ::llvm::raw_string_ostream;
using ::llvm::TargetRegistry;
using ::clang::driver::Driver;
static void
clc_dump_llvm(const llvm::Module *mod, FILE *f);
static void
#if LLVM_VERSION_MAJOR >= 19
llvm_log_handler(const ::llvm::DiagnosticInfo *di, void *data) {
#else
llvm_log_handler(const ::llvm::DiagnosticInfo &di, void *data) {
#endif
const clc_logger *logger = static_cast<clc_logger *>(data);
std::string log;
raw_string_ostream os { log };
::llvm::DiagnosticPrinterRawOStream printer { os };
#if LLVM_VERSION_MAJOR >= 19
di->print(printer);
#else
di.print(printer);
#endif
clc_error(logger, "%s", log.c_str());
}
class SPIRVKernelArg {
public:
SPIRVKernelArg(uint32_t id, uint32_t typeId) : id(id), typeId(typeId),
addrQualifier(CLC_KERNEL_ARG_ADDRESS_PRIVATE),
accessQualifier(0),
typeQualifier(0) { }
~SPIRVKernelArg() { }
uint32_t id;
uint32_t typeId;
std::string name;
std::string typeName;
enum clc_kernel_arg_address_qualifier addrQualifier;
unsigned accessQualifier;
unsigned typeQualifier;
};
class SPIRVKernelInfo {
public:
SPIRVKernelInfo(uint32_t fid, const char *nm)
: funcId(fid), name(nm), vecHint(0), localSize(), localSizeHint() { }
~SPIRVKernelInfo() { }
uint32_t funcId;
std::string name;
std::vector<SPIRVKernelArg> args;
unsigned vecHint;
unsigned localSize[3];
unsigned localSizeHint[3];
};
class SPIRVKernelParser {
public:
SPIRVKernelParser() : curKernel(NULL)
{
ctx = spvContextCreate(spirv_target);
}
~SPIRVKernelParser()
{
spvContextDestroy(ctx);
}
void parseEntryPoint(const spv_parsed_instruction_t *ins)
{
assert(ins->num_operands >= 3);
const spv_parsed_operand_t *op = &ins->operands[1];
assert(op->type == SPV_OPERAND_TYPE_ID);
uint32_t funcId = ins->words[op->offset];
for (auto &iter : kernels) {
if (funcId == iter.funcId)
return;
}
op = &ins->operands[2];
assert(op->type == SPV_OPERAND_TYPE_LITERAL_STRING);
const char *name = reinterpret_cast<const char *>(ins->words + op->offset);
kernels.push_back(SPIRVKernelInfo(funcId, name));
}
void parseFunction(const spv_parsed_instruction_t *ins)
{
assert(ins->num_operands == 4);
const spv_parsed_operand_t *op = &ins->operands[1];
assert(op->type == SPV_OPERAND_TYPE_RESULT_ID);
uint32_t funcId = ins->words[op->offset];
for (auto &kernel : kernels) {
if (funcId == kernel.funcId && !kernel.args.size()) {
curKernel = &kernel;
return;
}
}
}
void parseFunctionParam(const spv_parsed_instruction_t *ins)
{
const spv_parsed_operand_t *op;
uint32_t id, typeId;
if (!curKernel)
return;
assert(ins->num_operands == 2);
op = &ins->operands[0];
assert(op->type == SPV_OPERAND_TYPE_TYPE_ID);
typeId = ins->words[op->offset];
op = &ins->operands[1];
assert(op->type == SPV_OPERAND_TYPE_RESULT_ID);
id = ins->words[op->offset];
curKernel->args.push_back(SPIRVKernelArg(id, typeId));
}
void parseName(const spv_parsed_instruction_t *ins)
{
const spv_parsed_operand_t *op;
const char *name;
uint32_t id;
assert(ins->num_operands == 2);
op = &ins->operands[0];
assert(op->type == SPV_OPERAND_TYPE_ID);
id = ins->words[op->offset];
op = &ins->operands[1];
assert(op->type == SPV_OPERAND_TYPE_LITERAL_STRING);
name = reinterpret_cast<const char *>(ins->words + op->offset);
for (auto &kernel : kernels) {
for (auto &arg : kernel.args) {
if (arg.id == id && arg.name.empty()) {
arg.name = name;
break;
}
}
}
}
void parseTypePointer(const spv_parsed_instruction_t *ins)
{
enum clc_kernel_arg_address_qualifier addrQualifier;
uint32_t typeId, storageClass;
const spv_parsed_operand_t *op;
assert(ins->num_operands == 3);
op = &ins->operands[0];
assert(op->type == SPV_OPERAND_TYPE_RESULT_ID);
typeId = ins->words[op->offset];
op = &ins->operands[1];
assert(op->type == SPV_OPERAND_TYPE_STORAGE_CLASS);
storageClass = ins->words[op->offset];
switch (storageClass) {
case SpvStorageClassCrossWorkgroup:
addrQualifier = CLC_KERNEL_ARG_ADDRESS_GLOBAL;
break;
case SpvStorageClassWorkgroup:
addrQualifier = CLC_KERNEL_ARG_ADDRESS_LOCAL;
break;
case SpvStorageClassUniformConstant:
addrQualifier = CLC_KERNEL_ARG_ADDRESS_CONSTANT;
break;
default:
addrQualifier = CLC_KERNEL_ARG_ADDRESS_PRIVATE;
break;
}
for (auto &kernel : kernels) {
for (auto &arg : kernel.args) {
if (arg.typeId == typeId) {
arg.addrQualifier = addrQualifier;
if (addrQualifier == CLC_KERNEL_ARG_ADDRESS_CONSTANT)
arg.typeQualifier |= CLC_KERNEL_ARG_TYPE_CONST;
}
}
}
}
void parseOpString(const spv_parsed_instruction_t *ins)
{
const spv_parsed_operand_t *op;
std::string str;
assert(ins->num_operands == 2);
op = &ins->operands[1];
assert(op->type == SPV_OPERAND_TYPE_LITERAL_STRING);
str = reinterpret_cast<const char *>(ins->words + op->offset);
size_t start = 0;
enum class string_type {
arg_type,
arg_type_qual,
} str_type;
if (str.find("kernel_arg_type.") == 0) {
start = sizeof("kernel_arg_type.") - 1;
str_type = string_type::arg_type;
} else if (str.find("kernel_arg_type_qual.") == 0) {
start = sizeof("kernel_arg_type_qual.") - 1;
str_type = string_type::arg_type_qual;
} else {
return;
}
for (auto &kernel : kernels) {
size_t pos;
pos = str.find(kernel.name, start);
if (pos == std::string::npos ||
pos != start || str[start + kernel.name.size()] != '.')
continue;
pos = start + kernel.name.size();
if (str[pos++] != '.')
continue;
for (auto &arg : kernel.args) {
if (arg.name.empty())
break;
size_t entryEnd = str.find(',', pos);
if (entryEnd == std::string::npos)
break;
std::string entryVal = str.substr(pos, entryEnd - pos);
pos = entryEnd + 1;
if (str_type == string_type::arg_type) {
arg.typeName = std::move(entryVal);
} else if (str_type == string_type::arg_type_qual) {
if (entryVal.find("const") != std::string::npos)
arg.typeQualifier |= CLC_KERNEL_ARG_TYPE_CONST;
}
}
}
}
void applyDecoration(uint32_t id, const spv_parsed_instruction_t *ins)
{
auto iter = decorationGroups.find(id);
if (iter != decorationGroups.end()) {
for (uint32_t entry : iter->second)
applyDecoration(entry, ins);
return;
}
const spv_parsed_operand_t *op;
uint32_t decoration;
assert(ins->num_operands >= 2);
op = &ins->operands[1];
assert(op->type == SPV_OPERAND_TYPE_DECORATION);
decoration = ins->words[op->offset];
if (decoration == SpvDecorationSpecId) {
uint32_t spec_id = ins->words[ins->operands[2].offset];
for (auto &c : specConstants) {
if (c.second.id == spec_id) {
return;
}
}
specConstants.emplace_back(id, clc_parsed_spec_constant{ spec_id });
return;
}
for (auto &kernel : kernels) {
for (auto &arg : kernel.args) {
if (arg.id == id) {
switch (decoration) {
case SpvDecorationVolatile:
arg.typeQualifier |= CLC_KERNEL_ARG_TYPE_VOLATILE;
break;
case SpvDecorationConstant:
arg.typeQualifier |= CLC_KERNEL_ARG_TYPE_CONST;
break;
case SpvDecorationRestrict:
arg.typeQualifier |= CLC_KERNEL_ARG_TYPE_RESTRICT;
break;
case SpvDecorationFuncParamAttr:
op = &ins->operands[2];
assert(op->type == SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE);
switch (ins->words[op->offset]) {
case SpvFunctionParameterAttributeNoAlias:
arg.typeQualifier |= CLC_KERNEL_ARG_TYPE_RESTRICT;
break;
case SpvFunctionParameterAttributeNoWrite:
arg.typeQualifier |= CLC_KERNEL_ARG_TYPE_CONST;
break;
}
break;
}
}
}
}
}
void parseOpDecorate(const spv_parsed_instruction_t *ins)
{
const spv_parsed_operand_t *op;
uint32_t id;
assert(ins->num_operands >= 2);
op = &ins->operands[0];
assert(op->type == SPV_OPERAND_TYPE_ID);
id = ins->words[op->offset];
applyDecoration(id, ins);
}
void parseOpGroupDecorate(const spv_parsed_instruction_t *ins)
{
assert(ins->num_operands >= 2);
const spv_parsed_operand_t *op = &ins->operands[0];
assert(op->type == SPV_OPERAND_TYPE_ID);
uint32_t groupId = ins->words[op->offset];
auto lowerBound = decorationGroups.lower_bound(groupId);
if (lowerBound != decorationGroups.end() &&
lowerBound->first == groupId)
// Group already filled out
return;
auto iter = decorationGroups.emplace_hint(lowerBound, groupId, std::vector<uint32_t>{});
auto& vec = iter->second;
vec.reserve(ins->num_operands - 1);
for (uint32_t i = 1; i < ins->num_operands; ++i) {
op = &ins->operands[i];
assert(op->type == SPV_OPERAND_TYPE_ID);
vec.push_back(ins->words[op->offset]);
}
}
void parseOpTypeImage(const spv_parsed_instruction_t *ins)
{
const spv_parsed_operand_t *op;
uint32_t typeId;
unsigned accessQualifier = CLC_KERNEL_ARG_ACCESS_READ;
op = &ins->operands[0];
assert(op->type == SPV_OPERAND_TYPE_RESULT_ID);
typeId = ins->words[op->offset];
if (ins->num_operands >= 9) {
op = &ins->operands[8];
assert(op->type == SPV_OPERAND_TYPE_ACCESS_QUALIFIER);
switch (ins->words[op->offset]) {
case SpvAccessQualifierReadOnly:
accessQualifier = CLC_KERNEL_ARG_ACCESS_READ;
break;
case SpvAccessQualifierWriteOnly:
accessQualifier = CLC_KERNEL_ARG_ACCESS_WRITE;
break;
case SpvAccessQualifierReadWrite:
accessQualifier = CLC_KERNEL_ARG_ACCESS_WRITE |
CLC_KERNEL_ARG_ACCESS_READ;
break;
}
}
for (auto &kernel : kernels) {
for (auto &arg : kernel.args) {
if (arg.typeId == typeId) {
arg.accessQualifier = accessQualifier;
arg.addrQualifier = CLC_KERNEL_ARG_ADDRESS_GLOBAL;
}
}
}
}
void parseExecutionMode(const spv_parsed_instruction_t *ins)
{
uint32_t executionMode = ins->words[ins->operands[1].offset];
uint32_t funcId = ins->words[ins->operands[0].offset];
for (auto& kernel : kernels) {
if (kernel.funcId == funcId) {
switch (executionMode) {
case SpvExecutionModeVecTypeHint:
kernel.vecHint = ins->words[ins->operands[2].offset];
break;
case SpvExecutionModeLocalSize:
kernel.localSize[0] = ins->words[ins->operands[2].offset];
kernel.localSize[1] = ins->words[ins->operands[3].offset];
kernel.localSize[2] = ins->words[ins->operands[4].offset];
break;
case SpvExecutionModeLocalSizeHint:
kernel.localSizeHint[0] = ins->words[ins->operands[2].offset];
kernel.localSizeHint[1] = ins->words[ins->operands[3].offset];
kernel.localSizeHint[2] = ins->words[ins->operands[4].offset];
break;
default:
return;
}
}
}
}
void parseLiteralType(const spv_parsed_instruction_t *ins)
{
uint32_t typeId = ins->words[ins->operands[0].offset];
auto& literalType = literalTypes[typeId];
switch (ins->opcode) {
case SpvOpTypeBool:
literalType = CLC_SPEC_CONSTANT_BOOL;
break;
case SpvOpTypeFloat: {
uint32_t sizeInBits = ins->words[ins->operands[1].offset];
switch (sizeInBits) {
case 32:
literalType = CLC_SPEC_CONSTANT_FLOAT;
break;
case 64:
literalType = CLC_SPEC_CONSTANT_DOUBLE;
break;
case 16:
literalType = CLC_SPEC_CONSTANT_HALF;
break;
default:
build: avoid redefining unreachable() which is standard in C23 In the C23 standard unreachable() is now a predefined function-like macro in <stddef.h> See https://android.googlesource.com/platform/bionic/+/HEAD/docs/c23.md#is-now-a-predefined-function_like-macro-in And this causes build errors when building for C23: ----------------------------------------------------------------------- In file included from ../src/util/log.h:30, from ../src/util/log.c:30: ../src/util/macros.h:123:9: warning: "unreachable" redefined 123 | #define unreachable(str) \ | ^~~~~~~~~~~ In file included from ../src/util/macros.h:31: /usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h:456:9: note: this is the location of the previous definition 456 | #define unreachable() (__builtin_unreachable ()) | ^~~~~~~~~~~ ----------------------------------------------------------------------- So don't redefine it with the same name, but use the name UNREACHABLE() to also signify it's a macro. Using a different name also makes sense because the behavior of the macro was extending the one of __builtin_unreachable() anyway, and it also had a different signature, accepting one argument, compared to the standard unreachable() with no arguments. This change improves the chances of building mesa with the C23 standard, which for instance is the default in recent AOSP versions. All the instances of the macro, including the definition, were updated with the following command line: git grep -l '[^_]unreachable(' -- "src/**" | sort | uniq | \ while read file; \ do \ sed -e 's/\([^_]\)unreachable(/\1UNREACHABLE(/g' -i "$file"; \ done && \ sed -e 's/#undef unreachable/#undef UNREACHABLE/g' -i src/intel/isl/isl_aux_info.c Reviewed-by: Erik Faye-Lund <erik.faye-lund@collabora.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36437>
2025-07-23 09:17:35 +02:00
UNREACHABLE("Unexpected float bit size");
}
break;
}
case SpvOpTypeInt: {
uint32_t sizeInBits = ins->words[ins->operands[1].offset];
bool isSigned = ins->words[ins->operands[2].offset];
if (isSigned) {
switch (sizeInBits) {
case 8:
literalType = CLC_SPEC_CONSTANT_INT8;
break;
case 16:
literalType = CLC_SPEC_CONSTANT_INT16;
break;
case 32:
literalType = CLC_SPEC_CONSTANT_INT32;
break;
case 64:
literalType = CLC_SPEC_CONSTANT_INT64;
break;
default:
build: avoid redefining unreachable() which is standard in C23 In the C23 standard unreachable() is now a predefined function-like macro in <stddef.h> See https://android.googlesource.com/platform/bionic/+/HEAD/docs/c23.md#is-now-a-predefined-function_like-macro-in And this causes build errors when building for C23: ----------------------------------------------------------------------- In file included from ../src/util/log.h:30, from ../src/util/log.c:30: ../src/util/macros.h:123:9: warning: "unreachable" redefined 123 | #define unreachable(str) \ | ^~~~~~~~~~~ In file included from ../src/util/macros.h:31: /usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h:456:9: note: this is the location of the previous definition 456 | #define unreachable() (__builtin_unreachable ()) | ^~~~~~~~~~~ ----------------------------------------------------------------------- So don't redefine it with the same name, but use the name UNREACHABLE() to also signify it's a macro. Using a different name also makes sense because the behavior of the macro was extending the one of __builtin_unreachable() anyway, and it also had a different signature, accepting one argument, compared to the standard unreachable() with no arguments. This change improves the chances of building mesa with the C23 standard, which for instance is the default in recent AOSP versions. All the instances of the macro, including the definition, were updated with the following command line: git grep -l '[^_]unreachable(' -- "src/**" | sort | uniq | \ while read file; \ do \ sed -e 's/\([^_]\)unreachable(/\1UNREACHABLE(/g' -i "$file"; \ done && \ sed -e 's/#undef unreachable/#undef UNREACHABLE/g' -i src/intel/isl/isl_aux_info.c Reviewed-by: Erik Faye-Lund <erik.faye-lund@collabora.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36437>
2025-07-23 09:17:35 +02:00
UNREACHABLE("Unexpected int bit size");
}
} else {
switch (sizeInBits) {
case 8:
literalType = CLC_SPEC_CONSTANT_UINT8;
break;
case 16:
literalType = CLC_SPEC_CONSTANT_UINT16;
break;
case 32:
literalType = CLC_SPEC_CONSTANT_UINT32;
break;
case 64:
literalType = CLC_SPEC_CONSTANT_UINT64;
break;
default:
build: avoid redefining unreachable() which is standard in C23 In the C23 standard unreachable() is now a predefined function-like macro in <stddef.h> See https://android.googlesource.com/platform/bionic/+/HEAD/docs/c23.md#is-now-a-predefined-function_like-macro-in And this causes build errors when building for C23: ----------------------------------------------------------------------- In file included from ../src/util/log.h:30, from ../src/util/log.c:30: ../src/util/macros.h:123:9: warning: "unreachable" redefined 123 | #define unreachable(str) \ | ^~~~~~~~~~~ In file included from ../src/util/macros.h:31: /usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h:456:9: note: this is the location of the previous definition 456 | #define unreachable() (__builtin_unreachable ()) | ^~~~~~~~~~~ ----------------------------------------------------------------------- So don't redefine it with the same name, but use the name UNREACHABLE() to also signify it's a macro. Using a different name also makes sense because the behavior of the macro was extending the one of __builtin_unreachable() anyway, and it also had a different signature, accepting one argument, compared to the standard unreachable() with no arguments. This change improves the chances of building mesa with the C23 standard, which for instance is the default in recent AOSP versions. All the instances of the macro, including the definition, were updated with the following command line: git grep -l '[^_]unreachable(' -- "src/**" | sort | uniq | \ while read file; \ do \ sed -e 's/\([^_]\)unreachable(/\1UNREACHABLE(/g' -i "$file"; \ done && \ sed -e 's/#undef unreachable/#undef UNREACHABLE/g' -i src/intel/isl/isl_aux_info.c Reviewed-by: Erik Faye-Lund <erik.faye-lund@collabora.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36437>
2025-07-23 09:17:35 +02:00
UNREACHABLE("Unexpected uint bit size");
}
}
break;
}
default:
build: avoid redefining unreachable() which is standard in C23 In the C23 standard unreachable() is now a predefined function-like macro in <stddef.h> See https://android.googlesource.com/platform/bionic/+/HEAD/docs/c23.md#is-now-a-predefined-function_like-macro-in And this causes build errors when building for C23: ----------------------------------------------------------------------- In file included from ../src/util/log.h:30, from ../src/util/log.c:30: ../src/util/macros.h:123:9: warning: "unreachable" redefined 123 | #define unreachable(str) \ | ^~~~~~~~~~~ In file included from ../src/util/macros.h:31: /usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h:456:9: note: this is the location of the previous definition 456 | #define unreachable() (__builtin_unreachable ()) | ^~~~~~~~~~~ ----------------------------------------------------------------------- So don't redefine it with the same name, but use the name UNREACHABLE() to also signify it's a macro. Using a different name also makes sense because the behavior of the macro was extending the one of __builtin_unreachable() anyway, and it also had a different signature, accepting one argument, compared to the standard unreachable() with no arguments. This change improves the chances of building mesa with the C23 standard, which for instance is the default in recent AOSP versions. All the instances of the macro, including the definition, were updated with the following command line: git grep -l '[^_]unreachable(' -- "src/**" | sort | uniq | \ while read file; \ do \ sed -e 's/\([^_]\)unreachable(/\1UNREACHABLE(/g' -i "$file"; \ done && \ sed -e 's/#undef unreachable/#undef UNREACHABLE/g' -i src/intel/isl/isl_aux_info.c Reviewed-by: Erik Faye-Lund <erik.faye-lund@collabora.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36437>
2025-07-23 09:17:35 +02:00
UNREACHABLE("Unexpected type opcode");
}
}
void parseSpecConstant(const spv_parsed_instruction_t *ins)
{
uint32_t id = ins->result_id;
for (auto& c : specConstants) {
if (c.first == id) {
auto& data = c.second;
switch (ins->opcode) {
case SpvOpSpecConstant: {
uint32_t typeId = ins->words[ins->operands[0].offset];
// This better be an integer or float type
auto typeIter = literalTypes.find(typeId);
assert(typeIter != literalTypes.end());
data.type = typeIter->second;
break;
}
case SpvOpSpecConstantFalse:
case SpvOpSpecConstantTrue:
data.type = CLC_SPEC_CONSTANT_BOOL;
break;
default:
build: avoid redefining unreachable() which is standard in C23 In the C23 standard unreachable() is now a predefined function-like macro in <stddef.h> See https://android.googlesource.com/platform/bionic/+/HEAD/docs/c23.md#is-now-a-predefined-function_like-macro-in And this causes build errors when building for C23: ----------------------------------------------------------------------- In file included from ../src/util/log.h:30, from ../src/util/log.c:30: ../src/util/macros.h:123:9: warning: "unreachable" redefined 123 | #define unreachable(str) \ | ^~~~~~~~~~~ In file included from ../src/util/macros.h:31: /usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h:456:9: note: this is the location of the previous definition 456 | #define unreachable() (__builtin_unreachable ()) | ^~~~~~~~~~~ ----------------------------------------------------------------------- So don't redefine it with the same name, but use the name UNREACHABLE() to also signify it's a macro. Using a different name also makes sense because the behavior of the macro was extending the one of __builtin_unreachable() anyway, and it also had a different signature, accepting one argument, compared to the standard unreachable() with no arguments. This change improves the chances of building mesa with the C23 standard, which for instance is the default in recent AOSP versions. All the instances of the macro, including the definition, were updated with the following command line: git grep -l '[^_]unreachable(' -- "src/**" | sort | uniq | \ while read file; \ do \ sed -e 's/\([^_]\)unreachable(/\1UNREACHABLE(/g' -i "$file"; \ done && \ sed -e 's/#undef unreachable/#undef UNREACHABLE/g' -i src/intel/isl/isl_aux_info.c Reviewed-by: Erik Faye-Lund <erik.faye-lund@collabora.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36437>
2025-07-23 09:17:35 +02:00
UNREACHABLE("Composites and Ops are not directly specializable.");
}
}
}
}
static spv_result_t
parseInstruction(void *data, const spv_parsed_instruction_t *ins)
{
SPIRVKernelParser *parser = reinterpret_cast<SPIRVKernelParser *>(data);
switch (ins->opcode) {
case SpvOpName:
parser->parseName(ins);
break;
case SpvOpEntryPoint:
parser->parseEntryPoint(ins);
break;
case SpvOpFunction:
parser->parseFunction(ins);
break;
case SpvOpFunctionParameter:
parser->parseFunctionParam(ins);
break;
case SpvOpFunctionEnd:
case SpvOpLabel:
parser->curKernel = NULL;
break;
case SpvOpTypePointer:
parser->parseTypePointer(ins);
break;
case SpvOpTypeImage:
parser->parseOpTypeImage(ins);
break;
case SpvOpString:
parser->parseOpString(ins);
break;
case SpvOpDecorate:
parser->parseOpDecorate(ins);
break;
case SpvOpGroupDecorate:
parser->parseOpGroupDecorate(ins);
break;
case SpvOpExecutionMode:
parser->parseExecutionMode(ins);
break;
case SpvOpTypeBool:
case SpvOpTypeInt:
case SpvOpTypeFloat:
parser->parseLiteralType(ins);
break;
case SpvOpSpecConstant:
case SpvOpSpecConstantFalse:
case SpvOpSpecConstantTrue:
parser->parseSpecConstant(ins);
break;
default:
break;
}
return SPV_SUCCESS;
}
bool parseBinary(const struct clc_binary &spvbin, const struct clc_logger *logger)
{
/* 3 passes should be enough to retrieve all kernel information:
* 1st pass: all entry point name and number of args
* 2nd pass: argument names and type names
* 3rd pass: pointer type names
*/
for (unsigned pass = 0; pass < 3; pass++) {
spv_diagnostic diagnostic = NULL;
auto result = spvBinaryParse(ctx, reinterpret_cast<void *>(this),
static_cast<uint32_t*>(spvbin.data), spvbin.size / 4,
NULL, parseInstruction, &diagnostic);
if (result != SPV_SUCCESS) {
if (diagnostic && logger)
logger->error(logger->priv, diagnostic->error);
return false;
}
}
return true;
}
std::vector<SPIRVKernelInfo> kernels;
std::vector<std::pair<uint32_t, clc_parsed_spec_constant>> specConstants;
std::map<uint32_t, enum clc_spec_constant_type> literalTypes;
std::map<uint32_t, std::vector<uint32_t>> decorationGroups;
SPIRVKernelInfo *curKernel;
spv_context ctx;
};
bool
clc_spirv_get_kernels_info(const struct clc_binary *spvbin,
const struct clc_kernel_info **out_kernels,
unsigned *num_kernels,
const struct clc_parsed_spec_constant **out_spec_constants,
unsigned *num_spec_constants,
const struct clc_logger *logger)
{
struct clc_kernel_info *kernels = NULL;
struct clc_parsed_spec_constant *spec_constants = NULL;
SPIRVKernelParser parser;
if (!parser.parseBinary(*spvbin, logger))
return false;
*num_kernels = parser.kernels.size();
*num_spec_constants = parser.specConstants.size();
if (*num_kernels) {
kernels = reinterpret_cast<struct clc_kernel_info *>(calloc(*num_kernels,
sizeof(*kernels)));
assert(kernels);
for (unsigned i = 0; i < parser.kernels.size(); i++) {
kernels[i].name = strdup(parser.kernels[i].name.c_str());
kernels[i].num_args = parser.kernels[i].args.size();
kernels[i].vec_hint_size = parser.kernels[i].vecHint >> 16;
kernels[i].vec_hint_type = (enum clc_vec_hint_type)(parser.kernels[i].vecHint & 0xFFFF);
memcpy(kernels[i].local_size, parser.kernels[i].localSize, sizeof(kernels[i].local_size));
memcpy(kernels[i].local_size_hint, parser.kernels[i].localSizeHint, sizeof(kernels[i].local_size_hint));
if (!kernels[i].num_args)
continue;
struct clc_kernel_arg *args;
args = reinterpret_cast<struct clc_kernel_arg *>(calloc(kernels[i].num_args,
sizeof(*kernels->args)));
kernels[i].args = args;
assert(args);
for (unsigned j = 0; j < kernels[i].num_args; j++) {
if (!parser.kernels[i].args[j].name.empty())
args[j].name = strdup(parser.kernels[i].args[j].name.c_str());
args[j].type_name = strdup(parser.kernels[i].args[j].typeName.c_str());
args[j].address_qualifier = parser.kernels[i].args[j].addrQualifier;
args[j].type_qualifier = parser.kernels[i].args[j].typeQualifier;
args[j].access_qualifier = parser.kernels[i].args[j].accessQualifier;
}
}
}
if (*num_spec_constants) {
spec_constants = reinterpret_cast<struct clc_parsed_spec_constant *>(calloc(*num_spec_constants,
sizeof(*spec_constants)));
assert(spec_constants);
for (unsigned i = 0; i < parser.specConstants.size(); ++i) {
spec_constants[i] = parser.specConstants[i].second;
}
}
*out_kernels = kernels;
*out_spec_constants = spec_constants;
return true;
}
void
clc_free_kernels_info(const struct clc_kernel_info *kernels,
unsigned num_kernels)
{
if (!kernels)
return;
for (unsigned i = 0; i < num_kernels; i++) {
if (kernels[i].args) {
for (unsigned j = 0; j < kernels[i].num_args; j++) {
free((void *)kernels[i].args[j].name);
free((void *)kernels[i].args[j].type_name);
}
free((void *)kernels[i].args);
}
free((void *)kernels[i].name);
}
free((void *)kernels);
}
static std::unique_ptr<::llvm::Module>
clc_compile_to_llvm_module(LLVMContext &llvm_ctx,
const struct clc_compile_args *args,
const struct clc_logger *logger,
struct set *dependencies)
{
static_assert(std::has_unique_object_representations<clc_optional_features>(),
"no padding allowed inside clc_optional_features");
std::string diag_log_str;
raw_string_ostream diag_log_stream { diag_log_str };
std::unique_ptr<clang::CompilerInstance> c { new clang::CompilerInstance };
std::shared_ptr<clang::DependencyCollector> dep;
if (dependencies != nullptr) {
dep = std::make_shared<clang::DependencyCollector>();
c->addDependencyCollector(dep);
}
#if LLVM_VERSION_MAJOR >= 21
auto diag_opts = c->getDiagnosticOpts();
#else
auto diag_opts = &c->getDiagnosticOpts();
#endif
clang::DiagnosticsEngine diag {
new clang::DiagnosticIDs,
diag_opts,
new clang::TextDiagnosticPrinter(diag_log_stream,
diag_opts)
};
#if LLVM_VERSION_MAJOR >= 17
const char *triple = args->address_bits == 32 ? "spir-unknown-unknown" : "spirv64-unknown-unknown";
#else
const char *triple = args->address_bits == 32 ? "spir-unknown-unknown" : "spir64-unknown-unknown";
#endif
std::vector<const char *> clang_opts = {
args->source.name,
"-triple", triple,
// By default, clang prefers to use modules to pull in the default headers,
// which doesn't work with our technique of embedding the headers in our binary
"-fdeclare-opencl-builtins",
#if LLVM_VERSION_MAJOR < 17
"-no-opaque-pointers",
#endif
// Add a default CL compiler version. Clang will pick the last one specified
// on the command line, so the app can override this one.
"-cl-std=cl1.2",
// The LLVM-SPIRV-Translator doesn't support memset with variable size
"-fno-builtin-memset",
// LLVM's optimizations can produce code that the translator can't translate
"-O0",
// Ensure inline functions are actually emitted
"-fgnu89-inline",
};
// We assume there's appropriate defines for __OPENCL_VERSION__ and __IMAGE_SUPPORT__
// being provided by the caller here.
clang_opts.insert(clang_opts.end(), args->args, args->args + args->num_args);
if (!clang::CompilerInvocation::CreateFromArgs(c->getInvocation(),
clang_opts,
diag)) {
clc_error(logger, "Couldn't create Clang invocation.\n%s\n", diag_log_str.c_str());
return {};
}
if (diag.hasErrorOccurred()) {
clc_error(logger, "%sErrors occurred during Clang invocation.\n",
diag_log_str.c_str());
return {};
}
// This is a workaround for a Clang bug which causes the number
// of warnings and errors to be printed to stderr.
// http://www.llvm.org/bugs/show_bug.cgi?id=19735
c->getDiagnosticOpts().ShowCarets = false;
c->createDiagnostics(
#if LLVM_VERSION_MAJOR >= 20 && LLVM_VERSION_MAJOR < 22
*llvm::vfs::getRealFileSystem(),
#endif
new clang::TextDiagnosticPrinter(
diag_log_stream,
diag_opts));
c->setTarget(clang::TargetInfo::CreateTargetInfo(
#if LLVM_VERSION_MAJOR >= 21
c->getDiagnostics(), c->getInvocation().getTargetOpts()));
#else
c->getDiagnostics(), c->getInvocation().TargetOpts));
#endif
c->getFrontendOpts().ProgramAction = clang::frontend::EmitLLVMOnly;
#ifdef USE_STATIC_OPENCL_C_H
c->getHeaderSearchOpts().UseBuiltinIncludes = false;
c->getHeaderSearchOpts().UseStandardSystemIncludes = false;
// Add opencl-c generic search path
{
::llvm::SmallString<128> system_header_path;
::llvm::sys::path::system_temp_directory(true, system_header_path);
::llvm::sys::path::append(system_header_path, "openclon12");
c->getHeaderSearchOpts().AddPath(system_header_path.str(),
clang::frontend::Angled,
false, false);
::llvm::sys::path::append(system_header_path, "opencl-c-base.h");
c->getPreprocessorOpts().addRemappedFile(system_header_path.str(),
::llvm::MemoryBuffer::getMemBuffer(llvm::StringRef(opencl_c_base_source, ARRAY_SIZE(opencl_c_base_source) - 1)).release());
// this line is actually important to make it include `opencl-c.h`
::llvm::sys::path::remove_filename(system_header_path);
::llvm::sys::path::append(system_header_path, "opencl-c.h");
c->getPreprocessorOpts().addRemappedFile(system_header_path.str(),
::llvm::MemoryBuffer::getMemBuffer(llvm::StringRef(opencl_c_source, ARRAY_SIZE(opencl_c_source) - 1)).release());
}
#else
char *clang_path = util_dl_get_path_from_proc((const void *)clang::CompilerInvocation::CreateFromArgs);
if (clang_path == nullptr) {
clc_error(logger, "Couldn't find libclang path.\n");
return {};
}
// GetResourcePath is a way to retrieve the actual libclang resource dir based on a given binary
// or library.
auto tmp_res_path =
#if LLVM_VERSION_MAJOR >= 20
Driver::GetResourcesPath(std::string(clang_path));
#else
Driver::GetResourcesPath(std::string(clang_path), CLANG_RESOURCE_DIR);
#endif
auto clang_res_path = fs::path(tmp_res_path) / "include";
free(clang_path);
c->getHeaderSearchOpts().UseBuiltinIncludes = true;
c->getHeaderSearchOpts().UseStandardSystemIncludes = true;
c->getHeaderSearchOpts().ResourceDir = clang_res_path.string();
// Add opencl-c generic search path
c->getHeaderSearchOpts().AddPath(clang_res_path.string(),
clang::frontend::Angled,
false, false);
auto clang_install_res_path =
fs::path(LLVM_LIB_DIR) / "clang" / std::to_string(LLVM_VERSION_MAJOR) / "include";
c->getHeaderSearchOpts().AddPath(clang_install_res_path.string(),
clang::frontend::Angled,
false, false);
#endif
// Enable/Disable optional OpenCL C features. Some can be toggled via `OpenCLExtensionsAsWritten`
// others we have to (un)define via macros ourselves.
// Undefine clang added SPIR(V) defines so we don't magically enable extensions
c->getPreprocessorOpts().addMacroUndef("__SPIR__");
c->getPreprocessorOpts().addMacroUndef("__SPIRV__");
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("-all");
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_khr_byte_addressable_store");
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_khr_global_int32_base_atomics");
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_khr_global_int32_extended_atomics");
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_khr_local_int32_base_atomics");
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_khr_local_int32_extended_atomics");
if (args->c_compatible) {
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+__cl_clang_bitfields");
}
c->getPreprocessorOpts().addMacroDef("cl_khr_expect_assume=1");
bool needs_opencl_c_h = false;
if (args->features.extended_bit_ops) {
c->getPreprocessorOpts().addMacroDef("cl_khr_extended_bit_ops=1");
}
if (args->features.fp16) {
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_khr_fp16");
}
if (args->features.fp64) {
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_khr_fp64");
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+__opencl_c_fp64");
}
if (args->features.int64) {
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cles_khr_int64");
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+__opencl_c_int64");
} else {
// clang defines this unconditionally, we need to fix that.
c->getPreprocessorOpts().addMacroUndef("__opencl_c_int64");
}
if (args->features.images) {
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+__opencl_c_images");
} else {
// clang defines this unconditionally, we need to fix that.
c->getPreprocessorOpts().addMacroUndef("__IMAGE_SUPPORT__");
}
if (args->features.images_read_write) {
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+__opencl_c_read_write_images");
}
if (args->features.images_write_3d) {
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_khr_3d_image_writes");
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+__opencl_c_3d_image_writes");
}
if (args->features.images_depth) {
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_khr_depth_images");
}
if (args->features.images_gl_depth) {
c->getPreprocessorOpts().addMacroDef("cl_khr_gl_depth_images=1");
}
if (args->features.images_mipmap) {
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_khr_mipmap_image");
}
if (args->features.images_mipmap_writes) {
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_khr_mipmap_image_writes");
}
if (args->features.images_gl_msaa) {
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_khr_gl_msaa_sharing");
}
if (args->features.images_unorm_int_2_101010) {
c->getPreprocessorOpts().addMacroDef("__opencl_c_ext_image_unorm_int_2_101010=1");
if (LLVM_VERSION_MAJOR < 20 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR < 1)) {
/* This feature doesn't really need any compiler support, but it does define a CLK_
* macro for the type, which is only available with llvm-20.1 or newer.
*/
c->getPreprocessorOpts().addMacroDef("CLK_UNORM_INT_2_101010_EXT=0x10E5");
}
}
if (args->features.intel_subgroups) {
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_intel_subgroups");
needs_opencl_c_h = true;
}
if (args->features.kernel_clock && LLVM_VERSION_MAJOR >= 19) {
c->getPreprocessorOpts().addMacroDef("cl_khr_kernel_clock=1");
c->getPreprocessorOpts().addMacroDef("__opencl_c_kernel_clock_scope_device=1");
c->getPreprocessorOpts().addMacroDef("__opencl_c_kernel_clock_scope_sub_group=1");
}
if (args->features.subgroups) {
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+__opencl_c_subgroups");
if (args->features.subgroups_shuffle) {
c->getPreprocessorOpts().addMacroDef("cl_khr_subgroup_shuffle=1");
}
if (args->features.subgroups_shuffle_relative) {
c->getPreprocessorOpts().addMacroDef("cl_khr_subgroup_shuffle_relative=1");
}
if (args->features.subgroups_ballot) {
c->getPreprocessorOpts().addMacroDef("cl_khr_subgroup_ballot=1");
}
}
if (args->features.subgroups_ifp) {
assert(args->features.subgroups);
c->getTargetOpts().OpenCLExtensionsAsWritten.push_back("+cl_khr_subgroups");
}
if (args->features.integer_dot_product) {
c->getPreprocessorOpts().addMacroDef("cl_khr_integer_dot_product=1");
c->getPreprocessorOpts().addMacroDef("__opencl_c_integer_dot_product_input_4x8bit_packed=1");
c->getPreprocessorOpts().addMacroDef("__opencl_c_integer_dot_product_input_4x8bit=1");
}
// Add opencl include
c->getPreprocessorOpts().Includes.push_back("opencl-c-base.h");
if (needs_opencl_c_h) {
c->getPreprocessorOpts().Includes.push_back("opencl-c.h");
}
if (args->num_headers) {
::llvm::SmallString<128> tmp_header_path;
::llvm::sys::path::system_temp_directory(true, tmp_header_path);
::llvm::sys::path::append(tmp_header_path, "openclon12");
c->getHeaderSearchOpts().AddPath(tmp_header_path.str(),
clang::frontend::Quoted,
false, false);
for (size_t i = 0; i < args->num_headers; i++) {
auto path_copy = tmp_header_path;
::llvm::sys::path::append(path_copy, ::llvm::sys::path::convert_to_slash(args->headers[i].name));
c->getPreprocessorOpts().addRemappedFile(path_copy.str(),
::llvm::MemoryBuffer::getMemBufferCopy(args->headers[i].value).release());
}
}
c->getPreprocessorOpts().addRemappedFile(
args->source.name,
::llvm::MemoryBuffer::getMemBufferCopy(std::string(args->source.value)).release());
// Compile the code
clang::EmitLLVMOnlyAction act(&llvm_ctx);
if (!c->ExecuteAction(act)) {
clc_error(logger, "%sError executing LLVM compilation action.\n",
diag_log_str.c_str());
return {};
}
if (dependencies != nullptr) {
for (auto dep : dep->getDependencies()) {
_mesa_set_add(dependencies, ralloc_strdup(dependencies, dep.c_str()));
}
}
auto mod = act.takeModule();
if (clc_debug_flags() & CLC_DEBUG_DUMP_LLVM)
clc_dump_llvm(mod.get(), stdout);
return mod;
}
static SPIRV::VersionNumber
spirv_version_to_llvm_spirv_translator_version(enum clc_spirv_version version)
{
switch (version) {
case CLC_SPIRV_VERSION_MAX: return SPIRV::VersionNumber::MaximumVersion;
case CLC_SPIRV_VERSION_1_0: return SPIRV::VersionNumber::SPIRV_1_0;
case CLC_SPIRV_VERSION_1_1: return SPIRV::VersionNumber::SPIRV_1_1;
case CLC_SPIRV_VERSION_1_2: return SPIRV::VersionNumber::SPIRV_1_2;
case CLC_SPIRV_VERSION_1_3: return SPIRV::VersionNumber::SPIRV_1_3;
case CLC_SPIRV_VERSION_1_4: return SPIRV::VersionNumber::SPIRV_1_4;
default: return invalid_spirv_trans_version;
}
}
static int
llvm_mod_to_spirv(std::unique_ptr<::llvm::Module> mod,
LLVMContext &context,
const struct clc_compile_args *args,
const struct clc_logger *logger,
struct clc_binary *out_spirv)
{
std::string log;
SPIRV::VersionNumber version =
spirv_version_to_llvm_spirv_translator_version(args->spirv_version);
if (version == invalid_spirv_trans_version) {
clc_error(logger, "Invalid/unsupported SPIRV specified.\n");
return -1;
}
const char *const *extensions = args->allowed_spirv_extensions;
if (!extensions) {
/* The SPIR-V parser doesn't handle all extensions */
static const char *default_extensions[] = {
"SPV_EXT_shader_atomic_float_add",
"SPV_EXT_shader_atomic_float_min_max",
"SPV_KHR_float_controls",
NULL,
};
extensions = default_extensions;
}
SPIRV::TranslatorOpts::ExtensionsStatusMap ext_map;
for (int i = 0; extensions[i]; i++) {
#define EXT(X) \
if (strcmp(#X, extensions[i]) == 0) \
ext_map.insert(std::make_pair(SPIRV::ExtensionID::X, true));
#include "LLVMSPIRVLib/LLVMSPIRVExtensions.inc"
#undef EXT
}
SPIRV::TranslatorOpts spirv_opts = SPIRV::TranslatorOpts(version, ext_map);
/* This was the default in 12.0 and older, but currently we'll fail to parse without this */
spirv_opts.setPreserveOCLKernelArgTypeMetadataThroughString(true);
#if LLVM_VERSION_MAJOR >= 17
if (args->use_llvm_spirv_target) {
const char *triple = args->address_bits == 32 ? "spirv-unknown-unknown" : "spirv64-unknown-unknown";
std::string error_msg("");
auto target = TargetRegistry::lookupTarget(triple, error_msg);
if (target) {
auto TM = target->createTargetMachine(
#if LLVM_VERSION_MAJOR >= 21
llvm::Triple(triple),
#else
triple,
#endif
"", "", {}, std::nullopt, std::nullopt,
#if LLVM_VERSION_MAJOR >= 18
::llvm::CodeGenOptLevel::None
#else
::llvm::CodeGenOpt::None
#endif
);
auto PM = PassManager();
::llvm::SmallVector<char> buf;
auto OS = ::llvm::raw_svector_ostream(buf);
TM->addPassesToEmitFile(
PM, OS, nullptr,
#if LLVM_VERSION_MAJOR >= 18
::llvm::CodeGenFileType::ObjectFile
#else
::llvm::CGFT_ObjectFile
#endif
);
PM.run(*mod);
out_spirv->size = buf.size_in_bytes();
out_spirv->data = malloc(out_spirv->size);
memcpy(out_spirv->data, buf.data(), out_spirv->size);
return 0;
} else {
clc_error(logger, "LLVM SPIR-V target not found.\n");
return -1;
}
}
#endif
std::ostringstream spv_stream;
if (!::llvm::writeSpirv(mod.get(), spirv_opts, spv_stream, log)) {
clc_error(logger, "%sTranslation from LLVM IR to SPIR-V failed.\n",
log.c_str());
return -1;
}
const std::string spv_out = spv_stream.str();
out_spirv->size = spv_out.size();
out_spirv->data = malloc(out_spirv->size);
memcpy(out_spirv->data, spv_out.data(), out_spirv->size);
return 0;
}
int
clc_c_to_spir(const struct clc_compile_args *args,
const struct clc_logger *logger,
struct clc_binary *out_spir,
struct set *dependencies)
{
clc_initialize_llvm();
LLVMContext llvm_ctx;
llvm_ctx.setDiagnosticHandlerCallBack(llvm_log_handler,
const_cast<clc_logger *>(logger));
auto mod = clc_compile_to_llvm_module(llvm_ctx, args, logger, dependencies);
if (!mod)
return -1;
::llvm::SmallVector<char, 0> buffer;
::llvm::BitcodeWriter writer(buffer);
writer.writeModule(*mod);
out_spir->size = buffer.size_in_bytes();
out_spir->data = malloc(out_spir->size);
memcpy(out_spir->data, buffer.data(), out_spir->size);
return 0;
}
int
clc_c_to_spirv(const struct clc_compile_args *args,
const struct clc_logger *logger,
struct clc_binary *out_spirv,
struct set *dependencies)
{
clc_initialize_llvm();
LLVMContext llvm_ctx;
llvm_ctx.setDiagnosticHandlerCallBack(llvm_log_handler,
const_cast<clc_logger *>(logger));
auto mod = clc_compile_to_llvm_module(llvm_ctx, args, logger, dependencies);
if (!mod)
return -1;
return llvm_mod_to_spirv(std::move(mod), llvm_ctx, args, logger, out_spirv);
}
int
clc_spir_to_spirv(const struct clc_binary *in_spir,
const struct clc_logger *logger,
struct clc_binary *out_spirv)
{
clc_initialize_llvm();
LLVMContext llvm_ctx;
llvm_ctx.setDiagnosticHandlerCallBack(llvm_log_handler,
const_cast<clc_logger *>(logger));
::llvm::StringRef spir_ref(static_cast<const char*>(in_spir->data), in_spir->size);
auto mod = ::llvm::parseBitcodeFile(::llvm::MemoryBufferRef(spir_ref, "<spir>"), llvm_ctx);
if (!mod)
return -1;
return llvm_mod_to_spirv(std::move(mod.get()), llvm_ctx, NULL, logger, out_spirv);
}
class SPIRVMessageConsumer {
public:
SPIRVMessageConsumer(const struct clc_logger *logger): logger(logger) {}
void operator()(spv_message_level_t level, const char *src,
const spv_position_t &pos, const char *msg)
{
if (level == SPV_MSG_INFO || level == SPV_MSG_DEBUG)
return;
std::ostringstream message;
message << "(file=" << (src ? src : "input")
<< ",line=" << pos.line
<< ",column=" << pos.column
<< ",index=" << pos.index
<< "): " << msg << "\n";
if (level == SPV_MSG_WARNING)
clc_warning(logger, "%s", message.str().c_str());
else
clc_error(logger, "%s", message.str().c_str());
}
private:
const struct clc_logger *logger;
};
const char* clc_spirv_tools_version() {
return spvSoftwareVersionString();
}
int
clc_link_spirv_binaries(const struct clc_linker_args *args,
const struct clc_logger *logger,
struct clc_binary *out_spirv)
{
std::vector<std::vector<uint32_t>> binaries;
for (unsigned i = 0; i < args->num_in_objs; i++) {
const uint32_t *data = static_cast<const uint32_t *>(args->in_objs[i]->data);
std::vector<uint32_t> bin(data, data + (args->in_objs[i]->size / 4));
binaries.push_back(bin);
}
SPIRVMessageConsumer msgconsumer(logger);
spvtools::Context context(spirv_target);
context.SetMessageConsumer(msgconsumer);
spvtools::LinkerOptions options;
options.SetAllowPartialLinkage(args->create_library);
options.SetUseHighestVersion(true);
#if defined(HAS_SPIRV_LINK_LLVM_WORKAROUND) && LLVM_VERSION_MAJOR >= 17
options.SetAllowPtrTypeMismatch(true);
#endif
options.SetCreateLibrary(args->create_library);
std::vector<uint32_t> linkingResult;
spv_result_t status = spvtools::Link(context, binaries, &linkingResult, options);
if (status != SPV_SUCCESS) {
#if !defined(HAS_SPIRV_LINK_LLVM_WORKAROUND) && LLVM_VERSION_MAJOR >= 17
clc_warning(logger, "SPIRV-Tools doesn't contain https://github.com/KhronosGroup/SPIRV-Tools/pull/5534\n");
clc_warning(logger, "Please update in order to prevent spurious linking failures\n");
#endif
return -1;
}
out_spirv->size = linkingResult.size() * 4;
out_spirv->data = static_cast<uint32_t *>(malloc(out_spirv->size));
memcpy(out_spirv->data, linkingResult.data(), out_spirv->size);
return 0;
}
bool
clc_validate_spirv(const struct clc_binary *spirv,
const struct clc_logger *logger,
const struct clc_validator_options *options)
{
SPIRVMessageConsumer msgconsumer(logger);
spvtools::SpirvTools tools(spirv_target);
tools.SetMessageConsumer(msgconsumer);
spvtools::ValidatorOptions spirv_options;
const uint32_t *data = static_cast<const uint32_t *>(spirv->data);
if (options) {
spirv_options.SetUniversalLimit(
spv_validator_limit_max_function_args,
options->limit_max_function_arg);
}
return tools.Validate(data, spirv->size / 4, spirv_options);
}
int
clc_spirv_specialize(const struct clc_binary *in_spirv,
const struct clc_parsed_spirv *parsed_data,
const struct clc_spirv_specialization_consts *consts,
struct clc_binary *out_spirv)
{
std::unordered_map<uint32_t, std::vector<uint32_t>> spec_const_map;
for (unsigned i = 0; i < consts->num_specializations; ++i) {
unsigned id = consts->specializations[i].id;
auto parsed_spec_const = std::find_if(parsed_data->spec_constants,
parsed_data->spec_constants + parsed_data->num_spec_constants,
[id](const clc_parsed_spec_constant &c) { return c.id == id; });
assert(parsed_spec_const != parsed_data->spec_constants + parsed_data->num_spec_constants);
std::vector<uint32_t> words;
switch (parsed_spec_const->type) {
case CLC_SPEC_CONSTANT_BOOL:
words.push_back(consts->specializations[i].value.b);
break;
case CLC_SPEC_CONSTANT_INT32:
case CLC_SPEC_CONSTANT_UINT32:
case CLC_SPEC_CONSTANT_FLOAT:
words.push_back(consts->specializations[i].value.u32);
break;
case CLC_SPEC_CONSTANT_INT16:
words.push_back((uint32_t)(int32_t)consts->specializations[i].value.i16);
break;
case CLC_SPEC_CONSTANT_INT8:
words.push_back((uint32_t)(int32_t)consts->specializations[i].value.i8);
break;
case CLC_SPEC_CONSTANT_HALF:
case CLC_SPEC_CONSTANT_UINT16:
words.push_back((uint32_t)consts->specializations[i].value.u16);
break;
case CLC_SPEC_CONSTANT_UINT8:
words.push_back((uint32_t)consts->specializations[i].value.u8);
break;
case CLC_SPEC_CONSTANT_DOUBLE:
case CLC_SPEC_CONSTANT_INT64:
case CLC_SPEC_CONSTANT_UINT64:
words.resize(2);
memcpy(words.data(), &consts->specializations[i].value.u64, 8);
break;
case CLC_SPEC_CONSTANT_UNKNOWN:
assert(0);
break;
}
ASSERTED auto ret = spec_const_map.emplace(id, std::move(words));
assert(ret.second);
}
spvtools::Optimizer opt(spirv_target);
opt.RegisterPass(spvtools::CreateSetSpecConstantDefaultValuePass(std::move(spec_const_map)));
std::vector<uint32_t> result;
if (!opt.Run(static_cast<const uint32_t*>(in_spirv->data), in_spirv->size / 4, &result))
return false;
out_spirv->size = result.size() * 4;
out_spirv->data = malloc(out_spirv->size);
memcpy(out_spirv->data, result.data(), out_spirv->size);
return true;
}
static void
clc_dump_llvm(const llvm::Module *mod, FILE *f)
{
std::string out;
raw_string_ostream os(out);
mod->print(os, nullptr);
os.flush();
fwrite(out.c_str(), out.size(), 1, f);
}
void
clc_dump_spirv(const struct clc_binary *spvbin, FILE *f)
{
spvtools::SpirvTools tools(spirv_target);
const uint32_t *data = static_cast<const uint32_t *>(spvbin->data);
std::vector<uint32_t> bin(data, data + (spvbin->size / 4));
std::string out;
tools.Disassemble(bin, &out,
SPV_BINARY_TO_TEXT_OPTION_INDENT |
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
fwrite(out.c_str(), out.size(), 1, f);
}
void
clc_free_spir_binary(struct clc_binary *spir)
{
free(spir->data);
}
void
clc_free_spirv_binary(struct clc_binary *spvbin)
{
free(spvbin->data);
}
void
initialize_llvm_once(void)
{
LLVMInitializeAllTargets();
LLVMInitializeAllTargetInfos();
LLVMInitializeAllTargetMCs();
LLVMInitializeAllAsmParsers();
LLVMInitializeAllAsmPrinters();
}
std::once_flag initialize_llvm_once_flag;
void
clc_initialize_llvm(void)
{
std::call_once(initialize_llvm_once_flag,
[]() { initialize_llvm_once(); });
}