Bug 1566330 - Let BaselineDebugModeOSR resume in the interpreter, remove BaselineDebugModeOSRInfo. r=iain
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 19 Jul 2019 09:01:45 +0000
changeset 483627 49a2da59aa3e33b3719c7953a98fac6c24b8a514
parent 483626 ad5e0980c2731978f159e17622d6ba0aa1c05ea5
child 483628 499c8fa689ad7be86250f89d31a1cca5039abb11
push id36326
push userbtara@mozilla.com
push dateSun, 21 Jul 2019 21:59:35 +0000
treeherdermozilla-central@48797c5119a4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersiain
bugs1566330, 1566189
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1566330 - Let BaselineDebugModeOSR resume in the interpreter, remove BaselineDebugModeOSRInfo. r=iain At this point most of the DebugModeOSR complexity came from dealing with the On -> Off case because debugger callVMs are not present in the recompiled script. We also had to worry about loading unsynced stack values in R0/R1 in the DebugTrap case (because it resumes at the start of a bytecode op). We can now change these cases to resume after the corresponding Interpreter callVMs instead. This lets us remove BaselineDebugModeOSRInfo and the continuation fixer trampoline. We also no longer have to worry about unsynced R0/R1 stack values for DebugTrap because the interpreter always has a synced stack at the beginning of a bytecode op. This removes about 360 lines of complicated code. It also fixes a memory leak the fuzzers found a few days ago (bug 1566189). Differential Revision: https://phabricator.services.mozilla.com/D38477
js/src/jit/BaselineCodeGen.cpp
js/src/jit/BaselineCodeGen.h
js/src/jit/BaselineDebugModeOSR.cpp
js/src/jit/BaselineDebugModeOSR.h
js/src/jit/BaselineFrame.h
js/src/jit/BaselineJIT.cpp
js/src/jit/BaselineJIT.h
js/src/jit/Ion.cpp
js/src/jit/JSJitFrameIter.cpp
js/src/jit/JitFrames.cpp
js/src/jit/JitRealm.h
js/src/jit/VMFunctions.cpp
--- a/js/src/jit/BaselineCodeGen.cpp
+++ b/js/src/jit/BaselineCodeGen.cpp
@@ -125,19 +125,19 @@ bool BaselineCompilerHandler::init(JSCon
 bool BaselineCompiler::init() {
   if (!handler.init(cx)) {
     return false;
   }
 
   return true;
 }
 
-bool BaselineCompilerHandler::appendRetAddrEntry(JSContext* cx,
-                                                 RetAddrEntry::Kind kind,
-                                                 uint32_t retOffset) {
+bool BaselineCompilerHandler::recordCallRetAddr(JSContext* cx,
+                                                RetAddrEntry::Kind kind,
+                                                uint32_t retOffset) {
   uint32_t pcOffset = script_->pcToOffset(pc_);
 
   // Entries must be sorted by pcOffset for binary search to work.
   // See BaselineScript::retAddrEntryFromPCOffset.
   MOZ_ASSERT_IF(!retAddrEntries_.empty(),
                 retAddrEntries_.back().pcOffset() <= pcOffset);
 
   // Similarly, entries must be sorted by return offset and this offset must be
@@ -148,16 +148,36 @@ bool BaselineCompilerHandler::appendRetA
   if (!retAddrEntries_.emplaceBack(pcOffset, kind, CodeOffset(retOffset))) {
     ReportOutOfMemory(cx);
     return false;
   }
 
   return true;
 }
 
+bool BaselineInterpreterHandler::recordCallRetAddr(JSContext* cx,
+                                                   RetAddrEntry::Kind kind,
+                                                   uint32_t retOffset) {
+  switch (kind) {
+    case RetAddrEntry::Kind::DebugPrologue:
+      callVMOffsets_.debugPrologueOffset = retOffset;
+      break;
+    case RetAddrEntry::Kind::DebugEpilogue:
+      callVMOffsets_.debugEpilogueOffset = retOffset;
+      break;
+    case RetAddrEntry::Kind::DebugAfterYield:
+      callVMOffsets_.debugAfterYieldOffset = retOffset;
+      break;
+    default:
+      break;
+  }
+
+  return true;
+}
+
 bool BaselineCompiler::addPCMappingEntry(bool addIndexEntry) {
   // Don't add multiple entries for a single pc.
   size_t nentries = pcMappingEntries_.length();
   uint32_t pcOffset = handler.script()->pcToOffset(handler.pc());
   if (nentries > 0 && pcMappingEntries_[nentries - 1].pcOffset == pcOffset) {
     return true;
   }
 
@@ -290,25 +310,24 @@ MethodStatus BaselineCompiler::compile()
   }
 
   if (pcEntries.oom()) {
     ReportOutOfMemory(cx);
     return Method_Error;
   }
 
   UniquePtr<BaselineScript> baselineScript(
-      BaselineScript::New(
-          script, bailoutPrologueOffset_.offset(),
-          warmUpCheckPrologueOffset_.offset(), debugOsrPrologueOffset_.offset(),
-          debugOsrEpilogueOffset_.offset(),
-          profilerEnterFrameToggleOffset_.offset(),
-          profilerExitFrameToggleOffset_.offset(),
-          handler.retAddrEntries().length(), pcMappingIndexEntries.length(),
-          pcEntries.length(), script->resumeOffsets().size(),
-          traceLoggerToggleOffsets_.length()),
+      BaselineScript::New(script, bailoutPrologueOffset_.offset(),
+                          warmUpCheckPrologueOffset_.offset(),
+                          profilerEnterFrameToggleOffset_.offset(),
+                          profilerExitFrameToggleOffset_.offset(),
+                          handler.retAddrEntries().length(),
+                          pcMappingIndexEntries.length(), pcEntries.length(),
+                          script->resumeOffsets().size(),
+                          traceLoggerToggleOffsets_.length()),
       JS::DeletePolicy<BaselineScript>(cx->runtime()));
   if (!baselineScript) {
     ReportOutOfMemory(cx);
     return Method_Error;
   }
 
   baselineScript->setMethod(code);
   baselineScript->setTemplateEnvironment(templateEnv);
@@ -768,17 +787,17 @@ bool BaselineCodeGen<Handler>::callVMInt
     Label ok;
     masm.branchTest32(Assembler::Zero, frame.addressOfFlags(),
                       Imm32(BaselineFrame::HAS_OVERRIDE_PC), &ok);
     masm.assumeUnreachable("BaselineFrame shouldn't override pc after VM call");
     masm.bind(&ok);
   }
 #endif
 
-  return handler.appendRetAddrEntry(cx, kind, callOffset);
+  return handler.recordCallRetAddr(cx, kind, callOffset);
 }
 
 template <typename Handler>
 template <typename Fn, Fn fn>
 bool BaselineCodeGen<Handler>::callVM(RetAddrEntry::Kind kind,
                                       CallVMPhase phase) {
   VMFunctionId fnId = VMFunctionToId<Fn, fn>::id;
   return callVMInternal(fnId, kind, phase);
@@ -1150,23 +1169,17 @@ bool BaselineCodeGen<Handler>::emitDebug
     masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &done);
     {
       masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand);
       masm.jump(&return_);
     }
     masm.bind(&done);
     return true;
   };
