Skip to content

Commit d4da17c

Browse files
bmeurerCommit Bot
authored andcommitted
[es2015] Optimize Object.is baseline and interesting cases.
The Object.is builtin provides an entry point to the abstract operation SameValue, which properly distinguishes -0 and 0, and also identifies NaNs. Most of the time you don't need these, but rather just regular strict equality, but when you do, Object.is(o, -0) is the most readable way to check for minus zero. This is for example used in Node.js by formatNumber to properly print -0 for negative zero. However since the builtin thus far implemented as C++ builtin and TurboFan didn't know anything about it, Node.js considering to go with a more performant, less readable version (which also makes assumptions about the input value) in nodejs/node#15726 until the performance of Object.is will be on par (so hopefully we can go back to Object.is in Node 9). This CL ports the baseline implementation of Object.is to CSA, which is pretty straight-forward since SameValue is already available in CodeStubAssembler, and inlines a few interesting cases into TurboFan, i.e. comparing same SSA node, and checking for -0 and NaN explicitly. On the micro-benchmarks we go from testNumberIsMinusZero: 1000 ms. testObjectIsMinusZero: 929 ms. testObjectIsNaN: 954 ms. testObjectIsSame: 793 ms. testStrictEqualSame: 104 ms. to testNumberIsMinusZero: 89 ms. testObjectIsMinusZero: 88 ms. testObjectIsNaN: 88 ms. testObjectIsSame: 86 ms. testStrictEqualSame: 105 ms. which is a nice 10x to 11x improvement and brings Object.is on par with strict equality for most cases. Drive-by-fix: Also refactor and optimize the SameValue check in the CodeStubAssembler to avoid code bloat (by not inlining StrictEqual into every user of SameValue, and also avoiding useless checks). Bug: v8:6882 Change-Id: Ibffd8c36511f219fcce0d89ed4e1073f5d6c6344 Reviewed-on: https://chromium-review.googlesource.com/700254 Reviewed-by: Jaroslav Sevcik <jarin@chromium.org> Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Cr-Commit-Position: refs/heads/master@{#48275}
1 parent bfb43f8 commit d4da17c

21 files changed

Lines changed: 385 additions & 85 deletions

‎src/builtins/builtins-definitions.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ namespace internal {
709709
CPP(ObjectGetOwnPropertySymbols) \
710710
CPP(ObjectGetPrototypeOf) \
711711
CPP(ObjectSetPrototypeOf) \
712-
CPP(ObjectIs) \
712+
TFJ(ObjectIs, 2, kLeft, kRight) \
713713
CPP(ObjectIsExtensible) \
714714
CPP(ObjectIsFrozen) \
715715
CPP(ObjectIsSealed) \

‎src/builtins/builtins-object-gen.cc‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,21 @@ TF_BUILTIN(ObjectCreate, ObjectBuiltinsAssembler) {
598598
}
599599
}
600600

