| // 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 runtime { |
| extern transitioning runtime |
| ResolvePromise(implicit context: Context)(JSPromise, JSAny): JSAny; |
| } |
| |
| namespace promise { |
| extern macro ConstructorStringConstant(): String; |
| const kConstructorString: String = ConstructorStringConstant(); |
| |
| // https://tc39.es/ecma262/#sec-promise.resolve |
| transitioning javascript builtin |
| PromiseResolveTrampoline( |
| js-implicit context: NativeContext, receiver: JSAny)(value: JSAny): JSAny { |
| // 1. Let C be the this value. |
| // 2. If Type(C) is not Object, throw a TypeError exception. |
| const receiver = Cast<JSReceiver>(receiver) otherwise |
| ThrowTypeError(MessageTemplate::kCalledOnNonObject, 'PromiseResolve'); |
| |
| // 3. Return ? PromiseResolve(C, x). |
| return PromiseResolve(receiver, value); |
| } |
| |
| transitioning builtin |
| PromiseResolve(implicit context: Context)( |
| constructor: JSReceiver, value: JSAny): JSAny { |
| const nativeContext = LoadNativeContext(context); |
| const promiseFun = *NativeContextSlot(ContextSlot::PROMISE_FUNCTION_INDEX); |
| try { |
| // Check if {value} is a JSPromise. |
| const value = Cast<JSPromise>(value) otherwise NeedToAllocate; |
| |
| // We can skip the "constructor" lookup on {value} if it's [[Prototype]] |
| // is the (initial) Promise.prototype and the @@species protector is |
| // intact, as that guards the lookup path for "constructor" on |
| // JSPromise instances which have the (initial) Promise.prototype. |
| const promisePrototype = |
| *NativeContextSlot(ContextSlot::PROMISE_PROTOTYPE_INDEX); |
| // Check that Torque load elimination works. |
| static_assert(nativeContext == LoadNativeContext(context)); |
| if (value.map.prototype != promisePrototype) { |
| goto SlowConstructor; |
| } |
| |
| if (IsPromiseSpeciesProtectorCellInvalid()) goto SlowConstructor; |
| |
| // If the {constructor} is the Promise function, we just immediately |
| // return the {value} here and don't bother wrapping it into a |
| // native Promise. |
| if (promiseFun != constructor) goto SlowConstructor; |
| return value; |
| } label SlowConstructor deferred { |
| // At this point, value or/and constructor are not native promises, but |
| // they could be of the same subclass. |
| const valueConstructor = GetProperty(value, kConstructorString); |
| if (valueConstructor != constructor) goto NeedToAllocate; |
| return value; |
| } label NeedToAllocate { |
| if (promiseFun == constructor) { |
| // This adds a fast path for native promises that don't need to |
| // create NewPromiseCapability. |
| const result = NewJSPromise(); |
| ResolvePromise(context, result, value); |
| return result; |
| } else |
| deferred { |
| const capability = NewPromiseCapability(constructor, True); |
| const resolve = UnsafeCast<Callable>(capability.resolve); |
| Call(context, resolve, Undefined, value); |
| return capability.promise; |
| } |
| } |
| } |
| |
| extern macro IsJSReceiverMap(Map): bool; |
| |
| extern macro IsPromiseThenProtectorCellInvalid(): bool; |
| |
| extern macro ThenStringConstant(): String; |
| |
| const kThenString: String = ThenStringConstant(); |
| |
| // https://tc39.es/ecma262/#sec-promise-resolve-functions |
| transitioning builtin |
| ResolvePromise(implicit context: Context)( |
| promise: JSPromise, resolution: JSAny): JSAny { |
| // 7. If SameValue(resolution, promise) is true, then |
| // If promise hook is enabled or the debugger is active, let |
| // the runtime handle this operation, which greatly reduces |
| // the complexity here and also avoids a couple of back and |
| // forth between JavaScript and C++ land. |
| // We also let the runtime handle it if promise == resolution. |
| // We can use pointer comparison here, since the {promise} is guaranteed |
| // to be a JSPromise inside this function and thus is reference comparable. |
| if (IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() || |
| TaggedEqual(promise, resolution)) |
| deferred { |
| return runtime::ResolvePromise(promise, resolution); |
| } |
| |
| let then: Object = Undefined; |
| try { |
| // 8. If Type(resolution) is not Object, then |
| // 8.a Return FulfillPromise(promise, resolution). |
| if (TaggedIsSmi(resolution)) { |
| return FulfillPromise(promise, resolution); |
| } |
| |
| const heapResolution = UnsafeCast<HeapObject>(resolution); |
| const resolutionMap = heapResolution.map; |
| if (!IsJSReceiverMap(resolutionMap)) { |
| return FulfillPromise(promise, resolution); |
| } |
| |
| // We can skip the "then" lookup on {resolution} if its [[Prototype]] |
| // is the (initial) Promise.prototype and the Promise#then protector |
| // is intact, as that guards the lookup path for the "then" property |
| // on JSPromise instances which have the (initial) %PromisePrototype%. |
| if (IsForceSlowPath()) { |
| goto Slow; |
| } |
| |
| if (IsPromiseThenProtectorCellInvalid()) { |
| goto Slow; |
| } |
| |
| const nativeContext = LoadNativeContext(context); |
| if (!IsJSPromiseMap(resolutionMap)) { |
| // We can skip the lookup of "then" if the {resolution} is a (newly |
| // created) IterResultObject, as the Promise#then() protector also |
| // ensures that the intrinsic %ObjectPrototype% doesn't contain any |
| // "then" property. This helps to avoid negative lookups on iterator |
| // results from async generators. |
| assert(IsJSReceiverMap(resolutionMap)); |
| assert(!IsPromiseThenProtectorCellInvalid()); |
| if (resolutionMap == |
| *NativeContextSlot(ContextSlot::ITERATOR_RESULT_MAP_INDEX)) { |
| return FulfillPromise(promise, resolution); |
| } else { |
| goto Slow; |
| } |
| } |
| |
| const promisePrototype = |
| *NativeContextSlot(ContextSlot::PROMISE_PROTOTYPE_INDEX); |
| if (resolutionMap.prototype == promisePrototype) { |
| // The {resolution} is a native Promise in this case. |
| then = *NativeContextSlot(ContextSlot::PROMISE_THEN_INDEX); |
| // Check that Torque load elimination works. |
| static_assert(nativeContext == LoadNativeContext(context)); |
| goto Enqueue; |
| } |
| goto Slow; |
| } label Slow deferred { |
| // 9. Let then be Get(resolution, "then"). |
| // 10. If then is an abrupt completion, then |
| try { |
| then = GetProperty(resolution, kThenString); |
| } catch (e) { |
| // a. Return RejectPromise(promise, then.[[Value]]). |
| return RejectPromise(promise, e, False); |
| } |
| |
| // 11. Let thenAction be then.[[Value]]. |
| // 12. If IsCallable(thenAction) is false, then |
| if (!Is<Callable>(then)) { |
| // a. Return FulfillPromise(promise, resolution). |
| return FulfillPromise(promise, resolution); |
| } |
| goto Enqueue; |
| } label Enqueue { |
| // 13. Let job be NewPromiseResolveThenableJob(promise, resolution, |
| // thenAction). |
| const task = NewPromiseResolveThenableJobTask( |
| promise, UnsafeCast<JSReceiver>(resolution), |
| UnsafeCast<Callable>(then)); |
| |
| // 14. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). |
| // 15. Return undefined. |
| return EnqueueMicrotask(task.context, task); |
| } |
| } |
| } |