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