Commit b4ebbc57 authored by Choongwoo Han's avatar Choongwoo Han Committed by Commit Bot

[string] Add a fast path for empty separator in String.p.split

Optimize String.p.split for the case when the separator is empty and
the subject is a direct one-byte string.

Bug: v8:7103
Change-Id: Ica277d2c426679a1f77a1ef8ecb523bd596f65fb
Reviewed-on: https://chromium-review.googlesource.com/1045950
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53260}
parent 0154be8c
...@@ -1799,6 +1799,78 @@ TF_BUILTIN(StringPrototypeSlice, StringBuiltinsAssembler) { ...@@ -1799,6 +1799,78 @@ TF_BUILTIN(StringPrototypeSlice, StringBuiltinsAssembler) {
args.PopAndReturn(EmptyStringConstant()); args.PopAndReturn(EmptyStringConstant());
} }
TNode<JSArray> StringBuiltinsAssembler::StringToArray(
TNode<Context> context, TNode<String> subject_string,
TNode<Smi> subject_length, TNode<Number> limit_number) {
CSA_ASSERT(this, SmiGreaterThan(subject_length, SmiConstant(0)));
Label done(this), call_runtime(this, Label::kDeferred);
TVARIABLE(JSArray, result_array);
TNode<Int32T> instance_type = LoadInstanceType(subject_string);
GotoIfNot(IsOneByteStringInstanceType(instance_type), &call_runtime);
// Try to use cached one byte characters.
{
TNode<Smi> length_smi =
Select<Smi>(TaggedIsSmi(limit_number),
[=] { return SmiMin(CAST(limit_number), subject_length); },
[=] { return subject_length; });
TNode<IntPtrT> length = SmiToIntPtr(length_smi);
ToDirectStringAssembler to_direct(state(), subject_string);
to_direct.TryToDirect(&call_runtime);
TNode<RawPtrT> string_data =
UncheckedCast<RawPtrT>(to_direct.PointerToData(&call_runtime));
TNode<IntPtrT> string_data_offset = to_direct.offset();
TNode<Object> cache = LoadRoot(Heap::kSingleCharacterStringCacheRootIndex);
Label fill_thehole_and_call_runtime(this, Label::kDeferred);
TNode<FixedArray> elements =
AllocateFixedArray(PACKED_ELEMENTS, length_smi,
AllocationFlag::kAllowLargeObjectAllocation);
BuildFastLoop(
IntPtrConstant(0), length,
[&](Node* index) {
TNode<Int32T> char_code =
UncheckedCast<Int32T>(Load(MachineType::Uint8(), string_data,
IntPtrAdd(index, string_data_offset)));
Node* code_index = ChangeUint32ToWord(char_code);
TNode<Object> entry = LoadFixedArrayElement(CAST(cache), code_index);
// If we cannot find a char in the cache, fill the hole for the fixed
// array, and call runtime.
GotoIf(IsUndefined(entry), &fill_thehole_and_call_runtime);
StoreFixedArrayElement(elements, index, entry);
},
1, ParameterMode::INTPTR_PARAMETERS, IndexAdvanceMode::kPost);
TNode<Map> array_map = LoadJSArrayElementsMap(PACKED_ELEMENTS, context);
result_array = CAST(
AllocateUninitializedJSArrayWithoutElements(array_map, length_smi));
StoreObjectField(result_array.value(), JSObject::kElementsOffset, elements);
Goto(&done);
BIND(&fill_thehole_and_call_runtime);
{
FillFixedArrayWithValue(PACKED_ELEMENTS, elements, IntPtrConstant(0),
length, Heap::kTheHoleValueRootIndex);
Goto(&call_runtime);
}
}
BIND(&call_runtime);
{
result_array = CAST(CallRuntime(Runtime::kStringToArray, context,
subject_string, limit_number));
Goto(&done);
}
BIND(&done);
return result_array.value();
}
// ES6 section 21.1.3.19 String.prototype.split ( separator, limit ) // ES6 section 21.1.3.19 String.prototype.split ( separator, limit )
TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) { TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
const int kSeparatorArg = 0; const int kSeparatorArg = 0;
...@@ -1811,9 +1883,9 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) { ...@@ -1811,9 +1883,9 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
Node* const receiver = args.GetReceiver(); Node* const receiver = args.GetReceiver();
Node* const separator = args.GetOptionalArgumentValue(kSeparatorArg); Node* const separator = args.GetOptionalArgumentValue(kSeparatorArg);
Node* const limit = args.GetOptionalArgumentValue(kLimitArg); Node* const limit = args.GetOptionalArgumentValue(kLimitArg);
Node* const context = Parameter(BuiltinDescriptor::kContext); TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
Node* const smi_zero = SmiConstant(0); TNode<Smi> smi_zero = SmiConstant(0);
RequireObjectCoercible(context, receiver, "String.prototype.split"); RequireObjectCoercible(context, receiver, "String.prototype.split");
...@@ -1833,29 +1905,17 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) { ...@@ -1833,29 +1905,17 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
// String and integer conversions. // String and integer conversions.
Node* const subject_string = ToString_Inline(context, receiver); TNode<String> subject_string = ToString_Inline(context, receiver);
TNode<Number> const limit_number = Select<Number>( TNode<Number> limit_number = Select<Number>(
IsUndefined(limit), [=] { return NumberConstant(kMaxUInt32); }, IsUndefined(limit), [=] { return NumberConstant(kMaxUInt32); },
[=] { return ToUint32(context, limit); }); [=] { return ToUint32(context, limit); });
Node* const separator_string = ToString_Inline(context, separator); Node* const separator_string = ToString_Inline(context, separator);
// Shortcut for {limit} == 0. Label return_empty_array(this);
{
Label next(this);
GotoIfNot(WordEqual(limit_number, smi_zero), &next);
const ElementsKind kind = PACKED_ELEMENTS;
Node* const native_context = LoadNativeContext(context);
Node* const array_map = LoadJSArrayElementsMap(kind, native_context);
Node* const length = smi_zero; // Shortcut for {limit} == 0.
Node* const capacity = IntPtrConstant(0); GotoIf(WordEqual<Object, Object>(limit_number, smi_zero),
Node* const result = AllocateJSArray(kind, array_map, capacity, length); &return_empty_array);
args.PopAndReturn(result);
BIND(&next);
}
// ECMA-262 says that if {separator} is undefined, the result should // ECMA-262 says that if {separator} is undefined, the result should
// be an array of size 1 containing the entire string. // be an array of size 1 containing the entire string.
...@@ -1882,12 +1942,14 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) { ...@@ -1882,12 +1942,14 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
// If the separator string is empty then return the elements in the subject. // If the separator string is empty then return the elements in the subject.
{ {
Label next(this); Label next(this);
GotoIfNot(SmiEqual(LoadStringLengthAsSmi(separator_string), SmiConstant(0)), GotoIfNot(SmiEqual(LoadStringLengthAsSmi(separator_string), smi_zero),
&next); &next);
Node* const result = CallRuntime(Runtime::kStringToArray, context, TNode<Smi> subject_length = LoadStringLengthAsSmi(subject_string);
subject_string, limit_number); GotoIf(SmiEqual(subject_length, smi_zero), &return_empty_array);
args.PopAndReturn(result);
args.PopAndReturn(
StringToArray(context, subject_string, subject_length, limit_number));
BIND(&next); BIND(&next);
} }
...@@ -1896,6 +1958,19 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) { ...@@ -1896,6 +1958,19 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
CallRuntime(Runtime::kStringSplit, context, subject_string, CallRuntime(Runtime::kStringSplit, context, subject_string,
separator_string, limit_number); separator_string, limit_number);
args.PopAndReturn(result); args.PopAndReturn(result);
BIND(&return_empty_array);
{
const ElementsKind kind = PACKED_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);
args.PopAndReturn(result);
}
} }
// ES6 #sec-string.prototype.substr // ES6 #sec-string.prototype.substr
......
...@@ -76,6 +76,11 @@ class StringBuiltinsAssembler : public CodeStubAssembler { ...@@ -76,6 +76,11 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
TNode<Smi> IndexOfDollarChar(Node* const context, Node* const string); TNode<Smi> IndexOfDollarChar(Node* const context, Node* const string);
TNode<JSArray> StringToArray(TNode<Context> context,
TNode<String> subject_string,
TNode<Smi> subject_length,
TNode<Number> limit_number);
void RequireObjectCoercible(Node* const context, Node* const value, void RequireObjectCoercible(Node* const context, Node* const value,
const char* method_name); const char* method_name);
......
...@@ -116,6 +116,21 @@ RUNTIME_FUNCTION(Runtime_ConstructConsString) { ...@@ -116,6 +116,21 @@ RUNTIME_FUNCTION(Runtime_ConstructConsString) {
return *isolate->factory()->NewConsString(left, right, length, kIsOneByte); return *isolate->factory()->NewConsString(left, right, length, kIsOneByte);
} }
RUNTIME_FUNCTION(Runtime_ConstructSlicedString) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(String, string, 0);
CONVERT_ARG_HANDLE_CHECKED(Smi, index, 1);
CHECK(string->IsOneByteRepresentation());
CHECK_LT(index->value(), string->length());
Handle<String> sliced_string = isolate->factory()->NewSubString(
string, index->value(), string->length());
CHECK(sliced_string->IsSlicedString());
return *sliced_string;
}
RUNTIME_FUNCTION(Runtime_DeoptimizeFunction) { RUNTIME_FUNCTION(Runtime_DeoptimizeFunction) {
HandleScope scope(isolate); HandleScope scope(isolate);
DCHECK_EQ(1, args.length()); DCHECK_EQ(1, args.length());
......
...@@ -520,6 +520,7 @@ namespace internal { ...@@ -520,6 +520,7 @@ namespace internal {
F(ClearFunctionFeedback, 1, 1) \ F(ClearFunctionFeedback, 1, 1) \
F(CompleteInobjectSlackTracking, 1, 1) \ F(CompleteInobjectSlackTracking, 1, 1) \
F(ConstructConsString, 2, 1) \ F(ConstructConsString, 2, 1) \
F(ConstructSlicedString, 2, 1) \
F(ConstructDouble, 2, 1) \ F(ConstructDouble, 2, 1) \
F(DebugPrint, 1, 1) \ F(DebugPrint, 1, 1) \
F(DebugTrace, 0, 1) \ F(DebugTrace, 0, 1) \
......
...@@ -25,6 +25,8 @@ ...@@ -25,6 +25,8 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax
expected = ["A", undefined, "B", "bold", "/", "B", "and", undefined, "CODE", "coded", "/", "CODE", ""]; expected = ["A", undefined, "B", "bold", "/", "B", "and", undefined, "CODE", "coded", "/", "CODE", ""];
result = "A<B>bold</B>and<CODE>coded</CODE>".split(/<(\/)?([^<>]+)>/); result = "A<B>bold</B>and<CODE>coded</CODE>".split(/<(\/)?([^<>]+)>/);
assertArrayEquals(expected, result); assertArrayEquals(expected, result);
...@@ -134,6 +136,11 @@ assertEquals(["a", "b", "c"], "abc".split("", numberObj(3))); ...@@ -134,6 +136,11 @@ assertEquals(["a", "b", "c"], "abc".split("", numberObj(3)));
assertEquals(["a", "b", "c"], "abc".split("", 4)); assertEquals(["a", "b", "c"], "abc".split("", 4));
assertEquals(["a", "b", "c"], "abc".split("", numberObj(4))); assertEquals(["a", "b", "c"], "abc".split("", numberObj(4)));
// Check if split works also for sliced strings.
let sliced_string = %ConstructSlicedString("abcdefghijklmnopqrstuvwxyz", 13);
assertEquals("nopqrstuvwxyz".split(""), sliced_string.split(""));
// Invoke twice for caching
assertEquals("nopqrstuvwxyz".split(""), sliced_string.split(""));
var all_ascii_codes = []; var all_ascii_codes = [];
for (var i = 0; i < 128; i++) all_ascii_codes[i] = i; for (var i = 0; i < 128; i++) all_ascii_codes[i] = i;
...@@ -174,3 +181,5 @@ assertArrayEquals(["a", "b"], "a,b,c,d,e,f".split(/,/, -4294967294)); ...@@ -174,3 +181,5 @@ assertArrayEquals(["a", "b"], "a,b,c,d,e,f".split(/,/, -4294967294));
assertArrayEquals(["a", "b", "c"], "a,b,c,d,e,f".split(/,/, -4294967293)); assertArrayEquals(["a", "b", "c"], "a,b,c,d,e,f".split(/,/, -4294967293));
assertArrayEquals(["a", "b", "c", "d"], "a,b,c,d,e,f".split(/,/, -4294967292)); assertArrayEquals(["a", "b", "c", "d"], "a,b,c,d,e,f".split(/,/, -4294967292));
assertArrayEquals(["a", "b", "c", "d", "e", "f"], "a,b,c,d,e,f".split(/,/, -1)); assertArrayEquals(["a", "b", "c", "d", "e", "f"], "a,b,c,d,e,f".split(/,/, -1));
assertArrayEquals(["a", "b", "c"], "abc".split("", 0xffffffff));
assertArrayEquals(["\u0427", "\u0427"], "\u0427\u0427".split("", 0xffffffff));
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