Bug 1366650 (part 2) - In GeckoProfiler, do all pseudo-stack accesses via the PseudoStack class, instead of via raw array manipulation. r=mstange,shu.

- The profiler gives the JS engine a reference to the pseudo-stack via
  SetContextProfiilngStack(). That function now takes a |PseudoStack*| instead
  of a |ProfileEntry*| and pointer to the stack pointer.

- PseudoStack now has a |kMaxEntries| field, which is easier to work with than
  |mozilla::ArrayLength(entries)|.

- AddressOfStackPointer() is no longer needed.

- The patch also neatens up the push operations significantly. PseudoStack now
  has pushCppFrame() and pushJsFrame(), which nicely encapsulate the two main
  cases. These delegate to the updated initCppFrame() and initJsFrame()
  functions in ProfileEntry.

- Renames max_stck in testProfileStrings.cpp as peakStackPointer, which is a
  clearer name.

- Removes a couple of checks from testProfileStrings.cpp. These checks made
  sense when the pseudo-stack was accessed via raw manipulation, but are not
  applicable now because we can't artificially limit the maximum stack size so
  easily.
This commit is contained in:
Nicholas Nethercote 2017-05-26 09:51:31 +10:00
parent 0840bb61c6
commit a062b9be51
8 changed files with 182 additions and 250 deletions

View file

@ -7,8 +7,6 @@
#ifndef js_ProfilingStack_h #ifndef js_ProfilingStack_h
#define js_ProfilingStack_h #define js_ProfilingStack_h
#include "mozilla/ArrayUtils.h"
#include <algorithm> #include <algorithm>
#include <stdint.h> #include <stdint.h>
@ -20,6 +18,8 @@
struct JSRuntime; struct JSRuntime;
class JSTracer; class JSTracer;
class PseudoStack;
namespace js { namespace js {
// A call stack can be specified to the JS engine such that all JS entry/exits // A call stack can be specified to the JS engine such that all JS entry/exits
@ -58,6 +58,8 @@ class ProfileEntry
// General purpose storage describing this frame. // General purpose storage describing this frame.
uint32_t volatile flags_; uint32_t volatile flags_;
static int32_t pcToOffset(JSScript* aScript, jsbytecode* aPc);
public: public:
// These traits are bit masks. Make sure they're powers of 2. // These traits are bit masks. Make sure they're powers of 2.
enum Flags : uint32_t { enum Flags : uint32_t {
@ -112,18 +114,27 @@ class ProfileEntry
void setLabel(const char* aLabel) volatile { label_ = aLabel; } void setLabel(const char* aLabel) volatile { label_ = aLabel; }
const char* label() const volatile { return label_; } const char* label() const volatile { return label_; }
void setDynamicString(const char* aDynamicString) volatile { dynamicString_ = aDynamicString; }
const char* dynamicString() const volatile { return dynamicString_; } const char* dynamicString() const volatile { return dynamicString_; }
void initJsFrame(JSScript* aScript, jsbytecode* aPc) volatile { void initCppFrame(const char* aLabel, const char* aDynamicString, void* sp, uint32_t aLine,
flags_ = 0; js::ProfileEntry::Flags aFlags, js::ProfileEntry::Category aCategory)
spOrScript = aScript; volatile
setPC(aPc); {
} label_ = aLabel;
void initCppFrame(void* aSp, uint32_t aLine) volatile { dynamicString_ = aDynamicString;
flags_ = IS_CPP_ENTRY; spOrScript = sp;
spOrScript = aSp;
lineOrPcOffset = static_cast<int32_t>(aLine); lineOrPcOffset = static_cast<int32_t>(aLine);
flags_ = aFlags | js::ProfileEntry::IS_CPP_ENTRY | uint32_t(aCategory);
}
void initJsFrame(const char* aLabel, const char* aDynamicString, JSScript* aScript,
jsbytecode* aPc) volatile
{
label_ = aLabel;
dynamicString_ = aDynamicString;
spOrScript = aScript;
lineOrPcOffset = pcToOffset(aScript, aPc);
flags_ = uint32_t(js::ProfileEntry::Category::JS); // No flags, just the JS category.
} }
void setFlag(uint32_t flag) volatile { void setFlag(uint32_t flag) volatile {
@ -149,7 +160,7 @@ class ProfileEntry
MOZ_ASSERT(c >= Category::FIRST); MOZ_ASSERT(c >= Category::FIRST);
MOZ_ASSERT(c <= Category::LAST); MOZ_ASSERT(c <= Category::LAST);
flags_ &= ~CATEGORY_MASK; flags_ &= ~CATEGORY_MASK;
setFlag(static_cast<uint32_t>(c)); setFlag(uint32_t(c));
} }
void setOSR() volatile { void setOSR() volatile {
@ -184,7 +195,7 @@ class ProfileEntry
JS_FRIEND_API(jsbytecode*) pc() const volatile; JS_FRIEND_API(jsbytecode*) pc() const volatile;
JS_FRIEND_API(void) setPC(jsbytecode* pc) volatile; JS_FRIEND_API(void) setPC(jsbytecode* pc) volatile;
void trace(JSTracer* trc); void trace(JSTracer* trc) volatile;
// The offset of a pc into a script's code can actually be 0, so to // The offset of a pc into a script's code can actually be 0, so to
// signify a nullptr pc, use a -1 index. This is checked against in // signify a nullptr pc, use a -1 index. This is checked against in
@ -198,8 +209,7 @@ class ProfileEntry
}; };
JS_FRIEND_API(void) JS_FRIEND_API(void)
SetContextProfilingStack(JSContext* cx, ProfileEntry* stack, mozilla::Atomic<uint32_t>* size, SetContextProfilingStack(JSContext* cx, PseudoStack* pseudoStack);
uint32_t max);
JS_FRIEND_API(void) JS_FRIEND_API(void)
EnableContextProfilingStack(JSContext* cx, bool enabled); EnableContextProfilingStack(JSContext* cx, bool enabled);
@ -226,33 +236,35 @@ class PseudoStack
MOZ_RELEASE_ASSERT(stackPointer == 0); MOZ_RELEASE_ASSERT(stackPointer == 0);
} }
void push(const char* label, js::ProfileEntry::Category category, void pushCppFrame(const char* label, const char* dynamicString, void* sp, uint32_t line,
void* stackAddress, uint32_t line, const char* dynamicString) { js::ProfileEntry::Category category,
if (size_t(stackPointer) >= mozilla::ArrayLength(entries)) { js::ProfileEntry::Flags flags = js::ProfileEntry::Flags(0)) {
stackPointer++; if (stackPointer < MaxEntries) {
return; entries[stackPointer].initCppFrame(label, dynamicString, sp, line, flags, category);
} }
volatile js::ProfileEntry& entry = entries[int(stackPointer)];
entry.initCppFrame(stackAddress, line);
entry.setLabel(label);
entry.setDynamicString(dynamicString);
MOZ_ASSERT(entry.flags() == js::ProfileEntry::IS_CPP_ENTRY);
entry.setCategory(category);
// This must happen at the end! The compiler will not reorder this // This must happen at the end! The compiler will not reorder this
// update because stackPointer is Atomic. // update because stackPointer is Atomic.
stackPointer++; stackPointer++;
} }
void pop() { stackPointer--; } void pushJsFrame(const char* label, const char* dynamicString, JSScript* script,
jsbytecode* pc) {
if (stackPointer < MaxEntries) {
entries[stackPointer].initJsFrame(label, dynamicString, script, pc);
}
uint32_t stackSize() const { // This must happen at the end! The compiler will not reorder this
return std::min(uint32_t(stackPointer), uint32_t(mozilla::ArrayLength(entries))); // update because stackPointer is Atomic.
stackPointer++;
} }
mozilla::Atomic<uint32_t>* AddressOfStackPointer() { return &stackPointer; } void pop() {
MOZ_ASSERT(stackPointer > 0);
stackPointer--;
}
uint32_t stackSize() const { return std::min(uint32_t(stackPointer), uint32_t(MaxEntries)); }
private: private:
// No copying. // No copying.
@ -260,12 +272,15 @@ class PseudoStack
void operator=(const PseudoStack&) = delete; void operator=(const PseudoStack&) = delete;
public: public:
// The stack entries. static const uint32_t MaxEntries = 1024;
js::ProfileEntry volatile entries[1024];
protected: // The stack entries.
// This may exceed the length of |entries|, so instead use the stackSize() js::ProfileEntry volatile entries[MaxEntries];
// method to determine the number of valid samples in |entries|.
// This may exceed MaxEntries, so instead use the stackSize() method to
// determine the number of valid samples in entries. When this is less
// than MaxEntries, it refers to the first free entry past the top of the
// in-use stack (i.e. entries[stackPointer - 1] is the top stack entry).
mozilla::Atomic<uint32_t> stackPointer; mozilla::Atomic<uint32_t> stackPointer;
}; };

