• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2024 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 ark::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     }
126     hoistableInstructions_.push_back(inst);
127     inst->SetMarker(markerHoistInst_);
128     if (!inst->RequireState()) {
129         // Don't increment hoisted instruction count until check
130         // if there is another suitable SaveState for it.
131         COMPILER_LOG(DEBUG, LICM_OPT) << "Hoist instruction with id = " << inst->GetId();
132         hoistedInstCount_++;
133     }
134 }
135 
IsUnsafeForResolver(const Inst * inst)136 static bool IsUnsafeForResolver(const Inst *inst)
137 {
138     // Field and Method resolvers may call GetField and InitializeClass methods
139     // of the class linker resulting to class loading/initialization.
140     // An order of class initialization must be preserved, so the resolver
141     // must not cross other instructions performing class initialization
142     // We have to be conservative with respect to calls.
143     return inst->IsClassInst() || inst->IsResolver() || inst->IsCall();
144 }
145 
EndOfPreheaderIsSafe(Inst * hoisted,Inst * insertBefore)146 static bool EndOfPreheaderIsSafe(Inst *hoisted, Inst *insertBefore)
147 {
148     auto *preHeader = insertBefore->GetBasicBlock();
149     for (auto *inst : InstIter(*preHeader, insertBefore)) {
150         if (hoisted->IsResolver() && IsUnsafeForResolver(inst)) {
151             return false;
152         }
153         if (std::find(hoisted->GetInputs().begin(), hoisted->GetInputs().end(), inst) != hoisted->GetInputs().end()) {
154             // Not all inputs will be dominate
155             return false;
156         }
157         if (hoisted->IsMovableObject() && inst->IsRuntimeCall()) {
158             // We cannot insert movable object between `inst` and its SaveState
159             return false;
160         }
161     }
162     return true;
163 }
164 
UnmarkHoistUsers(Inst * inst)165 void Licm::UnmarkHoistUsers(Inst *inst)
166 {
167     for (auto &user : inst->GetUsers()) {
168         user.GetInst()->ResetMarker(markerHoistInst_);
169     }
170 }
171 
MoveInstToPreHeader(Inst * inst,BasicBlock * preHeader,Inst * insertBefore)172 static void MoveInstToPreHeader(Inst *inst, BasicBlock *preHeader, Inst *insertBefore)
173 {
174     // Find position to move: either add first instruction to the empty block or after the last instruction
175     auto *lastInst = preHeader->GetLastInst();
176 
177     inst->GetBasicBlock()->EraseInst(inst);
178     if (lastInst == nullptr || lastInst->IsPhi()) {
179         preHeader->AppendInst(inst);
180     } else {
181         ASSERT(lastInst->GetBasicBlock() == preHeader);
182         Inst *ss = preHeader->FindSaveStateDeoptimize();
183         // If insertBefore was set, we cannot insert inst after it
184         if (ss != nullptr && (insertBefore == nullptr || ss->IsPrecedingInSameBlock(insertBefore)) &&
185             EndOfPreheaderIsSafe(inst, ss)) {
186             insertBefore = ss;
187         }
188         if (insertBefore != nullptr) {
189             insertBefore->InsertBefore(inst);
190         } else {
191             lastInst->InsertAfter(inst);
192         }
193     }
194 }
195 
MoveInstructions(BasicBlock * preHeader,Loop * loop)196 void Licm::MoveInstructions(BasicBlock *preHeader, Loop *loop)
197 {
198     // Move instructions
199     for (auto inst : hoistableInstructions_) {
200         if (!inst->IsMarked(markerHoistInst_)) {
201             if (!inst->RequireState()) {
202                 hoistedInstCount_--;
203             }
204             UnmarkHoistUsers(inst);
205             continue;
206         }
207         Inst *insertBefore = nullptr;
208         if (inst->RequireState()) {
209             auto ss = FindSaveStateForHoist(inst, preHeader, &insertBefore);
210             if (ss != nullptr) {
211                 ASSERT(ss->IsSaveState());
212                 COMPILER_LOG(DEBUG, LICM_OPT)
213                     << "Hoist instruction with id = " << inst->GetId() << " and link with SaveState " << ss->GetId();
214                 inst->SetSaveState(ss);
215                 inst->SetPc(ss->GetPc());
216                 hoistedInstCount_++;
217             } else {
218                 UnmarkHoistUsers(inst);
219                 continue;
220             }
221         }
222         auto *target = inst->GetPrev();
223         auto *targetBB = inst->GetBasicBlock();
224         MoveInstToPreHeader(inst, preHeader, insertBefore);
225         GetGraph()->GetEventWriter().EventLicm(inst->GetId(), inst->GetPc(), loop->GetId(),
226                                                preHeader->GetLoop()->GetId());
227         if (inst->IsMovableObject()) {
228             ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), inst, target, nullptr, targetBB);
229         }
230     }
231 }
232 
233 /*
234  * Iterate over all instructions in the loop and move hoistable instructions to the loop preheader
235  */
VisitLoop(Loop * loop)236 void Licm::VisitLoop(Loop *loop)
237 {
238     auto markerHolder = MarkerHolder(GetGraph());
239     markerHoistInst_ = markerHolder.GetMarker();
240     hoistableInstructions_.clear();
241     // Collect instruction, which can be hoisted
242     for (auto block : loop->GetBlocks()) {
243         ASSERT(block->GetLoop() == loop);
244         for (auto inst : block->InstsSafe()) {
245             if (!IsInstHoistable(inst)) {
246                 continue;
247             }
248             TryAppendHoistableInst(inst, block, loop);
249         }
250     }
251     auto preHeader = loop->GetPreHeader();
252     ASSERT(preHeader != nullptr);
253     auto header = loop->GetHeader();
254     if (preHeader->GetSuccsBlocks().size() > 1) {
255         ASSERT(GetGraph()->IsAnalysisValid<DominatorsTree>());
256         // Create new pre-header with single successor: loop header
257         auto newPreHeader = preHeader->InsertNewBlockToSuccEdge(header);
258         preHeader->GetLoop()->AppendBlock(newPreHeader);
259         // Fix Dominators info
260         auto &domTree = GetGraph()->GetAnalysis<DominatorsTree>();
261         domTree.SetValid(true);
262         ASSERT(header->GetDominator() == preHeader);
263         preHeader->RemoveDominatedBlock(header);
264         domTree.SetDomPair(newPreHeader, header);
265         domTree.SetDomPair(preHeader, newPreHeader);
266         // Change pre-header
267         loop->SetPreHeader(newPreHeader);
268         preHeader = newPreHeader;
269     }
270     MoveInstructions(preHeader, loop);
271 }
272 
FindUnsafeInstBetween(const BasicBlock * domBb,BasicBlock * currBb,Marker visited,const Inst * resolver,Inst * startInst=nullptr)273 static bool FindUnsafeInstBetween(const BasicBlock *domBb, BasicBlock *currBb, Marker visited, const Inst *resolver,
274                                   Inst *startInst = nullptr)
275 {
276     ASSERT(resolver->IsResolver());
277 
278     if (domBb == currBb) {
279         return false;
280     }
281     if (currBb->SetMarker(visited)) {
282         return false;
283     }
284     // Do not cross a try block because the resolver
285     // can throw NoSuch{Method|Field}Exception
286     if (currBb->IsTry()) {
287         return true;
288     }
289     for (auto inst : InstSafeReverseIter(*currBb, startInst)) {
290         if (IsUnsafeForResolver(inst)) {
291             return true;
292         }
293     }
294     for (auto pred : currBb->GetPredsBlocks()) {
295         if (FindUnsafeInstBetween(domBb, pred, visited, resolver)) {
296             return true;
297         }
298     }
299     return false;
300 }
301 
FindSaveStateForHoist(Inst * hoisted,const BasicBlock * preHeader,Inst ** insertBefore)302 Inst *Licm::FindSaveStateForHoist(Inst *hoisted, const BasicBlock *preHeader, Inst **insertBefore)
303 {
304     ASSERT(!hoisted->IsMovableObject());
305     ASSERT(preHeader->GetSuccsBlocks().size() == 1);
306     // Lookup for an appropriate SaveState in the preheader
307     Inst *ss = nullptr;
308     for (const auto &inst : preHeader->InstsSafeReverse()) {
309         if (hoisted->IsRuntimeCall() && inst->IsMovableObject()) {
310             // In this case we have to avoid instructions, which may trigger GC and move objects after `inst`,
311             // thus we can move the instruction to the pre_header only before `inst`.
312             *insertBefore = inst;
313         }
314         if (inst->GetOpcode() == Opcode::SaveState) {
315             // Give up further checking if SaveState came from an inlined function.
316             if (static_cast<SaveStateInst *>(inst)->GetCallerInst() != nullptr) {
317                 return nullptr;
318             }
319             ss = inst;
320             break;
321         }
322     }
323     if (ss == nullptr) {
324         return nullptr;
325     }
326     ASSERT(ss->IsDominate(hoisted));
327     if (*insertBefore != nullptr && !EndOfPreheaderIsSafe(hoisted, *insertBefore)) {
328         return nullptr;
329     }
330     if (hoisted->IsResolver()) {
331         // Check if the path from the resolver instruction to the SaveState block is safe
332         MarkerHolder visited(GetGraph());
333         if (FindUnsafeInstBetween(preHeader, hoisted->GetBasicBlock(), visited.GetMarker(), hoisted,
334                                   hoisted->GetPrev())) {
335             return nullptr;
336         }
337     }
338     return ss;
339 }
340 
341 /*
342  * Instruction is not hoistable if one of the following conditions are true:
343  *  - it has input which is defined in the same loop and not mark as hoistable
344  *  - it has input which is not dominate instruction's loop preheader
345  */
InstInputDominatesPreheader(Inst * inst)346 bool Licm::InstInputDominatesPreheader(Inst *inst)
347 {
348     ASSERT(!inst->IsPhi());
349     auto instLoop = inst->GetBasicBlock()->GetLoop();
350     for (auto input : inst->GetInputs()) {
351         // Graph is in SSA form and the instructions not PHI,
352         // so every 'input' must dominate 'inst'.
353         ASSERT(input.GetInst()->IsDominate(inst));
354         auto inputLoop = input.GetInst()->GetBasicBlock()->GetLoop();
355         if (instLoop == inputLoop) {
356             if (input.GetInst()->IsSaveState()) {
357                 // Inst is coupled with SaveState that is not hoistable.
358                 // We will try to find an appropriate SaveState in the preheader.
359                 continue;
360             }
361             if (!input.GetInst()->IsMarked(markerHoistInst_)) {
362                 return false;
363             }
364         } else if (instLoop->GetOuterLoop() == inputLoop->GetOuterLoop()) {
365             if (!input.GetInst()->GetBasicBlock()->IsDominate(instLoop->GetPreHeader())) {
366                 return false;
367             }
368         } else if (!instLoop->IsInside(inputLoop)) {
369             return false;
370         }
371     }
372     return true;
373 }
374 
375 /*
376  * Hoistable instruction must dominate all loop exits
377  */
InstDominatesLoopExits(Inst * inst)378 bool Licm::InstDominatesLoopExits(Inst *inst)
379 {
380     auto instLoop = inst->GetBasicBlock()->GetLoop();
381     for (auto block : instLoop->GetBlocks()) {
382         if (IsBlockLoopExit(block) && !inst->GetBasicBlock()->IsDominate(block)) {
383             return false;
384         }
385     }
386     return true;
387 }
388 
389 /*
390  * Check if instruction can be moved to the loop preheader
391  */
IsInstHoistable(Inst * inst)392 bool Licm::IsInstHoistable(Inst *inst)
393 {
394     ASSERT(!inst->IsPhi());
395     if (inst->IsNotHoistable()) {
396         return false;
397     }
398     // Do not hoist the resolver if it came from an inlined function.
399     if (inst->IsResolver() && inst->GetSaveState()->GetCallerInst() != nullptr) {
400         return false;
401     }
402     if (inst->GetOpcode() == Opcode::LenArray &&
403         !BoundsAnalysis::IsInstNotNull(inst->GetDataFlowInput(0), inst->GetBasicBlock()->GetLoop()->GetHeader())) {
404         return false;
405     }
406     return InstInputDominatesPreheader(inst) && InstDominatesLoopExits(inst) && !GetGraph()->IsInstThrowable(inst);
407 }
408 }  // namespace ark::compiler
409