1 /**
2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "simplify_string_builder.h"
17
18 #include "compiler_logger.h"
19
20 #include "optimizer/analysis/alias_analysis.h"
21 #include "optimizer/analysis/bounds_analysis.h"
22 #include "optimizer/analysis/dominators_tree.h"
23 #include "optimizer/ir/analysis.h"
24 #include "optimizer/ir/inst.h"
25
26 #include "optimizer/optimizations/cleanup.h"
27 #include "optimizer/optimizations/string_builder_utils.h"
28
29 namespace ark::compiler {
30
SimplifyStringBuilder(Graph * graph)31 SimplifyStringBuilder::SimplifyStringBuilder(Graph *graph)
32 : Optimization(graph),
33 instructionsStack_ {graph->GetLocalAllocator()->Adapter()},
34 instructionsVector_ {graph->GetLocalAllocator()->Adapter()},
35 inputDescriptors_ {graph->GetLocalAllocator()->Adapter()},
36 usages_ {graph->GetLocalAllocator()->Adapter()},
37 matches_ {graph->GetLocalAllocator()->Adapter()}
38 {
39 }
40
HasTryCatchBlocks(Graph * graph)41 bool HasTryCatchBlocks(Graph *graph)
42 {
43 for (auto block : graph->GetBlocksRPO()) {
44 if (block->IsTryCatch()) {
45 return true;
46 }
47 }
48 return false;
49 }
50
RunImpl()51 bool SimplifyStringBuilder::RunImpl()
52 {
53 isApplied_ = false;
54
55 if (!GetGraph()->IsAnalysisValid<DominatorsTree>()) {
56 GetGraph()->RunPass<DominatorsTree>();
57 }
58
59 ASSERT(GetGraph()->GetRootLoop() != nullptr);
60
61 // Loops with try-catch block and OSR mode are not supported in current implementation
62 if (!HasTryCatchBlocks(GetGraph()) && !GetGraph()->IsOsrMode()) {
63 for (auto loop : GetGraph()->GetRootLoop()->GetInnerLoops()) {
64 OptimizeStringConcatenation(loop);
65 }
66 }
67
68 for (auto block : GetGraph()->GetBlocksRPO()) {
69 if (block->IsEmpty()) {
70 continue;
71 }
72 OptimizeStringBuilderToString(block);
73 OptimizeStringConcatenation(block);
74 }
75
76 COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Simplify StringBuilder complete";
77
78 // Remove save state inserted in loop post exit block at IR builder
79 for (auto block : GetGraph()->GetBlocksRPO()) {
80 for (auto inst : block->Insts()) {
81 if (inst->GetOpcode() != Opcode::SaveState) {
82 continue;
83 }
84 inst->ClearFlag(inst_flags::NO_DCE);
85 }
86 }
87
88 // Cleanup should be done inside pass, to satisfy GraphChecker
89 GetGraph()->RunPass<compiler::Cleanup>();
90
91 return isApplied_;
92 }
93
InvalidateAnalyses()94 void SimplifyStringBuilder::InvalidateAnalyses()
95 {
96 GetGraph()->InvalidateAnalysis<BoundsAnalysis>();
97 GetGraph()->InvalidateAnalysis<AliasAnalysis>();
98 }
99
SkipToStringBuilderConstructorWithStringArg(InstIter begin,InstIter end)100 InstIter SimplifyStringBuilder::SkipToStringBuilderConstructorWithStringArg(InstIter begin, InstIter end)
101 {
102 return std::find_if(std::move(begin), std::move(end),
103 [](auto inst) { return IsMethodStringBuilderConstructorWithStringArg(inst); });
104 }
105
IsDataFlowInput(Inst * inst,Inst * input)106 bool IsDataFlowInput(Inst *inst, Inst *input)
107 {
108 for (size_t i = 0; i < inst->GetInputsCount(); ++i) {
109 if (inst->GetDataFlowInput(i) == input) {
110 return true;
111 }
112 }
113 return false;
114 }
115
IsUsedOutsideBasicBlock(Inst * inst,BasicBlock * bb)116 bool IsUsedOutsideBasicBlock(Inst *inst, BasicBlock *bb)
117 {
118 for (auto &user : inst->GetUsers()) {
119 auto userInst = user.GetInst();
120 if (userInst->IsCheck()) {
121 if (!userInst->HasSingleUser()) {
122 // In case of multi user check-instruction we assume it is used outside current basic block without
123 // actually testing it.
124 return true;
125 }
126 // In case of single user check-instruction we test its the only user.
127 userInst = userInst->GetUsers().Front().GetInst();
128 }
129 if (userInst->GetBasicBlock() != bb) {
130 return true;
131 }
132 }
133 return false;
134 }
135
OptimizeStringBuilderToString(BasicBlock * block)136 void SimplifyStringBuilder::OptimizeStringBuilderToString(BasicBlock *block)
137 {
138 // Removes unnecessary String Builder instances
139
140 ASSERT(block != nullptr);
141 ASSERT(block->GetGraph() == GetGraph());
142
143 // Walk through a basic block, find every StringBuilder instance and constructor call,
144 // and check it we can remove/replace them
145 InstIter inst = block->Insts().begin();
146 while ((inst = SkipToStringBuilderConstructorWithStringArg(inst, block->Insts().end())) != block->Insts().end()) {
147 ASSERT((*inst)->IsStaticCall());
148 auto ctorCall = (*inst)->CastToCallStatic();
149
150 // void StringBuilder::<ctor> instance, arg, save_state
151 ASSERT(ctorCall->GetInputsCount() == ARGS_NUM_3);
152 auto instance = ctorCall->GetInput(0).GetInst();
153 auto arg = ctorCall->GetInput(1).GetInst();
154
155 // Look for StringBuilder usages within current basic block
156 auto nextInst = block->Insts().end();
157 bool removeInstance = true;
158 for (++inst; inst != block->Insts().end(); ++inst) {
159 // Skip SaveState instructions
160 if ((*inst)->IsSaveState()) {
161 continue;
162 }
163
164 // Skip check instructions, like NullCheck, RefTypeCheck, etc.
165 if ((*inst)->IsCheck()) {
166 continue;
167 }
168
169 // Continue (outer loop) with the next StringBuilder constructor,
170 // in case we met one in inner loop
171 if (IsMethodStringBuilderConstructorWithStringArg(*inst)) {
172 nextInst = nextInst != block->Insts().end() ? nextInst : inst;
173 }
174
175 if (!IsDataFlowInput(*inst, instance)) {
176 continue;
177 }
178
179 // Process usages of StringBuilder instance:
180 // replace toString()-calls until we met something else
181 if (IsStringBuilderToString(*inst)) {
182 (*inst)->ReplaceUsers(arg);
183 (*inst)->ClearFlag(compiler::inst_flags::NO_DCE);
184 COMPILER_LOG(DEBUG, SIMPLIFY_SB)
185 << "Remove StringBuilder toString()-call (id=" << (*inst)->GetId() << ")";
186 isApplied_ = true;
187 } else {
188 removeInstance = false;
189 break;
190 }
191 }
192
193 // Remove StringBuilder instance unless it has usages
194 if (removeInstance && !IsUsedOutsideBasicBlock(instance, instance->GetBasicBlock())) {
195 RemoveStringBuilderInstance(instance);
196 isApplied_ = true;
197 }
198
199 // Proceed to the next StringBuilder constructor
200 inst = nextInst != block->Insts().end() ? nextInst : inst;
201 }
202 }
203
SkipToStringBuilderDefaultConstructor(InstIter begin,InstIter end)204 InstIter SimplifyStringBuilder::SkipToStringBuilderDefaultConstructor(InstIter begin, InstIter end)
205 {
206 return std::find_if(std::move(begin), std::move(end),
207 [](auto inst) { return IsMethodStringBuilderDefaultConstructor(inst); });
208 }
209
CreateConcatIntrinsic(const std::array<IntrinsicInst *,ARGS_NUM_4> & appendIntrinsics,size_t appendCount,DataType::Type type,SaveStateInst * saveState)210 IntrinsicInst *SimplifyStringBuilder::CreateConcatIntrinsic(
211 const std::array<IntrinsicInst *, ARGS_NUM_4> &appendIntrinsics, size_t appendCount, DataType::Type type,
212 SaveStateInst *saveState)
213 {
214 auto concatIntrinsic =
215 GetGraph()->CreateInstIntrinsic(GetGraph()->GetRuntime()->GetStringConcatStringsIntrinsicId(appendCount));
216 ASSERT(concatIntrinsic->RequireState());
217
218 concatIntrinsic->SetType(type);
219 // Allocate input types (+1 input for save state)
220 concatIntrinsic->AllocateInputTypes(GetGraph()->GetAllocator(), appendCount + 1);
221
222 for (size_t index = 0; index < appendCount; ++index) {
223 auto arg = appendIntrinsics[index]->GetInput(1).GetInst();
224 concatIntrinsic->AppendInput(arg);
225 concatIntrinsic->AddInputType(arg->GetType());
226 }
227
228 auto saveStateClone = CopySaveState(GetGraph(), saveState);
229 concatIntrinsic->AppendInput(saveStateClone);
230 concatIntrinsic->AddInputType(saveStateClone->GetType());
231
232 return concatIntrinsic;
233 }
234
IsIntrinsicStringBuilderAppendString(Inst * inst) const235 bool SimplifyStringBuilder::IsIntrinsicStringBuilderAppendString(Inst *inst) const
236 {
237 if (!inst->IsIntrinsic()) {
238 return false;
239 }
240
241 auto runtime = GetGraph()->GetRuntime();
242 return runtime->IsIntrinsicStringBuilderAppendString(inst->CastToIntrinsic()->GetIntrinsicId());
243 }
244
MatchConcatenation(InstIter & begin,const InstIter & end,ConcatenationMatch & match)245 bool SimplifyStringBuilder::MatchConcatenation(InstIter &begin, const InstIter &end, ConcatenationMatch &match)
246 {
247 // Walk instruction range [begin, end) and fill the match structure with StringBuilder usage instructions found
248
249 auto instance = match.instance;
250
251 if (IsUsedOutsideBasicBlock(instance, instance->GetBasicBlock())) {
252 return false;
253 }
254
255 int toStringCallsCount = 0;
256 for (; begin != end; ++begin) {
257 if ((*begin)->IsSaveState()) {
258 continue;
259 }
260
261 // Skip instruction having nothing to do with current instance
262 if (!IsDataFlowInput(*begin, instance)) {
263 continue;
264 }
265
266 // Walk through NullChecks
267 auto inst = *begin;
268 if (inst->IsNullCheck()) {
269 if (!inst->HasSingleUser()) {
270 return false; // Unsupported case: doesn't look like concatenation pattern
271 }
272 inst = inst->GetUsers().Front().GetInst();
273 if (inst->GetBasicBlock() != instance->GetBasicBlock()) {
274 return false; // Unsupported case: doesn't look like concatenation pattern
275 }
276 continue;
277 }
278
279 if (IsIntrinsicStringBuilderAppendString(inst)) {
280 if (match.appendCount >= match.appendIntrinsics.size()) {
281 return false; // Unsupported case: too many arguments concatenated
282 }
283 auto intrinsic = inst->CastToIntrinsic();
284 match.appendIntrinsics[match.appendCount++] = intrinsic;
285 } else if (IsStringBuilderToString(inst)) {
286 toStringCallsCount++;
287 match.toStringCall = *begin;
288 } else {
289 break;
290 }
291 }
292
293 // Supported case: number of toString-calls is one,
294 // number of append calls is between 2 and 4
295 return toStringCallsCount == 1 && match.appendCount > 1;
296 }
297
FixBrokenSaveStates(Inst * source,Inst * target)298 void SimplifyStringBuilder::FixBrokenSaveStates(Inst *source, Inst *target)
299 {
300 if (source->IsMovableObject()) {
301 ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), source, target);
302 }
303 }
304
Check(const ConcatenationMatch & match)305 void SimplifyStringBuilder::Check(const ConcatenationMatch &match)
306 {
307 // NOLINTBEGIN(readability-magic-numbers)
308 [[maybe_unused]] auto &appendIntrinsics = match.appendIntrinsics;
309 ASSERT(match.appendCount > 1);
310 ASSERT(appendIntrinsics[0U] != nullptr);
311 ASSERT(appendIntrinsics[0U]->GetInputsCount() > 1);
312 ASSERT(appendIntrinsics[1U] != nullptr);
313 ASSERT(appendIntrinsics[1U]->GetInputsCount() > 1);
314
315 switch (match.appendCount) {
316 case ARGS_NUM_2:
317 break;
318 case ARGS_NUM_3: {
319 ASSERT(appendIntrinsics[2U] != nullptr);
320 ASSERT(appendIntrinsics[2U]->GetInputsCount() > 1);
321 break;
322 }
323 case ARGS_NUM_4: {
324 ASSERT(appendIntrinsics[2U] != nullptr);
325 ASSERT(appendIntrinsics[2U]->GetInputsCount() > 1);
326 ASSERT(appendIntrinsics[3U] != nullptr);
327 ASSERT(appendIntrinsics[3U]->GetInputsCount() > 1);
328 break;
329 }
330 default:
331 UNREACHABLE();
332 }
333 // NOLINTEND(readability-magic-numbers)
334 }
335
InsertIntrinsicAndFixSaveStates(IntrinsicInst * concatIntrinsic,const std::array<IntrinsicInst *,ARGS_NUM_4> & appendIntrinsics,size_t appendCount,Inst * before)336 void SimplifyStringBuilder::InsertIntrinsicAndFixSaveStates(
337 IntrinsicInst *concatIntrinsic, const std::array<IntrinsicInst *, ARGS_NUM_4> &appendIntrinsics, size_t appendCount,
338 Inst *before)
339 {
340 InsertBeforeWithSaveState(concatIntrinsic, before);
341 for (size_t index = 0; index < appendCount; ++index) {
342 auto arg = appendIntrinsics[index]->GetDataFlowInput(1);
343 FixBrokenSaveStates(arg, concatIntrinsic);
344 }
345 }
346
ReplaceWithIntrinsic(const ConcatenationMatch & match)347 void SimplifyStringBuilder::ReplaceWithIntrinsic(const ConcatenationMatch &match)
348 {
349 auto &appendIntrinsics = match.appendIntrinsics;
350 auto toStringCall = match.toStringCall;
351
352 auto concatIntrinsic = CreateConcatIntrinsic(appendIntrinsics, match.appendCount, toStringCall->GetType(),
353 toStringCall->GetSaveState());
354 InsertIntrinsicAndFixSaveStates(concatIntrinsic, appendIntrinsics, match.appendCount, toStringCall);
355 toStringCall->ReplaceUsers(concatIntrinsic);
356
357 COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Replace StringBuilder (id=" << match.ctorCall->GetId()
358 << ") append-intrinsics with concat intrinsic (id=" << concatIntrinsic->GetId()
359 << ")";
360 }
361
Cleanup(const ConcatenationMatch & match)362 void SimplifyStringBuilder::Cleanup(const ConcatenationMatch &match)
363 {
364 RemoveStringBuilderInstance(match.instance);
365 }
366
OptimizeStringConcatenation(BasicBlock * block)367 void SimplifyStringBuilder::OptimizeStringConcatenation(BasicBlock *block)
368 {
369 // Replace String Builder usage with string concatenation whenever optimal
370
371 ASSERT(block != nullptr);
372 ASSERT(block->GetGraph() == GetGraph());
373
374 MarkerHolder visitedMarker {GetGraph()};
375 Marker visited = visitedMarker.GetMarker();
376
377 bool isAppliedLocal;
378 do {
379 isAppliedLocal = false;
380
381 // Walk through a basic block, find every String concatenation pattern,
382 // and check if we can optimize them
383 InstIter inst = block->Insts().begin();
384 while ((inst = SkipToStringBuilderDefaultConstructor(inst, block->Insts().end())) != block->Insts().end()) {
385 ASSERT((*inst)->IsStaticCall());
386 auto ctorCall = (*inst)->CastToCallStatic();
387
388 ASSERT(ctorCall->GetInputsCount() > 0);
389 auto instance = ctorCall->GetInput(0).GetInst();
390
391 ++inst;
392 if (instance->IsMarked(visited)) {
393 continue;
394 }
395
396 ConcatenationMatch match {instance, ctorCall};
397 // Check if current StringBuilder instance can be optimized
398 if (MatchConcatenation(inst, block->Insts().end(), match)) {
399 Check(match);
400 ReplaceWithIntrinsic(match);
401 Cleanup(match);
402
403 instance->SetMarker(visited);
404
405 isAppliedLocal = true;
406 isApplied_ = true;
407 }
408 }
409 } while (isAppliedLocal);
410 }
411
Clear()412 void SimplifyStringBuilder::ConcatenationLoopMatch::TemporaryInstructions::Clear()
413 {
414 intermediateValue = nullptr;
415 toStringCall = nullptr;
416 instance = nullptr;
417 ctorCall = nullptr;
418 appendAccValue = nullptr;
419 }
420
IsEmpty() const421 bool SimplifyStringBuilder::ConcatenationLoopMatch::TemporaryInstructions::IsEmpty() const
422 {
423 return toStringCall == nullptr || intermediateValue == nullptr || instance == nullptr || ctorCall == nullptr ||
424 appendAccValue == nullptr;
425 }
426
Clear()427 void SimplifyStringBuilder::ConcatenationLoopMatch::Clear()
428 {
429 block = nullptr;
430 accValue = nullptr;
431 initialValue = nullptr;
432
433 preheader.instance = nullptr;
434 preheader.ctorCall = nullptr;
435 preheader.appendAccValue = nullptr;
436
437 loop.appendInstructions.clear();
438 temp.clear();
439 exit.toStringCall = nullptr;
440 }
441
HasAppendUsersOnly(Inst * inst) const442 bool SimplifyStringBuilder::HasAppendUsersOnly(Inst *inst) const
443 {
444 MarkerHolder visited {inst->GetBasicBlock()->GetGraph()};
445 bool found = HasUserPhiRecursively(inst, visited.GetMarker(), [inst](auto &user) {
446 bool sameLoop = user.GetInst()->GetBasicBlock()->GetLoop() == inst->GetBasicBlock()->GetLoop();
447 bool isSaveState = user.GetInst()->IsSaveState();
448 bool isPhi = user.GetInst()->IsPhi();
449 bool isAppendInstruction = IsStringBuilderAppend(user.GetInst());
450 return sameLoop && !isSaveState && !isPhi && !isAppendInstruction;
451 });
452 ResetUserMarkersRecursively(inst, visited.GetMarker());
453 return !found;
454 }
455
IsCheckCastWithoutUsers(Inst * inst)456 bool IsCheckCastWithoutUsers(Inst *inst)
457 {
458 return inst->GetOpcode() == Opcode::CheckCast && !inst->HasUsers();
459 }
460
HasPhiOrAppendUsersOnly(Inst * inst,Marker appendInstructionVisited) const461 bool SimplifyStringBuilder::HasPhiOrAppendUsersOnly(Inst *inst, Marker appendInstructionVisited) const
462 {
463 MarkerHolder phiVisited {GetGraph()};
464 bool found = HasUserPhiRecursively(
465 inst, phiVisited.GetMarker(), [loop = inst->GetBasicBlock()->GetLoop(), appendInstructionVisited](auto &user) {
466 bool sameLoop = user.GetInst()->GetBasicBlock()->GetLoop() == loop;
467 bool isSaveState = user.GetInst()->IsSaveState();
468 bool isCheckCast = IsCheckCastWithoutUsers(user.GetInst());
469 bool isPhi = user.GetInst()->IsPhi();
470 bool isVisited = user.GetInst()->IsMarked(appendInstructionVisited);
471 bool isAppendInstruction = IsStringBuilderAppend(user.GetInst());
472 return sameLoop && !isSaveState && !isCheckCast && !isPhi && !(isAppendInstruction && isVisited);
473 });
474 return !found;
475 }
476
IsInstanceHoistable() const477 bool SimplifyStringBuilder::ConcatenationLoopMatch::IsInstanceHoistable() const
478 {
479 if (block == nullptr || accValue == nullptr || initialValue == nullptr) {
480 return false;
481 }
482
483 if (preheader.instance == nullptr || preheader.ctorCall == nullptr || preheader.appendAccValue == nullptr) {
484 return false;
485 }
486
487 if (block != preheader.instance->GetBasicBlock() || block != preheader.ctorCall->GetBasicBlock() ||
488 block != preheader.appendAccValue->GetBasicBlock()) {
489 return false;
490 }
491
492 if ((block->IsTry() || block->IsCatch()) && block->GetTryId() != block->GetLoop()->GetPreHeader()->GetTryId()) {
493 return false;
494 }
495
496 if (loop.appendInstructions.empty()) {
497 return false;
498 }
499
500 if (exit.toStringCall == nullptr) {
501 return false;
502 }
503
504 return true;
505 }
506
IsInstanceHoistable(const ConcatenationLoopMatch & match) const507 bool SimplifyStringBuilder::IsInstanceHoistable(const ConcatenationLoopMatch &match) const
508 {
509 return match.IsInstanceHoistable() && HasAppendUsersOnly(match.accValue);
510 }
511
IsToStringHoistable(const ConcatenationLoopMatch & match,Marker appendInstructionVisited) const512 bool SimplifyStringBuilder::IsToStringHoistable(const ConcatenationLoopMatch &match,
513 Marker appendInstructionVisited) const
514 {
515 return HasPhiOrAppendUsersOnly(match.exit.toStringCall, appendInstructionVisited);
516 }
517
FindPreHeaderSaveState(Loop * loop)518 SaveStateInst *FindPreHeaderSaveState(Loop *loop)
519 {
520 for (const auto &inst : loop->GetPreHeader()->InstsReverse()) {
521 if (inst->GetOpcode() == Opcode::SaveState) {
522 return inst->CastToSaveState();
523 }
524 }
525 return nullptr;
526 }
527
FindFirstSaveState(BasicBlock * block)528 SaveStateInst *FindFirstSaveState(BasicBlock *block)
529 {
530 if (block->IsEmpty()) {
531 return nullptr;
532 }
533
534 for (auto inst : block->Insts()) {
535 if (inst->GetOpcode() == Opcode::SaveState) {
536 return inst->CastToSaveState();
537 }
538 }
539
540 return nullptr;
541 }
542
CountOuterLoopSuccs(BasicBlock * block)543 size_t CountOuterLoopSuccs(BasicBlock *block)
544 {
545 return std::count_if(block->GetSuccsBlocks().begin(), block->GetSuccsBlocks().end(),
546 [block](auto succ) { return succ->GetLoop() == block->GetLoop()->GetOuterLoop(); });
547 }
548
GetOuterLoopSucc(BasicBlock * block)549 BasicBlock *GetOuterLoopSucc(BasicBlock *block)
550 {
551 auto found = std::find_if(block->GetSuccsBlocks().begin(), block->GetSuccsBlocks().end(),
552 [block](auto succ) { return succ->GetLoop() == block->GetLoop()->GetOuterLoop(); });
553 return found != block->GetSuccsBlocks().end() ? *found : nullptr;
554 }
555
GetLoopPostExit(Loop * loop)556 BasicBlock *GetLoopPostExit(Loop *loop)
557 {
558 // Find a block immediately following after loop
559 // Supported case:
560 // 1. Preheader block exists
561 // 2. Loop has exactly one block with exactly one successor outside a loop (post exit block)
562 // 3. Preheader dominates post exit block found
563 // Loop structures different from the one described above are not supported in current implementation
564
565 BasicBlock *postExit = nullptr;
566 for (auto block : loop->GetBlocks()) {
567 size_t count = CountOuterLoopSuccs(block);
568 if (count == 0) {
569 continue;
570 }
571 if (count == 1 && postExit == nullptr) {
572 postExit = GetOuterLoopSucc(block);
573 continue;
574 }
575 // Unsupported case
576 return nullptr;
577 }
578
579 // Supported case
580 if (postExit != nullptr && postExit->GetPredsBlocks().size() == 1 && loop->GetPreHeader() != nullptr &&
581 loop->GetPreHeader()->IsDominate(postExit)) {
582 return postExit;
583 }
584
585 // Unsupported case
586 return nullptr;
587 }
588
CreateIntrinsicStringBuilderAppendString(Inst * instance,Inst * arg,SaveStateInst * saveState)589 IntrinsicInst *SimplifyStringBuilder::CreateIntrinsicStringBuilderAppendString(Inst *instance, Inst *arg,
590 SaveStateInst *saveState)
591 {
592 auto appendIntrinsic = GetGraph()->CreateInstIntrinsic(
593 GetGraph()->GetRuntime()->ConvertTypeToStringBuilderAppendIntrinsicId(DataType::REFERENCE));
594 ASSERT(appendIntrinsic->RequireState());
595
596 appendIntrinsic->SetType(instance->GetType());
597 appendIntrinsic->SetInputs(
598 GetGraph()->GetAllocator(),
599 {{instance, instance->GetType()}, {arg, arg->GetType()}, {saveState, saveState->GetType()}});
600
601 return appendIntrinsic;
602 }
603
NormalizeStringBuilderAppendInstructionUsers(Inst * instance,SaveStateInst * saveState)604 void SimplifyStringBuilder::NormalizeStringBuilderAppendInstructionUsers(Inst *instance, SaveStateInst *saveState)
605 {
606 [[maybe_unused]] Inst *ctorCall = nullptr;
607
608 for (auto &user : instance->GetUsers()) {
609 auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
610 // Make additional append-call out of constructor argument (if present)
611 if (IsMethodStringBuilderConstructorWithStringArg(userInst)) {
612 ASSERT(ctorCall == nullptr);
613 ctorCall = userInst;
614 auto ctorArg = ctorCall->GetInput(1).GetInst();
615 CreateIntrinsicStringBuilderAppendString(instance, ctorArg, saveState);
616 } else if (IsMethodStringBuilderDefaultConstructor(userInst)) {
617 ASSERT(ctorCall == nullptr);
618 ctorCall = userInst;
619 } else if (IsStringBuilderAppend(userInst)) {
620 // StringBuilder append-call returns 'this' (instance)
621 // Replace all users of append-call by instance for simplicity
622 userInst->ReplaceUsers(instance);
623 }
624 }
625 ASSERT(ctorCall != nullptr);
626 }
627
FindStringBuilderAppendInstructions(Inst * instance)628 ArenaVector<Inst *> SimplifyStringBuilder::FindStringBuilderAppendInstructions(Inst *instance)
629 {
630 ArenaVector<Inst *> appendInstructions {GetGraph()->GetAllocator()->Adapter()};
631 for (auto &user : instance->GetUsers()) {
632 auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
633 if (IsStringBuilderAppend(userInst)) {
634 appendInstructions.push_back(userInst);
635 }
636 }
637
638 return appendInstructions;
639 }
640
RemoveFromInstructionInputs(ArenaVector<std::pair<Inst *,size_t>> & inputDescriptors)641 void RemoveFromInstructionInputs(ArenaVector<std::pair<Inst *, size_t>> &inputDescriptors)
642 {
643 // Inputs must be walked in reverse order for removal
644 std::sort(inputDescriptors.begin(), inputDescriptors.end(),
645 [](auto inputDescX, auto inputDescY) { return inputDescX.second > inputDescY.second; });
646
647 for (auto inputDesc : inputDescriptors) {
648 auto inst = inputDesc.first;
649 auto index = inputDesc.second;
650
651 [[maybe_unused]] auto inputInst = inst->GetInput(index).GetInst();
652 COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Remove input id=" << inputInst->GetId() << " ("
653 << GetOpcodeString(inputInst->GetOpcode())
654 << ") from instruction id=" << inst->GetId() << " ("
655 << GetOpcodeString(inst->GetOpcode()) << ")";
656
657 inst->RemoveInput(index);
658 }
659 }
660
RemoveFromSaveStateInputs(Inst * inst)661 void SimplifyStringBuilder::RemoveFromSaveStateInputs(Inst *inst)
662 {
663 inputDescriptors_.clear();
664
665 for (auto &user : inst->GetUsers()) {
666 if (!user.GetInst()->IsSaveState()) {
667 continue;
668 }
669 inputDescriptors_.emplace_back(user.GetInst(), user.GetIndex());
670 }
671
672 RemoveFromInstructionInputs(inputDescriptors_);
673 }
674
RemoveFromAllExceptPhiInputs(Inst * inst)675 void SimplifyStringBuilder::RemoveFromAllExceptPhiInputs(Inst *inst)
676 {
677 inputDescriptors_.clear();
678
679 for (auto &user : inst->GetUsers()) {
680 if (user.GetInst()->IsPhi()) {
681 continue;
682 }
683 inputDescriptors_.emplace_back(user.GetInst(), user.GetIndex());
684 }
685
686 RemoveFromInstructionInputs(inputDescriptors_);
687 }
688
RemoveStringBuilderInstance(Inst * instance)689 void SimplifyStringBuilder::RemoveStringBuilderInstance(Inst *instance)
690 {
691 ASSERT(!HasUser(instance, [](auto &user) {
692 auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
693 auto hasUsers = userInst->HasUsers();
694 auto isSaveState = userInst->IsSaveState();
695 auto isCtorCall = IsMethodStringBuilderDefaultConstructor(userInst) ||
696 IsMethodStringBuilderConstructorWithStringArg(userInst) ||
697 IsMethodStringBuilderConstructorWithCharArrayArg(userInst);
698 auto isAppendInstruction = IsStringBuilderAppend(userInst);
699 auto isToStringCall = IsStringBuilderToString(userInst);
700 return !(isSaveState || isCtorCall || ((isAppendInstruction || isToStringCall) && !hasUsers));
701 }));
702
703 RemoveFromSaveStateInputs(instance);
704
705 for (auto &user : instance->GetUsers()) {
706 auto userInst = user.GetInst();
707 if (userInst->IsCheck()) {
708 auto checkUserInst = SkipSingleUserCheckInstruction(userInst);
709 checkUserInst->GetBasicBlock()->RemoveInst(checkUserInst);
710 COMPILER_LOG(DEBUG, SIMPLIFY_SB)
711 << "Remove StringBuilder user instruction (id=" << checkUserInst->GetId() << ")";
712 }
713
714 userInst->GetBasicBlock()->RemoveInst(userInst);
715 COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Remove StringBuilder user instruction (id=" << userInst->GetId() << ")";
716 }
717
718 ASSERT(!instance->HasUsers());
719 instance->GetBasicBlock()->RemoveInst(instance);
720 COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Remove StringBuilder instance (id=" << instance->GetId() << ")";
721 }
722
ReconnectStringBuilderCascade(Inst * instance,Inst * inputInst,Inst * appendInstruction,SaveStateInst * saveState)723 void SimplifyStringBuilder::ReconnectStringBuilderCascade(Inst *instance, Inst *inputInst, Inst *appendInstruction,
724 SaveStateInst *saveState)
725 {
726 // Reconnect all append-calls of input_instance to instance instruction
727
728 // Check if input of append-call is toString-call
729 if (inputInst->GetBasicBlock() != appendInstruction->GetBasicBlock() || !IsStringBuilderToString(inputInst)) {
730 return;
731 }
732
733 // Get cascading instance of input toString-call
734 auto inputToStringCall = inputInst;
735 ASSERT(inputToStringCall->GetInputsCount() > 0);
736 auto inputInstance = inputToStringCall->GetDataFlowInput(0);
737 if (inputInstance == instance) {
738 return;
739 }
740
741 instructionsStack_.push(inputInstance);
742
743 NormalizeStringBuilderAppendInstructionUsers(inputInstance, saveState);
744 for (auto inputAppendInstruction : FindStringBuilderAppendInstructions(inputInstance)) {
745 COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Retarget append instrisic (id=" << inputAppendInstruction->GetId()
746 << ") to instance (id=" << instance->GetId() << ")";
747
748 inputAppendInstruction->SetInput(0, instance);
749 inputAppendInstruction->SetSaveState(saveState);
750
751 if (inputAppendInstruction->GetBasicBlock() != nullptr) {
752 inputAppendInstruction->GetBasicBlock()->EraseInst(inputAppendInstruction, true);
753 }
754 appendInstruction->InsertAfter(inputAppendInstruction);
755
756 for (auto &input : inputAppendInstruction->GetInputs()) {
757 if (input.GetInst()->IsSaveState()) {
758 continue;
759 }
760 FixBrokenSaveStates(input.GetInst(), inputAppendInstruction);
761 }
762 }
763
764 // Erase input_instance constructor from block
765 for (auto &user : inputInstance->GetUsers()) {
766 auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
767 if (IsMethodStringBuilderConstructorWithStringArg(userInst) ||
768 IsMethodStringBuilderDefaultConstructor(userInst)) {
769 userInst->ClearFlag(compiler::inst_flags::NO_DCE);
770 }
771 }
772
773 // Cleanup save states
774 RemoveFromSaveStateInputs(inputInstance);
775 // Erase input_instance itself
776 inputInstance->ClearFlag(compiler::inst_flags::NO_DCE);
777
778 // Cleanup instructions we don't need anymore
779 appendInstruction->GetBasicBlock()->RemoveInst(appendInstruction);
780 RemoveFromSaveStateInputs(inputToStringCall);
781 inputToStringCall->ClearFlag(compiler::inst_flags::NO_DCE);
782 }
783
ReconnectStringBuilderCascades(const ConcatenationLoopMatch & match)784 void SimplifyStringBuilder::ReconnectStringBuilderCascades(const ConcatenationLoopMatch &match)
785 {
786 // Consider the following code:
787 // str += a + b + c + ...
788 // StringBuilder equivalent view:
789 // sb_abc.append(a)
790 // sb_abc.append(b)
791 // sb_abc.append(c)
792 // sb_str.append(sb_abc.toString())
793 //
794 // We call StringBuilder cascading to be calls like sb_str.append(sb_abc.toString()), i.e output of one SB used as
795 // input of another SB
796 //
797 // The code below transforms this into:
798 // sb_str.append(a)
799 // sb_str.append(b)
800 // sb_str.append(c)
801 // i.e appending args directly to string 'str', w/o the use of intermediate SB
802
803 ASSERT(instructionsStack_.empty());
804 instructionsStack_.push(match.preheader.instance);
805
806 while (!instructionsStack_.empty()) {
807 auto instance = instructionsStack_.top();
808 instructionsStack_.pop();
809
810 // For each append-call of current StringBuilder instance
811 for (auto appendInstruction : FindStringBuilderAppendInstructions(instance)) {
812 for (auto &input : appendInstruction->GetInputs()) {
813 auto inputInst = input.GetInst();
814
815 // Reconnect append-calls of cascading instance
816 ReconnectStringBuilderCascade(instance, inputInst, appendInstruction,
817 appendInstruction->GetSaveState());
818 }
819 }
820 }
821 }
822
ReconnectInstructions(const ConcatenationLoopMatch & match)823 void SimplifyStringBuilder::ReconnectInstructions(const ConcatenationLoopMatch &match)
824 {
825 // Make StringBuilder append-call hoisted point to an instance hoisted and initialize it with string initial value
826 match.preheader.appendAccValue->SetInput(0, match.preheader.instance);
827 match.preheader.appendAccValue->SetInput(1, match.initialValue);
828
829 // Make temporary instance users point to an instance hoisted
830 for (auto &temp : match.temp) {
831 instructionsVector_.clear();
832 for (auto &user : temp.instance->GetUsers()) {
833 auto userInst = user.GetInst();
834 if (userInst->IsSaveState()) {
835 continue;
836 }
837 instructionsVector_.push_back(userInst);
838 }
839 for (auto userInst : instructionsVector_) {
840 userInst->ReplaceInput(temp.instance, match.preheader.instance);
841
842 COMPILER_LOG(DEBUG, SIMPLIFY_SB)
843 << "Replace input of instruction "
844 << "id=" << userInst->GetId() << " (" << GetOpcodeString(userInst->GetOpcode())
845 << ") old id=" << temp.instance->GetId() << " (" << GetOpcodeString(temp.instance->GetOpcode())
846 << ") new id=" << match.preheader.instance->GetId() << " ("
847 << GetOpcodeString(match.preheader.instance->GetOpcode()) << ")";
848 }
849 }
850
851 // Replace users of accumulated value outside the loop by toString-call hoisted to post exit block
852 instructionsVector_.clear();
853 for (auto &user : match.accValue->GetUsers()) {
854 auto userInst = user.GetInst();
855 if (userInst->IsSaveState()) {
856 continue;
857 }
858 if (userInst->GetBasicBlock() != match.block) {
859 instructionsVector_.push_back(userInst);
860 }
861 }
862
863 for (auto userInst : instructionsVector_) {
864 userInst->ReplaceInput(match.accValue, match.exit.toStringCall);
865
866 COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Replace input of instruction "
867 << "id=" << userInst->GetId() << " (" << GetOpcodeString(userInst->GetOpcode())
868 << ") old id=" << match.accValue->GetId() << " ("
869 << GetOpcodeString(match.accValue->GetOpcode())
870 << ") new id=" << match.exit.toStringCall->GetId() << " ("
871 << GetOpcodeString(match.exit.toStringCall->GetOpcode()) << ")";
872 }
873
874 ReconnectStringBuilderCascades(match);
875 }
876
AllInstructionInputsDominate(Inst * inst,Inst * other)877 bool AllInstructionInputsDominate(Inst *inst, Inst *other)
878 {
879 return !HasInput(inst, [other](auto &input) { return !input.GetInst()->IsDominate(other); });
880 }
881
HoistInstructionToPreHeader(BasicBlock * preHeader,Inst * lastInst,Inst * inst,SaveStateInst * saveState)882 Inst *SimplifyStringBuilder::HoistInstructionToPreHeader(BasicBlock *preHeader, Inst *lastInst, Inst *inst,
883 SaveStateInst *saveState)
884 {
885 // Based on similar code in LICM-pass
886
887 COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Hoist instruction id=" << inst->GetId() << " ("
888 << GetOpcodeString(inst->GetOpcode()) << ")"
889 << " from loop block id=" << inst->GetBasicBlock()->GetId()
890 << " to preheader block id=" << preHeader->GetId();
891
892 Inst *target = nullptr;
893 if (inst->IsMovableObject()) {
894 target = inst->GetPrev();
895 if (target == nullptr) {
896 target = inst->GetNext();
897 }
898 if (target == nullptr) {
899 target = GetGraph()->CreateInstNOP();
900 inst->InsertAfter(target);
901 }
902 }
903 inst->GetBasicBlock()->EraseInst(inst, true);
904 if (lastInst == nullptr || lastInst->IsPhi()) {
905 preHeader->AppendInst(inst);
906 lastInst = inst;
907 } else {
908 ASSERT(lastInst->GetBasicBlock() == preHeader);
909 Inst *saveStateDeoptimize = preHeader->FindSaveStateDeoptimize();
910 if (saveStateDeoptimize != nullptr && AllInstructionInputsDominate(inst, saveStateDeoptimize)) {
911 saveStateDeoptimize->InsertBefore(inst);
912 } else {
913 lastInst->InsertAfter(inst);
914 lastInst = inst;
915 }
916 }
917
918 if (inst->RequireState()) {
919 ASSERT(saveState != nullptr);
920 auto saveStateClone = CopySaveState(GetGraph(), saveState);
921 if (saveStateClone->GetBasicBlock() == nullptr) {
922 inst->InsertBefore(saveStateClone);
923 }
924 inst->SetSaveState(saveStateClone);
925 inst->SetPc(saveStateClone->GetPc());
926 }
927
928 FixBrokenSaveStates(inst, target);
929
930 return lastInst;
931 }
932
HoistInstructionToPreHeaderRecursively(BasicBlock * preHeader,Inst * lastInst,Inst * inst,SaveStateInst * saveState)933 Inst *SimplifyStringBuilder::HoistInstructionToPreHeaderRecursively(BasicBlock *preHeader, Inst *lastInst, Inst *inst,
934 SaveStateInst *saveState)
935 {
936 // Hoist all non-SaveState instruction inputs first
937 for (auto &input : inst->GetInputs()) {
938 auto inputInst = input.GetInst();
939 if (inputInst->IsSaveState()) {
940 continue;
941 }
942
943 if (inputInst->GetBasicBlock() == preHeader) {
944 continue;
945 }
946
947 lastInst = HoistInstructionToPreHeaderRecursively(preHeader, lastInst, inputInst, saveState);
948 if (lastInst->RequireState()) {
949 saveState = lastInst->GetSaveState();
950 }
951 }
952
953 // Hoist instruction itself
954 return HoistInstructionToPreHeader(preHeader, lastInst, inst, saveState);
955 }
956
HoistInstructionsToPreHeader(const ConcatenationLoopMatch & match,SaveStateInst * initSaveState)957 void SimplifyStringBuilder::HoistInstructionsToPreHeader(const ConcatenationLoopMatch &match,
958 SaveStateInst *initSaveState)
959 {
960 // Move StringBuilder construction and initialization from inside loop to preheader block
961
962 auto loop = match.block->GetLoop();
963 auto preHeader = loop->GetPreHeader();
964
965 HoistInstructionToPreHeaderRecursively(preHeader, preHeader->GetLastInst(), match.preheader.ctorCall,
966 initSaveState);
967
968 ASSERT(match.preheader.ctorCall->RequireState());
969 HoistInstructionToPreHeader(preHeader, match.preheader.ctorCall, match.preheader.appendAccValue,
970 match.preheader.ctorCall->GetSaveState());
971
972 for (auto &input : match.preheader.appendAccValue->GetInputs()) {
973 auto inputInst = input.GetInst();
974 if (inputInst->IsSaveState()) {
975 continue;
976 }
977
978 FixBrokenSaveStates(inputInst, match.preheader.appendAccValue);
979 }
980 }
981
HoistCheckInsturctionInputs(Inst * inst,BasicBlock * loopBlock,BasicBlock * postExit)982 void HoistCheckInsturctionInputs(Inst *inst, BasicBlock *loopBlock, BasicBlock *postExit)
983 {
984 for (auto &input : inst->GetInputs()) {
985 auto inputInst = input.GetInst();
986 if (inputInst->GetBasicBlock() == loopBlock && inputInst->IsCheck()) {
987 inputInst->GetBasicBlock()->EraseInst(inputInst, true);
988 if (inputInst->RequireState()) {
989 inputInst->SetSaveState(CopySaveState(loopBlock->GetGraph(), inst->GetSaveState()));
990 }
991 InsertBeforeWithSaveState(inputInst, inst);
992
993 COMPILER_LOG(DEBUG, SIMPLIFY_SB)
994 << "Hoist instruction id=" << inputInst->GetId() << " (" << GetOpcodeString(inputInst->GetOpcode())
995 << ") from loop block id=" << loopBlock->GetId() << " to post exit block id=" << postExit->GetId();
996 }
997 }
998 }
999
HoistCheckCastInstructionUsers(Inst * inst,BasicBlock * loopBlock,BasicBlock * postExit)1000 void SimplifyStringBuilder::HoistCheckCastInstructionUsers(Inst *inst, BasicBlock *loopBlock, BasicBlock *postExit)
1001 {
1002 // Hoist CheckCast instruction to post exit block
1003
1004 // Start with CheckCast instruction itself
1005 ASSERT(instructionsStack_.empty());
1006 for (auto &user : inst->GetUsers()) {
1007 auto userInst = user.GetInst();
1008 if (userInst->GetBasicBlock() == loopBlock && IsCheckCastWithoutUsers(userInst)) {
1009 instructionsStack_.push(userInst);
1010 }
1011 }
1012
1013 // Collect all the inputs of CheckCast instruction as well
1014 instructionsVector_.clear();
1015 while (!instructionsStack_.empty()) {
1016 auto userInst = instructionsStack_.top();
1017 instructionsStack_.pop();
1018 for (auto &input : userInst->GetInputs()) {
1019 auto inputInst = input.GetInst();
1020 if (inputInst->IsSaveState()) {
1021 continue;
1022 }
1023 if (inputInst->GetBasicBlock() != loopBlock) {
1024 continue;
1025 }
1026
1027 instructionsStack_.push(inputInst);
1028 }
1029 instructionsVector_.push_back(userInst);
1030 }
1031
1032 // Hoist collected instructions
1033 for (auto userInst : instructionsVector_) {
1034 userInst->GetBasicBlock()->EraseInst(userInst, true);
1035 if (userInst->RequireState()) {
1036 userInst->SetSaveState(CopySaveState(GetGraph(), inst->GetSaveState()));
1037 }
1038 InsertAfterWithSaveState(userInst, inst);
1039
1040 COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Hoist instruction id=" << userInst->GetId() << " ("
1041 << GetOpcodeString(userInst->GetOpcode())
1042 << ") from loop block id=" << loopBlock->GetId()
1043 << " to post exit block id=" << postExit->GetId();
1044 }
1045 }
1046
HoistInstructionsToPostExit(const ConcatenationLoopMatch & match,SaveStateInst * saveState)1047 void SimplifyStringBuilder::HoistInstructionsToPostExit(const ConcatenationLoopMatch &match, SaveStateInst *saveState)
1048 {
1049 // Move toString()-call and its inputs/users (null-checks, etc) out of the loop
1050 // and place them in the loop exit successor block
1051
1052 auto postExit = GetLoopPostExit(match.block->GetLoop());
1053 ASSERT(postExit != nullptr);
1054 ASSERT(!postExit->IsEmpty());
1055 ASSERT(saveState->GetBasicBlock() == postExit);
1056 ASSERT(match.exit.toStringCall->RequireState());
1057
1058 auto loopBlock = match.exit.toStringCall->GetBasicBlock();
1059 loopBlock->EraseInst(match.exit.toStringCall, true);
1060
1061 if (!saveState->HasUsers()) {
1062 // First use of save state, insert toString-call after it
1063 match.exit.toStringCall->SetSaveState(saveState);
1064 saveState->InsertAfter(match.exit.toStringCall);
1065 } else {
1066 // Duplicate save state and prepend both instructions
1067 match.exit.toStringCall->SetSaveState(CopySaveState(GetGraph(), saveState));
1068 InsertBeforeWithSaveState(match.exit.toStringCall, postExit->GetFirstInst());
1069 }
1070
1071 COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Hoist toString()-call instruction id=" << match.exit.toStringCall->GetId()
1072 << " from loop block id=" << loopBlock->GetId()
1073 << " to post exit block id=" << postExit->GetId();
1074
1075 // Hoist all the toString-call Check instructions inputs
1076 HoistCheckInsturctionInputs(match.exit.toStringCall, loopBlock, postExit);
1077
1078 // Hoist toString-call instructions users
1079 HoistCheckCastInstructionUsers(match.exit.toStringCall, loopBlock, postExit);
1080 }
1081
Cleanup(const ConcatenationLoopMatch & match)1082 void SimplifyStringBuilder::Cleanup(const ConcatenationLoopMatch &match)
1083 {
1084 // Remove temporary instructions
1085
1086 for (auto &temp : match.temp) {
1087 temp.toStringCall->ClearFlag(compiler::inst_flags::NO_DCE);
1088 for (auto &user : temp.toStringCall->GetUsers()) {
1089 auto userInst = user.GetInst();
1090 if (IsCheckCastWithoutUsers(userInst)) {
1091 userInst->ClearFlag(compiler::inst_flags::NO_DCE);
1092 }
1093 }
1094 temp.intermediateValue->ClearFlag(compiler::inst_flags::NO_DCE);
1095 temp.instance->ReplaceUsers(match.preheader.instance);
1096 temp.instance->ClearFlag(compiler::inst_flags::NO_DCE);
1097 temp.ctorCall->ClearFlag(compiler::inst_flags::NO_DCE);
1098 temp.appendAccValue->ClearFlag(compiler::inst_flags::NO_DCE);
1099 }
1100 }
1101
NeedRemoveInputFromSaveStateInstruction(Inst * inputInst)1102 bool SimplifyStringBuilder::NeedRemoveInputFromSaveStateInstruction(Inst *inputInst)
1103 {
1104 for (auto &match : matches_) {
1105 // If input is hoisted toString-call or accumulated phi instruction mark it for removal
1106 if (inputInst == match.exit.toStringCall || inputInst == match.accValue) {
1107 return true;
1108 }
1109
1110 // If input is removed toString-call (temporary instruction) mark it for removal
1111 bool isIntermediateValue = std::find_if(match.temp.begin(), match.temp.end(), [inputInst](auto &temp) {
1112 return inputInst == temp.toStringCall;
1113 }) != match.temp.end();
1114 if (isIntermediateValue) {
1115 return true;
1116 }
1117 }
1118 return false;
1119 }
1120
CollectSaveStateInputsForRemoval(Inst * inst)1121 void SimplifyStringBuilder::CollectSaveStateInputsForRemoval(Inst *inst)
1122 {
1123 ASSERT(inst->IsSaveState());
1124
1125 for (size_t i = 0; i < inst->GetInputsCount(); ++i) {
1126 auto inputInst = inst->GetInput(i).GetInst();
1127 if (NeedRemoveInputFromSaveStateInstruction(inputInst)) {
1128 inputDescriptors_.emplace_back(inst, i);
1129 }
1130 }
1131 }
1132
CleanupSaveStateInstructionInputs(Loop * loop)1133 void SimplifyStringBuilder::CleanupSaveStateInstructionInputs(Loop *loop)
1134 {
1135 // StringBuilder toString-call is either hoisted to post exit block or erased from loop, accumulated value is erased
1136 // from loop, so this instructions need to be removed from the inputs of save state instructions within current loop
1137
1138 inputDescriptors_.clear();
1139 for (auto block : loop->GetBlocks()) {
1140 for (auto inst : block->Insts()) {
1141 if (!inst->IsSaveState()) {
1142 continue;
1143 }
1144 CollectSaveStateInputsForRemoval(inst);
1145 }
1146 }
1147 RemoveFromInstructionInputs(inputDescriptors_);
1148 }
1149
NeedRemoveInputFromPhiInstruction(Inst * inputInst)1150 bool SimplifyStringBuilder::NeedRemoveInputFromPhiInstruction(Inst *inputInst)
1151 {
1152 for (auto &match : matches_) {
1153 // If input is hoisted toString-call mark it for removal
1154 if (inputInst == match.exit.toStringCall) {
1155 return true;
1156 }
1157 }
1158
1159 return false;
1160 }
1161
CleanupPhiInstructionInputs(Inst * phi)1162 void SimplifyStringBuilder::CleanupPhiInstructionInputs(Inst *phi)
1163 {
1164 ASSERT(phi->IsPhi());
1165
1166 for (size_t i = 0; i < phi->GetInputsCount(); ++i) {
1167 auto inputInst = phi->GetInput(i).GetInst();
1168 if (NeedRemoveInputFromPhiInstruction(inputInst)) {
1169 phi->SetInput(i, phi);
1170 }
1171 }
1172 }
1173
CleanupPhiInstructionInputs(Loop * loop)1174 void SimplifyStringBuilder::CleanupPhiInstructionInputs(Loop *loop)
1175 {
1176 // Remove toString()-call from accumulated value phi-instruction inputs
1177 for (auto block : loop->GetBlocks()) {
1178 for (auto phi : block->PhiInsts()) {
1179 CleanupPhiInstructionInputs(phi);
1180 }
1181 }
1182 }
1183
HasNotHoistedUser(PhiInst * phi)1184 bool SimplifyStringBuilder::HasNotHoistedUser(PhiInst *phi)
1185 {
1186 return HasUser(phi, [phi](auto &user) {
1187 auto userInst = user.GetInst();
1188 bool isSelf = userInst == phi;
1189 bool isSaveState = userInst->IsSaveState();
1190 bool isRemovedAppendInstruction =
1191 IsStringBuilderAppend(userInst) && !userInst->GetFlag(compiler::inst_flags::NO_DCE);
1192 return !isSelf && !isSaveState && !isRemovedAppendInstruction;
1193 });
1194 }
1195
RemoveUnusedPhiInstructions(Loop * loop)1196 void SimplifyStringBuilder::RemoveUnusedPhiInstructions(Loop *loop)
1197 {
1198 // Remove instructions having no users, or instruction with all users hoisted out of the loop
1199 for (auto block : loop->GetBlocks()) {
1200 for (auto phi : block->PhiInstsSafe()) {
1201 if (HasNotHoistedUser(phi->CastToPhi())) {
1202 continue;
1203 }
1204
1205 RemoveFromAllExceptPhiInputs(phi);
1206 block->RemoveInst(phi);
1207
1208 COMPILER_LOG(DEBUG, SIMPLIFY_SB)
1209 << "Remove unused instruction id=" << phi->GetId() << " (" << GetOpcodeString(phi->GetOpcode())
1210 << ") from header block id=" << block->GetId();
1211 }
1212 }
1213 }
1214
FixBrokenSaveStates(Loop * loop)1215 void SimplifyStringBuilder::FixBrokenSaveStates(Loop *loop)
1216 {
1217 ssb_.FixSaveStatesInBB(loop->GetPreHeader());
1218 ssb_.FixSaveStatesInBB(GetLoopPostExit(loop));
1219 }
1220
Cleanup(Loop * loop)1221 void SimplifyStringBuilder::Cleanup(Loop *loop)
1222 {
1223 if (!isApplied_) {
1224 return;
1225 }
1226
1227 FixBrokenSaveStates(loop);
1228 CleanupSaveStateInstructionInputs(loop);
1229 CleanupPhiInstructionInputs(loop);
1230 RemoveUnusedPhiInstructions(loop);
1231 }
1232
MatchStringBuilderUsage(Inst * instance,StringBuilderUsage & usage)1233 void SimplifyStringBuilder::MatchStringBuilderUsage(Inst *instance, StringBuilderUsage &usage)
1234 {
1235 // Find all the usages of a given StringBuilder instance, and pack them into StringBuilderUsage structure:
1236 // i.e instance itself, constructor-call, all append-calls, all toString-calls
1237
1238 usage.instance = instance;
1239
1240 for (auto &user : instance->GetUsers()) {
1241 auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
1242 if (IsMethodStringBuilderConstructorWithStringArg(userInst)) {
1243 usage.ctorCall = userInst;
1244 } else if (IsMethodStringBuilderDefaultConstructor(userInst)) {
1245 usage.ctorCall = userInst;
1246 } else if (IsStringBuilderAppend(userInst)) {
1247 // StringBuilder append-call returns 'this' (instance)
1248 // Replace all users of append-call by instance for simplicity
1249 userInst->ReplaceUsers(instance);
1250 }
1251 }
1252 ASSERT(usage.ctorCall != nullptr);
1253
1254 for (auto &user : instance->GetUsers()) {
1255 auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
1256 if (IsStringBuilderAppend(userInst)) {
1257 usage.appendInstructions.push_back(userInst);
1258 } else if (IsStringBuilderToString(userInst)) {
1259 usage.toStringCalls.push_back(userInst);
1260 }
1261 }
1262 }
1263
HasInputFromPreHeader(PhiInst * phi) const1264 bool SimplifyStringBuilder::HasInputFromPreHeader(PhiInst *phi) const
1265 {
1266 auto preHeader = phi->GetBasicBlock()->GetLoop()->GetPreHeader();
1267 for (size_t i = 0; i < phi->GetInputsCount(); ++i) {
1268 if (phi->GetPhiInputBb(i) == preHeader) {
1269 return true;
1270 }
1271 }
1272 return false;
1273 }
1274
HasToStringCallInput(PhiInst * phi) const1275 bool SimplifyStringBuilder::HasToStringCallInput(PhiInst *phi) const
1276 {
1277 // Returns true if
1278 // (1) 'phi' has StringBuilder.toString call as one of its inputs, and
1279 // (2) StringBuilder.toString call has no effective usages except this 'phi' itself.
1280 // Users being save states, check casts, and not used users are skipped.
1281
1282 MarkerHolder visited {GetGraph()};
1283
1284 // (1)
1285 bool hasToStringCallInput = HasInputPhiRecursively(
1286 phi, visited.GetMarker(), [](auto &input) { return IsStringBuilderToString(input.GetInst()); });
1287
1288 // (2)
1289 bool toStringCallInputUsedAnywhereExceptPhi = HasInput(phi, [phi](auto &input) {
1290 return IsStringBuilderToString(input.GetInst()) && HasUser(input.GetInst(), [phi](auto &user) {
1291 auto userInst = user.GetInst();
1292 bool isPhi = userInst == phi;
1293 bool isSaveState = userInst->IsSaveState();
1294 bool isCheckCast = IsCheckCastWithoutUsers(userInst);
1295 bool hasUsers = userInst->HasUsers();
1296 return !isPhi && !isSaveState && !isCheckCast && hasUsers;
1297 });
1298 });
1299 ResetInputMarkersRecursively(phi, visited.GetMarker());
1300 return hasToStringCallInput && !toStringCallInputUsedAnywhereExceptPhi;
1301 }
1302
HasInputInst(Inst * inputInst,Inst * inst) const1303 bool SimplifyStringBuilder::HasInputInst(Inst *inputInst, Inst *inst) const
1304 {
1305 MarkerHolder visited {GetGraph()};
1306 bool found = HasInputPhiRecursively(inst, visited.GetMarker(),
1307 [inputInst](auto &input) { return inputInst == input.GetInst(); });
1308 ResetInputMarkersRecursively(inst, visited.GetMarker());
1309 return found;
1310 }
1311
HasAppendInstructionUser(Inst * inst) const1312 bool SimplifyStringBuilder::HasAppendInstructionUser(Inst *inst) const
1313 {
1314 for (auto &user : inst->GetUsers()) {
1315 auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
1316 if (userInst->IsSaveState()) {
1317 continue;
1318 }
1319
1320 if (IsStringBuilderAppend(userInst)) {
1321 return true;
1322 }
1323 }
1324
1325 return false;
1326 }
1327
IsPhiAccumulatedValue(PhiInst * phi) const1328 bool SimplifyStringBuilder::IsPhiAccumulatedValue(PhiInst *phi) const
1329 {
1330 // Phi-instruction is accumulated value, if it is used in the following way:
1331 // bb_preheader:
1332 // 0 LoadString ...
1333 // ...
1334 // bb_header:
1335 // 10p Phi v0(bb_preheader), v20(bb_back_edge)
1336 // ...
1337 // bb_back_edge:
1338 // ...
1339 // 15 Intrinsic.StdCoreSbAppendString sb, v10p, ss
1340 // ...
1341 // 20 CallStatic std.core.StringBuilder::toString sb, ss
1342 // ...
1343
1344 return HasInputFromPreHeader(phi) && HasToStringCallInput(phi) && HasAppendInstructionUser(phi);
1345 }
1346
GetPhiAccumulatedValues(Loop * loop)1347 ArenaVector<Inst *> SimplifyStringBuilder::GetPhiAccumulatedValues(Loop *loop)
1348 {
1349 // Search loop for all accumulated values
1350 // Phi accumulated value is an instruction used to store string concatenation result between loop iterations
1351
1352 instructionsVector_.clear();
1353
1354 for (auto inst : loop->GetHeader()->PhiInsts()) {
1355 if (IsPhiAccumulatedValue(inst->CastToPhi())) {
1356 instructionsVector_.push_back(inst);
1357 }
1358 }
1359
1360 for (auto backEdge : loop->GetBackEdges()) {
1361 if (backEdge == loop->GetHeader()) {
1362 // Already processed above
1363 continue;
1364 }
1365
1366 for (auto inst : backEdge->PhiInsts()) {
1367 if (IsPhiAccumulatedValue(inst->CastToPhi())) {
1368 instructionsVector_.push_back(inst);
1369 }
1370 }
1371 }
1372
1373 return instructionsVector_;
1374 }
1375
StringBuilderUsagesDFS(Inst * inst,Loop * loop,Marker visited)1376 void SimplifyStringBuilder::StringBuilderUsagesDFS(Inst *inst, Loop *loop, Marker visited)
1377 {
1378 // Recursively traverse current instruction users, and collect all the StringBuilder usages met
1379
1380 ASSERT(inst != nullptr);
1381 if (inst->GetBasicBlock()->GetLoop() != loop || inst->IsMarked(visited)) {
1382 return;
1383 }
1384
1385 inst->SetMarker(visited);
1386
1387 for (auto &user : inst->GetUsers()) {
1388 auto userInst = user.GetInst();
1389 if (userInst->IsPhi() && !userInst->IsMarked(visited)) {
1390 StringBuilderUsagesDFS(userInst, loop, visited);
1391 }
1392
1393 if (!IsStringBuilderAppend(userInst)) {
1394 continue;
1395 }
1396
1397 auto appendInstruction = userInst;
1398 ASSERT(appendInstruction->GetInputsCount() > 1);
1399 auto instance = appendInstruction->GetDataFlowInput(0);
1400 if (instance->GetBasicBlock()->GetLoop() == loop) {
1401 StringBuilderUsage usage {nullptr, nullptr, GetGraph()->GetAllocator()};
1402 MatchStringBuilderUsage(instance, usage);
1403
1404 if (usage.toStringCalls.size() != 1) {
1405 continue;
1406 }
1407
1408 if (!usage.toStringCalls[0]->IsMarked(visited)) {
1409 StringBuilderUsagesDFS(usage.toStringCalls[0], loop, visited);
1410 usages_.push_back(usage);
1411 }
1412 }
1413 }
1414 }
1415
GetStringBuilderUsagesPO(Inst * accValue)1416 const ArenaVector<SimplifyStringBuilder::StringBuilderUsage> &SimplifyStringBuilder::GetStringBuilderUsagesPO(
1417 Inst *accValue)
1418 {
1419 // Get all the StringBuilder usages under the phi accumulated value instruction data flow graph in post order (PO)
1420
1421 usages_.clear();
1422
1423 MarkerHolder usageMarker {GetGraph()};
1424 Marker visited = usageMarker.GetMarker();
1425
1426 StringBuilderUsagesDFS(accValue, accValue->GetBasicBlock()->GetLoop(), visited);
1427
1428 return usages_;
1429 }
1430
AllUsersAreVisitedAppendInstructions(Inst * inst,Marker appendInstructionVisited)1431 bool SimplifyStringBuilder::AllUsersAreVisitedAppendInstructions(Inst *inst, Marker appendInstructionVisited)
1432 {
1433 bool allUsersVisited = true;
1434 for (auto &user : inst->GetUsers()) {
1435 auto userInst = user.GetInst();
1436 if (userInst->IsSaveState()) {
1437 continue;
1438 }
1439 if (IsCheckCastWithoutUsers(userInst)) {
1440 continue;
1441 }
1442 allUsersVisited &= IsStringBuilderAppend(userInst);
1443 allUsersVisited &= userInst->IsMarked(appendInstructionVisited);
1444 }
1445 return allUsersVisited;
1446 }
1447
UpdateIntermediateValue(const StringBuilderUsage & usage,Inst * intermediateValue,Marker appendInstructionVisited)1448 Inst *SimplifyStringBuilder::UpdateIntermediateValue(const StringBuilderUsage &usage, Inst *intermediateValue,
1449 Marker appendInstructionVisited)
1450 {
1451 // Update intermediate value with toString-call of the current StringBuilder usage, if all its append-call users
1452 // were already visited
1453
1454 size_t usersCount = 0;
1455 bool allUsersVisited = true;
1456
1457 for (auto &user : intermediateValue->GetUsers()) {
1458 auto userInst = user.GetInst();
1459 if (userInst->IsSaveState()) {
1460 continue;
1461 }
1462
1463 if (userInst->IsPhi() && !userInst->HasUsers()) {
1464 continue;
1465 }
1466
1467 if (userInst->IsPhi() && !userInst->HasUsers()) {
1468 continue;
1469 }
1470
1471 if (userInst->IsPhi()) {
1472 ++usersCount;
1473 allUsersVisited &= AllUsersAreVisitedAppendInstructions(userInst, appendInstructionVisited);
1474 } else if (IsStringBuilderAppend(userInst)) {
1475 ++usersCount;
1476 allUsersVisited &= userInst->IsMarked(appendInstructionVisited);
1477 } else {
1478 break;
1479 }
1480 }
1481
1482 ASSERT(usage.toStringCalls.size() == 1);
1483 if (usersCount == 1) {
1484 intermediateValue = usage.toStringCalls[0];
1485 } else if (usersCount > 1 && allUsersVisited) {
1486 intermediateValue = usage.toStringCalls[0];
1487 for (auto &user : usage.toStringCalls[0]->GetUsers()) {
1488 auto userInst = user.GetInst();
1489 if (userInst->IsPhi() && userInst->HasUsers()) {
1490 intermediateValue = userInst;
1491 break;
1492 }
1493 }
1494 }
1495
1496 return intermediateValue;
1497 }
1498
MatchTemporaryInstructions(const StringBuilderUsage & usage,ConcatenationLoopMatch & match,Inst * accValue,Inst * intermediateValue,Marker appendInstructionVisited)1499 void SimplifyStringBuilder::MatchTemporaryInstructions(const StringBuilderUsage &usage, ConcatenationLoopMatch &match,
1500 Inst *accValue, Inst *intermediateValue,
1501 Marker appendInstructionVisited)
1502 {
1503 // Split all the instructions of a given StringBuilder usage into groups (substructures of ConcatenationLoopMatch):
1504 // 'temp' group - temporary instructions to be erased from loop
1505 // 'loop' group - append-call instructions to be kept inside loop
1506
1507 ConcatenationLoopMatch::TemporaryInstructions temp {};
1508 temp.intermediateValue = intermediateValue;
1509 temp.toStringCall = usage.toStringCalls[0];
1510 temp.instance = usage.instance;
1511 temp.ctorCall = usage.ctorCall;
1512
1513 for (auto appendInstruction : usage.appendInstructions) {
1514 ASSERT(appendInstruction->GetInputsCount() > 1);
1515 auto appendArg = appendInstruction->GetDataFlowInput(1);
1516 if ((appendArg->IsPhi() && IsDataFlowInput(appendArg, intermediateValue)) || appendArg == intermediateValue ||
1517 appendArg == accValue) {
1518 // Append-call needs to be removed, if its argument is either accumulated value, or intermediate value;
1519 // or intermediate value is data flow input of argument
1520
1521 if (temp.appendAccValue == nullptr) {
1522 temp.appendAccValue = appendInstruction;
1523 } else {
1524 // Does not look like string concatenation pattern
1525 temp.Clear();
1526 break;
1527 }
1528 appendInstruction->SetMarker(appendInstructionVisited);
1529 } else {
1530 // Keep append-call inside loop otherwise
1531 match.loop.appendInstructions.push_back(appendInstruction);
1532 }
1533 }
1534
1535 if (!temp.IsEmpty()) {
1536 match.temp.push_back(temp);
1537 }
1538 }
1539
MatchHoistableInstructions(const StringBuilderUsage & usage,ConcatenationLoopMatch & match,Marker appendInstructionVisited)1540 Inst *SimplifyStringBuilder::MatchHoistableInstructions(const StringBuilderUsage &usage, ConcatenationLoopMatch &match,
1541 Marker appendInstructionVisited)
1542 {
1543 // Split all the instructions of a given StringBuilder usage into groups (substructures of ConcatenationLoopMatch):
1544 // 'preheader' group - instructions to be hoisted to preheader block
1545 // 'loop' group - append-call instructions to be kept inside loop
1546 // 'exit' group - instructions to be hoisted to post exit block
1547
1548 match.block = usage.instance->GetBasicBlock();
1549
1550 ASSERT(usage.instance->GetInputsCount() > 0);
1551 match.preheader.instance = usage.instance;
1552 match.preheader.ctorCall = usage.ctorCall;
1553 ASSERT(usage.toStringCalls.size() == 1);
1554 match.exit.toStringCall = usage.toStringCalls[0];
1555
1556 for (auto &user : usage.instance->GetUsers()) {
1557 auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
1558 if (userInst->IsSaveState()) {
1559 continue;
1560 }
1561
1562 if (!IsStringBuilderAppend(userInst)) {
1563 continue;
1564 }
1565
1566 // Check if append-call needs to be hoisted or kept inside loop
1567 auto appendInstruction = userInst;
1568 ASSERT(appendInstruction->GetInputsCount() > 1);
1569 auto appendArg = appendInstruction->GetDataFlowInput(1);
1570 if (appendArg->IsPhi() && IsPhiAccumulatedValue(appendArg->CastToPhi())) {
1571 // Append-call needs to be hoisted, if its argument is accumulated value
1572 auto phiAppendArg = appendArg->CastToPhi();
1573 auto initialValue =
1574 phiAppendArg->GetPhiDataflowInput(usage.instance->GetBasicBlock()->GetLoop()->GetPreHeader());
1575 if (match.initialValue != nullptr || match.accValue != nullptr ||
1576 match.preheader.appendAccValue != nullptr) {
1577 // Does not look like string concatenation pattern
1578 match.Clear();
1579 break;
1580 }
1581
1582 match.initialValue = initialValue;
1583 match.accValue = phiAppendArg;
1584 match.preheader.appendAccValue = appendInstruction;
1585
1586 appendInstruction->SetMarker(appendInstructionVisited);
1587 } else {
1588 // Keep append-call inside loop otherwise
1589 match.loop.appendInstructions.push_back(appendInstruction);
1590 }
1591 }
1592
1593 // Initialize intermediate value with toString-call to be hoisted to post exit block
1594 return match.exit.toStringCall;
1595 }
1596
MatchLoopConcatenation(Loop * loop)1597 const ArenaVector<SimplifyStringBuilder::ConcatenationLoopMatch> &SimplifyStringBuilder::MatchLoopConcatenation(
1598 Loop *loop)
1599 {
1600 // Search loop for string concatenation patterns like the following:
1601 // let str = initial_value: String
1602 // for (...) {
1603 // str += a0 + b0 + ...
1604 // str += a1 + b2 + ...
1605 // ...
1606 // }
1607 // And fill ConcatenationLoopMatch structure with instructions from the pattern found
1608
1609 matches_.clear();
1610
1611 MarkerHolder appendInstructionMarker {GetGraph()};
1612 Marker appendInstructionVisited = appendInstructionMarker.GetMarker();
1613
1614 // Accumulated value (acc_value) is a phi-instruction holding concatenation result between loop iterations, and
1615 // final result after loop completes
1616 for (auto accValue : GetPhiAccumulatedValues(loop)) {
1617 // Intermediate value is an instruction holding concatenation result during loop iteration execution.
1618 // It is initialized with acc_value, and updated with either toString-calls, or other phi-instructions
1619 // (depending on the loop control flow and data flow)
1620 Inst *intermediateValue = accValue;
1621 ConcatenationLoopMatch match {GetGraph()->GetAllocator()};
1622
1623 // Get all the StringBuilders used to calculate current accumulated value (in PO)
1624 auto &usages = GetStringBuilderUsagesPO(accValue);
1625 // RPO traversal: walk through PO usages backwards
1626 for (auto usage = usages.rbegin(); usage != usages.rend(); ++usage) {
1627 if (usage->toStringCalls.size() != 1) {
1628 continue; // Unsupported: doesn't look like string concatenation pattern
1629 }
1630
1631 if (match.preheader.instance == nullptr) {
1632 // First StringBuilder instance are set to be hoisted
1633 intermediateValue = MatchHoistableInstructions(*usage, match, appendInstructionVisited);
1634 } else {
1635 // All other StringBuilder instances are set to be removed as temporary
1636 MatchTemporaryInstructions(*usage, match, accValue, intermediateValue, appendInstructionVisited);
1637 intermediateValue = UpdateIntermediateValue(*usage, intermediateValue, appendInstructionVisited);
1638 }
1639 }
1640
1641 if (IsInstanceHoistable(match) && IsToStringHoistable(match, appendInstructionVisited)) {
1642 matches_.push_back(match);
1643 }
1644
1645 // Reset markers
1646 if (match.preheader.appendAccValue != nullptr) {
1647 match.preheader.appendAccValue->ResetMarker(appendInstructionVisited);
1648 }
1649 for (auto &temp : match.temp) {
1650 if (temp.appendAccValue != nullptr) {
1651 temp.appendAccValue->ResetMarker(appendInstructionVisited);
1652 }
1653 }
1654 }
1655
1656 return matches_;
1657 }
1658
OptimizeStringConcatenation(Loop * loop)1659 void SimplifyStringBuilder::OptimizeStringConcatenation(Loop *loop)
1660 {
1661 // Optimize String Builder concatenation loops
1662
1663 // Process inner loops first
1664 for (auto innerLoop : loop->GetInnerLoops()) {
1665 OptimizeStringConcatenation(innerLoop);
1666 }
1667
1668 // Check if basic block for instructions to hoist exist
1669 // Alternative way maybe to create one
1670 if (loop->GetPreHeader() == nullptr) {
1671 return;
1672 }
1673
1674 auto preHeaderSaveState = FindPreHeaderSaveState(loop);
1675 if (preHeaderSaveState == nullptr) {
1676 return;
1677 }
1678
1679 // Check if basic block for instructions to hoist exist
1680 // Alternative way maybe to create one
1681 auto postExit = GetLoopPostExit(loop);
1682 if (postExit == nullptr) {
1683 return;
1684 }
1685
1686 auto postExitSaveState = FindFirstSaveState(postExit);
1687 ASSERT(postExitSaveState); // IR Builder must create empty save states for loop exits
1688
1689 for (auto &match : MatchLoopConcatenation(loop)) {
1690 ReconnectInstructions(match);
1691 HoistInstructionsToPreHeader(match, preHeaderSaveState);
1692 HoistInstructionsToPostExit(match, postExitSaveState);
1693 Cleanup(match);
1694
1695 isApplied_ = true;
1696 }
1697 Cleanup(loop);
1698 }
1699
1700 } // namespace ark::compiler
1701