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:
Stefano Milizia 2020-10-02 05:45:44 +02:00 committed by GitHub
parent 57abfd88c5
commit fc7860e2db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 3221 additions and 4 deletions

View File

@ -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

View File

@ -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);

View File

@ -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_ =

View File

@ -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_;

View 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

View 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_

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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()});

View File

@ -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

View File

@ -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_

View 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

View 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_

View File

@ -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

File diff suppressed because it is too large Load Diff