| // Copyright (c) 2016 Google 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 <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "spirv-tools/optimizer.hpp" | |
| #include "spirv/unified1/spirv.hpp11" | |
| namespace spvtools { | |
| namespace { | |
| using ::testing::ContainerEq; | |
| using ::testing::HasSubstr; | |
| // Return a string that contains the minimum instructions needed to form | |
| // a valid module. Other instructions can be appended to this string. | |
| std::string Header() { | |
| return R"(OpCapability Shader | |
| OpCapability Linkage | |
| OpMemoryModel Logical GLSL450 | |
| )"; | |
| } | |
| // When we assemble with a target environment of SPIR-V 1.1, we expect | |
| // the following in the module header version word. | |
| const uint32_t kExpectedSpvVersion = 0x10100; | |
| TEST(CppInterface, SuccessfulRoundTrip) { | |
| const std::string input_text = "%2 = OpSizeOf %1 %3\n"; | |
| SpirvTools t(SPV_ENV_UNIVERSAL_1_1); | |
| std::vector<uint32_t> binary; | |
| EXPECT_TRUE(t.Assemble(input_text, &binary)); | |
| EXPECT_TRUE(binary.size() > 5u); | |
| EXPECT_EQ(spv::MagicNumber, binary[0]); | |
| EXPECT_EQ(kExpectedSpvVersion, binary[1]); | |
| // This cannot pass validation since %1 is not defined. | |
| t.SetMessageConsumer([](spv_message_level_t level, const char* source, | |
| const spv_position_t& position, const char* message) { | |
| 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("ID '1[%1]' has not been defined\n %2 = OpSizeOf %1 %3\n", | |
| message); | |
| }); | |
| EXPECT_FALSE(t.Validate(binary)); | |
| std::string output_text; | |
| EXPECT_TRUE(t.Disassemble(binary, &output_text)); | |
| EXPECT_EQ(input_text, output_text); | |
| } | |
| TEST(CppInterface, AssembleEmptyModule) { | |
| std::vector<uint32_t> binary(10, 42); | |
| SpirvTools t(SPV_ENV_UNIVERSAL_1_1); | |
| EXPECT_TRUE(t.Assemble("", &binary)); | |
| // We only have the header. | |
| EXPECT_EQ(5u, binary.size()); | |
| EXPECT_EQ(spv::MagicNumber, binary[0]); | |
| EXPECT_EQ(kExpectedSpvVersion, binary[1]); | |
| } | |
| TEST(CppInterface, AssembleOverloads) { | |
| const std::string input_text = "%2 = OpSizeOf %1 %3\n"; | |
| SpirvTools t(SPV_ENV_UNIVERSAL_1_1); | |
| { | |
| std::vector<uint32_t> binary; | |
| EXPECT_TRUE(t.Assemble(input_text, &binary)); | |
| EXPECT_TRUE(binary.size() > 5u); | |
| EXPECT_EQ(spv::MagicNumber, binary[0]); | |
| EXPECT_EQ(kExpectedSpvVersion, binary[1]); | |
| } | |
| { | |
| std::vector<uint32_t> binary; | |
| EXPECT_TRUE(t.Assemble(input_text.data(), input_text.size(), &binary)); | |
| EXPECT_TRUE(binary.size() > 5u); | |
| EXPECT_EQ(spv::MagicNumber, binary[0]); | |
| EXPECT_EQ(kExpectedSpvVersion, binary[1]); | |
| } | |
| { // Ignore the last newline. | |
| std::vector<uint32_t> binary; | |
| EXPECT_TRUE(t.Assemble(input_text.data(), input_text.size() - 1, &binary)); | |
| EXPECT_TRUE(binary.size() > 5u); | |
| EXPECT_EQ(spv::MagicNumber, binary[0]); | |
| EXPECT_EQ(kExpectedSpvVersion, binary[1]); | |
| } | |
| } | |
| TEST(CppInterface, DisassembleEmptyModule) { | |
| std::string text(10, 'x'); | |
| SpirvTools t(SPV_ENV_UNIVERSAL_1_1); | |
| int invocation_count = 0; | |
| t.SetMessageConsumer( | |
| [&invocation_count](spv_message_level_t level, const char* source, | |
| const spv_position_t& position, const char* message) { | |
| ++invocation_count; | |
| EXPECT_EQ(SPV_MSG_ERROR, level); | |
| EXPECT_STREQ("input", source); | |
| EXPECT_EQ(0u, position.line); | |
| EXPECT_EQ(0u, position.column); | |
| EXPECT_EQ(0u, position.index); | |
| EXPECT_STREQ("Missing module.", message); | |
| }); | |
| EXPECT_FALSE(t.Disassemble({}, &text)); | |
| EXPECT_EQ("xxxxxxxxxx", text); // The original string is unmodified. | |
| EXPECT_EQ(1, invocation_count); | |
| } | |
| TEST(CppInterface, DisassembleOverloads) { | |
| const std::string input_text = "%2 = OpSizeOf %1 %3\n"; | |
| SpirvTools t(SPV_ENV_UNIVERSAL_1_1); | |
| std::vector<uint32_t> binary; | |
| EXPECT_TRUE(t.Assemble(input_text, &binary)); | |
| { | |
| std::string output_text; | |
| EXPECT_TRUE(t.Disassemble(binary, &output_text)); | |
| EXPECT_EQ(input_text, output_text); | |
| } | |
| { | |
| std::string output_text; | |
| EXPECT_TRUE(t.Disassemble(binary.data(), binary.size(), &output_text)); | |
| EXPECT_EQ(input_text, output_text); | |
| } | |
| } | |
| TEST(CppInterface, SuccessfulValidation) { | |
| SpirvTools t(SPV_ENV_UNIVERSAL_1_1); | |
| int invocation_count = 0; | |
| t.SetMessageConsumer([&invocation_count](spv_message_level_t, const char*, | |
| const spv_position_t&, const char*) { | |
| ++invocation_count; | |
| }); | |
| std::vector<uint32_t> binary; | |
| EXPECT_TRUE(t.Assemble(Header(), &binary)); | |
| EXPECT_TRUE(t.Validate(binary)); | |
| EXPECT_EQ(0, invocation_count); | |
| } | |
| TEST(CppInterface, ValidateOverloads) { | |
| SpirvTools t(SPV_ENV_UNIVERSAL_1_1); | |
| std::vector<uint32_t> binary; | |
| EXPECT_TRUE(t.Assemble(Header(), &binary)); | |
| { EXPECT_TRUE(t.Validate(binary)); } | |
| { EXPECT_TRUE(t.Validate(binary.data(), binary.size())); } | |
| } | |
| TEST(CppInterface, ValidateEmptyModule) { | |
| SpirvTools t(SPV_ENV_UNIVERSAL_1_1); | |
| int invocation_count = 0; | |
| t.SetMessageConsumer( | |
| [&invocation_count](spv_message_level_t level, const char* source, | |
| const spv_position_t& position, const char* message) { | |
| ++invocation_count; | |
| EXPECT_EQ(SPV_MSG_ERROR, level); | |
| EXPECT_STREQ("input", source); | |
| EXPECT_EQ(0u, position.line); | |
| EXPECT_EQ(0u, position.column); | |
| EXPECT_EQ(0u, position.index); | |
| EXPECT_STREQ("Invalid SPIR-V magic number.", message); | |
| }); | |
| EXPECT_FALSE(t.Validate({})); | |
| EXPECT_EQ(1, invocation_count); | |
| } | |
| // Returns the assembly for a SPIR-V module with a struct declaration | |
| // with the given number of members. | |
| std::string MakeModuleHavingStruct(int num_members) { | |
| std::stringstream os; | |
| os << Header(); | |
| os << R"(%1 = OpTypeInt 32 0 | |
| %2 = OpTypeStruct)"; | |
| for (int i = 0; i < num_members; i++) os << " %1"; | |
| return os.str(); | |
| } | |
| TEST(CppInterface, ValidateWithOptionsPass) { | |
| SpirvTools t(SPV_ENV_UNIVERSAL_1_1); | |
| std::vector<uint32_t> binary; | |
| EXPECT_TRUE(t.Assemble(MakeModuleHavingStruct(10), &binary)); | |
| const ValidatorOptions opts; | |
| EXPECT_TRUE(t.Validate(binary.data(), binary.size(), opts)); | |
| } | |
| TEST(CppInterface, ValidateWithOptionsFail) { | |
| SpirvTools t(SPV_ENV_UNIVERSAL_1_1); | |
| std::vector<uint32_t> binary; | |
| EXPECT_TRUE(t.Assemble(MakeModuleHavingStruct(10), &binary)); | |
| ValidatorOptions opts; | |
| opts.SetUniversalLimit(spv_validator_limit_max_struct_members, 9); | |
| std::stringstream os; | |
| t.SetMessageConsumer([&os](spv_message_level_t, const char*, | |
| const spv_position_t&, | |
| const char* message) { os << message; }); | |
| EXPECT_FALSE(t.Validate(binary.data(), binary.size(), opts)); | |
| EXPECT_THAT( | |
| os.str(), | |
| HasSubstr( | |
| "Number of OpTypeStruct members (10) has exceeded the limit (9)")); | |
| } | |
| // Checks that after running the given optimizer |opt| on the given |original| | |
| // source code, we can get the given |optimized| source code. | |
| void CheckOptimization(const std::string& original, | |
| const std::string& optimized, const Optimizer& opt) { | |
| SpirvTools t(SPV_ENV_UNIVERSAL_1_1); | |
| std::vector<uint32_t> original_binary; | |
| ASSERT_TRUE(t.Assemble(original, &original_binary)); | |
| std::vector<uint32_t> optimized_binary; | |
| EXPECT_TRUE(opt.Run(original_binary.data(), original_binary.size(), | |
| &optimized_binary)); | |
| std::string optimized_text; | |
| EXPECT_TRUE(t.Disassemble(optimized_binary, &optimized_text)); | |
| EXPECT_EQ(optimized, optimized_text); | |
| } | |
| TEST(CppInterface, OptimizeEmptyModule) { | |
| SpirvTools t(SPV_ENV_UNIVERSAL_1_1); | |
| std::vector<uint32_t> binary; | |
| EXPECT_TRUE(t.Assemble("", &binary)); | |
| Optimizer o(SPV_ENV_UNIVERSAL_1_1); | |
| o.RegisterPass(CreateStripDebugInfoPass()); | |
| // Fails to validate. | |
| EXPECT_FALSE(o.Run(binary.data(), binary.size(), &binary)); | |
| } | |
| TEST(CppInterface, OptimizeModifiedModule) { | |
| Optimizer o(SPV_ENV_UNIVERSAL_1_1); | |
| o.RegisterPass(CreateStripDebugInfoPass()); | |
| CheckOptimization(Header() + "OpSource GLSL 450", Header(), o); | |
| } | |
| TEST(CppInterface, OptimizeMulitplePasses) { | |
| std::string original_text = Header() + | |
| "OpSource GLSL 450 " | |
| "OpDecorate %true SpecId 1 " | |
| "%bool = OpTypeBool " | |
| "%true = OpSpecConstantTrue %bool"; | |
| Optimizer o(SPV_ENV_UNIVERSAL_1_1); | |
| o.RegisterPass(CreateStripDebugInfoPass()) | |
| .RegisterPass(CreateFreezeSpecConstantValuePass()); | |
| std::string expected_text = Header() + | |
| "%bool = OpTypeBool\n" | |
| "%true = OpConstantTrue %bool\n"; | |
| CheckOptimization(original_text, expected_text, o); | |
| } | |
| TEST(CppInterface, OptimizeDoNothingWithPassToken) { | |
| CreateFreezeSpecConstantValuePass(); | |
| auto token = CreateUnifyConstantPass(); | |
| } | |
| TEST(CppInterface, OptimizeReassignPassToken) { | |
| auto token = CreateNullPass(); | |
| token = CreateStripDebugInfoPass(); | |
| CheckOptimization( | |
| Header() + "OpSource GLSL 450", Header(), | |
| Optimizer(SPV_ENV_UNIVERSAL_1_1).RegisterPass(std::move(token))); | |
| } | |
| TEST(CppInterface, OptimizeMoveConstructPassToken) { | |
| auto token1 = CreateStripDebugInfoPass(); | |
| Optimizer::PassToken token2(std::move(token1)); | |
| CheckOptimization( | |
| Header() + "OpSource GLSL 450", Header(), | |
| Optimizer(SPV_ENV_UNIVERSAL_1_1).RegisterPass(std::move(token2))); | |
| } | |
| TEST(CppInterface, OptimizeMoveAssignPassToken) { | |
| auto token1 = CreateStripDebugInfoPass(); | |
| auto token2 = CreateNullPass(); | |
| token2 = std::move(token1); | |
| CheckOptimization( | |
| Header() + "OpSource GLSL 450", Header(), | |
| Optimizer(SPV_ENV_UNIVERSAL_1_1).RegisterPass(std::move(token2))); | |
| } | |
| TEST(CppInterface, OptimizeSameAddressForOriginalOptimizedBinary) { | |
| SpirvTools t(SPV_ENV_UNIVERSAL_1_1); | |
| std::vector<uint32_t> binary; | |
| ASSERT_TRUE(t.Assemble(Header() + "OpSource GLSL 450", &binary)); | |
| EXPECT_TRUE(Optimizer(SPV_ENV_UNIVERSAL_1_1) | |
| .RegisterPass(CreateStripDebugInfoPass()) | |
| .Run(binary.data(), binary.size(), &binary)); | |
| std::string optimized_text; | |
| EXPECT_TRUE(t.Disassemble(binary, &optimized_text)); | |
| EXPECT_EQ(Header(), optimized_text); | |
| } | |
| TEST(SpirvHeadersCpp, BitwiseOrMemoryAccessMask) { | |
| EXPECT_EQ(spv::MemoryAccessMask(6), spv::MemoryAccessMask::Aligned | | |
| spv::MemoryAccessMask::Nontemporal); | |
| } | |
| TEST(SpirvHeadersCpp, BitwiseAndMemoryAccessMask) { | |
| EXPECT_EQ(spv::MemoryAccessMask::Aligned, | |
| spv::MemoryAccessMask::Aligned & spv::MemoryAccessMask(6)); | |
| EXPECT_EQ(spv::MemoryAccessMask::Nontemporal, | |
| spv::MemoryAccessMask::Nontemporal & spv::MemoryAccessMask(6)); | |
| EXPECT_EQ(spv::MemoryAccessMask(0), spv::MemoryAccessMask::Nontemporal & | |
| spv::MemoryAccessMask::Aligned); | |
| } | |
| TEST(SpirvHeadersCpp, BitwiseXorMemoryAccessMask) { | |
| EXPECT_EQ(spv::MemoryAccessMask::Nontemporal, | |
| spv::MemoryAccessMask::Aligned ^ spv::MemoryAccessMask(6)); | |
| EXPECT_EQ(spv::MemoryAccessMask::Aligned, | |
| spv::MemoryAccessMask::Nontemporal ^ spv::MemoryAccessMask(6)); | |
| EXPECT_EQ(spv::MemoryAccessMask(6), spv::MemoryAccessMask::Nontemporal ^ | |
| spv::MemoryAccessMask::Aligned); | |
| EXPECT_EQ(spv::MemoryAccessMask(0), spv::MemoryAccessMask::Nontemporal ^ | |
| spv::MemoryAccessMask::Nontemporal); | |
| } | |
| TEST(SpirvHeadersCpp, BitwiseNegateMemoryAccessMask) { | |
| EXPECT_EQ(spv::MemoryAccessMask(~(uint32_t(4))), | |
| ~spv::MemoryAccessMask::Nontemporal); | |
| } | |
| // TODO(antiagainst): tests for SetMessageConsumer(). | |
| } // namespace | |
| } // namespace spvtools |