Bug 716647 - Part 6: Tests. (r=jimb)
authorShu-yu Guo <shu@rfrn.org>
Thu, 24 Apr 2014 01:59:38 -0700
changeset 199512 a49c8cdf14c43d0b69993be83eff5df5e8de36e4
parent 199511 0e61e88866ceacf80007481e37a23427e6b1efa4
child 199513 079f4f0ed6a6ebbbdbc985ed5799c066f50759f4
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb
bugs716647
milestone31.0a1
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 716647 - Part 6: Tests. (r=jimb)
js/src/jit-test/lib/jitopts.js
js/src/jit-test/tests/debug/Debugger-debuggees-22.js
js/src/jit-test/tests/debug/Debugger-debuggees-23.js
js/src/jit-test/tests/debug/Debugger-debuggees-24.js
js/src/jit-test/tests/debug/Debugger-debuggees-25.js
js/src/jit-test/tests/debug/Debugger-debuggees-26.js
js/src/jit-test/tests/debug/Frame-eval-19.js
js/src/jit-test/tests/debug/Frame-eval-20.js
js/src/jit-test/tests/debug/Frame-eval-21.js
js/src/jit-test/tests/debug/Frame-eval-22.js
js/src/jit-test/tests/debug/Frame-eval-23.js
js/src/jit-test/tests/debug/Frame-implementation-01.js
js/src/jit-test/tests/debug/Frame-implementation-02.js
js/src/jit-test/tests/debug/optimized-out-01.js
js/src/jit-test/tests/debug/resumption-08.js
js/src/jsapi.cpp
js/src/jsapi.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/lib/jitopts.js
@@ -0,0 +1,69 @@
+// These predicates are for tests that require a particular set of JIT options.
+
+// Check if toggles match. Useful for tests that shouldn't be run if a
+// different set of JIT toggles are set, since TBPL runs each jit-test
+// multiple times with a variety of flags.
+function jitTogglesMatch(opts) {
+  var currentOpts = getJitCompilerOptions();
+  for (var k in opts) {
+    if (k.indexOf(".enable") > 0 && opts[k] != currentOpts[k])
+      return false;
+  }
+  return true;
+}
+
+// Run fn under a particular set of JIT options.
+function withJitOptions(opts, fn) {
+  var oldOpts = getJitCompilerOptions();
+  for (var k in opts)
+    setJitCompilerOption(k, opts[k]);
+  try {
+    fn();
+  } finally {
+    for (var k in oldOpts)
+      setJitCompilerOption(k, oldOpts[k]);
+  }
+}
+
+// N.B. Ion opts *must come before* baseline opts because there's some kind of
+// "undo eager compilation" logic. If we don't set the baseline usecount
+// *after* the Ion usecount we end up setting the baseline usecount to be the
+// default if we hit the "undo eager compilation" logic.
+var Opts_BaselineEager =
+    {
+      'ion.enable': 1,
+      'baseline.enable': 1,
+      'baseline.usecount.trigger': 0,
+      'parallel-compilation.enable': 1
+    };
+
+// Checking for parallel compilation being off is often helpful if the test
+// requires a function be Ion compiled. Each individual test will usually
+// finish before the Ion compilation thread has a chance to attach the
+// compiled code.
+var Opts_IonEagerNoParallelCompilation =
+    {
+      'ion.enable': 1,
+      'ion.usecount.trigger': 0,
+      'baseline.enable': 1,
+      'baseline.usecount.trigger': 0,
+      'parallel-compilation.enable': 0,
+    };
+
+var Opts_Ion2NoParallelCompilation =
+    {
+      'ion.enable': 1,
+      'ion.usecount.trigger': 2,
+      'baseline.enable': 1,
+      'baseline.usecount.trigger': 1,
+      'parallel-compilation.enable': 0
+    };
+
+var Opts_NoJits =
+    {
+      'ion.enable': 0,
+      'ion.usecount.trigger': 0,
+      'baseline.usecount.trigger': 0,
+      'baseline.enable': 0,
+      'parallel-compilation.enable': 0
+    };
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-22.js
@@ -0,0 +1,24 @@
+// Adding a debuggee allowed with scripts on stack.
+
+var g = newGlobal();
+g.dbg = new Debugger;
+
+g.eval("" + function f(d) {
+  g(d);
+  if (d)
+    assertEq(dbg.hasDebuggee(this), true);
+});
+
+g.eval("" + function g(d) {
+  if (!d)
+    return;
+
+  dbg.addDebuggee(this);
+});
+
+g.eval("(" + function test() {
+  f(false);
+  f(false);
+  f(true);
+  f(true);
+} + ")();");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-23.js
@@ -0,0 +1,107 @@
+// Adding a debuggee allowed with scripts on stack from stranger places.
+
+// Test CCW.
+(function testCCW() {
+  var g = newGlobal();
+  var dbg = new Debugger;
+  g.dbg = dbg;
+  g.GLOBAL = g;
+
+  g.turnOnDebugger = function () {
+    dbg.addDebuggee(g);
+  };
+
+  g.eval("" + function f(d) {
+    turnOnDebugger();
+    assertEq(dbg.hasDebuggee(GLOBAL), true);
+  });
+
+  g.eval("(" + function test() {
+    f(false);
+    f(false);
+    f(true);
+    f(true);
+  } + ")();");
+})();
+
+// Test getter.
+(function testGetter() {
+  var g = newGlobal();
+  g.dbg = new Debugger;
+  g.GLOBAL = g;
+
+  g.eval("" + function f(obj) {
+    obj.foo;
+    assertEq(dbg.hasDebuggee(GLOBAL), true);
+  });
+
+  g.eval("(" + function test() {
+    f({ get foo() { dbg.addDebuggee(GLOBAL); } });
+  } + ")();");
+})();
+
+// Test setter.
+(function testSetter() {
+  var g = newGlobal();
+  g.dbg = new Debugger;
+  g.GLOBAL = g;
+
+  g.eval("" + function f(obj) {
+    obj.foo = 42;
+    assertEq(dbg.hasDebuggee(GLOBAL), true);
+  });
+
+  g.eval("(" + function test() {
+    f({ set foo(v) { dbg.addDebuggee(GLOBAL); } });
+  } + ")();");
+})();
+
+// Test toString.
+(function testToString() {
+  var g = newGlobal();
+  g.dbg = new Debugger;
+  g.GLOBAL = g;
+
+  g.eval("" + function f(obj) {
+    obj + "";
+    assertEq(dbg.hasDebuggee(GLOBAL), true);
+  });
+
+  g.eval("(" + function test() {
+    f({ toString: function () { dbg.addDebuggee(GLOBAL); }});
+  } + ")();");
+})();
+
+// Test valueOf.
+(function testValueOf() {
+  var g = newGlobal();
+  g.dbg = new Debugger;
+  g.GLOBAL = g;
+
+  g.eval("" + function f(obj) {
+    obj + "";
+    assertEq(dbg.hasDebuggee(GLOBAL), true);
+  });
+
+  g.eval("(" + function test() {
+    f({ valueOf: function () { dbg.addDebuggee(GLOBAL); }});
+  } + ")();");
+})();
+
+// Test proxy trap.
+(function testProxyTrap() {
+  var g = newGlobal();
+  g.dbg = new Debugger;
+  g.GLOBAL = g;
+
+  g.eval("" + function f(proxy) {
+    proxy["foo"];
+    assertEq(dbg.hasDebuggee(GLOBAL), true);
+  });
+
+  g.eval("(" + function test() {
+    var handler = { get: function () { dbg.addDebuggee(GLOBAL); } };
+    var proxy = new Proxy({}, handler);
+    f(proxy);
+  } + ")();");
+})();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-24.js
@@ -0,0 +1,55 @@
+// Turning debugger on for a particular global with on-stack scripts shouldn't
+// make other globals' scripts observable.
+
+var g1 = newGlobal();
+var g2 = newGlobal();
+var g3 = newGlobal();
+
+g1.eval("" + function f() {
+  var name = "f";
+  g();
+  return name;
+});
+g2.eval("" + function g() {
+  var name = "g";
+  h();
+  return name;
+});
+g3.eval("" + function h() {
+  var name = "h";
+  toggle();
+  return name;
+});
+
+g1.g = g2.g;
+g2.h = g3.h;
+
+function name(f) {
+  return f.environment.getVariable("name");
+}
+
+var dbg = new Debugger;
+g3.toggle = function () {
+  var frame;
+
+  // Only f should be visible.
+  dbg.addDebuggee(g1);
+  frame = dbg.getNewestFrame();
+  assertEq(name(frame), "f");
+
+  // Now h should also be visible.
+  dbg.addDebuggee(g3);
+  frame = dbg.getNewestFrame();
+  assertEq(name(frame), "h");
+  assertEq(name(frame.older), "f");
+
+  // Finally everything should be visible.
+  dbg.addDebuggee(g2);
+  frame = dbg.getNewestFrame();
+  assertEq(name(frame), "h");
+  assertEq(name(frame.older), "g");
+  assertEq(name(frame.older.older), "f");
+};
+
+g1.eval("(" + function () { f(); } + ")();");
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-25.js
@@ -0,0 +1,48 @@
+// Turning debugger off global at a time.
+
+var g1 = newGlobal();
+var g2 = newGlobal();
+var g3 = newGlobal();
+
+g1.eval("" + function f() {
+  var name = "f";
+  g();
+  return name;
+});
+g2.eval("" + function g() {
+  var name = "g";
+  h();
+  return name;
+});
+g3.eval("" + function h() {
+  var name = "h";
+  toggle();
+  return name;
+});
+
+g1.g = g2.g;
+g2.h = g3.h;
+
+function name(f) {
+  return f.environment.getVariable("name");
+}
+
+var dbg = new Debugger;
+g3.toggle = function () {
+  var frame;
+
+  // Add all globals.
+  dbg.addDebuggee(g1);
+  dbg.addDebuggee(g3);
+  dbg.addDebuggee(g2);
+
+  // Remove one at a time.
+  dbg.removeDebuggee(g3);
+  assertEq(name(dbg.getNewestFrame()), "g");
+  dbg.removeDebuggee(g2);
+  assertEq(name(dbg.getNewestFrame()), "f");
+  dbg.removeDebuggee(g1);
+};
+
+g1.eval("(" + function () { f(); } + ")();");
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-26.js
@@ -0,0 +1,34 @@
+// Ion can bail in-place when throwing exceptions with debug mode toggled on.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoParallelCompilation))
+  quit();
+
+withJitOptions(Opts_Ion2NoParallelCompilation, function () {
+  var g = newGlobal();
+  var dbg = new Debugger;
+
+  g.toggle = function toggle(x, d) {
+    if (d) {
+      dbg.addDebuggee(g);
+      var frame = dbg.getNewestFrame().older;
+      assertEq(frame.callee.name, "f");
+      assertEq(frame.implementation, "ion");
+      throw 42;
+    }
+  };
+
+  g.eval("" + function f(x, d) { g(x, d); });
+  g.eval("" + function g(x, d) { toggle(x, d); });
+
+  try {
+    g.eval("(" + function test() {
+      for (var i = 0; i < 5; i++)
+        f(42, false);
+      f(42, true);
+    } + ")();");
+  } catch (exc) {
+    assertEq(exc, 42);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-19.js
@@ -0,0 +1,30 @@
+// Eval-in-frame of optimized frames to break out of an infinite loop.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoParallelCompilation))
+  quit(0);
+
+withJitOptions(Opts_Ion2NoParallelCompilation, function () {
+  var g = newGlobal();
+  var dbg = new Debugger;
+
+  g.eval("" + function f(d) { g(d); });
+  g.eval("" + function g(d) { h(d); });
+  g.eval("" + function h(d) { while (d); });
+
+  timeout(5, function () {
+    dbg.addDebuggee(g);
+    var frame = dbg.getNewestFrame();
+    if (frame.callee.name != "h" || frame.implementation != "ion")
+      return true;
+    frame.eval("d = false;");
+    return true;
+  });
+
+  g.eval("(" + function () {
+    for (i = 0; i < 5; i++)
+      f(false);
+    f(true);
+  } + ")();");
+});
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-20.js
@@ -0,0 +1,46 @@
+// Eval-in-frame with different type on non-youngest Ion frame.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoParallelCompilation))
+  quit(0);
+
+withJitOptions(Opts_Ion2NoParallelCompilation, function () {
+  function test(shadow) {
+    var g = newGlobal();
+    var dbg = new Debugger;
+
+    // Note that we depend on CCW scripted functions being opaque to Ion
+    // optimization for this test.
+    g.h = function h(d) {
+      if (d) {
+        dbg.addDebuggee(g);
+        var f = dbg.getNewestFrame().older;
+        assertEq(f.implementation, "ion");
+        assertEq(f.environment.getVariable("foo"), 42);
+
+        // EIF of a different type too.
+        f.eval((shadow ? "var " : "") + "foo = 'string of 42'");
+        g.expected = shadow ? 42 : "string of 42";
+      }
+    }
+
+    g.eval("" + function f(d) {
+      var foo = 42;
+      g(d);
+      return foo;
+    });
+    g.eval("" + function g(d) {
+      h(d);
+    });
+
+    g.eval("(" + function () {
+      for (i = 0; i < 5; i++)
+        f(false);
+      assertEq(f(true), "string of 42");
+    } + ")();");
+  }
+
+  test(false);
+  test(true);
+});
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-21.js
@@ -0,0 +1,33 @@
+// Eval-in-frame with different type on baseline frame with let-scoping
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_BaselineEager))
+  quit(0);
+
+withJitOptions(Opts_BaselineEager, function () {
+  var g = newGlobal();
+  var dbg = new Debugger;
+
+  g.h = function h(d) {
+    if (d) {
+      dbg.addDebuggee(g);
+      var f = dbg.getNewestFrame().older;
+      assertEq(f.implementation, "baseline");
+      assertEq(f.environment.getVariable("foo"), 42);
+      f.eval("foo = 'string of 42'");
+    }
+  }
+
+  g.eval("" + function f(d) {
+    if (d) {
+      let foo = 42;
+      g(d);
+      return foo;
+    }
+  });
+
+  g.eval("" + function g(d) { h(d); });
+
+  g.eval("(" + function () { assertEq(f(true), "string of 42"); } + ")();");
+});
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-22.js
@@ -0,0 +1,32 @@
+// Debugger.Frame preserves Ion frame identity
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoParallelCompilation))
+  quit();
+
+withJitOptions(Opts_Ion2NoParallelCompilation, function () {
+  var g = newGlobal();
+  var dbg1 = new Debugger;
+  var dbg2 = new Debugger;
+
+  g.toggle = function toggle(x, d) {
+    if (d) {
+      dbg1.addDebuggee(g);
+      dbg2.addDebuggee(g);
+      var frame1 = dbg1.getNewestFrame();
+      assertEq(frame1.environment.getVariable("x"), x);
+      assertEq(frame1.implementation, "ion");
+      frame1.environment.setVariable("x", "not 42");
+      assertEq(dbg2.getNewestFrame().environment.getVariable("x"), "not 42");
+    }
+  };
+
+  g.eval("" + function f(x, d) { toggle(x, d); });
+
+  g.eval("(" + function test() {
+    for (var i = 0; i < 5; i++)
+      f(42, false);
+    f(42, true);
+  } + ")();");
+});
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-23.js
@@ -0,0 +1,37 @@
+// Debugger.Frame preserves Ion frame mutations after removing debuggee.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoParallelCompilation))
+  quit();
+
+withJitOptions(Opts_Ion2NoParallelCompilation, function () {
+  var g = newGlobal();
+  var dbg = new Debugger;
+
+  g.toggle = function toggle(x, d) {
+    if (d) {
+      dbg.addDebuggee(g);
+      var frame = dbg.getNewestFrame().older;
+      assertEq(frame.callee.name, "f");
+      assertEq(frame.environment.getVariable("x"), x);
+      assertEq(frame.implementation, "ion");
+      frame.environment.setVariable("x", "not 42");
+      dbg.removeDebuggee(g);
+    }
+  };
+
+  g.eval("" + function f(x, d) {
+    g(x, d);
+    if (d)
+      assertEq(x, "not 42");
+  });
+
+  g.eval("" + function g(x, d) { toggle(x, d); });
+
+  g.eval("(" + function test() {
+    for (var i = 0; i < 5; i++)
+      f(42, false);
+    f(42, true);
+  } + ")();");
+});
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-implementation-01.js
@@ -0,0 +1,45 @@
+// Debugger.Frames of all implementations.
+
+load(libdir + "jitopts.js");
+
+function testFrameImpl(jitopts, assertFrameImpl) {
+  if (!jitTogglesMatch(jitopts))
+    return;
+
+  withJitOptions(jitopts, function () {
+    var g = newGlobal();
+    var dbg = new Debugger;
+
+    g.toggle = function toggle(d) {
+      if (d) {
+        dbg.addDebuggee(g);
+        var frame = dbg.getNewestFrame();
+        // We only care about the f and g frames.
+        for (var i = 0; i < 2; i++) {
+          assertFrameImpl(frame);
+          frame = frame.older;
+        }
+      }
+    };
+
+    g.eval("" + function f(d) { g(d); });
+    g.eval("" + function g(d) { toggle(d); });
+
+    g.eval("(" + function test() {
+      for (var i = 0; i < 5; i++)
+        f(false);
+      f(true);
+    } + ")();");
+  });
+}
+
+[[Opts_BaselineEager,
+  function (f) { assertEq(f.implementation, "baseline"); }],
+ // Note that the Ion case *depends* on CCW scripted functions being opaque to
+ // Ion optimization and not deoptimizing the frames below the call to toggle.
+ [Opts_Ion2NoParallelCompilation,
+  function (f) { assertEq(f.implementation, "ion"); }],
+ [Opts_NoJits,
+  function (f) { assertEq(f.implementation, "interpreter"); }]].forEach(function ([opts, fn]) {
+    testFrameImpl(opts, fn);
+  });
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-implementation-02.js
@@ -0,0 +1,51 @@
+// Test that Ion frames are invalidated by turning on Debugger. Invalidation
+// is unobservable, but we know that Ion scripts cannot handle Debugger
+// handlers, so we test for the handlers being called.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoParallelCompilation))
+  quit();
+
+withJitOptions(Opts_Ion2NoParallelCompilation, function () {
+  var g = newGlobal();
+  var dbg = new Debugger;
+  var onPopExecuted = false;
+  var breakpointHit = false;
+
+  g.toggle = function toggle(d) {
+    if (d) {
+      dbg.addDebuggee(g);
+
+      var frame1 = dbg.getNewestFrame();
+      assertEq(frame1.implementation, "ion");
+      frame1.onPop = function () {
+        onPopExecuted = true;
+      };
+
+      var frame2 = frame1.older;
+      assertEq(frame2.implementation, "ion");
+      // Offset of |print(42 + 42)|
+      var offset = frame2.script.getLineOffsets(3)[0];
+      frame2.script.setBreakpoint(offset, { hit: function (fr) {
+        assertEq(fr.implementation != "ion", true);
+        breakpointHit = true;
+      }});
+    }
+  };
+
+  g.eval("" + function f(d) {
+    g(d);
+    print(42 + 42);
+  });
+  g.eval("" + function g(d) { toggle(d); });
+
+  g.eval("(" + function test() {
+    for (var i = 0; i < 5; i++)
+      f(false);
+    f(true);
+  } + ")();");
+
+  assertEq(onPopExecuted, true);
+  assertEq(breakpointHit, true);
+});
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/optimized-out-01.js
@@ -0,0 +1,42 @@
+// Tests that we can reflect optimized out values.
+//
+// Unfortunately these tests are brittle. They depend on opaque JIT heuristics
+// kicking in.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoParallelCompilation))
+  quit(0);
+
+withJitOptions(Opts_Ion2NoParallelCompilation, function () {
+  var g = newGlobal();
+  var dbg = new Debugger;
+
+  // Note that this *depends* on CCW scripted functions being opaque to Ion
+  // optimization and not deoptimizing the frames below the call to toggle.
+  g.toggle = function toggle(d) {
+    if (d) {
+      dbg.addDebuggee(g);
+      var frame = dbg.getNewestFrame();
+      assertEq(frame.implementation, "ion");
+      // x is unused and should be elided.
+      assertEq(frame.environment.getVariable("x").optimizedOut, true);
+      assertEq(frame.arguments[1].optimizedOut, true);
+    }
+  };
+
+  g.eval("" + function f(d, x) { g(d, x); });
+
+  g.eval("" + function g(d, x) {
+    for (var i = 0; i < 200; i++);
+    // Hack to prevent inlining.
+    function inner() { i = 42; };
+    toggle(d);
+  });
+
+  g.eval("(" + function test() {
+    for (i = 0; i < 5; i++)
+      f(false, 42);
+    f(true, 42);
+  } + ")();");
+});
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/resumption-08.js
@@ -0,0 +1,93 @@
+// Check whether we respect resumption values when toggling debug mode on->off
+// from various points with live scripts on the stack.
+
+var g = newGlobal();
+var dbg = new Debugger;
+
+function reset() {
+  dbg.onEnterFrame = undefined;
+  dbg.onDebuggerStatement = undefined;
+  dbg.addDebuggee(g);
+  g.eval("(" + function test() {
+    for (i = 0; i < 5; i++)
+      f(42);
+  } + ")();");
+}
+
+g.eval("" + function f(d) {
+  return g(d);
+});
+
+g.eval("" + function g(d) {
+  debugger;
+  return d;
+});
+
+function testResumptionValues(handlerSetter) {
+  // Test normal return.
+  reset();
+  handlerSetter(undefined);
+  assertEq(g.eval("(" + function test() { return f(42); } + ")();"), 42);
+
+  // Test forced return.
+  reset();
+  handlerSetter({ return: "not 42" });
+  assertEq(g.eval("(" + function test() { return f(42); } + ")();"), "not 42");
+
+  // Test throw.
+  reset();
+  handlerSetter({ throw: "thrown 42" });
+  try {
+    g.eval("(" + function test() { return f(42); } + ")();");;
+  } catch (e) {
+    assertEq(e, "thrown 42");
+  }
+}
+
+// Turn off from within the prologue.
+testResumptionValues(function (resumptionVal) {
+  dbg.onEnterFrame = function (frame) {
+    if (frame.older) {
+      if (frame.older.older) {
+        dbg.removeDebuggee(g);
+        return resumptionVal;
+      }
+    }
+  };
+});
+
+// Turn off from within the epilogue.
+testResumptionValues(function (resumptionVal) {
+  dbg.onEnterFrame = function (frame) {
+    if (frame.older) {
+      if (frame.older.older) {
+        frame.onPop = function () {
+          dbg.removeDebuggee(g);
+          return resumptionVal;
+        };
+      }
+    }
+  };
+});
+
+// Turn off from within debugger statement handler.
+testResumptionValues(function (resumptionVal) {
+  dbg.onDebuggerStatement = function (frame) {
+    dbg.removeDebuggee(g);
+    return resumptionVal;
+  };
+});
+
+// Turn off from within debug trap handler.
+testResumptionValues(function (resumptionVal) {
+  dbg.onEnterFrame = function (frame) {
+    if (frame.older) {
+      if (frame.older.older) {
+        frame.onStep = function () {
+          dbg.removeDebuggee(g);
+          return resumptionVal;
+        }
+      }
+    }
+  };
+});
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6106,16 +6106,25 @@ JS_SetGlobalJitCompilerOption(JSRuntime 
         if (value == 1) {
             JS::RuntimeOptionsRef(rt).setBaseline(true);
             IonSpew(js::jit::IonSpew_BaselineScripts, "Enable baseline");
         } else if (value == 0) {
             JS::RuntimeOptionsRef(rt).setBaseline(false);
             IonSpew(js::jit::IonSpew_BaselineScripts, "Disable baseline");
         }
         break;
+      case JSJITCOMPILER_PARALLEL_COMPILATION_ENABLE:
+        if (value == 1) {
+            rt->setParallelIonCompilationEnabled(true);
+            IonSpew(js::jit::IonSpew_Scripts, "Enable parallel compilation");
+        } else if (value == 0) {
+            rt->setParallelIonCompilationEnabled(false);
+            IonSpew(js::jit::IonSpew_Scripts, "Disable parallel compilation");
+        }
+        break;
       default:
         break;
     }
 #endif
 }
 
 JS_PUBLIC_API(int)
 JS_GetGlobalJitCompilerOption(JSRuntime *rt, JSJitCompilerOption opt)
