Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(150)

Side by Side Diff: src/builtins/builtins-regexp.cc

Issue 2398423002: [regexp] Port RegExp.prototype[@@replace] (Closed)
Patch Set: Tweaks in string code-stub-assembler methods Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 the V8 project authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "src/builtins/builtins-utils.h" 5 #include "src/builtins/builtins-utils.h"
6 #include "src/builtins/builtins.h" 6 #include "src/builtins/builtins.h"
7 7
8 #include "src/code-factory.h" 8 #include "src/code-factory.h"
9 #include "src/regexp/jsregexp.h" 9 #include "src/regexp/jsregexp.h"
10 #include "src/regexp/regexp-utils.h" 10 #include "src/regexp/regexp-utils.h"
(...skipping 456 matching lines...) Expand 10 before | Expand all | Expand 10 after
467 a->Return(result); 467 a->Return(result);
468 } 468 }
469 } 469 }
470 } 470 }
471 471
472 namespace { 472 namespace {
473 473
474 compiler::Node* ThrowIfNotJSReceiver(CodeStubAssembler* a, Isolate* isolate, 474 compiler::Node* ThrowIfNotJSReceiver(CodeStubAssembler* a, Isolate* isolate,
475 compiler::Node* context, 475 compiler::Node* context,
476 compiler::Node* value, 476 compiler::Node* value,
477 MessageTemplate::Template msg_template,
477 char const* method_name) { 478 char const* method_name) {
478 typedef compiler::Node Node; 479 typedef compiler::Node Node;
479 typedef CodeStubAssembler::Label Label; 480 typedef CodeStubAssembler::Label Label;
480 typedef CodeStubAssembler::Variable Variable; 481 typedef CodeStubAssembler::Variable Variable;
481 482
482 Label out(a), throw_exception(a, Label::kDeferred); 483 Label out(a), throw_exception(a, Label::kDeferred);
483 Variable var_value_map(a, MachineRepresentation::kTagged); 484 Variable var_value_map(a, MachineRepresentation::kTagged);
484 485
485 a->GotoIf(a->WordIsSmi(value), &throw_exception); 486 a->GotoIf(a->WordIsSmi(value), &throw_exception);
486 487
487 // Load the instance type of the {value}. 488 // Load the instance type of the {value}.
488 var_value_map.Bind(a->LoadMap(value)); 489 var_value_map.Bind(a->LoadMap(value));
489 Node* const value_instance_type = 490 Node* const value_instance_type =
490 a->LoadMapInstanceType(var_value_map.value()); 491 a->LoadMapInstanceType(var_value_map.value());
491 492
492 a->Branch(a->IsJSReceiverInstanceType(value_instance_type), &out, 493 a->Branch(a->IsJSReceiverInstanceType(value_instance_type), &out,
493 &throw_exception); 494 &throw_exception);
494 495
495 // The {value} is not a compatible receiver for this method. 496 // The {value} is not a compatible receiver for this method.
496 a->Bind(&throw_exception); 497 a->Bind(&throw_exception);
497 { 498 {
498 Node* const message_id = 499 Node* const message_id = a->SmiConstant(Smi::FromInt(msg_template));
499 a->SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonObject));
500 Node* const method_name_str = a->HeapConstant( 500 Node* const method_name_str = a->HeapConstant(
501 isolate->factory()->NewStringFromAsciiChecked(method_name, TENURED)); 501 isolate->factory()->NewStringFromAsciiChecked(method_name, TENURED));
502 502
503 Callable callable = CodeFactory::ToString(isolate); 503 Callable callable = CodeFactory::ToString(isolate);
504 Node* const value_str = a->CallStub(callable, context, value); 504 Node* const value_str = a->CallStub(callable, context, value);
505 505
506 a->CallRuntime(Runtime::kThrowTypeError, context, message_id, 506 a->CallRuntime(Runtime::kThrowTypeError, context, message_id,
507 method_name_str, value_str); 507 method_name_str, value_str);
508 var_value_map.Bind(a->UndefinedConstant()); 508 var_value_map.Bind(a->UndefinedConstant());
509 a->Goto(&out); // Never reached. 509 a->Goto(&out); // Never reached.
(...skipping 11 matching lines...) Expand all
521 Node* const native_context = a->LoadNativeContext(context); 521 Node* const native_context = a->LoadNativeContext(context);
522 Node* const regexp_fun = 522 Node* const regexp_fun =
523 a->LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); 523 a->LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
524 Node* const initial_map = 524 Node* const initial_map =
525 a->LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); 525 a->LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
526 Node* const has_initialmap = a->WordEqual(map, initial_map); 526 Node* const has_initialmap = a->WordEqual(map, initial_map);
527 527
528 return has_initialmap; 528 return has_initialmap;
529 } 529 }
530 530
531 // RegExp fast path implementations rely on unmodified JSRegExp instances.
532 // We use a fairly coarse granularity for this and simply check whether both
533 // the regexp itself is unmodified (i.e. its map has not changed) and its
534 // prototype is unmodified.
535 void BranchIfFastPath(CodeStubAssembler* a, compiler::Node* context,
536 compiler::Node* map,
537 CodeStubAssembler::Label* if_isunmodified,
538 CodeStubAssembler::Label* if_ismodified) {
539 typedef compiler::Node Node;
540
541 Node* const native_context = a->LoadNativeContext(context);
542 Node* const regexp_fun =
543 a->LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
544 Node* const initial_map =
545 a->LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
546 Node* const has_initialmap = a->WordEqual(map, initial_map);
547
548 a->GotoUnless(has_initialmap, if_ismodified);
549
550 Node* const initial_proto_initial_map = a->LoadContextElement(
551 native_context, Context::REGEXP_PROTOTYPE_MAP_INDEX);
552 Node* const proto_map = a->LoadMap(a->LoadMapPrototype(map));
553 Node* const proto_has_initialmap =
554 a->WordEqual(proto_map, initial_proto_initial_map);
Yang 2016/10/11 13:26:35 If we modify a property on the regexp prototype, d
jgruber 2016/10/13 07:39:44 Added.
555
556 // TODO(ishell): Update this check once map changes for constant field
557 // tracking are landing.
558
559 a->Branch(proto_has_initialmap, if_isunmodified, if_ismodified);
560 }
561
531 } // namespace 562 } // namespace
532 563
533 void Builtins::Generate_RegExpPrototypeFlagsGetter(CodeStubAssembler* a) { 564 void Builtins::Generate_RegExpPrototypeFlagsGetter(CodeStubAssembler* a) {
534 typedef CodeStubAssembler::Variable Variable; 565 typedef CodeStubAssembler::Variable Variable;
535 typedef CodeStubAssembler::Label Label; 566 typedef CodeStubAssembler::Label Label;
536 typedef compiler::Node Node; 567 typedef compiler::Node Node;
537 568
538 Node* const receiver = a->Parameter(0); 569 Node* const receiver = a->Parameter(0);
539 Node* const context = a->Parameter(3); 570 Node* const context = a->Parameter(3);
540 571
541 Isolate* isolate = a->isolate(); 572 Isolate* isolate = a->isolate();
542 Node* const int_zero = a->IntPtrConstant(0); 573 Node* const int_zero = a->IntPtrConstant(0);
543 Node* const int_one = a->IntPtrConstant(1); 574 Node* const int_one = a->IntPtrConstant(1);
544 575
545 Node* const map = ThrowIfNotJSReceiver(a, isolate, context, receiver, 576 Node* const map = ThrowIfNotJSReceiver(a, isolate, context, receiver,
577 MessageTemplate::kRegExpNonObject,
546 "RegExp.prototype.flags"); 578 "RegExp.prototype.flags");
547 579
548 Variable var_length(a, MachineType::PointerRepresentation()); 580 Variable var_length(a, MachineType::PointerRepresentation());
549 Variable var_flags(a, MachineType::PointerRepresentation()); 581 Variable var_flags(a, MachineType::PointerRepresentation());
550 582
551 // First, count the number of characters we will need and check which flags 583 // First, count the number of characters we will need and check which flags
552 // are set. 584 // are set.
553 585
554 var_length.Bind(int_zero); 586 var_length.Bind(int_zero);
555 587
(...skipping 276 matching lines...) Expand 10 before | Expand all | Expand 10 after
832 "RegExp.prototype.sticky"); 864 "RegExp.prototype.sticky");
833 } 865 }
834 866
835 // ES6 21.2.5.15. 867 // ES6 21.2.5.15.
836 void Builtins::Generate_RegExpPrototypeUnicodeGetter(CodeStubAssembler* a) { 868 void Builtins::Generate_RegExpPrototypeUnicodeGetter(CodeStubAssembler* a) {
837 Generate_FlagGetter(a, JSRegExp::kUnicode, 869 Generate_FlagGetter(a, JSRegExp::kUnicode,
838 v8::Isolate::kRegExpPrototypeUnicodeGetter, 870 v8::Isolate::kRegExpPrototypeUnicodeGetter,
839 "RegExp.prototype.unicode"); 871 "RegExp.prototype.unicode");
840 } 872 }
841 873
842
843 // The properties $1..$9 are the first nine capturing substrings of the last 874 // The properties $1..$9 are the first nine capturing substrings of the last
844 // successful match, or ''. The function RegExpMakeCaptureGetter will be 875 // successful match, or ''. The function RegExpMakeCaptureGetter will be
845 // called with indices from 1 to 9. 876 // called with indices from 1 to 9.
846 #define DEFINE_CAPTURE_GETTER(i) \ 877 #define DEFINE_CAPTURE_GETTER(i) \
847 BUILTIN(RegExpCapture##i##Getter) { \ 878 BUILTIN(RegExpCapture##i##Getter) { \
848 HandleScope scope(isolate); \ 879 HandleScope scope(isolate); \
849 return *RegExpUtils::GenericCaptureGetter( \ 880 return *RegExpUtils::GenericCaptureGetter( \
850 isolate, isolate->regexp_last_match_info(), i); \ 881 isolate, isolate->regexp_last_match_info(), i); \
851 } 882 }
852 DEFINE_CAPTURE_GETTER(1) 883 DEFINE_CAPTURE_GETTER(1)
(...skipping 336 matching lines...) Expand 10 before | Expand all | Expand 10 after
1189 } 1220 }
1190 1221
1191 { 1222 {
1192 Handle<String> substr = 1223 Handle<String> substr =
1193 factory->NewSubString(string, current_index, start_match); 1224 factory->NewSubString(string, current_index, start_match);
1194 elems = FixedArray::SetAndGrow(elems, num_elems++, substr); 1225 elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
1195 } 1226 }
1196 1227
1197 if (num_elems == limit) break; 1228 if (num_elems == limit) break;
1198 1229
1199 // TODO(jgruber): Refactor GetLastMatchInfo methods to take an input
1200 // argument.
1201 Handle<Object> num_captures_obj = 1230 Handle<Object> num_captures_obj =
1202 JSReceiver::GetElement(isolate, match_indices, 1231 JSReceiver::GetElement(isolate, match_indices,
1203 RegExpImpl::kLastCaptureCount) 1232 RegExpImpl::kLastCaptureCount)
1204 .ToHandleChecked(); 1233 .ToHandleChecked();
1205 const int match_indices_len = Handle<Smi>::cast(num_captures_obj)->value() + 1234 const int match_indices_len = Handle<Smi>::cast(num_captures_obj)->value() +
1206 RegExpImpl::kFirstCapture; 1235 RegExpImpl::kFirstCapture;
1207 1236
1208 for (int i = RegExpImpl::kFirstCapture + 2; i < match_indices_len;) { 1237 for (int i = RegExpImpl::kFirstCapture + 2; i < match_indices_len;) {
1209 Handle<Object> start_obj = 1238 Handle<Object> start_obj =
1210 JSReceiver::GetElement(isolate, match_indices, i++).ToHandleChecked(); 1239 JSReceiver::GetElement(isolate, match_indices, i++).ToHandleChecked();
(...skipping 221 matching lines...) Expand 10 before | Expand all | Expand 10 after
1432 Handle<String> substr = 1461 Handle<String> substr =
1433 factory->NewSubString(string, prev_string_index, length); 1462 factory->NewSubString(string, prev_string_index, length);
1434 elems = FixedArray::SetAndGrow(elems, num_elems++, substr); 1463 elems = FixedArray::SetAndGrow(elems, num_elems++, substr);
1435 } 1464 }
1436 1465
1437 out: 1466 out:
1438 elems->Shrink(num_elems); 1467 elems->Shrink(num_elems);
1439 return *factory->NewJSArrayWithElements(elems); 1468 return *factory->NewJSArrayWithElements(elems);
1440 } 1469 }
1441 1470
1471 namespace {
1472
1473 compiler::Node* ReplaceFastPath(CodeStubAssembler* a, compiler::Node* context,
1474 compiler::Node* regexp,
1475 compiler::Node* subject_string,
1476 compiler::Node* replace_string) {
1477 // The fast path is reached only if {receiver} is an unmodified
1478 // JSRegExp instance, {replace_value} is non-callable, and
1479 // ToString({replace_value}) does not contain '$', i.e. we're doing a simple
1480 // string replacement.
1481
1482 typedef CodeStubAssembler::Variable Variable;
1483 typedef CodeStubAssembler::Label Label;
1484 typedef compiler::Node Node;
1485
1486 Isolate* const isolate = a->isolate();
1487
1488 Node* const null = a->NullConstant();
1489 Node* const int_zero = a->IntPtrConstant(0);
1490 Node* const smi_zero = a->SmiConstant(Smi::FromInt(0));
1491
1492 Label out(a);
1493 Variable var_result(a, MachineRepresentation::kTagged);
1494
1495 // At this point, we know that {regexp} has an initial map.
1496 // TODO(jgruber): Refactor to not require this node.
1497 Node* const has_initialmap = a->WordEqual(int_zero, int_zero); // True.
Yang 2016/10/11 13:26:35 This would be the same as a->TrueConstant. Let's i
jgruber 2016/10/13 07:39:44 Done.
1498
1499 // Load the last match info.
1500 Node* const native_context = a->LoadNativeContext(context);
1501 Node* const last_match_info = a->LoadContextElement(
1502 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
1503
1504 // Is {regexp} global?
1505 Label if_isglobal(a), if_isnonglobal(a);
1506 Node* const flags = a->LoadObjectField(regexp, JSRegExp::kFlagsOffset);
1507 Node* const is_global =
1508 a->WordAnd(a->SmiUntag(flags), a->IntPtrConstant(JSRegExp::kGlobal));
1509 a->Branch(a->WordEqual(is_global, int_zero), &if_isnonglobal, &if_isglobal);
1510
1511 a->Bind(&if_isglobal);
1512 {
1513 // Hand off global regexps to runtime.
1514 StoreLastIndex(a, context, has_initialmap, regexp, smi_zero);
1515 Node* const result =
1516 a->CallRuntime(Runtime::kStringReplaceGlobalRegExpWithString, context,
1517 subject_string, regexp, replace_string, last_match_info);
1518 var_result.Bind(result);
1519 a->Goto(&out);
1520 }
1521
1522 a->Bind(&if_isnonglobal);
1523 {
1524 // Run exec, then manually construct the resulting string.
1525 Callable exec_callable = CodeFactory::RegExpExec(isolate);
1526 Node* const match_indices =
1527 a->CallStub(exec_callable, context, regexp, subject_string, smi_zero,
1528 last_match_info);
1529
1530 Label if_matched(a), if_didnotmatch(a);
1531 a->Branch(a->WordEqual(match_indices, null), &if_didnotmatch, &if_matched);
1532
1533 a->Bind(&if_didnotmatch);
1534 {
1535 StoreLastIndex(a, context, has_initialmap, regexp, smi_zero);
1536
1537 var_result.Bind(subject_string);
1538 a->Goto(&out);
1539 }
1540
1541 a->Bind(&if_matched);
1542 {
1543 Node* const match_elements = a->LoadElements(match_indices);
1544 CodeStubAssembler::ParameterMode mode =
1545 CodeStubAssembler::INTPTR_PARAMETERS;
1546
1547 Node* const subject_start = smi_zero;
1548 Node* const match_start = a->LoadFixedArrayElement(
1549 match_elements, a->IntPtrConstant(RegExpImpl::kFirstCapture), 0,
1550 mode);
1551 Node* const match_end = a->LoadFixedArrayElement(
1552 match_elements, a->IntPtrConstant(RegExpImpl::kFirstCapture + 1), 0,
1553 mode);
1554 Node* const subject_end = a->LoadStringLength(subject_string);
1555
1556 Label if_replaceisempty(a), if_replaceisnotempty(a);
1557 Node* const replace_length = a->LoadStringLength(replace_string);
1558 a->Branch(a->SmiEqual(replace_length, smi_zero), &if_replaceisempty,
1559 &if_replaceisnotempty);
1560
1561 a->Bind(&if_replaceisempty);
1562 {
1563 // TODO(jgruber): We could skip many of the checks that using SubString
1564 // here entails.
1565
1566 Node* const first_part =
1567 a->SubString(context, subject_string, subject_start, match_start);
1568 Node* const second_part =
1569 a->SubString(context, subject_string, match_end, subject_end);
1570
1571 Node* const result = a->StringConcat(context, first_part, second_part);
1572 var_result.Bind(result);
1573 a->Goto(&out);
1574 }
1575
1576 a->Bind(&if_replaceisnotempty);
1577 {
1578 Node* const first_part =
1579 a->SubString(context, subject_string, subject_start, match_start);
1580 Node* const second_part = replace_string;
1581 Node* const third_part =
1582 a->SubString(context, subject_string, match_end, subject_end);
1583
1584 Node* result = a->StringConcat(context, first_part, second_part);
1585 result = a->StringConcat(context, result, third_part);
1586
1587 var_result.Bind(result);
1588 a->Goto(&out);
1589 }
1590 }
1591 }
1592
1593 a->Bind(&out);
1594 return var_result.value();
1595 }
1596
1597 } // namespace
1598
1599 // ES#sec-regexp.prototype-@@replace
1600 // RegExp.prototype [ @@replace ] ( string, replaceValue )
1601 void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
1602 typedef CodeStubAssembler::Label Label;
1603 typedef compiler::Node Node;
1604
1605 Isolate* const isolate = a->isolate();
1606
1607 Node* const maybe_receiver = a->Parameter(0);
1608 Node* const maybe_string = a->Parameter(1);
1609 Node* const replace_value = a->Parameter(2);
1610 Node* const context = a->Parameter(5);
1611
1612 Node* const int_zero = a->IntPtrConstant(0);
1613
1614 // Ensure {receiver} is a JSReceiver.
1615 Node* const map =
1616 ThrowIfNotJSReceiver(a, isolate, context, maybe_receiver,
1617 MessageTemplate::kIncompatibleMethodReceiver,
1618 "RegExp.prototype.@@replace");
1619 Node* const receiver = maybe_receiver;
1620
1621 // Convert {maybe_string} to a String.
1622 Callable tostring_callable = CodeFactory::ToString(isolate);
1623 Node* const string = a->CallStub(tostring_callable, context, maybe_string);
1624
1625 // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance?
1626 Label checkreplacecallable(a), runtime(a, Label::kDeferred), fastpath(a);
1627 BranchIfFastPath(a, context, map, &checkreplacecallable, &runtime);
1628
1629 a->Bind(&checkreplacecallable);
1630 Node* const regexp = receiver;
1631
1632 // 2. Is {replace_value} callable?
1633 Label checkreplacestring(a);
1634 a->GotoIf(a->WordIsSmi(replace_value), &checkreplacestring);
1635
1636 Node* const replace_value_map = a->LoadMap(replace_value);
1637 a->Branch(
1638 a->Word32Equal(a->Word32And(a->LoadMapBitField(replace_value_map),
1639 a->Int32Constant(1 << Map::kIsCallable)),
1640 a->Int32Constant(0)),
1641 &checkreplacestring, &runtime);
1642
1643 // 3. Does ToString({replace_value}) contain '$'?
1644 a->Bind(&checkreplacestring);
1645 {
1646 Node* const replace_string =
1647 a->CallStub(tostring_callable, context, replace_value);
1648
1649 Node* const dollar_char = a->IntPtrConstant('$');
1650 Node* const smi_minusone = a->SmiConstant(Smi::FromInt(-1));
1651 a->GotoUnless(a->SmiEqual(a->StringIndexOfChar(context, replace_string,
1652 dollar_char, int_zero),
1653 smi_minusone),
1654 &runtime);
1655
1656 a->Return(ReplaceFastPath(a, context, regexp, string, replace_string));
1657 }
1658
1659 a->Bind(&runtime);
1660 {
1661 Node* const result = a->CallRuntime(Runtime::kRegExpReplace, context,
1662 receiver, string, replace_value);
1663 a->Return(result);
1664 }
1665 }
1666
1442 } // namespace internal 1667 } // namespace internal
1443 } // namespace v8 1668 } // namespace v8
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698