| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- // 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 <cassert>
- #include <cerrno>
- #include <cstdlib>
- #include <cstring>
- #include <functional>
- #include <sstream>
- #include "source/opt/build_module.h"
- #include "source/opt/ir_context.h"
- #include "source/opt/log.h"
- #include "source/reduce/reducer.h"
- #include "source/spirv_reducer_options.h"
- #include "source/util/string_utils.h"
- #include "tools/io.h"
- #include "tools/util/cli_consumer.h"
- namespace {
- // 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] <input.spv> -o <output.spv> -- <interestingness_test> [args...]
- The SPIR-V binary is read from <input.spv>. The reduced SPIR-V binary is
- written to <output.spv>.
- Whether a binary is interesting is determined by <interestingness_test>, which
- should be the path to a script. The "--" characters are optional but denote
- that all arguments that follow are positional arguments and thus will be
- forwarded to the interestingness test, and not parsed by %s.
- * The script must be executable.
- * The script should take the path to a SPIR-V binary file (.spv) as an
- argument, and exit with code 0 if and only if the binary file is
- interesting. The binary will be passed to the script as an argument after
- any other provided arguments [args...].
- * 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):
- --fail-on-validation-error
- Stop reduction with an error if any reduction step produces a
- SPIR-V module that fails to validate.
- -h, --help
- Print this help.
- --step-limit=
- 32-bit unsigned integer specifying maximum number of steps the
- reducer will take before giving up.
- --target-function=
- 32-bit unsigned integer specifying the id of a function in the
- input module. The reducer will restrict attention to this
- function, and will not make changes to other functions or to
- instructions outside of functions, except that some global
- instructions may be added in support of reducing the target
- function. If 0 is specified (the default) then all functions are
- reduced.
- --temp-file-prefix=
- Specifies a temporary file prefix that will be used to output
- temporary shader files during reduction. A number and .spv
- extension will be added. The default is "temp_", which will
- cause files like "temp_0001.spv" to be output to the current
- directory.
- --version
- Display reducer version information.
- Supported validator options are as follows. See `spirv-val --help` for details.
- --before-hlsl-legalization
- --relax-block-layout
- --relax-logical-pointer
- --relax-struct-store
- --scalar-block-layout
- --skip-block-layout
- )",
- program, 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,
- std::string* in_binary_file,
- std::string* out_binary_file,
- std::vector<std::string>* interestingness_test,
- std::string* temp_file_prefix,
- spvtools::ReducerOptions* reducer_options,
- spvtools::ValidatorOptions* validator_options) {
- uint32_t positional_arg_index = 0;
- bool only_positional_arguments_remain = false;
- for (int argi = 1; argi < argc; ++argi) {
- const char* cur_arg = argv[argi];
- if ('-' == cur_arg[0] && !only_positional_arguments_remain) {
- 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 == strcmp(cur_arg, "-o")) {
- if (out_binary_file->empty() && argi + 1 < argc) {
- *out_binary_file = std::string(argv[++argi]);
- } else {
- PrintUsage(argv[0]);
- return {REDUCE_STOP, 1};
- }
- } 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<uint32_t>(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 (0 == strncmp(cur_arg, "--target-function=",
- sizeof("--target-function=") - 1)) {
- const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
- char* end = nullptr;
- errno = 0;
- const auto target_function =
- static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
- assert(end != split_flag.second.c_str() && errno == 0);
- reducer_options->set_target_function(target_function);
- } else if (0 == strcmp(cur_arg, "--fail-on-validation-error")) {
- reducer_options->set_fail_on_validation_error(true);
- } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
- validator_options->SetBeforeHlslLegalization(true);
- } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
- validator_options->SetRelaxLogicalPointer(true);
- } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
- validator_options->SetRelaxBlockLayout(true);
- } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
- validator_options->SetScalarBlockLayout(true);
- } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
- validator_options->SetSkipBlockLayout(true);
- } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
- validator_options->SetRelaxStructStore(true);
- } else if (0 == strncmp(cur_arg, "--temp-file-prefix=",
- sizeof("--temp-file-prefix=") - 1)) {
- const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
- *temp_file_prefix = std::string(split_flag.second);
- } else if (0 == strcmp(cur_arg, "--")) {
- only_positional_arguments_remain = true;
- } else {
- std::stringstream ss;
- ss << "Unrecognized argument: " << cur_arg << std::endl;
- spvtools::Error(ReduceDiagnostic, nullptr, {}, ss.str().c_str());
- PrintUsage(argv[0]);
- return {REDUCE_STOP, 1};
- }
- } else if (positional_arg_index == 0) {
- // Binary input file name
- assert(in_binary_file->empty());
- *in_binary_file = std::string(cur_arg);
- positional_arg_index++;
- } else {
- interestingness_test->push_back(std::string(cur_arg));
- }
- }
- if (in_binary_file->empty()) {
- spvtools::Error(ReduceDiagnostic, nullptr, {}, "No input file specified");
- return {REDUCE_STOP, 1};
- }
- if (out_binary_file->empty()) {
- spvtools::Error(ReduceDiagnostic, nullptr, {}, "-o required");
- return {REDUCE_STOP, 1};
- }
- if (interestingness_test->empty()) {
- spvtools::Error(ReduceDiagnostic, nullptr, {},
- "No interestingness test specified");
- return {REDUCE_STOP, 1};
- }
- return {REDUCE_CONTINUE, 0};
- }
- } // namespace
- // Dumps |binary| to file |filename|. Useful for interactive debugging.
- void DumpShader(const std::vector<uint32_t>& binary, const char* filename) {
- auto write_file_succeeded =
- WriteFile(filename, "wb", &binary[0], binary.size());
- if (!write_file_succeeded) {
- std::cerr << "Failed to dump shader" << std::endl;
- }
- }
- // Dumps the SPIRV-V module in |context| to file |filename|. Useful for
- // interactive debugging.
- void DumpShader(spvtools::opt::IRContext* context, const char* filename) {
- std::vector<uint32_t> binary;
- context->module()->ToBinary(&binary, false);
- DumpShader(binary, filename);
- }
- const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_6;
- int main(int argc, const char** argv) {
- std::string in_binary_file;
- std::string out_binary_file;
- std::vector<std::string> interestingness_test;
- std::string temp_file_prefix = "temp_";
- spv_target_env target_env = kDefaultEnvironment;
- spvtools::ReducerOptions reducer_options;
- spvtools::ValidatorOptions validator_options;
- ReduceStatus status = ParseFlags(
- argc, argv, &in_binary_file, &out_binary_file, &interestingness_test,
- &temp_file_prefix, &reducer_options, &validator_options);
- if (status.action == REDUCE_STOP) {
- return status.code;
- }
- spvtools::reduce::Reducer reducer(target_env);
- std::stringstream joined;
- joined << interestingness_test[0];
- for (size_t i = 1, size = interestingness_test.size(); i < size; ++i) {
- joined << " " << interestingness_test[i];
- }
- std::string interestingness_command_joined = joined.str();
- reducer.SetInterestingnessFunction(
- [interestingness_command_joined, temp_file_prefix](
- std::vector<uint32_t> binary, uint32_t reductions_applied) -> bool {
- std::stringstream ss;
- ss << temp_file_prefix << std::setw(4) << std::setfill('0')
- << reductions_applied << ".spv";
- const auto spv_file = ss.str();
- const std::string command =
- interestingness_command_joined + " " + 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.AddDefaultReductionPasses();
- reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
- std::vector<uint32_t> binary_in;
- if (!ReadBinaryFile(in_binary_file.c_str(), &binary_in)) {
- return 1;
- }
- const uint32_t target_function = (*reducer_options).target_function;
- if (target_function) {
- // A target function was specified; check that it exists.
- std::unique_ptr<spvtools::opt::IRContext> context = spvtools::BuildModule(
- kDefaultEnvironment, spvtools::utils::CLIMessageConsumer,
- binary_in.data(), binary_in.size());
- bool found_target_function = false;
- for (auto& function : *context->module()) {
- if (function.result_id() == target_function) {
- found_target_function = true;
- break;
- }
- }
- if (!found_target_function) {
- std::stringstream strstr;
- strstr << "Target function with id " << target_function
- << " was requested, but not found in the module; stopping.";
- spvtools::utils::CLIMessageConsumer(SPV_MSG_ERROR, nullptr, {},
- strstr.str().c_str());
- return 1;
- }
- }
- std::vector<uint32_t> binary_out;
- const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out,
- reducer_options, validator_options);
- // Always try to write the output file, even if the reduction failed.
- if (!WriteFile<uint32_t>(out_binary_file.c_str(), "wb", binary_out.data(),
- binary_out.size())) {
- return 1;
- }
- // These are the only successful statuses.
- switch (reduction_status) {
- case spvtools::reduce::Reducer::ReductionResultStatus::kComplete:
- case spvtools::reduce::Reducer::ReductionResultStatus::kReachedStepLimit:
- return 0;
- default:
- break;
- }
- return 1;
- }
|