Roll external/abseil_cpp/ 8f739d18b..917bfee46 (2 commits) (#5887)
[KhronosGroup/SPIRV-Tools.git] / source / fuzz / transformation_outline_function.cpp
blob4ab68d078c46526c912367f019c0cbf3ddac1758
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.
15 #include "source/fuzz/transformation_outline_function.h"
17 #include <set>
19 #include "source/fuzz/fuzzer_util.h"
21 namespace spvtools {
22 namespace fuzz {
24 TransformationOutlineFunction::TransformationOutlineFunction(
25 protobufs::TransformationOutlineFunction message)
26 : message_(std::move(message)) {}
28 TransformationOutlineFunction::TransformationOutlineFunction(
29 uint32_t entry_block, uint32_t exit_block,
30 uint32_t new_function_struct_return_type_id, uint32_t new_function_type_id,
31 uint32_t new_function_id, uint32_t new_function_region_entry_block,
32 uint32_t new_caller_result_id, uint32_t new_callee_result_id,
33 const std::map<uint32_t, uint32_t>& input_id_to_fresh_id,
34 const std::map<uint32_t, uint32_t>& output_id_to_fresh_id) {
35 message_.set_entry_block(entry_block);
36 message_.set_exit_block(exit_block);
37 message_.set_new_function_struct_return_type_id(
38 new_function_struct_return_type_id);
39 message_.set_new_function_type_id(new_function_type_id);
40 message_.set_new_function_id(new_function_id);
41 message_.set_new_function_region_entry_block(new_function_region_entry_block);
42 message_.set_new_caller_result_id(new_caller_result_id);
43 message_.set_new_callee_result_id(new_callee_result_id);
44 *message_.mutable_input_id_to_fresh_id() =
45 fuzzerutil::MapToRepeatedUInt32Pair(input_id_to_fresh_id);
46 *message_.mutable_output_id_to_fresh_id() =
47 fuzzerutil::MapToRepeatedUInt32Pair(output_id_to_fresh_id);
50 bool TransformationOutlineFunction::IsApplicable(
51 opt::IRContext* ir_context,
52 const TransformationContext& transformation_context) const {
53 std::set<uint32_t> ids_used_by_this_transformation;
55 // The various new ids used by the transformation must be fresh and distinct.
57 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
58 message_.new_function_struct_return_type_id(), ir_context,
59 &ids_used_by_this_transformation)) {
60 return false;
63 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
64 message_.new_function_type_id(), ir_context,
65 &ids_used_by_this_transformation)) {
66 return false;
69 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
70 message_.new_function_id(), ir_context,
71 &ids_used_by_this_transformation)) {
72 return false;
75 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
76 message_.new_function_region_entry_block(), ir_context,
77 &ids_used_by_this_transformation)) {
78 return false;
81 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
82 message_.new_caller_result_id(), ir_context,
83 &ids_used_by_this_transformation)) {
84 return false;
87 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
88 message_.new_callee_result_id(), ir_context,
89 &ids_used_by_this_transformation)) {
90 return false;
93 for (auto& pair : message_.input_id_to_fresh_id()) {
94 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
95 pair.second(), ir_context, &ids_used_by_this_transformation)) {
96 return false;
100 for (auto& pair : message_.output_id_to_fresh_id()) {
101 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
102 pair.second(), ir_context, &ids_used_by_this_transformation)) {
103 return false;
107 // The entry and exit block ids must indeed refer to blocks.
108 for (auto block_id : {message_.entry_block(), message_.exit_block()}) {
109 auto block_label = ir_context->get_def_use_mgr()->GetDef(block_id);
110 if (!block_label || block_label->opcode() != spv::Op::OpLabel) {
111 return false;
115 auto entry_block = ir_context->cfg()->block(message_.entry_block());
116 auto exit_block = ir_context->cfg()->block(message_.exit_block());
118 // The entry block cannot start with OpVariable - this would mean that
119 // outlining would remove a variable from the function containing the region
120 // being outlined.
121 if (entry_block->begin()->opcode() == spv::Op::OpVariable) {
122 return false;
125 // For simplicity, we do not allow the entry block to be a loop header.
126 if (entry_block->GetLoopMergeInst()) {
127 return false;
130 // For simplicity, we do not allow the exit block to be a merge block or
131 // continue target.
132 if (fuzzerutil::IsMergeOrContinue(ir_context, exit_block->id())) {
133 return false;
136 // The entry block cannot start with OpPhi. This is to keep the
137 // transformation logic simple. (Another transformation to split the OpPhis
138 // from a block could be applied to avoid this scenario.)
139 if (entry_block->begin()->opcode() == spv::Op::OpPhi) {
140 return false;
143 // The block must be in the same function.
144 if (entry_block->GetParent() != exit_block->GetParent()) {
145 return false;
148 // The entry block must dominate the exit block.
149 auto dominator_analysis =
150 ir_context->GetDominatorAnalysis(entry_block->GetParent());
151 if (!dominator_analysis->Dominates(entry_block, exit_block)) {
152 return false;
155 // The exit block must post-dominate the entry block.
156 auto postdominator_analysis =
157 ir_context->GetPostDominatorAnalysis(entry_block->GetParent());
158 if (!postdominator_analysis->Dominates(exit_block, entry_block)) {
159 return false;
162 // Find all the blocks dominated by |message_.entry_block| and post-dominated
163 // by |message_.exit_block|.
164 auto region_set = GetRegionBlocks(
165 ir_context,
166 entry_block = ir_context->cfg()->block(message_.entry_block()),
167 exit_block = ir_context->cfg()->block(message_.exit_block()));
169 // Check whether |region_set| really is a single-entry single-exit region, and
170 // also check whether structured control flow constructs and their merge
171 // and continue constructs are either wholly in or wholly out of the region -
172 // e.g. avoid the situation where the region contains the head of a loop but
173 // not the loop's continue construct.
175 // This is achieved by going through every block in the function that contains
176 // the region.
177 for (auto& block : *entry_block->GetParent()) {
178 if (region_set.count(&block) != 0) {
179 // The block is in the region. Check that it does not have any unreachable
180 // predecessors. If it does, then we do not regard the region as single-
181 // entry-single-exit and hence do not outline it.
182 for (auto pred : ir_context->cfg()->preds(block.id())) {
183 if (!ir_context->IsReachable(*ir_context->cfg()->block(pred))) {
184 // The predecessor is unreachable.
185 return false;
190 if (&block == exit_block) {
191 // It is OK (and typically expected) for the exit block of the region to
192 // have successors outside the region.
194 // It is also OK for the exit block to head a selection construct: the
195 // block containing the call to the outlined function will end up heading
196 // this construct if outlining takes place. However, it is not OK for
197 // the exit block to head a loop construct.
198 if (block.GetLoopMergeInst()) {
199 return false;
201 continue;
204 if (region_set.count(&block) != 0) {
205 // The block is in the region and is not the region's exit block. Let's
206 // see whether all of the block's successors are in the region. If they
207 // are not, the region is not single-entry single-exit.
208 bool all_successors_in_region = true;
209 block.WhileEachSuccessorLabel([&all_successors_in_region, ir_context,
210 &region_set](uint32_t successor) -> bool {
211 if (region_set.count(ir_context->cfg()->block(successor)) == 0) {
212 all_successors_in_region = false;
213 return false;
215 return true;
217 if (!all_successors_in_region) {
218 return false;
222 if (auto merge = block.GetMergeInst()) {
223 // The block is a loop or selection header -- the header and its
224 // associated merge block had better both be in the region or both be
225 // outside the region.
226 auto merge_block =
227 ir_context->cfg()->block(merge->GetSingleWordOperand(0));
228 if (region_set.count(&block) != region_set.count(merge_block)) {
229 return false;
233 if (auto loop_merge = block.GetLoopMergeInst()) {
234 // Similar to the above, but for the continue target of a loop.
235 auto continue_target =
236 ir_context->cfg()->block(loop_merge->GetSingleWordOperand(1));
237 if (continue_target != exit_block &&
238 region_set.count(&block) != region_set.count(continue_target)) {
239 return false;
244 // For each region input id, i.e. every id defined outside the region but
245 // used inside the region, ...
246 auto input_id_to_fresh_id_map =
247 fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id());
248 for (auto id : GetRegionInputIds(ir_context, region_set, exit_block)) {
249 // There needs to be a corresponding fresh id to be used as a function
250 // parameter, or overflow ids need to be available.
251 if (input_id_to_fresh_id_map.count(id) == 0 &&
252 !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
253 return false;
255 // Furthermore, if the input id has pointer type it must be an OpVariable
256 // or OpFunctionParameter.
257 auto input_id_inst = ir_context->get_def_use_mgr()->GetDef(id);
258 if (ir_context->get_def_use_mgr()
259 ->GetDef(input_id_inst->type_id())
260 ->opcode() == spv::Op::OpTypePointer) {
261 switch (input_id_inst->opcode()) {
262 case spv::Op::OpFunctionParameter:
263 case spv::Op::OpVariable:
264 // These are OK.
265 break;
266 default:
267 // Anything else is not OK.
268 return false;
273 // For each region output id -- i.e. every id defined inside the region but
274 // used outside the region, ...
275 auto output_id_to_fresh_id_map =
276 fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id());
277 for (auto id : GetRegionOutputIds(ir_context, region_set, exit_block)) {
278 if (
279 // ... there needs to be a corresponding fresh id that can hold the
280 // value for this id computed in the outlined function (or overflow ids
281 // must be available), and ...
282 (output_id_to_fresh_id_map.count(id) == 0 &&
283 !transformation_context.GetOverflowIdSource()->HasOverflowIds())
284 // ... the output id must not have pointer type (to avoid creating a
285 // struct with pointer members to pass data out of the outlined
286 // function)
287 || ir_context->get_def_use_mgr()
288 ->GetDef(fuzzerutil::GetTypeId(ir_context, id))
289 ->opcode() == spv::Op::OpTypePointer) {
290 return false;
294 return true;
297 void TransformationOutlineFunction::Apply(
298 opt::IRContext* ir_context,
299 TransformationContext* transformation_context) const {
300 // The entry block for the region before outlining.
301 auto original_region_entry_block =
302 ir_context->cfg()->block(message_.entry_block());
304 // The exit block for the region before outlining.
305 auto original_region_exit_block =
306 ir_context->cfg()->block(message_.exit_block());
308 // The single-entry single-exit region defined by |message_.entry_block| and
309 // |message_.exit_block|.
310 std::set<opt::BasicBlock*> region_blocks = GetRegionBlocks(
311 ir_context, original_region_entry_block, original_region_exit_block);
313 // Input and output ids for the region being outlined.
314 std::vector<uint32_t> region_input_ids =
315 GetRegionInputIds(ir_context, region_blocks, original_region_exit_block);
316 std::vector<uint32_t> region_output_ids =
317 GetRegionOutputIds(ir_context, region_blocks, original_region_exit_block);
319 // Maps from input and output ids to fresh ids.
320 auto input_id_to_fresh_id_map =
321 fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id());
322 auto output_id_to_fresh_id_map =
323 fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id());
325 // Use overflow ids to augment these maps at any locations where fresh ids are
326 // required but not provided.
327 for (uint32_t id : region_input_ids) {
328 if (input_id_to_fresh_id_map.count(id) == 0) {
329 input_id_to_fresh_id_map.insert(
330 {id,
331 transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
334 for (uint32_t id : region_output_ids) {
335 if (output_id_to_fresh_id_map.count(id) == 0) {
336 output_id_to_fresh_id_map.insert(
337 {id,
338 transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
342 UpdateModuleIdBoundForFreshIds(ir_context, input_id_to_fresh_id_map,
343 output_id_to_fresh_id_map);
345 // Construct a map that associates each output id with its type id.
346 std::map<uint32_t, uint32_t> output_id_to_type_id;
347 for (uint32_t output_id : region_output_ids) {
348 output_id_to_type_id[output_id] =
349 ir_context->get_def_use_mgr()->GetDef(output_id)->type_id();
352 // The region will be collapsed to a single block that calls a function
353 // containing the outlined region. This block needs to end with whatever
354 // the exit block of the region ended with before outlining. We thus clone
355 // the terminator of the region's exit block, and the merge instruction for
356 // the block if there is one, so that we can append them to the end of the
357 // collapsed block later.
358 std::unique_ptr<opt::Instruction> cloned_exit_block_terminator =
359 std::unique_ptr<opt::Instruction>(
360 original_region_exit_block->terminator()->Clone(ir_context));
361 std::unique_ptr<opt::Instruction> cloned_exit_block_merge =
362 original_region_exit_block->GetMergeInst()
363 ? std::unique_ptr<opt::Instruction>(
364 original_region_exit_block->GetMergeInst()->Clone(ir_context))
365 : nullptr;
367 // Make a function prototype for the outlined function, which involves
368 // figuring out its required type.
369 std::unique_ptr<opt::Function> outlined_function = PrepareFunctionPrototype(
370 region_input_ids, region_output_ids, input_id_to_fresh_id_map, ir_context,
371 transformation_context);
373 // Adapt the region to be outlined so that its input ids are replaced with the
374 // ids of the outlined function's input parameters, and so that output ids
375 // are similarly remapped.
376 RemapInputAndOutputIdsInRegion(
377 ir_context, *original_region_exit_block, region_blocks, region_input_ids,
378 region_output_ids, input_id_to_fresh_id_map, output_id_to_fresh_id_map);
380 // Fill out the body of the outlined function according to the region that is
381 // being outlined.
382 PopulateOutlinedFunction(
383 *original_region_entry_block, *original_region_exit_block, region_blocks,
384 region_output_ids, output_id_to_type_id, output_id_to_fresh_id_map,
385 ir_context, outlined_function.get());
387 // Collapse the region that has been outlined into a function down to a single
388 // block that calls said function.
389 ShrinkOriginalRegion(
390 ir_context, region_blocks, region_input_ids, region_output_ids,
391 output_id_to_type_id, outlined_function->type_id(),
392 std::move(cloned_exit_block_merge),
393 std::move(cloned_exit_block_terminator), original_region_entry_block);
395 // Add the outlined function to the module.
396 const auto* outlined_function_ptr = outlined_function.get();
397 ir_context->module()->AddFunction(std::move(outlined_function));
399 // Major surgery has been conducted on the module, so invalidate all analyses.
400 ir_context->InvalidateAnalysesExceptFor(
401 opt::IRContext::Analysis::kAnalysisNone);
403 // If the original function was livesafe, the new function should also be
404 // livesafe.
405 if (transformation_context->GetFactManager()->FunctionIsLivesafe(
406 original_region_entry_block->GetParent()->result_id())) {
407 transformation_context->GetFactManager()->AddFactFunctionIsLivesafe(
408 message_.new_function_id());
411 // Record the fact that all blocks in the outlined region are dead if the
412 // first block is dead.
413 if (transformation_context->GetFactManager()->BlockIsDead(
414 original_region_entry_block->id())) {
415 transformation_context->GetFactManager()->AddFactBlockIsDead(
416 outlined_function_ptr->entry()->id());
420 protobufs::Transformation TransformationOutlineFunction::ToMessage() const {
421 protobufs::Transformation result;
422 *result.mutable_outline_function() = message_;
423 return result;
426 std::vector<uint32_t> TransformationOutlineFunction::GetRegionInputIds(
427 opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_set,
428 opt::BasicBlock* region_exit_block) {
429 std::vector<uint32_t> result;
431 auto enclosing_function = region_exit_block->GetParent();
433 // Consider each parameter of the function containing the region.
434 enclosing_function->ForEachParam(
435 [ir_context, &region_set, &result](opt::Instruction* function_parameter) {
436 // Consider every use of the parameter.
437 ir_context->get_def_use_mgr()->WhileEachUse(
438 function_parameter,
439 [ir_context, function_parameter, &region_set, &result](
440 opt::Instruction* use, uint32_t /*unused*/) {
441 // Get the block, if any, in which the parameter is used.
442 auto use_block = ir_context->get_instr_block(use);
443 // If the use is in a block that lies within the region, the
444 // parameter is an input id for the region.
445 if (use_block && region_set.count(use_block) != 0) {
446 result.push_back(function_parameter->result_id());
447 return false;
449 return true;
453 // Consider all definitions in the function that might turn out to be input
454 // ids.
455 for (auto& block : *enclosing_function) {
456 std::vector<opt::Instruction*> candidate_input_ids_for_block;
457 if (region_set.count(&block) == 0) {
458 // All instructions in blocks outside the region are candidate's for
459 // generating input ids.
460 for (auto& inst : block) {
461 candidate_input_ids_for_block.push_back(&inst);
463 } else {
464 // Blocks in the region cannot generate input ids.
465 continue;
468 // Consider each candidate input id to check whether it is used in the
469 // region.
470 for (auto& inst : candidate_input_ids_for_block) {
471 ir_context->get_def_use_mgr()->WhileEachUse(
472 inst,
473 [ir_context, &inst, region_exit_block, &region_set, &result](
474 opt::Instruction* use, uint32_t /*unused*/) -> bool {
475 // Find the block in which this id use occurs, recording the id as
476 // an input id if the block is outside the region, with some
477 // exceptions detailed below.
478 auto use_block = ir_context->get_instr_block(use);
480 if (!use_block) {
481 // There might be no containing block, e.g. if the use is in a
482 // decoration.
483 return true;
486 if (region_set.count(use_block) == 0) {
487 // The use is not in the region: this does not make it an input
488 // id.
489 return true;
492 if (use_block == region_exit_block && use->IsBlockTerminator()) {
493 // We do not regard uses in the exit block terminator as input
494 // ids, as this terminator does not get outlined.
495 return true;
498 result.push_back(inst->result_id());
499 return false;
503 return result;
506 std::vector<uint32_t> TransformationOutlineFunction::GetRegionOutputIds(
507 opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_set,
508 opt::BasicBlock* region_exit_block) {
509 std::vector<uint32_t> result;
511 // Consider each block in the function containing the region.
512 for (auto& block : *region_exit_block->GetParent()) {
513 if (region_set.count(&block) == 0) {
514 // Skip blocks that are not in the region.
515 continue;
517 // Consider each use of each instruction defined in the block.
518 for (auto& inst : block) {
519 ir_context->get_def_use_mgr()->WhileEachUse(
520 &inst,
521 [&region_set, ir_context, &inst, region_exit_block, &result](
522 opt::Instruction* use, uint32_t /*unused*/) -> bool {
523 // Find the block in which this id use occurs, recording the id as
524 // an output id if the block is outside the region, with some
525 // exceptions detailed below.
526 auto use_block = ir_context->get_instr_block(use);
528 if (!use_block) {
529 // There might be no containing block, e.g. if the use is in a
530 // decoration.
531 return true;
534 if (region_set.count(use_block) != 0) {
535 // The use is in the region.
536 if (use_block != region_exit_block || !use->IsBlockTerminator()) {
537 // Furthermore, the use is not in the terminator of the region's
538 // exit block.
539 return true;
543 result.push_back(inst.result_id());
544 return false;
548 return result;
551 std::set<opt::BasicBlock*> TransformationOutlineFunction::GetRegionBlocks(
552 opt::IRContext* ir_context, opt::BasicBlock* entry_block,
553 opt::BasicBlock* exit_block) {
554 auto enclosing_function = entry_block->GetParent();
555 auto dominator_analysis =
556 ir_context->GetDominatorAnalysis(enclosing_function);
557 auto postdominator_analysis =
558 ir_context->GetPostDominatorAnalysis(enclosing_function);
560 std::set<opt::BasicBlock*> result;
561 for (auto& block : *enclosing_function) {
562 if (dominator_analysis->Dominates(entry_block, &block) &&
563 postdominator_analysis->Dominates(exit_block, &block)) {
564 result.insert(&block);
567 return result;
570 std::unique_ptr<opt::Function>
571 TransformationOutlineFunction::PrepareFunctionPrototype(
572 const std::vector<uint32_t>& region_input_ids,
573 const std::vector<uint32_t>& region_output_ids,
574 const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
575 opt::IRContext* ir_context,
576 TransformationContext* transformation_context) const {
577 uint32_t return_type_id = 0;
578 uint32_t function_type_id = 0;
580 // First, try to find an existing function type that is suitable. This is
581 // only possible if the region generates no output ids; if it generates output
582 // ids we are going to make a new struct for those, and since that struct does
583 // not exist there cannot already be a function type with this struct as its
584 // return type.
585 if (region_output_ids.empty()) {
586 std::vector<uint32_t> return_and_parameter_types;
587 opt::analysis::Void void_type;
588 return_type_id = ir_context->get_type_mgr()->GetId(&void_type);
589 return_and_parameter_types.push_back(return_type_id);
590 for (auto id : region_input_ids) {
591 return_and_parameter_types.push_back(
592 ir_context->get_def_use_mgr()->GetDef(id)->type_id());
594 function_type_id =
595 fuzzerutil::FindFunctionType(ir_context, return_and_parameter_types);
598 // If no existing function type was found, we need to create one.
599 if (function_type_id == 0) {
600 assert(
601 ((return_type_id == 0) == !region_output_ids.empty()) &&
602 "We should only have set the return type if there are no output ids.");
603 // If the region generates output ids, we need to make a struct with one
604 // field per output id.
605 if (!region_output_ids.empty()) {
606 opt::Instruction::OperandList struct_member_types;
607 for (uint32_t output_id : region_output_ids) {
608 auto output_id_type =
609 ir_context->get_def_use_mgr()->GetDef(output_id)->type_id();
610 if (ir_context->get_def_use_mgr()->GetDef(output_id_type)->opcode() ==
611 spv::Op::OpTypeVoid) {
612 // We cannot add a void field to a struct. We instead use OpUndef to
613 // handle void output ids.
614 continue;
616 struct_member_types.push_back({SPV_OPERAND_TYPE_ID, {output_id_type}});
618 // Add a new struct type to the module.
619 ir_context->module()->AddType(MakeUnique<opt::Instruction>(
620 ir_context, spv::Op::OpTypeStruct, 0,
621 message_.new_function_struct_return_type_id(),
622 std::move(struct_member_types)));
623 // The return type for the function is the newly-created struct.
624 return_type_id = message_.new_function_struct_return_type_id();
626 assert(
627 return_type_id != 0 &&
628 "We should either have a void return type, or have created a struct.");
630 // The region's input ids dictate the parameter types to the function.
631 opt::Instruction::OperandList function_type_operands;
632 function_type_operands.push_back({SPV_OPERAND_TYPE_ID, {return_type_id}});
633 for (auto id : region_input_ids) {
634 function_type_operands.push_back(
635 {SPV_OPERAND_TYPE_ID,
636 {ir_context->get_def_use_mgr()->GetDef(id)->type_id()}});
638 // Add a new function type to the module, and record that this is the type
639 // id for the new function.
640 ir_context->module()->AddType(MakeUnique<opt::Instruction>(
641 ir_context, spv::Op::OpTypeFunction, 0, message_.new_function_type_id(),
642 function_type_operands));
643 function_type_id = message_.new_function_type_id();
646 // Create a new function with |message_.new_function_id| as the function id,
647 // and the return type and function type prepared above.
648 std::unique_ptr<opt::Function> outlined_function =
649 MakeUnique<opt::Function>(MakeUnique<opt::Instruction>(
650 ir_context, spv::Op::OpFunction, return_type_id,
651 message_.new_function_id(),
652 opt::Instruction::OperandList(
653 {{spv_operand_type_t ::SPV_OPERAND_TYPE_LITERAL_INTEGER,
654 {uint32_t(spv::FunctionControlMask::MaskNone)}},
655 {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
656 {function_type_id}}})));
658 // Add one parameter to the function for each input id, using the fresh ids
659 // provided in |input_id_to_fresh_id_map|, or overflow ids if needed.
660 for (auto id : region_input_ids) {
661 uint32_t fresh_id = input_id_to_fresh_id_map.at(id);
662 outlined_function->AddParameter(MakeUnique<opt::Instruction>(
663 ir_context, spv::Op::OpFunctionParameter,
664 ir_context->get_def_use_mgr()->GetDef(id)->type_id(), fresh_id,
665 opt::Instruction::OperandList()));
667 // Analyse the use of the new parameter instruction.
668 outlined_function->ForEachParam(
669 [fresh_id, ir_context](opt::Instruction* inst) {
670 if (inst->result_id() == fresh_id) {
671 ir_context->AnalyzeDefUse(inst);
675 // If the input id is an irrelevant-valued variable, the same should be true
676 // of the corresponding parameter.
677 if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
678 id)) {
679 transformation_context->GetFactManager()
680 ->AddFactValueOfPointeeIsIrrelevant(input_id_to_fresh_id_map.at(id));
684 return outlined_function;
687 void TransformationOutlineFunction::UpdateModuleIdBoundForFreshIds(
688 opt::IRContext* ir_context,
689 const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
690 const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) const {
691 // Enlarge the module's id bound as needed to accommodate the various fresh
692 // ids associated with the transformation.
693 fuzzerutil::UpdateModuleIdBound(
694 ir_context, message_.new_function_struct_return_type_id());
695 fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_function_type_id());
696 fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_function_id());
697 fuzzerutil::UpdateModuleIdBound(ir_context,
698 message_.new_function_region_entry_block());
699 fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_caller_result_id());
700 fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_callee_result_id());
702 for (auto& entry : input_id_to_fresh_id_map) {
703 fuzzerutil::UpdateModuleIdBound(ir_context, entry.second);
706 for (auto& entry : output_id_to_fresh_id_map) {
707 fuzzerutil::UpdateModuleIdBound(ir_context, entry.second);
711 void TransformationOutlineFunction::RemapInputAndOutputIdsInRegion(
712 opt::IRContext* ir_context,
713 const opt::BasicBlock& original_region_exit_block,
714 const std::set<opt::BasicBlock*>& region_blocks,
715 const std::vector<uint32_t>& region_input_ids,
716 const std::vector<uint32_t>& region_output_ids,
717 const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
718 const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) const {
719 // Change all uses of input ids inside the region to the corresponding fresh
720 // ids that will ultimately be parameters of the outlined function.
721 // This is done by considering each region input id in turn.
722 for (uint32_t id : region_input_ids) {
723 // We then consider each use of the input id.
724 ir_context->get_def_use_mgr()->ForEachUse(
725 id, [ir_context, id, &input_id_to_fresh_id_map, region_blocks](
726 opt::Instruction* use, uint32_t operand_index) {
727 // Find the block in which this use of the input id occurs.
728 opt::BasicBlock* use_block = ir_context->get_instr_block(use);
729 // We want to rewrite the use id if its block occurs in the outlined
730 // region.
731 if (region_blocks.count(use_block) != 0) {
732 // Rewrite this use of the input id.
733 use->SetOperand(operand_index, {input_id_to_fresh_id_map.at(id)});
738 // Change each definition of a region output id to define the corresponding
739 // fresh ids that will store intermediate value for the output ids. Also
740 // change all uses of the output id located in the outlined region.
741 // This is done by considering each region output id in turn.
742 for (uint32_t id : region_output_ids) {
743 // First consider each use of the output id and update the relevant uses.
744 ir_context->get_def_use_mgr()->ForEachUse(
745 id, [ir_context, &original_region_exit_block, id,
746 &output_id_to_fresh_id_map,
747 region_blocks](opt::Instruction* use, uint32_t operand_index) {
748 // Find the block in which this use of the output id occurs.
749 auto use_block = ir_context->get_instr_block(use);
750 // We want to rewrite the use id if its block occurs in the outlined
751 // region, with one exception: the terminator of the exit block of
752 // the region is going to remain in the original function, so if the
753 // use appears in such a terminator instruction we leave it alone.
754 if (
755 // The block is in the region ...
756 region_blocks.count(use_block) != 0 &&
757 // ... and the use is not in the terminator instruction of the
758 // region's exit block.
759 !(use_block == &original_region_exit_block &&
760 use->IsBlockTerminator())) {
761 // Rewrite this use of the output id.
762 use->SetOperand(operand_index, {output_id_to_fresh_id_map.at(id)});
766 // Now change the instruction that defines the output id so that it instead
767 // defines the corresponding fresh id. We do this after changing all the
768 // uses so that the definition of the original id is still registered when
769 // we analyse its uses.
770 ir_context->get_def_use_mgr()->GetDef(id)->SetResultId(
771 output_id_to_fresh_id_map.at(id));
775 void TransformationOutlineFunction::PopulateOutlinedFunction(
776 const opt::BasicBlock& original_region_entry_block,
777 const opt::BasicBlock& original_region_exit_block,
778 const std::set<opt::BasicBlock*>& region_blocks,
779 const std::vector<uint32_t>& region_output_ids,
780 const std::map<uint32_t, uint32_t>& output_id_to_type_id,
781 const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map,
782 opt::IRContext* ir_context, opt::Function* outlined_function) const {
783 // When we create the exit block for the outlined region, we use this pointer
784 // to track of it so that we can manipulate it later.
785 opt::BasicBlock* outlined_region_exit_block = nullptr;
787 // The region entry block in the new function is identical to the entry block
788 // of the region being outlined, except that it has
789 // |message_.new_function_region_entry_block| as its id.
790 std::unique_ptr<opt::BasicBlock> outlined_region_entry_block =
791 MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
792 ir_context, spv::Op::OpLabel, 0,
793 message_.new_function_region_entry_block(),
794 opt::Instruction::OperandList()));
796 if (&original_region_entry_block == &original_region_exit_block) {
797 outlined_region_exit_block = outlined_region_entry_block.get();
800 for (auto& inst : original_region_entry_block) {
801 outlined_region_entry_block->AddInstruction(
802 std::unique_ptr<opt::Instruction>(inst.Clone(ir_context)));
804 outlined_function->AddBasicBlock(std::move(outlined_region_entry_block));
806 // We now go through the single-entry single-exit region defined by the entry
807 // and exit blocks, adding clones of all blocks to the new function.
809 // Consider every block in the enclosing function.
810 auto enclosing_function = original_region_entry_block.GetParent();
811 for (auto block_it = enclosing_function->begin();
812 block_it != enclosing_function->end();) {
813 // Skip the region's entry block - we already dealt with it above.
814 if (region_blocks.count(&*block_it) == 0 ||
815 &*block_it == &original_region_entry_block) {
816 ++block_it;
817 continue;
819 // Clone the block so that it can be added to the new function.
820 auto cloned_block =
821 std::unique_ptr<opt::BasicBlock>(block_it->Clone(ir_context));
823 // If this is the region's exit block, then the cloned block is the outlined
824 // region's exit block.
825 if (&*block_it == &original_region_exit_block) {
826 assert(outlined_region_exit_block == nullptr &&
827 "We should not yet have encountered the exit block.");
828 outlined_region_exit_block = cloned_block.get();
831 // Redirect any OpPhi operands whose predecessors are the original region
832 // entry block to become the new function entry block.
833 cloned_block->ForEachPhiInst([this](opt::Instruction* phi_inst) {
834 for (uint32_t predecessor_index = 1;
835 predecessor_index < phi_inst->NumInOperands();
836 predecessor_index += 2) {
837 if (phi_inst->GetSingleWordInOperand(predecessor_index) ==
838 message_.entry_block()) {
839 phi_inst->SetInOperand(predecessor_index,
840 {message_.new_function_region_entry_block()});
845 outlined_function->AddBasicBlock(std::move(cloned_block));
846 block_it = block_it.Erase();
848 assert(outlined_region_exit_block != nullptr &&
849 "We should have encountered the region's exit block when iterating "
850 "through the function");
852 // We now need to adapt the exit block for the region - in the new function -
853 // so that it ends with a return.
855 // We first eliminate the merge instruction (if any) and the terminator for
856 // the cloned exit block.
857 for (auto inst_it = outlined_region_exit_block->begin();
858 inst_it != outlined_region_exit_block->end();) {
859 if (inst_it->opcode() == spv::Op::OpLoopMerge ||
860 inst_it->opcode() == spv::Op::OpSelectionMerge) {
861 inst_it = inst_it.Erase();
862 } else if (inst_it->IsBlockTerminator()) {
863 inst_it = inst_it.Erase();
864 } else {
865 ++inst_it;
869 // We now add either OpReturn or OpReturnValue as the cloned exit block's
870 // terminator.
871 if (region_output_ids.empty()) {
872 // The case where there are no region output ids is simple: we just add
873 // OpReturn.
874 outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
875 ir_context, spv::Op::OpReturn, 0, 0, opt::Instruction::OperandList()));
876 } else {
877 // In the case where there are output ids, we add an OpCompositeConstruct
878 // instruction to pack all the non-void output values into a struct, and
879 // then an OpReturnValue instruction to return this struct.
880 opt::Instruction::OperandList struct_member_operands;
881 for (uint32_t id : region_output_ids) {
882 if (ir_context->get_def_use_mgr()
883 ->GetDef(output_id_to_type_id.at(id))
884 ->opcode() != spv::Op::OpTypeVoid) {
885 struct_member_operands.push_back(
886 {SPV_OPERAND_TYPE_ID, {output_id_to_fresh_id_map.at(id)}});
889 outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
890 ir_context, spv::Op::OpCompositeConstruct,
891 message_.new_function_struct_return_type_id(),
892 message_.new_callee_result_id(), struct_member_operands));
893 outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
894 ir_context, spv::Op::OpReturnValue, 0, 0,
895 opt::Instruction::OperandList(
896 {{SPV_OPERAND_TYPE_ID, {message_.new_callee_result_id()}}})));
899 outlined_function->SetFunctionEnd(
900 MakeUnique<opt::Instruction>(ir_context, spv::Op::OpFunctionEnd, 0, 0,
901 opt::Instruction::OperandList()));
904 void TransformationOutlineFunction::ShrinkOriginalRegion(
905 opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_blocks,
906 const std::vector<uint32_t>& region_input_ids,
907 const std::vector<uint32_t>& region_output_ids,
908 const std::map<uint32_t, uint32_t>& output_id_to_type_id,
909 uint32_t return_type_id,
910 std::unique_ptr<opt::Instruction> cloned_exit_block_merge,
911 std::unique_ptr<opt::Instruction> cloned_exit_block_terminator,
912 opt::BasicBlock* original_region_entry_block) const {
913 // Erase all blocks from the original function that are in the outlined
914 // region, except for the region's entry block.
916 // In the process, identify all references to the exit block of the region,
917 // as merge blocks, continue targets, or OpPhi predecessors, and rewrite them
918 // to refer to the region entry block (the single block to which we are
919 // shrinking the region).
920 auto enclosing_function = original_region_entry_block->GetParent();
921 for (auto block_it = enclosing_function->begin();
922 block_it != enclosing_function->end();) {
923 if (&*block_it == original_region_entry_block) {
924 ++block_it;
925 } else if (region_blocks.count(&*block_it) == 0) {
926 // The block is not in the region. Check whether it has the last block
927 // of the region as an OpPhi predecessor, and if so change the
928 // predecessor to be the first block of the region (i.e. the block
929 // containing the call to what was outlined).
930 assert(block_it->MergeBlockIdIfAny() != message_.exit_block() &&
931 "Outlined region must not end with a merge block");
932 assert(block_it->ContinueBlockIdIfAny() != message_.exit_block() &&
933 "Outlined region must not end with a continue target");
934 block_it->ForEachPhiInst([this](opt::Instruction* phi_inst) {
935 for (uint32_t predecessor_index = 1;
936 predecessor_index < phi_inst->NumInOperands();
937 predecessor_index += 2) {
938 if (phi_inst->GetSingleWordInOperand(predecessor_index) ==
939 message_.exit_block()) {
940 phi_inst->SetInOperand(predecessor_index, {message_.entry_block()});
944 ++block_it;
945 } else {
946 // The block is in the region and is not the region's entry block: kill
947 // it.
948 block_it = block_it.Erase();
952 // Now erase all instructions from the region's entry block, as they have
953 // been outlined.
954 for (auto inst_it = original_region_entry_block->begin();
955 inst_it != original_region_entry_block->end();) {
956 inst_it = inst_it.Erase();
959 // Now we add a call to the outlined function to the region's entry block.
960 opt::Instruction::OperandList function_call_operands;
961 function_call_operands.push_back(
962 {SPV_OPERAND_TYPE_ID, {message_.new_function_id()}});
963 // The function parameters are the region input ids.
964 for (auto input_id : region_input_ids) {
965 function_call_operands.push_back({SPV_OPERAND_TYPE_ID, {input_id}});
968 original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
969 ir_context, spv::Op::OpFunctionCall, return_type_id,
970 message_.new_caller_result_id(), function_call_operands));
972 // If there are output ids, the function call will return a struct. For each
973 // output id, we add an extract operation to pull the appropriate struct
974 // member out into an output id. The exception is for output ids with void
975 // type. There are no struct entries for these, so we use an OpUndef of void
976 // type instead.
977 uint32_t struct_member_index = 0;
978 for (uint32_t output_id : region_output_ids) {
979 uint32_t output_type_id = output_id_to_type_id.at(output_id);
980 if (ir_context->get_def_use_mgr()->GetDef(output_type_id)->opcode() ==
981 spv::Op::OpTypeVoid) {
982 original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
983 ir_context, spv::Op::OpUndef, output_type_id, output_id,
984 opt::Instruction::OperandList()));
985 // struct_member_index is not incremented since there was no struct member
986 // associated with this void-typed output id.
987 } else {
988 original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
989 ir_context, spv::Op::OpCompositeExtract, output_type_id, output_id,
990 opt::Instruction::OperandList(
991 {{SPV_OPERAND_TYPE_ID, {message_.new_caller_result_id()}},
992 {SPV_OPERAND_TYPE_LITERAL_INTEGER, {struct_member_index}}})));
993 struct_member_index++;
997 // Finally, we terminate the block with the merge instruction (if any) that
998 // used to belong to the region's exit block, and the terminator that used
999 // to belong to the region's exit block.
1000 if (cloned_exit_block_merge != nullptr) {
1001 original_region_entry_block->AddInstruction(
1002 std::move(cloned_exit_block_merge));
1004 original_region_entry_block->AddInstruction(
1005 std::move(cloned_exit_block_terminator));
1008 std::unordered_set<uint32_t> TransformationOutlineFunction::GetFreshIds()
1009 const {
1010 std::unordered_set<uint32_t> result = {
1011 message_.new_function_struct_return_type_id(),
1012 message_.new_function_type_id(),
1013 message_.new_function_id(),
1014 message_.new_function_region_entry_block(),
1015 message_.new_caller_result_id(),
1016 message_.new_callee_result_id()};
1017 for (auto& pair : message_.input_id_to_fresh_id()) {
1018 result.insert(pair.second());
1020 for (auto& pair : message_.output_id_to_fresh_id()) {
1021 result.insert(pair.second());
1023 return result;
1026 } // namespace fuzz
1027 } // namespace spvtools