Bug 1562272 - Move script atoms from SharedScriptData to RuntimeScriptData. r=jandem
authorTed Campbell <tcampbell@mozilla.com>
Tue, 09 Jul 2019 16:15:26 +0000
changeset 542413 c1add3b2d16e70ea6dd749ccba7884e075e9d6a8
parent 542412 af6d2ceb1b9dbab8583d117216abfa2d4cf5ef41
child 542414 83e3a8db101ecf0d5b80366aa614b8ec0bd7db43
push id11848
push userffxbld-merge
push dateMon, 26 Aug 2019 19:26:25 +0000
treeherdermozilla-beta@9b31bfdfac10 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1562272
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 1562272 - Move script atoms from SharedScriptData to RuntimeScriptData. r=jandem This results in SharedScriptData having no pointers left and being an easy candidate for sharing accross procecces. The XDR and InitFromEmitter functions will be split in follow-up patches to make the changes easier to follow. Differential Revision: https://phabricator.services.mozilla.com/D36353
js/src/jit/BaselineCompiler.cpp
js/src/vm/JSScript.cpp
js/src/vm/JSScript.h
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -473,19 +473,18 @@ void BaselineCompilerCodeGen::loadScript
   MOZ_CRASH("BaselineCompiler shouldn't call loadScriptAtom");
 }
 
 template <>
 void BaselineInterpreterCodeGen::loadScriptAtom(Register index, Register dest) {
   MOZ_ASSERT(index != dest);
   loadScript(dest);
   masm.loadPtr(Address(dest, JSScript::offsetOfScriptData()), dest);
-  masm.loadPtr(Address(dest, RuntimeScriptData::offsetOfSSD()), dest);
   masm.loadPtr(
-      BaseIndex(dest, index, ScalePointer, SharedScriptData::offsetOfAtoms()),
+      BaseIndex(dest, index, ScalePointer, RuntimeScriptData::offsetOfAtoms()),
       dest);
 }
 
 template <>
 void BaselineCompilerCodeGen::emitInitializeLocals() {
   // Initialize all locals to |undefined|. Lexical bindings are temporal
   // dead zoned in bytecode.
 
@@ -1219,19 +1218,17 @@ void BaselineInterpreterCodeGen::emitIni
   masm.loadPtr(Address(scratch1, JSScript::offsetOfJitScript()), scratch2);
   masm.computeEffectiveAddress(
       Address(scratch2, JitScript::offsetOfICEntries()), scratch2);
   masm.storePtr(scratch2, frame.addressOfInterpreterICEntry());
 
   // Initialize interpreter pc.
   masm.loadPtr(Address(scratch1, JSScript::offsetOfScriptData()), scratch1);
   masm.loadPtr(Address(scratch1, RuntimeScriptData::offsetOfSSD()), scratch1);
-  masm.load32(Address(scratch1, SharedScriptData::offsetOfCodeOffset()),
-              scratch2);
-  masm.addPtr(scratch2, scratch1);
+  masm.addPtr(Imm32(SharedScriptData::offsetOfCode()), scratch1);
 
   if (HasInterpreterPCReg()) {
     MOZ_ASSERT(scratch1 == InterpreterPCReg,
                "pc must be stored in the pc register");
   } else {
     masm.storePtr(scratch1, frame.addressOfInterpreterPC());
   }
 }
@@ -4870,22 +4867,20 @@ void BaselineCodeGen<Handler>::emitInter
 
   // Load the resume pcOffset in |resumeIndex|.
   masm.load32(Address(script, SharedScriptData::offsetOfResumeOffsetsOffset()),
               scratch);
   masm.computeEffectiveAddress(BaseIndex(scratch, resumeIndex, TimesFour),
                                scratch);
   masm.load32(BaseIndex(script, scratch, TimesOne), resumeIndex);
 
-  // Load pc* in |script|.
-  masm.load32(Address(script, SharedScriptData::offsetOfCodeOffset()), scratch);
-  masm.addPtr(scratch, script);
-
   // Add resume offset to PC, jump to it.
