[builtins] Properly optimize Object.prototype.isPrototypeOf.
Port the baseline implementation of Object.prototype.isPrototypeOf to the CodeStubAssembler, sharing the existing prototype chain lookup logic with the instanceof / OrdinaryHasInstance implementation. Based on that, do the same in TurboFan, introducing a new JSHasInPrototypeChain operator, which encapsulates the central prototype chain walk logic. This speeds up Object.prototype.isPrototypeOf by more than a factor of four, so that the code A.prototype.isPrototypeOf(a) is now performance-wise on par with a instanceof A for the case where A is a regular constructor function and a is an instance of A. Since instanceof does more than just the fundamental prototype chain lookup, it was discovered in Node core that O.p.isPrototypeOf would be a more appropriate alternative for certain sanity checks, since it's less vulnerable to monkey-patching. In addition, the Object builtin would also avoid the performance-cliff associated with instanceof (due to the Symbol.hasInstance hook), as for example hit by https://github.com/nodejs/node/pull/13403#issuecomment-305915874. The main blocker was the missing performance of isPrototypeOf, since it was still a JS builtin backed by a runtime call. This CL also adds more test coverage for the Object.prototype.isPrototypeOf builtin, especially when called from optimized code. CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:linux_chromium_rel_ng BUG=v8:5269,v8:5989,v8:6483 R=jgruber@chromium.org Review-Url: https://codereview.chromium.org/2934893002 Cr-Commit-Position: refs/heads/master@{#45925}
This commit is contained in:
parent
8196e10265
commit
b11c557d32
@ -1331,6 +1331,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
|
||||
SimpleInstallFunction(isolate->initial_object_prototype(),
|
||||
"__lookupSetter__", Builtins::kObjectLookupSetter, 1,
|
||||
true);
|
||||
SimpleInstallFunction(isolate->initial_object_prototype(), "isPrototypeOf",
|
||||
Builtins::kObjectPrototypeIsPrototypeOf, 1, true);
|
||||
SimpleInstallFunction(
|
||||
isolate->initial_object_prototype(), "propertyIsEnumerable",
|
||||
Builtins::kObjectPrototypePropertyIsEnumerable, 1, false);
|
||||
|
@ -743,6 +743,7 @@ namespace internal {
|
||||
TFJ(ObjectProtoToString, 0) \
|
||||
/* ES6 #sec-object.prototype.valueof */ \
|
||||
TFJ(ObjectPrototypeValueOf, 0) \
|
||||
TFJ(ObjectPrototypeIsPrototypeOf, 1, kValue) \
|
||||
CPP(ObjectPrototypePropertyIsEnumerable) \
|
||||
CPP(ObjectPrototypeGetProto) \
|
||||
CPP(ObjectPrototypeSetProto) \
|
||||
|
@ -192,6 +192,52 @@ TF_BUILTIN(ObjectKeys, ObjectBuiltinsAssembler) {
|
||||
}
|
||||
}
|
||||
|
||||
// ES #sec-object.prototype.isprototypeof
|
||||
TF_BUILTIN(ObjectPrototypeIsPrototypeOf, ObjectBuiltinsAssembler) {
|
||||
Node* receiver = Parameter(Descriptor::kReceiver);
|
||||
Node* value = Parameter(Descriptor::kValue);
|
||||
Node* context = Parameter(Descriptor::kContext);
|
||||
Label if_receiverisnullorundefined(this, Label::kDeferred),
|
||||
if_valueisnotreceiver(this, Label::kDeferred);
|
||||
|
||||
// We only check whether {value} is a Smi here, so that the
|
||||
// prototype chain walk below can safely access the {value}s
|
||||
// map. We don't rule out Primitive {value}s, since all of
|
||||
// them have null as their prototype, so the chain walk below
|
||||
// immediately aborts and returns false anyways.
|
||||
GotoIf(TaggedIsSmi(value), &if_valueisnotreceiver);
|
||||
|
||||
// Check if {receiver} is either null or undefined and in that case,
|
||||
// invoke the ToObject builtin, which raises the appropriate error.
|
||||
// Otherwise we don't need to invoke ToObject, since {receiver} is
|
||||
// either already a JSReceiver, in which case ToObject is a no-op,
|
||||
// or it's a Primitive and ToObject would allocate a fresh JSValue
|
||||
// wrapper, which wouldn't be identical to any existing JSReceiver
|
||||
// found in the prototype chain of {value}, hence it will return
|
||||
// false no matter if we search for the Primitive {receiver} or
|
||||
// a newly allocated JSValue wrapper for {receiver}.
|
||||
GotoIf(IsNull(receiver), &if_receiverisnullorundefined);
|
||||
GotoIf(IsUndefined(receiver), &if_receiverisnullorundefined);
|
||||
|
||||
// Loop through the prototype chain looking for the {receiver}.
|
||||
Return(HasInPrototypeChain(context, value, receiver));
|
||||
|
||||
BIND(&if_receiverisnullorundefined);
|
||||
{
|
||||
// If {value} is a primitive HeapObject, we need to return
|
||||
// false instead of throwing an exception per order of the
|
||||
// steps in the specification, so check that first here.
|
||||
GotoIfNot(IsJSReceiver(value), &if_valueisnotreceiver);
|
||||
|
||||
// Simulate the ToObject invocation on {receiver}.
|
||||
CallBuiltin(Builtins::kToObject, context, receiver);
|
||||
Unreachable();
|
||||
}
|
||||
|
||||
BIND(&if_valueisnotreceiver);
|
||||
Return(FalseConstant());
|
||||
}
|
||||
|
||||
// ES6 #sec-object.prototype.tostring
|
||||
TF_BUILTIN(ObjectProtoToString, ObjectBuiltinsAssembler) {
|
||||
Label return_undefined(this, Label::kDeferred),
|
||||
|
@ -5997,18 +5997,78 @@ void CodeStubAssembler::TryPrototypeChainLookup(
|
||||
}
|
||||
}
|
||||
|
||||
Node* CodeStubAssembler::OrdinaryHasInstance(Node* context, Node* callable,
|
||||
Node* object) {
|
||||
Node* CodeStubAssembler::HasInPrototypeChain(Node* context, Node* object,
|
||||
Node* prototype) {
|
||||
CSA_ASSERT(this, TaggedIsNotSmi(object));
|
||||
VARIABLE(var_result, MachineRepresentation::kTagged);
|
||||
Label return_false(this), return_true(this),
|
||||
return_runtime(this, Label::kDeferred), return_result(this);
|
||||
|
||||
// Loop through the prototype chain looking for the {prototype}.
|
||||
VARIABLE(var_object_map, MachineRepresentation::kTagged, LoadMap(object));
|
||||
Label loop(this, &var_object_map);
|
||||
Goto(&loop);
|
||||
BIND(&loop);
|
||||
{
|
||||
// Check if we can determine the prototype directly from the {object_map}.
|
||||
Label if_objectisdirect(this), if_objectisspecial(this, Label::kDeferred);
|
||||
Node* object_map = var_object_map.value();
|
||||
Node* object_instance_type = LoadMapInstanceType(object_map);
|
||||
Branch(IsSpecialReceiverInstanceType(object_instance_type),
|
||||
&if_objectisspecial, &if_objectisdirect);
|
||||
BIND(&if_objectisspecial);
|
||||
{
|
||||
// The {object_map} is a special receiver map or a primitive map, check
|
||||
// if we need to use the if_objectisspecial path in the runtime.
|
||||
GotoIf(InstanceTypeEqual(object_instance_type, JS_PROXY_TYPE),
|
||||
&return_runtime);
|
||||
Node* object_bitfield = LoadMapBitField(object_map);
|
||||
Node* mask = Int32Constant(1 << Map::kHasNamedInterceptor |
|
||||
1 << Map::kIsAccessCheckNeeded);
|
||||
Branch(Word32NotEqual(Word32And(object_bitfield, mask), Int32Constant(0)),
|
||||
&return_runtime, &if_objectisdirect);
|
||||
}
|
||||
BIND(&if_objectisdirect);
|
||||
|
||||
// Check the current {object} prototype.
|
||||
Node* object_prototype = LoadMapPrototype(object_map);
|
||||
GotoIf(IsNull(object_prototype), &return_false);
|
||||
GotoIf(WordEqual(object_prototype, prototype), &return_true);
|
||||
|
||||
// Continue with the prototype.
|
||||
CSA_ASSERT(this, TaggedIsNotSmi(object_prototype));
|
||||
var_object_map.Bind(LoadMap(object_prototype));
|
||||
Goto(&loop);
|
||||
}
|
||||
|
||||
BIND(&return_true);
|
||||
var_result.Bind(TrueConstant());
|
||||
Goto(&return_result);
|
||||
|
||||
BIND(&return_false);
|
||||
var_result.Bind(FalseConstant());
|
||||
Goto(&return_result);
|
||||
|
||||
BIND(&return_runtime);
|
||||
{
|
||||
// Fallback to the runtime implementation.
|
||||
var_result.Bind(
|
||||
CallRuntime(Runtime::kHasInPrototypeChain, context, object, prototype));
|
||||
}
|
||||
Goto(&return_result);
|
||||
|
||||
BIND(&return_result);
|
||||
return var_result.value();
|
||||
}
|
||||
|
||||
Node* CodeStubAssembler::OrdinaryHasInstance(Node* context, Node* callable,
|
||||
Node* object) {
|
||||
VARIABLE(var_result, MachineRepresentation::kTagged);
|
||||
Label return_runtime(this, Label::kDeferred), return_result(this);
|
||||
|
||||
// Goto runtime if {object} is a Smi.
|
||||
GotoIf(TaggedIsSmi(object), &return_runtime);
|
||||
|
||||
// Load map of {object}.
|
||||
Node* object_map = LoadMap(object);
|
||||
|
||||
// Goto runtime if {callable} is a Smi.
|
||||
GotoIf(TaggedIsSmi(callable), &return_runtime);
|
||||
|
||||
@ -6056,42 +6116,7 @@ Node* CodeStubAssembler::OrdinaryHasInstance(Node* context, Node* callable,
|
||||
}
|
||||
|
||||
// Loop through the prototype chain looking for the {callable} prototype.
|
||||
VARIABLE(var_object_map, MachineRepresentation::kTagged, object_map);
|
||||
Label loop(this, &var_object_map);
|
||||
Goto(&loop);
|
||||
BIND(&loop);
|
||||
{
|
||||
Node* object_map = var_object_map.value();
|
||||
|
||||
// Check if the current {object} needs to be access checked.
|
||||
Node* object_bitfield = LoadMapBitField(object_map);
|
||||
GotoIfNot(
|
||||
Word32Equal(Word32And(object_bitfield,
|
||||
Int32Constant(1 << Map::kIsAccessCheckNeeded)),
|
||||
Int32Constant(0)),
|
||||
&return_runtime);
|
||||
|
||||
// Check if the current {object} is a proxy.
|
||||
Node* object_instance_type = LoadMapInstanceType(object_map);
|
||||
GotoIf(Word32Equal(object_instance_type, Int32Constant(JS_PROXY_TYPE)),
|
||||
&return_runtime);
|
||||
|
||||
// Check the current {object} prototype.
|
||||
Node* object_prototype = LoadMapPrototype(object_map);
|
||||
GotoIf(WordEqual(object_prototype, NullConstant()), &return_false);
|
||||
GotoIf(WordEqual(object_prototype, callable_prototype), &return_true);
|
||||
|
||||
// Continue with the prototype.
|
||||
var_object_map.Bind(LoadMap(object_prototype));
|
||||
Goto(&loop);
|
||||
}
|
||||
|
||||
BIND(&return_true);
|
||||
var_result.Bind(BooleanConstant(true));
|
||||
Goto(&return_result);
|
||||
|
||||
BIND(&return_false);
|
||||
var_result.Bind(BooleanConstant(false));
|
||||
var_result.Bind(HasInPrototypeChain(context, object, callable_prototype));
|
||||
Goto(&return_result);
|
||||
|
||||
BIND(&return_runtime);
|
||||
|
@ -1233,6 +1233,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
|
||||
Label* if_end, Label* if_bailout);
|
||||
|
||||
// Instanceof helpers.
|
||||
// Returns true if {object} has {prototype} somewhere in it's prototype
|
||||
// chain, otherwise false is returned. Might cause arbitrary side effects
|
||||
// due to [[GetPrototypeOf]] invocations.
|
||||
Node* HasInPrototypeChain(Node* context, Node* object, Node* prototype);
|
||||
// ES6 section 7.3.19 OrdinaryHasInstance (C, O)
|
||||
Node* OrdinaryHasInstance(Node* context, Node* callable, Node* object);
|
||||
|
||||
|
@ -342,6 +342,38 @@ Reduction JSCallReducer::ReduceObjectPrototypeGetProto(Node* node) {
|
||||
return ReduceObjectGetPrototype(node, receiver);
|
||||
}
|
||||
|
||||
// ES #sec-object.prototype.isprototypeof
|
||||
Reduction JSCallReducer::ReduceObjectPrototypeIsPrototypeOf(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
|
||||
Node* receiver = NodeProperties::GetValueInput(node, 1);
|
||||
Node* value = node->op()->ValueInputCount() > 2
|
||||
? NodeProperties::GetValueInput(node, 2)
|
||||
: jsgraph()->UndefinedConstant();
|
||||
Node* effect = NodeProperties::GetEffectInput(node);
|
||||
|
||||
// Ensure that the {receiver} is known to be a JSReceiver (so that
|
||||
// the ToObject step of Object.prototype.isPrototypeOf is a no-op).
|
||||
ZoneHandleSet<Map> receiver_maps;
|
||||
NodeProperties::InferReceiverMapsResult result =
|
||||
NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
|
||||
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
|
||||
for (size_t i = 0; i < receiver_maps.size(); ++i) {
|
||||
if (!receiver_maps[i]->IsJSReceiverMap()) return NoChange();
|
||||
}
|
||||
|
||||
// We don't check whether {value} is a proper JSReceiver here explicitly,
|
||||
// and don't explicitly rule out Primitive {value}s, since all of them
|
||||
// have null as their prototype, so the prototype chain walk inside the
|
||||
// JSHasInPrototypeChain operator immediately aborts and yields false.
|
||||
NodeProperties::ReplaceValueInput(node, value, 0);
|
||||
NodeProperties::ReplaceValueInput(node, receiver, 1);
|
||||
for (int i = node->op()->ValueInputCount(); i-- > 2;) {
|
||||
node->RemoveInput(i);
|
||||
}
|
||||
NodeProperties::ChangeOp(node, javascript()->HasInPrototypeChain());
|
||||
return Changed(node);
|
||||
}
|
||||
|
||||
// ES6 section 26.1.7 Reflect.getPrototypeOf ( target )
|
||||
Reduction JSCallReducer::ReduceReflectGetPrototypeOf(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
|
||||
@ -741,6 +773,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
|
||||
return ReduceObjectGetPrototypeOf(node);
|
||||
case Builtins::kObjectPrototypeGetProto:
|
||||
return ReduceObjectPrototypeGetProto(node);
|
||||
case Builtins::kObjectPrototypeIsPrototypeOf:
|
||||
return ReduceObjectPrototypeIsPrototypeOf(node);
|
||||
case Builtins::kReflectGetPrototypeOf:
|
||||
return ReduceReflectGetPrototypeOf(node);
|
||||
case Builtins::kArrayForEach:
|
||||
|
@ -49,6 +49,7 @@ class JSCallReducer final : public AdvancedReducer {
|
||||
Reduction ReduceObjectGetPrototype(Node* node, Node* object);
|
||||
Reduction ReduceObjectGetPrototypeOf(Node* node);
|
||||
Reduction ReduceObjectPrototypeGetProto(Node* node);
|
||||
Reduction ReduceObjectPrototypeIsPrototypeOf(Node* node);
|
||||
Reduction ReduceReflectGetPrototypeOf(Node* node);
|
||||
Reduction ReduceArrayForEach(Handle<JSFunction> function, Node* node);
|
||||
Reduction ReduceSpreadCall(Node* node, int arity);
|
||||
|
@ -338,6 +338,10 @@ void JSGenericLowering::LowerJSGetSuperConstructor(Node* node) {
|
||||
ReplaceWithStubCall(node, callable, flags);
|
||||
}
|
||||
|
||||
void JSGenericLowering::LowerJSHasInPrototypeChain(Node* node) {
|
||||
ReplaceWithRuntimeCall(node, Runtime::kHasInPrototypeChain);
|
||||
}
|
||||
|
||||
void JSGenericLowering::LowerJSInstanceOf(Node* node) {
|
||||
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
|
||||
Callable callable = Builtins::CallableFor(isolate(), Builtins::kInstanceOf);
|
||||
|
@ -274,10 +274,6 @@ Reduction JSNativeContextSpecialization::ReduceJSOrdinaryHasInstance(
|
||||
DCHECK_EQ(IrOpcode::kJSOrdinaryHasInstance, node->opcode());
|
||||
Node* constructor = NodeProperties::GetValueInput(node, 0);
|
||||
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);
|
||||
|
||||
// Check if the {constructor} is known at compile time.
|
||||
HeapObjectMatcher m(constructor);
|
||||
@ -311,143 +307,13 @@ Reduction JSNativeContextSpecialization::ReduceJSOrdinaryHasInstance(
|
||||
// Install a code dependency on the {function}s initial map.
|
||||
Handle<Map> initial_map(function->initial_map(), isolate());
|
||||
dependencies()->AssumeInitialMapCantChange(initial_map);
|
||||
Handle<JSReceiver> function_prototype =
|
||||
handle(JSReceiver::cast(initial_map->prototype()), isolate());
|
||||
Node* prototype =
|
||||
jsgraph()->Constant(handle(initial_map->prototype(), isolate()));
|
||||
|
||||
// Check if we can constant-fold the prototype chain walk
|
||||
// for the given {object} and the {function_prototype}.
|
||||
InferHasInPrototypeChainResult result =
|
||||
InferHasInPrototypeChain(object, effect, function_prototype);
|
||||
if (result != kMayBeInPrototypeChain) {
|
||||
Node* value = jsgraph()->BooleanConstant(result == kIsInPrototypeChain);
|
||||
ReplaceWithValue(node, value, effect, control);
|
||||
return Replace(value);
|
||||
}
|
||||
|
||||
Node* prototype = jsgraph()->Constant(function_prototype);
|
||||
|
||||
Node* check0 = graph()->NewNode(simplified()->ObjectIsSmi(), object);
|
||||
Node* branch0 = graph()->NewNode(common()->Branch(BranchHint::kFalse),
|
||||
check0, control);
|
||||
|
||||
Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0);
|
||||
Node* etrue0 = effect;
|
||||
Node* vtrue0 = jsgraph()->FalseConstant();
|
||||
|
||||
control = graph()->NewNode(common()->IfFalse(), branch0);
|
||||
|
||||
// Loop through the {object}s prototype chain looking for the {prototype}.
|
||||
Node* loop = control =
|
||||
graph()->NewNode(common()->Loop(2), control, control);
|
||||
Node* eloop = effect =
|
||||
graph()->NewNode(common()->EffectPhi(2), effect, effect, loop);
|
||||
Node* vloop = object =
|
||||
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
|
||||
object, object, loop);
|
||||
|
||||
// Load the {object} map and instance type.
|
||||
Node* object_map = effect =
|
||||
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
||||
object, effect, control);
|
||||
Node* object_instance_type = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForMapInstanceType()),
|
||||
object_map, effect, control);
|
||||
|
||||
// Check if the {object} is a special receiver, because for special
|
||||
// receivers, i.e. proxies or API objects that need access checks,
|
||||
// we have to use the %HasInPrototypeChain runtime function instead.
|
||||
Node* check1 = graph()->NewNode(
|
||||
simplified()->NumberLessThanOrEqual(), object_instance_type,
|
||||
jsgraph()->Constant(LAST_SPECIAL_RECEIVER_TYPE));
|
||||
Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kFalse),
|
||||
check1, control);
|
||||
|
||||
control = graph()->NewNode(common()->IfFalse(), branch1);
|
||||
|
||||
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
|
||||
Node* etrue1 = effect;
|
||||
Node* vtrue1;
|
||||
|
||||
// Check if the {object} is not a receiver at all.
|
||||
Node* check10 =
|
||||
graph()->NewNode(simplified()->NumberLessThan(), object_instance_type,
|
||||
jsgraph()->Constant(FIRST_JS_RECEIVER_TYPE));
|
||||
Node* branch10 = graph()->NewNode(common()->Branch(BranchHint::kTrue),
|
||||
check10, if_true1);
|
||||
|
||||
// A primitive value cannot match the {prototype} we're looking for.
|
||||
if_true1 = graph()->NewNode(common()->IfTrue(), branch10);
|
||||
vtrue1 = jsgraph()->FalseConstant();
|
||||
|
||||
Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch10);
|
||||
Node* efalse1 = etrue1;
|
||||
Node* vfalse1;
|
||||
{
|
||||
// Slow path, need to call the %HasInPrototypeChain runtime function.
|
||||
vfalse1 = efalse1 = if_false1 = graph()->NewNode(
|
||||
javascript()->CallRuntime(Runtime::kHasInPrototypeChain), object,
|
||||
prototype, context, frame_state, efalse1, if_false1);
|
||||
|
||||
// Replace any potential {IfException} uses of {node} to catch
|
||||
// exceptions from this %HasInPrototypeChain runtime call instead.
|
||||
Node* on_exception = nullptr;
|
||||
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
|
||||
NodeProperties::ReplaceControlInput(on_exception, vfalse1);
|
||||
NodeProperties::ReplaceEffectInput(on_exception, efalse1);
|
||||
if_false1 = graph()->NewNode(common()->IfSuccess(), vfalse1);
|
||||
Revisit(on_exception);
|
||||
}
|
||||
}
|
||||
|
||||
// Load the {object} prototype.
|
||||
Node* object_prototype = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForMapPrototype()), object_map,
|
||||
effect, control);
|
||||
|
||||
// Check if we reached the end of {object}s prototype chain.
|
||||
Node* check2 =
|
||||
graph()->NewNode(simplified()->ReferenceEqual(), object_prototype,
|
||||
jsgraph()->NullConstant());
|
||||
Node* branch2 = graph()->NewNode(common()->Branch(), check2, control);
|
||||
|
||||
Node* if_true2 = graph()->NewNode(common()->IfTrue(), branch2);
|
||||
Node* etrue2 = effect;
|
||||
Node* vtrue2 = jsgraph()->FalseConstant();
|
||||
|
||||
control = graph()->NewNode(common()->IfFalse(), branch2);
|
||||
|
||||
// Check if we reached the {prototype}.
|
||||
Node* check3 = graph()->NewNode(simplified()->ReferenceEqual(),
|
||||
object_prototype, prototype);
|
||||
Node* branch3 = graph()->NewNode(common()->Branch(), check3, control);
|
||||
|
||||
Node* if_true3 = graph()->NewNode(common()->IfTrue(), branch3);
|
||||
Node* etrue3 = effect;
|
||||
Node* vtrue3 = jsgraph()->TrueConstant();
|
||||
|
||||
control = graph()->NewNode(common()->IfFalse(), branch3);
|
||||
|
||||
// Close the loop.
|
||||
vloop->ReplaceInput(1, object_prototype);
|
||||
eloop->ReplaceInput(1, effect);
|
||||
loop->ReplaceInput(1, control);
|
||||
|
||||
control = graph()->NewNode(common()->Merge(5), if_true0, if_true1,
|
||||
if_true2, if_true3, if_false1);
|
||||
effect = graph()->NewNode(common()->EffectPhi(5), etrue0, etrue1, etrue2,
|
||||
etrue3, efalse1, control);
|
||||
|
||||
// Morph the {node} into an appropriate Phi.
|
||||
ReplaceWithValue(node, node, effect, control);
|
||||
node->ReplaceInput(0, vtrue0);
|
||||
node->ReplaceInput(1, vtrue1);
|
||||
node->ReplaceInput(2, vtrue2);
|
||||
node->ReplaceInput(3, vtrue3);
|
||||
node->ReplaceInput(4, vfalse1);
|
||||
node->ReplaceInput(5, control);
|
||||
node->TrimInputCount(6);
|
||||
NodeProperties::ChangeOp(
|
||||
node, common()->Phi(MachineRepresentation::kTagged, 5));
|
||||
// Lower the {node} to JSHasInPrototypeChain.
|
||||
NodeProperties::ReplaceValueInput(node, object, 0);
|
||||
NodeProperties::ReplaceValueInput(node, prototype, 1);
|
||||
NodeProperties::ChangeOp(node, javascript()->HasInPrototypeChain());
|
||||
return Changed(node);
|
||||
}
|
||||
}
|
||||
@ -2445,57 +2311,6 @@ bool JSNativeContextSpecialization::CanTreatHoleAsUndefined(
|
||||
return true;
|
||||
}
|
||||
|
||||
JSNativeContextSpecialization::InferHasInPrototypeChainResult
|
||||
JSNativeContextSpecialization::InferHasInPrototypeChain(
|
||||
Node* receiver, Node* effect, Handle<JSReceiver> prototype) {
|
||||
ZoneHandleSet<Map> receiver_maps;
|
||||
NodeProperties::InferReceiverMapsResult result =
|
||||
NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
|
||||
if (result == NodeProperties::kNoReceiverMaps) return kMayBeInPrototypeChain;
|
||||
|
||||
// Check if either all or none of the {receiver_maps} have the given
|
||||
// {prototype} in their prototype chain.
|
||||
bool all = true;
|
||||
bool none = true;
|
||||
for (size_t i = 0; i < receiver_maps.size(); ++i) {
|
||||
Handle<Map> receiver_map = receiver_maps[i];
|
||||
if (receiver_map->instance_type() <= LAST_SPECIAL_RECEIVER_TYPE) {
|
||||
return kMayBeInPrototypeChain;
|
||||
}
|
||||
if (result == NodeProperties::kUnreliableReceiverMaps) {
|
||||
// In case of an unreliable {result} we need to ensure that all
|
||||
// {receiver_maps} are stable, because otherwise we cannot trust
|
||||
// the {receiver_maps} information, since arbitrary side-effects
|
||||
// may have happened.
|
||||
if (!receiver_map->is_stable()) {
|
||||
return kMayBeInPrototypeChain;
|
||||
}
|
||||
}
|
||||
for (PrototypeIterator j(receiver_map);; j.Advance()) {
|
||||
if (j.IsAtEnd()) {
|
||||
all = false;
|
||||
break;
|
||||
}
|
||||
Handle<JSReceiver> const current =
|
||||
PrototypeIterator::GetCurrent<JSReceiver>(j);
|
||||
if (current.is_identical_to(prototype)) {
|
||||
none = false;
|
||||
break;
|
||||
}
|
||||
if (!current->map()->is_stable() ||
|
||||
current->map()->instance_type() <= LAST_SPECIAL_RECEIVER_TYPE) {
|
||||
return kMayBeInPrototypeChain;
|
||||
}
|
||||
}
|
||||
}
|
||||
DCHECK_IMPLIES(all, !none);
|
||||
DCHECK_IMPLIES(none, !all);
|
||||
|
||||
if (all) return kIsInPrototypeChain;
|
||||
if (none) return kIsNotInPrototypeChain;
|
||||
return kMayBeInPrototypeChain;
|
||||
}
|
||||
|
||||
bool JSNativeContextSpecialization::ExtractReceiverMaps(
|
||||
Node* receiver, Node* effect, FeedbackNexus const& nexus,
|
||||
MapHandles* receiver_maps) {
|
||||
|
@ -145,17 +145,6 @@ class JSNativeContextSpecialization final : public AdvancedReducer {
|
||||
// code dependencies and might use the array protector cell.
|
||||
bool CanTreatHoleAsUndefined(MapHandles const& receiver_maps);
|
||||
|
||||
// Checks if we know at compile time that the {receiver} either definitely
|
||||
// has the {prototype} in it's prototype chain, or the {receiver} definitely
|
||||
// doesn't have the {prototype} in it's prototype chain.
|
||||
enum InferHasInPrototypeChainResult {
|
||||
kIsInPrototypeChain,
|
||||
kIsNotInPrototypeChain,
|
||||
kMayBeInPrototypeChain
|
||||
};
|
||||
InferHasInPrototypeChainResult InferHasInPrototypeChain(
|
||||
Node* receiver, Node* effect, Handle<JSReceiver> prototype);
|
||||
|
||||
// Extract receiver maps from {nexus} and filter based on {receiver} if
|
||||
// possible.
|
||||
bool ExtractReceiverMaps(Node* receiver, Node* effect,
|
||||
|
@ -620,6 +620,7 @@ CompareOperationHint CompareOperationHintOf(const Operator* op) {
|
||||
V(HasProperty, Operator::kNoProperties, 2, 1) \
|
||||
V(ClassOf, Operator::kPure, 1, 1) \
|
||||
V(TypeOf, Operator::kPure, 1, 1) \
|
||||
V(HasInPrototypeChain, Operator::kNoProperties, 2, 1) \
|
||||
V(InstanceOf, Operator::kNoProperties, 2, 1) \
|
||||
V(OrdinaryHasInstance, Operator::kNoProperties, 2, 1) \
|
||||
V(ForInNext, Operator::kNoProperties, 4, 1) \
|
||||
|
@ -779,6 +779,7 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
|
||||
|
||||
const Operator* ClassOf();
|
||||
const Operator* TypeOf();
|
||||
const Operator* HasInPrototypeChain();
|
||||
const Operator* InstanceOf();
|
||||
const Operator* OrdinaryHasInstance();
|
||||
|
||||
|
@ -1390,6 +1390,210 @@ Reduction JSTypedLowering::ReduceJSStoreProperty(Node* node) {
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
JSTypedLowering::InferHasInPrototypeChainResult
|
||||
JSTypedLowering::InferHasInPrototypeChain(Node* receiver, Node* effect,
|
||||
Handle<HeapObject> prototype) {
|
||||
ZoneHandleSet<Map> receiver_maps;
|
||||
NodeProperties::InferReceiverMapsResult result =
|
||||
NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
|
||||
if (result == NodeProperties::kNoReceiverMaps) return kMayBeInPrototypeChain;
|
||||
|
||||
// Check if either all or none of the {receiver_maps} have the given
|
||||
// {prototype} in their prototype chain.
|
||||
bool all = true;
|
||||
bool none = true;
|
||||
for (size_t i = 0; i < receiver_maps.size(); ++i) {
|
||||
Handle<Map> receiver_map = receiver_maps[i];
|
||||
if (receiver_map->instance_type() <= LAST_SPECIAL_RECEIVER_TYPE) {
|
||||
return kMayBeInPrototypeChain;
|
||||
}
|
||||
if (result == NodeProperties::kUnreliableReceiverMaps) {
|
||||
// In case of an unreliable {result} we need to ensure that all
|
||||
// {receiver_maps} are stable, because otherwise we cannot trust
|
||||
// the {receiver_maps} information, since arbitrary side-effects
|
||||
// may have happened.
|
||||
if (!receiver_map->is_stable()) {
|
||||
return kMayBeInPrototypeChain;
|
||||
}
|
||||
}
|
||||
for (PrototypeIterator j(receiver_map);; j.Advance()) {
|
||||
if (j.IsAtEnd()) {
|
||||
all = false;
|
||||
break;
|
||||
}
|
||||
Handle<HeapObject> const current =
|
||||
PrototypeIterator::GetCurrent<HeapObject>(j);
|
||||
if (current.is_identical_to(prototype)) {
|
||||
none = false;
|
||||
break;
|
||||
}
|
||||
if (!current->map()->is_stable() ||
|
||||
current->map()->instance_type() <= LAST_SPECIAL_RECEIVER_TYPE) {
|
||||
return kMayBeInPrototypeChain;
|
||||
}
|
||||
}
|
||||
}
|
||||
DCHECK_IMPLIES(all, !none);
|
||||
DCHECK_IMPLIES(none, !all);
|
||||
|
||||
if (all) return kIsInPrototypeChain;
|
||||
if (none) return kIsNotInPrototypeChain;
|
||||
return kMayBeInPrototypeChain;
|
||||
}
|
||||
|
||||
Reduction JSTypedLowering::ReduceJSHasInPrototypeChain(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSHasInPrototypeChain, node->opcode());
|
||||
Node* value = NodeProperties::GetValueInput(node, 0);
|
||||
Type* value_type = NodeProperties::GetType(value);
|
||||
Node* prototype = NodeProperties::GetValueInput(node, 1);
|
||||
Type* prototype_type = NodeProperties::GetType(prototype);
|
||||
Node* context = NodeProperties::GetContextInput(node);
|
||||
Node* frame_state = NodeProperties::GetFrameStateInput(node);
|
||||
Node* effect = NodeProperties::GetEffectInput(node);
|
||||
Node* control = NodeProperties::GetControlInput(node);
|
||||
|
||||
// If {value} cannot be a receiver, then it cannot have {prototype} in
|
||||
// it's prototype chain (all Primitive values have a null prototype).
|
||||
if (value_type->Is(Type::Primitive())) {
|
||||
Node* value = jsgraph()->FalseConstant();
|
||||
ReplaceWithValue(node, value, effect, control);
|
||||
return Replace(value);
|
||||
}
|
||||
|
||||
// Check if we can constant-fold the prototype chain walk
|
||||
// for the given {value} and the {prototype}.
|
||||
if (prototype_type->IsHeapConstant()) {
|
||||
InferHasInPrototypeChainResult result = InferHasInPrototypeChain(
|
||||
value, effect, prototype_type->AsHeapConstant()->Value());
|
||||
if (result != kMayBeInPrototypeChain) {
|
||||
Node* value = jsgraph()->BooleanConstant(result == kIsInPrototypeChain);
|
||||
ReplaceWithValue(node, value, effect, control);
|
||||
return Replace(value);
|
||||
}
|
||||
}
|
||||
|
||||
Node* check0 = graph()->NewNode(simplified()->ObjectIsSmi(), value);
|
||||
Node* branch0 =
|
||||
graph()->NewNode(common()->Branch(BranchHint::kFalse), check0, control);
|
||||
|
||||
Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0);
|
||||
Node* etrue0 = effect;
|
||||
Node* vtrue0 = jsgraph()->FalseConstant();
|
||||
|
||||
control = graph()->NewNode(common()->IfFalse(), branch0);
|
||||
|
||||
// Loop through the {value}s prototype chain looking for the {prototype}.
|
||||
Node* loop = control = graph()->NewNode(common()->Loop(2), control, control);
|
||||
Node* eloop = effect =
|
||||
graph()->NewNode(common()->EffectPhi(2), effect, effect, loop);
|
||||
Node* vloop = value = graph()->NewNode(
|
||||
common()->Phi(MachineRepresentation::kTagged, 2), value, value, loop);
|
||||
NodeProperties::SetType(vloop, Type::NonInternal());
|
||||
|
||||
// Load the {value} map and instance type.
|
||||
Node* value_map = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForMap()), value, effect, control);
|
||||
Node* value_instance_type = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForMapInstanceType()), value_map,
|
||||
effect, control);
|
||||
|
||||
// Check if the {value} is a special receiver, because for special
|
||||
// receivers, i.e. proxies or API values that need access checks,
|
||||
// we have to use the %HasInPrototypeChain runtime function instead.
|
||||
Node* check1 = graph()->NewNode(
|
||||
simplified()->NumberLessThanOrEqual(), value_instance_type,
|
||||
jsgraph()->Constant(LAST_SPECIAL_RECEIVER_TYPE));
|
||||
Node* branch1 =
|
||||
graph()->NewNode(common()->Branch(BranchHint::kFalse), check1, control);
|
||||
|
||||
control = graph()->NewNode(common()->IfFalse(), branch1);
|
||||
|
||||
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
|
||||
Node* etrue1 = effect;
|
||||
Node* vtrue1;
|
||||
|
||||
// Check if the {value} is not a receiver at all.
|
||||
Node* check10 =
|
||||
graph()->NewNode(simplified()->NumberLessThan(), value_instance_type,
|
||||
jsgraph()->Constant(FIRST_JS_RECEIVER_TYPE));
|
||||
Node* branch10 =
|
||||
graph()->NewNode(common()->Branch(BranchHint::kTrue), check10, if_true1);
|
||||
|
||||
// A primitive value cannot match the {prototype} we're looking for.
|
||||
if_true1 = graph()->NewNode(common()->IfTrue(), branch10);
|
||||
vtrue1 = jsgraph()->FalseConstant();
|
||||
|
||||
Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch10);
|
||||
Node* efalse1 = etrue1;
|
||||
Node* vfalse1;
|
||||
{
|
||||
// Slow path, need to call the %HasInPrototypeChain runtime function.
|
||||
vfalse1 = efalse1 = if_false1 = graph()->NewNode(
|
||||
javascript()->CallRuntime(Runtime::kHasInPrototypeChain), value,
|
||||
prototype, context, frame_state, efalse1, if_false1);
|
||||
|
||||
// Replace any potential {IfException} uses of {node} to catch
|
||||
// exceptions from this %HasInPrototypeChain runtime call instead.
|
||||
Node* on_exception = nullptr;
|
||||
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
|
||||
NodeProperties::ReplaceControlInput(on_exception, vfalse1);
|
||||
NodeProperties::ReplaceEffectInput(on_exception, efalse1);
|
||||
if_false1 = graph()->NewNode(common()->IfSuccess(), vfalse1);
|
||||
Revisit(on_exception);
|
||||
}
|
||||
}
|
||||
|
||||
// Load the {value} prototype.
|
||||
Node* value_prototype = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForMapPrototype()), value_map,
|
||||
effect, control);
|
||||
|
||||
// Check if we reached the end of {value}s prototype chain.
|
||||
Node* check2 = graph()->NewNode(simplified()->ReferenceEqual(),
|
||||
value_prototype, jsgraph()->NullConstant());
|
||||
Node* branch2 = graph()->NewNode(common()->Branch(), check2, control);
|
||||
|
||||
Node* if_true2 = graph()->NewNode(common()->IfTrue(), branch2);
|
||||
Node* etrue2 = effect;
|
||||
Node* vtrue2 = jsgraph()->FalseConstant();
|
||||
|
||||
control = graph()->NewNode(common()->IfFalse(), branch2);
|
||||
|
||||
// Check if we reached the {prototype}.
|
||||
Node* check3 = graph()->NewNode(simplified()->ReferenceEqual(),
|
||||
value_prototype, prototype);
|
||||
Node* branch3 = graph()->NewNode(common()->Branch(), check3, control);
|
||||
|
||||
Node* if_true3 = graph()->NewNode(common()->IfTrue(), branch3);
|
||||
Node* etrue3 = effect;
|
||||
Node* vtrue3 = jsgraph()->TrueConstant();
|
||||
|
||||
control = graph()->NewNode(common()->IfFalse(), branch3);
|
||||
|
||||
// Close the loop.
|
||||
vloop->ReplaceInput(1, value_prototype);
|
||||
eloop->ReplaceInput(1, effect);
|
||||
loop->ReplaceInput(1, control);
|
||||
|
||||
control = graph()->NewNode(common()->Merge(5), if_true0, if_true1, if_true2,
|
||||
if_true3, if_false1);
|
||||
effect = graph()->NewNode(common()->EffectPhi(5), etrue0, etrue1, etrue2,
|
||||
etrue3, efalse1, control);
|
||||
|
||||
// Morph the {node} into an appropriate Phi.
|
||||
ReplaceWithValue(node, node, effect, control);
|
||||
node->ReplaceInput(0, vtrue0);
|
||||
node->ReplaceInput(1, vtrue1);
|
||||
node->ReplaceInput(2, vtrue2);
|
||||
node->ReplaceInput(3, vtrue3);
|
||||
node->ReplaceInput(4, vfalse1);
|
||||
node->ReplaceInput(5, control);
|
||||
node->TrimInputCount(6);
|
||||
NodeProperties::ChangeOp(node,
|
||||
common()->Phi(MachineRepresentation::kTagged, 5));
|
||||
return Changed(node);
|
||||
}
|
||||
|
||||
Reduction JSTypedLowering::ReduceJSOrdinaryHasInstance(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSOrdinaryHasInstance, node->opcode());
|
||||
Node* constructor = NodeProperties::GetValueInput(node, 0);
|
||||
@ -2247,6 +2451,8 @@ Reduction JSTypedLowering::Reduce(Node* node) {
|
||||
case IrOpcode::kJSDivide:
|
||||
case IrOpcode::kJSModulus:
|
||||
return ReduceNumberBinop(node);
|
||||
case IrOpcode::kJSHasInPrototypeChain:
|
||||
return ReduceJSHasInPrototypeChain(node);
|
||||
case IrOpcode::kJSOrdinaryHasInstance:
|
||||
return ReduceJSOrdinaryHasInstance(node);
|
||||
case IrOpcode::kJSToBoolean:
|
||||
|
@ -52,6 +52,7 @@ class V8_EXPORT_PRIVATE JSTypedLowering final
|
||||
Reduction ReduceJSLoadNamed(Node* node);
|
||||
Reduction ReduceJSLoadProperty(Node* node);
|
||||
Reduction ReduceJSStoreProperty(Node* node);
|
||||
Reduction ReduceJSHasInPrototypeChain(Node* node);
|
||||
Reduction ReduceJSOrdinaryHasInstance(Node* node);
|
||||
Reduction ReduceJSLoadContext(Node* node);
|
||||
Reduction ReduceJSStoreContext(Node* node);
|
||||
@ -93,6 +94,17 @@ class V8_EXPORT_PRIVATE JSTypedLowering final
|
||||
// Helper for ReduceJSLoadModule and ReduceJSStoreModule.
|
||||
Node* BuildGetModuleCell(Node* node);
|
||||
|
||||
// Checks if we know at compile time that the {receiver} either definitely
|
||||
// has the {prototype} in it's prototype chain, or the {receiver} definitely
|
||||
// doesn't have the {prototype} in it's prototype chain.
|
||||
enum InferHasInPrototypeChainResult {
|
||||
kIsInPrototypeChain,
|
||||
kIsNotInPrototypeChain,
|
||||
kMayBeInPrototypeChain
|
||||
};
|
||||
InferHasInPrototypeChainResult InferHasInPrototypeChain(
|
||||
Node* receiver, Node* effect, Handle<HeapObject> prototype);
|
||||
|
||||
Factory* factory() const;
|
||||
Graph* graph() const;
|
||||
JSGraph* jsgraph() const { return jsgraph_; }
|
||||
|
@ -106,6 +106,7 @@
|
||||
JS_COMPARE_BINOP_LIST(V) \
|
||||
JS_BITWISE_BINOP_LIST(V) \
|
||||
JS_ARITH_BINOP_LIST(V) \
|
||||
V(JSHasInPrototypeChain) \
|
||||
V(JSInstanceOf) \
|
||||
V(JSOrdinaryHasInstance)
|
||||
|
||||
|
@ -62,6 +62,7 @@ bool OperatorProperties::HasFrameStateInput(const Operator* op) {
|
||||
case IrOpcode::kJSLessThan:
|
||||
case IrOpcode::kJSLessThanOrEqual:
|
||||
case IrOpcode::kJSHasProperty:
|
||||
case IrOpcode::kJSHasInPrototypeChain:
|
||||
case IrOpcode::kJSInstanceOf:
|
||||
case IrOpcode::kJSOrdinaryHasInstance:
|
||||
|
||||
|
@ -1274,6 +1274,11 @@ Type* Typer::Visitor::TypeJSHasProperty(Node* node) { return Type::Boolean(); }
|
||||
|
||||
// JS instanceof operator.
|
||||
|
||||
Type* Typer::Visitor::JSHasInPrototypeChainTyper(Type* lhs, Type* rhs,
|
||||
Typer* t) {
|
||||
return Type::Boolean();
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::JSInstanceOfTyper(Type* lhs, Type* rhs, Typer* t) {
|
||||
return Type::Boolean();
|
||||
}
|
||||
@ -1515,6 +1520,7 @@ Type* Typer::Visitor::JSCallTyper(Type* fun, Typer* t) {
|
||||
case kObjectCreate:
|
||||
return Type::OtherObject();
|
||||
case kObjectHasOwnProperty:
|
||||
case kObjectIsPrototypeOf:
|
||||
return Type::Boolean();
|
||||
case kObjectToString:
|
||||
return Type::String();
|
||||
|
@ -668,6 +668,7 @@ void Verifier::Visitor::Check(Node* node) {
|
||||
break;
|
||||
case IrOpcode::kJSDeleteProperty:
|
||||
case IrOpcode::kJSHasProperty:
|
||||
case IrOpcode::kJSHasInPrototypeChain:
|
||||
case IrOpcode::kJSInstanceOf:
|
||||
case IrOpcode::kJSOrdinaryHasInstance:
|
||||
// Type is Boolean.
|
||||
|
@ -483,6 +483,7 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) {
|
||||
case Builtins::kObjectPrototypeValueOf:
|
||||
case Builtins::kObjectValues:
|
||||
case Builtins::kObjectHasOwnProperty:
|
||||
case Builtins::kObjectPrototypeIsPrototypeOf:
|
||||
case Builtins::kObjectPrototypePropertyIsEnumerable:
|
||||
case Builtins::kObjectProtoToString:
|
||||
// Array builtins.
|
||||
|
@ -21,15 +21,6 @@ function ObjectToLocaleString() {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
|
||||
// ES6 19.1.3.3 Object.prototype.isPrototypeOf(V)
|
||||
function ObjectIsPrototypeOf(V) {
|
||||
if (!IS_RECEIVER(V)) return false;
|
||||
var O = TO_OBJECT(this);
|
||||
return %HasInPrototypeChain(V, O);
|
||||
}
|
||||
|
||||
|
||||
// ES6 7.3.9
|
||||
function GetMethod(obj, p) {
|
||||
var func = obj[p];
|
||||
@ -62,7 +53,7 @@ utils.InstallFunctions(GlobalObject.prototype, DONT_ENUM, [
|
||||
// toString is added in bootstrapper.cc
|
||||
"toLocaleString", ObjectToLocaleString,
|
||||
// valueOf is added in bootstrapper.cc.
|
||||
"isPrototypeOf", ObjectIsPrototypeOf,
|
||||
// isPrototypeOf is added in bootstrapper.cc.
|
||||
// propertyIsEnumerable is added in bootstrapper.cc.
|
||||
// __defineGetter__ is added in bootstrapper.cc.
|
||||
// __lookupGetter__ is added in bootstrapper.cc.
|
||||
|
@ -4580,6 +4580,7 @@ class ContextExtension : public Struct {
|
||||
V(Object, assign, ObjectAssign) \
|
||||
V(Object, create, ObjectCreate) \
|
||||
V(Object.prototype, hasOwnProperty, ObjectHasOwnProperty) \
|
||||
V(Object.prototype, isPrototypeOf, ObjectIsPrototypeOf) \
|
||||
V(Object.prototype, toString, ObjectToString) \
|
||||
V(RegExp.prototype, compile, RegExpCompile) \
|
||||
V(RegExp.prototype, exec, RegExpExec) \
|
||||
|
@ -1050,10 +1050,11 @@ RUNTIME_FUNCTION(Runtime_Compare) {
|
||||
RUNTIME_FUNCTION(Runtime_HasInPrototypeChain) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(2, args.length());
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, object, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Object, object, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Object, prototype, 1);
|
||||
Maybe<bool> result =
|
||||
JSReceiver::HasInPrototypeChain(isolate, object, prototype);
|
||||
if (!object->IsJSReceiver()) return isolate->heap()->false_value();
|
||||
Maybe<bool> result = JSReceiver::HasInPrototypeChain(
|
||||
isolate, Handle<JSReceiver>::cast(object), prototype);
|
||||
MAYBE_RETURN(result, isolate->heap()->exception());
|
||||
return isolate->heap()->ToBoolean(result.FromJust());
|
||||
}
|
||||
|
153
test/mjsunit/compiler/object-isprototypeof.js
Normal file
153
test/mjsunit/compiler/object-isprototypeof.js
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright 2017 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
|
||||
|
||||
// Test corner cases with null/undefined receivers.
|
||||
(function() {
|
||||
function foo(x, y) { return Object.prototype.isPrototypeOf.call(x, y); }
|
||||
|
||||
assertThrows(() => foo(null, {}));
|
||||
assertThrows(() => foo(undefined, {}));
|
||||
assertThrows(() => foo(null, []));
|
||||
assertThrows(() => foo(undefined, []));
|
||||
assertFalse(foo(null, 0));
|
||||
assertFalse(foo(undefined, 0));
|
||||
assertFalse(foo(null, ""));
|
||||
assertFalse(foo(undefined, ""));
|
||||
assertFalse(foo(null, null));
|
||||
assertFalse(foo(undefined, null));
|
||||
assertFalse(foo(null, undefined));
|
||||
assertFalse(foo(undefined, undefined));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertThrows(() => foo(null, {}));
|
||||
assertThrows(() => foo(undefined, {}));
|
||||
assertThrows(() => foo(null, []));
|
||||
assertThrows(() => foo(undefined, []));
|
||||
assertFalse(foo(null, 0));
|
||||
assertFalse(foo(undefined, 0));
|
||||
assertFalse(foo(null, ""));
|
||||
assertFalse(foo(undefined, ""));
|
||||
assertFalse(foo(null, null));
|
||||
assertFalse(foo(undefined, null));
|
||||
assertFalse(foo(null, undefined));
|
||||
assertFalse(foo(undefined, undefined));
|
||||
})();
|
||||
|
||||
// Test general constructor prototype case.
|
||||
(function() {
|
||||
function A() {}
|
||||
A.prototype = {};
|
||||
var a = new A;
|
||||
|
||||
function foo(x) { return A.prototype.isPrototypeOf(x); }
|
||||
|
||||
assertFalse(foo(0));
|
||||
assertFalse(foo(""));
|
||||
assertFalse(foo(null));
|
||||
assertFalse(foo(undefined));
|
||||
assertFalse(foo({}));
|
||||
assertFalse(foo([]));
|
||||
assertTrue(foo(a));
|
||||
assertTrue(foo(new A));
|
||||
assertTrue(foo({__proto__: a}));
|
||||
assertTrue(foo({__proto__: A.prototype}));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertFalse(foo(0));
|
||||
assertFalse(foo(""));
|
||||
assertFalse(foo(null));
|
||||
assertFalse(foo(undefined));
|
||||
assertFalse(foo({}));
|
||||
assertFalse(foo([]));
|
||||
assertTrue(foo(a));
|
||||
assertTrue(foo(new A));
|
||||
assertTrue(foo({__proto__: a}));
|
||||
assertTrue(foo({__proto__: A.prototype}));
|
||||
})();
|
||||
|
||||
// Test known primitive values.
|
||||
(function() {
|
||||
function A() {}
|
||||
A.prototype = {};
|
||||
var a = new A;
|
||||
|
||||
function foo() { return A.prototype.isPrototypeOf(0); }
|
||||
|
||||
assertFalse(foo());
|
||||
assertFalse(foo());
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertFalse(foo());
|
||||
})();
|
||||
(function() {
|
||||
function A() {}
|
||||
A.prototype = {};
|
||||
var a = new A;
|
||||
|
||||
function foo() { return A.prototype.isPrototypeOf(null); }
|
||||
|
||||
assertFalse(foo());
|
||||
assertFalse(foo());
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertFalse(foo());
|
||||
})();
|
||||
(function() {
|
||||
function A() {}
|
||||
A.prototype = {};
|
||||
var a = new A;
|
||||
|
||||
function foo() { return A.prototype.isPrototypeOf(undefined); }
|
||||
|
||||
assertFalse(foo());
|
||||
assertFalse(foo());
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertFalse(foo());
|
||||
})();
|
||||
|
||||
// Test constant-folded prototype chain checks.
|
||||
(function() {
|
||||
function A() {}
|
||||
A.prototype = {};
|
||||
var a = new A;
|
||||
|
||||
function foo() { return A.prototype.isPrototypeOf(a); }
|
||||
|
||||
assertTrue(foo());
|
||||
assertTrue(foo());
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(foo());
|
||||
})();
|
||||
(function() {
|
||||
function A() {}
|
||||
var a = new A;
|
||||
A.prototype = {};
|
||||
|
||||
function foo() { return A.prototype.isPrototypeOf(a); }
|
||||
|
||||
assertFalse(foo());
|
||||
assertFalse(foo());
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertFalse(foo());
|
||||
})();
|
||||
|
||||
// Test Array prototype chain checks.
|
||||
(function() {
|
||||
var a = [];
|
||||
|
||||
function foo() { return Array.prototype.isPrototypeOf(a); }
|
||||
|
||||
assertTrue(foo());
|
||||
assertTrue(foo());
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(foo());
|
||||
})();
|
||||
(function() {
|
||||
var a = [];
|
||||
|
||||
function foo() { return Object.prototype.isPrototypeOf(a); }
|
||||
|
||||
assertTrue(foo());
|
||||
assertTrue(foo());
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(foo());
|
||||
})();
|
Loading…
Reference in New Issue
Block a user