• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023 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 "interop_intrinsic_optimization.h"
17 #include "optimizer/ir/runtime_interface.h"
18 #include "optimizer/analysis/countable_loop_parser.h"
19 #include "optimizer/analysis/loop_analyzer.h"
20 #include "plugins/ets/compiler/generated/interop_intrinsic_kinds.h"
21 
22 namespace panda::compiler {
23 
IsForbiddenInst(Inst * inst)24 static bool IsForbiddenInst(Inst *inst)
25 {
26     return inst->IsCall() && !static_cast<CallInst *>(inst)->IsInlined();
27 }
28 
IsScopeStart(Inst * inst)29 static bool IsScopeStart(Inst *inst)
30 {
31     return inst->IsIntrinsic() && inst->CastToIntrinsic()->GetIntrinsicId() ==
32                                       RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CREATE_LOCAL_SCOPE;
33 }
34 
IsScopeEnd(Inst * inst)35 static bool IsScopeEnd(Inst *inst)
36 {
37     return inst->IsIntrinsic() && inst->CastToIntrinsic()->GetIntrinsicId() ==
38                                       RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_DESTROY_LOCAL_SCOPE;
39 }
40 
IsWrapIntrinsicId(RuntimeInterface::IntrinsicId id)41 static bool IsWrapIntrinsicId(RuntimeInterface::IntrinsicId id)
42 {
43     return GetInteropIntrinsicKind(id) == InteropIntrinsicKind::WRAP;
44 }
45 
IsUnwrapIntrinsicId(RuntimeInterface::IntrinsicId id)46 static bool IsUnwrapIntrinsicId(RuntimeInterface::IntrinsicId id)
47 {
48     return GetInteropIntrinsicKind(id) == InteropIntrinsicKind::UNWRAP;
49 }
50 
GetUnwrapIntrinsicId(RuntimeInterface::IntrinsicId id)51 static RuntimeInterface::IntrinsicId GetUnwrapIntrinsicId(RuntimeInterface::IntrinsicId id)
52 {
53     switch (id) {
54         case RuntimeInterface::IntrinsicId::INTRINSIC_JS_RUNTIME_GET_VALUE_DOUBLE:
55             return RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_F64;
56         case RuntimeInterface::IntrinsicId::INTRINSIC_JS_RUNTIME_GET_VALUE_BOOLEAN:
57             return RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_U1;
58         case RuntimeInterface::IntrinsicId::INTRINSIC_JS_RUNTIME_GET_VALUE_STRING:
59             return RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_STRING;
60         default:
61             return RuntimeInterface::IntrinsicId::INVALID;
62     }
63 }
64 
IsConvertIntrinsic(Inst * inst)65 static bool IsConvertIntrinsic(Inst *inst)
66 {
67     if (!inst->IsIntrinsic()) {
68         return false;
69     }
70     auto id = inst->CastToIntrinsic()->GetIntrinsicId();
71     return IsWrapIntrinsicId(id) || IsUnwrapIntrinsicId(id);
72 }
73 
IsInteropIntrinsic(Inst * inst)74 static bool IsInteropIntrinsic(Inst *inst)
75 {
76     if (!inst->IsIntrinsic()) {
77         return false;
78     }
79     auto id = inst->CastToIntrinsic()->GetIntrinsicId();
80     return GetInteropIntrinsicKind(id) != InteropIntrinsicKind::NONE;
81 }
82 
CanCreateNewScopeObject(Inst * inst)83 static bool CanCreateNewScopeObject(Inst *inst)
84 {
85     if (!inst->IsIntrinsic()) {
86         return false;
87     }
88     auto kind = GetInteropIntrinsicKind(inst->CastToIntrinsic()->GetIntrinsicId());
89     return kind == InteropIntrinsicKind::WRAP || kind == InteropIntrinsicKind::CREATES_LOCAL;
90 }
91 
GetInfo(BasicBlock * block)92 InteropIntrinsicOptimization::BlockInfo &InteropIntrinsicOptimization::GetInfo(BasicBlock *block)
93 {
94     return blockInfo_[block->GetId()];
95 }
96 
MergeScopesInsideBlock(BasicBlock * block)97 void InteropIntrinsicOptimization::MergeScopesInsideBlock(BasicBlock *block)
98 {
99     Inst *lastStart = nullptr;  // Start of the current scope or nullptr if no scope is open
100     Inst *lastEnd = nullptr;    // End of the last closed scope
101     // Number of object creations in the last closed scope (valid value if we are in the next scope now)
102     uint32_t lastObjectCount = 0;
103     uint32_t currentObjectCount = 0;  // Number of object creations in the current scope or 0
104     for (auto *inst : block->InstsSafe()) {
105         if (IsScopeStart(inst)) {
106             ASSERT(lastStart == nullptr);
107             ASSERT(currentObjectCount == 0);
108             hasScopes_ = true;
109             lastStart = inst;
110         } else if (IsScopeEnd(inst)) {
111             ASSERT(lastStart != nullptr);
112             if (lastEnd != nullptr && lastObjectCount + currentObjectCount <= scopeObjectLimit_) {
113                 ASSERT(lastEnd->IsPrecedingInSameBlock(lastStart));
114                 COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "Remove scope end " << *lastEnd << "\nand scope start "
115                                                            << *lastStart << "\nfrom BB " << block->GetId();
116                 block->RemoveInst(lastEnd);
117                 block->RemoveInst(lastStart);
118                 lastObjectCount += currentObjectCount;
119                 isApplied_ = true;
120             } else {
121                 objectLimitHit_ |= (lastEnd != nullptr);
122                 lastObjectCount = currentObjectCount;
123             }
124             currentObjectCount = 0;
125 
126             lastEnd = inst;
127             lastStart = nullptr;
128         } else if (IsForbiddenInst(inst)) {
129             ASSERT(lastStart == nullptr);
130             lastEnd = nullptr;
131             lastObjectCount = 0;
132             currentObjectCount = 0;
133         } else if (CanCreateNewScopeObject(inst)) {
134             ASSERT(lastStart != nullptr);
135             ++currentObjectCount;
136         }
137     }
138 }
139 
TryCreateSingleScope(BasicBlock * bb)140 bool InteropIntrinsicOptimization::TryCreateSingleScope(BasicBlock *bb)
141 {
142     bool isStart = bb == GetGraph()->GetStartBlock()->GetSuccessor(0);
143     bool hasStart = false;
144     auto lastInst = bb->GetLastInst();
145     // We do not need to close scope in compiler before deoptimize or throw inst
146     bool isEnd = lastInst != nullptr && lastInst->IsReturn();
147     SaveStateInst *ss = nullptr;
148     Inst *lastEnd = nullptr;
149     for (auto *inst : bb->InstsSafe()) {
150         if (isStart && !hasStart && IsScopeStart(inst)) {
151             hasStart = true;
152         } else if (isEnd && IsScopeEnd(inst)) {
153             if (lastEnd != nullptr) {
154                 bb->RemoveInst(lastEnd);
155             }
156             lastEnd = inst;
157         } else if (IsScopeStart(inst) || IsScopeEnd(inst)) {
158             bb->RemoveInst(inst);
159         } else if (isEnd && inst->GetOpcode() == Opcode::SaveState && ss == nullptr) {
160             ss = inst->CastToSaveState();
161         }
162     }
163     if (!isEnd || lastEnd != nullptr) {
164         return hasStart;
165     }
166     if (ss == nullptr) {
167         ss = GetGraph()->CreateInstSaveState(DataType::NO_TYPE, lastInst->GetPc());
168         lastInst->InsertBefore(ss);
169         if (lastInst->GetOpcode() == Opcode::Return && lastInst->GetInput(0).GetInst()->IsMovableObject()) {
170             ss->AppendBridge(lastInst->GetInput(0).GetInst());
171         }
172     }
173     auto scopeEnd = GetGraph()->CreateInstIntrinsic(
174         DataType::VOID, ss->GetPc(), RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_DESTROY_LOCAL_SCOPE);
175     scopeEnd->SetInputs(GetGraph()->GetAllocator(), {{ss, DataType::NO_TYPE}});
176     ss->InsertAfter(scopeEnd);
177     return hasStart;
178 }
179 
TryCreateSingleScope()180 bool InteropIntrinsicOptimization::TryCreateSingleScope()
181 {
182     for (auto *bb : GetGraph()->GetBlocksRPO()) {
183         for (auto *inst : bb->Insts()) {
184             if (trySingleScope_ && IsForbiddenInst(inst)) {
185                 trySingleScope_ = false;
186             }
187             if (IsScopeStart(inst)) {
188                 hasScopes_ = true;
189             }
190         }
191     }
192     if (!hasScopes_ || !trySingleScope_) {
193         return false;
194     }
195     bool hasStart = false;
196     for (auto *bb : GetGraph()->GetBlocksRPO()) {
197         hasStart |= TryCreateSingleScope(bb);
198     }
199     if (hasStart) {
200         return true;
201     }
202     auto *startBlock = GetGraph()->GetStartBlock()->GetSuccessor(0);
203     SaveStateInst *ss = nullptr;
204     for (auto *inst : startBlock->InstsReverse()) {
205         if (inst->GetOpcode() == Opcode::SaveState) {
206             ss = inst->CastToSaveState();
207             break;
208         }
209     }
210     if (ss == nullptr) {
211         ss = GetGraph()->CreateInstSaveState(DataType::NO_TYPE, startBlock->GetFirstInst()->GetPc());
212         startBlock->PrependInst(ss);
213         for (auto *inst : GetGraph()->GetStartBlock()->Insts()) {
214             if (inst->IsMovableObject()) {
215                 ss->AppendBridge(inst);
216             }
217         }
218     }
219     auto scopeStart = GetGraph()->CreateInstIntrinsic(
220         DataType::VOID, ss->GetPc(), RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CREATE_LOCAL_SCOPE);
221     scopeStart->SetInputs(GetGraph()->GetAllocator(), {{ss, DataType::NO_TYPE}});
222     ss->InsertAfter(scopeStart);
223     return true;
224 }
225 
226 // Returns estimated object creations in loop if it can be moved into scope or std::nullopt otherwise
FindForbiddenLoops(Loop * loop)227 std::optional<uint64_t> InteropIntrinsicOptimization::FindForbiddenLoops(Loop *loop)
228 {
229     bool forbidden = false;
230     uint64_t objects = 0;
231     for (auto *innerLoop : loop->GetInnerLoops()) {
232         auto innerObjects = FindForbiddenLoops(innerLoop);
233         if (innerObjects.has_value()) {
234             objects += *innerObjects;
235         } else {
236             forbidden = true;
237         }
238     }
239     if (forbidden || loop->IsIrreducible()) {
240         forbiddenLoops_.insert(loop);
241         return std::nullopt;
242     }
243     for (auto *block : loop->GetBlocks()) {
244         for (auto *inst : block->Insts()) {
245             if (IsForbiddenInst(inst)) {
246                 forbiddenLoops_.insert(loop);
247                 return std::nullopt;
248             }
249             if (CanCreateNewScopeObject(inst)) {
250                 ++objects;
251             }
252         }
253     }
254     if (objects == 0) {
255         return 0;
256     }
257     if (objects > scopeObjectLimit_) {
258         forbiddenLoops_.insert(loop);
259         return std::nullopt;
260     }
261     if (auto loopInfo = CountableLoopParser(*loop).Parse()) {
262         auto optIterations = CountableLoopParser::GetLoopIterations(*loopInfo);
263         if (optIterations.has_value() &&
264             *optIterations <= scopeObjectLimit_ / objects) {  // compare using division to avoid overflow
265             COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
266                 << "Found small countable loop, id = " << loop->GetId() << ", iterations = " << *optIterations;
267             return objects * *optIterations;
268         }
269     }
270     forbiddenLoops_.insert(loop);
271     return std::nullopt;
272 }
273 
IsForbiddenLoopEntry(BasicBlock * block)274 bool InteropIntrinsicOptimization::IsForbiddenLoopEntry(BasicBlock *block)
275 {
276     return block->GetLoop()->IsIrreducible() || (block->IsLoopHeader() && forbiddenLoops_.count(block->GetLoop()) > 0);
277 }
278 
279 template <bool REVERSE>
GetInstsIter(BasicBlock * block)280 static auto GetInstsIter(BasicBlock *block)
281 {
282     if constexpr (REVERSE) {
283         return block->InstsReverse();
284     } else {
285         return block->Insts();
286     }
287 }
288 
289 // Implementation of disjoint set union with path compression
GetParentComponent(int32_t component)290 int32_t InteropIntrinsicOptimization::GetParentComponent(int32_t component)
291 {
292     auto &parent = components_[component].parent;
293     if (parent != component) {
294         parent = GetParentComponent(parent);
295     }
296     ASSERT(parent != DFS_NOT_VISITED);
297     return parent;
298 }
299 
MergeComponents(int32_t first,int32_t second)300 void InteropIntrinsicOptimization::MergeComponents(int32_t first, int32_t second)
301 {
302     first = GetParentComponent(first);
303     second = GetParentComponent(second);
304     if (first != second) {
305         components_[first].objectCount += components_[second].objectCount;
306         components_[second].parent = first;
307     }
308 }
309 
UpdateStatsForMerging(Inst * inst,int32_t otherEndComponent,uint32_t * scopeSwitches,uint32_t * objectsInBlockAfterMerge)310 void InteropIntrinsicOptimization::UpdateStatsForMerging(Inst *inst, int32_t otherEndComponent, uint32_t *scopeSwitches,
311                                                          uint32_t *objectsInBlockAfterMerge)
312 {
313     if (IsScopeStart(inst) || IsScopeEnd(inst)) {
314         ++*scopeSwitches;
315     } else if (CanCreateNewScopeObject(inst)) {
316         if (*scopeSwitches == 1) {
317             ++*objectsInBlockAfterMerge;
318         } else if (*scopeSwitches == 0 && otherEndComponent != currentComponent_) {
319             // If both ends of the block belong to the same component, count instructions only from
320             // the first visited end
321             ++components_[currentComponent_].objectCount;
322         }
323     } else if (IsForbiddenInst(inst)) {
324         if (*scopeSwitches == 1) {
325             canMerge_ = false;
326         } else if (*scopeSwitches == 0) {
327             COMPILER_LOG_IF(!components_[currentComponent_].isForbidden, DEBUG, INTEROP_INTRINSIC_OPT)
328                 << "Connected component " << currentComponent_
329                 << " cannot be moved into scope because it contains forbidden inst " << *inst;
330             components_[currentComponent_].isForbidden = true;
331         }
332     }
333 }
334 
335 template <bool IS_END>
IterateBlockFromBoundary(BasicBlock * block)336 void InteropIntrinsicOptimization::IterateBlockFromBoundary(BasicBlock *block)
337 {
338     auto otherEndComponent = GetInfo(block).blockComponent[static_cast<int32_t>(!IS_END)];
339     if (otherEndComponent != currentComponent_) {
340         currentComponentBlocks_.push_back(block);
341     }
342     uint32_t scopeSwitches = 0;
343     uint32_t objectsInBlockAfterMerge = 0;
344     for (auto *inst : GetInstsIter<IS_END>(block)) {
345         UpdateStatsForMerging(inst, otherEndComponent, &scopeSwitches, &objectsInBlockAfterMerge);
346     }
347     if (scopeSwitches == 0) {
348         if (block->IsStartBlock() || block->IsEndBlock()) {
349             COMPILER_LOG_IF(!components_[currentComponent_].isForbidden, DEBUG, INTEROP_INTRINSIC_OPT)
350                 << "Connected component " << currentComponent_ << " cannot be moved into scope because it contains "
351                 << (block->IsStartBlock() ? "start" : "end") << " block " << block->GetId();
352             components_[currentComponent_].isForbidden = true;
353         }
354         BlockBoundaryDfs<!IS_END>(block);
355     } else if (scopeSwitches == 1) {
356         // Other end of the block was already moved into scope (otherwise scope switch count in block would be even)
357         ASSERT(otherEndComponent != DFS_NOT_VISITED && otherEndComponent != currentComponent_);
358         otherEndComponent = GetParentComponent(otherEndComponent);
359         if (components_[otherEndComponent].isForbidden) {
360             canMerge_ = false;
361         } else {
362             objectsInScopeAfterMerge_ += GetObjectCountIfUnused(components_[otherEndComponent], currentComponent_);
363         }
364     } else if (scopeSwitches > 2U || otherEndComponent != currentComponent_) {
365         objectsInScopeAfterMerge_ += objectsInBlockAfterMerge;
366     }
367 }
368 
369 template <bool IS_END>
BlockBoundaryDfs(BasicBlock * block)370 void InteropIntrinsicOptimization::BlockBoundaryDfs(BasicBlock *block)
371 {
372     auto index = static_cast<int32_t>(IS_END);
373     if (GetInfo(block).blockComponent[index] != DFS_NOT_VISITED) {
374         return;
375     }
376     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "Added " << (IS_END ? "end" : "start ") << " of block "
377                                                << block->GetId() << " to connected component " << currentComponent_;
378     GetInfo(block).blockComponent[index] = currentComponent_;
379     if (!IS_END && IsForbiddenLoopEntry(block)) {
380         COMPILER_LOG_IF(!components_[currentComponent_].isForbidden, DEBUG, INTEROP_INTRINSIC_OPT)
381             << "Connected component " << currentComponent_
382             << " cannot be moved into scope because it contains entry to forbidden loop " << block->GetLoop()->GetId()
383             << " via BB " << block->GetId();
384         components_[currentComponent_].isForbidden = true;
385     }
386     IterateBlockFromBoundary<IS_END>(block);
387 
388     auto &neighbours = IS_END ? block->GetSuccsBlocks() : block->GetPredsBlocks();
389     for (auto *neighbour : neighbours) {
390         BlockBoundaryDfs<!IS_END>(neighbour);
391     }
392 }
393 
MoveBlockStartIntoScope(BasicBlock * block)394 static bool MoveBlockStartIntoScope(BasicBlock *block)
395 {
396     bool removed = false;
397     for (auto *inst : block->InstsSafe()) {
398         if (IsScopeStart(inst)) {
399             ASSERT(!removed);
400             COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
401                 << "Remove first scope start " << *inst << "\nfrom BB " << block->GetId();
402             block->RemoveInst(inst);
403             removed = true;
404         }
405         if (IsScopeEnd(inst)) {
406             ASSERT(removed);
407             return false;
408         }
409     }
410     return removed;
411 }
412 
MoveBlockEndIntoScope(BasicBlock * block)413 static bool MoveBlockEndIntoScope(BasicBlock *block)
414 {
415     bool removed = false;
416     for (auto *inst : block->InstsSafeReverse()) {
417         if (IsScopeEnd(inst)) {
418             ASSERT(!removed);
419             COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
420                 << "Remove last scope end " << *inst << "\nfrom BB " << block->GetId();
421             block->RemoveInst(inst);
422             removed = true;
423         }
424         if (IsScopeStart(inst)) {
425             ASSERT(removed);
426             return false;
427         }
428     }
429     return removed;
430 }
431 
MergeCurrentComponentWithNeighbours()432 void InteropIntrinsicOptimization::MergeCurrentComponentWithNeighbours()
433 {
434     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
435         << "Merging connected component " << currentComponent_ << " with its neighbours";
436     for (auto *block : currentComponentBlocks_) {
437         if (GetInfo(block).blockComponent[0] == currentComponent_ && MoveBlockStartIntoScope(block)) {
438             MergeComponents(currentComponent_, GetInfo(block).blockComponent[1]);
439         }
440         if (GetInfo(block).blockComponent[1] == currentComponent_ && MoveBlockEndIntoScope(block)) {
441             MergeComponents(currentComponent_, GetInfo(block).blockComponent[0]);
442         }
443     }
444     isApplied_ = true;
445 }
446 
447 template <bool IS_END>
FindComponentAndTryMerge(BasicBlock * block)448 void InteropIntrinsicOptimization::FindComponentAndTryMerge(BasicBlock *block)
449 {
450     auto index = static_cast<int32_t>(IS_END);
451     if (GetInfo(block).blockComponent[index] != DFS_NOT_VISITED) {
452         return;
453     }
454     components_.push_back({currentComponent_, 0, 0, false});
455     currentComponentBlocks_.clear();
456     objectsInScopeAfterMerge_ = 0;
457     canMerge_ = true;
458     BlockBoundaryDfs<IS_END>(block);
459     if (components_[currentComponent_].isForbidden) {
460         canMerge_ = false;
461     }
462     if (canMerge_) {
463         objectsInScopeAfterMerge_ += components_[currentComponent_].objectCount;
464         if (objectsInScopeAfterMerge_ > scopeObjectLimit_) {
465             objectLimitHit_ = true;
466         } else {
467             MergeCurrentComponentWithNeighbours();
468         }
469     }
470     ++currentComponent_;
471 }
472 
MergeInterScopeRegions()473 void InteropIntrinsicOptimization::MergeInterScopeRegions()
474 {
475     for (auto *block : GetGraph()->GetBlocksRPO()) {
476         FindComponentAndTryMerge<false>(block);
477         FindComponentAndTryMerge<true>(block);
478     }
479 }
480 
481 // Numbering is similar to pre-order, but we stop in blocks with scope starts
DfsNumbering(BasicBlock * block)482 void InteropIntrinsicOptimization::DfsNumbering(BasicBlock *block)
483 {
484     if (GetInfo(block).dfsNumIn != DFS_NOT_VISITED) {
485         return;
486     }
487     GetInfo(block).dfsNumIn = dfsNum_++;
488     for (auto inst : block->Insts()) {
489         ASSERT(!IsScopeEnd(inst));
490         if (IsScopeStart(inst)) {
491             GetInfo(block).dfsNumOut = dfsNum_;
492             return;
493         }
494     }
495     for (auto *succ : block->GetSuccsBlocks()) {
496         DfsNumbering(succ);
497     }
498     GetInfo(block).dfsNumOut = dfsNum_;
499 }
500 
501 // Calculate minimal and maximal `dfs_num_in` for blocks that can be reached by walking some edges forward
502 // and, after that, maybe one edge backward
503 // Visit order will be the same as in DfsNumbering
504 // We walk the graph again because during the first DFS some numbers for predecessors may be invalid yet
CalculateReachabilityRec(BasicBlock * block)505 void InteropIntrinsicOptimization::CalculateReachabilityRec(BasicBlock *block)
506 {
507     if (block->SetMarker(visited_)) {
508         return;
509     }
510     auto &info = GetInfo(block);
511     info.subgraphMinNum = info.subgraphMaxNum = info.dfsNumIn;
512 
513     bool isScopeStart = false;
514     for (auto inst : block->Insts()) {
515         ASSERT(!IsScopeEnd(inst));
516         if (IsForbiddenInst(inst)) {
517             info.subgraphMinNum = DFS_NOT_VISITED;
518         }
519         if (IsScopeStart(inst)) {
520             isScopeStart = true;
521             break;
522         }
523     }
524 
525     if (!isScopeStart) {
526         for (auto *succ : block->GetSuccsBlocks()) {
527             CalculateReachabilityRec(succ);
528             info.subgraphMinNum = std::min(info.subgraphMinNum, GetInfo(succ).subgraphMinNum);
529             info.subgraphMaxNum = std::max(info.subgraphMaxNum, GetInfo(succ).subgraphMaxNum);
530             if (IsForbiddenLoopEntry(succ)) {
531                 info.subgraphMinNum = DFS_NOT_VISITED;
532                 COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
533                     << "BB " << block->GetId() << " cannot be moved into scope because succ " << succ->GetId()
534                     << " is entry to forbidden loop " << succ->GetLoop()->GetId();
535                 break;
536             }
537         }
538         if (info.dfsNumIn <= info.subgraphMinNum && info.subgraphMaxNum < info.dfsNumOut) {
539             block->SetMarker(canHoistTo_);
540         }
541     }
542     for (auto *pred : block->GetPredsBlocks()) {
543         if (pred->IsMarked(startDfs_)) {
544             info.subgraphMinNum = DFS_NOT_VISITED;
545             break;
546         }
547         info.subgraphMinNum = std::min(info.subgraphMinNum, GetInfo(pred).dfsNumIn);
548         info.subgraphMaxNum = std::max(info.subgraphMaxNum, GetInfo(pred).dfsNumIn);
549     }
550 }
551 
552 template <void (InteropIntrinsicOptimization::*DFS)(BasicBlock *)>
DoDfs()553 void InteropIntrinsicOptimization::DoDfs()
554 {
555     // We launch DFS in a graph where vertices correspond to block starts not contained in scopes, and
556     // vertices for bb1 and bb2 are connected by a (directed) edge if bb1 and bb2 are connected in CFG and
557     // there are no scopes in bb1
558 
559     for (auto *block : GetGraph()->GetBlocksRPO()) {
560         // We mark block with `start_dfs_` marker if its **end** is contained in a new inter-scope region
561         // (i. e. block is the start block or last scope switch in block is scope end)
562         // And since our graph contains **starts** of blocks, we launch DFS from succs, not from the block itself
563         if (block->IsMarked(startDfs_)) {
564             for (auto *succ : block->GetSuccsBlocks()) {
565                 (this->*DFS)(succ);
566             }
567         }
568     }
569 }
570 
CreateScopeStartInBlock(BasicBlock * block)571 bool InteropIntrinsicOptimization::CreateScopeStartInBlock(BasicBlock *block)
572 {
573     for (auto *inst : block->InstsSafeReverse()) {
574         ASSERT(!IsForbiddenInst(inst) && !IsScopeStart(inst) && !IsScopeEnd(inst));
575         if (inst->GetOpcode() == Opcode::SaveState) {
576             auto scopeStart = GetGraph()->CreateInstIntrinsic(
577                 DataType::VOID, inst->GetPc(), RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CREATE_LOCAL_SCOPE);
578             scopeStart->SetInputs(GetGraph()->GetAllocator(), {{inst, DataType::NO_TYPE}});
579             inst->InsertAfter(scopeStart);
580             isApplied_ = true;
581             return true;
582         }
583     }
584     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "Failed to create new scope start in BB " << block->GetId();
585     return false;
586 }
587 
RemoveReachableScopeStarts(BasicBlock * block,BasicBlock * newStartBlock)588 void InteropIntrinsicOptimization::RemoveReachableScopeStarts(BasicBlock *block, BasicBlock *newStartBlock)
589 {
590     ASSERT(!block->IsEndBlock());
591     if (block->SetMarker(visited_)) {
592         return;
593     }
594     block->ResetMarker(canHoistTo_);
595     ASSERT(newStartBlock->IsDominate(block));
596     if (block != newStartBlock) {
597         ASSERT(!IsForbiddenLoopEntry(block));
598         for (auto *inst : block->Insts()) {
599             ASSERT(!IsForbiddenInst(inst) && !IsScopeEnd(inst));
600             if (IsScopeStart(inst)) {
601                 COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
602                     << "Removed scope start " << *inst << "from BB " << block->GetId() << ", new scope start in "
603                     << newStartBlock->GetId();
604                 block->RemoveInst(inst);
605                 return;
606             }
607         }
608     }
609     for (auto *succ : block->GetSuccsBlocks()) {
610         RemoveReachableScopeStarts(succ, newStartBlock);
611     }
612 }
613 
HoistScopeStarts()614 void InteropIntrinsicOptimization::HoistScopeStarts()
615 {
616     auto startDfsHolder = MarkerHolder(GetGraph());
617     startDfs_ = startDfsHolder.GetMarker();
618     for (auto *block : GetGraph()->GetBlocksRPO()) {
619         bool endInScope = false;
620         for (auto inst : block->InstsReverse()) {
621             if (IsScopeEnd(inst)) {
622                 block->SetMarker(startDfs_);
623                 break;
624             }
625             if (IsScopeStart(inst)) {
626                 endInScope = true;
627                 break;
628             }
629         }
630         if (block->IsStartBlock() && !endInScope) {
631             block->SetMarker(startDfs_);
632         }
633     }
634     DoDfs<&InteropIntrinsicOptimization::DfsNumbering>();
635     auto canHoistToHolder = MarkerHolder(GetGraph());
636     canHoistTo_ = canHoistToHolder.GetMarker();
637     {
638         auto visitedHolder = MarkerHolder(GetGraph());
639         visited_ = visitedHolder.GetMarker();
640         DoDfs<&InteropIntrinsicOptimization::CalculateReachabilityRec>();
641     }
642 
643     for (auto *block : GetGraph()->GetBlocksRPO()) {
644         if (block->IsMarked(canHoistTo_) && CreateScopeStartInBlock(block)) {
645             COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
646                 << "Hoisted scope start to BB " << block->GetId() << ", removing scope starts reachable from it";
647             auto visitedHolder = MarkerHolder(GetGraph());
648             visited_ = visitedHolder.GetMarker();
649             RemoveReachableScopeStarts(block, block);
650         }
651     }
652 }
653 
InvalidateScopesInSubgraph(BasicBlock * block)654 void InteropIntrinsicOptimization::InvalidateScopesInSubgraph(BasicBlock *block)
655 {
656     ASSERT(!block->IsEndBlock());
657     if (block->SetMarker(scopeStartInvalidated_)) {
658         return;
659     }
660     for (auto *inst : block->Insts()) {
661         ASSERT(!IsScopeStart(inst));
662         if (IsScopeEnd(inst)) {
663             return;
664         }
665         if (IsInteropIntrinsic(inst)) {
666             scopeForInst_[inst] = nullptr;
667         }
668     }
669     for (auto *succ : block->GetSuccsBlocks()) {
670         InvalidateScopesInSubgraph(succ);
671     }
672 }
673 
CheckGraphRec(BasicBlock * block,Inst * scopeStart)674 void InteropIntrinsicOptimization::CheckGraphRec(BasicBlock *block, Inst *scopeStart)
675 {
676     if (block->SetMarker(visited_)) {
677         if (GetInfo(block).lastScopeStart != scopeStart) {
678             // It's impossible to have scope start in one path to block and to have no scope in another path
679             ASSERT(GetInfo(block).lastScopeStart != nullptr);
680             ASSERT(scopeStart != nullptr);
681             // Different scope starts for different execution paths are possible
682             // NOTE(aefremov): find insts with always equal scopes somehow
683             InvalidateScopesInSubgraph(block);
684         }
685         return;
686     }
687     GetInfo(block).lastScopeStart = scopeStart;
688     for (auto *inst : block->Insts()) {
689         if (IsScopeEnd(inst)) {
690             ASSERT(scopeStart != nullptr);
691             scopeStart = nullptr;
692         } else if (IsScopeStart(inst)) {
693             ASSERT(scopeStart == nullptr);
694             scopeStart = inst;
695         } else if (IsForbiddenInst(inst)) {
696             ASSERT(scopeStart == nullptr);
697         } else if (IsInteropIntrinsic(inst)) {
698             ASSERT(scopeStart != nullptr);
699             scopeForInst_[inst] = scopeStart;
700         }
701     }
702     if (block->IsEndBlock()) {
703         ASSERT(scopeStart == nullptr);
704     }
705     for (auto *succ : block->GetSuccsBlocks()) {
706         CheckGraphRec(succ, scopeStart);
707     }
708 }
709 
CheckGraphAndFindScopes()710 void InteropIntrinsicOptimization::CheckGraphAndFindScopes()
711 {
712     auto visitedHolder = MarkerHolder(GetGraph());
713     visited_ = visitedHolder.GetMarker();
714     auto invalidatedHolder = MarkerHolder(GetGraph());
715     scopeStartInvalidated_ = invalidatedHolder.GetMarker();
716     CheckGraphRec(GetGraph()->GetStartBlock(), nullptr);
717 }
718 
MarkRequireRegMap(Inst * inst)719 void InteropIntrinsicOptimization::MarkRequireRegMap(Inst *inst)
720 {
721     SaveStateInst *saveState = nullptr;
722     if (inst->IsSaveState()) {
723         saveState = static_cast<SaveStateInst *>(inst);
724     } else if (inst->RequireState()) {
725         saveState = inst->GetSaveState();
726     }
727     while (saveState != nullptr && saveState->SetMarker(requireRegMap_) && saveState->GetCallerInst() != nullptr) {
728         saveState = saveState->GetCallerInst()->GetSaveState();
729     }
730 }
731 
TryRemoveUnwrapAndWrap(Inst * inst,Inst * input)732 void InteropIntrinsicOptimization::TryRemoveUnwrapAndWrap(Inst *inst, Inst *input)
733 {
734     if (scopeForInst_.at(inst) == nullptr || scopeForInst_.at(inst) != scopeForInst_.at(input)) {
735         COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
736             << "Scopes don't match, skip: wrap " << *inst << "\nwith unwrap input " << *input;
737         return;
738     }
739     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "Remove wrap " << *inst << "\nwith unwrap input " << *input;
740     auto *newInput = input->GetInput(0).GetInst();
741     // We don't extend scopes out of basic blocks in OSR mode
742     ASSERT(!GetGraph()->IsOsrMode() || inst->GetBasicBlock() == newInput->GetBasicBlock());
743     ASSERT(newInput->GetType() == DataType::POINTER);
744     ASSERT(inst->GetType() == DataType::POINTER);
745     inst->ReplaceUsers(newInput);
746     inst->GetBasicBlock()->RemoveInst(inst);
747     // If `input` (unwrap from local to JSValue or ets primitve) has SaveState users which require regmap,
748     // we will not delete the unwrap intrinsic
749     // NOTE(aefremov): support unwrap (local => JSValue/primitive) during deoptimization
750     isApplied_ = true;
751 }
752 
TryRemoveUnwrapToJSValue(Inst * inst)753 void InteropIntrinsicOptimization::TryRemoveUnwrapToJSValue(Inst *inst)
754 {
755     auto commonId = RuntimeInterface::IntrinsicId::INVALID;
756     DataType::Type userType;
757     for (auto &user : inst->GetUsers()) {
758         auto *userInst = user.GetInst();
759         if (userInst->IsSaveState()) {
760             // see the previous note
761             if (userInst->IsMarked(requireRegMap_)) {
762                 COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
763                     << "User " << *userInst << "\nof inst " << *inst << " requires regmap, skip";
764                 return;
765             }
766             continue;
767         }
768         if (!userInst->IsIntrinsic()) {
769             return;
770         }
771         if (HasOsrEntryBetween(inst, userInst)) {
772             return;
773         }
774         auto currentId = userInst->CastToIntrinsic()->GetIntrinsicId();
775         if (commonId == RuntimeInterface::IntrinsicId::INVALID) {
776             userType = userInst->GetType();
777             commonId = currentId;
778         } else if (currentId != commonId) {
779             return;
780         }
781     }
782     auto newId = GetUnwrapIntrinsicId(commonId);
783     if (newId == RuntimeInterface::IntrinsicId::INVALID) {
784         // includes the case when common_id is INVALID
785         return;
786     }
787     inst->CastToIntrinsic()->SetIntrinsicId(newId);
788     inst->SetOpcode(Opcode::Intrinsic);  // reset flags to default for intrinsic inst
789     AdjustFlags(newId, inst);
790     inst->SetType(userType);
791     for (auto userIt = inst->GetUsers().begin(); userIt != inst->GetUsers().end();) {
792         auto userInst = userIt->GetInst();
793         if (userInst->IsIntrinsic() && userInst->CastToIntrinsic()->GetIntrinsicId() == commonId) {
794             COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
795                 << "Replace cast from JSValue " << *userInst << "\nwith direct unwrap " << *inst;
796             userInst->ReplaceUsers(inst);
797             userInst->GetBasicBlock()->RemoveInst(userInst);
798             userIt = inst->GetUsers().begin();
799         } else {
800             ++userIt;
801         }
802     }
803     isApplied_ = true;
804 }
805 
TryRemoveIntrinsic(Inst * inst)806 void InteropIntrinsicOptimization::TryRemoveIntrinsic(Inst *inst)
807 {
808     if (!inst->IsIntrinsic()) {
809         return;
810     }
811     auto *input = inst->GetInput(0).GetInst();
812     auto intrinsicId = inst->CastToIntrinsic()->GetIntrinsicId();
813     if (input->IsIntrinsic() && IsWrapIntrinsicId(intrinsicId) &&
814         IsUnwrapIntrinsicId(input->CastToIntrinsic()->GetIntrinsicId())) {
815         TryRemoveUnwrapAndWrap(inst, input);
816     } else if (intrinsicId == RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_JS_VALUE) {
817         TryRemoveUnwrapToJSValue(inst);
818     } else if (intrinsicId == RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_JS_CALL_FUNCTION && !inst->HasUsers()) {
819         // avoid creation of handle for return value in local scope if it is unused
820         inst->SetType(DataType::VOID);
821         inst->CastToIntrinsic()->SetIntrinsicId(
822             RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_JS_CALL_VOID_FUNCTION);
823         isApplied_ = true;
824     }
825 }
826 
EliminateCastPairs()827 void InteropIntrinsicOptimization::EliminateCastPairs()
828 {
829     auto requireRegMapHolder = MarkerHolder(GetGraph());
830     requireRegMap_ = requireRegMapHolder.GetMarker();
831     auto &blocksRpo = GetGraph()->GetBlocksRPO();
832     for (auto it = blocksRpo.rbegin(); it != blocksRpo.rend(); ++it) {
833         auto *block = *it;
834         for (auto inst : block->InstsSafeReverse()) {
835             if (inst->RequireRegMap()) {
836                 MarkRequireRegMap(inst);
837             }
838             TryRemoveIntrinsic(inst);
839         }
840     }
841 }
842 
DomTreeDfs(BasicBlock * block)843 void InteropIntrinsicOptimization::DomTreeDfs(BasicBlock *block)
844 {
845     // bb1->IsDominate(bb2) iff bb1.dom_tree_in <= bb2.dom_tree_in < bb1.dom_tree_out
846     GetInfo(block).domTreeIn = domTreeNum_++;
847     for (auto *dom : block->GetDominatedBlocks()) {
848         DomTreeDfs(dom);
849     }
850     GetInfo(block).domTreeOut = domTreeNum_;
851 }
852 
MarkPartiallyAnticipated(BasicBlock * block,BasicBlock * stopBlock)853 void InteropIntrinsicOptimization::MarkPartiallyAnticipated(BasicBlock *block, BasicBlock *stopBlock)
854 {
855     if (block->SetMarker(instAnticipated_)) {
856         return;
857     }
858     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
859         << "Mark block " << block->GetId() << " where current inst is partially anticipated";
860     GetInfo(block).subgraphMinNum = DFS_NOT_VISITED;
861     GetInfo(block).maxChain = 0;
862     GetInfo(block).maxDepth = -1L;
863     if (block == stopBlock) {
864         return;
865     }
866     ASSERT(!block->IsStartBlock());
867     for (auto *pred : block->GetPredsBlocks()) {
868         MarkPartiallyAnticipated(pred, stopBlock);
869     }
870 }
871 
CalculateDownSafe(BasicBlock * block)872 void InteropIntrinsicOptimization::CalculateDownSafe(BasicBlock *block)
873 {
874     auto &info = GetInfo(block);
875     if (!block->IsMarked(instAnticipated_)) {
876         info.maxChain = 0;
877         info.maxDepth = -1L;
878         info.subgraphMinNum = DFS_NOT_VISITED;
879         return;
880     }
881     if (info.subgraphMinNum != DFS_NOT_VISITED) {
882         return;
883     }
884     ASSERT(info.domTreeIn >= 0);
885     info.subgraphMinNum = info.subgraphMaxNum = info.domTreeIn;
886     int32_t succMaxChain = 0;
887     for (auto *succ : block->GetSuccsBlocks()) {
888         CalculateDownSafe(succ);
889         succMaxChain = std::max(succMaxChain, GetInfo(succ).maxChain);
890         if (!block->IsMarked(eliminationCandidate_)) {
891             info.subgraphMinNum = std::min(info.subgraphMinNum, GetInfo(succ).subgraphMinNum);
892             info.subgraphMaxNum = std::max(info.subgraphMaxNum, GetInfo(succ).subgraphMaxNum);
893         }
894     }
895     for (auto *dom : block->GetSuccsBlocks()) {
896         if (dom->IsMarked(instAnticipated_)) {
897             info.maxDepth = std::max(info.maxDepth, GetInfo(dom).maxDepth);
898         }
899     }
900     info.maxChain += succMaxChain;
901 }
902 
ReplaceInst(Inst * inst,Inst ** newInst,Inst * insertAfter)903 void InteropIntrinsicOptimization::ReplaceInst(Inst *inst, Inst **newInst, Inst *insertAfter)
904 {
905     ASSERT(inst != nullptr);
906     ASSERT(*newInst != nullptr);
907     ASSERT((*newInst)->IsDominate(inst));
908     if ((*newInst)->GetOpcode() == Opcode::SaveState) {
909         ASSERT(insertAfter != nullptr);
910         auto oldNextInst = inst->GetNext();
911         auto *block = inst->GetBasicBlock();
912         block->EraseInst(inst);
913         insertAfter->InsertAfter(inst);
914         inst->SetSaveState(*newInst);
915         *newInst = inst;
916         if (inst->IsReferenceOrAny()) {
917             // SSB is needed for conversion from local to JSValue or other ref type
918             ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), *newInst, oldNextInst, nullptr, block);
919         }
920     } else {
921         ASSERT(inst->GetOpcode() == (*newInst)->GetOpcode());
922         inst->ReplaceUsers(*newInst);
923         if (inst->IsReferenceOrAny()) {
924             ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), *newInst, inst);
925         }
926     }
927 }
928 
HasSaveState(BasicBlock * block)929 static bool HasSaveState(BasicBlock *block)
930 {
931     for (auto inst : block->Insts()) {
932         if (inst->GetOpcode() == Opcode::SaveState) {
933             return true;
934         }
935     }
936     return false;
937 }
938 
CanHoistTo(BasicBlock * block)939 bool InteropIntrinsicOptimization::CanHoistTo(BasicBlock *block)
940 {
941     ASSERT(!block->IsMarked(eliminationCandidate_));
942     auto &info = GetInfo(block);
943     bool dominatesSubgraph = info.domTreeIn <= info.subgraphMinNum && info.subgraphMaxNum < info.domTreeOut;
944     auto depth = block->GetLoop()->GetDepth();
945     // Heuristics to estimate if hoisting to this blocks is profitable, and it is not better to hoist to some dominated
946     // blocks instead
947     if (dominatesSubgraph && info.maxChain > 1) {
948         for (auto *dom : block->GetDominatedBlocks()) {
949             auto domDepth = dom->GetLoop()->GetDepth();
950             if (GetInfo(dom).maxChain > 0 &&
951                 (GetInfo(dom).maxChain < info.maxChain || domDepth > depth || !HasSaveState(dom))) {
952                 COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
953                     << " Block " << block->GetId() << " is candidate for hoisting";
954                 return true;
955             }
956         }
957     }
958     if (info.maxDepth > static_cast<int32_t>(depth)) {
959         for (auto *dom : block->GetDominatedBlocks()) {
960             auto domDepth = dom->GetLoop()->GetDepth();
961             if (GetInfo(dom).maxDepth >= 0 && (domDepth > depth || !HasSaveState(dom))) {
962                 COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
963                     << " Block " << block->GetId() << " is candidate for hoisting";
964                 return true;
965             }
966         }
967     }
968     return false;
969 }
970 
971 // DFS blocks dominated by start block, and save blocks from its dominance frontier to process them later in
972 // HoistAndEliminate. If *new_inst is SaveState, we move the first dominated inst we want to replace after insert_after
973 // and set *new_inst to it. Otherwise just replace dominated inst with *new_inst
HoistAndEliminateRec(BasicBlock * block,const BlockInfo & startInfo,Inst ** newInst,Inst * insertAfter)974 void InteropIntrinsicOptimization::HoistAndEliminateRec(BasicBlock *block, const BlockInfo &startInfo, Inst **newInst,
975                                                         Inst *insertAfter)
976 {
977     if (block->ResetMarker(eliminationCandidate_)) {
978         for (auto *inst : block->InstsSafe()) {
979             if (inst->IsMarked(eliminationCandidate_) && inst != *newInst) {
980                 ReplaceInst(inst, newInst, insertAfter);
981                 isApplied_ = true;
982             }
983         }
984     }
985     for (auto *succ : block->GetSuccsBlocks()) {
986         if (!succ->ResetMarker(instAnticipated_)) {
987             continue;
988         }
989         // Fast IsDominate check
990         if (startInfo.domTreeIn <= GetInfo(succ).domTreeIn && GetInfo(succ).domTreeIn < startInfo.domTreeOut) {
991             HoistAndEliminateRec(succ, startInfo, newInst, insertAfter);
992         } else {
993             blocksToProcess_.push_back(succ);
994         }
995     }
996 }
997 
998 // Returns SaveState and inst to insert after
GetHoistPosition(BasicBlock * block,Inst * boundaryInst)999 static std::pair<Inst *, Inst *> GetHoistPosition(BasicBlock *block, Inst *boundaryInst)
1000 {
1001     for (auto inst : block->InstsReverse()) {
1002         if (inst == boundaryInst) {
1003             auto prev = inst->GetPrev();
1004             if (prev != nullptr && prev->GetOpcode() == Opcode::SaveState && !inst->IsMovableObject()) {
1005                 return {prev, inst};
1006             }
1007             return {nullptr, nullptr};
1008         }
1009         if (inst->GetOpcode() == Opcode::SaveState) {
1010             return {inst, inst};
1011         }
1012     }
1013     return {nullptr, nullptr};
1014 }
1015 
FindEliminationCandidate(BasicBlock * block)1016 Inst *InteropIntrinsicOptimization::FindEliminationCandidate(BasicBlock *block)
1017 {
1018     for (auto inst : block->Insts()) {
1019         if (inst->IsMarked(eliminationCandidate_)) {
1020             return inst;
1021         }
1022     }
1023     UNREACHABLE();
1024 }
1025 
HoistAndEliminate(BasicBlock * startBlock,Inst * boundaryInst)1026 void InteropIntrinsicOptimization::HoistAndEliminate(BasicBlock *startBlock, Inst *boundaryInst)
1027 {
1028     ASSERT(boundaryInst->GetBasicBlock() == startBlock);
1029     blocksToProcess_.clear();
1030     blocksToProcess_.push_back(startBlock);
1031     ASSERT(startBlock->ResetMarker(instAnticipated_));
1032     while (!blocksToProcess_.empty()) {
1033         auto *block = blocksToProcess_.back();
1034         blocksToProcess_.pop_back();
1035         // We reset inst_anticipated_ marker while we traverse the subgraph where inst is anticipated
1036         ASSERT(!block->IsMarked(instAnticipated_));
1037         auto &info = GetInfo(block);
1038         ASSERT(info.subgraphMinNum != DFS_INVALIDATED);
1039         Inst *newInst = nullptr;
1040         Inst *insertAfter = nullptr;
1041         if (block->IsMarked(eliminationCandidate_)) {
1042             newInst = FindEliminationCandidate(block);
1043         } else if (CanHoistTo(block)) {
1044             std::tie(newInst, insertAfter) = GetHoistPosition(block, boundaryInst);
1045             if (newInst == nullptr) {
1046                 info.subgraphMinNum = DFS_INVALIDATED;
1047                 continue;
1048             }
1049         }
1050         info.subgraphMinNum = DFS_INVALIDATED;
1051         if (newInst != nullptr) {
1052             HoistAndEliminateRec(block, GetInfo(block), &newInst, insertAfter);
1053             continue;
1054         }
1055         for (auto *succ : block->GetSuccsBlocks()) {
1056             if (succ->ResetMarker(instAnticipated_)) {
1057                 blocksToProcess_.push_back(succ);
1058             }
1059         }
1060     }
1061 }
1062 
DoRedundancyElimination(Inst * input,Inst * scopeStart,InstVector & insts)1063 void InteropIntrinsicOptimization::DoRedundancyElimination(Inst *input, Inst *scopeStart, InstVector &insts)
1064 {
1065     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
1066         << "Process group of intrinsics with identical inputs and scope start: " << *scopeStart
1067         << "\ninput: " << *input;
1068     auto *boundaryInst = input->IsDominate(scopeStart) ? scopeStart : input;
1069     auto *boundary = boundaryInst->GetBasicBlock();
1070     ASSERT(input->IsDominate(boundaryInst) && scopeStart->IsDominate(boundaryInst));
1071     auto instAnticipatedHolder = MarkerHolder(GetGraph());
1072     instAnticipated_ = instAnticipatedHolder.GetMarker();
1073     auto eliminationCandidateHolder = MarkerHolder(GetGraph());
1074     eliminationCandidate_ = eliminationCandidateHolder.GetMarker();
1075     // Marking blocks where inst is partially anticipated is needed only to reduce number of processed blocks
1076     for (auto *inst : insts) {
1077         COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << " Inst in this group: " << *inst;
1078         inst->SetMarker(eliminationCandidate_);
1079         auto *block = inst->GetBasicBlock();
1080         block->SetMarker(eliminationCandidate_);
1081         MarkPartiallyAnticipated(block, boundary);
1082         GetInfo(block).maxChain++;
1083         GetInfo(block).maxDepth = block->GetLoop()->GetDepth();
1084     }
1085     CalculateDownSafe(boundary);
1086     HoistAndEliminate(boundary, boundaryInst);
1087 }
1088 
SaveSiblingForElimination(Inst * sibling,ArenaMap<Inst *,InstVector> & currentInsts,RuntimeInterface::IntrinsicId id,Marker processed)1089 void InteropIntrinsicOptimization::SaveSiblingForElimination(Inst *sibling, ArenaMap<Inst *, InstVector> &currentInsts,
1090                                                              RuntimeInterface::IntrinsicId id, Marker processed)
1091 {
1092     if (!sibling->IsIntrinsic() || sibling->CastToIntrinsic()->GetIntrinsicId() != id) {
1093         return;
1094     }
1095     sibling->SetMarker(processed);
1096     auto scopeStart = scopeForInst_.at(sibling);
1097     if (scopeStart != nullptr) {
1098         auto it = currentInsts.try_emplace(scopeStart, GetGraph()->GetLocalAllocator()->Adapter()).first;
1099         it->second.push_back(sibling);
1100     }
1101 }
1102 
RedundancyElimination()1103 void InteropIntrinsicOptimization::RedundancyElimination()
1104 {
1105     DomTreeDfs(GetGraph()->GetStartBlock());
1106     auto processedHolder = MarkerHolder(GetGraph());
1107     auto processed = processedHolder.GetMarker();
1108     ArenaMap<Inst *, InstVector> currentInsts(GetGraph()->GetLocalAllocator()->Adapter());
1109     for (auto *block : GetGraph()->GetBlocksRPO()) {
1110         for (auto *inst : block->InstsSafe()) {
1111             if (inst->IsMarked(processed) || !IsConvertIntrinsic(inst)) {
1112                 continue;
1113             }
1114             auto id = inst->CastToIntrinsic()->GetIntrinsicId();
1115             currentInsts.clear();
1116             auto *input = inst->GetInput(0).GetInst();
1117             for (auto &user : input->GetUsers()) {
1118                 SaveSiblingForElimination(user.GetInst(), currentInsts, id, processed);
1119             }
1120             for (auto &[scope, insts] : currentInsts) {
1121                 DoRedundancyElimination(input, scope, insts);
1122             }
1123         }
1124     }
1125 }
1126 
RunImpl()1127 bool InteropIntrinsicOptimization::RunImpl()
1128 {
1129     bool oneScope = TryCreateSingleScope();
1130     if (!hasScopes_) {
1131         return false;
1132     }
1133     GetGraph()->RunPass<LoopAnalyzer>();
1134     GetGraph()->RunPass<DominatorsTree>();
1135     if (!oneScope) {
1136         COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "  Phase I: merge scopes inside basic blocks";
1137         for (auto *block : GetGraph()->GetBlocksRPO()) {
1138             MergeScopesInsideBlock(block);
1139         }
1140         if (!GetGraph()->IsOsrMode()) {
1141             for (auto loop : GetGraph()->GetRootLoop()->GetInnerLoops()) {
1142                 FindForbiddenLoops(loop);
1143             }
1144             COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
1145                 << "  Phase II: remove inter-scope regions to merge neighbouring scopes";
1146             MergeInterScopeRegions();
1147             if (!objectLimitHit_) {
1148                 COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "  Phase III: hoist scope starts";
1149                 HoistScopeStarts();
1150             }
1151         }
1152     }
1153     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
1154         << "  Phase IV: Check graph and find scope starts for interop intrinsics";
1155     // NB: we assume that each scope is residing inside one block before the pass, and that there are no nested scopes
1156     CheckGraphAndFindScopes();
1157     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "  Phase V: Peepholes for interop intrinsics";
1158     EliminateCastPairs();
1159     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "  Phase VI: Redundancy elimination for wrap intrinsics";
1160     RedundancyElimination();
1161     return IsApplied();
1162 }
1163 
1164 }  // namespace panda::compiler
1165