| // 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. | |
| #ifndef TEST_OPT_PASS_FIXTURE_H_ | |
| #define TEST_OPT_PASS_FIXTURE_H_ | |
| #include <iostream> | |
| #include <memory> | |
| #include <string> | |
| #include <tuple> | |
| #include <utility> | |
| #include <vector> | |
| #include "effcee/effcee.h" | |
| #include "gtest/gtest.h" | |
| #include "source/opt/build_module.h" | |
| #include "source/opt/pass_manager.h" | |
| #include "source/opt/passes.h" | |
| #include "source/spirv_optimizer_options.h" | |
| #include "source/spirv_validator_options.h" | |
| #include "source/util/make_unique.h" | |
| #include "spirv-tools/libspirv.hpp" | |
| namespace spvtools { | |
| namespace opt { | |
| // Template class for testing passes. It contains some handy utility methods for | |
| // running passes and checking results. | |
| // | |
| // To write value-Parameterized tests: | |
| // using ValueParamTest = PassTest<::testing::TestWithParam<std::string>>; | |
| // To use as normal fixture: | |
| // using FixtureTest = PassTest<::testing::Test>; | |
| template <typename TestT> | |
| class PassTest : public TestT { | |
| public: | |
| PassTest() | |
| : consumer_( | |
| [](spv_message_level_t, const char*, const spv_position_t&, | |
| const char* message) { std::cerr << message << std::endl; }), | |
| context_(nullptr), | |
| manager_(new PassManager()), | |
| assemble_options_(SpirvTools::kDefaultAssembleOption), | |
| disassemble_options_(SpirvTools::kDefaultDisassembleOption), | |
| env_(SPV_ENV_UNIVERSAL_1_3) {} | |
| // Runs the given |pass| on the binary assembled from the |original|. | |
| // Returns a tuple of the optimized binary and the boolean value returned | |
| // from pass Process() function. | |
| std::tuple<std::vector<uint32_t>, Pass::Status> OptimizeToBinary( | |
| Pass* pass, const std::string& original, bool skip_nop) { | |
| context_ = | |
| std::move(BuildModule(env_, consumer_, original, assemble_options_)); | |
| EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n" | |
| << original << std::endl; | |
| if (!context()) { | |
| return std::make_tuple(std::vector<uint32_t>(), Pass::Status::Failure); | |
| } | |
| context()->set_preserve_bindings(OptimizerOptions()->preserve_bindings_); | |
| context()->set_preserve_spec_constants( | |
| OptimizerOptions()->preserve_spec_constants_); | |
| const auto status = pass->Run(context()); | |
| std::vector<uint32_t> binary; | |
| if (status != Pass::Status::Failure) { | |
| context()->module()->ToBinary(&binary, skip_nop); | |
| } | |
| return std::make_tuple(binary, status); | |
| } | |
| // Runs a single pass of class |PassT| on the binary assembled from the | |
| // |assembly|. Returns a tuple of the optimized binary and the boolean value | |
| // from the pass Process() function. | |
| template <typename PassT, typename... Args> | |
| std::tuple<std::vector<uint32_t>, Pass::Status> SinglePassRunToBinary( | |
| const std::string& assembly, bool skip_nop, Args&&... args) { | |
| auto pass = MakeUnique<PassT>(std::forward<Args>(args)...); | |
| pass->SetMessageConsumer(consumer_); | |
| return OptimizeToBinary(pass.get(), assembly, skip_nop); | |
| } | |
| // Runs a single pass of class |PassT| on the binary assembled from the | |
| // |assembly|, disassembles the optimized binary. Returns a tuple of | |
| // disassembly string and the boolean value from the pass Process() function. | |
| template <typename PassT, typename... Args> | |
| std::tuple<std::string, Pass::Status> SinglePassRunAndDisassemble( | |
| const std::string& assembly, bool skip_nop, bool do_validation, | |
| Args&&... args) { | |
| std::vector<uint32_t> optimized_bin; | |
| auto status = Pass::Status::SuccessWithoutChange; | |
| std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>( | |
| assembly, skip_nop, std::forward<Args>(args)...); | |
| if (do_validation) { | |
| spv_context spvContext = spvContextCreate(env_); | |
| spv_diagnostic diagnostic = nullptr; | |
| spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()}; | |
| spv_result_t error = spvValidateWithOptions( | |
| spvContext, ValidatorOptions(), &binary, &diagnostic); | |
| EXPECT_EQ(error, 0); | |
| if (error != 0) spvDiagnosticPrint(diagnostic); | |
| spvDiagnosticDestroy(diagnostic); | |
| spvContextDestroy(spvContext); | |
| } | |
| std::string optimized_asm; | |
| SpirvTools tools(env_); | |
| EXPECT_TRUE( | |
| tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options_)) | |
| << "Disassembling failed for shader:\n" | |
| << assembly << std::endl; | |
| return std::make_tuple(optimized_asm, status); | |
| } | |
| // Runs a single pass of class |PassT| on the binary assembled from the | |
| // |original| assembly, and checks whether the optimized binary can be | |
| // disassembled to the |expected| assembly. Optionally will also validate | |
| // the optimized binary. This does *not* involve pass manager. Callers | |
| // are suggested to use SCOPED_TRACE() for better messages. | |
| template <typename PassT, typename... Args> | |
| void SinglePassRunAndCheck(const std::string& original, | |
| const std::string& expected, bool skip_nop, | |
| bool do_validation, Args&&... args) { | |
| std::vector<uint32_t> optimized_bin; | |
| auto status = Pass::Status::SuccessWithoutChange; | |
| std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>( | |
| original, skip_nop, std::forward<Args>(args)...); | |
| // Check whether the pass returns the correct modification indication. | |
| EXPECT_NE(Pass::Status::Failure, status); | |
| EXPECT_EQ(original == expected, | |
| status == Pass::Status::SuccessWithoutChange); | |
| if (do_validation) { | |
| spv_context spvContext = spvContextCreate(env_); | |
| spv_diagnostic diagnostic = nullptr; | |
| spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()}; | |
| spv_result_t error = spvValidateWithOptions( | |
| spvContext, ValidatorOptions(), &binary, &diagnostic); | |
| EXPECT_EQ(error, 0); | |
| if (error != 0) spvDiagnosticPrint(diagnostic); | |
| spvDiagnosticDestroy(diagnostic); | |
| spvContextDestroy(spvContext); | |
| } | |
| std::string optimized_asm; | |
| SpirvTools tools(env_); | |
| EXPECT_TRUE( | |
| tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options_)) | |
| << "Disassembling failed for shader:\n" | |
| << original << std::endl; | |
| EXPECT_EQ(expected, optimized_asm); | |
| } | |
| // Runs a single pass of class |PassT| on the binary assembled from the | |
| // |original| assembly, and checks whether the optimized binary can be | |
| // disassembled to the |expected| assembly. This does *not* involve pass | |
| // manager. Callers are suggested to use SCOPED_TRACE() for better messages. | |
| template <typename PassT, typename... Args> | |
| void SinglePassRunAndCheck(const std::string& original, | |
| const std::string& expected, bool skip_nop, | |
| Args&&... args) { | |
| SinglePassRunAndCheck<PassT>(original, expected, skip_nop, false, | |
| std::forward<Args>(args)...); | |
| } | |
| // Runs a single pass of class |PassT| on the binary assembled from the | |
| // |original| assembly, then runs an Effcee matcher over the disassembled | |
| // result, using checks parsed from |original|. Always skips OpNop. | |
| // This does *not* involve pass manager. Callers are suggested to use | |
| // SCOPED_TRACE() for better messages. | |
| template <typename PassT, typename... Args> | |
| void SinglePassRunAndMatch(const std::string& original, bool do_validation, | |
| Args&&... args) { | |
| const bool skip_nop = true; | |
| auto pass_result = SinglePassRunAndDisassemble<PassT>( | |
| original, skip_nop, do_validation, std::forward<Args>(args)...); | |
| auto disassembly = std::get<0>(pass_result); | |
| auto match_result = effcee::Match(disassembly, original); | |
| EXPECT_EQ(effcee::Result::Status::Ok, match_result.status()) | |
| << match_result.message() << "\nChecking result:\n" | |
| << disassembly; | |
| } | |
| // Runs a single pass of class |PassT| on the binary assembled from the | |
| // |original| assembly. Check for failure and expect an Effcee matcher | |
| // to pass when run on the diagnostic messages. This does *not* involve | |
| // pass manager. Callers are suggested to use SCOPED_TRACE() for better | |
| // messages. | |
| template <typename PassT, typename... Args> | |
| void SinglePassRunAndFail(const std::string& original, Args&&... args) { | |
| context_ = | |
| std::move(BuildModule(env_, consumer_, original, assemble_options_)); | |
| EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n" | |
| << original << std::endl; | |
| std::ostringstream errs; | |
| auto error_consumer = [&errs](spv_message_level_t, const char*, | |
| const spv_position_t&, const char* message) { | |
| errs << message << std::endl; | |
| }; | |
| auto pass = MakeUnique<PassT>(std::forward<Args>(args)...); | |
| pass->SetMessageConsumer(error_consumer); | |
| const auto status = pass->Run(context()); | |
| EXPECT_EQ(Pass::Status::Failure, status); | |
| auto match_result = effcee::Match(errs.str(), original); | |
| EXPECT_EQ(effcee::Result::Status::Ok, match_result.status()) | |
| << match_result.message() << "\nChecking messages:\n" | |
| << errs.str(); | |
| } | |
| // Adds a pass to be run. | |
| template <typename PassT, typename... Args> | |
| void AddPass(Args&&... args) { | |
| manager_->AddPass<PassT>(std::forward<Args>(args)...); | |
| } | |
| // Renews the pass manager, including clearing all previously added passes. | |
| void RenewPassManger() { | |
| manager_ = MakeUnique<PassManager>(); | |
| manager_->SetMessageConsumer(consumer_); | |
| } | |
| // Runs the passes added thus far using a pass manager on the binary assembled | |
| // from the |original| assembly, and checks whether the optimized binary can | |
| // be disassembled to the |expected| assembly. Callers are suggested to use | |
| // SCOPED_TRACE() for better messages. | |
| void RunAndCheck(const std::string& original, const std::string& expected) { | |
| assert(manager_->NumPasses()); | |
| context_ = | |
| std::move(BuildModule(env_, nullptr, original, assemble_options_)); | |
| ASSERT_NE(nullptr, context()); | |
| context()->set_preserve_bindings(OptimizerOptions()->preserve_bindings_); | |
| context()->set_preserve_spec_constants( | |
| OptimizerOptions()->preserve_spec_constants_); | |
| auto status = manager_->Run(context()); | |
| EXPECT_NE(status, Pass::Status::Failure); | |
| if (status != Pass::Status::Failure) { | |
| std::vector<uint32_t> binary; | |
| context()->module()->ToBinary(&binary, /* skip_nop = */ false); | |
| std::string optimized; | |
| SpirvTools tools(env_); | |
| EXPECT_TRUE(tools.Disassemble(binary, &optimized, disassemble_options_)); | |
| EXPECT_EQ(expected, optimized); | |
| } | |
| } | |
| void SetAssembleOptions(uint32_t assemble_options) { | |
| assemble_options_ = assemble_options; | |
| } | |
| void SetDisassembleOptions(uint32_t disassemble_options) { | |
| disassemble_options_ = disassemble_options; | |
| } | |
| MessageConsumer consumer() { return consumer_; } | |
| IRContext* context() { return context_.get(); } | |
| void SetMessageConsumer(MessageConsumer msg_consumer) { | |
| consumer_ = msg_consumer; | |
| } | |
| spv_optimizer_options OptimizerOptions() { return &optimizer_options_; } | |
| spv_validator_options ValidatorOptions() { return &validator_options_; } | |
| void SetTargetEnv(spv_target_env env) { env_ = env; } | |
| private: | |
| MessageConsumer consumer_; // Message consumer. | |
| std::unique_ptr<IRContext> context_; // IR context | |
| std::unique_ptr<PassManager> manager_; // The pass manager. | |
| uint32_t assemble_options_; | |
| uint32_t disassemble_options_; | |
| spv_optimizer_options_t optimizer_options_; | |
| spv_validator_options_t validator_options_; | |
| spv_target_env env_; | |
| }; | |
| } // namespace opt | |
| } // namespace spvtools | |
| #endif // TEST_OPT_PASS_FIXTURE_H_ |