| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027 |
- // Copyright (c) 2019 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- #include "source/fuzz/transformation_outline_function.h"
- #include <set>
- #include "source/fuzz/fuzzer_util.h"
- namespace spvtools {
- namespace fuzz {
- TransformationOutlineFunction::TransformationOutlineFunction(
- protobufs::TransformationOutlineFunction message)
- : message_(std::move(message)) {}
- TransformationOutlineFunction::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,
- const std::map<uint32_t, uint32_t>& input_id_to_fresh_id,
- const std::map<uint32_t, uint32_t>& output_id_to_fresh_id) {
- message_.set_entry_block(entry_block);
- message_.set_exit_block(exit_block);
- message_.set_new_function_struct_return_type_id(
- new_function_struct_return_type_id);
- message_.set_new_function_type_id(new_function_type_id);
- message_.set_new_function_id(new_function_id);
- message_.set_new_function_region_entry_block(new_function_region_entry_block);
- message_.set_new_caller_result_id(new_caller_result_id);
- message_.set_new_callee_result_id(new_callee_result_id);
- *message_.mutable_input_id_to_fresh_id() =
- fuzzerutil::MapToRepeatedUInt32Pair(input_id_to_fresh_id);
- *message_.mutable_output_id_to_fresh_id() =
- fuzzerutil::MapToRepeatedUInt32Pair(output_id_to_fresh_id);
- }
- bool TransformationOutlineFunction::IsApplicable(
- opt::IRContext* ir_context,
- const TransformationContext& transformation_context) const {
- std::set<uint32_t> ids_used_by_this_transformation;
- // The various new ids used by the transformation must be fresh and distinct.
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- message_.new_function_struct_return_type_id(), ir_context,
- &ids_used_by_this_transformation)) {
- return false;
- }
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- message_.new_function_type_id(), ir_context,
- &ids_used_by_this_transformation)) {
- return false;
- }
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- message_.new_function_id(), ir_context,
- &ids_used_by_this_transformation)) {
- return false;
- }
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- message_.new_function_region_entry_block(), ir_context,
- &ids_used_by_this_transformation)) {
- return false;
- }
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- message_.new_caller_result_id(), ir_context,
- &ids_used_by_this_transformation)) {
- return false;
- }
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- message_.new_callee_result_id(), ir_context,
- &ids_used_by_this_transformation)) {
- return false;
- }
- for (auto& pair : message_.input_id_to_fresh_id()) {
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- pair.second(), ir_context, &ids_used_by_this_transformation)) {
- return false;
- }
- }
- for (auto& pair : message_.output_id_to_fresh_id()) {
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- pair.second(), ir_context, &ids_used_by_this_transformation)) {
- return false;
- }
- }
- // The entry and exit block ids must indeed refer to blocks.
- for (auto block_id : {message_.entry_block(), message_.exit_block()}) {
- auto block_label = ir_context->get_def_use_mgr()->GetDef(block_id);
- if (!block_label || block_label->opcode() != spv::Op::OpLabel) {
- return false;
- }
- }
- auto entry_block = ir_context->cfg()->block(message_.entry_block());
- auto exit_block = ir_context->cfg()->block(message_.exit_block());
- // The entry block cannot start with OpVariable - this would mean that
- // outlining would remove a variable from the function containing the region
- // being outlined.
- if (entry_block->begin()->opcode() == spv::Op::OpVariable) {
- return false;
- }
- // For simplicity, we do not allow the entry block to be a loop header.
- if (entry_block->GetLoopMergeInst()) {
- return false;
- }
- // For simplicity, we do not allow the exit block to be a merge block or
- // continue target.
- if (fuzzerutil::IsMergeOrContinue(ir_context, exit_block->id())) {
- return false;
- }
- // The entry block cannot start with OpPhi. This is to keep the
- // transformation logic simple. (Another transformation to split the OpPhis
- // from a block could be applied to avoid this scenario.)
- if (entry_block->begin()->opcode() == spv::Op::OpPhi) {
- return false;
- }
- // The block must be in the same function.
- if (entry_block->GetParent() != exit_block->GetParent()) {
- return false;
- }
- // The entry block must dominate the exit block.
- auto dominator_analysis =
- ir_context->GetDominatorAnalysis(entry_block->GetParent());
- if (!dominator_analysis->Dominates(entry_block, exit_block)) {
- return false;
- }
- // The exit block must post-dominate the entry block.
- auto postdominator_analysis =
- ir_context->GetPostDominatorAnalysis(entry_block->GetParent());
- if (!postdominator_analysis->Dominates(exit_block, entry_block)) {
- return false;
- }
- // Find all the blocks dominated by |message_.entry_block| and post-dominated
- // by |message_.exit_block|.
- auto region_set = GetRegionBlocks(
- ir_context,
- entry_block = ir_context->cfg()->block(message_.entry_block()),
- exit_block = ir_context->cfg()->block(message_.exit_block()));
- // Check whether |region_set| really is a single-entry single-exit region, and
- // also check whether structured control flow constructs and their merge
- // and continue constructs are either wholly in or wholly out of the region -
- // e.g. avoid the situation where the region contains the head of a loop but
- // not the loop's continue construct.
- //
- // This is achieved by going through every block in the function that contains
- // the region.
- for (auto& block : *entry_block->GetParent()) {
- if (region_set.count(&block) != 0) {
- // The block is in the region. Check that it does not have any unreachable
- // predecessors. If it does, then we do not regard the region as single-
- // entry-single-exit and hence do not outline it.
- for (auto pred : ir_context->cfg()->preds(block.id())) {
- if (!ir_context->IsReachable(*ir_context->cfg()->block(pred))) {
- // The predecessor is unreachable.
- return false;
- }
- }
- }
- if (&block == exit_block) {
- // It is OK (and typically expected) for the exit block of the region to
- // have successors outside the region.
- //
- // It is also OK for the exit block to head a selection construct: the
- // block containing the call to the outlined function will end up heading
- // this construct if outlining takes place. However, it is not OK for
- // the exit block to head a loop construct.
- if (block.GetLoopMergeInst()) {
- return false;
- }
- continue;
- }
- if (region_set.count(&block) != 0) {
- // The block is in the region and is not the region's exit block. Let's
- // see whether all of the block's successors are in the region. If they
- // are not, the region is not single-entry single-exit.
- bool all_successors_in_region = true;
- block.WhileEachSuccessorLabel([&all_successors_in_region, ir_context,
- ®ion_set](uint32_t successor) -> bool {
- if (region_set.count(ir_context->cfg()->block(successor)) == 0) {
- all_successors_in_region = false;
- return false;
- }
- return true;
- });
- if (!all_successors_in_region) {
- return false;
- }
- }
- if (auto merge = block.GetMergeInst()) {
- // The block is a loop or selection header -- the header and its
- // associated merge block had better both be in the region or both be
- // outside the region.
- auto merge_block =
- ir_context->cfg()->block(merge->GetSingleWordOperand(0));
- if (region_set.count(&block) != region_set.count(merge_block)) {
- return false;
- }
- }
- if (auto loop_merge = block.GetLoopMergeInst()) {
- // Similar to the above, but for the continue target of a loop.
- auto continue_target =
- ir_context->cfg()->block(loop_merge->GetSingleWordOperand(1));
- if (continue_target != exit_block &&
- region_set.count(&block) != region_set.count(continue_target)) {
- return false;
- }
- }
- }
- // For each region input id, i.e. every id defined outside the region but
- // used inside the region, ...
- auto input_id_to_fresh_id_map =
- fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id());
- for (auto id : GetRegionInputIds(ir_context, region_set, exit_block)) {
- // There needs to be a corresponding fresh id to be used as a function
- // parameter, or overflow ids need to be available.
- if (input_id_to_fresh_id_map.count(id) == 0 &&
- !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
- return false;
- }
- // Furthermore, if the input id has pointer type it must be an OpVariable
- // or OpFunctionParameter.
- auto input_id_inst = ir_context->get_def_use_mgr()->GetDef(id);
- if (ir_context->get_def_use_mgr()
- ->GetDef(input_id_inst->type_id())
- ->opcode() == spv::Op::OpTypePointer) {
- switch (input_id_inst->opcode()) {
- case spv::Op::OpFunctionParameter:
- case spv::Op::OpVariable:
- // These are OK.
- break;
- default:
- // Anything else is not OK.
- return false;
- }
- }
- }
- // For each region output id -- i.e. every id defined inside the region but
- // used outside the region, ...
- auto output_id_to_fresh_id_map =
- fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id());
- for (auto id : GetRegionOutputIds(ir_context, region_set, exit_block)) {
- if (
- // ... there needs to be a corresponding fresh id that can hold the
- // value for this id computed in the outlined function (or overflow ids
- // must be available), and ...
- (output_id_to_fresh_id_map.count(id) == 0 &&
- !transformation_context.GetOverflowIdSource()->HasOverflowIds())
- // ... the output id must not have pointer type (to avoid creating a
- // struct with pointer members to pass data out of the outlined
- // function)
- || ir_context->get_def_use_mgr()
- ->GetDef(fuzzerutil::GetTypeId(ir_context, id))
- ->opcode() == spv::Op::OpTypePointer) {
- return false;
- }
- }
- return true;
- }
- void TransformationOutlineFunction::Apply(
- opt::IRContext* ir_context,
- TransformationContext* transformation_context) const {
- // The entry block for the region before outlining.
- auto original_region_entry_block =
- ir_context->cfg()->block(message_.entry_block());
- // The exit block for the region before outlining.
- auto original_region_exit_block =
- ir_context->cfg()->block(message_.exit_block());
- // The single-entry single-exit region defined by |message_.entry_block| and
- // |message_.exit_block|.
- std::set<opt::BasicBlock*> region_blocks = GetRegionBlocks(
- ir_context, original_region_entry_block, original_region_exit_block);
- // Input and output ids for the region being outlined.
- std::vector<uint32_t> region_input_ids =
- GetRegionInputIds(ir_context, region_blocks, original_region_exit_block);
- std::vector<uint32_t> region_output_ids =
- GetRegionOutputIds(ir_context, region_blocks, original_region_exit_block);
- // Maps from input and output ids to fresh ids.
- auto input_id_to_fresh_id_map =
- fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id());
- auto output_id_to_fresh_id_map =
- fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id());
- // Use overflow ids to augment these maps at any locations where fresh ids are
- // required but not provided.
- for (uint32_t id : region_input_ids) {
- if (input_id_to_fresh_id_map.count(id) == 0) {
- input_id_to_fresh_id_map.insert(
- {id,
- transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
- }
- }
- for (uint32_t id : region_output_ids) {
- if (output_id_to_fresh_id_map.count(id) == 0) {
- output_id_to_fresh_id_map.insert(
- {id,
- transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
- }
- }
- UpdateModuleIdBoundForFreshIds(ir_context, input_id_to_fresh_id_map,
- output_id_to_fresh_id_map);
- // Construct a map that associates each output id with its type id.
- std::map<uint32_t, uint32_t> output_id_to_type_id;
- for (uint32_t output_id : region_output_ids) {
- output_id_to_type_id[output_id] =
- ir_context->get_def_use_mgr()->GetDef(output_id)->type_id();
- }
- // The region will be collapsed to a single block that calls a function
- // containing the outlined region. This block needs to end with whatever
- // the exit block of the region ended with before outlining. We thus clone
- // the terminator of the region's exit block, and the merge instruction for
- // the block if there is one, so that we can append them to the end of the
- // collapsed block later.
- std::unique_ptr<opt::Instruction> cloned_exit_block_terminator =
- std::unique_ptr<opt::Instruction>(
- original_region_exit_block->terminator()->Clone(ir_context));
- std::unique_ptr<opt::Instruction> cloned_exit_block_merge =
- original_region_exit_block->GetMergeInst()
- ? std::unique_ptr<opt::Instruction>(
- original_region_exit_block->GetMergeInst()->Clone(ir_context))
- : nullptr;
- // Make a function prototype for the outlined function, which involves
- // figuring out its required type.
- std::unique_ptr<opt::Function> outlined_function = PrepareFunctionPrototype(
- region_input_ids, region_output_ids, input_id_to_fresh_id_map, ir_context,
- transformation_context);
- // Adapt the region to be outlined so that its input ids are replaced with the
- // ids of the outlined function's input parameters, and so that output ids
- // are similarly remapped.
- RemapInputAndOutputIdsInRegion(
- ir_context, *original_region_exit_block, region_blocks, region_input_ids,
- region_output_ids, input_id_to_fresh_id_map, output_id_to_fresh_id_map);
- // Fill out the body of the outlined function according to the region that is
- // being outlined.
- PopulateOutlinedFunction(
- *original_region_entry_block, *original_region_exit_block, region_blocks,
- region_output_ids, output_id_to_type_id, output_id_to_fresh_id_map,
- ir_context, outlined_function.get());
- // Collapse the region that has been outlined into a function down to a single
- // block that calls said function.
- ShrinkOriginalRegion(
- ir_context, region_blocks, region_input_ids, region_output_ids,
- output_id_to_type_id, outlined_function->type_id(),
- std::move(cloned_exit_block_merge),
- std::move(cloned_exit_block_terminator), original_region_entry_block);
- // Add the outlined function to the module.
- const auto* outlined_function_ptr = outlined_function.get();
- ir_context->module()->AddFunction(std::move(outlined_function));
- // Major surgery has been conducted on the module, so invalidate all analyses.
- ir_context->InvalidateAnalysesExceptFor(
- opt::IRContext::Analysis::kAnalysisNone);
- // If the original function was livesafe, the new function should also be
- // livesafe.
- if (transformation_context->GetFactManager()->FunctionIsLivesafe(
- original_region_entry_block->GetParent()->result_id())) {
- transformation_context->GetFactManager()->AddFactFunctionIsLivesafe(
- message_.new_function_id());
- }
- // Record the fact that all blocks in the outlined region are dead if the
- // first block is dead.
- if (transformation_context->GetFactManager()->BlockIsDead(
- original_region_entry_block->id())) {
- transformation_context->GetFactManager()->AddFactBlockIsDead(
- outlined_function_ptr->entry()->id());
- }
- }
- protobufs::Transformation TransformationOutlineFunction::ToMessage() const {
- protobufs::Transformation result;
- *result.mutable_outline_function() = message_;
- return result;
- }
- std::vector<uint32_t> TransformationOutlineFunction::GetRegionInputIds(
- opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_set,
- opt::BasicBlock* region_exit_block) {
- std::vector<uint32_t> result;
- auto enclosing_function = region_exit_block->GetParent();
- // Consider each parameter of the function containing the region.
- enclosing_function->ForEachParam(
- [ir_context, ®ion_set, &result](opt::Instruction* function_parameter) {
- // Consider every use of the parameter.
- ir_context->get_def_use_mgr()->WhileEachUse(
- function_parameter,
- [ir_context, function_parameter, ®ion_set, &result](
- opt::Instruction* use, uint32_t /*unused*/) {
- // Get the block, if any, in which the parameter is used.
- auto use_block = ir_context->get_instr_block(use);
- // If the use is in a block that lies within the region, the
- // parameter is an input id for the region.
- if (use_block && region_set.count(use_block) != 0) {
- result.push_back(function_parameter->result_id());
- return false;
- }
- return true;
- });
- });
- // Consider all definitions in the function that might turn out to be input
- // ids.
- for (auto& block : *enclosing_function) {
- std::vector<opt::Instruction*> candidate_input_ids_for_block;
- if (region_set.count(&block) == 0) {
- // All instructions in blocks outside the region are candidate's for
- // generating input ids.
- for (auto& inst : block) {
- candidate_input_ids_for_block.push_back(&inst);
- }
- } else {
- // Blocks in the region cannot generate input ids.
- continue;
- }
- // Consider each candidate input id to check whether it is used in the
- // region.
- for (auto& inst : candidate_input_ids_for_block) {
- ir_context->get_def_use_mgr()->WhileEachUse(
- inst,
- [ir_context, &inst, region_exit_block, ®ion_set, &result](
- opt::Instruction* use, uint32_t /*unused*/) -> bool {
- // Find the block in which this id use occurs, recording the id as
- // an input id if the block is outside the region, with some
- // exceptions detailed below.
- auto use_block = ir_context->get_instr_block(use);
- if (!use_block) {
- // There might be no containing block, e.g. if the use is in a
- // decoration.
- return true;
- }
- if (region_set.count(use_block) == 0) {
- // The use is not in the region: this does not make it an input
- // id.
- return true;
- }
- if (use_block == region_exit_block && use->IsBlockTerminator()) {
- // We do not regard uses in the exit block terminator as input
- // ids, as this terminator does not get outlined.
- return true;
- }
- result.push_back(inst->result_id());
- return false;
- });
- }
- }
- return result;
- }
- std::vector<uint32_t> TransformationOutlineFunction::GetRegionOutputIds(
- opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_set,
- opt::BasicBlock* region_exit_block) {
- std::vector<uint32_t> result;
- // Consider each block in the function containing the region.
- for (auto& block : *region_exit_block->GetParent()) {
- if (region_set.count(&block) == 0) {
- // Skip blocks that are not in the region.
- continue;
- }
- // Consider each use of each instruction defined in the block.
- for (auto& inst : block) {
- ir_context->get_def_use_mgr()->WhileEachUse(
- &inst,
- [®ion_set, ir_context, &inst, region_exit_block, &result](
- opt::Instruction* use, uint32_t /*unused*/) -> bool {
- // Find the block in which this id use occurs, recording the id as
- // an output id if the block is outside the region, with some
- // exceptions detailed below.
- auto use_block = ir_context->get_instr_block(use);
- if (!use_block) {
- // There might be no containing block, e.g. if the use is in a
- // decoration.
- return true;
- }
- if (region_set.count(use_block) != 0) {
- // The use is in the region.
- if (use_block != region_exit_block || !use->IsBlockTerminator()) {
- // Furthermore, the use is not in the terminator of the region's
- // exit block.
- return true;
- }
- }
- result.push_back(inst.result_id());
- return false;
- });
- }
- }
- return result;
- }
- std::set<opt::BasicBlock*> TransformationOutlineFunction::GetRegionBlocks(
- opt::IRContext* ir_context, opt::BasicBlock* entry_block,
- opt::BasicBlock* exit_block) {
- auto enclosing_function = entry_block->GetParent();
- auto dominator_analysis =
- ir_context->GetDominatorAnalysis(enclosing_function);
- auto postdominator_analysis =
- ir_context->GetPostDominatorAnalysis(enclosing_function);
- std::set<opt::BasicBlock*> result;
- for (auto& block : *enclosing_function) {
- if (dominator_analysis->Dominates(entry_block, &block) &&
- postdominator_analysis->Dominates(exit_block, &block)) {
- result.insert(&block);
- }
- }
- return result;
- }
- std::unique_ptr<opt::Function>
- TransformationOutlineFunction::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* ir_context,
- TransformationContext* transformation_context) const {
- uint32_t return_type_id = 0;
- uint32_t function_type_id = 0;
- // First, try to find an existing function type that is suitable. This is
- // only possible if the region generates no output ids; if it generates output
- // ids we are going to make a new struct for those, and since that struct does
- // not exist there cannot already be a function type with this struct as its
- // return type.
- if (region_output_ids.empty()) {
- std::vector<uint32_t> return_and_parameter_types;
- opt::analysis::Void void_type;
- return_type_id = ir_context->get_type_mgr()->GetId(&void_type);
- return_and_parameter_types.push_back(return_type_id);
- for (auto id : region_input_ids) {
- return_and_parameter_types.push_back(
- ir_context->get_def_use_mgr()->GetDef(id)->type_id());
- }
- function_type_id =
- fuzzerutil::FindFunctionType(ir_context, return_and_parameter_types);
- }
- // If no existing function type was found, we need to create one.
- if (function_type_id == 0) {
- assert(
- ((return_type_id == 0) == !region_output_ids.empty()) &&
- "We should only have set the return type if there are no output ids.");
- // If the region generates output ids, we need to make a struct with one
- // field per output id.
- if (!region_output_ids.empty()) {
- opt::Instruction::OperandList struct_member_types;
- for (uint32_t output_id : region_output_ids) {
- auto output_id_type =
- ir_context->get_def_use_mgr()->GetDef(output_id)->type_id();
- if (ir_context->get_def_use_mgr()->GetDef(output_id_type)->opcode() ==
- spv::Op::OpTypeVoid) {
- // We cannot add a void field to a struct. We instead use OpUndef to
- // handle void output ids.
- continue;
- }
- struct_member_types.push_back({SPV_OPERAND_TYPE_ID, {output_id_type}});
- }
- // Add a new struct type to the module.
- ir_context->module()->AddType(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpTypeStruct, 0,
- message_.new_function_struct_return_type_id(),
- std::move(struct_member_types)));
- // The return type for the function is the newly-created struct.
- return_type_id = message_.new_function_struct_return_type_id();
- }
- assert(
- return_type_id != 0 &&
- "We should either have a void return type, or have created a struct.");
- // The region's input ids dictate the parameter types to the function.
- opt::Instruction::OperandList function_type_operands;
- function_type_operands.push_back({SPV_OPERAND_TYPE_ID, {return_type_id}});
- for (auto id : region_input_ids) {
- function_type_operands.push_back(
- {SPV_OPERAND_TYPE_ID,
- {ir_context->get_def_use_mgr()->GetDef(id)->type_id()}});
- }
- // Add a new function type to the module, and record that this is the type
- // id for the new function.
- ir_context->module()->AddType(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpTypeFunction, 0, message_.new_function_type_id(),
- function_type_operands));
- function_type_id = message_.new_function_type_id();
- }
- // Create a new function with |message_.new_function_id| as the function id,
- // and the return type and function type prepared above.
- std::unique_ptr<opt::Function> outlined_function =
- MakeUnique<opt::Function>(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpFunction, return_type_id,
- message_.new_function_id(),
- opt::Instruction::OperandList(
- {{spv_operand_type_t ::SPV_OPERAND_TYPE_LITERAL_INTEGER,
- {uint32_t(spv::FunctionControlMask::MaskNone)}},
- {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
- {function_type_id}}})));
- // Add one parameter to the function for each input id, using the fresh ids
- // provided in |input_id_to_fresh_id_map|, or overflow ids if needed.
- for (auto id : region_input_ids) {
- uint32_t fresh_id = input_id_to_fresh_id_map.at(id);
- outlined_function->AddParameter(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpFunctionParameter,
- ir_context->get_def_use_mgr()->GetDef(id)->type_id(), fresh_id,
- opt::Instruction::OperandList()));
- // Analyse the use of the new parameter instruction.
- outlined_function->ForEachParam(
- [fresh_id, ir_context](opt::Instruction* inst) {
- if (inst->result_id() == fresh_id) {
- ir_context->AnalyzeDefUse(inst);
- }
- });
- // If the input id is an irrelevant-valued variable, the same should be true
- // of the corresponding parameter.
- if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
- id)) {
- transformation_context->GetFactManager()
- ->AddFactValueOfPointeeIsIrrelevant(input_id_to_fresh_id_map.at(id));
- }
- }
- return outlined_function;
- }
- void TransformationOutlineFunction::UpdateModuleIdBoundForFreshIds(
- opt::IRContext* ir_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) const {
- // Enlarge the module's id bound as needed to accommodate the various fresh
- // ids associated with the transformation.
- fuzzerutil::UpdateModuleIdBound(
- ir_context, message_.new_function_struct_return_type_id());
- fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_function_type_id());
- fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_function_id());
- fuzzerutil::UpdateModuleIdBound(ir_context,
- message_.new_function_region_entry_block());
- fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_caller_result_id());
- fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_callee_result_id());
- for (auto& entry : input_id_to_fresh_id_map) {
- fuzzerutil::UpdateModuleIdBound(ir_context, entry.second);
- }
- for (auto& entry : output_id_to_fresh_id_map) {
- fuzzerutil::UpdateModuleIdBound(ir_context, entry.second);
- }
- }
- void TransformationOutlineFunction::RemapInputAndOutputIdsInRegion(
- opt::IRContext* ir_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) const {
- // Change all uses of input ids inside the region to the corresponding fresh
- // ids that will ultimately be parameters of the outlined function.
- // This is done by considering each region input id in turn.
- for (uint32_t id : region_input_ids) {
- // We then consider each use of the input id.
- ir_context->get_def_use_mgr()->ForEachUse(
- id, [ir_context, id, &input_id_to_fresh_id_map, region_blocks](
- opt::Instruction* use, uint32_t operand_index) {
- // Find the block in which this use of the input id occurs.
- opt::BasicBlock* use_block = ir_context->get_instr_block(use);
- // We want to rewrite the use id if its block occurs in the outlined
- // region.
- if (region_blocks.count(use_block) != 0) {
- // Rewrite this use of the input id.
- use->SetOperand(operand_index, {input_id_to_fresh_id_map.at(id)});
- }
- });
- }
- // Change each definition of a region output id to define the corresponding
- // fresh ids that will store intermediate value for the output ids. Also
- // change all uses of the output id located in the outlined region.
- // This is done by considering each region output id in turn.
- for (uint32_t id : region_output_ids) {
- // First consider each use of the output id and update the relevant uses.
- ir_context->get_def_use_mgr()->ForEachUse(
- id, [ir_context, &original_region_exit_block, id,
- &output_id_to_fresh_id_map,
- region_blocks](opt::Instruction* use, uint32_t operand_index) {
- // Find the block in which this use of the output id occurs.
- auto use_block = ir_context->get_instr_block(use);
- // We want to rewrite the use id if its block occurs in the outlined
- // region, with one exception: the terminator of the exit block of
- // the region is going to remain in the original function, so if the
- // use appears in such a terminator instruction we leave it alone.
- if (
- // The block is in the region ...
- region_blocks.count(use_block) != 0 &&
- // ... and the use is not in the terminator instruction of the
- // region's exit block.
- !(use_block == &original_region_exit_block &&
- use->IsBlockTerminator())) {
- // Rewrite this use of the output id.
- use->SetOperand(operand_index, {output_id_to_fresh_id_map.at(id)});
- }
- });
- // Now change the instruction that defines the output id so that it instead
- // defines the corresponding fresh id. We do this after changing all the
- // uses so that the definition of the original id is still registered when
- // we analyse its uses.
- ir_context->get_def_use_mgr()->GetDef(id)->SetResultId(
- output_id_to_fresh_id_map.at(id));
- }
- }
- void TransformationOutlineFunction::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_type_id,
- const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map,
- opt::IRContext* ir_context, opt::Function* outlined_function) const {
- // When we create the exit block for the outlined region, we use this pointer
- // to track of it so that we can manipulate it later.
- opt::BasicBlock* outlined_region_exit_block = nullptr;
- // The region entry block in the new function is identical to the entry block
- // of the region being outlined, except that it has
- // |message_.new_function_region_entry_block| as its id.
- std::unique_ptr<opt::BasicBlock> outlined_region_entry_block =
- MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpLabel, 0,
- message_.new_function_region_entry_block(),
- opt::Instruction::OperandList()));
- if (&original_region_entry_block == &original_region_exit_block) {
- outlined_region_exit_block = outlined_region_entry_block.get();
- }
- for (auto& inst : original_region_entry_block) {
- outlined_region_entry_block->AddInstruction(
- std::unique_ptr<opt::Instruction>(inst.Clone(ir_context)));
- }
- outlined_function->AddBasicBlock(std::move(outlined_region_entry_block));
- // We now go through the single-entry single-exit region defined by the entry
- // and exit blocks, adding clones of all blocks to the new function.
- // Consider every block in the enclosing function.
- auto enclosing_function = original_region_entry_block.GetParent();
- for (auto block_it = enclosing_function->begin();
- block_it != enclosing_function->end();) {
- // Skip the region's entry block - we already dealt with it above.
- if (region_blocks.count(&*block_it) == 0 ||
- &*block_it == &original_region_entry_block) {
- ++block_it;
- continue;
- }
- // Clone the block so that it can be added to the new function.
- auto cloned_block =
- std::unique_ptr<opt::BasicBlock>(block_it->Clone(ir_context));
- // If this is the region's exit block, then the cloned block is the outlined
- // region's exit block.
- if (&*block_it == &original_region_exit_block) {
- assert(outlined_region_exit_block == nullptr &&
- "We should not yet have encountered the exit block.");
- outlined_region_exit_block = cloned_block.get();
- }
- // Redirect any OpPhi operands whose predecessors are the original region
- // entry block to become the new function entry block.
- cloned_block->ForEachPhiInst([this](opt::Instruction* phi_inst) {
- for (uint32_t predecessor_index = 1;
- predecessor_index < phi_inst->NumInOperands();
- predecessor_index += 2) {
- if (phi_inst->GetSingleWordInOperand(predecessor_index) ==
- message_.entry_block()) {
- phi_inst->SetInOperand(predecessor_index,
- {message_.new_function_region_entry_block()});
- }
- }
- });
- outlined_function->AddBasicBlock(std::move(cloned_block));
- block_it = block_it.Erase();
- }
- assert(outlined_region_exit_block != nullptr &&
- "We should have encountered the region's exit block when iterating "
- "through the function");
- // We now need to adapt the exit block for the region - in the new function -
- // so that it ends with a return.
- // We first eliminate the merge instruction (if any) and the terminator for
- // the cloned exit block.
- for (auto inst_it = outlined_region_exit_block->begin();
- inst_it != outlined_region_exit_block->end();) {
- if (inst_it->opcode() == spv::Op::OpLoopMerge ||
- inst_it->opcode() == spv::Op::OpSelectionMerge) {
- inst_it = inst_it.Erase();
- } else if (inst_it->IsBlockTerminator()) {
- inst_it = inst_it.Erase();
- } else {
- ++inst_it;
- }
- }
- // We now add either OpReturn or OpReturnValue as the cloned exit block's
- // terminator.
- if (region_output_ids.empty()) {
- // The case where there are no region output ids is simple: we just add
- // OpReturn.
- outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpReturn, 0, 0, opt::Instruction::OperandList()));
- } else {
- // In the case where there are output ids, we add an OpCompositeConstruct
- // instruction to pack all the non-void output values into a struct, and
- // then an OpReturnValue instruction to return this struct.
- opt::Instruction::OperandList struct_member_operands;
- for (uint32_t id : region_output_ids) {
- if (ir_context->get_def_use_mgr()
- ->GetDef(output_id_to_type_id.at(id))
- ->opcode() != spv::Op::OpTypeVoid) {
- struct_member_operands.push_back(
- {SPV_OPERAND_TYPE_ID, {output_id_to_fresh_id_map.at(id)}});
- }
- }
- outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpCompositeConstruct,
- message_.new_function_struct_return_type_id(),
- message_.new_callee_result_id(), struct_member_operands));
- outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpReturnValue, 0, 0,
- opt::Instruction::OperandList(
- {{SPV_OPERAND_TYPE_ID, {message_.new_callee_result_id()}}})));
- }
- outlined_function->SetFunctionEnd(
- MakeUnique<opt::Instruction>(ir_context, spv::Op::OpFunctionEnd, 0, 0,
- opt::Instruction::OperandList()));
- }
- void TransformationOutlineFunction::ShrinkOriginalRegion(
- opt::IRContext* ir_context, 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>& 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) const {
- // Erase all blocks from the original function that are in the outlined
- // region, except for the region's entry block.
- //
- // In the process, identify all references to the exit block of the region,
- // as merge blocks, continue targets, or OpPhi predecessors, and rewrite them
- // to refer to the region entry block (the single block to which we are
- // shrinking the region).
- auto enclosing_function = original_region_entry_block->GetParent();
- for (auto block_it = enclosing_function->begin();
- block_it != enclosing_function->end();) {
- if (&*block_it == original_region_entry_block) {
- ++block_it;
- } else if (region_blocks.count(&*block_it) == 0) {
- // The block is not in the region. Check whether it has the last block
- // of the region as an OpPhi predecessor, and if so change the
- // predecessor to be the first block of the region (i.e. the block
- // containing the call to what was outlined).
- assert(block_it->MergeBlockIdIfAny() != message_.exit_block() &&
- "Outlined region must not end with a merge block");
- assert(block_it->ContinueBlockIdIfAny() != message_.exit_block() &&
- "Outlined region must not end with a continue target");
- block_it->ForEachPhiInst([this](opt::Instruction* phi_inst) {
- for (uint32_t predecessor_index = 1;
- predecessor_index < phi_inst->NumInOperands();
- predecessor_index += 2) {
- if (phi_inst->GetSingleWordInOperand(predecessor_index) ==
- message_.exit_block()) {
- phi_inst->SetInOperand(predecessor_index, {message_.entry_block()});
- }
- }
- });
- ++block_it;
- } else {
- // The block is in the region and is not the region's entry block: kill
- // it.
- block_it = block_it.Erase();
- }
- }
- // Now erase all instructions from the region's entry block, as they have
- // been outlined.
- for (auto inst_it = original_region_entry_block->begin();
- inst_it != original_region_entry_block->end();) {
- inst_it = inst_it.Erase();
- }
- // Now we add a call to the outlined function to the region's entry block.
- opt::Instruction::OperandList function_call_operands;
- function_call_operands.push_back(
- {SPV_OPERAND_TYPE_ID, {message_.new_function_id()}});
- // The function parameters are the region input ids.
- for (auto input_id : region_input_ids) {
- function_call_operands.push_back({SPV_OPERAND_TYPE_ID, {input_id}});
- }
- original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpFunctionCall, return_type_id,
- message_.new_caller_result_id(), function_call_operands));
- // If there are output ids, the function call will return a struct. For each
- // output id, we add an extract operation to pull the appropriate struct
- // member out into an output id. The exception is for output ids with void
- // type. There are no struct entries for these, so we use an OpUndef of void
- // type instead.
- uint32_t struct_member_index = 0;
- for (uint32_t output_id : region_output_ids) {
- uint32_t output_type_id = output_id_to_type_id.at(output_id);
- if (ir_context->get_def_use_mgr()->GetDef(output_type_id)->opcode() ==
- spv::Op::OpTypeVoid) {
- original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpUndef, output_type_id, output_id,
- opt::Instruction::OperandList()));
- // struct_member_index is not incremented since there was no struct member
- // associated with this void-typed output id.
- } else {
- original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpCompositeExtract, output_type_id, output_id,
- opt::Instruction::OperandList(
- {{SPV_OPERAND_TYPE_ID, {message_.new_caller_result_id()}},
- {SPV_OPERAND_TYPE_LITERAL_INTEGER, {struct_member_index}}})));
- struct_member_index++;
- }
- }
- // Finally, we terminate the block with the merge instruction (if any) that
- // used to belong to the region's exit block, and the terminator that used
- // to belong to the region's exit block.
- if (cloned_exit_block_merge != nullptr) {
- original_region_entry_block->AddInstruction(
- std::move(cloned_exit_block_merge));
- }
- original_region_entry_block->AddInstruction(
- std::move(cloned_exit_block_terminator));
- }
- std::unordered_set<uint32_t> TransformationOutlineFunction::GetFreshIds()
- const {
- std::unordered_set<uint32_t> result = {
- message_.new_function_struct_return_type_id(),
- message_.new_function_type_id(),
- message_.new_function_id(),
- message_.new_function_region_entry_block(),
- message_.new_caller_result_id(),
- message_.new_callee_result_id()};
- for (auto& pair : message_.input_id_to_fresh_id()) {
- result.insert(pair.second());
- }
- for (auto& pair : message_.output_id_to_fresh_id()) {
- result.insert(pair.second());
- }
- return result;
- }
- } // namespace fuzz
- } // namespace spvtools
|