View file

@ -13,15 +13,13 @@
#include "jsapi-tests/tests.h" #include "jsapi-tests/tests.h"
static js::ProfileEntry pstack[10]; static PseudoStack pseudoStack;
static mozilla::Atomic<uint32_t> psize; static uint32_t peakStackPointer = 0;
static uint32_t max_stack = 0;
static void static void
reset(JSContext* cx) reset(JSContext* cx)
{ {
psize = max_stack = 0; pseudoStack.stackPointer = 0;
memset(pstack, 0, sizeof(pstack));
cx->runtime()->geckoProfiler().stringsReset(); cx->runtime()->geckoProfiler().stringsReset();
cx->runtime()->geckoProfiler().enableSlowAssertions(true); cx->runtime()->geckoProfiler().enableSlowAssertions(true);
js::EnableContextProfilingStack(cx, true); js::EnableContextProfilingStack(cx, true);
@ -34,7 +32,7 @@ static const JSClass ptestClass = {
static bool static bool
test_fn(JSContext* cx, unsigned argc, JS::Value* vp) test_fn(JSContext* cx, unsigned argc, JS::Value* vp)
{ {
max_stack = psize; peakStackPointer = pseudoStack.stackPointer;
return true; return true;
} }
@ -82,7 +80,7 @@ static const JSFunctionSpec ptestFunctions[] = {
static JSObject* static JSObject*
initialize(JSContext* cx) initialize(JSContext* cx)
{ {
js::SetContextProfilingStack(cx, pstack, &psize, 10); js::SetContextProfilingStack(cx, &pseudoStack);
JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
return JS_InitClass(cx, global, nullptr, &ptestClass, Prof, 0, return JS_InitClass(cx, global, nullptr, &ptestClass, Prof, 0,
nullptr, ptestFunctions, nullptr, nullptr); nullptr, ptestFunctions, nullptr, nullptr);
@ -108,15 +106,15 @@ BEGIN_TEST(testProfileStrings_isCalledWithInterpreter)
/* Make sure the stack resets and we have an entry for each stack */ /* Make sure the stack resets and we have an entry for each stack */
CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(), CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(),
&rval)); &rval));
CHECK(psize == 0); CHECK(pseudoStack.stackPointer == 0);
CHECK(max_stack >= 8); CHECK(peakStackPointer >= 8);
CHECK(cx->runtime()->geckoProfiler().stringsCount() == 8); CHECK(cx->runtime()->geckoProfiler().stringsCount() == 8);
/* Make sure the stack resets and we added no new entries */ /* Make sure the stack resets and we added no new entries */
max_stack = 0; peakStackPointer = 0;
CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(), CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(),
&rval)); &rval));
CHECK(psize == 0); CHECK(pseudoStack.stackPointer == 0);
CHECK(max_stack >= 8); CHECK(peakStackPointer >= 8);
CHECK(cx->runtime()->geckoProfiler().stringsCount() == 8); CHECK(cx->runtime()->geckoProfiler().stringsCount() == 8);
} }
reset(cx); reset(cx);
@ -125,20 +123,8 @@ BEGIN_TEST(testProfileStrings_isCalledWithInterpreter)
CHECK(JS_CallFunctionName(cx, global, "check2", JS::HandleValueArray::empty(), CHECK(JS_CallFunctionName(cx, global, "check2", JS::HandleValueArray::empty(),
&rval)); &rval));
CHECK(cx->runtime()->geckoProfiler().stringsCount() == 5); CHECK(cx->runtime()->geckoProfiler().stringsCount() == 5);
CHECK(max_stack >= 6); CHECK(peakStackPointer >= 6);
CHECK(psize == 0); CHECK(pseudoStack.stackPointer == 0);
}
js::EnableContextProfilingStack(cx, false);
js::SetContextProfilingStack(cx, pstack, &psize, 3);
reset(cx);
{
JS::RootedValue rval(cx);
pstack[3].setLabel((char*) 1234);
CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(),
&rval));
CHECK((size_t) pstack[3].label() == 1234);
CHECK(max_stack >= 8);
CHECK(psize == 0);
} }
return true; return true;
} }
@ -166,32 +152,19 @@ BEGIN_TEST(testProfileStrings_isCalledWithJIT)
/* Make sure the stack resets and we have an entry for each stack */ /* Make sure the stack resets and we have an entry for each stack */
CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(), CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(),
&rval)); &rval));
CHECK(psize == 0); CHECK(pseudoStack.stackPointer == 0);
CHECK(max_stack >= 8); CHECK(peakStackPointer >= 8);
/* Make sure the stack resets and we added no new entries */ /* Make sure the stack resets and we added no new entries */
uint32_t cnt = cx->runtime()->geckoProfiler().stringsCount(); uint32_t cnt = cx->runtime()->geckoProfiler().stringsCount();
max_stack = 0; peakStackPointer = 0;
CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(), CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(),
&rval)); &rval));
CHECK(psize == 0); CHECK(pseudoStack.stackPointer == 0);
CHECK(cx->runtime()->geckoProfiler().stringsCount() == cnt); CHECK(cx->runtime()->geckoProfiler().stringsCount() == cnt);
CHECK(max_stack >= 8); CHECK(peakStackPointer >= 8);
} }
js::EnableContextProfilingStack(cx, false);
js::SetContextProfilingStack(cx, pstack, &psize, 3);
reset(cx);
{
/* Limit the size of the stack and make sure we don't overflow */
JS::RootedValue rval(cx);
pstack[3].setLabel((char*) 1234);
CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(),
&rval));
CHECK(psize == 0);
CHECK(max_stack >= 8);
CHECK((size_t) pstack[3].label() == 1234);
}
return true; return true;
} }
END_TEST(testProfileStrings_isCalledWithJIT) END_TEST(testProfileStrings_isCalledWithJIT)
@ -211,11 +184,12 @@ BEGIN_TEST(testProfileStrings_isCalledWhenError)
bool ok = JS_CallFunctionName(cx, global, "check2", JS::HandleValueArray::empty(), bool ok = JS_CallFunctionName(cx, global, "check2", JS::HandleValueArray::empty(),
&rval); &rval);
CHECK(!ok); CHECK(!ok);
CHECK(psize == 0); CHECK(pseudoStack.stackPointer == 0);
CHECK(cx->runtime()->geckoProfiler().stringsCount() == 1); CHECK(cx->runtime()->geckoProfiler().stringsCount() == 1);
JS_ClearPendingException(cx); JS_ClearPendingException(cx);
} }
return true; return true;
} }
END_TEST(testProfileStrings_isCalledWhenError) END_TEST(testProfileStrings_isCalledWhenError)
@ -234,8 +208,8 @@ BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly)
/* enable it in the middle of JS and make sure things check out */ /* enable it in the middle of JS and make sure things check out */
JS::RootedValue rval(cx); JS::RootedValue rval(cx);
JS_CallFunctionName(cx, global, "a", JS::HandleValueArray::empty(), &rval); JS_CallFunctionName(cx, global, "a", JS::HandleValueArray::empty(), &rval);
CHECK(psize == 0); CHECK(pseudoStack.stackPointer == 0);
CHECK(max_stack >= 1); CHECK(peakStackPointer >= 1);
CHECK(cx->runtime()->geckoProfiler().stringsCount() == 1); CHECK(cx->runtime()->geckoProfiler().stringsCount() == 1);
} }
@ -246,7 +220,7 @@ BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly)
/* now disable in the middle of js */ /* now disable in the middle of js */
JS::RootedValue rval(cx); JS::RootedValue rval(cx);
JS_CallFunctionName(cx, global, "c", JS::HandleValueArray::empty(), &rval); JS_CallFunctionName(cx, global, "c", JS::HandleValueArray::empty(), &rval);
CHECK(psize == 0); CHECK(pseudoStack.stackPointer == 0);
} }
EXEC("function e() { var p = new Prof(); d(p); p.enable(); b(p); }"); EXEC("function e() { var p = new Prof(); d(p); p.enable(); b(p); }");
@ -255,8 +229,8 @@ BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly)
/* now disable in the middle of js, but re-enable before final exit */ /* now disable in the middle of js, but re-enable before final exit */
JS::RootedValue rval(cx); JS::RootedValue rval(cx);
JS_CallFunctionName(cx, global, "e", JS::HandleValueArray::empty(), &rval); JS_CallFunctionName(cx, global, "e", JS::HandleValueArray::empty(), &rval);
CHECK(psize == 0); CHECK(pseudoStack.stackPointer == 0);
CHECK(max_stack >= 3); CHECK(peakStackPointer >= 3);
} }
EXEC("function h() { }"); EXEC("function h() { }");
@ -269,7 +243,7 @@ BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly)
/* disable, and make sure that if we try to re-enter the JIT the pop /* disable, and make sure that if we try to re-enter the JIT the pop
* will still happen */ * will still happen */
JS_CallFunctionName(cx, global, "f", JS::HandleValueArray::empty(), &rval); JS_CallFunctionName(cx, global, "f", JS::HandleValueArray::empty(), &rval);
CHECK(psize == 0); CHECK(pseudoStack.stackPointer == 0);
} }
return true; return true;
} }

