Initial commit for spirv-reduce. (#2056)
Creates a new tool that can be used to reduce failing testcases, similar to creduce.
diff --git a/.gitignore b/.gitignore
index 4752a33..059b18e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,7 @@
# Vim
[._]*.s[a-w][a-z]
*~
+
+# C-Lion
+.idea
+cmake-build-debug
\ No newline at end of file
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index 4fcd276..496d502 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -368,6 +368,8 @@
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 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 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
diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp
index 26c0989..9cb5afe 100644
--- a/include/spirv-tools/libspirv.hpp
+++ b/include/spirv-tools/libspirv.hpp
@@ -144,6 +144,28 @@
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.
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 634dfa5..3f032e1 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -216,6 +216,7 @@
add_subdirectory(comp)
add_subdirectory(opt)
+add_subdirectory(reduce)
add_subdirectory(link)
set(SPIRV_SOURCES
@@ -253,6 +254,7 @@
${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 @@
${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
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
new file mode 100644
index 0000000..995e1e6
--- /dev/null
+++ b/source/reduce/CMakeLists.txt
@@ -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)
diff --git a/source/reduce/change_operand_reduction_opportunity.cpp b/source/reduce/change_operand_reduction_opportunity.cpp
new file mode 100644
index 0000000..5430d3e
--- /dev/null
+++ b/source/reduce/change_operand_reduction_opportunity.cpp
@@ -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
diff --git a/source/reduce/change_operand_reduction_opportunity.h b/source/reduce/change_operand_reduction_opportunity.h
new file mode 100644
index 0000000..ba67cd0
--- /dev/null
+++ b/source/reduce/change_operand_reduction_opportunity.h
@@ -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_
diff --git a/source/reduce/operand_to_const_reduction_pass.cpp b/source/reduce/operand_to_const_reduction_pass.cpp
new file mode 100644
index 0000000..b1b1385
--- /dev/null
+++ b/source/reduce/operand_to_const_reduction_pass.cpp
@@ -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
diff --git a/source/reduce/operand_to_const_reduction_pass.h b/source/reduce/operand_to_const_reduction_pass.h
new file mode 100644
index 0000000..fd7cfa0
--- /dev/null
+++ b/source/reduce/operand_to_const_reduction_pass.h
@@ -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_
diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp
new file mode 100644
index 0000000..9835069
--- /dev/null
+++ b/source/reduce/reducer.cpp
@@ -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
\ No newline at end of file
diff --git a/source/reduce/reducer.h b/source/reduce/reducer.h
new file mode 100644
index 0000000..3a4c26c
--- /dev/null
+++ b/source/reduce/reducer.h
@@ -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_
diff --git a/source/reduce/reduction_opportunity.cpp b/source/reduce/reduction_opportunity.cpp
new file mode 100644
index 0000000..f562678
--- /dev/null
+++ b/source/reduce/reduction_opportunity.cpp
@@ -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
diff --git a/source/reduce/reduction_opportunity.h b/source/reduce/reduction_opportunity.h
new file mode 100644
index 0000000..da382a2
--- /dev/null
+++ b/source/reduce/reduction_opportunity.h
@@ -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_
diff --git a/source/reduce/reduction_pass.cpp b/source/reduce/reduction_pass.cpp
new file mode 100644
index 0000000..befba8b
--- /dev/null
+++ b/source/reduce/reduction_pass.cpp
@@ -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
diff --git a/source/reduce/reduction_pass.h b/source/reduce/reduction_pass.h
new file mode 100644
index 0000000..afb95cc
--- /dev/null
+++ b/source/reduce/reduction_pass.h
@@ -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_
diff --git a/source/reduce/remove_instruction_reduction_opportunity.cpp b/source/reduce/remove_instruction_reduction_opportunity.cpp
new file mode 100644
index 0000000..7b7a74e
--- /dev/null
+++ b/source/reduce/remove_instruction_reduction_opportunity.cpp
@@ -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
diff --git a/source/reduce/remove_instruction_reduction_opportunity.h b/source/reduce/remove_instruction_reduction_opportunity.h
new file mode 100644
index 0000000..471ff15
--- /dev/null
+++ b/source/reduce/remove_instruction_reduction_opportunity.h
@@ -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_
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_pass.cpp b/source/reduce/remove_unreferenced_instruction_reduction_pass.cpp
new file mode 100644
index 0000000..bc4998d
--- /dev/null
+++ b/source/reduce/remove_unreferenced_instruction_reduction_pass.cpp
@@ -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
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_pass.h b/source/reduce/remove_unreferenced_instruction_reduction_pass.h
new file mode 100644
index 0000000..27c3fc2
--- /dev/null
+++ b/source/reduce/remove_unreferenced_instruction_reduction_pass.h
@@ -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_
diff --git a/source/spirv_reducer_options.cpp b/source/spirv_reducer_options.cpp
new file mode 100644
index 0000000..110ea3e
--- /dev/null
+++ b/source/spirv_reducer_options.cpp
@@ -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;
+}
diff --git a/source/spirv_reducer_options.h b/source/spirv_reducer_options.h
new file mode 100644
index 0000000..d48303c
--- /dev/null
+++ b/source/spirv_reducer_options.h
@@ -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_
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 53341a7..9226ea7 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -207,6 +207,7 @@
add_subdirectory(comp)
add_subdirectory(link)
add_subdirectory(opt)
+add_subdirectory(reduce)
add_subdirectory(stats)
add_subdirectory(tools)
add_subdirectory(util)
diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt
new file mode 100644
index 0000000..e3c1e95
--- /dev/null
+++ b/test/reduce/CMakeLists.txt
@@ -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
+ )
+
diff --git a/test/reduce/operand_to_constant_reduction_pass_test.cpp b/test/reduce/operand_to_constant_reduction_pass_test.cpp
new file mode 100644
index 0000000..34cc4a1
--- /dev/null
+++ b/test/reduce/operand_to_constant_reduction_pass_test.cpp
@@ -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
diff --git a/test/reduce/reduce_test_util.cpp b/test/reduce/reduce_test_util.cpp
new file mode 100644
index 0000000..022e7e3
--- /dev/null
+++ b/test/reduce/reduce_test_util.cpp
@@ -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
diff --git a/test/reduce/reduce_test_util.h b/test/reduce/reduce_test_util.h
new file mode 100644
index 0000000..0331799
--- /dev/null
+++ b/test/reduce/reduce_test_util.h
@@ -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_
diff --git a/test/reduce/reducer_test.cpp b/test/reduce/reducer_test.cpp
new file mode 100644
index 0000000..5441a56
--- /dev/null
+++ b/test/reduce/reducer_test.cpp
@@ -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
\ No newline at end of file
diff --git a/test/reduce/remove_unreferenced_instruction_reduction_pass_test.cpp b/test/reduce/remove_unreferenced_instruction_reduction_pass_test.cpp
new file mode 100644
index 0000000..a002fa3
--- /dev/null
+++ b/test/reduce/remove_unreferenced_instruction_reduction_pass_test.cpp
@@ -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
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index 67143d8..9fb3a91 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -42,6 +42,7 @@
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 @@
${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
diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp
new file mode 100644
index 0000000..ac82d66
--- /dev/null
+++ b/tools/reduce/reduce.cpp
@@ -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;
+}