// 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); } } }