Merge the last PGO-green inbound changeset to m-c.

This commit is contained in:
Ryan VanderMeulen 2012-07-08 13:48:33 -04:00
commit d0a6521d3c
51 changed files with 1981 additions and 634 deletions

View File

@ -4223,7 +4223,7 @@ MOZ_WAVE=1
MOZ_MEDIA=
MOZ_OPUS=1
MOZ_WEBM=1
MOZ_WEBRTC=
MOZ_WEBRTC=1
MOZ_WEBRTC_SIGNALING=
MOZ_MEDIA_PLUGINS=
MOZ_MEDIA_NAVIGATOR=
@ -4297,6 +4297,7 @@ case "${target}" in
MOZ_TREE_FREETYPE=1
MOZ_MEMORY=1
MOZ_RAW=1
MOZ_WEBRTC=
;;
esac
@ -5248,12 +5249,12 @@ if test "$NS_PRINTING"; then
fi
dnl ========================================================
dnl = Enable WebRTC code
dnl = Disable WebRTC code
dnl ========================================================
MOZ_ARG_ENABLE_BOOL(webrtc,
[ --enable-webrtc Enable support for WebRTC],
MOZ_WEBRTC=1,
MOZ_WEBRTC=)
MOZ_ARG_DISABLE_BOOL(webrtc,
[ --disable-webrtc Disable support for WebRTC],
MOZ_WEBRTC=,
MOZ_WEBRTC=1)
if test -n "$MOZ_WEBRTC"; then
AC_DEFINE(MOZ_WEBRTC)

View File

