OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |