1 // Copyright 2017 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "src/builtins/builtins-string-gen.h" 6 7 #include "src/builtins/builtins-regexp-gen.h" 8 #include "src/builtins/builtins-utils-gen.h" 9 #include "src/builtins/builtins.h" 10 #include "src/code-factory.h" 11 #include "src/heap/factory-inl.h" 12 #include "src/objects.h" 13 14 namespace v8 { 15 namespace internal { 16 17 typedef compiler::Node Node; 18 template <class T> 19 using TNode = compiler::TNode<T>; 20 21 Node* StringBuiltinsAssembler::DirectStringData(Node* string, 22 Node* string_instance_type) { 23 // Compute the effective offset of the first character. 24 VARIABLE(var_data, MachineType::PointerRepresentation()); 25 Label if_sequential(this), if_external(this), if_join(this); 26 Branch(Word32Equal(Word32And(string_instance_type, 27 Int32Constant(kStringRepresentationMask)), 28 Int32Constant(kSeqStringTag)), 29 &if_sequential, &if_external); 30 31 BIND(&if_sequential); 32 { 33 var_data.Bind(IntPtrAdd( 34 IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag), 35 BitcastTaggedToWord(string))); 36 Goto(&if_join); 37 } 38 39 BIND(&if_external); 40 { 41 // This is only valid for ExternalStrings where the resource data 42 // pointer is cached (i.e. no short external strings). 43 CSA_ASSERT( 44 this, Word32NotEqual(Word32And(string_instance_type, 45 Int32Constant(kShortExternalStringMask)), 46 Int32Constant(kShortExternalStringTag))); 47 var_data.Bind(LoadObjectField(string, ExternalString::kResourceDataOffset, 48 MachineType::Pointer())); 49 Goto(&if_join); 50 } 51 52 BIND(&if_join); 53 return var_data.value(); 54 } 55 56 void StringBuiltinsAssembler::DispatchOnStringEncodings( 57 Node* const lhs_instance_type, Node* const rhs_instance_type, 58 Label* if_one_one, Label* if_one_two, Label* if_two_one, 59 Label* if_two_two) { 60 STATIC_ASSERT(kStringEncodingMask == 0x8); 61 STATIC_ASSERT(kTwoByteStringTag == 0x0); 62 STATIC_ASSERT(kOneByteStringTag == 0x8); 63 64 // First combine the encodings. 65 66 Node* const encoding_mask = Int32Constant(kStringEncodingMask); 67 Node* const lhs_encoding = Word32And(lhs_instance_type, encoding_mask); 68 Node* const rhs_encoding = Word32And(rhs_instance_type, encoding_mask); 69 70 Node* const combined_encodings = 71 Word32Or(lhs_encoding, Word32Shr(rhs_encoding, 1)); 72 73 // Then dispatch on the combined encoding. 74 75 Label unreachable(this, Label::kDeferred); 76 77 int32_t values[] = { 78 kOneByteStringTag | (kOneByteStringTag >> 1), 79 kOneByteStringTag | (kTwoByteStringTag >> 1), 80 kTwoByteStringTag | (kOneByteStringTag >> 1), 81 kTwoByteStringTag | (kTwoByteStringTag >> 1), 82 }; 83 Label* labels[] = { 84 if_one_one, if_one_two, if_two_one, if_two_two, 85 }; 86 87 STATIC_ASSERT(arraysize(values) == arraysize(labels)); 88 Switch(combined_encodings, &unreachable, values, labels, arraysize(values)); 89 90 BIND(&unreachable); 91 Unreachable(); 92 } 93 94 template <typename SubjectChar, typename PatternChar> 95 Node* StringBuiltinsAssembler::CallSearchStringRaw(Node* const subject_ptr, 96 Node* const subject_length, 97 Node* const search_ptr, 98 Node* const search_length, 99 Node* const start_position) { 100 Node* const function_addr = ExternalConstant( 101 ExternalReference::search_string_raw<SubjectChar, PatternChar>()); 102 Node* const isolate_ptr = 103 ExternalConstant(ExternalReference::isolate_address(isolate())); 104 105 MachineType type_ptr = MachineType::Pointer(); 106 MachineType type_intptr = MachineType::IntPtr(); 107 108 Node* const result = CallCFunction6( 109 type_intptr, type_ptr, type_ptr, type_intptr, type_ptr, type_intptr, 110 type_intptr, function_addr, isolate_ptr, subject_ptr, subject_length, 111 search_ptr, search_length, start_position); 112 113 return result; 114 } 115 116 Node* StringBuiltinsAssembler::PointerToStringDataAtIndex( 117 Node* const string_data, Node* const index, String::Encoding encoding) { 118 const ElementsKind kind = (encoding == String::ONE_BYTE_ENCODING) 119 ? UINT8_ELEMENTS 120 : UINT16_ELEMENTS; 121 Node* const offset_in_bytes = 122 ElementOffsetFromIndex(index, kind, INTPTR_PARAMETERS); 123 return IntPtrAdd(string_data, offset_in_bytes); 124 } 125 126 void StringBuiltinsAssembler::GenerateStringEqual(Node* context, Node* left, 127 Node* right) { 128 VARIABLE(var_left, MachineRepresentation::kTagged, left); 129 VARIABLE(var_right, MachineRepresentation::kTagged, right); 130 Label if_equal(this), if_notequal(this), if_indirect(this, Label::kDeferred), 131 restart(this, {&var_left, &var_right}); 132 133 TNode<IntPtrT> lhs_length = LoadStringLengthAsWord(left); 134 TNode<IntPtrT> rhs_length = LoadStringLengthAsWord(right); 135 136 // Strings with different lengths cannot be equal. 137 GotoIf(WordNotEqual(lhs_length, rhs_length), &if_notequal); 138 139 Goto(&restart); 140 BIND(&restart); 141 Node* lhs = var_left.value(); 142 Node* rhs = var_right.value(); 143 144 Node* lhs_instance_type = LoadInstanceType(lhs); 145 Node* rhs_instance_type = LoadInstanceType(rhs); 146 147 StringEqual_Core(context, lhs, lhs_instance_type, rhs, rhs_instance_type, 148 lhs_length, &if_equal, &if_notequal, &if_indirect); 149 150 BIND(&if_indirect); 151 { 152 // Try to unwrap indirect strings, restart the above attempt on success. 153 MaybeDerefIndirectStrings(&var_left, lhs_instance_type, &var_right, 154 rhs_instance_type, &restart); 155 156 TailCallRuntime(Runtime::kStringEqual, context, lhs, rhs); 157 } 158 159 BIND(&if_equal); 160 Return(TrueConstant()); 161 162 BIND(&if_notequal); 163 Return(FalseConstant()); 164 } 165 166 void StringBuiltinsAssembler::StringEqual_Core( 167 Node* context, Node* lhs, Node* lhs_instance_type, Node* rhs, 168 Node* rhs_instance_type, TNode<IntPtrT> length, Label* if_equal, 169 Label* if_not_equal, Label* if_indirect) { 170 CSA_ASSERT(this, IsString(lhs)); 171 CSA_ASSERT(this, IsString(rhs)); 172 CSA_ASSERT(this, WordEqual(LoadStringLengthAsWord(lhs), length)); 173 CSA_ASSERT(this, WordEqual(LoadStringLengthAsWord(rhs), length)); 174 // Fast check to see if {lhs} and {rhs} refer to the same String object. 175 GotoIf(WordEqual(lhs, rhs), if_equal); 176 177 // Combine the instance types into a single 16-bit value, so we can check 178 // both of them at once. 179 Node* both_instance_types = Word32Or( 180 lhs_instance_type, Word32Shl(rhs_instance_type, Int32Constant(8))); 181 182 // Check if both {lhs} and {rhs} are internalized. Since we already know 183 // that they're not the same object, they're not equal in that case. 184 int const kBothInternalizedMask = 185 kIsNotInternalizedMask | (kIsNotInternalizedMask << 8); 186 int const kBothInternalizedTag = kInternalizedTag | (kInternalizedTag << 8); 187 GotoIf(Word32Equal(Word32And(both_instance_types, 188 Int32Constant(kBothInternalizedMask)), 189 Int32Constant(kBothInternalizedTag)), 190 if_not_equal); 191 192 // Check if both {lhs} and {rhs} are direct strings, and that in case of 193 // ExternalStrings the data pointer is cached. 194 STATIC_ASSERT(kShortExternalStringTag != 0); 195 STATIC_ASSERT(kIsIndirectStringTag != 0); 196 int const kBothDirectStringMask = 197 kIsIndirectStringMask | kShortExternalStringMask | 198 ((kIsIndirectStringMask | kShortExternalStringMask) << 8); 199 GotoIfNot(Word32Equal(Word32And(both_instance_types, 200 Int32Constant(kBothDirectStringMask)), 201 Int32Constant(0)), 202 if_indirect); 203 204 // Dispatch based on the {lhs} and {rhs} string encoding. 205 int const kBothStringEncodingMask = 206 kStringEncodingMask | (kStringEncodingMask << 8); 207 int const kOneOneByteStringTag = kOneByteStringTag | (kOneByteStringTag << 8); 208 int const kTwoTwoByteStringTag = kTwoByteStringTag | (kTwoByteStringTag << 8); 209 int const kOneTwoByteStringTag = kOneByteStringTag | (kTwoByteStringTag << 8); 210 Label if_oneonebytestring(this), if_twotwobytestring(this), 211 if_onetwobytestring(this), if_twoonebytestring(this); 212 Node* masked_instance_types = 213 Word32And(both_instance_types, Int32Constant(kBothStringEncodingMask)); 214 GotoIf( 215 Word32Equal(masked_instance_types, Int32Constant(kOneOneByteStringTag)), 216 &if_oneonebytestring); 217 GotoIf( 218 Word32Equal(masked_instance_types, Int32Constant(kTwoTwoByteStringTag)), 219 &if_twotwobytestring); 220 Branch( 221 Word32Equal(masked_instance_types, Int32Constant(kOneTwoByteStringTag)), 222 &if_onetwobytestring, &if_twoonebytestring); 223 224 BIND(&if_oneonebytestring); 225 StringEqual_Loop(lhs, lhs_instance_type, MachineType::Uint8(), rhs, 226 rhs_instance_type, MachineType::Uint8(), length, if_equal, 227 if_not_equal); 228 229 BIND(&if_twotwobytestring); 230 StringEqual_Loop(lhs, lhs_instance_type, MachineType::Uint16(), rhs, 231 rhs_instance_type, MachineType::Uint16(), length, if_equal, 232 if_not_equal); 233 234 BIND(&if_onetwobytestring); 235 StringEqual_Loop(lhs, lhs_instance_type, MachineType::Uint8(), rhs, 236 rhs_instance_type, MachineType::Uint16(), length, if_equal, 237 if_not_equal); 238 239 BIND(&if_twoonebytestring); 240 StringEqual_Loop(lhs, lhs_instance_type, MachineType::Uint16(), rhs, 241 rhs_instance_type, MachineType::Uint8(), length, if_equal, 242 if_not_equal); 243 } 244 245 void StringBuiltinsAssembler::StringEqual_Loop( 246 Node* lhs, Node* lhs_instance_type, MachineType lhs_type, Node* rhs, 247 Node* rhs_instance_type, MachineType rhs_type, TNode<IntPtrT> length, 248 Label* if_equal, Label* if_not_equal) { 249 CSA_ASSERT(this, IsString(lhs)); 250 CSA_ASSERT(this, IsString(rhs)); 251 CSA_ASSERT(this, WordEqual(LoadStringLengthAsWord(lhs), length)); 252 CSA_ASSERT(this, WordEqual(LoadStringLengthAsWord(rhs), length)); 253 254 // Compute the effective offset of the first character. 255 Node* lhs_data = DirectStringData(lhs, lhs_instance_type); 256 Node* rhs_data = DirectStringData(rhs, rhs_instance_type); 257 258 // Loop over the {lhs} and {rhs} strings to see if they are equal. 259 TVARIABLE(IntPtrT, var_offset, IntPtrConstant(0)); 260 Label loop(this, &var_offset); 261 Goto(&loop); 262 BIND(&loop); 263 { 264 // If {offset} equals {end}, no difference was found, so the 265 // strings are equal. 266 GotoIf(WordEqual(var_offset.value(), length), if_equal); 267 268 // Load the next characters from {lhs} and {rhs}. 269 Node* lhs_value = 270 Load(lhs_type, lhs_data, 271 WordShl(var_offset.value(), 272 ElementSizeLog2Of(lhs_type.representation()))); 273 Node* rhs_value = 274 Load(rhs_type, rhs_data, 275 WordShl(var_offset.value(), 276 ElementSizeLog2Of(rhs_type.representation()))); 277 278 // Check if the characters match. 279 GotoIf(Word32NotEqual(lhs_value, rhs_value), if_not_equal); 280 281 // Advance to next character. 282 var_offset = IntPtrAdd(var_offset.value(), IntPtrConstant(1)); 283 Goto(&loop); 284 } 285 } 286 287 void StringBuiltinsAssembler::Generate_StringAdd(StringAddFlags flags, 288 PretenureFlag pretenure_flag, 289 Node* context, Node* left, 290 Node* right) { 291 switch (flags) { 292 case STRING_ADD_CONVERT_LEFT: { 293 // TODO(danno): The ToString and JSReceiverToPrimitive below could be 294 // combined to avoid duplicate smi and instance type checks. 295 left = ToString(context, JSReceiverToPrimitive(context, left)); 296 Callable callable = CodeFactory::StringAdd( 297 isolate(), STRING_ADD_CHECK_NONE, pretenure_flag); 298 TailCallStub(callable, context, left, right); 299 break; 300 } 301 case STRING_ADD_CONVERT_RIGHT: { 302 // TODO(danno): The ToString and JSReceiverToPrimitive below could be 303 // combined to avoid duplicate smi and instance type checks. 304 right = ToString(context, JSReceiverToPrimitive(context, right)); 305 Callable callable = CodeFactory::StringAdd( 306 isolate(), STRING_ADD_CHECK_NONE, pretenure_flag); 307 TailCallStub(callable, context, left, right); 308 break; 309 } 310 case STRING_ADD_CHECK_NONE: { 311 CodeStubAssembler::AllocationFlag allocation_flags = 312 (pretenure_flag == TENURED) ? CodeStubAssembler::kPretenured 313 : CodeStubAssembler::kNone; 314 Return(StringAdd(context, CAST(left), CAST(right), allocation_flags)); 315 break; 316 } 317 } 318 } 319 320 TF_BUILTIN(StringAdd_CheckNone_NotTenured, StringBuiltinsAssembler) { 321 Node* left = Parameter(Descriptor::kLeft); 322 Node* right = Parameter(Descriptor::kRight); 323 Node* context = Parameter(Descriptor::kContext); 324 Generate_StringAdd(STRING_ADD_CHECK_NONE, NOT_TENURED, context, left, right); 325 } 326 327 TF_BUILTIN(StringAdd_CheckNone_Tenured, StringBuiltinsAssembler) { 328 Node* left = Parameter(Descriptor::kLeft); 329 Node* right = Parameter(Descriptor::kRight); 330 Node* context = Parameter(Descriptor::kContext); 331 Generate_StringAdd(STRING_ADD_CHECK_NONE, TENURED, context, left, right); 332 } 333 334 TF_BUILTIN(StringAdd_ConvertLeft_NotTenured, StringBuiltinsAssembler) { 335 Node* left = Parameter(Descriptor::kLeft); 336 Node* right = Parameter(Descriptor::kRight); 337 Node* context = Parameter(Descriptor::kContext); 338 Generate_StringAdd(STRING_ADD_CONVERT_LEFT, NOT_TENURED, context, left, 339 right); 340 } 341 342 TF_BUILTIN(StringAdd_ConvertRight_NotTenured, StringBuiltinsAssembler) { 343 Node* left = Parameter(Descriptor::kLeft); 344 Node* right = Parameter(Descriptor::kRight); 345 Node* context = Parameter(Descriptor::kContext); 346 Generate_StringAdd(STRING_ADD_CONVERT_RIGHT, NOT_TENURED, context, left, 347 right); 348 } 349 350 TF_BUILTIN(SubString, StringBuiltinsAssembler) { 351 TNode<String> string = CAST(Parameter(Descriptor::kString)); 352 TNode<Smi> from = CAST(Parameter(Descriptor::kFrom)); 353 TNode<Smi> to = CAST(Parameter(Descriptor::kTo)); 354 Return(SubString(string, SmiUntag(from), SmiUntag(to))); 355 } 356 357 void StringBuiltinsAssembler::GenerateStringAt(char const* method_name, 358 TNode<Context> context, 359 Node* receiver, 360 TNode<Object> maybe_position, 361 TNode<Object> default_return, 362 StringAtAccessor accessor) { 363 // Check that {receiver} is coercible to Object and convert it to a String. 364 TNode<String> string = ToThisString(context, receiver, method_name); 365 366 // Convert the {position} to a Smi and check that it's in bounds of the 367 // {string}. 368 Label if_outofbounds(this, Label::kDeferred); 369 TNode<Number> position = ToInteger_Inline( 370 context, maybe_position, CodeStubAssembler::kTruncateMinusZero); 371 GotoIfNot(TaggedIsSmi(position), &if_outofbounds); 372 TNode<IntPtrT> index = SmiUntag(CAST(position)); 373 TNode<IntPtrT> length = LoadStringLengthAsWord(string); 374 GotoIfNot(UintPtrLessThan(index, length), &if_outofbounds); 375 TNode<Object> result = accessor(string, length, index); 376 Return(result); 377 378 BIND(&if_outofbounds); 379 Return(default_return); 380 } 381 382 void StringBuiltinsAssembler::GenerateStringRelationalComparison(Node* context, 383 Node* left, 384 Node* right, 385 Operation op) { 386 VARIABLE(var_left, MachineRepresentation::kTagged, left); 387 VARIABLE(var_right, MachineRepresentation::kTagged, right); 388 389 Variable* input_vars[2] = {&var_left, &var_right}; 390 Label if_less(this), if_equal(this), if_greater(this); 391 Label restart(this, 2, input_vars); 392 Goto(&restart); 393 BIND(&restart); 394 395 Node* lhs = var_left.value(); 396 Node* rhs = var_right.value(); 397 // Fast check to see if {lhs} and {rhs} refer to the same String object. 398 GotoIf(WordEqual(lhs, rhs), &if_equal); 399 400 // Load instance types of {lhs} and {rhs}. 401 Node* lhs_instance_type = LoadInstanceType(lhs); 402 Node* rhs_instance_type = LoadInstanceType(rhs); 403 404 // Combine the instance types into a single 16-bit value, so we can check 405 // both of them at once. 406 Node* both_instance_types = Word32Or( 407 lhs_instance_type, Word32Shl(rhs_instance_type, Int32Constant(8))); 408 409 // Check that both {lhs} and {rhs} are flat one-byte strings. 410 int const kBothSeqOneByteStringMask = 411 kStringEncodingMask | kStringRepresentationMask | 412 ((kStringEncodingMask | kStringRepresentationMask) << 8); 413 int const kBothSeqOneByteStringTag = 414 kOneByteStringTag | kSeqStringTag | 415 ((kOneByteStringTag | kSeqStringTag) << 8); 416 Label if_bothonebyteseqstrings(this), if_notbothonebyteseqstrings(this); 417 Branch(Word32Equal(Word32And(both_instance_types, 418 Int32Constant(kBothSeqOneByteStringMask)), 419 Int32Constant(kBothSeqOneByteStringTag)), 420 &if_bothonebyteseqstrings, &if_notbothonebyteseqstrings); 421 422 BIND(&if_bothonebyteseqstrings); 423 { 424 // Load the length of {lhs} and {rhs}. 425 TNode<IntPtrT> lhs_length = LoadStringLengthAsWord(lhs); 426 TNode<IntPtrT> rhs_length = LoadStringLengthAsWord(rhs); 427 428 // Determine the minimum length. 429 TNode<IntPtrT> length = IntPtrMin(lhs_length, rhs_length); 430 431 // Compute the effective offset of the first character. 432 TNode<IntPtrT> begin = 433 IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag); 434 435 // Compute the first offset after the string from the length. 436 TNode<IntPtrT> end = IntPtrAdd(begin, length); 437 438 // Loop over the {lhs} and {rhs} strings to see if they are equal. 439 TVARIABLE(IntPtrT, var_offset, begin); 440 Label loop(this, &var_offset); 441 Goto(&loop); 442 BIND(&loop); 443 { 444 // Check if {offset} equals {end}. 445 Label if_done(this), if_notdone(this); 446 Branch(WordEqual(var_offset.value(), end), &if_done, &if_notdone); 447 448 BIND(&if_notdone); 449 { 450 // Load the next characters from {lhs} and {rhs}. 451 Node* lhs_value = Load(MachineType::Uint8(), lhs, var_offset.value()); 452 Node* rhs_value = Load(MachineType::Uint8(), rhs, var_offset.value()); 453 454 // Check if the characters match. 455 Label if_valueissame(this), if_valueisnotsame(this); 456 Branch(Word32Equal(lhs_value, rhs_value), &if_valueissame, 457 &if_valueisnotsame); 458 459 BIND(&if_valueissame); 460 { 461 // Advance to next character. 462 var_offset = IntPtrAdd(var_offset.value(), IntPtrConstant(1)); 463 } 464 Goto(&loop); 465 466 BIND(&if_valueisnotsame); 467 Branch(Uint32LessThan(lhs_value, rhs_value), &if_less, &if_greater); 468 } 469 470 BIND(&if_done); 471 { 472 // All characters up to the min length are equal, decide based on 473 // string length. 474 GotoIf(IntPtrEqual(lhs_length, rhs_length), &if_equal); 475 Branch(IntPtrLessThan(lhs_length, rhs_length), &if_less, &if_greater); 476 } 477 } 478 } 479 480 BIND(&if_notbothonebyteseqstrings); 481 { 482 // Try to unwrap indirect strings, restart the above attempt on success. 483 MaybeDerefIndirectStrings(&var_left, lhs_instance_type, &var_right, 484 rhs_instance_type, &restart); 485 // TODO(bmeurer): Add support for two byte string relational comparisons. 486 switch (op) { 487 case Operation::kLessThan: 488 TailCallRuntime(Runtime::kStringLessThan, context, lhs, rhs); 489 break; 490 case Operation::kLessThanOrEqual: 491 TailCallRuntime(Runtime::kStringLessThanOrEqual, context, lhs, rhs); 492 break; 493 case Operation::kGreaterThan: 494 TailCallRuntime(Runtime::kStringGreaterThan, context, lhs, rhs); 495 break; 496 case Operation::kGreaterThanOrEqual: 497 TailCallRuntime(Runtime::kStringGreaterThanOrEqual, context, lhs, rhs); 498 break; 499 default: 500 UNREACHABLE(); 501 } 502 } 503 504 BIND(&if_less); 505 switch (op) { 506 case Operation::kLessThan: 507 case Operation::kLessThanOrEqual: 508 Return(TrueConstant()); 509 break; 510 511 case Operation::kGreaterThan: 512 case Operation::kGreaterThanOrEqual: 513 Return(FalseConstant()); 514 break; 515 default: 516 UNREACHABLE(); 517 } 518 519 BIND(&if_equal); 520 switch (op) { 521 case Operation::kLessThan: 522 case Operation::kGreaterThan: 523 Return(FalseConstant()); 524 break; 525 526 case Operation::kLessThanOrEqual: 527 case Operation::kGreaterThanOrEqual: 528 Return(TrueConstant()); 529 break; 530 default: 531 UNREACHABLE(); 532 } 533 534 BIND(&if_greater); 535 switch (op) { 536 case Operation::kLessThan: 537 case Operation::kLessThanOrEqual: 538 Return(FalseConstant()); 539 break; 540 541 case Operation::kGreaterThan: 542 case Operation::kGreaterThanOrEqual: 543 Return(TrueConstant()); 544 break; 545 default: 546 UNREACHABLE(); 547 } 548 } 549 550 TF_BUILTIN(StringEqual, StringBuiltinsAssembler) { 551 Node* context = Parameter(Descriptor::kContext); 552 Node* left = Parameter(Descriptor::kLeft); 553 Node* right = Parameter(Descriptor::kRight); 554 GenerateStringEqual(context, left, right); 555 } 556 557 TF_BUILTIN(StringLessThan, StringBuiltinsAssembler) { 558 Node* context = Parameter(Descriptor::kContext); 559 Node* left = Parameter(Descriptor::kLeft); 560 Node* right = Parameter(Descriptor::kRight); 561 GenerateStringRelationalComparison(context, left, right, 562 Operation::kLessThan); 563 } 564 565 TF_BUILTIN(StringLessThanOrEqual, StringBuiltinsAssembler) { 566 Node* context = Parameter(Descriptor::kContext); 567 Node* left = Parameter(Descriptor::kLeft); 568 Node* right = Parameter(Descriptor::kRight); 569 GenerateStringRelationalComparison(context, left, right, 570 Operation::kLessThanOrEqual); 571 } 572 573 TF_BUILTIN(StringGreaterThan, StringBuiltinsAssembler) { 574 Node* context = Parameter(Descriptor::kContext); 575 Node* left = Parameter(Descriptor::kLeft); 576 Node* right = Parameter(Descriptor::kRight); 577 GenerateStringRelationalComparison(context, left, right, 578 Operation::kGreaterThan); 579 } 580 581 TF_BUILTIN(StringGreaterThanOrEqual, StringBuiltinsAssembler) { 582 Node* context = Parameter(Descriptor::kContext); 583 Node* left = Parameter(Descriptor::kLeft); 584 Node* right = Parameter(Descriptor::kRight); 585 GenerateStringRelationalComparison(context, left, right, 586 Operation::kGreaterThanOrEqual); 587 } 588 589 TF_BUILTIN(StringCharAt, StringBuiltinsAssembler) { 590 Node* receiver = Parameter(Descriptor::kReceiver); 591 Node* position = Parameter(Descriptor::kPosition); 592 593 // Load the character code at the {position} from the {receiver}. 594 TNode<Int32T> code = StringCharCodeAt(receiver, position); 595 596 // And return the single character string with only that {code} 597 TNode<String> result = StringFromSingleCharCode(code); 598 Return(result); 599 } 600 601 TF_BUILTIN(StringCodePointAtUTF16, StringBuiltinsAssembler) { 602 Node* receiver = Parameter(Descriptor::kReceiver); 603 Node* position = Parameter(Descriptor::kPosition); 604 // TODO(sigurds) Figure out if passing length as argument pays off. 605 TNode<IntPtrT> length = LoadStringLengthAsWord(receiver); 606 // Load the character code at the {position} from the {receiver}. 607 TNode<Int32T> code = 608 LoadSurrogatePairAt(receiver, length, position, UnicodeEncoding::UTF16); 609 // And return it as TaggedSigned value. 610 // TODO(turbofan): Allow builtins to return values untagged. 611 TNode<Smi> result = SmiFromInt32(code); 612 Return(result); 613 } 614 615 TF_BUILTIN(StringCodePointAtUTF32, StringBuiltinsAssembler) { 616 Node* receiver = Parameter(Descriptor::kReceiver); 617 Node* position = Parameter(Descriptor::kPosition); 618 619 // TODO(sigurds) Figure out if passing length as argument pays off. 620 TNode<IntPtrT> length = LoadStringLengthAsWord(receiver); 621 // Load the character code at the {position} from the {receiver}. 622 TNode<Int32T> code = 623 LoadSurrogatePairAt(receiver, length, position, UnicodeEncoding::UTF32); 624 // And return it as TaggedSigned value. 625 // TODO(turbofan): Allow builtins to return values untagged. 626 TNode<Smi> result = SmiFromInt32(code); 627 Return(result); 628 } 629 630 // ----------------------------------------------------------------------------- 631 // ES6 section 21.1 String Objects 632 633 // ES6 #sec-string.fromcharcode 634 TF_BUILTIN(StringFromCharCode, CodeStubAssembler) { 635 // TODO(ishell): use constants from Descriptor once the JSFunction linkage 636 // arguments are reordered. 637 TNode<Int32T> argc = 638 UncheckedCast<Int32T>(Parameter(Descriptor::kJSActualArgumentsCount)); 639 Node* context = Parameter(Descriptor::kContext); 640 641 CodeStubArguments arguments(this, ChangeInt32ToIntPtr(argc)); 642 TNode<Smi> smi_argc = SmiTag(arguments.GetLength(INTPTR_PARAMETERS)); 643 // Check if we have exactly one argument (plus the implicit receiver), i.e. 644 // if the parent frame is not an arguments adaptor frame. 645 Label if_oneargument(this), if_notoneargument(this); 646 Branch(Word32Equal(argc, Int32Constant(1)), &if_oneargument, 647 &if_notoneargument); 648 649 BIND(&if_oneargument); 650 { 651 // Single argument case, perform fast single character string cache lookup 652 // for one-byte code units, or fall back to creating a single character 653 // string on the fly otherwise. 654 Node* code = arguments.AtIndex(0); 655 Node* code32 = TruncateTaggedToWord32(context, code); 656 TNode<Int32T> code16 = 657 Signed(Word32And(code32, Int32Constant(String::kMaxUtf16CodeUnit))); 658 Node* result = StringFromSingleCharCode(code16); 659 arguments.PopAndReturn(result); 660 } 661 662 Node* code16 = nullptr; 663 BIND(&if_notoneargument); 664 { 665 Label two_byte(this); 666 // Assume that the resulting string contains only one-byte characters. 667 Node* one_byte_result = AllocateSeqOneByteString(context, smi_argc); 668 669 TVARIABLE(IntPtrT, var_max_index); 670 var_max_index = IntPtrConstant(0); 671 672 // Iterate over the incoming arguments, converting them to 8-bit character 673 // codes. Stop if any of the conversions generates a code that doesn't fit 674 // in 8 bits. 675 CodeStubAssembler::VariableList vars({&var_max_index}, zone()); 676 arguments.ForEach(vars, [this, context, &two_byte, &var_max_index, &code16, 677 one_byte_result](Node* arg) { 678 Node* code32 = TruncateTaggedToWord32(context, arg); 679 code16 = Word32And(code32, Int32Constant(String::kMaxUtf16CodeUnit)); 680 681 GotoIf( 682 Int32GreaterThan(code16, Int32Constant(String::kMaxOneByteCharCode)), 683 &two_byte); 684 685 // The {code16} fits into the SeqOneByteString {one_byte_result}. 686 Node* offset = ElementOffsetFromIndex( 687 var_max_index.value(), UINT8_ELEMENTS, 688 CodeStubAssembler::INTPTR_PARAMETERS, 689 SeqOneByteString::kHeaderSize - kHeapObjectTag); 690 StoreNoWriteBarrier(MachineRepresentation::kWord8, one_byte_result, 691 offset, code16); 692 var_max_index = IntPtrAdd(var_max_index.value(), IntPtrConstant(1)); 693 }); 694 arguments.PopAndReturn(one_byte_result); 695 696 BIND(&two_byte); 697 698 // At least one of the characters in the string requires a 16-bit 699 // representation. Allocate a SeqTwoByteString to hold the resulting 700 // string. 701 Node* two_byte_result = AllocateSeqTwoByteString(context, smi_argc); 702 703 // Copy the characters that have already been put in the 8-bit string into 704 // their corresponding positions in the new 16-bit string. 705 TNode<IntPtrT> zero = IntPtrConstant(0); 706 CopyStringCharacters(one_byte_result, two_byte_result, zero, zero, 707 var_max_index.value(), String::ONE_BYTE_ENCODING, 708 String::TWO_BYTE_ENCODING); 709 710 // Write the character that caused the 8-bit to 16-bit fault. 711 Node* max_index_offset = 712 ElementOffsetFromIndex(var_max_index.value(), UINT16_ELEMENTS, 713 CodeStubAssembler::INTPTR_PARAMETERS, 714 SeqTwoByteString::kHeaderSize - kHeapObjectTag); 715 StoreNoWriteBarrier(MachineRepresentation::kWord16, two_byte_result, 716 max_index_offset, code16); 717 var_max_index = IntPtrAdd(var_max_index.value(), IntPtrConstant(1)); 718 719 // Resume copying the passed-in arguments from the same place where the 720 // 8-bit copy stopped, but this time copying over all of the characters 721 // using a 16-bit representation. 722 arguments.ForEach( 723 vars, 724 [this, context, two_byte_result, &var_max_index](Node* arg) { 725 Node* code32 = TruncateTaggedToWord32(context, arg); 726 Node* code16 = 727 Word32And(code32, Int32Constant(String::kMaxUtf16CodeUnit)); 728 729 Node* offset = ElementOffsetFromIndex( 730 var_max_index.value(), UINT16_ELEMENTS, 731 CodeStubAssembler::INTPTR_PARAMETERS, 732 SeqTwoByteString::kHeaderSize - kHeapObjectTag); 733 StoreNoWriteBarrier(MachineRepresentation::kWord16, two_byte_result, 734 offset, code16); 735 var_max_index = IntPtrAdd(var_max_index.value(), IntPtrConstant(1)); 736 }, 737 var_max_index.value()); 738 739 arguments.PopAndReturn(two_byte_result); 740 } 741 } 742 743 // ES6 #sec-string.prototype.charat 744 TF_BUILTIN(StringPrototypeCharAt, StringBuiltinsAssembler) { 745 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 746 Node* receiver = Parameter(Descriptor::kReceiver); 747 TNode<Object> maybe_position = CAST(Parameter(Descriptor::kPosition)); 748 749 GenerateStringAt("String.prototype.charAt", context, receiver, maybe_position, 750 EmptyStringConstant(), 751 [this](TNode<String> string, TNode<IntPtrT> length, 752 TNode<IntPtrT> index) { 753 TNode<Int32T> code = StringCharCodeAt(string, index); 754 return StringFromSingleCharCode(code); 755 }); 756 } 757 758 // ES6 #sec-string.prototype.charcodeat 759 TF_BUILTIN(StringPrototypeCharCodeAt, StringBuiltinsAssembler) { 760 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 761 Node* receiver = Parameter(Descriptor::kReceiver); 762 TNode<Object> maybe_position = CAST(Parameter(Descriptor::kPosition)); 763 764 GenerateStringAt("String.prototype.charCodeAt", context, receiver, 765 maybe_position, NanConstant(), 766 [this](TNode<String> receiver, TNode<IntPtrT> length, 767 TNode<IntPtrT> index) { 768 Node* value = StringCharCodeAt(receiver, index); 769 return SmiFromInt32(value); 770 }); 771 } 772 773 // ES6 #sec-string.prototype.codepointat 774 TF_BUILTIN(StringPrototypeCodePointAt, StringBuiltinsAssembler) { 775 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 776 Node* receiver = Parameter(Descriptor::kReceiver); 777 TNode<Object> maybe_position = CAST(Parameter(Descriptor::kPosition)); 778 779 GenerateStringAt("String.prototype.codePointAt", context, receiver, 780 maybe_position, UndefinedConstant(), 781 [this](TNode<String> receiver, TNode<IntPtrT> length, 782 TNode<IntPtrT> index) { 783 // This is always a call to a builtin from Javascript, 784 // so we need to produce UTF32. 785 Node* value = LoadSurrogatePairAt(receiver, length, index, 786 UnicodeEncoding::UTF32); 787 return SmiFromInt32(value); 788 }); 789 } 790 791 // ES6 String.prototype.concat(...args) 792 // ES6 #sec-string.prototype.concat 793 TF_BUILTIN(StringPrototypeConcat, CodeStubAssembler) { 794 // TODO(ishell): use constants from Descriptor once the JSFunction linkage 795 // arguments are reordered. 796 CodeStubArguments arguments( 797 this, 798 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount))); 799 Node* receiver = arguments.GetReceiver(); 800 Node* context = Parameter(Descriptor::kContext); 801 802 // Check that {receiver} is coercible to Object and convert it to a String. 803 receiver = ToThisString(context, receiver, "String.prototype.concat"); 804 805 // Concatenate all the arguments passed to this builtin. 806 VARIABLE(var_result, MachineRepresentation::kTagged); 807 var_result.Bind(receiver); 808 arguments.ForEach( 809 CodeStubAssembler::VariableList({&var_result}, zone()), 810 [this, context, &var_result](Node* arg) { 811 arg = ToString_Inline(context, arg); 812 var_result.Bind(CallStub(CodeFactory::StringAdd(isolate()), context, 813 var_result.value(), arg)); 814 }); 815 arguments.PopAndReturn(var_result.value()); 816 } 817 818 void StringBuiltinsAssembler::StringIndexOf( 819 Node* const subject_string, Node* const search_string, Node* const position, 820 std::function<void(Node*)> f_return) { 821 CSA_ASSERT(this, IsString(subject_string)); 822 CSA_ASSERT(this, IsString(search_string)); 823 CSA_ASSERT(this, TaggedIsSmi(position)); 824 825 TNode<IntPtrT> const int_zero = IntPtrConstant(0); 826 TNode<IntPtrT> const search_length = LoadStringLengthAsWord(search_string); 827 TNode<IntPtrT> const subject_length = LoadStringLengthAsWord(subject_string); 828 TNode<IntPtrT> const start_position = IntPtrMax(SmiUntag(position), int_zero); 829 830 Label zero_length_needle(this), return_minus_1(this); 831 { 832 GotoIf(IntPtrEqual(int_zero, search_length), &zero_length_needle); 833 834 // Check that the needle fits in the start position. 835 GotoIfNot(IntPtrLessThanOrEqual(search_length, 836 IntPtrSub(subject_length, start_position)), 837 &return_minus_1); 838 } 839 840 // If the string pointers are identical, we can just return 0. Note that this 841 // implies {start_position} == 0 since we've passed the check above. 842 Label return_zero(this); 843 GotoIf(WordEqual(subject_string, search_string), &return_zero); 844 845 // Try to unpack subject and search strings. Bail to runtime if either needs 846 // to be flattened. 847 ToDirectStringAssembler subject_to_direct(state(), subject_string); 848 ToDirectStringAssembler search_to_direct(state(), search_string); 849 850 Label call_runtime_unchecked(this, Label::kDeferred); 851 852 subject_to_direct.TryToDirect(&call_runtime_unchecked); 853 search_to_direct.TryToDirect(&call_runtime_unchecked); 854 855 // Load pointers to string data. 856 Node* const subject_ptr = 857 subject_to_direct.PointerToData(&call_runtime_unchecked); 858 Node* const search_ptr = 859 search_to_direct.PointerToData(&call_runtime_unchecked); 860 861 Node* const subject_offset = subject_to_direct.offset(); 862 Node* const search_offset = search_to_direct.offset(); 863 864 // Like String::IndexOf, the actual matching is done by the optimized 865 // SearchString method in string-search.h. Dispatch based on string instance 866 // types, then call straight into C++ for matching. 867 868 CSA_ASSERT(this, IntPtrGreaterThan(search_length, int_zero)); 869 CSA_ASSERT(this, IntPtrGreaterThanOrEqual(start_position, int_zero)); 870 CSA_ASSERT(this, IntPtrGreaterThanOrEqual(subject_length, start_position)); 871 CSA_ASSERT(this, 872 IntPtrLessThanOrEqual(search_length, 873 IntPtrSub(subject_length, start_position))); 874 875 Label one_one(this), one_two(this), two_one(this), two_two(this); 876 DispatchOnStringEncodings(subject_to_direct.instance_type(), 877 search_to_direct.instance_type(), &one_one, 878 &one_two, &two_one, &two_two); 879 880 typedef const uint8_t onebyte_t; 881 typedef const uc16 twobyte_t; 882 883 BIND(&one_one); 884 { 885 Node* const adjusted_subject_ptr = PointerToStringDataAtIndex( 886 subject_ptr, subject_offset, String::ONE_BYTE_ENCODING); 887 Node* const adjusted_search_ptr = PointerToStringDataAtIndex( 888 search_ptr, search_offset, String::ONE_BYTE_ENCODING); 889 890 Label direct_memchr_call(this), generic_fast_path(this); 891 Branch(IntPtrEqual(search_length, IntPtrConstant(1)), &direct_memchr_call, 892 &generic_fast_path); 893 894 // An additional fast path that calls directly into memchr for 1-length 895 // search strings. 896 BIND(&direct_memchr_call); 897 { 898 Node* const string_addr = IntPtrAdd(adjusted_subject_ptr, start_position); 899 Node* const search_length = IntPtrSub(subject_length, start_position); 900 Node* const search_byte = 901 ChangeInt32ToIntPtr(Load(MachineType::Uint8(), adjusted_search_ptr)); 902 903 Node* const memchr = 904 ExternalConstant(ExternalReference::libc_memchr_function()); 905 Node* const result_address = 906 CallCFunction3(MachineType::Pointer(), MachineType::Pointer(), 907 MachineType::IntPtr(), MachineType::UintPtr(), memchr, 908 string_addr, search_byte, search_length); 909 GotoIf(WordEqual(result_address, int_zero), &return_minus_1); 910 Node* const result_index = 911 IntPtrAdd(IntPtrSub(result_address, string_addr), start_position); 912 f_return(SmiTag(result_index)); 913 } 914 915 BIND(&generic_fast_path); 916 { 917 Node* const result = CallSearchStringRaw<onebyte_t, onebyte_t>( 918 adjusted_subject_ptr, subject_length, adjusted_search_ptr, 919 search_length, start_position); 920 f_return(SmiTag(result)); 921 } 922 } 923 924 BIND(&one_two); 925 { 926 Node* const adjusted_subject_ptr = PointerToStringDataAtIndex( 927 subject_ptr, subject_offset, String::ONE_BYTE_ENCODING); 928 Node* const adjusted_search_ptr = PointerToStringDataAtIndex( 929 search_ptr, search_offset, String::TWO_BYTE_ENCODING); 930 931 Node* const result = CallSearchStringRaw<onebyte_t, twobyte_t>( 932 adjusted_subject_ptr, subject_length, adjusted_search_ptr, 933 search_length, start_position); 934 f_return(SmiTag(result)); 935 } 936 937 BIND(&two_one); 938 { 939 Node* const adjusted_subject_ptr = PointerToStringDataAtIndex( 940 subject_ptr, subject_offset, String::TWO_BYTE_ENCODING); 941 Node* const adjusted_search_ptr = PointerToStringDataAtIndex( 942 search_ptr, search_offset, String::ONE_BYTE_ENCODING); 943 944 Node* const result = CallSearchStringRaw<twobyte_t, onebyte_t>( 945 adjusted_subject_ptr, subject_length, adjusted_search_ptr, 946 search_length, start_position); 947 f_return(SmiTag(result)); 948 } 949 950 BIND(&two_two); 951 { 952 Node* const adjusted_subject_ptr = PointerToStringDataAtIndex( 953 subject_ptr, subject_offset, String::TWO_BYTE_ENCODING); 954 Node* const adjusted_search_ptr = PointerToStringDataAtIndex( 955 search_ptr, search_offset, String::TWO_BYTE_ENCODING); 956 957 Node* const result = CallSearchStringRaw<twobyte_t, twobyte_t>( 958 adjusted_subject_ptr, subject_length, adjusted_search_ptr, 959 search_length, start_position); 960 f_return(SmiTag(result)); 961 } 962 963 BIND(&return_minus_1); 964 f_return(SmiConstant(-1)); 965 966 BIND(&return_zero); 967 f_return(SmiConstant(0)); 968 969 BIND(&zero_length_needle); 970 { 971 Comment("0-length search_string"); 972 f_return(SmiTag(IntPtrMin(subject_length, start_position))); 973 } 974 975 BIND(&call_runtime_unchecked); 976 { 977 // Simplified version of the runtime call where the types of the arguments 978 // are already known due to type checks in this stub. 979 Comment("Call Runtime Unchecked"); 980 Node* result = 981 CallRuntime(Runtime::kStringIndexOfUnchecked, NoContextConstant(), 982 subject_string, search_string, position); 983 f_return(result); 984 } 985 } 986 987 // ES6 String.prototype.indexOf(searchString [, position]) 988 // #sec-string.prototype.indexof 989 // Unchecked helper for builtins lowering. 990 TF_BUILTIN(StringIndexOf, StringBuiltinsAssembler) { 991 Node* receiver = Parameter(Descriptor::kReceiver); 992 Node* search_string = Parameter(Descriptor::kSearchString); 993 Node* position = Parameter(Descriptor::kPosition); 994 StringIndexOf(receiver, search_string, position, 995 [this](Node* result) { this->Return(result); }); 996 } 997 998 // ES6 String.prototype.includes(searchString [, position]) 999 // #sec-string.prototype.includes 1000 TF_BUILTIN(StringPrototypeIncludes, StringIncludesIndexOfAssembler) { 1001 TNode<IntPtrT> argc = 1002 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); 1003 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1004 Generate(kIncludes, argc, context); 1005 } 1006 1007 // ES6 String.prototype.indexOf(searchString [, position]) 1008 // #sec-string.prototype.indexof 1009 TF_BUILTIN(StringPrototypeIndexOf, StringIncludesIndexOfAssembler) { 1010 TNode<IntPtrT> argc = 1011 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); 1012 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1013 Generate(kIndexOf, argc, context); 1014 } 1015 1016 void StringIncludesIndexOfAssembler::Generate(SearchVariant variant, 1017 TNode<IntPtrT> argc, 1018 TNode<Context> context) { 1019 CodeStubArguments arguments(this, argc); 1020 Node* const receiver = arguments.GetReceiver(); 1021 1022 VARIABLE(var_search_string, MachineRepresentation::kTagged); 1023 VARIABLE(var_position, MachineRepresentation::kTagged); 1024 Label argc_1(this), argc_2(this), call_runtime(this, Label::kDeferred), 1025 fast_path(this); 1026 1027 GotoIf(IntPtrEqual(argc, IntPtrConstant(1)), &argc_1); 1028 GotoIf(IntPtrGreaterThan(argc, IntPtrConstant(1)), &argc_2); 1029 { 1030 Comment("0 Argument case"); 1031 CSA_ASSERT(this, IntPtrEqual(argc, IntPtrConstant(0))); 1032 Node* const undefined = UndefinedConstant(); 1033 var_search_string.Bind(undefined); 1034 var_position.Bind(undefined); 1035 Goto(&call_runtime); 1036 } 1037 BIND(&argc_1); 1038 { 1039 Comment("1 Argument case"); 1040 var_search_string.Bind(arguments.AtIndex(0)); 1041 var_position.Bind(SmiConstant(0)); 1042 Goto(&fast_path); 1043 } 1044 BIND(&argc_2); 1045 { 1046 Comment("2 Argument case"); 1047 var_search_string.Bind(arguments.AtIndex(0)); 1048 var_position.Bind(arguments.AtIndex(1)); 1049 GotoIfNot(TaggedIsSmi(var_position.value()), &call_runtime); 1050 Goto(&fast_path); 1051 } 1052 BIND(&fast_path); 1053 { 1054 Comment("Fast Path"); 1055 Node* const search = var_search_string.value(); 1056 Node* const position = var_position.value(); 1057 GotoIf(TaggedIsSmi(receiver), &call_runtime); 1058 GotoIf(TaggedIsSmi(search), &call_runtime); 1059 GotoIfNot(IsString(receiver), &call_runtime); 1060 GotoIfNot(IsString(search), &call_runtime); 1061 1062 StringIndexOf(receiver, search, position, [&](Node* result) { 1063 CSA_ASSERT(this, TaggedIsSmi(result)); 1064 arguments.PopAndReturn((variant == kIndexOf) 1065 ? result 1066 : SelectBooleanConstant(SmiGreaterThanOrEqual( 1067 CAST(result), SmiConstant(0)))); 1068 }); 1069 } 1070 BIND(&call_runtime); 1071 { 1072 Comment("Call Runtime"); 1073 Runtime::FunctionId runtime = variant == kIndexOf 1074 ? Runtime::kStringIndexOf 1075 : Runtime::kStringIncludes; 1076 Node* const result = 1077 CallRuntime(runtime, context, receiver, var_search_string.value(), 1078 var_position.value()); 1079 arguments.PopAndReturn(result); 1080 } 1081 } 1082 1083 void StringBuiltinsAssembler::RequireObjectCoercible(Node* const context, 1084 Node* const value, 1085 const char* method_name) { 1086 Label out(this), throw_exception(this, Label::kDeferred); 1087 Branch(IsNullOrUndefined(value), &throw_exception, &out); 1088 1089 BIND(&throw_exception); 1090 ThrowTypeError(context, MessageTemplate::kCalledOnNullOrUndefined, 1091 method_name); 1092 1093 BIND(&out); 1094 } 1095 1096 void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol( 1097 Node* const context, Node* const object, Node* const maybe_string, 1098 Handle<Symbol> symbol, const NodeFunction0& regexp_call, 1099 const NodeFunction1& generic_call) { 1100 Label out(this); 1101 1102 // Smis definitely don't have an attached symbol. 1103 GotoIf(TaggedIsSmi(object), &out); 1104 1105 Node* const object_map = LoadMap(object); 1106 1107 // Skip the slow lookup for Strings. 1108 { 1109 Label next(this); 1110 1111 GotoIfNot(IsStringInstanceType(LoadMapInstanceType(object_map)), &next); 1112 1113 Node* const native_context = LoadNativeContext(context); 1114 Node* const initial_proto_initial_map = LoadContextElement( 1115 native_context, Context::STRING_FUNCTION_PROTOTYPE_MAP_INDEX); 1116 1117 Node* const string_fun = 1118 LoadContextElement(native_context, Context::STRING_FUNCTION_INDEX); 1119 Node* const initial_map = 1120 LoadObjectField(string_fun, JSFunction::kPrototypeOrInitialMapOffset); 1121 Node* const proto_map = LoadMap(LoadMapPrototype(initial_map)); 1122 1123 Branch(WordEqual(proto_map, initial_proto_initial_map), &out, &next); 1124 1125 BIND(&next); 1126 } 1127 1128 // Take the fast path for RegExps. 1129 // There's two conditions: {object} needs to be a fast regexp, and 1130 // {maybe_string} must be a string (we can't call ToString on the fast path 1131 // since it may mutate {object}). 1132 { 1133 Label stub_call(this), slow_lookup(this); 1134 1135 GotoIf(TaggedIsSmi(maybe_string), &slow_lookup); 1136 GotoIfNot(IsString(maybe_string), &slow_lookup); 1137 1138 RegExpBuiltinsAssembler regexp_asm(state()); 1139 regexp_asm.BranchIfFastRegExp(context, object, object_map, &stub_call, 1140 &slow_lookup); 1141 1142 BIND(&stub_call); 1143 // TODO(jgruber): Add a no-JS scope once it exists. 1144 regexp_call(); 1145 1146 BIND(&slow_lookup); 1147 } 1148 1149 GotoIf(IsNullOrUndefined(object), &out); 1150 1151 // Fall back to a slow lookup of {object[symbol]}. 1152 // 1153 // The spec uses GetMethod({object}, {symbol}), which has a few quirks: 1154 // * null values are turned into undefined, and 1155 // * an exception is thrown if the value is not undefined, null, or callable. 1156 // We handle the former by jumping to {out} for null values as well, while 1157 // the latter is already handled by the Call({maybe_func}) operation. 1158 1159 Node* const maybe_func = GetProperty(context, object, symbol); 1160 GotoIf(IsUndefined(maybe_func), &out); 1161 GotoIf(IsNull(maybe_func), &out); 1162 1163 // Attempt to call the function. 1164 generic_call(maybe_func); 1165 1166 BIND(&out); 1167 } 1168 1169 TNode<Smi> StringBuiltinsAssembler::IndexOfDollarChar(Node* const context, 1170 Node* const string) { 1171 CSA_ASSERT(this, IsString(string)); 1172 1173 TNode<String> const dollar_string = HeapConstant( 1174 isolate()->factory()->LookupSingleCharacterStringFromCode('$')); 1175 TNode<Smi> const dollar_ix = 1176 CAST(CallBuiltin(Builtins::kStringIndexOf, context, string, dollar_string, 1177 SmiConstant(0))); 1178 return dollar_ix; 1179 } 1180 1181 compiler::Node* StringBuiltinsAssembler::GetSubstitution( 1182 Node* context, Node* subject_string, Node* match_start_index, 1183 Node* match_end_index, Node* replace_string) { 1184 CSA_ASSERT(this, IsString(subject_string)); 1185 CSA_ASSERT(this, IsString(replace_string)); 1186 CSA_ASSERT(this, TaggedIsPositiveSmi(match_start_index)); 1187 CSA_ASSERT(this, TaggedIsPositiveSmi(match_end_index)); 1188 1189 VARIABLE(var_result, MachineRepresentation::kTagged, replace_string); 1190 Label runtime(this), out(this); 1191 1192 // In this primitive implementation we simply look for the next '$' char in 1193 // {replace_string}. If it doesn't exist, we can simply return 1194 // {replace_string} itself. If it does, then we delegate to 1195 // String::GetSubstitution, passing in the index of the first '$' to avoid 1196 // repeated scanning work. 1197 // TODO(jgruber): Possibly extend this in the future to handle more complex 1198 // cases without runtime calls. 1199 1200 TNode<Smi> const dollar_index = IndexOfDollarChar(context, replace_string); 1201 Branch(SmiIsNegative(dollar_index), &out, &runtime); 1202 1203 BIND(&runtime); 1204 { 1205 CSA_ASSERT(this, TaggedIsPositiveSmi(dollar_index)); 1206 1207 Node* const matched = 1208 CallBuiltin(Builtins::kStringSubstring, context, subject_string, 1209 SmiUntag(match_start_index), SmiUntag(match_end_index)); 1210 Node* const replacement_string = 1211 CallRuntime(Runtime::kGetSubstitution, context, matched, subject_string, 1212 match_start_index, replace_string, dollar_index); 1213 var_result.Bind(replacement_string); 1214 1215 Goto(&out); 1216 } 1217 1218 BIND(&out); 1219 return var_result.value(); 1220 } 1221 1222 // ES6 #sec-string.prototype.repeat 1223 TF_BUILTIN(StringPrototypeRepeat, StringBuiltinsAssembler) { 1224 Label invalid_count(this), invalid_string_length(this), 1225 return_emptystring(this); 1226 1227 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1228 Node* const receiver = Parameter(Descriptor::kReceiver); 1229 TNode<Object> count = CAST(Parameter(Descriptor::kCount)); 1230 Node* const string = 1231 ToThisString(context, receiver, "String.prototype.repeat"); 1232 Node* const is_stringempty = 1233 SmiEqual(LoadStringLengthAsSmi(string), SmiConstant(0)); 1234 1235 VARIABLE( 1236 var_count, MachineRepresentation::kTagged, 1237 ToInteger_Inline(context, count, CodeStubAssembler::kTruncateMinusZero)); 1238 1239 // Verifies a valid count and takes a fast path when the result will be an 1240 // empty string. 1241 { 1242 Label if_count_isheapnumber(this, Label::kDeferred); 1243 1244 GotoIfNot(TaggedIsSmi(var_count.value()), &if_count_isheapnumber); 1245 { 1246 // If count is a SMI, throw a RangeError if less than 0 or greater than 1247 // the maximum string length. 1248 TNode<Smi> smi_count = CAST(var_count.value()); 1249 GotoIf(SmiLessThan(smi_count, SmiConstant(0)), &invalid_count); 1250 GotoIf(SmiEqual(smi_count, SmiConstant(0)), &return_emptystring); 1251 GotoIf(is_stringempty, &return_emptystring); 1252 GotoIf(SmiGreaterThan(smi_count, SmiConstant(String::kMaxLength)), 1253 &invalid_string_length); 1254 Return(CallBuiltin(Builtins::kStringRepeat, context, string, smi_count)); 1255 } 1256 1257 // If count is a Heap Number... 1258 // 1) If count is Infinity, throw a RangeError exception 1259 // 2) If receiver is an empty string, return an empty string 1260 // 3) Otherwise, throw RangeError exception 1261 BIND(&if_count_isheapnumber); 1262 { 1263 CSA_ASSERT(this, IsNumberNormalized(var_count.value())); 1264 Node* const number_value = LoadHeapNumberValue(var_count.value()); 1265 GotoIf(Float64Equal(number_value, Float64Constant(V8_INFINITY)), 1266 &invalid_count); 1267 GotoIf(Float64LessThan(number_value, Float64Constant(0.0)), 1268 &invalid_count); 1269 Branch(is_stringempty, &return_emptystring, &invalid_string_length); 1270 } 1271 } 1272 1273 BIND(&return_emptystring); 1274 Return(EmptyStringConstant()); 1275 1276 BIND(&invalid_count); 1277 { 1278 ThrowRangeError(context, MessageTemplate::kInvalidCountValue, 1279 var_count.value()); 1280 } 1281 1282 BIND(&invalid_string_length); 1283 { 1284 CallRuntime(Runtime::kThrowInvalidStringLength, context); 1285 Unreachable(); 1286 } 1287 } 1288 1289 // Helper with less checks 1290 TF_BUILTIN(StringRepeat, StringBuiltinsAssembler) { 1291 Node* const context = Parameter(Descriptor::kContext); 1292 Node* const string = Parameter(Descriptor::kString); 1293 TNode<Smi> const count = CAST(Parameter(Descriptor::kCount)); 1294 1295 CSA_ASSERT(this, IsString(string)); 1296 CSA_ASSERT(this, Word32BinaryNot(IsEmptyString(string))); 1297 CSA_ASSERT(this, TaggedIsPositiveSmi(count)); 1298 CSA_ASSERT(this, SmiLessThanOrEqual(count, SmiConstant(String::kMaxLength))); 1299 1300 // The string is repeated with the following algorithm: 1301 // let n = count; 1302 // let power_of_two_repeats = string; 1303 // let result = ""; 1304 // while (true) { 1305 // if (n & 1) result += s; 1306 // n >>= 1; 1307 // if (n === 0) return result; 1308 // power_of_two_repeats += power_of_two_repeats; 1309 // } 1310 VARIABLE(var_result, MachineRepresentation::kTagged, EmptyStringConstant()); 1311 VARIABLE(var_temp, MachineRepresentation::kTagged, string); 1312 TVARIABLE(Smi, var_count, count); 1313 1314 Callable stringadd_callable = 1315 CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED); 1316 1317 Label loop(this, {&var_count, &var_result, &var_temp}), return_result(this); 1318 Goto(&loop); 1319 BIND(&loop); 1320 { 1321 { 1322 Label next(this); 1323 GotoIfNot(SmiToInt32(SmiAnd(var_count.value(), SmiConstant(1))), &next); 1324 var_result.Bind(CallStub(stringadd_callable, context, var_result.value(), 1325 var_temp.value())); 1326 Goto(&next); 1327 BIND(&next); 1328 } 1329 1330 var_count = SmiShr(var_count.value(), 1); 1331 GotoIf(SmiEqual(var_count.value(), SmiConstant(0)), &return_result); 1332 var_temp.Bind(CallStub(stringadd_callable, context, var_temp.value(), 1333 var_temp.value())); 1334 Goto(&loop); 1335 } 1336 1337 BIND(&return_result); 1338 Return(var_result.value()); 1339 } 1340 1341 // ES6 #sec-string.prototype.replace 1342 TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) { 1343 Label out(this); 1344 1345 Node* const receiver = Parameter(Descriptor::kReceiver); 1346 Node* const search = Parameter(Descriptor::kSearch); 1347 Node* const replace = Parameter(Descriptor::kReplace); 1348 Node* const context = Parameter(Descriptor::kContext); 1349 1350 TNode<Smi> const smi_zero = SmiConstant(0); 1351 1352 RequireObjectCoercible(context, receiver, "String.prototype.replace"); 1353 1354 // Redirect to replacer method if {search[@@replace]} is not undefined. 1355 1356 MaybeCallFunctionAtSymbol( 1357 context, search, receiver, isolate()->factory()->replace_symbol(), 1358 [=]() { 1359 Return(CallBuiltin(Builtins::kRegExpReplace, context, search, receiver, 1360 replace)); 1361 }, 1362 [=](Node* fn) { 1363 Callable call_callable = CodeFactory::Call(isolate()); 1364 Return(CallJS(call_callable, context, fn, search, receiver, replace)); 1365 }); 1366 1367 // Convert {receiver} and {search} to strings. 1368 1369 TNode<String> const subject_string = ToString_Inline(context, receiver); 1370 TNode<String> const search_string = ToString_Inline(context, search); 1371 1372 TNode<Smi> const subject_length = LoadStringLengthAsSmi(subject_string); 1373 TNode<Smi> const search_length = LoadStringLengthAsSmi(search_string); 1374 1375 // Fast-path single-char {search}, long cons {receiver}, and simple string 1376 // {replace}. 1377 { 1378 Label next(this); 1379 1380 GotoIfNot(SmiEqual(search_length, SmiConstant(1)), &next); 1381 GotoIfNot(SmiGreaterThan(subject_length, SmiConstant(0xFF)), &next); 1382 GotoIf(TaggedIsSmi(replace), &next); 1383 GotoIfNot(IsString(replace), &next); 1384 1385 Node* const subject_instance_type = LoadInstanceType(subject_string); 1386 GotoIfNot(IsConsStringInstanceType(subject_instance_type), &next); 1387 1388 GotoIf(TaggedIsPositiveSmi(IndexOfDollarChar(context, replace)), &next); 1389 1390 // Searching by traversing a cons string tree and replace with cons of 1391 // slices works only when the replaced string is a single character, being 1392 // replaced by a simple string and only pays off for long strings. 1393 // TODO(jgruber): Reevaluate if this is still beneficial. 1394 // TODO(jgruber): TailCallRuntime when it correctly handles adapter frames. 1395 Return(CallRuntime(Runtime::kStringReplaceOneCharWithString, context, 1396 subject_string, search_string, replace)); 1397 1398 BIND(&next); 1399 } 1400 1401 // TODO(jgruber): Extend StringIndexOf to handle two-byte strings and 1402 // longer substrings - we can handle up to 8 chars (one-byte) / 4 chars 1403 // (2-byte). 1404 1405 TNode<Smi> const match_start_index = 1406 CAST(CallBuiltin(Builtins::kStringIndexOf, context, subject_string, 1407 search_string, smi_zero)); 1408 1409 // Early exit if no match found. 1410 { 1411 Label next(this), return_subject(this); 1412 1413 GotoIfNot(SmiIsNegative(match_start_index), &next); 1414 1415 // The spec requires to perform ToString(replace) if the {replace} is not 1416 // callable even if we are going to exit here. 1417 // Since ToString() being applied to Smi does not have side effects for 1418 // numbers we can skip it. 1419 GotoIf(TaggedIsSmi(replace), &return_subject); 1420 GotoIf(IsCallableMap(LoadMap(replace)), &return_subject); 1421 1422 // TODO(jgruber): Could introduce ToStringSideeffectsStub which only 1423 // performs observable parts of ToString. 1424 ToString_Inline(context, replace); 1425 Goto(&return_subject); 1426 1427 BIND(&return_subject); 1428 Return(subject_string); 1429 1430 BIND(&next); 1431 } 1432 1433 TNode<Smi> const match_end_index = SmiAdd(match_start_index, search_length); 1434 1435 Callable stringadd_callable = 1436 CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED); 1437 1438 VARIABLE(var_result, MachineRepresentation::kTagged, EmptyStringConstant()); 1439 1440 // Compute the prefix. 1441 { 1442 Label next(this); 1443 1444 GotoIf(SmiEqual(match_start_index, smi_zero), &next); 1445 Node* const prefix = 1446 CallBuiltin(Builtins::kStringSubstring, context, subject_string, 1447 IntPtrConstant(0), SmiUntag(match_start_index)); 1448 var_result.Bind(prefix); 1449 1450 Goto(&next); 1451 BIND(&next); 1452 } 1453 1454 // Compute the string to replace with. 1455 1456 Label if_iscallablereplace(this), if_notcallablereplace(this); 1457 GotoIf(TaggedIsSmi(replace), &if_notcallablereplace); 1458 Branch(IsCallableMap(LoadMap(replace)), &if_iscallablereplace, 1459 &if_notcallablereplace); 1460 1461 BIND(&if_iscallablereplace); 1462 { 1463 Callable call_callable = CodeFactory::Call(isolate()); 1464 Node* const replacement = 1465 CallJS(call_callable, context, replace, UndefinedConstant(), 1466 search_string, match_start_index, subject_string); 1467 Node* const replacement_string = ToString_Inline(context, replacement); 1468 var_result.Bind(CallStub(stringadd_callable, context, var_result.value(), 1469 replacement_string)); 1470 Goto(&out); 1471 } 1472 1473 BIND(&if_notcallablereplace); 1474 { 1475 Node* const replace_string = ToString_Inline(context, replace); 1476 Node* const replacement = 1477 GetSubstitution(context, subject_string, match_start_index, 1478 match_end_index, replace_string); 1479 var_result.Bind( 1480 CallStub(stringadd_callable, context, var_result.value(), replacement)); 1481 Goto(&out); 1482 } 1483 1484 BIND(&out); 1485 { 1486 Node* const suffix = 1487 CallBuiltin(Builtins::kStringSubstring, context, subject_string, 1488 SmiUntag(match_end_index), SmiUntag(subject_length)); 1489 Node* const result = 1490 CallStub(stringadd_callable, context, var_result.value(), suffix); 1491 Return(result); 1492 } 1493 } 1494 1495 class StringMatchSearchAssembler : public StringBuiltinsAssembler { 1496 public: 1497 explicit StringMatchSearchAssembler(compiler::CodeAssemblerState* state) 1498 : StringBuiltinsAssembler(state) {} 1499 1500 protected: 1501 enum Variant { kMatch, kSearch }; 1502 1503 void Generate(Variant variant, const char* method_name, 1504 TNode<Object> receiver, TNode<Object> maybe_regexp, 1505 TNode<Context> context) { 1506 Label call_regexp_match_search(this); 1507 1508 Builtins::Name builtin; 1509 Handle<Symbol> symbol; 1510 if (variant == kMatch) { 1511 builtin = Builtins::kRegExpMatchFast; 1512 symbol = isolate()->factory()->match_symbol(); 1513 } else { 1514 builtin = Builtins::kRegExpSearchFast; 1515 symbol = isolate()->factory()->search_symbol(); 1516 } 1517 1518 RequireObjectCoercible(context, receiver, method_name); 1519 1520 MaybeCallFunctionAtSymbol( 1521 context, maybe_regexp, receiver, symbol, 1522 [=] { Return(CallBuiltin(builtin, context, maybe_regexp, receiver)); }, 1523 [=](Node* fn) { 1524 Callable call_callable = CodeFactory::Call(isolate()); 1525 Return(CallJS(call_callable, context, fn, maybe_regexp, receiver)); 1526 }); 1527 1528 // maybe_regexp is not a RegExp nor has [@@match / @@search] property. 1529 { 1530 RegExpBuiltinsAssembler regexp_asm(state()); 1531 1532 TNode<String> receiver_string = ToString_Inline(context, receiver); 1533 TNode<Context> native_context = LoadNativeContext(context); 1534 TNode<HeapObject> regexp_function = CAST( 1535 LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX)); 1536 TNode<Map> initial_map = CAST(LoadObjectField( 1537 regexp_function, JSFunction::kPrototypeOrInitialMapOffset)); 1538 TNode<Object> regexp = regexp_asm.RegExpCreate( 1539 context, initial_map, maybe_regexp, EmptyStringConstant()); 1540 1541 Label fast_path(this), slow_path(this); 1542 regexp_asm.BranchIfFastRegExp(context, regexp, initial_map, &fast_path, 1543 &slow_path); 1544 1545 BIND(&fast_path); 1546 Return(CallBuiltin(builtin, context, regexp, receiver_string)); 1547 1548 BIND(&slow_path); 1549 { 1550 TNode<Object> maybe_func = GetProperty(context, regexp, symbol); 1551 Callable call_callable = CodeFactory::Call(isolate()); 1552 Return(CallJS(call_callable, context, maybe_func, regexp, 1553 receiver_string)); 1554 } 1555 } 1556 } 1557 }; 1558 1559 // ES6 #sec-string.prototype.match 1560 TF_BUILTIN(StringPrototypeMatch, StringMatchSearchAssembler) { 1561 TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); 1562 TNode<Object> maybe_regexp = CAST(Parameter(Descriptor::kRegexp)); 1563 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1564 1565 Generate(kMatch, "String.prototype.match", receiver, maybe_regexp, context); 1566 } 1567 1568 // ES #sec-string.prototype.matchAll 1569 TF_BUILTIN(StringPrototypeMatchAll, StringBuiltinsAssembler) { 1570 char const* method_name = "String.prototype.matchAll"; 1571 1572 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1573 TNode<Object> maybe_regexp = CAST(Parameter(Descriptor::kRegexp)); 1574 TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); 1575 TNode<Context> native_context = LoadNativeContext(context); 1576 1577 // 1. Let O be ? RequireObjectCoercible(this value). 1578 RequireObjectCoercible(context, receiver, method_name); 1579 1580 // 2. If regexp is neither undefined nor null, then 1581 Label return_match_all_iterator(this), 1582 tostring_and_return_match_all_iterator(this, Label::kDeferred); 1583 TVARIABLE(BoolT, var_is_fast_regexp); 1584 TVARIABLE(String, var_receiver_string); 1585 GotoIf(IsNullOrUndefined(maybe_regexp), 1586 &tostring_and_return_match_all_iterator); 1587 { 1588 // a. Let matcher be ? GetMethod(regexp, @@matchAll). 1589 // b. If matcher is not undefined, then 1590 // i. Return ? Call(matcher, regexp, O ). 1591 auto if_regexp_call = [&] { 1592 // MaybeCallFunctionAtSymbol guarantees fast path is chosen only if 1593 // maybe_regexp is a fast regexp and receiver is a string. 1594 var_receiver_string = CAST(receiver); 1595 CSA_ASSERT(this, IsString(var_receiver_string.value())); 1596 var_is_fast_regexp = Int32TrueConstant(); 1597 Goto(&return_match_all_iterator); 1598 }; 1599 auto if_generic_call = [=](Node* fn) { 1600 Callable call_callable = CodeFactory::Call(isolate()); 1601 Return(CallJS(call_callable, context, fn, maybe_regexp, receiver)); 1602 }; 1603 MaybeCallFunctionAtSymbol(context, maybe_regexp, receiver, 1604 isolate()->factory()->match_all_symbol(), 1605 if_regexp_call, if_generic_call); 1606 Goto(&tostring_and_return_match_all_iterator); 1607 } 1608 BIND(&tostring_and_return_match_all_iterator); 1609 { 1610 var_receiver_string = ToString_Inline(context, receiver); 1611 var_is_fast_regexp = Int32FalseConstant(); 1612 Goto(&return_match_all_iterator); 1613 } 1614 BIND(&return_match_all_iterator); 1615 { 1616 // 3. Return ? MatchAllIterator(regexp, O). 1617 RegExpBuiltinsAssembler regexp_asm(state()); 1618 TNode<Object> iterator = regexp_asm.MatchAllIterator( 1619 context, native_context, maybe_regexp, var_receiver_string.value(), 1620 var_is_fast_regexp.value(), method_name); 1621 Return(iterator); 1622 } 1623 } 1624 1625 class StringPadAssembler : public StringBuiltinsAssembler { 1626 public: 1627 explicit StringPadAssembler(compiler::CodeAssemblerState* state) 1628 : StringBuiltinsAssembler(state) {} 1629 1630 protected: 1631 enum Variant { kStart, kEnd }; 1632 1633 void Generate(Variant variant, const char* method_name, TNode<IntPtrT> argc, 1634 TNode<Context> context) { 1635 CodeStubArguments arguments(this, argc); 1636 Node* const receiver = arguments.GetReceiver(); 1637 Node* const receiver_string = ToThisString(context, receiver, method_name); 1638 TNode<Smi> const string_length = LoadStringLengthAsSmi(receiver_string); 1639 1640 TVARIABLE(String, var_fill_string, StringConstant(" ")); 1641 TVARIABLE(IntPtrT, var_fill_length, IntPtrConstant(1)); 1642 1643 Label check_fill(this), dont_pad(this), invalid_string_length(this), 1644 pad(this); 1645 1646 // If no max_length was provided, return the string. 1647 GotoIf(IntPtrEqual(argc, IntPtrConstant(0)), &dont_pad); 1648 1649 TNode<Number> const max_length = 1650 ToLength_Inline(context, arguments.AtIndex(0)); 1651 CSA_ASSERT(this, IsNumberNormalized(max_length)); 1652 1653 // If max_length <= string_length, return the string. 1654 GotoIfNot(TaggedIsSmi(max_length), &check_fill); 1655 Branch(SmiLessThanOrEqual(CAST(max_length), string_length), &dont_pad, 1656 &check_fill); 1657 1658 BIND(&check_fill); 1659 { 1660 GotoIf(IntPtrEqual(argc, IntPtrConstant(1)), &pad); 1661 Node* const fill = arguments.AtIndex(1); 1662 GotoIf(IsUndefined(fill), &pad); 1663 1664 var_fill_string = ToString_Inline(context, fill); 1665 var_fill_length = LoadStringLengthAsWord(var_fill_string.value()); 1666 Branch(WordEqual(var_fill_length.value(), IntPtrConstant(0)), &dont_pad, 1667 &pad); 1668 } 1669 1670 BIND(&pad); 1671 { 1672 CSA_ASSERT(this, 1673 IntPtrGreaterThan(var_fill_length.value(), IntPtrConstant(0))); 1674 1675 // Throw if max_length is greater than String::kMaxLength. 1676 GotoIfNot(TaggedIsSmi(max_length), &invalid_string_length); 1677 TNode<Smi> smi_max_length = CAST(max_length); 1678 GotoIfNot( 1679 SmiLessThanOrEqual(smi_max_length, SmiConstant(String::kMaxLength)), 1680 &invalid_string_length); 1681 1682 Callable stringadd_callable = 1683 CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED); 1684 CSA_ASSERT(this, SmiGreaterThan(smi_max_length, string_length)); 1685 TNode<Smi> const pad_length = SmiSub(smi_max_length, string_length); 1686 1687 VARIABLE(var_pad, MachineRepresentation::kTagged); 1688 Label single_char_fill(this), multi_char_fill(this), return_result(this); 1689 Branch(IntPtrEqual(var_fill_length.value(), IntPtrConstant(1)), 1690 &single_char_fill, &multi_char_fill); 1691 1692 // Fast path for a single character fill. No need to calculate number of 1693 // repetitions or remainder. 1694 BIND(&single_char_fill); 1695 { 1696 var_pad.Bind(CallBuiltin(Builtins::kStringRepeat, context, 1697 static_cast<Node*>(var_fill_string.value()), 1698 pad_length)); 1699 Goto(&return_result); 1700 } 1701 BIND(&multi_char_fill); 1702 { 1703 TNode<Int32T> const fill_length_word32 = 1704 TruncateIntPtrToInt32(var_fill_length.value()); 1705 TNode<Int32T> const pad_length_word32 = SmiToInt32(pad_length); 1706 TNode<Int32T> const repetitions_word32 = 1707 Int32Div(pad_length_word32, fill_length_word32); 1708 TNode<Int32T> const remaining_word32 = 1709 Int32Mod(pad_length_word32, fill_length_word32); 1710 1711 var_pad.Bind(CallBuiltin(Builtins::kStringRepeat, context, 1712 var_fill_string.value(), 1713 SmiFromInt32(repetitions_word32))); 1714 1715 GotoIfNot(remaining_word32, &return_result); 1716 { 1717 Node* const remainder_string = CallBuiltin( 1718 Builtins::kStringSubstring, context, var_fill_string.value(), 1719 IntPtrConstant(0), ChangeInt32ToIntPtr(remaining_word32)); 1720 var_pad.Bind(CallStub(stringadd_callable, context, var_pad.value(), 1721 remainder_string)); 1722 Goto(&return_result); 1723 } 1724 } 1725 BIND(&return_result); 1726 CSA_ASSERT(this, 1727 SmiEqual(pad_length, LoadStringLengthAsSmi(var_pad.value()))); 1728 arguments.PopAndReturn(variant == kStart 1729 ? CallStub(stringadd_callable, context, 1730 var_pad.value(), receiver_string) 1731 : CallStub(stringadd_callable, context, 1732 receiver_string, var_pad.value())); 1733 } 1734 BIND(&dont_pad); 1735 arguments.PopAndReturn(receiver_string); 1736 BIND(&invalid_string_length); 1737 { 1738 CallRuntime(Runtime::kThrowInvalidStringLength, context); 1739 Unreachable(); 1740 } 1741 } 1742 }; 1743 1744 TF_BUILTIN(StringPrototypePadEnd, StringPadAssembler) { 1745 TNode<IntPtrT> argc = 1746 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); 1747 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1748 1749 Generate(kEnd, "String.prototype.padEnd", argc, context); 1750 } 1751 1752 TF_BUILTIN(StringPrototypePadStart, StringPadAssembler) { 1753 TNode<IntPtrT> argc = 1754 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); 1755 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1756 1757 Generate(kStart, "String.prototype.padStart", argc, context); 1758 } 1759 1760 // ES6 #sec-string.prototype.search 1761 TF_BUILTIN(StringPrototypeSearch, StringMatchSearchAssembler) { 1762 TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); 1763 TNode<Object> maybe_regexp = CAST(Parameter(Descriptor::kRegexp)); 1764 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1765 Generate(kSearch, "String.prototype.search", receiver, maybe_regexp, context); 1766 } 1767 1768 // ES6 section 21.1.3.18 String.prototype.slice ( start, end ) 1769 TF_BUILTIN(StringPrototypeSlice, StringBuiltinsAssembler) { 1770 Label out(this); 1771 TVARIABLE(IntPtrT, var_start); 1772 TVARIABLE(IntPtrT, var_end); 1773 1774 const int kStart = 0; 1775 const int kEnd = 1; 1776 Node* argc = 1777 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); 1778 CodeStubArguments args(this, argc); 1779 Node* const receiver = args.GetReceiver(); 1780 TNode<Object> start = args.GetOptionalArgumentValue(kStart); 1781 TNode<Object> end = args.GetOptionalArgumentValue(kEnd); 1782 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1783 1784 // 1. Let O be ? RequireObjectCoercible(this value). 1785 RequireObjectCoercible(context, receiver, "String.prototype.slice"); 1786 1787 // 2. Let S be ? ToString(O). 1788 TNode<String> const subject_string = 1789 CAST(CallBuiltin(Builtins::kToString, context, receiver)); 1790 1791 // 3. Let len be the number of elements in S. 1792 TNode<IntPtrT> const length = LoadStringLengthAsWord(subject_string); 1793 1794 // Convert {start} to a relative index. 1795 var_start = ConvertToRelativeIndex(context, start, length); 1796 1797 // 5. If end is undefined, let intEnd be len; 1798 var_end = length; 1799 GotoIf(IsUndefined(end), &out); 1800 1801 // Convert {end} to a relative index. 1802 var_end = ConvertToRelativeIndex(context, end, length); 1803 Goto(&out); 1804 1805 Label return_emptystring(this); 1806 BIND(&out); 1807 { 1808 GotoIf(IntPtrLessThanOrEqual(var_end.value(), var_start.value()), 1809 &return_emptystring); 1810 TNode<String> const result = 1811 SubString(subject_string, var_start.value(), var_end.value()); 1812 args.PopAndReturn(result); 1813 } 1814 1815 BIND(&return_emptystring); 1816 args.PopAndReturn(EmptyStringConstant()); 1817 } 1818 1819 TNode<JSArray> StringBuiltinsAssembler::StringToArray( 1820 TNode<Context> context, TNode<String> subject_string, 1821 TNode<Smi> subject_length, TNode<Number> limit_number) { 1822 CSA_ASSERT(this, SmiGreaterThan(subject_length, SmiConstant(0))); 1823 1824 Label done(this), call_runtime(this, Label::kDeferred), 1825 fill_thehole_and_call_runtime(this, Label::kDeferred); 1826 TVARIABLE(JSArray, result_array); 1827 1828 TNode<Int32T> instance_type = LoadInstanceType(subject_string); 1829 GotoIfNot(IsOneByteStringInstanceType(instance_type), &call_runtime); 1830 1831 // Try to use cached one byte characters. 1832 { 1833 TNode<Smi> length_smi = 1834 Select<Smi>(TaggedIsSmi(limit_number), 1835 [=] { return SmiMin(CAST(limit_number), subject_length); }, 1836 [=] { return subject_length; }); 1837 TNode<IntPtrT> length = SmiToIntPtr(length_smi); 1838 1839 ToDirectStringAssembler to_direct(state(), subject_string); 1840 to_direct.TryToDirect(&call_runtime); 1841 TNode<FixedArray> elements = CAST(AllocateFixedArray( 1842 PACKED_ELEMENTS, length, AllocationFlag::kAllowLargeObjectAllocation)); 1843 // Don't allocate anything while {string_data} is live! 1844 TNode<RawPtrT> string_data = UncheckedCast<RawPtrT>( 1845 to_direct.PointerToData(&fill_thehole_and_call_runtime)); 1846 TNode<IntPtrT> string_data_offset = to_direct.offset(); 1847 TNode<Object> cache = LoadRoot(Heap::kSingleCharacterStringCacheRootIndex); 1848 1849 BuildFastLoop( 1850 IntPtrConstant(0), length, 1851 [&](Node* index) { 1852 // TODO(jkummerow): Implement a CSA version of DisallowHeapAllocation 1853 // and use that to guard ToDirectStringAssembler.PointerToData(). 1854 CSA_ASSERT(this, WordEqual(to_direct.PointerToData(&call_runtime), 1855 string_data)); 1856 TNode<Int32T> char_code = 1857 UncheckedCast<Int32T>(Load(MachineType::Uint8(), string_data, 1858 IntPtrAdd(index, string_data_offset))); 1859 Node* code_index = ChangeUint32ToWord(char_code); 1860 TNode<Object> entry = LoadFixedArrayElement(CAST(cache), code_index); 1861 1862 // If we cannot find a char in the cache, fill the hole for the fixed 1863 // array, and call runtime. 1864 GotoIf(IsUndefined(entry), &fill_thehole_and_call_runtime); 1865 1866 StoreFixedArrayElement(elements, index, entry); 1867 }, 1868 1, ParameterMode::INTPTR_PARAMETERS, IndexAdvanceMode::kPost); 1869 1870 TNode<Map> array_map = LoadJSArrayElementsMap(PACKED_ELEMENTS, context); 1871 result_array = CAST( 1872 AllocateUninitializedJSArrayWithoutElements(array_map, length_smi)); 1873 StoreObjectField(result_array.value(), JSObject::kElementsOffset, elements); 1874 Goto(&done); 1875 1876 BIND(&fill_thehole_and_call_runtime); 1877 { 1878 FillFixedArrayWithValue(PACKED_ELEMENTS, elements, IntPtrConstant(0), 1879 length, Heap::kTheHoleValueRootIndex); 1880 Goto(&call_runtime); 1881 } 1882 } 1883 1884 BIND(&call_runtime); 1885 { 1886 result_array = CAST(CallRuntime(Runtime::kStringToArray, context, 1887 subject_string, limit_number)); 1888 Goto(&done); 1889 } 1890 1891 BIND(&done); 1892 return result_array.value(); 1893 } 1894 1895 // ES6 section 21.1.3.19 String.prototype.split ( separator, limit ) 1896 TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) { 1897 const int kSeparatorArg = 0; 1898 const int kLimitArg = 1; 1899 1900 Node* const argc = 1901 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); 1902 CodeStubArguments args(this, argc); 1903 1904 Node* const receiver = args.GetReceiver(); 1905 Node* const separator = args.GetOptionalArgumentValue(kSeparatorArg); 1906 Node* const limit = args.GetOptionalArgumentValue(kLimitArg); 1907 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1908 1909 TNode<Smi> smi_zero = SmiConstant(0); 1910 1911 RequireObjectCoercible(context, receiver, "String.prototype.split"); 1912 1913 // Redirect to splitter method if {separator[@@split]} is not undefined. 1914 1915 MaybeCallFunctionAtSymbol( 1916 context, separator, receiver, isolate()->factory()->split_symbol(), 1917 [&]() { 1918 args.PopAndReturn(CallBuiltin(Builtins::kRegExpSplit, context, 1919 separator, receiver, limit)); 1920 }, 1921 [&](Node* fn) { 1922 Callable call_callable = CodeFactory::Call(isolate()); 1923 args.PopAndReturn( 1924 CallJS(call_callable, context, fn, separator, receiver, limit)); 1925 }); 1926 1927 // String and integer conversions. 1928 1929 TNode<String> subject_string = ToString_Inline(context, receiver); 1930 TNode<Number> limit_number = Select<Number>( 1931 IsUndefined(limit), [=] { return NumberConstant(kMaxUInt32); }, 1932 [=] { return ToUint32(context, limit); }); 1933 Node* const separator_string = ToString_Inline(context, separator); 1934 1935 Label return_empty_array(this); 1936 1937 // Shortcut for {limit} == 0. 1938 GotoIf(WordEqual<Object, Object>(limit_number, smi_zero), 1939 &return_empty_array); 1940 1941 // ECMA-262 says that if {separator} is undefined, the result should 1942 // be an array of size 1 containing the entire string. 1943 { 1944 Label next(this); 1945 GotoIfNot(IsUndefined(separator), &next); 1946 1947 const ElementsKind kind = PACKED_ELEMENTS; 1948 Node* const native_context = LoadNativeContext(context); 1949 Node* const array_map = LoadJSArrayElementsMap(kind, native_context); 1950 1951 Node* const length = SmiConstant(1); 1952 Node* const capacity = IntPtrConstant(1); 1953 Node* const result = AllocateJSArray(kind, array_map, capacity, length); 1954 1955 TNode<FixedArray> const fixed_array = CAST(LoadElements(result)); 1956 StoreFixedArrayElement(fixed_array, 0, subject_string); 1957 1958 args.PopAndReturn(result); 1959 1960 BIND(&next); 1961 } 1962 1963 // If the separator string is empty then return the elements in the subject. 1964 { 1965 Label next(this); 1966 GotoIfNot(SmiEqual(LoadStringLengthAsSmi(separator_string), smi_zero), 1967 &next); 1968 1969 TNode<Smi> subject_length = LoadStringLengthAsSmi(subject_string); 1970 GotoIf(SmiEqual(subject_length, smi_zero), &return_empty_array); 1971 1972 args.PopAndReturn( 1973 StringToArray(context, subject_string, subject_length, limit_number)); 1974 1975 BIND(&next); 1976 } 1977 1978 Node* const result = 1979 CallRuntime(Runtime::kStringSplit, context, subject_string, 1980 separator_string, limit_number); 1981 args.PopAndReturn(result); 1982 1983 BIND(&return_empty_array); 1984 { 1985 const ElementsKind kind = PACKED_ELEMENTS; 1986 Node* const native_context = LoadNativeContext(context); 1987 Node* const array_map = LoadJSArrayElementsMap(kind, native_context); 1988 1989 Node* const length = smi_zero; 1990 Node* const capacity = IntPtrConstant(0); 1991 Node* const result = AllocateJSArray(kind, array_map, capacity, length); 1992 1993 args.PopAndReturn(result); 1994 } 1995 } 1996 1997 // ES6 #sec-string.prototype.substr 1998 TF_BUILTIN(StringPrototypeSubstr, StringBuiltinsAssembler) { 1999 const int kStartArg = 0; 2000 const int kLengthArg = 1; 2001 2002 Node* const argc = 2003 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); 2004 CodeStubArguments args(this, argc); 2005 2006 Node* const receiver = args.GetReceiver(); 2007 TNode<Object> start = args.GetOptionalArgumentValue(kStartArg); 2008 TNode<Object> length = args.GetOptionalArgumentValue(kLengthArg); 2009 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 2010 2011 Label out(this); 2012 2013 TVARIABLE(IntPtrT, var_start); 2014 TVARIABLE(Number, var_length); 2015 2016 TNode<IntPtrT> const zero = IntPtrConstant(0); 2017 2018 // Check that {receiver} is coercible to Object and convert it to a String. 2019 TNode<String> const string = 2020 ToThisString(context, receiver, "String.prototype.substr"); 2021 2022 TNode<IntPtrT> const string_length = LoadStringLengthAsWord(string); 2023 2024 // Convert {start} to a relative index. 2025 var_start = ConvertToRelativeIndex(context, start, string_length); 2026 2027 // Conversions and bounds-checks for {length}. 2028 Label if_issmi(this), if_isheapnumber(this, Label::kDeferred); 2029 2030 // Default to {string_length} if {length} is undefined. 2031 { 2032 Label if_isundefined(this, Label::kDeferred), if_isnotundefined(this); 2033 Branch(IsUndefined(length), &if_isundefined, &if_isnotundefined); 2034 2035 BIND(&if_isundefined); 2036 var_length = SmiTag(string_length); 2037 Goto(&if_issmi); 2038 2039 BIND(&if_isnotundefined); 2040 var_length = ToInteger_Inline(context, length, 2041 CodeStubAssembler::kTruncateMinusZero); 2042 } 2043 2044 TVARIABLE(IntPtrT, var_result_length); 2045 2046 Branch(TaggedIsSmi(var_length.value()), &if_issmi, &if_isheapnumber); 2047 2048 // Set {length} to min(max({length}, 0), {string_length} - {start} 2049 BIND(&if_issmi); 2050 { 2051 TNode<IntPtrT> const positive_length = 2052 IntPtrMax(SmiUntag(CAST(var_length.value())), zero); 2053 TNode<IntPtrT> const minimal_length = 2054 IntPtrSub(string_length, var_start.value()); 2055 var_result_length = IntPtrMin(positive_length, minimal_length); 2056 2057 GotoIfNot(IntPtrLessThanOrEqual(var_result_length.value(), zero), &out); 2058 args.PopAndReturn(EmptyStringConstant()); 2059 } 2060 2061 BIND(&if_isheapnumber); 2062 { 2063 // If {length} is a heap number, it is definitely out of bounds. There are 2064 // two cases according to the spec: if it is negative, "" is returned; if 2065 // it is positive, then length is set to {string_length} - {start}. 2066 2067 CSA_ASSERT(this, IsHeapNumber(CAST(var_length.value()))); 2068 2069 Label if_isnegative(this), if_ispositive(this); 2070 TNode<Float64T> const float_zero = Float64Constant(0.); 2071 TNode<Float64T> const length_float = 2072 LoadHeapNumberValue(CAST(var_length.value())); 2073 Branch(Float64LessThan(length_float, float_zero), &if_isnegative, 2074 &if_ispositive); 2075 2076 BIND(&if_isnegative); 2077 args.PopAndReturn(EmptyStringConstant()); 2078 2079 BIND(&if_ispositive); 2080 { 2081 var_result_length = IntPtrSub(string_length, var_start.value()); 2082 GotoIfNot(IntPtrLessThanOrEqual(var_result_length.value(), zero), &out); 2083 args.PopAndReturn(EmptyStringConstant()); 2084 } 2085 } 2086 2087 BIND(&out); 2088 { 2089 TNode<IntPtrT> const end = 2090 IntPtrAdd(var_start.value(), var_result_length.value()); 2091 args.PopAndReturn(SubString(string, var_start.value(), end)); 2092 } 2093 } 2094 2095 TNode<Smi> StringBuiltinsAssembler::ToSmiBetweenZeroAnd( 2096 SloppyTNode<Context> context, SloppyTNode<Object> value, 2097 SloppyTNode<Smi> limit) { 2098 Label out(this); 2099 TVARIABLE(Smi, var_result); 2100 2101 TNode<Number> const value_int = 2102 ToInteger_Inline(context, value, CodeStubAssembler::kTruncateMinusZero); 2103 2104 Label if_issmi(this), if_isnotsmi(this, Label::kDeferred); 2105 Branch(TaggedIsSmi(value_int), &if_issmi, &if_isnotsmi); 2106 2107 BIND(&if_issmi); 2108 { 2109 TNode<Smi> value_smi = CAST(value_int); 2110 Label if_isinbounds(this), if_isoutofbounds(this, Label::kDeferred); 2111 Branch(SmiAbove(value_smi, limit), &if_isoutofbounds, &if_isinbounds); 2112 2113 BIND(&if_isinbounds); 2114 { 2115 var_result = CAST(value_int); 2116 Goto(&out); 2117 } 2118 2119 BIND(&if_isoutofbounds); 2120 { 2121 TNode<Smi> const zero = SmiConstant(0); 2122 var_result = 2123 SelectConstant<Smi>(SmiLessThan(value_smi, zero), zero, limit); 2124 Goto(&out); 2125 } 2126 } 2127 2128 BIND(&if_isnotsmi); 2129 { 2130 // {value} is a heap number - in this case, it is definitely out of bounds. 2131 TNode<HeapNumber> value_int_hn = CAST(value_int); 2132 2133 TNode<Float64T> const float_zero = Float64Constant(0.); 2134 TNode<Smi> const smi_zero = SmiConstant(0); 2135 TNode<Float64T> const value_float = LoadHeapNumberValue(value_int_hn); 2136 var_result = SelectConstant<Smi>(Float64LessThan(value_float, float_zero), 2137 smi_zero, limit); 2138 Goto(&out); 2139 } 2140 2141 BIND(&out); 2142 return var_result.value(); 2143 } 2144 2145 TF_BUILTIN(StringSubstring, CodeStubAssembler) { 2146 TNode<String> string = CAST(Parameter(Descriptor::kString)); 2147 TNode<IntPtrT> from = UncheckedCast<IntPtrT>(Parameter(Descriptor::kFrom)); 2148 TNode<IntPtrT> to = UncheckedCast<IntPtrT>(Parameter(Descriptor::kTo)); 2149 2150 Return(SubString(string, from, to)); 2151 } 2152 2153 // ES6 #sec-string.prototype.substring 2154 TF_BUILTIN(StringPrototypeSubstring, StringBuiltinsAssembler) { 2155 const int kStartArg = 0; 2156 const int kEndArg = 1; 2157 2158 Node* const argc = 2159 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); 2160 CodeStubArguments args(this, argc); 2161 2162 Node* const receiver = args.GetReceiver(); 2163 Node* const start = args.GetOptionalArgumentValue(kStartArg); 2164 Node* const end = args.GetOptionalArgumentValue(kEndArg); 2165 Node* const context = Parameter(Descriptor::kContext); 2166 2167 Label out(this); 2168 2169 TVARIABLE(Smi, var_start); 2170 TVARIABLE(Smi, var_end); 2171 2172 // Check that {receiver} is coercible to Object and convert it to a String. 2173 TNode<String> const string = 2174 ToThisString(context, receiver, "String.prototype.substring"); 2175 2176 TNode<Smi> const length = LoadStringLengthAsSmi(string); 2177 2178 // Conversion and bounds-checks for {start}. 2179 var_start = ToSmiBetweenZeroAnd(context, start, length); 2180 2181 // Conversion and bounds-checks for {end}. 2182 { 2183 var_end = length; 2184 GotoIf(IsUndefined(end), &out); 2185 2186 var_end = ToSmiBetweenZeroAnd(context, end, length); 2187 2188 Label if_endislessthanstart(this); 2189 Branch(SmiLessThan(var_end.value(), var_start.value()), 2190 &if_endislessthanstart, &out); 2191 2192 BIND(&if_endislessthanstart); 2193 { 2194 TNode<Smi> const tmp = var_end.value(); 2195 var_end = var_start.value(); 2196 var_start = tmp; 2197 Goto(&out); 2198 } 2199 } 2200 2201 BIND(&out); 2202 { 2203 args.PopAndReturn(SubString(string, SmiUntag(var_start.value()), 2204 SmiUntag(var_end.value()))); 2205 } 2206 } 2207 2208 // ES6 #sec-string.prototype.trim 2209 TF_BUILTIN(StringPrototypeTrim, StringTrimAssembler) { 2210 TNode<IntPtrT> argc = 2211 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); 2212 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 2213 2214 Generate(String::kTrim, "String.prototype.trim", argc, context); 2215 } 2216 2217 // https://github.com/tc39/proposal-string-left-right-trim 2218 TF_BUILTIN(StringPrototypeTrimStart, StringTrimAssembler) { 2219 TNode<IntPtrT> argc = 2220 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); 2221 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 2222 2223 Generate(String::kTrimStart, "String.prototype.trimLeft", argc, context); 2224 } 2225 2226 // https://github.com/tc39/proposal-string-left-right-trim 2227 TF_BUILTIN(StringPrototypeTrimEnd, StringTrimAssembler) { 2228 TNode<IntPtrT> argc = 2229 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); 2230 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 2231 2232 Generate(String::kTrimEnd, "String.prototype.trimRight", argc, context); 2233 } 2234 2235 void StringTrimAssembler::Generate(String::TrimMode mode, 2236 const char* method_name, TNode<IntPtrT> argc, 2237 TNode<Context> context) { 2238 Label return_emptystring(this), if_runtime(this); 2239 2240 CodeStubArguments arguments(this, argc); 2241 Node* const receiver = arguments.GetReceiver(); 2242 2243 // Check that {receiver} is coercible to Object and convert it to a String. 2244 TNode<String> const string = ToThisString(context, receiver, method_name); 2245 TNode<IntPtrT> const string_length = LoadStringLengthAsWord(string); 2246 2247 ToDirectStringAssembler to_direct(state(), string); 2248 to_direct.TryToDirect(&if_runtime); 2249 Node* const string_data = to_direct.PointerToData(&if_runtime); 2250 Node* const instance_type = to_direct.instance_type(); 2251 Node* const is_stringonebyte = IsOneByteStringInstanceType(instance_type); 2252 Node* const string_data_offset = to_direct.offset(); 2253 2254 TVARIABLE(IntPtrT, var_start, IntPtrConstant(0)); 2255 TVARIABLE(IntPtrT, var_end, IntPtrSub(string_length, IntPtrConstant(1))); 2256 2257 if (mode == String::kTrimStart || mode == String::kTrim) { 2258 ScanForNonWhiteSpaceOrLineTerminator(string_data, string_data_offset, 2259 is_stringonebyte, &var_start, 2260 string_length, 1, &return_emptystring); 2261 } 2262 if (mode == String::kTrimEnd || mode == String::kTrim) { 2263 ScanForNonWhiteSpaceOrLineTerminator( 2264 string_data, string_data_offset, is_stringonebyte, &var_end, 2265 IntPtrConstant(-1), -1, &return_emptystring); 2266 } 2267 2268 arguments.PopAndReturn( 2269 SubString(string, var_start.value(), 2270 IntPtrAdd(var_end.value(), IntPtrConstant(1)))); 2271 2272 BIND(&if_runtime); 2273 arguments.PopAndReturn( 2274 CallRuntime(Runtime::kStringTrim, context, string, SmiConstant(mode))); 2275 2276 BIND(&return_emptystring); 2277 arguments.PopAndReturn(EmptyStringConstant()); 2278 } 2279 2280 void StringTrimAssembler::ScanForNonWhiteSpaceOrLineTerminator( 2281 Node* const string_data, Node* const string_data_offset, 2282 Node* const is_stringonebyte, Variable* const var_index, Node* const end, 2283 int increment, Label* const if_none_found) { 2284 Label if_stringisonebyte(this), out(this); 2285 2286 GotoIf(is_stringonebyte, &if_stringisonebyte); 2287 2288 // Two Byte String 2289 BuildLoop( 2290 var_index, end, increment, if_none_found, &out, [&](Node* const index) { 2291 return Load( 2292 MachineType::Uint16(), string_data, 2293 WordShl(IntPtrAdd(index, string_data_offset), IntPtrConstant(1))); 2294 }); 2295 2296 BIND(&if_stringisonebyte); 2297 BuildLoop(var_index, end, increment, if_none_found, &out, 2298 [&](Node* const index) { 2299 return Load(MachineType::Uint8(), string_data, 2300 IntPtrAdd(index, string_data_offset)); 2301 }); 2302 2303 BIND(&out); 2304 } 2305 2306 void StringTrimAssembler::BuildLoop(Variable* const var_index, Node* const end, 2307 int increment, Label* const if_none_found, 2308 Label* const out, 2309 std::function<Node*(Node*)> get_character) { 2310 Label loop(this, var_index); 2311 Goto(&loop); 2312 BIND(&loop); 2313 { 2314 Node* const index = var_index->value(); 2315 GotoIf(IntPtrEqual(index, end), if_none_found); 2316 GotoIfNotWhiteSpaceOrLineTerminator( 2317 UncheckedCast<Uint32T>(get_character(index)), out); 2318 Increment(var_index, increment); 2319 Goto(&loop); 2320 } 2321 } 2322 2323 void StringTrimAssembler::GotoIfNotWhiteSpaceOrLineTerminator( 2324 Node* const char_code, Label* const if_not_whitespace) { 2325 Label out(this); 2326 2327 // 0x0020 - SPACE (Intentionally out of order to fast path a commmon case) 2328 GotoIf(Word32Equal(char_code, Int32Constant(0x0020)), &out); 2329 2330 // 0x0009 - HORIZONTAL TAB 2331 GotoIf(Uint32LessThan(char_code, Int32Constant(0x0009)), if_not_whitespace); 2332 // 0x000A - LINE FEED OR NEW LINE 2333 // 0x000B - VERTICAL TAB 2334 // 0x000C - FORMFEED 2335 // 0x000D - HORIZONTAL TAB 2336 GotoIf(Uint32LessThanOrEqual(char_code, Int32Constant(0x000D)), &out); 2337 2338 // Common Non-whitespace characters 2339 GotoIf(Uint32LessThan(char_code, Int32Constant(0x00A0)), if_not_whitespace); 2340 2341 // 0x00A0 - NO-BREAK SPACE 2342 GotoIf(Word32Equal(char_code, Int32Constant(0x00A0)), &out); 2343 2344 // 0x1680 - Ogham Space Mark 2345 GotoIf(Word32Equal(char_code, Int32Constant(0x1680)), &out); 2346 2347 // 0x2000 - EN QUAD 2348 GotoIf(Uint32LessThan(char_code, Int32Constant(0x2000)), if_not_whitespace); 2349 // 0x2001 - EM QUAD 2350 // 0x2002 - EN SPACE 2351 // 0x2003 - EM SPACE 2352 // 0x2004 - THREE-PER-EM SPACE 2353 // 0x2005 - FOUR-PER-EM SPACE 2354 // 0x2006 - SIX-PER-EM SPACE 2355 // 0x2007 - FIGURE SPACE 2356 // 0x2008 - PUNCTUATION SPACE 2357 // 0x2009 - THIN SPACE 2358 // 0x200A - HAIR SPACE 2359 GotoIf(Uint32LessThanOrEqual(char_code, Int32Constant(0x200A)), &out); 2360 2361 // 0x2028 - LINE SEPARATOR 2362 GotoIf(Word32Equal(char_code, Int32Constant(0x2028)), &out); 2363 // 0x2029 - PARAGRAPH SEPARATOR 2364 GotoIf(Word32Equal(char_code, Int32Constant(0x2029)), &out); 2365 // 0x202F - NARROW NO-BREAK SPACE 2366 GotoIf(Word32Equal(char_code, Int32Constant(0x202F)), &out); 2367 // 0x205F - MEDIUM MATHEMATICAL SPACE 2368 GotoIf(Word32Equal(char_code, Int32Constant(0x205F)), &out); 2369 // 0xFEFF - BYTE ORDER MARK 2370 GotoIf(Word32Equal(char_code, Int32Constant(0xFEFF)), &out); 2371 // 0x3000 - IDEOGRAPHIC SPACE 2372 Branch(Word32Equal(char_code, Int32Constant(0x3000)), &out, 2373 if_not_whitespace); 2374 2375 BIND(&out); 2376 } 2377 2378 // ES6 #sec-string.prototype.tostring 2379 TF_BUILTIN(StringPrototypeToString, CodeStubAssembler) { 2380 Node* context = Parameter(Descriptor::kContext); 2381 Node* receiver = Parameter(Descriptor::kReceiver); 2382 2383 Node* result = ToThisValue(context, receiver, PrimitiveType::kString, 2384 "String.prototype.toString"); 2385 Return(result); 2386 } 2387 2388 // ES6 #sec-string.prototype.valueof 2389 TF_BUILTIN(StringPrototypeValueOf, CodeStubAssembler) { 2390 Node* context = Parameter(Descriptor::kContext); 2391 Node* receiver = Parameter(Descriptor::kReceiver); 2392 2393 Node* result = ToThisValue(context, receiver, PrimitiveType::kString, 2394 "String.prototype.valueOf"); 2395 Return(result); 2396 } 2397 2398 TF_BUILTIN(StringPrototypeIterator, CodeStubAssembler) { 2399 Node* context = Parameter(Descriptor::kContext); 2400 Node* receiver = Parameter(Descriptor::kReceiver); 2401 2402 Node* string = 2403 ToThisString(context, receiver, "String.prototype[Symbol.iterator]"); 2404 2405 Node* native_context = LoadNativeContext(context); 2406 Node* map = 2407 LoadContextElement(native_context, Context::STRING_ITERATOR_MAP_INDEX); 2408 Node* iterator = Allocate(JSStringIterator::kSize); 2409 StoreMapNoWriteBarrier(iterator, map); 2410 StoreObjectFieldRoot(iterator, JSValue::kPropertiesOrHashOffset, 2411 Heap::kEmptyFixedArrayRootIndex); 2412 StoreObjectFieldRoot(iterator, JSObject::kElementsOffset, 2413 Heap::kEmptyFixedArrayRootIndex); 2414 StoreObjectFieldNoWriteBarrier(iterator, JSStringIterator::kStringOffset, 2415 string); 2416 Node* index = SmiConstant(0); 2417 StoreObjectFieldNoWriteBarrier(iterator, JSStringIterator::kNextIndexOffset, 2418 index); 2419 Return(iterator); 2420 } 2421 2422 // Return the |word32| codepoint at {index}. Supports SeqStrings and 2423 // ExternalStrings. 2424 TNode<Int32T> StringBuiltinsAssembler::LoadSurrogatePairAt( 2425 SloppyTNode<String> string, SloppyTNode<IntPtrT> length, 2426 SloppyTNode<IntPtrT> index, UnicodeEncoding encoding) { 2427 Label handle_surrogate_pair(this), return_result(this); 2428 TVARIABLE(Int32T, var_result); 2429 TVARIABLE(Int32T, var_trail); 2430 var_result = StringCharCodeAt(string, index); 2431 var_trail = Int32Constant(0); 2432 2433 GotoIf(Word32NotEqual(Word32And(var_result.value(), Int32Constant(0xFC00)), 2434 Int32Constant(0xD800)), 2435 &return_result); 2436 TNode<IntPtrT> next_index = IntPtrAdd(index, IntPtrConstant(1)); 2437 2438 GotoIfNot(IntPtrLessThan(next_index, length), &return_result); 2439 var_trail = StringCharCodeAt(string, next_index); 2440 Branch(Word32Equal(Word32And(var_trail.value(), Int32Constant(0xFC00)), 2441 Int32Constant(0xDC00)), 2442 &handle_surrogate_pair, &return_result); 2443 2444 BIND(&handle_surrogate_pair); 2445 { 2446 TNode<Int32T> lead = var_result.value(); 2447 TNode<Int32T> trail = var_trail.value(); 2448 2449 // Check that this path is only taken if a surrogate pair is found 2450 CSA_SLOW_ASSERT(this, 2451 Uint32GreaterThanOrEqual(lead, Int32Constant(0xD800))); 2452 CSA_SLOW_ASSERT(this, Uint32LessThan(lead, Int32Constant(0xDC00))); 2453 CSA_SLOW_ASSERT(this, 2454 Uint32GreaterThanOrEqual(trail, Int32Constant(0xDC00))); 2455 CSA_SLOW_ASSERT(this, Uint32LessThan(trail, Int32Constant(0xE000))); 2456 2457 switch (encoding) { 2458 case UnicodeEncoding::UTF16: 2459 var_result = Signed(Word32Or( 2460 // Need to swap the order for big-endian platforms 2461 #if V8_TARGET_BIG_ENDIAN 2462 Word32Shl(lead, Int32Constant(16)), trail)); 2463 #else 2464 Word32Shl(trail, Int32Constant(16)), lead)); 2465 #endif 2466 break; 2467 2468 case UnicodeEncoding::UTF32: { 2469 // Convert UTF16 surrogate pair into |word32| code point, encoded as 2470 // UTF32. 2471 TNode<Int32T> surrogate_offset = 2472 Int32Constant(0x10000 - (0xD800 << 10) - 0xDC00); 2473 2474 // (lead << 10) + trail + SURROGATE_OFFSET 2475 var_result = Signed(Int32Add(Word32Shl(lead, Int32Constant(10)), 2476 Int32Add(trail, surrogate_offset))); 2477 break; 2478 } 2479 } 2480 Goto(&return_result); 2481 } 2482 2483 BIND(&return_result); 2484 return var_result.value(); 2485 } 2486 2487 // ES6 #sec-%stringiteratorprototype%.next 2488 TF_BUILTIN(StringIteratorPrototypeNext, StringBuiltinsAssembler) { 2489 VARIABLE(var_value, MachineRepresentation::kTagged); 2490 VARIABLE(var_done, MachineRepresentation::kTagged); 2491 2492 var_value.Bind(UndefinedConstant()); 2493 var_done.Bind(TrueConstant()); 2494 2495 Label throw_bad_receiver(this), next_codepoint(this), return_result(this); 2496 2497 Node* context = Parameter(Descriptor::kContext); 2498 Node* iterator = Parameter(Descriptor::kReceiver); 2499 2500 GotoIf(TaggedIsSmi(iterator), &throw_bad_receiver); 2501 GotoIfNot( 2502 InstanceTypeEqual(LoadInstanceType(iterator), JS_STRING_ITERATOR_TYPE), 2503 &throw_bad_receiver); 2504 2505 Node* string = LoadObjectField(iterator, JSStringIterator::kStringOffset); 2506 TNode<IntPtrT> position = SmiUntag( 2507 CAST(LoadObjectField(iterator, JSStringIterator::kNextIndexOffset))); 2508 TNode<IntPtrT> length = LoadStringLengthAsWord(string); 2509 2510 Branch(IntPtrLessThan(position, length), &next_codepoint, &return_result); 2511 2512 BIND(&next_codepoint); 2513 { 2514 UnicodeEncoding encoding = UnicodeEncoding::UTF16; 2515 TNode<Int32T> ch = LoadSurrogatePairAt(string, length, position, encoding); 2516 TNode<String> value = StringFromSingleCodePoint(ch, encoding); 2517 var_value.Bind(value); 2518 TNode<IntPtrT> length = LoadStringLengthAsWord(value); 2519 StoreObjectFieldNoWriteBarrier(iterator, JSStringIterator::kNextIndexOffset, 2520 SmiTag(Signed(IntPtrAdd(position, length)))); 2521 var_done.Bind(FalseConstant()); 2522 Goto(&return_result); 2523 } 2524 2525 BIND(&return_result); 2526 { 2527 Node* result = 2528 AllocateJSIteratorResult(context, var_value.value(), var_done.value()); 2529 Return(result); 2530 } 2531 2532 BIND(&throw_bad_receiver); 2533 { 2534 // The {receiver} is not a valid JSGeneratorObject. 2535 ThrowTypeError(context, MessageTemplate::kIncompatibleMethodReceiver, 2536 StringConstant("String Iterator.prototype.next"), iterator); 2537 } 2538 } 2539 2540 // ----------------------------------------------------------------------------- 2541 // ES6 section B.2.3 Additional Properties of the String.prototype object 2542 2543 class StringHtmlAssembler : public StringBuiltinsAssembler { 2544 public: 2545 explicit StringHtmlAssembler(compiler::CodeAssemblerState* state) 2546 : StringBuiltinsAssembler(state) {} 2547 2548 protected: 2549 void Generate(Node* const context, Node* const receiver, 2550 const char* method_name, const char* tag_name) { 2551 Node* const string = ToThisString(context, receiver, method_name); 2552 std::string open_tag = "<" + std::string(tag_name) + ">"; 2553 std::string close_tag = "</" + std::string(tag_name) + ">"; 2554 2555 Node* strings[] = {StringConstant(open_tag.c_str()), string, 2556 StringConstant(close_tag.c_str())}; 2557 Return(ConcatStrings(context, strings, arraysize(strings))); 2558 } 2559 2560 void GenerateWithAttribute(Node* const context, Node* const receiver, 2561 const char* method_name, const char* tag_name, 2562 const char* attr, Node* const value) { 2563 Node* const string = ToThisString(context, receiver, method_name); 2564 Node* const value_string = 2565 EscapeQuotes(context, ToString_Inline(context, value)); 2566 std::string open_tag_attr = 2567 "<" + std::string(tag_name) + " " + std::string(attr) + "=\""; 2568 std::string close_tag = "</" + std::string(tag_name) + ">"; 2569 2570 Node* strings[] = {StringConstant(open_tag_attr.c_str()), value_string, 2571 StringConstant("\">"), string, 2572 StringConstant(close_tag.c_str())}; 2573 Return(ConcatStrings(context, strings, arraysize(strings))); 2574 } 2575 2576 Node* ConcatStrings(Node* const context, Node** strings, int len) { 2577 VARIABLE(var_result, MachineRepresentation::kTagged, strings[0]); 2578 for (int i = 1; i < len; i++) { 2579 var_result.Bind(CallStub(CodeFactory::StringAdd(isolate()), context, 2580 var_result.value(), strings[i])); 2581 } 2582 return var_result.value(); 2583 } 2584 2585 Node* EscapeQuotes(Node* const context, Node* const string) { 2586 CSA_ASSERT(this, IsString(string)); 2587 Node* const regexp_function = LoadContextElement( 2588 LoadNativeContext(context), Context::REGEXP_FUNCTION_INDEX); 2589 Node* const initial_map = LoadObjectField( 2590 regexp_function, JSFunction::kPrototypeOrInitialMapOffset); 2591 // TODO(pwong): Refactor to not allocate RegExp 2592 Node* const regexp = 2593 CallRuntime(Runtime::kRegExpInitializeAndCompile, context, 2594 AllocateJSObjectFromMap(initial_map), StringConstant("\""), 2595 StringConstant("g")); 2596 2597 return CallRuntime(Runtime::kRegExpInternalReplace, context, regexp, string, 2598 StringConstant(""")); 2599 } 2600 }; 2601 2602 // ES6 #sec-string.prototype.anchor 2603 TF_BUILTIN(StringPrototypeAnchor, StringHtmlAssembler) { 2604 Node* const context = Parameter(Descriptor::kContext); 2605 Node* const receiver = Parameter(Descriptor::kReceiver); 2606 Node* const value = Parameter(Descriptor::kValue); 2607 GenerateWithAttribute(context, receiver, "String.prototype.anchor", "a", 2608 "name", value); 2609 } 2610 2611 // ES6 #sec-string.prototype.big 2612 TF_BUILTIN(StringPrototypeBig, StringHtmlAssembler) { 2613 Node* const context = Parameter(Descriptor::kContext); 2614 Node* const receiver = Parameter(Descriptor::kReceiver); 2615 Generate(context, receiver, "String.prototype.big", "big"); 2616 } 2617 2618 // ES6 #sec-string.prototype.blink 2619 TF_BUILTIN(StringPrototypeBlink, StringHtmlAssembler) { 2620 Node* const context = Parameter(Descriptor::kContext); 2621 Node* const receiver = Parameter(Descriptor::kReceiver); 2622 Generate(context, receiver, "String.prototype.blink", "blink"); 2623 } 2624 2625 // ES6 #sec-string.prototype.bold 2626 TF_BUILTIN(StringPrototypeBold, StringHtmlAssembler) { 2627 Node* const context = Parameter(Descriptor::kContext); 2628 Node* const receiver = Parameter(Descriptor::kReceiver); 2629 Generate(context, receiver, "String.prototype.bold", "b"); 2630 } 2631 2632 // ES6 #sec-string.prototype.fontcolor 2633 TF_BUILTIN(StringPrototypeFontcolor, StringHtmlAssembler) { 2634 Node* const context = Parameter(Descriptor::kContext); 2635 Node* const receiver = Parameter(Descriptor::kReceiver); 2636 Node* const value = Parameter(Descriptor::kValue); 2637 GenerateWithAttribute(context, receiver, "String.prototype.fontcolor", "font", 2638 "color", value); 2639 } 2640 2641 // ES6 #sec-string.prototype.fontsize 2642 TF_BUILTIN(StringPrototypeFontsize, StringHtmlAssembler) { 2643 Node* const context = Parameter(Descriptor::kContext); 2644 Node* const receiver = Parameter(Descriptor::kReceiver); 2645 Node* const value = Parameter(Descriptor::kValue); 2646 GenerateWithAttribute(context, receiver, "String.prototype.fontsize", "font", 2647 "size", value); 2648 } 2649 2650 // ES6 #sec-string.prototype.fixed 2651 TF_BUILTIN(StringPrototypeFixed, StringHtmlAssembler) { 2652 Node* const context = Parameter(Descriptor::kContext); 2653 Node* const receiver = Parameter(Descriptor::kReceiver); 2654 Generate(context, receiver, "String.prototype.fixed", "tt"); 2655 } 2656 2657 // ES6 #sec-string.prototype.italics 2658 TF_BUILTIN(StringPrototypeItalics, StringHtmlAssembler) { 2659 Node* const context = Parameter(Descriptor::kContext); 2660 Node* const receiver = Parameter(Descriptor::kReceiver); 2661 Generate(context, receiver, "String.prototype.italics", "i"); 2662 } 2663 2664 // ES6 #sec-string.prototype.link 2665 TF_BUILTIN(StringPrototypeLink, StringHtmlAssembler) { 2666 Node* const context = Parameter(Descriptor::kContext); 2667 Node* const receiver = Parameter(Descriptor::kReceiver); 2668 Node* const value = Parameter(Descriptor::kValue); 2669 GenerateWithAttribute(context, receiver, "String.prototype.link", "a", "href", 2670 value); 2671 } 2672 2673 // ES6 #sec-string.prototype.small 2674 TF_BUILTIN(StringPrototypeSmall, StringHtmlAssembler) { 2675 Node* const context = Parameter(Descriptor::kContext); 2676 Node* const receiver = Parameter(Descriptor::kReceiver); 2677 Generate(context, receiver, "String.prototype.small", "small"); 2678 } 2679 2680 // ES6 #sec-string.prototype.strike 2681 TF_BUILTIN(StringPrototypeStrike, StringHtmlAssembler) { 2682 Node* const context = Parameter(Descriptor::kContext); 2683 Node* const receiver = Parameter(Descriptor::kReceiver); 2684 Generate(context, receiver, "String.prototype.strike", "strike"); 2685 } 2686 2687 // ES6 #sec-string.prototype.sub 2688 TF_BUILTIN(StringPrototypeSub, StringHtmlAssembler) { 2689 Node* const context = Parameter(Descriptor::kContext); 2690 Node* const receiver = Parameter(Descriptor::kReceiver); 2691 Generate(context, receiver, "String.prototype.sub", "sub"); 2692 } 2693 2694 // ES6 #sec-string.prototype.sup 2695 TF_BUILTIN(StringPrototypeSup, StringHtmlAssembler) { 2696 Node* const context = Parameter(Descriptor::kContext); 2697 Node* const receiver = Parameter(Descriptor::kReceiver); 2698 Generate(context, receiver, "String.prototype.sup", "sup"); 2699 } 2700 2701 } // namespace internal 2702 } // namespace v8 2703