Bug 1583487 part 2 - Remove Baseline's WarmUpCount IC, use a plain VM call instead. r=tcampbell
authorJan de Mooij <jdemooij@mozilla.com>
Wed, 02 Oct 2019 12:56:53 +0000
changeset 496293 47830868674f0f617d5a8022da70b02df53913e6
parent 496292 ed2fda5017852c8b42a0c9b460d32bf36a2236c5
child 496294 989b75468b7e5475f73ff857b7da0a021e103f46
push id97158
push userjdemooij@mozilla.com
push dateFri, 04 Oct 2019 09:10:43 +0000
treeherderautoland@989b75468b7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell
bugs1583487
milestone71.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 1583487 part 2 - Remove Baseline's WarmUpCount IC, use a plain VM call instead. r=tcampbell We never attached stubs to this and the Baseline Interpreter does not use this IC at all so it's nicer and simpler to use a plain VM call. Differential Revision: https://phabricator.services.mozilla.com/D47881
js/src/jit/BaselineCodeGen.cpp
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineIC.h
js/src/jit/BaselineICList.h
js/src/jit/Ion.cpp
js/src/jit/Ion.h
js/src/jit/VMFunctionList-inl.h
js/src/vm/Opcodes.h
--- a/js/src/jit/BaselineCodeGen.cpp
+++ b/js/src/jit/BaselineCodeGen.cpp
@@ -1299,58 +1299,123 @@ bool BaselineCompilerCodeGen::emitWarmUp
     }
 
     if (!LoopEntryCanIonOsr(pc)) {
       // OSR into Ion not possible at this loop entry.
       return true;
     }
   }
 
-  Label skipCall;
+  Label done;
 
   const OptimizationInfo* info =
       IonOptimizations.get(IonOptimizations.firstLevel());
   uint32_t warmUpThreshold = info->compilerWarmUpThreshold(script, pc);
