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