Bug 1028418 - Part 5: Minimize stack walking when capturing SavedFrame stacks with a cache; r=shu

This commit is contained in:
Nick Fitzgerald 2015-07-28 13:04:56 -07:00
parent 94e723e2df
commit 47721e3965
9 changed files with 497 additions and 161 deletions

View File

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

View File

@ -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 */

View File

@ -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
View 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

View File

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

View File

@ -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.

View File

@ -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 */

View File

@ -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()
{

View File

@ -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 */