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