blob: e933dfbae0abf84c8693de7aa13a9f5d71dfaa69 [file] [log] [blame]
// 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);
}
}
}