Initial commit for spirv-reduce. (#2056)

Creates a new tool that can be used to reduce failing testcases, similar to creduce.
This commit is contained in:
Alastair Donaldson 2018-11-21 19:03:09 +00:00 committed by Steven Perron
parent fe39a6f342
commit f3acb955c2
30 changed files with 2077 additions and 1 deletions

4
.gitignore vendored
View File

@ -19,3 +19,7 @@ compile_commands.json
# Vim
[._]*.s[a-w][a-z]
*~
# C-Lion
.idea
cmake-build-debug

View File

@ -368,6 +368,8 @@ typedef struct spv_validator_options_t spv_validator_options_t;
typedef struct spv_optimizer_options_t spv_optimizer_options_t;
typedef struct spv_reducer_options_t spv_reducer_options_t;
// Type Definitions
typedef spv_const_binary_t* spv_const_binary;
@ -381,6 +383,8 @@ typedef spv_validator_options_t* spv_validator_options;
typedef const spv_validator_options_t* spv_const_validator_options;
typedef spv_optimizer_options_t* spv_optimizer_options;
typedef const spv_optimizer_options_t* spv_const_optimizer_options;
typedef spv_reducer_options_t* spv_reducer_options;
typedef const spv_reducer_options_t* spv_const_reducer_options;
// Platform API
@ -541,6 +545,23 @@ SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetValidatorOptions(
SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetMaxIdBound(
spv_optimizer_options options, uint32_t val);
// Creates a reducer options object with default options. Returns a valid
// options object. The object remains valid until it is passed into
// |spvReducerOptionsDestroy|.
SPIRV_TOOLS_EXPORT spv_reducer_options spvReducerOptionsCreate();
// Destroys the given reducer options object.
SPIRV_TOOLS_EXPORT void spvReducerOptionsDestroy(spv_reducer_options options);
// Records the maximum number of reduction steps that should run before the
// reducer gives up.
SPIRV_TOOLS_EXPORT void spvReducerOptionsSetStepLimit(
spv_reducer_options options, uint32_t step_limit);
// Sets seed for random number generation.
SPIRV_TOOLS_EXPORT void spvReducerOptionsSetSeed(spv_reducer_options options,
uint32_t seed);
// Encodes the given SPIR-V assembly text to its binary representation. The
// length parameter specifies the number of bytes for text. Encoded binary will
// be stored into *binary. Any error will be written into *diagnostic if

View File

@ -144,6 +144,28 @@ class OptimizerOptions {
spv_optimizer_options options_;
};
// A C++ wrapper around a reducer options object.
class ReducerOptions {
public:
ReducerOptions() : options_(spvReducerOptionsCreate()) {}
~ReducerOptions() { spvReducerOptionsDestroy(options_); }
// Allow implicit conversion to the underlying object.
operator spv_reducer_options() const { return options_; }
// Records the maximum number of reduction steps that should
// run before the reducer gives up.
void set_step_limit(uint32_t step_limit) {
spvReducerOptionsSetStepLimit(options_, step_limit);
}
// Sets a seed to be used for random number generation.
void set_seed(uint32_t seed) { spvReducerOptionsSetSeed(options_, seed); }
private:
spv_reducer_options options_;
};
// C++ interface for SPIRV-Tools functionalities. It wraps the context
// (including target environment and the corresponding SPIR-V grammar) and
// provides methods for assembling, disassembling, and validating.

View File

@ -216,6 +216,7 @@ set_source_files_properties(
add_subdirectory(comp)
add_subdirectory(opt)
add_subdirectory(reduce)
add_subdirectory(link)
set(SPIRV_SOURCES
@ -253,6 +254,7 @@ set(SPIRV_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/spirv_definition.h
${CMAKE_CURRENT_SOURCE_DIR}/spirv_endian.h
${CMAKE_CURRENT_SOURCE_DIR}/spirv_optimizer_options.h
${CMAKE_CURRENT_SOURCE_DIR}/spirv_reducer_options.h
${CMAKE_CURRENT_SOURCE_DIR}/spirv_target_env.h
${CMAKE_CURRENT_SOURCE_DIR}/spirv_validator_options.h
${CMAKE_CURRENT_SOURCE_DIR}/table.h
@ -280,6 +282,7 @@ set(SPIRV_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/software_version.cpp
${CMAKE_CURRENT_SOURCE_DIR}/spirv_endian.cpp
${CMAKE_CURRENT_SOURCE_DIR}/spirv_optimizer_options.cpp
${CMAKE_CURRENT_SOURCE_DIR}/spirv_reducer_options.cpp
${CMAKE_CURRENT_SOURCE_DIR}/spirv_target_env.cpp
${CMAKE_CURRENT_SOURCE_DIR}/spirv_validator_options.cpp
${CMAKE_CURRENT_SOURCE_DIR}/table.cpp

View File

@ -0,0 +1,60 @@
# 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.
set(SPIRV_TOOLS_REDUCE_SOURCES
change_operand_reduction_opportunity.h
operand_to_const_reduction_pass.h
reducer.h
reduction_opportunity.h
reduction_pass.h
remove_instruction_reduction_opportunity.h
remove_unreferenced_instruction_reduction_pass.h
change_operand_reduction_opportunity.cpp
operand_to_const_reduction_pass.cpp
reducer.cpp
reduction_opportunity.cpp
reduction_pass.cpp
remove_instruction_reduction_opportunity.cpp
remove_unreferenced_instruction_reduction_pass.cpp
)
if(MSVC)
# Enable parallel builds across four cores for this lib
add_definitions(/MP4)
endif()
spvtools_pch(SPIRV_TOOLS_REDUCE_SOURCES pch_source_reduce)
add_library(SPIRV-Tools-reduce ${SPIRV_TOOLS_REDUCE_SOURCES})
spvtools_default_compile_options(SPIRV-Tools-reduce)
target_include_directories(SPIRV-Tools-reduce
PUBLIC ${spirv-tools_SOURCE_DIR}/include
PUBLIC ${SPIRV_HEADER_INCLUDE_DIR}
PRIVATE ${spirv-tools_BINARY_DIR}
)
# The reducer reuses a lot of functionality from the SPIRV-Tools library.
target_link_libraries(SPIRV-Tools-reduce
PUBLIC ${SPIRV_TOOLS}
PUBLIC SPIRV-Tools-opt)
set_property(TARGET SPIRV-Tools-reduce PROPERTY FOLDER "SPIRV-Tools libraries")
spvtools_check_symbol_exports(SPIRV-Tools-reduce)
if(ENABLE_SPIRV_TOOLS_INSTALL)
install(TARGETS SPIRV-Tools-reduce
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif(ENABLE_SPIRV_TOOLS_INSTALL)

View File

@ -0,0 +1,32 @@
// 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 "change_operand_reduction_opportunity.h"
namespace spvtools {
namespace reduce {
bool ChangeOperandReductionOpportunity::PreconditionHolds() {
// Check that the instruction still has the original operand.
return inst_->NumOperands() > operand_index_ &&
inst_->GetOperand(operand_index_).words[0] == original_id_ &&
inst_->GetOperand(operand_index_).type == original_type_;
}
void ChangeOperandReductionOpportunity::Apply() {
inst_->SetOperand(operand_index_, {new_id_});
}
} // namespace reduce
} // namespace spvtools

View File

@ -0,0 +1,61 @@
// 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.
#ifndef SOURCE_REDUCE_CHANGE_OPERAND_REDUCTION_OPPORTUNITY_H_
#define SOURCE_REDUCE_CHANGE_OPERAND_REDUCTION_OPPORTUNITY_H_
#include "reduction_opportunity.h"
#include "source/opt/instruction.h"
#include "spirv-tools/libspirv.h"
namespace spvtools {
namespace reduce {
using namespace opt;
// Captures the opportunity to change an id operand of an instruction to some
// other id.
class ChangeOperandReductionOpportunity : public ReductionOpportunity {
public:
// Constructs the opportunity to replace operand |operand_index| of |inst|
// with |new_id|.
ChangeOperandReductionOpportunity(Instruction* inst, uint32_t operand_index,
uint32_t new_id)
: inst_(inst),
operand_index_(operand_index),
original_id_(inst_->GetOperand(operand_index_).words[0]),
original_type_(inst->GetOperand(operand_index).type),
new_id_(new_id) {}
// Determines whether the opportunity can be applied; it may have been viable
// when discovered but later disabled by the application of some other
// reduction opportunity.
bool PreconditionHolds() override;
protected:
// Apply the change of operand.
void Apply() override;
private:
Instruction* const inst_;
const uint32_t operand_index_;
const uint32_t original_id_;
const spv_operand_type_t original_type_;
const uint32_t new_id_;
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_CHANGE_OPERAND_REDUCTION_OPPORTUNITY_H_

View File

@ -0,0 +1,86 @@
// 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 "operand_to_const_reduction_pass.h"
#include "change_operand_reduction_opportunity.h"
#include "source/opt/instruction.h"
namespace spvtools {
namespace reduce {
using namespace opt;
std::vector<std::unique_ptr<ReductionOpportunity>>
OperandToConstReductionPass::GetAvailableOpportunities(
opt::IRContext* context) const {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
assert(result.empty());
// We first loop over all constants. This means that all the reduction
// opportunities to replace an operand with a particular constant will be
// contiguous, and in particular it means that multiple, incompatible
// reduction opportunities that try to replace the same operand with distinct
// constants are likely to be discontiguous. This is good because the
// reducer works in the spirit of delta debugging and tries applying large
// contiguous blocks of opportunities early on, and we want to avoid having a
// large block of incompatible opportunities if possible.
for (const auto& constant : context->GetConstants()) {
for (auto& function : *context->module()) {
for (auto& block : function) {
for (auto& inst : block) {
// We iterate through the operands using an explicit index (rather
// than using a lambda) so that we use said index in the construction
// of a ChangeOperandReductionOpportunity
for (uint32_t index = 0; index < inst.NumOperands(); index++) {
const auto& operand = inst.GetOperand(index);
if (spvIsIdType(operand.type)) {
if (operand.type == SPV_OPERAND_TYPE_RESULT_ID ||
operand.type == SPV_OPERAND_TYPE_TYPE_ID) {
continue;
}
const auto id = operand.words[0];
auto def = context->get_def_use_mgr()->GetDef(id);
if (spvOpcodeIsConstant(def->opcode())) {
// The argument is already a constant.
continue;
}
if (def->opcode() == SpvOpFunction) {
// The argument refers to a function, e.g. the function called
// by OpFunctionCall; avoid replacing this with a constant of
// the function's return type.
continue;
}
auto type_id = def->type_id();
if (type_id) {
if (constant->type_id() == type_id) {
result.push_back(
MakeUnique<ChangeOperandReductionOpportunity>(
&inst, index, constant->result_id()));
}
}
}
}
}
}
}
}
return result;
}
std::string OperandToConstReductionPass::GetName() const {
return "OperandToConstReductionPass";
}
} // namespace reduce
} // namespace spvtools

View File

@ -0,0 +1,51 @@
// 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.
#ifndef SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
#define SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
#include "reduction_pass.h"
namespace spvtools {
namespace reduce {
// A reduction pass for turning id operands of instructions into ids of
// constants. This reduces the extent to which ids of non-constants are used,
// paving the way for instructions that generate them to be eliminated by other
// passes.
class OperandToConstReductionPass : public ReductionPass {
public:
// Creates the reduction pass in the context of the given target environment
// |target_env|
explicit OperandToConstReductionPass(const spv_target_env target_env)
: ReductionPass(target_env) {}
~OperandToConstReductionPass() override = default;
// The name of this pass.
std::string GetName() const final;
protected:
// Finds all opportunities for replacing an operand with a constant in the
// given module.
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
opt::IRContext* context) const final;
private:
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_

146
source/reduce/reducer.cpp Normal file
View File

@ -0,0 +1,146 @@
// 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 <sstream>
#include "source/spirv_reducer_options.h"
#include "reducer.h"
#include "reduction_pass.h"
namespace spvtools {
namespace reduce {
struct Reducer::Impl {
explicit Impl(spv_target_env env) : target_env(env) {}
bool ReachedStepLimit(uint32_t current_step,
spv_const_reducer_options options);
const spv_target_env target_env; // Target environment.
MessageConsumer consumer; // Message consumer.
InterestingnessFunction interestingness_function;
std::vector<std::unique_ptr<ReductionPass>> passes;
};
Reducer::Reducer(spv_target_env env) : impl_(MakeUnique<Impl>(env)) {}
Reducer::~Reducer() = default;
void Reducer::SetMessageConsumer(MessageConsumer c) {
for (auto& pass : impl_->passes) {
pass->SetMessageConsumer(c);
}
impl_->consumer = std::move(c);
}
void Reducer::SetInterestingnessFunction(
Reducer::InterestingnessFunction interestingness_function) {
impl_->interestingness_function = std::move(interestingness_function);
}
Reducer::ReductionResultStatus Reducer::Run(
std::vector<uint32_t>&& binary_in, std::vector<uint32_t>* binary_out,
spv_const_reducer_options options) const {
std::vector<uint32_t> current_binary = binary_in;
// Keeps track of how many reduction attempts have been tried. Reduction
// bails out if this reaches a given limit.
uint32_t reductions_applied = 0;
// Initial state should be interesting.
if (!impl_->interestingness_function(current_binary, reductions_applied)) {
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Initial state was not interesting; stopping.");
return Reducer::ReductionResultStatus::kInitialStateNotInteresting;
}
// Determines whether, on completing one round of reduction passes, it is
// worthwhile trying a further round.
bool another_round_worthwhile = true;
// Apply round after round of reduction passes until we hit the reduction
// step limit, or deem that another round is not going to be worthwhile.
while (!impl_->ReachedStepLimit(reductions_applied, options) &&
another_round_worthwhile) {
// At the start of a round of reduction passes, assume another round will
// not be worthwhile unless we find evidence to the contrary.
another_round_worthwhile = false;
// Iterate through the available passes
for (auto& pass : impl_->passes) {
// Keep applying this pass at its current granularity until it stops
// working or we hit the reduction step limit.
impl_->consumer(SPV_MSG_INFO, nullptr, {},
("Trying pass " + pass->GetName() + ".").c_str());
do {
auto maybe_result = pass->TryApplyReduction(current_binary);
if (maybe_result.empty()) {
// This pass did not have any impact, so move on to the next pass.
// If this pass hasn't reached its minimum granularity then it's
// worth eventually doing another round of reductions, in order to
// try this pass at a finer granularity.
impl_->consumer(
SPV_MSG_INFO, nullptr, {},
("Pass " + pass->GetName() + " did not make a reduction step.")
.c_str());
another_round_worthwhile |= !pass->ReachedMinimumGranularity();
break;
}
std::stringstream stringstream;
reductions_applied++;
stringstream << "Pass " << pass->GetName() << " made reduction step "
<< reductions_applied << ".";
impl_->consumer(SPV_MSG_INFO, nullptr, {},
(stringstream.str().c_str()));
if (impl_->interestingness_function(maybe_result, reductions_applied)) {
// Success! The binary produced by this reduction step is
// interesting, so make it the binary of interest henceforth, and
// note that it's worth doing another round of reduction passes.
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Reduction step succeeded.");
current_binary = std::move(maybe_result);
another_round_worthwhile = true;
}
// Bail out if the reduction step limit has been reached.
} while (!impl_->ReachedStepLimit(reductions_applied, options));
}
}
*binary_out = std::move(current_binary);
// Report whether reduction completed, or bailed out early due to reaching
// the step limit.
if (impl_->ReachedStepLimit(reductions_applied, options)) {
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Reached reduction step limit; stopping.");
return Reducer::ReductionResultStatus::kReachedStepLimit;
}
impl_->consumer(SPV_MSG_INFO, nullptr, {}, "No more to reduce; stopping.");
return Reducer::ReductionResultStatus::kComplete;
}
void Reducer::AddReductionPass(
std::unique_ptr<ReductionPass>&& reduction_pass) {
impl_->passes.push_back(std::move(reduction_pass));
}
bool Reducer::Impl::ReachedStepLimit(uint32_t current_step,
spv_const_reducer_options options) {
return current_step >= options->step_limit;
}
} // namespace reduce
} // namespace spvtools

97
source/reduce/reducer.h Normal file
View File

@ -0,0 +1,97 @@
// 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.
#ifndef SOURCE_REDUCE_REDUCER_H_
#define SOURCE_REDUCE_REDUCER_H_
#include <functional>
#include <string>
#include "spirv-tools/libspirv.hpp"
#include "reduction_pass.h"
namespace spvtools {
namespace reduce {
// This class manages the process of applying a reduction -- parameterized by a
// number of reduction passes and an interestingness test, to a SPIR-V binary.
class Reducer {
public:
// Possible statuses that can result from running a reduction.
enum ReductionResultStatus {
kInitialStateNotInteresting,
kReachedStepLimit,
kComplete
};
// The type for a function that will take a binary and return true if and
// only if the binary is deemed interesting. (The function also takes an
// integer argument that will be incremented each time the function is
// called; this is for debugging purposes).
//
// The notion of "interesting" depends on what properties of the binary or
// tools that process the binary we are trying to maintain during reduction.
using InterestingnessFunction =
std::function<bool(const std::vector<uint32_t>&, uint32_t)>;
// Constructs an instance with the given target |env|, which is used to
// decode the binary to be reduced later.
//
// The constructed instance will have an empty message consumer, which just
// ignores all messages from the library. Use SetMessageConsumer() to supply
// one if messages are of concern.
//
// The constructed instance also needs to have an interestingness function
// set and some reduction passes added to it in order to be useful.
explicit Reducer(spv_target_env env);
// Disables copy/move constructor/assignment operations.
Reducer(const Reducer&) = delete;
Reducer(Reducer&&) = delete;
Reducer& operator=(const Reducer&) = delete;
Reducer& operator=(Reducer&&) = delete;
// Destructs this instance.
~Reducer();
// Sets the message consumer to the given |consumer|. The |consumer| will be
// invoked once for each message communicated from the library.
void SetMessageConsumer(MessageConsumer consumer);
// Sets the function that will be used to decide whether a reduced binary
// turned out to be interesting.
void SetInterestingnessFunction(
InterestingnessFunction interestingness_function);
// Adds a reduction pass to the sequence of passes that will be iterated
// over.
void AddReductionPass(std::unique_ptr<ReductionPass>&& reduction_pass);
// Reduces the given SPIR-V module |binary_out|.
// The reduced binary ends up in |binary_out|.
// A status is returned.
ReductionResultStatus Run(std::vector<uint32_t>&& binary_in,
std::vector<uint32_t>* binary_out,
spv_const_reducer_options options) const;
private:
struct Impl; // Opaque struct for holding internal data.
std::unique_ptr<Impl> impl_; // Unique pointer to internal data.
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_REDUCER_H_

View File

@ -0,0 +1,27 @@
// 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 "reduction_opportunity.h"
namespace spvtools {
namespace reduce {
void ReductionOpportunity::TryToApply() {
if (PreconditionHolds()) {
Apply();
}
}
} // namespace reduce
} // namespace spvtools

View File

@ -0,0 +1,46 @@
// 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.
#ifndef SOURCE_REDUCE_REDUCTION_OPPORTUNITY_H_
#define SOURCE_REDUCE_REDUCTION_OPPORTUNITY_H_
#include "spirv-tools/libspirv.hpp"
namespace spvtools {
namespace reduce {
// Abstract class capturing an opportunity to apply a reducing transformation.
class ReductionOpportunity {
public:
ReductionOpportunity() = default;
virtual ~ReductionOpportunity() = default;
// Determines whether the opportunity can be applied; it may have been viable
// when discovered but later disabled by the application of some other
// reduction opportunity.
virtual bool PreconditionHolds() = 0;
// A no-op if PreconditoinHolds() returns false; otherwise applies the
// opportunity.
void TryToApply();
protected:
// Apply the reduction opportunity.
virtual void Apply() = 0;
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_REDUCTION_OPPORTUNITY_H_

View File

@ -0,0 +1,86 @@
// 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 <algorithm>
#include "reduction_pass.h"
#include "source/opt/build_module.h"
namespace spvtools {
namespace reduce {
std::vector<uint32_t> ReductionPass::TryApplyReduction(
const std::vector<uint32_t>& binary) {
// We represent modules as binaries because (a) attempts at reduction need to
// end up in binary form to be passed on to SPIR-V-consuming tools, and (b)
// when we apply a reduction step we need to do it on a fresh version of the
// module as if the reduction step proves to be uninteresting we need to
// backtrack; re-parsing from binary provides a very clean way of cloning the
// module.
std::unique_ptr<opt::IRContext> context =
BuildModule(target_env_, consumer_, binary.data(), binary.size());
assert(context);
std::vector<std::unique_ptr<ReductionOpportunity>> opportunities =
GetAvailableOpportunities(context.get());
if (!is_initialized_) {
is_initialized_ = true;
index_ = 0;
granularity_ = (uint32_t)opportunities.size();
}
if (opportunities.empty()) {
granularity_ = 1;
return std::vector<uint32_t>();
}
assert(granularity_ > 0);
if (index_ >= opportunities.size()) {
index_ = 0;
granularity_ = std::max((uint32_t)1, granularity_ / 2);
return std::vector<uint32_t>();
}
for (uint32_t i = index_;
i < std::min(index_ + granularity_, (uint32_t)opportunities.size());
++i) {
opportunities[i]->TryToApply();
}
index_ += granularity_;
std::vector<uint32_t> result;
context->module()->ToBinary(&result, false);
return result;
}
void ReductionPass::SetMessageConsumer(MessageConsumer consumer) {
consumer_ = std::move(consumer);
}
bool ReductionPass::ReachedMinimumGranularity() const {
if (!is_initialized_) {
// Conceptually we can think that if the pass has not yet been initialized,
// it is operating at unbounded granularity.
return false;
}
assert(granularity_ != 0);
return granularity_ == 1;
}
} // namespace reduce
} // namespace spvtools

View File

@ -0,0 +1,73 @@
// 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.
#ifndef SOURCE_REDUCE_REDUCTION_PASS_H_
#define SOURCE_REDUCE_REDUCTION_PASS_H_
#include "spirv-tools/libspirv.hpp"
#include "reduction_opportunity.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace reduce {
// Abstract class representing a reduction pass, which can be repeatedly
// invoked to find and apply particular reduction opportunities to a SPIR-V
// binary. In the spirit of delta debugging, a pass initially tries to apply
// large chunks of reduction opportunities, iterating through available
// opportunities at a given granularity. When an iteration over available
// opportunities completes, the granularity is reduced and iteration starts
// again, until the minimum granularity is reached.
class ReductionPass {
public:
// Constructs a reduction pass with a given target environment, |target_env|.
// Initially the pass is uninitialized.
explicit ReductionPass(const spv_target_env target_env)
: target_env_(target_env), is_initialized_(false) {}
virtual ~ReductionPass() = default;
// Apply the reduction pass to the given binary.
std::vector<uint32_t> TryApplyReduction(const std::vector<uint32_t>& binary);
// Set a consumer to which relevant messages will be directed.
void SetMessageConsumer(MessageConsumer consumer);
// Determines whether the granularity with which reduction opportunities are
// applied has reached a minimum.
bool ReachedMinimumGranularity() const;
// Returns the name of the reduction pass (useful for monitoring reduction
// progress).
virtual std::string GetName() const = 0;
protected:
// Finds the reduction opportunities relevant to this pass that could be
// applied to a given SPIR-V module.
virtual std::vector<std::unique_ptr<ReductionOpportunity>>
GetAvailableOpportunities(opt::IRContext* context) const = 0;
private:
const spv_target_env target_env_;
MessageConsumer consumer_;
bool is_initialized_;
uint32_t index_;
uint32_t granularity_;
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_REDUCTION_PASS_H_

View File

@ -0,0 +1,29 @@
// 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 "source/opt/ir_context.h"
#include "remove_instruction_reduction_opportunity.h"
namespace spvtools {
namespace reduce {
bool RemoveInstructionReductionOpportunity::PreconditionHolds() { return true; }
void RemoveInstructionReductionOpportunity::Apply() {
inst_->context()->KillInst(inst_);
}
} // namespace reduce
} // namespace spvtools

View File

@ -0,0 +1,47 @@
// 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.
#ifndef SOURCE_REDUCE_REMOVE_INSTRUCTION_REDUCTION_OPPORTUNITY_H_
#define SOURCE_REDUCE_REMOVE_INSTRUCTION_REDUCTION_OPPORTUNITY_H_
#include "reduction_opportunity.h"
#include "source/opt/instruction.h"
namespace spvtools {
namespace reduce {
using namespace opt;
// Captures the opportunity to remove an instruction from the SPIR-V module.
class RemoveInstructionReductionOpportunity : public ReductionOpportunity {
public:
// Constructs the opportunity to remove |inst|.
explicit RemoveInstructionReductionOpportunity(Instruction* inst)
: inst_(inst) {}
// This kind of opportunity can be unconditionally applied.
bool PreconditionHolds() override;
protected:
// Remove the instruction.
void Apply() override;
private:
Instruction* inst_;
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_REMOVE_INSTRUCTION_REDUCTION_OPPORTUNITY_H_

View File

@ -0,0 +1,60 @@
// Copyright (c) 2018 Google Inc.
//
// 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 "remove_unreferenced_instruction_reduction_pass.h"
#include "remove_instruction_reduction_opportunity.h"
#include "source/opcode.h"
#include "source/opt/instruction.h"
namespace spvtools {
namespace reduce {
using namespace opt;
std::vector<std::unique_ptr<ReductionOpportunity>>
RemoveUnreferencedInstructionReductionPass::GetAvailableOpportunities(
opt::IRContext* context) const {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
for (auto& function : *context->module()) {
for (auto& block : function) {
for (auto& inst : block) {
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
continue;
}
if (spvOpcodeIsBlockTerminator(inst.opcode()) ||
inst.opcode() == SpvOpSelectionMerge ||
inst.opcode() == SpvOpLoopMerge) {
// In this reduction pass we do not want to affect static control
// flow.
continue;
}
// Given that we're in a block, we should only get here if the
// instruction is not directly related to control flow; i.e., it's
// some straightforward instruction with an unused result, like an
// arithmetic operation or function call.
result.push_back(
MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
}
}
}
return result;
}
std::string RemoveUnreferencedInstructionReductionPass::GetName() const {
return "RemoveUnreferencedInstructionReductionPass";
}
} // namespace reduce
} // namespace spvtools

View File

@ -0,0 +1,53 @@
// 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.
#ifndef SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
#define SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
#include "reduction_pass.h"
namespace spvtools {
namespace reduce {
// A reduction pass for removing non-control-flow instructions in blocks in
// cases where the instruction's id is not referenced. As well as making the
// module smaller, removing an instruction that referenced particular ids may
// create opportunities for subsequently removing the instructions that
// generated those ids.
class RemoveUnreferencedInstructionReductionPass : public ReductionPass {
public:
// Creates the reduction pass in the context of the given target environment
// |target_env|
explicit RemoveUnreferencedInstructionReductionPass(
const spv_target_env target_env)
: ReductionPass(target_env) {}
~RemoveUnreferencedInstructionReductionPass() override = default;
// The name of this pass.
std::string GetName() const final;
protected:
// Finds all opportunities for removing unreferenced instructions in the
// given module.
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
opt::IRContext* context) const final;
private:
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_

View File

@ -0,0 +1,31 @@
// 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 <cstring>
#include "source/spirv_reducer_options.h"
SPIRV_TOOLS_EXPORT spv_reducer_options spvReducerOptionsCreate() {
return new spv_reducer_options_t();
}
SPIRV_TOOLS_EXPORT void spvReducerOptionsDestroy(spv_reducer_options options) {
delete options;
}
SPIRV_TOOLS_EXPORT void spvReducerOptionsSetStepLimit(
spv_reducer_options options, uint32_t step_limit) {
options->step_limit = step_limit;
}

View File

@ -0,0 +1,35 @@
// 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.
#ifndef SOURCE_SPIRV_REDUCER_OPTIONS_H_
#define SOURCE_SPIRV_REDUCER_OPTIONS_H_
#include "spirv-tools/libspirv.h"
#include <string>
#include <utility>
// The default maximum number of steps for the reducer to run before giving up.
const uint32_t kDefaultStepLimit = 250;
// Manages command line options passed to the SPIR-V Reducer. New struct
// members may be added for any new option.
struct spv_reducer_options_t {
spv_reducer_options_t() : step_limit(kDefaultStepLimit) {}
// The number of steps the reducer will run for before giving up.
uint32_t step_limit;
};
#endif // SOURCE_SPIRV_REDUCER_OPTIONS_H_

View File

@ -207,6 +207,7 @@ add_spvtools_unittest(
add_subdirectory(comp)
add_subdirectory(link)
add_subdirectory(opt)
add_subdirectory(reduce)
add_subdirectory(stats)
add_subdirectory(tools)
add_subdirectory(util)

View File

@ -0,0 +1,23 @@
# 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.
add_spvtools_unittest(TARGET reduce
SRCS operand_to_constant_reduction_pass_test.cpp
reduce_test_util.cpp
reduce_test_util.h
reducer_test.cpp
remove_unreferenced_instruction_reduction_pass_test.cpp
LIBS SPIRV-Tools-reduce
)

View File

@ -0,0 +1,156 @@
// 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 "reduce_test_util.h"
#include "source/opt/build_module.h"
#include "source/reduce/operand_to_const_reduction_pass.h"
namespace spvtools {
namespace reduce {
namespace {
TEST(OperandToConstantReductionPassTest, BasicCheck) {
std::string prologue = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %37
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %9 "buf1"
OpMemberName %9 0 "f"
OpName %11 ""
OpName %24 "buf2"
OpMemberName %24 0 "i"
OpName %26 ""
OpName %37 "_GLF_color"
OpMemberDecorate %9 0 Offset 0
OpDecorate %9 Block
OpDecorate %11 DescriptorSet 0
OpDecorate %11 Binding 1
OpMemberDecorate %24 0 Offset 0
OpDecorate %24 Block
OpDecorate %26 DescriptorSet 0
OpDecorate %26 Binding 2
OpDecorate %37 Location 0
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%9 = OpTypeStruct %6
%10 = OpTypePointer Uniform %9
%11 = OpVariable %10 Uniform
%12 = OpTypeInt 32 1
%13 = OpConstant %12 0
%14 = OpTypePointer Uniform %6
%20 = OpConstant %6 2
%24 = OpTypeStruct %12
%25 = OpTypePointer Uniform %24
%26 = OpVariable %25 Uniform
%27 = OpTypePointer Uniform %12
%33 = OpConstant %12 3
%35 = OpTypeVector %6 4
%36 = OpTypePointer Output %35
%37 = OpVariable %36 Output
%4 = OpFunction %2 None %3
%5 = OpLabel
%15 = OpAccessChain %14 %11 %13
%16 = OpLoad %6 %15
%19 = OpFAdd %6 %16 %16
%21 = OpFAdd %6 %19 %20
%28 = OpAccessChain %27 %26 %13
%29 = OpLoad %12 %28
)";
std::string epilogue = R"(
%45 = OpConvertSToF %6 %34
%46 = OpCompositeConstruct %35 %16 %21 %43 %45
OpStore %37 %46
OpReturn
OpFunctionEnd
)";
std::string original = prologue + R"(
%32 = OpIAdd %12 %29 %29
%34 = OpIAdd %12 %32 %33
%43 = OpConvertSToF %6 %29
)" + epilogue;
std::string expected = prologue + R"(
%32 = OpIAdd %12 %13 %13 ; %29 -> %13 x 2
%34 = OpIAdd %12 %13 %33 ; %32 -> %13
%43 = OpConvertSToF %6 %13 ; %29 -> %13
)" + epilogue;
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, original, kReduceAssembleOption);
const auto pass = TestSubclass<OperandToConstReductionPass>(env);
const auto ops = pass.WrapGetAvailableOpportunities(context.get());
ASSERT_EQ(17, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
ASSERT_TRUE(ops[1]->PreconditionHolds());
ops[1]->TryToApply();
ASSERT_TRUE(ops[2]->PreconditionHolds());
ops[2]->TryToApply();
ASSERT_TRUE(ops[3]->PreconditionHolds());
ops[3]->TryToApply();
CheckEqual(env, expected, context.get());
}
TEST(OperandToConstantReductionPassTest, WithCalledFunction) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %10 %12
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeVector %6 4
%8 = OpTypeFunction %7
%9 = OpTypePointer Output %7
%10 = OpVariable %9 Output
%11 = OpTypePointer Input %7
%12 = OpVariable %11 Input
%13 = OpConstant %6 0
%14 = OpConstantComposite %7 %13 %13 %13 %13
%4 = OpFunction %2 None %3
%5 = OpLabel
%15 = OpFunctionCall %7 %16
OpReturn
OpFunctionEnd
%16 = OpFunction %7 None %8
%17 = OpLabel
OpReturnValue %14
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, shader, kReduceAssembleOption);
const auto pass = TestSubclass<OperandToConstReductionPass>(env);
const auto ops = pass.WrapGetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
} // namespace
} // namespace reduce
} // namespace spvtools

View File

@ -0,0 +1,52 @@
// 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 "reduce_test_util.h"
namespace spvtools {
namespace reduce {
void CheckEqual(const spv_target_env env,
const std::vector<uint32_t>& expected_binary,
const std::vector<uint32_t>& actual_binary) {
if (expected_binary != actual_binary) {
SpirvTools t(env);
std::string expected_disassembled;
std::string actual_disassembled;
ASSERT_TRUE(t.Disassemble(expected_binary, &expected_disassembled,
kReduceDisassembleOption));
ASSERT_TRUE(t.Disassemble(actual_binary, &actual_disassembled,
kReduceDisassembleOption));
ASSERT_EQ(expected_disassembled, actual_disassembled);
}
}
void CheckEqual(const spv_target_env env, const std::string& expected_text,
const std::vector<uint32_t>& actual_binary) {
std::vector<uint32_t> expected_binary;
SpirvTools t(env);
ASSERT_TRUE(
t.Assemble(expected_text, &expected_binary, kReduceAssembleOption));
CheckEqual(env, expected_binary, actual_binary);
}
void CheckEqual(const spv_target_env env, const std::string& expected_text,
const opt::IRContext* actual_ir) {
std::vector<uint32_t> actual_binary;
actual_ir->module()->ToBinary(&actual_binary, false);
CheckEqual(env, expected_text, actual_binary);
}
} // namespace reduce
} // namespace spvtools

View File

@ -0,0 +1,70 @@
// 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.
#ifndef TEST_REDUCE_REDUCE_TEST_UTIL_H_
#define TEST_REDUCE_REDUCE_TEST_UTIL_H_
#include "gtest/gtest.h"
#include "source/opt/ir_context.h"
#include "source/reduce/reduction_opportunity.h"
#include "spirv-tools/libspirv.h"
namespace spvtools {
namespace reduce {
// A helper class that subclasses a given reduction pass class in order to
// provide a wrapper for its protected methods.
template <class ReductionPassT>
class TestSubclass : public ReductionPassT {
public:
// Creates an instance of the reduction pass subclass with respect to target
// environment |env|.
explicit TestSubclass(const spv_target_env env) : ReductionPassT(env) {}
~TestSubclass() = default;
// A wrapper for GetAvailableOpportunities(...)
std::vector<std::unique_ptr<ReductionOpportunity>>
WrapGetAvailableOpportunities(opt::IRContext* context) const {
return ReductionPassT::GetAvailableOpportunities(context);
}
};
// Checks whether the given binaries are bit-wise equal.
void CheckEqual(spv_target_env env,
const std::vector<uint32_t>& expected_binary,
const std::vector<uint32_t>& actual_binary);
// Assembles the given text and check whether the resulting binary is bit-wise
// equal to the given binary.
void CheckEqual(spv_target_env env, const std::string& expected_text,
const std::vector<uint32_t>& actual_binary);
// Assembles the given text and turns the given IR into binary, then checks
// whether the resulting binaries are bit-wise equal.
void CheckEqual(spv_target_env env, const std::string& expected_text,
const opt::IRContext* actual_ir);
// Assembly options for writing reduction tests. It simplifies matters if
// numeric ids do not change.
const uint32_t kReduceAssembleOption =
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS;
// Disassembly options for writing reduction tests.
const uint32_t kReduceDisassembleOption =
SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | SPV_BINARY_TO_TEXT_OPTION_INDENT;
} // namespace reduce
} // namespace spvtools
#endif // TEST_REDUCE_REDUCE_TEST_UTIL_H_

View File

@ -0,0 +1,243 @@
// 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 "reduce_test_util.h"
#include "source/reduce/operand_to_const_reduction_pass.h"
#include "source/reduce/reducer.h"
#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
namespace spvtools {
namespace reduce {
namespace {
// Don't print reducer info during testing.
void NopDiagnostic(spv_message_level_t /*level*/, const char* /*source*/,
const spv_position_t& /*position*/,
const char* /*message*/) {}
// This changes is its mind each time IsInteresting is invoked as to whether the
// binary is interesting, until some limit is reached after which the binary is
// always deemed interesting. This is useful to test that reduction passes
// interleave in interesting ways for a while, and then always succeed after
// some point; the latter is important to end up with a predictable final
// reduced binary for tests.
class PingPongInteresting {
public:
explicit PingPongInteresting(uint32_t always_interesting_after)
: is_interesting_(true),
always_interesting_after_(always_interesting_after),
count_(0) {}
bool IsInteresting(const std::vector<uint32_t>&) {
bool result;
if (count_ > always_interesting_after_) {
result = true;
} else {
result = is_interesting_;
is_interesting_ = !is_interesting_;
}
count_++;
return result;
}
private:
bool is_interesting_;
const uint32_t always_interesting_after_;
uint32_t count_;
};
TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) {
std::string original = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %60
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %16 "buf2"
OpMemberName %16 0 "i"
OpName %18 ""
OpName %25 "buf1"
OpMemberName %25 0 "f"
OpName %27 ""
OpName %60 "_GLF_color"
OpMemberDecorate %16 0 Offset 0
OpDecorate %16 Block
OpDecorate %18 DescriptorSet 0
OpDecorate %18 Binding 2
OpMemberDecorate %25 0 Offset 0
OpDecorate %25 Block
OpDecorate %27 DescriptorSet 0
OpDecorate %27 Binding 1
OpDecorate %60 Location 0
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%9 = OpConstant %6 0
%16 = OpTypeStruct %6
%17 = OpTypePointer Uniform %16
%18 = OpVariable %17 Uniform
%19 = OpTypePointer Uniform %6
%22 = OpTypeBool
%100 = OpConstantTrue %22
%24 = OpTypeFloat 32
%25 = OpTypeStruct %24
%26 = OpTypePointer Uniform %25
%27 = OpVariable %26 Uniform
%28 = OpTypePointer Uniform %24
%31 = OpConstant %24 2
%56 = OpConstant %6 1
%58 = OpTypeVector %24 4
%59 = OpTypePointer Output %58
%60 = OpVariable %59 Output
%72 = OpUndef %24
%74 = OpUndef %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpBranch %10
%10 = OpLabel
%73 = OpPhi %6 %74 %5 %77 %34
%71 = OpPhi %24 %72 %5 %76 %34
%70 = OpPhi %6 %9 %5 %57 %34
%20 = OpAccessChain %19 %18 %9
%21 = OpLoad %6 %20
%23 = OpSLessThan %22 %70 %21
OpLoopMerge %12 %34 None
OpBranchConditional %23 %11 %12
%11 = OpLabel
%29 = OpAccessChain %28 %27 %9
%30 = OpLoad %24 %29
%32 = OpFOrdGreaterThan %22 %30 %31
OpSelectionMerge %34 None
OpBranchConditional %32 %33 %46
%33 = OpLabel
%40 = OpFAdd %24 %71 %30
%45 = OpISub %6 %73 %21
OpBranch %34
%46 = OpLabel
%50 = OpFMul %24 %71 %30
%54 = OpSDiv %6 %73 %21
OpBranch %34
%34 = OpLabel
%77 = OpPhi %6 %45 %33 %54 %46
%76 = OpPhi %24 %40 %33 %50 %46
%57 = OpIAdd %6 %70 %56
OpBranch %10
%12 = OpLabel
%61 = OpAccessChain %28 %27 %9
%62 = OpLoad %24 %61
%66 = OpConvertSToF %24 %21
%68 = OpConvertSToF %24 %73
%69 = OpCompositeConstruct %58 %62 %71 %66 %68
OpStore %60 %69
OpReturn
OpFunctionEnd
)";
std::string expected = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %60
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %16 "buf2"
OpMemberName %16 0 "i"
OpName %18 ""
OpName %25 "buf1"
OpMemberName %25 0 "f"
OpName %27 ""
OpName %60 "_GLF_color"
OpMemberDecorate %16 0 Offset 0
OpDecorate %16 Block
OpDecorate %18 DescriptorSet 0
OpDecorate %18 Binding 2
OpMemberDecorate %25 0 Offset 0
OpDecorate %25 Block
OpDecorate %27 DescriptorSet 0
OpDecorate %27 Binding 1
OpDecorate %60 Location 0
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%9 = OpConstant %6 0
%16 = OpTypeStruct %6
%17 = OpTypePointer Uniform %16
%18 = OpVariable %17 Uniform
%19 = OpTypePointer Uniform %6
%22 = OpTypeBool
%100 = OpConstantTrue %22
%24 = OpTypeFloat 32
%25 = OpTypeStruct %24
%26 = OpTypePointer Uniform %25
%27 = OpVariable %26 Uniform
%28 = OpTypePointer Uniform %24
%31 = OpConstant %24 2
%56 = OpConstant %6 1
%58 = OpTypeVector %24 4
%59 = OpTypePointer Output %58
%60 = OpVariable %59 Output
%72 = OpUndef %24
%74 = OpUndef %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %12 %34 None
OpBranchConditional %100 %11 %12
%11 = OpLabel
OpSelectionMerge %34 None
OpBranchConditional %100 %33 %46
%33 = OpLabel
OpBranch %34
%46 = OpLabel
OpBranch %34
%34 = OpLabel
OpBranch %10
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
Reducer reducer(env);
PingPongInteresting ping_pong_interesting(10);
reducer.SetMessageConsumer(NopDiagnostic);
reducer.SetInterestingnessFunction(
[&](const std::vector<uint32_t>& binary, uint32_t) -> bool {
return ping_pong_interesting.IsInteresting(binary);
});
reducer.AddReductionPass(MakeUnique<OperandToConstReductionPass>(env));
reducer.AddReductionPass(
MakeUnique<RemoveUnreferencedInstructionReductionPass>(env));
std::vector<uint32_t> binary_in;
SpirvTools t(env);
ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
std::vector<uint32_t> binary_out;
spvtools::ReducerOptions reducer_options;
reducer_options.set_step_limit(500);
reducer.Run(std::move(binary_in), &binary_out, reducer_options);
CheckEqual(env, expected, binary_out);
}
} // namespace
} // namespace reduce
} // namespace spvtools

View File

@ -0,0 +1,230 @@
// 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 "reduce_test_util.h"
#include "source/opt/build_module.h"
#include "source/reduce/reduction_opportunity.h"
#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
namespace spvtools {
namespace reduce {
namespace {
TEST(RemoveUnreferencedInstructionReductionPassTest, RemoveStores) {
const std::string prologue = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "a"
OpName %10 "b"
OpName %12 "c"
OpName %14 "d"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 10
%11 = OpConstant %6 20
%13 = OpConstant %6 30
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%10 = OpVariable %7 Function
%12 = OpVariable %7 Function
%14 = OpVariable %7 Function
)";
const std::string epilogue = R"(
OpReturn
OpFunctionEnd
)";
const std::string original = prologue + R"(
OpStore %8 %9
OpStore %10 %11
OpStore %12 %13
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
const std::string expected = prologue + R"(
OpStore %12 %13
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, original, kReduceAssembleOption);
const auto pass =
TestSubclass<RemoveUnreferencedInstructionReductionPass>(env);
const auto ops = pass.WrapGetAvailableOpportunities(context.get());
ASSERT_EQ(4, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
ASSERT_TRUE(ops[1]->PreconditionHolds());
ops[1]->TryToApply();
CheckEqual(env, expected, context.get());
}
TEST(RemoveUnreferencedInstructionReductionPassTest, ApplyReduction) {
const std::string prologue = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "a"
OpName %10 "b"
OpName %12 "c"
OpName %14 "d"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 10
%11 = OpConstant %6 20
%13 = OpConstant %6 30
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%10 = OpVariable %7 Function
%12 = OpVariable %7 Function
%14 = OpVariable %7 Function
)";
const std::string epilogue = R"(
OpReturn
OpFunctionEnd
)";
const std::string original = prologue + R"(
OpStore %8 %9
OpStore %10 %11
OpStore %12 %13
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
const auto env = SPV_ENV_UNIVERSAL_1_3;
std::vector<uint32_t> binary;
SpirvTools t(env);
ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
auto pass = TestSubclass<RemoveUnreferencedInstructionReductionPass>(env);
{
// Attempt 1 should remove everything removable.
const std::string expected_reduced = prologue + R"(
%15 = OpLoad %6 %8
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
// Attempt 2 should fail as pass with granularity 4 got to end.
ASSERT_EQ(0, pass.TryApplyReduction(binary).size());
{
// Attempt 3 should remove first two removable statements.
const std::string expected_reduced = prologue + R"(
OpStore %12 %13
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
{
// Attempt 4 should remove last two removable statements.
const std::string expected_reduced = prologue + R"(
OpStore %8 %9
OpStore %10 %11
%15 = OpLoad %6 %8
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
// Attempt 5 should fail as pass with granularity 2 got to end.
ASSERT_EQ(0, pass.TryApplyReduction(binary).size());
{
// Attempt 6 should remove first removable statement.
const std::string expected_reduced = prologue + R"(
OpStore %10 %11
OpStore %12 %13
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
{
// Attempt 7 should remove second removable statement.
const std::string expected_reduced = prologue + R"(
OpStore %8 %9
OpStore %12 %13
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
{
// Attempt 8 should remove third removable statement.
const std::string expected_reduced = prologue + R"(
OpStore %8 %9
OpStore %10 %11
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
{
// Attempt 9 should remove fourth removable statement.
const std::string expected_reduced = prologue + R"(
OpStore %8 %9
OpStore %10 %11
OpStore %12 %13
%15 = OpLoad %6 %8
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
// Attempt 10 should fail as pass with granularity 1 got to end.
ASSERT_EQ(0, pass.TryApplyReduction(binary).size());
ASSERT_TRUE(pass.ReachedMinimumGranularity());
}
} // namespace
} // namespace reduce
} // namespace spvtools

View File

@ -42,6 +42,7 @@ if (NOT ${SPIRV_SKIP_EXECUTABLES})
add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp util/cli_consumer.cpp LIBS ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-link SRCS link/linker.cpp LIBS SPIRV-Tools-link ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-stats
SRCS stats/stats.cpp
@ -61,7 +62,7 @@ if (NOT ${SPIRV_SKIP_EXECUTABLES})
${SPIRV_HEADER_INCLUDE_DIR})
set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt spirv-stats
spirv-cfg spirv-link)
spirv-cfg spirv-link spirv-reduce)
if(SPIRV_BUILD_COMPRESSION)
add_spvtools_tool(TARGET spirv-markv

230
tools/reduce/reduce.cpp Normal file
View File

@ -0,0 +1,230 @@
// 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/reducer.h"
#include "source/reduce/remove_unreferenced_instruction_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<OperandToConstReductionPass>(target_env));
reducer.AddReductionPass(
spvtools::MakeUnique<RemoveUnreferencedInstructionReductionPass>(
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;
}