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