Commit 0e788e32 authored by mvstanton's avatar mvstanton Committed by Commit bot

[builtins] String.prototype.slice as a CSA builtin.

BUG=v8:6370

Review-Url: https://codereview.chromium.org/2870013004
Cr-Commit-Position: refs/heads/master@{#45275}
parent 752bdcbf
...@@ -1810,6 +1810,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object, ...@@ -1810,6 +1810,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
#endif // V8_INTL_SUPPORT #endif // V8_INTL_SUPPORT
SimpleInstallFunction(prototype, "replace", SimpleInstallFunction(prototype, "replace",
Builtins::kStringPrototypeReplace, 2, true); Builtins::kStringPrototypeReplace, 2, true);
SimpleInstallFunction(prototype, "slice", Builtins::kStringPrototypeSlice,
2, false);
SimpleInstallFunction(prototype, "split", Builtins::kStringPrototypeSplit, SimpleInstallFunction(prototype, "split", Builtins::kStringPrototypeSplit,
2, true); 2, true);
SimpleInstallFunction(prototype, "substr", Builtins::kStringPrototypeSubstr, SimpleInstallFunction(prototype, "substr", Builtins::kStringPrototypeSubstr,
......
...@@ -853,6 +853,8 @@ namespace internal { ...@@ -853,6 +853,8 @@ namespace internal {
CPP(StringPrototypeLocaleCompare) \ CPP(StringPrototypeLocaleCompare) \
/* ES6 #sec-string.prototype.replace */ \ /* ES6 #sec-string.prototype.replace */ \
TFJ(StringPrototypeReplace, 2, kSearch, kReplace) \ TFJ(StringPrototypeReplace, 2, kSearch, kReplace) \
/* ES6 #sec-string.prototype.slice */ \
TFJ(StringPrototypeSlice, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* ES6 #sec-string.prototype.split */ \ /* ES6 #sec-string.prototype.split */ \
TFJ(StringPrototypeSplit, 2, kSeparator, kLimit) \ TFJ(StringPrototypeSplit, 2, kSeparator, kLimit) \
/* ES6 #sec-string.prototype.substr */ \ /* ES6 #sec-string.prototype.substr */ \
......
...@@ -155,6 +155,41 @@ class StringBuiltinsAssembler : public CodeStubAssembler { ...@@ -155,6 +155,41 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
return SmiLessThan(value, SmiConstant(0)); return SmiLessThan(value, SmiConstant(0));
} }
// substr and slice have a common way of handling the {start} argument.
void ConvertAndBoundsCheckStartArgument(Node* context, Variable* var_start,
Node* start, Node* string_length) {
Node* const start_int =
ToInteger(context, start, CodeStubAssembler::kTruncateMinusZero);
Node* const zero = SmiConstant(Smi::kZero);
Label done(this);
Label if_issmi(this), if_isheapnumber(this, Label::kDeferred);
Branch(TaggedIsSmi(start_int), &if_issmi, &if_isheapnumber);
BIND(&if_issmi);
{
var_start->Bind(
Select(SmiLessThan(start_int, zero),
[&] { return SmiMax(SmiAdd(string_length, start_int), zero); },
[&] { return start_int; }, MachineRepresentation::kTagged));
Goto(&done);
}
BIND(&if_isheapnumber);
{
// If {start} is a heap number, it is definitely out of bounds. If it is
// negative, {start} = max({string_length} + {start}),0) = 0'. If it is
// positive, set {start} to {string_length} which ultimately results in
// returning an empty string.
Node* const float_zero = Float64Constant(0.);
Node* const start_float = LoadHeapNumberValue(start_int);
var_start->Bind(SelectTaggedConstant(
Float64LessThan(start_float, float_zero), zero, string_length));
Goto(&done);
}
BIND(&done);
}
// Implements boilerplate logic for {match, split, replace, search} of the // Implements boilerplate logic for {match, split, replace, search} of the
// form: // form:
// //
...@@ -1293,6 +1328,89 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) { ...@@ -1293,6 +1328,89 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
} }
} }
// ES6 section 21.1.3.18 String.prototype.slice ( start, end )
TF_BUILTIN(StringPrototypeSlice, StringBuiltinsAssembler) {
Label out(this);
VARIABLE(var_start, MachineRepresentation::kTagged);
VARIABLE(var_end, MachineRepresentation::kTagged);
const int kStart = 0;
const int kEnd = 1;
Node* argc =
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
CodeStubArguments args(this, argc);
Node* const receiver = args.GetReceiver();
Node* const start =
args.GetOptionalArgumentValue(kStart, UndefinedConstant());
Node* const end = args.GetOptionalArgumentValue(kEnd, UndefinedConstant());
Node* const context = Parameter(BuiltinDescriptor::kContext);
Node* const smi_zero = SmiConstant(0);
// 1. Let O be ? RequireObjectCoercible(this value).
RequireObjectCoercible(context, receiver, "String.prototype.slice");
// 2. Let S be ? ToString(O).
Callable tostring_callable = CodeFactory::ToString(isolate());
Node* const subject_string = CallStub(tostring_callable, context, receiver);
// 3. Let len be the number of elements in S.
Node* const length = LoadStringLength(subject_string);
// Conversions and bounds-checks for {start}.
ConvertAndBoundsCheckStartArgument(context, &var_start, start, length);
// 5. If end is undefined, let intEnd be len;
var_end.Bind(length);
GotoIf(WordEqual(end, UndefinedConstant()), &out);
// else let intEnd be ? ToInteger(end).
Node* const end_int =
ToInteger(context, end, CodeStubAssembler::kTruncateMinusZero);
// 7. If intEnd < 0, let to be max(len + intEnd, 0);
// otherwise let to be min(intEnd, len).
Label if_issmi(this), if_isheapnumber(this, Label::kDeferred);
Branch(TaggedIsSmi(end_int), &if_issmi, &if_isheapnumber);
BIND(&if_issmi);
{
Node* const length_plus_end = SmiAdd(length, end_int);
var_end.Bind(Select(SmiLessThan(end_int, smi_zero),
[&] { return SmiMax(length_plus_end, smi_zero); },
[&] { return SmiMin(length, end_int); },
MachineRepresentation::kTagged));
Goto(&out);
}
BIND(&if_isheapnumber);
{
// If {end} is a heap number, it is definitely out of bounds. If it is
// negative, {int_end} = max({length} + {int_end}),0) = 0'. If it is
// positive, set {int_end} to {length} which ultimately results in
// returning an empty string.
Node* const float_zero = Float64Constant(0.);
Node* const end_float = LoadHeapNumberValue(end_int);
var_end.Bind(SelectTaggedConstant(Float64LessThan(end_float, float_zero),
smi_zero, length));
Goto(&out);
}
Label return_emptystring(this);
BIND(&out);
{
GotoIf(SmiLessThanOrEqual(var_end.value(), var_start.value()),
&return_emptystring);
Node* const result =
SubString(context, subject_string, var_start.value(), var_end.value(),
SubStringFlags::FROM_TO_ARE_BOUNDED);
args.PopAndReturn(result);
}
BIND(&return_emptystring);
args.PopAndReturn(EmptyStringConstant());
}
// ES6 section 21.1.3.19 String.prototype.split ( separator, limit ) // ES6 section 21.1.3.19 String.prototype.split ( separator, limit )
TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) { TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
Label out(this); Label out(this);
...@@ -1397,8 +1515,8 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) { ...@@ -1397,8 +1515,8 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
} }
// ES6 #sec-string.prototype.substr // ES6 #sec-string.prototype.substr
TF_BUILTIN(StringPrototypeSubstr, CodeStubAssembler) { TF_BUILTIN(StringPrototypeSubstr, StringBuiltinsAssembler) {
Label out(this), handle_length(this); Label out(this);
VARIABLE(var_start, MachineRepresentation::kTagged); VARIABLE(var_start, MachineRepresentation::kTagged);
VARIABLE(var_length, MachineRepresentation::kTagged); VARIABLE(var_length, MachineRepresentation::kTagged);
...@@ -1417,40 +1535,9 @@ TF_BUILTIN(StringPrototypeSubstr, CodeStubAssembler) { ...@@ -1417,40 +1535,9 @@ TF_BUILTIN(StringPrototypeSubstr, CodeStubAssembler) {
Node* const string_length = LoadStringLength(string); Node* const string_length = LoadStringLength(string);
// Conversions and bounds-checks for {start}. // Conversions and bounds-checks for {start}.
{ ConvertAndBoundsCheckStartArgument(context, &var_start, start, string_length);
Node* const start_int =
ToInteger(context, start, CodeStubAssembler::kTruncateMinusZero);
Label if_issmi(this), if_isheapnumber(this, Label::kDeferred);
Branch(TaggedIsSmi(start_int), &if_issmi, &if_isheapnumber);
BIND(&if_issmi);
{
Node* const length_plus_start = SmiAdd(string_length, start_int);
var_start.Bind(Select(SmiLessThan(start_int, zero),
[&] { return SmiMax(length_plus_start, zero); },
[&] { return start_int; },
MachineRepresentation::kTagged));
Goto(&handle_length);
}
BIND(&if_isheapnumber);
{
// If {start} is a heap number, it is definitely out of bounds. If it is
// negative, {start} = max({string_length} + {start}),0) = 0'. If it is
// positive, set {start} to {string_length} which ultimately results in
// returning an empty string.
Node* const float_zero = Float64Constant(0.);
Node* const start_float = LoadHeapNumberValue(start_int);
var_start.Bind(SelectTaggedConstant(
Float64LessThan(start_float, float_zero), zero, string_length));
Goto(&handle_length);
}
}
// Conversions and bounds-checks for {length}. // Conversions and bounds-checks for {length}.
BIND(&handle_length);
{
Label if_issmi(this), if_isheapnumber(this, Label::kDeferred); Label if_issmi(this), if_isheapnumber(this, Label::kDeferred);
// Default to {string_length} if {length} is undefined. // Default to {string_length} if {length} is undefined.
...@@ -1506,7 +1593,6 @@ TF_BUILTIN(StringPrototypeSubstr, CodeStubAssembler) { ...@@ -1506,7 +1593,6 @@ TF_BUILTIN(StringPrototypeSubstr, CodeStubAssembler) {
Return(EmptyStringConstant()); Return(EmptyStringConstant());
} }
} }
}
BIND(&out); BIND(&out);
{ {
......
...@@ -3548,7 +3548,9 @@ Node* AllocAndCopyStringCharacters(CodeStubAssembler* a, Node* context, ...@@ -3548,7 +3548,9 @@ Node* AllocAndCopyStringCharacters(CodeStubAssembler* a, Node* context,
} // namespace } // namespace
Node* CodeStubAssembler::SubString(Node* context, Node* string, Node* from, Node* CodeStubAssembler::SubString(Node* context, Node* string, Node* from,
Node* to) { Node* to, SubStringFlags flags) {
DCHECK(flags == SubStringFlags::NONE ||
flags == SubStringFlags::FROM_TO_ARE_BOUNDED);
VARIABLE(var_result, MachineRepresentation::kTagged); VARIABLE(var_result, MachineRepresentation::kTagged);
ToDirectStringAssembler to_direct(state(), string); ToDirectStringAssembler to_direct(state(), string);
Label end(this), runtime(this); Label end(this), runtime(this);
...@@ -3559,8 +3561,13 @@ Node* CodeStubAssembler::SubString(Node* context, Node* string, Node* from, ...@@ -3559,8 +3561,13 @@ Node* CodeStubAssembler::SubString(Node* context, Node* string, Node* from,
// Make sure that both from and to are non-negative smis. // Make sure that both from and to are non-negative smis.
if (flags == SubStringFlags::NONE) {
GotoIfNot(TaggedIsPositiveSmi(from), &runtime); GotoIfNot(TaggedIsPositiveSmi(from), &runtime);
GotoIfNot(TaggedIsPositiveSmi(to), &runtime); GotoIfNot(TaggedIsPositiveSmi(to), &runtime);
} else {
CSA_ASSERT(this, TaggedIsPositiveSmi(from));
CSA_ASSERT(this, TaggedIsPositiveSmi(to));
}
Node* const substr_length = SmiSub(to, from); Node* const substr_length = SmiSub(to, from);
Node* const string_length = LoadStringLength(string); Node* const string_length = LoadStringLength(string);
...@@ -3661,8 +3668,14 @@ Node* CodeStubAssembler::SubString(Node* context, Node* string, Node* from, ...@@ -3661,8 +3668,14 @@ Node* CodeStubAssembler::SubString(Node* context, Node* string, Node* from,
BIND(&original_string_or_invalid_length); BIND(&original_string_or_invalid_length);
{ {
if (flags == SubStringFlags::NONE) {
// Longer than original string's length or negative: unsafe arguments. // Longer than original string's length or negative: unsafe arguments.
GotoIf(SmiAbove(substr_length, string_length), &runtime); GotoIf(SmiAbove(substr_length, string_length), &runtime);
} else {
// with flag SubStringFlags::FROM_TO_ARE_BOUNDED, the only way we can
// get here is if substr_length is equal to string_length.
CSA_ASSERT(this, SmiEqual(substr_length, string_length));
}
// Equal length - check if {from, to} == {0, str.length}. // Equal length - check if {from, to} == {0, str.length}.
GotoIf(SmiAbove(from, SmiConstant(Smi::kZero)), &runtime); GotoIf(SmiAbove(from, SmiConstant(Smi::kZero)), &runtime);
......
...@@ -805,9 +805,15 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler { ...@@ -805,9 +805,15 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
ParameterMode parameter_mode = SMI_PARAMETERS); ParameterMode parameter_mode = SMI_PARAMETERS);
// Return the single character string with only {code}. // Return the single character string with only {code}.
Node* StringFromCharCode(Node* code); Node* StringFromCharCode(Node* code);
enum class SubStringFlags { NONE, FROM_TO_ARE_BOUNDED };
// Return a new string object which holds a substring containing the range // Return a new string object which holds a substring containing the range
// [from,to[ of string. |from| and |to| are expected to be tagged. // [from,to[ of string. |from| and |to| are expected to be tagged.
Node* SubString(Node* context, Node* string, Node* from, Node* to); // If flags has the value FROM_TO_ARE_BOUNDED then from and to are in
// the range [0, string-length)
Node* SubString(Node* context, Node* string, Node* from, Node* to,
SubStringFlags flags = SubStringFlags::NONE);
// Return a new string object produced by concatenating |first| with |second|. // Return a new string object produced by concatenating |first| with |second|.
Node* StringAdd(Node* context, Node* first, Node* second, Node* StringAdd(Node* context, Node* first, Node* second,
......
...@@ -584,6 +584,7 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) { ...@@ -584,6 +584,7 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) {
case Builtins::kStringPrototypeIncludes: case Builtins::kStringPrototypeIncludes:
case Builtins::kStringPrototypeIndexOf: case Builtins::kStringPrototypeIndexOf:
case Builtins::kStringPrototypeLastIndexOf: case Builtins::kStringPrototypeLastIndexOf:
case Builtins::kStringPrototypeSlice:
case Builtins::kStringPrototypeStartsWith: case Builtins::kStringPrototypeStartsWith:
case Builtins::kStringPrototypeSubstr: case Builtins::kStringPrototypeSubstr:
case Builtins::kStringPrototypeSubstring: case Builtins::kStringPrototypeSubstring:
......
...@@ -52,48 +52,6 @@ function StringSearch(pattern) { ...@@ -52,48 +52,6 @@ function StringSearch(pattern) {
} }
// ECMA-262 section 15.5.4.13
function StringSlice(start, end) {
CHECK_OBJECT_COERCIBLE(this, "String.prototype.slice");
var s = TO_STRING(this);
var s_len = s.length;
var start_i = TO_INTEGER(start);
var end_i = s_len;
if (!IS_UNDEFINED(end)) {
end_i = TO_INTEGER(end);
}
if (start_i < 0) {
start_i += s_len;
if (start_i < 0) {
start_i = 0;
}
} else {
if (start_i > s_len) {
return '';
}
}
if (end_i < 0) {
end_i += s_len;
if (end_i < 0) {
return '';
}
} else {
if (end_i > s_len) {
end_i = s_len;
}
}
if (end_i <= start_i) {
return '';
}
return %_SubString(s, start_i, end_i);
}
// ES6 draft, revision 26 (2014-07-18), section B.2.3.2.1 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2.1
function HtmlEscape(str) { function HtmlEscape(str) {
return %RegExpInternalReplace(/"/g, TO_STRING(str), "&quot;"); return %RegExpInternalReplace(/"/g, TO_STRING(str), "&quot;");
...@@ -334,8 +292,6 @@ utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [ ...@@ -334,8 +292,6 @@ utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [
"padStart", StringPadStart, "padStart", StringPadStart,
"repeat", StringRepeat, "repeat", StringRepeat,
"search", StringSearch, "search", StringSearch,
"slice", StringSlice,
"link", StringLink, "link", StringLink,
"anchor", StringAnchor, "anchor", StringAnchor,
"fontcolor", StringFontcolor, "fontcolor", StringFontcolor,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment