• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-2025 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "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/basicblock.h"
25 #include "optimizer/ir/inst.h"
26 
27 #include "optimizer/optimizations/cleanup.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       stringBuilderCalls_ {graph->GetLocalAllocator()->Adapter()},
39       stringBuilderFirstLastCalls_ {graph->GetLocalAllocator()->Adapter()}
40 {
41 }
42 
HasTryCatchBlocks(Graph * graph)43 bool HasTryCatchBlocks(Graph *graph)
44 {
45     for (auto block : graph->GetBlocksRPO()) {
46         if (block->IsTryCatch()) {
47             return true;
48         }
49     }
50     return false;
51 }
52 
53 // CC-OFFNXT(huge_method[C++]) solid logic
RunImpl()54 bool SimplifyStringBuilder::RunImpl()
55 {
56     isApplied_ = false;
57 
58     if (!GetGraph()->IsAnalysisValid<DominatorsTree>()) {
59         GetGraph()->RunPass<DominatorsTree>();
60     }
61 
62     ASSERT(GetGraph()->GetRootLoop() != nullptr);
63 
64     // NOTE: Disable this pass if there is a NativeApi call in the graph. That is because this pass can delete
65     // StringBuilder instances, which will lead to incorrect state in case Native call deoptimizes (i.e. unsupported API
66     // version). Remove this workaround after proper fix!
67     for (auto *bb : GetGraph()->GetBlocksRPO()) {
68         for (auto *inst : bb->Insts()) {
69             if (inst->GetOpcode() != Opcode::CallStatic) {
70                 continue;
71             }
72 
73             CallInst *callInst = inst->CastToCallStatic();
74             if (callInst->GetIsNative() && !GetGraph()->GetRuntime()->IsMethodIntrinsic(callInst->GetCallMethod())) {
75                 return false;
76             }
77         }
78     }
79 
80     // Loops with try-catch block and OSR mode are not supported in current implementation
81     if (!HasTryCatchBlocks(GetGraph()) && !GetGraph()->IsOsrMode()) {
82         for (auto loop : GetGraph()->GetRootLoop()->GetInnerLoops()) {
83             OptimizeStringConcatenation(loop);
84         }
85     }
86 
87     // Cleanup should be done before block optimizations, to erase instruction marked as dead
88     GetGraph()->RunPass<compiler::Cleanup>();
89 
90     OptimizeStringBuilderChain();
91 
92     // Cleanup should be done before block optimizations, to erase instruction marked as dead
93     GetGraph()->RunPass<compiler::Cleanup>();
94 
95     for (auto block : GetGraph()->GetBlocksRPO()) {
96         if (block->IsEmpty()) {
97             continue;
98         }
99         OptimizeStringBuilderToString(block);
100         OptimizeStringConcatenation(block);
101         OptimizeStringBuilderAppendChain(block);
102     }
103 
104     COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Simplify StringBuilder complete";
105 
106     // Remove save state inserted in loop post exit block at IR builder
107     for (auto block : GetGraph()->GetBlocksRPO()) {
108         for (auto inst : block->Insts()) {
109             if (inst->GetOpcode() != Opcode::SaveState) {
110                 continue;
111             }
112             inst->ClearFlag(inst_flags::NO_DCE);
113         }
114     }
115 
116     // Cleanup should be done inside pass, to satisfy GraphChecker
117     GetGraph()->RunPass<compiler::Cleanup>();
118 
119     return isApplied_;
120 }
121 
InvalidateAnalyses()122 void SimplifyStringBuilder::InvalidateAnalyses()
123 {
124     GetGraph()->InvalidateAnalysis<BoundsAnalysis>();
125     GetGraph()->InvalidateAnalysis<AliasAnalysis>();
126 }
127 
SkipToStringBuilderConstructorWithStringArg(InstIter begin,InstIter end)128 InstIter SimplifyStringBuilder::SkipToStringBuilderConstructorWithStringArg(InstIter begin, InstIter end)
129 {
130     return std::find_if(std::move(begin), std::move(end),
131                         [](auto inst) { return IsMethodStringBuilderConstructorWithStringArg(inst); });
132 }
133 
IsDataFlowInput(Inst * inst,Inst * input)134 bool IsDataFlowInput(Inst *inst, Inst *input)
135 {
136     for (size_t i = 0; i < inst->GetInputsCount(); ++i) {
137         if (inst->GetDataFlowInput(i) == input) {
138             return true;
139         }
140     }
141     return false;
142 }
143 
CanRemoveFromSaveStates(Inst * inst)144 static bool CanRemoveFromSaveStates(Inst *inst)
145 {
146     auto mayRequireRegMap = [instance = inst](const Inst *ssUser) {
147         // For now, assume no throws or deoptimizations occur in our methods
148         return SaveStateInst::InstMayRequireRegMap(ssUser) && !IsStringBuilderMethod(ssUser, instance) &&
149                // CC-OFFNXT(G.FMT.02-CPP) project code style
150                !IsIntrinsicStringConcat(ssUser) && !IsNullCheck(ssUser, instance) &&
151                // CC-OFFNXT(G.FMT.02-CPP) project code style
152                ssUser->GetOpcode() != Opcode::LoadString;
153     };
154     for (auto &user : inst->GetUsers()) {
155         auto userInst = user.GetInst();
156         if (userInst->IsSaveState() && !static_cast<SaveStateInst *>(userInst)->CanRemoveInputs(mayRequireRegMap)) {
157             return false;
158         }
159     }
160     return true;
161 }
162 
OptimizeStringBuilderToString(BasicBlock * block)163 void SimplifyStringBuilder::OptimizeStringBuilderToString(BasicBlock *block)
164 {
165     // Removes unnecessary String Builder instances
166 
167     ASSERT(block != nullptr);
168     ASSERT(block->GetGraph() == GetGraph());
169 
170     // Walk through a basic block, find every StringBuilder instance and constructor call,
171     // and check it we can remove/replace them
172     InstIter inst = block->Insts().begin();
173     while ((inst = SkipToStringBuilderConstructorWithStringArg(inst, block->Insts().end())) != block->Insts().end()) {
174         ASSERT((*inst)->IsStaticCall());
175         auto ctorCall = (*inst)->CastToCallStatic();
176 
177         // void StringBuilder::<ctor> instance, arg, save_state
178         ASSERT(ctorCall->GetInputsCount() == ARGS_NUM_3);
179         auto instance = ctorCall->GetInput(0).GetInst();
180         auto arg = ctorCall->GetInput(1).GetInst();
181 
182         // Look for StringBuilder usages within current basic block
183         auto nextInst = block->Insts().end();
184         bool removeInstance = true;
185         for (++inst; inst != block->Insts().end(); ++inst) {
186             // Skip SaveState instructions
187             if ((*inst)->IsSaveState()) {
188                 continue;
189             }
190 
191             // Skip check instructions, like NullCheck, RefTypeCheck, etc.
192             if ((*inst)->IsCheck()) {
193                 continue;
194             }
195 
196             // Continue (outer loop) with the next StringBuilder constructor,
197             // in case we met one in inner loop
198             if (IsMethodStringBuilderConstructorWithStringArg(*inst)) {
199                 nextInst = nextInst != block->Insts().end() ? nextInst : inst;
200             }
201 
202             if (!IsDataFlowInput(*inst, instance)) {
203                 continue;
204             }
205 
206             // Process usages of StringBuilder instance:
207             // replace toString()-calls until we met something else
208             if (IsStringBuilderToString(*inst)) {
209                 (*inst)->ReplaceUsers(arg);
210                 (*inst)->ClearFlag(compiler::inst_flags::NO_DCE);
211                 COMPILER_LOG(DEBUG, SIMPLIFY_SB)
212                     << "Remove StringBuilder toString()-call (id=" << (*inst)->GetId() << ")";
213                 isApplied_ = true;
214             } else {
215                 removeInstance = false;
216                 break;
217             }
218         }
219 
220         // Remove StringBuilder instance unless it has usages
221         if (removeInstance && CanRemoveFromSaveStates(instance) &&
222             !IsUsedOutsideBasicBlock(instance, instance->GetBasicBlock())) {
223             RemoveStringBuilderInstance(instance);
224             isApplied_ = true;
225         }
226 
227         // Proceed to the next StringBuilder constructor
228         inst = nextInst != block->Insts().end() ? nextInst : inst;
229     }
230 }
231 
SkipToStringBuilderDefaultConstructor(InstIter begin,InstIter end)232 InstIter SimplifyStringBuilder::SkipToStringBuilderDefaultConstructor(InstIter begin, InstIter end)
233 {
234     return std::find_if(std::move(begin), std::move(end),
235                         [](auto inst) { return IsMethodStringBuilderDefaultConstructor(inst); });
236 }
237 
CreateConcatIntrinsic(const std::array<IntrinsicInst *,ARGS_NUM_4> & appendIntrinsics,size_t appendCount,DataType::Type type,SaveStateInst * saveState)238 IntrinsicInst *SimplifyStringBuilder::CreateConcatIntrinsic(
239     const std::array<IntrinsicInst *, ARGS_NUM_4> &appendIntrinsics, size_t appendCount, DataType::Type type,
240     SaveStateInst *saveState)
241 {
242     auto concatIntrinsic =
243         GetGraph()->CreateInstIntrinsic(GetGraph()->GetRuntime()->GetStringConcatStringsIntrinsicId(appendCount));
244     ASSERT(concatIntrinsic->RequireState());
245 
246     concatIntrinsic->SetType(type);
247     // Allocate input types (+1 input for save state)
248     concatIntrinsic->AllocateInputTypes(GetGraph()->GetAllocator(), appendCount + 1);
249 
250     for (size_t index = 0; index < appendCount; ++index) {
251         auto arg = appendIntrinsics[index]->GetInput(1).GetInst();
252         concatIntrinsic->AppendInput(arg);
253         concatIntrinsic->AddInputType(arg->GetType());
254     }
255 
256     auto saveStateClone = CopySaveState(GetGraph(), saveState);
257     concatIntrinsic->AppendInput(saveStateClone);
258     concatIntrinsic->AddInputType(saveStateClone->GetType());
259 
260     return concatIntrinsic;
261 }
262 
CheckUnsupportedCases(Inst * instance)263 bool CheckUnsupportedCases(Inst *instance)
264 {
265     if (!CanRemoveFromSaveStates(instance)) {
266         return false;  // Unsupported case: this instance may be needed for deoptimization
267     }
268 
269     if (IsUsedOutsideBasicBlock(instance, instance->GetBasicBlock())) {
270         return false;  // Unsupported case: doesn't look like concatenation pattern
271     }
272 
273     auto toStringCount = CountUsers(instance, [](auto &user) { return IsStringBuilderToString(user.GetInst()); });
274     if (toStringCount != 1) {
275         return false;  // Unsupported case: doesn't look like concatenation pattern
276     }
277 
278     auto appendCount = CountUsers(instance, [](auto &user) { return IsStringBuilderAppend(user.GetInst()); });
279     auto appendStringCount =
280         CountUsers(instance, [](auto &user) { return IsIntrinsicStringBuilderAppendString(user.GetInst()); });
281     if (appendCount != appendStringCount) {
282         return false;  // Unsupported case: arguments must be strings
283     }
284     if (appendCount <= 1 || appendCount > ARGS_NUM_4) {
285         return false;  // Unsupported case: too many strings concatenated
286     }
287 
288     return true;
289 }
290 
MatchConcatenation(InstIter & begin,const InstIter & end,ConcatenationMatch & match)291 bool SimplifyStringBuilder::MatchConcatenation(InstIter &begin, const InstIter &end, ConcatenationMatch &match)
292 {
293     auto instance = match.instance;
294     if (!CheckUnsupportedCases(instance)) {
295         return false;
296     }
297 
298     // Walk instruction range [begin, end) and fill the match structure with StringBuilder usage instructions found
299     for (; begin != end; ++begin) {
300         if ((*begin)->IsSaveState()) {
301             continue;
302         }
303 
304         // Skip instruction having nothing to do with current instance
305         if (!IsDataFlowInput(*begin, instance)) {
306             continue;
307         }
308 
309         // Walk through NullChecks
310         auto inst = *begin;
311         if (inst->IsNullCheck()) {
312             if (!inst->HasSingleUser()) {
313                 return false;  // Unsupported case: doesn't look like concatenation pattern
314             }
315             inst = inst->GetUsers().Front().GetInst();
316             if (inst->GetBasicBlock() != instance->GetBasicBlock()) {
317                 return false;  // Unsupported case: doesn't look like concatenation pattern
318             }
319             continue;
320         }
321 
322         if (IsIntrinsicStringBuilderAppendString(inst)) {
323             auto intrinsic = inst->CastToIntrinsic();
324             match.appendIntrinsics[match.appendCount++] = intrinsic;
325         } else if (IsStringBuilderToString(inst)) {
326             match.toStringCall = *begin;
327         } else {
328             break;
329         }
330     }
331 
332     for (size_t index = 0; index < match.appendCount; ++index) {
333         if (!match.appendIntrinsics[index]->IsDominate(match.toStringCall)) {
334             return false;
335         }
336     }
337 
338     // Supported case: number of toString calls is one,
339     // number of append calls is between 2 and 4,
340     // toString call is dominated by all append calls.
341     return true;
342 }
343 
FixBrokenSaveStates(Inst * source,Inst * target)344 void SimplifyStringBuilder::FixBrokenSaveStates(Inst *source, Inst *target)
345 {
346     if (source->IsMovableObject()) {
347         ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), source, target);
348     }
349 }
350 
Check(const ConcatenationMatch & match)351 void SimplifyStringBuilder::Check(const ConcatenationMatch &match)
352 {
353     // NOLINTBEGIN(readability-magic-numbers)
354     [[maybe_unused]] auto &appendIntrinsics = match.appendIntrinsics;
355     ASSERT(match.appendCount > 1);
356     ASSERT(appendIntrinsics[0U] != nullptr);
357     ASSERT(appendIntrinsics[0U]->GetInputsCount() > 1);
358     ASSERT(appendIntrinsics[1U] != nullptr);
359     ASSERT(appendIntrinsics[1U]->GetInputsCount() > 1);
360 
361     switch (match.appendCount) {
362         case ARGS_NUM_2:
363             break;
364         case ARGS_NUM_3: {
365             ASSERT(appendIntrinsics[2U] != nullptr);
366             ASSERT(appendIntrinsics[2U]->GetInputsCount() > 1);
367             break;
368         }
369         case ARGS_NUM_4: {
370             ASSERT(appendIntrinsics[2U] != nullptr);
371             ASSERT(appendIntrinsics[2U]->GetInputsCount() > 1);
372             ASSERT(appendIntrinsics[3U] != nullptr);
373             ASSERT(appendIntrinsics[3U]->GetInputsCount() > 1);
374             break;
375         }
376         default:
377             UNREACHABLE();
378     }
379     // NOLINTEND(readability-magic-numbers)
380 }
381 
InsertIntrinsicAndFixSaveStates(IntrinsicInst * concatIntrinsic,const std::array<IntrinsicInst *,ARGS_NUM_4> & appendIntrinsics,size_t appendCount,Inst * before)382 void SimplifyStringBuilder::InsertIntrinsicAndFixSaveStates(
383     IntrinsicInst *concatIntrinsic, const std::array<IntrinsicInst *, ARGS_NUM_4> &appendIntrinsics, size_t appendCount,
384     Inst *before)
385 {
386     InsertBeforeWithSaveState(concatIntrinsic, before);
387     for (size_t index = 0; index < appendCount; ++index) {
388         auto arg = appendIntrinsics[index]->GetDataFlowInput(1);
389         FixBrokenSaveStates(arg, concatIntrinsic);
390     }
391 }
392 
ReplaceWithConcatIntrinsic(const ConcatenationMatch & match)393 void SimplifyStringBuilder::ReplaceWithConcatIntrinsic(const ConcatenationMatch &match)
394 {
395     auto &appendIntrinsics = match.appendIntrinsics;
396     auto toStringCall = match.toStringCall;
397 
398     auto concatIntrinsic = CreateConcatIntrinsic(appendIntrinsics, match.appendCount, toStringCall->GetType(),
399                                                  toStringCall->GetSaveState());
400     InsertIntrinsicAndFixSaveStates(concatIntrinsic, appendIntrinsics, match.appendCount, toStringCall);
401     toStringCall->ReplaceUsers(concatIntrinsic);
402 
403     COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Replace StringBuilder (id=" << match.ctorCall->GetId()
404                                      << ") append-intrinsics with concat intrinsic (id=" << concatIntrinsic->GetId()
405                                      << ")";
406 }
407 
Cleanup(const ConcatenationMatch & match)408 void SimplifyStringBuilder::Cleanup(const ConcatenationMatch &match)
409 {
410     RemoveStringBuilderInstance(match.instance);
411 }
412 
OptimizeStringConcatenation(BasicBlock * block)413 void SimplifyStringBuilder::OptimizeStringConcatenation(BasicBlock *block)
414 {
415     // Replace String Builder usage with string concatenation whenever optimal
416 
417     ASSERT(block != nullptr);
418     ASSERT(block->GetGraph() == GetGraph());
419 
420     MarkerHolder visitedMarker {GetGraph()};
421     Marker visited = visitedMarker.GetMarker();
422 
423     bool isAppliedLocal;
424     do {
425         isAppliedLocal = false;
426 
427         // Walk through a basic block, find every String concatenation pattern,
428         // and check if we can optimize them
429         InstIter inst = block->Insts().begin();
430         while ((inst = SkipToStringBuilderDefaultConstructor(inst, block->Insts().end())) != block->Insts().end()) {
431             ASSERT((*inst)->IsStaticCall());
432             auto ctorCall = (*inst)->CastToCallStatic();
433 
434             ASSERT(ctorCall->GetInputsCount() > 0);
435             auto instance = ctorCall->GetInput(0).GetInst();
436 
437             ++inst;
438             if (instance->IsMarked(visited)) {
439                 continue;
440             }
441 
442             ConcatenationMatch match {instance, ctorCall};
443             // Check if current StringBuilder instance can be optimized
444             if (MatchConcatenation(inst, block->Insts().end(), match)) {
445                 Check(match);
446                 ReplaceWithConcatIntrinsic(match);
447                 Cleanup(match);
448 
449                 instance->SetMarker(visited);
450 
451                 isAppliedLocal = true;
452                 isApplied_ = true;
453             }
454         }
455     } while (isAppliedLocal);
456 }
457 
Clear()458 void SimplifyStringBuilder::ConcatenationLoopMatch::TemporaryInstructions::Clear()
459 {
460     intermediateValue = nullptr;
461     toStringCall = nullptr;
462     instance = nullptr;
463     ctorCall = nullptr;
464     appendAccValue = nullptr;
465 }
466 
IsEmpty() const467 bool SimplifyStringBuilder::ConcatenationLoopMatch::TemporaryInstructions::IsEmpty() const
468 {
469     return toStringCall == nullptr || intermediateValue == nullptr || instance == nullptr || ctorCall == nullptr ||
470            appendAccValue == nullptr;
471 }
472 
Clear()473 void SimplifyStringBuilder::ConcatenationLoopMatch::Clear()
474 {
475     block = nullptr;
476     accValue = nullptr;
477     initialValue = nullptr;
478 
479     preheader.instance = nullptr;
480     preheader.ctorCall = nullptr;
481     preheader.appendAccValue = nullptr;
482 
483     loop.appendInstructions.clear();
484     temp.clear();
485     exit.toStringCall = nullptr;
486 }
487 
HasAppendUsersOnly(Inst * inst) const488 bool SimplifyStringBuilder::HasAppendUsersOnly(Inst *inst) const
489 {
490     MarkerHolder visited {inst->GetBasicBlock()->GetGraph()};
491     bool found = HasUserPhiRecursively(inst, visited.GetMarker(), [inst](auto &user) {
492         bool sameLoop = user.GetInst()->GetBasicBlock()->GetLoop() == inst->GetBasicBlock()->GetLoop();
493         bool isSaveState = user.GetInst()->IsSaveState();
494         bool isPhi = user.GetInst()->IsPhi();
495         bool isAppendInstruction = IsStringBuilderAppend(user.GetInst());
496         return sameLoop && !isSaveState && !isPhi && !isAppendInstruction;
497     });
498     ResetUserMarkersRecursively(inst, visited.GetMarker());
499     return !found;
500 }
501 
IsCheckCastWithoutUsers(Inst * inst)502 bool IsCheckCastWithoutUsers(Inst *inst)
503 {
504     return inst->GetOpcode() == Opcode::CheckCast && !inst->HasUsers();
505 }
506 
HasPhiOrAppendUsersOnly(Inst * inst,Marker appendInstructionVisited) const507 bool SimplifyStringBuilder::HasPhiOrAppendUsersOnly(Inst *inst, Marker appendInstructionVisited) const
508 {
509     MarkerHolder phiVisited {GetGraph()};
510     bool found = HasUserPhiRecursively(
511         inst, phiVisited.GetMarker(), [loop = inst->GetBasicBlock()->GetLoop(), appendInstructionVisited](auto &user) {
512             bool sameLoop = user.GetInst()->GetBasicBlock()->GetLoop() == loop;
513             bool isSaveState = user.GetInst()->IsSaveState();
514             bool isCheckCast = IsCheckCastWithoutUsers(user.GetInst());
515             bool isPhi = user.GetInst()->IsPhi();
516             bool isVisited = user.GetInst()->IsMarked(appendInstructionVisited);
517             bool isAppendInstruction = IsStringBuilderAppend(user.GetInst());
518             return sameLoop && !isSaveState && !isCheckCast && !isPhi && !(isAppendInstruction && isVisited);
519         });
520     return !found;
521 }
522 
IsInstanceHoistable() const523 bool SimplifyStringBuilder::ConcatenationLoopMatch::IsInstanceHoistable() const
524 {
525     if (block == nullptr || accValue == nullptr || initialValue == nullptr) {
526         return false;
527     }
528 
529     if (preheader.instance == nullptr || preheader.ctorCall == nullptr || preheader.appendAccValue == nullptr) {
530         return false;
531     }
532 
533     if (block != preheader.instance->GetBasicBlock() || block != preheader.ctorCall->GetBasicBlock() ||
534         block != preheader.appendAccValue->GetBasicBlock()) {
535         return false;
536     }
537 
538     if ((block->IsTry() || block->IsCatch()) && block->GetTryId() != block->GetLoop()->GetPreHeader()->GetTryId()) {
539         return false;
540     }
541 
542     if (loop.appendInstructions.empty()) {
543         return false;
544     }
545 
546     if (exit.toStringCall == nullptr) {
547         return false;
548     }
549 
550     return true;
551 }
552 
IsInstanceHoistable(const ConcatenationLoopMatch & match) const553 bool SimplifyStringBuilder::IsInstanceHoistable(const ConcatenationLoopMatch &match) const
554 {
555     return match.IsInstanceHoistable() && HasAppendUsersOnly(match.accValue);
556 }
557 
IsToStringHoistable(const ConcatenationLoopMatch & match,Marker appendInstructionVisited) const558 bool SimplifyStringBuilder::IsToStringHoistable(const ConcatenationLoopMatch &match,
559                                                 Marker appendInstructionVisited) const
560 {
561     return HasPhiOrAppendUsersOnly(match.exit.toStringCall, appendInstructionVisited);
562 }
563 
FindPreHeaderSaveState(Loop * loop)564 SaveStateInst *FindPreHeaderSaveState(Loop *loop)
565 {
566     for (const auto &inst : loop->GetPreHeader()->InstsReverse()) {
567         if (inst->GetOpcode() == Opcode::SaveState) {
568             return inst->CastToSaveState();
569         }
570     }
571     return nullptr;
572 }
573 
CountOuterLoopSuccs(BasicBlock * block)574 size_t CountOuterLoopSuccs(BasicBlock *block)
575 {
576     return std::count_if(block->GetSuccsBlocks().begin(), block->GetSuccsBlocks().end(),
577                          [block](auto succ) { return block->GetLoop()->IsInside(succ->GetLoop()); });
578 }
579 
GetOuterLoopSucc(BasicBlock * block)580 BasicBlock *GetOuterLoopSucc(BasicBlock *block)
581 {
582     auto found = std::find_if(block->GetSuccsBlocks().begin(), block->GetSuccsBlocks().end(),
583                               [block](auto succ) { return block->GetLoop()->IsInside(succ->GetLoop()); });
584     return found != block->GetSuccsBlocks().end() ? *found : nullptr;
585 }
586 
GetLoopPostExit(Loop * loop)587 BasicBlock *GetLoopPostExit(Loop *loop)
588 {
589     // Find a block immediately following after loop
590     // Supported case:
591     //  1. Preheader block exists
592     //  2. Loop has exactly one block with exactly one successor outside a loop (post exit block)
593     //  3. Preheader dominates post exit block found
594     // Loop structures different from the one described above are not supported in current implementation
595 
596     BasicBlock *postExit = nullptr;
597     for (auto block : loop->GetBlocks()) {
598         size_t count = CountOuterLoopSuccs(block);
599         if (count == 0) {
600             continue;
601         }
602         if (count == 1 && postExit == nullptr) {
603             postExit = GetOuterLoopSucc(block);
604             continue;
605         }
606         // Unsupported case
607         return nullptr;
608     }
609 
610     // Supported case
611     if (postExit != nullptr && postExit->GetPredsBlocks().size() == 1 && loop->GetPreHeader() != nullptr &&
612         loop->GetPreHeader()->IsDominate(postExit)) {
613         return postExit;
614     }
615 
616     // Unsupported case
617     return nullptr;
618 }
619 
CreateIntrinsic(Graph * graph,RuntimeInterface::IntrinsicId intrinsicId,DataType::Type type,const std::initializer_list<std::pair<Inst *,DataType::Type>> & args)620 IntrinsicInst *CreateIntrinsic(Graph *graph, RuntimeInterface::IntrinsicId intrinsicId, DataType::Type type,
621                                const std::initializer_list<std::pair<Inst *, DataType::Type>> &args)
622 {
623     auto intrinsic = graph->CreateInstIntrinsic(intrinsicId);
624 
625     intrinsic->SetType(type);
626     intrinsic->SetInputs(graph->GetAllocator(), args);
627 
628     return intrinsic;
629 }
630 
CreateIntrinsicStringBuilderAppendString(Inst * instance,Inst * arg,SaveStateInst * saveState)631 IntrinsicInst *SimplifyStringBuilder::CreateIntrinsicStringBuilderAppendString(Inst *instance, Inst *arg,
632                                                                                SaveStateInst *saveState)
633 {
634     return CreateIntrinsic(GetGraph(), GetGraph()->GetRuntime()->GetStringBuilderAppendStringsIntrinsicId(1U),
635                            DataType::REFERENCE,
636                            {{instance, instance->GetType()}, {arg, arg->GetType()}, {saveState, saveState->GetType()}});
637 }
638 
NormalizeStringBuilderAppendInstructionUsers(Inst * instance,SaveStateInst * saveState)639 void SimplifyStringBuilder::NormalizeStringBuilderAppendInstructionUsers(Inst *instance, SaveStateInst *saveState)
640 {
641     [[maybe_unused]] Inst *ctorCall = nullptr;
642 
643     for (auto &user : instance->GetUsers()) {
644         auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
645         // Make additional append-call out of constructor argument (if present)
646         if (IsMethodStringBuilderConstructorWithStringArg(userInst)) {
647             ASSERT(ctorCall == nullptr);
648             ctorCall = userInst;
649             auto ctorArg = ctorCall->GetInput(1).GetInst();
650             CreateIntrinsicStringBuilderAppendString(instance, ctorArg, CopySaveState(GetGraph(), saveState));
651         } else if (IsMethodStringBuilderDefaultConstructor(userInst)) {
652             ASSERT(ctorCall == nullptr);
653             ctorCall = userInst;
654         } else if (IsStringBuilderAppend(userInst)) {
655             // StringBuilder append-call returns 'this' (instance)
656             // Replace all users of append-call by instance for simplicity
657             userInst->ReplaceUsers(instance);
658         }
659     }
660     ASSERT(ctorCall != nullptr);
661 }
662 
FindStringBuilderAppendInstructions(Inst * instance)663 ArenaVector<Inst *> SimplifyStringBuilder::FindStringBuilderAppendInstructions(Inst *instance)
664 {
665     ArenaVector<Inst *> appendInstructions {GetGraph()->GetAllocator()->Adapter()};
666     for (auto &user : instance->GetUsers()) {
667         auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
668         if (IsStringBuilderAppend(userInst)) {
669             appendInstructions.push_back(userInst);
670         }
671     }
672 
673     return appendInstructions;
674 }
675 
RemoveFromSaveStateInputs(Inst * inst,bool doMark)676 void SimplifyStringBuilder::RemoveFromSaveStateInputs(Inst *inst, bool doMark)
677 {
678     inputDescriptors_.clear();
679 
680     for (auto &user : inst->GetUsers()) {
681         if (!user.GetInst()->IsSaveState()) {
682             continue;
683         }
684         inputDescriptors_.emplace_back(user.GetInst(), user.GetIndex());
685     }
686 
687     RemoveFromInstructionInputs(inputDescriptors_, doMark);
688 }
689 
RemoveFromAllExceptPhiInputs(Inst * inst)690 void SimplifyStringBuilder::RemoveFromAllExceptPhiInputs(Inst *inst)
691 {
692     inputDescriptors_.clear();
693 
694     for (auto &user : inst->GetUsers()) {
695         if (user.GetInst()->IsPhi()) {
696             continue;
697         }
698         inputDescriptors_.emplace_back(user.GetInst(), user.GetIndex());
699     }
700 
701     RemoveFromInstructionInputs(inputDescriptors_);
702 }
703 
RemoveStringBuilderInstance(Inst * instance)704 void SimplifyStringBuilder::RemoveStringBuilderInstance(Inst *instance)
705 {
706     ASSERT(!HasUser(instance, [](auto &user) {
707         auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
708         auto hasUsers = userInst->HasUsers();
709         auto isSaveState = userInst->IsSaveState();
710         auto isCtorCall = IsMethodStringBuilderDefaultConstructor(userInst) ||
711                           IsMethodStringBuilderConstructorWithStringArg(userInst) ||
712                           IsMethodStringBuilderConstructorWithCharArrayArg(userInst);
713         auto isAppendInstruction = IsStringBuilderAppend(userInst);
714         auto isToStringCall = IsStringBuilderToString(userInst);
715         return !(isSaveState || isCtorCall || ((isAppendInstruction || isToStringCall) && !hasUsers));
716     }));
717     ASSERT(CanRemoveFromSaveStates(instance));
718 
719     RemoveFromSaveStateInputs(instance, true);
720 
721     for (auto &user : instance->GetUsers()) {
722         auto userInst = user.GetInst();
723         if (userInst->IsCheck()) {
724             auto checkUserInst = SkipSingleUserCheckInstruction(userInst);
725             checkUserInst->GetBasicBlock()->RemoveInst(checkUserInst);
726             COMPILER_LOG(DEBUG, SIMPLIFY_SB)
727                 << "Remove StringBuilder user instruction (id=" << checkUserInst->GetId() << ")";
728         }
729 
730         userInst->GetBasicBlock()->RemoveInst(userInst);
731         COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Remove StringBuilder user instruction (id=" << userInst->GetId() << ")";
732     }
733 
734     ASSERT(!instance->HasUsers());
735     instance->GetBasicBlock()->RemoveInst(instance);
736     COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Remove StringBuilder instance (id=" << instance->GetId() << ")";
737 }
738 
ReconnectStringBuilderCascade(Inst * instance,Inst * inputInst,Inst * appendInstruction,SaveStateInst * saveState)739 void SimplifyStringBuilder::ReconnectStringBuilderCascade(Inst *instance, Inst *inputInst, Inst *appendInstruction,
740                                                           SaveStateInst *saveState)
741 {
742     // Reconnect all append-calls of input_instance to instance instruction
743 
744     // Check if input of append-call is toString-call
745     if (inputInst->GetBasicBlock() != appendInstruction->GetBasicBlock() || !IsStringBuilderToString(inputInst)) {
746         return;
747     }
748 
749     // Get cascading instance of input toString-call
750     auto inputToStringCall = inputInst;
751     ASSERT(inputToStringCall->GetInputsCount() > 0);
752     auto inputInstance = inputToStringCall->GetDataFlowInput(0);
753     if (inputInstance == instance) {
754         return;
755     }
756 
757     instructionsStack_.push(inputInstance);
758 
759     NormalizeStringBuilderAppendInstructionUsers(inputInstance, saveState);
760     for (auto inputAppendInstruction : FindStringBuilderAppendInstructions(inputInstance)) {
761         COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Retarget append instrisic (id=" << inputAppendInstruction->GetId()
762                                          << ") to instance (id=" << instance->GetId() << ")";
763 
764         inputAppendInstruction->SetInput(0, instance);
765         inputAppendInstruction->SetSaveState(saveState);
766 
767         if (inputAppendInstruction->GetBasicBlock() != nullptr) {
768             inputAppendInstruction->GetBasicBlock()->EraseInst(inputAppendInstruction, true);
769         }
770         appendInstruction->InsertAfter(inputAppendInstruction);
771 
772         for (auto &input : inputAppendInstruction->GetInputs()) {
773             if (input.GetInst()->IsSaveState()) {
774                 continue;
775             }
776             FixBrokenSaveStates(input.GetInst(), inputAppendInstruction);
777         }
778     }
779 
780     // Erase input_instance constructor from block
781     for (auto &user : inputInstance->GetUsers()) {
782         auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
783         if (IsMethodStringBuilderConstructorWithStringArg(userInst) ||
784             IsMethodStringBuilderDefaultConstructor(userInst)) {
785             userInst->ClearFlag(compiler::inst_flags::NO_DCE);
786         }
787     }
788 
789     // Cleanup save states
790     RemoveFromSaveStateInputs(inputInstance);
791     // Erase input_instance itself
792     inputInstance->ClearFlag(compiler::inst_flags::NO_DCE);
793 
794     // Cleanup instructions we don't need anymore
795     appendInstruction->GetBasicBlock()->RemoveInst(appendInstruction);
796     RemoveFromSaveStateInputs(inputToStringCall);
797     inputToStringCall->ClearFlag(compiler::inst_flags::NO_DCE);
798 }
799 
ReconnectStringBuilderCascades(const ConcatenationLoopMatch & match)800 void SimplifyStringBuilder::ReconnectStringBuilderCascades(const ConcatenationLoopMatch &match)
801 {
802     // Consider the following code:
803     //      str += a + b + c + ...
804     // StringBuilder equivalent view:
805     //      sb_abc.append(a)
806     //      sb_abc.append(b)
807     //      sb_abc.append(c)
808     //      sb_str.append(sb_abc.toString())
809     //
810     // We call StringBuilder cascading to be calls like sb_str.append(sb_abc.toString()), i.e output of one SB used as
811     // input of another SB
812     //
813     // The code below transforms this into:
814     //      sb_str.append(a)
815     //      sb_str.append(b)
816     //      sb_str.append(c)
817     // i.e appending args directly to string 'str', w/o the use of intermediate SB
818 
819     ASSERT(instructionsStack_.empty());
820     instructionsStack_.push(match.preheader.instance);
821 
822     while (!instructionsStack_.empty()) {
823         auto instance = instructionsStack_.top();
824         instructionsStack_.pop();
825 
826         // For each append-call of current StringBuilder instance
827         for (auto appendInstruction : FindStringBuilderAppendInstructions(instance)) {
828             for (auto &input : appendInstruction->GetInputs()) {
829                 auto inputInst = input.GetInst();
830 
831                 // Reconnect append-calls of cascading instance
832                 ReconnectStringBuilderCascade(instance, inputInst, appendInstruction,
833                                               appendInstruction->GetSaveState());
834             }
835         }
836     }
837 }
838 
ReconnectInstructions(const ConcatenationLoopMatch & match)839 void SimplifyStringBuilder::ReconnectInstructions(const ConcatenationLoopMatch &match)
840 {
841     // Make StringBuilder append-call hoisted point to an instance hoisted and initialize it with string initial value
842     match.preheader.appendAccValue->SetInput(0, match.preheader.instance);
843     match.preheader.appendAccValue->SetInput(1, match.initialValue);
844 
845     // Make temporary instance users point to an instance hoisted
846     for (auto &temp : match.temp) {
847         instructionsVector_.clear();
848         for (auto &user : temp.instance->GetUsers()) {
849             auto userInst = user.GetInst();
850             if (userInst->IsSaveState()) {
851                 continue;
852             }
853             instructionsVector_.push_back(userInst);
854         }
855         for (auto userInst : instructionsVector_) {
856             userInst->ReplaceInput(temp.instance, match.preheader.instance);
857 
858             COMPILER_LOG(DEBUG, SIMPLIFY_SB)
859                 << "Replace input of instruction "
860                 << "id=" << userInst->GetId() << " (" << GetOpcodeString(userInst->GetOpcode())
861                 << ") old id=" << temp.instance->GetId() << " (" << GetOpcodeString(temp.instance->GetOpcode())
862                 << ") new id=" << match.preheader.instance->GetId() << " ("
863                 << GetOpcodeString(match.preheader.instance->GetOpcode()) << ")";
864         }
865     }
866 
867     // Replace users of accumulated value outside the loop by toString-call hoisted to post exit block
868     instructionsVector_.clear();
869     for (auto &user : match.accValue->GetUsers()) {
870         auto userInst = user.GetInst();
871         if (userInst->IsSaveState()) {
872             continue;
873         }
874         if (userInst->GetBasicBlock() != match.block) {
875             instructionsVector_.push_back(userInst);
876         }
877     }
878 
879     for (auto userInst : instructionsVector_) {
880         userInst->ReplaceInput(match.accValue, match.exit.toStringCall);
881 
882         COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Replace input of instruction "
883                                          << "id=" << userInst->GetId() << " (" << GetOpcodeString(userInst->GetOpcode())
884                                          << ") old id=" << match.accValue->GetId() << " ("
885                                          << GetOpcodeString(match.accValue->GetOpcode())
886                                          << ") new id=" << match.exit.toStringCall->GetId() << " ("
887                                          << GetOpcodeString(match.exit.toStringCall->GetOpcode()) << ")";
888     }
889 
890     ReconnectStringBuilderCascades(match);
891 }
892 
AllInstructionInputsDominate(Inst * inst,Inst * other)893 bool AllInstructionInputsDominate(Inst *inst, Inst *other)
894 {
895     return !HasInput(inst, [other](auto &input) { return !input.GetInst()->IsDominate(other); });
896 }
897 
HoistInstructionToPreHeader(BasicBlock * preHeader,Inst * lastInst,Inst * inst,SaveStateInst * saveState)898 Inst *SimplifyStringBuilder::HoistInstructionToPreHeader(BasicBlock *preHeader, Inst *lastInst, Inst *inst,
899                                                          SaveStateInst *saveState)
900 {
901     // Based on similar code in LICM-pass
902 
903     COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Hoist instruction id=" << inst->GetId() << " ("
904                                      << GetOpcodeString(inst->GetOpcode()) << ")"
905                                      << " from loop block id=" << inst->GetBasicBlock()->GetId()
906                                      << " to preheader block id=" << preHeader->GetId();
907 
908     Inst *target = nullptr;
909     if (inst->IsMovableObject()) {
910         target = inst->GetPrev();
911         if (target == nullptr) {
912             target = inst->GetNext();
913         }
914         if (target == nullptr) {
915             target = GetGraph()->CreateInstNOP();
916             inst->InsertAfter(target);
917         }
918     }
919     inst->GetBasicBlock()->EraseInst(inst, true);
920     if (lastInst == nullptr || lastInst->IsPhi()) {
921         preHeader->AppendInst(inst);
922         lastInst = inst;
923     } else {
924         ASSERT(lastInst->GetBasicBlock() == preHeader);
925         Inst *saveStateDeoptimize = preHeader->FindSaveStateDeoptimize();
926         if (saveStateDeoptimize != nullptr && AllInstructionInputsDominate(inst, saveStateDeoptimize)) {
927             saveStateDeoptimize->InsertBefore(inst);
928         } else {
929             lastInst->InsertAfter(inst);
930             lastInst = inst;
931         }
932     }
933 
934     if (inst->RequireState()) {
935         ASSERT(saveState != nullptr);
936         auto saveStateClone = CopySaveState(GetGraph(), saveState);
937         if (saveStateClone->GetBasicBlock() == nullptr) {
938             inst->InsertBefore(saveStateClone);
939         }
940         inst->SetSaveState(saveStateClone);
941         inst->SetPc(saveStateClone->GetPc());
942     }
943 
944     FixBrokenSaveStates(inst, target);
945 
946     return lastInst;
947 }
948 
HoistInstructionToPreHeaderRecursively(BasicBlock * preHeader,Inst * lastInst,Inst * inst,SaveStateInst * saveState)949 Inst *SimplifyStringBuilder::HoistInstructionToPreHeaderRecursively(BasicBlock *preHeader, Inst *lastInst, Inst *inst,
950                                                                     SaveStateInst *saveState)
951 {
952     // Hoist all non-SaveState instruction inputs first
953     for (auto &input : inst->GetInputs()) {
954         auto inputInst = input.GetInst();
955         if (inputInst->IsSaveState()) {
956             continue;
957         }
958 
959         if (inputInst->GetBasicBlock() == preHeader) {
960             continue;
961         }
962 
963         lastInst = HoistInstructionToPreHeaderRecursively(preHeader, lastInst, inputInst, saveState);
964         if (lastInst->RequireState()) {
965             saveState = lastInst->GetSaveState();
966         }
967     }
968 
969     // Hoist instruction itself
970     return HoistInstructionToPreHeader(preHeader, lastInst, inst, saveState);
971 }
972 
HoistInstructionsToPreHeader(const ConcatenationLoopMatch & match,SaveStateInst * initSaveState)973 void SimplifyStringBuilder::HoistInstructionsToPreHeader(const ConcatenationLoopMatch &match,
974                                                          SaveStateInst *initSaveState)
975 {
976     // Move StringBuilder construction and initialization from inside loop to preheader block
977 
978     auto loop = match.block->GetLoop();
979     auto preHeader = loop->GetPreHeader();
980 
981     HoistInstructionToPreHeaderRecursively(preHeader, preHeader->GetLastInst(), match.preheader.ctorCall,
982                                            initSaveState);
983 
984     ASSERT(match.preheader.ctorCall->RequireState());
985     HoistInstructionToPreHeader(preHeader, match.preheader.ctorCall, match.preheader.appendAccValue,
986                                 match.preheader.ctorCall->GetSaveState());
987 
988     for (auto &input : match.preheader.appendAccValue->GetInputs()) {
989         auto inputInst = input.GetInst();
990         if (inputInst->IsSaveState()) {
991             continue;
992         }
993 
994         FixBrokenSaveStates(inputInst, match.preheader.appendAccValue);
995     }
996 }
997 
HoistCheckInsturctionInputs(Inst * inst,BasicBlock * loopBlock,BasicBlock * postExit)998 void HoistCheckInsturctionInputs(Inst *inst, BasicBlock *loopBlock, BasicBlock *postExit)
999 {
1000     for (auto &input : inst->GetInputs()) {
1001         auto inputInst = input.GetInst();
1002         if (inputInst->GetBasicBlock() == loopBlock && inputInst->IsCheck()) {
1003             inputInst->GetBasicBlock()->EraseInst(inputInst, true);
1004             if (inputInst->RequireState()) {
1005                 inputInst->SetSaveState(CopySaveState(loopBlock->GetGraph(), inst->GetSaveState()));
1006             }
1007             InsertBeforeWithSaveState(inputInst, inst);
1008 
1009             COMPILER_LOG(DEBUG, SIMPLIFY_SB)
1010                 << "Hoist instruction id=" << inputInst->GetId() << " (" << GetOpcodeString(inputInst->GetOpcode())
1011                 << ") from loop block id=" << loopBlock->GetId() << " to post exit block id=" << postExit->GetId();
1012         }
1013     }
1014 }
1015 
HoistCheckCastInstructionUsers(Inst * inst,BasicBlock * loopBlock,BasicBlock * postExit)1016 void SimplifyStringBuilder::HoistCheckCastInstructionUsers(Inst *inst, BasicBlock *loopBlock, BasicBlock *postExit)
1017 {
1018     // Hoist CheckCast instruction to post exit block
1019 
1020     // Start with CheckCast instruction itself
1021     ASSERT(instructionsStack_.empty());
1022     for (auto &user : inst->GetUsers()) {
1023         auto userInst = user.GetInst();
1024         if (userInst->GetBasicBlock() == loopBlock && IsCheckCastWithoutUsers(userInst)) {
1025             instructionsStack_.push(userInst);
1026         }
1027     }
1028 
1029     // Collect all the inputs of CheckCast instruction as well
1030     instructionsVector_.clear();
1031     while (!instructionsStack_.empty()) {
1032         auto userInst = instructionsStack_.top();
1033         instructionsStack_.pop();
1034         for (auto &input : userInst->GetInputs()) {
1035             auto inputInst = input.GetInst();
1036             if (inputInst->IsSaveState()) {
1037                 continue;
1038             }
1039             if (inputInst->GetBasicBlock() != loopBlock) {
1040                 continue;
1041             }
1042 
1043             instructionsStack_.push(inputInst);
1044         }
1045         instructionsVector_.push_back(userInst);
1046     }
1047 
1048     // Hoist collected instructions
1049     for (auto userInst : instructionsVector_) {
1050         userInst->GetBasicBlock()->EraseInst(userInst, true);
1051         if (userInst->RequireState()) {
1052             userInst->SetSaveState(CopySaveState(GetGraph(), inst->GetSaveState()));
1053         }
1054         InsertAfterWithSaveState(userInst, inst);
1055 
1056         COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Hoist instruction id=" << userInst->GetId() << " ("
1057                                          << GetOpcodeString(userInst->GetOpcode())
1058                                          << ") from loop block id=" << loopBlock->GetId()
1059                                          << " to post exit block id=" << postExit->GetId();
1060     }
1061 }
1062 
HoistInstructionsToPostExit(const ConcatenationLoopMatch & match,SaveStateInst * saveState)1063 void SimplifyStringBuilder::HoistInstructionsToPostExit(const ConcatenationLoopMatch &match, SaveStateInst *saveState)
1064 {
1065     // Move toString()-call and its inputs/users (null-checks, etc) out of the loop
1066     // and place them in the loop exit successor block
1067 
1068     auto postExit = GetLoopPostExit(match.block->GetLoop());
1069     ASSERT(postExit != nullptr);
1070     ASSERT(!postExit->IsEmpty());
1071     ASSERT(saveState->GetBasicBlock() == postExit);
1072     ASSERT(match.exit.toStringCall->RequireState());
1073 
1074     auto loopBlock = match.exit.toStringCall->GetBasicBlock();
1075     loopBlock->EraseInst(match.exit.toStringCall, true);
1076 
1077     ASSERT(saveState != nullptr);
1078     if (!saveState->HasUsers()) {
1079         // First use of save state, insert toString-call after it
1080         match.exit.toStringCall->SetSaveState(saveState);
1081         saveState->InsertAfter(match.exit.toStringCall);
1082     } else {
1083         // Duplicate save state and prepend both instructions
1084         match.exit.toStringCall->SetSaveState(CopySaveState(GetGraph(), saveState));
1085         InsertBeforeWithSaveState(match.exit.toStringCall, postExit->GetFirstInst());
1086     }
1087 
1088     COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Hoist toString()-call instruction id=" << match.exit.toStringCall->GetId()
1089                                      << " from loop block id=" << loopBlock->GetId()
1090                                      << " to post exit block id=" << postExit->GetId();
1091 
1092     // Hoist all the toString-call Check instructions inputs
1093     HoistCheckInsturctionInputs(match.exit.toStringCall, loopBlock, postExit);
1094 
1095     // Hoist toString-call instructions users
1096     HoistCheckCastInstructionUsers(match.exit.toStringCall, loopBlock, postExit);
1097 }
1098 
Cleanup(const ConcatenationLoopMatch & match)1099 void SimplifyStringBuilder::Cleanup(const ConcatenationLoopMatch &match)
1100 {
1101     // Remove temporary instructions
1102 
1103     for (auto &temp : match.temp) {
1104         temp.toStringCall->ClearFlag(compiler::inst_flags::NO_DCE);
1105         for (auto &user : temp.toStringCall->GetUsers()) {
1106             auto userInst = user.GetInst();
1107             if (IsCheckCastWithoutUsers(userInst)) {
1108                 userInst->ClearFlag(compiler::inst_flags::NO_DCE);
1109             }
1110         }
1111         temp.intermediateValue->ClearFlag(compiler::inst_flags::NO_DCE);
1112         temp.instance->ReplaceUsers(match.preheader.instance);
1113         temp.instance->ClearFlag(compiler::inst_flags::NO_DCE);
1114         temp.ctorCall->ClearFlag(compiler::inst_flags::NO_DCE);
1115         temp.appendAccValue->ClearFlag(compiler::inst_flags::NO_DCE);
1116     }
1117 }
1118 
NeedRemoveInputFromSaveStateInstruction(Inst * inputInst)1119 bool SimplifyStringBuilder::NeedRemoveInputFromSaveStateInstruction(Inst *inputInst)
1120 {
1121     for (auto &match : matches_) {
1122         // If input is hoisted toString-call or accumulated phi instruction mark it for removal
1123         if (inputInst == match.exit.toStringCall || inputInst == match.accValue) {
1124             return true;
1125         }
1126 
1127         // If input is removed toString-call (temporary instruction) mark it for removal
1128         bool isIntermediateValue = std::find_if(match.temp.begin(), match.temp.end(), [inputInst](auto &temp) {
1129                                        return inputInst == temp.toStringCall;
1130                                    }) != match.temp.end();
1131         if (isIntermediateValue) {
1132             return true;
1133         }
1134     }
1135     return false;
1136 }
1137 
CollectSaveStateInputsForRemoval(Inst * inst)1138 void SimplifyStringBuilder::CollectSaveStateInputsForRemoval(Inst *inst)
1139 {
1140     ASSERT(inst->IsSaveState());
1141 
1142     for (size_t i = 0; i < inst->GetInputsCount(); ++i) {
1143         auto inputInst = inst->GetInput(i).GetInst();
1144         if (NeedRemoveInputFromSaveStateInstruction(inputInst)) {
1145             inputDescriptors_.emplace_back(inst, i);
1146         }
1147     }
1148 }
1149 
CleanupSaveStateInstructionInputs(Loop * loop)1150 void SimplifyStringBuilder::CleanupSaveStateInstructionInputs(Loop *loop)
1151 {
1152     // StringBuilder toString-call is either hoisted to post exit block or erased from loop, accumulated value is erased
1153     // from loop, so this instructions need to be removed from the inputs of save state instructions within current loop
1154 
1155     inputDescriptors_.clear();
1156     for (auto block : loop->GetBlocks()) {
1157         for (auto inst : block->Insts()) {
1158             if (!inst->IsSaveState()) {
1159                 continue;
1160             }
1161             CollectSaveStateInputsForRemoval(inst);
1162         }
1163     }
1164     RemoveFromInstructionInputs(inputDescriptors_);
1165 }
1166 
FindHoistedToStringCallInput(Inst * phi)1167 Inst *SimplifyStringBuilder::FindHoistedToStringCallInput(Inst *phi)
1168 {
1169     for (auto &match : matches_) {
1170         auto toStringCall = match.exit.toStringCall;
1171         if (HasInput(phi, [toStringCall](auto &input) { return input.GetInst() == toStringCall; })) {
1172             return toStringCall;
1173         }
1174     }
1175 
1176     return nullptr;
1177 }
1178 
CleanupPhiInstructionInputs(Inst * phi)1179 void SimplifyStringBuilder::CleanupPhiInstructionInputs(Inst *phi)
1180 {
1181     ASSERT(phi->IsPhi());
1182 
1183     auto toStringCall = FindHoistedToStringCallInput(phi);
1184     if (toStringCall != nullptr) {
1185         RemoveFromSaveStateInputs(phi);
1186         phi->ReplaceUsers(toStringCall);
1187         phi->GetBasicBlock()->RemoveInst(phi);
1188     }
1189 }
1190 
CleanupPhiInstructionInputs(Loop * loop)1191 void SimplifyStringBuilder::CleanupPhiInstructionInputs(Loop *loop)
1192 {
1193     // Remove toString()-call from accumulated value phi-instruction inputs,
1194     // replace accumulated value users with toString()-call, and remove accumulated value itself
1195     for (auto block : loop->GetBlocks()) {
1196         for (auto phi : block->PhiInstsSafe()) {
1197             CleanupPhiInstructionInputs(phi);
1198         }
1199     }
1200 }
1201 
HasNotHoistedUser(PhiInst * phi)1202 bool SimplifyStringBuilder::HasNotHoistedUser(PhiInst *phi)
1203 {
1204     return HasUser(phi, [phi](auto &user) {
1205         auto userInst = user.GetInst();
1206         bool isSelf = userInst == phi;
1207         bool isSaveState = userInst->IsSaveState();
1208         bool isRemovedAppendInstruction =
1209             IsStringBuilderAppend(userInst) && !userInst->GetFlag(compiler::inst_flags::NO_DCE);
1210         return !isSelf && !isSaveState && !isRemovedAppendInstruction;
1211     });
1212 }
1213 
RemoveUnusedPhiInstructions(Loop * loop)1214 void SimplifyStringBuilder::RemoveUnusedPhiInstructions(Loop *loop)
1215 {
1216     // Remove instructions having no users, or instruction with all users hoisted out of the loop
1217     for (auto block : loop->GetBlocks()) {
1218         for (auto phi : block->PhiInstsSafe()) {
1219             if (HasNotHoistedUser(phi->CastToPhi())) {
1220                 continue;
1221             }
1222 
1223             RemoveFromAllExceptPhiInputs(phi);
1224             block->RemoveInst(phi);
1225 
1226             COMPILER_LOG(DEBUG, SIMPLIFY_SB)
1227                 << "Remove unused instruction id=" << phi->GetId() << " (" << GetOpcodeString(phi->GetOpcode())
1228                 << ") from header block id=" << block->GetId();
1229         }
1230     }
1231 }
1232 
FixBrokenSaveStates(Loop * loop)1233 void SimplifyStringBuilder::FixBrokenSaveStates(Loop *loop)
1234 {
1235     ssb_.FixSaveStatesInBB(loop->GetPreHeader());
1236     ssb_.FixSaveStatesInBB(GetLoopPostExit(loop));
1237 }
1238 
Cleanup(Loop * loop)1239 void SimplifyStringBuilder::Cleanup(Loop *loop)
1240 {
1241     if (!isApplied_) {
1242         return;
1243     }
1244 
1245     FixBrokenSaveStates(loop);
1246     CleanupSaveStateInstructionInputs(loop);
1247     CleanupPhiInstructionInputs(loop);
1248     RemoveUnusedPhiInstructions(loop);
1249 }
1250 
MatchStringBuilderUsage(Inst * instance,StringBuilderUsage & usage)1251 void SimplifyStringBuilder::MatchStringBuilderUsage(Inst *instance, StringBuilderUsage &usage)
1252 {
1253     // Find all the usages of a given StringBuilder instance, and pack them into StringBuilderUsage structure:
1254     // i.e instance itself, constructor-call, all append-calls, all toString-calls
1255 
1256     usage.instance = instance;
1257 
1258     for (auto &user : instance->GetUsers()) {
1259         auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
1260         if (IsMethodStringBuilderConstructorWithStringArg(userInst)) {
1261             usage.ctorCall = userInst;
1262         } else if (IsMethodStringBuilderDefaultConstructor(userInst)) {
1263             usage.ctorCall = userInst;
1264         } else if (IsStringBuilderAppend(userInst)) {
1265             // StringBuilder append-call returns 'this' (instance)
1266             // Replace all users of append-call by instance for simplicity
1267             userInst->ReplaceUsers(instance);
1268         }
1269     }
1270     ASSERT(usage.ctorCall != nullptr);
1271 
1272     for (auto &user : instance->GetUsers()) {
1273         auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
1274         if (IsStringBuilderAppend(userInst)) {
1275             usage.appendInstructions.push_back(userInst);
1276         } else if (IsStringBuilderToString(userInst)) {
1277             usage.toStringCalls.push_back(userInst);
1278         }
1279     }
1280 }
1281 
HasInputFromPreHeader(PhiInst * phi) const1282 bool SimplifyStringBuilder::HasInputFromPreHeader(PhiInst *phi) const
1283 {
1284     auto preHeader = phi->GetBasicBlock()->GetLoop()->GetPreHeader();
1285     for (size_t i = 0; i < phi->GetInputsCount(); ++i) {
1286         if (phi->GetPhiInputBb(i) == preHeader) {
1287             return true;
1288         }
1289     }
1290     return false;
1291 }
1292 
HasToStringCallInput(PhiInst * phi) const1293 bool SimplifyStringBuilder::HasToStringCallInput(PhiInst *phi) const
1294 {
1295     // Returns true if
1296     //  (1) 'phi' has StringBuilder.toString call as one of its inputs, and
1297     //  (2) StringBuilder.toString call has no effective usages except this 'phi' itself.
1298     //     Users being save states, check casts, and not used users are skipped.
1299 
1300     MarkerHolder visited {GetGraph()};
1301 
1302     // (1)
1303     bool hasToStringCallInput = HasInputPhiRecursively(
1304         phi, visited.GetMarker(), [](auto &input) { return IsStringBuilderToString(input.GetInst()); });
1305 
1306     // (2)
1307     bool toStringCallInputUsedAnywhereExceptPhi = HasInput(phi, [phi](auto &input) {
1308         return IsStringBuilderToString(input.GetInst()) && HasUser(input.GetInst(), [phi](auto &user) {
1309                    auto userInst = user.GetInst();
1310                    bool isPhi = userInst == phi;
1311                    bool isSaveState = userInst->IsSaveState();
1312                    bool isCheckCast = IsCheckCastWithoutUsers(userInst);
1313                    bool hasUsers = userInst->HasUsers();
1314                    return !isPhi && !isSaveState && !isCheckCast && hasUsers;
1315                });
1316     });
1317     ResetInputMarkersRecursively(phi, visited.GetMarker());
1318     return hasToStringCallInput && !toStringCallInputUsedAnywhereExceptPhi;
1319 }
1320 
UsedByPhiInstInSameBB(const PhiInst * phi)1321 static bool UsedByPhiInstInSameBB(const PhiInst *phi)
1322 {
1323     auto header = phi->GetBasicBlock();
1324     for (auto &user : phi->GetUsers()) {
1325         auto inst = user.GetInst();
1326         if (inst->IsPhi() && inst->GetBasicBlock() == header) {
1327             return true;
1328         }
1329     }
1330     return false;
1331 }
1332 
HasInputInst(Inst * inputInst,Inst * inst) const1333 bool SimplifyStringBuilder::HasInputInst(Inst *inputInst, Inst *inst) const
1334 {
1335     MarkerHolder visited {GetGraph()};
1336     bool found = HasInputPhiRecursively(inst, visited.GetMarker(),
1337                                         [inputInst](auto &input) { return inputInst == input.GetInst(); });
1338     ResetInputMarkersRecursively(inst, visited.GetMarker());
1339     return found;
1340 }
1341 
HasAppendInstructionUser(Inst * inst) const1342 bool SimplifyStringBuilder::HasAppendInstructionUser(Inst *inst) const
1343 {
1344     Loop *instLoop = inst->GetBasicBlock()->GetLoop();
1345     for (auto &user : inst->GetUsers()) {
1346         auto appendInst = SkipSingleUserCheckInstruction(user.GetInst());
1347         if (!IsStringBuilderAppend(appendInst) || appendInst->GetDataFlowInput(1) != inst ||
1348             appendInst->GetBasicBlock()->GetLoop() != instLoop) {
1349             continue;
1350         }
1351 
1352         auto instance = appendInst->GetDataFlowInput(0);
1353         if (!IsStringBuilderInstance(instance) || instance->GetBasicBlock() != appendInst->GetBasicBlock()) {
1354             continue;
1355         }
1356 
1357         // Cycle through instructions from appendInst to sb (backwards) and make sure no other appends in between
1358         auto currentInst = appendInst->GetPrev();
1359         while (currentInst != nullptr && currentInst != instance) {
1360             if (IsStringBuilderAppend(currentInst) && currentInst->GetDataFlowInput(0) == instance) {
1361                 return false;
1362             }
1363             currentInst = currentInst->GetPrev();
1364         }
1365         return true;
1366     }
1367 
1368     return false;
1369 }
1370 
IsPhiAccumulatedValue(PhiInst * phi) const1371 bool SimplifyStringBuilder::IsPhiAccumulatedValue(PhiInst *phi) const
1372 {
1373     // Phi-instruction is accumulated value, if it is used in the following way:
1374     //  bb_preheader:
1375     //      0 LoadString ...
1376     //      ...
1377     //  bb_header:
1378     //      10p Phi v0(bb_preheader), v20(bb_back_edge)
1379     //      ...
1380     //  bb_back_edge:
1381     //      ...
1382     //      15 Intrinsic.StdCoreSbAppendString sb, v10p, ss
1383     //      ...
1384     //      20 CallStatic std.core.StringBuilder::toString sb, ss
1385     //      ...
1386 
1387     return HasInputFromPreHeader(phi) && HasToStringCallInput(phi) && !UsedByPhiInstInSameBB(phi) &&
1388            HasAppendInstructionUser(phi);
1389 }
1390 
GetPhiAccumulatedValues(Loop * loop)1391 ArenaVector<Inst *> SimplifyStringBuilder::GetPhiAccumulatedValues(Loop *loop)
1392 {
1393     // Search loop for all accumulated values
1394     // Phi accumulated value is an instruction used to store string concatenation result between loop iterations
1395 
1396     instructionsVector_.clear();
1397 
1398     for (auto inst : loop->GetHeader()->PhiInsts()) {
1399         if (IsPhiAccumulatedValue(inst->CastToPhi())) {
1400             instructionsVector_.push_back(inst);
1401         }
1402     }
1403 
1404     for (auto backEdge : loop->GetBackEdges()) {
1405         if (backEdge == loop->GetHeader()) {
1406             // Already processed above
1407             continue;
1408         }
1409 
1410         for (auto inst : backEdge->PhiInsts()) {
1411             if (IsPhiAccumulatedValue(inst->CastToPhi())) {
1412                 instructionsVector_.push_back(inst);
1413             }
1414         }
1415     }
1416 
1417     return instructionsVector_;
1418 }
1419 
StringBuilderUsagesDFS(Inst * inst,Loop * loop,Marker visited)1420 void SimplifyStringBuilder::StringBuilderUsagesDFS(Inst *inst, Loop *loop, Marker visited)
1421 {
1422     // Recursively traverse current instruction users, and collect all the StringBuilder usages met
1423 
1424     ASSERT(inst != nullptr);
1425     if (inst->GetBasicBlock()->GetLoop() != loop || inst->IsMarked(visited)) {
1426         return;
1427     }
1428 
1429     inst->SetMarker(visited);
1430 
1431     for (auto &user : inst->GetUsers()) {
1432         auto userInst = user.GetInst();
1433         if (userInst->IsPhi() && !userInst->IsMarked(visited)) {
1434             StringBuilderUsagesDFS(userInst, loop, visited);
1435         }
1436 
1437         if (!IsStringBuilderAppend(userInst)) {
1438             continue;
1439         }
1440 
1441         auto appendInstruction = userInst;
1442         ASSERT(appendInstruction->GetInputsCount() > 1);
1443         auto instance = appendInstruction->GetDataFlowInput(0);
1444         if (instance->GetBasicBlock()->GetLoop() == loop) {
1445             StringBuilderUsage usage {nullptr, nullptr, GetGraph()->GetAllocator()};
1446             MatchStringBuilderUsage(instance, usage);
1447 
1448             if (usage.toStringCalls.size() != 1) {
1449                 continue;
1450             }
1451 
1452             if (!usage.toStringCalls[0]->IsMarked(visited)) {
1453                 StringBuilderUsagesDFS(usage.toStringCalls[0], loop, visited);
1454                 usages_.push_back(usage);
1455             }
1456         }
1457     }
1458 }
1459 
GetStringBuilderUsagesPO(Inst * accValue)1460 const ArenaVector<SimplifyStringBuilder::StringBuilderUsage> &SimplifyStringBuilder::GetStringBuilderUsagesPO(
1461     Inst *accValue)
1462 {
1463     // Get all the StringBuilder usages under the phi accumulated value instruction data flow graph in post order (PO)
1464 
1465     usages_.clear();
1466 
1467     MarkerHolder usageMarker {GetGraph()};
1468     Marker visited = usageMarker.GetMarker();
1469 
1470     StringBuilderUsagesDFS(accValue, accValue->GetBasicBlock()->GetLoop(), visited);
1471 
1472     return usages_;
1473 }
1474 
AllUsersAreVisitedAppendInstructions(Inst * inst,Marker appendInstructionVisited)1475 bool SimplifyStringBuilder::AllUsersAreVisitedAppendInstructions(Inst *inst, Marker appendInstructionVisited)
1476 {
1477     bool allUsersVisited = true;
1478     for (auto &user : inst->GetUsers()) {
1479         auto userInst = user.GetInst();
1480         if (userInst->IsSaveState()) {
1481             continue;
1482         }
1483         if (IsCheckCastWithoutUsers(userInst)) {
1484             continue;
1485         }
1486         allUsersVisited &= IsStringBuilderAppend(userInst);
1487         allUsersVisited &= userInst->IsMarked(appendInstructionVisited);
1488     }
1489     return allUsersVisited;
1490 }
1491 
UpdateIntermediateValue(const StringBuilderUsage & usage,Inst * intermediateValue,Marker appendInstructionVisited)1492 Inst *SimplifyStringBuilder::UpdateIntermediateValue(const StringBuilderUsage &usage, Inst *intermediateValue,
1493                                                      Marker appendInstructionVisited)
1494 {
1495     // Update intermediate value with toString-call of the current StringBuilder usage, if all its append-call users
1496     // were already visited
1497 
1498     size_t usersCount = 0;
1499     bool allUsersVisited = true;
1500 
1501     for (auto &user : intermediateValue->GetUsers()) {
1502         auto userInst = user.GetInst();
1503         if (userInst->IsSaveState()) {
1504             continue;
1505         }
1506 
1507         if (userInst->IsPhi() && !userInst->HasUsers()) {
1508             continue;
1509         }
1510 
1511         if (userInst->IsPhi() && !userInst->HasUsers()) {
1512             continue;
1513         }
1514 
1515         if (userInst->IsPhi()) {
1516             ++usersCount;
1517             allUsersVisited &= AllUsersAreVisitedAppendInstructions(userInst, appendInstructionVisited);
1518         } else if (IsStringBuilderAppend(userInst)) {
1519             ++usersCount;
1520             allUsersVisited &= userInst->IsMarked(appendInstructionVisited);
1521         } else {
1522             break;
1523         }
1524     }
1525 
1526     ASSERT(usage.toStringCalls.size() == 1);
1527     if (usersCount == 1) {
1528         intermediateValue = usage.toStringCalls[0];
1529     } else if (usersCount > 1 && allUsersVisited) {
1530         intermediateValue = usage.toStringCalls[0];
1531         for (auto &user : usage.toStringCalls[0]->GetUsers()) {
1532             auto userInst = user.GetInst();
1533             if (userInst->IsPhi() && userInst->HasUsers()) {
1534                 intermediateValue = userInst;
1535                 break;
1536             }
1537         }
1538     }
1539 
1540     return intermediateValue;
1541 }
1542 
MatchTemporaryInstruction(ConcatenationLoopMatch & match,ConcatenationLoopMatch::TemporaryInstructions & temp,Inst * appendInstruction,const Inst * accValue,Marker appendInstructionVisited)1543 bool SimplifyStringBuilder::MatchTemporaryInstruction(ConcatenationLoopMatch &match,
1544                                                       ConcatenationLoopMatch::TemporaryInstructions &temp,
1545                                                       Inst *appendInstruction, const Inst *accValue,
1546                                                       Marker appendInstructionVisited)
1547 {
1548     // Arrange single append instruction to one of the two groups (substructures of ConcatenationLoopMatch):
1549     //  'temp' group - temporary instructions to be erased from loop
1550     //  'loop' group - append-call instructions to be kept inside loop
1551 
1552     ASSERT(appendInstruction->GetInputsCount() > 1);
1553     auto appendArg = appendInstruction->GetDataFlowInput(1);
1554     if ((appendArg->IsPhi() && IsDataFlowInput(appendArg, temp.intermediateValue)) ||
1555         appendArg == temp.intermediateValue || appendArg == accValue) {
1556         // Append-call needs to be removed, if its argument is either accumulated value, or intermediate value;
1557         // or intermediate value is data flow input of argument
1558 
1559         if (temp.appendAccValue == nullptr) {
1560             temp.appendAccValue = appendInstruction;
1561         } else {
1562             // Does not look like string concatenation pattern
1563             temp.Clear();
1564             return false;
1565         }
1566         appendInstruction->SetMarker(appendInstructionVisited);
1567     } else {
1568         if (temp.appendAccValue != nullptr) {
1569             // First appendee should be acc value
1570             temp.Clear();
1571             return false;
1572         }
1573         // Keep append-call inside loop otherwise
1574         match.loop.appendInstructions.push_back(appendInstruction);
1575     }
1576     return true;
1577 }
1578 
MatchTemporaryInstructions(const StringBuilderUsage & usage,ConcatenationLoopMatch & match,Inst * accValue,Inst * intermediateValue,Marker appendInstructionVisited)1579 void SimplifyStringBuilder::MatchTemporaryInstructions(const StringBuilderUsage &usage, ConcatenationLoopMatch &match,
1580                                                        Inst *accValue, Inst *intermediateValue,
1581                                                        Marker appendInstructionVisited)
1582 {
1583     // Split all the instructions of a given StringBuilder usage into groups (substructures of ConcatenationLoopMatch):
1584     //  'temp' group - temporary instructions to be erased from loop
1585     //  'loop' group - append-call instructions to be kept inside loop
1586 
1587     ConcatenationLoopMatch::TemporaryInstructions temp {};
1588     temp.intermediateValue = intermediateValue;
1589     temp.toStringCall = usage.toStringCalls[0];
1590     temp.instance = usage.instance;
1591     temp.ctorCall = usage.ctorCall;
1592 
1593     for (auto appendInstruction : usage.appendInstructions) {
1594         bool isMatchStillValid =
1595             MatchTemporaryInstruction(match, temp, appendInstruction, accValue, appendInstructionVisited);
1596         if (!isMatchStillValid) {
1597             // Doesn't look like concatenation pattern. Clean entire match
1598             match.Clear();
1599             return;
1600         }
1601     }
1602 
1603     if (!temp.IsEmpty()) {
1604         match.temp.push_back(temp);
1605     }
1606 }
1607 
1608 // CC-OFFNXT(huge_depth) false positive
MatchHoistableInstructions(const StringBuilderUsage & usage,ConcatenationLoopMatch & match,Marker appendInstructionVisited)1609 Inst *SimplifyStringBuilder::MatchHoistableInstructions(const StringBuilderUsage &usage, ConcatenationLoopMatch &match,
1610                                                         Marker appendInstructionVisited)
1611 {
1612     // Split all the instructions of a given StringBuilder usage into groups (substructures of ConcatenationLoopMatch):
1613     //  'preheader' group - instructions to be hoisted to preheader block
1614     //  'loop' group - append-call instructions to be kept inside loop
1615     //  'exit' group - instructions to be hoisted to post exit block
1616 
1617     match.block = usage.instance->GetBasicBlock();
1618 
1619     ASSERT(usage.instance->GetInputsCount() > 0);
1620     match.preheader.instance = usage.instance;
1621     match.preheader.ctorCall = usage.ctorCall;
1622     ASSERT(usage.toStringCalls.size() == 1);
1623     match.exit.toStringCall = usage.toStringCalls[0];
1624 
1625     for (auto &user : usage.instance->GetUsers()) {
1626         auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
1627         if (userInst->IsSaveState()) {
1628             continue;
1629         }
1630 
1631         if (!IsStringBuilderAppend(userInst)) {
1632             continue;
1633         }
1634 
1635         // Check if append-call needs to be hoisted or kept inside loop
1636         auto appendInstruction = userInst;
1637         ASSERT(appendInstruction->GetInputsCount() > 1);
1638         auto appendArg = appendInstruction->GetDataFlowInput(1);
1639         if (appendArg->IsPhi() && IsPhiAccumulatedValue(appendArg->CastToPhi())) {
1640             // Append-call needs to be hoisted, if its argument is accumulated value
1641             auto phiAppendArg = appendArg->CastToPhi();
1642             auto initialValue =
1643                 phiAppendArg->GetPhiDataflowInput(usage.instance->GetBasicBlock()->GetLoop()->GetPreHeader());
1644             if (match.initialValue != nullptr || match.accValue != nullptr ||
1645                 match.preheader.appendAccValue != nullptr) {
1646                 // Does not look like string concatenation pattern
1647                 match.Clear();
1648                 break;
1649             }
1650 
1651             match.initialValue = initialValue;
1652             match.accValue = phiAppendArg;
1653             match.preheader.appendAccValue = appendInstruction;
1654 
1655             appendInstruction->SetMarker(appendInstructionVisited);
1656         } else {
1657             // Keep append-call inside loop otherwise
1658             match.loop.appendInstructions.push_back(appendInstruction);
1659         }
1660     }
1661 
1662     // Initialize intermediate value with toString-call to be hoisted to post exit block
1663     return match.exit.toStringCall;
1664 }
1665 
MatchLoopConcatenation(Loop * loop)1666 const ArenaVector<SimplifyStringBuilder::ConcatenationLoopMatch> &SimplifyStringBuilder::MatchLoopConcatenation(
1667     Loop *loop)
1668 {
1669     // Search loop for string concatenation patterns like the following:
1670     //   >  let str = initial_value: String
1671     //   >  for (...) {
1672     //   >      str += a0 + b0 + ...
1673     //   >      str += a1 + b2 + ...
1674     //   >      ...
1675     //   >  }
1676     // And fill ConcatenationLoopMatch structure with instructions from the pattern found
1677 
1678     matches_.clear();
1679 
1680     MarkerHolder appendInstructionMarker {GetGraph()};
1681     Marker appendInstructionVisited = appendInstructionMarker.GetMarker();
1682 
1683     // Accumulated value (acc_value) is a phi-instruction holding concatenation result between loop iterations, and
1684     // final result after loop completes
1685     for (auto accValue : GetPhiAccumulatedValues(loop)) {
1686         // Intermediate value is an instruction holding concatenation result during loop iteration execution.
1687         // It is initialized with acc_value, and updated with either toString-calls, or other phi-instructions
1688         // (depending on the loop control flow and data flow)
1689         Inst *intermediateValue = accValue;
1690         ConcatenationLoopMatch match {GetGraph()->GetAllocator()};
1691 
1692         // Get all the StringBuilders used to calculate current accumulated value (in PO)
1693         auto &usages = GetStringBuilderUsagesPO(accValue);
1694         // RPO traversal: walk through PO usages backwards
1695         for (auto usage = usages.rbegin(); usage != usages.rend(); ++usage) {
1696             if (usage->toStringCalls.size() != 1) {
1697                 continue;  // Unsupported: doesn't look like string concatenation pattern
1698             }
1699 
1700             if (match.preheader.instance == nullptr) {
1701                 // First StringBuilder instance are set to be hoisted
1702                 intermediateValue = MatchHoistableInstructions(*usage, match, appendInstructionVisited);
1703             } else {
1704                 // All other StringBuilder instances are set to be removed as temporary
1705                 MatchTemporaryInstructions(*usage, match, accValue, intermediateValue, appendInstructionVisited);
1706                 intermediateValue = UpdateIntermediateValue(*usage, intermediateValue, appendInstructionVisited);
1707             }
1708         }
1709 
1710         if (IsInstanceHoistable(match) && IsToStringHoistable(match, appendInstructionVisited)) {
1711             matches_.push_back(match);
1712         }
1713 
1714         // Reset markers
1715         if (match.preheader.appendAccValue != nullptr) {
1716             match.preheader.appendAccValue->ResetMarker(appendInstructionVisited);
1717         }
1718         for (auto &temp : match.temp) {
1719             if (temp.appendAccValue != nullptr) {
1720                 temp.appendAccValue->ResetMarker(appendInstructionVisited);
1721             }
1722         }
1723     }
1724 
1725     return matches_;
1726 }
1727 
OptimizeStringConcatenation(Loop * loop)1728 void SimplifyStringBuilder::OptimizeStringConcatenation(Loop *loop)
1729 {
1730     // Optimize String Builder concatenation loops
1731 
1732     // Process inner loops first
1733     for (auto innerLoop : loop->GetInnerLoops()) {
1734         OptimizeStringConcatenation(innerLoop);
1735     }
1736 
1737     // Check if basic block for instructions to hoist exist
1738     // Alternative way maybe to create one
1739     if (loop->GetPreHeader() == nullptr) {
1740         return;
1741     }
1742 
1743     auto preHeaderSaveState = FindPreHeaderSaveState(loop);
1744     if (preHeaderSaveState == nullptr) {
1745         return;
1746     }
1747 
1748     // Check if basic block for instructions to hoist exist
1749     // Alternative way maybe to create one
1750     auto postExit = GetLoopPostExit(loop);
1751     if (postExit == nullptr) {
1752         return;
1753     }
1754 
1755     auto postExitSaveState = FindFirstSaveState(postExit);
1756     ASSERT(postExitSaveState);  // IR Builder must create empty save states for loop exits
1757 
1758     for (auto &match : MatchLoopConcatenation(loop)) {
1759         ReconnectInstructions(match);
1760         HoistInstructionsToPreHeader(match, preHeaderSaveState);
1761         HoistInstructionsToPostExit(match, postExitSaveState);
1762         Cleanup(match);
1763 
1764         isApplied_ = true;
1765     }
1766     Cleanup(loop);
1767 }
1768 
CreateIntrinsicStringBuilderAppendStrings(const ConcatenationMatch & match,SaveStateInst * saveState)1769 IntrinsicInst *SimplifyStringBuilder::CreateIntrinsicStringBuilderAppendStrings(const ConcatenationMatch &match,
1770                                                                                 SaveStateInst *saveState)
1771 {
1772     auto arg0 = match.appendIntrinsics[0U]->GetInput(1U).GetInst();
1773     auto arg1 = match.appendIntrinsics[1U]->GetInput(1U).GetInst();
1774 
1775     switch (match.appendCount) {
1776         case 2U: {
1777             return CreateIntrinsic(
1778                 GetGraph(), GetGraph()->GetRuntime()->GetStringBuilderAppendStringsIntrinsicId(match.appendCount),
1779                 DataType::REFERENCE,
1780                 {{match.instance, match.instance->GetType()},
1781                  {arg0, arg0->GetType()},
1782                  {arg1, arg1->GetType()},
1783                  {saveState, saveState->GetType()}});
1784         }
1785         case 3U: {
1786             auto arg2 = match.appendIntrinsics[2U]->GetInput(1U).GetInst();
1787 
1788             return CreateIntrinsic(
1789                 GetGraph(), GetGraph()->GetRuntime()->GetStringBuilderAppendStringsIntrinsicId(match.appendCount),
1790                 DataType::REFERENCE,
1791                 {{match.instance, match.instance->GetType()},
1792                  {arg0, arg0->GetType()},
1793                  {arg1, arg1->GetType()},
1794                  {arg2, arg2->GetType()},
1795                  {saveState, saveState->GetType()}});
1796         }
1797         case 4U: {
1798             auto arg2 = match.appendIntrinsics[2U]->GetInput(1U).GetInst();
1799             auto arg3 = match.appendIntrinsics[3U]->GetInput(1U).GetInst();
1800 
1801             return CreateIntrinsic(
1802                 GetGraph(), GetGraph()->GetRuntime()->GetStringBuilderAppendStringsIntrinsicId(match.appendCount),
1803                 DataType::REFERENCE,
1804                 {{match.instance, match.instance->GetType()},
1805                  {arg0, arg0->GetType()},
1806                  {arg1, arg1->GetType()},
1807                  {arg2, arg2->GetType()},
1808                  {arg3, arg3->GetType()},
1809                  {saveState, saveState->GetType()}});
1810         }
1811         default:
1812             UNREACHABLE();
1813     }
1814 }
1815 
ReplaceWithAppendIntrinsic(const ConcatenationMatch & match)1816 void SimplifyStringBuilder::ReplaceWithAppendIntrinsic(const ConcatenationMatch &match)
1817 {
1818     COMPILER_LOG(DEBUG, SIMPLIFY_SB) << match.appendCount << " consecutive append string intrinsics found IDs: ";
1819     for (size_t index = 0; index < match.appendCount; ++index) {
1820         COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "\t" << match.appendIntrinsics[index]->GetId();
1821     }
1822     COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "for instance id=" << match.instance->GetId() << " ("
1823                                      << GetOpcodeString(match.instance->GetOpcode()) << "), applying";
1824 
1825     ASSERT(match.appendCount > 0);
1826     auto lastAppendIntrinsic = match.appendIntrinsics[match.appendCount - 1U];
1827     auto appendNIntrinsic = CreateIntrinsicStringBuilderAppendStrings(
1828         match, CopySaveState(GetGraph(), lastAppendIntrinsic->GetSaveState()));
1829 
1830     InsertBeforeWithSaveState(appendNIntrinsic, lastAppendIntrinsic);
1831 
1832     for (size_t index = 0; index < match.appendCount; ++index) {
1833         auto appendIntrinsic = match.appendIntrinsics[index];
1834         FixBrokenSaveStates(appendIntrinsic->GetDataFlowInput(1), appendNIntrinsic);
1835         appendIntrinsic->ClearFlag(inst_flags::NO_DCE);
1836     }
1837 
1838     COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "Replaced with append" << match.appendCount
1839                                      << " intrinsic id=" << appendNIntrinsic->GetId();
1840 
1841     isApplied_ = true;
1842 }
1843 
HasPhiOfStringBuilders(BasicBlock * block)1844 bool HasPhiOfStringBuilders(BasicBlock *block)
1845 {
1846     for (auto phi : block->PhiInsts()) {
1847         MarkerHolder visited {block->GetGraph()};
1848         if (HasInputPhiRecursively(phi, visited.GetMarker(),
1849                                    [](auto &input) { return IsStringBuilderInstance(input.GetInst()); })) {
1850             return true;
1851         }
1852     }
1853     return false;
1854 }
1855 
1856 // CC-OFFNXT(huge_depth) false positive
CollectStringBuilderCalls(BasicBlock * block)1857 const SimplifyStringBuilder::StringBuilderCallsMap &SimplifyStringBuilder::CollectStringBuilderCalls(BasicBlock *block)
1858 {
1859     auto &calls = stringBuilderCalls_;
1860     calls.clear();
1861 
1862     // Skip blocks with Phi of String Builders
1863     if (HasPhiOfStringBuilders(block)) {
1864         return calls;
1865     }
1866 
1867     auto current = calls.end();
1868     for (auto inst : block->Insts()) {
1869         if (inst->IsSaveState() || inst->IsCheck()) {
1870             continue;
1871         }
1872 
1873         for (size_t index = 0; index < inst->GetInputsCount(); ++index) {
1874             auto instance = inst->GetDataFlowInput(index);
1875             if (!IsStringBuilderInstance(instance)) {
1876                 continue;
1877             }
1878 
1879             if (current != calls.end() && current->first != instance) {
1880                 current = calls.find(instance);
1881             }
1882             if (current == calls.end()) {
1883                 current = calls.emplace(instance, InstVector {GetGraph()->GetLocalAllocator()->Adapter()}).first;
1884             }
1885             current->second.push_back(inst);
1886         }
1887     }
1888     return calls;
1889 }
1890 
CountStringBuilderAppendString(const InstVector & calls,size_t from)1891 size_t CountStringBuilderAppendString(const InstVector &calls, size_t from)
1892 {
1893     auto to = std::min(from + SB_APPEND_STRING_MAX_ARGS, calls.size());
1894     for (auto index = from; index < to; ++index) {
1895         if (IsIntrinsicStringBuilderAppendString(calls[index])) {
1896             continue;
1897         }
1898         return index;
1899     }
1900     return to;
1901 }
1902 
ReplaceWithAppendIntrinsic(Inst * instance,const InstVector & calls,size_t from,size_t to)1903 void SimplifyStringBuilder::ReplaceWithAppendIntrinsic(Inst *instance, const InstVector &calls, size_t from, size_t to)
1904 {
1905     auto appendCount = to - from;
1906     ASSERT(0 < appendCount && appendCount <= SB_APPEND_STRING_MAX_ARGS);
1907     if (appendCount == 1) {
1908         COMPILER_LOG(DEBUG, SIMPLIFY_SB) << "One consecutive append string intrinsic found id=" << calls[from]->GetId()
1909                                          << " for instance id=" << instance->GetId() << " ("
1910                                          << GetOpcodeString(instance->GetOpcode()) << "), skipping";
1911     } else {
1912         ConcatenationMatch match {instance, nullptr, nullptr, appendCount};
1913         for (size_t index = 0; index < appendCount; ++index) {
1914             match.appendIntrinsics[index] = calls[from + index]->CastToIntrinsic();
1915         }
1916         ReplaceWithAppendIntrinsic(match);
1917     }
1918 }
1919 
OptimizeStringBuilderAppendChain(BasicBlock * block)1920 void SimplifyStringBuilder::OptimizeStringBuilderAppendChain(BasicBlock *block)
1921 {
1922     // StringBuilder::append(s0, s1, ...) implemented with Irtoc functions with number of arguments more than 4, and
1923     // requires pre/post-write barriers to be supported. AARCH32 does not support neither barriers, nor so much
1924     // arguments in Irtoc functions.
1925     if (!GetGraph()->SupportsIrtocBarriers()) {
1926         return;
1927     }
1928 
1929     isApplied_ |= BreakStringBuilderAppendChains(block);
1930 
1931     for (auto &[instance, calls] : CollectStringBuilderCalls(block)) {
1932         for (size_t from = 0; from < calls.size();) {
1933             if (!IsIntrinsicStringBuilderAppendString(calls[from])) {
1934                 ++from;
1935                 continue;
1936             }
1937 
1938             size_t to = CountStringBuilderAppendString(calls, from);
1939             ReplaceWithAppendIntrinsic(instance, calls, from, to);
1940 
1941             ASSERT(from < to);
1942             from = to;
1943         }
1944     }
1945 }
1946 
1947 // CC-OFFNXT(huge_depth) false positive
CollectStringBuilderFirstCalls(BasicBlock * block)1948 void SimplifyStringBuilder::CollectStringBuilderFirstCalls(BasicBlock *block)
1949 {
1950     Inst *instance = nullptr;
1951     auto calls = stringBuilderFirstLastCalls_.end();
1952     for (auto inst : block->Insts()) {
1953         if (IsStringBuilderInstance(inst)) {
1954             instance = inst;
1955             calls = stringBuilderFirstLastCalls_.find(instance);
1956             if (calls == stringBuilderFirstLastCalls_.end()) {
1957                 calls = stringBuilderFirstLastCalls_.emplace(instance, InstPair {}).first;
1958             }
1959         }
1960 
1961         if (calls != stringBuilderFirstLastCalls_.end() && IsStringBuilderAppend(inst) &&
1962             inst->GetDataFlowInput(0) == instance) {
1963             auto &firstCall = calls->second.first;
1964             if (firstCall == nullptr) {
1965                 firstCall = inst;
1966             }
1967         }
1968     }
1969 }
1970 
1971 // CC-OFFNXT(huge_depth) false positive
CollectStringBuilderLastCalls(BasicBlock * block)1972 void SimplifyStringBuilder::CollectStringBuilderLastCalls(BasicBlock *block)
1973 {
1974     for (auto inst : block->InstsReverse()) {
1975         if (inst->IsSaveState() || inst->IsCheck()) {
1976             continue;
1977         }
1978 
1979         if (!IsStringBuilderToString(inst)) {
1980             continue;
1981         }
1982 
1983         auto inputInst = inst->GetDataFlowInput(0);
1984         if (inputInst->IsSaveState()) {
1985             continue;
1986         }
1987 
1988         auto instance = inputInst;
1989         if (!IsStringBuilderInstance(instance)) {
1990             continue;
1991         }
1992 
1993         auto calls = stringBuilderFirstLastCalls_.find(instance);
1994         if (calls == stringBuilderFirstLastCalls_.end()) {
1995             calls = stringBuilderFirstLastCalls_.emplace(instance, InstPair {}).first;
1996         }
1997         auto &lastCall = calls->second.second;
1998         if (lastCall == nullptr) {
1999             lastCall = inst;
2000         }
2001     }
2002 }
2003 
CollectStringBuilderFirstLastCalls()2004 void SimplifyStringBuilder::CollectStringBuilderFirstLastCalls()
2005 {
2006     // For each SB in graph, find its first (append) call and last (toString) call
2007     stringBuilderFirstLastCalls_.clear();
2008 
2009     // Traverse CFG in RPO
2010     for (auto block : GetGraph()->GetBlocksRPO()) {
2011         isApplied_ |= BreakStringBuilderAppendChains(block);
2012         CollectStringBuilderFirstCalls(block);
2013         CollectStringBuilderLastCalls(block);
2014     }
2015 }
2016 
CanMergeStringBuilders(Inst * instance,const InstPair & instanceCalls,Inst * inputInstance)2017 bool SimplifyStringBuilder::CanMergeStringBuilders(Inst *instance, const InstPair &instanceCalls, Inst *inputInstance)
2018 {
2019     if (instance == inputInstance) {
2020         return false;  // Skip instance.append(instance.toString()) case
2021     }
2022 
2023     auto inputInstanceCalls = stringBuilderFirstLastCalls_.find(inputInstance);
2024     if (inputInstanceCalls == stringBuilderFirstLastCalls_.end()) {
2025         return false;
2026     }
2027 
2028     auto &[inputInstanceFirstAppendCall, inputInstanceLastToStringCall] = inputInstanceCalls->second;
2029     if (inputInstanceFirstAppendCall == nullptr || inputInstanceLastToStringCall == nullptr) {
2030         return false;  // Unsupported case: doesn't look like concatenation pattern
2031     }
2032 
2033     for (auto &user : instance->GetUsers()) {
2034         auto userInst = user.GetInst();
2035         if (IsMethodStringBuilderConstructorWithCharArrayArg(userInst) ||
2036             IsMethodStringBuilderConstructorWithStringArg(userInst)) {
2037             // Does not look like string concatenation pattern
2038             return false;
2039         }
2040     }
2041 
2042     auto instanceFirstCall = instanceCalls.first;
2043 
2044     if (instance->GetBasicBlock() != instanceFirstCall->GetBasicBlock()) {
2045         // Does not look like string concatenation pattern
2046         return false;
2047     }
2048 
2049     // Check if 'inputInstance' toString call comes after any 'inputInstance' append call
2050     for (auto &user : inputInstance->GetUsers()) {
2051         auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
2052         if (!IsStringBuilderAppend(userInst)) {
2053             continue;
2054         }
2055 
2056         if (inputInstanceLastToStringCall->IsDominate(userInst)) {
2057             return false;
2058         }
2059     }
2060 
2061     // Check if 'inputInstance' toString call has single append call user
2062     if (CountUsers(inputInstanceLastToStringCall, [instanceFirstCall](auto &user) {
2063             auto userInst = user.GetInst();
2064             auto isAppend = IsStringBuilderAppend(userInst);
2065             return isAppend && userInst != instanceFirstCall;
2066         }) > 0) {
2067         return false;
2068     }
2069 
2070     // Check if all 'inputInstance' calls comes before any 'instance' call
2071     return inputInstanceLastToStringCall->IsDominate(instanceFirstCall);
2072 }
2073 
Cleanup(Inst * instance,Inst * instanceFirstAppendCall,Inst * inputInstanceToStringCall)2074 void SimplifyStringBuilder::Cleanup(Inst *instance, Inst *instanceFirstAppendCall, Inst *inputInstanceToStringCall)
2075 {
2076     // Mark 'instance' constructor as dead
2077     for (auto &user : instance->GetUsers()) {
2078         auto userInst = user.GetInst();
2079         if (!IsMethodStringBuilderDefaultConstructor(userInst)) {
2080             continue;
2081         }
2082         userInst->ClearFlag(inst_flags::NO_DCE);
2083     }
2084 
2085     // Mark 'instance' append call as dead
2086     instanceFirstAppendCall->ClearFlag(inst_flags::NO_DCE);
2087 
2088     // Mark 'inputInstance' toString call as dead
2089     inputInstanceToStringCall->ClearFlag(inst_flags::NO_DCE);
2090     for (auto &user : inputInstanceToStringCall->GetUsers()) {
2091         auto userInst = user.GetInst();
2092         if (!IsCheckCastWithoutUsers(userInst)) {
2093             continue;
2094         }
2095         userInst->ClearFlag(inst_flags::NO_DCE);
2096     }
2097 
2098     // Mark 'instance' itself as dead
2099     instance->ClearFlag(inst_flags::NO_DCE);
2100 
2101     // Remove instructions marked dead from save states
2102     RemoveFromSaveStateInputs(instance);
2103     RemoveFromSaveStateInputs(instanceFirstAppendCall);
2104 
2105     // Remove inputInstance.toString() call from save states only if it is not used anywhere else
2106     if (!HasUser(inputInstanceToStringCall, [instanceFirstAppendCall](auto &user) {
2107             auto userInst = user.GetInst();
2108             auto isSaveState = userInst->IsSaveState();
2109             auto isAppend = userInst == instanceFirstAppendCall;
2110             return !isSaveState && !isAppend;
2111         })) {
2112         RemoveFromSaveStateInputs(inputInstanceToStringCall);
2113     }
2114 }
2115 
FixBrokenSaveStatesForStringBuilderCalls(Inst * instance)2116 void SimplifyStringBuilder::FixBrokenSaveStatesForStringBuilderCalls(Inst *instance)
2117 {
2118     for (auto &user : instance->GetUsers()) {
2119         auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
2120         if (userInst->IsSaveState()) {
2121             continue;
2122         }
2123 
2124         if (IsStringBuilderAppend(userInst)) {
2125             ASSERT(userInst->GetInputsCount() > 1U);
2126             auto appendArg = userInst->GetDataFlowInput(1U);
2127             FixBrokenSaveStates(appendArg, userInst);
2128         }
2129 
2130         FixBrokenSaveStates(instance, userInst);
2131     }
2132 }
2133 
CollectStringBuilderChainCalls(Inst * instance,Inst * inputInstance)2134 void SimplifyStringBuilder::CollectStringBuilderChainCalls(Inst *instance, Inst *inputInstance)
2135 {
2136     ASSERT(stringBuilderCalls_.find(inputInstance) == stringBuilderCalls_.end());
2137 
2138     // Check if 'instance' already in calls map
2139     auto calls = stringBuilderCalls_.find(instance);
2140     if (calls == stringBuilderCalls_.end()) {
2141         // If not, add 'inputInstance' with empty instructions vector to a map
2142         calls = stringBuilderCalls_
2143                     .insert(std::make_pair(inputInstance, InstVector {GetGraph()->GetAllocator()->Adapter()}))
2144                     .first;
2145     } else {
2146         // If yes, do the following:
2147         // 1. Add 'inputInstance' with instructions vector taken from 'instance'
2148         // 2. Remove 'instance' from a map
2149         calls = stringBuilderCalls_.insert(std::make_pair(inputInstance, std::move(calls->second))).first;
2150         stringBuilderCalls_.erase(instance);
2151     }
2152 
2153     // Map 'instance' users to 'inputInstance'
2154     for (auto &user : instance->GetUsers()) {
2155         auto userInst = user.GetInst();
2156         // Skip save states
2157         if (userInst->IsSaveState()) {
2158             continue;
2159         }
2160 
2161         // Skip StringBuilder constructors
2162         if (IsMethodStringBuilderDefaultConstructor(userInst) ||
2163             IsMethodStringBuilderConstructorWithStringArg(userInst) ||
2164             IsMethodStringBuilderConstructorWithCharArrayArg(userInst)) {
2165             continue;
2166         }
2167 
2168         calls->second.push_back(userInst);
2169     }
2170 }
2171 
CollectStringBuilderChainCalls()2172 SimplifyStringBuilder::StringBuilderCallsMap &SimplifyStringBuilder::CollectStringBuilderChainCalls()
2173 {
2174     CollectStringBuilderFirstLastCalls();
2175     stringBuilderCalls_.clear();
2176 
2177     // Traverse CFG in PO
2178     auto &blocksRPO = GetGraph()->GetBlocksRPO();
2179     for (auto block = blocksRPO.rbegin(); block != blocksRPO.rend(); ++block) {
2180         // Traverse 'block' backwards
2181         for (auto instance : (*block)->InstsReverse()) {
2182             if (!IsStringBuilderInstance(instance)) {
2183                 continue;
2184             }
2185 
2186             auto instanceCalls = stringBuilderFirstLastCalls_.find(instance);
2187             if (instanceCalls == stringBuilderFirstLastCalls_.end()) {
2188                 continue;
2189             }
2190 
2191             auto &instanceFirstLastCalls = instanceCalls->second;
2192             auto &[instanceFirstAppendCall, instanceLastToStringCall] = instanceFirstLastCalls;
2193             if (instanceFirstAppendCall == nullptr || instanceLastToStringCall == nullptr) {
2194                 continue;  // Unsupported case: doesn't look like concatenation pattern
2195             }
2196 
2197             if (!IsStringBuilderAppend(instanceFirstAppendCall)) {
2198                 continue;
2199             }
2200 
2201             auto inputInstanceToStringCall = instanceFirstAppendCall->GetDataFlowInput(1);
2202             if (!IsStringBuilderToString(inputInstanceToStringCall)) {
2203                 continue;
2204             }
2205 
2206             if (instanceFirstAppendCall->GetDataFlowInput(1) != inputInstanceToStringCall) {
2207                 continue;  // Allow only cases like: instance.append(inputInstace.toString())
2208             }
2209 
2210             ASSERT(inputInstanceToStringCall->GetInputsCount() > 1);
2211             auto inputInstance = inputInstanceToStringCall->GetDataFlowInput(0);
2212             if (!IsStringBuilderInstance(inputInstance)) {
2213                 continue;
2214             }
2215 
2216             if (!CanMergeStringBuilders(instance, instanceFirstLastCalls, inputInstance)) {
2217                 continue;
2218             }
2219 
2220             Cleanup(instance, instanceFirstAppendCall, inputInstanceToStringCall);
2221             CollectStringBuilderChainCalls(instance, inputInstance);
2222         }
2223     }
2224 
2225     return stringBuilderCalls_;
2226 }
2227 
OptimizeStringBuilderChain()2228 void SimplifyStringBuilder::OptimizeStringBuilderChain()
2229 {
2230     /*
2231         Merges consecutive String Builders into one String Builder if possible
2232         Example of string concatenation (TS):
2233             let a: String = ...
2234             a += ...;
2235             a += ...;
2236             let result = a;
2237 
2238         Frontend generates the following SB-equivalent code for the example above:
2239             let inputInstance = new StringBuilder()
2240             inputInstance.append(...)                   // append calls for 'inputInstance'
2241             ...
2242             let instance = new StringBuilder()
2243             instance.append(inputInstance.toString())   // first call to append for 'instance'
2244                                                         // 'inputInstance' not used beyond this point
2245             instance.append(...)                        // append calls for 'instance'
2246             ...
2247             let result = instance.toString()
2248 
2249         The algorithm transforms it into:
2250             let inputInstance = new StringBuilder()
2251             inputInstance.append(...)                   // append calls for 'inputInstance'
2252             ...
2253             inputInstance.append(...)                   // append calls for 'instance' replaced by 'inputInstance'
2254             ...
2255             let result = inputInstance.toString()
2256 
2257         Note: arbirtary length of String Builder chain supported
2258 
2259         The algorithm works on SBs, but not on strings themselves, thus the following limitations assumed:
2260             1) all SBs must be created via default constructor,
2261             2) call to append for 'instance' SB with the input of inputInstance.toString() must be the first,
2262             3) 'inputInstance' must not be used after instance.append(inputInstance.toString()) call.
2263         Those limitations fully match SB patterns of string concatenation.
2264     */
2265 
2266     for (auto &instanceCalls : CollectStringBuilderChainCalls()) {
2267         auto inputInstance = instanceCalls.first;
2268         auto &calls = instanceCalls.second;
2269 
2270         for (auto call : calls) {
2271             // Switch call to new instance only if it is not marked for removal
2272             if (call->GetFlag(inst_flags::NO_DCE)) {
2273                 call->SetInput(0U, inputInstance);
2274             }
2275         }
2276 
2277         FixBrokenSaveStatesForStringBuilderCalls(inputInstance);
2278 
2279         isApplied_ = true;
2280     }
2281 }
2282 
2283 }  // namespace ark::compiler
2284