• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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