Commit 365bb5bb authored by Peter Marshall's avatar Peter Marshall

[regexp] Add a CSA fast path for replace with global regexp.

For simple replacement strings without $ characters, we can do the
replacement in CSA for a global regexp. This is a common case because
this is currently the most widely used way to 'replaceAll' in a string.

This CL speeds up the test case in the linked bug by 13%.

Bug: v8:7053
Change-Id: I0d1d7c25fed07dfd7927191a3ef3138302e10c8f
Reviewed-on: https://chromium-review.googlesource.com/774440
Commit-Queue: Peter Marshall <petermarshall@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#49472}
parent a9a16713
...@@ -2841,98 +2841,96 @@ Node* RegExpBuiltinsAssembler::ReplaceSimpleStringFastPath( ...@@ -2841,98 +2841,96 @@ Node* RegExpBuiltinsAssembler::ReplaceSimpleStringFastPath(
// ToString({replace_value}) does not contain '$', i.e. we're doing a simple // ToString({replace_value}) does not contain '$', i.e. we're doing a simple
// string replacement. // string replacement.
Node* const int_zero = IntPtrConstant(0);
Node* const smi_zero = SmiConstant(0); Node* const smi_zero = SmiConstant(0);
const bool kIsFastPath = true;
CSA_ASSERT(this, IsFastRegExp(context, regexp)); CSA_ASSERT(this, IsFastRegExp(context, regexp));
CSA_ASSERT(this, IsString(replace_string)); CSA_ASSERT(this, IsString(replace_string));
CSA_ASSERT(this, IsString(string)); CSA_ASSERT(this, IsString(string));
Label out(this); VARIABLE(var_result, MachineRepresentation::kTagged, EmptyStringConstant());
VARIABLE(var_result, MachineRepresentation::kTagged); VARIABLE(var_match_indices, MachineRepresentation::kTagged);
VARIABLE(var_last_match_end, MachineRepresentation::kTagged, smi_zero);
// Load the last match info. VARIABLE(var_is_unicode, MachineRepresentation::kWord32, Int32Constant(0));
Node* const native_context = LoadNativeContext(context); Variable* vars[] = {&var_result, &var_last_match_end};
Node* const last_match_info = Label out(this), loop(this, 2, vars), loop_end(this),
LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); if_nofurthermatches(this);
// Is {regexp} global? // Is {regexp} global?
Label if_isglobal(this), if_isnonglobal(this); Node* const is_global = FastFlagGetter(regexp, JSRegExp::kGlobal);
Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset); GotoIfNot(is_global, &loop);
Node* const is_global =
WordAnd(SmiUntag(flags), IntPtrConstant(JSRegExp::kGlobal));
Branch(WordEqual(is_global, int_zero), &if_isnonglobal, &if_isglobal);
BIND(&if_isglobal); var_is_unicode.Bind(FastFlagGetter(regexp, JSRegExp::kUnicode));
{ FastStoreLastIndex(regexp, smi_zero);
// Hand off global regexps to runtime. Goto(&loop);
FastStoreLastIndex(regexp, smi_zero);
Node* const result =
CallRuntime(Runtime::kStringReplaceGlobalRegExpWithString, context,
string, regexp, replace_string, last_match_info);
var_result.Bind(result);
Goto(&out);
}
BIND(&if_isnonglobal); BIND(&loop);
{ {
// Run exec, then manually construct the resulting string. var_match_indices.Bind(RegExpPrototypeExecBodyWithoutResult(
Label if_didnotmatch(this); context, regexp, string, &if_nofurthermatches, kIsFastPath));
Node* const match_indices = RegExpPrototypeExecBodyWithoutResult(
context, regexp, string, &if_didnotmatch, true);
// Successful match. // Successful match.
{ {
Node* const subject_start = smi_zero;
Node* const match_start = LoadFixedArrayElement( Node* const match_start = LoadFixedArrayElement(
match_indices, RegExpMatchInfo::kFirstCaptureIndex); var_match_indices.value(), RegExpMatchInfo::kFirstCaptureIndex);
Node* const match_end = LoadFixedArrayElement( Node* const match_end = LoadFixedArrayElement(
match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); var_match_indices.value(), RegExpMatchInfo::kFirstCaptureIndex + 1);
TNode<Smi> const subject_end = LoadStringLengthAsSmi(string);
Label if_replaceisempty(this), if_replaceisnotempty(this); Label if_replaceisempty(this), if_replaceisnotempty(this);
TNode<Smi> const replace_length = LoadStringLengthAsSmi(replace_string); TNode<Smi> const replace_length = LoadStringLengthAsSmi(replace_string);
Branch(SmiEqual(replace_length, SmiConstant(0)), &if_replaceisempty, Branch(SmiEqual(replace_length, smi_zero), &if_replaceisempty,
&if_replaceisnotempty); &if_replaceisnotempty);
BIND(&if_replaceisempty); BIND(&if_replaceisempty);
{ {
// TODO(jgruber): We could skip many of the checks that using SubString // TODO(jgruber): We could skip many of the checks that using SubString
// here entails. // here entails.
Node* const first_part = Node* const first_part =
SubString(context, string, subject_start, match_start); SubString(context, string, var_last_match_end.value(), match_start);
Node* const second_part =
SubString(context, string, match_end, subject_end);
Node* const result = StringAdd(context, first_part, second_part); Node* const result = StringAdd(context, var_result.value(), first_part);
var_result.Bind(result); var_result.Bind(result);
Goto(&out); Goto(&loop_end);
} }
BIND(&if_replaceisnotempty); BIND(&if_replaceisnotempty);
{ {
Node* const first_part = Node* const first_part =
SubString(context, string, subject_start, match_start); SubString(context, string, var_last_match_end.value(), match_start);
Node* const second_part = replace_string;
Node* const third_part =
SubString(context, string, match_end, subject_end);
Node* result = StringAdd(context, first_part, second_part);
result = StringAdd(context, result, third_part);
Node* result = StringAdd(context, var_result.value(), first_part);
result = StringAdd(context, result, replace_string);
var_result.Bind(result); var_result.Bind(result);
Goto(&out); Goto(&loop_end);
} }
}
BIND(&if_didnotmatch); BIND(&loop_end);
{ {
var_result.Bind(string); var_last_match_end.Bind(match_end);
Goto(&out); // Non-global case ends here after the first replacement.
GotoIfNot(is_global, &if_nofurthermatches);
GotoIf(SmiNotEqual(match_end, match_start), &loop);
// If match is the empty string, we have to increment lastIndex.
Node* const this_index = FastLoadLastIndex(regexp);
Node* const next_index = AdvanceStringIndex(
string, this_index, var_is_unicode.value(), kIsFastPath);
FastStoreLastIndex(regexp, next_index);
Goto(&loop);
}
} }
} }
BIND(&if_nofurthermatches);
{
TNode<Smi> const string_length = LoadStringLengthAsSmi(string);
Node* const last_part =
SubString(context, string, var_last_match_end.value(), string_length);
Node* const result = StringAdd(context, var_result.value(), last_part);
var_result.Bind(result);
Goto(&out);
}
BIND(&out); BIND(&out);
return var_result.value(); return var_result.value();
} }
...@@ -3033,7 +3031,7 @@ TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) { ...@@ -3033,7 +3031,7 @@ TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) {
// if (replace.contains("$")) { // if (replace.contains("$")) {
// CallRuntime(RegExpReplace) // CallRuntime(RegExpReplace)
// } else { // } else {
// ReplaceSimpleStringFastPath() // Bails to runtime for global regexps. // ReplaceSimpleStringFastPath()
// } // }
// } // }
......
...@@ -808,19 +808,6 @@ Object* StringReplaceGlobalRegExpWithStringHelper( ...@@ -808,19 +808,6 @@ Object* StringReplaceGlobalRegExpWithStringHelper(
} // namespace } // namespace
RUNTIME_FUNCTION(Runtime_StringReplaceGlobalRegExpWithString) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
CONVERT_ARG_HANDLE_CHECKED(String, subject, 0);
CONVERT_ARG_HANDLE_CHECKED(String, replacement, 2);
CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 1);
CONVERT_ARG_HANDLE_CHECKED(RegExpMatchInfo, last_match_info, 3);
return StringReplaceGlobalRegExpWithStringHelper(
isolate, regexp, subject, replacement, last_match_info);
}
RUNTIME_FUNCTION(Runtime_StringSplit) { RUNTIME_FUNCTION(Runtime_StringSplit) {
HandleScope handle_scope(isolate); HandleScope handle_scope(isolate);
DCHECK_EQ(3, args.length()); DCHECK_EQ(3, args.length());
......
...@@ -497,7 +497,6 @@ namespace internal { ...@@ -497,7 +497,6 @@ namespace internal {
F(RegExpInternalReplace, 3, 1) \ F(RegExpInternalReplace, 3, 1) \
F(RegExpReplace, 3, 1) \ F(RegExpReplace, 3, 1) \
F(RegExpSplit, 3, 1) \ F(RegExpSplit, 3, 1) \
F(StringReplaceGlobalRegExpWithString, 4, 1) \
F(StringReplaceNonGlobalRegExpWithFunction, 3, 1) \ F(StringReplaceNonGlobalRegExpWithFunction, 3, 1) \
F(StringSplit, 3, 1) F(StringSplit, 3, 1)
......
...@@ -1475,7 +1475,7 @@ TEST(StringReplaceAtomTwoByteResult) { ...@@ -1475,7 +1475,7 @@ TEST(StringReplaceAtomTwoByteResult) {
"subject.replace(/~/g, replace); "); "subject.replace(/~/g, replace); ");
CHECK(result->IsString()); CHECK(result->IsString());
Handle<String> string = v8::Utils::OpenHandle(v8::String::Cast(*result)); Handle<String> string = v8::Utils::OpenHandle(v8::String::Cast(*result));
CHECK(string->IsSeqTwoByteString()); CHECK(string->IsTwoByteRepresentation());
v8::Local<v8::String> expected = v8_str("one_byte\x80only\x80string\x80"); v8::Local<v8::String> expected = v8_str("one_byte\x80only\x80string\x80");
CHECK(expected->Equals(context.local(), result).FromJust()); CHECK(expected->Equals(context.local(), result).FromJust());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment