Commit 241c024c authored by bmeurer's avatar bmeurer Committed by Commit bot

[turbofan] Properly optimize instanceof (even in the presence of @@hasInstance).

This is the TurboFan counterpart of http://crrev.com/2504263004, but it
is a bit more involved, since in TurboFan we always inline the appropriate
call to the @@hasInstance handler, and by that we can optimize a lot more
patterns of instanceof than Crankshaft, and even yield fast instanceof
for custom @@hasInstance handlers (which we can now properly inline as
well).

Also we now properly optimize Function.prototype[@@hasInstance], even if
the right hand side of an instanceof doesn't have the Function.prototype
as its direct prototype.

For the baseline case, we still rely on the global protector cell, but
we can address that in a follow-up as well, and make it more robust in
general.

TEST=mjsunit/compiler/instanceof
BUG=v8:5640
R=yangguo@chromium.org

Review-Url: https://codereview.chromium.org/2511223003
Cr-Commit-Position: refs/heads/master@{#41092}
parent 5beb5ee7
......@@ -1257,6 +1257,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
JSObject::kHeaderSize, MaybeHandle<JSObject>(),
Builtins::kFunctionPrototypeHasInstance,
static_cast<PropertyAttributes>(DONT_ENUM | DONT_DELETE | READ_ONLY));
has_instance->shared()->set_builtin_function_id(kFunctionHasInstance);
native_context()->set_function_has_instance(*has_instance);
// Set the expected parameters for @@hasInstance to 1; required by builtin.
......
......@@ -1060,5 +1060,19 @@ void Builtins::Generate_InstanceOf(compiler::CodeAssemblerState* state) {
assembler.Return(assembler.InstanceOf(object, callable, context));
}
// ES6 section 7.3.19 OrdinaryHasInstance ( C, O )
void Builtins::Generate_OrdinaryHasInstance(
compiler::CodeAssemblerState* state) {
typedef compiler::Node Node;
typedef CompareDescriptor Descriptor;
CodeStubAssembler assembler(state);
Node* constructor = assembler.Parameter(Descriptor::kLeft);
Node* object = assembler.Parameter(Descriptor::kRight);
Node* context = assembler.Parameter(Descriptor::kContext);
assembler.Return(assembler.OrdinaryHasInstance(context, constructor, object));
}
} // namespace internal
} // namespace v8
......@@ -559,6 +559,7 @@ namespace internal {
\
TFS(HasProperty, BUILTIN, kNoExtraICState, HasProperty) \
TFS(InstanceOf, BUILTIN, kNoExtraICState, Compare) \
TFS(OrdinaryHasInstance, BUILTIN, kNoExtraICState, Compare) \
TFS(ForInFilter, BUILTIN, kNoExtraICState, ForInFilter) \
\
/* Promise */ \
......
......@@ -254,8 +254,11 @@ TFS_BUILTIN(ToLength)
TFS_BUILTIN(ToObject)
TFS_BUILTIN(Typeof)
TFS_BUILTIN(InstanceOf)
TFS_BUILTIN(OrdinaryHasInstance)
TFS_BUILTIN(ForInFilter)
#undef TFS_BUILTIN
// static
Callable CodeFactory::Inc(Isolate* isolate) {
IncStub stub(isolate);
......
......@@ -64,6 +64,7 @@ class V8_EXPORT_PRIVATE CodeFactory final {
// Code stubs. Add methods here as needed to reduce dependency on
// code-stubs.h.
static Callable InstanceOf(Isolate* isolate);
static Callable OrdinaryHasInstance(Isolate* isolate);
static Callable StringFromCharCode(Isolate* isolate);
......
......@@ -962,6 +962,34 @@ Reduction JSBuiltinReducer::ReduceDateGetTime(Node* node) {
return NoChange();
}
// ES6 section 19.2.3.6 Function.prototype [ @@hasInstance ] ( V )
Reduction JSBuiltinReducer::ReduceFunctionHasInstance(Node* node) {
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* object = (node->op()->ValueInputCount() >= 3)
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// TODO(turbofan): If JSOrdinaryToInstance raises an exception, the
// stack trace doesn't contain the @@hasInstance call; we have the
// corresponding bug in the baseline case. Some massaging of the frame
// state would be necessary here.
// Morph this {node} into a JSOrdinaryHasInstance node.
node->ReplaceInput(0, receiver);
node->ReplaceInput(1, object);
node->ReplaceInput(2, context);
node->ReplaceInput(3, frame_state);
node->ReplaceInput(4, effect);
node->ReplaceInput(5, control);
node->TrimInputCount(6);
NodeProperties::ChangeOp(node, javascript()->OrdinaryHasInstance());
return Changed(node);
}
// ES6 section 18.2.2 isFinite ( number )
Reduction JSBuiltinReducer::ReduceGlobalIsFinite(Node* node) {
JSCallReduction r(node);
......@@ -1845,6 +1873,9 @@ Reduction JSBuiltinReducer::Reduce(Node* node) {
return ReduceArrayPush(node);
case kDateGetTime:
return ReduceDateGetTime(node);
case kFunctionHasInstance:
return ReduceFunctionHasInstance(node);
break;
case kGlobalIsFinite:
reduction = ReduceGlobalIsFinite(node);
break;
......
......@@ -58,6 +58,7 @@ class V8_EXPORT_PRIVATE JSBuiltinReducer final
Reduction ReduceArrayPop(Node* node);
Reduction ReduceArrayPush(Node* node);
Reduction ReduceDateGetTime(Node* node);
Reduction ReduceFunctionHasInstance(Node* node);
Reduction ReduceGlobalIsFinite(Node* node);
Reduction ReduceGlobalIsNaN(Node* node);
Reduction ReduceMathAbs(Node* node);
......
......@@ -352,6 +352,11 @@ void JSGenericLowering::LowerJSInstanceOf(Node* node) {
ReplaceWithStubCall(node, callable, flags);
}
void JSGenericLowering::LowerJSOrdinaryHasInstance(Node* node) {
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
Callable callable = CodeFactory::OrdinaryHasInstance(isolate());
ReplaceWithStubCall(node, callable, flags);
}
void JSGenericLowering::LowerJSLoadContext(Node* node) {
const ContextAccess& access = ContextAccessOf(node->op());
......
......@@ -69,6 +69,8 @@ JSNativeContextSpecialization::JSNativeContextSpecialization(
Reduction JSNativeContextSpecialization::Reduce(Node* node) {
switch (node->opcode()) {
case IrOpcode::kJSInstanceOf:
return ReduceJSInstanceOf(node);
case IrOpcode::kJSLoadContext:
return ReduceJSLoadContext(node);
case IrOpcode::kJSLoadNamed:
......@@ -85,6 +87,92 @@ Reduction JSNativeContextSpecialization::Reduce(Node* node) {
return NoChange();
}
Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) {
DCHECK_EQ(IrOpcode::kJSInstanceOf, node->opcode());
Node* object = NodeProperties::GetValueInput(node, 0);
Node* constructor = NodeProperties::GetValueInput(node, 1);
Node* context = NodeProperties::GetContextInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// If deoptimization is disabled, we cannot optimize.
if (!(flags() & kDeoptimizationEnabled)) return NoChange();
// Check if the right hand side is a known {receiver}.
HeapObjectMatcher m(constructor);
if (!m.HasValue() || !m.Value()->IsJSObject()) return NoChange();
Handle<JSObject> receiver = Handle<JSObject>::cast(m.Value());
Handle<Map> receiver_map(receiver->map(), isolate());
// Compute property access info for @@hasInstance on {receiver}.
PropertyAccessInfo access_info;
AccessInfoFactory access_info_factory(dependencies(), native_context(),
graph()->zone());
if (!access_info_factory.ComputePropertyAccessInfo(
receiver_map, factory()->has_instance_symbol(), AccessMode::kLoad,
&access_info)) {
return NoChange();
}
if (access_info.IsNotFound()) {
// If there's no @@hasInstance handler, the OrdinaryHasInstance operation
// takes over, but that requires the {receiver} to be callable.
if (receiver->IsCallable()) {
// Determine actual holder and perform prototype chain checks.
Handle<JSObject> holder;
if (access_info.holder().ToHandle(&holder)) {
AssumePrototypesStable(access_info.receiver_maps(), holder);
}
// Monomorphic property access.
effect =
BuildCheckMaps(constructor, effect, control, MapList{receiver_map});
// Lower to OrdinaryHasInstance(C, O).
NodeProperties::ReplaceValueInput(node, constructor, 0);
NodeProperties::ReplaceValueInput(node, object, 1);
NodeProperties::ReplaceEffectInput(node, effect);
NodeProperties::ChangeOp(node, javascript()->OrdinaryHasInstance());
return Changed(node);
}
} else if (access_info.IsDataConstant()) {
DCHECK(access_info.constant()->IsCallable());
// Determine actual holder and perform prototype chain checks.
Handle<JSObject> holder;
if (access_info.holder().ToHandle(&holder)) {
AssumePrototypesStable(access_info.receiver_maps(), holder);
}
// Monomorphic property access.
effect =
BuildCheckMaps(constructor, effect, control, MapList{receiver_map});
// Call the @@hasInstance handler.
Node* target = jsgraph()->Constant(access_info.constant());
node->InsertInput(graph()->zone(), 0, target);
node->ReplaceInput(1, constructor);
node->ReplaceInput(2, object);
NodeProperties::ChangeOp(
node,
javascript()->CallFunction(3, 0.0f, VectorSlotPair(),
ConvertReceiverMode::kNotNullOrUndefined));
// Rewire the value uses of {node} to ToBoolean conversion of the result.
Node* value = graph()->NewNode(javascript()->ToBoolean(ToBooleanHint::kAny),
node, context);
for (Edge edge : node->use_edges()) {
if (NodeProperties::IsValueEdge(edge) && edge.from() != value) {
edge.UpdateTo(value);
Revisit(edge.from());
}
}
return Changed(node);
}
return NoChange();
}
Reduction JSNativeContextSpecialization::ReduceJSLoadContext(Node* node) {
DCHECK_EQ(IrOpcode::kJSLoadContext, node->opcode());
ContextAccess const& access = ContextAccessOf(node->op());
......
......@@ -53,6 +53,7 @@ class JSNativeContextSpecialization final : public AdvancedReducer {
Reduction Reduce(Node* node) final;
private:
Reduction ReduceJSInstanceOf(Node* node);
Reduction ReduceJSLoadContext(Node* node);
Reduction ReduceJSLoadNamed(Node* node);
Reduction ReduceJSStoreNamed(Node* node);
......
......@@ -449,6 +449,7 @@ CompareOperationHint CompareOperationHintOf(const Operator* op) {
V(HasProperty, Operator::kNoProperties, 2, 1) \
V(TypeOf, Operator::kPure, 1, 1) \
V(InstanceOf, Operator::kNoProperties, 2, 1) \
V(OrdinaryHasInstance, Operator::kNoProperties, 2, 1) \
V(ForInNext, Operator::kNoProperties, 4, 1) \
V(ForInPrepare, Operator::kNoProperties, 1, 3) \
V(LoadMessage, Operator::kNoThrow, 0, 1) \
......
......@@ -508,6 +508,7 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
const Operator* TypeOf();
const Operator* InstanceOf();
const Operator* OrdinaryHasInstance();
const Operator* ForInNext();
const Operator* ForInPrepare();
......
......@@ -1312,47 +1312,35 @@ Reduction JSTypedLowering::ReduceJSStoreProperty(Node* node) {
return NoChange();
}
Reduction JSTypedLowering::ReduceJSInstanceOf(Node* node) {
DCHECK_EQ(IrOpcode::kJSInstanceOf, node->opcode());
Node* const context = NodeProperties::GetContextInput(node);
Node* const frame_state = NodeProperties::GetFrameStateInput(node);
// If deoptimization is disabled, we cannot optimize.
if (!(flags() & kDeoptimizationEnabled)) return NoChange();
// If we are in a try block, don't optimize since the runtime call
// in the proxy case can throw.
if (NodeProperties::IsExceptionalCall(node)) return NoChange();
JSBinopReduction r(this, node);
Node* object = r.left();
Node* effect = r.effect();
Node* control = r.control();
Reduction JSTypedLowering::ReduceJSOrdinaryHasInstance(Node* node) {
DCHECK_EQ(IrOpcode::kJSOrdinaryHasInstance, node->opcode());
Node* constructor = NodeProperties::GetValueInput(node, 0);
Type* constructor_type = NodeProperties::GetType(constructor);
Node* object = NodeProperties::GetValueInput(node, 1);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (!r.right_type()->IsHeapConstant() ||
!r.right_type()->AsHeapConstant()->Value()->IsJSFunction()) {
// Check if the {constructor} is a (known) JSFunction.
if (!constructor_type->IsHeapConstant() ||
!constructor_type->AsHeapConstant()->Value()->IsJSFunction()) {
return NoChange();
}
Handle<JSFunction> function =
Handle<JSFunction>::cast(r.right_type()->AsHeapConstant()->Value());
Handle<SharedFunctionInfo> shared(function->shared(), isolate());
// Make sure the prototype of {function} is the %FunctionPrototype%, and it
// already has a meaningful initial map (i.e. we constructed at least one
// instance using the constructor {function}).
if (function->map()->prototype() != function->native_context()->closure() ||
function->map()->has_non_instance_prototype() ||
!function->has_initial_map()) {
return NoChange();
}
Handle<JSFunction>::cast(constructor_type->AsHeapConstant()->Value());
// Check if the {function} already has an initial map (i.e. the
// {function} has been used as a constructor at least once).
if (!function->has_initial_map()) return NoChange();
// We can only use the fast case if @@hasInstance was not used so far.
if (!isolate()->IsHasInstanceLookupChainIntact()) return NoChange();
dependencies()->AssumePropertyCell(factory()->has_instance_protector());
// Check if the {function}s "prototype" is a JSReceiver.
if (!function->prototype()->IsJSReceiver()) return NoChange();
// Install a code dependency on the {function}s initial map.
Handle<Map> initial_map(function->initial_map(), isolate());
dependencies()->AssumeInitialMapCantChange(initial_map);
Node* prototype =
jsgraph()->Constant(handle(initial_map->prototype(), isolate()));
......@@ -1420,6 +1408,15 @@ Reduction JSTypedLowering::ReduceJSInstanceOf(Node* node) {
javascript()->CallRuntime(Runtime::kHasInPrototypeChain), object,
prototype, context, frame_state, efalse1, if_false1);
if_false1 = graph()->NewNode(common()->IfSuccess(), vfalse1);
// Replace any potential IfException on {node} to catch exceptions
// from this %HasInPrototypeChain runtime call instead.
for (Edge edge : node->use_edges()) {
if (edge.from()->opcode() == IrOpcode::kIfException) {
edge.UpdateTo(vfalse1);
Revisit(edge.from());
}
}
}
// Load the {object} prototype.
......@@ -2199,6 +2196,8 @@ Reduction JSTypedLowering::Reduce(Node* node) {
case IrOpcode::kJSDivide:
case IrOpcode::kJSModulus:
return ReduceNumberBinop(node);
case IrOpcode::kJSOrdinaryHasInstance:
return ReduceJSOrdinaryHasInstance(node);
case IrOpcode::kJSToBoolean:
return ReduceJSToBoolean(node);
case IrOpcode::kJSToInteger:
......@@ -2221,8 +2220,6 @@ Reduction JSTypedLowering::Reduce(Node* node) {
return ReduceJSLoadProperty(node);
case IrOpcode::kJSStoreProperty:
return ReduceJSStoreProperty(node);
case IrOpcode::kJSInstanceOf:
return ReduceJSInstanceOf(node);
case IrOpcode::kJSLoadContext:
return ReduceJSLoadContext(node);
case IrOpcode::kJSStoreContext:
......
......@@ -52,7 +52,7 @@ class V8_EXPORT_PRIVATE JSTypedLowering final
Reduction ReduceJSLoadNamed(Node* node);
Reduction ReduceJSLoadProperty(Node* node);
Reduction ReduceJSStoreProperty(Node* node);
Reduction ReduceJSInstanceOf(Node* node);
Reduction ReduceJSOrdinaryHasInstance(Node* node);
Reduction ReduceJSLoadContext(Node* node);
Reduction ReduceJSStoreContext(Node* node);
Reduction ReduceJSLoadModule(Node* node);
......
......@@ -140,7 +140,8 @@
V(JSStoreGlobal) \
V(JSDeleteProperty) \
V(JSHasProperty) \
V(JSInstanceOf)
V(JSInstanceOf) \
V(JSOrdinaryHasInstance)
#define JS_CONTEXT_OP_LIST(V) \
V(JSLoadContext) \
......
......@@ -61,6 +61,7 @@ bool OperatorProperties::HasFrameStateInput(const Operator* op) {
case IrOpcode::kJSLessThanOrEqual:
case IrOpcode::kJSHasProperty:
case IrOpcode::kJSInstanceOf:
case IrOpcode::kJSOrdinaryHasInstance:
// Object operations
case IrOpcode::kJSCreate:
......
......@@ -1242,6 +1242,10 @@ Type* Typer::Visitor::TypeJSHasProperty(Node* node) { return Type::Boolean(); }
Type* Typer::Visitor::TypeJSInstanceOf(Node* node) { return Type::Boolean(); }
Type* Typer::Visitor::TypeJSOrdinaryHasInstance(Node* node) {
return Type::Boolean();
}
// JS context operators.
......@@ -1402,9 +1406,15 @@ Type* Typer::Visitor::JSCallFunctionTyper(Type* fun, Typer* t) {
return Type::Range(-1, kMaxSafeInteger, t->zone());
case kArrayPush:
return t->cache_.kPositiveSafeInteger;
// Object functions.
case kObjectHasOwnProperty:
return Type::Boolean();
// Function functions.
case kFunctionHasInstance:
return Type::Boolean();
// Global functions.
case kGlobalDecodeURI:
case kGlobalDecodeURIComponent:
......
......@@ -604,6 +604,7 @@ void Verifier::Visitor::Check(Node* node) {
case IrOpcode::kJSDeleteProperty:
case IrOpcode::kJSHasProperty:
case IrOpcode::kJSInstanceOf:
case IrOpcode::kJSOrdinaryHasInstance:
// Type is Boolean.
CheckTypeIs(node, Type::Boolean());
break;
......
......@@ -7309,6 +7309,7 @@ enum BuiltinFunctionId {
kDataViewBuffer,
kDataViewByteLength,
kDataViewByteOffset,
kFunctionHasInstance,
kGlobalDecodeURI,
kGlobalDecodeURIComponent,
kGlobalEncodeURI,
......
// Copyright 2016 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 A() {}
var a = new A();
var B = {
[Symbol.hasInstance](o) {
return false;
}
};
%ToFastProperties(B.__proto__);
var C = Object.create({
[Symbol.hasInstance](o) {
return true;
}
});
%ToFastProperties(C.__proto__);
var D = Object.create({
[Symbol.hasInstance](o) {
return o === a;
}
});
%ToFastProperties(D.__proto__);
var E = Object.create({
[Symbol.hasInstance](o) {
if (o === a) throw o;
return true;
}
});
%ToFastProperties(E.__proto__);
function F() {}
F.__proto__ = null;
(function() {
function foo(o) { return o instanceof A; }
assertTrue(foo(a));
assertTrue(foo(a));
assertTrue(foo(new A()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(a));
assertTrue(foo(new A()));
})();
(function() {
function foo(o) {
try {
return o instanceof A;
} catch (e) {
return e;
}
}
assertTrue(foo(a));
assertTrue(foo(a));
assertTrue(foo(new A()));
assertEquals(1, foo(new Proxy({}, {getPrototypeOf() { throw 1; }})));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(a));
assertTrue(foo(new A()));
assertEquals(1, foo(new Proxy({}, {getPrototypeOf() { throw 1; }})));
})();
(function() {
function foo(o) { return o instanceof B; }
assertFalse(foo(a));
assertFalse(foo(a));
assertFalse(foo(new A()));
%OptimizeFunctionOnNextCall(foo);
assertFalse(foo(a));
assertFalse(foo(new A()));
})();
(function() {
function foo(o) { return o instanceof C; }
assertTrue(foo(a));
assertTrue(foo(a));
assertTrue(foo(new A()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(a));
assertTrue(foo(new A()));
})();
(function() {
function foo(o) { return o instanceof D; }
assertTrue(foo(a));
assertTrue(foo(a));
assertFalse(foo(new A()));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(a));
assertFalse(foo(new A()));
})();
(function() {
function foo(o) {
try {
return o instanceof E;
} catch (e) {
return false;
}
}
assertFalse(foo(a));
assertTrue(foo(new A()));
%OptimizeFunctionOnNextCall(foo);
assertFalse(foo(a));
assertTrue(foo(new A()));
})();
(function() {
function foo(o) {
return o instanceof F;
}
assertFalse(foo(a));
assertFalse(foo(new A()));
assertTrue(foo(new F()));
%OptimizeFunctionOnNextCall(foo);
assertFalse(foo(a));
assertFalse(foo(new A()));
assertTrue(foo(new F()));
})();
......@@ -859,43 +859,6 @@ TEST_F(JSTypedLoweringTest, JSSubtractSmis) {
lhs, rhs, effect, control));
}
// -----------------------------------------------------------------------------
// JSInstanceOf
// Test that instanceOf is reduced if and only if the right-hand side is a
// function constant. Functional correctness is ensured elsewhere.
TEST_F(JSTypedLoweringTest, JSInstanceOfSpecialization) {
Node* const context = Parameter(Type::Any());
Node* const frame_state = EmptyFrameState();
Node* const effect = graph()->start();
Node* const control = graph()->start();
Node* instanceOf =
graph()->NewNode(javascript()->InstanceOf(), Parameter(Type::Any(), 0),
HeapConstant(isolate()->object_function()), context,
frame_state, effect, control);
Reduction r = Reduce(instanceOf);
ASSERT_TRUE(r.Changed());
}
TEST_F(JSTypedLoweringTest, JSInstanceOfNoSpecialization) {
Node* const context = Parameter(Type::Any());
Node* const frame_state = EmptyFrameState();
Node* const effect = graph()->start();
Node* const control = graph()->start();
// Do not reduce if right-hand side is not a function constant.
Node* instanceOf = graph()->NewNode(
javascript()->InstanceOf(), Parameter(Type::Any(), 0),
Parameter(Type::Any()), context, frame_state, effect, control);
Node* dummy = graph()->NewNode(javascript()->ToObject(), instanceOf, context,
frame_state, effect, control);
Reduction r = Reduce(instanceOf);
ASSERT_FALSE(r.Changed());
ASSERT_EQ(instanceOf, dummy->InputAt(0));
}
// -----------------------------------------------------------------------------
// JSBitwiseAnd
......
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