Commit 301bc628 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Handle JSBoundFunction targets for JSConstruct.

Properly handle known JSBoundFunction instances as targets to
JSConstruct by inlining the construction of the eventual target.
Also if the target is the result of a JSCreateBoundFunction call,
where we can also fold the construction and construct the bound
target function directly instead.

This addresses half of the TODO in the JSConstruct lowering in the
JSCallReducer where so far we didn't handle bound functions.

Bug: v8:5267, v8:7109
Change-Id: I022dc7d4fbbe2c9972472e78a6d64f51e3134c94
Reviewed-on: https://chromium-review.googlesource.com/792947Reviewed-by: 's avatarMichael Stanton <mvstanton@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#49664}
parent 10f4e6c3
......@@ -2139,7 +2139,7 @@ Reduction JSCallReducer::ReduceJSConstruct(Node* node) {
DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode());
ConstructParameters const& p = ConstructParametersOf(node->op());
DCHECK_LE(2u, p.arity());
int const arity = static_cast<int>(p.arity() - 2);
int arity = static_cast<int>(p.arity() - 2);
Node* target = NodeProperties::GetValueInput(node, 0);
Node* new_target = NodeProperties::GetValueInput(node, arity + 1);
Node* effect = NodeProperties::GetEffectInput(node);
......@@ -2217,18 +2217,18 @@ Reduction JSCallReducer::ReduceJSConstruct(Node* node) {
// Try to specialize JSConstruct {node}s with constant {target}s.
HeapObjectMatcher m(target);
if (m.HasValue()) {
// Raise a TypeError if the {target} is not a constructor.
if (!m.Value()->IsConstructor()) {
NodeProperties::ReplaceValueInputs(node, target);
NodeProperties::ChangeOp(node,
javascript()->CallRuntime(
Runtime::kThrowConstructedNonConstructable));
return Changed(node);
}
if (m.Value()->IsJSFunction()) {
Handle<JSFunction> function = Handle<JSFunction>::cast(m.Value());
// Raise a TypeError if the {target} is not a constructor.
if (!function->IsConstructor()) {
NodeProperties::ReplaceValueInputs(node, target);
NodeProperties::ChangeOp(
node, javascript()->CallRuntime(
Runtime::kThrowConstructedNonConstructable));
return Changed(node);
}
// Don't inline cross native context.
if (function->native_context() != *native_context()) return NoChange();
......@@ -2266,9 +2266,86 @@ Reduction JSCallReducer::ReduceJSConstruct(Node* node) {
return Changed(node);
}
}
} else if (m.Value()->IsJSBoundFunction()) {
Handle<JSBoundFunction> function =
Handle<JSBoundFunction>::cast(m.Value());
Handle<JSReceiver> bound_target_function(
function->bound_target_function(), isolate());
Handle<FixedArray> bound_arguments(function->bound_arguments(),
isolate());
// Patch {node} to use [[BoundTargetFunction]].
NodeProperties::ReplaceValueInput(
node, jsgraph()->Constant(bound_target_function), 0);
// Patch {node} to use [[BoundTargetFunction]]
// as new.target if {new_target} equals {target}.
NodeProperties::ReplaceValueInput(
node,
graph()->NewNode(common()->Select(MachineRepresentation::kTagged),
graph()->NewNode(simplified()->ReferenceEqual(),
target, new_target),
jsgraph()->Constant(bound_target_function),
new_target),
arity + 1);
// Insert the [[BoundArguments]] for {node}.
for (int i = 0; i < bound_arguments->length(); ++i) {
node->InsertInput(
graph()->zone(), i + 1,
jsgraph()->Constant(handle(bound_arguments->get(i), isolate())));
arity++;
}
// Update the JSConstruct operator on {node}.
NodeProperties::ChangeOp(
node,
javascript()->Construct(arity + 2, p.frequency(), VectorSlotPair()));
// Try to further reduce the JSConstruct {node}.
Reduction const reduction = ReduceJSConstruct(node);
return reduction.Changed() ? reduction : Changed(node);
}
// TODO(bmeurer): Also support optimizing proxies here.
}
// If {target} is the result of a JSCreateBoundFunction operation,
// we can just fold the construction and construct the bound target
// function directly instead.
if (target->opcode() == IrOpcode::kJSCreateBoundFunction) {
Node* bound_target_function = NodeProperties::GetValueInput(target, 0);
int const bound_arguments_length =
static_cast<int>(CreateBoundFunctionParametersOf(target->op()).arity());
// Patch the {node} to use [[BoundTargetFunction]].
NodeProperties::ReplaceValueInput(node, bound_target_function, 0);
// Patch {node} to use [[BoundTargetFunction]]
// as new.target if {new_target} equals {target}.
NodeProperties::ReplaceValueInput(
node,
graph()->NewNode(common()->Select(MachineRepresentation::kTagged),
graph()->NewNode(simplified()->ReferenceEqual(),
target, new_target),
bound_target_function, new_target),
arity + 1);
// Insert the [[BoundArguments]] for {node}.
for (int i = 0; i < bound_arguments_length; ++i) {
Node* value = NodeProperties::GetValueInput(target, 2 + i);
node->InsertInput(graph()->zone(), 1 + i, value);
arity++;
}
// TODO(bmeurer): Also support optimizing bound functions and proxies here.
// Update the JSConstruct operator on {node}.
NodeProperties::ChangeOp(
node,
javascript()->Construct(arity + 2, p.frequency(), VectorSlotPair()));
// Try to further reduce the JSConstruct {node}.
Reduction const reduction = ReduceJSConstruct(node);
return reduction.Changed() ? reduction : Changed(node);
}
return NoChange();
......
......@@ -111,3 +111,74 @@
%OptimizeFunctionOnNextCall(foo);
assertEquals(2, foo(inc));
})();
(function() {
const A = class A {};
const B = A.bind();
function foo() { return new B; }
assertInstanceof(foo(), A);
assertInstanceof(foo(), B);
%OptimizeFunctionOnNextCall(foo);
assertInstanceof(foo(), A);
assertInstanceof(foo(), B);
})();
(function() {
const A = class A {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
};
const B = A.bind(null, 1, 2);
function foo(z) { return new B(z); }
assertEquals(1, foo(3).x);
assertEquals(2, foo(3).y);
assertEquals(3, foo(3).z);
%OptimizeFunctionOnNextCall(foo);
assertEquals(1, foo(3).x);
assertEquals(2, foo(3).y);
assertEquals(3, foo(3).z);
})();
(function() {
const A = class A {};
function foo() {
const B = A.bind();
return new B;
}
assertInstanceof(foo(), A);
assertInstanceof(foo(), A);
%OptimizeFunctionOnNextCall(foo);
assertInstanceof(foo(), A);
})();
(function() {
const A = class A {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
};
function foo(z) {
const B = A.bind(null, 1, 2);
return new B(z);
}
assertEquals(1, foo(3).x);
assertEquals(2, foo(3).y);
assertEquals(3, foo(3).z);
%OptimizeFunctionOnNextCall(foo);
assertEquals(1, foo(3).x);
assertEquals(2, foo(3).y);
assertEquals(3, foo(3).z);
})();
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