[string] Migrate String.prototype.{split,replace} to TF
BUG=
Review-Url: https://codereview.chromium.org/2663803002
Cr-Original-Commit-Position: refs/heads/master@{#42881}
Committed: 65ad1e35d9
Review-Url: https://codereview.chromium.org/2663803002
Cr-Commit-Position: refs/heads/master@{#42883}
This commit is contained in:
parent
2517b79cd6
commit
cb19ecd610
1
BUILD.gn
1
BUILD.gn
@ -1001,6 +1001,7 @@ v8_source_set("v8_base") {
|
||||
"src/builtins/builtins-proxy.cc",
|
||||
"src/builtins/builtins-reflect.cc",
|
||||
"src/builtins/builtins-regexp.cc",
|
||||
"src/builtins/builtins-regexp.h",
|
||||
"src/builtins/builtins-sharedarraybuffer.cc",
|
||||
"src/builtins/builtins-string.cc",
|
||||
"src/builtins/builtins-symbol.cc",
|
||||
|
@ -1587,6 +1587,10 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
|
||||
Builtins::kStringPrototypeLocaleCompare, 1, true);
|
||||
SimpleInstallFunction(prototype, "normalize",
|
||||
Builtins::kStringPrototypeNormalize, 0, false);
|
||||
SimpleInstallFunction(prototype, "split", Builtins::kStringPrototypeSplit,
|
||||
2, true);
|
||||
SimpleInstallFunction(prototype, "replace",
|
||||
Builtins::kStringPrototypeReplace, 2, true);
|
||||
SimpleInstallFunction(prototype, "substr", Builtins::kStringPrototypeSubstr,
|
||||
2, true);
|
||||
SimpleInstallFunction(prototype, "substring",
|
||||
@ -3800,7 +3804,7 @@ bool Genesis::InstallNatives(GlobalContextType context_type) {
|
||||
DCHECK(JSObject::cast(
|
||||
string_function->initial_map()->prototype())->HasFastProperties());
|
||||
native_context()->set_string_function_prototype_map(
|
||||
HeapObject::cast(string_function->initial_map()->prototype())->map());
|
||||
HeapObject::cast(string_function->map()->prototype())->map());
|
||||
|
||||
Handle<JSGlobalObject> global_object =
|
||||
handle(native_context()->global_object());
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/builtins/builtins-regexp.h"
|
||||
|
||||
#include "src/builtins/builtins-constructor.h"
|
||||
#include "src/builtins/builtins-utils.h"
|
||||
#include "src/builtins/builtins.h"
|
||||
@ -14,82 +16,8 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
typedef compiler::Node Node;
|
||||
typedef CodeStubAssembler::ParameterMode ParameterMode;
|
||||
typedef compiler::CodeAssemblerState CodeAssemblerState;
|
||||
|
||||
class RegExpBuiltinsAssembler : public CodeStubAssembler {
|
||||
public:
|
||||
explicit RegExpBuiltinsAssembler(CodeAssemblerState* state)
|
||||
: CodeStubAssembler(state) {}
|
||||
|
||||
protected:
|
||||
Node* FastLoadLastIndex(Node* regexp);
|
||||
Node* SlowLoadLastIndex(Node* context, Node* regexp);
|
||||
Node* LoadLastIndex(Node* context, Node* regexp, bool is_fastpath);
|
||||
|
||||
void FastStoreLastIndex(Node* regexp, Node* value);
|
||||
void SlowStoreLastIndex(Node* context, Node* regexp, Node* value);
|
||||
void StoreLastIndex(Node* context, Node* regexp, Node* value,
|
||||
bool is_fastpath);
|
||||
|
||||
Node* ConstructNewResultFromMatchInfo(Node* const context, Node* const regexp,
|
||||
Node* const match_info,
|
||||
Node* const string);
|
||||
|
||||
Node* RegExpPrototypeExecBodyWithoutResult(Node* const context,
|
||||
Node* const regexp,
|
||||
Node* const string,
|
||||
Label* if_didnotmatch,
|
||||
const bool is_fastpath);
|
||||
Node* RegExpPrototypeExecBody(Node* const context, Node* const regexp,
|
||||
Node* const string, const bool is_fastpath);
|
||||
|
||||
Node* ThrowIfNotJSReceiver(Node* context, Node* maybe_receiver,
|
||||
MessageTemplate::Template msg_template,
|
||||
char const* method_name);
|
||||
|
||||
Node* IsInitialRegExpMap(Node* context, Node* map);
|
||||
void BranchIfFastRegExp(Node* context, Node* map, Label* if_isunmodified,
|
||||
Label* if_ismodified);
|
||||
void BranchIfFastRegExpResult(Node* context, Node* map,
|
||||
Label* if_isunmodified, Label* if_ismodified);
|
||||
|
||||
Node* FlagsGetter(Node* const context, Node* const regexp, bool is_fastpath);
|
||||
|
||||
Node* FastFlagGetter(Node* const regexp, JSRegExp::Flag flag);
|
||||
Node* SlowFlagGetter(Node* const context, Node* const regexp,
|
||||
JSRegExp::Flag flag);
|
||||
Node* FlagGetter(Node* const context, Node* const regexp, JSRegExp::Flag flag,
|
||||
bool is_fastpath);
|
||||
void FlagGetter(JSRegExp::Flag flag, v8::Isolate::UseCounterFeature counter,
|
||||
const char* method_name);
|
||||
|
||||
Node* IsRegExp(Node* const context, Node* const maybe_receiver);
|
||||
Node* RegExpInitialize(Node* const context, Node* const regexp,
|
||||
Node* const maybe_pattern, Node* const maybe_flags);
|
||||
|
||||
Node* RegExpExec(Node* context, Node* regexp, Node* string);
|
||||
|
||||
Node* AdvanceStringIndex(Node* const string, Node* const index,
|
||||
Node* const is_unicode);
|
||||
|
||||
void RegExpPrototypeMatchBody(Node* const context, Node* const regexp,
|
||||
Node* const string, const bool is_fastpath);
|
||||
|
||||
void RegExpPrototypeSearchBodyFast(Node* const context, Node* const regexp,
|
||||
Node* const string);
|
||||
void RegExpPrototypeSearchBodySlow(Node* const context, Node* const regexp,
|
||||
Node* const string);
|
||||
|
||||
void RegExpPrototypeSplitBody(Node* const context, Node* const regexp,
|
||||
Node* const string, Node* const limit);
|
||||
|
||||
Node* ReplaceGlobalCallableFastPath(Node* context, Node* regexp, Node* string,
|
||||
Node* replace_callable);
|
||||
Node* ReplaceSimpleStringFastPath(Node* context, Node* regexp, Node* string,
|
||||
Node* replace_string);
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// ES6 section 21.2 RegExp Objects
|
||||
|
94
src/builtins/builtins-regexp.h
Normal file
94
src/builtins/builtins-regexp.h
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright 2017 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef V8_BUILTINS_BUILTINS_REGEXP_H_
|
||||
#define V8_BUILTINS_BUILTINS_REGEXP_H_
|
||||
|
||||
#include "src/code-stub-assembler.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
typedef compiler::Node Node;
|
||||
typedef compiler::CodeAssemblerState CodeAssemblerState;
|
||||
typedef compiler::CodeAssemblerLabel CodeAssemblerLabel;
|
||||
|
||||
class RegExpBuiltinsAssembler : public CodeStubAssembler {
|
||||
public:
|
||||
explicit RegExpBuiltinsAssembler(CodeAssemblerState* state)
|
||||
: CodeStubAssembler(state) {}
|
||||
|
||||
void BranchIfFastRegExp(Node* context, Node* map, Label* if_isunmodified,
|
||||
Label* if_ismodified);
|
||||
|
||||
protected:
|
||||
Node* FastLoadLastIndex(Node* regexp);
|
||||
Node* SlowLoadLastIndex(Node* context, Node* regexp);
|
||||
Node* LoadLastIndex(Node* context, Node* regexp, bool is_fastpath);
|
||||
|
||||
void FastStoreLastIndex(Node* regexp, Node* value);
|
||||
void SlowStoreLastIndex(Node* context, Node* regexp, Node* value);
|
||||
void StoreLastIndex(Node* context, Node* regexp, Node* value,
|
||||
bool is_fastpath);
|
||||
|
||||
Node* ConstructNewResultFromMatchInfo(Node* const context, Node* const regexp,
|
||||
Node* const match_info,
|
||||
Node* const string);
|
||||
|
||||
Node* RegExpPrototypeExecBodyWithoutResult(Node* const context,
|
||||
Node* const regexp,
|
||||
Node* const string,
|
||||
Label* if_didnotmatch,
|
||||
const bool is_fastpath);
|
||||
Node* RegExpPrototypeExecBody(Node* const context, Node* const regexp,
|
||||
Node* const string, const bool is_fastpath);
|
||||
|
||||
Node* ThrowIfNotJSReceiver(Node* context, Node* maybe_receiver,
|
||||
MessageTemplate::Template msg_template,
|
||||
char const* method_name);
|
||||
|
||||
Node* IsInitialRegExpMap(Node* context, Node* map);
|
||||
void BranchIfFastRegExpResult(Node* context, Node* map,
|
||||
Label* if_isunmodified, Label* if_ismodified);
|
||||
|
||||
Node* FlagsGetter(Node* const context, Node* const regexp, bool is_fastpath);
|
||||
|
||||
Node* FastFlagGetter(Node* const regexp, JSRegExp::Flag flag);
|
||||
Node* SlowFlagGetter(Node* const context, Node* const regexp,
|
||||
JSRegExp::Flag flag);
|
||||
Node* FlagGetter(Node* const context, Node* const regexp, JSRegExp::Flag flag,
|
||||
bool is_fastpath);
|
||||
void FlagGetter(JSRegExp::Flag flag, v8::Isolate::UseCounterFeature counter,
|
||||
const char* method_name);
|
||||
|
||||
Node* IsRegExp(Node* const context, Node* const maybe_receiver);
|
||||
Node* RegExpInitialize(Node* const context, Node* const regexp,
|
||||
Node* const maybe_pattern, Node* const maybe_flags);
|
||||
|
||||
Node* RegExpExec(Node* context, Node* regexp, Node* string);
|
||||
|
||||
Node* AdvanceStringIndex(Node* const string, Node* const index,
|
||||
Node* const is_unicode);
|
||||
|
||||
void RegExpPrototypeMatchBody(Node* const context, Node* const regexp,
|
||||
Node* const string, const bool is_fastpath);
|
||||
|
||||
void RegExpPrototypeSearchBodyFast(Node* const context, Node* const regexp,
|
||||
Node* const string);
|
||||
void RegExpPrototypeSearchBodySlow(Node* const context, Node* const regexp,
|
||||
Node* const string);
|
||||
|
||||
void RegExpPrototypeSplitBody(Node* const context, Node* const regexp,
|
||||
Node* const string, Node* const limit);
|
||||
|
||||
Node* ReplaceGlobalCallableFastPath(Node* context, Node* regexp, Node* string,
|
||||
Node* replace_callable);
|
||||
Node* ReplaceSimpleStringFastPath(Node* context, Node* regexp, Node* string,
|
||||
Node* replace_string);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_BUILTINS_BUILTINS_REGEXP_H_
|
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/builtins/builtins-regexp.h"
|
||||
#include "src/builtins/builtins-utils.h"
|
||||
#include "src/builtins/builtins.h"
|
||||
#include "src/code-factory.h"
|
||||
@ -64,6 +65,32 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
|
||||
void StringIndexOf(Node* receiver, Node* instance_type, Node* search_string,
|
||||
Node* search_string_instance_type, Node* position,
|
||||
std::function<void(Node*)> f_return);
|
||||
|
||||
Node* IsNullOrUndefined(Node* const value);
|
||||
void RequireObjectCoercible(Node* const context, Node* const value,
|
||||
const char* method_name);
|
||||
|
||||
Node* SmiIsNegative(Node* const value) {
|
||||
return SmiLessThan(value, SmiConstant(0));
|
||||
}
|
||||
|
||||
// Implements boilerplate logic for {match, split, replace, search} of the
|
||||
// form:
|
||||
//
|
||||
// if (!IS_NULL_OR_UNDEFINED(object)) {
|
||||
// var maybe_function = object[symbol];
|
||||
// if (!IS_UNDEFINED(maybe_function)) {
|
||||
// return %_Call(maybe_function, ...);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Contains fast paths for Smi and RegExp objects.
|
||||
typedef std::function<Node*()> NodeFunction0;
|
||||
typedef std::function<Node*(Node* fn)> NodeFunction1;
|
||||
void MaybeCallFunctionAtSymbol(Node* const context, Node* const object,
|
||||
Handle<Symbol> symbol,
|
||||
const NodeFunction0& regexp_call,
|
||||
const NodeFunction1& generic_call);
|
||||
};
|
||||
|
||||
void StringBuiltinsAssembler::GenerateStringEqual(ResultMode mode) {
|
||||
@ -1038,6 +1065,339 @@ BUILTIN(StringPrototypeNormalize) {
|
||||
return *string;
|
||||
}
|
||||
|
||||
compiler::Node* StringBuiltinsAssembler::IsNullOrUndefined(Node* const value) {
|
||||
return Word32Or(IsUndefined(value), IsNull(value));
|
||||
}
|
||||
|
||||
void StringBuiltinsAssembler::RequireObjectCoercible(Node* const context,
|
||||
Node* const value,
|
||||
const char* method_name) {
|
||||
Label out(this), throw_exception(this, Label::kDeferred);
|
||||
Branch(IsNullOrUndefined(value), &throw_exception, &out);
|
||||
|
||||
Bind(&throw_exception);
|
||||
TailCallRuntime(
|
||||
Runtime::kThrowCalledOnNullOrUndefined, context,
|
||||
HeapConstant(factory()->NewStringFromAsciiChecked(method_name, TENURED)));
|
||||
|
||||
Bind(&out);
|
||||
}
|
||||
|
||||
void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol(
|
||||
Node* const context, Node* const object, Handle<Symbol> symbol,
|
||||
const NodeFunction0& regexp_call, const NodeFunction1& generic_call) {
|
||||
Label out(this);
|
||||
|
||||
// Smis definitely don't have an attached symbol.
|
||||
GotoIf(TaggedIsSmi(object), &out);
|
||||
|
||||
Node* const object_map = LoadMap(object);
|
||||
|
||||
// Skip the slow lookup for Strings.
|
||||
{
|
||||
Label next(this);
|
||||
|
||||
GotoUnless(IsStringInstanceType(LoadMapInstanceType(object_map)), &next);
|
||||
|
||||
Node* const native_context = LoadNativeContext(context);
|
||||
Node* const initial_proto_initial_map = LoadContextElement(
|
||||
native_context, Context::STRING_FUNCTION_PROTOTYPE_MAP_INDEX);
|
||||
|
||||
Node* const string_fun =
|
||||
LoadContextElement(native_context, Context::STRING_FUNCTION_INDEX);
|
||||
Node* const initial_map =
|
||||
LoadObjectField(string_fun, JSFunction::kPrototypeOrInitialMapOffset);
|
||||
Node* const proto_map = LoadMap(LoadMapPrototype(initial_map));
|
||||
|
||||
Branch(WordEqual(proto_map, initial_proto_initial_map), &out, &next);
|
||||
|
||||
Bind(&next);
|
||||
}
|
||||
|
||||
// Take the fast path for RegExps.
|
||||
if (regexp_call != nullptr) {
|
||||
Label stub_call(this), slow_lookup(this);
|
||||
|
||||
RegExpBuiltinsAssembler regexp_asm(state());
|
||||
regexp_asm.BranchIfFastRegExp(context, object_map, &stub_call,
|
||||
&slow_lookup);
|
||||
|
||||
Bind(&stub_call);
|
||||
Return(regexp_call());
|
||||
|
||||
Bind(&slow_lookup);
|
||||
}
|
||||
|
||||
GotoIf(IsNullOrUndefined(object), &out);
|
||||
|
||||
// Fall back to a slow lookup of {object[symbol]}.
|
||||
|
||||
Callable getproperty_callable = CodeFactory::GetProperty(isolate());
|
||||
Node* const key = HeapConstant(symbol);
|
||||
Node* const maybe_func = CallStub(getproperty_callable, context, object, key);
|
||||
|
||||
GotoIf(IsUndefined(maybe_func), &out);
|
||||
|
||||
// Attempt to call the function.
|
||||
|
||||
Node* const result = generic_call(maybe_func);
|
||||
Return(result);
|
||||
|
||||
Bind(&out);
|
||||
}
|
||||
|
||||
// ES6 section 21.1.3.16 String.prototype.replace ( search, replace )
|
||||
TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
|
||||
Label out(this);
|
||||
|
||||
Node* const receiver = Parameter(0);
|
||||
Node* const search = Parameter(1);
|
||||
Node* const replace = Parameter(2);
|
||||
Node* const context = Parameter(5);
|
||||
|
||||
Node* const smi_zero = SmiConstant(0);
|
||||
|
||||
RequireObjectCoercible(context, receiver, "String.prototype.replace");
|
||||
|
||||
// Redirect to replacer method if {search[@@replace]} is not undefined.
|
||||
// TODO(jgruber): Call RegExp.p.replace stub for fast path.
|
||||
|
||||
MaybeCallFunctionAtSymbol(
|
||||
context, search, isolate()->factory()->replace_symbol(), nullptr,
|
||||
[=](Node* fn) {
|
||||
Callable call_callable = CodeFactory::Call(isolate());
|
||||
return CallJS(call_callable, context, fn, search, receiver, replace);
|
||||
});
|
||||
|
||||
// Convert {receiver} and {search} to strings.
|
||||
|
||||
Callable tostring_callable = CodeFactory::ToString(isolate());
|
||||
Node* const subject_string = CallStub(tostring_callable, context, receiver);
|
||||
Node* const search_string = CallStub(tostring_callable, context, search);
|
||||
|
||||
Node* const subject_length = LoadStringLength(subject_string);
|
||||
Node* const search_length = LoadStringLength(search_string);
|
||||
|
||||
// Fast-path single-char {search}, long {receiver}, and simple string
|
||||
// {replace}.
|
||||
{
|
||||
Label next(this);
|
||||
|
||||
GotoUnless(SmiEqual(search_length, SmiConstant(1)), &next);
|
||||
GotoUnless(SmiGreaterThan(subject_length, SmiConstant(0xFF)), &next);
|
||||
GotoIf(TaggedIsSmi(replace), &next);
|
||||
GotoUnless(IsString(replace), &next);
|
||||
|
||||
Node* const dollar_char = Int32Constant('$');
|
||||
Node* const index_of_dollar =
|
||||
StringIndexOfChar(context, replace, dollar_char, smi_zero);
|
||||
GotoUnless(SmiIsNegative(index_of_dollar), &next);
|
||||
|
||||
// Searching by traversing a cons string tree and replace with cons of
|
||||
// slices works only when the replaced string is a single character, being
|
||||
// replaced by a simple string and only pays off for long strings.
|
||||
// TODO(jgruber): Reevaluate if this is still beneficial.
|
||||
TailCallRuntime(Runtime::kStringReplaceOneCharWithString, context,
|
||||
subject_string, search_string, replace);
|
||||
|
||||
Bind(&next);
|
||||
}
|
||||
|
||||
// TODO(jgruber): Extend StringIndexOfChar to handle two-byte strings and
|
||||
// longer substrings - we can handle up to 8 chars (one-byte) / 4 chars
|
||||
// (2-byte).
|
||||
|
||||
Callable indexof_stub = CodeFactory::StringIndexOf(isolate());
|
||||
Node* const match_start_index =
|
||||
CallStub(indexof_stub, context, subject_string, search_string, smi_zero);
|
||||
CSA_ASSERT(this, TaggedIsSmi(match_start_index));
|
||||
|
||||
// Early exit if no match found.
|
||||
{
|
||||
Label next(this), return_subject(this);
|
||||
|
||||
GotoUnless(SmiIsNegative(match_start_index), &next);
|
||||
|
||||
// The spec requires to perform ToString(replace) if the {replace} is not
|
||||
// callable even if we are going to exit here.
|
||||
// Since ToString() being applied to Smi does not have side effects for
|
||||
// numbers we can skip it.
|
||||
GotoIf(TaggedIsSmi(replace), &return_subject);
|
||||
GotoIf(IsCallableMap(LoadMap(replace)), &return_subject);
|
||||
|
||||
// TODO(jgruber): Could introduce ToStringSideeffectsStub which only
|
||||
// performs observable parts of ToString.
|
||||
CallStub(tostring_callable, context, replace);
|
||||
Goto(&return_subject);
|
||||
|
||||
Bind(&return_subject);
|
||||
Return(subject_string);
|
||||
|
||||
Bind(&next);
|
||||
}
|
||||
|
||||
Node* const match_end_index = SmiAdd(match_start_index, search_length);
|
||||
|
||||
Callable substring_callable = CodeFactory::SubString(isolate());
|
||||
Callable stringadd_callable =
|
||||
CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED);
|
||||
|
||||
Variable var_result(this, MachineRepresentation::kTagged,
|
||||
EmptyStringConstant());
|
||||
|
||||
// Compute the prefix.
|
||||
{
|
||||
Label next(this);
|
||||
|
||||
GotoIf(SmiEqual(match_start_index, smi_zero), &next);
|
||||
Node* const prefix = CallStub(substring_callable, context, subject_string,
|
||||
smi_zero, match_start_index);
|
||||
var_result.Bind(prefix);
|
||||
|
||||
Goto(&next);
|
||||
Bind(&next);
|
||||
}
|
||||
|
||||
// Compute the string to replace with.
|
||||
|
||||
Label if_iscallablereplace(this), if_notcallablereplace(this);
|
||||
GotoIf(TaggedIsSmi(replace), &if_notcallablereplace);
|
||||
Branch(IsCallableMap(LoadMap(replace)), &if_iscallablereplace,
|
||||
&if_notcallablereplace);
|
||||
|
||||
Bind(&if_iscallablereplace);
|
||||
{
|
||||
Callable call_callable = CodeFactory::Call(isolate());
|
||||
Node* const replacement =
|
||||
CallJS(call_callable, context, replace, UndefinedConstant(),
|
||||
search_string, match_start_index, subject_string);
|
||||
Node* const replacement_string =
|
||||
CallStub(tostring_callable, context, replacement);
|
||||
var_result.Bind(CallStub(stringadd_callable, context, var_result.value(),
|
||||
replacement_string));
|
||||
Goto(&out);
|
||||
}
|
||||
|
||||
Bind(&if_notcallablereplace);
|
||||
{
|
||||
Node* const replace_string = CallStub(tostring_callable, context, replace);
|
||||
|
||||
// TODO(jgruber): Simplified GetSubstitution implementation in CSA.
|
||||
Node* const matched = CallStub(substring_callable, context, subject_string,
|
||||
match_start_index, match_end_index);
|
||||
Node* const replacement_string =
|
||||
CallRuntime(Runtime::kGetSubstitution, context, matched, subject_string,
|
||||
match_start_index, replace_string);
|
||||
var_result.Bind(CallStub(stringadd_callable, context, var_result.value(),
|
||||
replacement_string));
|
||||
Goto(&out);
|
||||
}
|
||||
|
||||
Bind(&out);
|
||||
{
|
||||
Node* const suffix = CallStub(substring_callable, context, subject_string,
|
||||
match_end_index, subject_length);
|
||||
Node* const result =
|
||||
CallStub(stringadd_callable, context, var_result.value(), suffix);
|
||||
Return(result);
|
||||
}
|
||||
}
|
||||
|
||||
// ES6 section 21.1.3.19 String.prototype.split ( separator, limit )
|
||||
TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
|
||||
Label out(this);
|
||||
|
||||
Node* const receiver = Parameter(0);
|
||||
Node* const separator = Parameter(1);
|
||||
Node* const limit = Parameter(2);
|
||||
Node* const context = Parameter(5);
|
||||
|
||||
Node* const smi_zero = SmiConstant(0);
|
||||
|
||||
RequireObjectCoercible(context, receiver, "String.prototype.split");
|
||||
|
||||
// Redirect to splitter method if {separator[@@split]} is not undefined.
|
||||
// TODO(jgruber): Call RegExp.p.split stub for fast path.
|
||||
|
||||
MaybeCallFunctionAtSymbol(
|
||||
context, separator, isolate()->factory()->split_symbol(), nullptr,
|
||||
[=](Node* fn) {
|
||||
Callable call_callable = CodeFactory::Call(isolate());
|
||||
return CallJS(call_callable, context, fn, separator, receiver, limit);
|
||||
});
|
||||
|
||||
// String and integer conversions.
|
||||
// TODO(jgruber): The old implementation used Uint32Max instead of SmiMax -
|
||||
// but AFAIK there should not be a difference since arrays are capped at Smi
|
||||
// lengths.
|
||||
|
||||
Callable tostring_callable = CodeFactory::ToString(isolate());
|
||||
Node* const subject_string = CallStub(tostring_callable, context, receiver);
|
||||
Node* const limit_number =
|
||||
Select(IsUndefined(limit), [=]() { return SmiConstant(Smi::kMaxValue); },
|
||||
[=]() { return ToUint32(context, limit); },
|
||||
MachineRepresentation::kTagged);
|
||||
Node* const separator_string =
|
||||
CallStub(tostring_callable, context, separator);
|
||||
|
||||
// Shortcut for {limit} == 0.
|
||||
{
|
||||
Label next(this);
|
||||
GotoUnless(SmiEqual(limit_number, smi_zero), &next);
|
||||
|
||||
const ElementsKind kind = FAST_ELEMENTS;
|
||||
Node* const native_context = LoadNativeContext(context);
|
||||
Node* const array_map = LoadJSArrayElementsMap(kind, native_context);
|
||||
|
||||
Node* const length = smi_zero;
|
||||
Node* const capacity = IntPtrConstant(0);
|
||||
Node* const result = AllocateJSArray(kind, array_map, capacity, length);
|
||||
|
||||
Return(result);
|
||||
|
||||
Bind(&next);
|
||||
}
|
||||
|
||||
// ECMA-262 says that if {separator} is undefined, the result should
|
||||
// be an array of size 1 containing the entire string.
|
||||
{
|
||||
Label next(this);
|
||||
GotoUnless(IsUndefined(separator), &next);
|
||||
|
||||
const ElementsKind kind = FAST_ELEMENTS;
|
||||
Node* const native_context = LoadNativeContext(context);
|
||||
Node* const array_map = LoadJSArrayElementsMap(kind, native_context);
|
||||
|
||||
Node* const length = SmiConstant(1);
|
||||
Node* const capacity = IntPtrConstant(1);
|
||||
Node* const result = AllocateJSArray(kind, array_map, capacity, length);
|
||||
|
||||
Node* const fixed_array = LoadElements(result);
|
||||
StoreFixedArrayElement(fixed_array, 0, subject_string);
|
||||
|
||||
Return(result);
|
||||
|
||||
Bind(&next);
|
||||
}
|
||||
|
||||
// If the separator string is empty then return the elements in the subject.
|
||||
{
|
||||
Label next(this);
|
||||
GotoUnless(SmiEqual(LoadStringLength(separator_string), smi_zero), &next);
|
||||
|
||||
Node* const result = CallRuntime(Runtime::kStringToArray, context,
|
||||
subject_string, limit_number);
|
||||
Return(result);
|
||||
|
||||
Bind(&next);
|
||||
}
|
||||
|
||||
Node* const result =
|
||||
CallRuntime(Runtime::kStringSplit, context, subject_string,
|
||||
separator_string, limit_number);
|
||||
Return(result);
|
||||
}
|
||||
|
||||
// ES6 section B.2.3.1 String.prototype.substr ( start, length )
|
||||
TF_BUILTIN(StringPrototypeSubstr, CodeStubAssembler) {
|
||||
Label out(this), handle_length(this);
|
||||
|
@ -748,6 +748,10 @@ class Isolate;
|
||||
CPP(StringPrototypeLocaleCompare) \
|
||||
/* ES6 section 21.1.3.12 String.prototype.normalize ( [form] ) */ \
|
||||
CPP(StringPrototypeNormalize) \
|
||||
/* ES6 section 21.1.3.16 String.prototype.replace ( search, replace ) */ \
|
||||
TFJ(StringPrototypeReplace, 2) \
|
||||
/* ES6 section 21.1.3.19 String.prototype.split ( separator, limit ) */ \
|
||||
TFJ(StringPrototypeSplit, 2) \
|
||||
/* ES6 section B.2.3.1 String.prototype.substr ( start, length ) */ \
|
||||
TFJ(StringPrototypeSubstr, 2) \
|
||||
/* ES6 section 21.1.3.19 String.prototype.substring ( start, end ) */ \
|
||||
|
205
src/js/string.js
205
src/js/string.js
@ -9,21 +9,9 @@
|
||||
// -------------------------------------------------------------------
|
||||
// Imports
|
||||
|
||||
var ArrayJoin;
|
||||
var GlobalRegExp = global.RegExp;
|
||||
var GlobalString = global.String;
|
||||
var MaxSimple;
|
||||
var MinSimple;
|
||||
var matchSymbol = utils.ImportNow("match_symbol");
|
||||
var replaceSymbol = utils.ImportNow("replace_symbol");
|
||||
var searchSymbol = utils.ImportNow("search_symbol");
|
||||
var splitSymbol = utils.ImportNow("split_symbol");
|
||||
|
||||
utils.Import(function(from) {
|
||||
ArrayJoin = from.ArrayJoin;
|
||||
MaxSimple = from.MaxSimple;
|
||||
MinSimple = from.MinSimple;
|
||||
});
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
@ -58,154 +46,6 @@ function StringMatchJS(pattern) {
|
||||
return regexp[matchSymbol](subject);
|
||||
}
|
||||
|
||||
// ES#sec-getsubstitution
|
||||
// GetSubstitution(matched, str, position, captures, replacement)
|
||||
// Expand the $-expressions in the string and return a new string with
|
||||
// the result.
|
||||
function GetSubstitution(matched, string, position, captures, replacement) {
|
||||
var matchLength = matched.length;
|
||||
var stringLength = string.length;
|
||||
var capturesLength = captures.length;
|
||||
var tailPos = position + matchLength;
|
||||
var result = "";
|
||||
var pos, expansion, peek, next, scaledIndex, advance, newScaledIndex;
|
||||
|
||||
var next = %StringIndexOf(replacement, '$', 0);
|
||||
if (next < 0) {
|
||||
result += replacement;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (next > 0) result += %_SubString(replacement, 0, next);
|
||||
|
||||
while (true) {
|
||||
expansion = '$';
|
||||
pos = next + 1;
|
||||
if (pos < replacement.length) {
|
||||
peek = %_StringCharCodeAt(replacement, pos);
|
||||
if (peek == 36) { // $$
|
||||
++pos;
|
||||
result += '$';
|
||||
} else if (peek == 38) { // $& - match
|
||||
++pos;
|
||||
result += matched;
|
||||
} else if (peek == 96) { // $` - prefix
|
||||
++pos;
|
||||
result += %_SubString(string, 0, position);
|
||||
} else if (peek == 39) { // $' - suffix
|
||||
++pos;
|
||||
result += %_SubString(string, tailPos, stringLength);
|
||||
} else if (peek >= 48 && peek <= 57) {
|
||||
// Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
|
||||
scaledIndex = (peek - 48);
|
||||
advance = 1;
|
||||
if (pos + 1 < replacement.length) {
|
||||
next = %_StringCharCodeAt(replacement, pos + 1);
|
||||
if (next >= 48 && next <= 57) {
|
||||
newScaledIndex = scaledIndex * 10 + ((next - 48));
|
||||
if (newScaledIndex < capturesLength) {
|
||||
scaledIndex = newScaledIndex;
|
||||
advance = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scaledIndex != 0 && scaledIndex < capturesLength) {
|
||||
var capture = captures.at(scaledIndex);
|
||||
if (!IS_UNDEFINED(capture)) result += capture;
|
||||
pos += advance;
|
||||
} else {
|
||||
result += '$';
|
||||
}
|
||||
} else {
|
||||
result += '$';
|
||||
}
|
||||
} else {
|
||||
result += '$';
|
||||
}
|
||||
|
||||
// Go the the next $ in the replacement.
|
||||
next = %StringIndexOf(replacement, '$', pos);
|
||||
|
||||
// Return if there are no more $ characters in the replacement. If we
|
||||
// haven't reached the end, we need to append the suffix.
|
||||
if (next < 0) {
|
||||
if (pos < replacement.length) {
|
||||
result += %_SubString(replacement, pos, replacement.length);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Append substring between the previous and the next $ character.
|
||||
if (next > pos) {
|
||||
result += %_SubString(replacement, pos, next);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ES6, section 21.1.3.14
|
||||
function StringReplace(search, replace) {
|
||||
CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace");
|
||||
|
||||
// Decision tree for dispatch
|
||||
// .. regexp search (in src/js/regexp.js, RegExpReplace)
|
||||
// .... string replace
|
||||
// ...... non-global search
|
||||
// ........ empty string replace
|
||||
// ........ non-empty string replace (with $-expansion)
|
||||
// ...... global search
|
||||
// ........ no need to circumvent last match info override
|
||||
// ........ need to circument last match info override
|
||||
// .... function replace
|
||||
// ...... global search
|
||||
// ...... non-global search
|
||||
// .. string search
|
||||
// .... special case that replaces with one single character
|
||||
// ...... function replace
|
||||
// ...... string replace (with $-expansion)
|
||||
|
||||
if (!IS_NULL_OR_UNDEFINED(search)) {
|
||||
var replacer = search[replaceSymbol];
|
||||
if (!IS_UNDEFINED(replacer)) {
|
||||
return %_Call(replacer, search, this, replace);
|
||||
}
|
||||
}
|
||||
|
||||
var subject = TO_STRING(this);
|
||||
|
||||
search = TO_STRING(search);
|
||||
|
||||
if (search.length == 1 &&
|
||||
subject.length > 0xFF &&
|
||||
IS_STRING(replace) &&
|
||||
%StringIndexOf(replace, '$', 0) < 0) {
|
||||
// Searching by traversing a cons string tree and replace with cons of
|
||||
// slices works only when the replaced string is a single character, being
|
||||
// replaced by a simple string and only pays off for long strings.
|
||||
return %StringReplaceOneCharWithString(subject, search, replace);
|
||||
}
|
||||
var start = %StringIndexOf(subject, search, 0);
|
||||
if (start < 0) return subject;
|
||||
var end = start + search.length;
|
||||
|
||||
var result = %_SubString(subject, 0, start);
|
||||
|
||||
// Compute the string to replace with.
|
||||
if (IS_CALLABLE(replace)) {
|
||||
result += replace(search, start, subject);
|
||||
} else {
|
||||
// In this case, we don't have any capture groups and can get away with
|
||||
// faking the captures object by simply setting its length to 1.
|
||||
const captures = { length: 1 };
|
||||
const matched = %_SubString(subject, start, end);
|
||||
result += GetSubstitution(matched, subject, start, captures,
|
||||
TO_STRING(replace));
|
||||
}
|
||||
|
||||
return result + %_SubString(subject, end, subject.length);
|
||||
}
|
||||
|
||||
|
||||
// ES6 21.1.3.15.
|
||||
function StringSearch(pattern) {
|
||||
CHECK_OBJECT_COERCIBLE(this, "String.prototype.search");
|
||||
@ -266,39 +106,6 @@ function StringSlice(start, end) {
|
||||
return %_SubString(s, start_i, end_i);
|
||||
}
|
||||
|
||||
|
||||
// ES6 21.1.3.17.
|
||||
function StringSplitJS(separator, limit) {
|
||||
CHECK_OBJECT_COERCIBLE(this, "String.prototype.split");
|
||||
|
||||
if (!IS_NULL_OR_UNDEFINED(separator)) {
|
||||
var splitter = separator[splitSymbol];
|
||||
if (!IS_UNDEFINED(splitter)) {
|
||||
return %_Call(splitter, separator, this, limit);
|
||||
}
|
||||
}
|
||||
|
||||
var subject = TO_STRING(this);
|
||||
limit = (IS_UNDEFINED(limit)) ? kMaxUint32 : TO_UINT32(limit);
|
||||
|
||||
var length = subject.length;
|
||||
var separator_string = TO_STRING(separator);
|
||||
|
||||
if (limit === 0) return [];
|
||||
|
||||
// ECMA-262 says that if separator is undefined, the result should
|
||||
// be an array of size 1 containing the entire string.
|
||||
if (IS_UNDEFINED(separator)) return [subject];
|
||||
|
||||
var separator_length = separator_string.length;
|
||||
|
||||
// If the separator string is empty then return the elements in the subject.
|
||||
if (separator_length === 0) return %StringToArray(subject, limit);
|
||||
|
||||
return %StringSplit(subject, separator_string, limit);
|
||||
}
|
||||
|
||||
|
||||
// ECMA-262, 15.5.4.16
|
||||
function StringToLowerCaseJS() {
|
||||
CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase");
|
||||
@ -515,10 +322,8 @@ utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [
|
||||
"concat", StringConcat,
|
||||
"match", StringMatchJS,
|
||||
"repeat", StringRepeat,
|
||||
"replace", StringReplace,
|
||||
"search", StringSearch,
|
||||
"slice", StringSlice,
|
||||
"split", StringSplitJS,
|
||||
"toLowerCase", StringToLowerCaseJS,
|
||||
"toLocaleLowerCase", StringToLocaleLowerCase,
|
||||
"toUpperCase", StringToUpperCaseJS,
|
||||
@ -539,14 +344,4 @@ utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [
|
||||
"sup", StringSup
|
||||
]);
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Exports
|
||||
|
||||
utils.Export(function(to) {
|
||||
to.StringMatch = StringMatchJS;
|
||||
to.StringReplace = StringReplace;
|
||||
to.StringSlice = StringSlice;
|
||||
to.StringSplit = StringSplitJS;
|
||||
});
|
||||
|
||||
})
|
||||
|
@ -13,6 +13,44 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_GetSubstitution) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(4, args.length());
|
||||
CONVERT_ARG_HANDLE_CHECKED(String, matched, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(String, subject, 1);
|
||||
CONVERT_SMI_ARG_CHECKED(position, 2);
|
||||
CONVERT_ARG_HANDLE_CHECKED(String, replacement, 3);
|
||||
|
||||
// A simple match without captures.
|
||||
class SimpleMatch : public String::Match {
|
||||
public:
|
||||
SimpleMatch(Handle<String> match, Handle<String> prefix,
|
||||
Handle<String> suffix)
|
||||
: match_(match), prefix_(prefix), suffix_(suffix) {}
|
||||
|
||||
Handle<String> GetMatch() override { return match_; }
|
||||
MaybeHandle<String> GetCapture(int i, bool* capture_exists) override {
|
||||
*capture_exists = false;
|
||||
return match_; // Return arbitrary string handle.
|
||||
}
|
||||
Handle<String> GetPrefix() override { return prefix_; }
|
||||
Handle<String> GetSuffix() override { return suffix_; }
|
||||
int CaptureCount() override { return 0; }
|
||||
|
||||
private:
|
||||
Handle<String> match_, prefix_, suffix_;
|
||||
};
|
||||
|
||||
Handle<String> prefix =
|
||||
isolate->factory()->NewSubString(subject, 0, position);
|
||||
Handle<String> suffix = isolate->factory()->NewSubString(
|
||||
subject, position + matched->length(), subject->length());
|
||||
SimpleMatch match(matched, prefix, suffix);
|
||||
|
||||
RETURN_RESULT_OR_FAILURE(
|
||||
isolate, String::GetSubstitution(isolate, &match, replacement));
|
||||
}
|
||||
|
||||
// This may return an empty MaybeHandle if an exception is thrown or
|
||||
// we abort due to reaching the recursion limit.
|
||||
MaybeHandle<String> StringReplaceOneCharWithString(
|
||||
|
@ -819,6 +819,7 @@ namespace internal {
|
||||
F(Bool8x16NotEqual, 2, 1)
|
||||
|
||||
#define FOR_EACH_INTRINSIC_STRINGS(F) \
|
||||
F(GetSubstitution, 4, 1) \
|
||||
F(StringReplaceOneCharWithString, 3, 1) \
|
||||
F(StringIndexOf, 3, 1) \
|
||||
F(StringIndexOfUnchecked, 3, 1) \
|
||||
|
@ -507,6 +507,7 @@
|
||||
'builtins/builtins-proxy.cc',
|
||||
'builtins/builtins-reflect.cc',
|
||||
'builtins/builtins-regexp.cc',
|
||||
'builtins/builtins-regexp.h',
|
||||
'builtins/builtins-sharedarraybuffer.cc',
|
||||
'builtins/builtins-string.cc',
|
||||
'builtins/builtins-symbol.cc',
|
||||
|
@ -53,7 +53,10 @@ function listener(event, exec_state, event_data, data) {
|
||||
if (f == "normalize") continue;
|
||||
if (f == "match") continue;
|
||||
if (f == "search") continue;
|
||||
if (f == "split") continue;
|
||||
if (f == "split" || f == "replace") {
|
||||
fail(`'abcd'.${f}(2)`);
|
||||
continue;
|
||||
}
|
||||
success("abcd"[f](2), `"abcd".${f}(2);`);
|
||||
}
|
||||
}
|
||||
|
@ -283,3 +283,16 @@ function testIndices59(re) {
|
||||
|
||||
testIndices59(new RegExp(regexp59pattern));
|
||||
testIndices59(new RegExp(regexp59pattern, "g"));
|
||||
|
||||
// Test that ToString(replace) is called.
|
||||
|
||||
let replace_tostring_count = 0;
|
||||
const fake_replacer = {
|
||||
[Symbol.toPrimitive]: () => { replace_tostring_count++; return "b"; }
|
||||
};
|
||||
|
||||
"a".replace("x", fake_replacer);
|
||||
assertEquals(1, replace_tostring_count);
|
||||
|
||||
"a".replace("a", fake_replacer);
|
||||
assertEquals(2, replace_tostring_count);
|
||||
|
Loading…
Reference in New Issue
Block a user