diff --git a/BUILD.gn b/BUILD.gn index 3a7a9736f8..001a765835 100644 --- a/BUILD.gn +++ b/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", diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 21c7268713..63ccb2d5c8 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -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 */ \ diff --git a/src/builtins/builtins-promise-gen.cc b/src/builtins/builtins-promise-gen.cc index fc0a351588..132ac9d5dd 100644 --- a/src/builtins/builtins-promise-gen.cc +++ b/src/builtins/builtins-promise-gen.cc @@ -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 native_context, TNode promise_map, Label* if_fast, + Label* if_slow) { TNode 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 maybe_promise = CAST(Parameter(Descriptor::kReceiver)); - const TNode on_fulfilled = CAST(Parameter(Descriptor::kOnFulfilled)); - const TNode on_rejected = CAST(Parameter(Descriptor::kOnRejected)); - const TNode 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 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 native_context = LoadNativeContext(context); - TNode promise_fun = - CAST(LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX)); - const TNode promise_map = LoadMap(js_promise); - BranchIfPromiseSpeciesLookupChainIntact( - native_context, promise_map, &fast_promise_capability, &slow_constructor); - - BIND(&slow_constructor); - TNode 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 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 debug_event = TrueConstant(); - const TNode 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) { diff --git a/src/builtins/builtins-promise-gen.h b/src/builtins/builtins-promise-gen.h index 8bc07ed13d..445ce9532f 100644 --- a/src/builtins/builtins-promise-gen.h +++ b/src/builtins/builtins-promise-gen.h @@ -72,6 +72,14 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler { TNode 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 native_context, TNode 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 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 diff --git a/src/builtins/promise-then.tq b/src/builtins/promise-then.tq new file mode 100644 index 0000000000..1be4d6b867 --- /dev/null +++ b/src/builtins/promise-then.tq @@ -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(receiver) otherwise ThrowTypeError( + kIncompatibleMethodReceiver, 'Promise.prototype.then', receiver); + + // 3. Let C be ? SpeciesConstructor(promise, %Promise%). + const promiseFun = UnsafeCast(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(onFulfilled) : + Undefined; + + // 4. If IsCallable(onRejected) is false, then + // a. Set onRejected to undefined. + const onRejected = TaggedIsCallable(onRejected) ? + UnsafeCast(onRejected) : + Undefined; + + // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, + // resultCapability). + PerformPromiseThenImpl( + promise, onFulfilled, onRejected, resultPromiseOrCapability); + return resultPromise; + } +}