1 // Copyright (c) 2020 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 "transformation_add_loop_preheader.h"
16 
17 #include "source/fuzz/fuzzer_util.h"
18 #include "source/opt/instruction.h"
19 
20 namespace spvtools {
21 namespace fuzz {
TransformationAddLoopPreheader(const protobufs::TransformationAddLoopPreheader & message)22 TransformationAddLoopPreheader::TransformationAddLoopPreheader(
23     const protobufs::TransformationAddLoopPreheader& message)
24     : message_(message) {}
25 
TransformationAddLoopPreheader(uint32_t loop_header_block,uint32_t fresh_id,std::vector<uint32_t> phi_id)26 TransformationAddLoopPreheader::TransformationAddLoopPreheader(
27     uint32_t loop_header_block, uint32_t fresh_id,
28     std::vector<uint32_t> phi_id) {
29   message_.set_loop_header_block(loop_header_block);
30   message_.set_fresh_id(fresh_id);
31   for (auto id : phi_id) {
32     message_.add_phi_id(id);
33   }
34 }
35 
IsApplicable(opt::IRContext * ir_context,const TransformationContext &) const36 bool TransformationAddLoopPreheader::IsApplicable(
37     opt::IRContext* ir_context,
38     const TransformationContext& /* unused */) const {
39   // |message_.loop_header_block()| must be the id of a loop header block.
40   opt::BasicBlock* loop_header_block =
41       fuzzerutil::MaybeFindBlock(ir_context, message_.loop_header_block());
42   if (!loop_header_block || !loop_header_block->IsLoopHeader()) {
43     return false;
44   }
45 
46   // The id for the preheader must actually be fresh.
47   std::set<uint32_t> used_ids;
48   if (!CheckIdIsFreshAndNotUsedByThisTransformation(message_.fresh_id(),
49                                                     ir_context, &used_ids)) {
50     return false;
51   }
52 
53   size_t num_predecessors =
54       ir_context->cfg()->preds(message_.loop_header_block()).size();
55 
56   // The block must have at least 2 predecessors (the back-edge block and
57   // another predecessor outside of the loop)
58   if (num_predecessors < 2) {
59     return false;
60   }
61 
62   // If the block only has one predecessor outside of the loop (and thus 2 in
63   // total), then no additional fresh ids are necessary.
64   if (num_predecessors == 2) {
65     return true;
66   }
67 
68   // Count the number of OpPhi instructions.
69   int32_t num_phi_insts = 0;
70   loop_header_block->ForEachPhiInst(
71       [&num_phi_insts](opt::Instruction* /* unused */) { num_phi_insts++; });
72 
73   // There must be enough fresh ids for the OpPhi instructions.
74   if (num_phi_insts > message_.phi_id_size()) {
75     return false;
76   }
77 
78   // Check that the needed ids are fresh and distinct.
79   for (int32_t i = 0; i < num_phi_insts; i++) {
80     if (!CheckIdIsFreshAndNotUsedByThisTransformation(message_.phi_id(i),
81                                                       ir_context, &used_ids)) {
82       return false;
83     }
84   }
85 
86   return true;
87 }
88 
Apply(opt::IRContext * ir_context,TransformationContext *) const89 void TransformationAddLoopPreheader::Apply(
90     opt::IRContext* ir_context,
91     TransformationContext* /* transformation_context */) const {
92   // Find the loop header.
93   opt::BasicBlock* loop_header =
94       fuzzerutil::MaybeFindBlock(ir_context, message_.loop_header_block());
95 
96   auto dominator_analysis =
97       ir_context->GetDominatorAnalysis(loop_header->GetParent());
98 
99   uint32_t back_edge_block_id = 0;
100 
101   // Update the branching instructions of the out-of-loop predecessors of the
102   // header. Set |back_edge_block_id| to be the id of the back-edge block.
103   ir_context->get_def_use_mgr()->ForEachUse(
104       loop_header->id(),
105       [this, &ir_context, &dominator_analysis, &loop_header,
106        &back_edge_block_id](opt::Instruction* use_inst, uint32_t use_index) {
107         if (dominator_analysis->Dominates(loop_header->GetLabelInst(),
108                                           use_inst)) {
109           // If |use_inst| is a branch instruction dominated by the header, the
110           // block containing it is the back-edge block.
111           if (use_inst->IsBranch()) {
112             assert(back_edge_block_id == 0 &&
113                    "There should only be one back-edge block");
114             back_edge_block_id = ir_context->get_instr_block(use_inst)->id();
115           }
116           // References to the header inside the loop should not be updated
117           return;
118         }
119 
120         // If |use_inst| is not a branch or merge instruction, it should not be
121         // changed.
122         if (!use_inst->IsBranch() &&
123             use_inst->opcode() != SpvOpSelectionMerge &&
124             use_inst->opcode() != SpvOpLoopMerge) {
125           return;
126         }
127 
128         // Update the reference.
129         use_inst->SetOperand(use_index, {message_.fresh_id()});
130       });
131 
132   assert(back_edge_block_id && "The back-edge block should have been found");
133 
134   // Make a new block for the preheader.
135   std::unique_ptr<opt::BasicBlock> preheader = MakeUnique<opt::BasicBlock>(
136       std::unique_ptr<opt::Instruction>(new opt::Instruction(
137           ir_context, SpvOpLabel, 0, message_.fresh_id(), {})));
138 
139   uint32_t phi_ids_used = 0;
140 
141   // Update the OpPhi instructions and, if there is more than one out-of-loop
142   // predecessor, add necessary OpPhi instructions so the preheader.
143   loop_header->ForEachPhiInst([this, &ir_context, &preheader,
144                                &back_edge_block_id,
145                                &phi_ids_used](opt::Instruction* phi_inst) {
146     // The loop header must have at least 2 incoming edges (the back edge, and
147     // at least one from outside the loop).
148     assert(phi_inst->NumInOperands() >= 4);
149 
150     if (phi_inst->NumInOperands() == 4) {
151       // There is just one out-of-loop predecessor, so no additional
152       // instructions in the preheader are necessary. The reference to the
153       // original out-of-loop predecessor needs to be updated so that it refers
154       // to the preheader.
155       uint32_t index_of_out_of_loop_pred_id =
156           phi_inst->GetInOperand(1).words[0] == back_edge_block_id ? 3 : 1;
157       phi_inst->SetInOperand(index_of_out_of_loop_pred_id, {preheader->id()});
158     } else {
159       // There is more than one out-of-loop predecessor, so an OpPhi instruction
160       // needs to be added to the preheader, and its value will depend on all
161       // the current out-of-loop predecessors of the header.
162 
163       // Get the operand list and the value corresponding to the back-edge
164       // block.
165       std::vector<opt::Operand> preheader_in_operands;
166       uint32_t back_edge_val = 0;
167 
168       for (uint32_t i = 0; i < phi_inst->NumInOperands(); i += 2) {
169         // Only add operands if they don't refer to the back-edge block.
170         if (phi_inst->GetInOperand(i + 1).words[0] == back_edge_block_id) {
171           back_edge_val = phi_inst->GetInOperand(i).words[0];
172         } else {
173           preheader_in_operands.push_back(std::move(phi_inst->GetInOperand(i)));
174           preheader_in_operands.push_back(
175               std::move(phi_inst->GetInOperand(i + 1)));
176         }
177       }
178 
179       // Add the new instruction to the preheader.
180       uint32_t fresh_phi_id = message_.phi_id(phi_ids_used++);
181 
182       // Update id bound.
183       fuzzerutil::UpdateModuleIdBound(ir_context, fresh_phi_id);
184 
185       preheader->AddInstruction(std::unique_ptr<opt::Instruction>(
186           new opt::Instruction(ir_context, SpvOpPhi, phi_inst->type_id(),
187                                fresh_phi_id, preheader_in_operands)));
188 
189       // Update the OpPhi instruction in the header so that it refers to the
190       // back edge block and the preheader as the predecessors, and it uses the
191       // newly-defined OpPhi in the preheader for the corresponding value.
192       phi_inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {fresh_phi_id}},
193                                {SPV_OPERAND_TYPE_ID, {preheader->id()}},
194                                {SPV_OPERAND_TYPE_ID, {back_edge_val}},
195                                {SPV_OPERAND_TYPE_ID, {back_edge_block_id}}});
196     }
197   });
198 
199   // Update id bound.
200   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
201 
202   // Add an unconditional branch from the preheader to the header.
203   preheader->AddInstruction(
204       std::unique_ptr<opt::Instruction>(new opt::Instruction(
205           ir_context, SpvOpBranch, 0, 0,
206           std::initializer_list<opt::Operand>{opt::Operand(
207               spv_operand_type_t::SPV_OPERAND_TYPE_ID, {loop_header->id()})})));
208 
209   // Insert the preheader in the module.
210   loop_header->GetParent()->InsertBasicBlockBefore(std::move(preheader),
211                                                    loop_header);
212 
213   // Invalidate analyses because the structure of the program changed.
214   ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
215 }
216 
ToMessage() const217 protobufs::Transformation TransformationAddLoopPreheader::ToMessage() const {
218   protobufs::Transformation result;
219   *result.mutable_add_loop_preheader() = message_;
220   return result;
221 }
222 
GetFreshIds() const223 std::unordered_set<uint32_t> TransformationAddLoopPreheader::GetFreshIds()
224     const {
225   std::unordered_set<uint32_t> result = {message_.fresh_id()};
226   for (auto id : message_.phi_id()) {
227     result.insert(id);
228   }
229   return result;
230 }
231 
232 }  // namespace fuzz
233 }  // namespace spvtools
234