1 // Copyright (c) 2020 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/fuzz/added_function_reducer.h"
16
17 #include "source/fuzz/instruction_message.h"
18 #include "source/fuzz/replayer.h"
19 #include "source/fuzz/transformation_add_function.h"
20 #include "source/opt/build_module.h"
21 #include "source/opt/ir_context.h"
22 #include "source/reduce/reducer.h"
23
24 namespace spvtools {
25 namespace fuzz {
26
AddedFunctionReducer(spv_target_env target_env,MessageConsumer consumer,const std::vector<uint32_t> & binary_in,const protobufs::FactSequence & initial_facts,const protobufs::TransformationSequence & transformation_sequence_in,uint32_t index_of_add_function_transformation,const Shrinker::InterestingnessFunction & shrinker_interestingness_function,bool validate_during_replay,spv_validator_options validator_options,uint32_t shrinker_step_limit,uint32_t num_existing_shrink_attempts)27 AddedFunctionReducer::AddedFunctionReducer(
28 spv_target_env target_env, MessageConsumer consumer,
29 const std::vector<uint32_t>& binary_in,
30 const protobufs::FactSequence& initial_facts,
31 const protobufs::TransformationSequence& transformation_sequence_in,
32 uint32_t index_of_add_function_transformation,
33 const Shrinker::InterestingnessFunction& shrinker_interestingness_function,
34 bool validate_during_replay, spv_validator_options validator_options,
35 uint32_t shrinker_step_limit, uint32_t num_existing_shrink_attempts)
36 : target_env_(target_env),
37 consumer_(std::move(consumer)),
38 binary_in_(binary_in),
39 initial_facts_(initial_facts),
40 transformation_sequence_in_(transformation_sequence_in),
41 index_of_add_function_transformation_(
42 index_of_add_function_transformation),
43 shrinker_interestingness_function_(shrinker_interestingness_function),
44 validate_during_replay_(validate_during_replay),
45 validator_options_(validator_options),
46 shrinker_step_limit_(shrinker_step_limit),
47 num_existing_shrink_attempts_(num_existing_shrink_attempts),
48 num_reducer_interestingness_function_invocations_(0) {}
49
50 AddedFunctionReducer::~AddedFunctionReducer() = default;
51
Run()52 AddedFunctionReducer::AddedFunctionReducerResult AddedFunctionReducer::Run() {
53 // Replay all transformations before the AddFunction transformation, then
54 // add the raw function associated with the AddFunction transformation.
55 std::vector<uint32_t> binary_to_reduce;
56 std::unordered_set<uint32_t> irrelevant_pointee_global_variables;
57 ReplayPrefixAndAddFunction(&binary_to_reduce,
58 &irrelevant_pointee_global_variables);
59
60 // Set up spirv-reduce to use our very specific interestingness function.
61 reduce::Reducer reducer(target_env_);
62 reducer.SetMessageConsumer(consumer_);
63 reducer.AddDefaultReductionPasses();
64 reducer.SetInterestingnessFunction(
65 [this, &irrelevant_pointee_global_variables](
66 const std::vector<uint32_t>& binary_under_reduction,
67 uint32_t /*unused*/) {
68 return InterestingnessFunctionForReducingAddedFunction(
69 binary_under_reduction, irrelevant_pointee_global_variables);
70 });
71
72 // Instruct spirv-reduce to only target the function with the id associated
73 // with the AddFunction transformation that we care about.
74 spvtools::ReducerOptions reducer_options;
75 reducer_options.set_target_function(GetAddedFunctionId());
76 // Bound the number of reduction steps that spirv-reduce can make according
77 // to the overall shrinker step limit and the number of shrink attempts that
78 // have already been tried.
79 assert(shrinker_step_limit_ > num_existing_shrink_attempts_ &&
80 "The added function reducer should not have been invoked.");
81 reducer_options.set_step_limit(shrinker_step_limit_ -
82 num_existing_shrink_attempts_);
83
84 // Run spirv-reduce.
85 std::vector<uint32_t> reduced_binary;
86 auto reducer_result =
87 reducer.Run(std::move(binary_to_reduce), &reduced_binary, reducer_options,
88 validator_options_);
89 if (reducer_result != reduce::Reducer::kComplete &&
90 reducer_result != reduce::Reducer::kReachedStepLimit) {
91 return {AddedFunctionReducerResultStatus::kReductionFailed,
92 std::vector<uint32_t>(), protobufs::TransformationSequence(), 0};
93 }
94
95 // Provide the outer shrinker with an adapted sequence of transformations in
96 // which the AddFunction transformation of interest has been simplified to use
97 // the version of the added function that appears in |reduced_binary|.
98 std::vector<uint32_t> binary_out;
99 protobufs::TransformationSequence transformation_sequence_out;
100 ReplayAdaptedTransformations(reduced_binary, &binary_out,
101 &transformation_sequence_out);
102 // We subtract 1 from |num_reducer_interestingness_function_invocations_| to
103 // account for the fact that spirv-reduce invokes its interestingness test
104 // once before reduction commences in order to check that the initial module
105 // is interesting.
106 assert(num_reducer_interestingness_function_invocations_ > 0 &&
107 "At a minimum spirv-reduce should have invoked its interestingness "
108 "test once.");
109 return {AddedFunctionReducerResultStatus::kComplete, std::move(binary_out),
110 std::move(transformation_sequence_out),
111 num_reducer_interestingness_function_invocations_ - 1};
112 }
113
InterestingnessFunctionForReducingAddedFunction(const std::vector<uint32_t> & binary_under_reduction,const std::unordered_set<uint32_t> & irrelevant_pointee_global_variables)114 bool AddedFunctionReducer::InterestingnessFunctionForReducingAddedFunction(
115 const std::vector<uint32_t>& binary_under_reduction,
116 const std::unordered_set<uint32_t>& irrelevant_pointee_global_variables) {
117 uint32_t counter_for_shrinker_interestingness_function =
118 num_existing_shrink_attempts_ +
119 num_reducer_interestingness_function_invocations_;
120 num_reducer_interestingness_function_invocations_++;
121
122 // The reduced version of the added function must be limited to accessing
123 // global variables appearing in |irrelevant_pointee_global_variables|. This
124 // is to guard against the possibility of spirv-reduce changing a reference
125 // to an irrelevant global to a reference to a regular global variable, which
126 // could cause the added function to change the semantics of the original
127 // module.
128 auto ir_context =
129 BuildModule(target_env_, consumer_, binary_under_reduction.data(),
130 binary_under_reduction.size());
131 assert(ir_context != nullptr && "The binary should be parsable.");
132 for (auto& type_or_value : ir_context->module()->types_values()) {
133 if (type_or_value.opcode() != SpvOpVariable) {
134 continue;
135 }
136 if (irrelevant_pointee_global_variables.count(type_or_value.result_id())) {
137 continue;
138 }
139 if (!ir_context->get_def_use_mgr()->WhileEachUse(
140 &type_or_value,
141 [this, &ir_context](opt::Instruction* user,
142 uint32_t /*unused*/) -> bool {
143 auto block = ir_context->get_instr_block(user);
144 if (block != nullptr &&
145 block->GetParent()->result_id() == GetAddedFunctionId()) {
146 return false;
147 }
148 return true;
149 })) {
150 return false;
151 }
152 }
153
154 // For the binary to be deemed interesting, it must be possible to
155 // successfully apply all the transformations, with the transformation at
156 // index |index_of_add_function_transformation_| simplified to use the version
157 // of the added function from |binary_under_reduction|.
158 //
159 // This might not be the case: spirv-reduce might have removed a chunk of the
160 // added function on which future transformations depend.
161 //
162 // This is an optimization: the assumption is that having already shrunk the
163 // transformation sequence down to minimal form, all transformations have a
164 // role to play, and it's almost certainly a waste of time to invoke the
165 // shrinker's interestingness function if we have eliminated transformations
166 // that the shrinker previously tried to -- but could not -- eliminate.
167 std::vector<uint32_t> binary_out;
168 protobufs::TransformationSequence modified_transformations;
169 ReplayAdaptedTransformations(binary_under_reduction, &binary_out,
170 &modified_transformations);
171 if (transformation_sequence_in_.transformation_size() !=
172 modified_transformations.transformation_size()) {
173 return false;
174 }
175
176 // The resulting binary must be deemed interesting according to the shrinker's
177 // interestingness function.
178 return shrinker_interestingness_function_(
179 binary_out, counter_for_shrinker_interestingness_function);
180 }
181
ReplayPrefixAndAddFunction(std::vector<uint32_t> * binary_out,std::unordered_set<uint32_t> * irrelevant_pointee_global_variables) const182 void AddedFunctionReducer::ReplayPrefixAndAddFunction(
183 std::vector<uint32_t>* binary_out,
184 std::unordered_set<uint32_t>* irrelevant_pointee_global_variables) const {
185 assert(transformation_sequence_in_
186 .transformation(index_of_add_function_transformation_)
187 .has_add_function() &&
188 "A TransformationAddFunction is required at the given index.");
189
190 auto replay_result = Replayer(target_env_, consumer_, binary_in_,
191 initial_facts_, transformation_sequence_in_,
192 index_of_add_function_transformation_,
193 validate_during_replay_, validator_options_)
194 .Run();
195 assert(replay_result.status == Replayer::ReplayerResultStatus::kComplete &&
196 "Replay should succeed");
197 assert(static_cast<uint32_t>(
198 replay_result.applied_transformations.transformation_size()) ==
199 index_of_add_function_transformation_ &&
200 "All requested transformations should have applied.");
201
202 auto* ir_context = replay_result.transformed_module.get();
203
204 for (auto& type_or_value : ir_context->module()->types_values()) {
205 if (type_or_value.opcode() != SpvOpVariable) {
206 continue;
207 }
208 if (replay_result.transformation_context->GetFactManager()
209 ->PointeeValueIsIrrelevant(type_or_value.result_id())) {
210 irrelevant_pointee_global_variables->insert(type_or_value.result_id());
211 }
212 }
213
214 // Add the function associated with the transformation at
215 // |index_of_add_function_transformation| to the module. By construction this
216 // should succeed.
217 const protobufs::TransformationAddFunction&
218 transformation_add_function_message =
219 transformation_sequence_in_
220 .transformation(index_of_add_function_transformation_)
221 .add_function();
222 bool success = TransformationAddFunction(transformation_add_function_message)
223 .TryToAddFunction(ir_context);
224 (void)success; // Keep release mode compilers happy.
225 assert(success && "Addition of the function should have succeeded.");
226
227 // Get the binary representation of the module with this function added.
228 ir_context->module()->ToBinary(binary_out, false);
229 }
230
ReplayAdaptedTransformations(const std::vector<uint32_t> & binary_under_reduction,std::vector<uint32_t> * binary_out,protobufs::TransformationSequence * transformation_sequence_out) const231 void AddedFunctionReducer::ReplayAdaptedTransformations(
232 const std::vector<uint32_t>& binary_under_reduction,
233 std::vector<uint32_t>* binary_out,
234 protobufs::TransformationSequence* transformation_sequence_out) const {
235 assert(index_of_add_function_transformation_ <
236 static_cast<uint32_t>(
237 transformation_sequence_in_.transformation_size()) &&
238 "The relevant add function transformation must be present.");
239 std::unique_ptr<opt::IRContext> ir_context_under_reduction =
240 BuildModule(target_env_, consumer_, binary_under_reduction.data(),
241 binary_under_reduction.size());
242 assert(ir_context_under_reduction && "Error building module.");
243
244 protobufs::TransformationSequence modified_transformations;
245 for (uint32_t i = 0;
246 i <
247 static_cast<uint32_t>(transformation_sequence_in_.transformation_size());
248 i++) {
249 if (i == index_of_add_function_transformation_) {
250 protobufs::TransformationAddFunction modified_add_function =
251 transformation_sequence_in_
252 .transformation(index_of_add_function_transformation_)
253 .add_function();
254 assert(GetAddedFunctionId() ==
255 modified_add_function.instruction(0).result_id() &&
256 "Unexpected result id for added function.");
257 modified_add_function.clear_instruction();
258 for (auto& function : *ir_context_under_reduction->module()) {
259 if (function.result_id() != GetAddedFunctionId()) {
260 continue;
261 }
262 function.ForEachInst(
263 [&modified_add_function](const opt::Instruction* instruction) {
264 *modified_add_function.add_instruction() =
265 MakeInstructionMessage(instruction);
266 });
267 }
268 assert(modified_add_function.instruction_size() > 0 &&
269 "Some instructions for the added function should remain.");
270 *modified_transformations.add_transformation()->mutable_add_function() =
271 modified_add_function;
272 } else {
273 *modified_transformations.add_transformation() =
274 transformation_sequence_in_.transformation(i);
275 }
276 }
277 assert(
278 transformation_sequence_in_.transformation_size() ==
279 modified_transformations.transformation_size() &&
280 "The original and modified transformations should have the same size.");
281 auto replay_result = Replayer(target_env_, consumer_, binary_in_,
282 initial_facts_, modified_transformations,
283 modified_transformations.transformation_size(),
284 validate_during_replay_, validator_options_)
285 .Run();
286 assert(replay_result.status == Replayer::ReplayerResultStatus::kComplete &&
287 "Replay should succeed.");
288 replay_result.transformed_module->module()->ToBinary(binary_out, false);
289 *transformation_sequence_out =
290 std::move(replay_result.applied_transformations);
291 }
292
GetAddedFunctionId() const293 uint32_t AddedFunctionReducer::GetAddedFunctionId() const {
294 return transformation_sequence_in_
295 .transformation(index_of_add_function_transformation_)
296 .add_function()
297 .instruction(0)
298 .result_id();
299 }
300
301 } // namespace fuzz
302 } // namespace spvtools
303