Bug 1137569 - Delay stringification of JIT frames until streaming time. (r=djvj)

This commit is contained in:
Shu-yu Guo 2015-04-02 14:50:08 -07:00
parent e7aa0f3877
commit 2a5551df33
5 changed files with 158 additions and 65 deletions

View File

@ -8,6 +8,7 @@
#define js_ProfilingFrameIterator_h
#include "mozilla/Alignment.h"
#include "mozilla/Maybe.h"
#include <stdint.h>
@ -22,6 +23,7 @@ namespace js {
namespace jit {
class JitActivation;
class JitProfilingFrameIterator;
class JitcodeGlobalEntry;
}
}
@ -109,18 +111,22 @@ class JS_PUBLIC_API(ProfilingFrameIterator)
void* returnAddress;
void* activation;
const char* label;
bool mightHaveTrackedOptimizations;
};
bool isAsmJS() const;
bool isJit() const;
uint32_t extractStack(Frame* frames, uint32_t offset, uint32_t end) const;
mozilla::Maybe<Frame> getPhysicalFrameWithoutLabel() const;
private:
mozilla::Maybe<Frame> getPhysicalFrameAndEntry(js::jit::JitcodeGlobalEntry* entry) const;
void iteratorConstruct(const RegisterState& state);
void iteratorConstruct();
void iteratorDestroy();
bool iteratorDone();
bool isAsmJS() const;
bool isJit() const;
};
extern JS_PUBLIC_API(ProfilingFrameIterator::FrameKind)
@ -141,6 +147,15 @@ JS_FRIEND_API(void)
UpdateJSRuntimeProfilerSampleBufferGen(JSRuntime* runtime, uint32_t generation,
uint32_t lapCount);
struct ForEachProfiledFrameOp
{
// Called once per frame.
virtual void operator()(const char* label, bool mightHaveTrackedOptimizations) = 0;
};
JS_PUBLIC_API(void)
ForEachProfiledFrame(JSRuntime* rt, void* addr, ForEachProfiledFrameOp& op);
} // namespace JS
#endif /* js_ProfilingFrameIterator_h */

View File

