Bug 971673: Replace ScriptSettingsStack's array with a C++-stack-allocated linked list. r=bholley
authorJim Blandy <jimb@mozilla.com>
Thu, 12 Jun 2014 23:24:13 -0700
changeset 209422 6dc124fd973a01e0fc43de4736a0f815904ce158
parent 209421 161cad49ab7710afc96a3f98d8eada353d81f186
child 209423 297840857bb72ae5760ea1e8ff0e1acb1ecd4c2e
push id3857
push userraliiev@mozilla.com
push dateTue, 02 Sep 2014 16:39:23 +0000
treeherdermozilla-beta@5638b907b505 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs971673
milestone33.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 971673: Replace ScriptSettingsStack's array with a C++-stack-allocated linked list. r=bholley
dom/base/ScriptSettings.cpp
dom/base/ScriptSettings.h
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -15,112 +15,126 @@
 #include "nsIScriptContext.h"
 #include "nsContentUtils.h"
 #include "nsTArray.h"
 #include "nsJSUtils.h"
 
 namespace mozilla {
 namespace dom {
 
-class ScriptSettingsStack;
-static mozilla::ThreadLocal<ScriptSettingsStack*> sScriptSettingsTLS;
-
-ScriptSettingsStackEntry ScriptSettingsStackEntry::NoJSAPISingleton;
+static mozilla::ThreadLocal<ScriptSettingsStackEntry*> sScriptSettingsTLS;
 
 class ScriptSettingsStack {
 public:
-  static ScriptSettingsStack& Ref() {
-    return *sScriptSettingsTLS.get();
-  }
-  ScriptSettingsStack() {};
-
-  void Push(ScriptSettingsStackEntry* aSettings) {
-    // The bottom-most entry must always be a candidate entry point.
-    MOZ_ASSERT_IF(mStack.Length() == 0 || mStack.LastElement()->NoJSAPI(),
-                  aSettings->mIsCandidateEntryPoint);
-    mStack.AppendElement(aSettings);
+  static ScriptSettingsStackEntry* Top() {
+    return sScriptSettingsTLS.get();
   }
 
-  void PushNoJSAPI() {
-    mStack.AppendElement(&ScriptSettingsStackEntry::NoJSAPISingleton);
-  }
+  static void Push(ScriptSettingsStackEntry *aEntry) {
+    MOZ_ASSERT(!aEntry->mOlder);
+    // Whenever JSAPI use is disabled, the next stack entry pushed must
+    // always be a candidate entry point.
+    MOZ_ASSERT_IF(!Top() || Top()->NoJSAPI(), aEntry->mIsCandidateEntryPoint);
 
-  void Pop() {
-    MOZ_ASSERT(mStack.Length() > 0);
-    mStack.RemoveElementAt(mStack.Length() - 1);
+    aEntry->mOlder = Top();
+    sScriptSettingsTLS.set(aEntry);
   }
 
-  ScriptSettingsStackEntry* Incumbent() {
-    if (!mStack.Length()) {
-      return nullptr;
-    }
-    return mStack.LastElement();
+  static void Pop(ScriptSettingsStackEntry *aEntry) {
+    MOZ_ASSERT(aEntry == Top());
+    sScriptSettingsTLS.set(aEntry->mOlder);
   }
 
-  nsIGlobalObject* IncumbentGlobal() {
-    ScriptSettingsStackEntry *entry = Incumbent();
+  static nsIGlobalObject* IncumbentGlobal() {
+    ScriptSettingsStackEntry *entry = Top();
     return entry ? entry->mGlobalObject : nullptr;
   }
 
-  ScriptSettingsStackEntry* EntryPoint() {
-    if (!mStack.Length())
+  static ScriptSettingsStackEntry* EntryPoint() {
+    ScriptSettingsStackEntry *entry = Top();
+    if (!entry) {
       return nullptr;
-    for (int i = mStack.Length() - 1; i >= 0; --i) {
-      if (mStack[i]->mIsCandidateEntryPoint) {
-        return mStack[i];
-      }
+    }
+    while (entry) {
+      if (entry->mIsCandidateEntryPoint)
+        return entry;
+      entry = entry->mOlder;
     }
     MOZ_CRASH("Non-empty stack should always have an entry point");
   }
 
-  nsIGlobalObject* EntryGlobal() {
+  static nsIGlobalObject* EntryGlobal() {
     ScriptSettingsStackEntry *entry = EntryPoint();
     return entry ? entry->mGlobalObject : nullptr;
   }
-
-private:
-  // These pointers are caller-owned.
-  nsTArray<ScriptSettingsStackEntry*> mStack;
 };
 
 void
 InitScriptSettings()
 {
   if (!sScriptSettingsTLS.initialized()) {
     bool success = sScriptSettingsTLS.init();
     if (!success) {
       MOZ_CRASH();
     }
   }
 
-  ScriptSettingsStack* ptr = new ScriptSettingsStack();
-  sScriptSettingsTLS.set(ptr);
+  sScriptSettingsTLS.set(nullptr);
 }
 
 void DestroyScriptSettings()
 {
-  ScriptSettingsStack* ptr = sScriptSettingsTLS.get();
-  MOZ_ASSERT(ptr);
-  sScriptSettingsTLS.set(nullptr);
-  delete ptr;
+  MOZ_ASSERT(sScriptSettingsTLS.get() == nullptr);
+}
+
+ScriptSettingsStackEntry::ScriptSettingsStackEntry(nsIGlobalObject *aGlobal,
+                                                   bool aCandidate)
+  : mGlobalObject(aGlobal)
+  , mIsCandidateEntryPoint(aCandidate)
+  , mOlder(nullptr)
+{
+  MOZ_ASSERT(mGlobalObject);
+  MOZ_ASSERT(mGlobalObject->GetGlobalJSObject(),
+             "Must have an actual JS global for the duration on the stack");
+  MOZ_ASSERT(JS_IsGlobalObject(mGlobalObject->GetGlobalJSObject()),
+             "No outer windows allowed");
+
+  ScriptSettingsStack::Push(this);
+}
+
+// This constructor is only for use by AutoNoJSAPI.
+ScriptSettingsStackEntry::ScriptSettingsStackEntry()
+   : mGlobalObject(nullptr)
+   , mIsCandidateEntryPoint(true)
+   , mOlder(nullptr)
+{
+  ScriptSettingsStack::Push(this);
+}
+
+ScriptSettingsStackEntry::~ScriptSettingsStackEntry()
+{
+  // We must have an actual JS global for the entire time this is on the stack.
+  MOZ_ASSERT_IF(mGlobalObject, mGlobalObject->GetGlobalJSObject());
+
+  ScriptSettingsStack::Pop(this);
 }
 
 // This mostly gets the entry global, but doesn't entirely match the spec in
 // certain edge cases. It's good enough for some purposes, but not others. If
 // you want to call this function, ping bholley and describe your use-case.
 nsIGlobalObject*
 BrokenGetEntryGlobal()
 {
   // We need the current JSContext in order to check the JS for
   // scripted frames that may have appeared since anyone last
   // manipulated the stack. If it's null, that means that there
   // must be no entry global on the stack.
   JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
   if (!cx) {
-    MOZ_ASSERT(ScriptSettingsStack::Ref().EntryGlobal() == nullptr);
+    MOZ_ASSERT(ScriptSettingsStack::EntryGlobal() == nullptr);
     return nullptr;
   }
 
   return nsJSUtils::GetDynamicScriptGlobal(cx);
 }
 
 // Note: When we're ready to expose it, GetEntryGlobal will look similar to
 // GetIncumbentGlobal below.
@@ -130,41 +144,41 @@ GetIncumbentGlobal()
 {
   // We need the current JSContext in order to check the JS for
   // scripted frames that may have appeared since anyone last
   // manipulated the stack. If it's null, that means that there
   // must be no entry global on the stack, and therefore no incumbent
   // global either.
   JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
   if (!cx) {
-    MOZ_ASSERT(ScriptSettingsStack::Ref().EntryGlobal() == nullptr);
+    MOZ_ASSERT(ScriptSettingsStack::EntryGlobal() == nullptr);
     return nullptr;
   }
 
   // See what the JS engine has to say. If we've got a scripted caller
   // override in place, the JS engine will lie to us and pretend that
   // there's nothing on the JS stack, which will cause us to check the
   // incumbent script stack below.
   if (JSObject *global = JS::GetScriptedCallerGlobal(cx)) {
     return xpc::GetNativeForGlobal(global);
   }
 
   // Ok, nothing from the JS engine. Let's use whatever's on the
   // explicit stack.
-  return ScriptSettingsStack::Ref().IncumbentGlobal();
+  return ScriptSettingsStack::IncumbentGlobal();
 }
 
 nsIPrincipal*
 GetWebIDLCallerPrincipal()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  ScriptSettingsStackEntry *entry = ScriptSettingsStack::Ref().EntryPoint();
+  ScriptSettingsStackEntry *entry = ScriptSettingsStack::EntryPoint();
 
-  // If we have an entry point that is not the NoJSAPI singleton, we know it
-  // must be an AutoEntryScript.
+  // If we have an entry point that is not NoJSAPI, we know it must be an
+  // AutoEntryScript.
   if (!entry || entry->NoJSAPI()) {
     return nullptr;
   }
   AutoEntryScript* aes = static_cast<AutoEntryScript*>(entry);
 
   // We can't yet rely on the Script Settings Stack to properly determine the
   // entry script, because there are still lots of places in the tree where we
   // don't yet use an AutoEntryScript (bug 951991 tracks this work). In the
@@ -243,56 +257,34 @@ AutoJSAPIWithErrorsReportedToWindow::Aut
 }
 
 AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
                                  bool aIsMainThread,
                                  JSContext* aCx)
   : AutoJSAPI(aCx ? aCx : FindJSContext(aGlobalObject), aIsMainThread, /* aSkipNullAc = */ true)
   , ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ true)
   , mAc(cx(), aGlobalObject->GetGlobalJSObject())
-  , mStack(ScriptSettingsStack::Ref())
   , mWebIDLCallerPrincipal(nullptr)
 {
   MOZ_ASSERT(aGlobalObject);
   MOZ_ASSERT_IF(!aCx, aIsMainThread); // cx is mandatory off-main-thread.
   MOZ_ASSERT_IF(aCx && aIsMainThread, aCx == FindJSContext(aGlobalObject));
-  mStack.Push(this);
-}
-
-AutoEntryScript::~AutoEntryScript()
-{
-  MOZ_ASSERT(mStack.Incumbent() == this);
-  mStack.Pop();
 }
 
 AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject)
   : ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ false)
-  , mStack(ScriptSettingsStack::Ref())
   , mCallerOverride(nsContentUtils::GetCurrentJSContextForThread())
 {
-  mStack.Push(this);
-}
-
-AutoIncumbentScript::~AutoIncumbentScript()
-{
-  MOZ_ASSERT(mStack.Incumbent() == this);
-  mStack.Pop();
 }
 
 AutoNoJSAPI::AutoNoJSAPI(bool aIsMainThread)
-  : mStack(ScriptSettingsStack::Ref())
+  : ScriptSettingsStackEntry()
 {
   MOZ_ASSERT_IF(nsContentUtils::GetCurrentJSContextForThread(),
                 !JS_IsExceptionPending(nsContentUtils::GetCurrentJSContextForThread()));
   if (aIsMainThread) {
     mCxPusher.construct(static_cast<JSContext*>(nullptr),
                         /* aAllowNull = */ true);
   }
-  mStack.PushNoJSAPI();
-}
-
-AutoNoJSAPI::~AutoNoJSAPI()
-{
-  mStack.Pop();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -61,43 +61,35 @@ nsIPrincipal* GetWebIDLCallerPrincipal()
 // null (i.e. they know there have been no System Caller pushes since the
 // inner-most script execution).
 inline JSObject& IncumbentJSGlobal()
 {
   return *GetIncumbentGlobal()->GetGlobalJSObject();
 }
 
 class ScriptSettingsStack;
-struct ScriptSettingsStackEntry {
+class ScriptSettingsStackEntry {
+  friend class ScriptSettingsStack;
+
+public:
+  ScriptSettingsStackEntry(nsIGlobalObject *aGlobal, bool aCandidate);
+  ~ScriptSettingsStackEntry();
+
+  bool NoJSAPI() { return !mGlobalObject; }
+
+protected:
   nsCOMPtr<nsIGlobalObject> mGlobalObject;
   bool mIsCandidateEntryPoint;
 
-  ScriptSettingsStackEntry(nsIGlobalObject *aGlobal, bool aCandidate)
-    : mGlobalObject(aGlobal)
-    , mIsCandidateEntryPoint(aCandidate)
-  {
-    MOZ_ASSERT(mGlobalObject);
-    MOZ_ASSERT(mGlobalObject->GetGlobalJSObject(),
-               "Must have an actual JS global for the duration on the stack");
-    MOZ_ASSERT(JS_IsGlobalObject(mGlobalObject->GetGlobalJSObject()),
-               "No outer windows allowed");
-  }
+private:
+  // This constructor is only for use by AutoNoJSAPI.
+  friend class AutoNoJSAPI;
+  ScriptSettingsStackEntry();
 
-  ~ScriptSettingsStackEntry() {
-    // We must have an actual JS global for the entire time this is on the stack.
-    MOZ_ASSERT_IF(mGlobalObject, mGlobalObject->GetGlobalJSObject());
-  }
-
-  bool NoJSAPI() { return this == &NoJSAPISingleton; }
-  static ScriptSettingsStackEntry NoJSAPISingleton;
-
-private:
-  ScriptSettingsStackEntry() : mGlobalObject(nullptr)
-                             , mIsCandidateEntryPoint(true)
-  {}
+  ScriptSettingsStackEntry *mOlder;
 };
 
 /*
  * For any interaction with JSAPI, an AutoJSAPI (or one of its subclasses)
  * must be on the stack.
  *
  * This base class should be instantiated as-is when the caller wants to use
  * JSAPI but doesn't expect to run script. Its current duties are as-follows:
@@ -167,25 +159,23 @@ class AutoJSAPIWithErrorsReportedToWindo
  */
 class AutoEntryScript : public AutoJSAPI,
                         protected ScriptSettingsStackEntry {
 public:
   AutoEntryScript(nsIGlobalObject* aGlobalObject,
                   bool aIsMainThread = NS_IsMainThread(),
                   // Note: aCx is mandatory off-main-thread.
                   JSContext* aCx = nullptr);
-  ~AutoEntryScript();
 
   void SetWebIDLCallerPrincipal(nsIPrincipal *aPrincipal) {
     mWebIDLCallerPrincipal = aPrincipal;
   }
 
 private:
   JSAutoCompartment mAc;
-  dom::ScriptSettingsStack& mStack;
   // It's safe to make this a weak pointer, since it's the subject principal
   // when we go on the stack, so can't go away until after we're gone.  In
   // particular, this is only used from the CallSetup constructor, and only in
   // the aIsJSImplementedWebIDL case.  And in that case, the subject principal
   // is the principal of the callee function that is part of the CallArgs just a
   // bit up the stack, and which will outlive us.  So we know the principal
   // can't go away until then either.
   nsIPrincipal* mWebIDLCallerPrincipal;
@@ -193,35 +183,31 @@ private:
 };
 
 /*
  * A class that can be used to force a particular incumbent script on the stack.
  */
 class AutoIncumbentScript : protected ScriptSettingsStackEntry {
 public:
   AutoIncumbentScript(nsIGlobalObject* aGlobalObject);
-  ~AutoIncumbentScript();
 private:
-  dom::ScriptSettingsStack& mStack;
   JS::AutoHideScriptedCaller mCallerOverride;
 };
 
 /*
  * A class to put the JS engine in an unusable state. The subject principal
  * will become System, the information on the script settings stack is
  * rendered inaccessible, and JSAPI may not be manipulated until the class is
  * either popped or an AutoJSAPI instance is subsequently pushed.
  *
  * This class may not be instantiated if an exception is pending.
  */
-class AutoNoJSAPI {
+class AutoNoJSAPI : protected ScriptSettingsStackEntry {
 public:
   AutoNoJSAPI(bool aIsMainThread = NS_IsMainThread());
-  ~AutoNoJSAPI();
 private:
-  dom::ScriptSettingsStack& mStack;
   mozilla::Maybe<AutoCxPusher> mCxPusher;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ScriptSettings_h