SPIRV-Tools/test/BinaryToText.cpp

479 lines
18 KiB
C++
Raw Normal View History

2016-01-07 18:44:22 +00:00
// Copyright (c) 2015-2016 The Khronos Group Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and/or associated documentation files (the
// "Materials"), to deal in the Materials without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Materials, and to
// permit persons to whom the Materials are furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Materials.
//
// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
// https://www.khronos.org/registry/
//
// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
#include "UnitSPIRV.h"
#include <sstream>
#include "gmock/gmock.h"
#include "source/spirv_constant.h"
#include "TestFixture.h"
using ::testing::Eq;
using ::testing::HasSubstr;
using spvtest::AutoText;
2015-11-12 15:45:36 +00:00
using spvtest::TextToBinaryTest;
namespace {
class BinaryToText : public ::testing::Test {
public:
BinaryToText() : context(spvContextCreate()) {}
~BinaryToText() { spvContextDestroy(context); }
virtual void SetUp() {
const char* textStr = R"(
OpSource OpenCL_C 12
OpMemoryModel Physical64 OpenCL
OpSourceExtension "PlaceholderExtensionName"
Use opcode operand definitions from SPIR-V specification generator. The assembler and disassembler now use a dynamically adjusted sequence of expected operand types. (Internally, it is a deque, for readability.) Both parsers repeatedly pull an expected operand type from the left of this pattern list, and try to match the next input token against it. The expected pattern is adjusted during the parse to accommodate: - an extended instruction's expected operands, depending on the extended instruction's index. - when an operand itself has operands - to handle sequences of zero or more operands, or pairs of operands. These are expanded lazily during the parse. Adds spv::OperandClass from the SPIR-V specification generator. Modifies spv_operand_desc_t: - adds hasResult, hasType, and operandClass array to the opcode description type. - "wordCount" is replaced with "numTypes", which counts the number of entries in operandTypes. And each of those describes a *logical* operand, including the type id for the instruction, and the result id for the instruction. A logical operand could be variable-width, such as a literal string. Adds opcode.inc, an automatically-generated table of operation descriptions, with one line to describe each core instruction. Externally, we have modified the SPIR-V spec doc generator to emit this file. (We have hacked this copy to use the old semantics for OpLine.) Inside the assembler, parsing an operand may fail with new error code SPV_FAIL_MATCH. For an optional operand, this is not fatal, but should trigger backtracking at a higher level. The spvTextIsStartOfNewInst checks the case of the third letter of what might be an opcode. So now, "OpenCL" does not look like an opcode name. In assembly, the EntryPoint name field is mandatory, but can be an empty string. Adjust tests for changes to: - OpSampedImage - OpTypeSampler
2015-08-27 17:03:52 +00:00
OpEntryPoint Kernel %1 "foo"
OpExecutionMode %1 LocalSizeHint 1 1 1
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpTypeInt 8 0
%5 = OpTypeInt 8 1
%6 = OpTypeInt 16 0
%7 = OpTypeInt 16 1
%8 = OpTypeInt 32 0
%9 = OpTypeInt 32 1
%10 = OpTypeInt 64 0
%11 = OpTypeInt 64 1
%12 = OpTypeFloat 16
%13 = OpTypeFloat 32
%14 = OpTypeFloat 64
%15 = OpTypeVector %4 2
)";
spv_text_t text = {textStr, strlen(textStr)};
spv_diagnostic diagnostic = nullptr;
spv_result_t error =
spvTextToBinary(context, text.str, text.length, &binary, &diagnostic);
if (error) {
spvDiagnosticPrint(diagnostic);
spvDiagnosticDestroy(diagnostic);
ASSERT_EQ(SPV_SUCCESS, error);
}
}
virtual void TearDown() { spvBinaryDestroy(binary); }
// Compiles the given assembly text, and saves it into 'binary'.
void CompileSuccessfully(std::string text) {
spv_diagnostic diagnostic = nullptr;
EXPECT_EQ(SPV_SUCCESS, spvTextToBinary(context, text.c_str(), text.size(),
&binary, &diagnostic));
}
spv_context context;
spv_binary binary;
};
TEST_F(BinaryToText, Default) {
spv_text text = nullptr;
spv_diagnostic diagnostic = nullptr;
ASSERT_EQ(
SPV_SUCCESS,
spvBinaryToText(context, binary->code, binary->wordCount,
SPV_BINARY_TO_TEXT_OPTION_NONE, &text, &diagnostic));
printf("%s", text->str);
spvTextDestroy(text);
}
TEST_F(BinaryToText, MissingModule) {
spv_text text;
spv_diagnostic diagnostic = nullptr;
EXPECT_EQ(
SPV_ERROR_INVALID_BINARY,
spvBinaryToText(context, nullptr, 42, SPV_BINARY_TO_TEXT_OPTION_NONE,
&text, &diagnostic));
EXPECT_THAT(diagnostic->error, Eq(std::string("Missing module.")));
if (diagnostic) {
spvDiagnosticPrint(diagnostic);
spvDiagnosticDestroy(diagnostic);
}
}
TEST_F(BinaryToText, TruncatedModule) {
// Make a valid module with zero instructions.
CompileSuccessfully("");
EXPECT_EQ(SPV_INDEX_INSTRUCTION, binary->wordCount);
for (size_t length = 0; length < SPV_INDEX_INSTRUCTION; length++) {
spv_text text = nullptr;
spv_diagnostic diagnostic = nullptr;
EXPECT_EQ(
SPV_ERROR_INVALID_BINARY,
spvBinaryToText(context, binary->code, length,
SPV_BINARY_TO_TEXT_OPTION_NONE, &text, &diagnostic));
ASSERT_NE(nullptr, diagnostic);
std::stringstream expected;
expected << "Module has incomplete header: only " << length
<< " words instead of " << SPV_INDEX_INSTRUCTION;
EXPECT_THAT(diagnostic->error, Eq(expected.str()));
spvDiagnosticDestroy(diagnostic);
}
}
TEST_F(BinaryToText, InvalidMagicNumber) {
CompileSuccessfully("");
std::vector<uint32_t> damaged_binary(binary->code,
binary->code + binary->wordCount);
damaged_binary[SPV_INDEX_MAGIC_NUMBER] ^= 123;
spv_diagnostic diagnostic = nullptr;
spv_text text;
EXPECT_EQ(
SPV_ERROR_INVALID_BINARY,
spvBinaryToText(context, damaged_binary.data(), damaged_binary.size(),
SPV_BINARY_TO_TEXT_OPTION_NONE, &text, &diagnostic));
ASSERT_NE(nullptr, diagnostic);
std::stringstream expected;
expected << "Invalid SPIR-V magic number '" << std::hex
<< damaged_binary[SPV_INDEX_MAGIC_NUMBER] << "'.";
EXPECT_THAT(diagnostic->error, Eq(expected.str()));
spvDiagnosticDestroy(diagnostic);
}
TEST_F(BinaryToText, InvalidDiagnostic) {
spv_text text;
ASSERT_EQ(SPV_ERROR_INVALID_DIAGNOSTIC,
spvBinaryToText(context, binary->code, binary->wordCount,
SPV_BINARY_TO_TEXT_OPTION_NONE, &text, nullptr));
}
struct FailedDecodeCase {
std::string source_text;
std::vector<uint32_t> appended_instruction;
std::string expected_error_message;
};
using BinaryToTextFail =
spvtest::TextToBinaryTestBase<::testing::TestWithParam<FailedDecodeCase>>;
TEST_P(BinaryToTextFail, EncodeSuccessfullyDecodeFailed) {
EXPECT_THAT(EncodeSuccessfullyDecodeFailed(GetParam().source_text,
GetParam().appended_instruction),
Eq(GetParam().expected_error_message));
}
INSTANTIATE_TEST_CASE_P(
InvalidIds, BinaryToTextFail,
::testing::ValuesIn(std::vector<FailedDecodeCase>{
{"", spvtest::MakeInstruction(SpvOpTypeVoid, {0}),
"Error: Result Id is 0"},
{"", spvtest::MakeInstruction(SpvOpConstant, {0, 1, 42}),
"Error: Type Id is 0"},
{"%1 = OpTypeVoid", spvtest::MakeInstruction(SpvOpTypeVoid, {1}),
"Id 1 is defined more than once"},
{"%1 = OpTypeVoid\n"
"%2 = OpNot %1 %foo",
spvtest::MakeInstruction(SpvOpNot, {1, 2, 3}),
"Id 2 is defined more than once"},
{"%1 = OpTypeVoid\n"
"%2 = OpNot %1 %foo",
spvtest::MakeInstruction(SpvOpNot, {1, 1, 3}),
"Id 1 is defined more than once"},
// The following are the two failure cases for
// Parser::setNumericTypeInfoForType.
{"", spvtest::MakeInstruction(SpvOpConstant, {500, 1, 42}),
"Type Id 500 is not a type"},
{"%1 = OpTypeInt 32 0\n"
"%2 = OpTypeVector %1 4",
spvtest::MakeInstruction(SpvOpConstant, {2, 3, 999}),
"Type Id 2 is not a scalar numeric type"},
}));
INSTANTIATE_TEST_CASE_P(
InvalidIdsCheckedDuringLiteralCaseParsing, BinaryToTextFail,
::testing::ValuesIn(std::vector<FailedDecodeCase>{
{"", spvtest::MakeInstruction(SpvOpSwitch, {1, 2, 3, 4}),
"Invalid OpSwitch: selector id 1 has no type"},
{"%1 = OpTypeVoid\n",
spvtest::MakeInstruction(SpvOpSwitch, {1, 2, 3, 4}),
"Invalid OpSwitch: selector id 1 is a type, not a value"},
{"%1 = OpConstantTrue !500",
spvtest::MakeInstruction(SpvOpSwitch, {1, 2, 3, 4}),
"Type Id 500 is not a type"},
{"%1 = OpTypeFloat 32\n%2 = OpConstant %1 1.5",
spvtest::MakeInstruction(SpvOpSwitch, {2, 3, 4, 5}),
"Invalid OpSwitch: selector id 2 is not a scalar integer"},
}));
2015-11-12 15:45:36 +00:00
TEST_F(TextToBinaryTest, OneInstruction) {
const std::string input = "OpSource OpenCL_C 12\n";
EXPECT_EQ(input, EncodeAndDecodeSuccessfully(input));
}
// Exercise the case where an operand itself has operands.
// This could detect problems in updating the expected-set-of-operands
// list.
2015-11-12 15:45:36 +00:00
TEST_F(TextToBinaryTest, OperandWithOperands) {
const std::string input = R"(OpEntryPoint Kernel %1 "foo"
OpExecutionMode %1 LocalSizeHint 100 200 300
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%1 = OpFunction %1 None %3
)";
2015-11-12 15:45:36 +00:00
EXPECT_EQ(input, EncodeAndDecodeSuccessfully(input));
}
using RoundTripInstructionsTest =
spvtest::TextToBinaryTestBase<::testing::TestWithParam<std::string>>;
TEST_P(RoundTripInstructionsTest, Sample) {
EXPECT_THAT(EncodeAndDecodeSuccessfully(GetParam()), Eq(GetParam()));
};
// clang-format off
INSTANTIATE_TEST_CASE_P(
MemoryAccessMasks, RoundTripInstructionsTest,
::testing::ValuesIn(std::vector<std::string>{
"OpStore %1 %2\n", // 3 words long.
"OpStore %1 %2 None\n", // 4 words long, explicit final 0.
"OpStore %1 %2 Volatile\n",
"OpStore %1 %2 Aligned 8\n",
"OpStore %1 %2 Nontemporal\n",
// Combinations show the names from LSB to MSB
"OpStore %1 %2 Volatile|Aligned 16\n",
"OpStore %1 %2 Volatile|Nontemporal\n",
"OpStore %1 %2 Volatile|Aligned|Nontemporal 32\n",
}));
// clang-format on
INSTANTIATE_TEST_CASE_P(
FPFastMathModeMasks, RoundTripInstructionsTest,
::testing::ValuesIn(std::vector<std::string>{
"OpDecorate %1 FPFastMathMode None\n",
"OpDecorate %1 FPFastMathMode NotNaN\n",
"OpDecorate %1 FPFastMathMode NotInf\n",
"OpDecorate %1 FPFastMathMode NSZ\n",
"OpDecorate %1 FPFastMathMode AllowRecip\n",
"OpDecorate %1 FPFastMathMode Fast\n",
// Combinations show the names from LSB to MSB
"OpDecorate %1 FPFastMathMode NotNaN|NotInf\n",
"OpDecorate %1 FPFastMathMode NSZ|AllowRecip\n",
"OpDecorate %1 FPFastMathMode NotNaN|NotInf|NSZ|AllowRecip|Fast\n",
}));
INSTANTIATE_TEST_CASE_P(LoopControlMasks, RoundTripInstructionsTest,
::testing::ValuesIn(std::vector<std::string>{
"OpLoopMerge %1 %2 None\n",
"OpLoopMerge %1 %2 Unroll\n",
"OpLoopMerge %1 %2 DontUnroll\n",
"OpLoopMerge %1 %2 Unroll|DontUnroll\n",
}));
INSTANTIATE_TEST_CASE_P(SelectionControlMasks, RoundTripInstructionsTest,
::testing::ValuesIn(std::vector<std::string>{
"OpSelectionMerge %1 None\n",
"OpSelectionMerge %1 Flatten\n",
"OpSelectionMerge %1 DontFlatten\n",
"OpSelectionMerge %1 Flatten|DontFlatten\n",
}));
// clang-format off
INSTANTIATE_TEST_CASE_P(
FunctionControlMasks, RoundTripInstructionsTest,
::testing::ValuesIn(std::vector<std::string>{
"%2 = OpFunction %1 None %3\n",
"%2 = OpFunction %1 Inline %3\n",
"%2 = OpFunction %1 DontInline %3\n",
"%2 = OpFunction %1 Pure %3\n",
"%2 = OpFunction %1 Const %3\n",
"%2 = OpFunction %1 Inline|Pure|Const %3\n",
"%2 = OpFunction %1 DontInline|Const %3\n",
}));
// clang-format on
// clang-format off
INSTANTIATE_TEST_CASE_P(
ImageMasks, RoundTripInstructionsTest,
::testing::ValuesIn(std::vector<std::string>{
"%2 = OpImageFetch %1 %3 %4\n",
"%2 = OpImageFetch %1 %3 %4 None\n",
"%2 = OpImageFetch %1 %3 %4 Bias %5\n",
"%2 = OpImageFetch %1 %3 %4 Lod %5\n",
"%2 = OpImageFetch %1 %3 %4 Grad %5 %6\n",
"%2 = OpImageFetch %1 %3 %4 ConstOffset %5\n",
"%2 = OpImageFetch %1 %3 %4 Offset %5\n",
"%2 = OpImageFetch %1 %3 %4 ConstOffsets %5\n",
"%2 = OpImageFetch %1 %3 %4 Sample %5\n",
"%2 = OpImageFetch %1 %3 %4 MinLod %5\n",
"%2 = OpImageFetch %1 %3 %4 Bias|Lod|Grad %5 %6 %7 %8\n",
"%2 = OpImageFetch %1 %3 %4 ConstOffset|Offset|ConstOffsets"
" %5 %6 %7\n",
"%2 = OpImageFetch %1 %3 %4 Sample|MinLod %5 %6\n",
"%2 = OpImageFetch %1 %3 %4"
" Bias|Lod|Grad|ConstOffset|Offset|ConstOffsets|Sample|MinLod"
" %5 %6 %7 %8 %9 %10 %11 %12 %13\n"}));
// clang-format on
2015-11-12 15:45:36 +00:00
using MaskSorting = TextToBinaryTest;
TEST_F(MaskSorting, MasksAreSortedFromLSBToMSB) {
EXPECT_THAT(EncodeAndDecodeSuccessfully(
"OpStore %1 %2 Nontemporal|Aligned|Volatile 32"),
Eq("OpStore %1 %2 Volatile|Aligned|Nontemporal 32\n"));
EXPECT_THAT(
EncodeAndDecodeSuccessfully(
"OpDecorate %1 FPFastMathMode NotInf|Fast|AllowRecip|NotNaN|NSZ"),
Eq("OpDecorate %1 FPFastMathMode NotNaN|NotInf|NSZ|AllowRecip|Fast\n"));
EXPECT_THAT(
EncodeAndDecodeSuccessfully("OpLoopMerge %1 %2 DontUnroll|Unroll"),
Eq("OpLoopMerge %1 %2 Unroll|DontUnroll\n"));
EXPECT_THAT(
EncodeAndDecodeSuccessfully("OpSelectionMerge %1 DontFlatten|Flatten"),
Eq("OpSelectionMerge %1 Flatten|DontFlatten\n"));
EXPECT_THAT(EncodeAndDecodeSuccessfully(
"%2 = OpFunction %1 DontInline|Const|Pure|Inline %3"),
Eq("%2 = OpFunction %1 Inline|DontInline|Pure|Const %3\n"));
EXPECT_THAT(EncodeAndDecodeSuccessfully(
"%2 = OpImageFetch %1 %3 %4"
" MinLod|Sample|Offset|Lod|Grad|ConstOffsets|ConstOffset|Bias"
" %5 %6 %7 %8 %9 %10 %11 %12 %13\n"),
Eq("%2 = OpImageFetch %1 %3 %4"
" Bias|Lod|Grad|ConstOffset|Offset|ConstOffsets|Sample|MinLod"
" %5 %6 %7 %8 %9 %10 %11 %12 %13\n"));
}
2015-11-12 15:45:36 +00:00
using OperandTypeTest = TextToBinaryTest;
TEST_F(OperandTypeTest, OptionalTypedLiteralNumber) {
const std::string input =
"%1 = OpTypeInt 32 0\n"
"%2 = OpConstant %1 42\n"
"OpSwitch %2 %3 100 %4\n";
EXPECT_EQ(input, EncodeAndDecodeSuccessfully(input));
}
using IndentTest = spvtest::TextToBinaryTest;
TEST_F(IndentTest, Sample) {
const std::string input = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
%1 = OpTypeInt 32 0
%2 = OpTypeStruct %1 %3 %4 %5 %6 %7 %8 %9 %10 ; force IDs into double digits
%11 = OpConstant %1 42
OpStore %2 %3 Aligned|Volatile 4 ; bogus, but not indented
)";
const std::string expected =
R"( OpCapability Shader
OpMemoryModel Logical GLSL450
%1 = OpTypeInt 32 0
%2 = OpTypeStruct %1 %3 %4 %5 %6 %7 %8 %9 %10
%11 = OpConstant %1 42
OpStore %2 %3 Volatile|Aligned 4
)";
EXPECT_THAT(
EncodeAndDecodeSuccessfully(input, SPV_BINARY_TO_TEXT_OPTION_INDENT),
expected);
}
TEST_F(TextToBinaryTest, ShowByteOffsetsWhenRequested) {
const std::string input = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
%1 = OpTypeInt 32 0
%2 = OpTypeVoid
)";
const std::string expected =
R"(OpCapability Shader ; 0x00000014
OpMemoryModel Logical GLSL450 ; 0x0000001c
%1 = OpTypeInt 32 0 ; 0x00000028
%2 = OpTypeVoid ; 0x00000038
)";
EXPECT_THAT(EncodeAndDecodeSuccessfully(
input, SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET),
expected);
}
// Test version string.
TEST_F(TextToBinaryTest, VersionString) {
auto words = CompileSuccessfully("");
spv_text decoded_text = nullptr;
EXPECT_THAT(spvBinaryToText(context, words.data(), words.size(),
SPV_BINARY_TO_TEXT_OPTION_NONE, &decoded_text,
&diagnostic),
Eq(SPV_SUCCESS));
EXPECT_EQ(nullptr, diagnostic);
EXPECT_EQ(1, SPV_SPIRV_VERSION_MAJOR);
EXPECT_EQ(0, SPV_SPIRV_VERSION_MINOR);
EXPECT_THAT(decoded_text->str, HasSubstr("Version: 1.0\n"))
<< EncodeAndDecodeSuccessfully("");
spvTextDestroy(decoded_text);
}
// Test generator string.
// A test case for the generator string. This allows us to
// test both of the 16-bit components of the generator word.
struct GeneratorStringCase {
uint16_t generator;
uint16_t misc;
std::string expected;
};
using GeneratorStringTest = spvtest::TextToBinaryTestBase<
::testing::TestWithParam<GeneratorStringCase>>;
TEST_P(GeneratorStringTest, Sample) {
auto words = CompileSuccessfully("");
EXPECT_EQ(2u, SPV_INDEX_GENERATOR_NUMBER);
words[SPV_INDEX_GENERATOR_NUMBER] =
SPV_GENERATOR_WORD(GetParam().generator, GetParam().misc);
spv_text decoded_text = nullptr;
EXPECT_THAT(spvBinaryToText(context, words.data(), words.size(),
SPV_BINARY_TO_TEXT_OPTION_NONE, &decoded_text,
&diagnostic),
Eq(SPV_SUCCESS));
EXPECT_THAT(diagnostic, Eq(nullptr));
EXPECT_THAT(std::string(decoded_text->str), HasSubstr(GetParam().expected));
spvTextDestroy(decoded_text);
}
INSTANTIATE_TEST_CASE_P(GeneratorStrings, GeneratorStringTest,
::testing::ValuesIn(std::vector<GeneratorStringCase>{
{SPV_GENERATOR_KHRONOS, 12, "Khronos; 12"},
{SPV_GENERATOR_LUNARG, 99, "LunarG; 99"},
{SPV_GENERATOR_VALVE, 1, "Valve; 1"},
{SPV_GENERATOR_CODEPLAY, 65535,
"Codeplay Software Ltd.; 65535"},
{SPV_GENERATOR_NVIDIA, 19, "NVIDIA; 19"},
{SPV_GENERATOR_ARM, 1000, "ARM; 1000"},
{SPV_GENERATOR_KHRONOS_LLVM_TRANSLATOR, 38,
"Khronos LLVM/SPIR-V Translator; 38"},
{SPV_GENERATOR_KHRONOS_ASSEMBLER, 2,
"Khronos SPIR-V Tools Assembler; 2"},
2015-11-13 18:03:28 +00:00
{SPV_GENERATOR_KHRONOS_GLSLANG, 1,
"Khronos Glslang Reference Front End; 1"},
{9, 18, "Unknown(9); 18"},
{65535, 32767, "Unknown(65535); 32767"},
}));
} // anonymous namespace