[builtins] Port String.prototype.{padStart, padEnd} to CSA
- Extract core StringPrototypeRepeat code into a TFS builtin (StringRepeat) - Assumes arguments are a string and smi (no range checks) - Add StringPrototypePadStart and StringPrototypePadEnd TFJ builtins - Added StringPadAssembler to ensure common behavior - Removed functionality from string.js A quick benchmark shows significant performance gains for unoptimized code (2.1x to 2.46x) and optimized code (1.03x - 1.56x). https: //github.com/peterwmwong/v8-perf/blob/master/string-pad/README.md Bug: v8:5049 Change-Id: I6e4fe99fb62a3edb3d6906fd4f78b3576b5b0d13 Reviewed-on: https://chromium-review.googlesource.com/720067 Commit-Queue: Jakob Gruber <jgruber@chromium.org> Reviewed-by: Jakob Gruber <jgruber@chromium.org> Cr-Commit-Position: refs/heads/master@{#48595}
This commit is contained in:
parent
61c7af2e97
commit
33b23529f4
@ -1992,6 +1992,10 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> 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",
|
||||
|
@ -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 */ \
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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());
|
||||
})();
|
||||
|
@ -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());
|
||||
})();
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user