Line data Source code
1 : // Copyright 2017 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #include "src/builtins/builtins-regexp-gen.h"
6 :
7 : #include "src/builtins/builtins-constructor-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/code-stub-assembler.h"
12 : #include "src/objects/regexp-match-info.h"
13 : #include "src/regexp/regexp-macro-assembler.h"
14 :
15 : namespace v8 {
16 : namespace internal {
17 :
18 : using compiler::Node;
19 :
20 : // -----------------------------------------------------------------------------
21 : // ES6 section 21.2 RegExp Objects
22 :
23 0 : Node* RegExpBuiltinsAssembler::FastLoadLastIndex(Node* regexp) {
24 : // Load the in-object field.
25 : static const int field_offset =
26 : JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize;
27 817 : return LoadObjectField(regexp, field_offset);
28 : }
29 :
30 172 : Node* RegExpBuiltinsAssembler::SlowLoadLastIndex(Node* context, Node* regexp) {
31 : // Load through the GetProperty stub.
32 344 : return GetProperty(context, regexp, isolate()->factory()->lastIndex_string());
33 : }
34 :
35 387 : Node* RegExpBuiltinsAssembler::LoadLastIndex(Node* context, Node* regexp,
36 : bool is_fastpath) {
37 : return is_fastpath ? FastLoadLastIndex(regexp)
38 473 : : SlowLoadLastIndex(context, regexp);
39 : }
40 :
41 : // The fast-path of StoreLastIndex when regexp is guaranteed to be an unmodified
42 : // JSRegExp instance.
43 0 : void RegExpBuiltinsAssembler::FastStoreLastIndex(Node* regexp, Node* value) {
44 : // Store the in-object field.
45 : static const int field_offset =
46 : JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize;
47 1118 : StoreObjectField(regexp, field_offset, value);
48 0 : }
49 :
50 301 : void RegExpBuiltinsAssembler::SlowStoreLastIndex(Node* context, Node* regexp,
51 : Node* value) {
52 : // Store through runtime.
53 : // TODO(ishell): Use SetPropertyStub here once available.
54 602 : Node* const name = HeapConstant(isolate()->factory()->lastIndex_string());
55 301 : Node* const language_mode = SmiConstant(Smi::FromInt(STRICT));
56 : CallRuntime(Runtime::kSetProperty, context, regexp, name, value,
57 301 : language_mode);
58 301 : }
59 :
60 1075 : void RegExpBuiltinsAssembler::StoreLastIndex(Node* context, Node* regexp,
61 : Node* value, bool is_fastpath) {
62 1075 : if (is_fastpath) {
63 : FastStoreLastIndex(regexp, value);
64 : } else {
65 215 : SlowStoreLastIndex(context, regexp, value);
66 : }
67 1075 : }
68 :
69 172 : Node* RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
70 : Node* const context, Node* const regexp, Node* const match_info,
71 : Node* const string) {
72 : CSA_ASSERT(this, IsFixedArrayMap(LoadMap(match_info)));
73 : CSA_ASSERT(this, IsJSRegExp(regexp));
74 : CSA_ASSERT(this, IsString(string));
75 :
76 344 : Label named_captures(this), out(this);
77 :
78 : Node* const num_indices = SmiUntag(LoadFixedArrayElement(
79 172 : match_info, RegExpMatchInfo::kNumberOfCapturesIndex));
80 172 : Node* const num_results = SmiTag(WordShr(num_indices, 1));
81 : Node* const start =
82 172 : LoadFixedArrayElement(match_info, RegExpMatchInfo::kFirstCaptureIndex);
83 : Node* const end = LoadFixedArrayElement(
84 172 : match_info, RegExpMatchInfo::kFirstCaptureIndex + 1);
85 :
86 : // Calculate the substring of the first match before creating the result array
87 : // to avoid an unnecessary write barrier storing the first result.
88 172 : Node* const first = SubString(context, string, start, end);
89 :
90 : Node* const result =
91 172 : AllocateRegExpResult(context, num_results, start, string);
92 172 : Node* const result_elements = LoadElements(result);
93 :
94 172 : StoreFixedArrayElement(result_elements, 0, first, SKIP_WRITE_BARRIER);
95 :
96 : // If no captures exist we can skip named capture handling as well.
97 172 : GotoIf(SmiEqual(num_results, SmiConstant(1)), &out);
98 :
99 : // Store all remaining captures.
100 : Node* const limit = IntPtrAdd(
101 172 : IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), num_indices);
102 :
103 344 : VARIABLE(var_from_cursor, MachineType::PointerRepresentation(),
104 : IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 2));
105 344 : VARIABLE(var_to_cursor, MachineType::PointerRepresentation(),
106 : IntPtrConstant(1));
107 :
108 172 : Variable* vars[] = {&var_from_cursor, &var_to_cursor};
109 344 : Label loop(this, 2, vars);
110 :
111 172 : Goto(&loop);
112 172 : BIND(&loop);
113 : {
114 172 : Node* const from_cursor = var_from_cursor.value();
115 172 : Node* const to_cursor = var_to_cursor.value();
116 172 : Node* const start = LoadFixedArrayElement(match_info, from_cursor);
117 :
118 : Label next_iter(this);
119 172 : GotoIf(SmiEqual(start, SmiConstant(-1)), &next_iter);
120 :
121 172 : Node* const from_cursor_plus1 = IntPtrAdd(from_cursor, IntPtrConstant(1));
122 172 : Node* const end = LoadFixedArrayElement(match_info, from_cursor_plus1);
123 :
124 172 : Node* const capture = SubString(context, string, start, end);
125 172 : StoreFixedArrayElement(result_elements, to_cursor, capture);
126 172 : Goto(&next_iter);
127 :
128 172 : BIND(&next_iter);
129 172 : var_from_cursor.Bind(IntPtrAdd(from_cursor, IntPtrConstant(2)));
130 172 : var_to_cursor.Bind(IntPtrAdd(to_cursor, IntPtrConstant(1)));
131 : Branch(UintPtrLessThan(var_from_cursor.value(), limit), &loop,
132 172 : &named_captures);
133 : }
134 :
135 172 : BIND(&named_captures);
136 : {
137 : // We reach this point only if captures exist, implying that this is an
138 : // IRREGEXP JSRegExp.
139 :
140 : CSA_ASSERT(this, IsJSRegExp(regexp));
141 : CSA_ASSERT(this, SmiGreaterThan(num_results, SmiConstant(1)));
142 :
143 : // Preparations for named capture properties. Exit early if the result does
144 : // not have any named captures to minimize performance impact.
145 :
146 172 : Node* const data = LoadObjectField(regexp, JSRegExp::kDataOffset);
147 : CSA_ASSERT(this, SmiEqual(LoadFixedArrayElement(data, JSRegExp::kTagIndex),
148 : SmiConstant(JSRegExp::IRREGEXP)));
149 :
150 : // The names fixed array associates names at even indices with a capture
151 : // index at odd indices.
152 : Node* const names =
153 172 : LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureNameMapIndex);
154 172 : GotoIf(SmiEqual(names, SmiConstant(0)), &out);
155 :
156 : // Allocate a new object to store the named capture properties.
157 : // TODO(jgruber): Could be optimized by adding the object map to the heap
158 : // root list.
159 :
160 172 : Node* const native_context = LoadNativeContext(context);
161 : Node* const map = LoadContextElement(
162 172 : native_context, Context::SLOW_OBJECT_WITH_NULL_PROTOTYPE_MAP);
163 : Node* const properties =
164 172 : AllocateNameDictionary(NameDictionary::kInitialCapacity);
165 :
166 172 : Node* const group_object = AllocateJSObjectFromMap(map, properties);
167 :
168 : // Store it on the result as a 'group' property.
169 :
170 : {
171 344 : Node* const name = HeapConstant(isolate()->factory()->groups_string());
172 : CallRuntime(Runtime::kCreateDataProperty, context, result, name,
173 172 : group_object);
174 : }
175 :
176 : // One or more named captures exist, add a property for each one.
177 :
178 : CSA_ASSERT(this, HasInstanceType(names, FIXED_ARRAY_TYPE));
179 172 : Node* const names_length = LoadAndUntagFixedArrayBaseLength(names);
180 : CSA_ASSERT(this, IntPtrGreaterThan(names_length, IntPtrConstant(0)));
181 :
182 172 : VARIABLE(var_i, MachineType::PointerRepresentation());
183 172 : var_i.Bind(IntPtrConstant(0));
184 :
185 172 : Variable* vars[] = {&var_i};
186 : const int vars_count = sizeof(vars) / sizeof(vars[0]);
187 344 : Label loop(this, vars_count, vars);
188 :
189 172 : Goto(&loop);
190 172 : BIND(&loop);
191 : {
192 172 : Node* const i = var_i.value();
193 172 : Node* const i_plus_1 = IntPtrAdd(i, IntPtrConstant(1));
194 172 : Node* const i_plus_2 = IntPtrAdd(i_plus_1, IntPtrConstant(1));
195 :
196 172 : Node* const name = LoadFixedArrayElement(names, i);
197 172 : Node* const index = LoadFixedArrayElement(names, i_plus_1);
198 : Node* const capture =
199 172 : LoadFixedArrayElement(result_elements, SmiUntag(index));
200 :
201 : CallRuntime(Runtime::kCreateDataProperty, context, group_object, name,
202 172 : capture);
203 :
204 172 : var_i.Bind(i_plus_2);
205 : Branch(IntPtrGreaterThanOrEqual(var_i.value(), names_length), &out,
206 172 : &loop);
207 172 : }
208 : }
209 :
210 172 : BIND(&out);
211 172 : return result;
212 : }
213 :
214 860 : void RegExpBuiltinsAssembler::GetStringPointers(
215 : Node* const string_data, Node* const offset, Node* const last_index,
216 : Node* const string_length, String::Encoding encoding,
217 : Variable* var_string_start, Variable* var_string_end) {
218 : DCHECK_EQ(var_string_start->rep(), MachineType::PointerRepresentation());
219 : DCHECK_EQ(var_string_end->rep(), MachineType::PointerRepresentation());
220 :
221 : const ElementsKind kind = (encoding == String::ONE_BYTE_ENCODING)
222 : ? UINT8_ELEMENTS
223 860 : : UINT16_ELEMENTS;
224 :
225 : Node* const from_offset = ElementOffsetFromIndex(
226 860 : IntPtrAdd(offset, last_index), kind, INTPTR_PARAMETERS);
227 860 : var_string_start->Bind(IntPtrAdd(string_data, from_offset));
228 :
229 : Node* const to_offset = ElementOffsetFromIndex(
230 860 : IntPtrAdd(offset, string_length), kind, INTPTR_PARAMETERS);
231 860 : var_string_end->Bind(IntPtrAdd(string_data, to_offset));
232 860 : }
233 :
234 430 : Node* RegExpBuiltinsAssembler::RegExpExecInternal(Node* const context,
235 : Node* const regexp,
236 : Node* const string,
237 : Node* const last_index,
238 : Node* const match_info) {
239 : // Just jump directly to runtime if native RegExp is not selected at compile
240 : // time or if regexp entry in generated code is turned off runtime switch or
241 : // at compilation.
242 : #ifdef V8_INTERPRETED_REGEXP
243 : return CallRuntime(Runtime::kRegExpExec, context, regexp, string, last_index,
244 : match_info);
245 : #else // V8_INTERPRETED_REGEXP
246 : CSA_ASSERT(this, TaggedIsNotSmi(regexp));
247 : CSA_ASSERT(this, IsJSRegExp(regexp));
248 :
249 : CSA_ASSERT(this, TaggedIsNotSmi(string));
250 : CSA_ASSERT(this, IsString(string));
251 :
252 : CSA_ASSERT(this, IsNumber(last_index));
253 : CSA_ASSERT(this, IsFixedArrayMap(LoadReceiverMap(match_info)));
254 :
255 430 : Node* const int_zero = IntPtrConstant(0);
256 :
257 430 : ToDirectStringAssembler to_direct(state(), string);
258 :
259 860 : VARIABLE(var_result, MachineRepresentation::kTagged);
260 430 : Label out(this), runtime(this, Label::kDeferred);
261 :
262 : // External constants.
263 : Node* const isolate_address =
264 430 : ExternalConstant(ExternalReference::isolate_address(isolate()));
265 : Node* const regexp_stack_memory_address_address = ExternalConstant(
266 430 : ExternalReference::address_of_regexp_stack_memory_address(isolate()));
267 : Node* const regexp_stack_memory_size_address = ExternalConstant(
268 430 : ExternalReference::address_of_regexp_stack_memory_size(isolate()));
269 : Node* const static_offsets_vector_address = ExternalConstant(
270 430 : ExternalReference::address_of_static_offsets_vector(isolate()));
271 :
272 : // Ensure that a RegExp stack is allocated.
273 : {
274 : Node* const stack_size =
275 430 : Load(MachineType::IntPtr(), regexp_stack_memory_size_address);
276 430 : GotoIf(IntPtrEqual(stack_size, int_zero), &runtime);
277 : }
278 :
279 430 : Node* const data = LoadObjectField(regexp, JSRegExp::kDataOffset);
280 : {
281 : // Check that the RegExp has been compiled (data contains a fixed array).
282 : CSA_ASSERT(this, TaggedIsNotSmi(data));
283 : CSA_ASSERT(this, HasInstanceType(data, FIXED_ARRAY_TYPE));
284 :
285 : // Check the type of the RegExp. Only continue if type is
286 : // JSRegExp::IRREGEXP.
287 430 : Node* const tag = LoadFixedArrayElement(data, JSRegExp::kTagIndex);
288 430 : GotoIfNot(SmiEqual(tag, SmiConstant(JSRegExp::IRREGEXP)), &runtime);
289 :
290 : // Check (number_of_captures + 1) * 2 <= offsets vector size
291 : // Or number_of_captures <= offsets vector size / 2 - 1
292 : Node* const capture_count =
293 430 : LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureCountIndex);
294 : CSA_ASSERT(this, TaggedIsSmi(capture_count));
295 :
296 : STATIC_ASSERT(Isolate::kJSRegexpStaticOffsetsVectorSize >= 2);
297 : GotoIf(SmiAbove(
298 : capture_count,
299 : SmiConstant(Isolate::kJSRegexpStaticOffsetsVectorSize / 2 - 1)),
300 430 : &runtime);
301 : }
302 :
303 : // Unpack the string if possible.
304 :
305 430 : to_direct.TryToDirect(&runtime);
306 :
307 430 : Node* const smi_string_length = LoadStringLength(string);
308 :
309 : // Bail out to runtime for invalid {last_index} values.
310 430 : GotoIfNot(TaggedIsSmi(last_index), &runtime);
311 430 : GotoIf(SmiAboveOrEqual(last_index, smi_string_length), &runtime);
312 :
313 : // Load the irregexp code object and offsets into the subject string. Both
314 : // depend on whether the string is one- or two-byte.
315 :
316 430 : Node* const int_last_index = SmiUntag(last_index);
317 :
318 860 : VARIABLE(var_string_start, MachineType::PointerRepresentation());
319 860 : VARIABLE(var_string_end, MachineType::PointerRepresentation());
320 860 : VARIABLE(var_code, MachineRepresentation::kTagged);
321 :
322 : {
323 430 : Node* const int_string_length = SmiUntag(smi_string_length);
324 : Node* const direct_string_data = to_direct.PointerToData(&runtime);
325 :
326 430 : Label next(this), if_isonebyte(this), if_istwobyte(this, Label::kDeferred);
327 : Branch(IsOneByteStringInstanceType(to_direct.instance_type()),
328 430 : &if_isonebyte, &if_istwobyte);
329 :
330 430 : BIND(&if_isonebyte);
331 : {
332 : GetStringPointers(direct_string_data, to_direct.offset(), int_last_index,
333 : int_string_length, String::ONE_BYTE_ENCODING,
334 430 : &var_string_start, &var_string_end);
335 : var_code.Bind(
336 430 : LoadFixedArrayElement(data, JSRegExp::kIrregexpLatin1CodeIndex));
337 430 : Goto(&next);
338 : }
339 :
340 430 : BIND(&if_istwobyte);
341 : {
342 : GetStringPointers(direct_string_data, to_direct.offset(), int_last_index,
343 : int_string_length, String::TWO_BYTE_ENCODING,
344 430 : &var_string_start, &var_string_end);
345 : var_code.Bind(
346 430 : LoadFixedArrayElement(data, JSRegExp::kIrregexpUC16CodeIndex));
347 430 : Goto(&next);
348 : }
349 :
350 860 : BIND(&next);
351 : }
352 :
353 : // Check that the irregexp code has been generated for the actual string
354 : // encoding. If it has, the field contains a code object otherwise it contains
355 : // smi (code flushing support).
356 :
357 430 : Node* const code = var_code.value();
358 430 : GotoIf(TaggedIsSmi(code), &runtime);
359 : CSA_ASSERT(this, HasInstanceType(code, CODE_TYPE));
360 :
361 430 : Label if_success(this), if_failure(this),
362 430 : if_exception(this, Label::kDeferred);
363 : {
364 430 : IncrementCounter(isolate()->counters()->regexp_entry_native(), 1);
365 :
366 : // Set up args for the final call into generated Irregexp code.
367 :
368 : MachineType type_int32 = MachineType::Int32();
369 : MachineType type_tagged = MachineType::AnyTagged();
370 : MachineType type_ptr = MachineType::Pointer();
371 :
372 : // Result: A NativeRegExpMacroAssembler::Result return code.
373 430 : MachineType retval_type = type_int32;
374 :
375 : // Argument 0: Original subject string.
376 430 : MachineType arg0_type = type_tagged;
377 : Node* const arg0 = string;
378 :
379 : // Argument 1: Previous index.
380 430 : MachineType arg1_type = type_int32;
381 430 : Node* const arg1 = TruncateWordToWord32(int_last_index);
382 :
383 : // Argument 2: Start of string data.
384 430 : MachineType arg2_type = type_ptr;
385 430 : Node* const arg2 = var_string_start.value();
386 :
387 : // Argument 3: End of string data.
388 430 : MachineType arg3_type = type_ptr;
389 430 : Node* const arg3 = var_string_end.value();
390 :
391 : // Argument 4: static offsets vector buffer.
392 430 : MachineType arg4_type = type_ptr;
393 : Node* const arg4 = static_offsets_vector_address;
394 :
395 : // Argument 5: Set the number of capture registers to zero to force global
396 : // regexps to behave as non-global. This does not affect non-global
397 : // regexps.
398 430 : MachineType arg5_type = type_int32;
399 430 : Node* const arg5 = Int32Constant(0);
400 :
401 : // Argument 6: Start (high end) of backtracking stack memory area.
402 : Node* const stack_start =
403 430 : Load(MachineType::Pointer(), regexp_stack_memory_address_address);
404 : Node* const stack_size =
405 430 : Load(MachineType::IntPtr(), regexp_stack_memory_size_address);
406 430 : Node* const stack_end = IntPtrAdd(stack_start, stack_size);
407 :
408 430 : MachineType arg6_type = type_ptr;
409 : Node* const arg6 = stack_end;
410 :
411 : // Argument 7: Indicate that this is a direct call from JavaScript.
412 430 : MachineType arg7_type = type_int32;
413 430 : Node* const arg7 = Int32Constant(1);
414 :
415 : // Argument 8: Pass current isolate address.
416 430 : MachineType arg8_type = type_ptr;
417 : Node* const arg8 = isolate_address;
418 :
419 : Node* const code_entry =
420 : IntPtrAdd(BitcastTaggedToWord(code),
421 430 : IntPtrConstant(Code::kHeaderSize - kHeapObjectTag));
422 :
423 : Node* const result = CallCFunction9(
424 : retval_type, arg0_type, arg1_type, arg2_type, arg3_type, arg4_type,
425 : arg5_type, arg6_type, arg7_type, arg8_type, code_entry, arg0, arg1,
426 430 : arg2, arg3, arg4, arg5, arg6, arg7, arg8);
427 :
428 : // Check the result.
429 : // We expect exactly one result since we force the called regexp to behave
430 : // as non-global.
431 430 : Node* const int_result = ChangeInt32ToIntPtr(result);
432 : GotoIf(IntPtrEqual(int_result,
433 : IntPtrConstant(NativeRegExpMacroAssembler::SUCCESS)),
434 430 : &if_success);
435 : GotoIf(IntPtrEqual(int_result,
436 : IntPtrConstant(NativeRegExpMacroAssembler::FAILURE)),
437 430 : &if_failure);
438 : GotoIf(IntPtrEqual(int_result,
439 : IntPtrConstant(NativeRegExpMacroAssembler::EXCEPTION)),
440 430 : &if_exception);
441 :
442 : CSA_ASSERT(this,
443 : IntPtrEqual(int_result,
444 : IntPtrConstant(NativeRegExpMacroAssembler::RETRY)));
445 430 : Goto(&runtime);
446 : }
447 :
448 430 : BIND(&if_success);
449 : {
450 : // Check that the last match info has space for the capture registers and
451 : // the additional information. Ensure no overflow in add.
452 : STATIC_ASSERT(FixedArray::kMaxLength < kMaxInt - FixedArray::kLengthOffset);
453 : Node* const available_slots =
454 : SmiSub(LoadFixedArrayBaseLength(match_info),
455 430 : SmiConstant(RegExpMatchInfo::kLastMatchOverhead));
456 : Node* const capture_count =
457 430 : LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureCountIndex);
458 : // Calculate number of register_count = (capture_count + 1) * 2.
459 : Node* const register_count =
460 430 : SmiShl(SmiAdd(capture_count, SmiConstant(1)), 1);
461 430 : GotoIf(SmiGreaterThan(register_count, available_slots), &runtime);
462 :
463 : // Fill match_info.
464 :
465 : StoreFixedArrayElement(match_info, RegExpMatchInfo::kNumberOfCapturesIndex,
466 430 : register_count, SKIP_WRITE_BARRIER);
467 : StoreFixedArrayElement(match_info, RegExpMatchInfo::kLastSubjectIndex,
468 430 : string);
469 : StoreFixedArrayElement(match_info, RegExpMatchInfo::kLastInputIndex,
470 430 : string);
471 :
472 : // Fill match and capture offsets in match_info.
473 : {
474 : Node* const limit_offset = ElementOffsetFromIndex(
475 430 : register_count, INT32_ELEMENTS, SMI_PARAMETERS, 0);
476 :
477 : Node* const to_offset = ElementOffsetFromIndex(
478 : IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), FAST_ELEMENTS,
479 430 : INTPTR_PARAMETERS, RegExpMatchInfo::kHeaderSize - kHeapObjectTag);
480 430 : VARIABLE(var_to_offset, MachineType::PointerRepresentation(), to_offset);
481 :
482 430 : VariableList vars({&var_to_offset}, zone());
483 : BuildFastLoop(
484 : vars, int_zero, limit_offset,
485 430 : [=, &var_to_offset](Node* offset) {
486 : Node* const value = Load(MachineType::Int32(),
487 430 : static_offsets_vector_address, offset);
488 430 : Node* const smi_value = SmiFromWord32(value);
489 : StoreNoWriteBarrier(MachineRepresentation::kTagged, match_info,
490 430 : var_to_offset.value(), smi_value);
491 430 : Increment(var_to_offset, kPointerSize);
492 430 : },
493 860 : kInt32Size, INTPTR_PARAMETERS, IndexAdvanceMode::kPost);
494 : }
495 :
496 430 : var_result.Bind(match_info);
497 430 : Goto(&out);
498 : }
499 :
500 430 : BIND(&if_failure);
501 : {
502 430 : var_result.Bind(NullConstant());
503 430 : Goto(&out);
504 : }
505 :
506 430 : BIND(&if_exception);
507 : {
508 : // A stack overflow was detected in RegExp code.
509 : #ifdef DEBUG
510 : Node* const pending_exception_address = ExternalConstant(
511 : ExternalReference(Isolate::kPendingExceptionAddress, isolate()));
512 : CSA_ASSERT(this, IsTheHole(Load(MachineType::AnyTagged(),
513 : pending_exception_address)));
514 : #endif // DEBUG
515 430 : CallRuntime(Runtime::kThrowStackOverflow, context);
516 430 : Unreachable();
517 : }
518 :
519 430 : BIND(&runtime);
520 : {
521 : Node* const result = CallRuntime(Runtime::kRegExpExec, context, regexp,
522 430 : string, last_index, match_info);
523 430 : var_result.Bind(result);
524 430 : Goto(&out);
525 : }
526 :
527 430 : BIND(&out);
528 860 : return var_result.value();
529 : #endif // V8_INTERPRETED_REGEXP
530 : }
531 :
532 : // ES#sec-regexp.prototype.exec
533 : // RegExp.prototype.exec ( string )
534 : // Implements the core of RegExp.prototype.exec but without actually
535 : // constructing the JSRegExpResult. Returns either null (if the RegExp did not
536 : // match) or a fixed array containing match indices as returned by
537 : // RegExpExecStub.
538 301 : Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult(
539 : Node* const context, Node* const regexp, Node* const string,
540 : Label* if_didnotmatch, const bool is_fastpath) {
541 301 : Node* const null = NullConstant();
542 301 : Node* const int_zero = IntPtrConstant(0);
543 301 : Node* const smi_zero = SmiConstant(Smi::kZero);
544 :
545 301 : if (is_fastpath) {
546 : CSA_ASSERT(this, IsFastRegExpNoPrototype(context, regexp));
547 : } else {
548 : ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE,
549 43 : "RegExp.prototype.exec");
550 : }
551 :
552 : CSA_ASSERT(this, IsString(string));
553 : CSA_ASSERT(this, IsJSRegExp(regexp));
554 :
555 301 : VARIABLE(var_result, MachineRepresentation::kTagged);
556 301 : Label out(this);
557 :
558 : // Load lastIndex.
559 602 : VARIABLE(var_lastindex, MachineRepresentation::kTagged);
560 : {
561 301 : Node* const regexp_lastindex = LoadLastIndex(context, regexp, is_fastpath);
562 301 : var_lastindex.Bind(regexp_lastindex);
563 :
564 301 : if (is_fastpath) {
565 : // ToLength on a positive smi is a nop and can be skipped.
566 : CSA_ASSERT(this, TaggedIsPositiveSmi(regexp_lastindex));
567 : } else {
568 : // Omit ToLength if lastindex is a non-negative smi.
569 43 : Label call_tolength(this, Label::kDeferred), next(this);
570 43 : Branch(TaggedIsPositiveSmi(regexp_lastindex), &next, &call_tolength);
571 :
572 43 : BIND(&call_tolength);
573 : {
574 : var_lastindex.Bind(
575 43 : CallBuiltin(Builtins::kToLength, context, regexp_lastindex));
576 43 : Goto(&next);
577 : }
578 :
579 86 : BIND(&next);
580 : }
581 : }
582 :
583 : // Check whether the regexp is global or sticky, which determines whether we
584 : // update last index later on.
585 301 : Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset);
586 : Node* const is_global_or_sticky = WordAnd(
587 301 : SmiUntag(flags), IntPtrConstant(JSRegExp::kGlobal | JSRegExp::kSticky));
588 : Node* const should_update_last_index =
589 301 : WordNotEqual(is_global_or_sticky, int_zero);
590 :
591 : // Grab and possibly update last index.
592 301 : Label run_exec(this);
593 : {
594 301 : Label if_doupdate(this), if_dontupdate(this);
595 301 : Branch(should_update_last_index, &if_doupdate, &if_dontupdate);
596 :
597 301 : BIND(&if_doupdate);
598 : {
599 301 : Node* const lastindex = var_lastindex.value();
600 :
601 : Label if_isoob(this, Label::kDeferred);
602 301 : GotoIfNot(TaggedIsSmi(lastindex), &if_isoob);
603 301 : Node* const string_length = LoadStringLength(string);
604 301 : GotoIfNot(SmiLessThanOrEqual(lastindex, string_length), &if_isoob);
605 301 : Goto(&run_exec);
606 :
607 301 : BIND(&if_isoob);
608 : {
609 301 : StoreLastIndex(context, regexp, smi_zero, is_fastpath);
610 301 : var_result.Bind(null);
611 301 : Goto(if_didnotmatch);
612 301 : }
613 : }
614 :
615 301 : BIND(&if_dontupdate);
616 : {
617 301 : var_lastindex.Bind(smi_zero);
618 301 : Goto(&run_exec);
619 301 : }
620 : }
621 :
622 : Node* match_indices;
623 301 : Label successful_match(this);
624 301 : BIND(&run_exec);
625 : {
626 : // Get last match info from the context.
627 301 : Node* const native_context = LoadNativeContext(context);
628 : Node* const last_match_info = LoadContextElement(
629 301 : native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
630 :
631 : // Call the exec stub.
632 : match_indices = RegExpExecInternal(context, regexp, string,
633 301 : var_lastindex.value(), last_match_info);
634 301 : var_result.Bind(match_indices);
635 :
636 : // {match_indices} is either null or the RegExpMatchInfo array.
637 : // Return early if exec failed, possibly updating last index.
638 301 : GotoIfNot(WordEqual(match_indices, null), &successful_match);
639 :
640 301 : GotoIfNot(should_update_last_index, if_didnotmatch);
641 :
642 301 : StoreLastIndex(context, regexp, smi_zero, is_fastpath);
643 301 : Goto(if_didnotmatch);
644 : }
645 :
646 301 : BIND(&successful_match);
647 : {
648 301 : GotoIfNot(should_update_last_index, &out);
649 :
650 : // Update the new last index from {match_indices}.
651 : Node* const new_lastindex = LoadFixedArrayElement(
652 301 : match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1);
653 :
654 301 : StoreLastIndex(context, regexp, new_lastindex, is_fastpath);
655 301 : Goto(&out);
656 : }
657 :
658 301 : BIND(&out);
659 602 : return var_result.value();
660 : }
661 :
662 : // ES#sec-regexp.prototype.exec
663 : // RegExp.prototype.exec ( string )
664 129 : Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBody(Node* const context,
665 : Node* const regexp,
666 : Node* const string,
667 : const bool is_fastpath) {
668 129 : Node* const null = NullConstant();
669 :
670 129 : VARIABLE(var_result, MachineRepresentation::kTagged);
671 :
672 129 : Label if_didnotmatch(this), out(this);
673 : Node* const indices_or_null = RegExpPrototypeExecBodyWithoutResult(
674 129 : context, regexp, string, &if_didnotmatch, is_fastpath);
675 :
676 : // Successful match.
677 : {
678 : Node* const match_indices = indices_or_null;
679 : Node* const result =
680 129 : ConstructNewResultFromMatchInfo(context, regexp, match_indices, string);
681 129 : var_result.Bind(result);
682 129 : Goto(&out);
683 : }
684 :
685 129 : BIND(&if_didnotmatch);
686 : {
687 129 : var_result.Bind(null);
688 129 : Goto(&out);
689 : }
690 :
691 129 : BIND(&out);
692 258 : return var_result.value();
693 : }
694 :
695 430 : Node* RegExpBuiltinsAssembler::ThrowIfNotJSReceiver(
696 : Node* context, Node* maybe_receiver, MessageTemplate::Template msg_template,
697 : char const* method_name) {
698 860 : Label out(this), throw_exception(this, Label::kDeferred);
699 860 : VARIABLE(var_value_map, MachineRepresentation::kTagged);
700 :
701 430 : GotoIf(TaggedIsSmi(maybe_receiver), &throw_exception);
702 :
703 : // Load the instance type of the {value}.
704 430 : var_value_map.Bind(LoadMap(maybe_receiver));
705 430 : Node* const value_instance_type = LoadMapInstanceType(var_value_map.value());
706 :
707 430 : Branch(IsJSReceiverInstanceType(value_instance_type), &out, &throw_exception);
708 :
709 : // The {value} is not a compatible receiver for this method.
710 430 : BIND(&throw_exception);
711 : {
712 860 : Node* const message_id = SmiConstant(Smi::FromInt(msg_template));
713 : Node* const method_name_str = HeapConstant(
714 860 : isolate()->factory()->NewStringFromAsciiChecked(method_name, TENURED));
715 :
716 : Node* const value_str =
717 430 : CallBuiltin(Builtins::kToString, context, maybe_receiver);
718 :
719 : CallRuntime(Runtime::kThrowTypeError, context, message_id, method_name_str,
720 430 : value_str);
721 430 : Unreachable();
722 : }
723 :
724 430 : BIND(&out);
725 860 : return var_value_map.value();
726 : }
727 :
728 86 : Node* RegExpBuiltinsAssembler::IsFastRegExpNoPrototype(Node* const context,
729 : Node* const object,
730 : Node* const map) {
731 86 : Label out(this);
732 172 : VARIABLE(var_result, MachineRepresentation::kWord32);
733 :
734 86 : Node* const native_context = LoadNativeContext(context);
735 : Node* const regexp_fun =
736 86 : LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
737 : Node* const initial_map =
738 86 : LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
739 86 : Node* const has_initialmap = WordEqual(map, initial_map);
740 :
741 86 : var_result.Bind(has_initialmap);
742 86 : GotoIfNot(has_initialmap, &out);
743 :
744 : // The smi check is required to omit ToLength(lastIndex) calls with possible
745 : // user-code execution on the fast path.
746 : Node* const last_index = FastLoadLastIndex(object);
747 86 : var_result.Bind(TaggedIsPositiveSmi(last_index));
748 86 : Goto(&out);
749 :
750 86 : BIND(&out);
751 172 : return var_result.value();
752 : }
753 :
754 43 : Node* RegExpBuiltinsAssembler::IsFastRegExpNoPrototype(Node* const context,
755 : Node* const object) {
756 : CSA_ASSERT(this, TaggedIsNotSmi(object));
757 43 : return IsFastRegExpNoPrototype(context, object, LoadMap(object));
758 : }
759 :
760 : // RegExp fast path implementations rely on unmodified JSRegExp instances.
761 : // We use a fairly coarse granularity for this and simply check whether both
762 : // the regexp itself is unmodified (i.e. its map has not changed), its
763 : // prototype is unmodified, and lastIndex is a non-negative smi.
764 387 : void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context,
765 : Node* const object,
766 : Node* const map,
767 : Label* const if_isunmodified,
768 : Label* const if_ismodified) {
769 : CSA_ASSERT(this, WordEqual(LoadMap(object), map));
770 :
771 : // TODO(ishell): Update this check once map changes for constant field
772 : // tracking are landing.
773 :
774 387 : Node* const native_context = LoadNativeContext(context);
775 : Node* const regexp_fun =
776 387 : LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
777 : Node* const initial_map =
778 387 : LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
779 387 : Node* const has_initialmap = WordEqual(map, initial_map);
780 :
781 387 : GotoIfNot(has_initialmap, if_ismodified);
782 :
783 : Node* const initial_proto_initial_map =
784 387 : LoadContextElement(native_context, Context::REGEXP_PROTOTYPE_MAP_INDEX);
785 387 : Node* const proto_map = LoadMap(LoadMapPrototype(map));
786 : Node* const proto_has_initialmap =
787 387 : WordEqual(proto_map, initial_proto_initial_map);
788 :
789 387 : GotoIfNot(proto_has_initialmap, if_ismodified);
790 :
791 : // The smi check is required to omit ToLength(lastIndex) calls with possible
792 : // user-code execution on the fast path.
793 : Node* const last_index = FastLoadLastIndex(object);
794 387 : Branch(TaggedIsPositiveSmi(last_index), if_isunmodified, if_ismodified);
795 387 : }
796 :
797 301 : void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context,
798 : Node* const object,
799 : Label* const if_isunmodified,
800 : Label* const if_ismodified) {
801 : CSA_ASSERT(this, TaggedIsNotSmi(object));
802 : BranchIfFastRegExp(context, object, LoadMap(object), if_isunmodified,
803 301 : if_ismodified);
804 301 : }
805 :
806 0 : Node* RegExpBuiltinsAssembler::IsFastRegExp(Node* const context,
807 : Node* const object) {
808 0 : Label yup(this), nope(this), out(this);
809 0 : VARIABLE(var_result, MachineRepresentation::kWord32);
810 :
811 0 : BranchIfFastRegExp(context, object, &yup, &nope);
812 :
813 0 : BIND(&yup);
814 0 : var_result.Bind(Int32Constant(1));
815 0 : Goto(&out);
816 :
817 0 : BIND(&nope);
818 0 : var_result.Bind(Int32Constant(0));
819 0 : Goto(&out);
820 :
821 0 : BIND(&out);
822 0 : return var_result.value();
823 : }
824 :
825 86 : void RegExpBuiltinsAssembler::BranchIfFastRegExpResult(Node* const context,
826 : Node* const object,
827 : Label* if_isunmodified,
828 : Label* if_ismodified) {
829 : // Could be a Smi.
830 86 : Node* const map = LoadReceiverMap(object);
831 :
832 86 : Node* const native_context = LoadNativeContext(context);
833 : Node* const initial_regexp_result_map =
834 86 : LoadContextElement(native_context, Context::REGEXP_RESULT_MAP_INDEX);
835 :
836 : Branch(WordEqual(map, initial_regexp_result_map), if_isunmodified,
837 86 : if_ismodified);
838 86 : }
839 :
840 : // Slow path stub for RegExpPrototypeExec to decrease code size.
841 172 : TF_BUILTIN(RegExpPrototypeExecSlow, RegExpBuiltinsAssembler) {
842 : Node* const regexp = Parameter(Descriptor::kReceiver);
843 : Node* const string = Parameter(Descriptor::kString);
844 : Node* const context = Parameter(Descriptor::kContext);
845 :
846 43 : Return(RegExpPrototypeExecBody(context, regexp, string, false));
847 43 : }
848 :
849 : // ES#sec-regexp.prototype.exec
850 : // RegExp.prototype.exec ( string )
851 172 : TF_BUILTIN(RegExpPrototypeExec, RegExpBuiltinsAssembler) {
852 : Node* const maybe_receiver = Parameter(Descriptor::kReceiver);
853 : Node* const maybe_string = Parameter(Descriptor::kString);
854 : Node* const context = Parameter(Descriptor::kContext);
855 :
856 : // Ensure {maybe_receiver} is a JSRegExp.
857 : ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE,
858 43 : "RegExp.prototype.exec");
859 : Node* const receiver = maybe_receiver;
860 :
861 : // Convert {maybe_string} to a String.
862 43 : Node* const string = ToString(context, maybe_string);
863 :
864 43 : Label if_isfastpath(this), if_isslowpath(this);
865 : Branch(IsFastRegExpNoPrototype(context, receiver), &if_isfastpath,
866 43 : &if_isslowpath);
867 :
868 43 : BIND(&if_isfastpath);
869 : {
870 : Node* const result =
871 43 : RegExpPrototypeExecBody(context, receiver, string, true);
872 43 : Return(result);
873 : }
874 :
875 43 : BIND(&if_isslowpath);
876 : {
877 : Node* const result = CallBuiltin(Builtins::kRegExpPrototypeExecSlow,
878 43 : context, receiver, string);
879 43 : Return(result);
880 43 : }
881 43 : }
882 :
883 172 : Node* RegExpBuiltinsAssembler::FlagsGetter(Node* const context,
884 : Node* const regexp,
885 : bool is_fastpath) {
886 172 : Isolate* isolate = this->isolate();
887 :
888 172 : Node* const int_zero = IntPtrConstant(0);
889 172 : Node* const int_one = IntPtrConstant(1);
890 172 : VARIABLE(var_length, MachineType::PointerRepresentation(), int_zero);
891 344 : VARIABLE(var_flags, MachineType::PointerRepresentation());
892 :
893 172 : Node* const is_dotall_enabled = IsDotAllEnabled(isolate);
894 :
895 : // First, count the number of characters we will need and check which flags
896 : // are set.
897 :
898 172 : if (is_fastpath) {
899 : // Refer to JSRegExp's flag property on the fast-path.
900 : CSA_ASSERT(this, IsJSRegExp(regexp));
901 129 : Node* const flags_smi = LoadObjectField(regexp, JSRegExp::kFlagsOffset);
902 129 : Node* const flags_intptr = SmiUntag(flags_smi);
903 129 : var_flags.Bind(flags_intptr);
904 :
905 : #define CASE_FOR_FLAG(FLAG) \
906 : do { \
907 : Label next(this); \
908 : GotoIfNot(IsSetWord(flags_intptr, FLAG), &next); \
909 : var_length.Bind(IntPtrAdd(var_length.value(), int_one)); \
910 : Goto(&next); \
911 : BIND(&next); \
912 : } while (false)
913 :
914 129 : CASE_FOR_FLAG(JSRegExp::kGlobal);
915 129 : CASE_FOR_FLAG(JSRegExp::kIgnoreCase);
916 129 : CASE_FOR_FLAG(JSRegExp::kMultiline);
917 : {
918 : Label next(this);
919 129 : GotoIfNot(is_dotall_enabled, &next);
920 129 : CASE_FOR_FLAG(JSRegExp::kDotAll);
921 129 : Goto(&next);
922 129 : BIND(&next);
923 : }
924 129 : CASE_FOR_FLAG(JSRegExp::kUnicode);
925 129 : CASE_FOR_FLAG(JSRegExp::kSticky);
926 : #undef CASE_FOR_FLAG
927 : } else {
928 : DCHECK(!is_fastpath);
929 :
930 : // Fall back to GetProperty stub on the slow-path.
931 43 : var_flags.Bind(int_zero);
932 :
933 : #define CASE_FOR_FLAG(NAME, FLAG) \
934 : do { \
935 : Label next(this); \
936 : Node* const flag = GetProperty( \
937 : context, regexp, isolate->factory()->InternalizeUtf8String(NAME)); \
938 : Label if_isflagset(this); \
939 : BranchIfToBooleanIsTrue(flag, &if_isflagset, &next); \
940 : BIND(&if_isflagset); \
941 : var_length.Bind(IntPtrAdd(var_length.value(), int_one)); \
942 : var_flags.Bind(WordOr(var_flags.value(), IntPtrConstant(FLAG))); \
943 : Goto(&next); \
944 : BIND(&next); \
945 : } while (false)
946 :
947 129 : CASE_FOR_FLAG("global", JSRegExp::kGlobal);
948 129 : CASE_FOR_FLAG("ignoreCase", JSRegExp::kIgnoreCase);
949 129 : CASE_FOR_FLAG("multiline", JSRegExp::kMultiline);
950 : {
951 : Label next(this);
952 43 : GotoIfNot(is_dotall_enabled, &next);
953 129 : CASE_FOR_FLAG("dotAll", JSRegExp::kDotAll);
954 43 : Goto(&next);
955 43 : BIND(&next);
956 : }
957 129 : CASE_FOR_FLAG("unicode", JSRegExp::kUnicode);
958 129 : CASE_FOR_FLAG("sticky", JSRegExp::kSticky);
959 : #undef CASE_FOR_FLAG
960 : }
961 :
962 : // Allocate a string of the required length and fill it with the corresponding
963 : // char for each set flag.
964 :
965 : {
966 172 : Node* const result = AllocateSeqOneByteString(context, var_length.value());
967 172 : Node* const flags_intptr = var_flags.value();
968 :
969 172 : VARIABLE(var_offset, MachineType::PointerRepresentation(),
970 : IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag));
971 :
972 : #define CASE_FOR_FLAG(FLAG, CHAR) \
973 : do { \
974 : Label next(this); \
975 : GotoIfNot(IsSetWord(flags_intptr, FLAG), &next); \
976 : Node* const value = Int32Constant(CHAR); \
977 : StoreNoWriteBarrier(MachineRepresentation::kWord8, result, \
978 : var_offset.value(), value); \
979 : var_offset.Bind(IntPtrAdd(var_offset.value(), int_one)); \
980 : Goto(&next); \
981 : BIND(&next); \
982 : } while (false)
983 :
984 172 : CASE_FOR_FLAG(JSRegExp::kGlobal, 'g');
985 172 : CASE_FOR_FLAG(JSRegExp::kIgnoreCase, 'i');
986 172 : CASE_FOR_FLAG(JSRegExp::kMultiline, 'm');
987 : {
988 : Label next(this);
989 172 : GotoIfNot(is_dotall_enabled, &next);
990 172 : CASE_FOR_FLAG(JSRegExp::kDotAll, 's');
991 172 : Goto(&next);
992 172 : BIND(&next);
993 : }
994 172 : CASE_FOR_FLAG(JSRegExp::kUnicode, 'u');
995 172 : CASE_FOR_FLAG(JSRegExp::kSticky, 'y');
996 : #undef CASE_FOR_FLAG
997 :
998 344 : return result;
999 172 : }
1000 : }
1001 :
1002 : // ES#sec-isregexp IsRegExp ( argument )
1003 43 : Node* RegExpBuiltinsAssembler::IsRegExp(Node* const context,
1004 : Node* const maybe_receiver) {
1005 86 : Label out(this), if_isregexp(this);
1006 :
1007 86 : VARIABLE(var_result, MachineRepresentation::kWord32, Int32Constant(0));
1008 :
1009 43 : GotoIf(TaggedIsSmi(maybe_receiver), &out);
1010 43 : GotoIfNot(IsJSReceiver(maybe_receiver), &out);
1011 :
1012 : Node* const receiver = maybe_receiver;
1013 :
1014 : // Check @@match.
1015 : {
1016 : Node* const value =
1017 86 : GetProperty(context, receiver, isolate()->factory()->match_symbol());
1018 :
1019 43 : Label match_isundefined(this), match_isnotundefined(this);
1020 43 : Branch(IsUndefined(value), &match_isundefined, &match_isnotundefined);
1021 :
1022 43 : BIND(&match_isundefined);
1023 43 : Branch(IsJSRegExp(receiver), &if_isregexp, &out);
1024 :
1025 43 : BIND(&match_isnotundefined);
1026 86 : BranchIfToBooleanIsTrue(value, &if_isregexp, &out);
1027 : }
1028 :
1029 43 : BIND(&if_isregexp);
1030 43 : var_result.Bind(Int32Constant(1));
1031 43 : Goto(&out);
1032 :
1033 43 : BIND(&out);
1034 86 : return var_result.value();
1035 : }
1036 :
1037 : // ES#sec-regexpinitialize
1038 : // Runtime Semantics: RegExpInitialize ( obj, pattern, flags )
1039 86 : Node* RegExpBuiltinsAssembler::RegExpInitialize(Node* const context,
1040 : Node* const regexp,
1041 : Node* const maybe_pattern,
1042 : Node* const maybe_flags) {
1043 : CSA_ASSERT(this, IsJSRegExp(regexp));
1044 :
1045 : // Normalize pattern.
1046 : Node* const pattern =
1047 86 : Select(IsUndefined(maybe_pattern), [=] { return EmptyStringConstant(); },
1048 86 : [=] { return ToString(context, maybe_pattern); },
1049 258 : MachineRepresentation::kTagged);
1050 :
1051 : // Normalize flags.
1052 : Node* const flags =
1053 86 : Select(IsUndefined(maybe_flags), [=] { return EmptyStringConstant(); },
1054 86 : [=] { return ToString(context, maybe_flags); },
1055 258 : MachineRepresentation::kTagged);
1056 :
1057 : // Initialize.
1058 :
1059 : return CallRuntime(Runtime::kRegExpInitializeAndCompile, context, regexp,
1060 86 : pattern, flags);
1061 : }
1062 :
1063 : // ES #sec-get-regexp.prototype.flags
1064 172 : TF_BUILTIN(RegExpPrototypeFlagsGetter, RegExpBuiltinsAssembler) {
1065 : Node* const maybe_receiver = Parameter(Descriptor::kReceiver);
1066 : Node* const context = Parameter(Descriptor::kContext);
1067 :
1068 : Node* const map = ThrowIfNotJSReceiver(context, maybe_receiver,
1069 : MessageTemplate::kRegExpNonObject,
1070 43 : "RegExp.prototype.flags");
1071 : Node* const receiver = maybe_receiver;
1072 :
1073 43 : Label if_isfastpath(this), if_isslowpath(this, Label::kDeferred);
1074 : Branch(IsFastRegExpNoPrototype(context, receiver, map), &if_isfastpath,
1075 43 : &if_isslowpath);
1076 :
1077 43 : BIND(&if_isfastpath);
1078 43 : Return(FlagsGetter(context, receiver, true));
1079 :
1080 43 : BIND(&if_isslowpath);
1081 86 : Return(FlagsGetter(context, receiver, false));
1082 43 : }
1083 :
1084 : // ES#sec-regexp-pattern-flags
1085 : // RegExp ( pattern, flags )
1086 172 : TF_BUILTIN(RegExpConstructor, RegExpBuiltinsAssembler) {
1087 : Node* const pattern = Parameter(Descriptor::kPattern);
1088 : Node* const flags = Parameter(Descriptor::kFlags);
1089 : Node* const new_target = Parameter(Descriptor::kNewTarget);
1090 : Node* const context = Parameter(Descriptor::kContext);
1091 :
1092 43 : Isolate* isolate = this->isolate();
1093 :
1094 43 : VARIABLE(var_flags, MachineRepresentation::kTagged, flags);
1095 86 : VARIABLE(var_pattern, MachineRepresentation::kTagged, pattern);
1096 86 : VARIABLE(var_new_target, MachineRepresentation::kTagged, new_target);
1097 :
1098 43 : Node* const native_context = LoadNativeContext(context);
1099 : Node* const regexp_function =
1100 43 : LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
1101 :
1102 43 : Node* const pattern_is_regexp = IsRegExp(context, pattern);
1103 :
1104 : {
1105 : Label next(this);
1106 :
1107 43 : GotoIfNot(IsUndefined(new_target), &next);
1108 43 : var_new_target.Bind(regexp_function);
1109 :
1110 43 : GotoIfNot(pattern_is_regexp, &next);
1111 43 : GotoIfNot(IsUndefined(flags), &next);
1112 :
1113 : Node* const value =
1114 43 : GetProperty(context, pattern, isolate->factory()->constructor_string());
1115 :
1116 43 : GotoIfNot(WordEqual(value, regexp_function), &next);
1117 43 : Return(pattern);
1118 :
1119 43 : BIND(&next);
1120 : }
1121 :
1122 : {
1123 43 : Label next(this), if_patternisfastregexp(this),
1124 43 : if_patternisslowregexp(this);
1125 43 : GotoIf(TaggedIsSmi(pattern), &next);
1126 :
1127 43 : GotoIf(IsJSRegExp(pattern), &if_patternisfastregexp);
1128 :
1129 43 : Branch(pattern_is_regexp, &if_patternisslowregexp, &next);
1130 :
1131 43 : BIND(&if_patternisfastregexp);
1132 : {
1133 43 : Node* const source = LoadObjectField(pattern, JSRegExp::kSourceOffset);
1134 43 : var_pattern.Bind(source);
1135 :
1136 : {
1137 : Label inner_next(this);
1138 43 : GotoIfNot(IsUndefined(flags), &inner_next);
1139 :
1140 43 : Node* const value = FlagsGetter(context, pattern, true);
1141 43 : var_flags.Bind(value);
1142 43 : Goto(&inner_next);
1143 :
1144 43 : BIND(&inner_next);
1145 : }
1146 :
1147 43 : Goto(&next);
1148 : }
1149 :
1150 43 : BIND(&if_patternisslowregexp);
1151 : {
1152 : {
1153 : Node* const value =
1154 43 : GetProperty(context, pattern, isolate->factory()->source_string());
1155 43 : var_pattern.Bind(value);
1156 : }
1157 :
1158 : {
1159 : Label inner_next(this);
1160 43 : GotoIfNot(IsUndefined(flags), &inner_next);
1161 :
1162 : Node* const value =
1163 43 : GetProperty(context, pattern, isolate->factory()->flags_string());
1164 43 : var_flags.Bind(value);
1165 43 : Goto(&inner_next);
1166 :
1167 43 : BIND(&inner_next);
1168 : }
1169 :
1170 43 : Goto(&next);
1171 : }
1172 :
1173 86 : BIND(&next);
1174 : }
1175 :
1176 : // Allocate.
1177 :
1178 86 : VARIABLE(var_regexp, MachineRepresentation::kTagged);
1179 : {
1180 43 : Label allocate_jsregexp(this), allocate_generic(this, Label::kDeferred),
1181 43 : next(this);
1182 : Branch(WordEqual(var_new_target.value(), regexp_function),
1183 43 : &allocate_jsregexp, &allocate_generic);
1184 :
1185 43 : BIND(&allocate_jsregexp);
1186 : {
1187 : Node* const initial_map = LoadObjectField(
1188 43 : regexp_function, JSFunction::kPrototypeOrInitialMapOffset);
1189 43 : Node* const regexp = AllocateJSObjectFromMap(initial_map);
1190 43 : var_regexp.Bind(regexp);
1191 43 : Goto(&next);
1192 : }
1193 :
1194 43 : BIND(&allocate_generic);
1195 : {
1196 : ConstructorBuiltinsAssembler constructor_assembler(this->state());
1197 : Node* const regexp = constructor_assembler.EmitFastNewObject(
1198 43 : context, regexp_function, var_new_target.value());
1199 43 : var_regexp.Bind(regexp);
1200 43 : Goto(&next);
1201 : }
1202 :
1203 86 : BIND(&next);
1204 : }
1205 :
1206 : Node* const result = RegExpInitialize(context, var_regexp.value(),
1207 43 : var_pattern.value(), var_flags.value());
1208 86 : Return(result);
1209 43 : }
1210 :
1211 : // ES#sec-regexp.prototype.compile
1212 : // RegExp.prototype.compile ( pattern, flags )
1213 172 : TF_BUILTIN(RegExpPrototypeCompile, RegExpBuiltinsAssembler) {
1214 : Node* const maybe_receiver = Parameter(Descriptor::kReceiver);
1215 : Node* const maybe_pattern = Parameter(Descriptor::kPattern);
1216 : Node* const maybe_flags = Parameter(Descriptor::kFlags);
1217 : Node* const context = Parameter(Descriptor::kContext);
1218 :
1219 : ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE,
1220 43 : "RegExp.prototype.compile");
1221 : Node* const receiver = maybe_receiver;
1222 :
1223 43 : VARIABLE(var_flags, MachineRepresentation::kTagged, maybe_flags);
1224 86 : VARIABLE(var_pattern, MachineRepresentation::kTagged, maybe_pattern);
1225 :
1226 : // Handle a JSRegExp pattern.
1227 : {
1228 : Label next(this);
1229 :
1230 43 : GotoIf(TaggedIsSmi(maybe_pattern), &next);
1231 43 : GotoIfNot(IsJSRegExp(maybe_pattern), &next);
1232 :
1233 : Node* const pattern = maybe_pattern;
1234 :
1235 : // {maybe_flags} must be undefined in this case, otherwise throw.
1236 : {
1237 : Label next(this);
1238 43 : GotoIf(IsUndefined(maybe_flags), &next);
1239 :
1240 43 : Node* const message_id = SmiConstant(MessageTemplate::kRegExpFlags);
1241 43 : TailCallRuntime(Runtime::kThrowTypeError, context, message_id);
1242 :
1243 43 : BIND(&next);
1244 : }
1245 :
1246 43 : Node* const new_flags = FlagsGetter(context, pattern, true);
1247 43 : Node* const new_pattern = LoadObjectField(pattern, JSRegExp::kSourceOffset);
1248 :
1249 43 : var_flags.Bind(new_flags);
1250 43 : var_pattern.Bind(new_pattern);
1251 :
1252 43 : Goto(&next);
1253 43 : BIND(&next);
1254 : }
1255 :
1256 : Node* const result = RegExpInitialize(context, receiver, var_pattern.value(),
1257 43 : var_flags.value());
1258 86 : Return(result);
1259 43 : }
1260 :
1261 : // ES6 21.2.5.10.
1262 : // ES #sec-get-regexp.prototype.source
1263 172 : TF_BUILTIN(RegExpPrototypeSourceGetter, RegExpBuiltinsAssembler) {
1264 : Node* const receiver = Parameter(Descriptor::kReceiver);
1265 : Node* const context = Parameter(Descriptor::kContext);
1266 :
1267 : // Check whether we have an unmodified regexp instance.
1268 43 : Label if_isjsregexp(this), if_isnotjsregexp(this, Label::kDeferred);
1269 :
1270 43 : GotoIf(TaggedIsSmi(receiver), &if_isnotjsregexp);
1271 43 : Branch(IsJSRegExp(receiver), &if_isjsregexp, &if_isnotjsregexp);
1272 :
1273 43 : BIND(&if_isjsregexp);
1274 : {
1275 43 : Node* const source = LoadObjectField(receiver, JSRegExp::kSourceOffset);
1276 43 : Return(source);
1277 : }
1278 :
1279 43 : BIND(&if_isnotjsregexp);
1280 : {
1281 43 : Isolate* isolate = this->isolate();
1282 43 : Node* const native_context = LoadNativeContext(context);
1283 : Node* const regexp_fun =
1284 43 : LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
1285 : Node* const initial_map =
1286 43 : LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
1287 43 : Node* const initial_prototype = LoadMapPrototype(initial_map);
1288 :
1289 43 : Label if_isprototype(this), if_isnotprototype(this);
1290 : Branch(WordEqual(receiver, initial_prototype), &if_isprototype,
1291 43 : &if_isnotprototype);
1292 :
1293 43 : BIND(&if_isprototype);
1294 : {
1295 : const int counter = v8::Isolate::kRegExpPrototypeSourceGetter;
1296 43 : Node* const counter_smi = SmiConstant(counter);
1297 43 : CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi);
1298 :
1299 : Node* const result =
1300 86 : HeapConstant(isolate->factory()->NewStringFromAsciiChecked("(?:)"));
1301 43 : Return(result);
1302 : }
1303 :
1304 43 : BIND(&if_isnotprototype);
1305 : {
1306 : Node* const message_id =
1307 43 : SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonRegExp));
1308 : Node* const method_name_str =
1309 : HeapConstant(isolate->factory()->NewStringFromAsciiChecked(
1310 86 : "RegExp.prototype.source"));
1311 : TailCallRuntime(Runtime::kThrowTypeError, context, message_id,
1312 43 : method_name_str);
1313 43 : }
1314 43 : }
1315 43 : }
1316 :
1317 : // Fast-path implementation for flag checks on an unmodified JSRegExp instance.
1318 430 : Node* RegExpBuiltinsAssembler::FastFlagGetter(Node* const regexp,
1319 : JSRegExp::Flag flag) {
1320 430 : Node* const smi_zero = SmiConstant(Smi::kZero);
1321 430 : Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset);
1322 860 : Node* const mask = SmiConstant(Smi::FromInt(flag));
1323 430 : Node* const is_flag_set = WordNotEqual(SmiAnd(flags, mask), smi_zero);
1324 :
1325 430 : return is_flag_set;
1326 : }
1327 :
1328 : // Load through the GetProperty stub.
1329 86 : Node* RegExpBuiltinsAssembler::SlowFlagGetter(Node* const context,
1330 : Node* const regexp,
1331 : JSRegExp::Flag flag) {
1332 86 : Factory* factory = isolate()->factory();
1333 :
1334 : Label out(this);
1335 172 : VARIABLE(var_result, MachineRepresentation::kWord32);
1336 :
1337 : Handle<String> name;
1338 86 : switch (flag) {
1339 : case JSRegExp::kGlobal:
1340 : name = factory->global_string();
1341 43 : break;
1342 : case JSRegExp::kIgnoreCase:
1343 : name = factory->ignoreCase_string();
1344 0 : break;
1345 : case JSRegExp::kMultiline:
1346 : name = factory->multiline_string();
1347 0 : break;
1348 : case JSRegExp::kDotAll:
1349 0 : UNREACHABLE(); // Never called for dotAll.
1350 : break;
1351 : case JSRegExp::kSticky:
1352 : name = factory->sticky_string();
1353 0 : break;
1354 : case JSRegExp::kUnicode:
1355 : name = factory->unicode_string();
1356 43 : break;
1357 : default:
1358 0 : UNREACHABLE();
1359 : }
1360 :
1361 86 : Node* const value = GetProperty(context, regexp, name);
1362 :
1363 86 : Label if_true(this), if_false(this);
1364 86 : BranchIfToBooleanIsTrue(value, &if_true, &if_false);
1365 :
1366 86 : BIND(&if_true);
1367 : {
1368 86 : var_result.Bind(Int32Constant(1));
1369 86 : Goto(&out);
1370 : }
1371 :
1372 86 : BIND(&if_false);
1373 : {
1374 86 : var_result.Bind(Int32Constant(0));
1375 86 : Goto(&out);
1376 : }
1377 :
1378 86 : BIND(&out);
1379 258 : return var_result.value();
1380 : }
1381 :
1382 172 : Node* RegExpBuiltinsAssembler::FlagGetter(Node* const context,
1383 : Node* const regexp,
1384 : JSRegExp::Flag flag,
1385 : bool is_fastpath) {
1386 : return is_fastpath ? FastFlagGetter(regexp, flag)
1387 172 : : SlowFlagGetter(context, regexp, flag);
1388 : }
1389 :
1390 258 : void RegExpBuiltinsAssembler::FlagGetter(Node* context, Node* receiver,
1391 : JSRegExp::Flag flag, int counter,
1392 : const char* method_name) {
1393 258 : Isolate* isolate = this->isolate();
1394 :
1395 : // Check whether we have an unmodified regexp instance.
1396 : Label if_isunmodifiedjsregexp(this),
1397 258 : if_isnotunmodifiedjsregexp(this, Label::kDeferred);
1398 :
1399 258 : GotoIf(TaggedIsSmi(receiver), &if_isnotunmodifiedjsregexp);
1400 : Branch(IsJSRegExp(receiver), &if_isunmodifiedjsregexp,
1401 258 : &if_isnotunmodifiedjsregexp);
1402 :
1403 258 : BIND(&if_isunmodifiedjsregexp);
1404 : {
1405 : // Refer to JSRegExp's flag property on the fast-path.
1406 258 : Node* const is_flag_set = FastFlagGetter(receiver, flag);
1407 258 : Return(SelectBooleanConstant(is_flag_set));
1408 : }
1409 :
1410 258 : BIND(&if_isnotunmodifiedjsregexp);
1411 : {
1412 258 : Node* const native_context = LoadNativeContext(context);
1413 : Node* const regexp_fun =
1414 258 : LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
1415 : Node* const initial_map =
1416 258 : LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
1417 258 : Node* const initial_prototype = LoadMapPrototype(initial_map);
1418 :
1419 258 : Label if_isprototype(this), if_isnotprototype(this);
1420 : Branch(WordEqual(receiver, initial_prototype), &if_isprototype,
1421 258 : &if_isnotprototype);
1422 :
1423 258 : BIND(&if_isprototype);
1424 : {
1425 258 : if (counter != -1) {
1426 215 : Node* const counter_smi = SmiConstant(Smi::FromInt(counter));
1427 215 : CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi);
1428 : }
1429 258 : Return(UndefinedConstant());
1430 : }
1431 :
1432 258 : BIND(&if_isnotprototype);
1433 : {
1434 : Node* const message_id =
1435 258 : SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonRegExp));
1436 : Node* const method_name_str = HeapConstant(
1437 516 : isolate->factory()->NewStringFromAsciiChecked(method_name));
1438 : CallRuntime(Runtime::kThrowTypeError, context, message_id,
1439 258 : method_name_str);
1440 258 : Unreachable();
1441 258 : }
1442 258 : }
1443 258 : }
1444 :
1445 : // ES6 21.2.5.4.
1446 : // ES #sec-get-regexp.prototype.global
1447 172 : TF_BUILTIN(RegExpPrototypeGlobalGetter, RegExpBuiltinsAssembler) {
1448 : Node* context = Parameter(Descriptor::kContext);
1449 : Node* receiver = Parameter(Descriptor::kReceiver);
1450 : FlagGetter(context, receiver, JSRegExp::kGlobal,
1451 : v8::Isolate::kRegExpPrototypeOldFlagGetter,
1452 43 : "RegExp.prototype.global");
1453 43 : }
1454 :
1455 : // ES6 21.2.5.5.
1456 : // ES #sec-get-regexp.prototype.ignorecase
1457 172 : TF_BUILTIN(RegExpPrototypeIgnoreCaseGetter, RegExpBuiltinsAssembler) {
1458 : Node* context = Parameter(Descriptor::kContext);
1459 : Node* receiver = Parameter(Descriptor::kReceiver);
1460 : FlagGetter(context, receiver, JSRegExp::kIgnoreCase,
1461 : v8::Isolate::kRegExpPrototypeOldFlagGetter,
1462 43 : "RegExp.prototype.ignoreCase");
1463 43 : }
1464 :
1465 : // ES6 21.2.5.7.
1466 : // ES #sec-get-regexp.prototype.multiline
1467 172 : TF_BUILTIN(RegExpPrototypeMultilineGetter, RegExpBuiltinsAssembler) {
1468 : Node* context = Parameter(Descriptor::kContext);
1469 : Node* receiver = Parameter(Descriptor::kReceiver);
1470 : FlagGetter(context, receiver, JSRegExp::kMultiline,
1471 : v8::Isolate::kRegExpPrototypeOldFlagGetter,
1472 43 : "RegExp.prototype.multiline");
1473 43 : }
1474 :
1475 172 : Node* RegExpBuiltinsAssembler::IsDotAllEnabled(Isolate* isolate) {
1476 : Node* flag_ptr = ExternalConstant(
1477 172 : ExternalReference::address_of_regexp_dotall_flag(isolate));
1478 172 : Node* const flag_value = Load(MachineType::Int8(), flag_ptr);
1479 172 : return Word32NotEqual(flag_value, Int32Constant(0));
1480 : }
1481 :
1482 : // ES #sec-get-regexp.prototype.dotAll
1483 172 : TF_BUILTIN(RegExpPrototypeDotAllGetter, RegExpBuiltinsAssembler) {
1484 : Node* context = Parameter(Descriptor::kContext);
1485 : Node* receiver = Parameter(Descriptor::kReceiver);
1486 : static const int kNoCounter = -1;
1487 : CSA_ASSERT(this, IsDotAllEnabled(isolate()));
1488 : FlagGetter(context, receiver, JSRegExp::kDotAll, kNoCounter,
1489 43 : "RegExp.prototype.dotAll");
1490 43 : }
1491 :
1492 : // ES6 21.2.5.12.
1493 : // ES #sec-get-regexp.prototype.sticky
1494 172 : TF_BUILTIN(RegExpPrototypeStickyGetter, RegExpBuiltinsAssembler) {
1495 : Node* context = Parameter(Descriptor::kContext);
1496 : Node* receiver = Parameter(Descriptor::kReceiver);
1497 : FlagGetter(context, receiver, JSRegExp::kSticky,
1498 : v8::Isolate::kRegExpPrototypeStickyGetter,
1499 43 : "RegExp.prototype.sticky");
1500 43 : }
1501 :
1502 : // ES6 21.2.5.15.
1503 : // ES #sec-get-regexp.prototype.unicode
1504 172 : TF_BUILTIN(RegExpPrototypeUnicodeGetter, RegExpBuiltinsAssembler) {
1505 : Node* context = Parameter(Descriptor::kContext);
1506 : Node* receiver = Parameter(Descriptor::kReceiver);
1507 : FlagGetter(context, receiver, JSRegExp::kUnicode,
1508 : v8::Isolate::kRegExpPrototypeUnicodeGetter,
1509 43 : "RegExp.prototype.unicode");
1510 43 : }
1511 :
1512 : // ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S )
1513 172 : Node* RegExpBuiltinsAssembler::RegExpExec(Node* context, Node* regexp,
1514 : Node* string) {
1515 172 : VARIABLE(var_result, MachineRepresentation::kTagged);
1516 172 : Label out(this);
1517 :
1518 : // Take the slow path of fetching the exec property, calling it, and
1519 : // verifying its return value.
1520 :
1521 : // Get the exec property.
1522 : Node* const exec =
1523 344 : GetProperty(context, regexp, isolate()->factory()->exec_string());
1524 :
1525 : // Is {exec} callable?
1526 172 : Label if_iscallable(this), if_isnotcallable(this);
1527 :
1528 172 : GotoIf(TaggedIsSmi(exec), &if_isnotcallable);
1529 :
1530 172 : Node* const exec_map = LoadMap(exec);
1531 172 : Branch(IsCallableMap(exec_map), &if_iscallable, &if_isnotcallable);
1532 :
1533 172 : BIND(&if_iscallable);
1534 : {
1535 172 : Callable call_callable = CodeFactory::Call(isolate());
1536 172 : Node* const result = CallJS(call_callable, context, exec, regexp, string);
1537 :
1538 172 : var_result.Bind(result);
1539 172 : GotoIf(WordEqual(result, NullConstant()), &out);
1540 :
1541 : ThrowIfNotJSReceiver(context, result,
1542 172 : MessageTemplate::kInvalidRegExpExecResult, "unused");
1543 :
1544 172 : Goto(&out);
1545 : }
1546 :
1547 172 : BIND(&if_isnotcallable);
1548 : {
1549 : ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE,
1550 172 : "RegExp.prototype.exec");
1551 :
1552 : Node* const result = CallBuiltin(Builtins::kRegExpPrototypeExecSlow,
1553 172 : context, regexp, string);
1554 172 : var_result.Bind(result);
1555 172 : Goto(&out);
1556 : }
1557 :
1558 172 : BIND(&out);
1559 344 : return var_result.value();
1560 : }
1561 :
1562 : // ES#sec-regexp.prototype.test
1563 : // RegExp.prototype.test ( S )
1564 172 : TF_BUILTIN(RegExpPrototypeTest, RegExpBuiltinsAssembler) {
1565 : Node* const maybe_receiver = Parameter(Descriptor::kReceiver);
1566 : Node* const maybe_string = Parameter(Descriptor::kString);
1567 : Node* const context = Parameter(Descriptor::kContext);
1568 :
1569 : // Ensure {maybe_receiver} is a JSReceiver.
1570 : ThrowIfNotJSReceiver(context, maybe_receiver,
1571 : MessageTemplate::kIncompatibleMethodReceiver,
1572 43 : "RegExp.prototype.test");
1573 : Node* const receiver = maybe_receiver;
1574 :
1575 : // Convert {maybe_string} to a String.
1576 43 : Node* const string = ToString(context, maybe_string);
1577 :
1578 43 : Label fast_path(this), slow_path(this);
1579 43 : BranchIfFastRegExp(context, receiver, &fast_path, &slow_path);
1580 :
1581 43 : BIND(&fast_path);
1582 : {
1583 : Label if_didnotmatch(this);
1584 : RegExpPrototypeExecBodyWithoutResult(context, receiver, string,
1585 43 : &if_didnotmatch, true);
1586 43 : Return(TrueConstant());
1587 :
1588 43 : BIND(&if_didnotmatch);
1589 43 : Return(FalseConstant());
1590 : }
1591 :
1592 43 : BIND(&slow_path);
1593 : {
1594 : // Call exec.
1595 43 : Node* const match_indices = RegExpExec(context, receiver, string);
1596 :
1597 : // Return true iff exec matched successfully.
1598 : Node* const result =
1599 43 : SelectBooleanConstant(WordNotEqual(match_indices, NullConstant()));
1600 43 : Return(result);
1601 43 : }
1602 43 : }
1603 :
1604 129 : Node* RegExpBuiltinsAssembler::AdvanceStringIndex(Node* const string,
1605 : Node* const index,
1606 : Node* const is_unicode,
1607 : bool is_fastpath) {
1608 : CSA_ASSERT(this, IsString(string));
1609 : CSA_ASSERT(this, IsNumberNormalized(index));
1610 : if (is_fastpath) CSA_ASSERT(this, TaggedIsPositiveSmi(index));
1611 :
1612 : // Default to last_index + 1.
1613 129 : Node* const index_plus_one = NumberInc(index);
1614 129 : VARIABLE(var_result, MachineRepresentation::kTagged, index_plus_one);
1615 :
1616 : // Advancing the index has some subtle issues involving the distinction
1617 : // between Smis and HeapNumbers. There's three cases:
1618 : // * {index} is a Smi, {index_plus_one} is a Smi. The standard case.
1619 : // * {index} is a Smi, {index_plus_one} overflows into a HeapNumber.
1620 : // In this case we can return the result early, because
1621 : // {index_plus_one} > {string}.length.
1622 : // * {index} is a HeapNumber, {index_plus_one} is a HeapNumber. This can only
1623 : // occur when {index} is outside the Smi range since we normalize
1624 : // explicitly. Again we can return early.
1625 : if (is_fastpath) {
1626 : // Must be in Smi range on the fast path. We control the value of {index}
1627 : // on all call-sites and can never exceed the length of the string.
1628 : STATIC_ASSERT(String::kMaxLength + 2 < Smi::kMaxValue);
1629 : CSA_ASSERT(this, TaggedIsPositiveSmi(index_plus_one));
1630 : }
1631 :
1632 129 : Label if_isunicode(this), out(this);
1633 129 : GotoIfNot(is_unicode, &out);
1634 :
1635 : // Keep this unconditional (even on the fast path) just to be safe.
1636 129 : Branch(TaggedIsPositiveSmi(index_plus_one), &if_isunicode, &out);
1637 :
1638 129 : BIND(&if_isunicode);
1639 : {
1640 129 : Node* const string_length = LoadStringLength(string);
1641 129 : GotoIfNot(SmiLessThan(index_plus_one, string_length), &out);
1642 :
1643 129 : Node* const lead = StringCharCodeAt(string, index);
1644 : GotoIfNot(Word32Equal(Word32And(lead, Int32Constant(0xFC00)),
1645 : Int32Constant(0xD800)),
1646 129 : &out);
1647 :
1648 129 : Node* const trail = StringCharCodeAt(string, index_plus_one);
1649 : GotoIfNot(Word32Equal(Word32And(trail, Int32Constant(0xFC00)),
1650 : Int32Constant(0xDC00)),
1651 129 : &out);
1652 :
1653 : // At a surrogate pair, return index + 2.
1654 129 : Node* const index_plus_two = NumberInc(index_plus_one);
1655 129 : var_result.Bind(index_plus_two);
1656 :
1657 129 : Goto(&out);
1658 : }
1659 :
1660 129 : BIND(&out);
1661 258 : return var_result.value();
1662 : }
1663 :
1664 : namespace {
1665 :
1666 : // Utility class implementing a growable fixed array through CSA.
1667 129 : class GrowableFixedArray {
1668 : typedef CodeStubAssembler::Label Label;
1669 : typedef CodeStubAssembler::Variable Variable;
1670 :
1671 : public:
1672 129 : explicit GrowableFixedArray(CodeStubAssembler* a)
1673 : : assembler_(a),
1674 : var_array_(a, MachineRepresentation::kTagged),
1675 : var_length_(a, MachineType::PointerRepresentation()),
1676 129 : var_capacity_(a, MachineType::PointerRepresentation()) {
1677 129 : Initialize();
1678 129 : }
1679 :
1680 301 : Node* length() const { return var_length_.value(); }
1681 :
1682 : Variable* var_array() { return &var_array_; }
1683 : Variable* var_length() { return &var_length_; }
1684 : Variable* var_capacity() { return &var_capacity_; }
1685 :
1686 215 : void Push(Node* const value) {
1687 215 : CodeStubAssembler* a = assembler_;
1688 :
1689 215 : Node* const length = var_length_.value();
1690 215 : Node* const capacity = var_capacity_.value();
1691 :
1692 215 : Label grow(a), store(a);
1693 215 : a->Branch(a->IntPtrEqual(capacity, length), &grow, &store);
1694 :
1695 215 : a->BIND(&grow);
1696 : {
1697 215 : Node* const new_capacity = NewCapacity(a, capacity);
1698 215 : Node* const new_array = ResizeFixedArray(length, new_capacity);
1699 :
1700 215 : var_capacity_.Bind(new_capacity);
1701 215 : var_array_.Bind(new_array);
1702 215 : a->Goto(&store);
1703 : }
1704 :
1705 215 : a->BIND(&store);
1706 : {
1707 215 : Node* const array = var_array_.value();
1708 215 : a->StoreFixedArrayElement(array, length, value);
1709 :
1710 215 : Node* const new_length = a->IntPtrAdd(length, a->IntPtrConstant(1));
1711 215 : var_length_.Bind(new_length);
1712 215 : }
1713 215 : }
1714 :
1715 129 : Node* ToJSArray(Node* const context) {
1716 129 : CodeStubAssembler* a = assembler_;
1717 :
1718 : const ElementsKind kind = FAST_ELEMENTS;
1719 :
1720 129 : Node* const native_context = a->LoadNativeContext(context);
1721 129 : Node* const array_map = a->LoadJSArrayElementsMap(kind, native_context);
1722 :
1723 : // Shrink to fit if necessary.
1724 : {
1725 : Label next(a);
1726 :
1727 129 : Node* const length = var_length_.value();
1728 129 : Node* const capacity = var_capacity_.value();
1729 :
1730 129 : a->GotoIf(a->WordEqual(length, capacity), &next);
1731 :
1732 129 : Node* const array = ResizeFixedArray(length, length);
1733 129 : var_array_.Bind(array);
1734 129 : var_capacity_.Bind(length);
1735 129 : a->Goto(&next);
1736 :
1737 129 : a->BIND(&next);
1738 : }
1739 :
1740 129 : Node* const result_length = a->SmiTag(length());
1741 : Node* const result = a->AllocateUninitializedJSArrayWithoutElements(
1742 129 : kind, array_map, result_length, nullptr);
1743 :
1744 : // Note: We do not currently shrink the fixed array.
1745 :
1746 129 : a->StoreObjectField(result, JSObject::kElementsOffset, var_array_.value());
1747 :
1748 129 : return result;
1749 : }
1750 :
1751 : private:
1752 129 : void Initialize() {
1753 129 : CodeStubAssembler* a = assembler_;
1754 :
1755 : const ElementsKind kind = FAST_ELEMENTS;
1756 :
1757 : static const int kInitialArraySize = 8;
1758 129 : Node* const capacity = a->IntPtrConstant(kInitialArraySize);
1759 129 : Node* const array = a->AllocateFixedArray(kind, capacity);
1760 :
1761 : a->FillFixedArrayWithValue(kind, array, a->IntPtrConstant(0), capacity,
1762 129 : Heap::kTheHoleValueRootIndex);
1763 :
1764 129 : var_array_.Bind(array);
1765 129 : var_capacity_.Bind(capacity);
1766 129 : var_length_.Bind(a->IntPtrConstant(0));
1767 129 : }
1768 :
1769 215 : Node* NewCapacity(CodeStubAssembler* a, Node* const current_capacity) {
1770 : CSA_ASSERT(a, a->IntPtrGreaterThan(current_capacity, a->IntPtrConstant(0)));
1771 :
1772 : // Growth rate is analog to JSObject::NewElementsCapacity:
1773 : // new_capacity = (current_capacity + (current_capacity >> 1)) + 16.
1774 :
1775 : Node* const new_capacity = a->IntPtrAdd(
1776 : a->IntPtrAdd(current_capacity, a->WordShr(current_capacity, 1)),
1777 215 : a->IntPtrConstant(16));
1778 :
1779 215 : return new_capacity;
1780 : }
1781 :
1782 : // Creates a new array with {new_capacity} and copies the first
1783 : // {element_count} elements from the current array.
1784 344 : Node* ResizeFixedArray(Node* const element_count, Node* const new_capacity) {
1785 344 : CodeStubAssembler* a = assembler_;
1786 :
1787 : CSA_ASSERT(a, a->IntPtrGreaterThan(element_count, a->IntPtrConstant(0)));
1788 : CSA_ASSERT(a, a->IntPtrGreaterThan(new_capacity, a->IntPtrConstant(0)));
1789 : CSA_ASSERT(a, a->IntPtrGreaterThanOrEqual(new_capacity, element_count));
1790 :
1791 : const ElementsKind kind = FAST_ELEMENTS;
1792 : const WriteBarrierMode barrier_mode = UPDATE_WRITE_BARRIER;
1793 : const CodeStubAssembler::ParameterMode mode =
1794 : CodeStubAssembler::INTPTR_PARAMETERS;
1795 : const CodeStubAssembler::AllocationFlags flags =
1796 : CodeStubAssembler::kAllowLargeObjectAllocation;
1797 :
1798 344 : Node* const from_array = var_array_.value();
1799 : Node* const to_array =
1800 344 : a->AllocateFixedArray(kind, new_capacity, mode, flags);
1801 : a->CopyFixedArrayElements(kind, from_array, kind, to_array, element_count,
1802 344 : new_capacity, barrier_mode, mode);
1803 :
1804 344 : return to_array;
1805 : }
1806 :
1807 : private:
1808 : CodeStubAssembler* const assembler_;
1809 : Variable var_array_;
1810 : Variable var_length_;
1811 : Variable var_capacity_;
1812 : };
1813 :
1814 : } // namespace
1815 :
1816 86 : void RegExpBuiltinsAssembler::RegExpPrototypeMatchBody(Node* const context,
1817 : Node* const regexp,
1818 : Node* const string,
1819 : const bool is_fastpath) {
1820 : CSA_ASSERT(this, IsString(string));
1821 : if (is_fastpath) CSA_ASSERT(this, IsFastRegExp(context, regexp));
1822 :
1823 86 : Node* const null = NullConstant();
1824 86 : Node* const int_zero = IntPtrConstant(0);
1825 86 : Node* const smi_zero = SmiConstant(Smi::kZero);
1826 :
1827 : Node* const is_global =
1828 86 : FlagGetter(context, regexp, JSRegExp::kGlobal, is_fastpath);
1829 :
1830 86 : Label if_isglobal(this), if_isnotglobal(this);
1831 86 : Branch(is_global, &if_isglobal, &if_isnotglobal);
1832 :
1833 86 : BIND(&if_isnotglobal);
1834 : {
1835 : Node* const result =
1836 : is_fastpath ? RegExpPrototypeExecBody(context, regexp, string, true)
1837 86 : : RegExpExec(context, regexp, string);
1838 86 : Return(result);
1839 : }
1840 :
1841 86 : BIND(&if_isglobal);
1842 : {
1843 : Node* const is_unicode =
1844 86 : FlagGetter(context, regexp, JSRegExp::kUnicode, is_fastpath);
1845 :
1846 86 : StoreLastIndex(context, regexp, smi_zero, is_fastpath);
1847 :
1848 : // Allocate an array to store the resulting match strings.
1849 :
1850 86 : GrowableFixedArray array(this);
1851 :
1852 : // Loop preparations. Within the loop, collect results from RegExpExec
1853 : // and store match strings in the array.
1854 :
1855 : Variable* vars[] = {array.var_array(), array.var_length(),
1856 86 : array.var_capacity()};
1857 172 : Label loop(this, 3, vars), out(this);
1858 86 : Goto(&loop);
1859 :
1860 86 : BIND(&loop);
1861 : {
1862 86 : VARIABLE(var_match, MachineRepresentation::kTagged);
1863 :
1864 86 : Label if_didmatch(this), if_didnotmatch(this);
1865 86 : if (is_fastpath) {
1866 : // On the fast path, grab the matching string from the raw match index
1867 : // array.
1868 : Node* const match_indices = RegExpPrototypeExecBodyWithoutResult(
1869 43 : context, regexp, string, &if_didnotmatch, true);
1870 :
1871 : Node* const match_from = LoadFixedArrayElement(
1872 43 : match_indices, RegExpMatchInfo::kFirstCaptureIndex);
1873 : Node* const match_to = LoadFixedArrayElement(
1874 43 : match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1);
1875 :
1876 43 : Node* match = SubString(context, string, match_from, match_to);
1877 43 : var_match.Bind(match);
1878 :
1879 43 : Goto(&if_didmatch);
1880 : } else {
1881 : DCHECK(!is_fastpath);
1882 43 : Node* const result = RegExpExec(context, regexp, string);
1883 :
1884 : Label load_match(this);
1885 43 : Branch(WordEqual(result, null), &if_didnotmatch, &load_match);
1886 :
1887 43 : BIND(&load_match);
1888 : {
1889 43 : Label fast_result(this), slow_result(this);
1890 43 : BranchIfFastRegExpResult(context, result, &fast_result, &slow_result);
1891 :
1892 43 : BIND(&fast_result);
1893 : {
1894 43 : Node* const result_fixed_array = LoadElements(result);
1895 43 : Node* const match = LoadFixedArrayElement(result_fixed_array, 0);
1896 :
1897 : // The match is guaranteed to be a string on the fast path.
1898 : CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(match)));
1899 :
1900 43 : var_match.Bind(match);
1901 43 : Goto(&if_didmatch);
1902 : }
1903 :
1904 43 : BIND(&slow_result);
1905 : {
1906 : // TODO(ishell): Use GetElement stub once it's available.
1907 43 : Node* const match = GetProperty(context, result, smi_zero);
1908 43 : var_match.Bind(ToString(context, match));
1909 43 : Goto(&if_didmatch);
1910 43 : }
1911 43 : }
1912 : }
1913 :
1914 86 : BIND(&if_didnotmatch);
1915 : {
1916 : // Return null if there were no matches, otherwise just exit the loop.
1917 86 : GotoIfNot(IntPtrEqual(array.length(), int_zero), &out);
1918 86 : Return(null);
1919 : }
1920 :
1921 86 : BIND(&if_didmatch);
1922 : {
1923 86 : Node* match = var_match.value();
1924 :
1925 : // Store the match, growing the fixed array if needed.
1926 :
1927 86 : array.Push(match);
1928 :
1929 : // Advance last index if the match is the empty string.
1930 :
1931 86 : Node* const match_length = LoadStringLength(match);
1932 86 : GotoIfNot(SmiEqual(match_length, smi_zero), &loop);
1933 :
1934 86 : Node* last_index = LoadLastIndex(context, regexp, is_fastpath);
1935 86 : if (is_fastpath) {
1936 : CSA_ASSERT(this, TaggedIsPositiveSmi(last_index));
1937 : } else {
1938 43 : last_index = CallBuiltin(Builtins::kToLength, context, last_index);
1939 : }
1940 :
1941 : Node* const new_last_index =
1942 86 : AdvanceStringIndex(string, last_index, is_unicode, is_fastpath);
1943 :
1944 : if (is_fastpath) {
1945 : // On the fast path, we can be certain that lastIndex can never be
1946 : // incremented to overflow the Smi range since the maximal string
1947 : // length is less than the maximal Smi value.
1948 : STATIC_ASSERT(String::kMaxLength < Smi::kMaxValue);
1949 : CSA_ASSERT(this, TaggedIsPositiveSmi(new_last_index));
1950 : }
1951 :
1952 86 : StoreLastIndex(context, regexp, new_last_index, is_fastpath);
1953 :
1954 86 : Goto(&loop);
1955 86 : }
1956 : }
1957 :
1958 86 : BIND(&out);
1959 : {
1960 : // Wrap the match in a JSArray.
1961 :
1962 86 : Node* const result = array.ToJSArray(context);
1963 86 : Return(result);
1964 86 : }
1965 86 : }
1966 86 : }
1967 :
1968 : // ES#sec-regexp.prototype-@@match
1969 : // RegExp.prototype [ @@match ] ( string )
1970 172 : TF_BUILTIN(RegExpPrototypeMatch, RegExpBuiltinsAssembler) {
1971 : Node* const maybe_receiver = Parameter(Descriptor::kReceiver);
1972 : Node* const maybe_string = Parameter(Descriptor::kString);
1973 : Node* const context = Parameter(Descriptor::kContext);
1974 :
1975 : // Ensure {maybe_receiver} is a JSReceiver.
1976 : ThrowIfNotJSReceiver(context, maybe_receiver,
1977 : MessageTemplate::kIncompatibleMethodReceiver,
1978 43 : "RegExp.prototype.@@match");
1979 : Node* const receiver = maybe_receiver;
1980 :
1981 : // Convert {maybe_string} to a String.
1982 43 : Node* const string = ToString(context, maybe_string);
1983 :
1984 43 : Label fast_path(this), slow_path(this);
1985 43 : BranchIfFastRegExp(context, receiver, &fast_path, &slow_path);
1986 :
1987 43 : BIND(&fast_path);
1988 43 : RegExpPrototypeMatchBody(context, receiver, string, true);
1989 :
1990 43 : BIND(&slow_path);
1991 86 : RegExpPrototypeMatchBody(context, receiver, string, false);
1992 43 : }
1993 :
1994 43 : void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodyFast(
1995 : Node* const context, Node* const regexp, Node* const string) {
1996 : CSA_ASSERT(this, IsFastRegExp(context, regexp));
1997 : CSA_ASSERT(this, IsString(string));
1998 :
1999 : // Grab the initial value of last index.
2000 : Node* const previous_last_index = FastLoadLastIndex(regexp);
2001 :
2002 : // Ensure last index is 0.
2003 43 : FastStoreLastIndex(regexp, SmiConstant(Smi::kZero));
2004 :
2005 : // Call exec.
2006 : Label if_didnotmatch(this);
2007 : Node* const match_indices = RegExpPrototypeExecBodyWithoutResult(
2008 43 : context, regexp, string, &if_didnotmatch, true);
2009 :
2010 : // Successful match.
2011 : {
2012 : // Reset last index.
2013 : FastStoreLastIndex(regexp, previous_last_index);
2014 :
2015 : // Return the index of the match.
2016 : Node* const index = LoadFixedArrayElement(
2017 43 : match_indices, RegExpMatchInfo::kFirstCaptureIndex);
2018 43 : Return(index);
2019 : }
2020 :
2021 43 : BIND(&if_didnotmatch);
2022 : {
2023 : // Reset last index and return -1.
2024 : FastStoreLastIndex(regexp, previous_last_index);
2025 43 : Return(SmiConstant(-1));
2026 43 : }
2027 43 : }
2028 :
2029 43 : void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow(
2030 : Node* const context, Node* const regexp, Node* const string) {
2031 : CSA_ASSERT(this, IsJSReceiver(regexp));
2032 : CSA_ASSERT(this, IsString(string));
2033 :
2034 43 : Isolate* const isolate = this->isolate();
2035 :
2036 43 : Node* const smi_zero = SmiConstant(Smi::kZero);
2037 :
2038 : // Grab the initial value of last index.
2039 43 : Node* const previous_last_index = SlowLoadLastIndex(context, regexp);
2040 :
2041 : // Ensure last index is 0.
2042 : {
2043 : Label next(this);
2044 43 : GotoIf(SameValue(previous_last_index, smi_zero), &next);
2045 :
2046 43 : SlowStoreLastIndex(context, regexp, smi_zero);
2047 43 : Goto(&next);
2048 43 : BIND(&next);
2049 : }
2050 :
2051 : // Call exec.
2052 43 : Node* const exec_result = RegExpExec(context, regexp, string);
2053 :
2054 : // Reset last index if necessary.
2055 : {
2056 : Label next(this);
2057 43 : Node* const current_last_index = SlowLoadLastIndex(context, regexp);
2058 :
2059 43 : GotoIf(SameValue(current_last_index, previous_last_index), &next);
2060 :
2061 43 : SlowStoreLastIndex(context, regexp, previous_last_index);
2062 43 : Goto(&next);
2063 :
2064 43 : BIND(&next);
2065 : }
2066 :
2067 : // Return -1 if no match was found.
2068 : {
2069 : Label next(this);
2070 43 : GotoIfNot(WordEqual(exec_result, NullConstant()), &next);
2071 43 : Return(SmiConstant(-1));
2072 43 : BIND(&next);
2073 : }
2074 :
2075 : // Return the index of the match.
2076 : {
2077 43 : Label fast_result(this), slow_result(this, Label::kDeferred);
2078 43 : BranchIfFastRegExpResult(context, exec_result, &fast_result, &slow_result);
2079 :
2080 43 : BIND(&fast_result);
2081 : {
2082 : Node* const index =
2083 43 : LoadObjectField(exec_result, JSRegExpResult::kIndexOffset);
2084 43 : Return(index);
2085 : }
2086 :
2087 43 : BIND(&slow_result);
2088 : {
2089 : Return(GetProperty(context, exec_result,
2090 43 : isolate->factory()->index_string()));
2091 43 : }
2092 : }
2093 43 : }
2094 :
2095 : // ES#sec-regexp.prototype-@@search
2096 : // RegExp.prototype [ @@search ] ( string )
2097 172 : TF_BUILTIN(RegExpPrototypeSearch, RegExpBuiltinsAssembler) {
2098 : Node* const maybe_receiver = Parameter(Descriptor::kReceiver);
2099 : Node* const maybe_string = Parameter(Descriptor::kString);
2100 : Node* const context = Parameter(Descriptor::kContext);
2101 :
2102 : // Ensure {maybe_receiver} is a JSReceiver.
2103 : ThrowIfNotJSReceiver(context, maybe_receiver,
2104 : MessageTemplate::kIncompatibleMethodReceiver,
2105 43 : "RegExp.prototype.@@search");
2106 : Node* const receiver = maybe_receiver;
2107 :
2108 : // Convert {maybe_string} to a String.
2109 43 : Node* const string = ToString(context, maybe_string);
2110 :
2111 43 : Label fast_path(this), slow_path(this);
2112 43 : BranchIfFastRegExp(context, receiver, &fast_path, &slow_path);
2113 :
2114 43 : BIND(&fast_path);
2115 43 : RegExpPrototypeSearchBodyFast(context, receiver, string);
2116 :
2117 43 : BIND(&slow_path);
2118 86 : RegExpPrototypeSearchBodySlow(context, receiver, string);
2119 43 : }
2120 :
2121 : // Generates the fast path for @@split. {regexp} is an unmodified JSRegExp,
2122 : // {string} is a String, and {limit} is a Smi.
2123 43 : void RegExpBuiltinsAssembler::RegExpPrototypeSplitBody(Node* const context,
2124 : Node* const regexp,
2125 : Node* const string,
2126 : Node* const limit) {
2127 : CSA_ASSERT(this, IsFastRegExp(context, regexp));
2128 : CSA_ASSERT(this, TaggedIsSmi(limit));
2129 : CSA_ASSERT(this, IsString(string));
2130 :
2131 43 : Node* const null = NullConstant();
2132 43 : Node* const smi_zero = SmiConstant(0);
2133 43 : Node* const int_zero = IntPtrConstant(0);
2134 43 : Node* const int_limit = SmiUntag(limit);
2135 :
2136 : const ElementsKind kind = FAST_ELEMENTS;
2137 : const ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS;
2138 :
2139 : Node* const allocation_site = nullptr;
2140 43 : Node* const native_context = LoadNativeContext(context);
2141 43 : Node* const array_map = LoadJSArrayElementsMap(kind, native_context);
2142 :
2143 : Label return_empty_array(this, Label::kDeferred);
2144 :
2145 : // If limit is zero, return an empty array.
2146 : {
2147 43 : Label next(this), if_limitiszero(this, Label::kDeferred);
2148 43 : Branch(SmiEqual(limit, smi_zero), &return_empty_array, &next);
2149 86 : BIND(&next);
2150 : }
2151 :
2152 43 : Node* const string_length = LoadStringLength(string);
2153 :
2154 : // If passed the empty {string}, return either an empty array or a singleton
2155 : // array depending on whether the {regexp} matches.
2156 : {
2157 43 : Label next(this), if_stringisempty(this, Label::kDeferred);
2158 43 : Branch(SmiEqual(string_length, smi_zero), &if_stringisempty, &next);
2159 :
2160 43 : BIND(&if_stringisempty);
2161 : {
2162 : Node* const last_match_info = LoadContextElement(
2163 43 : native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
2164 :
2165 : Node* const match_indices = RegExpExecInternal(context, regexp, string,
2166 43 : smi_zero, last_match_info);
2167 :
2168 : Label return_singleton_array(this);
2169 : Branch(WordEqual(match_indices, null), &return_singleton_array,
2170 43 : &return_empty_array);
2171 :
2172 43 : BIND(&return_singleton_array);
2173 : {
2174 43 : Node* const length = SmiConstant(1);
2175 43 : Node* const capacity = IntPtrConstant(1);
2176 : Node* const result = AllocateJSArray(kind, array_map, capacity, length,
2177 43 : allocation_site, mode);
2178 :
2179 43 : Node* const fixed_array = LoadElements(result);
2180 43 : StoreFixedArrayElement(fixed_array, 0, string);
2181 :
2182 43 : Return(result);
2183 43 : }
2184 : }
2185 :
2186 86 : BIND(&next);
2187 : }
2188 :
2189 : // Loop preparations.
2190 :
2191 86 : GrowableFixedArray array(this);
2192 :
2193 86 : VARIABLE(var_last_matched_until, MachineRepresentation::kTagged);
2194 86 : VARIABLE(var_next_search_from, MachineRepresentation::kTagged);
2195 :
2196 43 : var_last_matched_until.Bind(smi_zero);
2197 43 : var_next_search_from.Bind(smi_zero);
2198 :
2199 : Variable* vars[] = {array.var_array(), array.var_length(),
2200 : array.var_capacity(), &var_last_matched_until,
2201 43 : &var_next_search_from};
2202 : const int vars_count = sizeof(vars) / sizeof(vars[0]);
2203 86 : Label loop(this, vars_count, vars), push_suffix_and_out(this), out(this);
2204 43 : Goto(&loop);
2205 :
2206 43 : BIND(&loop);
2207 : {
2208 43 : Node* const next_search_from = var_next_search_from.value();
2209 43 : Node* const last_matched_until = var_last_matched_until.value();
2210 :
2211 : CSA_ASSERT(this, TaggedIsSmi(next_search_from));
2212 : CSA_ASSERT(this, TaggedIsSmi(last_matched_until));
2213 :
2214 : // We're done if we've reached the end of the string.
2215 : {
2216 : Label next(this);
2217 : Branch(SmiEqual(next_search_from, string_length), &push_suffix_and_out,
2218 43 : &next);
2219 43 : BIND(&next);
2220 : }
2221 :
2222 : // Search for the given {regexp}.
2223 :
2224 : Node* const last_match_info = LoadContextElement(
2225 43 : native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
2226 :
2227 : Node* const match_indices = RegExpExecInternal(
2228 43 : context, regexp, string, next_search_from, last_match_info);
2229 :
2230 : // We're done if no match was found.
2231 : {
2232 : Label next(this);
2233 43 : Branch(WordEqual(match_indices, null), &push_suffix_and_out, &next);
2234 43 : BIND(&next);
2235 : }
2236 :
2237 : Node* const match_from = LoadFixedArrayElement(
2238 43 : match_indices, RegExpMatchInfo::kFirstCaptureIndex);
2239 :
2240 : // We're done if the match starts beyond the string.
2241 : {
2242 : Label next(this);
2243 43 : Branch(WordEqual(match_from, string_length), &push_suffix_and_out, &next);
2244 43 : BIND(&next);
2245 : }
2246 :
2247 : Node* const match_to = LoadFixedArrayElement(
2248 43 : match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1);
2249 :
2250 : // Advance index and continue if the match is empty.
2251 : {
2252 : Label next(this);
2253 :
2254 43 : GotoIfNot(SmiEqual(match_to, next_search_from), &next);
2255 43 : GotoIfNot(SmiEqual(match_to, last_matched_until), &next);
2256 :
2257 43 : Node* const is_unicode = FastFlagGetter(regexp, JSRegExp::kUnicode);
2258 : Node* const new_next_search_from =
2259 43 : AdvanceStringIndex(string, next_search_from, is_unicode, true);
2260 43 : var_next_search_from.Bind(new_next_search_from);
2261 43 : Goto(&loop);
2262 :
2263 43 : BIND(&next);
2264 : }
2265 :
2266 : // A valid match was found, add the new substring to the array.
2267 : {
2268 : Node* const from = last_matched_until;
2269 : Node* const to = match_from;
2270 :
2271 43 : Node* const substr = SubString(context, string, from, to);
2272 43 : array.Push(substr);
2273 :
2274 43 : GotoIf(WordEqual(array.length(), int_limit), &out);
2275 : }
2276 :
2277 : // Add all captures to the array.
2278 : {
2279 : Node* const num_registers = LoadFixedArrayElement(
2280 43 : match_indices, RegExpMatchInfo::kNumberOfCapturesIndex);
2281 43 : Node* const int_num_registers = SmiUntag(num_registers);
2282 :
2283 43 : VARIABLE(var_reg, MachineType::PointerRepresentation());
2284 43 : var_reg.Bind(IntPtrConstant(2));
2285 :
2286 : Variable* vars[] = {array.var_array(), array.var_length(),
2287 43 : array.var_capacity(), &var_reg};
2288 : const int vars_count = sizeof(vars) / sizeof(vars[0]);
2289 86 : Label nested_loop(this, vars_count, vars), nested_loop_out(this);
2290 : Branch(IntPtrLessThan(var_reg.value(), int_num_registers), &nested_loop,
2291 43 : &nested_loop_out);
2292 :
2293 43 : BIND(&nested_loop);
2294 : {
2295 43 : Node* const reg = var_reg.value();
2296 : Node* const from = LoadFixedArrayElement(
2297 : match_indices, reg,
2298 43 : RegExpMatchInfo::kFirstCaptureIndex * kPointerSize, mode);
2299 : Node* const to = LoadFixedArrayElement(
2300 : match_indices, reg,
2301 43 : (RegExpMatchInfo::kFirstCaptureIndex + 1) * kPointerSize, mode);
2302 :
2303 43 : Label select_capture(this), select_undefined(this), store_value(this);
2304 86 : VARIABLE(var_value, MachineRepresentation::kTagged);
2305 : Branch(SmiEqual(to, SmiConstant(-1)), &select_undefined,
2306 43 : &select_capture);
2307 :
2308 43 : BIND(&select_capture);
2309 : {
2310 43 : Node* const substr = SubString(context, string, from, to);
2311 43 : var_value.Bind(substr);
2312 43 : Goto(&store_value);
2313 : }
2314 :
2315 43 : BIND(&select_undefined);
2316 : {
2317 43 : Node* const undefined = UndefinedConstant();
2318 43 : var_value.Bind(undefined);
2319 43 : Goto(&store_value);
2320 : }
2321 :
2322 43 : BIND(&store_value);
2323 : {
2324 43 : array.Push(var_value.value());
2325 43 : GotoIf(WordEqual(array.length(), int_limit), &out);
2326 :
2327 43 : Node* const new_reg = IntPtrAdd(reg, IntPtrConstant(2));
2328 43 : var_reg.Bind(new_reg);
2329 :
2330 : Branch(IntPtrLessThan(new_reg, int_num_registers), &nested_loop,
2331 43 : &nested_loop_out);
2332 43 : }
2333 : }
2334 :
2335 86 : BIND(&nested_loop_out);
2336 : }
2337 :
2338 43 : var_last_matched_until.Bind(match_to);
2339 43 : var_next_search_from.Bind(match_to);
2340 43 : Goto(&loop);
2341 : }
2342 :
2343 43 : BIND(&push_suffix_and_out);
2344 : {
2345 43 : Node* const from = var_last_matched_until.value();
2346 : Node* const to = string_length;
2347 :
2348 43 : Node* const substr = SubString(context, string, from, to);
2349 43 : array.Push(substr);
2350 :
2351 43 : Goto(&out);
2352 : }
2353 :
2354 43 : BIND(&out);
2355 : {
2356 43 : Node* const result = array.ToJSArray(context);
2357 43 : Return(result);
2358 : }
2359 :
2360 43 : BIND(&return_empty_array);
2361 : {
2362 : Node* const length = smi_zero;
2363 : Node* const capacity = int_zero;
2364 : Node* const result = AllocateJSArray(kind, array_map, capacity, length,
2365 43 : allocation_site, mode);
2366 43 : Return(result);
2367 43 : }
2368 43 : }
2369 :
2370 : // Helper that skips a few initial checks.
2371 172 : TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) {
2372 : Node* const regexp = Parameter(Descriptor::kRegExp);
2373 : Node* const string = Parameter(Descriptor::kString);
2374 : Node* const maybe_limit = Parameter(Descriptor::kLimit);
2375 : Node* const context = Parameter(Descriptor::kContext);
2376 :
2377 : CSA_ASSERT(this, IsFastRegExp(context, regexp));
2378 : CSA_ASSERT(this, IsString(string));
2379 :
2380 : // TODO(jgruber): Even if map checks send us to the fast path, we still need
2381 : // to verify the constructor property and jump to the slow path if it has
2382 : // been changed.
2383 :
2384 : // Convert {maybe_limit} to a uint32, capping at the maximal smi value.
2385 43 : VARIABLE(var_limit, MachineRepresentation::kTagged, maybe_limit);
2386 43 : Label if_limitissmimax(this), limit_done(this), runtime(this);
2387 :
2388 43 : GotoIf(IsUndefined(maybe_limit), &if_limitissmimax);
2389 43 : GotoIf(TaggedIsPositiveSmi(maybe_limit), &limit_done);
2390 :
2391 43 : Node* const limit = ToUint32(context, maybe_limit);
2392 : {
2393 : // ToUint32(limit) could potentially change the shape of the RegExp
2394 : // object. Recheck that we are still on the fast path and bail to runtime
2395 : // otherwise.
2396 : {
2397 : Label next(this);
2398 43 : BranchIfFastRegExp(context, regexp, &next, &runtime);
2399 43 : BIND(&next);
2400 : }
2401 :
2402 43 : GotoIfNot(TaggedIsSmi(limit), &if_limitissmimax);
2403 :
2404 43 : var_limit.Bind(limit);
2405 43 : Goto(&limit_done);
2406 : }
2407 :
2408 43 : BIND(&if_limitissmimax);
2409 : {
2410 : // TODO(jgruber): In this case, we can probably avoid generation of limit
2411 : // checks in Generate_RegExpPrototypeSplitBody.
2412 43 : var_limit.Bind(SmiConstant(Smi::kMaxValue));
2413 43 : Goto(&limit_done);
2414 : }
2415 :
2416 43 : BIND(&limit_done);
2417 : {
2418 43 : Node* const limit = var_limit.value();
2419 43 : RegExpPrototypeSplitBody(context, regexp, string, limit);
2420 : }
2421 :
2422 43 : BIND(&runtime);
2423 : {
2424 : // The runtime call passes in limit to ensure the second ToUint32(limit)
2425 : // call is not observable.
2426 : CSA_ASSERT(this, IsNumber(limit));
2427 43 : Return(CallRuntime(Runtime::kRegExpSplit, context, regexp, string, limit));
2428 43 : }
2429 43 : }
2430 :
2431 : // ES#sec-regexp.prototype-@@split
2432 : // RegExp.prototype [ @@split ] ( string, limit )
2433 172 : TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) {
2434 : Node* const maybe_receiver = Parameter(Descriptor::kReceiver);
2435 : Node* const maybe_string = Parameter(Descriptor::kString);
2436 : Node* const maybe_limit = Parameter(Descriptor::kLimit);
2437 : Node* const context = Parameter(Descriptor::kContext);
2438 :
2439 : // Ensure {maybe_receiver} is a JSReceiver.
2440 : ThrowIfNotJSReceiver(context, maybe_receiver,
2441 : MessageTemplate::kIncompatibleMethodReceiver,
2442 43 : "RegExp.prototype.@@split");
2443 : Node* const receiver = maybe_receiver;
2444 :
2445 : // Convert {maybe_string} to a String.
2446 43 : Node* const string = ToString(context, maybe_string);
2447 :
2448 43 : Label stub(this), runtime(this, Label::kDeferred);
2449 43 : BranchIfFastRegExp(context, receiver, &stub, &runtime);
2450 :
2451 43 : BIND(&stub);
2452 : Return(CallBuiltin(Builtins::kRegExpSplit, context, receiver, string,
2453 43 : maybe_limit));
2454 :
2455 43 : BIND(&runtime);
2456 : Return(CallRuntime(Runtime::kRegExpSplit, context, receiver, string,
2457 86 : maybe_limit));
2458 43 : }
2459 :
2460 43 : Node* RegExpBuiltinsAssembler::ReplaceGlobalCallableFastPath(
2461 : Node* context, Node* regexp, Node* string, Node* replace_callable) {
2462 : // The fast path is reached only if {receiver} is a global unmodified
2463 : // JSRegExp instance and {replace_callable} is callable.
2464 :
2465 : CSA_ASSERT(this, IsFastRegExp(context, regexp));
2466 : CSA_ASSERT(this, IsCallable(replace_callable));
2467 : CSA_ASSERT(this, IsString(string));
2468 :
2469 43 : Isolate* const isolate = this->isolate();
2470 :
2471 43 : Node* const null = NullConstant();
2472 43 : Node* const undefined = UndefinedConstant();
2473 43 : Node* const int_zero = IntPtrConstant(0);
2474 43 : Node* const int_one = IntPtrConstant(1);
2475 43 : Node* const smi_zero = SmiConstant(Smi::kZero);
2476 :
2477 43 : Node* const native_context = LoadNativeContext(context);
2478 :
2479 : Label out(this);
2480 86 : VARIABLE(var_result, MachineRepresentation::kTagged);
2481 :
2482 : // Set last index to 0.
2483 : FastStoreLastIndex(regexp, smi_zero);
2484 :
2485 : // Allocate {result_array}.
2486 : Node* result_array;
2487 : {
2488 : ElementsKind kind = FAST_ELEMENTS;
2489 43 : Node* const array_map = LoadJSArrayElementsMap(kind, native_context);
2490 43 : Node* const capacity = IntPtrConstant(16);
2491 : Node* const length = smi_zero;
2492 : Node* const allocation_site = nullptr;
2493 : ParameterMode capacity_mode = CodeStubAssembler::INTPTR_PARAMETERS;
2494 :
2495 : result_array = AllocateJSArray(kind, array_map, capacity, length,
2496 43 : allocation_site, capacity_mode);
2497 : }
2498 :
2499 : // Call into runtime for RegExpExecMultiple.
2500 : Node* last_match_info =
2501 43 : LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
2502 : Node* const res = CallRuntime(Runtime::kRegExpExecMultiple, context, regexp,
2503 43 : string, last_match_info, result_array);
2504 :
2505 : // Reset last index to 0.
2506 : FastStoreLastIndex(regexp, smi_zero);
2507 :
2508 : // If no matches, return the subject string.
2509 43 : var_result.Bind(string);
2510 43 : GotoIf(WordEqual(res, null), &out);
2511 :
2512 : // Reload last match info since it might have changed.
2513 : last_match_info =
2514 43 : LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
2515 :
2516 43 : Node* const res_length = LoadJSArrayLength(res);
2517 43 : Node* const res_elems = LoadElements(res);
2518 : CSA_ASSERT(this, HasInstanceType(res_elems, FIXED_ARRAY_TYPE));
2519 :
2520 : Node* const num_capture_registers = LoadFixedArrayElement(
2521 43 : last_match_info, RegExpMatchInfo::kNumberOfCapturesIndex);
2522 :
2523 43 : Label if_hasexplicitcaptures(this), if_noexplicitcaptures(this),
2524 43 : create_result(this);
2525 : Branch(SmiEqual(num_capture_registers, SmiConstant(Smi::FromInt(2))),
2526 43 : &if_noexplicitcaptures, &if_hasexplicitcaptures);
2527 :
2528 43 : BIND(&if_noexplicitcaptures);
2529 : {
2530 : // If the number of captures is two then there are no explicit captures in
2531 : // the regexp, just the implicit capture that captures the whole match. In
2532 : // this case we can simplify quite a bit and end up with something faster.
2533 : // The builder will consist of some integers that indicate slices of the
2534 : // input string and some replacements that were returned from the replace
2535 : // function.
2536 :
2537 43 : VARIABLE(var_match_start, MachineRepresentation::kTagged);
2538 43 : var_match_start.Bind(smi_zero);
2539 :
2540 43 : Node* const end = SmiUntag(res_length);
2541 86 : VARIABLE(var_i, MachineType::PointerRepresentation());
2542 43 : var_i.Bind(int_zero);
2543 :
2544 43 : Variable* vars[] = {&var_i, &var_match_start};
2545 86 : Label loop(this, 2, vars);
2546 43 : Goto(&loop);
2547 43 : BIND(&loop);
2548 : {
2549 43 : Node* const i = var_i.value();
2550 43 : GotoIfNot(IntPtrLessThan(i, end), &create_result);
2551 :
2552 43 : Node* const elem = LoadFixedArrayElement(res_elems, i);
2553 :
2554 43 : Label if_issmi(this), if_isstring(this), loop_epilogue(this);
2555 43 : Branch(TaggedIsSmi(elem), &if_issmi, &if_isstring);
2556 :
2557 43 : BIND(&if_issmi);
2558 : {
2559 : // Integers represent slices of the original string.
2560 43 : Label if_isnegativeorzero(this), if_ispositive(this);
2561 : BranchIfSmiLessThanOrEqual(elem, smi_zero, &if_isnegativeorzero,
2562 43 : &if_ispositive);
2563 :
2564 43 : BIND(&if_ispositive);
2565 : {
2566 43 : Node* const int_elem = SmiUntag(elem);
2567 : Node* const new_match_start =
2568 : IntPtrAdd(WordShr(int_elem, IntPtrConstant(11)),
2569 43 : WordAnd(int_elem, IntPtrConstant(0x7ff)));
2570 43 : var_match_start.Bind(SmiTag(new_match_start));
2571 43 : Goto(&loop_epilogue);
2572 : }
2573 :
2574 43 : BIND(&if_isnegativeorzero);
2575 : {
2576 43 : Node* const next_i = IntPtrAdd(i, int_one);
2577 43 : var_i.Bind(next_i);
2578 :
2579 43 : Node* const next_elem = LoadFixedArrayElement(res_elems, next_i);
2580 :
2581 43 : Node* const new_match_start = SmiSub(next_elem, elem);
2582 43 : var_match_start.Bind(new_match_start);
2583 43 : Goto(&loop_epilogue);
2584 43 : }
2585 : }
2586 :
2587 43 : BIND(&if_isstring);
2588 : {
2589 : CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(elem)));
2590 :
2591 43 : Callable call_callable = CodeFactory::Call(isolate);
2592 : Node* const replacement_obj =
2593 : CallJS(call_callable, context, replace_callable, undefined, elem,
2594 43 : var_match_start.value(), string);
2595 :
2596 43 : Node* const replacement_str = ToString(context, replacement_obj);
2597 43 : StoreFixedArrayElement(res_elems, i, replacement_str);
2598 :
2599 43 : Node* const elem_length = LoadStringLength(elem);
2600 : Node* const new_match_start =
2601 43 : SmiAdd(var_match_start.value(), elem_length);
2602 43 : var_match_start.Bind(new_match_start);
2603 :
2604 43 : Goto(&loop_epilogue);
2605 : }
2606 :
2607 43 : BIND(&loop_epilogue);
2608 : {
2609 43 : var_i.Bind(IntPtrAdd(var_i.value(), int_one));
2610 43 : Goto(&loop);
2611 43 : }
2612 43 : }
2613 : }
2614 :
2615 43 : BIND(&if_hasexplicitcaptures);
2616 : {
2617 : Node* const from = int_zero;
2618 43 : Node* const to = SmiUntag(res_length);
2619 : const int increment = 1;
2620 :
2621 : BuildFastLoop(from, to,
2622 : [this, res_elems, isolate, native_context, context, undefined,
2623 43 : replace_callable](Node* index) {
2624 43 : Node* const elem = LoadFixedArrayElement(res_elems, index);
2625 :
2626 43 : Label do_continue(this);
2627 43 : GotoIf(TaggedIsSmi(elem), &do_continue);
2628 :
2629 : // elem must be an Array.
2630 : // Use the apply argument as backing for global RegExp
2631 : // properties.
2632 :
2633 : CSA_ASSERT(this, HasInstanceType(elem, JS_ARRAY_TYPE));
2634 :
2635 : // TODO(jgruber): Remove indirection through
2636 : // Call->ReflectApply.
2637 43 : Callable call_callable = CodeFactory::Call(isolate);
2638 : Node* const reflect_apply = LoadContextElement(
2639 43 : native_context, Context::REFLECT_APPLY_INDEX);
2640 :
2641 : Node* const replacement_obj =
2642 : CallJS(call_callable, context, reflect_apply, undefined,
2643 43 : replace_callable, undefined, elem);
2644 :
2645 : // Overwrite the i'th element in the results with the string
2646 : // we got back from the callback function.
2647 :
2648 : Node* const replacement_str =
2649 43 : ToString(context, replacement_obj);
2650 43 : StoreFixedArrayElement(res_elems, index, replacement_str);
2651 :
2652 43 : Goto(&do_continue);
2653 43 : BIND(&do_continue);
2654 43 : },
2655 : increment, CodeStubAssembler::INTPTR_PARAMETERS,
2656 86 : CodeStubAssembler::IndexAdvanceMode::kPost);
2657 :
2658 43 : Goto(&create_result);
2659 : }
2660 :
2661 43 : BIND(&create_result);
2662 : {
2663 : Node* const result = CallRuntime(Runtime::kStringBuilderConcat, context,
2664 43 : res, res_length, string);
2665 43 : var_result.Bind(result);
2666 43 : Goto(&out);
2667 : }
2668 :
2669 43 : BIND(&out);
2670 86 : return var_result.value();
2671 : }
2672 :
2673 43 : Node* RegExpBuiltinsAssembler::ReplaceSimpleStringFastPath(
2674 : Node* context, Node* regexp, Node* string, Node* replace_string) {
2675 : // The fast path is reached only if {receiver} is an unmodified
2676 : // JSRegExp instance, {replace_value} is non-callable, and
2677 : // ToString({replace_value}) does not contain '$', i.e. we're doing a simple
2678 : // string replacement.
2679 :
2680 43 : Node* const int_zero = IntPtrConstant(0);
2681 43 : Node* const smi_zero = SmiConstant(Smi::kZero);
2682 :
2683 : CSA_ASSERT(this, IsFastRegExp(context, regexp));
2684 : CSA_ASSERT(this, IsString(replace_string));
2685 : CSA_ASSERT(this, IsString(string));
2686 :
2687 : Label out(this);
2688 86 : VARIABLE(var_result, MachineRepresentation::kTagged);
2689 :
2690 : // Load the last match info.
2691 43 : Node* const native_context = LoadNativeContext(context);
2692 : Node* const last_match_info =
2693 43 : LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
2694 :
2695 : // Is {regexp} global?
2696 43 : Label if_isglobal(this), if_isnonglobal(this);
2697 43 : Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset);
2698 : Node* const is_global =
2699 43 : WordAnd(SmiUntag(flags), IntPtrConstant(JSRegExp::kGlobal));
2700 43 : Branch(WordEqual(is_global, int_zero), &if_isnonglobal, &if_isglobal);
2701 :
2702 43 : BIND(&if_isglobal);
2703 : {
2704 : // Hand off global regexps to runtime.
2705 : FastStoreLastIndex(regexp, smi_zero);
2706 : Node* const result =
2707 : CallRuntime(Runtime::kStringReplaceGlobalRegExpWithString, context,
2708 43 : string, regexp, replace_string, last_match_info);
2709 43 : var_result.Bind(result);
2710 43 : Goto(&out);
2711 : }
2712 :
2713 43 : BIND(&if_isnonglobal);
2714 : {
2715 : // Run exec, then manually construct the resulting string.
2716 : Label if_didnotmatch(this);
2717 : Node* const match_indices = RegExpPrototypeExecBodyWithoutResult(
2718 43 : context, regexp, string, &if_didnotmatch, true);
2719 :
2720 : // Successful match.
2721 : {
2722 : Node* const subject_start = smi_zero;
2723 : Node* const match_start = LoadFixedArrayElement(
2724 43 : match_indices, RegExpMatchInfo::kFirstCaptureIndex);
2725 : Node* const match_end = LoadFixedArrayElement(
2726 43 : match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1);
2727 43 : Node* const subject_end = LoadStringLength(string);
2728 :
2729 43 : Label if_replaceisempty(this), if_replaceisnotempty(this);
2730 43 : Node* const replace_length = LoadStringLength(replace_string);
2731 : Branch(SmiEqual(replace_length, smi_zero), &if_replaceisempty,
2732 43 : &if_replaceisnotempty);
2733 :
2734 43 : BIND(&if_replaceisempty);
2735 : {
2736 : // TODO(jgruber): We could skip many of the checks that using SubString
2737 : // here entails.
2738 :
2739 : Node* const first_part =
2740 43 : SubString(context, string, subject_start, match_start);
2741 : Node* const second_part =
2742 43 : SubString(context, string, match_end, subject_end);
2743 :
2744 43 : Node* const result = StringAdd(context, first_part, second_part);
2745 43 : var_result.Bind(result);
2746 43 : Goto(&out);
2747 : }
2748 :
2749 43 : BIND(&if_replaceisnotempty);
2750 : {
2751 : Node* const first_part =
2752 43 : SubString(context, string, subject_start, match_start);
2753 : Node* const second_part = replace_string;
2754 : Node* const third_part =
2755 43 : SubString(context, string, match_end, subject_end);
2756 :
2757 43 : Node* result = StringAdd(context, first_part, second_part);
2758 43 : result = StringAdd(context, result, third_part);
2759 :
2760 43 : var_result.Bind(result);
2761 43 : Goto(&out);
2762 43 : }
2763 : }
2764 :
2765 43 : BIND(&if_didnotmatch);
2766 : {
2767 43 : var_result.Bind(string);
2768 43 : Goto(&out);
2769 43 : }
2770 : }
2771 :
2772 43 : BIND(&out);
2773 86 : return var_result.value();
2774 : }
2775 :
2776 : // Helper that skips a few initial checks.
2777 172 : TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) {
2778 : Node* const regexp = Parameter(Descriptor::kRegExp);
2779 : Node* const string = Parameter(Descriptor::kString);
2780 : Node* const replace_value = Parameter(Descriptor::kReplaceValue);
2781 : Node* const context = Parameter(Descriptor::kContext);
2782 :
2783 : CSA_ASSERT(this, IsFastRegExp(context, regexp));
2784 : CSA_ASSERT(this, IsString(string));
2785 :
2786 43 : Label checkreplacestring(this), if_iscallable(this),
2787 43 : runtime(this, Label::kDeferred);
2788 :
2789 : // 2. Is {replace_value} callable?
2790 43 : GotoIf(TaggedIsSmi(replace_value), &checkreplacestring);
2791 : Branch(IsCallableMap(LoadMap(replace_value)), &if_iscallable,
2792 43 : &checkreplacestring);
2793 :
2794 : // 3. Does ToString({replace_value}) contain '$'?
2795 43 : BIND(&checkreplacestring);
2796 : {
2797 : Node* const replace_string =
2798 43 : CallBuiltin(Builtins::kToString, context, replace_value);
2799 :
2800 : // ToString(replaceValue) could potentially change the shape of the RegExp
2801 : // object. Recheck that we are still on the fast path and bail to runtime
2802 : // otherwise.
2803 : {
2804 : Label next(this);
2805 43 : BranchIfFastRegExp(context, regexp, &next, &runtime);
2806 43 : BIND(&next);
2807 : }
2808 :
2809 : Node* const dollar_string = HeapConstant(
2810 86 : isolate()->factory()->LookupSingleCharacterStringFromCode('$'));
2811 : Node* const dollar_ix =
2812 : CallBuiltin(Builtins::kStringIndexOf, context, replace_string,
2813 43 : dollar_string, SmiConstant(0));
2814 43 : GotoIfNot(SmiEqual(dollar_ix, SmiConstant(-1)), &runtime);
2815 :
2816 : Return(
2817 43 : ReplaceSimpleStringFastPath(context, regexp, string, replace_string));
2818 : }
2819 :
2820 : // {regexp} is unmodified and {replace_value} is callable.
2821 43 : BIND(&if_iscallable);
2822 : {
2823 : Node* const replace_fn = replace_value;
2824 :
2825 : // Check if the {regexp} is global.
2826 43 : Label if_isglobal(this), if_isnotglobal(this);
2827 :
2828 43 : Node* const is_global = FastFlagGetter(regexp, JSRegExp::kGlobal);
2829 43 : Branch(is_global, &if_isglobal, &if_isnotglobal);
2830 :
2831 43 : BIND(&if_isglobal);
2832 43 : Return(ReplaceGlobalCallableFastPath(context, regexp, string, replace_fn));
2833 :
2834 43 : BIND(&if_isnotglobal);
2835 : Return(CallRuntime(Runtime::kStringReplaceNonGlobalRegExpWithFunction,
2836 86 : context, string, regexp, replace_fn));
2837 : }
2838 :
2839 43 : BIND(&runtime);
2840 : Return(CallRuntime(Runtime::kRegExpReplace, context, regexp, string,
2841 86 : replace_value));
2842 43 : }
2843 :
2844 : // ES#sec-regexp.prototype-@@replace
2845 : // RegExp.prototype [ @@replace ] ( string, replaceValue )
2846 172 : TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) {
2847 : Node* const maybe_receiver = Parameter(Descriptor::kReceiver);
2848 : Node* const maybe_string = Parameter(Descriptor::kString);
2849 : Node* const replace_value = Parameter(Descriptor::kReplaceValue);
2850 : Node* const context = Parameter(Descriptor::kContext);
2851 :
2852 : // RegExpPrototypeReplace is a bit of a beast - a summary of dispatch logic:
2853 : //
2854 : // if (!IsFastRegExp(receiver)) CallRuntime(RegExpReplace)
2855 : // if (IsCallable(replace)) {
2856 : // if (IsGlobal(receiver)) {
2857 : // // Called 'fast-path' but contains several runtime calls.
2858 : // ReplaceGlobalCallableFastPath()
2859 : // } else {
2860 : // CallRuntime(StringReplaceNonGlobalRegExpWithFunction)
2861 : // }
2862 : // } else {
2863 : // if (replace.contains("$")) {
2864 : // CallRuntime(RegExpReplace)
2865 : // } else {
2866 : // ReplaceSimpleStringFastPath() // Bails to runtime for global regexps.
2867 : // }
2868 : // }
2869 :
2870 : // Ensure {maybe_receiver} is a JSReceiver.
2871 : ThrowIfNotJSReceiver(context, maybe_receiver,
2872 : MessageTemplate::kIncompatibleMethodReceiver,
2873 43 : "RegExp.prototype.@@replace");
2874 : Node* const receiver = maybe_receiver;
2875 :
2876 : // Convert {maybe_string} to a String.
2877 43 : Node* const string = CallBuiltin(Builtins::kToString, context, maybe_string);
2878 :
2879 : // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance?
2880 43 : Label stub(this), runtime(this, Label::kDeferred);
2881 43 : BranchIfFastRegExp(context, receiver, &stub, &runtime);
2882 :
2883 43 : BIND(&stub);
2884 : Return(CallBuiltin(Builtins::kRegExpReplace, context, receiver, string,
2885 43 : replace_value));
2886 :
2887 43 : BIND(&runtime);
2888 : Return(CallRuntime(Runtime::kRegExpReplace, context, receiver, string,
2889 86 : replace_value));
2890 43 : }
2891 :
2892 : // Simple string matching functionality for internal use which does not modify
2893 : // the last match info.
2894 172 : TF_BUILTIN(RegExpInternalMatch, RegExpBuiltinsAssembler) {
2895 : Node* const regexp = Parameter(Descriptor::kRegExp);
2896 : Node* const string = Parameter(Descriptor::kString);
2897 : Node* const context = Parameter(Descriptor::kContext);
2898 :
2899 43 : Node* const null = NullConstant();
2900 43 : Node* const smi_zero = SmiConstant(0);
2901 :
2902 : CSA_ASSERT(this, IsJSRegExp(regexp));
2903 : CSA_ASSERT(this, IsString(string));
2904 :
2905 43 : Node* const native_context = LoadNativeContext(context);
2906 : Node* const internal_match_info = LoadContextElement(
2907 43 : native_context, Context::REGEXP_INTERNAL_MATCH_INFO_INDEX);
2908 :
2909 : Node* const match_indices = RegExpExecInternal(context, regexp, string,
2910 43 : smi_zero, internal_match_info);
2911 :
2912 43 : Label if_matched(this), if_didnotmatch(this);
2913 43 : Branch(WordEqual(match_indices, null), &if_didnotmatch, &if_matched);
2914 :
2915 43 : BIND(&if_didnotmatch);
2916 43 : Return(null);
2917 :
2918 43 : BIND(&if_matched);
2919 : {
2920 : Node* result =
2921 43 : ConstructNewResultFromMatchInfo(context, regexp, match_indices, string);
2922 43 : Return(result);
2923 43 : }
2924 43 : }
2925 :
2926 : } // namespace internal
2927 : } // namespace v8
|