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