@ -172,7 +172,6 @@ JitcodeGlobalEntry::BaselineEntry::callStackAtAddr(JSRuntime* rt, void* ptr,
uint32_t maxResults) const
{
MOZ_ASSERT(containsPointer(ptr));
MOZ_ASSERT(script_->hasBaselineScript());
MOZ_ASSERT(maxResults >= 1);
results[0] = str();

View File

@ -26,6 +26,7 @@
using namespace js;
using mozilla::Maybe;
using mozilla::PodCopy;
/*****************************************************************************/
@ -1861,70 +1862,86 @@ JS::ProfilingFrameIterator::stackAddress() const
return jitIter().stackAddress();
}
Maybe<JS::ProfilingFrameIterator::Frame>
JS::ProfilingFrameIterator::getPhysicalFrameAndEntry(jit::JitcodeGlobalEntry* entry) const
{
void* stackAddr = stackAddress();
if (isAsmJS()) {
Frame frame;
frame.kind = Frame_AsmJS;
frame.stackAddress = stackAddr;
frame.returnAddress = nullptr;
frame.activation = activation_;
frame.label = nullptr;
return mozilla::Some(frame);
}
MOZ_ASSERT(isJit());
// Look up an entry for the return address.
void* returnAddr = jitIter().returnAddressToFp();
jit::JitcodeGlobalTable* table = rt_->jitRuntime()->getJitcodeGlobalTable();
if (hasSampleBufferGen())
table->lookupForSampler(returnAddr, entry, rt_, sampleBufferGen_);
else
table->lookupInfallible(returnAddr, entry, rt_);
MOZ_ASSERT(entry->isIon() || entry->isIonCache() || entry->isBaseline() || entry->isDummy());
// Dummy frames produce no stack frames.
if (entry->isDummy())
return mozilla::Nothing();
Frame frame;
frame.kind = entry->isBaseline() ? Frame_Baseline : Frame_Ion;
frame.stackAddress = stackAddr;
frame.returnAddress = returnAddr;
frame.activation = activation_;
frame.label = nullptr;
return mozilla::Some(frame);
}
uint32_t
JS::ProfilingFrameIterator::extractStack(Frame* frames, uint32_t offset, uint32_t end) const
{
if (offset >= end)
return 0;
void* stackAddr = stackAddress();
jit::JitcodeGlobalEntry entry;
Maybe<Frame> physicalFrame = getPhysicalFrameAndEntry(&entry);
// Dummy frames produce no stack frames.
if (physicalFrame.isNothing())
return 0;
if (isAsmJS()) {
frames[offset].kind = Frame_AsmJS;
frames[offset].stackAddress = stackAddr;
frames[offset].returnAddress = nullptr;
frames[offset].activation = activation_;
frames[offset] = physicalFrame.value();
frames[offset].label = asmJSIter().label();
frames[offset].mightHaveTrackedOptimizations = false;
return 1;
}
MOZ_ASSERT(isJit());
void* returnAddr = jitIter().returnAddressToFp();
// Look up an entry for the return address.
jit::JitcodeGlobalTable* table = rt_->jitRuntime()->getJitcodeGlobalTable();
jit::JitcodeGlobalEntry entry;
table->lookupInfallible(returnAddr, &entry, rt_);
if (hasSampleBufferGen())
table->lookupForSampler(returnAddr, &entry, rt_, sampleBufferGen_);
else
table->lookup(returnAddr, &entry, rt_);
MOZ_ASSERT(entry.isIon() || entry.isIonCache() || entry.isBaseline() || entry.isDummy());
// Dummy frames produce no stack frames.
if (entry.isDummy())
return 0;
FrameKind kind = entry.isBaseline() ? Frame_Baseline : Frame_Ion;
// Extract the stack for the entry. Assume maximum inlining depth is <64
const char* labels[64];
uint32_t depth = entry.callStackAtAddr(rt_, returnAddr, labels, 64);
uint32_t depth = entry.callStackAtAddr(rt_, jitIter().returnAddressToFp(), labels, 64);
MOZ_ASSERT(depth < 64);
for (uint32_t i = 0; i < depth; i++) {
if (offset + i >= end)
return i;
frames[offset + i].kind = kind;
frames[offset + i].stackAddress = stackAddr;
frames[offset + i].returnAddress = returnAddr;
frames[offset + i].activation = activation_;
frames[offset + i] = physicalFrame.value();
frames[offset + i].label = labels[i];
frames[offset + i].mightHaveTrackedOptimizations = false;
}
// A particular return address might have tracked optimizations only if
// there are any optimizations at all.
//
// All inlined Ion frames will have the same optimization information by
// virtue of sharing the JitcodeGlobalEntry, but such information is only
// interpretable on the youngest frame.
frames[offset].mightHaveTrackedOptimizations = entry.hasTrackedOptimizations();
return depth;
}
Maybe<JS::ProfilingFrameIterator::Frame>
JS::ProfilingFrameIterator::getPhysicalFrameWithoutLabel() const
{
jit::JitcodeGlobalEntry unused;
return getPhysicalFrameAndEntry(&unused);
}
bool
JS::ProfilingFrameIterator::isAsmJS() const
{
@ -1937,3 +1954,22 @@ JS::ProfilingFrameIterator::isJit() const
{
return activation_->isJit();
}
JS_PUBLIC_API(void)
JS::ForEachProfiledFrame(JSRuntime* rt, void* addr, ForEachProfiledFrameOp& op)
{
jit::JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable();
jit::JitcodeGlobalEntry entry;
table->lookupInfallible(addr, &entry, rt);
// Extract the stack for the entry. Assume maximum inlining depth is <64
const char* labels[64];
uint32_t depth = entry.callStackAtAddr(rt, addr, labels, 64);
MOZ_ASSERT(depth < 64);
for (uint32_t i = depth; i != 0; i--) {
// All inlined frames will have the same optimization information by
// virtue of sharing the JitcodeGlobalEntry, but such information is
// only interpretable on the youngest frame.
op(labels[i - 1], i == 1 && entry.hasTrackedOptimizations());
}
}

View File

@ -280,12 +280,47 @@ public:
void operator()(JS::TrackedStrategy strategy, JS::TrackedOutcome outcome) override {
mWriter.BeginObject();
{
// Stringify the reasons for now; could stream enum values in the future
// to save space.
mWriter.NameValue("strategy", JS::TrackedStrategyString(strategy));
mWriter.NameValue("outcome", JS::TrackedOutcomeString(outcome));
}
mWriter.EndObject();
}
};
class StreamJSFramesOp : public JS::ForEachProfiledFrameOp
{
JSRuntime* mRuntime;
void* mReturnAddress;
UniqueJITOptimizations& mUniqueOpts;
JSStreamWriter& mWriter;
public:
StreamJSFramesOp(JSRuntime* aRuntime, void* aReturnAddr, UniqueJITOptimizations& aUniqueOpts,
JSStreamWriter& aWriter)
: mRuntime(aRuntime)
, mReturnAddress(aReturnAddr)
, mUniqueOpts(aUniqueOpts)
, mWriter(aWriter)
{ }
void operator()(const char* label, bool mightHaveTrackedOptimizations) override {
mWriter.BeginObject();
mWriter.NameValue("location", label);
JS::ProfilingFrameIterator::FrameKind frameKind =
JS::GetProfilingFrameKindFromNativeAddr(mRuntime, mReturnAddress);
MOZ_ASSERT(frameKind == JS::ProfilingFrameIterator::Frame_Ion ||
frameKind == JS::ProfilingFrameIterator::Frame_Baseline);
const char* jitLevelString =
(frameKind == JS::ProfilingFrameIterator::Frame_Ion) ? "ion"
: "baseline";
mWriter.NameValue("implementation", jitLevelString);
if (mightHaveTrackedOptimizations) {
Maybe<unsigned> optsIndex = mUniqueOpts.getIndex(mReturnAddress, mRuntime);
if (optsIndex.isSome()) {
mWriter.NameValue("optsIndex", optsIndex.value());
}
}
mWriter.EndObject();
}
};
@ -473,6 +508,10 @@ void ProfileBuffer::StreamSamplesToJSObject(JSStreamWriter& b, int aThreadId, JS
incBy++;
}
b.EndObject();
} else if (frame.mTagName == 'J') {
void* pc = frame.mTagPtr;
StreamJSFramesOp framesOp(rt, pc, aUniqueOpts, b);
JS::ForEachProfiledFrame(rt, pc, framesOp);
}
framePos = (framePos + incBy) % mEntrySize;
}

View File

@ -550,11 +550,18 @@ void mergeStacksIntoProfile(ThreadProfile& aProfile, TickSample* aSample, Native
registerState,
startBufferGen);
for (; jsCount < maxFrames && !jsIter.done(); ++jsIter) {
uint32_t extracted = jsIter.extractStack(jsFrames, jsCount, maxFrames);
MOZ_ASSERT(extracted <= (maxFrames - jsCount));
jsCount += extracted;
if (jsCount == maxFrames)
break;
// See note below regarding 'J' entries.
if (aSample->isSamplingCurrentThread || jsIter.isAsmJS()) {
uint32_t extracted = jsIter.extractStack(jsFrames, jsCount, maxFrames);
jsCount += extracted;
if (jsCount == maxFrames)
break;
} else {
mozilla::Maybe<JS::ProfilingFrameIterator::Frame> frame =
jsIter.getPhysicalFrameWithoutLabel();
if (frame.isSome())
jsFrames[jsCount++] = frame.value();
}
}
}
}
@ -641,15 +648,10 @@ void mergeStacksIntoProfile(ThreadProfile& aProfile, TickSample* aSample, Native
if (jsStackAddr > nativeStackAddr) {
MOZ_ASSERT(jsIndex >= 0);
const JS::ProfilingFrameIterator::Frame& jsFrame = jsFrames[jsIndex];
addDynamicTag(aProfile, 'c', jsFrame.label);
// Stringifying optimization information and the JIT tier is delayed
// until streaming time. To re-lookup the entry in the
// JitcodeGlobalTable, we need to store the JIT code address in the
// circular buffer.
//
// Frames which may have optimization information are tagged by an 'O'
// entry. Otherwise they are tagged by a 'J' entry.
// Stringifying non-asm.js JIT frames is delayed until streaming
// time. To re-lookup the entry in the JitcodeGlobalTable, we need to
// store the JIT code address ('J') in the circular buffer.
//
// Note that we cannot do this when we are sychronously sampling the
// current thread; that is, when called from profiler_get_backtrace. The
@ -660,11 +662,13 @@ void mergeStacksIntoProfile(ThreadProfile& aProfile, TickSample* aSample, Native
// its JIT code. This means that if we inserted such 'J' entries into
// the buffer, nsRefreshDriver would now be holding on to a backtrace
// with stale JIT code return addresses.
if (!aSample->isSamplingCurrentThread &&
(jsFrame.kind == JS::ProfilingFrameIterator::Frame_Ion ||
jsFrame.kind == JS::ProfilingFrameIterator::Frame_Baseline)) {
char entryTag = jsFrame.mightHaveTrackedOptimizations ? 'O' : 'J';
aProfile.addTag(ProfileEntry(entryTag, jsFrames[jsIndex].returnAddress));
if (aSample->isSamplingCurrentThread ||
jsFrame.kind == JS::ProfilingFrameIterator::Frame_AsmJS) {
addDynamicTag(aProfile, 'c', jsFrame.label);
} else {
MOZ_ASSERT(jsFrame.kind == JS::ProfilingFrameIterator::Frame_Ion ||
jsFrame.kind == JS::ProfilingFrameIterator::Frame_Baseline);
aProfile.addTag(ProfileEntry('J', jsFrames[jsIndex].returnAddress));
}
jsIndex--;