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