mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-10-18 11:10:05 +00:00
spirv-fuzz: Merge the return instructions in a function (#3838)
This PR introduces TransformationMergeFunctionReturns, which changes a function so that it only has one reachable return statement. Fixes #3655.
This commit is contained in:
parent
57abfd88c5
commit
fc7860e2db
@ -91,6 +91,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
fuzzer_pass_interchange_zero_like_constants.h
|
||||
fuzzer_pass_make_vector_operations_dynamic.h
|
||||
fuzzer_pass_merge_blocks.h
|
||||
fuzzer_pass_merge_function_returns.h
|
||||
fuzzer_pass_mutate_pointers.h
|
||||
fuzzer_pass_obfuscate_constants.h
|
||||
fuzzer_pass_outline_functions.h
|
||||
@ -180,6 +181,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
transformation_load.h
|
||||
transformation_make_vector_operation_dynamic.h
|
||||
transformation_merge_blocks.h
|
||||
transformation_merge_function_returns.h
|
||||
transformation_move_block_down.h
|
||||
transformation_move_instruction_down.h
|
||||
transformation_mutate_pointer.h
|
||||
@ -269,6 +271,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
fuzzer_pass_interchange_zero_like_constants.cpp
|
||||
fuzzer_pass_make_vector_operations_dynamic.cpp
|
||||
fuzzer_pass_merge_blocks.cpp
|
||||
fuzzer_pass_merge_function_returns.cpp
|
||||
fuzzer_pass_mutate_pointers.cpp
|
||||
fuzzer_pass_obfuscate_constants.cpp
|
||||
fuzzer_pass_outline_functions.cpp
|
||||
@ -356,6 +359,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
transformation_load.cpp
|
||||
transformation_make_vector_operation_dynamic.cpp
|
||||
transformation_merge_blocks.cpp
|
||||
transformation_merge_function_returns.cpp
|
||||
transformation_move_block_down.cpp
|
||||
transformation_move_instruction_down.cpp
|
||||
transformation_mutate_pointer.cpp
|
||||
|
@ -60,6 +60,7 @@
|
||||
#include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
|
||||
#include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h"
|
||||
#include "source/fuzz/fuzzer_pass_merge_blocks.h"
|
||||
#include "source/fuzz/fuzzer_pass_merge_function_returns.h"
|
||||
#include "source/fuzz/fuzzer_pass_mutate_pointers.h"
|
||||
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
|
||||
#include "source/fuzz/fuzzer_pass_outline_functions.h"
|
||||
@ -269,6 +270,7 @@ Fuzzer::FuzzerResult Fuzzer::Run() {
|
||||
MaybeAddRepeatedPass<FuzzerPassMakeVectorOperationsDynamic>(
|
||||
&pass_instances);
|
||||
MaybeAddRepeatedPass<FuzzerPassMergeBlocks>(&pass_instances);
|
||||
MaybeAddRepeatedPass<FuzzerPassMergeFunctionReturns>(&pass_instances);
|
||||
MaybeAddRepeatedPass<FuzzerPassMutatePointers>(&pass_instances);
|
||||
MaybeAddRepeatedPass<FuzzerPassOutlineFunctions>(&pass_instances);
|
||||
MaybeAddRepeatedPass<FuzzerPassPermuteBlocks>(&pass_instances);
|
||||
|
@ -99,6 +99,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfMakingDonorLivesafe = {40, 60};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfMakingVectorOperationDynamic = {
|
||||
20, 90};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfMergingBlocks = {20, 95};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfMergingFunctionReturns = {20, 90};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfMutatingPointer = {20, 90};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
|
||||
@ -277,6 +278,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
|
||||
chance_of_making_vector_operation_dynamic_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfMakingVectorOperationDynamic);
|
||||
chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks);
|
||||
chance_of_merging_function_returns_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfMergingFunctionReturns);
|
||||
chance_of_moving_block_down_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfMovingBlockDown);
|
||||
chance_of_mutating_pointer_ =
|
||||
|
@ -244,6 +244,9 @@ class FuzzerContext {
|
||||
return chance_of_making_vector_operation_dynamic_;
|
||||
}
|
||||
uint32_t GetChanceOfMergingBlocks() { return chance_of_merging_blocks_; }
|
||||
uint32_t GetChanceOfMergingFunctionReturns() {
|
||||
return chance_of_merging_function_returns_;
|
||||
}
|
||||
uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
|
||||
uint32_t GetChanceOfMutatingPointer() { return chance_of_mutating_pointer_; }
|
||||
uint32_t GetChanceOfObfuscatingConstant() {
|
||||
@ -452,6 +455,7 @@ class FuzzerContext {
|
||||
uint32_t chance_of_making_donor_livesafe_;
|
||||
uint32_t chance_of_making_vector_operation_dynamic_;
|
||||
uint32_t chance_of_merging_blocks_;
|
||||
uint32_t chance_of_merging_function_returns_;
|
||||
uint32_t chance_of_moving_block_down_;
|
||||
uint32_t chance_of_mutating_pointer_;
|
||||
uint32_t chance_of_obfuscating_constant_;
|
||||
|
257
source/fuzz/fuzzer_pass_merge_function_returns.cpp
Normal file
257
source/fuzz/fuzzer_pass_merge_function_returns.cpp
Normal file
@ -0,0 +1,257 @@
|
||||
// Copyright (c) 2020 Stefano Milizia
|
||||
//
|
||||
// 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_merge_function_returns.h"
|
||||
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
#include "source/fuzz/instruction_descriptor.h"
|
||||
#include "source/fuzz/transformation_merge_function_returns.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
FuzzerPassMergeFunctionReturns::FuzzerPassMergeFunctionReturns(
|
||||
opt::IRContext* ir_context, TransformationContext* transformation_context,
|
||||
FuzzerContext* fuzzer_context,
|
||||
protobufs::TransformationSequence* transformations)
|
||||
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
|
||||
transformations) {}
|
||||
|
||||
FuzzerPassMergeFunctionReturns::~FuzzerPassMergeFunctionReturns() = default;
|
||||
|
||||
void FuzzerPassMergeFunctionReturns::Apply() {
|
||||
for (auto& function : *GetIRContext()->module()) {
|
||||
// Randomly decide whether to consider this function.
|
||||
if (GetFuzzerContext()->ChoosePercentage(
|
||||
GetFuzzerContext()->GetChanceOfMergingFunctionReturns())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only consider functions that have early returns.
|
||||
if (!function.HasEarlyReturn()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the return blocks.
|
||||
auto return_blocks = fuzzerutil::GetReachableReturnBlocks(
|
||||
GetIRContext(), function.result_id());
|
||||
|
||||
// Only go ahead if there is more than one reachable return block.
|
||||
if (return_blocks.size() <= 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure that OpConstantTrue and OpConstantFalse are in the module.
|
||||
FindOrCreateBoolConstant(true, false);
|
||||
FindOrCreateBoolConstant(false, false);
|
||||
|
||||
// Collect the ids available after the entry block of the function.
|
||||
auto ids_available_after_entry_block =
|
||||
GetTypesToIdsAvailableAfterEntryBlock(&function);
|
||||
|
||||
// If the entry block does not branch unconditionally to another block,
|
||||
// split it.
|
||||
if (function.entry()->terminator()->opcode() != SpvOpBranch) {
|
||||
SplitBlockAfterOpPhiOrOpVariable(function.entry()->id());
|
||||
}
|
||||
|
||||
// Collect the merge blocks of the function whose corresponding loops
|
||||
// contain return blocks.
|
||||
auto merge_blocks = GetMergeBlocksOfLoopsContainingBlocks(return_blocks);
|
||||
|
||||
// Split the merge blocks, if they contain instructions different from
|
||||
// OpLabel, OpPhi and OpBranch. Collect the new ids of merge blocks.
|
||||
std::vector<uint32_t> actual_merge_blocks;
|
||||
for (uint32_t merge_block : merge_blocks) {
|
||||
opt::BasicBlock* block = GetIRContext()->get_instr_block(merge_block);
|
||||
|
||||
// We don't need to split blocks that are already suitable (they only
|
||||
// contain OpLabel, OpPhi or OpBranch instructions).
|
||||
if (GetIRContext()
|
||||
->get_instr_block(merge_block)
|
||||
->WhileEachInst([](opt::Instruction* inst) {
|
||||
return inst->opcode() == SpvOpLabel ||
|
||||
inst->opcode() == SpvOpPhi ||
|
||||
inst->opcode() == SpvOpBranch;
|
||||
})) {
|
||||
actual_merge_blocks.emplace_back(merge_block);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the merge block is also a loop header, we need to add a preheader,
|
||||
// which will be the new merge block.
|
||||
if (block->IsLoopHeader()) {
|
||||
actual_merge_blocks.emplace_back(
|
||||
GetOrCreateSimpleLoopPreheader(merge_block)->id());
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the merge block is not a loop header, we must split it after the
|
||||
// last OpPhi instruction. The merge block will be the first of the pair
|
||||
// of blocks obtained after splitting, and it keeps the original id.
|
||||
SplitBlockAfterOpPhiOrOpVariable(merge_block);
|
||||
actual_merge_blocks.emplace_back(merge_block);
|
||||
}
|
||||
|
||||
// Get the ids needed by the transformation.
|
||||
uint32_t outer_header_id = GetFuzzerContext()->GetFreshId();
|
||||
uint32_t outer_return_id = GetFuzzerContext()->GetFreshId();
|
||||
|
||||
bool function_is_void =
|
||||
GetIRContext()->get_type_mgr()->GetType(function.type_id())->AsVoid();
|
||||
|
||||
// We only need a return value if the function is not void.
|
||||
uint32_t return_val_id =
|
||||
function_is_void ? 0 : GetFuzzerContext()->GetFreshId();
|
||||
|
||||
// We only need a placeholder for the return value if the function is not
|
||||
// void and there is at least one relevant merge block.
|
||||
uint32_t returnable_val_id = 0;
|
||||
if (!function_is_void && !actual_merge_blocks.empty()) {
|
||||
// If there is an id of the suitable type, choose one at random.
|
||||
if (ids_available_after_entry_block.count(function.type_id())) {
|
||||
const auto& candidates =
|
||||
ids_available_after_entry_block[function.type_id()];
|
||||
returnable_val_id =
|
||||
candidates[GetFuzzerContext()->RandomIndex(candidates)];
|
||||
} else {
|
||||
// If there is no id, add a global OpUndef.
|
||||
uint32_t suitable_id = FindOrCreateGlobalUndef(function.type_id());
|
||||
// Add the new id to the map of available ids.
|
||||
ids_available_after_entry_block.emplace(
|
||||
function.type_id(), std::vector<uint32_t>({suitable_id}));
|
||||
returnable_val_id = suitable_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all the ids needed for merge blocks.
|
||||
auto merge_blocks_info = GetInfoNeededForMergeBlocks(
|
||||
actual_merge_blocks, &ids_available_after_entry_block);
|
||||
|
||||
// Apply the transformation if it is applicable (it could be inapplicable if
|
||||
// adding new predecessors to merge blocks breaks dominance rules).
|
||||
MaybeApplyTransformation(TransformationMergeFunctionReturns(
|
||||
function.result_id(), outer_header_id, outer_return_id, return_val_id,
|
||||
returnable_val_id, merge_blocks_info));
|
||||
}
|
||||
}
|
||||
|
||||
std::map<uint32_t, std::vector<uint32_t>>
|
||||
FuzzerPassMergeFunctionReturns::GetTypesToIdsAvailableAfterEntryBlock(
|
||||
opt::Function* function) const {
|
||||
std::map<uint32_t, std::vector<uint32_t>> result;
|
||||
// Consider all global declarations
|
||||
for (auto& global : GetIRContext()->module()->types_values()) {
|
||||
if (global.HasResultId() && global.type_id()) {
|
||||
if (!result.count(global.type_id())) {
|
||||
result.emplace(global.type_id(), std::vector<uint32_t>());
|
||||
}
|
||||
result[global.type_id()].emplace_back(global.result_id());
|
||||
}
|
||||
}
|
||||
|
||||
// Consider all function parameters
|
||||
function->ForEachParam([&result](opt::Instruction* param) {
|
||||
if (param->HasResultId() && param->type_id()) {
|
||||
if (!result.count(param->type_id())) {
|
||||
result.emplace(param->type_id(), std::vector<uint32_t>());
|
||||
}
|
||||
|
||||
result[param->type_id()].emplace_back(param->result_id());
|
||||
}
|
||||
});
|
||||
|
||||
// Consider all the instructions in the entry block.
|
||||
for (auto& inst : *function->entry()) {
|
||||
if (inst.HasResultId() && inst.type_id()) {
|
||||
if (!result.count(inst.type_id())) {
|
||||
result.emplace(inst.type_id(), std::vector<uint32_t>());
|
||||
}
|
||||
result[inst.type_id()].emplace_back(inst.result_id());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::set<uint32_t>
|
||||
FuzzerPassMergeFunctionReturns::GetMergeBlocksOfLoopsContainingBlocks(
|
||||
const std::set<uint32_t>& blocks) const {
|
||||
std::set<uint32_t> result;
|
||||
for (uint32_t block : blocks) {
|
||||
uint32_t merge_block =
|
||||
GetIRContext()->GetStructuredCFGAnalysis()->LoopMergeBlock(block);
|
||||
|
||||
while (merge_block != 0 && !result.count(merge_block)) {
|
||||
// Add a new entry.
|
||||
result.emplace(merge_block);
|
||||
|
||||
// Walk up the loop tree.
|
||||
block = merge_block;
|
||||
merge_block = GetIRContext()->GetStructuredCFGAnalysis()->LoopMergeBlock(
|
||||
merge_block);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<protobufs::ReturnMergingInfo>
|
||||
FuzzerPassMergeFunctionReturns::GetInfoNeededForMergeBlocks(
|
||||
const std::vector<uint32_t>& merge_blocks,
|
||||
std::map<uint32_t, std::vector<uint32_t>>*
|
||||
ids_available_after_entry_block) {
|
||||
std::vector<protobufs::ReturnMergingInfo> result;
|
||||
for (uint32_t merge_block : merge_blocks) {
|
||||
protobufs::ReturnMergingInfo info;
|
||||
info.set_merge_block_id(merge_block);
|
||||
info.set_is_returning_id(this->GetFuzzerContext()->GetFreshId());
|
||||
info.set_maybe_return_val_id(this->GetFuzzerContext()->GetFreshId());
|
||||
|
||||
// Add all the ids needed for the OpPhi instructions.
|
||||
this->GetIRContext()
|
||||
->get_instr_block(merge_block)
|
||||
->ForEachPhiInst([this, &info, &ids_available_after_entry_block](
|
||||
opt::Instruction* phi_inst) {
|
||||
protobufs::UInt32Pair entry;
|
||||
entry.set_first(phi_inst->result_id());
|
||||
|
||||
// If there is an id of the suitable type, choose one at random.
|
||||
if (ids_available_after_entry_block->count(phi_inst->type_id())) {
|
||||
auto& candidates =
|
||||
ids_available_after_entry_block->at(phi_inst->type_id());
|
||||
entry.set_second(
|
||||
candidates[this->GetFuzzerContext()->RandomIndex(candidates)]);
|
||||
} else {
|
||||
// If there is no id, add a global OpUndef.
|
||||
uint32_t suitable_id =
|
||||
this->FindOrCreateGlobalUndef(phi_inst->type_id());
|
||||
// Add the new id to the map of available ids.
|
||||
ids_available_after_entry_block->emplace(
|
||||
phi_inst->type_id(), std::vector<uint32_t>({suitable_id}));
|
||||
entry.set_second(suitable_id);
|
||||
}
|
||||
|
||||
// Add the entry to the list.
|
||||
*info.add_opphi_to_suitable_id() = entry;
|
||||
});
|
||||
|
||||
result.emplace_back(info);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
61
source/fuzz/fuzzer_pass_merge_function_returns.h
Normal file
61
source/fuzz/fuzzer_pass_merge_function_returns.h
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2020 Stefano Milizia
|
||||
//
|
||||
// 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_MERGE_FUNCTION_RETURNS_H_
|
||||
#define SOURCE_FUZZ_FUZZER_PASS_MERGE_FUNCTION_RETURNS_H_
|
||||
|
||||
#include "source/fuzz/fuzzer_pass.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
// A fuzzer pass for changing functions in the module so that they don't have an
|
||||
// early return.
|
||||
class FuzzerPassMergeFunctionReturns : public FuzzerPass {
|
||||
public:
|
||||
FuzzerPassMergeFunctionReturns(
|
||||
opt::IRContext* ir_context, TransformationContext* transformation_context,
|
||||
FuzzerContext* fuzzer_context,
|
||||
protobufs::TransformationSequence* transformations);
|
||||
|
||||
~FuzzerPassMergeFunctionReturns();
|
||||
|
||||
void Apply() override;
|
||||
|
||||
private:
|
||||
// Returns a map from type ids to a list of ids with that type and which are
|
||||
// available at the end of the entry block of |function|.
|
||||
std::map<uint32_t, std::vector<uint32_t>>
|
||||
GetTypesToIdsAvailableAfterEntryBlock(opt::Function* function) const;
|
||||
|
||||
// Returns the set of all the loop merge blocks whose corresponding loops
|
||||
// contain at least one of the blocks in |blocks|.
|
||||
std::set<uint32_t> GetMergeBlocksOfLoopsContainingBlocks(
|
||||
const std::set<uint32_t>& blocks) const;
|
||||
|
||||
// Returns a list of ReturnMergingInfo messages, containing the information
|
||||
// needed by the transformation for each of the relevant merge blocks.
|
||||
// If a new id is created (because |ids_available_after_entry_block| does not
|
||||
// have an entry for the corresponding type), a new entry is added to
|
||||
// |ids_available_after_entry_block|, mapping its type to a singleton set
|
||||
// containing it.
|
||||
std::vector<protobufs::ReturnMergingInfo> GetInfoNeededForMergeBlocks(
|
||||
const std::vector<uint32_t>& merge_blocks,
|
||||
std::map<uint32_t, std::vector<uint32_t>>*
|
||||
ids_available_after_entry_block);
|
||||
};
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_FUZZ_FUZZER_PASS_MERGE_FUNCTION_RETURNS_H_
|
@ -437,6 +437,28 @@ bool IsMergeOrContinue(opt::IRContext* ir_context, uint32_t block_id) {
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t GetLoopFromMergeBlock(opt::IRContext* ir_context,
|
||||
uint32_t merge_block_id) {
|
||||
uint32_t result = 0;
|
||||
ir_context->get_def_use_mgr()->WhileEachUse(
|
||||
merge_block_id,
|
||||
[ir_context, &result](opt::Instruction* use_instruction,
|
||||
uint32_t use_index) -> bool {
|
||||
switch (use_instruction->opcode()) {
|
||||
case SpvOpLoopMerge:
|
||||
// The merge block operand is the first operand in OpLoopMerge.
|
||||
if (use_index == 0) {
|
||||
result = ir_context->get_instr_block(use_instruction)->id();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t FindFunctionType(opt::IRContext* ir_context,
|
||||
const std::vector<uint32_t>& type_ids) {
|
||||
// Look through the existing types for a match.
|
||||
@ -1673,6 +1695,23 @@ bool InstructionHasNoSideEffects(const opt::Instruction& instruction) {
|
||||
}
|
||||
}
|
||||
|
||||
std::set<uint32_t> GetReachableReturnBlocks(opt::IRContext* ir_context,
|
||||
uint32_t function_id) {
|
||||
auto function = ir_context->GetFunction(function_id);
|
||||
assert(function && "The function |function_id| must exist.");
|
||||
|
||||
std::set<uint32_t> result;
|
||||
|
||||
ir_context->cfg()->ForEachBlockInPostOrder(function->entry().get(),
|
||||
[&result](opt::BasicBlock* block) {
|
||||
if (block->IsReturn()) {
|
||||
result.emplace(block->id());
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace fuzzerutil
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
@ -164,6 +164,11 @@ bool IsNonFunctionTypeId(opt::IRContext* ir_context, uint32_t id);
|
||||
// Returns true if and only if |block_id| is a merge block or continue target
|
||||
bool IsMergeOrContinue(opt::IRContext* ir_context, uint32_t block_id);
|
||||
|
||||
// Returns the id of the header of the loop corresponding to the given loop
|
||||
// merge block. Returns 0 if |merge_block_id| is not a loop merge block.
|
||||
uint32_t GetLoopFromMergeBlock(opt::IRContext* ir_context,
|
||||
uint32_t merge_block_id);
|
||||
|
||||
// Returns the result id of an instruction of the form:
|
||||
// %id = OpTypeFunction |type_ids|
|
||||
// or 0 if no such instruction exists.
|
||||
@ -547,6 +552,12 @@ bool SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
|
||||
// (called using OpExtInst) have not been considered.
|
||||
bool InstructionHasNoSideEffects(const opt::Instruction& instruction);
|
||||
|
||||
// Returns a set of the ids of all the return blocks that are reachable from
|
||||
// the entry block of |function_id|.
|
||||
// Assumes that the function exists in the module.
|
||||
std::set<uint32_t> GetReachableReturnBlocks(opt::IRContext* ir_context,
|
||||
uint32_t function_id);
|
||||
|
||||
} // namespace fuzzerutil
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
@ -47,6 +47,7 @@
|
||||
#include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
|
||||
#include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h"
|
||||
#include "source/fuzz/fuzzer_pass_merge_blocks.h"
|
||||
#include "source/fuzz/fuzzer_pass_merge_function_returns.h"
|
||||
#include "source/fuzz/fuzzer_pass_mutate_pointers.h"
|
||||
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
|
||||
#include "source/fuzz/fuzzer_pass_outline_functions.h"
|
||||
@ -137,6 +138,7 @@ class RepeatedPassInstances {
|
||||
REPEATED_PASS_INSTANCE(InvertComparisonOperators);
|
||||
REPEATED_PASS_INSTANCE(MakeVectorOperationsDynamic);
|
||||
REPEATED_PASS_INSTANCE(MergeBlocks);
|
||||
REPEATED_PASS_INSTANCE(MergeFunctionReturns);
|
||||
REPEATED_PASS_INSTANCE(MutatePointers);
|
||||
REPEATED_PASS_INSTANCE(ObfuscateConstants);
|
||||
REPEATED_PASS_INSTANCE(OutlineFunctions);
|
||||
|
@ -188,8 +188,10 @@ RepeatedPassRecommenderStandard::GetFuturePassRecommendations(
|
||||
if (&pass == pass_instances_->GetDonateModules()) {
|
||||
// - New functions in the module can be called
|
||||
// - Donated dead functions produce irrelevant ids, which can be replaced
|
||||
// - Donated functions are good candidates for having their returns merged
|
||||
return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(),
|
||||
pass_instances_->GetReplaceIrrelevantIds()});
|
||||
pass_instances_->GetReplaceIrrelevantIds(),
|
||||
pass_instances_->GetMergeFunctionReturns()});
|
||||
}
|
||||
if (&pass == pass_instances_->GetDuplicateRegionsWithSelections()) {
|
||||
// - Parts of duplicated regions can be outlined
|
||||
@ -221,6 +223,11 @@ RepeatedPassRecommenderStandard::GetFuturePassRecommendations(
|
||||
// different way
|
||||
return RandomOrderAndNonNull({pass_instances_->GetSplitBlocks()});
|
||||
}
|
||||
if (&pass == pass_instances_->GetMergeFunctionReturns()) {
|
||||
// - Functions without early returns are more likely to be able to be
|
||||
// inlined.
|
||||
return RandomOrderAndNonNull({pass_instances_->GetInlineFunctions()});
|
||||
}
|
||||
if (&pass == pass_instances_->GetMutatePointers()) {
|
||||
// - This creates irrelevant ids, which can be replaced
|
||||
return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()});
|
||||
|
@ -381,6 +381,58 @@ message SideEffectWrapperInfo {
|
||||
uint32 value_to_copy_id = 7;
|
||||
}
|
||||
|
||||
message ReturnMergingInfo {
|
||||
// TransformationMergeFunctionReturns needs to modify each merge block of
|
||||
// loops containing return instructions, by:
|
||||
// - adding instructions to decide whether the function is returning
|
||||
// - adding instructions to pass on the return value of the function,
|
||||
// if it is returning
|
||||
// - changing the branch instruction (which must be an unconditional branch)
|
||||
// to a conditional branch that, if the function is returning, branches to
|
||||
// the merge block of the innermost loop that contains this merge block
|
||||
// (which can be the new merge block introduced by the transformation).
|
||||
//
|
||||
// One such merge block of the form:
|
||||
// %block = OpLabel
|
||||
// %phi1 = OpPhi %type1 %val1_1 %pred1 %val1_2 %pred2
|
||||
// %phi2 = OpPhi %type2 %val2_1 %pred1 %val2_2 %pred2
|
||||
// OpBranch %next
|
||||
//
|
||||
// is transformed into:
|
||||
// %block = OpLabel
|
||||
// %is_returning_id = OpPhi %bool %false %pred1 %false %pred2 %true %ret_bb1 %is_bb2_returning %mer_bb2
|
||||
// %maybe_return_val_id = OpPhi %return_type %any_returnable_val %pred1 %any_returnable_val %pred2
|
||||
// %ret_val1 %ret_bb1 %ret_val2 %mer_bb2
|
||||
// %phi1 = OpPhi %type1 %val1_1 %pred1 %val1_2 %pred2
|
||||
// %any_suitable_id_1 %ret_bb1 %any_suitable_id_1 %mer_bb2
|
||||
// %phi2 = OpPhi %type2 %val2_1 %pred1 %val2_2 %pred2
|
||||
// %any_suitable_id_1 %ret_bb1 %any_suitable_id_1 %mer_bb2
|
||||
// OpBranchConditional %is_returning_id %innermost_loop_merge %next
|
||||
//
|
||||
// where %ret_bb1 is a block that originally contains a return instruction and %mer_bb2 is the merge block of an inner
|
||||
// loop, from where the function might be returning.
|
||||
//
|
||||
// Note that the block is required to only have OpLabel, OpPhi or OpBranch instructions.
|
||||
|
||||
// The id of the merge block that needs to be modified.
|
||||
uint32 merge_block_id = 1;
|
||||
|
||||
// A fresh id for a boolean OpPhi whose value will be true iff the function
|
||||
// is returning. This will be used to decide whether to break out of the loop
|
||||
// or to use the original branch of the function. This value will also be
|
||||
// used by the merge block of the enclosing loop (if there is one) if the
|
||||
// function is returning from this block.
|
||||
uint32 is_returning_id = 2;
|
||||
|
||||
// A fresh id that will get the value being returned, if the function is
|
||||
// returning. If the function return type is void, this is ignored.
|
||||
uint32 maybe_return_val_id = 3;
|
||||
|
||||
// A mapping from each existing OpPhi id to a suitable id of the same type
|
||||
// available to use before the instruction.
|
||||
repeated UInt32Pair opphi_to_suitable_id = 4;
|
||||
}
|
||||
|
||||
message LoopLimiterInfo {
|
||||
|
||||
// Structure capturing the information required to manipulate a loop limiter
|
||||
@ -1489,6 +1541,65 @@ message TransformationMergeBlocks {
|
||||
|
||||
}
|
||||
|
||||
message TransformationMergeFunctionReturns {
|
||||
|
||||
// A transformation that modifies a function so that it does not return early,
|
||||
// so it only has one return statement (ignoring unreachable blocks).
|
||||
//
|
||||
// The function is enclosed inside an outer loop, that is only executed once,
|
||||
// and whose merge block is the new return block of the function.
|
||||
//
|
||||
// Each return instruction is replaced by:
|
||||
// OpBranch %innermost_loop_merge
|
||||
// where %innermost_loop_merge is the innermost loop containing the return
|
||||
// instruction.
|
||||
//
|
||||
// Each merge block whose associated loop contains return instructions is
|
||||
// changed so that it branches to the merge block of the loop containing it,
|
||||
// as explained in the comments to the ReturnMergingInfo message.
|
||||
//
|
||||
// The new return block (the merge block of the new outer loop) will be of
|
||||
// the following form (if the return type is not void):
|
||||
// %outer_return_id = OpLabel
|
||||
// %return_val_id = OpPhi %return_type %val1 %block_1 %val2 %block_2 ...
|
||||
// OpReturnValue %return_val_id
|
||||
// where %block_k is either a return block that, in the original function, is
|
||||
// outside of any loops, or the merge block of a loop that contains return
|
||||
// instructions and is not, originally, nested inside another loop, and
|
||||
// %block_k is the corresponding return value.
|
||||
// If the function has void type, there will be no OpPhi instruction and the
|
||||
// last instruction will be OpReturn.
|
||||
|
||||
// The id of the function to which the transformation is being applied.
|
||||
uint32 function_id = 1;
|
||||
|
||||
// A fresh id for the header of the new outer loop.
|
||||
uint32 outer_header_id = 2;
|
||||
|
||||
// A fresh id for the new return block of the function,
|
||||
// i.e. the merge block of the new outer loop.
|
||||
uint32 outer_return_id = 3;
|
||||
|
||||
// A fresh id for the value that will be returned.
|
||||
// This is ignored if the function has void return type.
|
||||
uint32 return_val_id = 4;
|
||||
|
||||
// An existing id of the same type as the return value, which is
|
||||
// available to use at the end of the entry block.
|
||||
// This is ignored if the function has void return type or if no
|
||||
// loops in the function contain a return instruction.
|
||||
// If the function is not void, the transformation will add an
|
||||
// OpPhi instruction to each merge block whose associated loop
|
||||
// contains at least a return instruction. The value associated
|
||||
// with existing predecessors from which the function cannot be
|
||||
// returning will be this id, used as a placeholder.
|
||||
uint32 any_returnable_val_id = 5;
|
||||
|
||||
// The information needed to modify the merge blocks of
|
||||
// loops containing return instructions.
|
||||
repeated ReturnMergingInfo return_merging_info = 6;
|
||||
}
|
||||
|
||||
message TransformationMoveBlockDown {
|
||||
|
||||
// A transformation that moves a basic block to be one position lower in
|
||||
|
@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H
|
||||
#define SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H
|
||||
#ifndef SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H_
|
||||
#define SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H_
|
||||
|
||||
#include "source/fuzz/transformation.h"
|
||||
|
||||
@ -119,4 +119,4 @@ class TransformationFlattenConditionalBranch : public Transformation {
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H
|
||||
#endif // SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H_
|
||||
|
816
source/fuzz/transformation_merge_function_returns.cpp
Normal file
816
source/fuzz/transformation_merge_function_returns.cpp
Normal file
@ -0,0 +1,816 @@
|
||||
// Copyright (c) 2020 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_merge_function_returns.h"
|
||||
|
||||
#include "source/fuzz/comparator_deep_blocks_first.h"
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
TransformationMergeFunctionReturns::TransformationMergeFunctionReturns(
|
||||
const protobufs::TransformationMergeFunctionReturns& message)
|
||||
: message_(message) {}
|
||||
|
||||
TransformationMergeFunctionReturns::TransformationMergeFunctionReturns(
|
||||
uint32_t function_id, uint32_t outer_header_id, uint32_t outer_return_id,
|
||||
uint32_t return_val_id, uint32_t any_returnable_val_id,
|
||||
const std::vector<protobufs::ReturnMergingInfo>& returns_merging_info) {
|
||||
message_.set_function_id(function_id);
|
||||
message_.set_outer_header_id(outer_header_id);
|
||||
message_.set_outer_return_id(outer_return_id);
|
||||
message_.set_return_val_id(return_val_id);
|
||||
message_.set_any_returnable_val_id(any_returnable_val_id);
|
||||
for (const auto& return_merging_info : returns_merging_info) {
|
||||
*message_.add_return_merging_info() = return_merging_info;
|
||||
}
|
||||
}
|
||||
|
||||
bool TransformationMergeFunctionReturns::IsApplicable(
|
||||
opt::IRContext* ir_context,
|
||||
const TransformationContext& transformation_context) const {
|
||||
auto function = ir_context->GetFunction(message_.function_id());
|
||||
// The function must exist.
|
||||
if (!function) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The entry block must end in an unconditional branch.
|
||||
if (function->entry()->terminator()->opcode() != SpvOpBranch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The module must contain an OpConstantTrue instruction.
|
||||
if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
|
||||
true, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The module must contain an OpConstantFalse instruction.
|
||||
if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
|
||||
false, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the fresh ids provided are fresh and distinct.
|
||||
std::set<uint32_t> used_fresh_ids;
|
||||
for (uint32_t id : {message_.outer_header_id(), message_.outer_return_id()}) {
|
||||
if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context,
|
||||
&used_fresh_ids)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the additional fresh id required if the function is not void.
|
||||
auto function_type = ir_context->get_type_mgr()->GetType(function->type_id());
|
||||
assert(function_type && "The function type should always exist.");
|
||||
|
||||
if (!function_type->AsVoid() &&
|
||||
(!message_.return_val_id() ||
|
||||
!CheckIdIsFreshAndNotUsedByThisTransformation(
|
||||
message_.return_val_id(), ir_context, &used_fresh_ids))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get a map from the types for which ids are available at the end of the
|
||||
// entry block to one of the ids with that type. We compute this here to avoid
|
||||
// potentially doing it multiple times later on.
|
||||
auto types_to_available_ids =
|
||||
GetTypesToIdAvailableAfterEntryBlock(ir_context);
|
||||
|
||||
// Get the reachable return blocks.
|
||||
auto return_blocks =
|
||||
fuzzerutil::GetReachableReturnBlocks(ir_context, message_.function_id());
|
||||
|
||||
// Map each merge block of loops containing reachable return blocks to the
|
||||
// corresponding returning predecessors (all the blocks that, at the end of
|
||||
// the transformation, will branch to the merge block because the function is
|
||||
// returning).
|
||||
std::map<uint32_t, std::set<uint32_t>> merge_blocks_to_returning_preds;
|
||||
for (uint32_t block : return_blocks) {
|
||||
uint32_t merge_block =
|
||||
ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(block);
|
||||
|
||||
while (merge_block != 0) {
|
||||
// If we have seen this merge block before, update the corresponding set
|
||||
// and break out of the loop.
|
||||
if (merge_blocks_to_returning_preds.count(merge_block)) {
|
||||
merge_blocks_to_returning_preds[merge_block].emplace(block);
|
||||
break;
|
||||
}
|
||||
|
||||
// If we have not seen this merge block before, add a new entry and walk
|
||||
// up the loop tree.
|
||||
merge_blocks_to_returning_preds.emplace(merge_block,
|
||||
std::set<uint32_t>({block}));
|
||||
|
||||
// Walk up the loop tree.
|
||||
block = merge_block;
|
||||
merge_block =
|
||||
ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(merge_block);
|
||||
}
|
||||
}
|
||||
|
||||
// Instructions in the relevant merge blocks must be restricted to OpLabel,
|
||||
// OpPhi and OpBranch.
|
||||
for (const auto& merge_block_entry : merge_blocks_to_returning_preds) {
|
||||
uint32_t merge_block = merge_block_entry.first;
|
||||
bool all_instructions_allowed =
|
||||
ir_context->get_instr_block(merge_block)
|
||||
->WhileEachInst([](opt::Instruction* inst) {
|
||||
return inst->opcode() == SpvOpLabel ||
|
||||
inst->opcode() == SpvOpPhi ||
|
||||
inst->opcode() == SpvOpBranch;
|
||||
});
|
||||
if (!all_instructions_allowed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto merge_blocks_to_info = GetMappingOfMergeBlocksToInfo();
|
||||
|
||||
// For each relevant merge block, check that the correct ids are available.
|
||||
for (const auto& merge_block_entry : merge_blocks_to_returning_preds) {
|
||||
if (!CheckThatTheCorrectIdsAreGivenForMergeBlock(
|
||||
merge_block_entry.first, merge_blocks_to_info,
|
||||
types_to_available_ids, function_type->AsVoid(), ir_context,
|
||||
transformation_context, &used_fresh_ids)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If the function has a non-void return type, and there are merge loops which
|
||||
// contain return instructions, we need to check that either:
|
||||
// - |message_.any_returnable_val_id| exists. In this case, it must have the
|
||||
// same type as the return type of the function and be available at the end
|
||||
// of the entry block.
|
||||
// - a suitable id, available at the end of the entry block can be found in
|
||||
// the module.
|
||||
if (!function_type->AsVoid() && !merge_blocks_to_returning_preds.empty()) {
|
||||
auto returnable_val_def =
|
||||
ir_context->get_def_use_mgr()->GetDef(message_.any_returnable_val_id());
|
||||
if (!returnable_val_def) {
|
||||
// Check if a suitable id can be found in the module.
|
||||
if (types_to_available_ids.count(function->type_id()) == 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (returnable_val_def->type_id() != function->type_id()) {
|
||||
return false;
|
||||
} else if (!fuzzerutil::IdIsAvailableBeforeInstruction(
|
||||
ir_context, function->entry()->terminator(),
|
||||
message_.any_returnable_val_id())) {
|
||||
// The id must be available at the end of the entry block.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that adding new predecessors to the relevant merge blocks does not
|
||||
// render any instructions invalid (each id definition must still dominate
|
||||
// each of its uses).
|
||||
if (!CheckDefinitionsStillDominateUsesAfterAddingNewPredecessors(
|
||||
ir_context, function, merge_blocks_to_returning_preds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TransformationMergeFunctionReturns::Apply(
|
||||
opt::IRContext* ir_context,
|
||||
TransformationContext* transformation_context) const {
|
||||
auto function = ir_context->GetFunction(message_.function_id());
|
||||
auto function_type = ir_context->get_type_mgr()->GetType(function->type_id());
|
||||
|
||||
// Get a map from the types for which ids are available at the end of the
|
||||
// entry block to one of the ids with that type. We compute this here to avoid
|
||||
// potentially doing it multiple times later on.
|
||||
auto types_to_available_ids =
|
||||
GetTypesToIdAvailableAfterEntryBlock(ir_context);
|
||||
|
||||
uint32_t bool_type = fuzzerutil::MaybeGetBoolType(ir_context);
|
||||
|
||||
uint32_t constant_true = fuzzerutil::MaybeGetBoolConstant(
|
||||
ir_context, *transformation_context, true, false);
|
||||
|
||||
uint32_t constant_false = fuzzerutil::MaybeGetBoolConstant(
|
||||
ir_context, *transformation_context, false, false);
|
||||
|
||||
// Get the reachable return blocks.
|
||||
auto return_blocks =
|
||||
fuzzerutil::GetReachableReturnBlocks(ir_context, message_.function_id());
|
||||
|
||||
// Keep a map from the relevant merge blocks to a mapping from each of the
|
||||
// returning predecessors to the corresponding pair (return value,
|
||||
// boolean specifying whether the function is returning). Returning
|
||||
// predecessors are blocks in the loop (not further nested inside loops),
|
||||
// which either return or are merge blocks of nested loops containing return
|
||||
// instructions.
|
||||
std::map<uint32_t, std::map<uint32_t, std::pair<uint32_t, uint32_t>>>
|
||||
merge_blocks_to_returning_predecessors;
|
||||
|
||||
// Initialise the map, mapping each relevant merge block to an empty map.
|
||||
for (uint32_t ret_block_id : return_blocks) {
|
||||
uint32_t merge_block_id =
|
||||
ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(ret_block_id);
|
||||
|
||||
while (merge_block_id != 0 &&
|
||||
!merge_blocks_to_returning_predecessors.count(merge_block_id)) {
|
||||
merge_blocks_to_returning_predecessors.emplace(
|
||||
merge_block_id, std::map<uint32_t, std::pair<uint32_t, uint32_t>>());
|
||||
merge_block_id = ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(
|
||||
merge_block_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Get a reference to an instruction with the same type id as the function's
|
||||
// return type, if the type of the function is not void and ther are loops
|
||||
// containing return instructions.
|
||||
uint32_t returnable_val_id = 0;
|
||||
if (!function_type->AsVoid() &&
|
||||
!merge_blocks_to_returning_predecessors.empty()) {
|
||||
// If |message.any_returnable_val_id| can be found in the module, use it.
|
||||
// Otherwise, use another suitable id found in the module.
|
||||
auto returnable_val_def =
|
||||
ir_context->get_def_use_mgr()->GetDef(message_.any_returnable_val_id());
|
||||
returnable_val_id = returnable_val_def
|
||||
? returnable_val_def->result_id()
|
||||
: types_to_available_ids[function->type_id()];
|
||||
}
|
||||
|
||||
// Keep a map from all the new predecessors of the merge block of the new
|
||||
// outer loop, to the related return value ids.
|
||||
std::map<uint32_t, uint32_t> outer_merge_predecessors;
|
||||
|
||||
// Adjust the return blocks and add the related information to the map or
|
||||
// |outer_merge_predecessors| set.
|
||||
for (uint32_t ret_block_id : return_blocks) {
|
||||
auto ret_block = ir_context->get_instr_block(ret_block_id);
|
||||
|
||||
// Get the return value id (if the function is not void).
|
||||
uint32_t ret_val_id =
|
||||
function_type->AsVoid()
|
||||
? 0
|
||||
: ret_block->terminator()->GetSingleWordInOperand(0);
|
||||
|
||||
uint32_t merge_block_id =
|
||||
ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(ret_block_id);
|
||||
|
||||
// Add a new entry to the map corresponding to the merge block of the
|
||||
// innermost enclosing loop (or that of the new outer loop if there is no
|
||||
// enclosing loop).
|
||||
if (merge_block_id != 0) {
|
||||
merge_blocks_to_returning_predecessors[merge_block_id].emplace(
|
||||
ret_block_id,
|
||||
std::pair<uint32_t, uint32_t>(ret_val_id, constant_true));
|
||||
} else {
|
||||
// If there is no enclosing loop, the block will branch to the merge block
|
||||
// of the new outer loop.
|
||||
merge_block_id = message_.outer_return_id();
|
||||
outer_merge_predecessors.emplace(ret_block_id, ret_val_id);
|
||||
}
|
||||
|
||||
// Replace the return instruction with an unconditional branch.
|
||||
ret_block->terminator()->SetOpcode(SpvOpBranch);
|
||||
ret_block->terminator()->SetInOperands(
|
||||
{{SPV_OPERAND_TYPE_RESULT_ID, {merge_block_id}}});
|
||||
}
|
||||
|
||||
// Get a list of all the relevant merge blocks.
|
||||
std::vector<uint32_t> merge_blocks;
|
||||
merge_blocks.reserve(merge_blocks_to_returning_predecessors.size());
|
||||
for (const auto& entry : merge_blocks_to_returning_predecessors) {
|
||||
merge_blocks.emplace_back(entry.first);
|
||||
}
|
||||
|
||||
// Sort the list so that deeper merge blocks come first.
|
||||
// We need to consider deeper merge blocks first so that, when a merge block
|
||||
// is considered, all the merge blocks enclosed by the corresponding loop have
|
||||
// already been considered and, thus, the mapping from this merge block to the
|
||||
// returning predecessors is complete.
|
||||
std::sort(merge_blocks.begin(), merge_blocks.end(),
|
||||
ComparatorDeepBlocksFirst(ir_context));
|
||||
|
||||
auto merge_blocks_to_info = GetMappingOfMergeBlocksToInfo();
|
||||
|
||||
// Adjust the merge blocks and add the related information to the map or
|
||||
// |outer_merge_predecessors| set.
|
||||
for (uint32_t merge_block_id : merge_blocks) {
|
||||
// Get the info corresponding to |merge_block| from the map, if a
|
||||
// corresponding entry exists. Otherwise use overflow ids and find suitable
|
||||
// ids in the module.
|
||||
protobufs::ReturnMergingInfo* info =
|
||||
merge_blocks_to_info.count(merge_block_id)
|
||||
? &merge_blocks_to_info[merge_block_id]
|
||||
: nullptr;
|
||||
|
||||
uint32_t is_returning_id =
|
||||
info ? info->is_returning_id()
|
||||
: transformation_context->GetOverflowIdSource()
|
||||
->GetNextOverflowId();
|
||||
|
||||
uint32_t maybe_return_val_id = 0;
|
||||
if (!function_type->AsVoid()) {
|
||||
maybe_return_val_id = info ? info->maybe_return_val_id()
|
||||
: transformation_context->GetOverflowIdSource()
|
||||
->GetNextOverflowId();
|
||||
}
|
||||
|
||||
// Map from existing OpPhi to overflow ids. If there is no mapping, get an
|
||||
// empty map.
|
||||
auto phi_to_id = info ? fuzzerutil::RepeatedUInt32PairToMap(
|
||||
*merge_blocks_to_info[merge_block_id]
|
||||
.mutable_opphi_to_suitable_id())
|
||||
: std::map<uint32_t, uint32_t>();
|
||||
|
||||
// Get a reference to the info related to the returning predecessors.
|
||||
const auto& returning_preds =
|
||||
merge_blocks_to_returning_predecessors[merge_block_id];
|
||||
|
||||
// Get a set of the original predecessors.
|
||||
auto preds_list = ir_context->cfg()->preds(merge_block_id);
|
||||
auto preds = std::set<uint32_t>(preds_list.begin(), preds_list.end());
|
||||
|
||||
auto merge_block = ir_context->get_instr_block(merge_block_id);
|
||||
|
||||
// Adjust the existing OpPhi instructions.
|
||||
merge_block->ForEachPhiInst([&preds, &returning_preds, &phi_to_id,
|
||||
&types_to_available_ids](
|
||||
opt::Instruction* inst) {
|
||||
// We need a placeholder value id. If |phi_to_id| contains a mapping
|
||||
// for this instruction, we use the given id, otherwise a suitable id
|
||||
// for the instruction's type from |types_to_available_ids|.
|
||||
uint32_t placeholder_val_id =
|
||||
phi_to_id.count(inst->result_id())
|
||||
? phi_to_id[inst->result_id()]
|
||||
: types_to_available_ids[inst->type_id()];
|
||||
assert(placeholder_val_id &&
|
||||
"We should always be able to find a suitable if the "
|
||||
"transformation is applicable.");
|
||||
|
||||
// Add a pair of operands (placeholder id, new predecessor) for each
|
||||
// new predecessor of the merge block.
|
||||
for (const auto& entry : returning_preds) {
|
||||
// A returning predecessor may already be a predecessor of the
|
||||
// block. In that case, we should not add new operands.
|
||||
// Each entry is in the form (predecessor, {return val, is returning}).
|
||||
if (!preds.count(entry.first)) {
|
||||
inst->AddOperand({SPV_OPERAND_TYPE_RESULT_ID, {placeholder_val_id}});
|
||||
inst->AddOperand({SPV_OPERAND_TYPE_RESULT_ID, {entry.first}});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If the function is not void, add a new OpPhi instructions to collect the
|
||||
// return value from the returning predecessors.
|
||||
if (!function_type->AsVoid()) {
|
||||
opt::Instruction::OperandList operand_list;
|
||||
|
||||
// Add two operands (return value, predecessor) for each returning
|
||||
// predecessor.
|
||||
for (auto entry : returning_preds) {
|
||||
// Each entry is in the form (predecessor, {return value,
|
||||
// is returning}).
|
||||
operand_list.emplace_back(
|
||||
opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {entry.second.first}});
|
||||
operand_list.emplace_back(
|
||||
opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {entry.first}});
|
||||
}
|
||||
|
||||
// Add two operands for each original predecessor from which the function
|
||||
// does not return.
|
||||
for (uint32_t original_pred : preds) {
|
||||
// Only add operands if the function cannot be returning from this
|
||||
// block.
|
||||
if (returning_preds.count(original_pred)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
operand_list.emplace_back(
|
||||
opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {returnable_val_id}});
|
||||
operand_list.emplace_back(
|
||||
opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {original_pred}});
|
||||
}
|
||||
|
||||
// Insert the instruction.
|
||||
merge_block->begin()->InsertBefore(MakeUnique<opt::Instruction>(
|
||||
ir_context, SpvOpPhi, function->type_id(), maybe_return_val_id,
|
||||
std::move(operand_list)));
|
||||
|
||||
fuzzerutil::UpdateModuleIdBound(ir_context, maybe_return_val_id);
|
||||
}
|
||||
|
||||
// Add an OpPhi instruction deciding whether the function is returning.
|
||||
{
|
||||
opt::Instruction::OperandList operand_list;
|
||||
|
||||
// Add two operands (return value, is returning) for each returning
|
||||
// predecessor.
|
||||
for (auto entry : returning_preds) {
|
||||
// Each entry is in the form (predecessor, {return value,
|
||||
// is returning}).
|
||||
operand_list.emplace_back(
|
||||
opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {entry.second.second}});
|
||||
operand_list.emplace_back(
|
||||
opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {entry.first}});
|
||||
}
|
||||
|
||||
// Add two operands for each original predecessor from which the function
|
||||
// does not return.
|
||||
for (uint32_t original_pred : preds) {
|
||||
// Only add operands if the function cannot be returning from this
|
||||
// block.
|
||||
if (returning_preds.count(original_pred)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
operand_list.emplace_back(
|
||||
opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {constant_false}});
|
||||
operand_list.emplace_back(
|
||||
opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {original_pred}});
|
||||
}
|
||||
|
||||
// Insert the instruction.
|
||||
merge_block->begin()->InsertBefore(MakeUnique<opt::Instruction>(
|
||||
ir_context, SpvOpPhi, bool_type, is_returning_id,
|
||||
std::move(operand_list)));
|
||||
|
||||
fuzzerutil::UpdateModuleIdBound(ir_context, is_returning_id);
|
||||
}
|
||||
|
||||
// Change the branching instruction of the block.
|
||||
assert(merge_block->terminator()->opcode() == SpvOpBranch &&
|
||||
"Each block should branch unconditionally to the next.");
|
||||
|
||||
// Add a new entry to the map corresponding to the merge block of the
|
||||
// innermost enclosing loop (or that of the new outer loop if there is no
|
||||
// enclosing loop).
|
||||
uint32_t enclosing_merge =
|
||||
ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(merge_block_id);
|
||||
if (enclosing_merge == 0) {
|
||||
enclosing_merge = message_.outer_return_id();
|
||||
outer_merge_predecessors.emplace(merge_block_id, maybe_return_val_id);
|
||||
} else {
|
||||
merge_blocks_to_returning_predecessors[enclosing_merge].emplace(
|
||||
merge_block_id,
|
||||
std::pair<uint32_t, uint32_t>(maybe_return_val_id, is_returning_id));
|
||||
}
|
||||
|
||||
// Get the current successor.
|
||||
uint32_t original_succ =
|
||||
merge_block->terminator()->GetSingleWordInOperand(0);
|
||||
// Leave the instruction as it is if the block already branches to the merge
|
||||
// block of the enclosing loop.
|
||||
if (original_succ == enclosing_merge) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The block should branch to |enclosing_merge| if |is_returning_id| is
|
||||
// true, to |original_succ| otherwise.
|
||||
merge_block->terminator()->SetOpcode(SpvOpBranchConditional);
|
||||
merge_block->terminator()->SetInOperands(
|
||||
{{SPV_OPERAND_TYPE_RESULT_ID, {is_returning_id}},
|
||||
{SPV_OPERAND_TYPE_RESULT_ID, {enclosing_merge}},
|
||||
{SPV_OPERAND_TYPE_RESULT_ID, {original_succ}}});
|
||||
}
|
||||
|
||||
assert(function->entry()->terminator()->opcode() == SpvOpBranch &&
|
||||
"The entry block should branch unconditionally to another block.");
|
||||
uint32_t block_after_entry =
|
||||
function->entry()->terminator()->GetSingleWordInOperand(0);
|
||||
|
||||
// Create the header for the new outer loop.
|
||||
auto outer_loop_header =
|
||||
MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
|
||||
ir_context, SpvOpLabel, 0, message_.outer_header_id(),
|
||||
opt::Instruction::OperandList()));
|
||||
|
||||
fuzzerutil::UpdateModuleIdBound(ir_context, message_.outer_header_id());
|
||||
|
||||
// Add the instruction: OpLoopMerge %outer_return_id %outer_header_id None
|
||||
// The header is the continue block of the outer loop.
|
||||
outer_loop_header->AddInstruction(MakeUnique<opt::Instruction>(
|
||||
ir_context, SpvOpLoopMerge, 0, 0,
|
||||
opt::Instruction::OperandList{
|
||||
{SPV_OPERAND_TYPE_RESULT_ID, {message_.outer_return_id()}},
|
||||
{SPV_OPERAND_TYPE_RESULT_ID, {message_.outer_header_id()}},
|
||||
{SPV_OPERAND_TYPE_LOOP_CONTROL, {SpvLoopControlMaskNone}}}));
|
||||
|
||||
// Add conditional branch:
|
||||
// OpBranchConditional %true %block_after_entry %outer_header_id
|
||||
// This will always branch to %block_after_entry, but it also creates a back
|
||||
// edge for the loop (which is never traversed).
|
||||
outer_loop_header->AddInstruction(MakeUnique<opt::Instruction>(
|
||||
ir_context, SpvOpBranchConditional, 0, 0,
|
||||
opt::Instruction::OperandList{
|
||||
{SPV_OPERAND_TYPE_RESULT_ID, {constant_true}},
|
||||
{SPV_OPERAND_TYPE_RESULT_ID, {block_after_entry}},
|
||||
{SPV_OPERAND_TYPE_LOOP_CONTROL, {message_.outer_header_id()}}}));
|
||||
|
||||
// Insert the header right after the entry block.
|
||||
function->InsertBasicBlockAfter(std::move(outer_loop_header),
|
||||
function->entry().get());
|
||||
|
||||
// Update the branching instruction of the entry block.
|
||||
function->entry()->terminator()->SetInOperands(
|
||||
{{SPV_OPERAND_TYPE_RESULT_ID, {message_.outer_header_id()}}});
|
||||
|
||||
// Create the merge block for the loop (and return block for the function).
|
||||
auto outer_return_block =
|
||||
MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
|
||||
ir_context, SpvOpLabel, 0, message_.outer_return_id(),
|
||||
opt::Instruction::OperandList()));
|
||||
|
||||
fuzzerutil::UpdateModuleIdBound(ir_context, message_.outer_return_id());
|
||||
|
||||
// If the function is not void, insert an instruction to collect the return
|
||||
// value from the predecessors and an OpReturnValue instruction.
|
||||
if (!function_type->AsVoid()) {
|
||||
opt::Instruction::OperandList operand_list;
|
||||
|
||||
// Add two operands (return value, predecessor) for each predecessor.
|
||||
for (auto entry : outer_merge_predecessors) {
|
||||
// Each entry is in the form (predecessor, return value).
|
||||
operand_list.emplace_back(
|
||||
opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {entry.second}});
|
||||
operand_list.emplace_back(
|
||||
opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {entry.first}});
|
||||
}
|
||||
|
||||
// Insert the OpPhi instruction.
|
||||
outer_return_block->AddInstruction(MakeUnique<opt::Instruction>(
|
||||
ir_context, SpvOpPhi, function->type_id(), message_.return_val_id(),
|
||||
std::move(operand_list)));
|
||||
|
||||
fuzzerutil::UpdateModuleIdBound(ir_context, message_.return_val_id());
|
||||
|
||||
// Insert the OpReturnValue instruction.
|
||||
outer_return_block->AddInstruction(MakeUnique<opt::Instruction>(
|
||||
ir_context, SpvOpReturnValue, 0, 0,
|
||||
opt::Instruction::OperandList{
|
||||
{SPV_OPERAND_TYPE_RESULT_ID, {message_.return_val_id()}}}));
|
||||
} else {
|
||||
// Insert an OpReturn instruction (the function is void).
|
||||
outer_return_block->AddInstruction(MakeUnique<opt::Instruction>(
|
||||
ir_context, SpvOpReturn, 0, 0, opt::Instruction::OperandList{}));
|
||||
}
|
||||
|
||||
// Insert the new return block at the end of the function.
|
||||
function->AddBasicBlock(std::move(outer_return_block));
|
||||
|
||||
// All analyses must be invalidated because the structure of the module was
|
||||
// changed.
|
||||
ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
|
||||
}
|
||||
|
||||
std::unordered_set<uint32_t> TransformationMergeFunctionReturns::GetFreshIds()
|
||||
const {
|
||||
std::unordered_set<uint32_t> result;
|
||||
result.emplace(message_.outer_header_id());
|
||||
result.emplace(message_.outer_return_id());
|
||||
// |message_.return_val_info| can be 0 if the function is void.
|
||||
if (message_.return_val_id()) {
|
||||
result.emplace(message_.return_val_id());
|
||||
}
|
||||
|
||||
for (auto merging_info : message_.return_merging_info()) {
|
||||
result.emplace(merging_info.is_returning_id());
|
||||
// |maybe_return_val_id| can be 0 if the function is void.
|
||||
if (merging_info.maybe_return_val_id()) {
|
||||
result.emplace(merging_info.maybe_return_val_id());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protobufs::Transformation TransformationMergeFunctionReturns::ToMessage()
|
||||
const {
|
||||
return protobufs::Transformation();
|
||||
}
|
||||
|
||||
std::map<uint32_t, protobufs::ReturnMergingInfo>
|
||||
TransformationMergeFunctionReturns::GetMappingOfMergeBlocksToInfo() const {
|
||||
std::map<uint32_t, protobufs::ReturnMergingInfo> result;
|
||||
for (const auto& info : message_.return_merging_info()) {
|
||||
result.emplace(info.merge_block_id(), info);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::map<uint32_t, uint32_t>
|
||||
TransformationMergeFunctionReturns::GetTypesToIdAvailableAfterEntryBlock(
|
||||
opt::IRContext* ir_context) const {
|
||||
std::map<uint32_t, uint32_t> result;
|
||||
// Consider all global declarations
|
||||
for (auto& global : ir_context->module()->types_values()) {
|
||||
if (global.HasResultId() && global.type_id()) {
|
||||
result.emplace(global.type_id(), global.result_id());
|
||||
}
|
||||
}
|
||||
|
||||
auto function = ir_context->GetFunction(message_.function_id());
|
||||
assert(function && "The function must exist.");
|
||||
|
||||
// Consider all function parameters
|
||||
function->ForEachParam([&result](opt::Instruction* param) {
|
||||
if (param->HasResultId() && param->type_id()) {
|
||||
result.emplace(param->type_id(), param->result_id());
|
||||
}
|
||||
});
|
||||
|
||||
// Consider all the instructions in the entry block.
|
||||
for (auto& inst : *function->entry()) {
|
||||
if (inst.HasResultId() && inst.type_id()) {
|
||||
result.emplace(inst.type_id(), inst.result_id());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TransformationMergeFunctionReturns::
|
||||
CheckDefinitionsStillDominateUsesAfterAddingNewPredecessors(
|
||||
opt::IRContext* ir_context, const opt::Function* function,
|
||||
const std::map<uint32_t, std::set<uint32_t>>&
|
||||
merge_blocks_to_new_predecessors) {
|
||||
for (const auto& merge_block_entry : merge_blocks_to_new_predecessors) {
|
||||
uint32_t merge_block = merge_block_entry.first;
|
||||
const auto& returning_preds = merge_block_entry.second;
|
||||
|
||||
// Find a list of blocks in which there might be problematic definitions.
|
||||
// These are all the blocks that dominate the merge block but do not
|
||||
// dominate all of the new predecessors.
|
||||
std::vector<opt::BasicBlock*> problematic_blocks;
|
||||
|
||||
auto dominator_analysis = ir_context->GetDominatorAnalysis(function);
|
||||
|
||||
// Start from the immediate dominator of the merge block.
|
||||
auto current_block = dominator_analysis->ImmediateDominator(merge_block);
|
||||
assert(current_block &&
|
||||
"Each merge block should have at least one dominator.");
|
||||
|
||||
for (uint32_t pred : returning_preds) {
|
||||
while (!dominator_analysis->Dominates(current_block->id(), pred)) {
|
||||
// The current block does not dominate all of the new predecessor
|
||||
// blocks, so it might be problematic.
|
||||
problematic_blocks.emplace_back(current_block);
|
||||
|
||||
// Walk up the dominator tree.
|
||||
current_block = dominator_analysis->ImmediateDominator(current_block);
|
||||
assert(current_block &&
|
||||
"We should be able to find a dominator for all the blocks, "
|
||||
"since they must all be dominated at least by the header.");
|
||||
}
|
||||
}
|
||||
|
||||
// Identify the loop header corresponding to the merge block.
|
||||
uint32_t loop_header =
|
||||
fuzzerutil::GetLoopFromMergeBlock(ir_context, merge_block);
|
||||
|
||||
// For all the ids defined in blocks inside |problematic_blocks|, check that
|
||||
// all their uses are either:
|
||||
// - inside the loop (or in the loop header). If this is the case, the path
|
||||
// from the definition to the use does not go through the merge block, so
|
||||
// adding new predecessor to it is not a problem.
|
||||
// - inside an OpPhi instruction in the merge block. If this is the case,
|
||||
// the definition does not need to dominate the merge block.
|
||||
for (auto block : problematic_blocks) {
|
||||
assert((block->id() == loop_header ||
|
||||
ir_context->GetStructuredCFGAnalysis()->ContainingLoop(
|
||||
block->id()) == loop_header) &&
|
||||
"The problematic blocks should all be inside the loop (also "
|
||||
"considering the header).");
|
||||
bool dominance_rules_maintained =
|
||||
block->WhileEachInst([ir_context, loop_header,
|
||||
merge_block](opt::Instruction* instruction) {
|
||||
// Instruction without a result id do not cause any problems.
|
||||
if (!instruction->HasResultId()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check that all the uses of the id are inside the loop.
|
||||
return ir_context->get_def_use_mgr()->WhileEachUse(
|
||||
instruction->result_id(),
|
||||
[ir_context, loop_header, merge_block](
|
||||
opt::Instruction* inst_use, uint32_t /* unused */) {
|
||||
uint32_t block_use =
|
||||
ir_context->get_instr_block(inst_use)->id();
|
||||
|
||||
// The usage is OK if it is inside the loop (including the
|
||||
// header).
|
||||
if (block_use == loop_header ||
|
||||
ir_context->GetStructuredCFGAnalysis()->ContainingLoop(
|
||||
block_use)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The usage is OK if it is inside an OpPhi instruction in the
|
||||
// merge block.
|
||||
return block_use == merge_block &&
|
||||
inst_use->opcode() == SpvOpPhi;
|
||||
});
|
||||
});
|
||||
|
||||
// If not all instructions in the block satisfy the requirement, the
|
||||
// transformation is not applicable.
|
||||
if (!dominance_rules_maintained) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TransformationMergeFunctionReturns::
|
||||
CheckThatTheCorrectIdsAreGivenForMergeBlock(
|
||||
uint32_t merge_block,
|
||||
const std::map<uint32_t, protobufs::ReturnMergingInfo>&
|
||||
merge_blocks_to_info,
|
||||
const std::map<uint32_t, uint32_t>& types_to_available_id,
|
||||
bool function_is_void, opt::IRContext* ir_context,
|
||||
const TransformationContext& transformation_context,
|
||||
std::set<uint32_t>* used_fresh_ids) {
|
||||
// A map from OpPhi ids to ids of the same type available at the beginning
|
||||
// of the merge block.
|
||||
std::map<uint32_t, uint32_t> phi_to_id;
|
||||
|
||||
if (merge_blocks_to_info.count(merge_block) > 0) {
|
||||
// If the map contains an entry for the merge block, check that the fresh
|
||||
// ids are fresh and distinct.
|
||||
auto info = merge_blocks_to_info.at(merge_block);
|
||||
if (!info.is_returning_id() ||
|
||||
!CheckIdIsFreshAndNotUsedByThisTransformation(
|
||||
info.is_returning_id(), ir_context, used_fresh_ids)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!function_is_void &&
|
||||
(!info.maybe_return_val_id() ||
|
||||
!CheckIdIsFreshAndNotUsedByThisTransformation(
|
||||
info.maybe_return_val_id(), ir_context, used_fresh_ids))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the mapping from OpPhis to suitable ids.
|
||||
phi_to_id = fuzzerutil::RepeatedUInt32PairToMap(
|
||||
*info.mutable_opphi_to_suitable_id());
|
||||
} else {
|
||||
// If the map does not contain an entry for the merge block, check that
|
||||
// overflow ids are available.
|
||||
if (!transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For each OpPhi instruction, check that a suitable placeholder id is
|
||||
// available.
|
||||
bool suitable_info_for_phi =
|
||||
ir_context->get_instr_block(merge_block)
|
||||
->WhileEachPhiInst([ir_context, &phi_to_id,
|
||||
&types_to_available_id](opt::Instruction* inst) {
|
||||
if (phi_to_id.count(inst->result_id()) > 0) {
|
||||
// If there exists a mapping for this instruction and the
|
||||
// placeholder id exists in the module, check that it has the
|
||||
// correct type and it is available before the instruction.
|
||||
auto placeholder_def = ir_context->get_def_use_mgr()->GetDef(
|
||||
phi_to_id[inst->result_id()]);
|
||||
if (placeholder_def) {
|
||||
if (inst->type_id() != placeholder_def->type_id()) {
|
||||
return false;
|
||||
}
|
||||
if (!fuzzerutil::IdIsAvailableBeforeInstruction(
|
||||
ir_context, inst, placeholder_def->result_id())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no mapping, check if there is a suitable id
|
||||
// available at the end of the entry block.
|
||||
return types_to_available_id.count(inst->type_id()) > 0;
|
||||
});
|
||||
|
||||
if (!suitable_info_for_phi) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
115
source/fuzz/transformation_merge_function_returns.h
Normal file
115
source/fuzz/transformation_merge_function_returns.h
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright (c) 2020 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_MERGE_FUNCTION_RETURNS_
|
||||
#define SOURCE_FUZZ_TRANSFORMATION_MERGE_FUNCTION_RETURNS_
|
||||
|
||||
#include "source/fuzz/transformation.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
class TransformationMergeFunctionReturns : public Transformation {
|
||||
public:
|
||||
explicit TransformationMergeFunctionReturns(
|
||||
const protobufs::TransformationMergeFunctionReturns& message);
|
||||
|
||||
TransformationMergeFunctionReturns(
|
||||
uint32_t function_id, uint32_t outer_header_id, uint32_t outer_return_id,
|
||||
uint32_t return_val_id, uint32_t any_returnable_val_id,
|
||||
const std::vector<protobufs::ReturnMergingInfo>& returns_merging_info);
|
||||
|
||||
// - |message_.function_id| is the id of a function.
|
||||
// - The entry block of |message_.function_id| branches unconditionally to
|
||||
// another block.
|
||||
// - |message_.any_returnable_val_id| is an id whose type is the same as the
|
||||
// return type of the function and which is available at the end of the
|
||||
// entry block. If this id is not found in the module, the transformation
|
||||
// will try to find a suitable one.
|
||||
// If the function is void, or no loops in the function contain return
|
||||
// statements, this id will be ignored.
|
||||
// - Merge blocks of reachable loops that contain return statements only
|
||||
// consist of OpLabel, OpPhi or OpBranch instructions.
|
||||
// - The model contains OpConstantTrue and OpConstantFalse instructions.
|
||||
// - For all merge blocks of reachable loops that contain return statements,
|
||||
// either:
|
||||
// - a mapping is provided in |message_.return_merging_info|, all of the
|
||||
// corresponding fresh ids are valid and, for each OpPhi instruction in
|
||||
// the block, there is a mapping to an available id of the same type in
|
||||
// |opphi_to_suitable_id| or a suitable id, available at the end of the
|
||||
// entry block, can be found in the module.
|
||||
// - there is no mapping, but overflow ids are available and, for every
|
||||
// OpPhi instruction in the merge blocks that need to be modified, a
|
||||
// suitable id, available at the end of the entry block, can be found.
|
||||
// - The addition of new predecessors to the relevant merge blocks does not
|
||||
// cause any id use to be invalid (i.e. every id must dominate all its uses
|
||||
// even after the transformation has added new branches).
|
||||
// - All of the fresh ids that are provided and needed by the transformation
|
||||
// are valid.
|
||||
bool IsApplicable(
|
||||
opt::IRContext* ir_context,
|
||||
const TransformationContext& transformation_context) const override;
|
||||
|
||||
// Changes the function so that there is only one reachable return
|
||||
// instruction. The function is enclosed by an outer loop, whose merge block
|
||||
// is the new return block. All existing return statements are replaced by
|
||||
// branch instructions to the merge block of the loop enclosing them, and
|
||||
// OpPhi instructions are used to keep track of the return value and of
|
||||
// whether the function is returning.
|
||||
void Apply(opt::IRContext* ir_context,
|
||||
TransformationContext* transformation_context) const override;
|
||||
|
||||
std::unordered_set<uint32_t> GetFreshIds() const override;
|
||||
|
||||
protobufs::Transformation ToMessage() const override;
|
||||
|
||||
private:
|
||||
// Returns a map from merge block ids to the corresponding info in
|
||||
// |message_.return_merging_info|.
|
||||
std::map<uint32_t, protobufs::ReturnMergingInfo>
|
||||
GetMappingOfMergeBlocksToInfo() const;
|
||||
|
||||
// Returns a map from type ids to an id with that type and which is available
|
||||
// at the end of the entry block of |message_.function_id|.
|
||||
// Assumes that the function exists.
|
||||
std::map<uint32_t, uint32_t> GetTypesToIdAvailableAfterEntryBlock(
|
||||
opt::IRContext* ir_context) const;
|
||||
|
||||
// Returns true if adding new predecessors to the given loop merge blocks
|
||||
// does not render any instructions invalid (each id definition must still
|
||||
// dominate all of its uses). The loop merge blocks and corresponding new
|
||||
// predecessors to consider are given in |merge_blocks_to_new_predecessors|.
|
||||
// All of the new predecessors are assumed to be inside the loop associated
|
||||
// with the corresponding loop merge block.
|
||||
static bool CheckDefinitionsStillDominateUsesAfterAddingNewPredecessors(
|
||||
opt::IRContext* ir_context, const opt::Function* function,
|
||||
const std::map<uint32_t, std::set<uint32_t>>&
|
||||
merge_blocks_to_new_predecessors);
|
||||
|
||||
// Returns true if the required ids for |merge_block| are provided in the
|
||||
// |merge_blocks_to_info| map, or if ids of the suitable type can be found.
|
||||
static bool CheckThatTheCorrectIdsAreGivenForMergeBlock(
|
||||
uint32_t merge_block,
|
||||
const std::map<uint32_t, protobufs::ReturnMergingInfo>&
|
||||
merge_blocks_to_info,
|
||||
const std::map<uint32_t, uint32_t>& types_to_available_id,
|
||||
bool function_is_void, opt::IRContext* ir_context,
|
||||
const TransformationContext& transformation_context,
|
||||
std::set<uint32_t>* used_fresh_ids);
|
||||
|
||||
protobufs::TransformationMergeFunctionReturns message_;
|
||||
};
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_FUZZ_TRANSFORMATION_MERGE_FUNCTION_RETURNS_
|
@ -75,6 +75,7 @@ if (${SPIRV_BUILD_FUZZER})
|
||||
transformation_load_test.cpp
|
||||
transformation_make_vector_operation_dynamic_test.cpp
|
||||
transformation_merge_blocks_test.cpp
|
||||
transformation_merge_function_returns_test.cpp
|
||||
transformation_move_block_down_test.cpp
|
||||
transformation_move_instruction_down_test.cpp
|
||||
transformation_mutate_pointer_test.cpp
|
||||
|
1784
test/fuzz/transformation_merge_function_returns_test.cpp
Normal file
1784
test/fuzz/transformation_merge_function_returns_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user