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.
14
15 #include "source/fuzz/transformation_add_dead_block.h"
16
17 #include "source/fuzz/fuzzer_util.h"
18
19 namespace spvtools {
20 namespace fuzz {
21
TransformationAddDeadBlock(const spvtools::fuzz::protobufs::TransformationAddDeadBlock & message)22 TransformationAddDeadBlock::TransformationAddDeadBlock(
23 const spvtools::fuzz::protobufs::TransformationAddDeadBlock& message)
24 : message_(message) {}
25
TransformationAddDeadBlock(uint32_t fresh_id,uint32_t existing_block,bool condition_value)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);
32 }
33
IsApplicable(opt::IRContext * ir_context,const TransformationContext &) const34 bool TransformationAddDeadBlock::IsApplicable(
35 opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
36 // The new block's id must be fresh.
37 if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
38 return false;
39 }
40
41 // First, we check that a constant with the same value as
42 // |message_.condition_value| is present.
43 if (!fuzzerutil::MaybeGetBoolConstantId(ir_context,
44 message_.condition_value())) {
45 // The required constant is not present, so the transformation cannot be
46 // applied.
47 return false;
48 }
49
50 // The existing block must indeed exist.
51 auto existing_block =
52 fuzzerutil::MaybeFindBlock(ir_context, message_.existing_block());
53 if (!existing_block) {
54 return false;
55 }
56
57 // It must not head a loop.
58 if (existing_block->IsLoopHeader()) {
59 return false;
60 }
61
62 // It must end with OpBranch.
63 if (existing_block->terminator()->opcode() != SpvOpBranch) {
64 return false;
65 }
66
67 // Its successor must not be a merge block nor continue target.
68 auto successor_block_id =
69 existing_block->terminator()->GetSingleWordInOperand(0);
70 if (fuzzerutil::IsMergeOrContinue(ir_context, successor_block_id)) {
71 return false;
72 }
73
74 // The successor must not be a loop header (i.e., |message_.existing_block|
75 // must not be a back-edge block.
76 if (ir_context->cfg()->block(successor_block_id)->IsLoopHeader()) {
77 return false;
78 }
79
80 return true;
81 }
82
Apply(opt::IRContext * ir_context,TransformationContext * transformation_context) const83 void TransformationAddDeadBlock::Apply(
84 opt::IRContext* ir_context,
85 TransformationContext* transformation_context) const {
86 // Update the module id bound so that it is at least the id of the new block.
87 fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
88
89 // Get the existing block and its successor.
90 auto existing_block = ir_context->cfg()->block(message_.existing_block());
91 auto successor_block_id =
92 existing_block->terminator()->GetSingleWordInOperand(0);
93
94 // Get the id of the boolean value that will be used as the branch condition.
95 auto bool_id = fuzzerutil::MaybeGetBoolConstantId(ir_context,
96 message_.condition_value());
97
98 // Make a new block that unconditionally branches to the original successor
99 // block.
100 auto enclosing_function = existing_block->GetParent();
101 std::unique_ptr<opt::BasicBlock> new_block =
102 MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
103 ir_context, SpvOpLabel, 0, message_.fresh_id(),
104 opt::Instruction::OperandList()));
105 new_block->AddInstruction(MakeUnique<opt::Instruction>(
106 ir_context, SpvOpBranch, 0, 0,
107 opt::Instruction::OperandList(
108 {{SPV_OPERAND_TYPE_ID, {successor_block_id}}})));
109
110 // Turn the original block into a selection merge, with its original successor
111 // as the merge block.
112 existing_block->terminator()->InsertBefore(MakeUnique<opt::Instruction>(
113 ir_context, SpvOpSelectionMerge, 0, 0,
114 opt::Instruction::OperandList(
115 {{SPV_OPERAND_TYPE_ID, {successor_block_id}},
116 {SPV_OPERAND_TYPE_SELECTION_CONTROL,
117 {SpvSelectionControlMaskNone}}})));
118
119 // Change the original block's terminator to be a conditional branch on the
120 // given boolean, with the original successor and the new successor as branch
121 // targets, and such that at runtime control will always transfer to the
122 // original successor.
123 existing_block->terminator()->SetOpcode(SpvOpBranchConditional);
124 existing_block->terminator()->SetInOperands(
125 {{SPV_OPERAND_TYPE_ID, {bool_id}},
126 {SPV_OPERAND_TYPE_ID,
127 {message_.condition_value() ? successor_block_id
128 : message_.fresh_id()}},
129 {SPV_OPERAND_TYPE_ID,
130 {message_.condition_value() ? message_.fresh_id()
131 : successor_block_id}}});
132
133 // Add the new block to the enclosing function.
134 new_block->SetParent(enclosing_function);
135 enclosing_function->InsertBasicBlockAfter(std::move(new_block),
136 existing_block);
137
138 // Record the fact that the new block is dead.
139 transformation_context->GetFactManager()->AddFactBlockIsDead(
140 message_.fresh_id());
141
142 // Fix up OpPhi instructions in the successor block, so that the values they
143 // yield when control has transferred from the new block are the same as if
144 // control had transferred from |message_.existing_block|. This is guaranteed
145 // to be valid since |message_.existing_block| dominates the new block by
146 // construction. Other transformations can change these phi operands to more
147 // interesting values.
148 ir_context->cfg()
149 ->block(successor_block_id)
150 ->ForEachPhiInst([this](opt::Instruction* phi_inst) {
151 // Copy the operand that provides the phi value for the first of any
152 // existing predecessors.
153 opt::Operand copy_of_existing_operand = phi_inst->GetInOperand(0);
154 // Use this as the value associated with the new predecessor.
155 phi_inst->AddOperand(std::move(copy_of_existing_operand));
156 phi_inst->AddOperand({SPV_OPERAND_TYPE_ID, {message_.fresh_id()}});
157 });
158
159 // Do not rely on any existing analysis results since the control flow graph
160 // of the module has changed.
161 ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
162 }
163
ToMessage() const164 protobufs::Transformation TransformationAddDeadBlock::ToMessage() const {
165 protobufs::Transformation result;
166 *result.mutable_add_dead_block() = message_;
167 return result;
168 }
169
170 } // namespace fuzz
171 } // namespace spvtools
172