-  if (!emitDebugInstrumentation(ifDebuggee)) {
-    return false;
-  }
-
-  debugOsrPrologueOffset_ = CodeOffset(masm.currentOffset());
-
-  return true;
+  return emitDebugInstrumentation(ifDebuggee);
 }
 
 template <>
 void BaselineCompilerCodeGen::emitInitFrameFields(Register nonFunctionEnv) {
   masm.store32(Imm32(0), frame.addressOfFlags());
   if (handler.function()) {
     Register scratch = R0.scratchReg();
     masm.loadFunctionFromCalleeToken(frame.addressOfCalleeToken(), scratch);
@@ -1580,18 +1593,18 @@ bool BaselineCompiler::emitDebugTrap() {
 
 #ifdef DEBUG
   // Patchable call offset has to match the pc mapping offset.
   PCMappingEntry& entry = pcMappingEntries_.back();
   MOZ_ASSERT((&offset)->offset() == entry.nativeOffset);
 #endif
 
   // Add a RetAddrEntry for the return offset -> pc mapping.
-  return handler.appendRetAddrEntry(cx, RetAddrEntry::Kind::DebugTrap,
-                                    masm.currentOffset());
+  return handler.recordCallRetAddr(cx, RetAddrEntry::Kind::DebugTrap,
+                                   masm.currentOffset());
 }
 
 #ifdef JS_TRACE_LOGGING
 template <>
 bool BaselineCompilerCodeGen::emitTraceLoggerEnter() {
   AllocatableRegisterSet regs(RegisterSet::Volatile());
   Register loggerReg = regs.takeAnyGeneral();
   Register scriptReg = regs.takeAnyGeneral();
@@ -6172,19 +6185,19 @@ bool BaselineCodeGen<Handler>::emitGener
   // generator returns.
   Label genStart, returnTarget;
 #ifdef JS_USE_LINK_REGISTER
   masm.call(&genStart);
 #else
   masm.callAndPushReturnAddress(&genStart);
 #endif
 
-  // Add a RetAddrEntry so the return offset -> pc mapping works.
-  if (!handler.appendRetAddrEntry(cx, RetAddrEntry::Kind::IC,
-                                  masm.currentOffset())) {
+  // Record the return address so the return offset -> pc mapping works.
+  if (!handler.recordCallRetAddr(cx, RetAddrEntry::Kind::IC,
+                                 masm.currentOffset())) {
     return false;
   }
 
   masm.jump(&returnTarget);
   masm.bind(&genStart);
 #ifdef JS_USE_LINK_REGISTER
   masm.pushReturnAddress();
 #endif
@@ -6770,20 +6783,16 @@ bool BaselineCodeGen<Handler>::emitProlo
     return false;
   }
 
   return true;
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emitEpilogue() {
-  // Record the offset of the epilogue, so we can do early return from
-  // Debugger handlers during on-stack recompile.
-  debugOsrEpilogueOffset_ = CodeOffset(masm.currentOffset());
-
   masm.bind(&return_);
 
 #ifdef JS_TRACE_LOGGING
   if (JS::TraceLoggerSupported() && !emitTraceLoggerExit()) {
     return false;
   }
 #endif
 
@@ -7001,17 +7010,18 @@ bool BaselineInterpreterGenerator::emitI
     if (!opEpilogue(OP, OP##_LENGTH)) { \
       return false;                     \
     }                                   \
   }
   OPCODE_LIST(EMIT_OP)
 #undef EMIT_OP
 
   // External entry point to start interpreting bytecode ops. This is used for
-  // things like exception handling and OSR.
+  // things like exception handling and OSR. DebugModeOSR patches JIT frames to
+  // return here from the DebugTrapHandler.
   masm.bind(handler.interpretOpLabel());
   interpretOpOffset_ = masm.currentOffset();
   restoreInterpreterPCReg();
   masm.jump(handler.interpretOpWithPCRegLabel());
 
   // Second external entry point: this skips the debug trap for the first op
   // and is used by OSR.
   interpretOpNoDebugTrapOffset_ = masm.currentOffset();
@@ -7147,17 +7157,18 @@ bool BaselineInterpreterGenerator::gener
     vtune::MarkStub(code, "BaselineInterpreter");
 #endif
 
     interpreter.init(code, interpretOpOffset_, interpretOpNoDebugTrapOffset_,
                      profilerEnterFrameToggleOffset_.offset(),
                      profilerExitFrameToggleOffset_.offset(),
                      std::move(handler.debugInstrumentationOffsets()),
                      std::move(debugTrapOffsets_),
-                     std::move(handler.codeCoverageOffsets()));
+                     std::move(handler.codeCoverageOffsets()),
+                     handler.callVMOffsets());
   }
 
   if (cx->runtime()->geckoProfiler().enabled()) {
     interpreter.toggleProfilerInstrumentation(true);
   }
 
   if (coverage::IsLCovEnabled()) {
     interpreter.toggleCodeCoverageInstrumentationUnchecked(true);
--- a/js/src/jit/BaselineCodeGen.h
+++ b/js/src/jit/BaselineCodeGen.h
@@ -283,24 +283,16 @@ class BaselineCodeGen {
   // Early Ion bailouts will enter at this address. This is after frame
   // construction and before environment chain is initialized.
   CodeOffset bailoutPrologueOffset_;
 
   // Baseline Interpreter can enter Baseline Compiler code at this address. This
   // is right after the warm-up counter check in the prologue.
   CodeOffset warmUpCheckPrologueOffset_;
 
-  // Baseline Debug OSR during prologue will enter at this address. This is
-  // right after where a debug prologue VM call would have returned.
-  CodeOffset debugOsrPrologueOffset_;
-
-  // Baseline Debug OSR during epilogue will enter at this address. This is
-  // right after where a debug epilogue VM call would have returned.
-  CodeOffset debugOsrEpilogueOffset_;
-
   uint32_t pushedBeforeCall_;
 #ifdef DEBUG
   bool inCall_;
 #endif
 
   // Whether any on stack arguments are modified.
   bool modifiesArguments_;
 
@@ -580,18 +572,18 @@ class BaselineCompilerHandler {
 
   uint32_t icEntryIndex() const { return icEntryIndex_; }
   void moveToNextICEntry() { icEntryIndex_++; }
 
   BytecodeAnalysis& analysis() { return analysis_; }
 
   RetAddrEntryVector& retAddrEntries() { return retAddrEntries_; }
 
-  MOZ_MUST_USE bool appendRetAddrEntry(JSContext* cx, RetAddrEntry::Kind kind,
-                                       uint32_t retOffset);
+  MOZ_MUST_USE bool recordCallRetAddr(JSContext* cx, RetAddrEntry::Kind kind,
+                                      uint32_t retOffset);
 
   // If a script has more |nslots| than this the stack check must account
   // for these slots explicitly.
   bool mustIncludeSlotsInStackCheck() const {
     static constexpr size_t NumSlotsLimit = 128;
     return script()->nslots() > NumSlotsLimit;
   }
 
@@ -673,16 +665,19 @@ class BaselineInterpreterHandler {
   using CodeOffsetVector = Vector<uint32_t, 0, SystemAllocPolicy>;
   CodeOffsetVector debugInstrumentationOffsets_;
 
   // Offsets of toggled jumps for code coverage instrumentation.
   CodeOffsetVector codeCoverageOffsets_;
   Label codeCoverageAtPrologueLabel_;
   Label codeCoverageAtPCLabel_;
 
+  // Offsets of some callVMs for BaselineDebugModeOSR.
+  BaselineInterpreter::CallVMOffsets callVMOffsets_;
+
  public:
   using FrameInfoT = InterpreterFrameInfo;
 
   explicit BaselineInterpreterHandler(JSContext* cx, MacroAssembler& masm);
 
   InterpreterFrameInfo& frame() { return frame_; }
 
   Label* interpretOpLabel() { return &interpretOp_; }
@@ -701,23 +696,23 @@ class BaselineInterpreterHandler {
   bool isDefinitelyLastOp() const { return false; }
   JSScript* maybeScript() const { return nullptr; }
   JSFunction* maybeFunction() const { return nullptr; }
 
   MOZ_MUST_USE bool addDebugInstrumentationOffset(CodeOffset offset) {
     return debugInstrumentationOffsets_.append(offset.offset());
   }
 
-  // Interpreter doesn't need to keep track of RetAddrEntries, so this is a
-  // no-op.
-  MOZ_MUST_USE bool appendRetAddrEntry(JSContext* cx, RetAddrEntry::Kind kind,
-                                       uint32_t retOffset) {
-    return true;
+  const BaselineInterpreter::CallVMOffsets& callVMOffsets() const {
+    return callVMOffsets_;
   }
 
+  MOZ_MUST_USE bool recordCallRetAddr(JSContext* cx, RetAddrEntry::Kind kind,
+                                      uint32_t retOffset);
+
   bool maybeIonCompileable() const { return true; }
 
   // The interpreter doesn't know the number of slots statically so we always
   // include them.
   bool mustIncludeSlotsInStackCheck() const { return true; }
 
   JSObject* maybeNoCloneSingletonObject() { return nullptr; }
 };
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -17,105 +17,51 @@
 #include "vm/TypeInference-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 struct DebugModeOSREntry {
   JSScript* script;
   BaselineScript* oldBaselineScript;
-  BaselineDebugModeOSRInfo* recompInfo;
   uint32_t pcOffset;
   RetAddrEntry::Kind frameKind;
 
   explicit DebugModeOSREntry(JSScript* script)
       : script(script),
         oldBaselineScript(script->baselineScript()),
-        recompInfo(nullptr),
         pcOffset(uint32_t(-1)),
         frameKind(RetAddrEntry::Kind::Invalid) {}
 
   DebugModeOSREntry(JSScript* script, uint32_t pcOffset)
       : script(script),
         oldBaselineScript(script->baselineScript()),
-        recompInfo(nullptr),
         pcOffset(pcOffset),
         frameKind(RetAddrEntry::Kind::Invalid) {}
 
   DebugModeOSREntry(JSScript* script, const RetAddrEntry& retAddrEntry)
       : script(script),
         oldBaselineScript(script->baselineScript()),
-        recompInfo(nullptr),
         pcOffset(retAddrEntry.pcOffset()),
         frameKind(retAddrEntry.kind()) {
 #ifdef DEBUG
     MOZ_ASSERT(pcOffset == retAddrEntry.pcOffset());
     MOZ_ASSERT(frameKind == retAddrEntry.kind());
 #endif
   }
 
-  DebugModeOSREntry(JSScript* script, BaselineDebugModeOSRInfo* info)
-      : script(script),
-        oldBaselineScript(script->baselineScript()),
-        recompInfo(nullptr),
-        pcOffset(script->pcToOffset(info->pc)),
-        frameKind(info->frameKind) {
-#ifdef DEBUG
-    MOZ_ASSERT(pcOffset == script->pcToOffset(info->pc));
-    MOZ_ASSERT(frameKind == info->frameKind);
-#endif
-  }
-
   DebugModeOSREntry(DebugModeOSREntry&& other)
       : script(other.script),
         oldBaselineScript(other.oldBaselineScript),
-        recompInfo(other.recompInfo ? other.takeRecompInfo() : nullptr),
         pcOffset(other.pcOffset),
         frameKind(other.frameKind) {}
 
-  ~DebugModeOSREntry() {
-    // Note that this is nulled out when the recompInfo is taken by the
-    // frame. The frame then has the responsibility of freeing the
-    // recompInfo.
-    js_delete(recompInfo);
-  }
-
-  bool needsRecompileInfo() const {
-    return frameKind == RetAddrEntry::Kind::DebugTrap ||
-           frameKind == RetAddrEntry::Kind::DebugPrologue ||
-           frameKind == RetAddrEntry::Kind::DebugAfterYield ||
-           frameKind == RetAddrEntry::Kind::DebugEpilogue;
-  }
-
   bool recompiled() const {
     return oldBaselineScript != script->baselineScript();
   }
-
-  BaselineDebugModeOSRInfo* takeRecompInfo() {
-    MOZ_ASSERT(needsRecompileInfo() && recompInfo);
-    BaselineDebugModeOSRInfo* tmp = recompInfo;
-    recompInfo = nullptr;
-    return tmp;
-  }
-
-  bool allocateRecompileInfo(JSContext* cx) {
-    MOZ_ASSERT(script);
-    MOZ_ASSERT(needsRecompileInfo());
-
-    // If we are returning to a frame which needs a continuation fixer,
-    // allocate the recompile info up front so that the patching function
-    // is infallible.
-    jsbytecode* pc = script->offsetToPC(pcOffset);
-
-    // XXX: Work around compiler error disallowing using bitfields
-    // with the template magic of new_.
-    RetAddrEntry::Kind kind = frameKind;
-    recompInfo = cx->new_<BaselineDebugModeOSRInfo>(pc, kind);
-    return !!recompInfo;
-  }
 };
 
 typedef Vector<DebugModeOSREntry> DebugModeOSREntryVector;
 
 class UniqueScriptOSREntryIter {
   const DebugModeOSREntryVector& entries_;
   size_t index_;
 
@@ -147,17 +93,16 @@ class UniqueScriptOSREntryIter {
     return *this;
   }
 };
 
 static bool CollectJitStackScripts(JSContext* cx,
                                    const Debugger::ExecutionObservableSet& obs,
                                    const ActivationIterator& activation,
                                    DebugModeOSREntryVector& entries) {
-  bool needsRecompileHandler = false;
   for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) {
     const JSJitFrameIter& frame = iter.frame();
     switch (frame.type()) {
       case FrameType::BaselineJS: {
         JSScript* script = frame.script();
 
         if (!obs.shouldRecompileOrInvalidate(script)) {
           break;
@@ -168,28 +113,16 @@ static bool CollectJitStackScripts(JSCon
         if (baselineFrame->runningInInterpreter()) {
           // Baseline Interpreter frames for scripts that have a BaselineScript
           // or IonScript don't need to be patched but they do need to be
           // invalidated and recompiled. See also CollectInterpreterStackScripts
           // for C++ interpreter frames.
           if (!entries.append(DebugModeOSREntry(script))) {
             return false;
           }
-        } else if (BaselineDebugModeOSRInfo* info =
-                       baselineFrame->getDebugModeOSRInfo()) {
-          // If patching a previously patched yet unpopped frame, we can
-          // use the BaselineDebugModeOSRInfo on the frame directly to
-          // patch. Indeed, we cannot use frame.resumePCinCurrentFrame(), as
-          // it points into the debug mode OSR handler and cannot be
-          // used to look up a corresponding RetAddrEntry.
-          //
-          // See case F in PatchBaselineFramesForDebugMode.
-          if (!entries.append(DebugModeOSREntry(script, info))) {
-            return false;
-          }
         } else if (baselineFrame->hasOverridePc()) {
           // If the frame is not settled on a pc with a RetAddrEntry,
           // overridePc will contain an explicit bytecode offset. We can
           // (and must) use that.
           uint32_t offset = script->pcToOffset(baselineFrame->overridePc());
           if (!entries.append(DebugModeOSREntry(script, offset))) {
             return false;
           }
@@ -198,23 +131,16 @@ static bool CollectJitStackScripts(JSCon
           uint8_t* retAddr = frame.resumePCinCurrentFrame();
           RetAddrEntry& retAddrEntry =
               script->baselineScript()->retAddrEntryFromReturnAddress(retAddr);
           if (!entries.append(DebugModeOSREntry(script, retAddrEntry))) {
             return false;
           }
         }
 
-        if (entries.back().needsRecompileInfo()) {
-          if (!entries.back().allocateRecompileInfo(cx)) {
-            return false;
-          }
-
-          needsRecompileHandler |= true;
-        }
         break;
       }
 
       case FrameType::BaselineStub:
         break;
 
       case FrameType::IonJS: {
         InlineFrameIterator inlineIter(cx, &frame);
@@ -231,25 +157,16 @@ static bool CollectJitStackScripts(JSCon
         }
         break;
       }
 
       default:;
     }
   }
 
-  // Initialize the on-stack recompile handler, which may fail, so that
-  // patching the stack is infallible.
-  if (needsRecompileHandler) {
-    JitRuntime* rt = cx->runtime()->jitRuntime();
-    if (!rt->getBaselineDebugModeOSRHandlerAddress(cx, true)) {
-      return false;
-    }
-  }
-
   return true;
 }
 
 static bool CollectInterpreterStackScripts(
     JSContext* cx, const Debugger::ExecutionObservableSet& obs,
     const ActivationIterator& activation, DebugModeOSREntryVector& entries) {
   // Collect interpreter frame stacks with IonScript or BaselineScript as
   // well. These do not need to be patched, but do need to be invalidated
@@ -324,37 +241,39 @@ static void PatchBaselineFramesForDebugM
   //
   // When toggling debug mode with live baseline scripts on the stack, we
   // could have entered the VM via the following ways from the baseline
   // script.
   //
   // Off to On:
   //  A. From a "can call" IC stub.
   //  B. From a VM call.
-  //  H. From inside HandleExceptionBaseline
-  //  I. From inside the interrupt handler via the prologue stack check.
-  //  J. From the warmup counter in the prologue.
+  //  C. From inside the interrupt handler via the prologue stack check.
+  //  D. From the warmup counter in the prologue.
+  //  E. From inside HandleExceptionBaseline
   //
   // On to Off:
   //  - All the ways above.
-  //  C. From the debug trap handler.
-  //  D. From the debug prologue.
-  //  E. From the debug epilogue.
-  //  G. From GeneratorThrowOrReturn
-  //  K. From a JSOP_AFTERYIELD instruction.
+  //  F. From the debug trap handler.
+  //  G. From the debug prologue.
+  //  H. From the debug epilogue.
+  //  I. From a JSOP_AFTERYIELD instruction.
+  //  J. From GeneratorThrowOrReturn
   //
-  // Cycles (On to Off to On)+ or (Off to On to Off)+:
-  //  F. Undo cases B, C, D, E, I or J above on previously patched yet unpopped
-  //     frames.
+  // In general, we patch the return address from VM calls and ICs to the
+  // corresponding entry in the recompiled BaselineScript. For entries that are
+  // not present in the recompiled script (cases F to I above) we switch the
+  // frame to interpreter mode and resume in the Baseline Interpreter.
   //
-  // In general, we patch the return address from the VM call to return to a
-  // "continuation fixer" to fix up machine state (registers and stack
-  // state). Specifics on what needs to be done are documented below.
+  // Specifics on what needs to be done are documented below.
   //
 
+  const BaselineInterpreter& baselineInterp =
+      cx->runtime()->jitRuntime()->baselineInterpreter();
+
   CommonFrameLayout* prev = nullptr;
   size_t entryIndex = *start;
 
   for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) {
     const JSJitFrameIter& frame = iter.frame();
     switch (frame.type()) {
       case FrameType::BaselineJS: {
         // If the script wasn't recompiled or is not observed, there's
@@ -382,167 +301,118 @@ static void PatchBaselineFramesForDebugM
         uint32_t pcOffset = entry.pcOffset;
         jsbytecode* pc = script->offsetToPC(pcOffset);
 
         MOZ_ASSERT(script == frame.script());
         MOZ_ASSERT(pcOffset < script->length());
 
         BaselineScript* bl = script->baselineScript();
         RetAddrEntry::Kind kind = entry.frameKind;
-
-        if (kind == RetAddrEntry::Kind::IC ||
-            kind == RetAddrEntry::Kind::CallVM ||
-            kind == RetAddrEntry::Kind::WarmupCounter ||
-            kind == RetAddrEntry::Kind::StackCheck) {
-          // Cases A, B, I, J above.
-          //
-          // For the baseline frame here, we resume right after the CallVM or IC
-          // returns.
-          //
-          // For CallVM (case B) the assumption is that all callVMs which can
-          // trigger debug mode OSR are the *only* callVMs generated for their
-          // respective pc locations in the Baseline JIT code.
-          RetAddrEntry* retAddrEntry = nullptr;
-          switch (kind) {
-            case RetAddrEntry::Kind::IC:
-            case RetAddrEntry::Kind::CallVM:
-              retAddrEntry = &bl->retAddrEntryFromPCOffset(pcOffset, kind);
-              break;
-            case RetAddrEntry::Kind::WarmupCounter:
-            case RetAddrEntry::Kind::StackCheck:
-              retAddrEntry = &bl->prologueRetAddrEntry(kind);
-              break;
-            default:
-              MOZ_CRASH("Unexpected kind");
+        uint8_t* retAddr = nullptr;
+        switch (kind) {
+          case RetAddrEntry::Kind::IC:
+          case RetAddrEntry::Kind::CallVM:
+          case RetAddrEntry::Kind::WarmupCounter:
+          case RetAddrEntry::Kind::StackCheck: {
+            // Cases A, B, C, D above.
+            //
+            // For the baseline frame here, we resume right after the CallVM or
+            // IC returns.
+            //
+            // For CallVM (case B) the assumption is that all callVMs which can
+            // trigger debug mode OSR are the *only* callVMs generated for their
+            // respective pc locations in the Baseline JIT code.
+            RetAddrEntry* retAddrEntry = nullptr;
+            switch (kind) {
+              case RetAddrEntry::Kind::IC:
+              case RetAddrEntry::Kind::CallVM:
+                retAddrEntry = &bl->retAddrEntryFromPCOffset(pcOffset, kind);
+                break;
+              case RetAddrEntry::Kind::WarmupCounter:
+              case RetAddrEntry::Kind::StackCheck:
+                retAddrEntry = &bl->prologueRetAddrEntry(kind);
+                break;
+              default:
+                MOZ_CRASH("Unexpected kind");
+            }
+            retAddr = bl->returnAddressForEntry(*retAddrEntry);
+            SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind,
+                                   pc);
+            break;
           }
-          uint8_t* retAddr = bl->returnAddressForEntry(*retAddrEntry);
-          SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind,
-                                 pc);
-          DebugModeOSRVolatileJitFrameIter::forwardLiveIterators(
-              cx, prev->returnAddress(), retAddr);
-          prev->setReturnAddress(retAddr);
-          entryIndex++;
-          break;
-        }
-
-        if (kind == RetAddrEntry::Kind::Invalid) {
-          // Cases G and H above.
-          //
-          // We are recompiling a frame with an override pc.
-          // This may occur from inside the exception handler,
-          // by way of an onExceptionUnwind invocation, on a pc
-          // without a RetAddrEntry. It may also happen if we call
-          // GeneratorThrowOrReturn and trigger onEnterFrame.
-          //
-          // If profiling is off, patch the resume address to nullptr,
-          // to ensure the old address is not used anywhere.
-          // If profiling is on, JSJitProfilingFrameIterator requires a
-          // valid return address.
-          MOZ_ASSERT(frame.baselineFrame()->overridePc() == pc);
-          uint8_t* retAddr;
-          if (cx->runtime()->geckoProfiler().enabled()) {
-            // Won't actually jump to this address so we can ignore the
-            // register state in the slot info.
-            PCMappingSlotInfo unused;
-            retAddr = bl->nativeCodeForPC(script, pc, &unused);
-          } else {
-            retAddr = nullptr;
+          case RetAddrEntry::Kind::DebugPrologue:
+          case RetAddrEntry::Kind::DebugEpilogue:
+          case RetAddrEntry::Kind::DebugTrap:
+          case RetAddrEntry::Kind::DebugAfterYield: {
+            // Cases F, G, H, I above.
+            //
+            // Resume in the Baseline Interpreter because these callVMs are not
+            // present in the new BaselineScript if we recompiled without debug
+            // instrumentation.
+            frame.baselineFrame()->switchFromJitToInterpreter(pc);
+            switch (kind) {
+              case RetAddrEntry::Kind::DebugTrap:
+                // DebugTrap handling is different from the ones below because
+                // it's not a callVM but a trampoline call at the start of the
+                // bytecode op. When we return to the frame we can resume at the
+                // interpretOp label.
+                retAddr = baselineInterp.interpretOpAddr().value;
+                break;
+              case RetAddrEntry::Kind::DebugPrologue:
+                retAddr = baselineInterp.retAddrForDebugPrologueCallVM();
+                break;
+              case RetAddrEntry::Kind::DebugEpilogue:
+                retAddr = baselineInterp.retAddrForDebugEpilogueCallVM();
+                break;
+              case RetAddrEntry::Kind::DebugAfterYield:
+                retAddr = baselineInterp.retAddrForDebugAfterYieldCallVM();
+                break;
+              default:
+                MOZ_CRASH("Unexpected kind");
+            }
+            SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind,
+                                   pc);
+            break;
           }
-          SpewPatchBaselineFrameFromExceptionHandler(prev->returnAddress(),
-                                                     retAddr, script, pc);
-          DebugModeOSRVolatileJitFrameIter::forwardLiveIterators(
-              cx, prev->returnAddress(), retAddr);
-          prev->setReturnAddress(retAddr);
-          entryIndex++;
-          break;
+          case RetAddrEntry::Kind::Invalid: {
+            // Cases E and J above.
+            //
+            // We are recompiling a frame with an override pc.
+            // This may occur from inside the exception handler,
+            // by way of an onExceptionUnwind invocation, on a pc
+            // without a RetAddrEntry. It may also happen if we call
+            // GeneratorThrowOrReturn and trigger onEnterFrame.
+            //
+            // If profiling is off, patch the resume address to nullptr,
+            // to ensure the old address is not used anywhere.
+            // If profiling is on, JSJitProfilingFrameIterator requires a
+            // valid return address.
+            MOZ_ASSERT(frame.baselineFrame()->overridePc() == pc);
+            uint8_t* retAddr;
+            if (cx->runtime()->geckoProfiler().enabled()) {
+              // Won't actually jump to this address so we can ignore the
+              // register state in the slot info.
+              PCMappingSlotInfo unused;
+              retAddr = bl->nativeCodeForPC(script, pc, &unused);
+            } else {
+              retAddr = nullptr;
+            }
+            SpewPatchBaselineFrameFromExceptionHandler(prev->returnAddress(),
+                                                       retAddr, script, pc);
+            break;
+          }
+          case RetAddrEntry::Kind::PrologueIC:
+          case RetAddrEntry::Kind::NonOpCallVM:
+            // These cannot trigger BaselineDebugModeOSR.
+            MOZ_CRASH("Unexpected RetAddrEntry Kind");
         }
 
-        // Case F above.
-        //
-        // We undo a previous recompile by handling cases C, D or E like normal,
-        // except that we retrieve the pc information via the previous OSR debug
-        // info stashed on the frame.
-        BaselineDebugModeOSRInfo* info =
-            frame.baselineFrame()->getDebugModeOSRInfo();
-        if (info) {
-          MOZ_ASSERT(info->pc == pc);
-          MOZ_ASSERT(info->frameKind == kind);
-          MOZ_ASSERT(kind == RetAddrEntry::Kind::DebugTrap ||
-                     kind == RetAddrEntry::Kind::DebugPrologue ||
-                     kind == RetAddrEntry::Kind::DebugAfterYield ||
-                     kind == RetAddrEntry::Kind::DebugEpilogue);
-
-          // We will have allocated a new recompile info, so delete the
-          // existing one.
-          frame.baselineFrame()->deleteDebugModeOSRInfo();
-        }
-
-        // The RecompileInfo must already be allocated so that this
-        // function may be infallible.
-        BaselineDebugModeOSRInfo* recompInfo = entry.takeRecompInfo();
-
-        bool popFrameReg;
-        switch (kind) {
-          case RetAddrEntry::Kind::DebugTrap:
-            // Case C above.
-            //
-            // Debug traps are emitted before each op, so we resume at the
-            // same op. Calling debug trap handlers is done via a toggled
-            // call to a thunk (DebugTrapHandler) that takes care tearing
-            // down its own stub frame so we don't need to worry about
-            // popping the frame reg.
-            recompInfo->resumeAddr =
-                bl->nativeCodeForPC(script, pc, &recompInfo->slotInfo);
-            popFrameReg = false;
-            break;
-
-          case RetAddrEntry::Kind::DebugPrologue:
-            // Case D above.
-            //
-            // We patch a jump directly to the right place in the prologue
-            // after popping the frame reg and checking for forced return.
-            recompInfo->resumeAddr = bl->debugOsrPrologueEntryAddr();
-            popFrameReg = true;
-            break;
-
-          case RetAddrEntry::Kind::DebugAfterYield:
-            // Case K above.
-            //
-            // Resume at the next instruction.
-            MOZ_ASSERT(*pc == JSOP_AFTERYIELD);
-            recompInfo->resumeAddr = bl->nativeCodeForPC(
-                script, pc + JSOP_AFTERYIELD_LENGTH, &recompInfo->slotInfo);
-            popFrameReg = true;
-            break;
-
-          default:
-            // Case E above.
-            //
-            // We patch a jump directly to the epilogue after popping the
-            // frame reg and checking for forced return.
-            MOZ_ASSERT(kind == RetAddrEntry::Kind::DebugEpilogue);
-            recompInfo->resumeAddr = bl->debugOsrEpilogueEntryAddr();
-            popFrameReg = true;
-            break;
-        }
-
-        SpewPatchBaselineFrame(prev->returnAddress(), recompInfo->resumeAddr,
-                               script, kind, recompInfo->pc);
-
-        // The recompile handler must already be created so that this
-        // function may be infallible.
-        JitRuntime* rt = cx->runtime()->jitRuntime();
-        void* handlerAddr =
-            rt->getBaselineDebugModeOSRHandlerAddress(cx, popFrameReg);
-        MOZ_ASSERT(handlerAddr);
-
-        prev->setReturnAddress(reinterpret_cast<uint8_t*>(handlerAddr));
-        frame.baselineFrame()->setDebugModeOSRInfo(recompInfo);
-        frame.baselineFrame()->setOverridePc(recompInfo->pc);
-
+        DebugModeOSRVolatileJitFrameIter::forwardLiveIterators(
+            cx, prev->returnAddress(), retAddr);
+        prev->setReturnAddress(retAddr);
         entryIndex++;
         break;
       }
 
       case FrameType::IonJS: {
         // Nothing to patch.
         InlineFrameIterator inlineIter(cx, &frame);
         while (true) {
@@ -737,231 +607,16 @@ bool jit::RecompileOnStackBaselineScript
       SkipInterpreterFrameEntries(obs, iter, &processed);
     }
   }
   MOZ_ASSERT(processed == entries.length());
 
   return true;
 }
 
-void BaselineDebugModeOSRInfo::popValueInto(PCMappingSlotInfo::SlotLocation loc,
-                                            Value* vp) {
-  switch (loc) {
-    case PCMappingSlotInfo::SlotInR0:
-      valueR0 = vp[stackAdjust];
-      break;
-    case PCMappingSlotInfo::SlotInR1:
-      valueR1 = vp[stackAdjust];
-      break;
-    case PCMappingSlotInfo::SlotIgnore:
-      break;
-    default:
-      MOZ_CRASH("Bad slot location");
-  }
-
-  stackAdjust++;
-}
-
-static inline bool HasForcedReturn(BaselineDebugModeOSRInfo* info, bool rv) {
-  RetAddrEntry::Kind kind = info->frameKind;
-
-  // The debug epilogue always checks its resumption value, so we don't need
-  // to check rv.
-  if (kind == RetAddrEntry::Kind::DebugEpilogue) {
-    return true;
-  }
-
-  // |rv| is the value in ReturnReg. If true, in the case of the prologue or
-  // after yield, it means a forced return.
-  if (kind == RetAddrEntry::Kind::DebugPrologue ||
-      kind == RetAddrEntry::Kind::DebugAfterYield) {
-    return rv;
-  }
-
-  // N.B. The debug trap handler handles its own forced return, so no
-  // need to deal with it here.
-  return false;
-}
-
-static void SyncBaselineDebugModeOSRInfo(BaselineFrame* frame, Value* vp,
-                                         bool rv) {
-  AutoUnsafeCallWithABI unsafe;
-  BaselineDebugModeOSRInfo* info = frame->debugModeOSRInfo();
-  MOZ_ASSERT(info);
-  MOZ_ASSERT(
-      frame->script()->baselineScript()->containsCodeAddress(info->resumeAddr));
-
-  if (HasForcedReturn(info, rv)) {
-    // Load the frame's rval and overwrite the resume address to go to the
-    // epilogue.
-    MOZ_ASSERT(R0 == JSReturnOperand);
-    info->valueR0 = frame->returnValue();
-    info->resumeAddr =
-        frame->script()->baselineScript()->debugOsrEpilogueEntryAddr();
-    return;
-  }
-
-  // Read stack values and make sure R0 and R1 have the right values.
-  unsigned numUnsynced = info->slotInfo.numUnsynced();
-  MOZ_ASSERT(numUnsynced <= 2);
-  if (numUnsynced > 0) {
-    info->popValueInto(info->slotInfo.topSlotLocation(), vp);
-  }
-  if (numUnsynced > 1) {
-    info->popValueInto(info->slotInfo.nextSlotLocation(), vp);
-  }
-
-  // Scale stackAdjust.
-  info->stackAdjust *= sizeof(Value);
-}
-
-static void FinishBaselineDebugModeOSR(BaselineFrame* frame) {
-  AutoUnsafeCallWithABI unsafe;
-  frame->deleteDebugModeOSRInfo();
-
-  // We will return to JIT code now so we have to clear the override pc.
-  frame->clearOverridePc();
-}
-
-void BaselineFrame::deleteDebugModeOSRInfo() {
-  js_delete(getDebugModeOSRInfo());
-  flags_ &= ~HAS_DEBUG_MODE_OSR_INFO;
-}
-
-JitCode* JitRuntime::getBaselineDebugModeOSRHandler(JSContext* cx) {
-  if (!baselineDebugModeOSRHandler_) {
-    MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(cx->runtime()));
-    AutoAllocInAtomsZone az(cx);
-    uint32_t offset;
-    if (JitCode* code = generateBaselineDebugModeOSRHandler(cx, &offset)) {
-      baselineDebugModeOSRHandler_ = code;
-      baselineDebugModeOSRHandlerNoFrameRegPopAddr_ = code->raw() + offset;
-    }
-  }
-
-  return baselineDebugModeOSRHandler_;
-}
-
-void* JitRuntime::getBaselineDebugModeOSRHandlerAddress(JSContext* cx,
-                                                        bool popFrameReg) {
-  if (!getBaselineDebugModeOSRHandler(cx)) {
-    return nullptr;
-  }
-  return popFrameReg ? baselineDebugModeOSRHandler_->raw()
-                     : baselineDebugModeOSRHandlerNoFrameRegPopAddr_.ref();
-}
-
-static void PushCallVMOutputRegisters(MacroAssembler& masm) {
-  // callVMs can use several different output registers, depending on the
-  // type of their outparam.
-  masm.push(ReturnReg);
-  masm.push(ReturnDoubleReg);
-  masm.Push(JSReturnOperand);
-}
-
-static void PopCallVMOutputRegisters(MacroAssembler& masm) {
-  masm.Pop(JSReturnOperand);
-  masm.pop(ReturnDoubleReg);
-  masm.pop(ReturnReg);
-}
-
-static void TakeCallVMOutputRegisters(AllocatableGeneralRegisterSet& regs) {
-  regs.take(ReturnReg);
-  regs.take(JSReturnOperand);
-}
-
-static void EmitBaselineDebugModeOSRHandlerTail(MacroAssembler& masm,
-                                                Register temp) {
-  // Push values we need later.
-  masm.pushValue(Address(temp, offsetof(BaselineDebugModeOSRInfo, valueR0)));
-  masm.pushValue(Address(temp, offsetof(BaselineDebugModeOSRInfo, valueR1)));
-  masm.push(BaselineFrameReg);
-  masm.push(Address(temp, offsetof(BaselineDebugModeOSRInfo, resumeAddr)));
-
-  // Call a stub to free the allocated info.
-  masm.setupUnalignedABICall(temp);
-  masm.loadBaselineFramePtr(BaselineFrameReg, temp);
-  masm.passABIArg(temp);
-  masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, FinishBaselineDebugModeOSR));
-
-  // Restore saved values.
-  AllocatableGeneralRegisterSet jumpRegs(GeneralRegisterSet::All());
-  jumpRegs.take(R0);
-  jumpRegs.take(R1);
-  jumpRegs.take(BaselineFrameReg);
-  Register target = jumpRegs.takeAny();
-
-  masm.pop(target);
-  masm.pop(BaselineFrameReg);
-
-  masm.popValue(R1);
-  masm.popValue(R0);
-
-  masm.jump(target);
-}
-
-JitCode* JitRuntime::generateBaselineDebugModeOSRHandler(
-    JSContext* cx, uint32_t* noFrameRegPopOffsetOut) {
-  StackMacroAssembler masm(cx);
-
-  AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
-  regs.take(BaselineFrameReg);
-  TakeCallVMOutputRegisters(regs);
-  Register temp = regs.takeAny();
-  Register syncedStackStart = regs.takeAny();
-
-  // Pop the frame reg.
-  masm.pop(BaselineFrameReg);
-
-  // Not all patched baseline frames are returning from a situation where
-  // the frame reg is already fixed up.
-  CodeOffset noFrameRegPopOffset(masm.currentOffset());
-
-  // Record the stack pointer for syncing.
-  masm.moveStackPtrTo(syncedStackStart);
-  PushCallVMOutputRegisters(masm);
-  masm.push(BaselineFrameReg);
-
-  // Call a stub to fully initialize the info.
-  masm.setupUnalignedABICall(temp);
-  masm.loadBaselineFramePtr(BaselineFrameReg, temp);
-  masm.passABIArg(temp);
-  masm.passABIArg(syncedStackStart);
-  masm.passABIArg(ReturnReg);
-  masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, SyncBaselineDebugModeOSRInfo));
-
-  // Discard stack values depending on how many were unsynced, as we always
-  // have a fully synced stack in the recompile handler. We arrive here via
-  // a callVM, and prepareCallVM in BaselineCompiler always fully syncs the
-  // stack.
-  masm.pop(BaselineFrameReg);
-  PopCallVMOutputRegisters(masm);
-  masm.loadPtr(
-      Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfScratchValue()),
-      temp);
-  masm.addToStackPtr(
-      Address(temp, offsetof(BaselineDebugModeOSRInfo, stackAdjust)));
-
-  EmitBaselineDebugModeOSRHandlerTail(masm, temp);
-
-  Linker linker(masm, "BaselineDebugModeOSRHandler");
-  JitCode* code = linker.newCode(cx, CodeKind::Other);
-  if (!code) {
-    return nullptr;
-  }
-
-  *noFrameRegPopOffsetOut = noFrameRegPopOffset.offset();
-
-#ifdef JS_ION_PERF
-  writePerfSpewerJitCodeProfile(code, "BaselineDebugModeOSRHandler");
-#endif
-
-  return code;
-}
-
 /* static */
 void DebugModeOSRVolatileJitFrameIter::forwardLiveIterators(JSContext* cx,
                                                             uint8_t* oldAddr,
                                                             uint8_t* newAddr) {
   DebugModeOSRVolatileJitFrameIter* iter;
   for (iter = cx->liveVolatileJitFrameIter_; iter; iter = iter->prev) {
     if (iter->isWasm()) {
       continue;
--- a/js/src/jit/BaselineDebugModeOSR.h
+++ b/js/src/jit/BaselineDebugModeOSR.h
@@ -42,42 +42,16 @@ class DebugModeOSRVolatileJitFrameIter :
     MOZ_ASSERT(*stack == this);
     *stack = prev;
   }
 
   static void forwardLiveIterators(JSContext* cx, uint8_t* oldAddr,
                                    uint8_t* newAddr);
 };
 
-//
-// Auxiliary info to help the DebugModeOSRHandler fix up state.
-//
-struct BaselineDebugModeOSRInfo {
-  uint8_t* resumeAddr;
-  jsbytecode* pc;
-  PCMappingSlotInfo slotInfo;
-  RetAddrEntry::Kind frameKind;
-
-  // Filled in by SyncBaselineDebugModeOSRInfo.
-  uintptr_t stackAdjust;
-  Value valueR0;
-  Value valueR1;
-
-  BaselineDebugModeOSRInfo(jsbytecode* pc, RetAddrEntry::Kind kind)
-      : resumeAddr(nullptr),
-        pc(pc),
-        slotInfo(0),
-        frameKind(kind),
-        stackAdjust(0),
-        valueR0(UndefinedValue()),
-        valueR1(UndefinedValue()) {}
-
-  void popValueInto(PCMappingSlotInfo::SlotLocation loc, Value* vp);
-};
-
 MOZ_MUST_USE bool RecompileOnStackBaselineScriptsForDebugMode(
     JSContext* cx, const Debugger::ExecutionObservableSet& obs,
     Debugger::IsObserving observing);
 
 }  // namespace jit
 }  // namespace js
 
 #endif  // jit_BaselineDebugModeOSR_h
--- a/js/src/jit/BaselineFrame.h
+++ b/js/src/jit/BaselineFrame.h
@@ -8,17 +8,16 @@
 #define jit_BaselineFrame_h
 
 #include "jit/JitFrames.h"
 #include "vm/Stack.h"
 
 namespace js {
 namespace jit {
 
-struct BaselineDebugModeOSRInfo;
 class ICEntry;
 
 // The stack looks like this, fp is the frame pointer:
 //
 // fp+y   arguments
 // fp+x   JitFrameLayout (frame header)
 // fp  => saved frame pointer
 // fp-x   BaselineFrame
@@ -46,59 +45,49 @@ class BaselineFrame {
     PREV_UP_TO_DATE = 1 << 5,
 
     // Frame has execution observed by a Debugger.
     //
     // See comment above 'isDebuggee' in vm/Realm.h for explanation
     // of invariants of debuggee compartments, scripts, and frames.
     DEBUGGEE = 1 << 6,
 
-    // Frame has a BaselineRecompileInfo stashed in the scratch value
-    // slot. See PatchBaselineFramesForDebugMode.
-    HAS_DEBUG_MODE_OSR_INFO = 1 << 7,
-
     // This flag is intended for use whenever the frame is settled on a
     // native code address without a corresponding RetAddrEntry. In this
     // case, the frame contains an explicit bytecode offset for frame
     // iterators.
     //
     // There can also be an override pc if the frame has had its
     // environment chain unwound to a pc during exception handling that is
     // different from its current pc.
     //
     // This flag should never be set on the top frame while we're
     // executing JIT code. In debug builds, it is checked before and
     // after VM calls.
-    HAS_OVERRIDE_PC = 1 << 8,
+    HAS_OVERRIDE_PC = 1 << 7,
 
     // If set, we're handling an exception for this frame. This is set for
     // debug mode OSR sanity checking when it handles corner cases which
     // only arise during exception handling.
-    HANDLING_EXCEPTION = 1 << 9,
+    HANDLING_EXCEPTION = 1 << 8,
   };
 
  protected:  // Silence Clang warning about unused private fields.
   // The fields below are only valid if RUNNING_IN_INTERPRETER.
   JSScript* interpreterScript_;
   jsbytecode* interpreterPC_;
   ICEntry* interpreterICEntry_;
 
   JSObject* envChain_;        // Environment chain (always initialized).
   ArgumentsObject* argsObj_;  // If HAS_ARGS_OBJ, the arguments object.
 
   // We need to split the Value into 2 fields of 32 bits, otherwise the C++
   // compiler may add some padding between the fields.
-  union {
-    struct {
-      uint32_t loScratchValue_;
-      uint32_t hiScratchValue_;
-    };
-    BaselineDebugModeOSRInfo* debugModeOSRInfo_;
-  };
-
+  uint32_t loScratchValue_;
+  uint32_t hiScratchValue_;
   uint32_t flags_;
   uint32_t frameSize_;
   uint32_t loReturnValue_;  // If HAS_RVAL, the frame's return value.
   uint32_t hiReturnValue_;
   uint32_t overrideOffset_;  // If HAS_OVERRIDE_PC, the bytecode offset.
 #if JS_BITS_PER_WORD == 32
   // Ensure frame is 8-byte aligned, see static_assert below.
   uint32_t padding_;
@@ -235,16 +224,26 @@ class BaselineFrame {
     // Note: we can initialize interpreterICEntry_ to nullptr because it won't
     // be used anyway (we are going to enter the exception handler).
     flags_ |= RUNNING_IN_INTERPRETER;
     interpreterScript_ = script;
     interpreterPC_ = pc;
     interpreterICEntry_ = nullptr;
   }
 
+  // Switch a JIT frame on the stack to Interpreter mode. The caller is
+  // responsible for patching the return address into this frame to a location
+  // in the interpreter code.
+  void switchFromJitToInterpreter(jsbytecode* pc) {
+    MOZ_ASSERT(!runningInInterpreter());
+    flags_ |= RUNNING_IN_INTERPRETER;
+    interpreterScript_ = script();
+    setInterpreterPC(pc);
+  }
+
   bool runningInInterpreter() const { return flags_ & RUNNING_IN_INTERPRETER; }
 
   JSScript* interpreterScript() const {
     MOZ_ASSERT(runningInInterpreter());
     return interpreterScript_;
   }
 
   jsbytecode* interpreterPC() const {
@@ -305,35 +304,16 @@ class BaselineFrame {
   bool isDebuggee() const { return flags_ & DEBUGGEE; }
   void setIsDebuggee() { flags_ |= DEBUGGEE; }
   inline void unsetIsDebuggee();
 
   bool isHandlingException() const { return flags_ & HANDLING_EXCEPTION; }
   void setIsHandlingException() { flags_ |= HANDLING_EXCEPTION; }
   void unsetIsHandlingException() { flags_ &= ~HANDLING_EXCEPTION; }
 
-  BaselineDebugModeOSRInfo* debugModeOSRInfo() {
-    MOZ_ASSERT(flags_ & HAS_DEBUG_MODE_OSR_INFO);
-    return debugModeOSRInfo_;
-  }
-
-  BaselineDebugModeOSRInfo* getDebugModeOSRInfo() {
-    if (flags_ & HAS_DEBUG_MODE_OSR_INFO) {
-      return debugModeOSRInfo();
-    }
-    return nullptr;
-  }
-
-  void setDebugModeOSRInfo(BaselineDebugModeOSRInfo* info) {
-    flags_ |= HAS_DEBUG_MODE_OSR_INFO;
-    debugModeOSRInfo_ = info;
-  }
-
-  void deleteDebugModeOSRInfo();
-
   // See the HAS_OVERRIDE_PC comment.
   bool hasOverridePc() const { return flags_ & HAS_OVERRIDE_PC; }
 
   jsbytecode* overridePc() const {
     MOZ_ASSERT(hasOverridePc());
     return script()->offsetToPC(overrideOffset_);
   }
 
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -435,18 +435,17 @@ bool jit::BaselineCompileFromBaselineInt
     }
   }
 
   MOZ_CRASH("Unexpected status");
 }
 
 BaselineScript* BaselineScript::New(
     JSScript* jsscript, uint32_t bailoutPrologueOffset,
-    uint32_t warmUpCheckPrologueOffset, uint32_t debugOsrPrologueOffset,
-    uint32_t debugOsrEpilogueOffset, uint32_t profilerEnterToggleOffset,
+    uint32_t warmUpCheckPrologueOffset, uint32_t profilerEnterToggleOffset,
     uint32_t profilerExitToggleOffset, size_t retAddrEntries,
     size_t pcMappingIndexEntries, size_t pcMappingSize, size_t resumeEntries,
     size_t traceLoggerToggleOffsetEntries) {
   static const unsigned DataAlignment = sizeof(uintptr_t);
 
   size_t retAddrEntriesSize = retAddrEntries * sizeof(RetAddrEntry);
   size_t pcMappingIndexEntriesSize =
       pcMappingIndexEntries * sizeof(PCMappingIndexEntry);
@@ -468,17 +467,16 @@ BaselineScript* BaselineScript::New(
   BaselineScript* script =
       jsscript->zone()->pod_malloc_with_extra<BaselineScript, uint8_t>(
           allocBytes);
   if (!script) {
     return nullptr;
   }
   new (script)
       BaselineScript(bailoutPrologueOffset, warmUpCheckPrologueOffset,
-                     debugOsrPrologueOffset, debugOsrEpilogueOffset,
                      profilerEnterToggleOffset, profilerExitToggleOffset);
 
   size_t offsetCursor = sizeof(BaselineScript);
   MOZ_ASSERT(offsetCursor == AlignBytes(sizeof(BaselineScript), DataAlignment));
 
   script->retAddrEntriesOffset_ = offsetCursor;
   script->retAddrEntries_ = retAddrEntries;
   offsetCursor += paddedRetAddrEntriesSize;
@@ -1115,25 +1113,27 @@ void jit::ToggleBaselineTraceLoggerEngin
 #endif
 
 void BaselineInterpreter::init(JitCode* code, uint32_t interpretOpOffset,
                                uint32_t interpretOpNoDebugTrapOffset,
                                uint32_t profilerEnterToggleOffset,
                                uint32_t profilerExitToggleOffset,
                                CodeOffsetVector&& debugInstrumentationOffsets,
                                CodeOffsetVector&& debugTrapOffsets,
-                               CodeOffsetVector&& codeCoverageOffsets) {
+                               CodeOffsetVector&& codeCoverageOffsets,
+                               const CallVMOffsets& callVMOffsets) {
   code_ = code;
   interpretOpOffset_ = interpretOpOffset;
   interpretOpNoDebugTrapOffset_ = interpretOpNoDebugTrapOffset;
   profilerEnterToggleOffset_ = profilerEnterToggleOffset;
   profilerExitToggleOffset_ = profilerExitToggleOffset;
   debugInstrumentationOffsets_ = std::move(debugInstrumentationOffsets);
   debugTrapOffsets_ = std::move(debugTrapOffsets);
   codeCoverageOffsets_ = std::move(codeCoverageOffsets);
+  callVMOffsets_ = callVMOffsets;
 }
 
 bool jit::GenerateBaselineInterpreter(JSContext* cx,
                                       BaselineInterpreter& interpreter) {
   // Temporary IsBaselineInterpreterEnabled check to not generate the
   // interpreter code (until it's enabled by default).
   if (IsBaselineInterpreterEnabled()) {
     BaselineInterpreterGenerator generator(cx);
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -210,24 +210,16 @@ struct BaselineScript final {
   // Early Ion bailouts will enter at this address. This is after frame
   // construction and before environment chain is initialized.
   uint32_t bailoutPrologueOffset_;
 
   // Baseline Interpreter can enter Baseline Compiler code at this address. This
   // is right after the warm-up counter check in the prologue.
   uint32_t warmUpCheckPrologueOffset_;
 
-  // Baseline Debug OSR during prologue will enter at this address. This is
-  // right after where a debug prologue VM call would have returned.
-  uint32_t debugOsrPrologueOffset_;
-
-  // Baseline Debug OSR during epilogue will enter at this address. This is
-  // right after where a debug epilogue VM call would have returned.
-  uint32_t debugOsrEpilogueOffset_;
-
   // The offsets for the toggledJump instructions for profiler instrumentation.
   uint32_t profilerEnterToggleOffset_;
   uint32_t profilerExitToggleOffset_;
 
   // The offsets and event used for Tracelogger toggling.
 #ifdef JS_TRACE_LOGGING
 #  ifdef DEBUG
   bool traceLoggerScriptsEnabled_ = false;
@@ -305,35 +297,32 @@ struct BaselineScript final {
   IonBuilder* pendingBuilder_ = nullptr;
 
   ControlFlowGraph* controlFlowGraph_ = nullptr;
 
   // Use BaselineScript::New to create new instances. It will properly
   // allocate trailing objects.
   BaselineScript(uint32_t bailoutPrologueOffset,
                  uint32_t warmUpCheckPrologueOffset,
-                 uint32_t debugOsrPrologueOffset,
-                 uint32_t debugOsrEpilogueOffset,
                  uint32_t profilerEnterToggleOffset,
                  uint32_t profilerExitToggleOffset)
       : bailoutPrologueOffset_(bailoutPrologueOffset),
         warmUpCheckPrologueOffset_(warmUpCheckPrologueOffset),
-        debugOsrPrologueOffset_(debugOsrPrologueOffset),
-        debugOsrEpilogueOffset_(debugOsrEpilogueOffset),
         profilerEnterToggleOffset_(profilerEnterToggleOffset),
         profilerExitToggleOffset_(profilerExitToggleOffset) {}
 
  public:
-  static BaselineScript* New(
-      JSScript* jsscript, uint32_t bailoutPrologueOffset,
-      uint32_t warmUpCheckPrologueOffset, uint32_t debugOsrPrologueOffset,
-      uint32_t debugOsrEpilogueOffset, uint32_t profilerEnterToggleOffset,
-      uint32_t profilerExitToggleOffset, size_t retAddrEntries,
-      size_t pcMappingIndexEntries, size_t pcMappingSize, size_t resumeEntries,
-      size_t traceLoggerToggleOffsetEntries);
+  static BaselineScript* New(JSScript* jsscript, uint32_t bailoutPrologueOffset,
+                             uint32_t warmUpCheckPrologueOffset,
+                             uint32_t profilerEnterToggleOffset,
+                             uint32_t profilerExitToggleOffset,
+                             size_t retAddrEntries,
+                             size_t pcMappingIndexEntries, size_t pcMappingSize,
+                             size_t resumeEntries,
+                             size_t traceLoggerToggleOffsetEntries);
 
   static void Trace(JSTracer* trc, BaselineScript* script);
   static void Destroy(FreeOp* fop, BaselineScript* script);
 
   static inline size_t offsetOfMethod() {
     return offsetof(BaselineScript, method_);
   }
 
@@ -358,22 +347,16 @@ struct BaselineScript final {
   bool usesEnvironmentChain() const { return flags_ & USES_ENVIRONMENT_CHAIN; }
 
   uint8_t* bailoutPrologueEntryAddr() const {
     return method_->raw() + bailoutPrologueOffset_;
   }
   uint8_t* warmUpCheckPrologueAddr() const {
     return method_->raw() + warmUpCheckPrologueOffset_;
   }
-  uint8_t* debugOsrPrologueEntryAddr() const {
-    return method_->raw() + debugOsrPrologueOffset_;
-  }
-  uint8_t* debugOsrEpilogueEntryAddr() const {
-    return method_->raw() + debugOsrEpilogueOffset_;
-  }
 
   RetAddrEntry* retAddrEntryList() {
     return (RetAddrEntry*)(reinterpret_cast<uint8_t*>(this) +
                            retAddrEntriesOffset_);
   }
   uint8_t** resumeEntryList() {
     return (uint8_t**)(reinterpret_cast<uint8_t*>(this) + resumeEntriesOffset_);
   }
@@ -654,36 +637,64 @@ class BaselineInterpreter {
   // Offsets of toggled calls to the DebugTrapHandler trampoline (for
   // breakpoints and stepping).
   CodeOffsetVector debugTrapOffsets_;
 
   // Offsets of toggled jumps for code coverage.
   CodeOffsetVector codeCoverageOffsets_;
 
  public:
+  // Offsets of some callVMs for BaselineDebugModeOSR.
+  struct CallVMOffsets {
+    uint32_t debugPrologueOffset = 0;
+    uint32_t debugEpilogueOffset = 0;
+    uint32_t debugAfterYieldOffset = 0;
+  };
+
+ private:
+  CallVMOffsets callVMOffsets_;
+
+  uint8_t* codeAtOffset(uint32_t offset) const {
+    MOZ_ASSERT(offset > 0);
+    MOZ_ASSERT(offset < code_->instructionsSize());
+    return codeRaw() + offset;
+  }
+
+ public:
   BaselineInterpreter() = default;
 
   BaselineInterpreter(const BaselineInterpreter&) = delete;
   void operator=(const BaselineInterpreter&) = delete;
 
   void init(JitCode* code, uint32_t interpretOpOffset,
             uint32_t interpretOpNoDebugTrapOffset,
             uint32_t profilerEnterToggleOffset,
             uint32_t profilerExitToggleOffset,
             CodeOffsetVector&& debugInstrumentationOffsets,
             CodeOffsetVector&& debugTrapOffsets,
-            CodeOffsetVector&& codeCoverageOffsets);
+            CodeOffsetVector&& codeCoverageOffsets,
+            const CallVMOffsets& callVMOffsets);
 
   uint8_t* codeRaw() const { return code_->raw(); }
 
+  uint8_t* retAddrForDebugPrologueCallVM() const {
+    return codeAtOffset(callVMOffsets_.debugPrologueOffset);
+  }
+  uint8_t* retAddrForDebugEpilogueCallVM() const {
+    return codeAtOffset(callVMOffsets_.debugEpilogueOffset);
+  }
+  uint8_t* retAddrForDebugAfterYieldCallVM() const {
+    return codeAtOffset(callVMOffsets_.debugAfterYieldOffset);
+  }
+
   TrampolinePtr interpretOpAddr() const {
-    return TrampolinePtr(codeRaw() + interpretOpOffset_);
+    return TrampolinePtr(codeAtOffset(interpretOpOffset_));
   }
   TrampolinePtr interpretOpNoDebugTrapAddr() const {
-    return TrampolinePtr(codeRaw() + interpretOpNoDebugTrapOffset_);
+    return TrampolinePtr(codeAtOffset(interpretOpNoDebugTrapOffset_));
   }
 
   void toggleProfilerInstrumentation(bool enable);
   void toggleDebuggerInstrumentation(bool enable);
 
   void toggleCodeCoverageInstrumentationUnchecked(bool enable);
   void toggleCodeCoverageInstrumentation(bool enable);
 };
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -166,17 +166,16 @@ JitRuntime::JitRuntime()
       bailoutHandlerOffset_(0),
       argumentsRectifierOffset_(0),
       argumentsRectifierReturnOffset_(0),
       invalidatorOffset_(0),
       lazyLinkStubOffset_(0),
       interpreterStubOffset_(0),
       doubleToInt32ValueStubOffset_(0),
       debugTrapHandlers_(),
-      baselineDebugModeOSRHandler_(nullptr),
       baselineInterpreter_(),
       trampolineCode_(nullptr),
       jitcodeGlobalTable_(nullptr),
 #ifdef DEBUG
       ionBailAfter_(0),
 #endif
       numFinishedBuilders_(0),
       ionLazyLinkListSize_(0) {
--- a/js/src/jit/JSJitFrameIter.cpp
+++ b/js/src/jit/JSJitFrameIter.cpp
@@ -627,23 +627,16 @@ bool JSJitProfilingFrameIterator::tryIni
   return false;
 }
 
 void JSJitProfilingFrameIterator::fixBaselineReturnAddress() {
   MOZ_ASSERT(type_ == FrameType::BaselineJS);
   BaselineFrame* bl = (BaselineFrame*)(fp_ - BaselineFrame::FramePointerOffset -
                                        BaselineFrame::Size());
 
-  // Debug mode OSR for Baseline uses a "continuation fixer" and stashes the
-  // actual return address in an auxiliary structure.
-  if (BaselineDebugModeOSRInfo* info = bl->getDebugModeOSRInfo()) {
-    resumePCinCurrentFrame_ = info->resumeAddr;
-    return;
-  }
-
   // Certain exception handling cases such as debug OSR or resuming a generator
   // with .throw() will use BaselineFrame::setOverridePc() to indicate the
   // effective |pc|. We translate the effective-pc into a Baseline code
   // address.
   if (jsbytecode* overridePC = bl->maybeOverridePc()) {
     JSScript* script = bl->script();
     if (bl->runningInInterpreter()) {
       // The return address won't be used for pc mapping when running in the
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -696,23 +696,16 @@ void HandleException(ResumeFromException
       //     and clear it before it returns to JIT code.
       jsbytecode* pc;
       frame.baselineScriptAndPc(nullptr, &pc);
       AutoBaselineHandlingException handlingException(frame.baselineFrame(),
                                                       pc);
 
       HandleExceptionBaseline(cx, frame, rfe, pc);
 
-      // If we are propagating an exception through a frame with
-      // on-stack recompile info, we should free the allocated
-      // RecompileInfo struct before we leave this block, as we will not
-      // be returning to the recompile handler.
-      auto deleteDebugModeOSRInfo = mozilla::MakeScopeExit(
-          [=] { frame.baselineFrame()->deleteDebugModeOSRInfo(); });
-
       if (rfe->kind != ResumeFromException::RESUME_ENTRY_FRAME &&
           rfe->kind != ResumeFromException::RESUME_FORCED_RETURN) {
         return;
       }
 
       TraceLogStopEvent(logger, TraceLogger_Baseline);
       TraceLogStopEvent(logger, TraceLogger_Scripts);
 
--- a/js/src/jit/JitRealm.h
+++ b/js/src/jit/JitRealm.h
@@ -190,20 +190,16 @@ class JitRuntime {
   // Note: this stub treats -0 as +0 and may clobber R1.scratchReg().
   WriteOnceData<uint32_t> doubleToInt32ValueStubOffset_;
 
   // Thunk used by the debugger for breakpoint and step mode.
   mozilla::EnumeratedArray<DebugTrapHandlerKind, DebugTrapHandlerKind::Count,
                            WriteOnceData<JitCode*>>
       debugTrapHandlers_;
 
-  // Thunk used to fix up on-stack recompile of baseline scripts.
-  WriteOnceData<JitCode*> baselineDebugModeOSRHandler_;
-  WriteOnceData<void*> baselineDebugModeOSRHandlerNoFrameRegPopAddr_;
-
   // BaselineInterpreter state.
   BaselineInterpreter baselineInterpreter_;
 
   // Code for trampolines and VMFunction wrappers.
   WriteOnceData<JitCode*> trampolineCode_;
 
   // Map VMFunction addresses to the offset of the wrapper in
   // trampolineCode_.
@@ -262,18 +258,16 @@ class JitRuntime {
   BailoutTable generateBailoutTable(MacroAssembler& masm, Label* bailoutTail,
                                     uint32_t frameClass);
   void generateBailoutHandler(MacroAssembler& masm, Label* bailoutTail);
   void generateInvalidator(MacroAssembler& masm, Label* bailoutTail);
   uint32_t generatePreBarrier(JSContext* cx, MacroAssembler& masm,
                               MIRType type);
   void generateFreeStub(MacroAssembler& masm);
   JitCode* generateDebugTrapHandler(JSContext* cx, DebugTrapHandlerKind kind);
-  JitCode* generateBaselineDebugModeOSRHandler(
-      JSContext* cx, uint32_t* noFrameRegPopOffsetOut);
 
   bool generateVMWrapper(JSContext* cx, MacroAssembler& masm,
                          const VMFunctionData& f, void* nativeFun,
                          uint32_t* wrapperOffset);
 
   template <typename IdT>
   bool generateVMWrappers(JSContext* cx, MacroAssembler& masm,
                           VMWrapperOffsets& offsets);
@@ -324,18 +318,16 @@ class JitRuntime {
     return trampolineCode(functionWrapperOffsets_[size_t(funId)]);
   }
   TrampolinePtr getVMWrapper(TailCallVMFunctionId funId) const {
     MOZ_ASSERT(trampolineCode_);
     return trampolineCode(tailCallFunctionWrapperOffsets_[size_t(funId)]);
   }
 
   JitCode* debugTrapHandler(JSContext* cx, DebugTrapHandlerKind kind);
-  JitCode* getBaselineDebugModeOSRHandler(JSContext* cx);
-  void* getBaselineDebugModeOSRHandlerAddress(JSContext* cx, bool popFrameReg);
 
   BaselineInterpreter& baselineInterpreter() { return baselineInterpreter_; }
 
   TrampolinePtr getGenericBailoutHandler() const {
     return trampolineCode(bailoutHandlerOffset_);
   }
 
   TrampolinePtr getExceptionTail() const {
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -911,17 +911,16 @@ bool DebugEpilogue(JSContext* cx, Baseli
   EnvironmentIter ei(cx, frame, pc);
   UnwindAllEnvironmentsInFrame(cx, ei);
   JSScript* script = frame->script();
   frame->setOverridePc(script->offsetToPC(0));
 
   if (!ok) {
     // Pop this frame by updating packedExitFP, so that the exception
     // handling code will start at the previous frame.
-    frame->deleteDebugModeOSRInfo();
     JitFrameLayout* prefix = frame->framePrefix();
     EnsureBareExitFrame(cx->activation()->asJit(), prefix);
     return false;
   }
 
   // Clear the override pc. This is not necessary for correctness: the frame
   // will return immediately, but this simplifies the check we emit in debug
   // builds after each callVM, to ensure this flag is not set.