Issue #2089 - Further runtime perf improvements for PromiseState

- Reduce slot size of PromiseReactionRecord to fit into OBJECT8 alloc kind
- Use the Call() helper from Interpreter.h to reduce code duplication
- Clear the incumbent global slot when it's no longer used

Based-on: m-c 1475678/{12-14}
This commit is contained in:
Martok 2023-01-27 00:09:15 +01:00 • committed by roytam1
commit 078daa705a

View file

@ -471,8 +471,14 @@ enum ReactionRecordSlots {
// the |PromiseHandler| enum, or null. If the value is null, either the // the |PromiseHandler| enum, or null. If the value is null, either the
// REACTION_FLAG_DEBUGGER_DUMMY or the // REACTION_FLAG_DEBUGGER_DUMMY or the
// REACTION_FLAG_DEFAULT_RESOLVING_HANDLER flag must be set. // REACTION_FLAG_DEFAULT_RESOLVING_HANDLER flag must be set.
//
// After setting the target state for a PromiseReaction, the slot of the
// no longer used handler gets reused to store the argument of the active
// handler.
ReactionRecordSlot_OnFulfilled, ReactionRecordSlot_OnFulfilled,
ReactionRecordSlot_OnRejectedArg = ReactionRecordSlot_OnFulfilled,
ReactionRecordSlot_OnRejected, ReactionRecordSlot_OnRejected,
ReactionRecordSlot_OnFulfilledArg = ReactionRecordSlot_OnRejected,
// The functions to resolve or reject the promise. Matches the // The functions to resolve or reject the promise. Matches the
// [[Capability]].[[Resolve]] and [[Capability]].[[Reject]] fields from // [[Capability]].[[Resolve]] and [[Capability]].[[Reject]] fields from
@ -490,9 +496,6 @@ enum ReactionRecordSlots {
// Bitmask of the REACTION_FLAG values. // Bitmask of the REACTION_FLAG values.
ReactionRecordSlot_Flags, ReactionRecordSlot_Flags,
// Argument when calling the function stored in OnFulfilled or OnRejected.
ReactionRecordSlot_HandlerArg,
// Additional slot to store extra data for specific reaction record types. // Additional slot to store extra data for specific reaction record types.
// //
// - When the REACTION_FLAG_ASYNC_GENERATOR flag is set, this slot store // - When the REACTION_FLAG_ASYNC_GENERATOR flag is set, this slot store
@ -522,6 +525,20 @@ class PromiseReactionRecord : public NativeObject
setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
} }
uint32_t handlerSlot() {
MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
return targetState() == JS::PromiseState::Fulfilled
? ReactionRecordSlot_OnFulfilled
: ReactionRecordSlot_OnRejected;
}
uint32_t handlerArgSlot() {
MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
return targetState() == JS::PromiseState::Fulfilled
? ReactionRecordSlot_OnFulfilledArg
: ReactionRecordSlot_OnRejectedArg;
}
public: public:
static const Class class_; static const Class class_;
@ -535,14 +552,17 @@ class PromiseReactionRecord : public NativeObject
? JS::PromiseState::Fulfilled ? JS::PromiseState::Fulfilled
: JS::PromiseState::Rejected; : JS::PromiseState::Rejected;
} }
void setTargetState(JS::PromiseState state) { void setTargetStateAndHandlerArg(JS::PromiseState state, const Value& arg) {
int32_t flags = this->flags(); MOZ_ASSERT(targetState() == JS::PromiseState::Pending);
MOZ_ASSERT(!(flags & REACTION_FLAG_RESOLVED));
MOZ_ASSERT(state != JS::PromiseState::Pending, "Can't revert a reaction to pending."); MOZ_ASSERT(state != JS::PromiseState::Pending, "Can't revert a reaction to pending.");
int32_t flags = this->flags();
flags |= REACTION_FLAG_RESOLVED; flags |= REACTION_FLAG_RESOLVED;
if (state == JS::PromiseState::Fulfilled) if (state == JS::PromiseState::Fulfilled)
flags |= REACTION_FLAG_FULFILLED; flags |= REACTION_FLAG_FULFILLED;
setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
setFixedSlot(handlerArgSlot(), arg);
} }
void setIsDefaultResolvingHandler(PromiseObject* promiseToResolve) { void setIsDefaultResolvingHandler(PromiseObject* promiseToResolve) {
setFlagOnInitialState(REACTION_FLAG_DEFAULT_RESOLVING_HANDLER); setFlagOnInitialState(REACTION_FLAG_DEFAULT_RESOLVING_HANDLER);
@ -586,21 +606,16 @@ class PromiseReactionRecord : public NativeObject
} }
Value handler() { Value handler() {
MOZ_ASSERT(targetState() != JS::PromiseState::Pending); MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
uint32_t slot = targetState() == JS::PromiseState::Fulfilled return getFixedSlot(handlerSlot());
? ReactionRecordSlot_OnFulfilled
: ReactionRecordSlot_OnRejected;
return getFixedSlot(slot);
} }
Value handlerArg() { Value handlerArg() {
MOZ_ASSERT(targetState() != JS::PromiseState::Pending); MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
return getFixedSlot(ReactionRecordSlot_HandlerArg); return getFixedSlot(handlerArgSlot());
} }
void setHandlerArg(Value& arg) { JSObject* getAndClearIncumbentGlobalObject() {
MOZ_ASSERT(targetState() == JS::PromiseState::Pending); JSObject* obj = getFixedSlot(ReactionRecordSlot_IncumbentGlobalObject).toObjectOrNull();
setFixedSlot(ReactionRecordSlot_HandlerArg, arg); setFixedSlot(ReactionRecordSlot_IncumbentGlobalObject, UndefinedValue());
} return obj;
JSObject* incumbentGlobalObject() {
return getFixedSlot(ReactionRecordSlot_IncumbentGlobalObject).toObjectOrNull();
} }
}; };
@ -908,11 +923,9 @@ EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj,
MOZ_ASSERT(reaction->targetState() == JS::PromiseState::Pending); MOZ_ASSERT(reaction->targetState() == JS::PromiseState::Pending);
assertSameCompartment(cx, handlerArg); assertSameCompartment(cx, handlerArg);
reaction->setHandlerArg(handlerArg.get()); reaction->setTargetStateAndHandlerArg(targetState, handlerArg);
RootedValue reactionVal(cx, ObjectValue(*reaction)); RootedValue reactionVal(cx, ObjectValue(*reaction));
reaction->setTargetState(targetState);
RootedValue handler(cx, reaction->handler()); RootedValue handler(cx, reaction->handler());
// If we have a handler callback, we enter that handler's compartment so // If we have a handler callback, we enter that handler's compartment so
@ -968,7 +981,7 @@ EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj,
// much better than having to store the original global as a private value // 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. // because we couldn't wrap it to store it as a normal JS value.
RootedObject global(cx); RootedObject global(cx);
if (JSObject* objectFromIncumbentGlobal = reaction->incumbentGlobalObject()) { if (JSObject* objectFromIncumbentGlobal = reaction->getAndClearIncumbentGlobalObject()) {
objectFromIncumbentGlobal = CheckedUnwrap(objectFromIncumbentGlobal); objectFromIncumbentGlobal = CheckedUnwrap(objectFromIncumbentGlobal);
MOZ_ASSERT(objectFromIncumbentGlobal); MOZ_ASSERT(objectFromIncumbentGlobal);
global = &objectFromIncumbentGlobal->global(); global = &objectFromIncumbentGlobal->global();
@ -1502,9 +1515,7 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp)
MOZ_ASSERT(IsCallable(handlerVal)); MOZ_ASSERT(IsCallable(handlerVal));
// Step 6. // Step 6.
FixedInvokeArgs<1> args2(cx); if (!Call(cx, handlerVal, UndefinedHandleValue, argument, &handlerResult)) {
args2[0].set(argument);
if (!Call(cx, handlerVal, UndefinedHandleValue, args2, &handlerResult)) {
resolutionMode = RejectMode; resolutionMode = RejectMode;
if (!MaybeGetAndClearException(cx, &handlerResult)) if (!MaybeGetAndClearException(cx, &handlerResult))
return false; return false;
@ -1573,11 +1584,8 @@ PromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp)
if (!MaybeGetAndClearException(cx, &rval)) if (!MaybeGetAndClearException(cx, &rval))
return false; return false;
FixedInvokeArgs<1> rejectArgs(cx);
rejectArgs[0].set(rval);
RootedValue rejectVal(cx, ObjectValue(*rejectFn)); RootedValue rejectVal(cx, ObjectValue(*rejectFn));
return Call(cx, rejectVal, UndefinedHandleValue, rejectArgs, &rval); return Call(cx, rejectVal, UndefinedHandleValue, rval, &rval);
} }
static MOZ_MUST_USE bool static MOZ_MUST_USE bool
@ -1989,7 +1997,6 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /
bool success; bool success;
{ {
FixedInvokeArgs<2> args(cx); FixedInvokeArgs<2> args(cx);
args[0].setObject(*resolveFn); args[0].setObject(*resolveFn);
args[1].setObject(*rejectFn); args[1].setObject(*rejectFn);
@ -2003,12 +2010,8 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /
if (!MaybeGetAndClearException(cx, &exceptionVal)) if (!MaybeGetAndClearException(cx, &exceptionVal))
return nullptr; return nullptr;
FixedInvokeArgs<1> args(cx);
args[0].set(exceptionVal);
RootedValue calleeOrRval(cx, ObjectValue(*rejectFn)); RootedValue calleeOrRval(cx, ObjectValue(*rejectFn));
if (!Call(cx, calleeOrRval, UndefinedHandleValue, args, &calleeOrRval)) if (!Call(cx, calleeOrRval, UndefinedHandleValue, exceptionVal, &calleeOrRval))
return nullptr; return nullptr;
} }
@ -2293,9 +2296,7 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res
assertSameCompartment(cx, promiseObj); assertSameCompartment(cx, promiseObj);
if (resolutionFun) { if (resolutionFun) {
RootedValue calleeOrRval(cx, ObjectValue(*resolutionFun)); RootedValue calleeOrRval(cx, ObjectValue(*resolutionFun));
FixedInvokeArgs<1> resolveArgs(cx); return Call(cx, calleeOrRval, UndefinedHandleValue, result, &calleeOrRval);
resolveArgs[0].set(result);
return Call(cx, calleeOrRval, UndefinedHandleValue, resolveArgs, &calleeOrRval);
} }
if (!promiseObj) if (!promiseObj)
@ -3516,11 +3517,8 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co
// 11.1.3.2.1 steps 5-6 (partially). // 11.1.3.2.1 steps 5-6 (partially).
// 11.1.3.2.2, 11.1.3.2.3 steps 8-9. // 11.1.3.2.2, 11.1.3.2.3 steps 8-9.
RootedValue iterVal(cx, ObjectValue(*iter)); RootedValue iterVal(cx, ObjectValue(*iter));
FixedInvokeArgs<1> args2(cx);
args2[0].set(args.get(0));
RootedValue resultVal(cx); RootedValue resultVal(cx);
if (!js::Call(cx, func, iterVal, args2, &resultVal)) if (!Call(cx, func, iterVal, args.get(0), &resultVal))
return AbruptRejectPromise(cx, args, resultPromise, nullptr); return AbruptRejectPromise(cx, args, resultPromise, nullptr);
// 11.1.3.2.1 steps 5-6 (partially). // 11.1.3.2.1 steps 5-6 (partially).
@ -4245,11 +4243,8 @@ PromiseObject::resolve(JSContext* cx, Handle<PromiseObject*> promise, HandleValu
if (!cx->compartment()->wrap(cx, &funVal)) if (!cx->compartment()->wrap(cx, &funVal))
return false; return false;
FixedInvokeArgs<1> args(cx);
args[0].set(resolutionValue);
RootedValue dummy(cx); RootedValue dummy(cx);
return Call(cx, funVal, UndefinedHandleValue, args, &dummy); return Call(cx, funVal, UndefinedHandleValue, resolutionValue, &dummy);
} }
/* static */ bool /* static */ bool
@ -4265,11 +4260,8 @@ PromiseObject::reject(JSContext* cx, Handle<PromiseObject*> promise, HandleValue
RootedValue funVal(cx, promise->getFixedSlot(PromiseSlot_RejectFunction)); RootedValue funVal(cx, promise->getFixedSlot(PromiseSlot_RejectFunction));
MOZ_ASSERT(IsCallable(funVal)); MOZ_ASSERT(IsCallable(funVal));
FixedInvokeArgs<1> args(cx);
args[0].set(rejectionValue);
RootedValue dummy(cx); RootedValue dummy(cx);
return Call(cx, funVal, UndefinedHandleValue, args, &dummy); return Call(cx, funVal, UndefinedHandleValue, rejectionValue, &dummy);
} }
/* static */ void /* static */ void