Commit d8cb3b3f authored by Joshua Litt's avatar Joshua Litt Committed by Commit Bot

[promises] Port PerformPromiseThen to torque

Bug: v8:9838
Change-Id: I7597e55744c577bd1a7619110db88e1adb4239a2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1922488
Commit-Queue: Joshua Litt <joshualitt@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65118}
parent 1a639cf0
......@@ -722,8 +722,6 @@ namespace internal {
CPP(IsPromise) \
/* ES #sec-promise.prototype.then */ \
TFJ(PromisePrototypeThen, 2, kReceiver, kOnFulfilled, kOnRejected) \
/* ES #sec-performpromisethen */ \
TFS(PerformPromiseThen, kPromise, kOnFulfilled, kOnRejected, kResultPromise) \
/* ES #sec-promise.prototype.catch */ \
TFJ(PromisePrototypeCatch, 1, kReceiver, kOnRejected) \
/* ES #sec-promisereactionjob */ \
......
......@@ -254,125 +254,6 @@ void PromiseBuiltinsAssembler::PromiseSetHandledHint(Node* promise) {
StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags);
}
// ES #sec-performpromisethen
void PromiseBuiltinsAssembler::PerformPromiseThen(
TNode<Context> context, TNode<JSPromise> promise,
TNode<HeapObject> on_fulfilled, TNode<HeapObject> on_rejected,
TNode<HeapObject> result_promise_or_capability) {
CSA_ASSERT(this,
Word32Or(IsCallable(on_fulfilled), IsUndefined(on_fulfilled)));
CSA_ASSERT(this, Word32Or(IsCallable(on_rejected), IsUndefined(on_rejected)));
CSA_ASSERT(
this,
Word32Or(Word32Or(IsJSPromise(result_promise_or_capability),
IsPromiseCapability(result_promise_or_capability)),
IsUndefined(result_promise_or_capability)));
Label if_pending(this), if_notpending(this), done(this);
const TNode<Word32T> status = PromiseStatus(promise);
Branch(IsPromiseStatus(status, v8::Promise::kPending), &if_pending,
&if_notpending);
BIND(&if_pending);
{
// The {promise} is still in "Pending" state, so we just record a new
// PromiseReaction holding both the onFulfilled and onRejected callbacks.
// Once the {promise} is resolved we decide on the concrete handler to
// push onto the microtask queue.
const TNode<Object> promise_reactions =
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
const TNode<PromiseReaction> reaction =
AllocatePromiseReaction(promise_reactions, result_promise_or_capability,
on_fulfilled, on_rejected);
StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, reaction);
Goto(&done);
}
BIND(&if_notpending);
{
TVARIABLE(Map, var_map);
TVARIABLE(HeapObject, var_handler);
TVARIABLE(Object, var_handler_context, UndefinedConstant());
Label if_fulfilled(this), if_rejected(this, Label::kDeferred),
enqueue(this);
Branch(IsPromiseStatus(status, v8::Promise::kFulfilled), &if_fulfilled,
&if_rejected);
BIND(&if_fulfilled);
{
var_map = PromiseFulfillReactionJobTaskMapConstant();
var_handler = on_fulfilled;
Label use_fallback(this, Label::kDeferred), done(this);
ExtractHandlerContext(on_fulfilled, &var_handler_context);
Branch(IsUndefined(var_handler_context.value()), &use_fallback, &done);
BIND(&use_fallback);
var_handler_context = context;
ExtractHandlerContext(on_rejected, &var_handler_context);
Goto(&done);
BIND(&done);
Goto(&enqueue);
}
BIND(&if_rejected);
{
CSA_ASSERT(this, IsPromiseStatus(status, v8::Promise::kRejected));
var_map = PromiseRejectReactionJobTaskMapConstant();
var_handler = on_rejected;
Label use_fallback(this, Label::kDeferred), done(this);
ExtractHandlerContext(on_rejected, &var_handler_context);
Branch(IsUndefined(var_handler_context.value()), &use_fallback, &done);
BIND(&use_fallback);
var_handler_context = context;
ExtractHandlerContext(on_fulfilled, &var_handler_context);
Goto(&done);
BIND(&done);
GotoIf(PromiseHasHandler(promise), &enqueue);
CallRuntime(Runtime::kPromiseRevokeReject, context, promise);
Goto(&enqueue);
}
BIND(&enqueue);
{
TNode<Object> argument =
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
TNode<PromiseReactionJobTask> microtask = AllocatePromiseReactionJobTask(
var_map.value(), CAST(var_handler_context.value()), argument,
var_handler.value(), result_promise_or_capability);
CallBuiltin(Builtins::kEnqueueMicrotask, var_handler_context.value(),
microtask);
Goto(&done);
}
}
BIND(&done);
PromiseSetHasHandler(promise);
}
// ES #sec-performpromisethen
TF_BUILTIN(PerformPromiseThen, PromiseBuiltinsAssembler) {
const TNode<Context> context = CAST(Parameter(Descriptor::kContext));
const TNode<JSPromise> promise = CAST(Parameter(Descriptor::kPromise));
const TNode<HeapObject> on_fulfilled =
CAST(Parameter(Descriptor::kOnFulfilled));
const TNode<HeapObject> on_rejected =
CAST(Parameter(Descriptor::kOnRejected));
const TNode<HeapObject> result_promise =
CAST(Parameter(Descriptor::kResultPromise));
CSA_ASSERT(
this, Word32Or(IsJSPromise(result_promise), IsUndefined(result_promise)));
PerformPromiseThen(context, promise, on_fulfilled, on_rejected,
result_promise);
Return(result_promise);
}
TNode<PromiseReaction> PromiseBuiltinsAssembler::AllocatePromiseReaction(
TNode<Object> next, TNode<HeapObject> promise_or_capability,
TNode<HeapObject> fulfill_handler, TNode<HeapObject> reject_handler) {
......@@ -848,9 +729,9 @@ TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler) {
Goto(&if_rejected_done);
BIND(&if_rejected_done);
PerformPromiseThen(context, js_promise, CAST(var_on_fulfilled.value()),
CAST(var_on_rejected.value()),
var_result_promise_or_capability.value());
PerformPromiseThenImpl(context, js_promise, CAST(var_on_fulfilled.value()),
CAST(var_on_rejected.value()),
var_result_promise_or_capability.value());
Return(var_result_promise.value());
}
}
......@@ -1721,9 +1602,9 @@ Node* PromiseBuiltinsAssembler::PerformPromiseAll(
// Register the PromiseReaction immediately on the {next_value}, not
// passing any chained promise since neither async_hooks nor DevTools
// are enabled, so there's no use of the resulting promise.
PerformPromiseThen(native_context, CAST(next_value),
CAST(resolve_element_fun), CAST(reject_element_fun),
UndefinedConstant());
PerformPromiseThenImpl(native_context, CAST(next_value),
CAST(resolve_element_fun),
CAST(reject_element_fun), UndefinedConstant());
Goto(&loop);
}
......
......@@ -72,11 +72,6 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler {
void PromiseSetHasHandler(Node* promise);
void PromiseSetHandledHint(Node* promise);
void PerformPromiseThen(TNode<Context> context, TNode<JSPromise> promise,
TNode<HeapObject> on_fulfilled,
TNode<HeapObject> on_rejected,
TNode<HeapObject> result_promise_or_capability);
// We can skip the "resolve" lookup on {constructor} if it's the (initial)
// Promise constructor and the Promise.resolve() protector is intact, as
// that guards the lookup path for the "resolve" property on the %Promise%
......
......@@ -9,6 +9,9 @@ namespace runtime {
extern transitioning runtime
RejectPromise(implicit context: Context)(JSPromise, JSAny, Boolean): JSAny;
extern transitioning runtime
PromiseRevokeReject(implicit context: Context)(JSPromise): JSAny;
extern transitioning runtime
PromiseRejectAfterResolved(implicit context: Context)(JSPromise, JSAny):
JSAny;
......@@ -73,7 +76,23 @@ namespace promise {
goto NotFound;
}
transitioning macro MorpAndEnqueuePromiseReaction(implicit context: Context)(
// According to the HTML specification, we use the handler's context to
// EnqueueJob for Promise resolution.
macro
ExtractHandlerContext(implicit context: Context)(
primary: Callable|Undefined, secondary: Callable|Undefined): Context {
try {
return ExtractHandlerContext(primary) otherwise NotFound;
}
label NotFound {
return ExtractHandlerContext(secondary) otherwise Default;
}
label Default {
return context;
}
}
transitioning macro MorphAndEnqueuePromiseReaction(implicit context: Context)(
promiseReaction: PromiseReaction, argument: JSAny,
reactionType: constexpr PromiseReactionType): void {
let primaryHandler: Callable|Undefined;
......@@ -87,17 +106,8 @@ namespace promise {
secondaryHandler = promiseReaction.fulfill_handler;
}
let handlerContext: Context;
try {
handlerContext = ExtractHandlerContext(primaryHandler) otherwise NotFound;
}
label NotFound {
handlerContext =
ExtractHandlerContext(secondaryHandler) otherwise Default;
}
label Default {
handlerContext = context;
}
const handlerContext: Context =
ExtractHandlerContext(primaryHandler, secondaryHandler);
// Morph {current} from a PromiseReaction into a PromiseReactionJobTask
// and schedule that on the microtask queue. We try to minimize the number
......@@ -169,7 +179,7 @@ namespace promise {
}
case (currentReaction: PromiseReaction): {
current = currentReaction.next;
MorpAndEnqueuePromiseReaction(
MorphAndEnqueuePromiseReaction(
currentReaction, argument, reactionType);
}
}
......@@ -408,4 +418,63 @@ namespace promise {
// encapsulated in the dedicated ResolvePromise builtin.
return ResolvePromise(context, promise, resolution);
}
extern macro
PromiseBuiltinsAssembler::AllocatePromiseReaction(
Object, HeapObject, HeapObject, HeapObject): PromiseReaction;
extern macro
PromiseBuiltinsAssembler::AllocatePromiseReactionJobTask(
Map, Context, Object, HeapObject, HeapObject): PromiseReactionJobTask;
@export
transitioning macro PerformPromiseThenImpl(implicit context: Context)(
promise: JSPromise, onFulfilled: Callable|Undefined,
onRejected: Callable|Undefined,
resultPromiseOrCapability: JSPromise|PromiseCapability|Undefined): void {
if (promise.Status() == kPromisePending) {
// The {promise} is still in "Pending" state, so we just record a new
// PromiseReaction holding both the onFulfilled and onRejected callbacks.
// Once the {promise} is resolved we decide on the concrete handler to
// push onto the microtask queue.
const promiseReactions = promise.reactions_or_result;
const reaction = AllocatePromiseReaction(
promiseReactions, resultPromiseOrCapability, onFulfilled, onRejected);
promise.reactions_or_result = reaction;
} else {
let map: Map;
let handler: HeapObject;
let handlerContext: Context;
if (promise.Status() == kPromiseFulfilled) {
map = PromiseFulfillReactionJobTaskMapConstant();
handler = onFulfilled;
handlerContext = ExtractHandlerContext(onFulfilled, onRejected);
} else {
assert(promise.Status() == kPromiseRejected);
map = PromiseRejectReactionJobTaskMapConstant();
handler = onRejected;
handlerContext = ExtractHandlerContext(onRejected, onFulfilled);
if (!promise.HasHandler()) {
runtime::PromiseRevokeReject(promise);
}
}
const reactionsOrResult = promise.reactions_or_result;
const microtask = AllocatePromiseReactionJobTask(
map, handlerContext, reactionsOrResult, handler,
resultPromiseOrCapability);
EnqueueMicrotask(handlerContext, microtask);
}
promise.SetHasHandler();
}
// https://tc39.es/ecma262/#sec-performpromisethen
transitioning builtin
PerformPromiseThen(implicit context: Context)(
promise: JSPromise, onFulfilled: Callable|Undefined,
onRejected: Callable|Undefined,
resultPromise: JSPromise|Undefined): HeapObject {
PerformPromiseThenImpl(promise, onFulfilled, onRejected, resultPromise);
return resultPromise;
}
}
......@@ -30,6 +30,10 @@ extern class JSPromise extends JSObject {
return (this.flags & kJSPromiseHasHandlerMask) != 0;
}
SetHasHandler(): void {
this.flags |= kJSPromiseHasHandlerMask;
}
// Smi 0 terminated list of PromiseReaction objects in case the JSPromise was
// not settled yet, otherwise the result.
reactions_or_result: Zero|PromiseReaction|JSAny;
......
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