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