Bug 631951 - Shrink methodjit memory usage by interpreting a few times before compiling (r=dvander)
authorBill McCloskey <wmccloskey@mozilla.com>
Fri, 11 Feb 2011 16:31:32 -0800
changeset 62574 f569d49576bb7271dd6ae7abb7f6525d0e28c712
parent 62573 bf89669b34cba01684dbe6f5b135aeead6c428c7
child 62575 097da81cf4232c4a946e47998650c0e4c22ab623
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdvander
bugs631951
milestone2.0b12pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 631951 - Shrink methodjit memory usage by interpreting a few times before compiling (r=dvander)
build/automation.py.in
dom/base/nsJSEnvironment.cpp
js/src/jit-test/jit_test.py
js/src/jit-test/tests/jaeger/bug563000/eif-call-newvar.js
js/src/jit-test/tests/jaeger/bug563000/eif-call-typechange.js
js/src/jit-test/tests/jaeger/bug563000/eif-call.js
js/src/jit-test/tests/jaeger/bug563000/eif-getter-newvar.js
js/src/jit-test/tests/jaeger/bug563000/eif-getter-typechange.js
js/src/jit-test/tests/jaeger/bug563000/eif-getter.js
js/src/jit-test/tests/jaeger/bug563000/eif-trap-newvar.js
js/src/jit-test/tests/jaeger/bug563000/eif-trap-typechange.js
js/src/jit-test/tests/jaeger/bug563000/eif-trap.js
js/src/jit-test/tests/jaeger/testForOps.js
js/src/jsapi.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsinterp.cpp
js/src/jsscript.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/InvokeHelpers.cpp
js/src/methodjit/MethodJIT-inl.h
js/src/methodjit/MethodJIT.h
js/src/methodjit/MonoIC.cpp
js/src/methodjit/StubCalls.h
js/src/shell/js.cpp
js/src/tests/shell.js
js/src/tests/user.js
modules/libpref/src/init/all.js
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -349,16 +349,17 @@ user_pref("browser.warnOnQuit", false);
 user_pref("accessibility.typeaheadfind.autostart", false);
 user_pref("javascript.options.showInConsole", true);
 user_pref("devtools.errorconsole.enabled", true);
 user_pref("layout.debug.enable_data_xbl", true);
 user_pref("browser.EULA.override", true);
 user_pref("javascript.options.tracejit.content", true);
 user_pref("javascript.options.methodjit.content", true);
 user_pref("javascript.options.jitprofiling.content", true);
+user_pref("javascript.options.methodjit_always", false);
 user_pref("gfx.color_management.force_srgb", true);
 user_pref("network.manage-offline-status", false);
 user_pref("test.mousescroll", true);
 user_pref("security.default_personal_cert", "Select Automatically"); // Need to client auth test be w/o any dialogs
 user_pref("network.http.prompt-temp-redirect", false);
 user_pref("media.cache_size", 100);
 user_pref("security.warn_viewing_mixed", false);
 user_pref("app.update.enabled", false);
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1010,20 +1010,21 @@ static const char js_strict_debug_option
 #endif
 static const char js_werror_option_str[] = JS_OPTIONS_DOT_STR "werror";
 static const char js_relimit_option_str[]= JS_OPTIONS_DOT_STR "relimit";
 #ifdef JS_GC_ZEAL
 static const char js_zeal_option_str[]   = JS_OPTIONS_DOT_STR "gczeal";
 #endif
 static const char js_tracejit_content_str[]   = JS_OPTIONS_DOT_STR "tracejit.content";
 static const char js_tracejit_chrome_str[]    = JS_OPTIONS_DOT_STR "tracejit.chrome";
-static const char js_methodjit_content_str[]   = JS_OPTIONS_DOT_STR "methodjit.content";
-static const char js_methodjit_chrome_str[]    = JS_OPTIONS_DOT_STR "methodjit.chrome";
-static const char js_profiling_content_str[]   = JS_OPTIONS_DOT_STR "jitprofiling.content";
-static const char js_profiling_chrome_str[]    = JS_OPTIONS_DOT_STR "jitprofiling.chrome";
+static const char js_methodjit_content_str[]  = JS_OPTIONS_DOT_STR "methodjit.content";
+static const char js_methodjit_chrome_str[]   = JS_OPTIONS_DOT_STR "methodjit.chrome";
+static const char js_profiling_content_str[]  = JS_OPTIONS_DOT_STR "jitprofiling.content";
+static const char js_profiling_chrome_str[]   = JS_OPTIONS_DOT_STR "jitprofiling.chrome";
+static const char js_methodjit_always_str[]   = JS_OPTIONS_DOT_STR "methodjit_always";
 
 int
 nsJSContext::JSOptionChangedCallback(const char *pref, void *data)
 {
   nsJSContext *context = reinterpret_cast<nsJSContext *>(data);
   PRUint32 oldDefaultJSOptions = context->mDefaultJSOptions;
   PRUint32 newDefaultJSOptions = oldDefaultJSOptions;
 
@@ -1042,24 +1043,26 @@ nsJSContext::JSOptionChangedCallback(con
                                                    js_tracejit_chrome_str :
                                                    js_tracejit_content_str);
   PRBool useMethodJIT = nsContentUtils::GetBoolPref(chromeWindow ?
                                                     js_methodjit_chrome_str :
                                                     js_methodjit_content_str);
   PRBool useProfiling = nsContentUtils::GetBoolPref(chromeWindow ?
                                                     js_profiling_chrome_str :
                                                     js_profiling_content_str);
+  PRBool useMethodJITAlways = nsContentUtils::GetBoolPref(js_methodjit_always_str);
   nsCOMPtr<nsIXULRuntime> xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
   if (xr) {
     PRBool safeMode = PR_FALSE;
     xr->GetInSafeMode(&safeMode);
     if (safeMode) {
       useTraceJIT = PR_FALSE;
       useMethodJIT = PR_FALSE;
       useProfiling = PR_FALSE;
+      useMethodJITAlways = PR_TRUE;
     }
   }    
 
   if (useTraceJIT)
     newDefaultJSOptions |= JSOPTION_JIT;
   else
     newDefaultJSOptions &= ~JSOPTION_JIT;
 
@@ -1068,16 +1071,21 @@ nsJSContext::JSOptionChangedCallback(con
   else
     newDefaultJSOptions &= ~JSOPTION_METHODJIT;
 
   if (useProfiling)
     newDefaultJSOptions |= JSOPTION_PROFILING;
   else
     newDefaultJSOptions &= ~JSOPTION_PROFILING;
 
+  if (useMethodJITAlways)
+    newDefaultJSOptions |= JSOPTION_METHODJIT_ALWAYS;
+  else
+    newDefaultJSOptions &= ~JSOPTION_METHODJIT_ALWAYS;
+
 #ifdef DEBUG
   // In debug builds, warnings are enabled in chrome context if
   // javascript.options.strict.debug is true
   PRBool strictDebug = nsContentUtils::GetBoolPref(js_strict_debug_option_str);
   // Note this callback is also called from context's InitClasses thus we don't
   // need to enable this directly from InitContext
   if (strictDebug && (newDefaultJSOptions & JSOPTION_STRICT) == 0) {
     if (chromeWindow)
--- a/js/src/jit-test/jit_test.py
+++ b/js/src/jit-test/jit_test.py
@@ -88,16 +88,18 @@ class Test:
                         print('warning: unrecognized |jit-test| attribute %s'%part)
                 else:
                     if name == 'slow':
                         test.slow = True
                     elif name == 'allow-oom':
                         test.allow_oom = True
                     elif name == 'valgrind':
                         test.valgrind = options.valgrind
+                    elif name == 'mjitalways':
+                        test.jitflags.append('-a')
                     else:
                         print('warning: unrecognized |jit-test| attribute %s'%part)
 
         if options.valgrind_all:
             test.valgrind = True
 
         return test
 
@@ -421,17 +423,17 @@ def main(argv):
         test_list = [ _ for _ in test_list if not _.slow ]
 
     # The full test list is ready. Now create copies for each JIT configuration.
     job_list = []
     jitflags_list = parse_jitflags()
     for test in test_list:
         for jitflags in jitflags_list:
             new_test = test.copy()
-            new_test.jitflags = jitflags
+            new_test.jitflags.extend(jitflags)
             job_list.append(new_test)
     
 
     if OPTIONS.debug:
         if len(job_list) > 1:
             print('Multiple tests match command line arguments, debugger can only run one')
             for tc in job_list:
                 print('    %s'%tc.path)
--- a/js/src/jit-test/tests/jaeger/bug563000/eif-call-newvar.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/eif-call-newvar.js
@@ -1,8 +1,9 @@
+// |jit-test| mjitalways
 setDebug(true);
 
 function callee() {
   assertJit();
   evalInFrame(1, "var x = 'success'");
 }
 function caller() {
   assertJit();
--- a/js/src/jit-test/tests/jaeger/bug563000/eif-call-typechange.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/eif-call-typechange.js
@@ -1,8 +1,9 @@
+// |jit-test| mjitalways
 setDebug(true);
 
 function callee() {
   assertJit();
   evalInFrame(1, "x = 'success'");
 }
 function caller() {
   assertJit();
--- a/js/src/jit-test/tests/jaeger/bug563000/eif-call.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/eif-call.js
@@ -1,8 +1,9 @@
+// |jit-test| mjitalways
 setDebug(true);
 
 function callee() {
   assertJit();
   evalInFrame(1, "x = 'success'");
 }
 function caller() {
   assertJit();
--- a/js/src/jit-test/tests/jaeger/bug563000/eif-getter-newvar.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/eif-getter-newvar.js
@@ -1,8 +1,9 @@
+// |jit-test| mjitalways
 setDebug(true);
 
 this.__defineGetter__("someProperty", function () { evalInFrame(1, "var x = 'success'"); });
 function caller(obj) {
   assertJit();
   obj.someProperty;
   return x;
 }
--- a/js/src/jit-test/tests/jaeger/bug563000/eif-getter-typechange.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/eif-getter-typechange.js
@@ -1,8 +1,9 @@
+// |jit-test| mjitalways
 setDebug(true);
 
 this.__defineGetter__("someProperty", function () { evalInFrame(1, "var x = 'success'"); });
 function caller(obj) {
   assertJit();
   var x = ({ dana : 'zuul' });
   obj.someProperty;
   return x;
--- a/js/src/jit-test/tests/jaeger/bug563000/eif-getter.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/eif-getter.js
@@ -1,8 +1,9 @@
+// |jit-test| mjitalways
 setDebug(true);
 
 this.__defineGetter__("someProperty", function () { evalInFrame(1, "x = 'success'"); });
 function caller(obj) {
   assertJit();
   var x = "failure";
   obj.someProperty;
   return x;
--- a/js/src/jit-test/tests/jaeger/bug563000/eif-trap-newvar.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/eif-trap-newvar.js
@@ -1,8 +1,9 @@
+// |jit-test| mjitalways
 setDebug(true);
 
 function nop(){}
 function caller(obj) {
   assertJit();
   return x;
 }
 trap(caller, 7, "var x = 'success'; nop()");
--- a/js/src/jit-test/tests/jaeger/bug563000/eif-trap-typechange.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/eif-trap-typechange.js
@@ -1,8 +1,9 @@
+// |jit-test| mjitalways
 setDebug(true);
 
 function nop(){}
 function caller(obj) {
   assertJit();
   var x = ({ dana : "zuul" });
   return x;
 }
--- a/js/src/jit-test/tests/jaeger/bug563000/eif-trap.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/eif-trap.js
@@ -1,8 +1,9 @@
+// |jit-test| mjitalways
 setDebug(true);
 
 function nop(){}
 function caller(obj) {
   assertJit();
   var x = "failure";
   return x;
 }
--- a/js/src/jit-test/tests/jaeger/testForOps.js
+++ b/js/src/jit-test/tests/jaeger/testForOps.js
@@ -1,8 +1,9 @@
+// |jit-test| mjitalways
 // vim: set ts=4 sw=4 tw=99 et:
 
 function assertObjectsEqual(obj1, obj2) {
     assertEq(obj1.a, obj2.a);
     assertEq(obj1.b, obj2.b);
     assertEq(obj1.c, obj2.c);
     assertEq(obj1.d, obj2.d);
     assertEq(obj2.a, 1);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -950,21 +950,24 @@ JS_StringToVersion(const char *string);
 #define JSOPTION_UNROOTED_GLOBAL JS_BIT(13)     /* The GC will not root the
                                                    contexts' global objects
                                                    (see JS_GetGlobalObject),
                                                    leaving that up to the
                                                    embedding. */
 
 #define JSOPTION_METHODJIT      JS_BIT(14)      /* Whole-method JIT. */
 #define JSOPTION_PROFILING      JS_BIT(15)      /* Profiler to make tracer/methodjit choices. */
+#define JSOPTION_METHODJIT_ALWAYS \
+                                JS_BIT(16)      /* Always whole-method JIT,
+                                                   don't tune at run-time. */
 
 /* Options which reflect compile-time properties of scripts. */
 #define JSCOMPILEOPTION_MASK    (JSOPTION_XML | JSOPTION_ANONFUNFIX)
 
-#define JSRUNOPTION_MASK        (JS_BITMASK(16) & ~JSCOMPILEOPTION_MASK)
+#define JSRUNOPTION_MASK        (JS_BITMASK(17) & ~JSCOMPILEOPTION_MASK)
 #define JSALLOPTION_MASK        (JSCOMPILEOPTION_MASK | JSRUNOPTION_MASK)
 
 extern JS_PUBLIC_API(uint32)
 JS_GetOptions(JSContext *cx);
 
 extern JS_PUBLIC_API(uint32)
 JS_SetOptions(JSContext *cx, uint32 options);
 
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -149,20 +149,22 @@ JSCompartment::init()
         return false;
 
 #if ENABLE_YARR_JIT
     regExpAllocator = JSC::ExecutableAllocator::create();
     if (!regExpAllocator)
         return false;
 #endif
 
+    if (!backEdgeTable.init())
+        return false;
+
 #ifdef JS_METHODJIT
-    if (!(jaegerCompartment = js_new<mjit::JaegerCompartment>())) {
+    if (!(jaegerCompartment = js_new<mjit::JaegerCompartment>()))
         return false;
-    }
     return jaegerCompartment->Initialize();
 #else
     return true;
 #endif
 }
 
 bool
 JSCompartment::arenaListsAreEmpty()
@@ -568,8 +570,30 @@ MathCache *
 JSCompartment::allocMathCache(JSContext *cx)
 {
     JS_ASSERT(!mathCache);
     mathCache = js_new<MathCache>();
     if (!mathCache)
         js_ReportOutOfMemory(cx);
     return mathCache;
 }
+
+size_t
+JSCompartment::backEdgeCount(jsbytecode *pc) const
+{
+    if (BackEdgeMap::Ptr p = backEdgeTable.lookup(pc))
+        return p->value;
+
+    return 0;
+}
+
+size_t
+JSCompartment::incBackEdgeCount(jsbytecode *pc)
+{
+    if (BackEdgeMap::AddPtr p = backEdgeTable.lookupForAdd(pc)) {
+        p->value++;
+        return p->value;
+    } else {
+        backEdgeTable.add(p, pc, 1);
+        return 1;
+    }
+}
+
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -477,23 +477,33 @@ struct JS_FRIEND_API(JSCompartment) {
 
   private:
     js::MathCache                *mathCache;
 
     js::MathCache *allocMathCache(JSContext *cx);
 
     bool                         marked;
     
+    typedef js::HashMap<jsbytecode*,
+                        size_t,
+                        js::DefaultHasher<jsbytecode*>,
+                        js::SystemAllocPolicy> BackEdgeMap;
+
+    BackEdgeMap                  backEdgeTable;
+
   public:
     js::MathCache *getMathCache(JSContext *cx) {
         return mathCache ? mathCache : allocMathCache(cx);
     }
 
     bool isMarked() { return marked; }
     void clearMark() { marked = false; }
+
+    size_t backEdgeCount(jsbytecode *pc) const;
+    size_t incBackEdgeCount(jsbytecode *pc);
 };
 
 #define JS_SCRIPTS_TO_GC(cx)    ((cx)->compartment->scriptsToGC)
 #define JS_PROPERTY_TREE(cx)    ((cx)->compartment->propertyTree)
 
 #ifdef DEBUG
 #define JS_COMPARTMENT_METER(x) x
 #else
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -71,16 +71,17 @@
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jsstr.h"
 #include "jsstaticcheck.h"
 #include "jstracer.h"
 #include "jslibmath.h"
 #include "jsvector.h"
 #include "methodjit/MethodJIT.h"
+#include "methodjit/MethodJIT-inl.h"
 #include "methodjit/Logging.h"
 
 #include "jsatominlines.h"
 #include "jscntxtinlines.h"
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
 #include "jsprobes.h"
 #include "jspropertycacheinlines.h"
@@ -624,17 +625,18 @@ RunScript(JSContext *cx, JSScript *scrip
 #endif
 
     AutoInterpPreparer prepareInterp(cx, script);
 
     JS_ASSERT(fp == cx->fp());
     JS_ASSERT(fp->script() == script);
 
 #ifdef JS_METHODJIT
-    mjit::CompileStatus status = mjit::CanMethodJIT(cx, script, fp);
+    mjit::CompileStatus status =
+        mjit::CanMethodJIT(cx, script, fp, mjit::CompileRequest_Interpreter);
     if (status == mjit::Compile_Error)
         return JS_FALSE;
 
     if (status == mjit::Compile_Okay)
         return mjit::JaegerShot(cx);
 #endif
 
     return Interpret(cx, fp);
@@ -767,17 +769,17 @@ InvokeSessionGuard::start(JSContext *cx,
         if (!stack.getInvokeFrame(cx, args_, fun, script_, &flags, &frame_))
             return false;
         JSStackFrame *fp = frame_.fp();
         fp->initCallFrame(cx, calleev.toObject(), fun, argc, flags);
         stack.pushInvokeFrame(cx, args_, &frame_);
 
 #ifdef JS_METHODJIT
         /* Hoist dynamic checks from RunScript. */
-        mjit::CompileStatus status = mjit::CanMethodJIT(cx, script_, fp);
+        mjit::CompileStatus status = mjit::CanMethodJIT(cx, script_, fp, mjit::CompileRequest_JIT);
         if (status == mjit::Compile_Error)
             return false;
         if (status != mjit::Compile_Okay)
             break;
         /* Cannot also cache the raw code pointer; it can change. */
 
         /* Hoist dynamic checks from CheckStackAndEnterMethodJIT. */
         JS_CHECK_RECURSION(cx, return JS_FALSE);
@@ -2317,16 +2319,40 @@ Interpret(JSContext *cx, JSStackFrame *e
     (obj = script->getObject(GET_FULL_INDEX(PCOFF)))
 
 #define LOAD_FUNCTION(PCOFF)                                                  \
     (fun = script->getFunction(GET_FULL_INDEX(PCOFF)))
 
 #define LOAD_DOUBLE(PCOFF, dbl)                                               \
     (dbl = script->getConst(GET_FULL_INDEX(PCOFF)).toDouble())
 
+#ifdef JS_METHODJIT
+
+#define MONITOR_BRANCH_METHODJIT()                                            \
+    JS_BEGIN_MACRO                                                            \
+        mjit::CompileStatus status =                                          \
+            mjit::CanMethodJITAtBranch(cx, script, regs.fp, regs.pc);         \
+        if (status == mjit::Compile_Error)                                    \
+            goto error;                                                       \
+        if (status == mjit::Compile_Okay) {                                   \
+            void *ncode =                                                     \
+                script->nativeCodeForPC(regs.fp->isConstructing(), regs.pc);  \
+            interpReturnOK = mjit::JaegerShotAtSafePoint(cx, ncode);          \
+            if (inlineCallCount)                                              \
+                goto jit_return;                                              \
+            goto leave_on_safe_point;                                         \
+        }                                                                     \
+    JS_END_MACRO
+
+#else
+
+#define MONITOR_BRANCH_METHODJIT() ((void) 0)
+
+#endif
+        
 #ifdef JS_TRACER
 
 #ifdef MOZ_TRACEVIS
 #if JS_THREADED_INTERP
 #define MONITOR_BRANCH_TRACEVIS                                               \
     JS_BEGIN_MACRO                                                            \
         if (jumpTable != interruptJumpTable)                                  \
             EnterTraceVisState(cx, S_RECORD, R_NONE);                         \
@@ -2349,28 +2375,34 @@ Interpret(JSContext *cx, JSStackFrame *e
         JS_ASSERT(cx->regs == &regs);                                         \
         if (cx->isExceptionPending())                                         \
             goto error;                                                       \
     JS_END_MACRO
 
 #define MONITOR_BRANCH()                                                      \
     JS_BEGIN_MACRO                                                            \
         if (TRACING_ENABLED(cx)) {                                            \
-            MonitorResult r = MonitorLoopEdge(cx, inlineCallCount);           \
-            if (r == MONITOR_RECORDING) {                                     \
-                JS_ASSERT(TRACE_RECORDER(cx));                                \
-                JS_ASSERT(!TRACE_PROFILER(cx));                               \
-                MONITOR_BRANCH_TRACEVIS;                                      \
-                ENABLE_INTERRUPTS();                                          \
-                CLEAR_LEAVE_ON_TRACE_POINT();                                 \
+            if (!TRACE_RECORDER(cx) && !TRACE_PROFILER(cx) &&                 \
+                interpMode == JSINTERP_NORMAL)                                \
+            {                                                                 \
+                MONITOR_BRANCH_METHODJIT();                                   \
+            } else {                                                          \
+                MonitorResult r = MonitorLoopEdge(cx, inlineCallCount);       \
+                if (r == MONITOR_RECORDING) {                                 \
+                    JS_ASSERT(TRACE_RECORDER(cx));                            \
+                    JS_ASSERT(!TRACE_PROFILER(cx));                           \
+                    MONITOR_BRANCH_TRACEVIS;                                  \
+                    ENABLE_INTERRUPTS();                                      \
+                    CLEAR_LEAVE_ON_TRACE_POINT();                             \
+                }                                                             \
+                RESTORE_INTERP_VARS();                                        \
+                JS_ASSERT_IF(cx->isExceptionPending(), r == MONITOR_ERROR);   \
+                if (r == MONITOR_ERROR)                                       \
+                    goto error;                                               \
             }                                                                 \
-            RESTORE_INTERP_VARS();                                            \
-            JS_ASSERT_IF(cx->isExceptionPending(), r == MONITOR_ERROR);       \
-            if (r == MONITOR_ERROR)                                           \
-                goto error;                                                   \
         }                                                                     \
     JS_END_MACRO
 
 #else /* !JS_TRACER */
 
 #define MONITOR_BRANCH() ((void) 0)
 
 #endif /* !JS_TRACER */
@@ -4688,17 +4720,20 @@ BEGIN_CASE(JSOP_FUNCALL)
             JS_RUNTIME_METER(rt, inlineCalls);
 
             TRACE_0(EnterFrame);
 
             CHECK_INTERRUPT_HANDLER();
 
 #ifdef JS_METHODJIT
             /* Try to ensure methods are method JIT'd.  */
-            mjit::CompileStatus status = mjit::CanMethodJIT(cx, script, regs.fp);
+            mjit::CompileRequest request = (interpMode == JSINTERP_NORMAL)
+                                           ? mjit::CompileRequest_Interpreter
+                                           : mjit::CompileRequest_JIT;
+            mjit::CompileStatus status = mjit::CanMethodJIT(cx, script, regs.fp, request);
             if (status == mjit::Compile_Error)
                 goto error;
             if (!TRACE_RECORDER(cx) && !TRACE_PROFILER(cx) && status == mjit::Compile_Okay) {
                 interpReturnOK = mjit::JaegerShot(cx);
                 CHECK_INTERRUPT_HANDLER();
                 goto jit_return;
             }
 #endif
@@ -6915,17 +6950,17 @@ END_CASE(JSOP_ARRAYPUSH)
             js_ReportIsNotDefined(cx, printable.ptr());
     }
     goto error;
 
     /*
      * This path is used when it's guaranteed the method can be finished
      * inside the JIT.
      */
-#if defined(JS_TRACER) && defined(JS_METHODJIT)
+#if defined(JS_METHODJIT)
   leave_on_safe_point:
 #endif
     return interpReturnOK;
 }
 
 } /* namespace js */
 
 #endif /* !defined jsinvoke_cpp___ */
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -368,16 +368,18 @@ struct JSScript {
     /* FIXME: bug 586181 */
     JSCList         links;      /* Links for compartment script list */
     jsbytecode      *code;      /* bytecodes and their immediate operands */
     uint32          length;     /* length of code vector */
 
   private:
     uint16          version;    /* JS version under which script was compiled */
 
+    size_t          callCount_; /* Number of times the script has been called. */
+
   public:
     uint16          nfixed;     /* number of slots besides stack operands in
                                    slot array */
 
     /*
      * Offsets to various array structures from the end of this script, or
      * JSScript::INVALID_OFFSET if the array has length 0.
      */
@@ -468,16 +470,19 @@ struct JSScript {
     inline void **nativeMap(bool constructing);
     inline void *maybeNativeCodeForPC(bool constructing, jsbytecode *pc);
     inline void *nativeCodeForPC(bool constructing, jsbytecode *pc);
 
     js::mjit::JITScript *getJIT(bool constructing) {
         return constructing ? jitCtor : jitNormal;
     }
 
+    size_t callCount() const  { return callCount_; }
+    size_t incCallCount() { return ++callCount_; }
+
     JITScriptStatus getJITStatus(bool constructing) {
         void *addr = constructing ? jitArityCheckCtor : jitArityCheckNormal;
         if (addr == NULL)
             return JITScript_None;
         if (addr == JS_UNJITTABLE_SCRIPT)
             return JITScript_Invalid;
         return JITScript_Valid;
     }
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -640,17 +640,18 @@ mjit::Compiler::finishThisUp(JITScript *
         jitTraceICs[i].jumpTargetPC = traceICs[i].jumpTarget;
 #endif
         jitTraceICs[i].hasSlowTraceHint = traceICs[i].slowTraceHint.isSet();
         if (traceICs[i].slowTraceHint.isSet())
             jitTraceICs[i].slowTraceHint = stubCode.locationOf(traceICs[i].slowTraceHint.get());
 #ifdef JS_TRACER
         jitTraceICs[i].loopCounterStart = GetHotloop(cx);
 #endif
-        jitTraceICs[i].loopCounter = jitTraceICs[i].loopCounterStart;
+        jitTraceICs[i].loopCounter = jitTraceICs[i].loopCounterStart
+            - cx->compartment->backEdgeCount(traceICs[i].jumpTarget);
         
         stubCode.patch(traceICs[i].addrLabel, &jitTraceICs[i]);
     }
 #endif /* JS_MONOIC */
 
     for (size_t i = 0; i < callPatches.length(); i++) {
         CallPatchInfo &patch = callPatches[i];
 
--- a/js/src/methodjit/InvokeHelpers.cpp
+++ b/js/src/methodjit/InvokeHelpers.cpp
@@ -63,16 +63,17 @@
 #include "jspropertycacheinlines.h"
 #include "jsscopeinlines.h"
 #include "jsscriptinlines.h"
 #include "jsstrinlines.h"
 #include "jsobjinlines.h"
 #include "jscntxtinlines.h"
 #include "jsatominlines.h"
 #include "StubCalls-inl.h"
+#include "MethodJIT-inl.h"
 
 #include "jsautooplen.h"
 
 using namespace js;
 using namespace js::mjit;
 using namespace JSC;
 
 using ic::Repatcher;
@@ -165,22 +166,17 @@ top:
             }
         }
     }
 
     return NULL;
 }
 
 /*
- * Clean up a frame and return.  popFrame indicates whether to additionally pop
- * the frame and store the return value on the caller's stack.  The frame will
- * normally be popped by the caller on return from a call into JIT code,
- * so must be popped here when that caller code will not execute.  This can be
- * either because of a call into an un-JITable script, or because the call is
- * throwing an exception.
+ * Clean up a frame and return.
  */
 static void
 InlineReturn(VMFrame &f)
 {
     JSContext *cx = f.cx;
     JSStackFrame *fp = f.regs.fp;
 
     JS_ASSERT(f.fp() != f.entryfp);
@@ -306,18 +302,17 @@ stubs::CompileFunction(VMFrame &f, uint3
      * we must carefully extract the callee from the nactual.
      */
     JSObject &callee = fp->formalArgsEnd()[-(int(nactual) + 2)].toObject();
     JSFunction *fun = callee.getFunctionPrivate();
     JSScript *script = fun->script();
 
     /*
      * FixupArity/RemovePartialFrame expect to be called after the early
-     * prologue. Pass the existing value for ncode, it has already been set
-     * by the jit code calling into this stub.
+     * prologue.
      */
     fp->initCallFrameEarlyPrologue(fun, nactual);
 
     if (nactual != fp->numFormalArgs()) {
         fp = (JSStackFrame *)FixupArity(f, nactual);
         if (!fp)
             return NULL;
     }
@@ -328,32 +323,32 @@ stubs::CompileFunction(VMFrame &f, uint3
     /* These would have been initialized by the prologue. */
     f.regs.fp = fp;
     f.regs.sp = fp->base();
     f.regs.pc = script->code;
 
     if (fun->isHeavyweight() && !js_GetCallObject(cx, fp))
         THROWV(NULL);
 
-    CompileStatus status = CanMethodJIT(cx, script, fp);
+    CompileStatus status = CanMethodJIT(cx, script, fp, CompileRequest_JIT);
     if (status == Compile_Okay)
         return script->getJIT(fp->isConstructing())->invokeEntry;
 
     /* Function did not compile... interpret it. */
     JSBool ok = Interpret(cx, fp);
     InlineReturn(f);
 
     if (!ok)
         THROWV(NULL);
 
     return NULL;
 }
 
 static inline bool
-UncachedInlineCall(VMFrame &f, uint32 flags, void **pret, uint32 argc)
+UncachedInlineCall(VMFrame &f, uint32 flags, void **pret, bool *unjittable, uint32 argc)
 {
     JSContext *cx = f.cx;
     Value *vp = f.regs.sp - (argc + 2);
     JSObject &callee = vp->toObject();
     JSFunction *newfun = callee.getFunctionPrivate();
     JSScript *newscript = newfun->script();
 
     /* Get pointer to new frame/slots, prepare arguments. */
@@ -375,21 +370,24 @@ UncachedInlineCall(VMFrame &f, uint32 fl
     JS_ASSERT(newfp == f.regs.fp);
 
     /* Scope with a call object parented by callee's parent. */
     if (newfun->isHeavyweight() && !js_GetCallObject(cx, newfp))
         return false;
 
     /* Try to compile if not already compiled. */
     if (newscript->getJITStatus(newfp->isConstructing()) == JITScript_None) {
-        if (mjit::TryCompile(cx, newfp) == Compile_Error) {
+        CompileStatus status = CanMethodJIT(cx, newscript, newfp, CompileRequest_Interpreter);
+        if (status == Compile_Error) {
             /* A runtime exception was thrown, get out. */
             InlineReturn(f);
             return false;
         }
+        if (status == Compile_Abort)
+            *unjittable = true;
     }
 
     /* If newscript was successfully compiled, run it. */
     if (JITScript *jit = newscript->getJIT(newfp->isConstructing())) {
         *pret = jit->invokeEntry;
         return true;
     }
 
@@ -415,17 +413,17 @@ stubs::UncachedNewHelper(VMFrame &f, uin
     ucr->init();
 
     JSContext *cx = f.cx;
     Value *vp = f.regs.sp - (argc + 2);
 
     /* Try to do a fast inline call before the general Invoke path. */
     if (IsFunctionObject(*vp, &ucr->fun) && ucr->fun->isInterpreted()) {
         ucr->callee = &vp->toObject();
-        if (!UncachedInlineCall(f, JSFRAME_CONSTRUCTING, &ucr->codeAddr, argc))
+        if (!UncachedInlineCall(f, JSFRAME_CONSTRUCTING, &ucr->codeAddr, &ucr->unjittable, argc))
             THROW();
     } else {
         if (!InvokeConstructor(cx, InvokeArgsAlreadyOnTheStack(vp, argc)))
             THROW();
     }
 }
 
 void * JS_FASTCALL
@@ -465,17 +463,17 @@ stubs::UncachedCallHelper(VMFrame &f, ui
     JSContext *cx = f.cx;
     Value *vp = f.regs.sp - (argc + 2);
 
     if (IsFunctionObject(*vp, &ucr->callee)) {
         ucr->callee = &vp->toObject();
         ucr->fun = GET_FUNCTION_PRIVATE(cx, ucr->callee);
 
         if (ucr->fun->isInterpreted()) {
-            if (!UncachedInlineCall(f, 0, &ucr->codeAddr, argc))
+            if (!UncachedInlineCall(f, 0, &ucr->codeAddr, &ucr->unjittable, argc))
                 THROW();
             return;
         }
 
         if (ucr->fun->isNative()) {
             if (!CallJSNative(cx, ucr->fun->u.n.native, argc, vp))
                 THROW();
             return;
new file mode 100644
--- /dev/null
+++ b/js/src/methodjit/MethodJIT-inl.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et tw=99:
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released
+ * May 28, 2008.
+ *
+ * The Initial Developer of the Original Code is
+ *   Brendan Eich <brendan@mozilla.org>
+ *
+ * Contributor(s):
+ *   David Anderson <danderson@mozilla.com>
+ *   David Mandelin <dmandelin@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#if !defined jsjaeger_methodjit_inl_h__ && defined JS_METHODJIT
+#define jsjaeger_methodjit_inl_h__
+
+namespace js {
+namespace mjit {
+
+enum CompileRequest
+{
+    CompileRequest_Interpreter,
+    CompileRequest_JIT
+};
+
+/* Number of times a script must be called before we run it in the methodjit. */
+static const size_t CALLS_BEFORE_COMPILE = 16;
+
+/* Number of loop back-edges we execute in the interpreter before methodjitting. */
+static const size_t BACKEDGES_BEFORE_COMPILE = 16;
+
+static inline CompileStatus
+CanMethodJIT(JSContext *cx, JSScript *script, JSStackFrame *fp, CompileRequest request)
+{
+    if (!cx->methodJitEnabled)
+        return Compile_Abort;
+    JITScriptStatus status = script->getJITStatus(fp->isConstructing());
+    if (status == JITScript_Invalid)
+        return Compile_Abort;
+    if (request == CompileRequest_Interpreter &&
+        status == JITScript_None &&
+        !cx->hasRunOption(JSOPTION_METHODJIT_ALWAYS) &&
+        script->incCallCount() <= CALLS_BEFORE_COMPILE)
+    {
+        return Compile_Skipped;
+    }
+    if (status == JITScript_None)
+        return TryCompile(cx, fp);
+    return Compile_Okay;
+}
+
+/*
+ * Called from a backedge in the interpreter to decide if we should transition to the
+ * methodjit. If so, we compile the given function.
+ */
+static inline CompileStatus
+CanMethodJITAtBranch(JSContext *cx, JSScript *script, JSStackFrame *fp, jsbytecode *pc)
+{
+    if (!cx->methodJitEnabled)
+        return Compile_Abort;
+    JITScriptStatus status = script->getJITStatus(fp->isConstructing());
+    if (status == JITScript_Invalid)
+        return Compile_Abort;
+    if (status == JITScript_None &&
+        !cx->hasRunOption(JSOPTION_METHODJIT_ALWAYS) &&
+        cx->compartment->incBackEdgeCount(pc) <= BACKEDGES_BEFORE_COMPILE)
+    {
+        return Compile_Skipped;
+    }
+    if (status == JITScript_None)
+        return TryCompile(cx, fp);
+    return Compile_Okay;
+}
+
+}
+}
+
+#endif
--- a/js/src/methodjit/MethodJIT.h
+++ b/js/src/methodjit/MethodJIT.h
@@ -389,41 +389,29 @@ JSBool JaegerShot(JSContext *cx);
 
 /* Drop into the middle of a method at an arbitrary point, and execute. */
 JSBool JaegerShotAtSafePoint(JSContext *cx, void *safePoint);
 
 enum CompileStatus
 {
     Compile_Okay,
     Compile_Abort,
-    Compile_Error
+    Compile_Error,
+    Compile_Skipped
 };
 
 void JS_FASTCALL
 ProfileStubCall(VMFrame &f);
 
 CompileStatus JS_NEVER_INLINE
 TryCompile(JSContext *cx, JSStackFrame *fp);
 
 void
 ReleaseScriptCode(JSContext *cx, JSScript *script);
 
-static inline CompileStatus
-CanMethodJIT(JSContext *cx, JSScript *script, JSStackFrame *fp)
-{
-    if (!cx->methodJitEnabled)
-        return Compile_Abort;
-    JITScriptStatus status = script->getJITStatus(fp->isConstructing());
-    if (status == JITScript_Invalid)
-        return Compile_Abort;
-    if (status == JITScript_None)
-        return TryCompile(cx, fp);
-    return Compile_Okay;
-}
-
 struct CallSite
 {
     uint32 codeOffset;
     uint32 pcOffset;
     uint32 id;
 
     // Normally, callsite ID is the __LINE__ in the program that added the
     // callsite. Since traps can be removed, we make sure they carry over
--- a/js/src/methodjit/MonoIC.cpp
+++ b/js/src/methodjit/MonoIC.cpp
@@ -945,17 +945,18 @@ class CallCompiler : public BaseCompiler
         if (callingNew)
             stubs::UncachedNewHelper(f, ic.frameSize.staticArgc(), &ucr);
         else
             stubs::UncachedCallHelper(f, ic.frameSize.getArgc(f), &ucr);
 
         // If the function cannot be jitted (generally unjittable or empty script),
         // patch this site to go to a slow path always.
         if (!ucr.codeAddr) {
-            disable(jit);
+            if (ucr.unjittable)
+                disable(jit);
             return NULL;
         }
             
         JSFunction *fun = ucr.fun;
         JS_ASSERT(fun);
         JSScript *script = fun->script();
         JS_ASSERT(script);
         JSObject *callee = ucr.callee;
--- a/js/src/methodjit/StubCalls.h
+++ b/js/src/methodjit/StubCalls.h
@@ -75,30 +75,32 @@ void JS_FASTCALL EnterScript(VMFrame &f)
 void JS_FASTCALL LeaveScript(VMFrame &f);
 
 /*
  * Result struct for UncachedXHelper.
  *
  * These functions can have one of two results:
  *
  *   (1) The function was executed in the interpreter. Then all fields
- *       are NULL.
+ *       are NULL except unjittable.
  *
  *   (2) The function was not executed, and the function has been compiled
  *       to JM native code. Then all fields are non-NULL.
  */
 struct UncachedCallResult {
     JSObject   *callee;       // callee object
     JSFunction *fun;          // callee function
     void       *codeAddr;     // code address of compiled callee function
+    bool       unjittable;    // did we try to JIT and fail?
 
     void init() {
         callee = NULL;
         fun = NULL;
         codeAddr = NULL;
+        unjittable = false;
     }        
 };
 
 /*
  * Helper functions for stubs and IC functions for calling functions.
  * These functions either execute the function, return a native code
  * pointer that can be used to call the function, or throw.
  */
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -577,16 +577,18 @@ usage(void)
                       "  -s            Toggle JSOPTION_STRICT flag\n"
                       "  -w            Report strict warnings\n"
                       "  -W            Do not report strict warnings\n"
                       "  -x            Toggle JSOPTION_XML flag\n"
                       "  -C            Compile-only; do not execute\n"
                       "  -i            Enable interactive read-eval-print loop\n"
                       "  -j            Enable the TraceMonkey tracing JIT\n"
                       "  -m            Enable the JaegerMonkey method JIT\n"
+                      "  -a            Always method JIT, ignore internal tuning\n"
+                      "                This only has effect with -m\n"
                       "  -p            Enable loop profiling for TraceMonkey\n"
                       "  -d            Enable debug mode\n"
                       "  -b            Print timing statistics\n"
                       "  -t <timeout>  Interrupt long-running execution after <timeout> seconds, where\n"
                       "                <timeout> <= 1800.0. Negative values indicate no timeout (default).\n"
                       "  -c <size>     Suggest stack chunk size of <size> bytes. Default is 8192.\n"
                       "                Warning: this option is currently ignored.\n"
                       "  -o <option>   Enable a context option flag by name\n"
@@ -629,19 +631,20 @@ usage(void)
  * order for better reporting.
  */
 static const struct {
     const char  *name;
     uint32      flag;
 } js_options[] = {
     {"anonfunfix",      JSOPTION_ANONFUNFIX},
     {"atline",          JSOPTION_ATLINE},
+    {"jitprofiling",    JSOPTION_PROFILING},
     {"tracejit",        JSOPTION_JIT},
     {"methodjit",       JSOPTION_METHODJIT},
-    {"jitprofiling",    JSOPTION_PROFILING},
+    {"methodjit_always",JSOPTION_METHODJIT_ALWAYS},
     {"relimit",         JSOPTION_RELIMIT},
     {"strict",          JSOPTION_STRICT},
     {"werror",          JSOPTION_WERROR},
     {"xml",             JSOPTION_XML},
 };
 
 static uint32
 MapContextOptionNameToFlag(JSContext* cx, const char* name)
@@ -802,16 +805,20 @@ ProcessArgs(JSContext *cx, JSObject *obj
 #endif
             break;
 
         case 'm':
             enableMethodJit = !enableMethodJit;
             JS_ToggleOptions(cx, JSOPTION_METHODJIT);
             break;
 
+        case 'a':
+            JS_ToggleOptions(cx, JSOPTION_METHODJIT_ALWAYS);
+            break;
+
         case 'p':
             enableProfiling = !enableProfiling;
             JS_ToggleOptions(cx, JSOPTION_PROFILING);
             break;
            
         case 'o':
           {
             if (++i == argc)
--- a/js/src/tests/shell.js
+++ b/js/src/tests/shell.js
@@ -645,17 +645,18 @@ function optionsClear() {
   // except jit.
   var optionNames = options().split(',');
   for (var i = 0; i < optionNames.length; i++)
   {
     var optionName = optionNames[i];
     if (optionName &&
         optionName != "methodjit" &&
         optionName != "tracejit" &&
-        optionName != "jitprofiling")
+        optionName != "jitprofiling" &&
+        optionName != "methodjit_always")
     {
       options(optionName);
     }
   }
 }
 
 function optionsPush()
 {
--- a/js/src/tests/user.js
+++ b/js/src/tests/user.js
@@ -28,10 +28,11 @@ user_pref("extensions.checkCompatibility
 user_pref("extensions.checkUpdateSecurity", false);
 user_pref("browser.EULA.override", true);
 user_pref("javascript.options.tracejit.chrome", true);
 user_pref("javascript.options.tracejit.content", true);
 user_pref("javascript.options.methodjit.chrome", false);
 user_pref("javascript.options.methodjit.content", true);
 user_pref("javascript.options.jitprofiling.chrome", false);
 user_pref("javascript.options.jitprofiling.content", true);
+user_pref("javascript.options.methodjit_always", false);
 user_pref("javascript.options.strict", false);
 user_pref("javascript.options.werror", false);
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -599,16 +599,17 @@ pref("javascript.options.strict.debug", 
 #endif
 pref("javascript.options.relimit",          true);
 pref("javascript.options.tracejit.content",  true);
 pref("javascript.options.tracejit.chrome",   true);
 pref("javascript.options.methodjit.content", true);
 pref("javascript.options.methodjit.chrome",  false);
 pref("javascript.options.jitprofiling.content", true);
 pref("javascript.options.jitprofiling.chrome",  false);
+pref("javascript.options.methodjit_always", false);
 // This preference limits the memory usage of javascript.
 // If you want to change these values for your device,
 // please find Bug 417052 comment 17 and Bug 456721
 // Comment 32 and Bug 613551.
 pref("javascript.options.mem.high_water_mark", 128);
 pref("javascript.options.mem.max", -1);
 pref("javascript.options.mem.gc_frequency",   300);
 pref("javascript.options.mem.gc_per_compartment", true);