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