Bug 1541404 part 13 - Add some code to support entering the interpreter and triggering Baseline compilation from the interpreter. r=tcampbell

The script->incWarmUpCounter() calls are moved out of the CanEnterBaseline functions
and into the callers. This makes it easier to reason about and prevents incrementing it
multiple times for the different tiers/flags.

baselineWarmUpThreshold should be renamed to baseline{Jit,Compiler}WarmUpThreshold,
but that will happen later with other prefs-related changes.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jan de Mooij 2019-04-17 17:26:59 +00:00
parent 4ef4fd39da
commit 34cabb31fa
9 changed files with 165 additions and 27 deletions

View File

@ -136,18 +136,22 @@ JitExecStatus jit::EnterBaselineAtBranch(JSContext* cx, InterpreterFrame* fp,
jsbytecode* pc) {
MOZ_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY);
BaselineScript* baseline = fp->script()->baselineScript();
EnterJitData data(cx);
PCMappingSlotInfo slotInfo;
data.jitcode = baseline->nativeCodeForPC(fp->script(), pc, &slotInfo);
MOZ_ASSERT(slotInfo.isStackSynced());
// Skip debug breakpoint/trap handler, the interpreter already handled it
// for the current op.
if (fp->isDebuggee()) {
MOZ_RELEASE_ASSERT(baseline->hasDebugInstrumentation());
data.jitcode += MacroAssembler::ToggledCallSize(data.jitcode);
if (fp->script()->hasBaselineScript()) {
BaselineScript* baseline = fp->script()->baselineScript();
PCMappingSlotInfo slotInfo;
data.jitcode = baseline->nativeCodeForPC(fp->script(), pc, &slotInfo);
MOZ_ASSERT(slotInfo.isStackSynced());
// Skip debug breakpoint/trap handler, the interpreter already handled it
// for the current op.
if (fp->isDebuggee()) {
MOZ_RELEASE_ASSERT(baseline->hasDebugInstrumentation());
data.jitcode += MacroAssembler::ToggledCallSize(data.jitcode);
}
} else {
MOZ_CRASH("NYI: Interpreter executeOp code");
}
// Note: keep this in sync with SetEnterJitData.
@ -231,7 +235,7 @@ MethodStatus jit::BaselineCompile(JSContext* cx, JSScript* script,
}
static MethodStatus CanEnterBaselineJIT(JSContext* cx, HandleScript script,
InterpreterFrame* osrSourceFrame) {
AbstractFramePtr osrSourceFrame) {
MOZ_ASSERT(jit::IsBaselineEnabled(cx));
// Skip if the script has been disabled.
@ -259,7 +263,7 @@ static MethodStatus CanEnterBaselineJIT(JSContext* cx, HandleScript script,
// already compiled in baseline, execution jumps directly into baseline
// code. This is incorrect as h's baseline script does not have debug
// instrumentation.
if (osrSourceFrame && osrSourceFrame->isDebuggee() &&
if (osrSourceFrame && osrSourceFrame.isDebuggee() &&
!Debugger::ensureExecutionObservabilityOfOsrFrame(cx, osrSourceFrame)) {
return Method_Error;
}
@ -277,7 +281,7 @@ static MethodStatus CanEnterBaselineJIT(JSContext* cx, HandleScript script,
}
// Check script warm-up counter.
if (script->incWarmUpCounter() <= JitOptions.baselineWarmUpThreshold) {
if (script->getWarmUpCount() <= JitOptions.baselineWarmUpThreshold) {
return Method_Skipped;
}
@ -295,10 +299,38 @@ static MethodStatus CanEnterBaselineJIT(JSContext* cx, HandleScript script,
// script being a debuggee script, e.g., when performing
// Debugger.Frame.prototype.eval.
bool forceDebugInstrumentation =
osrSourceFrame && osrSourceFrame->isDebuggee();
osrSourceFrame && osrSourceFrame.isDebuggee();
return BaselineCompile(cx, script, forceDebugInstrumentation);
}
static MethodStatus CanEnterBaselineInterpreter(JSContext* cx,
HandleScript script) {
MOZ_ASSERT(jit::IsBaselineEnabled(cx));
MOZ_ASSERT(JitOptions.baselineInterpreter);
if (script->types()) {
return Method_Compiled;
}
// Check script warm-up counter.
if (script->getWarmUpCount() <=
JitOptions.baselineInterpreterWarmUpThreshold) {
return Method_Skipped;
}
if (!cx->realm()->ensureJitRealmExists(cx)) {
return Method_Error;
}
AutoKeepTypeScripts keepTypes(cx);
if (!script->ensureHasTypes(cx, keepTypes)) {
return Method_Error;
}
return Method_Compiled;
}
template <BaselineTier Tier>
MethodStatus jit::CanEnterBaselineAtBranch(JSContext* cx,
InterpreterFrame* fp) {
if (!CheckFrame(fp)) {
@ -306,9 +338,23 @@ MethodStatus jit::CanEnterBaselineAtBranch(JSContext* cx,
}
RootedScript script(cx, fp->script());
return CanEnterBaselineJIT(cx, script, fp);
switch (Tier) {
case BaselineTier::Interpreter:
return CanEnterBaselineInterpreter(cx, script);
case BaselineTier::Compiler:
return CanEnterBaselineJIT(cx, script, fp);
}
MOZ_CRASH("Unexpected tier");
}
template MethodStatus jit::CanEnterBaselineAtBranch<BaselineTier::Interpreter>(
JSContext* cx, InterpreterFrame* fp);
template MethodStatus jit::CanEnterBaselineAtBranch<BaselineTier::Compiler>(
JSContext* cx, InterpreterFrame* fp);
template <BaselineTier Tier>
MethodStatus jit::CanEnterBaselineMethod(JSContext* cx, RunState& state) {
if (state.isInvoke()) {
InvokeState& invoke = *state.asInvoke();
@ -325,8 +371,58 @@ MethodStatus jit::CanEnterBaselineMethod(JSContext* cx, RunState& state) {
}
RootedScript script(cx, state.script());
return CanEnterBaselineJIT(cx, script, /* osrSourceFrame = */ nullptr);
};
switch (Tier) {
case BaselineTier::Interpreter:
return CanEnterBaselineInterpreter(cx, script);
case BaselineTier::Compiler:
return CanEnterBaselineJIT(cx, script,
/* osrSourceFrame = */ NullFramePtr());
}
MOZ_CRASH("Unexpected tier");
}
template MethodStatus jit::CanEnterBaselineMethod<BaselineTier::Interpreter>(
JSContext* cx, RunState& state);
template MethodStatus jit::CanEnterBaselineMethod<BaselineTier::Compiler>(
JSContext* cx, RunState& state);
bool jit::BaselineCompileFromBaselineInterpreter(JSContext* cx,
BaselineFrame* frame,
uint8_t** res) {
MOZ_ASSERT(frame->runningInInterpreter());
RootedScript script(cx, frame->script());
jsbytecode* pc = frame->interpreterPC();
MOZ_ASSERT(pc == script->code() || *pc == JSOP_LOOPENTRY);
MethodStatus status = CanEnterBaselineJIT(cx, script,
/* osrSourceFrame = */ frame);
switch (status) {
case Method_Error:
return false;
case Method_CantCompile:
case Method_Skipped:
*res = nullptr;
return true;
case Method_Compiled: {
if (*pc == JSOP_LOOPENTRY) {
PCMappingSlotInfo slotInfo;
*res = script->baselineScript()->nativeCodeForPC(script, pc, &slotInfo);
MOZ_ASSERT(slotInfo.isStackSynced());
} else {
*res = script->baselineScript()->warmUpCheckPrologueAddr();
}
frame->prepareForBaselineInterpreterToJitOSR();
return true;
}
}
MOZ_CRASH("Unexpected status");
}
BaselineScript* BaselineScript::New(
JSScript* jsscript, uint32_t bailoutPrologueOffset,

View File

@ -555,13 +555,23 @@ inline bool IsBaselineEnabled(JSContext* cx) {
#endif
}
enum class BaselineTier { Interpreter, Compiler };
template <BaselineTier Tier>
MethodStatus CanEnterBaselineMethod(JSContext* cx, RunState& state);
template <BaselineTier Tier>
MethodStatus CanEnterBaselineAtBranch(JSContext* cx, InterpreterFrame* fp);
JitExecStatus EnterBaselineAtBranch(JSContext* cx, InterpreterFrame* fp,
jsbytecode* pc);
// Called by the Baseline Interpreter to compile a script for the Baseline JIT.
// |res| is set to the native code address in the BaselineScript to jump to, or
// nullptr if we were unable to compile this script.
bool BaselineCompileFromBaselineInterpreter(JSContext* cx, BaselineFrame* frame,
uint8_t** res);
void FinishDiscardBaselineScript(FreeOp* fop, JSScript* script);
void AddSizeOfBaselineData(JSScript* script, mozilla::MallocSizeOf mallocSizeOf,

View File

@ -2353,7 +2353,8 @@ MethodStatus jit::CanEnterIon(JSContext* cx, RunState& state) {
// If --ion-eager is used, compile with Baseline first, so that we
// can directly enter IonMonkey.
if (JitOptions.eagerIonCompilation() && !script->hasBaselineScript()) {
MethodStatus status = CanEnterBaselineMethod(cx, state);
MethodStatus status =
CanEnterBaselineMethod<BaselineTier::Compiler>(cx, state);
if (status != Method_Compiled) {
return status;
}

View File

@ -138,6 +138,8 @@ EnterJitStatus js::jit::MaybeEnterJit(JSContext* cx, RunState& state) {
break;
}
script->incWarmUpCounter();
// Try to Ion-compile.
if (jit::IsIonEnabled(cx)) {
jit::MethodStatus status = jit::CanEnterIon(cx, state);
@ -152,7 +154,8 @@ EnterJitStatus js::jit::MaybeEnterJit(JSContext* cx, RunState& state) {
// Try to Baseline-compile.
if (jit::IsBaselineEnabled(cx)) {
jit::MethodStatus status = jit::CanEnterBaselineMethod(cx, state);
jit::MethodStatus status =
jit::CanEnterBaselineMethod<BaselineTier::Compiler>(cx, state);
if (status == jit::Method_Error) {
return EnterJitStatus::Error;
}
@ -160,6 +163,18 @@ EnterJitStatus js::jit::MaybeEnterJit(JSContext* cx, RunState& state) {
code = script->jitCodeRaw();
break;
}
if (JitOptions.baselineInterpreter) {
jit::MethodStatus status =
jit::CanEnterBaselineMethod<BaselineTier::Interpreter>(cx, state);
if (status == jit::Method_Error) {
return EnterJitStatus::Error;
}
if (status == jit::Method_Compiled) {
code = script->jitCodeRaw();
break;
}
}
}
return EnterJitStatus::NotEntered;

View File

@ -140,6 +140,9 @@ DefaultJitOptions::DefaultJitOptions() {
// disabled.
SET_DEFAULT(disableOptimizationLevels, false);
// Whether the Baseline Interpreter is enabled.
SET_DEFAULT(baselineInterpreter, false);
// Whether IonBuilder should prefer IC generation above specialized MIR.
SET_DEFAULT(forceInlineCaches, false);
@ -152,6 +155,10 @@ DefaultJitOptions::DefaultJitOptions() {
// Whether to enable extra code to perform dynamic validations.
SET_DEFAULT(runExtraChecks, false);
// How many invocations or loop iterations are needed before functions
// enter the Baseline Interpreter.
SET_DEFAULT(baselineInterpreterWarmUpThreshold, 10);
// How many invocations or loop iterations are needed before functions
// are compiled with the baseline compiler.
// Duplicated in all.js - ensure both match.

View File

@ -66,6 +66,7 @@ struct DefaultJitOptions {
bool disableSincos;
bool disableSink;
bool disableOptimizationLevels;
bool baselineInterpreter;
bool forceInlineCaches;
bool fullDebugChecks;
bool limitScriptSize;
@ -82,6 +83,7 @@ struct DefaultJitOptions {
bool enableWasmImportCallSpew;
bool enableWasmFuncCallSpew;
#endif
uint32_t baselineInterpreterWarmUpThreshold;
uint32_t baselineWarmUpThreshold;
uint32_t normalIonWarmUpThreshold;
uint32_t fullIonWarmUpThreshold;

View File

@ -3034,14 +3034,14 @@ bool Debugger::ensureExecutionObservabilityOfScript(JSContext* cx,
}
/* static */
bool Debugger::ensureExecutionObservabilityOfOsrFrame(JSContext* cx,
InterpreterFrame* frame) {
MOZ_ASSERT(frame->isDebuggee());
if (frame->script()->hasBaselineScript() &&
frame->script()->baselineScript()->hasDebugInstrumentation()) {
bool Debugger::ensureExecutionObservabilityOfOsrFrame(
JSContext* cx, AbstractFramePtr osrSourceFrame) {
MOZ_ASSERT(osrSourceFrame.isDebuggee());
if (osrSourceFrame.script()->hasBaselineScript() &&
osrSourceFrame.script()->baselineScript()->hasDebugInstrumentation()) {
return true;
}
ExecutionObservableFrame obs(frame);
ExecutionObservableFrame obs(osrSourceFrame);
return updateExecutionObservabilityOfFrames(cx, obs, Observing);
}

View File

@ -818,7 +818,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger> {
public:
static MOZ_MUST_USE bool ensureExecutionObservabilityOfOsrFrame(
JSContext* cx, InterpreterFrame* frame);
JSContext* cx, AbstractFramePtr osrSourceFrame);
// Public for DebuggerScript_setBreakpoint.
static MOZ_MUST_USE bool ensureExecutionObservabilityOfScript(

View File

@ -1966,7 +1966,14 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER bool Interpret(JSContext* cx,
COUNT_COVERAGE();
// Attempt on-stack replacement with Baseline code.
if (jit::IsBaselineEnabled(cx)) {
jit::MethodStatus status = jit::CanEnterBaselineAtBranch(cx, REGS.fp());
script->incWarmUpCounter();
using Tier = jit::BaselineTier;
jit::MethodStatus status =
jit::JitOptions.baselineInterpreter
? jit::CanEnterBaselineAtBranch<Tier::Interpreter>(cx,
REGS.fp())
: jit::CanEnterBaselineAtBranch<Tier::Compiler>(cx, REGS.fp());
if (status == jit::Method_Error) {
goto error;
}