@ -1198,6 +1198,9 @@ nsContentSubtreeIterator::Init(nsIDOMRange* aRange)
nsINode* endParent = mRange->GetEndParent();
PRInt32 endOffset = mRange->EndOffset();
MOZ_ASSERT(mCommonParent && startParent && endParent);
// Bug 767169
MOZ_ASSERT(startOffset <= startParent->Length() &&
endOffset <= endParent->Length());
// short circuit when start node == end node
if (startParent == endParent) {
@ -1268,9 +1271,7 @@ nsContentSubtreeIterator::Init(nsIDOMRange* aRange)
PRInt32 numChildren = endParent->GetChildCount();
if (offset > numChildren) {
// Can happen for text nodes -- or if we're being called from
// nsNodeUtils::ContentRemoved and the range hasn't been adjusted yet (bug
// 767169).
// Can happen for text nodes
offset = numChildren;
}
if (!offset || !numChildren) {

View File

@ -699,10 +699,9 @@ SVGPathData::GetMarkerPositioningData(nsTArray<nsSVGMark> *aMarks) const
double cyp = -root * ry * x1p / rx;
double theta, delta;
theta = AngleOfVector(gfxPoint((x1p-cxp)/rx, (y1p-cyp)/ry) - // F.6.5.5
gfxPoint(1.0, 0.0));
delta = AngleOfVector(gfxPoint((-x1p-cxp)/rx, (-y1p-cyp)/ry) - // F.6.5.6
gfxPoint((x1p-cxp)/rx, (y1p-cyp)/ry));
theta = AngleOfVector(gfxPoint((x1p-cxp)/rx, (y1p-cyp)/ry)); // F.6.5.5
delta = AngleOfVector(gfxPoint((-x1p-cxp)/rx, (-y1p-cyp)/ry)) - // F.6.5.6
theta;
if (!sweepFlag && delta > 0)
delta -= 2.0 * M_PI;
else if (sweepFlag && delta < 0)

View File

@ -31,64 +31,81 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/ConsoleAPIStorage.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
let gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
/**
* The window.console API implementation. One instance is lazily created for
* every inner window, when the window.console object is accessed.
*/
function ConsoleAPI() {}
ConsoleAPI.prototype = {
classID: Components.ID("{b49c18f8-3379-4fc0-8c90-d7772c1a9ff3}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer]),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsISupportsWeakReference,
Ci.nsIObserver]),
_timerInitialized: false,
_queuedCalls: null,
_timerCallback: null,
_destroyedWindows: null,
_window: null,
_innerID: null,
_outerID: null,
_windowDestroyed: false,
_timer: null,
// nsIDOMGlobalPropertyInitializer
init: function CA_init(aWindow) {
Services.obs.addObserver(this, "xpcom-shutdown", false);
Services.obs.addObserver(this, "inner-window-destroyed", false);
Services.obs.addObserver(this, "inner-window-destroyed", true);
try {
let windowUtils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
this._outerID = windowUtils.outerWindowID;
this._innerID = windowUtils.currentInnerWindowID;
}
catch (ex) {
Cu.reportError(ex);
}
let self = this;
let chromeObject = {
// window.console API
log: function CA_log() {
self.queueCall("log", arguments, aWindow);
self.queueCall("log", arguments);
},
info: function CA_info() {
self.queueCall("info", arguments, aWindow);
self.queueCall("info", arguments);
},
warn: function CA_warn() {
self.queueCall("warn", arguments, aWindow);
self.queueCall("warn", arguments);
},
error: function CA_error() {
self.queueCall("error", arguments, aWindow);
self.queueCall("error", arguments);
},
debug: function CA_debug() {
self.queueCall("debug", arguments, aWindow);
self.queueCall("debug", arguments);
},
trace: function CA_trace() {
self.queueCall("trace", arguments, aWindow);
self.queueCall("trace", arguments);
},
// Displays an interactive listing of all the properties of an object.
dir: function CA_dir() {
self.queueCall("dir", arguments, aWindow);
self.queueCall("dir", arguments);
},
group: function CA_group() {
self.queueCall("group", arguments, aWindow);
self.queueCall("group", arguments);
},
groupCollapsed: function CA_groupCollapsed() {
self.queueCall("groupCollapsed", arguments, aWindow);
self.queueCall("groupCollapsed", arguments);
},
groupEnd: function CA_groupEnd() {
self.queueCall("groupEnd", arguments, aWindow);
self.queueCall("groupEnd", arguments);
},
time: function CA_time() {
self.queueCall("time", arguments, aWindow);
self.queueCall("time", arguments);
},
timeEnd: function CA_timeEnd() {
self.queueCall("timeEnd", arguments, aWindow);
self.queueCall("timeEnd", arguments);
},
__exposedProps__: {
log: "r",
@ -135,24 +152,24 @@ ConsoleAPI.prototype = {
Cu.makeObjectPropsNormal(contentObj);
this._queuedCalls = [];
this._destroyedWindows = [];
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._window = Cu.getWeakReference(aWindow);
this.timerRegistry = {};
return contentObj;
},
observe: function CA_observe(aSubject, aTopic, aData)
{
if (aTopic == "xpcom-shutdown") {
Services.obs.removeObserver(this, "xpcom-shutdown");
Services.obs.removeObserver(this, "inner-window-destroyed");
this._destroyedWindows = [];
this._queuedCalls = [];
gTimer = null;
}
else if (aTopic == "inner-window-destroyed") {
if (aTopic == "inner-window-destroyed") {
let innerWindowID = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
delete this.timerRegistry[innerWindowID + ""];
this._destroyedWindows.push(innerWindowID);
if (innerWindowID == this._innerID) {
Services.obs.removeObserver(this, "inner-window-destroyed");
this._windowDestroyed = true;
if (!this._timerInitialized) {
this.timerRegistry = {};
}
}
}
},
@ -163,29 +180,11 @@ ConsoleAPI.prototype = {
* The console method the code has invoked.
* @param object aArguments
* The arguments passed to the console method.
* @param object aWindow
* The window from where the console method was called.
*/
queueCall: function CA_queueCall(aMethod, aArguments, aWindow)
queueCall: function CA_queueCall(aMethod, aArguments)
{
let outerID;
let innerID;
try {
let windowUtils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
outerID = windowUtils.outerWindowID;
innerID = windowUtils.currentInnerWindowID;
}
catch (ex) {
Cu.reportError(ex);
return;
}
let metaForCall = {
outerID: outerID,
innerID: innerID,
isPrivate: PrivateBrowsingUtils.isWindowPrivate(aWindow),
isPrivate: PrivateBrowsingUtils.isWindowPrivate(this._window.get()),
timeStamp: Date.now(),
stack: this.getStackTrace(aMethod != "trace" ? 1 : null),
};
@ -193,8 +192,8 @@ ConsoleAPI.prototype = {
this._queuedCalls.push([aMethod, aArguments, metaForCall]);
if (!this._timerInitialized) {
gTimer.initWithCallback(this._timerCallback.bind(this), CALL_DELAY,
Ci.nsITimer.TYPE_REPEATING_SLACK);
this._timer.initWithCallback(this._timerCallback.bind(this), CALL_DELAY,
Ci.nsITimer.TYPE_REPEATING_SLACK);
this._timerInitialized = true;
}
},
@ -210,8 +209,12 @@ ConsoleAPI.prototype = {
if (!this._queuedCalls.length) {
this._timerInitialized = false;
this._destroyedWindows = [];
gTimer.cancel();
this._timer.cancel();
if (this._windowDestroyed) {
ConsoleAPIStorage.clearEvents(this._innerID);
this.timerRegistry = {};
}
}
},
@ -227,8 +230,6 @@ ConsoleAPI.prototype = {
let [method, args, meta] = aCall;
let notifyMeta = {
outerID: meta.outerID,
innerID: meta.innerID,
isPrivate: meta.isPrivate,
timeStamp: meta.timeStamp,
frame: meta.stack[0],
@ -256,10 +257,10 @@ ConsoleAPI.prototype = {
notifyArguments = args;
break;
case "time":
notifyArguments = this.startTimer(meta.innerID, args[0], meta.timeStamp);
notifyArguments = this.startTimer(args[0], meta.timeStamp);
break;
case "timeEnd":
notifyArguments = this.stopTimer(meta.innerID, args[0], meta.timeStamp);
notifyArguments = this.stopTimer(args[0], meta.timeStamp);
break;
default:
// unknown console API method!
@ -278,16 +279,14 @@ ConsoleAPI.prototype = {
* The arguments given to the console API call.
* @param object aMeta
* Object that holds metadata about the console API call:
* - outerID - the outer ID of the window where the message came from.
* - innerID - the inner ID of the window where the message came from.
* - isPrivate - Whether the window is in private browsing mode.
* - frame - the youngest content frame in the call stack.
* - timeStamp - when the console API call occurred.
*/
notifyObservers: function CA_notifyObservers(aLevel, aArguments, aMeta) {
let consoleEvent = {
ID: aMeta.outerID,
innerID: aMeta.innerID,
ID: this._outerID,
innerID: this._innerID,
level: aLevel,
filename: aMeta.frame.filename,
lineNumber: aMeta.frame.lineNumber,
@ -299,12 +298,12 @@ ConsoleAPI.prototype = {
consoleEvent.wrappedJSObject = consoleEvent;
// Store non-private messages for which the inner window was not destroyed.
if (!aMeta.isPrivate && this._destroyedWindows.indexOf(aMeta.innerID) == -1) {
ConsoleAPIStorage.recordEvent(aMeta.innerID, consoleEvent);
if (!aMeta.isPrivate) {
ConsoleAPIStorage.recordEvent(this._innerID, consoleEvent);
}
Services.obs.notifyObservers(consoleEvent, "console-api-log-event",
aMeta.outerID);
this._outerID);
},
/**
@ -384,19 +383,16 @@ ConsoleAPI.prototype = {
},
/*
* A registry of started timers. It contains a map of pages (defined by their
* inner window IDs) to timer maps. Timer maps are key-value pairs of timer
* names to timer start times, for all timers defined in that page. Timer
* A registry of started timers. Timer maps are key-value pairs of timer
* names to timer start times, for all timers defined in the page. Timer
* names are prepended with the inner window ID in order to avoid conflicts
* with Object.prototype functions.
*/
timerRegistry: {},
timerRegistry: null,
/**
* Create a new timer by recording the current time under the specified name.
*
* @param number aWindowId
* The inner ID of the window.
* @param string aName
* The name of the timer.
* @param number [aTimestamp=Date.now()]
@ -407,30 +403,23 @@ ConsoleAPI.prototype = {
* an object with the single property "error" that contains the key
* for retrieving the localized error message.
**/
startTimer: function CA_startTimer(aWindowId, aName, aTimestamp) {
startTimer: function CA_startTimer(aName, aTimestamp) {
if (!aName) {
return;
}
let innerID = aWindowId + "";
if (!this.timerRegistry[innerID]) {
this.timerRegistry[innerID] = {};
}
let pageTimers = this.timerRegistry[innerID];
if (Object.keys(pageTimers).length > MAX_PAGE_TIMERS - 1) {
if (Object.keys(this.timerRegistry).length > MAX_PAGE_TIMERS - 1) {
return { error: "maxTimersExceeded" };
}
let key = aWindowId + "-" + aName.toString();
if (!pageTimers[key]) {
pageTimers[key] = aTimestamp || Date.now();
let key = this._innerID + "-" + aName.toString();
if (!(key in this.timerRegistry)) {
this.timerRegistry[key] = aTimestamp || Date.now();
}
return { name: aName, started: pageTimers[key] };
return { name: aName, started: this.timerRegistry[key] };
},
/**
* Stop the timer with the specified name and retrieve the elapsed time.
*
* @param number aWindowId
* The inner ID of the window.
* @param string aName
* The name of the timer.
* @param number [aTimestamp=Date.now()]
@ -439,21 +428,16 @@ ConsoleAPI.prototype = {
* The name property holds the timer name and the duration property
* holds the number of milliseconds since the timer was started.
**/
stopTimer: function CA_stopTimer(aWindowId, aName, aTimestamp) {
stopTimer: function CA_stopTimer(aName, aTimestamp) {
if (!aName) {
return;
}
let innerID = aWindowId + "";
let pageTimers = this.timerRegistry[innerID];
if (!pageTimers) {
let key = this._innerID + "-" + aName.toString();
if (!(key in this.timerRegistry)) {
return;
}
let key = aWindowId + "-" + aName.toString();
if (!pageTimers[key]) {
return;
}
let duration = (aTimestamp || Date.now()) - pageTimers[key];
delete pageTimers[key];
let duration = (aTimestamp || Date.now()) - this.timerRegistry[key];
delete this.timerRegistry[key];
return { name: aName, duration: duration };
}
};

View File

@ -3967,6 +3967,14 @@ nsGlobalWindow::MatchMedia(const nsAString& aMediaQueryList,
*aResult = nsnull;
// We need this now to ensure that we have a non-null |presContext|
// when we ought to.
// This is similar to EnsureSizeUpToDate, but only flushes frames.
nsGlobalWindow *parent = static_cast<nsGlobalWindow*>(GetPrivateParent());
if (parent) {
parent->FlushPendingNotifications(Flush_Frames);
}
if (!mDocShell)
return NS_OK;

View File

@ -3270,7 +3270,8 @@ nsHTMLEditor::ContentInserted(nsIDocument *aDocument, nsIContent* aContainer,
nsCOMPtr<nsIHTMLEditor> kungFuDeathGrip(this);
if (ShouldReplaceRootElement()) {
ResetRootElementAndEventTarget();
nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(
this, &nsHTMLEditor::ResetRootElementAndEventTarget));
}
// We don't need to handle our own modifications
else if (!mAction && (aContainer ? aContainer->IsEditable() : aDocument->IsEditable())) {
@ -3300,7 +3301,8 @@ nsHTMLEditor::ContentRemoved(nsIDocument *aDocument, nsIContent* aContainer,
nsCOMPtr<nsIHTMLEditor> kungFuDeathGrip(this);
if (SameCOMIdentity(aChild, mRootElement)) {
ResetRootElementAndEventTarget();
nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(
this, &nsHTMLEditor::ResetRootElementAndEventTarget));
}
// We don't need to handle our own modifications
else if (!mAction && (aContainer ? aContainer->IsEditable() : aDocument->IsEditable())) {

View File

@ -126,6 +126,7 @@ CPPSRCS = \
ParseNode.cpp \
Parser.cpp \
SemanticAnalysis.cpp \
SPSProfiler.cpp \
TokenStream.cpp \
TreeContext.cpp \
TestingFunctions.cpp \

View File

@ -389,8 +389,6 @@ js::DirectEval(JSContext *cx, const CallArgs &args)
JS_ASSERT(IsBuiltinEvalForScope(caller->scopeChain(), args.calleev()));
JS_ASSERT(JSOp(*cx->regs().pc) == JSOP_EVAL);
AutoFunctionCallProbe callProbe(cx, args.callee().toFunction(), caller->script());
if (!WarnOnTooManyArgs(cx, args))
return false;

View File

@ -71,6 +71,7 @@ The meaning of the items:
tz-pacific Always run test with the Pacific time zone (TZ=PST8PDT).
mjitalways Run js with -a, whether --jitflags says to or not
debug Run js with -d, whether --jitflags says to or not
dump-bytecode Run js with -D, whether --jitflags says to or not
error The test should be considered to pass iff it throws the
given JS exception.

View File

@ -111,6 +111,8 @@ class Test:
test.jitflags.append('-d')
elif name == 'mjit':
test.jitflags.append('-m')
elif name == 'dump-bytecode':
test.jitflags.append('-D')
else:
print('warning: unrecognized |jit-test| attribute %s'%part)

View File

@ -0,0 +1,6 @@
// |jit-test| mjit; mjitalways; dump-bytecode
function f() { }
evaluate('function g() { f(); }', {newContext: true});
for (var i = 0; i < 2; i++)
g(0);

View File

@ -63,6 +63,7 @@ CPPSRCS = \
testValueABI.cpp \
testVersion.cpp \
testXDR.cpp \
testProfileStrings.cpp \
$(NULL)
CSRCS = \

View File

@ -0,0 +1,215 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sw=4 et tw=99:
*
* Tests the stack-based instrumentation profiler on a JSRuntime
*/
/* 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/. */
#include "tests.h"
#include "jscntxt.h"
static js::ProfileEntry stack[10];
static uint32_t size = 0;
static uint32_t max_stack = 0;
static void
reset(JSContext *cx)
{
size = max_stack = 0;
memset(stack, 0, sizeof(stack));
cx->runtime->spsProfiler.stringsReset();
}
static JSClass ptestClass = {
"Prof", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
};
static JSBool
test_fn(JSContext *cx, unsigned argc, jsval *vp)
{
max_stack = size;
return JS_TRUE;
}
static JSBool
test_fn2(JSContext *cx, unsigned argc, jsval *vp)
{
jsval r;
return JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "d", 0, NULL, &r);
}
static JSBool
test_fn3(JSContext *cx, unsigned argc, jsval *vp)
{
js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 10);
return JS_TRUE;
}
static JSBool
Prof(JSContext* cx, unsigned argc, jsval *vp)
{
JSObject *obj = JS_NewObjectForConstructor(cx, &ptestClass, vp);
if (!obj)
return JS_FALSE;
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));
return JS_TRUE;
}
static JSFunctionSpec ptestFunctions[] = {
JS_FS("test_fn", test_fn, 0, 0),
JS_FS("test_fn2", test_fn2, 0, 0),
JS_FS("test_fn3", test_fn3, 0, 0),
JS_FS_END
};
static JSObject*
initialize(JSContext *cx)
{
js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 10);
return JS_InitClass(cx, JS_GetGlobalObject(cx), NULL, &ptestClass, Prof, 0,
NULL, ptestFunctions, NULL, NULL);
}
BEGIN_TEST(testProfileStrings_isCalled)
{
CHECK(initialize(cx));
EXEC("function g() { var p = new Prof(); p.test_fn(); }");
EXEC("function f() { g(); }");
EXEC("function e() { f(); }");
EXEC("function d() { e(); }");
EXEC("function c() { d(); }");
EXEC("function b() { c(); }");
EXEC("function a() { b(); }");
EXEC("function check() { var p = new Prof(); p.test_fn(); a(); }");
EXEC("function check2() { var p = new Prof(); p.test_fn2(); }");
reset(cx);
{
jsvalRoot rval(cx);
/* Make sure the stack resets and we have an entry for each stack */
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.addr()));
CHECK(size == 0);
CHECK(max_stack == 9);
CHECK(cx->runtime->spsProfiler.stringsCount() == 8);
/* Make sure the stack resets and we added no new entries */
max_stack = 0;
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.addr()));
CHECK(size == 0);
CHECK(max_stack == 9);
CHECK(cx->runtime->spsProfiler.stringsCount() == 8);
}
reset(cx);
{
jsvalRoot rval(cx);
CHECK(JS_CallFunctionName(cx, global, "check2", 0, NULL, rval.addr()));
CHECK(cx->runtime->spsProfiler.stringsCount() == 5);
CHECK(max_stack == 7);
CHECK(size == 0);
}
reset(cx);
{
jsvalRoot rval(cx);
js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 3);
stack[3].string = (char*) 1234;
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.addr()));
CHECK((size_t) stack[3].string == 1234);
CHECK(max_stack == 9);
CHECK(size == 0);
}
return true;
}
END_TEST(testProfileStrings_isCalled)
BEGIN_TEST(testProfileStrings_isCalledWithJIT)
{
CHECK(initialize(cx));
JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_METHODJIT |
JSOPTION_METHODJIT_ALWAYS);
EXEC("function g() { var p = new Prof(); p.test_fn(); }");
EXEC("function f() { g(); }");
EXEC("function e() { f(); }");
EXEC("function d() { e(); }");
EXEC("function c() { d(); }");
EXEC("function b() { c(); }");
EXEC("function a() { b(); }");
EXEC("function check() { var p = new Prof(); p.test_fn(); a(); }");
EXEC("function check2() { var p = new Prof(); p.test_fn2(); }");
reset(cx);
{
jsvalRoot rval(cx);
/* Make sure the stack resets and we have an entry for each stack */
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.addr()));
CHECK(size == 0);
CHECK(max_stack == 9);
/* Make sure the stack resets and we added no new entries */
uint32_t cnt = cx->runtime->spsProfiler.stringsCount();
max_stack = 0;
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.addr()));
CHECK(size == 0);
CHECK(cx->runtime->spsProfiler.stringsCount() == cnt);
CHECK(max_stack == 9);
}
reset(cx);
{
/* Limit the size of the stack and make sure we don't overflow */
jsvalRoot rval(cx);
js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 3);
stack[3].string = (char*) 1234;
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.addr()));
CHECK(size == 0);
CHECK(max_stack == 9);
CHECK((size_t) stack[3].string == 1234);
}
return true;
}
END_TEST(testProfileStrings_isCalledWithJIT)
BEGIN_TEST(testProfileStrings_isCalledWhenError)
{
CHECK(initialize(cx));
JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_METHODJIT |
JSOPTION_METHODJIT_ALWAYS);
EXEC("function check2() { throw 'a'; }");
reset(cx);
{
jsvalRoot rval(cx);
/* Make sure the stack resets and we have an entry for each stack */
JS_CallFunctionName(cx, global, "check2", 0, NULL, rval.addr());
CHECK(size == 0);
CHECK(cx->runtime->spsProfiler.stringsCount() == 1);
}
return true;
}
END_TEST(testProfileStrings_isCalledWhenError)
BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly)
{
CHECK(initialize(cx));
JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_METHODJIT |
JSOPTION_METHODJIT_ALWAYS);
EXEC("function b() { }");
EXEC("function a() { var p = new Prof(); p.test_fn3(); b(); }");
reset(cx);
js::SetRuntimeProfilingStack(cx->runtime, NULL, NULL, 10);
{
jsvalRoot rval(cx);
JS_CallFunctionName(cx, global, "a", 0, NULL, rval.addr());
CHECK(size == 0);
CHECK(cx->runtime->spsProfiler.stringsCount() == 1);
}
return true;
}
END_TEST(testProfileStrings_worksWhenEnabledOnTheFly)

