[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/700254
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48275}
This commit is contained in:
Benedikt Meurer 2017-10-04 08:25:26 +02:00 committed by Commit Bot
parent bfb43f8c12
commit d4da17c6e3
21 changed files with 384 additions and 84 deletions

View File

@ -709,7 +709,7 @@ namespace internal {
CPP(ObjectGetOwnPropertySymbols) \
CPP(ObjectGetPrototypeOf) \
CPP(ObjectSetPrototypeOf) \
CPP(ObjectIs) \
TFJ(ObjectIs, 2, kLeft, kRight) \
CPP(ObjectIsExtensible) \
CPP(ObjectIsFrozen) \
CPP(ObjectIsSealed) \

View File

@ -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) {
Node* const value = Parameter(Descriptor::kValue);
Node* const done = Parameter(Descriptor::kDone);

View File

@ -369,15 +369,6 @@ BUILTIN(ObjectGetOwnPropertySymbols) {
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 )
BUILTIN(ObjectIsExtensible) {
HandleScope scope(isolate);

View File

@ -683,7 +683,8 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context,
VARIABLE(var_reason, 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);
Label cycle_check(this);
@ -693,7 +694,8 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context,
BIND(&cycle_check);
// 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
GotoIf(TaggedIsSmi(result), &fulfill);
@ -1435,11 +1437,13 @@ TF_BUILTIN(PromiseResolve, PromiseBuiltinsAssembler) {
// they could be of the same subclass.
BIND(&if_value_or_constructor_are_not_native_promise);
{
Label if_return(this);
Node* const xConstructor =
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);
}

View File

@ -517,9 +517,8 @@ void ProxiesCodeStubAssembler::CheckGetSetTrapResult(
// If SameValue(trapResult, targetDesc.[[Value]]) is false,
// throw a TypeError exception.
GotoIfNot(SameValue(trap_result, var_value.value()),
&throw_non_configurable_data);
Goto(check_passed);
BranchIfSameValue(trap_result, var_value.value(), check_passed,
&throw_non_configurable_data);
}
BIND(&check_accessor);

View File

@ -2208,9 +2208,10 @@ void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow(
// Ensure last index is 0.
{
Label next(this);
GotoIf(SameValue(previous_last_index, smi_zero), &next);
Label next(this), slow(this, Label::kDeferred);
BranchIfSameValue(previous_last_index, smi_zero, &next, &slow);
BIND(&slow);
SlowStoreLastIndex(context, regexp, smi_zero);
Goto(&next);
BIND(&next);
@ -2221,14 +2222,14 @@ void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow(
// Reset last index if necessary.
{
Label next(this);
Label next(this), slow(this, Label::kDeferred);
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);
Goto(&next);
BIND(&next);
}

View File

@ -9010,88 +9010,107 @@ Node* CodeStubAssembler::StrictEqual(Node* lhs, Node* rhs,
// ECMA#sec-samevalue
// This algorithm differs from the Strict Equality Comparison Algorithm in its
// treatment of signed zeroes and NaNs.
Node* CodeStubAssembler::SameValue(Node* lhs, Node* rhs) {
VARIABLE(var_result, MachineRepresentation::kWord32);
Label strict_equal(this), out(this);
void CodeStubAssembler::BranchIfSameValue(Node* lhs, Node* rhs, Label* if_true,
Label* if_false) {
VARIABLE(var_lhs_value, MachineRepresentation::kFloat64);
VARIABLE(var_rhs_value, MachineRepresentation::kFloat64);
Label do_fcmp(this);
Node* const int_false = Int32Constant(0);
Node* const int_true = Int32Constant(1);
// Immediately jump to {if_true} if {lhs} == {rhs}, because - unlike
// StrictEqual - SameValue considers two NaNs to be equal.
GotoIf(WordEqual(lhs, rhs), if_true);
Label if_equal(this), if_notequal(this);
Branch(WordEqual(lhs, rhs), &if_equal, &if_notequal);
// Check if the {lhs} is a Smi.
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
// because SameValue considers two NaNs to be equal.
var_result.Bind(int_true);
Goto(&out);
// Since {lhs} is a Smi, the comparison can only yield true
// iff the {rhs} is a HeapNumber with the same float64 value.
GotoIf(TaggedIsSmi(rhs), if_false);
GotoIfNot(IsHeapNumber(rhs), if_false);
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
// and defer to StrictEqual for the rest.
// Check if the {rhs} is a Smi.
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);
BranchIfFloat64IsNaN(lhs_float, &if_lhsisnan, &if_lhsnotnan);
BIND(&if_lhsisnan);
BIND(&if_rhsissmi);
{
// Return true iff {rhs} is NaN.
Node* const result =
SelectConstant(Float64Equal(rhs_float, rhs_float), int_false,
int_true, MachineRepresentation::kWord32);
var_result.Bind(result);
Goto(&out);
// Since {rhs} is a Smi, the comparison can only yield true
// iff the {lhs} is a HeapNumber with the same float64 value.
GotoIfNot(IsHeapNumber(lhs), if_false);
var_lhs_value.Bind(LoadHeapNumberValue(lhs));
var_rhs_value.Bind(SmiToFloat64(rhs));
Goto(&do_fcmp);
}
BIND(&if_lhsnotnan);
BIND(&if_rhsisheapobject);
{
Label if_floatisequal(this), if_floatnotequal(this);
Branch(Float64Equal(lhs_float, rhs_float), &if_floatisequal,
&if_floatnotequal);
// Now this can only yield true if either both {lhs} and {rhs}
// are HeapNumbers with the same value or both {lhs} and {rhs}
// are Strings with the same character sequence.
Label if_lhsisheapnumber(this), if_lhsisstring(this);
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_floatisequal);
BIND(&if_lhsisheapnumber);
{
// 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_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);
GotoIfNot(IsHeapNumber(rhs), if_false);
var_lhs_value.Bind(LoadHeapNumberValue(lhs));
var_rhs_value.Bind(LoadHeapNumberValue(rhs));
Goto(&do_fcmp);
}
BIND(&if_floatnotequal);
BIND(&if_lhsisstring);
{
var_result.Bind(int_false);
Goto(&out);
// Now we can only yield true if {rhs} is also a String
// 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 result = WordEqual(is_equal, TrueConstant());
var_result.Bind(result);
Goto(&out);
}
Node* const lhs_value = var_lhs_value.value();
Node* const rhs_value = var_rhs_value.value();
BIND(&out);
return var_result.value();
Label if_equal(this), if_notequal(this);
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,

View File

@ -1580,9 +1580,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
// ECMA#sec-samevalue
// Similar to StrictEqual except that NaNs are treated as equal and minus zero
// differs from positive zero.
// Unlike Equal and StrictEqual, returns a value suitable for use in Branch
// instructions, e.g. Branch(SameValue(...), &label).
Node* SameValue(Node* lhs, Node* rhs);
void BranchIfSameValue(Node* lhs, Node* rhs, Label* if_true, Label* if_false);
enum HasPropertyLookupMode { kHasProperty, kForInHasProperty };

View File

@ -721,6 +721,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kObjectIsDetectableCallable:
result = LowerObjectIsDetectableCallable(node);
break;
case IrOpcode::kObjectIsMinusZero:
result = LowerObjectIsMinusZero(node);
break;
case IrOpcode::kObjectIsNaN:
result = LowerObjectIsNaN(node);
break;
@ -1944,6 +1947,31 @@ Node* EffectControlLinearizer::LowerObjectIsDetectableCallable(Node* node) {
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* value = node->InputAt(0);
Node* zero = __ Int32Constant(0);

View File

@ -87,6 +87,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* LowerObjectIsArrayBufferView(Node* node);
Node* LowerObjectIsCallable(Node* node);
Node* LowerObjectIsDetectableCallable(Node* node);
Node* LowerObjectIsMinusZero(Node* node);
Node* LowerObjectIsNaN(Node* node);
Node* LowerObjectIsNonCallable(Node* node);
Node* LowerObjectIsNumber(Node* node);

View File

@ -52,6 +52,7 @@ namespace compiler {
V(Int32LessThan) \
V(Float64Add) \
V(Float64Sub) \
V(Float64Div) \
V(Float64Mod) \
V(Float64Equal) \
V(Float64LessThan) \

View File

@ -2496,6 +2496,52 @@ Reduction JSBuiltinReducer::ReduceObjectCreate(Node* node) {
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 )
Reduction JSBuiltinReducer::ReduceStringFromCharCode(Node* node) {
JSCallReduction r(node);
@ -3127,6 +3173,9 @@ Reduction JSBuiltinReducer::Reduce(Node* node) {
case kObjectCreate:
reduction = ReduceObjectCreate(node);
break;
case kObjectIs:
reduction = ReduceObjectIs(node);
break;
case kSetEntries:
return ReduceCollectionIterator(
node, JS_SET_TYPE, Context::SET_KEY_VALUE_ITERATOR_MAP_INDEX);

View File

@ -111,6 +111,7 @@ class V8_EXPORT_PRIVATE JSBuiltinReducer final
Reduction ReduceNumberIsSafeInteger(Node* node);
Reduction ReduceNumberParseInt(Node* node);
Reduction ReduceObjectCreate(Node* node);
Reduction ReduceObjectIs(Node* node);
Reduction ReduceStringCharAt(Node* node);
Reduction ReduceStringCharCodeAt(Node* node);
Reduction ReduceStringConcat(Node* node);

View File

@ -355,6 +355,7 @@
V(ObjectIsArrayBufferView) \
V(ObjectIsCallable) \
V(ObjectIsDetectableCallable) \
V(ObjectIsMinusZero) \
V(ObjectIsNaN) \
V(ObjectIsNonCallable) \
V(ObjectIsNumber) \

View File

@ -2677,6 +2677,39 @@ class RepresentationSelector {
VisitObjectIs(node, Type::DetectableCallable(), lowering);
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: {
Type* const input_type = GetUpperBound(node->InputAt(0));
if (input_type->Is(Type::NaN())) {

View File

@ -498,6 +498,7 @@ BailoutReason BailoutReasonOf(const Operator* op) {
V(ObjectIsArrayBufferView, Operator::kNoProperties, 1, 0) \
V(ObjectIsCallable, Operator::kNoProperties, 1, 0) \
V(ObjectIsDetectableCallable, Operator::kNoProperties, 1, 0) \
V(ObjectIsMinusZero, Operator::kNoProperties, 1, 0) \
V(ObjectIsNaN, Operator::kNoProperties, 1, 0) \
V(ObjectIsNonCallable, Operator::kNoProperties, 1, 0) \
V(ObjectIsNumber, Operator::kNoProperties, 1, 0) \

View File

@ -438,6 +438,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* ObjectIsArrayBufferView();
const Operator* ObjectIsCallable();
const Operator* ObjectIsDetectableCallable();
const Operator* ObjectIsMinusZero();
const Operator* ObjectIsNaN();
const Operator* ObjectIsNonCallable();
const Operator* ObjectIsNumber();

View File

@ -290,6 +290,7 @@ class Typer::Visitor : public Reducer {
static Type* ObjectIsArrayBufferView(Type*, Typer*);
static Type* ObjectIsCallable(Type*, Typer*);
static Type* ObjectIsDetectableCallable(Type*, Typer*);
static Type* ObjectIsMinusZero(Type*, Typer*);
static Type* ObjectIsNaN(Type*, Typer*);
static Type* ObjectIsNonCallable(Type*, Typer*);
static Type* ObjectIsNumber(Type*, Typer*);
@ -528,6 +529,12 @@ Type* Typer::Visitor::ObjectIsDetectableCallable(Type* type, Typer* t) {
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) {
if (type->Is(Type::NaN())) return t->singleton_true_;
if (!type->Maybe(Type::NaN())) return t->singleton_false_;
@ -1544,6 +1551,7 @@ Type* Typer::Visitor::JSCallTyper(Type* fun, Typer* t) {
return Type::Receiver();
case kObjectCreate:
return Type::OtherObject();
case kObjectIs:
case kObjectHasOwnProperty:
case kObjectIsPrototypeOf:
return Type::Boolean();
@ -1980,6 +1988,10 @@ Type* Typer::Visitor::TypeObjectIsDetectableCallable(Node* node) {
return TypeUnaryOp(node, ObjectIsDetectableCallable);
}
Type* Typer::Visitor::TypeObjectIsMinusZero(Node* node) {
return TypeUnaryOp(node, ObjectIsMinusZero);
}
Type* Typer::Visitor::TypeObjectIsNaN(Node* node) {
return TypeUnaryOp(node, ObjectIsNaN);
}

View File

@ -1008,6 +1008,7 @@ void Verifier::Visitor::Check(Node* node) {
case IrOpcode::kObjectIsArrayBufferView:
case IrOpcode::kObjectIsCallable:
case IrOpcode::kObjectIsDetectableCallable:
case IrOpcode::kObjectIsMinusZero:
case IrOpcode::kObjectIsNaN:
case IrOpcode::kObjectIsNonCallable:
case IrOpcode::kObjectIsNumber:

View File

@ -4519,6 +4519,7 @@ class ContextExtension : public Struct {
V(Function.prototype, call, FunctionCall) \
V(Object, assign, ObjectAssign) \
V(Object, create, ObjectCreate) \
V(Object, is, ObjectIs) \
V(Object.prototype, hasOwnProperty, ObjectHasOwnProperty) \
V(Object.prototype, isPrototypeOf, ObjectIsPrototypeOf) \
V(Object.prototype, toString, ObjectToString) \

View File

@ -0,0 +1,143 @@
// 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({}));
})();