[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:
parent
025ea28bca
commit
226e63fc13
@ -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:
|
||||
|
@ -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);
|
||||
|
32
test/mjsunit/compiler/object-constructor.js
Normal file
32
test/mjsunit/compiler/object-constructor.js
Normal 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());
|
||||
})();
|
Loading…
Reference in New Issue
Block a user