Line data Source code
1 : // Copyright 2017 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #include "src/builtins/builtins-regexp-gen.h"
6 : #include "src/builtins/builtins-utils-gen.h"
7 : #include "src/builtins/builtins.h"
8 : #include "src/code-factory.h"
9 : #include "src/code-stub-assembler.h"
10 : #include "src/objects.h"
11 :
12 : namespace v8 {
13 : namespace internal {
14 :
15 : typedef CodeStubAssembler::RelationalComparisonMode RelationalComparisonMode;
16 :
17 : class StringBuiltinsAssembler : public CodeStubAssembler {
18 : public:
19 : explicit StringBuiltinsAssembler(compiler::CodeAssemblerState* state)
20 473 : : CodeStubAssembler(state) {}
21 :
22 : // ES#sec-getsubstitution
23 : Node* GetSubstitution(Node* context, Node* subject_string,
24 : Node* match_start_index, Node* match_end_index,
25 : Node* replace_string);
26 :
27 : protected:
28 86 : Node* DirectStringData(Node* string, Node* string_instance_type) {
29 : // Compute the effective offset of the first character.
30 86 : VARIABLE(var_data, MachineType::PointerRepresentation());
31 86 : Label if_sequential(this), if_external(this), if_join(this);
32 : Branch(Word32Equal(Word32And(string_instance_type,
33 : Int32Constant(kStringRepresentationMask)),
34 : Int32Constant(kSeqStringTag)),
35 86 : &if_sequential, &if_external);
36 :
37 86 : BIND(&if_sequential);
38 : {
39 : var_data.Bind(IntPtrAdd(
40 : IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag),
41 86 : BitcastTaggedToWord(string)));
42 86 : Goto(&if_join);
43 : }
44 :
45 86 : BIND(&if_external);
46 : {
47 : // This is only valid for ExternalStrings where the resource data
48 : // pointer is cached (i.e. no short external strings).
49 : CSA_ASSERT(this, Word32NotEqual(
50 : Word32And(string_instance_type,
51 : Int32Constant(kShortExternalStringMask)),
52 : Int32Constant(kShortExternalStringTag)));
53 : var_data.Bind(LoadObjectField(string, ExternalString::kResourceDataOffset,
54 86 : MachineType::Pointer()));
55 86 : Goto(&if_join);
56 : }
57 :
58 86 : BIND(&if_join);
59 172 : return var_data.value();
60 : }
61 :
62 86 : void DispatchOnStringEncodings(Node* const lhs_instance_type,
63 : Node* const rhs_instance_type,
64 : Label* if_one_one, Label* if_one_two,
65 : Label* if_two_one, Label* if_two_two) {
66 : STATIC_ASSERT(kStringEncodingMask == 0x8);
67 : STATIC_ASSERT(kTwoByteStringTag == 0x0);
68 : STATIC_ASSERT(kOneByteStringTag == 0x8);
69 :
70 : // First combine the encodings.
71 :
72 86 : Node* const encoding_mask = Int32Constant(kStringEncodingMask);
73 86 : Node* const lhs_encoding = Word32And(lhs_instance_type, encoding_mask);
74 86 : Node* const rhs_encoding = Word32And(rhs_instance_type, encoding_mask);
75 :
76 : Node* const combined_encodings =
77 86 : Word32Or(lhs_encoding, Word32Shr(rhs_encoding, 1));
78 :
79 : // Then dispatch on the combined encoding.
80 :
81 : Label unreachable(this, Label::kDeferred);
82 :
83 : int32_t values[] = {
84 : kOneByteStringTag | (kOneByteStringTag >> 1),
85 : kOneByteStringTag | (kTwoByteStringTag >> 1),
86 : kTwoByteStringTag | (kOneByteStringTag >> 1),
87 : kTwoByteStringTag | (kTwoByteStringTag >> 1),
88 86 : };
89 : Label* labels[] = {
90 : if_one_one, if_one_two, if_two_one, if_two_two,
91 86 : };
92 :
93 : STATIC_ASSERT(arraysize(values) == arraysize(labels));
94 86 : Switch(combined_encodings, &unreachable, values, labels, arraysize(values));
95 :
96 86 : BIND(&unreachable);
97 86 : Unreachable();
98 86 : }
99 :
100 : template <typename SubjectChar, typename PatternChar>
101 344 : Node* CallSearchStringRaw(Node* const subject_ptr, Node* const subject_length,
102 : Node* const search_ptr, Node* const search_length,
103 : Node* const start_position) {
104 : Node* const function_addr = ExternalConstant(
105 : ExternalReference::search_string_raw<SubjectChar, PatternChar>(
106 344 : isolate()));
107 : Node* const isolate_ptr =
108 344 : ExternalConstant(ExternalReference::isolate_address(isolate()));
109 :
110 344 : MachineType type_ptr = MachineType::Pointer();
111 344 : MachineType type_intptr = MachineType::IntPtr();
112 :
113 : Node* const result = CallCFunction6(
114 : type_intptr, type_ptr, type_ptr, type_intptr, type_ptr, type_intptr,
115 : type_intptr, function_addr, isolate_ptr, subject_ptr, subject_length,
116 344 : search_ptr, search_length, start_position);
117 :
118 344 : return result;
119 : }
120 :
121 688 : Node* PointerToStringDataAtIndex(Node* const string_data, Node* const index,
122 : String::Encoding encoding) {
123 : const ElementsKind kind = (encoding == String::ONE_BYTE_ENCODING)
124 : ? UINT8_ELEMENTS
125 688 : : UINT16_ELEMENTS;
126 :
127 : Node* const offset_in_bytes =
128 688 : ElementOffsetFromIndex(index, kind, INTPTR_PARAMETERS);
129 688 : return IntPtrAdd(string_data, offset_in_bytes);
130 : }
131 :
132 : void GenerateStringEqual(Node* context, Node* left, Node* right);
133 : void GenerateStringRelationalComparison(Node* context, Node* left,
134 : Node* right,
135 : RelationalComparisonMode mode);
136 :
137 : Node* ToSmiBetweenZeroAnd(Node* context, Node* value, Node* limit);
138 :
139 : Node* LoadSurrogatePairAt(Node* string, Node* length, Node* index,
140 : UnicodeEncoding encoding);
141 :
142 : void StringIndexOf(Node* const subject_string,
143 : Node* const subject_instance_type,
144 : Node* const search_string,
145 : Node* const search_instance_type, Node* const position,
146 : std::function<void(Node*)> f_return);
147 :
148 : Node* IndexOfDollarChar(Node* const context, Node* const string);
149 :
150 : Node* IsNullOrUndefined(Node* const value);
151 : void RequireObjectCoercible(Node* const context, Node* const value,
152 : const char* method_name);
153 :
154 86 : Node* SmiIsNegative(Node* const value) {
155 86 : return SmiLessThan(value, SmiConstant(0));
156 : }
157 :
158 : // Implements boilerplate logic for {match, split, replace, search} of the
159 : // form:
160 : //
161 : // if (!IS_NULL_OR_UNDEFINED(object)) {
162 : // var maybe_function = object[symbol];
163 : // if (!IS_UNDEFINED(maybe_function)) {
164 : // return %_Call(maybe_function, ...);
165 : // }
166 : // }
167 : //
168 : // Contains fast paths for Smi and RegExp objects.
169 : typedef std::function<Node*()> NodeFunction0;
170 : typedef std::function<Node*(Node* fn)> NodeFunction1;
171 : void MaybeCallFunctionAtSymbol(Node* const context, Node* const object,
172 : Handle<Symbol> symbol,
173 : const NodeFunction0& regexp_call,
174 : const NodeFunction1& generic_call);
175 : };
176 :
177 43 : void StringBuiltinsAssembler::GenerateStringEqual(Node* context, Node* left,
178 : Node* right) {
179 : // Here's pseudo-code for the algorithm below:
180 : //
181 : // if (lhs == rhs) return true;
182 : // if (lhs->length() != rhs->length()) return false;
183 : // if (lhs->IsInternalizedString() && rhs->IsInternalizedString()) {
184 : // return false;
185 : // }
186 : // if (lhs->IsSeqOneByteString() && rhs->IsSeqOneByteString()) {
187 : // for (i = 0; i != lhs->length(); ++i) {
188 : // if (lhs[i] != rhs[i]) return false;
189 : // }
190 : // return true;
191 : // }
192 : // if (lhs and/or rhs are indirect strings) {
193 : // unwrap them and restart from the beginning;
194 : // }
195 : // return %StringEqual(lhs, rhs);
196 :
197 43 : VARIABLE(var_left, MachineRepresentation::kTagged, left);
198 86 : VARIABLE(var_right, MachineRepresentation::kTagged, right);
199 :
200 43 : Variable* input_vars[2] = {&var_left, &var_right};
201 86 : Label if_equal(this), if_notequal(this), restart(this, 2, input_vars);
202 43 : Goto(&restart);
203 43 : BIND(&restart);
204 43 : Node* lhs = var_left.value();
205 43 : Node* rhs = var_right.value();
206 :
207 : // Fast check to see if {lhs} and {rhs} refer to the same String object.
208 43 : GotoIf(WordEqual(lhs, rhs), &if_equal);
209 :
210 : // Load the length of {lhs} and {rhs}.
211 43 : Node* lhs_length = LoadStringLength(lhs);
212 43 : Node* rhs_length = LoadStringLength(rhs);
213 :
214 : // Strings with different lengths cannot be equal.
215 43 : GotoIf(WordNotEqual(lhs_length, rhs_length), &if_notequal);
216 :
217 : // Load instance types of {lhs} and {rhs}.
218 43 : Node* lhs_instance_type = LoadInstanceType(lhs);
219 43 : Node* rhs_instance_type = LoadInstanceType(rhs);
220 :
221 : // Combine the instance types into a single 16-bit value, so we can check
222 : // both of them at once.
223 : Node* both_instance_types = Word32Or(
224 43 : lhs_instance_type, Word32Shl(rhs_instance_type, Int32Constant(8)));
225 :
226 : // Check if both {lhs} and {rhs} are internalized. Since we already know
227 : // that they're not the same object, they're not equal in that case.
228 : int const kBothInternalizedMask =
229 : kIsNotInternalizedMask | (kIsNotInternalizedMask << 8);
230 : int const kBothInternalizedTag = kInternalizedTag | (kInternalizedTag << 8);
231 : GotoIf(Word32Equal(Word32And(both_instance_types,
232 : Int32Constant(kBothInternalizedMask)),
233 : Int32Constant(kBothInternalizedTag)),
234 43 : &if_notequal);
235 :
236 : // Check that both {lhs} and {rhs} are flat one-byte strings, and that
237 : // in case of ExternalStrings the data pointer is cached..
238 : STATIC_ASSERT(kShortExternalStringTag != 0);
239 : int const kBothDirectOneByteStringMask =
240 : kStringEncodingMask | kIsIndirectStringMask | kShortExternalStringMask |
241 : ((kStringEncodingMask | kIsIndirectStringMask | kShortExternalStringMask)
242 : << 8);
243 : int const kBothDirectOneByteStringTag =
244 : kOneByteStringTag | (kOneByteStringTag << 8);
245 43 : Label if_bothdirectonebytestrings(this), if_notbothdirectonebytestrings(this);
246 : Branch(Word32Equal(Word32And(both_instance_types,
247 : Int32Constant(kBothDirectOneByteStringMask)),
248 : Int32Constant(kBothDirectOneByteStringTag)),
249 43 : &if_bothdirectonebytestrings, &if_notbothdirectonebytestrings);
250 :
251 43 : BIND(&if_bothdirectonebytestrings);
252 : {
253 : // Compute the effective offset of the first character.
254 43 : Node* lhs_data = DirectStringData(lhs, lhs_instance_type);
255 43 : Node* rhs_data = DirectStringData(rhs, rhs_instance_type);
256 :
257 : // Compute the first offset after the string from the length.
258 43 : Node* length = SmiUntag(lhs_length);
259 :
260 : // Loop over the {lhs} and {rhs} strings to see if they are equal.
261 43 : VARIABLE(var_offset, MachineType::PointerRepresentation());
262 43 : Label loop(this, &var_offset);
263 43 : var_offset.Bind(IntPtrConstant(0));
264 43 : Goto(&loop);
265 43 : BIND(&loop);
266 : {
267 : // If {offset} equals {end}, no difference was found, so the
268 : // strings are equal.
269 43 : Node* offset = var_offset.value();
270 43 : GotoIf(WordEqual(offset, length), &if_equal);
271 :
272 : // Load the next characters from {lhs} and {rhs}.
273 43 : Node* lhs_value = Load(MachineType::Uint8(), lhs_data, offset);
274 43 : Node* rhs_value = Load(MachineType::Uint8(), rhs_data, offset);
275 :
276 : // Check if the characters match.
277 43 : GotoIf(Word32NotEqual(lhs_value, rhs_value), &if_notequal);
278 :
279 : // Advance to next character.
280 43 : var_offset.Bind(IntPtrAdd(offset, IntPtrConstant(1)));
281 43 : Goto(&loop);
282 43 : }
283 : }
284 :
285 43 : BIND(&if_notbothdirectonebytestrings);
286 : {
287 : // Try to unwrap indirect strings, restart the above attempt on success.
288 : MaybeDerefIndirectStrings(&var_left, lhs_instance_type, &var_right,
289 43 : rhs_instance_type, &restart);
290 : // TODO(bmeurer): Add support for two byte string equality checks.
291 :
292 43 : TailCallRuntime(Runtime::kStringEqual, context, lhs, rhs);
293 : }
294 :
295 43 : BIND(&if_equal);
296 43 : Return(TrueConstant());
297 :
298 43 : BIND(&if_notequal);
299 86 : Return(FalseConstant());
300 43 : }
301 :
302 172 : void StringBuiltinsAssembler::GenerateStringRelationalComparison(
303 : Node* context, Node* left, Node* right, RelationalComparisonMode mode) {
304 172 : VARIABLE(var_left, MachineRepresentation::kTagged, left);
305 344 : VARIABLE(var_right, MachineRepresentation::kTagged, right);
306 :
307 172 : Variable* input_vars[2] = {&var_left, &var_right};
308 172 : Label if_less(this), if_equal(this), if_greater(this);
309 344 : Label restart(this, 2, input_vars);
310 172 : Goto(&restart);
311 172 : BIND(&restart);
312 :
313 172 : Node* lhs = var_left.value();
314 172 : Node* rhs = var_right.value();
315 : // Fast check to see if {lhs} and {rhs} refer to the same String object.
316 172 : GotoIf(WordEqual(lhs, rhs), &if_equal);
317 :
318 : // Load instance types of {lhs} and {rhs}.
319 172 : Node* lhs_instance_type = LoadInstanceType(lhs);
320 172 : Node* rhs_instance_type = LoadInstanceType(rhs);
321 :
322 : // Combine the instance types into a single 16-bit value, so we can check
323 : // both of them at once.
324 : Node* both_instance_types = Word32Or(
325 172 : lhs_instance_type, Word32Shl(rhs_instance_type, Int32Constant(8)));
326 :
327 : // Check that both {lhs} and {rhs} are flat one-byte strings.
328 : int const kBothSeqOneByteStringMask =
329 : kStringEncodingMask | kStringRepresentationMask |
330 : ((kStringEncodingMask | kStringRepresentationMask) << 8);
331 : int const kBothSeqOneByteStringTag =
332 : kOneByteStringTag | kSeqStringTag |
333 : ((kOneByteStringTag | kSeqStringTag) << 8);
334 172 : Label if_bothonebyteseqstrings(this), if_notbothonebyteseqstrings(this);
335 : Branch(Word32Equal(Word32And(both_instance_types,
336 : Int32Constant(kBothSeqOneByteStringMask)),
337 : Int32Constant(kBothSeqOneByteStringTag)),
338 172 : &if_bothonebyteseqstrings, &if_notbothonebyteseqstrings);
339 :
340 172 : BIND(&if_bothonebyteseqstrings);
341 : {
342 : // Load the length of {lhs} and {rhs}.
343 172 : Node* lhs_length = LoadStringLength(lhs);
344 172 : Node* rhs_length = LoadStringLength(rhs);
345 :
346 : // Determine the minimum length.
347 172 : Node* length = SmiMin(lhs_length, rhs_length);
348 :
349 : // Compute the effective offset of the first character.
350 : Node* begin =
351 172 : IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag);
352 :
353 : // Compute the first offset after the string from the length.
354 172 : Node* end = IntPtrAdd(begin, SmiUntag(length));
355 :
356 : // Loop over the {lhs} and {rhs} strings to see if they are equal.
357 172 : VARIABLE(var_offset, MachineType::PointerRepresentation());
358 172 : Label loop(this, &var_offset);
359 172 : var_offset.Bind(begin);
360 172 : Goto(&loop);
361 172 : BIND(&loop);
362 : {
363 : // Check if {offset} equals {end}.
364 172 : Node* offset = var_offset.value();
365 172 : Label if_done(this), if_notdone(this);
366 172 : Branch(WordEqual(offset, end), &if_done, &if_notdone);
367 :
368 172 : BIND(&if_notdone);
369 : {
370 : // Load the next characters from {lhs} and {rhs}.
371 172 : Node* lhs_value = Load(MachineType::Uint8(), lhs, offset);
372 172 : Node* rhs_value = Load(MachineType::Uint8(), rhs, offset);
373 :
374 : // Check if the characters match.
375 172 : Label if_valueissame(this), if_valueisnotsame(this);
376 : Branch(Word32Equal(lhs_value, rhs_value), &if_valueissame,
377 172 : &if_valueisnotsame);
378 :
379 172 : BIND(&if_valueissame);
380 : {
381 : // Advance to next character.
382 172 : var_offset.Bind(IntPtrAdd(offset, IntPtrConstant(1)));
383 : }
384 172 : Goto(&loop);
385 :
386 172 : BIND(&if_valueisnotsame);
387 344 : Branch(Uint32LessThan(lhs_value, rhs_value), &if_less, &if_greater);
388 : }
389 :
390 172 : BIND(&if_done);
391 : {
392 : // All characters up to the min length are equal, decide based on
393 : // string length.
394 172 : GotoIf(SmiEqual(lhs_length, rhs_length), &if_equal);
395 172 : BranchIfSmiLessThan(lhs_length, rhs_length, &if_less, &if_greater);
396 172 : }
397 172 : }
398 : }
399 :
400 172 : BIND(&if_notbothonebyteseqstrings);
401 : {
402 : // Try to unwrap indirect strings, restart the above attempt on success.
403 : MaybeDerefIndirectStrings(&var_left, lhs_instance_type, &var_right,
404 172 : rhs_instance_type, &restart);
405 : // TODO(bmeurer): Add support for two byte string relational comparisons.
406 172 : switch (mode) {
407 : case RelationalComparisonMode::kLessThan:
408 43 : TailCallRuntime(Runtime::kStringLessThan, context, lhs, rhs);
409 43 : break;
410 : case RelationalComparisonMode::kLessThanOrEqual:
411 43 : TailCallRuntime(Runtime::kStringLessThanOrEqual, context, lhs, rhs);
412 43 : break;
413 : case RelationalComparisonMode::kGreaterThan:
414 43 : TailCallRuntime(Runtime::kStringGreaterThan, context, lhs, rhs);
415 43 : break;
416 : case RelationalComparisonMode::kGreaterThanOrEqual:
417 43 : TailCallRuntime(Runtime::kStringGreaterThanOrEqual, context, lhs, rhs);
418 43 : break;
419 : }
420 : }
421 :
422 172 : BIND(&if_less);
423 172 : switch (mode) {
424 : case RelationalComparisonMode::kLessThan:
425 : case RelationalComparisonMode::kLessThanOrEqual:
426 86 : Return(BooleanConstant(true));
427 86 : break;
428 :
429 : case RelationalComparisonMode::kGreaterThan:
430 : case RelationalComparisonMode::kGreaterThanOrEqual:
431 86 : Return(BooleanConstant(false));
432 86 : break;
433 : }
434 :
435 172 : BIND(&if_equal);
436 172 : switch (mode) {
437 : case RelationalComparisonMode::kLessThan:
438 : case RelationalComparisonMode::kGreaterThan:
439 86 : Return(BooleanConstant(false));
440 86 : break;
441 :
442 : case RelationalComparisonMode::kLessThanOrEqual:
443 : case RelationalComparisonMode::kGreaterThanOrEqual:
444 86 : Return(BooleanConstant(true));
445 86 : break;
446 : }
447 :
448 172 : BIND(&if_greater);
449 172 : switch (mode) {
450 : case RelationalComparisonMode::kLessThan:
451 : case RelationalComparisonMode::kLessThanOrEqual:
452 86 : Return(BooleanConstant(false));
453 86 : break;
454 :
455 : case RelationalComparisonMode::kGreaterThan:
456 : case RelationalComparisonMode::kGreaterThanOrEqual:
457 86 : Return(BooleanConstant(true));
458 86 : break;
459 172 : }
460 172 : }
461 :
462 172 : TF_BUILTIN(StringEqual, StringBuiltinsAssembler) {
463 : Node* context = Parameter(Descriptor::kContext);
464 : Node* left = Parameter(Descriptor::kLeft);
465 : Node* right = Parameter(Descriptor::kRight);
466 43 : GenerateStringEqual(context, left, right);
467 43 : }
468 :
469 172 : TF_BUILTIN(StringLessThan, StringBuiltinsAssembler) {
470 : Node* context = Parameter(Descriptor::kContext);
471 : Node* left = Parameter(Descriptor::kLeft);
472 : Node* right = Parameter(Descriptor::kRight);
473 : GenerateStringRelationalComparison(context, left, right,
474 43 : RelationalComparisonMode::kLessThan);
475 43 : }
476 :
477 172 : TF_BUILTIN(StringLessThanOrEqual, StringBuiltinsAssembler) {
478 : Node* context = Parameter(Descriptor::kContext);
479 : Node* left = Parameter(Descriptor::kLeft);
480 : Node* right = Parameter(Descriptor::kRight);
481 : GenerateStringRelationalComparison(
482 43 : context, left, right, RelationalComparisonMode::kLessThanOrEqual);
483 43 : }
484 :
485 172 : TF_BUILTIN(StringGreaterThan, StringBuiltinsAssembler) {
486 : Node* context = Parameter(Descriptor::kContext);
487 : Node* left = Parameter(Descriptor::kLeft);
488 : Node* right = Parameter(Descriptor::kRight);
489 : GenerateStringRelationalComparison(context, left, right,
490 43 : RelationalComparisonMode::kGreaterThan);
491 43 : }
492 :
493 172 : TF_BUILTIN(StringGreaterThanOrEqual, StringBuiltinsAssembler) {
494 : Node* context = Parameter(Descriptor::kContext);
495 : Node* left = Parameter(Descriptor::kLeft);
496 : Node* right = Parameter(Descriptor::kRight);
497 : GenerateStringRelationalComparison(
498 43 : context, left, right, RelationalComparisonMode::kGreaterThanOrEqual);
499 43 : }
500 :
501 129 : TF_BUILTIN(StringCharAt, CodeStubAssembler) {
502 : Node* receiver = Parameter(Descriptor::kReceiver);
503 : Node* position = Parameter(Descriptor::kPosition);
504 :
505 : // Load the character code at the {position} from the {receiver}.
506 43 : Node* code = StringCharCodeAt(receiver, position, INTPTR_PARAMETERS);
507 :
508 : // And return the single character string with only that {code}
509 43 : Node* result = StringFromCharCode(code);
510 43 : Return(result);
511 43 : }
512 :
513 129 : TF_BUILTIN(StringCharCodeAt, CodeStubAssembler) {
514 : Node* receiver = Parameter(Descriptor::kReceiver);
515 : Node* position = Parameter(Descriptor::kPosition);
516 :
517 : // Load the character code at the {position} from the {receiver}.
518 43 : Node* code = StringCharCodeAt(receiver, position, INTPTR_PARAMETERS);
519 :
520 : // And return it as TaggedSigned value.
521 : // TODO(turbofan): Allow builtins to return values untagged.
522 43 : Node* result = SmiFromWord32(code);
523 43 : Return(result);
524 43 : }
525 :
526 : // -----------------------------------------------------------------------------
527 : // ES6 section 21.1 String Objects
528 :
529 : // ES6 #sec-string.fromcharcode
530 129 : TF_BUILTIN(StringFromCharCode, CodeStubAssembler) {
531 : // TODO(ishell): use constants from Descriptor once the JSFunction linkage
532 : // arguments are reordered.
533 : Node* argc = Parameter(BuiltinDescriptor::kArgumentsCount);
534 : Node* context = Parameter(BuiltinDescriptor::kContext);
535 :
536 43 : CodeStubArguments arguments(this, ChangeInt32ToIntPtr(argc));
537 : // From now on use word-size argc value.
538 43 : argc = arguments.GetLength();
539 :
540 : // Check if we have exactly one argument (plus the implicit receiver), i.e.
541 : // if the parent frame is not an arguments adaptor frame.
542 43 : Label if_oneargument(this), if_notoneargument(this);
543 : Branch(WordEqual(argc, IntPtrConstant(1)), &if_oneargument,
544 43 : &if_notoneargument);
545 :
546 43 : BIND(&if_oneargument);
547 : {
548 : // Single argument case, perform fast single character string cache lookup
549 : // for one-byte code units, or fall back to creating a single character
550 : // string on the fly otherwise.
551 43 : Node* code = arguments.AtIndex(0);
552 43 : Node* code32 = TruncateTaggedToWord32(context, code);
553 43 : Node* code16 = Word32And(code32, Int32Constant(String::kMaxUtf16CodeUnit));
554 43 : Node* result = StringFromCharCode(code16);
555 43 : arguments.PopAndReturn(result);
556 : }
557 :
558 43 : Node* code16 = nullptr;
559 43 : BIND(&if_notoneargument);
560 : {
561 : Label two_byte(this);
562 : // Assume that the resulting string contains only one-byte characters.
563 43 : Node* one_byte_result = AllocateSeqOneByteString(context, argc);
564 :
565 86 : VARIABLE(max_index, MachineType::PointerRepresentation());
566 43 : max_index.Bind(IntPtrConstant(0));
567 :
568 : // Iterate over the incoming arguments, converting them to 8-bit character
569 : // codes. Stop if any of the conversions generates a code that doesn't fit
570 : // in 8 bits.
571 43 : CodeStubAssembler::VariableList vars({&max_index}, zone());
572 : arguments.ForEach(vars, [this, context, &two_byte, &max_index, &code16,
573 43 : one_byte_result](Node* arg) {
574 43 : Node* code32 = TruncateTaggedToWord32(context, arg);
575 43 : code16 = Word32And(code32, Int32Constant(String::kMaxUtf16CodeUnit));
576 :
577 : GotoIf(
578 : Int32GreaterThan(code16, Int32Constant(String::kMaxOneByteCharCode)),
579 43 : &two_byte);
580 :
581 : // The {code16} fits into the SeqOneByteString {one_byte_result}.
582 : Node* offset = ElementOffsetFromIndex(
583 : max_index.value(), UINT8_ELEMENTS,
584 : CodeStubAssembler::INTPTR_PARAMETERS,
585 43 : SeqOneByteString::kHeaderSize - kHeapObjectTag);
586 : StoreNoWriteBarrier(MachineRepresentation::kWord8, one_byte_result,
587 43 : offset, code16);
588 43 : max_index.Bind(IntPtrAdd(max_index.value(), IntPtrConstant(1)));
589 129 : });
590 43 : arguments.PopAndReturn(one_byte_result);
591 :
592 43 : BIND(&two_byte);
593 :
594 : // At least one of the characters in the string requires a 16-bit
595 : // representation. Allocate a SeqTwoByteString to hold the resulting
596 : // string.
597 43 : Node* two_byte_result = AllocateSeqTwoByteString(context, argc);
598 :
599 : // Copy the characters that have already been put in the 8-bit string into
600 : // their corresponding positions in the new 16-bit string.
601 43 : Node* zero = IntPtrConstant(0);
602 : CopyStringCharacters(one_byte_result, two_byte_result, zero, zero,
603 : max_index.value(), String::ONE_BYTE_ENCODING,
604 : String::TWO_BYTE_ENCODING,
605 43 : CodeStubAssembler::INTPTR_PARAMETERS);
606 :
607 : // Write the character that caused the 8-bit to 16-bit fault.
608 : Node* max_index_offset =
609 : ElementOffsetFromIndex(max_index.value(), UINT16_ELEMENTS,
610 : CodeStubAssembler::INTPTR_PARAMETERS,
611 43 : SeqTwoByteString::kHeaderSize - kHeapObjectTag);
612 : StoreNoWriteBarrier(MachineRepresentation::kWord16, two_byte_result,
613 43 : max_index_offset, code16);
614 43 : max_index.Bind(IntPtrAdd(max_index.value(), IntPtrConstant(1)));
615 :
616 : // Resume copying the passed-in arguments from the same place where the
617 : // 8-bit copy stopped, but this time copying over all of the characters
618 : // using a 16-bit representation.
619 : arguments.ForEach(
620 : vars,
621 43 : [this, context, two_byte_result, &max_index](Node* arg) {
622 43 : Node* code32 = TruncateTaggedToWord32(context, arg);
623 : Node* code16 =
624 43 : Word32And(code32, Int32Constant(String::kMaxUtf16CodeUnit));
625 :
626 : Node* offset = ElementOffsetFromIndex(
627 : max_index.value(), UINT16_ELEMENTS,
628 : CodeStubAssembler::INTPTR_PARAMETERS,
629 43 : SeqTwoByteString::kHeaderSize - kHeapObjectTag);
630 : StoreNoWriteBarrier(MachineRepresentation::kWord16, two_byte_result,
631 43 : offset, code16);
632 43 : max_index.Bind(IntPtrAdd(max_index.value(), IntPtrConstant(1)));
633 43 : },
634 129 : max_index.value());
635 :
636 86 : arguments.PopAndReturn(two_byte_result);
637 43 : }
638 43 : }
639 :
640 : // ES6 #sec-string.prototype.charat
641 129 : TF_BUILTIN(StringPrototypeCharAt, CodeStubAssembler) {
642 : Node* receiver = Parameter(Descriptor::kReceiver);
643 : Node* position = Parameter(Descriptor::kPosition);
644 : Node* context = Parameter(Descriptor::kContext);
645 :
646 : // Check that {receiver} is coercible to Object and convert it to a String.
647 43 : receiver = ToThisString(context, receiver, "String.prototype.charAt");
648 :
649 : // Convert the {position} to a Smi and check that it's in bounds of the
650 : // {receiver}.
651 : {
652 : Label return_emptystring(this, Label::kDeferred);
653 : position =
654 43 : ToInteger(context, position, CodeStubAssembler::kTruncateMinusZero);
655 43 : GotoIfNot(TaggedIsSmi(position), &return_emptystring);
656 :
657 : // Determine the actual length of the {receiver} String.
658 43 : Node* receiver_length = LoadObjectField(receiver, String::kLengthOffset);
659 :
660 : // Return "" if the Smi {position} is outside the bounds of the {receiver}.
661 43 : Label if_positioninbounds(this);
662 : Branch(SmiAboveOrEqual(position, receiver_length), &return_emptystring,
663 43 : &if_positioninbounds);
664 :
665 43 : BIND(&return_emptystring);
666 43 : Return(EmptyStringConstant());
667 :
668 86 : BIND(&if_positioninbounds);
669 : }
670 :
671 : // Load the character code at the {position} from the {receiver}.
672 43 : Node* code = StringCharCodeAt(receiver, position);
673 :
674 : // And return the single character string with only that {code}.
675 43 : Node* result = StringFromCharCode(code);
676 43 : Return(result);
677 43 : }
678 :
679 : // ES6 #sec-string.prototype.charcodeat
680 129 : TF_BUILTIN(StringPrototypeCharCodeAt, CodeStubAssembler) {
681 : Node* receiver = Parameter(Descriptor::kReceiver);
682 : Node* position = Parameter(Descriptor::kPosition);
683 : Node* context = Parameter(Descriptor::kContext);
684 :
685 : // Check that {receiver} is coercible to Object and convert it to a String.
686 43 : receiver = ToThisString(context, receiver, "String.prototype.charCodeAt");
687 :
688 : // Convert the {position} to a Smi and check that it's in bounds of the
689 : // {receiver}.
690 : {
691 : Label return_nan(this, Label::kDeferred);
692 : position =
693 43 : ToInteger(context, position, CodeStubAssembler::kTruncateMinusZero);
694 43 : GotoIfNot(TaggedIsSmi(position), &return_nan);
695 :
696 : // Determine the actual length of the {receiver} String.
697 43 : Node* receiver_length = LoadObjectField(receiver, String::kLengthOffset);
698 :
699 : // Return NaN if the Smi {position} is outside the bounds of the {receiver}.
700 43 : Label if_positioninbounds(this);
701 : Branch(SmiAboveOrEqual(position, receiver_length), &return_nan,
702 43 : &if_positioninbounds);
703 :
704 43 : BIND(&return_nan);
705 43 : Return(NaNConstant());
706 :
707 86 : BIND(&if_positioninbounds);
708 : }
709 :
710 : // Load the character at the {position} from the {receiver}.
711 43 : Node* value = StringCharCodeAt(receiver, position);
712 43 : Node* result = SmiFromWord32(value);
713 43 : Return(result);
714 43 : }
715 :
716 : // ES6 String.prototype.concat(...args)
717 : // ES6 #sec-string.prototype.concat
718 172 : TF_BUILTIN(StringPrototypeConcat, CodeStubAssembler) {
719 : // TODO(ishell): use constants from Descriptor once the JSFunction linkage
720 : // arguments are reordered.
721 : CodeStubArguments arguments(
722 43 : this, ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount)));
723 43 : Node* receiver = arguments.GetReceiver();
724 : Node* context = Parameter(BuiltinDescriptor::kContext);
725 :
726 : // Check that {receiver} is coercible to Object and convert it to a String.
727 43 : receiver = ToThisString(context, receiver, "String.prototype.concat");
728 :
729 : // Concatenate all the arguments passed to this builtin.
730 43 : VARIABLE(var_result, MachineRepresentation::kTagged);
731 43 : var_result.Bind(receiver);
732 : arguments.ForEach(
733 : CodeStubAssembler::VariableList({&var_result}, zone()),
734 43 : [this, context, &var_result](Node* arg) {
735 86 : arg = CallStub(CodeFactory::ToString(isolate()), context, arg);
736 : var_result.Bind(CallStub(CodeFactory::StringAdd(isolate()), context,
737 43 : var_result.value(), arg));
738 129 : });
739 43 : arguments.PopAndReturn(var_result.value());
740 43 : }
741 :
742 86 : void StringBuiltinsAssembler::StringIndexOf(
743 : Node* const subject_string, Node* const subject_instance_type,
744 : Node* const search_string, Node* const search_instance_type,
745 : Node* const position, std::function<void(Node*)> f_return) {
746 : CSA_ASSERT(this, IsString(subject_string));
747 : CSA_ASSERT(this, IsString(search_string));
748 : CSA_ASSERT(this, TaggedIsSmi(position));
749 :
750 86 : Node* const int_zero = IntPtrConstant(0);
751 :
752 86 : VARIABLE(var_needle_byte, MachineType::PointerRepresentation(), int_zero);
753 172 : VARIABLE(var_string_addr, MachineType::PointerRepresentation(), int_zero);
754 :
755 86 : Node* const search_length = SmiUntag(LoadStringLength(search_string));
756 86 : Node* const subject_length = SmiUntag(LoadStringLength(subject_string));
757 86 : Node* const start_position = IntPtrMax(SmiUntag(position), int_zero);
758 :
759 86 : Label zero_length_needle(this), return_minus_1(this);
760 : {
761 86 : GotoIf(IntPtrEqual(int_zero, search_length), &zero_length_needle);
762 :
763 : // Check that the needle fits in the start position.
764 : GotoIfNot(IntPtrLessThanOrEqual(search_length,
765 : IntPtrSub(subject_length, start_position)),
766 86 : &return_minus_1);
767 : }
768 :
769 : // Try to unpack subject and search strings. Bail to runtime if either needs
770 : // to be flattened.
771 172 : ToDirectStringAssembler subject_to_direct(state(), subject_string);
772 172 : ToDirectStringAssembler search_to_direct(state(), search_string);
773 :
774 86 : Label call_runtime_unchecked(this, Label::kDeferred);
775 :
776 86 : subject_to_direct.TryToDirect(&call_runtime_unchecked);
777 86 : search_to_direct.TryToDirect(&call_runtime_unchecked);
778 :
779 : // Load pointers to string data.
780 : Node* const subject_ptr =
781 : subject_to_direct.PointerToData(&call_runtime_unchecked);
782 : Node* const search_ptr =
783 : search_to_direct.PointerToData(&call_runtime_unchecked);
784 :
785 : Node* const subject_offset = subject_to_direct.offset();
786 : Node* const search_offset = search_to_direct.offset();
787 :
788 : // Like String::IndexOf, the actual matching is done by the optimized
789 : // SearchString method in string-search.h. Dispatch based on string instance
790 : // types, then call straight into C++ for matching.
791 :
792 : CSA_ASSERT(this, IntPtrGreaterThan(search_length, int_zero));
793 : CSA_ASSERT(this, IntPtrGreaterThanOrEqual(start_position, int_zero));
794 : CSA_ASSERT(this, IntPtrGreaterThanOrEqual(subject_length, start_position));
795 : CSA_ASSERT(this,
796 : IntPtrLessThanOrEqual(search_length,
797 : IntPtrSub(subject_length, start_position)));
798 :
799 86 : Label one_one(this), one_two(this), two_one(this), two_two(this);
800 : DispatchOnStringEncodings(subject_to_direct.instance_type(),
801 : search_to_direct.instance_type(), &one_one,
802 86 : &one_two, &two_one, &two_two);
803 :
804 : typedef const uint8_t onebyte_t;
805 : typedef const uc16 twobyte_t;
806 :
807 86 : BIND(&one_one);
808 : {
809 : Node* const adjusted_subject_ptr = PointerToStringDataAtIndex(
810 86 : subject_ptr, subject_offset, String::ONE_BYTE_ENCODING);
811 : Node* const adjusted_search_ptr = PointerToStringDataAtIndex(
812 86 : search_ptr, search_offset, String::ONE_BYTE_ENCODING);
813 :
814 86 : Label direct_memchr_call(this), generic_fast_path(this);
815 : Branch(IntPtrEqual(search_length, IntPtrConstant(1)), &direct_memchr_call,
816 86 : &generic_fast_path);
817 :
818 : // An additional fast path that calls directly into memchr for 1-length
819 : // search strings.
820 86 : BIND(&direct_memchr_call);
821 : {
822 86 : Node* const string_addr = IntPtrAdd(adjusted_subject_ptr, start_position);
823 86 : Node* const search_length = IntPtrSub(subject_length, start_position);
824 : Node* const search_byte =
825 86 : ChangeInt32ToIntPtr(Load(MachineType::Uint8(), adjusted_search_ptr));
826 :
827 : Node* const memchr =
828 86 : ExternalConstant(ExternalReference::libc_memchr_function(isolate()));
829 : Node* const result_address =
830 : CallCFunction3(MachineType::Pointer(), MachineType::Pointer(),
831 : MachineType::IntPtr(), MachineType::UintPtr(), memchr,
832 86 : string_addr, search_byte, search_length);
833 86 : GotoIf(WordEqual(result_address, int_zero), &return_minus_1);
834 : Node* const result_index =
835 86 : IntPtrAdd(IntPtrSub(result_address, string_addr), start_position);
836 86 : f_return(SmiTag(result_index));
837 : }
838 :
839 86 : BIND(&generic_fast_path);
840 : {
841 : Node* const result = CallSearchStringRaw<onebyte_t, onebyte_t>(
842 : adjusted_subject_ptr, subject_length, adjusted_search_ptr,
843 86 : search_length, start_position);
844 86 : f_return(SmiTag(result));
845 86 : }
846 : }
847 :
848 86 : BIND(&one_two);
849 : {
850 : Node* const adjusted_subject_ptr = PointerToStringDataAtIndex(
851 86 : subject_ptr, subject_offset, String::ONE_BYTE_ENCODING);
852 : Node* const adjusted_search_ptr = PointerToStringDataAtIndex(
853 86 : search_ptr, search_offset, String::TWO_BYTE_ENCODING);
854 :
855 : Node* const result = CallSearchStringRaw<onebyte_t, twobyte_t>(
856 : adjusted_subject_ptr, subject_length, adjusted_search_ptr,
857 86 : search_length, start_position);
858 86 : f_return(SmiTag(result));
859 : }
860 :
861 86 : BIND(&two_one);
862 : {
863 : Node* const adjusted_subject_ptr = PointerToStringDataAtIndex(
864 86 : subject_ptr, subject_offset, String::TWO_BYTE_ENCODING);
865 : Node* const adjusted_search_ptr = PointerToStringDataAtIndex(
866 86 : search_ptr, search_offset, String::ONE_BYTE_ENCODING);
867 :
868 : Node* const result = CallSearchStringRaw<twobyte_t, onebyte_t>(
869 : adjusted_subject_ptr, subject_length, adjusted_search_ptr,
870 86 : search_length, start_position);
871 86 : f_return(SmiTag(result));
872 : }
873 :
874 86 : BIND(&two_two);
875 : {
876 : Node* const adjusted_subject_ptr = PointerToStringDataAtIndex(
877 86 : subject_ptr, subject_offset, String::TWO_BYTE_ENCODING);
878 : Node* const adjusted_search_ptr = PointerToStringDataAtIndex(
879 86 : search_ptr, search_offset, String::TWO_BYTE_ENCODING);
880 :
881 : Node* const result = CallSearchStringRaw<twobyte_t, twobyte_t>(
882 : adjusted_subject_ptr, subject_length, adjusted_search_ptr,
883 86 : search_length, start_position);
884 86 : f_return(SmiTag(result));
885 : }
886 :
887 86 : BIND(&return_minus_1);
888 86 : f_return(SmiConstant(-1));
889 :
890 86 : BIND(&zero_length_needle);
891 : {
892 86 : Comment("0-length search_string");
893 86 : f_return(SmiTag(IntPtrMin(subject_length, start_position)));
894 : }
895 :
896 86 : BIND(&call_runtime_unchecked);
897 : {
898 : // Simplified version of the runtime call where the types of the arguments
899 : // are already known due to type checks in this stub.
900 86 : Comment("Call Runtime Unchecked");
901 : Node* result = CallRuntime(Runtime::kStringIndexOfUnchecked, SmiConstant(0),
902 86 : subject_string, search_string, position);
903 86 : f_return(result);
904 86 : }
905 86 : }
906 :
907 : // ES6 String.prototype.indexOf(searchString [, position])
908 : // #sec-string.prototype.indexof
909 : // Unchecked helper for builtins lowering.
910 172 : TF_BUILTIN(StringIndexOf, StringBuiltinsAssembler) {
911 : Node* receiver = Parameter(Descriptor::kReceiver);
912 : Node* search_string = Parameter(Descriptor::kSearchString);
913 : Node* position = Parameter(Descriptor::kPosition);
914 :
915 43 : Node* instance_type = LoadInstanceType(receiver);
916 43 : Node* search_string_instance_type = LoadInstanceType(search_string);
917 :
918 : StringIndexOf(receiver, instance_type, search_string,
919 : search_string_instance_type, position,
920 430 : [this](Node* result) { this->Return(result); });
921 43 : }
922 :
923 : // ES6 String.prototype.indexOf(searchString [, position])
924 : // #sec-string.prototype.indexof
925 215 : TF_BUILTIN(StringPrototypeIndexOf, StringBuiltinsAssembler) {
926 43 : VARIABLE(search_string, MachineRepresentation::kTagged);
927 86 : VARIABLE(position, MachineRepresentation::kTagged);
928 43 : Label call_runtime(this), call_runtime_unchecked(this), argc_0(this),
929 43 : no_argc_0(this), argc_1(this), no_argc_1(this), argc_2(this),
930 43 : fast_path(this), return_minus_1(this);
931 :
932 : // TODO(ishell): use constants from Descriptor once the JSFunction linkage
933 : // arguments are reordered.
934 : Node* argc = Parameter(BuiltinDescriptor::kArgumentsCount);
935 : Node* context = Parameter(BuiltinDescriptor::kContext);
936 :
937 43 : CodeStubArguments arguments(this, ChangeInt32ToIntPtr(argc));
938 43 : Node* receiver = arguments.GetReceiver();
939 : // From now on use word-size argc value.
940 43 : argc = arguments.GetLength();
941 :
942 43 : GotoIf(IntPtrEqual(argc, IntPtrConstant(0)), &argc_0);
943 43 : GotoIf(IntPtrEqual(argc, IntPtrConstant(1)), &argc_1);
944 43 : Goto(&argc_2);
945 43 : BIND(&argc_0);
946 : {
947 43 : Comment("0 Argument case");
948 43 : Node* undefined = UndefinedConstant();
949 43 : search_string.Bind(undefined);
950 43 : position.Bind(undefined);
951 43 : Goto(&call_runtime);
952 : }
953 43 : BIND(&argc_1);
954 : {
955 43 : Comment("1 Argument case");
956 43 : search_string.Bind(arguments.AtIndex(0));
957 43 : position.Bind(SmiConstant(0));
958 43 : Goto(&fast_path);
959 : }
960 43 : BIND(&argc_2);
961 : {
962 43 : Comment("2 Argument case");
963 43 : search_string.Bind(arguments.AtIndex(0));
964 43 : position.Bind(arguments.AtIndex(1));
965 43 : GotoIfNot(TaggedIsSmi(position.value()), &call_runtime);
966 43 : Goto(&fast_path);
967 : }
968 :
969 43 : BIND(&fast_path);
970 : {
971 43 : Comment("Fast Path");
972 43 : GotoIf(TaggedIsSmi(receiver), &call_runtime);
973 43 : Node* needle = search_string.value();
974 43 : GotoIf(TaggedIsSmi(needle), &call_runtime);
975 :
976 43 : Node* instance_type = LoadInstanceType(receiver);
977 43 : GotoIfNot(IsStringInstanceType(instance_type), &call_runtime);
978 :
979 43 : Node* needle_instance_type = LoadInstanceType(needle);
980 43 : GotoIfNot(IsStringInstanceType(needle_instance_type), &call_runtime);
981 :
982 : StringIndexOf(
983 : receiver, instance_type, needle, needle_instance_type, position.value(),
984 430 : [&arguments](Node* result) { arguments.PopAndReturn(result); });
985 : }
986 :
987 43 : BIND(&call_runtime);
988 : {
989 43 : Comment("Call Runtime");
990 : Node* result = CallRuntime(Runtime::kStringIndexOf, context, receiver,
991 43 : search_string.value(), position.value());
992 43 : arguments.PopAndReturn(result);
993 43 : }
994 43 : }
995 :
996 172 : compiler::Node* StringBuiltinsAssembler::IsNullOrUndefined(Node* const value) {
997 172 : return Word32Or(IsUndefined(value), IsNull(value));
998 : }
999 :
1000 86 : void StringBuiltinsAssembler::RequireObjectCoercible(Node* const context,
1001 : Node* const value,
1002 : const char* method_name) {
1003 172 : Label out(this), throw_exception(this, Label::kDeferred);
1004 86 : Branch(IsNullOrUndefined(value), &throw_exception, &out);
1005 :
1006 86 : BIND(&throw_exception);
1007 : TailCallRuntime(
1008 : Runtime::kThrowCalledOnNullOrUndefined, context,
1009 172 : HeapConstant(factory()->NewStringFromAsciiChecked(method_name, TENURED)));
1010 :
1011 172 : BIND(&out);
1012 86 : }
1013 :
1014 86 : void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol(
1015 : Node* const context, Node* const object, Handle<Symbol> symbol,
1016 : const NodeFunction0& regexp_call, const NodeFunction1& generic_call) {
1017 86 : Label out(this);
1018 :
1019 : // Smis definitely don't have an attached symbol.
1020 86 : GotoIf(TaggedIsSmi(object), &out);
1021 :
1022 86 : Node* const object_map = LoadMap(object);
1023 :
1024 : // Skip the slow lookup for Strings.
1025 : {
1026 : Label next(this);
1027 :
1028 86 : GotoIfNot(IsStringInstanceType(LoadMapInstanceType(object_map)), &next);
1029 :
1030 86 : Node* const native_context = LoadNativeContext(context);
1031 : Node* const initial_proto_initial_map = LoadContextElement(
1032 86 : native_context, Context::STRING_FUNCTION_PROTOTYPE_MAP_INDEX);
1033 :
1034 : Node* const string_fun =
1035 86 : LoadContextElement(native_context, Context::STRING_FUNCTION_INDEX);
1036 : Node* const initial_map =
1037 86 : LoadObjectField(string_fun, JSFunction::kPrototypeOrInitialMapOffset);
1038 86 : Node* const proto_map = LoadMap(LoadMapPrototype(initial_map));
1039 :
1040 86 : Branch(WordEqual(proto_map, initial_proto_initial_map), &out, &next);
1041 :
1042 86 : BIND(&next);
1043 : }
1044 :
1045 : // Take the fast path for RegExps.
1046 : {
1047 86 : Label stub_call(this), slow_lookup(this);
1048 :
1049 : RegExpBuiltinsAssembler regexp_asm(state());
1050 : regexp_asm.BranchIfFastRegExp(context, object, object_map, &stub_call,
1051 86 : &slow_lookup);
1052 :
1053 86 : BIND(&stub_call);
1054 86 : Return(regexp_call());
1055 :
1056 172 : BIND(&slow_lookup);
1057 : }
1058 :
1059 86 : GotoIf(IsNullOrUndefined(object), &out);
1060 :
1061 : // Fall back to a slow lookup of {object[symbol]}.
1062 :
1063 86 : Node* const maybe_func = GetProperty(context, object, symbol);
1064 86 : GotoIf(IsUndefined(maybe_func), &out);
1065 :
1066 : // Attempt to call the function.
1067 :
1068 86 : Node* const result = generic_call(maybe_func);
1069 86 : Return(result);
1070 :
1071 86 : BIND(&out);
1072 86 : }
1073 :
1074 86 : compiler::Node* StringBuiltinsAssembler::IndexOfDollarChar(Node* const context,
1075 : Node* const string) {
1076 : CSA_ASSERT(this, IsString(string));
1077 :
1078 : Node* const dollar_string = HeapConstant(
1079 172 : isolate()->factory()->LookupSingleCharacterStringFromCode('$'));
1080 : Node* const dollar_ix = CallBuiltin(Builtins::kStringIndexOf, context, string,
1081 86 : dollar_string, SmiConstant(0));
1082 :
1083 : CSA_ASSERT(this, TaggedIsSmi(dollar_ix));
1084 86 : return dollar_ix;
1085 : }
1086 :
1087 43 : compiler::Node* StringBuiltinsAssembler::GetSubstitution(
1088 : Node* context, Node* subject_string, Node* match_start_index,
1089 : Node* match_end_index, Node* replace_string) {
1090 : CSA_ASSERT(this, IsString(subject_string));
1091 : CSA_ASSERT(this, IsString(replace_string));
1092 : CSA_ASSERT(this, TaggedIsPositiveSmi(match_start_index));
1093 : CSA_ASSERT(this, TaggedIsPositiveSmi(match_end_index));
1094 :
1095 43 : VARIABLE(var_result, MachineRepresentation::kTagged, replace_string);
1096 43 : Label runtime(this), out(this);
1097 :
1098 : // In this primitive implementation we simply look for the next '$' char in
1099 : // {replace_string}. If it doesn't exist, we can simply return
1100 : // {replace_string} itself. If it does, then we delegate to
1101 : // String::GetSubstitution, passing in the index of the first '$' to avoid
1102 : // repeated scanning work.
1103 : // TODO(jgruber): Possibly extend this in the future to handle more complex
1104 : // cases without runtime calls.
1105 :
1106 43 : Node* const dollar_index = IndexOfDollarChar(context, replace_string);
1107 43 : Branch(SmiIsNegative(dollar_index), &out, &runtime);
1108 :
1109 43 : BIND(&runtime);
1110 : {
1111 : CSA_ASSERT(this, TaggedIsPositiveSmi(dollar_index));
1112 :
1113 43 : Callable substring_callable = CodeFactory::SubString(isolate());
1114 : Node* const matched = CallStub(substring_callable, context, subject_string,
1115 43 : match_start_index, match_end_index);
1116 : Node* const replacement_string =
1117 : CallRuntime(Runtime::kGetSubstitution, context, matched, subject_string,
1118 43 : match_start_index, replace_string, dollar_index);
1119 43 : var_result.Bind(replacement_string);
1120 :
1121 43 : Goto(&out);
1122 : }
1123 :
1124 43 : BIND(&out);
1125 86 : return var_result.value();
1126 : }
1127 :
1128 : // ES6 #sec-string.prototype.replace
1129 215 : TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
1130 43 : Label out(this);
1131 :
1132 : Node* const receiver = Parameter(Descriptor::kReceiver);
1133 : Node* const search = Parameter(Descriptor::kSearch);
1134 : Node* const replace = Parameter(Descriptor::kReplace);
1135 : Node* const context = Parameter(Descriptor::kContext);
1136 :
1137 43 : Node* const smi_zero = SmiConstant(0);
1138 :
1139 43 : RequireObjectCoercible(context, receiver, "String.prototype.replace");
1140 :
1141 : // Redirect to replacer method if {search[@@replace]} is not undefined.
1142 :
1143 : MaybeCallFunctionAtSymbol(
1144 : context, search, isolate()->factory()->replace_symbol(),
1145 43 : [=]() {
1146 43 : Callable tostring_callable = CodeFactory::ToString(isolate());
1147 : Node* const subject_string =
1148 43 : CallStub(tostring_callable, context, receiver);
1149 :
1150 43 : Callable replace_callable = CodeFactory::RegExpReplace(isolate());
1151 : return CallStub(replace_callable, context, search, subject_string,
1152 43 : replace);
1153 : },
1154 43 : [=](Node* fn) {
1155 43 : Callable call_callable = CodeFactory::Call(isolate());
1156 43 : return CallJS(call_callable, context, fn, search, receiver, replace);
1157 172 : });
1158 :
1159 : // Convert {receiver} and {search} to strings.
1160 :
1161 43 : Callable tostring_callable = CodeFactory::ToString(isolate());
1162 43 : Callable indexof_callable = CodeFactory::StringIndexOf(isolate());
1163 :
1164 43 : Node* const subject_string = CallStub(tostring_callable, context, receiver);
1165 43 : Node* const search_string = CallStub(tostring_callable, context, search);
1166 :
1167 43 : Node* const subject_length = LoadStringLength(subject_string);
1168 43 : Node* const search_length = LoadStringLength(search_string);
1169 :
1170 : // Fast-path single-char {search}, long cons {receiver}, and simple string
1171 : // {replace}.
1172 : {
1173 : Label next(this);
1174 :
1175 43 : GotoIfNot(SmiEqual(search_length, SmiConstant(1)), &next);
1176 43 : GotoIfNot(SmiGreaterThan(subject_length, SmiConstant(0xFF)), &next);
1177 43 : GotoIf(TaggedIsSmi(replace), &next);
1178 43 : GotoIfNot(IsString(replace), &next);
1179 :
1180 43 : Node* const subject_instance_type = LoadInstanceType(subject_string);
1181 43 : GotoIfNot(IsConsStringInstanceType(subject_instance_type), &next);
1182 :
1183 43 : GotoIf(TaggedIsPositiveSmi(IndexOfDollarChar(context, replace)), &next);
1184 :
1185 : // Searching by traversing a cons string tree and replace with cons of
1186 : // slices works only when the replaced string is a single character, being
1187 : // replaced by a simple string and only pays off for long strings.
1188 : // TODO(jgruber): Reevaluate if this is still beneficial.
1189 : // TODO(jgruber): TailCallRuntime when it correctly handles adapter frames.
1190 : Return(CallRuntime(Runtime::kStringReplaceOneCharWithString, context,
1191 43 : subject_string, search_string, replace));
1192 :
1193 43 : BIND(&next);
1194 : }
1195 :
1196 : // TODO(jgruber): Extend StringIndexOf to handle two-byte strings and
1197 : // longer substrings - we can handle up to 8 chars (one-byte) / 4 chars
1198 : // (2-byte).
1199 :
1200 : Node* const match_start_index = CallStub(
1201 43 : indexof_callable, context, subject_string, search_string, smi_zero);
1202 : CSA_ASSERT(this, TaggedIsSmi(match_start_index));
1203 :
1204 : // Early exit if no match found.
1205 : {
1206 43 : Label next(this), return_subject(this);
1207 :
1208 43 : GotoIfNot(SmiIsNegative(match_start_index), &next);
1209 :
1210 : // The spec requires to perform ToString(replace) if the {replace} is not
1211 : // callable even if we are going to exit here.
1212 : // Since ToString() being applied to Smi does not have side effects for
1213 : // numbers we can skip it.
1214 43 : GotoIf(TaggedIsSmi(replace), &return_subject);
1215 43 : GotoIf(IsCallableMap(LoadMap(replace)), &return_subject);
1216 :
1217 : // TODO(jgruber): Could introduce ToStringSideeffectsStub which only
1218 : // performs observable parts of ToString.
1219 43 : CallStub(tostring_callable, context, replace);
1220 43 : Goto(&return_subject);
1221 :
1222 43 : BIND(&return_subject);
1223 43 : Return(subject_string);
1224 :
1225 86 : BIND(&next);
1226 : }
1227 :
1228 43 : Node* const match_end_index = SmiAdd(match_start_index, search_length);
1229 :
1230 43 : Callable substring_callable = CodeFactory::SubString(isolate());
1231 : Callable stringadd_callable =
1232 43 : CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED);
1233 :
1234 86 : VARIABLE(var_result, MachineRepresentation::kTagged, EmptyStringConstant());
1235 :
1236 : // Compute the prefix.
1237 : {
1238 : Label next(this);
1239 :
1240 43 : GotoIf(SmiEqual(match_start_index, smi_zero), &next);
1241 : Node* const prefix = CallStub(substring_callable, context, subject_string,
1242 43 : smi_zero, match_start_index);
1243 43 : var_result.Bind(prefix);
1244 :
1245 43 : Goto(&next);
1246 43 : BIND(&next);
1247 : }
1248 :
1249 : // Compute the string to replace with.
1250 :
1251 43 : Label if_iscallablereplace(this), if_notcallablereplace(this);
1252 43 : GotoIf(TaggedIsSmi(replace), &if_notcallablereplace);
1253 : Branch(IsCallableMap(LoadMap(replace)), &if_iscallablereplace,
1254 43 : &if_notcallablereplace);
1255 :
1256 43 : BIND(&if_iscallablereplace);
1257 : {
1258 43 : Callable call_callable = CodeFactory::Call(isolate());
1259 : Node* const replacement =
1260 : CallJS(call_callable, context, replace, UndefinedConstant(),
1261 43 : search_string, match_start_index, subject_string);
1262 : Node* const replacement_string =
1263 43 : CallStub(tostring_callable, context, replacement);
1264 : var_result.Bind(CallStub(stringadd_callable, context, var_result.value(),
1265 43 : replacement_string));
1266 43 : Goto(&out);
1267 : }
1268 :
1269 43 : BIND(&if_notcallablereplace);
1270 : {
1271 43 : Node* const replace_string = CallStub(tostring_callable, context, replace);
1272 : Node* const replacement =
1273 : GetSubstitution(context, subject_string, match_start_index,
1274 43 : match_end_index, replace_string);
1275 : var_result.Bind(
1276 43 : CallStub(stringadd_callable, context, var_result.value(), replacement));
1277 43 : Goto(&out);
1278 : }
1279 :
1280 43 : BIND(&out);
1281 : {
1282 : Node* const suffix = CallStub(substring_callable, context, subject_string,
1283 43 : match_end_index, subject_length);
1284 : Node* const result =
1285 43 : CallStub(stringadd_callable, context, var_result.value(), suffix);
1286 43 : Return(result);
1287 43 : }
1288 43 : }
1289 :
1290 : // ES6 section 21.1.3.19 String.prototype.split ( separator, limit )
1291 215 : TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
1292 43 : Label out(this);
1293 :
1294 : Node* const receiver = Parameter(Descriptor::kReceiver);
1295 : Node* const separator = Parameter(Descriptor::kSeparator);
1296 : Node* const limit = Parameter(Descriptor::kLimit);
1297 : Node* const context = Parameter(Descriptor::kContext);
1298 :
1299 43 : Node* const smi_zero = SmiConstant(0);
1300 :
1301 43 : RequireObjectCoercible(context, receiver, "String.prototype.split");
1302 :
1303 : // Redirect to splitter method if {separator[@@split]} is not undefined.
1304 :
1305 : MaybeCallFunctionAtSymbol(
1306 : context, separator, isolate()->factory()->split_symbol(),
1307 43 : [=]() {
1308 43 : Callable tostring_callable = CodeFactory::ToString(isolate());
1309 : Node* const subject_string =
1310 43 : CallStub(tostring_callable, context, receiver);
1311 :
1312 43 : Callable split_callable = CodeFactory::RegExpSplit(isolate());
1313 : return CallStub(split_callable, context, separator, subject_string,
1314 43 : limit);
1315 : },
1316 43 : [=](Node* fn) {
1317 43 : Callable call_callable = CodeFactory::Call(isolate());
1318 43 : return CallJS(call_callable, context, fn, separator, receiver, limit);
1319 172 : });
1320 :
1321 : // String and integer conversions.
1322 : // TODO(jgruber): The old implementation used Uint32Max instead of SmiMax -
1323 : // but AFAIK there should not be a difference since arrays are capped at Smi
1324 : // lengths.
1325 :
1326 43 : Callable tostring_callable = CodeFactory::ToString(isolate());
1327 43 : Node* const subject_string = CallStub(tostring_callable, context, receiver);
1328 : Node* const limit_number =
1329 43 : Select(IsUndefined(limit), [=]() { return SmiConstant(Smi::kMaxValue); },
1330 43 : [=]() { return ToUint32(context, limit); },
1331 129 : MachineRepresentation::kTagged);
1332 : Node* const separator_string =
1333 43 : CallStub(tostring_callable, context, separator);
1334 :
1335 : // Shortcut for {limit} == 0.
1336 : {
1337 : Label next(this);
1338 43 : GotoIfNot(SmiEqual(limit_number, smi_zero), &next);
1339 :
1340 : const ElementsKind kind = FAST_ELEMENTS;
1341 43 : Node* const native_context = LoadNativeContext(context);
1342 43 : Node* const array_map = LoadJSArrayElementsMap(kind, native_context);
1343 :
1344 : Node* const length = smi_zero;
1345 43 : Node* const capacity = IntPtrConstant(0);
1346 43 : Node* const result = AllocateJSArray(kind, array_map, capacity, length);
1347 :
1348 43 : Return(result);
1349 :
1350 43 : BIND(&next);
1351 : }
1352 :
1353 : // ECMA-262 says that if {separator} is undefined, the result should
1354 : // be an array of size 1 containing the entire string.
1355 : {
1356 : Label next(this);
1357 43 : GotoIfNot(IsUndefined(separator), &next);
1358 :
1359 : const ElementsKind kind = FAST_ELEMENTS;
1360 43 : Node* const native_context = LoadNativeContext(context);
1361 43 : Node* const array_map = LoadJSArrayElementsMap(kind, native_context);
1362 :
1363 43 : Node* const length = SmiConstant(1);
1364 43 : Node* const capacity = IntPtrConstant(1);
1365 43 : Node* const result = AllocateJSArray(kind, array_map, capacity, length);
1366 :
1367 43 : Node* const fixed_array = LoadElements(result);
1368 43 : StoreFixedArrayElement(fixed_array, 0, subject_string);
1369 :
1370 43 : Return(result);
1371 :
1372 43 : BIND(&next);
1373 : }
1374 :
1375 : // If the separator string is empty then return the elements in the subject.
1376 : {
1377 : Label next(this);
1378 43 : GotoIfNot(SmiEqual(LoadStringLength(separator_string), smi_zero), &next);
1379 :
1380 : Node* const result = CallRuntime(Runtime::kStringToArray, context,
1381 43 : subject_string, limit_number);
1382 43 : Return(result);
1383 :
1384 43 : BIND(&next);
1385 : }
1386 :
1387 : Node* const result =
1388 : CallRuntime(Runtime::kStringSplit, context, subject_string,
1389 43 : separator_string, limit_number);
1390 86 : Return(result);
1391 43 : }
1392 :
1393 : // ES6 #sec-string.prototype.substr
1394 172 : TF_BUILTIN(StringPrototypeSubstr, CodeStubAssembler) {
1395 86 : Label out(this), handle_length(this);
1396 :
1397 86 : VARIABLE(var_start, MachineRepresentation::kTagged);
1398 86 : VARIABLE(var_length, MachineRepresentation::kTagged);
1399 :
1400 : Node* const receiver = Parameter(Descriptor::kReceiver);
1401 : Node* const start = Parameter(Descriptor::kStart);
1402 : Node* const length = Parameter(Descriptor::kLength);
1403 : Node* const context = Parameter(Descriptor::kContext);
1404 :
1405 43 : Node* const zero = SmiConstant(Smi::kZero);
1406 :
1407 : // Check that {receiver} is coercible to Object and convert it to a String.
1408 : Node* const string =
1409 43 : ToThisString(context, receiver, "String.prototype.substr");
1410 :
1411 43 : Node* const string_length = LoadStringLength(string);
1412 :
1413 : // Conversions and bounds-checks for {start}.
1414 : {
1415 : Node* const start_int =
1416 43 : ToInteger(context, start, CodeStubAssembler::kTruncateMinusZero);
1417 :
1418 43 : Label if_issmi(this), if_isheapnumber(this, Label::kDeferred);
1419 43 : Branch(TaggedIsSmi(start_int), &if_issmi, &if_isheapnumber);
1420 :
1421 43 : BIND(&if_issmi);
1422 : {
1423 43 : Node* const length_plus_start = SmiAdd(string_length, start_int);
1424 : var_start.Bind(Select(SmiLessThan(start_int, zero),
1425 43 : [&] { return SmiMax(length_plus_start, zero); },
1426 43 : [&] { return start_int; },
1427 129 : MachineRepresentation::kTagged));
1428 43 : Goto(&handle_length);
1429 : }
1430 :
1431 43 : BIND(&if_isheapnumber);
1432 : {
1433 : // If {start} is a heap number, it is definitely out of bounds. If it is
1434 : // negative, {start} = max({string_length} + {start}),0) = 0'. If it is
1435 : // positive, set {start} to {string_length} which ultimately results in
1436 : // returning an empty string.
1437 43 : Node* const float_zero = Float64Constant(0.);
1438 43 : Node* const start_float = LoadHeapNumberValue(start_int);
1439 : var_start.Bind(SelectTaggedConstant(
1440 43 : Float64LessThan(start_float, float_zero), zero, string_length));
1441 43 : Goto(&handle_length);
1442 43 : }
1443 : }
1444 :
1445 : // Conversions and bounds-checks for {length}.
1446 43 : BIND(&handle_length);
1447 : {
1448 43 : Label if_issmi(this), if_isheapnumber(this, Label::kDeferred);
1449 :
1450 : // Default to {string_length} if {length} is undefined.
1451 : {
1452 43 : Label if_isundefined(this, Label::kDeferred), if_isnotundefined(this);
1453 : Branch(WordEqual(length, UndefinedConstant()), &if_isundefined,
1454 43 : &if_isnotundefined);
1455 :
1456 43 : BIND(&if_isundefined);
1457 43 : var_length.Bind(string_length);
1458 43 : Goto(&if_issmi);
1459 :
1460 43 : BIND(&if_isnotundefined);
1461 : var_length.Bind(
1462 86 : ToInteger(context, length, CodeStubAssembler::kTruncateMinusZero));
1463 : }
1464 :
1465 43 : Branch(TaggedIsSmi(var_length.value()), &if_issmi, &if_isheapnumber);
1466 :
1467 : // Set {length} to min(max({length}, 0), {string_length} - {start}
1468 43 : BIND(&if_issmi);
1469 : {
1470 43 : Node* const positive_length = SmiMax(var_length.value(), zero);
1471 :
1472 43 : Node* const minimal_length = SmiSub(string_length, var_start.value());
1473 43 : var_length.Bind(SmiMin(positive_length, minimal_length));
1474 :
1475 43 : GotoIfNot(SmiLessThanOrEqual(var_length.value(), zero), &out);
1476 43 : Return(EmptyStringConstant());
1477 : }
1478 :
1479 43 : BIND(&if_isheapnumber);
1480 : {
1481 : // If {length} is a heap number, it is definitely out of bounds. There are
1482 : // two cases according to the spec: if it is negative, "" is returned; if
1483 : // it is positive, then length is set to {string_length} - {start}.
1484 :
1485 : CSA_ASSERT(this, IsHeapNumberMap(LoadMap(var_length.value())));
1486 :
1487 43 : Label if_isnegative(this), if_ispositive(this);
1488 43 : Node* const float_zero = Float64Constant(0.);
1489 43 : Node* const length_float = LoadHeapNumberValue(var_length.value());
1490 : Branch(Float64LessThan(length_float, float_zero), &if_isnegative,
1491 43 : &if_ispositive);
1492 :
1493 43 : BIND(&if_isnegative);
1494 43 : Return(EmptyStringConstant());
1495 :
1496 43 : BIND(&if_ispositive);
1497 : {
1498 43 : var_length.Bind(SmiSub(string_length, var_start.value()));
1499 43 : GotoIfNot(SmiLessThanOrEqual(var_length.value(), zero), &out);
1500 43 : Return(EmptyStringConstant());
1501 43 : }
1502 43 : }
1503 : }
1504 :
1505 43 : BIND(&out);
1506 : {
1507 43 : Node* const end = SmiAdd(var_start.value(), var_length.value());
1508 43 : Node* const result = SubString(context, string, var_start.value(), end);
1509 43 : Return(result);
1510 43 : }
1511 43 : }
1512 :
1513 86 : compiler::Node* StringBuiltinsAssembler::ToSmiBetweenZeroAnd(Node* context,
1514 : Node* value,
1515 : Node* limit) {
1516 86 : Label out(this);
1517 172 : VARIABLE(var_result, MachineRepresentation::kTagged);
1518 :
1519 : Node* const value_int =
1520 86 : this->ToInteger(context, value, CodeStubAssembler::kTruncateMinusZero);
1521 :
1522 86 : Label if_issmi(this), if_isnotsmi(this, Label::kDeferred);
1523 86 : Branch(TaggedIsSmi(value_int), &if_issmi, &if_isnotsmi);
1524 :
1525 86 : BIND(&if_issmi);
1526 : {
1527 86 : Label if_isinbounds(this), if_isoutofbounds(this, Label::kDeferred);
1528 86 : Branch(SmiAbove(value_int, limit), &if_isoutofbounds, &if_isinbounds);
1529 :
1530 86 : BIND(&if_isinbounds);
1531 : {
1532 86 : var_result.Bind(value_int);
1533 86 : Goto(&out);
1534 : }
1535 :
1536 86 : BIND(&if_isoutofbounds);
1537 : {
1538 86 : Node* const zero = SmiConstant(Smi::kZero);
1539 : var_result.Bind(
1540 86 : SelectTaggedConstant(SmiLessThan(value_int, zero), zero, limit));
1541 86 : Goto(&out);
1542 86 : }
1543 : }
1544 :
1545 86 : BIND(&if_isnotsmi);
1546 : {
1547 : // {value} is a heap number - in this case, it is definitely out of bounds.
1548 : CSA_ASSERT(this, IsHeapNumberMap(LoadMap(value_int)));
1549 :
1550 86 : Node* const float_zero = Float64Constant(0.);
1551 86 : Node* const smi_zero = SmiConstant(Smi::kZero);
1552 86 : Node* const value_float = LoadHeapNumberValue(value_int);
1553 : var_result.Bind(SelectTaggedConstant(
1554 86 : Float64LessThan(value_float, float_zero), smi_zero, limit));
1555 86 : Goto(&out);
1556 : }
1557 :
1558 86 : BIND(&out);
1559 172 : return var_result.value();
1560 : }
1561 :
1562 : // ES6 #sec-string.prototype.substring
1563 215 : TF_BUILTIN(StringPrototypeSubstring, StringBuiltinsAssembler) {
1564 43 : Label out(this);
1565 :
1566 86 : VARIABLE(var_start, MachineRepresentation::kTagged);
1567 86 : VARIABLE(var_end, MachineRepresentation::kTagged);
1568 :
1569 : Node* const receiver = Parameter(Descriptor::kReceiver);
1570 : Node* const start = Parameter(Descriptor::kStart);
1571 : Node* const end = Parameter(Descriptor::kEnd);
1572 : Node* const context = Parameter(Descriptor::kContext);
1573 :
1574 : // Check that {receiver} is coercible to Object and convert it to a String.
1575 : Node* const string =
1576 43 : ToThisString(context, receiver, "String.prototype.substring");
1577 :
1578 43 : Node* const length = LoadStringLength(string);
1579 :
1580 : // Conversion and bounds-checks for {start}.
1581 43 : var_start.Bind(ToSmiBetweenZeroAnd(context, start, length));
1582 :
1583 : // Conversion and bounds-checks for {end}.
1584 : {
1585 43 : var_end.Bind(length);
1586 43 : GotoIf(WordEqual(end, UndefinedConstant()), &out);
1587 :
1588 43 : var_end.Bind(ToSmiBetweenZeroAnd(context, end, length));
1589 :
1590 : Label if_endislessthanstart(this);
1591 : Branch(SmiLessThan(var_end.value(), var_start.value()),
1592 43 : &if_endislessthanstart, &out);
1593 :
1594 43 : BIND(&if_endislessthanstart);
1595 : {
1596 43 : Node* const tmp = var_end.value();
1597 43 : var_end.Bind(var_start.value());
1598 43 : var_start.Bind(tmp);
1599 43 : Goto(&out);
1600 43 : }
1601 : }
1602 :
1603 43 : BIND(&out);
1604 : {
1605 : Node* result =
1606 43 : SubString(context, string, var_start.value(), var_end.value());
1607 43 : Return(result);
1608 43 : }
1609 43 : }
1610 :
1611 : // ES6 #sec-string.prototype.tostring
1612 129 : TF_BUILTIN(StringPrototypeToString, CodeStubAssembler) {
1613 : Node* context = Parameter(Descriptor::kContext);
1614 : Node* receiver = Parameter(Descriptor::kReceiver);
1615 :
1616 : Node* result = ToThisValue(context, receiver, PrimitiveType::kString,
1617 43 : "String.prototype.toString");
1618 43 : Return(result);
1619 43 : }
1620 :
1621 : // ES6 #sec-string.prototype.valueof
1622 129 : TF_BUILTIN(StringPrototypeValueOf, CodeStubAssembler) {
1623 : Node* context = Parameter(Descriptor::kContext);
1624 : Node* receiver = Parameter(Descriptor::kReceiver);
1625 :
1626 : Node* result = ToThisValue(context, receiver, PrimitiveType::kString,
1627 43 : "String.prototype.valueOf");
1628 43 : Return(result);
1629 43 : }
1630 :
1631 129 : TF_BUILTIN(StringPrototypeIterator, CodeStubAssembler) {
1632 : Node* context = Parameter(Descriptor::kContext);
1633 : Node* receiver = Parameter(Descriptor::kReceiver);
1634 :
1635 : Node* string =
1636 43 : ToThisString(context, receiver, "String.prototype[Symbol.iterator]");
1637 :
1638 43 : Node* native_context = LoadNativeContext(context);
1639 : Node* map =
1640 43 : LoadContextElement(native_context, Context::STRING_ITERATOR_MAP_INDEX);
1641 43 : Node* iterator = Allocate(JSStringIterator::kSize);
1642 43 : StoreMapNoWriteBarrier(iterator, map);
1643 : StoreObjectFieldRoot(iterator, JSValue::kPropertiesOffset,
1644 43 : Heap::kEmptyFixedArrayRootIndex);
1645 : StoreObjectFieldRoot(iterator, JSObject::kElementsOffset,
1646 43 : Heap::kEmptyFixedArrayRootIndex);
1647 : StoreObjectFieldNoWriteBarrier(iterator, JSStringIterator::kStringOffset,
1648 43 : string);
1649 43 : Node* index = SmiConstant(Smi::kZero);
1650 : StoreObjectFieldNoWriteBarrier(iterator, JSStringIterator::kNextIndexOffset,
1651 43 : index);
1652 43 : Return(iterator);
1653 43 : }
1654 :
1655 : // Return the |word32| codepoint at {index}. Supports SeqStrings and
1656 : // ExternalStrings.
1657 43 : compiler::Node* StringBuiltinsAssembler::LoadSurrogatePairAt(
1658 : compiler::Node* string, compiler::Node* length, compiler::Node* index,
1659 : UnicodeEncoding encoding) {
1660 86 : Label handle_surrogate_pair(this), return_result(this);
1661 86 : VARIABLE(var_result, MachineRepresentation::kWord32);
1662 86 : VARIABLE(var_trail, MachineRepresentation::kWord32);
1663 43 : var_result.Bind(StringCharCodeAt(string, index));
1664 43 : var_trail.Bind(Int32Constant(0));
1665 :
1666 : GotoIf(Word32NotEqual(Word32And(var_result.value(), Int32Constant(0xFC00)),
1667 : Int32Constant(0xD800)),
1668 43 : &return_result);
1669 43 : Node* next_index = SmiAdd(index, SmiConstant(Smi::FromInt(1)));
1670 :
1671 43 : GotoIfNot(SmiLessThan(next_index, length), &return_result);
1672 43 : var_trail.Bind(StringCharCodeAt(string, next_index));
1673 : Branch(Word32Equal(Word32And(var_trail.value(), Int32Constant(0xFC00)),
1674 : Int32Constant(0xDC00)),
1675 43 : &handle_surrogate_pair, &return_result);
1676 :
1677 43 : BIND(&handle_surrogate_pair);
1678 : {
1679 43 : Node* lead = var_result.value();
1680 43 : Node* trail = var_trail.value();
1681 :
1682 : // Check that this path is only taken if a surrogate pair is found
1683 : CSA_SLOW_ASSERT(this,
1684 : Uint32GreaterThanOrEqual(lead, Int32Constant(0xD800)));
1685 : CSA_SLOW_ASSERT(this, Uint32LessThan(lead, Int32Constant(0xDC00)));
1686 : CSA_SLOW_ASSERT(this,
1687 : Uint32GreaterThanOrEqual(trail, Int32Constant(0xDC00)));
1688 : CSA_SLOW_ASSERT(this, Uint32LessThan(trail, Int32Constant(0xE000)));
1689 :
1690 43 : switch (encoding) {
1691 : case UnicodeEncoding::UTF16:
1692 : var_result.Bind(Word32Or(
1693 : // Need to swap the order for big-endian platforms
1694 : #if V8_TARGET_BIG_ENDIAN
1695 : Word32Shl(lead, Int32Constant(16)), trail));
1696 : #else
1697 43 : Word32Shl(trail, Int32Constant(16)), lead));
1698 : #endif
1699 43 : break;
1700 :
1701 : case UnicodeEncoding::UTF32: {
1702 : // Convert UTF16 surrogate pair into |word32| code point, encoded as
1703 : // UTF32.
1704 : Node* surrogate_offset =
1705 0 : Int32Constant(0x10000 - (0xD800 << 10) - 0xDC00);
1706 :
1707 : // (lead << 10) + trail + SURROGATE_OFFSET
1708 : var_result.Bind(Int32Add(WordShl(lead, Int32Constant(10)),
1709 0 : Int32Add(trail, surrogate_offset)));
1710 0 : break;
1711 : }
1712 : }
1713 43 : Goto(&return_result);
1714 : }
1715 :
1716 43 : BIND(&return_result);
1717 86 : return var_result.value();
1718 : }
1719 :
1720 : // ES6 #sec-%stringiteratorprototype%.next
1721 215 : TF_BUILTIN(StringIteratorPrototypeNext, StringBuiltinsAssembler) {
1722 43 : VARIABLE(var_value, MachineRepresentation::kTagged);
1723 86 : VARIABLE(var_done, MachineRepresentation::kTagged);
1724 :
1725 43 : var_value.Bind(UndefinedConstant());
1726 43 : var_done.Bind(BooleanConstant(true));
1727 :
1728 43 : Label throw_bad_receiver(this), next_codepoint(this), return_result(this);
1729 :
1730 : Node* context = Parameter(Descriptor::kContext);
1731 : Node* iterator = Parameter(Descriptor::kReceiver);
1732 :
1733 43 : GotoIf(TaggedIsSmi(iterator), &throw_bad_receiver);
1734 : GotoIfNot(Word32Equal(LoadInstanceType(iterator),
1735 : Int32Constant(JS_STRING_ITERATOR_TYPE)),
1736 43 : &throw_bad_receiver);
1737 :
1738 43 : Node* string = LoadObjectField(iterator, JSStringIterator::kStringOffset);
1739 : Node* position =
1740 43 : LoadObjectField(iterator, JSStringIterator::kNextIndexOffset);
1741 43 : Node* length = LoadObjectField(string, String::kLengthOffset);
1742 :
1743 43 : Branch(SmiLessThan(position, length), &next_codepoint, &return_result);
1744 :
1745 43 : BIND(&next_codepoint);
1746 : {
1747 : UnicodeEncoding encoding = UnicodeEncoding::UTF16;
1748 43 : Node* ch = LoadSurrogatePairAt(string, length, position, encoding);
1749 43 : Node* value = StringFromCodePoint(ch, encoding);
1750 43 : var_value.Bind(value);
1751 43 : Node* length = LoadObjectField(value, String::kLengthOffset);
1752 : StoreObjectFieldNoWriteBarrier(iterator, JSStringIterator::kNextIndexOffset,
1753 43 : SmiAdd(position, length));
1754 43 : var_done.Bind(BooleanConstant(false));
1755 43 : Goto(&return_result);
1756 : }
1757 :
1758 43 : BIND(&return_result);
1759 : {
1760 43 : Node* native_context = LoadNativeContext(context);
1761 : Node* map =
1762 43 : LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
1763 43 : Node* result = Allocate(JSIteratorResult::kSize);
1764 43 : StoreMapNoWriteBarrier(result, map);
1765 : StoreObjectFieldRoot(result, JSIteratorResult::kPropertiesOffset,
1766 43 : Heap::kEmptyFixedArrayRootIndex);
1767 : StoreObjectFieldRoot(result, JSIteratorResult::kElementsOffset,
1768 43 : Heap::kEmptyFixedArrayRootIndex);
1769 : StoreObjectFieldNoWriteBarrier(result, JSIteratorResult::kValueOffset,
1770 43 : var_value.value());
1771 : StoreObjectFieldNoWriteBarrier(result, JSIteratorResult::kDoneOffset,
1772 43 : var_done.value());
1773 43 : Return(result);
1774 : }
1775 :
1776 43 : BIND(&throw_bad_receiver);
1777 : {
1778 : // The {receiver} is not a valid JSGeneratorObject.
1779 : CallRuntime(Runtime::kThrowIncompatibleMethodReceiver, context,
1780 : HeapConstant(factory()->NewStringFromAsciiChecked(
1781 : "String Iterator.prototype.next", TENURED)),
1782 86 : iterator);
1783 43 : Unreachable();
1784 43 : }
1785 43 : }
1786 :
1787 : } // namespace internal
1788 : } // namespace v8
|