Bug 1277562 - Part 9: Add Wasm Tier 2 compilation tasks. r=luke
authorLars T Hansen <lhansen@mozilla.com>
Thu, 09 Feb 2017 15:15:17 +0100
changeset 417142 0f4d52995594cc5c2d302c11b34088e5e5174fb2
parent 417141 21129f558137d87de170fbed432ab4b6206b9ff3
child 417143 b8b7771cce0d66e6fbfec285a84ef1b7e2e3ff0d
push id75
push userfmarier@mozilla.com
push dateWed, 23 Aug 2017 01:08:28 +0000
reviewersluke
bugs1277562
milestone57.0a1
Bug 1277562 - Part 9: Add Wasm Tier 2 compilation tasks. r=luke
js/public/Utility.h
js/src/vm/HelperThreads.cpp
js/src/vm/HelperThreads.h
js/src/vm/MutexIDs.h
js/src/wasm/AsmJS.cpp
js/src/wasm/WasmCode.h
js/src/wasm/WasmCompile.cpp
js/src/wasm/WasmCompile.h
js/src/wasm/WasmGenerator.cpp
js/src/wasm/WasmGenerator.h
js/src/wasm/WasmJS.cpp
js/src/wasm/WasmModule.cpp
js/src/wasm/WasmModule.h
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -61,16 +61,17 @@ enum ThreadType {
     THREAD_TYPE_WASM,           // 2
     THREAD_TYPE_ION,            // 3
     THREAD_TYPE_PARSE,          // 4
     THREAD_TYPE_COMPRESS,       // 5
     THREAD_TYPE_GCHELPER,       // 6
     THREAD_TYPE_GCPARALLEL,     // 7
     THREAD_TYPE_PROMISE_TASK,   // 8
     THREAD_TYPE_ION_FREE,       // 9
+    THREAD_TYPE_WASM_TIER2,     // 10
     THREAD_TYPE_MAX             // Used to check shell function arguments
 };
 
 namespace oom {
 
 /*
  * Theads are tagged only in certain debug contexts.  Notably, to make testing
  * OOM in certain helper threads more effective, we allow restricting the OOM
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -97,16 +97,34 @@ js::StartOffThreadWasmCompile(wasm::Comp
     if (!HelperThreadState().wasmWorklist(lock, mode).append(task))
         return false;
 
     HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
     return true;
 }
 
 bool
+js::StartOffThreadWasmTier2Generator(wasm::Tier2GeneratorTask* task)
+{
+    AutoLockHelperThreadState lock;
+
+    if (!HelperThreadState().wasmTier2GeneratorWorklist(lock).append(task))
+        return false;
+
+    HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
+    return true;
+}
+
+void
+js::CancelOffThreadWasmTier2Generator()
+{
+    // TODO: Implement this
+}
+
+bool
 js::StartOffThreadIonCompile(JSContext* cx, jit::IonBuilder* builder)
 {
     AutoLockHelperThreadState lock;
 
     if (!HelperThreadState().ionWorklist(lock).append(builder))
         return false;
 
     HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
@@ -875,16 +893,17 @@ GlobalHelperThreadState::GlobalHelperThr
     threadCount = ThreadCountForCPUCount(cpuCount);
 
     MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
 }
 
 void
 GlobalHelperThreadState::finish()
 {
+    CancelOffThreadWasmTier2Generator();
     finishThreads();
 
     // Make sure there are no Ion free tasks left. We check this here because,
     // unlike the other tasks, we don't explicitly block on this when
     // destroying a runtime.
     AutoLockHelperThreadState lock;
     auto& freeList = ionFreeList(lock);
     while (!freeList.empty())
@@ -955,16 +974,17 @@ GlobalHelperThreadState::hasActiveThread
 
     return false;
 }
 
 void
 GlobalHelperThreadState::waitForAllThreads()
 {
     CancelOffThreadIonCompile();
+    CancelOffThreadWasmTier2Generator();
 
     AutoLockHelperThreadState lock;
     while (hasActiveThreads(lock))
         wait(lock, CONSUMER);
 }
 
 // A task can be a "master" task, ie, it will block waiting for other worker
 // threads that perform work on its behalf.  If so it must not take the last
@@ -1057,16 +1077,22 @@ size_t
 GlobalHelperThreadState::maxWasmCompilationThreads() const
 {
     if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_WASM))
         return 1;
     return cpuCount;
 }
 
 size_t
+GlobalHelperThreadState::maxWasmTier2GeneratorThreads() const
+{
+    return MaxTier2GeneratorTasks;
+}
+
+size_t
 GlobalHelperThreadState::maxParseThreads() const
 {
     if (IsHelperThreadSimulatingOOM(js::THREAD_TYPE_PARSE))
         return 1;
 
     // Don't allow simultaneous off thread parses, to reduce contention on the
     // atoms table. Note that wasm compilation depends on this to avoid
     // stalling the helper thread, as off thread parse tasks can trigger and
@@ -1104,25 +1130,57 @@ GlobalHelperThreadState::maxGCParallelTh
 bool
 GlobalHelperThreadState::canStartWasmCompile(const AutoLockHelperThreadState& lock,
                                              wasm::CompileMode mode)
 {
     // Don't execute a wasm job if an earlier one failed.
     if (wasmWorklist(lock, mode).empty() || wasmFailed(lock, mode))
         return false;
 
-    // Honor the maximum allowed threads to compile wasm jobs at once,
-    // to avoid oversaturating the machine.
-    if (!checkTaskThreadLimit<wasm::CompileTask*>(maxWasmCompilationThreads()))
+    // For Tier1 and Once compilation, honor the maximum allowed threads to
+    // compile wasm jobs at once, to avoid oversaturating the machine.
+    //
+    // For Tier2 compilation we need to allow other things to happen too, so for
+    // now we only allow one thread.
+    //
+    // TODO: We should investigate more intelligent strategies, see bug 1380033.
+    //
+    // If Tier2 is very backlogged we must give priority to it, since the Tier2
+    // queue holds onto Tier1 tasks.  Indeed if Tier2 is backlogged we will
+    // devote more resources to Tier2 and not start any Tier1 work at all.
+
+    bool tier2oversubscribed = wasmTier2GeneratorWorklist(lock).length() > 20;
+
+    size_t threads;
+    if (mode == wasm::CompileMode::Tier2) {
+        if (tier2oversubscribed)
+            threads = maxWasmCompilationThreads();
+        else
+            threads = 1;
+    } else {
+        if (tier2oversubscribed)
+            threads = 0;
+        else
+            threads = maxWasmCompilationThreads();
+    }
+
+    if (!threads || !checkTaskThreadLimit<wasm::CompileTask*>(threads))
         return false;
 
     return true;
 }
 
 bool
+GlobalHelperThreadState::canStartWasmTier2Generator(const AutoLockHelperThreadState& lock)
+{
+    return !wasmTier2GeneratorWorklist(lock).empty() &&
+           checkTaskThreadLimit<wasm::Tier2GeneratorTask*>(maxWasmTier2GeneratorThreads());
+}
+
+bool
 GlobalHelperThreadState::canStartPromiseHelperTask(const AutoLockHelperThreadState& lock)
 {
     return !promiseHelperTasks(lock).empty();
 }
 
 static bool
 IonBuilderHasHigherPriority(jit::IonBuilder* first, jit::IonBuilder* second)
 {
@@ -1754,16 +1812,44 @@ HelperThread::handleWasmWorkload(AutoLoc
     }
 
     // Notify the active thread in case it's waiting.
     HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
     currentTask.reset();
 }
 
 void
+HelperThread::handleWasmTier2GeneratorWorkload(AutoLockHelperThreadState& locked)
+{
+    MOZ_ASSERT(HelperThreadState().canStartWasmTier2Generator(locked));
+    MOZ_ASSERT(idle());
+
+    currentTask.emplace(HelperThreadState().wasmTier2GeneratorWorklist(locked).popCopy());
+    bool success = false;
+
+    wasm::Tier2GeneratorTask* task = wasmTier2GeneratorTask();
+    {
+        AutoUnlockHelperThreadState unlock(locked);
+        success = wasm::GenerateTier2(task);
+    }
+
+    // We silently ignore failures.  Such failures must be resource exhaustion,
+    // because all error checking was performed by the initial compilation.
+    mozilla::Unused << success;
+
+    // During shutdown the main thread will wait for any ongoing (cancelled)
+    // tier-2 generation to shut down normally.  To do so, it waits on the
+    // CONSUMER condition for the count of finished generators to rise.
+    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
+
+    wasm::DeleteTier2GeneratorTask(task);
+    currentTask.reset();
+}
+
+void
 HelperThread::handlePromiseHelperTaskWorkload(AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().canStartPromiseHelperTask(locked));
     MOZ_ASSERT(idle());
 
     PromiseHelperTask* task = HelperThreadState().promiseHelperTasks(locked).popCopy();
     currentTask.emplace(task);
 
@@ -2200,16 +2286,18 @@ HelperThread::threadLoop()
                 task = js::THREAD_TYPE_PARSE;
             } else if (HelperThreadState().canStartCompressionTask(lock)) {
                 task = js::THREAD_TYPE_COMPRESS;
             } else if (HelperThreadState().canStartIonFreeTask(lock)) {
                 task = js::THREAD_TYPE_ION_FREE;
             } else if (HelperThreadState().canStartWasmCompile(lock, wasm::CompileMode::Tier2)) {
                 task = js::THREAD_TYPE_WASM;
                 tier = wasm::CompileMode::Tier2;
+            } else if (HelperThreadState().canStartWasmTier2Generator(lock)) {
+                task = js::THREAD_TYPE_WASM_TIER2;
             } else {
                 task = js::THREAD_TYPE_NONE;
             }
 
             if (task != js::THREAD_TYPE_NONE)
                 break;
 
             HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER);
@@ -2236,14 +2324,17 @@ HelperThread::threadLoop()
             handleParseWorkload(lock);
             break;
           case js::THREAD_TYPE_COMPRESS:
             handleCompressionWorkload(lock);
             break;
           case js::THREAD_TYPE_ION_FREE:
             handleIonFreeWorkload(lock);
             break;
+          case js::THREAD_TYPE_WASM_TIER2:
+            handleWasmTier2GeneratorWorkload(lock);
+            break;
           default:
             MOZ_CRASH("No task to perform");
         }
         js::oom::SetThreadType(js::THREAD_TYPE_NONE);
     }
 }
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -41,17 +41,19 @@ struct ParseTask;
 struct PromiseHelperTask;
 namespace jit {
   class IonBuilder;
 } // namespace jit
 namespace wasm {
   class FuncIR;
   class FunctionCompileResults;
   class CompileTask;
+  struct Tier2GeneratorTask;
   typedef Vector<CompileTask*, 0, SystemAllocPolicy> CompileTaskPtrVector;
+  typedef Vector<Tier2GeneratorTask*, 0, SystemAllocPolicy> Tier2GeneratorTaskPtrVector;
 } // namespace wasm
 
 enum class ParseTaskKind
 {
     Script,
     Module,
     ScriptDecode,
     MultiScriptsDecode
@@ -59,16 +61,20 @@ enum class ParseTaskKind
 
 // Per-process state for off thread work items.
 class GlobalHelperThreadState
 {
     friend class AutoLockHelperThreadState;
     friend class AutoUnlockHelperThreadState;
 
   public:
+    // A single tier-2 ModuleGenerator job spawns many compilation jobs, and we
+    // do not want to allow more than one such ModuleGenerator to run at a time.
+    static const size_t MaxTier2GeneratorTasks = 1;
+
     // Number of CPUs to treat this machine as having when creating threads.
     // 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;
@@ -86,16 +92,17 @@ class GlobalHelperThreadState
     // The lists below are all protected by |lock|.
 
     // Ion compilation worklist and finished jobs.
     IonBuilderVector ionWorklist_, ionFinishedList_, ionFreeList_;
 
     // wasm worklist and finished jobs.
     wasm::CompileTaskPtrVector wasmWorklist_tier1_, wasmFinishedList_tier1_;
     wasm::CompileTaskPtrVector wasmWorklist_tier2_, wasmFinishedList_tier2_;
+    wasm::Tier2GeneratorTaskPtrVector wasmTier2GeneratorWorklist_;
 
     // For now, only allow a single parallel wasm compilation at each tier to
     // happen at a time.  This avoids race conditions on
     // wasmWorklist/wasmFinishedList/etc, for which there is one for each tier.
     //
     // TODO: remove this restriction by making the work and finish lists
     // per-compilation state, not part of the global state.
 
@@ -142,16 +149,17 @@ class GlobalHelperThreadState
     GCParallelTaskVector gcParallelWorklist_;
 
     ParseTask* removeFinishedParseTask(ParseTaskKind kind, void* token);
 
   public:
     size_t maxIonCompilationThreads() const;
     size_t maxUnpausedIonCompilationThreads() const;
     size_t maxWasmCompilationThreads() const;
+    size_t maxWasmTier2GeneratorThreads() const;
     size_t maxParseThreads() const;
     size_t maxCompressionThreads() const;
     size_t maxGCHelperThreads() const;
     size_t maxGCParallelThreads() const;
 
     GlobalHelperThreadState();
 
     bool ensureInitialized();
@@ -224,16 +232,20 @@ class GlobalHelperThreadState
             return wasmFinishedList_tier1_;
           case wasm::CompileMode::Tier2:
             return wasmFinishedList_tier2_;
           default:
             MOZ_CRASH();
         }
     }
 
+    wasm::Tier2GeneratorTaskPtrVector& wasmTier2GeneratorWorklist(const AutoLockHelperThreadState&) {
+        return wasmTier2GeneratorWorklist_;
+    }
+
     PromiseHelperTaskVector& promiseHelperTasks(const AutoLockHelperThreadState&) {
         return promiseHelperTasks_;
     }
 
     ParseTaskVector& parseWorklist(const AutoLockHelperThreadState&) {
         return parseWorklist_;
     }
     ParseTaskVector& parseFinishedList(const AutoLockHelperThreadState&) {
@@ -259,16 +271,17 @@ class GlobalHelperThreadState
         return gcHelperWorklist_;
     }
 
     GCParallelTaskVector& gcParallelWorklist(const AutoLockHelperThreadState&) {
         return gcParallelWorklist_;
     }
 
     bool canStartWasmCompile(const AutoLockHelperThreadState& lock, wasm::CompileMode mode);
+    bool canStartWasmTier2Generator(const AutoLockHelperThreadState& lock);
     bool canStartPromiseHelperTask(const AutoLockHelperThreadState& lock);
     bool canStartIonCompile(const AutoLockHelperThreadState& lock);
     bool canStartIonFreeTask(const AutoLockHelperThreadState& lock);
     bool canStartParseTask(const AutoLockHelperThreadState& lock);
     bool canStartCompressionTask(const AutoLockHelperThreadState& lock);
     bool canStartGCHelperTask(const AutoLockHelperThreadState& lock);
     bool canStartGCParallelTask(const AutoLockHelperThreadState& lock);
 
@@ -435,16 +448,17 @@ HelperThreadState()
     extern GlobalHelperThreadState* gHelperThreadState;
 
     MOZ_ASSERT(gHelperThreadState);
     return *gHelperThreadState;
 }
 
 typedef mozilla::Variant<jit::IonBuilder*,
                          wasm::CompileTask*,
+                         wasm::Tier2GeneratorTask*,
                          PromiseHelperTask*,
                          ParseTask*,
                          SourceCompressionTask*,
                          GCHelperState*,
                          GCParallelTask*> HelperTaskUnion;
 
 /* Individual helper thread, one allocated per core. */
 struct HelperThread
@@ -476,16 +490,20 @@ struct HelperThread
         return maybeCurrentTaskAs<jit::IonBuilder*>();
     }
 
     /* Any wasm data currently being optimized on this thread. */
     wasm::CompileTask* wasmTask() {
         return maybeCurrentTaskAs<wasm::CompileTask*>();
     }
 
