1 /*
2 * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
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
16 #include "optimizer/analysis/alias_analysis.h"
17 #include "optimizer/optimizations/branch_elimination.h"
18 #include "compiler_logger.h"
19 #include "optimizer/analysis/bounds_analysis.h"
20 #include "optimizer/analysis/dominators_tree.h"
21 #include "optimizer/analysis/rpo.h"
22 #include "optimizer/analysis/loop_analyzer.h"
23 #include "optimizer/ir/basicblock.h"
24 #include "optimizer/ir/inst.h"
25 #include "optimizer/ir/analysis.h"
26
27 namespace panda::compiler {
28 /**
29 * Branch Elimination optimization finds `if-true` blocks with resolvable conditional instruction.
30 * Condition can be resolved in the following ways:
31 * - Condition is constant;
32 * - Condition is dominated by the equal one condition with the same inputs and the only one successor of the dominant
33 * reaches dominated condition
34 */
BranchElimination(Graph * graph)35 BranchElimination::BranchElimination(Graph *graph)
36 : Optimization(graph),
37 sameInputCompares_(graph->GetLocalAllocator()->Adapter()),
38 sameInputCompareAnyType_(graph->GetLocalAllocator()->Adapter())
39 {
40 }
41
RunImpl()42 bool BranchElimination::RunImpl()
43 {
44 GetGraph()->RunPass<DominatorsTree>();
45 isApplied_ = false;
46 sameInputCompares_.clear();
47 auto markerHolder = MarkerHolder(GetGraph());
48 rmBlockMarker_ = markerHolder.GetMarker();
49 for (auto block : GetGraph()->GetBlocksRPO()) {
50 if (block->IsEmpty() || (block->IsTry() && GetGraph()->IsBytecodeOptimizer())) {
51 continue;
52 }
53 if (block->GetLastInst()->GetOpcode() == Opcode::IfImm) {
54 if (SkipForOsr(block)) {
55 COMPILER_LOG(DEBUG, BRANCH_ELIM) << "Skip for OSR, id = " << block->GetId();
56 continue;
57 }
58 /* skip branch() elimination at the end of the
59 * preheader until LoopUnrolling pass is done */
60 if (!GetGraph()->IsBytecodeOptimizer() && g_options.IsCompilerDeferPreheaderTransform() &&
61 !GetGraph()->IsUnrollComplete() && block->IsLoopValid() && block->IsLoopPreHeader()) {
62 continue;
63 }
64 VisitBlock(block);
65 }
66 }
67 DisconnectBlocks();
68
69 if (isApplied_ && GetGraph()->IsOsrMode()) {
70 CleanupGraphSaveStateOSR(GetGraph());
71 }
72 COMPILER_LOG(DEBUG, BRANCH_ELIM) << "Branch elimination complete";
73 return isApplied_;
74 }
75
SkipForOsr(const BasicBlock * block)76 bool BranchElimination::SkipForOsr(const BasicBlock *block)
77 {
78 if (!block->IsLoopValid()) {
79 return false;
80 }
81 auto loop = block->GetLoop();
82 if (loop->IsRoot() || !loop->GetHeader()->IsOsrEntry()) {
83 return false;
84 }
85 if (loop->GetBackEdges().size() > 1) {
86 return false;
87 }
88 return block->IsDominate(loop->GetBackEdges()[0]);
89 }
90
InvalidateAnalyses()91 void BranchElimination::InvalidateAnalyses()
92 {
93 GetGraph()->InvalidateAnalysis<BoundsAnalysis>();
94 GetGraph()->InvalidateAnalysis<AliasAnalysis>();
95 // Before in "CleanupGraphSaveStateOSR" already run "LoopAnalyzer"
96 // in case (is_applied_ && GetGraph()->IsOsrMode())
97 if (!(isApplied_ && GetGraph()->IsOsrMode())) {
98 GetGraph()->InvalidateAnalysis<LoopAnalyzer>();
99 }
100 InvalidateBlocksOrderAnalyzes(GetGraph());
101 }
102
BranchEliminationConst(BasicBlock * ifBlock)103 void BranchElimination::BranchEliminationConst(BasicBlock *ifBlock)
104 {
105 auto ifImm = ifBlock->GetLastInst()->CastToIfImm();
106 auto conditionInst = ifBlock->GetLastInst()->GetInput(0).GetInst();
107 COMPILER_LOG(DEBUG, BRANCH_ELIM) << "Block with constant if instruction input is visited, id = "
108 << ifBlock->GetId();
109
110 uint64_t constValue = conditionInst->CastToConstant()->GetIntValue();
111 bool condResult = (constValue == ifImm->GetImm());
112 if (ifImm->GetCc() == CC_NE) {
113 condResult = !condResult;
114 } else {
115 ASSERT(ifImm->GetCc() == CC_EQ);
116 }
117 auto eliminatedSuccessor = ifBlock->GetFalseSuccessor();
118 if (!condResult) {
119 eliminatedSuccessor = ifBlock->GetTrueSuccessor();
120 }
121 EliminateBranch(ifBlock, eliminatedSuccessor);
122 GetGraph()->GetEventWriter().EventBranchElimination(ifBlock->GetId(), ifBlock->GetGuestPc(), conditionInst->GetId(),
123 conditionInst->GetPc(), "const-condition",
124 eliminatedSuccessor == ifBlock->GetTrueSuccessor());
125 }
126
BranchEliminationCompare(BasicBlock * ifBlock)127 void BranchElimination::BranchEliminationCompare(BasicBlock *ifBlock)
128 {
129 auto ifImm = ifBlock->GetLastInst()->CastToIfImm();
130 auto conditionInst = ifBlock->GetLastInst()->GetInput(0).GetInst();
131 if (auto result = GetConditionResult(conditionInst)) {
132 COMPILER_LOG(DEBUG, BRANCH_ELIM) << "Compare instruction result was resolved. Instruction id = "
133 << conditionInst->GetId()
134 << ", resolved result: " << (result.value() ? "true" : "false");
135 auto eliminatedSuccessor = ifImm->GetEdgeIfInputFalse();
136 if (!result.value()) {
137 eliminatedSuccessor = ifImm->GetEdgeIfInputTrue();
138 }
139 EliminateBranch(ifBlock, eliminatedSuccessor);
140 GetGraph()->GetEventWriter().EventBranchElimination(
141 ifBlock->GetId(), ifBlock->GetGuestPc(), conditionInst->GetId(), conditionInst->GetPc(), "dominant-if",
142 eliminatedSuccessor == ifBlock->GetTrueSuccessor());
143 } else {
144 ConditionOps ops {conditionInst->GetInput(0).GetInst(), conditionInst->GetInput(1).GetInst()};
145 auto it = sameInputCompares_.try_emplace(ops, GetGraph()->GetLocalAllocator()->Adapter());
146 it.first->second.push_back(conditionInst);
147 }
148 }
149
BranchEliminationCompareAnyType(BasicBlock * ifBlock)150 void BranchElimination::BranchEliminationCompareAnyType(BasicBlock *ifBlock)
151 {
152 auto ifImm = ifBlock->GetLastInst()->CastToIfImm();
153 auto compareAny = ifBlock->GetLastInst()->GetInput(0).GetInst()->CastToCompareAnyType();
154 if (auto result = GetCompareAnyTypeResult(ifImm)) {
155 COMPILER_LOG(DEBUG, BRANCH_ELIM) << "CompareAnyType instruction result was resolved. Instruction id = "
156 << compareAny->GetId()
157 << ", resolved result: " << (result.value() ? "true" : "false");
158 auto eliminatedSuccessor = ifImm->GetEdgeIfInputFalse();
159 if (!result.value()) {
160 eliminatedSuccessor = ifImm->GetEdgeIfInputTrue();
161 }
162 EliminateBranch(ifBlock, eliminatedSuccessor);
163 GetGraph()->GetEventWriter().EventBranchElimination(ifBlock->GetId(), ifBlock->GetGuestPc(),
164 compareAny->GetId(), compareAny->GetPc(), "dominant-if",
165 eliminatedSuccessor == ifBlock->GetTrueSuccessor());
166 return;
167 }
168
169 Inst *input = compareAny->GetInput(0).GetInst();
170 auto it = sameInputCompareAnyType_.try_emplace(input, GetGraph()->GetLocalAllocator()->Adapter());
171 it.first->second.push_back(compareAny);
172 }
173
174 /**
175 * Select unreachable successor and run elimination process
176 * @param blocks - list of blocks with constant `if` instruction input
177 */
VisitBlock(BasicBlock * ifBlock)178 void BranchElimination::VisitBlock(BasicBlock *ifBlock)
179 {
180 ASSERT(ifBlock != nullptr);
181 ASSERT(ifBlock->GetGraph() == GetGraph());
182 ASSERT(ifBlock->GetLastInst()->GetOpcode() == Opcode::IfImm);
183 ASSERT(ifBlock->GetSuccsBlocks().size() == MAX_SUCCS_NUM);
184
185 auto conditionInst = ifBlock->GetLastInst()->GetInput(0).GetInst();
186 switch (conditionInst->GetOpcode()) {
187 case Opcode::Constant:
188 BranchEliminationConst(ifBlock);
189 break;
190 case Opcode::Compare:
191 BranchEliminationCompare(ifBlock);
192 break;
193 case Opcode::CompareAnyType:
194 BranchEliminationCompareAnyType(ifBlock);
195 break;
196 default:
197 break;
198 }
199 }
200
201 /**
202 * If `eliminated_block` has no other predecessor than `if_block`, mark `eliminated_block` to be disconnected
203 * If `eliminated_block` dominates all predecessors, exclude `if_block`, mark `eliminated_block` to be disconneted
204 * Else remove edge between these blocks
205 * @param if_block - block with constant 'if' instruction input
206 * @param eliminated_block - unreachable form `if_block`
207 */
EliminateBranch(BasicBlock * ifBlock,BasicBlock * eliminatedBlock)208 void BranchElimination::EliminateBranch(BasicBlock *ifBlock, BasicBlock *eliminatedBlock)
209 {
210 ASSERT(ifBlock != nullptr && ifBlock->GetGraph() == GetGraph());
211 ASSERT(eliminatedBlock != nullptr && eliminatedBlock->GetGraph() == GetGraph());
212 // find predecessor which is not dominated by `eliminated_block`
213 auto preds = eliminatedBlock->GetPredsBlocks();
214 auto it = std::find_if(preds.begin(), preds.end(), [ifBlock, eliminatedBlock](BasicBlock *pred) {
215 return pred != ifBlock && !eliminatedBlock->IsDominate(pred);
216 });
217 bool dominatesAllPreds = (it == preds.cend());
218 if (preds.size() > 1 && !dominatesAllPreds) {
219 RemovePredecessorUpdateDF(eliminatedBlock, ifBlock);
220 ifBlock->RemoveSucc(eliminatedBlock);
221 ifBlock->RemoveInst(ifBlock->GetLastInst());
222 GetGraph()->GetAnalysis<Rpo>().SetValid(true);
223 // NOTE (a.popov) DominatorsTree could be restored inplace
224 GetGraph()->RunPass<DominatorsTree>();
225 } else {
226 eliminatedBlock->SetMarker(rmBlockMarker_);
227 }
228 isApplied_ = true;
229 }
230
231 /**
232 * Before disconnecting the `block` find and disconnect all its successors dominated by it.
233 * Use DFS to disconnect blocks in the PRO
234 * @param block - unreachable block to disconnect from the graph
235 */
MarkUnreachableBlocks(BasicBlock * block)236 void BranchElimination::MarkUnreachableBlocks(BasicBlock *block)
237 {
238 for (auto dom : block->GetDominatedBlocks()) {
239 dom->SetMarker(rmBlockMarker_);
240 MarkUnreachableBlocks(dom);
241 }
242 }
243
AllPredecessorsMarked(BasicBlock * block,Marker marker)244 bool AllPredecessorsMarked(BasicBlock *block, Marker marker)
245 {
246 if (block->GetPredsBlocks().empty()) {
247 ASSERT(block->IsStartBlock());
248 return false;
249 }
250
251 for (auto pred : block->GetPredsBlocks()) {
252 if (!pred->IsMarked(marker)) {
253 return false;
254 }
255 }
256 block->SetMarker(marker);
257 return true;
258 }
259
260 /// Disconnect selected blocks
DisconnectBlocks()261 void BranchElimination::DisconnectBlocks()
262 {
263 for (auto block : GetGraph()->GetBlocksRPO()) {
264 if (block->IsMarked(rmBlockMarker_) || AllPredecessorsMarked(block, rmBlockMarker_)) {
265 MarkUnreachableBlocks(block);
266 }
267 }
268
269 const auto &rpoBlocks = GetGraph()->GetBlocksRPO();
270 for (auto it = rpoBlocks.rbegin(); it != rpoBlocks.rend(); it++) {
271 auto block = *it;
272 if (block != nullptr && block->IsMarked(rmBlockMarker_)) {
273 GetGraph()->DisconnectBlock(block);
274 COMPILER_LOG(DEBUG, BRANCH_ELIM) << "Block was disconnected, id = " << block->GetId();
275 }
276 }
277 }
278
279 /**
280 * Check if the `target_block` is reachable from one successor only of the `dominant_block`
281 *
282 * If `target_block` is dominated by one of the successors, need to check that `target_block`
283 * is NOT reachable by the other successor
284 */
BlockIsReachedFromOnlySuccessor(BasicBlock * targetBlock,BasicBlock * dominantBlock)285 bool BlockIsReachedFromOnlySuccessor(BasicBlock *targetBlock, BasicBlock *dominantBlock)
286 {
287 ASSERT(dominantBlock->IsDominate(targetBlock));
288 BasicBlock *otherSuccesor = nullptr;
289 if (dominantBlock->GetTrueSuccessor()->IsDominate(targetBlock)) {
290 otherSuccesor = dominantBlock->GetFalseSuccessor();
291 } else if (dominantBlock->GetFalseSuccessor()->IsDominate(targetBlock)) {
292 otherSuccesor = dominantBlock->GetTrueSuccessor();
293 } else {
294 return false;
295 }
296
297 auto markerHolder = MarkerHolder(targetBlock->GetGraph());
298 if (BlocksPathDfsSearch(markerHolder.GetMarker(), otherSuccesor, targetBlock)) {
299 return false;
300 }
301 ASSERT(!otherSuccesor->IsDominate(targetBlock));
302 return true;
303 }
304
305 /**
306 * NOTE (a.popov) Here can be supported more complex case:
307 * when `dom_compare` has 2 or more `if_imm` users and `target_compare` is reachable from the same successors of these
308 * if_imms
309 */
FindIfImmDominatesCondition(Inst * domCompare,Inst * targetCompare)310 Inst *FindIfImmDominatesCondition(Inst *domCompare, Inst *targetCompare)
311 {
312 for (auto &user : domCompare->GetUsers()) {
313 auto inst = user.GetInst();
314 if (inst->GetOpcode() == Opcode::IfImm && inst->IsDominate(targetCompare)) {
315 return inst;
316 }
317 }
318 return nullptr;
319 }
320
321 /// Resolve condition result if there is a dominant equal condition
GetConditionResult(Inst * condition)322 std::optional<bool> BranchElimination::GetConditionResult(Inst *condition)
323 {
324 ConditionOps ops {condition->GetInput(0).GetInst(), condition->GetInput(1).GetInst()};
325 if (sameInputCompares_.count(ops) <= 0) {
326 return std::nullopt;
327 }
328 auto instructions = sameInputCompares_.at(ops);
329 ASSERT(!instructions.empty());
330 for (auto domCond : instructions) {
331 // Find dom_cond's if_imm, that dominates target condition
332 auto ifImm = FindIfImmDominatesCondition(domCond, condition);
333 if (ifImm == nullptr) {
334 continue;
335 }
336 if (BlockIsReachedFromOnlySuccessor(condition->GetBasicBlock(), ifImm->GetBasicBlock())) {
337 if (auto result = TryResolveResult(condition, domCond, ifImm->CastToIfImm())) {
338 COMPILER_LOG(DEBUG, BRANCH_ELIM)
339 << "Equal compare instructions were found. Dominant id = " << domCond->GetId()
340 << ", dominated id = " << condition->GetId();
341 return result;
342 }
343 }
344 }
345 return std::nullopt;
346 }
347
348 /// Resolve condition result if there is a dominant IfImmInst after CompareAnyTypeInst.
GetCompareAnyTypeResult(IfImmInst * ifImm)349 std::optional<bool> BranchElimination::GetCompareAnyTypeResult(IfImmInst *ifImm)
350 {
351 auto compareAny = ifImm->GetInput(0).GetInst()->CastToCompareAnyType();
352 Inst *input = compareAny->GetInput(0).GetInst();
353 const auto it = sameInputCompareAnyType_.find(input);
354 if (it == sameInputCompareAnyType_.end()) {
355 return std::nullopt;
356 }
357
358 const ArenaVector<CompareAnyTypeInst *> &instructions = it->second;
359 ASSERT(!instructions.empty());
360 for (const auto domCompareAny : instructions) {
361 // Find dom_cond's if_imm, that dominates target condition.
362 auto ifImmDomBlock = FindIfImmDominatesCondition(domCompareAny, ifImm);
363 if (ifImmDomBlock == nullptr) {
364 continue;
365 }
366
367 if (!BlockIsReachedFromOnlySuccessor(ifImm->GetBasicBlock(), ifImmDomBlock->GetBasicBlock())) {
368 continue;
369 }
370
371 auto result = TryResolveCompareAnyTypeResult(compareAny, domCompareAny, ifImmDomBlock->CastToIfImm());
372 if (!result) {
373 continue;
374 }
375
376 COMPILER_LOG(DEBUG, BRANCH_ELIM) << "Equal CompareAnyType instructions were found. Dominant id = "
377 << domCompareAny->GetId() << ", dominated id = " << compareAny->GetId();
378 return result;
379 }
380 return std::nullopt;
381 }
382
383 /**
384 * Try to resolve CompareAnyTypeInst result with the information
385 * about dominant CompareAnyTypeInst with the same inputs.
386 */
TryResolveCompareAnyTypeResult(CompareAnyTypeInst * compareAny,CompareAnyTypeInst * domCompareAny,IfImmInst * ifImmDomBlock)387 std::optional<bool> BranchElimination::TryResolveCompareAnyTypeResult(CompareAnyTypeInst *compareAny,
388 CompareAnyTypeInst *domCompareAny,
389 IfImmInst *ifImmDomBlock)
390 {
391 auto compareAnyBb = compareAny->GetBasicBlock();
392 bool isTrueDomBranch = ifImmDomBlock->GetEdgeIfInputTrue()->IsDominate(compareAnyBb);
393
394 auto graph = compareAnyBb->GetGraph();
395 auto language = graph->GetRuntime()->GetMethodSourceLanguage(graph->GetMethod());
396 auto res = IsAnyTypeCanBeSubtypeOf(language, compareAny->GetAnyType(), domCompareAny->GetAnyType());
397 if (!res) {
398 // We cannot compare types in compile-time
399 return std::nullopt;
400 }
401
402 if (res.value()) {
403 // If CompareAnyTypeInst has the same types for any, then it can be optimized in any case.
404 return isTrueDomBranch;
405 }
406
407 // If CompareAnyTypeInst has the different types for any, then it can be optimized only in true-branch.
408 if (!isTrueDomBranch) {
409 return std::nullopt;
410 }
411
412 return false;
413 }
414
415 /**
416 * Try to resolve condition result with the information about dominant condition with the same inputs
417 *
418 * - The result of the dominant condition is counted using dom-tree by checking which successor of if_imm_block
419 * (true/false) dominates the current block;
420 *
421 * - Then this result is applied to the current condition, if it is possible, using the table of condition codes
422 * relation
423 */
TryResolveResult(Inst * condition,Inst * dominantCondition,IfImmInst * ifImm)424 std::optional<bool> BranchElimination::TryResolveResult(Inst *condition, Inst *dominantCondition, IfImmInst *ifImm)
425 {
426 using std::nullopt;
427
428 // Table keeps the result of the condition in the row if the condition in the column is true
429 // Order must be same as in "ir/inst.h"
430 static constexpr std::array<std::array<std::optional<bool>, ConditionCode::CC_LAST + 1>, ConditionCode::CC_LAST + 1>
431 // clang-format off
432 COND_RELATION = {{
433 // CC_EQ CC_NE CC_LT CC_LE CC_GT CC_GE CC_B CC_BE CC_A CC_AE
434 {true, false, false, nullopt, false, nullopt, false, nullopt, false, nullopt}, // CC_EQ
435 {false, true, true, nullopt, true, nullopt, true, nullopt, true, nullopt}, // CC_NE
436 {false, nullopt, true, nullopt, false, false, nullopt, nullopt, nullopt, nullopt}, // CC_LT
437 {true, nullopt, true, true, false, nullopt, nullopt, nullopt, nullopt, nullopt}, // CC_LE
438 {false, nullopt, false, false, true, nullopt, nullopt, nullopt, nullopt, nullopt}, // CC_GT
439 {true, nullopt, false, nullopt, true, true, nullopt, nullopt, nullopt, nullopt}, // CC_GE
440 {false, nullopt, nullopt, nullopt, nullopt, nullopt, true, nullopt, false, false}, // CC_B
441 {true, nullopt, nullopt, nullopt, nullopt, nullopt, true, true, false, nullopt}, // CC_BE
442 {false, nullopt, nullopt, nullopt, nullopt, nullopt, false, false, true, nullopt}, // CC_A
443 {true, nullopt, nullopt, nullopt, nullopt, nullopt, false, nullopt, true, true}, // CC_AE
444 }};
445 // clang-format on
446
447 auto dominantCc = dominantCondition->CastToCompare()->GetCc();
448 // Swap the dominant condition code, if inputs are reversed: 'if (a < b)' -> 'if (b > a)'
449 if (condition->GetInput(0) != dominantCondition->GetInput(0)) {
450 ASSERT(condition->GetInput(0) == dominantCondition->GetInput(1));
451 dominantCc = SwapOperandsConditionCode(dominantCc);
452 }
453
454 // Reverse the `dominant_cc` if the `condition` is reached after branching the false succesor of the
455 // if_imm's basic block
456 auto conditionBb = condition->GetBasicBlock();
457 if (ifImm->GetEdgeIfInputFalse()->IsDominate(conditionBb)) {
458 dominantCc = GetInverseConditionCode(dominantCc);
459 } else {
460 ASSERT(ifImm->GetEdgeIfInputTrue()->IsDominate(conditionBb));
461 }
462 // After these transformations dominant condition with current `dominant_cc` is equal to `true`
463 // So `condition` result is resolved via table
464 return COND_RELATION[condition->CastToCompare()->GetCc()][dominantCc];
465 }
466 } // namespace panda::compiler
467