mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-22 19:50:05 +00:00
spirv-fuzz: function outlining fuzzer pass (#3078)
A new transformation and associated fuzzer pass in spirv-fuzz that selects single-entry single-exit control flow graph regions and for each selected region outlines the region into a new function and replaces the original region with a call to this function.
This commit is contained in:
parent
983b5b4fcc
commit
0a2b38d082
@ -48,6 +48,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
fuzzer_pass_construct_composites.h
|
||||
fuzzer_pass_copy_objects.h
|
||||
fuzzer_pass_obfuscate_constants.h
|
||||
fuzzer_pass_outline_functions.h
|
||||
fuzzer_pass_permute_blocks.h
|
||||
fuzzer_pass_split_blocks.h
|
||||
fuzzer_util.h
|
||||
@ -72,6 +73,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
transformation_composite_extract.h
|
||||
transformation_copy_object.h
|
||||
transformation_move_block_down.h
|
||||
transformation_outline_function.h
|
||||
transformation_replace_boolean_constant_with_constant_binary.h
|
||||
transformation_replace_constant_with_uniform.h
|
||||
transformation_replace_id_with_synonym.h
|
||||
@ -102,6 +104,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
fuzzer_pass_construct_composites.cpp
|
||||
fuzzer_pass_copy_objects.cpp
|
||||
fuzzer_pass_obfuscate_constants.cpp
|
||||
fuzzer_pass_outline_functions.cpp
|
||||
fuzzer_pass_permute_blocks.cpp
|
||||
fuzzer_pass_split_blocks.cpp
|
||||
fuzzer_util.cpp
|
||||
@ -125,6 +128,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
transformation_composite_extract.cpp
|
||||
transformation_copy_object.cpp
|
||||
transformation_move_block_down.cpp
|
||||
transformation_outline_function.cpp
|
||||
transformation_replace_boolean_constant_with_constant_binary.cpp
|
||||
transformation_replace_constant_with_uniform.cpp
|
||||
transformation_replace_id_with_synonym.cpp
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "source/fuzz/fuzzer_pass_construct_composites.h"
|
||||
#include "source/fuzz/fuzzer_pass_copy_objects.h"
|
||||
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
|
||||
#include "source/fuzz/fuzzer_pass_outline_functions.h"
|
||||
#include "source/fuzz/fuzzer_pass_permute_blocks.h"
|
||||
#include "source/fuzz/fuzzer_pass_split_blocks.h"
|
||||
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
|
||||
@ -185,6 +186,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
|
||||
MaybeAddPass<FuzzerPassObfuscateConstants>(&passes, ir_context.get(),
|
||||
&fact_manager, &fuzzer_context,
|
||||
transformation_sequence_out);
|
||||
MaybeAddPass<FuzzerPassOutlineFunctions>(&passes, ir_context.get(),
|
||||
&fact_manager, &fuzzer_context,
|
||||
transformation_sequence_out);
|
||||
MaybeAddPass<FuzzerPassPermuteBlocks>(&passes, ir_context.get(),
|
||||
&fact_manager, &fuzzer_context,
|
||||
transformation_sequence_out);
|
||||
|
@ -38,6 +38,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfConstructingComposite = {20, 50};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfOutliningFunction = {10, 90};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
|
||||
|
||||
@ -85,6 +86,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
|
||||
ChooseBetweenMinAndMax(kChanceOfMovingBlockDown);
|
||||
chance_of_obfuscating_constant_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant);
|
||||
chance_of_outlining_function_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfOutliningFunction);
|
||||
chance_of_replacing_id_with_synonym_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
|
||||
chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
|
||||
|
@ -85,6 +85,9 @@ class FuzzerContext {
|
||||
uint32_t GetChanceOfObfuscatingConstant() {
|
||||
return chance_of_obfuscating_constant_;
|
||||
}
|
||||
uint32_t GetChanceOfOutliningFunction() {
|
||||
return chance_of_outlining_function_;
|
||||
}
|
||||
uint32_t GetChanceOfReplacingIdWithSynonym() {
|
||||
return chance_of_replacing_id_with_synonym_;
|
||||
}
|
||||
@ -121,6 +124,7 @@ class FuzzerContext {
|
||||
uint32_t chance_of_copying_object_;
|
||||
uint32_t chance_of_moving_block_down_;
|
||||
uint32_t chance_of_obfuscating_constant_;
|
||||
uint32_t chance_of_outlining_function_;
|
||||
uint32_t chance_of_replacing_id_with_synonym_;
|
||||
uint32_t chance_of_splitting_block_;
|
||||
|
||||
|
@ -148,7 +148,6 @@ void FuzzerPassConstructComposites::Apply() {
|
||||
transformation.Apply(GetIRContext(), GetFactManager());
|
||||
*GetTransformations()->add_transformation() =
|
||||
transformation.ToMessage();
|
||||
// Indicate that one instruction was added.
|
||||
});
|
||||
}
|
||||
|
||||
|
99
source/fuzz/fuzzer_pass_outline_functions.cpp
Normal file
99
source/fuzz/fuzzer_pass_outline_functions.cpp
Normal file
@ -0,0 +1,99 @@
|
||||
// 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/fuzzer_pass_outline_functions.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
#include "source/fuzz/transformation_outline_function.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
FuzzerPassOutlineFunctions::FuzzerPassOutlineFunctions(
|
||||
opt::IRContext* ir_context, FactManager* fact_manager,
|
||||
FuzzerContext* fuzzer_context,
|
||||
protobufs::TransformationSequence* transformations)
|
||||
: FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
|
||||
|
||||
FuzzerPassOutlineFunctions::~FuzzerPassOutlineFunctions() = default;
|
||||
|
||||
void FuzzerPassOutlineFunctions::Apply() {
|
||||
std::vector<opt::Function*> original_functions;
|
||||
for (auto& function : *GetIRContext()->module()) {
|
||||
original_functions.push_back(&function);
|
||||
}
|
||||
for (auto& function : original_functions) {
|
||||
if (!GetFuzzerContext()->ChoosePercentage(
|
||||
GetFuzzerContext()->GetChanceOfOutliningFunction())) {
|
||||
continue;
|
||||
}
|
||||
std::vector<opt::BasicBlock*> blocks;
|
||||
for (auto& block : *function) {
|
||||
blocks.push_back(&block);
|
||||
}
|
||||
auto entry_block = blocks[GetFuzzerContext()->RandomIndex(blocks)];
|
||||
auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(function);
|
||||
auto postdominator_analysis =
|
||||
GetIRContext()->GetPostDominatorAnalysis(function);
|
||||
std::vector<opt::BasicBlock*> candidate_exit_blocks;
|
||||
for (auto postdominates_entry_block = entry_block;
|
||||
postdominates_entry_block != nullptr;
|
||||
postdominates_entry_block = postdominator_analysis->ImmediateDominator(
|
||||
postdominates_entry_block)) {
|
||||
if (dominator_analysis->Dominates(entry_block,
|
||||
postdominates_entry_block)) {
|
||||
candidate_exit_blocks.push_back(postdominates_entry_block);
|
||||
}
|
||||
}
|
||||
if (candidate_exit_blocks.empty()) {
|
||||
continue;
|
||||
}
|
||||
auto exit_block = candidate_exit_blocks[GetFuzzerContext()->RandomIndex(
|
||||
candidate_exit_blocks)];
|
||||
|
||||
auto region_blocks = TransformationOutlineFunction::GetRegionBlocks(
|
||||
GetIRContext(), entry_block, exit_block);
|
||||
std::map<uint32_t, uint32_t> input_id_to_fresh_id;
|
||||
for (auto id : TransformationOutlineFunction::GetRegionInputIds(
|
||||
GetIRContext(), region_blocks, exit_block)) {
|
||||
input_id_to_fresh_id[id] = GetFuzzerContext()->GetFreshId();
|
||||
}
|
||||
std::map<uint32_t, uint32_t> output_id_to_fresh_id;
|
||||
for (auto id : TransformationOutlineFunction::GetRegionOutputIds(
|
||||
GetIRContext(), region_blocks, exit_block)) {
|
||||
output_id_to_fresh_id[id] = GetFuzzerContext()->GetFreshId();
|
||||
}
|
||||
TransformationOutlineFunction transformation(
|
||||
entry_block->id(), exit_block->id(),
|
||||
/*new_function_struct_return_type_id*/
|
||||
GetFuzzerContext()->GetFreshId(),
|
||||
/*new_function_type_id*/ GetFuzzerContext()->GetFreshId(),
|
||||
/*new_function_id*/ GetFuzzerContext()->GetFreshId(),
|
||||
/*new_function_region_entry_block*/
|
||||
GetFuzzerContext()->GetFreshId(),
|
||||
/*new_caller_result_id*/ GetFuzzerContext()->GetFreshId(),
|
||||
/*new_callee_result_id*/ GetFuzzerContext()->GetFreshId(),
|
||||
/*input_id_to_fresh_id*/ std::move(input_id_to_fresh_id),
|
||||
/*output_id_to_fresh_id*/ std::move(output_id_to_fresh_id));
|
||||
if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) {
|
||||
transformation.Apply(GetIRContext(), GetFactManager());
|
||||
*GetTransformations()->add_transformation() = transformation.ToMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
40
source/fuzz/fuzzer_pass_outline_functions.h
Normal file
40
source/fuzz/fuzzer_pass_outline_functions.h
Normal file
@ -0,0 +1,40 @@
|
||||
// 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.
|
||||
|
||||
#ifndef SOURCE_FUZZ_FUZZER_PASS_OUTLINE_FUNCTIONS_H_
|
||||
#define SOURCE_FUZZ_FUZZER_PASS_OUTLINE_FUNCTIONS_H_
|
||||
|
||||
#include "source/fuzz/fuzzer_pass.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
// A fuzzer pass for outlining single-entry single-exit regions of a control
|
||||
// flow graph into their own functions.
|
||||
class FuzzerPassOutlineFunctions : public FuzzerPass {
|
||||
public:
|
||||
FuzzerPassOutlineFunctions(
|
||||
opt::IRContext* ir_context, FactManager* fact_manager,
|
||||
FuzzerContext* fuzzer_context,
|
||||
protobufs::TransformationSequence* transformations);
|
||||
|
||||
~FuzzerPassOutlineFunctions();
|
||||
|
||||
void Apply() override;
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_FUZZ_FUZZER_PASS_OUTLINE_FUNCTIONS_H_
|
@ -21,6 +21,16 @@ syntax = "proto3";
|
||||
|
||||
package spvtools.fuzz.protobufs;
|
||||
|
||||
message UInt32Pair {
|
||||
|
||||
// A pair of uint32s; useful for defining mappings.
|
||||
|
||||
uint32 first = 1;
|
||||
|
||||
uint32 second = 2;
|
||||
|
||||
}
|
||||
|
||||
message InstructionDescriptor {
|
||||
|
||||
// Describes an instruction in some block of a function with respect to a
|
||||
@ -190,6 +200,7 @@ message Transformation {
|
||||
TransformationSetMemoryOperandsMask set_memory_operands_mask = 20;
|
||||
TransformationCompositeExtract composite_extract = 21;
|
||||
TransformationVectorShuffle vector_shuffle = 22;
|
||||
TransformationOutlineFunction outline_function = 23;
|
||||
// Add additional option using the next available number.
|
||||
}
|
||||
}
|
||||
@ -389,6 +400,53 @@ message TransformationMoveBlockDown {
|
||||
uint32 block_id = 1;
|
||||
}
|
||||
|
||||
message TransformationOutlineFunction {
|
||||
|
||||
// A transformation that outlines a single-entry single-exit region of a
|
||||
// control flow graph into a separate function, and replaces the region with
|
||||
// a call to that function.
|
||||
|
||||
// Id of the entry block of the single-entry single-exit region to be outlined
|
||||
uint32 entry_block = 1;
|
||||
|
||||
// Id of the exit block of the single-entry single-exit region to be outlined
|
||||
uint32 exit_block = 2;
|
||||
|
||||
// Id of a struct that will store the return values of the new function
|
||||
uint32 new_function_struct_return_type_id = 3;
|
||||
|
||||
// A fresh id for the type of the outlined function
|
||||
uint32 new_function_type_id = 4;
|
||||
|
||||
// A fresh id for the outlined function itself
|
||||
uint32 new_function_id = 5;
|
||||
|
||||
// A fresh id to represent the block in the outlined function that represents
|
||||
// the first block of the outlined region.
|
||||
uint32 new_function_region_entry_block = 6;
|
||||
|
||||
// A fresh id for the result of the OpFunctionCall instruction that will call
|
||||
// the outlined function
|
||||
uint32 new_caller_result_id = 7;
|
||||
|
||||
// A fresh id to capture the return value of the outlined function - the
|
||||
// argument to OpReturn
|
||||
uint32 new_callee_result_id = 8;
|
||||
|
||||
// Ids defined outside the region and used inside the region will become
|
||||
// parameters to the outlined function. This is a mapping from used ids to
|
||||
// fresh parameter ids.
|
||||
repeated UInt32Pair input_id_to_fresh_id = 9;
|
||||
|
||||
// Ids defined inside the region and used outside the region will become
|
||||
// fresh ids defined by the outlined function, which get copied into the
|
||||
// function's struct return value and then copied into their destination ids
|
||||
// by the caller. This is a mapping from original ids to corresponding fresh
|
||||
// ids.
|
||||
repeated UInt32Pair output_id_to_fresh_id = 10;
|
||||
|
||||
}
|
||||
|
||||
message TransformationReplaceBooleanConstantWithConstantBinary {
|
||||
|
||||
// A transformation to capture replacing a use of a boolean constant with
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "source/fuzz/transformation_composite_extract.h"
|
||||
#include "source/fuzz/transformation_copy_object.h"
|
||||
#include "source/fuzz/transformation_move_block_down.h"
|
||||
#include "source/fuzz/transformation_outline_function.h"
|
||||
#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
|
||||
#include "source/fuzz/transformation_replace_constant_with_uniform.h"
|
||||
#include "source/fuzz/transformation_replace_id_with_synonym.h"
|
||||
@ -83,6 +84,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
|
||||
return MakeUnique<TransformationCopyObject>(message.copy_object());
|
||||
case protobufs::Transformation::TransformationCase::kMoveBlockDown:
|
||||
return MakeUnique<TransformationMoveBlockDown>(message.move_block_down());
|
||||
case protobufs::Transformation::TransformationCase::kOutlineFunction:
|
||||
return MakeUnique<TransformationOutlineFunction>(
|
||||
message.outline_function());
|
||||
case protobufs::Transformation::TransformationCase::
|
||||
kReplaceBooleanConstantWithConstantBinary:
|
||||
return MakeUnique<TransformationReplaceBooleanConstantWithConstantBinary>(
|
||||
|
931
source/fuzz/transformation_outline_function.cpp
Normal file
931
source/fuzz/transformation_outline_function.cpp
Normal file
@ -0,0 +1,931 @@
|
||||
// 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 {
|
||||
|
||||
namespace {
|
||||
|
||||
std::map<uint32_t, uint32_t> PairSequenceToMap(
|
||||
const google::protobuf::RepeatedPtrField<protobufs::UInt32Pair>&
|
||||
pair_sequence) {
|
||||
std::map<uint32_t, uint32_t> result;
|
||||
for (auto& pair : pair_sequence) {
|
||||
result[pair.first()] = pair.second();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TransformationOutlineFunction::TransformationOutlineFunction(
|
||||
const spvtools::fuzz::protobufs::TransformationOutlineFunction& message)
|
||||
: message_(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,
|
||||
std::map<uint32_t, uint32_t>&& input_id_to_fresh_id,
|
||||
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);
|
||||
for (auto& entry : input_id_to_fresh_id) {
|
||||
protobufs::UInt32Pair pair;
|
||||
pair.set_first(entry.first);
|
||||
pair.set_second(entry.second);
|
||||
*message_.add_input_id_to_fresh_id() = pair;
|
||||
}
|
||||
for (auto& entry : output_id_to_fresh_id) {
|
||||
protobufs::UInt32Pair pair;
|
||||
pair.set_first(entry.first);
|
||||
pair.set_second(entry.second);
|
||||
*message_.add_output_id_to_fresh_id() = pair;
|
||||
}
|
||||
}
|
||||
|
||||
bool TransformationOutlineFunction::IsApplicable(
|
||||
opt::IRContext* context,
|
||||
const spvtools::fuzz::FactManager& /*unused*/) 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(), context,
|
||||
&ids_used_by_this_transformation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckIdIsFreshAndNotUsedByThisTransformation(
|
||||
message_.new_function_type_id(), context,
|
||||
&ids_used_by_this_transformation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckIdIsFreshAndNotUsedByThisTransformation(
|
||||
message_.new_function_id(), context,
|
||||
&ids_used_by_this_transformation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckIdIsFreshAndNotUsedByThisTransformation(
|
||||
message_.new_function_region_entry_block(), context,
|
||||
&ids_used_by_this_transformation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckIdIsFreshAndNotUsedByThisTransformation(
|
||||
message_.new_caller_result_id(), context,
|
||||
&ids_used_by_this_transformation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckIdIsFreshAndNotUsedByThisTransformation(
|
||||
message_.new_callee_result_id(), context,
|
||||
&ids_used_by_this_transformation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& pair : message_.input_id_to_fresh_id()) {
|
||||
if (!CheckIdIsFreshAndNotUsedByThisTransformation(
|
||||
pair.second(), context, &ids_used_by_this_transformation)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& pair : message_.output_id_to_fresh_id()) {
|
||||
if (!CheckIdIsFreshAndNotUsedByThisTransformation(
|
||||
pair.second(), 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 = context->get_def_use_mgr()->GetDef(block_id);
|
||||
if (!block_label || block_label->opcode() != SpvOpLabel) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto entry_block = context->cfg()->block(message_.entry_block());
|
||||
auto exit_block = 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() == SpvOpVariable) {
|
||||
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.
|
||||
bool exit_block_is_merge_or_continue = false;
|
||||
context->get_def_use_mgr()->WhileEachUse(
|
||||
exit_block->id(),
|
||||
[&exit_block_is_merge_or_continue](
|
||||
const opt::Instruction* use_instruction,
|
||||
uint32_t /*unused*/) -> bool {
|
||||
switch (use_instruction->opcode()) {
|
||||
case SpvOpLoopMerge:
|
||||
case SpvOpSelectionMerge:
|
||||
exit_block_is_merge_or_continue = true;
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (exit_block_is_merge_or_continue) {
|
||||
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() == SpvOpPhi) {
|
||||
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 =
|
||||
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 =
|
||||
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(
|
||||
context, entry_block = context->cfg()->block(message_.entry_block()),
|
||||
exit_block = 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 (&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 structured control flow construct - the block containing the
|
||||
// call to the outlined function will end up heading this construct if
|
||||
// outlining takes place.
|
||||
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, context,
|
||||
®ion_set](uint32_t successor) -> bool {
|
||||
if (region_set.count(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 = 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 =
|
||||
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 -- there needs to be a corresponding fresh id to be
|
||||
// used as a function parameter.
|
||||
std::map<uint32_t, uint32_t> input_id_to_fresh_id_map =
|
||||
PairSequenceToMap(message_.input_id_to_fresh_id());
|
||||
for (auto id : GetRegionInputIds(context, region_set, exit_block)) {
|
||||
if (input_id_to_fresh_id_map.count(id) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For each region output id -- i.e. every id defined inside the region but
|
||||
// used outside the region -- there needs to be a corresponding fresh id that
|
||||
// can hold the value for this id computed in the outlined function.
|
||||
std::map<uint32_t, uint32_t> output_id_to_fresh_id_map =
|
||||
PairSequenceToMap(message_.output_id_to_fresh_id());
|
||||
for (auto id : GetRegionOutputIds(context, region_set, exit_block)) {
|
||||
if (output_id_to_fresh_id_map.count(id) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TransformationOutlineFunction::Apply(
|
||||
opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
|
||||
// The entry block for the region before outlining.
|
||||
auto original_region_entry_block =
|
||||
context->cfg()->block(message_.entry_block());
|
||||
|
||||
// The exit block for the region before outlining.
|
||||
auto original_region_exit_block =
|
||||
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(
|
||||
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(context, region_blocks, original_region_exit_block);
|
||||
std::vector<uint32_t> region_output_ids =
|
||||
GetRegionOutputIds(context, region_blocks, original_region_exit_block);
|
||||
|
||||
// Maps from input and output ids to fresh ids.
|
||||
std::map<uint32_t, uint32_t> input_id_to_fresh_id_map =
|
||||
PairSequenceToMap(message_.input_id_to_fresh_id());
|
||||
std::map<uint32_t, uint32_t> output_id_to_fresh_id_map =
|
||||
PairSequenceToMap(message_.output_id_to_fresh_id());
|
||||
|
||||
UpdateModuleIdBoundForFreshIds(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] =
|
||||
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(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(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(
|
||||
context, region_input_ids, region_output_ids, input_id_to_fresh_id_map);
|
||||
|
||||
// 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(
|
||||
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(context, *original_region_entry_block,
|
||||
*original_region_exit_block, region_blocks,
|
||||
region_output_ids, output_id_to_fresh_id_map,
|
||||
outlined_function.get());
|
||||
|
||||
// Collapse the region that has been outlined into a function down to a single
|
||||
// block that calls said function.
|
||||
ShrinkOriginalRegion(
|
||||
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.
|
||||
context->module()->AddFunction(std::move(outlined_function));
|
||||
|
||||
// Major surgery has been conducted on the module, so invalidate all analyses.
|
||||
context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
|
||||
}
|
||||
|
||||
protobufs::Transformation TransformationOutlineFunction::ToMessage() const {
|
||||
protobufs::Transformation result;
|
||||
*result.mutable_outline_function() = message_;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TransformationOutlineFunction::
|
||||
CheckIdIsFreshAndNotUsedByThisTransformation(
|
||||
uint32_t id, opt::IRContext* context,
|
||||
std::set<uint32_t>* ids_used_by_this_transformation) const {
|
||||
if (!fuzzerutil::IsFreshId(context, id)) {
|
||||
return false;
|
||||
}
|
||||
if (ids_used_by_this_transformation->count(id) != 0) {
|
||||
return false;
|
||||
}
|
||||
ids_used_by_this_transformation->insert(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> TransformationOutlineFunction::GetRegionInputIds(
|
||||
opt::IRContext* 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([context, ®ion_set, &result](
|
||||
opt::Instruction* function_parameter) {
|
||||
// Consider every use of the parameter.
|
||||
context->get_def_use_mgr()->WhileEachUse(
|
||||
function_parameter, [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 = 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) {
|
||||
context->get_def_use_mgr()->WhileEachUse(
|
||||
inst,
|
||||
[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 = 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* 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) {
|
||||
context->get_def_use_mgr()->WhileEachUse(
|
||||
&inst,
|
||||
[®ion_set, 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 = 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* context, opt::BasicBlock* entry_block,
|
||||
opt::BasicBlock* exit_block) {
|
||||
auto enclosing_function = entry_block->GetParent();
|
||||
auto dominator_analysis = context->GetDominatorAnalysis(enclosing_function);
|
||||
auto postdominator_analysis =
|
||||
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(
|
||||
opt::IRContext* context, 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 {
|
||||
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()) {
|
||||
opt::analysis::Void void_type;
|
||||
return_type_id = context->get_type_mgr()->GetId(&void_type);
|
||||
std::vector<const opt::analysis::Type*> argument_types;
|
||||
for (auto id : region_input_ids) {
|
||||
argument_types.push_back(context->get_type_mgr()->GetType(
|
||||
context->get_def_use_mgr()->GetDef(id)->type_id()));
|
||||
}
|
||||
opt::analysis::Function function_type(&void_type, argument_types);
|
||||
function_type_id = context->get_type_mgr()->GetId(&function_type);
|
||||
}
|
||||
|
||||
// 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 =
|
||||
context->get_def_use_mgr()->GetDef(output_id)->type_id();
|
||||
struct_member_types.push_back({SPV_OPERAND_TYPE_ID, {output_id_type}});
|
||||
}
|
||||
// Add a new struct type to the module.
|
||||
context->module()->AddType(MakeUnique<opt::Instruction>(
|
||||
context, SpvOpTypeStruct, 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,
|
||||
{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.
|
||||
context->module()->AddType(MakeUnique<opt::Instruction>(
|
||||
context, SpvOpTypeFunction, 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>(
|
||||
context, SpvOpFunction, return_type_id, message_.new_function_id(),
|
||||
opt::Instruction::OperandList(
|
||||
{{spv_operand_type_t ::SPV_OPERAND_TYPE_LITERAL_INTEGER,
|
||||
{SpvFunctionControlMaskNone}},
|
||||
{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|.
|
||||
for (auto id : region_input_ids) {
|
||||
outlined_function->AddParameter(MakeUnique<opt::Instruction>(
|
||||
context, SpvOpFunctionParameter,
|
||||
context->get_def_use_mgr()->GetDef(id)->type_id(),
|
||||
input_id_to_fresh_id_map.at(id), opt::Instruction::OperandList()));
|
||||
}
|
||||
|
||||
return outlined_function;
|
||||
}
|
||||
|
||||
void TransformationOutlineFunction::UpdateModuleIdBoundForFreshIds(
|
||||
opt::IRContext* context,
|
||||
const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
|
||||
const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) const {
|
||||
// Enlarge the module's id bound as needed to accommodate the various fresh
|
||||
// ids associated with the transformation.
|
||||
fuzzerutil::UpdateModuleIdBound(
|
||||
context, message_.new_function_struct_return_type_id());
|
||||
fuzzerutil::UpdateModuleIdBound(context, message_.new_function_type_id());
|
||||
fuzzerutil::UpdateModuleIdBound(context, message_.new_function_id());
|
||||
fuzzerutil::UpdateModuleIdBound(context,
|
||||
message_.new_function_region_entry_block());
|
||||
fuzzerutil::UpdateModuleIdBound(context, message_.new_caller_result_id());
|
||||
fuzzerutil::UpdateModuleIdBound(context, message_.new_callee_result_id());
|
||||
|
||||
for (auto& entry : input_id_to_fresh_id_map) {
|
||||
fuzzerutil::UpdateModuleIdBound(context, entry.second);
|
||||
}
|
||||
|
||||
for (auto& entry : output_id_to_fresh_id_map) {
|
||||
fuzzerutil::UpdateModuleIdBound(context, entry.second);
|
||||
}
|
||||
}
|
||||
|
||||
void TransformationOutlineFunction::RemapInputAndOutputIdsInRegion(
|
||||
opt::IRContext* context, const opt::BasicBlock& original_region_exit_block,
|
||||
const std::set<opt::BasicBlock*>& region_blocks,
|
||||
const std::vector<uint32_t>& region_input_ids,
|
||||
const std::vector<uint32_t>& region_output_ids,
|
||||
const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
|
||||
const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) 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.
|
||||
context->get_def_use_mgr()->ForEachUse(
|
||||
id, [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 = 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.
|
||||
context->get_def_use_mgr()->ForEachUse(
|
||||
id,
|
||||
[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 = 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.
|
||||
context->get_def_use_mgr()->GetDef(id)->SetResultId(
|
||||
output_id_to_fresh_id_map.at(id));
|
||||
}
|
||||
}
|
||||
|
||||
void TransformationOutlineFunction::PopulateOutlinedFunction(
|
||||
opt::IRContext* context, const opt::BasicBlock& original_region_entry_block,
|
||||
const opt::BasicBlock& original_region_exit_block,
|
||||
const std::set<opt::BasicBlock*>& region_blocks,
|
||||
const std::vector<uint32_t>& region_output_ids,
|
||||
const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map,
|
||||
opt::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>(
|
||||
context, SpvOpLabel, 0, message_.new_function_region_entry_block(),
|
||||
opt::Instruction::OperandList()));
|
||||
outlined_region_entry_block->SetParent(outlined_function);
|
||||
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(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(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();
|
||||
}
|
||||
|
||||
cloned_block->SetParent(outlined_function);
|
||||
|
||||
// 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() == SpvOpLoopMerge ||
|
||||
inst_it->opcode() == SpvOpSelectionMerge) {
|
||||
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>(
|
||||
context, SpvOpReturn, 0, 0, opt::Instruction::OperandList()));
|
||||
} else {
|
||||
// In the case where there are output ids, we add an OpCompositeConstruct
|
||||
// instruction to pack all the 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) {
|
||||
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>(
|
||||
context, SpvOpCompositeConstruct,
|
||||
message_.new_function_struct_return_type_id(),
|
||||
message_.new_callee_result_id(), struct_member_operands));
|
||||
outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
|
||||
context, SpvOpReturnValue, 0, 0,
|
||||
opt::Instruction::OperandList(
|
||||
{{SPV_OPERAND_TYPE_ID, {message_.new_callee_result_id()}}})));
|
||||
}
|
||||
|
||||
outlined_function->SetFunctionEnd(MakeUnique<opt::Instruction>(
|
||||
context, SpvOpFunctionEnd, 0, 0, opt::Instruction::OperandList()));
|
||||
}
|
||||
|
||||
void TransformationOutlineFunction::ShrinkOriginalRegion(
|
||||
opt::IRContext* context, std::set<opt::BasicBlock*>& region_blocks,
|
||||
const std::vector<uint32_t>& region_input_ids,
|
||||
const std::vector<uint32_t>& region_output_ids,
|
||||
const std::map<uint32_t, uint32_t>& output_id_to_type_id,
|
||||
uint32_t return_type_id,
|
||||
std::unique_ptr<opt::Instruction> cloned_exit_block_merge,
|
||||
std::unique_ptr<opt::Instruction> cloned_exit_block_terminator,
|
||||
opt::BasicBlock* original_region_entry_block) 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>(
|
||||
context, SpvOpFunctionCall, 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.
|
||||
for (uint32_t index = 0; index < region_output_ids.size(); ++index) {
|
||||
uint32_t output_id = region_output_ids[index];
|
||||
original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
|
||||
context, SpvOpCompositeExtract, output_id_to_type_id.at(output_id),
|
||||
output_id,
|
||||
opt::Instruction::OperandList(
|
||||
{{SPV_OPERAND_TYPE_ID, {message_.new_caller_result_id()}},
|
||||
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {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));
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
222
source/fuzz/transformation_outline_function.h
Normal file
222
source/fuzz/transformation_outline_function.h
Normal file
@ -0,0 +1,222 @@
|
||||
// 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.
|
||||
|
||||
#ifndef SOURCE_FUZZ_TRANSFORMATION_OUTLINE_FUNCTION_H_
|
||||
#define SOURCE_FUZZ_TRANSFORMATION_OUTLINE_FUNCTION_H_
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "source/fuzz/fact_manager.h"
|
||||
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
|
||||
#include "source/fuzz/transformation.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
class TransformationOutlineFunction : public Transformation {
|
||||
public:
|
||||
explicit TransformationOutlineFunction(
|
||||
const protobufs::TransformationOutlineFunction& message);
|
||||
|
||||
TransformationOutlineFunction(
|
||||
uint32_t entry_block, uint32_t exit_block,
|
||||
uint32_t new_function_struct_return_type_id,
|
||||
uint32_t new_function_type_id, uint32_t new_function_id,
|
||||
uint32_t new_function_region_entry_block, uint32_t new_caller_result_id,
|
||||
uint32_t new_callee_result_id,
|
||||
std::map<uint32_t, uint32_t>&& input_id_to_fresh_id,
|
||||
std::map<uint32_t, uint32_t>&& output_id_to_fresh_id);
|
||||
|
||||
// - All the fresh ids occurring in the transformation must be distinct and
|
||||
// fresh
|
||||
// - |message_.entry_block| and |message_.exit_block| must form a single-entry
|
||||
// single-exit control flow graph region
|
||||
// - |message_.entry_block| must not start with OpVariable
|
||||
// - |message_.entry_block| must not be a loop header
|
||||
// - |message_.exit_block| must not be a merge block or the continue target
|
||||
// of a loop
|
||||
// - A structured control flow construct must lie either completely within the
|
||||
// region or completely outside it
|
||||
// - |message.entry_block| must not start with OpPhi; this is to keep the
|
||||
// transformation simple - another transformation should be used to split
|
||||
// a desired entry block that starts with OpPhi if needed
|
||||
// - |message_.input_id_to_fresh_id| must contain an entry for every id
|
||||
// defined outside the region but used in the region
|
||||
// - |message_.output_id_to_fresh_id| must contain an entry for every id
|
||||
// defined in the region but used outside the region
|
||||
bool IsApplicable(opt::IRContext* context,
|
||||
const FactManager& fact_manager) const override;
|
||||
|
||||
// - A new function with id |message_.new_function_id| is added to the module.
|
||||
// - If the region generates output ids, the return type of this function is
|
||||
// a new struct type with one field per output id, and with type id
|
||||
// |message_.new_function_struct_return_type|, otherwise the function return
|
||||
// types is void and |message_.new_function_struct_return_type| is not used.
|
||||
// - If the region generates input ids, the new function has one parameter per
|
||||
// input id. Fresh ids for these parameters are provided by
|
||||
// |message_.input_id_to_fresh_id|.
|
||||
// - Unless the type required for the new function is already known,
|
||||
// |message_.new_function_type_id| is used as the type id for a new function
|
||||
// type, and the new function uses this type.
|
||||
// - The new function starts with a dummy block with id
|
||||
// |message_.new_function_first_block|, which jumps straight to a successor
|
||||
// block, to avoid violating rules on what the first block in a function may
|
||||
// look like.
|
||||
// - The outlined region is replaced with a single block, with the same id
|
||||
// as |message_.entry_block|, and which calls the new function, passing the
|
||||
// region's input ids as parameters. The result is stored in
|
||||
// |message_.new_caller_result_id|, which has type
|
||||
// |message_.new_function_struct_return_type| (unless there are
|
||||
// no output ids, in which case the return type is void). The components
|
||||
// of this returned struct are then copied out into the region's output ids.
|
||||
// The block ends with the merge instruction (if any) and terminator of
|
||||
// |message_.exit_block|.
|
||||
// - The body of the new function is identical to the outlined region, except
|
||||
// that (a) the region's entry block has id
|
||||
// |message_.new_function_region_entry_block|, (b) input id uses are
|
||||
// replaced with parameter accesses, (c) and definitions of output ids are
|
||||
// replaced with definitions of corresponding fresh ids provided by
|
||||
// |message_.output_id_to_fresh_id|, and (d) the block of the function
|
||||
// ends by returning a composite of type
|
||||
// |message_.new_function_struct_return_type| comprised of all the fresh
|
||||
// output ids (unless the return type is void, in which case no value is
|
||||
// returned.
|
||||
void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
|
||||
|
||||
protobufs::Transformation ToMessage() const override;
|
||||
|
||||
// Returns the set of blocks dominated by |entry_block| and post-dominated
|
||||
// by |exit_block|.
|
||||
static std::set<opt::BasicBlock*> GetRegionBlocks(
|
||||
opt::IRContext* context, opt::BasicBlock* entry_block,
|
||||
opt::BasicBlock* exit_block);
|
||||
|
||||
// Yields ids that are used in |region_set| and that are either parameters
|
||||
// to the function containing |region_set|, or are defined by blocks of this
|
||||
// function that are outside |region_set|.
|
||||
//
|
||||
// Special cases: OpPhi instructions in |region_entry_block| and the
|
||||
// terminator of |region_exit_block| do not get outlined, therefore
|
||||
// - id uses in OpPhi instructions in |region_entry_block| are ignored
|
||||
// - id uses in the terminator instruction of |region_exit_block| are ignored
|
||||
static std::vector<uint32_t> GetRegionInputIds(
|
||||
opt::IRContext* context, const std::set<opt::BasicBlock*>& region_set,
|
||||
opt::BasicBlock* region_exit_block);
|
||||
|
||||
// Yields all ids that are defined in |region_set| and used outside
|
||||
// |region_set|.
|
||||
//
|
||||
// Special cases: for similar reasons as for |GetRegionInputIds|,
|
||||
// - ids defined in the region and used in the terminator of
|
||||
// |region_exit_block| count as output ids
|
||||
static std::vector<uint32_t> GetRegionOutputIds(
|
||||
opt::IRContext* context, const std::set<opt::BasicBlock*>& region_set,
|
||||
opt::BasicBlock* region_exit_block);
|
||||
|
||||
private:
|
||||
// A helper method for the applicability check. Returns true if and only if
|
||||
// |id| is (a) a fresh id for the module, and (b) an id that has not
|
||||
// previously been subject to this check. We use this to check whether the
|
||||
// ids given for the transformation are not only fresh but also different from
|
||||
// one another.
|
||||
bool CheckIdIsFreshAndNotUsedByThisTransformation(
|
||||
uint32_t id, opt::IRContext* context,
|
||||
std::set<uint32_t>* ids_used_by_this_transformation) const;
|
||||
|
||||
// Ensures that the module's id bound is at least the maximum of any fresh id
|
||||
// associated with the transformation.
|
||||
void UpdateModuleIdBoundForFreshIds(
|
||||
opt::IRContext* context,
|
||||
const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
|
||||
const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) const;
|
||||
|
||||
// Uses |input_id_to_fresh_id_map| and |output_id_to_fresh_id_map| to convert,
|
||||
// in the region to be outlined, all the input ids in |region_input_ids| and
|
||||
// the output ids in |region_output_ids| to their fresh counterparts.
|
||||
// Parameters |region_blocks| provides access to the blocks that must be
|
||||
// modified, and |original_region_exit_block| allows for some special cases
|
||||
// where ids should not be remapped.
|
||||
void RemapInputAndOutputIdsInRegion(
|
||||
opt::IRContext* context,
|
||||
const opt::BasicBlock& original_region_exit_block,
|
||||
const std::set<opt::BasicBlock*>& region_blocks,
|
||||
const std::vector<uint32_t>& region_input_ids,
|
||||
const std::vector<uint32_t>& region_output_ids,
|
||||
const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
|
||||
const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) const;
|
||||
|
||||
// Produce a Function object that has the right function type and parameter
|
||||
// declarations. The function argument types and parameter ids are dictated
|
||||
// by |region_input_ids| and |input_id_to_fresh_id_map|. The function return
|
||||
// type is dictated by |region_output_ids|.
|
||||
//
|
||||
// A new struct type to represent the function return type, and a new function
|
||||
// type for the function, will be added to the module (unless suitable types
|
||||
// are already present).
|
||||
std::unique_ptr<opt::Function> PrepareFunctionPrototype(
|
||||
opt::IRContext* context, 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;
|
||||
|
||||
// Creates the body of the outlined function by cloning blocks from the
|
||||
// original region, given by |region_blocks|, adapting the cloned version
|
||||
// of |original_region_exit_block| so that it returns something appropriate,
|
||||
// and patching up branches to |original_region_entry_block| to refer to its
|
||||
// clone. Parameters |region_output_ids| and |output_id_to_fresh_id_map| are
|
||||
// used to determine what the function should return.
|
||||
void PopulateOutlinedFunction(
|
||||
opt::IRContext* context,
|
||||
const opt::BasicBlock& original_region_entry_block,
|
||||
const opt::BasicBlock& original_region_exit_block,
|
||||
const std::set<opt::BasicBlock*>& region_blocks,
|
||||
const std::vector<uint32_t>& region_output_ids,
|
||||
const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map,
|
||||
opt::Function* outlined_function) const;
|
||||
|
||||
// Shrinks the outlined region, given by |region_blocks|, down to the single
|
||||
// block |original_region_entry_block|. This block is itself shrunk to just
|
||||
// contain:
|
||||
// - any OpPhi instructions that were originally present
|
||||
// - a call to the outlined function, with parameters provided by
|
||||
// |region_input_ids|
|
||||
// - instructions to route components of the call's return value into
|
||||
// |region_output_ids|
|
||||
// - The merge instruction (if any) and terminator of the original region's
|
||||
// exit block, given by |cloned_exit_block_merge| and
|
||||
// |cloned_exit_block_terminator|
|
||||
// Parameters |output_id_to_type_id| and |return_type_id| provide the
|
||||
// provide types for the region's output ids, and the return type of the
|
||||
// outlined function: as the module is in an inconsistent state when this
|
||||
// function is called, this information cannot be gotten from the def-use
|
||||
// manager.
|
||||
void ShrinkOriginalRegion(
|
||||
opt::IRContext* context, std::set<opt::BasicBlock*>& region_blocks,
|
||||
const std::vector<uint32_t>& region_input_ids,
|
||||
const std::vector<uint32_t>& region_output_ids,
|
||||
const std::map<uint32_t, uint32_t>& output_id_to_type_id,
|
||||
uint32_t return_type_id,
|
||||
std::unique_ptr<opt::Instruction> cloned_exit_block_merge,
|
||||
std::unique_ptr<opt::Instruction> cloned_exit_block_terminator,
|
||||
opt::BasicBlock* original_region_entry_block) const;
|
||||
|
||||
protobufs::TransformationOutlineFunction message_;
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_FUZZ_TRANSFORMATION_OUTLINE_FUNCTION_H_
|
@ -36,6 +36,7 @@ if (${SPIRV_BUILD_FUZZER})
|
||||
transformation_composite_extract_test.cpp
|
||||
transformation_copy_object_test.cpp
|
||||
transformation_move_block_down_test.cpp
|
||||
transformation_outline_function_test.cpp
|
||||
transformation_replace_boolean_constant_with_constant_binary_test.cpp
|
||||
transformation_replace_constant_with_uniform_test.cpp
|
||||
transformation_replace_id_with_synonym_test.cpp
|
||||
|
1989
test/fuzz/transformation_outline_function_test.cpp
Normal file
1989
test/fuzz/transformation_outline_function_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user