diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index a2122f8e7498..1c81a1f4079d 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -5660,7 +5660,9 @@ bool BaselineCodeGen::emit_JSOP_FINALYIELDRVAL() { template <> void BaselineCompilerCodeGen::emitJumpToInterpretOpLabel() { - MOZ_CRASH("NYI: Interpreter emitJumpToInterpretOpLabel"); + TrampolinePtr code = + cx->runtime()->jitRuntime()->baselineInterpreter().interpretOpAddr(); + masm.jump(code); } template <> diff --git a/js/src/jit/BaselineJIT.cpp b/js/src/jit/BaselineJIT.cpp index 84749d01128b..3dbc082519eb 100644 --- a/js/src/jit/BaselineJIT.cpp +++ b/js/src/jit/BaselineJIT.cpp @@ -151,7 +151,9 @@ JitExecStatus jit::EnterBaselineAtBranch(JSContext* cx, InterpreterFrame* fp, data.jitcode += MacroAssembler::ToggledCallSize(data.jitcode); } } else { - MOZ_CRASH("NYI: Interpreter executeOp code"); + const BaselineInterpreter& interp = + cx->runtime()->jitRuntime()->baselineInterpreter(); + data.jitcode = interp.interpretOpAddr().value; } // Note: keep this in sync with SetEnterJitData. @@ -1080,6 +1082,23 @@ void BaselineScript::toggleTraceLoggerEngine(bool enable) { } #endif +static void ToggleProfilerInstrumentation(JitCode* code, + uint32_t profilerEnterToggleOffset, + uint32_t profilerExitToggleOffset, + bool enable) { + CodeLocationLabel enterToggleLocation(code, + CodeOffset(profilerEnterToggleOffset)); + CodeLocationLabel exitToggleLocation(code, + CodeOffset(profilerExitToggleOffset)); + if (enable) { + Assembler::ToggleToCmp(enterToggleLocation); + Assembler::ToggleToCmp(exitToggleLocation); + } else { + Assembler::ToggleToJmp(enterToggleLocation); + Assembler::ToggleToJmp(exitToggleLocation); + } +} + void BaselineScript::toggleProfilerInstrumentation(bool enable) { if (enable == isProfilerInstrumentationOn()) { return; @@ -1088,22 +1107,49 @@ void BaselineScript::toggleProfilerInstrumentation(bool enable) { JitSpew(JitSpew_BaselineIC, " toggling profiling %s for BaselineScript %p", enable ? "on" : "off", this); - // Toggle the jump - CodeLocationLabel enterToggleLocation(method_, - CodeOffset(profilerEnterToggleOffset_)); - CodeLocationLabel exitToggleLocation(method_, - CodeOffset(profilerExitToggleOffset_)); + ToggleProfilerInstrumentation(method_, profilerEnterToggleOffset_, + profilerExitToggleOffset_, enable); + if (enable) { - Assembler::ToggleToCmp(enterToggleLocation); - Assembler::ToggleToCmp(exitToggleLocation); flags_ |= uint32_t(PROFILER_INSTRUMENTATION_ON); } else { - Assembler::ToggleToJmp(enterToggleLocation); - Assembler::ToggleToJmp(exitToggleLocation); flags_ &= ~uint32_t(PROFILER_INSTRUMENTATION_ON); } } +void BaselineInterpreter::toggleProfilerInstrumentation(bool enable) { + if (!JitOptions.baselineInterpreter) { + return; + } + + AutoWritableJitCode awjc(code_); + ToggleProfilerInstrumentation(code_, profilerEnterToggleOffset_, + profilerExitToggleOffset_, enable); +} + +void BaselineInterpreter::toggleDebuggerInstrumentation(bool enable) { + if (!JitOptions.baselineInterpreter) { + return; + } + + AutoWritableJitCode awjc(code_); + + // Toggle prologue IsDebuggeeCheck code. + CodeLocationLabel debuggeeCheckLocation(code_, + CodeOffset(debuggeeCheckOffset_)); + if (enable) { + Assembler::ToggleToCmp(debuggeeCheckLocation); + } else { + Assembler::ToggleToJmp(debuggeeCheckLocation); + } + + // Toggle DebugTrapHandler calls. + for (uint32_t offset : debugTrapOffsets_) { + CodeLocationLabel trapLocation(code_, CodeOffset(offset)); + Assembler::ToggleCall(trapLocation, enable); + } +} + void ICScript::purgeOptimizedStubs(JSScript* script) { MOZ_ASSERT(script->icScript() == this); @@ -1277,6 +1323,8 @@ void jit::ToggleBaselineProfiling(JSRuntime* runtime, bool enable) { return; } + jrt->baselineInterpreter().toggleProfilerInstrumentation(enable); + for (ZonesIter zone(runtime, SkipAtoms); !zone.done(); zone.next()) { for (auto script = zone->cellIter(); !script.done(); script.next()) { @@ -1365,3 +1413,26 @@ void jit::MarkActiveTypeScripts(Zone* zone) { } } } + +void BaselineInterpreter::init(JitCode* code, uint32_t interpretOpOffset, + uint32_t profilerEnterToggleOffset, + uint32_t profilerExitToggleOffset, + uint32_t debuggeeCheckOffset, + DebugTrapOffsets&& debugTrapOffsets) { + code_ = code; + interpretOpOffset_ = interpretOpOffset; + profilerEnterToggleOffset_ = profilerEnterToggleOffset; + profilerExitToggleOffset_ = profilerExitToggleOffset; + debuggeeCheckOffset_ = debuggeeCheckOffset; + debugTrapOffsets_ = std::move(debugTrapOffsets); +} + +bool jit::GenerateBaselineInterpreter(JSContext* cx, + BaselineInterpreter& interpreter) { + // Temporary JitOptions check to prevent crashes for now. + if (JitOptions.baselineInterpreter) { + MOZ_CRASH("NYI: GenerateBaselineInterpreter"); + } + + return true; +} diff --git a/js/src/jit/BaselineJIT.h b/js/src/jit/BaselineJIT.h index 580c5d23d8b4..05577cd0f0f9 100644 --- a/js/src/jit/BaselineJIT.h +++ b/js/src/jit/BaselineJIT.h @@ -651,6 +651,51 @@ void JitSpewBaselineICStats(JSScript* script, const char* dumpReason); static const unsigned BASELINE_MAX_ARGS_LENGTH = 20000; +// Class storing the generated Baseline Interpreter code for the runtime. +class BaselineInterpreter { + // The interpreter code. + JitCode* code_ = nullptr; + + // Offset of the code to start interpreting a bytecode op. + uint32_t interpretOpOffset_ = 0; + + // The offsets for the toggledJump instructions for profiler instrumentation. + uint32_t profilerEnterToggleOffset_ = 0; + uint32_t profilerExitToggleOffset_ = 0; + + // The offset for the toggledJump instruction for the debugger's + // IsDebuggeeCheck code in the prologue. + uint32_t debuggeeCheckOffset_ = 0; + + // Offsets of toggled calls to the DebugTrapHandler trampoline (for + // breakpoints and stepping). + using DebugTrapOffsets = js::Vector; + DebugTrapOffsets debugTrapOffsets_; + + public: + BaselineInterpreter() = default; + + BaselineInterpreter(const BaselineInterpreter&) = delete; + void operator=(const BaselineInterpreter&) = delete; + + void init(JitCode* code, uint32_t interpretOpOffset, + uint32_t profilerEnterToggleOffset, + uint32_t profilerExitToggleOffset, uint32_t debuggeeCheckOffset, + DebugTrapOffsets&& debugTrapOffsets); + + uint8_t* codeRaw() const { return code_->raw(); } + + TrampolinePtr interpretOpAddr() const { + return TrampolinePtr(codeRaw() + interpretOpOffset_); + } + + void toggleProfilerInstrumentation(bool enable); + void toggleDebuggerInstrumentation(bool enable); +}; + +MOZ_MUST_USE bool GenerateBaselineInterpreter(JSContext* cx, + BaselineInterpreter& interpreter); + } // namespace jit } // namespace js diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index db0253429d69..8e5e52acfea2 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -166,6 +166,7 @@ JitRuntime::JitRuntime() doubleToInt32ValueStubOffset_(0), debugTrapHandler_(nullptr), baselineDebugModeOSRHandler_(nullptr), + baselineInterpreter_(), trampolineCode_(nullptr), jitcodeGlobalTable_(nullptr), #ifdef DEBUG @@ -213,6 +214,10 @@ bool JitRuntime::initialize(JSContext* cx) { return false; } + if (!GenerateBaselineInterpreter(cx, baselineInterpreter_)) { + return false; + } + return true; } diff --git a/js/src/jit/Jit.cpp b/js/src/jit/Jit.cpp index 267fa956aa28..ed07a7136654 100644 --- a/js/src/jit/Jit.cpp +++ b/js/src/jit/Jit.cpp @@ -20,8 +20,11 @@ using namespace js::jit; static EnterJitStatus JS_HAZ_JSNATIVE_CALLER EnterJit(JSContext* cx, RunState& state, uint8_t* code) { - MOZ_ASSERT(state.script()->hasBaselineScript()); + // We don't want to call the interpreter stub here (because + // C++ -> interpreterStub -> C++ is slower than staying in C++). MOZ_ASSERT(code); + MOZ_ASSERT(code != cx->runtime()->jitRuntime()->interpreterStub().value); + MOZ_ASSERT(IsBaselineEnabled(cx)); if (!CheckRecursionLimit(cx)) { @@ -55,7 +58,11 @@ static EnterJitStatus JS_HAZ_JSNATIVE_CALLER EnterJit(JSContext* cx, if (numActualArgs > BASELINE_MAX_ARGS_LENGTH) { return EnterJitStatus::NotEntered; } - code = script->baselineScript()->method()->raw(); + if (script->hasBaselineScript()) { + code = script->baselineScript()->method()->raw(); + } else { + code = cx->runtime()->jitRuntime()->baselineInterpreter().codeRaw(); + } } constructing = state.asInvoke()->constructing(); @@ -131,11 +138,16 @@ EnterJitStatus js::jit::MaybeEnterJit(JSContext* cx, RunState& state) { uint8_t* code = script->jitCodeRaw(); do { - // Make sure we have a BaselineScript: we don't want to call the - // interpreter stub here. Note that Baseline code contains warm-up - // checks in the prologue to Ion-compile if needed. - if (script->hasBaselineScript()) { - break; + // Make sure we can enter Baseline Interpreter or JIT code. Note that + // the prologue has warm-up checks to tier up if needed. + if (JitOptions.baselineInterpreter) { + if (script->types()) { + break; + } + } else { + if (script->hasBaselineScript()) { + break; + } } script->incWarmUpCounter(); diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index d65206d93fa4..612f72f0f809 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -376,21 +376,35 @@ static bool ProcessTryNotesBaseline(JSContext* cx, const JSJitFrameIter& frame, script->resetWarmUpCounter(); // Resume at the start of the catch block. - PCMappingSlotInfo slotInfo; rfe->kind = ResumeFromException::RESUME_CATCH; - rfe->target = - script->baselineScript()->nativeCodeForPC(script, *pc, &slotInfo); - MOZ_ASSERT(slotInfo.isStackSynced()); + if (frame.baselineFrame()->runningInInterpreter()) { + const BaselineInterpreter& interp = + cx->runtime()->jitRuntime()->baselineInterpreter(); + frame.baselineFrame()->setInterpreterPC(*pc); + rfe->target = interp.interpretOpAddr().value; + } else { + PCMappingSlotInfo slotInfo; + rfe->target = + script->baselineScript()->nativeCodeForPC(script, *pc, &slotInfo); + MOZ_ASSERT(slotInfo.isStackSynced()); + } return true; } case JSTRY_FINALLY: { - PCMappingSlotInfo slotInfo; SettleOnTryNote(cx, tn, frame, ei, rfe, pc); rfe->kind = ResumeFromException::RESUME_FINALLY; - rfe->target = - script->baselineScript()->nativeCodeForPC(script, *pc, &slotInfo); - MOZ_ASSERT(slotInfo.isStackSynced()); + if (frame.baselineFrame()->runningInInterpreter()) { + const BaselineInterpreter& interp = + cx->runtime()->jitRuntime()->baselineInterpreter(); + frame.baselineFrame()->setInterpreterPC(*pc); + rfe->target = interp.interpretOpAddr().value; + } else { + PCMappingSlotInfo slotInfo; + rfe->target = + script->baselineScript()->nativeCodeForPC(script, *pc, &slotInfo); + MOZ_ASSERT(slotInfo.isStackSynced()); + } // Drop the exception instead of leaking cross compartment data. if (!cx->getPendingException( MutableHandleValue::fromMarkedLocation(&rfe->exception))) { diff --git a/js/src/jit/JitRealm.h b/js/src/jit/JitRealm.h index 61cbfe280cfe..f494d2faa99e 100644 --- a/js/src/jit/JitRealm.h +++ b/js/src/jit/JitRealm.h @@ -16,6 +16,7 @@ #include "builtin/TypedObject.h" #include "jit/BaselineICList.h" +#include "jit/BaselineJIT.h" #include "jit/CompileInfo.h" #include "jit/ICStubSpace.h" #include "jit/IonCode.h" @@ -195,6 +196,9 @@ class JitRuntime { WriteOnceData baselineDebugModeOSRHandler_; WriteOnceData baselineDebugModeOSRHandlerNoFrameRegPopAddr_; + // BaselineInterpreter state. + BaselineInterpreter baselineInterpreter_; + // Code for trampolines and VMFunction wrappers. WriteOnceData trampolineCode_; @@ -326,6 +330,8 @@ class JitRuntime { JitCode* getBaselineDebugModeOSRHandler(JSContext* cx); void* getBaselineDebugModeOSRHandlerAddress(JSContext* cx, bool popFrameReg); + BaselineInterpreter& baselineInterpreter() { return baselineInterpreter_; } + TrampolinePtr getGenericBailoutHandler() const { return trampolineCode(bailoutHandlerOffset_); } diff --git a/js/src/vm/JSScript.cpp b/js/src/vm/JSScript.cpp index 260581d732f1..e86c584b6da7 100644 --- a/js/src/vm/JSScript.cpp +++ b/js/src/vm/JSScript.cpp @@ -5431,6 +5431,9 @@ void JSScript::updateJitCodeRaw(JSRuntime* rt) { } else if (hasBaselineScript()) { jitCodeRaw_ = baseline->method()->raw(); jitCodeSkipArgCheck_ = jitCodeRaw_; + } else if (types() && js::jit::JitOptions.baselineInterpreter) { + jitCodeRaw_ = rt->jitRuntime()->baselineInterpreter().codeRaw(); + jitCodeSkipArgCheck_ = jitCodeRaw_; } else { jitCodeRaw_ = rt->jitRuntime()->interpreterStub().value; jitCodeSkipArgCheck_ = jitCodeRaw_; diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 7dcf8c99daa6..1d729fd3a6c9 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -770,6 +770,10 @@ void JSRuntime::clearUsedByHelperThread(Zone* zone) { } void JSRuntime::incrementNumDebuggeeRealms() { + if (numDebuggeeRealms_ == 0) { + jitRuntime()->baselineInterpreter().toggleDebuggerInstrumentation(true); + } + numDebuggeeRealms_++; MOZ_ASSERT(numDebuggeeRealms_ <= numRealms); } @@ -777,6 +781,10 @@ void JSRuntime::incrementNumDebuggeeRealms() { void JSRuntime::decrementNumDebuggeeRealms() { MOZ_ASSERT(numDebuggeeRealms_ > 0); numDebuggeeRealms_--; + + if (numDebuggeeRealms_ == 0) { + jitRuntime()->baselineInterpreter().toggleDebuggerInstrumentation(false); + } } bool js::CurrentThreadCanAccessRuntime(const JSRuntime* rt) { diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index 2d5b62b869bd..0ad3850e6314 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -3555,6 +3555,10 @@ bool JSScript::makeTypes(JSContext* cx) { types_ = new (typeScript) TypeScript(this, std::move(icScript), numTypeSets); + // We have a TypeScript so we can set the script's jitCodeRaw_ pointer to the + // Baseline Interpreter code. + updateJitCodeRaw(cx->runtime()); + #ifdef DEBUG StackTypeSet* typeArray = typeScript->typeArrayDontCheckGeneration(); for (unsigned i = 0; i < numBytecodeTypeSets(); i++) { @@ -4585,6 +4589,7 @@ void JSScript::maybeReleaseTypes() { types_->destroy(zone()); types_ = nullptr; + updateJitCodeRaw(runtimeFromMainThread()); } void TypeScript::destroy(Zone* zone) {