Bug 1606113 - Trace/finalize script side-tables from BaseScript. r=jandem
authorTed Campbell <tcampbell@mozilla.com>
Mon, 30 Dec 2019 14:19:12 +0000
changeset 508490 710e512a73524c36120734a60c721578bf415f49
parent 508489 33ae0330cbe4d25f47ba07badbe87f939b1d0b1a
child 508491 bc6505aeeb65679dbfa2b40bf434b52132f88e63
push id104019
push usertcampbell@mozilla.com
push dateMon, 30 Dec 2019 14:20:00 +0000
treeherderautoland@710e512a7352 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1606113
milestone73.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 1606113 - Trace/finalize script side-tables from BaseScript. r=jandem A number of side-tables exist that are keyed by JSScript pointers. This patch moves the tracing and finalization of these from JSScript to BaseScript to support combining LazyScript/JSScript GC arena. This patch eliminates the LazyScript and JSScript finalize method specializations. A BaseScript::hasBytecode() method is added to determine if a BaseScript was a JSScript that was successfully compiled with bytecode. Only these scripts may be used as keys in side-tables so we check this in finalize methods. A down-cast to JSScript is used once again to avoid rewriting all the accessor methods yet. Depends on D58303 Differential Revision: https://phabricator.services.mozilla.com/D58304
js/src/debugger/DebugScript.cpp
js/src/gc/Marking.cpp
js/src/vm/CodeCoverage.cpp
js/src/vm/GeckoProfiler.cpp
js/src/vm/JSScript.cpp
js/src/vm/JSScript.h
--- a/js/src/debugger/DebugScript.cpp
+++ b/js/src/debugger/DebugScript.cpp
@@ -67,16 +67,18 @@ DebugScript* DebugScript::getOrCreate(JS
     auto map = cx->make_unique<DebugScriptMap>();
     if (!map) {
       return nullptr;
     }
 
     script->zone()->debugScriptMap = std::move(map);
   }
 
