Issue #2089 - Improve Runtime perf of Promises

- reduce typecasting
- reduce rooting already rooted values
- directly handle unwrapped promises

Based-on: m-c 1475678/{1-7}
This commit is contained in:
Martok 2023-01-23 00:19:20 +01:00 • committed by roytam1
commit 1fd51e14f3
3 changed files with 214 additions and 187 deletions

View file

@ -162,7 +162,7 @@ static PromiseAllDataHolder*
NewPromiseAllDataHolder(JSContext* cx, HandleObject resultPromise, HandleValue valuesArray,
HandleObject resolve)
{
Rooted<PromiseAllDataHolder*> dataHolder(cx, NewObjectWithClassProto<PromiseAllDataHolder>(cx));
PromiseAllDataHolder* dataHolder = NewObjectWithClassProto<PromiseAllDataHolder>(cx);
if (!dataHolder)
return nullptr;
@ -436,19 +436,19 @@ enum ReactionRecordSlots {
// ES2016, 25.4.1.2.
class PromiseReactionRecord : public NativeObject
{
static constexpr size_t REACTION_FLAG_RESOLVED = 0x1;
static constexpr size_t REACTION_FLAG_FULFILLED = 0x2;
static constexpr size_t REACTION_FLAG_DEFAULT_RESOLVING_HANDLER = 0x4;
static constexpr size_t REACTION_FLAG_ASYNC_FUNCTION = 0x8;
static constexpr size_t REACTION_FLAG_ASYNC_GENERATOR = 0x10;
static constexpr size_t REACTION_FLAG_DEBUGGER_DUMMY = 0x20;
static constexpr uint32_t REACTION_FLAG_RESOLVED = 0x1;
static constexpr uint32_t REACTION_FLAG_FULFILLED = 0x2;
static constexpr uint32_t REACTION_FLAG_DEFAULT_RESOLVING_HANDLER = 0x4;
static constexpr uint32_t REACTION_FLAG_ASYNC_FUNCTION = 0x8;
static constexpr uint32_t REACTION_FLAG_ASYNC_GENERATOR = 0x10;
static constexpr uint32_t REACTION_FLAG_DEBUGGER_DUMMY = 0x20;
void setFlagOnInitialState(size_t flag) {
int32_t flags = this->flags();
MOZ_ASSERT(flags == 0, "Can't modify with non-default flags");
flags |= flag;
setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
}
void setFlagOnInitialState(uint32_t flag) {
int32_t flags = this->flags();
MOZ_ASSERT(flags == 0, "Can't modify with non-default flags");
flags |= flag;
setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
}
public:
static const Class class_;
@ -598,8 +598,13 @@ IsSettledMaybeWrappedPromise(JSObject* promise)
return promise->as<PromiseObject>().state() != JS::PromiseState::Pending;
}
static MOZ_MUST_USE bool RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj,
HandleValue reason);
// ES2016, 25.4.1.7.
static MOZ_MUST_USE bool
RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue reason);
// ES2016, 25.4.1.7.
static MOZ_MUST_USE bool
RejectPromiseInternal(JSContext* cx, Handle<PromiseObject*> promise, HandleValue reason);
// ES2016, 25.4.1.3.1.
static bool
@ -607,11 +612,11 @@ RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedFunction reject(cx, &args.callee().as<JSFunction>());
RootedValue reasonVal(cx, args.get(0));
JSFunction* reject = &args.callee().as<JSFunction>();
HandleValue reasonVal = args.get(0);
// Steps 1-2.
RootedValue promiseVal(cx, reject->getExtendedSlot(RejectFunctionSlot_Promise));
const Value& promiseVal = reject->getExtendedSlot(RejectFunctionSlot_Promise);
// Steps 3-4.
// If the Promise isn't available anymore, it has been resolved and the
@ -621,13 +626,15 @@ RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp)
return true;
}
// Store the promise value in |promise| before ClearResolutionFunctionSlots
// removes the reference.
RootedObject promise(cx, &promiseVal.toObject());
// Step 5.
// Here, we only remove the Promise reference from the resolution
// functions. Actually marking it as fulfilled/rejected happens later.
ClearResolutionFunctionSlots(reject);
RootedObject promise(cx, &promiseVal.toObject());
// In some cases the Promise reference on the resolution function won't
// have been removed during resolution, so we need to check that here,
// too.
@ -749,8 +756,8 @@ ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedFunction resolve(cx, &args.callee().as<JSFunction>());
RootedValue resolutionVal(cx, args.get(0));
JSFunction* resolve = &args.callee().as<JSFunction>();
HandleValue resolutionVal = args.get(0);
// Steps 3-4 (reordered).
// We use the reference to the reject function as a signal for whether
@ -813,14 +820,15 @@ EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj,
MOZ_RELEASE_ASSERT(reactionObj->is<PromiseReactionRecord>());
reaction = &reactionObj->as<PromiseReactionRecord>();
} else {
if (JS_IsDeadWrapper(UncheckedUnwrap(reactionObj))) {
JSObject* unwrappedReactionObj = UncheckedUnwrap(reactionObj);
if (JS_IsDeadWrapper(unwrappedReactionObj)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
return false;
}
reaction = &UncheckedUnwrap(reactionObj)->as<PromiseReactionRecord>();
reaction = &unwrappedReactionObj->as<PromiseReactionRecord>();
MOZ_RELEASE_ASSERT(reaction->is<PromiseReactionRecord>());
ac.emplace(cx, reaction);
if (!reaction->compartment()->wrap(cx, &handlerArg))
if (!cx->compartment()->wrap(cx, &handlerArg))
return false;
}
@ -842,13 +850,11 @@ EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj,
// from said global.
mozilla::Maybe<AutoCompartment> ac2;
if (handler.isObject()) {
RootedObject handlerObj(cx, &handler.toObject());
// The unwrapping has to be unchecked because we specifically want to
// be able to use handlers with wrappers that would only allow calls.
// E.g., it's ok to have a handler from a chrome compartment in a
// reaction to a content compartment's Promise instance.
handlerObj = UncheckedUnwrap(handlerObj);
JSObject* handlerObj = UncheckedUnwrap(&handler.toObject());
MOZ_ASSERT(handlerObj);
ac2.emplace(cx, handlerObj);
@ -890,8 +896,7 @@ EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj,
// much better than having to store the original global as a private value
// because we couldn't wrap it to store it as a normal JS value.
RootedObject global(cx);
RootedObject objectFromIncumbentGlobal(cx, reaction->incumbentGlobalObject());
if (objectFromIncumbentGlobal) {
if (JSObject* objectFromIncumbentGlobal = reaction->incumbentGlobalObject()) {
objectFromIncumbentGlobal = CheckedUnwrap(objectFromIncumbentGlobal);
MOZ_ASSERT(objectFromIncumbentGlobal);
global = &objectFromIncumbentGlobal->global();
@ -951,6 +956,13 @@ ResolvePromise(JSContext* cx, Handle<PromiseObject*> promise, HandleValue valueO
return true;
}
// ES2016, 25.4.1.7.
static MOZ_MUST_USE bool
RejectPromiseInternal(JSContext* cx, Handle<PromiseObject*> promise, HandleValue reason)
{
return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected);
}
// ES2016, 25.4.1.4.
static MOZ_MUST_USE bool
FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue value_)
@ -962,13 +974,14 @@ FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue v
if (!IsProxy(promiseObj)) {
promise = &promiseObj->as<PromiseObject>();
} else {
if (JS_IsDeadWrapper(UncheckedUnwrap(promiseObj))) {
JSObject* unwrappedPromiseObj = UncheckedUnwrap(promiseObj);
if (JS_IsDeadWrapper(unwrappedPromiseObj)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
return false;
}
promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
promise = &unwrappedPromiseObj->as<PromiseObject>();
ac.emplace(cx, promise);
if (!promise->compartment()->wrap(cx, &value))
if (!cx->compartment()->wrap(cx, &value))
return false;
}
@ -1079,7 +1092,7 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise,
return false;
// Step 7.
RootedValue resolveVal(cx, executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve));
const Value& resolveVal = executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve);
if (!IsCallable(resolveVal)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE);
@ -1087,7 +1100,7 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise,
}
// Step 8.
RootedValue rejectVal(cx, executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject));
const Value& rejectVal = executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject);
if (!IsCallable(rejectVal)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE);
@ -1107,7 +1120,7 @@ static bool
GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedFunction F(cx, &args.callee().as<JSFunction>());
JSFunction* F = &args.callee().as<JSFunction>();
// Steps 1-2 (implicit).
@ -1142,11 +1155,12 @@ RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue re
if (!IsProxy(promiseObj)) {
promise = &promiseObj->as<PromiseObject>();
} else {
if (JS_IsDeadWrapper(UncheckedUnwrap(promiseObj))) {
JSObject* unwrappedPromiseObj = UncheckedUnwrap(promiseObj);
if (JS_IsDeadWrapper(unwrappedPromiseObj)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
return false;
}
promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
promise = &unwrappedPromiseObj->as<PromiseObject>();
ac.emplace(cx, promise);
// The rejection reason might've been created in a compartment with higher
@ -1156,7 +1170,7 @@ RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue re
// avoid that situation, we synthesize a generic error that doesn't
// expose any privileged information but can safely be used in the
// rejection handler.
if (!promise->compartment()->wrap(cx, &reason))
if (!cx->compartment()->wrap(cx, &reason))
return false;
if (reason.isObject() && !CheckedUnwrap(&reason.toObject())) {
// Async stacks are only properly adopted if there's at least one
@ -1179,17 +1193,19 @@ TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseStat
MOZ_ASSERT(state == JS::PromiseState::Fulfilled || state == JS::PromiseState::Rejected);
RootedObject reactions(cx, &reactionsVal.toObject());
RootedObject reaction(cx);
if (reactions->is<PromiseReactionRecord>() || IsWrapper(reactions))
return EnqueuePromiseReactionJob(cx, reactions, valueOrReason, state);
RootedNativeObject reactionsList(cx, &reactions->as<NativeObject>());
size_t reactionsCount = reactionsList->getDenseInitializedLength();
HandleNativeObject reactionsList = reactions.as<NativeObject>();
uint32_t reactionsCount = reactionsList->getDenseInitializedLength();
MOZ_ASSERT(reactionsCount > 1, "Reactions list should be created lazily");
for (size_t i = 0; i < reactionsCount; i++) {
reaction = &reactionsList->getDenseElement(i).toObject();
RootedObject reaction(cx);
for (uint32_t i = 0; i < reactionsCount; i++) {
const Value& reactionVal = reactionsList->getDenseElement(i);
MOZ_RELEASE_ASSERT(reactionVal.isObject());
reaction = &reactionVal.toObject();
if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state))
return false;
}
@ -1223,7 +1239,7 @@ DefaultResolvingPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*>
if (reaction->targetState() == JS::PromiseState::Fulfilled)
ok = ResolvePromiseInternal(cx, promiseToResolve, argument);
else
ok = RejectMaybeWrappedPromise(cx, promiseToResolve, argument);
ok = RejectPromiseInternal(cx, promiseToResolve, argument);
if (!ok) {
resolutionMode = RejectMode;
@ -1233,9 +1249,9 @@ DefaultResolvingPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*>
}
// Steps 7-9.
size_t hookSlot = resolutionMode == RejectMode
? ReactionRecordSlot_Reject
: ReactionRecordSlot_Resolve;
uint32_t hookSlot = resolutionMode == RejectMode
? ReactionRecordSlot_Reject
: ReactionRecordSlot_Resolve;
RootedObject callee(cx, reaction->getFixedSlot(hookSlot).toObjectOrNull());
RootedObject promiseObj(cx, reaction->promise());
if (!RunResolutionFunction(cx, callee, handlerResult, resolutionMode, promiseObj))
@ -1364,7 +1380,7 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp)
}
// Steps 1-2.
Rooted<PromiseReactionRecord*> reaction(cx, &reactionObj->as<PromiseReactionRecord>());
Handle<PromiseReactionRecord*> reaction = reactionObj.as<PromiseReactionRecord>();
if (reaction->isDefaultResolvingHandler())
return DefaultResolvingPromiseReactionJob(cx, reaction, args.rval());
if (reaction->isAsyncFunction())
@ -1399,7 +1415,7 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp)
bool done = handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone;
// Async Iteration proposal 11.1.3.2.5 step 1.
RootedObject resultObj(cx, CreateIterResultObject(cx, argument, done));
JSObject* resultObj = CreateIterResultObject(cx, argument, done);
if (!resultObj)
return false;
@ -1420,9 +1436,9 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp)
}
// Steps 7-9.
size_t hookSlot = resolutionMode == RejectMode
? ReactionRecordSlot_Reject
: ReactionRecordSlot_Resolve;
uint32_t hookSlot = resolutionMode == RejectMode
? ReactionRecordSlot_Reject
: ReactionRecordSlot_Resolve;
RootedObject callee(cx, reaction->getFixedSlot(hookSlot).toObjectOrNull());
RootedObject promiseObj(cx, reaction->promise());
if (!RunResolutionFunction(cx, callee, handlerResult, resolutionMode, promiseObj))
@ -1540,7 +1556,7 @@ PromiseResolveBuiltinThenableJob(JSContext* cx, unsigned argc, Value* vp)
if (promise->as<PromiseObject>().state() != JS::PromiseState::Pending)
return true;
return RejectMaybeWrappedPromise(cx, promise, exception);
return RejectPromiseInternal(cx, promise.as<PromiseObject>(), exception);
}
/**
@ -1566,6 +1582,14 @@ EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_,
RootedObject then(cx, CheckedUnwrap(&thenVal.toObject()));
AutoCompartment ac(cx, then);
// Wrap the `promiseToResolve` and `thenable` arguments.
if (!cx->compartment()->wrap(cx, &promiseToResolve))
return false;
MOZ_ASSERT(thenable.isObject());
if (!cx->compartment()->wrap(cx, &thenable))
return false;
HandlePropertyName funName = cx->names().empty;
RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName,
gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
@ -1579,29 +1603,21 @@ EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_,
// work.
// The layout is described in the ThenableJobDataIndices enum.
RootedArrayObject data(cx, NewDenseFullyAllocatedArray(cx, ThenableJobDataLength));
if (!data ||
data->ensureDenseElements(cx, 0, ThenableJobDataLength) != DenseElementResult::Success)
{
if (!data)
return false;
}
// Wrap and set the `promiseToResolve` argument.
if (!cx->compartment()->wrap(cx, &promiseToResolve))
return false;
data->setDenseElement(ThenableJobDataIndex_Promise, promiseToResolve);
// At this point the promise is guaranteed to be wrapped into the job's
// compartment.
RootedObject promise(cx, &promiseToResolve.toObject());
// Wrap and set the `thenable` argument.
MOZ_ASSERT(thenable.isObject());
if (!cx->compartment()->wrap(cx, &thenable))
return false;
data->setDenseElement(ThenableJobDataIndex_Thenable, thenable);
// Set the `promiseToResolve` and `thenable` arguments.
data->setDenseInitializedLength(ThenableJobDataLength);
data->initDenseElement(ThenableJobDataIndex_Promise, promiseToResolve);
data->initDenseElement(ThenableJobDataIndex_Thenable, thenable);
// Store the data array on the reaction job.
job->setExtendedSlot(ThenableJobSlot_JobData, ObjectValue(*data));
// At this point the promise is guaranteed to be wrapped into the job's
// compartment.
RootedObject promise(cx, &promiseToResolve.toObject());
RootedObject incumbentGlobal(cx, cx->runtime()->getIncumbentGlobal(cx));
return cx->runtime()->enqueuePromiseJob(cx, job, promise, incumbentGlobal);
}
@ -1771,7 +1787,7 @@ PromiseConstructor(JSContext* cx, unsigned argc, Value* vp)
return false;
// Step 2.
RootedValue executorVal(cx, args.get(0));
HandleValue executorVal = args.get(0);
if (!IsCallable(executorVal))
return ReportIsNotFunction(cx, executorVal);
RootedObject executor(cx, &executorVal.toObject());
@ -1809,13 +1825,16 @@ PromiseConstructor(JSContext* cx, unsigned argc, Value* vp)
// Promises, with the unprivileged one resolved with the resolution of the
// privileged one.
if (IsWrapper(newTarget)) {
newTarget = CheckedUnwrap(newTarget);
MOZ_ASSERT(newTarget);
MOZ_ASSERT(newTarget != originalNewTarget);
JSObject* unwrappedNewTarget = CheckedUnwrap(newTarget);
MOZ_ASSERT(unwrappedNewTarget);
MOZ_ASSERT(unwrappedNewTarget != newTarget);
newTarget = unwrappedNewTarget;
{
AutoCompartment ac(cx, newTarget);
RootedObject promiseCtor(cx);
if (!GetBuiltinConstructor(cx, JSProto_Promise, &promiseCtor))
Handle<GlobalObject*> global = cx->global();
JSFunction* promiseCtor = GlobalObject::getOrCreatePromiseConstructor(cx, global);
if (!promiseCtor)
return false;
// Promise subclasses don't get the special Xray treatment, so
@ -1857,8 +1876,10 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /
if (needsWrapping) {
MOZ_ASSERT(proto);
usedProto = CheckedUnwrap(proto);
if (!usedProto)
if (!usedProto) {
/* ReportAccessDenied(cx); */
return nullptr;
}
}
@ -1944,10 +1965,10 @@ static bool
Promise_static_all(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedValue iterable(cx, args.get(0));
HandleValue iterable = args.get(0);
// Step 2 (reordered).
RootedValue CVal(cx, args.thisv());
HandleValue CVal = args.thisv();
if (!CVal.isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
"Receiver of Promise.all call");
@ -2098,8 +2119,7 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises)
RootedNativeObject valuesArray(cx, NewDenseFullyAllocatedArray(cx, promiseCount));
if (!valuesArray)
return nullptr;
if (valuesArray->ensureDenseElements(cx, 0, promiseCount) != DenseElementResult::Success)
return nullptr;
valuesArray->ensureDenseInitializedLength(cx, 0, promiseCount);
// Sub-step 4.
// Create our data holder that holds all the things shared across
@ -2112,7 +2132,6 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises)
resolve));
if (!dataHolder)
return nullptr;
RootedValue dataHolderVal(cx, ObjectValue(*dataHolder));
// Sub-step 5 (inline in loop-header below).
@ -2137,7 +2156,8 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises)
return nullptr;
// Steps k-o.
resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal);
resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data,
ObjectValue(*dataHolder));
resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex,
Int32Value(index));
@ -2205,7 +2225,7 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res
if (!promiseObj)
return true;
Rooted<PromiseObject*> promise(cx, &promiseObj->as<PromiseObject>());
Handle<PromiseObject*> promise = promiseObj.as<PromiseObject>();
if (promise->state() != JS::PromiseState::Pending)
return true;
@ -2215,7 +2235,7 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res
if (mode == ResolveMode)
return ResolvePromiseInternal(cx, promise, result);
return RejectMaybeWrappedPromise(cx, promiseObj, result);
return RejectPromiseInternal(cx, promise, result);
}
// ES2016, 25.4.4.1.1.
@ -2226,12 +2246,6 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
{
*done = false;
RootedObject unwrappedPromiseObj(cx);
if (IsWrapper(promiseObj)) {
unwrappedPromiseObj = CheckedUnwrap(promiseObj);
MOZ_ASSERT(unwrappedPromiseObj);
}
// Step 1.
MOZ_ASSERT(C->isConstructor());
RootedValue CVal(cx, ObjectValue(*C));
@ -2259,8 +2273,11 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
// canonical "then" function and content can't see our
// PromiseAllResolveElement.
RootedObject valuesArray(cx);
if (unwrappedPromiseObj) {
JSAutoCompartment ac(cx, unwrappedPromiseObj);
if (IsWrapper(promiseObj)) {
JSObject* unwrappedPromiseObj = CheckedUnwrap(promiseObj);
MOZ_ASSERT(unwrappedPromiseObj);
AutoCompartment ac(cx, unwrappedPromiseObj);
valuesArray = NewDenseFullyAllocatedArray(cx, 0);
} else {
valuesArray = NewDenseFullyAllocatedArray(cx, 0);
@ -2281,15 +2298,17 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
valuesArrayVal, resolve));
if (!dataHolder)
return false;
RootedValue dataHolderVal(cx, ObjectValue(*dataHolder));
// Step 5.
uint32_t index = 0;
// Step 6.
RootedValue nextValue(cx);
RootedValue nextPromise(cx);
RootedId indexId(cx);
RootedValue rejectFunVal(cx, ObjectValue(*reject));
RootedValue resolveFunVal(cx);
RootedValue staticResolve(cx);
while (true) {
// Steps a-c, e-g.
@ -2319,10 +2338,10 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
}
// Step h.
{ // Scope for the JSAutoCompartment we need to work with valuesArray. We
{ // Scope for the AutoCompartment we need to work with valuesArray. We
// mostly do this for performance; we could go ahead and do the define via
// a cross-compartment proxy instead...
JSAutoCompartment ac(cx, valuesArray);
AutoCompartment ac(cx, valuesArray);
indexId = INT_TO_JSID(index);
if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue))
return false;
@ -2331,9 +2350,7 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
// Step i.
// Sadly, because someone could have overridden
// "resolve" on the canonical Promise constructor.
RootedValue nextPromise(cx);
RootedValue staticResolve(cx);
if (!GetProperty(cx, C, CVal, cx->names().resolve, &staticResolve))
if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve))
return false;
FixedInvokeArgs<1> resolveArgs(cx);
@ -2342,15 +2359,15 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
return false;
// Step j.
RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllResolveElementFunction,
1, nullptr,
gc::AllocKind::FUNCTION_EXTENDED,
GenericObject));
JSFunction* resolveFunc = NewNativeFunction(cx, PromiseAllResolveElementFunction, 1,
nullptr,gc::AllocKind::FUNCTION_EXTENDED,
GenericObject);
if (!resolveFunc)
return false;
// Steps k,m,n.
resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal);
resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data,
ObjectValue(*dataHolder));
// Step l.
resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex,
@ -2360,7 +2377,7 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
dataHolder->increaseRemainingCount();
// Step q.
RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
resolveFunVal.setObject(*resolveFunc);
if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal, true))
return false;
@ -2490,11 +2507,11 @@ PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedFunction resolve(cx, &args.callee().as<JSFunction>());
JSFunction* resolve = &args.callee().as<JSFunction>();
RootedValue xVal(cx, args.get(0));
// Step 1.
RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data));
const Value& dataVal = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data);
// Step 2.
// We use the existence of the data holder as a signal for whether the
@ -2517,24 +2534,27 @@ PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp)
// Step 5.
RootedValue valuesVal(cx, data->valuesArray());
RootedObject valuesObj(cx, &valuesVal.toObject());
bool valuesListIsWrapped = false;
if (IsWrapper(valuesObj)) {
valuesListIsWrapped = true;
if (IsProxy(valuesObj)) {
// See comment for PerformPromiseAll, step 3 for why we unwrap here.
valuesObj = UncheckedUnwrap(valuesObj);
if (JS_IsDeadWrapper(valuesObj)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
return false;
}
AutoCompartment ac(cx, valuesObj);
if (!cx->compartment()->wrap(cx, &xVal))
return false;
}
RootedNativeObject values(cx, &valuesObj->as<NativeObject>());
HandleNativeObject values = valuesObj.as<NativeObject>();
// Step 6 (moved under step 10).
// Step 7 (moved to step 9).
// Step 8.
// The index is guaranteed to be initialized to `undefined`.
if (valuesListIsWrapped) {
AutoCompartment ac(cx, values);
if (!cx->compartment()->wrap(cx, &xVal))
return false;
}
MOZ_ASSERT(values->getDenseElement(index).isUndefined());
values->setDenseElement(index, xVal);
// Steps 7,9.
@ -2581,20 +2601,23 @@ PromiseAllSettledResolveElementFunction(JSContext* cx, unsigned argc, Value* vp)
RootedValue valuesVal(cx, data->valuesArray());
RootedObject valuesObj(cx, &valuesVal.toObject());
bool valuesListIsWrapped = false;
if (IsWrapper(valuesObj)) {
valuesListIsWrapped = true;
if (IsProxy(valuesObj)) {
// See comment for PerformPromiseAll, step 3 for why we unwrap here.
valuesObj = UncheckedUnwrap(valuesObj);
if (JS_IsDeadWrapper(valuesObj)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
return false;
}
AutoCompartment ac(cx, valuesObj);
if (!cx->compartment()->wrap(cx, &xVal))
return false;
}
NativeObject* values = &valuesObj->as<NativeObject>();
// The index is guaranteed to be initialized to `undefined`.
if (valuesListIsWrapped) {
AutoCompartment ac(cx, values);
if (!cx->compartment()->wrap(cx, &xVal))
return false;
}
MOZ_ASSERT(values->getDenseElement(index).isUndefined());
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!obj) {
@ -2656,20 +2679,23 @@ PromiseAllSettledRejectElementFunction(JSContext* cx, unsigned argc, Value* vp)
RootedValue valuesVal(cx, data->valuesArray());
RootedObject valuesObj(cx, &valuesVal.toObject());
bool valuesListIsWrapped = false;
if (IsWrapper(valuesObj)) {
valuesListIsWrapped = true;
if (IsProxy(valuesObj)) {
// See comment for PerformPromiseAll, step 3 for why we unwrap here.
valuesObj = UncheckedUnwrap(valuesObj);
if (JS_IsDeadWrapper(valuesObj)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
return false;
}
AutoCompartment ac(cx, valuesObj);
if (!cx->compartment()->wrap(cx, &xVal))
return false;
}
NativeObject* values = &valuesObj->as<NativeObject>();
// The index is guaranteed to be initialized to `undefined`.
if (valuesListIsWrapped) {
AutoCompartment ac(cx, values);
if (!cx->compartment()->wrap(cx, &xVal))
return false;
}
MOZ_ASSERT(values->getDenseElement(index).isUndefined());
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!obj) {
@ -2720,10 +2746,10 @@ static bool
Promise_static_race(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedValue iterable(cx, args.get(0));
HandleValue iterable = args.get(0);
// Step 2 (reordered).
RootedValue CVal(cx, args.thisv());
HandleValue CVal = args.thisv();
if (!CVal.isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
"Receiver of Promise.race call");
@ -2862,7 +2888,7 @@ CommonStaticResolveRejectImpl(JSContext* cx, HandleValue thisVal, HandleValue ar
// outcome, so instead of unwrapping and then performing the
// GetProperty, just check here and then operate on the original
// object again.
RootedObject unwrappedObject(cx, CheckedUnwrap(xObj));
JSObject* unwrappedObject = CheckedUnwrap(xObj);
if (unwrappedObject && unwrappedObject->is<PromiseObject>())
isPromise = true;
}
@ -2907,8 +2933,8 @@ static bool
Promise_reject(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedValue thisVal(cx, args.thisv());
RootedValue argVal(cx, args.get(0));
HandleValue thisVal = args.thisv();
HandleValue argVal = args.get(0);
JSObject* result = CommonStaticResolveRejectImpl(cx, thisVal, argVal, RejectMode);
if (!result)
return false;
@ -2922,7 +2948,7 @@ Promise_reject(JSContext* cx, unsigned argc, Value* vp)
/* static */ JSObject*
PromiseObject::unforgeableReject(JSContext* cx, HandleValue value)
{
RootedObject promiseCtor(cx, JS::GetPromiseConstructor(cx));
JSObject* promiseCtor = JS::GetPromiseConstructor(cx);
if (!promiseCtor)
return nullptr;
RootedValue cVal(cx, ObjectValue(*promiseCtor));
@ -2936,8 +2962,8 @@ static bool
Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedValue thisVal(cx, args.thisv());
RootedValue argVal(cx, args.get(0));
HandleValue thisVal = args.thisv();
HandleValue argVal = args.get(0);
JSObject* result = CommonStaticResolveRejectImpl(cx, thisVal, argVal, ResolveMode);
if (!result)
return false;
@ -2951,7 +2977,7 @@ Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
/* static */ JSObject*
PromiseObject::unforgeableResolve(JSContext* cx, HandleValue value)
{
RootedObject promiseCtor(cx, JS::GetPromiseConstructor(cx));
JSObject* promiseCtor = JS::GetPromiseConstructor(cx);
if (!promiseCtor)
return nullptr;
RootedValue cVal(cx, ObjectValue(*promiseCtor));
@ -3012,7 +3038,7 @@ NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfi
return nullptr;
}
Rooted<PromiseReactionRecord*> reaction(cx, NewObjectWithClassProto<PromiseReactionRecord>(cx));
PromiseReactionRecord* reaction = NewObjectWithClassProto<PromiseReactionRecord>(cx);
if (!reaction)
return nullptr;
@ -3153,7 +3179,7 @@ js::AsyncFunctionThrown(JSContext* cx, Handle<PromiseObject*> resultPromise)
if (!MaybeGetAndClearException(cx, &exc))
return false;
if (!RejectMaybeWrappedPromise(cx, resultPromise, exc))
if (!RejectPromiseInternal(cx, resultPromise, exc))
return false;
// Step 3.g.
@ -3244,10 +3270,10 @@ bool
js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind completionKind)
{
// Step 1.
RootedValue thisVal(cx, args.thisv());
HandleValue thisVal = args.thisv();
// Step 2.
RootedObject resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
Rooted<PromiseObject*> resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
if (!resultPromise)
return false;
@ -3259,7 +3285,7 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co
return false;
// Step 3.b.
if (!RejectMaybeWrappedPromise(cx, resultPromise, badGeneratorError))
if (!RejectPromiseInternal(cx, resultPromise, badGeneratorError))
return false;
// Step 3.c.
@ -3273,7 +3299,6 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co
// Step 4.
RootedObject iter(cx, asyncIter->iterator());
RootedValue resultVal(cx);
RootedValue func(cx);
if (completionKind == CompletionKind::Normal) {
// 11.1.3.2.1 steps 5-6 (partially).
@ -3287,7 +3312,7 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co
// Step 7.
if (func.isNullOrUndefined()) {
// Step 7.a.
RootedObject resultObj(cx, CreateIterResultObject(cx, args.get(0), true));
JSObject* resultObj = CreateIterResultObject(cx, args.get(0), true);
if (!resultObj)
return AbruptRejectPromise(cx, args, resultPromise, nullptr);
@ -3310,7 +3335,7 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co
// Step 7.
if (func.isNullOrUndefined()) {
// Step 7.a.
if (!RejectMaybeWrappedPromise(cx, resultPromise, args.get(0)))
if (!RejectPromiseInternal(cx, resultPromise, args.get(0)))
return AbruptRejectPromise(cx, args, resultPromise, nullptr);
// Step 7.b.
@ -3324,6 +3349,8 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co
RootedValue iterVal(cx, ObjectValue(*iter));
FixedInvokeArgs<1> args2(cx);
args2[0].set(args.get(0));
RootedValue resultVal(cx);
if (!js::Call(cx, func, iterVal, args2, &resultVal))
return AbruptRejectPromise(cx, args, resultPromise, nullptr);
@ -3428,18 +3455,18 @@ AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenOb
MOZ_ASSERT(!asyncGenObj->isQueueEmpty());
// Step 4.
Rooted<AsyncGeneratorRequest*> request(
cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj));
AsyncGeneratorRequest* request =
AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj);
if (!request)
return false;
// Step 5.
RootedObject resultPromise(cx, request->promise());
Rooted<PromiseObject*> resultPromise(cx, request->promise());
asyncGenObj->cacheRequest(request);
// Step 6.
if (!RejectMaybeWrappedPromise(cx, resultPromise, exception))
if (!RejectPromiseInternal(cx, resultPromise, exception))
return false;
// Steps 7-8.
@ -3455,18 +3482,18 @@ AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenOb
MOZ_ASSERT(!asyncGenObj->isQueueEmpty());
// Step 4.
Rooted<AsyncGeneratorRequest*> request(
cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj));
AsyncGeneratorRequest* request =
AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj);
if (!request)
return false;
// Step 5.
RootedObject resultPromise(cx, request->promise());
Rooted<PromiseObject*> resultPromise(cx, request->promise());
asyncGenObj->cacheRequest(request);
// Step 6.
RootedObject resultObj(cx, CreateIterResultObject(cx, value, done));
JSObject* resultObj = CreateIterResultObject(cx, value, done);
if (!resultObj)
return false;
@ -3594,7 +3621,7 @@ js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal,
// Step 1 (implicit).
// Step 2.
RootedObject resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
Rooted<PromiseObject*> resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
if (!resultPromise)
return false;
@ -3606,7 +3633,7 @@ js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal,
return false;
// Step 3.b.
if (!RejectMaybeWrappedPromise(cx, resultPromise, badGeneratorError))
if (!RejectPromiseInternal(cx, resultPromise, badGeneratorError))
return false;
// Step 3.c.
@ -3716,11 +3743,10 @@ Promise_then_impl(JSContext* cx, HandleValue promiseVal, HandleValue onFulfilled
RootedObject promiseObj(cx, &promiseVal.toObject());
Rooted<PromiseObject*> promise(cx);
bool isPromise = promiseObj->is<PromiseObject>();
if (isPromise) {
if (promiseObj->is<PromiseObject>()) {
promise = &promiseObj->as<PromiseObject>();
} else {
RootedObject unwrappedPromiseObj(cx, CheckedUnwrap(promiseObj));
JSObject* unwrappedPromiseObj = CheckedUnwrap(promiseObj);
if (!unwrappedPromiseObj) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED);
return false;
@ -3934,7 +3960,7 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis
}
// 25.4.5.3., step 5.
Rooted<PromiseObject*> promise(cx, &promiseObj->as<PromiseObject>());
Handle<PromiseObject*> promise = promiseObj.as<PromiseObject>();
if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise,
resolveFun, rejectFun))
{
@ -3993,7 +4019,7 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis
if (!blockedPromise_->is<PromiseObject>())
return true;
Rooted<PromiseObject*> promise(cx, &unwrappedPromiseObj->as<PromiseObject>());
Handle<PromiseObject*> promise = unwrappedPromiseObj.as<PromiseObject>();
return AddDummyPromiseReactionForDebugger(cx, promise, blockedPromise);
}
@ -4017,7 +4043,6 @@ AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise,
// 25.4.5.3.1 steps 7.a,b.
RootedValue reactionsVal(cx, promise->reactions());
RootedNativeObject reactions(cx);
if (reactionsVal.isUndefined()) {
// If no reactions existed so far, just store the reaction record directly.
@ -4042,22 +4067,24 @@ AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise,
if (reactionsObj->is<PromiseReactionRecord>()) {
// If a single reaction existed so far, create a list and store the
// old and the new reaction in it.
reactions = NewDenseFullyAllocatedArray(cx, 2);
ArrayObject* reactions = NewDenseFullyAllocatedArray(cx, 2);
if (!reactions)
return false;
if (reactions->ensureDenseElements(cx, 0, 2) != DenseElementResult::Success)
return false;
reactions->setDenseElement(0, reactionsVal);
reactions->setDenseElement(1, reactionVal);
reactions->setDenseInitializedLength(2);
reactions->initDenseElement(0, reactionsVal);
reactions->initDenseElement(1, reactionVal);
promise->setFixedSlot(PromiseSlot_ReactionsOrResult, ObjectValue(*reactions));
} else {
// Otherwise, just store the new reaction.
reactions = &reactionsObj->as<NativeObject>();
HandleNativeObject reactions = reactionsObj.as<NativeObject>();
uint32_t len = reactions->getDenseInitializedLength();
if (reactions->ensureDenseElements(cx, 0, len + 1) != DenseElementResult::Success)
DenseElementResult result = reactions->ensureDenseElements(cx, len, 1);
if (result != DenseElementResult::Success) {
MOZ_ASSERT(result == DenseElementResult::Failure);
return false;
}
reactions->setDenseElement(len, reactionVal);
}
@ -4137,10 +4164,10 @@ PromiseObject::dependentPromises(JSContext* cx, MutableHandle<GCVector<Value>> v
uint32_t len = reactions->getDenseInitializedLength();
MOZ_ASSERT(len >= 2);
size_t valuesIndex = 0;
Rooted<PromiseReactionRecord*> reaction(cx);
for (size_t i = 0; i < len; i++) {
reaction = &reactions->getDenseElement(i).toObject().as<PromiseReactionRecord>();
uint32_t valuesIndex = 0;
for (uint32_t i = 0; i < len; i++) {
const Value& element = reactions->getDenseElement(i);
PromiseReactionRecord* reaction = &element.toObject().as<PromiseReactionRecord>();
// Not all reactions have a Promise on them.
RootedObject promiseObj(cx, reaction->promise());
@ -4165,7 +4192,7 @@ PromiseObject::resolve(JSContext* cx, Handle<PromiseObject*> promise, HandleValu
if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS))
return ResolvePromiseInternal(cx, promise, resolutionValue);
RootedObject resolveFun(cx, GetResolveFunctionFromPromise(promise));
JSFunction* resolveFun = GetResolveFunctionFromPromise(promise);
if (!resolveFun)
return true;

View file

@ -319,7 +319,7 @@ AsyncGeneratorObject::create(JSContext* cx, HandleFunction asyncGen, HandleValue
/* static */ AsyncGeneratorRequest*
AsyncGeneratorObject::createRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
CompletionKind completionKind, HandleValue completionValue,
HandleObject promise)
Handle<PromiseObject*> promise)
{
if (!asyncGenObj->hasCachedRequest())
return AsyncGeneratorRequest::create(cx, completionKind, completionValue, promise);
@ -444,7 +444,7 @@ const Class AsyncGeneratorRequest::class_ = {
// Async Iteration proposal 11.4.3.1.
/* static */ AsyncGeneratorRequest*
AsyncGeneratorRequest::create(JSContext* cx, CompletionKind completionKind,
HandleValue completionValue, HandleObject promise)
HandleValue completionValue, Handle<PromiseObject*> promise)
{
RootedObject obj(cx, NewNativeObjectWithGivenProto(cx, &class_, nullptr));
if (!obj)

View file

@ -62,8 +62,7 @@ class AsyncGeneratorRequest : public NativeObject
Slots,
};
void init(CompletionKind completionKind, HandleValue completionValue,
HandleObject promise) {
void init(CompletionKind completionKind, const Value& completionValue, PromiseObject* promise) {
setFixedSlot(Slot_CompletionKind,
Int32Value(static_cast<int32_t>(completionKind)));
setFixedSlot(Slot_CompletionValue, completionValue);
@ -81,7 +80,8 @@ class AsyncGeneratorRequest : public NativeObject
static const Class class_;
static AsyncGeneratorRequest* create(JSContext* cx, CompletionKind completionKind,
HandleValue completionValue, HandleObject promise);
HandleValue completionValue,
Handle<PromiseObject*> promise);
CompletionKind completionKind() const {
return static_cast<CompletionKind>(getFixedSlot(Slot_CompletionKind).toInt32());
@ -89,8 +89,8 @@ class AsyncGeneratorRequest : public NativeObject
JS::Value completionValue() const {
return getFixedSlot(Slot_CompletionValue);
}
JSObject* promise() const {
return &getFixedSlot(Slot_Promise).toObject();
PromiseObject* promise() const {
return &getFixedSlot(Slot_Promise).toObject().as<PromiseObject>();
}
};
@ -232,7 +232,7 @@ class AsyncGeneratorObject : public NativeObject
Handle<AsyncGeneratorObject*> asyncGenObj,
CompletionKind completionKind,
HandleValue completionValue,
HandleObject promise);
Handle<PromiseObject*> promise);
// Stores the given request to the generator's cache after clearing its data
// slots. The cached request will be reused in the subsequent createRequest