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