[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:
parent
493e5b0aaa
commit
35614b7215
@ -689,6 +689,80 @@ Reduction JSCallReducer::ReduceReflectGetPrototypeOf(Node* node) {
|
|||||||
return ReduceObjectGetPrototype(node, target);
|
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
|
// ES section #sec-reflect.has
|
||||||
Reduction JSCallReducer::ReduceReflectHas(Node* node) {
|
Reduction JSCallReducer::ReduceReflectHas(Node* node) {
|
||||||
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
|
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
|
||||||
@ -1595,6 +1669,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
|
|||||||
return ReduceReflectApply(node);
|
return ReduceReflectApply(node);
|
||||||
case Builtins::kReflectConstruct:
|
case Builtins::kReflectConstruct:
|
||||||
return ReduceReflectConstruct(node);
|
return ReduceReflectConstruct(node);
|
||||||
|
case Builtins::kReflectGet:
|
||||||
|
return ReduceReflectGet(node);
|
||||||
case Builtins::kReflectGetPrototypeOf:
|
case Builtins::kReflectGetPrototypeOf:
|
||||||
return ReduceReflectGetPrototypeOf(node);
|
return ReduceReflectGetPrototypeOf(node);
|
||||||
case Builtins::kReflectHas:
|
case Builtins::kReflectHas:
|
||||||
|
@ -69,6 +69,7 @@ class JSCallReducer final : public AdvancedReducer {
|
|||||||
Reduction ReduceObjectPrototypeIsPrototypeOf(Node* node);
|
Reduction ReduceObjectPrototypeIsPrototypeOf(Node* node);
|
||||||
Reduction ReduceReflectApply(Node* node);
|
Reduction ReduceReflectApply(Node* node);
|
||||||
Reduction ReduceReflectConstruct(Node* node);
|
Reduction ReduceReflectConstruct(Node* node);
|
||||||
|
Reduction ReduceReflectGet(Node* node);
|
||||||
Reduction ReduceReflectGetPrototypeOf(Node* node);
|
Reduction ReduceReflectGetPrototypeOf(Node* node);
|
||||||
Reduction ReduceReflectHas(Node* node);
|
Reduction ReduceReflectHas(Node* node);
|
||||||
Reduction ReduceArrayForEach(Handle<JSFunction> function, Node* node);
|
Reduction ReduceArrayForEach(Handle<JSFunction> function, Node* node);
|
||||||
|
68
test/mjsunit/compiler/reflect-get.js
Normal file
68
test/mjsunit/compiler/reflect-get.js
Normal 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(); }}));
|
||||||
|
})();
|
Loading…
Reference in New Issue
Block a user