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