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 | |
35 | namespace JSC { |
36 | |
37 | class JITWorklist::Plan : public ThreadSafeRefCounted<JITWorklist::Plan> { |
38 | public: |
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 | |
93 | private: |
94 | CodeBlock* m_codeBlock; |
95 | JIT m_jit; |
96 | Lock m_lock; |
97 | bool m_isFinishedCompiling { false }; |
98 | }; |
99 | |
100 | class JITWorklist::Thread : public AutomaticThread { |
101 | public: |
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 | |
118 | protected: |
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 | |
152 | private: |
153 | JITWorklist& m_worklist; |
154 | Plans m_myPlans; |
155 | }; |
156 | |
157 | JITWorklist::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 | |
165 | JITWorklist::~JITWorklist() |
166 | { |
167 | UNREACHABLE_FOR_PLATFORM(); |
168 | } |
169 | |
170 | bool 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 | |
211 | void 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 | |
231 | void 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 | |
283 | void 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 | |
315 | void 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 | |
325 | static JITWorklist* theGlobalJITWorklist { nullptr }; |
326 | |
327 | JITWorklist* JITWorklist::existingGlobalWorklistOrNull() |
328 | { |
329 | return theGlobalJITWorklist; |
330 | } |
331 | |
332 | JITWorklist& 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 | |