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