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:
Till Schneidereit 2017-08-20 00:32:41 +02:00
Родитель 718ce2a817
Коммит d56216e4ce
2 изменённых файлов: 180 добавлений и 42 удалений

Просмотреть файл

@ -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);