[promises] Port Promise.prototype.then to Torque.
Bug: v8:9838 Change-Id: Ib2741501330629a29c1f3fe4f4a93a73982ab4de Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1929400 Commit-Queue: Joshua Litt <joshualitt@chromium.org> Reviewed-by: Jakob Gruber <jgruber@chromium.org> Cr-Commit-Position: refs/heads/master@{#65368}
This commit is contained in:
parent
406622277a
commit
bed702fa07
1
BUILD.gn
1
BUILD.gn
@ -973,6 +973,7 @@ torque_files = [
|
||||
"src/builtins/object.tq",
|
||||
"src/builtins/promise-abstract-operations.tq",
|
||||
"src/builtins/promise-constructor.tq",
|
||||
"src/builtins/promise-then.tq",
|
||||
"src/builtins/proxy-constructor.tq",
|
||||
"src/builtins/proxy-delete-property.tq",
|
||||
"src/builtins/proxy-get-property.tq",
|
||||
|
@ -718,8 +718,6 @@ namespace internal {
|
||||
TFJ(PromiseConstructorLazyDeoptContinuation, 4, kReceiver, kPromise, \
|
||||
kReject, kException, kResult) \
|
||||
CPP(IsPromise) \
|
||||
/* ES #sec-promise.prototype.then */ \
|
||||
TFJ(PromisePrototypeThen, 2, kReceiver, kOnFulfilled, kOnRejected) \
|
||||
/* ES #sec-promise.prototype.catch */ \
|
||||
TFJ(PromisePrototypeCatch, 1, kReceiver, kOnRejected) \
|
||||
/* ES #sec-promisereactionjob */ \
|
||||
|
@ -412,10 +412,8 @@ void PromiseBuiltinsAssembler::GotoIfNotPromiseResolveLookupChainIntact(
|
||||
}
|
||||
|
||||
void PromiseBuiltinsAssembler::BranchIfPromiseSpeciesLookupChainIntact(
|
||||
Node* native_context, Node* promise_map, Label* if_fast, Label* if_slow) {
|
||||
CSA_ASSERT(this, IsNativeContext(native_context));
|
||||
CSA_ASSERT(this, IsJSPromiseMap(promise_map));
|
||||
|
||||
TNode<NativeContext> native_context, TNode<Map> promise_map, Label* if_fast,
|
||||
Label* if_slow) {
|
||||
TNode<Object> promise_prototype =
|
||||
LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
|
||||
GotoIfForceSlowPath(if_slow);
|
||||
@ -528,101 +526,6 @@ TF_BUILTIN(PromiseConstructorLazyDeoptContinuation, PromiseBuiltinsAssembler) {
|
||||
Return(promise);
|
||||
}
|
||||
|
||||
// ES#sec-promise.prototype.then
|
||||
// Promise.prototype.then ( onFulfilled, onRejected )
|
||||
TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler) {
|
||||
// 1. Let promise be the this value.
|
||||
const TNode<Object> maybe_promise = CAST(Parameter(Descriptor::kReceiver));
|
||||
const TNode<Object> on_fulfilled = CAST(Parameter(Descriptor::kOnFulfilled));
|
||||
const TNode<Object> on_rejected = CAST(Parameter(Descriptor::kOnRejected));
|
||||
const TNode<Context> context = CAST(Parameter(Descriptor::kContext));
|
||||
|
||||
// 2. If IsPromise(promise) is false, throw a TypeError exception.
|
||||
ThrowIfNotInstanceType(context, maybe_promise, JS_PROMISE_TYPE,
|
||||
"Promise.prototype.then");
|
||||
TNode<JSPromise> js_promise = CAST(maybe_promise);
|
||||
|
||||
// 3. Let C be ? SpeciesConstructor(promise, %Promise%).
|
||||
Label fast_promise_capability(this), slow_constructor(this, Label::kDeferred),
|
||||
slow_promise_capability(this, Label::kDeferred);
|
||||
const TNode<NativeContext> native_context = LoadNativeContext(context);
|
||||
TNode<JSFunction> promise_fun =
|
||||
CAST(LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX));
|
||||
const TNode<Map> promise_map = LoadMap(js_promise);
|
||||
BranchIfPromiseSpeciesLookupChainIntact(
|
||||
native_context, promise_map, &fast_promise_capability, &slow_constructor);
|
||||
|
||||
BIND(&slow_constructor);
|
||||
TNode<JSReceiver> constructor =
|
||||
SpeciesConstructor(native_context, js_promise, promise_fun);
|
||||
Branch(TaggedEqual(constructor, promise_fun), &fast_promise_capability,
|
||||
&slow_promise_capability);
|
||||
|
||||
// 4. Let resultCapability be ? NewPromiseCapability(C).
|
||||
Label perform_promise_then(this);
|
||||
TVARIABLE(Object, var_result_promise);
|
||||
TVARIABLE(HeapObject, var_result_promise_or_capability);
|
||||
|
||||
BIND(&fast_promise_capability);
|
||||
{
|
||||
const TNode<JSPromise> result_promise =
|
||||
AllocateAndInitJSPromise(context, js_promise);
|
||||
var_result_promise_or_capability = result_promise;
|
||||
var_result_promise = result_promise;
|
||||
Goto(&perform_promise_then);
|
||||
}
|
||||
|
||||
BIND(&slow_promise_capability);
|
||||
{
|
||||
const TNode<Oddball> debug_event = TrueConstant();
|
||||
const TNode<PromiseCapability> capability = CAST(CallBuiltin(
|
||||
Builtins::kNewPromiseCapability, context, constructor, debug_event));
|
||||
var_result_promise =
|
||||
LoadObjectField(capability, PromiseCapability::kPromiseOffset);
|
||||
var_result_promise_or_capability = capability;
|
||||
Goto(&perform_promise_then);
|
||||
}
|
||||
|
||||
// 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
|
||||
// resultCapability).
|
||||
BIND(&perform_promise_then);
|
||||
{
|
||||
// We do some work of the PerformPromiseThen operation here, in that
|
||||
// we check the handlers and turn non-callable handlers into undefined.
|
||||
// This is because this is the one and only callsite of PerformPromiseThen
|
||||
// that has to do this.
|
||||
|
||||
// 3. If IsCallable(onFulfilled) is false, then
|
||||
// a. Set onFulfilled to undefined.
|
||||
TVARIABLE(Object, var_on_fulfilled, on_fulfilled);
|
||||
Label if_fulfilled_done(this), if_fulfilled_notcallable(this);
|
||||
GotoIf(TaggedIsSmi(on_fulfilled), &if_fulfilled_notcallable);
|
||||
Branch(IsCallable(CAST(on_fulfilled)), &if_fulfilled_done,
|
||||
&if_fulfilled_notcallable);
|
||||
BIND(&if_fulfilled_notcallable);
|
||||
var_on_fulfilled = UndefinedConstant();
|
||||
Goto(&if_fulfilled_done);
|
||||
BIND(&if_fulfilled_done);
|
||||
|
||||
// 4. If IsCallable(onRejected) is false, then
|
||||
// a. Set onRejected to undefined.
|
||||
TVARIABLE(Object, var_on_rejected, on_rejected);
|
||||
Label if_rejected_done(this), if_rejected_notcallable(this);
|
||||
GotoIf(TaggedIsSmi(on_rejected), &if_rejected_notcallable);
|
||||
Branch(IsCallable(CAST(on_rejected)), &if_rejected_done,
|
||||
&if_rejected_notcallable);
|
||||
BIND(&if_rejected_notcallable);
|
||||
var_on_rejected = UndefinedConstant();
|
||||
Goto(&if_rejected_done);
|
||||
BIND(&if_rejected_done);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// ES#sec-promise.prototype.catch
|
||||
// Promise.prototype.catch ( onRejected )
|
||||
TF_BUILTIN(PromisePrototypeCatch, PromiseBuiltinsAssembler) {
|
||||
|
@ -72,6 +72,14 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler {
|
||||
TNode<Object> executor, Label* if_noaccess);
|
||||
void PromiseInit(Node* promise);
|
||||
|
||||
// We can shortcut the SpeciesConstructor on {promise_map} if it's
|
||||
// [[Prototype]] is the (initial) Promise.prototype and the @@species
|
||||
// protector is intact, as that guards the lookup path for the "constructor"
|
||||
// property on JSPromise instances which have the %PromisePrototype%.
|
||||
void BranchIfPromiseSpeciesLookupChainIntact(
|
||||
TNode<NativeContext> native_context, TNode<Map> promise_map,
|
||||
Label* if_fast, Label* if_slow);
|
||||
|
||||
protected:
|
||||
void PromiseSetHasHandler(Node* promise);
|
||||
void PromiseSetHandledHint(Node* promise);
|
||||
@ -87,14 +95,6 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler {
|
||||
SloppyTNode<Object> constructor,
|
||||
Label* if_slow);
|
||||
|
||||
// We can shortcut the SpeciesConstructor on {promise_map} if it's
|
||||
// [[Prototype]] is the (initial) Promise.prototype and the @@species
|
||||
// protector is intact, as that guards the lookup path for the "constructor"
|
||||
// property on JSPromise instances which have the %PromisePrototype%.
|
||||
void BranchIfPromiseSpeciesLookupChainIntact(Node* native_context,
|
||||
Node* promise_map,
|
||||
Label* if_fast, Label* if_slow);
|
||||
|
||||
// We can skip the "then" lookup on {receiver_map} if it's [[Prototype]]
|
||||
// is the (initial) Promise.prototype and the Promise#then() protector
|
||||
// is intact, as that guards the lookup path for the "then" property
|
||||
|
83
src/builtins/promise-then.tq
Normal file
83
src/builtins/promise-then.tq
Normal file
@ -0,0 +1,83 @@
|
||||
// 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-gen.h'
|
||||
|
||||
namespace promise {
|
||||
|
||||
extern macro
|
||||
PromiseBuiltinsAssembler::BranchIfPromiseSpeciesLookupChainIntact(
|
||||
NativeContext, Map): never labels IsIntact,
|
||||
IsNotIntact;
|
||||
|
||||
macro
|
||||
IsPromiseSpeciesLookupChainIntact(
|
||||
nativeContext: NativeContext, promiseMap: Map): bool {
|
||||
BranchIfPromiseSpeciesLookupChainIntact(nativeContext, promiseMap)
|
||||
otherwise return true, return false;
|
||||
}
|
||||
|
||||
extern macro
|
||||
PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Context, Object):
|
||||
JSPromise;
|
||||
|
||||
// https://tc39.es/ecma262/#sec-promise.prototype.then
|
||||
transitioning javascript builtin
|
||||
PromisePrototypeThen(js-implicit context: NativeContext, receiver: JSAny)(
|
||||
onFulfilled: JSAny, onRejected: JSAny): JSAny {
|
||||
// 1. Let promise be the this value.
|
||||
// 2. If IsPromise(promise) is false, throw a TypeError exception.
|
||||
const promise = Cast<JSPromise>(receiver) otherwise ThrowTypeError(
|
||||
kIncompatibleMethodReceiver, 'Promise.prototype.then', receiver);
|
||||
|
||||
// 3. Let C be ? SpeciesConstructor(promise, %Promise%).
|
||||
const promiseFun = UnsafeCast<JSFunction>(context[PROMISE_FUNCTION_INDEX]);
|
||||
|
||||
// 4. Let resultCapability be ? NewPromiseCapability(C).
|
||||
let resultPromiseOrCapability: JSPromise|PromiseCapability;
|
||||
let resultPromise: JSAny;
|
||||
try {
|
||||
if (IsPromiseSpeciesLookupChainIntact(context, promise.map)) {
|
||||
goto AllocateAndInit;
|
||||
}
|
||||
|
||||
const constructor = SpeciesConstructor(promise, promiseFun);
|
||||
if (TaggedEqual(constructor, promiseFun)) {
|
||||
goto AllocateAndInit;
|
||||
} else {
|
||||
const promiseCapability = NewPromiseCapability(constructor, True);
|
||||
resultPromiseOrCapability = promiseCapability;
|
||||
resultPromise = promiseCapability.promise;
|
||||
}
|
||||
}
|
||||
label AllocateAndInit {
|
||||
const resultJSPromise = AllocateAndInitJSPromise(context, promise);
|
||||
resultPromiseOrCapability = resultJSPromise;
|
||||
resultPromise = resultJSPromise;
|
||||
}
|
||||
|
||||
// We do some work of the PerformPromiseThen operation here, in that
|
||||
// we check the handlers and turn non-callable handlers into undefined.
|
||||
// This is because this is the one and only callsite of PerformPromiseThen
|
||||
// that has to do this.
|
||||
|
||||
// 3. If IsCallable(onFulfilled) is false, then
|
||||
// a. Set onFulfilled to undefined.
|
||||
const onFulfilled = TaggedIsCallable(onFulfilled) ?
|
||||
UnsafeCast<Callable>(onFulfilled) :
|
||||
Undefined;
|
||||
|
||||
// 4. If IsCallable(onRejected) is false, then
|
||||
// a. Set onRejected to undefined.
|
||||
const onRejected = TaggedIsCallable(onRejected) ?
|
||||
UnsafeCast<Callable>(onRejected) :
|
||||
Undefined;
|
||||
|
||||
// 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
|
||||
// resultCapability).
|
||||
PerformPromiseThenImpl(
|
||||
promise, onFulfilled, onRejected, resultPromiseOrCapability);
|
||||
return resultPromise;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user