@@ -6125,16 +6134,18 @@ JS_GetGlobalJitCompilerOption(JSRuntime 
       case JSJITCOMPILER_BASELINE_USECOUNT_TRIGGER:
         return jit::js_JitOptions.baselineUsesBeforeCompile;
       case JSJITCOMPILER_ION_USECOUNT_TRIGGER:
         return jit::js_JitOptions.forcedDefaultIonUsesBeforeCompile;
       case JSJITCOMPILER_ION_ENABLE:
         return JS::RuntimeOptionsRef(rt).ion();
       case JSJITCOMPILER_BASELINE_ENABLE:
         return JS::RuntimeOptionsRef(rt).baseline();
+      case JSJITCOMPILER_PARALLEL_COMPILATION_ENABLE:
+        return rt->canUseParallelIonCompilation();
       default:
         break;
     }
 #endif
     return 0;
 }
 
 /************************************************************************/
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4626,21 +4626,22 @@ JS_ScheduleGC(JSContext *cx, uint32_t co
 #endif
 
 extern JS_PUBLIC_API(void)
 JS_SetParallelParsingEnabled(JSRuntime *rt, bool enabled);
 
 extern JS_PUBLIC_API(void)
 JS_SetParallelIonCompilationEnabled(JSRuntime *rt, bool enabled);
 
-#define JIT_COMPILER_OPTIONS(Register)                             \
-  Register(BASELINE_USECOUNT_TRIGGER, "baseline.usecount.trigger") \
-  Register(ION_USECOUNT_TRIGGER, "ion.usecount.trigger")           \
-  Register(ION_ENABLE, "ion.enable")                               \
-  Register(BASELINE_ENABLE, "baseline.enable")
+#define JIT_COMPILER_OPTIONS(Register)                                  \
+    Register(BASELINE_USECOUNT_TRIGGER, "baseline.usecount.trigger")    \
+    Register(ION_USECOUNT_TRIGGER, "ion.usecount.trigger")              \
+    Register(ION_ENABLE, "ion.enable")                                  \
+    Register(BASELINE_ENABLE, "baseline.enable")                        \
+    Register(PARALLEL_COMPILATION_ENABLE, "parallel-compilation.enable")
 
 typedef enum JSJitCompilerOption {
 #define JIT_COMPILER_DECLARE(key, str) \
     JSJITCOMPILER_ ## key,
 
     JIT_COMPILER_OPTIONS(JIT_COMPILER_DECLARE)
 #undef JIT_COMPILER_DECLARE