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/codegen/code-factory.h"
12 #include "src/codegen/code-stub-assembler.h"
13 #include "src/codegen/macro-assembler.h"
14 #include "src/execution/protectors.h"
15 #include "src/heap/factory-inl.h"
16 #include "src/logging/counters.h"
17 #include "src/objects/js-regexp-string-iterator.h"
18 #include "src/objects/js-regexp.h"
19 #include "src/objects/regexp-match-info.h"
20 #include "src/regexp/regexp.h"
21
22 namespace v8 {
23 namespace internal {
24
25 using compiler::Node;
26
27 // Tail calls the regular expression interpreter.
28 // static
Generate_RegExpInterpreterTrampoline(MacroAssembler * masm)29 void Builtins::Generate_RegExpInterpreterTrampoline(MacroAssembler* masm) {
30 ExternalReference interpreter_code_entry =
31 ExternalReference::re_match_for_call_from_js();
32 masm->Jump(interpreter_code_entry);
33 }
34
35 // Tail calls the experimental regular expression engine.
36 // static
Generate_RegExpExperimentalTrampoline(MacroAssembler * masm)37 void Builtins::Generate_RegExpExperimentalTrampoline(MacroAssembler* masm) {
38 ExternalReference interpreter_code_entry =
39 ExternalReference::re_experimental_match_for_call_from_js();
40 masm->Jump(interpreter_code_entry);
41 }
42
SmiZero()43 TNode<Smi> RegExpBuiltinsAssembler::SmiZero() { return SmiConstant(0); }
44
IntPtrZero()45 TNode<IntPtrT> RegExpBuiltinsAssembler::IntPtrZero() {
46 return IntPtrConstant(0);
47 }
48
49 // If code is a builtin, return the address to the (possibly embedded) builtin
50 // code entry, otherwise return the entry of the code object itself.
LoadCodeObjectEntry(TNode<Code> code)51 TNode<RawPtrT> RegExpBuiltinsAssembler::LoadCodeObjectEntry(TNode<Code> code) {
52 TVARIABLE(RawPtrT, var_result);
53
54 Label if_code_is_off_heap(this), out(this);
55 TNode<Int32T> builtin_index =
56 LoadObjectField<Int32T>(code, Code::kBuiltinIndexOffset);
57 {
58 GotoIfNot(Word32Equal(builtin_index, Int32Constant(Builtins::kNoBuiltinId)),
59 &if_code_is_off_heap);
60 var_result = ReinterpretCast<RawPtrT>(
61 IntPtrAdd(BitcastTaggedToWord(code),
62 IntPtrConstant(Code::kHeaderSize - kHeapObjectTag)));
63 Goto(&out);
64 }
65
66 BIND(&if_code_is_off_heap);
67 {
68 TNode<IntPtrT> builtin_entry_offset_from_isolate_root =
69 IntPtrAdd(IntPtrConstant(IsolateData::builtin_entry_table_offset()),
70 ChangeInt32ToIntPtr(Word32Shl(
71 builtin_index, Int32Constant(kSystemPointerSizeLog2))));
72
73 var_result = ReinterpretCast<RawPtrT>(
74 Load(MachineType::Pointer(),
75 ExternalConstant(ExternalReference::isolate_root(isolate())),
76 builtin_entry_offset_from_isolate_root));
77 Goto(&out);
78 }
79
80 BIND(&out);
81 return var_result.value();
82 }
83
84 // -----------------------------------------------------------------------------
85 // ES6 section 21.2 RegExp Objects
86
AllocateRegExpResult(TNode<Context> context,TNode<Smi> length,TNode<Smi> index,TNode<String> input,TNode<JSRegExp> regexp,TNode<Number> last_index,TNode<FixedArray> * elements_out)87 TNode<JSRegExpResult> RegExpBuiltinsAssembler::AllocateRegExpResult(
88 TNode<Context> context, TNode<Smi> length, TNode<Smi> index,
89 TNode<String> input, TNode<JSRegExp> regexp, TNode<Number> last_index,
90 TNode<FixedArray>* elements_out) {
91 CSA_ASSERT(this, SmiLessThanOrEqual(
92 length, SmiConstant(JSArray::kMaxFastArrayLength)));
93 CSA_ASSERT(this, SmiGreaterThan(length, SmiConstant(0)));
94
95 // Allocate.
96
97 const ElementsKind elements_kind = PACKED_ELEMENTS;
98 TNode<Map> map = CAST(LoadContextElement(LoadNativeContext(context),
99 Context::REGEXP_RESULT_MAP_INDEX));
100 base::Optional<TNode<AllocationSite>> no_allocation_site = base::nullopt;
101 TNode<IntPtrT> length_intptr = SmiUntag(length);
102
103 // Note: The returned `elements` may be in young large object space, but
104 // `array` is guaranteed to be in new space so we could skip write barriers
105 // below.
106 TNode<JSArray> array;
107 TNode<FixedArrayBase> elements;
108 std::tie(array, elements) = AllocateUninitializedJSArrayWithElements(
109 elements_kind, map, length, no_allocation_site, length_intptr,
110 kAllowLargeObjectAllocation, JSRegExpResult::kSize);
111
112 // Finish result initialization.
113
114 TNode<JSRegExpResult> result = UncheckedCast<JSRegExpResult>(array);
115
116 // Load undefined value once here to avoid multiple LoadRoots.
117 TNode<Oddball> undefined_value = UncheckedCast<Oddball>(
118 CodeAssembler::LoadRoot(RootIndex::kUndefinedValue));
119
120 StoreObjectFieldNoWriteBarrier(result, JSRegExpResult::kIndexOffset, index);
121 // TODO(jgruber,tebbi): Could skip barrier but the MemoryOptimizer complains.
122 StoreObjectField(result, JSRegExpResult::kInputOffset, input);
123 StoreObjectFieldNoWriteBarrier(result, JSRegExpResult::kGroupsOffset,
124 undefined_value);
125 StoreObjectFieldNoWriteBarrier(result, JSRegExpResult::kNamesOffset,
126 undefined_value);
127
128 // Stash regexp in order to re-execute and build JSRegExpResultIndices lazily
129 // when the 'indices' property is accessed.
130 StoreObjectField(result, JSRegExpResult::kCachedIndicesOrRegexpOffset,
131 regexp);
132 StoreObjectField(result, JSRegExpResult::kRegexpInputOffset, input);
133
134 // If non-smi last_index then store an SmiZero instead.
135 {
136 TNode<Smi> last_index_smi = Select<Smi>(
137 TaggedIsSmi(last_index), [=] { return CAST(last_index); },
138 [=] { return SmiZero(); });
139 StoreObjectField(result, JSRegExpResult::kRegexpLastIndexOffset,
140 last_index_smi);
141 }
142
143 // Finish elements initialization.
144
145 FillFixedArrayWithValue(elements_kind, elements, IntPtrZero(), length_intptr,
146 RootIndex::kUndefinedValue);
147
148 if (elements_out) *elements_out = CAST(elements);
149 return result;
150 }
151
FastLoadLastIndexBeforeSmiCheck(TNode<JSRegExp> regexp)152 TNode<Object> RegExpBuiltinsAssembler::FastLoadLastIndexBeforeSmiCheck(
153 TNode<JSRegExp> regexp) {
154 // Load the in-object field.
155 static const int field_offset =
156 JSRegExp::kHeaderSize + JSRegExp::kLastIndexFieldIndex * kTaggedSize;
157 return LoadObjectField(regexp, field_offset);
158 }
159
SlowLoadLastIndex(TNode<Context> context,TNode<Object> regexp)160 TNode<Object> RegExpBuiltinsAssembler::SlowLoadLastIndex(TNode<Context> context,
161 TNode<Object> regexp) {
162 return GetProperty(context, regexp, isolate()->factory()->lastIndex_string());
163 }
164
165 // The fast-path of StoreLastIndex when regexp is guaranteed to be an unmodified
166 // JSRegExp instance.
FastStoreLastIndex(TNode<JSRegExp> regexp,TNode<Smi> value)167 void RegExpBuiltinsAssembler::FastStoreLastIndex(TNode<JSRegExp> regexp,
168 TNode<Smi> value) {
169 // Store the in-object field.
170 static const int field_offset =
171 JSRegExp::kHeaderSize + JSRegExp::kLastIndexFieldIndex * kTaggedSize;
172 StoreObjectField(regexp, field_offset, value);
173 }
174
SlowStoreLastIndex(TNode<Context> context,TNode<Object> regexp,TNode<Object> value)175 void RegExpBuiltinsAssembler::SlowStoreLastIndex(TNode<Context> context,
176 TNode<Object> regexp,
177 TNode<Object> value) {
178 TNode<String> name = HeapConstant(isolate()->factory()->lastIndex_string());
179 SetPropertyStrict(context, regexp, name, value);
180 }
181
ConstructNewResultFromMatchInfo(TNode<Context> context,TNode<JSRegExp> regexp,TNode<RegExpMatchInfo> match_info,TNode<String> string,TNode<Number> last_index)182 TNode<JSRegExpResult> RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
183 TNode<Context> context, TNode<JSRegExp> regexp,
184 TNode<RegExpMatchInfo> match_info, TNode<String> string,
185 TNode<Number> last_index) {
186 Label named_captures(this), out(this);
187
188 TNode<IntPtrT> num_indices = SmiUntag(CAST(UnsafeLoadFixedArrayElement(
189 match_info, RegExpMatchInfo::kNumberOfCapturesIndex)));
190 TNode<Smi> num_results = SmiTag(WordShr(num_indices, 1));
191 TNode<Smi> start = CAST(UnsafeLoadFixedArrayElement(
192 match_info, RegExpMatchInfo::kFirstCaptureIndex));
193 TNode<Smi> end = CAST(UnsafeLoadFixedArrayElement(
194 match_info, RegExpMatchInfo::kFirstCaptureIndex + 1));
195
196 // Calculate the substring of the first match before creating the result array
197 // to avoid an unnecessary write barrier storing the first result.
198
199 TNode<String> first =
200 CAST(CallBuiltin(Builtins::kSubString, context, string, start, end));
201
202 TNode<FixedArray> result_elements;
203 TNode<JSRegExpResult> result =
204 AllocateRegExpResult(context, num_results, start, string, regexp,
205 last_index, &result_elements);
206
207 UnsafeStoreFixedArrayElement(result_elements, 0, first);
208
209 // If no captures exist we can skip named capture handling as well.
210 GotoIf(SmiEqual(num_results, SmiConstant(1)), &out);
211
212 // Store all remaining captures.
213 TNode<IntPtrT> limit = IntPtrAdd(
214 IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), num_indices);
215
216 TVARIABLE(IntPtrT, var_from_cursor,
217 IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 2));
218 TVARIABLE(IntPtrT, var_to_cursor, IntPtrConstant(1));
219
220 Label loop(this, {&var_from_cursor, &var_to_cursor});
221
222 Goto(&loop);
223 BIND(&loop);
224 {
225 TNode<IntPtrT> from_cursor = var_from_cursor.value();
226 TNode<IntPtrT> to_cursor = var_to_cursor.value();
227 TNode<Smi> start =
228 CAST(UnsafeLoadFixedArrayElement(match_info, from_cursor));
229
230 Label next_iter(this);
231 GotoIf(SmiEqual(start, SmiConstant(-1)), &next_iter);
232
233 TNode<IntPtrT> from_cursor_plus1 =
234 IntPtrAdd(from_cursor, IntPtrConstant(1));
235 TNode<Smi> end =
236 CAST(UnsafeLoadFixedArrayElement(match_info, from_cursor_plus1));
237
238 TNode<String> capture =
239 CAST(CallBuiltin(Builtins::kSubString, context, string, start, end));
240 UnsafeStoreFixedArrayElement(result_elements, to_cursor, capture);
241 Goto(&next_iter);
242
243 BIND(&next_iter);
244 var_from_cursor = IntPtrAdd(from_cursor, IntPtrConstant(2));
245 var_to_cursor = IntPtrAdd(to_cursor, IntPtrConstant(1));
246 Branch(UintPtrLessThan(var_from_cursor.value(), limit), &loop,
247 &named_captures);
248 }
249
250 BIND(&named_captures);
251 {
252 CSA_ASSERT(this, SmiGreaterThan(num_results, SmiConstant(1)));
253
254 // Preparations for named capture properties. Exit early if the result does
255 // not have any named captures to minimize performance impact.
256
257 TNode<FixedArray> data =
258 CAST(LoadObjectField(regexp, JSRegExp::kDataOffset));
259
260 // We reach this point only if captures exist, implying that the assigned
261 // regexp engine must be able to handle captures.
262 CSA_ASSERT(
263 this,
264 Word32Or(
265 SmiEqual(CAST(LoadFixedArrayElement(data, JSRegExp::kTagIndex)),
266 SmiConstant(JSRegExp::IRREGEXP)),
267 SmiEqual(CAST(LoadFixedArrayElement(data, JSRegExp::kTagIndex)),
268 SmiConstant(JSRegExp::EXPERIMENTAL))));
269
270 // The names fixed array associates names at even indices with a capture
271 // index at odd indices.
272 TNode<Object> maybe_names =
273 LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureNameMapIndex);
274 GotoIf(TaggedEqual(maybe_names, SmiZero()), &out);
275
276 // One or more named captures exist, add a property for each one.
277
278 TNode<FixedArray> names = CAST(maybe_names);
279 TNode<IntPtrT> names_length = LoadAndUntagFixedArrayBaseLength(names);
280 CSA_ASSERT(this, IntPtrGreaterThan(names_length, IntPtrZero()));
281
282 // Stash names in case we need them to build the indices array later.
283 StoreObjectField(result, JSRegExpResult::kNamesOffset, names);
284
285 // Allocate a new object to store the named capture properties.
286 // TODO(jgruber): Could be optimized by adding the object map to the heap
287 // root list.
288
289 TNode<IntPtrT> num_properties = WordSar(names_length, 1);
290 TNode<NativeContext> native_context = LoadNativeContext(context);
291 TNode<Map> map = LoadSlowObjectWithNullPrototypeMap(native_context);
292 TNode<NameDictionary> properties =
293 AllocateNameDictionary(num_properties, kAllowLargeObjectAllocation);
294
295 TNode<JSObject> group_object = AllocateJSObjectFromMap(map, properties);
296 StoreObjectField(result, JSRegExpResult::kGroupsOffset, group_object);
297
298 TVARIABLE(IntPtrT, var_i, IntPtrZero());
299
300 Label loop(this, &var_i);
301
302 Goto(&loop);
303 BIND(&loop);
304 {
305 TNode<IntPtrT> i = var_i.value();
306 TNode<IntPtrT> i_plus_1 = IntPtrAdd(i, IntPtrConstant(1));
307 TNode<IntPtrT> i_plus_2 = IntPtrAdd(i_plus_1, IntPtrConstant(1));
308
309 TNode<String> name = CAST(LoadFixedArrayElement(names, i));
310 TNode<Smi> index = CAST(LoadFixedArrayElement(names, i_plus_1));
311 TNode<HeapObject> capture =
312 CAST(LoadFixedArrayElement(result_elements, SmiUntag(index)));
313
314 // TODO(v8:8213): For maintainability, we should call a CSA/Torque
315 // implementation of CreateDataProperty instead.
316
317 // At this point the spec says to call CreateDataProperty. However, we can
318 // skip most of the steps and go straight to adding a dictionary entry
319 // because we know a bunch of useful facts:
320 // - All keys are non-numeric internalized strings
321 // - No keys repeat
322 // - Receiver has no prototype
323 // - Receiver isn't used as a prototype
324 // - Receiver isn't any special object like a Promise intrinsic object
325 // - Receiver is extensible
326 // - Receiver has no interceptors
327 Label add_dictionary_property_slow(this, Label::kDeferred);
328 Add<NameDictionary>(properties, name, capture,
329 &add_dictionary_property_slow);
330
331 var_i = i_plus_2;
332 Branch(IntPtrGreaterThanOrEqual(var_i.value(), names_length), &out,
333 &loop);
334
335 BIND(&add_dictionary_property_slow);
336 // If the dictionary needs resizing, the above Add call will jump here
337 // before making any changes. This shouldn't happen because we allocated
338 // the dictionary with enough space above.
339 Unreachable();
340 }
341 }
342
343 BIND(&out);
344 return result;
345 }
346
GetStringPointers(TNode<RawPtrT> string_data,TNode<IntPtrT> offset,TNode<IntPtrT> last_index,TNode<IntPtrT> string_length,String::Encoding encoding,TVariable<RawPtrT> * var_string_start,TVariable<RawPtrT> * var_string_end)347 void RegExpBuiltinsAssembler::GetStringPointers(
348 TNode<RawPtrT> string_data, TNode<IntPtrT> offset,
349 TNode<IntPtrT> last_index, TNode<IntPtrT> string_length,
350 String::Encoding encoding, TVariable<RawPtrT>* var_string_start,
351 TVariable<RawPtrT>* var_string_end) {
352 DCHECK_EQ(var_string_start->rep(), MachineType::PointerRepresentation());
353 DCHECK_EQ(var_string_end->rep(), MachineType::PointerRepresentation());
354
355 const ElementsKind kind = (encoding == String::ONE_BYTE_ENCODING)
356 ? UINT8_ELEMENTS
357 : UINT16_ELEMENTS;
358
359 TNode<IntPtrT> from_offset =
360 ElementOffsetFromIndex(IntPtrAdd(offset, last_index), kind);
361 *var_string_start =
362 ReinterpretCast<RawPtrT>(IntPtrAdd(string_data, from_offset));
363
364 TNode<IntPtrT> to_offset =
365 ElementOffsetFromIndex(IntPtrAdd(offset, string_length), kind);
366 *var_string_end = ReinterpretCast<RawPtrT>(IntPtrAdd(string_data, to_offset));
367 }
368
RegExpExecInternal(TNode<Context> context,TNode<JSRegExp> regexp,TNode<String> string,TNode<Number> last_index,TNode<RegExpMatchInfo> match_info)369 TNode<HeapObject> RegExpBuiltinsAssembler::RegExpExecInternal(
370 TNode<Context> context, TNode<JSRegExp> regexp, TNode<String> string,
371 TNode<Number> last_index, TNode<RegExpMatchInfo> match_info) {
372 ToDirectStringAssembler to_direct(state(), string);
373
374 TVARIABLE(HeapObject, var_result);
375 Label out(this), atom(this), runtime(this, Label::kDeferred),
376 retry_experimental(this, Label::kDeferred);
377
378 // External constants.
379 TNode<ExternalReference> isolate_address =
380 ExternalConstant(ExternalReference::isolate_address(isolate()));
381 TNode<ExternalReference> regexp_stack_memory_top_address = ExternalConstant(
382 ExternalReference::address_of_regexp_stack_memory_top_address(isolate()));
383 TNode<ExternalReference> static_offsets_vector_address = ExternalConstant(
384 ExternalReference::address_of_static_offsets_vector(isolate()));
385
386 // At this point, last_index is definitely a canonicalized non-negative
387 // number, which implies that any non-Smi last_index is greater than
388 // the maximal string length. If lastIndex > string.length then the matcher
389 // must fail.
390
391 Label if_failure(this);
392
393 CSA_ASSERT(this, IsNumberNormalized(last_index));
394 CSA_ASSERT(this, IsNumberPositive(last_index));
395 GotoIf(TaggedIsNotSmi(last_index), &if_failure);
396
397 TNode<IntPtrT> int_string_length = LoadStringLengthAsWord(string);
398 TNode<IntPtrT> int_last_index = SmiUntag(CAST(last_index));
399
400 GotoIf(UintPtrGreaterThan(int_last_index, int_string_length), &if_failure);
401
402 // Since the RegExp has been compiled, data contains a fixed array.
403 TNode<FixedArray> data = CAST(LoadObjectField(regexp, JSRegExp::kDataOffset));
404 {
405 // Dispatch on the type of the RegExp.
406 {
407 Label next(this), unreachable(this, Label::kDeferred);
408 TNode<Int32T> tag = LoadAndUntagToWord32FixedArrayElement(
409 data, IntPtrConstant(JSRegExp::kTagIndex));
410
411 int32_t values[] = {
412 JSRegExp::IRREGEXP,
413 JSRegExp::ATOM,
414 JSRegExp::EXPERIMENTAL,
415 };
416 Label* labels[] = {&next, &atom, &next};
417
418 STATIC_ASSERT(arraysize(values) == arraysize(labels));
419 Switch(tag, &unreachable, values, labels, arraysize(values));
420
421 BIND(&unreachable);
422 Unreachable();
423
424 BIND(&next);
425 }
426
427 // Check (number_of_captures + 1) * 2 <= offsets vector size
428 // Or number_of_captures <= offsets vector size / 2 - 1
429 TNode<Smi> capture_count = CAST(UnsafeLoadFixedArrayElement(
430 data, JSRegExp::kIrregexpCaptureCountIndex));
431
432 const int kOffsetsSize = Isolate::kJSRegexpStaticOffsetsVectorSize;
433 STATIC_ASSERT(kOffsetsSize >= 2);
434 GotoIf(SmiAbove(capture_count, SmiConstant(kOffsetsSize / 2 - 1)),
435 &runtime);
436 }
437
438 // Unpack the string if possible.
439
440 to_direct.TryToDirect(&runtime);
441
442 // Load the irregexp code or bytecode object and offsets into the subject
443 // string. Both depend on whether the string is one- or two-byte.
444
445 TVARIABLE(RawPtrT, var_string_start);
446 TVARIABLE(RawPtrT, var_string_end);
447 TVARIABLE(Object, var_code);
448 TVARIABLE(Object, var_bytecode);
449
450 {
451 TNode<RawPtrT> direct_string_data = to_direct.PointerToData(&runtime);
452
453 Label next(this), if_isonebyte(this), if_istwobyte(this, Label::kDeferred);
454 Branch(IsOneByteStringInstanceType(to_direct.instance_type()),
455 &if_isonebyte, &if_istwobyte);
456
457 BIND(&if_isonebyte);
458 {
459 GetStringPointers(direct_string_data, to_direct.offset(), int_last_index,
460 int_string_length, String::ONE_BYTE_ENCODING,
461 &var_string_start, &var_string_end);
462 var_code =
463 UnsafeLoadFixedArrayElement(data, JSRegExp::kIrregexpLatin1CodeIndex);
464 var_bytecode = UnsafeLoadFixedArrayElement(
465 data, JSRegExp::kIrregexpLatin1BytecodeIndex);
466 Goto(&next);
467 }
468
469 BIND(&if_istwobyte);
470 {
471 GetStringPointers(direct_string_data, to_direct.offset(), int_last_index,
472 int_string_length, String::TWO_BYTE_ENCODING,
473 &var_string_start, &var_string_end);
474 var_code =
475 UnsafeLoadFixedArrayElement(data, JSRegExp::kIrregexpUC16CodeIndex);
476 var_bytecode = UnsafeLoadFixedArrayElement(
477 data, JSRegExp::kIrregexpUC16BytecodeIndex);
478 Goto(&next);
479 }
480
481 BIND(&next);
482 }
483
484 // Check that the irregexp code has been generated for the actual string
485 // encoding. If it has, the field contains a code object; and otherwise it
486 // contains the uninitialized sentinel as a smi.
487 #ifdef DEBUG
488 {
489 Label next(this);
490 GotoIfNot(TaggedIsSmi(var_code.value()), &next);
491 CSA_ASSERT(this, SmiEqual(CAST(var_code.value()),
492 SmiConstant(JSRegExp::kUninitializedValue)));
493 Goto(&next);
494 BIND(&next);
495 }
496 #endif
497
498 GotoIf(TaggedIsSmi(var_code.value()), &runtime);
499 TNode<Code> code = CAST(var_code.value());
500
501 Label if_success(this), if_exception(this, Label::kDeferred);
502 {
503 IncrementCounter(isolate()->counters()->regexp_entry_native(), 1);
504
505 // Set up args for the final call into generated Irregexp code.
506
507 MachineType type_int32 = MachineType::Int32();
508 MachineType type_tagged = MachineType::AnyTagged();
509 MachineType type_ptr = MachineType::Pointer();
510
511 // Result: A NativeRegExpMacroAssembler::Result return code.
512 MachineType retval_type = type_int32;
513
514 // Argument 0: Original subject string.
515 MachineType arg0_type = type_tagged;
516 TNode<String> arg0 = string;
517
518 // Argument 1: Previous index.
519 MachineType arg1_type = type_int32;
520 TNode<Int32T> arg1 = TruncateIntPtrToInt32(int_last_index);
521
522 // Argument 2: Start of string data. This argument is ignored in the
523 // interpreter.
524 MachineType arg2_type = type_ptr;
525 TNode<RawPtrT> arg2 = var_string_start.value();
526
527 // Argument 3: End of string data. This argument is ignored in the
528 // interpreter.
529 MachineType arg3_type = type_ptr;
530 TNode<RawPtrT> arg3 = var_string_end.value();
531
532 // Argument 4: static offsets vector buffer.
533 MachineType arg4_type = type_ptr;
534 TNode<ExternalReference> arg4 = static_offsets_vector_address;
535
536 // Argument 5: Number of capture registers.
537 // Setting this to the number of registers required to store all captures
538 // forces global regexps to behave as non-global.
539 TNode<Smi> capture_count = CAST(UnsafeLoadFixedArrayElement(
540 data, JSRegExp::kIrregexpCaptureCountIndex));
541 // capture_count is the number of captures without the match itself.
542 // Required registers = (capture_count + 1) * 2.
543 STATIC_ASSERT(Internals::IsValidSmi((JSRegExp::kMaxCaptures + 1) * 2));
544 TNode<Smi> register_count =
545 SmiShl(SmiAdd(capture_count, SmiConstant(1)), 1);
546
547 MachineType arg5_type = type_int32;
548 TNode<Int32T> arg5 = SmiToInt32(register_count);
549
550 // Argument 6: Start (high end) of backtracking stack memory area. This
551 // argument is ignored in the interpreter.
552 TNode<RawPtrT> stack_top = UncheckedCast<RawPtrT>(
553 Load(MachineType::Pointer(), regexp_stack_memory_top_address));
554
555 MachineType arg6_type = type_ptr;
556 TNode<RawPtrT> arg6 = stack_top;
557
558 // Argument 7: Indicate that this is a direct call from JavaScript.
559 MachineType arg7_type = type_int32;
560 TNode<Int32T> arg7 = Int32Constant(RegExp::CallOrigin::kFromJs);
561
562 // Argument 8: Pass current isolate address.
563 MachineType arg8_type = type_ptr;
564 TNode<ExternalReference> arg8 = isolate_address;
565
566 // Argument 9: Regular expression object. This argument is ignored in native
567 // irregexp code.
568 MachineType arg9_type = type_tagged;
569 TNode<JSRegExp> arg9 = regexp;
570
571 TNode<RawPtrT> code_entry = LoadCodeObjectEntry(code);
572
573 // AIX uses function descriptors on CFunction calls. code_entry in this case
574 // may also point to a Regex interpreter entry trampoline which does not
575 // have a function descriptor. This method is ineffective on other platforms
576 // and is equivalent to CallCFunction.
577 TNode<Int32T> result =
578 UncheckedCast<Int32T>(CallCFunctionWithoutFunctionDescriptor(
579 code_entry, retval_type, std::make_pair(arg0_type, arg0),
580 std::make_pair(arg1_type, arg1), std::make_pair(arg2_type, arg2),
581 std::make_pair(arg3_type, arg3), std::make_pair(arg4_type, arg4),
582 std::make_pair(arg5_type, arg5), std::make_pair(arg6_type, arg6),
583 std::make_pair(arg7_type, arg7), std::make_pair(arg8_type, arg8),
584 std::make_pair(arg9_type, arg9)));
585
586 // Check the result.
587 // We expect exactly one result since we force the called regexp to behave
588 // as non-global.
589 TNode<IntPtrT> int_result = ChangeInt32ToIntPtr(result);
590 GotoIf(
591 IntPtrEqual(int_result, IntPtrConstant(RegExp::kInternalRegExpSuccess)),
592 &if_success);
593 GotoIf(
594 IntPtrEqual(int_result, IntPtrConstant(RegExp::kInternalRegExpFailure)),
595 &if_failure);
596 GotoIf(IntPtrEqual(int_result,
597 IntPtrConstant(RegExp::kInternalRegExpException)),
598 &if_exception);
599 GotoIf(IntPtrEqual(
600 int_result,
601 IntPtrConstant(RegExp::kInternalRegExpFallbackToExperimental)),
602 &retry_experimental);
603
604 CSA_ASSERT(this, IntPtrEqual(int_result,
605 IntPtrConstant(RegExp::kInternalRegExpRetry)));
606 Goto(&runtime);
607 }
608
609 BIND(&if_success);
610 {
611 // Check that the last match info has space for the capture registers and
612 // the additional information. Ensure no overflow in add.
613 STATIC_ASSERT(FixedArray::kMaxLength < kMaxInt - FixedArray::kLengthOffset);
614 TNode<Smi> available_slots =
615 SmiSub(LoadFixedArrayBaseLength(match_info),
616 SmiConstant(RegExpMatchInfo::kLastMatchOverhead));
617 TNode<Smi> capture_count = CAST(UnsafeLoadFixedArrayElement(
618 data, JSRegExp::kIrregexpCaptureCountIndex));
619 // Calculate number of register_count = (capture_count + 1) * 2.
620 TNode<Smi> register_count =
621 SmiShl(SmiAdd(capture_count, SmiConstant(1)), 1);
622 GotoIf(SmiGreaterThan(register_count, available_slots), &runtime);
623
624 // Fill match_info.
625 UnsafeStoreFixedArrayElement(
626 match_info, RegExpMatchInfo::kNumberOfCapturesIndex, register_count);
627 UnsafeStoreFixedArrayElement(match_info, RegExpMatchInfo::kLastSubjectIndex,
628 string);
629 UnsafeStoreFixedArrayElement(match_info, RegExpMatchInfo::kLastInputIndex,
630 string);
631
632 // Fill match and capture offsets in match_info.
633 {
634 TNode<IntPtrT> limit_offset =
635 ElementOffsetFromIndex(register_count, INT32_ELEMENTS, 0);
636
637 TNode<IntPtrT> to_offset = ElementOffsetFromIndex(
638 IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), PACKED_ELEMENTS,
639 RegExpMatchInfo::kHeaderSize - kHeapObjectTag);
640 TVARIABLE(IntPtrT, var_to_offset, to_offset);
641
642 VariableList vars({&var_to_offset}, zone());
643 BuildFastLoop<IntPtrT>(
644 vars, IntPtrZero(), limit_offset,
645 [&](TNode<IntPtrT> offset) {
646 TNode<Int32T> value = UncheckedCast<Int32T>(Load(
647 MachineType::Int32(), static_offsets_vector_address, offset));
648 TNode<Smi> smi_value = SmiFromInt32(value);
649 StoreNoWriteBarrier(MachineRepresentation::kTagged, match_info,
650 var_to_offset.value(), smi_value);
651 Increment(&var_to_offset, kTaggedSize);
652 },
653 kInt32Size, IndexAdvanceMode::kPost);
654 }
655
656 var_result = match_info;
657 Goto(&out);
658 }
659
660 BIND(&if_failure);
661 {
662 var_result = NullConstant();
663 Goto(&out);
664 }
665
666 BIND(&if_exception);
667 {
668 // A stack overflow was detected in RegExp code.
669 #ifdef DEBUG
670 TNode<ExternalReference> pending_exception_address =
671 ExternalConstant(ExternalReference::Create(
672 IsolateAddressId::kPendingExceptionAddress, isolate()));
673 CSA_ASSERT(this, IsTheHole(Load(MachineType::AnyTagged(),
674 pending_exception_address)));
675 #endif // DEBUG
676 CallRuntime(Runtime::kThrowStackOverflow, context);
677 Unreachable();
678 }
679
680 BIND(&retry_experimental);
681 {
682 var_result =
683 CAST(CallRuntime(Runtime::kRegExpExperimentalOneshotExec, context,
684 regexp, string, last_index, match_info));
685 Goto(&out);
686 }
687
688 BIND(&runtime);
689 {
690 var_result = CAST(CallRuntime(Runtime::kRegExpExec, context, regexp, string,
691 last_index, match_info));
692 Goto(&out);
693 }
694
695 BIND(&atom);
696 {
697 // TODO(jgruber): A call with 4 args stresses register allocation, this
698 // should probably just be inlined.
699 var_result = CAST(CallBuiltin(Builtins::kRegExpExecAtom, context, regexp,
700 string, last_index, match_info));
701 Goto(&out);
702 }
703
704 BIND(&out);
705 return var_result.value();
706 }
707
IsFastRegExpNoPrototype(TNode<Context> context,TNode<Object> object,TNode<Map> map)708 TNode<BoolT> RegExpBuiltinsAssembler::IsFastRegExpNoPrototype(
709 TNode<Context> context, TNode<Object> object, TNode<Map> map) {
710 Label out(this);
711 TVARIABLE(BoolT, var_result);
712
713 #ifdef V8_ENABLE_FORCE_SLOW_PATH
714 var_result = Int32FalseConstant();
715 GotoIfForceSlowPath(&out);
716 #endif
717
718 const TNode<NativeContext> native_context = LoadNativeContext(context);
719 const TNode<HeapObject> regexp_fun =
720 CAST(LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX));
721 const TNode<Object> initial_map =
722 LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
723 const TNode<BoolT> has_initialmap = TaggedEqual(map, initial_map);
724
725 var_result = has_initialmap;
726 GotoIfNot(has_initialmap, &out);
727
728 // The smi check is required to omit ToLength(lastIndex) calls with possible
729 // user-code execution on the fast path.
730 TNode<Object> last_index = FastLoadLastIndexBeforeSmiCheck(CAST(object));
731 var_result = TaggedIsPositiveSmi(last_index);
732 Goto(&out);
733
734 BIND(&out);
735 return var_result.value();
736 }
737
IsFastRegExpNoPrototype(TNode<Context> context,TNode<Object> object)738 TNode<BoolT> RegExpBuiltinsAssembler::IsFastRegExpNoPrototype(
739 TNode<Context> context, TNode<Object> object) {
740 CSA_ASSERT(this, TaggedIsNotSmi(object));
741 return IsFastRegExpNoPrototype(context, object, LoadMap(CAST(object)));
742 }
743
BranchIfFastRegExp(TNode<Context> context,TNode<HeapObject> object,TNode<Map> map,PrototypeCheckAssembler::Flags prototype_check_flags,base::Optional<DescriptorIndexNameValue> additional_property_to_check,Label * if_isunmodified,Label * if_ismodified)744 void RegExpBuiltinsAssembler::BranchIfFastRegExp(
745 TNode<Context> context, TNode<HeapObject> object, TNode<Map> map,
746 PrototypeCheckAssembler::Flags prototype_check_flags,
747 base::Optional<DescriptorIndexNameValue> additional_property_to_check,
748 Label* if_isunmodified, Label* if_ismodified) {
749 CSA_ASSERT(this, TaggedEqual(LoadMap(object), map));
750
751 GotoIfForceSlowPath(if_ismodified);
752
753 // This should only be needed for String.p.(split||matchAll), but we are
754 // conservative here.
755 GotoIf(IsRegExpSpeciesProtectorCellInvalid(), if_ismodified);
756
757 TNode<NativeContext> native_context = LoadNativeContext(context);
758 TNode<JSFunction> regexp_fun =
759 CAST(LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX));
760 TNode<Map> initial_map = CAST(
761 LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset));
762 TNode<BoolT> has_initialmap = TaggedEqual(map, initial_map);
763
764 GotoIfNot(has_initialmap, if_ismodified);
765
766 // The smi check is required to omit ToLength(lastIndex) calls with possible
767 // user-code execution on the fast path.
768 TNode<Object> last_index = FastLoadLastIndexBeforeSmiCheck(CAST(object));
769 GotoIfNot(TaggedIsPositiveSmi(last_index), if_ismodified);
770
771 // Verify the prototype.
772
773 TNode<Map> initial_proto_initial_map = CAST(
774 LoadContextElement(native_context, Context::REGEXP_PROTOTYPE_MAP_INDEX));
775
776 DescriptorIndexNameValue properties_to_check[2];
777 int property_count = 0;
778 properties_to_check[property_count++] = DescriptorIndexNameValue{
779 JSRegExp::kExecFunctionDescriptorIndex, RootIndex::kexec_string,
780 Context::REGEXP_EXEC_FUNCTION_INDEX};
781 if (additional_property_to_check) {
782 properties_to_check[property_count++] = *additional_property_to_check;
783 }
784
785 PrototypeCheckAssembler prototype_check_assembler(
786 state(), prototype_check_flags, native_context, initial_proto_initial_map,
787 Vector<DescriptorIndexNameValue>(properties_to_check, property_count));
788
789 TNode<HeapObject> prototype = LoadMapPrototype(map);
790 prototype_check_assembler.CheckAndBranch(prototype, if_isunmodified,
791 if_ismodified);
792 }
793
BranchIfFastRegExp_Strict(TNode<Context> context,TNode<HeapObject> object,Label * if_isunmodified,Label * if_ismodified)794 void RegExpBuiltinsAssembler::BranchIfFastRegExp_Strict(
795 TNode<Context> context, TNode<HeapObject> object, Label* if_isunmodified,
796 Label* if_ismodified) {
797 BranchIfFastRegExp(context, object, LoadMap(object),
798 PrototypeCheckAssembler::kCheckPrototypePropertyConstness,
799 base::nullopt, if_isunmodified, if_ismodified);
800 }
801
BranchIfFastRegExp_Permissive(TNode<Context> context,TNode<HeapObject> object,Label * if_isunmodified,Label * if_ismodified)802 void RegExpBuiltinsAssembler::BranchIfFastRegExp_Permissive(
803 TNode<Context> context, TNode<HeapObject> object, Label* if_isunmodified,
804 Label* if_ismodified) {
805 BranchIfFastRegExp(context, object, LoadMap(object),
806 PrototypeCheckAssembler::kCheckFull, base::nullopt,
807 if_isunmodified, if_ismodified);
808 }
809
BranchIfRegExpResult(const TNode<Context> context,const TNode<Object> object,Label * if_isunmodified,Label * if_ismodified)810 void RegExpBuiltinsAssembler::BranchIfRegExpResult(const TNode<Context> context,
811 const TNode<Object> object,
812 Label* if_isunmodified,
813 Label* if_ismodified) {
814 // Could be a Smi.
815 const TNode<Map> map = LoadReceiverMap(object);
816
817 const TNode<NativeContext> native_context = LoadNativeContext(context);
818 const TNode<Object> initial_regexp_result_map =
819 LoadContextElement(native_context, Context::REGEXP_RESULT_MAP_INDEX);
820
821 Branch(TaggedEqual(map, initial_regexp_result_map), if_isunmodified,
822 if_ismodified);
823 }
824
825 // Fast path stub for ATOM regexps. String matching is done by StringIndexOf,
826 // and {match_info} is updated on success.
827 // The slow path is implemented in RegExp::AtomExec.
TF_BUILTIN(RegExpExecAtom,RegExpBuiltinsAssembler)828 TF_BUILTIN(RegExpExecAtom, RegExpBuiltinsAssembler) {
829 auto regexp = Parameter<JSRegExp>(Descriptor::kRegExp);
830 auto subject_string = Parameter<String>(Descriptor::kString);
831 auto last_index = Parameter<Smi>(Descriptor::kLastIndex);
832 auto match_info = Parameter<FixedArray>(Descriptor::kMatchInfo);
833 auto context = Parameter<Context>(Descriptor::kContext);
834
835 CSA_ASSERT(this, TaggedIsPositiveSmi(last_index));
836
837 TNode<FixedArray> data = CAST(LoadObjectField(regexp, JSRegExp::kDataOffset));
838 CSA_ASSERT(
839 this,
840 SmiEqual(CAST(UnsafeLoadFixedArrayElement(data, JSRegExp::kTagIndex)),
841 SmiConstant(JSRegExp::ATOM)));
842
843 // Callers ensure that last_index is in-bounds.
844 CSA_ASSERT(this,
845 UintPtrLessThanOrEqual(SmiUntag(last_index),
846 LoadStringLengthAsWord(subject_string)));
847
848 const TNode<String> needle_string =
849 CAST(UnsafeLoadFixedArrayElement(data, JSRegExp::kAtomPatternIndex));
850
851 const TNode<Smi> match_from =
852 CAST(CallBuiltin(Builtins::kStringIndexOf, context, subject_string,
853 needle_string, last_index));
854
855 Label if_failure(this), if_success(this);
856 Branch(SmiEqual(match_from, SmiConstant(-1)), &if_failure, &if_success);
857
858 BIND(&if_success);
859 {
860 CSA_ASSERT(this, TaggedIsPositiveSmi(match_from));
861 CSA_ASSERT(this, UintPtrLessThan(SmiUntag(match_from),
862 LoadStringLengthAsWord(subject_string)));
863
864 const int kNumRegisters = 2;
865 STATIC_ASSERT(RegExpMatchInfo::kInitialCaptureIndices >= kNumRegisters);
866
867 const TNode<Smi> match_to =
868 SmiAdd(match_from, LoadStringLengthAsSmi(needle_string));
869
870 UnsafeStoreFixedArrayElement(match_info,
871 RegExpMatchInfo::kNumberOfCapturesIndex,
872 SmiConstant(kNumRegisters));
873 UnsafeStoreFixedArrayElement(match_info, RegExpMatchInfo::kLastSubjectIndex,
874 subject_string);
875 UnsafeStoreFixedArrayElement(match_info, RegExpMatchInfo::kLastInputIndex,
876 subject_string);
877 UnsafeStoreFixedArrayElement(
878 match_info, RegExpMatchInfo::kFirstCaptureIndex, match_from);
879 UnsafeStoreFixedArrayElement(
880 match_info, RegExpMatchInfo::kFirstCaptureIndex + 1, match_to);
881
882 Return(match_info);
883 }
884
885 BIND(&if_failure);
886 Return(NullConstant());
887 }
888
TF_BUILTIN(RegExpExecInternal,RegExpBuiltinsAssembler)889 TF_BUILTIN(RegExpExecInternal, RegExpBuiltinsAssembler) {
890 auto regexp = Parameter<JSRegExp>(Descriptor::kRegExp);
891 auto string = Parameter<String>(Descriptor::kString);
892 auto last_index = Parameter<Number>(Descriptor::kLastIndex);
893 auto match_info = Parameter<RegExpMatchInfo>(Descriptor::kMatchInfo);
894 auto context = Parameter<Context>(Descriptor::kContext);
895
896 CSA_ASSERT(this, IsNumberNormalized(last_index));
897 CSA_ASSERT(this, IsNumberPositive(last_index));
898
899 Return(RegExpExecInternal(context, regexp, string, last_index, match_info));
900 }
901
FlagsGetter(TNode<Context> context,TNode<Object> regexp,bool is_fastpath)902 TNode<String> RegExpBuiltinsAssembler::FlagsGetter(TNode<Context> context,
903 TNode<Object> regexp,
904 bool is_fastpath) {
905 Isolate* isolate = this->isolate();
906
907 const TNode<IntPtrT> int_one = IntPtrConstant(1);
908 TVARIABLE(Uint32T, var_length, Uint32Constant(0));
909 TVARIABLE(IntPtrT, var_flags);
910
911 // First, count the number of characters we will need and check which flags
912 // are set.
913
914 if (is_fastpath) {
915 // Refer to JSRegExp's flag property on the fast-path.
916 CSA_ASSERT(this, IsJSRegExp(CAST(regexp)));
917 const TNode<Smi> flags_smi =
918 CAST(LoadObjectField(CAST(regexp), JSRegExp::kFlagsOffset));
919 var_flags = SmiUntag(flags_smi);
920
921 #define CASE_FOR_FLAG(FLAG) \
922 do { \
923 Label next(this); \
924 GotoIfNot(IsSetWord(var_flags.value(), FLAG), &next); \
925 var_length = Uint32Add(var_length.value(), Uint32Constant(1)); \
926 Goto(&next); \
927 BIND(&next); \
928 } while (false)
929
930 CASE_FOR_FLAG(JSRegExp::kGlobal);
931 CASE_FOR_FLAG(JSRegExp::kIgnoreCase);
932 CASE_FOR_FLAG(JSRegExp::kLinear);
933 CASE_FOR_FLAG(JSRegExp::kMultiline);
934 CASE_FOR_FLAG(JSRegExp::kDotAll);
935 CASE_FOR_FLAG(JSRegExp::kUnicode);
936 CASE_FOR_FLAG(JSRegExp::kSticky);
937 #undef CASE_FOR_FLAG
938 } else {
939 DCHECK(!is_fastpath);
940
941 // Fall back to GetProperty stub on the slow-path.
942 var_flags = IntPtrZero();
943
944 #define CASE_FOR_FLAG(NAME, FLAG) \
945 do { \
946 Label next(this); \
947 const TNode<Object> flag = GetProperty( \
948 context, regexp, isolate->factory()->InternalizeUtf8String(NAME)); \
949 Label if_isflagset(this); \
950 BranchIfToBooleanIsTrue(flag, &if_isflagset, &next); \
951 BIND(&if_isflagset); \
952 var_length = Uint32Add(var_length.value(), Uint32Constant(1)); \
953 var_flags = Signed(WordOr(var_flags.value(), IntPtrConstant(FLAG))); \
954 Goto(&next); \
955 BIND(&next); \
956 } while (false)
957
958 CASE_FOR_FLAG("global", JSRegExp::kGlobal);
959 CASE_FOR_FLAG("ignoreCase", JSRegExp::kIgnoreCase);
960 CASE_FOR_FLAG("multiline", JSRegExp::kMultiline);
961 CASE_FOR_FLAG("dotAll", JSRegExp::kDotAll);
962 CASE_FOR_FLAG("unicode", JSRegExp::kUnicode);
963 CASE_FOR_FLAG("sticky", JSRegExp::kSticky);
964 #undef CASE_FOR_FLAG
965
966 {
967 Label next(this);
968
969 // Check the runtime value of FLAG_enable_experimental_regexp_engine
970 // first.
971 TNode<Word32T> flag_value = UncheckedCast<Word32T>(
972 Load(MachineType::Uint8(),
973 ExternalConstant(
974 ExternalReference::
975 address_of_enable_experimental_regexp_engine())));
976 GotoIf(Word32Equal(Word32And(flag_value, Int32Constant(0xFF)),
977 Int32Constant(0)),
978 &next);
979
980 const TNode<Object> flag = GetProperty(
981 context, regexp, isolate->factory()->InternalizeUtf8String("linear"));
982 Label if_isflagset(this);
983 BranchIfToBooleanIsTrue(flag, &if_isflagset, &next);
984 BIND(&if_isflagset);
985 var_length = Uint32Add(var_length.value(), Uint32Constant(1));
986 var_flags =
987 Signed(WordOr(var_flags.value(), IntPtrConstant(JSRegExp::kLinear)));
988 Goto(&next);
989 BIND(&next);
990 }
991 }
992
993 // Allocate a string of the required length and fill it with the corresponding
994 // char for each set flag.
995
996 {
997 const TNode<String> result = AllocateSeqOneByteString(var_length.value());
998
999 TVARIABLE(IntPtrT, var_offset,
1000 IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag));
1001
1002 #define CASE_FOR_FLAG(FLAG, CHAR) \
1003 do { \
1004 Label next(this); \
1005 GotoIfNot(IsSetWord(var_flags.value(), FLAG), &next); \
1006 const TNode<Int32T> value = Int32Constant(CHAR); \
1007 StoreNoWriteBarrier(MachineRepresentation::kWord8, result, \
1008 var_offset.value(), value); \
1009 var_offset = IntPtrAdd(var_offset.value(), int_one); \
1010 Goto(&next); \
1011 BIND(&next); \
1012 } while (false)
1013
1014 CASE_FOR_FLAG(JSRegExp::kGlobal, 'g');
1015 CASE_FOR_FLAG(JSRegExp::kIgnoreCase, 'i');
1016 CASE_FOR_FLAG(JSRegExp::kLinear, 'l');
1017 CASE_FOR_FLAG(JSRegExp::kMultiline, 'm');
1018 CASE_FOR_FLAG(JSRegExp::kDotAll, 's');
1019 CASE_FOR_FLAG(JSRegExp::kUnicode, 'u');
1020 CASE_FOR_FLAG(JSRegExp::kSticky, 'y');
1021 #undef CASE_FOR_FLAG
1022
1023 return result;
1024 }
1025 }
1026
1027 // ES#sec-regexpinitialize
1028 // Runtime Semantics: RegExpInitialize ( obj, pattern, flags )
RegExpInitialize(const TNode<Context> context,const TNode<JSRegExp> regexp,const TNode<Object> maybe_pattern,const TNode<Object> maybe_flags)1029 TNode<Object> RegExpBuiltinsAssembler::RegExpInitialize(
1030 const TNode<Context> context, const TNode<JSRegExp> regexp,
1031 const TNode<Object> maybe_pattern, const TNode<Object> maybe_flags) {
1032 // Normalize pattern.
1033 const TNode<Object> pattern = Select<Object>(
1034 IsUndefined(maybe_pattern), [=] { return EmptyStringConstant(); },
1035 [=] { return ToString_Inline(context, maybe_pattern); });
1036
1037 // Normalize flags.
1038 const TNode<Object> flags = Select<Object>(
1039 IsUndefined(maybe_flags), [=] { return EmptyStringConstant(); },
1040 [=] { return ToString_Inline(context, maybe_flags); });
1041
1042 // Initialize.
1043
1044 return CallRuntime(Runtime::kRegExpInitializeAndCompile, context, regexp,
1045 pattern, flags);
1046 }
1047
1048 // ES#sec-regexp-pattern-flags
1049 // RegExp ( pattern, flags )
TF_BUILTIN(RegExpConstructor,RegExpBuiltinsAssembler)1050 TF_BUILTIN(RegExpConstructor, RegExpBuiltinsAssembler) {
1051 auto pattern = Parameter<Object>(Descriptor::kPattern);
1052 auto flags = Parameter<Object>(Descriptor::kFlags);
1053 auto new_target = Parameter<Object>(Descriptor::kJSNewTarget);
1054 auto context = Parameter<Context>(Descriptor::kContext);
1055
1056 Isolate* isolate = this->isolate();
1057
1058 TVARIABLE(Object, var_flags, flags);
1059 TVARIABLE(Object, var_pattern, pattern);
1060 TVARIABLE(Object, var_new_target, new_target);
1061
1062 TNode<NativeContext> native_context = LoadNativeContext(context);
1063 TNode<JSFunction> regexp_function =
1064 CAST(LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX));
1065
1066 TNode<BoolT> pattern_is_regexp = IsRegExp(context, pattern);
1067
1068 {
1069 Label next(this);
1070
1071 GotoIfNot(IsUndefined(new_target), &next);
1072 var_new_target = regexp_function;
1073
1074 GotoIfNot(pattern_is_regexp, &next);
1075 GotoIfNot(IsUndefined(flags), &next);
1076
1077 TNode<Object> value =
1078 GetProperty(context, pattern, isolate->factory()->constructor_string());
1079
1080 GotoIfNot(TaggedEqual(value, regexp_function), &next);
1081 Return(pattern);
1082
1083 BIND(&next);
1084 }
1085
1086 {
1087 Label next(this), if_patternisfastregexp(this),
1088 if_patternisslowregexp(this);
1089 GotoIf(TaggedIsSmi(pattern), &next);
1090
1091 GotoIf(IsJSRegExp(CAST(pattern)), &if_patternisfastregexp);
1092
1093 Branch(pattern_is_regexp, &if_patternisslowregexp, &next);
1094
1095 BIND(&if_patternisfastregexp);
1096 {
1097 TNode<Object> source =
1098 LoadObjectField(CAST(pattern), JSRegExp::kSourceOffset);
1099 var_pattern = source;
1100
1101 {
1102 Label inner_next(this);
1103 GotoIfNot(IsUndefined(flags), &inner_next);
1104
1105 var_flags = FlagsGetter(context, pattern, true);
1106 Goto(&inner_next);
1107
1108 BIND(&inner_next);
1109 }
1110
1111 Goto(&next);
1112 }
1113
1114 BIND(&if_patternisslowregexp);
1115 {
1116 var_pattern =
1117 GetProperty(context, pattern, isolate->factory()->source_string());
1118
1119 {
1120 Label inner_next(this);
1121 GotoIfNot(IsUndefined(flags), &inner_next);
1122
1123 var_flags =
1124 GetProperty(context, pattern, isolate->factory()->flags_string());
1125 Goto(&inner_next);
1126
1127 BIND(&inner_next);
1128 }
1129
1130 Goto(&next);
1131 }
1132
1133 BIND(&next);
1134 }
1135
1136 // Allocate.
1137
1138 TVARIABLE(JSRegExp, var_regexp);
1139 {
1140 Label allocate_jsregexp(this), allocate_generic(this, Label::kDeferred),
1141 next(this);
1142 Branch(TaggedEqual(var_new_target.value(), regexp_function),
1143 &allocate_jsregexp, &allocate_generic);
1144
1145 BIND(&allocate_jsregexp);
1146 {
1147 const TNode<Map> initial_map = CAST(LoadObjectField(
1148 regexp_function, JSFunction::kPrototypeOrInitialMapOffset));
1149 var_regexp = CAST(AllocateJSObjectFromMap(initial_map));
1150 Goto(&next);
1151 }
1152
1153 BIND(&allocate_generic);
1154 {
1155 ConstructorBuiltinsAssembler constructor_assembler(this->state());
1156 var_regexp = CAST(constructor_assembler.FastNewObject(
1157 context, regexp_function, CAST(var_new_target.value())));
1158 Goto(&next);
1159 }
1160
1161 BIND(&next);
1162 }
1163
1164 const TNode<Object> result = RegExpInitialize(
1165 context, var_regexp.value(), var_pattern.value(), var_flags.value());
1166 Return(result);
1167 }
1168
1169 // ES#sec-regexp.prototype.compile
1170 // RegExp.prototype.compile ( pattern, flags )
TF_BUILTIN(RegExpPrototypeCompile,RegExpBuiltinsAssembler)1171 TF_BUILTIN(RegExpPrototypeCompile, RegExpBuiltinsAssembler) {
1172 auto maybe_receiver = Parameter<Object>(Descriptor::kReceiver);
1173 auto maybe_pattern = Parameter<Object>(Descriptor::kPattern);
1174 auto maybe_flags = Parameter<Object>(Descriptor::kFlags);
1175 auto context = Parameter<Context>(Descriptor::kContext);
1176
1177 ThrowIfNotInstanceType(context, maybe_receiver, JS_REG_EXP_TYPE,
1178 "RegExp.prototype.compile");
1179 const TNode<JSRegExp> receiver = CAST(maybe_receiver);
1180
1181 TVARIABLE(Object, var_flags, maybe_flags);
1182 TVARIABLE(Object, var_pattern, maybe_pattern);
1183
1184 // Handle a JSRegExp pattern.
1185 {
1186 Label next(this);
1187
1188 GotoIf(TaggedIsSmi(maybe_pattern), &next);
1189 GotoIfNot(IsJSRegExp(CAST(maybe_pattern)), &next);
1190
1191 // {maybe_flags} must be undefined in this case, otherwise throw.
1192 {
1193 Label next(this);
1194 GotoIf(IsUndefined(maybe_flags), &next);
1195
1196 ThrowTypeError(context, MessageTemplate::kRegExpFlags);
1197
1198 BIND(&next);
1199 }
1200
1201 const TNode<JSRegExp> pattern = CAST(maybe_pattern);
1202 const TNode<String> new_flags = FlagsGetter(context, pattern, true);
1203 const TNode<Object> new_pattern =
1204 LoadObjectField(pattern, JSRegExp::kSourceOffset);
1205
1206 var_flags = new_flags;
1207 var_pattern = new_pattern;
1208
1209 Goto(&next);
1210 BIND(&next);
1211 }
1212
1213 const TNode<Object> result = RegExpInitialize(
1214 context, receiver, var_pattern.value(), var_flags.value());
1215 Return(result);
1216 }
1217
1218 // Fast-path implementation for flag checks on an unmodified JSRegExp instance.
FastFlagGetter(TNode<JSRegExp> regexp,JSRegExp::Flag flag)1219 TNode<BoolT> RegExpBuiltinsAssembler::FastFlagGetter(TNode<JSRegExp> regexp,
1220 JSRegExp::Flag flag) {
1221 TNode<Smi> flags = CAST(LoadObjectField(regexp, JSRegExp::kFlagsOffset));
1222 TNode<Smi> mask = SmiConstant(flag);
1223 return ReinterpretCast<BoolT>(SmiToInt32(
1224 SmiShr(SmiAnd(flags, mask),
1225 base::bits::CountTrailingZeros(static_cast<int>(flag)))));
1226 }
1227
1228 // Load through the GetProperty stub.
SlowFlagGetter(TNode<Context> context,TNode<Object> regexp,JSRegExp::Flag flag)1229 TNode<BoolT> RegExpBuiltinsAssembler::SlowFlagGetter(TNode<Context> context,
1230 TNode<Object> regexp,
1231 JSRegExp::Flag flag) {
1232 Label out(this), if_true(this), if_false(this);
1233 TVARIABLE(BoolT, var_result);
1234
1235 // Only enabled based on a runtime flag.
1236 if (flag == JSRegExp::kLinear) {
1237 TNode<Word32T> flag_value = UncheckedCast<Word32T>(Load(
1238 MachineType::Uint8(),
1239 ExternalConstant(ExternalReference::
1240 address_of_enable_experimental_regexp_engine())));
1241 GotoIf(Word32Equal(Word32And(flag_value, Int32Constant(0xFF)),
1242 Int32Constant(0)),
1243 &if_false);
1244 }
1245
1246 Handle<String> name;
1247 switch (flag) {
1248 case JSRegExp::kNone:
1249 UNREACHABLE();
1250 case JSRegExp::kGlobal:
1251 name = isolate()->factory()->global_string();
1252 break;
1253 case JSRegExp::kIgnoreCase:
1254 name = isolate()->factory()->ignoreCase_string();
1255 break;
1256 case JSRegExp::kMultiline:
1257 name = isolate()->factory()->multiline_string();
1258 break;
1259 case JSRegExp::kDotAll:
1260 UNREACHABLE(); // Never called for dotAll.
1261 break;
1262 case JSRegExp::kSticky:
1263 name = isolate()->factory()->sticky_string();
1264 break;
1265 case JSRegExp::kUnicode:
1266 name = isolate()->factory()->unicode_string();
1267 break;
1268 case JSRegExp::kLinear:
1269 name = isolate()->factory()->linear_string();
1270 break;
1271 }
1272
1273 TNode<Object> value = GetProperty(context, regexp, name);
1274 BranchIfToBooleanIsTrue(value, &if_true, &if_false);
1275
1276 BIND(&if_true);
1277 var_result = BoolConstant(true);
1278 Goto(&out);
1279
1280 BIND(&if_false);
1281 var_result = BoolConstant(false);
1282 Goto(&out);
1283
1284 BIND(&out);
1285 return var_result.value();
1286 }
1287
FlagGetter(TNode<Context> context,TNode<Object> regexp,JSRegExp::Flag flag,bool is_fastpath)1288 TNode<BoolT> RegExpBuiltinsAssembler::FlagGetter(TNode<Context> context,
1289 TNode<Object> regexp,
1290 JSRegExp::Flag flag,
1291 bool is_fastpath) {
1292 return is_fastpath ? FastFlagGetter(CAST(regexp), flag)
1293 : SlowFlagGetter(context, regexp, flag);
1294 }
1295
AdvanceStringIndex(TNode<String> string,TNode<Number> index,TNode<BoolT> is_unicode,bool is_fastpath)1296 TNode<Number> RegExpBuiltinsAssembler::AdvanceStringIndex(
1297 TNode<String> string, TNode<Number> index, TNode<BoolT> is_unicode,
1298 bool is_fastpath) {
1299 CSA_ASSERT(this, IsNumberNormalized(index));
1300 if (is_fastpath) CSA_ASSERT(this, TaggedIsPositiveSmi(index));
1301
1302 // Default to last_index + 1.
1303 // TODO(pwong): Consider using TrySmiAdd for the fast path to reduce generated
1304 // code.
1305 TNode<Number> index_plus_one = NumberInc(index);
1306 TVARIABLE(Number, var_result, index_plus_one);
1307
1308 // TODO(v8:9880): Given that we have to convert index from Number to UintPtrT
1309 // anyway, consider using UintPtrT index to simplify the code below.
1310
1311 // Advancing the index has some subtle issues involving the distinction
1312 // between Smis and HeapNumbers. There's three cases:
1313 // * {index} is a Smi, {index_plus_one} is a Smi. The standard case.
1314 // * {index} is a Smi, {index_plus_one} overflows into a HeapNumber.
1315 // In this case we can return the result early, because
1316 // {index_plus_one} > {string}.length.
1317 // * {index} is a HeapNumber, {index_plus_one} is a HeapNumber. This can only
1318 // occur when {index} is outside the Smi range since we normalize
1319 // explicitly. Again we can return early.
1320 if (is_fastpath) {
1321 // Must be in Smi range on the fast path. We control the value of {index}
1322 // on all call-sites and can never exceed the length of the string.
1323 STATIC_ASSERT(String::kMaxLength + 2 < Smi::kMaxValue);
1324 CSA_ASSERT(this, TaggedIsPositiveSmi(index_plus_one));
1325 }
1326
1327 Label if_isunicode(this), out(this);
1328 GotoIfNot(is_unicode, &out);
1329
1330 // Keep this unconditional (even on the fast path) just to be safe.
1331 Branch(TaggedIsPositiveSmi(index_plus_one), &if_isunicode, &out);
1332
1333 BIND(&if_isunicode);
1334 {
1335 TNode<UintPtrT> string_length = Unsigned(LoadStringLengthAsWord(string));
1336 TNode<UintPtrT> untagged_plus_one =
1337 Unsigned(SmiUntag(CAST(index_plus_one)));
1338 GotoIfNot(UintPtrLessThan(untagged_plus_one, string_length), &out);
1339
1340 TNode<Int32T> lead =
1341 StringCharCodeAt(string, Unsigned(SmiUntag(CAST(index))));
1342 GotoIfNot(Word32Equal(Word32And(lead, Int32Constant(0xFC00)),
1343 Int32Constant(0xD800)),
1344 &out);
1345
1346 TNode<Int32T> trail = StringCharCodeAt(string, untagged_plus_one);
1347 GotoIfNot(Word32Equal(Word32And(trail, Int32Constant(0xFC00)),
1348 Int32Constant(0xDC00)),
1349 &out);
1350
1351 // At a surrogate pair, return index + 2.
1352 TNode<Number> index_plus_two = NumberInc(index_plus_one);
1353 var_result = index_plus_two;
1354
1355 Goto(&out);
1356 }
1357
1358 BIND(&out);
1359 return var_result.value();
1360 }
1361
1362 // ES#sec-createregexpstringiterator
1363 // CreateRegExpStringIterator ( R, S, global, fullUnicode )
CreateRegExpStringIterator(TNode<NativeContext> native_context,TNode<Object> regexp,TNode<String> string,TNode<BoolT> global,TNode<BoolT> full_unicode)1364 TNode<Object> RegExpMatchAllAssembler::CreateRegExpStringIterator(
1365 TNode<NativeContext> native_context, TNode<Object> regexp,
1366 TNode<String> string, TNode<BoolT> global, TNode<BoolT> full_unicode) {
1367 TNode<Map> map = CAST(LoadContextElement(
1368 native_context,
1369 Context::INITIAL_REGEXP_STRING_ITERATOR_PROTOTYPE_MAP_INDEX));
1370
1371 // 4. Let iterator be ObjectCreate(%RegExpStringIteratorPrototype%, «
1372 // [[IteratingRegExp]], [[IteratedString]], [[Global]], [[Unicode]],
1373 // [[Done]] »).
1374 TNode<HeapObject> iterator = Allocate(JSRegExpStringIterator::kHeaderSize);
1375 StoreMapNoWriteBarrier(iterator, map);
1376 StoreObjectFieldRoot(iterator,
1377 JSRegExpStringIterator::kPropertiesOrHashOffset,
1378 RootIndex::kEmptyFixedArray);
1379 StoreObjectFieldRoot(iterator, JSRegExpStringIterator::kElementsOffset,
1380 RootIndex::kEmptyFixedArray);
1381
1382 // 5. Set iterator.[[IteratingRegExp]] to R.
1383 StoreObjectFieldNoWriteBarrier(
1384 iterator, JSRegExpStringIterator::kIteratingRegExpOffset, regexp);
1385
1386 // 6. Set iterator.[[IteratedString]] to S.
1387 StoreObjectFieldNoWriteBarrier(
1388 iterator, JSRegExpStringIterator::kIteratedStringOffset, string);
1389
1390 // 7. Set iterator.[[Global]] to global.
1391 // 8. Set iterator.[[Unicode]] to fullUnicode.
1392 // 9. Set iterator.[[Done]] to false.
1393 TNode<Int32T> global_flag =
1394 Word32Shl(ReinterpretCast<Int32T>(global),
1395 Int32Constant(JSRegExpStringIterator::GlobalBit::kShift));
1396 TNode<Int32T> unicode_flag =
1397 Word32Shl(ReinterpretCast<Int32T>(full_unicode),
1398 Int32Constant(JSRegExpStringIterator::UnicodeBit::kShift));
1399 TNode<Int32T> iterator_flags = Word32Or(global_flag, unicode_flag);
1400 StoreObjectFieldNoWriteBarrier(iterator, JSRegExpStringIterator::kFlagsOffset,
1401 SmiFromInt32(iterator_flags));
1402
1403 return iterator;
1404 }
1405
1406 // Generates the fast path for @@split. {regexp} is an unmodified, non-sticky
1407 // JSRegExp, {string} is a String, and {limit} is a Smi.
RegExpPrototypeSplitBody(TNode<Context> context,TNode<JSRegExp> regexp,TNode<String> string,const TNode<Smi> limit)1408 TNode<JSArray> RegExpBuiltinsAssembler::RegExpPrototypeSplitBody(
1409 TNode<Context> context, TNode<JSRegExp> regexp, TNode<String> string,
1410 const TNode<Smi> limit) {
1411 CSA_ASSERT(this, IsFastRegExpPermissive(context, regexp));
1412 CSA_ASSERT(this, Word32BinaryNot(FastFlagGetter(regexp, JSRegExp::kSticky)));
1413
1414 const TNode<IntPtrT> int_limit = SmiUntag(limit);
1415
1416 const ElementsKind kind = PACKED_ELEMENTS;
1417
1418 const TNode<NativeContext> native_context = LoadNativeContext(context);
1419 TNode<Map> array_map = LoadJSArrayElementsMap(kind, native_context);
1420
1421 Label return_empty_array(this, Label::kDeferred);
1422 TVARIABLE(JSArray, var_result);
1423 Label done(this);
1424
1425 // If limit is zero, return an empty array.
1426 {
1427 Label next(this), if_limitiszero(this, Label::kDeferred);
1428 Branch(SmiEqual(limit, SmiZero()), &return_empty_array, &next);
1429 BIND(&next);
1430 }
1431
1432 const TNode<Smi> string_length = LoadStringLengthAsSmi(string);
1433
1434 // If passed the empty {string}, return either an empty array or a singleton
1435 // array depending on whether the {regexp} matches.
1436 {
1437 Label next(this), if_stringisempty(this, Label::kDeferred);
1438 Branch(SmiEqual(string_length, SmiZero()), &if_stringisempty, &next);
1439
1440 BIND(&if_stringisempty);
1441 {
1442 const TNode<Object> last_match_info = LoadContextElement(
1443 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
1444
1445 const TNode<Object> match_indices =
1446 CallBuiltin(Builtins::kRegExpExecInternal, context, regexp, string,
1447 SmiZero(), last_match_info);
1448
1449 Label return_singleton_array(this);
1450 Branch(IsNull(match_indices), &return_singleton_array,
1451 &return_empty_array);
1452
1453 BIND(&return_singleton_array);
1454 {
1455 TNode<Smi> length = SmiConstant(1);
1456 TNode<IntPtrT> capacity = IntPtrConstant(1);
1457 base::Optional<TNode<AllocationSite>> allocation_site = base::nullopt;
1458 var_result =
1459 AllocateJSArray(kind, array_map, capacity, length, allocation_site);
1460
1461 TNode<FixedArray> fixed_array = CAST(LoadElements(var_result.value()));
1462 UnsafeStoreFixedArrayElement(fixed_array, 0, string);
1463
1464 Goto(&done);
1465 }
1466 }
1467
1468 BIND(&next);
1469 }
1470
1471 // Loop preparations.
1472
1473 GrowableFixedArray array(state());
1474
1475 TVARIABLE(Smi, var_last_matched_until, SmiZero());
1476 TVARIABLE(Smi, var_next_search_from, SmiZero());
1477
1478 Label loop(this, {array.var_array(), array.var_length(), array.var_capacity(),
1479 &var_last_matched_until, &var_next_search_from}),
1480 push_suffix_and_out(this), out(this);
1481 Goto(&loop);
1482
1483 BIND(&loop);
1484 {
1485 const TNode<Smi> next_search_from = var_next_search_from.value();
1486 const TNode<Smi> last_matched_until = var_last_matched_until.value();
1487
1488 // We're done if we've reached the end of the string.
1489 {
1490 Label next(this);
1491 Branch(SmiEqual(next_search_from, string_length), &push_suffix_and_out,
1492 &next);
1493 BIND(&next);
1494 }
1495
1496 // Search for the given {regexp}.
1497
1498 const TNode<Object> last_match_info = LoadContextElement(
1499 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
1500
1501 const TNode<HeapObject> match_indices_ho =
1502 CAST(CallBuiltin(Builtins::kRegExpExecInternal, context, regexp, string,
1503 next_search_from, last_match_info));
1504
1505 // We're done if no match was found.
1506 {
1507 Label next(this);
1508 Branch(IsNull(match_indices_ho), &push_suffix_and_out, &next);
1509 BIND(&next);
1510 }
1511
1512 TNode<FixedArray> match_indices = CAST(match_indices_ho);
1513 const TNode<Smi> match_from = CAST(UnsafeLoadFixedArrayElement(
1514 match_indices, RegExpMatchInfo::kFirstCaptureIndex));
1515
1516 // We're done if the match starts beyond the string.
1517 {
1518 Label next(this);
1519 Branch(SmiEqual(match_from, string_length), &push_suffix_and_out, &next);
1520 BIND(&next);
1521 }
1522
1523 const TNode<Smi> match_to = CAST(UnsafeLoadFixedArrayElement(
1524 match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1));
1525
1526 // Advance index and continue if the match is empty.
1527 {
1528 Label next(this);
1529
1530 GotoIfNot(SmiEqual(match_to, next_search_from), &next);
1531 GotoIfNot(SmiEqual(match_to, last_matched_until), &next);
1532
1533 const TNode<BoolT> is_unicode =
1534 FastFlagGetter(regexp, JSRegExp::kUnicode);
1535 const TNode<Number> new_next_search_from =
1536 AdvanceStringIndex(string, next_search_from, is_unicode, true);
1537 var_next_search_from = CAST(new_next_search_from);
1538 Goto(&loop);
1539
1540 BIND(&next);
1541 }
1542
1543 // A valid match was found, add the new substring to the array.
1544 {
1545 const TNode<Smi> from = last_matched_until;
1546 const TNode<Smi> to = match_from;
1547 array.Push(CallBuiltin(Builtins::kSubString, context, string, from, to));
1548 GotoIf(WordEqual(array.length(), int_limit), &out);
1549 }
1550
1551 // Add all captures to the array.
1552 {
1553 const TNode<Smi> num_registers = CAST(LoadFixedArrayElement(
1554 match_indices, RegExpMatchInfo::kNumberOfCapturesIndex));
1555 const TNode<IntPtrT> int_num_registers = SmiUntag(num_registers);
1556
1557 TVARIABLE(IntPtrT, var_reg, IntPtrConstant(2));
1558
1559 Label nested_loop(this, {array.var_array(), array.var_length(),
1560 array.var_capacity(), &var_reg}),
1561 nested_loop_out(this);
1562 Branch(IntPtrLessThan(var_reg.value(), int_num_registers), &nested_loop,
1563 &nested_loop_out);
1564
1565 BIND(&nested_loop);
1566 {
1567 const TNode<IntPtrT> reg = var_reg.value();
1568 const TNode<Object> from = LoadFixedArrayElement(
1569 match_indices, reg,
1570 RegExpMatchInfo::kFirstCaptureIndex * kTaggedSize);
1571 const TNode<Smi> to = CAST(LoadFixedArrayElement(
1572 match_indices, reg,
1573 (RegExpMatchInfo::kFirstCaptureIndex + 1) * kTaggedSize));
1574
1575 Label select_capture(this), select_undefined(this), store_value(this);
1576 TVARIABLE(Object, var_value);
1577 Branch(SmiEqual(to, SmiConstant(-1)), &select_undefined,
1578 &select_capture);
1579
1580 BIND(&select_capture);
1581 {
1582 var_value =
1583 CallBuiltin(Builtins::kSubString, context, string, from, to);
1584 Goto(&store_value);
1585 }
1586
1587 BIND(&select_undefined);
1588 {
1589 var_value = UndefinedConstant();
1590 Goto(&store_value);
1591 }
1592
1593 BIND(&store_value);
1594 {
1595 array.Push(var_value.value());
1596 GotoIf(WordEqual(array.length(), int_limit), &out);
1597
1598 const TNode<IntPtrT> new_reg = IntPtrAdd(reg, IntPtrConstant(2));
1599 var_reg = new_reg;
1600
1601 Branch(IntPtrLessThan(new_reg, int_num_registers), &nested_loop,
1602 &nested_loop_out);
1603 }
1604 }
1605
1606 BIND(&nested_loop_out);
1607 }
1608
1609 var_last_matched_until = match_to;
1610 var_next_search_from = match_to;
1611 Goto(&loop);
1612 }
1613
1614 BIND(&push_suffix_and_out);
1615 {
1616 const TNode<Smi> from = var_last_matched_until.value();
1617 const TNode<Smi> to = string_length;
1618 array.Push(CallBuiltin(Builtins::kSubString, context, string, from, to));
1619 Goto(&out);
1620 }
1621
1622 BIND(&out);
1623 {
1624 var_result = array.ToJSArray(context);
1625 Goto(&done);
1626 }
1627
1628 BIND(&return_empty_array);
1629 {
1630 TNode<Smi> length = SmiZero();
1631 TNode<IntPtrT> capacity = IntPtrZero();
1632 base::Optional<TNode<AllocationSite>> allocation_site = base::nullopt;
1633 var_result =
1634 AllocateJSArray(kind, array_map, capacity, length, allocation_site);
1635 Goto(&done);
1636 }
1637
1638 BIND(&done);
1639 return var_result.value();
1640 }
1641
1642 } // namespace internal
1643 } // namespace v8
1644