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 "compiler/optimizer/ir/analysis.h"
18 #include "optimizer/ir/basicblock.h"
19 #include "optimizer/ir/inst.h"
20 #include "optimizer/analysis/alias_analysis.h"
21 #include "optimizer/analysis/dominators_tree.h"
22 #include "optimizer/analysis/rpo.h"
23 #include "optimizer/analysis/loop_analyzer.h"
24 #include "optimizer/optimizations/lse.h"
25
26 namespace ark::compiler {
27
LogInst(const Inst * inst)28 static std::string LogInst(const Inst *inst)
29 {
30 return "v" + std::to_string(inst->GetId()) + " (" + GetOpcodeString(inst->GetOpcode()) + ")";
31 }
32
GetEquivClass(Inst * inst)33 static Lse::EquivClass GetEquivClass(Inst *inst)
34 {
35 switch (inst->GetOpcode()) {
36 case Opcode::LoadArray:
37 case Opcode::LoadArrayI:
38 case Opcode::StoreArray:
39 case Opcode::StoreArrayI:
40 case Opcode::LoadArrayPair:
41 case Opcode::LoadArrayPairI:
42 case Opcode::StoreArrayPair:
43 case Opcode::StoreArrayPairI:
44 case Opcode::LoadConstArray:
45 case Opcode::FillConstArray:
46 return Lse::EquivClass::EQ_ARRAY;
47 case Opcode::LoadStatic:
48 case Opcode::StoreStatic:
49 case Opcode::UnresolvedStoreStatic:
50 case Opcode::LoadResolvedObjectFieldStatic:
51 case Opcode::StoreResolvedObjectFieldStatic:
52 return Lse::EquivClass::EQ_STATIC;
53 case Opcode::LoadString:
54 case Opcode::LoadType:
55 case Opcode::UnresolvedLoadType:
56 return Lse::EquivClass::EQ_POOL;
57 default:
58 return Lse::EquivClass::EQ_OBJECT;
59 }
60 }
61
62 class LseVisitor {
63 public:
LseVisitor(Graph * graph,Lse::HeapEqClasses * heaps)64 explicit LseVisitor(Graph *graph, Lse::HeapEqClasses *heaps)
65 : aa_(graph->GetAnalysis<AliasAnalysis>()),
66 heaps_(*heaps),
67 eliminations_(graph->GetLocalAllocator()->Adapter()),
68 shadowedStores_(graph->GetLocalAllocator()->Adapter()),
69 disabledObjects_(graph->GetLocalAllocator()->Adapter())
70 {
71 }
72
73 NO_MOVE_SEMANTIC(LseVisitor);
74 NO_COPY_SEMANTIC(LseVisitor);
75 ~LseVisitor() = default;
76
MarkForElimination(Inst * inst,Inst * reason,const Lse::HeapValue * hvalue)77 void MarkForElimination(Inst *inst, Inst *reason, const Lse::HeapValue *hvalue)
78 {
79 if (Lse::CanEliminateInstruction(inst)) {
80 auto heap = *hvalue;
81 while (eliminations_.find(heap.val) != eliminations_.end()) {
82 heap = eliminations_[heap.val];
83 }
84 eliminations_[inst] = heap;
85 COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " is eliminated because of " << LogInst(reason);
86 }
87 }
88
CheckMustAlias(Inst * baseObject)89 bool CheckMustAlias(Inst *baseObject)
90 {
91 for (auto disabledObject : disabledObjects_) {
92 aliasCalls_++;
93 if (aa_.CheckRefAlias(baseObject, disabledObject) == MUST_ALIAS) {
94 return true;
95 }
96 }
97 return false;
98 }
99
VisitStore(Inst * inst,Inst * val)100 void VisitStore(Inst *inst, Inst *val)
101 {
102 if (val == nullptr) {
103 /* Instruction has no stored value (e.g. FillConstArray) */
104 return;
105 }
106 auto baseObject = inst->GetDataFlowInput(0);
107 if (CheckMustAlias(baseObject)) {
108 return;
109 }
110 auto hvalue = GetHeapValue(inst);
111 /* Value can be eliminated already */
112 auto alive = val;
113 auto eliminated = eliminations_.find(val);
114 if (eliminated != eliminations_.end()) {
115 alive = eliminated->second.val;
116 }
117 /* If store was assigned to VAL then we can eliminate the second assignment */
118 if (hvalue != nullptr && hvalue->val == alive) {
119 MarkForElimination(inst, hvalue->origin, hvalue);
120 return;
121 }
122 if (hvalue != nullptr && hvalue->origin->IsStore() && !hvalue->read) {
123 if (hvalue->origin->GetBasicBlock() == inst->GetBasicBlock()) {
124 const Lse::HeapValue heap = {inst, alive, false, false};
125 MarkForElimination(hvalue->origin, inst, &heap);
126 } else {
127 shadowedStores_[hvalue->origin].push_back(inst);
128 }
129 }
130 COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " updated heap with v" << alive->GetId();
131
132 /* Stores added to eliminations_ above aren't checked versus phis -> no double instruction elimination */
133 UpdatePhis(inst);
134
135 auto &blockHeap = heaps_[GetEquivClass(inst)].first.at(inst->GetBasicBlock());
136 uint32_t encounters = 0;
137 /* Erase all aliased values, because they may be overwritten */
138 EraseAliasedValues(blockHeap, inst, baseObject, encounters);
139
140 // If we reached limit for this object, remove all its MUST_ALIASed instructions from heap
141 // and disable this object for this BB
142 if (encounters > Lse::LS_ACCESS_LIMIT) {
143 for (auto heapIter = blockHeap.begin(), heapLast = blockHeap.end(); heapIter != heapLast;) {
144 auto hinst = heapIter->first;
145 aliasCalls_++;
146 if (HasBaseObject(hinst) && aa_.CheckRefAlias(baseObject, hinst->GetDataFlowInput(0)) == MUST_ALIAS) {
147 COMPILER_LOG(DEBUG, LSE_OPT)
148 << "\tDrop from heap { " << LogInst(hinst) << ", v" << heapIter->second.val->GetId() << "}";
149 heapIter = blockHeap.erase(heapIter);
150 } else {
151 heapIter++;
152 }
153 }
154 disabledObjects_.push_back(baseObject);
155 return;
156 }
157
158 /* Set value of the inst to VAL */
159 blockHeap[inst] = {inst, alive, false, false};
160 }
161
VisitLoad(Inst * inst)162 void VisitLoad(Inst *inst)
163 {
164 if (HasBaseObject(inst)) {
165 auto input = inst->GetDataFlowInput(0);
166 for (auto disabledObject : disabledObjects_) {
167 aliasCalls_++;
168 if (aa_.CheckRefAlias(input, disabledObject) == MUST_ALIAS) {
169 return;
170 }
171 }
172 }
173 /* If we have a heap value for this load instruction then we can eliminate it */
174 auto hvalue = GetHeapValue(inst);
175 if (hvalue != nullptr) {
176 MarkForElimination(inst, hvalue->origin, hvalue);
177 return;
178 }
179 COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " updated heap with v" << inst->GetId();
180
181 /* Loads added to eliminations_ above are not checked versus phis -> no double instruction elimination */
182 UpdatePhis(inst);
183
184 /* Otherwise set the value of instruction to itself and update MUST_ALIASes */
185 heaps_[GetEquivClass(inst)].first.at(inst->GetBasicBlock())[inst] = {inst, inst, true, false};
186 }
187
HasBaseObject(Inst * inst)188 bool HasBaseObject(Inst *inst)
189 {
190 ASSERT(inst->IsMemory());
191 if (inst->GetInputsCount() == 0 || inst->GetInput(0).GetInst()->IsSaveState() ||
192 inst->GetDataFlowInput(0)->GetType() == DataType::POINTER) {
193 return false;
194 }
195 ASSERT(inst->GetDataFlowInput(0)->IsReferenceOrAny());
196 return true;
197 }
198
VisitIntrinsicCheckInvariant(Inst * inv)199 void VisitIntrinsicCheckInvariant(Inst *inv)
200 {
201 for (int eqClass = Lse::EquivClass::EQ_ARRAY; eqClass != Lse::EquivClass::EQ_LAST; eqClass++) {
202 auto &blockHeap = heaps_[eqClass].first.at(inv->GetBasicBlock());
203 for (auto heapIter = blockHeap.begin(), heapLast = blockHeap.end(); heapIter != heapLast;) {
204 auto hinst = heapIter->first;
205 aliasCalls_++;
206
207 if (!HasBaseObject(hinst) || aa_.CheckRefAlias(inv, hinst->GetDataFlowInput(0)) == NO_ALIAS) {
208 heapIter++;
209 } else {
210 COMPILER_LOG(DEBUG, LSE_OPT)
211 << "\tDrop from heap { " << LogInst(hinst) << ", v" << heapIter->second.val->GetId() << "}";
212 heapIter = blockHeap.erase(heapIter);
213 }
214 }
215 }
216 }
VisitIntrinsic(Inst * inst,InstVector * invs)217 void VisitIntrinsic(Inst *inst, InstVector *invs)
218 {
219 switch (inst->CastToIntrinsic()->GetIntrinsicId()) {
220 #include "intrinsics_lse_heap_inv_args.inl"
221 default:
222 return;
223 }
224 for (auto inv : *invs) {
225 VisitIntrinsicCheckInvariant(inv);
226 }
227 invs->clear();
228 }
229
230 /// Completely resets the accumulated state: heap and phi candidates.
InvalidateHeap(BasicBlock * block)231 void InvalidateHeap(BasicBlock *block)
232 {
233 for (int eqClass = Lse::EquivClass::EQ_ARRAY; eqClass != Lse::EquivClass::EQ_LAST; eqClass++) {
234 heaps_[eqClass].first.at(block).clear();
235 auto loop = block->GetLoop();
236 while (!loop->IsRoot()) {
237 heaps_[eqClass].second.at(loop).clear();
238 loop = loop->GetOuterLoop();
239 }
240 }
241 disabledObjects_.clear();
242 }
243
244 /// Clears heap of local only values as we don't want them to affect analysis further
ClearLocalValuesFromHeap(BasicBlock * block)245 void ClearLocalValuesFromHeap(BasicBlock *block)
246 {
247 for (int eqClass = Lse::EquivClass::EQ_ARRAY; eqClass != Lse::EquivClass::EQ_LAST; eqClass++) {
248 auto &blockHeap = heaps_[eqClass].first.at(block);
249
250 auto heapIt = blockHeap.begin();
251 while (heapIt != blockHeap.end()) {
252 if (heapIt->second.local) {
253 heapIt = blockHeap.erase(heapIt);
254 } else {
255 heapIt++;
256 }
257 }
258 }
259 }
260
261 /// Release objects and reset Alias Analysis count
ResetLimits()262 void ResetLimits()
263 {
264 disabledObjects_.clear();
265 aliasCalls_ = 0;
266 }
267
GetAliasAnalysisCallCount()268 uint32_t GetAliasAnalysisCallCount()
269 {
270 return aliasCalls_;
271 }
272
273 /// Marks all values currently on heap as potentially read
SetHeapAsRead(BasicBlock * block)274 void SetHeapAsRead(BasicBlock *block)
275 {
276 for (int eqClass = Lse::EquivClass::EQ_ARRAY; eqClass != Lse::EquivClass::EQ_LAST; eqClass++) {
277 auto &bheap = heaps_[eqClass].first.at(block);
278 for (auto it = bheap.begin(); it != bheap.end();) {
279 it->second.read = true;
280 it++;
281 }
282 }
283 }
284
GetEliminations()285 auto &GetEliminations()
286 {
287 return eliminations_;
288 }
289
ProcessBackedges(PhiInst * phi,Loop * loop,Inst * cand,InstVector * insts)290 bool ProcessBackedges(PhiInst *phi, Loop *loop, Inst *cand, InstVector *insts)
291 {
292 // Now find which values are alive for each backedge. Due to MUST_ALIAS requirement,
293 // there should only be one
294 auto &heap = heaps_[GetEquivClass(cand)].first;
295 for (auto bb : loop->GetHeader()->GetPredsBlocks()) {
296 if (bb == loop->GetPreHeader()) {
297 phi->AppendInput(cand->IsStore() ? InstStoredValue(cand) : cand);
298 continue;
299 }
300 auto &blockHeap = heap.at(bb);
301 Inst *alive = nullptr;
302 for (auto &entry : blockHeap) {
303 if (std::find(insts->begin(), insts->end(), entry.first) != insts->end()) {
304 alive = entry.first;
305 break;
306 }
307 }
308 if (alive == nullptr) {
309 COMPILER_LOG(DEBUG, LSE_OPT)
310 << "Skipping phi candidate " << LogInst(cand) << ": no alive insts found for backedge block";
311 return false;
312 }
313 // There are several cases when a MUST_ALIAS load is alive at backedge while stores
314 // exist in the loop. The one we're interested here is inner loops.
315 if (alive->IsLoad() && !loop->GetInnerLoops().empty()) {
316 auto it = std::find(insts->rbegin(), insts->rend(), alive);
317 // We've checked that no stores exist in inner loops earlier, which allows us to just check
318 // the first store before the load
319 while (it != insts->rend()) {
320 auto sInst = *it;
321 if (sInst->IsStore()) {
322 break;
323 }
324 it++;
325 }
326 if (it != insts->rend() && (*it)->IsDominate(alive)) {
327 auto val = heap.at((*it)->GetBasicBlock())[(*it)].val;
328 // Use the store instead of load
329 phi->AppendInput(val);
330 // And eliminate load
331 struct Lse::HeapValue hvalue = {*it, val, false, false};
332 MarkForElimination(alive, *it, &hvalue);
333 continue;
334 }
335 }
336 phi->AppendInput(blockHeap[alive].val);
337 }
338 return true;
339 }
340
LoopDoElimination(Inst * cand,Loop * loop,PhiInst * phi,InstVector * insts)341 void LoopDoElimination(Inst *cand, Loop *loop, PhiInst *phi, InstVector *insts)
342 {
343 auto replacement = phi != nullptr ? phi : cand;
344 struct Lse::HeapValue hvalue = {
345 replacement, replacement->IsStore() ? InstStoredValue(replacement) : replacement, false, false};
346 for (auto inst : *insts) {
347 // No need to check MUST_ALIAS again, but we need to check for double elim
348 if (eliminations_.find(inst) != eliminations_.end()) {
349 continue;
350 }
351
352 // Don't replace loads that are also phi inputs
353 if (phi != nullptr &&
354 std::find_if(phi->GetInputs().begin(), phi->GetInputs().end(),
355 [&inst](const Input &x) { return x.GetInst() == inst; }) != phi->GetInputs().end()) {
356 continue;
357 }
358 if (inst->IsLoad()) {
359 MarkForElimination(inst, replacement, &hvalue);
360 }
361 }
362 // And fix savestates for loads
363 if (phi != nullptr) {
364 for (size_t i = 0; i < phi->GetInputsCount(); i++) {
365 auto bb = loop->GetHeader()->GetPredecessor(i);
366 auto phiInput = phi->GetInput(i).GetInst();
367 if (bb == loop->GetPreHeader() && cand->IsLoad() && cand->IsMovableObject()) {
368 ssb_.SearchAndCreateMissingObjInSaveState(bb->GetGraph(), cand, phi);
369 } else if (phiInput->IsMovableObject()) {
370 ssb_.SearchAndCreateMissingObjInSaveState(bb->GetGraph(), phiInput, bb->GetLastInst(), nullptr, bb);
371 }
372 }
373 } else if (hvalue.val->IsMovableObject()) {
374 ASSERT(!loop->IsIrreducible());
375 auto head = loop->GetHeader();
376 // Add cand value to all SaveStates in this loop and inner loops
377 ssb_.SearchAndCreateMissingObjInSaveState(head->GetGraph(), hvalue.val, head->GetLastInst(), nullptr, head);
378 }
379 }
380
381 /// Add eliminations inside loops if there is no overwrites on backedges.
FinalizeLoops(Graph * graph,const ArenaVector<Loop * > & rpoLoops)382 void FinalizeLoops(Graph *graph, const ArenaVector<Loop *> &rpoLoops)
383 {
384 for (int eqClass = Lse::EquivClass::EQ_ARRAY; eqClass != Lse::EquivClass::EQ_LAST; eqClass++) {
385 for (auto loopIt = rpoLoops.rbegin(); loopIt != rpoLoops.rend(); loopIt++) {
386 auto loop = *loopIt;
387 auto &phis = heaps_[eqClass].second.at(loop);
388 COMPILER_LOG(DEBUG, LSE_OPT) << "Finalizing loop #" << loop->GetId();
389 FinalizeLoopsWithPhiCands(graph, loop, phis);
390 }
391
392 COMPILER_LOG(DEBUG, LSE_OPT) << "Fixing elimination list after backedge substitutions";
393 for (auto &entry : eliminations_) {
394 auto hvalue = entry.second;
395 if (eliminations_.find(hvalue.val) == eliminations_.end()) {
396 continue;
397 }
398
399 [[maybe_unused]] auto initial = hvalue.val;
400 while (eliminations_.find(hvalue.val) != eliminations_.end()) {
401 auto elimValue = eliminations_[hvalue.val];
402 COMPILER_LOG(DEBUG, LSE_OPT) << "\t" << LogInst(hvalue.val)
403 << " is eliminated. Trying to replace by " << LogInst(elimValue.val);
404 hvalue = elimValue;
405 ASSERT_PRINT(initial != hvalue.val, "A cyclic elimination has been detected");
406 }
407 entry.second = hvalue;
408 }
409 }
410 }
411
ExistsPathWithoutShadowingStores(BasicBlock * start,BasicBlock * block,Marker marker)412 bool ExistsPathWithoutShadowingStores(BasicBlock *start, BasicBlock *block, Marker marker)
413 {
414 if (block->IsEndBlock()) {
415 // Found a path without shadowing stores
416 return true;
417 }
418 if (block->IsMarked(marker)) {
419 // Found a path with shadowing store
420 return false;
421 }
422 ASSERT(start->GetLoop() == block->GetLoop());
423 for (auto succ : block->GetSuccsBlocks()) {
424 if (block->GetLoop() != succ->GetLoop()) {
425 // Edge to a different loop. We currently don't carry heap values through loops and don't
426 // handle irreducible loops, so we can't be sure there are shadows on this path.
427 return true;
428 }
429 if (succ == block->GetLoop()->GetHeader()) {
430 // If next block is a loop header for this block's loop it means that instruction shadows itself
431 return true;
432 }
433 if (ExistsPathWithoutShadowingStores(start, succ, marker)) {
434 return true;
435 }
436 }
437 return false;
438 }
439
FinalizeShadowedStores()440 void FinalizeShadowedStores()
441 {
442 // We want to see if there are no paths from the store that evade any of its shadow stores
443 for (auto &entry : shadowedStores_) {
444 auto inst = entry.first;
445 auto marker = inst->GetBasicBlock()->GetGraph()->NewMarker();
446 auto &shadows = shadowedStores_.at(inst);
447 for (auto shadow : shadows) {
448 shadow->GetBasicBlock()->SetMarker(marker);
449 }
450 if (!ExistsPathWithoutShadowingStores(inst->GetBasicBlock(), inst->GetBasicBlock(), marker)) {
451 COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " is fully shadowed by aliased stores";
452 auto alive = InstStoredValue(entry.second[0]);
453 auto eliminated = eliminations_.find(alive);
454 if (eliminated != eliminations_.end()) {
455 alive = eliminated->second.val;
456 }
457 const Lse::HeapValue heap = {entry.second[0], alive, false, false};
458 MarkForElimination(inst, entry.second[0], &heap);
459 }
460 inst->GetBasicBlock()->GetGraph()->EraseMarker(marker);
461 }
462 }
463
464 private:
465 /// Return a MUST_ALIASed heap entry, nullptr if not present.
GetHeapValue(Inst * inst)466 const Lse::HeapValue *GetHeapValue(Inst *inst)
467 {
468 auto &blockHeap = heaps_[GetEquivClass(inst)].first.at(inst->GetBasicBlock());
469 for (auto &entry : blockHeap) {
470 aliasCalls_++;
471 if (aa_.CheckInstAlias(inst, entry.first) == MUST_ALIAS) {
472 return &entry.second;
473 }
474 }
475 return nullptr;
476 }
477
478 /// Update phi candidates with aliased accesses
UpdatePhis(Inst * inst)479 void UpdatePhis(Inst *inst)
480 {
481 Loop *loop = inst->GetBasicBlock()->GetLoop();
482
483 while (!loop->IsRoot()) {
484 auto &phis = heaps_[GetEquivClass(inst)].second.at(loop);
485 for (auto &[mem, values] : phis) {
486 aliasCalls_++;
487 if (aa_.CheckInstAlias(inst, mem) != NO_ALIAS) {
488 values.push_back(inst);
489 }
490 }
491 loop = loop->GetOuterLoop();
492 }
493 }
494
EraseAliasedValues(Lse::BasicBlockHeap & blockHeap,Inst * inst,Inst * baseObject,uint32_t & encounters)495 void EraseAliasedValues(Lse::BasicBlockHeap &blockHeap, Inst *inst, Inst *baseObject, uint32_t &encounters)
496 {
497 for (auto heapIter = blockHeap.begin(), heapLast = blockHeap.end(); heapIter != heapLast;) {
498 auto hinst = heapIter->first;
499 ASSERT(GetEquivClass(inst) == GetEquivClass(hinst));
500 aliasCalls_++;
501 if (aa_.CheckInstAlias(inst, hinst) == NO_ALIAS) {
502 // Keep track if it's the same object but with different offset
503 heapIter++;
504 aliasCalls_++;
505 if (aa_.CheckRefAlias(baseObject, hinst->GetDataFlowInput(0)) == MUST_ALIAS) {
506 encounters++;
507 }
508 } else {
509 COMPILER_LOG(DEBUG, LSE_OPT)
510 << "\tDrop from heap { " << LogInst(hinst) << ", v" << heapIter->second.val->GetId() << "}";
511 heapIter = blockHeap.erase(heapIter);
512 }
513 }
514 }
515
FinalizeLoopsWithPhiCands(Graph * graph,Loop * loop,ArenaMap<Inst *,InstVector> & phis)516 void FinalizeLoopsWithPhiCands(Graph *graph, Loop *loop, ArenaMap<Inst *, InstVector> &phis)
517 {
518 InstVector instsMustAlias(graph->GetLocalAllocator()->Adapter());
519 for (auto &[cand, insts] : phis) {
520 if (insts.empty()) {
521 COMPILER_LOG(DEBUG, LSE_OPT) << "Skipping phi candidate " << LogInst(cand) << " (no users)";
522 continue;
523 }
524
525 instsMustAlias.clear();
526
527 COMPILER_LOG(DEBUG, LSE_OPT) << "Processing phi candidate: " << LogInst(cand);
528 bool hasStores = false;
529 bool hasLoads = false;
530 bool valid = true;
531
532 for (auto inst : insts) {
533 // Skip eliminated instructions
534 if (eliminations_.find(inst) != eliminations_.end()) {
535 continue;
536 }
537 auto aliasResult = aa_.CheckInstAlias(cand, inst);
538 if (aliasResult == MAY_ALIAS && inst->IsLoad()) {
539 // Ignore MAY_ALIAS loads, they won't interfere with our analysis
540 continue;
541 } else if (aliasResult == MAY_ALIAS) { // NOLINT(readability-else-after-return)
542 // If we have a MAY_ALIAS store we can't be sure about our values
543 // in phi creation
544 ASSERT(inst->IsStore());
545 COMPILER_LOG(DEBUG, LSE_OPT)
546 << "Skipping phi candidate " << LogInst(cand) << ": MAY_ALIAS by " << LogInst(inst);
547 valid = false;
548 break;
549 }
550 ASSERT(aliasResult == MUST_ALIAS);
551
552 if (inst->IsStore() && inst->GetBasicBlock()->GetLoop() != loop) {
553 // We can handle if loads are in inner loop, but if a store is in inner loop
554 // then we can't replace anything
555 COMPILER_LOG(DEBUG, LSE_OPT)
556 << "Skipping phi candidate " << LogInst(cand) << ": " << LogInst(inst) << " is in inner loop";
557 valid = false;
558 break;
559 }
560
561 instsMustAlias.push_back(inst);
562 if (inst->IsStore()) {
563 hasStores = true;
564 } else if (inst->IsLoad()) {
565 hasLoads = true;
566 }
567 }
568 // Other than validity, it's also possible that all instructions are already eliminated
569 if (!valid || instsMustAlias.empty()) {
570 continue;
571 }
572
573 TryLoopDoElimination(cand, loop, &instsMustAlias, hasLoads, hasStores);
574 }
575 }
576
TryLoopDoElimination(Inst * cand,Loop * loop,InstVector * insts,bool hasLoads,bool hasStores)577 void TryLoopDoElimination(Inst *cand, Loop *loop, InstVector *insts, bool hasLoads, bool hasStores)
578 {
579 if (hasStores) {
580 if (!hasLoads) {
581 // Nothing to replace
582 COMPILER_LOG(DEBUG, LSE_OPT)
583 << "Skipping phi candidate " << LogInst(cand) << ": no loads to convert to phi";
584 return;
585 }
586
587 auto phi = cand->GetBasicBlock()->GetGraph()->CreateInstPhi(cand->GetType(), cand->GetPc());
588 loop->GetHeader()->AppendPhi(phi);
589
590 if (!ProcessBackedges(phi, loop, cand, insts)) {
591 loop->GetHeader()->RemoveInst(phi);
592 return;
593 }
594
595 LoopDoElimination(cand, loop, phi, insts);
596 } else {
597 ASSERT(hasLoads);
598 // Without stores, we can replace all MUST_ALIAS loads with instruction itself
599 LoopDoElimination(cand, loop, nullptr, insts);
600 }
601 }
602
603 private:
604 AliasAnalysis &aa_;
605 Lse::HeapEqClasses &heaps_;
606 /* Map of instructions to be deleted with values to replace them with */
607 Lse::BasicBlockHeap eliminations_;
608 ArenaMap<Inst *, std::vector<Inst *>> shadowedStores_;
609 SaveStateBridgesBuilder ssb_;
610 InstVector disabledObjects_;
611 uint32_t aliasCalls_ {0};
612 };
613
614 /// Returns true if the instruction invalidates the whole heap
IsHeapInvalidatingInst(Inst * inst)615 static bool IsHeapInvalidatingInst(Inst *inst)
616 {
617 switch (inst->GetOpcode()) {
618 case Opcode::LoadStatic:
619 return inst->CastToLoadStatic()->GetVolatile();
620 case Opcode::LoadObject:
621 return inst->CastToLoadObject()->GetVolatile();
622 case Opcode::InitObject:
623 case Opcode::InitClass:
624 case Opcode::LoadAndInitClass:
625 case Opcode::UnresolvedLoadAndInitClass:
626 case Opcode::UnresolvedStoreStatic:
627 case Opcode::ResolveObjectField:
628 case Opcode::ResolveObjectFieldStatic:
629 return true; // runtime calls invalidate the heap
630 case Opcode::CallVirtual:
631 return !inst->CastToCallVirtual()->IsInlined();
632 case Opcode::CallResolvedVirtual:
633 return !inst->CastToCallResolvedVirtual()->IsInlined();
634 case Opcode::CallStatic:
635 return !inst->CastToCallStatic()->IsInlined();
636 case Opcode::CallDynamic:
637 return !inst->CastToCallDynamic()->IsInlined();
638 case Opcode::CallResolvedStatic:
639 return !inst->CastToCallResolvedStatic()->IsInlined();
640 case Opcode::Monitor:
641 return inst->CastToMonitor()->IsEntry();
642 default:
643 return inst->GetFlag(compiler::inst_flags::HEAP_INV);
644 }
645 }
646
647 /// Returns true if the instruction reads from heap and we're not sure about its effects
IsHeapReadingInst(Inst * inst)648 static bool IsHeapReadingInst(Inst *inst)
649 {
650 if (inst->CanThrow()) {
651 return true;
652 }
653 if (inst->IsIntrinsic()) {
654 for (auto input : inst->GetInputs()) {
655 if (input.GetInst()->GetType() == DataType::REFERENCE) {
656 return true;
657 }
658 }
659 }
660 if (inst->IsStore() && IsVolatileMemInst(inst)) {
661 // Heap writes before the volatile store should be visible from another thread after a corresponding volatile
662 // load
663 return true;
664 }
665 if (inst->GetOpcode() == Opcode::Monitor) {
666 return inst->CastToMonitor()->IsExit();
667 }
668 return false;
669 }
670
CanEliminateInstruction(Inst * inst)671 bool Lse::CanEliminateInstruction(Inst *inst)
672 {
673 if (inst->IsBarrier()) {
674 COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " was suppressed: a barrier";
675 return false;
676 }
677 auto loop = inst->GetBasicBlock()->GetLoop();
678 if (loop->IsIrreducible()) {
679 COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " was suppressed: an irreducible loop";
680 return false;
681 }
682 if (loop->IsOsrLoop()) {
683 COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " was suppressed: an OSR loop";
684 return false;
685 }
686 if (loop->IsTryCatchLoop()) {
687 COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " was suppressed: an try-catch loop";
688 return false;
689 }
690 return true;
691 }
692
InitializeHeap(BasicBlock * block,HeapEqClasses * heaps)693 void Lse::InitializeHeap(BasicBlock *block, HeapEqClasses *heaps)
694 {
695 for (int eqClass = Lse::EquivClass::EQ_ARRAY; eqClass != Lse::EquivClass::EQ_LAST; eqClass++) {
696 auto &heap = (*heaps)[eqClass].first;
697 auto &phiCands = (*heaps)[eqClass].second;
698 [[maybe_unused]] auto it = heap.emplace(block, GetGraph()->GetLocalAllocator()->Adapter());
699 ASSERT(it.second);
700 if (block->IsLoopHeader()) {
701 COMPILER_LOG(DEBUG, LSE_OPT) << "Append loop #" << block->GetLoop()->GetId();
702 if (std::find(rpoLoops_.begin(), rpoLoops_.end(), block->GetLoop()) == rpoLoops_.end()) {
703 rpoLoops_.emplace_back(block->GetLoop());
704 }
705 [[maybe_unused]] auto phit = phiCands.emplace(block->GetLoop(), GetGraph()->GetLocalAllocator()->Adapter());
706 ASSERT(phit.second);
707 }
708 }
709 }
710
711 /**
712 * While entering in the loop we put all heap values obtained from loads as phi candidates.
713 * Further phi candidates would replace MUST_ALIAS accesses in the loop if no aliased stores were met.
714 */
MergeHeapValuesForLoop(BasicBlock * block,HeapEqClasses * heaps)715 void Lse::MergeHeapValuesForLoop(BasicBlock *block, HeapEqClasses *heaps)
716 {
717 ASSERT(block->IsLoopHeader());
718
719 // Do not eliminate anything in irreducible or osr loops
720 auto loop = block->GetLoop();
721 if (loop->IsIrreducible() || loop->IsOsrLoop() || loop->IsTryCatchLoop()) {
722 return;
723 }
724
725 auto preheader = loop->GetPreHeader();
726
727 for (int eqClass = Lse::EquivClass::EQ_ARRAY; eqClass != Lse::EquivClass::EQ_LAST; eqClass++) {
728 auto &preheaderHeap = (*heaps)[eqClass].first.at(preheader);
729
730 auto &blockPhis = (*heaps)[eqClass].second.at(loop);
731 for (auto mem : preheaderHeap) {
732 blockPhis.try_emplace(mem.second.origin, GetGraph()->GetLocalAllocator()->Adapter());
733 COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(mem.first) << " is a phi cand for BB #" << block->GetId();
734 }
735 }
736 }
737
738 /// Merge heap values for passed block from its direct predecessors.
MergeHeapValuesForBlock(BasicBlock * block,HeapEqClasses * heaps,Marker phiFixupMrk)739 size_t Lse::MergeHeapValuesForBlock(BasicBlock *block, HeapEqClasses *heaps, Marker phiFixupMrk)
740 {
741 size_t aliasCalls = 0;
742 for (int eqClass = Lse::EquivClass::EQ_ARRAY; eqClass != Lse::EquivClass::EQ_LAST; eqClass++) {
743 auto &heap = (*heaps)[eqClass].first;
744 auto &blockHeap = heap.at(block);
745 /* Copy a heap of one of predecessors */
746 auto preds = block->GetPredsBlocks();
747 auto predIt = preds.begin();
748 if (predIt != preds.end()) {
749 blockHeap.insert(heap.at(*predIt).begin(), heap.at(*predIt).end());
750 predIt++;
751 }
752
753 /* Erase from the heap anything that disappeared or was changed in other predecessors */
754 while (predIt != preds.end()) {
755 auto predHeap = heap.at(*predIt);
756 auto heapIt = blockHeap.begin();
757 while (heapIt != blockHeap.end()) {
758 auto &heapValue = heapIt->second;
759 auto predInstIt = predHeap.begin();
760 while (predInstIt != predHeap.end()) {
761 if (predInstIt->first == heapIt->first) {
762 break;
763 }
764 aliasCalls++;
765 if (GetGraph()->CheckInstAlias(heapIt->first, predInstIt->first) == MUST_ALIAS) {
766 break;
767 }
768 predInstIt++;
769 }
770 if (predInstIt == predHeap.end()) {
771 // If this is a phi we're creating during merge, delete it
772 if (heapValue.val->IsPhi() && heapValue.local) {
773 block->RemoveInst(heapValue.val);
774 }
775 heapIt = blockHeap.erase(heapIt);
776 continue;
777 }
778 if (predInstIt->second.val != heapValue.val) {
779 // Try to create a phi instead
780 // We limit possible phis to cases where value originated in the same predecessor
781 // in order to not increase register usage too much
782 if (block->GetLoop()->IsIrreducible() || block->IsCatch() ||
783 predInstIt->second.origin->GetBasicBlock() != *predIt) {
784 // If this is a phi we're creating during merge, delete it
785 if (heapValue.val->IsPhi() && heapValue.local) {
786 block->RemoveInst(heapValue.val);
787 }
788 heapIt = blockHeap.erase(heapIt);
789 continue;
790 }
791 if (heapValue.val->IsPhi() && heapValue.local) {
792 heapValue.val->AppendInput(predInstIt->second.val);
793 } else {
794 // Values can only originate in a single block. If this predecessor is not
795 // the second predecessor, that means that this value did not originate in other
796 // predecessors, thus we don't create a phi
797 if (heapValue.origin->GetBasicBlock() != *(preds.begin()) ||
798 std::distance(preds.begin(), predIt) > 1) {
799 heapIt = blockHeap.erase(heapIt);
800 continue;
801 }
802 auto phi =
803 block->GetGraph()->CreateInstPhi(heapValue.origin->GetType(), heapValue.origin->GetPc());
804 block->AppendPhi(phi);
805 phi->AppendInput(heapValue.val);
806 phi->AppendInput(predInstIt->second.val);
807 phi->SetMarker(phiFixupMrk);
808 heapValue.val = phi;
809 heapValue.local = true;
810 }
811 } else {
812 heapIt->second.read |= predInstIt->second.read;
813 }
814 heapIt++;
815 }
816 predIt++;
817 }
818 }
819 return aliasCalls;
820 }
821
822 /**
823 * When creating phis while merging predecessor heaps, we don't know yet if
824 * we're creating a useful phi, and can't fix SaveStates because of that.
825 * Do that here.
826 */
FixupPhisInBlock(BasicBlock * block,Marker phiFixupMrk)827 void Lse::FixupPhisInBlock(BasicBlock *block, Marker phiFixupMrk)
828 {
829 for (auto phiInst : block->PhiInstsSafe()) {
830 auto phi = phiInst->CastToPhi();
831 if (!phi->IsMarked(phiFixupMrk)) {
832 continue;
833 }
834 if (!phi->HasUsers()) {
835 block->RemoveInst(phi);
836 } else if (GetGraph()->IsBytecodeOptimizer() || !phi->IsReferenceOrAny()) {
837 continue;
838 }
839 // Here case: !GetGraph()->IsBytecodeOptimizer() && phi->IsReferenceOrAny()
840 for (auto i = 0U; i < phi->GetInputsCount(); i++) {
841 auto input = phi->GetInput(i);
842 if (input.GetInst()->IsMovableObject()) {
843 auto bb = phi->GetPhiInputBb(i);
844 ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), input.GetInst(), bb->GetLastInst(), nullptr, bb);
845 }
846 }
847 }
848 }
849
850 /**
851 * Returns the elimination code in two letter format.
852 *
853 * The first letter describes a [L]oad or [S]tore that was eliminated.
854 * The second letter describes the dominant [L]oad or [S]tore that is the
855 * reason why instruction was eliminated.
856 */
GetEliminationCode(Inst * inst,Inst * origin)857 const char *Lse::GetEliminationCode(Inst *inst, Inst *origin)
858 {
859 ASSERT(inst->IsMemory() && (origin->IsMemory() || origin->IsPhi()));
860 if (inst->IsLoad()) {
861 if (origin->IsLoad()) {
862 return "LL";
863 }
864 if (origin->IsStore()) {
865 return "LS";
866 }
867 if (origin->IsPhi()) {
868 return "LP";
869 }
870 }
871 if (inst->IsStore()) {
872 if (origin->IsLoad()) {
873 return "SL";
874 }
875 if (origin->IsStore()) {
876 return "SS";
877 }
878 if (origin->IsPhi()) {
879 return "SP";
880 }
881 }
882 UNREACHABLE();
883 }
884
885 /**
886 * In the codegen of bytecode optimizer, we don't have corresponding pandasm
887 * for the IR `Cast` of with some pairs of input types and output types. So
888 * in the bytecode optimizer mode, we need to avoid generating such `Cast` IR.
889 * The following function gives the list of legal pairs of types.
890 * This function should not be used in compiler mode.
891 */
892
IsTypeLegalForCast(DataType::Type output,DataType::Type input)893 static bool IsTypeLegalForCast(DataType::Type output, DataType::Type input)
894 {
895 ASSERT(output != input);
896 switch (input) {
897 case DataType::INT32:
898 case DataType::INT64:
899 case DataType::FLOAT64:
900 switch (output) {
901 case DataType::FLOAT64:
902 case DataType::INT64:
903 case DataType::UINT32:
904 case DataType::INT32:
905 case DataType::INT16:
906 case DataType::UINT16:
907 case DataType::INT8:
908 case DataType::UINT8:
909 case DataType::ANY:
910 return true;
911 default:
912 return false;
913 }
914 case DataType::REFERENCE:
915 return output == DataType::ANY;
916 default:
917 return false;
918 }
919 }
920
921 /**
922 * Replace inputs of INST with VALUE and delete this INST. If deletion led to
923 * appearance of instruction that has no users delete this instruction too.
924 */
DeleteInstruction(Inst * inst,Inst * value)925 void Lse::DeleteInstruction(Inst *inst, Inst *value)
926 {
927 // Have to cast a value to the type of eliminated inst. Actually required only for loads.
928 if (inst->GetType() != value->GetType() && inst->HasUsers()) {
929 ASSERT(inst->GetType() != DataType::REFERENCE && value->GetType() != DataType::REFERENCE);
930 // We will do nothing in bytecode optimizer mode when the types are not legal for cast.
931 if (GetGraph()->IsBytecodeOptimizer() && !IsTypeLegalForCast(inst->GetType(), value->GetType())) {
932 COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " was not eliminated: requires an inappropriate cast";
933 return;
934 }
935 auto cast = GetGraph()->CreateInstCast(inst->GetType(), inst->GetPc(), value, value->GetType());
936 inst->InsertAfter(cast);
937 value = cast;
938 }
939 inst->ReplaceUsers(value);
940
941 ArenaQueue<Inst *> queue(GetGraph()->GetLocalAllocator()->Adapter());
942 queue.push(inst);
943 while (!queue.empty()) {
944 Inst *frontInst = queue.front();
945 BasicBlock *block = frontInst->GetBasicBlock();
946 queue.pop();
947
948 // Have been already deleted or could not be deleted
949 if (block == nullptr || frontInst->HasUsers()) {
950 continue;
951 }
952
953 for (auto &input : frontInst->GetInputs()) {
954 /* Delete only instructions that has no data flow impact */
955 if (input.GetInst()->HasPseudoDestination()) {
956 queue.push(input.GetInst());
957 }
958 }
959 block->RemoveInst(frontInst);
960 applied_ = true;
961 }
962 }
963
DeleteInstructions(const BasicBlockHeap & eliminated)964 void Lse::DeleteInstructions(const BasicBlockHeap &eliminated)
965 {
966 for (auto elim : eliminated) {
967 Inst *inst = elim.first;
968 Inst *origin = elim.second.origin;
969 Inst *value = elim.second.val;
970
971 ASSERT_DO(eliminated.find(value) == eliminated.end(),
972 (std::cerr << "Instruction:\n", inst->Dump(&std::cerr),
973 std::cerr << "is replaced by eliminated value:\n", value->Dump(&std::cerr)));
974
975 while (origin->GetBasicBlock() == nullptr) {
976 auto elimIt = eliminated.find(origin);
977 ASSERT(elimIt != eliminated.end());
978 origin = elimIt->second.origin;
979 }
980
981 GetGraph()->GetEventWriter().EventLse(inst->GetId(), inst->GetPc(), origin->GetId(), origin->GetPc(),
982 GetEliminationCode(inst, origin));
983 // Try to update savestates
984 if (!GetGraph()->IsBytecodeOptimizer() && value->IsMovableObject()) {
985 if (!value->IsPhi() && origin->IsMovableObject() && origin->IsLoad() && origin->IsDominate(inst)) {
986 // this branch is not required, but can be faster if origin is closer to inst than value
987 ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), origin, inst);
988 } else {
989 ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), value, inst);
990 }
991 }
992 DeleteInstruction(inst, value);
993 }
994 }
995
ApplyHoistToCandidate(Loop * loop,Inst * alive)996 void Lse::ApplyHoistToCandidate(Loop *loop, Inst *alive)
997 {
998 ASSERT(alive->IsLoad());
999 COMPILER_LOG(DEBUG, LSE_OPT) << " v" << alive->GetId();
1000 if (alive->GetBasicBlock()->GetLoop() != loop) {
1001 COMPILER_LOG(DEBUG, LSE_OPT) << "\tFailed because inst is part of a more inner loop";
1002 return;
1003 }
1004 if (GetGraph()->IsInstThrowable(alive)) {
1005 COMPILER_LOG(DEBUG, LSE_OPT) << "\tFailed because inst is throwable";
1006 return;
1007 }
1008 for (const auto &input : alive->GetInputs()) {
1009 if (!input.GetInst()->GetBasicBlock()->IsDominate(loop->GetPreHeader())) {
1010 COMPILER_LOG(DEBUG, LSE_OPT) << "\tFailed because of def-use chain of inputs: " << LogInst(input.GetInst());
1011 return;
1012 }
1013 }
1014 const auto &rpo = GetGraph()->GetBlocksRPO();
1015 auto blockIter = std::find(rpo.rbegin(), rpo.rend(), alive->GetBasicBlock());
1016 ASSERT(blockIter != rpo.rend());
1017 auto inst = alive->GetPrev();
1018 while (*blockIter != loop->GetPreHeader()) {
1019 while (inst != nullptr) {
1020 if (IsHeapInvalidatingInst(inst) || (inst->IsMemory() && GetEquivClass(inst) == GetEquivClass(alive) &&
1021 GetGraph()->CheckInstAlias(inst, alive) != NO_ALIAS)) {
1022 COMPILER_LOG(DEBUG, LSE_OPT) << "\tFailed because of invalidating inst:" << LogInst(inst);
1023 return;
1024 }
1025 inst = inst->GetPrev();
1026 }
1027 blockIter++;
1028 inst = (*blockIter)->GetLastInst();
1029 }
1030 alive->GetBasicBlock()->EraseInst(alive, true);
1031 auto lastInst = loop->GetPreHeader()->GetLastInst();
1032 if (lastInst != nullptr && lastInst->IsControlFlow()) {
1033 loop->GetPreHeader()->InsertBefore(alive, lastInst);
1034 } else {
1035 loop->GetPreHeader()->AppendInst(alive);
1036 }
1037 if (!GetGraph()->IsBytecodeOptimizer() && alive->IsMovableObject()) {
1038 ASSERT(!loop->IsIrreducible());
1039 // loop backedges will be walked inside SSB
1040 ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), alive, loop->GetHeader()->GetLastInst(), nullptr,
1041 loop->GetHeader());
1042 }
1043 applied_ = true;
1044 }
1045
TryToHoistLoadFromLoop(Loop * loop,HeapEqClasses * heaps,const BasicBlockHeap * eliminated)1046 void Lse::TryToHoistLoadFromLoop(Loop *loop, HeapEqClasses *heaps, const BasicBlockHeap *eliminated)
1047 {
1048 for (auto innerLoop : loop->GetInnerLoops()) {
1049 TryToHoistLoadFromLoop(innerLoop, heaps, eliminated);
1050 }
1051
1052 if (loop->IsIrreducible() || loop->IsOsrLoop()) {
1053 return;
1054 }
1055
1056 auto &backBbs = loop->GetBackEdges();
1057 beAlive_.clear();
1058
1059 // Initiate alive set
1060 auto backBb = backBbs.begin();
1061 ASSERT(backBb != backBbs.end());
1062 for (int eqClass = Lse::EquivClass::EQ_ARRAY; eqClass != Lse::EquivClass::EQ_LAST; eqClass++) {
1063 for (const auto &entry : (*heaps)[eqClass].first.at(*backBb)) {
1064 // Do not touch Stores and eliminated ones
1065 if (entry.first->IsLoad() && eliminated->find(entry.first) == eliminated->end()) {
1066 beAlive_.insert(entry.first);
1067 }
1068 }
1069 }
1070
1071 // Throw values not alive on other backedges
1072 while (++backBb != backBbs.end()) {
1073 auto alive = beAlive_.begin();
1074 while (alive != beAlive_.end()) {
1075 auto &heap = heaps->at(GetEquivClass(*alive)).first;
1076 if (heap.at(*backBb).find(*alive) == heap.at(*backBb).end()) {
1077 alive = beAlive_.erase(alive);
1078 } else {
1079 alive++;
1080 }
1081 }
1082 }
1083 COMPILER_LOG(DEBUG, LSE_OPT) << "Loop #" << loop->GetId() << " has the following motion candidates:";
1084 for (auto alive : beAlive_) {
1085 ApplyHoistToCandidate(loop, alive);
1086 }
1087 }
1088
ProcessAllBBs(LseVisitor & visitor,HeapEqClasses * heaps,Marker phiFixupMrk)1089 void Lse::ProcessAllBBs(LseVisitor &visitor, HeapEqClasses *heaps, Marker phiFixupMrk)
1090 {
1091 InstVector invs(GetGraph()->GetLocalAllocator()->Adapter());
1092 size_t aliasCalls = 0;
1093 for (auto block : GetGraph()->GetBlocksRPO()) {
1094 COMPILER_LOG(DEBUG, LSE_OPT) << "Processing BB " << block->GetId();
1095 InitializeHeap(block, heaps);
1096
1097 if (block->IsLoopHeader()) {
1098 MergeHeapValuesForLoop(block, heaps);
1099 } else {
1100 aliasCalls += MergeHeapValuesForBlock(block, heaps, phiFixupMrk);
1101 }
1102
1103 for (auto inst : block->Insts()) {
1104 if (IsHeapReadingInst(inst)) {
1105 visitor.SetHeapAsRead(block);
1106 }
1107 if (IsHeapInvalidatingInst(inst)) {
1108 COMPILER_LOG(DEBUG, LSE_OPT) << LogInst(inst) << " invalidates heap";
1109 visitor.InvalidateHeap(block);
1110 } else if (inst->IsLoad()) {
1111 visitor.VisitLoad(inst);
1112 } else if (inst->IsStore()) {
1113 visitor.VisitStore(inst, InstStoredValue(inst));
1114 }
1115 if (inst->IsIntrinsic()) {
1116 visitor.VisitIntrinsic(inst, &invs);
1117 }
1118 // If we call Alias Analysis too much, we assume that this block has too many
1119 // instructions and we should bail in favor of performance.
1120 if (visitor.GetAliasAnalysisCallCount() + aliasCalls > Lse::AA_CALLS_LIMIT) {
1121 COMPILER_LOG(DEBUG, LSE_OPT) << "Exiting BB " << block->GetId() << ": too many Alias Analysis calls";
1122 visitor.InvalidateHeap(block);
1123 break;
1124 }
1125 }
1126 visitor.ClearLocalValuesFromHeap(block);
1127 visitor.ResetLimits();
1128 }
1129 }
1130
RunImpl()1131 bool Lse::RunImpl()
1132 {
1133 if (GetGraph()->IsBytecodeOptimizer() && GetGraph()->IsDynamicMethod()) {
1134 COMPILER_LOG(DEBUG, LSE_OPT) << "Load-Store Elimination skipped: es bytecode optimizer";
1135 return false;
1136 }
1137
1138 HeapEqClasses heaps(GetGraph()->GetLocalAllocator()->Adapter());
1139 for (int eqClass = Lse::EquivClass::EQ_ARRAY; eqClass != Lse::EquivClass::EQ_LAST; eqClass++) {
1140 std::pair<Heap, PhiCands> heapPhi(GetGraph()->GetLocalAllocator()->Adapter(),
1141 GetGraph()->GetLocalAllocator()->Adapter());
1142 heaps.emplace_back(heapPhi);
1143 }
1144
1145 GetGraph()->RunPass<LoopAnalyzer>();
1146 GetGraph()->RunPass<AliasAnalysis>();
1147
1148 LseVisitor visitor(GetGraph(), &heaps);
1149 auto markerHolder = MarkerHolder(GetGraph());
1150 auto phiFixupMrk = markerHolder.GetMarker();
1151
1152 ProcessAllBBs(visitor, &heaps, phiFixupMrk);
1153
1154 visitor.FinalizeShadowedStores();
1155 visitor.FinalizeLoops(GetGraph(), rpoLoops_);
1156
1157 auto &eliminated = visitor.GetEliminations();
1158 GetGraph()->RunPass<DominatorsTree>();
1159 if (hoistLoads_) {
1160 for (auto loop : GetGraph()->GetRootLoop()->GetInnerLoops()) {
1161 TryToHoistLoadFromLoop(loop, &heaps, &eliminated);
1162 }
1163 }
1164
1165 DeleteInstructions(visitor.GetEliminations());
1166
1167 for (auto block : GetGraph()->GetBlocksRPO()) {
1168 FixupPhisInBlock(block, phiFixupMrk);
1169 }
1170
1171 COMPILER_LOG(DEBUG, LSE_OPT) << "Load-Store Elimination complete";
1172 return applied_;
1173 }
1174 } // namespace ark::compiler
1175