1 // Copyright 2016 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-regexp.h" 6 #include "src/builtins/builtins-utils.h" 7 #include "src/builtins/builtins.h" 8 #include "src/code-factory.h" 9 #include "src/code-stub-assembler.h" 10 #include "src/conversions.h" 11 #include "src/counters.h" 12 #include "src/objects-inl.h" 13 #include "src/regexp/regexp-utils.h" 14 #include "src/string-case.h" 15 #include "src/unicode-inl.h" 16 #include "src/unicode.h" 17 18 namespace v8 { 19 namespace internal { 20 21 typedef CodeStubAssembler::ResultMode ResultMode; 22 typedef CodeStubAssembler::RelationalComparisonMode RelationalComparisonMode; 23 24 class StringBuiltinsAssembler : public CodeStubAssembler { 25 public: 26 explicit StringBuiltinsAssembler(compiler::CodeAssemblerState* state) 27 : CodeStubAssembler(state) {} 28 29 protected: 30 Node* DirectStringData(Node* string, Node* string_instance_type) { 31 // Compute the effective offset of the first character. 32 Variable var_data(this, MachineType::PointerRepresentation()); 33 Label if_sequential(this), if_external(this), if_join(this); 34 Branch(Word32Equal(Word32And(string_instance_type, 35 Int32Constant(kStringRepresentationMask)), 36 Int32Constant(kSeqStringTag)), 37 &if_sequential, &if_external); 38 39 Bind(&if_sequential); 40 { 41 var_data.Bind(IntPtrAdd( 42 IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag), 43 BitcastTaggedToWord(string))); 44 Goto(&if_join); 45 } 46 47 Bind(&if_external); 48 { 49 // This is only valid for ExternalStrings where the resource data 50 // pointer is cached (i.e. no short external strings). 51 CSA_ASSERT(this, Word32NotEqual( 52 Word32And(string_instance_type, 53 Int32Constant(kShortExternalStringMask)), 54 Int32Constant(kShortExternalStringTag))); 55 var_data.Bind(LoadObjectField(string, ExternalString::kResourceDataOffset, 56 MachineType::Pointer())); 57 Goto(&if_join); 58 } 59 60 Bind(&if_join); 61 return var_data.value(); 62 } 63 64 Node* LoadOneByteChar(Node* string, Node* index) { 65 return Load(MachineType::Uint8(), string, OneByteCharOffset(index)); 66 } 67 68 Node* OneByteCharAddress(Node* string, Node* index) { 69 Node* offset = OneByteCharOffset(index); 70 return IntPtrAdd(string, offset); 71 } 72 73 Node* OneByteCharOffset(Node* index) { 74 return CharOffset(String::ONE_BYTE_ENCODING, index); 75 } 76 77 Node* CharOffset(String::Encoding encoding, Node* index) { 78 const int header = SeqOneByteString::kHeaderSize - kHeapObjectTag; 79 Node* offset = index; 80 if (encoding == String::TWO_BYTE_ENCODING) { 81 offset = IntPtrAdd(offset, offset); 82 } 83 offset = IntPtrAdd(offset, IntPtrConstant(header)); 84 return offset; 85 } 86 87 void DispatchOnStringInstanceType(Node* const instance_type, 88 Label* if_onebyte_sequential, 89 Label* if_onebyte_external, 90 Label* if_otherwise) { 91 const int kMask = kStringRepresentationMask | kStringEncodingMask; 92 Node* const encoding_and_representation = 93 Word32And(instance_type, Int32Constant(kMask)); 94 95 int32_t values[] = { 96 kOneByteStringTag | kSeqStringTag, 97 kOneByteStringTag | kExternalStringTag, 98 }; 99 Label* labels[] = { 100 if_onebyte_sequential, if_onebyte_external, 101 }; 102 STATIC_ASSERT(arraysize(values) == arraysize(labels)); 103 104 Switch(encoding_and_representation, if_otherwise, values, labels, 105 arraysize(values)); 106 } 107 108 void GenerateStringEqual(ResultMode mode); 109 void GenerateStringRelationalComparison(RelationalComparisonMode mode); 110 111 Node* ToSmiBetweenZeroAnd(Node* context, Node* value, Node* limit); 112 113 Node* LoadSurrogatePairAt(Node* string, Node* length, Node* index, 114 UnicodeEncoding encoding); 115 116 void StringIndexOf(Node* receiver, Node* instance_type, Node* search_string, 117 Node* search_string_instance_type, Node* position, 118 std::function<void(Node*)> f_return); 119 120 Node* IsNullOrUndefined(Node* const value); 121 void RequireObjectCoercible(Node* const context, Node* const value, 122 const char* method_name); 123 124 Node* SmiIsNegative(Node* const value) { 125 return SmiLessThan(value, SmiConstant(0)); 126 } 127 128 // Implements boilerplate logic for {match, split, replace, search} of the 129 // form: 130 // 131 // if (!IS_NULL_OR_UNDEFINED(object)) { 132 // var maybe_function = object[symbol]; 133 // if (!IS_UNDEFINED(maybe_function)) { 134 // return %_Call(maybe_function, ...); 135 // } 136 // } 137 // 138 // Contains fast paths for Smi and RegExp objects. 139 typedef std::function<Node*()> NodeFunction0; 140 typedef std::function<Node*(Node* fn)> NodeFunction1; 141 void MaybeCallFunctionAtSymbol(Node* const context, Node* const object, 142 Handle<Symbol> symbol, 143 const NodeFunction0& regexp_call, 144 const NodeFunction1& generic_call); 145 }; 146 147 void StringBuiltinsAssembler::GenerateStringEqual(ResultMode mode) { 148 // Here's pseudo-code for the algorithm below in case of kDontNegateResult 149 // mode; for kNegateResult mode we properly negate the result. 150 // 151 // if (lhs == rhs) return true; 152 // if (lhs->length() != rhs->length()) return false; 153 // if (lhs->IsInternalizedString() && rhs->IsInternalizedString()) { 154 // return false; 155 // } 156 // if (lhs->IsSeqOneByteString() && rhs->IsSeqOneByteString()) { 157 // for (i = 0; i != lhs->length(); ++i) { 158 // if (lhs[i] != rhs[i]) return false; 159 // } 160 // return true; 161 // } 162 // if (lhs and/or rhs are indirect strings) { 163 // unwrap them and restart from the beginning; 164 // } 165 // return %StringEqual(lhs, rhs); 166 167 Variable var_left(this, MachineRepresentation::kTagged); 168 Variable var_right(this, MachineRepresentation::kTagged); 169 var_left.Bind(Parameter(0)); 170 var_right.Bind(Parameter(1)); 171 Node* context = Parameter(2); 172 173 Variable* input_vars[2] = {&var_left, &var_right}; 174 Label if_equal(this), if_notequal(this), restart(this, 2, input_vars); 175 Goto(&restart); 176 Bind(&restart); 177 Node* lhs = var_left.value(); 178 Node* rhs = var_right.value(); 179 180 // Fast check to see if {lhs} and {rhs} refer to the same String object. 181 GotoIf(WordEqual(lhs, rhs), &if_equal); 182 183 // Load the length of {lhs} and {rhs}. 184 Node* lhs_length = LoadStringLength(lhs); 185 Node* rhs_length = LoadStringLength(rhs); 186 187 // Strings with different lengths cannot be equal. 188 GotoIf(WordNotEqual(lhs_length, rhs_length), &if_notequal); 189 190 // Load instance types of {lhs} and {rhs}. 191 Node* lhs_instance_type = LoadInstanceType(lhs); 192 Node* rhs_instance_type = LoadInstanceType(rhs); 193 194 // Combine the instance types into a single 16-bit value, so we can check 195 // both of them at once. 196 Node* both_instance_types = Word32Or( 197 lhs_instance_type, Word32Shl(rhs_instance_type, Int32Constant(8))); 198 199 // Check if both {lhs} and {rhs} are internalized. Since we already know 200 // that they're not the same object, they're not equal in that case. 201 int const kBothInternalizedMask = 202 kIsNotInternalizedMask | (kIsNotInternalizedMask << 8); 203 int const kBothInternalizedTag = kInternalizedTag | (kInternalizedTag << 8); 204 GotoIf(Word32Equal(Word32And(both_instance_types, 205 Int32Constant(kBothInternalizedMask)), 206 Int32Constant(kBothInternalizedTag)), 207 &if_notequal); 208 209 // Check that both {lhs} and {rhs} are flat one-byte strings, and that 210 // in case of ExternalStrings the data pointer is cached.. 211 STATIC_ASSERT(kShortExternalStringTag != 0); 212 int const kBothDirectOneByteStringMask = 213 kStringEncodingMask | kIsIndirectStringMask | kShortExternalStringMask | 214 ((kStringEncodingMask | kIsIndirectStringMask | kShortExternalStringMask) 215 << 8); 216 int const kBothDirectOneByteStringTag = 217 kOneByteStringTag | (kOneByteStringTag << 8); 218 Label if_bothdirectonebytestrings(this), if_notbothdirectonebytestrings(this); 219 Branch(Word32Equal(Word32And(both_instance_types, 220 Int32Constant(kBothDirectOneByteStringMask)), 221 Int32Constant(kBothDirectOneByteStringTag)), 222 &if_bothdirectonebytestrings, &if_notbothdirectonebytestrings); 223 224 Bind(&if_bothdirectonebytestrings); 225 { 226 // Compute the effective offset of the first character. 227 Node* lhs_data = DirectStringData(lhs, lhs_instance_type); 228 Node* rhs_data = DirectStringData(rhs, rhs_instance_type); 229 230 // Compute the first offset after the string from the length. 231 Node* length = SmiUntag(lhs_length); 232 233 // Loop over the {lhs} and {rhs} strings to see if they are equal. 234 Variable var_offset(this, MachineType::PointerRepresentation()); 235 Label loop(this, &var_offset); 236 var_offset.Bind(IntPtrConstant(0)); 237 Goto(&loop); 238 Bind(&loop); 239 { 240 // If {offset} equals {end}, no difference was found, so the 241 // strings are equal. 242 Node* offset = var_offset.value(); 243 GotoIf(WordEqual(offset, length), &if_equal); 244 245 // Load the next characters from {lhs} and {rhs}. 246 Node* lhs_value = Load(MachineType::Uint8(), lhs_data, offset); 247 Node* rhs_value = Load(MachineType::Uint8(), rhs_data, offset); 248 249 // Check if the characters match. 250 GotoIf(Word32NotEqual(lhs_value, rhs_value), &if_notequal); 251 252 // Advance to next character. 253 var_offset.Bind(IntPtrAdd(offset, IntPtrConstant(1))); 254 Goto(&loop); 255 } 256 } 257 258 Bind(&if_notbothdirectonebytestrings); 259 { 260 // Try to unwrap indirect strings, restart the above attempt on success. 261 MaybeDerefIndirectStrings(&var_left, lhs_instance_type, &var_right, 262 rhs_instance_type, &restart); 263 // TODO(bmeurer): Add support for two byte string equality checks. 264 265 Runtime::FunctionId function_id = (mode == ResultMode::kDontNegateResult) 266 ? Runtime::kStringEqual 267 : Runtime::kStringNotEqual; 268 TailCallRuntime(function_id, context, lhs, rhs); 269 } 270 271 Bind(&if_equal); 272 Return(BooleanConstant(mode == ResultMode::kDontNegateResult)); 273 274 Bind(&if_notequal); 275 Return(BooleanConstant(mode == ResultMode::kNegateResult)); 276 } 277 278 void StringBuiltinsAssembler::GenerateStringRelationalComparison( 279 RelationalComparisonMode mode) { 280 Variable var_left(this, MachineRepresentation::kTagged); 281 Variable var_right(this, MachineRepresentation::kTagged); 282 var_left.Bind(Parameter(0)); 283 var_right.Bind(Parameter(1)); 284 Node* context = Parameter(2); 285 286 Variable* input_vars[2] = {&var_left, &var_right}; 287 Label if_less(this), if_equal(this), if_greater(this); 288 Label restart(this, 2, input_vars); 289 Goto(&restart); 290 Bind(&restart); 291 292 Node* lhs = var_left.value(); 293 Node* rhs = var_right.value(); 294 // Fast check to see if {lhs} and {rhs} refer to the same String object. 295 GotoIf(WordEqual(lhs, rhs), &if_equal); 296 297 // Load instance types of {lhs} and {rhs}. 298 Node* lhs_instance_type = LoadInstanceType(lhs); 299 Node* rhs_instance_type = LoadInstanceType(rhs); 300 301 // Combine the instance types into a single 16-bit value, so we can check 302 // both of them at once. 303 Node* both_instance_types = Word32Or( 304 lhs_instance_type, Word32Shl(rhs_instance_type, Int32Constant(8))); 305 306 // Check that both {lhs} and {rhs} are flat one-byte strings. 307 int const kBothSeqOneByteStringMask = 308 kStringEncodingMask | kStringRepresentationMask | 309 ((kStringEncodingMask | kStringRepresentationMask) << 8); 310 int const kBothSeqOneByteStringTag = 311 kOneByteStringTag | kSeqStringTag | 312 ((kOneByteStringTag | kSeqStringTag) << 8); 313 Label if_bothonebyteseqstrings(this), if_notbothonebyteseqstrings(this); 314 Branch(Word32Equal(Word32And(both_instance_types, 315 Int32Constant(kBothSeqOneByteStringMask)), 316 Int32Constant(kBothSeqOneByteStringTag)), 317 &if_bothonebyteseqstrings, &if_notbothonebyteseqstrings); 318 319 Bind(&if_bothonebyteseqstrings); 320 { 321 // Load the length of {lhs} and {rhs}. 322 Node* lhs_length = LoadStringLength(lhs); 323 Node* rhs_length = LoadStringLength(rhs); 324 325 // Determine the minimum length. 326 Node* length = SmiMin(lhs_length, rhs_length); 327 328 // Compute the effective offset of the first character. 329 Node* begin = 330 IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag); 331 332 // Compute the first offset after the string from the length. 333 Node* end = IntPtrAdd(begin, SmiUntag(length)); 334 335 // Loop over the {lhs} and {rhs} strings to see if they are equal. 336 Variable var_offset(this, MachineType::PointerRepresentation()); 337 Label loop(this, &var_offset); 338 var_offset.Bind(begin); 339 Goto(&loop); 340 Bind(&loop); 341 { 342 // Check if {offset} equals {end}. 343 Node* offset = var_offset.value(); 344 Label if_done(this), if_notdone(this); 345 Branch(WordEqual(offset, end), &if_done, &if_notdone); 346 347 Bind(&if_notdone); 348 { 349 // Load the next characters from {lhs} and {rhs}. 350 Node* lhs_value = Load(MachineType::Uint8(), lhs, offset); 351 Node* rhs_value = Load(MachineType::Uint8(), rhs, offset); 352 353 // Check if the characters match. 354 Label if_valueissame(this), if_valueisnotsame(this); 355 Branch(Word32Equal(lhs_value, rhs_value), &if_valueissame, 356 &if_valueisnotsame); 357 358 Bind(&if_valueissame); 359 { 360 // Advance to next character. 361 var_offset.Bind(IntPtrAdd(offset, IntPtrConstant(1))); 362 } 363 Goto(&loop); 364 365 Bind(&if_valueisnotsame); 366 Branch(Uint32LessThan(lhs_value, rhs_value), &if_less, &if_greater); 367 } 368 369 Bind(&if_done); 370 { 371 // All characters up to the min length are equal, decide based on 372 // string length. 373 GotoIf(SmiEqual(lhs_length, rhs_length), &if_equal); 374 BranchIfSmiLessThan(lhs_length, rhs_length, &if_less, &if_greater); 375 } 376 } 377 } 378 379 Bind(&if_notbothonebyteseqstrings); 380 { 381 // Try to unwrap indirect strings, restart the above attempt on success. 382 MaybeDerefIndirectStrings(&var_left, lhs_instance_type, &var_right, 383 rhs_instance_type, &restart); 384 // TODO(bmeurer): Add support for two byte string relational comparisons. 385 switch (mode) { 386 case RelationalComparisonMode::kLessThan: 387 TailCallRuntime(Runtime::kStringLessThan, context, lhs, rhs); 388 break; 389 case RelationalComparisonMode::kLessThanOrEqual: 390 TailCallRuntime(Runtime::kStringLessThanOrEqual, context, lhs, rhs); 391 break; 392 case RelationalComparisonMode::kGreaterThan: 393 TailCallRuntime(Runtime::kStringGreaterThan, context, lhs, rhs); 394 break; 395 case RelationalComparisonMode::kGreaterThanOrEqual: 396 TailCallRuntime(Runtime::kStringGreaterThanOrEqual, context, lhs, 397 rhs); 398 break; 399 } 400 } 401 402 Bind(&if_less); 403 switch (mode) { 404 case RelationalComparisonMode::kLessThan: 405 case RelationalComparisonMode::kLessThanOrEqual: 406 Return(BooleanConstant(true)); 407 break; 408 409 case RelationalComparisonMode::kGreaterThan: 410 case RelationalComparisonMode::kGreaterThanOrEqual: 411 Return(BooleanConstant(false)); 412 break; 413 } 414 415 Bind(&if_equal); 416 switch (mode) { 417 case RelationalComparisonMode::kLessThan: 418 case RelationalComparisonMode::kGreaterThan: 419 Return(BooleanConstant(false)); 420 break; 421 422 case RelationalComparisonMode::kLessThanOrEqual: 423 case RelationalComparisonMode::kGreaterThanOrEqual: 424 Return(BooleanConstant(true)); 425 break; 426 } 427 428 Bind(&if_greater); 429 switch (mode) { 430 case RelationalComparisonMode::kLessThan: 431 case RelationalComparisonMode::kLessThanOrEqual: 432 Return(BooleanConstant(false)); 433 break; 434 435 case RelationalComparisonMode::kGreaterThan: 436 case RelationalComparisonMode::kGreaterThanOrEqual: 437 Return(BooleanConstant(true)); 438 break; 439 } 440 } 441 442 TF_BUILTIN(StringEqual, StringBuiltinsAssembler) { 443 GenerateStringEqual(ResultMode::kDontNegateResult); 444 } 445 446 TF_BUILTIN(StringNotEqual, StringBuiltinsAssembler) { 447 GenerateStringEqual(ResultMode::kNegateResult); 448 } 449 450 TF_BUILTIN(StringLessThan, StringBuiltinsAssembler) { 451 GenerateStringRelationalComparison(RelationalComparisonMode::kLessThan); 452 } 453 454 TF_BUILTIN(StringLessThanOrEqual, StringBuiltinsAssembler) { 455 GenerateStringRelationalComparison( 456 RelationalComparisonMode::kLessThanOrEqual); 457 } 458 459 TF_BUILTIN(StringGreaterThan, StringBuiltinsAssembler) { 460 GenerateStringRelationalComparison(RelationalComparisonMode::kGreaterThan); 461 } 462 463 TF_BUILTIN(StringGreaterThanOrEqual, StringBuiltinsAssembler) { 464 GenerateStringRelationalComparison( 465 RelationalComparisonMode::kGreaterThanOrEqual); 466 } 467 468 TF_BUILTIN(StringCharAt, CodeStubAssembler) { 469 Node* receiver = Parameter(0); 470 Node* position = Parameter(1); 471 472 // Load the character code at the {position} from the {receiver}. 473 Node* code = StringCharCodeAt(receiver, position, INTPTR_PARAMETERS); 474 475 // And return the single character string with only that {code} 476 Node* result = StringFromCharCode(code); 477 Return(result); 478 } 479 480 TF_BUILTIN(StringCharCodeAt, CodeStubAssembler) { 481 Node* receiver = Parameter(0); 482 Node* position = Parameter(1); 483 484 // Load the character code at the {position} from the {receiver}. 485 Node* code = StringCharCodeAt(receiver, position, INTPTR_PARAMETERS); 486 487 // And return it as TaggedSigned value. 488 // TODO(turbofan): Allow builtins to return values untagged. 489 Node* result = SmiFromWord32(code); 490 Return(result); 491 } 492 493 // ----------------------------------------------------------------------------- 494 // ES6 section 21.1 String Objects 495 496 // ES6 section 21.1.2.1 String.fromCharCode ( ...codeUnits ) 497 TF_BUILTIN(StringFromCharCode, CodeStubAssembler) { 498 Node* argc = Parameter(BuiltinDescriptor::kArgumentsCount); 499 Node* context = Parameter(BuiltinDescriptor::kContext); 500 501 CodeStubArguments arguments(this, ChangeInt32ToIntPtr(argc)); 502 // From now on use word-size argc value. 503 argc = arguments.GetLength(); 504 505 // Check if we have exactly one argument (plus the implicit receiver), i.e. 506 // if the parent frame is not an arguments adaptor frame. 507 Label if_oneargument(this), if_notoneargument(this); 508 Branch(WordEqual(argc, IntPtrConstant(1)), &if_oneargument, 509 &if_notoneargument); 510 511 Bind(&if_oneargument); 512 { 513 // Single argument case, perform fast single character string cache lookup 514 // for one-byte code units, or fall back to creating a single character 515 // string on the fly otherwise. 516 Node* code = arguments.AtIndex(0); 517 Node* code32 = TruncateTaggedToWord32(context, code); 518 Node* code16 = Word32And(code32, Int32Constant(String::kMaxUtf16CodeUnit)); 519 Node* result = StringFromCharCode(code16); 520 arguments.PopAndReturn(result); 521 } 522 523 Node* code16 = nullptr; 524 Bind(&if_notoneargument); 525 { 526 Label two_byte(this); 527 // Assume that the resulting string contains only one-byte characters. 528 Node* one_byte_result = AllocateSeqOneByteString(context, argc); 529 530 Variable max_index(this, MachineType::PointerRepresentation()); 531 max_index.Bind(IntPtrConstant(0)); 532 533 // Iterate over the incoming arguments, converting them to 8-bit character 534 // codes. Stop if any of the conversions generates a code that doesn't fit 535 // in 8 bits. 536 CodeStubAssembler::VariableList vars({&max_index}, zone()); 537 arguments.ForEach(vars, [this, context, &two_byte, &max_index, &code16, 538 one_byte_result](Node* arg) { 539 Node* code32 = TruncateTaggedToWord32(context, arg); 540 code16 = Word32And(code32, Int32Constant(String::kMaxUtf16CodeUnit)); 541 542 GotoIf( 543 Int32GreaterThan(code16, Int32Constant(String::kMaxOneByteCharCode)), 544 &two_byte); 545 546 // The {code16} fits into the SeqOneByteString {one_byte_result}. 547 Node* offset = ElementOffsetFromIndex( 548 max_index.value(), UINT8_ELEMENTS, 549 CodeStubAssembler::INTPTR_PARAMETERS, 550 SeqOneByteString::kHeaderSize - kHeapObjectTag); 551 StoreNoWriteBarrier(MachineRepresentation::kWord8, one_byte_result, 552 offset, code16); 553 max_index.Bind(IntPtrAdd(max_index.value(), IntPtrConstant(1))); 554 }); 555 arguments.PopAndReturn(one_byte_result); 556 557 Bind(&two_byte); 558 559 // At least one of the characters in the string requires a 16-bit 560 // representation. Allocate a SeqTwoByteString to hold the resulting 561 // string. 562 Node* two_byte_result = AllocateSeqTwoByteString(context, argc); 563 564 // Copy the characters that have already been put in the 8-bit string into 565 // their corresponding positions in the new 16-bit string. 566 Node* zero = IntPtrConstant(0); 567 CopyStringCharacters(one_byte_result, two_byte_result, zero, zero, 568 max_index.value(), String::ONE_BYTE_ENCODING, 569 String::TWO_BYTE_ENCODING, 570 CodeStubAssembler::INTPTR_PARAMETERS); 571 572 // Write the character that caused the 8-bit to 16-bit fault. 573 Node* max_index_offset = 574 ElementOffsetFromIndex(max_index.value(), UINT16_ELEMENTS, 575 CodeStubAssembler::INTPTR_PARAMETERS, 576 SeqTwoByteString::kHeaderSize - kHeapObjectTag); 577 StoreNoWriteBarrier(MachineRepresentation::kWord16, two_byte_result, 578 max_index_offset, code16); 579 max_index.Bind(IntPtrAdd(max_index.value(), IntPtrConstant(1))); 580 581 // Resume copying the passed-in arguments from the same place where the 582 // 8-bit copy stopped, but this time copying over all of the characters 583 // using a 16-bit representation. 584 arguments.ForEach( 585 vars, 586 [this, context, two_byte_result, &max_index](Node* arg) { 587 Node* code32 = TruncateTaggedToWord32(context, arg); 588 Node* code16 = 589 Word32And(code32, Int32Constant(String::kMaxUtf16CodeUnit)); 590 591 Node* offset = ElementOffsetFromIndex( 592 max_index.value(), UINT16_ELEMENTS, 593 CodeStubAssembler::INTPTR_PARAMETERS, 594 SeqTwoByteString::kHeaderSize - kHeapObjectTag); 595 StoreNoWriteBarrier(MachineRepresentation::kWord16, two_byte_result, 596 offset, code16); 597 max_index.Bind(IntPtrAdd(max_index.value(), IntPtrConstant(1))); 598 }, 599 max_index.value()); 600 601 arguments.PopAndReturn(two_byte_result); 602 } 603 } 604 605 namespace { // for String.fromCodePoint 606 607 bool IsValidCodePoint(Isolate* isolate, Handle<Object> value) { 608 if (!value->IsNumber() && !Object::ToNumber(value).ToHandle(&value)) { 609 return false; 610 } 611 612 if (Object::ToInteger(isolate, value).ToHandleChecked()->Number() != 613 value->Number()) { 614 return false; 615 } 616 617 if (value->Number() < 0 || value->Number() > 0x10FFFF) { 618 return false; 619 } 620 621 return true; 622 } 623 624 uc32 NextCodePoint(Isolate* isolate, BuiltinArguments args, int index) { 625 Handle<Object> value = args.at(1 + index); 626 ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, value, Object::ToNumber(value), -1); 627 if (!IsValidCodePoint(isolate, value)) { 628 isolate->Throw(*isolate->factory()->NewRangeError( 629 MessageTemplate::kInvalidCodePoint, value)); 630 return -1; 631 } 632 return DoubleToUint32(value->Number()); 633 } 634 635 } // namespace 636 637 // ES6 section 21.1.2.2 String.fromCodePoint ( ...codePoints ) 638 BUILTIN(StringFromCodePoint) { 639 HandleScope scope(isolate); 640 int const length = args.length() - 1; 641 if (length == 0) return isolate->heap()->empty_string(); 642 DCHECK_LT(0, length); 643 644 // Optimistically assume that the resulting String contains only one byte 645 // characters. 646 List<uint8_t> one_byte_buffer(length); 647 uc32 code = 0; 648 int index; 649 for (index = 0; index < length; index++) { 650 code = NextCodePoint(isolate, args, index); 651 if (code < 0) { 652 return isolate->heap()->exception(); 653 } 654 if (code > String::kMaxOneByteCharCode) { 655 break; 656 } 657 one_byte_buffer.Add(code); 658 } 659 660 if (index == length) { 661 RETURN_RESULT_OR_FAILURE(isolate, isolate->factory()->NewStringFromOneByte( 662 one_byte_buffer.ToConstVector())); 663 } 664 665 List<uc16> two_byte_buffer(length - index); 666 667 while (true) { 668 if (code <= static_cast<uc32>(unibrow::Utf16::kMaxNonSurrogateCharCode)) { 669 two_byte_buffer.Add(code); 670 } else { 671 two_byte_buffer.Add(unibrow::Utf16::LeadSurrogate(code)); 672 two_byte_buffer.Add(unibrow::Utf16::TrailSurrogate(code)); 673 } 674 675 if (++index == length) { 676 break; 677 } 678 code = NextCodePoint(isolate, args, index); 679 if (code < 0) { 680 return isolate->heap()->exception(); 681 } 682 } 683 684 Handle<SeqTwoByteString> result; 685 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( 686 isolate, result, 687 isolate->factory()->NewRawTwoByteString(one_byte_buffer.length() + 688 two_byte_buffer.length())); 689 690 CopyChars(result->GetChars(), one_byte_buffer.ToConstVector().start(), 691 one_byte_buffer.length()); 692 CopyChars(result->GetChars() + one_byte_buffer.length(), 693 two_byte_buffer.ToConstVector().start(), two_byte_buffer.length()); 694 695 return *result; 696 } 697 698 // ES6 section 21.1.3.1 String.prototype.charAt ( pos ) 699 TF_BUILTIN(StringPrototypeCharAt, CodeStubAssembler) { 700 Node* receiver = Parameter(0); 701 Node* position = Parameter(1); 702 Node* context = Parameter(4); 703 704 // Check that {receiver} is coercible to Object and convert it to a String. 705 receiver = ToThisString(context, receiver, "String.prototype.charAt"); 706 707 // Convert the {position} to a Smi and check that it's in bounds of the 708 // {receiver}. 709 { 710 Label return_emptystring(this, Label::kDeferred); 711 position = 712 ToInteger(context, position, CodeStubAssembler::kTruncateMinusZero); 713 GotoIfNot(TaggedIsSmi(position), &return_emptystring); 714 715 // Determine the actual length of the {receiver} String. 716 Node* receiver_length = LoadObjectField(receiver, String::kLengthOffset); 717 718 // Return "" if the Smi {position} is outside the bounds of the {receiver}. 719 Label if_positioninbounds(this); 720 Branch(SmiAboveOrEqual(position, receiver_length), &return_emptystring, 721 &if_positioninbounds); 722 723 Bind(&return_emptystring); 724 Return(EmptyStringConstant()); 725 726 Bind(&if_positioninbounds); 727 } 728 729 // Load the character code at the {position} from the {receiver}. 730 Node* code = StringCharCodeAt(receiver, position); 731 732 // And return the single character string with only that {code}. 733 Node* result = StringFromCharCode(code); 734 Return(result); 735 } 736 737 // ES6 section 21.1.3.2 String.prototype.charCodeAt ( pos ) 738 TF_BUILTIN(StringPrototypeCharCodeAt, CodeStubAssembler) { 739 Node* receiver = Parameter(0); 740 Node* position = Parameter(1); 741 Node* context = Parameter(4); 742 743 // Check that {receiver} is coercible to Object and convert it to a String. 744 receiver = ToThisString(context, receiver, "String.prototype.charCodeAt"); 745 746 // Convert the {position} to a Smi and check that it's in bounds of the 747 // {receiver}. 748 { 749 Label return_nan(this, Label::kDeferred); 750 position = 751 ToInteger(context, position, CodeStubAssembler::kTruncateMinusZero); 752 GotoIfNot(TaggedIsSmi(position), &return_nan); 753 754 // Determine the actual length of the {receiver} String. 755 Node* receiver_length = LoadObjectField(receiver, String::kLengthOffset); 756 757 // Return NaN if the Smi {position} is outside the bounds of the {receiver}. 758 Label if_positioninbounds(this); 759 Branch(SmiAboveOrEqual(position, receiver_length), &return_nan, 760 &if_positioninbounds); 761 762 Bind(&return_nan); 763 Return(NaNConstant()); 764 765 Bind(&if_positioninbounds); 766 } 767 768 // Load the character at the {position} from the {receiver}. 769 Node* value = StringCharCodeAt(receiver, position); 770 Node* result = SmiFromWord32(value); 771 Return(result); 772 } 773 774 // ES6 section 21.1.3.6 775 // String.prototype.endsWith ( searchString [ , endPosition ] ) 776 BUILTIN(StringPrototypeEndsWith) { 777 HandleScope handle_scope(isolate); 778 TO_THIS_STRING(str, "String.prototype.endsWith"); 779 780 // Check if the search string is a regExp and fail if it is. 781 Handle<Object> search = args.atOrUndefined(isolate, 1); 782 Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search); 783 if (is_reg_exp.IsNothing()) { 784 DCHECK(isolate->has_pending_exception()); 785 return isolate->heap()->exception(); 786 } 787 if (is_reg_exp.FromJust()) { 788 THROW_NEW_ERROR_RETURN_FAILURE( 789 isolate, NewTypeError(MessageTemplate::kFirstArgumentNotRegExp, 790 isolate->factory()->NewStringFromStaticChars( 791 "String.prototype.endsWith"))); 792 } 793 Handle<String> search_string; 794 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, search_string, 795 Object::ToString(isolate, search)); 796 797 Handle<Object> position = args.atOrUndefined(isolate, 2); 798 int end; 799 800 if (position->IsUndefined(isolate)) { 801 end = str->length(); 802 } else { 803 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, position, 804 Object::ToInteger(isolate, position)); 805 end = str->ToValidIndex(*position); 806 } 807 808 int start = end - search_string->length(); 809 if (start < 0) return isolate->heap()->false_value(); 810 811 str = String::Flatten(str); 812 search_string = String::Flatten(search_string); 813 814 DisallowHeapAllocation no_gc; // ensure vectors stay valid 815 String::FlatContent str_content = str->GetFlatContent(); 816 String::FlatContent search_content = search_string->GetFlatContent(); 817 818 if (str_content.IsOneByte() && search_content.IsOneByte()) { 819 Vector<const uint8_t> str_vector = str_content.ToOneByteVector(); 820 Vector<const uint8_t> search_vector = search_content.ToOneByteVector(); 821 822 return isolate->heap()->ToBoolean(memcmp(str_vector.start() + start, 823 search_vector.start(), 824 search_string->length()) == 0); 825 } 826 827 FlatStringReader str_reader(isolate, str); 828 FlatStringReader search_reader(isolate, search_string); 829 830 for (int i = 0; i < search_string->length(); i++) { 831 if (str_reader.Get(start + i) != search_reader.Get(i)) { 832 return isolate->heap()->false_value(); 833 } 834 } 835 return isolate->heap()->true_value(); 836 } 837 838 // ES6 section 21.1.3.7 839 // String.prototype.includes ( searchString [ , position ] ) 840 BUILTIN(StringPrototypeIncludes) { 841 HandleScope handle_scope(isolate); 842 TO_THIS_STRING(str, "String.prototype.includes"); 843 844 // Check if the search string is a regExp and fail if it is. 845 Handle<Object> search = args.atOrUndefined(isolate, 1); 846 Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search); 847 if (is_reg_exp.IsNothing()) { 848 DCHECK(isolate->has_pending_exception()); 849 return isolate->heap()->exception(); 850 } 851 if (is_reg_exp.FromJust()) { 852 THROW_NEW_ERROR_RETURN_FAILURE( 853 isolate, NewTypeError(MessageTemplate::kFirstArgumentNotRegExp, 854 isolate->factory()->NewStringFromStaticChars( 855 "String.prototype.includes"))); 856 } 857 Handle<String> search_string; 858 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, search_string, 859 Object::ToString(isolate, search)); 860 Handle<Object> position; 861 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( 862 isolate, position, 863 Object::ToInteger(isolate, args.atOrUndefined(isolate, 2))); 864 865 uint32_t index = str->ToValidIndex(*position); 866 int index_in_str = String::IndexOf(isolate, str, search_string, index); 867 return *isolate->factory()->ToBoolean(index_in_str != -1); 868 } 869 870 void StringBuiltinsAssembler::StringIndexOf( 871 Node* receiver, Node* instance_type, Node* search_string, 872 Node* search_string_instance_type, Node* position, 873 std::function<void(Node*)> f_return) { 874 CSA_ASSERT(this, IsString(receiver)); 875 CSA_ASSERT(this, IsString(search_string)); 876 CSA_ASSERT(this, TaggedIsSmi(position)); 877 878 Label zero_length_needle(this), 879 call_runtime_unchecked(this, Label::kDeferred), return_minus_1(this), 880 check_search_string(this), continue_fast_path(this); 881 882 Node* const int_zero = IntPtrConstant(0); 883 Variable var_needle_byte(this, MachineType::PointerRepresentation(), 884 int_zero); 885 Variable var_string_addr(this, MachineType::PointerRepresentation(), 886 int_zero); 887 888 Node* needle_length = SmiUntag(LoadStringLength(search_string)); 889 // Use faster/complex runtime fallback for long search strings. 890 GotoIf(IntPtrLessThan(IntPtrConstant(1), needle_length), 891 &call_runtime_unchecked); 892 Node* string_length = SmiUntag(LoadStringLength(receiver)); 893 Node* start_position = IntPtrMax(SmiUntag(position), int_zero); 894 895 GotoIf(IntPtrEqual(int_zero, needle_length), &zero_length_needle); 896 // Check that the needle fits in the start position. 897 GotoIfNot(IntPtrLessThanOrEqual(needle_length, 898 IntPtrSub(string_length, start_position)), 899 &return_minus_1); 900 901 // Load the string address. 902 { 903 Label if_onebyte_sequential(this); 904 Label if_onebyte_external(this, Label::kDeferred); 905 906 // Only support one-byte strings on the fast path. 907 DispatchOnStringInstanceType(instance_type, &if_onebyte_sequential, 908 &if_onebyte_external, &call_runtime_unchecked); 909 910 Bind(&if_onebyte_sequential); 911 { 912 var_string_addr.Bind( 913 OneByteCharAddress(BitcastTaggedToWord(receiver), start_position)); 914 Goto(&check_search_string); 915 } 916 917 Bind(&if_onebyte_external); 918 { 919 Node* const unpacked = TryDerefExternalString(receiver, instance_type, 920 &call_runtime_unchecked); 921 var_string_addr.Bind(OneByteCharAddress(unpacked, start_position)); 922 Goto(&check_search_string); 923 } 924 } 925 926 // Load the needle character. 927 Bind(&check_search_string); 928 { 929 Label if_onebyte_sequential(this); 930 Label if_onebyte_external(this, Label::kDeferred); 931 932 DispatchOnStringInstanceType(search_string_instance_type, 933 &if_onebyte_sequential, &if_onebyte_external, 934 &call_runtime_unchecked); 935 936 Bind(&if_onebyte_sequential); 937 { 938 var_needle_byte.Bind( 939 ChangeInt32ToIntPtr(LoadOneByteChar(search_string, int_zero))); 940 Goto(&continue_fast_path); 941 } 942 943 Bind(&if_onebyte_external); 944 { 945 Node* const unpacked = TryDerefExternalString( 946 search_string, search_string_instance_type, &call_runtime_unchecked); 947 var_needle_byte.Bind( 948 ChangeInt32ToIntPtr(LoadOneByteChar(unpacked, int_zero))); 949 Goto(&continue_fast_path); 950 } 951 } 952 953 Bind(&continue_fast_path); 954 { 955 Node* needle_byte = var_needle_byte.value(); 956 Node* string_addr = var_string_addr.value(); 957 Node* search_length = IntPtrSub(string_length, start_position); 958 // Call out to the highly optimized memchr to perform the actual byte 959 // search. 960 Node* memchr = 961 ExternalConstant(ExternalReference::libc_memchr_function(isolate())); 962 Node* result_address = 963 CallCFunction3(MachineType::Pointer(), MachineType::Pointer(), 964 MachineType::IntPtr(), MachineType::UintPtr(), memchr, 965 string_addr, needle_byte, search_length); 966 GotoIf(WordEqual(result_address, int_zero), &return_minus_1); 967 Node* result_index = 968 IntPtrAdd(IntPtrSub(result_address, string_addr), start_position); 969 f_return(SmiTag(result_index)); 970 } 971 972 Bind(&return_minus_1); 973 f_return(SmiConstant(-1)); 974 975 Bind(&zero_length_needle); 976 { 977 Comment("0-length search_string"); 978 f_return(SmiTag(IntPtrMin(string_length, start_position))); 979 } 980 981 Bind(&call_runtime_unchecked); 982 { 983 // Simplified version of the runtime call where the types of the arguments 984 // are already known due to type checks in this stub. 985 Comment("Call Runtime Unchecked"); 986 Node* result = CallRuntime(Runtime::kStringIndexOfUnchecked, SmiConstant(0), 987 receiver, search_string, position); 988 f_return(result); 989 } 990 } 991 992 // ES6 String.prototype.indexOf(searchString [, position]) 993 // #sec-string.prototype.indexof 994 // Unchecked helper for builtins lowering. 995 TF_BUILTIN(StringIndexOf, StringBuiltinsAssembler) { 996 Node* receiver = Parameter(0); 997 Node* search_string = Parameter(1); 998 Node* position = Parameter(2); 999 1000 Node* instance_type = LoadInstanceType(receiver); 1001 Node* search_string_instance_type = LoadInstanceType(search_string); 1002 1003 StringIndexOf(receiver, instance_type, search_string, 1004 search_string_instance_type, position, 1005 [this](Node* result) { this->Return(result); }); 1006 } 1007 1008 // ES6 String.prototype.indexOf(searchString [, position]) 1009 // #sec-string.prototype.indexof 1010 TF_BUILTIN(StringPrototypeIndexOf, StringBuiltinsAssembler) { 1011 Variable search_string(this, MachineRepresentation::kTagged), 1012 position(this, MachineRepresentation::kTagged); 1013 Label call_runtime(this), call_runtime_unchecked(this), argc_0(this), 1014 no_argc_0(this), argc_1(this), no_argc_1(this), argc_2(this), 1015 fast_path(this), return_minus_1(this); 1016 1017 Node* argc = Parameter(BuiltinDescriptor::kArgumentsCount); 1018 Node* context = Parameter(BuiltinDescriptor::kContext); 1019 1020 CodeStubArguments arguments(this, ChangeInt32ToIntPtr(argc)); 1021 Node* receiver = arguments.GetReceiver(); 1022 // From now on use word-size argc value. 1023 argc = arguments.GetLength(); 1024 1025 GotoIf(IntPtrEqual(argc, IntPtrConstant(0)), &argc_0); 1026 GotoIf(IntPtrEqual(argc, IntPtrConstant(1)), &argc_1); 1027 Goto(&argc_2); 1028 Bind(&argc_0); 1029 { 1030 Comment("0 Argument case"); 1031 Node* undefined = UndefinedConstant(); 1032 search_string.Bind(undefined); 1033 position.Bind(undefined); 1034 Goto(&call_runtime); 1035 } 1036 Bind(&argc_1); 1037 { 1038 Comment("1 Argument case"); 1039 search_string.Bind(arguments.AtIndex(0)); 1040 position.Bind(SmiConstant(0)); 1041 Goto(&fast_path); 1042 } 1043 Bind(&argc_2); 1044 { 1045 Comment("2 Argument case"); 1046 search_string.Bind(arguments.AtIndex(0)); 1047 position.Bind(arguments.AtIndex(1)); 1048 GotoIfNot(TaggedIsSmi(position.value()), &call_runtime); 1049 Goto(&fast_path); 1050 } 1051 1052 Bind(&fast_path); 1053 { 1054 Comment("Fast Path"); 1055 GotoIf(TaggedIsSmi(receiver), &call_runtime); 1056 Node* needle = search_string.value(); 1057 GotoIf(TaggedIsSmi(needle), &call_runtime); 1058 1059 Node* instance_type = LoadInstanceType(receiver); 1060 GotoIfNot(IsStringInstanceType(instance_type), &call_runtime); 1061 1062 Node* needle_instance_type = LoadInstanceType(needle); 1063 GotoIfNot(IsStringInstanceType(needle_instance_type), &call_runtime); 1064 1065 StringIndexOf( 1066 receiver, instance_type, needle, needle_instance_type, position.value(), 1067 [&arguments](Node* result) { arguments.PopAndReturn(result); }); 1068 } 1069 1070 Bind(&call_runtime); 1071 { 1072 Comment("Call Runtime"); 1073 Node* result = CallRuntime(Runtime::kStringIndexOf, context, receiver, 1074 search_string.value(), position.value()); 1075 arguments.PopAndReturn(result); 1076 } 1077 } 1078 1079 // ES6 section 21.1.3.9 1080 // String.prototype.lastIndexOf ( searchString [ , position ] ) 1081 BUILTIN(StringPrototypeLastIndexOf) { 1082 HandleScope handle_scope(isolate); 1083 return String::LastIndexOf(isolate, args.receiver(), 1084 args.atOrUndefined(isolate, 1), 1085 args.atOrUndefined(isolate, 2)); 1086 } 1087 1088 // ES6 section 21.1.3.10 String.prototype.localeCompare ( that ) 1089 // 1090 // This function is implementation specific. For now, we do not 1091 // do anything locale specific. 1092 // If internationalization is enabled, then i18n.js will override this function 1093 // and provide the proper functionality, so this is just a fallback. 1094 BUILTIN(StringPrototypeLocaleCompare) { 1095 HandleScope handle_scope(isolate); 1096 DCHECK_EQ(2, args.length()); 1097 1098 TO_THIS_STRING(str1, "String.prototype.localeCompare"); 1099 Handle<String> str2; 1100 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, str2, 1101 Object::ToString(isolate, args.at(1))); 1102 1103 if (str1.is_identical_to(str2)) return Smi::kZero; // Equal. 1104 int str1_length = str1->length(); 1105 int str2_length = str2->length(); 1106 1107 // Decide trivial cases without flattening. 1108 if (str1_length == 0) { 1109 if (str2_length == 0) return Smi::kZero; // Equal. 1110 return Smi::FromInt(-str2_length); 1111 } else { 1112 if (str2_length == 0) return Smi::FromInt(str1_length); 1113 } 1114 1115 int end = str1_length < str2_length ? str1_length : str2_length; 1116 1117 // No need to flatten if we are going to find the answer on the first 1118 // character. At this point we know there is at least one character 1119 // in each string, due to the trivial case handling above. 1120 int d = str1->Get(0) - str2->Get(0); 1121 if (d != 0) return Smi::FromInt(d); 1122 1123 str1 = String::Flatten(str1); 1124 str2 = String::Flatten(str2); 1125 1126 DisallowHeapAllocation no_gc; 1127 String::FlatContent flat1 = str1->GetFlatContent(); 1128 String::FlatContent flat2 = str2->GetFlatContent(); 1129 1130 for (int i = 0; i < end; i++) { 1131 if (flat1.Get(i) != flat2.Get(i)) { 1132 return Smi::FromInt(flat1.Get(i) - flat2.Get(i)); 1133 } 1134 } 1135 1136 return Smi::FromInt(str1_length - str2_length); 1137 } 1138 1139 // ES6 section 21.1.3.12 String.prototype.normalize ( [form] ) 1140 // 1141 // Simply checks the argument is valid and returns the string itself. 1142 // If internationalization is enabled, then i18n.js will override this function 1143 // and provide the proper functionality, so this is just a fallback. 1144 BUILTIN(StringPrototypeNormalize) { 1145 HandleScope handle_scope(isolate); 1146 TO_THIS_STRING(string, "String.prototype.normalize"); 1147 1148 Handle<Object> form_input = args.atOrUndefined(isolate, 1); 1149 if (form_input->IsUndefined(isolate)) return *string; 1150 1151 Handle<String> form; 1152 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, form, 1153 Object::ToString(isolate, form_input)); 1154 1155 if (!(String::Equals(form, 1156 isolate->factory()->NewStringFromStaticChars("NFC")) || 1157 String::Equals(form, 1158 isolate->factory()->NewStringFromStaticChars("NFD")) || 1159 String::Equals(form, 1160 isolate->factory()->NewStringFromStaticChars("NFKC")) || 1161 String::Equals(form, 1162 isolate->factory()->NewStringFromStaticChars("NFKD")))) { 1163 Handle<String> valid_forms = 1164 isolate->factory()->NewStringFromStaticChars("NFC, NFD, NFKC, NFKD"); 1165 THROW_NEW_ERROR_RETURN_FAILURE( 1166 isolate, 1167 NewRangeError(MessageTemplate::kNormalizationForm, valid_forms)); 1168 } 1169 1170 return *string; 1171 } 1172 1173 compiler::Node* StringBuiltinsAssembler::IsNullOrUndefined(Node* const value) { 1174 return Word32Or(IsUndefined(value), IsNull(value)); 1175 } 1176 1177 void StringBuiltinsAssembler::RequireObjectCoercible(Node* const context, 1178 Node* const value, 1179 const char* method_name) { 1180 Label out(this), throw_exception(this, Label::kDeferred); 1181 Branch(IsNullOrUndefined(value), &throw_exception, &out); 1182 1183 Bind(&throw_exception); 1184 TailCallRuntime( 1185 Runtime::kThrowCalledOnNullOrUndefined, context, 1186 HeapConstant(factory()->NewStringFromAsciiChecked(method_name, TENURED))); 1187 1188 Bind(&out); 1189 } 1190 1191 void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol( 1192 Node* const context, Node* const object, Handle<Symbol> symbol, 1193 const NodeFunction0& regexp_call, const NodeFunction1& generic_call) { 1194 Label out(this); 1195 1196 // Smis definitely don't have an attached symbol. 1197 GotoIf(TaggedIsSmi(object), &out); 1198 1199 Node* const object_map = LoadMap(object); 1200 1201 // Skip the slow lookup for Strings. 1202 { 1203 Label next(this); 1204 1205 GotoIfNot(IsStringInstanceType(LoadMapInstanceType(object_map)), &next); 1206 1207 Node* const native_context = LoadNativeContext(context); 1208 Node* const initial_proto_initial_map = LoadContextElement( 1209 native_context, Context::STRING_FUNCTION_PROTOTYPE_MAP_INDEX); 1210 1211 Node* const string_fun = 1212 LoadContextElement(native_context, Context::STRING_FUNCTION_INDEX); 1213 Node* const initial_map = 1214 LoadObjectField(string_fun, JSFunction::kPrototypeOrInitialMapOffset); 1215 Node* const proto_map = LoadMap(LoadMapPrototype(initial_map)); 1216 1217 Branch(WordEqual(proto_map, initial_proto_initial_map), &out, &next); 1218 1219 Bind(&next); 1220 } 1221 1222 // Take the fast path for RegExps. 1223 { 1224 Label stub_call(this), slow_lookup(this); 1225 1226 RegExpBuiltinsAssembler regexp_asm(state()); 1227 regexp_asm.BranchIfFastRegExp(context, object, object_map, &stub_call, 1228 &slow_lookup); 1229 1230 Bind(&stub_call); 1231 Return(regexp_call()); 1232 1233 Bind(&slow_lookup); 1234 } 1235 1236 GotoIf(IsNullOrUndefined(object), &out); 1237 1238 // Fall back to a slow lookup of {object[symbol]}. 1239 1240 Callable getproperty_callable = CodeFactory::GetProperty(isolate()); 1241 Node* const key = HeapConstant(symbol); 1242 Node* const maybe_func = CallStub(getproperty_callable, context, object, key); 1243 1244 GotoIf(IsUndefined(maybe_func), &out); 1245 1246 // Attempt to call the function. 1247 1248 Node* const result = generic_call(maybe_func); 1249 Return(result); 1250 1251 Bind(&out); 1252 } 1253 1254 // ES6 section 21.1.3.16 String.prototype.replace ( search, replace ) 1255 TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) { 1256 Label out(this); 1257 1258 Node* const receiver = Parameter(0); 1259 Node* const search = Parameter(1); 1260 Node* const replace = Parameter(2); 1261 Node* const context = Parameter(5); 1262 1263 Node* const smi_zero = SmiConstant(0); 1264 1265 RequireObjectCoercible(context, receiver, "String.prototype.replace"); 1266 1267 // Redirect to replacer method if {search[@@replace]} is not undefined. 1268 1269 MaybeCallFunctionAtSymbol( 1270 context, search, isolate()->factory()->replace_symbol(), 1271 [=]() { 1272 Callable tostring_callable = CodeFactory::ToString(isolate()); 1273 Node* const subject_string = 1274 CallStub(tostring_callable, context, receiver); 1275 1276 Callable replace_callable = CodeFactory::RegExpReplace(isolate()); 1277 return CallStub(replace_callable, context, search, subject_string, 1278 replace); 1279 }, 1280 [=](Node* fn) { 1281 Callable call_callable = CodeFactory::Call(isolate()); 1282 return CallJS(call_callable, context, fn, search, receiver, replace); 1283 }); 1284 1285 // Convert {receiver} and {search} to strings. 1286 1287 Callable tostring_callable = CodeFactory::ToString(isolate()); 1288 Callable indexof_callable = CodeFactory::StringIndexOf(isolate()); 1289 1290 Node* const subject_string = CallStub(tostring_callable, context, receiver); 1291 Node* const search_string = CallStub(tostring_callable, context, search); 1292 1293 Node* const subject_length = LoadStringLength(subject_string); 1294 Node* const search_length = LoadStringLength(search_string); 1295 1296 // Fast-path single-char {search}, long {receiver}, and simple string 1297 // {replace}. 1298 { 1299 Label next(this); 1300 1301 GotoIfNot(SmiEqual(search_length, SmiConstant(1)), &next); 1302 GotoIfNot(SmiGreaterThan(subject_length, SmiConstant(0xFF)), &next); 1303 GotoIf(TaggedIsSmi(replace), &next); 1304 GotoIfNot(IsString(replace), &next); 1305 1306 Node* const dollar_string = HeapConstant( 1307 isolate()->factory()->LookupSingleCharacterStringFromCode('$')); 1308 Node* const dollar_ix = 1309 CallStub(indexof_callable, context, replace, dollar_string, smi_zero); 1310 GotoIfNot(SmiIsNegative(dollar_ix), &next); 1311 1312 // Searching by traversing a cons string tree and replace with cons of 1313 // slices works only when the replaced string is a single character, being 1314 // replaced by a simple string and only pays off for long strings. 1315 // TODO(jgruber): Reevaluate if this is still beneficial. 1316 // TODO(jgruber): TailCallRuntime when it correctly handles adapter frames. 1317 Return(CallRuntime(Runtime::kStringReplaceOneCharWithString, context, 1318 subject_string, search_string, replace)); 1319 1320 Bind(&next); 1321 } 1322 1323 // TODO(jgruber): Extend StringIndexOf to handle two-byte strings and 1324 // longer substrings - we can handle up to 8 chars (one-byte) / 4 chars 1325 // (2-byte). 1326 1327 Node* const match_start_index = CallStub( 1328 indexof_callable, context, subject_string, search_string, smi_zero); 1329 CSA_ASSERT(this, TaggedIsSmi(match_start_index)); 1330 1331 // Early exit if no match found. 1332 { 1333 Label next(this), return_subject(this); 1334 1335 GotoIfNot(SmiIsNegative(match_start_index), &next); 1336 1337 // The spec requires to perform ToString(replace) if the {replace} is not 1338 // callable even if we are going to exit here. 1339 // Since ToString() being applied to Smi does not have side effects for 1340 // numbers we can skip it. 1341 GotoIf(TaggedIsSmi(replace), &return_subject); 1342 GotoIf(IsCallableMap(LoadMap(replace)), &return_subject); 1343 1344 // TODO(jgruber): Could introduce ToStringSideeffectsStub which only 1345 // performs observable parts of ToString. 1346 CallStub(tostring_callable, context, replace); 1347 Goto(&return_subject); 1348 1349 Bind(&return_subject); 1350 Return(subject_string); 1351 1352 Bind(&next); 1353 } 1354 1355 Node* const match_end_index = SmiAdd(match_start_index, search_length); 1356 1357 Callable substring_callable = CodeFactory::SubString(isolate()); 1358 Callable stringadd_callable = 1359 CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED); 1360 1361 Variable var_result(this, MachineRepresentation::kTagged, 1362 EmptyStringConstant()); 1363 1364 // Compute the prefix. 1365 { 1366 Label next(this); 1367 1368 GotoIf(SmiEqual(match_start_index, smi_zero), &next); 1369 Node* const prefix = CallStub(substring_callable, context, subject_string, 1370 smi_zero, match_start_index); 1371 var_result.Bind(prefix); 1372 1373 Goto(&next); 1374 Bind(&next); 1375 } 1376 1377 // Compute the string to replace with. 1378 1379 Label if_iscallablereplace(this), if_notcallablereplace(this); 1380 GotoIf(TaggedIsSmi(replace), &if_notcallablereplace); 1381 Branch(IsCallableMap(LoadMap(replace)), &if_iscallablereplace, 1382 &if_notcallablereplace); 1383 1384 Bind(&if_iscallablereplace); 1385 { 1386 Callable call_callable = CodeFactory::Call(isolate()); 1387 Node* const replacement = 1388 CallJS(call_callable, context, replace, UndefinedConstant(), 1389 search_string, match_start_index, subject_string); 1390 Node* const replacement_string = 1391 CallStub(tostring_callable, context, replacement); 1392 var_result.Bind(CallStub(stringadd_callable, context, var_result.value(), 1393 replacement_string)); 1394 Goto(&out); 1395 } 1396 1397 Bind(&if_notcallablereplace); 1398 { 1399 Node* const replace_string = CallStub(tostring_callable, context, replace); 1400 1401 // TODO(jgruber): Simplified GetSubstitution implementation in CSA. 1402 Node* const matched = CallStub(substring_callable, context, subject_string, 1403 match_start_index, match_end_index); 1404 Node* const replacement_string = 1405 CallRuntime(Runtime::kGetSubstitution, context, matched, subject_string, 1406 match_start_index, replace_string); 1407 var_result.Bind(CallStub(stringadd_callable, context, var_result.value(), 1408 replacement_string)); 1409 Goto(&out); 1410 } 1411 1412 Bind(&out); 1413 { 1414 Node* const suffix = CallStub(substring_callable, context, subject_string, 1415 match_end_index, subject_length); 1416 Node* const result = 1417 CallStub(stringadd_callable, context, var_result.value(), suffix); 1418 Return(result); 1419 } 1420 } 1421 1422 // ES6 section 21.1.3.19 String.prototype.split ( separator, limit ) 1423 TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) { 1424 Label out(this); 1425 1426 Node* const receiver = Parameter(0); 1427 Node* const separator = Parameter(1); 1428 Node* const limit = Parameter(2); 1429 Node* const context = Parameter(5); 1430 1431 Node* const smi_zero = SmiConstant(0); 1432 1433 RequireObjectCoercible(context, receiver, "String.prototype.split"); 1434 1435 // Redirect to splitter method if {separator[@@split]} is not undefined. 1436 1437 MaybeCallFunctionAtSymbol( 1438 context, separator, isolate()->factory()->split_symbol(), 1439 [=]() { 1440 Callable tostring_callable = CodeFactory::ToString(isolate()); 1441 Node* const subject_string = 1442 CallStub(tostring_callable, context, receiver); 1443 1444 Callable split_callable = CodeFactory::RegExpSplit(isolate()); 1445 return CallStub(split_callable, context, separator, subject_string, 1446 limit); 1447 }, 1448 [=](Node* fn) { 1449 Callable call_callable = CodeFactory::Call(isolate()); 1450 return CallJS(call_callable, context, fn, separator, receiver, limit); 1451 }); 1452 1453 // String and integer conversions. 1454 // TODO(jgruber): The old implementation used Uint32Max instead of SmiMax - 1455 // but AFAIK there should not be a difference since arrays are capped at Smi 1456 // lengths. 1457 1458 Callable tostring_callable = CodeFactory::ToString(isolate()); 1459 Node* const subject_string = CallStub(tostring_callable, context, receiver); 1460 Node* const limit_number = 1461 Select(IsUndefined(limit), [=]() { return SmiConstant(Smi::kMaxValue); }, 1462 [=]() { return ToUint32(context, limit); }, 1463 MachineRepresentation::kTagged); 1464 Node* const separator_string = 1465 CallStub(tostring_callable, context, separator); 1466 1467 // Shortcut for {limit} == 0. 1468 { 1469 Label next(this); 1470 GotoIfNot(SmiEqual(limit_number, smi_zero), &next); 1471 1472 const ElementsKind kind = FAST_ELEMENTS; 1473 Node* const native_context = LoadNativeContext(context); 1474 Node* const array_map = LoadJSArrayElementsMap(kind, native_context); 1475 1476 Node* const length = smi_zero; 1477 Node* const capacity = IntPtrConstant(0); 1478 Node* const result = AllocateJSArray(kind, array_map, capacity, length); 1479 1480 Return(result); 1481 1482 Bind(&next); 1483 } 1484 1485 // ECMA-262 says that if {separator} is undefined, the result should 1486 // be an array of size 1 containing the entire string. 1487 { 1488 Label next(this); 1489 GotoIfNot(IsUndefined(separator), &next); 1490 1491 const ElementsKind kind = FAST_ELEMENTS; 1492 Node* const native_context = LoadNativeContext(context); 1493 Node* const array_map = LoadJSArrayElementsMap(kind, native_context); 1494 1495 Node* const length = SmiConstant(1); 1496 Node* const capacity = IntPtrConstant(1); 1497 Node* const result = AllocateJSArray(kind, array_map, capacity, length); 1498 1499 Node* const fixed_array = LoadElements(result); 1500 StoreFixedArrayElement(fixed_array, 0, subject_string); 1501 1502 Return(result); 1503 1504 Bind(&next); 1505 } 1506 1507 // If the separator string is empty then return the elements in the subject. 1508 { 1509 Label next(this); 1510 GotoIfNot(SmiEqual(LoadStringLength(separator_string), smi_zero), &next); 1511 1512 Node* const result = CallRuntime(Runtime::kStringToArray, context, 1513 subject_string, limit_number); 1514 Return(result); 1515 1516 Bind(&next); 1517 } 1518 1519 Node* const result = 1520 CallRuntime(Runtime::kStringSplit, context, subject_string, 1521 separator_string, limit_number); 1522 Return(result); 1523 } 1524 1525 // ES6 section B.2.3.1 String.prototype.substr ( start, length ) 1526 TF_BUILTIN(StringPrototypeSubstr, CodeStubAssembler) { 1527 Label out(this), handle_length(this); 1528 1529 Variable var_start(this, MachineRepresentation::kTagged); 1530 Variable var_length(this, MachineRepresentation::kTagged); 1531 1532 Node* const receiver = Parameter(0); 1533 Node* const start = Parameter(1); 1534 Node* const length = Parameter(2); 1535 Node* const context = Parameter(5); 1536 1537 Node* const zero = SmiConstant(Smi::kZero); 1538 1539 // Check that {receiver} is coercible to Object and convert it to a String. 1540 Node* const string = 1541 ToThisString(context, receiver, "String.prototype.substr"); 1542 1543 Node* const string_length = LoadStringLength(string); 1544 1545 // Conversions and bounds-checks for {start}. 1546 { 1547 Node* const start_int = 1548 ToInteger(context, start, CodeStubAssembler::kTruncateMinusZero); 1549 1550 Label if_issmi(this), if_isheapnumber(this, Label::kDeferred); 1551 Branch(TaggedIsSmi(start_int), &if_issmi, &if_isheapnumber); 1552 1553 Bind(&if_issmi); 1554 { 1555 Node* const length_plus_start = SmiAdd(string_length, start_int); 1556 var_start.Bind(Select(SmiLessThan(start_int, zero), 1557 [&] { return SmiMax(length_plus_start, zero); }, 1558 [&] { return start_int; }, 1559 MachineRepresentation::kTagged)); 1560 Goto(&handle_length); 1561 } 1562 1563 Bind(&if_isheapnumber); 1564 { 1565 // If {start} is a heap number, it is definitely out of bounds. If it is 1566 // negative, {start} = max({string_length} + {start}),0) = 0'. If it is 1567 // positive, set {start} to {string_length} which ultimately results in 1568 // returning an empty string. 1569 Node* const float_zero = Float64Constant(0.); 1570 Node* const start_float = LoadHeapNumberValue(start_int); 1571 var_start.Bind(SelectTaggedConstant( 1572 Float64LessThan(start_float, float_zero), zero, string_length)); 1573 Goto(&handle_length); 1574 } 1575 } 1576 1577 // Conversions and bounds-checks for {length}. 1578 Bind(&handle_length); 1579 { 1580 Label if_issmi(this), if_isheapnumber(this, Label::kDeferred); 1581 1582 // Default to {string_length} if {length} is undefined. 1583 { 1584 Label if_isundefined(this, Label::kDeferred), if_isnotundefined(this); 1585 Branch(WordEqual(length, UndefinedConstant()), &if_isundefined, 1586 &if_isnotundefined); 1587 1588 Bind(&if_isundefined); 1589 var_length.Bind(string_length); 1590 Goto(&if_issmi); 1591 1592 Bind(&if_isnotundefined); 1593 var_length.Bind( 1594 ToInteger(context, length, CodeStubAssembler::kTruncateMinusZero)); 1595 } 1596 1597 Branch(TaggedIsSmi(var_length.value()), &if_issmi, &if_isheapnumber); 1598 1599 // Set {length} to min(max({length}, 0), {string_length} - {start} 1600 Bind(&if_issmi); 1601 { 1602 Node* const positive_length = SmiMax(var_length.value(), zero); 1603 1604 Node* const minimal_length = SmiSub(string_length, var_start.value()); 1605 var_length.Bind(SmiMin(positive_length, minimal_length)); 1606 1607 GotoIfNot(SmiLessThanOrEqual(var_length.value(), zero), &out); 1608 Return(EmptyStringConstant()); 1609 } 1610 1611 Bind(&if_isheapnumber); 1612 { 1613 // If {length} is a heap number, it is definitely out of bounds. There are 1614 // two cases according to the spec: if it is negative, "" is returned; if 1615 // it is positive, then length is set to {string_length} - {start}. 1616 1617 CSA_ASSERT(this, IsHeapNumberMap(LoadMap(var_length.value()))); 1618 1619 Label if_isnegative(this), if_ispositive(this); 1620 Node* const float_zero = Float64Constant(0.); 1621 Node* const length_float = LoadHeapNumberValue(var_length.value()); 1622 Branch(Float64LessThan(length_float, float_zero), &if_isnegative, 1623 &if_ispositive); 1624 1625 Bind(&if_isnegative); 1626 Return(EmptyStringConstant()); 1627 1628 Bind(&if_ispositive); 1629 { 1630 var_length.Bind(SmiSub(string_length, var_start.value())); 1631 GotoIfNot(SmiLessThanOrEqual(var_length.value(), zero), &out); 1632 Return(EmptyStringConstant()); 1633 } 1634 } 1635 } 1636 1637 Bind(&out); 1638 { 1639 Node* const end = SmiAdd(var_start.value(), var_length.value()); 1640 Node* const result = SubString(context, string, var_start.value(), end); 1641 Return(result); 1642 } 1643 } 1644 1645 compiler::Node* StringBuiltinsAssembler::ToSmiBetweenZeroAnd(Node* context, 1646 Node* value, 1647 Node* limit) { 1648 Label out(this); 1649 Variable var_result(this, MachineRepresentation::kTagged); 1650 1651 Node* const value_int = 1652 this->ToInteger(context, value, CodeStubAssembler::kTruncateMinusZero); 1653 1654 Label if_issmi(this), if_isnotsmi(this, Label::kDeferred); 1655 Branch(TaggedIsSmi(value_int), &if_issmi, &if_isnotsmi); 1656 1657 Bind(&if_issmi); 1658 { 1659 Label if_isinbounds(this), if_isoutofbounds(this, Label::kDeferred); 1660 Branch(SmiAbove(value_int, limit), &if_isoutofbounds, &if_isinbounds); 1661 1662 Bind(&if_isinbounds); 1663 { 1664 var_result.Bind(value_int); 1665 Goto(&out); 1666 } 1667 1668 Bind(&if_isoutofbounds); 1669 { 1670 Node* const zero = SmiConstant(Smi::kZero); 1671 var_result.Bind( 1672 SelectTaggedConstant(SmiLessThan(value_int, zero), zero, limit)); 1673 Goto(&out); 1674 } 1675 } 1676 1677 Bind(&if_isnotsmi); 1678 { 1679 // {value} is a heap number - in this case, it is definitely out of bounds. 1680 CSA_ASSERT(this, IsHeapNumberMap(LoadMap(value_int))); 1681 1682 Node* const float_zero = Float64Constant(0.); 1683 Node* const smi_zero = SmiConstant(Smi::kZero); 1684 Node* const value_float = LoadHeapNumberValue(value_int); 1685 var_result.Bind(SelectTaggedConstant( 1686 Float64LessThan(value_float, float_zero), smi_zero, limit)); 1687 Goto(&out); 1688 } 1689 1690 Bind(&out); 1691 return var_result.value(); 1692 } 1693 1694 // ES6 section 21.1.3.19 String.prototype.substring ( start, end ) 1695 TF_BUILTIN(StringPrototypeSubstring, StringBuiltinsAssembler) { 1696 Label out(this); 1697 1698 Variable var_start(this, MachineRepresentation::kTagged); 1699 Variable var_end(this, MachineRepresentation::kTagged); 1700 1701 Node* const receiver = Parameter(0); 1702 Node* const start = Parameter(1); 1703 Node* const end = Parameter(2); 1704 Node* const context = Parameter(5); 1705 1706 // Check that {receiver} is coercible to Object and convert it to a String. 1707 Node* const string = 1708 ToThisString(context, receiver, "String.prototype.substring"); 1709 1710 Node* const length = LoadStringLength(string); 1711 1712 // Conversion and bounds-checks for {start}. 1713 var_start.Bind(ToSmiBetweenZeroAnd(context, start, length)); 1714 1715 // Conversion and bounds-checks for {end}. 1716 { 1717 var_end.Bind(length); 1718 GotoIf(WordEqual(end, UndefinedConstant()), &out); 1719 1720 var_end.Bind(ToSmiBetweenZeroAnd(context, end, length)); 1721 1722 Label if_endislessthanstart(this); 1723 Branch(SmiLessThan(var_end.value(), var_start.value()), 1724 &if_endislessthanstart, &out); 1725 1726 Bind(&if_endislessthanstart); 1727 { 1728 Node* const tmp = var_end.value(); 1729 var_end.Bind(var_start.value()); 1730 var_start.Bind(tmp); 1731 Goto(&out); 1732 } 1733 } 1734 1735 Bind(&out); 1736 { 1737 Node* result = 1738 SubString(context, string, var_start.value(), var_end.value()); 1739 Return(result); 1740 } 1741 } 1742 1743 BUILTIN(StringPrototypeStartsWith) { 1744 HandleScope handle_scope(isolate); 1745 TO_THIS_STRING(str, "String.prototype.startsWith"); 1746 1747 // Check if the search string is a regExp and fail if it is. 1748 Handle<Object> search = args.atOrUndefined(isolate, 1); 1749 Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search); 1750 if (is_reg_exp.IsNothing()) { 1751 DCHECK(isolate->has_pending_exception()); 1752 return isolate->heap()->exception(); 1753 } 1754 if (is_reg_exp.FromJust()) { 1755 THROW_NEW_ERROR_RETURN_FAILURE( 1756 isolate, NewTypeError(MessageTemplate::kFirstArgumentNotRegExp, 1757 isolate->factory()->NewStringFromStaticChars( 1758 "String.prototype.startsWith"))); 1759 } 1760 Handle<String> search_string; 1761 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, search_string, 1762 Object::ToString(isolate, search)); 1763 1764 Handle<Object> position = args.atOrUndefined(isolate, 2); 1765 int start; 1766 1767 if (position->IsUndefined(isolate)) { 1768 start = 0; 1769 } else { 1770 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, position, 1771 Object::ToInteger(isolate, position)); 1772 start = str->ToValidIndex(*position); 1773 } 1774 1775 if (start + search_string->length() > str->length()) { 1776 return isolate->heap()->false_value(); 1777 } 1778 1779 FlatStringReader str_reader(isolate, String::Flatten(str)); 1780 FlatStringReader search_reader(isolate, String::Flatten(search_string)); 1781 1782 for (int i = 0; i < search_string->length(); i++) { 1783 if (str_reader.Get(start + i) != search_reader.Get(i)) { 1784 return isolate->heap()->false_value(); 1785 } 1786 } 1787 return isolate->heap()->true_value(); 1788 } 1789 1790 // ES6 section 21.1.3.25 String.prototype.toString () 1791 TF_BUILTIN(StringPrototypeToString, CodeStubAssembler) { 1792 Node* receiver = Parameter(0); 1793 Node* context = Parameter(3); 1794 1795 Node* result = ToThisValue(context, receiver, PrimitiveType::kString, 1796 "String.prototype.toString"); 1797 Return(result); 1798 } 1799 1800 // ES6 section 21.1.3.27 String.prototype.trim () 1801 BUILTIN(StringPrototypeTrim) { 1802 HandleScope scope(isolate); 1803 TO_THIS_STRING(string, "String.prototype.trim"); 1804 return *String::Trim(string, String::kTrim); 1805 } 1806 1807 // Non-standard WebKit extension 1808 BUILTIN(StringPrototypeTrimLeft) { 1809 HandleScope scope(isolate); 1810 TO_THIS_STRING(string, "String.prototype.trimLeft"); 1811 return *String::Trim(string, String::kTrimLeft); 1812 } 1813 1814 // Non-standard WebKit extension 1815 BUILTIN(StringPrototypeTrimRight) { 1816 HandleScope scope(isolate); 1817 TO_THIS_STRING(string, "String.prototype.trimRight"); 1818 return *String::Trim(string, String::kTrimRight); 1819 } 1820 1821 // ES6 section 21.1.3.28 String.prototype.valueOf ( ) 1822 TF_BUILTIN(StringPrototypeValueOf, CodeStubAssembler) { 1823 Node* receiver = Parameter(0); 1824 Node* context = Parameter(3); 1825 1826 Node* result = ToThisValue(context, receiver, PrimitiveType::kString, 1827 "String.prototype.valueOf"); 1828 Return(result); 1829 } 1830 1831 TF_BUILTIN(StringPrototypeIterator, CodeStubAssembler) { 1832 Node* receiver = Parameter(0); 1833 Node* context = Parameter(3); 1834 1835 Node* string = 1836 ToThisString(context, receiver, "String.prototype[Symbol.iterator]"); 1837 1838 Node* native_context = LoadNativeContext(context); 1839 Node* map = 1840 LoadContextElement(native_context, Context::STRING_ITERATOR_MAP_INDEX); 1841 Node* iterator = Allocate(JSStringIterator::kSize); 1842 StoreMapNoWriteBarrier(iterator, map); 1843 StoreObjectFieldRoot(iterator, JSValue::kPropertiesOffset, 1844 Heap::kEmptyFixedArrayRootIndex); 1845 StoreObjectFieldRoot(iterator, JSObject::kElementsOffset, 1846 Heap::kEmptyFixedArrayRootIndex); 1847 StoreObjectFieldNoWriteBarrier(iterator, JSStringIterator::kStringOffset, 1848 string); 1849 Node* index = SmiConstant(Smi::kZero); 1850 StoreObjectFieldNoWriteBarrier(iterator, JSStringIterator::kNextIndexOffset, 1851 index); 1852 Return(iterator); 1853 } 1854 1855 // Return the |word32| codepoint at {index}. Supports SeqStrings and 1856 // ExternalStrings. 1857 compiler::Node* StringBuiltinsAssembler::LoadSurrogatePairAt( 1858 compiler::Node* string, compiler::Node* length, compiler::Node* index, 1859 UnicodeEncoding encoding) { 1860 Label handle_surrogate_pair(this), return_result(this); 1861 Variable var_result(this, MachineRepresentation::kWord32); 1862 Variable var_trail(this, MachineRepresentation::kWord32); 1863 var_result.Bind(StringCharCodeAt(string, index)); 1864 var_trail.Bind(Int32Constant(0)); 1865 1866 GotoIf(Word32NotEqual(Word32And(var_result.value(), Int32Constant(0xFC00)), 1867 Int32Constant(0xD800)), 1868 &return_result); 1869 Node* next_index = SmiAdd(index, SmiConstant(Smi::FromInt(1))); 1870 1871 GotoIfNot(SmiLessThan(next_index, length), &return_result); 1872 var_trail.Bind(StringCharCodeAt(string, next_index)); 1873 Branch(Word32Equal(Word32And(var_trail.value(), Int32Constant(0xFC00)), 1874 Int32Constant(0xDC00)), 1875 &handle_surrogate_pair, &return_result); 1876 1877 Bind(&handle_surrogate_pair); 1878 { 1879 Node* lead = var_result.value(); 1880 Node* trail = var_trail.value(); 1881 1882 // Check that this path is only taken if a surrogate pair is found 1883 CSA_SLOW_ASSERT(this, 1884 Uint32GreaterThanOrEqual(lead, Int32Constant(0xD800))); 1885 CSA_SLOW_ASSERT(this, Uint32LessThan(lead, Int32Constant(0xDC00))); 1886 CSA_SLOW_ASSERT(this, 1887 Uint32GreaterThanOrEqual(trail, Int32Constant(0xDC00))); 1888 CSA_SLOW_ASSERT(this, Uint32LessThan(trail, Int32Constant(0xE000))); 1889 1890 switch (encoding) { 1891 case UnicodeEncoding::UTF16: 1892 var_result.Bind(Word32Or( 1893 // Need to swap the order for big-endian platforms 1894 #if V8_TARGET_BIG_ENDIAN 1895 Word32Shl(lead, Int32Constant(16)), trail)); 1896 #else 1897 Word32Shl(trail, Int32Constant(16)), lead)); 1898 #endif 1899 break; 1900 1901 case UnicodeEncoding::UTF32: { 1902 // Convert UTF16 surrogate pair into |word32| code point, encoded as 1903 // UTF32. 1904 Node* surrogate_offset = 1905 Int32Constant(0x10000 - (0xD800 << 10) - 0xDC00); 1906 1907 // (lead << 10) + trail + SURROGATE_OFFSET 1908 var_result.Bind(Int32Add(WordShl(lead, Int32Constant(10)), 1909 Int32Add(trail, surrogate_offset))); 1910 break; 1911 } 1912 } 1913 Goto(&return_result); 1914 } 1915 1916 Bind(&return_result); 1917 return var_result.value(); 1918 } 1919 1920 TF_BUILTIN(StringIteratorPrototypeNext, StringBuiltinsAssembler) { 1921 Variable var_value(this, MachineRepresentation::kTagged); 1922 Variable var_done(this, MachineRepresentation::kTagged); 1923 1924 var_value.Bind(UndefinedConstant()); 1925 var_done.Bind(BooleanConstant(true)); 1926 1927 Label throw_bad_receiver(this), next_codepoint(this), return_result(this); 1928 1929 Node* iterator = Parameter(0); 1930 Node* context = Parameter(3); 1931 1932 GotoIf(TaggedIsSmi(iterator), &throw_bad_receiver); 1933 GotoIfNot(Word32Equal(LoadInstanceType(iterator), 1934 Int32Constant(JS_STRING_ITERATOR_TYPE)), 1935 &throw_bad_receiver); 1936 1937 Node* string = LoadObjectField(iterator, JSStringIterator::kStringOffset); 1938 Node* position = 1939 LoadObjectField(iterator, JSStringIterator::kNextIndexOffset); 1940 Node* length = LoadObjectField(string, String::kLengthOffset); 1941 1942 Branch(SmiLessThan(position, length), &next_codepoint, &return_result); 1943 1944 Bind(&next_codepoint); 1945 { 1946 UnicodeEncoding encoding = UnicodeEncoding::UTF16; 1947 Node* ch = LoadSurrogatePairAt(string, length, position, encoding); 1948 Node* value = StringFromCodePoint(ch, encoding); 1949 var_value.Bind(value); 1950 Node* length = LoadObjectField(value, String::kLengthOffset); 1951 StoreObjectFieldNoWriteBarrier(iterator, JSStringIterator::kNextIndexOffset, 1952 SmiAdd(position, length)); 1953 var_done.Bind(BooleanConstant(false)); 1954 Goto(&return_result); 1955 } 1956 1957 Bind(&return_result); 1958 { 1959 Node* native_context = LoadNativeContext(context); 1960 Node* map = 1961 LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX); 1962 Node* result = Allocate(JSIteratorResult::kSize); 1963 StoreMapNoWriteBarrier(result, map); 1964 StoreObjectFieldRoot(result, JSIteratorResult::kPropertiesOffset, 1965 Heap::kEmptyFixedArrayRootIndex); 1966 StoreObjectFieldRoot(result, JSIteratorResult::kElementsOffset, 1967 Heap::kEmptyFixedArrayRootIndex); 1968 StoreObjectFieldNoWriteBarrier(result, JSIteratorResult::kValueOffset, 1969 var_value.value()); 1970 StoreObjectFieldNoWriteBarrier(result, JSIteratorResult::kDoneOffset, 1971 var_done.value()); 1972 Return(result); 1973 } 1974 1975 Bind(&throw_bad_receiver); 1976 { 1977 // The {receiver} is not a valid JSGeneratorObject. 1978 CallRuntime(Runtime::kThrowIncompatibleMethodReceiver, context, 1979 HeapConstant(factory()->NewStringFromAsciiChecked( 1980 "String Iterator.prototype.next", TENURED)), 1981 iterator); 1982 Unreachable(); 1983 } 1984 } 1985 1986 namespace { 1987 1988 inline bool ToUpperOverflows(uc32 character) { 1989 // y with umlauts and the micro sign are the only characters that stop 1990 // fitting into one-byte when converting to uppercase. 1991 static const uc32 yuml_code = 0xff; 1992 static const uc32 micro_code = 0xb5; 1993 return (character == yuml_code || character == micro_code); 1994 } 1995 1996 template <class Converter> 1997 MUST_USE_RESULT static Object* ConvertCaseHelper( 1998 Isolate* isolate, String* string, SeqString* result, int result_length, 1999 unibrow::Mapping<Converter, 128>* mapping) { 2000 DisallowHeapAllocation no_gc; 2001 // We try this twice, once with the assumption that the result is no longer 2002 // than the input and, if that assumption breaks, again with the exact 2003 // length. This may not be pretty, but it is nicer than what was here before 2004 // and I hereby claim my vaffel-is. 2005 // 2006 // NOTE: This assumes that the upper/lower case of an ASCII 2007 // character is also ASCII. This is currently the case, but it 2008 // might break in the future if we implement more context and locale 2009 // dependent upper/lower conversions. 2010 bool has_changed_character = false; 2011 2012 // Convert all characters to upper case, assuming that they will fit 2013 // in the buffer 2014 StringCharacterStream stream(string); 2015 unibrow::uchar chars[Converter::kMaxWidth]; 2016 // We can assume that the string is not empty 2017 uc32 current = stream.GetNext(); 2018 bool ignore_overflow = Converter::kIsToLower || result->IsSeqTwoByteString(); 2019 for (int i = 0; i < result_length;) { 2020 bool has_next = stream.HasMore(); 2021 uc32 next = has_next ? stream.GetNext() : 0; 2022 int char_length = mapping->get(current, next, chars); 2023 if (char_length == 0) { 2024 // The case conversion of this character is the character itself. 2025 result->Set(i, current); 2026 i++; 2027 } else if (char_length == 1 && 2028 (ignore_overflow || !ToUpperOverflows(current))) { 2029 // Common case: converting the letter resulted in one character. 2030 DCHECK(static_cast<uc32>(chars[0]) != current); 2031 result->Set(i, chars[0]); 2032 has_changed_character = true; 2033 i++; 2034 } else if (result_length == string->length()) { 2035 bool overflows = ToUpperOverflows(current); 2036 // We've assumed that the result would be as long as the 2037 // input but here is a character that converts to several 2038 // characters. No matter, we calculate the exact length 2039 // of the result and try the whole thing again. 2040 // 2041 // Note that this leaves room for optimization. We could just 2042 // memcpy what we already have to the result string. Also, 2043 // the result string is the last object allocated we could 2044 // "realloc" it and probably, in the vast majority of cases, 2045 // extend the existing string to be able to hold the full 2046 // result. 2047 int next_length = 0; 2048 if (has_next) { 2049 next_length = mapping->get(next, 0, chars); 2050 if (next_length == 0) next_length = 1; 2051 } 2052 int current_length = i + char_length + next_length; 2053 while (stream.HasMore()) { 2054 current = stream.GetNext(); 2055 overflows |= ToUpperOverflows(current); 2056 // NOTE: we use 0 as the next character here because, while 2057 // the next character may affect what a character converts to, 2058 // it does not in any case affect the length of what it convert 2059 // to. 2060 int char_length = mapping->get(current, 0, chars); 2061 if (char_length == 0) char_length = 1; 2062 current_length += char_length; 2063 if (current_length > String::kMaxLength) { 2064 AllowHeapAllocation allocate_error_and_return; 2065 THROW_NEW_ERROR_RETURN_FAILURE(isolate, 2066 NewInvalidStringLengthError()); 2067 } 2068 } 2069 // Try again with the real length. Return signed if we need 2070 // to allocate a two-byte string for to uppercase. 2071 return (overflows && !ignore_overflow) ? Smi::FromInt(-current_length) 2072 : Smi::FromInt(current_length); 2073 } else { 2074 for (int j = 0; j < char_length; j++) { 2075 result->Set(i, chars[j]); 2076 i++; 2077 } 2078 has_changed_character = true; 2079 } 2080 current = next; 2081 } 2082 if (has_changed_character) { 2083 return result; 2084 } else { 2085 // If we didn't actually change anything in doing the conversion 2086 // we simple return the result and let the converted string 2087 // become garbage; there is no reason to keep two identical strings 2088 // alive. 2089 return string; 2090 } 2091 } 2092 2093 template <class Converter> 2094 MUST_USE_RESULT static Object* ConvertCase( 2095 Handle<String> s, Isolate* isolate, 2096 unibrow::Mapping<Converter, 128>* mapping) { 2097 s = String::Flatten(s); 2098 int length = s->length(); 2099 // Assume that the string is not empty; we need this assumption later 2100 if (length == 0) return *s; 2101 2102 // Simpler handling of ASCII strings. 2103 // 2104 // NOTE: This assumes that the upper/lower case of an ASCII 2105 // character is also ASCII. This is currently the case, but it 2106 // might break in the future if we implement more context and locale 2107 // dependent upper/lower conversions. 2108 if (s->IsOneByteRepresentationUnderneath()) { 2109 // Same length as input. 2110 Handle<SeqOneByteString> result = 2111 isolate->factory()->NewRawOneByteString(length).ToHandleChecked(); 2112 DisallowHeapAllocation no_gc; 2113 String::FlatContent flat_content = s->GetFlatContent(); 2114 DCHECK(flat_content.IsFlat()); 2115 bool has_changed_character = false; 2116 int index_to_first_unprocessed = FastAsciiConvert<Converter::kIsToLower>( 2117 reinterpret_cast<char*>(result->GetChars()), 2118 reinterpret_cast<const char*>(flat_content.ToOneByteVector().start()), 2119 length, &has_changed_character); 2120 // If not ASCII, we discard the result and take the 2 byte path. 2121 if (index_to_first_unprocessed == length) 2122 return has_changed_character ? *result : *s; 2123 } 2124 2125 Handle<SeqString> result; // Same length as input. 2126 if (s->IsOneByteRepresentation()) { 2127 result = isolate->factory()->NewRawOneByteString(length).ToHandleChecked(); 2128 } else { 2129 result = isolate->factory()->NewRawTwoByteString(length).ToHandleChecked(); 2130 } 2131 2132 Object* answer = ConvertCaseHelper(isolate, *s, *result, length, mapping); 2133 if (answer->IsException(isolate) || answer->IsString()) return answer; 2134 2135 DCHECK(answer->IsSmi()); 2136 length = Smi::cast(answer)->value(); 2137 if (s->IsOneByteRepresentation() && length > 0) { 2138 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( 2139 isolate, result, isolate->factory()->NewRawOneByteString(length)); 2140 } else { 2141 if (length < 0) length = -length; 2142 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( 2143 isolate, result, isolate->factory()->NewRawTwoByteString(length)); 2144 } 2145 return ConvertCaseHelper(isolate, *s, *result, length, mapping); 2146 } 2147 2148 } // namespace 2149 2150 BUILTIN(StringPrototypeToLocaleLowerCase) { 2151 HandleScope scope(isolate); 2152 TO_THIS_STRING(string, "String.prototype.toLocaleLowerCase"); 2153 return ConvertCase(string, isolate, 2154 isolate->runtime_state()->to_lower_mapping()); 2155 } 2156 2157 BUILTIN(StringPrototypeToLocaleUpperCase) { 2158 HandleScope scope(isolate); 2159 TO_THIS_STRING(string, "String.prototype.toLocaleUpperCase"); 2160 return ConvertCase(string, isolate, 2161 isolate->runtime_state()->to_upper_mapping()); 2162 } 2163 2164 BUILTIN(StringPrototypeToLowerCase) { 2165 HandleScope scope(isolate); 2166 TO_THIS_STRING(string, "String.prototype.toLowerCase"); 2167 return ConvertCase(string, isolate, 2168 isolate->runtime_state()->to_lower_mapping()); 2169 } 2170 2171 BUILTIN(StringPrototypeToUpperCase) { 2172 HandleScope scope(isolate); 2173 TO_THIS_STRING(string, "String.prototype.toUpperCase"); 2174 return ConvertCase(string, isolate, 2175 isolate->runtime_state()->to_upper_mapping()); 2176 } 2177 2178 } // namespace internal 2179 } // namespace v8 2180