1 // Copyright (c) 2018 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "source/reduce/reducer.h"
16
17 #include <cassert>
18 #include <sstream>
19
20 #include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
21 #include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
22 #include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
23 #include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
24 #include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
25 #include "source/reduce/remove_block_reduction_opportunity_finder.h"
26 #include "source/reduce/remove_function_reduction_opportunity_finder.h"
27 #include "source/reduce/remove_selection_reduction_opportunity_finder.h"
28 #include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
29 #include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h"
30 #include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
31 #include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
32 #include "source/spirv_reducer_options.h"
33
34 namespace spvtools {
35 namespace reduce {
36
Reducer(spv_target_env target_env)37 Reducer::Reducer(spv_target_env target_env) : target_env_(target_env) {}
38
39 Reducer::~Reducer() = default;
40
SetMessageConsumer(MessageConsumer c)41 void Reducer::SetMessageConsumer(MessageConsumer c) {
42 for (auto& pass : passes_) {
43 pass->SetMessageConsumer(c);
44 }
45 for (auto& pass : cleanup_passes_) {
46 pass->SetMessageConsumer(c);
47 }
48 consumer_ = std::move(c);
49 }
50
SetInterestingnessFunction(Reducer::InterestingnessFunction interestingness_function)51 void Reducer::SetInterestingnessFunction(
52 Reducer::InterestingnessFunction interestingness_function) {
53 interestingness_function_ = std::move(interestingness_function);
54 }
55
Run(const std::vector<uint32_t> & binary_in,std::vector<uint32_t> * binary_out,spv_const_reducer_options options,spv_validator_options validator_options)56 Reducer::ReductionResultStatus Reducer::Run(
57 const std::vector<uint32_t>& binary_in, std::vector<uint32_t>* binary_out,
58 spv_const_reducer_options options,
59 spv_validator_options validator_options) {
60 std::vector<uint32_t> current_binary(binary_in);
61
62 spvtools::SpirvTools tools(target_env_);
63 assert(tools.IsValid() && "Failed to create SPIRV-Tools interface");
64
65 // Keeps track of how many reduction attempts have been tried. Reduction
66 // bails out if this reaches a given limit.
67 uint32_t reductions_applied = 0;
68
69 // Initial state should be valid.
70 if (!tools.Validate(¤t_binary[0], current_binary.size(),
71 validator_options)) {
72 consumer_(SPV_MSG_INFO, nullptr, {},
73 "Initial binary is invalid; stopping.");
74 return Reducer::ReductionResultStatus::kInitialStateInvalid;
75 }
76
77 // Initial state should be interesting.
78 if (!interestingness_function_(current_binary, reductions_applied)) {
79 consumer_(SPV_MSG_INFO, nullptr, {},
80 "Initial state was not interesting; stopping.");
81 return Reducer::ReductionResultStatus::kInitialStateNotInteresting;
82 }
83
84 Reducer::ReductionResultStatus result =
85 RunPasses(&passes_, options, validator_options, tools, ¤t_binary,
86 &reductions_applied);
87
88 if (result == Reducer::ReductionResultStatus::kComplete) {
89 // Cleanup passes.
90 result = RunPasses(&cleanup_passes_, options, validator_options, tools,
91 ¤t_binary, &reductions_applied);
92 }
93
94 if (result == Reducer::ReductionResultStatus::kComplete) {
95 consumer_(SPV_MSG_INFO, nullptr, {}, "No more to reduce; stopping.");
96 }
97
98 // Even if the reduction has failed by this point (e.g. due to producing an
99 // invalid binary), we still update the output binary for better debugging.
100 *binary_out = std::move(current_binary);
101
102 return result;
103 }
104
AddDefaultReductionPasses()105 void Reducer::AddDefaultReductionPasses() {
106 AddReductionPass(
107 spvtools::MakeUnique<RemoveUnusedInstructionReductionOpportunityFinder>(
108 false));
109 AddReductionPass(
110 spvtools::MakeUnique<OperandToUndefReductionOpportunityFinder>());
111 AddReductionPass(
112 spvtools::MakeUnique<OperandToConstReductionOpportunityFinder>());
113 AddReductionPass(
114 spvtools::MakeUnique<OperandToDominatingIdReductionOpportunityFinder>());
115 AddReductionPass(spvtools::MakeUnique<
116 StructuredLoopToSelectionReductionOpportunityFinder>());
117 AddReductionPass(
118 spvtools::MakeUnique<MergeBlocksReductionOpportunityFinder>());
119 AddReductionPass(
120 spvtools::MakeUnique<RemoveFunctionReductionOpportunityFinder>());
121 AddReductionPass(
122 spvtools::MakeUnique<RemoveBlockReductionOpportunityFinder>());
123 AddReductionPass(
124 spvtools::MakeUnique<RemoveSelectionReductionOpportunityFinder>());
125 AddReductionPass(
126 spvtools::MakeUnique<
127 ConditionalBranchToSimpleConditionalBranchOpportunityFinder>());
128 AddReductionPass(
129 spvtools::MakeUnique<SimpleConditionalBranchToBranchOpportunityFinder>());
130 AddReductionPass(spvtools::MakeUnique<
131 RemoveUnusedStructMemberReductionOpportunityFinder>());
132
133 // Cleanup passes.
134
135 AddCleanupReductionPass(
136 spvtools::MakeUnique<RemoveUnusedInstructionReductionOpportunityFinder>(
137 true));
138 }
139
AddReductionPass(std::unique_ptr<ReductionOpportunityFinder> finder)140 void Reducer::AddReductionPass(
141 std::unique_ptr<ReductionOpportunityFinder> finder) {
142 passes_.push_back(
143 spvtools::MakeUnique<ReductionPass>(target_env_, std::move(finder)));
144 }
145
AddCleanupReductionPass(std::unique_ptr<ReductionOpportunityFinder> finder)146 void Reducer::AddCleanupReductionPass(
147 std::unique_ptr<ReductionOpportunityFinder> finder) {
148 cleanup_passes_.push_back(
149 spvtools::MakeUnique<ReductionPass>(target_env_, std::move(finder)));
150 }
151
ReachedStepLimit(uint32_t current_step,spv_const_reducer_options options)152 bool Reducer::ReachedStepLimit(uint32_t current_step,
153 spv_const_reducer_options options) {
154 return current_step >= options->step_limit;
155 }
156
RunPasses(std::vector<std::unique_ptr<ReductionPass>> * passes,spv_const_reducer_options options,spv_validator_options validator_options,const SpirvTools & tools,std::vector<uint32_t> * current_binary,uint32_t * const reductions_applied)157 Reducer::ReductionResultStatus Reducer::RunPasses(
158 std::vector<std::unique_ptr<ReductionPass>>* passes,
159 spv_const_reducer_options options, spv_validator_options validator_options,
160 const SpirvTools& tools, std::vector<uint32_t>* current_binary,
161 uint32_t* const reductions_applied) {
162 // Determines whether, on completing one round of reduction passes, it is
163 // worthwhile trying a further round.
164 bool another_round_worthwhile = true;
165
166 // Apply round after round of reduction passes until we hit the reduction
167 // step limit, or deem that another round is not going to be worthwhile.
168 while (!ReachedStepLimit(*reductions_applied, options) &&
169 another_round_worthwhile) {
170 // At the start of a round of reduction passes, assume another round will
171 // not be worthwhile unless we find evidence to the contrary.
172 another_round_worthwhile = false;
173
174 // Iterate through the available passes.
175 for (auto& pass : *passes) {
176 // If this pass hasn't reached its minimum granularity then it's
177 // worth eventually doing another round of reductions, in order to
178 // try this pass at a finer granularity.
179 another_round_worthwhile |= !pass->ReachedMinimumGranularity();
180
181 // Keep applying this pass at its current granularity until it stops
182 // working or we hit the reduction step limit.
183 consumer_(SPV_MSG_INFO, nullptr, {},
184 ("Trying pass " + pass->GetName() + ".").c_str());
185 do {
186 auto maybe_result =
187 pass->TryApplyReduction(*current_binary, options->target_function);
188 if (maybe_result.empty()) {
189 // For this round, the pass has no more opportunities (chunks) to
190 // apply, so move on to the next pass.
191 consumer_(
192 SPV_MSG_INFO, nullptr, {},
193 ("Pass " + pass->GetName() + " did not make a reduction step.")
194 .c_str());
195 break;
196 }
197 bool interesting = false;
198 std::stringstream stringstream;
199 (*reductions_applied)++;
200 stringstream << "Pass " << pass->GetName() << " made reduction step "
201 << *reductions_applied << ".";
202 consumer_(SPV_MSG_INFO, nullptr, {}, (stringstream.str().c_str()));
203 if (!tools.Validate(&maybe_result[0], maybe_result.size(),
204 validator_options)) {
205 // The reduction step went wrong and an invalid binary was produced.
206 // By design, this shouldn't happen; this is a safeguard to stop an
207 // invalid binary from being regarded as interesting.
208 consumer_(SPV_MSG_INFO, nullptr, {},
209 "Reduction step produced an invalid binary.");
210 if (options->fail_on_validation_error) {
211 // In this mode, we fail, so we update the current binary so it is
212 // output for debugging.
213 *current_binary = std::move(maybe_result);
214 return Reducer::ReductionResultStatus::kStateInvalid;
215 }
216 } else if (interestingness_function_(maybe_result,
217 *reductions_applied)) {
218 // Success! The binary produced by this reduction step is
219 // interesting, so make it the binary of interest henceforth, and
220 // note that it's worth doing another round of reduction passes.
221 consumer_(SPV_MSG_INFO, nullptr, {}, "Reduction step succeeded.");
222 *current_binary = std::move(maybe_result);
223 interesting = true;
224 another_round_worthwhile = true;
225 }
226 // We must call this before the next call to TryApplyReduction.
227 pass->NotifyInteresting(interesting);
228 // Bail out if the reduction step limit has been reached.
229 } while (!ReachedStepLimit(*reductions_applied, options));
230 }
231 }
232
233 // Report whether reduction completed, or bailed out early due to reaching
234 // the step limit.
235 if (ReachedStepLimit(*reductions_applied, options)) {
236 consumer_(SPV_MSG_INFO, nullptr, {},
237 "Reached reduction step limit; stopping.");
238 return Reducer::ReductionResultStatus::kReachedStepLimit;
239 }
240
241 // The passes completed successfully, although we may still run more passes.
242 return Reducer::ReductionResultStatus::kComplete;
243 }
244
245 } // namespace reduce
246 } // namespace spvtools
247