Bug 1617666 - Use a separate Debugger to improve performance of eval. r=jlast

The SpiderMonkey Debugger API maintains a flag per-debuggee that tracks whether
the SpiderMonkey should notify the debugger when new frames are entered, and
whether the JIT scripts associated with the debuggee realm have been compiled
with debug instrumentation. To avoid constantly needing to clear and regenerate
JIT scripts, the flag is permanently enabled once "onEnterFrame" has been used
with a given Debugger instance, and when the flag is enabled, there is a
runtime performance cost.

This runtime cost is what is causing the performance regression here, so to
ensure that the flag is cleared at the end of a given instant-eval, we only
set "onEnterFrame" on a new temporary Debugger instance, instead of setting
it on th existing persistent Debugger instance.

Differential Revision: https://phabricator.services.mozilla.com/D64912

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Logan Smyth 2020-03-02 22:55:28 +00:00
Родитель 411af42272
Коммит cde4370d90
1 изменённых файлов: 30 добавлений и 28 удалений

Просмотреть файл

@ -5,6 +5,7 @@
"use strict";
const Debugger = require("Debugger");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
loader.lazyRequireGetter(
@ -162,7 +163,7 @@ exports.evalWithDebugger = function(string, options = {}, webConsole) {
);
if (options.eager) {
allowSideEffects(dbg, sideEffectData);
allowSideEffects(sideEffectData);
}
// Attempt to initialize any declarations found in the evaluated string
@ -268,17 +269,17 @@ function preventSideEffects(dbg) {
throw new Error("Debugger has hook installed");
}
const data = {
executedScripts: new Set(),
debuggees: dbg.getDebuggees(),
handler: {
hit: () => null,
},
};
// Note: It is critical for debuggee performance that we implement all of
// this debuggee tracking logic with a separate Debugger instance.
// Bug 1617666 arises otherwise if we set an onEnterFrame hook on the
// existing debugger object and then later clear it.
const newDbg = new Debugger();
for (const debuggee of dbg.getDebuggees()) {
newDbg.addDebuggee(debuggee.unsafeDereference());
}
// TODO: re-enable addAllGlobalsAsDebuggees(bug #1610532)
// dbg.addAllGlobalsAsDebuggees();
// newDbg.addAllGlobalsAsDebuggees();
const timeoutDuration = 100;
const endTime = Date.now() + timeoutDuration;
@ -290,7 +291,12 @@ function preventSideEffects(dbg) {
return ++count % 100 === 0 && Date.now() > endTime;
}
dbg.onEnterFrame = frame => {
const executedScripts = new Set();
const handler = {
hit: () => null,
};
newDbg.onEnterFrame = frame => {
if (shouldCancel()) {
return null;
}
@ -303,19 +309,22 @@ function preventSideEffects(dbg) {
const script = frame.script;
if (data.executedScripts.has(script)) {
if (executedScripts.has(script)) {
return undefined;
}
data.executedScripts.add(script);
executedScripts.add(script);
const offsets = script.getEffectfulOffsets();
for (const offset of offsets) {
script.setBreakpoint(offset, data.handler);
script.setBreakpoint(offset, handler);
}
return undefined;
};
// The debugger only calls onNativeCall handlers on the debugger that is
// explicitly calling eval, so we need to add this hook on "dbg" even though
// the rest of our hooks work via "newDbg".
dbg.onNativeCall = (callee, reason) => {
// Getters are never considered effectful, and setters are always effectful.
// Natives called normally are handled with a whitelist.
@ -330,22 +339,15 @@ function preventSideEffects(dbg) {
return null;
};
return data;
return {
dbg,
newDbg,
};
}
function allowSideEffects(dbg, data) {
for (const script of data.executedScripts) {
script.clearBreakpoint(data.handler);
}
for (const global of dbg.getDebuggees()) {
if (!data.debuggees.includes(global)) {
dbg.removeDebuggee(global);
}
}
dbg.onEnterFrame = undefined;
dbg.onNativeCall = undefined;
function allowSideEffects(data) {
data.dbg.onNativeCall = undefined;
data.newDbg.removeAllDebuggees();
}
// Native functions which are considered to be side effect free.