View File

@ -30,6 +30,7 @@
#include "js/HashTable.h"
#include "js/Vector.h"
#include "vm/Stack.h"
#include "vm/SPSProfiler.h"
#ifdef _MSC_VER
#pragma warning(push)
@ -690,6 +691,9 @@ struct JSRuntime : js::RuntimeFriendFields
/* If true, new compartments are initially in debug mode. */
bool debugMode;
/* SPS profiling metadata */
js::SPSProfiler spsProfiler;
/* If true, new scripts must be created with PC counter information. */
bool profilingScripts;

File diff suppressed because it is too large Load Diff

View File

@ -867,4 +867,12 @@ GetTestingFunctions(JSContext *cx)
return obj;
}
JS_FRIEND_API(void)
SetRuntimeProfilingStack(JSRuntime *rt, ProfileEntry *stack, uint32_t *size,
uint32_t max)
{
rt->spsProfiler.setProfilingStack(stack, size, max);
ReleaseAllJITCode(rt->defaultFreeOp());
}
} // namespace js

View File

@ -524,6 +524,31 @@ GetPCCountScriptSummary(JSContext *cx, size_t script);
JS_FRIEND_API(JSString *)
GetPCCountScriptContents(JSContext *cx, size_t script);
/*
* A call stack can be specified to the JS engine such that all JS entry/exits
* to functions push/pop an entry to/from the specified stack.
*
* For more detailed information, see vm/SPSProfiler.h
*/
struct ProfileEntry {
/*
* These two fields are marked as 'volatile' so that the compiler doesn't
* re-order instructions which modify them. The operation in question is:
*
* stack[i].string = str;
* (*size)++;
*
* If the size increment were re-ordered before the store of the string,
* then if sampling occurred there would be a bogus entry on the stack.
*/
const char * volatile string;
void * volatile sp;
};
JS_FRIEND_API(void)
SetRuntimeProfilingStack(JSRuntime *rt, ProfileEntry *stack, uint32_t *size,
uint32_t max);
#ifdef JS_THREADSAFE
JS_FRIEND_API(void *)
GetOwnerThread(const JSContext *cx);

View File

@ -4681,7 +4681,7 @@ MaybeVerifyBarriers(JSContext *cx, bool always)
} /* namespace gc */
static void ReleaseAllJITCode(FreeOp *fop)
void ReleaseAllJITCode(FreeOp *fop)
{
#ifdef JS_METHODJIT
for (CompartmentsIter c(fop->runtime()); !c.done(); c.next()) {

View File

@ -457,6 +457,9 @@ MaybeGC(JSContext *cx);
extern void
ShrinkGCBuffers(JSRuntime *rt);
extern void
ReleaseAllJITCode(FreeOp *op);
extern JS_FRIEND_API(void)
PrepareForFullGC(JSRuntime *rt);

View File

@ -285,6 +285,8 @@ js::RunScript(JSContext *cx, JSScript *script, StackFrame *fp)
} check(cx);
#endif
SPSEntryMarker marker(cx->runtime);
#ifdef JS_METHODJIT
mjit::CompileStatus status;
status = mjit::CanMethodJIT(cx, script, script->code, fp->isConstructing(),
@ -1306,8 +1308,10 @@ js::Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode)
* To support generator_throw and to catch ignored exceptions,
* fail if cx->isExceptionPending() is true.
*/
if (cx->isExceptionPending())
if (cx->isExceptionPending()) {
Probes::enterScript(cx, script, script->function(), regs.fp());
goto error;
}
}
#endif
@ -1317,8 +1321,12 @@ js::Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode)
/* Don't call the script prologue if executing between Method and Trace JIT. */
if (interpMode == JSINTERP_NORMAL) {
StackFrame *fp = regs.fp();
if (!fp->isGeneratorFrame() && !fp->prologue(cx, UseNewTypeAtEntry(cx, fp)))
goto error;
if (!fp->isGeneratorFrame()) {
if (!fp->prologue(cx, UseNewTypeAtEntry(cx, fp)))
goto error;
} else {
Probes::enterScript(cx, script, script->function(), fp);
}
if (cx->compartment->debugMode()) {
JSTrapStatus status = ScriptDebugPrologue(cx, fp);
switch (status) {
@ -1607,6 +1615,8 @@ BEGIN_CASE(JSOP_STOP)
if (!regs.fp()->isYielding())
regs.fp()->epilogue(cx);
else
Probes::exitScript(cx, script, script->function(), regs.fp());
/* The JIT inlines the epilogue. */
#ifdef JS_METHODJIT
@ -2513,7 +2523,6 @@ BEGIN_CASE(JSOP_FUNCALL)
if (!regs.fp()->prologue(cx, newType))
goto error;
if (cx->compartment->debugMode()) {
switch (ScriptDebugPrologue(cx, regs.fp())) {
case JSTRAP_CONTINUE:
@ -3974,6 +3983,8 @@ END_CASE(JSOP_ARRAYPUSH)
interpReturnOK = ScriptDebugEpilogue(cx, regs.fp(), interpReturnOK);
if (!regs.fp()->isYielding())
regs.fp()->epilogue(cx);
else
Probes::exitScript(cx, script, script->function(), regs.fp());
regs.fp()->setFinishedInInterpreter();
#ifdef JS_METHODJIT

View File

@ -92,10 +92,10 @@ bool callTrackingActive(JSContext *);
bool wantNativeAddressInfo(JSContext *);
/* Entering a JS function */
bool enterJSFun(JSContext *, JSFunction *, JSScript *, int counter = 1);
bool enterScript(JSContext *, JSScript *, JSFunction *, StackFrame *);
/* About to leave a JS function */
bool exitJSFun(JSContext *, JSFunction *, JSScript *, int counter = 0);
bool exitScript(JSContext *, JSScript *, JSFunction *, StackFrame *);
/* Executing a script */
bool startExecution(JSContext *cx, JSScript *script);
@ -303,8 +303,8 @@ void
discardExecutableRegion(void *start, size_t size);
/*
* Internal: DTrace-specific functions to be called during Probes::enterJSFun
* and Probes::exitJSFun. These will not be inlined, but the argument
* Internal: DTrace-specific functions to be called during Probes::enterScript
* and Probes::exitScript. These will not be inlined, but the argument
* marshalling required for these probe points is expensive enough that it
* shouldn't really matter.
*/
@ -380,43 +380,53 @@ Probes::wantNativeAddressInfo(JSContext *cx)
}
inline bool
Probes::enterJSFun(JSContext *cx, JSFunction *fun, JSScript *script, int counter)
Probes::enterScript(JSContext *cx, JSScript *script, JSFunction *maybeFun,
StackFrame *fp)
{
bool ok = true;
#ifdef INCLUDE_MOZILLA_DTRACE
if (JAVASCRIPT_FUNCTION_ENTRY_ENABLED())
DTraceEnterJSFun(cx, fun, script);
DTraceEnterJSFun(cx, maybeFun, script);
#endif
#ifdef MOZ_TRACE_JSCALLS
cx->doFunctionCallback(fun, script, counter);
cx->doFunctionCallback(maybeFun, script, 1);
#endif
#ifdef MOZ_ETW
if (ProfilingActive && !ETWEnterJSFun(cx, fun, script, counter))
if (ProfilingActive && !ETWEnterJSFun(cx, maybeFun, script, 1))
ok = false;
#endif
JSRuntime *rt = cx->runtime;
if (rt->spsProfiler.enabled()) {
rt->spsProfiler.enter(cx, script, maybeFun);
JS_ASSERT_IF(!fp->isGeneratorFrame(), !fp->hasPushedSPSFrame());
fp->setPushedSPSFrame();
}
return ok;
}
inline bool
Probes::exitJSFun(JSContext *cx, JSFunction *fun, JSScript *script, int counter)
Probes::exitScript(JSContext *cx, JSScript *script, JSFunction *maybeFun,
StackFrame *fp)
{
bool ok = true;
#ifdef INCLUDE_MOZILLA_DTRACE
if (JAVASCRIPT_FUNCTION_RETURN_ENABLED())
DTraceExitJSFun(cx, fun, script);
DTraceExitJSFun(cx, maybeFun, script);
#endif
#ifdef MOZ_TRACE_JSCALLS
if (counter > 0)
counter = -counter;
cx->doFunctionCallback(fun, script, counter);
cx->doFunctionCallback(maybeFun, script, 0);
#endif
#ifdef MOZ_ETW
if (ProfilingActive && !ETWExitJSFun(cx, fun, script, counter))
if (ProfilingActive && !ETWExitJSFun(cx, maybeFun, script, 0))
ok = false;
#endif
JSRuntime *rt = cx->runtime;
if (rt->spsProfiler.enabled() && fp->hasPushedSPSFrame())
rt->spsProfiler.exit(cx, script, maybeFun);
return ok;
}
@ -766,25 +776,6 @@ Probes::stopExecution(JSContext *cx, JSScript *script)
return ok;
}
struct AutoFunctionCallProbe {
JSContext * const cx;
JSFunction *fun;
JSScript *script;
JS_DECL_USE_GUARD_OBJECT_NOTIFIER
AutoFunctionCallProbe(JSContext *cx, JSFunction *fun, JSScript *script
JS_GUARD_OBJECT_NOTIFIER_PARAM)
: cx(cx), fun(fun), script(script)
{
JS_GUARD_OBJECT_NOTIFIER_INIT;
Probes::enterJSFun(cx, fun, script);
}
~AutoFunctionCallProbe() {
Probes::exitJSFun(cx, fun, script);
}
};
} /* namespace js */
/*

View File

@ -1416,6 +1416,7 @@ JSScript::finalize(FreeOp *fop)
// fullyInitFromEmitter() or fullyInitTrivial().
CallDestroyScriptHook(fop, this);
fop->runtime()->spsProfiler.onScriptFinalized(this);
JS_ASSERT_IF(principals, originPrincipals);
if (principals)

View File

@ -282,6 +282,12 @@ mjit::Compiler::scanInlineCalls(uint32_t index, uint32_t depth)
break;
}
/* See bug 768313. */
if (script->hasScriptCounts != outerScript->hasScriptCounts) {
okay = false;
break;
}
/*
* The outer and inner scripts must have the same scope. This only
* allows us to inline calls between non-inner functions. Also
@ -1174,17 +1180,11 @@ mjit::Compiler::generatePrologue()
}
}
if (debugMode()) {
prepareStubCall(Uses(0));
INLINE_STUBCALL(stubs::ScriptDebugPrologue, REJOIN_RESUME);
} else if (Probes::callTrackingActive(cx)) {
prepareStubCall(Uses(0));
INLINE_STUBCALL(stubs::ScriptProbeOnlyPrologue, REJOIN_RESUME);
}
CompileStatus status = methodEntryHelper();
if (status == Compile_Okay)
recompileCheckHelper();
recompileCheckHelper();
return Compile_Okay;
return status;
}
void
@ -3740,7 +3740,7 @@ mjit::Compiler::emitReturn(FrameEntry *fe)
/* Only the top of the stack can be returned. */
JS_ASSERT_IF(fe, fe == frame.peek(-1));
if (debugMode() || Probes::callTrackingActive(cx)) {
if (debugMode()) {
/* If the return value isn't in the frame's rval slot, move it there. */
if (fe) {
frame.storeTo(fe, Address(JSFrameReg, StackFrame::offsetOfReturnValue()), true);
@ -3761,6 +3761,9 @@ mjit::Compiler::emitReturn(FrameEntry *fe)
}
if (a != outer) {
JS_ASSERT(!debugMode());
profilingPopHelper();
/*
* Returning from an inlined script. The checks we do for inlineability
* and recompilation triggered by args object construction ensure that
@ -3800,8 +3803,12 @@ mjit::Compiler::emitReturn(FrameEntry *fe)
if (debugMode()) {
prepareStubCall(Uses(0));
INLINE_STUBCALL(stubs::Epilogue, REJOIN_NONE);
} else if (script->function() && script->nesting()) {
masm.sub32(Imm32(1), AbsoluteAddress(&script->nesting()->activeFrames));
} else {
profilingPopHelper();
if (script->function() && script->nesting()) {
masm.sub32(Imm32(1), AbsoluteAddress(&script->nesting()->activeFrames));
}
}
emitReturnValue(&masm, fe);
@ -3893,6 +3900,78 @@ mjit::Compiler::recompileCheckHelper()
stubcc.rejoin(Changes(0));
}
CompileStatus
mjit::Compiler::methodEntryHelper()
{
if (debugMode()) {
prepareStubCall(Uses(0));
INLINE_STUBCALL(stubs::ScriptDebugPrologue, REJOIN_RESUME);
} else if (Probes::callTrackingActive(cx)) {
prepareStubCall(Uses(0));
INLINE_STUBCALL(stubs::ScriptProbeOnlyPrologue, REJOIN_RESUME);
} else {
return profilingPushHelper();
}
return Compile_Okay;
}
CompileStatus
mjit::Compiler::profilingPushHelper()
{
SPSProfiler *p = &cx->runtime->spsProfiler;
if (!p->enabled())
return Compile_Okay;
/* If allocation fails, make sure no PopHelper() is emitted */
const char *str = p->profileString(cx, script, script->function());
if (str == NULL)
return Compile_Error;
/* Check if there's still space on the stack */
RegisterID size = frame.allocReg();
RegisterID base = frame.allocReg();
masm.load32(p->size(), size);
Jump j = masm.branch32(Assembler::GreaterThanOrEqual, size,
Imm32(p->maxSize()));
/* With room, store our string onto the stack */
masm.move(ImmPtr(p->stack()), base);
JS_STATIC_ASSERT(sizeof(ProfileEntry) == 2 * sizeof(void*));
masm.lshift32(Imm32(sizeof(void*) == 4 ? 3 : 4), size);
masm.addPtr(size, base);
masm.storePtr(ImmPtr(str), Address(base, offsetof(ProfileEntry, string)));
masm.storePtr(ImmPtr(NULL), Address(base, offsetof(ProfileEntry, sp)));
frame.freeReg(base);
frame.freeReg(size);
/* Always increment the stack size (paired with a decrement below) */
j.linkTo(masm.label(), &masm);
masm.add32(Imm32(1), AbsoluteAddress(p->size()));
/* Set the flags that we've pushed information onto the SPS stack */
RegisterID reg = frame.allocReg();
masm.load32(FrameFlagsAddress(), reg);
masm.or32(Imm32(StackFrame::HAS_PUSHED_SPS_FRAME), reg);
masm.store32(reg, FrameFlagsAddress());
frame.freeReg(reg);
return Compile_Okay;
}
void
mjit::Compiler::profilingPopHelper()
{
if (Probes::callTrackingActive(cx) ||
cx->runtime->spsProfiler.slowAssertionsEnabled())
{
prepareStubCall(Uses(0));
INLINE_STUBCALL(stubs::ScriptProbeOnlyEpilogue, REJOIN_RESUME);
} else if (cx->runtime->spsProfiler.enabled()) {
masm.sub32(Imm32(1), AbsoluteAddress(cx->runtime->spsProfiler.size()));
}
}
void
mjit::Compiler::addReturnSite()
{
@ -4456,7 +4535,10 @@ mjit::Compiler::inlineScriptedFunction(uint32_t argc, bool callingNew)
markUndefinedLocals();
status = generateMethod();
status = methodEntryHelper();
if (status == Compile_Okay)
status = generateMethod();
if (status != Compile_Okay) {
popActiveFrame();
if (status == Compile_Abort) {

View File

@ -637,6 +637,9 @@ private:
void dispatchCall(VoidPtrStubUInt32 stub, uint32_t argc);
void interruptCheckHelper();
void recompileCheckHelper();
CompileStatus methodEntryHelper();
CompileStatus profilingPushHelper();
void profilingPopHelper();
void emitUncachedCall(uint32_t argc, bool callingNew);
void checkCallApplySpeculation(uint32_t argc, FrameEntry *origCallee, FrameEntry *origThis,
MaybeRegisterID origCalleeType, RegisterID origCalleeData,

View File

@ -610,7 +610,7 @@ stubs::CreateThis(VMFrame &f, JSObject *proto)
void JS_FASTCALL
stubs::ScriptDebugPrologue(VMFrame &f)
{
Probes::enterJSFun(f.cx, f.fp()->maybeFun(), f.fp()->script());
Probes::enterScript(f.cx, f.script(), f.script()->function(), f.fp());
JSTrapStatus status = js::ScriptDebugPrologue(f.cx, f.fp());
switch (status) {
case JSTRAP_CONTINUE:
@ -629,7 +629,6 @@ stubs::ScriptDebugPrologue(VMFrame &f)
void JS_FASTCALL
stubs::ScriptDebugEpilogue(VMFrame &f)
{
Probes::exitJSFun(f.cx, f.fp()->maybeFun(), f.fp()->script());
if (!js::ScriptDebugEpilogue(f.cx, f.fp(), JS_TRUE))
THROW();
}
@ -637,13 +636,13 @@ stubs::ScriptDebugEpilogue(VMFrame &f)
void JS_FASTCALL
stubs::ScriptProbeOnlyPrologue(VMFrame &f)
{
Probes::enterJSFun(f.cx, f.fp()->fun(), f.fp()->script());
Probes::enterScript(f.cx, f.script(), f.script()->function(), f.fp());
}
void JS_FASTCALL
stubs::ScriptProbeOnlyEpilogue(VMFrame &f)
{
Probes::exitJSFun(f.cx, f.fp()->fun(), f.fp()->script());
Probes::exitScript(f.cx, f.script(), f.script()->function(), f.fp());
}
void JS_FASTCALL
@ -868,8 +867,7 @@ js_InternalInterpret(void *returnData, void *returnType, void *returnReg, js::VM
return js_InternalThrow(f);
fp->thisValue() = ObjectValue(*obj);
if (Probes::callTrackingActive(cx))
Probes::enterJSFun(f.cx, f.fp()->maybeFun(), f.fp()->script());
Probes::enterScript(f.cx, f.script(), f.script()->function(), fp);
if (script->debugMode) {
JSTrapStatus status = js::ScriptDebugPrologue(f.cx, f.fp());
@ -925,8 +923,8 @@ js_InternalInterpret(void *returnData, void *returnType, void *returnReg, js::VM
}
/* FALLTHROUGH */
case REJOIN_EVAL_PROLOGUE:
Probes::enterScript(cx, f.script(), f.script()->function(), fp);
if (cx->compartment->debugMode()) {
Probes::enterJSFun(cx, fp->maybeFun(), fp->script());
JSTrapStatus status = ScriptDebugPrologue(cx, fp);
switch (status) {
case JSTRAP_CONTINUE:

View File

@ -3410,6 +3410,29 @@ EnableStackWalkingAssertion(JSContext *cx, unsigned argc, jsval *vp)
return true;
}
static JSBool
EnableSPSProfilingAssertions(JSContext *cx, unsigned argc, jsval *vp)
{
jsval arg = JS_ARGV(cx, vp)[0];
if (argc == 0 || !JSVAL_IS_BOOLEAN(arg)) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS,
"enableSPSProfilingAssertions");
return false;
}
static ProfileEntry stack[1000];
static uint32_t stack_size = 0;
if (JSVAL_TO_BOOLEAN(arg))
SetRuntimeProfilingStack(cx->runtime, stack, &stack_size, 1000);
else
SetRuntimeProfilingStack(cx->runtime, NULL, NULL, 0);
cx->runtime->spsProfiler.enableSlowAssertions(JSVAL_TO_BOOLEAN(arg));
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return true;
}
static JSBool
GetMaxArgs(JSContext *cx, unsigned arg, jsval *vp)
{
@ -3710,6 +3733,11 @@ static JSFunctionSpecWithHelp shell_functions[] = {
"getMaxArgs()",
" Return the maximum number of supported args for a call."),
JS_FN_HELP("enableSPSProfilingAssertions", EnableSPSProfilingAssertions, 1, 0,
"enableProfilingAssertions(enabled)",
" Enables or disables the assertions related to SPS profiling. This is fairly\n"
" expensive, so it shouldn't be enabled normally."),
JS_FS_END
};
#ifdef MOZ_PROFILING

172
js/src/vm/SPSProfiler.cpp Normal file
View File

@ -0,0 +1,172 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=4 sw=4 et tw=99 ft=cpp:
*
* 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/. */
#include "jsnum.h"
#include "vm/SPSProfiler.h"
#include "vm/StringBuffer.h"
using namespace js;
SPSProfiler::~SPSProfiler()
{
if (strings.initialized()) {
for (ProfileStringMap::Enum e(strings); !e.empty(); e.popFront())
js_free((void*) e.front().value);
}
}
void
SPSProfiler::setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max)
{
if (!strings.initialized())
strings.init(max);
stack_ = stack;
size_ = size;
max_ = max;
}
/* Lookup the string for the function/script, creating one if necessary */
const char*
SPSProfiler::profileString(JSContext *cx, JSScript *script, JSFunction *maybeFun)
{
JS_ASSERT(enabled());
JS_ASSERT(strings.initialized());
ProfileStringMap::AddPtr s = strings.lookupForAdd(script);
if (s)
return s->value;
const char *str = allocProfileString(cx, script, maybeFun);
if (str == NULL)
return NULL;
if (!strings.add(s, script, str)) {
js_free((void*) str);
return NULL;
}
return str;
}
void
SPSProfiler::onScriptFinalized(JSScript *script)
{
/*
* This function is called whenever a script is destroyed, regardless of
* whether profiling has been turned on, so don't invoke a function on an
* invalid hash set. Also, even if profiling was enabled but then turned
* off, we still want to remove the string, so no check of enabled() is
* done.
*/
if (!strings.initialized())
return;
if (ProfileStringMap::Ptr entry = strings.lookup(script)) {
const char *tofree = entry->value;
strings.remove(entry);
js_free((void*) tofree);
}
}
bool
SPSProfiler::enter(JSContext *cx, JSScript *script, JSFunction *maybeFun)
{
JS_ASSERT(enabled());
const char *str = profileString(cx, script, maybeFun);
if (str == NULL)
return false;
if (*size_ < max_) {
stack_[*size_].string = str;
stack_[*size_].sp = NULL;
}
(*size_)++;
return true;
}
void
SPSProfiler::exit(JSContext *cx, JSScript *script, JSFunction *maybeFun)
{
JS_ASSERT(enabled());
(*size_)--;
JS_ASSERT(*(int*)size_ >= 0);
#ifdef DEBUG
/* Sanity check to make sure push/pop balanced */
if (*size_ < max_) {
const char *str = profileString(cx, script, maybeFun);
/* Can't fail lookup because we should already be in the set */
JS_ASSERT(str != NULL);
JS_ASSERT(strcmp((const char*) stack_[*size_].string, str) == 0);
stack_[*size_].string = NULL;
stack_[*size_].sp = NULL;
}
#endif
}
/*
* Serializes the script/function pair into a "descriptive string" which is
* allowed to fail. This function cannot trigger a GC because it could finalize
* some scripts, resize the hash table of profile strings, and invalidate the
* AddPtr held while invoking allocProfileString.
*/
const char*
SPSProfiler::allocProfileString(JSContext *cx, JSScript *script, JSFunction *maybeFun)
{
DebugOnly<uint64_t> gcBefore = cx->runtime->gcNumber;
StringBuffer buf(cx);
bool hasAtom = maybeFun != NULL && maybeFun->atom != NULL;
if (hasAtom) {
if (!buf.append(maybeFun->atom))
return NULL;
if (!buf.append(" ("))
return NULL;
}
if (script->filename) {
if (!buf.appendInflated(script->filename, strlen(script->filename)))
return NULL;
} else if (!buf.append("<unknown>")) {
return NULL;
}
if (!buf.append(":"))
return NULL;
if (!NumberValueToStringBuffer(cx, NumberValue(script->lineno), buf))
return NULL;
if (hasAtom && !buf.append(")"))
return NULL;
size_t len = buf.length();
char *cstr = (char*) js_malloc(len + 1);
if (cstr == NULL)
return NULL;
const jschar *ptr = buf.begin();
for (size_t i = 0; i < len; i++)
cstr[i] = ptr[i];
cstr[len] = 0;
JS_ASSERT(gcBefore == cx->runtime->gcNumber);
return cstr;
}
SPSEntryMarker::SPSEntryMarker(JSRuntime *rt) : profiler(&rt->spsProfiler), pushed(false)
{
if (!profiler->enabled())
return;
uint32_t *size = profiler->size_;
size_before = *size;
if (*size < profiler->max_) {
profiler->stack_[*size].string = "js::RunScript";
profiler->stack_[*size].sp = this;
}
(*size)++;
pushed = true;
}
SPSEntryMarker::~SPSEntryMarker()
{
if (!pushed || !profiler->enabled())
return;
(*profiler->size_)--;
JS_ASSERT(*profiler->size_ == size_before);
}

144
js/src/vm/SPSProfiler.h Normal file
View File

@ -0,0 +1,144 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sw=4 et tw=99 ft=cpp:
*
* 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 SPSProfiler_h__
#define SPSProfiler_h__
#include "mozilla/HashFunctions.h"
#include <stddef.h>
#include "jsfriendapi.h"
#include "jsfun.h"
#include "jsscript.h"
/*
* SPS Profiler integration with the JS Engine
* https://developer.mozilla.org/en/Performance/Profiling_with_the_Built-in_Profiler
*
* The SPS profiler (found in tools/profiler) is an implementation of a profiler
* which has the ability to walk the C++ stack as well as use instrumentation to
* gather information. When dealing with JS, however, SPS needs integration
* with the engine because otherwise it is very difficult to figure out what
* javascript is executing.
*
* The current method of integration with SPS is a form of instrumentation:
* every time a JS function is entered, a bit of information is pushed onto a
* stack that SPS owns and maintains. This information is then popped at the end
* of the JS function. SPS informs the JS engine of this stack at runtime, and
* it can by turned on/off dynamically.
*
* The SPS stack has three parameters: a base pointer, a size, and a maximum
* size. The stack is the ProfileEntry stack which will have information written
* to it. The size location is a pointer to an integer which represents the
* current size of the stack (number of valid frames). This size will be
* modified when JS functions are called. The maximum specified is the maximum
* capacity of the ProfileEntry stack.
*
* Throughout execution, the size of the stack recorded in memory may exceed the
* maximum. The JS engine will not write any information past the maximum limit,
* but it will still maintain the size of the stack. SPS code is aware of this
* and iterates the stack accordingly.
*
* There are two pointers of information pushed on the SPS stack for every JS
* function that is entered. First is a char* pointer of a description of what
* function was entered. Currently this string is of the form
* "function (file:line)" if there's a function name, or just "file:line" if
* there's no function name available. The other bit of information is the
* relevant C++ (native) stack pointer. This stack pointer is what enables the
* interleaving of the C++ and the JS stack.
*
* = Profile Strings
*
* The profile strings' allocations and deallocation must be carefully
* maintained, and ideally at a very low overhead cost. For this reason, the JS
* engine maintains a mapping of all known profile strings. These strings are
* keyed in lookup by a JSScript*, but are serialized with a JSFunction*,
* JSScript* pair. A JSScript will destroy its corresponding profile string when
* the script is finalized.
*
* For this reason, a char* pointer pushed on the SPS stack is valid only while
* it is on the SPS stack. SPS uses sampling to read off information from this
* instrumented stack, and it therefore copies the string byte for byte when a
* JS function is encountered during sampling.
*
* = Native Stack Pointer
*
* The actual value pushed as the native pointer is NULL for most JS functions.
* The reason for this is that there's actually very little correlation between
* the JS stack and the C++ stack because many JS functions all run in the same
* C++ frame, or can even go backwards in C++ when going from the JIT back to
* the interpreter.
*
* To alleviate this problem, all JS functions push NULL as their "native stack
* pointer" to indicate that it's a JS function call. The function RunScript(),
* however, pushes an actual C++ stack pointer onto the SPS stack. This way when
* interleaving C++ and JS, if SPS sees a NULL native stack pointer on the SPS
* stack, it looks backwards for the first non-NULL pointer and uses that for
* all subsequent NULL native stack pointers.
*/
namespace js {
typedef HashMap<JSScript*, const char*, DefaultHasher<JSScript*>, SystemAllocPolicy>
ProfileStringMap;
class SPSEntryMarker;
class SPSProfiler
{
friend class SPSEntryMarker;
ProfileStringMap strings;
ProfileEntry *stack_;
uint32_t *size_;
uint32_t max_;
bool slowAssertions;
static const char *allocProfileString(JSContext *cx, JSScript *script,
JSFunction *function);
public:
SPSProfiler() : stack_(NULL), size_(NULL), max_(0), slowAssertions(false) {}
~SPSProfiler();
uint32_t *size() { return size_; }
uint32_t maxSize() { return max_; }
ProfileEntry *stack() { return stack_; }
bool enabled() { return stack_ != NULL; }
void enableSlowAssertions(bool enabled) { slowAssertions = enabled; }
bool slowAssertionsEnabled() { return slowAssertions; }
bool enter(JSContext *cx, JSScript *script, JSFunction *maybeFun);
void exit(JSContext *cx, JSScript *script, JSFunction *maybeFun);
void setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max);
const char *profileString(JSContext *cx, JSScript *script, JSFunction *maybeFun);
void onScriptFinalized(JSScript *script);
/* meant to be used for testing, not recommended to call in normal code */
size_t stringsCount() { return strings.count(); }
void stringsReset() { strings.clear(); }
};
/*
* This class is used in RunScript() to push the marker onto the sampling stack
* that we're about to enter JS function calls. This is the only time in which a
* valid stack pointer is pushed to the sampling stack.
*/
class SPSEntryMarker
{
SPSProfiler *profiler;
bool pushed;
DebugOnly<uint32_t> size_before;
public:
SPSEntryMarker(JSRuntime *rt);
~SPSEntryMarker();
};
} /* namespace js */
#endif /* SPSProfiler_h__ */

View File

@ -114,6 +114,9 @@ StackFrame::initInlineFrame(JSFunction *fun, StackFrame *prevfp, jsbytecode *pre
flags_ = StackFrame::FUNCTION;
exec.fun = fun;
resetInlinePrev(prevfp, prevpc);
if (prevfp->hasPushedSPSFrame())
setPushedSPSFrame();
}
inline void

View File

@ -237,11 +237,14 @@ StackFrame::prologue(JSContext *cx, bool newType)
pushOnScopeChain(*callobj);
flags_ |= HAS_CALL_OBJ;
}
Probes::enterScript(cx, script(), NULL, this);
return true;
}
if (isGlobalFrame())
if (isGlobalFrame()) {
Probes::enterScript(cx, script(), NULL, this);
return true;
}
JS_ASSERT(isNonEvalFunctionFrame());
@ -266,7 +269,7 @@ StackFrame::prologue(JSContext *cx, bool newType)
functionThis() = ObjectValue(*obj);
}
Probes::enterJSFun(cx, fun(), script());
Probes::enterScript(cx, script(), script()->function(), this);
return true;
}
@ -277,6 +280,8 @@ StackFrame::epilogue(JSContext *cx)
JS_ASSERT(!isYielding());
JS_ASSERT(!hasBlockChain());
Probes::exitScript(cx, script(), script()->function(), this);
if (isEvalFrame()) {
if (isStrictEvalFrame()) {
JS_ASSERT_IF(hasCallObj(), scopeChain()->asCall().isForEval());
@ -309,8 +314,6 @@ StackFrame::epilogue(JSContext *cx)
if (cx->compartment->debugMode())
cx->runtime->debugScopes->onPopCall(this, cx);
Probes::exitJSFun(cx, fun(), script());
if (script()->nesting() && (flags_ & HAS_NESTING))
types::NestingEpilogue(this);

View File

@ -15,8 +15,6 @@
struct JSContext;
struct JSCompartment;
extern void js_DumpStackFrame(JSContext *, js::StackFrame *);
namespace js {
class StackFrame;
@ -57,10 +55,6 @@ struct InlinedSite {};
#endif
typedef size_t FrameRejoinState;
namespace detail {
struct OOMCheck;
}
/*****************************************************************************/
/*
@ -266,7 +260,10 @@ class StackFrame
LOWERED_CALL_APPLY = 0x200000, /* Pushed by a lowered call/apply */
/* Debugger state */
PREV_UP_TO_DATE = 0x400000 /* see DebugScopes::updateLiveScopes */
PREV_UP_TO_DATE = 0x400000, /* see DebugScopes::updateLiveScopes */
/* Used in tracking calls and profiling (see vm/SPSProfiler.cpp) */
HAS_PUSHED_SPS_FRAME = 0x800000 /* SPS was notified of enty */
};
private:
@ -801,6 +798,14 @@ class StackFrame
flags_ |= HAS_HOOK_DATA;
}
bool hasPushedSPSFrame() {
return !!(flags_ & HAS_PUSHED_SPS_FRAME);
}
void setPushedSPSFrame() {
flags_ |= HAS_PUSHED_SPS_FRAME;
}
/* Return value */
bool hasReturnValue() const {

View File

@ -30,6 +30,7 @@
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/Attributes.h"
#include "sampler.h"
#include "nsJSPrincipals.h"
#ifdef MOZ_CRASHREPORTER
@ -2000,6 +2001,10 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
js::SetPreserveWrapperCallback(mJSRuntime, PreserveWrapper);
#ifdef MOZ_CRASHREPORTER
JS_EnumerateDiagnosticMemoryRegions(DiagnosticMemoryCallback);
#endif
#ifdef MOZ_ENABLE_PROFILER_SPS
if (ProfileStack *stack = mozilla_profile_stack())
stack->sampleRuntime(mJSRuntime);
#endif
JS_SetAccumulateTelemetryCallback(mJSRuntime, AccumulateTelemetryCallback);
js::SetActivityCallback(mJSRuntime, ActivityCallback, this);

View File

@ -0,0 +1,68 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Reference for test that marker orientation is correct at the end of arcs</title>
<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=769115 -->
<defs>
<g id="m1" transform="translate(-20,-20)" fill="blue">
<rect x="5" y="15" width="22" height="10"/>
<path d="M 25,10 35,20 25,30 z"/>
</g>
<g id="m2" transform="translate(-20,-20)" fill="red">
<rect x="5" y="15" width="22" height="10"/>
<path d="M 25,10 35,20 25,30 z"/>
</g>
</defs>
<g fill="none">
<!-- arcs that go from the left of the circle to... -->
<g>
<!-- ...90 degrees anti-clockwise -->
<use xlink:href="#m1" transform="translate(100,100)rotate(90)"/>
<use xlink:href="#m2" transform="rotate(-90,150,100)translate(100,100)rotate(90)"/>
<!-- ...180 degrees anti-clockwise -->
<use xlink:href="#m2" transform="rotate(-180,150,100)translate(100,100)rotate(90)"/>
<!-- ...270 degrees anti-clockwise -->
<use xlink:href="#m2" transform="rotate(-270,150,100)translate(100,100)rotate(90)"/>
</g>
<!-- arcs that go from the left of the circle to... -->
<g transform="translate(250,0)">
<!-- ...90 degrees anti-clockwise -->
<use xlink:href="#m1" transform="translate(100,100)rotate(-90)"/>
<use xlink:href="#m2" transform="rotate(90,150,100)translate(100,100)rotate(-90)"/>
<!-- ...180 degrees anti-clockwise -->
<use xlink:href="#m2" transform="rotate(180,150,100)translate(100,100)rotate(-90)"/>
<!-- ...270 degrees anti-clockwise -->
<use xlink:href="#m2" transform="rotate(270,150,100)translate(100,100)rotate(-90)"/>
</g>
<!-- arcs that go from the right of the circle to... -->
<g transform="translate(0,250)">
<!-- ...90 degrees anti-clockwise -->
<use xlink:href="#m1" transform="translate(200,100)rotate(90)"/>
<use xlink:href="#m2" transform="rotate(90,150,100)translate(200,100)rotate(90)"/>
<!-- ...180 degrees anti-clockwise -->
<use xlink:href="#m2" transform="rotate(180,150,100)translate(200,100)rotate(90)"/>
<!-- ...270 degrees anti-clockwise -->
<use xlink:href="#m2" transform="rotate(270,150,100)translate(200,100)rotate(90)"/>
</g>
<!-- arcs that go from the right of the circle to... -->
<g transform="translate(250,250)">
<!-- ...90 degrees clockwise -->
<use xlink:href="#m1" transform="translate(200,100)rotate(-90)"/>
<use xlink:href="#m2" transform="rotate(-90,150,100)translate(200,100)rotate(-90)"/>
<!-- ...180 degrees clockwise -->
<use xlink:href="#m2" transform="rotate(-180,150,100)translate(200,100)rotate(-90)"/>
<!-- ...270 degrees clockwise -->
<use xlink:href="#m2" transform="rotate(-270,150,100)translate(200,100)rotate(-90)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,63 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<svg xmlns="http://www.w3.org/2000/svg">
<title>Test that marker orientation is correct at the end of arcs</title>
<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=769115 -->
<marker id="m1" markerWidth="40" markerHeight="40" refX="20" refY="20"
markerUnits="userSpaceOnUse" orient="auto" fill="blue">
<rect x="5" y="15" width="22" height="10"/>
<path d="M 25,10 35,20 25,30 z"/>
</marker>
<marker id="m2" markerWidth="40" markerHeight="40" refX="20" refY="20"
markerUnits="userSpaceOnUse" orient="auto" fill="red">
<rect x="5" y="15" width="22" height="10"/>
<path d="M 25,10 35,20 25,30 z"/>
</marker>
<g fill="none">
<!-- arcs that go from the left of the circle to... -->
<g marker-end="url(#m2)">
<!-- ...90 degrees anti-clockwise -->
<path d="M100,100 A 50,50 0 1 0 150,50" marker-start="url(#m1)"/>
<!-- ...180 degrees anti-clockwise -->
<path d="M100,100 A 50,50 0 0 0 200,100"/>
<!-- ...270 degrees anti-clockwise -->
<path d="M100,100 A 50,50 0 0 0 150,150"/>
</g>
<!-- arcs that go from the left of the circle to... -->
<g marker-end="url(#m2)" transform="translate(250,0)">
<!-- ...90 degrees clockwise -->
<path d="M100,100 A 50,50 0 0 1 150,50" marker-start="url(#m1)"/>
<!-- ...180 degrees clockwise -->
<path d="M100,100 A 50,50 0 1 1 200,100"/>
<!-- ...270 degrees clockwise -->
<path d="M100,100 A 50,50 0 1 1 150,150"/>
</g>
<!-- arcs that go from the right of the circle to... -->
<g marker-end="url(#m2)" transform="translate(0,250)">
<!-- ...90 degrees anti-clockwise -->
<path d="M200,100 A 50,50 0 0 1 150,150" marker-start="url(#m1)"/>
<!-- ...180 degrees anti-clockwise -->
<path d="M200,100 A 50,50 0 0 1 100,100"/>
<!-- ...270 degrees anti-clockwise -->
<path d="M200,100 A 50,50 0 1 1 150,50"/>
</g>
<!-- arcs that go from the right of the circle to... -->
<g marker-end="url(#m2)" transform="translate(250,250)">
<!-- ...90 degrees anti-clockwise -->
<path d="M200,100 A 50,50 0 0 0 150,50" marker-start="url(#m1)"/>
<!-- ...180 degrees anti-clockwise -->
<path d="M200,100 A 50,50 0 1 0 100,100"/>
<!-- ...270 degrees anti-clockwise -->
<path d="M200,100 A 50,50 0 1 0 150,150"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -169,6 +169,7 @@ fails == inline-in-xul-basic-01.xul pass.svg
== markers-and-group-opacity-01.svg markers-and-group-opacity-01-ref.svg
== marker-attribute-01.svg pass.svg
== marker-viewBox-01.svg marker-viewBox-01-ref.svg
== marker-orientation-01.svg marker-orientation-01-ref.svg
== mask-basic-01.svg pass.svg
== mask-basic-02.svg mask-basic-02-ref.svg
== mask-extref-dataURI-01.svg pass.svg

View File

@ -1841,6 +1841,8 @@ CSSParserImpl::ParseMediaQueryExpression(nsMediaQuery* aQuery)
NS_ASSERTION(!mToken.mIdent.IsEmpty(), "unit lied");
if (mToken.mIdent.LowerCaseEqualsLiteral("dpi")) {
expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Inch);
} else if (mToken.mIdent.LowerCaseEqualsLiteral("dppx")) {
expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Pixel);
} else if (mToken.mIdent.LowerCaseEqualsLiteral("dpcm")) {
expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Centimeter);
} else {

View File

@ -250,17 +250,25 @@ nsMediaExpression::Matches(nsPresContext *aPresContext,
case nsMediaFeature::eResolution:
{
NS_ASSERTION(actual.GetUnit() == eCSSUnit_Inch ||
actual.GetUnit() == eCSSUnit_Pixel ||
actual.GetUnit() == eCSSUnit_Centimeter,
"bad actual value");
NS_ASSERTION(required.GetUnit() == eCSSUnit_Inch ||
required.GetUnit() == eCSSUnit_Pixel ||
required.GetUnit() == eCSSUnit_Centimeter,
"bad required value");
float actualDPI = actual.GetFloatValue();
if (actual.GetUnit() == eCSSUnit_Centimeter)
if (actual.GetUnit() == eCSSUnit_Centimeter) {
actualDPI = actualDPI * 2.54f;
} else if (actual.GetUnit() == eCSSUnit_Pixel) {
actualDPI = actualDPI * 96.0f;
}
float requiredDPI = required.GetFloatValue();
if (required.GetUnit() == eCSSUnit_Centimeter)
if (required.GetUnit() == eCSSUnit_Centimeter) {
requiredDPI = requiredDPI * 2.54f;
} else if (required.GetUnit() == eCSSUnit_Pixel) {
requiredDPI = requiredDPI * 96.0f;
}
cmp = DoCompare(actualDPI, requiredDPI);
}
break;
@ -432,6 +440,8 @@ nsMediaQuery::AppendToString(nsAString& aString) const
aString.AppendFloat(expr.mValue.GetFloatValue());
if (expr.mValue.GetUnit() == eCSSUnit_Inch) {
aString.AppendLiteral("dpi");
} else if (expr.mValue.GetUnit() == eCSSUnit_Pixel) {
aString.AppendLiteral("dppx");
} else {
NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Centimeter,
"bad unit");

View File

@ -248,9 +248,10 @@ static nsresult
GetResolution(nsPresContext* aPresContext, const nsMediaFeature*,
nsCSSValue& aResult)
{
// Resolution values are in device pixels, not CSS pixels.
nsDeviceContext *dx = GetDeviceContextFor(aPresContext);
float dpi = float(dx->AppUnitsPerPhysicalInch()) / float(dx->AppUnitsPerDevPixel());
// Resolution measures device pixels per CSS (inch/cm/pixel). We
// return it in device pixels per CSS inches.
float dpi = float(nsPresContext::AppUnitsPerCSSInch()) /
float(aPresContext->AppUnitsPerDevPixel());
aResult.SetFloatValue(dpi, eCSSUnit_Inch);
return NS_OK;
}

View File

@ -34,7 +34,8 @@ struct nsMediaFeature {
eFloat, // values are eCSSUnit_Number
eBoolInteger,// values are eCSSUnit_Integer (0, -0, or 1 only)
eIntRatio, // values are eCSSUnit_Array of two eCSSUnit_Integer
eResolution, // values are in eCSSUnit_Inch (for dpi) or
eResolution, // values are in eCSSUnit_Inch (for dpi),
// eCSSUnit_Pixel (for dppx), or
// eCSSUnit_Centimeter (for dpcm)
eEnumerated, // values are eCSSUnit_Enumerated (uses keyword table)
eIdent // values are eCSSUnit_Ident

View File

@ -464,8 +464,12 @@ function run() {
expression_should_be_parseable(feature + ": 3.0dpi");
expression_should_be_parseable(feature + ": 3.4dpi");
expression_should_be_parseable(feature + "\t: 120dpcm");
expression_should_be_parseable(feature + ": 1dppx");
expression_should_be_parseable(feature + ": 1.5dppx");
expression_should_be_parseable(feature + ": 2.0dppx");
expression_should_not_be_parseable(feature + ": 0dpi");
expression_should_not_be_parseable(feature + ": -3dpi");
expression_should_not_be_parseable(feature + ": 0dppx");
}
// Find the resolution using max-resolution
@ -483,13 +487,16 @@ function run() {
var dpi_low = resolution - 1;
if (query_applies("(min-resolution: " + resolution + "dpi)")) {
// It's exact!
is(resolution % 96, 0, "resolution should be a multiple of 96dpi");
should_apply("(resolution: " + resolution + "dpi)");
should_apply("(resolution: " + Math.floor(resolution/96) + "dppx)");
should_not_apply("(resolution: " + (resolution + 1) + "dpi)");
should_not_apply("(resolution: " + (resolution - 1) + "dpi)");
dpi_high = resolution + 1;
} else {
// We have no way to test resolution applying since it need not be
// an integer.
ok(false, "resolution should be a multiple of 96dpi");
should_not_apply("(resolution: " + resolution + "dpi)");
should_not_apply("(resolution: " + (resolution - 1) + "dpi)");
dpi_high = resolution;

View File

@ -280,6 +280,19 @@ function run() {
mql.removeListener(null);
})();
/* Bug 753777: test that things work in a freshly-created iframe */
(function() {
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
is(iframe.contentWindow.matchMedia("(min-width: 1px)").matches, true,
"(min-width: 1px) should match in newly-created iframe");
is(iframe.contentWindow.matchMedia("(max-width: 1px)").matches, false,
"(max-width: 1px) should not match in newly-created iframe");
document.body.removeChild(iframe);
})();
/* Bug 716751: listeners lost due to GC */
var gc_received = [];
(function() {

View File

@ -271,6 +271,15 @@ nsSVGDisplayContainerFrame::UpdateBounds()
nsSVGEffects::UpdateEffects(this);
}
// We only invalidate if we are dirty, if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be invalidated
// when it gets that initial reflow), and if our parent is not dirty (since
// if it is, then it will invalidate its entire new area, which will include
// our new area).
bool invalidate = (mState & NS_FRAME_IS_DIRTY) &&
!(GetParent()->GetStateBits() &
(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY));
FinishAndStoreOverflow(overflowRects, mRect.Size());
// Remove state bits after FinishAndStoreOverflow so that it doesn't
@ -278,10 +287,7 @@ nsSVGDisplayContainerFrame::UpdateBounds()
mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
NS_FRAME_HAS_DIRTY_CHILDREN);
if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
// We only invalidate if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be
// invalidated when it gets that initial reflow):
if (invalidate) {
// XXXSDL Let FinishAndStoreOverflow do this.
nsSVGUtils::InvalidateBounds(this, true);
}

View File

@ -381,6 +381,15 @@ nsSVGForeignObjectFrame::UpdateBounds()
nsSVGEffects::UpdateEffects(this);
}
// We only invalidate if we are dirty, if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be invalidated
// when it gets that initial reflow), and if our parent is not dirty (since
// if it is, then it will invalidate its entire new area, which will include
// our new area).
bool invalidate = (mState & NS_FRAME_IS_DIRTY) &&
!(GetParent()->GetStateBits() &
(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY));
// TODO: once we support |overflow:visible| on foreignObject, then we will
// need to take account of our descendants here.
nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
@ -391,10 +400,7 @@ nsSVGForeignObjectFrame::UpdateBounds()
mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
NS_FRAME_HAS_DIRTY_CHILDREN);
if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
// We only invalidate if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be
// invalidated when it gets that initial reflow):
if (invalidate) {
// XXXSDL Let FinishAndStoreOverflow do this.
nsSVGUtils::InvalidateBounds(this, true);
}

View File

@ -490,6 +490,15 @@ nsSVGGlyphFrame::UpdateBounds()
mCoveredRegion = nsSVGUtils::TransformFrameRectToOuterSVG(
mRect, GetCanvasTM(FOR_OUTERSVG_TM), PresContext());
// We only invalidate if we are dirty, if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be invalidated
// when it gets that initial reflow), and if our parent is not dirty (since
// if it is, then it will invalidate its entire new area, which will include
// our new area).
bool invalidate = (mState & NS_FRAME_IS_DIRTY) &&
!(GetParent()->GetStateBits() &
(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY));
nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
nsOverflowAreas overflowAreas(overflow, overflow);
FinishAndStoreOverflow(overflowAreas, mRect.Size());
@ -497,10 +506,7 @@ nsSVGGlyphFrame::UpdateBounds()
mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
NS_FRAME_HAS_DIRTY_CHILDREN);
if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
// We only invalidate if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be
// invalidated when it gets that initial reflow):
if (invalidate) {
// XXXSDL Let FinishAndStoreOverflow do this.
nsSVGUtils::InvalidateBounds(this, true);
}

View File

@ -496,6 +496,15 @@ nsSVGImageFrame::UpdateBounds()
nsSVGEffects::UpdateEffects(this);
}
// We only invalidate if we are dirty, if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be invalidated
// when it gets that initial reflow), and if our parent is not dirty (since
// if it is, then it will invalidate its entire new area, which will include
// our new area).
bool invalidate = (mState & NS_FRAME_IS_DIRTY) &&
!(GetParent()->GetStateBits() &
(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY));
nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
nsOverflowAreas overflowAreas(overflow, overflow);
FinishAndStoreOverflow(overflowAreas, mRect.Size());
@ -503,10 +512,7 @@ nsSVGImageFrame::UpdateBounds()
mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
NS_FRAME_HAS_DIRTY_CHILDREN);
if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
// We only invalidate if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be
// invalidated when it gets that initial reflow):
if (invalidate) {
// XXXSDL Let FinishAndStoreOverflow do this.
nsSVGUtils::InvalidateBounds(this, true);
}

View File

@ -239,6 +239,15 @@ nsSVGPathGeometryFrame::UpdateBounds()
nsSVGEffects::UpdateEffects(this);
}
// We only invalidate if we are dirty, if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be invalidated
// when it gets that initial reflow), and if our parent is not dirty (since
// if it is, then it will invalidate its entire new area, which will include
// our new area).
bool invalidate = (mState & NS_FRAME_IS_DIRTY) &&
!(GetParent()->GetStateBits() &
(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY));
nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
nsOverflowAreas overflowAreas(overflow, overflow);
FinishAndStoreOverflow(overflowAreas, mRect.Size());
@ -246,12 +255,8 @@ nsSVGPathGeometryFrame::UpdateBounds()
mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
NS_FRAME_HAS_DIRTY_CHILDREN);
// XXXSDL get rid of this in favor of the invalidate call in
// FinishAndStoreOverflow?
if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
// We only invalidate if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be
// invalidated when it gets that initial reflow):
if (invalidate) {
// XXXSDL Let FinishAndStoreOverflow do this.
nsSVGUtils::InvalidateBounds(this, true);
}
}

View File

@ -184,6 +184,15 @@ nsSVGSwitchFrame::UpdateBounds()
nsSVGEffects::UpdateEffects(this);
}
// We only invalidate if we are dirty, if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be invalidated
// when it gets that initial reflow), and if our parent is not dirty (since
// if it is, then it will invalidate its entire new area, which will include
// our new area).
bool invalidate = (mState & NS_FRAME_IS_DIRTY) &&
!(GetParent()->GetStateBits() &
(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY));
FinishAndStoreOverflow(overflowRects, mRect.Size());
// Remove state bits after FinishAndStoreOverflow so that it doesn't
@ -191,10 +200,7 @@ nsSVGSwitchFrame::UpdateBounds()
mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
NS_FRAME_HAS_DIRTY_CHILDREN);
if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
// We only invalidate if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be
// invalidated when it gets that initial reflow):
if (invalidate) {
// XXXSDL Let FinishAndStoreOverflow do this.
nsSVGUtils::InvalidateBounds(this, true);
}

View File

@ -231,14 +231,20 @@ nsSVGTextFrame::UpdateBounds()
UpdateGlyphPositioning(false);
// We only invalidate if we are dirty, if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be invalidated
// when it gets that initial reflow), and if our parent is not dirty (since
// if it is, then it will invalidate its entire new area, which will include
// our new area).
bool invalidate = (mState & NS_FRAME_IS_DIRTY) &&
!(GetParent()->GetStateBits() &
(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY));
// With glyph positions updated, our descendants can invalidate their new
// areas correctly:
nsSVGTextFrameBase::UpdateBounds();
if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
// We only invalidate if our outer-<svg> has already had its
// initial reflow (since if it hasn't, its entire area will be
// invalidated when it gets that initial reflow):
if (invalidate) {
// XXXSDL Let FinishAndStoreOverflow do this.
nsSVGUtils::InvalidateBounds(this, true);
}

