• 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/reducer.h"
24 #include "source/spirv_reducer_options.h"
25 #include "source/util/string_utils.h"
26 #include "tools/io.h"
27 #include "tools/util/cli_consumer.h"
28 
29 namespace {
30 
31 // Check that the std::system function can actually be used.
CheckExecuteCommand()32 bool CheckExecuteCommand() {
33   int res = std::system(nullptr);
34   return res != 0;
35 }
36 
37 // Execute a command using the shell.
38 // Returns true if and only if the command's exit status was 0.
ExecuteCommand(const std::string & command)39 bool ExecuteCommand(const std::string& command) {
40   errno = 0;
41   int status = std::system(command.c_str());
42   assert(errno == 0 && "failed to execute command");
43   // The result returned by 'system' is implementation-defined, but is
44   // usually the case that the returned value is 0 when the command's exit
45   // code was 0.  We are assuming that here, and that's all we depend on.
46   return status == 0;
47 }
48 
49 // Status and actions to perform after parsing command-line arguments.
50 enum ReduceActions { REDUCE_CONTINUE, REDUCE_STOP };
51 
52 struct ReduceStatus {
53   ReduceActions action;
54   int code;
55 };
56 
PrintUsage(const char * program)57 void PrintUsage(const char* program) {
58   // NOTE: Please maintain flags in lexicographical order.
59   printf(
60       R"(%s - Reduce a SPIR-V binary file with respect to a user-provided
61 interestingness test.
62 
63 USAGE: %s [options] <input.spv> -o <output.spv> -- <interestingness_test> [args...]
64 
65 The SPIR-V binary is read from <input.spv>. The reduced SPIR-V binary is
66 written to <output.spv>.
67 
68 Whether a binary is interesting is determined by <interestingness_test>, which
69 should be the path to a script. The "--" characters are optional but denote
70 that all arguments that follow are positional arguments and thus will be
71 forwarded to the interestingness test, and not parsed by %s.
72 
73  * The script must be executable.
74 
75  * The script should take the path to a SPIR-V binary file (.spv) as an
76    argument, and exit with code 0 if and only if the binary file is
77    interesting.  The binary will be passed to the script as an argument after
78    any other provided arguments [args...].
79 
80  * Example: an interestingness test for reducing a SPIR-V binary file that
81    causes tool "foo" to exit with error code 1 and print "Fatal error: bar" to
82    standard error should:
83      - invoke "foo" on the binary passed as the script argument;
84      - capture the return code and standard error from "bar";
85      - exit with code 0 if and only if the return code of "foo" was 1 and the
86        standard error from "bar" contained "Fatal error: bar".
87 
88  * The reducer does not place a time limit on how long the interestingness test
89    takes to run, so it is advisable to use per-command timeouts inside the
90    script when invoking SPIR-V-processing tools (such as "foo" in the above
91    example).
92 
93 NOTE: The reducer is a work in progress.
94 
95 Options (in lexicographical order):
96 
97   --fail-on-validation-error
98                Stop reduction with an error if any reduction step produces a
99                SPIR-V module that fails to validate.
100   -h, --help
101                Print this help.
102   --step-limit=
103                32-bit unsigned integer specifying maximum number of steps the
104                reducer will take before giving up.
105   --temp-file-prefix=
106                Specifies a temporary file prefix that will be used to output
107                temporary shader files during reduction.  A number and .spv
108                extension will be added.  The default is "temp_", which will
109                cause files like "temp_0001.spv" to be output to the current
110                directory.
111   --version
112                Display reducer version information.
113 
114 Supported validator options are as follows. See `spirv-val --help` for details.
115   --before-hlsl-legalization
116   --relax-block-layout
117   --relax-logical-pointer
118   --relax-struct-store
119   --scalar-block-layout
120   --skip-block-layout
121 )",
122       program, program, program);
123 }
124 
125 // Message consumer for this tool.  Used to emit diagnostics during
126 // initialization and setup. Note that |source| and |position| are irrelevant
127 // 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)128 void ReduceDiagnostic(spv_message_level_t level, const char* /*source*/,
129                       const spv_position_t& /*position*/, const char* message) {
130   if (level == SPV_MSG_ERROR) {
131     fprintf(stderr, "error: ");
132   }
133   fprintf(stderr, "%s\n", message);
134 }
135 
ParseFlags(int argc,const char ** argv,std::string * in_binary_file,std::string * out_binary_file,std::vector<std::string> * interestingness_test,std::string * temp_file_prefix,spvtools::ReducerOptions * reducer_options,spvtools::ValidatorOptions * validator_options)136 ReduceStatus ParseFlags(int argc, const char** argv,
137                         std::string* in_binary_file,
138                         std::string* out_binary_file,
139                         std::vector<std::string>* interestingness_test,
140                         std::string* temp_file_prefix,
141                         spvtools::ReducerOptions* reducer_options,
142                         spvtools::ValidatorOptions* validator_options) {
143   uint32_t positional_arg_index = 0;
144   bool only_positional_arguments_remain = false;
145 
146   for (int argi = 1; argi < argc; ++argi) {
147     const char* cur_arg = argv[argi];
148     if ('-' == cur_arg[0] && !only_positional_arguments_remain) {
149       if (0 == strcmp(cur_arg, "--version")) {
150         spvtools::Logf(ReduceDiagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
151                        spvSoftwareVersionDetailsString());
152         return {REDUCE_STOP, 0};
153       } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
154         PrintUsage(argv[0]);
155         return {REDUCE_STOP, 0};
156       } else if (0 == strcmp(cur_arg, "-o")) {
157         if (out_binary_file->empty() && argi + 1 < argc) {
158           *out_binary_file = std::string(argv[++argi]);
159         } else {
160           PrintUsage(argv[0]);
161           return {REDUCE_STOP, 1};
162         }
163       } else if (0 == strncmp(cur_arg,
164                               "--step-limit=", sizeof("--step-limit=") - 1)) {
165         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
166         char* end = nullptr;
167         errno = 0;
168         const auto step_limit =
169             static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
170         assert(end != split_flag.second.c_str() && errno == 0);
171         reducer_options->set_step_limit(step_limit);
172       } else if (0 == strcmp(cur_arg, "--fail-on-validation-error")) {
173         reducer_options->set_fail_on_validation_error(true);
174       } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
175         validator_options->SetBeforeHlslLegalization(true);
176       } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
177         validator_options->SetRelaxLogicalPointer(true);
178       } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
179         validator_options->SetRelaxBlockLayout(true);
180       } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
181         validator_options->SetScalarBlockLayout(true);
182       } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
183         validator_options->SetSkipBlockLayout(true);
184       } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
185         validator_options->SetRelaxStructStore(true);
186       } else if (0 == strncmp(cur_arg, "--temp-file-prefix=",
187                               sizeof("--temp-file-prefix=") - 1)) {
188         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
189         *temp_file_prefix = std::string(split_flag.second);
190       } else if (0 == strcmp(cur_arg, "--")) {
191         only_positional_arguments_remain = true;
192       } else {
193         std::stringstream ss;
194         ss << "Unrecognized argument: " << cur_arg << std::endl;
195         spvtools::Error(ReduceDiagnostic, nullptr, {}, ss.str().c_str());
196         PrintUsage(argv[0]);
197         return {REDUCE_STOP, 1};
198       }
199     } else if (positional_arg_index == 0) {
200       // Binary input file name
201       assert(in_binary_file->empty());
202       *in_binary_file = std::string(cur_arg);
203       positional_arg_index++;
204     } else {
205       interestingness_test->push_back(std::string(cur_arg));
206     }
207   }
208 
209   if (in_binary_file->empty()) {
210     spvtools::Error(ReduceDiagnostic, nullptr, {}, "No input file specified");
211     return {REDUCE_STOP, 1};
212   }
213 
214   if (out_binary_file->empty()) {
215     spvtools::Error(ReduceDiagnostic, nullptr, {}, "-o required");
216     return {REDUCE_STOP, 1};
217   }
218 
219   if (interestingness_test->empty()) {
220     spvtools::Error(ReduceDiagnostic, nullptr, {},
221                     "No interestingness test specified");
222     return {REDUCE_STOP, 1};
223   }
224 
225   return {REDUCE_CONTINUE, 0};
226 }
227 
228 }  // namespace
229 
230 // Dumps |binary| to file |filename|. Useful for interactive debugging.
DumpShader(const std::vector<uint32_t> & binary,const char * filename)231 void DumpShader(const std::vector<uint32_t>& binary, const char* filename) {
232   auto write_file_succeeded =
233       WriteFile(filename, "wb", &binary[0], binary.size());
234   if (!write_file_succeeded) {
235     std::cerr << "Failed to dump shader" << std::endl;
236   }
237 }
238 
239 // Dumps the SPIRV-V module in |context| to file |filename|. Useful for
240 // interactive debugging.
DumpShader(spvtools::opt::IRContext * context,const char * filename)241 void DumpShader(spvtools::opt::IRContext* context, const char* filename) {
242   std::vector<uint32_t> binary;
243   context->module()->ToBinary(&binary, false);
244   DumpShader(binary, filename);
245 }
246 
247 const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
248 
main(int argc,const char ** argv)249 int main(int argc, const char** argv) {
250   std::string in_binary_file;
251   std::string out_binary_file;
252   std::vector<std::string> interestingness_test;
253   std::string temp_file_prefix = "temp_";
254 
255   spv_target_env target_env = kDefaultEnvironment;
256   spvtools::ReducerOptions reducer_options;
257   spvtools::ValidatorOptions validator_options;
258 
259   ReduceStatus status = ParseFlags(
260       argc, argv, &in_binary_file, &out_binary_file, &interestingness_test,
261       &temp_file_prefix, &reducer_options, &validator_options);
262 
263   if (status.action == REDUCE_STOP) {
264     return status.code;
265   }
266 
267   if (!CheckExecuteCommand()) {
268     std::cerr << "could not find shell interpreter for executing a command"
269               << std::endl;
270     return 2;
271   }
272 
273   spvtools::reduce::Reducer reducer(target_env);
274 
275   std::stringstream joined;
276   joined << interestingness_test[0];
277   for (size_t i = 1, size = interestingness_test.size(); i < size; ++i) {
278     joined << " " << interestingness_test[i];
279   }
280   std::string interestingness_command_joined = joined.str();
281 
282   reducer.SetInterestingnessFunction(
283       [interestingness_command_joined, temp_file_prefix](
284           std::vector<uint32_t> binary, uint32_t reductions_applied) -> bool {
285         std::stringstream ss;
286         ss << temp_file_prefix << std::setw(4) << std::setfill('0')
287            << reductions_applied << ".spv";
288         const auto spv_file = ss.str();
289         const std::string command =
290             interestingness_command_joined + " " + spv_file;
291         auto write_file_succeeded =
292             WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size());
293         (void)(write_file_succeeded);
294         assert(write_file_succeeded);
295         return ExecuteCommand(command);
296       });
297 
298   reducer.AddDefaultReductionPasses();
299 
300   reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
301 
302   std::vector<uint32_t> binary_in;
303   if (!ReadFile<uint32_t>(in_binary_file.c_str(), "rb", &binary_in)) {
304     return 1;
305   }
306 
307   std::vector<uint32_t> binary_out;
308   const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out,
309                                             reducer_options, validator_options);
310 
311   // Always try to write the output file, even if the reduction failed.
312   if (!WriteFile<uint32_t>(out_binary_file.c_str(), "wb", binary_out.data(),
313                            binary_out.size())) {
314     return 1;
315   }
316 
317   // These are the only successful statuses.
318   switch (reduction_status) {
319     case spvtools::reduce::Reducer::ReductionResultStatus::kComplete:
320     case spvtools::reduce::Reducer::ReductionResultStatus::kReachedStepLimit:
321       return 0;
322     default:
323       break;
324   }
325 
326   return 1;
327 }
328