diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index eb9ef4cd19..c0d19c988c 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1992,6 +1992,10 @@ void Genesis::InitializeGlobal(Handle global_object, SimpleInstallFunction(prototype, "normalize", Builtins::kStringPrototypeNormalize, 0, false); #endif // V8_INTL_SUPPORT + SimpleInstallFunction(prototype, "padEnd", Builtins::kStringPrototypePadEnd, + 1, false); + SimpleInstallFunction(prototype, "padStart", + Builtins::kStringPrototypePadStart, 1, false); SimpleInstallFunction(prototype, "repeat", Builtins::kStringPrototypeRepeat, 1, true); SimpleInstallFunction(prototype, "replace", diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 83993c6c00..67d54a2c1a 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -99,6 +99,7 @@ namespace internal { TFS(StringIndexOf, kReceiver, kSearchString, kPosition) \ TFC(StringLessThan, Compare, 1) \ TFC(StringLessThanOrEqual, Compare, 1) \ + TFS(StringRepeat, kString, kCount) \ \ /* OrderedHashTable helpers */ \ TFS(OrderedHashTableHealIndex, kTable, kIndex) \ @@ -939,6 +940,11 @@ namespace internal { TFJ(StringPrototypeMatch, 1, kRegexp) \ /* ES6 #sec-string.prototype.localecompare */ \ CPP(StringPrototypeLocaleCompare) \ + /* ES6 #sec-string.prototype.padEnd */ \ + TFJ(StringPrototypePadEnd, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \ + /* ES6 #sec-string.prototype.padStart */ \ + TFJ(StringPrototypePadStart, \ + SharedFunctionInfo::kDontAdaptArgumentsSentinel) \ /* ES6 #sec-string.prototype.repeat */ \ TFJ(StringPrototypeRepeat, 1, kCount) \ /* ES6 #sec-string.prototype.replace */ \ diff --git a/src/builtins/builtins-string-gen.cc b/src/builtins/builtins-string-gen.cc index 7996ebd108..24abc25b19 100644 --- a/src/builtins/builtins-string-gen.cc +++ b/src/builtins/builtins-string-gen.cc @@ -1200,20 +1200,19 @@ TF_BUILTIN(StringPrototypeRepeat, StringBuiltinsAssembler) { // Verifies a valid count and takes a fast path when the result will be an // empty string. { - Label next(this), if_count_isheapnumber(this, Label::kDeferred); + Label if_count_isheapnumber(this, Label::kDeferred); GotoIfNot(TaggedIsSmi(var_count.value()), &if_count_isheapnumber); // If count is a SMI, throw a RangeError if less than 0 or greater than // the maximum string length. - { - GotoIf(SmiLessThan(var_count.value(), SmiConstant(0)), &invalid_count); - GotoIf(SmiEqual(var_count.value(), SmiConstant(0)), &return_emptystring); - GotoIf(is_stringempty, &return_emptystring); - GotoIf(SmiGreaterThan(var_count.value(), SmiConstant(String::kMaxLength)), - &invalid_string_length); - Goto(&next); - } + GotoIf(SmiLessThan(var_count.value(), SmiConstant(0)), &invalid_count); + GotoIf(SmiEqual(var_count.value(), SmiConstant(0)), &return_emptystring); + GotoIf(is_stringempty, &return_emptystring); + GotoIf(SmiGreaterThan(var_count.value(), SmiConstant(String::kMaxLength)), + &invalid_string_length); + Return(CallBuiltin(Builtins::kStringRepeat, context, string, + var_count.value())); // If count is a Heap Number... // 1) If count is Infinity, throw a RangeError exception @@ -1229,49 +1228,6 @@ TF_BUILTIN(StringPrototypeRepeat, StringBuiltinsAssembler) { &invalid_count); Branch(is_stringempty, &return_emptystring, &invalid_string_length); } - BIND(&next); - } - - // The receiver is repeated with the following algorithm: - // let n = count; - // let power_of_two_repeats = receiver; - // let result = ""; - // while (true) { - // if (n & 1) result += s; - // n >>= 1; - // if (n === 0) return result; - // power_of_two_repeats += power_of_two_repeats; - // } - { - VARIABLE(var_result, MachineRepresentation::kTagged, EmptyStringConstant()); - VARIABLE(var_temp, MachineRepresentation::kTagged, string); - - Callable stringadd_callable = - CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED); - - Label loop(this, {&var_count, &var_result, &var_temp}), return_result(this); - Goto(&loop); - BIND(&loop); - { - { - Label next(this); - GotoIfNot(SmiToWord32(SmiAnd(var_count.value(), SmiConstant(1))), - &next); - var_result.Bind(CallStub(stringadd_callable, context, - var_result.value(), var_temp.value())); - Goto(&next); - BIND(&next); - } - - var_count.Bind(SmiShr(var_count.value(), 1)); - GotoIf(SmiEqual(var_count.value(), SmiConstant(0)), &return_result); - var_temp.Bind(CallStub(stringadd_callable, context, var_temp.value(), - var_temp.value())); - Goto(&loop); - } - - BIND(&return_result); - Return(var_result.value()); } BIND(&return_emptystring); @@ -1291,6 +1247,58 @@ TF_BUILTIN(StringPrototypeRepeat, StringBuiltinsAssembler) { } } +// Helper with less checks +TF_BUILTIN(StringRepeat, StringBuiltinsAssembler) { + Node* const context = Parameter(Descriptor::kContext); + Node* const string = Parameter(Descriptor::kString); + Node* const count = Parameter(Descriptor::kCount); + + CSA_ASSERT(this, IsString(string)); + CSA_ASSERT(this, Word32BinaryNot(IsEmptyString(string))); + CSA_ASSERT(this, TaggedIsPositiveSmi(count)); + CSA_ASSERT(this, SmiLessThanOrEqual(count, SmiConstant(String::kMaxLength))); + + // The string is repeated with the following algorithm: + // let n = count; + // let power_of_two_repeats = string; + // let result = ""; + // while (true) { + // if (n & 1) result += s; + // n >>= 1; + // if (n === 0) return result; + // power_of_two_repeats += power_of_two_repeats; + // } + VARIABLE(var_result, MachineRepresentation::kTagged, EmptyStringConstant()); + VARIABLE(var_temp, MachineRepresentation::kTagged, string); + VARIABLE(var_count, MachineRepresentation::kTagged, count); + + Callable stringadd_callable = + CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED); + + Label loop(this, {&var_count, &var_result, &var_temp}), return_result(this); + Goto(&loop); + BIND(&loop); + { + { + Label next(this); + GotoIfNot(SmiToWord32(SmiAnd(var_count.value(), SmiConstant(1))), &next); + var_result.Bind(CallStub(stringadd_callable, context, var_result.value(), + var_temp.value())); + Goto(&next); + BIND(&next); + } + + var_count.Bind(SmiShr(var_count.value(), 1)); + GotoIf(SmiEqual(var_count.value(), SmiConstant(0)), &return_result); + var_temp.Bind(CallStub(stringadd_callable, context, var_temp.value(), + var_temp.value())); + Goto(&loop); + } + + BIND(&return_result); + Return(var_result.value()); +} + // ES6 #sec-string.prototype.replace TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) { Label out(this); @@ -1531,6 +1539,130 @@ TF_BUILTIN(StringPrototypeMatch, StringMatchSearchAssembler) { Generate(kMatch, "String.prototype.match", receiver, maybe_regexp, context); } +class StringPadAssembler : public StringBuiltinsAssembler { + public: + explicit StringPadAssembler(compiler::CodeAssemblerState* state) + : StringBuiltinsAssembler(state) {} + + protected: + enum Variant { kStart, kEnd }; + + void Generate(Variant variant, const char* method_name) { + Node* const context = Parameter(BuiltinDescriptor::kContext); + Node* argc = + ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount)); + CodeStubArguments arguments(this, argc); + Node* const receiver = arguments.GetReceiver(); + Node* const receiver_string = ToThisString(context, receiver, method_name); + Node* const string_length = LoadStringLength(receiver_string); + + VARIABLE(var_fill_string, MachineRepresentation::kTagged, + StringConstant(" ")); + VARIABLE(var_fill_length, MachineRepresentation::kTagged, SmiConstant(1)); + + Label argc_2(this), dont_pad(this), invalid_string_length(this), pad(this); + + // If no max_length was provided, return the string. + GotoIf(IntPtrEqual(argc, IntPtrConstant(0)), &dont_pad); + + Node* const max_length = ToLength_Inline(context, arguments.AtIndex(0)); + CSA_ASSERT(this, IsNumberNormalized(max_length)); + + // Throw if max_length is not a smi or greater than the max string length. + GotoIfNot(Word32And(TaggedIsSmi(max_length), + SmiLessThanOrEqual(max_length, + SmiConstant(String::kMaxLength))), + &invalid_string_length); + + // If the max_length is less than length of the string, return the string. + CSA_ASSERT(this, TaggedIsPositiveSmi(max_length)); + GotoIf(SmiLessThanOrEqual(max_length, string_length), &dont_pad); + + Branch(IntPtrEqual(argc, IntPtrConstant(1)), &pad, &argc_2); + BIND(&argc_2); + { + Node* const fill = arguments.AtIndex(1); + GotoIf(IsUndefined(fill), &pad); + + var_fill_string.Bind(ToString_Inline(context, fill)); + var_fill_length.Bind(LoadStringLength(var_fill_string.value())); + + Branch(SmiGreaterThan(var_fill_length.value(), SmiConstant(0)), &pad, + &dont_pad); + } + BIND(&pad); + { + CSA_ASSERT(this, SmiGreaterThan(var_fill_length.value(), SmiConstant(0))); + CSA_ASSERT(this, SmiGreaterThan(max_length, string_length)); + + Callable stringadd_callable = + CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED); + Node* const pad_length = SmiSub(max_length, string_length); + + VARIABLE(var_pad, MachineRepresentation::kTagged); + + Label single_char_fill(this), multi_char_fill(this), return_result(this); + Branch(SmiEqual(var_fill_length.value(), SmiConstant(1)), + &single_char_fill, &multi_char_fill); + + // Fast path for a single character fill. No need to calculate number of + // repetitions or remainder. + BIND(&single_char_fill); + { + var_pad.Bind(CallBuiltin(Builtins::kStringRepeat, context, + var_fill_string.value(), pad_length)); + Goto(&return_result); + } + BIND(&multi_char_fill); + { + Node* const fill_length_word32 = SmiToWord32(var_fill_length.value()); + Node* const pad_length_word32 = SmiToWord32(pad_length); + Node* const repetitions_word32 = + Int32Div(pad_length_word32, fill_length_word32); + Node* const remaining_word32 = + Int32Mod(pad_length_word32, fill_length_word32); + + var_pad.Bind(CallBuiltin(Builtins::kStringRepeat, context, + var_fill_string.value(), + SmiFromWord32(repetitions_word32))); + + GotoIfNot(remaining_word32, &return_result); + { + Callable substring_callable = CodeFactory::SubString(isolate()); + Node* const remainder_string = + CallStub(substring_callable, context, var_fill_string.value(), + SmiConstant(0), SmiFromWord32(remaining_word32)); + var_pad.Bind(CallStub(stringadd_callable, context, var_pad.value(), + remainder_string)); + Goto(&return_result); + } + } + BIND(&return_result); + CSA_ASSERT(this, SmiEqual(pad_length, LoadStringLength(var_pad.value()))); + arguments.PopAndReturn(variant == kStart + ? CallStub(stringadd_callable, context, + var_pad.value(), receiver_string) + : CallStub(stringadd_callable, context, + receiver_string, var_pad.value())); + } + BIND(&dont_pad); + arguments.PopAndReturn(receiver_string); + BIND(&invalid_string_length); + { + CallRuntime(Runtime::kThrowInvalidStringLength, context); + Unreachable(); + } + } +}; + +TF_BUILTIN(StringPrototypePadEnd, StringPadAssembler) { + Generate(kEnd, "String.prototype.padEnd"); +} + +TF_BUILTIN(StringPrototypePadStart, StringPadAssembler) { + Generate(kStart, "String.prototype.padStart"); +} + // ES6 #sec-string.prototype.search TF_BUILTIN(StringPrototypeSearch, StringMatchSearchAssembler) { Node* const receiver = Parameter(Descriptor::kReceiver); diff --git a/src/debug/debug-evaluate.cc b/src/debug/debug-evaluate.cc index 44203c394e..ab51012644 100644 --- a/src/debug/debug-evaluate.cc +++ b/src/debug/debug-evaluate.cc @@ -609,6 +609,8 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) { case Builtins::kStringPrototypeItalics: case Builtins::kStringPrototypeLastIndexOf: case Builtins::kStringPrototypeLink: + case Builtins::kStringPrototypePadEnd: + case Builtins::kStringPrototypePadStart: case Builtins::kStringPrototypeRepeat: case Builtins::kStringPrototypeSlice: case Builtins::kStringPrototypeSmall: diff --git a/src/js/string.js b/src/js/string.js index 69f85f11d3..448a9b8209 100644 --- a/src/js/string.js +++ b/src/js/string.js @@ -13,67 +13,6 @@ var GlobalString = global.String; -//------------------------------------------------------------------- - -function StringPad(thisString, maxLength, fillString) { - maxLength = TO_LENGTH(maxLength); - var stringLength = thisString.length; - - if (maxLength <= stringLength) return ""; - - if (IS_UNDEFINED(fillString)) { - fillString = " "; - } else { - fillString = TO_STRING(fillString); - if (fillString === "") { - // If filler is the empty String, return S. - return ""; - } - } - - var fillLength = maxLength - stringLength; - var repetitions = (fillLength / fillString.length) | 0; - var remainingChars = (fillLength - fillString.length * repetitions) | 0; - - var filler = ""; - while (true) { - if (repetitions & 1) filler += fillString; - repetitions >>= 1; - if (repetitions === 0) break; - fillString += fillString; - } - - if (remainingChars) { - filler += %_SubString(fillString, 0, remainingChars); - } - - return filler; -} - -DEFINE_METHODS_LEN( - GlobalString.prototype, - { - /* ES#sec-string.prototype.padstart */ - /* String.prototype.padStart(maxLength [, fillString]) */ - padStart(maxLength, fillString) { - CHECK_OBJECT_COERCIBLE(this, "String.prototype.padStart"); - var thisString = TO_STRING(this); - - return StringPad(thisString, maxLength, fillString) + thisString; - } - - /* ES#sec-string.prototype.padend */ - /* String.prototype.padEnd(maxLength [, fillString]) */ - padEnd(maxLength, fillString) { - CHECK_OBJECT_COERCIBLE(this, "String.prototype.padEnd"); - var thisString = TO_STRING(this); - - return thisString + StringPad(thisString, maxLength, fillString); - } - }, - 1 /* Set functions length */ -); - // ------------------------------------------------------------------- // String methods related to templates diff --git a/test/mjsunit/harmony/harmony-string-pad-end.js b/test/mjsunit/harmony/harmony-string-pad-end.js index a218a8ae6f..3c96031a8f 100644 --- a/test/mjsunit/harmony/harmony-string-pad-end.js +++ b/test/mjsunit/harmony/harmony-string-pad-end.js @@ -94,3 +94,14 @@ (function TestTruncation() { assertEquals("ab", "a".padEnd(2, "bc")); })(); + + +(function TestMaxLength() { + assertThrows(() => "123".padEnd(Math.pow(2, 40)), RangeError); + assertThrows(() => "123".padEnd(1 << 30), RangeError); +})(); + + +(function TestNoArguments() { + assertEquals("abc", "abc".padEnd()); +})(); diff --git a/test/mjsunit/harmony/harmony-string-pad-start.js b/test/mjsunit/harmony/harmony-string-pad-start.js index 5e1c36b949..d27ad5418f 100644 --- a/test/mjsunit/harmony/harmony-string-pad-start.js +++ b/test/mjsunit/harmony/harmony-string-pad-start.js @@ -94,3 +94,14 @@ (function TestTruncation() { assertEquals("ba", "a".padStart(2, "bc")); })(); + + +(function TestMaxLength() { + assertThrows(() => "123".padStart(Math.pow(2, 40)), RangeError); + assertThrows(() => "123".padStart(1 << 30), RangeError); +})(); + + +(function TestNoArguments() { + assertEquals("abc", "abc".padStart()); +})(); diff --git a/test/mjsunit/messages.js b/test/mjsunit/messages.js index 74b3607a3d..e438adeb08 100644 --- a/test/mjsunit/messages.js +++ b/test/mjsunit/messages.js @@ -449,6 +449,14 @@ test(function() { }, "Invalid typed array length: -1", RangeError); // kThrowInvalidStringLength +test(function() { + "a".padEnd(1 << 30); +}, "Invalid string length", RangeError); + +test(function() { + "a".padStart(1 << 30); +}, "Invalid string length", RangeError); + test(function() { "a".repeat(1 << 30); }, "Invalid string length", RangeError);