[turbofan] Inline promise constructor in turbofan.

Inline the promise constructor when we have one argument and target
matches new_target.

This is not complete, and is sitting behind an experimental flag for
now. We need to fix deoptimization by providing proper frame states.

Create a unittest class for JSCallReducer - just assert whether there
was a change or not, rather than specify the exact graph that should be
produced.

Bug: v8:7253
Change-Id: Ib6886a8feb2799f47cd647853cabcf12a189bc25
Reviewed-on: https://chromium-review.googlesource.com/919282
Commit-Queue: Peter Marshall <petermarshall@chromium.org>
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51389}
This commit is contained in:
Peter Marshall 2018-02-19 14:31:18 +01:00 committed by Commit Bot
parent 7ecb6a38b9
commit 46c199a5c7
8 changed files with 369 additions and 2 deletions

View File

@ -2383,12 +2383,14 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Handle<SharedFunctionInfo> info = SimpleCreateSharedFunctionInfo(
isolate, Builtins::kPromiseCapabilityDefaultResolve,
factory->empty_string(), 1);
info->set_native(true);
native_context()->set_promise_capability_default_resolve_shared_fun(
*info);
info = SimpleCreateSharedFunctionInfo(
isolate, Builtins::kPromiseCapabilityDefaultReject,
factory->empty_string(), 1);
info->set_native(true);
native_context()->set_promise_capability_default_reject_shared_fun(*info);
}

View File

@ -3268,6 +3268,11 @@ Reduction JSCallReducer::ReduceJSConstruct(Node* node) {
return Changed(node);
}
}
// Check for the PromiseConstructor
if (*function == function->native_context()->promise_function()) {
return ReducePromiseConstructor(node);
}
} else if (m.Value()->IsJSBoundFunction()) {
Handle<JSBoundFunction> function =
Handle<JSBoundFunction>::cast(m.Value());
@ -4075,6 +4080,124 @@ Reduction JSCallReducer::ReducePromiseCapabilityDefaultResolve(Node* node) {
return Replace(value);
}
Reduction JSCallReducer::ReducePromiseConstructor(Node* node) {
DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode());
ConstructParameters const& p = ConstructParametersOf(node->op());
int arity = static_cast<int>(p.arity() - 2);
// We only inline when we have the executor.
if (arity < 1) return NoChange();
Node* target = NodeProperties::GetValueInput(node, 0);
Node* executor = NodeProperties::GetValueInput(node, 1);
Node* new_target = NodeProperties::GetValueInput(node, arity + 1);
Node* context = NodeProperties::GetContextInput(node);
Node* outer_frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (!FLAG_experimental_inline_promise_constructor) return NoChange();
// Only handle builtins Promises, not subclasses.
if (target != new_target) return NoChange();
// Add a code dependency on the promise hook protector.
dependencies()->AssumePropertyCell(factory()->promise_hook_protector());
// Check if executor is callable
Node* check_fail = nullptr;
Node* check_throw = nullptr;
// TODO(petermarshall): The frame state is wrong here.
WireInCallbackIsCallableCheck(executor, context, outer_frame_state, effect,
&control, &check_fail, &check_throw);
// Create the resulting JSPromise.
Node* promise = effect =
graph()->NewNode(javascript()->CreatePromise(), context, effect);
// 8. CreatePromiseResolvingFunctions
// Allocate a promise context for the closures below.
Node* promise_context = effect = graph()->NewNode(
javascript()->CreateFunctionContext(
PromiseBuiltinsAssembler::kPromiseContextLength, FUNCTION_SCOPE),
context, context, effect, control);
effect =
graph()->NewNode(simplified()->StoreField(AccessBuilder::ForContextSlot(
PromiseBuiltinsAssembler::kPromiseSlot)),
promise_context, promise, effect, control);
effect = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForContextSlot(
PromiseBuiltinsAssembler::kDebugEventSlot)),
promise_context, jsgraph()->TrueConstant(), effect, control);
// Allocate the closure for the resolve case.
Handle<SharedFunctionInfo> resolve_shared(
native_context()->promise_capability_default_resolve_shared_fun(),
isolate());
Node* resolve = effect =
graph()->NewNode(javascript()->CreateClosure(resolve_shared),
promise_context, effect, control);
// Allocate the closure for the reject case.
Handle<SharedFunctionInfo> reject_shared(
native_context()->promise_capability_default_reject_shared_fun(),
isolate());
Node* reject = effect =
graph()->NewNode(javascript()->CreateClosure(reject_shared),
promise_context, effect, control);
// 9. Call executor with both resolving functions
effect = control = graph()->NewNode(
javascript()->Call(4, p.frequency(), VectorSlotPair(),
ConvertReceiverMode::kNullOrUndefined,
SpeculationMode::kDisallowSpeculation),
executor, jsgraph()->UndefinedConstant(), resolve, reject, context,
outer_frame_state, effect, control);
Node* exception_effect = effect;
Node* exception_control = control;
{
Node* reason = exception_effect = exception_control = graph()->NewNode(
common()->IfException(), exception_control, exception_effect);
// 10a. Call reject if the call to executor threw.
exception_effect = exception_control = graph()->NewNode(
javascript()->Call(3, p.frequency(), VectorSlotPair(),
ConvertReceiverMode::kNullOrUndefined,
SpeculationMode::kDisallowSpeculation),
reject, jsgraph()->UndefinedConstant(), reason, context,
outer_frame_state, exception_effect, exception_control);
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
RewirePostCallbackExceptionEdges(check_throw, on_exception,
exception_effect, &check_fail,
&exception_control);
}
}
Node* success_effect = effect;
Node* success_control = control;
{
success_control = graph()->NewNode(common()->IfSuccess(), success_control);
}
control =
graph()->NewNode(common()->Merge(2), success_control, exception_control);
effect = graph()->NewNode(common()->EffectPhi(2), success_effect,
exception_effect, control);
// Wire up the branch for the case when IsCallable fails for the executor.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
// completion to the graph end.
Node* throw_node =
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), throw_node);
ReplaceWithValue(node, promise, effect, control);
return Replace(promise);
}
// V8 Extras: v8.createPromise(parent)
Reduction JSCallReducer::ReducePromiseInternalConstructor(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());