View File

@ -485,6 +485,11 @@ include $(topsrcdir)/config/rules.mk
# recursively-expanded variable.
ifdef VPX_NEED_OBJ_INT_EXTRACT
# only for MSVC
ifdef _MSC_VER
asm_com_offsets.$(OBJ_SUFFIX): CFLAGS += -GL-
endif
asm_com_offsets.asm: asm_com_offsets.$(OBJ_SUFFIX) $(HOST_PROGRAM)
./$(HOST_PROGRAM) $(VPX_OIE_FORMAT) $< \
$(if $(VPX_AS_CONVERSION),| $(VPX_AS_CONVERSION)) > $@
@ -495,6 +500,10 @@ OBJS := $(filter-out asm_com_offsets.$(OBJ_SUFFIX),$(OBJS))
ifdef MOZ_VP8_ENCODER
ifdef _MSC_VER
asm_enc_offsets.$(OBJ_SUFFIX): CFLAGS += -GL-
endif
asm_enc_offsets.asm: asm_enc_offsets.$(OBJ_SUFFIX) $(HOST_PROGRAM)
./$(HOST_PROGRAM) $(VPX_OIE_FORMAT) $< \
$(if $(VPX_AS_CONVERSION),| $(VPX_AS_CONVERSION)) > $@

View File

