[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:
parent
7ecb6a38b9
commit
46c199a5c7
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
117
test/mjsunit/compiler/promise-constructor.js
Normal file
117
test/mjsunit/compiler/promise-constructor.js
Normal 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();
|
||||
})();
|
@ -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",
|
||||
|
121
test/unittests/compiler/js-call-reducer-unittest.cc
Normal file
121
test/unittests/compiler/js-call-reducer-unittest.cc
Normal 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
|
Loading…
Reference in New Issue
Block a user