View File

@ -28,7 +28,7 @@ class SimplifiedOperatorBuilder;
// Performs strength reduction on {JSConstruct} and {JSCall} nodes,
// which might allow inlining or other optimizations to be performed afterwards.
class JSCallReducer final : public AdvancedReducer {
class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
public:
// Flags that control the mode of operation.
enum Flag { kNoFlags = 0u, kBailoutOnUninitialized = 1u << 0 };
@ -103,6 +103,7 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceAsyncFunctionPromiseRelease(Node* node);
Reduction ReducePromiseCapabilityDefaultReject(Node* node);
Reduction ReducePromiseCapabilityDefaultResolve(Node* node);
Reduction ReducePromiseConstructor(Node* node);
Reduction ReducePromiseInternalConstructor(Node* node);
Reduction ReducePromiseInternalReject(Node* node);
Reduction ReducePromiseInternalResolve(Node* node);

View File

@ -485,6 +485,8 @@ DEFINE_BOOL(turbo_store_elimination, true,
DEFINE_BOOL(trace_store_elimination, false, "trace store elimination")
DEFINE_BOOL(turbo_rewrite_far_jumps, true,
"rewrite far to near jumps (ia32,x64)")
DEFINE_BOOL(experimental_inline_promise_constructor, false,
"inline the Promise constructor in TurboFan")
#ifdef DISABLE_UNTRUSTED_CODE_MITIGATIONS
#define V8_DEFAULT_UNTRUSTED_CODE_MITIGATIONS false

View File

@ -1112,7 +1112,7 @@ class Isolate {
void InvalidateStringLengthOverflowProtector();
void InvalidateArrayIteratorProtector();
void InvalidateArrayBufferNeuteringProtector();
void InvalidatePromiseHookProtector();
V8_EXPORT_PRIVATE void InvalidatePromiseHookProtector();
void InvalidatePromiseThenProtector();
// Returns true if array is the initial array prototype in any native context.

View File

@ -0,0 +1,117 @@
// Copyright 2018 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 --experimental-inline-promise-constructor
// We have to patch mjsunit because normal assertion failures just throw
// exceptions which are swallowed in a then clause.
failWithMessage = (msg) => %AbortJS(msg);
// Don't crash.
(function() {
function foo() {
let resolve, reject, promise;
promise = new Promise((a, b) => { resolve = a; reject = b; });
return {resolve, reject, promise};
}
foo();
foo();
%OptimizeFunctionOnNextCall(foo);
foo();
})();
// Check that when executor throws, the promise is rejected
(function() {
function foo() {
return new Promise((a, b) => { throw new Error(); });
}
function bar(i) {
let error = null;
foo().then(_ => error = 1, e => error = e);
setTimeout(_ => assertInstanceof(error, Error));
if (i == 1) %OptimizeFunctionOnNextCall(foo);
if (i > 0) setTimeout(bar.bind(null, i - 1));
}
bar(3);
})();
(function() {
function foo() {
let p;
try {
p = new Promise((a, b) => { %DeoptimizeFunction(foo); });
} catch (e) {
// Nothing should throw
assertUnreachable();
}
// TODO(petermarshall): This fails but should not.
// assertInstanceof(p, Promise);
}
foo();
foo();
%OptimizeFunctionOnNextCall(foo);
foo();
})();
(function() {
function foo() {
let p;
try {
p = new Promise((a, b) => { %DeoptimizeFunction(foo); throw new Error(); });
} catch (e) {
// The promise constructor should catch the exception and reject the
// promise instead.
// TODO(petermarshall): This fails but should not. We need to fix deopts.
// assertUnreachable();
}
// TODO(petermarshall): This fails but should not.
// assertInstanceof(p, Promise);
}
foo();
foo();
%OptimizeFunctionOnNextCall(foo);
foo();
})();
// Test when the executor is not inlined.
(function() {
let resolve, reject, promise;
function bar(a, b) {
resolve = a; reject = b;
throw new Error();
}
function foo() {
promise = new Promise(bar);
}
foo();
foo();
%NeverOptimizeFunction(bar);
%OptimizeFunctionOnNextCall(foo);
foo();
})();
// Test that the stack trace contains 'new Promise'
(function() {
let resolve, reject, promise;
function bar(a, b) {
resolve = a; reject = b;
let stack = new Error().stack;
// TODO(petermarshall): This fails but should not.
// assertContains("new Promise", stack);
throw new Error();
}
function foo() {
promise = new Promise(bar);
}
foo();
foo();
%OptimizeFunctionOnNextCall(foo);
foo();
})();

View File

@ -98,6 +98,7 @@ v8_source_set("unittests_sources") {
"compiler/instruction-unittest.cc",
"compiler/int64-lowering-unittest.cc",
"compiler/js-builtin-reducer-unittest.cc",
"compiler/js-call-reducer-unittest.cc",
"compiler/js-create-lowering-unittest.cc",
"compiler/js-intrinsic-lowering-unittest.cc",
"compiler/js-operator-unittest.cc",

View File

@ -0,0 +1,121 @@
// Copyright 2018 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.
#include "src/compiler/js-call-reducer.h"
#include "src/compilation-dependencies.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/simplified-operator.h"
#include "src/isolate.h"
#include "test/unittests/compiler/graph-unittest.h"
namespace v8 {
namespace internal {
namespace compiler {
class JSCallReducerTest : public TypedGraphTest {
public:
JSCallReducerTest()
: TypedGraphTest(3), javascript_(zone()), deps_(isolate(), zone()) {}
~JSCallReducerTest() override {}
protected:
Reduction Reduce(Node* node) {
MachineOperatorBuilder machine(zone());
SimplifiedOperatorBuilder simplified(zone());
JSGraph jsgraph(isolate(), graph(), common(), javascript(), &simplified,
&machine);
// TODO(titzer): mock the GraphReducer here for better unit testing.
GraphReducer graph_reducer(zone(), graph());
JSCallReducer reducer(&graph_reducer, &jsgraph, JSCallReducer::kNoFlags,
native_context(), &deps_);
return reducer.Reduce(node);
}
JSOperatorBuilder* javascript() { return &javascript_; }
private:
JSOperatorBuilder javascript_;
CompilationDependencies deps_;
};
TEST_F(JSCallReducerTest, PromiseConstructorNoArgs) {
Node* promise = HeapConstant(handle(native_context()->promise_function()));
Node* effect = graph()->start();
Node* control = graph()->start();
Node* context = UndefinedConstant();
Node* frame_state = graph()->start();
Node* construct =
graph()->NewNode(javascript()->Construct(2), promise, promise, context,
frame_state, effect, control);
Reduction r = Reduce(construct);
ASSERT_FALSE(r.Changed());
}
TEST_F(JSCallReducerTest, PromiseConstructorSubclass) {
Node* promise = HeapConstant(handle(native_context()->promise_function()));
Node* new_target = HeapConstant(handle(native_context()->array_function()));
Node* effect = graph()->start();
Node* control = graph()->start();
Node* context = UndefinedConstant();
Node* frame_state = graph()->start();
Node* executor = UndefinedConstant();
Node* construct =
graph()->NewNode(javascript()->Construct(3), promise, executor,
new_target, context, frame_state, effect, control);
Reduction r = Reduce(construct);
ASSERT_FALSE(r.Changed());
}
TEST_F(JSCallReducerTest, PromiseConstructorBasic) {
Node* promise = HeapConstant(handle(native_context()->promise_function()));
Node* effect = graph()->start();
Node* control = graph()->start();
Node* context = UndefinedConstant();
Node* frame_state = graph()->start();
Node* executor = UndefinedConstant();
Node* construct =
graph()->NewNode(javascript()->Construct(3), promise, executor, promise,
context, frame_state, effect, control);
Reduction r = Reduce(construct);
if (FLAG_experimental_inline_promise_constructor) {
ASSERT_TRUE(r.Changed());
} else {
ASSERT_FALSE(r.Changed());
}
}
// Exactly the same as PromiseConstructorBasic which expects a reduction, except
// that we invalidate the protector cell.
TEST_F(JSCallReducerTest, PromiseConstructorWithHook) {
Node* promise = HeapConstant(handle(native_context()->promise_function()));
Node* effect = graph()->start();
Node* control = graph()->start();
Node* context = UndefinedConstant();
Node* frame_state = graph()->start();
Node* executor = UndefinedConstant();
Node* construct =
graph()->NewNode(javascript()->Construct(3), promise, executor, promise,
context, frame_state, effect, control);
isolate()->InvalidatePromiseHookProtector();
Reduction r = Reduce(construct);
ASSERT_FALSE(r.Changed());
}
} // namespace compiler
} // namespace internal
} // namespace v8