Commit 6bd85fc6 authored by Joshua Litt's avatar Joshua Litt Committed by Commit Bot

[promises] Port PromiseResolveThenableJob to Torque.

Bug: v8:9838
Change-Id: Ia9a84ca2c9c5ba9bed7b6ebddc3135f689e2cfaa
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1955665Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Commit-Queue: Joshua Litt <joshualitt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65404}
parent 86ab4039
......@@ -974,6 +974,7 @@ torque_files = [
"src/builtins/promise-abstract-operations.tq",
"src/builtins/promise-constructor.tq",
"src/builtins/promise-then.tq",
"src/builtins/promise-jobs.tq",
"src/builtins/proxy-constructor.tq",
"src/builtins/proxy-delete-property.tq",
"src/builtins/proxy-get-property.tq",
......
......@@ -723,8 +723,6 @@ namespace internal {
/* ES #sec-promisereactionjob */ \
TFS(PromiseRejectReactionJob, kReason, kHandler, kPromiseOrCapability) \
TFS(PromiseFulfillReactionJob, kValue, kHandler, kPromiseOrCapability) \
/* ES #sec-promiseresolvethenablejob */ \
TFS(PromiseResolveThenableJob, kPromiseToResolve, kThenable, kThen) \
/* ES #sec-promise.resolve */ \
TFJ(PromiseResolveTrampoline, 1, kReceiver, kValue) \
/* ES #sec-promise-resolve */ \
......
......@@ -540,92 +540,6 @@ TF_BUILTIN(PromisePrototypeCatch, PromiseBuiltinsAssembler) {
Return(InvokeThen(native_context, receiver, on_fulfilled, on_rejected));
}
// ES #sec-promiseresolvethenablejob
TF_BUILTIN(PromiseResolveThenableJob, PromiseBuiltinsAssembler) {
TNode<NativeContext> native_context = CAST(Parameter(Descriptor::kContext));
TNode<JSPromise> promise_to_resolve =
CAST(Parameter(Descriptor::kPromiseToResolve));
TNode<JSReceiver> thenable = CAST(Parameter(Descriptor::kThenable));
TNode<Object> then = CAST(Parameter(Descriptor::kThen));
// We can use a simple optimization here if we know that {then} is the initial
// Promise.prototype.then method, and {thenable} is a JSPromise whose
// @@species lookup chain is intact: We can connect {thenable} and
// {promise_to_resolve} directly in that case and avoid the allocation of a
// temporary JSPromise and the closures plus context.
//
// We take the generic (slow-)path if a PromiseHook is enabled or the debugger
// is active, to make sure we expose spec compliant behavior.
Label if_fast(this), if_slow(this, Label::kDeferred);
TNode<Object> promise_then =
LoadContextElement(native_context, Context::PROMISE_THEN_INDEX);
GotoIfNot(TaggedEqual(then, promise_then), &if_slow);
const TNode<Map> thenable_map = LoadMap(thenable);
GotoIfNot(IsJSPromiseMap(thenable_map), &if_slow);
GotoIf(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
&if_slow);
BranchIfPromiseSpeciesLookupChainIntact(native_context, thenable_map,
&if_fast, &if_slow);
BIND(&if_fast);
{
// We know that the {thenable} is a JSPromise, which doesn't require
// any special treatment and that {then} corresponds to the initial
// Promise.prototype.then method. So instead of allocating a temporary
// JSPromise to connect the {thenable} with the {promise_to_resolve},
// we can directly schedule the {promise_to_resolve} with default
// handlers onto the {thenable} promise. This does not only save the
// JSPromise allocation, but also avoids the allocation of the two
// resolving closures and the shared context.
//
// What happens normally in this case is
//
// resolve, reject = CreateResolvingFunctions(promise_to_resolve)
// result_capability = NewPromiseCapability(%Promise%)
// PerformPromiseThen(thenable, resolve, reject, result_capability)
//
// which means that PerformPromiseThen will either schedule a new
// PromiseReaction with resolve and reject or a PromiseReactionJob
// with resolve or reject based on the state of {thenable}. And
// resolve or reject will just invoke the default [[Resolve]] or
// [[Reject]] functions on the {promise_to_resolve}.
//
// This is the same as just doing
//
// PerformPromiseThen(thenable, undefined, undefined, promise_to_resolve)
//
// which performs exactly the same (observable) steps.
TailCallBuiltin(Builtins::kPerformPromiseThen, native_context, thenable,
UndefinedConstant(), UndefinedConstant(),
promise_to_resolve);
}
BIND(&if_slow);
{
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
PromiseResolvingFunctions funcs = CreatePromiseResolvingFunctions(
context, promise_to_resolve, FalseConstant(), native_context);
Node *resolve = funcs.resolve, *reject = funcs.reject;
Label if_exception(this, Label::kDeferred);
VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant());
const TNode<Object> result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
native_context, then, thenable, resolve, reject);
GotoIfException(result, &if_exception, &var_exception);
Return(result);
BIND(&if_exception);
{
// We need to reject the {thenable}.
const TNode<Object> result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
native_context, reject, UndefinedConstant(), var_exception.value());
Return(result);
}
}
}
// ES #sec-promisereactionjob
void PromiseBuiltinsAssembler::PromiseReactionJob(Node* context, Node* argument,
Node* handler,
......
......@@ -474,7 +474,7 @@ namespace promise {
PerformPromiseThen(implicit context: Context)(
promise: JSPromise, onFulfilled: Callable|Undefined,
onRejected: Callable|Undefined,
resultPromise: JSPromise|Undefined): HeapObject {
resultPromise: JSPromise|Undefined): JSAny {
PerformPromiseThenImpl(promise, onFulfilled, onRejected, resultPromise);
return resultPromise;
}
......
// Copyright 2019 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/builtins/builtins-promise.h'
// https://tc39.es/ecma262/#sec-promise-jobs
namespace promise {
const PROMISE_THEN_INDEX: constexpr NativeContextSlot
generates 'Context::PROMISE_THEN_INDEX';
extern macro IsJSPromiseMap(Map): bool;
// https://tc39.es/ecma262/#sec-promiseresolvethenablejob
transitioning builtin
PromiseResolveThenableJob(implicit context: Context)(
promiseToResolve: JSPromise, thenable: JSReceiver, then: JSAny): JSAny {
// We can use a simple optimization here if we know that {then} is the
// initial Promise.prototype.then method, and {thenable} is a JSPromise
// whose
// @@species lookup chain is intact: We can connect {thenable} and
// {promise_to_resolve} directly in that case and avoid the allocation of a
// temporary JSPromise and the closures plus context.
//
// We take the generic (slow-)path if a PromiseHook is enabled or the
// debugger is active, to make sure we expose spec compliant behavior.
const nativeContext = LoadNativeContext(context);
const promiseThen = nativeContext[PROMISE_THEN_INDEX];
const thenableMap = thenable.map;
if (TaggedEqual(then, promiseThen) && IsJSPromiseMap(thenableMap) &&
!IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() &&
IsPromiseSpeciesLookupChainIntact(nativeContext, thenableMap)) {
// We know that the {thenable} is a JSPromise, which doesn't require
// any special treatment and that {then} corresponds to the initial
// Promise.prototype.then method. So instead of allocating a temporary
// JSPromise to connect the {thenable} with the {promise_to_resolve},
// we can directly schedule the {promise_to_resolve} with default
// handlers onto the {thenable} promise. This does not only save the
// JSPromise allocation, but also avoids the allocation of the two
// resolving closures and the shared context.
//
// What happens normally in this case is
//
// resolve, reject = CreateResolvingFunctions(promise_to_resolve)
// result_capability = NewPromiseCapability(%Promise%)
// PerformPromiseThen(thenable, resolve, reject, result_capability)
//
// which means that PerformPromiseThen will either schedule a new
// PromiseReaction with resolve and reject or a PromiseReactionJob
// with resolve or reject based on the state of {thenable}. And
// resolve or reject will just invoke the default [[Resolve]] or
// [[Reject]] functions on the {promise_to_resolve}.
//
// This is the same as just doing
//
// PerformPromiseThen(thenable, undefined, undefined,
// promise_to_resolve)
//
// which performs exactly the same (observable) steps.
return PerformPromiseThen(
UnsafeCast<JSPromise>(thenable), UndefinedConstant(),
UndefinedConstant(), promiseToResolve);
} else {
const funcs = CreatePromiseResolvingFunctions(
promiseToResolve, False, nativeContext);
const resolve = funcs.resolve;
const reject = funcs.reject;
try {
return Call(
context, UnsafeCast<Callable>(then), thenable, resolve, reject);
} catch (e) {
return Call(context, UnsafeCast<Callable>(reject), Undefined, e);
}
}
}
}
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