• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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