601+
// ES #sec-object.is
602+
TF_BUILTIN(ObjectIs, ObjectBuiltinsAssembler) {
603+
Node* const left = Parameter(Descriptor::kLeft);
604+
Node* const right = Parameter(Descriptor::kRight);
605+
606+
Label return_true(this), return_false(this);
607+
BranchIfSameValue(left, right, &return_true, &return_false);
608+
609+
BIND(&return_true);
610+
Return(TrueConstant());
611+
612+
BIND(&return_false);
613+
Return(FalseConstant());
614+
}
615+
601616
TF_BUILTIN(CreateIterResultObject, ObjectBuiltinsAssembler) {
602617
Node* const value = Parameter(Descriptor::kValue);
603618
Node* const done = Parameter(Descriptor::kDone);

‎src/builtins/builtins-object.cc‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -369,15 +369,6 @@ BUILTIN(ObjectGetOwnPropertySymbols) {
369369
return GetOwnPropertyKeys(isolate, args, SKIP_STRINGS);
370370
}
371371

372-
// ES#sec-object.is Object.is ( value1, value2 )
373-
BUILTIN(ObjectIs) {
374-
SealHandleScope shs(isolate);
375-
DCHECK_EQ(3, args.length());
376-
Handle<Object> value1 = args.at(1);
377-
Handle<Object> value2 = args.at(2);
378-
return isolate->heap()->ToBoolean(value1->SameValue(*value2));
379-
}
380-
381372
// ES6 section 19.1.2.11 Object.isExtensible ( O )
382373
BUILTIN(ObjectIsExtensible) {
383374
HandleScope scope(isolate);

‎src/builtins/builtins-promise-gen.cc‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,8 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context,
683683
VARIABLE(var_reason, MachineRepresentation::kTagged);
684684
VARIABLE(var_then, MachineRepresentation::kTagged);
685685

686-
Label do_enqueue(this), fulfill(this), if_cycle(this, Label::kDeferred),
686+
Label do_enqueue(this), fulfill(this), if_nocycle(this),
687+
if_cycle(this, Label::kDeferred),
687688
if_rejectpromise(this, Label::kDeferred), out(this);
688689

689690
Label cycle_check(this);
@@ -693,7 +694,8 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context,
693694

694695
BIND(&cycle_check);
695696
// 6. If SameValue(resolution, promise) is true, then
696-
GotoIf(SameValue(promise, result), &if_cycle);
697+
BranchIfSameValue(promise, result, &if_cycle, &if_nocycle);
698+
BIND(&if_nocycle);
697699

698700
// 7. If Type(resolution) is not Object, then
699701
GotoIf(TaggedIsSmi(result), &fulfill);
@@ -1435,11 +1437,13 @@ TF_BUILTIN(PromiseResolve, PromiseBuiltinsAssembler) {
14351437
// they could be of the same subclass.
14361438
BIND(&if_value_or_constructor_are_not_native_promise);
14371439
{
1440+
Label if_return(this);
14381441
Node* const xConstructor =
14391442
GetProperty(context, value, isolate->factory()->constructor_string());
1443+
BranchIfSameValue(xConstructor, constructor, &if_return,
1444+
&if_need_to_allocate);
14401445

1441-
GotoIfNot(SameValue(xConstructor, constructor), &if_need_to_allocate);
1442-
1446+
BIND(&if_return);
14431447
Return(value);
14441448
}
14451449

‎src/builtins/builtins-proxy-gen.cc‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -517,9 +517,8 @@ void ProxiesCodeStubAssembler::CheckGetSetTrapResult(
517517

518518
// If SameValue(trapResult, targetDesc.[[Value]]) is false,
519519
// throw a TypeError exception.
520-
GotoIfNot(SameValue(trap_result, var_value.value()),
521-
&throw_non_configurable_data);
522-
Goto(check_passed);
520+
BranchIfSameValue(trap_result, var_value.value(), check_passed,
521+
&throw_non_configurable_data);
523522
}
524523

525524
BIND(&check_accessor);

‎src/builtins/builtins-regexp-gen.cc‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2208,9 +2208,10 @@ void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow(
22082208

22092209
// Ensure last index is 0.
22102210
{
2211-
Label next(this);
2212-
GotoIf(SameValue(previous_last_index, smi_zero), &next);
2211+
Label next(this), slow(this, Label::kDeferred);
2212+
BranchIfSameValue(previous_last_index, smi_zero, &next, &slow);
22132213

2214+
BIND(&slow);
22142215
SlowStoreLastIndex(context, regexp, smi_zero);
22152216
Goto(&next);
22162217
BIND(&next);
@@ -2221,14 +2222,14 @@ void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow(
22212222

22222223
// Reset last index if necessary.
22232224
{
2224-
Label next(this);
2225+
Label next(this), slow(this, Label::kDeferred);
22252226
Node* const current_last_index = SlowLoadLastIndex(context, regexp);
22262227

2227-
GotoIf(SameValue(current_last_index, previous_last_index), &next);
2228+
BranchIfSameValue(current_last_index, previous_last_index, &next, &slow);
22282229

2230+
BIND(&slow);
22292231
SlowStoreLastIndex(context, regexp, previous_last_index);
22302232
Goto(&next);
2231-
22322233
BIND(&next);
22332234
}
22342235

‎src/code-stub-assembler.cc‎

Lines changed: 79 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9010,88 +9010,107 @@ Node* CodeStubAssembler::StrictEqual(Node* lhs, Node* rhs,
90109010
// ECMA#sec-samevalue
90119011
// This algorithm differs from the Strict Equality Comparison Algorithm in its
90129012
// treatment of signed zeroes and NaNs.
9013-
Node* CodeStubAssembler::SameValue(Node* lhs, Node* rhs) {
9014-
VARIABLE(var_result, MachineRepresentation::kWord32);
9015-
Label strict_equal(this), out(this);
9013+
void CodeStubAssembler::BranchIfSameValue(Node* lhs, Node* rhs, Label* if_true,
9014+
Label* if_false) {
9015+
VARIABLE(var_lhs_value, MachineRepresentation::kFloat64);
9016+
VARIABLE(var_rhs_value, MachineRepresentation::kFloat64);
9017+
Label do_fcmp(this);
90169018

9017-
Node* const int_false = Int32Constant(0);
9018-
Node* const int_true = Int32Constant(1);
9019+
// Immediately jump to {if_true} if {lhs} == {rhs}, because - unlike
9020+
// StrictEqual - SameValue considers two NaNs to be equal.
9021+
GotoIf(WordEqual(lhs, rhs), if_true);
90199022

9020-
Label if_equal(this), if_notequal(this);
9021-
Branch(WordEqual(lhs, rhs), &if_equal, &if_notequal);
9023+
// Check if the {lhs} is a Smi.
9024+
Label if_lhsissmi(this), if_lhsisheapobject(this);
9025+
Branch(TaggedIsSmi(lhs), &if_lhsissmi, &if_lhsisheapobject);
90229026

9023-
BIND(&if_equal);
9027+
BIND(&if_lhsissmi);
90249028
{
9025-
// This covers the case when {lhs} == {rhs}. We can simply return true
9026-
// because SameValue considers two NaNs to be equal.
9027-
9028-
var_result.Bind(int_true);
9029-
Goto(&out);
9029+
// Since {lhs} is a Smi, the comparison can only yield true
9030+
// iff the {rhs} is a HeapNumber with the same float64 value.
9031+
GotoIf(TaggedIsSmi(rhs), if_false);
9032+
GotoIfNot(IsHeapNumber(rhs), if_false);
9033+
var_lhs_value.Bind(SmiToFloat64(lhs));
9034+
var_rhs_value.Bind(LoadHeapNumberValue(rhs));
9035+
Goto(&do_fcmp);
90309036
}
90319037

9032-
BIND(&if_notequal);
9038+
BIND(&if_lhsisheapobject);
90339039
{
9034-
// This covers the case when {lhs} != {rhs}. We only handle numbers here
9035-
// and defer to StrictEqual for the rest.
9036-
9037-
Node* const lhs_float = TryTaggedToFloat64(lhs, &strict_equal);
9038-
Node* const rhs_float = TryTaggedToFloat64(rhs, &strict_equal);
9040+
// Check if the {rhs} is a Smi.
9041+
Label if_rhsissmi(this), if_rhsisheapobject(this);
9042+
Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisheapobject);
90399043

9040-
Label if_lhsisnan(this), if_lhsnotnan(this);
9041-
BranchIfFloat64IsNaN(lhs_float, &if_lhsisnan, &if_lhsnotnan);
9042-
9043-
BIND(&if_lhsisnan);
9044+
BIND(&if_rhsissmi);
90449045
{
9045-
// Return true iff {rhs} is NaN.
9046-
9047-
Node* const result =
9048-
SelectConstant(Float64Equal(rhs_float, rhs_float), int_false,
9049-
int_true, MachineRepresentation::kWord32);
9050-
var_result.Bind(result);
9051-
Goto(&out);
9046+
// Since {rhs} is a Smi, the comparison can only yield true
9047+
// iff the {lhs} is a HeapNumber with the same float64 value.
9048+
GotoIfNot(IsHeapNumber(lhs), if_false);
9049+
var_lhs_value.Bind(LoadHeapNumberValue(lhs));
9050+
var_rhs_value.Bind(SmiToFloat64(rhs));
9051+
Goto(&do_fcmp);
90529052
}
90539053

9054-
BIND(&if_lhsnotnan);
9054+
BIND(&if_rhsisheapobject);
90559055
{
9056-
Label if_floatisequal(this), if_floatnotequal(this);
9057-
Branch(Float64Equal(lhs_float, rhs_float), &if_floatisequal,
9058-
&if_floatnotequal);
9059-
9060-
BIND(&if_floatisequal);
9056+
// Now this can only yield true if either both {lhs} and {rhs}
9057+
// are HeapNumbers with the same value or both {lhs} and {rhs}
9058+
// are Strings with the same character sequence.
9059+
Label if_lhsisheapnumber(this), if_lhsisstring(this);
9060+
Node* const lhs_map = LoadMap(lhs);
9061+
GotoIf(IsHeapNumberMap(lhs_map), &if_lhsisheapnumber);
9062+
Node* const lhs_instance_type = LoadMapInstanceType(lhs_map);
9063+
Branch(IsStringInstanceType(lhs_instance_type), &if_lhsisstring,
9064+
if_false);
9065+
9066+
BIND(&if_lhsisheapnumber);
90619067
{
9062-
// We still need to handle the case when {lhs} and {rhs} are -0.0 and
9063-
// 0.0 (or vice versa). Compare the high word to
9064-
// distinguish between the two.
9065-
9066-
Node* const lhs_hi_word = Float64ExtractHighWord32(lhs_float);
9067-
Node* const rhs_hi_word = Float64ExtractHighWord32(rhs_float);
9068-
9069-
// If x is +0 and y is -0, return false.
9070-
// If x is -0 and y is +0, return false.
9071-
9072-
Node* const result = Word32Equal(lhs_hi_word, rhs_hi_word);
9073-
var_result.Bind(result);
9074-
Goto(&out);
9068+
GotoIfNot(IsHeapNumber(rhs), if_false);
9069+
var_lhs_value.Bind(LoadHeapNumberValue(lhs));
9070+
var_rhs_value.Bind(LoadHeapNumberValue(rhs));
9071+
Goto(&do_fcmp);
90759072
}
90769073

9077-
BIND(&if_floatnotequal);
9074+
BIND(&if_lhsisstring);
90789075
{
9079-
var_result.Bind(int_false);
9080-
Goto(&out);
9076+
// Now we can only yield true if {rhs} is also a String
9077+
// with the same sequence of characters.
9078+
GotoIfNot(IsString(rhs), if_false);
9079+
Node* const result =
9080+
CallBuiltin(Builtins::kStringEqual, NoContextConstant(), lhs, rhs);
9081+
Branch(IsTrue(result), if_true, if_false);
90819082
}
90829083
}
90839084
}
90849085

9085-
BIND(&strict_equal);
9086+
BIND(&do_fcmp);
90869087
{
9087-
Node* const is_equal = StrictEqual(lhs, rhs);
9088-
Node* const result = WordEqual(is_equal, TrueConstant());
9089-
var_result.Bind(result);
9090-
Goto(&out);
9091-
}
9088+
Node* const lhs_value = var_lhs_value.value();
9089+
Node* const rhs_value = var_rhs_value.value();
90929090

9093-
BIND(&out);
9094-
return var_result.value();
9091+
Label if_equal(this), if_notequal(this);
9092+
Branch(Float64Equal(lhs_value, rhs_value), &if_equal, &if_notequal);
9093+
9094+
BIND(&if_equal);
9095+
{
9096+
// We still need to handle the case when {lhs} and {rhs} are -0.0 and
9097+
// 0.0 (or vice versa). Compare the high word to
9098+
// distinguish between the two.
9099+
Node* const lhs_hi_word = Float64ExtractHighWord32(lhs_value);
9100+
Node* const rhs_hi_word = Float64ExtractHighWord32(rhs_value);
9101+
9102+
// If x is +0 and y is -0, return false.
9103+
// If x is -0 and y is +0, return false.
9104+
Branch(Word32Equal(lhs_hi_word, rhs_hi_word), if_true, if_false);
9105+
}
9106+
9107+
BIND(&if_notequal);
9108+
{
9109+
// Return true iff both {rhs} and {lhs} are NaN.
9110+
GotoIf(Float64Equal(lhs_value, lhs_value), if_false);
9111+
Branch(Float64Equal(rhs_value, rhs_value), if_false, if_true);
9112+
}
9113+
}
90959114
}
90969115

90979116
Node* CodeStubAssembler::HasProperty(Node* object, Node* key, Node* context,

‎src/code-stub-assembler.h‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,9 +1580,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
15801580
// ECMA#sec-samevalue
15811581
// Similar to StrictEqual except that NaNs are treated as equal and minus zero
15821582
// differs from positive zero.
1583-
// Unlike Equal and StrictEqual, returns a value suitable for use in Branch
1584-
// instructions, e.g. Branch(SameValue(...), &label).
1585-
Node* SameValue(Node* lhs, Node* rhs);
1583+
void BranchIfSameValue(Node* lhs, Node* rhs, Label* if_true, Label* if_false);
15861584

15871585
enum HasPropertyLookupMode { kHasProperty, kForInHasProperty };
15881586

‎src/compiler/effect-control-linearizer.cc‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
721721
case IrOpcode::kObjectIsDetectableCallable:
722722
result = LowerObjectIsDetectableCallable(node);
723723
break;
724+
case IrOpcode::kObjectIsMinusZero:
725+
result = LowerObjectIsMinusZero(node);
726+
break;
724727
case IrOpcode::kObjectIsNaN:
725728
result = LowerObjectIsNaN(node);
726729
break;
@@ -1944,6 +1947,31 @@ Node* EffectControlLinearizer::LowerObjectIsDetectableCallable(Node* node) {
19441947
return done.PhiAt(0);
19451948
}
19461949

1950+
Node* EffectControlLinearizer::LowerObjectIsMinusZero(Node* node) {
1951+
Node* value = node->InputAt(0);
1952+
Node* zero = __ Int32Constant(0);
1953+
1954+
auto done = __ MakeLabel(MachineRepresentation::kBit);
1955+
1956+
// Check if {value} is a Smi.
1957+
__ GotoIf(ObjectIsSmi(value), &done, zero);
1958+
1959+
// Check if {value} is a HeapNumber.
1960+
Node* value_map = __ LoadField(AccessBuilder::ForMap(), value);
1961+
__ GotoIfNot(__ WordEqual(value_map, __ HeapNumberMapConstant()), &done,
1962+
zero);
1963+
1964+
// Check if {value} contains -0.
1965+
Node* value_value = __ LoadField(AccessBuilder::ForHeapNumberValue(), value);
1966+
__ Goto(&done,
1967+
__ Float64Equal(
1968+
__ Float64Div(__ Float64Constant(1.0), value_value),
1969+
__ Float64Constant(-std::numeric_limits<double>::infinity())));
1970+
1971+
__ Bind(&done);
1972+
return done.PhiAt(0);
1973+
}
1974+
19471975
Node* EffectControlLinearizer::LowerObjectIsNaN(Node* node) {
19481976
Node* value = node->InputAt(0);
19491977
Node* zero = __ Int32Constant(0);

‎src/compiler/effect-control-linearizer.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
8787
Node* LowerObjectIsArrayBufferView(Node* node);
8888
Node* LowerObjectIsCallable(Node* node);
8989
Node* LowerObjectIsDetectableCallable(Node* node);
90+
Node* LowerObjectIsMinusZero(Node* node);
9091
Node* LowerObjectIsNaN(Node* node);
9192
Node* LowerObjectIsNonCallable(Node* node);
9293
Node* LowerObjectIsNumber(Node* node);

0 commit comments

Comments
 (0)