• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2024-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 "reserve_string_builder_buffer.h"
17 
18 #include "compiler_logger.h"
19 
20 #include "optimizer/analysis/alias_analysis.h"
21 #include "optimizer/analysis/bounds_analysis.h"
22 #include "optimizer/analysis/dominators_tree.h"
23 #include "optimizer/ir/analysis.h"
24 #include "optimizer/ir/inst.h"
25 
26 #include "optimizer/optimizations/cleanup.h"
27 #include "optimizer/optimizations/string_builder_utils.h"
28 
29 namespace ark::compiler {
30 
31 constexpr size_t ARG_IDX_0 = 0;
32 constexpr size_t ARG_IDX_1 = 1;
33 constexpr size_t ARG_IDX_2 = 2;
34 constexpr uint64_t INVALID_COUNT = std::numeric_limits<uint64_t>::max();
35 constexpr size_t TLAB_MAX_ALLOC_SIZE = 4_KB;
36 constexpr uint64_t SB_INITIAL_BUFFER_SIZE = 16;
37 
ReserveStringBuilderBuffer(Graph * graph)38 ReserveStringBuilderBuffer::ReserveStringBuilderBuffer(Graph *graph)
39     : Optimization(graph), blockWeightsMap_ {graph->GetLocalAllocator()->Adapter()}
40 {
41     // String Builder internal buffer size is limited by TLAB max allocation size
42     // minus offset reserved for an array header (elements info, array size)
43     auto arrayDataOffset = graph->GetRuntime()->GetArrayDataOffset(graph->GetArch());
44     tlabArraySizeMax_ = (TLAB_MAX_ALLOC_SIZE - arrayDataOffset) / sizeof(ObjectPointerType);
45 }
46 
GetLoopIterationsCount(Loop * loop)47 uint64_t GetLoopIterationsCount(Loop *loop)
48 {
49     auto loopParser = CountableLoopParser(*loop);
50     auto loopInfo = loopParser.Parse();
51     if (!loopInfo.has_value()) {
52         return INVALID_COUNT;
53     }
54 
55     auto loopCount = CountableLoopParser::GetLoopIterations(loopInfo.value());
56     return loopCount.value_or(INVALID_COUNT);
57 }
58 
HasAppendInstructions(Inst * instance,BasicBlock * block)59 bool HasAppendInstructions(Inst *instance, BasicBlock *block)
60 {
61     for (auto appendInstruction : block->Insts()) {
62         if (!IsStringBuilderAppend(appendInstruction)) {
63             continue;
64         }
65 
66         if (appendInstruction->GetDataFlowInput(0) == instance) {
67             return true;
68         }
69     }
70 
71     return false;
72 }
73 
HasAppendInstructions(Inst * instance,Loop * loop)74 bool HasAppendInstructions(Inst *instance, Loop *loop)
75 {
76     for (auto innerLoop : loop->GetInnerLoops()) {
77         if (HasAppendInstructions(instance, innerLoop)) {
78             return true;
79         }
80     }
81 
82     for (auto block : loop->GetBlocks()) {
83         if (HasAppendInstructions(instance, block)) {
84             return true;
85         }
86     }
87 
88     return false;
89 }
90 
GetStringBuilderAppendChainInstance(Inst * appendInstruction)91 Inst *GetStringBuilderAppendChainInstance(Inst *appendInstruction)
92 {
93     // For code like this:
94     //      sb.append(a).append(b).append(c) ...
95     // returns 'sb' instance for any append call in a chain
96 
97     ASSERT(IsStringBuilderAppend(appendInstruction));
98     ASSERT(appendInstruction->GetInputsCount() > 0);
99 
100     auto inputInst = appendInstruction->GetDataFlowInput(0);
101     while (IsStringBuilderAppend(inputInst)) {
102         ASSERT(inputInst->GetInputsCount() > 0);
103         inputInst = inputInst->GetDataFlowInput(0);
104     }
105 
106     return inputInst;
107 }
108 
CountStringBuilderAppendCalls(Inst * instance,BasicBlock * block)109 uint64_t CountStringBuilderAppendCalls(Inst *instance, BasicBlock *block)
110 {
111     uint64_t count = 0;
112     for (auto appendInstruction : block->Insts()) {
113         if (!IsStringBuilderAppend(appendInstruction)) {
114             continue;
115         }
116 
117         ASSERT(appendInstruction->GetInputsCount() > 0);
118         if (appendInstruction->GetDataFlowInput(0) == instance ||
119             GetStringBuilderAppendChainInstance(appendInstruction) == instance) {
120             count += appendInstruction->GetInputsCount() - 2U;  // -2 for sb instance and save state
121         }
122     }
123     return count;
124 }
125 
IsFieldStringBuilderBuffer(StoreObjectInst * storeObject)126 bool IsFieldStringBuilderBuffer(StoreObjectInst *storeObject)
127 {
128     auto field = storeObject->GetObjField();
129     auto runtime = storeObject->GetBasicBlock()->GetGraph()->GetRuntime();
130 
131     return runtime->IsFieldStringBuilderBuffer(field);
132 }
133 
IsFieldStringBuilderIndex(StoreObjectInst * storeObject)134 bool IsFieldStringBuilderIndex(StoreObjectInst *storeObject)
135 {
136     auto field = storeObject->GetObjField();
137     auto runtime = storeObject->GetBasicBlock()->GetGraph()->GetRuntime();
138 
139     return runtime->IsFieldStringBuilderIndex(field);
140 }
141 
ReplaceInitialBufferSizeConstantInlined(Inst * instance,uint64_t appendCallsCount)142 void ReserveStringBuilderBuffer::ReplaceInitialBufferSizeConstantInlined(Inst *instance, uint64_t appendCallsCount)
143 {
144     for (auto &user : instance->GetUsers()) {
145         auto storeObject = SkipSingleUserCheckInstruction(user.GetInst());
146         if (storeObject->GetOpcode() != Opcode::StoreObject) {
147             continue;
148         }
149 
150         if (!IsFieldStringBuilderBuffer(storeObject->CastToStoreObject())) {
151             continue;
152         }
153 
154         // storeObject instruction is
155         //  N.ref  StoreObject std.core.StringBuilder.buf instance, newArray
156         ASSERT(storeObject->GetInputsCount() > 1);
157         ASSERT(storeObject->GetDataFlowInput(0) == instance);
158 
159         // newArray instruction is
160         //  M.ref  NewArray objectClass, originalSize, saveState
161         auto newArray = storeObject->GetInput(1).GetInst()->CastToNewArray();
162         ASSERT(newArray->GetInputsCount() > 1);
163         auto originalSize = newArray->GetInput(1).GetInst();
164 
165         // Create Constant instruction for new Object[] array size
166         auto newSize = GetGraph()->FindOrCreateConstant(appendCallsCount);
167 
168         // Replace originalSize itself
169         newArray->SetInput(1, newSize);
170 
171         // Remove unused check instruction, because it won't be deleted automatically by Cleanup
172         if (originalSize->IsCheck() && !originalSize->HasUsers()) {
173             originalSize->ClearFlag(inst_flags::NO_DCE);
174         }
175 
176         isApplied_ = true;
177     }
178 }
179 
CreateIntrinsicStringIsCompressed(Graph * graph,Inst * arg,SaveStateInst * saveState)180 IntrinsicInst *CreateIntrinsicStringIsCompressed(Graph *graph, Inst *arg, SaveStateInst *saveState)
181 {
182     auto intrinsic = graph->CreateInstIntrinsic(graph->GetRuntime()->GetStringIsCompressedIntrinsicId());
183     ASSERT(intrinsic->RequireState());
184 
185     intrinsic->SetType(DataType::BOOL);
186     intrinsic->SetInputs(graph->GetAllocator(), {{arg, arg->GetType()}, {saveState, saveState->GetType()}});
187 
188     return intrinsic;
189 }
190 
191 using StringBuilderConstructorSignature = StringCtorType;
GetStringBuilderConstructorSignature(Inst * instance,Inst * & ctorCall,Inst * & ctorArg)192 StringBuilderConstructorSignature GetStringBuilderConstructorSignature(Inst *instance, Inst *&ctorCall, Inst *&ctorArg)
193 {
194     ASSERT(IsStringBuilderInstance(instance));
195 
196     for (auto &user : instance->GetUsers()) {
197         auto userInst = user.GetInst();
198         if (IsMethodStringBuilderDefaultConstructor(userInst)) {
199             ctorCall = userInst;
200             ctorArg = nullptr;
201             return StringBuilderConstructorSignature::UNKNOWN;
202         }
203         if (IsMethodStringBuilderConstructorWithStringArg(userInst)) {
204             ctorCall = userInst;
205             ASSERT(userInst->GetInputsCount() > 1);
206             ctorArg = userInst->GetDataFlowInput(1);
207             return StringBuilderConstructorSignature::STRING;
208         }
209         if (IsMethodStringBuilderConstructorWithCharArrayArg(userInst)) {
210             ctorCall = userInst;
211             ASSERT(userInst->GetInputsCount() > 1);
212             ctorArg = userInst->GetDataFlowInput(1);
213             return StringBuilderConstructorSignature::CHAR_ARRAY;
214         }
215     }
216 
217     UNREACHABLE();
218 }
219 
CreateInstructionNewObjectsArray(Graph * graph,Inst * ctorCall,uint64_t size)220 Inst *CreateInstructionNewObjectsArray(Graph *graph, Inst *ctorCall, uint64_t size)
221 {
222     auto runtime = graph->GetRuntime();
223     auto method = graph->GetMethod();
224 
225     // Create LoadAndInitClass instruction for Object[] type
226     auto objectsArrayClassId = runtime->GetClassOffsetObjectsArray(method);
227     auto loadClassObjectsArray = graph->CreateInstLoadAndInitClass(
228         DataType::REFERENCE, ctorCall->GetPc(), CopySaveState(graph, ctorCall->GetSaveState()),
229         TypeIdMixin {objectsArrayClassId, method}, runtime->ResolveType(method, objectsArrayClassId));
230     InsertBeforeWithSaveState(loadClassObjectsArray, ctorCall->GetSaveState());
231 
232     // Create Constant instruction for new Object[] array size
233     auto sizeConstant = graph->FindOrCreateConstant(size);
234 
235     // Create NewArray instruction for Object[] type and new array size
236     auto newObjectsArray = graph->CreateInstNewArray(DataType::REFERENCE, ctorCall->GetPc(), loadClassObjectsArray,
237                                                      sizeConstant, CopySaveState(graph, ctorCall->GetSaveState()),
238                                                      TypeIdMixin {objectsArrayClassId, method});
239     InsertBeforeWithSaveState(newObjectsArray, ctorCall->GetSaveState());
240 
241     return newObjectsArray;
242 }
243 
StoreStringBuilderConstructorArgument(Graph * graph,Inst * arg,Inst * newObjectsArray,Inst * ctorCall)244 void StoreStringBuilderConstructorArgument(Graph *graph, Inst *arg, Inst *newObjectsArray, Inst *ctorCall)
245 {
246     auto storeArray = graph->CreateInstStoreArray(DataType::REFERENCE, ctorCall->GetPc());
247     storeArray->SetInput(ARG_IDX_0, newObjectsArray);
248     storeArray->SetInput(ARG_IDX_1, graph->FindOrCreateConstant(0));
249     storeArray->SetInput(ARG_IDX_2, arg);
250     storeArray->SetNeedBarrier(true);
251     InsertBeforeWithSaveState(storeArray, ctorCall->GetSaveState());
252 }
253 
CreateStringBuilderConstructorArgumentLength(Graph * graph,Inst * arg,Inst * ctorCall)254 Inst *CreateStringBuilderConstructorArgumentLength(Graph *graph, Inst *arg, Inst *ctorCall)
255 {
256     auto lenArray = graph->CreateInstLenArray(DataType::INT32, ctorCall->GetPc(), arg);
257     InsertBeforeWithSaveState(lenArray, ctorCall->GetSaveState());
258 
259     auto argLength = graph->CreateInstShr(DataType::INT32, ctorCall->GetPc());
260     argLength->SetInput(ARG_IDX_0, lenArray);
261     argLength->SetInput(ARG_IDX_1, graph->FindOrCreateConstant(1));
262     InsertBeforeWithSaveState(argLength, ctorCall->GetSaveState());
263 
264     return argLength;
265 }
266 
CreateStringBuilderConstructorArgumentIsCompressed(Graph * graph,Inst * arg,Inst * ctorCall)267 Inst *CreateStringBuilderConstructorArgumentIsCompressed(Graph *graph, Inst *arg, Inst *ctorCall)
268 {
269     auto argIsCompressed =
270         CreateIntrinsicStringIsCompressed(graph, arg, CopySaveState(graph, ctorCall->GetSaveState()));
271     InsertBeforeWithSaveState(argIsCompressed, ctorCall->GetSaveState());
272 
273     return argIsCompressed;
274 }
275 
StoreStringBuilderBufferField(Graph * graph,Inst * buffer,Inst * instance,RuntimeInterface::ClassPtr klass,Inst * ctorCall)276 Inst *StoreStringBuilderBufferField(Graph *graph, Inst *buffer, Inst *instance, RuntimeInterface::ClassPtr klass,
277                                     Inst *ctorCall)
278 {
279     auto runtime = graph->GetRuntime();
280     auto field = runtime->GetFieldStringBuilderBuffer(klass);
281     auto storeObject = graph->CreateInstStoreObject(DataType::REFERENCE, ctorCall->GetPc(), instance, buffer,
282                                                     TypeIdMixin {runtime->GetFieldId(field), graph->GetMethod()}, field,
283                                                     runtime->IsFieldVolatile(field), true);
284     InsertBeforeWithSaveState(storeObject, ctorCall->GetSaveState());
285 
286     return storeObject;
287 }
288 
StoreStringBuilderIndexField(Graph * graph,Inst * index,Inst * instance,RuntimeInterface::ClassPtr klass,Inst * ctorCall)289 void StoreStringBuilderIndexField(Graph *graph, Inst *index, Inst *instance, RuntimeInterface::ClassPtr klass,
290                                   Inst *ctorCall)
291 {
292     auto runtime = graph->GetRuntime();
293     auto field = runtime->GetFieldStringBuilderIndex(klass);
294     auto storeObject = graph->CreateInstStoreObject(DataType::INT32, ctorCall->GetPc(), instance, index,
295                                                     TypeIdMixin {runtime->GetFieldId(field), graph->GetMethod()}, field,
296                                                     runtime->IsFieldVolatile(field), false);
297     InsertBeforeWithSaveState(storeObject, ctorCall->GetSaveState());
298 }
299 
StoreStringBuilderLengthField(Graph * graph,Inst * length,Inst * instance,RuntimeInterface::ClassPtr klass,Inst * ctorCall)300 void StoreStringBuilderLengthField(Graph *graph, Inst *length, Inst *instance, RuntimeInterface::ClassPtr klass,
301                                    Inst *ctorCall)
302 {
303     auto runtime = graph->GetRuntime();
304     auto field = runtime->GetFieldStringBuilderLength(klass);
305     auto storeObject = graph->CreateInstStoreObject(DataType::INT32, ctorCall->GetPc(), instance, length,
306                                                     TypeIdMixin {runtime->GetFieldId(field), graph->GetMethod()}, field,
307                                                     runtime->IsFieldVolatile(field), false);
308     InsertBeforeWithSaveState(storeObject, ctorCall->GetSaveState());
309 }
310 
StoreStringBuilderIsCompressedField(Graph * graph,Inst * isCompressed,Inst * instance,RuntimeInterface::ClassPtr klass,Inst * ctorCall)311 void StoreStringBuilderIsCompressedField(Graph *graph, Inst *isCompressed, Inst *instance,
312                                          RuntimeInterface::ClassPtr klass, Inst *ctorCall)
313 {
314     auto runtime = graph->GetRuntime();
315     auto field = runtime->GetFieldStringBuilderCompress(klass);
316     auto storeObject = graph->CreateInstStoreObject(DataType::BOOL, ctorCall->GetPc(), instance, isCompressed,
317                                                     TypeIdMixin {runtime->GetFieldId(field), graph->GetMethod()}, field,
318                                                     runtime->IsFieldVolatile(field), false);
319     InsertBeforeWithSaveState(storeObject, ctorCall->GetSaveState());
320 }
321 
ReplaceInitialBufferSizeConstantNotInlined(Inst * instance,uint64_t appendCallsCount)322 void ReserveStringBuilderBuffer::ReplaceInitialBufferSizeConstantNotInlined(Inst *instance, uint64_t appendCallsCount)
323 {
324     Inst *ctorCall = nullptr;
325     Inst *ctorArg = nullptr;
326     StringBuilderConstructorSignature ctorSignature = GetStringBuilderConstructorSignature(instance, ctorCall, ctorArg);
327 
328     ASSERT(ctorCall != nullptr);
329 
330     // Create NewArray instruction for Object[] type and new array size
331     auto newObjectsArray = CreateInstructionNewObjectsArray(GetGraph(), ctorCall, appendCallsCount);
332 
333     // Create StoreArray instruction to store constructor argument
334     Inst *argLength = nullptr;
335     Inst *argIsCompressed = nullptr;
336     Inst *argString = ctorArg;
337 
338     switch (ctorSignature) {
339         case StringBuilderConstructorSignature::UNKNOWN:
340             argLength = GetGraph()->FindOrCreateConstant(0);
341             argIsCompressed = GetGraph()->FindOrCreateConstant(1);
342             break;
343 
344         case StringBuilderConstructorSignature::CHAR_ARRAY:
345             ASSERT(ctorArg != nullptr);
346             // Create string object out of char[] array
347             argString = GetGraph()->CreateInstInitString(DataType::REFERENCE, ctorCall->GetPc(), ctorArg,
348                                                          CopySaveState(GetGraph(), ctorCall->GetSaveState()),
349                                                          StringCtorType::CHAR_ARRAY);
350             InsertBeforeWithSaveState(argString, ctorCall->GetSaveState());
351 
352             // Store string object in Objects[] array
353             StoreStringBuilderConstructorArgument(GetGraph(), argString, newObjectsArray, ctorCall);
354 
355             argLength = CreateStringBuilderConstructorArgumentLength(GetGraph(), argString, ctorCall);
356             argIsCompressed = CreateStringBuilderConstructorArgumentIsCompressed(GetGraph(), argString, ctorCall);
357             ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), argString, argIsCompressed);
358             break;
359 
360         case StringBuilderConstructorSignature::STRING:
361             // Store constructor argument in Objects[] array
362             StoreStringBuilderConstructorArgument(GetGraph(), argString, newObjectsArray, ctorCall);
363 
364             argLength = CreateStringBuilderConstructorArgumentLength(GetGraph(), argString, ctorCall);
365             argIsCompressed = CreateStringBuilderConstructorArgumentIsCompressed(GetGraph(), argString, ctorCall);
366             ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), argString, argIsCompressed);
367             break;
368 
369         default:
370             UNREACHABLE();
371     }
372 
373     auto stringBuilderClass = GetObjectClass(instance);
374     ASSERT(stringBuilderClass != nullptr);
375 
376     // Create StoreObject instruction to store Object[] array
377     auto storeObjectBuffer =
378         StoreStringBuilderBufferField(GetGraph(), newObjectsArray, instance, stringBuilderClass, ctorCall);
379     ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), newObjectsArray, storeObjectBuffer);
380 
381     // Create StoreObject instruction to store initial buffer index
382     StoreStringBuilderIndexField(
383         GetGraph(),
384         GetGraph()->FindOrCreateConstant(ctorSignature == StringBuilderConstructorSignature::UNKNOWN ? 0 : 1), instance,
385         stringBuilderClass, ctorCall);
386 
387     // Create StoreObject instruction to store initial string length
388     StoreStringBuilderLengthField(GetGraph(), argLength, instance, stringBuilderClass, ctorCall);
389 
390     // Create StoreObject instruction to store initial compression flag
391     StoreStringBuilderIsCompressedField(GetGraph(), argIsCompressed, instance, stringBuilderClass, ctorCall);
392 
393     ctorCall->ClearFlag(inst_flags::NO_DCE);
394     isApplied_ = true;
395 }
396 
CountStringBuilderConstructorArgumentsInlined(Inst * instance)397 uint64_t CountStringBuilderConstructorArgumentsInlined(Inst *instance)
398 {
399     for (auto &user : instance->GetUsers()) {
400         auto storeObject = SkipSingleUserCheckInstruction(user.GetInst());
401         if (storeObject->GetOpcode() != Opcode::StoreObject) {
402             continue;
403         }
404 
405         if (!IsFieldStringBuilderIndex(storeObject->CastToStoreObject())) {
406             continue;
407         }
408 
409         ASSERT(storeObject->GetInputsCount() > 1);
410         auto initialIndex = storeObject->GetDataFlowInput(1);
411         if (!initialIndex->IsConst()) {
412             continue;
413         }
414 
415         return initialIndex->CastToConstant()->GetInt64Value();
416     }
417     return INVALID_COUNT;
418 }
419 
CountStringBuilderConstructorArgumentsNotInlined(Inst * instance)420 uint64_t CountStringBuilderConstructorArgumentsNotInlined(Inst *instance)
421 {
422     for (auto &user : instance->GetUsers()) {
423         auto userInst = user.GetInst();
424         if (IsMethodStringBuilderDefaultConstructor(userInst)) {
425             return 0;
426         }
427         if (IsMethodStringBuilderConstructorWithStringArg(userInst) ||
428             IsMethodStringBuilderConstructorWithCharArrayArg(userInst)) {
429             return 1;
430         }
431     }
432     return INVALID_COUNT;
433 }
434 
GetLoopPostExits(Loop * loop)435 ArenaVector<BasicBlock *> GetLoopPostExits(Loop *loop)
436 {
437     // Find blocks immediately following after the loop
438 
439     ArenaVector<BasicBlock *> postExits {loop->GetHeader()->GetGraph()->GetLocalAllocator()->Adapter()};
440     for (auto block : loop->GetBlocks()) {
441         for (auto succ : block->GetSuccsBlocks()) {
442             if (succ->GetLoop() == loop->GetOuterLoop()) {
443                 postExits.push_back(succ);
444             }
445         }
446     }
447 
448     return postExits;
449 }
450 
FindLongestPathLength(Inst * instance,Loop * loop,Marker visited)451 uint64_t ReserveStringBuilderBuffer::FindLongestPathLength(Inst *instance, Loop *loop, Marker visited)
452 {
453     // The problem of calculating a number of append instructions calls of a given string builder instance inside a
454     // given loop is equivalent to the longest path problem between loop header and exit for loop CFG weighted by
455     // the number of append instructions within each block.
456     // Use special 'INVALID_COUNT' value for uncountable loops
457 
458     uint64_t postExitAppendCallsCountMax = 0;
459     for (auto postExit : GetLoopPostExits(loop)) {
460         postExitAppendCallsCountMax =
461             std::max(postExitAppendCallsCountMax, FindLongestPathLength(instance, postExit, visited));
462     }
463     if (postExitAppendCallsCountMax == INVALID_COUNT) {
464         return INVALID_COUNT;
465     }
466 
467     // Early exit for loops containing no calls to append instructions
468     if (!HasAppendInstructions(instance, loop)) {
469         return postExitAppendCallsCountMax;
470     }
471 
472     uint64_t loopIterationsCount = GetLoopIterationsCount(loop);
473     if (loopIterationsCount == INVALID_COUNT) {
474         return INVALID_COUNT;
475     }
476 
477     // Set stop condition
478     auto stopAtOuterLoopBlock = [loop](BasicBlock *block) { return block->GetLoop() == loop->GetOuterLoop(); };
479     // Find the longest path within current loop: i.e longest path from header to exit blocks
480     uint64_t insideLoopAppendCallsCount =
481         FindLongestPathLength(instance, loop->GetHeader(), visited, stopAtOuterLoopBlock);
482     if (insideLoopAppendCallsCount == INVALID_COUNT) {
483         return INVALID_COUNT;
484     }
485 
486     // Weight of loop: the longest path from header to exit times the loop count value plus the longest path from post
487     // exit blocks
488     auto loopAppendCallsCount =
489         BoundsRange::MulWithOverflowCheck(loopIterationsCount, insideLoopAppendCallsCount).value_or(INVALID_COUNT);
490     return BoundsRange::AddWithOverflowCheck(loopAppendCallsCount, postExitAppendCallsCountMax).value_or(INVALID_COUNT);
491 }
492 
FindLongestPathLength(Inst * instance,BasicBlock * block,Marker visited,const BlockPredicate & stopAtBlock)493 uint64_t ReserveStringBuilderBuffer::FindLongestPathLength(Inst *instance, BasicBlock *block, Marker visited,
494                                                            const BlockPredicate &stopAtBlock)
495 {
496     // The problem of calculating a number of append instructions calls of a given string builder instance is equivalent
497     // to the longest path problem between instance block and latest user block for CFG weighted by the number of append
498     // instructions within each block.
499 
500     block->SetMarker(visited);
501     uint64_t appendCallsCount = 0;
502 
503     // Recursively find longest path from block successors
504     for (auto succ : block->GetSuccsBlocks()) {
505         if (stopAtBlock(succ)) {
506             continue;
507         }
508         if (succ->IsMarked(visited)) {
509             // Found already visited successor: take its weight from map
510             appendCallsCount = std::max(appendCallsCount, blockWeightsMap_[succ]);
511         } else if (succ->GetLoop() == block->GetLoop()) {
512             // Same loop case
513             appendCallsCount = std::max(appendCallsCount, FindLongestPathLength(instance, succ, visited, stopAtBlock));
514         } else if (succ->GetLoop()->GetOuterLoop() == block->GetLoop()) {
515             // Edge from block to succ cr1osses loop boundary: e.g block is loop preheader, succ is loop header
516             ASSERT(succ == succ->GetLoop()->GetHeader());
517             appendCallsCount =
518                 succ->GetLoop()->IsIrreducible()
519                     ? INVALID_COUNT
520                     : std::max(appendCallsCount, FindLongestPathLength(instance, succ->GetLoop(), visited));
521         } else if (succ->GetLoop() == block->GetLoop()->GetOuterLoop()) {
522             // Edge from block to succ crosses loop boundary: e.g block is loop exit, succ is loop post exit
523             // Do nothing, since we already counted loop at preheader/header boundary
524         } else {
525             // Invalid/unexpected/unsupported CFG
526             return INVALID_COUNT;
527         }
528     }
529 
530     // Propagate 'INVALID_COUNT' value to the caller
531     if (appendCallsCount == INVALID_COUNT) {
532         return INVALID_COUNT;
533     }
534 
535     // Count current block weight, add longest path from successors, and store into map
536     appendCallsCount =
537         BoundsRange::AddWithOverflowCheck(appendCallsCount, compiler::CountStringBuilderAppendCalls(instance, block))
538             .value_or(INVALID_COUNT);
539     blockWeightsMap_[block] = appendCallsCount;
540     return appendCallsCount;
541 }
542 
CountStringBuilderAppendCalls(Inst * instance)543 uint64_t ReserveStringBuilderBuffer::CountStringBuilderAppendCalls(Inst *instance)
544 {
545     // The problem of calculating a number of append instruction calls of a given string builder instance is
546     // equivalent to the longest path problem between instance and its latest user over a CFG weighted by the number
547     // of append instructions within each block.
548 
549     blockWeightsMap_.clear();
550     MarkerHolder visited {GetGraph()};
551 
552     // Traverse graph starting from instance block and find the longest path
553     auto appendCallsCount = FindLongestPathLength(instance, instance->GetBasicBlock(), visited.GetMarker());
554 
555     // Check if constructor is inlined or not
556     bool ctorIsInlined = !HasUser(instance, [](auto &user) {
557         auto userInst = user.GetInst();
558         return IsMethodStringBuilderDefaultConstructor(userInst) ||
559                IsMethodStringBuilderConstructorWithStringArg(userInst) ||
560                IsMethodStringBuilderConstructorWithCharArrayArg(userInst);
561     });
562     // Increase buffer size if constructor has args
563     if (ctorIsInlined) {
564         appendCallsCount =
565             BoundsRange::AddWithOverflowCheck(appendCallsCount, CountStringBuilderConstructorArgumentsInlined(instance))
566                 .value_or(INVALID_COUNT);
567     } else {
568         appendCallsCount = BoundsRange::AddWithOverflowCheck(appendCallsCount,
569                                                              CountStringBuilderConstructorArgumentsNotInlined(instance))
570                                .value_or(INVALID_COUNT);
571     }
572 
573     return appendCallsCount;
574 }
575 
GetBufferSizeMin(Inst * instance) const576 uint64_t ReserveStringBuilderBuffer::GetBufferSizeMin(Inst *instance) const
577 {
578     // Returns minimal StringBuilder internal buffer size:
579     //      - if instance has Return/Store* users or passed as an argument to a function, return initial buffer size
580     //      - return zero (any buffer size allowed), otherwise
581 
582     for (auto &user : instance->GetUsers()) {
583         auto userInst = SkipSingleUserCheckInstruction(user.GetInst());
584         if (userInst->IsReturn() || userInst->GetOpcode() == Opcode::StoreStatic ||
585             userInst->GetOpcode() == Opcode::StoreArray || userInst->GetOpcode() == Opcode::StoreArrayPair) {
586             return SB_INITIAL_BUFFER_SIZE;
587         }
588 
589         if ((userInst->GetOpcode() == Opcode::StoreObject || userInst->GetOpcode() == Opcode::StoreObjectPair) &&
590             userInst->GetDataFlowInput(0) != instance) {
591             return SB_INITIAL_BUFFER_SIZE;
592         }
593 
594         if (userInst->GetOpcode() == Opcode::CallStatic || userInst->GetOpcode() == Opcode::CallVirtual) {
595             if (!IsMethodStringBuilderDefaultConstructor(userInst) &&
596                 !IsMethodStringBuilderConstructorWithCharArrayArg(userInst) &&
597                 !IsMethodStringBuilderConstructorWithStringArg(userInst) && !IsStringBuilderAppend<true>(userInst) &&
598                 !IsStringBuilderToString(userInst)) {
599                 return SB_INITIAL_BUFFER_SIZE;
600             }
601         }
602     }
603 
604     return 0;
605 }
606 
GetBufferSizeMax() const607 uint64_t ReserveStringBuilderBuffer::GetBufferSizeMax() const
608 {
609     // Returns maximal StringBuilder internal buffer size
610     // NOTE(mivanov): The algorithm computes upper bound of append calls count.
611     //                In case we can prove that this number is constant at runtime (e.g, does not depend on CFG path
612     //                taken be a program), we can allow any buffer size by returning UINT64_MAX.
613 
614     return tlabArraySizeMax_;
615 }
616 
RunImpl()617 bool ReserveStringBuilderBuffer::RunImpl()
618 {
619     isApplied_ = false;
620 
621     if (!GetGraph()->IsAnalysisValid<BoundsAnalysis>()) {
622         GetGraph()->RunPass<BoundsAnalysis>();
623     }
624 
625     for (auto block : GetGraph()->GetBlocksRPO()) {
626         for (auto instance : block->Insts()) {
627             if (!IsStringBuilderInstance(instance)) {
628                 continue;
629             }
630 
631             // Skip instance if it used in resolved context or in phi instruction
632             if (HasUser(instance, [](auto &user) {
633                     auto opcode = user.GetInst()->GetOpcode();
634                     return opcode == Opcode::CallResolvedStatic || opcode == Opcode::CallResolvedVirtual ||
635                            opcode == Opcode::Phi || opcode == Opcode::CatchPhi;
636                 })) {
637                 continue;
638             }
639 
640             auto appendCallsCount = CountStringBuilderAppendCalls(instance);
641             // Check if number of calls are successfully counted and fits allowed range
642             if (appendCallsCount == INVALID_COUNT || appendCallsCount <= GetBufferSizeMin(instance) ||
643                 appendCallsCount > GetBufferSizeMax()) {
644                 continue;
645             }
646 
647             // Check if constructor is inlined or not
648             bool ctorIsInlined = !HasUser(instance, [](auto &user) {
649                 auto userInst = user.GetInst();
650                 return IsMethodStringBuilderDefaultConstructor(userInst) ||
651                        IsMethodStringBuilderConstructorWithStringArg(userInst) ||
652                        IsMethodStringBuilderConstructorWithCharArrayArg(userInst);
653             });
654             if (ctorIsInlined) {
655                 // Patch inlined constructor
656                 ReplaceInitialBufferSizeConstantInlined(instance, appendCallsCount);
657             } else {
658                 // Inline constructor manually and patch
659                 ReplaceInitialBufferSizeConstantNotInlined(instance, appendCallsCount);
660             }
661         }
662     }
663 
664     return isApplied_;
665 }
666 
667 }  // namespace ark::compiler
668