Bug 1177488 - use |const char*| for representing async call reasons; r=bz,fitzgen
☠☠ backed out by 04ea7a18573d ☠ ☠
authorNathan Froyd <froydnj@mozilla.com>
Tue, 08 Mar 2016 16:29:25 -0500
changeset 288588 ee3c99e6f6768fd804b58544efd97262cfea4779
parent 288587 8e89d1b9a72228a7f0e76532edec3171da96f674
child 288589 a1a8876174c3258948d979707b2df1b913956902
push id73472
push usernfroyd@mozilla.com
push dateMon, 14 Mar 2016 19:03:29 +0000
treeherdermozilla-inbound@ee3c99e6f676 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, fitzgen
bugs1177488
milestone48.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 1177488 - use |const char*| for representing async call reasons; r=bz,fitzgen Using a simple |const char*| is more memory-efficient than allocating a JS string. We still have to allocate the JS string for passing things into JS, but ideally we will be able to move the point of allocation much closer to where it's actually needed, rather than indiscriminantly doing it all the time.
docshell/base/nsDocShell.cpp
docshell/base/nsIDocShell.idl
docshell/base/timeline/JavascriptTimelineMarker.h
dom/base/ScriptSettings.cpp
dom/base/ScriptSettings.h
dom/bindings/CallbackObject.cpp
dom/bindings/CallbackObject.h
dom/promise/Promise.cpp
js/public/Debug.h
js/src/builtin/TestingFunctions.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/shell/js.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/SavedStacks.cpp
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
js/xpconnect/src/XPCComponents.cpp
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -14106,23 +14106,24 @@ nsDocShell::SetOpener(nsITabParent* aOpe
 
 nsITabParent*
 nsDocShell::GetOpener()
 {
   nsCOMPtr<nsITabParent> opener(do_QueryReferent(mOpener));
   return opener;
 }
 
+// The caller owns |aAsyncCause| here.
 void
 nsDocShell::NotifyJSRunToCompletionStart(const char* aReason,
                                          const char16_t* aFunctionName,
                                          const char16_t* aFilename,
                                          const uint32_t aLineNumber,
                                          JS::Handle<JS::Value> aAsyncStack,
-                                         JS::Handle<JS::Value> aAsyncCause)
+                                         const char* aAsyncCause)
 {
   // If first start, mark interval start.
   if (mJSRunToCompletionDepth == 0) {
     RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
     if (timelines && timelines->HasConsumer(this)) {
       timelines->AddMarkerForDocShell(this, Move(
         mozilla::MakeUnique<JavascriptTimelineMarker>(
           aReason, aFunctionName, aFilename, aLineNumber, MarkerTracingType::START,
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -1051,17 +1051,17 @@ interface nsIDocShell : nsIDocShellTreeI
    * that execution has stopped.  This only occurs when the Timeline devtool
    * is collecting information.
    */
   [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStart(in string aReason,
                                                                   in wstring functionName,
                                                                   in wstring fileName,
                                                                   in unsigned long lineNumber,
                                                                   in jsval asyncStack,
-                                                                  in jsval asyncCause);
+                                                                  in string asyncCause);
   [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStop();
 
   /**
    * This attribute determines whether a document which is not about:blank has
    * already be loaded by this docShell.
    */
   [infallible] readonly attribute boolean hasLoadedNonBlankURI;
 
--- a/docshell/base/timeline/JavascriptTimelineMarker.h
+++ b/docshell/base/timeline/JavascriptTimelineMarker.h
@@ -12,53 +12,61 @@
 #include "mozilla/dom/RootedDictionary.h"
 #include "mozilla/dom/ToJSValue.h"
 
 namespace mozilla {
 
 class JavascriptTimelineMarker : public TimelineMarker
 {
 public:
+  // The caller owns |aAsyncCause| here, so we must copy it into a separate
+  // string for use later on.
   JavascriptTimelineMarker(const char* aReason,
                            const char16_t* aFunctionName,
                            const char16_t* aFileName,
                            uint32_t aLineNumber,
                            MarkerTracingType aTracingType,
                            JS::Handle<JS::Value> aAsyncStack,
-                           JS::Handle<JS::Value> aAsyncCause)
+                           const char* aAsyncCause)
     : TimelineMarker("Javascript", aTracingType, MarkerStackRequest::NO_STACK)
     , mCause(NS_ConvertUTF8toUTF16(aReason))
     , mFunctionName(aFunctionName)
     , mFileName(aFileName)
     , mLineNumber(aLineNumber)
+    , mAsyncCause(aAsyncCause)
   {
     JSContext* ctx = nsContentUtils::GetCurrentJSContext();
     if (ctx) {
       mAsyncStack.init(ctx, aAsyncStack);
-      mAsyncCause.init(ctx, aAsyncCause);
     }
   }
 
   virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) override
   {
     TimelineMarker::AddDetails(aCx, aMarker);
 
     aMarker.mCauseName.Construct(mCause);
 
     if (!mFunctionName.IsEmpty() || !mFileName.IsEmpty()) {
       dom::RootedDictionary<dom::ProfileTimelineStackFrame> stackFrame(aCx);
       stackFrame.mLine.Construct(mLineNumber);
       stackFrame.mSource.Construct(mFileName);
       stackFrame.mFunctionDisplayName.Construct(mFunctionName);
 
       if (mAsyncStack.isObject() && !mAsyncStack.isNullOrUndefined() &&
-          mAsyncCause.isString()) {
+          !mAsyncCause.IsEmpty()) {
         JS::Rooted<JSObject*> asyncStack(aCx, mAsyncStack.toObjectOrNull());
-        JS::Rooted<JSString*> asyncCause(aCx, mAsyncCause.toString());
         JS::Rooted<JSObject*> parentFrame(aCx);
+        JS::Rooted<JSString*> asyncCause(aCx, JS_NewUCStringCopyN(aCx, mAsyncCause.BeginReading(),
+                                                                  mAsyncCause.Length()));
+        if (!asyncCause) {
+          JS_ClearPendingException(aCx);
+          return;
+        }
+
         if (!JS::CopyAsyncStack(aCx, asyncStack, asyncCause, &parentFrame, 0)) {
           JS_ClearPendingException(aCx);
         } else {
           stackFrame.mAsyncParent = parentFrame;
         }
       }
 
       JS::Rooted<JS::Value> newStack(aCx);
@@ -73,14 +81,14 @@ public:
   }
 
 private:
   nsString mCause;
   nsString mFunctionName;
   nsString mFileName;
   uint32_t mLineNumber;
   JS::PersistentRooted<JS::Value> mAsyncStack;
-  JS::PersistentRooted<JS::Value> mAsyncCause;
+  NS_ConvertUTF8toUTF16 mAsyncCause;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_JavascriptTimelineMarker_h_
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -618,17 +618,17 @@ AutoEntryScript::DocshellEntryMonitor::D
   : JS::dbg::AutoEntryMonitor(aCx)
   , mReason(aReason)
 {
 }
 
 void
 AutoEntryScript::DocshellEntryMonitor::Entry(JSContext* aCx, JSFunction* aFunction,
                                              JSScript* aScript, JS::Handle<JS::Value> aAsyncStack,
-                                             JS::Handle<JSString*> aAsyncCause)
+                                             const char* aAsyncCause)
 {
   JS::Rooted<JSFunction*> rootedFunction(aCx);
   if (aFunction) {
     rootedFunction = aFunction;
   }
   JS::Rooted<JSScript*> rootedScript(aCx);
   if (aScript) {
     rootedScript = aScript;
@@ -663,23 +663,21 @@ AutoEntryScript::DocshellEntryMonitor::E
     filename = NS_ConvertUTF8toUTF16(JS_GetScriptFilename(rootedScript));
     lineNumber = JS_GetScriptBaseLineNumber(aCx, rootedScript);
   }
 
   if (!filename.IsEmpty() || functionName.isTwoByte()) {
     const char16_t* functionNameChars = functionName.isTwoByte() ?
       functionName.twoByteChars() : nullptr;
 
-    JS::Rooted<JS::Value> asyncCauseValue(aCx, aAsyncCause ? StringValue(aAsyncCause) :
-                                          JS::NullValue());
     docShellForJSRunToCompletion->NotifyJSRunToCompletionStart(mReason,
                                                                functionNameChars,
                                                                filename.BeginReading(),
                                                                lineNumber, aAsyncStack,
-                                                               asyncCauseValue);
+                                                               aAsyncCause);
   }
 }
 
 void
 AutoEntryScript::DocshellEntryMonitor::Exit(JSContext* aCx)
 {
   nsCOMPtr<nsPIDOMWindowInner> window =
     do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)));
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -347,36 +347,41 @@ public:
 
 private:
   // A subclass of AutoEntryMonitor that notifies the docshell.
   class DocshellEntryMonitor : public JS::dbg::AutoEntryMonitor
   {
   public:
     DocshellEntryMonitor(JSContext* aCx, const char* aReason);
 
+    // Please note that |aAsyncCause| here is owned by the caller, and its
+    // lifetime must outlive the lifetime of the DocshellEntryMonitor object.
+    // In practice, |aAsyncCause| is identical to |aReason| passed into
+    // the AutoEntryScript constructor, so the lifetime requirements are
+    // trivially satisfied by |aReason| being a statically allocated string.
     void Entry(JSContext* aCx, JSFunction* aFunction,
                JS::Handle<JS::Value> aAsyncStack,
-               JS::Handle<JSString*> aAsyncCause) override
+               const char* aAsyncCause) override
     {
       Entry(aCx, aFunction, nullptr, aAsyncStack, aAsyncCause);
     }
 
     void Entry(JSContext* aCx, JSScript* aScript,
                JS::Handle<JS::Value> aAsyncStack,
-               JS::Handle<JSString*> aAsyncCause) override
+               const char* aAsyncCause) override
     {
       Entry(aCx, nullptr, aScript, aAsyncStack, aAsyncCause);
     }
 
     void Exit(JSContext* aCx) override;
 
   private:
     void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript,
                JS::Handle<JS::Value> aAsyncStack,
-               JS::Handle<JSString*> aAsyncCause);
+               const char* aAsyncCause);
 
     const char* mReason;
   };
 
   // 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
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -167,22 +167,17 @@ CallbackObject::CallSetup::CallSetup(Cal
 
     if (!allowed) {
       return;
     }
   }
 
   mAsyncStack.emplace(cx, aCallback->GetCreationStack());
   if (*mAsyncStack) {
-    mAsyncCause.emplace(cx, JS_NewStringCopyZ(cx, aExecutionReason));
-    if (*mAsyncCause) {
-      mAsyncStackSetter.emplace(cx, *mAsyncStack, *mAsyncCause);
-    } else {
-      JS_ClearPendingException(cx);
-    }
+    mAsyncStackSetter.emplace(cx, *mAsyncStack, aExecutionReason);
   }
 
   // Enter the compartment of our callback, so we can actually work with it.
   //
   // Note that if the callback is a wrapper, this will not be the same
   // compartment that we ended up in with mAutoEntryScript above, because the
   // entry point is based off of the unwrapped callback (realCallback).
   mAc.emplace(cx, *mRootedCallable);
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -242,17 +242,16 @@ protected:
     Maybe<AutoIncumbentScript> mAutoIncumbentScript;
 
     // Constructed the rooter within the scope of mCxPusher above, so that it's
     // always within a request during its lifetime.
     Maybe<JS::Rooted<JSObject*> > mRootedCallable;
 
     // Members which are used to set the async stack.
     Maybe<JS::Rooted<JSObject*>> mAsyncStack;
-    Maybe<JS::Rooted<JSString*>> mAsyncCause;
     Maybe<JS::AutoSetAsyncStackForNewCalls> mAsyncStackSetter;
 
     // Can't construct a JSAutoCompartment without a JSContext either.  Also,
     // Put mAc after mAutoEntryScript so that we exit the compartment before
     // we pop the JSContext. Though in practice we'll often manually order
     // those two things.
     Maybe<JSAutoCompartment> mAc;
 
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -87,26 +87,21 @@ protected:
     JS::Rooted<JS::Value> value(cx, mValue);
     if (!MaybeWrapValue(cx, &value)) {
       NS_WARNING("Failed to wrap value into the right compartment.");
       JS_ClearPendingException(cx);
       return NS_OK;
     }
 
     JS::Rooted<JSObject*> asyncStack(cx, mPromise->mAllocationStack);
-    JS::Rooted<JSString*> asyncCause(cx, JS_NewStringCopyZ(cx, "Promise"));
-    if (!asyncCause) {
-      JS_ClearPendingException(cx);
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
 
     {
       Maybe<JS::AutoSetAsyncStackForNewCalls> sas;
       if (asyncStack) {
-        sas.emplace(cx, asyncStack, asyncCause);
+        sas.emplace(cx, asyncStack, "Promise");
       }
       mCallback->Call(cx, value);
     }
 
     return NS_OK;
   }
 
 private:
--- a/js/public/Debug.h
+++ b/js/public/Debug.h
@@ -346,30 +346,35 @@ class MOZ_STACK_CLASS AutoEntryMonitor {
 
   public:
     explicit AutoEntryMonitor(JSContext* cx);
     ~AutoEntryMonitor();
 
     // SpiderMonkey reports the JavaScript entry points occuring within this
     // AutoEntryMonitor's scope to the following member functions, which the
     // embedding is expected to override.
+    //
+    // It is important to note that |asyncCause| is owned by the caller and its
+    // lifetime must outlive the lifetime of the AutoEntryMonitor object. It is
+    // strongly encouraged that |asyncCause| be a string constant or similar
+    // statically allocated string.
 
     // We have begun executing |function|. Note that |function| may not be the
     // actual closure we are running, but only the canonical function object to
     // which the script refers.
     virtual void Entry(JSContext* cx, JSFunction* function,
                        HandleValue asyncStack,
-                       HandleString asyncCause) = 0;
+                       const char* asyncCause) = 0;
 
     // Execution has begun at the entry point of |script|, which is not a
     // function body. (This is probably being executed by 'eval' or some
     // JSAPI equivalent.)
     virtual void Entry(JSContext* cx, JSScript* script,
                        HandleValue asyncStack,
-                       HandleString asyncCause) = 0;
+                       const char* asyncCause) = 0;
 
     // Execution of the function or script has ended.
     virtual void Exit(JSContext* cx) { }
 };
 
 
 
 } // namespace dbg
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1115,18 +1115,23 @@ CallFunctionWithAsyncStack(JSContext* cx
     if (!args[2].isString() || args[2].toString()->empty()) {
         JS_ReportError(cx, "The third argument should be a non-empty string.");
         return false;
     }
 
     RootedObject function(cx, &args[0].toObject());
     RootedObject stack(cx, &args[1].toObject());
     RootedString asyncCause(cx, args[2].toString());
-
-    JS::AutoSetAsyncStackForNewCalls sas(cx, stack, asyncCause,
+    JSAutoByteString utf8Cause;
+    if (!utf8Cause.encodeUtf8(cx, asyncCause)) {
+        MOZ_ASSERT(cx->isExceptionPending());
+        return false;
+    }
+
+    JS::AutoSetAsyncStackForNewCalls sas(cx, stack, utf8Cause.ptr(),
                                          JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);
     return Call(cx, UndefinedHandleValue, function,
                 JS::HandleValueArray::empty(), args.rval());
 }
 
 static bool
 EnableTrackAllocations(JSContext* cx, unsigned argc, Value* vp)
 {
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4653,33 +4653,32 @@ JS_PUBLIC_API(void)
 JS_RestoreFrameChain(JSContext* cx)
 {
     AssertHeapIsIdleOrIterating(cx);
     CHECK_REQUEST(cx);
     cx->restoreFrameChain();
 }
 
 JS::AutoSetAsyncStackForNewCalls::AutoSetAsyncStackForNewCalls(
-  JSContext* cx, HandleObject stack, HandleString asyncCause,
+  JSContext* cx, HandleObject stack, const char* asyncCause,
   JS::AutoSetAsyncStackForNewCalls::AsyncCallKind kind)
   : cx(cx),
     oldAsyncStack(cx, cx->runtime()->asyncStackForNewActivations),
-    oldAsyncCause(cx, cx->runtime()->asyncCauseForNewActivations),
+    oldAsyncCause(cx->runtime()->asyncCauseForNewActivations),
     oldAsyncCallIsExplicit(cx->runtime()->asyncCallIsExplicit)
 {
     CHECK_REQUEST(cx);
 
     // The option determines whether we actually use the new values at this
     // point. It will not affect restoring the previous values when the object
     // is destroyed, so if the option changes it won't cause consistency issues.
     if (!cx->runtime()->options().asyncStack())
         return;
 
     SavedFrame* asyncStack = &stack->as<SavedFrame>();
-    MOZ_ASSERT(!asyncCause->empty());
 
     cx->runtime()->asyncStackForNewActivations = asyncStack;
     cx->runtime()->asyncCauseForNewActivations = asyncCause;
     cx->runtime()->asyncCallIsExplicit = kind == AsyncCallKind::EXPLICIT;
 }
 
 JS::AutoSetAsyncStackForNewCalls::~AutoSetAsyncStackForNewCalls()
 {
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4326,35 +4326,40 @@ namespace JS {
  *
  * See also `js/src/doc/SavedFrame/SavedFrame.md` for documentation on async
  * stack frames.
  */
 class MOZ_STACK_CLASS JS_PUBLIC_API(AutoSetAsyncStackForNewCalls)
 {
     JSContext* cx;
     RootedObject oldAsyncStack;
-    RootedString oldAsyncCause;
+    const char* oldAsyncCause;
     bool oldAsyncCallIsExplicit;
 
   public:
     enum class AsyncCallKind {
         // The ordinary kind of call, where we may apply an async
         // parent if there is no ordinary parent.
         IMPLICIT,
         // An explicit async parent, e.g., callFunctionWithAsyncStack,
         // where we always want to override any ordinary parent.
         EXPLICIT
     };
 
     // The stack parameter cannot be null by design, because it would be
     // ambiguous whether that would clear any scheduled async stack and make the
     // normal stack reappear in the new call, or just keep the async stack
     // already scheduled for the new call, if any.
+    //
+    // asyncCause is owned by the caller and its lifetime must outlive the
+    // lifetime of the AutoSetAsyncStackForNewCalls object. It is strongly
+    // encouraged that asyncCause be a string constant or similar statically
+    // allocated string.
     AutoSetAsyncStackForNewCalls(JSContext* cx, HandleObject stack,
-                                 HandleString asyncCause,
+                                 const char* asyncCause,
                                  AsyncCallKind kind = AsyncCallKind::IMPLICIT);
     ~AutoSetAsyncStackForNewCalls();
 };
 
 } // namespace JS
 
 /************************************************************************/
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4846,32 +4846,32 @@ class ShellAutoEntryMonitor : JS::dbg::A
         enteredWithoutExit(false)
     { }
 
     ~ShellAutoEntryMonitor() {
         MOZ_ASSERT(!enteredWithoutExit);
     }
 
     void Entry(JSContext* cx, JSFunction* function, JS::HandleValue asyncStack,
-               JS::HandleString asyncCause) override {
+               const char* asyncCause) override {
         MOZ_ASSERT(!enteredWithoutExit);
         enteredWithoutExit = true;
 
         RootedString displayId(cx, JS_GetFunctionDisplayId(function));
         if (displayId) {
             UniqueChars displayIdStr(JS_EncodeStringToUTF8(cx, displayId));
             oom = !displayIdStr || !log.append(mozilla::Move(displayIdStr));
             return;
         }
 
         oom = !log.append(DuplicateString("anonymous"));
     }
 
     void Entry(JSContext* cx, JSScript* script, JS::HandleValue asyncStack,
-               JS::HandleString asyncCause) override {
+               const char* asyncCause) override {
         MOZ_ASSERT(!enteredWithoutExit);
         enteredWithoutExit = true;
 
         UniqueChars label(JS_smprintf("eval:%s", JS_GetScriptFilename(script)));
         oom = !label || !log.append(mozilla::Move(label));
     }
 
     void Exit(JSContext* cx) override {
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -133,17 +133,17 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     jitStackLimit_(0xbad),
     jitStackLimitNoInterrupt_(0xbad),
     activation_(nullptr),
     profilingActivation_(nullptr),
     profilerSampleBufferGen_(0),
     profilerSampleBufferLapCount_(1),
     wasmActivationStack_(nullptr),
     asyncStackForNewActivations(this),
-    asyncCauseForNewActivations(this),
+    asyncCauseForNewActivations(nullptr),
     asyncCallIsExplicit(false),
     entryMonitor(nullptr),
     noExecuteDebuggerTop(nullptr),
     parentRuntime(parentRuntime),
 #ifdef DEBUG
     updateChildRuntimeCount(parentRuntime),
 #endif
     interrupt_(false),
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -704,17 +704,17 @@ struct JSRuntime : public JS::shadow::Ru
      * New activations will reset this to nullptr on construction after getting
      * the current value, and will restore the previous value on destruction.
      */
     JS::PersistentRooted<js::SavedFrame*> asyncStackForNewActivations;
 
     /*
      * Value of asyncCause to be attached to asyncStackForNewActivations.
      */
-    JS::PersistentRooted<JSString*> asyncCauseForNewActivations;
+    const char* asyncCauseForNewActivations;
 
     /*
      * True if the async call was explicitly requested, e.g. via
      * callFunctionWithAsyncStack.
      */
     bool asyncCallIsExplicit;
 
     /* If non-null, report JavaScript entry points to this monitor. */
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -20,16 +20,17 @@
 #include "jshashutil.h"
 #include "jsmath.h"
 #include "jsnum.h"
 #include "jsscript.h"
 
 #include "gc/Marking.h"
 #include "gc/Policy.h"
 #include "gc/Rooting.h"
+#include "js/CharacterEncoding.h"
 #include "js/Vector.h"
 #include "vm/Debugger.h"
 #include "vm/SavedFrame.h"
 #include "vm/SPSProfiler.h"
 #include "vm/StringBuffer.h"
 #include "vm/Time.h"
 #include "vm/WrapperObject.h"
 
@@ -1114,17 +1115,32 @@ SavedStacks::insertFrames(JSContext* cx,
         if (!asyncActivation) {
             asyncStack = activation.asyncStack();
             if (asyncStack) {
                 // While walking from the youngest to the oldest frame, we found
                 // an activation that has an async stack set. We will use the
                 // youngest frame of the async stack as the parent of the oldest
                 // frame of this activation. We still need to iterate over other
                 // frames in this activation before reaching the oldest frame.
-                asyncCause = activation.asyncCause();
+                AutoCompartment ac(cx, iter.compartment());
+                const char* cause = activation.asyncCause();
+                UTF8Chars utf8Chars(cause, strlen(cause));
+                size_t twoByteCharsLen = 0;
+                char16_t* twoByteChars = UTF8CharsToNewTwoByteCharsZ(cx, utf8Chars,
+                                                                     &twoByteCharsLen).get();
+                if (!twoByteChars)
+                    return false;
+
+                // We expect that there will be a relatively small set of
+                // asyncCause reasons ("setTimeout", "promise", etc.), so we
+                // atomize the cause here in hopes of being able to benefit
+                // from reuse.
+                asyncCause = JS_AtomizeUCStringN(cx, twoByteChars, twoByteCharsLen);
+                if (!asyncCause)
+                    return false;
                 asyncActivation = &activation;
             }
         }
 
         Rooted<LocationValue> location(cx);
         {
             AutoCompartment ac(cx, iter.compartment());
             if (!cx->compartment()->savedStacks().getLocation(cx, iter, &location))
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -868,17 +868,17 @@ Activation::Activation(JSContext* cx, Ki
   : cx_(cx),
     compartment_(cx->compartment()),
     prev_(cx->runtime_->activation_),
     prevProfiling_(prev_ ? prev_->mostRecentProfiling() : nullptr),
     savedFrameChain_(0),
     hideScriptedCallerCount_(0),
     frameCache_(cx),
     asyncStack_(cx, cx->runtime_->asyncStackForNewActivations),
-    asyncCause_(cx, cx->runtime_->asyncCauseForNewActivations),
+    asyncCause_(cx->runtime_->asyncCauseForNewActivations),
     asyncCallIsExplicit_(cx->runtime_->asyncCallIsExplicit),
     kind_(kind)
 {
     cx->runtime_->asyncStackForNewActivations = nullptr;
     cx->runtime_->asyncCauseForNewActivations = nullptr;
     cx->runtime_->asyncCallIsExplicit = false;
     cx->runtime_->activation_ = this;
 }
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1396,33 +1396,33 @@ ActivationEntryMonitor::asyncStack(JSCon
 ActivationEntryMonitor::ActivationEntryMonitor(JSContext* cx, InterpreterFrame* entryFrame)
   : ActivationEntryMonitor(cx)
 {
     if (entryMonitor_) {
         // The InterpreterFrame is not yet part of an Activation, so it won't
         // be traced if we trigger GC here. Suppress GC to avoid this.
         gc::AutoSuppressGC suppressGC(cx);
         RootedValue stack(cx, asyncStack(cx));
-        RootedString asyncCause(cx, cx->runtime()->asyncCauseForNewActivations);
+        const char* asyncCause = cx->runtime()->asyncCauseForNewActivations;
         if (entryFrame->isFunctionFrame())
             entryMonitor_->Entry(cx, &entryFrame->callee(), stack, asyncCause);
         else
             entryMonitor_->Entry(cx, entryFrame->script(), stack, asyncCause);
     }
 }
 
 ActivationEntryMonitor::ActivationEntryMonitor(JSContext* cx, jit::CalleeToken entryToken)
   : ActivationEntryMonitor(cx)
 {
     if (entryMonitor_) {
         // The CalleeToken is not traced at this point and we also don't want
         // a GC to discard the code we're about to enter, so we suppress GC.
         gc::AutoSuppressGC suppressGC(cx);
         RootedValue stack(cx, asyncStack(cx));
-        RootedString asyncCause(cx, cx->runtime()->asyncCauseForNewActivations);
+        const char* asyncCause = cx->runtime()->asyncCauseForNewActivations;
         if (jit::CalleeTokenIsFunction(entryToken))
             entryMonitor_->Entry(cx_, jit::CalleeTokenToFunction(entryToken), stack, asyncCause);
         else
             entryMonitor_->Entry(cx_, jit::CalleeTokenToScript(entryToken), stack, asyncCause);
     }
 }
 
 /*****************************************************************************/
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1178,17 +1178,17 @@ class Activation
     // capture in place of the actual stack of previous activations. Note that
     // the stack of this activation is captured entirely before this is used.
     //
     // Usually this is nullptr, meaning that normal stack capture will occur.
     // When this is set, the stack of any previous activation is ignored.
     Rooted<SavedFrame*> asyncStack_;
 
     // Value of asyncCause to be attached to asyncStack_.
-    RootedString asyncCause_;
+    const char* asyncCause_;
 
     // True if the async call was explicitly requested, e.g. via
     // callFunctionWithAsyncStack.
     bool asyncCallIsExplicit_;
 
     enum Kind { Interpreter, Jit, Wasm };
     Kind kind_;
 
@@ -1260,17 +1260,17 @@ class Activation
     static size_t offsetOfPrevProfiling() {
         return offsetof(Activation, prevProfiling_);
     }
 
     SavedFrame* asyncStack() {
         return asyncStack_;
     }
 
-    JSString* asyncCause() {
+    const char* asyncCause() const {
         return asyncCause_;
     }
 
     bool asyncCallIsExplicit() const {
         return asyncCallIsExplicit_;
     }
 
     inline LiveSavedFrameCache* getLiveSavedFrameCache(JSContext* cx);
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2707,22 +2707,19 @@ nsXPCComponents_Utils::CallFunctionWithA
     if (NS_FAILED(rv))
         return rv;
     if (!asyncStack.isObject()) {
         JS_ReportError(cx, "Must use a native JavaScript stack frame");
         return NS_ERROR_INVALID_ARG;
     }
 
     JS::Rooted<JSObject*> asyncStackObj(cx, &asyncStack.toObject());
-    JS::Rooted<JSString*> asyncCauseString(cx, JS_NewUCStringCopyN(cx, asyncCause.BeginReading(),
-                                                                       asyncCause.Length()));
-    if (!asyncCauseString)
-        return NS_ERROR_OUT_OF_MEMORY;
-
-    JS::AutoSetAsyncStackForNewCalls sas(cx, asyncStackObj, asyncCauseString,
+
+    NS_ConvertUTF16toUTF8 utf8Cause(asyncCause);
+    JS::AutoSetAsyncStackForNewCalls sas(cx, asyncStackObj, utf8Cause.get(),
                                          JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);
 
     if (!JS_CallFunctionValue(cx, nullptr, function,
                               JS::HandleValueArray::empty(), retval))
     {
         return NS_ERROR_XPC_JAVASCRIPT_ERROR;
     }