| OLD | NEW |
| 1 // Copyright 2016 the V8 project authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "src/builtins/builtins-regexp.h" | |
| 6 #include "src/builtins/builtins-utils.h" | 5 #include "src/builtins/builtins-utils.h" |
| 7 #include "src/builtins/builtins.h" | 6 #include "src/builtins/builtins.h" |
| 8 #include "src/code-factory.h" | |
| 9 #include "src/code-stub-assembler.h" | |
| 10 #include "src/conversions.h" | 7 #include "src/conversions.h" |
| 11 #include "src/counters.h" | 8 #include "src/counters.h" |
| 12 #include "src/objects-inl.h" | 9 #include "src/objects-inl.h" |
| 13 #include "src/regexp/regexp-utils.h" | 10 #include "src/regexp/regexp-utils.h" |
| 14 #include "src/string-case.h" | 11 #include "src/string-case.h" |
| 15 #include "src/unicode-inl.h" | 12 #include "src/unicode-inl.h" |
| 16 #include "src/unicode.h" | 13 #include "src/unicode.h" |
| 17 | 14 |
| 18 namespace v8 { | 15 namespace v8 { |
| 19 namespace internal { | 16 namespace internal { |
| 20 | 17 |
| 21 typedef CodeStubAssembler::RelationalComparisonMode RelationalComparisonMode; | |
| 22 | |
| 23 class StringBuiltinsAssembler : public CodeStubAssembler { | |
| 24 public: | |
| 25 explicit StringBuiltinsAssembler(compiler::CodeAssemblerState* state) | |
| 26 : CodeStubAssembler(state) {} | |
| 27 | |
| 28 protected: | |
| 29 Node* DirectStringData(Node* string, Node* string_instance_type) { | |
| 30 // Compute the effective offset of the first character. | |
| 31 Variable var_data(this, MachineType::PointerRepresentation()); | |
| 32 Label if_sequential(this), if_external(this), if_join(this); | |
| 33 Branch(Word32Equal(Word32And(string_instance_type, | |
| 34 Int32Constant(kStringRepresentationMask)), | |
| 35 Int32Constant(kSeqStringTag)), | |
| 36 &if_sequential, &if_external); | |
| 37 | |
| 38 Bind(&if_sequential); | |
| 39 { | |
| 40 var_data.Bind(IntPtrAdd( | |
| 41 IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag), | |
| 42 BitcastTaggedToWord(string))); | |
| 43 Goto(&if_join); | |
| 44 } | |
| 45 | |
| 46 Bind(&if_external); | |
| 47 { | |
| 48 // This is only valid for ExternalStrings where the resource data | |
| 49 // pointer is cached (i.e. no short external strings). | |
| 50 CSA_ASSERT(this, Word32NotEqual( | |
| 51 Word32And(string_instance_type, | |
| 52 Int32Constant(kShortExternalStringMask)), | |
| 53 Int32Constant(kShortExternalStringTag))); | |
| 54 var_data.Bind(LoadObjectField(string, ExternalString::kResourceDataOffset, | |
| 55 MachineType::Pointer())); | |
| 56 Goto(&if_join); | |
| 57 } | |
| 58 | |
| 59 Bind(&if_join); | |
| 60 return var_data.value(); | |
| 61 } | |
| 62 | |
| 63 Node* LoadOneByteChar(Node* string, Node* index) { | |
| 64 return Load(MachineType::Uint8(), string, OneByteCharOffset(index)); | |
| 65 } | |
| 66 | |
| 67 Node* OneByteCharAddress(Node* string, Node* index) { | |
| 68 Node* offset = OneByteCharOffset(index); | |
| 69 return IntPtrAdd(string, offset); | |
| 70 } | |
| 71 | |
| 72 Node* OneByteCharOffset(Node* index) { | |
| 73 return CharOffset(String::ONE_BYTE_ENCODING, index); | |
| 74 } | |
| 75 | |
| 76 Node* CharOffset(String::Encoding encoding, Node* index) { | |
| 77 const int header = SeqOneByteString::kHeaderSize - kHeapObjectTag; | |
| 78 Node* offset = index; | |
| 79 if (encoding == String::TWO_BYTE_ENCODING) { | |
| 80 offset = IntPtrAdd(offset, offset); | |
| 81 } | |
| 82 offset = IntPtrAdd(offset, IntPtrConstant(header)); | |
| 83 return offset; | |
| 84 } | |
| 85 | |
| 86 void DispatchOnStringInstanceType(Node* const instance_type, | |
| 87 Label* if_onebyte_sequential, | |
| 88 Label* if_onebyte_external, | |
| 89 Label* if_otherwise) { | |
| 90 const int kMask = kStringRepresentationMask | kStringEncodingMask; | |
| 91 Node* const encoding_and_representation = | |
| 92 Word32And(instance_type, Int32Constant(kMask)); | |
| 93 | |
| 94 int32_t values[] = { | |
| 95 kOneByteStringTag | kSeqStringTag, | |
| 96 kOneByteStringTag | kExternalStringTag, | |
| 97 }; | |
| 98 Label* labels[] = { | |
| 99 if_onebyte_sequential, if_onebyte_external, | |
| 100 }; | |
| 101 STATIC_ASSERT(arraysize(values) == arraysize(labels)); | |
| 102 | |
| 103 Switch(encoding_and_representation, if_otherwise, values, labels, | |
| 104 arraysize(values)); | |
| 105 } | |
| 106 | |
| 107 void GenerateStringEqual(); | |
| 108 void GenerateStringRelationalComparison(RelationalComparisonMode mode); | |
| 109 | |
| 110 Node* ToSmiBetweenZeroAnd(Node* context, Node* value, Node* limit); | |
| 111 | |
| 112 Node* LoadSurrogatePairAt(Node* string, Node* length, Node* index, | |
| 113 UnicodeEncoding encoding); | |
| 114 | |
| 115 void StringIndexOf(Node* receiver, Node* instance_type, Node* search_string, | |
| 116 Node* search_string_instance_type, Node* position, | |
| 117 std::function<void(Node*)> f_return); | |
| 118 | |
| 119 Node* IsNullOrUndefined(Node* const value); | |
| 120 void RequireObjectCoercible(Node* const context, Node* const value, | |
| 121 const char* method_name); | |
| 122 | |
| 123 Node* SmiIsNegative(Node* const value) { | |
| 124 return SmiLessThan(value, SmiConstant(0)); | |
| 125 } | |
| 126 | |
| 127 // Implements boilerplate logic for {match, split, replace, search} of the | |
| 128 // form: | |
| 129 // | |
| 130 // if (!IS_NULL_OR_UNDEFINED(object)) { | |
| 131 // var maybe_function = object[symbol]; | |
| 132 // if (!IS_UNDEFINED(maybe_function)) { | |
| 133 // return %_Call(maybe_function, ...); | |
| 134 // } | |
| 135 // } | |
| 136 // | |
| 137 // Contains fast paths for Smi and RegExp objects. | |
| 138 typedef std::function<Node*()> NodeFunction0; | |
| 139 typedef std::function<Node*(Node* fn)> NodeFunction1; | |
| 140 void MaybeCallFunctionAtSymbol(Node* const context, Node* const object, | |
| 141 Handle<Symbol> symbol, | |
| 142 const NodeFunction0& regexp_call, | |
| 143 const NodeFunction1& generic_call); | |
| 144 }; | |
| 145 | |
| 146 void StringBuiltinsAssembler::GenerateStringEqual() { | |
| 147 // Here's pseudo-code for the algorithm below: | |
| 148 // | |
| 149 // if (lhs == rhs) return true; | |
| 150 // if (lhs->length() != rhs->length()) return false; | |
| 151 // if (lhs->IsInternalizedString() && rhs->IsInternalizedString()) { | |
| 152 // return false; | |
| 153 // } | |
| 154 // if (lhs->IsSeqOneByteString() && rhs->IsSeqOneByteString()) { | |
| 155 // for (i = 0; i != lhs->length(); ++i) { | |
| 156 // if (lhs[i] != rhs[i]) return false; | |
| 157 // } | |
| 158 // return true; | |
| 159 // } | |
| 160 // if (lhs and/or rhs are indirect strings) { | |
| 161 // unwrap them and restart from the beginning; | |
| 162 // } | |
| 163 // return %StringEqual(lhs, rhs); | |
| 164 | |
| 165 Variable var_left(this, MachineRepresentation::kTagged); | |
| 166 Variable var_right(this, MachineRepresentation::kTagged); | |
| 167 var_left.Bind(Parameter(0)); | |
| 168 var_right.Bind(Parameter(1)); | |
| 169 Node* context = Parameter(2); | |
| 170 | |
| 171 Variable* input_vars[2] = {&var_left, &var_right}; | |
| 172 Label if_equal(this), if_notequal(this), restart(this, 2, input_vars); | |
| 173 Goto(&restart); | |
| 174 Bind(&restart); | |
| 175 Node* lhs = var_left.value(); | |
| 176 Node* rhs = var_right.value(); | |
| 177 | |
| 178 // Fast check to see if {lhs} and {rhs} refer to the same String object. | |
| 179 GotoIf(WordEqual(lhs, rhs), &if_equal); | |
| 180 | |
| 181 // Load the length of {lhs} and {rhs}. | |
| 182 Node* lhs_length = LoadStringLength(lhs); | |
| 183 Node* rhs_length = LoadStringLength(rhs); | |
| 184 | |
| 185 // Strings with different lengths cannot be equal. | |
| 186 GotoIf(WordNotEqual(lhs_length, rhs_length), &if_notequal); | |
| 187 | |
| 188 // Load instance types of {lhs} and {rhs}. | |
| 189 Node* lhs_instance_type = LoadInstanceType(lhs); | |
| 190 Node* rhs_instance_type = LoadInstanceType(rhs); | |
| 191 | |
| 192 // Combine the instance types into a single 16-bit value, so we can check | |
| 193 // both of them at once. | |
| 194 Node* both_instance_types = Word32Or( | |
| 195 lhs_instance_type, Word32Shl(rhs_instance_type, Int32Constant(8))); | |
| 196 | |
| 197 // Check if both {lhs} and {rhs} are internalized. Since we already know | |
| 198 // that they're not the same object, they're not equal in that case. | |
| 199 int const kBothInternalizedMask = | |
| 200 kIsNotInternalizedMask | (kIsNotInternalizedMask << 8); | |
| 201 int const kBothInternalizedTag = kInternalizedTag | (kInternalizedTag << 8); | |
| 202 GotoIf(Word32Equal(Word32And(both_instance_types, | |
| 203 Int32Constant(kBothInternalizedMask)), | |
| 204 Int32Constant(kBothInternalizedTag)), | |
| 205 &if_notequal); | |
| 206 | |
| 207 // Check that both {lhs} and {rhs} are flat one-byte strings, and that | |
| 208 // in case of ExternalStrings the data pointer is cached.. | |
| 209 STATIC_ASSERT(kShortExternalStringTag != 0); | |
| 210 int const kBothDirectOneByteStringMask = | |
| 211 kStringEncodingMask | kIsIndirectStringMask | kShortExternalStringMask | | |
| 212 ((kStringEncodingMask | kIsIndirectStringMask | kShortExternalStringMask) | |
| 213 << 8); | |
| 214 int const kBothDirectOneByteStringTag = | |
| 215 kOneByteStringTag | (kOneByteStringTag << 8); | |
| 216 Label if_bothdirectonebytestrings(this), if_notbothdirectonebytestrings(this); | |
| 217 Branch(Word32Equal(Word32And(both_instance_types, | |
| 218 Int32Constant(kBothDirectOneByteStringMask)), | |
| 219 Int32Constant(kBothDirectOneByteStringTag)), | |
| 220 &if_bothdirectonebytestrings, &if_notbothdirectonebytestrings); | |
| 221 | |
| 222 Bind(&if_bothdirectonebytestrings); | |
| 223 { | |
| 224 // Compute the effective offset of the first character. | |
| 225 Node* lhs_data = DirectStringData(lhs, lhs_instance_type); | |
| 226 Node* rhs_data = DirectStringData(rhs, rhs_instance_type); | |
| 227 | |
| 228 // Compute the first offset after the string from the length. | |
| 229 Node* length = SmiUntag(lhs_length); | |
| 230 | |
| 231 // Loop over the {lhs} and {rhs} strings to see if they are equal. | |
| 232 Variable var_offset(this, MachineType::PointerRepresentation()); | |
| 233 Label loop(this, &var_offset); | |
| 234 var_offset.Bind(IntPtrConstant(0)); | |
| 235 Goto(&loop); | |
| 236 Bind(&loop); | |
| 237 { | |
| 238 // If {offset} equals {end}, no difference was found, so the | |
| 239 // strings are equal. | |
| 240 Node* offset = var_offset.value(); | |
| 241 GotoIf(WordEqual(offset, length), &if_equal); | |
| 242 | |
| 243 // Load the next characters from {lhs} and {rhs}. | |
| 244 Node* lhs_value = Load(MachineType::Uint8(), lhs_data, offset); | |
| 245 Node* rhs_value = Load(MachineType::Uint8(), rhs_data, offset); | |
| 246 | |
| 247 // Check if the characters match. | |
| 248 GotoIf(Word32NotEqual(lhs_value, rhs_value), &if_notequal); | |
| 249 | |
| 250 // Advance to next character. | |
| 251 var_offset.Bind(IntPtrAdd(offset, IntPtrConstant(1))); | |
| 252 Goto(&loop); | |
| 253 } | |
| 254 } | |
| 255 | |
| 256 Bind(&if_notbothdirectonebytestrings); | |
| 257 { | |
| 258 // Try to unwrap indirect strings, restart the above attempt on success. | |
| 259 MaybeDerefIndirectStrings(&var_left, lhs_instance_type, &var_right, | |
| 260 rhs_instance_type, &restart); | |
| 261 // TODO(bmeurer): Add support for two byte string equality checks. | |
| 262 | |
| 263 TailCallRuntime(Runtime::kStringEqual, context, lhs, rhs); | |
| 264 } | |
| 265 | |
| 266 Bind(&if_equal); | |
| 267 Return(TrueConstant()); | |
| 268 | |
| 269 Bind(&if_notequal); | |
| 270 Return(FalseConstant()); | |
| 271 } | |
| 272 | |
| 273 void StringBuiltinsAssembler::GenerateStringRelationalComparison( | |
| 274 RelationalComparisonMode mode) { | |
| 275 Variable var_left(this, MachineRepresentation::kTagged); | |
| 276 Variable var_right(this, MachineRepresentation::kTagged); | |
| 277 var_left.Bind(Parameter(0)); | |
| 278 var_right.Bind(Parameter(1)); | |
| 279 Node* context = Parameter(2); | |
| 280 | |
| 281 Variable* input_vars[2] = {&var_left, &var_right}; | |
| 282 Label if_less(this), if_equal(this), if_greater(this); | |
| 283 Label restart(this, 2, input_vars); | |
| 284 Goto(&restart); | |
| 285 Bind(&restart); | |
| 286 | |
| 287 Node* lhs = var_left.value(); | |
| 288 Node* rhs = var_right.value(); | |
| 289 // Fast check to see if {lhs} and {rhs} refer to the same String object. | |
| 290 GotoIf(WordEqual(lhs, rhs), &if_equal); | |
| 291 | |
| 292 // Load instance types of {lhs} and {rhs}. | |
| 293 Node* lhs_instance_type = LoadInstanceType(lhs); | |
| 294 Node* rhs_instance_type = LoadInstanceType(rhs); | |
| 295 | |
| 296 // Combine the instance types into a single 16-bit value, so we can check | |
| 297 // both of them at once. | |
| 298 Node* both_instance_types = Word32Or( | |
| 299 lhs_instance_type, Word32Shl(rhs_instance_type, Int32Constant(8))); | |
| 300 | |
| 301 // Check that both {lhs} and {rhs} are flat one-byte strings. | |
| 302 int const kBothSeqOneByteStringMask = | |
| 303 kStringEncodingMask | kStringRepresentationMask | | |
| 304 ((kStringEncodingMask | kStringRepresentationMask) << 8); | |
| 305 int const kBothSeqOneByteStringTag = | |
| 306 kOneByteStringTag | kSeqStringTag | | |
| 307 ((kOneByteStringTag | kSeqStringTag) << 8); | |
| 308 Label if_bothonebyteseqstrings(this), if_notbothonebyteseqstrings(this); | |
| 309 Branch(Word32Equal(Word32And(both_instance_types, | |
| 310 Int32Constant(kBothSeqOneByteStringMask)), | |
| 311 Int32Constant(kBothSeqOneByteStringTag)), | |
| 312 &if_bothonebyteseqstrings, &if_notbothonebyteseqstrings); | |
| 313 | |
| 314 Bind(&if_bothonebyteseqstrings); | |
| 315 { | |
| 316 // Load the length of {lhs} and {rhs}. | |
| 317 Node* lhs_length = LoadStringLength(lhs); | |
| 318 Node* rhs_length = LoadStringLength(rhs); | |
| 319 | |
| 320 // Determine the minimum length. | |
| 321 Node* length = SmiMin(lhs_length, rhs_length); | |
| 322 | |
| 323 // Compute the effective offset of the first character. | |
| 324 Node* begin = | |
| 325 IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag); | |
| 326 | |
| 327 // Compute the first offset after the string from the length. | |
| 328 Node* end = IntPtrAdd(begin, SmiUntag(length)); | |
| 329 | |
| 330 // Loop over the {lhs} and {rhs} strings to see if they are equal. | |
| 331 Variable var_offset(this, MachineType::PointerRepresentation()); | |
| 332 Label loop(this, &var_offset); | |
| 333 var_offset.Bind(begin); | |
| 334 Goto(&loop); | |
| 335 Bind(&loop); | |
| 336 { | |
| 337 // Check if {offset} equals {end}. | |
| 338 Node* offset = var_offset.value(); | |
| 339 Label if_done(this), if_notdone(this); | |
| 340 Branch(WordEqual(offset, end), &if_done, &if_notdone); | |
| 341 | |
| 342 Bind(&if_notdone); | |
| 343 { | |
| 344 // Load the next characters from {lhs} and {rhs}. | |
| 345 Node* lhs_value = Load(MachineType::Uint8(), lhs, offset); | |
| 346 Node* rhs_value = Load(MachineType::Uint8(), rhs, offset); | |
| 347 | |
| 348 // Check if the characters match. | |
| 349 Label if_valueissame(this), if_valueisnotsame(this); | |
| 350 Branch(Word32Equal(lhs_value, rhs_value), &if_valueissame, | |
| 351 &if_valueisnotsame); | |
| 352 | |
| 353 Bind(&if_valueissame); | |
| 354 { | |
| 355 // Advance to next character. | |
| 356 var_offset.Bind(IntPtrAdd(offset, IntPtrConstant(1))); | |
| 357 } | |
| 358 Goto(&loop); | |
| 359 | |
| 360 Bind(&if_valueisnotsame); | |
| 361 Branch(Uint32LessThan(lhs_value, rhs_value), &if_less, &if_greater); | |
| 362 } | |
| 363 | |
| 364 Bind(&if_done); | |
| 365 { | |
| 366 // All characters up to the min length are equal, decide based on | |
| 367 // string length. | |
| 368 GotoIf(SmiEqual(lhs_length, rhs_length), &if_equal); | |
| 369 BranchIfSmiLessThan(lhs_length, rhs_length, &if_less, &if_greater); | |
| 370 } | |
| 371 } | |
| 372 } | |
| 373 | |
| 374 Bind(&if_notbothonebyteseqstrings); | |
| 375 { | |
| 376 // Try to unwrap indirect strings, restart the above attempt on success. | |
| 377 MaybeDerefIndirectStrings(&var_left, lhs_instance_type, &var_right, | |
| 378 rhs_instance_type, &restart); | |
| 379 // TODO(bmeurer): Add support for two byte string relational comparisons. | |
| 380 switch (mode) { | |
| 381 case RelationalComparisonMode::kLessThan: | |
| 382 TailCallRuntime(Runtime::kStringLessThan, context, lhs, rhs); | |
| 383 break; | |
| 384 case RelationalComparisonMode::kLessThanOrEqual: | |
| 385 TailCallRuntime(Runtime::kStringLessThanOrEqual, context, lhs, rhs); | |
| 386 break; | |
| 387 case RelationalComparisonMode::kGreaterThan: | |
| 388 TailCallRuntime(Runtime::kStringGreaterThan, context, lhs, rhs); | |
| 389 break; | |
| 390 case RelationalComparisonMode::kGreaterThanOrEqual: | |
| 391 TailCallRuntime(Runtime::kStringGreaterThanOrEqual, context, lhs, | |
| 392 rhs); | |
| 393 break; | |
| 394 } | |
| 395 } | |
| 396 | |
| 397 Bind(&if_less); | |
| 398 switch (mode) { | |
| 399 case RelationalComparisonMode::kLessThan: | |
| 400 case RelationalComparisonMode::kLessThanOrEqual: | |
| 401 Return(BooleanConstant(true)); | |
| 402 break; | |
| 403 | |
| 404 case RelationalComparisonMode::kGreaterThan: | |
| 405 case RelationalComparisonMode::kGreaterThanOrEqual: | |
| 406 Return(BooleanConstant(false)); | |
| 407 break; | |
| 408 } | |
| 409 | |
| 410 Bind(&if_equal); | |
| 411 switch (mode) { | |
| 412 case RelationalComparisonMode::kLessThan: | |
| 413 case RelationalComparisonMode::kGreaterThan: | |
| 414 Return(BooleanConstant(false)); | |
| 415 break; | |
| 416 | |
| 417 case RelationalComparisonMode::kLessThanOrEqual: | |
| 418 case RelationalComparisonMode::kGreaterThanOrEqual: | |
| 419 Return(BooleanConstant(true)); | |
| 420 break; | |
| 421 } | |
| 422 | |
| 423 Bind(&if_greater); | |
| 424 switch (mode) { | |
| 425 case RelationalComparisonMode::kLessThan: | |
| 426 case RelationalComparisonMode::kLessThanOrEqual: | |
| 427 Return(BooleanConstant(false)); | |
| 428 break; | |
| 429 | |
| 430 case RelationalComparisonMode::kGreaterThan: | |
| 431 case RelationalComparisonMode::kGreaterThanOrEqual: | |
| 432 Return(BooleanConstant(true)); | |
| 433 break; | |
| 434 } | |
| 435 } | |
| 436 | |
| 437 TF_BUILTIN(StringEqual, StringBuiltinsAssembler) { GenerateStringEqual(); } | |
| 438 | |
| 439 TF_BUILTIN(StringLessThan, StringBuiltinsAssembler) { | |
| 440 GenerateStringRelationalComparison(RelationalComparisonMode::kLessThan); | |
| 441 } | |
| 442 | |
| 443 TF_BUILTIN(StringLessThanOrEqual, StringBuiltinsAssembler) { | |
| 444 GenerateStringRelationalComparison( | |
| 445 RelationalComparisonMode::kLessThanOrEqual); | |
| 446 } | |
| 447 | |
| 448 TF_BUILTIN(StringGreaterThan, StringBuiltinsAssembler) { | |
| 449 GenerateStringRelationalComparison(RelationalComparisonMode::kGreaterThan); | |
| 450 } | |
| 451 | |
| 452 TF_BUILTIN(StringGreaterThanOrEqual, StringBuiltinsAssembler) { | |
| 453 GenerateStringRelationalComparison( | |
| 454 RelationalComparisonMode::kGreaterThanOrEqual); | |
| 455 } | |
| 456 | |
| 457 TF_BUILTIN(StringCharAt, CodeStubAssembler) { | |
| 458 Node* receiver = Parameter(0); | |
| 459 Node* position = Parameter(1); | |
| 460 | |
| 461 // Load the character code at the {position} from the {receiver}. | |
| 462 Node* code = StringCharCodeAt(receiver, position, INTPTR_PARAMETERS); | |
| 463 | |
| 464 // And return the single character string with only that {code} | |
| 465 Node* result = StringFromCharCode(code); | |
| 466 Return(result); | |
| 467 } | |
| 468 | |
| 469 TF_BUILTIN(StringCharCodeAt, CodeStubAssembler) { | |
| 470 Node* receiver = Parameter(0); | |
| 471 Node* position = Parameter(1); | |
| 472 | |
| 473 // Load the character code at the {position} from the {receiver}. | |
| 474 Node* code = StringCharCodeAt(receiver, position, INTPTR_PARAMETERS); | |
| 475 | |
| 476 // And return it as TaggedSigned value. | |
| 477 // TODO(turbofan): Allow builtins to return values untagged. | |
| 478 Node* result = SmiFromWord32(code); | |
| 479 Return(result); | |
| 480 } | |
| 481 | |
| 482 // ----------------------------------------------------------------------------- | |
| 483 // ES6 section 21.1 String Objects | |
| 484 | |
| 485 // ES6 section 21.1.2.1 String.fromCharCode ( ...codeUnits ) | |
| 486 TF_BUILTIN(StringFromCharCode, CodeStubAssembler) { | |
| 487 Node* argc = Parameter(BuiltinDescriptor::kArgumentsCount); | |
| 488 Node* context = Parameter(BuiltinDescriptor::kContext); | |
| 489 | |
| 490 CodeStubArguments arguments(this, ChangeInt32ToIntPtr(argc)); | |
| 491 // From now on use word-size argc value. | |
| 492 argc = arguments.GetLength(); | |
| 493 | |
| 494 // Check if we have exactly one argument (plus the implicit receiver), i.e. | |
| 495 // if the parent frame is not an arguments adaptor frame. | |
| 496 Label if_oneargument(this), if_notoneargument(this); | |
| 497 Branch(WordEqual(argc, IntPtrConstant(1)), &if_oneargument, | |
| 498 &if_notoneargument); | |
| 499 | |
| 500 Bind(&if_oneargument); | |
| 501 { | |
| 502 // Single argument case, perform fast single character string cache lookup | |
| 503 // for one-byte code units, or fall back to creating a single character | |
| 504 // string on the fly otherwise. | |
| 505 Node* code = arguments.AtIndex(0); | |
| 506 Node* code32 = TruncateTaggedToWord32(context, code); | |
| 507 Node* code16 = Word32And(code32, Int32Constant(String::kMaxUtf16CodeUnit)); | |
| 508 Node* result = StringFromCharCode(code16); | |
| 509 arguments.PopAndReturn(result); | |
| 510 } | |
| 511 | |
| 512 Node* code16 = nullptr; | |
| 513 Bind(&if_notoneargument); | |
| 514 { | |
| 515 Label two_byte(this); | |
| 516 // Assume that the resulting string contains only one-byte characters. | |
| 517 Node* one_byte_result = AllocateSeqOneByteString(context, argc); | |
| 518 | |
| 519 Variable max_index(this, MachineType::PointerRepresentation()); | |
| 520 max_index.Bind(IntPtrConstant(0)); | |
| 521 | |
| 522 // Iterate over the incoming arguments, converting them to 8-bit character | |
| 523 // codes. Stop if any of the conversions generates a code that doesn't fit | |
| 524 // in 8 bits. | |
| 525 CodeStubAssembler::VariableList vars({&max_index}, zone()); | |
| 526 arguments.ForEach(vars, [this, context, &two_byte, &max_index, &code16, | |
| 527 one_byte_result](Node* arg) { | |
| 528 Node* code32 = TruncateTaggedToWord32(context, arg); | |
| 529 code16 = Word32And(code32, Int32Constant(String::kMaxUtf16CodeUnit)); | |
| 530 | |
| 531 GotoIf( | |
| 532 Int32GreaterThan(code16, Int32Constant(String::kMaxOneByteCharCode)), | |
| 533 &two_byte); | |
| 534 | |
| 535 // The {code16} fits into the SeqOneByteString {one_byte_result}. | |
| 536 Node* offset = ElementOffsetFromIndex( | |
| 537 max_index.value(), UINT8_ELEMENTS, | |
| 538 CodeStubAssembler::INTPTR_PARAMETERS, | |
| 539 SeqOneByteString::kHeaderSize - kHeapObjectTag); | |
| 540 StoreNoWriteBarrier(MachineRepresentation::kWord8, one_byte_result, | |
| 541 offset, code16); | |
| 542 max_index.Bind(IntPtrAdd(max_index.value(), IntPtrConstant(1))); | |
| 543 }); | |
| 544 arguments.PopAndReturn(one_byte_result); | |
| 545 | |
| 546 Bind(&two_byte); | |
| 547 | |
| 548 // At least one of the characters in the string requires a 16-bit | |
| 549 // representation. Allocate a SeqTwoByteString to hold the resulting | |
| 550 // string. | |
| 551 Node* two_byte_result = AllocateSeqTwoByteString(context, argc); | |
| 552 | |
| 553 // Copy the characters that have already been put in the 8-bit string into | |
| 554 // their corresponding positions in the new 16-bit string. | |
| 555 Node* zero = IntPtrConstant(0); | |
| 556 CopyStringCharacters(one_byte_result, two_byte_result, zero, zero, | |
| 557 max_index.value(), String::ONE_BYTE_ENCODING, | |
| 558 String::TWO_BYTE_ENCODING, | |
| 559 CodeStubAssembler::INTPTR_PARAMETERS); | |
| 560 | |
| 561 // Write the character that caused the 8-bit to 16-bit fault. | |
| 562 Node* max_index_offset = | |
| 563 ElementOffsetFromIndex(max_index.value(), UINT16_ELEMENTS, | |
| 564 CodeStubAssembler::INTPTR_PARAMETERS, | |
| 565 SeqTwoByteString::kHeaderSize - kHeapObjectTag); | |
| 566 StoreNoWriteBarrier(MachineRepresentation::kWord16, two_byte_result, | |
| 567 max_index_offset, code16); | |
| 568 max_index.Bind(IntPtrAdd(max_index.value(), IntPtrConstant(1))); | |
| 569 | |
| 570 // Resume copying the passed-in arguments from the same place where the | |
| 571 // 8-bit copy stopped, but this time copying over all of the characters | |
| 572 // using a 16-bit representation. | |
| 573 arguments.ForEach( | |
| 574 vars, | |
| 575 [this, context, two_byte_result, &max_index](Node* arg) { | |
| 576 Node* code32 = TruncateTaggedToWord32(context, arg); | |
| 577 Node* code16 = | |
| 578 Word32And(code32, Int32Constant(String::kMaxUtf16CodeUnit)); | |
| 579 | |
| 580 Node* offset = ElementOffsetFromIndex( | |
| 581 max_index.value(), UINT16_ELEMENTS, | |
| 582 CodeStubAssembler::INTPTR_PARAMETERS, | |
| 583 SeqTwoByteString::kHeaderSize - kHeapObjectTag); | |
| 584 StoreNoWriteBarrier(MachineRepresentation::kWord16, two_byte_result, | |
| 585 offset, code16); | |
| 586 max_index.Bind(IntPtrAdd(max_index.value(), IntPtrConstant(1))); | |
| 587 }, | |
| 588 max_index.value()); | |
| 589 | |
| 590 arguments.PopAndReturn(two_byte_result); | |
| 591 } | |
| 592 } | |
| 593 | |
| 594 namespace { // for String.fromCodePoint | 18 namespace { // for String.fromCodePoint |
| 595 | 19 |
| 596 bool IsValidCodePoint(Isolate* isolate, Handle<Object> value) { | 20 bool IsValidCodePoint(Isolate* isolate, Handle<Object> value) { |
| 597 if (!value->IsNumber() && !Object::ToNumber(value).ToHandle(&value)) { | 21 if (!value->IsNumber() && !Object::ToNumber(value).ToHandle(&value)) { |
| 598 return false; | 22 return false; |
| 599 } | 23 } |
| 600 | 24 |
| 601 if (Object::ToInteger(isolate, value).ToHandleChecked()->Number() != | 25 if (Object::ToInteger(isolate, value).ToHandleChecked()->Number() != |
| 602 value->Number()) { | 26 value->Number()) { |
| 603 return false; | 27 return false; |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 677 two_byte_buffer.length())); | 101 two_byte_buffer.length())); |
| 678 | 102 |
| 679 CopyChars(result->GetChars(), one_byte_buffer.ToConstVector().start(), | 103 CopyChars(result->GetChars(), one_byte_buffer.ToConstVector().start(), |
| 680 one_byte_buffer.length()); | 104 one_byte_buffer.length()); |
| 681 CopyChars(result->GetChars() + one_byte_buffer.length(), | 105 CopyChars(result->GetChars() + one_byte_buffer.length(), |
| 682 two_byte_buffer.ToConstVector().start(), two_byte_buffer.length()); | 106 two_byte_buffer.ToConstVector().start(), two_byte_buffer.length()); |
| 683 | 107 |
| 684 return *result; | 108 return *result; |
| 685 } | 109 } |
| 686 | 110 |
| 687 // ES6 section 21.1.3.1 String.prototype.charAt ( pos ) | |
| 688 TF_BUILTIN(StringPrototypeCharAt, CodeStubAssembler) { | |
| 689 Node* receiver = Parameter(0); | |
| 690 Node* position = Parameter(1); | |
| 691 Node* context = Parameter(4); | |
| 692 | |
| 693 // Check that {receiver} is coercible to Object and convert it to a String. | |
| 694 receiver = ToThisString(context, receiver, "String.prototype.charAt"); | |
| 695 | |
| 696 // Convert the {position} to a Smi and check that it's in bounds of the | |
| 697 // {receiver}. | |
| 698 { | |
| 699 Label return_emptystring(this, Label::kDeferred); | |
| 700 position = | |
| 701 ToInteger(context, position, CodeStubAssembler::kTruncateMinusZero); | |
| 702 GotoIfNot(TaggedIsSmi(position), &return_emptystring); | |
| 703 | |
| 704 // Determine the actual length of the {receiver} String. | |
| 705 Node* receiver_length = LoadObjectField(receiver, String::kLengthOffset); | |
| 706 | |
| 707 // Return "" if the Smi {position} is outside the bounds of the {receiver}. | |
| 708 Label if_positioninbounds(this); | |
| 709 Branch(SmiAboveOrEqual(position, receiver_length), &return_emptystring, | |
| 710 &if_positioninbounds); | |
| 711 | |
| 712 Bind(&return_emptystring); | |
| 713 Return(EmptyStringConstant()); | |
| 714 | |
| 715 Bind(&if_positioninbounds); | |
| 716 } | |
| 717 | |
| 718 // Load the character code at the {position} from the {receiver}. | |
| 719 Node* code = StringCharCodeAt(receiver, position); | |
| 720 | |
| 721 // And return the single character string with only that {code}. | |
| 722 Node* result = StringFromCharCode(code); | |
| 723 Return(result); | |
| 724 } | |
| 725 | |
| 726 // ES6 section 21.1.3.2 String.prototype.charCodeAt ( pos ) | |
| 727 TF_BUILTIN(StringPrototypeCharCodeAt, CodeStubAssembler) { | |
| 728 Node* receiver = Parameter(0); | |
| 729 Node* position = Parameter(1); | |
| 730 Node* context = Parameter(4); | |
| 731 | |
| 732 // Check that {receiver} is coercible to Object and convert it to a String. | |
| 733 receiver = ToThisString(context, receiver, "String.prototype.charCodeAt"); | |
| 734 | |
| 735 // Convert the {position} to a Smi and check that it's in bounds of the | |
| 736 // {receiver}. | |
| 737 { | |
| 738 Label return_nan(this, Label::kDeferred); | |
| 739 position = | |
| 740 ToInteger(context, position, CodeStubAssembler::kTruncateMinusZero); | |
| 741 GotoIfNot(TaggedIsSmi(position), &return_nan); | |
| 742 | |
| 743 // Determine the actual length of the {receiver} String. | |
| 744 Node* receiver_length = LoadObjectField(receiver, String::kLengthOffset); | |
| 745 | |
| 746 // Return NaN if the Smi {position} is outside the bounds of the {receiver}. | |
| 747 Label if_positioninbounds(this); | |
| 748 Branch(SmiAboveOrEqual(position, receiver_length), &return_nan, | |
| 749 &if_positioninbounds); | |
| 750 | |
| 751 Bind(&return_nan); | |
| 752 Return(NaNConstant()); | |
| 753 | |
| 754 Bind(&if_positioninbounds); | |
| 755 } | |
| 756 | |
| 757 // Load the character at the {position} from the {receiver}. | |
| 758 Node* value = StringCharCodeAt(receiver, position); | |
| 759 Node* result = SmiFromWord32(value); | |
| 760 Return(result); | |
| 761 } | |
| 762 | |
| 763 // ES6 section 21.1.3.6 | 111 // ES6 section 21.1.3.6 |
| 764 // String.prototype.endsWith ( searchString [ , endPosition ] ) | 112 // String.prototype.endsWith ( searchString [ , endPosition ] ) |
| 765 BUILTIN(StringPrototypeEndsWith) { | 113 BUILTIN(StringPrototypeEndsWith) { |
| 766 HandleScope handle_scope(isolate); | 114 HandleScope handle_scope(isolate); |
| 767 TO_THIS_STRING(str, "String.prototype.endsWith"); | 115 TO_THIS_STRING(str, "String.prototype.endsWith"); |
| 768 | 116 |
| 769 // Check if the search string is a regExp and fail if it is. | 117 // Check if the search string is a regExp and fail if it is. |
| 770 Handle<Object> search = args.atOrUndefined(isolate, 1); | 118 Handle<Object> search = args.atOrUndefined(isolate, 1); |
| 771 Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search); | 119 Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search); |
| 772 if (is_reg_exp.IsNothing()) { | 120 if (is_reg_exp.IsNothing()) { |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 849 Handle<Object> position; | 197 Handle<Object> position; |
| 850 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( | 198 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| 851 isolate, position, | 199 isolate, position, |
| 852 Object::ToInteger(isolate, args.atOrUndefined(isolate, 2))); | 200 Object::ToInteger(isolate, args.atOrUndefined(isolate, 2))); |
| 853 | 201 |
| 854 uint32_t index = str->ToValidIndex(*position); | 202 uint32_t index = str->ToValidIndex(*position); |
| 855 int index_in_str = String::IndexOf(isolate, str, search_string, index); | 203 int index_in_str = String::IndexOf(isolate, str, search_string, index); |
| 856 return *isolate->factory()->ToBoolean(index_in_str != -1); | 204 return *isolate->factory()->ToBoolean(index_in_str != -1); |
| 857 } | 205 } |
| 858 | 206 |
| 859 void StringBuiltinsAssembler::StringIndexOf( | |
| 860 Node* receiver, Node* instance_type, Node* search_string, | |
| 861 Node* search_string_instance_type, Node* position, | |
| 862 std::function<void(Node*)> f_return) { | |
| 863 CSA_ASSERT(this, IsString(receiver)); | |
| 864 CSA_ASSERT(this, IsString(search_string)); | |
| 865 CSA_ASSERT(this, TaggedIsSmi(position)); | |
| 866 | |
| 867 Label zero_length_needle(this), | |
| 868 call_runtime_unchecked(this, Label::kDeferred), return_minus_1(this), | |
| 869 check_search_string(this), continue_fast_path(this); | |
| 870 | |
| 871 Node* const int_zero = IntPtrConstant(0); | |
| 872 Variable var_needle_byte(this, MachineType::PointerRepresentation(), | |
| 873 int_zero); | |
| 874 Variable var_string_addr(this, MachineType::PointerRepresentation(), | |
| 875 int_zero); | |
| 876 | |
| 877 Node* needle_length = SmiUntag(LoadStringLength(search_string)); | |
| 878 // Use faster/complex runtime fallback for long search strings. | |
| 879 GotoIf(IntPtrLessThan(IntPtrConstant(1), needle_length), | |
| 880 &call_runtime_unchecked); | |
| 881 Node* string_length = SmiUntag(LoadStringLength(receiver)); | |
| 882 Node* start_position = IntPtrMax(SmiUntag(position), int_zero); | |
| 883 | |
| 884 GotoIf(IntPtrEqual(int_zero, needle_length), &zero_length_needle); | |
| 885 // Check that the needle fits in the start position. | |
| 886 GotoIfNot(IntPtrLessThanOrEqual(needle_length, | |
| 887 IntPtrSub(string_length, start_position)), | |
| 888 &return_minus_1); | |
| 889 | |
| 890 // Load the string address. | |
| 891 { | |
| 892 Label if_onebyte_sequential(this); | |
| 893 Label if_onebyte_external(this, Label::kDeferred); | |
| 894 | |
| 895 // Only support one-byte strings on the fast path. | |
| 896 DispatchOnStringInstanceType(instance_type, &if_onebyte_sequential, | |
| 897 &if_onebyte_external, &call_runtime_unchecked); | |
| 898 | |
| 899 Bind(&if_onebyte_sequential); | |
| 900 { | |
| 901 var_string_addr.Bind( | |
| 902 OneByteCharAddress(BitcastTaggedToWord(receiver), start_position)); | |
| 903 Goto(&check_search_string); | |
| 904 } | |
| 905 | |
| 906 Bind(&if_onebyte_external); | |
| 907 { | |
| 908 Node* const unpacked = TryDerefExternalString(receiver, instance_type, | |
| 909 &call_runtime_unchecked); | |
| 910 var_string_addr.Bind(OneByteCharAddress(unpacked, start_position)); | |
| 911 Goto(&check_search_string); | |
| 912 } | |
| 913 } | |
| 914 | |
| 915 // Load the needle character. | |
| 916 Bind(&check_search_string); | |
| 917 { | |
| 918 Label if_onebyte_sequential(this); | |
| 919 Label if_onebyte_external(this, Label::kDeferred); | |
| 920 | |
| 921 DispatchOnStringInstanceType(search_string_instance_type, | |
| 922 &if_onebyte_sequential, &if_onebyte_external, | |
| 923 &call_runtime_unchecked); | |
| 924 | |
| 925 Bind(&if_onebyte_sequential); | |
| 926 { | |
| 927 var_needle_byte.Bind( | |
| 928 ChangeInt32ToIntPtr(LoadOneByteChar(search_string, int_zero))); | |
| 929 Goto(&continue_fast_path); | |
| 930 } | |
| 931 | |
| 932 Bind(&if_onebyte_external); | |
| 933 { | |
| 934 Node* const unpacked = TryDerefExternalString( | |
| 935 search_string, search_string_instance_type, &call_runtime_unchecked); | |
| 936 var_needle_byte.Bind( | |
| 937 ChangeInt32ToIntPtr(LoadOneByteChar(unpacked, int_zero))); | |
| 938 Goto(&continue_fast_path); | |
| 939 } | |
| 940 } | |
| 941 | |
| 942 Bind(&continue_fast_path); | |
| 943 { | |
| 944 Node* needle_byte = var_needle_byte.value(); | |
| 945 Node* string_addr = var_string_addr.value(); | |
| 946 Node* search_length = IntPtrSub(string_length, start_position); | |
| 947 // Call out to the highly optimized memchr to perform the actual byte | |
| 948 // search. | |
| 949 Node* memchr = | |
| 950 ExternalConstant(ExternalReference::libc_memchr_function(isolate())); | |
| 951 Node* result_address = | |
| 952 CallCFunction3(MachineType::Pointer(), MachineType::Pointer(), | |
| 953 MachineType::IntPtr(), MachineType::UintPtr(), memchr, | |
| 954 string_addr, needle_byte, search_length); | |
| 955 GotoIf(WordEqual(result_address, int_zero), &return_minus_1); | |
| 956 Node* result_index = | |
| 957 IntPtrAdd(IntPtrSub(result_address, string_addr), start_position); | |
| 958 f_return(SmiTag(result_index)); | |
| 959 } | |
| 960 | |
| 961 Bind(&return_minus_1); | |
| 962 f_return(SmiConstant(-1)); | |
| 963 | |
| 964 Bind(&zero_length_needle); | |
| 965 { | |
| 966 Comment("0-length search_string"); | |
| 967 f_return(SmiTag(IntPtrMin(string_length, start_position))); | |
| 968 } | |
| 969 | |
| 970 Bind(&call_runtime_unchecked); | |
| 971 { | |
| 972 // Simplified version of the runtime call where the types of the arguments | |
| 973 // are already known due to type checks in this stub. | |
| 974 Comment("Call Runtime Unchecked"); | |
| 975 Node* result = CallRuntime(Runtime::kStringIndexOfUnchecked, SmiConstant(0), | |
| 976 receiver, search_string, position); | |
| 977 f_return(result); | |
| 978 } | |
| 979 } | |
| 980 | |
| 981 // ES6 String.prototype.indexOf(searchString [, position]) | |
| 982 // #sec-string.prototype.indexof | |
| 983 // Unchecked helper for builtins lowering. | |
| 984 TF_BUILTIN(StringIndexOf, StringBuiltinsAssembler) { | |
| 985 Node* receiver = Parameter(0); | |
| 986 Node* search_string = Parameter(1); | |
| 987 Node* position = Parameter(2); | |
| 988 | |
| 989 Node* instance_type = LoadInstanceType(receiver); | |
| 990 Node* search_string_instance_type = LoadInstanceType(search_string); | |
| 991 | |
| 992 StringIndexOf(receiver, instance_type, search_string, | |
| 993 search_string_instance_type, position, | |
| 994 [this](Node* result) { this->Return(result); }); | |
| 995 } | |
| 996 | |
| 997 // ES6 String.prototype.indexOf(searchString [, position]) | |
| 998 // #sec-string.prototype.indexof | |
| 999 TF_BUILTIN(StringPrototypeIndexOf, StringBuiltinsAssembler) { | |
| 1000 Variable search_string(this, MachineRepresentation::kTagged), | |
| 1001 position(this, MachineRepresentation::kTagged); | |
| 1002 Label call_runtime(this), call_runtime_unchecked(this), argc_0(this), | |
| 1003 no_argc_0(this), argc_1(this), no_argc_1(this), argc_2(this), | |
| 1004 fast_path(this), return_minus_1(this); | |
| 1005 | |
| 1006 Node* argc = Parameter(BuiltinDescriptor::kArgumentsCount); | |
| 1007 Node* context = Parameter(BuiltinDescriptor::kContext); | |
| 1008 | |
| 1009 CodeStubArguments arguments(this, ChangeInt32ToIntPtr(argc)); | |
| 1010 Node* receiver = arguments.GetReceiver(); | |
| 1011 // From now on use word-size argc value. | |
| 1012 argc = arguments.GetLength(); | |
| 1013 | |
| 1014 GotoIf(IntPtrEqual(argc, IntPtrConstant(0)), &argc_0); | |
| 1015 GotoIf(IntPtrEqual(argc, IntPtrConstant(1)), &argc_1); | |
| 1016 Goto(&argc_2); | |
| 1017 Bind(&argc_0); | |
| 1018 { | |
| 1019 Comment("0 Argument case"); | |
| 1020 Node* undefined = UndefinedConstant(); | |
| 1021 search_string.Bind(undefined); | |
| 1022 position.Bind(undefined); | |
| 1023 Goto(&call_runtime); | |
| 1024 } | |
| 1025 Bind(&argc_1); | |
| 1026 { | |
| 1027 Comment("1 Argument case"); | |
| 1028 search_string.Bind(arguments.AtIndex(0)); | |
| 1029 position.Bind(SmiConstant(0)); | |
| 1030 Goto(&fast_path); | |
| 1031 } | |
| 1032 Bind(&argc_2); | |
| 1033 { | |
| 1034 Comment("2 Argument case"); | |
| 1035 search_string.Bind(arguments.AtIndex(0)); | |
| 1036 position.Bind(arguments.AtIndex(1)); | |
| 1037 GotoIfNot(TaggedIsSmi(position.value()), &call_runtime); | |
| 1038 Goto(&fast_path); | |
| 1039 } | |
| 1040 | |
| 1041 Bind(&fast_path); | |
| 1042 { | |
| 1043 Comment("Fast Path"); | |
| 1044 GotoIf(TaggedIsSmi(receiver), &call_runtime); | |
| 1045 Node* needle = search_string.value(); | |
| 1046 GotoIf(TaggedIsSmi(needle), &call_runtime); | |
| 1047 | |
| 1048 Node* instance_type = LoadInstanceType(receiver); | |
| 1049 GotoIfNot(IsStringInstanceType(instance_type), &call_runtime); | |
| 1050 | |
| 1051 Node* needle_instance_type = LoadInstanceType(needle); | |
| 1052 GotoIfNot(IsStringInstanceType(needle_instance_type), &call_runtime); | |
| 1053 | |
| 1054 StringIndexOf( | |
| 1055 receiver, instance_type, needle, needle_instance_type, position.value(), | |
| 1056 [&arguments](Node* result) { arguments.PopAndReturn(result); }); | |
| 1057 } | |
| 1058 | |
| 1059 Bind(&call_runtime); | |
| 1060 { | |
| 1061 Comment("Call Runtime"); | |
| 1062 Node* result = CallRuntime(Runtime::kStringIndexOf, context, receiver, | |
| 1063 search_string.value(), position.value()); | |
| 1064 arguments.PopAndReturn(result); | |
| 1065 } | |
| 1066 } | |
| 1067 | |
| 1068 // ES6 section 21.1.3.9 | 207 // ES6 section 21.1.3.9 |
| 1069 // String.prototype.lastIndexOf ( searchString [ , position ] ) | 208 // String.prototype.lastIndexOf ( searchString [ , position ] ) |
| 1070 BUILTIN(StringPrototypeLastIndexOf) { | 209 BUILTIN(StringPrototypeLastIndexOf) { |
| 1071 HandleScope handle_scope(isolate); | 210 HandleScope handle_scope(isolate); |
| 1072 return String::LastIndexOf(isolate, args.receiver(), | 211 return String::LastIndexOf(isolate, args.receiver(), |
| 1073 args.atOrUndefined(isolate, 1), | 212 args.atOrUndefined(isolate, 1), |
| 1074 args.atOrUndefined(isolate, 2)); | 213 args.atOrUndefined(isolate, 2)); |
| 1075 } | 214 } |
| 1076 | 215 |
| 1077 // ES6 section 21.1.3.10 String.prototype.localeCompare ( that ) | 216 // ES6 section 21.1.3.10 String.prototype.localeCompare ( that ) |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1152 Handle<String> valid_forms = | 291 Handle<String> valid_forms = |
| 1153 isolate->factory()->NewStringFromStaticChars("NFC, NFD, NFKC, NFKD"); | 292 isolate->factory()->NewStringFromStaticChars("NFC, NFD, NFKC, NFKD"); |
| 1154 THROW_NEW_ERROR_RETURN_FAILURE( | 293 THROW_NEW_ERROR_RETURN_FAILURE( |
| 1155 isolate, | 294 isolate, |
| 1156 NewRangeError(MessageTemplate::kNormalizationForm, valid_forms)); | 295 NewRangeError(MessageTemplate::kNormalizationForm, valid_forms)); |
| 1157 } | 296 } |
| 1158 | 297 |
| 1159 return *string; | 298 return *string; |
| 1160 } | 299 } |
| 1161 | 300 |
| 1162 compiler::Node* StringBuiltinsAssembler::IsNullOrUndefined(Node* const value) { | |
| 1163 return Word32Or(IsUndefined(value), IsNull(value)); | |
| 1164 } | |
| 1165 | |
| 1166 void StringBuiltinsAssembler::RequireObjectCoercible(Node* const context, | |
| 1167 Node* const value, | |
| 1168 const char* method_name) { | |
| 1169 Label out(this), throw_exception(this, Label::kDeferred); | |
| 1170 Branch(IsNullOrUndefined(value), &throw_exception, &out); | |
| 1171 | |
| 1172 Bind(&throw_exception); | |
| 1173 TailCallRuntime( | |
| 1174 Runtime::kThrowCalledOnNullOrUndefined, context, | |
| 1175 HeapConstant(factory()->NewStringFromAsciiChecked(method_name, TENURED))); | |
| 1176 | |
| 1177 Bind(&out); | |
| 1178 } | |
| 1179 | |
| 1180 void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol( | |
| 1181 Node* const context, Node* const object, Handle<Symbol> symbol, | |
| 1182 const NodeFunction0& regexp_call, const NodeFunction1& generic_call) { | |
| 1183 Label out(this); | |
| 1184 | |
| 1185 // Smis definitely don't have an attached symbol. | |
| 1186 GotoIf(TaggedIsSmi(object), &out); | |
| 1187 | |
| 1188 Node* const object_map = LoadMap(object); | |
| 1189 | |
| 1190 // Skip the slow lookup for Strings. | |
| 1191 { | |
| 1192 Label next(this); | |
| 1193 | |
| 1194 GotoIfNot(IsStringInstanceType(LoadMapInstanceType(object_map)), &next); | |
| 1195 | |
| 1196 Node* const native_context = LoadNativeContext(context); | |
| 1197 Node* const initial_proto_initial_map = LoadContextElement( | |
| 1198 native_context, Context::STRING_FUNCTION_PROTOTYPE_MAP_INDEX); | |
| 1199 | |
| 1200 Node* const string_fun = | |
| 1201 LoadContextElement(native_context, Context::STRING_FUNCTION_INDEX); | |
| 1202 Node* const initial_map = | |
| 1203 LoadObjectField(string_fun, JSFunction::kPrototypeOrInitialMapOffset); | |
| 1204 Node* const proto_map = LoadMap(LoadMapPrototype(initial_map)); | |
| 1205 | |
| 1206 Branch(WordEqual(proto_map, initial_proto_initial_map), &out, &next); | |
| 1207 | |
| 1208 Bind(&next); | |
| 1209 } | |
| 1210 | |
| 1211 // Take the fast path for RegExps. | |
| 1212 { | |
| 1213 Label stub_call(this), slow_lookup(this); | |
| 1214 | |
| 1215 RegExpBuiltinsAssembler regexp_asm(state()); | |
| 1216 regexp_asm.BranchIfFastRegExp(context, object_map, &stub_call, | |
| 1217 &slow_lookup); | |
| 1218 | |
| 1219 Bind(&stub_call); | |
| 1220 Return(regexp_call()); | |
| 1221 | |
| 1222 Bind(&slow_lookup); | |
| 1223 } | |
| 1224 | |
| 1225 GotoIf(IsNullOrUndefined(object), &out); | |
| 1226 | |
| 1227 // Fall back to a slow lookup of {object[symbol]}. | |
| 1228 | |
| 1229 Node* const maybe_func = GetProperty(context, object, symbol); | |
| 1230 GotoIf(IsUndefined(maybe_func), &out); | |
| 1231 | |
| 1232 // Attempt to call the function. | |
| 1233 | |
| 1234 Node* const result = generic_call(maybe_func); | |
| 1235 Return(result); | |
| 1236 | |
| 1237 Bind(&out); | |
| 1238 } | |
| 1239 | |
| 1240 // ES6 section 21.1.3.16 String.prototype.replace ( search, replace ) | |
| 1241 TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) { | |
| 1242 Label out(this); | |
| 1243 | |
| 1244 Node* const receiver = Parameter(0); | |
| 1245 Node* const search = Parameter(1); | |
| 1246 Node* const replace = Parameter(2); | |
| 1247 Node* const context = Parameter(5); | |
| 1248 | |
| 1249 Node* const smi_zero = SmiConstant(0); | |
| 1250 | |
| 1251 RequireObjectCoercible(context, receiver, "String.prototype.replace"); | |
| 1252 | |
| 1253 // Redirect to replacer method if {search[@@replace]} is not undefined. | |
| 1254 | |
| 1255 MaybeCallFunctionAtSymbol( | |
| 1256 context, search, isolate()->factory()->replace_symbol(), | |
| 1257 [=]() { | |
| 1258 Callable tostring_callable = CodeFactory::ToString(isolate()); | |
| 1259 Node* const subject_string = | |
| 1260 CallStub(tostring_callable, context, receiver); | |
| 1261 | |
| 1262 Callable replace_callable = CodeFactory::RegExpReplace(isolate()); | |
| 1263 return CallStub(replace_callable, context, search, subject_string, | |
| 1264 replace); | |
| 1265 }, | |
| 1266 [=](Node* fn) { | |
| 1267 Callable call_callable = CodeFactory::Call(isolate()); | |
| 1268 return CallJS(call_callable, context, fn, search, receiver, replace); | |
| 1269 }); | |
| 1270 | |
| 1271 // Convert {receiver} and {search} to strings. | |
| 1272 | |
| 1273 Callable tostring_callable = CodeFactory::ToString(isolate()); | |
| 1274 Callable indexof_callable = CodeFactory::StringIndexOf(isolate()); | |
| 1275 | |
| 1276 Node* const subject_string = CallStub(tostring_callable, context, receiver); | |
| 1277 Node* const search_string = CallStub(tostring_callable, context, search); | |
| 1278 | |
| 1279 Node* const subject_length = LoadStringLength(subject_string); | |
| 1280 Node* const search_length = LoadStringLength(search_string); | |
| 1281 | |
| 1282 // Fast-path single-char {search}, long {receiver}, and simple string | |
| 1283 // {replace}. | |
| 1284 { | |
| 1285 Label next(this); | |
| 1286 | |
| 1287 GotoIfNot(SmiEqual(search_length, SmiConstant(1)), &next); | |
| 1288 GotoIfNot(SmiGreaterThan(subject_length, SmiConstant(0xFF)), &next); | |
| 1289 GotoIf(TaggedIsSmi(replace), &next); | |
| 1290 GotoIfNot(IsString(replace), &next); | |
| 1291 | |
| 1292 Node* const dollar_string = HeapConstant( | |
| 1293 isolate()->factory()->LookupSingleCharacterStringFromCode('$')); | |
| 1294 Node* const dollar_ix = | |
| 1295 CallStub(indexof_callable, context, replace, dollar_string, smi_zero); | |
| 1296 GotoIfNot(SmiIsNegative(dollar_ix), &next); | |
| 1297 | |
| 1298 // Searching by traversing a cons string tree and replace with cons of | |
| 1299 // slices works only when the replaced string is a single character, being | |
| 1300 // replaced by a simple string and only pays off for long strings. | |
| 1301 // TODO(jgruber): Reevaluate if this is still beneficial. | |
| 1302 // TODO(jgruber): TailCallRuntime when it correctly handles adapter frames. | |
| 1303 Return(CallRuntime(Runtime::kStringReplaceOneCharWithString, context, | |
| 1304 subject_string, search_string, replace)); | |
| 1305 | |
| 1306 Bind(&next); | |
| 1307 } | |
| 1308 | |
| 1309 // TODO(jgruber): Extend StringIndexOf to handle two-byte strings and | |
| 1310 // longer substrings - we can handle up to 8 chars (one-byte) / 4 chars | |
| 1311 // (2-byte). | |
| 1312 | |
| 1313 Node* const match_start_index = CallStub( | |
| 1314 indexof_callable, context, subject_string, search_string, smi_zero); | |
| 1315 CSA_ASSERT(this, TaggedIsSmi(match_start_index)); | |
| 1316 | |
| 1317 // Early exit if no match found. | |
| 1318 { | |
| 1319 Label next(this), return_subject(this); | |
| 1320 | |
| 1321 GotoIfNot(SmiIsNegative(match_start_index), &next); | |
| 1322 | |
| 1323 // The spec requires to perform ToString(replace) if the {replace} is not | |
| 1324 // callable even if we are going to exit here. | |
| 1325 // Since ToString() being applied to Smi does not have side effects for | |
| 1326 // numbers we can skip it. | |
| 1327 GotoIf(TaggedIsSmi(replace), &return_subject); | |
| 1328 GotoIf(IsCallableMap(LoadMap(replace)), &return_subject); | |
| 1329 | |
| 1330 // TODO(jgruber): Could introduce ToStringSideeffectsStub which only | |
| 1331 // performs observable parts of ToString. | |
| 1332 CallStub(tostring_callable, context, replace); | |
| 1333 Goto(&return_subject); | |
| 1334 | |
| 1335 Bind(&return_subject); | |
| 1336 Return(subject_string); | |
| 1337 | |
| 1338 Bind(&next); | |
| 1339 } | |
| 1340 | |
| 1341 Node* const match_end_index = SmiAdd(match_start_index, search_length); | |
| 1342 | |
| 1343 Callable substring_callable = CodeFactory::SubString(isolate()); | |
| 1344 Callable stringadd_callable = | |
| 1345 CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED); | |
| 1346 | |
| 1347 Variable var_result(this, MachineRepresentation::kTagged, | |
| 1348 EmptyStringConstant()); | |
| 1349 | |
| 1350 // Compute the prefix. | |
| 1351 { | |
| 1352 Label next(this); | |
| 1353 | |
| 1354 GotoIf(SmiEqual(match_start_index, smi_zero), &next); | |
| 1355 Node* const prefix = CallStub(substring_callable, context, subject_string, | |
| 1356 smi_zero, match_start_index); | |
| 1357 var_result.Bind(prefix); | |
| 1358 | |
| 1359 Goto(&next); | |
| 1360 Bind(&next); | |
| 1361 } | |
| 1362 | |
| 1363 // Compute the string to replace with. | |
| 1364 | |
| 1365 Label if_iscallablereplace(this), if_notcallablereplace(this); | |
| 1366 GotoIf(TaggedIsSmi(replace), &if_notcallablereplace); | |
| 1367 Branch(IsCallableMap(LoadMap(replace)), &if_iscallablereplace, | |
| 1368 &if_notcallablereplace); | |
| 1369 | |
| 1370 Bind(&if_iscallablereplace); | |
| 1371 { | |
| 1372 Callable call_callable = CodeFactory::Call(isolate()); | |
| 1373 Node* const replacement = | |
| 1374 CallJS(call_callable, context, replace, UndefinedConstant(), | |
| 1375 search_string, match_start_index, subject_string); | |
| 1376 Node* const replacement_string = | |
| 1377 CallStub(tostring_callable, context, replacement); | |
| 1378 var_result.Bind(CallStub(stringadd_callable, context, var_result.value(), | |
| 1379 replacement_string)); | |
| 1380 Goto(&out); | |
| 1381 } | |
| 1382 | |
| 1383 Bind(&if_notcallablereplace); | |
| 1384 { | |
| 1385 Node* const replace_string = CallStub(tostring_callable, context, replace); | |
| 1386 | |
| 1387 // TODO(jgruber): Simplified GetSubstitution implementation in CSA. | |
| 1388 Node* const matched = CallStub(substring_callable, context, subject_string, | |
| 1389 match_start_index, match_end_index); | |
| 1390 Node* const replacement_string = | |
| 1391 CallRuntime(Runtime::kGetSubstitution, context, matched, subject_string, | |
| 1392 match_start_index, replace_string); | |
| 1393 var_result.Bind(CallStub(stringadd_callable, context, var_result.value(), | |
| 1394 replacement_string)); | |
| 1395 Goto(&out); | |
| 1396 } | |
| 1397 | |
| 1398 Bind(&out); | |
| 1399 { | |
| 1400 Node* const suffix = CallStub(substring_callable, context, subject_string, | |
| 1401 match_end_index, subject_length); | |
| 1402 Node* const result = | |
| 1403 CallStub(stringadd_callable, context, var_result.value(), suffix); | |
| 1404 Return(result); | |
| 1405 } | |
| 1406 } | |
| 1407 | |
| 1408 // ES6 section 21.1.3.19 String.prototype.split ( separator, limit ) | |
| 1409 TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) { | |
| 1410 Label out(this); | |
| 1411 | |
| 1412 Node* const receiver = Parameter(0); | |
| 1413 Node* const separator = Parameter(1); | |
| 1414 Node* const limit = Parameter(2); | |
| 1415 Node* const context = Parameter(5); | |
| 1416 | |
| 1417 Node* const smi_zero = SmiConstant(0); | |
| 1418 | |
| 1419 RequireObjectCoercible(context, receiver, "String.prototype.split"); | |
| 1420 | |
| 1421 // Redirect to splitter method if {separator[@@split]} is not undefined. | |
| 1422 | |
| 1423 MaybeCallFunctionAtSymbol( | |
| 1424 context, separator, isolate()->factory()->split_symbol(), | |
| 1425 [=]() { | |
| 1426 Callable tostring_callable = CodeFactory::ToString(isolate()); | |
| 1427 Node* const subject_string = | |
| 1428 CallStub(tostring_callable, context, receiver); | |
| 1429 | |
| 1430 Callable split_callable = CodeFactory::RegExpSplit(isolate()); | |
| 1431 return CallStub(split_callable, context, separator, subject_string, | |
| 1432 limit); | |
| 1433 }, | |
| 1434 [=](Node* fn) { | |
| 1435 Callable call_callable = CodeFactory::Call(isolate()); | |
| 1436 return CallJS(call_callable, context, fn, separator, receiver, limit); | |
| 1437 }); | |
| 1438 | |
| 1439 // String and integer conversions. | |
| 1440 // TODO(jgruber): The old implementation used Uint32Max instead of SmiMax - | |
| 1441 // but AFAIK there should not be a difference since arrays are capped at Smi | |
| 1442 // lengths. | |
| 1443 | |
| 1444 Callable tostring_callable = CodeFactory::ToString(isolate()); | |
| 1445 Node* const subject_string = CallStub(tostring_callable, context, receiver); | |
| 1446 Node* const limit_number = | |
| 1447 Select(IsUndefined(limit), [=]() { return SmiConstant(Smi::kMaxValue); }, | |
| 1448 [=]() { return ToUint32(context, limit); }, | |
| 1449 MachineRepresentation::kTagged); | |
| 1450 Node* const separator_string = | |
| 1451 CallStub(tostring_callable, context, separator); | |
| 1452 | |
| 1453 // Shortcut for {limit} == 0. | |
| 1454 { | |
| 1455 Label next(this); | |
| 1456 GotoIfNot(SmiEqual(limit_number, smi_zero), &next); | |
| 1457 | |
| 1458 const ElementsKind kind = FAST_ELEMENTS; | |
| 1459 Node* const native_context = LoadNativeContext(context); | |
| 1460 Node* const array_map = LoadJSArrayElementsMap(kind, native_context); | |
| 1461 | |
| 1462 Node* const length = smi_zero; | |
| 1463 Node* const capacity = IntPtrConstant(0); | |
| 1464 Node* const result = AllocateJSArray(kind, array_map, capacity, length); | |
| 1465 | |
| 1466 Return(result); | |
| 1467 | |
| 1468 Bind(&next); | |
| 1469 } | |
| 1470 | |
| 1471 // ECMA-262 says that if {separator} is undefined, the result should | |
| 1472 // be an array of size 1 containing the entire string. | |
| 1473 { | |
| 1474 Label next(this); | |
| 1475 GotoIfNot(IsUndefined(separator), &next); | |
| 1476 | |
| 1477 const ElementsKind kind = FAST_ELEMENTS; | |
| 1478 Node* const native_context = LoadNativeContext(context); | |
| 1479 Node* const array_map = LoadJSArrayElementsMap(kind, native_context); | |
| 1480 | |
| 1481 Node* const length = SmiConstant(1); | |
| 1482 Node* const capacity = IntPtrConstant(1); | |
| 1483 Node* const result = AllocateJSArray(kind, array_map, capacity, length); | |
| 1484 | |
| 1485 Node* const fixed_array = LoadElements(result); | |
| 1486 StoreFixedArrayElement(fixed_array, 0, subject_string); | |
| 1487 | |
| 1488 Return(result); | |
| 1489 | |
| 1490 Bind(&next); | |
| 1491 } | |
| 1492 | |
| 1493 // If the separator string is empty then return the elements in the subject. | |
| 1494 { | |
| 1495 Label next(this); | |
| 1496 GotoIfNot(SmiEqual(LoadStringLength(separator_string), smi_zero), &next); | |
| 1497 | |
| 1498 Node* const result = CallRuntime(Runtime::kStringToArray, context, | |
| 1499 subject_string, limit_number); | |
| 1500 Return(result); | |
| 1501 | |
| 1502 Bind(&next); | |
| 1503 } | |
| 1504 | |
| 1505 Node* const result = | |
| 1506 CallRuntime(Runtime::kStringSplit, context, subject_string, | |
| 1507 separator_string, limit_number); | |
| 1508 Return(result); | |
| 1509 } | |
| 1510 | |
| 1511 // ES6 section B.2.3.1 String.prototype.substr ( start, length ) | |
| 1512 TF_BUILTIN(StringPrototypeSubstr, CodeStubAssembler) { | |
| 1513 Label out(this), handle_length(this); | |
| 1514 | |
| 1515 Variable var_start(this, MachineRepresentation::kTagged); | |
| 1516 Variable var_length(this, MachineRepresentation::kTagged); | |
| 1517 | |
| 1518 Node* const receiver = Parameter(0); | |
| 1519 Node* const start = Parameter(1); | |
| 1520 Node* const length = Parameter(2); | |
| 1521 Node* const context = Parameter(5); | |
| 1522 | |
| 1523 Node* const zero = SmiConstant(Smi::kZero); | |
| 1524 | |
| 1525 // Check that {receiver} is coercible to Object and convert it to a String. | |
| 1526 Node* const string = | |
| 1527 ToThisString(context, receiver, "String.prototype.substr"); | |
| 1528 | |
| 1529 Node* const string_length = LoadStringLength(string); | |
| 1530 | |
| 1531 // Conversions and bounds-checks for {start}. | |
| 1532 { | |
| 1533 Node* const start_int = | |
| 1534 ToInteger(context, start, CodeStubAssembler::kTruncateMinusZero); | |
| 1535 | |
| 1536 Label if_issmi(this), if_isheapnumber(this, Label::kDeferred); | |
| 1537 Branch(TaggedIsSmi(start_int), &if_issmi, &if_isheapnumber); | |
| 1538 | |
| 1539 Bind(&if_issmi); | |
| 1540 { | |
| 1541 Node* const length_plus_start = SmiAdd(string_length, start_int); | |
| 1542 var_start.Bind(Select(SmiLessThan(start_int, zero), | |
| 1543 [&] { return SmiMax(length_plus_start, zero); }, | |
| 1544 [&] { return start_int; }, | |
| 1545 MachineRepresentation::kTagged)); | |
| 1546 Goto(&handle_length); | |
| 1547 } | |
| 1548 | |
| 1549 Bind(&if_isheapnumber); | |
| 1550 { | |
| 1551 // If {start} is a heap number, it is definitely out of bounds. If it is | |
| 1552 // negative, {start} = max({string_length} + {start}),0) = 0'. If it is | |
| 1553 // positive, set {start} to {string_length} which ultimately results in | |
| 1554 // returning an empty string. | |
| 1555 Node* const float_zero = Float64Constant(0.); | |
| 1556 Node* const start_float = LoadHeapNumberValue(start_int); | |
| 1557 var_start.Bind(SelectTaggedConstant( | |
| 1558 Float64LessThan(start_float, float_zero), zero, string_length)); | |
| 1559 Goto(&handle_length); | |
| 1560 } | |
| 1561 } | |
| 1562 | |
| 1563 // Conversions and bounds-checks for {length}. | |
| 1564 Bind(&handle_length); | |
| 1565 { | |
| 1566 Label if_issmi(this), if_isheapnumber(this, Label::kDeferred); | |
| 1567 | |
| 1568 // Default to {string_length} if {length} is undefined. | |
| 1569 { | |
| 1570 Label if_isundefined(this, Label::kDeferred), if_isnotundefined(this); | |
| 1571 Branch(WordEqual(length, UndefinedConstant()), &if_isundefined, | |
| 1572 &if_isnotundefined); | |
| 1573 | |
| 1574 Bind(&if_isundefined); | |
| 1575 var_length.Bind(string_length); | |
| 1576 Goto(&if_issmi); | |
| 1577 | |
| 1578 Bind(&if_isnotundefined); | |
| 1579 var_length.Bind( | |
| 1580 ToInteger(context, length, CodeStubAssembler::kTruncateMinusZero)); | |
| 1581 } | |
| 1582 | |
| 1583 Branch(TaggedIsSmi(var_length.value()), &if_issmi, &if_isheapnumber); | |
| 1584 | |
| 1585 // Set {length} to min(max({length}, 0), {string_length} - {start} | |
| 1586 Bind(&if_issmi); | |
| 1587 { | |
| 1588 Node* const positive_length = SmiMax(var_length.value(), zero); | |
| 1589 | |
| 1590 Node* const minimal_length = SmiSub(string_length, var_start.value()); | |
| 1591 var_length.Bind(SmiMin(positive_length, minimal_length)); | |
| 1592 | |
| 1593 GotoIfNot(SmiLessThanOrEqual(var_length.value(), zero), &out); | |
| 1594 Return(EmptyStringConstant()); | |
| 1595 } | |
| 1596 | |
| 1597 Bind(&if_isheapnumber); | |
| 1598 { | |
| 1599 // If {length} is a heap number, it is definitely out of bounds. There are | |
| 1600 // two cases according to the spec: if it is negative, "" is returned; if | |
| 1601 // it is positive, then length is set to {string_length} - {start}. | |
| 1602 | |
| 1603 CSA_ASSERT(this, IsHeapNumberMap(LoadMap(var_length.value()))); | |
| 1604 | |
| 1605 Label if_isnegative(this), if_ispositive(this); | |
| 1606 Node* const float_zero = Float64Constant(0.); | |
| 1607 Node* const length_float = LoadHeapNumberValue(var_length.value()); | |
| 1608 Branch(Float64LessThan(length_float, float_zero), &if_isnegative, | |
| 1609 &if_ispositive); | |
| 1610 | |
| 1611 Bind(&if_isnegative); | |
| 1612 Return(EmptyStringConstant()); | |
| 1613 | |
| 1614 Bind(&if_ispositive); | |
| 1615 { | |
| 1616 var_length.Bind(SmiSub(string_length, var_start.value())); | |
| 1617 GotoIfNot(SmiLessThanOrEqual(var_length.value(), zero), &out); | |
| 1618 Return(EmptyStringConstant()); | |
| 1619 } | |
| 1620 } | |
| 1621 } | |
| 1622 | |
| 1623 Bind(&out); | |
| 1624 { | |
| 1625 Node* const end = SmiAdd(var_start.value(), var_length.value()); | |
| 1626 Node* const result = SubString(context, string, var_start.value(), end); | |
| 1627 Return(result); | |
| 1628 } | |
| 1629 } | |
| 1630 | |
| 1631 compiler::Node* StringBuiltinsAssembler::ToSmiBetweenZeroAnd(Node* context, | |
| 1632 Node* value, | |
| 1633 Node* limit) { | |
| 1634 Label out(this); | |
| 1635 Variable var_result(this, MachineRepresentation::kTagged); | |
| 1636 | |
| 1637 Node* const value_int = | |
| 1638 this->ToInteger(context, value, CodeStubAssembler::kTruncateMinusZero); | |
| 1639 | |
| 1640 Label if_issmi(this), if_isnotsmi(this, Label::kDeferred); | |
| 1641 Branch(TaggedIsSmi(value_int), &if_issmi, &if_isnotsmi); | |
| 1642 | |
| 1643 Bind(&if_issmi); | |
| 1644 { | |
| 1645 Label if_isinbounds(this), if_isoutofbounds(this, Label::kDeferred); | |
| 1646 Branch(SmiAbove(value_int, limit), &if_isoutofbounds, &if_isinbounds); | |
| 1647 | |
| 1648 Bind(&if_isinbounds); | |
| 1649 { | |
| 1650 var_result.Bind(value_int); | |
| 1651 Goto(&out); | |
| 1652 } | |
| 1653 | |
| 1654 Bind(&if_isoutofbounds); | |
| 1655 { | |
| 1656 Node* const zero = SmiConstant(Smi::kZero); | |
| 1657 var_result.Bind( | |
| 1658 SelectTaggedConstant(SmiLessThan(value_int, zero), zero, limit)); | |
| 1659 Goto(&out); | |
| 1660 } | |
| 1661 } | |
| 1662 | |
| 1663 Bind(&if_isnotsmi); | |
| 1664 { | |
| 1665 // {value} is a heap number - in this case, it is definitely out of bounds. | |
| 1666 CSA_ASSERT(this, IsHeapNumberMap(LoadMap(value_int))); | |
| 1667 | |
| 1668 Node* const float_zero = Float64Constant(0.); | |
| 1669 Node* const smi_zero = SmiConstant(Smi::kZero); | |
| 1670 Node* const value_float = LoadHeapNumberValue(value_int); | |
| 1671 var_result.Bind(SelectTaggedConstant( | |
| 1672 Float64LessThan(value_float, float_zero), smi_zero, limit)); | |
| 1673 Goto(&out); | |
| 1674 } | |
| 1675 | |
| 1676 Bind(&out); | |
| 1677 return var_result.value(); | |
| 1678 } | |
| 1679 | |
| 1680 // ES6 section 21.1.3.19 String.prototype.substring ( start, end ) | |
| 1681 TF_BUILTIN(StringPrototypeSubstring, StringBuiltinsAssembler) { | |
| 1682 Label out(this); | |
| 1683 | |
| 1684 Variable var_start(this, MachineRepresentation::kTagged); | |
| 1685 Variable var_end(this, MachineRepresentation::kTagged); | |
| 1686 | |
| 1687 Node* const receiver = Parameter(0); | |
| 1688 Node* const start = Parameter(1); | |
| 1689 Node* const end = Parameter(2); | |
| 1690 Node* const context = Parameter(5); | |
| 1691 | |
| 1692 // Check that {receiver} is coercible to Object and convert it to a String. | |
| 1693 Node* const string = | |
| 1694 ToThisString(context, receiver, "String.prototype.substring"); | |
| 1695 | |
| 1696 Node* const length = LoadStringLength(string); | |
| 1697 | |
| 1698 // Conversion and bounds-checks for {start}. | |
| 1699 var_start.Bind(ToSmiBetweenZeroAnd(context, start, length)); | |
| 1700 | |
| 1701 // Conversion and bounds-checks for {end}. | |
| 1702 { | |
| 1703 var_end.Bind(length); | |
| 1704 GotoIf(WordEqual(end, UndefinedConstant()), &out); | |
| 1705 | |
| 1706 var_end.Bind(ToSmiBetweenZeroAnd(context, end, length)); | |
| 1707 | |
| 1708 Label if_endislessthanstart(this); | |
| 1709 Branch(SmiLessThan(var_end.value(), var_start.value()), | |
| 1710 &if_endislessthanstart, &out); | |
| 1711 | |
| 1712 Bind(&if_endislessthanstart); | |
| 1713 { | |
| 1714 Node* const tmp = var_end.value(); | |
| 1715 var_end.Bind(var_start.value()); | |
| 1716 var_start.Bind(tmp); | |
| 1717 Goto(&out); | |
| 1718 } | |
| 1719 } | |
| 1720 | |
| 1721 Bind(&out); | |
| 1722 { | |
| 1723 Node* result = | |
| 1724 SubString(context, string, var_start.value(), var_end.value()); | |
| 1725 Return(result); | |
| 1726 } | |
| 1727 } | |
| 1728 | |
| 1729 BUILTIN(StringPrototypeStartsWith) { | 301 BUILTIN(StringPrototypeStartsWith) { |
| 1730 HandleScope handle_scope(isolate); | 302 HandleScope handle_scope(isolate); |
| 1731 TO_THIS_STRING(str, "String.prototype.startsWith"); | 303 TO_THIS_STRING(str, "String.prototype.startsWith"); |
| 1732 | 304 |
| 1733 // Check if the search string is a regExp and fail if it is. | 305 // Check if the search string is a regExp and fail if it is. |
| 1734 Handle<Object> search = args.atOrUndefined(isolate, 1); | 306 Handle<Object> search = args.atOrUndefined(isolate, 1); |
| 1735 Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search); | 307 Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search); |
| 1736 if (is_reg_exp.IsNothing()) { | 308 if (is_reg_exp.IsNothing()) { |
| 1737 DCHECK(isolate->has_pending_exception()); | 309 DCHECK(isolate->has_pending_exception()); |
| 1738 return isolate->heap()->exception(); | 310 return isolate->heap()->exception(); |
| (...skipping 27 matching lines...) Expand all Loading... |
| 1766 FlatStringReader search_reader(isolate, String::Flatten(search_string)); | 338 FlatStringReader search_reader(isolate, String::Flatten(search_string)); |
| 1767 | 339 |
| 1768 for (int i = 0; i < search_string->length(); i++) { | 340 for (int i = 0; i < search_string->length(); i++) { |
| 1769 if (str_reader.Get(start + i) != search_reader.Get(i)) { | 341 if (str_reader.Get(start + i) != search_reader.Get(i)) { |
| 1770 return isolate->heap()->false_value(); | 342 return isolate->heap()->false_value(); |
| 1771 } | 343 } |
| 1772 } | 344 } |
| 1773 return isolate->heap()->true_value(); | 345 return isolate->heap()->true_value(); |
| 1774 } | 346 } |
| 1775 | 347 |
| 1776 // ES6 section 21.1.3.25 String.prototype.toString () | |
| 1777 TF_BUILTIN(StringPrototypeToString, CodeStubAssembler) { | |
| 1778 Node* receiver = Parameter(0); | |
| 1779 Node* context = Parameter(3); | |
| 1780 | |
| 1781 Node* result = ToThisValue(context, receiver, PrimitiveType::kString, | |
| 1782 "String.prototype.toString"); | |
| 1783 Return(result); | |
| 1784 } | |
| 1785 | |
| 1786 // ES6 section 21.1.3.27 String.prototype.trim () | 348 // ES6 section 21.1.3.27 String.prototype.trim () |
| 1787 BUILTIN(StringPrototypeTrim) { | 349 BUILTIN(StringPrototypeTrim) { |
| 1788 HandleScope scope(isolate); | 350 HandleScope scope(isolate); |
| 1789 TO_THIS_STRING(string, "String.prototype.trim"); | 351 TO_THIS_STRING(string, "String.prototype.trim"); |
| 1790 return *String::Trim(string, String::kTrim); | 352 return *String::Trim(string, String::kTrim); |
| 1791 } | 353 } |
| 1792 | 354 |
| 1793 // Non-standard WebKit extension | 355 // Non-standard WebKit extension |
| 1794 BUILTIN(StringPrototypeTrimLeft) { | 356 BUILTIN(StringPrototypeTrimLeft) { |
| 1795 HandleScope scope(isolate); | 357 HandleScope scope(isolate); |
| 1796 TO_THIS_STRING(string, "String.prototype.trimLeft"); | 358 TO_THIS_STRING(string, "String.prototype.trimLeft"); |
| 1797 return *String::Trim(string, String::kTrimLeft); | 359 return *String::Trim(string, String::kTrimLeft); |
| 1798 } | 360 } |
| 1799 | 361 |
| 1800 // Non-standard WebKit extension | 362 // Non-standard WebKit extension |
| 1801 BUILTIN(StringPrototypeTrimRight) { | 363 BUILTIN(StringPrototypeTrimRight) { |
| 1802 HandleScope scope(isolate); | 364 HandleScope scope(isolate); |
| 1803 TO_THIS_STRING(string, "String.prototype.trimRight"); | 365 TO_THIS_STRING(string, "String.prototype.trimRight"); |
| 1804 return *String::Trim(string, String::kTrimRight); | 366 return *String::Trim(string, String::kTrimRight); |
| 1805 } | 367 } |
| 1806 | 368 |
| 1807 // ES6 section 21.1.3.28 String.prototype.valueOf ( ) | |
| 1808 TF_BUILTIN(StringPrototypeValueOf, CodeStubAssembler) { | |
| 1809 Node* receiver = Parameter(0); | |
| 1810 Node* context = Parameter(3); | |
| 1811 | |
| 1812 Node* result = ToThisValue(context, receiver, PrimitiveType::kString, | |
| 1813 "String.prototype.valueOf"); | |
| 1814 Return(result); | |
| 1815 } | |
| 1816 | |
| 1817 TF_BUILTIN(StringPrototypeIterator, CodeStubAssembler) { | |
| 1818 Node* receiver = Parameter(0); | |
| 1819 Node* context = Parameter(3); | |
| 1820 | |
| 1821 Node* string = | |
| 1822 ToThisString(context, receiver, "String.prototype[Symbol.iterator]"); | |
| 1823 | |
| 1824 Node* native_context = LoadNativeContext(context); | |
| 1825 Node* map = | |
| 1826 LoadContextElement(native_context, Context::STRING_ITERATOR_MAP_INDEX); | |
| 1827 Node* iterator = Allocate(JSStringIterator::kSize); | |
| 1828 StoreMapNoWriteBarrier(iterator, map); | |
| 1829 StoreObjectFieldRoot(iterator, JSValue::kPropertiesOffset, | |
| 1830 Heap::kEmptyFixedArrayRootIndex); | |
| 1831 StoreObjectFieldRoot(iterator, JSObject::kElementsOffset, | |
| 1832 Heap::kEmptyFixedArrayRootIndex); | |
| 1833 StoreObjectFieldNoWriteBarrier(iterator, JSStringIterator::kStringOffset, | |
| 1834 string); | |
| 1835 Node* index = SmiConstant(Smi::kZero); | |
| 1836 StoreObjectFieldNoWriteBarrier(iterator, JSStringIterator::kNextIndexOffset, | |
| 1837 index); | |
| 1838 Return(iterator); | |
| 1839 } | |
| 1840 | |
| 1841 // Return the |word32| codepoint at {index}. Supports SeqStrings and | |
| 1842 // ExternalStrings. | |
| 1843 compiler::Node* StringBuiltinsAssembler::LoadSurrogatePairAt( | |
| 1844 compiler::Node* string, compiler::Node* length, compiler::Node* index, | |
| 1845 UnicodeEncoding encoding) { | |
| 1846 Label handle_surrogate_pair(this), return_result(this); | |
| 1847 Variable var_result(this, MachineRepresentation::kWord32); | |
| 1848 Variable var_trail(this, MachineRepresentation::kWord32); | |
| 1849 var_result.Bind(StringCharCodeAt(string, index)); | |
| 1850 var_trail.Bind(Int32Constant(0)); | |
| 1851 | |
| 1852 GotoIf(Word32NotEqual(Word32And(var_result.value(), Int32Constant(0xFC00)), | |
| 1853 Int32Constant(0xD800)), | |
| 1854 &return_result); | |
| 1855 Node* next_index = SmiAdd(index, SmiConstant(Smi::FromInt(1))); | |
| 1856 | |
| 1857 GotoIfNot(SmiLessThan(next_index, length), &return_result); | |
| 1858 var_trail.Bind(StringCharCodeAt(string, next_index)); | |
| 1859 Branch(Word32Equal(Word32And(var_trail.value(), Int32Constant(0xFC00)), | |
| 1860 Int32Constant(0xDC00)), | |
| 1861 &handle_surrogate_pair, &return_result); | |
| 1862 | |
| 1863 Bind(&handle_surrogate_pair); | |
| 1864 { | |
| 1865 Node* lead = var_result.value(); | |
| 1866 Node* trail = var_trail.value(); | |
| 1867 | |
| 1868 // Check that this path is only taken if a surrogate pair is found | |
| 1869 CSA_SLOW_ASSERT(this, | |
| 1870 Uint32GreaterThanOrEqual(lead, Int32Constant(0xD800))); | |
| 1871 CSA_SLOW_ASSERT(this, Uint32LessThan(lead, Int32Constant(0xDC00))); | |
| 1872 CSA_SLOW_ASSERT(this, | |
| 1873 Uint32GreaterThanOrEqual(trail, Int32Constant(0xDC00))); | |
| 1874 CSA_SLOW_ASSERT(this, Uint32LessThan(trail, Int32Constant(0xE000))); | |
| 1875 | |
| 1876 switch (encoding) { | |
| 1877 case UnicodeEncoding::UTF16: | |
| 1878 var_result.Bind(Word32Or( | |
| 1879 // Need to swap the order for big-endian platforms | |
| 1880 #if V8_TARGET_BIG_ENDIAN | |
| 1881 Word32Shl(lead, Int32Constant(16)), trail)); | |
| 1882 #else | |
| 1883 Word32Shl(trail, Int32Constant(16)), lead)); | |
| 1884 #endif | |
| 1885 break; | |
| 1886 | |
| 1887 case UnicodeEncoding::UTF32: { | |
| 1888 // Convert UTF16 surrogate pair into |word32| code point, encoded as | |
| 1889 // UTF32. | |
| 1890 Node* surrogate_offset = | |
| 1891 Int32Constant(0x10000 - (0xD800 << 10) - 0xDC00); | |
| 1892 | |
| 1893 // (lead << 10) + trail + SURROGATE_OFFSET | |
| 1894 var_result.Bind(Int32Add(WordShl(lead, Int32Constant(10)), | |
| 1895 Int32Add(trail, surrogate_offset))); | |
| 1896 break; | |
| 1897 } | |
| 1898 } | |
| 1899 Goto(&return_result); | |
| 1900 } | |
| 1901 | |
| 1902 Bind(&return_result); | |
| 1903 return var_result.value(); | |
| 1904 } | |
| 1905 | |
| 1906 TF_BUILTIN(StringIteratorPrototypeNext, StringBuiltinsAssembler) { | |
| 1907 Variable var_value(this, MachineRepresentation::kTagged); | |
| 1908 Variable var_done(this, MachineRepresentation::kTagged); | |
| 1909 | |
| 1910 var_value.Bind(UndefinedConstant()); | |
| 1911 var_done.Bind(BooleanConstant(true)); | |
| 1912 | |
| 1913 Label throw_bad_receiver(this), next_codepoint(this), return_result(this); | |
| 1914 | |
| 1915 Node* iterator = Parameter(0); | |
| 1916 Node* context = Parameter(3); | |
| 1917 | |
| 1918 GotoIf(TaggedIsSmi(iterator), &throw_bad_receiver); | |
| 1919 GotoIfNot(Word32Equal(LoadInstanceType(iterator), | |
| 1920 Int32Constant(JS_STRING_ITERATOR_TYPE)), | |
| 1921 &throw_bad_receiver); | |
| 1922 | |
| 1923 Node* string = LoadObjectField(iterator, JSStringIterator::kStringOffset); | |
| 1924 Node* position = | |
| 1925 LoadObjectField(iterator, JSStringIterator::kNextIndexOffset); | |
| 1926 Node* length = LoadObjectField(string, String::kLengthOffset); | |
| 1927 | |
| 1928 Branch(SmiLessThan(position, length), &next_codepoint, &return_result); | |
| 1929 | |
| 1930 Bind(&next_codepoint); | |
| 1931 { | |
| 1932 UnicodeEncoding encoding = UnicodeEncoding::UTF16; | |
| 1933 Node* ch = LoadSurrogatePairAt(string, length, position, encoding); | |
| 1934 Node* value = StringFromCodePoint(ch, encoding); | |
| 1935 var_value.Bind(value); | |
| 1936 Node* length = LoadObjectField(value, String::kLengthOffset); | |
| 1937 StoreObjectFieldNoWriteBarrier(iterator, JSStringIterator::kNextIndexOffset, | |
| 1938 SmiAdd(position, length)); | |
| 1939 var_done.Bind(BooleanConstant(false)); | |
| 1940 Goto(&return_result); | |
| 1941 } | |
| 1942 | |
| 1943 Bind(&return_result); | |
| 1944 { | |
| 1945 Node* native_context = LoadNativeContext(context); | |
| 1946 Node* map = | |
| 1947 LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX); | |
| 1948 Node* result = Allocate(JSIteratorResult::kSize); | |
| 1949 StoreMapNoWriteBarrier(result, map); | |
| 1950 StoreObjectFieldRoot(result, JSIteratorResult::kPropertiesOffset, | |
| 1951 Heap::kEmptyFixedArrayRootIndex); | |
| 1952 StoreObjectFieldRoot(result, JSIteratorResult::kElementsOffset, | |
| 1953 Heap::kEmptyFixedArrayRootIndex); | |
| 1954 StoreObjectFieldNoWriteBarrier(result, JSIteratorResult::kValueOffset, | |
| 1955 var_value.value()); | |
| 1956 StoreObjectFieldNoWriteBarrier(result, JSIteratorResult::kDoneOffset, | |
| 1957 var_done.value()); | |
| 1958 Return(result); | |
| 1959 } | |
| 1960 | |
| 1961 Bind(&throw_bad_receiver); | |
| 1962 { | |
| 1963 // The {receiver} is not a valid JSGeneratorObject. | |
| 1964 CallRuntime(Runtime::kThrowIncompatibleMethodReceiver, context, | |
| 1965 HeapConstant(factory()->NewStringFromAsciiChecked( | |
| 1966 "String Iterator.prototype.next", TENURED)), | |
| 1967 iterator); | |
| 1968 Unreachable(); | |
| 1969 } | |
| 1970 } | |
| 1971 | |
| 1972 namespace { | 369 namespace { |
| 1973 | 370 |
| 1974 inline bool ToUpperOverflows(uc32 character) { | 371 inline bool ToUpperOverflows(uc32 character) { |
| 1975 // y with umlauts and the micro sign are the only characters that stop | 372 // y with umlauts and the micro sign are the only characters that stop |
| 1976 // fitting into one-byte when converting to uppercase. | 373 // fitting into one-byte when converting to uppercase. |
| 1977 static const uc32 yuml_code = 0xff; | 374 static const uc32 yuml_code = 0xff; |
| 1978 static const uc32 micro_code = 0xb5; | 375 static const uc32 micro_code = 0xb5; |
| 1979 return (character == yuml_code || character == micro_code); | 376 return (character == yuml_code || character == micro_code); |
| 1980 } | 377 } |
| 1981 | 378 |
| (...skipping 174 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2156 | 553 |
| 2157 BUILTIN(StringPrototypeToUpperCase) { | 554 BUILTIN(StringPrototypeToUpperCase) { |
| 2158 HandleScope scope(isolate); | 555 HandleScope scope(isolate); |
| 2159 TO_THIS_STRING(string, "String.prototype.toUpperCase"); | 556 TO_THIS_STRING(string, "String.prototype.toUpperCase"); |
| 2160 return ConvertCase(string, isolate, | 557 return ConvertCase(string, isolate, |
| 2161 isolate->runtime_state()->to_upper_mapping()); | 558 isolate->runtime_state()->to_upper_mapping()); |
| 2162 } | 559 } |
| 2163 | 560 |
| 2164 } // namespace internal | 561 } // namespace internal |
| 2165 } // namespace v8 | 562 } // namespace v8 |
| OLD | NEW |