SPIRV-Tools/tools/reduce/reduce.cpp
Hugues Evrard 4aeadc0199 Add RemoveOpNameInstruction reduction pass (#2187)
Add a spirv-reduce pass which removes OpName and OpMemberName instructions.

This is useful to enable other reduction passes, e.g. RemoveUnreferencedInstruction may not be able to remove an instruction creating an id whose only usage is an OpName for this id.
2018-12-10 11:53:31 -05:00

240 lines
8.0 KiB
C++

// 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 <cstring>
#include <functional>
#include "source/opt/build_module.h"
#include "source/opt/ir_context.h"
#include "source/opt/log.h"
#include "source/reduce/operand_to_const_reduction_pass.h"
#include "source/reduce/operand_to_dominating_id_reduction_pass.h"
#include "source/reduce/reducer.h"
#include "source/reduce/remove_opname_instruction_reduction_pass.h"
#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
#include "source/reduce/structured_loop_to_selection_reduction_pass.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<std::string, int>;
// 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] <input> <interestingness-test>
The SPIR-V binary is read from <input>.
Whether a binary is interesting is determined by <interestingness-test>, which
is typically a script.
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<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 (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<uint32_t> 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<RemoveOpNameInstructionReductionPass>(target_env));
reducer.AddReductionPass(
spvtools::MakeUnique<OperandToConstReductionPass>(target_env));
reducer.AddReductionPass(
spvtools::MakeUnique<OperandToDominatingIdReductionPass>(target_env));
reducer.AddReductionPass(
spvtools::MakeUnique<RemoveUnreferencedInstructionReductionPass>(
target_env));
reducer.AddReductionPass(
spvtools::MakeUnique<StructuredLoopToSelectionReductionPass>(target_env));
reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
std::vector<uint32_t> binary_in;
if (!ReadFile<uint32_t>(in_file, "rb", &binary_in)) {
return 1;
}
std::vector<uint32_t> binary_out;
const auto reduction_status =
reducer.Run(std::move(binary_in), &binary_out, reducer_options);
if (reduction_status ==
Reducer::ReductionResultStatus::kInitialStateNotInteresting ||
!WriteFile<uint32_t>("_reduced_final.spv", "wb", binary_out.data(),
binary_out.size())) {
return 1;
}
return 0;
}