Commit c9c7dd0d authored by bmeurer's avatar bmeurer Committed by Commit bot

[turbofan] Constant-fold certain JSOrdinaryHasInstance nodes.

Move JSOrdinaryHasInstance lowering to JSNativeContextSpecialization,
which was previously mostly done in JSTypedLowering (for no reason).
Add new logic to the lowering to constant-fold OrdinaryHasInstance
checks when the map of the left-hand side and the "prototype" of the
right-hand side is known. This address the performance issue with the
(base) class constructors generated by Babel, i.e.:

  function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }

  var C = function C() { _classCallCheck(this, C); };

for

  class C {}

Also ensure that a known constructor being used inside an instanceof
get's a proper initial map on-demand.

BUG=v8:6275
R=mstarzinger@chromium.org

Review-Url: https://codereview.chromium.org/2827013002
Cr-Commit-Position: refs/heads/master@{#44727}
parent 34a26e7d
......@@ -146,6 +146,17 @@ class JSNativeContextSpecialization final : public AdvancedReducer {
// code dependencies and might use the array protector cell.
bool CanTreatHoleAsUndefined(std::vector<Handle<Map>> 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,
......
......@@ -1326,16 +1326,12 @@ Reduction JSTypedLowering::ReduceJSOrdinaryHasInstance(Node* node) {
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);
ReplaceWithValue(node, value);
return Replace(value);
}
......@@ -1345,156 +1341,11 @@ Reduction JSTypedLowering::ReduceJSOrdinaryHasInstance(Node* node) {
if (!object_type->Maybe(Type::Receiver()) &&
!constructor_type->Maybe(Type::BoundFunction())) {
Node* value = jsgraph()->FalseConstant();
ReplaceWithValue(node, value, effect, control);
ReplaceWithValue(node, value);
return Replace(value);
}
// Check if the {constructor} is a (known) JSFunction.
if (!constructor_type->IsHeapConstant() ||
!constructor_type->AsHeapConstant()->Value()->IsJSFunction()) {
return NoChange();
}
Handle<JSFunction> function =
Handle<JSFunction>::cast(constructor_type->AsHeapConstant()->Value());
// Check if the {function} already has an initial map (i.e. the
// {function} has been used as a constructor at least once).
if (!function->has_initial_map()) return NoChange();
// Check if the {function}s "prototype" is a JSReceiver.
if (!function->prototype()->IsJSReceiver()) return NoChange();
// Install a code dependency on the {function}s initial map.
Handle<Map> initial_map(function->initial_map(), isolate());
dependencies()->AssumeInitialMapCantChange(initial_map);
Node* prototype =
jsgraph()->Constant(handle(initial_map->prototype(), isolate()));
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);
// TODO(jarin): This is a very ugly hack to work-around the super-smart
// implicit typing of the Phi, which goes completely nuts if the {object}
// is for example a HeapConstant.
NodeProperties::SetType(vloop, Type::NonInternal());
// 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));
return Changed(node);
return NoChange();
}
Reduction JSTypedLowering::ReduceJSLoadContext(Node* node) {
......
......@@ -131,3 +131,15 @@ F.__proto__ = null;
assertFalse(foo(new A()));
assertTrue(foo(new F()));
})();
(function() {
function foo() {
var a = new A();
return a instanceof 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