[builtins] Port String.prototype.{trim, trimLeft, trimRight} to CSA

- Convert S.p.{trim, trimLeft, trimRight} to TFJ
  - Fast paths for one/two byte strings
  - Added StringTrimAssembler
- Added helper kStringTrim runtime to handle slow paths

Quick measurements show >2.7x improvement:
https://github.com/peterwmwong/v8-perf/tree/master/string-trim

Bug: v8:6680
Change-Id: I79929129aa3d5dea20f094d648afe46adbf61a49
Reviewed-on: https://chromium-review.googlesource.com/647647
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47853}
This commit is contained in:
peterwmwong 2017-09-04 21:19:29 -05:00 committed by Commit Bot
parent 54a3027033
commit 7493802ca5
9 changed files with 245 additions and 26 deletions

View File

@ -918,9 +918,11 @@ namespace internal {
CPP(StringPrototypeStartsWith) \
/* ES6 #sec-string.prototype.tostring */ \
TFJ(StringPrototypeToString, 0) \
CPP(StringPrototypeTrim) \
CPP(StringPrototypeTrimLeft) \
CPP(StringPrototypeTrimRight) \
TFJ(StringPrototypeTrim, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
TFJ(StringPrototypeTrimLeft, \
SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
TFJ(StringPrototypeTrimRight, \
SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* ES6 #sec-string.prototype.valueof */ \
TFJ(StringPrototypeValueOf, 0) \
/* ES6 #sec-string.prototype-@@iterator */ \

View File

@ -1719,6 +1719,167 @@ TF_BUILTIN(StringPrototypeSubstring, StringBuiltinsAssembler) {
}
}
// ES6 #sec-string.prototype.trim
TF_BUILTIN(StringPrototypeTrim, StringTrimAssembler) {
Generate(String::kTrim, "String.prototype.trim");
}
// Non-standard WebKit extension
TF_BUILTIN(StringPrototypeTrimLeft, StringTrimAssembler) {
Generate(String::kTrimLeft, "String.prototype.trimLeft");
}
// Non-standard WebKit extension
TF_BUILTIN(StringPrototypeTrimRight, StringTrimAssembler) {
Generate(String::kTrimRight, "String.prototype.trimRight");
}
void StringTrimAssembler::Generate(String::TrimMode mode,
const char* method_name) {
Label return_emptystring(this), if_runtime(this);
Node* const argc = Parameter(BuiltinDescriptor::kArgumentsCount);
Node* const context = Parameter(BuiltinDescriptor::kContext);
CodeStubArguments arguments(this, ChangeInt32ToIntPtr(argc));
Node* const receiver = arguments.GetReceiver();
// Check that {receiver} is coercible to Object and convert it to a String.
Node* const string = ToThisString(context, receiver, method_name);
Node* const string_length = SmiUntag(LoadStringLength(string));
ToDirectStringAssembler to_direct(state(), string);
to_direct.TryToDirect(&if_runtime);
Node* const string_data = to_direct.PointerToData(&if_runtime);
Node* const instance_type = to_direct.instance_type();
Node* const is_stringonebyte = IsOneByteStringInstanceType(instance_type);
Node* const string_data_offset = to_direct.offset();
VARIABLE(var_start, MachineType::PointerRepresentation(), IntPtrConstant(0));
VARIABLE(var_end, MachineType::PointerRepresentation(),
IntPtrSub(string_length, IntPtrConstant(1)));
if (mode == String::kTrimLeft || mode == String::kTrim) {
ScanForNonWhiteSpaceOrLineTerminator(string_data, string_data_offset,
is_stringonebyte, &var_start,
string_length, 1, &return_emptystring);
}
if (mode == String::kTrimRight || mode == String::kTrim) {
ScanForNonWhiteSpaceOrLineTerminator(
string_data, string_data_offset, is_stringonebyte, &var_end,
IntPtrConstant(-1), -1, &return_emptystring);
}
arguments.PopAndReturn(
SubString(context, string, SmiTag(var_start.value()),
SmiAdd(SmiTag(var_end.value()), SmiConstant(1)),
SubStringFlags::FROM_TO_ARE_BOUNDED));
BIND(&if_runtime);
arguments.PopAndReturn(CallRuntime(Runtime::kStringTrim, context, string,
SmiConstant(static_cast<int>(mode))));
BIND(&return_emptystring);
arguments.PopAndReturn(EmptyStringConstant());
}
void StringTrimAssembler::ScanForNonWhiteSpaceOrLineTerminator(
Node* const string_data, Node* const string_data_offset,
Node* const is_stringonebyte, Variable* const var_index, Node* const end,
int increment, Label* const if_none_found) {
Label if_stringisonebyte(this), out(this);
GotoIf(is_stringonebyte, &if_stringisonebyte);
// Two Byte String
BuildLoop(
var_index, end, increment, if_none_found, &out, [&](Node* const index) {
return Load(
MachineType::Uint16(), string_data,
WordShl(IntPtrAdd(index, string_data_offset), IntPtrConstant(1)));
});
BIND(&if_stringisonebyte);
BuildLoop(var_index, end, increment, if_none_found, &out,
[&](Node* const index) {
return Load(MachineType::Uint8(), string_data,
IntPtrAdd(index, string_data_offset));
});
BIND(&out);
}
void StringTrimAssembler::BuildLoop(Variable* const var_index, Node* const end,
int increment, Label* const if_none_found,
Label* const out,
std::function<Node*(Node*)> get_character) {
Label loop(this, var_index);
Goto(&loop);
BIND(&loop);
{
Node* const index = var_index->value();
GotoIf(IntPtrEqual(index, end), if_none_found);
GotoIfNotWhiteSpaceOrLineTerminator(
UncheckedCast<Uint32T>(get_character(index)), out);
Increment(var_index, increment);
Goto(&loop);
}
}
void StringTrimAssembler::GotoIfNotWhiteSpaceOrLineTerminator(
Node* const char_code, Label* const if_not_whitespace) {
Label out(this);
// 0x0020 - SPACE (Intentionally out of order to fast path a commmon case)
GotoIf(Word32Equal(char_code, Int32Constant(0x0020)), &out);
// 0x0009 - HORIZONTAL TAB
GotoIf(Uint32LessThan(char_code, Int32Constant(0x0009)), if_not_whitespace);
// 0x000A - LINE FEED OR NEW LINE
// 0x000B - VERTICAL TAB
// 0x000C - FORMFEED
// 0x000D - HORIZONTAL TAB
GotoIf(Uint32LessThanOrEqual(char_code, Int32Constant(0x000D)), &out);
// Common Non-whitespace characters
GotoIf(Uint32LessThan(char_code, Int32Constant(0x00A0)), if_not_whitespace);
// 0x00A0 - NO-BREAK SPACE
GotoIf(Word32Equal(char_code, Int32Constant(0x00A0)), &out);
// 0x1680 - Ogham Space Mark
GotoIf(Word32Equal(char_code, Int32Constant(0x1680)), &out);
// 0x2000 - EN QUAD
GotoIf(Uint32LessThan(char_code, Int32Constant(0x2000)), if_not_whitespace);
// 0x2001 - EM QUAD
// 0x2002 - EN SPACE
// 0x2003 - EM SPACE
// 0x2004 - THREE-PER-EM SPACE
// 0x2005 - FOUR-PER-EM SPACE
// 0x2006 - SIX-PER-EM SPACE
// 0x2007 - FIGURE SPACE
// 0x2008 - PUNCTUATION SPACE
// 0x2009 - THIN SPACE
// 0x200A - HAIR SPACE
GotoIf(Uint32LessThanOrEqual(char_code, Int32Constant(0x200A)), &out);
// 0x2028 - LINE SEPARATOR
GotoIf(Word32Equal(char_code, Int32Constant(0x2028)), &out);
// 0x2029 - PARAGRAPH SEPARATOR
GotoIf(Word32Equal(char_code, Int32Constant(0x2029)), &out);
// 0x202F - NARROW NO-BREAK SPACE
GotoIf(Word32Equal(char_code, Int32Constant(0x202F)), &out);
// 0x205F - MEDIUM MATHEMATICAL SPACE
GotoIf(Word32Equal(char_code, Int32Constant(0x205F)), &out);
// 0xFEFF - BYTE ORDER MARK
GotoIf(Word32Equal(char_code, Int32Constant(0xFEFF)), &out);
// 0x3000 - IDEOGRAPHIC SPACE
Branch(Word32Equal(char_code, Int32Constant(0x3000)), &out,
if_not_whitespace);
BIND(&out);
}
// ES6 #sec-string.prototype.tostring
TF_BUILTIN(StringPrototypeToString, CodeStubAssembler) {
Node* context = Parameter(Descriptor::kContext);

View File

@ -102,6 +102,29 @@ class StringIncludesIndexOfAssembler : public StringBuiltinsAssembler {
void Generate(SearchVariant variant);
};
class StringTrimAssembler : public StringBuiltinsAssembler {
public:
explicit StringTrimAssembler(compiler::CodeAssemblerState* state)
: StringBuiltinsAssembler(state) {}
void GotoIfNotWhiteSpaceOrLineTerminator(Node* const char_code,
Label* const if_not_whitespace);
protected:
void Generate(String::TrimMode mode, const char* method);
void ScanForNonWhiteSpaceOrLineTerminator(Node* const string_data,
Node* const string_data_offset,
Node* const is_stringonebyte,
Variable* const var_index,
Node* const end, int increment,
Label* const if_none_found);
void BuildLoop(Variable* const var_index, Node* const end, int increment,
Label* const if_none_found, Label* const out,
std::function<Node*(Node*)> get_character);
};
} // namespace internal
} // namespace v8

View File

@ -317,27 +317,6 @@ BUILTIN(StringPrototypeStartsWith) {
return isolate->heap()->true_value();
}
// ES6 section 21.1.3.27 String.prototype.trim ()
BUILTIN(StringPrototypeTrim) {
HandleScope scope(isolate);
TO_THIS_STRING(string, "String.prototype.trim");
return *String::Trim(string, String::kTrim);
}
// Non-standard WebKit extension
BUILTIN(StringPrototypeTrimLeft) {
HandleScope scope(isolate);
TO_THIS_STRING(string, "String.prototype.trimLeft");
return *String::Trim(string, String::kTrimLeft);
}
// Non-standard WebKit extension
BUILTIN(StringPrototypeTrimRight) {
HandleScope scope(isolate);
TO_THIS_STRING(string, "String.prototype.trimRight");
return *String::Trim(string, String::kTrimRight);
}
#ifndef V8_INTL_SUPPORT
namespace {

View File

@ -284,6 +284,7 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) {
V(StringIndexOf) \
V(StringIncludes) \
V(StringReplaceOneCharWithString) \
V(StringTrim) \
V(SubString) \
V(RegExpInternalReplace) \
/* Literals */ \

View File

@ -137,6 +137,15 @@ RUNTIME_FUNCTION(Runtime_StringReplaceOneCharWithString) {
return isolate->StackOverflow();
}
RUNTIME_FUNCTION(Runtime_StringTrim) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
Handle<String> string = args.at<String>(0);
CONVERT_SMI_ARG_CHECKED(mode, 1);
String::TrimMode trim_mode = static_cast<String::TrimMode>(mode);
return *String::Trim(string, trim_mode);
}
// ES6 #sec-string.prototype.includes
// String.prototype.includes(searchString [, position])
RUNTIME_FUNCTION(Runtime_StringIncludes) {

View File

@ -516,6 +516,7 @@ namespace internal {
F(GetSubstitution, 5, 1) \
F(StringReplaceOneCharWithString, 3, 1) \
F(StringIncludes, 3, 1) \
F(StringTrim, 2, 1) \
F(StringIndexOf, 3, 1) \
F(StringIndexOfUnchecked, 3, 1) \
F(StringLastIndexOf, 2, 1) \

View File

@ -7,6 +7,8 @@
#include "src/api.h"
#include "src/base/utils/random-number-generator.h"
#include "src/builtins/builtins-promise-gen.h"
#include "src/builtins/builtins-string-gen.h"
#include "src/char-predicates.h"
#include "src/code-factory.h"
#include "src/code-stub-assembler.h"
#include "src/compiler/node.h"
@ -2640,6 +2642,35 @@ TEST(AllocateStruct) {
}
}
TEST(GotoIfNotWhiteSpaceOrLineTerminator) {
Isolate* isolate(CcTest::InitIsolateOnce());
const int kNumParams = 1;
CodeAssemblerTester asm_tester(isolate, kNumParams);
StringTrimAssembler m(asm_tester.state());
{ // Returns true if whitespace, false otherwise.
Label if_not_whitespace(&m);
m.GotoIfNotWhiteSpaceOrLineTerminator(m.SmiToWord32(m.Parameter(0)),
&if_not_whitespace);
m.Return(m.TrueConstant());
m.BIND(&if_not_whitespace);
m.Return(m.FalseConstant());
}
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
Handle<Object> true_value = ft.true_value();
Handle<Object> false_value = ft.false_value();
for (uc16 c = 0; c < 0xFFFF; c++) {
Handle<Object> expected_value =
WhiteSpaceOrLineTerminator::Is(c) ? true_value : false_value;
ft.CheckCall(expected_value, handle(Smi::FromInt(c), isolate));
}
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -69,13 +69,25 @@ test(function() {
}, "CreateListFromArrayLike called on non-object", TypeError);
// kCalledOnNullOrUndefined
test(function() {
String.prototype.includes.call(null);
}, "String.prototype.includes called on null or undefined", TypeError);
test(function() {
Array.prototype.shift.call(null);
}, "Array.prototype.shift called on null or undefined", TypeError);
test(function() {
String.prototype.includes.call(null);
}, "String.prototype.includes called on null or undefined", TypeError);
String.prototype.trim.call(null);
}, "String.prototype.trim called on null or undefined", TypeError);
test(function() {
String.prototype.trimLeft.call(null);
}, "String.prototype.trimLeft called on null or undefined", TypeError);
test(function() {
String.prototype.trimRight.call(null);
}, "String.prototype.trimRight called on null or undefined", TypeError);
// kCannotFreezeArrayBufferView
test(function() {