@ -376,6 +376,7 @@ class TableTicker: public Sampler {
//XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point
mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank");
mProfileJS = hasFeature(aFeatures, aFeatureCount, "js");
mPrimaryThreadProfile.addTag(ProfileEntry('m', "Start"));
}
@ -402,6 +403,8 @@ class TableTicker: public Sampler {
JSObject *ToJSObject(JSContext *aCx);
JSObject *GetMetaJSObject(JSObjectBuilder& b);
const bool ProfileJS() { return mProfileJS; }
private:
// Not implemented on platforms which do not support backtracing
void doBacktrace(ThreadProfile &aProfile, TickSample* aSample);
@ -413,6 +416,7 @@ private:
bool mSaveRequested;
bool mUseStackWalk;
bool mJankOnly;
bool mProfileJS;
};
std::string GetSharedLibraryInfoString();
@ -683,7 +687,9 @@ void doSampleStackTrace(ProfileStack *aStack, ThreadProfile &aProfile, TickSampl
// 's' tag denotes the start of a sample block
// followed by 0 or more 'c' tags.
aProfile.addTag(ProfileEntry('s', "(root)"));
for (mozilla::sig_safe_t i = 0; i < aStack->mStackPointer; i++) {
for (mozilla::sig_safe_t i = 0;
i < aStack->mStackPointer && i < mozilla::ArrayLength(aStack->mStack);
i++) {
// First entry has tagName 's' (start)
// Check for magic pointer bit 1 to indicate copy
const char* sampleLabel = aStack->mStack[i].mLabel;
@ -906,6 +912,7 @@ const char** mozilla_sampler_get_features()
"stackwalk",
#endif
"jank",
"js",
NULL
};
@ -931,6 +938,8 @@ void mozilla_sampler_start(int aProfileEntries, int aInterval,
aFeatures, aFeatureCount);
tlsTicker.set(t);
t->Start();
if (t->ProfileJS())
stack->installJSSampling();
}
void mozilla_sampler_stop()
@ -943,9 +952,16 @@ void mozilla_sampler_stop()
return;
}
bool uninstallJS = t->ProfileJS();
t->Stop();
delete t;
tlsTicker.set(NULL);
ProfileStack *stack = tlsStack.get();
ASSERT(stack != NULL);
if (uninstallJS)
stack->uninstallJSSampling();
}
bool mozilla_sampler_is_active()

