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 "compiler_logger.h"
17 #include "optimizer/analysis/alias_analysis.h"
18 #include "optimizer/analysis/bounds_analysis.h"
19 #include "optimizer/analysis/dominators_tree.h"
20 #include "optimizer/analysis/loop_analyzer.h"
21 #include "optimizer/ir/inst.h"
22 #include "optimizer/optimizations/licm.h"
23 #include "optimizer/ir/basicblock.h"
24 #include "optimizer/ir/analysis.h"
25
26 namespace panda::compiler {
Licm(Graph * graph,uint32_t hoistLimit)27 Licm::Licm(Graph *graph, uint32_t hoistLimit)
28 : Optimization(graph), hoistLimit_(hoistLimit), hoistableInstructions_(graph->GetLocalAllocator()->Adapter())
29 {
30 }
31
32 // NOTE (a.popov) Use `LoopTransform` base class similarly `LoopUnroll`, `LoopPeeling`
RunImpl()33 bool Licm::RunImpl()
34 {
35 if (!GetGraph()->GetAnalysis<LoopAnalyzer>().IsValid()) {
36 GetGraph()->GetAnalysis<LoopAnalyzer>().Run();
37 }
38
39 ASSERT(GetGraph()->GetRootLoop() != nullptr);
40 if (!GetGraph()->GetRootLoop()->GetInnerLoops().empty()) {
41 markerLoopExit_ = GetGraph()->NewMarker();
42 MarkLoopExits(GetGraph(), markerLoopExit_);
43 for (auto loop : GetGraph()->GetRootLoop()->GetInnerLoops()) {
44 LoopSearchDFS(loop);
45 }
46 GetGraph()->EraseMarker(markerLoopExit_);
47 }
48 return (hoistedInstCount_ != 0);
49 }
50
InvalidateAnalyses()51 void Licm::InvalidateAnalyses()
52 {
53 GetGraph()->InvalidateAnalysis<BoundsAnalysis>();
54 GetGraph()->InvalidateAnalysis<AliasAnalysis>();
55 }
56
57 /*
58 * Check if block is loop exit, exclude loop header
59 */
IsBlockLoopExit(BasicBlock * block)60 bool Licm::IsBlockLoopExit(BasicBlock *block)
61 {
62 return block->IsMarked(markerLoopExit_) && !block->IsLoopHeader();
63 }
64
65 /*
66 * Search loops in DFS order to visit inner loops firstly
67 */
LoopSearchDFS(Loop * loop)68 void Licm::LoopSearchDFS(Loop *loop)
69 {
70 ASSERT(loop != nullptr);
71 for (auto innerLoop : loop->GetInnerLoops()) {
72 LoopSearchDFS(innerLoop);
73 }
74 if (IsLoopVisited(*loop)) {
75 VisitLoop(loop);
76 }
77 }
78
IsLoopVisited(const Loop & loop) const79 bool Licm::IsLoopVisited(const Loop &loop) const
80 {
81 if (loop.IsIrreducible()) {
82 COMPILER_LOG(DEBUG, LOOP_TRANSFORM) << "Irreducible loop isn't visited, id = " << loop.GetId();
83 return false;
84 }
85 if (loop.IsOsrLoop()) {
86 COMPILER_LOG(DEBUG, LOOP_TRANSFORM) << "OSR entry isn't visited, loop id = " << loop.GetId();
87 return false;
88 }
89 if (loop.IsTryCatchLoop()) {
90 COMPILER_LOG(DEBUG, LOOP_TRANSFORM) << "Try-catch loop isn't visited, loop id = " << loop.GetId();
91 return false;
92 }
93 if (hoistedInstCount_ >= hoistLimit_) {
94 COMPILER_LOG(DEBUG, LICM_OPT) << "Limit is exceeded, loop isn't visited, id = " << loop.GetId();
95 return false;
96 }
97 COMPILER_LOG(DEBUG, LICM_OPT) << "Visit Loop, id = " << loop.GetId();
98 return true;
99 }
100
AllInputsDominate(Inst * inst,Inst * dom)101 bool AllInputsDominate(Inst *inst, Inst *dom)
102 {
103 for (auto input : inst->GetInputs()) {
104 if (!input.GetInst()->IsDominate(dom)) {
105 return false;
106 }
107 }
108 return true;
109 }
110
TryAppendHoistableInst(Inst * inst,BasicBlock * block,Loop * loop)111 void Licm::TryAppendHoistableInst(Inst *inst, BasicBlock *block, Loop *loop)
112 {
113 if (hoistedInstCount_ >= hoistLimit_) {
114 COMPILER_LOG(DEBUG, LICM_OPT) << "Limit is exceeded, Can't hoist instruction with id = " << inst->GetId();
115 return;
116 }
117 if (inst->IsResolver()) {
118 // If it is not "do-while" or "while(true)" loops then the resolver
119 // can not be hoisted out as it might perform class loading/initialization.
120 if (block != loop->GetHeader() && loop->GetHeader()->IsMarked(markerLoopExit_)) {
121 COMPILER_LOG(DEBUG, LICM_OPT)
122 << "Header is a loop exit, Can't hoist (resolver) instruction with id = " << inst->GetId();
123 return;
124 }
125 // Don't increment hoisted instruction count until check
126 // if there is another suitable SaveState for the resolver.
127 hoistableInstructions_.push_back(inst);
128 inst->SetMarker(markerHoistInst_);
129 } else {
130 COMPILER_LOG(DEBUG, LICM_OPT) << "Hoist instruction with id = " << inst->GetId();
131 hoistableInstructions_.push_back(inst);
132 inst->SetMarker(markerHoistInst_);
133 hoistedInstCount_++;
134 }
135 }
136
MoveInstructions(BasicBlock * preHeader,Loop * loop)137 void Licm::MoveInstructions(BasicBlock *preHeader, Loop *loop)
138 {
139 // Find position to move: either add first instruction to the empty block or after the last instruction
140 Inst *lastInst = preHeader->GetLastInst();
141
142 // Move instructions
143 for (auto inst : hoistableInstructions_) {
144 Inst *target = nullptr;
145 if (inst->IsResolver()) {
146 auto ss = FindSaveStateForResolver(inst, preHeader);
147 if (ss != nullptr) {
148 ASSERT(ss->IsSaveState());
149 COMPILER_LOG(DEBUG, LICM_OPT) << "Hoist (resolver) instruction with id = " << inst->GetId();
150 inst->SetSaveState(ss);
151 inst->SetPc(ss->GetPc());
152 hoistedInstCount_++;
153 } else {
154 continue;
155 }
156 } else if (inst->IsMovableObject()) {
157 target = inst->GetPrev();
158 if (target == nullptr) {
159 target = inst->GetNext();
160 }
161 if (target == nullptr) {
162 target = GetGraph()->CreateInstNOP();
163 inst->GetBasicBlock()->InsertAfter(target, inst);
164 }
165 }
166 inst->GetBasicBlock()->EraseInst(inst);
167 if (lastInst == nullptr || lastInst->IsPhi()) {
168 preHeader->AppendInst(inst);
169 lastInst = inst;
170 } else {
171 ASSERT(lastInst->GetBasicBlock() == preHeader);
172 Inst *ss = preHeader->FindSaveStateDeoptimize();
173 if (ss != nullptr && AllInputsDominate(inst, ss)) {
174 ss->InsertBefore(inst);
175 } else {
176 lastInst->InsertAfter(inst);
177 lastInst = inst;
178 }
179 }
180 GetGraph()->GetEventWriter().EventLicm(inst->GetId(), inst->GetPc(), loop->GetId(),
181 preHeader->GetLoop()->GetId());
182 if (inst->IsMovableObject()) {
183 ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), inst, target);
184 }
185 }
186 }
187
188 /*
189 * Iterate over all instructions in the loop and move hoistable instructions to the loop preheader
190 */
VisitLoop(Loop * loop)191 void Licm::VisitLoop(Loop *loop)
192 {
193 auto markerHolder = MarkerHolder(GetGraph());
194 markerHoistInst_ = markerHolder.GetMarker();
195 hoistableInstructions_.clear();
196 // Collect instruction, which can be hoisted
197 for (auto block : loop->GetBlocks()) {
198 ASSERT(block->GetLoop() == loop);
199 for (auto inst : block->InstsSafe()) {
200 if (!IsInstHoistable(inst)) {
201 continue;
202 }
203 TryAppendHoistableInst(inst, block, loop);
204 }
205 }
206 auto preHeader = loop->GetPreHeader();
207 ASSERT(preHeader != nullptr);
208 auto header = loop->GetHeader();
209 if (preHeader->GetSuccsBlocks().size() > 1) {
210 ASSERT(GetGraph()->IsAnalysisValid<DominatorsTree>());
211 // Create new pre-header with single successor: loop header
212 auto newPreHeader = preHeader->InsertNewBlockToSuccEdge(header);
213 preHeader->GetLoop()->AppendBlock(newPreHeader);
214 // Fix Dominators info
215 auto &domTree = GetGraph()->GetAnalysis<DominatorsTree>();
216 domTree.SetValid(true);
217 ASSERT(header->GetDominator() == preHeader);
218 preHeader->RemoveDominatedBlock(header);
219 domTree.SetDomPair(newPreHeader, header);
220 domTree.SetDomPair(preHeader, newPreHeader);
221 // Change pre-header
222 loop->SetPreHeader(newPreHeader);
223 preHeader = newPreHeader;
224 }
225 MoveInstructions(preHeader, loop);
226 }
227
FindUnsafeInstBetween(BasicBlock * domBb,BasicBlock * currBb,Marker visited,const Inst * resolver,Inst * startInst=nullptr)228 static bool FindUnsafeInstBetween(BasicBlock *domBb, BasicBlock *currBb, Marker visited, const Inst *resolver,
229 Inst *startInst = nullptr)
230 {
231 ASSERT(resolver->IsResolver());
232
233 if (domBb == currBb) {
234 return false;
235 }
236 if (currBb->SetMarker(visited)) {
237 return false;
238 }
239 // Do not cross a try block because the resolver
240 // can throw NoSuch{Method|Field}Exception
241 if (currBb->IsTry()) {
242 return true;
243 }
244 // Field and Method resolvers may call GetField and InitializeClass methods
245 // of the class linker resulting to class loading/initialization.
246 // An order of class initialization must be preserved, so the resolver
247 // must not cross other instructions performing class initialization
248 // We have to be conservative with respect to calls.
249 for (auto inst : InstSafeReverseIter(*currBb, startInst)) {
250 if (inst->IsClassInst() || inst->IsResolver() || inst->IsCall()) {
251 return true;
252 }
253 }
254 for (auto pred : currBb->GetPredsBlocks()) {
255 if (FindUnsafeInstBetween(domBb, pred, visited, resolver)) {
256 return true;
257 }
258 }
259 return false;
260 }
261
FindSaveStateForResolver(Inst * resolver,const BasicBlock * preHeader)262 Inst *Licm::FindSaveStateForResolver(Inst *resolver, const BasicBlock *preHeader)
263 {
264 ASSERT(preHeader->GetSuccsBlocks().size() == 1);
265 // Lookup for an appropriate SaveState in the preheader
266 Inst *ss = nullptr;
267 for (const auto &inst : preHeader->InstsSafeReverse()) {
268 if (inst->GetOpcode() == Opcode::SaveState) {
269 // Give up further checking if SaveState came from an inlined function.
270 if (static_cast<SaveStateInst *>(inst)->GetCallerInst() != nullptr) {
271 return nullptr;
272 }
273 for (auto i = preHeader->GetLastInst(); i != inst; i = i->GetPrev()) {
274 if (i->IsMovableObject()) {
275 // In this case we have to avoid instructions, which may trigger GC and move objects,
276 // thus we can not move the resolver to the pre_header and link with SaveState.
277 return nullptr;
278 }
279 }
280 ss = inst;
281 break;
282 }
283 }
284 if (ss == nullptr) {
285 return nullptr;
286 }
287 // Check if the path from the resolver instruction to the SaveState block is safe
288 ASSERT(ss->IsDominate(resolver));
289 MarkerHolder visited(GetGraph());
290 if (FindUnsafeInstBetween(ss->GetBasicBlock(), resolver->GetBasicBlock(), visited.GetMarker(), resolver,
291 resolver->GetPrev())) {
292 return nullptr;
293 }
294 return ss;
295 }
296
297 /*
298 * Instruction is not hoistable if one of the following conditions are true:
299 * - it has input which is defined in the same loop and not mark as hoistable
300 * - it has input which is not dominate instruction's loop preheader
301 */
InstInputDominatesPreheader(Inst * inst)302 bool Licm::InstInputDominatesPreheader(Inst *inst)
303 {
304 ASSERT(!inst->IsPhi());
305 auto instLoop = inst->GetBasicBlock()->GetLoop();
306 for (auto input : inst->GetInputs()) {
307 // Graph is in SSA form and the instructions not PHI,
308 // so every 'input' must dominate 'inst'.
309 ASSERT(input.GetInst()->IsDominate(inst));
310 auto inputLoop = input.GetInst()->GetBasicBlock()->GetLoop();
311 if (instLoop == inputLoop) {
312 if (inst->IsResolver() && input.GetInst()->IsSaveState()) {
313 // Resolver is coupled with SaveState that is not hoistable.
314 // We will try to find an appropriate SaveState in the preheader.
315 continue;
316 }
317 if (!input.GetInst()->IsMarked(markerHoistInst_)) {
318 return false;
319 }
320 } else if (instLoop->GetOuterLoop() == inputLoop->GetOuterLoop()) {
321 if (!input.GetInst()->GetBasicBlock()->IsDominate(instLoop->GetPreHeader())) {
322 return false;
323 }
324 } else if (!instLoop->IsInside(inputLoop)) {
325 return false;
326 }
327 }
328 return true;
329 }
330
331 /*
332 * Hoistable instruction must dominate all loop exits
333 */
InstDominatesLoopExits(Inst * inst)334 bool Licm::InstDominatesLoopExits(Inst *inst)
335 {
336 auto instLoop = inst->GetBasicBlock()->GetLoop();
337 for (auto block : instLoop->GetBlocks()) {
338 if (IsBlockLoopExit(block) && !inst->GetBasicBlock()->IsDominate(block)) {
339 return false;
340 }
341 }
342 return true;
343 }
344
345 /*
346 * Check if instruction can be moved to the loop preheader
347 */
IsInstHoistable(Inst * inst)348 bool Licm::IsInstHoistable(Inst *inst)
349 {
350 ASSERT(!inst->IsPhi());
351 if (inst->IsNotHoistable()) {
352 return false;
353 }
354 // Do not hoist the resolver if it came from an inlined function.
355 if (inst->IsResolver() && inst->GetSaveState()->GetCallerInst() != nullptr) {
356 return false;
357 }
358 if (inst->GetOpcode() == Opcode::LenArray &&
359 !BoundsAnalysis::IsInstNotNull(inst->GetDataFlowInput(0), inst->GetBasicBlock()->GetLoop()->GetHeader())) {
360 return false;
361 }
362 return InstInputDominatesPreheader(inst) && InstDominatesLoopExits(inst) && !GetGraph()->IsInstThrowable(inst);
363 }
364 } // namespace panda::compiler
365