1 // Copyright (c) 2019 Google LLC
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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"
19 #include "source/fuzz/fuzzer_util.h"
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
)) {
63 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
64 message_
.new_function_type_id(), ir_context
,
65 &ids_used_by_this_transformation
)) {
69 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
70 message_
.new_function_id(), ir_context
,
71 &ids_used_by_this_transformation
)) {
75 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
76 message_
.new_function_region_entry_block(), ir_context
,
77 &ids_used_by_this_transformation
)) {
81 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
82 message_
.new_caller_result_id(), ir_context
,
83 &ids_used_by_this_transformation
)) {
87 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
88 message_
.new_callee_result_id(), ir_context
,
89 &ids_used_by_this_transformation
)) {
93 for (auto& pair
: message_
.input_id_to_fresh_id()) {
94 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
95 pair
.second(), ir_context
, &ids_used_by_this_transformation
)) {
100 for (auto& pair
: message_
.output_id_to_fresh_id()) {
101 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
102 pair
.second(), ir_context
, &ids_used_by_this_transformation
)) {
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
) {
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
121 if (entry_block
->begin()->opcode() == spv::Op::OpVariable
) {
125 // For simplicity, we do not allow the entry block to be a loop header.
126 if (entry_block
->GetLoopMergeInst()) {
130 // For simplicity, we do not allow the exit block to be a merge block or
132 if (fuzzerutil::IsMergeOrContinue(ir_context
, exit_block
->id())) {
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
) {
143 // The block must be in the same function.
144 if (entry_block
->GetParent() != exit_block
->GetParent()) {
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
)) {
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
)) {
162 // Find all the blocks dominated by |message_.entry_block| and post-dominated
163 // by |message_.exit_block|.
164 auto region_set
= GetRegionBlocks(
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
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.
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()) {
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 ®ion_set
](uint32_t successor
) -> bool {
211 if (region_set
.count(ir_context
->cfg()->block(successor
)) == 0) {
212 all_successors_in_region
= false;
217 if (!all_successors_in_region
) {
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.
227 ir_context
->cfg()->block(merge
->GetSingleWordOperand(0));
228 if (region_set
.count(&block
) != region_set
.count(merge_block
)) {
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
)) {
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()) {
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
:
267 // Anything else is not OK.
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
)) {
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
287 || ir_context
->get_def_use_mgr()
288 ->GetDef(fuzzerutil::GetTypeId(ir_context
, id
))
289 ->opcode() == spv::Op::OpTypePointer
) {
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(
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(
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
))
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
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
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_
;
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
, ®ion_set
, &result
](opt::Instruction
* function_parameter
) {
436 // Consider every use of the parameter.
437 ir_context
->get_def_use_mgr()->WhileEachUse(
439 [ir_context
, function_parameter
, ®ion_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());
453 // Consider all definitions in the function that might turn out to be input
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
);
464 // Blocks in the region cannot generate input ids.
468 // Consider each candidate input id to check whether it is used in the
470 for (auto& inst
: candidate_input_ids_for_block
) {
471 ir_context
->get_def_use_mgr()->WhileEachUse(
473 [ir_context
, &inst
, region_exit_block
, ®ion_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
);
481 // There might be no containing block, e.g. if the use is in a
486 if (region_set
.count(use_block
) == 0) {
487 // The use is not in the region: this does not make it an input
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.
498 result
.push_back(inst
->result_id());
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.
517 // Consider each use of each instruction defined in the block.
518 for (auto& inst
: block
) {
519 ir_context
->get_def_use_mgr()->WhileEachUse(
521 [®ion_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
);
529 // There might be no containing block, e.g. if the use is in a
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
543 result
.push_back(inst
.result_id());
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
);
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
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());
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) {
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.
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();
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(
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
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.
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
) {
819 // Clone the block so that it can be added to the new function.
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();
869 // We now add either OpReturn or OpReturnValue as the cloned exit block's
871 if (region_output_ids
.empty()) {
872 // The case where there are no region output ids is simple: we just add
874 outlined_region_exit_block
->AddInstruction(MakeUnique
<opt::Instruction
>(
875 ir_context
, spv::Op::OpReturn
, 0, 0, opt::Instruction::OperandList()));
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
) {
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()});
946 // The block is in the region and is not the region's entry block: kill
948 block_it
= block_it
.Erase();
952 // Now erase all instructions from the region's entry block, as they have
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
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.
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()
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());
1027 } // namespace spvtools