[csa] Add ToLength and ToString variants with inlined fast checks

Smis can easily be handled outside the stub call without adding much to code
size.

The ToString inlining adds overhead of repeated instance type loads and checks,
but under the assumption that it is called with mostly string values it should
speed things up (a local RegExp.p[@@replace] microbenchmark shows consistent
1.6% improvements).

Drive-by-fix: Remove duplication in ToString implementations.

BUG=

Review-Url: https://codereview.chromium.org/2874423003
Cr-Commit-Position: refs/heads/master@{#45287}
This commit is contained in:
jgruber 2017-05-15 00:51:15 -07:00 committed by Commit bot
parent 3a80814d53
commit 646fdacaa7
6 changed files with 46 additions and 57 deletions

View File

@ -303,8 +303,7 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
BIND(&not_js_array); BIND(&not_js_array);
Node* len_property = Node* len_property =
GetProperty(context(), o(), isolate()->factory()->length_string()); GetProperty(context(), o(), isolate()->factory()->length_string());
merged_length.Bind( merged_length.Bind(ToLength_Inline(context(), len_property));
CallStub(CodeFactory::ToLength(isolate()), context(), len_property));
Goto(&has_length); Goto(&has_length);
BIND(&has_length); BIND(&has_length);
len_ = merged_length.value(); len_ = merged_length.value();
@ -2197,8 +2196,7 @@ TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) {
{ {
Node* length = Node* length =
GetProperty(context, array, factory()->length_string()); GetProperty(context, array, factory()->length_string());
Callable to_length = CodeFactory::ToLength(isolate()); var_length.Bind(ToLength_Inline(context, length));
var_length.Bind(CallStub(to_length, context, length));
Goto(&done); Goto(&done);
} }

View File

@ -133,35 +133,7 @@ TF_BUILTIN(ToString, CodeStubAssembler) {
Node* context = Parameter(Descriptor::kContext); Node* context = Parameter(Descriptor::kContext);
Node* input = Parameter(Descriptor::kArgument); Node* input = Parameter(Descriptor::kArgument);
Label is_number(this); Return(ToString(context, input));
Label runtime(this);
GotoIf(TaggedIsSmi(input), &is_number);
Node* input_map = LoadMap(input);
Node* input_instance_type = LoadMapInstanceType(input_map);
Label not_string(this);
GotoIfNot(IsStringInstanceType(input_instance_type), &not_string);
Return(input);
Label not_heap_number(this);
BIND(&not_string);
{ Branch(IsHeapNumberMap(input_map), &is_number, &not_heap_number); }
BIND(&is_number);
{ Return(NumberToString(context, input)); }
BIND(&not_heap_number);
{
GotoIf(Word32NotEqual(input_instance_type, Int32Constant(ODDBALL_TYPE)),
&runtime);
Return(LoadObjectField(input, Oddball::kToStringOffset));
}
BIND(&runtime);
{ Return(CallRuntime(Runtime::kToString, context, input)); }
} }
// 7.1.1.1 OrdinaryToPrimitive ( O, hint ) // 7.1.1.1 OrdinaryToPrimitive ( O, hint )

View File

