Bug 716647 - Part 6: Tests. (r=jimb)
authorShu-yu Guo <shu@rfrn.org>
Thu, 24 Apr 2014 01:59:38 -0700
changeset 180268 a49c8cdf14c43d0b69993be83eff5df5e8de36e4
parent 180267 0e61e88866ceacf80007481e37a23427e6b1efa4
child 180269 079f4f0ed6a6ebbbdbc985ed5799c066f50759f4
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersjimb
bugs716647
milestone31.0a1
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