Bug 1357012 - Use fallible append for compression tasks and use UniquePtrs. (r=jonco)
authorShu-yu Guo <shu@rfrn.org>
Wed, 19 Apr 2017 13:58:01 -0700
changeset 353984 bcf9341329ab5ade13acd349acc96cdafe5e883e
parent 353983 70debe6d9f951070571152bf144da9d406ca3058
child 353985 0a2fca8aa2e64c151f566c189e03f6dbf303e47b
push id31684
push usercbook@mozilla.com
push dateThu, 20 Apr 2017 09:13:26 +0000
treeherdermozilla-central@27311156637f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1357012
milestone55.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 1357012 - Use fallible append for compression tasks and use UniquePtrs. (r=jonco)
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeCompiler.h
js/src/jsgc.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/vm/HelperThreads.cpp
js/src/vm/HelperThreads.h
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -52,17 +52,16 @@ class MOZ_STACK_CLASS BytecodeCompiler
 
     ScriptSourceObject* sourceObjectPtr() const;
     SourceCompressionTask* sourceCompressionTask() const;
 
   private:
     JSScript* compileScript(HandleObject environment, SharedContext* sc);
     bool checkLength();
     bool createScriptSource(const Maybe<uint32_t>& parameterListEnd);
-    bool enqueueOffThreadSourceCompression();
     bool canLazilyParse();
     bool createParser();
     bool createSourceAndParser(const Maybe<uint32_t>& parameterListEnd = Nothing());
 
     // If toString{Start,End} are not explicitly passed, assume the script's
     // offsets in the source used to parse it are the same as what should be
     // used to compute its Function.prototype.toString() value.
     bool createScript();
@@ -78,17 +77,16 @@ class MOZ_STACK_CLASS BytecodeCompiler
     LifoAlloc& alloc;
     const ReadOnlyCompileOptions& options;
     SourceBufferHolder& sourceBuffer;
 
     RootedScope enclosingScope;
 
     RootedScriptSource sourceObject;
     ScriptSource* scriptSource;
-    SourceCompressionTask* sourceCompressionTask_;
 
     Maybe<UsedNameTracker> usedNames;
     Maybe<Parser<SyntaxParseHandler>> syntaxParser;
     Maybe<Parser<FullParseHandler>> parser;
 
     Directives directives;
     TokenStream::Position startPosition;
 
@@ -168,17 +166,16 @@ BytecodeCompiler::BytecodeCompiler(JSCon
   : keepAtoms(cx),
     cx(cx),
     alloc(alloc),
     options(options),
     sourceBuffer(sourceBuffer),
     enclosingScope(cx, enclosingScope),
     sourceObject(cx),
     scriptSource(nullptr),
-    sourceCompressionTask_(nullptr),
     directives(options.strictOption),
     startPosition(keepAtoms),
     script(cx)
 {
     MOZ_ASSERT(sourceBuffer.get());
 }
 
 bool
@@ -215,50 +212,16 @@ BytecodeCompiler::createScriptSource(con
             return false;
         }
     }
 
     return true;
 }
 
 bool
-BytecodeCompiler::enqueueOffThreadSourceCompression()
-{
-    // There are several cases where source compression is not a good idea:
-    //  - If the script is tiny, then compression will save little or no space.
-    //  - If there is only one core, then compression will contend with JS
-    //    execution (which hurts benchmarketing).
-    //
-    // Otherwise, enqueue a compression task to be processed when a major
-    // GC is requested.
-
-    if (!scriptSource->hasUncompressedSource())
-        return true;
-
-    bool canCompressOffThread =
-        HelperThreadState().cpuCount > 1 &&
-        HelperThreadState().threadCount >= 2 &&
-        CanUseExtraThreads();
-    const size_t TINY_SCRIPT = 256;
-    if (TINY_SCRIPT <= sourceBuffer.length() && canCompressOffThread) {
-        // Heap allocate the task. It will be freed upon compression
-        // completing in AttachFinishedCompressedSources.
-        SourceCompressionTask* task = cx->new_<SourceCompressionTask>(cx->runtime(),
-                                                                      scriptSource);
-        if (!task)
-            return false;
-        if (!EnqueueOffThreadCompression(cx, task))
-            return false;
-        sourceCompressionTask_ = task;
-    }
-
-    return true;
-}
-
-bool
 BytecodeCompiler::canLazilyParse()
 {
     return options.canLazilyParse &&
            !(enclosingScope && enclosingScope->hasOnChain(ScopeKind::NonSyntactic)) &&
            !cx->compartment()->behaviors().disableLazyParsing() &&
            !cx->compartment()->behaviors().discardSource() &&
            !options.sourceIsLazy &&
            !cx->lcovEnabled();
@@ -411,17 +374,17 @@ BytecodeCompiler::compileScript(HandleOb
         usedNames->reset();
     }
 
     // We have just finished parsing the source. Inform the source so that we
     // can compute statistics (e.g. how much time our functions remain lazy).
     script->scriptSource()->recordParseEnded();
 
     // Enqueue an off-thread source compression task after finishing parsing.
-    if (!enqueueOffThreadSourceCompression())
+    if (!scriptSource->tryCompressOffThread(cx))
         return nullptr;
 
     MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending());
 
     return script;
 }
 
 JSScript*
@@ -476,17 +439,17 @@ BytecodeCompiler::compileModule()
 
     RootedModuleEnvironmentObject env(cx, ModuleEnvironmentObject::create(cx, module));
     if (!env)
         return nullptr;
 
     module->setInitialEnvironment(env);
 
     // Enqueue an off-thread source compression task after finishing parsing.
-    if (!enqueueOffThreadSourceCompression())
+    if (!scriptSource->tryCompressOffThread(cx))
         return nullptr;
 
     MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending());
     return module;
 }
 
 // Compile a standalone JS function, which might appear as the value of an
 // event handler attribute in an HTML <INPUT> tag, or in a Function()
@@ -532,34 +495,28 @@ BytecodeCompiler::compileStandaloneFunct
         fun.set(fn->pn_funbox->function());
         MOZ_ASSERT(IsAsmJSModule(fun));
     }
 
     if (!NameFunctions(cx, fn))
         return false;
 
     // Enqueue an off-thread source compression task after finishing parsing.
-    if (!enqueueOffThreadSourceCompression())
+    if (!scriptSource->tryCompressOffThread(cx))
         return false;
 
     return true;
 }
 
 ScriptSourceObject*
 BytecodeCompiler::sourceObjectPtr() const
 {
     return sourceObject.get();
 }
 
-SourceCompressionTask*
-BytecodeCompiler::sourceCompressionTask() const
-{
-    return sourceCompressionTask_;
-}
-
 ScriptSourceObject*
 frontend::CreateScriptSourceObject(JSContext* cx, const ReadOnlyCompileOptions& options,
                                    const Maybe<uint32_t>& parameterListEnd /* = Nothing() */)
 {
     ScriptSource* ss = cx->new_<ScriptSource>();
     if (!ss)
         return nullptr;
     ScriptSourceHolder ssHolder(ss);
@@ -601,78 +558,70 @@ frontend::CreateScriptSourceObject(JSCon
 // Whatever happens to the top-level script compilation (even if it fails and
 // returns null), we must finish initializing the SSO.  This is because there
 // may be valid inner scripts observable by the debugger which reference the
 // partially-initialized SSO.
 class MOZ_STACK_CLASS AutoInitializeSourceObject
 {
     BytecodeCompiler& compiler_;
     ScriptSourceObject** sourceObjectOut_;
-    SourceCompressionTask** sourceCompressionTaskOut_;
 
   public:
     AutoInitializeSourceObject(BytecodeCompiler& compiler,
-                               ScriptSourceObject** sourceObjectOut,
-                               SourceCompressionTask** sourceCompressionTaskOut)
+                               ScriptSourceObject** sourceObjectOut)
       : compiler_(compiler),
-        sourceObjectOut_(sourceObjectOut),
-        sourceCompressionTaskOut_(sourceCompressionTaskOut)
+        sourceObjectOut_(sourceObjectOut)
     { }
 
     ~AutoInitializeSourceObject() {
         if (sourceObjectOut_)
             *sourceObjectOut_ = compiler_.sourceObjectPtr();
-        if (sourceCompressionTaskOut_)
-            *sourceCompressionTaskOut_ = compiler_.sourceCompressionTask();
     }
 };
 
 JSScript*
 frontend::CompileGlobalScript(JSContext* cx, LifoAlloc& alloc, ScopeKind scopeKind,
                               const ReadOnlyCompileOptions& options,
                               SourceBufferHolder& srcBuf,
-                              ScriptSourceObject** sourceObjectOut,
-                              SourceCompressionTask** sourceCompressionTaskOut)
+                              ScriptSourceObject** sourceObjectOut)
 {
     MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
     BytecodeCompiler compiler(cx, alloc, options, srcBuf, /* enclosingScope = */ nullptr);
-    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut, sourceCompressionTaskOut);
+    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
     return compiler.compileGlobalScript(scopeKind);
 }
 
 JSScript*
 frontend::CompileEvalScript(JSContext* cx, LifoAlloc& alloc,
                             HandleObject environment, HandleScope enclosingScope,
                             const ReadOnlyCompileOptions& options,
                             SourceBufferHolder& srcBuf,
-                            ScriptSourceObject** sourceObjectOut,
-                            SourceCompressionTask** sourceCompressionTaskOut)
+                            ScriptSourceObject** sourceObjectOut)
 {
     BytecodeCompiler compiler(cx, alloc, options, srcBuf, enclosingScope);
-    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut, sourceCompressionTaskOut);
+    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
     return compiler.compileEvalScript(environment, enclosingScope);
 }
 
 ModuleObject*
 frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInput,
                         SourceBufferHolder& srcBuf, LifoAlloc& alloc,
-                        ScriptSourceObject** sourceObjectOut,
-                        SourceCompressionTask** sourceCompressionTaskOut)
+                        ScriptSourceObject** sourceObjectOut)
 {
     MOZ_ASSERT(srcBuf.get());
     MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr);
 
     CompileOptions options(cx, optionsInput);
     options.maybeMakeStrictMode(true); // ES6 10.2.1 Module code is always strict mode code.
     options.setIsRunOnce(true);
     options.allowHTMLComments = false;
 
     RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
     BytecodeCompiler compiler(cx, alloc, options, srcBuf, emptyGlobalScope);
-    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut, sourceCompressionTaskOut);
+    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
     return compiler.compileModule();
 }
 
 ModuleObject*
 frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
                         SourceBufferHolder& srcBuf)
 {
     if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global()))
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -18,48 +18,44 @@
 class JSLinearString;
 
 namespace js {
 
 class LazyScript;
 class LifoAlloc;
 class ModuleObject;
 class ScriptSourceObject;
-class SourceCompressionTask;
 
 namespace frontend {
 
 class TokenStream;
 class FunctionBox;
 class ParseNode;
 
 JSScript*
 CompileGlobalScript(JSContext* cx, LifoAlloc& alloc, ScopeKind scopeKind,
                     const ReadOnlyCompileOptions& options,
                     SourceBufferHolder& srcBuf,
-                    ScriptSourceObject** sourceObjectOut = nullptr,
-                    SourceCompressionTask** sourceCompressionTaskOut = nullptr);
+                    ScriptSourceObject** sourceObjectOut = nullptr);
 
 JSScript*
 CompileEvalScript(JSContext* cx, LifoAlloc& alloc,
                   HandleObject scopeChain, HandleScope enclosingScope,
                   const ReadOnlyCompileOptions& options,
                   SourceBufferHolder& srcBuf,
-                  ScriptSourceObject** sourceObjectOut = nullptr,
-                  SourceCompressionTask** sourceCompressionTaskOut = nullptr);
+                  ScriptSourceObject** sourceObjectOut = nullptr);
 
 ModuleObject*
 CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
               SourceBufferHolder& srcBuf);
 
 ModuleObject*
 CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
               SourceBufferHolder& srcBuf, LifoAlloc& alloc,
-              ScriptSourceObject** sourceObjectOut = nullptr,
-              SourceCompressionTask** sourceCompressionTaskOut = nullptr);
+              ScriptSourceObject** sourceObjectOut = nullptr);
 
 MOZ_MUST_USE bool
 CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length);
 
 //
 // Compile a single function. The source in srcBuf must match the ECMA-262
 // FunctionExpression production.
 //
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -5048,32 +5048,28 @@ SweepMiscTask::run()
 /* virtual */ void
 SweepCompressionTasksTask::run()
 {
     AutoLockHelperThreadState lock;
 
     // Attach finished compression tasks.
     auto& finished = HelperThreadState().compressionFinishedList(lock);
     for (size_t i = 0; i < finished.length(); i++) {
-        SourceCompressionTask* task = finished[i];
-        if (task->runtimeMatches(runtime())) {
+        if (finished[i]->runtimeMatches(runtime())) {
+            UniquePtr<SourceCompressionTask> task(Move(finished[i]));
             HelperThreadState().remove(finished, &i);
             task->complete();
-            js_delete(task);
         }
     }
 
     // Sweep pending tasks that are holding onto should-be-dead ScriptSources.
     auto& pending = HelperThreadState().compressionPendingList(lock);
     for (size_t i = 0; i < pending.length(); i++) {
-        SourceCompressionTask* task = pending[i];
-        if (task->shouldCancel()) {
+        if (pending[i]->shouldCancel())
             HelperThreadState().remove(pending, &i);
-            js_delete(task);
-        }
     }
 }
 
 void
 GCRuntime::startTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked)
 {
     if (!task.startWithLockHeld(locked)) {
         AutoUnlockHelperThreadState unlock(locked);
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1816,16 +1816,60 @@ ScriptSource::setSource(JSContext* cx,
 
 void
 ScriptSource::setSource(SharedImmutableTwoByteString&& string)
 {
     MOZ_ASSERT(data.is<Missing>());
     data = SourceType(Uncompressed(mozilla::Move(string)));
 }
 
+bool
+ScriptSource::tryCompressOffThread(JSContext* cx)
+{
+    if (!data.is<Uncompressed>())
+        return true;
+
+    // There are several cases where source compression is not a good idea:
+    //  - If the script is tiny, then compression will save little or no space.
+    //  - If there is only one core, then compression will contend with JS
+    //    execution (which hurts benchmarketing).
+    //
+    // Otherwise, enqueue a compression task to be processed when a major
+    // GC is requested.
+
+    bool canCompressOffThread =
+        HelperThreadState().cpuCount > 1 &&
+        HelperThreadState().threadCount >= 2 &&
+        CanUseExtraThreads();
+    const size_t TINY_SCRIPT = 256;
+    if (TINY_SCRIPT > length() || !canCompressOffThread)
+        return true;
+
+    // The SourceCompressionTask needs to record the major GC number for
+    // scheduling. If we're parsing off thread, this number is not safe to
+    // access.
+    //
+    // When parsing on the main thread, the attempts made to compress off
+    // thread in BytecodeCompiler will succeed.
+    //
+    // When parsing off-thread, the above attempts will fail and the attempt
+    // made in ParseTask::finish will succeed.
+    if (!CurrentThreadCanAccessRuntime(cx->runtime()))
+        return true;
+
+    // Heap allocate the task. It will be freed upon compression
+    // completing in AttachFinishedCompressedSources.
+    auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), this);
+    if (!task) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+    return EnqueueOffThreadCompression(cx, Move(task));
+}
+
 MOZ_MUST_USE bool
 ScriptSource::setCompressedSource(JSContext* cx,
                                   mozilla::UniquePtr<char[], JS::FreePolicy>&& raw,
                                   size_t rawLength,
                                   size_t sourceLength)
 {
     MOZ_ASSERT(raw);
     auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings();
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -568,16 +568,18 @@ class ScriptSource
     void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                 JS::ScriptSourceInfo* info) const;
 
     MOZ_MUST_USE bool setSource(JSContext* cx,
                                 UniqueTwoByteChars&& source,
                                 size_t length);
     void setSource(SharedImmutableTwoByteString&& string);
 
+    MOZ_MUST_USE bool tryCompressOffThread(JSContext* cx);
+
     MOZ_MUST_USE bool setCompressedSource(JSContext* cx,
                                           UniqueChars&& raw,
                                           size_t rawLength,
                                           size_t sourceLength);
     void setCompressedSource(SharedImmutableString&& raw, size_t sourceLength);
 
     // XDR handling
     template <XDRMode mode>
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -296,29 +296,29 @@ static const JSClass parseTaskGlobalClas
 
 ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
                      const char16_t* chars, size_t length,
                      JS::OffThreadCompileCallback callback, void* callbackData)
   : kind(kind), options(cx), chars(chars), length(length),
     alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     parseGlobal(parseGlobal),
     callback(callback), callbackData(callbackData),
-    script(nullptr), sourceObject(nullptr), sourceCompressionTask(nullptr),
+    script(nullptr), sourceObject(nullptr),
     overRecursed(false), outOfMemory(false)
 {
 }
 
 ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
                      JS::TranscodeBuffer& buffer, size_t cursor,
                      JS::OffThreadCompileCallback callback, void* callbackData)
   : kind(kind), options(cx), buffer(&buffer), cursor(cursor),
     alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     parseGlobal(parseGlobal),
     callback(callback), callbackData(callbackData),
-    script(nullptr), sourceObject(nullptr), sourceCompressionTask(nullptr),
+    script(nullptr), sourceObject(nullptr),
     overRecursed(false), outOfMemory(false)
 {
 }
 
 bool
 ParseTask::init(JSContext* cx, const ReadOnlyCompileOptions& options)
 {
     if (!this->options.copy(cx, options))
@@ -335,18 +335,18 @@ ParseTask::activate(JSRuntime* rt)
 
 bool
 ParseTask::finish(JSContext* cx)
 {
     if (sourceObject) {
         RootedScriptSource sso(cx, sourceObject);
         if (!ScriptSourceObject::initFromOptions(cx, sso, options))
             return false;
-        if (sourceCompressionTask)
-            sourceCompressionTask->fixupMajorGCNumber(cx->runtime());
+        if (!sso->source()->tryCompressOffThread(cx))
+            return false;
     }
 
     return true;
 }
 
 ParseTask::~ParseTask()
 {
     for (size_t i = 0; i < errors.length(); i++)
@@ -380,34 +380,32 @@ ScriptParseTask::ScriptParseTask(JSConte
 }
 
 void
 ScriptParseTask::parse(JSContext* cx)
 {
     SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
     script = frontend::CompileGlobalScript(cx, alloc, ScopeKind::Global,
                                            options, srcBuf,
-                                           /* sourceObjectOut = */ &sourceObject,
-                                           /* sourceCompressionTaskOut = */ &sourceCompressionTask);
+                                           /* sourceObjectOut = */ &sourceObject);
 }
 
 ModuleParseTask::ModuleParseTask(JSContext* cx, JSObject* parseGlobal,
                                  const char16_t* chars, size_t length,
                                  JS::OffThreadCompileCallback callback, void* callbackData)
   : ParseTask(ParseTaskKind::Module, cx, parseGlobal, chars, length, callback,
               callbackData)
 {
 }
 
 void
 ModuleParseTask::parse(JSContext* cx)
 {
     SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
-    ModuleObject* module = frontend::CompileModule(cx, options, srcBuf, alloc, &sourceObject,
-                                                   &sourceCompressionTask);
+    ModuleObject* module = frontend::CompileModule(cx, options, srcBuf, alloc, &sourceObject);
     if (module)
         script = module->script();
 }
 
 ScriptDecodeTask::ScriptDecodeTask(JSContext* cx, JSObject* parseGlobal,
                                    JS::TranscodeBuffer& buffer, size_t cursor,
                                    JS::OffThreadCompileCallback callback, void* callbackData)
   : ParseTask(ParseTaskKind::ScriptDecode, cx, parseGlobal,
@@ -1118,23 +1116,23 @@ GlobalHelperThreadState::startHandlingCo
         notifyOne(PRODUCER, lock);
 }
 
 void
 GlobalHelperThreadState::scheduleCompressionTasks(const AutoLockHelperThreadState& lock)
 {
     auto& pending = compressionPendingList(lock);
     auto& worklist = compressionWorklist(lock);
-    MOZ_ASSERT(worklist.capacity() >= pending.length());
 
     for (size_t i = 0; i < pending.length(); i++) {
-        SourceCompressionTask* task = pending[i];
-        if (task->shouldStart()) {
+        if (pending[i]->shouldStart()) {
+            // OOMing during appending results in the task not being scheduled
+            // and deleted.
+            Unused << worklist.append(Move(pending[i]));
             remove(pending, &i);
-            worklist.infallibleAppend(task);
         }
     }
 }
 
 bool
 GlobalHelperThreadState::canStartGCHelperTask(const AutoLockHelperThreadState& lock)
 {
     return !gcHelperWorklist(lock).empty() &&
@@ -1725,73 +1723,67 @@ HelperThread::handleParseWorkload(AutoLo
 }
 
 void
 HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().canStartCompressionTask(locked));
     MOZ_ASSERT(idle());
 
-    currentTask.emplace(HelperThreadState().compressionWorklist(locked).popCopy());
-    SourceCompressionTask* task = compressionTask();
+    UniquePtr<SourceCompressionTask> task;
+    {
+        auto& worklist = HelperThreadState().compressionWorklist(locked);
+        task = Move(worklist.back());
+        worklist.popBack();
+        currentTask.emplace(task.get());
+    }
 
     {
         AutoUnlockHelperThreadState unlock(locked);
 
         TraceLoggerThread* logger = TraceLoggerForCurrentThread();
         AutoTraceLog logCompile(logger, TraceLogger_CompressSource);
 
         task->work();
     }
 
     {
         AutoEnterOOMUnsafeRegion oomUnsafe;
-        if (!HelperThreadState().compressionFinishedList(locked).append(task))
+        if (!HelperThreadState().compressionFinishedList(locked).append(Move(task)))
             oomUnsafe.crash("handleCompressionWorkload");
     }
 
     currentTask.reset();
 
     // Notify the active thread in case it is waiting for the compression to finish.
     HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
 }
 
 bool
-js::EnqueueOffThreadCompression(JSContext* cx, SourceCompressionTask* task)
+js::EnqueueOffThreadCompression(JSContext* cx, UniquePtr<SourceCompressionTask> task)
 {
     AutoLockHelperThreadState lock;
 
     auto& pending = HelperThreadState().compressionPendingList(lock);
-    auto& worklist = HelperThreadState().compressionWorklist(lock);
-    if (!pending.append(task)) {
+    if (!pending.append(Move(task))) {
         if (!cx->helperThread())
             ReportOutOfMemory(cx);
-        js_delete(task);
-        return false;
-    }
-    if (!worklist.reserve(pending.length())) {
-        if (!cx->helperThread())
-            ReportOutOfMemory(cx);
-        pending.popBack();
         return false;
     }
 
     return true;
 }
 
 template <typename T>
 static void
 ClearCompressionTaskList(T& list, JSRuntime* runtime)
 {
     for (size_t i = 0; i < list.length(); i++) {
-        SourceCompressionTask* task = list[i];
-        if (task->runtimeMatches(runtime)) {
+        if (list[i]->runtimeMatches(runtime))
             HelperThreadState().remove(list, &i);
-            js_delete(task);
-        }
     }
 }
 
 void
 js::CancelOffThreadCompressions(JSRuntime* runtime)
 {
     AutoLockHelperThreadState lock;
 
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -67,17 +67,17 @@ class GlobalHelperThreadState
     // May be accessed without locking.
     size_t cpuCount;
 
     // Number of threads to create. May be accessed without locking.
     size_t threadCount;
 
     typedef Vector<jit::IonBuilder*, 0, SystemAllocPolicy> IonBuilderVector;
     typedef Vector<ParseTask*, 0, SystemAllocPolicy> ParseTaskVector;
-    typedef Vector<SourceCompressionTask*, 0, SystemAllocPolicy> SourceCompressionTaskVector;
+    typedef Vector<UniquePtr<SourceCompressionTask>, 0, SystemAllocPolicy> SourceCompressionTaskVector;
     typedef Vector<GCHelperState*, 0, SystemAllocPolicy> GCHelperStateVector;
     typedef Vector<GCParallelTask*, 0, SystemAllocPolicy> GCParallelTaskVector;
     typedef Vector<PromiseTask*, 0, SystemAllocPolicy> PromiseTaskVector;
 
     // List of available threads, or null if the thread state has not been initialized.
     using HelperThreadVector = Vector<HelperThread, 0, SystemAllocPolicy>;
     UniquePtr<HelperThreadVector> threads;
 
@@ -160,17 +160,20 @@ class GlobalHelperThreadState
               mozilla::TimeDuration timeout = mozilla::TimeDuration::Forever());
     void notifyAll(CondVar which, const AutoLockHelperThreadState&);
     void notifyOne(CondVar which, const AutoLockHelperThreadState&);
 
     // Helper method for removing items from the vectors below while iterating over them.
     template <typename T>
     void remove(T& vector, size_t* index)
     {
-        vector[(*index)--] = vector.back();
+        // Self-moving is undefined behavior.
+        if (*index != vector.length() - 1)
+            vector[*index] = mozilla::Move(vector.back());
+        (*index)--;
         vector.popBack();
     }
 
     IonBuilderVector& ionWorklist(const AutoLockHelperThreadState&) {
         return ionWorklist_;
     }
     IonBuilderVector& ionFinishedList(const AutoLockHelperThreadState&) {
         return ionFinishedList_;
@@ -545,17 +548,17 @@ EnqueuePendingParseTasksAfterGC(JSRuntim
 struct AutoEnqueuePendingParseTasksAfterGC {
     const gc::GCRuntime& gc_;
     explicit AutoEnqueuePendingParseTasksAfterGC(const gc::GCRuntime& gc) : gc_(gc) {}
     ~AutoEnqueuePendingParseTasksAfterGC();
 };
 
 // Enqueue a compression job to be processed if there's a major GC.
 bool
-EnqueueOffThreadCompression(JSContext* cx, SourceCompressionTask* task);
+EnqueueOffThreadCompression(JSContext* cx, UniquePtr<SourceCompressionTask> task);
 
 // Cancel all scheduled, in progress, or finished compression tasks for
 // runtime.
 void
 CancelOffThreadCompressions(JSRuntime* runtime);
 
 class MOZ_RAII AutoLockHelperThreadState : public LockGuard<Mutex>
 {
@@ -618,20 +621,16 @@ struct ParseTask
     // Holds the final script between the invocation of the callback and the
     // point where FinishOffThreadScript is called, which will destroy the
     // ParseTask.
     JSScript* script;
 
     // Holds the ScriptSourceObject generated for the script compilation.
     ScriptSourceObject* sourceObject;
 
-    // Holds the SourceCompressionTask, if any were enqueued for the
-    // ScriptSource of sourceObject.
-    SourceCompressionTask* sourceCompressionTask;
-
     // Any errors or warnings produced during compilation. These are reported
     // when finishing the script.
     Vector<CompileError*, 0, SystemAllocPolicy> errors;
     bool overRecursed;
     bool outOfMemory;
 
     ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
               const char16_t* chars, size_t length,
@@ -699,54 +698,41 @@ class SourceCompressionTask
     friend struct HelperThread;
     friend class ScriptSource;
 
     // The runtime that the ScriptSource is associated with, in the sense that
     // it uses the runtime's immutable string cache.
     JSRuntime* runtime_;
 
     // The major GC number of the runtime when the task was enqueued.
-    static const uint64_t MajorGCNumberWaitingForFixup = UINT64_MAX;
     uint64_t majorGCNumber_;
 
     // The source to be compressed.
     ScriptSourceHolder sourceHolder_;
 
     // The resultant compressed string. If the compressed string is larger
     // than the original, or we OOM'd during compression, or nothing else
     // except the task is holding the ScriptSource alive when scheduled to
     // compress, this will remain None upon completion.
     mozilla::Maybe<SharedImmutableString> resultString_;
 
   public:
-    // The majorGCNumber is used for scheduling tasks. If the task is being
-    // enqueued from an off-thread parsing task, leave the GC number
-    // UINT64_MAX to be fixed up when the parse task finishes.
+    // The majorGCNumber is used for scheduling tasks.
     SourceCompressionTask(JSRuntime* rt, ScriptSource* source)
       : runtime_(rt),
-        majorGCNumber_(CurrentThreadCanAccessRuntime(rt)
-                       ? rt->gc.majorGCCount()
-                       : MajorGCNumberWaitingForFixup),
+        majorGCNumber_(rt->gc.majorGCCount()),
         sourceHolder_(source)
     { }
 
     bool runtimeMatches(JSRuntime* runtime) const {
         return runtime == runtime_;
     }
-
-    void fixupMajorGCNumber(JSRuntime* runtime) {
-        MOZ_ASSERT(majorGCNumber_ == MajorGCNumberWaitingForFixup);
-        majorGCNumber_ = runtime->gc.majorGCCount();
-    }
-
     bool shouldStart() const {
         // We wait 2 major GCs to start compressing, in order to avoid
         // immediate compression.
-        if (majorGCNumber_ == MajorGCNumberWaitingForFixup)
-            return false;
         return runtime_->gc.majorGCCount() > majorGCNumber_ + 1;
     }
 
     bool shouldCancel() const {
         // If the refcount is exactly 1, then nothing else is holding on to the
         // ScriptSource, so no reason to compress it and we should cancel the task.
         return sourceHolder_.get()->refs == 1;
     }