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