[turbofan] Optimize Reflect.get(target, key) calls.

When TurboFan sees a call to Reflect.get with exactly two parameters,
we can lower that to a direct call to the GetPropertyStub, which is
certainly faster than the general C++ builtin. This gives a nice
7-8% improvement on the chai test in the web-tooling-benchmark.

The micro-benchmark on the issue goes from

  reflectGetPresent: 461 ms.
  reflectGetAbsent: 470 ms.

to 

  reflectGetPresent: 141 ms.
  reflectGetAbsent: 245 ms.

which is an up to 3.2x improvement.

Bug: v8:5996, v8:6936, v8:6937
Change-Id: Ic439fccb13f1a2f84386bf9fc31b4283d101afc4
Reviewed-on: https://chromium-review.googlesource.com/732988
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Michael Stanton <mvstanton@chromium.org>
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48841}
This commit is contained in:
Benedikt Meurer 2017-10-23 14:34:50 +02:00 committed by Commit Bot
parent 493e5b0aaa
commit 35614b7215
3 changed files with 145 additions and 0 deletions

View File

@ -689,6 +689,80 @@ Reduction JSCallReducer::ReduceReflectGetPrototypeOf(Node* node) {
return ReduceObjectGetPrototype(node, target);
}
// ES section #sec-reflect.get
Reduction JSCallReducer::ReduceReflectGet(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
int arity = static_cast<int>(p.arity() - 2);
if (arity != 2) return NoChange();
Node* target = NodeProperties::GetValueInput(node, 2);
Node* key = NodeProperties::GetValueInput(node, 3);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Check whether {target} is a JSReceiver.
Node* check = graph()->NewNode(simplified()->ObjectIsReceiver(), target);
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
// Throw an appropriate TypeError if the {target} is not a JSReceiver.
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
{
if_false = efalse = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowTypeError, 2),
jsgraph()->Constant(MessageTemplate::kCalledOnNonObject),
jsgraph()->HeapConstant(
factory()->NewStringFromAsciiChecked("Reflect.get")),
context, frame_state, efalse, if_false);
}
// Otherwise just use the existing GetPropertyStub.
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* vtrue;
{
Callable callable = CodeFactory::GetProperty(isolate());
CallDescriptor const* const desc = Linkage::GetStubCallDescriptor(
isolate(), graph()->zone(), callable.descriptor(), 0,
CallDescriptor::kNeedsFrameState, Operator::kNoProperties,
MachineType::AnyTagged(), 1);
Node* stub_code = jsgraph()->HeapConstant(callable.code());
vtrue = etrue = if_true =
graph()->NewNode(common()->Call(desc), stub_code, target, key, context,
frame_state, etrue, if_true);
}
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
// Create appropriate {IfException} and {IfSuccess} nodes.
Node* extrue = graph()->NewNode(common()->IfException(), etrue, if_true);
if_true = graph()->NewNode(common()->IfSuccess(), if_true);
Node* exfalse = graph()->NewNode(common()->IfException(), efalse, if_false);
if_false = graph()->NewNode(common()->IfSuccess(), if_false);
// Join the exception edges.
Node* merge = graph()->NewNode(common()->Merge(2), extrue, exfalse);
Node* ephi =
graph()->NewNode(common()->EffectPhi(2), extrue, exfalse, merge);
Node* phi =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
extrue, exfalse, merge);
ReplaceWithValue(on_exception, phi, ephi, merge);
}
// Connect the throwing path to end.
if_false = graph()->NewNode(common()->Throw(), efalse, if_false);
NodeProperties::MergeControlToEnd(graph(), common(), if_false);
// Continue on the regular path.
ReplaceWithValue(node, vtrue, etrue, if_true);
return Changed(vtrue);
}
// ES section #sec-reflect.has
Reduction JSCallReducer::ReduceReflectHas(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
@ -1595,6 +1669,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReduceReflectApply(node);
case Builtins::kReflectConstruct:
return ReduceReflectConstruct(node);
case Builtins::kReflectGet:
return ReduceReflectGet(node);
case Builtins::kReflectGetPrototypeOf:
return ReduceReflectGetPrototypeOf(node);
case Builtins::kReflectHas:

View File

@ -69,6 +69,7 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceObjectPrototypeIsPrototypeOf(Node* node);
Reduction ReduceReflectApply(Node* node);
Reduction ReduceReflectConstruct(Node* node);
Reduction ReduceReflectGet(Node* node);
Reduction ReduceReflectGetPrototypeOf(Node* node);
Reduction ReduceReflectHas(Node* node);
Reduction ReduceArrayForEach(Handle<JSFunction> function, Node* node);

View File

@ -0,0 +1,68 @@
// 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 Reflect.get with wrong (number of) arguments.
(function() {
"use strict";
function foo() { return Reflect.get(); }
assertThrows(foo);
assertThrows(foo);
%OptimizeFunctionOnNextCall(foo);
assertThrows(foo);
})();
(function() {
"use strict";
function foo(o) { return Reflect.get(o); }
assertEquals(undefined, foo({}));
assertEquals(undefined, foo({}));
%OptimizeFunctionOnNextCall(foo);
assertEquals(undefined, foo({}));
})();
(function() {
"use strict";
function foo(o) { return Reflect.get(o); }
assertThrows(foo.bind(undefined, 1));
assertThrows(foo.bind(undefined, undefined));
%OptimizeFunctionOnNextCall(foo);
assertThrows(foo.bind(undefined, 'o'));
})();
// Test Reflect.get within try/catch.
(function() {
const o = {x: 10};
"use strict";
function foo() {
try {
return Reflect.get(o, "x");
} catch (e) {
return 1;
}
}
assertEquals(10, foo());
assertEquals(10, foo());
%OptimizeFunctionOnNextCall(foo);
assertEquals(10, foo());
})();
(function() {
"use strict";
const o = {};
function foo(n) {
try {
return Reflect.get(o, n);
} catch (e) {
return 1;
}
}
assertEquals(1, foo({[Symbol.toPrimitive]() { throw new Error(); }}));
assertEquals(1, foo({[Symbol.toPrimitive]() { throw new Error(); }}));
%OptimizeFunctionOnNextCall(foo);
assertEquals(1, foo({[Symbol.toPrimitive]() { throw new Error(); }}));
})();