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:
Pierre Moreau 2022-01-22 21:40:19 +01:00 committed by GitHub
parent 8a40f6de57
commit c38495656d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 517 additions and 254 deletions

View File

@ -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),
&current_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),
&current_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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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" \

View File

@ -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

View File

@ -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;
}