[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 <jgruber@chromium.org>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48023}
This commit is contained in:
peterwmwong 2017-09-14 07:43:49 -05:00 committed by Commit Bot
parent 549692cbc0
commit 78446a8afd
8 changed files with 128 additions and 26 deletions

View File

@ -1959,6 +1959,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> 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,

View File

@ -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 */ \

View File

@ -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);

View File

@ -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:

View File

@ -142,31 +142,6 @@ DEFINE_METHODS(
CHECK_OBJECT_COERCIBLE(this, "String.prototype.sup");
return "<sup>" + TO_STRING(this) + "</sup>";
}
/* 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;
}
}
}
);

View File

@ -6,5 +6,5 @@
"foo".repeat(1 << 30)
^
RangeError: Invalid string length
at String.repeat (native)
at String.repeat (<anonymous>)
at *%(basename)s:5:7

View File

@ -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)));

View File

@ -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");