-  masm.addPtr(resumeIndex, script);
+  masm.computeEffectiveAddress(BaseIndex(script, resumeIndex, TimesOne,
+                                         SharedScriptData::offsetOfCode()),
+                               script);
   Address pcAddr(BaselineFrameReg,
                  BaselineFrame::reverseOffsetOfInterpreterPC());
   masm.storePtr(script, pcAddr);
   emitJumpToInterpretOpLabel();
 }
 
 template <>
 void BaselineCompilerCodeGen::jumpToResumeEntry(Register resumeIndex,
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -654,22 +654,23 @@ XDRResult js::PrivateScriptData::XDR(XDR
 
   // Verify marker to detect data corruption after decoding GC things. A
   // mismatch here indicates we will almost certainly crash in release.
   MOZ_TRY(xdr->codeMarker(0xF83B989A));
 
   return Ok();
 }
 
-/* static */ size_t SharedScriptData::AllocationSize(
-    uint32_t codeLength, uint32_t noteLength, uint32_t natoms,
-    uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes) {
+/* static */ size_t SharedScriptData::AllocationSize(uint32_t codeLength,
+                                                     uint32_t noteLength,
+                                                     uint32_t numResumeOffsets,
+                                                     uint32_t numScopeNotes,
+                                                     uint32_t numTryNotes) {
   size_t size = sizeof(SharedScriptData);
 
-  size += natoms * sizeof(GCPtrAtom);
   size += sizeof(Flags);
   size += codeLength * sizeof(jsbytecode);
   size += noteLength * sizeof(jssrcnote);
 
   unsigned numOptionalArrays = unsigned(numResumeOffsets > 0) +
                                unsigned(numScopeNotes > 0) +
                                unsigned(numTryNotes > 0);
   size += numOptionalArrays * sizeof(uint32_t);
@@ -756,83 +757,77 @@ void SharedScriptData::initOptionalArray
   }
   flags->tryNotesEndIndex = offsetIndex;
 
   MOZ_ASSERT(endOffset() == cursor);
   (*pcursor) = cursor;
 }
 
 SharedScriptData::SharedScriptData(uint32_t codeLength, uint32_t noteLength,
-                                   uint32_t natoms, uint32_t numResumeOffsets,
+                                   uint32_t numResumeOffsets,
                                    uint32_t numScopeNotes, uint32_t numTryNotes)
     : codeLength_(codeLength) {
   // Variable-length data begins immediately after SharedScriptData itself.
   size_t cursor = sizeof(*this);
 
-  // Default-initialize 'atoms'
-  static_assert(alignof(SharedScriptData) >= alignof(GCPtrAtom),
-                "Incompatible alignment");
-  initElements<GCPtrAtom>(cursor, natoms);
-  cursor += natoms * sizeof(GCPtrAtom);
-
   // The following arrays are byte-aligned with additional padding to ensure
   // that together they maintain uint32_t-alignment.
   {
     MOZ_ASSERT(cursor % CodeNoteAlign == 0);
 
     // Zero-initialize 'flags'
     static_assert(CodeNoteAlign >= alignof(Flags), "Incompatible alignment");
     new (offsetToPointer<void>(cursor)) Flags{};
     cursor += sizeof(Flags);
 
     static_assert(alignof(Flags) >= alignof(jsbytecode),
                   "Incompatible alignment");
-    codeOffset_ = cursor;
     initElements<jsbytecode>(cursor, codeLength);
     cursor += codeLength * sizeof(jsbytecode);
 
     static_assert(alignof(jsbytecode) >= alignof(jssrcnote),
                   "Incompatible alignment");
     initElements<jssrcnote>(cursor, noteLength);
     cursor += noteLength * sizeof(jssrcnote);
 
     MOZ_ASSERT(cursor % CodeNoteAlign == 0);
   }
 
   // Initialization for remaining arrays.
   initOptionalArrays(&cursor, &flagsRef(), numResumeOffsets, numScopeNotes,
                      numTryNotes);
 
   // Check that we correctly recompute the expected values.
-  MOZ_ASSERT(this->natoms() == natoms);
   MOZ_ASSERT(this->codeLength() == codeLength);
   MOZ_ASSERT(this->noteLength() == noteLength);
 
   // Sanity check
-  MOZ_ASSERT(AllocationSize(codeLength, noteLength, natoms, numResumeOffsets,
+  MOZ_ASSERT(AllocationSize(codeLength, noteLength, numResumeOffsets,
                             numScopeNotes, numTryNotes) == cursor);
 }
 
 template <XDRMode mode>
 /* static */
 XDRResult SharedScriptData::XDR(XDRState<mode>* xdr, HandleScript script) {
   uint32_t natoms = 0;
   uint32_t codeLength = 0;
   uint32_t noteLength = 0;
   uint32_t numResumeOffsets = 0;
   uint32_t numScopeNotes = 0;
   uint32_t numTryNotes = 0;
 
   JSContext* cx = xdr->cx();
+  RuntimeScriptData* rsd = nullptr;
   SharedScriptData* ssd = nullptr;
 
   if (mode == XDR_ENCODE) {
+    rsd = script->scriptData();
     ssd = script->sharedScriptData();
 
-    natoms = ssd->natoms();
+    natoms = rsd->natoms();
     codeLength = ssd->codeLength();
     noteLength = ssd->noteLength();
 
     numResumeOffsets = ssd->resumeOffsets().size();
     numScopeNotes = ssd->scopeNotes().size();
     numTryNotes = ssd->tryNotes().size();
   }
 
@@ -844,16 +839,17 @@ XDRResult SharedScriptData::XDR(XDRState
   MOZ_TRY(xdr->codeUint32(&numTryNotes));
 
   if (mode == XDR_DECODE) {
     if (!script->createSharedScriptData(cx, codeLength, noteLength, natoms,
                                         numResumeOffsets, numScopeNotes,
                                         numTryNotes)) {
       return xdr->fail(JS::TranscodeResult_Throw);
     }
+    rsd = script->scriptData();
     ssd = script->sharedScriptData();
   }
 
   MOZ_TRY(xdr->codeUint32(&ssd->mainOffset));
   MOZ_TRY(xdr->codeUint32(&ssd->nfixed));
   MOZ_TRY(xdr->codeUint32(&ssd->nslots));
   MOZ_TRY(xdr->codeUint32(&ssd->bodyScopeIndex));
   MOZ_TRY(xdr->codeUint32(&ssd->numICEntries));
@@ -865,17 +861,17 @@ XDRResult SharedScriptData::XDR(XDRState
 
   jsbytecode* code = ssd->code();
   jssrcnote* notes = ssd->notes();
   MOZ_TRY(xdr->codeBytes(code, codeLength));
   MOZ_TRY(xdr->codeBytes(notes, noteLength));
 
   {
     RootedAtom atom(cx);
-    GCPtrAtom* vector = ssd->atoms();
+    GCPtrAtom* vector = rsd->atoms();
 
     for (uint32_t i = 0; i != natoms; ++i) {
       if (mode == XDR_ENCODE) {
         atom = vector[i];
       }
       MOZ_TRY(XDRAtom(xdr, &atom));
       if (mode == XDR_DECODE) {
         vector[i].init(atom);
@@ -903,36 +899,48 @@ template
     XDRResult
     SharedScriptData::XDR(XDRState<XDR_ENCODE>* xdr, HandleScript script);
 
 template
     /* static */
     XDRResult
     SharedScriptData::XDR(XDRState<XDR_DECODE>* xdr, HandleScript script);
 
-/* static */ size_t RuntimeScriptData::AllocationSize() {
+/* static */ size_t RuntimeScriptData::AllocationSize(uint32_t natoms) {
   size_t size = sizeof(RuntimeScriptData);
 
+  size += natoms * sizeof(GCPtrAtom);
+
   return size;
 }
 
 // Placement-new elements of an array. This should optimize away for types with
 // trivial default initiation.
 template <typename T>
 void RuntimeScriptData::initElements(size_t offset, size_t length) {
   uintptr_t base = reinterpret_cast<uintptr_t>(this);
   DefaultInitializeElements<T>(reinterpret_cast<void*>(base + offset), length);
 }
 
-RuntimeScriptData::RuntimeScriptData() {
+RuntimeScriptData::RuntimeScriptData(uint32_t natoms) : natoms_(natoms) {
   // Variable-length data begins immediately after RuntimeScriptData itself.
   size_t cursor = sizeof(*this);
 
+  // Default-initialize trailing arrays.
+
+  static_assert(alignof(RuntimeScriptData) >= alignof(GCPtrAtom),
+                "Incompatible alignment");
+  initElements<GCPtrAtom>(cursor, natoms);
+  cursor += natoms * sizeof(GCPtrAtom);
+
+  // Check that we correctly recompute the expected values.
+  MOZ_ASSERT(this->natoms() == natoms);
+
   // Sanity check
-  MOZ_ASSERT(AllocationSize() == cursor);
+  MOZ_ASSERT(AllocationSize(natoms) == cursor);
 }
 
 template <XDRMode mode>
 XDRResult js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
                         HandleScriptSourceObject sourceObjectArg,
                         HandleFunction fun, MutableHandleScript scriptp) {
   using ImmutableFlags = JSScript::ImmutableFlags;
 
@@ -3475,66 +3483,64 @@ bool ScriptSource::setSourceMapURL(JSCon
 
 /* static */ mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent,
                              mozilla::recordreplay::Behavior::DontPreserve>
     ScriptSource::idCount_;
 
 /*
  * [SMDOC] JSScript data layout (shared)
  *
- * Shared script data management.
- *
- * SharedScriptData::data contains data that can be shared within a runtime.
- * The atoms() data is placed first to simplify its alignment.
+ * Script data that shareable across processes. There are no pointers (GC or
+ * otherwise) and the data is relocatable.
  *
  * Array elements   Pointed to by         Length
  * --------------   -------------         ------
- * GCPtrAtom        atoms()               natoms()
  * jsbytecode       code()                codeLength()
  * jsscrnote        notes()               noteLength()
  * uint32_t         resumeOffsets()
  * ScopeNote        scopeNotes()
  * JSTryNote        tryNotes()
  */
 
-SharedScriptData* js::SharedScriptData::new_(
-    JSContext* cx, uint32_t codeLength, uint32_t noteLength, uint32_t natoms,
-    uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes) {
+SharedScriptData* js::SharedScriptData::new_(JSContext* cx, uint32_t codeLength,
+                                             uint32_t noteLength,
+                                             uint32_t numResumeOffsets,
+                                             uint32_t numScopeNotes,
+                                             uint32_t numTryNotes) {
   // Compute size including trailing arrays
-  size_t size = AllocationSize(codeLength, noteLength, natoms, numResumeOffsets,
+  size_t size = AllocationSize(codeLength, noteLength, numResumeOffsets,
                                numScopeNotes, numTryNotes);
 
   // Allocate contiguous raw buffer
   void* raw = cx->pod_malloc<uint8_t>(size);
   MOZ_ASSERT(uintptr_t(raw) % alignof(SharedScriptData) == 0);
   if (!raw) {
     return nullptr;
   }
 
   // Constuct the SharedScriptData. Trailing arrays are uninitialized but
   // GCPtrs are put into a safe state.
-  return new (raw)
-      SharedScriptData(codeLength, noteLength, natoms, numResumeOffsets,
-                       numScopeNotes, numTryNotes);
-}
-
-RuntimeScriptData* js::RuntimeScriptData::new_(JSContext* cx) {
+  return new (raw) SharedScriptData(codeLength, noteLength, numResumeOffsets,
+                                    numScopeNotes, numTryNotes);
+}
+
+RuntimeScriptData* js::RuntimeScriptData::new_(JSContext* cx, uint32_t natoms) {
   // Compute size including trailing arrays
-  size_t size = AllocationSize();
+  size_t size = AllocationSize(natoms);
 
   // Allocate contiguous raw buffer
   void* raw = cx->pod_malloc<uint8_t>(size);
   MOZ_ASSERT(uintptr_t(raw) % alignof(RuntimeScriptData) == 0);
   if (!raw) {
     return nullptr;
   }
 
   // Constuct the RuntimeScriptData. Trailing arrays are uninitialized but
   // GCPtrs are put into a safe state.
-  return new (raw) RuntimeScriptData();
+  return new (raw) RuntimeScriptData(natoms);
 }
 
 bool JSScript::createSharedScriptData(JSContext* cx, uint32_t codeLength,
                                       uint32_t noteLength, uint32_t natoms,
                                       uint32_t numResumeOffsets,
                                       uint32_t numScopeNotes,
                                       uint32_t numTryNotes) {
 #ifdef DEBUG
@@ -3543,24 +3549,24 @@ bool JSScript::createSharedScriptData(JS
   size_t byteArrayLength =
       sizeof(SharedScriptData::Flags) + codeLength + noteLength;
   MOZ_ASSERT(byteArrayLength % sizeof(uint32_t) == 0,
              "Source notes should have been padded already");
 #endif
 
   MOZ_ASSERT(!scriptData_);
 
-  RefPtr<RuntimeScriptData> rsd(RuntimeScriptData::new_(cx));
+  RefPtr<RuntimeScriptData> rsd(RuntimeScriptData::new_(cx, natoms));
   if (!rsd) {
     return false;
   }
 
   js::UniquePtr<SharedScriptData> ssd(
-      SharedScriptData::new_(cx, codeLength, noteLength, natoms,
-                             numResumeOffsets, numScopeNotes, numTryNotes));
+      SharedScriptData::new_(cx, codeLength, noteLength, numResumeOffsets,
+                             numScopeNotes, numTryNotes));
   if (!ssd) {
     return false;
   }
 
   rsd->ssd_ = std::move(ssd);
   scriptData_ = std::move(rsd);
   return true;
 }
@@ -4986,16 +4992,17 @@ bool JSScript::hasBreakpointsAt(jsbyteco
 
   // Create and initialize SharedScriptData
   if (!script->createSharedScriptData(cx, codeLength, noteLength + nullLength,
                                       natoms, numResumeOffsets, numScopeNotes,
                                       numTryNotes)) {
     return false;
   }
 
+  js::RuntimeScriptData* rsd = script->scriptData();
   js::SharedScriptData* data = script->sharedScriptData();
 
   // Initialize POD fields
   data->mainOffset = bce->mainOffset();
   data->nfixed = bce->maxFixedSlots;
   data->nslots = nslots;
   data->bodyScopeIndex = bce->bodyScopeIndex;
   data->numICEntries = bce->bytecodeSection().numICEntries();
@@ -5003,49 +5010,42 @@ bool JSScript::hasBreakpointsAt(jsbyteco
       std::min<uint32_t>(uint32_t(JSScript::MaxBytecodeTypeSets),
                          bce->bytecodeSection().numTypeSets());
 
   if (bce->sc->isFunctionBox()) {
     data->funLength = bce->sc->asFunctionBox()->length;
   }
 
   // Initialize trailing arrays
-  InitAtomMap(*bce->perScriptData().atomIndices(), data->atoms());
   std::copy_n(bce->bytecodeSection().code().begin(), codeLength, data->code());
   std::copy_n(bce->bytecodeSection().notes().begin(), noteLength,
               data->notes());
   std::fill_n(data->notes() + noteLength, nullLength, SRC_NULL);
 
   bce->bytecodeSection().resumeOffsetList().finish(data->resumeOffsets());
   bce->bytecodeSection().scopeNoteList().finish(data->scopeNotes());
   bce->bytecodeSection().tryNoteList().finish(data->tryNotes());
 
+  InitAtomMap(*bce->perScriptData().atomIndices(), rsd->atoms());
+
   return true;
 }
 
-void SharedScriptData::traceChildren(JSTracer* trc) {
-  for (uint32_t i = 0; i < natoms(); ++i) {
-    TraceNullableEdge(trc, &atoms()[i], "atom");
-  }
-}
-
-void SharedScriptData::markForCrossZone(JSContext* cx) {
-  for (uint32_t i = 0; i < natoms(); ++i) {
-    cx->markAtom(atoms()[i]);
-  }
-}
-
 void RuntimeScriptData::traceChildren(JSTracer* trc) {
   MOZ_ASSERT(refCount() != 0);
 
-  ssd_->traceChildren(trc);
+  for (uint32_t i = 0; i < natoms(); ++i) {
+    TraceNullableEdge(trc, &atoms()[i], "atom");
+  }
 }
 
 void RuntimeScriptData::markForCrossZone(JSContext* cx) {
-  ssd_->markForCrossZone(cx);
+  for (uint32_t i = 0; i < natoms(); ++i) {
+    cx->markAtom(atoms()[i]);
+  }
 }
 
 void JSScript::traceChildren(JSTracer* trc) {
   // NOTE: this JSScript may be partially initialized at this point.  E.g. we
   // may have created it and partially initialized it with
   // JSScript::Create(), but not yet finished initializing it with
   // fullyInitFromEmitter() or fullyInitTrivial().
 
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -1527,18 +1527,16 @@ class alignas(uintptr_t) PrivateScriptDa
 // bounds. These arrays have varying requirements for alignment, performance,
 // and jit-friendliness which leads to the complex indexing system below.
 //
 // Note: The '----' separators are for readability only.
 //
 // ----
 //   <SharedScriptData itself>
 // ----
-//   (OPTIONAL) Array of GCPtrAtom constituting atoms()
-// ----
 //   (REQUIRED) Flags structure
 //  codeOffset:
 //   (REQUIRED) Array of jsbytecode constituting code()
 //   (REQUIRED) Array of jssrcnote constituting notes()
 // ----
 //   (OPTIONAL) Array of uint32_t optional-offsets
 //  optArrayOffset:
 // ----
@@ -1568,20 +1566,19 @@ class alignas(uintptr_t) PrivateScriptDa
 // optional-offsets table marks the *end* of array. The array starts where the
 // previous array ends and the first array begins at 'optArrayOffset'. The
 // optional-offset table is addressed at negative indices from
 // 'optArrayOffset'.
 //
 // In general, the length of each array is computed from subtracting the start
 // offset of the array from the start offset of the subsequent array. The
 // notable exception is that bytecode length is stored explicitly.
-class alignas(uintptr_t) SharedScriptData final {
-  uint32_t codeOffset_ = 0;  // Byte-offset from 'this'
+class alignas(uint32_t) SharedScriptData final {
   uint32_t codeLength_ = 0;
-  uint32_t optArrayOffset_ = 0;
+  uint32_t optArrayOffset_ = 0;  // Byte-offset from 'this'
 
   // Offset of main entry point from code, after predef'ing prologue.
   uint32_t mainOffset = 0;
 
   // Fixed frame slots.
   uint32_t nfixed = 0;
 
   // Slots plus maximum stack depth.
@@ -1612,20 +1609,19 @@ class alignas(uintptr_t) SharedScriptDat
                 "Structure packing is broken");
 
   friend class ::JSScript;
 
  private:
   // Offsets (in bytes) from 'this' to each component array. The delta between
   // each offset and the next offset is the size of each array and is defined
   // even if an array is empty.
-  size_t atomOffset() const { return offsetOfAtoms(); }
-  size_t flagOffset() const { return codeOffset_ - sizeof(Flags); }
-  size_t codeOffset() const { return codeOffset_; }
-  size_t noteOffset() const { return codeOffset_ + codeLength_; }
+  size_t flagOffset() const { return offsetOfCode() - sizeof(Flags); }
+  size_t codeOffset() const { return offsetOfCode(); }
+  size_t noteOffset() const { return offsetOfCode() + codeLength_; }
   size_t optionalOffsetsOffset() const {
     // Determine the location to beginning of optional-offsets array by looking
     // at index for try-notes.
     //
     //   optionalOffsetsOffset():
     //     (OPTIONAL) tryNotesEndOffset
     //     (OPTIONAL) scopeNotesEndOffset
     //     (OPTIONAL) resumeOffsetsEndOffset
@@ -1645,17 +1641,17 @@ class alignas(uintptr_t) SharedScriptDat
     return getOptionalOffset(flags().scopeNotesEndIndex);
   }
   size_t endOffset() const {
     return getOptionalOffset(flags().tryNotesEndIndex);
   }
 
   // Size to allocate
   static size_t AllocationSize(uint32_t codeLength, uint32_t noteLength,
-                               uint32_t natoms, uint32_t numResumeOffsets,
+                               uint32_t numResumeOffsets,
                                uint32_t numScopeNotes, uint32_t numTryNotes);
 
   // Translate an offset into a concrete pointer.
   template <typename T>
   T* offsetToPointer(size_t offset) {
     uintptr_t base = reinterpret_cast<uintptr_t>(this);
     return reinterpret_cast<T*>(base + offset);
   }
@@ -1663,17 +1659,17 @@ class alignas(uintptr_t) SharedScriptDat
   template <typename T>
   void initElements(size_t offset, size_t length);
 
   void initOptionalArrays(size_t* cursor, Flags* flags,
                           uint32_t numResumeOffsets, uint32_t numScopeNotes,
                           uint32_t numTryNotes);
 
   // Initialize to GC-safe state
-  SharedScriptData(uint32_t codeLength, uint32_t noteLength, uint32_t natoms,
+  SharedScriptData(uint32_t codeLength, uint32_t noteLength,
                    uint32_t numResumeOffsets, uint32_t numScopeNotes,
                    uint32_t numTryNotes);
 
   void setOptionalOffset(int index, uint32_t offset) {
     MOZ_ASSERT((index > 0) && (offset != optArrayOffset_),
                "Implicit offset should not be stored");
     offsetToPointer<uint32_t>(optArrayOffset_)[-index] = offset;
   }
@@ -1684,18 +1680,17 @@ class alignas(uintptr_t) SharedScriptDat
     }
 
     SharedScriptData* this_ = const_cast<SharedScriptData*>(this);
     return this_->offsetToPointer<uint32_t>(optArrayOffset_)[-index];
   }
 
  public:
   static SharedScriptData* new_(JSContext* cx, uint32_t codeLength,
-                                uint32_t noteLength, uint32_t natoms,
-                                uint32_t numResumeOffsets,
+                                uint32_t noteLength, uint32_t numResumeOffsets,
                                 uint32_t numScopeNotes, uint32_t numTryNotes);
 
   // The code() and note() arrays together maintain an target alignment by
   // padding the source notes with null. This allows arrays with stricter
   // alignment requirements to follow them.
   static constexpr size_t CodeNoteAlign = sizeof(uint32_t);
 
   // Compute number of null notes to pad out source notes with.
@@ -1711,21 +1706,16 @@ class alignas(uintptr_t) SharedScriptDat
   }
 
   // Span over all raw bytes in this struct and its trailing arrays.
   mozilla::Span<const uint8_t> immutableData() const {
     size_t allocSize = endOffset();
     return mozilla::MakeSpan(reinterpret_cast<const uint8_t*>(this), allocSize);
   }
 
-  uint32_t natoms() const {
-    return (flagOffset() - atomOffset()) / sizeof(GCPtrAtom);
-  }
-  GCPtrAtom* atoms() { return offsetToPointer<GCPtrAtom>(atomOffset()); }
-
   Flags& flagsRef() { return *offsetToPointer<Flags>(flagOffset()); }
   const Flags& flags() const {
     return const_cast<SharedScriptData*>(this)->flagsRef();
   }
 
   uint32_t codeLength() const { return codeLength_; }
   jsbytecode* code() { return offsetToPointer<jsbytecode>(codeOffset()); }
 
@@ -1740,97 +1730,111 @@ class alignas(uintptr_t) SharedScriptDat
     return mozilla::MakeSpan(offsetToPointer<ScopeNote>(scopeNotesOffset()),
                              offsetToPointer<ScopeNote>(tryNotesOffset()));
   }
   mozilla::Span<JSTryNote> tryNotes() {
     return mozilla::MakeSpan(offsetToPointer<JSTryNote>(tryNotesOffset()),
                              offsetToPointer<JSTryNote>(endOffset()));
   }
 
-  static constexpr size_t offsetOfCodeOffset() {
-    return offsetof(SharedScriptData, codeOffset_);
+  static constexpr size_t offsetOfCode() {
+    return sizeof(SharedScriptData) + sizeof(Flags);
   }
   static constexpr size_t offsetOfResumeOffsetsOffset() {
     // Resume-offsets are the first optional array if they exist. Locate the
     // array with the 'optArrayOffset_' field.
     return offsetof(SharedScriptData, optArrayOffset_);
   }
   static constexpr size_t offsetOfNfixed() {
     return offsetof(SharedScriptData, nfixed);
   }
   static constexpr size_t offsetOfNslots() {
     return offsetof(SharedScriptData, nslots);
   }
   static constexpr size_t offsetOfFunLength() {
     return offsetof(SharedScriptData, funLength);
   }
-  static constexpr size_t offsetOfAtoms() { return sizeof(SharedScriptData); }
-
-  void traceChildren(JSTracer* trc);
 
   template <XDRMode mode>
   static MOZ_MUST_USE XDRResult XDR(js::XDRState<mode>* xdr,
                                     js::HandleScript script);
 
   static bool InitFromEmitter(JSContext* cx, js::HandleScript script,
                               js::frontend::BytecodeEmitter* bce,
                               uint32_t nslots);
 
-  // Mark this SharedScriptData for use in a new zone
-  void markForCrossZone(JSContext* cx);
-
   // SharedScriptData has trailing data so isn't copyable or movable.
   SharedScriptData(const SharedScriptData&) = delete;
   SharedScriptData& operator=(const SharedScriptData&) = delete;
 };
 
 struct SharedScriptDataHasher;
 
 // Script data that is shareable across a JSRuntime.
 class RuntimeScriptData final {
   // This class is reference counted as follows: each pointer from a JSScript
   // counts as one reference plus there may be one reference from the shared
   // script data table.
   mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent,
                   mozilla::recordreplay::Behavior::DontPreserve>
       refCount_ = {};
 
+  uint32_t natoms_ = 0;
+
   js::UniquePtr<SharedScriptData> ssd_ = nullptr;
 
   // NOTE: The raw bytes of this structure are used for hashing so use explicit
   // padding values as needed for predicatable results across compilers.
 
   friend class ::JSScript;
   friend class js::SharedScriptData;
   friend struct js::SharedScriptDataHasher;
 
  private:
+  // Layout of trailing arrays.
+  size_t atomOffset() const { return offsetOfAtoms(); }
+
   // Size to allocate.
-  static size_t AllocationSize();
+  static size_t AllocationSize(uint32_t natoms);
 
   template <typename T>
   void initElements(size_t offset, size_t length);
 
   // Initialize to GC-safe state.
-  RuntimeScriptData();
+  explicit RuntimeScriptData(uint32_t natoms);
 
  public:
-  static RuntimeScriptData* new_(JSContext* cx);
+  static RuntimeScriptData* new_(JSContext* cx, uint32_t natoms);
 
   uint32_t refCount() const { return refCount_; }
   void AddRef() { refCount_++; }
   void Release() {
     MOZ_ASSERT(refCount_ != 0);
     uint32_t remain = --refCount_;
     if (remain == 0) {
       ssd_ = nullptr;
       js_free(this);
     }
   }
 
+  uint32_t natoms() const { return natoms_; }
+  GCPtrAtom* atoms() {
+    uintptr_t base = reinterpret_cast<uintptr_t>(this);
+    return reinterpret_cast<GCPtrAtom*>(base + atomOffset());
+  }
+
+  mozilla::Span<const GCPtrAtom> atomsSpan() const {
+    uintptr_t base = reinterpret_cast<uintptr_t>(this);
+    const GCPtrAtom* p =
+        reinterpret_cast<const GCPtrAtom*>(base + atomOffset());
+    return mozilla::MakeSpan(p, natoms_);
+  }
+
+  static constexpr size_t offsetOfAtoms() { return sizeof(RuntimeScriptData); }
+
   static constexpr size_t offsetOfSSD() {
     return offsetof(RuntimeScriptData, ssd_);
   }
 
   void traceChildren(JSTracer* trc);
 
   // Mark this RuntimeScriptData for use in a new zone.
   void markForCrossZone(JSContext* cx);
@@ -1846,21 +1850,26 @@ class RuntimeScriptData final {
 
 // Two SharedScriptData instances may be de-duplicated if they have the same
 // data in their immutableData() span. This Hasher enables that comparison.
 struct SharedScriptDataHasher {
   using Lookup = RefPtr<RuntimeScriptData>;
 
   static HashNumber hash(const Lookup& l) {
     mozilla::Span<const uint8_t> immutableData = l->ssd_->immutableData();
-    return mozilla::HashBytes(immutableData.data(), immutableData.size());
+
+    HashNumber h =
+        mozilla::HashBytes(immutableData.data(), immutableData.size());
+    return mozilla::AddToHash(
+        h, mozilla::HashBytes(l->atoms(), l->natoms() * sizeof(GCPtrAtom)));
   }
 
   static bool match(RuntimeScriptData* entry, const Lookup& lookup) {
-    return entry->ssd_->immutableData() == lookup->ssd_->immutableData();
+    return (entry->atomsSpan() == lookup->atomsSpan()) &&
+           (entry->ssd_->immutableData() == lookup->ssd_->immutableData());
   }
 };
 
 class AutoLockScriptData;
 
 using ScriptDataTable =
     HashSet<RuntimeScriptData*, SharedScriptDataHasher, SystemAllocPolicy>;
 
@@ -2952,21 +2961,21 @@ class JSScript : public js::gc::TenuredC
   }
   jssrcnote* notes() const {
     MOZ_ASSERT(scriptData_);
     return sharedScriptData()->notes();
   }
 
   size_t natoms() const {
     MOZ_ASSERT(scriptData_);
-    return sharedScriptData()->natoms();
+    return scriptData_->natoms();
   }
   js::GCPtrAtom* atoms() const {
     MOZ_ASSERT(scriptData_);
-    return sharedScriptData()->atoms();
+    return scriptData_->atoms();
   }
 
   js::GCPtrAtom& getAtom(size_t index) const {
     MOZ_ASSERT(index < natoms());
     return atoms()[index];
   }
 
   js::GCPtrAtom& getAtom(jsbytecode* pc) const {