From b36af0f7f2d41abf024ca141978fe8a046edc0d7 Mon Sep 17 00:00:00 2001 From: Jason Orendorff Date: Sat, 7 Jul 2012 16:06:09 -0700 Subject: [PATCH 01/18] Bug 768313 - Crash with newGlobal, newContext, --dump-bytecode, happens because jaeger inlines the non-pccount script into the pccount script, r=bhackett --- js/src/jit-test/README | 1 + js/src/jit-test/jit_test.py | 2 ++ js/src/jit-test/tests/jaeger/bug768313.js | 6 ++++++ js/src/methodjit/Compiler.cpp | 6 ++++++ 4 files changed, 15 insertions(+) create mode 100644 js/src/jit-test/tests/jaeger/bug768313.js diff --git a/js/src/jit-test/README b/js/src/jit-test/README index 6eaeb8d5025..811b01fc8d6 100644 --- a/js/src/jit-test/README +++ b/js/src/jit-test/README @@ -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. diff --git a/js/src/jit-test/jit_test.py b/js/src/jit-test/jit_test.py index 4f8265dcfe4..3c5326f9299 100755 --- a/js/src/jit-test/jit_test.py +++ b/js/src/jit-test/jit_test.py @@ -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) diff --git a/js/src/jit-test/tests/jaeger/bug768313.js b/js/src/jit-test/tests/jaeger/bug768313.js new file mode 100644 index 00000000000..9ec798c2c77 --- /dev/null +++ b/js/src/jit-test/tests/jaeger/bug768313.js @@ -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); diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index 4ba242fafc4..3cd3b8e7504 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -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 From 49aaa718a1084b2b79e05cf31c303658f7bcfade Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Fri, 6 Jul 2012 13:52:53 -0700 Subject: [PATCH 02/18] Bug 771742 - Reorganize all the date/time spec algorithms and constants to be in spec order (when possible), have proper types, be methods rather than macros, and so on. This will make it easier to refactor some of the Date method implementations to read more like the spec algorithms. r=luke --HG-- extra : rebase_source : 66fb1c02124713ffa3fb66afde15dc075f309744 --- js/src/jsdate.cpp | 291 +++++++++++++++++++++++++++------------------- 1 file changed, 170 insertions(+), 121 deletions(-) diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index 2b28df175c5..4f0dfd163f8 100644 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -128,76 +128,58 @@ using namespace js::types; * general going-over. */ -/* - * Supporting functions - ECMA 15.9.1.* - */ +/* ES5 15.9.1.2. */ +const double msPerDay = 86400000; -#define HoursPerDay 24.0 -#define MinutesPerDay (HoursPerDay * MinutesPerHour) -#define MinutesPerHour 60.0 -#define SecondsPerDay (MinutesPerDay * SecondsPerMinute) -#define SecondsPerHour (MinutesPerHour * SecondsPerMinute) -#define SecondsPerMinute 60.0 - -#if defined(XP_WIN) || defined(XP_OS2) -/* Work around msvc double optimization bug by making these runtime values; if - * they're available at compile time, msvc optimizes division by them by - * computing the reciprocal and multiplying instead of dividing - this loses - * when the reciprocal isn't representable in a double. - */ -static double msPerSecond = 1000.0; -static double msPerDay = SecondsPerDay * 1000.0; -static double msPerHour = SecondsPerHour * 1000.0; -static double msPerMinute = SecondsPerMinute * 1000.0; -#else -#define msPerDay (SecondsPerDay * msPerSecond) -#define msPerHour (SecondsPerHour * msPerSecond) -#define msPerMinute (SecondsPerMinute * msPerSecond) -#define msPerSecond 1000.0 -#endif - -#define Day(t) floor((t) / msPerDay) +inline double +Day(double t) +{ + return floor(t / msPerDay); +} static double TimeWithinDay(double t) { - double result; - result = fmod(t, msPerDay); + double result = fmod(t, msPerDay); if (result < 0) result += msPerDay; return result; } -static inline bool +/* ES5 15.9.1.3. */ +inline bool IsLeapYear(int year) { return year % 4 == 0 && (year % 100 || (year % 400 == 0)); } -static inline int +inline int DaysInYear(int year) { return IsLeapYear(year) ? 366 : 365; } -static inline int -DaysInFebruary(int year) +inline int +DayFromYear(int y) { - return IsLeapYear(year) ? 29 : 28; + /* This is floating-point math so that floor((1968 - 1969) / 4) == -1. */ + return 365 * (y - 1970) + + floor((y - 1969) / 4.0) - + floor((y - 1901) / 100.0) + + floor((y - 1601) / 400.0); } -/* math here has to be f.p, because we need - * floor((1968 - 1969) / 4) == -1 - */ -#define DayFromYear(y) (365 * ((y)-1970) + floor(((y)-1969)/4.0) \ - - floor(((y)-1901)/100.0) + floor(((y)-1601)/400.0)) -#define TimeFromYear(y) (DayFromYear(y) * msPerDay) +inline double +TimeFromYear(int y) +{ + return DayFromYear(y) * msPerDay; +} static int YearFromTime(double t) { - int y = (int) floor(t /(msPerDay*365.2425)) + 1970; - double t2 = (double) TimeFromYear(y); + int y = (int) floor(t / (msPerDay * 365.2425)) + 1970; + double t2 = TimeFromYear(y); /* * Adjust the year if the approximation was wrong. Since the year was @@ -213,34 +195,27 @@ YearFromTime(double t) return y; } -#define DayWithinYear(t, year) ((int) (Day(t) - DayFromYear(year))) - -/* - * The following array contains the day of year for the first day of - * each month, where index 0 is January, and day 0 is January 1. - */ -static double firstDayOfMonth[2][13] = { - {0.0, 31.0, 59.0, 90.0, 120.0, 151.0, 181.0, 212.0, 243.0, 273.0, 304.0, 334.0, 365.0}, - {0.0, 31.0, 60.0, 91.0, 121.0, 152.0, 182.0, 213.0, 244.0, 274.0, 305.0, 335.0, 366.0} -}; - -#define DayFromMonth(m, leap) firstDayOfMonth[leap][(int)m] - -static int -DaysInMonth(int year, int month) +inline int +DaysInFebruary(int year) { - JSBool leap = IsLeapYear(year); - int result = int(DayFromMonth(month, leap) - DayFromMonth(month-1, leap)); - return result; + return IsLeapYear(year) ? 29 : 28; +} + +/* ES5 15.9.1.4. */ +inline int +DayWithinYear(double t, int year) +{ + JS_ASSERT(YearFromTime(t) == year); + return int(Day(t) - DayFromYear(year)); } static int MonthFromTime(double t) { - int d, step; int year = YearFromTime(t); - d = DayWithinYear(t, year); + int d = DayWithinYear(t, year); + int step; if (d < (step = 31)) return 0; if (d < (step += DaysInFebruary(year))) @@ -266,16 +241,17 @@ MonthFromTime(double t) return 11; } +/* ES5 15.9.1.5. */ static int DateFromTime(double t) { - int d, step, next; int year = YearFromTime(t); - d = DayWithinYear(t, year); + int d = DayWithinYear(t, year); + int next; if (d <= (next = 30)) return d + 1; - step = next; + int step = next; if (d <= (next += DaysInFebruary(year))) return d - step; step = next; @@ -309,56 +285,18 @@ DateFromTime(double t) return d - step; } +/* ES5 15.9.1.6. */ static int WeekDay(double t) { - int result; - result = (int) Day(t) + 4; - result = result % 7; + int result = (int(Day(t)) + 4) % 7; if (result < 0) result += 7; - return (int) result; + return result; } -#define MakeTime(hour, min, sec, ms) \ -((((hour) * MinutesPerHour + (min)) * SecondsPerMinute + (sec)) * msPerSecond + (ms)) - -static double -MakeDay(double year, double month, double date) -{ - JSBool leap; - double yearday; - double monthday; - - year += floor(month / 12); - - month = fmod(month, 12.0); - if (month < 0) - month += 12; - - leap = IsLeapYear((int) year); - - yearday = floor(TimeFromYear(year) / msPerDay); - monthday = DayFromMonth(month, leap); - - return yearday + monthday + date - 1; -} - -#define MakeDate(day, time) ((day) * msPerDay + (time)) - -/* - * Years and leap years on which Jan 1 is a Sunday, Monday, etc. - * - * yearStartingWith[0][i] is an example non-leap year where - * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc. - * - * yearStartingWith[1][i] is an example leap year where - * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc. - */ -static int yearStartingWith[2][7] = { - {1978, 1973, 1974, 1975, 1981, 1971, 1977}, - {1984, 1996, 1980, 1992, 1976, 1988, 1972} -}; +/* ES5 15.9.1.7. */ +static double LocalTZA; // set by js_InitDateClass /* * Find a year for which any given date will fall on the same weekday. @@ -370,25 +308,86 @@ static int yearStartingWith[2][7] = { static int EquivalentYearForDST(int year) { - int day; + /* + * Years and leap years on which Jan 1 is a Sunday, Monday, etc. + * + * yearStartingWith[0][i] is an example non-leap year where + * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc. + * + * yearStartingWith[1][i] is an example leap year where + * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc. + */ + static const int yearStartingWith[2][7] = { + {1978, 1973, 1974, 1975, 1981, 1971, 1977}, + {1984, 1996, 1980, 1992, 1976, 1988, 1972} + }; - day = (int) DayFromYear(year) + 4; - day = day % 7; + int day = int(DayFromYear(year) + 4) % 7; if (day < 0) day += 7; return yearStartingWith[IsLeapYear(year)][day]; } -/* LocalTZA gets set by js_InitDateClass() */ -static double LocalTZA; +inline int +DayFromMonth(int month, bool isLeapYear) +{ + /* + * The following array contains the day of year for the first day of + * each month, where index 0 is January, and day 0 is January 1. + */ + static const int firstDayOfMonth[2][13] = { + {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, + {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366} + }; + JS_ASSERT(0 <= month && month <= 12); + return firstDayOfMonth[isLeapYear][month]; +} + +/* ES5 15.9.1.12 (out of order to accommodate DaylightSavingTA). */ +static double +MakeDay(double year, double month, double date) +{ + if (!MOZ_DOUBLE_IS_FINITE(year) || !MOZ_DOUBLE_IS_FINITE(month) || !MOZ_DOUBLE_IS_FINITE(date)) + return js_NaN; + + JS_ASSERT(ToInteger(year) == year); + JS_ASSERT(ToInteger(month) == month); + JS_ASSERT(ToInteger(date) == date); + + year += floor(month / 12); + + month = fmod(month, 12.0); + if (month < 0) + month += 12; + + bool leap = IsLeapYear((int) year); + + double yearday = floor(TimeFromYear(year) / msPerDay); + double monthday = DayFromMonth(month, leap); + + return yearday + monthday + date - 1; +} + +/* ES5 15.9.1.13 (out of order to accommodate DaylightSavingTA). */ +inline double +MakeDate(double day, double time) +{ + /* Step 1. */ + if (!MOZ_DOUBLE_IS_FINITE(day) || !MOZ_DOUBLE_IS_FINITE(time)) + return js_NaN; + + /* Step 2. */ + return day * msPerDay + time; +} + +/* ES5 15.9.1.8. */ static double DaylightSavingTA(double t, JSContext *cx) { - /* abort if NaN */ - if (MOZ_DOUBLE_IS_NaN(t)) - return t; + if (!MOZ_DOUBLE_IS_FINITE(t)) + return js_NaN; /* * If earlier than 1970 or after 2038, potentially beyond the ken of @@ -413,6 +412,7 @@ AdjustTime(double date, JSContext *cx) return t; } +/* ES5 15.9.1.9. */ static double LocalTime(double t, JSContext *cx) { @@ -425,12 +425,20 @@ UTC(double t, JSContext *cx) return t - AdjustTime(t - LocalTZA, cx); } +/* ES5 15.9.1.10. */ +const double HoursPerDay = 24.0; +const double MinutesPerHour = 60; +const double SecondsPerMinute = 60; +const double msPerSecond = 1000; +const double msPerMinute = msPerSecond * SecondsPerMinute; +const double msPerHour = msPerMinute * MinutesPerHour; + static int HourFromTime(double t) { int result = (int) fmod(floor(t/msPerHour), HoursPerDay); if (result < 0) - result += (int)HoursPerDay; + result += int(HoursPerDay); return result; } @@ -439,7 +447,7 @@ MinFromTime(double t) { int result = (int) fmod(floor(t / msPerMinute), MinutesPerHour); if (result < 0) - result += (int)MinutesPerHour; + result += int(MinutesPerHour); return result; } @@ -448,7 +456,7 @@ SecFromTime(double t) { int result = (int) fmod(floor(t / msPerSecond), SecondsPerMinute); if (result < 0) - result += (int)SecondsPerMinute; + result += int(SecondsPerMinute); return result; } @@ -457,7 +465,48 @@ msFromTime(double t) { int result = (int) fmod(t, msPerSecond); if (result < 0) - result += (int)msPerSecond; + result += int(msPerSecond); + return result; +} + +/* ES5 15.9.1.11. */ +static double +MakeTime(double hour, double min, double sec, double ms) +{ + /* Step 1. */ + if (!MOZ_DOUBLE_IS_FINITE(hour) || + !MOZ_DOUBLE_IS_FINITE(min) || + !MOZ_DOUBLE_IS_FINITE(sec) || + !MOZ_DOUBLE_IS_FINITE(ms)) + { + return js_NaN; + } + + /* Step 2. */ + double h = ToInteger(hour); + + /* Step 3. */ + double m = ToInteger(min); + + /* Step 4. */ + double s = ToInteger(sec); + + /* Step 5. */ + double milli = ToInteger(ms); + + /* Steps 6-7. */ + return h * msPerHour + m * msPerMinute + s * msPerSecond + milli; +} + +/* Additional quantities not mentioned in the spec. */ +const double SecondsPerDay = SecondsPerMinute * MinutesPerHour * HoursPerDay; + +/* Additional methods not mentioned in the spec. */ +static int +DaysInMonth(int year, int month) +{ + bool leap = IsLeapYear(year); + int result = int(DayFromMonth(month, leap) - DayFromMonth(month - 1, leap)); return result; } From d68f5c5e3e8fbfd0309d19d36bcb1b60de798e2b Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Fri, 6 Jul 2012 13:53:10 -0700 Subject: [PATCH 03/18] Bug 771742 - Reimplement the Date.prototype.set functions in terms of their spec steps, and remove the hard-to-understand date_makeTime. r=luke --HG-- extra : rebase_source : cf070e258834853ebe24ec9cf0027de1d194a62c --- js/src/jsdate.cpp | 375 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 278 insertions(+), 97 deletions(-) diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index 4f0dfd163f8..a7aaf287878 100644 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -1783,149 +1783,330 @@ date_setTime(JSContext *cx, unsigned argc, Value *vp) return SetUTCTime(cx, thisObj, TimeClip(result), &args.rval()); } +static bool +GetMsecsOrDefault(JSContext *cx, const CallArgs &args, unsigned i, double t, double *millis) +{ + if (args.length() <= i) { + *millis = msFromTime(t); + return true; + } + return ToNumber(cx, args[i], millis); +} + +static bool +GetSecsOrDefault(JSContext *cx, const CallArgs &args, unsigned i, double t, double *sec) +{ + if (args.length() <= i) { + *sec = SecFromTime(t); + return true; + } + return ToNumber(cx, args[i], sec); +} + +static bool +GetMinsOrDefault(JSContext *cx, const CallArgs &args, unsigned i, double t, double *mins) +{ + if (args.length() <= i) { + *mins = MinFromTime(t); + return true; + } + return ToNumber(cx, args[i], mins); +} + +/* ES5 15.9.5.28. */ static JSBool -date_makeTime(JSContext *cx, Native native, unsigned maxargs, JSBool local, unsigned argc, Value *vp) +date_setMilliseconds(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); - RootedObject thisObj(cx); - if (!NonGenericMethodGuard(cx, args, native, &DateClass, thisObj.address())) + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setMilliseconds, &DateClass, thisObj.address())) return false; if (!thisObj) return true; - double result = thisObj->getDateUTCTime().toNumber(); + /* Step 1. */ + double t = LocalTime(thisObj->getDateUTCTime().toNumber(), cx); - /* - * Satisfy the ECMA rule that if a function is called with - * fewer arguments than the specified formal arguments, the - * remaining arguments are set to undefined. Seems like all - * the Date.setWhatever functions in ECMA are only varargs - * beyond the first argument; this should be set to undefined - * if it's not given. This means that "d = new Date(); - * d.setMilliseconds()" returns NaN. Blech. - */ - if (args.length() == 0) { - SetDateToNaN(cx, thisObj, &args.rval()); - return true; - } + /* Step 2. */ + double milli; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &milli)) + return false; + double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli); - unsigned numNums = Min(args.length(), maxargs); - JS_ASSERT(numNums <= 4); - double nums[4]; - bool argIsNotFinite = false; - for (unsigned i = 0; i < numNums; i++) { - if (!ToNumber(cx, args[i], &nums[i])) - return false; - if (!MOZ_DOUBLE_IS_FINITE(nums[i])) { - argIsNotFinite = true; - } else { - nums[i] = ToInteger(nums[i]); - } - } + /* Step 3. */ + double u = TimeClip(UTC(MakeDate(Day(t), time), cx)); - /* - * Return NaN if the date is already NaN, but don't short-circuit argument - * evaluation. - */ - if (!MOZ_DOUBLE_IS_FINITE(result)) { - args.rval().setNumber(result); - return true; - } - - /* set Date to NaN, after argument evaluation. */ - if (argIsNotFinite) { - SetDateToNaN(cx, thisObj, &args.rval()); - return true; - } - - double lorutime; /* Local or UTC version of *date */ - if (local) - lorutime = LocalTime(result, cx); - else - lorutime = result; - - double *argp = nums; - double *stop = argp + numNums; - double hour; - if (maxargs >= 4 && argp < stop) - hour = *argp++; - else - hour = HourFromTime(lorutime); - - double min; - if (maxargs >= 3 && argp < stop) - min = *argp++; - else - min = MinFromTime(lorutime); - - double sec; - if (maxargs >= 2 && argp < stop) - sec = *argp++; - else - sec = SecFromTime(lorutime); - - double msec; - if (maxargs >= 1 && argp < stop) - msec = *argp; - else - msec = msFromTime(lorutime); - - double msec_time = MakeTime(hour, min, sec, msec); - result = MakeDate(Day(lorutime), msec_time); - - if (local) - result = UTC(result, cx); - - return SetUTCTime(cx, thisObj, TimeClip(result), &args.rval()); -} - -static JSBool -date_setMilliseconds(JSContext *cx, unsigned argc, Value *vp) -{ - return date_makeTime(cx, date_setMilliseconds, 1, JS_TRUE, argc, vp); + /* Steps 4-5. */ + return SetUTCTime(cx, thisObj, u, &args.rval()); } +/* ES5 15.9.5.29. */ static JSBool date_setUTCMilliseconds(JSContext *cx, unsigned argc, Value *vp) { - return date_makeTime(cx, date_setUTCMilliseconds, 1, JS_FALSE, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setUTCMilliseconds, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + /* Step 1. */ + double t = thisObj->getDateUTCTime().toNumber(); + + /* Step 2. */ + double milli; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &milli)) + return false; + double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli); + + /* Step 3. */ + double v = TimeClip(MakeDate(Day(t), time)); + + /* Steps 4-5. */ + return SetUTCTime(cx, thisObj, v, &args.rval()); } +/* ES5 15.9.5.30. */ static JSBool date_setSeconds(JSContext *cx, unsigned argc, Value *vp) { - return date_makeTime(cx, date_setSeconds, 2, JS_TRUE, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setSeconds, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + /* Step 1. */ + double t = LocalTime(thisObj->getDateUTCTime().toNumber(), cx); + + /* Step 2. */ + double s; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &s)) + return false; + + /* Step 3. */ + double milli; + if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) + return false; + + /* Step 4. */ + double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)); + + /* Step 5. */ + double u = TimeClip(UTC(date, cx)); + + /* Steps 6-7. */ + return SetUTCTime(cx, thisObj, u, &args.rval()); } +/* ES5 15.9.5.31. */ static JSBool date_setUTCSeconds(JSContext *cx, unsigned argc, Value *vp) { - return date_makeTime(cx, date_setUTCSeconds, 2, JS_FALSE, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setUTCSeconds, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + /* Step 1. */ + double t = thisObj->getDateUTCTime().toNumber(); + + /* Step 2. */ + double s; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &s)) + return false; + + /* Step 3. */ + double milli; + if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) + return false; + + /* Step 4. */ + double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)); + + /* Step 5. */ + double v = TimeClip(date); + + /* Steps 6-7. */ + return SetUTCTime(cx, thisObj, v, &args.rval()); } +/* ES5 15.9.5.32. */ static JSBool date_setMinutes(JSContext *cx, unsigned argc, Value *vp) { - return date_makeTime(cx, date_setMinutes, 3, JS_TRUE, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setMinutes, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + /* Step 1. */ + double t = LocalTime(thisObj->getDateUTCTime().toNumber(), cx); + + /* Step 2. */ + double m; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &m)) + return false; + + /* Step 3. */ + double s; + if (!GetSecsOrDefault(cx, args, 1, t, &s)) + return false; + + /* Step 4. */ + double milli; + if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) + return false; + + /* Step 5. */ + double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)); + + /* Step 6. */ + double u = TimeClip(UTC(date, cx)); + + /* Steps 7-8. */ + return SetUTCTime(cx, thisObj, u, &args.rval()); } +/* ES5 15.9.5.33. */ static JSBool date_setUTCMinutes(JSContext *cx, unsigned argc, Value *vp) { - return date_makeTime(cx, date_setUTCMinutes, 3, JS_FALSE, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setUTCMinutes, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + /* Step 1. */ + double t = thisObj->getDateUTCTime().toNumber(); + + /* Step 2. */ + double m; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &m)) + return false; + + /* Step 3. */ + double s; + if (!GetSecsOrDefault(cx, args, 1, t, &s)) + return false; + + /* Step 4. */ + double milli; + if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) + return false; + + /* Step 5. */ + double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)); + + /* Step 6. */ + double v = TimeClip(date); + + /* Steps 7-8. */ + return SetUTCTime(cx, thisObj, v, &args.rval()); } +/* ES5 15.9.5.34. */ static JSBool date_setHours(JSContext *cx, unsigned argc, Value *vp) { - return date_makeTime(cx, date_setHours, 4, JS_TRUE, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setHours, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + /* Step 1. */ + double t = LocalTime(thisObj->getDateUTCTime().toNumber(), cx); + + /* Step 2. */ + double h; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &h)) + return false; + + /* Step 3. */ + double m; + if (!GetMinsOrDefault(cx, args, 1, t, &m)) + return false; + + /* Step 4. */ + double s; + if (!GetSecsOrDefault(cx, args, 2, t, &s)) + return false; + + /* Step 5. */ + double milli; + if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) + return false; + + /* Step 6. */ + double date = MakeDate(Day(t), MakeTime(h, m, s, milli)); + + /* Step 6. */ + double u = TimeClip(UTC(date, cx)); + + /* Steps 7-8. */ + return SetUTCTime(cx, thisObj, u, &args.rval()); } +/* ES5 15.9.5.35. */ static JSBool date_setUTCHours(JSContext *cx, unsigned argc, Value *vp) { - return date_makeTime(cx, date_setUTCHours, 4, JS_FALSE, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setUTCHours, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + /* Step 1. */ + double t = thisObj->getDateUTCTime().toNumber(); + + /* Step 2. */ + double h; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &h)) + return false; + + /* Step 3. */ + double m; + if (!GetMinsOrDefault(cx, args, 1, t, &m)) + return false; + + /* Step 4. */ + double s; + if (!GetSecsOrDefault(cx, args, 2, t, &s)) + return false; + + /* Step 5. */ + double milli; + if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) + return false; + + /* Step 6. */ + double newDate = MakeDate(Day(t), MakeTime(h, m, s, milli)); + + /* Step 7. */ + double v = TimeClip(newDate); + + /* Steps 8-9. */ + return SetUTCTime(cx, thisObj, v, &args.rval()); } static JSBool From 8c6e1140e9abd1aaf5ff36f2cb019a576129b347 Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Fri, 6 Jul 2012 13:53:11 -0700 Subject: [PATCH 04/18] Bug 771742 - Reimplement the Date.prototype.set functions in terms of their spec steps, and remove the hard-to-understand date_makeDate. r=luke --HG-- extra : rebase_source : 84ced3e65ab44f2e0440c8e24bb9b70d12259940 --- js/src/jsdate.cpp | 326 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 228 insertions(+), 98 deletions(-) diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index a7aaf287878..5c5cc6c6805 100644 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -2109,125 +2109,253 @@ date_setUTCHours(JSContext *cx, unsigned argc, Value *vp) return SetUTCTime(cx, thisObj, v, &args.rval()); } +/* ES5 15.9.5.36. */ static JSBool -date_makeDate(JSContext *cx, Native native, unsigned maxargs, JSBool local, unsigned argc, Value *vp) +date_setDate(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); - RootedObject thisObj(cx); - if (!NonGenericMethodGuard(cx, args, native, &DateClass, thisObj.address())) + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setDate, &DateClass, thisObj.address())) return false; if (!thisObj) return true; - double result = thisObj->getDateUTCTime().toNumber(); + /* Step 1. */ + double t = LocalTime(thisObj->getDateUTCTime().toNumber(), cx); - /* See complaint about ECMA in date_makeTime. */ - if (args.length() == 0) { - SetDateToNaN(cx, thisObj, &args.rval()); - return true; - } + /* Step 2. */ + double dt; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &dt)) + return false; - unsigned numNums = Min(args.length(), maxargs); - JS_ASSERT(1 <= numNums && numNums <= 3); - double nums[3]; - bool argIsNotFinite = false; - for (unsigned i = 0; i < numNums; i++) { - if (!ToNumber(cx, args[i], &nums[i])) - return JS_FALSE; - if (!MOZ_DOUBLE_IS_FINITE(nums[i])) { - argIsNotFinite = true; - } else { - nums[i] = ToInteger(nums[i]); - } - } + /* Step 3. */ + double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t)); - /* If we found a non-finite argument, set the date to NaN and return. */ - if (argIsNotFinite) { - SetDateToNaN(cx, thisObj, &args.rval()); - return true; - } + /* Step 4. */ + double u = TimeClip(UTC(newDate, cx)); - /* - * Return NaN if date is NaN and we're not setting the year. If we are, - * use 0 as the time. - */ - double lorutime; /* local or UTC version of *date */ - if (!MOZ_DOUBLE_IS_FINITE(result)) { - if (maxargs < 3) { - args.rval().setDouble(result); - return true; - } - lorutime = +0.; - } else { - lorutime = local ? LocalTime(result, cx) : result; - } - - double *argp = nums; - double *stop = argp + numNums; - double year; - if (maxargs >= 3 && argp < stop) - year = *argp++; - else - year = YearFromTime(lorutime); - - double month; - if (maxargs >= 2 && argp < stop) - month = *argp++; - else - month = MonthFromTime(lorutime); - - double day; - if (maxargs >= 1 && argp < stop) - day = *argp++; - else - day = DateFromTime(lorutime); - - day = MakeDay(year, month, day); /* day within year */ - result = MakeDate(day, TimeWithinDay(lorutime)); - - if (local) - result = UTC(result, cx); - - return SetUTCTime(cx, thisObj, TimeClip(result), &args.rval()); -} - -static JSBool -date_setDate(JSContext *cx, unsigned argc, Value *vp) -{ - return date_makeDate(cx, date_setDate, 1, JS_TRUE, argc, vp); + /* Steps 5-6. */ + return SetUTCTime(cx, thisObj, u, &args.rval()); } +/* ES5 15.9.5.37. */ static JSBool date_setUTCDate(JSContext *cx, unsigned argc, Value *vp) { - return date_makeDate(cx, date_setUTCDate, 1, JS_FALSE, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setUTCDate, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + /* Step 1. */ + double t = thisObj->getDateUTCTime().toNumber(); + + /* Step 2. */ + double dt; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &dt)) + return false; + + /* Step 3. */ + double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t)); + + /* Step 4. */ + double v = TimeClip(newDate); + + /* Steps 5-6. */ + return SetUTCTime(cx, thisObj, v, &args.rval()); } +static bool +GetDateOrDefault(JSContext *cx, const CallArgs &args, unsigned i, double t, double *date) +{ + if (args.length() <= i) { + *date = DateFromTime(t); + return true; + } + return ToNumber(cx, args[i], date); +} + +static bool +GetMonthOrDefault(JSContext *cx, const CallArgs &args, unsigned i, double t, double *month) +{ + if (args.length() <= i) { + *month = MonthFromTime(t); + return true; + } + return ToNumber(cx, args[i], month); +} + +/* ES5 15.9.5.38. */ static JSBool date_setMonth(JSContext *cx, unsigned argc, Value *vp) { - return date_makeDate(cx, date_setMonth, 2, JS_TRUE, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setMonth, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + /* Step 1. */ + double t = LocalTime(thisObj->getDateUTCTime().toNumber(), cx); + + /* Step 2. */ + double m; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &m)) + return false; + + /* Step 3. */ + double dt; + if (!GetDateOrDefault(cx, args, 1, t, &dt)) + return false; + + /* Step 4. */ + double newDate = MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t)); + + /* Step 5. */ + double u = TimeClip(UTC(newDate, cx)); + + /* Steps 6-7. */ + return SetUTCTime(cx, thisObj, u, &args.rval()); } +/* ES5 15.9.5.39. */ static JSBool date_setUTCMonth(JSContext *cx, unsigned argc, Value *vp) { - return date_makeDate(cx, date_setUTCMonth, 2, JS_FALSE, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setUTCMonth, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + /* Step 1. */ + double t = thisObj->getDateUTCTime().toNumber(); + + /* Step 2. */ + double m; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &m)) + return false; + + /* Step 3. */ + double dt; + if (!GetDateOrDefault(cx, args, 1, t, &dt)) + return false; + + /* Step 4. */ + double newDate = MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t)); + + /* Step 5. */ + double v = TimeClip(newDate); + + /* Steps 6-7. */ + return SetUTCTime(cx, thisObj, v, &args.rval()); } +static double +ThisLocalTimeOrZero(Handle date, JSContext *cx) +{ + double t = date->getDateUTCTime().toNumber(); + if (MOZ_DOUBLE_IS_NaN(t)) + return +0; + return LocalTime(t, cx); +} + +static double +ThisUTCTimeOrZero(Handle date) +{ + double t = date->getDateUTCTime().toNumber(); + return MOZ_DOUBLE_IS_NaN(t) ? +0 : t; +} + +/* ES5 15.9.5.40. */ static JSBool date_setFullYear(JSContext *cx, unsigned argc, Value *vp) { - return date_makeDate(cx, date_setFullYear, 3, JS_TRUE, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setFullYear, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + /* Step 1. */ + double t = ThisLocalTimeOrZero(thisObj, cx); + + /* Step 2. */ + double y; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &y)) + return false; + + /* Step 3. */ + double m; + if (!GetMonthOrDefault(cx, args, 1, t, &m)) + return false; + + /* Step 4. */ + double dt; + if (!GetDateOrDefault(cx, args, 2, t, &dt)) + return false; + + /* Step 5. */ + double newDate = MakeDate(MakeDay(y, m, dt), TimeWithinDay(t)); + + /* Step 6. */ + double u = TimeClip(UTC(newDate, cx)); + + /* Steps 7-8. */ + return SetUTCTime(cx, thisObj, u, &args.rval()); } +/* ES5 15.9.5.41. */ static JSBool date_setUTCFullYear(JSContext *cx, unsigned argc, Value *vp) { - return date_makeDate(cx, date_setUTCFullYear, 3, JS_FALSE, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_setFullYear, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + /* Step 1. */ + double t = ThisUTCTimeOrZero(thisObj); + + /* Step 2. */ + double y; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &y)) + return false; + + /* Step 3. */ + double m; + if (!GetMonthOrDefault(cx, args, 1, t, &m)) + return false; + + /* Step 4. */ + double dt; + if (!GetDateOrDefault(cx, args, 2, t, &dt)) + return false; + + /* Step 5. */ + double newDate = MakeDate(MakeDay(y, m, dt), TimeWithinDay(t)); + + /* Step 6. */ + double v = TimeClip(newDate); + + /* Steps 7-8. */ + return SetUTCTime(cx, thisObj, v, &args.rval()); } +/* ES5 Annex B.2.5. */ static JSBool date_setYear(JSContext *cx, unsigned argc, Value *vp) { @@ -2239,31 +2367,33 @@ date_setYear(JSContext *cx, unsigned argc, Value *vp) if (!thisObj) return true; - if (args.length() == 0) { - /* Call this only after verifying that obj.[[Class]] = "Date". */ - SetDateToNaN(cx, thisObj, &args.rval()); - return true; - } + /* Step 1. */ + double t = ThisLocalTimeOrZero(thisObj, cx); - double result = thisObj->getDateUTCTime().toNumber(); - - double year; - if (!ToNumber(cx, args[0], &year)) + /* Step 2. */ + double y; + if (!ToNumber(cx, args.length() > 0 ? args[0] : UndefinedValue(), &y)) return false; - if (!MOZ_DOUBLE_IS_FINITE(year)) { + + /* Step 3. */ + if (MOZ_DOUBLE_IS_NaN(y)) { SetDateToNaN(cx, thisObj, &args.rval()); return true; } - year = ToInteger(year); - if (year >= 0 && year <= 99) - year += 1900; - double t = MOZ_DOUBLE_IS_FINITE(result) ? LocalTime(result, cx) : +0.0; - double day = MakeDay(year, MonthFromTime(t), DateFromTime(t)); - result = MakeDate(day, TimeWithinDay(t)); - result = UTC(result, cx); + /* Step 4. */ + double yint = ToInteger(y); + if (0 <= yint && yint <= 99) + yint += 1900; - return SetUTCTime(cx, thisObj, TimeClip(result), &args.rval()); + /* Step 5. */ + double day = MakeDay(yint, MonthFromTime(t), DateFromTime(t)); + + /* Step 6. */ + double u = UTC(MakeDate(day, TimeWithinDay(t)), cx); + + /* Steps 7-8. */ + return SetUTCTime(cx, thisObj, TimeClip(u), &args.rval()); } /* constants for toString, toUTCString */ From 3eac4cef53c36b94590ef0b6421d731404da73ee Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Fri, 6 Jul 2012 15:26:11 -0700 Subject: [PATCH 05/18] Bug 771742 - Refactor the date to-locale-string methods to do their method-guarding right at the start, and never in nested method calls. r=luke --HG-- extra : rebase_source : 736038a0511730eea9cf8c87c720ea1d5e10bd8c --- js/src/jsdate.cpp | 79 +++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index 5c5cc6c6805..f01a2cecf44 100644 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -2679,7 +2679,7 @@ ToLocaleHelper(JSContext *cx, CallReceiver call, JSObject *obj, const char *form PRMJTime split; new_explode(local, &split, cx); - /* let PRMJTime format it. */ + /* Let PRMJTime format it. */ result_len = PRMJ_FormatTime(buf, sizeof buf, format, &split); /* If it failed, default to toString. */ @@ -2711,82 +2711,93 @@ ToLocaleHelper(JSContext *cx, CallReceiver call, JSObject *obj, const char *form return true; } -/* - * NB: Because of NonGenericMethodGuard, the calling native return immediately - * after calling date_toLocaleHelper, even if it returns 'true'. - */ -static JSBool -date_toLocaleHelper(JSContext *cx, unsigned argc, Value *vp, Native native, const char *format) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - JSObject *thisObj; - if (!NonGenericMethodGuard(cx, args, native, &DateClass, &thisObj)) - return false; - if (!thisObj) - return true; - - return ToLocaleHelper(cx, args, thisObj, format); -} - -static JSBool -date_toLocaleStringHelper(JSContext *cx, Native native, unsigned argc, Value *vp) +static bool +ToLocaleStringHelper(JSContext *cx, CallReceiver call, Handle thisObj) { /* * Use '%#c' for windows, because '%c' is backward-compatible and non-y2k * with msvc; '%#c' requests that a full year be used in the result string. */ - return date_toLocaleHelper(cx, argc, vp, native, + return ToLocaleHelper(cx, call, thisObj, #if defined(_WIN32) && !defined(__MWERKS__) - "%#c" + "%#c" #else - "%c" + "%c" #endif - ); + ); } +/* ES5 15.9.5.5. */ static JSBool date_toLocaleString(JSContext *cx, unsigned argc, Value *vp) { - return date_toLocaleStringHelper(cx, date_toLocaleString, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_toLocaleString, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + return ToLocaleStringHelper(cx, args, thisObj); } +/* ES5 15.9.5.6. */ static JSBool date_toLocaleDateString(JSContext *cx, unsigned argc, Value *vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + JSObject *thisObj; + if (!NonGenericMethodGuard(cx, args, date_toLocaleDateString, &DateClass, &thisObj)) + return false; + if (!thisObj) + return true; + /* * Use '%#x' for windows, because '%x' is backward-compatible and non-y2k * with msvc; '%#x' requests that a full year be used in the result string. */ - return date_toLocaleHelper(cx, argc, vp, date_toLocaleDateString, + static const char format[] = #if defined(_WIN32) && !defined(__MWERKS__) "%#x" #else "%x" #endif - ); + ; + + return ToLocaleHelper(cx, args, thisObj, format); } +/* ES5 15.9.5.7. */ static JSBool date_toLocaleTimeString(JSContext *cx, unsigned argc, Value *vp) { - return date_toLocaleHelper(cx, argc, vp, date_toLocaleTimeString, "%X"); + CallArgs args = CallArgsFromVp(argc, vp); + + JSObject *thisObj; + if (!NonGenericMethodGuard(cx, args, date_toLocaleTimeString, &DateClass, &thisObj)) + return false; + if (!thisObj) + return true; + + return ToLocaleHelper(cx, args, thisObj, "%X"); } static JSBool date_toLocaleFormat(JSContext *cx, unsigned argc, Value *vp) { - if (argc == 0) - return date_toLocaleStringHelper(cx, date_toLocaleFormat, argc, vp); - CallArgs args = CallArgsFromVp(argc, vp); - JSObject *thisObj; - if (!NonGenericMethodGuard(cx, args, date_toLocaleFormat, &DateClass, &thisObj)) + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_toLocaleFormat, &DateClass, thisObj.address())) return false; if (!thisObj) return true; + if (argc == 0) + return ToLocaleStringHelper(cx, args, thisObj); + JSString *fmt = ToString(cx, args[0]); if (!fmt) return false; From 5033396fac5678581f0e3fdee94779ec69f40cf0 Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Fri, 6 Jul 2012 15:35:45 -0700 Subject: [PATCH 06/18] Bug 771742 - Refactor the date to-UTC-string methods to not use date_format_utc. r=luke --HG-- extra : rebase_source : 3e56e0bcf6d26aa16e56a7558f21329d9bf48cf7 --- js/src/jsdate.cpp | 54 +++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index f01a2cecf44..b9b6555e0c4 100644 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -2436,12 +2436,14 @@ print_iso_string(char* buf, size_t size, double utctime) msFromTime(utctime)); } +/* ES5 B.2.6. */ static JSBool -date_utc_format(JSContext *cx, Native native, CallArgs args, - void (*printFunc)(char*, size_t, double)) +date_toGMTString(JSContext *cx, unsigned argc, Value *vp) { - JSObject *thisObj; - if (!NonGenericMethodGuard(cx, args, native, &DateClass, &thisObj)) + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_toGMTString, &DateClass, thisObj.address())) return false; if (!thisObj) return true; @@ -2449,16 +2451,10 @@ date_utc_format(JSContext *cx, Native native, CallArgs args, double utctime = thisObj->getDateUTCTime().toNumber(); char buf[100]; - if (!MOZ_DOUBLE_IS_FINITE(utctime)) { - if (printFunc == print_iso_string) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INVALID_DATE); - return false; - } - + if (!MOZ_DOUBLE_IS_FINITE(utctime)) JS_snprintf(buf, sizeof buf, js_NaN_date_str); - } else { - (*printFunc)(buf, sizeof buf, utctime); - } + else + print_gmt_string(buf, sizeof buf, utctime); JSString *str = JS_NewStringCopyZ(cx, buf); if (!str) @@ -2467,16 +2463,34 @@ date_utc_format(JSContext *cx, Native native, CallArgs args, return true; } -static JSBool -date_toGMTString(JSContext *cx, unsigned argc, Value *vp) -{ - return date_utc_format(cx, date_toGMTString, CallArgsFromVp(argc, vp), print_gmt_string); -} - +/* ES5 15.9.5.43. */ static JSBool date_toISOString(JSContext *cx, unsigned argc, Value *vp) { - return date_utc_format(cx, date_toISOString, CallArgsFromVp(argc, vp), print_iso_string); + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted thisObj(cx); + if (!NonGenericMethodGuard(cx, args, date_toISOString, &DateClass, thisObj.address())) + return false; + if (!thisObj) + return true; + + double utctime = thisObj->getDateUTCTime().toNumber(); + + if (!MOZ_DOUBLE_IS_FINITE(utctime)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INVALID_DATE); + return false; + } + + char buf[100]; + print_iso_string(buf, sizeof buf, utctime); + + JSString *str = JS_NewStringCopyZ(cx, buf); + if (!str) + return false; + args.rval().setString(str); + return true; + } /* ES5 15.9.5.44. */ From cd618d4e330c900d74dedbcba6da8316896eb195 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 20 Jun 2012 17:58:55 -0700 Subject: [PATCH 07/18] Bug 761261 - Add JS profiling to SPS (r=luke,ehsan) --HG-- extra : rebase_source : 328a82697aa9a9f63d18c7a30a813f436e163922 --- js/src/Makefile.in | 1 + js/src/builtin/Eval.cpp | 2 - js/src/jsapi-tests/Makefile.in | 1 + js/src/jsapi-tests/testProfileStrings.cpp | 215 ++++++++++++++++++++++ js/src/jscntxt.h | 4 + js/src/jsfriendapi.cpp | 8 + js/src/jsfriendapi.h | 25 +++ js/src/jsgc.cpp | 2 +- js/src/jsgc.h | 3 + js/src/jsinterp.cpp | 19 +- js/src/jsprobes.h | 57 +++--- js/src/jsscript.cpp | 1 + js/src/methodjit/Compiler.cpp | 109 +++++++++-- js/src/methodjit/Compiler.h | 3 + js/src/methodjit/InvokeHelpers.cpp | 12 +- js/src/vm/SPSProfiler.cpp | 172 +++++++++++++++++ js/src/vm/SPSProfiler.h | 141 ++++++++++++++ js/src/vm/Stack-inl.h | 3 + js/src/vm/Stack.cpp | 11 +- js/src/vm/Stack.h | 13 +- js/xpconnect/src/XPCJSRuntime.cpp | 5 + tools/profiler/TableTicker.cpp | 18 +- tools/profiler/sps_sampler.h | 41 ++++- 23 files changed, 791 insertions(+), 75 deletions(-) create mode 100644 js/src/jsapi-tests/testProfileStrings.cpp create mode 100644 js/src/vm/SPSProfiler.cpp create mode 100644 js/src/vm/SPSProfiler.h diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 5d310f7172a..c98937fe9ac 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -126,6 +126,7 @@ CPPSRCS = \ ParseNode.cpp \ Parser.cpp \ SemanticAnalysis.cpp \ + SPSProfiler.cpp \ TokenStream.cpp \ TreeContext.cpp \ TestingFunctions.cpp \ diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp index fb7962970c9..2df23ffe445 100644 --- a/js/src/builtin/Eval.cpp +++ b/js/src/builtin/Eval.cpp @@ -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; diff --git a/js/src/jsapi-tests/Makefile.in b/js/src/jsapi-tests/Makefile.in index 0ceee042f69..d085c4cc377 100644 --- a/js/src/jsapi-tests/Makefile.in +++ b/js/src/jsapi-tests/Makefile.in @@ -63,6 +63,7 @@ CPPSRCS = \ testValueABI.cpp \ testVersion.cpp \ testXDR.cpp \ + testProfileStrings.cpp \ $(NULL) CSRCS = \ diff --git a/js/src/jsapi-tests/testProfileStrings.cpp b/js/src/jsapi-tests/testProfileStrings.cpp new file mode 100644 index 00000000000..387a7d1d1d0 --- /dev/null +++ b/js/src/jsapi-tests/testProfileStrings.cpp @@ -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) diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 57d20f82b61..c8cbc695341 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -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; diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index 7c4e625c922..fa2096208ce 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -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 diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 6b9c48159ea..7dcb5f5dc01 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -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); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 19063d0226a..59a4ccf0147 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -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()) { diff --git a/js/src/jsgc.h b/js/src/jsgc.h index ae20b85eb71..0cb842505c5 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -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); diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index e2dc99c60ca..e3d9a098562 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -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 diff --git a/js/src/jsprobes.h b/js/src/jsprobes.h index a5bf8ca2da4..850d4b1f076 100644 --- a/js/src/jsprobes.h +++ b/js/src/jsprobes.h @@ -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 */ /* diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 12183c71ecf..8b5d14828da 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -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) diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index 3cd3b8e7504..10fd70a392b 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -1180,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 @@ -3746,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); @@ -3767,6 +3761,14 @@ mjit::Compiler::emitReturn(FrameEntry *fe) } if (a != outer) { + JS_ASSERT(!debugMode()); + if (Probes::callTrackingActive(cx)) { + prepareStubCall(Uses(0)); + INLINE_STUBCALL(stubs::ScriptProbeOnlyEpilogue, REJOIN_RESUME); + } else { + profilingPopHelper(); + } + /* * Returning from an inlined script. The checks we do for inlineability * and recompilation triggered by args object construction ensure that @@ -3806,8 +3808,17 @@ 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 { + if (Probes::callTrackingActive(cx)) { + prepareStubCall(Uses(0)); + INLINE_STUBCALL(stubs::ScriptProbeOnlyEpilogue, REJOIN_RESUME); + } else { + profilingPopHelper(); + } + + if (script->function() && script->nesting()) { + masm.sub32(Imm32(1), AbsoluteAddress(&script->nesting()->activeFrames)); + } } emitReturnValue(&masm, fe); @@ -3899,6 +3910,73 @@ 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 (!cx->runtime->spsProfiler.enabled()) + return; + masm.sub32(Imm32(1), AbsoluteAddress(cx->runtime->spsProfiler.size())); +} + void mjit::Compiler::addReturnSite() { @@ -4462,7 +4540,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) { diff --git a/js/src/methodjit/Compiler.h b/js/src/methodjit/Compiler.h index 10ab4c91270..7cc13162baa 100644 --- a/js/src/methodjit/Compiler.h +++ b/js/src/methodjit/Compiler.h @@ -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, diff --git a/js/src/methodjit/InvokeHelpers.cpp b/js/src/methodjit/InvokeHelpers.cpp index 74b9e282055..f5e37ef7b2a 100644 --- a/js/src/methodjit/InvokeHelpers.cpp +++ b/js/src/methodjit/InvokeHelpers.cpp @@ -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: diff --git a/js/src/vm/SPSProfiler.cpp b/js/src/vm/SPSProfiler.cpp new file mode 100644 index 00000000000..16a521971ad --- /dev/null +++ b/js/src/vm/SPSProfiler.cpp @@ -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 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("")) { + 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); +} diff --git a/js/src/vm/SPSProfiler.h b/js/src/vm/SPSProfiler.h new file mode 100644 index 00000000000..7a3f0ae15d1 --- /dev/null +++ b/js/src/vm/SPSProfiler.h @@ -0,0 +1,141 @@ +/* -*- 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 + +#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, SystemAllocPolicy> + ProfileStringMap; + +class SPSEntryMarker; + +class SPSProfiler +{ + friend class SPSEntryMarker; + + ProfileStringMap strings; + ProfileEntry *stack_; + uint32_t *size_; + uint32_t max_; + + static const char *allocProfileString(JSContext *cx, JSScript *script, + JSFunction *function); + public: + SPSProfiler() : stack_(NULL), size_(NULL), max_(0) {} + ~SPSProfiler(); + + uint32_t *size() { return size_; } + uint32_t maxSize() { return max_; } + ProfileEntry *stack() { return stack_; } + + bool enabled() { return stack_ != NULL; } + 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 size_before; + + public: + SPSEntryMarker(JSRuntime *rt); + ~SPSEntryMarker(); +}; + +} /* namespace js */ + +#endif /* SPSProfiler_h__ */ diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index 4ad61c13c28..09292d20b04 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -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 diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 8e163c86ada..619ece1f947 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -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); diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index e6f7518fd94..a918c169acb 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -266,7 +266,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 +804,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 { diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index a1ff351a0b2..41c19fa2870 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -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); diff --git a/tools/profiler/TableTicker.cpp b/tools/profiler/TableTicker.cpp index 76e55ff4ee4..f3725c54ce0 100644 --- a/tools/profiler/TableTicker.cpp +++ b/tools/profiler/TableTicker.cpp @@ -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() diff --git a/tools/profiler/sps_sampler.h b/tools/profiler/sps_sampler.h index cc2e14bbd16..b9eb09ca9a7 100644 --- a/tools/profiler/sps_sampler.h +++ b/tools/profiler/sps_sampler.h @@ -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 From 13a898fcad6b9daf5b2eca96359c3cab8d59e863 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 5 Jul 2012 10:43:50 -0700 Subject: [PATCH 08/18] Bug 770663 - Add a JS shell function to turn on a static profiling stack (r=luke) --HG-- extra : rebase_source : 7e7ad51d73d5a517ef6bb9d62255e673944b4330 --- js/src/methodjit/Compiler.cpp | 25 ++++++++++--------------- js/src/shell/js.cpp | 28 ++++++++++++++++++++++++++++ js/src/vm/SPSProfiler.h | 5 ++++- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index 10fd70a392b..e33f6d28669 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -3762,12 +3762,7 @@ mjit::Compiler::emitReturn(FrameEntry *fe) if (a != outer) { JS_ASSERT(!debugMode()); - if (Probes::callTrackingActive(cx)) { - prepareStubCall(Uses(0)); - INLINE_STUBCALL(stubs::ScriptProbeOnlyEpilogue, REJOIN_RESUME); - } else { - profilingPopHelper(); - } + profilingPopHelper(); /* * Returning from an inlined script. The checks we do for inlineability @@ -3809,12 +3804,7 @@ mjit::Compiler::emitReturn(FrameEntry *fe) prepareStubCall(Uses(0)); INLINE_STUBCALL(stubs::Epilogue, REJOIN_NONE); } else { - if (Probes::callTrackingActive(cx)) { - prepareStubCall(Uses(0)); - INLINE_STUBCALL(stubs::ScriptProbeOnlyEpilogue, REJOIN_RESUME); - } else { - profilingPopHelper(); - } + profilingPopHelper(); if (script->function() && script->nesting()) { masm.sub32(Imm32(1), AbsoluteAddress(&script->nesting()->activeFrames)); @@ -3972,9 +3962,14 @@ mjit::Compiler::profilingPushHelper() void mjit::Compiler::profilingPopHelper() { - if (!cx->runtime->spsProfiler.enabled()) - return; - masm.sub32(Imm32(1), AbsoluteAddress(cx->runtime->spsProfiler.size())); + 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 diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 8bdb810c066..bd656e00fec 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -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 diff --git a/js/src/vm/SPSProfiler.h b/js/src/vm/SPSProfiler.h index 7a3f0ae15d1..e53e84b3aee 100644 --- a/js/src/vm/SPSProfiler.h +++ b/js/src/vm/SPSProfiler.h @@ -97,11 +97,12 @@ class SPSProfiler 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) {} + SPSProfiler() : stack_(NULL), size_(NULL), max_(0), slowAssertions(false) {} ~SPSProfiler(); uint32_t *size() { return size_; } @@ -109,6 +110,8 @@ class SPSProfiler 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); From a7b1dd068cf7a39f79dc1a3f41e9b6ea6080f832 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Sat, 7 Jul 2012 21:26:14 -0700 Subject: [PATCH 09/18] Remove unnecessary decls (mostly to trigger a clobbering rebuild) (no bug, r=me) --- js/src/vm/Stack.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index a918c169acb..e63ca4aeead 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -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; -} - /*****************************************************************************/ /* From 22266587622ec914c62ec17ae29d57b1a1c7e7e6 Mon Sep 17 00:00:00 2001 From: Mihai Sucan Date: Sat, 7 Jul 2012 13:21:04 +0300 Subject: [PATCH 10/18] Bug 770993 - ConsoleAPI.js consumes excessive amounts of memory; r=dbaron,rcampbell --- dom/base/ConsoleAPI.js | 172 +++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 94 deletions(-) diff --git a/dom/base/ConsoleAPI.js b/dom/base/ConsoleAPI.js index 2f0faad4f46..a816f3212e9 100644 --- a/dom/base/ConsoleAPI.js +++ b/dom/base/ConsoleAPI.js @@ -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 }; } }; From b91a4250d71d7354c75b804b40a42df0854221a0 Mon Sep 17 00:00:00 2001 From: "L. David Baron" Date: Sat, 7 Jul 2012 21:41:27 -0700 Subject: [PATCH 11/18] Change our interpretation of resolution units in CSS to match updates to the spec: i.e., device pixels per CSS inch (instead of device pixels per physical inch). (Bug 771390) r=bzbarsky Given that this makes GetResolution work like GetDevicePixelRatio, I believe this should also fix bug 662061 (on resolution's behavior when zooming)? --- layout/style/nsMediaFeatures.cpp | 7 ++++--- layout/style/test/test_media_queries.html | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp index a814f30a42a..1fc94d689ad 100644 --- a/layout/style/nsMediaFeatures.cpp +++ b/layout/style/nsMediaFeatures.cpp @@ -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; } diff --git a/layout/style/test/test_media_queries.html b/layout/style/test/test_media_queries.html index 074e323d7d7..dbbbf10eb15 100644 --- a/layout/style/test/test_media_queries.html +++ b/layout/style/test/test_media_queries.html @@ -483,6 +483,7 @@ 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_not_apply("(resolution: " + (resolution + 1) + "dpi)"); should_not_apply("(resolution: " + (resolution - 1) + "dpi)"); @@ -490,6 +491,7 @@ function run() { } 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; From 16924486266e7ff710f1eae035f5d7d5a71f492e Mon Sep 17 00:00:00 2001 From: "L. David Baron" Date: Sat, 7 Jul 2012 21:41:27 -0700 Subject: [PATCH 12/18] Implement dppx units [css3-images] for resolution media query. (Bug 741644) r=bzbarsky --- layout/style/nsCSSParser.cpp | 2 ++ layout/style/nsCSSStyleSheet.cpp | 14 ++++++++++++-- layout/style/nsMediaFeatures.h | 3 ++- layout/style/test/test_media_queries.html | 5 +++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 812afffb49c..ac1d1f21e47 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -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 { diff --git a/layout/style/nsCSSStyleSheet.cpp b/layout/style/nsCSSStyleSheet.cpp index 5d8c50919f7..743e765dbdd 100644 --- a/layout/style/nsCSSStyleSheet.cpp +++ b/layout/style/nsCSSStyleSheet.cpp @@ -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"); diff --git a/layout/style/nsMediaFeatures.h b/layout/style/nsMediaFeatures.h index 1f9d230ad65..d00ebc95bac 100644 --- a/layout/style/nsMediaFeatures.h +++ b/layout/style/nsMediaFeatures.h @@ -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 diff --git a/layout/style/test/test_media_queries.html b/layout/style/test/test_media_queries.html index dbbbf10eb15..55a6b8b7cb8 100644 --- a/layout/style/test/test_media_queries.html +++ b/layout/style/test/test_media_queries.html @@ -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 @@ -485,6 +489,7 @@ function run() { // 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; From 22e6bbc5780ba89579c52c55f445449a774cc407 Mon Sep 17 00:00:00 2001 From: "L. David Baron" Date: Sat, 7 Jul 2012 21:41:27 -0700 Subject: [PATCH 13/18] Add flush before we find our pres context, to fix media queries tests on ringmark. (Bug 753777) r=bzbarsky --- dom/base/nsGlobalWindow.cpp | 8 ++++++++ layout/style/test/test_media_query_list.html | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index d2c4534cd74..35aae73ea91 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -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(GetPrivateParent()); + if (parent) { + parent->FlushPendingNotifications(Flush_Frames); + } + if (!mDocShell) return NS_OK; diff --git a/layout/style/test/test_media_query_list.html b/layout/style/test/test_media_query_list.html index 434cc46dd89..b5672050faf 100644 --- a/layout/style/test/test_media_query_list.html +++ b/layout/style/test/test_media_query_list.html @@ -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() { From 7328eb27bfed8abeffc48d103bc43f6227778033 Mon Sep 17 00:00:00 2001 From: Jonathan Watt Date: Sun, 8 Jul 2012 08:29:57 +0200 Subject: [PATCH 14/18] Bug 771795 - Stop over-invalidating SVG. r=heycam. --- layout/svg/base/src/nsSVGContainerFrame.cpp | 14 ++++++++++---- layout/svg/base/src/nsSVGForeignObjectFrame.cpp | 14 ++++++++++---- layout/svg/base/src/nsSVGGlyphFrame.cpp | 14 ++++++++++---- layout/svg/base/src/nsSVGImageFrame.cpp | 14 ++++++++++---- layout/svg/base/src/nsSVGPathGeometryFrame.cpp | 17 +++++++++++------ layout/svg/base/src/nsSVGSwitchFrame.cpp | 14 ++++++++++---- layout/svg/base/src/nsSVGTextFrame.cpp | 14 ++++++++++---- 7 files changed, 71 insertions(+), 30 deletions(-) diff --git a/layout/svg/base/src/nsSVGContainerFrame.cpp b/layout/svg/base/src/nsSVGContainerFrame.cpp index 46b898b95bf..5bcf3aa8640 100644 --- a/layout/svg/base/src/nsSVGContainerFrame.cpp +++ b/layout/svg/base/src/nsSVGContainerFrame.cpp @@ -271,6 +271,15 @@ nsSVGDisplayContainerFrame::UpdateBounds() nsSVGEffects::UpdateEffects(this); } + // We only invalidate if we are dirty, if our outer- 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- 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); } diff --git a/layout/svg/base/src/nsSVGForeignObjectFrame.cpp b/layout/svg/base/src/nsSVGForeignObjectFrame.cpp index 2e71816c512..7f452fada14 100644 --- a/layout/svg/base/src/nsSVGForeignObjectFrame.cpp +++ b/layout/svg/base/src/nsSVGForeignObjectFrame.cpp @@ -381,6 +381,15 @@ nsSVGForeignObjectFrame::UpdateBounds() nsSVGEffects::UpdateEffects(this); } + // We only invalidate if we are dirty, if our outer- 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- 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); } diff --git a/layout/svg/base/src/nsSVGGlyphFrame.cpp b/layout/svg/base/src/nsSVGGlyphFrame.cpp index f767028942a..c8f34db69d3 100644 --- a/layout/svg/base/src/nsSVGGlyphFrame.cpp +++ b/layout/svg/base/src/nsSVGGlyphFrame.cpp @@ -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- 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- 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); } diff --git a/layout/svg/base/src/nsSVGImageFrame.cpp b/layout/svg/base/src/nsSVGImageFrame.cpp index 2b54eca01ca..161234584d9 100644 --- a/layout/svg/base/src/nsSVGImageFrame.cpp +++ b/layout/svg/base/src/nsSVGImageFrame.cpp @@ -496,6 +496,15 @@ nsSVGImageFrame::UpdateBounds() nsSVGEffects::UpdateEffects(this); } + // We only invalidate if we are dirty, if our outer- 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- 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); } diff --git a/layout/svg/base/src/nsSVGPathGeometryFrame.cpp b/layout/svg/base/src/nsSVGPathGeometryFrame.cpp index a95095456a8..4e83f1e1a9a 100644 --- a/layout/svg/base/src/nsSVGPathGeometryFrame.cpp +++ b/layout/svg/base/src/nsSVGPathGeometryFrame.cpp @@ -239,6 +239,15 @@ nsSVGPathGeometryFrame::UpdateBounds() nsSVGEffects::UpdateEffects(this); } + // We only invalidate if we are dirty, if our outer- 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- 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); } } diff --git a/layout/svg/base/src/nsSVGSwitchFrame.cpp b/layout/svg/base/src/nsSVGSwitchFrame.cpp index af7f7c579b1..e2364cd77a8 100644 --- a/layout/svg/base/src/nsSVGSwitchFrame.cpp +++ b/layout/svg/base/src/nsSVGSwitchFrame.cpp @@ -184,6 +184,15 @@ nsSVGSwitchFrame::UpdateBounds() nsSVGEffects::UpdateEffects(this); } + // We only invalidate if we are dirty, if our outer- 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- 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); } diff --git a/layout/svg/base/src/nsSVGTextFrame.cpp b/layout/svg/base/src/nsSVGTextFrame.cpp index 832cee2a2be..7b718b3cca6 100644 --- a/layout/svg/base/src/nsSVGTextFrame.cpp +++ b/layout/svg/base/src/nsSVGTextFrame.cpp @@ -231,14 +231,20 @@ nsSVGTextFrame::UpdateBounds() UpdateGlyphPositioning(false); + // We only invalidate if we are dirty, if our outer- 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- 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); } From c437bc6e9242f1132cd624ff7a90d081c9e7298a Mon Sep 17 00:00:00 2001 From: Cameron McCormack Date: Sun, 8 Jul 2012 21:36:07 +1000 Subject: [PATCH 15/18] Bug 769115 - Calculate marker orientation correctly for arc endpoints. r=jwatt --- content/svg/content/src/SVGPathData.cpp | 7 +- .../svg/marker-orientation-01-ref.svg | 68 +++++++++++++++++++ layout/reftests/svg/marker-orientation-01.svg | 63 +++++++++++++++++ layout/reftests/svg/reftest.list | 1 + 4 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 layout/reftests/svg/marker-orientation-01-ref.svg create mode 100644 layout/reftests/svg/marker-orientation-01.svg diff --git a/content/svg/content/src/SVGPathData.cpp b/content/svg/content/src/SVGPathData.cpp index 96ac049b1ba..3065b3391ba 100644 --- a/content/svg/content/src/SVGPathData.cpp +++ b/content/svg/content/src/SVGPathData.cpp @@ -699,10 +699,9 @@ SVGPathData::GetMarkerPositioningData(nsTArray *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) diff --git a/layout/reftests/svg/marker-orientation-01-ref.svg b/layout/reftests/svg/marker-orientation-01-ref.svg new file mode 100644 index 00000000000..00cc9a3a4ab --- /dev/null +++ b/layout/reftests/svg/marker-orientation-01-ref.svg @@ -0,0 +1,68 @@ + + + Reference for test that marker orientation is correct at the end of arcs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/layout/reftests/svg/marker-orientation-01.svg b/layout/reftests/svg/marker-orientation-01.svg new file mode 100644 index 00000000000..350c43d7b4a --- /dev/null +++ b/layout/reftests/svg/marker-orientation-01.svg @@ -0,0 +1,63 @@ + + + Test that marker orientation is correct at the end of arcs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/layout/reftests/svg/reftest.list b/layout/reftests/svg/reftest.list index 8985aeff91a..22269c8cb11 100644 --- a/layout/reftests/svg/reftest.list +++ b/layout/reftests/svg/reftest.list @@ -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 From b91cca4ef624e50a95452fac0e159d41c5b9d8ae Mon Sep 17 00:00:00 2001 From: Randell Jesup Date: Sun, 8 Jul 2012 08:35:05 -0400 Subject: [PATCH 16/18] Bug 771248: Flip webrtc build pref to on r=roc --- configure.in | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/configure.in b/configure.in index 3d6fbf6da64..da83c8486df 100644 --- a/configure.in +++ b/configure.in @@ -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) From 75efbb9ea09bf00a39e490848d2fa1fa349838f2 Mon Sep 17 00:00:00 2001 From: Randell Jesup Date: Sun, 8 Jul 2012 08:39:03 -0400 Subject: [PATCH 17/18] Bug 771588: don't PGO asm_enc_offsets.c in libvpx - fix for MSVC2010 r=bsmedberg --- media/libvpx/Makefile.in | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/media/libvpx/Makefile.in b/media/libvpx/Makefile.in index 1d4af9d0443..bc11fd831e2 100644 --- a/media/libvpx/Makefile.in +++ b/media/libvpx/Makefile.in @@ -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)) > $@ From 0b6b626d97814fddc3ef18493d3d9f5c888b26b2 Mon Sep 17 00:00:00 2001 From: Aryeh Gregor Date: Thu, 5 Jul 2012 10:45:08 +0300 Subject: [PATCH 18/18] Bug 767169 part 3 - Use script runner for nsHTMLEditor::ResetRootElementAndEventTarget; r=ehsan,bz --- content/base/src/nsContentIterator.cpp | 7 ++++--- editor/libeditor/html/nsHTMLEditor.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/content/base/src/nsContentIterator.cpp b/content/base/src/nsContentIterator.cpp index a4209a3d98e..eb829c8c8fa 100644 --- a/content/base/src/nsContentIterator.cpp +++ b/content/base/src/nsContentIterator.cpp @@ -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) { diff --git a/editor/libeditor/html/nsHTMLEditor.cpp b/editor/libeditor/html/nsHTMLEditor.cpp index 73dc01b1011..529c67548e4 100644 --- a/editor/libeditor/html/nsHTMLEditor.cpp +++ b/editor/libeditor/html/nsHTMLEditor.cpp @@ -3270,7 +3270,8 @@ nsHTMLEditor::ContentInserted(nsIDocument *aDocument, nsIContent* aContainer, nsCOMPtr 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 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())) {