Commit b11c557d authored by bmeurer's avatar bmeurer Committed by Commit Bot

[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}
parent 8196e102
......@@ -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());
// 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(handle(initial_map->prototype(), isolate()));
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());
}
......
// 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());
})();
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