Bug 716647 - Part 6: Tests. (r=jimb)

This commit is contained in:
Shu-yu Guo 2014-04-24 01:59:38 -07:00
parent 51af97477a
commit 34f8bd3c53
17 changed files with 763 additions and 5 deletions

View File

@ -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
};

View File

@ -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);
} + ")();");

View File

@ -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);
} + ")();");
})();

View File

@ -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(); } + ")();");

View File

@ -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(); } + ")();");

View File

@ -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);
}
});

View File

@ -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);
} + ")();");
});

View File

@ -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);
});

View File

@ -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"); } + ")();");
});

View File

@ -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);
} + ")();");
});

View File

@ -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);
} + ")();");
});

View File

@ -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);
});

View File

@ -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);
});

View File

@ -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);
} + ")();");
});

View File

@ -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;
}
}
}
};
});

View File

@ -6111,6 +6111,15 @@ JS_SetGlobalJitCompilerOption(JSRuntime *rt, JSJitCompilerOption opt, uint32_t v
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;
}
@ -6130,6 +6139,8 @@ JS_GetGlobalJitCompilerOption(JSRuntime *rt, JSJitCompilerOption opt)
return JS::RuntimeOptionsRef(rt).ion();
case JSJITCOMPILER_BASELINE_ENABLE:
return JS::RuntimeOptionsRef(rt).baseline();
case JSJITCOMPILER_PARALLEL_COMPILATION_ENABLE:
return rt->canUseParallelIonCompilation();
default:
break;
}

View File

@ -4631,11 +4631,12 @@ 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) \