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 "include/class.h"
17 #include "runtime/include/class-inl.h"
18 #include "compiler_logger.h"
19 #include "optimizer/analysis/dominators_tree.h"
20 #include "optimizer/analysis/loop_analyzer.h"
21 #include "optimizer/ir/basicblock.h"
22 #include "optimizer/ir/inst.h"
23 #include "optimizer/optimizations/cleanup.h"
24 #include "optimizer/optimizations/try_catch_resolving.h"
25
26 namespace panda::compiler {
TryCatchResolving(Graph * graph)27 TryCatchResolving::TryCatchResolving(Graph *graph) : Optimization(graph) {}
28
RunImpl()29 bool TryCatchResolving::RunImpl()
30 {
31 marker_ = GetGraph()->NewMarker();
32 for (auto block : GetGraph()->GetBlocksRPO()) {
33 if (!block->IsTryBegin()) {
34 continue;
35 }
36 COMPILER_LOG(DEBUG, TRY_CATCH_RESOLVING) << "Visit try-begin id = " << std::to_string(block->GetId());
37 VisitTry(GetTryBeginInst(block));
38 }
39 GetGraph()->RemoveUnreachableBlocks();
40 GetGraph()->ClearTryCatchInfo();
41 GetGraph()->EraseMarker(marker_);
42 #ifndef NDEBUG
43 InvalidateAnalyses();
44 // Cleanup should be done inside pass, to satisfy GraphChecker
45 GetGraph()->RunPass<Cleanup>();
46 #endif
47 return true;
48 }
49
50 /**
51 * Search throw instruction with known at compile-time `object_id`
52 * and directly connect catch-handler for this `object_id` if it exists in the current graph
53 */
VisitTry(TryInst * try_inst)54 void TryCatchResolving::VisitTry(TryInst *try_inst)
55 {
56 auto try_begin = try_inst->GetBasicBlock();
57 auto try_end = try_inst->GetTryEndBlock();
58 ASSERT(try_begin->IsTryBegin());
59 ASSERT(try_end->IsTryEnd());
60 // First of all, try to find catch-handler that can be directly connected to the block with `throw`
61 auto resolved_catch_handler = TryFindResolvedCatchHandler(try_begin, try_end);
62 // Now, when catch-handler was searched - remove all edges from `try_begin` and `try_end` blocks
63 DeleteTryCatchEdges(try_begin, try_end);
64 // If resolved catch-handler exists, connect it
65 if (resolved_catch_handler != nullptr) {
66 ConnectCatchHandlerAfterThrow(try_end, resolved_catch_handler);
67 }
68 // Clean-up lables and `try_inst`
69 try_begin->EraseInst(try_inst);
70 try_begin->SetTryBegin(false);
71 try_end->SetTryEnd(false);
72 }
73
TryFindResolvedCatchHandler(BasicBlock * try_begin,BasicBlock * try_end)74 BasicBlock *TryCatchResolving::TryFindResolvedCatchHandler(BasicBlock *try_begin, BasicBlock *try_end)
75 {
76 // `try_end` could be removed after inlining
77 if (try_end->GetPredsBlocks().size() != 1U) {
78 return nullptr;
79 }
80 auto throw_inst = try_end->GetPredecessor(0)->GetLastInst();
81 if (throw_inst == nullptr || throw_inst->GetOpcode() != Opcode::Throw) {
82 return nullptr;
83 }
84 auto object_id = TryGetObjectId(throw_inst);
85 if (!object_id) {
86 return nullptr;
87 }
88 // There should be no side-exits from try
89 auto holder = MarkerHolder(GetGraph());
90 auto marker = holder.GetMarker();
91 if (!DFS(try_begin->GetSuccessor(0), marker, try_begin->GetTryId())) {
92 return nullptr;
93 }
94 // We've got `object_id` which is thrown form try-block
95 // Let's find handler, which catches this `object_id`
96 BasicBlock *resolved_handler = nullptr;
97 try_begin->EnumerateCatchHandlers([this, object_id, &resolved_handler](BasicBlock *catch_handler, size_t type_id) {
98 // We don't connect catch-handlers which are related to more than one try-block.
99 // So that we skip blocks that:
100 // - have more than 2 predecessors (one try-begin/end pair);
101 // - were already visited from another try-block and were marked;
102 if (catch_handler->GetPredsBlocks().size() > 2U || catch_handler->IsMarked(marker_)) {
103 return true;
104 }
105 static constexpr size_t CATCH_ALL_ID = 0;
106 if (type_id == CATCH_ALL_ID) {
107 resolved_handler = catch_handler;
108 return false;
109 }
110 auto runtime = GetGraph()->GetRuntime();
111 auto *cls = runtime->ResolveType(GetGraph()->GetMethod(), object_id.value());
112 auto *handler_cls = runtime->ResolveType(GetGraph()->GetMethod(), type_id);
113 if (static_cast<Class *>(cls)->IsSubClassOf(static_cast<Class *>(handler_cls))) {
114 resolved_handler = catch_handler;
115 return false;
116 }
117 return true;
118 });
119 return resolved_handler;
120 }
121
122 /**
123 * Disconnect auxiliary `try_begin` and `try_end`. That means all related catch-handlers become unreachable
124 */
DeleteTryCatchEdges(BasicBlock * try_begin,BasicBlock * try_end)125 void TryCatchResolving::DeleteTryCatchEdges(BasicBlock *try_begin, BasicBlock *try_end)
126 {
127 while (try_begin->GetSuccsBlocks().size() > 1U) {
128 auto catch_succ = try_begin->GetSuccessor(1U);
129 ASSERT(catch_succ->IsCatchBegin());
130 try_begin->RemoveSucc(catch_succ);
131 catch_succ->RemovePred(try_begin);
132 if (try_end->GetGraph() != nullptr) {
133 ASSERT(try_end->GetSuccessor(1) == catch_succ);
134 try_end->RemoveSucc(catch_succ);
135 catch_succ->RemovePred(try_end);
136 }
137 // Mark that catch-handler was visited
138 catch_succ->SetMarker(marker_);
139 }
140 }
141
142 /**
143 * Add edge between catch-handler and `try_end` follows `throw` instruction
144 */
ConnectCatchHandlerAfterThrow(BasicBlock * try_end,BasicBlock * catch_block)145 void TryCatchResolving::ConnectCatchHandlerAfterThrow(BasicBlock *try_end, BasicBlock *catch_block)
146 {
147 ASSERT(try_end != nullptr && try_end->IsTryEnd());
148 ASSERT(catch_block != nullptr && catch_block->IsCatchBegin());
149 ASSERT(try_end->GetPredsBlocks().size() == 1U);
150 auto throw_inst = try_end->GetPredecessor(0)->GetLastInst();
151 ASSERT(throw_inst != nullptr && throw_inst->GetOpcode() == Opcode::Throw);
152
153 COMPILER_LOG(DEBUG, TRY_CATCH_RESOLVING)
154 << "Connect blocks: " << std::to_string(try_end->GetId()) << " -> " << std::to_string(catch_block->GetId());
155
156 auto succ = try_end->GetSuccessor(0);
157 try_end->ReplaceSucc(succ, catch_block);
158 succ->RemovePred(try_end);
159 RemoveCatchPhis(catch_block, throw_inst);
160
161 auto save_state = throw_inst->GetInput(1).GetInst();
162 ASSERT(save_state->GetOpcode() == Opcode::SaveState);
163 auto throw_block = throw_inst->GetBasicBlock();
164 throw_block->RemoveInst(throw_inst);
165 throw_block->RemoveInst(save_state);
166 }
167
168 /**
169 * Replace all catch-phi instructions with their inputs
170 * Replace accumulator's catch-phi with exception's object
171 */
RemoveCatchPhis(BasicBlock * block,Inst * throw_inst)172 void TryCatchResolving::RemoveCatchPhis(BasicBlock *block, Inst *throw_inst)
173 {
174 ASSERT(block->IsCatchBegin());
175 for (auto inst : block->AllInstsSafe()) {
176 if (!inst->IsCatchPhi()) {
177 break;
178 }
179 auto catch_phi = inst->CastToCatchPhi();
180 if (catch_phi->IsAcc()) {
181 auto throw_obj = throw_inst->GetInput(0).GetInst();
182 catch_phi->ReplaceUsers(throw_obj);
183 } else {
184 auto throw_insts = catch_phi->GetThrowableInsts();
185 auto it = std::find(throw_insts->begin(), throw_insts->end(), throw_inst);
186 if (it != throw_insts->end()) {
187 auto input_index = std::distance(throw_insts->begin(), it);
188 auto input_inst = catch_phi->GetInput(input_index).GetInst();
189 catch_phi->ReplaceUsers(input_inst);
190 } else {
191 // Virtual register related to the catch-phi is undefined, so that there should be no real users
192 auto users = catch_phi->GetUsers();
193 while (!users.Empty()) {
194 auto &user = users.Front();
195 ASSERT(user.GetInst()->IsSaveState() || user.GetInst()->IsCatchPhi());
196 user.GetInst()->RemoveInput(user.GetIndex());
197 }
198 }
199 }
200 block->RemoveInst(catch_phi);
201 }
202 block->SetCatch(false);
203 block->SetCatchBegin(false);
204 }
205
206 /**
207 * Return object's id if `Throw` has `NewObject` input
208 */
TryGetObjectId(const Inst * inst)209 std::optional<uint32_t> TryCatchResolving::TryGetObjectId(const Inst *inst)
210 {
211 ASSERT(inst->GetOpcode() == Opcode::Throw);
212 auto ref_input = inst->GetInput(0).GetInst();
213 if (ref_input->GetOpcode() == Opcode::NewObject) {
214 return ref_input->CastToNewObject()->GetTypeId();
215 }
216 return std::nullopt;
217 }
218
DFS(BasicBlock * block,Marker marker,uint32_t try_id)219 bool TryCatchResolving::DFS(BasicBlock *block, Marker marker, uint32_t try_id)
220 {
221 if (block->GetTryId() != try_id) {
222 return false;
223 }
224 if (block->IsTryEnd() && block->GetTryId() == try_id) {
225 return true;
226 }
227 block->SetMarker(marker);
228 for (auto succ : block->GetSuccsBlocks()) {
229 if (succ->IsMarked(marker)) {
230 continue;
231 }
232 if (!DFS(succ, marker, try_id)) {
233 return false;
234 }
235 }
236 return true;
237 }
238
InvalidateAnalyses()239 void TryCatchResolving::InvalidateAnalyses()
240 {
241 GetGraph()->InvalidateAnalysis<DominatorsTree>();
242 GetGraph()->InvalidateAnalysis<LoopAnalyzer>();
243 InvalidateBlocksOrderAnalyzes(GetGraph());
244 }
245 } // namespace panda::compiler
246