[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:
parent
1852300954
commit
777e142ca1
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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) \
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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) \
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
18
test/mjsunit/compiler/instanceof-opt1.js
Normal file
18
test/mjsunit/compiler/instanceof-opt1.js
Normal 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());
|
16
test/mjsunit/compiler/instanceof-opt2.js
Normal file
16
test/mjsunit/compiler/instanceof-opt2.js
Normal 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());
|
Loading…
Reference in New Issue
Block a user