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