[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);
|
||||
}
|
||||
|
||||
// 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:
|
||||
|
@ -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);
|
||||
|
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