View file

@ -387,8 +387,7 @@ ShellContext::ShellContext(JSContext* cx)
quitting(false), quitting(false),
readLineBufPos(0), readLineBufPos(0),
errFilePtr(nullptr), errFilePtr(nullptr),
outFilePtr(nullptr), outFilePtr(nullptr)
geckoProfilingStackSize(0)
{} {}
ShellContext* ShellContext*
@ -5217,8 +5216,7 @@ EnableGeckoProfiling(JSContext* cx, unsigned argc, Value* vp)
if (cx->runtime()->geckoProfiler().installed()) if (cx->runtime()->geckoProfiler().installed())
MOZ_ALWAYS_TRUE(cx->runtime()->geckoProfiler().enable(false)); MOZ_ALWAYS_TRUE(cx->runtime()->geckoProfiler().enable(false));
SetContextProfilingStack(cx, sc->geckoProfilingStack, &sc->geckoProfilingStackSize, SetContextProfilingStack(cx, &sc->geckoProfilingStack);
ShellContext::GeckoProfilingMaxStackSize);
cx->runtime()->geckoProfiler().enableSlowAssertions(false); cx->runtime()->geckoProfiler().enableSlowAssertions(false);
if (!cx->runtime()->geckoProfiler().enable(true)) if (!cx->runtime()->geckoProfiler().enable(true))
JS_ReportErrorASCII(cx, "Cannot ensure single threaded execution in profiler"); JS_ReportErrorASCII(cx, "Cannot ensure single threaded execution in profiler");
@ -5250,8 +5248,7 @@ EnableGeckoProfilingWithSlowAssertions(JSContext* cx, unsigned argc, Value* vp)
if (cx->runtime()->geckoProfiler().installed()) if (cx->runtime()->geckoProfiler().installed())
MOZ_ALWAYS_TRUE(cx->runtime()->geckoProfiler().enable(false)); MOZ_ALWAYS_TRUE(cx->runtime()->geckoProfiler().enable(false));
SetContextProfilingStack(cx, sc->geckoProfilingStack, &sc->geckoProfilingStackSize, SetContextProfilingStack(cx, &sc->geckoProfilingStack);
ShellContext::GeckoProfilingMaxStackSize);
cx->runtime()->geckoProfiler().enableSlowAssertions(true); cx->runtime()->geckoProfiler().enableSlowAssertions(true);
if (!cx->runtime()->geckoProfiler().enable(true)) if (!cx->runtime()->geckoProfiler().enable(true))
JS_ReportErrorASCII(cx, "Cannot ensure single threaded execution in profiler"); JS_ReportErrorASCII(cx, "Cannot ensure single threaded execution in profiler");

