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