+    wasm::Tier2GeneratorTask* wasmTier2GeneratorTask() {
+        return maybeCurrentTaskAs<wasm::Tier2GeneratorTask*>();
+    }
+
     /* Any source being parsed/emitted on this thread. */
     ParseTask* parseTask() {
         return maybeCurrentTaskAs<ParseTask*>();
     }
 
     /* Any source being compressed on this thread. */
     SourceCompressionTask* compressionTask() {
         return maybeCurrentTaskAs<SourceCompressionTask*>();
@@ -511,16 +529,17 @@ struct HelperThread
     T maybeCurrentTaskAs() {
         if (currentTask.isSome() && currentTask->is<T>())
             return currentTask->as<T>();
 
         return nullptr;
     }
 
     void handleWasmWorkload(AutoLockHelperThreadState& locked, wasm::CompileMode mode);
+    void handleWasmTier2GeneratorWorkload(AutoLockHelperThreadState& locked);
     void handlePromiseHelperTaskWorkload(AutoLockHelperThreadState& locked);
     void handleIonWorkload(AutoLockHelperThreadState& locked);
     void handleIonFreeWorkload(AutoLockHelperThreadState& locked);
     void handleParseWorkload(AutoLockHelperThreadState& locked);
     void handleCompressionWorkload(AutoLockHelperThreadState& locked);
     void handleGCHelperWorkload(AutoLockHelperThreadState& locked);
     void handleGCParallelWorkload(AutoLockHelperThreadState& locked);
 };
@@ -551,22 +570,35 @@ CurrentHelperThread();
 // Pause the current thread until it's pause flag is unset.
 void
 PauseCurrentHelperThread();
 
 // Enqueues a wasm compilation task.
 bool
 StartOffThreadWasmCompile(wasm::CompileTask* task, wasm::CompileMode mode);
 
+// Enqueues a wasm compilation task.
+bool
+StartOffThreadWasmTier2Generator(wasm::Tier2GeneratorTask* task);
+
+// Cancel all background Wasm Tier-2 compilations.
+void
+CancelOffThreadWasmTier2Generator();
+
 namespace wasm {
 
 // Performs MIR optimization and LIR generation on one or several functions.
 MOZ_MUST_USE bool
 CompileFunction(CompileTask* task, UniqueChars* error);
 
+MOZ_MUST_USE bool
+GenerateTier2(Tier2GeneratorTask* task);
+
+void
+DeleteTier2GeneratorTask(Tier2GeneratorTask* task);
 }
 
 /*
  * If helper threads are available, call execute() then dispatchResolve() on the
  * given task in a helper thread. If no helper threads are available, the given
  * task is executed and resolved synchronously.
  */
 bool
--- a/js/src/vm/MutexIDs.h
+++ b/js/src/vm/MutexIDs.h
@@ -38,16 +38,17 @@
   _(PerfSpewer,                  500) \
   _(CacheIRSpewer,               500) \
   _(TraceLoggerThreadState,      500) \
   _(DateTimeInfoMutex,           500) \
   _(IcuTimeZoneStateMutex,       500) \
   _(ProcessExecutableRegion,     500) \
   _(WasmCodeProfilingLabels,     500) \
   _(OffThreadPromiseState,       500) \
+  _(WasmTier2GeneratorComplete,  500) \
                                       \
   _(TraceLoggerGraphState,       600) \
   _(VTuneLock,                   600)
 
 namespace js {
 namespace mutexid {
 
 #define DEFINE_MUTEX_ID(name, order)  \
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -1858,34 +1858,34 @@ class MOZ_STACK_CLASS ModuleValidator
         ScriptedCaller scriptedCaller;
         if (parser_.ss->filename()) {
             scriptedCaller.line = scriptedCaller.column = 0;  // unused
             scriptedCaller.filename = DuplicateString(parser_.ss->filename());
             if (!scriptedCaller.filename)
                 return false;
         }
 
-        CompileArgs args;
-        if (!args.initFromContext(cx_, Move(scriptedCaller)))
+        MutableCompileArgs args = cx_->new_<CompileArgs>();
+        if (!args || !args->initFromContext(cx_, Move(scriptedCaller)))
             return false;
 
         auto env = MakeUnique<ModuleEnvironment>(ModuleKind::AsmJS);
         if (!env ||
             !env->sigs.resize(AsmJSMaxTypes) ||
             !env->funcSigs.resize(AsmJSMaxFuncs) ||
             !env->funcImportGlobalDataOffsets.resize(AsmJSMaxImports) ||
             !env->tables.resize(AsmJSMaxTables) ||
             !env->asmJSSigToTableIndex.resize(AsmJSMaxTypes))
         {
             return false;
         }
 
         env->minMemoryLength = RoundUpToNextValidAsmJSHeapLength(0);
 
-        if (!mg_.init(Move(env), args, CompileMode::Once, asmJSMetadata_.get()))
+        if (!mg_.init(Move(env), *args, CompileMode::Once, asmJSMetadata_.get()))
             return false;
 
         return true;
     }
 
     JSContext* cx() const                    { return cx_; }
     PropertyName* moduleFunctionName() const { return moduleFunctionName_; }
     PropertyName* globalArgumentName() const { return globalArgumentName_; }
@@ -2397,21 +2397,21 @@ class MOZ_STACK_CLASS ModuleValidator
 
         TokenPos pos;
         JS_ALWAYS_TRUE(tokenStream().peekTokenPos(&pos, TokenStream::Operand));
         uint32_t endAfterCurly = pos.end;
         asmJSMetadata_->srcLengthWithRightBrace = endAfterCurly - asmJSMetadata_->srcStart;
 
         // asm.js does not have any wasm bytecode to save; view-source is
         // provided through the ScriptSource.
-        SharedBytes bytes = js_new<ShareableBytes>();
+        SharedBytes bytes = cx_->new_<ShareableBytes>();
         if (!bytes)
             return nullptr;
 
-        return mg_.finish(*bytes);
+        return mg_.finishModule(*bytes);
     }
 };
 
 /*****************************************************************************/
 // Numeric literal utilities
 
 static bool
 IsNumericNonFloatLiteral(ParseNode* pn)
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -388,16 +388,22 @@ class Metadata : public ShareableBase<Me
     void commitTier2() const;
     bool hasTier2() const { return hasTier2_; }
     void setTier2(UniqueMetadataTier metadata) const;
     Tiers tiers() const;
 
     const MetadataTier& metadata(Tier t) const;
     MetadataTier& metadata(Tier t);
 
+    UniquePtr<MetadataTier> takeMetadata(Tier tier) {
+        MOZ_ASSERT(!hasTier2());
+        MOZ_ASSERT(metadata1_->tier == tier);
+        return Move(metadata1_);
+    }
+
     SigWithIdVector       sigIds;
     GlobalDescVector      globals;
     TableDescVector       tables;
     NameInBytecodeVector  funcNames;
     CustomSectionVector   customSections;
     CacheableChars        filename;
     ModuleHash            hash;
 
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -13,16 +13,19 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "wasm/WasmCompile.h"
 
+#include "mozilla/Maybe.h"
+#include "mozilla/Unused.h"
+
 #include "jsprf.h"
 
 #include "wasm/WasmBaselineCompile.h"
 #include "wasm/WasmBinaryIterator.h"
 #include "wasm/WasmGenerator.h"
 #include "wasm/WasmSignalHandlers.h"
 #include "wasm/WasmValidate.h"
 
@@ -120,32 +123,38 @@ CompilerAvailability(ModuleKind kind, co
     // that don't have Ion at all, so this can happen if the user has disabled
     // both compilers or if she has disabled Ion but baseline can't compile the
     // code.
 
     if (!(*baselineEnabled || *ionEnabled))
         *ionEnabled = true;
 }
 
+static bool
+BackgroundWorkPossible()
+{
+    return CanUseExtraThreads() && HelperThreadState().cpuCount > 1;
+}
+
 bool
 wasm::GetDebugEnabled(const CompileArgs& args, ModuleKind kind)
 {
     bool baselineEnabled, debugEnabled, ionEnabled;
     CompilerAvailability(kind, args, &baselineEnabled, &debugEnabled, &ionEnabled);
 
     return debugEnabled;
 }
 
 wasm::CompileMode
 wasm::GetInitialCompileMode(const CompileArgs& args, ModuleKind kind)
 {
     bool baselineEnabled, debugEnabled, ionEnabled;
     CompilerAvailability(kind, args, &baselineEnabled, &debugEnabled, &ionEnabled);
 
-    return (baselineEnabled && ionEnabled && !debugEnabled)
+    return BackgroundWorkPossible() && baselineEnabled && ionEnabled && !debugEnabled
            ? CompileMode::Tier1
            : CompileMode::Once;
 }
 
 wasm::Tier
 wasm::GetTier(const CompileArgs& args, CompileMode compileMode, ModuleKind kind)
 {
     bool baselineEnabled, debugEnabled, ionEnabled;
@@ -163,38 +172,111 @@ wasm::GetTier(const CompileArgs& args, C
       case CompileMode::Once:
         return (debugEnabled || !ionEnabled) ? Tier::Baseline : Tier::Ion;
 
       default:
         MOZ_CRASH("Bad mode");
     }
 }
 
+namespace js {
+namespace wasm {
+
+struct Tier2GeneratorTask
+{
+    // The module that wants the results of the compilation
+    SharedModule            module;
+
+    // The arguments for the compilation
+    SharedCompileArgs       compileArgs;
+
+    Tier2GeneratorTask(Module& module, const CompileArgs& compileArgs)
+      : module(&module),
+        compileArgs(&compileArgs)
+    {}
+};
+
+}
+}
+
+static bool
+Compile(ModuleGenerator& mg, const ShareableBytes& bytecode, const CompileArgs& args,
+        UniqueChars* error, CompileMode compileMode)
+{
+    auto env = js::MakeUnique<ModuleEnvironment>();
+    if (!env)
+        return false;
+
+    Decoder d(bytecode.bytes, error);
+    if (!DecodeModuleEnvironment(d, env.get()))
+        return false;
+
+    if (!mg.init(Move(env), args, compileMode))
+        return false;
+
+    if (!DecodeCodeSection(d, mg))
+        return false;
+
+    if (!DecodeModuleTail(d, &mg.mutableEnv()))
+        return false;
+
+    return true;
+}
+
+
 SharedModule
 wasm::Compile(const ShareableBytes& bytecode, const CompileArgs& args, UniqueChars* error)
 {
     MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
 
-    Decoder d(bytecode.bytes, error);
-
-    auto env = js::MakeUnique<ModuleEnvironment>();
-    if (!env)
-        return nullptr;
-
-    if (!DecodeModuleEnvironment(d, env.get()))
-        return nullptr;
+    ModuleGenerator mg(error);
 
-    CompileMode compileMode = GetInitialCompileMode(args);
-
-    ModuleGenerator mg(error);
-    if (!mg.init(Move(env), args, compileMode))
-        return nullptr;
-
-    if (!DecodeCodeSection(d, mg))
-        return nullptr;
-
-    if (!DecodeModuleTail(d, &mg.mutableEnv()))
+    CompileMode mode = GetInitialCompileMode(args);
+    if (!::Compile(mg, bytecode, args, error, mode))
         return nullptr;
 
     MOZ_ASSERT(!*error, "unreported error in decoding");
 
-    return mg.finish(bytecode);
+    SharedModule module = mg.finishModule(bytecode);
+    if (!module)
+        return nullptr;
+
+    if (mode == CompileMode::Tier1) {
+        MOZ_ASSERT(BackgroundWorkPossible());
+
+        auto task = js::MakeUnique<Tier2GeneratorTask>(*module, args);
+        if (!task) {
+            module->unblockOnTier2GeneratorFinished(CompileMode::Once);
+            return nullptr;
+        }
+
+        if (!StartOffThreadWasmTier2Generator(&*task)) {
+            module->unblockOnTier2GeneratorFinished(CompileMode::Once);
+            return nullptr;
+        }
+
+        mozilla::Unused << task.release();
+    }
+
+    return module;
 }
+
+// This runs on a helper thread.
+bool
+wasm::GenerateTier2(Tier2GeneratorTask* task)
+{
+    UniqueChars     error;
+    ModuleGenerator mg(&error);
+
+    bool res =
+        ::Compile(mg, task->module->bytecode(), *task->compileArgs, &error, CompileMode::Tier2) &&
+        mg.finishTier2(task->module->bytecode(), task->module);
+
+    task->module->unblockOnTier2GeneratorFinished(res ? CompileMode::Tier2 : CompileMode::Once);
+
+    return res;
+}
+
+void
+wasm::DeleteTier2GeneratorTask(Tier2GeneratorTask* task)
+{
+    js_delete(task);
+}
--- a/js/src/wasm/WasmCompile.h
+++ b/js/src/wasm/WasmCompile.h
@@ -30,17 +30,17 @@ struct ScriptedCaller
 {
     UniqueChars filename;
     unsigned line;
     unsigned column;
 };
 
 // Describes all the parameters that control wasm compilation.
 
-struct CompileArgs
+struct CompileArgs : ShareableBase<CompileArgs>
 {
     Assumptions assumptions;
     ScriptedCaller scriptedCaller;
     bool baselineEnabled;
     bool debugEnabled;
     bool ionEnabled;
 
     CompileArgs(Assumptions&& assumptions, ScriptedCaller&& scriptedCaller)
@@ -52,16 +52,19 @@ struct CompileArgs
     {}
 
     // If CompileArgs is constructed without arguments, initFromContext() must
     // be called to complete initialization.
     CompileArgs() = default;
     bool initFromContext(JSContext* cx, ScriptedCaller&& scriptedCaller);
 };
 
+typedef RefPtr<CompileArgs> MutableCompileArgs;
+typedef RefPtr<const CompileArgs> SharedCompileArgs;
+
 // Compile the given WebAssembly bytecode with the given arguments into a
 // wasm::Module. On success, the Module is returned. On failure, the returned
 // SharedModule pointer is null and either:
 //  - *error points to a string description of the error
 //  - *error is null and the caller should report out-of-memory.
 
 SharedModule
 Compile(const ShareableBytes& bytecode, const CompileArgs& args, UniqueChars* error);
@@ -70,20 +73,21 @@ Compile(const ShareableBytes& bytecode, 
 // configuration options, and the nature of the module.  Note debugging can be
 // unavailable even if selected, if Rabaldr is unavailable or the module is not
 // compilable by Rabaldr.
 
 bool
 GetDebugEnabled(const CompileArgs& args, ModuleKind kind = ModuleKind::Wasm);
 
 // Select the mode for the initial compilation of a module.  The mode is "Tier1"
-// precisely if both compilers are available and we're not debugging, and in
-// that case, we'll compile twice, with the mode set to "Tier2" during the
-// second compilation.  Otherwise, the tier is "Once" and we'll compile once,
-// with the appropriate compiler.
+// precisely if both compilers are available, we're not debugging, and it is
+// possible to compile in the background, and in that case, we'll compile twice,
+// with the mode set to "Tier2" during the second (background) compilation.
+// Otherwise, the tier is "Once" and we'll compile once, with the appropriate
+// compiler.
 
 CompileMode
 GetInitialCompileMode(const CompileArgs& args, ModuleKind kind = ModuleKind::Wasm);
 
 // Select the tier for a compilation.  The tier is Tier::Baseline if we're
 // debugging, if Baldr is not available, or if both compilers are are available
 // and the compileMode is Tier1; otherwise the tier is Tier::Ion.
 
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -1128,32 +1128,23 @@ ModuleGenerator::generateBytecodeHash(co
 {
     mozilla::SHA1Sum::Hash hash;
     mozilla::SHA1Sum sha1Sum;
     sha1Sum.update(bytecode.begin(), bytecode.length());
     sha1Sum.finish(hash);
     memcpy(metadata_->hash, hash, sizeof(ModuleHash));
 }
 
-SharedModule
-ModuleGenerator::finish(const ShareableBytes& bytecode)
+bool
+ModuleGenerator::finishMetadata(const ShareableBytes& bytecode)
 {
-    MOZ_ASSERT(!activeFuncDef_);
-    MOZ_ASSERT(finishedFuncDefs_);
-
-    if (!finishFuncExports())
-        return nullptr;
-
-    if (!finishCodegen())
-        return nullptr;
-
     // Convert the CallSiteAndTargetVector (needed during generation) to a
     // CallSiteVector (what is stored in the Module).
     if (!metadataTier_->callSites.appendAll(masm_.callSites()))
-        return nullptr;
+        return false;
 
     // The MacroAssembler has accumulated all the memory accesses during codegen.
     metadataTier_->memoryAccesses = masm_.extractMemoryAccesses();
 
     // Copy over data from the ModuleEnvironment.
     metadata_->memoryUsage = env_->memoryUsage;
     metadata_->minMemoryLength = env_->minMemoryLength;
     metadata_->maxMemoryLength = env_->maxMemoryLength;
@@ -1174,17 +1165,37 @@ ModuleGenerator::finish(const ShareableB
     metadataTier_->codeRanges.podResizeToFit();
     metadataTier_->callSites.podResizeToFit();
     metadataTier_->debugTrapFarJumpOffsets.podResizeToFit();
     metadataTier_->debugFuncToCodeRange.podResizeToFit();
 
     // For asm.js, the tables vector is over-allocated (to avoid resize during
     // parallel copilation). Shrink it back down to fit.
     if (isAsmJS() && !metadata_->tables.resize(numTables_))
-        return nullptr;
+        return false;
+
+    generateBytecodeHash(bytecode);
+
+    return true;
+}
+
+bool
+ModuleGenerator::finishCommon(const ShareableBytes& bytecode)
+{
+    MOZ_ASSERT(!activeFuncDef_);
+    MOZ_ASSERT(finishedFuncDefs_);
+
+    if (!finishFuncExports())
+        return false;
+
+    if (!finishCodegen())
+        return false;
+
+    if (!finishMetadata(bytecode))
+        return false;
 
     // Assert CodeRanges are sorted.
 #ifdef DEBUG
     uint32_t lastEnd = 0;
     for (const CodeRange& codeRange : metadataTier_->codeRanges) {
         MOZ_ASSERT(codeRange.begin() >= lastEnd);
         lastEnd = codeRange.end();
     }
@@ -1195,19 +1206,28 @@ ModuleGenerator::finish(const ShareableB
     uint32_t lastOffset = 0;
     for (uint32_t debugTrapFarJumpOffset : metadataTier_->debugTrapFarJumpOffsets) {
         MOZ_ASSERT(debugTrapFarJumpOffset >= lastOffset);
         lastOffset = debugTrapFarJumpOffset;
     }
 #endif
 
     if (!finishLinkData())
-        return nullptr;
+        return false;
+
+    return true;
+}
 
-    generateBytecodeHash(bytecode);
+SharedModule
+ModuleGenerator::finishModule(const ShareableBytes& bytecode)
+{
+    MOZ_ASSERT(compileMode_ == CompileMode::Once || compileMode_ == CompileMode::Tier1);
+
+    if (!finishCommon(bytecode))
+        return nullptr;
 
     UniqueConstCodeSegment codeSegment = CodeSegment::create(tier_,
                                                              masm_,
                                                              bytecode,
                                                              *linkDataTier_,
                                                              *metadata_);
     if (!codeSegment)
         return nullptr;
@@ -1222,28 +1242,54 @@ ModuleGenerator::finish(const ShareableB
         if (!maybeDebuggingBytes)
             return nullptr;
     }
 
     SharedCode code = js_new<Code>(Move(codeSegment), *metadata_);
     if (!code)
         return nullptr;
 
-    return SharedModule(js_new<Module>(Move(assumptions_),
+    return SharedModule(js_new<Module>(compileMode_,
+                                       Move(assumptions_),
                                        *code,
                                        Move(maybeDebuggingBytes),
                                        Move(linkData_),
                                        Move(env_->imports),
                                        Move(env_->exports),
                                        Move(env_->dataSegments),
                                        Move(env_->elemSegments),
                                        bytecode));
 }
 
 bool
+ModuleGenerator::finishTier2(const ShareableBytes& bytecode, SharedModule module)
+{
+    MOZ_ASSERT(compileMode_ == CompileMode::Tier2);
+
+    if (!finishCommon(bytecode))
+        return false;
+
+    UniqueConstCodeSegment codeSegment = CodeSegment::create(tier_,
+                                                             masm_,
+                                                             bytecode,
+                                                             *linkDataTier_,
+                                                             *metadata_);
+    if (!codeSegment)
+        return false;
+
+    MOZ_ASSERT(!metadata_->debugEnabled);
+
+    module->finishTier2Generator(linkData_.takeLinkData(tier_),
+                                 metadata_->takeMetadata(tier_),
+                                 Move(codeSegment),
+                                 Move(env_));
+    return true;
+}
+
+bool
 wasm::CompileFunction(CompileTask* task, UniqueChars* error)
 {
     TraceLoggerThread* logger = TraceLoggerForCurrentThread();
     AutoTraceLog logCompile(logger, TraceLogger_WasmCompilation);
 
     switch (task->tier()) {
       case Tier::Ion:
         for (FuncCompileUnit& unit : task->units()) {
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -196,16 +196,18 @@ class CompileTask
         alloc_.reset();
         lifo_.releaseAll();
 
         init();
         return true;
     }
 };
 
+struct Tier2GeneratorTask;
+
 // A ModuleGenerator encapsulates the creation of a wasm module. During the
 // lifetime of a ModuleGenerator, a sequence of FunctionGenerators are created
 // and destroyed to compile the individual function bodies. After generating all
 // functions, ModuleGenerator::finish() must be called to complete the
 // compilation and extract the resulting wasm module.
 
 class MOZ_STACK_CLASS ModuleGenerator
 {
@@ -263,16 +265,18 @@ class MOZ_STACK_CLASS ModuleGenerator
     MOZ_MUST_USE bool patchCallSites();
     MOZ_MUST_USE bool patchFarJumps(const TrapExitOffsetArray& trapExits, const Offsets& debugTrapStub);
     MOZ_MUST_USE bool finishTask(CompileTask* task);
     MOZ_MUST_USE bool finishOutstandingTask();
     MOZ_MUST_USE bool finishFuncExports();
     MOZ_MUST_USE bool finishCodegen();
     MOZ_MUST_USE bool finishLinkData();
     void generateBytecodeHash(const ShareableBytes& bytecode);
+    MOZ_MUST_USE bool finishMetadata(const ShareableBytes& bytecode);
+    MOZ_MUST_USE bool finishCommon(const ShareableBytes& bytecode);
     MOZ_MUST_USE bool addFuncImport(const Sig& sig, uint32_t globalDataOffset);
     MOZ_MUST_USE bool allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOff);
     MOZ_MUST_USE bool allocateGlobal(GlobalDesc* global);
 
     MOZ_MUST_USE bool launchBatchCompile();
 
     MOZ_MUST_USE bool initAsmJS(Metadata* asmJSMetadata);
     MOZ_MUST_USE bool initWasm(const CompileArgs& args);
@@ -323,17 +327,21 @@ class MOZ_STACK_CLASS ModuleGenerator
     MOZ_MUST_USE bool initSigTableLength(uint32_t sigIndex, uint32_t length);
     MOZ_MUST_USE bool initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncIndices);
     void initMemoryUsage(MemoryUsage memoryUsage);
     void bumpMinMemoryLength(uint32_t newMinMemoryLength);
     MOZ_MUST_USE bool addGlobal(ValType type, bool isConst, uint32_t* index);
     MOZ_MUST_USE bool addExport(CacheableChars&& fieldChars, uint32_t funcIndex);
 
     // Finish compilation of the given bytecode.
-    SharedModule finish(const ShareableBytes& bytecode);
+    SharedModule finishModule(const ShareableBytes& bytecode);
+
+    // Finish compilation of the given bytecode, installing tier-variant parts
+    // for Tier 2 into module.
+    MOZ_MUST_USE bool finishTier2(const ShareableBytes& bytecode, SharedModule module);
 };
 
 // A FunctionGenerator encapsulates the generation of a single function body.
 // ModuleGenerator::startFuncDef must be called after construction and before
 // doing anything else.
 //
 // After the body is complete, ModuleGenerator::finishFuncDef must be called
 // before the FunctionGenerator is destroyed and the next function is started.
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -380,22 +380,22 @@ wasm::Eval(JSContext* cx, Handle<TypedAr
         ReportOutOfMemory(cx);
         return false;
     }
 
     ScriptedCaller scriptedCaller;
     if (!DescribeScriptedCaller(cx, &scriptedCaller))
         return false;
 
-    CompileArgs compileArgs;
-    if (!compileArgs.initFromContext(cx, Move(scriptedCaller)))
+    MutableCompileArgs compileArgs = cx->new_<CompileArgs>();
+    if (!compileArgs || !compileArgs->initFromContext(cx, Move(scriptedCaller)))
         return false;
 
     UniqueChars error;
-    SharedModule module = Compile(*bytecode, compileArgs, &error);
+    SharedModule module = Compile(*bytecode, *compileArgs, &error);
     if (!module) {
         if (error) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR,
                                       error.get());
             return false;
         }
         ReportOutOfMemory(cx);
         return false;
@@ -882,22 +882,22 @@ WasmModuleObject::construct(JSContext* c
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
         return false;
     }
 
     MutableBytes bytecode;
     if (!GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG, &bytecode))
         return false;
 
-    CompileArgs compileArgs;
-    if (!InitCompileArgs(cx, &compileArgs))
+    MutableCompileArgs compileArgs = cx->new_<CompileArgs>();
+    if (!compileArgs || !InitCompileArgs(cx, compileArgs.get()))
         return false;
 
     UniqueChars error;
-    SharedModule module = Compile(*bytecode, compileArgs, &error);
+    SharedModule module = Compile(*bytecode, *compileArgs, &error);
     if (!module) {
         if (error) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR,
                                       error.get());
             return false;
         }
         ReportOutOfMemory(cx);
         return false;
@@ -1894,46 +1894,47 @@ Reject(JSContext* cx, const CompileArgs&
     if (!errorObj)
         return false;
 
     RootedValue rejectionValue(cx, ObjectValue(*errorObj));
     return PromiseObject::reject(cx, promise, rejectionValue);
 }
 
 static bool
-ResolveCompilation(JSContext* cx, Module& module, Handle<PromiseObject*> promise)
+ResolveCompilation(JSContext* cx, Module& module, const CompileArgs& compileArgs,
+                   Handle<PromiseObject*> promise)
 {
     RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
     RootedObject moduleObj(cx, WasmModuleObject::create(cx, module, proto));
     if (!moduleObj)
         return false;
 
     RootedValue resolutionValue(cx, ObjectValue(*moduleObj));
     return PromiseObject::resolve(cx, promise, resolutionValue);
 }
 
 struct CompilePromiseTask : PromiseHelperTask
 {
-    MutableBytes bytecode;
-    CompileArgs  compileArgs;
-    UniqueChars  error;
-    SharedModule module;
+    MutableBytes      bytecode;
+    SharedCompileArgs compileArgs;
+    UniqueChars       error;
+    SharedModule      module;
 
     CompilePromiseTask(JSContext* cx, Handle<PromiseObject*> promise)
       : PromiseHelperTask(cx, promise)
     {}
 
     void execute() override {
-        module = Compile(*bytecode, compileArgs, &error);
+        module = Compile(*bytecode, *compileArgs, &error);
     }
 
     bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
         return module
-               ? ResolveCompilation(cx, *module, promise)
-               : Reject(cx, compileArgs, Move(error), promise);
+               ? ResolveCompilation(cx, *module, *compileArgs, promise)
+               : Reject(cx, *compileArgs, Move(error), promise);
     }
 };
 
 static bool
 RejectWithPendingException(JSContext* cx, Handle<PromiseObject*> promise)
 {
     if (!cx->isExceptionPending())
         return false;
@@ -1993,29 +1994,31 @@ WebAssembly_compile(JSContext* cx, unsig
     if (!task || !task->init(cx))
         return false;
 
     CallArgs callArgs = CallArgsFromVp(argc, vp);
 
     if (!GetBufferSource(cx, callArgs, "WebAssembly.compile", &task->bytecode))
         return RejectWithPendingException(cx, promise, callArgs);
 
-    if (!InitCompileArgs(cx, &task->compileArgs))
+    MutableCompileArgs compileArgs = cx->new_<CompileArgs>();
+    if (!compileArgs || !InitCompileArgs(cx, compileArgs))
         return false;
+    task->compileArgs = compileArgs;
 
     if (!StartOffThreadPromiseHelperTask(cx, Move(task)))
         return false;
 
     callArgs.rval().setObject(*promise);
     return true;
 }
 
 static bool
-ResolveInstantiation(JSContext* cx, Module& module, HandleObject importObj,
-                     Handle<PromiseObject*> promise)
+ResolveInstantiation(JSContext* cx, Module& module, const CompileArgs& compileArgs,
+                     HandleObject importObj, Handle<PromiseObject*> promise)
 {
     RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
     RootedObject moduleObj(cx, WasmModuleObject::create(cx, module, proto));
     if (!moduleObj)
         return false;
 
     RootedWasmInstanceObject instanceObj(cx);
     if (!Instantiate(cx, module, importObj, &instanceObj))
@@ -2035,34 +2038,36 @@ ResolveInstantiation(JSContext* cx, Modu
 
     val = ObjectValue(*resultObj);
     return PromiseObject::resolve(cx, promise, val);
 }
 
 struct InstantiatePromiseTask : PromiseHelperTask
 {
     MutableBytes           bytecode;
-    CompileArgs            compileArgs;
+    SharedCompileArgs      compileArgs;
     UniqueChars            error;
     SharedModule           module;
     PersistentRootedObject importObj;
 
-    InstantiatePromiseTask(JSContext* cx, Handle<PromiseObject*> promise, HandleObject importObj)
+    InstantiatePromiseTask(JSContext* cx, Handle<PromiseObject*> promise,
+                           const CompileArgs& compileArgs, HandleObject importObj)
       : PromiseHelperTask(cx, promise),
+        compileArgs(&compileArgs),
         importObj(cx, importObj)
     {}
 
     void execute() override {
-        module = Compile(*bytecode, compileArgs, &error);
+        module = Compile(*bytecode, *compileArgs, &error);
     }
 
     bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
         return module
-               ? ResolveInstantiation(cx, *module, importObj, promise)
-               : Reject(cx, compileArgs, Move(error), promise);
+               ? ResolveInstantiation(cx, *module, *compileArgs, importObj, promise)
+               : Reject(cx, *compileArgs, Move(error), promise);
     }
 };
 
 static bool
 GetInstantiateArgs(JSContext* cx, CallArgs callArgs, MutableHandleObject firstArg,
                    MutableHandleObject importObj)
 {
     if (!callArgs.requireAtLeast(cx, "WebAssembly.instantiate", 1))
@@ -2100,26 +2105,27 @@ WebAssembly_instantiate(JSContext* cx, u
         RootedWasmInstanceObject instanceObj(cx);
         if (!Instantiate(cx, *module, importObj, &instanceObj))
             return RejectWithPendingException(cx, promise, callArgs);
 
         RootedValue resolutionValue(cx, ObjectValue(*instanceObj));
         if (!PromiseObject::resolve(cx, promise, resolutionValue))
             return false;
     } else {
-        auto task = cx->make_unique<InstantiatePromiseTask>(cx, promise, importObj);
+        MutableCompileArgs compileArgs = cx->new_<CompileArgs>();
+        if (!compileArgs || !InitCompileArgs(cx, compileArgs.get()))
+            return false;
+
+        auto task = cx->make_unique<InstantiatePromiseTask>(cx, promise, *compileArgs, importObj);
         if (!task || !task->init(cx))
             return false;
 
         if (!GetBufferSource(cx, firstArg, JSMSG_WASM_BAD_BUF_MOD_ARG, &task->bytecode))
             return RejectWithPendingException(cx, promise, callArgs);
 
-        if (!InitCompileArgs(cx, &task->compileArgs))
-            return false;
-
         if (!StartOffThreadPromiseHelperTask(cx, Move(task)))
             return false;
     }
 
     callArgs.rval().setObject(*promise);
     return true;
 }
 
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -16,16 +16,17 @@
  * limitations under the License.
  */
 
 #include "wasm/WasmModule.h"
 
 #include "jsnspr.h"
 
 #include "jit/JitOptions.h"
+#include "threading/LockGuard.h"
 #include "wasm/WasmCompile.h"
 #include "wasm/WasmInstance.h"
 #include "wasm/WasmJS.h"
 #include "wasm/WasmSerialize.h"
 
 #include "jsatominlines.h"
 
 #include "vm/ArrayBufferObject-inl.h"
@@ -257,16 +258,20 @@ Module::bytecodeSerialize(uint8_t* bytec
 Module::compiledSerializedSize() const
 {
     // The compiled debug code must not be saved, set compiled size to 0,
     // so Module::assumptionsMatch will return false during assumptions
     // deserialization.
     if (metadata().debugEnabled)
         return 0;
 
+    blockOnIonCompileFinished();
+    if (!code_->hasTier(Tier::Serialized))
+        return 0;
+
     return assumptions_.serializedSize() +
            linkData_.serializedSize() +
            SerializedVectorSize(imports_) +
            SerializedVectorSize(exports_) +
            SerializedPodVectorSize(dataSegments_) +
            SerializedVectorSize(elemSegments_) +
            code_->serializedSize();
 }
@@ -274,31 +279,67 @@ Module::compiledSerializedSize() const
 /* virtual */ void
 Module::compiledSerialize(uint8_t* compiledBegin, size_t compiledSize) const
 {
     if (metadata().debugEnabled) {
         MOZ_RELEASE_ASSERT(compiledSize == 0);
         return;
     }
 
-    // Assumption must be serialized at the beginning of the compiled bytes so
-    // that compiledAssumptionsMatch can detect a build-id mismatch before any
-    // other decoding occurs.
+    blockOnIonCompileFinished();
+    if (!code_->hasTier(Tier::Serialized)) {
+        MOZ_RELEASE_ASSERT(compiledSize == 0);
+        return;
+    }
 
     uint8_t* cursor = compiledBegin;
     cursor = assumptions_.serialize(cursor);
     cursor = linkData_.serialize(cursor);
     cursor = SerializeVector(cursor, imports_);
     cursor = SerializeVector(cursor, exports_);
     cursor = SerializePodVector(cursor, dataSegments_);
     cursor = SerializeVector(cursor, elemSegments_);
     cursor = code_->serialize(cursor, linkData_);
     MOZ_RELEASE_ASSERT(cursor == compiledBegin + compiledSize);
 }
 
+void
+Module::finishTier2Generator(UniqueLinkDataTier linkData2, UniqueMetadataTier metadata2,
+                             UniqueConstCodeSegment code2, UniqueModuleEnvironment env2)
+{
+    // Install the data in the data structures. They will not be visible yet.
+
+    metadata().setTier2(Move(metadata2));
+    linkData().setTier2(Move(linkData2));
+    code().setTier2(Move(code2));
+    for (uint32_t i = 0; i < elemSegments_.length(); i++)
+        elemSegments_[i].setTier2(Move(env2->elemSegments[i].elemCodeRangeIndices(Tier::Ion)));
+
+    // Set the flag atomically to make the tier 2 data visible everywhere at
+    // once.
+
+    metadata().commitTier2();
+}
+
+void
+Module::blockOnIonCompileFinished() const
+{
+    LockGuard<Mutex> l(tier2Lock_);
+    while (mode_ == CompileMode::Tier1 && !metadata().hasTier2())
+        tier2Cond_.wait(l);
+}
+
+void
+Module::unblockOnTier2GeneratorFinished(CompileMode newMode) const
+{
+    LockGuard<Mutex> l(tier2Lock_);
+    mode_ = newMode;
+    tier2Cond_.notify_all();
+}
+
 /* static */ bool
 Module::assumptionsMatch(const Assumptions& current, const uint8_t* compiledBegin, size_t remain)
 {
     Assumptions cached;
     if (!cached.deserialize(compiledBegin, remain))
         return false;
 
     return current == cached;
@@ -362,19 +403,20 @@ Module::deserialize(const uint8_t* bytec
     MutableCode code = js_new<Code>();
     cursor = code->deserialize(cursor, bytecode, linkData, *metadata);
     if (!cursor)
         return nullptr;
 
     MOZ_RELEASE_ASSERT(cursor == compiledBegin + compiledSize);
     MOZ_RELEASE_ASSERT(!!maybeMetadata == code->metadata().isAsmJS());
 
-    return js_new<Module>(Move(assumptions),
+    return js_new<Module>(CompileMode::Tier2, // Serialized code is always Tier 2
+                          Move(assumptions),
                           *code,
-                          nullptr, // Serialized code is never debuggable
+                          nullptr,            // Serialized code is never debuggable
                           Move(linkData),
                           Move(imports),
                           Move(exports),
                           Move(dataSegments),
                           Move(elemSegments),
                           *bytecode);
 }
 
@@ -458,20 +500,22 @@ wasm::DeserializeModule(PRFileDesc* byte
 
     memcpy(bytecode->bytes.begin(), bytecodeMapping.get(), bytecodeInfo.size);
 
     ScriptedCaller scriptedCaller;
     scriptedCaller.filename = Move(filename);
     scriptedCaller.line = line;
     scriptedCaller.column = column;
 
-    CompileArgs args(Assumptions(Move(buildId)), Move(scriptedCaller));
+    SharedCompileArgs args = js_new<CompileArgs>(Assumptions(Move(buildId)), Move(scriptedCaller));
+    if (!args)
+        return nullptr;
 
     UniqueChars error;
-    return Compile(*bytecode, Move(args), &error);
+    return Compile(*bytecode, *args, &error);
 }
 
 /* virtual */ void
 Module::addSizeOfMisc(MallocSizeOf mallocSizeOf,
                       Metadata::SeenSet* seenMetadata,
                       ShareableBytes::SeenSet* seenBytes,
                       Code::SeenSet* seenCode,
                       size_t* code,
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -15,23 +15,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef wasm_module_h
 #define wasm_module_h
 
 #include "js/TypeDecls.h"
-
+#include "threading/ConditionVariable.h"
+#include "threading/Mutex.h"
+#include "vm/MutexIDs.h"
 #include "wasm/WasmCode.h"
 #include "wasm/WasmTable.h"
+#include "wasm/WasmValidate.h"
 
 namespace js {
 namespace wasm {
 
+struct CompileArgs;
+
 // LinkData contains all the metadata necessary to patch all the locations
 // that depend on the absolute address of a CodeSegment.
 //
 // LinkData is built incrementing by ModuleGenerator and then stored immutably
 // in Module.
 
 struct LinkDataTierCacheablePod
 {
@@ -90,16 +95,22 @@ class LinkData
 
     bool hasTier2() const { return metadata_->hasTier2(); }
     void setTier2(UniqueLinkDataTier linkData) const;
     Tiers tiers() const;
 
     const LinkDataTier& linkData(Tier tier) const;
     LinkDataTier& linkData(Tier tier);
 
+    UniquePtr<LinkDataTier> takeLinkData(Tier tier) {
+        MOZ_ASSERT(!hasTier2());
+        MOZ_ASSERT(linkData1_->tier == tier);
+        return Move(linkData1_);
+    }
+
     WASM_DECLARE_SERIALIZABLE(LinkData)
 };
 
 // Module represents a compiled wasm module and primarily provides two
 // operations: instantiation and serialization. A Module can be instantiated any
 // number of times to produce new Instance objects. A Module can be serialized
 // any number of times such that the serialized bytes can be deserialized later
 // to produce a new, equivalent Module.
@@ -125,29 +136,44 @@ class Module : public JS::WasmModule
 
     // `codeIsBusy_` is set to false initially and then to true when `code_` is
     // already being used for an instance and can't be shared because it may be
     // patched by the debugger. Subsequent instances must then create copies
     // by linking the `unlinkedCodeForDebugging_`.
 
     mutable mozilla::Atomic<bool> codeIsBusy_;
 
+    // The lock guards the mode_ member, and the lock/cond pair are used to
+    // allow threads to wait for the availability of Ion code and signal the
+    // completion of tier-2 compilation; see blockOnIonCompileFinished and
+    // unblockOnTier2GeneratorFinished, below.
+
+    mutable Mutex                 tier2Lock_;
+    mutable ConditionVariable     tier2Cond_;
+
+    // Access mode_ only under the lock.  It will be changed from Tier1 to Tier2
+    // once Tier2 compilation is finished, and from Tier1 to Once if Tier2
+    // compilation is disabled (in testing modes) or cancelled.
+
+    mutable CompileMode           mode_;
+
     bool instantiateFunctions(JSContext* cx, Handle<FunctionVector> funcImports) const;
     bool instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) const;
     bool instantiateTable(JSContext* cx,
                           MutableHandleWasmTableObject table,
                           SharedTableVector* tables) const;
     bool initSegments(JSContext* cx,
                       HandleWasmInstanceObject instance,
                       Handle<FunctionVector> funcImports,
                       HandleWasmMemoryObject memory,
                       const ValVector& globalImports) const;
 
   public:
-    Module(Assumptions&& assumptions,
+    Module(CompileMode mode,
+           Assumptions&& assumptions,
            const Code& code,
            UniqueConstBytes unlinkedCodeForDebugging,
            LinkData&& linkData,
            ImportVector&& imports,
            ExportVector&& exports,
            DataSegmentVector&& dataSegments,
            ElemSegmentVector&& elemSegments,
            const ShareableBytes& bytecode)
@@ -155,40 +181,68 @@ class Module : public JS::WasmModule
         code_(&code),
         unlinkedCodeForDebugging_(Move(unlinkedCodeForDebugging)),
         linkData_(Move(linkData)),
         imports_(Move(imports)),
         exports_(Move(exports)),
         dataSegments_(Move(dataSegments)),
         elemSegments_(Move(elemSegments)),
         bytecode_(&bytecode),
-        codeIsBusy_(false)
+        codeIsBusy_(false),
+        tier2Lock_(js::mutexid::WasmTier2GeneratorComplete),
+        mode_(mode)
     {
         MOZ_ASSERT_IF(metadata().debugEnabled, unlinkedCodeForDebugging_);
     }
     ~Module() override { /* Note: can be called on any thread */ }
 
     const Code& code() const { return *code_; }
+    const CodeSegment& codeSegment(Tier t) const { return code_->segment(t); }
     const Metadata& metadata() const { return code_->metadata(); }
     const MetadataTier& metadata(Tier t) const { return code_->metadata(t); }
+    const LinkData& linkData() const { return linkData_; }
+    const LinkDataTier& linkData(Tier t) const { return linkData_.linkData(t); }
     const ImportVector& imports() const { return imports_; }
     const ExportVector& exports() const { return exports_; }
-    const Bytes& bytecode() const { return bytecode_->bytes; }
+    const ShareableBytes& bytecode() const { return *bytecode_; }
     uint32_t codeLength(Tier t) const { return code_->segment(t).length(); }
 
     // Instantiate this module with the given imports:
 
     bool instantiate(JSContext* cx,
                      Handle<FunctionVector> funcImports,
                      HandleWasmTableObject tableImport,
                      HandleWasmMemoryObject memoryImport,
                      const ValVector& globalImports,
                      HandleObject instanceProto,
                      MutableHandleWasmInstanceObject instanceObj) const;
 
+    // Tiered compilation support: these run on the compilation thread.
+
+    // Will be invoked once the second-tier compilation of the module is
+    // finished; it is passed the tier-variant data for Tier 2, which it
+    // installs in the module and makes visible.
+
+    void finishTier2Generator(UniqueLinkDataTier linkData2, UniqueMetadataTier metadata2,
+                              UniqueConstCodeSegment code2, UniqueModuleEnvironment env2);
+
+    // Wait until Ion-compiled code is available, which will be true either
+    // immediately (first-level compile was Ion and is already done), not at all
+    // (first-level compile was Baseline and there's not a second level), or
+    // later (ongoing second-level compilation).  Once this returns, one can use
+    // code().hasTier() to check code availability - there is no guarantee that
+    // Ion code will be available, but if it isn't then it never will.
+
+    void blockOnIonCompileFinished() const;
+
+    // Signal all waiters that are waiting on tier-2 compilation to be done that
+    // they should wake up.  They will be waiting in blockOnIonCompileFinished.
+
+    void unblockOnTier2GeneratorFinished(CompileMode newMode) const;
+
     // Structured clone support:
 
     size_t bytecodeSerializedSize() const override;
     void bytecodeSerialize(uint8_t* bytecodeBegin, size_t bytecodeSize) const override;
     size_t compiledSerializedSize() const override;
     void compiledSerialize(uint8_t* compiledBegin, size_t compiledSize) const override;
 
     static bool assumptionsMatch(const Assumptions& current, const uint8_t* compiledBegin,