Bug 1505689 part 5 - Move BaselineScript and IonScript pointers from JSScript to JitScript. r=tcampbell
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 15 Aug 2019 16:13:50 +0000
changeset 488277 7db7c0c4fadd8303e1002a8f737d8f31a9400c4b
parent 488276 257f973566ba9b0331af6858092345cce9832b34
child 488278 8bf0e43c0df043e3527a419e095e641ff52bfcb4
push id113906
push userncsoregi@mozilla.com
push dateFri, 16 Aug 2019 04:07:24 +0000
treeherdermozilla-inbound@d887276421d3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell
bugs1505689
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 1505689 part 5 - Move BaselineScript and IonScript pointers from JSScript to JitScript. r=tcampbell Differential Revision: https://phabricator.services.mozilla.com/D41582
js/src/debugger/DebugScript.cpp
js/src/gc/Zone.cpp
js/src/jit/BaselineCodeGen.cpp
js/src/jit/BaselineDebugModeOSR.cpp
js/src/jit/BaselineJIT.cpp
js/src/jit/BaselineJIT.h
js/src/jit/CodeGenerator.cpp
js/src/jit/Ion.cpp
js/src/jit/JSJitFrameIter.cpp
js/src/jit/JitScript.cpp
js/src/jit/JitScript.h
js/src/jit/arm/Bailouts-arm.cpp
js/src/jit/arm64/Bailouts-arm64.cpp
js/src/jit/mips32/Bailouts-mips32.cpp
js/src/jit/mips64/Bailouts-mips64.cpp
js/src/jit/x64/Bailouts-x64.cpp
js/src/jit/x86/Bailouts-x86.cpp
js/src/jsapi-tests/testPreserveJitCode.cpp
js/src/vm/JSScript-inl.h
js/src/vm/JSScript.cpp
js/src/vm/JSScript.h
js/src/vm/TypeInference-inl.h
js/src/vm/TypeInference.cpp
--- a/js/src/debugger/DebugScript.cpp
+++ b/js/src/debugger/DebugScript.cpp
@@ -29,16 +29,17 @@
 #include "vm/Realm.h"           // for Realm, AutoRealm
 #include "vm/Runtime.h"         // for ReportOutOfMemory
 #include "vm/Stack.h"           // for ActivationIterator, Activation
 
 #include "gc/FreeOp-inl.h"     // for JSFreeOp::free_
 #include "gc/GC-inl.h"         // for ZoneCellIter
 #include "gc/Marking-inl.h"    // for CheckGCThingAfterMovingGC
 #include "vm/JSContext-inl.h"  // for JSContext::check
