// Copyright (c) 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include "source/opt/build_module.h" #include "source/opt/ir_context.h" #include "source/opt/log.h" #include "source/reduce/merge_blocks_reduction_opportunity_finder.h" #include "source/reduce/operand_to_const_reduction_opportunity_finder.h" #include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h" #include "source/reduce/operand_to_undef_reduction_opportunity_finder.h" #include "source/reduce/reducer.h" #include "source/reduce/remove_function_reduction_opportunity_finder.h" #include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h" #include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h" #include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h" #include "source/spirv_reducer_options.h" #include "source/util/make_unique.h" #include "source/util/string_utils.h" #include "spirv-tools/libspirv.hpp" #include "tools/io.h" #include "tools/util/cli_consumer.h" using namespace spvtools::reduce; namespace { using ErrorOrInt = std::pair; // Check that the std::system function can actually be used. bool CheckExecuteCommand() { int res = std::system(nullptr); return res != 0; } // Execute a command using the shell. // Returns true if and only if the command's exit status was 0. bool ExecuteCommand(const std::string& command) { errno = 0; int status = std::system(command.c_str()); assert(errno == 0 && "failed to execute command"); // The result returned by 'system' is implementation-defined, but is // usually the case that the returned value is 0 when the command's exit // code was 0. We are assuming that here, and that's all we depend on. return status == 0; } // Status and actions to perform after parsing command-line arguments. enum ReduceActions { REDUCE_CONTINUE, REDUCE_STOP }; struct ReduceStatus { ReduceActions action; int code; }; void PrintUsage(const char* program) { // NOTE: Please maintain flags in lexicographical order. printf( R"(%s - Reduce a SPIR-V binary file with respect to a user-provided interestingness test. USAGE: %s [options] The SPIR-V binary is read from . Whether a binary is interesting is determined by , which should be the path to a script. * The script must be executable. * The script should take the path to a SPIR-V binary file (.spv) as its single argument, and exit with code 0 if and only if the binary file is interesting. * Example: an interestingness test for reducing a SPIR-V binary file that causes tool "foo" to exit with error code 1 and print "Fatal error: bar" to standard error should: - invoke "foo" on the binary passed as the script argument; - capture the return code and standard error from "bar"; - exit with code 0 if and only if the return code of "foo" was 1 and the standard error from "bar" contained "Fatal error: bar". * The reducer does not place a time limit on how long the interestingness test takes to run, so it is advisable to use per-command timeouts inside the script when invoking SPIR-V-processing tools (such as "foo" in the above example). NOTE: The reducer is a work in progress. Options (in lexicographical order): -h, --help Print this help. --step-limit 32-bit unsigned integer specifying maximum number of steps the reducer will take before giving up. --version Display reducer version information. )", program, program); } // Message consumer for this tool. Used to emit diagnostics during // initialization and setup. Note that |source| and |position| are irrelevant // here because we are still not processing a SPIR-V input file. void ReduceDiagnostic(spv_message_level_t level, const char* /*source*/, const spv_position_t& /*position*/, const char* message) { if (level == SPV_MSG_ERROR) { fprintf(stderr, "error: "); } fprintf(stderr, "%s\n", message); } ReduceStatus ParseFlags(int argc, const char** argv, const char** in_file, const char** interestingness_test, spvtools::ReducerOptions* reducer_options) { uint32_t positional_arg_index = 0; for (int argi = 1; argi < argc; ++argi) { const char* cur_arg = argv[argi]; if ('-' == cur_arg[0]) { if (0 == strcmp(cur_arg, "--version")) { spvtools::Logf(ReduceDiagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n", spvSoftwareVersionDetailsString()); return {REDUCE_STOP, 0}; } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) { PrintUsage(argv[0]); return {REDUCE_STOP, 0}; } else if ('\0' == cur_arg[1]) { // We do not support reduction from standard input. We could support // this if there was a compelling use case. PrintUsage(argv[0]); return {REDUCE_STOP, 0}; } else if (0 == strncmp(cur_arg, "--step-limit=", sizeof("--step-limit=") - 1)) { const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); char* end = nullptr; errno = 0; const auto step_limit = static_cast(strtol(split_flag.second.c_str(), &end, 10)); assert(end != split_flag.second.c_str() && errno == 0); reducer_options->set_step_limit(step_limit); } } else if (positional_arg_index == 0) { // Input file name assert(!*in_file); *in_file = cur_arg; positional_arg_index++; } else if (positional_arg_index == 1) { assert(!*interestingness_test); *interestingness_test = cur_arg; positional_arg_index++; } else { spvtools::Error(ReduceDiagnostic, nullptr, {}, "Too many positional arguments specified"); return {REDUCE_STOP, 1}; } } if (!*in_file) { spvtools::Error(ReduceDiagnostic, nullptr, {}, "No input file specified"); return {REDUCE_STOP, 1}; } if (!*interestingness_test) { spvtools::Error(ReduceDiagnostic, nullptr, {}, "No interestingness test specified"); return {REDUCE_STOP, 1}; } return {REDUCE_CONTINUE, 0}; } } // namespace const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3; int main(int argc, const char** argv) { const char* in_file = nullptr; const char* interestingness_test = nullptr; spv_target_env target_env = kDefaultEnvironment; spvtools::ReducerOptions reducer_options; ReduceStatus status = ParseFlags(argc, argv, &in_file, &interestingness_test, &reducer_options); if (status.action == REDUCE_STOP) { return status.code; } if (!CheckExecuteCommand()) { std::cerr << "could not find shell interpreter for executing a command" << std::endl; return 2; } Reducer reducer(target_env); reducer.SetInterestingnessFunction( [interestingness_test](std::vector binary, uint32_t reductions_applied) -> bool { std::stringstream ss; ss << "temp_" << std::setw(4) << std::setfill('0') << reductions_applied << ".spv"; const auto spv_file = ss.str(); const std::string command = std::string(interestingness_test) + " " + spv_file; auto write_file_succeeded = WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size()); (void)(write_file_succeeded); assert(write_file_succeeded); return ExecuteCommand(command); }); reducer.AddReductionPass( spvtools::MakeUnique< RemoveOpNameInstructionReductionOpportunityFinder>()); reducer.AddReductionPass( spvtools::MakeUnique()); reducer.AddReductionPass( spvtools::MakeUnique()); reducer.AddReductionPass( spvtools::MakeUnique()); reducer.AddReductionPass( spvtools::MakeUnique< RemoveUnreferencedInstructionReductionOpportunityFinder>()); reducer.AddReductionPass( spvtools::MakeUnique< StructuredLoopToSelectionReductionOpportunityFinder>()); reducer.AddReductionPass( spvtools::MakeUnique()); reducer.AddReductionPass( spvtools::MakeUnique()); reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer); std::vector binary_in; if (!ReadFile(in_file, "rb", &binary_in)) { return 1; } std::vector binary_out; const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out, reducer_options); if (reduction_status == Reducer::ReductionResultStatus::kInitialStateNotInteresting || !WriteFile("_reduced_final.spv", "wb", binary_out.data(), binary_out.size())) { return 1; } return 0; }