From 642276f276cedc93b4d470ab8b2c06f9ce5ad5c2 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 16 Jun 2011 13:01:04 +1000 Subject: [PATCH] Bug 571249 - Add memory reporters for JSScripts, non-fixed object slot arrays, and string chars. r=igor. --- js/src/jsgc.h | 2 +- js/src/jsscript.cpp | 30 ++++- js/src/jsscript.h | 8 +- js/src/xpconnect/src/xpcjsruntime.cpp | 117 ++++++++++++++++++ .../tests/chrome/test_aboutmemory.xul | 4 + 5 files changed, 152 insertions(+), 9 deletions(-) diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 11d491aabf8d..f03ca539a03c 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -1258,7 +1258,7 @@ typedef void (*IterateCallback)(JSContext *cx, void *data, size_t traceKind, voi * selected. The mask should be constructed by ORing |TraceKindMask(...)| * results. */ -void +extern JS_FRIEND_API(void) IterateCells(JSContext *cx, JSCompartment *comp, uint64 traceKindMask, void *data, IterateCallback callback); diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index b8a869236bf9..b310c993bdf2 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -320,7 +320,6 @@ js_XDRScript(JSXDRState *xdr, JSScript **scriptp) uint16 nClosedArgs = 0, nClosedVars = 0; JSPrincipals *principals; uint32 encodeable; - jssrcnote *sn; JSSecurityCallbacks *callbacks; uint32 scriptBits = 0; @@ -471,12 +470,8 @@ js_XDRScript(JSXDRState *xdr, JSScript **scriptp) nslots = (uint32)((script->staticLevel << 16) | script->nslots); natoms = (uint32)script->atomMap.length; - /* Count the srcnotes, keeping notes pointing at the first one. */ notes = script->notes(); - for (sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) - continue; - nsrcnotes = sn - notes; - nsrcnotes++; /* room for the terminator */ + nsrcnotes = script->numNotes(); if (JSScript::isValidOffset(script->objectsOffset)) nobjects = script->objects()->length; @@ -1422,6 +1417,29 @@ bad: return NULL; } +size_t +JSScript::totalSize() +{ + return code + + length * sizeof(jsbytecode) + + numNotes() * sizeof(jssrcnote) - + (uint8 *) this; +} + +/* + * Nb: srcnotes are variable-length. This function computes the number of + * srcnote *slots*, which may be greater than the number of srcnotes. + */ +uint32 +JSScript::numNotes() +{ + jssrcnote *sn; + jssrcnote *notes_ = notes(); + for (sn = notes_; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) + continue; + return sn - notes_ + 1; /* +1 for the terminator */ +} + JS_FRIEND_API(void) js_CallNewScriptHook(JSContext *cx, JSScript *script, JSFunction *fun) { diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 3f0cecc013b8..74cfd08a68bc 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -458,16 +458,17 @@ struct JSScript { private: uint16 version; /* JS version under which script was compiled */ - size_t callCount_; /* Number of times the script has been called. */ - public: uint16 nfixed; /* number of slots besides stack operands in slot array */ + private: + size_t callCount_; /* Number of times the script has been called. */ /* * Offsets to various array structures from the end of this script, or * JSScript::INVALID_OFFSET if the array has length 0. */ + public: uint8 objectsOffset; /* offset to the array of nested function, block, scope, xml and one-time regexps objects */ @@ -576,6 +577,9 @@ struct JSScript { } #endif + JS_FRIEND_API(size_t) totalSize(); /* Size of the JSScript and all sections */ + uint32 numNotes(); /* Number of srcnote slots in the srcnotes section */ + /* Script notes are allocated right after the code. */ jssrcnote *notes() { return (jssrcnote *)(code + length); } diff --git a/js/src/xpconnect/src/xpcjsruntime.cpp b/js/src/xpconnect/src/xpcjsruntime.cpp index 89467f99ec3b..edfa86d55c63 100644 --- a/js/src/xpconnect/src/xpcjsruntime.cpp +++ b/js/src/xpconnect/src/xpcjsruntime.cpp @@ -1303,6 +1303,119 @@ NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSStack, GetJSStack, NULL) +static PRInt64 +GetCompartmentScriptsSize(JSCompartment *c) +{ + PRInt64 n = 0; + for (JSScript *script = (JSScript *)c->scripts.next; + &script->links != &c->scripts; + script = (JSScript *)script->links.next) + { + n += script->totalSize(); + } + return n; +} + +static PRInt64 +GetJSScripts(void *data) +{ + return GetPerCompartmentSize(GetCompartmentScriptsSize); +} + +struct PRInt64Data { + PRInt64Data() : n(0) { } + PRInt64 n; +}; + +void +GetJSObjectSlotsCallback(JSContext *cx, void *v, size_t traceKind, void *thing) +{ + JS_ASSERT(traceKind == JSTRACE_OBJECT); + JSObject *obj = (JSObject *)thing; + if (obj->hasSlotsArray()) { + PRInt64Data *data = (PRInt64Data *) v; + data->n += obj->numSlots() * sizeof(js::Value); + } +} + +static PRInt64 +GetJSObjectSlots(void *dummy) +{ + JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime(); + JSContext *cx = JS_NewContext(rt, 0); + if (!cx) { + NS_ERROR("couldn't create context for memory tracing"); + return (PRInt64) -1; + } + + PRInt64Data data; + js::IterateCells(cx, NULL, js::TraceKindMask(JSTRACE_OBJECT), &data, + *GetJSObjectSlotsCallback); + + JS_DestroyContextNoGC(cx); + + return data.n; +} + +void +GetJSStringCharsCallback(JSContext *cx, void *v, size_t traceKind, void *thing) +{ + JS_ASSERT(traceKind == JSTRACE_STRING); + JSString *str = (JSString *)thing; + PRInt64Data *data = (PRInt64Data *) v; + data->n += str->charsHeapSize(); +} + +static PRInt64 +GetJSStringChars(void *dummy) +{ + JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime(); + JSContext *cx = JS_NewContext(rt, 0); + if (!cx) { + NS_ERROR("couldn't create context for memory tracing"); + return (PRInt64) -1; + } + + PRInt64Data data; + js::IterateCells(cx, NULL, js::TraceKindMask(JSTRACE_STRING), &data, + *GetJSStringCharsCallback); + + JS_DestroyContextNoGC(cx); + + return data.n; +} + +NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSScripts, + "explicit/js/scripts", + MR_HEAP, + "Memory allocated for JSScripts. A JSScript is created for each " + "user-defined function in a script. One is also created for " + "the top-level code in a script. Each JSScript includes byte-code and " + "various other things.", + GetJSScripts, + NULL) + +NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSObjectSlots, + "explicit/js/object-slots", + MR_HEAP, + "Memory allocated for non-fixed object slot arrays, which are used " + "to represent object properties. Some objects also contain a fixed " + "number of slots which are stored on the JavaScript heap; those slots " + "are not counted here.", + GetJSObjectSlots, + NULL) + +NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSStringChars, + "explicit/js/string-chars", + MR_HEAP, + "Memory allocated to hold string characters. Not all of this allocated " + "memory is necessarily used to hold characters. Each string also " + "includes a header which is stored on the JavaScript heap; that header " + "is not counted here.", + GetJSStringChars, + NULL) + + #ifdef JS_METHODJIT static PRInt64 @@ -1476,6 +1589,10 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect) NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSGCHeap)); NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSStack)); + NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSScripts)); + // XXX: these two are disabled due to crashes. See bug 664647. + //NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSObjectSlots)); + //NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSStringChars)); #ifdef JS_METHODJIT NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSMjitCode)); NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSMjitData)); diff --git a/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul b/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul index f465432e5970..1782147302ec 100644 --- a/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul +++ b/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul @@ -21,8 +21,12 @@ // Remove all the real reporters; save them to restore at the end. var e = mgr.enumerateReporters(); var realReporters = []; + var dummy = 0; while (e.hasMoreElements()) { var mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter); + // Get the memoryUsed field, even though we don't use it, just to test + // that the reporter doesn't crash or anything. + dummy += mr.memoryUsed; mgr.unregisterReporter(mr); realReporters.push(mr); }