View File

@ -12,6 +12,14 @@
#include "mozilla/TimeStamp.h"
#include "mozilla/Util.h"
/* QT has a #define for the word "slots" and jsfriendapi.h has a struct with
* this variable name, causing compilation problems. Alleviate this for now by
* removing this #define */
#ifdef MOZ_WIDGET_QT
#undef slots
#endif
#include "jsfriendapi.h"
using mozilla::TimeStamp;
using mozilla::TimeDuration;
@ -225,7 +233,6 @@ public:
ProfileStack()
: mStackPointer(0)
, mMarkerPointer(0)
, mDroppedStackEntries(0)
, mQueueClearMarker(false)
{ }
@ -273,7 +280,7 @@ public:
void push(const char *aName, void *aStackAddress, bool aCopy)
{
if (size_t(mStackPointer) >= mozilla::ArrayLength(mStack)) {
mDroppedStackEntries++;
mStackPointer++;
return;
}
@ -288,29 +295,47 @@ public:
}
void pop()
{
if (mDroppedStackEntries > 0) {
mDroppedStackEntries--;
} else {
mStackPointer--;
}
mStackPointer--;
}
bool isEmpty()
{
return mStackPointer == 0;
}
void sampleRuntime(JSRuntime *runtime) {
mRuntime = runtime;
}
void installJSSampling() {
JS_STATIC_ASSERT(sizeof(mStack[0]) == sizeof(js::ProfileEntry));
js::SetRuntimeProfilingStack(mRuntime,
(js::ProfileEntry*) mStack,
(uint32_t*) &mStackPointer,
mozilla::ArrayLength(mStack));
}
void uninstallJSSampling() {
js::SetRuntimeProfilingStack(mRuntime, NULL, NULL, 0);
}
// Keep a list of active checkpoints
StackEntry volatile mStack[1024];
// Keep a list of active markers to be applied to the next sample taken
char const * volatile mMarkers[1024];
volatile mozilla::sig_safe_t mStackPointer;
volatile mozilla::sig_safe_t mMarkerPointer;
volatile mozilla::sig_safe_t mDroppedStackEntries;
// We don't want to modify _markers from within the signal so we allow
// it to queue a clear operation.
volatile mozilla::sig_safe_t mQueueClearMarker;
// The runtime which is being sampled
JSRuntime *mRuntime;
};
inline ProfileStack* mozilla_profile_stack(void)
{
if (!stack_key_initialized)
return NULL;
return tlsStack.get();
}
inline void* mozilla_sampler_call_enter(const char *aInfo, void *aFrameAddress, bool aCopy)
{
// check if we've been initialized to avoid calling pthread_getspecific