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