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 |