1 // Copyright (c) 2018 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/reduce/structured_loop_to_selection_reduction_opportunity.h"
16
17 #include "source/opt/aggressive_dead_code_elim_pass.h"
18 #include "source/opt/ir_context.h"
19 #include "source/reduce/reduction_util.h"
20
21 namespace spvtools {
22 namespace reduce {
23
24 namespace {
25 const uint32_t kMergeNodeIndex = 0;
26 } // namespace
27
PreconditionHolds()28 bool StructuredLoopToSelectionReductionOpportunity::PreconditionHolds() {
29 // Is the loop header reachable?
30 return loop_construct_header_->GetLabel()->context()->IsReachable(
31 *loop_construct_header_);
32 }
33
Apply()34 void StructuredLoopToSelectionReductionOpportunity::Apply() {
35 // Force computation of dominator analysis, CFG and structured CFG analysis
36 // before we start to mess with edges in the function.
37 context_->GetDominatorAnalysis(loop_construct_header_->GetParent());
38 context_->cfg();
39 context_->GetStructuredCFGAnalysis();
40
41 // (1) Redirect edges that point to the loop's continue target to their
42 // closest merge block.
43 RedirectToClosestMergeBlock(loop_construct_header_->ContinueBlockId());
44
45 // (2) Redirect edges that point to the loop's merge block to their closest
46 // merge block (which might be that of an enclosing selection, for instance).
47 RedirectToClosestMergeBlock(loop_construct_header_->MergeBlockId());
48
49 // (3) Turn the loop construct header into a selection.
50 ChangeLoopToSelection();
51
52 // We have made control flow changes that do not preserve the analyses that
53 // were performed.
54 context_->InvalidateAnalysesExceptFor(
55 opt::IRContext::Analysis::kAnalysisNone);
56
57 // (4) By changing CFG edges we may have created scenarios where ids are used
58 // without being dominated; we fix instances of this.
59 FixNonDominatedIdUses();
60
61 // Invalidate the analyses we just used.
62 context_->InvalidateAnalysesExceptFor(
63 opt::IRContext::Analysis::kAnalysisNone);
64 }
65
RedirectToClosestMergeBlock(uint32_t original_target_id)66 void StructuredLoopToSelectionReductionOpportunity::RedirectToClosestMergeBlock(
67 uint32_t original_target_id) {
68 // Consider every predecessor of the node with respect to which edges should
69 // be redirected.
70 std::set<uint32_t> already_seen;
71 for (auto pred : context_->cfg()->preds(original_target_id)) {
72 if (already_seen.find(pred) != already_seen.end()) {
73 // We have already handled this predecessor (this scenario can arise if
74 // there are multiple edges from a block b to original_target_id).
75 continue;
76 }
77 already_seen.insert(pred);
78
79 if (!context_->IsReachable(*context_->cfg()->block(pred))) {
80 // We do not care about unreachable predecessors (and dominance
81 // information, and thus the notion of structured control flow, makes
82 // little sense for unreachable blocks).
83 continue;
84 }
85 // Find the merge block of the structured control construct that most
86 // tightly encloses the predecessor.
87 uint32_t new_merge_target;
88 // The structured CFG analysis deliberately does not regard a header as
89 // belonging to the structure that it heads. We want it to, so handle this
90 // case specially.
91 if (context_->cfg()->block(pred)->MergeBlockIdIfAny()) {
92 new_merge_target = context_->cfg()->block(pred)->MergeBlockIdIfAny();
93 } else {
94 new_merge_target = context_->GetStructuredCFGAnalysis()->MergeBlock(pred);
95 }
96 assert(new_merge_target != pred);
97
98 if (!new_merge_target) {
99 // If the loop being transformed is outermost, and the predecessor is
100 // part of that loop's continue construct, there will be no such
101 // enclosing control construct. In this case, the continue construct
102 // will become unreachable anyway, so it is fine not to redirect the
103 // edge.
104 continue;
105 }
106
107 if (new_merge_target != original_target_id) {
108 // Redirect the edge if it doesn't already point to the desired block.
109 RedirectEdge(pred, original_target_id, new_merge_target);
110 }
111 }
112 }
113
RedirectEdge(uint32_t source_id,uint32_t original_target_id,uint32_t new_target_id)114 void StructuredLoopToSelectionReductionOpportunity::RedirectEdge(
115 uint32_t source_id, uint32_t original_target_id, uint32_t new_target_id) {
116 // Redirect edge source_id->original_target_id to edge
117 // source_id->new_target_id, where the blocks involved are all different.
118 assert(source_id != original_target_id);
119 assert(source_id != new_target_id);
120 assert(original_target_id != new_target_id);
121
122 // original_target_id must either be the merge target or continue construct
123 // for the loop being operated on.
124 assert(original_target_id == loop_construct_header_->MergeBlockId() ||
125 original_target_id == loop_construct_header_->ContinueBlockId());
126
127 auto terminator = context_->cfg()->block(source_id)->terminator();
128
129 // Figure out which operands of the terminator need to be considered for
130 // redirection.
131 std::vector<uint32_t> operand_indices;
132 if (terminator->opcode() == SpvOpBranch) {
133 operand_indices = {0};
134 } else if (terminator->opcode() == SpvOpBranchConditional) {
135 operand_indices = {1, 2};
136 } else {
137 assert(terminator->opcode() == SpvOpSwitch);
138 for (uint32_t label_index = 1; label_index < terminator->NumOperands();
139 label_index += 2) {
140 operand_indices.push_back(label_index);
141 }
142 }
143
144 // Redirect the relevant operands, asserting that at least one redirection is
145 // made.
146 bool redirected = false;
147 for (auto operand_index : operand_indices) {
148 if (terminator->GetSingleWordOperand(operand_index) == original_target_id) {
149 terminator->SetOperand(operand_index, {new_target_id});
150 redirected = true;
151 }
152 }
153 (void)(redirected);
154 assert(redirected);
155
156 // The old and new targets may have phi instructions; these will need to
157 // respect the change in edges.
158 AdaptPhiInstructionsForRemovedEdge(
159 source_id, context_->cfg()->block(original_target_id));
160 AdaptPhiInstructionsForAddedEdge(source_id,
161 context_->cfg()->block(new_target_id));
162 }
163
164 void StructuredLoopToSelectionReductionOpportunity::
AdaptPhiInstructionsForAddedEdge(uint32_t from_id,opt::BasicBlock * to_block)165 AdaptPhiInstructionsForAddedEdge(uint32_t from_id,
166 opt::BasicBlock* to_block) {
167 to_block->ForEachPhiInst([this, &from_id](opt::Instruction* phi_inst) {
168 // Add to the phi operand an (undef, from_id) pair to reflect the added
169 // edge.
170 auto undef_id = FindOrCreateGlobalUndef(context_, phi_inst->type_id());
171 phi_inst->AddOperand(opt::Operand(SPV_OPERAND_TYPE_ID, {undef_id}));
172 phi_inst->AddOperand(opt::Operand(SPV_OPERAND_TYPE_ID, {from_id}));
173 });
174 }
175
ChangeLoopToSelection()176 void StructuredLoopToSelectionReductionOpportunity::ChangeLoopToSelection() {
177 // Change the merge instruction from OpLoopMerge to OpSelectionMerge, with
178 // the same merge block.
179 auto loop_merge_inst = loop_construct_header_->GetLoopMergeInst();
180 auto const loop_merge_block_id =
181 loop_merge_inst->GetSingleWordOperand(kMergeNodeIndex);
182 loop_merge_inst->SetOpcode(SpvOpSelectionMerge);
183 loop_merge_inst->ReplaceOperands(
184 {{loop_merge_inst->GetOperand(kMergeNodeIndex).type,
185 {loop_merge_block_id}},
186 {SPV_OPERAND_TYPE_SELECTION_CONTROL, {SpvSelectionControlMaskNone}}});
187
188 // The loop header either finishes with OpBranch or OpBranchConditional.
189 // The latter is fine for a selection. In the former case we need to turn
190 // it into OpBranchConditional. We use "true" as the condition, and make
191 // the "else" branch be the merge block.
192 auto terminator = loop_construct_header_->terminator();
193 if (terminator->opcode() == SpvOpBranch) {
194 opt::analysis::Bool temp;
195 const opt::analysis::Bool* bool_type =
196 context_->get_type_mgr()->GetRegisteredType(&temp)->AsBool();
197 auto const_mgr = context_->get_constant_mgr();
198 auto true_const = const_mgr->GetConstant(bool_type, {1});
199 auto true_const_result_id =
200 const_mgr->GetDefiningInstruction(true_const)->result_id();
201 auto original_branch_id = terminator->GetSingleWordOperand(0);
202 terminator->SetOpcode(SpvOpBranchConditional);
203 terminator->ReplaceOperands({{SPV_OPERAND_TYPE_ID, {true_const_result_id}},
204 {SPV_OPERAND_TYPE_ID, {original_branch_id}},
205 {SPV_OPERAND_TYPE_ID, {loop_merge_block_id}}});
206 if (original_branch_id != loop_merge_block_id) {
207 AdaptPhiInstructionsForAddedEdge(
208 loop_construct_header_->id(),
209 context_->cfg()->block(loop_merge_block_id));
210 }
211 }
212 }
213
FixNonDominatedIdUses()214 void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() {
215 // Consider each instruction in the function.
216 for (auto& block : *loop_construct_header_->GetParent()) {
217 for (auto& def : block) {
218 if (def.opcode() == SpvOpVariable) {
219 // Variables are defined at the start of the function, and can be
220 // accessed by all blocks, even by unreachable blocks that have no
221 // dominators, so we do not need to worry about them.
222 continue;
223 }
224 context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def](
225 opt::Instruction* use,
226 uint32_t index) {
227 // Ignore uses outside of blocks, such as in OpDecorate.
228 if (context_->get_instr_block(use) == nullptr) {
229 return;
230 }
231 // If a use is not appropriately dominated by its definition,
232 // replace the use with an OpUndef, unless the definition is an
233 // access chain, in which case replace it with some (possibly fresh)
234 // variable (as we cannot load from / store to OpUndef).
235 if (!DefinitionSufficientlyDominatesUse(&def, use, index, block)) {
236 if (def.opcode() == SpvOpAccessChain) {
237 auto pointer_type =
238 context_->get_type_mgr()->GetType(def.type_id())->AsPointer();
239 switch (pointer_type->storage_class()) {
240 case SpvStorageClassFunction:
241 use->SetOperand(
242 index, {FindOrCreateFunctionVariable(
243 context_, loop_construct_header_->GetParent(),
244 context_->get_type_mgr()->GetId(pointer_type))});
245 break;
246 default:
247 // TODO(2183) Need to think carefully about whether it makes
248 // sense to add new variables for all storage classes; it's
249 // fine for Private but might not be OK for input/output
250 // storage classes for example.
251 use->SetOperand(
252 index, {FindOrCreateGlobalVariable(
253 context_,
254 context_->get_type_mgr()->GetId(pointer_type))});
255 break;
256 break;
257 }
258 } else {
259 use->SetOperand(index,
260 {FindOrCreateGlobalUndef(context_, def.type_id())});
261 }
262 }
263 });
264 }
265 }
266 }
267
268 bool StructuredLoopToSelectionReductionOpportunity::
DefinitionSufficientlyDominatesUse(opt::Instruction * def,opt::Instruction * use,uint32_t use_index,opt::BasicBlock & def_block)269 DefinitionSufficientlyDominatesUse(opt::Instruction* def,
270 opt::Instruction* use,
271 uint32_t use_index,
272 opt::BasicBlock& def_block) {
273 if (use->opcode() == SpvOpPhi) {
274 // A use in a phi doesn't need to be dominated by its definition, but the
275 // associated parent block does need to be dominated by the definition.
276 return context_->GetDominatorAnalysis(loop_construct_header_->GetParent())
277 ->Dominates(def_block.id(), use->GetSingleWordOperand(use_index + 1));
278 }
279 // In non-phi cases, a use needs to be dominated by its definition.
280 return context_->GetDominatorAnalysis(loop_construct_header_->GetParent())
281 ->Dominates(def, use);
282 }
283
284 } // namespace reduce
285 } // namespace spvtools
286