Home | History | Annotate | Download | only in reduce
      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