From 78446a8afd29cc87dcd6420187b7f2dc9cb4a824 Mon Sep 17 00:00:00 2001 From: peterwmwong Date: Thu, 14 Sep 2017 07:43:49 -0500 Subject: [PATCH] [builtins] Port String.prototype.repeat to CSA - Removes S.p.repeat from string.js - Adds StringPrototypeRepeat TFJ Bug: v8:5049 Change-Id: I0b2d512bffd97dfc2c3ba6783e2e41c4db6c8faa Reviewed-on: https://chromium-review.googlesource.com/659097 Commit-Queue: Jakob Gruber Reviewed-by: Jakob Gruber Cr-Commit-Position: refs/heads/master@{#48023} --- src/bootstrapper.cc | 2 + src/builtins/builtins-definitions.h | 2 + src/builtins/builtins-string-gen.cc | 110 ++++++++++++++++++ src/debug/debug-evaluate.cc | 3 + src/js/string.js | 25 ---- test/message/regress/regress-crbug-691194.out | 2 +- test/mjsunit/es6/string-repeat.js | 5 + test/mjsunit/messages.js | 5 + 8 files changed, 128 insertions(+), 26 deletions(-) diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index bcfd326353..ac828d9048 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1959,6 +1959,8 @@ void Genesis::InitializeGlobal(Handle global_object, SimpleInstallFunction(prototype, "normalize", Builtins::kStringPrototypeNormalize, 0, false); #endif // V8_INTL_SUPPORT + SimpleInstallFunction(prototype, "repeat", Builtins::kStringPrototypeRepeat, + 1, true); SimpleInstallFunction(prototype, "replace", Builtins::kStringPrototypeReplace, 2, true); SimpleInstallFunction(prototype, "slice", Builtins::kStringPrototypeSlice, diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index efe76f57de..3ea3765392 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -909,6 +909,8 @@ namespace internal { CPP(StringPrototypeLastIndexOf) \ /* ES6 #sec-string.prototype.localecompare */ \ CPP(StringPrototypeLocaleCompare) \ + /* ES6 #sec-string.prototype.repeat */ \ + TFJ(StringPrototypeRepeat, 1, kCount) \ /* ES6 #sec-string.prototype.replace */ \ TFJ(StringPrototypeReplace, 2, kSearch, kReplace) \ /* ES6 #sec-string.prototype.slice */ \ diff --git a/src/builtins/builtins-string-gen.cc b/src/builtins/builtins-string-gen.cc index aeb2549c50..953519aeb6 100644 --- a/src/builtins/builtins-string-gen.cc +++ b/src/builtins/builtins-string-gen.cc @@ -1181,6 +1181,116 @@ compiler::Node* StringBuiltinsAssembler::GetSubstitution( return var_result.value(); } +// ES6 #sec-string.prototype.repeat +TF_BUILTIN(StringPrototypeRepeat, StringBuiltinsAssembler) { + Label invalid_count(this), invalid_string_length(this), + return_emptystring(this); + + Node* const context = Parameter(Descriptor::kContext); + Node* const receiver = Parameter(Descriptor::kReceiver); + Node* const count = Parameter(Descriptor::kCount); + Node* const string = + ToThisString(context, receiver, "String.prototype.repeat"); + Node* const is_stringempty = + SmiEqual(LoadStringLength(string), SmiConstant(0)); + + VARIABLE(var_count, MachineRepresentation::kTagged, + ToInteger(context, count, CodeStubAssembler::kTruncateMinusZero)); + + // 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); + + 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); + } + + // If count is a Heap Number... + // 1) If count is Infinity, throw a RangeError exception + // 2) If receiver is an empty string, return an empty string + // 3) Otherwise, throw RangeError exception + BIND(&if_count_isheapnumber); + { + CSA_ASSERT(this, IsNumberNormalized(var_count.value())); + Node* const number_value = LoadHeapNumberValue(var_count.value()); + GotoIf(Float64Equal(number_value, Float64Constant(V8_INFINITY)), + &invalid_count); + GotoIf(Float64LessThan(number_value, Float64Constant(0.0)), + &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); + Return(EmptyStringConstant()); + + BIND(&invalid_count); + { + CallRuntime(Runtime::kThrowRangeError, context, + SmiConstant(MessageTemplate::kInvalidCountValue), + var_count.value()); + Unreachable(); + } + BIND(&invalid_string_length); + { + CallRuntime(Runtime::kThrowInvalidStringLength, context); + Unreachable(); + } +} + // ES6 #sec-string.prototype.replace TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) { Label out(this); diff --git a/src/debug/debug-evaluate.cc b/src/debug/debug-evaluate.cc index f88eb366df..31238fc4a7 100644 --- a/src/debug/debug-evaluate.cc +++ b/src/debug/debug-evaluate.cc @@ -300,6 +300,7 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) { /* Collections */ \ V(GenericHash) \ /* Called from builtins */ \ + V(StringAdd) \ V(StringParseFloat) \ V(StringParseInt) \ V(StringCharCodeAt) \ @@ -321,6 +322,7 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) { V(ThrowIncompatibleMethodReceiver) \ V(ThrowInvalidHint) \ V(ThrowNotDateError) \ + V(ThrowRangeError) \ /* Misc. */ \ V(Call) \ V(MaxSmi) \ @@ -594,6 +596,7 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) { case Builtins::kStringPrototypeIncludes: case Builtins::kStringPrototypeIndexOf: case Builtins::kStringPrototypeLastIndexOf: + case Builtins::kStringPrototypeRepeat: case Builtins::kStringPrototypeSlice: case Builtins::kStringPrototypeStartsWith: case Builtins::kStringPrototypeSubstr: diff --git a/src/js/string.js b/src/js/string.js index f718a64946..18e6b22275 100644 --- a/src/js/string.js +++ b/src/js/string.js @@ -142,31 +142,6 @@ DEFINE_METHODS( CHECK_OBJECT_COERCIBLE(this, "String.prototype.sup"); return "" + TO_STRING(this) + ""; } - - /* ES#sec-string.prototype.repeat */ - repeat(count) { - CHECK_OBJECT_COERCIBLE(this, "String.prototype.repeat"); - - var s = TO_STRING(this); - var n = TO_INTEGER(count); - - if (n < 0 || n === INFINITY) throw %make_range_error(kInvalidCountValue); - - // Early return to allow an arbitrarily-large repeat of the empty string. - if (s.length === 0) return ""; - - // The maximum string length is stored in a smi, so a longer repeat - // must result in a range error. - if (n > %_StringMaxLength()) %ThrowInvalidStringLength(); - - var r = ""; - while (true) { - if (n & 1) r += s; - n >>= 1; - if (n === 0) return r; - s += s; - } - } } ); diff --git a/test/message/regress/regress-crbug-691194.out b/test/message/regress/regress-crbug-691194.out index 6d7db0f535..43453900db 100644 --- a/test/message/regress/regress-crbug-691194.out +++ b/test/message/regress/regress-crbug-691194.out @@ -6,5 +6,5 @@ "foo".repeat(1 << 30) ^ RangeError: Invalid string length - at String.repeat (native) + at String.repeat () at *%(basename)s:5:7 diff --git a/test/mjsunit/es6/string-repeat.js b/test/mjsunit/es6/string-repeat.js index d61aec066c..3649c5b09b 100644 --- a/test/mjsunit/es6/string-repeat.js +++ b/test/mjsunit/es6/string-repeat.js @@ -60,14 +60,19 @@ assertEquals("", "abc".repeat(0)); assertEquals("abcabc", "abc".repeat(2.0)); assertEquals("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "a".repeat(37)); +assertEquals("", "a".repeat(NaN)); assertThrows('"a".repeat(-1)', RangeError); assertThrows('"a".repeat(Number.POSITIVE_INFINITY)', RangeError); +assertThrows('"a".repeat(Number.NEGATIVE_INFINITY)', RangeError); assertThrows('"a".repeat(Math.pow(2, 30))', RangeError); assertThrows('"a".repeat(Math.pow(2, 40))', RangeError); +assertThrows('"a".repeat(-Math.pow(2, 40))', RangeError); // Handling empty strings assertThrows('"".repeat(-1)', RangeError); assertThrows('"".repeat(Number.POSITIVE_INFINITY)', RangeError); +assertThrows('"".repeat(Number.NEGATIVE_INFINITY)', RangeError); +assertThrows('"a".repeat(-Math.pow(2, 40))', RangeError); assertEquals("", "".repeat(Math.pow(2, 30))); assertEquals("", "".repeat(Math.pow(2, 40))); diff --git a/test/mjsunit/messages.js b/test/mjsunit/messages.js index 9fb4f5af55..934a731e8f 100644 --- a/test/mjsunit/messages.js +++ b/test/mjsunit/messages.js @@ -440,6 +440,11 @@ test(function() { new Uint16Array(-1); }, "Invalid typed array length: -1", RangeError); +// kThrowInvalidStringLength +test(function() { + "a".repeat(1 << 30); +}, "Invalid string length", RangeError); + // kNormalizationForm test(function() { "".normalize("ABC");