• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2019 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/fuzzer_pass_donate_modules.h"
16 
17 #include <map>
18 #include <queue>
19 #include <set>
20 
21 #include "source/fuzz/call_graph.h"
22 #include "source/fuzz/instruction_message.h"
23 #include "source/fuzz/transformation_add_constant_boolean.h"
24 #include "source/fuzz/transformation_add_constant_composite.h"
25 #include "source/fuzz/transformation_add_constant_null.h"
26 #include "source/fuzz/transformation_add_constant_scalar.h"
27 #include "source/fuzz/transformation_add_function.h"
28 #include "source/fuzz/transformation_add_global_undef.h"
29 #include "source/fuzz/transformation_add_global_variable.h"
30 #include "source/fuzz/transformation_add_spec_constant_op.h"
31 #include "source/fuzz/transformation_add_type_array.h"
32 #include "source/fuzz/transformation_add_type_boolean.h"
33 #include "source/fuzz/transformation_add_type_float.h"
34 #include "source/fuzz/transformation_add_type_function.h"
35 #include "source/fuzz/transformation_add_type_int.h"
36 #include "source/fuzz/transformation_add_type_matrix.h"
37 #include "source/fuzz/transformation_add_type_pointer.h"
38 #include "source/fuzz/transformation_add_type_struct.h"
39 #include "source/fuzz/transformation_add_type_vector.h"
40 
41 namespace spvtools {
42 namespace fuzz {
43 
FuzzerPassDonateModules(opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformations,const std::vector<fuzzerutil::ModuleSupplier> & donor_suppliers)44 FuzzerPassDonateModules::FuzzerPassDonateModules(
45     opt::IRContext* ir_context, TransformationContext* transformation_context,
46     FuzzerContext* fuzzer_context,
47     protobufs::TransformationSequence* transformations,
48     const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers)
49     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
50                  transformations),
51       donor_suppliers_(donor_suppliers) {}
52 
Apply()53 void FuzzerPassDonateModules::Apply() {
54   // If there are no donor suppliers, this fuzzer pass is a no-op.
55   if (donor_suppliers_.empty()) {
56     return;
57   }
58 
59   // Donate at least one module, and probabilistically decide when to stop
60   // donating modules.
61   do {
62     // Choose a donor supplier at random, and get the module that it provides.
63     std::unique_ptr<opt::IRContext> donor_ir_context = donor_suppliers_.at(
64         GetFuzzerContext()->RandomIndex(donor_suppliers_))();
65     assert(donor_ir_context != nullptr && "Supplying of donor failed");
66     assert(
67         fuzzerutil::IsValid(donor_ir_context.get(),
68                             GetTransformationContext()->GetValidatorOptions(),
69                             fuzzerutil::kSilentMessageConsumer) &&
70         "The donor module must be valid");
71     // Donate the supplied module.
72     //
73     // Randomly decide whether to make the module livesafe (see
74     // FactFunctionIsLivesafe); doing so allows it to be used for live code
75     // injection but restricts its behaviour to allow this, and means that its
76     // functions cannot be transformed as if they were arbitrary dead code.
77     bool make_livesafe = GetFuzzerContext()->ChoosePercentage(
78         GetFuzzerContext()->ChanceOfMakingDonorLivesafe());
79     DonateSingleModule(donor_ir_context.get(), make_livesafe);
80   } while (GetFuzzerContext()->ChoosePercentage(
81       GetFuzzerContext()->GetChanceOfDonatingAdditionalModule()));
82 }
83 
DonateSingleModule(opt::IRContext * donor_ir_context,bool make_livesafe)84 void FuzzerPassDonateModules::DonateSingleModule(
85     opt::IRContext* donor_ir_context, bool make_livesafe) {
86   // Check that the donated module has capabilities, supported by the recipient
87   // module.
88   for (const auto& capability_inst : donor_ir_context->capabilities()) {
89     auto capability =
90         static_cast<SpvCapability>(capability_inst.GetSingleWordInOperand(0));
91     if (!GetIRContext()->get_feature_mgr()->HasCapability(capability)) {
92       return;
93     }
94   }
95 
96   // The ids used by the donor module may very well clash with ids defined in
97   // the recipient module.  Furthermore, some instructions defined in the donor
98   // module will be equivalent to instructions defined in the recipient module,
99   // and it is not always legal to re-declare equivalent instructions.  For
100   // example, OpTypeVoid cannot be declared twice.
101   //
102   // To handle this, we maintain a mapping from an id used in the donor module
103   // to the corresponding id that will be used by the donated code when it
104   // appears in the recipient module.
105   //
106   // This mapping is populated in two ways:
107   // (1) by mapping a donor instruction's result id to the id of some equivalent
108   //     existing instruction in the recipient (e.g. this has to be done for
109   //     OpTypeVoid)
110   // (2) by mapping a donor instruction's result id to a freshly chosen id that
111   //     is guaranteed to be different from any id already used by the recipient
112   //     (or from any id already chosen to handle a previous donor id)
113   std::map<uint32_t, uint32_t> original_id_to_donated_id;
114 
115   HandleExternalInstructionImports(donor_ir_context,
116                                    &original_id_to_donated_id);
117   HandleTypesAndValues(donor_ir_context, &original_id_to_donated_id);
118   HandleFunctions(donor_ir_context, &original_id_to_donated_id, make_livesafe);
119 
120   // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3115) Handle some
121   //  kinds of decoration.
122 }
123 
AdaptStorageClass(SpvStorageClass donor_storage_class)124 SpvStorageClass FuzzerPassDonateModules::AdaptStorageClass(
125     SpvStorageClass donor_storage_class) {
126   switch (donor_storage_class) {
127     case SpvStorageClassFunction:
128     case SpvStorageClassPrivate:
129     case SpvStorageClassWorkgroup:
130       // We leave these alone
131       return donor_storage_class;
132     case SpvStorageClassInput:
133     case SpvStorageClassOutput:
134     case SpvStorageClassUniform:
135     case SpvStorageClassUniformConstant:
136     case SpvStorageClassPushConstant:
137     case SpvStorageClassImage:
138     case SpvStorageClassStorageBuffer:
139       // We change these to Private
140       return SpvStorageClassPrivate;
141     default:
142       // Handle other cases on demand.
143       assert(false && "Currently unsupported storage class.");
144       return SpvStorageClassMax;
145   }
146 }
147 
HandleExternalInstructionImports(opt::IRContext * donor_ir_context,std::map<uint32_t,uint32_t> * original_id_to_donated_id)148 void FuzzerPassDonateModules::HandleExternalInstructionImports(
149     opt::IRContext* donor_ir_context,
150     std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
151   // Consider every external instruction set import in the donor module.
152   for (auto& donor_import : donor_ir_context->module()->ext_inst_imports()) {
153     const auto& donor_import_name_words = donor_import.GetInOperand(0).words;
154     // Look for an identical import in the recipient module.
155     for (auto& existing_import : GetIRContext()->module()->ext_inst_imports()) {
156       const auto& existing_import_name_words =
157           existing_import.GetInOperand(0).words;
158       if (donor_import_name_words == existing_import_name_words) {
159         // A matching import has found.  Map the result id for the donor import
160         // to the id of the existing import, so that when donor instructions
161         // rely on the import they will be rewritten to use the existing import.
162         original_id_to_donated_id->insert(
163             {donor_import.result_id(), existing_import.result_id()});
164         break;
165       }
166     }
167     // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3116): At present
168     //  we do not handle donation of instruction imports, i.e. we do not allow
169     //  the donor to import instruction sets that the recipient did not already
170     //  import.  It might be a good idea to allow this, but it requires some
171     //  thought.
172     assert(original_id_to_donated_id->count(donor_import.result_id()) &&
173            "Donation of imports is not yet supported.");
174   }
175 }
176 
HandleTypesAndValues(opt::IRContext * donor_ir_context,std::map<uint32_t,uint32_t> * original_id_to_donated_id)177 void FuzzerPassDonateModules::HandleTypesAndValues(
178     opt::IRContext* donor_ir_context,
179     std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
180   // Consider every type/global/constant/undef in the module.
181   for (auto& type_or_value : donor_ir_context->module()->types_values()) {
182     HandleTypeOrValue(type_or_value, original_id_to_donated_id);
183   }
184 }
185 
HandleTypeOrValue(const opt::Instruction & type_or_value,std::map<uint32_t,uint32_t> * original_id_to_donated_id)186 void FuzzerPassDonateModules::HandleTypeOrValue(
187     const opt::Instruction& type_or_value,
188     std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
189   // The type/value instruction generates a result id, and we need to associate
190   // the donor's result id with a new result id.  That new result id will either
191   // be the id of some existing instruction, or a fresh id.  This variable
192   // captures it.
193   uint32_t new_result_id;
194 
195   // Decide how to handle each kind of instruction on a case-by-case basis.
196   //
197   // Because the donor module is required to be valid, when we encounter a
198   // type comprised of component types (e.g. an aggregate or pointer), we know
199   // that its component types will have been considered previously, and that
200   // |original_id_to_donated_id| will already contain an entry for them.
201   switch (type_or_value.opcode()) {
202     case SpvOpTypeImage:
203     case SpvOpTypeSampledImage:
204     case SpvOpTypeSampler:
205       // We do not donate types and variables that relate to images and
206       // samplers, so we skip these types and subsequently skip anything that
207       // depends on them.
208       return;
209     case SpvOpTypeVoid: {
210       // Void has to exist already in order for us to have an entry point.
211       // Get the existing id of void.
212       opt::analysis::Void void_type;
213       new_result_id = GetIRContext()->get_type_mgr()->GetId(&void_type);
214       assert(new_result_id &&
215              "The module being transformed will always have 'void' type "
216              "declared.");
217     } break;
218     case SpvOpTypeBool: {
219       // Bool cannot be declared multiple times, so use its existing id if
220       // present, or add a declaration of Bool with a fresh id if not.
221       opt::analysis::Bool bool_type;
222       auto bool_type_id = GetIRContext()->get_type_mgr()->GetId(&bool_type);
223       if (bool_type_id) {
224         new_result_id = bool_type_id;
225       } else {
226         new_result_id = GetFuzzerContext()->GetFreshId();
227         ApplyTransformation(TransformationAddTypeBoolean(new_result_id));
228       }
229     } break;
230     case SpvOpTypeInt: {
231       // Int cannot be declared multiple times with the same width and
232       // signedness, so check whether an existing identical Int type is
233       // present and use its id if so.  Otherwise add a declaration of the
234       // Int type used by the donor, with a fresh id.
235       const uint32_t width = type_or_value.GetSingleWordInOperand(0);
236       const bool is_signed =
237           static_cast<bool>(type_or_value.GetSingleWordInOperand(1));
238       opt::analysis::Integer int_type(width, is_signed);
239       auto int_type_id = GetIRContext()->get_type_mgr()->GetId(&int_type);
240       if (int_type_id) {
241         new_result_id = int_type_id;
242       } else {
243         new_result_id = GetFuzzerContext()->GetFreshId();
244         ApplyTransformation(
245             TransformationAddTypeInt(new_result_id, width, is_signed));
246       }
247     } break;
248     case SpvOpTypeFloat: {
249       // Similar to SpvOpTypeInt.
250       const uint32_t width = type_or_value.GetSingleWordInOperand(0);
251       opt::analysis::Float float_type(width);
252       auto float_type_id = GetIRContext()->get_type_mgr()->GetId(&float_type);
253       if (float_type_id) {
254         new_result_id = float_type_id;
255       } else {
256         new_result_id = GetFuzzerContext()->GetFreshId();
257         ApplyTransformation(TransformationAddTypeFloat(new_result_id, width));
258       }
259     } break;
260     case SpvOpTypeVector: {
261       // It is not legal to have two Vector type declarations with identical
262       // element types and element counts, so check whether an existing
263       // identical Vector type is present and use its id if so.  Otherwise add
264       // a declaration of the Vector type used by the donor, with a fresh id.
265 
266       // When considering the vector's component type id, we look up the id
267       // use in the donor to find the id to which this has been remapped.
268       uint32_t component_type_id = original_id_to_donated_id->at(
269           type_or_value.GetSingleWordInOperand(0));
270       auto component_type =
271           GetIRContext()->get_type_mgr()->GetType(component_type_id);
272       assert(component_type && "The base type should be registered.");
273       auto component_count = type_or_value.GetSingleWordInOperand(1);
274       opt::analysis::Vector vector_type(component_type, component_count);
275       auto vector_type_id = GetIRContext()->get_type_mgr()->GetId(&vector_type);
276       if (vector_type_id) {
277         new_result_id = vector_type_id;
278       } else {
279         new_result_id = GetFuzzerContext()->GetFreshId();
280         ApplyTransformation(TransformationAddTypeVector(
281             new_result_id, component_type_id, component_count));
282       }
283     } break;
284     case SpvOpTypeMatrix: {
285       // Similar to SpvOpTypeVector.
286       uint32_t column_type_id = original_id_to_donated_id->at(
287           type_or_value.GetSingleWordInOperand(0));
288       auto column_type =
289           GetIRContext()->get_type_mgr()->GetType(column_type_id);
290       assert(column_type && column_type->AsVector() &&
291              "The column type should be a registered vector type.");
292       auto column_count = type_or_value.GetSingleWordInOperand(1);
293       opt::analysis::Matrix matrix_type(column_type, column_count);
294       auto matrix_type_id = GetIRContext()->get_type_mgr()->GetId(&matrix_type);
295       if (matrix_type_id) {
296         new_result_id = matrix_type_id;
297       } else {
298         new_result_id = GetFuzzerContext()->GetFreshId();
299         ApplyTransformation(TransformationAddTypeMatrix(
300             new_result_id, column_type_id, column_count));
301       }
302 
303     } break;
304     case SpvOpTypeArray: {
305       // It is OK to have multiple structurally identical array types, so
306       // we go ahead and add a remapped version of the type declared by the
307       // donor.
308       uint32_t component_type_id = type_or_value.GetSingleWordInOperand(0);
309       if (!original_id_to_donated_id->count(component_type_id)) {
310         // We did not donate the component type of this array type, so we
311         // cannot donate the array type.
312         return;
313       }
314       new_result_id = GetFuzzerContext()->GetFreshId();
315       ApplyTransformation(TransformationAddTypeArray(
316           new_result_id, original_id_to_donated_id->at(component_type_id),
317           original_id_to_donated_id->at(
318               type_or_value.GetSingleWordInOperand(1))));
319     } break;
320     case SpvOpTypeRuntimeArray: {
321       // A runtime array is allowed as the final member of an SSBO.  During
322       // donation we turn runtime arrays into fixed-size arrays.  For dead
323       // code donations this is OK because the array is never indexed into at
324       // runtime, so it does not matter what its size is.  For live-safe code,
325       // all accesses are made in-bounds, so this is also OK.
326       //
327       // The special OpArrayLength instruction, which works on runtime arrays,
328       // is rewritten to yield the fixed length that is used for the array.
329 
330       uint32_t component_type_id = type_or_value.GetSingleWordInOperand(0);
331       if (!original_id_to_donated_id->count(component_type_id)) {
332         // We did not donate the component type of this runtime array type, so
333         // we cannot donate it as a fixed-size array.
334         return;
335       }
336       new_result_id = GetFuzzerContext()->GetFreshId();
337       ApplyTransformation(TransformationAddTypeArray(
338           new_result_id, original_id_to_donated_id->at(component_type_id),
339           FindOrCreateIntegerConstant(
340               {GetFuzzerContext()->GetRandomSizeForNewArray()}, 32, false,
341               false)));
342     } break;
343     case SpvOpTypeStruct: {
344       // Similar to SpvOpTypeArray.
345       std::vector<uint32_t> member_type_ids;
346       for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) {
347         auto component_type_id = type_or_value.GetSingleWordInOperand(i);
348         if (!original_id_to_donated_id->count(component_type_id)) {
349           // We did not donate every member type for this struct type, so we
350           // cannot donate the struct type.
351           return;
352         }
353         member_type_ids.push_back(
354             original_id_to_donated_id->at(component_type_id));
355       }
356       new_result_id = GetFuzzerContext()->GetFreshId();
357       ApplyTransformation(
358           TransformationAddTypeStruct(new_result_id, member_type_ids));
359     } break;
360     case SpvOpTypePointer: {
361       // Similar to SpvOpTypeArray.
362       uint32_t pointee_type_id = type_or_value.GetSingleWordInOperand(1);
363       if (!original_id_to_donated_id->count(pointee_type_id)) {
364         // We did not donate the pointee type for this pointer type, so we
365         // cannot donate the pointer type.
366         return;
367       }
368       new_result_id = GetFuzzerContext()->GetFreshId();
369       ApplyTransformation(TransformationAddTypePointer(
370           new_result_id,
371           AdaptStorageClass(static_cast<SpvStorageClass>(
372               type_or_value.GetSingleWordInOperand(0))),
373           original_id_to_donated_id->at(pointee_type_id)));
374     } break;
375     case SpvOpTypeFunction: {
376       // It is not OK to have multiple function types that use identical ids
377       // for their return and parameter types.  We thus go through all
378       // existing function types to look for a match.  We do not use the
379       // type manager here because we want to regard two function types that
380       // are structurally identical but that differ with respect to the
381       // actual ids used for pointer types as different.
382       //
383       // Example:
384       //
385       // %1 = OpTypeVoid
386       // %2 = OpTypeInt 32 0
387       // %3 = OpTypePointer Function %2
388       // %4 = OpTypePointer Function %2
389       // %5 = OpTypeFunction %1 %3
390       // %6 = OpTypeFunction %1 %4
391       //
392       // We regard %5 and %6 as distinct function types here, even though
393       // they both have the form "uint32* -> void"
394 
395       std::vector<uint32_t> return_and_parameter_types;
396       for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) {
397         uint32_t return_or_parameter_type =
398             type_or_value.GetSingleWordInOperand(i);
399         if (!original_id_to_donated_id->count(return_or_parameter_type)) {
400           // We did not donate every return/parameter type for this function
401           // type, so we cannot donate the function type.
402           return;
403         }
404         return_and_parameter_types.push_back(
405             original_id_to_donated_id->at(return_or_parameter_type));
406       }
407       uint32_t existing_function_id = fuzzerutil::FindFunctionType(
408           GetIRContext(), return_and_parameter_types);
409       if (existing_function_id) {
410         new_result_id = existing_function_id;
411       } else {
412         // No match was found, so add a remapped version of the function type
413         // to the module, with a fresh id.
414         new_result_id = GetFuzzerContext()->GetFreshId();
415         std::vector<uint32_t> argument_type_ids;
416         for (uint32_t i = 1; i < type_or_value.NumInOperands(); i++) {
417           argument_type_ids.push_back(original_id_to_donated_id->at(
418               type_or_value.GetSingleWordInOperand(i)));
419         }
420         ApplyTransformation(TransformationAddTypeFunction(
421             new_result_id,
422             original_id_to_donated_id->at(
423                 type_or_value.GetSingleWordInOperand(0)),
424             argument_type_ids));
425       }
426     } break;
427     case SpvOpSpecConstantOp: {
428       new_result_id = GetFuzzerContext()->GetFreshId();
429       auto type_id = original_id_to_donated_id->at(type_or_value.type_id());
430       auto opcode = static_cast<SpvOp>(type_or_value.GetSingleWordInOperand(0));
431 
432       // Make sure we take into account |original_id_to_donated_id| when
433       // computing operands for OpSpecConstantOp.
434       opt::Instruction::OperandList operands;
435       for (uint32_t i = 1; i < type_or_value.NumInOperands(); ++i) {
436         const auto& operand = type_or_value.GetInOperand(i);
437         auto data =
438             operand.type == SPV_OPERAND_TYPE_ID
439                 ? opt::Operand::OperandData{original_id_to_donated_id->at(
440                       operand.words[0])}
441                 : operand.words;
442 
443         operands.push_back({operand.type, std::move(data)});
444       }
445 
446       ApplyTransformation(TransformationAddSpecConstantOp(
447           new_result_id, type_id, opcode, std::move(operands)));
448     } break;
449     case SpvOpSpecConstantTrue:
450     case SpvOpSpecConstantFalse:
451     case SpvOpConstantTrue:
452     case SpvOpConstantFalse: {
453       // It is OK to have duplicate definitions of True and False, so add
454       // these to the module, using a remapped Bool type.
455       new_result_id = GetFuzzerContext()->GetFreshId();
456       auto value = type_or_value.opcode() == SpvOpConstantTrue ||
457                    type_or_value.opcode() == SpvOpSpecConstantTrue;
458       ApplyTransformation(
459           TransformationAddConstantBoolean(new_result_id, value, false));
460     } break;
461     case SpvOpSpecConstant:
462     case SpvOpConstant: {
463       // It is OK to have duplicate constant definitions, so add this to the
464       // module using a remapped result type.
465       new_result_id = GetFuzzerContext()->GetFreshId();
466       std::vector<uint32_t> data_words;
467       type_or_value.ForEachInOperand([&data_words](const uint32_t* in_operand) {
468         data_words.push_back(*in_operand);
469       });
470       ApplyTransformation(TransformationAddConstantScalar(
471           new_result_id, original_id_to_donated_id->at(type_or_value.type_id()),
472           data_words, false));
473     } break;
474     case SpvOpSpecConstantComposite:
475     case SpvOpConstantComposite: {
476       assert(original_id_to_donated_id->count(type_or_value.type_id()) &&
477              "Composite types for which it is possible to create a constant "
478              "should have been donated.");
479 
480       // It is OK to have duplicate constant composite definitions, so add
481       // this to the module using remapped versions of all consituent ids and
482       // the result type.
483       new_result_id = GetFuzzerContext()->GetFreshId();
484       std::vector<uint32_t> constituent_ids;
485       type_or_value.ForEachInId([&constituent_ids, &original_id_to_donated_id](
486                                     const uint32_t* constituent_id) {
487         assert(original_id_to_donated_id->count(*constituent_id) &&
488                "The constants used to construct this composite should "
489                "have been donated.");
490         constituent_ids.push_back(
491             original_id_to_donated_id->at(*constituent_id));
492       });
493       ApplyTransformation(TransformationAddConstantComposite(
494           new_result_id, original_id_to_donated_id->at(type_or_value.type_id()),
495           constituent_ids, false));
496     } break;
497     case SpvOpConstantNull: {
498       if (!original_id_to_donated_id->count(type_or_value.type_id())) {
499         // We did not donate the type associated with this null constant, so
500         // we cannot donate the null constant.
501         return;
502       }
503 
504       // It is fine to have multiple OpConstantNull instructions of the same
505       // type, so we just add this to the recipient module.
506       new_result_id = GetFuzzerContext()->GetFreshId();
507       ApplyTransformation(TransformationAddConstantNull(
508           new_result_id,
509           original_id_to_donated_id->at(type_or_value.type_id())));
510     } break;
511     case SpvOpVariable: {
512       if (!original_id_to_donated_id->count(type_or_value.type_id())) {
513         // We did not donate the pointer type associated with this variable,
514         // so we cannot donate the variable.
515         return;
516       }
517 
518       // This is a global variable that could have one of various storage
519       // classes.  However, we change all global variable pointer storage
520       // classes (such as Uniform, Input and Output) to private when donating
521       // pointer types, with the exception of the Workgroup storage class.
522       //
523       // Thus this variable's pointer type is guaranteed to have storage class
524       // Private or Workgroup.
525       //
526       // We add a global variable with either Private or Workgroup storage
527       // class, using remapped versions of the result type and initializer ids
528       // for the global variable in the donor.
529       //
530       // We regard the added variable as having an irrelevant value.  This
531       // means that future passes can add stores to the variable in any
532       // way they wish, and pass them as pointer parameters to functions
533       // without worrying about whether their data might get modified.
534       new_result_id = GetFuzzerContext()->GetFreshId();
535       uint32_t remapped_pointer_type =
536           original_id_to_donated_id->at(type_or_value.type_id());
537       uint32_t initializer_id;
538       SpvStorageClass storage_class =
539           static_cast<SpvStorageClass>(type_or_value.GetSingleWordInOperand(
540               0)) == SpvStorageClassWorkgroup
541               ? SpvStorageClassWorkgroup
542               : SpvStorageClassPrivate;
543       if (type_or_value.NumInOperands() == 1) {
544         // The variable did not have an initializer.  Initialize it to zero
545         // if it has Private storage class (to limit problems associated with
546         // uninitialized data), and leave it uninitialized if it has Workgroup
547         // storage class (as Workgroup variables cannot have initializers).
548 
549         // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3275): we
550         //  could initialize Workgroup variables at the start of an entry
551         //  point, and should do so if their uninitialized nature proves
552         //  problematic.
553         initializer_id = storage_class == SpvStorageClassWorkgroup
554                              ? 0
555                              : FindOrCreateZeroConstant(
556                                    fuzzerutil::GetPointeeTypeIdFromPointerType(
557                                        GetIRContext(), remapped_pointer_type),
558                                    false);
559       } else {
560         // The variable already had an initializer; use its remapped id.
561         initializer_id = original_id_to_donated_id->at(
562             type_or_value.GetSingleWordInOperand(1));
563       }
564       ApplyTransformation(
565           TransformationAddGlobalVariable(new_result_id, remapped_pointer_type,
566                                           storage_class, initializer_id, true));
567     } break;
568     case SpvOpUndef: {
569       if (!original_id_to_donated_id->count(type_or_value.type_id())) {
570         // We did not donate the type associated with this undef, so we cannot
571         // donate the undef.
572         return;
573       }
574 
575       // It is fine to have multiple Undef instructions of the same type, so
576       // we just add this to the recipient module.
577       new_result_id = GetFuzzerContext()->GetFreshId();
578       ApplyTransformation(TransformationAddGlobalUndef(
579           new_result_id,
580           original_id_to_donated_id->at(type_or_value.type_id())));
581     } break;
582     default: {
583       assert(0 && "Unknown type/value.");
584       new_result_id = 0;
585     } break;
586   }
587 
588   // Update the id mapping to associate the instruction's result id with its
589   // corresponding id in the recipient.
590   original_id_to_donated_id->insert({type_or_value.result_id(), new_result_id});
591 }
592 
HandleFunctions(opt::IRContext * donor_ir_context,std::map<uint32_t,uint32_t> * original_id_to_donated_id,bool make_livesafe)593 void FuzzerPassDonateModules::HandleFunctions(
594     opt::IRContext* donor_ir_context,
595     std::map<uint32_t, uint32_t>* original_id_to_donated_id,
596     bool make_livesafe) {
597   // Get the ids of functions in the donor module, topologically sorted
598   // according to the donor's call graph.
599   auto topological_order =
600       CallGraph(donor_ir_context).GetFunctionsInTopologicalOrder();
601 
602   // Donate the functions in reverse topological order.  This ensures that a
603   // function gets donated before any function that depends on it.  This allows
604   // donation of the functions to be separated into a number of transformations,
605   // each adding one function, such that every prefix of transformations leaves
606   // the module valid.
607   for (auto function_id = topological_order.rbegin();
608        function_id != topological_order.rend(); ++function_id) {
609     // Find the function to be donated.
610     opt::Function* function_to_donate = nullptr;
611     for (auto& function : *donor_ir_context->module()) {
612       if (function.result_id() == *function_id) {
613         function_to_donate = &function;
614         break;
615       }
616     }
617     assert(function_to_donate && "Function to be donated was not found.");
618 
619     if (!original_id_to_donated_id->count(
620             function_to_donate->DefInst().GetSingleWordInOperand(1))) {
621       // We were not able to donate this function's type, so we cannot donate
622       // the function.
623       continue;
624     }
625 
626     // We will collect up protobuf messages representing the donor function's
627     // instructions here, and use them to create an AddFunction transformation.
628     std::vector<protobufs::Instruction> donated_instructions;
629 
630     // This set tracks the ids of those instructions for which donation was
631     // completely skipped: neither the instruction nor a substitute for it was
632     // donated.
633     std::set<uint32_t> skipped_instructions;
634 
635     // Consider every instruction of the donor function.
636     function_to_donate->ForEachInst(
637         [this, &donated_instructions, donor_ir_context,
638          &original_id_to_donated_id,
639          &skipped_instructions](const opt::Instruction* instruction) {
640           if (instruction->opcode() == SpvOpArrayLength) {
641             // We treat OpArrayLength specially.
642             HandleOpArrayLength(*instruction, original_id_to_donated_id,
643                                 &donated_instructions);
644           } else if (!CanDonateInstruction(donor_ir_context, *instruction,
645                                            *original_id_to_donated_id,
646                                            skipped_instructions)) {
647             // This is an instruction that we cannot directly donate.
648             HandleDifficultInstruction(*instruction, original_id_to_donated_id,
649                                        &donated_instructions,
650                                        &skipped_instructions);
651           } else {
652             PrepareInstructionForDonation(*instruction, donor_ir_context,
653                                           original_id_to_donated_id,
654                                           &donated_instructions);
655           }
656         });
657 
658     // If |make_livesafe| is true, try to add the function in a livesafe manner.
659     // Otherwise (if |make_lifesafe| is false or an attempt to make the function
660     // livesafe has failed), add the function in a non-livesafe manner.
661     if (!make_livesafe ||
662         !MaybeAddLivesafeFunction(*function_to_donate, donor_ir_context,
663                                   *original_id_to_donated_id,
664                                   donated_instructions)) {
665       ApplyTransformation(TransformationAddFunction(donated_instructions));
666     }
667   }
668 }
669 
CanDonateInstruction(opt::IRContext * donor_ir_context,const opt::Instruction & instruction,const std::map<uint32_t,uint32_t> & original_id_to_donated_id,const std::set<uint32_t> & skipped_instructions) const670 bool FuzzerPassDonateModules::CanDonateInstruction(
671     opt::IRContext* donor_ir_context, const opt::Instruction& instruction,
672     const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
673     const std::set<uint32_t>& skipped_instructions) const {
674   if (instruction.type_id() &&
675       !original_id_to_donated_id.count(instruction.type_id())) {
676     // We could not donate the result type of this instruction, so we cannot
677     // donate the instruction.
678     return false;
679   }
680 
681   // Now consider instructions we specifically want to skip because we do not
682   // yet support them.
683   switch (instruction.opcode()) {
684     case SpvOpAtomicLoad:
685     case SpvOpAtomicStore:
686     case SpvOpAtomicExchange:
687     case SpvOpAtomicCompareExchange:
688     case SpvOpAtomicCompareExchangeWeak:
689     case SpvOpAtomicIIncrement:
690     case SpvOpAtomicIDecrement:
691     case SpvOpAtomicIAdd:
692     case SpvOpAtomicISub:
693     case SpvOpAtomicSMin:
694     case SpvOpAtomicUMin:
695     case SpvOpAtomicSMax:
696     case SpvOpAtomicUMax:
697     case SpvOpAtomicAnd:
698     case SpvOpAtomicOr:
699     case SpvOpAtomicXor:
700       // We conservatively ignore all atomic instructions at present.
701       // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3276): Consider
702       //  being less conservative here.
703     case SpvOpImageSampleImplicitLod:
704     case SpvOpImageSampleExplicitLod:
705     case SpvOpImageSampleDrefImplicitLod:
706     case SpvOpImageSampleDrefExplicitLod:
707     case SpvOpImageSampleProjImplicitLod:
708     case SpvOpImageSampleProjExplicitLod:
709     case SpvOpImageSampleProjDrefImplicitLod:
710     case SpvOpImageSampleProjDrefExplicitLod:
711     case SpvOpImageFetch:
712     case SpvOpImageGather:
713     case SpvOpImageDrefGather:
714     case SpvOpImageRead:
715     case SpvOpImageWrite:
716     case SpvOpImageSparseSampleImplicitLod:
717     case SpvOpImageSparseSampleExplicitLod:
718     case SpvOpImageSparseSampleDrefImplicitLod:
719     case SpvOpImageSparseSampleDrefExplicitLod:
720     case SpvOpImageSparseSampleProjImplicitLod:
721     case SpvOpImageSparseSampleProjExplicitLod:
722     case SpvOpImageSparseSampleProjDrefImplicitLod:
723     case SpvOpImageSparseSampleProjDrefExplicitLod:
724     case SpvOpImageSparseFetch:
725     case SpvOpImageSparseGather:
726     case SpvOpImageSparseDrefGather:
727     case SpvOpImageSparseRead:
728     case SpvOpImageSampleFootprintNV:
729     case SpvOpImage:
730     case SpvOpImageQueryFormat:
731     case SpvOpImageQueryLevels:
732     case SpvOpImageQueryLod:
733     case SpvOpImageQueryOrder:
734     case SpvOpImageQuerySamples:
735     case SpvOpImageQuerySize:
736     case SpvOpImageQuerySizeLod:
737     case SpvOpSampledImage:
738       // We ignore all instructions related to accessing images, since we do not
739       // donate images.
740       return false;
741     case SpvOpLoad:
742       switch (donor_ir_context->get_def_use_mgr()
743                   ->GetDef(instruction.type_id())
744                   ->opcode()) {
745         case SpvOpTypeImage:
746         case SpvOpTypeSampledImage:
747         case SpvOpTypeSampler:
748           // Again, we ignore instructions that relate to accessing images.
749           return false;
750         default:
751           break;
752       }
753     default:
754       break;
755   }
756 
757   // Examine each id input operand to the instruction.  If it turns out that we
758   // have skipped any of these operands then we cannot donate the instruction.
759   bool result = true;
760   instruction.WhileEachInId(
761       [donor_ir_context, &original_id_to_donated_id, &result,
762        &skipped_instructions](const uint32_t* in_id) -> bool {
763         if (!original_id_to_donated_id.count(*in_id)) {
764           // We do not have a mapped result id for this id operand.  That either
765           // means that it is a forward reference (which is OK), that we skipped
766           // the instruction that generated it (which is not OK), or that it is
767           // the id of a function or global value that we did not donate (which
768           // is not OK).  We check for the latter two cases.
769           if (skipped_instructions.count(*in_id) ||
770               // A function or global value does not have an associated basic
771               // block.
772               !donor_ir_context->get_instr_block(*in_id)) {
773             result = false;
774             return false;
775           }
776         }
777         return true;
778       });
779   return result;
780 }
781 
IsBasicType(const opt::Instruction & instruction) const782 bool FuzzerPassDonateModules::IsBasicType(
783     const opt::Instruction& instruction) const {
784   switch (instruction.opcode()) {
785     case SpvOpTypeArray:
786     case SpvOpTypeBool:
787     case SpvOpTypeFloat:
788     case SpvOpTypeInt:
789     case SpvOpTypeMatrix:
790     case SpvOpTypeStruct:
791     case SpvOpTypeVector:
792       return true;
793     default:
794       return false;
795   }
796 }
797 
HandleOpArrayLength(const opt::Instruction & instruction,std::map<uint32_t,uint32_t> * original_id_to_donated_id,std::vector<protobufs::Instruction> * donated_instructions) const798 void FuzzerPassDonateModules::HandleOpArrayLength(
799     const opt::Instruction& instruction,
800     std::map<uint32_t, uint32_t>* original_id_to_donated_id,
801     std::vector<protobufs::Instruction>* donated_instructions) const {
802   assert(instruction.opcode() == SpvOpArrayLength &&
803          "Precondition: instruction must be OpArrayLength.");
804   uint32_t donated_variable_id =
805       original_id_to_donated_id->at(instruction.GetSingleWordInOperand(0));
806   auto donated_variable_instruction =
807       GetIRContext()->get_def_use_mgr()->GetDef(donated_variable_id);
808   auto pointer_to_struct_instruction =
809       GetIRContext()->get_def_use_mgr()->GetDef(
810           donated_variable_instruction->type_id());
811   assert(pointer_to_struct_instruction->opcode() == SpvOpTypePointer &&
812          "Type of variable must be pointer.");
813   auto donated_struct_type_instruction =
814       GetIRContext()->get_def_use_mgr()->GetDef(
815           pointer_to_struct_instruction->GetSingleWordInOperand(1));
816   assert(donated_struct_type_instruction->opcode() == SpvOpTypeStruct &&
817          "Pointee type of pointer used by OpArrayLength must be struct.");
818   assert(donated_struct_type_instruction->NumInOperands() ==
819              instruction.GetSingleWordInOperand(1) + 1 &&
820          "OpArrayLength must refer to the final member of the given "
821          "struct.");
822   uint32_t fixed_size_array_type_id =
823       donated_struct_type_instruction->GetSingleWordInOperand(
824           donated_struct_type_instruction->NumInOperands() - 1);
825   auto fixed_size_array_type_instruction =
826       GetIRContext()->get_def_use_mgr()->GetDef(fixed_size_array_type_id);
827   assert(fixed_size_array_type_instruction->opcode() == SpvOpTypeArray &&
828          "The donated array type must be fixed-size.");
829   auto array_size_id =
830       fixed_size_array_type_instruction->GetSingleWordInOperand(1);
831 
832   if (instruction.result_id() &&
833       !original_id_to_donated_id->count(instruction.result_id())) {
834     original_id_to_donated_id->insert(
835         {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
836   }
837 
838   donated_instructions->push_back(MakeInstructionMessage(
839       SpvOpCopyObject, original_id_to_donated_id->at(instruction.type_id()),
840       original_id_to_donated_id->at(instruction.result_id()),
841       opt::Instruction::OperandList({{SPV_OPERAND_TYPE_ID, {array_size_id}}})));
842 }
843 
HandleDifficultInstruction(const opt::Instruction & instruction,std::map<uint32_t,uint32_t> * original_id_to_donated_id,std::vector<protobufs::Instruction> * donated_instructions,std::set<uint32_t> * skipped_instructions)844 void FuzzerPassDonateModules::HandleDifficultInstruction(
845     const opt::Instruction& instruction,
846     std::map<uint32_t, uint32_t>* original_id_to_donated_id,
847     std::vector<protobufs::Instruction>* donated_instructions,
848     std::set<uint32_t>* skipped_instructions) {
849   if (!instruction.result_id()) {
850     // It does not generate a result id, so it can be ignored.
851     return;
852   }
853   if (!original_id_to_donated_id->count(instruction.type_id())) {
854     // We cannot handle this instruction's result type, so we need to skip it
855     // all together.
856     skipped_instructions->insert(instruction.result_id());
857     return;
858   }
859 
860   // We now attempt to replace the instruction with an OpCopyObject.
861   // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3278): We could do
862   //  something more refined here - we could check which operands to the
863   //  instruction could not be donated and replace those operands with
864   //  references to other ids (such as constants), so that we still get an
865   //  instruction with the opcode and easy-to-handle operands of the donor
866   //  instruction.
867   auto remapped_type_id = original_id_to_donated_id->at(instruction.type_id());
868   if (!IsBasicType(
869           *GetIRContext()->get_def_use_mgr()->GetDef(remapped_type_id))) {
870     // The instruction has a non-basic result type, so we cannot replace it with
871     // an object copy of a constant.  We thus skip it completely.
872     // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3279): We could
873     //  instead look for an available id of the right type and generate an
874     //  OpCopyObject of that id.
875     skipped_instructions->insert(instruction.result_id());
876     return;
877   }
878 
879   // We are going to add an OpCopyObject instruction.  Add a mapping for the
880   // result id of the original instruction if does not already exist (it may
881   // exist in the case that it has been forward-referenced).
882   if (!original_id_to_donated_id->count(instruction.result_id())) {
883     original_id_to_donated_id->insert(
884         {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
885   }
886 
887   // We find or add a zero constant to the receiving module for the type in
888   // question, and add an OpCopyObject instruction that copies this zero.
889   //
890   // We mark the constant as irrelevant so that we can replace it with a
891   // more interesting value later.
892   auto zero_constant = FindOrCreateZeroConstant(remapped_type_id, true);
893   donated_instructions->push_back(MakeInstructionMessage(
894       SpvOpCopyObject, remapped_type_id,
895       original_id_to_donated_id->at(instruction.result_id()),
896       opt::Instruction::OperandList({{SPV_OPERAND_TYPE_ID, {zero_constant}}})));
897 }
898 
PrepareInstructionForDonation(const opt::Instruction & instruction,opt::IRContext * donor_ir_context,std::map<uint32_t,uint32_t> * original_id_to_donated_id,std::vector<protobufs::Instruction> * donated_instructions)899 void FuzzerPassDonateModules::PrepareInstructionForDonation(
900     const opt::Instruction& instruction, opt::IRContext* donor_ir_context,
901     std::map<uint32_t, uint32_t>* original_id_to_donated_id,
902     std::vector<protobufs::Instruction>* donated_instructions) {
903   // Get the instruction's input operands into donation-ready form,
904   // remapping any id uses in the process.
905   opt::Instruction::OperandList input_operands;
906 
907   // Consider each input operand in turn.
908   for (uint32_t in_operand_index = 0;
909        in_operand_index < instruction.NumInOperands(); in_operand_index++) {
910     std::vector<uint32_t> operand_data;
911     const opt::Operand& in_operand = instruction.GetInOperand(in_operand_index);
912     // Check whether this operand is an id.
913     if (spvIsIdType(in_operand.type)) {
914       // This is an id operand - it consists of a single word of data,
915       // which needs to be remapped so that it is replaced with the
916       // donated form of the id.
917       auto operand_id = in_operand.words[0];
918       if (!original_id_to_donated_id->count(operand_id)) {
919         // This is a forward reference.  We will choose a corresponding
920         // donor id for the referenced id and update the mapping to
921         // reflect it.
922 
923         // Keep release compilers happy because |donor_ir_context| is only used
924         // in this assertion.
925         (void)(donor_ir_context);
926         assert((donor_ir_context->get_def_use_mgr()
927                         ->GetDef(operand_id)
928                         ->opcode() == SpvOpLabel ||
929                 instruction.opcode() == SpvOpPhi) &&
930                "Unsupported forward reference.");
931         original_id_to_donated_id->insert(
932             {operand_id, GetFuzzerContext()->GetFreshId()});
933       }
934       operand_data.push_back(original_id_to_donated_id->at(operand_id));
935     } else {
936       // For non-id operands, we just add each of the data words.
937       for (auto word : in_operand.words) {
938         operand_data.push_back(word);
939       }
940     }
941     input_operands.push_back({in_operand.type, operand_data});
942   }
943 
944   if (instruction.opcode() == SpvOpVariable &&
945       instruction.NumInOperands() == 1) {
946     // This is an uninitialized local variable.  Initialize it to zero.
947     input_operands.push_back(
948         {SPV_OPERAND_TYPE_ID,
949          {FindOrCreateZeroConstant(
950              fuzzerutil::GetPointeeTypeIdFromPointerType(
951                  GetIRContext(),
952                  original_id_to_donated_id->at(instruction.type_id())),
953              false)}});
954   }
955 
956   if (instruction.result_id() &&
957       !original_id_to_donated_id->count(instruction.result_id())) {
958     original_id_to_donated_id->insert(
959         {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
960   }
961 
962   // Remap the result type and result id (if present) of the
963   // instruction, and turn it into a protobuf message.
964   donated_instructions->push_back(MakeInstructionMessage(
965       instruction.opcode(),
966       instruction.type_id()
967           ? original_id_to_donated_id->at(instruction.type_id())
968           : 0,
969       instruction.result_id()
970           ? original_id_to_donated_id->at(instruction.result_id())
971           : 0,
972       input_operands));
973 }
974 
CreateLoopLimiterInfo(opt::IRContext * donor_ir_context,const opt::BasicBlock & loop_header,const std::map<uint32_t,uint32_t> & original_id_to_donated_id,protobufs::LoopLimiterInfo * out)975 bool FuzzerPassDonateModules::CreateLoopLimiterInfo(
976     opt::IRContext* donor_ir_context, const opt::BasicBlock& loop_header,
977     const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
978     protobufs::LoopLimiterInfo* out) {
979   assert(loop_header.IsLoopHeader() && "|loop_header| is not a loop header");
980 
981   // Grab the loop header's id, mapped to its donated value.
982   out->set_loop_header_id(original_id_to_donated_id.at(loop_header.id()));
983 
984   // Get fresh ids that will be used to load the loop limiter, increment
985   // it, compare it with the loop limit, and an id for a new block that
986   // will contain the loop's original terminator.
987   out->set_load_id(GetFuzzerContext()->GetFreshId());
988   out->set_increment_id(GetFuzzerContext()->GetFreshId());
989   out->set_compare_id(GetFuzzerContext()->GetFreshId());
990   out->set_logical_op_id(GetFuzzerContext()->GetFreshId());
991 
992   // We are creating a branch from the back-edge block to the merge block. Thus,
993   // if merge block has any OpPhi instructions, we might need to adjust
994   // them.
995 
996   // Note that the loop might have an unreachable back-edge block. This means
997   // that the loop can't iterate, so we don't need to adjust anything.
998   const auto back_edge_block_id = TransformationAddFunction::GetBackEdgeBlockId(
999       donor_ir_context, loop_header.id());
1000   if (!back_edge_block_id) {
1001     return true;
1002   }
1003 
1004   auto* back_edge_block = donor_ir_context->cfg()->block(back_edge_block_id);
1005   assert(back_edge_block && "|back_edge_block_id| is invalid");
1006 
1007   const auto* merge_block =
1008       donor_ir_context->cfg()->block(loop_header.MergeBlockId());
1009   assert(merge_block && "Loop header has invalid merge block id");
1010 
1011   // We don't need to adjust anything if there is already a branch from
1012   // the back-edge block to the merge block.
1013   if (back_edge_block->IsSuccessor(merge_block)) {
1014     return true;
1015   }
1016 
1017   // Adjust OpPhi instructions in the |merge_block|.
1018   for (const auto& inst : *merge_block) {
1019     if (inst.opcode() != SpvOpPhi) {
1020       break;
1021     }
1022 
1023     // There is no simple way to ensure that a chosen operand for the OpPhi
1024     // instruction will never cause any problems (e.g. if we choose an
1025     // integer id, it might have a zero value when we branch from the back
1026     // edge block. This might cause a division by 0 later in the function.).
1027     // Thus, we ignore possible problems and proceed as follows:
1028     // - if any of the existing OpPhi operands dominates the back-edge
1029     //   block - use it
1030     // - if OpPhi has a basic type (see IsBasicType method) - create
1031     //   a zero constant
1032     // - otherwise, we can't add a livesafe function.
1033     uint32_t suitable_operand_id = 0;
1034     for (uint32_t i = 0; i < inst.NumInOperands(); i += 2) {
1035       auto dependency_inst_id = inst.GetSingleWordInOperand(i);
1036 
1037       if (fuzzerutil::IdIsAvailableBeforeInstruction(
1038               donor_ir_context, back_edge_block->terminator(),
1039               dependency_inst_id)) {
1040         suitable_operand_id = original_id_to_donated_id.at(dependency_inst_id);
1041         break;
1042       }
1043     }
1044 
1045     if (suitable_operand_id == 0 &&
1046         IsBasicType(
1047             *donor_ir_context->get_def_use_mgr()->GetDef(inst.type_id()))) {
1048       // We mark this constant as irrelevant so that we can replace it
1049       // with more interesting value later.
1050       suitable_operand_id = FindOrCreateZeroConstant(
1051           original_id_to_donated_id.at(inst.type_id()), true);
1052     }
1053 
1054     if (suitable_operand_id == 0) {
1055       return false;
1056     }
1057 
1058     out->add_phi_id(suitable_operand_id);
1059   }
1060 
1061   return true;
1062 }
1063 
MaybeAddLivesafeFunction(const opt::Function & function_to_donate,opt::IRContext * donor_ir_context,const std::map<uint32_t,uint32_t> & original_id_to_donated_id,const std::vector<protobufs::Instruction> & donated_instructions)1064 bool FuzzerPassDonateModules::MaybeAddLivesafeFunction(
1065     const opt::Function& function_to_donate, opt::IRContext* donor_ir_context,
1066     const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
1067     const std::vector<protobufs::Instruction>& donated_instructions) {
1068   // Various types and constants must be in place for a function to be made
1069   // live-safe.  Add them if not already present.
1070   FindOrCreateBoolType();  // Needed for comparisons
1071   FindOrCreatePointerToIntegerType(
1072       32, false, SpvStorageClassFunction);  // Needed for adding loop limiters
1073   FindOrCreateIntegerConstant({0}, 32, false,
1074                               false);  // Needed for initializing loop limiters
1075   FindOrCreateIntegerConstant({1}, 32, false,
1076                               false);  // Needed for incrementing loop limiters
1077 
1078   // Get a fresh id for the variable that will be used as a loop limiter.
1079   const uint32_t loop_limiter_variable_id = GetFuzzerContext()->GetFreshId();
1080   // Choose a random loop limit, and add the required constant to the
1081   // module if not already there.
1082   const uint32_t loop_limit = FindOrCreateIntegerConstant(
1083       {GetFuzzerContext()->GetRandomLoopLimit()}, 32, false, false);
1084 
1085   // Consider every loop header in the function to donate, and create a
1086   // structure capturing the ids to be used for manipulating the loop
1087   // limiter each time the loop is iterated.
1088   std::vector<protobufs::LoopLimiterInfo> loop_limiters;
1089   for (auto& block : function_to_donate) {
1090     if (block.IsLoopHeader()) {
1091       protobufs::LoopLimiterInfo loop_limiter;
1092 
1093       if (!CreateLoopLimiterInfo(donor_ir_context, block,
1094                                  original_id_to_donated_id, &loop_limiter)) {
1095         return false;
1096       }
1097 
1098       loop_limiters.emplace_back(std::move(loop_limiter));
1099     }
1100   }
1101 
1102   // Consider every access chain in the function to donate, and create a
1103   // structure containing the ids necessary to clamp the access chain
1104   // indices to be in-bounds.
1105   std::vector<protobufs::AccessChainClampingInfo> access_chain_clamping_info;
1106   for (auto& block : function_to_donate) {
1107     for (auto& inst : block) {
1108       switch (inst.opcode()) {
1109         case SpvOpAccessChain:
1110         case SpvOpInBoundsAccessChain: {
1111           protobufs::AccessChainClampingInfo clamping_info;
1112           clamping_info.set_access_chain_id(
1113               original_id_to_donated_id.at(inst.result_id()));
1114 
1115           auto base_object = donor_ir_context->get_def_use_mgr()->GetDef(
1116               inst.GetSingleWordInOperand(0));
1117           assert(base_object && "The base object must exist.");
1118           auto pointer_type = donor_ir_context->get_def_use_mgr()->GetDef(
1119               base_object->type_id());
1120           assert(pointer_type && pointer_type->opcode() == SpvOpTypePointer &&
1121                  "The base object must have pointer type.");
1122 
1123           auto should_be_composite_type =
1124               donor_ir_context->get_def_use_mgr()->GetDef(
1125                   pointer_type->GetSingleWordInOperand(1));
1126 
1127           // Walk the access chain, creating fresh ids to facilitate
1128           // clamping each index.  For simplicity we do this for every
1129           // index, even though constant indices will not end up being
1130           // clamped.
1131           for (uint32_t index = 1; index < inst.NumInOperands(); index++) {
1132             auto compare_and_select_ids =
1133                 clamping_info.add_compare_and_select_ids();
1134             compare_and_select_ids->set_first(GetFuzzerContext()->GetFreshId());
1135             compare_and_select_ids->set_second(
1136                 GetFuzzerContext()->GetFreshId());
1137 
1138             // Get the bound for the component being indexed into.
1139             uint32_t bound;
1140             if (should_be_composite_type->opcode() == SpvOpTypeRuntimeArray) {
1141               // The donor is indexing into a runtime array.  We do not
1142               // donate runtime arrays.  Instead, we donate a corresponding
1143               // fixed-size array for every runtime array.  We should thus
1144               // find that donor composite type's result id maps to a fixed-
1145               // size array.
1146               auto fixed_size_array_type =
1147                   GetIRContext()->get_def_use_mgr()->GetDef(
1148                       original_id_to_donated_id.at(
1149                           should_be_composite_type->result_id()));
1150               assert(fixed_size_array_type->opcode() == SpvOpTypeArray &&
1151                      "A runtime array type in the donor should have been "
1152                      "replaced by a fixed-sized array in the recipient.");
1153               // The size of this fixed-size array is a suitable bound.
1154               bound = fuzzerutil::GetBoundForCompositeIndex(
1155                   *fixed_size_array_type, GetIRContext());
1156             } else {
1157               bound = fuzzerutil::GetBoundForCompositeIndex(
1158                   *should_be_composite_type, donor_ir_context);
1159             }
1160             const uint32_t index_id = inst.GetSingleWordInOperand(index);
1161             auto index_inst =
1162                 donor_ir_context->get_def_use_mgr()->GetDef(index_id);
1163             auto index_type_inst = donor_ir_context->get_def_use_mgr()->GetDef(
1164                 index_inst->type_id());
1165             assert(index_type_inst->opcode() == SpvOpTypeInt);
1166             opt::analysis::Integer* index_int_type =
1167                 donor_ir_context->get_type_mgr()
1168                     ->GetType(index_type_inst->result_id())
1169                     ->AsInteger();
1170             if (index_inst->opcode() != SpvOpConstant) {
1171               // We will have to clamp this index, so we need a constant
1172               // whose value is one less than the bound, to compare
1173               // against and to use as the clamped value.
1174               FindOrCreateIntegerConstant({bound - 1}, 32,
1175                                           index_int_type->IsSigned(), false);
1176             }
1177             should_be_composite_type =
1178                 TransformationAddFunction::FollowCompositeIndex(
1179                     donor_ir_context, *should_be_composite_type, index_id);
1180           }
1181           access_chain_clamping_info.push_back(clamping_info);
1182           break;
1183         }
1184         default:
1185           break;
1186       }
1187     }
1188   }
1189 
1190   // If |function_to_donate| has non-void return type and contains an
1191   // OpKill/OpUnreachable instruction, then a value is needed in order to turn
1192   // these into instructions of the form OpReturnValue %value_id.
1193   uint32_t kill_unreachable_return_value_id = 0;
1194   auto function_return_type_inst =
1195       donor_ir_context->get_def_use_mgr()->GetDef(function_to_donate.type_id());
1196   if (function_return_type_inst->opcode() != SpvOpTypeVoid &&
1197       fuzzerutil::FunctionContainsOpKillOrUnreachable(function_to_donate)) {
1198     kill_unreachable_return_value_id = FindOrCreateZeroConstant(
1199         original_id_to_donated_id.at(function_return_type_inst->result_id()),
1200         false);
1201   }
1202 
1203   // Try to add the function in a livesafe manner. This may fail due to edge
1204   // cases, e.g. where adding loop limiters changes dominance such that the
1205   // module becomes invalid. It would be ideal to handle all such edge cases,
1206   // but as they are rare it is more pragmatic to bail out of making the
1207   // function livesafe if the transformation's precondition fails to hold.
1208   return MaybeApplyTransformation(TransformationAddFunction(
1209       donated_instructions, loop_limiter_variable_id, loop_limit, loop_limiters,
1210       kill_unreachable_return_value_id, access_chain_clamping_info));
1211 }
1212 
1213 }  // namespace fuzz
1214 }  // namespace spvtools
1215