1/**
2 * Copyright (c) 2021-2022 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 "cleanup.h"
17
18#include "compiler_logger.h"
19#include "optimizer/analysis/linear_order.h"
20#include "optimizer/analysis/loop_analyzer.h"
21#include "optimizer/ir/basicblock.h"
22
23namespace panda::compiler {
24
25static bool SkipBasicBlock(BasicBlock *bb)
26{
27    // TODO (a.popov) Make empty catch-begin and try-end blocks removeable
28    return bb == nullptr || bb->IsStartBlock() || bb->IsEndBlock() || bb->IsCatchBegin() || bb->IsTryEnd();
29}
30
31/* Cleanup pass works like dead code elimination (DCE) and removes code which does not affect the program results.
32 * It also removes empty basic blocks when it is possible and merges a linear basic block sequence to one bigger
33 * basic block, thus simplifying control flow graph.
34 */
35bool Cleanup::RunImpl()
36{
37    GetGraph()->RunPass<DominatorsTree>();
38    GetGraph()->RunPass<LoopAnalyzer>();
39
40    // Two vectors to store basic blocks lists
41    auto empty_blocks = &empty1_;
42    auto new_empty_blocks = &empty2_;
43
44    bool modified = PhiChecker();
45
46    for (auto bb : GetGraph()->GetVectorBlocks()) {
47        if (!SkipBasicBlock(bb) && bb->IsEmpty()) {
48            empty_blocks->insert(bb);
49        }
50    }
51
52    bool first_run = true;
53    do {
54        modified |= RunOnce(empty_blocks, new_empty_blocks, !first_run);
55        first_run = false;
56        // Swap vectors pointers
57        auto temp = empty_blocks;
58        empty_blocks = new_empty_blocks;
59        new_empty_blocks = temp;
60        // Clean the "new" list
61        new_empty_blocks->clear();
62    } while (!empty_blocks->empty());
63
64    empty1_.clear();
65    empty2_.clear();
66
67    /* Merge linear sectors.
68     * For merging possibility a block must have one successor, and its successor must have one predecessor.
69     * EXAMPLE:
70     *              [1]
71     *               |
72     *              [2]
73     *               |
74     *              [3]
75     *               |
76     *              [4]
77     *
78     * turns into this:
79     *              [1']
80     */
81    for (auto bb : GetGraph()->GetVectorBlocks()) {
82        if (bb == nullptr || bb->IsPseudoControlFlowBlock()) {
83            continue;
84        }
85
86        while (bb->GetSuccsBlocks().size() == 1 && bb->GetSuccessor(0)->GetPredsBlocks().size() == 1 &&
87               !bb->GetSuccessor(0)->IsPseudoControlFlowBlock() && bb->IsTry() == bb->GetSuccessor(0)->IsTry()) {
88            ASSERT(!bb->GetSuccessor(0)->HasPhi());
89            COMPILER_LOG(DEBUG, CLEANUP) << "Merged block " << bb->GetSuccessor(0)->GetId() << " into " << bb->GetId();
90            bb->JoinSuccessorBlock();
91            modified = true;
92        }
93    }
94    return modified;
95}
96
97bool Cleanup::RunOnce(ArenaSet<BasicBlock *> *empty_blocks, ArenaSet<BasicBlock *> *new_empty_blocks, bool simple_dce)
98{
99    bool modified = false;
100    auto marker_holder = MarkerHolder(GetGraph());
101    auto dead_mrk = marker_holder.GetMarker();
102
103    // Check all basic blocks in "old" list
104    for (auto bb : *empty_blocks) {
105        // In some tricky cases block may be already deleted in previous iteration
106        if (bb->GetGraph() == nullptr) {
107            continue;
108        }
109
110        auto succ = bb->GetSuccessor(0);
111        // Strange infinite loop with only one empty block, or loop pre-header - lets bail out
112        if (succ == bb || succ->GetLoop()->GetPreHeader() == bb) {
113            continue;
114        }
115
116#ifndef NDEBUG
117        // Now we know that 'bb' is not a loop pre-header, so if both 'bb' and 'succ' have many predecessors
118        // all 'bb' Phi(s) must have user only in successor Phis
119        if (succ->GetPredsBlocks().size() > 1) {
120            for (auto phi : bb->PhiInsts()) {
121                for (auto &user_item : phi->GetUsers()) {
122                    auto user = user_item.GetInst();
123                    ASSERT((user->GetBasicBlock() == succ && user->IsPhi()) || user->IsCatchPhi());
124                }
125            }
126        }
127#endif
128        modified |= ProcessBB(bb, dead_mrk, new_empty_blocks);
129    }
130
131    if (simple_dce) {
132        modified |= SimpleDce(dead_mrk, new_empty_blocks);
133    } else {
134        modified |= Dce(dead_mrk, new_empty_blocks);
135    }
136    dead_.clear();
137
138    return modified;
139}
140
141// Check around for special triangle case
142bool Cleanup::CheckSpecialTriangle(BasicBlock *bb)
143{
144    auto succ = bb->GetSuccessor(0);
145    size_t i = 0;
146    for (auto pred : bb->GetPredsBlocks()) {
147        if (pred->GetSuccessor(0) == succ ||
148            (pred->GetSuccsBlocks().size() == MAX_SUCCS_NUM && pred->GetSuccessor(1) == succ)) {
149            // Checking all Phis
150            for (auto phi : succ->PhiInsts()) {
151                size_t index_bb = phi->CastToPhi()->GetPredBlockIndex(bb);
152                size_t index_pred = phi->CastToPhi()->GetPredBlockIndex(pred);
153                ASSERT(index_bb != index_pred);
154
155                auto inst_pred = phi->GetInput(index_pred).GetInst();
156                auto inst_bb = phi->GetInput(index_bb).GetInst();
157                // If phi input is in 'bb', check input of that phi instead
158                if (inst_bb->GetBasicBlock() == bb) {
159                    ASSERT(inst_bb->IsPhi());
160                    inst_bb = inst_bb->CastToPhi()->GetInput(i).GetInst();
161                }
162                if (inst_bb != inst_pred) {
163                    return true;
164                }
165            }
166            // Would fully remove 'straight' pred->succ edge, and second one would stay after 'bb' removal
167            saved_preds_.push_back(pred);
168        }
169        i++;
170    }
171    return false;
172}
173
174void Cleanup::RemoveDeadPhi(BasicBlock *bb, ArenaSet<BasicBlock *> *new_empty_blocks)
175{
176    for (auto phi : bb->PhiInstsSafe()) {
177        if (!phi->GetUsers().Empty()) {
178            continue;
179        }
180        bb->RemoveInst(phi);
181        COMPILER_LOG(DEBUG, CLEANUP) << "Dead Phi removed " << phi->GetId();
182        GetGraph()->GetEventWriter().EventCleanup(phi->GetId(), phi->GetPc());
183
184        for (auto pred : bb->GetPredsBlocks()) {
185            if (pred->IsEmpty() && !SkipBasicBlock(pred)) {
186                COMPILER_LOG(DEBUG, CLEANUP) << "Would re-check empty block " << pred->GetId();
187                new_empty_blocks->insert(pred);
188            }
189        }
190    }
191}
192
193bool Cleanup::ProcessBB(BasicBlock *bb, Marker dead_mrk, ArenaSet<BasicBlock *> *new_empty_blocks)
194{
195    auto succ = bb->GetSuccessor(0);
196    if (CheckSpecialTriangle(bb)) {
197        return false;
198    }
199    // Remove dead Phi(s)
200    RemoveDeadPhi(bb, new_empty_blocks);
201    // Process saved predecessors
202    for (auto pred : saved_preds_) {
203        ASSERT(pred->GetSuccsBlocks().size() == MAX_SUCCS_NUM);
204        constexpr auto PREDS_BLOCK_NUM = 2;
205        ASSERT(succ->GetPredsBlocks().size() >= PREDS_BLOCK_NUM);
206
207        auto last = pred->GetLastInst();
208        if (last->GetOpcode() == Opcode::If || last->GetOpcode() == Opcode::IfImm) {
209            last->SetMarker(dead_mrk);
210            dead_.push_back(last);
211        } else {
212            ASSERT(last->GetOpcode() == Opcode::Try);
213        }
214        pred->RemoveSucc(succ);
215        if (succ->GetPredsBlocks().size() == PREDS_BLOCK_NUM) {
216            for (auto phi : succ->PhiInstsSafe()) {
217                auto rm_index = phi->CastToPhi()->GetPredBlockIndex(pred);
218                auto remaining_inst = phi->GetInputs()[1 - rm_index].GetInst();
219                phi->ReplaceUsers(remaining_inst);
220                succ->RemoveInst(phi);
221            }
222        } else {  // more than 2 predecessors
223            for (auto phi : succ->PhiInstsSafe()) {
224                auto rm_index = phi->CastToPhi()->GetPredBlockIndex(pred);
225                phi->CastToPhi()->RemoveInput(rm_index);
226            }
227        }
228        succ->RemovePred(pred);
229        // Fixing LoopAnalysis or DomTree is no necessary here, because there would be another edge
230    }
231    saved_preds_.clear();
232    bool bad_loop = bb->GetLoop()->IsIrreducible();
233    GetGraph()->RemoveEmptyBlockWithPhis(bb, bad_loop);
234    if (bad_loop) {
235        GetGraph()->InvalidateAnalysis<LoopAnalyzer>();
236        GetGraph()->RunPass<LoopAnalyzer>();
237    }
238    COMPILER_LOG(DEBUG, CLEANUP) << "Removed empty block: " << bb->GetId();
239    return true;
240}
241
242// Mark instructions that have the NOT_REMOVABLE property
243// and recursively mark all their inputs
244void Cleanup::MarkLiveRec(Marker live_mrk, Inst *inst)
245{
246    // No recursion for one-input case, otherwise got stackoverflow on TSAN job
247    bool marked = false;
248    while (inst->GetInputsCount() == 1) {
249        marked = inst->SetMarker(live_mrk);
250        if (marked) {
251            break;
252        }
253        inst = inst->GetInput(0).GetInst();
254    }
255    if (!marked && !inst->SetMarker(live_mrk)) {
256        for (auto input : inst->GetInputs()) {
257            MarkLiveRec(live_mrk, input.GetInst());
258        }
259    }
260}
261
262bool Cleanup::Dce(Marker dead_mrk, ArenaSet<BasicBlock *> *new_empty_blocks)
263{
264    bool modified = false;
265    auto marker_holder = MarkerHolder(GetGraph());
266    auto live_mrk = marker_holder.GetMarker();
267
268    // Mark live instructions
269    for (auto bb : GetGraph()->GetBlocksRPO()) {
270        for (auto inst : bb->AllInsts()) {
271            if (inst->IsNotRemovable() && !inst->IsMarked(dead_mrk)) {
272                MarkLiveRec(live_mrk, inst);
273            }
274        }
275    }
276    // Remove non-live instructions
277    for (auto bb : GetGraph()->GetBlocksRPO()) {
278        for (auto inst : bb->AllInstsSafe()) {
279            if (inst->IsMarked(live_mrk)) {
280                continue;
281            }
282            bool is_phi = inst->IsPhi();
283            bb->RemoveInst(inst);
284            COMPILER_LOG(DEBUG, CLEANUP) << "Dead instruction " << inst->GetId();
285            GetGraph()->GetEventWriter().EventCleanup(inst->GetId(), inst->GetPc());
286            modified = true;
287
288            if (is_phi) {
289                for (auto pred : bb->GetPredsBlocks()) {
290                    if (pred->IsEmpty() && !SkipBasicBlock(pred)) {
291                        COMPILER_LOG(DEBUG, CLEANUP) << "Would re-check empty block " << pred->GetId();
292                        new_empty_blocks->insert(pred);
293                    }
294                }
295            } else if (bb->IsEmpty() && !SkipBasicBlock(bb)) {
296                COMPILER_LOG(DEBUG, CLEANUP) << "No more non-Phi instructions in block " << bb->GetId();
297                new_empty_blocks->insert(bb);
298            }
299        }
300    }
301    return modified;
302}
303
304void Cleanup::SetLiveRec(Inst *inst, Marker mrk, Marker live_mrk)
305{
306    for (auto input_item : inst->GetInputs()) {
307        auto input = input_item.GetInst();
308        if (!input->IsMarked(live_mrk) && input->IsMarked(mrk)) {
309            input->ResetMarker(mrk);
310            input->SetMarker(live_mrk);
311            SetLiveRec(input, mrk, live_mrk);
312        }
313    }
314}
315
316void Cleanup::LiveUserSearchRec(Inst *inst, Marker mrk, Marker live_mrk, Marker dead_mrk)
317{
318    ASSERT(!inst->IsMarked(mrk));
319    ASSERT(!inst->IsMarked(dead_mrk));
320    if (inst->IsMarked(live_mrk)) {
321        SetLiveRec(inst, mrk, live_mrk);
322        return;
323    }
324    if (inst->IsNotRemovable()) {
325        inst->SetMarker(live_mrk);
326        SetLiveRec(inst, mrk, live_mrk);
327        return;
328    }
329    inst->SetMarker(mrk);
330    temp_.push_back(inst);
331    bool unknown = false;
332    for (auto &user_item : inst->GetUsers()) {
333        auto user = user_item.GetInst();
334        if (user->IsMarked(mrk)) {
335            unknown = true;
336            continue;
337        }
338        if (user->IsMarked(dead_mrk)) {
339            continue;
340        }
341        LiveUserSearchRec(user, mrk, live_mrk, dead_mrk);
342        if (user->IsMarked(live_mrk)) {
343            ASSERT(!inst->IsMarked(mrk) && inst->IsMarked(live_mrk));
344            return;
345        }
346        ASSERT(inst->IsMarked(mrk));
347        if (user->IsMarked(mrk)) {
348            ASSERT(!user->IsMarked(live_mrk) && !user->IsMarked(dead_mrk));
349            unknown = true;
350        } else {
351            ASSERT(user->IsMarked(dead_mrk));
352        }
353    }
354    if (!unknown) {
355        inst->ResetMarker(mrk);
356        inst->SetMarker(dead_mrk);
357        dead_.push_back(inst);
358    }
359}
360
361void Cleanup::Marking(Marker dead_mrk, Marker mrk, Marker live_mrk)
362{
363    size_t i = 0;
364    while (i < dead_.size()) {
365        auto inst = dead_.at(i);
366        for (auto input_item : inst->GetInputs()) {
367            auto input = input_item.GetInst();
368            if (input->IsMarked(dead_mrk) || input->IsMarked(mrk)) {
369                continue;
370            }
371            LiveUserSearchRec(input, mrk, live_mrk, dead_mrk);
372            for (auto temp : temp_) {
373                if (temp->IsMarked(mrk)) {
374                    ASSERT(!temp->IsMarked(live_mrk) && !temp->IsMarked(dead_mrk));
375                    inst->ResetMarker(mrk);
376                    inst->SetMarker(dead_mrk);
377                    dead_.push_back(inst);
378                }
379            }
380            temp_.clear();
381        }
382        i++;
383    }
384}
385
386bool Cleanup::Removal(ArenaSet<BasicBlock *> *new_empty_blocks)
387{
388    bool modified = false;
389
390    for (auto inst : dead_) {
391        inst->ClearMarkers();
392        auto bb = inst->GetBasicBlock();
393        if (bb == nullptr) {
394            continue;
395        }
396        bb->RemoveInst(inst);
397        COMPILER_LOG(DEBUG, CLEANUP) << "Dead instruction " << inst->GetId();
398        GetGraph()->GetEventWriter().EventCleanup(inst->GetId(), inst->GetPc());
399        modified = true;
400
401        if (inst->IsPhi()) {
402            for (auto pred : bb->GetPredsBlocks()) {
403                if (pred->IsEmpty() && !SkipBasicBlock(pred)) {
404                    COMPILER_LOG(DEBUG, CLEANUP) << "Would re-check empty block " << pred->GetId();
405                    new_empty_blocks->insert(pred);
406                }
407            }
408        } else {
409            if (bb->IsEmpty() && !SkipBasicBlock(bb)) {
410                COMPILER_LOG(DEBUG, CLEANUP) << "No more non-Phi instructions in block " << bb->GetId();
411                new_empty_blocks->insert(bb);
412            }
413        }
414    }
415    return modified;
416}
417
418bool Cleanup::SimpleDce(Marker dead_mrk, ArenaSet<BasicBlock *> *new_empty_blocks)
419{
420    auto marker_holder = MarkerHolder(GetGraph());
421    auto mrk = marker_holder.GetMarker();
422    auto live_marker_holder = MarkerHolder(GetGraph());
423    auto live_mrk = live_marker_holder.GetMarker();
424
425    // Step 1. Marking
426    Marking(dead_mrk, mrk, live_mrk);
427
428    // Step 2. Removal
429    return Removal(new_empty_blocks);
430}
431
432void Cleanup::BuildDominators()
433{
434    size_t amount = 0;
435    fake_root_ = reinterpret_cast<Inst *>(sizeof(Inst *));
436    map_.insert({fake_root_, amount});
437    for (auto bb : GetGraph()->GetBlocksRPO()) {
438        for (auto inst : bb->PhiInsts()) {
439            amount++;
440            map_.insert({inst, amount});
441            for (auto input : inst->GetInputs()) {
442                auto pred = input.GetInst();
443                if (!pred->IsPhi() && map_.count(pred) == 0) {
444                    amount++;
445                    map_.insert({pred, amount});
446                }
447            }
448        }
449    }
450    Init(amount + 1);
451    SetVertex(0, fake_root_);
452    for (auto bb : GetGraph()->GetBlocksRPO()) {
453        for (auto inst : bb->Insts()) {
454            if (map_.count(inst) > 0 && GetSemi(inst) == DEFAULT_DFS_VAL) {
455                SetParent(inst, fake_root_);
456                DfsNumbering(inst);
457            }
458        }
459    }
460    ASSERT(static_cast<size_t>(dfs_num_) == amount);
461
462    for (size_t i = amount; i > 0; i--) {
463        ComputeImmediateDominators(GetVertex(i));
464    }
465
466    for (size_t i = 1; i <= amount; i++) {
467        AdjustImmediateDominators(GetVertex(i));
468    }
469}
470
471/*
472 * Adjust immediate dominators,
473 * Update dominator information for 'inst'
474 */
475void Cleanup::AdjustImmediateDominators(Inst *inst)
476{
477    ASSERT(inst != nullptr);
478
479    if (GetIdom(inst) != GetVertex(GetSemi(inst))) {
480        SetIdom(inst, GetIdom(GetIdom(inst)));
481    }
482}
483
484/*
485 * Compute initial values for semidominators,
486 * store instructions with the same semidominator in the same bucket,
487 * compute immediate dominators for instructions in the bucket of 'inst' parent
488 */
489void Cleanup::ComputeImmediateDominators(Inst *inst)
490{
491    ASSERT(inst != nullptr);
492
493    if (inst->IsPhi()) {
494        for (auto input : inst->GetInputs()) {
495            auto pred = input.GetInst();
496            auto eval = Eval(pred);
497            if (GetSemi(eval) < GetSemi(inst)) {
498                SetSemi(inst, GetSemi(eval));
499            }
500        }
501    } else {
502        auto eval = fake_root_;
503        if (GetSemi(eval) < GetSemi(inst)) {
504            SetSemi(inst, GetSemi(eval));
505        }
506    }
507
508    auto vertex = GetVertex(GetSemi(inst));
509    GetBucket(vertex).push_back(inst);
510    auto parent = GetParent(inst);
511    SetAncestor(inst, parent);
512
513    auto &bucket = GetBucket(parent);
514    while (!bucket.empty()) {
515        auto v = *bucket.rbegin();
516        auto eval = Eval(v);
517        if (GetSemi(eval) < GetSemi(v)) {
518            SetIdom(v, eval);
519        } else {
520            SetIdom(v, parent);
521        }
522        bucket.pop_back();
523    }
524}
525
526/*
527 * Compress ancestor path to 'inst' to the instruction whose label has the maximal semidominator number
528 */
529void Cleanup::Compress(Inst *inst)
530{
531    auto anc = GetAncestor(inst);
532    ASSERT(anc != nullptr);
533
534    if (GetAncestor(anc) != nullptr) {
535        Compress(anc);
536        if (GetSemi(GetLabel(anc)) < GetSemi(GetLabel(inst))) {
537            SetLabel(inst, GetLabel(anc));
538        }
539        SetAncestor(inst, GetAncestor(anc));
540    }
541}
542
543/*
544 *  Depth-first search with numbering instruction in order they are reaching
545 */
546void Cleanup::DfsNumbering(Inst *inst)
547{
548    ASSERT(inst != nullptr || inst != fake_root_);
549    dfs_num_++;
550    ASSERT_PRINT(static_cast<size_t>(dfs_num_) < vertices_.size(), "DFS-number overflow");
551
552    SetVertex(dfs_num_, inst);
553    SetLabel(inst, inst);
554    SetSemi(inst, dfs_num_);
555    SetAncestor(inst, nullptr);
556
557    for (auto &user : inst->GetUsers()) {
558        auto succ = user.GetInst();
559        if (succ->IsPhi() && GetSemi(succ) == DEFAULT_DFS_VAL) {
560            SetParent(succ, inst);
561            DfsNumbering(succ);
562        }
563    }
564}
565
566/*
567 * Return 'inst' if it is the root of a tree
568 * Otherwise, after tree compressing
569 * return the instruction in the ancestors chain with the minimal semidominator DFS-number
570 */
571Inst *Cleanup::Eval(Inst *inst)
572{
573    ASSERT(inst != nullptr);
574    if (GetAncestor(inst) == nullptr) {
575        return inst;
576    }
577    Compress(inst);
578    return GetLabel(inst);
579}
580
581/*
582 * Initialize data structures to start DFS
583 */
584void Cleanup::Init(size_t count)
585{
586    ancestors_.clear();
587    idoms_.clear();
588    labels_.clear();
589    parents_.clear();
590    vertices_.clear();
591    semi_.clear();
592
593    ancestors_.resize(count);
594    idoms_.resize(count);
595    labels_.resize(count);
596    parents_.resize(count);
597    vertices_.resize(count);
598    semi_.resize(count);
599
600    std::fill(vertices_.begin(), vertices_.end(), nullptr);
601    std::fill(semi_.begin(), semi_.end(), DEFAULT_DFS_VAL);
602
603    if (buckets_.size() < count) {
604        buckets_.resize(count, InstVector(GetGraph()->GetLocalAllocator()->Adapter()));
605    }
606    for (auto &bucket : buckets_) {
607        bucket.clear();
608    }
609    dfs_num_ = DEFAULT_DFS_VAL;
610}
611
612/*
613 * Selecting phi instructions that can be deleted
614 * and replaced with a single instruction for all uses
615 *
616 * Example
617 *    ...
618 *    6p.u64  Phi                        v2(bb4), v2(bb3)
619 *    7.u64  Return                      v6p
620 *
621 * Removing instruction 6 and replacing use with v2
622 *
623 *    ...
624 *    7.u64  Return                      v2
625 */
626bool Cleanup::PhiChecker()
627{
628    BuildDominators();
629    bool modified = false;
630    for (auto bb : GetGraph()->GetBlocksRPO()) {
631        for (auto phi : bb->PhiInstsSafe()) {
632            auto change = GetIdom(phi);
633            if (change == fake_root_) {
634                continue;
635            }
636            while (GetIdom(change) != fake_root_) {
637                change = GetIdom(change);
638            }
639            auto basic_block = phi->GetBasicBlock();
640            phi->ReplaceUsers(change);
641            basic_block->RemoveInst(phi);
642            modified = true;
643        }
644    }
645    map_.clear();
646    return modified;
647}
648
649void Cleanup::InvalidateAnalyses()
650{
651    GetGraph()->InvalidateAnalysis<LinearOrder>();
652}
653}  // namespace panda::compiler
654