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