Commit 7a75f34b authored by jgruber's avatar jgruber Committed by Commit bot

[regexp] RegExp.prototype.replace fast-paths

This CL adds two new fast-paths for RegExp.prototype.replace in the case
that the regexp itself is an unmodified JSRegExp instance and the
replace argument is callable. Such cases call directly into runtime.

This could be improved even further by turning the relevant runtime
functions into inline TurboFan.

BUG=v8:5339

Review-Url: https://codereview.chromium.org/2415663007
Cr-Commit-Position: refs/heads/master@{#40381}
parent 8146402c
...@@ -750,6 +750,20 @@ BUILTIN(RegExpPrototypeSpeciesGetter) { ...@@ -750,6 +750,20 @@ BUILTIN(RegExpPrototypeSpeciesGetter) {
namespace { namespace {
// Fast-path implementation for flag checks on an unmodified JSRegExp instance.
compiler::Node* FastFlagGetter(CodeStubAssembler* a,
compiler::Node* const regexp,
JSRegExp::Flag flag) {
typedef compiler::Node Node;
Node* const smi_zero = a->SmiConstant(Smi::kZero);
Node* const flags = a->LoadObjectField(regexp, JSRegExp::kFlagsOffset);
Node* const mask = a->SmiConstant(Smi::FromInt(flag));
Node* const is_flag_set = a->WordNotEqual(a->WordAnd(flags, mask), smi_zero);
return is_flag_set;
}
void Generate_FlagGetter(CodeStubAssembler* a, JSRegExp::Flag flag, void Generate_FlagGetter(CodeStubAssembler* a, JSRegExp::Flag flag,
v8::Isolate::UseCounterFeature counter, v8::Isolate::UseCounterFeature counter,
const char* method_name) { const char* method_name) {
...@@ -760,7 +774,6 @@ void Generate_FlagGetter(CodeStubAssembler* a, JSRegExp::Flag flag, ...@@ -760,7 +774,6 @@ void Generate_FlagGetter(CodeStubAssembler* a, JSRegExp::Flag flag,
Node* const context = a->Parameter(3); Node* const context = a->Parameter(3);
Isolate* isolate = a->isolate(); Isolate* isolate = a->isolate();
Node* const int_zero = a->IntPtrConstant(0);
// Check whether we have an unmodified regexp instance. // Check whether we have an unmodified regexp instance.
Label if_isunmodifiedjsregexp(a), Label if_isunmodifiedjsregexp(a),
...@@ -777,13 +790,8 @@ void Generate_FlagGetter(CodeStubAssembler* a, JSRegExp::Flag flag, ...@@ -777,13 +790,8 @@ void Generate_FlagGetter(CodeStubAssembler* a, JSRegExp::Flag flag,
a->Bind(&if_isunmodifiedjsregexp); a->Bind(&if_isunmodifiedjsregexp);
{ {
// Refer to JSRegExp's flag property on the fast-path. // Refer to JSRegExp's flag property on the fast-path.
Node* const flags_smi = Node* const is_flag_set = FastFlagGetter(a, receiver, flag);
a->LoadObjectField(receiver, JSRegExp::kFlagsOffset); a->Return(a->Select(is_flag_set, a->TrueConstant(), a->FalseConstant()));
Node* const flags_intptr = a->SmiUntag(flags_smi);
Node* const mask = a->IntPtrConstant(flag);
Node* const is_global =
a->WordNotEqual(a->WordAnd(flags_intptr, mask), int_zero);
a->Return(a->Select(is_global, a->TrueConstant(), a->FalseConstant()));
} }
a->Bind(&if_isnotunmodifiedjsregexp); a->Bind(&if_isnotunmodifiedjsregexp);
...@@ -1572,6 +1580,7 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) { ...@@ -1572,6 +1580,7 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
Node* const context = a->Parameter(5); Node* const context = a->Parameter(5);
Node* const int_zero = a->IntPtrConstant(0); Node* const int_zero = a->IntPtrConstant(0);
Node* const smi_zero = a->SmiConstant(Smi::kZero);
// Ensure {receiver} is a JSReceiver. // Ensure {receiver} is a JSReceiver.
Node* const map = Node* const map =
...@@ -1592,7 +1601,7 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) { ...@@ -1592,7 +1601,7 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
Node* const regexp = receiver; Node* const regexp = receiver;
// 2. Is {replace_value} callable? // 2. Is {replace_value} callable?
Label checkreplacestring(a); Label checkreplacestring(a), if_iscallable(a, Label::kDeferred);
a->GotoIf(a->TaggedIsSmi(replace_value), &checkreplacestring); a->GotoIf(a->TaggedIsSmi(replace_value), &checkreplacestring);
Node* const replace_value_map = a->LoadMap(replace_value); Node* const replace_value_map = a->LoadMap(replace_value);
...@@ -1600,7 +1609,7 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) { ...@@ -1600,7 +1609,7 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
a->Word32Equal(a->Word32And(a->LoadMapBitField(replace_value_map), a->Word32Equal(a->Word32And(a->LoadMapBitField(replace_value_map),
a->Int32Constant(1 << Map::kIsCallable)), a->Int32Constant(1 << Map::kIsCallable)),
a->Int32Constant(0)), a->Int32Constant(0)),
&checkreplacestring, &runtime); &checkreplacestring, &if_iscallable);
// 3. Does ToString({replace_value}) contain '$'? // 3. Does ToString({replace_value}) contain '$'?
a->Bind(&checkreplacestring); a->Bind(&checkreplacestring);
...@@ -1618,6 +1627,34 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) { ...@@ -1618,6 +1627,34 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
a->Return(ReplaceFastPath(a, context, regexp, string, replace_string)); a->Return(ReplaceFastPath(a, context, regexp, string, replace_string));
} }
// {regexp} is unmodified and {replace_value} is callable.
a->Bind(&if_iscallable);
{
Node* const replace_callable = replace_value;
// Check if the {regexp} is global.
Label if_isglobal(a), if_isnotglobal(a);
Node* const is_global = FastFlagGetter(a, regexp, JSRegExp::kGlobal);
a->Branch(is_global, &if_isglobal, &if_isnotglobal);
a->Bind(&if_isglobal);
{
FastStoreLastIndex(a, context, regexp, smi_zero);
Node* const result =
a->CallRuntime(Runtime::kStringReplaceGlobalRegExpWithFunction,
context, string, regexp, replace_callable);
a->Return(result);
}
a->Bind(&if_isnotglobal);
{
Node* const result =
a->CallRuntime(Runtime::kStringReplaceNonGlobalRegExpWithFunction,
context, string, regexp, replace_callable);
a->Return(result);
}
}
a->Bind(&runtime); a->Bind(&runtime);
{ {
Node* const result = a->CallRuntime(Runtime::kRegExpReplace, context, Node* const result = a->CallRuntime(Runtime::kRegExpReplace, context,
......
...@@ -1461,6 +1461,30 @@ MaybeHandle<String> RegExpReplace(Isolate* isolate, Handle<JSRegExp> regexp, ...@@ -1461,6 +1461,30 @@ MaybeHandle<String> RegExpReplace(Isolate* isolate, Handle<JSRegExp> regexp,
} // namespace } // namespace
RUNTIME_FUNCTION(Runtime_StringReplaceGlobalRegExpWithFunction) {
HandleScope scope(isolate);
DCHECK(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(String, subject, 0);
CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 1);
CONVERT_ARG_HANDLE_CHECKED(JSObject, replace, 2);
RETURN_RESULT_OR_FAILURE(isolate, StringReplaceGlobalRegExpWithFunction(
isolate, subject, regexp, replace));
}
RUNTIME_FUNCTION(Runtime_StringReplaceNonGlobalRegExpWithFunction) {
HandleScope scope(isolate);
DCHECK(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(String, subject, 0);
CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 1);
CONVERT_ARG_HANDLE_CHECKED(JSObject, replace, 2);
RETURN_RESULT_OR_FAILURE(isolate, StringReplaceNonGlobalRegExpWithFunction(
isolate, subject, regexp, replace));
}
// Slow path for: // Slow path for:
// ES#sec-regexp.prototype-@@replace // ES#sec-regexp.prototype-@@replace
// RegExp.prototype [ @@replace ] ( string, replaceValue ) // RegExp.prototype [ @@replace ] ( string, replaceValue )
...@@ -1503,6 +1527,11 @@ RUNTIME_FUNCTION(Runtime_RegExpReplace) { ...@@ -1503,6 +1527,11 @@ RUNTIME_FUNCTION(Runtime_RegExpReplace) {
RegExpUtils::SetLastIndex(isolate, recv, 0)); RegExpUtils::SetLastIndex(isolate, recv, 0));
} }
// TODO(jgruber): Here and at all other fast path checks, rely on map checks
// instead.
// TODO(jgruber): We could speed up the fast path by checking flags
// afterwards, but that would violate the spec (which states that exec is
// accessed after global and unicode).
// TODO(adamk): this fast path is wrong as we doesn't ensure that 'exec' // TODO(adamk): this fast path is wrong as we doesn't ensure that 'exec'
// is actually a data property on RegExp.prototype. // is actually a data property on RegExp.prototype.
Handle<Object> exec = factory->undefined_value(); Handle<Object> exec = factory->undefined_value();
......
...@@ -457,18 +457,20 @@ namespace internal { ...@@ -457,18 +457,20 @@ namespace internal {
F(JSProxyRevoke, 1, 1) F(JSProxyRevoke, 1, 1)
#define FOR_EACH_INTRINSIC_REGEXP(F) \ #define FOR_EACH_INTRINSIC_REGEXP(F) \
F(StringReplaceGlobalRegExpWithString, 4, 1) \ F(IsRegExp, 1, 1) \
F(StringSplit, 3, 1) \ F(RegExpConstructResult, 3, 1) \
F(RegExpCreate, 1, 1) \ F(RegExpCreate, 1, 1) \
F(RegExpExec, 4, 1) \ F(RegExpExec, 4, 1) \
F(RegExpExecReThrow, 4, 1) \
F(RegExpFlags, 1, 1) \ F(RegExpFlags, 1, 1) \
F(RegExpReplace, 3, 1) \
F(RegExpSource, 1, 1) \
F(RegExpConstructResult, 3, 1) \
F(RegExpInitializeAndCompile, 3, 1) \ F(RegExpInitializeAndCompile, 3, 1) \
F(RegExpInternalReplace, 3, 1) \ F(RegExpInternalReplace, 3, 1) \
F(RegExpExecReThrow, 4, 1) \ F(RegExpReplace, 3, 1) \
F(IsRegExp, 1, 1) F(RegExpSource, 1, 1) \
F(StringReplaceGlobalRegExpWithString, 4, 1) \
F(StringReplaceGlobalRegExpWithFunction, 3, 1) \
F(StringReplaceNonGlobalRegExpWithFunction, 3, 1) \
F(StringSplit, 3, 1)
#define FOR_EACH_INTRINSIC_SCOPES(F) \ #define FOR_EACH_INTRINSIC_SCOPES(F) \
F(ThrowConstAssignError, 0, 1) \ F(ThrowConstAssignError, 0, 1) \
......
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