View file

@ -203,9 +203,7 @@ struct ShellContext
js::shell::RCFile** errFilePtr; js::shell::RCFile** errFilePtr;
js::shell::RCFile** outFilePtr; js::shell::RCFile** outFilePtr;
static const uint32_t GeckoProfilingMaxStackSize = 1000; PseudoStack geckoProfilingStack;
js::ProfileEntry geckoProfilingStack[GeckoProfilingMaxStackSize];
mozilla::Atomic<uint32_t> geckoProfilingStackSize;
OffThreadState offThreadState; OffThreadState offThreadState;

View file

@ -28,9 +28,7 @@ using mozilla::DebugOnly;
GeckoProfiler::GeckoProfiler(JSRuntime* rt) GeckoProfiler::GeckoProfiler(JSRuntime* rt)
: rt(rt), : rt(rt),
strings(mutexid::GeckoProfilerStrings), strings(mutexid::GeckoProfilerStrings),
stack_(nullptr), pseudoStack_(nullptr),
size_(nullptr),
max_(0),
slowAssertions(false), slowAssertions(false),
enabled_(false), enabled_(false),
eventMarker_(nullptr) eventMarker_(nullptr)
@ -49,14 +47,12 @@ GeckoProfiler::init()
} }
void void
GeckoProfiler::setProfilingStack(ProfileEntry* stack, mozilla::Atomic<uint32_t>* size, uint32_t max) GeckoProfiler::setProfilingStack(PseudoStack* pseudoStack)
{ {
MOZ_ASSERT_IF(size_ && *size_ != 0, !enabled()); MOZ_ASSERT_IF(pseudoStack_, !enabled());
MOZ_ASSERT(strings.lock()->initialized()); MOZ_ASSERT(strings.lock()->initialized());
stack_ = stack; pseudoStack_ = pseudoStack;
size_ = size;
max_ = max;
} }
void void
@ -210,106 +206,55 @@ GeckoProfiler::enter(JSContext* cx, JSScript* script, JSFunction* maybeFun)
// In debug builds, assert the JS pseudo frames already on the stack // In debug builds, assert the JS pseudo frames already on the stack
// have a non-null pc. Only look at the top frames to avoid quadratic // have a non-null pc. Only look at the top frames to avoid quadratic
// behavior. // behavior.
if (*size_ > 0 && *size_ - 1 < max_) { uint32_t sp = pseudoStack_->stackPointer;
size_t start = (*size_ > 4) ? *size_ - 4 : 0; if (sp > 0 && sp - 1 < PseudoStack::MaxEntries) {
for (size_t i = start; i < *size_ - 1; i++) size_t start = (sp > 4) ? sp - 4 : 0;
MOZ_ASSERT_IF(stack_[i].isJs(), stack_[i].pc() != nullptr); for (size_t i = start; i < sp - 1; i++)
MOZ_ASSERT_IF(pseudoStack_->entries[i].isJs(), pseudoStack_->entries[i].pc());
} }
#endif #endif
push("", dynamicString, /* sp = */ nullptr, script, script->code()); pseudoStack_->pushJsFrame("", dynamicString, script, script->code());
return true; return true;
} }
void void
GeckoProfiler::exit(JSScript* script, JSFunction* maybeFun) GeckoProfiler::exit(JSScript* script, JSFunction* maybeFun)
{ {
pop(); pseudoStack_->pop();
#ifdef DEBUG #ifdef DEBUG
/* Sanity check to make sure push/pop balanced */ /* Sanity check to make sure push/pop balanced */
if (*size_ < max_) { uint32_t sp = pseudoStack_->stackPointer;
if (sp < PseudoStack::MaxEntries) {
const char* dynamicString = profileString(script, maybeFun); const char* dynamicString = profileString(script, maybeFun);
/* Can't fail lookup because we should already be in the set */ /* Can't fail lookup because we should already be in the set */
MOZ_ASSERT(dynamicString != nullptr); MOZ_ASSERT(dynamicString);
// Bug 822041 // Bug 822041
if (!stack_[*size_].isJs()) { if (!pseudoStack_->entries[sp].isJs()) {
fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n"); fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n");
fprintf(stderr, " stack=%p size=%d/%d\n", (void*) stack_, size(), max_); fprintf(stderr, " entries=%p size=%u/%u\n",
for (int32_t i = *size_; i >= 0; i--) { (void*) pseudoStack_->entries,
if (stack_[i].isJs()) uint32_t(pseudoStack_->stackPointer),
fprintf(stderr, " [%d] JS %s\n", i, stack_[i].dynamicString()); PseudoStack::MaxEntries);
for (int32_t i = sp; i >= 0; i--) {
volatile ProfileEntry& entry = pseudoStack_->entries[i];
if (entry.isJs())
fprintf(stderr, " [%d] JS %s\n", i, entry.dynamicString());
else else
fprintf(stderr, " [%d] C line %d %s\n", i, stack_[i].line(), stack_[i].dynamicString()); fprintf(stderr, " [%d] C line %d %s\n", i, entry.line(), entry.dynamicString());
} }
} }
MOZ_ASSERT(stack_[*size_].isJs()); volatile ProfileEntry& entry = pseudoStack_->entries[sp];
MOZ_ASSERT(stack_[*size_].script() == script); MOZ_ASSERT(entry.isJs());
MOZ_ASSERT(strcmp((const char*) stack_[*size_].dynamicString(), dynamicString) == 0); MOZ_ASSERT(entry.script() == script);
stack_[*size_].setDynamicString(nullptr); MOZ_ASSERT(strcmp((const char*) entry.dynamicString(), dynamicString) == 0);
stack_[*size_].setPC(nullptr);
} }
#endif #endif
} }
void
GeckoProfiler::beginPseudoJS(const char* label, void* sp)
{
/* these operations cannot be re-ordered, so volatile-ize operations */
volatile ProfileEntry* stack = stack_;
uint32_t current = *size_;
MOZ_ASSERT(installed());
if (current < max_) {
stack[current].setLabel(label);
stack[current].initCppFrame(sp, 0);
stack[current].setFlag(ProfileEntry::BEGIN_PSEUDO_JS);
}
*size_ = current + 1;
}
void
GeckoProfiler::push(const char* label, const char* dynamicString, void* sp, JSScript* script,
jsbytecode* pc, ProfileEntry::Category category)
{
MOZ_ASSERT(label[0] == '\0' || !dynamicString);
MOZ_ASSERT_IF(sp != nullptr, script == nullptr && pc == nullptr);
MOZ_ASSERT_IF(sp == nullptr, script != nullptr && pc != nullptr);
/* these operations cannot be re-ordered, so volatile-ize operations */
volatile ProfileEntry* stack = stack_;
uint32_t current = *size_;
MOZ_ASSERT(installed());
if (current < max_) {
volatile ProfileEntry& entry = stack[current];
if (sp != nullptr) {
entry.initCppFrame(sp, 0);
MOZ_ASSERT(entry.flags() == js::ProfileEntry::IS_CPP_ENTRY);
}
else {
entry.initJsFrame(script, pc);
MOZ_ASSERT(entry.flags() == 0);
}
entry.setLabel(label);
entry.setDynamicString(dynamicString);
entry.setCategory(category);
}
*size_ = current + 1;
}
void
GeckoProfiler::pop()
{
MOZ_ASSERT(installed());
MOZ_ASSERT(*size_ > 0);
(*size_)--;
}
/* /*
* Serializes the script/function pair into a "descriptive string" which is * Serializes the script/function pair into a "descriptive string" which is
* allowed to fail. This function cannot trigger a GC because it could finalize * allowed to fail. This function cannot trigger a GC because it could finalize
@ -365,12 +310,12 @@ GeckoProfiler::allocProfileString(JSScript* script, JSFunction* maybeFun)
} }
void void
GeckoProfiler::trace(JSTracer* trc) GeckoProfiler::trace(JSTracer* trc) volatile
{ {
if (stack_) { if (pseudoStack_) {
size_t limit = Min(uint32_t(*size_), max_); size_t size = pseudoStack_->stackSize();
for (size_t i = 0; i < limit; i++) for (size_t i = 0; i < size; i++)
stack_[i].trace(trc); pseudoStack_->entries[i].trace(trc);
} }
} }
@ -408,7 +353,7 @@ GeckoProfiler::checkStringsMapAfterMovingGC()
#endif #endif
void void
ProfileEntry::trace(JSTracer* trc) ProfileEntry::trace(JSTracer* trc) volatile
{ {
if (isJs()) { if (isJs()) {
JSScript* s = rawScript(); JSScript* s = rawScript();
@ -427,11 +372,15 @@ GeckoProfilerEntryMarker::GeckoProfilerEntryMarker(JSRuntime* rt,
profiler = nullptr; profiler = nullptr;
return; return;
} }
size_before = *profiler->size_; spBefore_ = profiler->stackPointer();
// We want to push a CPP frame so the profiler can correctly order JS and native stacks. // We want to push a CPP frame so the profiler can correctly order JS and native stacks.
profiler->beginPseudoJS("js::RunScript", this); profiler->pseudoStack_->pushCppFrame(
profiler->push("js::RunScript", /* dynamicString = */ nullptr, /* sp = */ nullptr, script, "js::RunScript", /* dynamicString = */ nullptr, /* sp = */ this, /* line = */ 0,
script->code()); js::ProfileEntry::Category::OTHER, js::ProfileEntry::BEGIN_PSEUDO_JS);
profiler->pseudoStack_->pushJsFrame(
"js::RunScript", /* dynamicString = */ nullptr, script, script->code());
} }
GeckoProfilerEntryMarker::~GeckoProfilerEntryMarker() GeckoProfilerEntryMarker::~GeckoProfilerEntryMarker()
@ -439,9 +388,9 @@ GeckoProfilerEntryMarker::~GeckoProfilerEntryMarker()
if (profiler == nullptr) if (profiler == nullptr)
return; return;
profiler->pop(); profiler->pseudoStack_->pop(); // the JS frame
profiler->endPseudoJS(); profiler->pseudoStack_->pop(); // the BEGIN_PSEUDO_JS frame
MOZ_ASSERT(size_before == *profiler->size_); MOZ_ASSERT(spBefore_ == profiler->stackPointer());
} }
AutoGeckoProfilerEntry::AutoGeckoProfilerEntry(JSRuntime* rt, const char* label, AutoGeckoProfilerEntry::AutoGeckoProfilerEntry(JSRuntime* rt, const char* label,
@ -454,10 +403,14 @@ AutoGeckoProfilerEntry::AutoGeckoProfilerEntry(JSRuntime* rt, const char* label,
profiler_ = nullptr; profiler_ = nullptr;
return; return;
} }
sizeBefore_ = *profiler_->size_; spBefore_ = profiler_->stackPointer();
profiler_->beginPseudoJS(label, this);
profiler_->push(label, /* dynamicString = */ nullptr, /* sp = */ this, /* script = */ nullptr, profiler_->pseudoStack_->pushCppFrame(
/* pc = */ nullptr, category); label, /* dynamicString = */ nullptr, /* sp = */ this, /* line = */ 0,
js::ProfileEntry::Category::OTHER, js::ProfileEntry::BEGIN_PSEUDO_JS);
profiler_->pseudoStack_->pushCppFrame(
label, /* dynamicString = */ nullptr, /* sp = */ this, /* line = */ 0, category);
} }
AutoGeckoProfilerEntry::~AutoGeckoProfilerEntry() AutoGeckoProfilerEntry::~AutoGeckoProfilerEntry()
@ -465,9 +418,9 @@ AutoGeckoProfilerEntry::~AutoGeckoProfilerEntry()
if (!profiler_) if (!profiler_)
return; return;
profiler_->pop(); profiler_->pseudoStack_->pop(); // the C++ frame
profiler_->endPseudoJS(); profiler_->pseudoStack_->pop(); // the BEGIN_PSEUDO_JS frame
MOZ_ASSERT(sizeBefore_ == *profiler_->size_); MOZ_ASSERT(spBefore_ == profiler_->stackPointer());
} }
GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(JSRuntime* rt, bool hasProfilerFrame GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(JSRuntime* rt, bool hasProfilerFrame
@ -475,18 +428,22 @@ GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(JSRuntime* rt, bo
: profiler(&rt->geckoProfiler()) : profiler(&rt->geckoProfiler())
{ {
MOZ_GUARD_OBJECT_NOTIFIER_INIT; MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (!hasProfilerFrame || !profiler->enabled() || if (!hasProfilerFrame || !profiler->enabled()) {
profiler->size() >= profiler->maxSize())
{
profiler = nullptr; profiler = nullptr;
return; return;
} }
size_before = profiler->size(); uint32_t sp = profiler->pseudoStack_->stackPointer;
if (profiler->size() == 0) if (sp >= PseudoStack::MaxEntries) {
profiler = nullptr;
return;
}
spBefore_ = sp;
if (sp == 0)
return; return;
ProfileEntry& entry = profiler->stack()[profiler->size() - 1]; volatile ProfileEntry& entry = profiler->pseudoStack_->entries[sp - 1];
MOZ_ASSERT(entry.isJs()); MOZ_ASSERT(entry.isJs());
entry.setOSR(); entry.setOSR();
} }
@ -496,11 +453,12 @@ GeckoProfilerBaselineOSRMarker::~GeckoProfilerBaselineOSRMarker()
if (profiler == nullptr) if (profiler == nullptr)
return; return;
MOZ_ASSERT(size_before == *profiler->size_); uint32_t sp = profiler->stackPointer();
if (profiler->size() == 0) MOZ_ASSERT(spBefore_ == sp);
if (sp == 0)
return; return;
ProfileEntry& entry = profiler->stack()[profiler->size() - 1]; volatile ProfileEntry& entry = profiler->stack()[sp - 1];
MOZ_ASSERT(entry.isJs()); MOZ_ASSERT(entry.isJs());
entry.unsetOSR(); entry.unsetOSR();
} }
@ -539,19 +497,24 @@ ProfileEntry::pc() const volatile
return script ? script->offsetToPC(lineOrPcOffset) : nullptr; return script ? script->offsetToPC(lineOrPcOffset) : nullptr;
} }
/* static */ int32_t
ProfileEntry::pcToOffset(JSScript* aScript, jsbytecode* aPc) {
return aPc ? aScript->pcToOffset(aPc) : NullPCOffset;
}
JS_FRIEND_API(void) JS_FRIEND_API(void)
ProfileEntry::setPC(jsbytecode* pc) volatile ProfileEntry::setPC(jsbytecode* pc) volatile
{ {
MOZ_ASSERT(isJs()); MOZ_ASSERT(isJs());
JSScript* script = this->script(); JSScript* script = this->script();
MOZ_ASSERT(script); // This should not be called while profiling is suppressed. MOZ_ASSERT(script); // This should not be called while profiling is suppressed.
lineOrPcOffset = pc == nullptr ? NullPCOffset : script->pcToOffset(pc); lineOrPcOffset = pcToOffset(script, pc);
} }
JS_FRIEND_API(void) JS_FRIEND_API(void)
js::SetContextProfilingStack(JSContext* cx, ProfileEntry* stack, mozilla::Atomic<uint32_t>* size, uint32_t max) js::SetContextProfilingStack(JSContext* cx, PseudoStack* pseudoStack)
{ {
cx->runtime()->geckoProfiler().setProfilingStack(stack, size, max); cx->runtime()->geckoProfiler().setProfilingStack(pseudoStack);
} }
JS_FRIEND_API(void) JS_FRIEND_API(void)

