1 /*
2 * Copyright (c) 2021-2025 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 auto saveState = inst->GetSaveState();
400 ASSERT(!inst->IsResolver() || saveState != nullptr);
401 if (inst->IsResolver() && saveState->GetCallerInst() != nullptr) {
402 return false;
403 }
404 if (inst->GetOpcode() == Opcode::LenArray &&
405 !BoundsAnalysis::IsInstNotNull(inst->GetDataFlowInput(0), inst->GetBasicBlock()->GetLoop()->GetHeader())) {
406 return false;
407 }
408 return InstInputDominatesPreheader(inst) && InstDominatesLoopExits(inst) && !GetGraph()->IsInstThrowable(inst);
409 }
410 } // namespace ark::compiler
411