Roll external/abseil_cpp/ 8f739d18b..917bfee46 (2 commits) (#5887)
[KhronosGroup/SPIRV-Tools.git] / source / fuzz / transformation_add_dead_block.cpp
blob930adc1084fbb6e382de2cf3b1e7aed84ad50231
1 // Copyright (c) 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 #include "source/fuzz/transformation_add_dead_block.h"
17 #include "source/fuzz/fuzzer_util.h"
19 namespace spvtools {
20 namespace fuzz {
22 TransformationAddDeadBlock::TransformationAddDeadBlock(
23 protobufs::TransformationAddDeadBlock message)
24 : message_(std::move(message)) {}
26 TransformationAddDeadBlock::TransformationAddDeadBlock(uint32_t fresh_id,
27 uint32_t existing_block,
28 bool condition_value) {
29 message_.set_fresh_id(fresh_id);
30 message_.set_existing_block(existing_block);
31 message_.set_condition_value(condition_value);
34 bool TransformationAddDeadBlock::IsApplicable(
35 opt::IRContext* ir_context,
36 const TransformationContext& transformation_context) const {
37 // The new block's id must be fresh.
38 if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
39 return false;
42 // First, we check that a constant with the same value as
43 // |message_.condition_value| is present.
44 if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
45 message_.condition_value(), false)) {
46 // The required constant is not present, so the transformation cannot be
47 // applied.
48 return false;
51 // The existing block must indeed exist.
52 auto existing_block =
53 fuzzerutil::MaybeFindBlock(ir_context, message_.existing_block());
54 if (!existing_block) {
55 return false;
58 // It must not head a loop.
59 if (existing_block->IsLoopHeader()) {
60 return false;
63 // It must end with OpBranch.
64 if (existing_block->terminator()->opcode() != spv::Op::OpBranch) {
65 return false;
68 // Its successor must not be a merge block nor continue target.
69 auto successor_block_id =
70 existing_block->terminator()->GetSingleWordInOperand(0);
71 if (fuzzerutil::IsMergeOrContinue(ir_context, successor_block_id)) {
72 return false;
75 // The successor must not be a loop header (i.e., |message_.existing_block|
76 // must not be a back-edge block.
77 if (ir_context->cfg()->block(successor_block_id)->IsLoopHeader()) {
78 return false;
81 // |existing_block| must be reachable.
82 if (!ir_context->IsReachable(*existing_block)) {
83 return false;
86 assert(existing_block->id() != successor_block_id &&
87 "|existing_block| must be different from |successor_block_id|");
89 // Even though we know |successor_block_id| is not a merge block, it might
90 // still have multiple predecessors because divergent control flow is allowed
91 // to converge early (before the merge block). In this case, when we create
92 // the selection construct, its header |existing_block| will not dominate the
93 // merge block |successor_block_id|, which is invalid. Thus, |existing_block|
94 // must dominate |successor_block_id|.
95 opt::DominatorAnalysis* dominator_analysis =
96 ir_context->GetDominatorAnalysis(existing_block->GetParent());
97 if (!dominator_analysis->Dominates(existing_block->id(),
98 successor_block_id)) {
99 return false;
102 return true;
105 void TransformationAddDeadBlock::Apply(
106 opt::IRContext* ir_context,
107 TransformationContext* transformation_context) const {
108 // Update the module id bound so that it is at least the id of the new block.
109 fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
111 // Get the existing block and its successor.
112 auto existing_block = ir_context->cfg()->block(message_.existing_block());
113 auto successor_block_id =
114 existing_block->terminator()->GetSingleWordInOperand(0);
116 // Get the id of the boolean value that will be used as the branch condition.
117 auto bool_id = fuzzerutil::MaybeGetBoolConstant(
118 ir_context, *transformation_context, message_.condition_value(), false);
120 // Make a new block that unconditionally branches to the original successor
121 // block.
122 auto enclosing_function = existing_block->GetParent();
123 std::unique_ptr<opt::BasicBlock> new_block =
124 MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
125 ir_context, spv::Op::OpLabel, 0, message_.fresh_id(),
126 opt::Instruction::OperandList()));
127 new_block->AddInstruction(MakeUnique<opt::Instruction>(
128 ir_context, spv::Op::OpBranch, 0, 0,
129 opt::Instruction::OperandList(
130 {{SPV_OPERAND_TYPE_ID, {successor_block_id}}})));
132 // Turn the original block into a selection merge, with its original successor
133 // as the merge block.
134 existing_block->terminator()->InsertBefore(MakeUnique<opt::Instruction>(
135 ir_context, spv::Op::OpSelectionMerge, 0, 0,
136 opt::Instruction::OperandList(
137 {{SPV_OPERAND_TYPE_ID, {successor_block_id}},
138 {SPV_OPERAND_TYPE_SELECTION_CONTROL,
139 {uint32_t(spv::SelectionControlMask::MaskNone)}}})));
141 // Change the original block's terminator to be a conditional branch on the
142 // given boolean, with the original successor and the new successor as branch
143 // targets, and such that at runtime control will always transfer to the
144 // original successor.
145 existing_block->terminator()->SetOpcode(spv::Op::OpBranchConditional);
146 existing_block->terminator()->SetInOperands(
147 {{SPV_OPERAND_TYPE_ID, {bool_id}},
148 {SPV_OPERAND_TYPE_ID,
149 {message_.condition_value() ? successor_block_id
150 : message_.fresh_id()}},
151 {SPV_OPERAND_TYPE_ID,
152 {message_.condition_value() ? message_.fresh_id()
153 : successor_block_id}}});
155 // Add the new block to the enclosing function.
156 enclosing_function->InsertBasicBlockAfter(std::move(new_block),
157 existing_block);
159 // Fix up OpPhi instructions in the successor block, so that the values they
160 // yield when control has transferred from the new block are the same as if
161 // control had transferred from |message_.existing_block|. This is guaranteed
162 // to be valid since |message_.existing_block| dominates the new block by
163 // construction. Other transformations can change these phi operands to more
164 // interesting values.
165 ir_context->cfg()
166 ->block(successor_block_id)
167 ->ForEachPhiInst([this](opt::Instruction* phi_inst) {
168 // Copy the operand that provides the phi value for the first of any
169 // existing predecessors.
170 opt::Operand copy_of_existing_operand = phi_inst->GetInOperand(0);
171 // Use this as the value associated with the new predecessor.
172 phi_inst->AddOperand(std::move(copy_of_existing_operand));
173 phi_inst->AddOperand({SPV_OPERAND_TYPE_ID, {message_.fresh_id()}});
176 // Do not rely on any existing analysis results since the control flow graph
177 // of the module has changed.
178 ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
180 // Record the fact that the new block is dead.
181 transformation_context->GetFactManager()->AddFactBlockIsDead(
182 message_.fresh_id());
185 protobufs::Transformation TransformationAddDeadBlock::ToMessage() const {
186 protobufs::Transformation result;
187 *result.mutable_add_dead_block() = message_;
188 return result;
191 std::unordered_set<uint32_t> TransformationAddDeadBlock::GetFreshIds() const {
192 return {message_.fresh_id()};
195 } // namespace fuzz
196 } // namespace spvtools