-  masm.branch32(Assembler::LessThan, countReg, Imm32(warmUpThreshold),
-                &skipCall);
+  masm.branch32(Assembler::LessThan, countReg, Imm32(warmUpThreshold), &done);
 
   // Do nothing if Ion is already compiling this script off-thread or if Ion has
   // been disabled for this script.
   masm.movePtr(ImmPtr(script->jitScript()), scriptReg);
   masm.loadPtr(Address(scriptReg, JitScript::offsetOfIonScript()), scriptReg);
   masm.branchPtr(Assembler::Equal, scriptReg, ImmPtr(IonCompilingScriptPtr),
-                 &skipCall);
+                 &done);
   masm.branchPtr(Assembler::Equal, scriptReg, ImmPtr(IonDisabledScriptPtr),
-                 &skipCall);
+                 &done);
 
   // Try to compile and/or finish a compilation.
   if (JSOp(*pc) == JSOP_LOOPENTRY) {
-    // During the loop entry we can try to OSR into ion. The IC for this expects
-    // the frame size in R0.scratchReg().
+    // Try to OSR into Ion.
     computeFrameSize(R0.scratchReg());
-    if (!emitNextIC()) {
+
+    prepareVMCall();
+
+    pushBytecodePCArg();
+    pushArg(R0.scratchReg());
+    masm.PushBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
+
+    using Fn = bool (*)(JSContext*, BaselineFrame*, uint32_t, jsbytecode*,
+                        IonOsrTempData**);
+    if (!callVM<Fn, IonCompileScriptForBaselineOSR>()) {
       return false;
     }
+
+    // The return register holds the IonOsrTempData*. Perform OSR if it's not
+    // nullptr.
+    static_assert(ReturnReg != OsrFrameReg,
+                  "Code below depends on osrDataReg != OsrFrameReg");
+    Register osrDataReg = ReturnReg;
+    masm.branchTestPtr(Assembler::Zero, osrDataReg, osrDataReg, &done);
+
+    // Success! Switch from Baseline JIT code to Ion JIT code.
+
+    // At this point, stack looks like:
+    //
+    //  +-> [...Calling-Frame...]
+    //  |   [...Actual-Args/ThisV/ArgCount/Callee...]
+    //  |   [Descriptor]
+    //  |   [Return-Addr]
+    //  +---[Saved-FramePtr]
+    //      [...Baseline-Frame...]
+
+    // Restore the stack pointer so that the return address is on top of
+    // the stack.
+    masm.addToStackPtr(Imm32(frame.frameSize()));
+
+#ifdef DEBUG
+    // Get a scratch register that's not osrDataReg or OsrFrameReg.
+    AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
+    regs.take(BaselineFrameReg);
+    regs.take(osrDataReg);
+    regs.take(OsrFrameReg);
+
+    Register scratchReg = regs.takeAny();
+
+    // If profiler instrumentation is on, ensure that lastProfilingFrame is
+    // the frame currently being OSR-ed
+    {
+      Label checkOk;
+      AbsoluteAddress addressOfEnabled(
+          cx->runtime()->geckoProfiler().addressOfEnabled());
+      masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &checkOk);
+      masm.loadPtr(AbsoluteAddress((void*)&cx->jitActivation), scratchReg);
+      masm.loadPtr(
+          Address(scratchReg, JitActivation::offsetOfLastProfilingFrame()),
+          scratchReg);
+
+      // It may be the case that we entered the baseline frame with
+      // profiling turned off on, then in a call within a loop (i.e. a
+      // callee frame), turn on profiling, then return to this frame,
+      // and then OSR with profiling turned on.  In this case, allow for
+      // lastProfilingFrame to be null.
+      masm.branchPtr(Assembler::Equal, scratchReg, ImmWord(0), &checkOk);
+
+      masm.branchStackPtr(Assembler::Equal, scratchReg, &checkOk);
+      masm.assumeUnreachable("Baseline OSR lastProfilingFrame mismatch.");
+      masm.bind(&checkOk);
+    }
+#endif
+
+    // Jump into Ion.
+    masm.loadPtr(Address(osrDataReg, IonOsrTempData::offsetOfBaselineFrame()),
+                 OsrFrameReg);
+    masm.jump(Address(osrDataReg, IonOsrTempData::offsetOfJitCode()));
   } else {
-    // To call stubs we need to have an opcode. This code handles the
-    // prologue and there is no dedicatd opcode present. Therefore use an
-    // annotated vm call.
     prepareVMCall();
 
     masm.PushBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
     const RetAddrEntry::Kind kind = RetAddrEntry::Kind::WarmupCounter;
 
     using Fn = bool (*)(JSContext*, BaselineFrame*);
     if (!callVM<Fn, IonCompileScriptForBaselineAtEntry>(kind)) {
       return false;
     }
   }
