Commit 082bf306 authored by jgruber's avatar jgruber Committed by Commit bot

[stubs] Add RegExpReplace and RegExpSplit stubs

This adds helper stubs for RegExp split and replace operations, called directly
by both RegExpPrototype{Replace,Split} and StringPrototype{Replace,Split}.

BUG=

Review-Url: https://codereview.chromium.org/2668703002
Cr-Commit-Position: refs/heads/master@{#42919}
parent 87e53cb8
...@@ -422,9 +422,10 @@ Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* map) { ...@@ -422,9 +422,10 @@ Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* map) {
// We use a fairly coarse granularity for this and simply check whether both // 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 // the regexp itself is unmodified (i.e. its map has not changed) and its
// prototype is unmodified. // prototype is unmodified.
void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* context, Node* map, void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context,
Label* if_isunmodified, Node* const map,
Label* if_ismodified) { Label* const if_isunmodified,
Label* const if_ismodified) {
Node* const native_context = LoadNativeContext(context); Node* const native_context = LoadNativeContext(context);
Node* const regexp_fun = Node* const regexp_fun =
LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
...@@ -446,6 +447,25 @@ void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* context, Node* map, ...@@ -446,6 +447,25 @@ void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* context, Node* map,
Branch(proto_has_initialmap, if_isunmodified, if_ismodified); Branch(proto_has_initialmap, if_isunmodified, if_ismodified);
} }
Node* RegExpBuiltinsAssembler::IsFastRegExpMap(Node* const context,
Node* const map) {
Label yup(this), nope(this), out(this);
Variable var_result(this, MachineRepresentation::kWord32);
BranchIfFastRegExp(context, map, &yup, &nope);
Bind(&yup);
var_result.Bind(Int32Constant(1));
Goto(&out);
Bind(&nope);
var_result.Bind(Int32Constant(0));
Goto(&out);
Bind(&out);
return var_result.value();
}
void RegExpBuiltinsAssembler::BranchIfFastRegExpResult(Node* context, Node* map, void RegExpBuiltinsAssembler::BranchIfFastRegExpResult(Node* context, Node* map,
Label* if_isunmodified, Label* if_isunmodified,
Label* if_ismodified) { Label* if_ismodified) {
...@@ -2029,6 +2049,51 @@ void RegExpBuiltinsAssembler::RegExpPrototypeSplitBody(Node* const context, ...@@ -2029,6 +2049,51 @@ void RegExpBuiltinsAssembler::RegExpPrototypeSplitBody(Node* const context,
} }
} }
// Helper that skips a few initial checks.
TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) {
typedef RegExpSplitDescriptor Descriptor;
Node* const regexp = Parameter(Descriptor::kReceiver);
Node* const string = Parameter(Descriptor::kString);
Node* const maybe_limit = Parameter(Descriptor::kLimit);
Node* const context = Parameter(Descriptor::kContext);
CSA_ASSERT(this, IsFastRegExpMap(context, LoadMap(regexp)));
CSA_ASSERT(this, IsString(string));
// TODO(jgruber): Even if map checks send us to the fast path, we still need
// to verify the constructor property and jump to the slow path if it has
// 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);
GotoIf(IsUndefined(maybe_limit), &if_limitissmimax);
{
Node* const limit = ToUint32(context, maybe_limit);
GotoUnless(TaggedIsSmi(limit), &if_limitissmimax);
var_limit.Bind(limit);
Goto(&limit_done);
}
Bind(&if_limitissmimax);
{
// TODO(jgruber): In this case, we can probably avoid generation of limit
// checks in Generate_RegExpPrototypeSplitBody.
var_limit.Bind(SmiConstant(Smi::kMaxValue));
Goto(&limit_done);
}
Bind(&limit_done);
{
Node* const limit = var_limit.value();
RegExpPrototypeSplitBody(context, regexp, string, limit);
}
}
// ES#sec-regexp.prototype-@@split // ES#sec-regexp.prototype-@@split
// RegExp.prototype [ @@split ] ( string, limit ) // RegExp.prototype [ @@split ] ( string, limit )
TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) { TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) {
...@@ -2046,51 +2111,16 @@ TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) { ...@@ -2046,51 +2111,16 @@ TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) {
// Convert {maybe_string} to a String. // Convert {maybe_string} to a String.
Node* const string = ToString(context, maybe_string); Node* const string = ToString(context, maybe_string);
Label fast_path(this), slow_path(this); Label stub(this), runtime(this, Label::kDeferred);
BranchIfFastRegExp(context, map, &fast_path, &slow_path); BranchIfFastRegExp(context, map, &stub, &runtime);
Bind(&fast_path);
{
// TODO(jgruber): Even if map checks send us to the fast path, we still need
// to verify the constructor property and jump to the slow path if it has
// 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);
GotoIf(IsUndefined(maybe_limit), &if_limitissmimax);
{
Node* const limit = ToUint32(context, maybe_limit);
GotoUnless(TaggedIsSmi(limit), &if_limitissmimax);
var_limit.Bind(limit);
Goto(&limit_done);
}
Bind(&if_limitissmimax);
{
// TODO(jgruber): In this case, we can probably generation of limit checks
// in Generate_RegExpPrototypeSplitBody.
Node* const smi_max = SmiConstant(Smi::kMaxValue);
var_limit.Bind(smi_max);
Goto(&limit_done);
}
Bind(&limit_done); Bind(&stub);
{ Callable split_callable = CodeFactory::RegExpSplit(isolate());
Node* const limit = var_limit.value(); Return(CallStub(split_callable, context, receiver, string, maybe_limit));
RegExpPrototypeSplitBody(context, receiver, string, limit);
}
}
Bind(&slow_path); Bind(&runtime);
{ Return(CallRuntime(Runtime::kRegExpSplit, context, receiver, string,
Node* const result = CallRuntime(Runtime::kRegExpSplit, context, receiver, maybe_limit));
string, maybe_limit);
Return(result);
}
} }
Node* RegExpBuiltinsAssembler::ReplaceGlobalCallableFastPath( Node* RegExpBuiltinsAssembler::ReplaceGlobalCallableFastPath(
...@@ -2406,50 +2436,37 @@ Node* RegExpBuiltinsAssembler::ReplaceSimpleStringFastPath( ...@@ -2406,50 +2436,37 @@ Node* RegExpBuiltinsAssembler::ReplaceSimpleStringFastPath(
return var_result.value(); return var_result.value();
} }
// ES#sec-regexp.prototype-@@replace // Helper that skips a few initial checks.
// RegExp.prototype [ @@replace ] ( string, replaceValue ) TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) {
TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) { typedef RegExpReplaceDescriptor Descriptor;
Node* const maybe_receiver = Parameter(0);
Node* const maybe_string = Parameter(1);
Node* const replace_value = Parameter(2);
Node* const context = Parameter(5);
// Ensure {maybe_receiver} is a JSReceiver.
Node* const map = ThrowIfNotJSReceiver(
context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver,
"RegExp.prototype.@@replace");
Node* const receiver = maybe_receiver;
// Convert {maybe_string} to a String. Node* const regexp = Parameter(Descriptor::kReceiver);
Callable tostring_callable = CodeFactory::ToString(isolate()); Node* const string = Parameter(Descriptor::kString);
Node* const string = CallStub(tostring_callable, context, maybe_string); Node* const replace_value = Parameter(Descriptor::kReplaceValue);
Node* const context = Parameter(Descriptor::kContext);
// Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance? CSA_ASSERT(this, IsFastRegExpMap(context, LoadMap(regexp)));
Label checkreplacecallable(this), runtime(this, Label::kDeferred), CSA_ASSERT(this, IsString(string));
fastpath(this);
BranchIfFastRegExp(context, map, &checkreplacecallable, &runtime);
Bind(&checkreplacecallable); Label checkreplacestring(this), if_iscallable(this),
Node* const regexp = receiver; runtime(this, Label::kDeferred);
// 2. Is {replace_value} callable? // 2. Is {replace_value} callable?
Label checkreplacestring(this), if_iscallable(this);
GotoIf(TaggedIsSmi(replace_value), &checkreplacestring); GotoIf(TaggedIsSmi(replace_value), &checkreplacestring);
Branch(IsCallableMap(LoadMap(replace_value)), &if_iscallable,
Node* const replace_value_map = LoadMap(replace_value); &checkreplacestring);
Branch(IsCallableMap(replace_value_map), &if_iscallable, &checkreplacestring);
// 3. Does ToString({replace_value}) contain '$'? // 3. Does ToString({replace_value}) contain '$'?
Bind(&checkreplacestring); Bind(&checkreplacestring);
{ {
Callable tostring_callable = CodeFactory::ToString(isolate());
Node* const replace_string = Node* const replace_string =
CallStub(tostring_callable, context, replace_value); CallStub(tostring_callable, context, replace_value);
Node* const dollar_char = Int32Constant('$'); Node* const dollar_char = Int32Constant('$');
Node* const smi_minusone = SmiConstant(Smi::FromInt(-1));
GotoUnless(SmiEqual(StringIndexOfChar(context, replace_string, dollar_char, GotoUnless(SmiEqual(StringIndexOfChar(context, replace_string, dollar_char,
SmiConstant(0)), SmiConstant(0)),
smi_minusone), SmiConstant(-1)),
&runtime); &runtime);
Return( Return(
...@@ -2459,35 +2476,56 @@ TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) { ...@@ -2459,35 +2476,56 @@ TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) {
// {regexp} is unmodified and {replace_value} is callable. // {regexp} is unmodified and {replace_value} is callable.
Bind(&if_iscallable); Bind(&if_iscallable);
{ {
Node* const replace_callable = replace_value; Node* const replace_fn = replace_value;
// Check if the {regexp} is global. // Check if the {regexp} is global.
Label if_isglobal(this), if_isnotglobal(this); Label if_isglobal(this), if_isnotglobal(this);
Node* const is_global = FastFlagGetter(regexp, JSRegExp::kGlobal); Node* const is_global = FastFlagGetter(regexp, JSRegExp::kGlobal);
Branch(is_global, &if_isglobal, &if_isnotglobal); Branch(is_global, &if_isglobal, &if_isnotglobal);
Bind(&if_isglobal); Bind(&if_isglobal);
{ Return(ReplaceGlobalCallableFastPath(context, regexp, string, replace_fn));
Node* const result = ReplaceGlobalCallableFastPath(
context, regexp, string, replace_callable);
Return(result);
}
Bind(&if_isnotglobal); Bind(&if_isnotglobal);
{ Return(CallRuntime(Runtime::kStringReplaceNonGlobalRegExpWithFunction,
Node* const result = context, string, regexp, replace_fn));
CallRuntime(Runtime::kStringReplaceNonGlobalRegExpWithFunction,
context, string, regexp, replace_callable);
Return(result);
}
} }
Bind(&runtime); Bind(&runtime);
{ Return(CallRuntime(Runtime::kRegExpReplace, context, regexp, string,
Node* const result = CallRuntime(Runtime::kRegExpReplace, context, receiver, replace_value));
string, replace_value); }
Return(result);
} // ES#sec-regexp.prototype-@@replace
// RegExp.prototype [ @@replace ] ( string, replaceValue )
TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) {
Node* const maybe_receiver = Parameter(0);
Node* const maybe_string = Parameter(1);
Node* const replace_value = Parameter(2);
Node* const context = Parameter(5);
// Ensure {maybe_receiver} is a JSReceiver.
Node* const map = ThrowIfNotJSReceiver(
context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver,
"RegExp.prototype.@@replace");
Node* const receiver = maybe_receiver;
// Convert {maybe_string} to a String.
Callable tostring_callable = CodeFactory::ToString(isolate());
Node* const string = CallStub(tostring_callable, context, maybe_string);
// Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance?
Label stub(this), runtime(this, Label::kDeferred);
BranchIfFastRegExp(context, map, &stub, &runtime);
Bind(&stub);
Callable replace_callable = CodeFactory::RegExpReplace(isolate());
Return(CallStub(replace_callable, context, receiver, string, replace_value));
Bind(&runtime);
Return(CallRuntime(Runtime::kRegExpReplace, context, receiver, string,
replace_value));
} }
// Simple string matching functionality for internal use which does not modify // Simple string matching functionality for internal use which does not modify
......
...@@ -19,8 +19,9 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler { ...@@ -19,8 +19,9 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
explicit RegExpBuiltinsAssembler(CodeAssemblerState* state) explicit RegExpBuiltinsAssembler(CodeAssemblerState* state)
: CodeStubAssembler(state) {} : CodeStubAssembler(state) {}
void BranchIfFastRegExp(Node* context, Node* map, Label* if_isunmodified, void BranchIfFastRegExp(Node* const context, Node* const map,
Label* if_ismodified); Label* const if_isunmodified,
Label* const if_ismodified);
protected: protected:
Node* FastLoadLastIndex(Node* regexp); Node* FastLoadLastIndex(Node* regexp);
...@@ -48,6 +49,9 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler { ...@@ -48,6 +49,9 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
MessageTemplate::Template msg_template, MessageTemplate::Template msg_template,
char const* method_name); char const* method_name);
// Analogous to BranchIfFastRegExp, for use in asserts.
Node* IsFastRegExpMap(Node* const context, Node* const map);
Node* IsInitialRegExpMap(Node* context, Node* map); Node* IsInitialRegExpMap(Node* context, Node* map);
void BranchIfFastRegExpResult(Node* context, Node* map, void BranchIfFastRegExpResult(Node* context, Node* map,
Label* if_isunmodified, Label* if_ismodified); Label* if_isunmodified, Label* if_ismodified);
......
...@@ -1115,7 +1115,7 @@ void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol( ...@@ -1115,7 +1115,7 @@ void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol(
} }
// Take the fast path for RegExps. // Take the fast path for RegExps.
if (regexp_call != nullptr) { {
Label stub_call(this), slow_lookup(this); Label stub_call(this), slow_lookup(this);
RegExpBuiltinsAssembler regexp_asm(state()); RegExpBuiltinsAssembler regexp_asm(state());
...@@ -1160,10 +1160,18 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) { ...@@ -1160,10 +1160,18 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
RequireObjectCoercible(context, receiver, "String.prototype.replace"); RequireObjectCoercible(context, receiver, "String.prototype.replace");
// Redirect to replacer method if {search[@@replace]} is not undefined. // Redirect to replacer method if {search[@@replace]} is not undefined.
// TODO(jgruber): Call RegExp.p.replace stub for fast path.
MaybeCallFunctionAtSymbol( MaybeCallFunctionAtSymbol(
context, search, isolate()->factory()->replace_symbol(), nullptr, context, search, isolate()->factory()->replace_symbol(),
[=]() {
Callable tostring_callable = CodeFactory::ToString(isolate());
Node* const subject_string =
CallStub(tostring_callable, context, receiver);
Callable replace_callable = CodeFactory::RegExpReplace(isolate());
return CallStub(replace_callable, context, search, subject_string,
replace);
},
[=](Node* fn) { [=](Node* fn) {
Callable call_callable = CodeFactory::Call(isolate()); Callable call_callable = CodeFactory::Call(isolate());
return CallJS(call_callable, context, fn, search, receiver, replace); return CallJS(call_callable, context, fn, search, receiver, replace);
...@@ -1317,10 +1325,18 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) { ...@@ -1317,10 +1325,18 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
RequireObjectCoercible(context, receiver, "String.prototype.split"); RequireObjectCoercible(context, receiver, "String.prototype.split");
// Redirect to splitter method if {separator[@@split]} is not undefined. // Redirect to splitter method if {separator[@@split]} is not undefined.
// TODO(jgruber): Call RegExp.p.split stub for fast path.
MaybeCallFunctionAtSymbol( MaybeCallFunctionAtSymbol(
context, separator, isolate()->factory()->split_symbol(), nullptr, context, separator, isolate()->factory()->split_symbol(),
[=]() {
Callable tostring_callable = CodeFactory::ToString(isolate());
Node* const subject_string =
CallStub(tostring_callable, context, receiver);
Callable split_callable = CodeFactory::RegExpSplit(isolate());
return CallStub(split_callable, context, separator, subject_string,
limit);
},
[=](Node* fn) { [=](Node* fn) {
Callable call_callable = CodeFactory::Call(isolate()); Callable call_callable = CodeFactory::Call(isolate());
return CallJS(call_callable, context, fn, separator, receiver, limit); return CallJS(call_callable, context, fn, separator, receiver, limit);
......
...@@ -707,16 +707,20 @@ class Isolate; ...@@ -707,16 +707,20 @@ class Isolate;
TFJ(RegExpPrototypeIgnoreCaseGetter, 0) \ TFJ(RegExpPrototypeIgnoreCaseGetter, 0) \
TFJ(RegExpPrototypeMatch, 1) \ TFJ(RegExpPrototypeMatch, 1) \
TFJ(RegExpPrototypeMultilineGetter, 0) \ TFJ(RegExpPrototypeMultilineGetter, 0) \
TFJ(RegExpPrototypeReplace, 2) \
TFJ(RegExpPrototypeSearch, 1) \ TFJ(RegExpPrototypeSearch, 1) \
TFJ(RegExpPrototypeSourceGetter, 0) \ TFJ(RegExpPrototypeSourceGetter, 0) \
TFJ(RegExpPrototypeSplit, 2) \
TFJ(RegExpPrototypeStickyGetter, 0) \ TFJ(RegExpPrototypeStickyGetter, 0) \
TFJ(RegExpPrototypeTest, 1) \ TFJ(RegExpPrototypeTest, 1) \
CPP(RegExpPrototypeToString) \ CPP(RegExpPrototypeToString) \
TFJ(RegExpPrototypeUnicodeGetter, 0) \ TFJ(RegExpPrototypeUnicodeGetter, 0) \
CPP(RegExpRightContextGetter) \ CPP(RegExpRightContextGetter) \
\ \
TFS(RegExpReplace, BUILTIN, kNoExtraICState, RegExpReplace) \
TFJ(RegExpPrototypeReplace, 2) \
\
TFS(RegExpSplit, BUILTIN, kNoExtraICState, RegExpSplit) \
TFJ(RegExpPrototypeSplit, 2) \
\
/* SharedArrayBuffer */ \ /* SharedArrayBuffer */ \
CPP(SharedArrayBufferPrototypeGetByteLength) \ CPP(SharedArrayBufferPrototypeGetByteLength) \
TFJ(AtomicsLoad, 2) \ TFJ(AtomicsLoad, 2) \
......
...@@ -251,6 +251,8 @@ TFS_BUILTIN(ForInFilter) ...@@ -251,6 +251,8 @@ TFS_BUILTIN(ForInFilter)
TFS_BUILTIN(GetSuperConstructor) TFS_BUILTIN(GetSuperConstructor)
TFS_BUILTIN(KeyedLoadIC_Megamorphic) TFS_BUILTIN(KeyedLoadIC_Megamorphic)
TFS_BUILTIN(PromiseHandleReject) TFS_BUILTIN(PromiseHandleReject)
TFS_BUILTIN(RegExpReplace)
TFS_BUILTIN(RegExpSplit)
TFS_BUILTIN(StringCharAt) TFS_BUILTIN(StringCharAt)
TFS_BUILTIN(StringCharCodeAt) TFS_BUILTIN(StringCharCodeAt)
TFS_BUILTIN(StringEqual) TFS_BUILTIN(StringEqual)
......
...@@ -132,6 +132,9 @@ class V8_EXPORT_PRIVATE CodeFactory final { ...@@ -132,6 +132,9 @@ class V8_EXPORT_PRIVATE CodeFactory final {
static Callable SubString(Isolate* isolate); static Callable SubString(Isolate* isolate);
static Callable StringIndexOf(Isolate* isolate); static Callable StringIndexOf(Isolate* isolate);
static Callable RegExpReplace(Isolate* isolate);
static Callable RegExpSplit(Isolate* isolate);
static Callable ClassOf(Isolate* isolate); static Callable ClassOf(Isolate* isolate);
static Callable Typeof(Isolate* isolate); static Callable Typeof(Isolate* isolate);
static Callable GetSuperConstructor(Isolate* isolate); static Callable GetSuperConstructor(Isolate* isolate);
......
...@@ -52,6 +52,8 @@ class PlatformInterfaceDescriptor; ...@@ -52,6 +52,8 @@ class PlatformInterfaceDescriptor;
V(ConstructStub) \ V(ConstructStub) \
V(ConstructTrampoline) \ V(ConstructTrampoline) \
V(RegExpExec) \ V(RegExpExec) \
V(RegExpReplace) \
V(RegExpSplit) \
V(CopyFastSmiOrObjectElements) \ V(CopyFastSmiOrObjectElements) \
V(TransitionElementsKind) \ V(TransitionElementsKind) \
V(AllocateHeapNumber) \ V(AllocateHeapNumber) \
...@@ -619,6 +621,20 @@ class RegExpExecDescriptor : public CallInterfaceDescriptor { ...@@ -619,6 +621,20 @@ class RegExpExecDescriptor : public CallInterfaceDescriptor {
CallInterfaceDescriptor) CallInterfaceDescriptor)
}; };
class RegExpReplaceDescriptor : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS(kReceiver, kString, kReplaceValue)
DECLARE_DEFAULT_DESCRIPTOR(RegExpReplaceDescriptor, CallInterfaceDescriptor,
kParameterCount)
};
class RegExpSplitDescriptor : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS(kReceiver, kString, kLimit)
DECLARE_DEFAULT_DESCRIPTOR(RegExpSplitDescriptor, CallInterfaceDescriptor,
kParameterCount)
};
class CopyFastSmiOrObjectElementsDescriptor : public CallInterfaceDescriptor { class CopyFastSmiOrObjectElementsDescriptor : public CallInterfaceDescriptor {
public: public:
DEFINE_PARAMETERS(kObject) DEFINE_PARAMETERS(kObject)
......
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