mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 12:25:53 +00:00
Bug 1028418 - Part 5: Minimize stack walking when capturing SavedFrame stacks with a cache; r=shu
This commit is contained in:
parent
871954ed90
commit
c014d4c6dc
@ -609,6 +609,10 @@ class DispatchWrapper
|
||||
public:
|
||||
// Mimic a pointer type, so that we can drop into Rooted.
|
||||
MOZ_IMPLICIT DispatchWrapper(const T& initial) : tracer(&T::trace), storage(initial) {}
|
||||
MOZ_IMPLICIT DispatchWrapper(T&& initial)
|
||||
: tracer(&T::trace),
|
||||
storage(mozilla::Forward<T>(initial))
|
||||
{ }
|
||||
T* operator &() { return &storage; }
|
||||
const T* operator &() const { return &storage; }
|
||||
operator T&() { return storage; }
|
||||
|
@ -20,6 +20,7 @@ class ArrayObject;
|
||||
class GlobalObject;
|
||||
class PlainObject;
|
||||
class ScriptSourceObject;
|
||||
class SavedFrame;
|
||||
class Shape;
|
||||
class ObjectGroup;
|
||||
|
||||
@ -33,12 +34,14 @@ typedef JS::Handle<JSLinearString*> HandleLinearString;
|
||||
typedef JS::Handle<PropertyName*> HandlePropertyName;
|
||||
typedef JS::Handle<ArrayObject*> HandleArrayObject;
|
||||
typedef JS::Handle<PlainObject*> HandlePlainObject;
|
||||
typedef JS::Handle<SavedFrame*> HandleSavedFrame;
|
||||
typedef JS::Handle<ScriptSourceObject*> HandleScriptSource;
|
||||
|
||||
typedef JS::MutableHandle<Shape*> MutableHandleShape;
|
||||
typedef JS::MutableHandle<JSAtom*> MutableHandleAtom;
|
||||
typedef JS::MutableHandle<NativeObject*> MutableHandleNativeObject;
|
||||
typedef JS::MutableHandle<PlainObject*> MutableHandlePlainObject;
|
||||
typedef JS::MutableHandle<SavedFrame*> MutableHandleSavedFrame;
|
||||
|
||||
typedef JS::Rooted<NativeObject*> RootedNativeObject;
|
||||
typedef JS::Rooted<Shape*> RootedShape;
|
||||
@ -49,6 +52,7 @@ typedef JS::Rooted<PropertyName*> RootedPropertyName;
|
||||
typedef JS::Rooted<ArrayObject*> RootedArrayObject;
|
||||
typedef JS::Rooted<GlobalObject*> RootedGlobalObject;
|
||||
typedef JS::Rooted<PlainObject*> RootedPlainObject;
|
||||
typedef JS::Rooted<SavedFrame*> RootedSavedFrame;
|
||||
typedef JS::Rooted<ScriptSourceObject*> RootedScriptSource;
|
||||
|
||||
} /* namespace js */
|
||||
|
@ -2609,6 +2609,7 @@ GCRuntime::updatePointersToRelocatedCells(Zone* zone)
|
||||
Debugger::markIncomingCrossCompartmentEdges(&trc);
|
||||
|
||||
for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
|
||||
c->trace(&trc);
|
||||
WeakMapBase::markAll(c, &trc);
|
||||
if (c->watchpointMap)
|
||||
c->watchpointMap->markAll(&trc);
|
||||
|
124
js/src/vm/SavedFrame.h
Normal file
124
js/src/vm/SavedFrame.h
Normal file
@ -0,0 +1,124 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef vm_SavedFrame_h
|
||||
#define vm_SavedFrame_h
|
||||
|
||||
namespace js {
|
||||
|
||||
class SavedFrame : public NativeObject {
|
||||
friend class SavedStacks;
|
||||
|
||||
public:
|
||||
static const Class class_;
|
||||
static const JSPropertySpec protoAccessors[];
|
||||
static const JSFunctionSpec protoFunctions[];
|
||||
static const JSFunctionSpec staticFunctions[];
|
||||
|
||||
// Prototype methods and properties to be exposed to JS.
|
||||
static bool construct(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool sourceProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool lineProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool columnProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool functionDisplayNameProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool asyncCauseProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool asyncParentProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool parentProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool toStringMethod(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
static void finalize(FreeOp* fop, JSObject* obj);
|
||||
|
||||
// Convenient getters for SavedFrame's reserved slots for use from C++.
|
||||
JSAtom* getSource();
|
||||
uint32_t getLine();
|
||||
uint32_t getColumn();
|
||||
JSAtom* getFunctionDisplayName();
|
||||
JSAtom* getAsyncCause();
|
||||
SavedFrame* getParent();
|
||||
JSPrincipals* getPrincipals();
|
||||
|
||||
bool isSelfHosted();
|
||||
|
||||
static bool isSavedFrameAndNotProto(JSObject& obj) {
|
||||
return obj.is<SavedFrame>() &&
|
||||
!obj.as<SavedFrame>().getReservedSlot(JSSLOT_SOURCE).isNull();
|
||||
}
|
||||
|
||||
struct Lookup;
|
||||
struct HashPolicy;
|
||||
|
||||
typedef HashSet<js::ReadBarriered<SavedFrame*>,
|
||||
HashPolicy,
|
||||
SystemAllocPolicy> Set;
|
||||
|
||||
class AutoLookupVector;
|
||||
|
||||
class MOZ_STACK_CLASS HandleLookup {
|
||||
friend class AutoLookupVector;
|
||||
|
||||
Lookup& lookup;
|
||||
|
||||
explicit HandleLookup(Lookup& lookup) : lookup(lookup) { }
|
||||
|
||||
public:
|
||||
inline Lookup& get() { return lookup; }
|
||||
inline Lookup* operator->() { return &lookup; }
|
||||
};
|
||||
|
||||
private:
|
||||
static bool finishSavedFrameInit(JSContext* cx, HandleObject ctor, HandleObject proto);
|
||||
void initFromLookup(HandleLookup lookup);
|
||||
|
||||
enum {
|
||||
// The reserved slots in the SavedFrame class.
|
||||
JSSLOT_SOURCE,
|
||||
JSSLOT_LINE,
|
||||
JSSLOT_COLUMN,
|
||||
JSSLOT_FUNCTIONDISPLAYNAME,
|
||||
JSSLOT_ASYNCCAUSE,
|
||||
JSSLOT_PARENT,
|
||||
JSSLOT_PRINCIPALS,
|
||||
JSSLOT_PRIVATE_PARENT,
|
||||
|
||||
// The total number of reserved slots in the SavedFrame class.
|
||||
JSSLOT_COUNT
|
||||
};
|
||||
|
||||
// Because we hash the parent pointer, we need to rekey a saved frame
|
||||
// whenever its parent was relocated by the GC. However, the GC doesn't
|
||||
// notify us when this occurs. As a work around, we keep a duplicate copy of
|
||||
// the parent pointer as a private value in a reserved slot. Whenever the
|
||||
// private value parent pointer doesn't match the regular parent pointer, we
|
||||
// know that GC moved the parent and we need to update our private value and
|
||||
// rekey the saved frame in its hash set. These two methods are helpers for
|
||||
// this process.
|
||||
bool parentMoved();
|
||||
void updatePrivateParent();
|
||||
|
||||
static bool checkThis(JSContext* cx, CallArgs& args, const char* fnName,
|
||||
MutableHandleObject frame);
|
||||
};
|
||||
|
||||
struct SavedFrame::HashPolicy
|
||||
{
|
||||
typedef SavedFrame::Lookup Lookup;
|
||||
typedef PointerHasher<SavedFrame*, 3> SavedFramePtrHasher;
|
||||
typedef PointerHasher<JSPrincipals*, 3> JSPrincipalsPtrHasher;
|
||||
|
||||
static HashNumber hash(const Lookup& lookup);
|
||||
static bool match(SavedFrame* existing, const Lookup& lookup);
|
||||
|
||||
typedef ReadBarriered<SavedFrame*> Key;
|
||||
static void rekey(Key& key, const Key& newKey);
|
||||
};
|
||||
|
||||
// Assert that if the given object is not null, that it must be either a
|
||||
// SavedFrame object or wrapper (Xray or CCW) around a SavedFrame object.
|
||||
inline void AssertObjectIsSavedFrameOrWrapper(JSContext* cx, HandleObject stack);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // vm_SavedFrame_h
|
@ -8,13 +8,12 @@
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Move.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "jscntxt.h"
|
||||
#include "jscompartment.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "jshashutil.h"
|
||||
@ -33,11 +32,15 @@
|
||||
#include "jscntxtinlines.h"
|
||||
|
||||
#include "vm/NativeObject-inl.h"
|
||||
#include "vm/Stack-inl.h"
|
||||
|
||||
using mozilla::AddToHash;
|
||||
using mozilla::DebugOnly;
|
||||
using mozilla::HashString;
|
||||
using mozilla::Maybe;
|
||||
using mozilla::Move;
|
||||
using mozilla::Nothing;
|
||||
using mozilla::Some;
|
||||
|
||||
namespace js {
|
||||
|
||||
@ -46,19 +49,110 @@ namespace js {
|
||||
*/
|
||||
const unsigned ASYNC_STACK_MAX_FRAME_COUNT = 60;
|
||||
|
||||
/* static */ Maybe<LiveSavedFrameCache::FramePtr>
|
||||
LiveSavedFrameCache::getFramePtr(FrameIter& iter)
|
||||
{
|
||||
if (iter.hasUsableAbstractFramePtr())
|
||||
return Some(FramePtr(iter.abstractFramePtr()));
|
||||
|
||||
if (iter.isPhysicalIonFrame())
|
||||
return Some(FramePtr(iter.physicalIonFrame()));
|
||||
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
LiveSavedFrameCache::trace(LiveSavedFrameCache* cache, JSTracer* trc)
|
||||
{
|
||||
if (!cache->initialized())
|
||||
return;
|
||||
|
||||
for (auto* entry = cache->frames->begin(); entry < cache->frames->end(); entry++) {
|
||||
TraceEdge(trc,
|
||||
&entry->savedFrame,
|
||||
"LiveSavedFrameCache::frames SavedFrame");
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
LiveSavedFrameCache::insert(JSContext* cx, FramePtr& framePtr, jsbytecode* pc,
|
||||
HandleSavedFrame savedFrame)
|
||||
{
|
||||
MOZ_ASSERT(initialized());
|
||||
|
||||
if (!frames->emplaceBack(framePtr, pc, savedFrame)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Safe to dereference the cache key because the stack frames are still
|
||||
// live. After this point, they should never be dereferenced again.
|
||||
if (framePtr.is<AbstractFramePtr>())
|
||||
framePtr.as<AbstractFramePtr>().setHasCachedSavedFrame();
|
||||
else
|
||||
framePtr.as<jit::CommonFrameLayout*>()->setHasCachedSavedFrame();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
LiveSavedFrameCache::find(JSContext* cx, FrameIter& frameIter, MutableHandleSavedFrame frame) const
|
||||
{
|
||||
MOZ_ASSERT(initialized());
|
||||
MOZ_ASSERT(!frameIter.done());
|
||||
MOZ_ASSERT(frameIter.hasCachedSavedFrame());
|
||||
|
||||
Maybe<FramePtr> maybeFramePtr = getFramePtr(frameIter);
|
||||
MOZ_ASSERT(maybeFramePtr.isSome());
|
||||
|
||||
FramePtr framePtr(*maybeFramePtr);
|
||||
jsbytecode* pc = frameIter.pc();
|
||||
size_t numberStillValid = 0;
|
||||
|
||||
frame.set(nullptr);
|
||||
for (auto* p = frames->begin(); p < frames->end(); p++) {
|
||||
numberStillValid++;
|
||||
if (framePtr == p->framePtr && pc == p->pc) {
|
||||
frame.set(p->savedFrame);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!frame) {
|
||||
frames->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(0 < numberStillValid && numberStillValid <= frames->length());
|
||||
|
||||
if (frame->compartment() != cx->compartment()) {
|
||||
frame.set(nullptr);
|
||||
numberStillValid--;
|
||||
}
|
||||
|
||||
// Everything after the cached SavedFrame are stale younger frames we have
|
||||
// since popped.
|
||||
frames->shrinkBy(frames->length() - numberStillValid);
|
||||
}
|
||||
|
||||
struct SavedFrame::Lookup {
|
||||
Lookup(JSAtom* source, uint32_t line, uint32_t column,
|
||||
JSAtom* functionDisplayName, JSAtom* asyncCause, SavedFrame* parent,
|
||||
JSPrincipals* principals)
|
||||
JSPrincipals* principals, Maybe<LiveSavedFrameCache::FramePtr> framePtr, jsbytecode* pc,
|
||||
Activation* activation)
|
||||
: source(source),
|
||||
line(line),
|
||||
column(column),
|
||||
functionDisplayName(functionDisplayName),
|
||||
asyncCause(asyncCause),
|
||||
parent(parent),
|
||||
principals(principals)
|
||||
principals(principals),
|
||||
framePtr(framePtr),
|
||||
pc(pc),
|
||||
activation(activation)
|
||||
{
|
||||
MOZ_ASSERT(source);
|
||||
MOZ_ASSERT(activation);
|
||||
}
|
||||
|
||||
explicit Lookup(SavedFrame& savedFrame)
|
||||
@ -68,19 +162,28 @@ struct SavedFrame::Lookup {
|
||||
functionDisplayName(savedFrame.getFunctionDisplayName()),
|
||||
asyncCause(savedFrame.getAsyncCause()),
|
||||
parent(savedFrame.getParent()),
|
||||
principals(savedFrame.getPrincipals())
|
||||
principals(savedFrame.getPrincipals()),
|
||||
framePtr(Nothing()),
|
||||
pc(nullptr),
|
||||
activation(nullptr)
|
||||
{
|
||||
MOZ_ASSERT(source);
|
||||
}
|
||||
|
||||
JSAtom* source;
|
||||
uint32_t line;
|
||||
uint32_t column;
|
||||
JSAtom* functionDisplayName;
|
||||
JSAtom* asyncCause;
|
||||
SavedFrame* parent;
|
||||
JSAtom* source;
|
||||
uint32_t line;
|
||||
uint32_t column;
|
||||
JSAtom* functionDisplayName;
|
||||
JSAtom* asyncCause;
|
||||
SavedFrame* parent;
|
||||
JSPrincipals* principals;
|
||||
|
||||
// These are used only by the LiveSavedFrameCache and not used for identity or
|
||||
// hashing.
|
||||
Maybe<LiveSavedFrameCache::FramePtr> framePtr;
|
||||
jsbytecode* pc;
|
||||
Activation* activation;
|
||||
|
||||
void trace(JSTracer* trc) {
|
||||
TraceManuallyBarrieredEdge(trc, &source, "SavedFrame::Lookup::source");
|
||||
if (functionDisplayName) {
|
||||
@ -399,8 +502,7 @@ SavedFrame::checkThis(JSContext* cx, CallArgs& args, const char* fnName,
|
||||
const Value& thisValue = args.thisv();
|
||||
|
||||
if (!thisValue.isObject()) {
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
|
||||
InformalValueTypeName(thisValue));
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, InformalValueTypeName(thisValue));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -814,7 +916,7 @@ SavedStacks::saveCurrentStack(JSContext* cx, MutableHandleSavedFrame frame, unsi
|
||||
MOZ_ASSERT(initialized());
|
||||
assertSameCompartment(cx, this);
|
||||
|
||||
if (creatingSavedFrame) {
|
||||
if (creatingSavedFrame || cx->isExceptionPending()) {
|
||||
frame.set(nullptr);
|
||||
return true;
|
||||
}
|
||||
@ -855,13 +957,12 @@ SavedStacks::sweep(JSRuntime* rt)
|
||||
void
|
||||
SavedStacks::trace(JSTracer* trc)
|
||||
{
|
||||
if (!pcLocationMap.initialized())
|
||||
return;
|
||||
|
||||
// Mark each of the source strings in our pc to location cache.
|
||||
for (PCLocationMap::Enum e(pcLocationMap); !e.empty(); e.popFront()) {
|
||||
LocationValue& loc = e.front().value();
|
||||
TraceEdge(trc, &loc.source, "SavedStacks::PCLocationMap's memoized script source name");
|
||||
if (pcLocationMap.initialized()) {
|
||||
// Mark each of the source strings in our pc to location cache.
|
||||
for (PCLocationMap::Enum e(pcLocationMap); !e.empty(); e.popFront()) {
|
||||
LocationValue& loc = e.front().value();
|
||||
TraceEdge(trc, &loc.source, "SavedStacks::PCLocationMap's memoized script source name");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -906,6 +1007,8 @@ SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFram
|
||||
Activation* asyncActivation = nullptr;
|
||||
RootedSavedFrame asyncStack(cx, nullptr);
|
||||
RootedString asyncCause(cx, nullptr);
|
||||
bool parentIsInCache = false;
|
||||
RootedSavedFrame cachedFrame(cx, nullptr);
|
||||
|
||||
// Accumulate the vector of Lookup objects in |stackChain|.
|
||||
SavedFrame::AutoLookupVector stackChain(cx);
|
||||
@ -943,6 +1046,11 @@ SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFram
|
||||
return false;
|
||||
}
|
||||
|
||||
// The bit set means that the next older parent (frame, pc) pair *must*
|
||||
// be in the cache.
|
||||
if (maxFrameCount == 0)
|
||||
parentIsInCache = iter.hasCachedSavedFrame();
|
||||
|
||||
auto displayAtom = iter.isNonEvalFunctionFrame() ? iter.functionDisplayAtom() : nullptr;
|
||||
if (!stackChain->emplaceBack(location->source,
|
||||
location->line,
|
||||
@ -950,7 +1058,10 @@ SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFram
|
||||
displayAtom,
|
||||
nullptr,
|
||||
nullptr,
|
||||
iter.compartment()->principals()))
|
||||
iter.compartment()->principals(),
|
||||
LiveSavedFrameCache::getFramePtr(iter),
|
||||
iter.pc(),
|
||||
&activation))
|
||||
{
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
@ -958,10 +1069,6 @@ SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFram
|
||||
|
||||
++iter;
|
||||
|
||||
// If maxFrameCount is zero there's no limit on the number of frames.
|
||||
if (maxFrameCount == 0)
|
||||
continue;
|
||||
|
||||
if (maxFrameCount == 1) {
|
||||
// The frame we just saved was the last one we were asked to save.
|
||||
// If we had an async stack, ensure we don't use any of its frames.
|
||||
@ -969,13 +1076,29 @@ SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFram
|
||||
break;
|
||||
}
|
||||
|
||||
if (parentIsInCache &&
|
||||
!iter.done() &&
|
||||
iter.hasCachedSavedFrame())
|
||||
{
|
||||
auto* cache = activation.getLiveSavedFrameCache(cx);
|
||||
if (!cache)
|
||||
return false;
|
||||
cache->find(cx, iter, &cachedFrame);
|
||||
if (cachedFrame)
|
||||
break;
|
||||
}
|
||||
|
||||
// If maxFrameCount is zero there's no limit on the number of frames.
|
||||
if (maxFrameCount == 0)
|
||||
continue;
|
||||
|
||||
maxFrameCount--;
|
||||
}
|
||||
|
||||
// Limit the depth of the async stack, if any, and ensure that the
|
||||
// SavedFrame instances we use are stored in the same compartment as the
|
||||
// rest of the synchronous stack chain.
|
||||
RootedSavedFrame parentFrame(cx, nullptr);
|
||||
RootedSavedFrame parentFrame(cx, cachedFrame);
|
||||
if (asyncStack && !adoptAsyncStack(cx, asyncStack, asyncCause, &parentFrame, maxFrameCount))
|
||||
return false;
|
||||
|
||||
@ -987,6 +1110,12 @@ SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFram
|
||||
parentFrame.set(getOrCreateSavedFrame(cx, lookup));
|
||||
if (!parentFrame)
|
||||
return false;
|
||||
|
||||
if (maxFrameCount == 0 && lookup->framePtr && parentFrame != cachedFrame) {
|
||||
auto* cache = lookup->activation->getLiveSavedFrameCache(cx);
|
||||
if (!cache || !cache->insert(cx, *lookup->framePtr, lookup->pc, parentFrame))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
frame.set(parentFrame);
|
||||
|
@ -11,8 +11,11 @@
|
||||
#include "jsmath.h"
|
||||
#include "jswrapper.h"
|
||||
#include "js/HashTable.h"
|
||||
#include "vm/SavedFrame.h"
|
||||
#include "vm/Stack.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
// # Saved Stacks
|
||||
//
|
||||
// The `SavedStacks` class provides a compact way to capture and save JS stacks
|
||||
@ -142,123 +145,6 @@
|
||||
// because the cx's current compartment's principals do not subsume A's captured
|
||||
// principals.
|
||||
|
||||
|
||||
namespace js {
|
||||
|
||||
class SavedFrame;
|
||||
typedef JS::Handle<SavedFrame*> HandleSavedFrame;
|
||||
typedef JS::MutableHandle<SavedFrame*> MutableHandleSavedFrame;
|
||||
typedef JS::Rooted<SavedFrame*> RootedSavedFrame;
|
||||
|
||||
class SavedFrame : public NativeObject {
|
||||
friend class SavedStacks;
|
||||
|
||||
public:
|
||||
static const Class class_;
|
||||
static void finalize(FreeOp* fop, JSObject* obj);
|
||||
static const JSPropertySpec protoAccessors[];
|
||||
static const JSFunctionSpec protoFunctions[];
|
||||
static const JSFunctionSpec staticFunctions[];
|
||||
|
||||
// Prototype methods and properties to be exposed to JS.
|
||||
static bool construct(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool sourceProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool lineProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool columnProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool functionDisplayNameProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool asyncCauseProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool asyncParentProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool parentProperty(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool toStringMethod(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
// Convenient getters for SavedFrame's reserved slots for use from C++.
|
||||
JSAtom* getSource();
|
||||
uint32_t getLine();
|
||||
uint32_t getColumn();
|
||||
JSAtom* getFunctionDisplayName();
|
||||
JSAtom* getAsyncCause();
|
||||
SavedFrame* getParent();
|
||||
JSPrincipals* getPrincipals();
|
||||
|
||||
bool isSelfHosted();
|
||||
|
||||
static bool isSavedFrameAndNotProto(JSObject& obj) {
|
||||
return obj.is<SavedFrame>() &&
|
||||
!obj.as<SavedFrame>().getReservedSlot(JSSLOT_SOURCE).isNull();
|
||||
}
|
||||
|
||||
struct Lookup;
|
||||
struct HashPolicy;
|
||||
|
||||
typedef HashSet<js::ReadBarriered<SavedFrame*>,
|
||||
HashPolicy,
|
||||
SystemAllocPolicy> Set;
|
||||
|
||||
class AutoLookupVector;
|
||||
|
||||
class MOZ_STACK_CLASS HandleLookup {
|
||||
friend class AutoLookupVector;
|
||||
|
||||
Lookup& lookup;
|
||||
|
||||
explicit HandleLookup(Lookup& lookup) : lookup(lookup) { }
|
||||
|
||||
public:
|
||||
inline Lookup& get() { return lookup; }
|
||||
inline Lookup* operator->() { return &lookup; }
|
||||
};
|
||||
|
||||
private:
|
||||
static bool finishSavedFrameInit(JSContext* cx, HandleObject ctor, HandleObject proto);
|
||||
void initFromLookup(HandleLookup lookup);
|
||||
|
||||
enum {
|
||||
// The reserved slots in the SavedFrame class.
|
||||
JSSLOT_SOURCE,
|
||||
JSSLOT_LINE,
|
||||
JSSLOT_COLUMN,
|
||||
JSSLOT_FUNCTIONDISPLAYNAME,
|
||||
JSSLOT_ASYNCCAUSE,
|
||||
JSSLOT_PARENT,
|
||||
JSSLOT_PRINCIPALS,
|
||||
JSSLOT_PRIVATE_PARENT,
|
||||
|
||||
// The total number of reserved slots in the SavedFrame class.
|
||||
JSSLOT_COUNT
|
||||
};
|
||||
|
||||
// Because we hash the parent pointer, we need to rekey a saved frame
|
||||
// whenever its parent was relocated by the GC. However, the GC doesn't
|
||||
// notify us when this occurs. As a work around, we keep a duplicate copy of
|
||||
// the parent pointer as a private value in a reserved slot. Whenever the
|
||||
// private value parent pointer doesn't match the regular parent pointer, we
|
||||
// know that GC moved the parent and we need to update our private value and
|
||||
// rekey the saved frame in its hash set. These two methods are helpers for
|
||||
// this process.
|
||||
bool parentMoved();
|
||||
void updatePrivateParent();
|
||||
|
||||
static bool checkThis(JSContext* cx, CallArgs& args, const char* fnName,
|
||||
MutableHandleObject frame);
|
||||
};
|
||||
|
||||
struct SavedFrame::HashPolicy
|
||||
{
|
||||
typedef SavedFrame::Lookup Lookup;
|
||||
typedef PointerHasher<SavedFrame*, 3> SavedFramePtrHasher;
|
||||
typedef PointerHasher<JSPrincipals*, 3> JSPrincipalsPtrHasher;
|
||||
|
||||
static HashNumber hash(const Lookup& lookup);
|
||||
static bool match(SavedFrame* existing, const Lookup& lookup);
|
||||
|
||||
typedef ReadBarriered<SavedFrame*> Key;
|
||||
static void rekey(Key& key, const Key& newKey);
|
||||
};
|
||||
|
||||
// Assert that if the given object is not null, that it must be either a
|
||||
// SavedFrame object or wrapper (Xray or CCW) around a SavedFrame object.
|
||||
inline void AssertObjectIsSavedFrameOrWrapper(JSContext* cx, HandleObject stack);
|
||||
|
||||
class SavedStacks {
|
||||
friend JSObject* SavedStacksMetadataCallback(JSContext* cx, JSObject* target);
|
||||
|
||||
@ -309,15 +195,15 @@ class SavedStacks {
|
||||
}
|
||||
};
|
||||
|
||||
bool insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFrame frame,
|
||||
unsigned maxFrameCount = 0);
|
||||
bool adoptAsyncStack(JSContext* cx, HandleSavedFrame asyncStack,
|
||||
HandleString asyncCause,
|
||||
MutableHandleSavedFrame adoptedStack,
|
||||
unsigned maxFrameCount);
|
||||
bool insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFrame frame,
|
||||
unsigned maxFrameCount = 0);
|
||||
bool adoptAsyncStack(JSContext* cx, HandleSavedFrame asyncStack,
|
||||
HandleString asyncCause,
|
||||
MutableHandleSavedFrame adoptedStack,
|
||||
unsigned maxFrameCount);
|
||||
SavedFrame* getOrCreateSavedFrame(JSContext* cx, SavedFrame::HandleLookup lookup);
|
||||
SavedFrame* createFrameFromLookup(JSContext* cx, SavedFrame::HandleLookup lookup);
|
||||
void chooseSamplingProbability(JSContext* cx);
|
||||
void chooseSamplingProbability(JSContext* cx);
|
||||
|
||||
// Cache for memoizing PCToLineNumber lookups.
|
||||
|
||||
|
@ -887,6 +887,7 @@ Activation::Activation(JSContext* cx, Kind kind)
|
||||
prevProfiling_(prev_ ? prev_->mostRecentProfiling() : nullptr),
|
||||
savedFrameChain_(0),
|
||||
hideScriptedCallerCount_(0),
|
||||
frameCache_(cx),
|
||||
asyncStack_(cx, cx->runtime_->asyncStackForNewActivations),
|
||||
asyncCause_(cx, cx->runtime_->asyncCauseForNewActivations),
|
||||
asyncCallIsExplicit_(cx->runtime_->asyncCallIsExplicit),
|
||||
@ -933,6 +934,13 @@ Activation::mostRecentProfiling()
|
||||
return prevProfiling_;
|
||||
}
|
||||
|
||||
inline LiveSavedFrameCache*
|
||||
Activation::getLiveSavedFrameCache(JSContext* cx) {
|
||||
if (!frameCache_.get().initialized() && !frameCache_.get().init(cx))
|
||||
return nullptr;
|
||||
return frameCache_.address();
|
||||
}
|
||||
|
||||
InterpreterActivation::InterpreterActivation(RunState& state, JSContext* cx,
|
||||
InterpreterFrame* entryFrame)
|
||||
: Activation(cx, Interpreter),
|
||||
@ -1010,6 +1018,36 @@ AsmJSActivation::cx()
|
||||
return cx_->asJSContext();
|
||||
}
|
||||
|
||||
inline bool
|
||||
FrameIter::hasCachedSavedFrame() const
|
||||
{
|
||||
if (isAsmJS())
|
||||
return false;
|
||||
|
||||
if (hasUsableAbstractFramePtr())
|
||||
return abstractFramePtr().hasCachedSavedFrame();
|
||||
|
||||
MOZ_ASSERT(data_.jitFrames_.isIonScripted());
|
||||
// SavedFrame caching is done at the physical frame granularity (rather than
|
||||
// for each inlined frame) for ion. Therefore, it is impossible to have a
|
||||
// cached SavedFrame if this frame is not a physical frame.
|
||||
return isPhysicalIonFrame() && data_.jitFrames_.current()->hasCachedSavedFrame();
|
||||
}
|
||||
|
||||
inline void
|
||||
FrameIter::setHasCachedSavedFrame()
|
||||
{
|
||||
MOZ_ASSERT(!isAsmJS());
|
||||
|
||||
if (hasUsableAbstractFramePtr()) {
|
||||
abstractFramePtr().setHasCachedSavedFrame();
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(isPhysicalIonFrame());
|
||||
data_.jitFrames_.current()->setHasCachedSavedFrame();
|
||||
}
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* vm_Stack_inl_h */
|
||||
|
@ -471,15 +471,6 @@ InterpreterStack::pushExecuteFrame(JSContext* cx, HandleScript script, const Val
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
bool
|
||||
FrameIter::hasCachedSavedFrame(JSContext* cx, bool* hasCachedSavedFramep)
|
||||
{
|
||||
if (isIon() && !ensureHasRematerializedFrame(cx))
|
||||
return false;
|
||||
*hasCachedSavedFramep = abstractFramePtr().hasCachedSavedFrame();
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
FrameIter::popActivation()
|
||||
{
|
||||
|
@ -8,17 +8,22 @@
|
||||
#define vm_Stack_h
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/Variant.h"
|
||||
|
||||
#include "jsfun.h"
|
||||
#include "jsscript.h"
|
||||
#include "jsutil.h"
|
||||
|
||||
#include "asmjs/AsmJSFrameIterator.h"
|
||||
#include "gc/Rooting.h"
|
||||
#include "jit/JitFrameIterator.h"
|
||||
#ifdef CHECK_OSIPOINT_REGISTERS
|
||||
#include "jit/Registers.h" // for RegisterDump
|
||||
#endif
|
||||
#include "js/RootingAPI.h"
|
||||
#include "vm/SavedFrame.h"
|
||||
|
||||
struct JSCompartment;
|
||||
|
||||
@ -34,6 +39,7 @@ class ArgumentsObject;
|
||||
class AsmJSModule;
|
||||
class InterpreterRegs;
|
||||
class CallObject;
|
||||
class FrameIter;
|
||||
class ScopeObject;
|
||||
class ScriptFrameIter;
|
||||
class SPSProfiler;
|
||||
@ -44,6 +50,10 @@ class ScopeCoordinate;
|
||||
|
||||
class SavedFrame;
|
||||
|
||||
namespace jit {
|
||||
class CommonFrameLayout;
|
||||
}
|
||||
|
||||
// VM stack layout
|
||||
//
|
||||
// A JSRuntime's stack consists of a linked list of activations. Every activation
|
||||
@ -1108,6 +1118,128 @@ struct DefaultHasher<AbstractFramePtr> {
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
// SavedFrame caching to minimize stack walking.
|
||||
//
|
||||
// SavedFrames are hash consed to minimize expensive (with regards to both space
|
||||
// and time) allocations in the face of many stack frames that tend to share the
|
||||
// same older tail frames. Despite that, in scenarios where we are frequently
|
||||
// saving the same or similar stacks, such as when the Debugger's allocation
|
||||
// site tracking is enabled, these older stack frames still get walked
|
||||
// repeatedly just to create the lookup structs to find their corresponding
|
||||
// SavedFrames in the hash table. This stack walking is slow, and we would like
|
||||
// to minimize it.
|
||||
//
|
||||
// We have reserved a bit on most of SpiderMonkey's various frame
|
||||
// representations (the exceptions being asm and inlined ion frames). As we
|
||||
// create SavedFrame objects for live stack frames in SavedStacks::insertFrames,
|
||||
// we set this bit and append the SavedFrame object to the cache. As we walk the
|
||||
// stack, if we encounter a frame that has this bit set, that indicates that we
|
||||
// have already captured a SavedFrame object for the given stack frame (but not
|
||||
// necessarily the current pc) during a previous call to insertFrames. We know
|
||||
// that the frame's parent was also captured and has its bit set as well, but
|
||||
// additionally we know the parent was captured at its current pc. For the
|
||||
// parent, rather than continuing the expensive stack walk, we do a quick and
|
||||
// cache-friendly linear search through the frame cache. Upon finishing search
|
||||
// through the frame cache, stale entries are removed.
|
||||
//
|
||||
// The frame cache maintains the invariant that its first E[0] .. E[j-1]
|
||||
// entries are live and sorted from oldest to younger frames, where 0 < j < n
|
||||
// and n = the length of the cache. When searching the cache, we require
|
||||
// that we are considering the youngest live frame whose bit is set. Every
|
||||
// cache entry E[i] where i >= j is a stale entry. Consider the following
|
||||
// scenario:
|
||||
//
|
||||
// P > Q > R > S Initial stack, bits not set.
|
||||
// P* > Q* > R* > S* Capture a SavedFrame stack, set bits.
|
||||
// P* > Q* > R* Return from S.
|
||||
// P* > Q* Return from R.
|
||||
// P* > Q* > T Call T, its bit is not set.
|
||||
//
|
||||
// The frame cache was populated with [P, Q, R, S] when we captured a
|
||||
// SavedFrame stack, but because we returned from frames R and S, their
|
||||
// entries in the frame cache are now stale. This fact is unbeknownst to us
|
||||
// because we do not observe frame pops. Upon capturing a second stack, we
|
||||
// start stack walking at the youngest frame T, which does not have its bit
|
||||
// set and must take the hash table lookup slow path rather than the frame
|
||||
// cache short circuit. Next we proceed to Q and find that it has its bit
|
||||
// set, and it is therefore the youngest live frame with its bit set. We
|
||||
// search through the frame cache from oldest to youngest and find the cache
|
||||
// entry matching Q. We know that T is the next younger live frame from Q
|
||||
// and that T does not have an entry in the frame cache because its bit was
|
||||
// not set. Therefore, we have found entry E[j-1] and the subsequent entries
|
||||
// are stale and should be purged from the frame cache.
|
||||
//
|
||||
// We have a LiveSavedFrameCache for each activation to minimize the number of
|
||||
// entries that must be scanned through, and to avoid the headaches of
|
||||
// maintaining a cache for each compartment and invalidating stale cache entries
|
||||
// in the presence of cross-compartment calls.
|
||||
class LiveSavedFrameCache : public JS::StaticTraceable
|
||||
{
|
||||
public:
|
||||
using FramePtr = mozilla::Variant<AbstractFramePtr, jit::CommonFrameLayout*>;
|
||||
|
||||
private:
|
||||
struct Entry
|
||||
{
|
||||
FramePtr framePtr;
|
||||
jsbytecode* pc;
|
||||
RelocatablePtr<SavedFrame*> savedFrame;
|
||||
|
||||
Entry(FramePtr& framePtr, jsbytecode* pc, SavedFrame* savedFrame)
|
||||
: framePtr(framePtr)
|
||||
, pc(pc)
|
||||
, savedFrame(savedFrame)
|
||||
{ }
|
||||
};
|
||||
|
||||
using EntryVector = Vector<Entry, 0, SystemAllocPolicy>;
|
||||
EntryVector* frames;
|
||||
|
||||
LiveSavedFrameCache(const LiveSavedFrameCache&) = delete;
|
||||
LiveSavedFrameCache& operator=(const LiveSavedFrameCache&) = delete;
|
||||
|
||||
public:
|
||||
explicit LiveSavedFrameCache() : frames(nullptr) { }
|
||||
|
||||
LiveSavedFrameCache(LiveSavedFrameCache&& rhs)
|
||||
: frames(rhs.frames)
|
||||
{
|
||||
MOZ_ASSERT(this != &rhs, "self-move disallowed");
|
||||
rhs.frames = nullptr;
|
||||
}
|
||||
|
||||
~LiveSavedFrameCache() {
|
||||
if (frames) {
|
||||
js_delete(frames);
|
||||
frames = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool initialized() const { return !!frames; }
|
||||
bool init(JSContext* cx) {
|
||||
frames = js_new<EntryVector>();
|
||||
if (!frames) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static mozilla::Maybe<FramePtr> getFramePtr(FrameIter& iter);
|
||||
static void trace(LiveSavedFrameCache* cache, JSTracer* trc);
|
||||
|
||||
void find(JSContext* cx, FrameIter& frameIter, MutableHandleSavedFrame frame) const;
|
||||
bool insert(JSContext* cx, FramePtr& framePtr, jsbytecode* pc, HandleSavedFrame savedFrame);
|
||||
};
|
||||
|
||||
static_assert(sizeof(LiveSavedFrameCache) == sizeof(uintptr_t),
|
||||
"Every js::Activation has a LiveSavedFrameCache, so we need to be pretty careful "
|
||||
"about avoiding bloat. If you're adding members to LiveSavedFrameCache, maybe you "
|
||||
"should consider figuring out a way to make js::Activation have a "
|
||||
"LiveSavedFrameCache* instead of a Rooted<LiveSavedFrameCache>.");
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
class InterpreterActivation;
|
||||
class AsmJSActivation;
|
||||
|
||||
@ -1136,6 +1268,10 @@ class Activation
|
||||
// data structures instead.
|
||||
size_t hideScriptedCallerCount_;
|
||||
|
||||
// The cache of SavedFrame objects we have already captured when walking
|
||||
// this activation's stack.
|
||||
Rooted<LiveSavedFrameCache> frameCache_;
|
||||
|
||||
// Youngest saved frame of an async stack that will be iterated during stack
|
||||
// capture in place of the actual stack of previous activations. Note that
|
||||
// the stack of this activation is captured entirely before this is used.
|
||||
@ -1240,6 +1376,8 @@ class Activation
|
||||
return asyncCallIsExplicit_;
|
||||
}
|
||||
|
||||
inline LiveSavedFrameCache* getLiveSavedFrameCache(JSContext* cx);
|
||||
|
||||
private:
|
||||
Activation(const Activation& other) = delete;
|
||||
void operator=(const Activation& other) = delete;
|
||||
@ -1730,6 +1868,7 @@ class FrameIter
|
||||
bool isAsmJS() const { MOZ_ASSERT(!done()); return data_.state_ == ASMJS; }
|
||||
inline bool isIon() const;
|
||||
inline bool isBaseline() const;
|
||||
inline bool isPhysicalIonFrame() const;
|
||||
|
||||
bool isFunctionFrame() const;
|
||||
bool isGlobalFrame() const;
|
||||
@ -1737,7 +1876,9 @@ class FrameIter
|
||||
bool isNonEvalFunctionFrame() const;
|
||||
bool hasArgs() const { return isNonEvalFunctionFrame(); }
|
||||
|
||||
bool hasCachedSavedFrame(JSContext* cx, bool* hasCachedSavedFramep);
|
||||
// These two methods may not be called with asm frames.
|
||||
inline bool hasCachedSavedFrame() const;
|
||||
inline void setHasCachedSavedFrame();
|
||||
|
||||
ScriptSource* scriptSource() const;
|
||||
const char* scriptFilename() const;
|
||||
@ -1826,6 +1967,9 @@ class FrameIter
|
||||
// This can only be called when isInterp():
|
||||
inline InterpreterFrame* interpFrame() const;
|
||||
|
||||
// This can only be called when isPhysicalIonFrame():
|
||||
inline jit::CommonFrameLayout* physicalIonFrame() const;
|
||||
|
||||
private:
|
||||
Data data_;
|
||||
jit::InlineFrameIterator ionInlineFrames_;
|
||||
@ -2034,5 +2178,20 @@ FrameIter::interpFrame() const
|
||||
return data_.interpFrames_.frame();
|
||||
}
|
||||
|
||||
inline bool
|
||||
FrameIter::isPhysicalIonFrame() const
|
||||
{
|
||||
return isJit() &&
|
||||
data_.jitFrames_.isIonScripted() &&
|
||||
ionInlineFrames_.frameNo() == 0;
|
||||
}
|
||||
|
||||
inline jit::CommonFrameLayout*
|
||||
FrameIter::physicalIonFrame() const
|
||||
{
|
||||
MOZ_ASSERT(isPhysicalIonFrame());
|
||||
return data_.jitFrames_.current();
|
||||
}
|
||||
|
||||
} /* namespace js */
|
||||
#endif /* vm_Stack_h */
|
||||
|
Loading…
Reference in New Issue
Block a user