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