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