@ -577,8 +577,7 @@ Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult(
BIND(&call_tolength); BIND(&call_tolength);
{ {
var_lastindex.Bind( var_lastindex.Bind(ToLength_Inline(context, regexp_lastindex));
CallBuiltin(Builtins::kToLength, context, regexp_lastindex));
Goto(&next); Goto(&next);
} }
@ -1941,7 +1940,7 @@ void RegExpBuiltinsAssembler::RegExpPrototypeMatchBody(Node* const context,
if (is_fastpath) { if (is_fastpath) {
CSA_ASSERT(this, TaggedIsPositiveSmi(last_index)); CSA_ASSERT(this, TaggedIsPositiveSmi(last_index));
} else { } else {
last_index = CallBuiltin(Builtins::kToLength, context, last_index); last_index = ToLength_Inline(context, last_index);
} }
Node* const new_last_index = Node* const new_last_index =
@ -2809,8 +2808,7 @@ TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) {
// 3. Does ToString({replace_value}) contain '$'? // 3. Does ToString({replace_value}) contain '$'?
BIND(&checkreplacestring); BIND(&checkreplacestring);
{ {
Node* const replace_string = Node* const replace_string = ToString_Inline(context, replace_value);
CallBuiltin(Builtins::kToString, context, replace_value);
// ToString(replaceValue) could potentially change the shape of the RegExp // ToString(replaceValue) could potentially change the shape of the RegExp
// object. Recheck that we are still on the fast path and bail to runtime // object. Recheck that we are still on the fast path and bail to runtime
@ -2898,7 +2896,7 @@ TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) {
Node* const receiver = maybe_receiver; Node* const receiver = maybe_receiver;
// Convert {maybe_string} to a String. // Convert {maybe_string} to a String.
Node* const string = CallBuiltin(Builtins::kToString, context, maybe_string); Node* const string = ToString_Inline(context, maybe_string);
// Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance? // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance?
Label stub(this), runtime(this, Label::kDeferred); Label stub(this), runtime(this, Label::kDeferred);

View File

@ -767,7 +767,7 @@ TF_BUILTIN(StringPrototypeConcat, CodeStubAssembler) {
arguments.ForEach( arguments.ForEach(
CodeStubAssembler::VariableList({&var_result}, zone()), CodeStubAssembler::VariableList({&var_result}, zone()),
[this, context, &var_result](Node* arg) { [this, context, &var_result](Node* arg) {
arg = CallStub(CodeFactory::ToString(isolate()), context, arg); arg = ToString_Inline(context, arg);
var_result.Bind(CallStub(CodeFactory::StringAdd(isolate()), context, var_result.Bind(CallStub(CodeFactory::StringAdd(isolate()), context,
var_result.value(), arg)); var_result.value(), arg));
}); });
@ -1184,9 +1184,7 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
MaybeCallFunctionAtSymbol( MaybeCallFunctionAtSymbol(
context, search, isolate()->factory()->replace_symbol(), context, search, isolate()->factory()->replace_symbol(),
[=]() { [=]() {
Callable tostring_callable = CodeFactory::ToString(isolate()); Node* const subject_string = ToString_Inline(context, receiver);
Node* const subject_string =
CallStub(tostring_callable, context, receiver);
Callable replace_callable = CodeFactory::RegExpReplace(isolate()); Callable replace_callable = CodeFactory::RegExpReplace(isolate());
return CallStub(replace_callable, context, search, subject_string, return CallStub(replace_callable, context, search, subject_string,
@ -1199,11 +1197,10 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
// Convert {receiver} and {search} to strings. // Convert {receiver} and {search} to strings.
Callable tostring_callable = CodeFactory::ToString(isolate());
Callable indexof_callable = CodeFactory::StringIndexOf(isolate()); Callable indexof_callable = CodeFactory::StringIndexOf(isolate());
Node* const subject_string = CallStub(tostring_callable, context, receiver); Node* const subject_string = ToString_Inline(context, receiver);
Node* const search_string = CallStub(tostring_callable, context, search); Node* const search_string = ToString_Inline(context, search);
Node* const subject_length = LoadStringLength(subject_string); Node* const subject_length = LoadStringLength(subject_string);
Node* const search_length = LoadStringLength(search_string); Node* const search_length = LoadStringLength(search_string);
@ -1257,7 +1254,7 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
// TODO(jgruber): Could introduce ToStringSideeffectsStub which only // TODO(jgruber): Could introduce ToStringSideeffectsStub which only
// performs observable parts of ToString. // performs observable parts of ToString.
CallStub(tostring_callable, context, replace); ToString_Inline(context, replace);
Goto(&return_subject); Goto(&return_subject);
BIND(&return_subject); BIND(&return_subject);
@ -1300,8 +1297,7 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
Node* const replacement = Node* const replacement =
CallJS(call_callable, context, replace, UndefinedConstant(), CallJS(call_callable, context, replace, UndefinedConstant(),
search_string, match_start_index, subject_string); search_string, match_start_index, subject_string);
Node* const replacement_string = Node* const replacement_string = ToString_Inline(context, replacement);
CallStub(tostring_callable, context, replacement);
var_result.Bind(CallStub(stringadd_callable, context, var_result.value(), var_result.Bind(CallStub(stringadd_callable, context, var_result.value(),
replacement_string)); replacement_string));
Goto(&out); Goto(&out);
@ -1309,7 +1305,7 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
BIND(&if_notcallablereplace); BIND(&if_notcallablereplace);
{ {
Node* const replace_string = CallStub(tostring_callable, context, replace); Node* const replace_string = ToString_Inline(context, replace);
Node* const replacement = Node* const replacement =
GetSubstitution(context, subject_string, match_start_index, GetSubstitution(context, subject_string, match_start_index,
match_end_index, replace_string); match_end_index, replace_string);
@ -1429,9 +1425,7 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
MaybeCallFunctionAtSymbol( MaybeCallFunctionAtSymbol(
context, separator, isolate()->factory()->split_symbol(), context, separator, isolate()->factory()->split_symbol(),
[=]() { [=]() {
Callable tostring_callable = CodeFactory::ToString(isolate()); Node* const subject_string = ToString_Inline(context, receiver);
Node* const subject_string =
CallStub(tostring_callable, context, receiver);
Callable split_callable = CodeFactory::RegExpSplit(isolate()); Callable split_callable = CodeFactory::RegExpSplit(isolate());
return CallStub(split_callable, context, separator, subject_string, return CallStub(split_callable, context, separator, subject_string,
@ -1447,14 +1441,12 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
// but AFAIK there should not be a difference since arrays are capped at Smi // but AFAIK there should not be a difference since arrays are capped at Smi
// lengths. // lengths.
Callable tostring_callable = CodeFactory::ToString(isolate()); Node* const subject_string = ToString_Inline(context, receiver);
Node* const subject_string = CallStub(tostring_callable, context, receiver);
Node* const limit_number = Node* const limit_number =
Select(IsUndefined(limit), [=]() { return SmiConstant(Smi::kMaxValue); }, Select(IsUndefined(limit), [=]() { return SmiConstant(Smi::kMaxValue); },
[=]() { return ToUint32(context, limit); }, [=]() { return ToUint32(context, limit); },
MachineRepresentation::kTagged); MachineRepresentation::kTagged);
Node* const separator_string = Node* const separator_string = ToString_Inline(context, separator);
CallStub(tostring_callable, context, separator);
// Shortcut for {limit} == 0. // Shortcut for {limit} == 0.
{ {

View File

@ -4480,6 +4480,22 @@ Node* CodeStubAssembler::ToString(Node* context, Node* input) {
return result.value(); return result.value();
} }
Node* CodeStubAssembler::ToString_Inline(Node* const context,
Node* const input) {
VARIABLE(var_result, MachineRepresentation::kTagged, input);
Label stub_call(this, Label::kDeferred), out(this);
GotoIf(TaggedIsSmi(input), &stub_call);
Branch(IsString(input), &out, &stub_call);
BIND(&stub_call);
var_result.Bind(CallBuiltin(Builtins::kToString, context, input));
Goto(&out);
BIND(&out);
return var_result.value();
}
Node* CodeStubAssembler::JSReceiverToPrimitive(Node* context, Node* input) { Node* CodeStubAssembler::JSReceiverToPrimitive(Node* context, Node* input) {
Label if_isreceiver(this, Label::kDeferred), if_isnotreceiver(this); Label if_isreceiver(this, Label::kDeferred), if_isnotreceiver(this);
VARIABLE(result, MachineRepresentation::kTagged); VARIABLE(result, MachineRepresentation::kTagged);
@ -4557,6 +4573,15 @@ Node* CodeStubAssembler::ToSmiLength(Node* input, Node* const context,
return result.value(); return result.value();
} }
Node* CodeStubAssembler::ToLength_Inline(Node* const context,
Node* const input) {
Node* const smi_zero = SmiConstant(0);
return Select(
TaggedIsSmi(input), [=] { return SmiMax(input, smi_zero); },
[=] { return CallBuiltin(Builtins::kToLength, context, input); },
MachineRepresentation::kTagged);
}
Node* CodeStubAssembler::ToInteger(Node* context, Node* input, Node* CodeStubAssembler::ToInteger(Node* context, Node* input,
ToIntegerTruncationMode mode) { ToIntegerTruncationMode mode) {
// We might need to loop once for ToNumber conversion. // We might need to loop once for ToNumber conversion.

View File

@ -856,6 +856,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
// Convert any object to a String. // Convert any object to a String.
Node* ToString(Node* context, Node* input); Node* ToString(Node* context, Node* input);
Node* ToString_Inline(Node* const context, Node* const input);
// Convert any object to a Primitive. // Convert any object to a Primitive.
Node* JSReceiverToPrimitive(Node* context, Node* input); Node* JSReceiverToPrimitive(Node* context, Node* input);
@ -871,6 +872,9 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
// ES6 7.1.15 ToLength, but jumps to range_error if the result is not a Smi. // ES6 7.1.15 ToLength, but jumps to range_error if the result is not a Smi.
Node* ToSmiLength(Node* input, Node* const context, Label* range_error); Node* ToSmiLength(Node* input, Node* const context, Label* range_error);
// ES6 7.1.15 ToLength, but with inlined fast path.
Node* ToLength_Inline(Node* const context, Node* const input);
// Convert any object to an Integer. // Convert any object to an Integer.
Node* ToInteger(Node* context, Node* input, Node* ToInteger(Node* context, Node* input,
ToIntegerTruncationMode mode = kNoTruncation); ToIntegerTruncationMode mode = kNoTruncation);