+  MOZ_ASSERT(script->hasBytecode());
+
   DebugScript* borrowed = debug.get();
   if (!script->zone()->debugScriptMap->putNew(script, std::move(debug))) {
     ReportOutOfMemory(cx);
     return nullptr;
   }
 
   // It is safe to set this: we can't fail after this point.
   script->setHasDebugScript(true);
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1093,47 +1093,54 @@ bool js::GCMarker::mark(T* thing) {
 /*** Inline, Eager GC Marking ***********************************************/
 
 // Each of the eager, inline marking paths is directly preceeded by the
 // out-of-line, generic tracing code for comparison. Both paths must end up
 // traversing equivalent subgraphs.
 
 void BaseScript::traceChildren(JSTracer* trc) {
   TraceEdge(trc, &functionOrGlobal_, "function");
-  TraceNullableEdge(trc, &sourceObject_, "sourceObject");
+  TraceEdge(trc, &sourceObject_, "sourceObject");
 
   warmUpData_.trace(trc);
 
   if (data_) {
     data_->trace(trc);
   }
 
   if (sharedData_) {
     sharedData_->traceChildren(trc);
   }
+
+  // Scripts with bytecode may have optional data stored in per-runtime or
+  // per-zone maps. Note that a failed compilation must not have entries since
+  // the script itself will not be marked as having bytecode.
+  if (hasBytecode()) {
+    JSScript* script = static_cast<JSScript*>(this);
+
+    if (hasDebugScript()) {
+      DebugAPI::traceDebugScript(trc, script);
+    }
+  }
 }
 
 void LazyScript::traceChildren(JSTracer* trc) {
-  // Trace base class fields.
   BaseScript::traceChildren(trc);
 
   if (trc->traceWeakEdges()) {
     TraceNullableEdge(trc, &script_, "script");
   }
 
   if (trc->isMarkingTracer()) {
     GCMarker::fromTracer(trc)->markImplicitEdges(this);
   }
 }
 inline void js::GCMarker::eagerlyMarkChildren(LazyScript* thing) {
   traverseEdge(thing, static_cast<JSObject*>(thing->functionOrGlobal_));
-
-  if (thing->sourceObject_) {
-    traverseEdge(thing, static_cast<JSObject*>(thing->sourceObject_));
-  }
+  traverseEdge(thing, static_cast<JSObject*>(thing->sourceObject_));
 
   thing->warmUpData_.trace(this);
 
   if (thing->data_) {
     // Traverse the PrivateScriptData::gcthings() array.
     for (JS::GCCellPtr& elem : thing->data_->gcthings()) {
       if (elem.is<JSObject>()) {
         traverseEdge(thing, &elem.as<JSObject>());
--- a/js/src/vm/CodeCoverage.cpp
+++ b/js/src/vm/CodeCoverage.cpp
@@ -666,16 +666,18 @@ bool InitScriptCoverage(JSContext* cx, J
   JS::Zone* zone = script->zone();
   if (!zone->scriptLCovMap) {
     zone->scriptLCovMap = cx->make_unique<ScriptLCovMap>();
   }
   if (!zone->scriptLCovMap) {
     return false;
   }
 
+  MOZ_ASSERT(script->hasBytecode());
+
   // Save source in map for when we collect coverage.
   if (!zone->scriptLCovMap->putNew(script,
                                    mozilla::MakeTuple(source, scriptName))) {
     ReportOutOfMemory(cx);
     return false;
   }
 
   return true;
--- a/js/src/vm/GeckoProfiler.cpp
+++ b/js/src/vm/GeckoProfiler.cpp
@@ -168,16 +168,17 @@ const char* GeckoProfilerRuntime::profil
                                                 JSScript* script) {
   ProfileStringMap::AddPtr s = strings().lookupForAdd(script);
 
   if (!s) {
     UniqueChars str = allocProfileString(cx, script);
     if (!str) {
       return nullptr;
     }
+    MOZ_ASSERT(script->hasBytecode());
     if (!strings().add(s, script, std::move(str))) {
       ReportOutOfMemory(cx);
       return nullptr;
     }
   }
 
   return s->value().get();
 }
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -676,16 +676,40 @@ void js::BaseScript::setEnclosingScope(S
     warmUpData_.clearEnclosingScript();
   }
 
   MOZ_ASSERT(enclosingScope);
   warmUpData_.initEnclosingScope(enclosingScope);
 }
 
 void js::BaseScript::finalize(JSFreeOp* fop) {
+  // Scripts with bytecode may have optional data stored in per-runtime or
+  // per-zone maps. Note that a failed compilation must not have entries since
+  // the script itself will not be marked as having bytecode.
+  if (hasBytecode()) {
+    JSScript* script = static_cast<JSScript*>(this);
+
+    if (coverage::IsLCovEnabled()) {
+      coverage::CollectScriptCoverage(script);
+    }
+
+    fop->runtime()->geckoProfiler().onScriptFinalized(script);
+
+    script->destroyScriptCounts();
+
+    DebugAPI::destroyDebugScript(fop, script);
+
+#ifdef MOZ_VTUNE
+    if (zone()->scriptVTuneIdMap) {
+      // Note: we should only get here if the VTune JIT profiler is running.
+      zone()->scriptVTuneIdMap->remove(script);
+    }
+#endif
+  }
+
   if (warmUpData_.isJitScript()) {
     JSScript* script = static_cast<JSScript*>(this);
     script->releaseJitScriptOnFinalize(fop);
   }
 
   if (data_) {
     size_t size = data_->allocationSize();
     AlwaysPoison(data_, JS_POISONED_JSSCRIPT_DATA_PATTERN, size,
@@ -1426,16 +1450,18 @@ bool JSScript::initScriptCounts(JSContex
 
   // Allocate the ScriptCounts.
   UniqueScriptCounts sc = cx->make_unique<ScriptCounts>(std::move(base));
   if (!sc) {
     ReportOutOfMemory(cx);
     return false;
   }
 
+  MOZ_ASSERT(this->hasBytecode());
+
   // Register the current ScriptCounts in the zone's map.
   if (!zone()->scriptCountsMap->putNew(this, std::move(sc))) {
     ReportOutOfMemory(cx);
     return false;
   }
 
   // safe to set this;  we can't fail after this point.
   setFlag(MutableFlags::HasScriptCounts);
@@ -4362,16 +4388,18 @@ uint32_t JSScript::vtuneMethodID() {
     zone()->scriptVTuneIdMap = std::move(map);
   }
 
   ScriptVTuneIdMap::AddPtr p = zone()->scriptVTuneIdMap->lookupForAdd(this);
   if (p) {
     return p->value();
   }
 
+  MOZ_ASSERT(this->hasBytecode());
+
   uint32_t id = vtune::GenerateUniqueMethodID();
   if (!zone()->scriptVTuneIdMap->add(p, this, id)) {
     MOZ_CRASH("Failed to add vtune method id");
   }
 
   return id;
 }
 #endif
@@ -4624,50 +4652,16 @@ void JSScript::addSizeOfJitScript(mozill
   }
 
   jitScript()->addSizeOfIncludingThis(mallocSizeOf, sizeOfJitScript,
                                       sizeOfBaselineFallbackStubs);
 }
 
 js::GlobalObject& JSScript::uninlinedGlobal() const { return global(); }
 
-void JSScript::finalize(JSFreeOp* fop) {
-  // 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().
-
-  if (coverage::IsLCovEnabled()) {
-    coverage::CollectScriptCoverage(this);
-  }
-
-  fop->runtime()->geckoProfiler().onScriptFinalized(this);
-
-  destroyScriptCounts();
-  DebugAPI::destroyDebugScript(fop, this);
-
-#ifdef MOZ_VTUNE
-  if (zone()->scriptVTuneIdMap) {
-    // Note: we should only get here if the VTune JIT profiler is running.
-    zone()->scriptVTuneIdMap->remove(this);
-  }
-#endif
-
-  // Finalize the base-script fields.
-  BaseScript::finalize(fop);
-
-  // In most cases, our LazyScript's script pointer will reference this
-  // script, and thus be nulled out by normal weakref processing. However, if
-  // we unlazified the LazyScript during incremental sweeping, it will have a
-  // completely different JSScript.
-  MOZ_ASSERT_IF(
-      lazyScript && !IsAboutToBeFinalizedUnbarriered(&lazyScript),
-      !lazyScript->hasScript() || lazyScript->maybeScriptUnbarriered() != this);
-}
-
 static const uint32_t GSN_CACHE_THRESHOLD = 100;
 
 void GSNCache::purge() {
   code = nullptr;
   map.clearAndCompact();
 }
 
 jssrcnote* js::GetSrcNote(GSNCache& cache, JSScript* script, jsbytecode* pc) {
@@ -5311,32 +5305,22 @@ void ScriptWarmUpData::trace(JSTracer* t
     default: {
       MOZ_ASSERT(isWarmUpCount());
       break;
     }
   }
 }
 
 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().
-
-  // Trace base class fields.
   BaseScript::traceChildren(trc);
 
-  if (maybeLazyScript()) {
+  if (lazyScript) {
     TraceManuallyBarrieredEdge(trc, &lazyScript, "lazyScript");
   }
 
-  if (hasDebugScript()) {
-    DebugAPI::traceDebugScript(trc, this);
-  }
-
   if (trc->isMarkingTracer()) {
     GCMarker::fromTracer(trc)->markImplicitEdges(this);
   }
 }
 
 size_t JSScript::calculateLiveFixed(jsbytecode* pc) {
   size_t nlivefixed = numAlwaysLiveFixedSlots();
 
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -2509,24 +2509,34 @@ setterLevel:                            
     MOZ_ASSERT(data_);
     data_->setFieldInitializers(fieldInitializers);
   }
   const FieldInitializers& getFieldInitializers() const {
     MOZ_ASSERT(data_);
     return data_->getFieldInitializers();
   }
 
-  RuntimeScriptData* sharedData() { return sharedData_; }
+  RuntimeScriptData* sharedData() const { return sharedData_; }
   void freeSharedData() { sharedData_ = nullptr; }
 
+  bool hasBytecode() const {
+    if (sharedData_) {
+      MOZ_ASSERT(data_);
+      MOZ_ASSERT(warmUpData_.isWarmUpCount() || warmUpData_.isJitScript());
+      return true;
+    }
+    return false;
+  }
+
  protected:
   void traceChildren(JSTracer* trc);
-  void finalize(JSFreeOp* fop);
 
  public:
+  void finalize(JSFreeOp* fop);
+
   size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
     return mallocSizeOf(data_);
   }
 
   // JIT accessors
   static constexpr size_t offsetOfJitCodeRaw() {
     return offsetof(BaseScript, jitCodeRaw_);
   }
@@ -3203,18 +3213,16 @@ class JSScript : public js::BaseScript {
 
   bool formalIsAliased(unsigned argSlot);
   bool formalLivesInArgumentsObject(unsigned argSlot);
 
   // See comment above 'debugMode' in Realm.h for explanation of
   // invariants of debuggee compartments, scripts, and frames.
   inline bool isDebuggee() const;
 
-  void finalize(JSFreeOp* fop);
-
   static const JS::TraceKind TraceKind = JS::TraceKind::Script;
 
   void traceChildren(JSTracer* trc);
 
   // A helper class to prevent relazification of the given function's script
   // while it's holding on to it.  This class automatically roots the script.
   class AutoDelazify;
   friend class AutoDelazify;
@@ -3408,17 +3416,16 @@ class LazyScript : public BaseScript {
   // The enclosing JSScript can be GCed later if the enclosing scope is not
   // FunctionScope or ModuleScope.
   bool enclosingScriptHasEverBeenCompiled() const {
     return warmUpData_.isEnclosingScope();
   }
 
   friend class GCMarker;
   void traceChildren(JSTracer* trc);
-  void finalize(JSFreeOp* fop) { BaseScript::finalize(fop); }
 
   static const JS::TraceKind TraceKind = JS::TraceKind::LazyScript;
 };
 
 /* If this fails, add/remove padding within LazyScript. */
 static_assert(sizeof(LazyScript) % js::gc::CellAlignBytes == 0,
               "Size of LazyScript must be an integral multiple of "
               "js::gc::CellAlignBytes");