Commit d4da17c6 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[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

  https://github.com/nodejs/node/pull/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/700254Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48275}
parent bfb43f8c
...@@ -709,7 +709,7 @@ namespace internal { ...@@ -709,7 +709,7 @@ namespace internal {
CPP(ObjectGetOwnPropertySymbols) \ CPP(ObjectGetOwnPropertySymbols) \
CPP(ObjectGetPrototypeOf) \ CPP(ObjectGetPrototypeOf) \
CPP(ObjectSetPrototypeOf) \ CPP(ObjectSetPrototypeOf) \
CPP(ObjectIs) \ TFJ(ObjectIs, 2, kLeft, kRight) \
CPP(ObjectIsExtensible) \ CPP(ObjectIsExtensible) \
CPP(ObjectIsFrozen) \ CPP(ObjectIsFrozen) \
CPP(ObjectIsSealed) \ CPP(ObjectIsSealed) \
......
...@@ -598,6 +598,21 @@ TF_BUILTIN(ObjectCreate, ObjectBuiltinsAssembler) { ...@@ -598,6 +598,21 @@ TF_BUILTIN(ObjectCreate, ObjectBuiltinsAssembler) {
} }
} }
// ES #sec-object.is
TF_BUILTIN(ObjectIs, ObjectBuiltinsAssembler) {
Node* const left = Parameter(Descriptor::kLeft);
Node* const right = Parameter(Descriptor::kRight);
Label return_true(this), return_false(this);
BranchIfSameValue(left, right, &return_true, &return_false);
BIND(&return_true);
Return(TrueConstant());
BIND(&return_false);
Return(FalseConstant());
}
TF_BUILTIN(CreateIterResultObject, ObjectBuiltinsAssembler) { TF_BUILTIN(CreateIterResultObject, ObjectBuiltinsAssembler) {
Node* const value = Parameter(Descriptor::kValue); Node* const value = Parameter(Descriptor::kValue);
Node* const done = Parameter(Descriptor::kDone); Node* const done = Parameter(Descriptor::kDone);
......
...@@ -369,15 +369,6 @@ BUILTIN(ObjectGetOwnPropertySymbols) { ...@@ -369,15 +369,6 @@ BUILTIN(ObjectGetOwnPropertySymbols) {
return GetOwnPropertyKeys(isolate, args, SKIP_STRINGS); return GetOwnPropertyKeys(isolate, args, SKIP_STRINGS);
} }
// ES#sec-object.is Object.is ( value1, value2 )
BUILTIN(ObjectIs) {
SealHandleScope shs(isolate);
DCHECK_EQ(3, args.length());
Handle<Object> value1 = args.at(1);
Handle<Object> value2 = args.at(2);
return isolate->heap()->ToBoolean(value1->SameValue(*value2));
}
// ES6 section 19.1.2.11 Object.isExtensible ( O ) // ES6 section 19.1.2.11 Object.isExtensible ( O )
BUILTIN(ObjectIsExtensible) { BUILTIN(ObjectIsExtensible) {
HandleScope scope(isolate); HandleScope scope(isolate);
......
...@@ -683,7 +683,8 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context, ...@@ -683,7 +683,8 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context,
VARIABLE(var_reason, MachineRepresentation::kTagged); VARIABLE(var_reason, MachineRepresentation::kTagged);
VARIABLE(var_then, MachineRepresentation::kTagged); VARIABLE(var_then, MachineRepresentation::kTagged);
Label do_enqueue(this), fulfill(this), if_cycle(this, Label::kDeferred), Label do_enqueue(this), fulfill(this), if_nocycle(this),
if_cycle(this, Label::kDeferred),
if_rejectpromise(this, Label::kDeferred), out(this); if_rejectpromise(this, Label::kDeferred), out(this);
Label cycle_check(this); Label cycle_check(this);
...@@ -693,7 +694,8 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context, ...@@ -693,7 +694,8 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context,
BIND(&cycle_check); BIND(&cycle_check);
// 6. If SameValue(resolution, promise) is true, then // 6. If SameValue(resolution, promise) is true, then
GotoIf(SameValue(promise, result), &if_cycle); BranchIfSameValue(promise, result, &if_cycle, &if_nocycle);
BIND(&if_nocycle);
// 7. If Type(resolution) is not Object, then // 7. If Type(resolution) is not Object, then
GotoIf(TaggedIsSmi(result), &fulfill); GotoIf(TaggedIsSmi(result), &fulfill);
...@@ -1435,11 +1437,13 @@ TF_BUILTIN(PromiseResolve, PromiseBuiltinsAssembler) { ...@@ -1435,11 +1437,13 @@ TF_BUILTIN(PromiseResolve, PromiseBuiltinsAssembler) {
// they could be of the same subclass. // they could be of the same subclass.
BIND(&if_value_or_constructor_are_not_native_promise); BIND(&if_value_or_constructor_are_not_native_promise);
{ {
Label if_return(this);
Node* const xConstructor = Node* const xConstructor =
GetProperty(context, value, isolate->factory()->constructor_string()); GetProperty(context, value, isolate->factory()->constructor_string());
BranchIfSameValue(xConstructor, constructor, &if_return,
&if_need_to_allocate);
GotoIfNot(SameValue(xConstructor, constructor), &if_need_to_allocate); BIND(&if_return);
Return(value); Return(value);
} }
......
...@@ -517,9 +517,8 @@ void ProxiesCodeStubAssembler::CheckGetSetTrapResult( ...@@ -517,9 +517,8 @@ void ProxiesCodeStubAssembler::CheckGetSetTrapResult(
// If SameValue(trapResult, targetDesc.[[Value]]) is false, // If SameValue(trapResult, targetDesc.[[Value]]) is false,
// throw a TypeError exception. // throw a TypeError exception.
GotoIfNot(SameValue(trap_result, var_value.value()), BranchIfSameValue(trap_result, var_value.value(), check_passed,
&throw_non_configurable_data); &throw_non_configurable_data);
Goto(check_passed);
} }
BIND(&check_accessor); BIND(&check_accessor);
......
...@@ -2208,9 +2208,10 @@ void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow( ...@@ -2208,9 +2208,10 @@ void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow(
// Ensure last index is 0. // Ensure last index is 0.
{ {
Label next(this); Label next(this), slow(this, Label::kDeferred);
GotoIf(SameValue(previous_last_index, smi_zero), &next); BranchIfSameValue(previous_last_index, smi_zero, &next, &slow);
BIND(&slow);
SlowStoreLastIndex(context, regexp, smi_zero); SlowStoreLastIndex(context, regexp, smi_zero);
Goto(&next); Goto(&next);
BIND(&next); BIND(&next);
...@@ -2221,14 +2222,14 @@ void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow( ...@@ -2221,14 +2222,14 @@ void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow(
// Reset last index if necessary. // Reset last index if necessary.
{ {
Label next(this); Label next(this), slow(this, Label::kDeferred);
Node* const current_last_index = SlowLoadLastIndex(context, regexp); Node* const current_last_index = SlowLoadLastIndex(context, regexp);
GotoIf(SameValue(current_last_index, previous_last_index), &next); BranchIfSameValue(current_last_index, previous_last_index, &next, &slow);
BIND(&slow);
SlowStoreLastIndex(context, regexp, previous_last_index); SlowStoreLastIndex(context, regexp, previous_last_index);
Goto(&next); Goto(&next);
BIND(&next); BIND(&next);
} }
......
...@@ -9010,88 +9010,107 @@ Node* CodeStubAssembler::StrictEqual(Node* lhs, Node* rhs, ...@@ -9010,88 +9010,107 @@ Node* CodeStubAssembler::StrictEqual(Node* lhs, Node* rhs,
// ECMA#sec-samevalue // ECMA#sec-samevalue
// This algorithm differs from the Strict Equality Comparison Algorithm in its // This algorithm differs from the Strict Equality Comparison Algorithm in its
// treatment of signed zeroes and NaNs. // treatment of signed zeroes and NaNs.
Node* CodeStubAssembler::SameValue(Node* lhs, Node* rhs) { void CodeStubAssembler::BranchIfSameValue(Node* lhs, Node* rhs, Label* if_true,
VARIABLE(var_result, MachineRepresentation::kWord32); Label* if_false) {
Label strict_equal(this), out(this); VARIABLE(var_lhs_value, MachineRepresentation::kFloat64);
VARIABLE(var_rhs_value, MachineRepresentation::kFloat64);
Label do_fcmp(this);
Node* const int_false = Int32Constant(0); // Immediately jump to {if_true} if {lhs} == {rhs}, because - unlike
Node* const int_true = Int32Constant(1); // StrictEqual - SameValue considers two NaNs to be equal.
GotoIf(WordEqual(lhs, rhs), if_true);
Label if_equal(this), if_notequal(this); // Check if the {lhs} is a Smi.
Branch(WordEqual(lhs, rhs), &if_equal, &if_notequal); Label if_lhsissmi(this), if_lhsisheapobject(this);
Branch(TaggedIsSmi(lhs), &if_lhsissmi, &if_lhsisheapobject);
BIND(&if_equal); BIND(&if_lhsissmi);
{ {
// This covers the case when {lhs} == {rhs}. We can simply return true // Since {lhs} is a Smi, the comparison can only yield true
// because SameValue considers two NaNs to be equal. // iff the {rhs} is a HeapNumber with the same float64 value.
GotoIf(TaggedIsSmi(rhs), if_false);
var_result.Bind(int_true); GotoIfNot(IsHeapNumber(rhs), if_false);
Goto(&out); var_lhs_value.Bind(SmiToFloat64(lhs));
var_rhs_value.Bind(LoadHeapNumberValue(rhs));
Goto(&do_fcmp);
} }
BIND(&if_notequal); BIND(&if_lhsisheapobject);
{ {
// This covers the case when {lhs} != {rhs}. We only handle numbers here // Check if the {rhs} is a Smi.
// and defer to StrictEqual for the rest. Label if_rhsissmi(this), if_rhsisheapobject(this);
Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisheapobject);
Node* const lhs_float = TryTaggedToFloat64(lhs, &strict_equal);
Node* const rhs_float = TryTaggedToFloat64(rhs, &strict_equal);
Label if_lhsisnan(this), if_lhsnotnan(this); BIND(&if_rhsissmi);
BranchIfFloat64IsNaN(lhs_float, &if_lhsisnan, &if_lhsnotnan);
BIND(&if_lhsisnan);
{ {
// Return true iff {rhs} is NaN. // Since {rhs} is a Smi, the comparison can only yield true
// iff the {lhs} is a HeapNumber with the same float64 value.
Node* const result = GotoIfNot(IsHeapNumber(lhs), if_false);
SelectConstant(Float64Equal(rhs_float, rhs_float), int_false, var_lhs_value.Bind(LoadHeapNumberValue(lhs));
int_true, MachineRepresentation::kWord32); var_rhs_value.Bind(SmiToFloat64(rhs));
var_result.Bind(result); Goto(&do_fcmp);
Goto(&out);
} }
BIND(&if_lhsnotnan); BIND(&if_rhsisheapobject);
{ {
Label if_floatisequal(this), if_floatnotequal(this); // Now this can only yield true if either both {lhs} and {rhs}
Branch(Float64Equal(lhs_float, rhs_float), &if_floatisequal, // are HeapNumbers with the same value or both {lhs} and {rhs}
&if_floatnotequal); // are Strings with the same character sequence.
Label if_lhsisheapnumber(this), if_lhsisstring(this);
BIND(&if_floatisequal); Node* const lhs_map = LoadMap(lhs);
GotoIf(IsHeapNumberMap(lhs_map), &if_lhsisheapnumber);
Node* const lhs_instance_type = LoadMapInstanceType(lhs_map);
Branch(IsStringInstanceType(lhs_instance_type), &if_lhsisstring,
if_false);
BIND(&if_lhsisheapnumber);
{ {
// We still need to handle the case when {lhs} and {rhs} are -0.0 and GotoIfNot(IsHeapNumber(rhs), if_false);
// 0.0 (or vice versa). Compare the high word to var_lhs_value.Bind(LoadHeapNumberValue(lhs));
// distinguish between the two. var_rhs_value.Bind(LoadHeapNumberValue(rhs));
Goto(&do_fcmp);
Node* const lhs_hi_word = Float64ExtractHighWord32(lhs_float);
Node* const rhs_hi_word = Float64ExtractHighWord32(rhs_float);
// If x is +0 and y is -0, return false.
// If x is -0 and y is +0, return false.
Node* const result = Word32Equal(lhs_hi_word, rhs_hi_word);
var_result.Bind(result);
Goto(&out);
} }
BIND(&if_floatnotequal); BIND(&if_lhsisstring);
{ {
var_result.Bind(int_false); // Now we can only yield true if {rhs} is also a String
Goto(&out); // with the same sequence of characters.
GotoIfNot(IsString(rhs), if_false);
Node* const result =
CallBuiltin(Builtins::kStringEqual, NoContextConstant(), lhs, rhs);
Branch(IsTrue(result), if_true, if_false);
} }
} }
} }
BIND(&strict_equal); BIND(&do_fcmp);
{ {
Node* const is_equal = StrictEqual(lhs, rhs); Node* const lhs_value = var_lhs_value.value();
Node* const result = WordEqual(is_equal, TrueConstant()); Node* const rhs_value = var_rhs_value.value();
var_result.Bind(result);
Goto(&out);
}
BIND(&out); Label if_equal(this), if_notequal(this);
return var_result.value(); Branch(Float64Equal(lhs_value, rhs_value), &if_equal, &if_notequal);
BIND(&if_equal);
{
// We still need to handle the case when {lhs} and {rhs} are -0.0 and
// 0.0 (or vice versa). Compare the high word to
// distinguish between the two.
Node* const lhs_hi_word = Float64ExtractHighWord32(lhs_value);
Node* const rhs_hi_word = Float64ExtractHighWord32(rhs_value);
// If x is +0 and y is -0, return false.
// If x is -0 and y is +0, return false.
Branch(Word32Equal(lhs_hi_word, rhs_hi_word), if_true, if_false);
}
BIND(&if_notequal);
{
// Return true iff both {rhs} and {lhs} are NaN.
GotoIf(Float64Equal(lhs_value, lhs_value), if_false);
Branch(Float64Equal(rhs_value, rhs_value), if_false, if_true);
}
}
} }
Node* CodeStubAssembler::HasProperty(Node* object, Node* key, Node* context, Node* CodeStubAssembler::HasProperty(Node* object, Node* key, Node* context,
......
...@@ -1580,9 +1580,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler { ...@@ -1580,9 +1580,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
// ECMA#sec-samevalue // ECMA#sec-samevalue
// Similar to StrictEqual except that NaNs are treated as equal and minus zero // Similar to StrictEqual except that NaNs are treated as equal and minus zero
// differs from positive zero. // differs from positive zero.
// Unlike Equal and StrictEqual, returns a value suitable for use in Branch void BranchIfSameValue(Node* lhs, Node* rhs, Label* if_true, Label* if_false);
// instructions, e.g. Branch(SameValue(...), &label).
Node* SameValue(Node* lhs, Node* rhs);
enum HasPropertyLookupMode { kHasProperty, kForInHasProperty }; enum HasPropertyLookupMode { kHasProperty, kForInHasProperty };
......
...@@ -721,6 +721,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node, ...@@ -721,6 +721,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kObjectIsDetectableCallable: case IrOpcode::kObjectIsDetectableCallable:
result = LowerObjectIsDetectableCallable(node); result = LowerObjectIsDetectableCallable(node);
break; break;
case IrOpcode::kObjectIsMinusZero:
result = LowerObjectIsMinusZero(node);
break;
case IrOpcode::kObjectIsNaN: case IrOpcode::kObjectIsNaN:
result = LowerObjectIsNaN(node); result = LowerObjectIsNaN(node);
break; break;
...@@ -1944,6 +1947,31 @@ Node* EffectControlLinearizer::LowerObjectIsDetectableCallable(Node* node) { ...@@ -1944,6 +1947,31 @@ Node* EffectControlLinearizer::LowerObjectIsDetectableCallable(Node* node) {
return done.PhiAt(0); return done.PhiAt(0);
} }
Node* EffectControlLinearizer::LowerObjectIsMinusZero(Node* node) {
Node* value = node->InputAt(0);
Node* zero = __ Int32Constant(0);
auto done = __ MakeLabel(MachineRepresentation::kBit);
// Check if {value} is a Smi.
__ GotoIf(ObjectIsSmi(value), &done, zero);
// Check if {value} is a HeapNumber.
Node* value_map = __ LoadField(AccessBuilder::ForMap(), value);
__ GotoIfNot(__ WordEqual(value_map, __ HeapNumberMapConstant()), &done,
zero);
// Check if {value} contains -0.
Node* value_value = __ LoadField(AccessBuilder::ForHeapNumberValue(), value);
__ Goto(&done,
__ Float64Equal(
__ Float64Div(__ Float64Constant(1.0), value_value),
__ Float64Constant(-std::numeric_limits<double>::infinity())));
__ Bind(&done);
return done.PhiAt(0);
}
Node* EffectControlLinearizer::LowerObjectIsNaN(Node* node) { Node* EffectControlLinearizer::LowerObjectIsNaN(Node* node) {
Node* value = node->InputAt(0); Node* value = node->InputAt(0);
Node* zero = __ Int32Constant(0); Node* zero = __ Int32Constant(0);
......
...@@ -87,6 +87,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer { ...@@ -87,6 +87,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* LowerObjectIsArrayBufferView(Node* node); Node* LowerObjectIsArrayBufferView(Node* node);
Node* LowerObjectIsCallable(Node* node); Node* LowerObjectIsCallable(Node* node);
Node* LowerObjectIsDetectableCallable(Node* node); Node* LowerObjectIsDetectableCallable(Node* node);
Node* LowerObjectIsMinusZero(Node* node);
Node* LowerObjectIsNaN(Node* node); Node* LowerObjectIsNaN(Node* node);
Node* LowerObjectIsNonCallable(Node* node); Node* LowerObjectIsNonCallable(Node* node);
Node* LowerObjectIsNumber(Node* node); Node* LowerObjectIsNumber(Node* node);
......
...@@ -52,6 +52,7 @@ namespace compiler { ...@@ -52,6 +52,7 @@ namespace compiler {
V(Int32LessThan) \ V(Int32LessThan) \
V(Float64Add) \ V(Float64Add) \
V(Float64Sub) \ V(Float64Sub) \
V(Float64Div) \
V(Float64Mod) \ V(Float64Mod) \
V(Float64Equal) \ V(Float64Equal) \
V(Float64LessThan) \ V(Float64LessThan) \
......
...@@ -2496,6 +2496,52 @@ Reduction JSBuiltinReducer::ReduceObjectCreate(Node* node) { ...@@ -2496,6 +2496,52 @@ Reduction JSBuiltinReducer::ReduceObjectCreate(Node* node) {
return Replace(value); return Replace(value);
} }
// ES #sec-object.is
Reduction JSBuiltinReducer::ReduceObjectIs(Node* node) {
// TODO(turbofan): At some point we should probably introduce a new
// SameValue simplified operator (and also a StrictEqual simplified
// operator) and create unified handling in SimplifiedLowering.
JSCallReduction r(node);
if (r.left() == r.right()) {
// Object.is(x,x) => #true
Node* value = jsgraph()->TrueConstant();
return Replace(value);
} else if (r.InputsMatchTwo(Type::Unique(), Type::Unique())) {
// Object.is(x:Unique,y:Unique) => ReferenceEqual(x,y)
Node* left = r.GetJSCallInput(0);
Node* right = r.GetJSCallInput(1);
Node* value = graph()->NewNode(simplified()->ReferenceEqual(), left, right);
return Replace(value);
} else if (r.InputsMatchTwo(Type::MinusZero(), Type::Any())) {
// Object.is(x:MinusZero,y) => ObjectIsMinusZero(y)
Node* input = r.GetJSCallInput(1);
Node* value = graph()->NewNode(simplified()->ObjectIsMinusZero(), input);
return Replace(value);
} else if (r.InputsMatchTwo(Type::Any(), Type::MinusZero())) {
// Object.is(x,y:MinusZero) => ObjectIsMinusZero(x)
Node* input = r.GetJSCallInput(0);
Node* value = graph()->NewNode(simplified()->ObjectIsMinusZero(), input);
return Replace(value);
} else if (r.InputsMatchTwo(Type::NaN(), Type::Any())) {
// Object.is(x:NaN,y) => ObjectIsNaN(y)
Node* input = r.GetJSCallInput(1);
Node* value = graph()->NewNode(simplified()->ObjectIsNaN(), input);
return Replace(value);
} else if (r.InputsMatchTwo(Type::Any(), Type::NaN())) {
// Object.is(x,y:NaN) => ObjectIsNaN(x)
Node* input = r.GetJSCallInput(0);
Node* value = graph()->NewNode(simplified()->ObjectIsNaN(), input);
return Replace(value);
} else if (r.InputsMatchTwo(Type::String(), Type::String())) {
// Object.is(x:String,y:String) => StringEqual(x,y)
Node* left = r.GetJSCallInput(0);
Node* right = r.GetJSCallInput(1);
Node* value = graph()->NewNode(simplified()->StringEqual(), left, right);
return Replace(value);
}
return NoChange();
}
// ES6 section 21.1.2.1 String.fromCharCode ( ...codeUnits ) // ES6 section 21.1.2.1 String.fromCharCode ( ...codeUnits )
Reduction JSBuiltinReducer::ReduceStringFromCharCode(Node* node) { Reduction JSBuiltinReducer::ReduceStringFromCharCode(Node* node) {
JSCallReduction r(node); JSCallReduction r(node);
...@@ -3127,6 +3173,9 @@ Reduction JSBuiltinReducer::Reduce(Node* node) { ...@@ -3127,6 +3173,9 @@ Reduction JSBuiltinReducer::Reduce(Node* node) {
case kObjectCreate: case kObjectCreate:
reduction = ReduceObjectCreate(node); reduction = ReduceObjectCreate(node);
break; break;
case kObjectIs:
reduction = ReduceObjectIs(node);
break;
case kSetEntries: case kSetEntries:
return ReduceCollectionIterator( return ReduceCollectionIterator(
node, JS_SET_TYPE, Context::SET_KEY_VALUE_ITERATOR_MAP_INDEX); node, JS_SET_TYPE, Context::SET_KEY_VALUE_ITERATOR_MAP_INDEX);
......
...@@ -111,6 +111,7 @@ class V8_EXPORT_PRIVATE JSBuiltinReducer final ...@@ -111,6 +111,7 @@ class V8_EXPORT_PRIVATE JSBuiltinReducer final
Reduction ReduceNumberIsSafeInteger(Node* node); Reduction ReduceNumberIsSafeInteger(Node* node);
Reduction ReduceNumberParseInt(Node* node); Reduction ReduceNumberParseInt(Node* node);
Reduction ReduceObjectCreate(Node* node); Reduction ReduceObjectCreate(Node* node);
Reduction ReduceObjectIs(Node* node);
Reduction ReduceStringCharAt(Node* node); Reduction ReduceStringCharAt(Node* node);
Reduction ReduceStringCharCodeAt(Node* node); Reduction ReduceStringCharCodeAt(Node* node);
Reduction ReduceStringConcat(Node* node); Reduction ReduceStringConcat(Node* node);
......
...@@ -355,6 +355,7 @@ ...@@ -355,6 +355,7 @@
V(ObjectIsArrayBufferView) \ V(ObjectIsArrayBufferView) \
V(ObjectIsCallable) \ V(ObjectIsCallable) \
V(ObjectIsDetectableCallable) \ V(ObjectIsDetectableCallable) \
V(ObjectIsMinusZero) \
V(ObjectIsNaN) \ V(ObjectIsNaN) \
V(ObjectIsNonCallable) \ V(ObjectIsNonCallable) \
V(ObjectIsNumber) \ V(ObjectIsNumber) \
......
...@@ -2677,6 +2677,39 @@ class RepresentationSelector { ...@@ -2677,6 +2677,39 @@ class RepresentationSelector {
VisitObjectIs(node, Type::DetectableCallable(), lowering); VisitObjectIs(node, Type::DetectableCallable(), lowering);
return; return;
} }
case IrOpcode::kObjectIsMinusZero: {
Type* const input_type = GetUpperBound(node->InputAt(0));
if (input_type->Is(Type::MinusZero())) {
VisitUnop(node, UseInfo::None(), MachineRepresentation::kBit);
if (lower()) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(1));
}
} else if (!input_type->Maybe(Type::MinusZero())) {
VisitUnop(node, UseInfo::Any(), MachineRepresentation::kBit);
if (lower()) {
DeferReplacement(node, lowering->jsgraph()->Int32Constant(0));
}
} else if (input_type->Is(Type::Number())) {
VisitUnop(node, UseInfo::TruncatingFloat64(),
MachineRepresentation::kBit);
if (lower()) {
// ObjectIsMinusZero(x:kRepFloat64)
// => Float64Equal(Float64Div(1.0,x),-Infinity)
Node* const input = node->InputAt(0);
node->ReplaceInput(
0, jsgraph_->graph()->NewNode(
lowering->machine()->Float64Div(),
lowering->jsgraph()->Float64Constant(1.0), input));
node->AppendInput(jsgraph_->zone(),
jsgraph_->Float64Constant(
-std::numeric_limits<double>::infinity()));
NodeProperties::ChangeOp(node, lowering->machine()->Float64Equal());
}
} else {
VisitUnop(node, UseInfo::AnyTagged(), MachineRepresentation::kBit);
}
return;
}
case IrOpcode::kObjectIsNaN: { case IrOpcode::kObjectIsNaN: {
Type* const input_type = GetUpperBound(node->InputAt(0)); Type* const input_type = GetUpperBound(node->InputAt(0));
if (input_type->Is(Type::NaN())) { if (input_type->Is(Type::NaN())) {
......
...@@ -498,6 +498,7 @@ BailoutReason BailoutReasonOf(const Operator* op) { ...@@ -498,6 +498,7 @@ BailoutReason BailoutReasonOf(const Operator* op) {
V(ObjectIsArrayBufferView, Operator::kNoProperties, 1, 0) \ V(ObjectIsArrayBufferView, Operator::kNoProperties, 1, 0) \
V(ObjectIsCallable, Operator::kNoProperties, 1, 0) \ V(ObjectIsCallable, Operator::kNoProperties, 1, 0) \
V(ObjectIsDetectableCallable, Operator::kNoProperties, 1, 0) \ V(ObjectIsDetectableCallable, Operator::kNoProperties, 1, 0) \
V(ObjectIsMinusZero, Operator::kNoProperties, 1, 0) \
V(ObjectIsNaN, Operator::kNoProperties, 1, 0) \ V(ObjectIsNaN, Operator::kNoProperties, 1, 0) \
V(ObjectIsNonCallable, Operator::kNoProperties, 1, 0) \ V(ObjectIsNonCallable, Operator::kNoProperties, 1, 0) \
V(ObjectIsNumber, Operator::kNoProperties, 1, 0) \ V(ObjectIsNumber, Operator::kNoProperties, 1, 0) \
......
...@@ -438,6 +438,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final ...@@ -438,6 +438,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* ObjectIsArrayBufferView(); const Operator* ObjectIsArrayBufferView();
const Operator* ObjectIsCallable(); const Operator* ObjectIsCallable();
const Operator* ObjectIsDetectableCallable(); const Operator* ObjectIsDetectableCallable();
const Operator* ObjectIsMinusZero();
const Operator* ObjectIsNaN(); const Operator* ObjectIsNaN();
const Operator* ObjectIsNonCallable(); const Operator* ObjectIsNonCallable();
const Operator* ObjectIsNumber(); const Operator* ObjectIsNumber();
......
...@@ -290,6 +290,7 @@ class Typer::Visitor : public Reducer { ...@@ -290,6 +290,7 @@ class Typer::Visitor : public Reducer {
static Type* ObjectIsArrayBufferView(Type*, Typer*); static Type* ObjectIsArrayBufferView(Type*, Typer*);
static Type* ObjectIsCallable(Type*, Typer*); static Type* ObjectIsCallable(Type*, Typer*);
static Type* ObjectIsDetectableCallable(Type*, Typer*); static Type* ObjectIsDetectableCallable(Type*, Typer*);
static Type* ObjectIsMinusZero(Type*, Typer*);
static Type* ObjectIsNaN(Type*, Typer*); static Type* ObjectIsNaN(Type*, Typer*);
static Type* ObjectIsNonCallable(Type*, Typer*); static Type* ObjectIsNonCallable(Type*, Typer*);
static Type* ObjectIsNumber(Type*, Typer*); static Type* ObjectIsNumber(Type*, Typer*);
...@@ -528,6 +529,12 @@ Type* Typer::Visitor::ObjectIsDetectableCallable(Type* type, Typer* t) { ...@@ -528,6 +529,12 @@ Type* Typer::Visitor::ObjectIsDetectableCallable(Type* type, Typer* t) {
return Type::Boolean(); return Type::Boolean();
} }
Type* Typer::Visitor::ObjectIsMinusZero(Type* type, Typer* t) {
if (type->Is(Type::MinusZero())) return t->singleton_true_;
if (!type->Maybe(Type::MinusZero())) return t->singleton_false_;
return Type::Boolean();
}
Type* Typer::Visitor::ObjectIsNaN(Type* type, Typer* t) { Type* Typer::Visitor::ObjectIsNaN(Type* type, Typer* t) {
if (type->Is(Type::NaN())) return t->singleton_true_; if (type->Is(Type::NaN())) return t->singleton_true_;
if (!type->Maybe(Type::NaN())) return t->singleton_false_; if (!type->Maybe(Type::NaN())) return t->singleton_false_;
...@@ -1544,6 +1551,7 @@ Type* Typer::Visitor::JSCallTyper(Type* fun, Typer* t) { ...@@ -1544,6 +1551,7 @@ Type* Typer::Visitor::JSCallTyper(Type* fun, Typer* t) {
return Type::Receiver(); return Type::Receiver();
case kObjectCreate: case kObjectCreate:
return Type::OtherObject(); return Type::OtherObject();
case kObjectIs:
case kObjectHasOwnProperty: case kObjectHasOwnProperty:
case kObjectIsPrototypeOf: case kObjectIsPrototypeOf:
return Type::Boolean(); return Type::Boolean();
...@@ -1980,6 +1988,10 @@ Type* Typer::Visitor::TypeObjectIsDetectableCallable(Node* node) { ...@@ -1980,6 +1988,10 @@ Type* Typer::Visitor::TypeObjectIsDetectableCallable(Node* node) {
return TypeUnaryOp(node, ObjectIsDetectableCallable); return TypeUnaryOp(node, ObjectIsDetectableCallable);
} }
Type* Typer::Visitor::TypeObjectIsMinusZero(Node* node) {
return TypeUnaryOp(node, ObjectIsMinusZero);
}
Type* Typer::Visitor::TypeObjectIsNaN(Node* node) { Type* Typer::Visitor::TypeObjectIsNaN(Node* node) {
return TypeUnaryOp(node, ObjectIsNaN); return TypeUnaryOp(node, ObjectIsNaN);
} }
......
...@@ -1008,6 +1008,7 @@ void Verifier::Visitor::Check(Node* node) { ...@@ -1008,6 +1008,7 @@ void Verifier::Visitor::Check(Node* node) {
case IrOpcode::kObjectIsArrayBufferView: case IrOpcode::kObjectIsArrayBufferView:
case IrOpcode::kObjectIsCallable: case IrOpcode::kObjectIsCallable:
case IrOpcode::kObjectIsDetectableCallable: case IrOpcode::kObjectIsDetectableCallable:
case IrOpcode::kObjectIsMinusZero:
case IrOpcode::kObjectIsNaN: case IrOpcode::kObjectIsNaN:
case IrOpcode::kObjectIsNonCallable: case IrOpcode::kObjectIsNonCallable:
case IrOpcode::kObjectIsNumber: case IrOpcode::kObjectIsNumber:
......
...@@ -4519,6 +4519,7 @@ class ContextExtension : public Struct { ...@@ -4519,6 +4519,7 @@ class ContextExtension : public Struct {
V(Function.prototype, call, FunctionCall) \ V(Function.prototype, call, FunctionCall) \
V(Object, assign, ObjectAssign) \ V(Object, assign, ObjectAssign) \
V(Object, create, ObjectCreate) \ V(Object, create, ObjectCreate) \
V(Object, is, ObjectIs) \
V(Object.prototype, hasOwnProperty, ObjectHasOwnProperty) \ V(Object.prototype, hasOwnProperty, ObjectHasOwnProperty) \
V(Object.prototype, isPrototypeOf, ObjectIsPrototypeOf) \ V(Object.prototype, isPrototypeOf, ObjectIsPrototypeOf) \
V(Object.prototype, toString, ObjectToString) \ V(Object.prototype, toString, ObjectToString) \
......
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax
(function() {
function foo(o) { return Object.is(o, -0); }
assertTrue(foo(-0));
assertFalse(foo(0));
assertFalse(foo(NaN));
assertFalse(foo(''));
assertFalse(foo([]));
assertFalse(foo({}));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(-0));
assertFalse(foo(0));
assertFalse(foo(NaN));
assertFalse(foo(''));
assertFalse(foo([]));
assertFalse(foo({}));
})();
(function() {
function foo(o) { return Object.is(-0, o); }
assertTrue(foo(-0));
assertFalse(foo(0));
assertFalse(foo(NaN));
assertFalse(foo(''));
assertFalse(foo([]));
assertFalse(foo({}));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(-0));
assertFalse(foo(0));
assertFalse(foo(NaN));
assertFalse(foo(''));
assertFalse(foo([]));
assertFalse(foo({}));
})();
(function() {
function foo(o) { return Object.is(+o, -0); }
assertTrue(foo(-0));
assertFalse(foo(0));
assertFalse(foo(NaN));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(-0));
assertFalse(foo(0));
assertFalse(foo(NaN));
})();
(function() {
function foo(o) { return Object.is(-0, +o); }
assertTrue(foo(-0));
assertFalse(foo(0));
assertFalse(foo(NaN));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(-0));
assertFalse(foo(0));
assertFalse(foo(NaN));
})();
(function() {
function foo(o) { return Object.is(o, NaN); }
assertFalse(foo(-0));
assertFalse(foo(0));
assertTrue(foo(NaN));
assertFalse(foo(''));
assertFalse(foo([]));
assertFalse(foo({}));
%OptimizeFunctionOnNextCall(foo);
assertFalse(foo(-0));
assertFalse(foo(0));
assertTrue(foo(NaN));
assertFalse(foo(''));
assertFalse(foo([]));
assertFalse(foo({}));
})();
(function() {
function foo(o) { return Object.is(NaN, o); }
assertFalse(foo(-0));
assertFalse(foo(0));
assertTrue(foo(NaN));
assertFalse(foo(''));
assertFalse(foo([]));
assertFalse(foo({}));
%OptimizeFunctionOnNextCall(foo);
assertFalse(foo(-0));
assertFalse(foo(0));
assertTrue(foo(NaN));
assertFalse(foo(''));
assertFalse(foo([]));
assertFalse(foo({}));
})();
(function() {
function foo(o) { return Object.is(+o, NaN); }
assertFalse(foo(-0));
assertFalse(foo(0));
assertTrue(foo(NaN));
%OptimizeFunctionOnNextCall(foo);
assertFalse(foo(-0));
assertFalse(foo(0));
assertTrue(foo(NaN));
})();
(function() {
function foo(o) { return Object.is(NaN, +o); }
assertFalse(foo(-0));
assertFalse(foo(0));
assertTrue(foo(NaN));
%OptimizeFunctionOnNextCall(foo);
assertFalse(foo(-0));
assertFalse(foo(0));
assertTrue(foo(NaN));
})();
(function() {
function foo(o) { return Object.is(`${o}`, "foo"); }
assertFalse(foo("bar"));
assertTrue(foo("foo"));
%OptimizeFunctionOnNextCall(foo);
assertFalse(foo("bar"));
assertTrue(foo("foo"));
})();
(function() {
function foo(o) { return Object.is(o, o); }
assertTrue(foo(-0));
assertTrue(foo(0));
assertTrue(foo(NaN));
assertTrue(foo(''));
assertTrue(foo([]));
assertTrue(foo({}));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(-0));
assertTrue(foo(0));
assertTrue(foo(NaN));
assertTrue(foo(''));
assertTrue(foo([]));
assertTrue(foo({}));
})();
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