mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-24 20:40:13 +00:00
Linker improvements (#4679)
* test/linker: Code factorisation and small tweaks
* linker: Do not fail when going over limits
The limits are minima and implementations or APIs might support higher
limits, so just warn the user about it. And only check for the limits
right before emitting the binary, as limits might change earlier when
removing duplicate instructions, function prototypes, etc.
The only check performed right before merging, is making sure the ID
bound will not overflow the 32 bits following the merge.
Also, use the defines for the limits instead of hard-coding them.
* linker: Require a memory model in each input module
The existing code could run into weird situation. For example, if the
first module had no memory model, it would not emit any memory model
(sort of reasonable) and would accept without complains all possible mix
from later modules as it would not verify them.
* linker: Replace hex version with SPV_SPIRV_VERSION_WORD
* linker: Error out when linking together different versions
Fixes https://github.com/KhronosGroup/SPIRV-Tools/issues/4135
* tools/linker: Do not write to disk if linking failed
Also, do not consider warnings as errors.
* tools/linker: Fix formatting in help message
* tools/linker: Further clarify the use of --target-env
Also update the text for the default version to reflect the change made
in 7d768812
("Basic support for SPIR-V 1.6 (#4663)").
This commit is contained in:
parent
8a40f6de57
commit
c38495656d
@ -87,10 +87,6 @@ spv_result_t ShiftIdsInModules(const MessageConsumer& consumer,
|
||||
//
|
||||
// |header| should not be null, |modules| should not be empty and pointers
|
||||
// should be non-null. |max_id_bound| should be strictly greater than 0.
|
||||
//
|
||||
// TODO(pierremoreau): What to do when binaries use different versions of
|
||||
// SPIR-V? For now, use the max of all versions found in
|
||||
// the input modules.
|
||||
spv_result_t GenerateHeader(const MessageConsumer& consumer,
|
||||
const std::vector<opt::Module*>& modules,
|
||||
uint32_t max_id_bound, opt::ModuleHeader* header);
|
||||
@ -149,6 +145,15 @@ spv_result_t RemoveLinkageSpecificInstructions(
|
||||
spv_result_t VerifyIds(const MessageConsumer& consumer,
|
||||
opt::IRContext* linked_context);
|
||||
|
||||
// Verify that the universal limits are not crossed, and warn the user
|
||||
// otherwise.
|
||||
//
|
||||
// TODO(pierremoreau):
|
||||
// - Verify against the limits of the environment (e.g. Vulkan limits if
|
||||
// consuming vulkan1.x)
|
||||
spv_result_t VerifyLimits(const MessageConsumer& consumer,
|
||||
const opt::IRContext& linked_context);
|
||||
|
||||
spv_result_t ShiftIdsInModules(const MessageConsumer& consumer,
|
||||
std::vector<opt::Module*>* modules,
|
||||
uint32_t* max_id_bound) {
|
||||
@ -164,7 +169,7 @@ spv_result_t ShiftIdsInModules(const MessageConsumer& consumer,
|
||||
return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_DATA)
|
||||
<< "|max_id_bound| of ShiftIdsInModules should not be null.";
|
||||
|
||||
uint32_t id_bound = modules->front()->IdBound() - 1u;
|
||||
size_t id_bound = modules->front()->IdBound() - 1u;
|
||||
for (auto module_iter = modules->begin() + 1; module_iter != modules->end();
|
||||
++module_iter) {
|
||||
Module* module = *module_iter;
|
||||
@ -172,21 +177,18 @@ spv_result_t ShiftIdsInModules(const MessageConsumer& consumer,
|
||||
insn->ForEachId([&id_bound](uint32_t* id) { *id += id_bound; });
|
||||
});
|
||||
id_bound += module->IdBound() - 1u;
|
||||
if (id_bound > 0x3FFFFF)
|
||||
return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_ID)
|
||||
<< "The limit of IDs, 4194303, was exceeded:"
|
||||
<< " " << id_bound << " is the current ID bound.";
|
||||
|
||||
// Invalidate the DefUseManager
|
||||
module->context()->InvalidateAnalyses(opt::IRContext::kAnalysisDefUse);
|
||||
}
|
||||
++id_bound;
|
||||
if (id_bound > 0x3FFFFF)
|
||||
return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_ID)
|
||||
<< "The limit of IDs, 4194303, was exceeded:"
|
||||
<< " " << id_bound << " is the current ID bound.";
|
||||
if (id_bound > std::numeric_limits<uint32_t>::max())
|
||||
return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_DATA)
|
||||
<< "Too many IDs (" << id_bound
|
||||
<< "): combining all modules would overflow the 32-bit word of the "
|
||||
"SPIR-V header.";
|
||||
|
||||
*max_id_bound = id_bound;
|
||||
*max_id_bound = static_cast<uint32_t>(id_bound);
|
||||
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
@ -203,12 +205,22 @@ spv_result_t GenerateHeader(const MessageConsumer& consumer,
|
||||
return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_DATA)
|
||||
<< "|max_id_bound| of GenerateHeader should not be null.";
|
||||
|
||||
uint32_t version = 0u;
|
||||
for (const auto& module : modules)
|
||||
version = std::max(version, module->version());
|
||||
const uint32_t linked_version = modules.front()->version();
|
||||
for (std::size_t i = 1; i < modules.size(); ++i) {
|
||||
const uint32_t module_version = modules[i]->version();
|
||||
if (module_version != linked_version)
|
||||
return DiagnosticStream({0, 0, 1}, consumer, "", SPV_ERROR_INTERNAL)
|
||||
<< "Conflicting SPIR-V versions: "
|
||||
<< SPV_SPIRV_VERSION_MAJOR_PART(linked_version) << "."
|
||||
<< SPV_SPIRV_VERSION_MINOR_PART(linked_version)
|
||||
<< " (input modules 1 through " << i << ") vs "
|
||||
<< SPV_SPIRV_VERSION_MAJOR_PART(module_version) << "."
|
||||
<< SPV_SPIRV_VERSION_MINOR_PART(module_version)
|
||||
<< " (input module " << (i + 1) << ").";
|
||||
}
|
||||
|
||||
header->magic_number = SpvMagicNumber;
|
||||
header->version = version;
|
||||
header->version = linked_version;
|
||||
header->generator = SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_LINKER, 0);
|
||||
header->bound = max_id_bound;
|
||||
header->schema = 0u;
|
||||
@ -244,44 +256,55 @@ spv_result_t MergeModules(const MessageConsumer& consumer,
|
||||
linked_module->AddExtInstImport(
|
||||
std::unique_ptr<Instruction>(inst.Clone(linked_context)));
|
||||
|
||||
do {
|
||||
const Instruction* memory_model_inst = input_modules[0]->GetMemoryModel();
|
||||
if (memory_model_inst == nullptr) break;
|
||||
const Instruction* linked_memory_model_inst =
|
||||
input_modules.front()->GetMemoryModel();
|
||||
if (linked_memory_model_inst == nullptr) {
|
||||
return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_BINARY)
|
||||
<< "Input module 1 is lacking an OpMemoryModel instruction.";
|
||||
}
|
||||
const uint32_t linked_addressing_model =
|
||||
linked_memory_model_inst->GetSingleWordOperand(0u);
|
||||
const uint32_t linked_memory_model =
|
||||
linked_memory_model_inst->GetSingleWordOperand(1u);
|
||||
|
||||
uint32_t addressing_model = memory_model_inst->GetSingleWordOperand(0u);
|
||||
uint32_t memory_model = memory_model_inst->GetSingleWordOperand(1u);
|
||||
for (const auto& module : input_modules) {
|
||||
memory_model_inst = module->GetMemoryModel();
|
||||
if (memory_model_inst == nullptr) continue;
|
||||
for (std::size_t i = 1; i < input_modules.size(); ++i) {
|
||||
const Module* module = input_modules[i];
|
||||
const Instruction* memory_model_inst = module->GetMemoryModel();
|
||||
if (memory_model_inst == nullptr)
|
||||
return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_BINARY)
|
||||
<< "Input module " << (i + 1)
|
||||
<< " is lacking an OpMemoryModel instruction.";
|
||||
|
||||
if (addressing_model != memory_model_inst->GetSingleWordOperand(0u)) {
|
||||
spv_operand_desc initial_desc = nullptr, current_desc = nullptr;
|
||||
grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL,
|
||||
addressing_model, &initial_desc);
|
||||
grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL,
|
||||
memory_model_inst->GetSingleWordOperand(0u),
|
||||
¤t_desc);
|
||||
return DiagnosticStream(position, consumer, "", SPV_ERROR_INTERNAL)
|
||||
<< "Conflicting addressing models: " << initial_desc->name
|
||||
<< " vs " << current_desc->name << ".";
|
||||
}
|
||||
if (memory_model != memory_model_inst->GetSingleWordOperand(1u)) {
|
||||
spv_operand_desc initial_desc = nullptr, current_desc = nullptr;
|
||||
grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL, memory_model,
|
||||
&initial_desc);
|
||||
grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL,
|
||||
memory_model_inst->GetSingleWordOperand(1u),
|
||||
¤t_desc);
|
||||
return DiagnosticStream(position, consumer, "", SPV_ERROR_INTERNAL)
|
||||
<< "Conflicting memory models: " << initial_desc->name << " vs "
|
||||
<< current_desc->name << ".";
|
||||
}
|
||||
const uint32_t module_addressing_model =
|
||||
memory_model_inst->GetSingleWordOperand(0u);
|
||||
if (module_addressing_model != linked_addressing_model) {
|
||||
spv_operand_desc linked_desc = nullptr, module_desc = nullptr;
|
||||
grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL,
|
||||
linked_addressing_model, &linked_desc);
|
||||
grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL,
|
||||
module_addressing_model, &module_desc);
|
||||
return DiagnosticStream(position, consumer, "", SPV_ERROR_INTERNAL)
|
||||
<< "Conflicting addressing models: " << linked_desc->name
|
||||
<< " (input modules 1 through " << i << ") vs "
|
||||
<< module_desc->name << " (input module " << (i + 1) << ").";
|
||||
}
|
||||
|
||||
if (memory_model_inst != nullptr)
|
||||
linked_module->SetMemoryModel(std::unique_ptr<Instruction>(
|
||||
memory_model_inst->Clone(linked_context)));
|
||||
} while (false);
|
||||
const uint32_t module_memory_model =
|
||||
memory_model_inst->GetSingleWordOperand(1u);
|
||||
if (module_memory_model != linked_memory_model) {
|
||||
spv_operand_desc linked_desc = nullptr, module_desc = nullptr;
|
||||
grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL, linked_memory_model,
|
||||
&linked_desc);
|
||||
grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL, module_memory_model,
|
||||
&module_desc);
|
||||
return DiagnosticStream(position, consumer, "", SPV_ERROR_INTERNAL)
|
||||
<< "Conflicting memory models: " << linked_desc->name
|
||||
<< " (input modules 1 through " << i << ") vs "
|
||||
<< module_desc->name << " (input module " << (i + 1) << ").";
|
||||
}
|
||||
}
|
||||
linked_module->SetMemoryModel(std::unique_ptr<Instruction>(
|
||||
linked_memory_model_inst->Clone(linked_context)));
|
||||
|
||||
std::vector<std::pair<uint32_t, std::string>> entry_points;
|
||||
for (const auto& module : input_modules)
|
||||
@ -332,7 +355,7 @@ spv_result_t MergeModules(const MessageConsumer& consumer,
|
||||
|
||||
// If the generated module uses SPIR-V 1.1 or higher, add an
|
||||
// OpModuleProcessed instruction about the linking step.
|
||||
if (linked_module->version() >= 0x10100) {
|
||||
if (linked_module->version() >= SPV_SPIRV_VERSION_WORD(1, 1)) {
|
||||
const std::string processed_string("Linked by SPIR-V Tools Linker");
|
||||
std::vector<uint32_t> processed_words =
|
||||
spvtools::utils::MakeVector(processed_string);
|
||||
@ -349,18 +372,12 @@ spv_result_t MergeModules(const MessageConsumer& consumer,
|
||||
// TODO(pierremoreau): Since the modules have not been validate, should we
|
||||
// expect SpvStorageClassFunction variables outside
|
||||
// functions?
|
||||
uint32_t num_global_values = 0u;
|
||||
for (const auto& module : input_modules) {
|
||||
for (const auto& inst : module->types_values()) {
|
||||
linked_module->AddType(
|
||||
std::unique_ptr<Instruction>(inst.Clone(linked_context)));
|
||||
num_global_values += inst.opcode() == SpvOpVariable;
|
||||
}
|
||||
}
|
||||
if (num_global_values > 0xFFFF)
|
||||
return DiagnosticStream(position, consumer, "", SPV_ERROR_INTERNAL)
|
||||
<< "The limit of global values, 65535, was exceeded;"
|
||||
<< " " << num_global_values << " global values were found.";
|
||||
|
||||
// Process functions and their basic blocks
|
||||
for (const auto& module : input_modules) {
|
||||
@ -632,6 +649,34 @@ spv_result_t VerifyIds(const MessageConsumer& consumer,
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
spv_result_t VerifyLimits(const MessageConsumer& consumer,
|
||||
const opt::IRContext& linked_context) {
|
||||
spv_position_t position = {};
|
||||
|
||||
const uint32_t max_id_bound = linked_context.module()->id_bound();
|
||||
if (max_id_bound >= SPV_LIMIT_RESULT_ID_BOUND)
|
||||
DiagnosticStream({0u, 0u, 4u}, consumer, "", SPV_WARNING)
|
||||
<< "The minimum limit of IDs, " << (SPV_LIMIT_RESULT_ID_BOUND - 1)
|
||||
<< ", was exceeded:"
|
||||
<< " " << max_id_bound << " is the current ID bound.\n"
|
||||
<< "The resulting module might not be supported by all "
|
||||
"implementations.";
|
||||
|
||||
size_t num_global_values = 0u;
|
||||
for (const auto& inst : linked_context.module()->types_values()) {
|
||||
num_global_values += inst.opcode() == SpvOpVariable;
|
||||
}
|
||||
if (num_global_values >= SPV_LIMIT_GLOBAL_VARIABLES_MAX)
|
||||
DiagnosticStream(position, consumer, "", SPV_WARNING)
|
||||
<< "The minimum limit of global values, "
|
||||
<< (SPV_LIMIT_GLOBAL_VARIABLES_MAX - 1) << ", was exceeded;"
|
||||
<< " " << num_global_values << " global values were found.\n"
|
||||
<< "The resulting module might not be supported by all "
|
||||
"implementations.";
|
||||
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
spv_result_t Link(const Context& context,
|
||||
@ -756,7 +801,11 @@ spv_result_t Link(const Context& context, const uint32_t* const* binaries,
|
||||
pass_res = manager.Run(&linked_context);
|
||||
if (pass_res == opt::Pass::Status::Failure) return SPV_ERROR_INVALID_DATA;
|
||||
|
||||
// Phase 11: Output the module
|
||||
// Phase 11: Warn if SPIR-V limits were exceeded
|
||||
res = VerifyLimits(consumer, linked_context);
|
||||
if (res != SPV_SUCCESS) return res;
|
||||
|
||||
// Phase 12: Output the module
|
||||
linked_context.module()->ToBinary(linked_binary, true);
|
||||
|
||||
return SPV_SUCCESS;
|
||||
|
@ -20,40 +20,57 @@
|
||||
namespace spvtools {
|
||||
namespace {
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
using BinaryVersion = spvtest::LinkerTest;
|
||||
|
||||
TEST_F(BinaryVersion, LinkerChoosesMaxSpirvVersion) {
|
||||
spvtest::Binary CreateBinary(uint32_t version) {
|
||||
return {
|
||||
// clang-format off
|
||||
// Header
|
||||
SpvMagicNumber,
|
||||
version,
|
||||
SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS, 0),
|
||||
1u, // NOTE: Bound
|
||||
0u, // NOTE: Schema; reserved
|
||||
|
||||
// OpCapability Shader
|
||||
SpvOpCapability | 2u << SpvWordCountShift,
|
||||
SpvCapabilityShader,
|
||||
|
||||
// OpMemoryModel Logical Simple
|
||||
SpvOpMemoryModel | 3u << SpvWordCountShift,
|
||||
SpvAddressingModelLogical,
|
||||
SpvMemoryModelSimple
|
||||
// clang-format on
|
||||
};
|
||||
}
|
||||
|
||||
TEST_F(BinaryVersion, Match) {
|
||||
// clang-format off
|
||||
spvtest::Binaries binaries = {
|
||||
{
|
||||
SpvMagicNumber,
|
||||
0x00010300u,
|
||||
SPV_GENERATOR_CODEPLAY,
|
||||
1u, // NOTE: Bound
|
||||
0u // NOTE: Schema; reserved
|
||||
},
|
||||
{
|
||||
SpvMagicNumber,
|
||||
0x00010500u,
|
||||
SPV_GENERATOR_CODEPLAY,
|
||||
1u, // NOTE: Bound
|
||||
0u // NOTE: Schema; reserved
|
||||
},
|
||||
{
|
||||
SpvMagicNumber,
|
||||
0x00010100u,
|
||||
SPV_GENERATOR_CODEPLAY,
|
||||
1u, // NOTE: Bound
|
||||
0u // NOTE: Schema; reserved
|
||||
}
|
||||
CreateBinary(SPV_SPIRV_VERSION_WORD(1, 3)),
|
||||
CreateBinary(SPV_SPIRV_VERSION_WORD(1, 3)),
|
||||
};
|
||||
// clang-format on
|
||||
spvtest::Binary linked_binary;
|
||||
|
||||
ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary));
|
||||
ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)) << GetErrorMessage();
|
||||
EXPECT_THAT(GetErrorMessage(), std::string());
|
||||
EXPECT_EQ(SPV_SPIRV_VERSION_WORD(1, 3), linked_binary[1]);
|
||||
}
|
||||
|
||||
EXPECT_EQ(0x00010500u, linked_binary[1]);
|
||||
TEST_F(BinaryVersion, Mismatch) {
|
||||
// clang-format off
|
||||
spvtest::Binaries binaries = {
|
||||
CreateBinary(SPV_SPIRV_VERSION_WORD(1, 3)),
|
||||
CreateBinary(SPV_SPIRV_VERSION_WORD(1, 5)),
|
||||
};
|
||||
// clang-format on
|
||||
spvtest::Binary linked_binary;
|
||||
ASSERT_EQ(SPV_ERROR_INTERNAL, Link(binaries, &linked_binary))
|
||||
<< GetErrorMessage();
|
||||
EXPECT_THAT(GetErrorMessage(),
|
||||
HasSubstr("Conflicting SPIR-V versions: 1.3 (input modules 1 "
|
||||
"through 1) vs 1.5 (input module 2)."));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -26,6 +26,8 @@ class EntryPoints : public spvtest::LinkerTest {};
|
||||
|
||||
TEST_F(EntryPoints, SameModelDifferentName) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %3 "foo"
|
||||
%1 = OpTypeVoid
|
||||
%2 = OpTypeFunction %1
|
||||
@ -33,6 +35,8 @@ OpEntryPoint GLCompute %3 "foo"
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %3 "bar"
|
||||
%1 = OpTypeVoid
|
||||
%2 = OpTypeFunction %1
|
||||
@ -41,12 +45,15 @@ OpFunctionEnd
|
||||
)";
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary));
|
||||
ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
|
||||
<< GetErrorMessage();
|
||||
EXPECT_THAT(GetErrorMessage(), std::string());
|
||||
}
|
||||
|
||||
TEST_F(EntryPoints, DifferentModelSameName) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %3 "foo"
|
||||
%1 = OpTypeVoid
|
||||
%2 = OpTypeFunction %1
|
||||
@ -54,6 +61,8 @@ OpEntryPoint GLCompute %3 "foo"
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Vertex %3 "foo"
|
||||
%1 = OpTypeVoid
|
||||
%2 = OpTypeFunction %1
|
||||
@ -62,12 +71,15 @@ OpFunctionEnd
|
||||
)";
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary));
|
||||
ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
|
||||
<< GetErrorMessage();
|
||||
EXPECT_THAT(GetErrorMessage(), std::string());
|
||||
}
|
||||
|
||||
TEST_F(EntryPoints, SameModelAndName) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %3 "foo"
|
||||
%1 = OpTypeVoid
|
||||
%2 = OpTypeFunction %1
|
||||
@ -75,6 +87,8 @@ OpEntryPoint GLCompute %3 "foo"
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %3 "foo"
|
||||
%1 = OpTypeVoid
|
||||
%2 = OpTypeFunction %1
|
||||
|
@ -22,85 +22,56 @@ namespace {
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
|
||||
const uint32_t binary_count = 2u;
|
||||
|
||||
class EntryPointsAmountTest : public spvtest::LinkerTest {
|
||||
public:
|
||||
EntryPointsAmountTest() { binaries.reserve(0xFFFF); }
|
||||
EntryPointsAmountTest() { binaries.reserve(binary_count + 1u); }
|
||||
|
||||
void SetUp() override {
|
||||
binaries.push_back({SpvMagicNumber,
|
||||
SpvVersion,
|
||||
SPV_GENERATOR_CODEPLAY,
|
||||
10u, // NOTE: Bound
|
||||
0u, // NOTE: Schema; reserved
|
||||
const uint32_t global_variable_count_per_binary =
|
||||
(SPV_LIMIT_GLOBAL_VARIABLES_MAX - 1u) / binary_count;
|
||||
|
||||
3u << SpvWordCountShift | SpvOpTypeFloat,
|
||||
1u, // NOTE: Result ID
|
||||
32u, // NOTE: Width
|
||||
spvtest::Binary common_binary = {
|
||||
// clang-format off
|
||||
SpvMagicNumber,
|
||||
SpvVersion,
|
||||
SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS, 0),
|
||||
3u + global_variable_count_per_binary, // NOTE: Bound
|
||||
0u, // NOTE: Schema; reserved
|
||||
|
||||
4u << SpvWordCountShift | SpvOpTypePointer,
|
||||
2u, // NOTE: Result ID
|
||||
SpvStorageClassInput,
|
||||
1u, // NOTE: Type ID
|
||||
SpvOpCapability | 2u << SpvWordCountShift,
|
||||
SpvCapabilityShader,
|
||||
|
||||
2u << SpvWordCountShift | SpvOpTypeVoid,
|
||||
3u, // NOTE: Result ID
|
||||
SpvOpMemoryModel | 3u << SpvWordCountShift,
|
||||
SpvAddressingModelLogical,
|
||||
SpvMemoryModelSimple,
|
||||
|
||||
3u << SpvWordCountShift | SpvOpTypeFunction,
|
||||
4u, // NOTE: Result ID
|
||||
3u, // NOTE: Return type
|
||||
SpvOpTypeFloat | 3u << SpvWordCountShift,
|
||||
1u, // NOTE: Result ID
|
||||
32u, // NOTE: Width
|
||||
|
||||
5u << SpvWordCountShift | SpvOpFunction,
|
||||
3u, // NOTE: Result type
|
||||
5u, // NOTE: Result ID
|
||||
SpvFunctionControlMaskNone,
|
||||
4u, // NOTE: Function type
|
||||
SpvOpTypePointer | 4u << SpvWordCountShift,
|
||||
2u, // NOTE: Result ID
|
||||
SpvStorageClassInput,
|
||||
1u // NOTE: Type ID
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
2u << SpvWordCountShift | SpvOpLabel,
|
||||
6u, // NOTE: Result ID
|
||||
binaries.push_back({});
|
||||
spvtest::Binary& binary = binaries.back();
|
||||
binary.reserve(common_binary.size() + global_variable_count_per_binary * 4);
|
||||
binary.insert(binary.end(), common_binary.cbegin(), common_binary.cend());
|
||||
|
||||
4u << SpvWordCountShift | SpvOpVariable,
|
||||
2u, // NOTE: Type ID
|
||||
7u, // NOTE: Result ID
|
||||
SpvStorageClassFunction,
|
||||
for (uint32_t i = 0u; i < global_variable_count_per_binary; ++i) {
|
||||
binary.push_back(SpvOpVariable | 4u << SpvWordCountShift);
|
||||
binary.push_back(2u); // NOTE: Type ID
|
||||
binary.push_back(3u + i); // NOTE: Result ID
|
||||
binary.push_back(SpvStorageClassInput);
|
||||
}
|
||||
|
||||
4u << SpvWordCountShift | SpvOpVariable,
|
||||
2u, // NOTE: Type ID
|
||||
8u, // NOTE: Result ID
|
||||
SpvStorageClassFunction,
|
||||
|
||||
4u << SpvWordCountShift | SpvOpVariable,
|
||||
2u, // NOTE: Type ID
|
||||
9u, // NOTE: Result ID
|
||||
SpvStorageClassFunction,
|
||||
|
||||
1u << SpvWordCountShift | SpvOpReturn,
|
||||
|
||||
1u << SpvWordCountShift | SpvOpFunctionEnd});
|
||||
for (size_t i = 0u; i < 2u; ++i) {
|
||||
spvtest::Binary binary = {
|
||||
SpvMagicNumber,
|
||||
SpvVersion,
|
||||
SPV_GENERATOR_CODEPLAY,
|
||||
103u, // NOTE: Bound
|
||||
0u, // NOTE: Schema; reserved
|
||||
|
||||
3u << SpvWordCountShift | SpvOpTypeFloat,
|
||||
1u, // NOTE: Result ID
|
||||
32u, // NOTE: Width
|
||||
|
||||
4u << SpvWordCountShift | SpvOpTypePointer,
|
||||
2u, // NOTE: Result ID
|
||||
SpvStorageClassInput,
|
||||
1u // NOTE: Type ID
|
||||
};
|
||||
|
||||
for (uint32_t j = 0u; j < 0xFFFFu / 2u; ++j) {
|
||||
binary.push_back(4u << SpvWordCountShift | SpvOpVariable);
|
||||
binary.push_back(2u); // NOTE: Type ID
|
||||
binary.push_back(j + 3u); // NOTE: Result ID
|
||||
binary.push_back(SpvStorageClassInput);
|
||||
}
|
||||
binaries.push_back(binary);
|
||||
for (uint32_t i = 0u; i < binary_count - 1u; ++i) {
|
||||
binaries.push_back(binaries.back());
|
||||
}
|
||||
}
|
||||
void TearDown() override { binaries.clear(); }
|
||||
@ -111,42 +82,53 @@ class EntryPointsAmountTest : public spvtest::LinkerTest {
|
||||
TEST_F(EntryPointsAmountTest, UnderLimit) {
|
||||
spvtest::Binary linked_binary;
|
||||
|
||||
EXPECT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary));
|
||||
ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)) << GetErrorMessage();
|
||||
EXPECT_THAT(GetErrorMessage(), std::string());
|
||||
}
|
||||
|
||||
TEST_F(EntryPointsAmountTest, OverLimit) {
|
||||
binaries.push_back({SpvMagicNumber,
|
||||
SpvVersion,
|
||||
SPV_GENERATOR_CODEPLAY,
|
||||
5u, // NOTE: Bound
|
||||
0u, // NOTE: Schema; reserved
|
||||
binaries.push_back({
|
||||
// clang-format off
|
||||
SpvMagicNumber,
|
||||
SpvVersion,
|
||||
SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS, 0),
|
||||
5u, // NOTE: Bound
|
||||
0u, // NOTE: Schema; reserved
|
||||
|
||||
3u << SpvWordCountShift | SpvOpTypeFloat,
|
||||
1u, // NOTE: Result ID
|
||||
32u, // NOTE: Width
|
||||
SpvOpCapability | 2u << SpvWordCountShift,
|
||||
SpvCapabilityShader,
|
||||
|
||||
4u << SpvWordCountShift | SpvOpTypePointer,
|
||||
2u, // NOTE: Result ID
|
||||
SpvStorageClassInput,
|
||||
1u, // NOTE: Type ID
|
||||
SpvOpMemoryModel | 3u << SpvWordCountShift,
|
||||
SpvAddressingModelLogical,
|
||||
SpvMemoryModelSimple,
|
||||
|
||||
4u << SpvWordCountShift | SpvOpVariable,
|
||||
2u, // NOTE: Type ID
|
||||
3u, // NOTE: Result ID
|
||||
SpvStorageClassInput,
|
||||
SpvOpTypeFloat | 3u << SpvWordCountShift,
|
||||
1u, // NOTE: Result ID
|
||||
32u, // NOTE: Width
|
||||
|
||||
4u << SpvWordCountShift | SpvOpVariable,
|
||||
2u, // NOTE: Type ID
|
||||
4u, // NOTE: Result ID
|
||||
SpvStorageClassInput});
|
||||
SpvOpTypePointer | 4u << SpvWordCountShift,
|
||||
2u, // NOTE: Result ID
|
||||
SpvStorageClassInput,
|
||||
1u, // NOTE: Type ID
|
||||
|
||||
SpvOpVariable | 4u << SpvWordCountShift,
|
||||
2u, // NOTE: Type ID
|
||||
3u, // NOTE: Result ID
|
||||
SpvStorageClassInput,
|
||||
|
||||
SpvOpVariable | 4u << SpvWordCountShift,
|
||||
2u, // NOTE: Type ID
|
||||
4u, // NOTE: Result ID
|
||||
SpvStorageClassInput
|
||||
// clang-format on
|
||||
});
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
|
||||
EXPECT_EQ(SPV_ERROR_INTERNAL, Link(binaries, &linked_binary));
|
||||
EXPECT_THAT(GetErrorMessage(),
|
||||
HasSubstr("The limit of global values, 65535, was exceeded; "
|
||||
"65536 global values were found."));
|
||||
ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)) << GetErrorMessage();
|
||||
EXPECT_THAT(
|
||||
GetErrorMessage(),
|
||||
HasSubstr("The minimum limit of global values, 65535, was exceeded; "
|
||||
"65536 global values were found."));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -21,51 +21,114 @@ namespace spvtools {
|
||||
namespace {
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
using IdsLimit = spvtest::LinkerTest;
|
||||
|
||||
class IdsLimit : public spvtest::LinkerTest {
|
||||
public:
|
||||
IdsLimit() { binaries.reserve(2u); }
|
||||
|
||||
void SetUp() override {
|
||||
const uint32_t id_bound = SPV_LIMIT_RESULT_ID_BOUND - 1u;
|
||||
const uint32_t constant_count =
|
||||
id_bound -
|
||||
2u; // One ID is used for TypeBool, and (constant_count + 1) < id_bound
|
||||
|
||||
// This is needed, as otherwise the ID bound will get reset to 1 while
|
||||
// running the RemoveDuplicates pass.
|
||||
spvtest::Binary common_binary = {
|
||||
// clang-format off
|
||||
SpvMagicNumber,
|
||||
SpvVersion,
|
||||
SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS, 0),
|
||||
id_bound, // NOTE: Bound
|
||||
0u, // NOTE: Schema; reserved
|
||||
|
||||
SpvOpCapability | 2u << SpvWordCountShift,
|
||||
SpvCapabilityShader,
|
||||
|
||||
SpvOpMemoryModel | 3u << SpvWordCountShift,
|
||||
SpvAddressingModelLogical,
|
||||
SpvMemoryModelSimple,
|
||||
|
||||
SpvOpTypeBool | 2u << SpvWordCountShift,
|
||||
1u // NOTE: Result ID
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
binaries.push_back({});
|
||||
spvtest::Binary& binary = binaries.back();
|
||||
binary.reserve(common_binary.size() + constant_count * 3u);
|
||||
binary.insert(binary.end(), common_binary.cbegin(), common_binary.cend());
|
||||
|
||||
for (uint32_t i = 0u; i < constant_count; ++i) {
|
||||
binary.push_back(SpvOpConstantTrue | 3u << SpvWordCountShift);
|
||||
binary.push_back(1u); // NOTE: Type ID
|
||||
binary.push_back(2u + i); // NOTE: Result ID
|
||||
}
|
||||
}
|
||||
void TearDown() override { binaries.clear(); }
|
||||
|
||||
spvtest::Binaries binaries;
|
||||
};
|
||||
|
||||
spvtest::Binary CreateBinary(uint32_t id_bound) {
|
||||
return {
|
||||
// clang-format off
|
||||
// Header
|
||||
SpvMagicNumber,
|
||||
SpvVersion,
|
||||
SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS, 0),
|
||||
id_bound, // NOTE: Bound
|
||||
0u, // NOTE: Schema; reserved
|
||||
|
||||
// OpCapability Shader
|
||||
SpvOpCapability | 2u << SpvWordCountShift,
|
||||
SpvCapabilityShader,
|
||||
|
||||
// OpMemoryModel Logical Simple
|
||||
SpvOpMemoryModel | 3u << SpvWordCountShift,
|
||||
SpvAddressingModelLogical,
|
||||
SpvMemoryModelSimple
|
||||
// clang-format on
|
||||
};
|
||||
}
|
||||
|
||||
TEST_F(IdsLimit, UnderLimit) {
|
||||
spvtest::Binaries binaries = {
|
||||
{
|
||||
SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY,
|
||||
0x2FFFFFu, // NOTE: Bound
|
||||
0u, // NOTE: Schema; reserved
|
||||
},
|
||||
{
|
||||
SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY,
|
||||
0x100000u, // NOTE: Bound
|
||||
0u, // NOTE: Schema; reserved
|
||||
}};
|
||||
spvtest::Binary linked_binary;
|
||||
|
||||
ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary));
|
||||
ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)) << GetErrorMessage();
|
||||
EXPECT_THAT(GetErrorMessage(), std::string());
|
||||
EXPECT_EQ(0x3FFFFEu, linked_binary[3]);
|
||||
EXPECT_EQ(0x3FFFFFu, linked_binary[3]);
|
||||
}
|
||||
|
||||
TEST_F(IdsLimit, OverLimit) {
|
||||
spvtest::Binaries binaries = {
|
||||
{
|
||||
SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY,
|
||||
0x2FFFFFu, // NOTE: Bound
|
||||
0u, // NOTE: Schema; reserved
|
||||
},
|
||||
{
|
||||
SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY,
|
||||
0x100000u, // NOTE: Bound
|
||||
0u, // NOTE: Schema; reserved
|
||||
},
|
||||
{
|
||||
SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY,
|
||||
3u, // NOTE: Bound
|
||||
0u, // NOTE: Schema; reserved
|
||||
}};
|
||||
spvtest::Binary& binary = binaries.back();
|
||||
|
||||
const uint32_t id_bound = binary[3];
|
||||
binary[3] = id_bound + 1u;
|
||||
|
||||
binary.push_back(SpvOpConstantFalse | 3u << SpvWordCountShift);
|
||||
binary.push_back(1u); // NOTE: Type ID
|
||||
binary.push_back(id_bound); // NOTE: Result ID
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)) << GetErrorMessage();
|
||||
EXPECT_THAT(
|
||||
GetErrorMessage(),
|
||||
HasSubstr("The minimum limit of IDs, 4194303, was exceeded: 4194304 is "
|
||||
"the current ID bound."));
|
||||
EXPECT_EQ(0x400000u, linked_binary[3]);
|
||||
}
|
||||
|
||||
TEST_F(IdsLimit, Overflow) {
|
||||
spvtest::Binaries binaries = {CreateBinary(0xFFFFFFFFu),
|
||||
CreateBinary(0x00000002u)};
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_ID, Link(binaries, &linked_binary));
|
||||
EXPECT_THAT(GetErrorMessage(),
|
||||
HasSubstr("The limit of IDs, 4194303, was exceeded: 4194304 is "
|
||||
"the current ID bound."));
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, Link(binaries, &linked_binary));
|
||||
EXPECT_THAT(
|
||||
GetErrorMessage(),
|
||||
HasSubstr("Too many IDs (4294967296): combining all modules would "
|
||||
"overflow the 32-bit word of the SPIR-V header."));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -26,6 +26,9 @@ using MatchingImportsToExports = spvtest::LinkerTest;
|
||||
TEST_F(MatchingImportsToExports, Default) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Import
|
||||
%2 = OpTypeFloat 32
|
||||
%1 = OpVariable %2 Uniform
|
||||
@ -33,6 +36,9 @@ OpDecorate %1 LinkageAttributes "foo" Import
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
%2 = OpTypeFloat 32
|
||||
%3 = OpConstant %2 42
|
||||
@ -40,11 +46,14 @@ OpDecorate %1 LinkageAttributes "foo" Export
|
||||
)";
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
|
||||
ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
|
||||
<< GetErrorMessage();
|
||||
|
||||
const std::string expected_res =
|
||||
R"(OpModuleProcessed "Linked by SPIR-V Tools Linker"
|
||||
R"(OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpModuleProcessed "Linked by SPIR-V Tools Linker"
|
||||
%1 = OpTypeFloat 32
|
||||
%2 = OpVariable %1 Input
|
||||
%3 = OpConstant %1 42
|
||||
@ -52,7 +61,7 @@ OpDecorate %1 LinkageAttributes "foo" Export
|
||||
)";
|
||||
std::string res_body;
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
|
||||
EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
<< GetErrorMessage();
|
||||
EXPECT_EQ(expected_res, res_body);
|
||||
}
|
||||
@ -60,23 +69,29 @@ OpDecorate %1 LinkageAttributes "foo" Export
|
||||
TEST_F(MatchingImportsToExports, NotALibraryExtraExports) {
|
||||
const std::string body = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
%2 = OpTypeFloat 32
|
||||
%1 = OpVariable %2 Uniform
|
||||
)";
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body}, &linked_binary))
|
||||
ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body}, &linked_binary))
|
||||
<< GetErrorMessage();
|
||||
|
||||
const std::string expected_res =
|
||||
R"(OpModuleProcessed "Linked by SPIR-V Tools Linker"
|
||||
R"(OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpModuleProcessed "Linked by SPIR-V Tools Linker"
|
||||
%1 = OpTypeFloat 32
|
||||
%2 = OpVariable %1 Uniform
|
||||
)";
|
||||
std::string res_body;
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
|
||||
EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
<< GetErrorMessage();
|
||||
EXPECT_EQ(expected_res, res_body);
|
||||
}
|
||||
@ -84,6 +99,9 @@ OpDecorate %1 LinkageAttributes "foo" Export
|
||||
TEST_F(MatchingImportsToExports, LibraryExtraExports) {
|
||||
const std::string body = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
%2 = OpTypeFloat 32
|
||||
%1 = OpVariable %2 Uniform
|
||||
@ -92,10 +110,13 @@ OpDecorate %1 LinkageAttributes "foo" Export
|
||||
spvtest::Binary linked_binary;
|
||||
LinkerOptions options;
|
||||
options.SetCreateLibrary(true);
|
||||
EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body}, &linked_binary, options))
|
||||
ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body}, &linked_binary, options))
|
||||
<< GetErrorMessage();
|
||||
|
||||
const std::string expected_res = R"(OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpModuleProcessed "Linked by SPIR-V Tools Linker"
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
%2 = OpTypeFloat 32
|
||||
@ -103,7 +124,7 @@ OpDecorate %1 LinkageAttributes "foo" Export
|
||||
)";
|
||||
std::string res_body;
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
|
||||
EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
<< GetErrorMessage();
|
||||
EXPECT_EQ(expected_res, res_body);
|
||||
}
|
||||
@ -111,11 +132,18 @@ OpDecorate %1 LinkageAttributes "foo" Export
|
||||
TEST_F(MatchingImportsToExports, UnresolvedImports) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Import
|
||||
%2 = OpTypeFloat 32
|
||||
%1 = OpVariable %2 Uniform
|
||||
)";
|
||||
const std::string body2 = R"()";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
)";
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
|
||||
@ -127,6 +155,9 @@ OpDecorate %1 LinkageAttributes "foo" Import
|
||||
TEST_F(MatchingImportsToExports, TypeMismatch) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Import
|
||||
%2 = OpTypeFloat 32
|
||||
%1 = OpVariable %2 Uniform
|
||||
@ -134,6 +165,9 @@ OpDecorate %1 LinkageAttributes "foo" Import
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
%2 = OpTypeInt 32 0
|
||||
%3 = OpConstant %2 42
|
||||
@ -153,6 +187,9 @@ OpDecorate %1 LinkageAttributes "foo" Export
|
||||
TEST_F(MatchingImportsToExports, MultipleDefinitions) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Import
|
||||
%2 = OpTypeFloat 32
|
||||
%1 = OpVariable %2 Uniform
|
||||
@ -160,6 +197,9 @@ OpDecorate %1 LinkageAttributes "foo" Import
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
%2 = OpTypeFloat 32
|
||||
%3 = OpConstant %2 42
|
||||
@ -167,6 +207,9 @@ OpDecorate %1 LinkageAttributes "foo" Export
|
||||
)";
|
||||
const std::string body3 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
%2 = OpTypeFloat 32
|
||||
%3 = OpConstant %2 -1
|
||||
@ -185,6 +228,9 @@ OpDecorate %1 LinkageAttributes "foo" Export
|
||||
TEST_F(MatchingImportsToExports, SameNameDifferentTypes) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Import
|
||||
%2 = OpTypeFloat 32
|
||||
%1 = OpVariable %2 Uniform
|
||||
@ -192,6 +238,9 @@ OpDecorate %1 LinkageAttributes "foo" Import
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
%2 = OpTypeInt 32 0
|
||||
%3 = OpConstant %2 42
|
||||
@ -199,6 +248,9 @@ OpDecorate %1 LinkageAttributes "foo" Export
|
||||
)";
|
||||
const std::string body3 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
%2 = OpTypeFloat 32
|
||||
%3 = OpConstant %2 12
|
||||
@ -217,6 +269,9 @@ OpDecorate %1 LinkageAttributes "foo" Export
|
||||
TEST_F(MatchingImportsToExports, DecorationMismatch) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Import
|
||||
OpDecorate %2 Constant
|
||||
%2 = OpTypeFloat 32
|
||||
@ -225,6 +280,9 @@ OpDecorate %2 Constant
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
%2 = OpTypeFloat 32
|
||||
%3 = OpConstant %2 42
|
||||
@ -244,8 +302,10 @@ OpDecorate %1 LinkageAttributes "foo" Export
|
||||
TEST_F(MatchingImportsToExports,
|
||||
FuncParamAttrDifferButStillMatchExportToImport) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Kernel
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Import
|
||||
OpDecorate %2 FuncParamAttr Zext
|
||||
%3 = OpTypeVoid
|
||||
@ -256,8 +316,10 @@ OpDecorate %2 FuncParamAttr Zext
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Kernel
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
OpDecorate %2 FuncParamAttr Sext
|
||||
%3 = OpTypeVoid
|
||||
@ -271,10 +333,12 @@ OpFunctionEnd
|
||||
)";
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
|
||||
ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
|
||||
<< GetErrorMessage();
|
||||
|
||||
const std::string expected_res = R"(OpCapability Kernel
|
||||
const std::string expected_res = R"(OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpModuleProcessed "Linked by SPIR-V Tools Linker"
|
||||
OpDecorate %1 FuncParamAttr Sext
|
||||
%2 = OpTypeVoid
|
||||
@ -288,7 +352,7 @@ OpFunctionEnd
|
||||
)";
|
||||
std::string res_body;
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
|
||||
EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
<< GetErrorMessage();
|
||||
EXPECT_EQ(expected_res, res_body);
|
||||
}
|
||||
@ -296,6 +360,9 @@ OpFunctionEnd
|
||||
TEST_F(MatchingImportsToExports, FunctionCtrl) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Import
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
@ -306,6 +373,9 @@ OpFunctionEnd
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
@ -316,11 +386,14 @@ OpFunctionEnd
|
||||
)";
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
|
||||
ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
|
||||
<< GetErrorMessage();
|
||||
|
||||
const std::string expected_res =
|
||||
R"(OpModuleProcessed "Linked by SPIR-V Tools Linker"
|
||||
R"(OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpModuleProcessed "Linked by SPIR-V Tools Linker"
|
||||
%1 = OpTypeVoid
|
||||
%2 = OpTypeFunction %1
|
||||
%3 = OpTypeFloat 32
|
||||
@ -332,15 +405,17 @@ OpFunctionEnd
|
||||
)";
|
||||
std::string res_body;
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
|
||||
EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
<< GetErrorMessage();
|
||||
EXPECT_EQ(expected_res, res_body);
|
||||
}
|
||||
|
||||
TEST_F(MatchingImportsToExports, UseExportedFuncParamAttr) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Kernel
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Import
|
||||
OpDecorate %2 FuncParamAttr Zext
|
||||
%2 = OpDecorationGroup
|
||||
@ -356,8 +431,10 @@ OpFunctionEnd
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Kernel
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
OpDecorate %2 FuncParamAttr Sext
|
||||
%3 = OpTypeVoid
|
||||
@ -371,10 +448,12 @@ OpFunctionEnd
|
||||
)";
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
|
||||
ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
|
||||
<< GetErrorMessage();
|
||||
|
||||
const std::string expected_res = R"(OpCapability Kernel
|
||||
const std::string expected_res = R"(OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpModuleProcessed "Linked by SPIR-V Tools Linker"
|
||||
OpDecorate %1 FuncParamAttr Zext
|
||||
%1 = OpDecorationGroup
|
||||
@ -394,15 +473,17 @@ OpFunctionEnd
|
||||
)";
|
||||
std::string res_body;
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
|
||||
EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
<< GetErrorMessage();
|
||||
EXPECT_EQ(expected_res, res_body);
|
||||
}
|
||||
|
||||
TEST_F(MatchingImportsToExports, NamesAndDecorations) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Kernel
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpName %1 "foo"
|
||||
OpName %3 "param"
|
||||
OpDecorate %1 LinkageAttributes "foo" Import
|
||||
@ -422,8 +503,10 @@ OpFunctionEnd
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Kernel
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpName %1 "foo"
|
||||
OpName %2 "param"
|
||||
OpDecorate %1 LinkageAttributes "foo" Export
|
||||
@ -440,10 +523,12 @@ OpFunctionEnd
|
||||
)";
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
|
||||
ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
|
||||
<< GetErrorMessage();
|
||||
|
||||
const std::string expected_res = R"(OpCapability Kernel
|
||||
const std::string expected_res = R"(OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpName %1 "foo"
|
||||
OpName %2 "param"
|
||||
OpModuleProcessed "Linked by SPIR-V Tools Linker"
|
||||
@ -467,7 +552,7 @@ OpFunctionEnd
|
||||
)";
|
||||
std::string res_body;
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
|
||||
EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
|
||||
<< GetErrorMessage();
|
||||
EXPECT_EQ(expected_res, res_body);
|
||||
}
|
||||
|
@ -50,9 +50,9 @@ OpMemoryModel Physical32 Simple
|
||||
spvtest::Binary linked_binary;
|
||||
EXPECT_EQ(SPV_ERROR_INTERNAL,
|
||||
AssembleAndLink({body1, body2}, &linked_binary));
|
||||
EXPECT_THAT(
|
||||
GetErrorMessage(),
|
||||
HasSubstr("Conflicting addressing models: Logical vs Physical32."));
|
||||
EXPECT_THAT(GetErrorMessage(),
|
||||
HasSubstr("Conflicting addressing models: Logical (input modules "
|
||||
"1 through 1) vs Physical32 (input module 2)."));
|
||||
}
|
||||
|
||||
TEST_F(MemoryModel, MemoryMismatch) {
|
||||
@ -67,7 +67,38 @@ OpMemoryModel Logical GLSL450
|
||||
EXPECT_EQ(SPV_ERROR_INTERNAL,
|
||||
AssembleAndLink({body1, body2}, &linked_binary));
|
||||
EXPECT_THAT(GetErrorMessage(),
|
||||
HasSubstr("Conflicting memory models: Simple vs GLSL450."));
|
||||
HasSubstr("Conflicting memory models: Simple (input modules 1 "
|
||||
"through 1) vs GLSL450 (input module 2)."));
|
||||
}
|
||||
|
||||
TEST_F(MemoryModel, FirstLackMemoryModel) {
|
||||
const std::string body1 = R"(
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpMemoryModel Logical GLSL450
|
||||
)";
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
|
||||
AssembleAndLink({body1, body2}, &linked_binary));
|
||||
EXPECT_THAT(
|
||||
GetErrorMessage(),
|
||||
HasSubstr("Input module 1 is lacking an OpMemoryModel instruction."));
|
||||
}
|
||||
|
||||
TEST_F(MemoryModel, SecondLackMemoryModel) {
|
||||
const std::string body1 = R"(
|
||||
OpMemoryModel Logical GLSL450
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
)";
|
||||
|
||||
spvtest::Binary linked_binary;
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
|
||||
AssembleAndLink({body1, body2}, &linked_binary));
|
||||
EXPECT_THAT(
|
||||
GetErrorMessage(),
|
||||
HasSubstr("Input module 2 is lacking an OpMemoryModel instruction."));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -26,6 +26,9 @@ using PartialLinkage = spvtest::LinkerTest;
|
||||
TEST_F(PartialLinkage, Allowed) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Import
|
||||
OpDecorate %2 LinkageAttributes "bar" Import
|
||||
%3 = OpTypeFloat 32
|
||||
@ -34,6 +37,9 @@ OpDecorate %2 LinkageAttributes "bar" Import
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "bar" Export
|
||||
%2 = OpTypeFloat 32
|
||||
%3 = OpConstant %2 3.1415
|
||||
@ -44,9 +50,13 @@ OpDecorate %1 LinkageAttributes "bar" Export
|
||||
LinkerOptions linker_options;
|
||||
linker_options.SetAllowPartialLinkage(true);
|
||||
ASSERT_EQ(SPV_SUCCESS,
|
||||
AssembleAndLink({body1, body2}, &linked_binary, linker_options));
|
||||
AssembleAndLink({body1, body2}, &linked_binary, linker_options))
|
||||
<< GetErrorMessage();
|
||||
|
||||
const std::string expected_res = R"(OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpModuleProcessed "Linked by SPIR-V Tools Linker"
|
||||
OpDecorate %1 LinkageAttributes "foo" Import
|
||||
%2 = OpTypeFloat 32
|
||||
@ -64,6 +74,9 @@ OpDecorate %1 LinkageAttributes "foo" Import
|
||||
TEST_F(PartialLinkage, Disallowed) {
|
||||
const std::string body1 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "foo" Import
|
||||
OpDecorate %2 LinkageAttributes "bar" Import
|
||||
%3 = OpTypeFloat 32
|
||||
@ -72,6 +85,9 @@ OpDecorate %2 LinkageAttributes "bar" Import
|
||||
)";
|
||||
const std::string body2 = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability Addresses
|
||||
OpCapability Kernel
|
||||
OpMemoryModel Physical64 OpenCL
|
||||
OpDecorate %1 LinkageAttributes "bar" Export
|
||||
%2 = OpTypeFloat 32
|
||||
%3 = OpConstant %2 3.1415
|
||||
|
@ -66,6 +66,7 @@ using TypeMatch = spvtest::LinkerTest;
|
||||
"OpCapability Kernel\n" \
|
||||
"OpCapability Shader\n" \
|
||||
"OpCapability Addresses\n" \
|
||||
"OpMemoryModel Physical64 OpenCL\n" \
|
||||
"OpDecorate %var LinkageAttributes \"foo\" " \
|
||||
"{Import,Export}\n" \
|
||||
"; CHECK: [[baseint:%\\w+]] = OpTypeInt 32 1\n" \
|
||||
|
@ -135,7 +135,8 @@ TEST_F(UniqueIds, UniquelyMerged) {
|
||||
LinkerOptions options;
|
||||
options.SetVerifyIds(true);
|
||||
spv_result_t res = AssembleAndLink(bodies, &linked_binary, options);
|
||||
EXPECT_EQ(SPV_SUCCESS, res);
|
||||
ASSERT_EQ(SPV_SUCCESS, res) << GetErrorMessage();
|
||||
EXPECT_THAT(GetErrorMessage(), std::string());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -49,15 +49,18 @@ Options (in lexicographical order):
|
||||
--create-library
|
||||
Link the binaries into a library, keeping all exported symbols.
|
||||
-h, --help
|
||||
Print this help.
|
||||
Print this help.
|
||||
--target-env <env>
|
||||
Set the target environment. Without this flag the target
|
||||
environment defaults to spv1.5. <env> must be one of
|
||||
{%s}
|
||||
Set the environment used for interpreting the inputs. Without
|
||||
this option the environment defaults to spv1.6. <env> must be
|
||||
one of {%s}.
|
||||
NOTE: The SPIR-V version used by the linked binary module
|
||||
depends only on the version of the inputs, and is not affected
|
||||
by this option.
|
||||
--verify-ids
|
||||
Verify that IDs in the resulting modules are truly unique.
|
||||
--version
|
||||
Display linker version information
|
||||
Display linker version information.
|
||||
)",
|
||||
program, program, target_env_list.c_str());
|
||||
}
|
||||
@ -160,10 +163,11 @@ int main(int argc, char** argv) {
|
||||
|
||||
std::vector<uint32_t> linkingResult;
|
||||
spv_result_t status = Link(context, contents, &linkingResult, options);
|
||||
if (status != SPV_SUCCESS && status != SPV_WARNING) return 1;
|
||||
|
||||
if (!WriteFile<uint32_t>(outFile, "wb", linkingResult.data(),
|
||||
linkingResult.size()))
|
||||
return 1;
|
||||
|
||||
return status == SPV_SUCCESS ? 0 : 1;
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user