[turbofan] Add appropriate types to express Callable.

This introduces three new types OtherCallable, CallableProxy (and OtherProxy),
and BoundFunction to make it possible to express Callable in the Type system.
It also forces all undetectable receivers to be Callable, which matches the
use case for undetectable, namely document.all (guarded by proper checks and
tests).

It also uses these new types to properly optimize instanceof (indirectly via
OrdinaryHasInstance) based on the type of the constructor and the object. So
we are able to constant-fold certain instanceof expressions based on types
and completely avoid the builtin call.

R=jarin@chromium.org
BUG=v8:5267

Review-Url: https://codereview.chromium.org/2535753004
Cr-Commit-Position: refs/heads/master@{#41345}
This commit is contained in:
bmeurer 2016-11-29 02:47:53 -08:00 committed by Commit bot
parent 1852300954
commit 777e142ca1
10 changed files with 113 additions and 22 deletions

View File

@ -675,6 +675,12 @@ Handle<JSFunction> ApiNatives::CreateApiFunction(
// Mark as undetectable if needed.
if (obj->undetectable()) {
// We only allow callable undetectable receivers here, since this whole
// undetectable business is only to support document.all, which is both
// undetectable and callable. If we ever see the need to have an object
// that is undetectable but not callable, we need to update the types.h
// to allow encoding this.
CHECK(!obj->instance_call_handler()->IsUndefined(isolate));
map->set_is_undetectable();
}

View File

@ -1331,11 +1331,30 @@ Reduction JSTypedLowering::ReduceJSOrdinaryHasInstance(Node* node) {
Node* constructor = NodeProperties::GetValueInput(node, 0);
Type* constructor_type = NodeProperties::GetType(constructor);
Node* object = NodeProperties::GetValueInput(node, 1);
Type* object_type = NodeProperties::GetType(object);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Check if the {constructor} cannot be callable.
// See ES6 section 7.3.19 OrdinaryHasInstance ( C, O ) step 1.
if (!constructor_type->Maybe(Type::Callable())) {
Node* value = jsgraph()->FalseConstant();
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// If the {constructor} cannot be a JSBoundFunction and then {object}
// cannot be a JSReceiver, then this can be constant-folded to false.
// See ES6 section 7.3.19 OrdinaryHasInstance ( C, O ) step 2 and 3.
if (!object_type->Maybe(Type::Receiver()) &&
!constructor_type->Maybe(Type::BoundFunction())) {
Node* value = jsgraph()->FalseConstant();
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// Check if the {constructor} is a (known) JSFunction.
if (!constructor_type->IsHeapConstant() ||
!constructor_type->AsHeapConstant()->Value()->IsJSFunction()) {

View File

@ -104,7 +104,9 @@
#define JS_SIMPLE_BINOP_LIST(V) \
JS_COMPARE_BINOP_LIST(V) \
JS_BITWISE_BINOP_LIST(V) \
JS_ARITH_BINOP_LIST(V)
JS_ARITH_BINOP_LIST(V) \
V(JSInstanceOf) \
V(JSOrdinaryHasInstance)
#define JS_CONVERSION_UNOP_LIST(V) \
V(JSToBoolean) \
@ -140,9 +142,7 @@
V(JSStoreGlobal) \
V(JSStoreDataPropertyInLiteral) \
V(JSDeleteProperty) \
V(JSHasProperty) \
V(JSInstanceOf) \
V(JSOrdinaryHasInstance)
V(JSHasProperty)
#define JS_CONTEXT_OP_LIST(V) \
V(JSLoadContext) \

View File

@ -1244,9 +1244,14 @@ Type* Typer::Visitor::TypeJSDeleteProperty(Node* node) {
Type* Typer::Visitor::TypeJSHasProperty(Node* node) { return Type::Boolean(); }
Type* Typer::Visitor::TypeJSInstanceOf(Node* node) { return Type::Boolean(); }
// JS instanceof operator.
Type* Typer::Visitor::TypeJSOrdinaryHasInstance(Node* node) {
Type* Typer::Visitor::JSInstanceOfTyper(Type* lhs, Type* rhs, Typer* t) {
return Type::Boolean();
}
Type* Typer::Visitor::JSOrdinaryHasInstanceTyper(Type* lhs, Type* rhs,
Typer* t) {
return Type::Boolean();
}

View File

@ -196,7 +196,17 @@ Type::bitset BitsetType::Lub(i::Map* map) {
case JS_GLOBAL_PROXY_TYPE:
case JS_API_OBJECT_TYPE:
case JS_SPECIAL_API_OBJECT_TYPE:
if (map->is_undetectable()) return kOtherUndetectable;
if (map->is_undetectable()) {
// Currently we assume that every undetectable receiver is also
// callable, which is what we need to support document.all. We
// could add another Type bit to support other use cases in the
// future if necessary.
DCHECK(map->is_callable());
return kOtherUndetectable;
}
if (map->is_callable()) {
return kOtherCallable;
}
return kOtherObject;
case JS_VALUE_TYPE:
case JS_MESSAGE_OBJECT_TYPE:
@ -255,15 +265,19 @@ Type::bitset BitsetType::Lub(i::Map* map) {
case JS_WEAK_MAP_TYPE:
case JS_WEAK_SET_TYPE:
case JS_PROMISE_TYPE:
case JS_BOUND_FUNCTION_TYPE:
DCHECK(!map->is_callable());
DCHECK(!map->is_undetectable());
return kOtherObject;
case JS_BOUND_FUNCTION_TYPE:
DCHECK(!map->is_undetectable());
return kBoundFunction;
case JS_FUNCTION_TYPE:
DCHECK(!map->is_undetectable());
return kFunction;
case JS_PROXY_TYPE:
DCHECK(!map->is_undetectable());
return kProxy;
if (map->is_callable()) return kCallableProxy;
return kOtherProxy;
case MAP_TYPE:
case ALLOCATION_SITE_TYPE:
case ACCESSOR_INFO_TYPE:

View File

@ -117,13 +117,16 @@ namespace compiler {
V(InternalizedString, 1u << 13) \
V(OtherString, 1u << 14) \
V(Simd, 1u << 15) \
V(OtherCallable, 1u << 16) \
V(OtherObject, 1u << 17) \
V(OtherUndetectable, 1u << 16) \
V(Proxy, 1u << 18) \
V(Function, 1u << 19) \
V(Hole, 1u << 20) \
V(OtherInternal, 1u << 21) \
V(ExternalPointer, 1u << 22) \
V(OtherUndetectable, 1u << 18) \
V(CallableProxy, 1u << 19) \
V(OtherProxy, 1u << 20) \
V(Function, 1u << 21) \
V(BoundFunction, 1u << 22) \
V(Hole, 1u << 23) \
V(OtherInternal, 1u << 24) \
V(ExternalPointer, 1u << 25) \
\
V(Signed31, kUnsigned30 | kNegative31) \
V(Signed32, kSigned31 | kOtherUnsigned31 | kOtherSigned32) \
@ -155,9 +158,14 @@ namespace compiler {
V(NumberOrUndefined, kNumber | kUndefined) \
V(PlainPrimitive, kNumberOrString | kBoolean | kNullOrUndefined) \
V(Primitive, kSymbol | kSimd | kPlainPrimitive) \
V(DetectableReceiver, kFunction | kOtherObject | kProxy) \
V(Proxy, kCallableProxy | kOtherProxy) \
V(Callable, kFunction | kBoundFunction | kOtherCallable | \
kCallableProxy | kOtherUndetectable) \
V(DetectableObject, kFunction | kBoundFunction | kOtherCallable | \
kOtherObject) \
V(DetectableReceiver, kDetectableObject | kProxy) \
V(DetectableReceiverOrNull, kDetectableReceiver | kNull) \
V(Object, kFunction | kOtherObject | kOtherUndetectable) \
V(Object, kDetectableObject | kOtherUndetectable) \
V(Receiver, kObject | kProxy) \
V(ReceiverOrUndefined, kReceiver | kUndefined) \
V(ReceiverOrNullOrUndefined, kReceiver | kNull | kUndefined) \

View File

@ -299,6 +299,9 @@ RUNTIME_FUNCTION(Runtime_GetOptimizationCount) {
return Smi::FromInt(function->shared()->opt_count());
}
static void ReturnThis(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(args.This());
}
RUNTIME_FUNCTION(Runtime_GetUndetectable) {
HandleScope scope(isolate);
@ -307,6 +310,7 @@ RUNTIME_FUNCTION(Runtime_GetUndetectable) {
Local<v8::ObjectTemplate> desc = v8::ObjectTemplate::New(v8_isolate);
desc->MarkAsUndetectable();
desc->SetCallAsFunctionHandler(ReturnThis);
Local<v8::Object> obj;
if (!desc->NewInstance(v8_isolate->GetCurrentContext()).ToLocal(&obj)) {
return nullptr;

View File

@ -7085,6 +7085,9 @@ THREADED_TEST(Regress892105) {
.FromJust());
}
static void ReturnThis(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(args.This());
}
THREADED_TEST(UndetectableObject) {
LocalContext env;
@ -7093,6 +7096,7 @@ THREADED_TEST(UndetectableObject) {
Local<v8::FunctionTemplate> desc =
v8::FunctionTemplate::New(env->GetIsolate());
desc->InstanceTemplate()->MarkAsUndetectable(); // undetectable
desc->InstanceTemplate()->SetCallAsFunctionHandler(ReturnThis); // callable
Local<v8::Object> obj = desc->GetFunction(env.local())
.ToLocalChecked()
@ -7141,6 +7145,7 @@ THREADED_TEST(VoidLiteral) {
Local<v8::FunctionTemplate> desc = v8::FunctionTemplate::New(isolate);
desc->InstanceTemplate()->MarkAsUndetectable(); // undetectable
desc->InstanceTemplate()->SetCallAsFunctionHandler(ReturnThis); // callable
Local<v8::Object> obj = desc->GetFunction(env.local())
.ToLocalChecked()
@ -7191,6 +7196,7 @@ THREADED_TEST(ExtensibleOnUndetectable) {
Local<v8::FunctionTemplate> desc = v8::FunctionTemplate::New(isolate);
desc->InstanceTemplate()->MarkAsUndetectable(); // undetectable
desc->InstanceTemplate()->SetCallAsFunctionHandler(ReturnThis); // callable
Local<v8::Object> obj = desc->GetFunction(env.local())
.ToLocalChecked()
@ -11775,11 +11781,6 @@ static void call_as_function(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
static void ReturnThis(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(args.This());
}
// Test that a call handler can be set for objects which will allow
// non-function objects created through the API to be called as
// functions.

View File

@ -0,0 +1,18 @@
// 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
var Foo = {
[Symbol.hasInstance]: Function.prototype[Symbol.hasInstance]
};
// TurboFan will optimize this to false via constant-folding the
// OrdinaryHasInstance call inside Function.prototype[@@hasInstance].
function foo() { return 1 instanceof Foo; }
assertEquals(false, foo());
assertEquals(false, foo());
%OptimizeFunctionOnNextCall(foo);
assertEquals(false, foo());

View File

@ -0,0 +1,16 @@
// 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 Foo() {}
// TurboFan will optimize this to false via constant-folding the
// OrdinaryHasInstance call inside Function.prototype[@@hasInstance].
function foo() { return 1 instanceof Foo; }
assertEquals(false, foo());
assertEquals(false, foo());
%OptimizeFunctionOnNextCall(foo);
assertEquals(false, foo());