[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:
parent
54a3027033
commit
7493802ca5
@ -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 */ \
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -284,6 +284,7 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) {
|
||||
V(StringIndexOf) \
|
||||
V(StringIncludes) \
|
||||
V(StringReplaceOneCharWithString) \
|
||||
V(StringTrim) \
|
||||
V(SubString) \
|
||||
V(RegExpInternalReplace) \
|
||||
/* Literals */ \
|
||||
|
@ -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) {
|
||||
|
@ -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) \
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user