View file

@ -129,38 +129,24 @@ class GeckoProfiler
JSRuntime* rt; JSRuntime* rt;
ExclusiveData<ProfileStringMap> strings; ExclusiveData<ProfileStringMap> strings;
ProfileEntry* stack_; PseudoStack* pseudoStack_;
mozilla::Atomic<uint32_t>* size_;
uint32_t max_;
bool slowAssertions; bool slowAssertions;
uint32_t enabled_; uint32_t enabled_;
void (*eventMarker_)(const char*); void (*eventMarker_)(const char*);
UniqueChars allocProfileString(JSScript* script, JSFunction* function); UniqueChars allocProfileString(JSScript* script, JSFunction* function);
void push(const char* label, const char* dynamicString, void* sp, JSScript* script,
jsbytecode* pc, ProfileEntry::Category category = ProfileEntry::Category::JS);
void pop();
public: public:
explicit GeckoProfiler(JSRuntime* rt); explicit GeckoProfiler(JSRuntime* rt);
bool init(); bool init();
uint32_t* addressOfMaxSize() { uint32_t stackPointer() { MOZ_ASSERT(installed()); return pseudoStack_->stackPointer; }
return &max_; volatile ProfileEntry* stack() { return pseudoStack_->entries; }
}
ProfileEntry** addressOfStack() {
return &stack_;
}
uint32_t maxSize() { return max_; }
uint32_t size() { MOZ_ASSERT(installed()); return *size_; }
ProfileEntry* stack() { return stack_; }
/* management of whether instrumentation is on or off */ /* management of whether instrumentation is on or off */
bool enabled() { MOZ_ASSERT_IF(enabled_, installed()); return enabled_; } bool enabled() { MOZ_ASSERT_IF(enabled_, installed()); return enabled_; }
bool installed() { return stack_ != nullptr && size_ != nullptr; } bool installed() { return pseudoStack_ != nullptr; }
MOZ_MUST_USE bool enable(bool enabled); MOZ_MUST_USE bool enable(bool enabled);
void enableSlowAssertions(bool enabled) { slowAssertions = enabled; } void enableSlowAssertions(bool enabled) { slowAssertions = enabled; }
bool slowAssertionsEnabled() { return slowAssertions; } bool slowAssertionsEnabled() { return slowAssertions; }
@ -177,18 +163,18 @@ class GeckoProfiler
bool enter(JSContext* cx, JSScript* script, JSFunction* maybeFun); bool enter(JSContext* cx, JSScript* script, JSFunction* maybeFun);
void exit(JSScript* script, JSFunction* maybeFun); void exit(JSScript* script, JSFunction* maybeFun);
void updatePC(JSScript* script, jsbytecode* pc) { void updatePC(JSScript* script, jsbytecode* pc) {
if (enabled() && *size_ - 1 < max_) { if (!enabled())
MOZ_ASSERT(*size_ > 0); return;
MOZ_ASSERT(stack_[*size_ - 1].rawScript() == script);
stack_[*size_ - 1].setPC(pc); uint32_t sp = pseudoStack_->stackPointer;
if (sp - 1 < PseudoStack::MaxEntries) {
MOZ_ASSERT(sp > 0);
MOZ_ASSERT(pseudoStack_->entries[sp - 1].rawScript() == script);
pseudoStack_->entries[sp - 1].setPC(pc);
} }
} }
/* Enter wasm code */ void setProfilingStack(PseudoStack* pseudoStack);
void beginPseudoJS(const char* label, void* sp); // label must be a static string!
void endPseudoJS() { pop(); }
void setProfilingStack(ProfileEntry* stack, mozilla::Atomic<uint32_t>* size, uint32_t max);
void setEventMarker(void (*fn)(const char*)); void setEventMarker(void (*fn)(const char*));
const char* profileString(JSScript* script, JSFunction* maybeFun); const char* profileString(JSScript* script, JSFunction* maybeFun);
void onScriptFinalized(JSScript* script); void onScriptFinalized(JSScript* script);
@ -203,7 +189,7 @@ class GeckoProfiler
return &enabled_; return &enabled_;
} }
void trace(JSTracer* trc); void trace(JSTracer* trc) volatile;
void fixupStringsMapAfterMovingGC(); void fixupStringsMapAfterMovingGC();
#ifdef JSGC_HASH_TABLE_CHECKS #ifdef JSGC_HASH_TABLE_CHECKS
void checkStringsMapAfterMovingGC(); void checkStringsMapAfterMovingGC();
@ -237,7 +223,7 @@ class MOZ_RAII GeckoProfilerEntryMarker
private: private:
GeckoProfiler* profiler; GeckoProfiler* profiler;
mozilla::DebugOnly<uint32_t> size_before; mozilla::DebugOnly<uint32_t> spBefore_;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
}; };
@ -256,7 +242,7 @@ class MOZ_NONHEAP_CLASS AutoGeckoProfilerEntry
private: private:
GeckoProfiler* profiler_; GeckoProfiler* profiler_;
mozilla::DebugOnly<uint32_t> sizeBefore_; mozilla::DebugOnly<uint32_t> spBefore_;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
}; };
@ -274,7 +260,7 @@ class MOZ_RAII GeckoProfilerBaselineOSRMarker
private: private:
GeckoProfiler* profiler; GeckoProfiler* profiler;
mozilla::DebugOnly<uint32_t> size_before; mozilla::DebugOnly<uint32_t> spBefore_;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
}; };

