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) {
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,
v8::Isolate::UseCounterFeature counter,
const char* method_name) {
......@@ -760,7 +774,6 @@ void Generate_FlagGetter(CodeStubAssembler* a, JSRegExp::Flag flag,
Node* const context = a->Parameter(3);
Isolate* isolate = a->isolate();
Node* const int_zero = a->IntPtrConstant(0);
// Check whether we have an unmodified regexp instance.
Label if_isunmodifiedjsregexp(a),
......@@ -777,13 +790,8 @@ void Generate_FlagGetter(CodeStubAssembler* a, JSRegExp::Flag flag,
a->Bind(&if_isunmodifiedjsregexp);
{
// Refer to JSRegExp's flag property on the fast-path.
Node* const flags_smi =
a->LoadObjectField(receiver, JSRegExp::kFlagsOffset);
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()));
Node* const is_flag_set = FastFlagGetter(a, receiver, flag);
a->Return(a->Select(is_flag_set, a->TrueConstant(), a->FalseConstant()));
}
a->Bind(&if_isnotunmodifiedjsregexp);
......@@ -1572,6 +1580,7 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
Node* const context = a->Parameter(5);
Node* const int_zero = a->IntPtrConstant(0);
Node* const smi_zero = a->SmiConstant(Smi::kZero);
// Ensure {receiver} is a JSReceiver.
Node* const map =
......@@ -1592,7 +1601,7 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
Node* const regexp = receiver;
// 2. Is {replace_value} callable?
Label checkreplacestring(a);
Label checkreplacestring(a), if_iscallable(a, Label::kDeferred);
a->GotoIf(a->TaggedIsSmi(replace_value), &checkreplacestring);
Node* const replace_value_map = a->LoadMap(replace_value);
......@@ -1600,7 +1609,7 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
a->Word32Equal(a->Word32And(a->LoadMapBitField(replace_value_map),
a->Int32Constant(1 << Map::kIsCallable)),
a->Int32Constant(0)),
&checkreplacestring, &runtime);
&checkreplacestring, &if_iscallable);
// 3. Does ToString({replace_value}) contain '$'?
a->Bind(&checkreplacestring);
......@@ -1618,6 +1627,34 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
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);
{
Node* const result = a->CallRuntime(Runtime::kRegExpReplace, context,
......
......@@ -1461,6 +1461,30 @@ MaybeHandle<String> RegExpReplace(Isolate* isolate, Handle<JSRegExp> regexp,
} // 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:
// ES#sec-regexp.prototype-@@replace
// RegExp.prototype [ @@replace ] ( string, replaceValue )
......@@ -1503,6 +1527,11 @@ RUNTIME_FUNCTION(Runtime_RegExpReplace) {
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'
// is actually a data property on RegExp.prototype.
Handle<Object> exec = factory->undefined_value();
......
......@@ -456,19 +456,21 @@ namespace internal {
F(JSProxyGetHandler, 1, 1) \
F(JSProxyRevoke, 1, 1)
#define FOR_EACH_INTRINSIC_REGEXP(F) \
F(StringReplaceGlobalRegExpWithString, 4, 1) \
F(StringSplit, 3, 1) \
F(RegExpCreate, 1, 1) \
F(RegExpExec, 4, 1) \
F(RegExpFlags, 1, 1) \
F(RegExpReplace, 3, 1) \
F(RegExpSource, 1, 1) \
F(RegExpConstructResult, 3, 1) \
F(RegExpInitializeAndCompile, 3, 1) \
F(RegExpInternalReplace, 3, 1) \
F(RegExpExecReThrow, 4, 1) \
F(IsRegExp, 1, 1)
#define FOR_EACH_INTRINSIC_REGEXP(F) \
F(IsRegExp, 1, 1) \
F(RegExpConstructResult, 3, 1) \
F(RegExpCreate, 1, 1) \
F(RegExpExec, 4, 1) \
F(RegExpExecReThrow, 4, 1) \
F(RegExpFlags, 1, 1) \
F(RegExpInitializeAndCompile, 3, 1) \
F(RegExpInternalReplace, 3, 1) \
F(RegExpReplace, 3, 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) \
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