• 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
24#
25# Counts a number of digits in the long v (+1 for the sign if v is negative)
26#
27scoped_macro(:count_digits) do |v|
28  negative := Compare(v, 0).SrcType("DataType::INT64").LT.b
29  n_digits1 := Add(Cast(1).u64, Cast(negative).u64).u64
30  val := Cast(Abs(v).i64).u64
31  pow10_4 := Cast(10000).u64
32Label(:Loop)
33  val1 := Phi(val, val2).u64
34  n_digits := Phi(n_digits1, n_digits5).u32
35  If(val1, 10).AE.Likely.b {
36    If(val1, 100).B.b {
37      n_digits2 := AddI(n_digits).Imm(1).u32
38      Goto(:Done)
39    }
40    If(val1,1000).B.b {
41      n_digits3 := AddI(n_digits).Imm(2).u32
42      Goto(:Done)
43    }
44    If(val1, 10000).B.b {
45      n_digits4 := AddI(n_digits).Imm(3).u32
46      Goto(:Done)
47    }
48    n_digits5 := AddI(n_digits).Imm(4).u32
49    val2 := Div(val1, pow10_4).u64
50    Goto(:Loop)
51  }
52Label(:Done)
53  result := Phi(n_digits, n_digits2, n_digits3, n_digits4).u32
54end
55
56
57# Converts long to array of chars
58# Ex: '-123' is converted to array of chars as follows:
59#   chars[0] = 0x002D // '-'
60#   chars[1] = 0x0031 // '1'
61#   chars[2] = 0x0032 // '2'
62#   chars[3] = 0x0033 // '3'
63scoped_macro(:convert_long_to_char_array) do |v, chars, n_digits|
64  ten := Cast(10).u64
65  uv := Cast(Abs(v).i64).u64
66  offs := SubI(ShlI(n_digits).Imm(1).u32).Imm(2).u32
67Label(:NextDigit)
68  uv1 := Phi(uv, uv2).u64
69  dig := Mod(uv1, ten).u64         # get least significant digit as 'uv1 % 10'
70  c := AddI(dig).Imm(0x0030).u16   # convert it to utf16 char
71  offs1 := Phi(offs, offs2).u32    # select its offset
72  Store(chars, offs1, c).u16       # store char to array
73  offs2 := SubI(offs1).Imm(2).u32  # decrease offset
74  uv2 := Div(uv1, ten).u64         # prepare for the next decimal digit
75  If(uv2, 0).NE.Likely.b {
76    Goto(:NextDigit)
77  }
78  # Convert sign if any
79  If(v, 0).LT.b {
80    minus := Cast(0x002D).u16
81    StoreI(chars, minus).Imm(0).u16
82  }
83end
84
85
86scoped_macro(:convert_bool_to_char_array) do |v, chars|
87  If(v, 0).EQ.b {
88    Goto(:BoolFalse)
89  }
90  true_code := Cast(0x0065007500720074).u64
91  StoreI(chars, true_code).Imm(0).u64
92  Goto(:Done)
93Label(:BoolFalse)
94  fals_code := Cast(0x0073006c00610066).u64
95  StoreI(chars, fals_code).Imm(0).u64
96  e_code := Cast(0x0065).u16
97  StoreI(chars, e_code).Imm(8).u16
98Label(:Done)
99end
100
101
102function(:StringBuilderAppendLong,
103         params: {sb: 'ref', v: 'i64', array_klass: 'ref'},
104         regmap: $full_regmap,
105         regalloc_set: $panda_mask,
106         mode: [:FastPath]) {
107
108  # Arm32 is not supported
109  if Options.arch == :arm32
110    Intrinsic(:UNREACHABLE).void.Terminator
111    next
112  end
113
114  # 1. Check if there is a free slot in the buffer
115  index := LoadI(sb).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
116  buffer := LoadI(sb).Imm(Constants::ETS_SB_BUFFER_OFFSET).ref
117  If(index, LoadI(buffer).Imm(Constants::ARRAY_LENGTH_OFFSET).i32).GE.Unlikely.b {
118    Goto(:SlowPathEntrypoint)
119  }
120  # 2. CountDigits
121  n_digits := count_digits(v)
122  # 3. Allocate array of chars in TLAB
123  char_array := allocate_array_of_chars_tlab(array_klass, Cast(n_digits).word)
124  # Let the memory writes (TLAB) be visible to other threads
125  Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
126  # 4. Convert Long to utf16 chars
127  chars := Add(char_array, Constants::ARRAY_DATA_OFFSET).ptr
128  convert_long_to_char_array(v, chars, n_digits)
129  # 5. Remember array in the buffer
130  slot_offs := AddI(ShlI(index).Imm(Constants::REFERENCE_TYPE_SHIFT).i32).Imm(Constants::ARRAY_DATA_OFFSET).i32
131  Store(buffer, slot_offs, char_array).SetNeedBarrier(true).ref
132  # 6. Increment 'index' field
133  StoreI(sb, AddI(index).Imm(1).i32).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
134  # 7. Update 'length' field
135  length := LoadI(sb).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
136  length := Add(length, n_digits).i32
137  StoreI(sb, length).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
138  # Do not update 'compress' field, as a string representation of a number is always compressable
139  Return(sb).ref
140Label(:SlowPathEntrypoint)
141  entrypoint = get_entrypoint_offset("STRING_BUILDER_APPEND_LONG_SLOW_PATH")
142  Intrinsic(:SLOW_PATH_ENTRY, sb, v).AddImm(entrypoint).MethodAsImm("StringBuilderAppendLong3ArgBridge").Terminator.ref
143  Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
144}
145
146
147function(:StringBuilderAppendBool,
148         params: {sb: 'ref', v: 'u8', array_klass: 'ref'},
149         regmap: $full_regmap,
150         regalloc_set: $panda_mask,
151         mode: [:FastPath]) {
152
153  # Arm32 is not supported
154  if Options.arch == :arm32
155    Intrinsic(:UNREACHABLE).void.Terminator
156    next
157  end
158
159  # 1. Check if there is a free slot in the buffer
160  index := LoadI(sb).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
161  buffer := LoadI(sb).Imm(Constants::ETS_SB_BUFFER_OFFSET).ref
162  If(index, LoadI(buffer).Imm(Constants::ARRAY_LENGTH_OFFSET).i32).GE.Unlikely.b {
163    Goto(:SlowPathEntrypoint)
164  }
165  # 2. Allocate array of chars in TLAB
166  If(v, 0).EQ.b {
167    n_digits1 := Cast(5).word  # false
168  } Else {
169    n_digits2 := Cast(4).word  # true
170  }
171  n_digits := Phi(n_digits1, n_digits2).word
172  char_array := allocate_array_of_chars_tlab(array_klass, n_digits)
173  # Let the memory writes (TLAB) be visible to other threads
174  Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
175  # 3. Store 'true' or 'false' to array
176  chars := Add(char_array, Constants::ARRAY_DATA_OFFSET).ptr
177  convert_bool_to_char_array(v, chars)
178  # 4. Remember array in the buffer
179  slot_offs := AddI(ShlI(index).Imm(Constants::REFERENCE_TYPE_SHIFT).i32).Imm(Constants::ARRAY_DATA_OFFSET).i32
180  Store(buffer, slot_offs, char_array).SetNeedBarrier(true).ref
181  # 5. Increment 'index' field
182  StoreI(sb, AddI(index).Imm(1).i32).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
183  # 6. Update 'length' field
184  length := LoadI(sb).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
185  length := Add(length, Cast(n_digits).i32).i32
186  StoreI(sb, length).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
187  # Do not update 'compress' field, as 'true' and 'false' are always compressable
188  Return(sb).ref
189Label(:SlowPathEntrypoint)
190  entrypoint = get_entrypoint_offset("STRING_BUILDER_APPEND_BOOL_SLOW_PATH")
191  Intrinsic(:SLOW_PATH_ENTRY, sb, v).AddImm(entrypoint).MethodAsImm("StringBuilderAppendBool3ArgBridge").Terminator.ref
192  Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
193}
194
195
196def GenerateStringBuilderAppendChar(string_compression_enabled)
197    suffix = (string_compression_enabled ? "Compressed" : "")
198    function("StringBuilderAppendChar#{suffix}".to_sym,
199          params: {sb: 'ref', ch: 'u16', array_klass: 'ref'},
200          regmap: $full_regmap,
201          regalloc_set: $panda_mask,
202          mode: [:FastPath]) {
203
204  # Arm32 is not supported
205  if Options.arch == :arm32
206    Intrinsic(:UNREACHABLE).void.Terminator
207    next
208  end
209
210  # 1. Check if there is a free slot in the buffer
211  index := LoadI(sb).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
212  buffer := LoadI(sb).Imm(Constants::ETS_SB_BUFFER_OFFSET).ref
213  If(index, LoadI(buffer).Imm(Constants::ARRAY_LENGTH_OFFSET).i32).GE.Unlikely.b {
214    Goto(:SlowPathEntrypoint)
215  }
216  # 2. Allocate array of chars in TLAB
217  char_array := allocate_array_of_chars_tlab(array_klass, Cast(1).word)
218  # Let the memory writes (TLAB) be visible to other threads
219  Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
220  # 3. Store char to array
221  chars := Add(char_array, Constants::ARRAY_DATA_OFFSET).ptr
222  StoreI(chars, ch).Imm(0).u16
223  # 4. Remember array in the buffer
224  slot_offs := AddI(ShlI(index).Imm(Constants::REFERENCE_TYPE_SHIFT).i32).Imm(Constants::ARRAY_DATA_OFFSET).i32
225  Store(buffer, slot_offs, char_array).SetNeedBarrier(true).ref
226  # 5. Increment 'index' field
227  StoreI(sb, AddI(index).Imm(1).i32).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
228  # 6. Update 'length' field
229  length := LoadI(sb).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
230  length := AddI(length).Imm(1).i32
231  StoreI(sb, length).Imm(Constants::ETS_SB_LENGTH_OFFSET).i32
232  # 7. Update 'compress' field
233  compress := LoadI(sb).Imm(Constants::ETS_SB_COMPRESS_OFFSET).u8
234  If(compress, 0).NE.Likely.b {
235    if string_compression_enabled
236      compressable := Compare(SubI(ch).Imm(1).u16, Cast(Constants::STRING_MUTF8_1B_MAX).u16).B.b
237      If(compressable, 0).EQ.Unlikely.b {
238        StoreI(sb, 0).Imm(Constants::ETS_SB_COMPRESS_OFFSET).u8
239      }
240    else
241      StoreI(sb, 0).Imm(Constants::ETS_SB_COMPRESS_OFFSET).u8
242    end
243  }
244  Return(sb).ref
245Label(:SlowPathEntrypoint)
246  entrypoint = get_entrypoint_offset("STRING_BUILDER_APPEND_CHAR_SLOW_PATH")
247  Intrinsic(:SLOW_PATH_ENTRY, sb, ch).AddImm(entrypoint).MethodAsImm("StringBuilderAppendChar3ArgBridge").Terminator.ref
248  Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
249}
250end
251
252GenerateStringBuilderAppendChar(true)
253GenerateStringBuilderAppendChar(false)
254
255
256#
257# StringBuilder.toString
258#
259# Assumtion: string compression is enabled.
260#
261function(:StringBuilderToString,
262          params: {sb: 'ref', string_klass: 'ref'},
263          regmap: $full_regmap,
264          regalloc_set: $panda_mask,
265          mode: [:FastPath]) {
266
267  # Arm32 is not supported
268  if Options.arch == :arm32
269    Intrinsic(:UNREACHABLE).void.Terminator
270    next
271  end
272
273  # Compute data size and length of a new string.
274  n_chars := LoadI(sb).Imm(Constants::ETS_SB_LENGTH_OFFSET).u32
275  len_compressed := ShlI(n_chars).Imm(1).u32  # set 'uncompressed' bit to 0
276  sb_compress := LoadI(sb).Imm(Constants::ETS_SB_COMPRESS_OFFSET).u8
277  If(sb_compress, 0).EQ.Unlikely.b {
278    size := len_compressed
279    len_uncompressed := OrI(len_compressed).Imm(1).u32  # set 'uncompressed' bit to 1
280  }
281  data_size := Phi(n_chars, size).u32
282  packed_length := Phi(len_compressed, len_uncompressed).u32
283  # Allocate a string
284  new_str := allocate_string_tlab(string_klass, Cast(data_size).word)
285  # Let the memory writes (TLAB) be visible to other threads
286  Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
287  # Set new string's length
288  StoreI(new_str,packed_length).Imm(Constants::STRING_LENGTH_OFFSET).u32
289  # Set new string's hashcode to 0, so as not to spend time on its computation
290  StoreI(new_str, Cast(0).u32).Imm(Constants::STRING_HASHCODE_OFFSET).u32
291  # If string is empty, then there is nothing to do anymore
292  If(n_chars, 0).EQ.Unlikely.b {
293    Return(new_str).ptr
294  }
295
296  # Walk through the buffer elements and append their content to new_str
297  index := LoadI(sb).Imm(Constants::ETS_SB_INDEX_OFFSET).i32
298  buffer := LoadI(sb).Imm(Constants::ETS_SB_BUFFER_OFFSET).ref
299  dst_data1 := AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr
300  i1 := Cast(0).i32
301Label(:ForEachBufferSlot)
302  i := Phi(i1, i2).i32
303  dst_data := Phi(dst_data1, dst_data2).ptr
304  slot := AddI(ShlI(i).Imm(Constants::REFERENCE_TYPE_SHIFT).i32).Imm(Constants::ARRAY_DATA_OFFSET).i32
305  obj := Load(buffer, slot).ref
306  klass := LoadI(obj).Imm(Constants::OBJECT_CLASS_OFFSET).ref
307  klass_flags := LoadI(klass).Imm(Constants::BASE_CLASS_FLAGS_OFFSET).u32
308  If(AndI(klass_flags).Imm("ark::Class::STRING_CLASS").u32, 0).EQ.b {
309    Goto(:ArrayObject)
310  }
311  # -------------------
312  # Object is a string
313  # -------------------
314  str_len := LoadI(obj).Imm(Constants::STRING_LENGTH_OFFSET).i32
315  src_data := AddI(Cast(obj).SrcType(Constants::COMPILER_REFERENCE).ptr).Imm(Constants::STRING_DATA_OFFSET).ptr
316  src_len := ShrI(str_len).Imm(1).i32
317  If(sb_compress, 0).EQ.Unlikely.b {
318    Goto(:DoNotCompressString)
319  }
320  copy_u8_chars(src_data, dst_data, Cast(src_len).u64)
321  n_bytes1 := src_len
322  Goto(:NextObject)
323Label(:DoNotCompressString)
324  If(AndI(str_len).Imm(1).u32, 1).EQ.b {
325    Goto(:SrcNotCompressed)
326  }
327  expand_u8_to_u16_chars(src_data, dst_data, Cast(src_len).u64)
328  n_bytes2 := ShlI(src_len).Imm(1).i32
329  Goto(:NextObject)
330Label(:SrcNotCompressed)
331  copy_u16_chars(src_data, dst_data, Cast(src_len).u64)
332  n_bytes3 := ShlI(src_len).Imm(1).i32
333  Goto(:NextObject)
334  # -------------------
335  # Object is an array
336  # -------------------
337Label(:ArrayObject)
338  src_arr_data := AddI(Cast(obj).SrcType(Constants::COMPILER_REFERENCE).ptr).Imm(Constants::ARRAY_DATA_OFFSET).ptr
339  src_arr_len := LoadI(obj).Imm(Constants::ARRAY_LENGTH_OFFSET).i32
340  If(sb_compress, 0).EQ.Unlikely.b {
341    Goto(:DoNotCompressArray)
342  }
343  compress_u16_to_u8_chars(src_arr_data, dst_data, Cast(src_arr_len).u64)
344  n_bytes4 := src_arr_len
345  Goto(:NextObject)
346Label(:DoNotCompressArray)
347  copy_u16_chars(src_arr_data, dst_data, Cast(src_arr_len).u64)
348  n_bytes5 := ShlI(src_arr_len).Imm(1).i32
349  # Go to the next buffer slot if any
350Label(:NextObject)
351  n_bytes := Phi(n_bytes1, n_bytes2, n_bytes3, n_bytes4, n_bytes5).i32
352  dst_data2 := Add(dst_data, n_bytes).ptr
353  i2 := AddI(i).Imm(1).i32
354  If(i2, index).LT.Likely.b {
355    Goto(:ForEachBufferSlot)
356  }
357  # Everything is done
358  Return(new_str).ptr
359  # SlowPath if new string can not be allocated in TLAB
360Label(:SlowPathEntrypoint)
361  entrypoint = get_entrypoint_offset("STRING_BUILDER_TO_STRING_SLOW_PATH")
362  Intrinsic(:SLOW_PATH_ENTRY, sb).AddImm(entrypoint).MethodAsImm("StringBuilderToString2ArgBridge").Terminator.ptr
363  Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
364}
365