зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1342050 - Shrink Promise instances from 8 to 4 slots by moving debug information to an external object. r=arai
The debug info object is only allocated if, when the Promise is created, either async stacks are enabled or the Promise is created in a debuggee compartment. MozReview-Commit-ID: 2Ct6QkSeNmA
This commit is contained in:
Родитель
718ce2a817
Коммит
d56216e4ce
|
@ -155,6 +155,172 @@ NewPromiseAllDataHolder(JSContext* cx, HandleObject resultPromise, HandleValue v
|
|||
return dataHolder;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Generator used by PromiseObject::getID.
|
||||
mozilla::Atomic<uint64_t> gIDGenerator(0);
|
||||
} // namespace
|
||||
|
||||
static MOZ_ALWAYS_INLINE bool
|
||||
ShouldCaptureDebugInfo(JSContext* cx)
|
||||
{
|
||||
return cx->options().asyncStack() || cx->compartment()->isDebuggee();
|
||||
}
|
||||
|
||||
class PromiseDebugInfo : public NativeObject
|
||||
{
|
||||
private:
|
||||
enum Slots {
|
||||
Slot_AllocationSite,
|
||||
Slot_ResolutionSite,
|
||||
Slot_AllocationTime,
|
||||
Slot_ResolutionTime,
|
||||
Slot_Id,
|
||||
SlotCount
|
||||
};
|
||||
|
||||
public:
|
||||
static const Class class_;
|
||||
static PromiseDebugInfo* create(JSContext* cx, Handle<PromiseObject*> promise) {
|
||||
Rooted<PromiseDebugInfo*> debugInfo(cx, NewObjectWithClassProto<PromiseDebugInfo>(cx));
|
||||
if (!debugInfo)
|
||||
return nullptr;
|
||||
|
||||
RootedObject stack(cx);
|
||||
if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames())))
|
||||
return nullptr;
|
||||
debugInfo->setFixedSlot(Slot_AllocationSite, ObjectOrNullValue(stack));
|
||||
debugInfo->setFixedSlot(Slot_ResolutionSite, NullValue());
|
||||
debugInfo->setFixedSlot(Slot_AllocationTime, DoubleValue(MillisecondsSinceStartup()));
|
||||
debugInfo->setFixedSlot(Slot_ResolutionTime, NumberValue(0));
|
||||
promise->setFixedSlot(PromiseSlot_DebugInfo, ObjectValue(*debugInfo));
|
||||
|
||||
return debugInfo;
|
||||
}
|
||||
|
||||
static PromiseDebugInfo* FromPromise(PromiseObject* promise) {
|
||||
Value val = promise->getFixedSlot(PromiseSlot_DebugInfo);
|
||||
if (val.isObject())
|
||||
return &val.toObject().as<PromiseDebugInfo>();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given PromiseObject's process-unique ID.
|
||||
* The ID is lazily assigned when first queried, and then either stored
|
||||
* in the DebugInfo slot if no debug info was recorded for this Promise,
|
||||
* or in the Id slot of the DebugInfo object.
|
||||
*/
|
||||
static uint64_t id(PromiseObject* promise) {
|
||||
Value idVal(promise->getFixedSlot(PromiseSlot_DebugInfo));
|
||||
if (idVal.isUndefined()) {
|
||||
idVal.setDouble(++gIDGenerator);
|
||||
promise->setFixedSlot(PromiseSlot_DebugInfo, idVal);
|
||||
} else if (idVal.isObject()) {
|
||||
PromiseDebugInfo* debugInfo = FromPromise(promise);
|
||||
idVal = debugInfo->getFixedSlot(Slot_Id);
|
||||
if (idVal.isUndefined()) {
|
||||
idVal.setDouble(++gIDGenerator);
|
||||
debugInfo->setFixedSlot(Slot_Id, idVal);
|
||||
}
|
||||
}
|
||||
return uint64_t(idVal.toNumber());
|
||||
}
|
||||
|
||||
double allocationTime() { return getFixedSlot(Slot_AllocationTime).toNumber(); }
|
||||
double resolutionTime() { return getFixedSlot(Slot_ResolutionTime).toNumber(); }
|
||||
JSObject* allocationSite() { return getFixedSlot(Slot_AllocationSite).toObjectOrNull(); }
|
||||
JSObject* resolutionSite() { return getFixedSlot(Slot_ResolutionSite).toObjectOrNull(); }
|
||||
|
||||
static void setResolutionInfo(JSContext* cx, Handle<PromiseObject*> promise) {
|
||||
if (!ShouldCaptureDebugInfo(cx))
|
||||
return;
|
||||
|
||||
// If async stacks weren't enabled and the Promise's global wasn't a
|
||||
// debuggee when the Promise was created, we won't have a debugInfo
|
||||
// object. We still want to capture the resolution stack, so we
|
||||
// create the object now and change it's slots' values around a bit.
|
||||
Rooted<PromiseDebugInfo*> debugInfo(cx, FromPromise(promise));
|
||||
if (!debugInfo) {
|
||||
RootedValue idVal(cx, promise->getFixedSlot(PromiseSlot_DebugInfo));
|
||||
debugInfo = create(cx, promise);
|
||||
if (!debugInfo) {
|
||||
cx->clearPendingException();
|
||||
return;
|
||||
}
|
||||
|
||||
// The current stack was stored in the AllocationSite slot, move
|
||||
// it to ResolutionSite as that's what it really is.
|
||||
debugInfo->setFixedSlot(Slot_ResolutionSite,
|
||||
debugInfo->getFixedSlot(Slot_AllocationSite));
|
||||
debugInfo->setFixedSlot(Slot_AllocationSite, NullValue());
|
||||
|
||||
// There's no good default for a missing AllocationTime, so
|
||||
// instead of resetting that, ensure that it's the same as
|
||||
// ResolutionTime, so that the diff shows as 0, which isn't great,
|
||||
// but bearable.
|
||||
debugInfo->setFixedSlot(Slot_ResolutionTime,
|
||||
debugInfo->getFixedSlot(Slot_AllocationTime));
|
||||
|
||||
// The Promise's ID might've been queried earlier, in which case
|
||||
// it's stored in the DebugInfo slot. We saved that earlier, so
|
||||
// now we can store it in the right place (or leave it as
|
||||
// undefined if it wasn't ever initialized.)
|
||||
debugInfo->setFixedSlot(Slot_Id, idVal);
|
||||
return;
|
||||
}
|
||||
|
||||
RootedObject stack(cx);
|
||||
if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames()))) {
|
||||
cx->clearPendingException();
|
||||
return;
|
||||
}
|
||||
|
||||
debugInfo->setFixedSlot(Slot_ResolutionSite, ObjectOrNullValue(stack));
|
||||
debugInfo->setFixedSlot(Slot_ResolutionTime, DoubleValue(MillisecondsSinceStartup()));
|
||||
}
|
||||
};
|
||||
|
||||
const Class PromiseDebugInfo::class_ = {
|
||||
"PromiseDebugInfo",
|
||||
JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
|
||||
};
|
||||
|
||||
double
|
||||
PromiseObject::allocationTime()
|
||||
{
|
||||
auto debugInfo = PromiseDebugInfo::FromPromise(this);
|
||||
if (debugInfo)
|
||||
return debugInfo->allocationTime();
|
||||
return 0;
|
||||
}
|
||||
|
||||
double
|
||||
PromiseObject::resolutionTime()
|
||||
{
|
||||
auto debugInfo = PromiseDebugInfo::FromPromise(this);
|
||||
if (debugInfo)
|
||||
return debugInfo->resolutionTime();
|
||||
return 0;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
PromiseObject::allocationSite()
|
||||
{
|
||||
auto debugInfo = PromiseDebugInfo::FromPromise(this);
|
||||
if (debugInfo)
|
||||
return debugInfo->allocationSite();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
PromiseObject::resolutionSite()
|
||||
{
|
||||
auto debugInfo = PromiseDebugInfo::FromPromise(this);
|
||||
if (debugInfo)
|
||||
return debugInfo->resolutionSite();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for GetAndClearException that handles cases where no exception is
|
||||
* pending, but an error occurred. This can be the case if an OOM was
|
||||
|
@ -1290,13 +1456,11 @@ CreatePromiseObjectInternal(JSContext* cx, HandleObject proto /* = nullptr */,
|
|||
// Store an allocation stack so we can later figure out what the
|
||||
// control flow was for some unexpected results. Frightfully expensive,
|
||||
// but oh well.
|
||||
RootedObject stack(cx);
|
||||
if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) {
|
||||
if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames())))
|
||||
if (ShouldCaptureDebugInfo(cx)) {
|
||||
PromiseDebugInfo* debugInfo = PromiseDebugInfo::create(cx, promise);
|
||||
if (!debugInfo)
|
||||
return nullptr;
|
||||
}
|
||||
promise->setFixedSlot(PromiseSlot_AllocationSite, ObjectOrNullValue(stack));
|
||||
promise->setFixedSlot(PromiseSlot_AllocationTime, DoubleValue(MillisecondsSinceStartup()));
|
||||
|
||||
// Let the Debugger know about this Promise.
|
||||
if (informDebugger)
|
||||
|
@ -3061,10 +3225,11 @@ AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise, HandleValue on
|
|||
return AddPromiseReaction(cx, promise, reaction);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Generator used by PromiseObject::getID.
|
||||
mozilla::Atomic<uint64_t> gIDGenerator(0);
|
||||
} // namespace
|
||||
uint64_t
|
||||
PromiseObject::getID()
|
||||
{
|
||||
return PromiseDebugInfo::id(this);
|
||||
}
|
||||
|
||||
double
|
||||
PromiseObject::lifetime()
|
||||
|
@ -3072,17 +3237,6 @@ PromiseObject::lifetime()
|
|||
return MillisecondsSinceStartup() - allocationTime();
|
||||
}
|
||||
|
||||
uint64_t
|
||||
PromiseObject::getID()
|
||||
{
|
||||
Value idVal(getFixedSlot(PromiseSlot_Id));
|
||||
if (idVal.isUndefined()) {
|
||||
idVal.setDouble(++gIDGenerator);
|
||||
setFixedSlot(PromiseSlot_Id, idVal);
|
||||
}
|
||||
return uint64_t(idVal.toNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all promises that directly depend on this one. That means those
|
||||
* created by calling `then` on this promise, or the promise returned by
|
||||
|
@ -3192,15 +3346,7 @@ PromiseObject::reject(JSContext* cx, Handle<PromiseObject*> promise, HandleValue
|
|||
/* static */ void
|
||||
PromiseObject::onSettled(JSContext* cx, Handle<PromiseObject*> promise)
|
||||
{
|
||||
RootedObject stack(cx);
|
||||
if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) {
|
||||
if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames()))) {
|
||||
cx->clearPendingException();
|
||||
return;
|
||||
}
|
||||
}
|
||||
promise->setFixedSlot(PromiseSlot_ResolutionSite, ObjectOrNullValue(stack));
|
||||
promise->setFixedSlot(PromiseSlot_ResolutionTime, DoubleValue(MillisecondsSinceStartup()));
|
||||
PromiseDebugInfo::setResolutionInfo(cx, promise);
|
||||
|
||||
if (promise->state() == JS::PromiseState::Rejected && promise->isUnhandled())
|
||||
cx->runtime()->addUnhandledRejectedPromise(cx, promise);
|
||||
|
|
|
@ -19,11 +19,7 @@ enum PromiseSlots {
|
|||
PromiseSlot_ReactionsOrResult,
|
||||
PromiseSlot_RejectFunction,
|
||||
PromiseSlot_AwaitGenerator = PromiseSlot_RejectFunction,
|
||||
PromiseSlot_AllocationSite,
|
||||
PromiseSlot_ResolutionSite,
|
||||
PromiseSlot_AllocationTime,
|
||||
PromiseSlot_ResolutionTime,
|
||||
PromiseSlot_Id,
|
||||
PromiseSlot_DebugInfo,
|
||||
PromiseSlots,
|
||||
};
|
||||
|
||||
|
@ -77,14 +73,10 @@ class PromiseObject : public NativeObject
|
|||
|
||||
static void onSettled(JSContext* cx, Handle<PromiseObject*> promise);
|
||||
|
||||
double allocationTime() { return getFixedSlot(PromiseSlot_AllocationTime).toNumber(); }
|
||||
double resolutionTime() { return getFixedSlot(PromiseSlot_ResolutionTime).toNumber(); }
|
||||
JSObject* allocationSite() {
|
||||
return getFixedSlot(PromiseSlot_AllocationSite).toObjectOrNull();
|
||||
}
|
||||
JSObject* resolutionSite() {
|
||||
return getFixedSlot(PromiseSlot_ResolutionSite).toObjectOrNull();
|
||||
}
|
||||
double allocationTime();
|
||||
double resolutionTime();
|
||||
JSObject* allocationSite();
|
||||
JSObject* resolutionSite();
|
||||
double lifetime();
|
||||
double timeToResolution() {
|
||||
MOZ_ASSERT(state() != JS::PromiseState::Pending);
|
||||
|
|
Загрузка…
Ссылка в новой задаче