[turbofan] Fold Object constructor calls with certain values.

When calling

  Object(value)

where the value is known to be a JSReceiver, we can just replace it with
value, as the Object constructor call is a no-op in that case. Otherwise
when value is known to be not null or undefined then we can replace the
Object constructor call with an invocation of ToObject.

This covers the common pattern found in bundles generated by Webpack,
where the Object constructor is used to call imported functions, i.e.

  Object(module.foo)(1, 2, 3)

There's a lot of detail in https://github.com/webpack/webpack/issues/5600
on this matter and why this pattern was chosen.

Bug: v8:6772
Change-Id: I2b4f0b4542b68b97b337ce571d6d79946c73d8bb
Reviewed-on: https://chromium-review.googlesource.com/643868
Reviewed-by: Yang Guo <yangguo@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47728}
This commit is contained in:
Benedikt Meurer 2017-08-30 21:11:52 +02:00 committed by Commit Bot
parent 025ea28bca
commit 226e63fc13
3 changed files with 114 additions and 32 deletions

View File

@ -20,6 +20,62 @@ namespace v8 {
namespace internal {
namespace compiler {
namespace {
bool CanBePrimitive(Node* node) {
switch (node->opcode()) {
case IrOpcode::kJSCreate:
case IrOpcode::kJSCreateArguments:
case IrOpcode::kJSCreateArray:
case IrOpcode::kJSCreateClosure:
case IrOpcode::kJSCreateEmptyLiteralArray:
case IrOpcode::kJSCreateEmptyLiteralObject:
case IrOpcode::kJSCreateIterResultObject:
case IrOpcode::kJSCreateKeyValueArray:
case IrOpcode::kJSCreateLiteralArray:
case IrOpcode::kJSCreateLiteralObject:
case IrOpcode::kJSCreateLiteralRegExp:
case IrOpcode::kJSConstructForwardVarargs:
case IrOpcode::kJSConstruct:
case IrOpcode::kJSConstructWithArrayLike:
case IrOpcode::kJSConstructWithSpread:
case IrOpcode::kJSConvertReceiver:
case IrOpcode::kJSGetSuperConstructor:
case IrOpcode::kJSToObject:
return false;
case IrOpcode::kHeapConstant: {
Handle<HeapObject> value = HeapObjectMatcher(node).Value();
return value->IsPrimitive();
}
default:
return true;
}
}
bool CanBeNullOrUndefined(Node* node) {
if (CanBePrimitive(node)) {
switch (node->opcode()) {
case IrOpcode::kJSToBoolean:
case IrOpcode::kJSToInteger:
case IrOpcode::kJSToLength:
case IrOpcode::kJSToName:
case IrOpcode::kJSToNumber:
case IrOpcode::kJSToString:
return false;
case IrOpcode::kHeapConstant: {
Handle<HeapObject> value = HeapObjectMatcher(node).Value();
Isolate* const isolate = value->GetIsolate();
return value->IsNullOrUndefined(isolate);
}
default:
return true;
}
}
return false;
}
} // namespace
Reduction JSCallReducer::Reduce(Node* node) {
switch (node->opcode()) {
case IrOpcode::kJSConstruct:
@ -104,43 +160,30 @@ Reduction JSCallReducer::ReduceNumberConstructor(Node* node) {
return Changed(node);
}
namespace {
// ES section #sec-object-constructor
Reduction JSCallReducer::ReduceObjectConstructor(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
if (p.arity() < 3) return NoChange();
Node* value = (p.arity() >= 3) ? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
bool CanBeNullOrUndefined(Node* node) {
switch (node->opcode()) {
case IrOpcode::kJSCreate:
case IrOpcode::kJSCreateArguments:
case IrOpcode::kJSCreateArray:
case IrOpcode::kJSCreateClosure:
case IrOpcode::kJSCreateIterResultObject:
case IrOpcode::kJSCreateKeyValueArray:
case IrOpcode::kJSCreateLiteralArray:
case IrOpcode::kJSCreateLiteralObject:
case IrOpcode::kJSCreateLiteralRegExp:
case IrOpcode::kJSConstruct:
case IrOpcode::kJSConstructForwardVarargs:
case IrOpcode::kJSConstructWithSpread:
case IrOpcode::kJSConvertReceiver:
case IrOpcode::kJSToBoolean:
case IrOpcode::kJSToInteger:
case IrOpcode::kJSToLength:
case IrOpcode::kJSToName:
case IrOpcode::kJSToNumber:
case IrOpcode::kJSToObject:
case IrOpcode::kJSToString:
return false;
case IrOpcode::kHeapConstant: {
Handle<HeapObject> value = HeapObjectMatcher(node).Value();
Isolate* const isolate = value->GetIsolate();
return value->IsNull(isolate) || value->IsUndefined(isolate);
// We can fold away the Object(x) call if |x| is definitely not a primitive.
if (CanBePrimitive(value)) {
if (!CanBeNullOrUndefined(value)) {
// Turn the {node} into a {JSToObject} call if we know that
// the {value} cannot be null or undefined.
NodeProperties::ReplaceValueInputs(node, value);
NodeProperties::ChangeOp(node, javascript()->ToObject());
return Changed(node);
}
default:
return true;
} else {
ReplaceWithValue(node, value);
return Replace(node);
}
return NoChange();
}
} // namespace
// ES6 section 19.2.3.1 Function.prototype.apply ( thisArg, argArray )
Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
@ -1312,6 +1355,12 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
// Don't inline cross native context.
if (function->native_context() != *native_context()) return NoChange();
// TODO(turbofan): Merge this into the switch below once the
// Object constructor is a proper TFJ builtin.
if (*function == native_context()->object_function()) {
return ReduceObjectConstructor(node);
}
// Check for known builtin functions.
switch (shared->code()->builtin_index()) {
case Builtins::kArrayConstructor:

View File

@ -60,6 +60,7 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceFunctionPrototypeApply(Node* node);
Reduction ReduceFunctionPrototypeCall(Node* node);
Reduction ReduceFunctionPrototypeHasInstance(Node* node);
Reduction ReduceObjectConstructor(Node* node);
Reduction ReduceObjectGetPrototype(Node* node, Node* object);
Reduction ReduceObjectGetPrototypeOf(Node* node);
Reduction ReduceObjectPrototypeGetProto(Node* node);

View File

@ -0,0 +1,32 @@
// 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
// Common pattern in Webpack 3 generated bundles, see
// https://github.com/webpack/webpack/issues/5600 for details.
(function ObjectConstructorWithKnownFunction() {
"use strict";
class A {
bar() { return this; }
};
function foo(a) {
return Object(a.bar)();
}
assertEquals(undefined, foo(new A));
assertEquals(undefined, foo(new A));
%OptimizeFunctionOnNextCall(foo);
assertEquals(undefined, foo(new A));
})();
(function ObjectConstructorWithString() {
"use strict";
function foo() {
return Object("a");
}
assertEquals('object', typeof foo());
assertEquals('object', typeof foo());
%OptimizeFunctionOnNextCall(foo);
assertEquals('object', typeof foo());
})();