diff --git a/BUILD.gn b/BUILD.gn index 8e52daf32a..b824f596e4 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1221,8 +1221,9 @@ torque_files = [ "src/builtins/string-replaceall.tq", "src/builtins/string-slice.tq", "src/builtins/string-startswith.tq", - "src/builtins/string-substring.tq", "src/builtins/string-substr.tq", + "src/builtins/string-substring.tq", + "src/builtins/string-trim.tq", "src/builtins/symbol.tq", "src/builtins/torque-internal.tq", "src/builtins/typed-array-createtypedarray.tq", diff --git a/src/builtins/base.tq b/src/builtins/base.tq index 8dde031e04..e068cfa9d1 100644 --- a/src/builtins/base.tq +++ b/src/builtins/base.tq @@ -834,6 +834,10 @@ extern operator '==' macro ConstexprInt31Equal(constexpr int31, constexpr int31): constexpr bool; extern operator '!=' macro ConstexprInt31NotEqual(constexpr int31, constexpr int31): constexpr bool; +extern operator '==' macro +ConstexprUint32Equal(constexpr uint32, constexpr uint32): constexpr bool; +extern operator '!=' macro +ConstexprUint32NotEqual(constexpr uint32, constexpr uint32): constexpr bool; extern operator '>=' macro ConstexprInt31GreaterThanEqual( constexpr int31, constexpr int31): constexpr bool; diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 27e9cdc0f4..a30520d150 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -763,9 +763,6 @@ namespace internal { TFJ(StringPrototypeSearch, 1, kReceiver, kRegexp) \ /* ES6 #sec-string.prototype.split */ \ TFJ(StringPrototypeSplit, kDontAdaptArgumentsSentinel) \ - TFJ(StringPrototypeTrim, kDontAdaptArgumentsSentinel) \ - TFJ(StringPrototypeTrimEnd, kDontAdaptArgumentsSentinel) \ - TFJ(StringPrototypeTrimStart, kDontAdaptArgumentsSentinel) \ /* ES6 #sec-string.raw */ \ CPP(StringRaw) \ \ diff --git a/src/builtins/builtins-string-gen.cc b/src/builtins/builtins-string-gen.cc index b031f4ee09..8f62bac462 100644 --- a/src/builtins/builtins-string-gen.cc +++ b/src/builtins/builtins-string-gen.cc @@ -1803,178 +1803,6 @@ TF_BUILTIN(StringSubstring, StringBuiltinsAssembler) { Return(SubString(string, from, to)); } -// ES6 #sec-string.prototype.trim -TF_BUILTIN(StringPrototypeTrim, StringTrimAssembler) { - TNode argc = ChangeInt32ToIntPtr( - UncheckedParameter(Descriptor::kJSActualArgumentsCount)); - auto context = Parameter(Descriptor::kContext); - - Generate(String::kTrim, "String.prototype.trim", argc, context); -} - -// https://github.com/tc39/proposal-string-left-right-trim -TF_BUILTIN(StringPrototypeTrimStart, StringTrimAssembler) { - TNode argc = ChangeInt32ToIntPtr( - UncheckedParameter(Descriptor::kJSActualArgumentsCount)); - auto context = Parameter(Descriptor::kContext); - - Generate(String::kTrimStart, "String.prototype.trimLeft", argc, context); -} - -// https://github.com/tc39/proposal-string-left-right-trim -TF_BUILTIN(StringPrototypeTrimEnd, StringTrimAssembler) { - TNode argc = ChangeInt32ToIntPtr( - UncheckedParameter(Descriptor::kJSActualArgumentsCount)); - auto context = Parameter(Descriptor::kContext); - - Generate(String::kTrimEnd, "String.prototype.trimRight", argc, context); -} - -void StringTrimAssembler::Generate(String::TrimMode mode, - const char* method_name, TNode argc, - TNode context) { - Label return_emptystring(this), if_runtime(this); - - CodeStubArguments arguments(this, argc); - TNode receiver = arguments.GetReceiver(); - - // Check that {receiver} is coercible to Object and convert it to a String. - const TNode string = ToThisString(context, receiver, method_name); - const TNode string_length = LoadStringLengthAsWord(string); - - ToDirectStringAssembler to_direct(state(), string); - to_direct.TryToDirect(&if_runtime); - const TNode string_data = to_direct.PointerToData(&if_runtime); - const TNode instance_type = to_direct.instance_type(); - const TNode is_stringonebyte = - IsOneByteStringInstanceType(instance_type); - const TNode string_data_offset = to_direct.offset(); - - TVARIABLE(IntPtrT, var_start, IntPtrConstant(0)); - TVARIABLE(IntPtrT, var_end, IntPtrSub(string_length, IntPtrConstant(1))); - - if (mode == String::kTrimStart || mode == String::kTrim) { - ScanForNonWhiteSpaceOrLineTerminator(string_data, string_data_offset, - is_stringonebyte, &var_start, - string_length, 1, &return_emptystring); - } - if (mode == String::kTrimEnd || mode == String::kTrim) { - ScanForNonWhiteSpaceOrLineTerminator( - string_data, string_data_offset, is_stringonebyte, &var_end, - IntPtrConstant(-1), -1, &return_emptystring); - } - - arguments.PopAndReturn( - SubString(string, var_start.value(), - IntPtrAdd(var_end.value(), IntPtrConstant(1)))); - - BIND(&if_runtime); - arguments.PopAndReturn( - CallRuntime(Runtime::kStringTrim, context, string, SmiConstant(mode))); - - BIND(&return_emptystring); - arguments.PopAndReturn(EmptyStringConstant()); -} - -void StringTrimAssembler::ScanForNonWhiteSpaceOrLineTerminator( - const TNode string_data, const TNode string_data_offset, - const TNode is_stringonebyte, TVariable* const var_index, - const TNode 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, - [&](const TNode index) { - return Load( - string_data, - WordShl(IntPtrAdd(index, string_data_offset), IntPtrConstant(1))); - }); - - BIND(&if_stringisonebyte); - BuildLoop(var_index, end, increment, if_none_found, &out, - [&](const TNode index) { - return Load(string_data, - IntPtrAdd(index, string_data_offset)); - }); - - BIND(&out); -} - -template -void StringTrimAssembler::BuildLoop( - TVariable* const var_index, const TNode end, - int increment, Label* const if_none_found, Label* const out, - const std::function(const TNode)>& get_character) { - Label loop(this, var_index); - Goto(&loop); - BIND(&loop); - { - TNode 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( - const TNode 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); -} // Return the |word32| codepoint at {index}. Supports SeqStrings and // ExternalStrings. diff --git a/src/builtins/builtins-string-gen.h b/src/builtins/builtins-string-gen.h index 2b4dadbbb0..402cbc871e 100644 --- a/src/builtins/builtins-string-gen.h +++ b/src/builtins/builtins-string-gen.h @@ -184,30 +184,6 @@ class StringIncludesIndexOfAssembler : public StringBuiltinsAssembler { TNode context); }; -class StringTrimAssembler : public StringBuiltinsAssembler { - public: - explicit StringTrimAssembler(compiler::CodeAssemblerState* state) - : StringBuiltinsAssembler(state) {} - - V8_EXPORT_PRIVATE void GotoIfNotWhiteSpaceOrLineTerminator( - const TNode char_code, Label* const if_not_whitespace); - - protected: - void Generate(String::TrimMode mode, const char* method, TNode argc, - TNode context); - - void ScanForNonWhiteSpaceOrLineTerminator( - const TNode string_data, const TNode string_data_offset, - const TNode is_stringonebyte, TVariable* const var_index, - const TNode end, int increment, Label* const if_none_found); - - template - void BuildLoop( - TVariable* const var_index, const TNode end, - int increment, Label* const if_none_found, Label* const out, - const std::function(const TNode)>& get_character); -}; - } // namespace internal } // namespace v8 diff --git a/src/builtins/convert.tq b/src/builtins/convert.tq index 6ac9901028..6f6cbb1f68 100644 --- a/src/builtins/convert.tq +++ b/src/builtins/convert.tq @@ -115,6 +115,11 @@ FromConstexpr( return %RawDownCast(Unsigned(%FromConstexpr(c))); } +FromConstexpr( + c: string::constexpr TrimMode): string::TrimMode { + return %RawDownCast(Unsigned(%FromConstexpr(c))); +} + macro Convert(i: From): To { return i; } diff --git a/src/builtins/string-trim.tq b/src/builtins/string-trim.tq new file mode 100644 index 0000000000..eef0ccd84f --- /dev/null +++ b/src/builtins/string-trim.tq @@ -0,0 +1,168 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include 'src/builtins/builtins-string-gen.h' + +namespace string { + +extern enum TrimMode extends uint31 constexpr 'String::TrimMode' { + kTrim, + kTrimStart, + kTrimEnd +} + +@export +macro IsWhiteSpaceOrLineTerminator(charCode: int32): bool { + // 0x0020 - SPACE (Intentionally out of order to fast path a commmon case) + if (charCode == Int32Constant(0x0020)) { + return true; + } + + // 0x0009 - HORIZONTAL TAB + if (charCode < Int32Constant(0x0009)) { + return false; + } + // 0x000A - LINE FEED OR NEW LINE + // 0x000B - VERTICAL TAB + // 0x000C - FORMFEED + // 0x000D - HORIZONTAL TAB + if (charCode <= Int32Constant(0x000D)) { + return true; + } + + // Common Non-whitespace characters + if (charCode < Int32Constant(0x00A0)) { + return false; + } + + // 0x00A0 - NO-BREAK SPACE + if (charCode == Int32Constant(0x00A0)) { + return true; + } + + // 0x1680 - Ogham Space Mark + if (charCode == Int32Constant(0x1680)) { + return true; + } + + // 0x2000 - EN QUAD + if (charCode < Int32Constant(0x2000)) { + return false; + } + // 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 + if (charCode <= Int32Constant(0x200A)) { + return true; + } + + // 0x2028 - LINE SEPARATOR + if (charCode == Int32Constant(0x2028)) { + return true; + } + // 0x2029 - PARAGRAPH SEPARATOR + if (charCode == Int32Constant(0x2029)) { + return true; + } + // 0x202F - NARROW NO-BREAK SPACE + if (charCode == Int32Constant(0x202F)) { + return true; + } + // 0x205F - MEDIUM MATHEMATICAL SPACE + if (charCode == Int32Constant(0x205F)) { + return true; + } + // 0xFEFF - BYTE ORDER MARK + if (charCode == Int32Constant(0xFEFF)) { + return true; + } + // 0x3000 - IDEOGRAPHIC SPACE + if (charCode == Int32Constant(0x3000)) { + return true; + } + + return false; +} + +transitioning macro StringTrim(implicit context: Context)( + receiver: JSAny, _arguments: Arguments, methodName: constexpr string, + variant: constexpr TrimMode): String { + const receiverString: String = ToThisString(receiver, methodName); + const stringLength: intptr = receiverString.length_intptr; + + const directString = Cast(receiverString) + otherwise return runtime::StringTrim( + receiverString, SmiTag(variant)); + + let startIndex: intptr = 0; + let endIndex: intptr = stringLength - 1; + + // TODO(duongn): It would probably be more efficient to turn StringTrim into a + // tempalate for the different string types and specialize the loop for them. + if (variant == TrimMode::kTrim || variant == TrimMode::kTrimStart) { + while (true) { + if (startIndex == stringLength) { + return EmptyStringConstant(); + } + if (!IsWhiteSpaceOrLineTerminator( + StringCharCodeAt(directString, Unsigned(startIndex)))) { + break; + } + startIndex++; + } + } + + if (variant == TrimMode::kTrim || variant == TrimMode::kTrimEnd) { + while (true) { + if (endIndex == -1) { + return EmptyStringConstant(); + } + if (!IsWhiteSpaceOrLineTerminator( + StringCharCodeAt(directString, Unsigned(endIndex)))) { + break; + } + endIndex--; + } + } + + return SubString( + receiverString, Unsigned(startIndex), Unsigned(endIndex + 1)); +} + +// ES6 #sec-string.prototype.trim +transitioning javascript builtin +StringPrototypeTrim( + js-implicit context: NativeContext, receiver: JSAny)(...arguments): String { + const methodName: constexpr string = 'String.prototype.trim'; + return StringTrim(receiver, arguments, methodName, TrimMode::kTrim); +} + +// https://github.com/tc39/proposal-string-left-right-trim +transitioning javascript builtin +StringPrototypeTrimStart( + js-implicit context: NativeContext, receiver: JSAny)(...arguments): String { + const methodName: constexpr string = 'String.prototype.trimLeft'; + return StringTrim(receiver, arguments, methodName, TrimMode::kTrimStart); +} + +// https://github.com/tc39/proposal-string-left-right-trim +transitioning javascript builtin +StringPrototypeTrimEnd( + js-implicit context: NativeContext, receiver: JSAny)(...arguments): String { + const methodName: constexpr string = 'String.prototype.trimRight'; + return StringTrim(receiver, arguments, methodName, TrimMode::kTrimEnd); +} +} + +namespace runtime { +extern runtime StringTrim(implicit context: Context)( + String, SmiTagged): String; +} diff --git a/src/codegen/code-stub-assembler.h b/src/codegen/code-stub-assembler.h index 52e58699bf..f92fbd9dd7 100644 --- a/src/codegen/code-stub-assembler.h +++ b/src/codegen/code-stub-assembler.h @@ -3481,6 +3481,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler bool ConstexprInt31Equal(int31_t a, int31_t b) { return a == b; } bool ConstexprInt31NotEqual(int31_t a, int31_t b) { return a != b; } bool ConstexprInt31GreaterThanEqual(int31_t a, int31_t b) { return a >= b; } + bool ConstexprUint32Equal(uint32_t a, uint32_t b) { return a == b; } + bool ConstexprUint32NotEqual(uint32_t a, uint32_t b) { return a != b; } bool ConstexprInt32Equal(int32_t a, int32_t b) { return a == b; } bool ConstexprInt32NotEqual(int32_t a, int32_t b) { return a != b; } bool ConstexprInt32GreaterThanEqual(int32_t a, int32_t b) { return a >= b; } diff --git a/test/cctest/test-code-stub-assembler.cc b/test/cctest/test-code-stub-assembler.cc index 8cf844f100..fe7aebe682 100644 --- a/test/cctest/test-code-stub-assembler.cc +++ b/test/cctest/test-code-stub-assembler.cc @@ -3135,21 +3135,20 @@ TEST(LoadJSArrayElementsMap) { } } -TEST(GotoIfNotWhiteSpaceOrLineTerminator) { +TEST(IsWhiteSpaceOrLineTerminator) { Isolate* isolate(CcTest::InitIsolateOnce()); const int kNumParams = 1; CodeAssemblerTester asm_tester(isolate, kNumParams + 1); // Include receiver. - StringTrimAssembler m(asm_tester.state()); { // Returns true if whitespace, false otherwise. - Label if_not_whitespace(&m); - - m.GotoIfNotWhiteSpaceOrLineTerminator(m.SmiToInt32(m.Parameter(1)), - &if_not_whitespace); + CodeStubAssembler m(asm_tester.state()); + Label if_true(&m), if_false(&m); + m.Branch(m.IsWhiteSpaceOrLineTerminator(m.SmiToInt32(m.Parameter(1))), + &if_true, &if_false); + m.BIND(&if_true); m.Return(m.TrueConstant()); - - m.BIND(&if_not_whitespace); + m.BIND(&if_false); m.Return(m.FalseConstant()); } FunctionTester ft(asm_tester.GenerateCode(), kNumParams);