-  masm.bind(&skipCall);
-
+
+  masm.bind(&done);
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emitWarmUpCounterIncrement() {
   Register scriptReg = R2.scratchReg();
   Register countReg = R0.scratchReg();
 
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -303,24 +303,16 @@ bool JitScript::initICEntriesAndBytecode
       case JSOP_STRICTEQ:
       case JSOP_STRICTNE: {
         ICStub* stub = alloc.newStub<ICCompare_Fallback>(Kind::Compare);
         if (!addIC(loc, stub)) {
           return false;
         }
         break;
       }
-      case JSOP_LOOPENTRY: {
-        ICStub* stub =
-            alloc.newStub<ICWarmUpCounter_Fallback>(Kind::WarmUpCounter);
-        if (!addIC(loc, stub)) {
-          return false;
-        }
-        break;
-      }
       case JSOP_NEWARRAY: {
         ObjectGroup* group = ObjectGroup::allocationSiteGroup(
             cx, script, loc.toRawBytecode(), JSProto_Array);
         if (!group) {
           return false;
         }
         ICStub* stub =
             alloc.newStub<ICNewArray_Fallback>(Kind::NewArray, group);
@@ -568,17 +560,16 @@ void ICStubIterator::unlink(JSContext* c
 
 /* static */
 bool ICStub::NonCacheIRStubMakesGCCalls(Kind kind) {
   MOZ_ASSERT(IsValidKind(kind));
   MOZ_ASSERT(!IsCacheIRKind(kind));
 
   switch (kind) {
     case Call_Fallback:
-    case WarmUpCounter_Fallback:
     // These three fallback stubs don't actually make non-tail calls,
     // but the fallback code for the bailout path needs to pop the stub frame
     // pushed during the bailout.
     case GetProp_Fallback:
     case SetProp_Fallback:
     case GetElem_Fallback:
       return true;
     default:
@@ -733,226 +724,16 @@ static void TryAttachStub(const char* na
         break;
     }
     if (!attached) {
       stub->state().trackNotAttached();
     }
   }
 }
 
-//
-// WarmUpCounter_Fallback
-//
-
-/* clang-format off */
-// The following data is kept in a temporary heap-allocated buffer, stored in
-// JitRuntime (high memory addresses at top, low at bottom):
-//
-//     +----->+=================================+  --      <---- High Address
-//     |      |                                 |   |
-//     |      |     ...BaselineFrame...         |   |-- Copy of BaselineFrame + stack values
-//     |      |                                 |   |
-//     |      +---------------------------------+   |
-//     |      |                                 |   |
-//     |      |     ...Locals/Stack...          |   |
-//     |      |                                 |   |
-//     |      +=================================+  --
-//     |      |     Padding(Maybe Empty)        |
-//     |      +=================================+  --
-//     +------|-- baselineFrame                 |   |-- IonOsrTempData
-//            |   jitcode                       |   |
-//            +=================================+  --      <---- Low Address
-//
-// A pointer to the IonOsrTempData is returned.
-/* clang-format on */
-
-struct IonOsrTempData {
-  void* jitcode;
-  uint8_t* baselineFrame;
-};
-
-static IonOsrTempData* PrepareOsrTempData(JSContext* cx, BaselineFrame* frame,
-                                          uint32_t frameSize, void* jitcode) {
-  uint32_t numValueSlots = frame->numValueSlots(frameSize);
-
-  // Calculate the amount of space to allocate:
-  //      BaselineFrame space:
-  //          (sizeof(Value) * numValueSlots)
-  //        + sizeof(BaselineFrame)
-  //
-  //      IonOsrTempData space:
-  //          sizeof(IonOsrTempData)
-
-  size_t frameSpace = sizeof(BaselineFrame) + sizeof(Value) * numValueSlots;
-  size_t ionOsrTempDataSpace = sizeof(IonOsrTempData);
-
-  size_t totalSpace = AlignBytes(frameSpace, sizeof(Value)) +
-                      AlignBytes(ionOsrTempDataSpace, sizeof(Value));
-
-  IonOsrTempData* info = (IonOsrTempData*)cx->allocateOsrTempData(totalSpace);
-  if (!info) {
-    ReportOutOfMemory(cx);
-    return nullptr;
-  }
-
-  memset(info, 0, totalSpace);
-
-  info->jitcode = jitcode;
-
-  // Copy the BaselineFrame + local/stack Values to the buffer. Arguments and
-  // |this| are not copied but left on the stack: the Baseline and Ion frame
-  // share the same frame prefix and Ion won't clobber these values. Note
-  // that info->baselineFrame will point to the *end* of the frame data, like
-  // the frame pointer register in baseline frames.
-  uint8_t* frameStart =
-      (uint8_t*)info + AlignBytes(ionOsrTempDataSpace, sizeof(Value));
-  info->baselineFrame = frameStart + frameSpace;
-
-  memcpy(frameStart, (uint8_t*)frame - numValueSlots * sizeof(Value),
-         frameSpace);
-
-  JitSpew(JitSpew_BaselineOSR, "Allocated IonOsrTempData at %p", (void*)info);
-  JitSpew(JitSpew_BaselineOSR, "Jitcode is %p", info->jitcode);
-
-  // All done.
-  return info;
-}
-
-bool DoWarmUpCounterFallbackOSR(JSContext* cx, BaselineFrame* frame,
-                                uint32_t frameSize,
-                                ICWarmUpCounter_Fallback* stub,
-                                IonOsrTempData** infoPtr) {
-  MOZ_ASSERT(infoPtr);
-  *infoPtr = nullptr;
-
-  MOZ_ASSERT(frame->debugFrameSize() == frameSize);
-
-  RootedScript script(cx, frame->script());
-  jsbytecode* pc = stub->icEntry()->pc(script);
-  MOZ_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY);
-
-  FallbackICSpew(cx, stub, "WarmUpCounter(%d)", int(script->pcToOffset(pc)));
-
-  if (!IonCompileScriptForBaseline(cx, frame, frameSize, pc)) {
-    return false;
-  }
-
-  if (!script->hasIonScript() || script->ionScript()->osrPc() != pc ||
-      script->ionScript()->bailoutExpected() || frame->isDebuggee()) {
-    return true;
-  }
-
-  IonScript* ion = script->ionScript();
-  MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled() ==
-             ion->hasProfilingInstrumentation());
-  MOZ_ASSERT(ion->osrPc() == pc);
-
-  JitSpew(JitSpew_BaselineOSR, "  OSR possible!");
-  void* jitcode = ion->method()->raw() + ion->osrEntryOffset();
-
-  // Prepare the temporary heap copy of the fake InterpreterFrame and actual
-  // args list.
-  JitSpew(JitSpew_BaselineOSR, "Got jitcode.  Preparing for OSR into ion.");
-  IonOsrTempData* info = PrepareOsrTempData(cx, frame, frameSize, jitcode);
-  if (!info) {
-    return false;
-  }
-  *infoPtr = info;
-
-  return true;
-}
-
-bool FallbackICCodeCompiler::emit_WarmUpCounter() {
-  // Note: frame size is stored in R0.scratchReg().
-
-  // Push a stub frame so that we can perform a non-tail call.
-  enterStubFrame(masm, R1.scratchReg());
-
-  Label noCompiledCode;
-  // Call DoWarmUpCounterFallbackOSR to compile/check-for Ion-compiled function
-  {
-    // Push stub pointer, frame size, frame pointer.
-    masm.push(ICStubReg);
-    masm.push(R0.scratchReg());
-    pushStubPayload(masm, R0.scratchReg());
-
-    using Fn = bool (*)(JSContext*, BaselineFrame*, uint32_t,
-                        ICWarmUpCounter_Fallback*, IonOsrTempData**);
-    if (!callVM<Fn, DoWarmUpCounterFallbackOSR>(masm)) {
-      return false;
-    }
-
-    leaveStubFrame(masm);
-
-    // If no JitCode was found, then skip just exit the IC.
-    masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, &noCompiledCode);
-  }
-
-  // Get a scratch register.
-  AllocatableGeneralRegisterSet regs(availableGeneralRegs(0));
-  Register osrDataReg = ReturnReg;
-  regs.take(osrDataReg);
-  regs.takeUnchecked(OsrFrameReg);
-
-  Register scratchReg = regs.takeAny();
-
-  // At this point, stack looks like:
-  //  +-> [...Calling-Frame...]
-  //  |   [...Actual-Args/ThisV/ArgCount/Callee...]
-  //  |   [Descriptor]
-  //  |   [Return-Addr]
-  //  +---[Saved-FramePtr]            <-- BaselineFrameReg points here.
-  //      [...Baseline-Frame...]
-
-  // Restore the stack pointer to point to the saved frame pointer.
-  masm.moveToStackPtr(BaselineFrameReg);
-
-  // Discard saved frame pointer, so that the return address is on top of
-  // the stack.
-  masm.pop(scratchReg);
-
-#ifdef DEBUG
-  // If profiler instrumentation is on, ensure that lastProfilingFrame is
-  // the frame currently being OSR-ed
-  {
-    Label checkOk;
-    AbsoluteAddress addressOfEnabled(
-        cx->runtime()->geckoProfiler().addressOfEnabled());
-    masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &checkOk);
-    masm.loadPtr(AbsoluteAddress((void*)&cx->jitActivation), scratchReg);
-    masm.loadPtr(
-        Address(scratchReg, JitActivation::offsetOfLastProfilingFrame()),
-        scratchReg);
-
-    // It may be the case that we entered the baseline frame with
-    // profiling turned off on, then in a call within a loop (i.e. a
-    // callee frame), turn on profiling, then return to this frame,
-    // and then OSR with profiling turned on.  In this case, allow for
-    // lastProfilingFrame to be null.
-    masm.branchPtr(Assembler::Equal, scratchReg, ImmWord(0), &checkOk);
-
-    masm.branchStackPtr(Assembler::Equal, scratchReg, &checkOk);
-    masm.assumeUnreachable("Baseline OSR lastProfilingFrame mismatch.");
-    masm.bind(&checkOk);
-  }
-#endif
-
-  // Jump into Ion.
-  masm.loadPtr(Address(osrDataReg, offsetof(IonOsrTempData, jitcode)),
-               scratchReg);
-  masm.loadPtr(Address(osrDataReg, offsetof(IonOsrTempData, baselineFrame)),
-               OsrFrameReg);
-  masm.jump(scratchReg);
-
-  // No jitcode available, do nothing.
-  masm.bind(&noCompiledCode);
-  EmitReturnFromIC(masm);
-  return true;
-}
-
 void ICFallbackStub::unlinkStub(Zone* zone, ICStub* prev, ICStub* stub) {
   MOZ_ASSERT(stub->next());
 
   // If stub is the last optimized stub, update lastStubPtrAddr.
   if (stub->next() == this) {
     MOZ_ASSERT(lastStubPtrAddr_ == stub->addressOfNext());
     if (prev) {
       lastStubPtrAddr_ = prev->addressOfNext();
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -1020,26 +1020,16 @@ class ICStubCompiler : public ICStubComp
   static ICStubSpace* StubSpaceForStub(bool makesGCCalls, JSScript* script);
 
   ICStubSpace* getStubSpace(JSScript* outerScript) {
     return StubSpaceForStub(ICStub::NonCacheIRStubMakesGCCalls(kind),
                             outerScript);
   }
 };
 
-// WarmUpCounter_Fallback
-
-// A WarmUpCounter IC chain has only the fallback stub.
-class ICWarmUpCounter_Fallback : public ICFallbackStub {
-  friend class ICStubSpace;
-
-  explicit ICWarmUpCounter_Fallback(TrampolinePtr stubCode)
-      : ICFallbackStub(ICStub::WarmUpCounter_Fallback, stubCode) {}
-};
-
 // Monitored fallback stubs - as the name implies.
 class ICMonitoredFallbackStub : public ICFallbackStub {
  protected:
   // Pointer to the fallback monitor stub. Created lazily by
   // getFallbackMonitorStub if needed.
   ICTypeMonitor_Fallback* fallbackMonitorStub_;
 
   ICMonitoredFallbackStub(Kind kind, TrampolinePtr stubCode)
@@ -1856,21 +1846,16 @@ extern MOZ_MUST_USE bool TypeMonitorResu
                                            BaselineFrame* frame,
                                            HandleScript script, jsbytecode* pc,
                                            HandleValue val);
 
 extern bool DoTypeUpdateFallback(JSContext* cx, BaselineFrame* frame,
                                  ICCacheIR_Updated* stub, HandleValue objval,
                                  HandleValue value);
 
-extern bool DoWarmUpCounterFallbackOSR(JSContext* cx, BaselineFrame* frame,
-                                       uint32_t frameSize,
-                                       ICWarmUpCounter_Fallback* stub,
-                                       IonOsrTempData** infoPtr);
-
 extern bool DoCallFallback(JSContext* cx, BaselineFrame* frame,
                            ICCall_Fallback* stub, uint32_t argc, Value* vp,
                            MutableHandleValue res);
 
 extern bool DoSpreadCallFallback(JSContext* cx, BaselineFrame* frame,
                                  ICCall_Fallback* stub, Value* vp,
                                  MutableHandleValue res);
 
--- a/js/src/jit/BaselineICList.h
+++ b/js/src/jit/BaselineICList.h
@@ -8,18 +8,16 @@
 #define jit_BaselineICList_h
 
 namespace js {
 namespace jit {
 
 // List of Baseline IC stub kinds. The stub kind determines the structure of the
 // ICStub data.
 #define IC_BASELINE_STUB_KIND_LIST(_) \
-  _(WarmUpCounter_Fallback)           \
-                                      \
   _(TypeMonitor_Fallback)             \
   _(TypeMonitor_SingleObject)         \
   _(TypeMonitor_ObjectGroup)          \
   _(TypeMonitor_PrimitiveSet)         \
   _(TypeMonitor_AnyValue)             \
                                       \
   _(TypeUpdate_Fallback)              \
   _(TypeUpdate_SingleObject)          \
@@ -69,17 +67,16 @@ namespace jit {
   _(CacheIR_Updated)
 
 // List of fallback trampolines. Each of these fallback trampolines exists as
 // part of the JitRuntime. Note that some fallback stubs in previous list may
 // have multiple trampolines in this list. For example, Call_Fallback has
 // constructing/spread variants here with different calling conventions needing
 // different trampolines.
 #define IC_BASELINE_FALLBACK_CODE_KIND_LIST(_) \
-  _(WarmUpCounter)                             \
   _(TypeMonitor)                               \
   _(TypeUpdate)                                \
   _(NewArray)                                  \
   _(NewObject)                                 \
   _(ToBool)                                    \
   _(UnaryArith)                                \
   _(Call)                                      \
   _(CallConstructing)                          \
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2342,18 +2342,18 @@ static MethodStatus BaselineCanEnterAtBr
   // Or when we didn't force a recompile.
   if (script->hasIonScript() && pc != script->ionScript()->osrPc()) {
     return Method_Skipped;
   }
 
   return Method_Compiled;
 }
 
-bool jit::IonCompileScriptForBaseline(JSContext* cx, BaselineFrame* frame,
-                                      uint32_t frameSize, jsbytecode* pc) {
+static bool IonCompileScriptForBaseline(JSContext* cx, BaselineFrame* frame,
+                                        uint32_t frameSize, jsbytecode* pc) {
   MOZ_ASSERT(IsIonEnabled());
   MOZ_ASSERT(frame->debugFrameSize() == frameSize);
 
   RootedScript script(cx, frame->script());
   bool isLoopEntry = JSOp(*pc) == JSOP_LOOPENTRY;
 
   MOZ_ASSERT(!isLoopEntry || LoopEntryCanIonOsr(pc));
 
@@ -2429,16 +2429,124 @@ bool jit::IonCompileScriptForBaseline(JS
 bool jit::IonCompileScriptForBaselineAtEntry(JSContext* cx,
                                              BaselineFrame* frame) {
   JSScript* script = frame->script();
   uint32_t frameSize =
       BaselineFrame::frameSizeForNumValueSlots(script->nfixed());
   return IonCompileScriptForBaseline(cx, frame, frameSize, script->code());
 }
 
+/* clang-format off */
+// The following data is kept in a temporary heap-allocated buffer, stored in
+// JitRuntime (high memory addresses at top, low at bottom):
+//
+//     +----->+=================================+  --      <---- High Address
+//     |      |                                 |   |
+//     |      |     ...BaselineFrame...         |   |-- Copy of BaselineFrame + stack values
+//     |      |                                 |   |
+//     |      +---------------------------------+   |
+//     |      |                                 |   |
+//     |      |     ...Locals/Stack...          |   |
+//     |      |                                 |   |
+//     |      +=================================+  --
+//     |      |     Padding(Maybe Empty)        |
+//     |      +=================================+  --
+//     +------|-- baselineFrame                 |   |-- IonOsrTempData
+//            |   jitcode                       |   |
+//            +=================================+  --      <---- Low Address
+//
+// A pointer to the IonOsrTempData is returned.
+/* clang-format on */
+
+static IonOsrTempData* PrepareOsrTempData(JSContext* cx, BaselineFrame* frame,
+                                          uint32_t frameSize, void* jitcode) {
+  uint32_t numValueSlots = frame->numValueSlots(frameSize);
+
+  // Calculate the amount of space to allocate:
+  //      BaselineFrame space:
+  //          (sizeof(Value) * numValueSlots)
+  //        + sizeof(BaselineFrame)
+  //
+  //      IonOsrTempData space:
+  //          sizeof(IonOsrTempData)
+
+  size_t frameSpace = sizeof(BaselineFrame) + sizeof(Value) * numValueSlots;
+  size_t ionOsrTempDataSpace = sizeof(IonOsrTempData);
+
+  size_t totalSpace = AlignBytes(frameSpace, sizeof(Value)) +
+                      AlignBytes(ionOsrTempDataSpace, sizeof(Value));
+
+  IonOsrTempData* info = (IonOsrTempData*)cx->allocateOsrTempData(totalSpace);
+  if (!info) {
+    ReportOutOfMemory(cx);
+    return nullptr;
+  }
+
+  memset(info, 0, totalSpace);
+
+  info->jitcode = jitcode;
+
+  // Copy the BaselineFrame + local/stack Values to the buffer. Arguments and
+  // |this| are not copied but left on the stack: the Baseline and Ion frame
+  // share the same frame prefix and Ion won't clobber these values. Note
+  // that info->baselineFrame will point to the *end* of the frame data, like
+  // the frame pointer register in baseline frames.
+  uint8_t* frameStart =
+      (uint8_t*)info + AlignBytes(ionOsrTempDataSpace, sizeof(Value));
+  info->baselineFrame = frameStart + frameSpace;
+
+  memcpy(frameStart, (uint8_t*)frame - numValueSlots * sizeof(Value),
+         frameSpace);
+
+  JitSpew(JitSpew_BaselineOSR, "Allocated IonOsrTempData at %p", (void*)info);
+  JitSpew(JitSpew_BaselineOSR, "Jitcode is %p", info->jitcode);
+
+  // All done.
+  return info;
+}
+
+bool jit::IonCompileScriptForBaselineOSR(JSContext* cx, BaselineFrame* frame,
+                                         uint32_t frameSize, jsbytecode* pc,
+                                         IonOsrTempData** infoPtr) {
+  MOZ_ASSERT(infoPtr);
+  *infoPtr = nullptr;
+
+  MOZ_ASSERT(frame->debugFrameSize() == frameSize);
+  MOZ_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY);
+
+  if (!IonCompileScriptForBaseline(cx, frame, frameSize, pc)) {
+    return false;
+  }
+
+  RootedScript script(cx, frame->script());
+  if (!script->hasIonScript() || script->ionScript()->osrPc() != pc ||
+      script->ionScript()->bailoutExpected() || frame->isDebuggee()) {
+    return true;
+  }
+
+  IonScript* ion = script->ionScript();
+  MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled() ==
+             ion->hasProfilingInstrumentation());
+  MOZ_ASSERT(ion->osrPc() == pc);
+
+  JitSpew(JitSpew_BaselineOSR, "  OSR possible!");
+  void* jitcode = ion->method()->raw() + ion->osrEntryOffset();
+
+  // Prepare the temporary heap copy of the fake InterpreterFrame and actual
+  // args list.
+  JitSpew(JitSpew_BaselineOSR, "Got jitcode.  Preparing for OSR into ion.");
+  IonOsrTempData* info = PrepareOsrTempData(cx, frame, frameSize, jitcode);
+  if (!info) {
+    return false;
+  }
+
+  *infoPtr = info;
+  return true;
+}
+
 MethodStatus jit::Recompile(JSContext* cx, HandleScript script, bool force) {
   MOZ_ASSERT(script->hasIonScript());
   if (script->ionScript()->isRecompiling()) {
     return Method_Compiled;
   }
 
   MOZ_ASSERT(!script->baselineScript()->hasPendingIonBuilder());
 
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -135,24 +135,37 @@ MOZ_MUST_USE bool InitializeJit();
 JitContext* GetJitContext();
 JitContext* MaybeGetJitContext();
 
 void SetJitContext(JitContext* ctx);
 
 bool CanIonCompileScript(JSContext* cx, JSScript* script);
 bool CanIonInlineScript(JSScript* script);
 
-MOZ_MUST_USE bool IonCompileScriptForBaseline(JSContext* cx,
-                                              BaselineFrame* frame,
-                                              uint32_t frameSize,
-                                              jsbytecode* pc);
-
 MOZ_MUST_USE bool IonCompileScriptForBaselineAtEntry(JSContext* cx,
                                                      BaselineFrame* frame);
 
+struct IonOsrTempData {
+  void* jitcode;
+  uint8_t* baselineFrame;
+
+  static constexpr size_t offsetOfJitCode() {
+    return offsetof(IonOsrTempData, jitcode);
+  }
+  static constexpr size_t offsetOfBaselineFrame() {
+    return offsetof(IonOsrTempData, baselineFrame);
+  }
+};
+
+MOZ_MUST_USE bool IonCompileScriptForBaselineOSR(JSContext* cx,
+                                                 BaselineFrame* frame,
+                                                 uint32_t frameSize,
+                                                 jsbytecode* pc,
+                                                 IonOsrTempData** infoPtr);
+
 MethodStatus CanEnterIon(JSContext* cx, RunState& state);
 
 MethodStatus Recompile(JSContext* cx, HandleScript script, bool force);
 
 enum JitExecStatus {
   // The method call had to be aborted due to a stack limit check. This
   // error indicates that Ion never attempted to clean up frames.
   JitExec_Aborted,
--- a/js/src/jit/VMFunctionList-inl.h
+++ b/js/src/jit/VMFunctionList-inl.h
@@ -98,17 +98,16 @@ namespace jit {
   _(DirectEvalStringFromIon, js::DirectEvalStringFromIon)                      \
   _(DivValues, js::DivValues)                                                  \
   _(DoCallFallback, js::jit::DoCallFallback)                                   \
   _(DoConcatStringObject, js::jit::DoConcatStringObject)                       \
   _(DoSpreadCallFallback, js::jit::DoSpreadCallFallback)                       \
   _(DoToNumber, js::jit::DoToNumber)                                           \
   _(DoToNumeric, js::jit::DoToNumeric)                                         \
   _(DoTypeUpdateFallback, js::jit::DoTypeUpdateFallback)                       \
-  _(DoWarmUpCounterFallbackOSR, js::jit::DoWarmUpCounterFallbackOSR)           \
   _(EnterWith, js::jit::EnterWith)                                             \
   _(FinalSuspend, js::jit::FinalSuspend)                                       \
   _(FinishBoundFunctionInit, JSFunction::finishBoundFunctionInit)              \
   _(FreshenLexicalEnv, js::jit::FreshenLexicalEnv)                             \
   _(FunWithProtoOperation, js::FunWithProtoOperation)                          \
   _(GeneratorThrowOrReturn, js::jit::GeneratorThrowOrReturn)                   \
   _(GetAndClearException, js::GetAndClearException)                            \
   _(GetElementOperation, js::GetElementOperation)                              \
@@ -142,16 +141,17 @@ namespace jit {
   _(InterruptCheck, js::jit::InterruptCheck)                                   \
   _(InvokeFunction, js::jit::InvokeFunction)                                   \
   _(InvokeFunctionShuffleNewTarget, js::jit::InvokeFunctionShuffleNewTarget)   \
   _(IonBinaryArithICUpdate, js::jit::IonBinaryArithIC::update)                 \
   _(IonBindNameICUpdate, js::jit::IonBindNameIC::update)                       \
   _(IonCompareICUpdate, js::jit::IonCompareIC::update)                         \
   _(IonCompileScriptForBaselineAtEntry,                                        \
     js::jit::IonCompileScriptForBaselineAtEntry)                               \
+  _(IonCompileScriptForBaselineOSR, js::jit::IonCompileScriptForBaselineOSR)   \
   _(IonForcedInvalidation, js::jit::IonForcedInvalidation)                     \
   _(IonForcedRecompile, js::jit::IonForcedRecompile)                           \
   _(IonGetIteratorICUpdate, js::jit::IonGetIteratorIC::update)                 \
   _(IonGetNameICUpdate, js::jit::IonGetNameIC::update)                         \
   _(IonGetPropSuperICUpdate, js::jit::IonGetPropSuperIC::update)               \
   _(IonGetPropertyICUpdate, js::jit::IonGetPropertyIC::update)                 \
   _(IonHasOwnICUpdate, js::jit::IonHasOwnIC::update)                           \
   _(IonInICUpdate, js::jit::IonInIC::update)                                   \
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -2451,17 +2451,17 @@
      * able to OSR at this point, which is true unless there is non-loop state
      * on the stack. See JSOP_JUMPTARGET for the icIndex argument.
      *
      *   Category: Statements
      *   Type: Jumps
      *   Operands: uint32_t icIndex, uint8_t BITFIELD
      *   Stack: =>
      */ \
-    MACRO(JSOP_LOOPENTRY, 227, "loopentry", NULL, 6, 0, 0, JOF_LOOPENTRY|JOF_IC) \
+    MACRO(JSOP_LOOPENTRY, 227, "loopentry", NULL, 6, 0, 0, JOF_LOOPENTRY) \
     /*
      * Converts the value on the top of the stack to a String.
      *
      *   Category: Other
      *   Operands:
      *   Stack: val => ToString(val)
      */ \
     MACRO(JSOP_TOSTRING, 228, "tostring", NULL, 1, 1, 1, JOF_BYTE) \