1 // Copyright (c) 2016 Google 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 #ifndef TEST_OPT_PASS_FIXTURE_H_ 16 #define TEST_OPT_PASS_FIXTURE_H_ 17 18 #include <iostream> 19 #include <memory> 20 #include <string> 21 #include <tuple> 22 #include <utility> 23 #include <vector> 24 25 #include "effcee/effcee.h" 26 #include "gtest/gtest.h" 27 #include "source/opt/build_module.h" 28 #include "source/opt/pass_manager.h" 29 #include "source/opt/passes.h" 30 #include "source/spirv_validator_options.h" 31 #include "source/util/make_unique.h" 32 #include "spirv-tools/libspirv.hpp" 33 34 namespace spvtools { 35 namespace opt { 36 37 // Template class for testing passes. It contains some handy utility methods for 38 // running passes and checking results. 39 // 40 // To write value-Parameterized tests: 41 // using ValueParamTest = PassTest<::testing::TestWithParam<std::string>>; 42 // To use as normal fixture: 43 // using FixtureTest = PassTest<::testing::Test>; 44 template <typename TestT> 45 class PassTest : public TestT { 46 public: PassTest()47 PassTest() 48 : consumer_( 49 [](spv_message_level_t, const char*, const spv_position_t&, 50 const char* message) { std::cerr << message << std::endl; }), 51 context_(nullptr), 52 tools_(SPV_ENV_UNIVERSAL_1_3), 53 manager_(new PassManager()), 54 assemble_options_(SpirvTools::kDefaultAssembleOption), 55 disassemble_options_(SpirvTools::kDefaultDisassembleOption) {} 56 57 // Runs the given |pass| on the binary assembled from the |original|. 58 // Returns a tuple of the optimized binary and the boolean value returned 59 // from pass Process() function. OptimizeToBinary(Pass * pass,const std::string & original,bool skip_nop)60 std::tuple<std::vector<uint32_t>, Pass::Status> OptimizeToBinary( 61 Pass* pass, const std::string& original, bool skip_nop) { 62 context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_3, consumer_, original, 63 assemble_options_)); 64 EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n" 65 << original << std::endl; 66 if (!context()) { 67 return std::make_tuple(std::vector<uint32_t>(), Pass::Status::Failure); 68 } 69 70 const auto status = pass->Run(context()); 71 72 std::vector<uint32_t> binary; 73 context()->module()->ToBinary(&binary, skip_nop); 74 return std::make_tuple(binary, status); 75 } 76 77 // Runs a single pass of class |PassT| on the binary assembled from the 78 // |assembly|. Returns a tuple of the optimized binary and the boolean value 79 // from the pass Process() function. 80 template <typename PassT, typename... Args> SinglePassRunToBinary(const std::string & assembly,bool skip_nop,Args &&...args)81 std::tuple<std::vector<uint32_t>, Pass::Status> SinglePassRunToBinary( 82 const std::string& assembly, bool skip_nop, Args&&... args) { 83 auto pass = MakeUnique<PassT>(std::forward<Args>(args)...); 84 pass->SetMessageConsumer(consumer_); 85 return OptimizeToBinary(pass.get(), assembly, skip_nop); 86 } 87 88 // Runs a single pass of class |PassT| on the binary assembled from the 89 // |assembly|, disassembles the optimized binary. Returns a tuple of 90 // disassembly string and the boolean value from the pass Process() function. 91 template <typename PassT, typename... Args> SinglePassRunAndDisassemble(const std::string & assembly,bool skip_nop,bool do_validation,Args &&...args)92 std::tuple<std::string, Pass::Status> SinglePassRunAndDisassemble( 93 const std::string& assembly, bool skip_nop, bool do_validation, 94 Args&&... args) { 95 std::vector<uint32_t> optimized_bin; 96 auto status = Pass::Status::SuccessWithoutChange; 97 std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>( 98 assembly, skip_nop, std::forward<Args>(args)...); 99 if (do_validation) { 100 spv_target_env target_env = SPV_ENV_UNIVERSAL_1_3; 101 spv_context spvContext = spvContextCreate(target_env); 102 spv_diagnostic diagnostic = nullptr; 103 spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()}; 104 spv_result_t error = spvValidateWithOptions( 105 spvContext, ValidatorOptions(), &binary, &diagnostic); 106 EXPECT_EQ(error, 0); 107 if (error != 0) spvDiagnosticPrint(diagnostic); 108 spvDiagnosticDestroy(diagnostic); 109 spvContextDestroy(spvContext); 110 } 111 std::string optimized_asm; 112 EXPECT_TRUE( 113 tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_)) 114 << "Disassembling failed for shader:\n" 115 << assembly << std::endl; 116 return std::make_tuple(optimized_asm, status); 117 } 118 119 // Runs a single pass of class |PassT| on the binary assembled from the 120 // |original| assembly, and checks whether the optimized binary can be 121 // disassembled to the |expected| assembly. Optionally will also validate 122 // the optimized binary. This does *not* involve pass manager. Callers 123 // are suggested to use SCOPED_TRACE() for better messages. 124 template <typename PassT, typename... Args> SinglePassRunAndCheck(const std::string & original,const std::string & expected,bool skip_nop,bool do_validation,Args &&...args)125 void SinglePassRunAndCheck(const std::string& original, 126 const std::string& expected, bool skip_nop, 127 bool do_validation, Args&&... args) { 128 std::vector<uint32_t> optimized_bin; 129 auto status = Pass::Status::SuccessWithoutChange; 130 std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>( 131 original, skip_nop, std::forward<Args>(args)...); 132 // Check whether the pass returns the correct modification indication. 133 EXPECT_NE(Pass::Status::Failure, status); 134 EXPECT_EQ(original == expected, 135 status == Pass::Status::SuccessWithoutChange); 136 if (do_validation) { 137 spv_target_env target_env = SPV_ENV_UNIVERSAL_1_3; 138 spv_context spvContext = spvContextCreate(target_env); 139 spv_diagnostic diagnostic = nullptr; 140 spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()}; 141 spv_result_t error = spvValidateWithOptions( 142 spvContext, ValidatorOptions(), &binary, &diagnostic); 143 EXPECT_EQ(error, 0); 144 if (error != 0) spvDiagnosticPrint(diagnostic); 145 spvDiagnosticDestroy(diagnostic); 146 spvContextDestroy(spvContext); 147 } 148 std::string optimized_asm; 149 EXPECT_TRUE( 150 tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_)) 151 << "Disassembling failed for shader:\n" 152 << original << std::endl; 153 EXPECT_EQ(expected, optimized_asm); 154 } 155 156 // Runs a single pass of class |PassT| on the binary assembled from the 157 // |original| assembly, and checks whether the optimized binary can be 158 // disassembled to the |expected| assembly. This does *not* involve pass 159 // manager. Callers are suggested to use SCOPED_TRACE() for better messages. 160 template <typename PassT, typename... Args> SinglePassRunAndCheck(const std::string & original,const std::string & expected,bool skip_nop,Args &&...args)161 void SinglePassRunAndCheck(const std::string& original, 162 const std::string& expected, bool skip_nop, 163 Args&&... args) { 164 SinglePassRunAndCheck<PassT>(original, expected, skip_nop, false, 165 std::forward<Args>(args)...); 166 } 167 168 // Runs a single pass of class |PassT| on the binary assembled from the 169 // |original| assembly, then runs an Effcee matcher over the disassembled 170 // result, using checks parsed from |original|. Always skips OpNop. 171 // This does *not* involve pass manager. Callers are suggested to use 172 // SCOPED_TRACE() for better messages. 173 template <typename PassT, typename... Args> SinglePassRunAndMatch(const std::string & original,bool do_validation,Args &&...args)174 void SinglePassRunAndMatch(const std::string& original, bool do_validation, 175 Args&&... args) { 176 const bool skip_nop = true; 177 auto pass_result = SinglePassRunAndDisassemble<PassT>( 178 original, skip_nop, do_validation, std::forward<Args>(args)...); 179 auto disassembly = std::get<0>(pass_result); 180 auto match_result = effcee::Match(disassembly, original); 181 EXPECT_EQ(effcee::Result::Status::Ok, match_result.status()) 182 << match_result.message() << "\nChecking result:\n" 183 << disassembly; 184 } 185 186 // Adds a pass to be run. 187 template <typename PassT, typename... Args> AddPass(Args &&...args)188 void AddPass(Args&&... args) { 189 manager_->AddPass<PassT>(std::forward<Args>(args)...); 190 } 191 192 // Renews the pass manager, including clearing all previously added passes. RenewPassManger()193 void RenewPassManger() { 194 manager_ = MakeUnique<PassManager>(); 195 manager_->SetMessageConsumer(consumer_); 196 } 197 198 // Runs the passes added thus far using a pass manager on the binary assembled 199 // from the |original| assembly, and checks whether the optimized binary can 200 // be disassembled to the |expected| assembly. Callers are suggested to use 201 // SCOPED_TRACE() for better messages. RunAndCheck(const std::string & original,const std::string & expected)202 void RunAndCheck(const std::string& original, const std::string& expected) { 203 assert(manager_->NumPasses()); 204 205 context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, original, 206 assemble_options_)); 207 ASSERT_NE(nullptr, context()); 208 209 manager_->Run(context()); 210 211 std::vector<uint32_t> binary; 212 context()->module()->ToBinary(&binary, /* skip_nop = */ false); 213 214 std::string optimized; 215 EXPECT_TRUE(tools_.Disassemble(binary, &optimized, disassemble_options_)); 216 EXPECT_EQ(expected, optimized); 217 } 218 SetAssembleOptions(uint32_t assemble_options)219 void SetAssembleOptions(uint32_t assemble_options) { 220 assemble_options_ = assemble_options; 221 } 222 SetDisassembleOptions(uint32_t disassemble_options)223 void SetDisassembleOptions(uint32_t disassemble_options) { 224 disassemble_options_ = disassemble_options; 225 } 226 consumer()227 MessageConsumer consumer() { return consumer_; } context()228 IRContext* context() { return context_.get(); } 229 SetMessageConsumer(MessageConsumer msg_consumer)230 void SetMessageConsumer(MessageConsumer msg_consumer) { 231 consumer_ = msg_consumer; 232 } 233 ValidatorOptions()234 spv_validator_options ValidatorOptions() { return &validator_options_; } 235 236 private: 237 MessageConsumer consumer_; // Message consumer. 238 std::unique_ptr<IRContext> context_; // IR context 239 SpirvTools tools_; // An instance for calling SPIRV-Tools functionalities. 240 std::unique_ptr<PassManager> manager_; // The pass manager. 241 uint32_t assemble_options_; 242 uint32_t disassemble_options_; 243 spv_validator_options_t validator_options_; 244 }; 245 246 } // namespace opt 247 } // namespace spvtools 248 249 #endif // TEST_OPT_PASS_FIXTURE_H_ 250