[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}
This commit is contained in:
bmeurer 2016-11-17 22:30:57 -08:00 committed by Commit bot
parent 5beb5ee7e6
commit 241c024c10
21 changed files with 328 additions and 73 deletions

View File

@ -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.

View File

@ -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

View File

@ -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 */ \

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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());

View File

@ -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());

View File

@ -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);

View File

@ -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) \

View File

@ -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();

View File

@ -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);
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 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();
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());
Handle<JSFunction>::cast(constructor_type->AsHeapConstant()->Value());
// 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();
}
// 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:

View File

@ -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);

View File

@ -140,7 +140,8 @@
V(JSStoreGlobal) \
V(JSDeleteProperty) \
V(JSHasProperty) \
V(JSInstanceOf)
V(JSInstanceOf) \
V(JSOrdinaryHasInstance)
#define JS_CONTEXT_OP_LIST(V) \
V(JSLoadContext) \

View File

@ -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:

View File

@ -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:

View File

@ -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;

View File

@ -7309,6 +7309,7 @@ enum BuiltinFunctionId {
kDataViewBuffer,
kDataViewByteLength,
kDataViewByteOffset,
kFunctionHasInstance,
kGlobalDecodeURI,
kGlobalDecodeURIComponent,
kGlobalEncodeURI,

View File

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

View File

@ -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