summaryrefslogtreecommitdiff
path: root/chromium/v8/src/builtins/builtins-regexp.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/v8/src/builtins/builtins-regexp.cc')
-rw-r--r--chromium/v8/src/builtins/builtins-regexp.cc211
1 files changed, 152 insertions, 59 deletions
diff --git a/chromium/v8/src/builtins/builtins-regexp.cc b/chromium/v8/src/builtins/builtins-regexp.cc
index 93cb55899b4..f76136b8066 100644
--- a/chromium/v8/src/builtins/builtins-regexp.cc
+++ b/chromium/v8/src/builtins/builtins-regexp.cc
@@ -247,19 +247,24 @@ Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult(
Node* const regexp_lastindex = LoadLastIndex(context, regexp, is_fastpath);
var_lastindex.Bind(regexp_lastindex);
- // Omit ToLength if lastindex is a non-negative smi.
- Label call_tolength(this, Label::kDeferred), next(this);
- Branch(TaggedIsPositiveSmi(regexp_lastindex), &next, &call_tolength);
+ if (is_fastpath) {
+ // ToLength on a positive smi is a nop and can be skipped.
+ CSA_ASSERT(this, TaggedIsPositiveSmi(regexp_lastindex));
+ } else {
+ // Omit ToLength if lastindex is a non-negative smi.
+ Label call_tolength(this, Label::kDeferred), next(this);
+ Branch(TaggedIsPositiveSmi(regexp_lastindex), &next, &call_tolength);
+
+ Bind(&call_tolength);
+ {
+ Callable tolength_callable = CodeFactory::ToLength(isolate);
+ var_lastindex.Bind(
+ CallStub(tolength_callable, context, regexp_lastindex));
+ Goto(&next);
+ }
- Bind(&call_tolength);
- {
- Callable tolength_callable = CodeFactory::ToLength(isolate);
- var_lastindex.Bind(
- CallStub(tolength_callable, context, regexp_lastindex));
- Goto(&next);
+ Bind(&next);
}
-
- Bind(&next);
}
// Check whether the regexp is global or sticky, which determines whether we
@@ -408,7 +413,11 @@ Node* RegExpBuiltinsAssembler::ThrowIfNotJSReceiver(
return var_value_map.value();
}
-Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* map) {
+Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* object,
+ Node* map) {
+ Label out(this);
+ Variable var_result(this, MachineRepresentation::kWord32);
+
Node* const native_context = LoadNativeContext(context);
Node* const regexp_fun =
LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
@@ -416,17 +425,33 @@ Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* map) {
LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
Node* const has_initialmap = WordEqual(map, initial_map);
- return has_initialmap;
+ var_result.Bind(has_initialmap);
+ GotoIfNot(has_initialmap, &out);
+
+ // The smi check is required to omit ToLength(lastIndex) calls with possible
+ // user-code execution on the fast path.
+ Node* const last_index = FastLoadLastIndex(object);
+ var_result.Bind(TaggedIsPositiveSmi(last_index));
+ Goto(&out);
+
+ Bind(&out);
+ return var_result.value();
}
// RegExp fast path implementations rely on unmodified JSRegExp instances.
// We use a fairly coarse granularity for this and simply check whether both
-// the regexp itself is unmodified (i.e. its map has not changed) and its
-// prototype is unmodified.
+// the regexp itself is unmodified (i.e. its map has not changed), its
+// prototype is unmodified, and lastIndex is a non-negative smi.
void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context,
+ Node* const object,
Node* const map,
Label* const if_isunmodified,
Label* const if_ismodified) {
+ CSA_ASSERT(this, WordEqual(LoadMap(object), map));
+
+ // TODO(ishell): Update this check once map changes for constant field
+ // tracking are landing.
+
Node* const native_context = LoadNativeContext(context);
Node* const regexp_fun =
LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
@@ -442,18 +467,21 @@ void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context,
Node* const proto_has_initialmap =
WordEqual(proto_map, initial_proto_initial_map);
- // TODO(ishell): Update this check once map changes for constant field
- // tracking are landing.
+ GotoIfNot(proto_has_initialmap, if_ismodified);
- Branch(proto_has_initialmap, if_isunmodified, if_ismodified);
+ // The smi check is required to omit ToLength(lastIndex) calls with possible
+ // user-code execution on the fast path.
+ Node* const last_index = FastLoadLastIndex(object);
+ Branch(TaggedIsPositiveSmi(last_index), if_isunmodified, if_ismodified);
}
Node* RegExpBuiltinsAssembler::IsFastRegExpMap(Node* const context,
+ Node* const object,
Node* const map) {
Label yup(this), nope(this), out(this);
Variable var_result(this, MachineRepresentation::kWord32);
- BranchIfFastRegExp(context, map, &yup, &nope);
+ BranchIfFastRegExp(context, object, map, &yup, &nope);
Bind(&yup);
var_result.Bind(Int32Constant(1));
@@ -486,16 +514,16 @@ TF_BUILTIN(RegExpPrototypeExec, RegExpBuiltinsAssembler) {
Node* const context = Parameter(4);
// Ensure {maybe_receiver} is a JSRegExp.
- Node* const regexp_map = ThrowIfNotInstanceType(
- context, maybe_receiver, JS_REGEXP_TYPE, "RegExp.prototype.exec");
+ ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE,
+ "RegExp.prototype.exec");
Node* const receiver = maybe_receiver;
// Convert {maybe_string} to a String.
Node* const string = ToString(context, maybe_string);
Label if_isfastpath(this), if_isslowpath(this);
- Branch(IsInitialRegExpMap(context, regexp_map), &if_isfastpath,
- &if_isslowpath);
+ Branch(IsInitialRegExpMap(context, receiver, LoadMap(receiver)),
+ &if_isfastpath, &if_isslowpath);
Bind(&if_isfastpath);
{
@@ -681,7 +709,8 @@ TF_BUILTIN(RegExpPrototypeFlagsGetter, RegExpBuiltinsAssembler) {
Node* const receiver = maybe_receiver;
Label if_isfastpath(this), if_isslowpath(this, Label::kDeferred);
- Branch(IsInitialRegExpMap(context, map), &if_isfastpath, &if_isslowpath);
+ Branch(IsInitialRegExpMap(context, receiver, map), &if_isfastpath,
+ &if_isslowpath);
Bind(&if_isfastpath);
Return(FlagsGetter(context, receiver, true));
@@ -1221,7 +1250,7 @@ Node* RegExpBuiltinsAssembler::RegExpExec(Node* context, Node* regexp,
Label out(this), if_isfastpath(this), if_isslowpath(this);
Node* const map = LoadMap(regexp);
- BranchIfFastRegExp(context, map, &if_isfastpath, &if_isslowpath);
+ BranchIfFastRegExp(context, regexp, map, &if_isfastpath, &if_isslowpath);
Bind(&if_isfastpath);
{
@@ -1286,16 +1315,17 @@ TF_BUILTIN(RegExpPrototypeTest, RegExpBuiltinsAssembler) {
Node* const context = Parameter(4);
// Ensure {maybe_receiver} is a JSReceiver.
- Node* const map = ThrowIfNotJSReceiver(
- context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver,
- "RegExp.prototype.test");
+ ThrowIfNotJSReceiver(context, maybe_receiver,
+ MessageTemplate::kIncompatibleMethodReceiver,
+ "RegExp.prototype.test");
Node* const receiver = maybe_receiver;
// Convert {maybe_string} to a String.
Node* const string = ToString(context, maybe_string);
Label fast_path(this), slow_path(this);
- BranchIfFastRegExp(context, map, &fast_path, &slow_path);
+ BranchIfFastRegExp(context, receiver, LoadMap(receiver), &fast_path,
+ &slow_path);
Bind(&fast_path);
{
@@ -1322,13 +1352,36 @@ TF_BUILTIN(RegExpPrototypeTest, RegExpBuiltinsAssembler) {
Node* RegExpBuiltinsAssembler::AdvanceStringIndex(Node* const string,
Node* const index,
- Node* const is_unicode) {
+ Node* const is_unicode,
+ bool is_fastpath) {
+ CSA_ASSERT(this, IsHeapNumberMap(LoadReceiverMap(index)));
+ if (is_fastpath) CSA_ASSERT(this, TaggedIsPositiveSmi(index));
+
// Default to last_index + 1.
- Node* const index_plus_one = SmiAdd(index, SmiConstant(1));
+ Node* const index_plus_one = NumberInc(index);
Variable var_result(this, MachineRepresentation::kTagged, index_plus_one);
+ // Advancing the index has some subtle issues involving the distinction
+ // between Smis and HeapNumbers. There's three cases:
+ // * {index} is a Smi, {index_plus_one} is a Smi. The standard case.
+ // * {index} is a Smi, {index_plus_one} overflows into a HeapNumber.
+ // In this case we can return the result early, because
+ // {index_plus_one} > {string}.length.
+ // * {index} is a HeapNumber, {index_plus_one} is a HeapNumber. This can only
+ // occur when {index} is outside the Smi range since we normalize
+ // explicitly. Again we can return early.
+ if (is_fastpath) {
+ // Must be in Smi range on the fast path. We control the value of {index}
+ // on all call-sites and can never exceed the length of the string.
+ STATIC_ASSERT(String::kMaxLength + 2 < Smi::kMaxValue);
+ CSA_ASSERT(this, TaggedIsPositiveSmi(index_plus_one));
+ }
+
Label if_isunicode(this), out(this);
- Branch(is_unicode, &if_isunicode, &out);
+ GotoIfNot(is_unicode, &out);
+
+ // Keep this unconditional (even on the fast path) just to be safe.
+ Branch(TaggedIsPositiveSmi(index_plus_one), &if_isunicode, &out);
Bind(&if_isunicode);
{
@@ -1346,7 +1399,7 @@ Node* RegExpBuiltinsAssembler::AdvanceStringIndex(Node* const string,
&out);
// At a surrogate pair, return index + 2.
- Node* const index_plus_two = SmiAdd(index, SmiConstant(2));
+ Node* const index_plus_two = NumberInc(index_plus_one);
var_result.Bind(index_plus_two);
Goto(&out);
@@ -1630,12 +1683,23 @@ void RegExpBuiltinsAssembler::RegExpPrototypeMatchBody(Node* const context,
GotoIfNot(SmiEqual(match_length, smi_zero), &loop);
Node* last_index = LoadLastIndex(context, regexp, is_fastpath);
-
- Callable tolength_callable = CodeFactory::ToLength(isolate);
- last_index = CallStub(tolength_callable, context, last_index);
+ if (is_fastpath) {
+ CSA_ASSERT(this, TaggedIsPositiveSmi(last_index));
+ } else {
+ Callable tolength_callable = CodeFactory::ToLength(isolate);
+ last_index = CallStub(tolength_callable, context, last_index);
+ }
Node* const new_last_index =
- AdvanceStringIndex(string, last_index, is_unicode);
+ AdvanceStringIndex(string, last_index, is_unicode, is_fastpath);
+
+ if (is_fastpath) {
+ // On the fast path, we can be certain that lastIndex can never be
+ // incremented to overflow the Smi range since the maximal string
+ // length is less than the maximal Smi value.
+ STATIC_ASSERT(String::kMaxLength < Smi::kMaxValue);
+ CSA_ASSERT(this, TaggedIsPositiveSmi(new_last_index));
+ }
StoreLastIndex(context, regexp, new_last_index, is_fastpath);
@@ -1661,16 +1725,17 @@ TF_BUILTIN(RegExpPrototypeMatch, RegExpBuiltinsAssembler) {
Node* const context = Parameter(4);
// Ensure {maybe_receiver} is a JSReceiver.
- Node* const map = ThrowIfNotJSReceiver(
- context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver,
- "RegExp.prototype.@@match");
+ ThrowIfNotJSReceiver(context, maybe_receiver,
+ MessageTemplate::kIncompatibleMethodReceiver,
+ "RegExp.prototype.@@match");
Node* const receiver = maybe_receiver;
// Convert {maybe_string} to a String.
Node* const string = ToString(context, maybe_string);
Label fast_path(this), slow_path(this);
- BranchIfFastRegExp(context, map, &fast_path, &slow_path);
+ BranchIfFastRegExp(context, receiver, LoadMap(receiver), &fast_path,
+ &slow_path);
Bind(&fast_path);
RegExpPrototypeMatchBody(context, receiver, string, true);
@@ -1786,16 +1851,17 @@ TF_BUILTIN(RegExpPrototypeSearch, RegExpBuiltinsAssembler) {
Node* const context = Parameter(4);
// Ensure {maybe_receiver} is a JSReceiver.
- Node* const map = ThrowIfNotJSReceiver(
- context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver,
- "RegExp.prototype.@@search");
+ ThrowIfNotJSReceiver(context, maybe_receiver,
+ MessageTemplate::kIncompatibleMethodReceiver,
+ "RegExp.prototype.@@search");
Node* const receiver = maybe_receiver;
// Convert {maybe_string} to a String.
Node* const string = ToString(context, maybe_string);
Label fast_path(this), slow_path(this);
- BranchIfFastRegExp(context, map, &fast_path, &slow_path);
+ BranchIfFastRegExp(context, receiver, LoadMap(receiver), &fast_path,
+ &slow_path);
Bind(&fast_path);
RegExpPrototypeSearchBodyFast(context, receiver, string);
@@ -1939,7 +2005,7 @@ void RegExpBuiltinsAssembler::RegExpPrototypeSplitBody(Node* const context,
Node* const is_unicode = FastFlagGetter(regexp, JSRegExp::kUnicode);
Node* const new_next_search_from =
- AdvanceStringIndex(string, next_search_from, is_unicode);
+ AdvanceStringIndex(string, next_search_from, is_unicode, true);
var_next_search_from.Bind(new_next_search_from);
Goto(&loop);
@@ -2059,7 +2125,7 @@ TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) {
Node* const maybe_limit = Parameter(Descriptor::kLimit);
Node* const context = Parameter(Descriptor::kContext);
- CSA_ASSERT(this, IsFastRegExpMap(context, LoadMap(regexp)));
+ CSA_ASSERT(this, IsFastRegExpMap(context, regexp, LoadMap(regexp)));
CSA_ASSERT(this, IsString(string));
// TODO(jgruber): Even if map checks send us to the fast path, we still need
@@ -2067,13 +2133,23 @@ TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) {
// been changed.
// Convert {maybe_limit} to a uint32, capping at the maximal smi value.
- Variable var_limit(this, MachineRepresentation::kTagged);
- Label if_limitissmimax(this), limit_done(this);
+ Variable var_limit(this, MachineRepresentation::kTagged, maybe_limit);
+ Label if_limitissmimax(this), limit_done(this), runtime(this);
GotoIf(IsUndefined(maybe_limit), &if_limitissmimax);
+ GotoIf(TaggedIsPositiveSmi(maybe_limit), &limit_done);
+ Node* const limit = ToUint32(context, maybe_limit);
{
- Node* const limit = ToUint32(context, maybe_limit);
+ // ToUint32(limit) could potentially change the shape of the RegExp
+ // object. Recheck that we are still on the fast path and bail to runtime
+ // otherwise.
+ {
+ Label next(this);
+ BranchIfFastRegExp(context, regexp, LoadMap(regexp), &next, &runtime);
+ Bind(&next);
+ }
+
GotoIfNot(TaggedIsSmi(limit), &if_limitissmimax);
var_limit.Bind(limit);
@@ -2093,6 +2169,14 @@ TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) {
Node* const limit = var_limit.value();
RegExpPrototypeSplitBody(context, regexp, string, limit);
}
+
+ Bind(&runtime);
+ {
+ // The runtime call passes in limit to ensure the second ToUint32(limit)
+ // call is not observable.
+ CSA_ASSERT(this, IsHeapNumberMap(LoadReceiverMap(limit)));
+ Return(CallRuntime(Runtime::kRegExpSplit, context, regexp, string, limit));
+ }
}
// ES#sec-regexp.prototype-@@split
@@ -2104,16 +2188,16 @@ TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) {
Node* const context = Parameter(5);
// Ensure {maybe_receiver} is a JSReceiver.
- Node* const map = ThrowIfNotJSReceiver(
- context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver,
- "RegExp.prototype.@@split");
+ ThrowIfNotJSReceiver(context, maybe_receiver,
+ MessageTemplate::kIncompatibleMethodReceiver,
+ "RegExp.prototype.@@split");
Node* const receiver = maybe_receiver;
// Convert {maybe_string} to a String.
Node* const string = ToString(context, maybe_string);
Label stub(this), runtime(this, Label::kDeferred);
- BranchIfFastRegExp(context, map, &stub, &runtime);
+ BranchIfFastRegExp(context, receiver, LoadMap(receiver), &stub, &runtime);
Bind(&stub);
Callable split_callable = CodeFactory::RegExpSplit(isolate());
@@ -2439,7 +2523,7 @@ TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) {
Node* const replace_value = Parameter(Descriptor::kReplaceValue);
Node* const context = Parameter(Descriptor::kContext);
- CSA_ASSERT(this, IsFastRegExpMap(context, LoadMap(regexp)));
+ CSA_ASSERT(this, IsFastRegExpMap(context, regexp, LoadMap(regexp)));
CSA_ASSERT(this, IsString(string));
Label checkreplacestring(this), if_iscallable(this),
@@ -2457,6 +2541,15 @@ TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) {
Node* const replace_string =
CallStub(tostring_callable, context, replace_value);
+ // ToString(replaceValue) could potentially change the shape of the RegExp
+ // object. Recheck that we are still on the fast path and bail to runtime
+ // otherwise.
+ {
+ Label next(this);
+ BranchIfFastRegExp(context, regexp, LoadMap(regexp), &next, &runtime);
+ Bind(&next);
+ }
+
Callable indexof_callable = CodeFactory::StringIndexOf(isolate());
Node* const dollar_string = HeapConstant(
isolate()->factory()->LookupSingleCharacterStringFromCode('$'));
@@ -2519,9 +2612,9 @@ TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) {
// }
// Ensure {maybe_receiver} is a JSReceiver.
- Node* const map = ThrowIfNotJSReceiver(
- context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver,
- "RegExp.prototype.@@replace");
+ ThrowIfNotJSReceiver(context, maybe_receiver,
+ MessageTemplate::kIncompatibleMethodReceiver,
+ "RegExp.prototype.@@replace");
Node* const receiver = maybe_receiver;
// Convert {maybe_string} to a String.
@@ -2530,7 +2623,7 @@ TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) {
// Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance?
Label stub(this), runtime(this, Label::kDeferred);
- BranchIfFastRegExp(context, map, &stub, &runtime);
+ BranchIfFastRegExp(context, receiver, LoadMap(receiver), &stub, &runtime);
Bind(&stub);
Callable replace_callable = CodeFactory::RegExpReplace(isolate());