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