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. 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. 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 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. 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 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 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