• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-2025 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "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 ark::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     if constexpr (IS_END) {
389         auto &neighbours = block->GetSuccsBlocks();
390         for (auto *neighbour : neighbours) {
391             BlockBoundaryDfs<!IS_END>(neighbour);
392         }
393     } else {
394         auto &neighbours = block->GetPredsBlocks();
395         for (auto *neighbour : neighbours) {
396             BlockBoundaryDfs<!IS_END>(neighbour);
397         }
398     }
399 }
400 
MoveBlockStartIntoScope(BasicBlock * block)401 static bool MoveBlockStartIntoScope(BasicBlock *block)
402 {
403     bool removed = false;
404     for (auto *inst : block->InstsSafe()) {
405         if (IsScopeStart(inst)) {
406             ASSERT(!removed);
407             COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
408                 << "Remove first scope start " << *inst << "\nfrom BB " << block->GetId();
409             block->RemoveInst(inst);
410             removed = true;
411         }
412         if (IsScopeEnd(inst)) {
413             ASSERT(removed);
414             return false;
415         }
416     }
417     return removed;
418 }
419 
MoveBlockEndIntoScope(BasicBlock * block)420 static bool MoveBlockEndIntoScope(BasicBlock *block)
421 {
422     bool removed = false;
423     for (auto *inst : block->InstsSafeReverse()) {
424         if (IsScopeEnd(inst)) {
425             ASSERT(!removed);
426             COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
427                 << "Remove last scope end " << *inst << "\nfrom BB " << block->GetId();
428             block->RemoveInst(inst);
429             removed = true;
430         }
431         if (IsScopeStart(inst)) {
432             ASSERT(removed);
433             return false;
434         }
435     }
436     return removed;
437 }
438 
MergeCurrentComponentWithNeighbours()439 void InteropIntrinsicOptimization::MergeCurrentComponentWithNeighbours()
440 {
441     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
442         << "Merging connected component " << currentComponent_ << " with its neighbours";
443     for (auto *block : currentComponentBlocks_) {
444         if (GetInfo(block).blockComponent[0] == currentComponent_ && MoveBlockStartIntoScope(block)) {
445             MergeComponents(currentComponent_, GetInfo(block).blockComponent[1]);
446         }
447         if (GetInfo(block).blockComponent[1] == currentComponent_ && MoveBlockEndIntoScope(block)) {
448             MergeComponents(currentComponent_, GetInfo(block).blockComponent[0]);
449         }
450     }
451     isApplied_ = true;
452 }
453 
454 template <bool IS_END>
FindComponentAndTryMerge(BasicBlock * block)455 void InteropIntrinsicOptimization::FindComponentAndTryMerge(BasicBlock *block)
456 {
457     auto index = static_cast<int32_t>(IS_END);
458     if (GetInfo(block).blockComponent[index] != DFS_NOT_VISITED) {
459         return;
460     }
461     components_.push_back({currentComponent_, 0, 0, false});
462     currentComponentBlocks_.clear();
463     objectsInScopeAfterMerge_ = 0;
464     canMerge_ = true;
465     BlockBoundaryDfs<IS_END>(block);
466     if (components_[currentComponent_].isForbidden) {
467         canMerge_ = false;
468     }
469     if (canMerge_) {
470         objectsInScopeAfterMerge_ += components_[currentComponent_].objectCount;
471         if (objectsInScopeAfterMerge_ > scopeObjectLimit_) {
472             objectLimitHit_ = true;
473         } else {
474             MergeCurrentComponentWithNeighbours();
475         }
476     }
477     ++currentComponent_;
478 }
479 
MergeInterScopeRegions()480 void InteropIntrinsicOptimization::MergeInterScopeRegions()
481 {
482     for (auto *block : GetGraph()->GetBlocksRPO()) {
483         FindComponentAndTryMerge<false>(block);
484         FindComponentAndTryMerge<true>(block);
485     }
486 }
487 
488 // Numbering is similar to pre-order, but we stop in blocks with scope starts
DfsNumbering(BasicBlock * block)489 void InteropIntrinsicOptimization::DfsNumbering(BasicBlock *block)
490 {
491     if (GetInfo(block).dfsNumIn != DFS_NOT_VISITED) {
492         return;
493     }
494     GetInfo(block).dfsNumIn = dfsNum_++;
495     for (auto inst : block->Insts()) {
496         ASSERT(!IsScopeEnd(inst));
497         if (IsScopeStart(inst)) {
498             GetInfo(block).dfsNumOut = dfsNum_;
499             return;
500         }
501     }
502     for (auto *succ : block->GetSuccsBlocks()) {
503         DfsNumbering(succ);
504     }
505     GetInfo(block).dfsNumOut = dfsNum_;
506 }
507 
508 // Calculate minimal and maximal `dfs_num_in` for blocks that can be reached by walking some edges forward
509 // and, after that, maybe one edge backward
510 // Visit order will be the same as in DfsNumbering
511 // We walk the graph again because during the first DFS some numbers for predecessors may be invalid yet
CalculateReachabilityRec(BasicBlock * block)512 void InteropIntrinsicOptimization::CalculateReachabilityRec(BasicBlock *block)
513 {
514     if (block->SetMarker(visited_)) {
515         return;
516     }
517     auto &info = GetInfo(block);
518     info.subgraphMinNum = info.subgraphMaxNum = info.dfsNumIn;
519 
520     bool isScopeStart = false;
521     for (auto inst : block->Insts()) {
522         ASSERT(!IsScopeEnd(inst));
523         if (IsForbiddenInst(inst)) {
524             info.subgraphMinNum = DFS_NOT_VISITED;
525         }
526         if (IsScopeStart(inst)) {
527             isScopeStart = true;
528             break;
529         }
530     }
531 
532     if (!isScopeStart) {
533         for (auto *succ : block->GetSuccsBlocks()) {
534             CalculateReachabilityRec(succ);
535             info.subgraphMinNum = std::min(info.subgraphMinNum, GetInfo(succ).subgraphMinNum);
536             info.subgraphMaxNum = std::max(info.subgraphMaxNum, GetInfo(succ).subgraphMaxNum);
537             if (IsForbiddenLoopEntry(succ)) {
538                 info.subgraphMinNum = DFS_NOT_VISITED;
539                 COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
540                     << "BB " << block->GetId() << " cannot be moved into scope because succ " << succ->GetId()
541                     << " is entry to forbidden loop " << succ->GetLoop()->GetId();
542                 break;
543             }
544         }
545         if (info.dfsNumIn <= info.subgraphMinNum && info.subgraphMaxNum < info.dfsNumOut) {
546             block->SetMarker(canHoistTo_);
547         }
548     }
549     for (auto *pred : block->GetPredsBlocks()) {
550         if (pred->IsMarked(startDfs_)) {
551             info.subgraphMinNum = DFS_NOT_VISITED;
552             break;
553         }
554         info.subgraphMinNum = std::min(info.subgraphMinNum, GetInfo(pred).dfsNumIn);
555         info.subgraphMaxNum = std::max(info.subgraphMaxNum, GetInfo(pred).dfsNumIn);
556     }
557 }
558 
559 template <void (InteropIntrinsicOptimization::*DFS)(BasicBlock *)>
DoDfs()560 void InteropIntrinsicOptimization::DoDfs()
561 {
562     // We launch DFS in a graph where vertices correspond to block starts not contained in scopes, and
563     // vertices for bb1 and bb2 are connected by a (directed) edge if bb1 and bb2 are connected in CFG and
564     // there are no scopes in bb1
565 
566     for (auto *block : GetGraph()->GetBlocksRPO()) {
567         // We mark block with `start_dfs_` marker if its **end** is contained in a new inter-scope region
568         // (i. e. block is the start block or last scope switch in block is scope end)
569         // And since our graph contains **starts** of blocks, we launch DFS from succs, not from the block itself
570         if (block->IsMarked(startDfs_)) {
571             for (auto *succ : block->GetSuccsBlocks()) {
572                 (this->*DFS)(succ);
573             }
574         }
575     }
576 }
577 
CreateScopeStartInBlock(BasicBlock * block)578 bool InteropIntrinsicOptimization::CreateScopeStartInBlock(BasicBlock *block)
579 {
580     for (auto *inst : block->InstsSafeReverse()) {
581         ASSERT(!IsForbiddenInst(inst) && !IsScopeStart(inst) && !IsScopeEnd(inst));
582         if (inst->GetOpcode() == Opcode::SaveState) {
583             auto scopeStart = GetGraph()->CreateInstIntrinsic(
584                 DataType::VOID, inst->GetPc(), RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CREATE_LOCAL_SCOPE);
585             scopeStart->SetInputs(GetGraph()->GetAllocator(), {{inst, DataType::NO_TYPE}});
586             inst->InsertAfter(scopeStart);
587             isApplied_ = true;
588             return true;
589         }
590     }
591     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "Failed to create new scope start in BB " << block->GetId();
592     return false;
593 }
594 
RemoveReachableScopeStarts(BasicBlock * block,BasicBlock * newStartBlock)595 void InteropIntrinsicOptimization::RemoveReachableScopeStarts(BasicBlock *block, BasicBlock *newStartBlock)
596 {
597     ASSERT(!block->IsEndBlock());
598     if (block->SetMarker(visited_)) {
599         return;
600     }
601     block->ResetMarker(canHoistTo_);
602     ASSERT(newStartBlock->IsDominate(block));
603     if (block != newStartBlock) {
604         ASSERT(!IsForbiddenLoopEntry(block));
605         for (auto *inst : block->Insts()) {
606             ASSERT(!IsForbiddenInst(inst) && !IsScopeEnd(inst));
607             if (IsScopeStart(inst)) {
608                 COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
609                     << "Removed scope start " << *inst << "from BB " << block->GetId() << ", new scope start in "
610                     << newStartBlock->GetId();
611                 block->RemoveInst(inst);
612                 return;
613             }
614         }
615     }
616     for (auto *succ : block->GetSuccsBlocks()) {
617         RemoveReachableScopeStarts(succ, newStartBlock);
618     }
619 }
620 
HoistScopeStarts()621 void InteropIntrinsicOptimization::HoistScopeStarts()
622 {
623     auto startDfsHolder = MarkerHolder(GetGraph());
624     startDfs_ = startDfsHolder.GetMarker();
625     for (auto *block : GetGraph()->GetBlocksRPO()) {
626         bool endInScope = false;
627         for (auto inst : block->InstsReverse()) {
628             if (IsScopeEnd(inst)) {
629                 block->SetMarker(startDfs_);
630                 break;
631             }
632             if (IsScopeStart(inst)) {
633                 endInScope = true;
634                 break;
635             }
636         }
637         if (block->IsStartBlock() && !endInScope) {
638             block->SetMarker(startDfs_);
639         }
640     }
641     DoDfs<&InteropIntrinsicOptimization::DfsNumbering>();
642     auto canHoistToHolder = MarkerHolder(GetGraph());
643     canHoistTo_ = canHoistToHolder.GetMarker();
644     {
645         auto visitedHolder = MarkerHolder(GetGraph());
646         visited_ = visitedHolder.GetMarker();
647         DoDfs<&InteropIntrinsicOptimization::CalculateReachabilityRec>();
648     }
649 
650     for (auto *block : GetGraph()->GetBlocksRPO()) {
651         if (block->IsMarked(canHoistTo_) && CreateScopeStartInBlock(block)) {
652             COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
653                 << "Hoisted scope start to BB " << block->GetId() << ", removing scope starts reachable from it";
654             auto visitedHolder = MarkerHolder(GetGraph());
655             visited_ = visitedHolder.GetMarker();
656             RemoveReachableScopeStarts(block, block);
657         }
658     }
659 }
660 
InvalidateScopesInSubgraph(BasicBlock * block)661 void InteropIntrinsicOptimization::InvalidateScopesInSubgraph(BasicBlock *block)
662 {
663     ASSERT(!block->IsEndBlock());
664     if (block->SetMarker(scopeStartInvalidated_)) {
665         return;
666     }
667     for (auto *inst : block->Insts()) {
668         ASSERT(!IsScopeStart(inst));
669         if (IsScopeEnd(inst)) {
670             return;
671         }
672         if (IsInteropIntrinsic(inst)) {
673             scopeForInst_[inst] = nullptr;
674         }
675     }
676     for (auto *succ : block->GetSuccsBlocks()) {
677         InvalidateScopesInSubgraph(succ);
678     }
679 }
680 
CheckGraphRec(BasicBlock * block,Inst * scopeStart)681 void InteropIntrinsicOptimization::CheckGraphRec(BasicBlock *block, Inst *scopeStart)
682 {
683     if (block->SetMarker(visited_)) {
684         if (GetInfo(block).lastScopeStart != scopeStart) {
685             // It's impossible to have scope start in one path to block and to have no scope in another path
686             ASSERT(GetInfo(block).lastScopeStart != nullptr);
687             ASSERT(scopeStart != nullptr);
688             // Different scope starts for different execution paths are possible
689             // NOTE(aefremov): find insts with always equal scopes somehow
690             InvalidateScopesInSubgraph(block);
691         }
692         return;
693     }
694     GetInfo(block).lastScopeStart = scopeStart;
695     for (auto *inst : block->Insts()) {
696         if (IsScopeEnd(inst)) {
697             ASSERT(scopeStart != nullptr);
698             scopeStart = nullptr;
699         } else if (IsScopeStart(inst)) {
700             ASSERT(scopeStart == nullptr);
701             scopeStart = inst;
702         } else if (IsForbiddenInst(inst)) {
703             ASSERT(scopeStart == nullptr);
704         } else if (IsInteropIntrinsic(inst)) {
705             ASSERT(scopeStart != nullptr);
706             scopeForInst_[inst] = scopeStart;
707         }
708     }
709     if (block->IsEndBlock()) {
710         ASSERT(scopeStart == nullptr);
711     }
712     for (auto *succ : block->GetSuccsBlocks()) {
713         CheckGraphRec(succ, scopeStart);
714     }
715 }
716 
CheckGraphAndFindScopes()717 void InteropIntrinsicOptimization::CheckGraphAndFindScopes()
718 {
719     auto visitedHolder = MarkerHolder(GetGraph());
720     visited_ = visitedHolder.GetMarker();
721     auto invalidatedHolder = MarkerHolder(GetGraph());
722     scopeStartInvalidated_ = invalidatedHolder.GetMarker();
723     CheckGraphRec(GetGraph()->GetStartBlock(), nullptr);
724 }
725 
MarkRequireRegMap(Inst * inst)726 void InteropIntrinsicOptimization::MarkRequireRegMap(Inst *inst)
727 {
728     SaveStateInst *saveState = nullptr;
729     if (inst->IsSaveState()) {
730         saveState = static_cast<SaveStateInst *>(inst);
731     } else if (inst->RequireState()) {
732         saveState = inst->GetSaveState();
733     }
734     while (saveState != nullptr && saveState->SetMarker(requireRegMap_) && saveState->GetCallerInst() != nullptr) {
735         saveState = saveState->GetCallerInst()->GetSaveState();
736     }
737 }
738 
TryRemoveUnwrapAndWrap(Inst * inst,Inst * input)739 void InteropIntrinsicOptimization::TryRemoveUnwrapAndWrap(Inst *inst, Inst *input)
740 {
741     if (scopeForInst_.at(inst) == nullptr || scopeForInst_.at(inst) != scopeForInst_.at(input)) {
742         COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
743             << "Scopes don't match, skip: wrap " << *inst << "\nwith unwrap input " << *input;
744         return;
745     }
746     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "Remove wrap " << *inst << "\nwith unwrap input " << *input;
747     auto *newInput = input->GetInput(0).GetInst();
748     // We don't extend scopes out of basic blocks in OSR mode
749     ASSERT(!GetGraph()->IsOsrMode() || inst->GetBasicBlock() == newInput->GetBasicBlock());
750     ASSERT(newInput->GetType() == DataType::POINTER);
751     ASSERT(inst->GetType() == DataType::POINTER);
752     inst->ReplaceUsers(newInput);
753     inst->GetBasicBlock()->RemoveInst(inst);
754     // If `input` (unwrap from local to JSValue or ets primitve) has SaveState users which require regmap,
755     // we will not delete the unwrap intrinsic
756     // NOTE(aefremov): support unwrap (local => JSValue/primitive) during deoptimization
757     isApplied_ = true;
758 }
759 
TryRemoveUnwrapToJSValue(Inst * inst)760 void InteropIntrinsicOptimization::TryRemoveUnwrapToJSValue(Inst *inst)
761 {
762     auto commonId = RuntimeInterface::IntrinsicId::INVALID;
763     DataType::Type userType = DataType::Type::NO_TYPE;
764     for (auto &user : inst->GetUsers()) {
765         auto *userInst = user.GetInst();
766         if (userInst->IsSaveState()) {
767             // see the previous note
768             if (userInst->IsMarked(requireRegMap_)) {
769                 COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
770                     << "User " << *userInst << "\nof inst " << *inst << " requires regmap, skip";
771                 return;
772             }
773             continue;
774         }
775         if (!userInst->IsIntrinsic()) {
776             return;
777         }
778         if (HasOsrEntryBetween(inst, userInst)) {
779             return;
780         }
781         auto currentId = userInst->CastToIntrinsic()->GetIntrinsicId();
782         if (commonId == RuntimeInterface::IntrinsicId::INVALID) {
783             userType = userInst->GetType();
784             commonId = currentId;
785         } else if (currentId != commonId) {
786             return;
787         }
788     }
789     auto newId = GetUnwrapIntrinsicId(commonId);
790     if (newId == RuntimeInterface::IntrinsicId::INVALID) {
791         // includes the case when common_id is INVALID
792         return;
793     }
794     inst->CastToIntrinsic()->SetIntrinsicId(newId);
795     inst->SetType(userType);
796     for (auto userIt = inst->GetUsers().begin(); userIt != inst->GetUsers().end();) {
797         auto userInst = userIt->GetInst();
798         if (userInst->IsIntrinsic() && userInst->CastToIntrinsic()->GetIntrinsicId() == commonId) {
799             COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
800                 << "Replace cast from JSValue " << *userInst << "\nwith direct unwrap " << *inst;
801             userInst->ReplaceUsers(inst);
802             userInst->GetBasicBlock()->RemoveInst(userInst);
803             userIt = inst->GetUsers().begin();
804         } else {
805             ++userIt;
806         }
807     }
808     isApplied_ = true;
809 }
810 
TryRemoveIntrinsic(Inst * inst)811 void InteropIntrinsicOptimization::TryRemoveIntrinsic(Inst *inst)
812 {
813     if (!inst->IsIntrinsic()) {
814         return;
815     }
816     auto *input = inst->GetInput(0).GetInst();
817     auto intrinsicId = inst->CastToIntrinsic()->GetIntrinsicId();
818     if (input->IsIntrinsic() && IsWrapIntrinsicId(intrinsicId) &&
819         IsUnwrapIntrinsicId(input->CastToIntrinsic()->GetIntrinsicId())) {
820         TryRemoveUnwrapAndWrap(inst, input);
821     } else if (intrinsicId == RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_JS_VALUE) {
822         TryRemoveUnwrapToJSValue(inst);
823     } else if (intrinsicId == RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_JS_CALL_FUNCTION && !inst->HasUsers()) {
824         // avoid creation of handle for return value in local scope if it is unused
825         inst->SetType(DataType::VOID);
826         inst->CastToIntrinsic()->SetIntrinsicId(
827             RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_JS_CALL_VOID_FUNCTION);
828         isApplied_ = true;
829     }
830 }
831 
EliminateCastPairs()832 void InteropIntrinsicOptimization::EliminateCastPairs()
833 {
834     auto requireRegMapHolder = MarkerHolder(GetGraph());
835     requireRegMap_ = requireRegMapHolder.GetMarker();
836     auto &blocksRpo = GetGraph()->GetBlocksRPO();
837     for (auto it = blocksRpo.rbegin(); it != blocksRpo.rend(); ++it) {
838         auto *block = *it;
839         for (auto inst : block->InstsSafeReverse()) {
840             if (inst->RequireRegMap()) {
841                 MarkRequireRegMap(inst);
842             }
843             TryRemoveIntrinsic(inst);
844         }
845     }
846 }
847 
DomTreeDfs(BasicBlock * block)848 void InteropIntrinsicOptimization::DomTreeDfs(BasicBlock *block)
849 {
850     // bb1->IsDominate(bb2) iff bb1.dom_tree_in <= bb2.dom_tree_in < bb1.dom_tree_out
851     GetInfo(block).domTreeIn = domTreeNum_++;
852     for (auto *dom : block->GetDominatedBlocks()) {
853         DomTreeDfs(dom);
854     }
855     GetInfo(block).domTreeOut = domTreeNum_;
856 }
857 
MarkPartiallyAnticipated(BasicBlock * block,BasicBlock * stopBlock)858 void InteropIntrinsicOptimization::MarkPartiallyAnticipated(BasicBlock *block, BasicBlock *stopBlock)
859 {
860     if (block->SetMarker(instAnticipated_)) {
861         return;
862     }
863     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
864         << "Mark block " << block->GetId() << " where current inst is partially anticipated";
865     GetInfo(block).subgraphMinNum = DFS_NOT_VISITED;
866     GetInfo(block).maxChain = 0;
867     GetInfo(block).maxDepth = -1L;
868     if (block == stopBlock) {
869         return;
870     }
871     ASSERT(!block->IsStartBlock());
872     for (auto *pred : block->GetPredsBlocks()) {
873         MarkPartiallyAnticipated(pred, stopBlock);
874     }
875 }
876 
CalculateDownSafe(BasicBlock * block)877 void InteropIntrinsicOptimization::CalculateDownSafe(BasicBlock *block)
878 {
879     auto &info = GetInfo(block);
880     if (!block->IsMarked(instAnticipated_)) {
881         info.maxChain = 0;
882         info.maxDepth = -1L;
883         info.subgraphMinNum = DFS_NOT_VISITED;
884         return;
885     }
886     if (info.subgraphMinNum != DFS_NOT_VISITED) {
887         return;
888     }
889     ASSERT(info.domTreeIn >= 0);
890     info.subgraphMinNum = info.subgraphMaxNum = info.domTreeIn;
891     int32_t succMaxChain = 0;
892     for (auto *succ : block->GetSuccsBlocks()) {
893         CalculateDownSafe(succ);
894         succMaxChain = std::max(succMaxChain, GetInfo(succ).maxChain);
895         if (!block->IsMarked(eliminationCandidate_)) {
896             info.subgraphMinNum = std::min(info.subgraphMinNum, GetInfo(succ).subgraphMinNum);
897             info.subgraphMaxNum = std::max(info.subgraphMaxNum, GetInfo(succ).subgraphMaxNum);
898         }
899     }
900     for (auto *dom : block->GetSuccsBlocks()) {
901         if (dom->IsMarked(instAnticipated_)) {
902             info.maxDepth = std::max(info.maxDepth, GetInfo(dom).maxDepth);
903         }
904     }
905     info.maxChain += succMaxChain;
906 }
907 
ReplaceInst(Inst * inst,Inst ** newInst,Inst * insertAfter)908 void InteropIntrinsicOptimization::ReplaceInst(Inst *inst, Inst **newInst, Inst *insertAfter)
909 {
910     ASSERT(inst != nullptr);
911     ASSERT(*newInst != nullptr);
912     ASSERT((*newInst)->IsDominate(inst));
913     if ((*newInst)->GetOpcode() == Opcode::SaveState) {
914         ASSERT(insertAfter != nullptr);
915         auto oldNextInst = inst->GetNext();
916         auto *block = inst->GetBasicBlock();
917         block->EraseInst(inst);
918         insertAfter->InsertAfter(inst);
919         inst->SetSaveState(*newInst);
920         *newInst = inst;
921         if (inst->IsReferenceOrAny()) {
922             // SSB is needed for conversion from local to JSValue or other ref type
923             ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), *newInst, oldNextInst, nullptr, block);
924         }
925     } else {
926         ASSERT(inst->GetOpcode() == (*newInst)->GetOpcode());
927         inst->ReplaceUsers(*newInst);
928         if (inst->IsReferenceOrAny()) {
929             ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), *newInst, inst);
930         }
931     }
932 }
933 
HasSaveState(BasicBlock * block)934 static bool HasSaveState(BasicBlock *block)
935 {
936     for (auto inst : block->Insts()) {
937         if (inst->GetOpcode() == Opcode::SaveState) {
938             return true;
939         }
940     }
941     return false;
942 }
943 
CanHoistTo(BasicBlock * block)944 bool InteropIntrinsicOptimization::CanHoistTo(BasicBlock *block)
945 {
946     ASSERT(!block->IsMarked(eliminationCandidate_));
947     auto &info = GetInfo(block);
948     bool dominatesSubgraph = info.domTreeIn <= info.subgraphMinNum && info.subgraphMaxNum < info.domTreeOut;
949     auto depth = block->GetLoop()->GetDepth();
950     // Heuristics to estimate if hoisting to this blocks is profitable, and it is not better to hoist to some dominated
951     // blocks instead
952     if (dominatesSubgraph && info.maxChain > 1) {
953         for (auto *dom : block->GetDominatedBlocks()) {
954             auto domDepth = dom->GetLoop()->GetDepth();
955             if (GetInfo(dom).maxChain > 0 &&
956                 (GetInfo(dom).maxChain < info.maxChain || domDepth > depth || !HasSaveState(dom))) {
957                 COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
958                     << " Block " << block->GetId() << " is candidate for hoisting";
959                 return true;
960             }
961         }
962     }
963     if (info.maxDepth > static_cast<int32_t>(depth)) {
964         for (auto *dom : block->GetDominatedBlocks()) {
965             auto domDepth = dom->GetLoop()->GetDepth();
966             if (GetInfo(dom).maxDepth >= 0 && (domDepth > depth || !HasSaveState(dom))) {
967                 COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
968                     << " Block " << block->GetId() << " is candidate for hoisting";
969                 return true;
970             }
971         }
972     }
973     return false;
974 }
975 
976 // DFS blocks dominated by start block, and save blocks from its dominance frontier to process them later in
977 // HoistAndEliminate. If *new_inst is SaveState, we move the first dominated inst we want to replace after insert_after
978 // and set *new_inst to it. Otherwise just replace dominated inst with *new_inst
HoistAndEliminateRec(BasicBlock * block,const BlockInfo & startInfo,Inst ** newInst,Inst * insertAfter)979 void InteropIntrinsicOptimization::HoistAndEliminateRec(BasicBlock *block, const BlockInfo &startInfo, Inst **newInst,
980                                                         Inst *insertAfter)
981 {
982     if (block->ResetMarker(eliminationCandidate_)) {
983         for (auto *inst : block->InstsSafe()) {
984             if (inst->IsMarked(eliminationCandidate_) && inst != *newInst) {
985                 ReplaceInst(inst, newInst, insertAfter);
986                 isApplied_ = true;
987             }
988         }
989     }
990     for (auto *succ : block->GetSuccsBlocks()) {
991         if (!succ->ResetMarker(instAnticipated_)) {
992             continue;
993         }
994         // Fast IsDominate check
995         if (startInfo.domTreeIn <= GetInfo(succ).domTreeIn && GetInfo(succ).domTreeIn < startInfo.domTreeOut) {
996             HoistAndEliminateRec(succ, startInfo, newInst, insertAfter);
997         } else {
998             blocksToProcess_.push_back(succ);
999         }
1000     }
1001 }
1002 
1003 // Returns SaveState and inst to insert after
GetHoistPosition(BasicBlock * block,Inst * boundaryInst)1004 static std::pair<Inst *, Inst *> GetHoistPosition(BasicBlock *block, Inst *boundaryInst)
1005 {
1006     for (auto inst : block->InstsReverse()) {
1007         if (inst == boundaryInst) {
1008             auto prev = inst->GetPrev();
1009             if (prev != nullptr && prev->GetOpcode() == Opcode::SaveState && !inst->IsMovableObject()) {
1010                 return std::make_pair(prev, inst);
1011             }
1012             return std::make_pair(nullptr, nullptr);
1013         }
1014         if (inst->GetOpcode() == Opcode::SaveState) {
1015             return std::make_pair(inst, inst);
1016         }
1017     }
1018     return std::make_pair(nullptr, nullptr);
1019 }
1020 
FindEliminationCandidate(BasicBlock * block)1021 Inst *InteropIntrinsicOptimization::FindEliminationCandidate(BasicBlock *block)
1022 {
1023     for (auto inst : block->Insts()) {
1024         if (inst->IsMarked(eliminationCandidate_)) {
1025             return inst;
1026         }
1027     }
1028     UNREACHABLE();
1029 }
1030 
HoistAndEliminate(BasicBlock * startBlock,Inst * boundaryInst)1031 void InteropIntrinsicOptimization::HoistAndEliminate(BasicBlock *startBlock, Inst *boundaryInst)
1032 {
1033     ASSERT(boundaryInst->GetBasicBlock() == startBlock);
1034     blocksToProcess_.clear();
1035     blocksToProcess_.push_back(startBlock);
1036     ASSERT(startBlock->ResetMarker(instAnticipated_));
1037     while (!blocksToProcess_.empty()) {
1038         auto *block = blocksToProcess_.back();
1039         blocksToProcess_.pop_back();
1040         // We reset inst_anticipated_ marker while we traverse the subgraph where inst is anticipated
1041         ASSERT(!block->IsMarked(instAnticipated_));
1042         auto &info = GetInfo(block);
1043         ASSERT(info.subgraphMinNum != DFS_INVALIDATED);
1044         Inst *newInst = nullptr;
1045         Inst *insertAfter = nullptr;
1046         if (block->IsMarked(eliminationCandidate_)) {
1047             newInst = FindEliminationCandidate(block);
1048         } else if (CanHoistTo(block)) {
1049             std::tie(newInst, insertAfter) = GetHoistPosition(block, boundaryInst);
1050             if (newInst == nullptr) {
1051                 info.subgraphMinNum = DFS_INVALIDATED;
1052                 continue;
1053             }
1054         }
1055         info.subgraphMinNum = DFS_INVALIDATED;
1056         if (newInst != nullptr) {
1057             HoistAndEliminateRec(block, GetInfo(block), &newInst, insertAfter);
1058             continue;
1059         }
1060         for (auto *succ : block->GetSuccsBlocks()) {
1061             if (succ->ResetMarker(instAnticipated_)) {
1062                 blocksToProcess_.push_back(succ);
1063             }
1064         }
1065     }
1066 }
1067 
DoRedundancyElimination(Inst * scopeStart,InstVector & insts)1068 void InteropIntrinsicOptimization::DoRedundancyElimination(Inst *scopeStart, InstVector &insts)
1069 {
1070     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
1071         << "Process group of intrinsics with identical inputs and scope start: " << *scopeStart;
1072     ASSERT(!insts.empty());
1073 #ifndef NDEBUG
1074     for (auto *inst : insts) {
1075         for (size_t i = 0; i < inst->GetInputsCount(); i++) {
1076             auto inputInst = inst->GetInput(i).GetInst();
1077             ASSERT(inputInst->IsSaveState() || inputInst == insts[0]->GetInput(i).GetInst());
1078         }
1079     }
1080 #endif
1081     auto *boundaryInst = scopeStart;
1082     // find highest common dominated inst of `scopeStart` and `insts` inputs
1083     // i. e. lowest (latest) inst among them
1084     for (auto &input : insts.front()->GetInputs()) {
1085         auto *inputInst = input.GetInst();
1086         if (!inputInst->IsSaveState() && boundaryInst->IsDominate(inputInst)) {
1087             boundaryInst = inputInst;
1088         }
1089     }
1090     ASSERT(scopeStart->IsDominate(boundaryInst));
1091     auto *boundary = boundaryInst->GetBasicBlock();
1092     auto instAnticipatedHolder = MarkerHolder(GetGraph());
1093     instAnticipated_ = instAnticipatedHolder.GetMarker();
1094     auto eliminationCandidateHolder = MarkerHolder(GetGraph());
1095     eliminationCandidate_ = eliminationCandidateHolder.GetMarker();
1096     // Marking blocks where inst is partially anticipated is needed only to reduce number of processed blocks
1097     for (auto *inst : insts) {
1098         COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << " Inst in this group: " << *inst;
1099         inst->SetMarker(eliminationCandidate_);
1100         auto *block = inst->GetBasicBlock();
1101         block->SetMarker(eliminationCandidate_);
1102         MarkPartiallyAnticipated(block, boundary);
1103         GetInfo(block).maxChain++;
1104         GetInfo(block).maxDepth = static_cast<int32_t>(block->GetLoop()->GetDepth());
1105     }
1106     CalculateDownSafe(boundary);
1107     HoistAndEliminate(boundary, boundaryInst);
1108 }
1109 
SaveSiblingForElimination(Inst * sibling,ArenaMap<Inst *,InstVector> & currentInsts,RuntimeInterface::IntrinsicId id,Marker processed)1110 void InteropIntrinsicOptimization::SaveSiblingForElimination(Inst *sibling, ArenaMap<Inst *, InstVector> &currentInsts,
1111                                                              RuntimeInterface::IntrinsicId id, Marker processed)
1112 {
1113     if (!sibling->IsIntrinsic() || sibling->CastToIntrinsic()->GetIntrinsicId() != id) {
1114         return;
1115     }
1116     sibling->SetMarker(processed);
1117     auto scopeStart = scopeForInst_.at(sibling);
1118     if (scopeStart != nullptr) {
1119         auto it = currentInsts.try_emplace(scopeStart, GetGraph()->GetLocalAllocator()->Adapter()).first;
1120         it->second.push_back(sibling);
1121     }
1122 }
1123 
RedundancyElimination()1124 void InteropIntrinsicOptimization::RedundancyElimination()
1125 {
1126     DomTreeDfs(GetGraph()->GetStartBlock());
1127     auto processedHolder = MarkerHolder(GetGraph());
1128     auto processed = processedHolder.GetMarker();
1129     ArenaMap<Inst *, InstVector> currentInsts(GetGraph()->GetLocalAllocator()->Adapter());
1130     for (auto *block : GetGraph()->GetBlocksRPO()) {
1131         for (auto *inst : block->InstsSafe()) {
1132             if (inst->IsMarked(processed) || !IsConvertIntrinsic(inst)) {
1133                 continue;
1134             }
1135             auto id = inst->CastToIntrinsic()->GetIntrinsicId();
1136             currentInsts.clear();
1137             auto *input = inst->GetInput(0).GetInst();
1138             for (auto &user : input->GetUsers()) {
1139                 SaveSiblingForElimination(user.GetInst(), currentInsts, id, processed);
1140             }
1141             for (auto &[scope, insts] : currentInsts) {
1142                 DoRedundancyElimination(scope, insts);
1143             }
1144         }
1145     }
1146     {
1147         currentInsts.clear();
1148         auto id = RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_LOAD_JS_CONSTANT_POOL;
1149         for (auto *block : GetGraph()->GetBlocksRPO()) {
1150             for (auto *inst : block->Insts()) {
1151                 if (inst->IsIntrinsic() && inst->CastToIntrinsic()->GetIntrinsicId() == id) {
1152                     SaveSiblingForElimination(inst, currentInsts, id, processed);
1153                 }
1154             }
1155         }
1156         for (auto &[scope, insts] : currentInsts) {
1157             DoRedundancyElimination(scope, insts);
1158         }
1159     }
1160 }
1161 
RunImpl()1162 bool InteropIntrinsicOptimization::RunImpl()
1163 {
1164     ASSERT(!GetGraph()->IsBytecodeOptimizer());
1165     bool oneScope = TryCreateSingleScope();
1166     if (!hasScopes_) {
1167         return false;
1168     }
1169     GetGraph()->RunPass<LoopAnalyzer>();
1170     GetGraph()->RunPass<DominatorsTree>();
1171     if (!oneScope) {
1172         COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "  Phase I: merge scopes inside basic blocks";
1173         for (auto *block : GetGraph()->GetBlocksRPO()) {
1174             MergeScopesInsideBlock(block);
1175         }
1176         if (!GetGraph()->IsOsrMode()) {
1177             for (auto loop : GetGraph()->GetRootLoop()->GetInnerLoops()) {
1178                 FindForbiddenLoops(loop);
1179             }
1180             COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
1181                 << "  Phase II: remove inter-scope regions to merge neighbouring scopes";
1182             MergeInterScopeRegions();
1183             if (!objectLimitHit_) {
1184                 COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "  Phase III: hoist scope starts";
1185                 HoistScopeStarts();
1186             }
1187         }
1188     }
1189     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT)
1190         << "  Phase IV: Check graph and find scope starts for interop intrinsics";
1191     // NB: we assume that each scope is residing inside one block before the pass, and that there are no nested scopes
1192     CheckGraphAndFindScopes();
1193     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "  Phase V: Peepholes for interop intrinsics";
1194     EliminateCastPairs();
1195     COMPILER_LOG(DEBUG, INTEROP_INTRINSIC_OPT) << "  Phase VI: Redundancy elimination for wrap intrinsics";
1196     RedundancyElimination();
1197     return IsApplied();
1198 }
1199 
1200 }  // namespace ark::compiler
1201