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) {
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 )
TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
const int kSeparatorArg = 0;
......@@ -1811,9 +1883,9 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
Node* const receiver = args.GetReceiver();
Node* const separator = args.GetOptionalArgumentValue(kSeparatorArg);
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");
......@@ -1833,29 +1905,17 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
// String and integer conversions.
Node* const subject_string = ToString_Inline(context, receiver);
TNode<Number> const limit_number = Select<Number>(
TNode<String> subject_string = ToString_Inline(context, receiver);
TNode<Number> limit_number = Select<Number>(
IsUndefined(limit), [=] { return NumberConstant(kMaxUInt32); },
[=] { return ToUint32(context, limit); });
Node* const separator_string = ToString_Inline(context, separator);
// Shortcut for {limit} == 0.
{
Label next(this);
GotoIfNot(WordEqual(limit_number, smi_zero), &next);
Label return_empty_array(this);
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);
BIND(&next);
}
// Shortcut for {limit} == 0.
GotoIf(WordEqual<Object, Object>(limit_number, smi_zero),
&return_empty_array);
// ECMA-262 says that if {separator} is undefined, the result should
// be an array of size 1 containing the entire string.
......@@ -1882,12 +1942,14 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
// If the separator string is empty then return the elements in the subject.
{
Label next(this);
GotoIfNot(SmiEqual(LoadStringLengthAsSmi(separator_string), SmiConstant(0)),
GotoIfNot(SmiEqual(LoadStringLengthAsSmi(separator_string), smi_zero),
&next);
Node* const result = CallRuntime(Runtime::kStringToArray, context,
subject_string, limit_number);
args.PopAndReturn(result);
TNode<Smi> subject_length = LoadStringLengthAsSmi(subject_string);
GotoIf(SmiEqual(subject_length, smi_zero), &return_empty_array);
args.PopAndReturn(
StringToArray(context, subject_string, subject_length, limit_number));
BIND(&next);
}
......@@ -1896,6 +1958,19 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
CallRuntime(Runtime::kStringSplit, context, subject_string,
separator_string, limit_number);
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
......
......@@ -76,6 +76,11 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
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,
const char* method_name);
......
......@@ -116,6 +116,21 @@ RUNTIME_FUNCTION(Runtime_ConstructConsString) {
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) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
......
......@@ -520,6 +520,7 @@ namespace internal {
F(ClearFunctionFeedback, 1, 1) \
F(CompleteInobjectSlackTracking, 1, 1) \
F(ConstructConsString, 2, 1) \
F(ConstructSlicedString, 2, 1) \
F(ConstructDouble, 2, 1) \
F(DebugPrint, 1, 1) \
F(DebugTrace, 0, 1) \
......
......@@ -25,6 +25,8 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// 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", ""];
result = "A<B>bold</B>and<CODE>coded</CODE>".split(/<(\/)?([^<>]+)>/);
assertArrayEquals(expected, result);
......@@ -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("", 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 = [];
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));
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", "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