Bug 1177488 - use |const char*| for representing async call reasons; r=bz,fitzgen

Using a simple |const char*| is more memory-efficient than allocating a
JS string. We still have to allocate the JS string for passing things
into JS, but ideally we will be able to move the point of allocation
much closer to where it's actually needed, rather than indiscriminantly
doing it all the time.
This commit is contained in:
Nathan Froyd 2016-03-23 10:40:53 -04:00
parent 16d14681ea
commit cccdd9fbca
20 changed files with 79 additions and 50 deletions

View File

@ -14108,13 +14108,14 @@ nsDocShell::GetOpener()
return opener;
}
// The caller owns |aAsyncCause| here.
void
nsDocShell::NotifyJSRunToCompletionStart(const char* aReason,
const char16_t* aFunctionName,
const char16_t* aFilename,
const uint32_t aLineNumber,
JS::Handle<JS::Value> aAsyncStack,
JS::Handle<JS::Value> aAsyncCause)
const char* aAsyncCause)
{
// If first start, mark interval start.
if (mJSRunToCompletionDepth == 0) {

View File

@ -1059,7 +1059,7 @@ interface nsIDocShell : nsIDocShellTreeItem
in wstring fileName,
in unsigned long lineNumber,
in jsval asyncStack,
in jsval asyncCause);
in string asyncCause);
[noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStop();
/**

View File

@ -17,23 +17,25 @@ namespace mozilla {
class JavascriptTimelineMarker : public TimelineMarker
{
public:
// The caller owns |aAsyncCause| here, so we must copy it into a separate
// string for use later on.
JavascriptTimelineMarker(const char* aReason,
const char16_t* aFunctionName,
const char16_t* aFileName,
uint32_t aLineNumber,
MarkerTracingType aTracingType,
JS::Handle<JS::Value> aAsyncStack,
JS::Handle<JS::Value> aAsyncCause)
const char* aAsyncCause)
: TimelineMarker("Javascript", aTracingType, MarkerStackRequest::NO_STACK)
, mCause(NS_ConvertUTF8toUTF16(aReason))
, mFunctionName(aFunctionName)
, mFileName(aFileName)
, mLineNumber(aLineNumber)
, mAsyncCause(aAsyncCause)
{
JSContext* ctx = nsContentUtils::GetCurrentJSContext();
if (ctx) {
mAsyncStack.init(ctx, aAsyncStack);
mAsyncCause.init(ctx, aAsyncCause);
}
}
@ -50,10 +52,16 @@ public:
stackFrame.mFunctionDisplayName.Construct(mFunctionName);
if (mAsyncStack.isObject() && !mAsyncStack.isNullOrUndefined() &&
mAsyncCause.isString()) {
!mAsyncCause.IsEmpty()) {
JS::Rooted<JSObject*> asyncStack(aCx, mAsyncStack.toObjectOrNull());
JS::Rooted<JSString*> asyncCause(aCx, mAsyncCause.toString());
JS::Rooted<JSObject*> parentFrame(aCx);
JS::Rooted<JSString*> asyncCause(aCx, JS_NewUCStringCopyN(aCx, mAsyncCause.BeginReading(),
mAsyncCause.Length()));
if (!asyncCause) {
JS_ClearPendingException(aCx);
return;
}
if (!JS::CopyAsyncStack(aCx, asyncStack, asyncCause, &parentFrame, 0)) {
JS_ClearPendingException(aCx);
} else {
@ -78,7 +86,7 @@ private:
nsString mFileName;
uint32_t mLineNumber;
JS::PersistentRooted<JS::Value> mAsyncStack;
JS::PersistentRooted<JS::Value> mAsyncCause;
NS_ConvertUTF8toUTF16 mAsyncCause;
};
} // namespace mozilla

View File

@ -683,7 +683,7 @@ AutoEntryScript::DocshellEntryMonitor::DocshellEntryMonitor(JSContext* aCx,
void
AutoEntryScript::DocshellEntryMonitor::Entry(JSContext* aCx, JSFunction* aFunction,
JSScript* aScript, JS::Handle<JS::Value> aAsyncStack,
JS::Handle<JSString*> aAsyncCause)
const char* aAsyncCause)
{
JS::Rooted<JSFunction*> rootedFunction(aCx);
if (aFunction) {
@ -728,13 +728,11 @@ AutoEntryScript::DocshellEntryMonitor::Entry(JSContext* aCx, JSFunction* aFuncti
const char16_t* functionNameChars = functionName.isTwoByte() ?
functionName.twoByteChars() : nullptr;
JS::Rooted<JS::Value> asyncCauseValue(aCx, aAsyncCause ? StringValue(aAsyncCause) :
JS::NullValue());
docShellForJSRunToCompletion->NotifyJSRunToCompletionStart(mReason,
functionNameChars,
filename.BeginReading(),
lineNumber, aAsyncStack,
asyncCauseValue);
aAsyncCause);
}
}

View File

@ -356,16 +356,21 @@ private:
public:
DocshellEntryMonitor(JSContext* aCx, const char* aReason);
// Please note that |aAsyncCause| here is owned by the caller, and its
// lifetime must outlive the lifetime of the DocshellEntryMonitor object.
// In practice, |aAsyncCause| is identical to |aReason| passed into
// the AutoEntryScript constructor, so the lifetime requirements are
// trivially satisfied by |aReason| being a statically allocated string.
void Entry(JSContext* aCx, JSFunction* aFunction,
JS::Handle<JS::Value> aAsyncStack,
JS::Handle<JSString*> aAsyncCause) override
const char* aAsyncCause) override
{
Entry(aCx, aFunction, nullptr, aAsyncStack, aAsyncCause);
}
void Entry(JSContext* aCx, JSScript* aScript,
JS::Handle<JS::Value> aAsyncStack,
JS::Handle<JSString*> aAsyncCause) override
const char* aAsyncCause) override
{
Entry(aCx, nullptr, aScript, aAsyncStack, aAsyncCause);
}
@ -375,7 +380,7 @@ private:
private:
void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript,
JS::Handle<JS::Value> aAsyncStack,
JS::Handle<JSString*> aAsyncCause);
const char* aAsyncCause);
const char* mReason;
};

View File

@ -172,12 +172,7 @@ CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback,
mAsyncStack.emplace(cx, aCallback->GetCreationStack());
if (*mAsyncStack) {
mAsyncCause.emplace(cx, JS_NewStringCopyZ(cx, aExecutionReason));
if (*mAsyncCause) {
mAsyncStackSetter.emplace(cx, *mAsyncStack, *mAsyncCause);
} else {
JS_ClearPendingException(cx);
}
mAsyncStackSetter.emplace(cx, *mAsyncStack, aExecutionReason);
}
// Enter the compartment of our callback, so we can actually work with it.

View File

@ -257,7 +257,6 @@ protected:
// Members which are used to set the async stack.
Maybe<JS::Rooted<JSObject*>> mAsyncStack;
Maybe<JS::Rooted<JSString*>> mAsyncCause;
Maybe<JS::AutoSetAsyncStackForNewCalls> mAsyncStackSetter;
// Can't construct a JSAutoCompartment without a JSContext either. Also,

View File

@ -96,16 +96,11 @@ protected:
}
JS::Rooted<JSObject*> asyncStack(cx, mPromise->mAllocationStack);
JS::Rooted<JSString*> asyncCause(cx, JS_NewStringCopyZ(cx, "Promise"));
if (!asyncCause) {
JS_ClearPendingException(cx);
return NS_ERROR_OUT_OF_MEMORY;
}
{
Maybe<JS::AutoSetAsyncStackForNewCalls> sas;
if (asyncStack) {
sas.emplace(cx, asyncStack, asyncCause);
sas.emplace(cx, asyncStack, "Promise");
}
mCallback->Call(cx, value);
}

View File

@ -351,20 +351,25 @@ class MOZ_STACK_CLASS AutoEntryMonitor {
// SpiderMonkey reports the JavaScript entry points occuring within this
// AutoEntryMonitor's scope to the following member functions, which the
// embedding is expected to override.
//
// It is important to note that |asyncCause| is owned by the caller and its
// lifetime must outlive the lifetime of the AutoEntryMonitor object. It is
// strongly encouraged that |asyncCause| be a string constant or similar
// statically allocated string.
// We have begun executing |function|. Note that |function| may not be the
// actual closure we are running, but only the canonical function object to
// which the script refers.
virtual void Entry(JSContext* cx, JSFunction* function,
HandleValue asyncStack,
HandleString asyncCause) = 0;
const char* asyncCause) = 0;
// Execution has begun at the entry point of |script|, which is not a
// function body. (This is probably being executed by 'eval' or some
// JSAPI equivalent.)
virtual void Entry(JSContext* cx, JSScript* script,
HandleValue asyncStack,
HandleString asyncCause) = 0;
const char* asyncCause) = 0;
// Execution of the function or script has ended.
virtual void Exit(JSContext* cx) { }

View File

@ -1122,8 +1122,13 @@ CallFunctionWithAsyncStack(JSContext* cx, unsigned argc, Value* vp)
RootedObject function(cx, &args[0].toObject());
RootedObject stack(cx, &args[1].toObject());
RootedString asyncCause(cx, args[2].toString());
JSAutoByteString utf8Cause;
if (!utf8Cause.encodeUtf8(cx, asyncCause)) {
MOZ_ASSERT(cx->isExceptionPending());
return false;
}
JS::AutoSetAsyncStackForNewCalls sas(cx, stack, asyncCause,
JS::AutoSetAsyncStackForNewCalls sas(cx, stack, utf8Cause.ptr(),
JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);
return Call(cx, UndefinedHandleValue, function,
JS::HandleValueArray::empty(), args.rval());

View File

@ -4865,11 +4865,11 @@ JS_RestoreFrameChain(JSContext* cx)
}
JS::AutoSetAsyncStackForNewCalls::AutoSetAsyncStackForNewCalls(
JSContext* cx, HandleObject stack, HandleString asyncCause,
JSContext* cx, HandleObject stack, const char* asyncCause,
JS::AutoSetAsyncStackForNewCalls::AsyncCallKind kind)
: cx(cx),
oldAsyncStack(cx, cx->runtime()->asyncStackForNewActivations),
oldAsyncCause(cx, cx->runtime()->asyncCauseForNewActivations),
oldAsyncCause(cx->runtime()->asyncCauseForNewActivations),
oldAsyncCallIsExplicit(cx->runtime()->asyncCallIsExplicit)
{
CHECK_REQUEST(cx);
@ -4881,7 +4881,6 @@ JS::AutoSetAsyncStackForNewCalls::AutoSetAsyncStackForNewCalls(
return;
SavedFrame* asyncStack = &stack->as<SavedFrame>();
MOZ_ASSERT(!asyncCause->empty());
cx->runtime()->asyncStackForNewActivations = asyncStack;
cx->runtime()->asyncCauseForNewActivations = asyncCause;

View File

@ -4479,7 +4479,7 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(AutoSetAsyncStackForNewCalls)
{
JSContext* cx;
RootedObject oldAsyncStack;
RootedString oldAsyncCause;
const char* oldAsyncCause;
bool oldAsyncCallIsExplicit;
public:
@ -4496,8 +4496,13 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(AutoSetAsyncStackForNewCalls)
// ambiguous whether that would clear any scheduled async stack and make the
// normal stack reappear in the new call, or just keep the async stack
// already scheduled for the new call, if any.
//
// asyncCause is owned by the caller and its lifetime must outlive the
// lifetime of the AutoSetAsyncStackForNewCalls object. It is strongly
// encouraged that asyncCause be a string constant or similar statically
// allocated string.
AutoSetAsyncStackForNewCalls(JSContext* cx, HandleObject stack,
HandleString asyncCause,
const char* asyncCause,
AsyncCallKind kind = AsyncCallKind::IMPLICIT);
~AutoSetAsyncStackForNewCalls();
};

View File

@ -4959,7 +4959,7 @@ class ShellAutoEntryMonitor : JS::dbg::AutoEntryMonitor {
}
void Entry(JSContext* cx, JSFunction* function, JS::HandleValue asyncStack,
JS::HandleString asyncCause) override {
const char* asyncCause) override {
MOZ_ASSERT(!enteredWithoutExit);
enteredWithoutExit = true;
@ -4974,7 +4974,7 @@ class ShellAutoEntryMonitor : JS::dbg::AutoEntryMonitor {
}
void Entry(JSContext* cx, JSScript* script, JS::HandleValue asyncStack,
JS::HandleString asyncCause) override {
const char* asyncCause) override {
MOZ_ASSERT(!enteredWithoutExit);
enteredWithoutExit = true;

View File

@ -138,7 +138,7 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
profilerSampleBufferLapCount_(1),
wasmActivationStack_(nullptr),
asyncStackForNewActivations(this),
asyncCauseForNewActivations(this),
asyncCauseForNewActivations(nullptr),
asyncCallIsExplicit(false),
entryMonitor(nullptr),
noExecuteDebuggerTop(nullptr),

View File

@ -708,7 +708,7 @@ struct JSRuntime : public JS::shadow::Runtime,
/*
* Value of asyncCause to be attached to asyncStackForNewActivations.
*/
JS::PersistentRooted<JSString*> asyncCauseForNewActivations;
const char* asyncCauseForNewActivations;
/*
* True if the async call was explicitly requested, e.g. via

View File

@ -25,6 +25,7 @@
#include "gc/Marking.h"
#include "gc/Policy.h"
#include "gc/Rooting.h"
#include "js/CharacterEncoding.h"
#include "js/Vector.h"
#include "vm/Debugger.h"
#include "vm/SavedFrame.h"
@ -1164,7 +1165,23 @@ SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFram
// youngest frame of the async stack as the parent of the oldest
// frame of this activation. We still need to iterate over other
// frames in this activation before reaching the oldest frame.
asyncCause = activation.asyncCause();
AutoCompartment ac(cx, iter.compartment());
const char* cause = activation.asyncCause();
UTF8Chars utf8Chars(cause, strlen(cause));
size_t twoByteCharsLen = 0;
char16_t* twoByteChars = UTF8CharsToNewTwoByteCharsZ(cx, utf8Chars,
&twoByteCharsLen).get();
if (!twoByteChars)
return false;
// We expect that there will be a relatively small set of
// asyncCause reasons ("setTimeout", "promise", etc.), so we
// atomize the cause here in hopes of being able to benefit
// from reuse.
asyncCause = JS_AtomizeUCStringN(cx, twoByteChars, twoByteCharsLen);
js_free(twoByteChars);
if (!asyncCause)
return false;
asyncActivation = &activation;
}
}

View File

@ -873,7 +873,7 @@ Activation::Activation(JSContext* cx, Kind kind)
hideScriptedCallerCount_(0),
frameCache_(cx),
asyncStack_(cx, cx->runtime_->asyncStackForNewActivations),
asyncCause_(cx, cx->runtime_->asyncCauseForNewActivations),
asyncCause_(cx->runtime_->asyncCauseForNewActivations),
asyncCallIsExplicit_(cx->runtime_->asyncCallIsExplicit),
kind_(kind)
{

View File

@ -1401,7 +1401,7 @@ ActivationEntryMonitor::ActivationEntryMonitor(JSContext* cx, InterpreterFrame*
// be traced if we trigger GC here. Suppress GC to avoid this.
gc::AutoSuppressGC suppressGC(cx);
RootedValue stack(cx, asyncStack(cx));
RootedString asyncCause(cx, cx->runtime()->asyncCauseForNewActivations);
const char* asyncCause = cx->runtime()->asyncCauseForNewActivations;
if (entryFrame->isFunctionFrame())
entryMonitor_->Entry(cx, &entryFrame->callee(), stack, asyncCause);
else
@ -1417,7 +1417,7 @@ ActivationEntryMonitor::ActivationEntryMonitor(JSContext* cx, jit::CalleeToken e
// a GC to discard the code we're about to enter, so we suppress GC.
gc::AutoSuppressGC suppressGC(cx);
RootedValue stack(cx, asyncStack(cx));
RootedString asyncCause(cx, cx->runtime()->asyncCauseForNewActivations);
const char* asyncCause = cx->runtime()->asyncCauseForNewActivations;
if (jit::CalleeTokenIsFunction(entryToken))
entryMonitor_->Entry(cx_, jit::CalleeTokenToFunction(entryToken), stack, asyncCause);
else

View File

@ -1237,7 +1237,7 @@ class Activation
Rooted<SavedFrame*> asyncStack_;
// Value of asyncCause to be attached to asyncStack_.
RootedString asyncCause_;
const char* asyncCause_;
// True if the async call was explicitly requested, e.g. via
// callFunctionWithAsyncStack.
@ -1319,7 +1319,7 @@ class Activation
return asyncStack_;
}
JSString* asyncCause() {
const char* asyncCause() const {
return asyncCause_;
}

View File

@ -2712,12 +2712,9 @@ nsXPCComponents_Utils::CallFunctionWithAsyncStack(HandleValue function,
}
JS::Rooted<JSObject*> asyncStackObj(cx, &asyncStack.toObject());
JS::Rooted<JSString*> asyncCauseString(cx, JS_NewUCStringCopyN(cx, asyncCause.BeginReading(),
asyncCause.Length()));
if (!asyncCauseString)
return NS_ERROR_OUT_OF_MEMORY;
JS::AutoSetAsyncStackForNewCalls sas(cx, asyncStackObj, asyncCauseString,
NS_ConvertUTF16toUTF8 utf8Cause(asyncCause);
JS::AutoSetAsyncStackForNewCalls sas(cx, asyncStackObj, utf8Cause.get(),
JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);
if (!JS_CallFunctionValue(cx, nullptr, function,