• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2018 Google LLC
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 #include <cassert>
16 #include <cerrno>
17 #include <cstring>
18 #include <functional>
19 
20 #include "source/opt/build_module.h"
21 #include "source/opt/ir_context.h"
22 #include "source/opt/log.h"
23 #include "source/reduce/operand_to_const_reduction_pass.h"
24 #include "source/reduce/operand_to_dominating_id_reduction_pass.h"
25 #include "source/reduce/reducer.h"
26 #include "source/reduce/remove_opname_instruction_reduction_pass.h"
27 #include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
28 #include "source/reduce/structured_loop_to_selection_reduction_pass.h"
29 #include "source/spirv_reducer_options.h"
30 #include "source/util/make_unique.h"
31 #include "source/util/string_utils.h"
32 #include "spirv-tools/libspirv.hpp"
33 #include "tools/io.h"
34 #include "tools/util/cli_consumer.h"
35 
36 using namespace spvtools::reduce;
37 
38 namespace {
39 
40 using ErrorOrInt = std::pair<std::string, int>;
41 
42 // Check that the std::system function can actually be used.
CheckExecuteCommand()43 bool CheckExecuteCommand() {
44   int res = std::system(nullptr);
45   return res != 0;
46 }
47 
48 // Execute a command using the shell.
49 // Returns true if and only if the command's exit status was 0.
ExecuteCommand(const std::string & command)50 bool ExecuteCommand(const std::string& command) {
51   errno = 0;
52   int status = std::system(command.c_str());
53   assert(errno == 0 && "failed to execute command");
54   // The result returned by 'system' is implementation-defined, but is
55   // usually the case that the returned value is 0 when the command's exit
56   // code was 0.  We are assuming that here, and that's all we depend on.
57   return status == 0;
58 }
59 
60 // Status and actions to perform after parsing command-line arguments.
61 enum ReduceActions { REDUCE_CONTINUE, REDUCE_STOP };
62 
63 struct ReduceStatus {
64   ReduceActions action;
65   int code;
66 };
67 
PrintUsage(const char * program)68 void PrintUsage(const char* program) {
69   // NOTE: Please maintain flags in lexicographical order.
70   printf(
71       R"(%s - Reduce a SPIR-V binary file with respect to a user-provided
72               interestingness test.
73 
74 USAGE: %s [options] <input> <interestingness-test>
75 
76 The SPIR-V binary is read from <input>.
77 
78 Whether a binary is interesting is determined by <interestingness-test>, which
79 is typically a script.
80 
81 NOTE: The reducer is a work in progress.
82 
83 Options (in lexicographical order):
84   -h, --help
85                Print this help.
86   --step-limit
87                32-bit unsigned integer specifying maximum number of
88                steps the reducer will take before giving up.
89   --version
90                Display reducer version information.
91 )",
92       program, program);
93 }
94 
95 // Message consumer for this tool.  Used to emit diagnostics during
96 // initialization and setup. Note that |source| and |position| are irrelevant
97 // here because we are still not processing a SPIR-V input file.
ReduceDiagnostic(spv_message_level_t level,const char *,const spv_position_t &,const char * message)98 void ReduceDiagnostic(spv_message_level_t level, const char* /*source*/,
99                       const spv_position_t& /*position*/, const char* message) {
100   if (level == SPV_MSG_ERROR) {
101     fprintf(stderr, "error: ");
102   }
103   fprintf(stderr, "%s\n", message);
104 }
105 
ParseFlags(int argc,const char ** argv,const char ** in_file,const char ** interestingness_test,spvtools::ReducerOptions * reducer_options)106 ReduceStatus ParseFlags(int argc, const char** argv, const char** in_file,
107                         const char** interestingness_test,
108                         spvtools::ReducerOptions* reducer_options) {
109   uint32_t positional_arg_index = 0;
110 
111   for (int argi = 1; argi < argc; ++argi) {
112     const char* cur_arg = argv[argi];
113     if ('-' == cur_arg[0]) {
114       if (0 == strcmp(cur_arg, "--version")) {
115         spvtools::Logf(ReduceDiagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
116                        spvSoftwareVersionDetailsString());
117         return {REDUCE_STOP, 0};
118       } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
119         PrintUsage(argv[0]);
120         return {REDUCE_STOP, 0};
121       } else if ('\0' == cur_arg[1]) {
122         // We do not support reduction from standard input.  We could support
123         // this if there was a compelling use case.
124         PrintUsage(argv[0]);
125         return {REDUCE_STOP, 0};
126       } else if (0 == strncmp(cur_arg,
127                               "--step-limit=", sizeof("--step-limit=") - 1)) {
128         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
129         char* end = nullptr;
130         errno = 0;
131         const auto step_limit =
132             static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
133         assert(end != split_flag.second.c_str() && errno == 0);
134         reducer_options->set_step_limit(step_limit);
135       }
136     } else if (positional_arg_index == 0) {
137       // Input file name
138       assert(!*in_file);
139       *in_file = cur_arg;
140       positional_arg_index++;
141     } else if (positional_arg_index == 1) {
142       assert(!*interestingness_test);
143       *interestingness_test = cur_arg;
144       positional_arg_index++;
145     } else {
146       spvtools::Error(ReduceDiagnostic, nullptr, {},
147                       "Too many positional arguments specified");
148       return {REDUCE_STOP, 1};
149     }
150   }
151 
152   if (!*in_file) {
153     spvtools::Error(ReduceDiagnostic, nullptr, {}, "No input file specified");
154     return {REDUCE_STOP, 1};
155   }
156 
157   if (!*interestingness_test) {
158     spvtools::Error(ReduceDiagnostic, nullptr, {},
159                     "No interestingness test specified");
160     return {REDUCE_STOP, 1};
161   }
162 
163   return {REDUCE_CONTINUE, 0};
164 }
165 
166 }  // namespace
167 
168 const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
169 
main(int argc,const char ** argv)170 int main(int argc, const char** argv) {
171   const char* in_file = nullptr;
172   const char* interestingness_test = nullptr;
173 
174   spv_target_env target_env = kDefaultEnvironment;
175   spvtools::ReducerOptions reducer_options;
176 
177   ReduceStatus status =
178       ParseFlags(argc, argv, &in_file, &interestingness_test, &reducer_options);
179 
180   if (status.action == REDUCE_STOP) {
181     return status.code;
182   }
183 
184   if (!CheckExecuteCommand()) {
185     std::cerr << "could not find shell interpreter for executing a command"
186               << std::endl;
187     return 2;
188   }
189 
190   Reducer reducer(target_env);
191 
192   reducer.SetInterestingnessFunction(
193       [interestingness_test](std::vector<uint32_t> binary,
194                              uint32_t reductions_applied) -> bool {
195         std::stringstream ss;
196         ss << "temp_" << std::setw(4) << std::setfill('0') << reductions_applied
197            << ".spv";
198         const auto spv_file = ss.str();
199         const std::string command =
200             std::string(interestingness_test) + " " + spv_file;
201         auto write_file_succeeded =
202             WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size());
203         (void)(write_file_succeeded);
204         assert(write_file_succeeded);
205         return ExecuteCommand(command);
206       });
207 
208   reducer.AddReductionPass(
209       spvtools::MakeUnique<RemoveOpNameInstructionReductionPass>(target_env));
210   reducer.AddReductionPass(
211       spvtools::MakeUnique<OperandToConstReductionPass>(target_env));
212   reducer.AddReductionPass(
213       spvtools::MakeUnique<OperandToDominatingIdReductionPass>(target_env));
214   reducer.AddReductionPass(
215       spvtools::MakeUnique<RemoveUnreferencedInstructionReductionPass>(
216           target_env));
217   reducer.AddReductionPass(
218       spvtools::MakeUnique<StructuredLoopToSelectionReductionPass>(target_env));
219 
220   reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
221 
222   std::vector<uint32_t> binary_in;
223   if (!ReadFile<uint32_t>(in_file, "rb", &binary_in)) {
224     return 1;
225   }
226 
227   std::vector<uint32_t> binary_out;
228   const auto reduction_status =
229       reducer.Run(std::move(binary_in), &binary_out, reducer_options);
230 
231   if (reduction_status ==
232           Reducer::ReductionResultStatus::kInitialStateNotInteresting ||
233       !WriteFile<uint32_t>("_reduced_final.spv", "wb", binary_out.data(),
234                            binary_out.size())) {
235     return 1;
236   }
237 
238   return 0;
239 }
240