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