211 строки
7.9 KiB
C++
211 строки
7.9 KiB
C++
// 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_replace_opselect_with_conditional_branch.h"
|
|
|
|
#include "source/fuzz/fuzzer_util.h"
|
|
|
|
namespace spvtools {
|
|
namespace fuzz {
|
|
TransformationReplaceOpSelectWithConditionalBranch::
|
|
TransformationReplaceOpSelectWithConditionalBranch(
|
|
protobufs::TransformationReplaceOpSelectWithConditionalBranch message)
|
|
: message_(std::move(message)) {}
|
|
|
|
TransformationReplaceOpSelectWithConditionalBranch::
|
|
TransformationReplaceOpSelectWithConditionalBranch(
|
|
uint32_t select_id, uint32_t true_block_id, uint32_t false_block_id) {
|
|
message_.set_select_id(select_id);
|
|
message_.set_true_block_id(true_block_id);
|
|
message_.set_false_block_id(false_block_id);
|
|
}
|
|
|
|
bool TransformationReplaceOpSelectWithConditionalBranch::IsApplicable(
|
|
opt::IRContext* ir_context,
|
|
const TransformationContext& /* unused */) const {
|
|
assert((message_.true_block_id() || message_.false_block_id()) &&
|
|
"At least one of the ids must be non-zero.");
|
|
|
|
// Check that the non-zero ids are fresh.
|
|
std::set<uint32_t> used_ids;
|
|
for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) {
|
|
if (id && !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context,
|
|
&used_ids)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
auto instruction =
|
|
ir_context->get_def_use_mgr()->GetDef(message_.select_id());
|
|
|
|
// The instruction must exist and it must be an OpSelect instruction.
|
|
if (!instruction || instruction->opcode() != spv::Op::OpSelect) {
|
|
return false;
|
|
}
|
|
|
|
// Check that the condition is a scalar boolean.
|
|
auto condition = ir_context->get_def_use_mgr()->GetDef(
|
|
instruction->GetSingleWordInOperand(0));
|
|
assert(condition && "The condition should always exist in a valid module.");
|
|
|
|
auto condition_type =
|
|
ir_context->get_type_mgr()->GetType(condition->type_id());
|
|
if (!condition_type->AsBool()) {
|
|
return false;
|
|
}
|
|
|
|
auto block = ir_context->get_instr_block(instruction);
|
|
assert(block && "The block containing the instruction must be found");
|
|
|
|
// The instruction must be the first in its block.
|
|
if (instruction->unique_id() != block->begin()->unique_id()) {
|
|
return false;
|
|
}
|
|
|
|
// The block must not be a merge block.
|
|
if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) {
|
|
return false;
|
|
}
|
|
|
|
// The block must have exactly one predecessor.
|
|
auto predecessors = ir_context->cfg()->preds(block->id());
|
|
if (predecessors.size() != 1) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t pred_id = predecessors[0];
|
|
auto predecessor = ir_context->get_instr_block(pred_id);
|
|
|
|
// The predecessor must not be the header of a construct and it must end with
|
|
// OpBranch.
|
|
if (predecessor->GetMergeInst() != nullptr ||
|
|
predecessor->terminator()->opcode() != spv::Op::OpBranch) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void TransformationReplaceOpSelectWithConditionalBranch::Apply(
|
|
opt::IRContext* ir_context, TransformationContext* /* unused */) const {
|
|
auto instruction =
|
|
ir_context->get_def_use_mgr()->GetDef(message_.select_id());
|
|
|
|
auto block = ir_context->get_instr_block(instruction);
|
|
|
|
auto predecessor =
|
|
ir_context->get_instr_block(ir_context->cfg()->preds(block->id())[0]);
|
|
|
|
// Create a new block for each non-zero id in {|message_.true_branch_id|,
|
|
// |message_.false_branch_id|}. Make each newly-created block branch
|
|
// unconditionally to the instruction block.
|
|
for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) {
|
|
if (id) {
|
|
fuzzerutil::UpdateModuleIdBound(ir_context, id);
|
|
|
|
// Create the new block.
|
|
auto new_block = MakeUnique<opt::BasicBlock>(
|
|
MakeUnique<opt::Instruction>(ir_context, spv::Op::OpLabel, 0, id,
|
|
opt::Instruction::OperandList{}));
|
|
|
|
// Add an unconditional branch from the new block to the instruction
|
|
// block.
|
|
new_block->AddInstruction(MakeUnique<opt::Instruction>(
|
|
ir_context, spv::Op::OpBranch, 0, 0,
|
|
opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {block->id()}}}));
|
|
|
|
// Insert the new block right after the predecessor of the instruction
|
|
// block.
|
|
block->GetParent()->InsertBasicBlockBefore(std::move(new_block), block);
|
|
}
|
|
}
|
|
|
|
// Delete the OpBranch instruction from the predecessor.
|
|
ir_context->KillInst(predecessor->terminator());
|
|
|
|
// Add an OpSelectionMerge instruction to the predecessor block, where the
|
|
// merge block is the instruction block.
|
|
predecessor->AddInstruction(MakeUnique<opt::Instruction>(
|
|
ir_context, spv::Op::OpSelectionMerge, 0, 0,
|
|
opt::Instruction::OperandList{
|
|
{SPV_OPERAND_TYPE_ID, {block->id()}},
|
|
{SPV_OPERAND_TYPE_SELECTION_CONTROL,
|
|
{uint32_t(spv::SelectionControlMask::MaskNone)}}}));
|
|
|
|
// |if_block| will be the true block, if it has been created, the instruction
|
|
// block otherwise.
|
|
uint32_t if_block =
|
|
message_.true_block_id() ? message_.true_block_id() : block->id();
|
|
|
|
// |else_block| will be the false block, if it has been created, the
|
|
// instruction block otherwise.
|
|
uint32_t else_block =
|
|
message_.false_block_id() ? message_.false_block_id() : block->id();
|
|
|
|
assert(if_block != else_block &&
|
|
"|if_block| and |else_block| should always be different, if the "
|
|
"transformation is applicable.");
|
|
|
|
// Add a conditional branching instruction to the predecessor, branching to
|
|
// |if_block| if the condition is true and to |if_false| otherwise.
|
|
predecessor->AddInstruction(MakeUnique<opt::Instruction>(
|
|
ir_context, spv::Op::OpBranchConditional, 0, 0,
|
|
opt::Instruction::OperandList{
|
|
{SPV_OPERAND_TYPE_ID, {instruction->GetSingleWordInOperand(0)}},
|
|
{SPV_OPERAND_TYPE_ID, {if_block}},
|
|
{SPV_OPERAND_TYPE_ID, {else_block}}}));
|
|
|
|
// |if_pred| will be the true block, if it has been created, the existing
|
|
// predecessor otherwise.
|
|
uint32_t if_pred =
|
|
message_.true_block_id() ? message_.true_block_id() : predecessor->id();
|
|
|
|
// |else_pred| will be the false block, if it has been created, the existing
|
|
// predecessor otherwise.
|
|
uint32_t else_pred =
|
|
message_.false_block_id() ? message_.false_block_id() : predecessor->id();
|
|
|
|
// Replace the OpSelect instruction in the merge block with an OpPhi.
|
|
// This: OpSelect %type %cond %if %else
|
|
// will become: OpPhi %type %if %if_pred %else %else_pred
|
|
instruction->SetOpcode(spv::Op::OpPhi);
|
|
std::vector<opt::Operand> operands;
|
|
|
|
operands.emplace_back(instruction->GetInOperand(1));
|
|
operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {if_pred}});
|
|
|
|
operands.emplace_back(instruction->GetInOperand(2));
|
|
operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {else_pred}});
|
|
|
|
instruction->SetInOperands(std::move(operands));
|
|
|
|
// Invalidate all analyses, since the structure of the module was changed.
|
|
ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
|
|
}
|
|
|
|
protobufs::Transformation
|
|
TransformationReplaceOpSelectWithConditionalBranch::ToMessage() const {
|
|
protobufs::Transformation result;
|
|
*result.mutable_replace_opselect_with_conditional_branch() = message_;
|
|
return result;
|
|
}
|
|
|
|
std::unordered_set<uint32_t>
|
|
TransformationReplaceOpSelectWithConditionalBranch::GetFreshIds() const {
|
|
return {message_.true_block_id(), message_.false_block_id()};
|
|
}
|
|
|
|
} // namespace fuzz
|
|
} // namespace spvtools
|