123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893 |
- // Copyright (c) 2015-2016 The Khronos Group Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- #include <algorithm>
- #include <limits>
- #include <sstream>
- #include <string>
- #include <vector>
- #include "gmock/gmock.h"
- #include "source/latest_version_opencl_std_header.h"
- #include "source/table.h"
- #include "test/test_fixture.h"
- #include "test/unit_spirv.h"
- // Returns true if two spv_parsed_operand_t values are equal.
- // To use this operator, this definition must appear in the same namespace
- // as spv_parsed_operand_t.
- static bool operator==(const spv_parsed_operand_t& a,
- const spv_parsed_operand_t& b) {
- return a.offset == b.offset && a.num_words == b.num_words &&
- a.type == b.type && a.number_kind == b.number_kind &&
- a.number_bit_width == b.number_bit_width;
- }
- namespace spvtools {
- namespace {
- using ::spvtest::Concatenate;
- using ::spvtest::MakeInstruction;
- using ::spvtest::MakeVector;
- using ::spvtest::ScopedContext;
- using ::testing::_;
- using ::testing::AnyOf;
- using ::testing::Eq;
- using ::testing::InSequence;
- using ::testing::Return;
- // An easily-constructible and comparable object for the contents of an
- // spv_parsed_instruction_t. Unlike spv_parsed_instruction_t, owns the memory
- // of its components.
- struct ParsedInstruction {
- explicit ParsedInstruction(const spv_parsed_instruction_t& inst)
- : words(inst.words, inst.words + inst.num_words),
- opcode(static_cast<SpvOp>(inst.opcode)),
- ext_inst_type(inst.ext_inst_type),
- type_id(inst.type_id),
- result_id(inst.result_id),
- operands(inst.operands, inst.operands + inst.num_operands) {}
- std::vector<uint32_t> words;
- SpvOp opcode;
- spv_ext_inst_type_t ext_inst_type;
- uint32_t type_id;
- uint32_t result_id;
- std::vector<spv_parsed_operand_t> operands;
- bool operator==(const ParsedInstruction& b) const {
- return words == b.words && opcode == b.opcode &&
- ext_inst_type == b.ext_inst_type && type_id == b.type_id &&
- result_id == b.result_id && operands == b.operands;
- }
- };
- // Prints a ParsedInstruction object to the given output stream, and returns
- // the stream.
- std::ostream& operator<<(std::ostream& os, const ParsedInstruction& inst) {
- os << "\nParsedInstruction( {";
- spvtest::PrintTo(spvtest::WordVector(inst.words), &os);
- os << "}, opcode: " << int(inst.opcode)
- << " ext_inst_type: " << int(inst.ext_inst_type)
- << " type_id: " << inst.type_id << " result_id: " << inst.result_id;
- for (const auto& operand : inst.operands) {
- os << " { offset: " << operand.offset << " num_words: " << operand.num_words
- << " type: " << int(operand.type)
- << " number_kind: " << int(operand.number_kind)
- << " number_bit_width: " << int(operand.number_bit_width) << "}";
- }
- os << ")";
- return os;
- }
- // Sanity check for the equality operator on ParsedInstruction.
- TEST(ParsedInstruction, ZeroInitializedAreEqual) {
- spv_parsed_instruction_t pi = {};
- ParsedInstruction a(pi);
- ParsedInstruction b(pi);
- EXPECT_THAT(a, ::testing::TypedEq<ParsedInstruction>(b));
- }
- // Googlemock class receiving Header/Instruction calls from spvBinaryParse().
- class MockParseClient {
- public:
- MOCK_METHOD6(Header, spv_result_t(spv_endianness_t endian, uint32_t magic,
- uint32_t version, uint32_t generator,
- uint32_t id_bound, uint32_t reserved));
- MOCK_METHOD1(Instruction, spv_result_t(const ParsedInstruction&));
- };
- // Casts user_data as MockParseClient and invokes its Header().
- spv_result_t 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) {
- return static_cast<MockParseClient*>(user_data)->Header(
- endian, magic, version, generator, id_bound, reserved);
- }
- // Casts user_data as MockParseClient and invokes its Instruction().
- spv_result_t invoke_instruction(
- void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
- return static_cast<MockParseClient*>(user_data)->Instruction(
- ParsedInstruction(*parsed_instruction));
- }
- // The SPIR-V module header words for the Khronos Assembler generator,
- // for a module with an ID bound of 1.
- const uint32_t kHeaderForBound1[] = {
- SpvMagicNumber, SpvVersion,
- SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), 1 /*bound*/,
- 0 /*schema*/};
- // Returns the expected SPIR-V module header words for the Khronos
- // Assembler generator, and with a given Id bound.
- std::vector<uint32_t> ExpectedHeaderForBound(uint32_t bound) {
- return {SpvMagicNumber, 0x10000,
- SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), bound, 0};
- }
- // Returns a parsed operand for a non-number value at the given word offset
- // within an instruction.
- spv_parsed_operand_t MakeSimpleOperand(uint16_t offset,
- spv_operand_type_t type) {
- return {offset, 1, type, SPV_NUMBER_NONE, 0};
- }
- // Returns a parsed operand for a literal unsigned integer value at the given
- // word offset within an instruction.
- spv_parsed_operand_t MakeLiteralNumberOperand(uint16_t offset) {
- return {offset, 1, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_NUMBER_UNSIGNED_INT,
- 32};
- }
- // Returns a parsed operand for a literal string value at the given
- // word offset within an instruction.
- spv_parsed_operand_t MakeLiteralStringOperand(uint16_t offset,
- uint16_t length) {
- return {offset, length, SPV_OPERAND_TYPE_LITERAL_STRING, SPV_NUMBER_NONE, 0};
- }
- // Returns a ParsedInstruction for an OpTypeVoid instruction that would
- // generate the given result Id.
- ParsedInstruction MakeParsedVoidTypeInstruction(uint32_t result_id) {
- const auto void_inst = MakeInstruction(SpvOpTypeVoid, {result_id});
- const auto void_operands = std::vector<spv_parsed_operand_t>{
- MakeSimpleOperand(1, SPV_OPERAND_TYPE_RESULT_ID)};
- const spv_parsed_instruction_t parsed_void_inst = {
- void_inst.data(),
- static_cast<uint16_t>(void_inst.size()),
- SpvOpTypeVoid,
- SPV_EXT_INST_TYPE_NONE,
- 0, // type id
- result_id,
- void_operands.data(),
- static_cast<uint16_t>(void_operands.size())};
- return ParsedInstruction(parsed_void_inst);
- }
- // Returns a ParsedInstruction for an OpTypeInt instruction that generates
- // the given result Id for a 32-bit signed integer scalar type.
- ParsedInstruction MakeParsedInt32TypeInstruction(uint32_t result_id) {
- const auto i32_inst = MakeInstruction(SpvOpTypeInt, {result_id, 32, 1});
- const auto i32_operands = std::vector<spv_parsed_operand_t>{
- MakeSimpleOperand(1, SPV_OPERAND_TYPE_RESULT_ID),
- MakeLiteralNumberOperand(2), MakeLiteralNumberOperand(3)};
- spv_parsed_instruction_t parsed_i32_inst = {
- i32_inst.data(),
- static_cast<uint16_t>(i32_inst.size()),
- SpvOpTypeInt,
- SPV_EXT_INST_TYPE_NONE,
- 0, // type id
- result_id,
- i32_operands.data(),
- static_cast<uint16_t>(i32_operands.size())};
- return ParsedInstruction(parsed_i32_inst);
- }
- class BinaryParseTest : public spvtest::TextToBinaryTestBase<::testing::Test> {
- protected:
- ~BinaryParseTest() { spvDiagnosticDestroy(diagnostic_); }
- void Parse(const SpirvVector& words, spv_result_t expected_result,
- bool flip_words = false) {
- SpirvVector flipped_words(words);
- SCOPED_TRACE(flip_words ? "Flipped Endianness" : "Normal Endianness");
- if (flip_words) {
- std::transform(flipped_words.begin(), flipped_words.end(),
- flipped_words.begin(), [](const uint32_t raw_word) {
- return spvFixWord(raw_word,
- I32_ENDIAN_HOST == I32_ENDIAN_BIG
- ? SPV_ENDIANNESS_LITTLE
- : SPV_ENDIANNESS_BIG);
- });
- }
- EXPECT_EQ(expected_result,
- spvBinaryParse(ScopedContext().context, &client_,
- flipped_words.data(), flipped_words.size(),
- invoke_header, invoke_instruction, &diagnostic_));
- }
- spv_diagnostic diagnostic_ = nullptr;
- MockParseClient client_;
- };
- // Adds an EXPECT_CALL to client_->Header() with appropriate parameters,
- // including bound. Returns the EXPECT_CALL result.
- #define EXPECT_HEADER(bound) \
- EXPECT_CALL( \
- client_, \
- Header(AnyOf(SPV_ENDIANNESS_LITTLE, SPV_ENDIANNESS_BIG), SpvMagicNumber, \
- 0x10000, SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), \
- bound, 0 /*reserved*/))
- static const bool kSwapEndians[] = {false, true};
- TEST_F(BinaryParseTest, EmptyModuleHasValidHeaderAndNoInstructionCallbacks) {
- for (bool endian_swap : kSwapEndians) {
- const auto words = CompileSuccessfully("");
- EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
- Parse(words, SPV_SUCCESS, endian_swap);
- EXPECT_EQ(nullptr, diagnostic_);
- }
- }
- TEST_F(BinaryParseTest, NullDiagnosticsIsOkForGoodParse) {
- const auto words = CompileSuccessfully("");
- EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
- EXPECT_EQ(
- SPV_SUCCESS,
- spvBinaryParse(ScopedContext().context, &client_, words.data(),
- words.size(), invoke_header, invoke_instruction, nullptr));
- }
- TEST_F(BinaryParseTest, NullDiagnosticsIsOkForBadParse) {
- auto words = CompileSuccessfully("");
- words.push_back(0xffffffff); // Certainly invalid instruction header.
- EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
- EXPECT_EQ(
- SPV_ERROR_INVALID_BINARY,
- spvBinaryParse(ScopedContext().context, &client_, words.data(),
- words.size(), invoke_header, invoke_instruction, nullptr));
- }
- // Make sure that we don't blow up when both the consumer and the diagnostic are
- // null.
- TEST_F(BinaryParseTest, NullConsumerNullDiagnosticsForBadParse) {
- auto words = CompileSuccessfully("");
- auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
- ctx.SetMessageConsumer(nullptr);
- words.push_back(0xffffffff); // Certainly invalid instruction header.
- EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
- EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
- spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
- invoke_header, invoke_instruction, nullptr));
- }
- TEST_F(BinaryParseTest, SpecifyConsumerNullDiagnosticsForGoodParse) {
- const auto words = CompileSuccessfully("");
- auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
- int invocation = 0;
- ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
- const spv_position_t&,
- const char*) { ++invocation; });
- EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
- EXPECT_EQ(SPV_SUCCESS,
- spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
- invoke_header, invoke_instruction, nullptr));
- EXPECT_EQ(0, invocation);
- }
- TEST_F(BinaryParseTest, SpecifyConsumerNullDiagnosticsForBadParse) {
- auto words = CompileSuccessfully("");
- auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
- int invocation = 0;
- ctx.SetMessageConsumer(
- [&invocation](spv_message_level_t level, const char* source,
- const spv_position_t& position, const char* message) {
- ++invocation;
- EXPECT_EQ(SPV_MSG_ERROR, level);
- EXPECT_STREQ("input", source);
- EXPECT_EQ(0u, position.line);
- EXPECT_EQ(0u, position.column);
- EXPECT_EQ(1u, position.index);
- EXPECT_STREQ("Invalid opcode: 65535", message);
- });
- words.push_back(0xffffffff); // Certainly invalid instruction header.
- EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
- EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
- spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
- invoke_header, invoke_instruction, nullptr));
- EXPECT_EQ(1, invocation);
- }
- TEST_F(BinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForGoodParse) {
- const auto words = CompileSuccessfully("");
- auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
- int invocation = 0;
- ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
- const spv_position_t&,
- const char*) { ++invocation; });
- EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
- EXPECT_EQ(SPV_SUCCESS,
- spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
- invoke_header, invoke_instruction, &diagnostic_));
- EXPECT_EQ(0, invocation);
- EXPECT_EQ(nullptr, diagnostic_);
- }
- TEST_F(BinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForBadParse) {
- auto words = CompileSuccessfully("");
- auto ctx = spvtools::Context(SPV_ENV_UNIVERSAL_1_1);
- int invocation = 0;
- ctx.SetMessageConsumer([&invocation](spv_message_level_t, const char*,
- const spv_position_t&,
- const char*) { ++invocation; });
- words.push_back(0xffffffff); // Certainly invalid instruction header.
- EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
- EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
- spvBinaryParse(ctx.CContext(), &client_, words.data(), words.size(),
- invoke_header, invoke_instruction, &diagnostic_));
- EXPECT_EQ(0, invocation);
- EXPECT_STREQ("Invalid opcode: 65535", diagnostic_->error);
- }
- TEST_F(BinaryParseTest,
- ModuleWithSingleInstructionHasValidHeaderAndInstructionCallback) {
- for (bool endian_swap : kSwapEndians) {
- const auto words = CompileSuccessfully("%1 = OpTypeVoid");
- InSequence calls_expected_in_specific_order;
- EXPECT_HEADER(2).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
- .WillOnce(Return(SPV_SUCCESS));
- Parse(words, SPV_SUCCESS, endian_swap);
- EXPECT_EQ(nullptr, diagnostic_);
- }
- }
- TEST_F(BinaryParseTest, NullHeaderCallbackIsIgnored) {
- const auto words = CompileSuccessfully("%1 = OpTypeVoid");
- EXPECT_CALL(client_, Header(_, _, _, _, _, _))
- .Times(0); // No header callback.
- EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
- .WillOnce(Return(SPV_SUCCESS));
- EXPECT_EQ(SPV_SUCCESS, spvBinaryParse(ScopedContext().context, &client_,
- words.data(), words.size(), nullptr,
- invoke_instruction, &diagnostic_));
- EXPECT_EQ(nullptr, diagnostic_);
- }
- TEST_F(BinaryParseTest, NullInstructionCallbackIsIgnored) {
- const auto words = CompileSuccessfully("%1 = OpTypeVoid");
- EXPECT_HEADER((2)).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback.
- EXPECT_EQ(SPV_SUCCESS,
- spvBinaryParse(ScopedContext().context, &client_, words.data(),
- words.size(), invoke_header, nullptr, &diagnostic_));
- EXPECT_EQ(nullptr, diagnostic_);
- }
- // Check the result of multiple instruction callbacks.
- //
- // This test exercises non-default values for the following members of the
- // spv_parsed_instruction_t struct: words, num_words, opcode, result_id,
- // operands, num_operands.
- TEST_F(BinaryParseTest, TwoScalarTypesGenerateTwoInstructionCallbacks) {
- for (bool endian_swap : kSwapEndians) {
- const auto words = CompileSuccessfully(
- "%1 = OpTypeVoid "
- "%2 = OpTypeInt 32 1");
- InSequence calls_expected_in_specific_order;
- EXPECT_HEADER(3).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
- .WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2)))
- .WillOnce(Return(SPV_SUCCESS));
- Parse(words, SPV_SUCCESS, endian_swap);
- EXPECT_EQ(nullptr, diagnostic_);
- }
- }
- TEST_F(BinaryParseTest, EarlyReturnWithZeroPassingCallbacks) {
- for (bool endian_swap : kSwapEndians) {
- const auto words = CompileSuccessfully(
- "%1 = OpTypeVoid "
- "%2 = OpTypeInt 32 1");
- InSequence calls_expected_in_specific_order;
- EXPECT_HEADER(3).WillOnce(Return(SPV_ERROR_INVALID_BINARY));
- // Early exit means no calls to Instruction().
- EXPECT_CALL(client_, Instruction(_)).Times(0);
- Parse(words, SPV_ERROR_INVALID_BINARY, endian_swap);
- // On error, the binary parser doesn't generate its own diagnostics.
- EXPECT_EQ(nullptr, diagnostic_);
- }
- }
- TEST_F(BinaryParseTest,
- EarlyReturnWithZeroPassingCallbacksAndSpecifiedResultCode) {
- for (bool endian_swap : kSwapEndians) {
- const auto words = CompileSuccessfully(
- "%1 = OpTypeVoid "
- "%2 = OpTypeInt 32 1");
- InSequence calls_expected_in_specific_order;
- EXPECT_HEADER(3).WillOnce(Return(SPV_REQUESTED_TERMINATION));
- // Early exit means no calls to Instruction().
- EXPECT_CALL(client_, Instruction(_)).Times(0);
- Parse(words, SPV_REQUESTED_TERMINATION, endian_swap);
- // On early termination, the binary parser doesn't generate its own
- // diagnostics.
- EXPECT_EQ(nullptr, diagnostic_);
- }
- }
- TEST_F(BinaryParseTest, EarlyReturnWithOnePassingCallback) {
- for (bool endian_swap : kSwapEndians) {
- const auto words = CompileSuccessfully(
- "%1 = OpTypeVoid "
- "%2 = OpTypeInt 32 1 "
- "%3 = OpTypeFloat 32");
- InSequence calls_expected_in_specific_order;
- EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
- .WillOnce(Return(SPV_REQUESTED_TERMINATION));
- Parse(words, SPV_REQUESTED_TERMINATION, endian_swap);
- // On early termination, the binary parser doesn't generate its own
- // diagnostics.
- EXPECT_EQ(nullptr, diagnostic_);
- }
- }
- TEST_F(BinaryParseTest, EarlyReturnWithTwoPassingCallbacks) {
- for (bool endian_swap : kSwapEndians) {
- const auto words = CompileSuccessfully(
- "%1 = OpTypeVoid "
- "%2 = OpTypeInt 32 1 "
- "%3 = OpTypeFloat 32");
- InSequence calls_expected_in_specific_order;
- EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1)))
- .WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2)))
- .WillOnce(Return(SPV_REQUESTED_TERMINATION));
- Parse(words, SPV_REQUESTED_TERMINATION, endian_swap);
- // On early termination, the binary parser doesn't generate its own
- // diagnostics.
- EXPECT_EQ(nullptr, diagnostic_);
- }
- }
- TEST_F(BinaryParseTest, InstructionWithStringOperand) {
- const std::string str =
- "the future is already here, it's just not evenly distributed";
- const auto str_words = MakeVector(str);
- const auto instruction = MakeInstruction(SpvOpName, {99}, str_words);
- const auto words = Concatenate({ExpectedHeaderForBound(100), instruction});
- InSequence calls_expected_in_specific_order;
- EXPECT_HEADER(100).WillOnce(Return(SPV_SUCCESS));
- const auto operands = std::vector<spv_parsed_operand_t>{
- MakeSimpleOperand(1, SPV_OPERAND_TYPE_ID),
- MakeLiteralStringOperand(2, static_cast<uint16_t>(str_words.size()))};
- EXPECT_CALL(client_,
- Instruction(ParsedInstruction(spv_parsed_instruction_t{
- instruction.data(), static_cast<uint16_t>(instruction.size()),
- SpvOpName, SPV_EXT_INST_TYPE_NONE, 0 /*type id*/,
- 0 /* No result id for OpName*/, operands.data(),
- static_cast<uint16_t>(operands.size())})))
- .WillOnce(Return(SPV_SUCCESS));
- // Since we are actually checking the output, don't test the
- // endian-swapped version.
- Parse(words, SPV_SUCCESS, false);
- EXPECT_EQ(nullptr, diagnostic_);
- }
- // Checks for non-zero values for the result_id and ext_inst_type members
- // spv_parsed_instruction_t.
- TEST_F(BinaryParseTest, ExtendedInstruction) {
- const auto words = CompileSuccessfully(
- "%extcl = OpExtInstImport \"OpenCL.std\" "
- "%result = OpExtInst %float %extcl sqrt %x");
- EXPECT_HEADER(5).WillOnce(Return(SPV_SUCCESS));
- EXPECT_CALL(client_, Instruction(_)).WillOnce(Return(SPV_SUCCESS));
- // We're only interested in the second call to Instruction():
- const auto operands = std::vector<spv_parsed_operand_t>{
- MakeSimpleOperand(1, SPV_OPERAND_TYPE_TYPE_ID),
- MakeSimpleOperand(2, SPV_OPERAND_TYPE_RESULT_ID),
- MakeSimpleOperand(3,
- SPV_OPERAND_TYPE_ID), // Extended instruction set Id
- MakeSimpleOperand(4, SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER),
- MakeSimpleOperand(5, SPV_OPERAND_TYPE_ID), // Id of the argument
- };
- const auto instruction = MakeInstruction(
- SpvOpExtInst,
- {2, 3, 1, static_cast<uint32_t>(OpenCLLIB::Entrypoints::Sqrt), 4});
- EXPECT_CALL(client_,
- Instruction(ParsedInstruction(spv_parsed_instruction_t{
- instruction.data(), static_cast<uint16_t>(instruction.size()),
- SpvOpExtInst, SPV_EXT_INST_TYPE_OPENCL_STD, 2 /*type id*/,
- 3 /*result id*/, operands.data(),
- static_cast<uint16_t>(operands.size())})))
- .WillOnce(Return(SPV_SUCCESS));
- // Since we are actually checking the output, don't test the
- // endian-swapped version.
- Parse(words, SPV_SUCCESS, false);
- EXPECT_EQ(nullptr, diagnostic_);
- }
- // A binary parser diagnostic test case where we provide the words array
- // pointer and word count explicitly.
- struct WordsAndCountDiagnosticCase {
- const uint32_t* words;
- size_t num_words;
- std::string expected_diagnostic;
- };
- using BinaryParseWordsAndCountDiagnosticTest = spvtest::TextToBinaryTestBase<
- ::testing::TestWithParam<WordsAndCountDiagnosticCase>>;
- TEST_P(BinaryParseWordsAndCountDiagnosticTest, WordAndCountCases) {
- EXPECT_EQ(
- SPV_ERROR_INVALID_BINARY,
- spvBinaryParse(ScopedContext().context, nullptr, GetParam().words,
- GetParam().num_words, nullptr, nullptr, &diagnostic));
- ASSERT_NE(nullptr, diagnostic);
- EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
- }
- INSTANTIATE_TEST_SUITE_P(
- BinaryParseDiagnostic, BinaryParseWordsAndCountDiagnosticTest,
- ::testing::ValuesIn(std::vector<WordsAndCountDiagnosticCase>{
- {nullptr, 0, "Missing module."},
- {kHeaderForBound1, 0,
- "Module has incomplete header: only 0 words instead of 5"},
- {kHeaderForBound1, 1,
- "Module has incomplete header: only 1 words instead of 5"},
- {kHeaderForBound1, 2,
- "Module has incomplete header: only 2 words instead of 5"},
- {kHeaderForBound1, 3,
- "Module has incomplete header: only 3 words instead of 5"},
- {kHeaderForBound1, 4,
- "Module has incomplete header: only 4 words instead of 5"},
- }));
- // A binary parser diagnostic test case where a vector of words is
- // provided. We'll use this to express cases that can't be created
- // via the assembler. Either we want to make a malformed instruction,
- // or an invalid case the assembler would reject.
- struct WordVectorDiagnosticCase {
- std::vector<uint32_t> words;
- std::string expected_diagnostic;
- };
- using BinaryParseWordVectorDiagnosticTest = spvtest::TextToBinaryTestBase<
- ::testing::TestWithParam<WordVectorDiagnosticCase>>;
- TEST_P(BinaryParseWordVectorDiagnosticTest, WordVectorCases) {
- const auto& words = GetParam().words;
- EXPECT_THAT(spvBinaryParse(ScopedContext().context, nullptr, words.data(),
- words.size(), nullptr, nullptr, &diagnostic),
- AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID));
- ASSERT_NE(nullptr, diagnostic);
- EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
- }
- INSTANTIATE_TEST_SUITE_P(
- BinaryParseDiagnostic, BinaryParseWordVectorDiagnosticTest,
- ::testing::ValuesIn(std::vector<WordVectorDiagnosticCase>{
- {Concatenate({ExpectedHeaderForBound(1), {spvOpcodeMake(0, SpvOpNop)}}),
- "Invalid instruction word count: 0"},
- {Concatenate(
- {ExpectedHeaderForBound(1),
- {spvOpcodeMake(1, static_cast<SpvOp>(
- std::numeric_limits<uint16_t>::max()))}}),
- "Invalid opcode: 65535"},
- {Concatenate({ExpectedHeaderForBound(1),
- MakeInstruction(SpvOpNop, {42})}),
- "Invalid instruction OpNop starting at word 5: expected "
- "no more operands after 1 words, but stated word count is 2."},
- // Supply several more unexpectd words.
- {Concatenate({ExpectedHeaderForBound(1),
- MakeInstruction(SpvOpNop, {42, 43, 44, 45, 46, 47})}),
- "Invalid instruction OpNop starting at word 5: expected "
- "no more operands after 1 words, but stated word count is 7."},
- {Concatenate({ExpectedHeaderForBound(1),
- MakeInstruction(SpvOpTypeVoid, {1, 2})}),
- "Invalid instruction OpTypeVoid starting at word 5: expected "
- "no more operands after 2 words, but stated word count is 3."},
- {Concatenate({ExpectedHeaderForBound(1),
- MakeInstruction(SpvOpTypeVoid, {1, 2, 5, 9, 10})}),
- "Invalid instruction OpTypeVoid starting at word 5: expected "
- "no more operands after 2 words, but stated word count is 6."},
- {Concatenate({ExpectedHeaderForBound(1),
- MakeInstruction(SpvOpTypeInt, {1, 32, 1, 9})}),
- "Invalid instruction OpTypeInt starting at word 5: expected "
- "no more operands after 4 words, but stated word count is 5."},
- {Concatenate({ExpectedHeaderForBound(1),
- MakeInstruction(SpvOpTypeInt, {1})}),
- "End of input reached while decoding OpTypeInt starting at word 5:"
- " expected more operands after 2 words."},
- // Check several cases for running off the end of input.
- // Detect a missing single word operand.
- {Concatenate({ExpectedHeaderForBound(1),
- {spvOpcodeMake(2, SpvOpTypeStruct)}}),
- "End of input reached while decoding OpTypeStruct starting at word"
- " 5: missing result ID operand at word offset 1."},
- // Detect this a missing a multi-word operand to OpConstant.
- // We also lie and say the OpConstant instruction has 5 words when
- // it only has 3. Corresponds to something like this:
- // %1 = OpTypeInt 64 0
- // %2 = OpConstant %1 <missing>
- {Concatenate({ExpectedHeaderForBound(3),
- {MakeInstruction(SpvOpTypeInt, {1, 64, 0})},
- {spvOpcodeMake(5, SpvOpConstant), 1, 2}}),
- "End of input reached while decoding OpConstant starting at word"
- " 9: missing possibly multi-word literal number operand at word "
- "offset 3."},
- // Detect when we provide only one word from the 64-bit literal,
- // and again lie about the number of words in the instruction.
- {Concatenate({ExpectedHeaderForBound(3),
- {MakeInstruction(SpvOpTypeInt, {1, 64, 0})},
- {spvOpcodeMake(5, SpvOpConstant), 1, 2, 42}}),
- "End of input reached while decoding OpConstant starting at word"
- " 9: truncated possibly multi-word literal number operand at word "
- "offset 3."},
- // Detect when a required string operand is missing.
- // Also, lie about the length of the instruction.
- {Concatenate({ExpectedHeaderForBound(3),
- {spvOpcodeMake(3, SpvOpString), 1}}),
- "End of input reached while decoding OpString starting at word"
- " 5: missing literal string operand at word offset 2."},
- // Detect when a required string operand is truncated: it's missing
- // a null terminator. Catching the error avoids a buffer overrun.
- {Concatenate({ExpectedHeaderForBound(3),
- {spvOpcodeMake(4, SpvOpString), 1, 0x41414141,
- 0x41414141}}),
- "End of input reached while decoding OpString starting at word"
- " 5: truncated literal string operand at word offset 2."},
- // Detect when an optional string operand is truncated: it's missing
- // a null terminator. Catching the error avoids a buffer overrun.
- // (It is valid for an optional string operand to be absent.)
- {Concatenate({ExpectedHeaderForBound(3),
- {spvOpcodeMake(6, SpvOpSource),
- static_cast<uint32_t>(SpvSourceLanguageOpenCL_C), 210,
- 1 /* file id */,
- /*start of string*/ 0x41414141, 0x41414141}}),
- "End of input reached while decoding OpSource starting at word"
- " 5: truncated literal string operand at word offset 4."},
- // (End of input exhaustion test cases.)
- // In this case the instruction word count is too small, where
- // it would truncate a multi-word operand to OpConstant.
- {Concatenate({ExpectedHeaderForBound(3),
- {MakeInstruction(SpvOpTypeInt, {1, 64, 0})},
- {spvOpcodeMake(4, SpvOpConstant), 1, 2, 44, 44}}),
- "Invalid word count: OpConstant starting at word 9 says it has 4"
- " words, but found 5 words instead."},
- // Word count is to small, where it would truncate a literal string.
- {Concatenate({ExpectedHeaderForBound(2),
- {spvOpcodeMake(3, SpvOpString), 1, 0x41414141, 0}}),
- "Invalid word count: OpString starting at word 5 says it has 3"
- " words, but found 4 words instead."},
- // Word count is too large. The string terminates before the last
- // word.
- {Concatenate({ExpectedHeaderForBound(2),
- {spvOpcodeMake(4, SpvOpString), 1 /* result id */},
- MakeVector("abc"),
- {0 /* this word does not belong*/}}),
- "Invalid instruction OpString starting at word 5: expected no more"
- " operands after 3 words, but stated word count is 4."},
- // Word count is too large. There are too many words after the string
- // literal. A linkage attribute decoration is the only case in SPIR-V
- // where a string operand is followed by another operand.
- {Concatenate({ExpectedHeaderForBound(2),
- {spvOpcodeMake(6, SpvOpDecorate), 1 /* target id */,
- static_cast<uint32_t>(SpvDecorationLinkageAttributes)},
- MakeVector("abc"),
- {static_cast<uint32_t>(SpvLinkageTypeImport),
- 0 /* does not belong */}}),
- "Invalid instruction OpDecorate starting at word 5: expected no more"
- " operands after 5 words, but stated word count is 6."},
- // Like the previous case, but with 5 extra words.
- {Concatenate({ExpectedHeaderForBound(2),
- {spvOpcodeMake(10, SpvOpDecorate), 1 /* target id */,
- static_cast<uint32_t>(SpvDecorationLinkageAttributes)},
- MakeVector("abc"),
- {static_cast<uint32_t>(SpvLinkageTypeImport),
- /* don't belong */ 0, 1, 2, 3, 4}}),
- "Invalid instruction OpDecorate starting at word 5: expected no more"
- " operands after 5 words, but stated word count is 10."},
- // Like the previous two cases, but with OpMemberDecorate.
- {Concatenate({ExpectedHeaderForBound(2),
- {spvOpcodeMake(7, SpvOpMemberDecorate), 1 /* target id */,
- 42 /* member index */,
- static_cast<uint32_t>(SpvDecorationLinkageAttributes)},
- MakeVector("abc"),
- {static_cast<uint32_t>(SpvLinkageTypeImport),
- 0 /* does not belong */}}),
- "Invalid instruction OpMemberDecorate starting at word 5: expected no"
- " more operands after 6 words, but stated word count is 7."},
- {Concatenate({ExpectedHeaderForBound(2),
- {spvOpcodeMake(11, SpvOpMemberDecorate),
- 1 /* target id */, 42 /* member index */,
- static_cast<uint32_t>(SpvDecorationLinkageAttributes)},
- MakeVector("abc"),
- {static_cast<uint32_t>(SpvLinkageTypeImport),
- /* don't belong */ 0, 1, 2, 3, 4}}),
- "Invalid instruction OpMemberDecorate starting at word 5: expected no"
- " more operands after 6 words, but stated word count is 11."},
- // Word count is too large. There should be no more words
- // after the RelaxedPrecision decoration.
- {Concatenate({ExpectedHeaderForBound(2),
- {spvOpcodeMake(4, SpvOpDecorate), 1 /* target id */,
- static_cast<uint32_t>(SpvDecorationRelaxedPrecision),
- 0 /* does not belong */}}),
- "Invalid instruction OpDecorate starting at word 5: expected no"
- " more operands after 3 words, but stated word count is 4."},
- // Word count is too large. There should be only one word after
- // the SpecId decoration enum word.
- {Concatenate({ExpectedHeaderForBound(2),
- {spvOpcodeMake(5, SpvOpDecorate), 1 /* target id */,
- static_cast<uint32_t>(SpvDecorationSpecId),
- 42 /* the spec id */, 0 /* does not belong */}}),
- "Invalid instruction OpDecorate starting at word 5: expected no"
- " more operands after 4 words, but stated word count is 5."},
- {Concatenate({ExpectedHeaderForBound(2),
- {spvOpcodeMake(2, SpvOpTypeVoid), 0}}),
- "Error: Result Id is 0"},
- {Concatenate({
- ExpectedHeaderForBound(2),
- {spvOpcodeMake(2, SpvOpTypeVoid), 1},
- {spvOpcodeMake(2, SpvOpTypeBool), 1},
- }),
- "Id 1 is defined more than once"},
- {Concatenate({ExpectedHeaderForBound(3),
- MakeInstruction(SpvOpExtInst, {2, 3, 100, 4, 5})}),
- "OpExtInst set Id 100 does not reference an OpExtInstImport result "
- "Id"},
- {Concatenate({ExpectedHeaderForBound(101),
- MakeInstruction(SpvOpExtInstImport, {100},
- MakeVector("OpenCL.std")),
- // OpenCL cos is #14
- MakeInstruction(SpvOpExtInst, {2, 3, 100, 14, 5, 999})}),
- "Invalid instruction OpExtInst starting at word 10: expected no "
- "more operands after 6 words, but stated word count is 7."},
- // In this case, the OpSwitch selector refers to an invalid ID.
- {Concatenate({ExpectedHeaderForBound(3),
- MakeInstruction(SpvOpSwitch, {1, 2, 42, 3})}),
- "Invalid OpSwitch: selector id 1 has no type"},
- // In this case, the OpSwitch selector refers to an ID that has
- // no type.
- {Concatenate({ExpectedHeaderForBound(3),
- MakeInstruction(SpvOpLabel, {1}),
- MakeInstruction(SpvOpSwitch, {1, 2, 42, 3})}),
- "Invalid OpSwitch: selector id 1 has no type"},
- {Concatenate({ExpectedHeaderForBound(3),
- MakeInstruction(SpvOpTypeInt, {1, 32, 0}),
- MakeInstruction(SpvOpSwitch, {1, 3, 42, 3})}),
- "Invalid OpSwitch: selector id 1 is a type, not a value"},
- {Concatenate({ExpectedHeaderForBound(3),
- MakeInstruction(SpvOpTypeFloat, {1, 32}),
- MakeInstruction(SpvOpConstant, {1, 2, 0x78f00000}),
- MakeInstruction(SpvOpSwitch, {2, 3, 42, 3})}),
- "Invalid OpSwitch: selector id 2 is not a scalar integer"},
- {Concatenate({ExpectedHeaderForBound(3),
- MakeInstruction(SpvOpExtInstImport, {1},
- MakeVector("invalid-import"))}),
- "Invalid extended instruction import 'invalid-import'"},
- {Concatenate({
- ExpectedHeaderForBound(3),
- MakeInstruction(SpvOpTypeInt, {1, 32, 0}),
- MakeInstruction(SpvOpConstant, {2, 2, 42}),
- }),
- "Type Id 2 is not a type"},
- {Concatenate({
- ExpectedHeaderForBound(3),
- MakeInstruction(SpvOpTypeBool, {1}),
- MakeInstruction(SpvOpConstant, {1, 2, 42}),
- }),
- "Type Id 1 is not a scalar numeric type"},
- }));
- // A binary parser diagnostic case generated from an assembly text input.
- struct AssemblyDiagnosticCase {
- std::string assembly;
- std::string expected_diagnostic;
- };
- using BinaryParseAssemblyDiagnosticTest = spvtest::TextToBinaryTestBase<
- ::testing::TestWithParam<AssemblyDiagnosticCase>>;
- TEST_P(BinaryParseAssemblyDiagnosticTest, AssemblyCases) {
- auto words = CompileSuccessfully(GetParam().assembly);
- EXPECT_THAT(spvBinaryParse(ScopedContext().context, nullptr, words.data(),
- words.size(), nullptr, nullptr, &diagnostic),
- AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID));
- ASSERT_NE(nullptr, diagnostic);
- EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
- }
- INSTANTIATE_TEST_SUITE_P(
- BinaryParseDiagnostic, BinaryParseAssemblyDiagnosticTest,
- ::testing::ValuesIn(std::vector<AssemblyDiagnosticCase>{
- {"%1 = OpConstant !0 42", "Error: Type Id is 0"},
- // A required id is 0.
- {"OpName !0 \"foo\"", "Id is 0"},
- // An optional id is 0, in this case the optional
- // initializer.
- {"%2 = OpVariable %1 CrossWorkgroup !0", "Id is 0"},
- {"OpControlBarrier !0 %1 %2", "scope ID is 0"},
- {"OpControlBarrier %1 !0 %2", "scope ID is 0"},
- {"OpControlBarrier %1 %2 !0", "memory semantics ID is 0"},
- {"%import = OpExtInstImport \"GLSL.std.450\" "
- "%result = OpExtInst %type %import !999999 %x",
- "Invalid extended instruction number: 999999"},
- {"%2 = OpSpecConstantOp %1 !1000 %2",
- "Invalid OpSpecConstantOp opcode: 1000"},
- {"OpCapability !9999", "Invalid capability operand: 9999"},
- {"OpSource !9999 100", "Invalid source language operand: 9999"},
- {"OpEntryPoint !9999", "Invalid execution model operand: 9999"},
- {"OpMemoryModel !9999", "Invalid addressing model operand: 9999"},
- {"OpMemoryModel Logical !9999", "Invalid memory model operand: 9999"},
- {"OpExecutionMode %1 !9999", "Invalid execution mode operand: 9999"},
- {"OpTypeForwardPointer %1 !9999",
- "Invalid storage class operand: 9999"},
- {"%2 = OpTypeImage %1 !9999", "Invalid dimensionality operand: 9999"},
- {"%2 = OpTypeImage %1 1D 0 0 0 0 !9999",
- "Invalid image format operand: 9999"},
- {"OpDecorate %1 FPRoundingMode !9999",
- "Invalid floating-point rounding mode operand: 9999"},
- {"OpDecorate %1 LinkageAttributes \"C\" !9999",
- "Invalid linkage type operand: 9999"},
- {"%1 = OpTypePipe !9999", "Invalid access qualifier operand: 9999"},
- {"OpDecorate %1 FuncParamAttr !9999",
- "Invalid function parameter attribute operand: 9999"},
- {"OpDecorate %1 !9999", "Invalid decoration operand: 9999"},
- {"OpDecorate %1 BuiltIn !9999", "Invalid built-in operand: 9999"},
- {"%2 = OpGroupIAdd %1 %3 !9999",
- "Invalid group operation operand: 9999"},
- {"OpDecorate %1 FPFastMathMode !63",
- "Invalid floating-point fast math mode operand: 63 has invalid mask "
- "component 32"},
- {"%2 = OpFunction %2 !31",
- "Invalid function control operand: 31 has invalid mask component 16"},
- {"OpLoopMerge %1 %2 !1027",
- "Invalid loop control operand: 1027 has invalid mask component 1024"},
- {"%2 = OpImageFetch %1 %image %coord !32770",
- "Invalid image operand: 32770 has invalid mask component 32768"},
- {"OpSelectionMerge %1 !7",
- "Invalid selection control operand: 7 has invalid mask component 4"},
- }));
- } // namespace
- } // namespace spvtools
|