reduce.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  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. #include <cassert>
  15. #include <cerrno>
  16. #include <cstring>
  17. #include <functional>
  18. #include "source/opt/build_module.h"
  19. #include "source/opt/log.h"
  20. #include "source/reduce/reducer.h"
  21. #include "source/spirv_reducer_options.h"
  22. #include "source/util/string_utils.h"
  23. #include "tools/io.h"
  24. #include "tools/util/cli_consumer.h"
  25. using namespace spvtools::reduce;
  26. namespace {
  27. using ErrorOrInt = std::pair<std::string, int>;
  28. // Check that the std::system function can actually be used.
  29. bool CheckExecuteCommand() {
  30. int res = std::system(nullptr);
  31. return res != 0;
  32. }
  33. // Execute a command using the shell.
  34. // Returns true if and only if the command's exit status was 0.
  35. bool ExecuteCommand(const std::string& command) {
  36. errno = 0;
  37. int status = std::system(command.c_str());
  38. assert(errno == 0 && "failed to execute command");
  39. // The result returned by 'system' is implementation-defined, but is
  40. // usually the case that the returned value is 0 when the command's exit
  41. // code was 0. We are assuming that here, and that's all we depend on.
  42. return status == 0;
  43. }
  44. // Status and actions to perform after parsing command-line arguments.
  45. enum ReduceActions { REDUCE_CONTINUE, REDUCE_STOP };
  46. struct ReduceStatus {
  47. ReduceActions action;
  48. int code;
  49. };
  50. void PrintUsage(const char* program) {
  51. // NOTE: Please maintain flags in lexicographical order.
  52. printf(
  53. R"(%s - Reduce a SPIR-V binary file with respect to a user-provided
  54. interestingness test.
  55. USAGE: %s [options] <input> <interestingness-test>
  56. The SPIR-V binary is read from <input>.
  57. Whether a binary is interesting is determined by <interestingness-test>, which
  58. should be the path to a script.
  59. * The script must be executable.
  60. * The script should take the path to a SPIR-V binary file (.spv) as its single
  61. argument, and exit with code 0 if and only if the binary file is
  62. interesting.
  63. * Example: an interestingness test for reducing a SPIR-V binary file that
  64. causes tool "foo" to exit with error code 1 and print "Fatal error: bar" to
  65. standard error should:
  66. - invoke "foo" on the binary passed as the script argument;
  67. - capture the return code and standard error from "bar";
  68. - exit with code 0 if and only if the return code of "foo" was 1 and the
  69. standard error from "bar" contained "Fatal error: bar".
  70. * The reducer does not place a time limit on how long the interestingness test
  71. takes to run, so it is advisable to use per-command timeouts inside the
  72. script when invoking SPIR-V-processing tools (such as "foo" in the above
  73. example).
  74. NOTE: The reducer is a work in progress.
  75. Options (in lexicographical order):
  76. --fail-on-validation-error
  77. Stop reduction with an error if any reduction step produces a
  78. SPIR-V module that fails to validate.
  79. -h, --help
  80. Print this help.
  81. --step-limit
  82. 32-bit unsigned integer specifying maximum number of steps the
  83. reducer will take before giving up.
  84. --version
  85. Display reducer version information.
  86. Supported validator options are as follows. See `spirv-val --help` for details.
  87. --relax-logical-pointer
  88. --relax-block-layout
  89. --scalar-block-layout
  90. --skip-block-layout
  91. --relax-struct-store
  92. )",
  93. program, program);
  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. ReduceStatus ParseFlags(int argc, const char** argv, const char** in_file,
  106. const char** interestingness_test,
  107. spvtools::ReducerOptions* reducer_options,
  108. spvtools::ValidatorOptions* validator_options) {
  109. uint32_t positional_arg_index = 0;
  110. for (int argi = 1; argi < argc; ++argi) {
  111. const char* cur_arg = argv[argi];
  112. if ('-' == cur_arg[0]) {
  113. if (0 == strcmp(cur_arg, "--version")) {
  114. spvtools::Logf(ReduceDiagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
  115. spvSoftwareVersionDetailsString());
  116. return {REDUCE_STOP, 0};
  117. } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
  118. PrintUsage(argv[0]);
  119. return {REDUCE_STOP, 0};
  120. } else if ('\0' == cur_arg[1]) {
  121. // We do not support reduction from standard input. We could support
  122. // this if there was a compelling use case.
  123. PrintUsage(argv[0]);
  124. return {REDUCE_STOP, 0};
  125. } else if (0 == strncmp(cur_arg,
  126. "--step-limit=", sizeof("--step-limit=") - 1)) {
  127. const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
  128. char* end = nullptr;
  129. errno = 0;
  130. const auto step_limit =
  131. static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
  132. assert(end != split_flag.second.c_str() && errno == 0);
  133. reducer_options->set_step_limit(step_limit);
  134. }
  135. } else if (positional_arg_index == 0) {
  136. // Input file name
  137. assert(!*in_file);
  138. *in_file = cur_arg;
  139. positional_arg_index++;
  140. } else if (positional_arg_index == 1) {
  141. assert(!*interestingness_test);
  142. *interestingness_test = cur_arg;
  143. positional_arg_index++;
  144. } else if (0 == strcmp(cur_arg, "--fail-on-validation-error")) {
  145. reducer_options->set_fail_on_validation_error(true);
  146. } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
  147. validator_options->SetRelaxLogicalPointer(true);
  148. } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
  149. validator_options->SetRelaxBlockLayout(true);
  150. } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
  151. validator_options->SetScalarBlockLayout(true);
  152. } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
  153. validator_options->SetSkipBlockLayout(true);
  154. } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
  155. validator_options->SetRelaxStructStore(true);
  156. } else {
  157. spvtools::Error(ReduceDiagnostic, nullptr, {},
  158. "Too many positional arguments specified");
  159. return {REDUCE_STOP, 1};
  160. }
  161. }
  162. if (!*in_file) {
  163. spvtools::Error(ReduceDiagnostic, nullptr, {}, "No input file specified");
  164. return {REDUCE_STOP, 1};
  165. }
  166. if (!*interestingness_test) {
  167. spvtools::Error(ReduceDiagnostic, nullptr, {},
  168. "No interestingness test specified");
  169. return {REDUCE_STOP, 1};
  170. }
  171. return {REDUCE_CONTINUE, 0};
  172. }
  173. } // namespace
  174. const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
  175. int main(int argc, const char** argv) {
  176. const char* in_file = nullptr;
  177. const char* interestingness_test = nullptr;
  178. spv_target_env target_env = kDefaultEnvironment;
  179. spvtools::ReducerOptions reducer_options;
  180. spvtools::ValidatorOptions validator_options;
  181. ReduceStatus status = ParseFlags(argc, argv, &in_file, &interestingness_test,
  182. &reducer_options, &validator_options);
  183. if (status.action == REDUCE_STOP) {
  184. return status.code;
  185. }
  186. if (!CheckExecuteCommand()) {
  187. std::cerr << "could not find shell interpreter for executing a command"
  188. << std::endl;
  189. return 2;
  190. }
  191. Reducer reducer(target_env);
  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. reducer.AddDefaultReductionPasses();
  208. reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
  209. std::vector<uint32_t> binary_in;
  210. if (!ReadFile<uint32_t>(in_file, "rb", &binary_in)) {
  211. return 1;
  212. }
  213. std::vector<uint32_t> binary_out;
  214. const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out,
  215. reducer_options, validator_options);
  216. if (reduction_status ==
  217. Reducer::ReductionResultStatus::kInitialStateNotInteresting ||
  218. !WriteFile<uint32_t>("_reduced_final.spv", "wb", binary_out.data(),
  219. binary_out.size())) {
  220. return 1;
  221. }
  222. return 0;
  223. }