[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:
parent
549692cbc0
commit
78446a8afd
@ -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,
|
||||
|
@ -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 */ \
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)));
|
||||
|
||||
|
@ -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");
|
||||
|
Loading…
Reference in New Issue
Block a user