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