diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 977c6ed933..716d6d84b8 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -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 */ \ diff --git a/src/builtins/builtins-string-gen.cc b/src/builtins/builtins-string-gen.cc index 90e4d3f542..04d956d861 100644 --- a/src/builtins/builtins-string-gen.cc +++ b/src/builtins/builtins-string-gen.cc @@ -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(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 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(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); diff --git a/src/builtins/builtins-string-gen.h b/src/builtins/builtins-string-gen.h index 851fab6181..607f7b6acb 100644 --- a/src/builtins/builtins-string-gen.h +++ b/src/builtins/builtins-string-gen.h @@ -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 get_character); +}; + } // namespace internal } // namespace v8 diff --git a/src/builtins/builtins-string.cc b/src/builtins/builtins-string.cc index 47874aba96..4e3058c220 100644 --- a/src/builtins/builtins-string.cc +++ b/src/builtins/builtins-string.cc @@ -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 { diff --git a/src/debug/debug-evaluate.cc b/src/debug/debug-evaluate.cc index e4b4519b7f..cb351020c7 100644 --- a/src/debug/debug-evaluate.cc +++ b/src/debug/debug-evaluate.cc @@ -284,6 +284,7 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) { V(StringIndexOf) \ V(StringIncludes) \ V(StringReplaceOneCharWithString) \ + V(StringTrim) \ V(SubString) \ V(RegExpInternalReplace) \ /* Literals */ \ diff --git a/src/runtime/runtime-strings.cc b/src/runtime/runtime-strings.cc index b5c09b94a9..1b0f67a8d3 100644 --- a/src/runtime/runtime-strings.cc +++ b/src/runtime/runtime-strings.cc @@ -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 = args.at(0); + CONVERT_SMI_ARG_CHECKED(mode, 1); + String::TrimMode trim_mode = static_cast(mode); + return *String::Trim(string, trim_mode); +} + // ES6 #sec-string.prototype.includes // String.prototype.includes(searchString [, position]) RUNTIME_FUNCTION(Runtime_StringIncludes) { diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 0359955ea7..49a569874a 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -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) \ diff --git a/test/cctest/test-code-stub-assembler.cc b/test/cctest/test-code-stub-assembler.cc index 6750e1f86f..164466f161 100644 --- a/test/cctest/test-code-stub-assembler.cc +++ b/test/cctest/test-code-stub-assembler.cc @@ -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 true_value = ft.true_value(); + Handle false_value = ft.false_value(); + + for (uc16 c = 0; c < 0xFFFF; c++) { + Handle expected_value = + WhiteSpaceOrLineTerminator::Is(c) ? true_value : false_value; + ft.CheckCall(expected_value, handle(Smi::FromInt(c), isolate)); + } +} + } // namespace compiler } // namespace internal } // namespace v8 diff --git a/test/mjsunit/messages.js b/test/mjsunit/messages.js index a57e1fcf1c..9fb4f5af55 100644 --- a/test/mjsunit/messages.js +++ b/test/mjsunit/messages.js @@ -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() {