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> ¤tInsts,
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