1/*
2 * Copyright (C) 2016 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "JITWorklist.h"
28
29#if ENABLE(JIT)
30
31#include "JIT.h"
32#include "JSCInlines.h"
33#include "VMInlines.h"
34
35namespace JSC {
36
37class JITWorklist::Plan : public ThreadSafeRefCounted<JITWorklist::Plan> {
38public:
39 Plan(CodeBlock* codeBlock, unsigned loopOSREntryBytecodeOffset)
40 : m_codeBlock(codeBlock)
41 , m_jit(codeBlock->vm(), codeBlock, loopOSREntryBytecodeOffset)
42 {
43 m_jit.doMainThreadPreparationBeforeCompile();
44 }
45
46 void compileInThread()
47 {
48 m_jit.compileWithoutLinking(JITCompilationCanFail);
49
50 LockHolder locker(m_lock);
51 m_isFinishedCompiling = true;
52 }
53
54 void finalize()
55 {
56 CompilationResult result = m_jit.link();
57 switch (result) {
58 case CompilationFailed:
59 CODEBLOCK_LOG_EVENT(m_codeBlock, "delayJITCompile", ("compilation failed"));
60 if (Options::verboseOSR())
61 dataLogF(" JIT compilation failed.\n");
62 m_codeBlock->dontJITAnytimeSoon();
63 m_codeBlock->m_didFailJITCompilation = true;
64 return;
65 case CompilationSuccessful:
66 if (Options::verboseOSR())
67 dataLogF(" JIT compilation successful.\n");
68 m_codeBlock->ownerExecutable()->installCode(m_codeBlock);
69 m_codeBlock->jitSoon();
70 return;
71 default:
72 RELEASE_ASSERT_NOT_REACHED();
73 return;
74 }
75 }
76
77 CodeBlock* codeBlock() { return m_codeBlock; }
78 VM* vm() { return m_codeBlock->vm(); }
79
80 bool isFinishedCompiling()
81 {
82 LockHolder locker(m_lock);
83 return m_isFinishedCompiling;
84 }
85
86 static void compileNow(CodeBlock* codeBlock, unsigned loopOSREntryBytecodeOffset)
87 {
88 Plan plan(codeBlock, loopOSREntryBytecodeOffset);
89 plan.compileInThread();
90 plan.finalize();
91 }
92
93private:
94 CodeBlock* m_codeBlock;
95 JIT m_jit;
96 Lock m_lock;
97 bool m_isFinishedCompiling { false };
98};
99
100class JITWorklist::Thread : public AutomaticThread {
101public:
102 Thread(const AbstractLocker& locker, JITWorklist& worklist)
103 : AutomaticThread(locker, worklist.m_lock, worklist.m_condition.copyRef())
104 , m_worklist(worklist)
105 {
106 m_worklist.m_numAvailableThreads++;
107 }
108
109 const char* name() const override
110 {
111#if OS(LINUX)
112 return "JITWorker";
113#else
114 return "JIT Worklist Helper Thread";
115#endif
116 }
117
118protected:
119 PollResult poll(const AbstractLocker&) override
120 {
121 RELEASE_ASSERT(m_worklist.m_numAvailableThreads);
122
123 if (m_worklist.m_queue.isEmpty())
124 return PollResult::Wait;
125
126 m_myPlans = WTFMove(m_worklist.m_queue);
127 m_worklist.m_numAvailableThreads--;
128 return PollResult::Work;
129 }
130
131 WorkResult work() override
132 {
133 RELEASE_ASSERT(!m_myPlans.isEmpty());
134
135 for (RefPtr<Plan>& plan : m_myPlans) {
136 plan->compileInThread();
137 plan = nullptr;
138
139 // Make sure that the main thread realizes that we just compiled something. Notifying
140 // a condition is basically free if nobody is waiting.
141 LockHolder locker(*m_worklist.m_lock);
142 m_worklist.m_condition->notifyAll(locker);
143 }
144
145 m_myPlans.clear();
146
147 LockHolder locker(*m_worklist.m_lock);
148 m_worklist.m_numAvailableThreads++;
149 return WorkResult::Continue;
150 }
151
152private:
153 JITWorklist& m_worklist;
154 Plans m_myPlans;
155};
156
157JITWorklist::JITWorklist()
158 : m_lock(Box<Lock>::create())
159 , m_condition(AutomaticThreadCondition::create())
160{
161 LockHolder locker(*m_lock);
162 m_thread = new Thread(locker, *this);
163}
164
165JITWorklist::~JITWorklist()
166{
167 UNREACHABLE_FOR_PLATFORM();
168}
169
170bool JITWorklist::completeAllForVM(VM& vm)
171{
172 bool result = false;
173 DeferGC deferGC(vm.heap);
174 for (;;) {
175 Vector<RefPtr<Plan>, 32> myPlans;
176 {
177 LockHolder locker(*m_lock);
178 for (;;) {
179 bool didFindUnfinishedPlan = false;
180 m_plans.removeAllMatching(
181 [&] (RefPtr<Plan>& plan) {
182 if (plan->vm() != &vm)
183 return false;
184 if (!plan->isFinishedCompiling()) {
185 didFindUnfinishedPlan = true;
186 return false;
187 }
188 myPlans.append(WTFMove(plan));
189 return true;
190 });
191
192 // If we found plans then we should finalize them now.
193 if (!myPlans.isEmpty())
194 break;
195
196 // If we don't find plans, then we're either done or we need to wait, depending on
197 // whether we found some unfinished plans.
198 if (!didFindUnfinishedPlan)
199 return result;
200
201 m_condition->wait(*m_lock);
202 }
203 }
204
205 RELEASE_ASSERT(!myPlans.isEmpty());
206 result = true;
207 finalizePlans(myPlans);
208 }
209}
210
211void JITWorklist::poll(VM& vm)
212{
213 DeferGC deferGC(vm.heap);
214 Plans myPlans;
215 {
216 LockHolder locker(*m_lock);
217 m_plans.removeAllMatching(
218 [&] (RefPtr<Plan>& plan) {
219 if (plan->vm() != &vm)
220 return false;
221 if (!plan->isFinishedCompiling())
222 return false;
223 myPlans.append(WTFMove(plan));
224 return true;
225 });
226 }
227
228 finalizePlans(myPlans);
229}
230
231void JITWorklist::compileLater(CodeBlock* codeBlock, unsigned loopOSREntryBytecodeOffset)
232{
233 DeferGC deferGC(codeBlock->vm()->heap);
234 RELEASE_ASSERT(codeBlock->jitType() == JITType::InterpreterThunk);
235
236 if (codeBlock->m_didFailJITCompilation) {
237 codeBlock->dontJITAnytimeSoon();
238 return;
239 }
240
241 if (!Options::useConcurrentJIT()) {
242 Plan::compileNow(codeBlock, loopOSREntryBytecodeOffset);
243 return;
244 }
245
246 codeBlock->jitSoon();
247
248 {
249 LockHolder locker(*m_lock);
250
251 if (m_planned.contains(codeBlock))
252 return;
253
254 if (m_numAvailableThreads) {
255 m_planned.add(codeBlock);
256 RefPtr<Plan> plan = adoptRef(new Plan(codeBlock, loopOSREntryBytecodeOffset));
257 m_plans.append(plan);
258 m_queue.append(plan);
259 m_condition->notifyAll(locker);
260 return;
261 }
262 }
263
264 // Compiling on the main thread if the helper thread isn't available is a defense against this
265 // pathology:
266 //
267 // 1) Do something that is allowed to take a while, like load a giant piece of initialization
268 // code. This plans the compile of the init code, but doesn't finish it. It will take a
269 // while.
270 //
271 // 2) Do something that is supposed to be quick. Now all baseline compiles, and so all DFG and
272 // FTL compiles, of everything is blocked on the long-running baseline compile of that
273 // initialization code.
274 //
275 // The single-threaded concurrent JIT has this tendency to convoy everything while at the same
276 // time postponing when it happens, which means that the convoy delays are less predictable.
277 // This works around the issue. If the concurrent JIT thread is convoyed, we revert to main
278 // thread compiles. This is probably not as good as if we had multiple JIT threads. Maybe we
279 // can do that someday.
280 Plan::compileNow(codeBlock, loopOSREntryBytecodeOffset);
281}
282
283void JITWorklist::compileNow(CodeBlock* codeBlock, unsigned loopOSREntryBytecodeOffset)
284{
285 VM* vm = codeBlock->vm();
286 DeferGC deferGC(vm->heap);
287 if (codeBlock->jitType() != JITType::InterpreterThunk)
288 return;
289
290 bool isPlanned;
291 {
292 LockHolder locker(*m_lock);
293 isPlanned = m_planned.contains(codeBlock);
294 }
295
296 if (isPlanned) {
297 RELEASE_ASSERT(Options::useConcurrentJIT());
298 // This is expensive, but probably good enough.
299 completeAllForVM(*vm);
300 }
301
302 // Now it might be compiled!
303 if (codeBlock->jitType() != JITType::InterpreterThunk)
304 return;
305
306 // We do this in case we had previously attempted, and then failed, to compile with the
307 // baseline JIT.
308 codeBlock->resetJITData();
309
310 // OK, just compile it.
311 JIT::compile(vm, codeBlock, JITCompilationMustSucceed, loopOSREntryBytecodeOffset);
312 codeBlock->ownerExecutable()->installCode(codeBlock);
313}
314
315void JITWorklist::finalizePlans(Plans& myPlans)
316{
317 for (RefPtr<Plan>& plan : myPlans) {
318 plan->finalize();
319
320 LockHolder locker(*m_lock);
321 m_planned.remove(plan->codeBlock());
322 }
323}
324
325static JITWorklist* theGlobalJITWorklist { nullptr };
326
327JITWorklist* JITWorklist::existingGlobalWorklistOrNull()
328{
329 return theGlobalJITWorklist;
330}
331
332JITWorklist& JITWorklist::ensureGlobalWorklist()
333{
334 static std::once_flag once;
335 std::call_once(
336 once,
337 [] {
338 auto* worklist = new JITWorklist();
339 WTF::storeStoreFence();
340 theGlobalJITWorklist = worklist;
341 });
342 return *theGlobalJITWorklist;
343}
344
345} // namespace JSC
346
347#endif // ENABLE(JIT)
348
349