View file

@ -230,11 +230,10 @@ public:
mContext = aContext; mContext = aContext;
js::SetContextProfilingStack( // We give the JS engine a non-owning reference to the RacyInfo (just the
aContext, // PseudoStack, really). It's important that the JS engine doesn't touch
(js::ProfileEntry*) RacyInfo()->entries, // this once the thread dies.
RacyInfo()->AddressOfStackPointer(), js::SetContextProfilingStack(aContext, RacyInfo());
(uint32_t) mozilla::ArrayLength(RacyInfo()->entries));
PollJSSampling(); PollJSSampling();
} }

View file

@ -415,7 +415,7 @@ extern MOZ_THREAD_LOCAL(PseudoStack*) sPseudoStack;
// necessarily bounded by the lifetime of the thread, which ensures that the // necessarily bounded by the lifetime of the thread, which ensures that the
// references held can't be used after the PseudoStack is destroyed. // references held can't be used after the PseudoStack is destroyed.
inline void* inline void*
profiler_call_enter(const char* aInfo, js::ProfileEntry::Category aCategory, profiler_call_enter(const char* aLabel, js::ProfileEntry::Category aCategory,
void* aFrameAddress, uint32_t aLine, void* aFrameAddress, uint32_t aLine,
const char* aDynamicString = nullptr) const char* aDynamicString = nullptr)
{ {
@ -425,7 +425,7 @@ profiler_call_enter(const char* aInfo, js::ProfileEntry::Category aCategory,
if (!pseudoStack) { if (!pseudoStack) {
return pseudoStack; return pseudoStack;
} }
pseudoStack->push(aInfo, aCategory, aFrameAddress, aLine, aDynamicString); pseudoStack->pushCppFrame(aLabel, aDynamicString, aFrameAddress, aLine, aCategory);
// The handle is meant to support future changes but for now it is simply // The handle is meant to support future changes but for now it is simply
// used to avoid having to call TLSInfo::RacyInfo() in profiler_call_exit(). // used to avoid having to call TLSInfo::RacyInfo() in profiler_call_exit().