• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# String Builder optimizations
2
3## Overview
4
5This set of optimizations targets String Builder usage specifics.
6
7## Rationality
8
9String Builder is used to construct a string out of smaller pieces. In some cases it is optimal to use String Builder to collect intermediate parts, but in other cases we prefer naive string concatenation, due to an overhead introduced by String Builder object.
10
11## Dependence
12
13* BoundsAnalysis
14* AliasAnalysis
15* LoopAnalysis
16* DominatorsTree
17
18## Algorithms
19
20**Remove unnecessary String Builder**
21
22Example:
23```TS
24let input: String = ...
25let sb = new StringBuilder(input)
26let output = sb.toString()
27```
28Since there are no `StringBuilder::append()`-calls in between `constructor` and `toString()`-call, `input` string is equal to `output` string. So, the example code is equivalent to
29```TS
30let input: String = ...
31let output = input
32```
33
34**Replace String Builder with string concatenation**
35
36String concatenation expressed as a plus-operator over string operands turned into a String Builder usage by a frontend.
37
38Example:
39```TS
40let a, b: String
41...
42let output = a + b
43```
44Frontend output equivalent:
45```TS
46let a, b: String
47...
48let sb = new StringBuilder()
49sb.append(a)
50sb.append(b)
51let output = sb.toString()
52```
53The overhead of String Builder object exceeds the benefits of its usage (comparing to a naive string concatenation) for a small number of operands (two in the example above). So, we replace String Builder in such a cases back to naive string concatenation.
54
55**Optimize concatenation loops**
56
57Consider a string accumulation loop example:
58```TS
59let inputs: string[] = ... // array of strings
60let output = ""
61for (let input in inputs)
62    output += input
63```
64Like in **Replace String Builder with string concatenation** section, frontend replaces string accumulation `output += input` with a String Builder usage, resulting in a huge performance degradation (comparing to a naive string concatenation), because *at each loop iteration* String Builder object is constructed, used to append two operands, builds resulting string, and discarded.
65
66The equivalent code looks like the following:
67```TS
68let inputs: string[] = ... // array of strings
69let output = ""
70for (let input in inputs) {
71    let sb = new StringBuilder()
72    sb.append(output)
73    sb.append(input)
74    output = sb.toString()
75}
76```
77To optimize cases like this, we implement the following equivalent transformation:
78```TS
79let inputs: string[] = ... // array of strings
80let sb = new StringBuilder()
81for (let input in inputs) {
82    sb.append(input)
83}
84let output = sb.toString()
85```
86
87**Merge StringBuilder::append calls chain**
88
89Consider a code sample like the following:
90
91```TS
92// String semantics
93let result = str0 + str1
94
95// StringBuilder semantics
96let sb = new StringBuilder()
97sb.append(str0)
98sb.append(str1)
99let result = sb.toString()
100```
101
102Here we call `StringBuilder::append` twice. Proposed algorith merges them into a single call to (in this case) `StringBuilder::append2`. Merging up to 4 consecutive calls supported.
103
104Optimized example is equivalent to:
105
106```TS
107// StringBuilder semantics
108let sb = new StringBuilder()
109sb.append2(str0, str1)
110let result = sb.toString()
111```
112
113**Merge StringBuilder objects chain**
114
115Consider a code sample like the following:
116
117```TS
118// String semantics
119let result0 = str00 + str01
120let result = result0 + str10 + str11
121
122// StringBuilder semantics
123let sb0 = new StringBuilder()
124sb0.append(str00)
125sb0.append(str01)
126
127let sb1 = new StringBuilder()
128sb1.append(sb0.toString())
129sb1.append(str10)
130sb1.append(str11)
131let result = sb1.toString()
132```
133
134Here we construct `result0` and `sb0` and use it only once as a first argument of the concatenation which comes next. As we can see, two `StringBuilder` objects created. Instead, we can use only one of them as follows:
135
136```TS
137// StringBuilder semantics
138let sb0 = new StringBuilder()
139sb0.append(str00)
140sb0.append(str01)
141sb0.append(str10)
142sb0.append(str11)
143let result = sb0.toString()
144```
145
146Proposed algorithm merges consecutive chain of `StringBuilder` objects into a single object (if possible).
147
148## Pseudocode
149
150**Complete algorithm**
151
152```C#
153function SimplifyStringBuilder(graph: Graph)
154    foreach loop in graph
155        OptimizeStringConcatenation(loop)
156
157    OptimizeStringBuilderChain()
158
159    foreach block in graph (in RPO)
160        OptimizeStringBuilderToString(block)
161        OptimizeStringConcatenation(block)
162        OptimizeStringBuilderAppendChain(block)
163```
164
165Below we describe the algorithm in more details
166
167**Remove unnecessary String Builder**
168
169The algorithm works as follows: first we search for all the StringBuilder instances in a basic block, then we replace all their toString-call usages with instance constructor argument until we meet any other usage in RPO.
170```C#
171function OptimizeStringBuilderToString(block: BasicBlock)
172    foreach ctor being StringBuilder constructor with String argument in block
173        let instance be StringBuilder instance of ctor-call
174        let arg be ctor-call string argument
175        foreach usage of instance (in RPO)
176            if usage is toString-call
177                replace usage with arg
178            else
179                break
180        if instance is not used
181            remove ctor from block
182            remove instance from block
183```
184**Replace String Builder with string concatenation**
185
186The algorithm works as follows: first we search for all the StringBuilder instances in a basic block, then we check if the use of instance matches concatenation pattern for 2, 3, or 4 arguments. If so, we replace the whole use of StringBuilder object with concatenation intrinsics.
187```C#
188function OptimizeStringConcatenation(block: BasicBlock)
189    foreach ctor being StringBuilder default constructor in block
190        let instance be StringBuilder instance of ctor-call
191        let match = MatchConcatenation(instance)
192        let appendCount = match.appendCount be number of append-calls of instance
193        let append = match.append be an array of append-calls of instance
194        let toStringCall = match.toStringCall be toString-call of instance
195        let concat01 = ConcatIntrinsic(append[0].input(1), append[1].input(1))
196        remove append[0] from block
197        remove append[1] from block
198        switch appendCount
199            case 2
200                replace toStringCall with concat01
201                break
202            case 3
203                let concat012 = ConcatIntrinsic(concat01, append[2].input(1))
204                remove append[2] from block
205                replace toStringCall with concat012
206                break
207            case 4
208                let concat23 = ConcatIntrinsic(append[2].input(1), append[3].input(1))
209                let concat0123 = ConcatIntrinsic(concat01, concat23)
210                remove append[2] from block
211                remove append[3] from block
212                replace toStringCall with concat0123
213        remove toStringCall from block
214        remove ctor from block
215        remove instance from block
216
217function ConcatIntrinsic(arg0, arg1): IntrinsicInst
218    return concatenation intrinsic for arg0 and arg1
219
220type Match
221    toStringCall: CallInst
222    appendCount: Integer
223    append: array of CallInst
224
225function MatchConcatenation(instance: StringBuilder): Match
226    let match: Match
227    foreach usage of instance
228        if usage is toString-call
229            set match.toStringCall = usage
230        elif usage is append-call
231            add usage to match.append array
232            increment match.appendCount
233    return match
234```
235**Optimize concatenation loops**
236
237The algorithm works as follows: first we recursively process all the inner loops of current loop, then we search for string concatenation patterns within a current loop. For each pattern found we reconnect StringBuilder usage instructions in a correct way (making them point to the only one instance we have chosen), move chosen String Builder object creation and initial string value appending to a loop pre-header, move chosen StringBuilder object toString-call to loop exit block. We cleanup unused instructions at the end.
238```C#
239function OptimizeStringConcatenation(loop: Loop)
240    foreach innerLoop being inner loop of loop
241        OptimizeStringConcatenation(innerLoop)
242    let matches = MatchConcatenationLoop(loop)
243    foreach match in matches)
244        ReconnectInstructions(match)
245        HoistInstructionsToPreHeader(match)
246        HoistInstructionsToExitBlock(match)
247    }
248    Cleanup(loop, matches)
249
250type Match
251    accValue: PhiInst
252    initialValue: Inst
253    // instructions to be hoisted to preheader
254    preheader: type
255        instance: Inst
256        appendAccValue: IntrinsicInst
257    // instructions to be left inside loop
258    loop: type
259        appendIntrinsics: array of IntrinsicInst
260    // instructions to be deleted
261    temp: array of type
262        toStringCall: Inst
263        instance: Inst
264        appendAccValue: IntrinsicInst
265    // instructions to be hoisted to exit block
266    exit: type
267        toStringCall: Inst
268
269function MatchConcatenationLoop(loop: Loop)
270    let matches: array of Match
271    foreach accValue being string accumulator in a loop
272        let match: Match
273        foreach instance being StringBuilder instance used to update accValue (in RPO)
274            if match is empty
275                // Fill preheader and exit parts of a match
276                set match.accValue = accValue
277                set match.initialValue = FindInitialValue(accValue)
278                set match.exit.toStringCall = FindToStringCall(instance)
279                set match.preheader.instance = instance
280                set match.preheader.appendAccValue = FindAppendIntrinsic(instance, accValue)
281                // Init loop part of a match
282                add other append instrinsics to match.loop.appendInstrinsics array
283            else
284                // Fill loop and temporary parts of a match
285                let temp: TemporaryInstructions
286                set temp.instance = instance
287                set temp.toStringCall = FindToStringCall(instance)
288                foreach appendIntrinsic in FindAppendIntrinsics(instance)
289                    if appendIntrinsic.input(1) is accValue
290                        set temp.appendAccValue = appendIntrinsic
291                    else
292                        add appendIntricsic to match.loop.appendInstrinsics array
293                add temp to match.temp array
294        add match to matches array
295    return matches
296
297function ReconnectInstructions(match: Match)
298    match.preheader.appendAcc.setInput(0, match.preheader.instance)
299    match.preheader.appendAcc.setInput(1, be match.initialValue)
300    match.exit.toStringCall.setInput(0, match.preheader.instance)
301    foreach user being users of match.accValue outside loop
302        user.replaceInput(match.accValue, match.exit.toStringCall)
303
304function HoistInstructionsToPreHeader(match: Match)
305    foreach inst in match.preheader
306        hoist inst to loop preheader
307    fix broken save states
308
309function HoistInstructionsToExitBlock(match: Match)
310    let exitBlock be to exit block of loop
311    hoist match.exit.toStringCall to exitBlock
312    foreach input being inputs of match.exit.toStringCall inside loop
313        hoist input to exitBlock
314
315function Cleanup(loop: Loop, matches: array of Match)
316    foreach block in loop
317        fix save states in block
318    foreach match in matches
319        foreach temp in match.temp
320            foreach inst in temp
321                remove inst
322    foreach block in loop
323        foreach phi in block
324            if phi is not used
325                remove phi from block
326```
327
328**Merge StringBuilder::append calls chain**
329
330The algorithm works as follows. First, we find all the `StringBuilder` objects and their `append` calls in a current `block`. Second, we split vector of calls found into a groups of 2, 3, or 4 elements. We replace each group by a corresponding `StringBuilder::appendN` call.
331
332```C#
333function OptimizeStringBuilderAppendChain(block: BasicBlock)
334    foreach [instance, calls] being StringBuilder instance and its vector of append calls in block
335        foreach group being consicutive subvector of 2, 3, or 4 from calls
336            replace group with instance.appendN call
337```
338
339**Merge StringBuilder objects chain**
340
341The algorithm works as follows. The algorithm traverses blocks of graph in Post Order, and instructions of each block in Reverse Order, this allows us iteratively merge potentially long chains of `StringBuilder` objects into a single object. First, we search a pairs `[instance, inputInstance]` of `StringBuilder` objects which we can merge, merge condition is: last call to `inputInstance.toString()` appended as a first argument to `instance`. Then we remove first call to `StringBuilder::append` from `instance` and last call to `StringBuilder::toString` from `inputInstance`. We retarget remaining calls of `instance` to `inputInstance`.
342
343```C#
344function OptimizeStringBuilderChain()
345    foreach block in graph in PO
346        foreach two objects [instance, inputInstance] being a consicutive pair of Stringbuilders in block in reverse order
347            if CanMerge(instance, inputInstance)
348                let firstAppend be 1st StringBuilder::append call of instance
349                let lastToString be last StringBuilder::toString call of inputInstance
350                remove firstAppend from instance users
351                remove lastToString from inputInstance users
352                foreach call being user of instance
353                    call.setInput(0, inputInstance) // retarget append call to inputInstance
354```
355
356## Examples
357
358**Remove unnecessary String Builder**
359
360ETS function example:
361```TS
362function toString0(str: String): String {
363    return new StringBuilder(str).toString();
364}
365```
366
367IR before transformation:
368
369(Save state and null check instructions are skipped for simplicity)
370```
371Method: std.core.String ETSGLOBAL::toString0(std.core.String)
372
373BB 1
374prop: start
375    0.ref  Parameter                  arg 0 -> (v5)
376succs: [bb 0]
377
378BB 0  preds: [bb 1]
379prop:
380    3.ref  LoadAndInitClass 'std.core.StringBuilder' ss -> (v4)
381    4.ref  NewObject 15300            v3, ss -> (v5, v10)
382    5.void CallStatic 51211 std.core.StringBuilder::<ctor> v4, v0, ss
383   10.ref  CallVirtual 51332 std.core.StringBuilder::toString v4, ss -> (v11)
384   11.ref  Return                     v10
385succs: [bb 2]
386
387BB 2  preds: [bb 0]
388prop: end
389```
390IR after transformation:
391```
392Method: std.core.String ETSGLOBAL::toString0(std.core.String)
393
394BB 1
395prop: start
396    0.ref  Parameter                  arg 0 -> (v10)
397succs: [bb 0]
398
399BB 0  preds: [bb 1]
400prop:
401   10.ref  Return                     v0
402succs: [bb 2]
403
404BB 2  preds: [bb 0]
405prop: end
406```
407
408**Replace String Builder with string concatenation**
409
410ETS function example:
411```TS
412function concat0(a: String, b: String): String {
413    return a + b;
414}
415```
416IR before transformation:
417```
418Method: std.core.String ETSGLOBAL::concat0(std.core.String, std.core.String)
419
420BB 1
421prop: start
422    0.ref  Parameter                  arg 0 -> (v10)
423    1.ref  Parameter                  arg 1 -> (v13)
424succs: [bb 0]
425
426BB 0  preds: [bb 1]
427prop:
428    4.ref  LoadAndInitClass 'std.core.StringBuilder' ss -> (v5)
429    5.ref  NewObject 11355            v4, ss -> (v13, v10, v6)
430    6.void CallStatic 60100 std.core.StringBuilder::<ctor> v5, ss
431   10.ref  Intrinsic.StdCoreSbAppendString v5, v0, ss
432   13.ref  Intrinsic.StdCoreSbAppendString v5, v1, ss
433   16.ref  CallStatic 60290 std.core.StringBuilder::toString v5, ss -> (v17)
434   17.ref  Return                     v16
435succs: [bb 2]
436
437BB 2  preds: [bb 0]
438prop: end
439```
440IR after transformation:
441```
442Method: std.core.String ETSGLOBAL::concat0(std.core.String, std.core.String)
443
444BB 1
445prop: start
446    0.ref  Parameter                  arg 0 -> (v18)
447    1.ref  Parameter                  arg 1 -> (v18)
448succs: [bb 0]
449
450BB 0  preds: [bb 1]
451prop:
452   18.ref  Intrinsic.StdCoreStringConcat2 v0, v1, ss -> (v17)
453   17.ref  Return                     v18
454succs: [bb 2]
455
456BB 2  preds: [bb 0]
457prop: end
458```
459
460**Optimize concatenation loops**
461
462ETS function example:
463```TS
464function concat_loop0(a: String, n: int): String {
465    let str: String = "";
466    for (let i = 0; i < n; ++i)
467        str = str + a;
468    return str;
469}
470```
471IR before transformation:
472```
473Method: std.core.String ETSGLOBAL::concat_loop0(std.core.String, i32)
474
475BB 4
476prop: start
477    0.ref  Parameter                  arg 0 -> (v9p)
478    1.i32  Parameter                  arg 1 -> (v10p)
479    3.i64  Constant                   0x0 -> (v7p)
480   30.i64  Constant                   0x1 -> (v29)
481succs: [bb 0]
482
483BB 0  preds: [bb 4]
484prop: prehead
485    4.ref  LoadString 63726           v5 -> (v8p)
486succs: [bb 3]
487
488BB 3  preds: [bb 0, bb 2]
489prop: head, loop 1, depth 1
490   7p.i32  Phi                        v3(bb0), v29(bb2) -> (v29, v13)
491   8p.ref  Phi                        v4(bb0), v28(bb2) -> (v31, v12)
492   9p.ref  Phi                        v0(bb0), v9p(bb2) -> (v12, v9p, v25)
493  10p.i32  Phi                        v1(bb0), v10p(bb2) -> (v10p, v13)
494   13.b    Compare GE i32             v7p, v10p -> (v14)
495   14.     IfImm NE b                 v13, 0x0
496succs: [bb 1, bb 2]
497
498BB 2  preds: [bb 3]
499prop: loop 1, depth 1
500   16.ref  LoadAndInitClass 'std.core.StringBuilder' ss -> (v17)
501   17.ref  NewObject 22178            v16, ss -> (v28, v25, v22)
502   18.void CallStatic 60220 std.core.StringBuilder::<ctor> v17, ss
503   22.ref  Intrinsic.StdCoreSbAppendString v17, v8p, ss
504   25.ref  Intrinsic.StdCoreSbAppendString v17, v9p, ss
505   28.ref  CallStatic 60410 std.core.StringBuilder::toString v17, ss -> (v11p, v8p)
506   29.i32  Add                        v7p, v30 -> (v7p)
507succs: [bb 3]
508
509BB 1  preds: [bb 3]
510prop:
511   31.ref  Return                     v8p
512succs: [bb 5]
513
514BB 5  preds: [bb 1]
515prop: end
516```
517IR after transformation:
518```
519Method: std.core.String ETSGLOBAL::concat_loop0(std.core.String, i32)
520
521BB 4
522prop: start
523    0.ref  Parameter                  arg 0 -> (v25)
524    1.i32  Parameter                  arg 1 -> (v40, v13)
525    3.i64  Constant                   0x0 -> (v40, v7p)
526   30.i64  Constant                   0x1 -> (v29)
527succs: [bb 0]
528
529BB 0  preds: [bb 4]
530prop: prehead
531    4.ref  LoadString 63726           ss -> (v22)
532   16.ref  LoadAndInitClass 'std.core.StringBuilder' ss -> (v17)
533   17.ref  NewObject 22178            v16, ss -> (v25, v28, v22, v18)
534   18.void CallStatic 60220 std.core.StringBuilder::<ctor> v17, ss
535   22.ref  Intrinsic.StdCoreSbAppendString v17, v4, ss
536   40.b    Compare GE i32             v3, v1 -> (v41)
537   41.     IfImm NE b                 v40, 0x0
538succs: [bb 1, bb 2]
539
540BB 2  preds: [bb 0, bb 2]
541prop: head, loop 1, depth 1
542   7p.i32  Phi                        v3(bb0), v29(bb2) -> (v29)
543   25.ref  Intrinsic.StdCoreSbAppendString v17, v0, ss
544   29.i32  Add                        v7p, v30 -> (v13, v7p)
545   13.b    Compare GE i32             v29, v1 -> (v14)
546   14.     IfImm NE b                 v13, 0x0
547succs: [bb 1, bb 2]
548
549BB 1  preds: [bb 2, bb 0]
550prop:
551   28.ref  CallStatic 60410 std.core.StringBuilder::toString v17, ss
552   31.ref  Return                     v28
553succs: [bb 5]
554
555BB 5  preds: [bb 1]
556prop: end
557```
558
559**Merge StringBuilde::append calls chain**
560
561ETS function example:
562```TS
563function append2(str0: string, str1: string): string {
564    let sb = new StringBuilder();
565
566    sb.append(str0);
567    sb.append(str1);
568
569    return sb.toString();
570}
571```
572IR before transformation:
573```
574Method: std.core.String ETSGLOBAL::append2(std.core.String, std.core.String)
575
576BB 1
577prop: start
578    0.ref  Parameter                  arg 0 -> (v13)
579    1.ref  Parameter                  arg 1 -> (v14)
580succs: [bb 0]
581
582BB 0  preds: [bb 1]
583    4.ref  LoadAndInitClass 'std.core.StringBuilder' v3 -> (v5)
584    5.ref  NewObject 15705            v4, ss -> (v19, v16, v13, v6)
585    6.void CallStatic 86153 std.core.StringBuilder::<ctor> v5, ss
586   13.ref  Intrinsic.StdCoreSbAppendString v5, v0, ss
587   16.ref  Intrinsic.StdCoreSbAppendString v5, v1, ss
588   19.ref  Intrinsic.StdCoreSbToString v5, ss
589   20.ref  Return                     v19
590succs: [bb 2]
591
592BB 2  preds: [bb 0]
593prop: end
594```
595IR after transformation:
596```
597Method: std.core.String ETSGLOBAL::append2(std.core.String, std.core.String)
598
599BB 1
600prop: start
601    0.ref  Parameter                  arg 0 -> (v13)
602    1.ref  Parameter                  arg 1 -> (v14)
603succs: [bb 0]
604
605BB 0  preds: [bb 1]
606    4.ref  LoadAndInitClass 'std.core.StringBuilder' ss -> (v5)
607    5.ref  NewObject 15705            v4, ss -> (v19, v18, v6)
608    6.void CallStatic 86153 std.core.StringBuilder::<ctor> v5, ss
609   18.ref  Intrinsic.StdCoreSbAppendString2 v5, v0, v1, ss
610   19.ref  Intrinsic.StdCoreSbToString v5, ss
611   20.ref  Return                     v19
612succs: [bb 2]
613
614BB 2  preds: [bb 0]
615prop: end
616```
617
618**Merge StringBuilder objects chain**
619
620ETS function example:
621```TS
622function concat2(a: String, b: String): String {
623    let sb0 = new StringBuilder()
624    sb0.append(a)
625    let sb1 = new StringBuilder()
626    sb1.append(sb0.toString())
627    sb1.append(b)
628    return sb1.toString();
629}
630```
631IR before transformation:
632```
633Method: std.core.String ETSGLOBAL::concat2(std.core.String, std.core.String)
634
635BB 1
636prop: start
637    0.ref  Parameter                  arg 0 -> (v10)
638    1.ref  Parameter                  arg 1 -> (v24)
639succs: [bb 0]
640
641BB 0  preds: [bb 1]
642    4.ref  LoadAndInitClass 'std.core.StringBuilder' ss -> (v5)
643    5.ref  NewObject 12080            v4, ss -> (v18, v10, v6)
644    6.void CallStatic 86229 std.core.StringBuilder::<ctor> v5, ss
645   10.ref  Intrinsic.StdCoreSbAppendString v5, v0, ss
646   12.ref  LoadAndInitClass 'std.core.StringBuilder' ss -> (v13)
647   13.ref  NewObject 12080            v12, ss -> (v27, v24, v21, v14)
648   14.void CallStatic 86229 std.core.StringBuilder::<ctor> v13, ss
649   18.ref  Intrinsic.StdCoreSbToString v5, ss -> (v21)
650   21.ref  Intrinsic.StdCoreSbAppendString v13, v18, ss
651   24.ref  Intrinsic.StdCoreSbAppendString v13, v1, ss
652   27.ref  Intrinsic.StdCoreSbToString v13, ss -> (v28)
653   28.ref  Return                     v27
654succs: [bb 2]
655
656BB 2  preds: [bb 0]
657prop: end
658```
659IR after transformation:
660```
661Method: std.core.String ETSGLOBAL::concat2(std.core.String, std.core.String)
662
663BB 1
664prop: start
665    0.ref  Parameter                  arg 0 -> (v10)
666    1.ref  Parameter                  arg 1 -> (v24)
667succs: [bb 0]
668
669BB 0  preds: [bb 1]
670    4.ref  LoadAndInitClass 'std.core.StringBuilder' ss -> (v5)
671    5.ref  NewObject 12080            v4, ss -> (v27, v24, v18, v10, v6)
672    6.void CallStatic 86229 std.core.StringBuilder::<ctor> v5, ss
673   10.ref  Intrinsic.StdCoreSbAppendString v5, v0, ss
674   24.ref  Intrinsic.StdCoreSbAppendString v5, v1, ss
675   27.ref  Intrinsic.StdCoreSbToString v5, ss -> (v28)
676   28.ref  Return                     v27
677succs: [bb 2]
678
679BB 2  preds: [bb 0]
680prop: end
681```
682
683## Links
684
685* Implementation
686    * [simplify_string_builder.h](../optimizer/optimizations/simplify_string_builder.h)
687    * [simplify_string_builder.cpp](../optimizer/optimizations/simplify_string_builder.cpp)
688* Tests
689    * [ets_stringbuilder.ets](../../plugins/ets/tests/checked/ets_stringbuilder.ets)
690    * [ets_string_builder_append_merge.ets](../../plugins/ets/tests/checked/ets_string_builder_append_merge.ets)
691    * [ets_string_builder_merge.ets](../../plugins/ets/tests/checked/ets_string_builder_merge.ets)
692    * [ets_string_concat.ets](../../plugins/ets/tests/checked/ets_string_concat.ets)
693    * [ets_string_concat_loop.ets](../../plugins/ets/tests/checked/ets_string_concat_loop.ets)
694