1 // Copyright (c) 2015-2016 The Khronos Group Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <algorithm>
16 #include <limits>
17 #include <sstream>
18 #include <string>
19 #include <vector>
20
21 #include "gmock/gmock.h"
22 #include "source/latest_version_opencl_std_header.h"
23 #include "source/table.h"
24 #include "source/util/string_utils.h"
25 #include "test/test_fixture.h"
26 #include "test/unit_spirv.h"
27
28 // Returns true if two spv_parsed_operand_t values are equal.
29 // To use this operator, this definition must appear in the same namespace
30 // as spv_parsed_operand_t.
operator ==(const spv_parsed_operand_t & a,const spv_parsed_operand_t & b)31 static bool operator==(const spv_parsed_operand_t& a,
32 const spv_parsed_operand_t& b) {
33 return a.offset == b.offset && a.num_words == b.num_words &&
34 a.type == b.type && a.number_kind == b.number_kind &&
35 a.number_bit_width == b.number_bit_width;
36 }
37
38 namespace spvtools {
39 namespace {
40
41 using ::spvtest::Concatenate;
42 using ::spvtest::MakeInstruction;
43 using utils::MakeVector;
44 using ::spvtest::ScopedContext;
45 using ::testing::_;
46 using ::testing::AnyOf;
47 using ::testing::Eq;
48 using ::testing::InSequence;
49 using ::testing::Return;
50
51 // An easily-constructible and comparable object for the contents of an
52 // spv_parsed_instruction_t. Unlike spv_parsed_instruction_t, owns the memory
53 // of its components.
54 struct ParsedInstruction {
ParsedInstructionspvtools::__anoncf75f7a20111::ParsedInstruction55 explicit ParsedInstruction(const spv_parsed_instruction_t& inst)
56 : words(inst.words, inst.words + inst.num_words),
57 opcode(static_cast<SpvOp>(inst.opcode)),
58 ext_inst_type(inst.ext_inst_type),
59 type_id(inst.type_id),
60 result_id(inst.result_id),
61 operands(inst.operands, inst.operands + inst.num_operands) {}
62
63 std::vector<uint32_t> words;
64 SpvOp opcode;
65 spv_ext_inst_type_t ext_inst_type;
66 uint32_t type_id;
67 uint32_t result_id;
68 std::vector<spv_parsed_operand_t> operands;
69
operator ==spvtools::__anoncf75f7a20111::ParsedInstruction70 bool operator==(const ParsedInstruction& b) const {
71 return words == b.words && opcode == b.opcode &&
72 ext_inst_type == b.ext_inst_type && type_id == b.type_id &&
73 result_id == b.result_id && operands == b.operands;
74 }
75 };
76
77 // Prints a ParsedInstruction object to the given output stream, and returns
78 // the stream.
operator <<(std::ostream & os,const ParsedInstruction & inst)79 std::ostream& operator<<(std::ostream& os, const ParsedInstruction& inst) {
80 os << "\nParsedInstruction( {";
81 spvtest::PrintTo(spvtest::WordVector(inst.words), &os);
82 os << "}, opcode: " << int(inst.opcode)
83 << " ext_inst_type: " << int(inst.ext_inst_type)
84 << " type_id: " << inst.type_id << " result_id: " << inst.result_id;
85 for (const auto& operand : inst.operands) {
86 os << " { offset: " << operand.offset << " num_words: " << operand.num_words
87 << " type: " << int(operand.type)
88 << " number_kind: " << int(operand.number_kind)
89 << " number_bit_width: " << int(operand.number_bit_width) << "}";
90 }
91 os << ")";
92 return os;
93 }
94
95 // Basic check for the equality operator on ParsedInstruction.
TEST(ParsedInstruction,ZeroInitializedAreEqual)96 TEST(ParsedInstruction, ZeroInitializedAreEqual) {
97 spv_parsed_instruction_t pi = {};
98 ParsedInstruction a(pi);
99 ParsedInstruction b(pi);
100 EXPECT_THAT(a, ::testing::TypedEq<ParsedInstruction>(b));
101 }
102
103 // Googlemock class receiving Header/Instruction calls from spvBinaryParse().
104 class MockParseClient {
105 public:
106 MOCK_METHOD6(Header, spv_result_t(spv_endianness_t endian, uint32_t magic,
107 uint32_t version, uint32_t generator,
108 uint32_t id_bound, uint32_t reserved));
109 MOCK_METHOD1(Instruction, spv_result_t(const ParsedInstruction&));
110 };
111
112 // Casts user_data as MockParseClient and invokes its Header().
invoke_header(void * user_data,spv_endianness_t endian,uint32_t magic,uint32_t version,uint32_t generator,uint32_t id_bound,uint32_t reserved)113 spv_result_t invoke_header(void* user_data, spv_endianness_t endian,
114 uint32_t magic, uint32_t version, uint32_t generator,
115 uint32_t id_bound, uint32_t reserved) {
116 return static_cast<MockParseClient*>(user_data)->Header(
117 endian, magic, version, generator, id_bound, reserved);
118 }
119
120 // Casts user_data as MockParseClient and invokes its Instruction().
invoke_instruction(void * user_data,const spv_parsed_instruction_t * parsed_instruction)121 spv_result_t invoke_instruction(
122 void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
123 return static_cast<MockParseClient*>(user_data)->Instruction(
124 ParsedInstruction(*parsed_instruction));
125 }
126
127 // The SPIR-V module header words for the Khronos Assembler generator,
128 // for a module with an ID bound of 1.
129 const uint32_t kHeaderForBound1[] = {
130 SpvMagicNumber, SpvVersion,
131 SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), 1 /*bound*/,
132 0 /*schema*/};
133
134 // Returns the expected SPIR-V module header words for the Khronos
135 // Assembler generator, and with a given Id bound.
ExpectedHeaderForBound(uint32_t bound)136 std::vector<uint32_t> ExpectedHeaderForBound(uint32_t bound) {
137 return {SpvMagicNumber, 0x10000,
138 SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), bound, 0};
139 }
140
141 // Returns a parsed operand for a non-number value at the given word offset
142 // within an instruction.
MakeSimpleOperand(uint16_t offset,spv_operand_type_t type)143 spv_parsed_operand_t MakeSimpleOperand(uint16_t offset,
144 spv_operand_type_t type) {
145 return {offset, 1, type, SPV_NUMBER_NONE, 0};
146 }
147
148 // Returns a parsed operand for a literal unsigned integer value at the given
149 // word offset within an instruction.
MakeLiteralNumberOperand(uint16_t offset)150 spv_parsed_operand_t MakeLiteralNumberOperand(uint16_t offset) {
151 return {offset, 1, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_NUMBER_UNSIGNED_INT,
152 32};
153 }
154
155 // Returns a parsed operand for a literal string value at the given
156 // word offset within an instruction.
MakeLiteralStringOperand(uint16_t offset,uint16_t length)157 spv_parsed_operand_t MakeLiteralStringOperand(uint16_t offset,
158 uint16_t length) {
159 return {offset, length, SPV_OPERAND_TYPE_LITERAL_STRING, SPV_NUMBER_NONE, 0};
160 }
161
162 // Returns a ParsedInstruction for an OpTypeVoid instruction that would
163 // generate the given result Id.
MakeParsedVoidTypeInstruction(uint32_t result_id)164 ParsedInstruction MakeParsedVoidTypeInstruction(uint32_t result_id) {
165 const auto void_inst = MakeInstruction(SpvOpTypeVoid, {result_id});
166 const auto void_operands = std::vector<spv_parsed_operand_t>{
167 MakeSimpleOperand(1, SPV_OPERAND_TYPE_RESULT_ID)};
168 const spv_parsed_instruction_t parsed_void_inst = {
169 void_inst.data(),
170 static_cast<uint16_t>(void_inst.size()),
171 SpvOpTypeVoid,
172 SPV_EXT_INST_TYPE_NONE,
173 0, // type id
174 result_id,
175 void_operands.data(),
176 static_cast<uint16_t>(void_operands.size())};
177 return ParsedInstruction(parsed_void_inst);
178 }
179
180 // Returns a ParsedInstruction for an OpTypeInt instruction that generates
181 // the given result Id for a 32-bit signed integer scalar type.
MakeParsedInt32TypeInstruction(uint32_t result_id)182 ParsedInstruction MakeParsedInt32TypeInstruction(uint32_t result_id) {
183 const auto i32_inst = MakeInstruction(SpvOpTypeInt, {result_id, 32, 1});
184 const auto i32_operands = std::vector<spv_parsed_operand_t>{
185 MakeSimpleOperand(1, SPV_OPERAND_TYPE_RESULT_ID),
186 MakeLiteralNumberOperand(2), MakeLiteralNumberOperand(3)};
187 spv_parsed_instruction_t parsed_i32_inst = {
188 i32_inst.data(),
189 static_cast<uint16_t>(i32_inst.size()),
190 SpvOpTypeInt,
191 SPV_EXT_INST_TYPE_NONE,
192 0, // type id
193 result_id,
194 i32_operands.data(),
195 static_cast<uint16_t>(i32_operands.size())};
196 return ParsedInstruction(parsed_i32_inst);
197 }
198
199 class BinaryParseTest : public spvtest::TextToBinaryTestBase<::testing::Test> {
200 protected:
~BinaryParseTest()201 ~BinaryParseTest() override { spvDiagnosticDestroy(diagnostic_); }
202
Parse(const SpirvVector & words,spv_result_t expected_result,bool flip_words=false)203 void Parse(const SpirvVector& words, spv_result_t expected_result,
204 bool flip_words = false) {
205 SpirvVector flipped_words(words);
206 MaybeFlipWords(flip_words, flipped_words.begin(), flipped_words.end());
207 EXPECT_EQ(expected_result,
208 spvBinaryParse(ScopedContext().context, &client_,
209 flipped_words.data(), flipped_words.size(),
210 invoke_header, invoke_instruction, &diagnostic_));
211 }
212
213 spv_diagnostic diagnostic_ = nullptr;
214 MockParseClient client_;
215 };
216
217 // Adds an EXPECT_CALL to client_->Header() with appropriate parameters,
218 // including bound. Returns the EXPECT_CALL result.
219 #define EXPECT_HEADER(bound) \
220 EXPECT_CALL( \
221 client_, \
222 Header(AnyOf(SPV_ENDIANNESS_LITTLE, SPV_ENDIANNESS_BIG), SpvMagicNumber, \
223 0x10000, SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), \
224 bound, 0 /*reserved*/))
225
226 static const bool kSwapEndians[] = {false, true};
227
TEST_F(BinaryParseTest,EmptyModuleHasValidHeaderAndNoInstructionCallbacks)228 TEST_F(BinaryParseTest, EmptyModuleHasValidHeaderAndNoInstructionCallbacks) {
229 for (bool endian_swap : kSwapEndians) {
230 const auto words = CompileSuccessfully("");
231 EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
232 EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
233 Parse(words, SPV_SUCCESS, endian_swap);
234 EXPECT_EQ(nullptr, diagnostic_);
235 }
236 }
237
TEST_F(BinaryParseTest,NullDiagnosticsIsOkForGoodParse)238 TEST_F(BinaryParseTest, NullDiagnosticsIsOkForGoodParse) {
239 const auto words = CompileSuccessfully("");
240 EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
241 EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
242 EXPECT_EQ(
243 SPV_SUCCESS,
244 spvBinaryParse(ScopedContext().context, &client_, words.data(),
245 words.size(), invoke_header, invoke_instruction, nullptr));
246 }
247
TEST_F(BinaryParseTest,NullDiagnosticsIsOkForBadParse)248 TEST_F(BinaryParseTest, NullDiagnosticsIsOkForBadParse) {
249 auto words = CompileSuccessfully("");
250 words.push_back(0xffffffff); // Certainly invalid instruction header.
251 EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
252 EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
253 EXPECT_EQ(
254 SPV_ERROR_INVALID_BINARY,
255 spvBinaryParse(ScopedContext().context, &client_, words.data(),
256 words.size(), invoke_header, invoke_instruction, nullptr));
257 }
258
259 // Make sure that we don't blow up when both the consumer and the diagnostic are
260 // null.
TEST_F(BinaryParseTest,NullConsumerNullDiagnosticsForBadParse)261 TEST_F(BinaryParseTest, NullConsumerNullDiagnosticsForBadParse) {
262 auto words = CompileSuccessfully("");
263
264 auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
265 ctx.SetMessageConsumer(nullptr);
266
267 words.push_back(0xffffffff); // Certainly invalid instruction header.
268 EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
269 EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
270 EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
271 spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
272 invoke_header, invoke_instruction, nullptr));
273 }
274
TEST_F(BinaryParseTest,SpecifyConsumerNullDiagnosticsForGoodParse)275 TEST_F(BinaryParseTest, SpecifyConsumerNullDiagnosticsForGoodParse) {
276 const auto words = CompileSuccessfully("");
277
278 auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
279 int invocation = 0;
280 ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
281 const spv_position_t&,
282 const char*) { ++invocation; });
283
284 EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
285 EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
286 EXPECT_EQ(SPV_SUCCESS,
287 spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
288 invoke_header, invoke_instruction, nullptr));
289 EXPECT_EQ(0, invocation);
290 }
291
TEST_F(BinaryParseTest,SpecifyConsumerNullDiagnosticsForBadParse)292 TEST_F(BinaryParseTest, SpecifyConsumerNullDiagnosticsForBadParse) {
293 auto words = CompileSuccessfully("");
294
295 auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
296 int invocation = 0;
297 ctx.SetMessageConsumer(
298 [&invocation](spv_message_level_t level, const char* source,
299 const spv_position_t& position, const char* message) {
300 ++invocation;
301 EXPECT_EQ(SPV_MSG_ERROR, level);
302 EXPECT_STREQ("input", source);
303 EXPECT_EQ(0u, position.line);
304 EXPECT_EQ(0u, position.column);
305 EXPECT_EQ(1u, position.index);
306 EXPECT_STREQ("Invalid opcode: 65535", message);
307 });
308
309 words.push_back(0xffffffff); // Certainly invalid instruction header.
310 EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
311 EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
312 EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
313 spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
314 invoke_header, invoke_instruction, nullptr));
315 EXPECT_EQ(1, invocation);
316 }
317
TEST_F(BinaryParseTest,SpecifyConsumerSpecifyDiagnosticsForGoodParse)318 TEST_F(BinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForGoodParse) {
319 const auto words = CompileSuccessfully("");
320
321 auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
322 int invocation = 0;
323 ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
324 const spv_position_t&,
325 const char*) { ++invocation; });
326
327 EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
328 EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
329 EXPECT_EQ(SPV_SUCCESS,
330 spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
331 invoke_header, invoke_instruction, &diagnostic_));
332 EXPECT_EQ(0, invocation);
333 EXPECT_EQ(nullptr, diagnostic_);
334 }
335
TEST_F(BinaryParseTest,SpecifyConsumerSpecifyDiagnosticsForBadParse)336 TEST_F(BinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForBadParse) {
337 auto words = CompileSuccessfully("");
338
339 auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
340 int invocation = 0;
341 ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
342 const spv_position_t&,
343 const char*) { ++invocation; });
344
345 words.push_back(0xffffffff); // Certainly invalid instruction header.
346 EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
347 EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
348 EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
349 spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
350 invoke_header, invoke_instruction, &diagnostic_));
351 EXPECT_EQ(0, invocation);
352 EXPECT_STREQ("Invalid opcode: 65535", diagnostic_->error);
353 }
354
TEST_F(BinaryParseTest,ModuleWithSingleInstructionHasValidHeaderAndInstructionCallback)355 TEST_F(BinaryParseTest,
356 ModuleWithSingleInstructionHasValidHeaderAndInstructionCallback) {
357 for (bool endian_swap : kSwapEndians) {
358 const auto words = CompileSuccessfully("%1 = OpTypeVoid");
359 InSequence calls_expected_in_specific_order;
360 EXPECT_HEADER(2).WillOnce(Return(SPV_SUCCESS));
361 EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
362 .WillOnce(Return(SPV_SUCCESS));
363 Parse(words, SPV_SUCCESS, endian_swap);
364 EXPECT_EQ(nullptr, diagnostic_);
365 }
366 }
367
TEST_F(BinaryParseTest,NullHeaderCallbackIsIgnored)368 TEST_F(BinaryParseTest, NullHeaderCallbackIsIgnored) {
369 const auto words = CompileSuccessfully("%1 = OpTypeVoid");
370 EXPECT_CALL(client_, Header(_, _, _, _, _, _))
371 .Times(0); // No header callback.
372 EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
373 .WillOnce(Return(SPV_SUCCESS));
374 EXPECT_EQ(SPV_SUCCESS, spvBinaryParse(ScopedContext().context, &client_,
375 words.data(), words.size(), nullptr,
376 invoke_instruction, &diagnostic_));
377 EXPECT_EQ(nullptr, diagnostic_);
378 }
379
TEST_F(BinaryParseTest,NullInstructionCallbackIsIgnored)380 TEST_F(BinaryParseTest, NullInstructionCallbackIsIgnored) {
381 const auto words = CompileSuccessfully("%1 = OpTypeVoid");
382 EXPECT_HEADER((2)).WillOnce(Return(SPV_SUCCESS));
383 EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
384 EXPECT_EQ(SPV_SUCCESS,
385 spvBinaryParse(ScopedContext().context, &client_, words.data(),
386 words.size(), invoke_header, nullptr, &diagnostic_));
387 EXPECT_EQ(nullptr, diagnostic_);
388 }
389
390 // Check the result of multiple instruction callbacks.
391 //
392 // This test exercises non-default values for the following members of the
393 // spv_parsed_instruction_t struct: words, num_words, opcode, result_id,
394 // operands, num_operands.
TEST_F(BinaryParseTest,TwoScalarTypesGenerateTwoInstructionCallbacks)395 TEST_F(BinaryParseTest, TwoScalarTypesGenerateTwoInstructionCallbacks) {
396 for (bool endian_swap : kSwapEndians) {
397 const auto words = CompileSuccessfully(
398 "%1 = OpTypeVoid "
399 "%2 = OpTypeInt 32 1");
400 InSequence calls_expected_in_specific_order;
401 EXPECT_HEADER(3).WillOnce(Return(SPV_SUCCESS));
402 EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
403 .WillOnce(Return(SPV_SUCCESS));
404 EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2)))
405 .WillOnce(Return(SPV_SUCCESS));
406 Parse(words, SPV_SUCCESS, endian_swap);
407 EXPECT_EQ(nullptr, diagnostic_);
408 }
409 }
410
TEST_F(BinaryParseTest,EarlyReturnWithZeroPassingCallbacks)411 TEST_F(BinaryParseTest, EarlyReturnWithZeroPassingCallbacks) {
412 for (bool endian_swap : kSwapEndians) {
413 const auto words = CompileSuccessfully(
414 "%1 = OpTypeVoid "
415 "%2 = OpTypeInt 32 1");
416 InSequence calls_expected_in_specific_order;
417 EXPECT_HEADER(3).WillOnce(Return(SPV_ERROR_INVALID_BINARY));
418 // Early exit means no calls to Instruction().
419 EXPECT_CALL(client_, Instruction(_)).Times(0);
420 Parse(words, SPV_ERROR_INVALID_BINARY, endian_swap);
421 // On error, the binary parser doesn't generate its own diagnostics.
422 EXPECT_EQ(nullptr, diagnostic_);
423 }
424 }
425
TEST_F(BinaryParseTest,EarlyReturnWithZeroPassingCallbacksAndSpecifiedResultCode)426 TEST_F(BinaryParseTest,
427 EarlyReturnWithZeroPassingCallbacksAndSpecifiedResultCode) {
428 for (bool endian_swap : kSwapEndians) {
429 const auto words = CompileSuccessfully(
430 "%1 = OpTypeVoid "
431 "%2 = OpTypeInt 32 1");
432 InSequence calls_expected_in_specific_order;
433 EXPECT_HEADER(3).WillOnce(Return(SPV_REQUESTED_TERMINATION));
434 // Early exit means no calls to Instruction().
435 EXPECT_CALL(client_, Instruction(_)).Times(0);
436 Parse(words, SPV_REQUESTED_TERMINATION, endian_swap);
437 // On early termination, the binary parser doesn't generate its own
438 // diagnostics.
439 EXPECT_EQ(nullptr, diagnostic_);
440 }
441 }
442
TEST_F(BinaryParseTest,EarlyReturnWithOnePassingCallback)443 TEST_F(BinaryParseTest, EarlyReturnWithOnePassingCallback) {
444 for (bool endian_swap : kSwapEndians) {
445 const auto words = CompileSuccessfully(
446 "%1 = OpTypeVoid "
447 "%2 = OpTypeInt 32 1 "
448 "%3 = OpTypeFloat 32");
449 InSequence calls_expected_in_specific_order;
450 EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS));
451 EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
452 .WillOnce(Return(SPV_REQUESTED_TERMINATION));
453 Parse(words, SPV_REQUESTED_TERMINATION, endian_swap);
454 // On early termination, the binary parser doesn't generate its own
455 // diagnostics.
456 EXPECT_EQ(nullptr, diagnostic_);
457 }
458 }
459
TEST_F(BinaryParseTest,EarlyReturnWithTwoPassingCallbacks)460 TEST_F(BinaryParseTest, EarlyReturnWithTwoPassingCallbacks) {
461 for (bool endian_swap : kSwapEndians) {
462 const auto words = CompileSuccessfully(
463 "%1 = OpTypeVoid "
464 "%2 = OpTypeInt 32 1 "
465 "%3 = OpTypeFloat 32");
466 InSequence calls_expected_in_specific_order;
467 EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS));
468 EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
469 .WillOnce(Return(SPV_SUCCESS));
470 EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2)))
471 .WillOnce(Return(SPV_REQUESTED_TERMINATION));
472 Parse(words, SPV_REQUESTED_TERMINATION, endian_swap);
473 // On early termination, the binary parser doesn't generate its own
474 // diagnostics.
475 EXPECT_EQ(nullptr, diagnostic_);
476 }
477 }
478
TEST_F(BinaryParseTest,InstructionWithStringOperand)479 TEST_F(BinaryParseTest, InstructionWithStringOperand) {
480 for (bool endian_swap : kSwapEndians) {
481 const std::string str =
482 "the future is already here, it's just not evenly distributed";
483 const auto str_words = MakeVector(str);
484 const auto instruction = MakeInstruction(SpvOpName, {99}, str_words);
485 const auto words = Concatenate({ExpectedHeaderForBound(100), instruction});
486 InSequence calls_expected_in_specific_order;
487 EXPECT_HEADER(100).WillOnce(Return(SPV_SUCCESS));
488 const auto operands = std::vector<spv_parsed_operand_t>{
489 MakeSimpleOperand(1, SPV_OPERAND_TYPE_ID),
490 MakeLiteralStringOperand(2, static_cast<uint16_t>(str_words.size()))};
491 EXPECT_CALL(client_, Instruction(ParsedInstruction(spv_parsed_instruction_t{
492 instruction.data(),
493 static_cast<uint16_t>(instruction.size()),
494 SpvOpName, SPV_EXT_INST_TYPE_NONE, 0 /*type id*/,
495 0 /* No result id for OpName*/, operands.data(),
496 static_cast<uint16_t>(operands.size())})))
497 .WillOnce(Return(SPV_SUCCESS));
498 Parse(words, SPV_SUCCESS, endian_swap);
499 EXPECT_EQ(nullptr, diagnostic_);
500 }
501 }
502
503 // Checks for non-zero values for the result_id and ext_inst_type members
504 // spv_parsed_instruction_t.
TEST_F(BinaryParseTest,ExtendedInstruction)505 TEST_F(BinaryParseTest, ExtendedInstruction) {
506 const auto words = CompileSuccessfully(
507 "%extcl = OpExtInstImport \"OpenCL.std\" "
508 "%result = OpExtInst %float %extcl sqrt %x");
509 EXPECT_HEADER(5).WillOnce(Return(SPV_SUCCESS));
510 EXPECT_CALL(client_, Instruction(_)).WillOnce(Return(SPV_SUCCESS));
511 // We're only interested in the second call to Instruction():
512 const auto operands = std::vector<spv_parsed_operand_t>{
513 MakeSimpleOperand(1, SPV_OPERAND_TYPE_TYPE_ID),
514 MakeSimpleOperand(2, SPV_OPERAND_TYPE_RESULT_ID),
515 MakeSimpleOperand(3,
516 SPV_OPERAND_TYPE_ID), // Extended instruction set Id
517 MakeSimpleOperand(4, SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER),
518 MakeSimpleOperand(5, SPV_OPERAND_TYPE_ID), // Id of the argument
519 };
520 const auto instruction = MakeInstruction(
521 SpvOpExtInst,
522 {2, 3, 1, static_cast<uint32_t>(OpenCLLIB::Entrypoints::Sqrt), 4});
523 EXPECT_CALL(client_,
524 Instruction(ParsedInstruction(spv_parsed_instruction_t{
525 instruction.data(), static_cast<uint16_t>(instruction.size()),
526 SpvOpExtInst, SPV_EXT_INST_TYPE_OPENCL_STD, 2 /*type id*/,
527 3 /*result id*/, operands.data(),
528 static_cast<uint16_t>(operands.size())})))
529 .WillOnce(Return(SPV_SUCCESS));
530 // Since we are actually checking the output, don't test the
531 // endian-swapped version.
532 Parse(words, SPV_SUCCESS, false);
533 EXPECT_EQ(nullptr, diagnostic_);
534 }
535
536 // A binary parser diagnostic test case where we provide the words array
537 // pointer and word count explicitly.
538 struct WordsAndCountDiagnosticCase {
539 const uint32_t* words;
540 size_t num_words;
541 std::string expected_diagnostic;
542 };
543
544 using BinaryParseWordsAndCountDiagnosticTest = spvtest::TextToBinaryTestBase<
545 ::testing::TestWithParam<WordsAndCountDiagnosticCase>>;
546
TEST_P(BinaryParseWordsAndCountDiagnosticTest,WordAndCountCases)547 TEST_P(BinaryParseWordsAndCountDiagnosticTest, WordAndCountCases) {
548 EXPECT_EQ(
549 SPV_ERROR_INVALID_BINARY,
550 spvBinaryParse(ScopedContext().context, nullptr, GetParam().words,
551 GetParam().num_words, nullptr, nullptr, &diagnostic));
552 ASSERT_NE(nullptr, diagnostic);
553 EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
554 }
555
556 INSTANTIATE_TEST_SUITE_P(
557 BinaryParseDiagnostic, BinaryParseWordsAndCountDiagnosticTest,
558 ::testing::ValuesIn(std::vector<WordsAndCountDiagnosticCase>{
559 {nullptr, 0, "Missing module."},
560 {kHeaderForBound1, 0,
561 "Module has incomplete header: only 0 words instead of 5"},
562 {kHeaderForBound1, 1,
563 "Module has incomplete header: only 1 words instead of 5"},
564 {kHeaderForBound1, 2,
565 "Module has incomplete header: only 2 words instead of 5"},
566 {kHeaderForBound1, 3,
567 "Module has incomplete header: only 3 words instead of 5"},
568 {kHeaderForBound1, 4,
569 "Module has incomplete header: only 4 words instead of 5"},
570 }));
571
572 // A binary parser diagnostic test case where a vector of words is
573 // provided. We'll use this to express cases that can't be created
574 // via the assembler. Either we want to make a malformed instruction,
575 // or an invalid case the assembler would reject.
576 struct WordVectorDiagnosticCase {
577 std::vector<uint32_t> words;
578 std::string expected_diagnostic;
579 };
580
581 using BinaryParseWordVectorDiagnosticTest = spvtest::TextToBinaryTestBase<
582 ::testing::TestWithParam<WordVectorDiagnosticCase>>;
583
TEST_P(BinaryParseWordVectorDiagnosticTest,WordVectorCases)584 TEST_P(BinaryParseWordVectorDiagnosticTest, WordVectorCases) {
585 const auto& words = GetParam().words;
586 EXPECT_THAT(spvBinaryParse(ScopedContext().context, nullptr, words.data(),
587 words.size(), nullptr, nullptr, &diagnostic),
588 AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID));
589 ASSERT_NE(nullptr, diagnostic);
590 EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
591 }
592
593 INSTANTIATE_TEST_SUITE_P(
594 BinaryParseDiagnostic, BinaryParseWordVectorDiagnosticTest,
595 ::testing::ValuesIn(std::vector<WordVectorDiagnosticCase>{
596 {Concatenate({ExpectedHeaderForBound(1), {spvOpcodeMake(0, SpvOpNop)}}),
597 "Invalid instruction word count: 0"},
598 {Concatenate(
599 {ExpectedHeaderForBound(1),
600 {spvOpcodeMake(1, static_cast<SpvOp>(
601 std::numeric_limits<uint16_t>::max()))}}),
602 "Invalid opcode: 65535"},
603 {Concatenate({ExpectedHeaderForBound(1),
604 MakeInstruction(SpvOpNop, {42})}),
605 "Invalid instruction OpNop starting at word 5: expected "
606 "no more operands after 1 words, but stated word count is 2."},
607 // Supply several more unexpected words.
608 {Concatenate({ExpectedHeaderForBound(1),
609 MakeInstruction(SpvOpNop, {42, 43, 44, 45, 46, 47})}),
610 "Invalid instruction OpNop starting at word 5: expected "
611 "no more operands after 1 words, but stated word count is 7."},
612 {Concatenate({ExpectedHeaderForBound(1),
613 MakeInstruction(SpvOpTypeVoid, {1, 2})}),
614 "Invalid instruction OpTypeVoid starting at word 5: expected "
615 "no more operands after 2 words, but stated word count is 3."},
616 {Concatenate({ExpectedHeaderForBound(1),
617 MakeInstruction(SpvOpTypeVoid, {1, 2, 5, 9, 10})}),
618 "Invalid instruction OpTypeVoid starting at word 5: expected "
619 "no more operands after 2 words, but stated word count is 6."},
620 {Concatenate({ExpectedHeaderForBound(1),
621 MakeInstruction(SpvOpTypeInt, {1, 32, 1, 9})}),
622 "Invalid instruction OpTypeInt starting at word 5: expected "
623 "no more operands after 4 words, but stated word count is 5."},
624 {Concatenate({ExpectedHeaderForBound(1),
625 MakeInstruction(SpvOpTypeInt, {1})}),
626 "End of input reached while decoding OpTypeInt starting at word 5:"
627 " expected more operands after 2 words."},
628
629 // Check several cases for running off the end of input.
630
631 // Detect a missing single word operand.
632 {Concatenate({ExpectedHeaderForBound(1),
633 {spvOpcodeMake(2, SpvOpTypeStruct)}}),
634 "End of input reached while decoding OpTypeStruct starting at word"
635 " 5: missing result ID operand at word offset 1."},
636 // Detect this a missing a multi-word operand to OpConstant.
637 // We also lie and say the OpConstant instruction has 5 words when
638 // it only has 3. Corresponds to something like this:
639 // %1 = OpTypeInt 64 0
640 // %2 = OpConstant %1 <missing>
641 {Concatenate({ExpectedHeaderForBound(3),
642 {MakeInstruction(SpvOpTypeInt, {1, 64, 0})},
643 {spvOpcodeMake(5, SpvOpConstant), 1, 2}}),
644 "End of input reached while decoding OpConstant starting at word"
645 " 9: missing possibly multi-word literal number operand at word "
646 "offset 3."},
647 // Detect when we provide only one word from the 64-bit literal,
648 // and again lie about the number of words in the instruction.
649 {Concatenate({ExpectedHeaderForBound(3),
650 {MakeInstruction(SpvOpTypeInt, {1, 64, 0})},
651 {spvOpcodeMake(5, SpvOpConstant), 1, 2, 42}}),
652 "End of input reached while decoding OpConstant starting at word"
653 " 9: truncated possibly multi-word literal number operand at word "
654 "offset 3."},
655 // Detect when a required string operand is missing.
656 // Also, lie about the length of the instruction.
657 {Concatenate({ExpectedHeaderForBound(3),
658 {spvOpcodeMake(3, SpvOpString), 1}}),
659 "End of input reached while decoding OpString starting at word"
660 " 5: missing literal string operand at word offset 2."},
661 // Detect when a required string operand is truncated: it's missing
662 // a null terminator. Catching the error avoids a buffer overrun.
663 {Concatenate({ExpectedHeaderForBound(3),
664 {spvOpcodeMake(4, SpvOpString), 1, 0x41414141,
665 0x41414141}}),
666 "End of input reached while decoding OpString starting at word"
667 " 5: truncated literal string operand at word offset 2."},
668 // Detect when an optional string operand is truncated: it's missing
669 // a null terminator. Catching the error avoids a buffer overrun.
670 // (It is valid for an optional string operand to be absent.)
671 {Concatenate({ExpectedHeaderForBound(3),
672 {spvOpcodeMake(6, SpvOpSource),
673 static_cast<uint32_t>(SpvSourceLanguageOpenCL_C), 210,
674 1 /* file id */,
675 /*start of string*/ 0x41414141, 0x41414141}}),
676 "End of input reached while decoding OpSource starting at word"
677 " 5: truncated literal string operand at word offset 4."},
678
679 // (End of input exhaustion test cases.)
680
681 // In this case the instruction word count is too small, where
682 // it would truncate a multi-word operand to OpConstant.
683 {Concatenate({ExpectedHeaderForBound(3),
684 {MakeInstruction(SpvOpTypeInt, {1, 64, 0})},
685 {spvOpcodeMake(4, SpvOpConstant), 1, 2, 44, 44}}),
686 "Invalid word count: OpConstant starting at word 9 says it has 4"
687 " words, but found 5 words instead."},
688 // Word count is to small, where it would truncate a literal string.
689 {Concatenate({ExpectedHeaderForBound(2),
690 {spvOpcodeMake(3, SpvOpString), 1, 0x41414141, 0}}),
691 "Invalid word count: OpString starting at word 5 says it has 3"
692 " words, but found 4 words instead."},
693 // Word count is too large. The string terminates before the last
694 // word.
695 {Concatenate({ExpectedHeaderForBound(2),
696 {spvOpcodeMake(4, SpvOpString), 1 /* result id */},
697 MakeVector("abc"),
698 {0 /* this word does not belong*/}}),
699 "Invalid instruction OpString starting at word 5: expected no more"
700 " operands after 3 words, but stated word count is 4."},
701 // Word count is too large. There are too many words after the string
702 // literal. A linkage attribute decoration is the only case in SPIR-V
703 // where a string operand is followed by another operand.
704 {Concatenate({ExpectedHeaderForBound(2),
705 {spvOpcodeMake(6, SpvOpDecorate), 1 /* target id */,
706 static_cast<uint32_t>(SpvDecorationLinkageAttributes)},
707 MakeVector("abc"),
708 {static_cast<uint32_t>(SpvLinkageTypeImport),
709 0 /* does not belong */}}),
710 "Invalid instruction OpDecorate starting at word 5: expected no more"
711 " operands after 5 words, but stated word count is 6."},
712 // Like the previous case, but with 5 extra words.
713 {Concatenate({ExpectedHeaderForBound(2),
714 {spvOpcodeMake(10, SpvOpDecorate), 1 /* target id */,
715 static_cast<uint32_t>(SpvDecorationLinkageAttributes)},
716 MakeVector("abc"),
717 {static_cast<uint32_t>(SpvLinkageTypeImport),
718 /* don't belong */ 0, 1, 2, 3, 4}}),
719 "Invalid instruction OpDecorate starting at word 5: expected no more"
720 " operands after 5 words, but stated word count is 10."},
721 // Like the previous two cases, but with OpMemberDecorate.
722 {Concatenate({ExpectedHeaderForBound(2),
723 {spvOpcodeMake(7, SpvOpMemberDecorate), 1 /* target id */,
724 42 /* member index */,
725 static_cast<uint32_t>(SpvDecorationLinkageAttributes)},
726 MakeVector("abc"),
727 {static_cast<uint32_t>(SpvLinkageTypeImport),
728 0 /* does not belong */}}),
729 "Invalid instruction OpMemberDecorate starting at word 5: expected no"
730 " more operands after 6 words, but stated word count is 7."},
731 {Concatenate({ExpectedHeaderForBound(2),
732 {spvOpcodeMake(11, SpvOpMemberDecorate),
733 1 /* target id */, 42 /* member index */,
734 static_cast<uint32_t>(SpvDecorationLinkageAttributes)},
735 MakeVector("abc"),
736 {static_cast<uint32_t>(SpvLinkageTypeImport),
737 /* don't belong */ 0, 1, 2, 3, 4}}),
738 "Invalid instruction OpMemberDecorate starting at word 5: expected no"
739 " more operands after 6 words, but stated word count is 11."},
740 // Word count is too large. There should be no more words
741 // after the RelaxedPrecision decoration.
742 {Concatenate({ExpectedHeaderForBound(2),
743 {spvOpcodeMake(4, SpvOpDecorate), 1 /* target id */,
744 static_cast<uint32_t>(SpvDecorationRelaxedPrecision),
745 0 /* does not belong */}}),
746 "Invalid instruction OpDecorate starting at word 5: expected no"
747 " more operands after 3 words, but stated word count is 4."},
748 // Word count is too large. There should be only one word after
749 // the SpecId decoration enum word.
750 {Concatenate({ExpectedHeaderForBound(2),
751 {spvOpcodeMake(5, SpvOpDecorate), 1 /* target id */,
752 static_cast<uint32_t>(SpvDecorationSpecId),
753 42 /* the spec id */, 0 /* does not belong */}}),
754 "Invalid instruction OpDecorate starting at word 5: expected no"
755 " more operands after 4 words, but stated word count is 5."},
756 {Concatenate({ExpectedHeaderForBound(2),
757 {spvOpcodeMake(2, SpvOpTypeVoid), 0}}),
758 "Error: Result Id is 0"},
759 {Concatenate({
760 ExpectedHeaderForBound(2),
761 {spvOpcodeMake(2, SpvOpTypeVoid), 1},
762 {spvOpcodeMake(2, SpvOpTypeBool), 1},
763 }),
764 "Id 1 is defined more than once"},
765 {Concatenate({ExpectedHeaderForBound(3),
766 MakeInstruction(SpvOpExtInst, {2, 3, 100, 4, 5})}),
767 "OpExtInst set Id 100 does not reference an OpExtInstImport result "
768 "Id"},
769 {Concatenate({ExpectedHeaderForBound(101),
770 MakeInstruction(SpvOpExtInstImport, {100},
771 MakeVector("OpenCL.std")),
772 // OpenCL cos is #14
773 MakeInstruction(SpvOpExtInst, {2, 3, 100, 14, 5, 999})}),
774 "Invalid instruction OpExtInst starting at word 10: expected no "
775 "more operands after 6 words, but stated word count is 7."},
776 // In this case, the OpSwitch selector refers to an invalid ID.
777 {Concatenate({ExpectedHeaderForBound(3),
778 MakeInstruction(SpvOpSwitch, {1, 2, 42, 3})}),
779 "Invalid OpSwitch: selector id 1 has no type"},
780 // In this case, the OpSwitch selector refers to an ID that has
781 // no type.
782 {Concatenate({ExpectedHeaderForBound(3),
783 MakeInstruction(SpvOpLabel, {1}),
784 MakeInstruction(SpvOpSwitch, {1, 2, 42, 3})}),
785 "Invalid OpSwitch: selector id 1 has no type"},
786 {Concatenate({ExpectedHeaderForBound(3),
787 MakeInstruction(SpvOpTypeInt, {1, 32, 0}),
788 MakeInstruction(SpvOpSwitch, {1, 3, 42, 3})}),
789 "Invalid OpSwitch: selector id 1 is a type, not a value"},
790 {Concatenate({ExpectedHeaderForBound(3),
791 MakeInstruction(SpvOpTypeFloat, {1, 32}),
792 MakeInstruction(SpvOpConstant, {1, 2, 0x78f00000}),
793 MakeInstruction(SpvOpSwitch, {2, 3, 42, 3})}),
794 "Invalid OpSwitch: selector id 2 is not a scalar integer"},
795 {Concatenate({ExpectedHeaderForBound(3),
796 MakeInstruction(SpvOpExtInstImport, {1},
797 MakeVector("invalid-import"))}),
798 "Invalid extended instruction import 'invalid-import'"},
799 {Concatenate({
800 ExpectedHeaderForBound(3),
801 MakeInstruction(SpvOpTypeInt, {1, 32, 0}),
802 MakeInstruction(SpvOpConstant, {2, 2, 42}),
803 }),
804 "Type Id 2 is not a type"},
805 {Concatenate({
806 ExpectedHeaderForBound(3),
807 MakeInstruction(SpvOpTypeBool, {1}),
808 MakeInstruction(SpvOpConstant, {1, 2, 42}),
809 }),
810 "Type Id 1 is not a scalar numeric type"},
811 }));
812
813 // A binary parser diagnostic case generated from an assembly text input.
814 struct AssemblyDiagnosticCase {
815 std::string assembly;
816 std::string expected_diagnostic;
817 };
818
819 using BinaryParseAssemblyDiagnosticTest = spvtest::TextToBinaryTestBase<
820 ::testing::TestWithParam<AssemblyDiagnosticCase>>;
821
TEST_P(BinaryParseAssemblyDiagnosticTest,AssemblyCases)822 TEST_P(BinaryParseAssemblyDiagnosticTest, AssemblyCases) {
823 auto words = CompileSuccessfully(GetParam().assembly);
824 EXPECT_THAT(spvBinaryParse(ScopedContext().context, nullptr, words.data(),
825 words.size(), nullptr, nullptr, &diagnostic),
826 AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID));
827 ASSERT_NE(nullptr, diagnostic);
828 EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
829 }
830
831 INSTANTIATE_TEST_SUITE_P(
832 BinaryParseDiagnostic, BinaryParseAssemblyDiagnosticTest,
833 ::testing::ValuesIn(std::vector<AssemblyDiagnosticCase>{
834 {"%1 = OpConstant !0 42", "Error: Type Id is 0"},
835 // A required id is 0.
836 {"OpName !0 \"foo\"", "Id is 0"},
837 // An optional id is 0, in this case the optional
838 // initializer.
839 {"%2 = OpVariable %1 CrossWorkgroup !0", "Id is 0"},
840 {"OpControlBarrier !0 %1 %2", "scope ID is 0"},
841 {"OpControlBarrier %1 !0 %2", "scope ID is 0"},
842 {"OpControlBarrier %1 %2 !0", "memory semantics ID is 0"},
843 {"%import = OpExtInstImport \"GLSL.std.450\" "
844 "%result = OpExtInst %type %import !999999 %x",
845 "Invalid extended instruction number: 999999"},
846 {"%2 = OpSpecConstantOp %1 !1000 %2",
847 "Invalid OpSpecConstantOp opcode: 1000"},
848 {"OpCapability !9999", "Invalid capability operand: 9999"},
849 {"OpSource !9999 100", "Invalid source language operand: 9999"},
850 {"OpEntryPoint !9999", "Invalid execution model operand: 9999"},
851 {"OpMemoryModel !9999", "Invalid addressing model operand: 9999"},
852 {"OpMemoryModel Logical !9999", "Invalid memory model operand: 9999"},
853 {"OpExecutionMode %1 !9999", "Invalid execution mode operand: 9999"},
854 {"OpTypeForwardPointer %1 !9999",
855 "Invalid storage class operand: 9999"},
856 {"%2 = OpTypeImage %1 !9999", "Invalid dimensionality operand: 9999"},
857 {"%2 = OpTypeImage %1 1D 0 0 0 0 !9999",
858 "Invalid image format operand: 9999"},
859 {"OpDecorate %1 FPRoundingMode !9999",
860 "Invalid floating-point rounding mode operand: 9999"},
861 {"OpDecorate %1 LinkageAttributes \"C\" !9999",
862 "Invalid linkage type operand: 9999"},
863 {"%1 = OpTypePipe !9999", "Invalid access qualifier operand: 9999"},
864 {"OpDecorate %1 FuncParamAttr !9999",
865 "Invalid function parameter attribute operand: 9999"},
866 {"OpDecorate %1 !9999", "Invalid decoration operand: 9999"},
867 {"OpDecorate %1 BuiltIn !9999", "Invalid built-in operand: 9999"},
868 {"%2 = OpGroupIAdd %1 %3 !9999",
869 "Invalid group operation operand: 9999"},
870 {"OpDecorate %1 FPFastMathMode !63",
871 "Invalid floating-point fast math mode operand: 63 has invalid mask "
872 "component 32"},
873 {"%2 = OpFunction %2 !31",
874 "Invalid function control operand: 31 has invalid mask component 16"},
875 {"OpLoopMerge %1 %2 !1027",
876 "Invalid loop control operand: 1027 has invalid mask component 1024"},
877 {"%2 = OpImageFetch %1 %image %coord !32770",
878 "Invalid image operand: 32770 has invalid mask component 32768"},
879 {"OpSelectionMerge %1 !7",
880 "Invalid selection control operand: 7 has invalid mask component 4"},
881 }));
882
883 } // namespace
884 } // namespace spvtools
885