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

[turbofan] Introduce SameValue operator.

We now represent the SameValue operation explicitly in TurboFan and the
operation can thus participate in all kinds of optimizations. Especially
we get rid of the JSCall node in the general case, which blocks several
optimizations across the call. The general, baseline performance is now
always on par with StrictEqual.

Once the StrictEqual operator is also a simplified operator, we should
start unifying the type based optimizations in SimplifiedLowering.

In the micro-benchmark we go from

  testStrictEqual: 1422 ms.
  testObjectIs: 1520 ms.
  testManualSameValue: 1759 ms.

to

  testStrictEqual: 1426 ms.
  testObjectIs: 1357 ms.
  testManualSameValue: 1766 ms.

which gives the expected result.

Bug: v8:7007
Change-Id: I0de3ff6ff6209ab4c3edb69de6a16e387295a9c8
Reviewed-on: https://chromium-review.googlesource.com/741228Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48994}
parent 03035038
......@@ -700,6 +700,7 @@ namespace internal {
TFC(GreaterThan, Compare, 1) \
TFC(GreaterThanOrEqual, Compare, 1) \
TFC(Equal, Compare, 1) \
TFC(SameValue, Compare, 1) \
TFC(StrictEqual, Compare, 1) \
\
/* Object */ \
......
......@@ -593,5 +593,19 @@ TF_BUILTIN(ForInFilter, CodeStubAssembler) {
Return(UndefinedConstant());
}
TF_BUILTIN(SameValue, CodeStubAssembler) {
Node* lhs = Parameter(Descriptor::kLeft);
Node* rhs = Parameter(Descriptor::kRight);
Label if_true(this), if_false(this);
BranchIfSameValue(lhs, rhs, &if_true, &if_false);
BIND(&if_true);
Return(TrueConstant());
BIND(&if_false);
Return(FalseConstant());
}
} // namespace internal
} // namespace v8
......@@ -778,6 +778,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kArrayBufferWasNeutered:
result = LowerArrayBufferWasNeutered(node);
break;
case IrOpcode::kSameValue:
result = LowerSameValue(node);
break;
case IrOpcode::kStringFromCharCode:
result = LowerStringFromCharCode(node);
break;
......@@ -2440,6 +2443,20 @@ Node* EffectControlLinearizer::LowerArrayBufferWasNeutered(Node* node) {
__ Int32Constant(0));
}
Node* EffectControlLinearizer::LowerSameValue(Node* node) {
Node* lhs = node->InputAt(0);
Node* rhs = node->InputAt(1);
Callable const callable =
Builtins::CallableFor(isolate(), Builtins::kSameValue);
Operator::Properties properties = Operator::kEliminatable;
CallDescriptor::Flags flags = CallDescriptor::kNoFlags;
CallDescriptor* desc = Linkage::GetStubCallDescriptor(
isolate(), graph()->zone(), callable.descriptor(), 0, flags, properties);
return __ Call(desc, __ HeapConstant(callable.code()), lhs, rhs,
__ NoContextConstant());
}
Node* EffectControlLinearizer::LowerStringToNumber(Node* node) {
Node* string = node->InputAt(0);
......
......@@ -103,6 +103,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* LowerNewSmiOrObjectElements(Node* node);
Node* LowerNewArgumentsElements(Node* node);
Node* LowerArrayBufferWasNeutered(Node* node);
Node* LowerSameValue(Node* node);
Node* LowerStringToNumber(Node* node);
Node* LowerStringCharAt(Node* node);
Node* LowerStringCharCodeAt(Node* node);
......
......@@ -2325,52 +2325,6 @@ 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.GetJSCallArity() == 2 && 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);
......@@ -2988,9 +2942,6 @@ 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);
......
......@@ -110,7 +110,6 @@ 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);
......
......@@ -518,6 +518,20 @@ Reduction JSCallReducer::ReduceObjectGetPrototypeOf(Node* node) {
return ReduceObjectGetPrototype(node, object);
}
// ES section #sec-object.is
Reduction JSCallReducer::ReduceObjectIs(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& params = CallParametersOf(node->op());
int const argc = static_cast<int>(params.arity() - 2);
Node* lhs = (argc >= 1) ? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* rhs = (argc >= 2) ? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
Node* value = graph()->NewNode(simplified()->SameValue(), lhs, rhs);
ReplaceWithValue(node, value);
return Replace(value);
}
// ES6 section B.2.2.1.1 get Object.prototype.__proto__
Reduction JSCallReducer::ReduceObjectPrototypeGetProto(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
......@@ -1946,6 +1960,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReduceObjectConstructor(node);
case Builtins::kObjectGetPrototypeOf:
return ReduceObjectGetPrototypeOf(node);
case Builtins::kObjectIs:
return ReduceObjectIs(node);
case Builtins::kObjectPrototypeGetProto:
return ReduceObjectPrototypeGetProto(node);
case Builtins::kObjectPrototypeHasOwnProperty:
......
......@@ -64,6 +64,7 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceObjectConstructor(Node* node);
Reduction ReduceObjectGetPrototype(Node* node, Node* object);
Reduction ReduceObjectGetPrototypeOf(Node* node);
Reduction ReduceObjectIs(Node* node);
Reduction ReduceObjectPrototypeGetProto(Node* node);
Reduction ReduceObjectPrototypeHasOwnProperty(Node* node);
Reduction ReduceObjectPrototypeIsPrototypeOf(Node* node);
......
......@@ -234,6 +234,7 @@
V(SpeculativeNumberLessThan) \
V(SpeculativeNumberLessThanOrEqual) \
V(ReferenceEqual) \
V(SameValue) \
V(StringEqual) \
V(StringLessThan) \
V(StringLessThanOrEqual)
......
......@@ -1058,6 +1058,42 @@ Type* OperationTyper::FalsifyUndefined(ComparisonOutcome outcome) {
return singleton_true();
}
namespace {
Type* JSType(Type* type) {
if (type->Is(Type::Boolean())) return Type::Boolean();
if (type->Is(Type::String())) return Type::String();
if (type->Is(Type::Number())) return Type::Number();
if (type->Is(Type::Undefined())) return Type::Undefined();
if (type->Is(Type::Null())) return Type::Null();
if (type->Is(Type::Symbol())) return Type::Symbol();
if (type->Is(Type::Receiver())) return Type::Receiver(); // JS "Object"
return Type::Any();
}
} // namespace
Type* OperationTyper::SameValue(Type* lhs, Type* rhs) {
if (!JSType(lhs)->Maybe(JSType(rhs))) return singleton_false();
if (lhs->Is(Type::NaN())) {
if (rhs->Is(Type::NaN())) return singleton_true();
if (!rhs->Maybe(Type::NaN())) return singleton_false();
} else if (rhs->Is(Type::NaN())) {
if (!lhs->Maybe(Type::NaN())) return singleton_false();
}
if (lhs->Is(Type::MinusZero())) {
if (rhs->Is(Type::MinusZero())) return singleton_true();
if (!rhs->Maybe(Type::MinusZero())) return singleton_false();
} else if (rhs->Is(Type::MinusZero())) {
if (!lhs->Maybe(Type::MinusZero())) return singleton_false();
}
if (lhs->Is(Type::OrderedNumber()) && rhs->Is(Type::OrderedNumber()) &&
(lhs->Max() < rhs->Min() || lhs->Min() > rhs->Max())) {
return singleton_false();
}
return Type::Boolean();
}
Type* OperationTyper::CheckFloat64Hole(Type* type) {
if (type->Maybe(Type::Hole())) {
// Turn "the hole" into undefined.
......
......@@ -49,6 +49,9 @@ class V8_EXPORT_PRIVATE OperationTyper {
SIMPLIFIED_SPECULATIVE_NUMBER_BINOP_LIST(DECLARE_METHOD)
#undef DECLARE_METHOD
// Comparison operators.
Type* SameValue(Type* lhs, Type* rhs);
// Check operators.
Type* CheckFloat64Hole(Type* type);
Type* CheckNumber(Type* type);
......
......@@ -426,6 +426,7 @@ class RepresentationSelector {
break; \
}
SIMPLIFIED_NUMBER_BINOP_LIST(DECLARE_CASE)
DECLARE_CASE(SameValue)
#undef DECLARE_CASE
#define DECLARE_CASE(Name) \
......@@ -2349,6 +2350,12 @@ class RepresentationSelector {
}
return;
}
case IrOpcode::kSameValue: {
if (truncation.IsUnused()) return VisitUnused(node);
VisitBinop(node, UseInfo::AnyTagged(),
MachineRepresentation::kTaggedPointer);
return;
}
case IrOpcode::kClassOf:
case IrOpcode::kTypeOf: {
return VisitUnop(node, UseInfo::AnyTagged(),
......
......@@ -630,6 +630,7 @@ DeoptimizeReason DeoptimizeReasonOf(const Operator* op) {
V(ObjectIsSymbol, Operator::kNoProperties, 1, 0) \
V(ObjectIsUndetectable, Operator::kNoProperties, 1, 0) \
V(ConvertTaggedHoleToUndefined, Operator::kNoProperties, 1, 0) \
V(SameValue, Operator::kCommutative, 2, 0) \
V(ReferenceEqual, Operator::kCommutative, 2, 0) \
V(StringEqual, Operator::kCommutative, 2, 0) \
V(StringLessThan, Operator::kNoProperties, 2, 0) \
......
......@@ -392,6 +392,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* SpeculativeNumberEqual(NumberOperationHint hint);
const Operator* ReferenceEqual();
const Operator* SameValue();
const Operator* TypeOf();
const Operator* ClassOf();
......
......@@ -105,6 +105,8 @@ Reduction TypedOptimization::Reduce(Node* node) {
return ReducePhi(node);
case IrOpcode::kReferenceEqual:
return ReduceReferenceEqual(node);
case IrOpcode::kSameValue:
return ReduceSameValue(node);
case IrOpcode::kSelect:
return ReduceSelect(node);
case IrOpcode::kTypeOf:
......@@ -357,6 +359,52 @@ Reduction TypedOptimization::ReduceReferenceEqual(Node* node) {
return NoChange();
}
Reduction TypedOptimization::ReduceSameValue(Node* node) {
DCHECK_EQ(IrOpcode::kSameValue, node->opcode());
Node* const lhs = NodeProperties::GetValueInput(node, 0);
Node* const rhs = NodeProperties::GetValueInput(node, 1);
Type* const lhs_type = NodeProperties::GetType(lhs);
Type* const rhs_type = NodeProperties::GetType(rhs);
if (lhs == rhs) {
// SameValue(x,x) => #true
return Replace(jsgraph()->TrueConstant());
} else if (lhs_type->Is(Type::Unique()) && rhs_type->Is(Type::Unique())) {
// SameValue(x:unique,y:unique) => ReferenceEqual(x,y)
NodeProperties::ChangeOp(node, simplified()->ReferenceEqual());
return Changed(node);
} else if (lhs_type->Is(Type::String()) && rhs_type->Is(Type::String())) {
// SameValue(x:string,y:string) => StringEqual(x,y)
NodeProperties::ChangeOp(node, simplified()->StringEqual());
return Changed(node);
} else if (lhs_type->Is(Type::MinusZero())) {
// SameValue(x:minus-zero,y) => ObjectIsMinusZero(y)
node->RemoveInput(0);
NodeProperties::ChangeOp(node, simplified()->ObjectIsMinusZero());
return Changed(node);
} else if (rhs_type->Is(Type::MinusZero())) {
// SameValue(x,y:minus-zero) => ObjectIsMinusZero(x)
node->RemoveInput(1);
NodeProperties::ChangeOp(node, simplified()->ObjectIsMinusZero());
return Changed(node);
} else if (lhs_type->Is(Type::NaN())) {
// SameValue(x:nan,y) => ObjectIsNaN(y)
node->RemoveInput(0);
NodeProperties::ChangeOp(node, simplified()->ObjectIsNaN());
return Changed(node);
} else if (rhs_type->Is(Type::NaN())) {
// SameValue(x,y:nan) => ObjectIsNaN(x)
node->RemoveInput(1);
NodeProperties::ChangeOp(node, simplified()->ObjectIsNaN());
return Changed(node);
} else if (lhs_type->Is(Type::PlainNumber()) &&
rhs_type->Is(Type::PlainNumber())) {
// SameValue(x:plain-number,y:plain-number) => NumberEqual(x,y)
NodeProperties::ChangeOp(node, simplified()->NumberEqual());
return Changed(node);
}
return NoChange();
}
Reduction TypedOptimization::ReduceSelect(Node* node) {
DCHECK_EQ(IrOpcode::kSelect, node->opcode());
Node* const condition = NodeProperties::GetValueInput(node, 0);
......
......@@ -50,6 +50,7 @@ class V8_EXPORT_PRIVATE TypedOptimization final
Reduction ReduceNumberToUint8Clamped(Node* node);
Reduction ReducePhi(Node* node);
Reduction ReduceReferenceEqual(Node* node);
Reduction ReduceSameValue(Node* node);
Reduction ReduceSelect(Node* node);
Reduction ReduceSpeculativeToNumber(Node* node);
Reduction ReduceCheckNotTaggedHole(Node* node);
......
......@@ -314,6 +314,7 @@ class Typer::Visitor : public Reducer {
static Type* NumberLessThanTyper(Type*, Type*, Typer*);
static Type* NumberLessThanOrEqualTyper(Type*, Type*, Typer*);
static Type* ReferenceEqualTyper(Type*, Type*, Typer*);
static Type* SameValueTyper(Type*, Type*, Typer*);
static Type* StringFromCharCodeTyper(Type*, Typer*);
static Type* StringFromCodePointTyper(Type*, Typer*);
......@@ -1822,6 +1823,15 @@ Type* Typer::Visitor::TypeReferenceEqual(Node* node) {
return TypeBinaryOp(node, ReferenceEqualTyper);
}
// static
Type* Typer::Visitor::SameValueTyper(Type* lhs, Type* rhs, Typer* t) {
return t->operation_typer()->SameValue(lhs, rhs);
}
Type* Typer::Visitor::TypeSameValue(Node* node) {
return TypeBinaryOp(node, SameValueTyper);
}
Type* Typer::Visitor::TypeStringEqual(Node* node) { return Type::Boolean(); }
Type* Typer::Visitor::TypeStringLessThan(Node* node) { return Type::Boolean(); }
......
......@@ -1030,6 +1030,12 @@ void Verifier::Visitor::Check(Node* node) {
// (Any, Unique) -> Boolean
CheckTypeIs(node, Type::Boolean());
break;
case IrOpcode::kSameValue:
// (Any, Any) -> Boolean
CheckValueInputIs(node, 0, Type::Any());
CheckValueInputIs(node, 1, Type::Any());
CheckTypeIs(node, Type::Boolean());
break;
case IrOpcode::kObjectIsArrayBufferView:
case IrOpcode::kObjectIsCallable:
......
......@@ -125,6 +125,15 @@
assertTrue(foo("foo"));
})();
(function() {
function foo(o) { return Object.is(String(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));
......@@ -141,3 +150,25 @@
assertTrue(foo([]));
assertTrue(foo({}));
})();
(function() {
function foo(o) { return Object.is(o|0, 0); }
assertTrue(foo(0));
assertTrue(foo(-0));
assertTrue(foo(NaN));
assertFalse(foo(1));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(0));
assertTrue(foo(-0));
assertTrue(foo(NaN));
assertFalse(foo(1));
})();
(function() {
const s = Symbol();
function foo() { return Object.is(s, Symbol()); }
assertFalse(foo());
assertFalse(foo());
%OptimizeFunctionOnNextCall(foo);
assertFalse(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