+#include "vm/JSScript-inl.h"   // for JSScript::hasBaselineScript
 #include "vm/Realm-inl.h"      // for AutoRealm::AutoRealm
 
 namespace js {
 
 /* static */
 DebugScript* DebugScript::get(JSScript* script) {
   MOZ_ASSERT(script->hasDebugScript());
   DebugScriptMap* map = script->realm()->debugScriptMap.get();
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -359,54 +359,62 @@ void Zone::discardJitCode(JSFreeOp* fop,
     jit::MarkActiveJitScripts(this);
   }
 
   // Invalidate all Ion code in this zone.
   jit::InvalidateAll(fop, this);
 
   for (auto script = cellIterUnsafe<JSScript>(); !script.done();
        script.next()) {
+    jit::JitScript* jitScript = script->jitScript();
+    if (!jitScript) {
+      continue;
+    }
+
     jit::FinishInvalidation(fop, script);
 
     // Discard baseline script if it's not marked as active.
-    if (discardBaselineCode && script->hasBaselineScript() &&
-        !script->jitScript()->active()) {
-      jit::FinishDiscardBaselineScript(fop, script);
+    if (discardBaselineCode) {
+      if (jitScript->hasBaselineScript() && !jitScript->active()) {
+        jit::FinishDiscardBaselineScript(fop, script);
+      }
     }
 
     // Warm-up counter for scripts are reset on GC. After discarding code we
     // need to let it warm back up to get information such as which
     // opcodes are setting array holes or accessing getter properties.
     script->resetWarmUpCounterForGC();
 
     // Try to release the script's JitScript. This should happen after
     // releasing JIT code because we can't do this when the script still has
     // JIT code.
     if (discardJitScripts) {
       script->maybeReleaseJitScript(fop);
+      jitScript = script->jitScript();
+      if (!jitScript) {
+        continue;
+      }
     }
 
-    if (jit::JitScript* jitScript = script->jitScript()) {
-      // If we did not release the JitScript, we need to purge optimized IC
-      // stubs because the optimizedStubSpace will be purged below.
-      if (discardBaselineCode) {
-        jitScript->purgeOptimizedStubs(script);
+    // If we did not release the JitScript, we need to purge optimized IC
+    // stubs because the optimizedStubSpace will be purged below.
+    if (discardBaselineCode) {
+      jitScript->purgeOptimizedStubs(script);
 
-        // ICs were purged so the script will need to warm back up before it can
-        // be inlined during Ion compilation.
-        jitScript->clearIonCompiledOrInlined();
-      }
+      // ICs were purged so the script will need to warm back up before it can
+      // be inlined during Ion compilation.
+      jitScript->clearIonCompiledOrInlined();
+    }
 
-      // Clear the JitScript's control flow graph. The LifoAlloc is purged
-      // below.
-      jitScript->clearControlFlowGraph();
+    // Clear the JitScript's control flow graph. The LifoAlloc is purged
+    // below.
+    jitScript->clearControlFlowGraph();
 
-      // Finally, reset the active flag.
-      jitScript->resetActive();
-    }
+    // Finally, reset the active flag.
+    jitScript->resetActive();
   }
 
   /*
    * When scripts contains pointers to nursery things, the store buffer
    * can contain entries that point into the optimized stub space. Since
    * this method can be called outside the context of a GC, this situation
    * could result in us trying to mark invalid store buffer entries.
    *
--- a/js/src/jit/BaselineCodeGen.cpp
+++ b/js/src/jit/BaselineCodeGen.cpp
@@ -301,17 +301,17 @@ MethodStatus BaselineCompiler::compile()
       ReportOutOfMemory(cx);
       return Method_Error;
     }
 
     // Mark the jitcode as having a bytecode map.
     code->setHasBytecodeMap();
   }
 
-  script->setBaselineScript(cx->runtime(), baselineScript.release());
+  script->jitScript()->setBaselineScript(script, baselineScript.release());
 
 #ifdef JS_ION_PERF
   writePerfSpewerBaselineProfile(script, code);
 #endif
 
 #ifdef MOZ_VTUNE
   vtune::MarkScript(code, script, "baseline");
 #endif
@@ -1307,17 +1307,18 @@ bool BaselineCompilerCodeGen::emitWarmUp
   const OptimizationInfo* info =
       IonOptimizations.get(IonOptimizations.firstLevel());
   uint32_t warmUpThreshold = info->compilerWarmUpThreshold(script, pc);
   masm.branch32(Assembler::LessThan, countReg, Imm32(warmUpThreshold),
                 &skipCall);
 
   // Do nothing if Ion is already compiling this script off-thread or if Ion has
   // been disabled for this script.
-  masm.loadPtr(Address(scriptReg, JSScript::offsetOfIonScript()), scriptReg);
+  masm.movePtr(ImmPtr(script->jitScript()), scriptReg);
+  masm.loadPtr(Address(scriptReg, JitScript::offsetOfIonScript()), scriptReg);
   masm.branchPtr(Assembler::Equal, scriptReg, ImmPtr(ION_COMPILING_SCRIPT),
                  &skipCall);
   masm.branchPtr(Assembler::Equal, scriptReg, ImmPtr(ION_DISABLED_SCRIPT),
                  &skipCall);
 
   // Try to compile and/or finish a compilation.
   if (JSOp(*pc) == JSOP_LOOPENTRY) {
     // During the loop entry we can try to OSR into ion.
@@ -1358,18 +1359,19 @@ bool BaselineInterpreterCodeGen::emitWar
   masm.add32(Imm32(1), countReg);
   masm.store32(countReg, warmUpCounterAddr);
 
   // If the script is warm enough for Baseline compilation, call into the VM to
   // compile it.
   Label done;
   masm.branch32(Assembler::BelowOrEqual, countReg,
                 Imm32(JitOptions.baselineJitWarmUpThreshold), &done);
+  masm.loadPtr(Address(scriptReg, JSScript::offsetOfJitScript()), scriptReg);
   masm.branchPtr(Assembler::Equal,
-                 Address(scriptReg, JSScript::offsetOfBaselineScript()),
+                 Address(scriptReg, JitScript::offsetOfBaselineScript()),
                  ImmPtr(BASELINE_DISABLED_SCRIPT), &done);
   {
     prepareVMCall();
 
     masm.PushBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
     using Fn = bool (*)(JSContext*, BaselineFrame*, uint8_t**);
     if (!callVM<Fn, BaselineCompileFromBaselineInterpreter>()) {
@@ -1520,18 +1522,18 @@ bool BaselineCompilerCodeGen::emitTraceL
   }
 
   masm.Push(loggerReg);
   masm.Push(scriptReg);
 
   masm.loadTraceLogger(loggerReg);
 
   // Script start.
-  masm.movePtr(ImmGCPtr(handler.script()), scriptReg);
-  masm.loadPtr(Address(scriptReg, JSScript::offsetOfBaselineScript()),
+  masm.movePtr(ImmPtr(handler.script()->jitScript()), scriptReg);
+  masm.loadPtr(Address(scriptReg, JitScript::offsetOfBaselineScript()),
                scriptReg);
   Address scriptEvent(scriptReg,
                       BaselineScript::offsetOfTraceLoggerScriptEvent());
   masm.computeEffectiveAddress(scriptEvent, scriptReg);
   masm.tracelogStartEvent(loggerReg, scriptReg);
 
   // Engine start.
   masm.tracelogStartId(loggerReg, TraceLogger_Baseline, /* force = */ true);
@@ -4748,18 +4750,18 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   return true;
 }
 
 static void LoadBaselineScriptResumeEntries(MacroAssembler& masm,
                                             JSScript* script, Register dest,
                                             Register scratch) {
   MOZ_ASSERT(dest != scratch);
 
-  masm.movePtr(ImmGCPtr(script), dest);
-  masm.loadPtr(Address(dest, JSScript::offsetOfBaselineScript()), dest);
+  masm.movePtr(ImmPtr(script->jitScript()), dest);
+  masm.loadPtr(Address(dest, JitScript::offsetOfBaselineScript()), dest);
   masm.load32(Address(dest, BaselineScript::offsetOfResumeEntriesOffset()),
               scratch);
   masm.addPtr(scratch, dest);
 }
 
 template <typename Handler>
 void BaselineCodeGen<Handler>::emitInterpJumpToResumeEntry(Register script,
                                                            Register resumeIndex,
@@ -5979,25 +5981,24 @@ template <>
 void BaselineInterpreterCodeGen::emitJumpToInterpretOpLabel() {
   masm.jump(handler.interpretOpLabel());
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emitEnterGeneratorCode(Register script,
                                                       Register resumeIndex,
                                                       Register scratch) {
-  Address baselineAddr(script, JSScript::offsetOfBaselineScript());
-
   // Resume in either the BaselineScript (if present) or Baseline Interpreter.
   Label noBaselineScript;
-  masm.branchPtr(Assembler::BelowOrEqual, baselineAddr,
+  masm.loadPtr(Address(script, JSScript::offsetOfJitScript()), scratch);
+  masm.loadPtr(Address(scratch, JitScript::offsetOfBaselineScript()), scratch);
+  masm.branchPtr(Assembler::BelowOrEqual, scratch,
                  ImmPtr(BASELINE_DISABLED_SCRIPT), &noBaselineScript);
-  masm.loadPtr(baselineAddr, script);
-  masm.load32(Address(script, BaselineScript::offsetOfResumeEntriesOffset()),
-              scratch);
+  masm.load32(Address(scratch, BaselineScript::offsetOfResumeEntriesOffset()),
+              script);
   masm.addPtr(scratch, script);
   masm.loadPtr(
       BaseIndex(script, resumeIndex, ScaleFromElemWidth(sizeof(uintptr_t))),
       scratch);
   masm.jump(scratch);
 
   masm.bind(&noBaselineScript);
 
@@ -6048,17 +6049,18 @@ bool BaselineCodeGen<Handler>::emitGener
   masm.branchPtr(Assembler::Equal,
                  Address(scratch1, JSScript::offsetOfJitScript()),
                  ImmPtr(nullptr), &interpret);
 
 #ifdef JS_TRACE_LOGGING
   if (JS::TraceLoggerSupported()) {
     // TODO (bug 1565788): add Baseline Interpreter support.
     MOZ_CRASH("Unimplemented Baseline Interpreter TraceLogger support");
-    Address baselineAddr(scratch1, JSScript::offsetOfBaselineScript());
+    masm.loadPtr(Address(scratch1, JSScript::offsetOfJitScript()), scratch1);
+    Address baselineAddr(scratch1, JitScript::offsetOfBaselineScript());
     masm.loadPtr(baselineAddr, scratch1);
     if (!emitTraceLoggerResume(scratch1, regs)) {
       return false;
     }
   }
 #endif
 
   // Push |undefined| for all formals.
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -408,26 +408,26 @@ static bool RecompileBaselineScriptForDe
     return true;
   }
 
   JitSpew(JitSpew_BaselineDebugModeOSR, "Recompiling (%s:%u:%u) for %s",
           script->filename(), script->lineno(), script->column(),
           observing ? "DEBUGGING" : "NORMAL EXECUTION");
 
   AutoKeepJitScripts keepJitScripts(cx);
-  script->clearBaselineScript(cx->defaultFreeOp());
+  script->jitScript()->clearBaselineScript(cx->defaultFreeOp(), script);
 
   MethodStatus status =
       BaselineCompile(cx, script, /* forceDebugMode = */ observing);
   if (status != Method_Compiled) {
     // We will only fail to recompile for debug mode due to OOM. Restore
     // the old baseline script in case something doesn't properly
     // propagate OOM.
     MOZ_ASSERT(status == Method_Error);
-    script->setBaselineScript(cx->runtime(), oldBaselineScript);
+    script->jitScript()->setBaselineScript(script, oldBaselineScript);
     return false;
   }
 
   // Don't destroy the old baseline script yet, since if we fail any of the
   // recompiles we need to rollback all the old baseline scripts.
   MOZ_ASSERT(script->baselineScript()->hasDebugInstrumentation() == observing);
   return true;
 }
@@ -468,17 +468,17 @@ static void UndoRecompileBaselineScripts
     JSContext* cx, const DebugModeOSREntryVector& entries) {
   // In case of failure, roll back the entire set of active scripts so that
   // we don't have to patch return addresses on the stack.
   for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
     const DebugModeOSREntry& entry = iter.entry();
     JSScript* script = entry.script;
     BaselineScript* baselineScript = script->baselineScript();
     if (entry.recompiled()) {
-      script->setBaselineScript(cx->runtime(), entry.oldBaselineScript);
+      script->jitScript()->setBaselineScript(script, entry.oldBaselineScript);
       BaselineScript::Destroy(cx->runtime()->defaultFreeOp(), baselineScript);
     }
   }
 }
 
 bool jit::RecompileOnStackBaselineScriptsForDebugMode(
     JSContext* cx, const DebugAPI::ExecutionObservableSet& obs,
     DebugAPI::IsObserving observing) {
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -216,31 +216,31 @@ MethodStatus jit::BaselineCompile(JSCont
   }
 
   MethodStatus status = compiler.compile();
 
   MOZ_ASSERT_IF(status == Method_Compiled, script->hasBaselineScript());
   MOZ_ASSERT_IF(status != Method_Compiled, !script->hasBaselineScript());
 
   if (status == Method_CantCompile) {
-    script->disableBaselineCompile(cx->runtime());
+    script->disableBaselineCompile();
   }
 
   return status;
 }
 
 static MethodStatus CanEnterBaselineJIT(JSContext* cx, HandleScript script,
                                         AbstractFramePtr osrSourceFrame) {
   // Skip if the script has been disabled.
   if (!script->canBaselineCompile()) {
     return Method_Skipped;
   }
 
   if (!IsBaselineJitEnabled()) {
-    script->disableBaselineCompile(cx->runtime());
+    script->disableBaselineCompile();
     return Method_CantCompile;
   }
 
   // This check is needed in the following corner case. Consider a function h,
   //
   //   function h(x) {
   //      if (!x)
   //        return;
@@ -260,22 +260,22 @@ static MethodStatus CanEnterBaselineJIT(
   // code. This is incorrect as h's baseline script does not have debug
   // instrumentation.
   if (osrSourceFrame && osrSourceFrame.isDebuggee() &&
       !DebugAPI::ensureExecutionObservabilityOfOsrFrame(cx, osrSourceFrame)) {
     return Method_Error;
   }
 
   if (script->length() > BaselineMaxScriptLength) {
-    script->disableBaselineCompile(cx->runtime());
+    script->disableBaselineCompile();
     return Method_CantCompile;
   }
 
   if (script->nslots() > BaselineMaxScriptSlots) {
-    script->disableBaselineCompile(cx->runtime());
+    script->disableBaselineCompile();
     return Method_CantCompile;
   }
 
   if (script->hasBaselineScript()) {
     return Method_Compiled;
   }
 
   // Check script warm-up counter.
@@ -289,17 +289,17 @@ static MethodStatus CanEnterBaselineJIT(
     return Method_Skipped;
   }
 
   if (!cx->realm()->ensureJitRealmExists(cx)) {
     return Method_Error;
   }
 
   if (script->hasForceInterpreterOp()) {
-    script->disableBaselineCompile(cx->runtime());
+    script->disableBaselineCompile();
     return Method_CantCompile;
   }
 
   // Frames can be marked as debuggee frames independently of its underlying
   // script being a debuggee script, e.g., when performing
   // Debugger.Frame.prototype.eval.
   bool forceDebugInstrumentation =
       osrSourceFrame && osrSourceFrame.isDebuggee();
@@ -736,16 +736,38 @@ void BaselineScript::toggleDebugTraps(JS
                    DebugAPI::hasBreakpointsAt(script, entryPC);
 
     // Patch the trap.
     CodeLocationLabel label(method(), CodeOffset(entry.nativeOffset()));
     Assembler::ToggleCall(label, enabled);
   }
 }
 
+void BaselineScript::setPendingIonBuilder(JSRuntime* rt, JSScript* script,
+                                          js::jit::IonBuilder* builder) {
+  MOZ_ASSERT(script->baselineScript() == this);
+  MOZ_ASSERT(builder);
+  MOZ_ASSERT(!hasPendingIonBuilder());
+
+  if (script->isIonCompilingOffThread()) {
+    script->jitScript()->clearIsIonCompilingOffThread(script);
+  }
+
+  pendingBuilder_ = builder;
+  script->updateJitCodeRaw(rt);
+}
+
+void BaselineScript::removePendingIonBuilder(JSRuntime* rt, JSScript* script) {
+  MOZ_ASSERT(script->baselineScript() == this);
+  MOZ_ASSERT(hasPendingIonBuilder());
+
+  pendingBuilder_ = nullptr;
+  script->updateJitCodeRaw(rt);
+}
+
 #ifdef JS_TRACE_LOGGING
 void BaselineScript::initTraceLogger(JSScript* script,
                                      const Vector<CodeOffset>& offsets) {
 #  ifdef DEBUG
   traceLoggerScriptsEnabled_ = TraceLogTextIdEnabled(TraceLogger_Scripts);
   traceLoggerEngineEnabled_ = TraceLogTextIdEnabled(TraceLogger_Engine);
 #  endif
 
@@ -914,17 +936,17 @@ void BaselineInterpreter::toggleCodeCove
   toggleCodeCoverageInstrumentationUnchecked(enable);
 }
 
 void jit::FinishDiscardBaselineScript(JSFreeOp* fop, JSScript* script) {
   MOZ_ASSERT(script->hasBaselineScript());
   MOZ_ASSERT(!script->jitScript()->active());
 
   BaselineScript* baseline = script->baselineScript();
-  script->clearBaselineScript(fop);
+  script->jitScript()->clearBaselineScript(fop, script);
   BaselineScript::Destroy(fop, baseline);
 }
 
 void jit::AddSizeOfBaselineData(JSScript* script,
                                 mozilla::MallocSizeOf mallocSizeOf,
                                 size_t* data) {
   if (script->hasBaselineScript()) {
     script->baselineScript()->addSizeOfIncludingThis(mallocSizeOf, data);
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -373,35 +373,18 @@ struct BaselineScript final {
 
   bool hasPendingIonBuilder() const { return !!pendingBuilder_; }
 
   js::jit::IonBuilder* pendingIonBuilder() {
     MOZ_ASSERT(hasPendingIonBuilder());
     return pendingBuilder_;
   }
   void setPendingIonBuilder(JSRuntime* rt, JSScript* script,
-                            js::jit::IonBuilder* builder) {
-    MOZ_ASSERT(script->baselineScript() == this);
-    MOZ_ASSERT(builder);
-    MOZ_ASSERT(!hasPendingIonBuilder());
-
-    if (script->isIonCompilingOffThread()) {
-      script->clearIsIonCompilingOffThread(rt);
-    }
-
-    pendingBuilder_ = builder;
-    script->updateJitCodeRaw(rt);
-  }
-  void removePendingIonBuilder(JSRuntime* rt, JSScript* script) {
-    MOZ_ASSERT(script->baselineScript() == this);
-    MOZ_ASSERT(hasPendingIonBuilder());
-
-    pendingBuilder_ = nullptr;
-    script->updateJitCodeRaw(rt);
-  }
+                            js::jit::IonBuilder* builder);
+  void removePendingIonBuilder(JSRuntime* rt, JSScript* script);
 
   size_t allocBytes() const { return allocBytes_; }
 };
 static_assert(
     sizeof(BaselineScript) % sizeof(uintptr_t) == 0,
     "The data attached to the script must be aligned for fast JIT access.");
 
 enum class BaselineTier { Interpreter, Compiler };
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -10851,17 +10851,17 @@ bool CodeGenerator::link(JSContext* cx, 
   ionScript->setSkipArgCheckEntryOffset(getSkipArgCheckEntryOffset());
 
   // If the Gecko Profiler is enabled, mark IonScript as having been
   // instrumented accordingly.
   if (isProfilerInstrumentationEnabled()) {
     ionScript->setHasProfilingInstrumentation();
   }
 
-  script->setIonScript(cx->runtime(), ionScript);
+  script->jitScript()->setIonScript(script, ionScript);
 
   Assembler::PatchDataWithValueCheck(
       CodeLocationLabel(code, invalidateEpilogueData_), ImmPtr(ionScript),
       ImmPtr((void*)-1));
 
   for (size_t i = 0; i < ionScriptLabels_.length(); i++) {
     Assembler::PatchDataWithValueCheck(
         CodeLocationLabel(code, ionScriptLabels_[i]), ImmPtr(ionScript),
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -422,41 +422,42 @@ void jit::FreeIonBuilder(IonBuilder* bui
   js_delete(builder->backgroundCodegen());
   js_delete(builder->alloc().lifoAlloc());
 }
 
 void jit::FinishOffThreadBuilder(JSRuntime* runtime, IonBuilder* builder,
                                  const AutoLockHelperThreadState& locked) {
   MOZ_ASSERT(runtime);
 
+  JSScript* script = builder->script();
+
   // Clean the references to the pending IonBuilder, if we just finished it.
-  if (builder->script()->baselineScript()->hasPendingIonBuilder() &&
-      builder->script()->baselineScript()->pendingIonBuilder() == builder) {
-    builder->script()->baselineScript()->removePendingIonBuilder(
-        runtime, builder->script());
+  if (script->baselineScript()->hasPendingIonBuilder() &&
+      script->baselineScript()->pendingIonBuilder() == builder) {
+    script->baselineScript()->removePendingIonBuilder(runtime, script);
   }
 
   // If the builder is still in one of the helper thread list, then remove it.
   if (builder->isInList()) {
     runtime->jitRuntime()->ionLazyLinkListRemove(runtime, builder);
   }
 
   // Clear the recompiling flag of the old ionScript, since we continue to
   // use the old ionScript if recompiling fails.
-  if (builder->script()->hasIonScript()) {
-    builder->script()->ionScript()->clearRecompiling();
+  if (script->hasIonScript()) {
+    script->ionScript()->clearRecompiling();
   }
 
   // Clean up if compilation did not succeed.
-  if (builder->script()->isIonCompilingOffThread()) {
-    builder->script()->clearIsIonCompilingOffThread(runtime);
+  if (script->isIonCompilingOffThread()) {
+    script->jitScript()->clearIsIonCompilingOffThread(script);
 
     AbortReasonOr<Ok> status = builder->getOffThreadStatus();
     if (status.isErr() && status.unwrapErr() == AbortReason::Disable) {
-      builder->script()->disableIon(runtime);
+      script->disableIon();
     }
   }
 
   // Free Ion LifoAlloc off-thread. Free on the main thread if this OOMs.
   if (!StartOffThreadIonFree(builder, locked)) {
     FreeIonBuilder(builder);
   }
 }
@@ -1936,17 +1937,17 @@ static AbortReason IonCompile(JSContext*
     AutoLockHelperThreadState lock;
     if (!StartOffThreadIonCompile(builder, lock)) {
       JitSpew(JitSpew_IonAbort, "Unable to start off-thread ion compilation.");
       builder->graphSpewer().endFunction();
       return AbortReason::Alloc;
     }
 
     if (!recompile) {
-      builderScript->setIsIonCompilingOffThread(cx->runtime());
+      builderScript->jitScript()->setIsIonCompilingOffThread(builderScript);
     }
 
     // The allocator and associated data will be destroyed after being
     // processed in the finishedOffThreadCompilations list.
     mozilla::Unused << alloc.release();
 
     return AbortReason::NoAbort;
   }
@@ -2663,17 +2664,17 @@ void jit::InvalidateAll(JSFreeOp* fop, Z
       JitSpew(JitSpew_IonInvalidate, "Invalidating all frames for GC");
       InvalidateActivation(fop, iter, true);
     }
   }
 }
 
 static void ClearIonScriptAfterInvalidation(JSContext* cx, JSScript* script,
                                             bool resetUses) {
-  script->clearIonScript(cx->defaultFreeOp());
+  script->jitScript()->clearIonScript(cx->defaultFreeOp(), script);
 
   // Wait for the scripts to get warm again before doing another
   // compile, unless we are recompiling *because* a script got hot
   // (resetUses is false).
   if (resetUses) {
     script->resetWarmUpCounterToDelayIonCompilation();
   }
 }
@@ -2809,17 +2810,17 @@ void jit::Invalidate(JSContext* cx, JSSc
 
 void jit::FinishInvalidation(JSFreeOp* fop, JSScript* script) {
   if (!script->hasIonScript()) {
     return;
   }
 
   // In all cases, null out script->ion to avoid re-entry.
   IonScript* ion = script->ionScript();
-  script->clearIonScript(fop);
+  script->jitScript()->clearIonScript(fop, script);
 
   // If this script has Ion code on the stack, invalidated() will return
   // true. In this case we have to wait until destroying it.
   if (!ion->invalidated()) {
     jit::IonScript::Destroy(fop, ion);
   }
 }
 
@@ -2828,17 +2829,17 @@ void jit::ForbidCompilation(JSContext* c
           script->filename(), script->lineno(), script->column());
 
   CancelOffThreadIonCompile(script);
 
   if (script->hasIonScript()) {
     Invalidate(cx, script, false);
   }
 
-  script->disableIon(cx->runtime());
+  script->disableIon();
 }
 
 AutoFlushICache* JSContext::autoFlushICache() const { return autoFlushICache_; }
 
 void JSContext::setAutoFlushICache(AutoFlushICache* afc) {
   autoFlushICache_ = afc;
 }
 
@@ -2997,31 +2998,33 @@ size_t jit::SizeOfIonData(JSScript* scri
   if (script->hasIonScript()) {
     result += script->ionScript()->sizeOfIncludingThis(mallocSizeOf);
   }
 
   return result;
 }
 
 void jit::DestroyJitScripts(JSFreeOp* fop, JSScript* script) {
+  if (!script->hasJitScript()) {
+    return;
+  }
+
   if (script->hasIonScript()) {
     IonScript* ion = script->ionScript();
-    script->clearIonScript(fop);
+    script->jitScript()->clearIonScript(fop, script);
     jit::IonScript::Destroy(fop, ion);
   }
 
   if (script->hasBaselineScript()) {
     BaselineScript* baseline = script->baselineScript();
-    script->clearBaselineScript(fop);
+    script->jitScript()->clearBaselineScript(fop, script);
     jit::BaselineScript::Destroy(fop, baseline);
   }
 
-  if (script->hasJitScript()) {
-    script->releaseJitScript(fop);
-  }
+  script->releaseJitScript(fop);
 }
 
 void jit::TraceJitScripts(JSTracer* trc, JSScript* script) {
   if (script->hasIonScript()) {
     jit::IonScript::Trace(trc, script->ionScript());
   }
 
   if (script->hasBaselineScript()) {
--- a/js/src/jit/JSJitFrameIter.cpp
+++ b/js/src/jit/JSJitFrameIter.cpp
@@ -8,16 +8,18 @@
 
 #include "jit/BaselineDebugModeOSR.h"
 #include "jit/BaselineIC.h"
 #include "jit/JitcodeMap.h"
 #include "jit/JitFrames.h"
 #include "jit/JitScript.h"
 #include "jit/Safepoints.h"
 
+#include "vm/JSScript-inl.h"
+
 using namespace js;
 using namespace js::jit;
 
 JSJitFrameIter::JSJitFrameIter(const JitActivation* activation)
     : current_(activation->jsExitFP()),
       type_(FrameType::Exit),
       resumePCinCurrentFrame_(nullptr),
       frameSize_(0),
--- a/js/src/jit/JitScript.cpp
+++ b/js/src/jit/JitScript.cpp
@@ -49,16 +49,25 @@ JitScript::JitScript(JSScript* script, u
     : profileString_(profileString),
       typeSetOffset_(typeSetOffset),
       bytecodeTypeMapOffset_(bytecodeTypeMapOffset),
       allocBytes_(allocBytes) {
   setTypesGeneration(script->zone()->types.generation);
 
   uint8_t* base = reinterpret_cast<uint8_t*>(this);
   DefaultInitializeElements<StackTypeSet>(base + typeSetOffset, numTypeSets());
+
+  // Ensure the baselineScript_ and ionScript_ fields match the BaselineDisabled
+  // and IonDisabled script flags.
+  if (!script->canBaselineCompile()) {
+    setBaselineScriptImpl(script, BASELINE_DISABLED_SCRIPT);
+  }
+  if (!script->canIonCompile()) {
+    setIonScriptImpl(script, ION_DISABLED_SCRIPT);
+  }
 }
 
 bool JSScript::createJitScript(JSContext* cx) {
   MOZ_ASSERT(!jitScript_);
   cx->check(this);
 
   // Scripts with a JitScript can run in the Baseline Interpreter. Make sure
   // we don't create a JitScript for scripts we shouldn't Baseline interpret.
@@ -159,17 +168,19 @@ bool JSScript::createJitScript(JSContext
               types, InferSpewColorReset(), i, this);
   }
 #endif
 
   return true;
 }
 
 void JSScript::maybeReleaseJitScript(JSFreeOp* fop) {
-  if (!jitScript_ || zone()->types.keepJitScripts || hasBaselineScript() ||
+  MOZ_ASSERT(hasJitScript());
+
+  if (zone()->types.keepJitScripts || jitScript_->hasBaselineScript() ||
       jitScript_->active()) {
     return;
   }
 
   releaseJitScript(fop);
 }
 
 void JSScript::releaseJitScript(JSFreeOp* fop) {
@@ -548,16 +559,69 @@ bool JitScript::ensureHasCachedIonData(J
   if (!data) {
     return false;
   }
 
   cachedIonData_ = std::move(data);
   return true;
 }
 
+void JitScript::setBaselineScriptImpl(JSScript* script,
+                                      BaselineScript* baselineScript) {
+  JSRuntime* rt = script->runtimeFromMainThread();
+  setBaselineScriptImpl(rt->defaultFreeOp(), script, baselineScript);
+}
+
+void JitScript::setBaselineScriptImpl(JSFreeOp* fop, JSScript* script,
+                                      BaselineScript* baselineScript) {
+  if (hasBaselineScript()) {
+    BaselineScript::writeBarrierPre(script->zone(), baselineScript_);
+    fop->removeCellMemory(script, baselineScript_->allocBytes(),
+                          MemoryUse::BaselineScript);
+    baselineScript_ = nullptr;
+  }
+
+  MOZ_ASSERT(!ionScript_ || ionScript_ == ION_DISABLED_SCRIPT);
+
+  baselineScript_ = baselineScript;
+  if (hasBaselineScript()) {
+    AddCellMemory(script, baselineScript_->allocBytes(),
+                  MemoryUse::BaselineScript);
+  }
+
+  script->resetWarmUpResetCounter();
+  script->updateJitCodeRaw(fop->runtime());
+}
+
+void JitScript::setIonScriptImpl(JSScript* script, IonScript* ionScript) {
+  JSRuntime* rt = script->runtimeFromMainThread();
+  setIonScriptImpl(rt->defaultFreeOp(), script, ionScript);
+}
+
+void JitScript::setIonScriptImpl(JSFreeOp* fop, JSScript* script,
+                                 IonScript* ionScript) {
+  MOZ_ASSERT_IF(ionScript != ION_DISABLED_SCRIPT,
+                !baselineScript()->hasPendingIonBuilder());
+
+  if (hasIonScript()) {
+    IonScript::writeBarrierPre(script->zone(), ionScript_);
+    fop->removeCellMemory(script, ionScript_->allocBytes(),
+                          MemoryUse::IonScript);
+    ionScript_ = nullptr;
+  }
+
+  ionScript_ = ionScript;
+  MOZ_ASSERT_IF(hasIonScript(), hasBaselineScript());
+  if (hasIonScript()) {
+    AddCellMemory(script, ionScript_->allocBytes(), MemoryUse::IonScript);
+  }
+
+  script->updateJitCodeRaw(fop->runtime());
+}
+
 #ifdef JS_STRUCTURED_SPEW
 static bool GetStubEnteredCount(ICStub* stub, uint32_t* count) {
   switch (stub->kind()) {
     case ICStub::CacheIR_Regular:
       *count = stub->toCacheIR_Regular()->enteredCount();
       return true;
     case ICStub::CacheIR_Updated:
       *count = stub->toCacheIR_Updated()->enteredCount();
--- a/js/src/jit/JitScript.h
+++ b/js/src/jit/JitScript.h
@@ -30,16 +30,24 @@ struct DependentWasmImport {
 
 // Information about a script's bytecode, used by IonBuilder. This is cached
 // in JitScript.
 struct IonBytecodeInfo {
   bool usesEnvironmentChain = false;
   bool modifiesArguments = false;
 };
 
+// Magic BaselineScript value indicating Baseline compilation has been disabled.
+#define BASELINE_DISABLED_SCRIPT ((js::jit::BaselineScript*)0x1)
+
+// Magic IonScript values indicating Ion compilation has been disabled or the
+// script is being Ion-compiled off-thread.
+#define ION_DISABLED_SCRIPT ((js::jit::IonScript*)0x1)
+#define ION_COMPILING_SCRIPT ((js::jit::IonScript*)0x2)
+
 // [SMDOC] JitScript
 //
 // JitScript stores type inference data, Baseline ICs and other JIT-related data
 // for a script. Scripts with a JitScript can run in the Baseline Interpreter.
 //
 // IC Data
 // =======
 // All IC data for Baseline (Interpreter and JIT) is stored in JitScript. Ion
@@ -149,16 +157,24 @@ class alignas(uintptr_t) JitScript final
 
     CachedIonData(const CachedIonData&) = delete;
     void operator=(const CachedIonData&) = delete;
 
     void trace(JSTracer* trc);
   };
   js::UniquePtr<CachedIonData> cachedIonData_;
 
+  // Baseline code for the script. Either nullptr, BASELINE_DISABLED_SCRIPT or
+  // a valid BaselineScript*.
+  BaselineScript* baselineScript_ = nullptr;
+
+  // Ion code for this script. Either nullptr, ION_DISABLED_SCRIPT,
+  // ION_COMPILING_SCRIPT or a valid IonScript*.
+  IonScript* ionScript_ = nullptr;
+
   // Offset of the StackTypeSet array.
   uint32_t typeSetOffset_ = 0;
 
   // Offset of the bytecode type map.
   uint32_t bytecodeTypeMapOffset_ = 0;
 
   // This field is used to avoid binary searches for the sought entry when
   // bytecode map queries are in linear order.
@@ -359,16 +375,20 @@ class alignas(uintptr_t) JitScript final
 
   static void Destroy(Zone* zone, JitScript* script);
 
   static constexpr size_t offsetOfICEntries() { return sizeof(JitScript); }
 
   static constexpr size_t offsetOfJitCodeSkipArgCheck() {
     return offsetof(JitScript, jitCodeSkipArgCheck_);
   }
+  static size_t offsetOfBaselineScript() {
+    return offsetof(JitScript, baselineScript_);
+  }
+  static size_t offsetOfIonScript() { return offsetof(JitScript, ionScript_); }
 
 #ifdef DEBUG
   void printTypes(JSContext* cx, HandleScript script);
 #endif
 
   void prepareForDestruction(Zone* zone) {
     // When the script contains pointers to nursery things, the store buffer can
     // contain entries that point into the fallback stub space. Since we can
@@ -456,16 +476,85 @@ class alignas(uintptr_t) JitScript final
     return hasCachedIonData() ? cachedIonData().inlinedBytecodeLength : 0;
   }
   void setInlinedBytecodeLength(uint32_t len) {
     if (len > UINT16_MAX) {
       len = UINT16_MAX;
     }
     cachedIonData().inlinedBytecodeLength = len;
   }
+
+ private:
+  // Methods to set baselineScript_ to a BaselineScript*, nullptr, or
+  // BASELINE_DISABLED_SCRIPT.
+  void setBaselineScriptImpl(JSScript* script, BaselineScript* baselineScript);
+  void setBaselineScriptImpl(JSFreeOp* fop, JSScript* script,
+                             BaselineScript* baselineScript);
+
+ public:
+  // Methods for getting/setting/clearing a BaselineScript*.
+  bool hasBaselineScript() const {
+    bool res = baselineScript_ && baselineScript_ != BASELINE_DISABLED_SCRIPT;
+    MOZ_ASSERT_IF(!res, !hasIonScript());
+    return res;
+  }
+  BaselineScript* baselineScript() const {
+    MOZ_ASSERT(hasBaselineScript());
+    return baselineScript_;
+  }
+  void setBaselineScript(JSScript* script, BaselineScript* baselineScript) {
+    MOZ_ASSERT(!hasBaselineScript());
+    setBaselineScriptImpl(script, baselineScript);
+    MOZ_ASSERT(hasBaselineScript());
+  }
+  void clearBaselineScript(JSFreeOp* fop, JSScript* script) {
+    MOZ_ASSERT(hasBaselineScript());
+    setBaselineScriptImpl(fop, script, nullptr);
+  }
+
+ private:
+  // Methods to set ionScript_ to an IonScript*, nullptr, or one of the special
+  // ION_{DISABLED,COMPILING}_SCRIPT values.
+  void setIonScriptImpl(JSFreeOp* fop, JSScript* script, IonScript* ionScript);
+  void setIonScriptImpl(JSScript* script, IonScript* ionScript);
+
+ public:
+  // Methods for getting/setting/clearing an IonScript*.
+  bool hasIonScript() const {
+    bool res = ionScript_ && ionScript_ != ION_DISABLED_SCRIPT &&
+               ionScript_ != ION_COMPILING_SCRIPT;
+    MOZ_ASSERT_IF(res, baselineScript_);
+    return res;
+  }
+  IonScript* ionScript() const {
+    MOZ_ASSERT(hasIonScript());
+    return ionScript_;
+  }
+  void setIonScript(JSScript* script, IonScript* ionScript) {
+    MOZ_ASSERT(!hasIonScript());
+    setIonScriptImpl(script, ionScript);
+    MOZ_ASSERT(hasIonScript());
+  }
+  void clearIonScript(JSFreeOp* fop, JSScript* script) {
+    MOZ_ASSERT(hasIonScript());
+    setIonScriptImpl(fop, script, nullptr);
+  }
+
+  // Methods for off-thread compilation.
+  bool isIonCompilingOffThread() const {
+    return ionScript_ == ION_COMPILING_SCRIPT;
+  }
+  void setIsIonCompilingOffThread(JSScript* script) {
+    MOZ_ASSERT(ionScript_ == nullptr);
+    setIonScriptImpl(script, ION_COMPILING_SCRIPT);
+  }
+  void clearIsIonCompilingOffThread(JSScript* script) {
+    MOZ_ASSERT(isIonCompilingOffThread());
+    setIonScriptImpl(script, nullptr);
+  }
 };
 
 // Ensures no JitScripts are purged in the current zone.
 class MOZ_RAII AutoKeepJitScripts {
   TypeZone& zone_;
   bool prev_;
 
   AutoKeepJitScripts(const AutoKeepJitScripts&) = delete;
--- a/js/src/jit/arm/Bailouts-arm.cpp
+++ b/js/src/jit/arm/Bailouts-arm.cpp
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/arm/Assembler-arm.h"
 #include "jit/Bailouts.h"
 #include "jit/JitRealm.h"
 #include "vm/JSContext.h"
 #include "vm/Realm.h"
 
+#include "vm/JSScript-inl.h"
+
 using namespace js;
 using namespace js::jit;
 
 namespace js {
 namespace jit {
 
 class BailoutStack {
   uintptr_t frameClassId_;
--- a/js/src/jit/arm64/Bailouts-arm64.cpp
+++ b/js/src/jit/arm64/Bailouts-arm64.cpp
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/Bailouts.h"
 
+#include "vm/JSScript-inl.h"
+
 using namespace js;
 using namespace js::jit;
 
 namespace js {
 namespace jit {
 
 class BailoutStack {
   RegisterDump::FPUArray fpregs_;
--- a/js/src/jit/mips32/Bailouts-mips32.cpp
+++ b/js/src/jit/mips32/Bailouts-mips32.cpp
@@ -4,16 +4,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/mips32/Bailouts-mips32.h"
 
 #include "vm/JSContext.h"
 #include "vm/Realm.h"
 
+#include "vm/JSScript-inl.h"
+
 using namespace js;
 using namespace js::jit;
 
 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations,
                                    BailoutStack* bailout)
     : machine_(bailout->machine()) {
   uint8_t* sp = bailout->parentStackPointer();
   framePointer_ = sp + bailout->frameSize();
--- a/js/src/jit/mips64/Bailouts-mips64.cpp
+++ b/js/src/jit/mips64/Bailouts-mips64.cpp
@@ -4,16 +4,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/mips64/Bailouts-mips64.h"
 
 #include "vm/JSContext.h"
 #include "vm/Realm.h"
 
+#include "vm/JSScript-inl.h"
+
 using namespace js;
 using namespace js::jit;
 
 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations,
                                    BailoutStack* bailout)
     : machine_(bailout->machineState()) {
   uint8_t* sp = bailout->parentStackPointer();
   framePointer_ = sp + bailout->frameSize();
--- a/js/src/jit/x64/Bailouts-x64.cpp
+++ b/js/src/jit/x64/Bailouts-x64.cpp
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/Bailouts.h"
 
+#include "vm/JSScript-inl.h"
+
 using namespace js;
 using namespace js::jit;
 
 #if defined(_WIN32)
 #  pragma pack(push, 1)
 #endif
 
 namespace js {
--- a/js/src/jit/x86/Bailouts-x86.cpp
+++ b/js/src/jit/x86/Bailouts-x86.cpp
@@ -4,16 +4,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/Bailouts.h"
 #include "jit/JitRealm.h"
 #include "vm/JSContext.h"
 #include "vm/Realm.h"
 
+#include "vm/JSScript-inl.h"
+
 using namespace js;
 using namespace js::jit;
 
 #if defined(_WIN32)
 #  pragma pack(push, 1)
 #endif
 
 namespace js {
--- a/js/src/jsapi-tests/testPreserveJitCode.cpp
+++ b/js/src/jsapi-tests/testPreserveJitCode.cpp
@@ -5,16 +5,17 @@
 #include "mozilla/Utf8.h"  // mozilla::Utf8Unit
 
 #include "jit/Ion.h"                      // js::jit::IsIonEnabled
 #include "js/CompilationAndEvaluation.h"  // JS::CompileFunction
 #include "js/SourceText.h"                // JS::Source{Ownership,Text}
 #include "jsapi-tests/tests.h"
 
 #include "vm/JSObject-inl.h"
+#include "vm/JSScript-inl.h"
 
 using namespace JS;
 
 static void ScriptCallback(JSRuntime* rt, void* data, JSScript* script,
                            const JS::AutoRequireNoGC& nogc) {
   unsigned& count = *static_cast<unsigned*>(data);
   if (script->hasIonScript()) {
     ++count;
--- a/js/src/vm/JSScript-inl.h
+++ b/js/src/vm/JSScript-inl.h
@@ -166,9 +166,67 @@ inline bool JSScript::ensureHasAnalyzedA
   }
   return js::jit::AnalyzeArgumentsUsage(cx, this);
 }
 
 inline bool JSScript::isDebuggee() const {
   return realm_->debuggerObservesAllExecution() || hasDebugScript();
 }
 
+inline bool JSScript::hasBaselineScript() const {
+  return jitScript_ && jitScript_->hasBaselineScript();
+}
+
+inline bool JSScript::hasIonScript() const {
+  return jitScript_ && jitScript_->hasIonScript();
+}
+
+inline bool JSScript::isIonCompilingOffThread() const {
+  return jitScript_ && jitScript_->isIonCompilingOffThread();
+}
+
+inline bool JSScript::canBaselineCompile() const {
+  bool disabled = hasFlag(MutableFlags::BaselineDisabled);
+#ifdef DEBUG
+  if (jitScript_) {
+    bool jitScriptDisabled =
+        jitScript_->baselineScript_ == BASELINE_DISABLED_SCRIPT;
+    MOZ_ASSERT(disabled == jitScriptDisabled);
+  }
+#endif
+  return !disabled;
+}
+
+inline bool JSScript::canIonCompile() const {
+  bool disabled = hasFlag(MutableFlags::IonDisabled);
+#ifdef DEBUG
+  if (jitScript_) {
+    bool jitScriptDisabled = jitScript_->ionScript_ == ION_DISABLED_SCRIPT;
+    MOZ_ASSERT(disabled == jitScriptDisabled);
+  }
+#endif
+  return !disabled;
+}
+
+inline void JSScript::disableBaselineCompile() {
+  MOZ_ASSERT(!hasBaselineScript());
+  setFlag(MutableFlags::BaselineDisabled);
+  if (jitScript_) {
+    jitScript_->setBaselineScriptImpl(this, BASELINE_DISABLED_SCRIPT);
+  }
+}
+
+inline void JSScript::disableIon() {
+  setFlag(MutableFlags::IonDisabled);
+  if (jitScript_) {
+    jitScript_->setIonScriptImpl(this, ION_DISABLED_SCRIPT);
+  }
+}
+
+inline js::jit::BaselineScript* JSScript::baselineScript() const {
+  return jitScript_->baselineScript();
+}
+
+inline js::jit::IonScript* JSScript::ionScript() const {
+  return jitScript_->ionScript();
+}
+
 #endif /* vm_JSScript_inl_h */
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -1506,59 +1506,16 @@ js::PCCounts* ScriptCounts::getThrowCoun
 }
 
 size_t ScriptCounts::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
   return mallocSizeOf(this) + pcCounts_.sizeOfExcludingThis(mallocSizeOf) +
          throwCounts_.sizeOfExcludingThis(mallocSizeOf) +
          ionCounts_->sizeOfIncludingThis(mallocSizeOf);
 }
 
-void JSScript::setBaselineScriptImpl(JSRuntime* rt,
-                                     js::jit::BaselineScript* baselineScript) {
-  setBaselineScriptImpl(rt->defaultFreeOp(), baselineScript);
-}
-
-void JSScript::setBaselineScriptImpl(JSFreeOp* fop,
-                                     js::jit::BaselineScript* baselineScript) {
-  if (hasBaselineScript()) {
-    js::jit::BaselineScript::writeBarrierPre(zone(), baseline);
-    fop->removeCellMemory(this, baseline->allocBytes(),
-                          js::MemoryUse::BaselineScript);
-    baseline = nullptr;
-  }
-  MOZ_ASSERT(!ion || ion == ION_DISABLED_SCRIPT);
-
-  baseline = baselineScript;
-  if (hasBaselineScript()) {
-    AddCellMemory(this, baseline->allocBytes(), js::MemoryUse::BaselineScript);
-  }
-  resetWarmUpResetCounter();
-  updateJitCodeRaw(fop->runtime());
-}
-
-void JSScript::setIonScriptImpl(JSRuntime* rt, js::jit::IonScript* ionScript) {
-  setIonScriptImpl(rt->defaultFreeOp(), ionScript);
-}
-
-void JSScript::setIonScriptImpl(JSFreeOp* fop, js::jit::IonScript* ionScript) {
-  MOZ_ASSERT_IF(ionScript != ION_DISABLED_SCRIPT,
-                !baselineScript()->hasPendingIonBuilder());
-  if (hasIonScript()) {
-    js::jit::IonScript::writeBarrierPre(zone(), ion);
-    fop->removeCellMemory(this, ion->allocBytes(), js::MemoryUse::IonScript);
-    ion = nullptr;
-  }
-  ion = ionScript;
-  MOZ_ASSERT_IF(hasIonScript(), hasBaselineScript());
-  if (hasIonScript()) {
-    AddCellMemory(this, ion->allocBytes(), js::MemoryUse::IonScript);
-  }
-  updateJitCodeRaw(fop->runtime());
-}
-
 js::PCCounts* JSScript::maybeGetPCCounts(jsbytecode* pc) {
   MOZ_ASSERT(containsPC(pc));
   return getScriptCounts().maybeGetPCCounts(pcToOffset(pc));
 }
 
 const js::PCCounts* JSScript::maybeGetThrowCounts(jsbytecode* pc) {
   MOZ_ASSERT(containsPC(pc));
   return getScriptCounts().maybeGetThrowCounts(pcToOffset(pc));
@@ -5336,25 +5293,26 @@ LazyScript* LazyScript::CreateForXDR(
   }
 
   return res;
 }
 
 void JSScript::updateJitCodeRaw(JSRuntime* rt) {
   MOZ_ASSERT(rt);
   uint8_t* jitCodeSkipArgCheck;
-  if (hasBaselineScript() && baseline->hasPendingIonBuilder()) {
+  if (hasBaselineScript() && baselineScript()->hasPendingIonBuilder()) {
     MOZ_ASSERT(!isIonCompilingOffThread());
     jitCodeRaw_ = rt->jitRuntime()->lazyLinkStub().value;
     jitCodeSkipArgCheck = jitCodeRaw_;
   } else if (hasIonScript()) {
+    jit::IonScript* ion = ionScript();
     jitCodeRaw_ = ion->method()->raw();
     jitCodeSkipArgCheck = jitCodeRaw_ + ion->getSkipArgCheckEntryOffset();
   } else if (hasBaselineScript()) {
-    jitCodeRaw_ = baseline->method()->raw();
+    jitCodeRaw_ = baselineScript()->method()->raw();
     jitCodeSkipArgCheck = jitCodeRaw_;
   } else if (jitScript() && js::jit::IsBaselineInterpreterEnabled()) {
     jitCodeRaw_ = rt->jitRuntime()->baselineInterpreter().codeRaw();
     jitCodeSkipArgCheck = jitCodeRaw_;
   } else {
     jitCodeRaw_ = rt->jitRuntime()->interpreterStub().value;
     jitCodeSkipArgCheck = jitCodeRaw_;
   }
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -56,21 +56,16 @@ namespace js {
 
 namespace jit {
 class AutoKeepJitScripts;
 struct BaselineScript;
 struct IonScriptCounts;
 class JitScript;
 }  // namespace jit
 
-#define ION_DISABLED_SCRIPT ((js::jit::IonScript*)0x1)
-#define ION_COMPILING_SCRIPT ((js::jit::IonScript*)0x2)
-
-#define BASELINE_DISABLED_SCRIPT ((js::jit::BaselineScript*)0x1)
-
 class AutoSweepJitScript;
 class GCParallelTask;
 class LazyScript;
 class ModuleObject;
 class RegExpObject;
 class SourceCompressionTask;
 class Shape;
 class DebugAPI;
@@ -2284,25 +2279,16 @@ class JSScript : public js::BaseScript {
 
  public:
   JS::Realm* realm_ = nullptr;
 
  private:
   // JIT and type inference data for this script. May be purged on GC.
   js::jit::JitScript* jitScript_ = nullptr;
 
-  /*
-   * Information attached by Ion. Nexto a valid IonScript this could be
-   * ION_DISABLED_SCRIPT or ION_COMPILING_SCRIPT.
-   */
-  js::jit::IonScript* ion = nullptr;
-
-  /* Information attached by Baseline. */
-  js::jit::BaselineScript* baseline = nullptr;
-
   /* Information used to re-lazify a lazily-parsed interpreted function. */
   js::LazyScript* lazyScript = nullptr;
 
   // 32-bit fields.
 
   // Number of times the script has been called or has had backedges taken.
   // When running in ion, also increased for any inlined scripts. Reset if
   // the script's JIT code is forcibly discarded.
@@ -2601,124 +2587,27 @@ class JSScript : public js::BaseScript {
   }
   static constexpr size_t offsetOfPrivateScriptData() {
     return offsetof(JSScript, data_);
   }
   static constexpr size_t offsetOfJitScript() {
     return offsetof(JSScript, jitScript_);
   }
 
- private:
-  // Methods to set |ion| to an IonScript*, nullptr, or one of the special
-  // ION_{DISABLED,COMPILING,PENDING}_SCRIPT values.
-  void setIonScriptImpl(JSFreeOp* fop, js::jit::IonScript* ionScript);
-  void setIonScriptImpl(JSRuntime* rt, js::jit::IonScript* ionScript);
-
- public:
-  // Methods for getting/setting/clearing an IonScript*.
-  bool hasIonScript() const {
-    bool res = ion && ion != ION_DISABLED_SCRIPT && ion != ION_COMPILING_SCRIPT;
-    MOZ_ASSERT_IF(res, baseline);
-    return res;
-  }
-  js::jit::IonScript* ionScript() const {
-    MOZ_ASSERT(hasIonScript());
-    return ion;
-  }
-  void setIonScript(JSRuntime* rt, js::jit::IonScript* ionScript) {
-    MOZ_ASSERT(!hasIonScript());
-    setIonScriptImpl(rt, ionScript);
-    MOZ_ASSERT(hasIonScript());
-  }
-  void clearIonScript(JSFreeOp* fop) {
-    MOZ_ASSERT(hasIonScript());
-    setIonScriptImpl(fop, nullptr);
-  }
-
-  // Methods for the Ion-disabled status.
-  bool canIonCompile() const {
-    bool disabled = hasFlag(MutableFlags::IonDisabled);
-    MOZ_ASSERT_IF(disabled, ion == ION_DISABLED_SCRIPT);
-    return !disabled;
-  }
-  void disableIon(JSRuntime* rt) {
-    setFlag(MutableFlags::IonDisabled);
-    setIonScriptImpl(rt, ION_DISABLED_SCRIPT);
-  }
-
-  // Methods for off-thread compilation.
-  bool isIonCompilingOffThread() const { return ion == ION_COMPILING_SCRIPT; }
-  void setIsIonCompilingOffThread(JSRuntime* rt) {
-    MOZ_ASSERT(!ion);
-    setIonScriptImpl(rt, ION_COMPILING_SCRIPT);
-  }
-  void clearIsIonCompilingOffThread(JSRuntime* rt) {
-    MOZ_ASSERT(isIonCompilingOffThread());
-    setIonScriptImpl(rt, nullptr);
-  }
-
- private:
-  // Methods to set |baseline| to a BaselineScript*, nullptr, or
-  // BASELINE_DISABLED_SCRIPT.
-  void setBaselineScriptImpl(JSRuntime* rt,
-                             js::jit::BaselineScript* baselineScript);
-  void setBaselineScriptImpl(JSFreeOp* fop,
-                             js::jit::BaselineScript* baselineScript);
-
- public:
-  // Methods for getting/setting/clearing a BaselineScript*.
-  bool hasBaselineScript() const {
-    bool res = baseline && baseline != BASELINE_DISABLED_SCRIPT;
-    MOZ_ASSERT_IF(!res, !hasIonScript());
-    return res;
-  }
-  js::jit::BaselineScript* baselineScript() const {
-    MOZ_ASSERT(hasBaselineScript());
-    return baseline;
-  }
-  void setBaselineScript(JSRuntime* rt,
-                         js::jit::BaselineScript* baselineScript) {
-    MOZ_ASSERT(!hasBaselineScript());
-    setBaselineScriptImpl(rt, baselineScript);
-    MOZ_ASSERT(hasBaselineScript());
-  }
-  void clearBaselineScript(JSFreeOp* fop) {
-    MOZ_ASSERT(hasBaselineScript());
-    setBaselineScriptImpl(fop, nullptr);
-  }
-
-  // Methods for the Baseline-disabled status.
-  bool canBaselineCompile() const {
-    bool disabled = hasFlag(MutableFlags::BaselineDisabled);
-    MOZ_ASSERT_IF(disabled, baseline == BASELINE_DISABLED_SCRIPT);
-    return !disabled;
-  }
-  void disableBaselineCompile(JSRuntime* rt) {
-    MOZ_ASSERT(!hasBaselineScript());
-    setFlag(MutableFlags::BaselineDisabled);
-    setBaselineScriptImpl(rt, BASELINE_DISABLED_SCRIPT);
-  }
-
   void updateJitCodeRaw(JSRuntime* rt);
 
-  static size_t offsetOfBaselineScript() {
-    return offsetof(JSScript, baseline);
-  }
-  static size_t offsetOfIonScript() { return offsetof(JSScript, ion); }
-
   // We don't relazify functions with a JitScript or JIT code, but some
   // callers (XDR, testing functions) want to know whether this script is
   // relazifiable ignoring (or after) discarding JIT code.
   bool isRelazifiableIgnoringJitCode() const {
     return (selfHosted() || lazyScript) && !hasInnerFunctions() &&
            !isGenerator() && !isAsync() && !isDefaultClassConstructor() &&
            !doNotRelazify() && !hasCallSiteObj();
   }
   bool isRelazifiable() const {
-    MOZ_ASSERT_IF(hasBaselineScript() || hasIonScript(), jitScript_);
     return isRelazifiableIgnoringJitCode() && !jitScript_;
   }
   void setLazyScript(js::LazyScript* lazy) { lazyScript = lazy; }
   js::LazyScript* maybeLazyScript() { return lazyScript; }
 
   /*
    * Original compiled function for the script, if it has a function.
    * nullptr for global and eval scripts.
@@ -2809,16 +2698,29 @@ class JSScript : public js::BaseScript {
   inline bool ensureHasJitScript(JSContext* cx, js::jit::AutoKeepJitScripts&);
 
   bool hasJitScript() const { return jitScript_ != nullptr; }
   js::jit::JitScript* jitScript() { return jitScript_; }
 
   void maybeReleaseJitScript(JSFreeOp* fop);
   void releaseJitScript(JSFreeOp* fop);
 
+  inline bool hasBaselineScript() const;
+  inline bool hasIonScript() const;
+
+  inline js::jit::BaselineScript* baselineScript() const;
+  inline js::jit::IonScript* ionScript() const;
+
+  inline bool isIonCompilingOffThread() const;
+  inline bool canIonCompile() const;
+  inline void disableIon();
+
+  inline bool canBaselineCompile() const;
+  inline void disableBaselineCompile();
+
   inline js::GlobalObject& global() const;
   inline bool hasGlobal(const js::GlobalObject* global) const;
   js::GlobalObject& uninlinedGlobal() const;
 
   uint32_t bodyScopeIndex() const {
     return immutableScriptData()->bodyScopeIndex;
   }
 
--- a/js/src/vm/TypeInference-inl.h
+++ b/js/src/vm/TypeInference-inl.h
@@ -30,16 +30,17 @@
 #include "vm/ObjectGroup.h"
 #include "vm/Shape.h"
 #include "vm/SharedArrayObject.h"
 #include "vm/StringObject.h"
 #include "vm/TypedArrayObject.h"
 
 #include "jit/JitScript-inl.h"
 #include "vm/JSContext-inl.h"
+#include "vm/JSScript-inl.h"
 #include "vm/ObjectGroup-inl.h"
 
 namespace js {
 
 /////////////////////////////////////////////////////////////////////
 // RecompileInfo
 /////////////////////////////////////////////////////////////////////
 
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -2685,23 +2685,24 @@ void TypeZone::addPendingRecompile(JSCon
 void TypeZone::addPendingRecompile(JSContext* cx, JSScript* script) {
   MOZ_ASSERT(script);
 
   CancelOffThreadIonCompile(script);
 
   // Let the script warm up again before attempting another compile.
   script->resetWarmUpCounterToDelayIonCompilation();
 
-  if (script->hasIonScript()) {
-    addPendingRecompile(
-        cx, RecompileInfo(script, script->ionScript()->compilationId()));
-  }
-
-  // Trigger recompilation of any callers inlining this script.
   if (JitScript* jitScript = script->jitScript()) {
+    // Trigger recompilation of the IonScript.
+    if (jitScript->hasIonScript()) {
+      addPendingRecompile(
+          cx, RecompileInfo(script, jitScript->ionScript()->compilationId()));
+    }
+
+    // Trigger recompilation of any callers inlining this script.
     AutoSweepJitScript sweep(script);
     RecompileInfoVector* inlinedCompilations =
         jitScript->maybeInlinedCompilations(sweep);
     if (inlinedCompilations) {
       for (const RecompileInfo& info : *inlinedCompilations) {
         addPendingRecompile(cx, info);
       }
       inlinedCompilations->clearAndFree();