1 /**
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "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::CallResolvedLaunchStatic || opcode == Opcode::CallResolvedLaunchVirtual ||
636 opcode == Opcode::Phi || opcode == Opcode::CatchPhi;
637 })) {
638 continue;
639 }
640
641 auto appendCallsCount = CountStringBuilderAppendCalls(instance);
642 // Check if number of calls are successfully counted and fits allowed range
643 if (appendCallsCount == INVALID_COUNT || appendCallsCount <= GetBufferSizeMin(instance) ||
644 appendCallsCount > GetBufferSizeMax()) {
645 continue;
646 }
647
648 // Check if constructor is inlined or not
649 bool ctorIsInlined = !HasUser(instance, [](auto &user) {
650 auto userInst = user.GetInst();
651 return IsMethodStringBuilderDefaultConstructor(userInst) ||
652 IsMethodStringBuilderConstructorWithStringArg(userInst) ||
653 IsMethodStringBuilderConstructorWithCharArrayArg(userInst);
654 });
655 if (ctorIsInlined) {
656 // Patch inlined constructor
657 ReplaceInitialBufferSizeConstantInlined(instance, appendCallsCount);
658 } else {
659 // Inline constructor manually and patch
660 ReplaceInitialBufferSizeConstantNotInlined(instance, appendCallsCount);
661 }
662 }
663 }
664
665 return isApplied_;
666 }
667
668 } // namespace ark::compiler
669