• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# plugin ets_string_builder
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
15include_relative 'string_helpers.irt'
16
17module Constants
18    ETS_SB_BUFFER_OFFSET = "ark::ObjectHeader::ObjectHeaderSize()"
19    ETS_SB_INDEX_OFFSET = ETS_SB_BUFFER_OFFSET + " + ark::OBJECT_POINTER_SIZE"
20    ETS_SB_LENGTH_OFFSET = ETS_SB_INDEX_OFFSET + " + sizeof(int32_t)"
21    ETS_SB_COMPRESS_OFFSET = ETS_SB_LENGTH_OFFSET + " + sizeof(int32_t)"
22end
23
24class SynchronizationType
25  :Sync
26  :Async
27  :AsyncManual
28end
29
30#
31# Counts a number of digits in the long v (+1 for the sign if v is negative)
32#
33scoped_macro(:count_digits) do |v|
34  negative := Compare(v, 0).SrcType("DataType::INT64").LT.b
35  n_digits1 := Add(Cast(1).u64, Cast(negative).u64).u64
36  val := Cast(Abs(v).i64).u64
37  pow10_4 := Cast(10000).u64
38Label(:Loop)
39  val1 := Phi(val, val2).u64
40  n_digits := Phi(n_digits1, n_digits5).u32
41  If(val1, 10).AE.Likely.b {
42    If(val1, 100).B.b {
43      n_digits2 := AddI(n_digits).Imm(1).u32
44      Goto(:Done)
45    }
46    If(val1,1000).B.b {
47      n_digits3 := AddI(n_digits).Imm(2).u32
48      Goto(:Done)
49    }
50    If(val1, 10000).B.b {
51      n_digits4 := AddI(n_digits).Imm(3).u32
52      Goto(:Done)
53    }
54    n_digits5 := AddI(n_digits).Imm(4).u32
55    val2 := Div(val1, pow10_4).u64
56    Goto(:Loop)
57  }
58Label(:Done)
59  result := Phi(n_digits, n_digits2, n_digits3, n_digits4).u32
60end
61
62
63# Converts long to array of chars
64# Ex: '-123' is converted to array of chars as follows:
65#   chars[0] = 0x002D // '-'
66#   chars[1] = 0x0031 // '1'
67#   chars[2] = 0x0032 // '2'
68#   chars[3] = 0x0033 // '3'
69scoped_macro(:convert_long_to_char_array) do |v, chars, n_digits|
70  ten := Cast(10).u64
71  uv := Cast(Abs(v).i64).u64
72  offs := SubI(ShlI(n_digits).Imm(1).u32).Imm(2).u32
73Label(:NextDigit)
74  uv1 := Phi(uv, uv2).u64
75  dig := Mod(uv1, ten).u64         # get least significant digit as 'uv1 % 10'
76  c := AddI(dig).Imm(0x0030).u16   # convert it to utf16 char
77  offs1 := Phi(offs, offs2).u32    # select its offset
78  Store(chars, offs1, c).u16       # store char to array
79  offs2 := SubI(offs1).Imm(2).u32  # decrease offset
80  uv2 := Div(uv1, ten).u64         # prepare for the next decimal digit
81  If(uv2, 0).NE.Likely.b {
82    Goto(:NextDigit)
83  }
84  # Convert sign if any
85  If(v, 0).LT.b {
86    minus := Cast(0x002D).u16
87    StoreI(chars, minus).Imm(0).u16
88  }
89end
90
91
92scoped_macro(:convert_bool_to_char_array) do |v, chars|
93  If(v, 0).EQ.b {
94    Goto(:BoolFalse)
95  }
96  true_code := Cast(0x0065007500720074).u64
97  StoreI(chars, true_code).Imm(0).u64
98  Goto(:Done)
99Label(:BoolFalse)
100  fals_code := Cast(0x0073006c00610066).u64
101  StoreI(chars, fals_code).Imm(0).u64
102  e_code := Cast(0x0065).u16
103  StoreI(chars, e_code).Imm(8).u16
104Label(:Done)
105end
106
107macro(:ref_is_null) do |obj|
108  Compare(obj, 0).SrcType(Constants::COMPILER_REFERENCE).EQ.b
109end
110
111scoped_macro(:store_with_barrier) do |buffer, buffer_uint, offset, obj, sync_type|
112  Store(buffer, offset, obj).SetNeedBarrier(sync_type == :Async).ref
113  if sync_type == :AsyncManual
114    min_addr := LoadI(%tr).Imm(Constants::TLS_CARD_TABLE_MIN_ADDR_OFFSET).word
115    cards := LoadI(%tr).Imm(Constants::TLS_CARD_TABLE_ADDR_OFFSET).ptr
116
117    mem_word := Cast(buffer_uint).SrcType(Constants::REF_UINT).word
118    card_offset := ShrI(Sub(mem_word, min_addr).word).Imm(Constants::CARD_TABLE_CARD_BITS).word
119    card := Add(cards, card_offset).ptr
120    StoreI(card, Constants::CARD_DIRTY_VALUE).Imm(Constants::CARD_VALUE_OFFSET).u8
121  end
122end
123
124function(:StringBuilderAppendLong,
125         params: {sb: 'ref', v: 'i64', array_klass: 'ref'},
126         regmap: $full_regmap,
127         regalloc_set: $panda_mask,
128         mode: [:FastPath]) {
129
130  # Arm32 is not supported
131  if Options.arch == :arm32
132    Intrinsic(:UNREACHABLE).void.Terminator
133    next
134  end
135
136  # 1. Check if there is a free slot in the buffer
137  index := LoadI(sb).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
138  buffer := LoadI(sb).Imm(Constants::ETS_SB_BUFFER_OFFSET).ref
139  If(index, LoadI(buffer).Imm(Constants::ARRAY_LENGTH_OFFSET).i32).GE.Unlikely.b {
140    Goto(:SlowPathEntrypoint)
141  }
142  # 2. CountDigits
143  n_digits := count_digits(v)
144  # 3. Allocate array of chars in TLAB
145  char_array := allocate_array_of_chars_tlab(array_klass, Cast(n_digits).word)
146  # Let the memory writes (TLAB) be visible to other threads
147  Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
148  # 4. Convert Long to utf16 chars
149  chars := Add(char_array, Constants::ARRAY_DATA_OFFSET).ptr
150  convert_long_to_char_array(v, chars, n_digits)
151  # 5. Remember array in the buffer
152  slot_offs := AddI(ShlI(index).Imm(Constants::REFERENCE_TYPE_SHIFT).i32).Imm(Constants::ARRAY_DATA_OFFSET).i32
153  Store(buffer, slot_offs, char_array).SetNeedBarrier(true).ref
154  # 6. Increment 'index' field
155  StoreI(sb, AddI(index).Imm(1).i32).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
156  # 7. Update 'length' field
157  length := LoadI(sb).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
158  length := Add(length, n_digits).i32
159  StoreI(sb, length).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
160  # Do not update 'compress' field, as a string representation of a number is always compressable
161  Return(sb).ref
162Label(:SlowPathEntrypoint)
163  entrypoint = get_entrypoint_offset("STRING_BUILDER_APPEND_LONG_SLOW_PATH")
164  Intrinsic(:SLOW_PATH_ENTRY, sb, v).AddImm(entrypoint).MethodAsImm("StringBuilderAppendLong3ArgBridge").Terminator.ref
165  Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
166}
167
168
169function(:StringBuilderAppendBool,
170         params: {sb: 'ref', v: 'u8', array_klass: 'ref'},
171         regmap: $full_regmap,
172         regalloc_set: $panda_mask,
173         mode: [:FastPath]) {
174
175  # Arm32 is not supported
176  if Options.arch == :arm32
177    Intrinsic(:UNREACHABLE).void.Terminator
178    next
179  end
180
181  # 1. Check if there is a free slot in the buffer
182  index := LoadI(sb).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
183  buffer := LoadI(sb).Imm(Constants::ETS_SB_BUFFER_OFFSET).ref
184  If(index, LoadI(buffer).Imm(Constants::ARRAY_LENGTH_OFFSET).i32).GE.Unlikely.b {
185    Goto(:SlowPathEntrypoint)
186  }
187  # 2. Allocate array of chars in TLAB
188  If(v, 0).EQ.b {
189    n_digits1 := Cast(5).word  # false
190  } Else {
191    n_digits2 := Cast(4).word  # true
192  }
193  n_digits := Phi(n_digits1, n_digits2).word
194  char_array := allocate_array_of_chars_tlab(array_klass, n_digits)
195  # Let the memory writes (TLAB) be visible to other threads
196  Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
197  # 3. Store 'true' or 'false' to array
198  chars := Add(char_array, Constants::ARRAY_DATA_OFFSET).ptr
199  convert_bool_to_char_array(v, chars)
200  # 4. Remember array in the buffer
201  slot_offs := AddI(ShlI(index).Imm(Constants::REFERENCE_TYPE_SHIFT).i32).Imm(Constants::ARRAY_DATA_OFFSET).i32
202  Store(buffer, slot_offs, char_array).SetNeedBarrier(true).ref
203  # 5. Increment 'index' field
204  StoreI(sb, AddI(index).Imm(1).i32).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
205  # 6. Update 'length' field
206  length := LoadI(sb).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
207  length := Add(length, Cast(n_digits).i32).i32
208  StoreI(sb, length).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
209  # Do not update 'compress' field, as 'true' and 'false' are always compressable
210  Return(sb).ref
211Label(:SlowPathEntrypoint)
212  entrypoint = get_entrypoint_offset("STRING_BUILDER_APPEND_BOOL_SLOW_PATH")
213  Intrinsic(:SLOW_PATH_ENTRY, sb, v).AddImm(entrypoint).MethodAsImm("StringBuilderAppendBool3ArgBridge").Terminator.ref
214  Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
215}
216
217
218def GenerateStringBuilderAppendChar(string_compression_enabled)
219    suffix = (string_compression_enabled ? "Compressed" : "")
220    function("StringBuilderAppendChar#{suffix}".to_sym,
221          params: {sb: 'ref', ch: 'u16', array_klass: 'ref'},
222          regmap: $full_regmap,
223          regalloc_set: $panda_mask,
224          mode: [:FastPath]) {
225
226  # Arm32 is not supported
227  if Options.arch == :arm32
228    Intrinsic(:UNREACHABLE).void.Terminator
229    next
230  end
231
232  # 1. Check if there is a free slot in the buffer
233  index := LoadI(sb).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
234  buffer := LoadI(sb).Imm(Constants::ETS_SB_BUFFER_OFFSET).ref
235  If(index, LoadI(buffer).Imm(Constants::ARRAY_LENGTH_OFFSET).i32).GE.Unlikely.b {
236    Goto(:SlowPathEntrypoint)
237  }
238  # 2. Allocate array of chars in TLAB
239  char_array := allocate_array_of_chars_tlab(array_klass, Cast(1).word)
240  # Let the memory writes (TLAB) be visible to other threads
241  Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
242  # 3. Store char to array
243  chars := Add(char_array, Constants::ARRAY_DATA_OFFSET).ptr
244  StoreI(chars, ch).Imm(0).u16
245  # 4. Remember array in the buffer
246  slot_offs := AddI(ShlI(index).Imm(Constants::REFERENCE_TYPE_SHIFT).i32).Imm(Constants::ARRAY_DATA_OFFSET).i32
247  Store(buffer, slot_offs, char_array).SetNeedBarrier(true).ref
248  # 5. Increment 'index' field
249  StoreI(sb, AddI(index).Imm(1).i32).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
250  # 6. Update 'length' field
251  length := LoadI(sb).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
252  length := AddI(length).Imm(1).i32
253  StoreI(sb, length).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
254  # 7. Update 'compress' field
255  compress := LoadI(sb).Imm(Constants::ETS_SB_COMPRESS_OFFSET).u8
256  If(compress, 0).NE.Likely.b {
257    if string_compression_enabled
258      compressable := Compare(SubI(ch).Imm(1).u16, Cast(Constants::STRING_MUTF8_1B_MAX).u16).B.b
259      If(compressable, 0).EQ.Unlikely.b {
260        StoreI(sb, 0).Imm(Constants::ETS_SB_COMPRESS_OFFSET).u8
261      }
262    else
263      StoreI(sb, 0).Imm(Constants::ETS_SB_COMPRESS_OFFSET).u8
264    end
265  }
266  Return(sb).ref
267Label(:SlowPathEntrypoint)
268  entrypoint = get_entrypoint_offset("STRING_BUILDER_APPEND_CHAR_SLOW_PATH")
269  Intrinsic(:SLOW_PATH_ENTRY, sb, ch).AddImm(entrypoint).MethodAsImm("StringBuilderAppendChar3ArgBridge").Terminator.ref
270  Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
271}
272end
273
274GenerateStringBuilderAppendChar(true)
275GenerateStringBuilderAppendChar(false)
276
277def GenerateStringBuilderAppendStrings(num_args, sync_type)
278  case num_args
279    when 1
280      params = {sb: 'ref', str0: 'ref'}
281    when 2
282      params = {sb: 'ref', str0: 'ref', str1: 'ref'}
283    when 3
284      params = {sb: 'ref', str0: 'ref', str1: 'ref', str2: 'ref'}
285    when 4
286      params = {sb: 'ref', str0: 'ref', str1: 'ref', str2: 'ref', str3: 'ref'}
287    else
288      raise "Unexpected number of arguments: #{num_args}"
289  end
290  function("StringBuilderAppendString#{num_args}#{sync_type}".to_sym,
291            params: params,
292            regmap: $full_regmap,
293            regalloc_set: $panda_mask,
294            mode: [:FastPath]) {
295
296  # Arm32 is not supported
297  if Options.arch == :arm32
298    Intrinsic(:UNREACHABLE).void.Terminator
299    next
300  end
301
302  # 1. Check if any input string is null
303  if num_args > 0
304    str_is_null := ref_is_null(str0)
305  end
306  if num_args > 1
307    str_is_null := Or(str_is_null, ref_is_null(str1)).b
308  end
309  if num_args > 2
310    str_is_null := Or(str_is_null, ref_is_null(str2)).b
311  end
312  if num_args > 3
313    str_is_null := Or(str_is_null, ref_is_null(str3)).b
314  end
315  IfImm(str_is_null).Imm(0).SrcType(Constants::COMPILER_BOOL).NE.Unlikely.b {
316    Goto(:SlowPathEntrypoint)
317  }
318
319  # 2. Check if there is a free slot in the buffer
320  if num_args > 0
321    index0 := LoadI(sb).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
322  end
323  if num_args > 1
324    index1 := AddI(index0).Imm(1).i32
325  end
326  if num_args > 2
327    index2 := AddI(index1).Imm(1).i32
328  end
329  if num_args > 3
330    index3 := AddI(index2).Imm(1).i32
331  end
332  case num_args
333    when 1
334      index := index0
335    when 2
336      index := index1
337    when 3
338      index := index2
339    when 4
340      index := index3
341    else
342      raise "Unexpected number of arguments: #{num_args}"
343  end
344
345  buffer := LoadI(sb).Imm(Constants::ETS_SB_BUFFER_OFFSET).ref
346  If(index, LoadI(buffer).Imm(Constants::ARRAY_LENGTH_OFFSET).i32).GE.Unlikely.b {
347    Goto(:SlowPathEntrypoint)
348  }
349  buffer_uint := LoadI(sb).Imm(Constants::ETS_SB_BUFFER_OFFSET).ref_uint
350
351  # 3. Increment 'index' field
352  StoreI(sb, AddI(index).Imm(1).i32).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
353
354  # 4. Store input strings into buffer
355  if num_args > 0
356    Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
357    offset0 := AddI(ShlI(index0).Imm(Constants::REFERENCE_TYPE_SHIFT).i32).Imm(Constants::ARRAY_DATA_OFFSET).i32
358    store_with_barrier(buffer, buffer_uint, offset0, str0, sync_type)
359  end
360  if num_args > 1
361    Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
362    offset1 := AddI(ShlI(index1).Imm(Constants::REFERENCE_TYPE_SHIFT).i32).Imm(Constants::ARRAY_DATA_OFFSET).i32
363    store_with_barrier(buffer, buffer_uint, offset1, str1, sync_type)
364  end
365  if num_args > 2
366    Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
367    offset2 := AddI(ShlI(index2).Imm(Constants::REFERENCE_TYPE_SHIFT).i32).Imm(Constants::ARRAY_DATA_OFFSET).i32
368    store_with_barrier(buffer, buffer_uint, offset2, str2, sync_type)
369  end
370  if num_args > 3
371    Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
372    offset3 := AddI(ShlI(index3).Imm(Constants::REFERENCE_TYPE_SHIFT).i32).Imm(Constants::ARRAY_DATA_OFFSET).i32
373    store_with_barrier(buffer, buffer_uint, offset3, str3, sync_type)
374  end
375
376  # 5. Load raw string 'length' field
377  if num_args > 0
378    length0 := LoadI(str0).Imm(Constants::STRING_LENGTH_OFFSET).i32
379  end
380  if num_args > 1
381    length1 := LoadI(str1).Imm(Constants::STRING_LENGTH_OFFSET).i32
382  end
383  if num_args > 2
384    length2 := LoadI(str2).Imm(Constants::STRING_LENGTH_OFFSET).i32
385  end
386  if num_args > 3
387    length3 := LoadI(str3).Imm(Constants::STRING_LENGTH_OFFSET).i32
388  end
389
390  # 6. Update 'compress' field
391  compress := LoadI(sb).Imm(Constants::ETS_SB_COMPRESS_OFFSET).u8
392  If(compress, 0).NE.Likely.b {
393    if num_args > 0
394      compress0 := XorI(length0).Imm(1).i32
395      compress  := And(compress, compress0).i32
396    end
397    if num_args > 1
398      compress1 := XorI(length1).Imm(1).i32
399      compress  := And(compress, compress1).i32
400    end
401    if num_args > 2
402      compress2 := XorI(length2).Imm(1).i32
403      compress  := And(compress, compress2).i32
404    end
405    if num_args > 3
406      compress3 := XorI(length3).Imm(1).i32
407      compress  := And(compress, compress3).i32
408    end
409    If(compress, 0).EQ.Unlikely.b {
410      StoreI(sb, 0).Imm(Constants::ETS_SB_COMPRESS_OFFSET).u8
411    }
412  }
413
414  # 7. Update 'length' field
415  length := LoadI(sb).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
416  if num_args > 0
417    length0 := ShrI(length0).Imm(1).i32
418    length := Add(length, length0).i32
419  end
420  if num_args > 1
421    length1 := ShrI(length1).Imm(1).i32
422    length := Add(length, length1).i32
423  end
424  if num_args > 2
425    length2 := ShrI(length2).Imm(1).i32
426    length := Add(length, length2).i32
427  end
428  if num_args > 3
429    length3 := ShrI(length3).Imm(1).i32
430    length := Add(length, length3).i32
431  end
432
433  StoreI(sb, length).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
434
435  Return(sb).ref
436Label(:SlowPathEntrypoint)
437  entrypoint = get_entrypoint_offset("STRING_BUILDER_APPEND_STRING#{num_args}_SLOW_PATH")
438  case num_args
439    when 1
440      method = "StringBuilderAppendString#{num_args}UsualBridge"
441      Intrinsic(:SLOW_PATH_ENTRY, sb, str0).AddImm(entrypoint).MethodAsImm(method).Terminator.ref
442    when 2
443      method = "StringBuilderAppendString#{num_args}OddSavedBridge"
444      Intrinsic(:SLOW_PATH_ENTRY, sb, str0, str1).AddImm(entrypoint).MethodAsImm(method).Terminator.ref
445    when 3
446      method = "StringBuilderAppendString#{num_args}UsualBridge"
447      Intrinsic(:SLOW_PATH_ENTRY, sb, str0, str1, str2).AddImm(entrypoint).MethodAsImm(method).Terminator.ref
448    when 4
449      method = "StringBuilderAppendString#{num_args}OddSavedBridge"
450      Intrinsic(:SLOW_PATH_ENTRY, sb, str0, str1, str2, str3).AddImm(entrypoint).MethodAsImm(method).Terminator.ref
451    else
452      raise "Unexpected number of arguments: #{num_args}"
453  end
454  Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
455}
456end
457
458GenerateStringBuilderAppendStrings(2, :Sync)
459GenerateStringBuilderAppendStrings(3, :Sync)
460GenerateStringBuilderAppendStrings(4, :Sync)
461
462GenerateStringBuilderAppendStrings(2, :Async)
463GenerateStringBuilderAppendStrings(3, :Async)
464GenerateStringBuilderAppendStrings(4, :Async)
465
466GenerateStringBuilderAppendStrings(2, :AsyncManual)
467GenerateStringBuilderAppendStrings(3, :AsyncManual)
468GenerateStringBuilderAppendStrings(4, :AsyncManual)
469
470#
471# StringBuilder.toString
472#
473# Assumtion: string compression is enabled.
474#
475function(:StringBuilderToString,
476          params: {sb: 'ref', string_klass: 'ref'},
477          regmap: $full_regmap,
478          regalloc_set: $panda_mask,
479          mode: [:FastPath]) {
480
481  # Arm32 is not supported
482  if Options.arch == :arm32
483    Intrinsic(:UNREACHABLE).void.Terminator
484    next
485  end
486
487  # Compute data size and length of a new string.
488  n_chars := LoadI(sb).Imm(Constants::ETS_SB_LENGTH_OFFSET).u32
489  len_compressed := ShlI(n_chars).Imm(1).u32  # set 'uncompressed' bit to 0
490  sb_compress := LoadI(sb).Imm(Constants::ETS_SB_COMPRESS_OFFSET).u8
491  If(sb_compress, 0).EQ.Unlikely.b {
492    size := len_compressed
493    len_uncompressed := OrI(len_compressed).Imm(1).u32  # set 'uncompressed' bit to 1
494  }
495  data_size := Phi(n_chars, size).u32
496  packed_length := Phi(len_compressed, len_uncompressed).u32
497  # Allocate a string
498  new_str := allocate_string_tlab(string_klass, Cast(data_size).word)
499  # Let the memory writes (TLAB) be visible to other threads
500  Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
501  # Set new string's length
502  StoreI(new_str,packed_length).Imm(Constants::STRING_LENGTH_OFFSET).u32
503  # Set new string's hashcode to 0, so as not to spend time on its computation
504  StoreI(new_str, Cast(0).u32).Imm(Constants::STRING_HASHCODE_OFFSET).u32
505  # If string is empty, then there is nothing to do anymore
506  If(n_chars, 0).EQ.Unlikely.b {
507    Return(new_str).ptr
508  }
509
510  # Walk through the buffer elements and append their content to new_str
511  index := LoadI(sb).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
512  buffer := LoadI(sb).Imm(Constants::ETS_SB_BUFFER_OFFSET).ref
513  dst_data1 := AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr
514  i1 := Cast(0).i32
515Label(:ForEachBufferSlot)
516  i := Phi(i1, i2).i32
517  dst_data := Phi(dst_data1, dst_data2).ptr
518  slot := AddI(ShlI(i).Imm(Constants::REFERENCE_TYPE_SHIFT).i32).Imm(Constants::ARRAY_DATA_OFFSET).i32
519  obj := Load(buffer, slot).ref
520  klass := load_class(obj)
521  klass_flags := LoadI(klass).Imm(Constants::BASE_CLASS_FLAGS_OFFSET).u32
522  If(AndI(klass_flags).Imm("ark::Class::STRING_CLASS").u32, 0).EQ.b {
523    Goto(:ArrayObject)
524  }
525  # -------------------
526  # Object is a string
527  # -------------------
528  str_len := LoadI(obj).Imm(Constants::STRING_LENGTH_OFFSET).i32
529  src_data := AddI(Cast(obj).SrcType(Constants::COMPILER_REFERENCE).ptr).Imm(Constants::STRING_DATA_OFFSET).ptr
530  src_len := ShrI(str_len).Imm(1).i32
531  If(sb_compress, 0).EQ.Unlikely.b {
532    Goto(:DoNotCompressString)
533  }
534  copy_u8_chars(src_data, dst_data, Cast(src_len).u64)
535  n_bytes1 := src_len
536  Goto(:NextObject)
537Label(:DoNotCompressString)
538  If(AndI(str_len).Imm(1).u32, 1).EQ.b {
539    Goto(:SrcNotCompressed)
540  }
541  expand_u8_to_u16_chars(src_data, dst_data, Cast(src_len).u64)
542  n_bytes2 := ShlI(src_len).Imm(1).i32
543  Goto(:NextObject)
544Label(:SrcNotCompressed)
545  copy_u16_chars(src_data, dst_data, Cast(src_len).u64)
546  n_bytes3 := ShlI(src_len).Imm(1).i32
547  Goto(:NextObject)
548  # -------------------
549  # Object is an array
550  # -------------------
551Label(:ArrayObject)
552  src_arr_data := AddI(Cast(obj).SrcType(Constants::COMPILER_REFERENCE).ptr).Imm(Constants::ARRAY_DATA_OFFSET).ptr
553  src_arr_len := LoadI(obj).Imm(Constants::ARRAY_LENGTH_OFFSET).i32
554  If(sb_compress, 0).EQ.Unlikely.b {
555    Goto(:DoNotCompressArray)
556  }
557  compress_u16_to_u8_chars(src_arr_data, dst_data, Cast(src_arr_len).u64)
558  n_bytes4 := src_arr_len
559  Goto(:NextObject)
560Label(:DoNotCompressArray)
561  copy_u16_chars(src_arr_data, dst_data, Cast(src_arr_len).u64)
562  n_bytes5 := ShlI(src_arr_len).Imm(1).i32
563  # Go to the next buffer slot if any
564Label(:NextObject)
565  n_bytes := Phi(n_bytes1, n_bytes2, n_bytes3, n_bytes4, n_bytes5).i32
566  dst_data2 := Add(dst_data, n_bytes).ptr
567  i2 := AddI(i).Imm(1).i32
568  If(i2, index).LT.Likely.b {
569    Goto(:ForEachBufferSlot)
570  }
571  # Everything is done
572  Return(new_str).ptr
573  # SlowPath if new string can not be allocated in TLAB
574Label(:SlowPathEntrypoint)
575  entrypoint = get_entrypoint_offset("STRING_BUILDER_TO_STRING_SLOW_PATH")
576  Intrinsic(:SLOW_PATH_ENTRY, sb).AddImm(entrypoint).MethodAsImm("StringBuilderToString2ArgBridge").Terminator.ptr
577  Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
578}
579