Commit 9e855013 authored by Peter Marshall's avatar Peter Marshall Committed by Commit Bot

[turbofan] Add a frame state for inlined Promise constructors.

This adds a frame state for the call to the executor in inlined promise
constructors. We provide a continuation function in case of deopts which
just returns the created promise. This is not totally correct yet: if
the executor function also throws, we need to catch it and call the
reject function instead.

We also still need to add a frame state for the isCallable check on the
executor, so that the stack is correct for the thrown TypeError.

Bug: v8:7253
Change-Id: I3ee042ec82f1a9a35d59e576f6c8efe9bc98698c
Reviewed-on: https://chromium-review.googlesource.com/926523Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Peter Marshall <petermarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51417}
parent 5a68df2a
......@@ -810,6 +810,7 @@ namespace internal {
TFJ(PromiseGetCapabilitiesExecutor, 2, kResolve, kReject) \
/* ES6 #sec-newpromisecapability */ \
TFS(NewPromiseCapability, kConstructor, kDebugEvent) \
TFJ(PromiseConstructorLazyDeoptContinuation, 2, kPromise, kResult) \
/* ES6 #sec-promise-executor */ \
TFJ(PromiseConstructor, 1, kExecutor) \
CPP(IsPromise) \
......
......@@ -666,6 +666,11 @@ TF_BUILTIN(PromiseCapabilityDefaultResolve, PromiseBuiltinsAssembler) {
Return(CallBuiltin(Builtins::kResolvePromise, context, promise, resolution));
}
TF_BUILTIN(PromiseConstructorLazyDeoptContinuation, PromiseBuiltinsAssembler) {
Node* promise = Parameter(Descriptor::kPromise);
Return(promise);
}
// ES6 #sec-promise-executor
TF_BUILTIN(PromiseConstructor, PromiseBuiltinsAssembler) {
Node* const executor = Parameter(Descriptor::kExecutor);
......
......@@ -247,6 +247,7 @@ bool Builtins::IsLazy(int index) {
case kInterpreterEnterBytecodeDispatch:
case kInterpreterEntryTrampoline:
case kObjectConstructor_ConstructStub: // https://crbug.com/v8/6787.
case kPromiseConstructorLazyDeoptContinuation: // crbug/v8/6786.
case kProxyConstructor_ConstructStub: // https://crbug.com/v8/6787.
case kNumberConstructor_ConstructStub: // https://crbug.com/v8/6787.
case kStringConstructor_ConstructStub: // https://crbug.com/v8/6787.
......
......@@ -4174,13 +4174,31 @@ Reduction JSCallReducer::ReducePromiseConstructor(Node* node) {
graph()->NewNode(javascript()->CreateClosure(reject_shared),
promise_context, effect, control);
Handle<SharedFunctionInfo> promise_function_info =
handle(native_context()->promise_function()->shared());
// The first param is undefined for the receiver.
std::vector<Node*> checkpoint_params(
{jsgraph()->UndefinedConstant(), promise});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
// This simple continuation just returns the created promise.
// TODO(petermarshall): If the executor function causes lazy deopt, and it
// also throws an exception, we should catch the exception and call the reject
// function.
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), promise_function_info,
Builtins::kPromiseConstructorLazyDeoptContinuation, target, context,
&checkpoint_params[0], stack_parameters, outer_frame_state,
ContinuationFrameStateMode::LAZY);
// 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);
frame_state, effect, control);
Node* exception_effect = effect;
Node* exception_control = control;
......
......@@ -23,23 +23,77 @@ failWithMessage = (msg) => %AbortJS(msg);
foo();
})();
// Check that when executor is non-callable, the constructor throws.
(function() {
function foo() {
return new Promise(1);
}
assertThrows(foo, TypeError);
assertThrows(foo, TypeError);
%OptimizeFunctionOnNextCall(foo);
assertThrows(foo, TypeError);
})();
// Check that when the promise constructor throws because the executor is
// non-callable, the stack contains 'new Promise'.
(function() {
function foo() {
return new Promise(1);
}
// Check that when executor throws, the promise is rejected
let threw;
try {
threw = false;
foo();
} catch (e) {
threw = true;
assertContains('new Promise', e.stack);
} finally {
assertTrue(threw);
}
try {
threw = false;
foo();
} catch (e) {
threw = true;
assertContains('new Promise', e.stack);
} finally {
assertTrue(threw);
}
%OptimizeFunctionOnNextCall(foo);
try {
threw = false;
foo();
} catch (e) {
threw = true;
// TODO(petermarshall): This fails but should not.
// assertContains('new Promise', e.stack);
} finally {
assertTrue(threw);
}
})();
// 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);
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);
})();
// Check that when executor causes lazy deoptimization of the inlined
// constructor, we return the promise value and not the return value of the
// executor function itself.
(function() {
function foo() {
let p;
......@@ -49,8 +103,7 @@ bar(3);
// Nothing should throw
assertUnreachable();
}
// TODO(petermarshall): This fails but should not.
// assertInstanceof(p, Promise);
assertInstanceof(p, Promise);
}
foo();
......@@ -59,11 +112,41 @@ bar(3);
foo();
})();
// The same as above, except that the executor function also creates a promise
// and both executor functions cause a lazy deopt of the calling function.
(function() {
function executor(a, b) {
%DeoptimizeFunction(foo);
let p = new Promise((a, b) => { %DeoptimizeFunction(executor); });
}
function foo() {
let p;
try {
p = new Promise((a, b) => { %DeoptimizeFunction(foo); throw new Error(); });
p = new Promise(executor);
} catch (e) {
// Nothing should throw
assertUnreachable();
}
assertInstanceof(p, Promise);
}
foo();
foo();
%OptimizeFunctionOnNextCall(foo);
foo();
})();
// Check that when the executor causes lazy deoptimization of the inlined
// constructor, and then throws, the deopt continuation catches and then calls
// the reject function instead of propagating the exception.
(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.
......@@ -103,8 +186,9 @@ bar(3);
function bar(a, b) {
resolve = a; reject = b;
let stack = new Error().stack;
// TODO(petermarshall): This fails but should not.
// assertContains("new Promise", stack);
// TODO(petermarshall): This check should see 'new Promise', not just
// Promise
assertContains("Promise", stack);
throw new Error();
}
function foo() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment