1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "jit/IonCompileTask.h"
8 
9 #include "jit/CodeGenerator.h"
10 #include "jit/CompileInfo.h"
11 #include "jit/Ion.h"
12 #include "jit/JitRuntime.h"
13 #include "jit/JitScript.h"
14 #include "jit/WarpSnapshot.h"
15 #include "vm/HelperThreadState.h"
16 #include "vm/JSScript.h"
17 
18 #include "vm/JSScript-inl.h"
19 
20 using namespace js;
21 using namespace js::jit;
22 
runHelperThreadTask(AutoLockHelperThreadState & locked)23 void IonCompileTask::runHelperThreadTask(AutoLockHelperThreadState& locked) {
24   // The build is taken by this thread. Unfreeze the LifoAlloc to allow
25   // mutations.
26   alloc().lifoAlloc()->setReadWrite();
27 
28   {
29     AutoUnlockHelperThreadState unlock(locked);
30     runTask();
31   }
32 
33   FinishOffThreadIonCompile(this, locked);
34 
35   JSRuntime* rt = script()->runtimeFromAnyThread();
36 
37   // Ping the main thread so that the compiled code can be incorporated at the
38   // next interrupt callback.
39   //
40   // This must happen before the current task is reset. DestroyContext
41   // cancels in progress Ion compilations before destroying its target
42   // context, and after we reset the current task we are no longer considered
43   // to be Ion compiling.
44   rt->mainContextFromAnyThread()->requestInterrupt(
45       InterruptReason::AttachIonCompilations);
46 }
47 
runTask()48 void IonCompileTask::runTask() {
49   // This is the entry point when ion compiles are run offthread.
50   TraceLoggerThread* logger = TraceLoggerForCurrentThread();
51   TraceLoggerEvent event(TraceLogger_AnnotateScripts, script());
52   AutoTraceLog logScript(logger, event);
53   AutoTraceLog logCompile(logger, TraceLogger_IonCompilation);
54 
55   jit::JitContext jctx(mirGen_.realm->runtime(), mirGen_.realm, &alloc());
56   setBackgroundCodegen(jit::CompileBackEnd(&mirGen_, snapshot_));
57 }
58 
trace(JSTracer * trc)59 void IonCompileTask::trace(JSTracer* trc) {
60   if (!mirGen_.runtime->runtimeMatches(trc->runtime())) {
61     return;
62   }
63 
64   snapshot_->trace(trc);
65 }
66 
IonCompileTask(MIRGenerator & mirGen,WarpSnapshot * snapshot)67 IonCompileTask::IonCompileTask(MIRGenerator& mirGen, WarpSnapshot* snapshot)
68     : mirGen_(mirGen), snapshot_(snapshot) {}
69 
sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)70 size_t IonCompileTask::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
71   // See js::jit::FreeIonCompileTask.
72   // The IonCompileTask and most of its contents live in the LifoAlloc we point
73   // to.
74 
75   size_t result = alloc().lifoAlloc()->sizeOfIncludingThis(mallocSizeOf);
76 
77   if (backgroundCodegen_) {
78     result += mallocSizeOf(backgroundCodegen_);
79   }
80 
81   return result;
82 }
83 
TooManyUnlinkedTasks(JSRuntime * rt)84 static inline bool TooManyUnlinkedTasks(JSRuntime* rt) {
85   static const size_t MaxUnlinkedTasks = 100;
86   return rt->jitRuntime()->ionLazyLinkListSize() > MaxUnlinkedTasks;
87 }
88 
MoveFinishedTasksToLazyLinkList(JSRuntime * rt,const AutoLockHelperThreadState & lock)89 static void MoveFinishedTasksToLazyLinkList(
90     JSRuntime* rt, const AutoLockHelperThreadState& lock) {
91   // Incorporate any off thread compilations for the runtime which have
92   // finished, failed or have been cancelled.
93 
94   GlobalHelperThreadState::IonCompileTaskVector& finished =
95       HelperThreadState().ionFinishedList(lock);
96 
97   for (size_t i = 0; i < finished.length(); i++) {
98     // Find a finished task for the runtime.
99     IonCompileTask* task = finished[i];
100     if (task->script()->runtimeFromAnyThread() != rt) {
101       continue;
102     }
103 
104     HelperThreadState().remove(finished, &i);
105     rt->jitRuntime()->numFinishedOffThreadTasksRef(lock)--;
106 
107     JSScript* script = task->script();
108     MOZ_ASSERT(script->hasBaselineScript());
109     script->baselineScript()->setPendingIonCompileTask(rt, script, task);
110     rt->jitRuntime()->ionLazyLinkListAdd(rt, task);
111   }
112 }
113 
EagerlyLinkExcessTasks(JSContext * cx,AutoLockHelperThreadState & lock)114 static void EagerlyLinkExcessTasks(JSContext* cx,
115                                    AutoLockHelperThreadState& lock) {
116   JSRuntime* rt = cx->runtime();
117   MOZ_ASSERT(TooManyUnlinkedTasks(rt));
118 
119   do {
120     jit::IonCompileTask* task = rt->jitRuntime()->ionLazyLinkList(rt).getLast();
121     RootedScript script(cx, task->script());
122 
123     AutoUnlockHelperThreadState unlock(lock);
124     AutoRealm ar(cx, script);
125     jit::LinkIonScript(cx, script);
126   } while (TooManyUnlinkedTasks(rt));
127 }
128 
AttachFinishedCompilations(JSContext * cx)129 void jit::AttachFinishedCompilations(JSContext* cx) {
130   JSRuntime* rt = cx->runtime();
131   MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
132 
133   if (!rt->jitRuntime() || !rt->jitRuntime()->numFinishedOffThreadTasks()) {
134     return;
135   }
136 
137   AutoLockHelperThreadState lock;
138 
139   while (true) {
140     MoveFinishedTasksToLazyLinkList(rt, lock);
141 
142     if (!TooManyUnlinkedTasks(rt)) {
143       break;
144     }
145 
146     EagerlyLinkExcessTasks(cx, lock);
147 
148     // Linking releases the lock so we must now check the finished list
149     // again.
150   }
151 
152   MOZ_ASSERT(!rt->jitRuntime()->numFinishedOffThreadTasks());
153 }
154 
FreeIonCompileTask(IonCompileTask * task)155 void jit::FreeIonCompileTask(IonCompileTask* task) {
156   // The task is allocated into its LifoAlloc, so destroying that will
157   // destroy the task and all other data accumulated during compilation,
158   // except any final codegen (which includes an assembler and needs to be
159   // explicitly destroyed).
160   js_delete(task->backgroundCodegen());
161   js_delete(task->alloc().lifoAlloc());
162 }
163 
runHelperThreadTask(AutoLockHelperThreadState & locked)164 void IonFreeTask::runHelperThreadTask(AutoLockHelperThreadState& locked) {
165   {
166     AutoUnlockHelperThreadState unlock(locked);
167     jit::FreeIonCompileTask(task_);
168   }
169 
170   js_delete(this);
171 }
172 
FinishOffThreadTask(JSRuntime * runtime,IonCompileTask * task,const AutoLockHelperThreadState & locked)173 void jit::FinishOffThreadTask(JSRuntime* runtime, IonCompileTask* task,
174                               const AutoLockHelperThreadState& locked) {
175   MOZ_ASSERT(runtime);
176 
177   JSScript* script = task->script();
178 
179   // Clean the references to the pending IonCompileTask, if we just finished it.
180   if (script->baselineScript()->hasPendingIonCompileTask() &&
181       script->baselineScript()->pendingIonCompileTask() == task) {
182     script->baselineScript()->removePendingIonCompileTask(runtime, script);
183   }
184 
185   // If the task is still in one of the helper thread lists, then remove it.
186   if (task->isInList()) {
187     runtime->jitRuntime()->ionLazyLinkListRemove(runtime, task);
188   }
189 
190   // Clean up if compilation did not succeed.
191   if (script->isIonCompilingOffThread()) {
192     script->jitScript()->clearIsIonCompilingOffThread(script);
193 
194     const AbortReasonOr<Ok>& status = task->mirGen().getOffThreadStatus();
195     if (status.isErr() && status.inspectErr() == AbortReason::Disable) {
196       script->disableIon();
197     }
198   }
199 
200   // Free Ion LifoAlloc off-thread. Free on the main thread if this OOMs.
201   if (!StartOffThreadIonFree(task, locked)) {
202     FreeIonCompileTask(task);
203   }
204 }
205