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/transformation_outline_function.h"
16
17 #include <set>
18
19 #include "source/fuzz/fuzzer_util.h"
20
21 namespace spvtools {
22 namespace fuzz {
23
24 namespace {
25
PairSequenceToMap(const google::protobuf::RepeatedPtrField<protobufs::UInt32Pair> & pair_sequence)26 std::map<uint32_t, uint32_t> PairSequenceToMap(
27 const google::protobuf::RepeatedPtrField<protobufs::UInt32Pair>&
28 pair_sequence) {
29 std::map<uint32_t, uint32_t> result;
30 for (auto& pair : pair_sequence) {
31 result[pair.first()] = pair.second();
32 }
33 return result;
34 }
35
36 } // namespace
37
TransformationOutlineFunction(const spvtools::fuzz::protobufs::TransformationOutlineFunction & message)38 TransformationOutlineFunction::TransformationOutlineFunction(
39 const spvtools::fuzz::protobufs::TransformationOutlineFunction& message)
40 : message_(message) {}
41
TransformationOutlineFunction(uint32_t entry_block,uint32_t exit_block,uint32_t new_function_struct_return_type_id,uint32_t new_function_type_id,uint32_t new_function_id,uint32_t new_function_region_entry_block,uint32_t new_caller_result_id,uint32_t new_callee_result_id,std::map<uint32_t,uint32_t> && input_id_to_fresh_id,std::map<uint32_t,uint32_t> && output_id_to_fresh_id)42 TransformationOutlineFunction::TransformationOutlineFunction(
43 uint32_t entry_block, uint32_t exit_block,
44 uint32_t new_function_struct_return_type_id, uint32_t new_function_type_id,
45 uint32_t new_function_id, uint32_t new_function_region_entry_block,
46 uint32_t new_caller_result_id, uint32_t new_callee_result_id,
47 std::map<uint32_t, uint32_t>&& input_id_to_fresh_id,
48 std::map<uint32_t, uint32_t>&& output_id_to_fresh_id) {
49 message_.set_entry_block(entry_block);
50 message_.set_exit_block(exit_block);
51 message_.set_new_function_struct_return_type_id(
52 new_function_struct_return_type_id);
53 message_.set_new_function_type_id(new_function_type_id);
54 message_.set_new_function_id(new_function_id);
55 message_.set_new_function_region_entry_block(new_function_region_entry_block);
56 message_.set_new_caller_result_id(new_caller_result_id);
57 message_.set_new_callee_result_id(new_callee_result_id);
58 for (auto& entry : input_id_to_fresh_id) {
59 protobufs::UInt32Pair pair;
60 pair.set_first(entry.first);
61 pair.set_second(entry.second);
62 *message_.add_input_id_to_fresh_id() = pair;
63 }
64 for (auto& entry : output_id_to_fresh_id) {
65 protobufs::UInt32Pair pair;
66 pair.set_first(entry.first);
67 pair.set_second(entry.second);
68 *message_.add_output_id_to_fresh_id() = pair;
69 }
70 }
71
IsApplicable(opt::IRContext * context,const spvtools::fuzz::FactManager &) const72 bool TransformationOutlineFunction::IsApplicable(
73 opt::IRContext* context,
74 const spvtools::fuzz::FactManager& /*unused*/) const {
75 std::set<uint32_t> ids_used_by_this_transformation;
76
77 // The various new ids used by the transformation must be fresh and distinct.
78
79 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
80 message_.new_function_struct_return_type_id(), context,
81 &ids_used_by_this_transformation)) {
82 return false;
83 }
84
85 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
86 message_.new_function_type_id(), context,
87 &ids_used_by_this_transformation)) {
88 return false;
89 }
90
91 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
92 message_.new_function_id(), context,
93 &ids_used_by_this_transformation)) {
94 return false;
95 }
96
97 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
98 message_.new_function_region_entry_block(), context,
99 &ids_used_by_this_transformation)) {
100 return false;
101 }
102
103 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
104 message_.new_caller_result_id(), context,
105 &ids_used_by_this_transformation)) {
106 return false;
107 }
108
109 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
110 message_.new_callee_result_id(), context,
111 &ids_used_by_this_transformation)) {
112 return false;
113 }
114
115 for (auto& pair : message_.input_id_to_fresh_id()) {
116 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
117 pair.second(), context, &ids_used_by_this_transformation)) {
118 return false;
119 }
120 }
121
122 for (auto& pair : message_.output_id_to_fresh_id()) {
123 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
124 pair.second(), context, &ids_used_by_this_transformation)) {
125 return false;
126 }
127 }
128
129 // The entry and exit block ids must indeed refer to blocks.
130 for (auto block_id : {message_.entry_block(), message_.exit_block()}) {
131 auto block_label = context->get_def_use_mgr()->GetDef(block_id);
132 if (!block_label || block_label->opcode() != SpvOpLabel) {
133 return false;
134 }
135 }
136
137 auto entry_block = context->cfg()->block(message_.entry_block());
138 auto exit_block = context->cfg()->block(message_.exit_block());
139
140 // The entry block cannot start with OpVariable - this would mean that
141 // outlining would remove a variable from the function containing the region
142 // being outlined.
143 if (entry_block->begin()->opcode() == SpvOpVariable) {
144 return false;
145 }
146
147 // For simplicity, we do not allow the entry block to be a loop header.
148 if (entry_block->GetLoopMergeInst()) {
149 return false;
150 }
151
152 // For simplicity, we do not allow the exit block to be a merge block or
153 // continue target.
154 if (fuzzerutil::IsMergeOrContinue(context, exit_block->id())) {
155 return false;
156 }
157
158 // The entry block cannot start with OpPhi. This is to keep the
159 // transformation logic simple. (Another transformation to split the OpPhis
160 // from a block could be applied to avoid this scenario.)
161 if (entry_block->begin()->opcode() == SpvOpPhi) {
162 return false;
163 }
164
165 // The block must be in the same function.
166 if (entry_block->GetParent() != exit_block->GetParent()) {
167 return false;
168 }
169
170 // The entry block must dominate the exit block.
171 auto dominator_analysis =
172 context->GetDominatorAnalysis(entry_block->GetParent());
173 if (!dominator_analysis->Dominates(entry_block, exit_block)) {
174 return false;
175 }
176
177 // The exit block must post-dominate the entry block.
178 auto postdominator_analysis =
179 context->GetPostDominatorAnalysis(entry_block->GetParent());
180 if (!postdominator_analysis->Dominates(exit_block, entry_block)) {
181 return false;
182 }
183
184 // Find all the blocks dominated by |message_.entry_block| and post-dominated
185 // by |message_.exit_block|.
186 auto region_set = GetRegionBlocks(
187 context, entry_block = context->cfg()->block(message_.entry_block()),
188 exit_block = context->cfg()->block(message_.exit_block()));
189
190 // Check whether |region_set| really is a single-entry single-exit region, and
191 // also check whether structured control flow constructs and their merge
192 // and continue constructs are either wholly in or wholly out of the region -
193 // e.g. avoid the situation where the region contains the head of a loop but
194 // not the loop's continue construct.
195 //
196 // This is achieved by going through every block in the function that contains
197 // the region.
198 for (auto& block : *entry_block->GetParent()) {
199 if (&block == exit_block) {
200 // It is OK (and typically expected) for the exit block of the region to
201 // have successors outside the region. It is also OK for the exit block
202 // to head a structured control flow construct - the block containing the
203 // call to the outlined function will end up heading this construct if
204 // outlining takes place.
205 continue;
206 }
207
208 if (region_set.count(&block) != 0) {
209 // The block is in the region and is not the region's exit block. Let's
210 // see whether all of the block's successors are in the region. If they
211 // are not, the region is not single-entry single-exit.
212 bool all_successors_in_region = true;
213 block.WhileEachSuccessorLabel([&all_successors_in_region, context,
214 ®ion_set](uint32_t successor) -> bool {
215 if (region_set.count(context->cfg()->block(successor)) == 0) {
216 all_successors_in_region = false;
217 return false;
218 }
219 return true;
220 });
221 if (!all_successors_in_region) {
222 return false;
223 }
224 }
225
226 if (auto merge = block.GetMergeInst()) {
227 // The block is a loop or selection header -- the header and its
228 // associated merge block had better both be in the region or both be
229 // outside the region.
230 auto merge_block = context->cfg()->block(merge->GetSingleWordOperand(0));
231 if (region_set.count(&block) != region_set.count(merge_block)) {
232 return false;
233 }
234 }
235
236 if (auto loop_merge = block.GetLoopMergeInst()) {
237 // Similar to the above, but for the continue target of a loop.
238 auto continue_target =
239 context->cfg()->block(loop_merge->GetSingleWordOperand(1));
240 if (continue_target != exit_block &&
241 region_set.count(&block) != region_set.count(continue_target)) {
242 return false;
243 }
244 }
245 }
246
247 // For each region input id, i.e. every id defined outside the region but
248 // used inside the region, ...
249 std::map<uint32_t, uint32_t> input_id_to_fresh_id_map =
250 PairSequenceToMap(message_.input_id_to_fresh_id());
251 for (auto id : GetRegionInputIds(context, region_set, exit_block)) {
252 // There needs to be a corresponding fresh id to be used as a function
253 // parameter.
254 if (input_id_to_fresh_id_map.count(id) == 0) {
255 return false;
256 }
257 // Furthermore, if the input id has pointer type it must be an OpVariable
258 // or OpFunctionParameter.
259 auto input_id_inst = context->get_def_use_mgr()->GetDef(id);
260 if (context->get_def_use_mgr()
261 ->GetDef(input_id_inst->type_id())
262 ->opcode() == SpvOpTypePointer) {
263 switch (input_id_inst->opcode()) {
264 case SpvOpFunctionParameter:
265 case SpvOpVariable:
266 // These are OK.
267 break;
268 default:
269 // Anything else is not OK.
270 return false;
271 }
272 }
273 }
274
275 // For each region output id -- i.e. every id defined inside the region but
276 // used outside the region -- there needs to be a corresponding fresh id that
277 // can hold the value for this id computed in the outlined function.
278 std::map<uint32_t, uint32_t> output_id_to_fresh_id_map =
279 PairSequenceToMap(message_.output_id_to_fresh_id());
280 for (auto id : GetRegionOutputIds(context, region_set, exit_block)) {
281 if (output_id_to_fresh_id_map.count(id) == 0) {
282 return false;
283 }
284 }
285
286 return true;
287 }
288
Apply(opt::IRContext * context,spvtools::fuzz::FactManager * fact_manager) const289 void TransformationOutlineFunction::Apply(
290 opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const {
291 // The entry block for the region before outlining.
292 auto original_region_entry_block =
293 context->cfg()->block(message_.entry_block());
294
295 // The exit block for the region before outlining.
296 auto original_region_exit_block =
297 context->cfg()->block(message_.exit_block());
298
299 // The single-entry single-exit region defined by |message_.entry_block| and
300 // |message_.exit_block|.
301 std::set<opt::BasicBlock*> region_blocks = GetRegionBlocks(
302 context, original_region_entry_block, original_region_exit_block);
303
304 // Input and output ids for the region being outlined.
305 std::vector<uint32_t> region_input_ids =
306 GetRegionInputIds(context, region_blocks, original_region_exit_block);
307 std::vector<uint32_t> region_output_ids =
308 GetRegionOutputIds(context, region_blocks, original_region_exit_block);
309
310 // Maps from input and output ids to fresh ids.
311 std::map<uint32_t, uint32_t> input_id_to_fresh_id_map =
312 PairSequenceToMap(message_.input_id_to_fresh_id());
313 std::map<uint32_t, uint32_t> output_id_to_fresh_id_map =
314 PairSequenceToMap(message_.output_id_to_fresh_id());
315
316 UpdateModuleIdBoundForFreshIds(context, input_id_to_fresh_id_map,
317 output_id_to_fresh_id_map);
318
319 // Construct a map that associates each output id with its type id.
320 std::map<uint32_t, uint32_t> output_id_to_type_id;
321 for (uint32_t output_id : region_output_ids) {
322 output_id_to_type_id[output_id] =
323 context->get_def_use_mgr()->GetDef(output_id)->type_id();
324 }
325
326 // The region will be collapsed to a single block that calls a function
327 // containing the outlined region. This block needs to end with whatever
328 // the exit block of the region ended with before outlining. We thus clone
329 // the terminator of the region's exit block, and the merge instruction for
330 // the block if there is one, so that we can append them to the end of the
331 // collapsed block later.
332 std::unique_ptr<opt::Instruction> cloned_exit_block_terminator =
333 std::unique_ptr<opt::Instruction>(
334 original_region_exit_block->terminator()->Clone(context));
335 std::unique_ptr<opt::Instruction> cloned_exit_block_merge =
336 original_region_exit_block->GetMergeInst()
337 ? std::unique_ptr<opt::Instruction>(
338 original_region_exit_block->GetMergeInst()->Clone(context))
339 : nullptr;
340
341 // Make a function prototype for the outlined function, which involves
342 // figuring out its required type.
343 std::unique_ptr<opt::Function> outlined_function =
344 PrepareFunctionPrototype(region_input_ids, region_output_ids,
345 input_id_to_fresh_id_map, context, fact_manager);
346
347 // If the original function was livesafe, the new function should also be
348 // livesafe.
349 if (fact_manager->FunctionIsLivesafe(
350 original_region_entry_block->GetParent()->result_id())) {
351 fact_manager->AddFactFunctionIsLivesafe(message_.new_function_id());
352 }
353
354 // Adapt the region to be outlined so that its input ids are replaced with the
355 // ids of the outlined function's input parameters, and so that output ids
356 // are similarly remapped.
357 RemapInputAndOutputIdsInRegion(
358 context, *original_region_exit_block, region_blocks, region_input_ids,
359 region_output_ids, input_id_to_fresh_id_map, output_id_to_fresh_id_map);
360
361 // Fill out the body of the outlined function according to the region that is
362 // being outlined.
363 PopulateOutlinedFunction(*original_region_entry_block,
364 *original_region_exit_block, region_blocks,
365 region_output_ids, output_id_to_fresh_id_map,
366 context, outlined_function.get(), fact_manager);
367
368 // Collapse the region that has been outlined into a function down to a single
369 // block that calls said function.
370 ShrinkOriginalRegion(
371 context, region_blocks, region_input_ids, region_output_ids,
372 output_id_to_type_id, outlined_function->type_id(),
373 std::move(cloned_exit_block_merge),
374 std::move(cloned_exit_block_terminator), original_region_entry_block);
375
376 // Add the outlined function to the module.
377 context->module()->AddFunction(std::move(outlined_function));
378
379 // Major surgery has been conducted on the module, so invalidate all analyses.
380 context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
381 }
382
ToMessage() const383 protobufs::Transformation TransformationOutlineFunction::ToMessage() const {
384 protobufs::Transformation result;
385 *result.mutable_outline_function() = message_;
386 return result;
387 }
388
GetRegionInputIds(opt::IRContext * context,const std::set<opt::BasicBlock * > & region_set,opt::BasicBlock * region_exit_block)389 std::vector<uint32_t> TransformationOutlineFunction::GetRegionInputIds(
390 opt::IRContext* context, const std::set<opt::BasicBlock*>& region_set,
391 opt::BasicBlock* region_exit_block) {
392 std::vector<uint32_t> result;
393
394 auto enclosing_function = region_exit_block->GetParent();
395
396 // Consider each parameter of the function containing the region.
397 enclosing_function->ForEachParam([context, ®ion_set, &result](
398 opt::Instruction* function_parameter) {
399 // Consider every use of the parameter.
400 context->get_def_use_mgr()->WhileEachUse(
401 function_parameter, [context, function_parameter, ®ion_set, &result](
402 opt::Instruction* use, uint32_t /*unused*/) {
403 // Get the block, if any, in which the parameter is used.
404 auto use_block = context->get_instr_block(use);
405 // If the use is in a block that lies within the region, the
406 // parameter is an input id for the region.
407 if (use_block && region_set.count(use_block) != 0) {
408 result.push_back(function_parameter->result_id());
409 return false;
410 }
411 return true;
412 });
413 });
414
415 // Consider all definitions in the function that might turn out to be input
416 // ids.
417 for (auto& block : *enclosing_function) {
418 std::vector<opt::Instruction*> candidate_input_ids_for_block;
419 if (region_set.count(&block) == 0) {
420 // All instructions in blocks outside the region are candidate's for
421 // generating input ids.
422 for (auto& inst : block) {
423 candidate_input_ids_for_block.push_back(&inst);
424 }
425 } else {
426 // Blocks in the region cannot generate input ids.
427 continue;
428 }
429
430 // Consider each candidate input id to check whether it is used in the
431 // region.
432 for (auto& inst : candidate_input_ids_for_block) {
433 context->get_def_use_mgr()->WhileEachUse(
434 inst,
435 [context, &inst, region_exit_block, ®ion_set, &result](
436 opt::Instruction* use, uint32_t /*unused*/) -> bool {
437
438 // Find the block in which this id use occurs, recording the id as
439 // an input id if the block is outside the region, with some
440 // exceptions detailed below.
441 auto use_block = context->get_instr_block(use);
442
443 if (!use_block) {
444 // There might be no containing block, e.g. if the use is in a
445 // decoration.
446 return true;
447 }
448
449 if (region_set.count(use_block) == 0) {
450 // The use is not in the region: this does not make it an input
451 // id.
452 return true;
453 }
454
455 if (use_block == region_exit_block && use->IsBlockTerminator()) {
456 // We do not regard uses in the exit block terminator as input
457 // ids, as this terminator does not get outlined.
458 return true;
459 }
460
461 result.push_back(inst->result_id());
462 return false;
463 });
464 }
465 }
466 return result;
467 }
468
GetRegionOutputIds(opt::IRContext * context,const std::set<opt::BasicBlock * > & region_set,opt::BasicBlock * region_exit_block)469 std::vector<uint32_t> TransformationOutlineFunction::GetRegionOutputIds(
470 opt::IRContext* context, const std::set<opt::BasicBlock*>& region_set,
471 opt::BasicBlock* region_exit_block) {
472 std::vector<uint32_t> result;
473
474 // Consider each block in the function containing the region.
475 for (auto& block : *region_exit_block->GetParent()) {
476 if (region_set.count(&block) == 0) {
477 // Skip blocks that are not in the region.
478 continue;
479 }
480 // Consider each use of each instruction defined in the block.
481 for (auto& inst : block) {
482 context->get_def_use_mgr()->WhileEachUse(
483 &inst,
484 [®ion_set, context, &inst, region_exit_block, &result](
485 opt::Instruction* use, uint32_t /*unused*/) -> bool {
486
487 // Find the block in which this id use occurs, recording the id as
488 // an output id if the block is outside the region, with some
489 // exceptions detailed below.
490 auto use_block = context->get_instr_block(use);
491
492 if (!use_block) {
493 // There might be no containing block, e.g. if the use is in a
494 // decoration.
495 return true;
496 }
497
498 if (region_set.count(use_block) != 0) {
499 // The use is in the region.
500 if (use_block != region_exit_block || !use->IsBlockTerminator()) {
501 // Furthermore, the use is not in the terminator of the region's
502 // exit block.
503 return true;
504 }
505 }
506
507 result.push_back(inst.result_id());
508 return false;
509 });
510 }
511 }
512 return result;
513 }
514
GetRegionBlocks(opt::IRContext * context,opt::BasicBlock * entry_block,opt::BasicBlock * exit_block)515 std::set<opt::BasicBlock*> TransformationOutlineFunction::GetRegionBlocks(
516 opt::IRContext* context, opt::BasicBlock* entry_block,
517 opt::BasicBlock* exit_block) {
518 auto enclosing_function = entry_block->GetParent();
519 auto dominator_analysis = context->GetDominatorAnalysis(enclosing_function);
520 auto postdominator_analysis =
521 context->GetPostDominatorAnalysis(enclosing_function);
522
523 std::set<opt::BasicBlock*> result;
524 for (auto& block : *enclosing_function) {
525 if (dominator_analysis->Dominates(entry_block, &block) &&
526 postdominator_analysis->Dominates(exit_block, &block)) {
527 result.insert(&block);
528 }
529 }
530 return result;
531 }
532
533 std::unique_ptr<opt::Function>
PrepareFunctionPrototype(const std::vector<uint32_t> & region_input_ids,const std::vector<uint32_t> & region_output_ids,const std::map<uint32_t,uint32_t> & input_id_to_fresh_id_map,opt::IRContext * context,FactManager * fact_manager) const534 TransformationOutlineFunction::PrepareFunctionPrototype(
535 const std::vector<uint32_t>& region_input_ids,
536 const std::vector<uint32_t>& region_output_ids,
537 const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
538 opt::IRContext* context, FactManager* fact_manager) const {
539 uint32_t return_type_id = 0;
540 uint32_t function_type_id = 0;
541
542 // First, try to find an existing function type that is suitable. This is
543 // only possible if the region generates no output ids; if it generates output
544 // ids we are going to make a new struct for those, and since that struct does
545 // not exist there cannot already be a function type with this struct as its
546 // return type.
547 if (region_output_ids.empty()) {
548 std::vector<uint32_t> return_and_parameter_types;
549 opt::analysis::Void void_type;
550 return_type_id = context->get_type_mgr()->GetId(&void_type);
551 return_and_parameter_types.push_back(return_type_id);
552 for (auto id : region_input_ids) {
553 return_and_parameter_types.push_back(
554 context->get_def_use_mgr()->GetDef(id)->type_id());
555 }
556 function_type_id =
557 fuzzerutil::FindFunctionType(context, return_and_parameter_types);
558 }
559
560 // If no existing function type was found, we need to create one.
561 if (function_type_id == 0) {
562 assert(
563 ((return_type_id == 0) == !region_output_ids.empty()) &&
564 "We should only have set the return type if there are no output ids.");
565 // If the region generates output ids, we need to make a struct with one
566 // field per output id.
567 if (!region_output_ids.empty()) {
568 opt::Instruction::OperandList struct_member_types;
569 for (uint32_t output_id : region_output_ids) {
570 auto output_id_type =
571 context->get_def_use_mgr()->GetDef(output_id)->type_id();
572 struct_member_types.push_back({SPV_OPERAND_TYPE_ID, {output_id_type}});
573 }
574 // Add a new struct type to the module.
575 context->module()->AddType(MakeUnique<opt::Instruction>(
576 context, SpvOpTypeStruct, 0,
577 message_.new_function_struct_return_type_id(),
578 std::move(struct_member_types)));
579 // The return type for the function is the newly-created struct.
580 return_type_id = message_.new_function_struct_return_type_id();
581 }
582 assert(
583 return_type_id != 0 &&
584 "We should either have a void return type, or have created a struct.");
585
586 // The region's input ids dictate the parameter types to the function.
587 opt::Instruction::OperandList function_type_operands;
588 function_type_operands.push_back({SPV_OPERAND_TYPE_ID, {return_type_id}});
589 for (auto id : region_input_ids) {
590 function_type_operands.push_back(
591 {SPV_OPERAND_TYPE_ID,
592 {context->get_def_use_mgr()->GetDef(id)->type_id()}});
593 }
594 // Add a new function type to the module, and record that this is the type
595 // id for the new function.
596 context->module()->AddType(MakeUnique<opt::Instruction>(
597 context, SpvOpTypeFunction, 0, message_.new_function_type_id(),
598 function_type_operands));
599 function_type_id = message_.new_function_type_id();
600 }
601
602 // Create a new function with |message_.new_function_id| as the function id,
603 // and the return type and function type prepared above.
604 std::unique_ptr<opt::Function> outlined_function =
605 MakeUnique<opt::Function>(MakeUnique<opt::Instruction>(
606 context, SpvOpFunction, return_type_id, message_.new_function_id(),
607 opt::Instruction::OperandList(
608 {{spv_operand_type_t ::SPV_OPERAND_TYPE_LITERAL_INTEGER,
609 {SpvFunctionControlMaskNone}},
610 {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
611 {function_type_id}}})));
612
613 // Add one parameter to the function for each input id, using the fresh ids
614 // provided in |input_id_to_fresh_id_map|.
615 for (auto id : region_input_ids) {
616 outlined_function->AddParameter(MakeUnique<opt::Instruction>(
617 context, SpvOpFunctionParameter,
618 context->get_def_use_mgr()->GetDef(id)->type_id(),
619 input_id_to_fresh_id_map.at(id), opt::Instruction::OperandList()));
620 // If the input id is an irrelevant-valued variable, the same should be true
621 // of the corresponding parameter.
622 if (fact_manager->PointeeValueIsIrrelevant(id)) {
623 fact_manager->AddFactValueOfPointeeIsIrrelevant(
624 input_id_to_fresh_id_map.at(id));
625 }
626 }
627
628 return outlined_function;
629 }
630
UpdateModuleIdBoundForFreshIds(opt::IRContext * context,const std::map<uint32_t,uint32_t> & input_id_to_fresh_id_map,const std::map<uint32_t,uint32_t> & output_id_to_fresh_id_map) const631 void TransformationOutlineFunction::UpdateModuleIdBoundForFreshIds(
632 opt::IRContext* context,
633 const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
634 const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) const {
635 // Enlarge the module's id bound as needed to accommodate the various fresh
636 // ids associated with the transformation.
637 fuzzerutil::UpdateModuleIdBound(
638 context, message_.new_function_struct_return_type_id());
639 fuzzerutil::UpdateModuleIdBound(context, message_.new_function_type_id());
640 fuzzerutil::UpdateModuleIdBound(context, message_.new_function_id());
641 fuzzerutil::UpdateModuleIdBound(context,
642 message_.new_function_region_entry_block());
643 fuzzerutil::UpdateModuleIdBound(context, message_.new_caller_result_id());
644 fuzzerutil::UpdateModuleIdBound(context, message_.new_callee_result_id());
645
646 for (auto& entry : input_id_to_fresh_id_map) {
647 fuzzerutil::UpdateModuleIdBound(context, entry.second);
648 }
649
650 for (auto& entry : output_id_to_fresh_id_map) {
651 fuzzerutil::UpdateModuleIdBound(context, entry.second);
652 }
653 }
654
RemapInputAndOutputIdsInRegion(opt::IRContext * context,const opt::BasicBlock & original_region_exit_block,const std::set<opt::BasicBlock * > & region_blocks,const std::vector<uint32_t> & region_input_ids,const std::vector<uint32_t> & region_output_ids,const std::map<uint32_t,uint32_t> & input_id_to_fresh_id_map,const std::map<uint32_t,uint32_t> & output_id_to_fresh_id_map) const655 void TransformationOutlineFunction::RemapInputAndOutputIdsInRegion(
656 opt::IRContext* context, const opt::BasicBlock& original_region_exit_block,
657 const std::set<opt::BasicBlock*>& region_blocks,
658 const std::vector<uint32_t>& region_input_ids,
659 const std::vector<uint32_t>& region_output_ids,
660 const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
661 const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) const {
662 // Change all uses of input ids inside the region to the corresponding fresh
663 // ids that will ultimately be parameters of the outlined function.
664 // This is done by considering each region input id in turn.
665 for (uint32_t id : region_input_ids) {
666 // We then consider each use of the input id.
667 context->get_def_use_mgr()->ForEachUse(
668 id, [context, id, &input_id_to_fresh_id_map, region_blocks](
669 opt::Instruction* use, uint32_t operand_index) {
670 // Find the block in which this use of the input id occurs.
671 opt::BasicBlock* use_block = context->get_instr_block(use);
672 // We want to rewrite the use id if its block occurs in the outlined
673 // region.
674 if (region_blocks.count(use_block) != 0) {
675 // Rewrite this use of the input id.
676 use->SetOperand(operand_index, {input_id_to_fresh_id_map.at(id)});
677 }
678 });
679 }
680
681 // Change each definition of a region output id to define the corresponding
682 // fresh ids that will store intermediate value for the output ids. Also
683 // change all uses of the output id located in the outlined region.
684 // This is done by considering each region output id in turn.
685 for (uint32_t id : region_output_ids) {
686 // First consider each use of the output id and update the relevant uses.
687 context->get_def_use_mgr()->ForEachUse(
688 id,
689 [context, &original_region_exit_block, id, &output_id_to_fresh_id_map,
690 region_blocks](opt::Instruction* use, uint32_t operand_index) {
691 // Find the block in which this use of the output id occurs.
692 auto use_block = context->get_instr_block(use);
693 // We want to rewrite the use id if its block occurs in the outlined
694 // region, with one exception: the terminator of the exit block of
695 // the region is going to remain in the original function, so if the
696 // use appears in such a terminator instruction we leave it alone.
697 if (
698 // The block is in the region ...
699 region_blocks.count(use_block) != 0 &&
700 // ... and the use is not in the terminator instruction of the
701 // region's exit block.
702 !(use_block == &original_region_exit_block &&
703 use->IsBlockTerminator())) {
704 // Rewrite this use of the output id.
705 use->SetOperand(operand_index, {output_id_to_fresh_id_map.at(id)});
706 }
707 });
708
709 // Now change the instruction that defines the output id so that it instead
710 // defines the corresponding fresh id. We do this after changing all the
711 // uses so that the definition of the original id is still registered when
712 // we analyse its uses.
713 context->get_def_use_mgr()->GetDef(id)->SetResultId(
714 output_id_to_fresh_id_map.at(id));
715 }
716 }
717
PopulateOutlinedFunction(const opt::BasicBlock & original_region_entry_block,const opt::BasicBlock & original_region_exit_block,const std::set<opt::BasicBlock * > & region_blocks,const std::vector<uint32_t> & region_output_ids,const std::map<uint32_t,uint32_t> & output_id_to_fresh_id_map,opt::IRContext * context,opt::Function * outlined_function,FactManager * fact_manager) const718 void TransformationOutlineFunction::PopulateOutlinedFunction(
719 const opt::BasicBlock& original_region_entry_block,
720 const opt::BasicBlock& original_region_exit_block,
721 const std::set<opt::BasicBlock*>& region_blocks,
722 const std::vector<uint32_t>& region_output_ids,
723 const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map,
724 opt::IRContext* context, opt::Function* outlined_function,
725 FactManager* fact_manager) const {
726 // When we create the exit block for the outlined region, we use this pointer
727 // to track of it so that we can manipulate it later.
728 opt::BasicBlock* outlined_region_exit_block = nullptr;
729
730 // The region entry block in the new function is identical to the entry block
731 // of the region being outlined, except that it has
732 // |message_.new_function_region_entry_block| as its id.
733 std::unique_ptr<opt::BasicBlock> outlined_region_entry_block =
734 MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
735 context, SpvOpLabel, 0, message_.new_function_region_entry_block(),
736 opt::Instruction::OperandList()));
737 outlined_region_entry_block->SetParent(outlined_function);
738
739 // If the original region's entry block was dead, the outlined region's entry
740 // block is also dead.
741 if (fact_manager->BlockIsDead(original_region_entry_block.id())) {
742 fact_manager->AddFactBlockIsDead(outlined_region_entry_block->id());
743 }
744
745 if (&original_region_entry_block == &original_region_exit_block) {
746 outlined_region_exit_block = outlined_region_entry_block.get();
747 }
748
749 for (auto& inst : original_region_entry_block) {
750 outlined_region_entry_block->AddInstruction(
751 std::unique_ptr<opt::Instruction>(inst.Clone(context)));
752 }
753 outlined_function->AddBasicBlock(std::move(outlined_region_entry_block));
754
755 // We now go through the single-entry single-exit region defined by the entry
756 // and exit blocks, adding clones of all blocks to the new function.
757
758 // Consider every block in the enclosing function.
759 auto enclosing_function = original_region_entry_block.GetParent();
760 for (auto block_it = enclosing_function->begin();
761 block_it != enclosing_function->end();) {
762 // Skip the region's entry block - we already dealt with it above.
763 if (region_blocks.count(&*block_it) == 0 ||
764 &*block_it == &original_region_entry_block) {
765 ++block_it;
766 continue;
767 }
768 // Clone the block so that it can be added to the new function.
769 auto cloned_block =
770 std::unique_ptr<opt::BasicBlock>(block_it->Clone(context));
771
772 // If this is the region's exit block, then the cloned block is the outlined
773 // region's exit block.
774 if (&*block_it == &original_region_exit_block) {
775 assert(outlined_region_exit_block == nullptr &&
776 "We should not yet have encountered the exit block.");
777 outlined_region_exit_block = cloned_block.get();
778 }
779
780 cloned_block->SetParent(outlined_function);
781
782 // Redirect any OpPhi operands whose predecessors are the original region
783 // entry block to become the new function entry block.
784 cloned_block->ForEachPhiInst([this](opt::Instruction* phi_inst) {
785 for (uint32_t predecessor_index = 1;
786 predecessor_index < phi_inst->NumInOperands();
787 predecessor_index += 2) {
788 if (phi_inst->GetSingleWordInOperand(predecessor_index) ==
789 message_.entry_block()) {
790 phi_inst->SetInOperand(predecessor_index,
791 {message_.new_function_region_entry_block()});
792 }
793 }
794 });
795
796 outlined_function->AddBasicBlock(std::move(cloned_block));
797 block_it = block_it.Erase();
798 }
799 assert(outlined_region_exit_block != nullptr &&
800 "We should have encountered the region's exit block when iterating "
801 "through the function");
802
803 // We now need to adapt the exit block for the region - in the new function -
804 // so that it ends with a return.
805
806 // We first eliminate the merge instruction (if any) and the terminator for
807 // the cloned exit block.
808 for (auto inst_it = outlined_region_exit_block->begin();
809 inst_it != outlined_region_exit_block->end();) {
810 if (inst_it->opcode() == SpvOpLoopMerge ||
811 inst_it->opcode() == SpvOpSelectionMerge) {
812 inst_it = inst_it.Erase();
813 } else if (inst_it->IsBlockTerminator()) {
814 inst_it = inst_it.Erase();
815 } else {
816 ++inst_it;
817 }
818 }
819
820 // We now add either OpReturn or OpReturnValue as the cloned exit block's
821 // terminator.
822 if (region_output_ids.empty()) {
823 // The case where there are no region output ids is simple: we just add
824 // OpReturn.
825 outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
826 context, SpvOpReturn, 0, 0, opt::Instruction::OperandList()));
827 } else {
828 // In the case where there are output ids, we add an OpCompositeConstruct
829 // instruction to pack all the output values into a struct, and then an
830 // OpReturnValue instruction to return this struct.
831 opt::Instruction::OperandList struct_member_operands;
832 for (uint32_t id : region_output_ids) {
833 struct_member_operands.push_back(
834 {SPV_OPERAND_TYPE_ID, {output_id_to_fresh_id_map.at(id)}});
835 }
836 outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
837 context, SpvOpCompositeConstruct,
838 message_.new_function_struct_return_type_id(),
839 message_.new_callee_result_id(), struct_member_operands));
840 outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
841 context, SpvOpReturnValue, 0, 0,
842 opt::Instruction::OperandList(
843 {{SPV_OPERAND_TYPE_ID, {message_.new_callee_result_id()}}})));
844 }
845
846 outlined_function->SetFunctionEnd(MakeUnique<opt::Instruction>(
847 context, SpvOpFunctionEnd, 0, 0, opt::Instruction::OperandList()));
848 }
849
ShrinkOriginalRegion(opt::IRContext * context,std::set<opt::BasicBlock * > & region_blocks,const std::vector<uint32_t> & region_input_ids,const std::vector<uint32_t> & region_output_ids,const std::map<uint32_t,uint32_t> & output_id_to_type_id,uint32_t return_type_id,std::unique_ptr<opt::Instruction> cloned_exit_block_merge,std::unique_ptr<opt::Instruction> cloned_exit_block_terminator,opt::BasicBlock * original_region_entry_block) const850 void TransformationOutlineFunction::ShrinkOriginalRegion(
851 opt::IRContext* context, std::set<opt::BasicBlock*>& region_blocks,
852 const std::vector<uint32_t>& region_input_ids,
853 const std::vector<uint32_t>& region_output_ids,
854 const std::map<uint32_t, uint32_t>& output_id_to_type_id,
855 uint32_t return_type_id,
856 std::unique_ptr<opt::Instruction> cloned_exit_block_merge,
857 std::unique_ptr<opt::Instruction> cloned_exit_block_terminator,
858 opt::BasicBlock* original_region_entry_block) const {
859 // Erase all blocks from the original function that are in the outlined
860 // region, except for the region's entry block.
861 //
862 // In the process, identify all references to the exit block of the region,
863 // as merge blocks, continue targets, or OpPhi predecessors, and rewrite them
864 // to refer to the region entry block (the single block to which we are
865 // shrinking the region).
866 auto enclosing_function = original_region_entry_block->GetParent();
867 for (auto block_it = enclosing_function->begin();
868 block_it != enclosing_function->end();) {
869 if (&*block_it == original_region_entry_block) {
870 ++block_it;
871 } else if (region_blocks.count(&*block_it) == 0) {
872 // The block is not in the region. Check whether it has the last block
873 // of the region as an OpPhi predecessor, and if so change the
874 // predecessor to be the first block of the region (i.e. the block
875 // containing the call to what was outlined).
876 assert(block_it->MergeBlockIdIfAny() != message_.exit_block() &&
877 "Outlined region must not end with a merge block");
878 assert(block_it->ContinueBlockIdIfAny() != message_.exit_block() &&
879 "Outlined region must not end with a continue target");
880 block_it->ForEachPhiInst([this](opt::Instruction* phi_inst) {
881 for (uint32_t predecessor_index = 1;
882 predecessor_index < phi_inst->NumInOperands();
883 predecessor_index += 2) {
884 if (phi_inst->GetSingleWordInOperand(predecessor_index) ==
885 message_.exit_block()) {
886 phi_inst->SetInOperand(predecessor_index, {message_.entry_block()});
887 }
888 }
889 });
890 ++block_it;
891 } else {
892 // The block is in the region and is not the region's entry block: kill
893 // it.
894 block_it = block_it.Erase();
895 }
896 }
897
898 // Now erase all instructions from the region's entry block, as they have
899 // been outlined.
900 for (auto inst_it = original_region_entry_block->begin();
901 inst_it != original_region_entry_block->end();) {
902 inst_it = inst_it.Erase();
903 }
904
905 // Now we add a call to the outlined function to the region's entry block.
906 opt::Instruction::OperandList function_call_operands;
907 function_call_operands.push_back(
908 {SPV_OPERAND_TYPE_ID, {message_.new_function_id()}});
909 // The function parameters are the region input ids.
910 for (auto input_id : region_input_ids) {
911 function_call_operands.push_back({SPV_OPERAND_TYPE_ID, {input_id}});
912 }
913
914 original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
915 context, SpvOpFunctionCall, return_type_id,
916 message_.new_caller_result_id(), function_call_operands));
917
918 // If there are output ids, the function call will return a struct. For each
919 // output id, we add an extract operation to pull the appropriate struct
920 // member out into an output id.
921 for (uint32_t index = 0; index < region_output_ids.size(); ++index) {
922 uint32_t output_id = region_output_ids[index];
923 original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
924 context, SpvOpCompositeExtract, output_id_to_type_id.at(output_id),
925 output_id,
926 opt::Instruction::OperandList(
927 {{SPV_OPERAND_TYPE_ID, {message_.new_caller_result_id()}},
928 {SPV_OPERAND_TYPE_LITERAL_INTEGER, {index}}})));
929 }
930
931 // Finally, we terminate the block with the merge instruction (if any) that
932 // used to belong to the region's exit block, and the terminator that used
933 // to belong to the region's exit block.
934 if (cloned_exit_block_merge != nullptr) {
935 original_region_entry_block->AddInstruction(
936 std::move(cloned_exit_block_merge));
937 }
938 original_region_entry_block->AddInstruction(
939 std::move(cloned_exit_block_terminator));
940 }
941
942 } // namespace fuzz
943 } // namespace spvtools
944