]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. | |
3 | * Copyright (C) 2007 Eric Seidel <eric@webkit.org> | |
4 | * Copyright (C) 2009 Acision BV. All rights reserved. | |
5 | * | |
6 | * This library is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU Lesser General Public | |
8 | * License as published by the Free Software Foundation; either | |
9 | * version 2 of the License, or (at your option) any later version. | |
10 | * | |
11 | * This library is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * Lesser General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU Lesser General Public | |
17 | * License along with this library; if not, write to the Free Software | |
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
19 | * | |
20 | */ | |
21 | ||
22 | #include "config.h" | |
23 | #include "MachineStackMarker.h" | |
24 | ||
25 | #include "ConservativeRoots.h" | |
26 | #include "Heap.h" | |
27 | #include "JSArray.h" | |
28 | #include "JSCInlines.h" | |
29 | #include "VM.h" | |
30 | #include <setjmp.h> | |
31 | #include <stdlib.h> | |
32 | #include <wtf/StdLibExtras.h> | |
33 | ||
34 | #if OS(DARWIN) | |
35 | ||
36 | #include <mach/mach_init.h> | |
37 | #include <mach/mach_port.h> | |
38 | #include <mach/task.h> | |
39 | #include <mach/thread_act.h> | |
40 | #include <mach/vm_map.h> | |
41 | ||
42 | #elif OS(WINDOWS) | |
43 | ||
44 | #include <windows.h> | |
45 | #include <malloc.h> | |
46 | ||
47 | #elif OS(UNIX) | |
48 | ||
49 | #include <sys/mman.h> | |
50 | #include <unistd.h> | |
51 | ||
52 | #if OS(SOLARIS) | |
53 | #include <thread.h> | |
54 | #else | |
55 | #include <pthread.h> | |
56 | #endif | |
57 | ||
58 | #if HAVE(PTHREAD_NP_H) | |
59 | #include <pthread_np.h> | |
60 | #endif | |
61 | ||
62 | #if USE(PTHREADS) && !OS(WINDOWS) && !OS(DARWIN) | |
63 | #include <signal.h> | |
64 | #endif | |
65 | ||
66 | #endif | |
67 | ||
68 | using namespace WTF; | |
69 | ||
70 | namespace JSC { | |
71 | ||
72 | static inline void swapIfBackwards(void*& begin, void*& end) | |
73 | { | |
74 | #if OS(WINCE) | |
75 | if (begin <= end) | |
76 | return; | |
77 | std::swap(begin, end); | |
78 | #else | |
79 | UNUSED_PARAM(begin); | |
80 | UNUSED_PARAM(end); | |
81 | #endif | |
82 | } | |
83 | ||
84 | #if OS(DARWIN) | |
85 | typedef mach_port_t PlatformThread; | |
86 | #elif OS(WINDOWS) | |
87 | typedef HANDLE PlatformThread; | |
88 | #elif USE(PTHREADS) | |
89 | typedef pthread_t PlatformThread; | |
90 | static const int SigThreadSuspendResume = SIGUSR2; | |
91 | ||
92 | #if defined(SA_RESTART) | |
93 | static void pthreadSignalHandlerSuspendResume(int) | |
94 | { | |
95 | sigset_t signalSet; | |
96 | sigemptyset(&signalSet); | |
97 | sigaddset(&signalSet, SigThreadSuspendResume); | |
98 | sigsuspend(&signalSet); | |
99 | } | |
100 | #endif | |
101 | #endif | |
102 | ||
103 | class MachineThreads::Thread { | |
104 | WTF_MAKE_FAST_ALLOCATED; | |
105 | public: | |
106 | Thread(const PlatformThread& platThread, void* base) | |
107 | : platformThread(platThread) | |
108 | , stackBase(base) | |
109 | { | |
110 | #if USE(PTHREADS) && !OS(WINDOWS) && !OS(DARWIN) && defined(SA_RESTART) | |
111 | // if we have SA_RESTART, enable SIGUSR2 debugging mechanism | |
112 | struct sigaction action; | |
113 | action.sa_handler = pthreadSignalHandlerSuspendResume; | |
114 | sigemptyset(&action.sa_mask); | |
115 | action.sa_flags = SA_RESTART; | |
116 | sigaction(SigThreadSuspendResume, &action, 0); | |
117 | ||
118 | sigset_t mask; | |
119 | sigemptyset(&mask); | |
120 | sigaddset(&mask, SigThreadSuspendResume); | |
121 | pthread_sigmask(SIG_UNBLOCK, &mask, 0); | |
122 | #endif | |
123 | } | |
124 | ||
125 | Thread* next; | |
126 | PlatformThread platformThread; | |
127 | void* stackBase; | |
128 | }; | |
129 | ||
130 | MachineThreads::MachineThreads(Heap* heap) | |
131 | : m_registeredThreads(0) | |
132 | , m_threadSpecific(0) | |
133 | #if !ASSERT_DISABLED | |
134 | , m_heap(heap) | |
135 | #endif | |
136 | { | |
137 | UNUSED_PARAM(heap); | |
138 | } | |
139 | ||
140 | MachineThreads::~MachineThreads() | |
141 | { | |
142 | if (m_threadSpecific) | |
143 | threadSpecificKeyDelete(m_threadSpecific); | |
144 | ||
145 | MutexLocker registeredThreadsLock(m_registeredThreadsMutex); | |
146 | for (Thread* t = m_registeredThreads; t;) { | |
147 | Thread* next = t->next; | |
148 | delete t; | |
149 | t = next; | |
150 | } | |
151 | } | |
152 | ||
153 | static inline PlatformThread getCurrentPlatformThread() | |
154 | { | |
155 | #if OS(DARWIN) | |
156 | return pthread_mach_thread_np(pthread_self()); | |
157 | #elif OS(WINDOWS) | |
158 | return GetCurrentThread(); | |
159 | #elif USE(PTHREADS) | |
160 | return pthread_self(); | |
161 | #endif | |
162 | } | |
163 | ||
164 | static inline bool equalThread(const PlatformThread& first, const PlatformThread& second) | |
165 | { | |
166 | #if OS(DARWIN) || OS(WINDOWS) | |
167 | return first == second; | |
168 | #elif USE(PTHREADS) | |
169 | return !!pthread_equal(first, second); | |
170 | #else | |
171 | #error Need a way to compare threads on this platform | |
172 | #endif | |
173 | } | |
174 | ||
175 | void MachineThreads::makeUsableFromMultipleThreads() | |
176 | { | |
177 | if (m_threadSpecific) | |
178 | return; | |
179 | ||
180 | threadSpecificKeyCreate(&m_threadSpecific, removeThread); | |
181 | } | |
182 | ||
183 | void MachineThreads::addCurrentThread() | |
184 | { | |
185 | ASSERT(!m_heap->vm()->hasExclusiveThread() || m_heap->vm()->exclusiveThread() == std::this_thread::get_id()); | |
186 | ||
187 | if (!m_threadSpecific || threadSpecificGet(m_threadSpecific)) | |
188 | return; | |
189 | ||
190 | threadSpecificSet(m_threadSpecific, this); | |
191 | Thread* thread = new Thread(getCurrentPlatformThread(), wtfThreadData().stack().origin()); | |
192 | ||
193 | MutexLocker lock(m_registeredThreadsMutex); | |
194 | ||
195 | thread->next = m_registeredThreads; | |
196 | m_registeredThreads = thread; | |
197 | } | |
198 | ||
199 | void MachineThreads::removeThread(void* p) | |
200 | { | |
201 | if (p) | |
202 | static_cast<MachineThreads*>(p)->removeCurrentThread(); | |
203 | } | |
204 | ||
205 | void MachineThreads::removeCurrentThread() | |
206 | { | |
207 | PlatformThread currentPlatformThread = getCurrentPlatformThread(); | |
208 | ||
209 | MutexLocker lock(m_registeredThreadsMutex); | |
210 | ||
211 | if (equalThread(currentPlatformThread, m_registeredThreads->platformThread)) { | |
212 | Thread* t = m_registeredThreads; | |
213 | m_registeredThreads = m_registeredThreads->next; | |
214 | delete t; | |
215 | } else { | |
216 | Thread* last = m_registeredThreads; | |
217 | Thread* t; | |
218 | for (t = m_registeredThreads->next; t; t = t->next) { | |
219 | if (equalThread(t->platformThread, currentPlatformThread)) { | |
220 | last->next = t->next; | |
221 | break; | |
222 | } | |
223 | last = t; | |
224 | } | |
225 | ASSERT(t); // If t is NULL, we never found ourselves in the list. | |
226 | delete t; | |
227 | } | |
228 | } | |
229 | ||
230 | void MachineThreads::gatherFromCurrentThread(ConservativeRoots& conservativeRoots, JITStubRoutineSet& jitStubRoutines, CodeBlockSet& codeBlocks, void* stackCurrent, RegisterState& registers) | |
231 | { | |
232 | void* registersBegin = ®isters; | |
233 | void* registersEnd = reinterpret_cast<void*>(roundUpToMultipleOf<sizeof(void*)>(reinterpret_cast<uintptr_t>(®isters + 1))); | |
234 | swapIfBackwards(registersBegin, registersEnd); | |
235 | conservativeRoots.add(registersBegin, registersEnd, jitStubRoutines, codeBlocks); | |
236 | ||
237 | void* stackBegin = stackCurrent; | |
238 | void* stackEnd = wtfThreadData().stack().origin(); | |
239 | swapIfBackwards(stackBegin, stackEnd); | |
240 | conservativeRoots.add(stackBegin, stackEnd, jitStubRoutines, codeBlocks); | |
241 | } | |
242 | ||
243 | static inline bool suspendThread(const PlatformThread& platformThread) | |
244 | { | |
245 | #if OS(DARWIN) | |
246 | kern_return_t result = thread_suspend(platformThread); | |
247 | return result == KERN_SUCCESS; | |
248 | #elif OS(WINDOWS) | |
249 | bool threadIsSuspended = (SuspendThread(platformThread) != (DWORD)-1); | |
250 | ASSERT(threadIsSuspended); | |
251 | return threadIsSuspended; | |
252 | #elif USE(PTHREADS) | |
253 | pthread_kill(platformThread, SigThreadSuspendResume); | |
254 | return true; | |
255 | #else | |
256 | #error Need a way to suspend threads on this platform | |
257 | #endif | |
258 | } | |
259 | ||
260 | static inline void resumeThread(const PlatformThread& platformThread) | |
261 | { | |
262 | #if OS(DARWIN) | |
263 | thread_resume(platformThread); | |
264 | #elif OS(WINDOWS) | |
265 | ResumeThread(platformThread); | |
266 | #elif USE(PTHREADS) | |
267 | pthread_kill(platformThread, SigThreadSuspendResume); | |
268 | #else | |
269 | #error Need a way to resume threads on this platform | |
270 | #endif | |
271 | } | |
272 | ||
273 | typedef unsigned long usword_t; // word size, assumed to be either 32 or 64 bit | |
274 | ||
275 | #if OS(DARWIN) | |
276 | ||
277 | #if CPU(X86) | |
278 | typedef i386_thread_state_t PlatformThreadRegisters; | |
279 | #elif CPU(X86_64) | |
280 | typedef x86_thread_state64_t PlatformThreadRegisters; | |
281 | #elif CPU(PPC) | |
282 | typedef ppc_thread_state_t PlatformThreadRegisters; | |
283 | #elif CPU(PPC64) | |
284 | typedef ppc_thread_state64_t PlatformThreadRegisters; | |
285 | #elif CPU(ARM) | |
286 | typedef arm_thread_state_t PlatformThreadRegisters; | |
287 | #elif CPU(ARM64) | |
288 | typedef arm_thread_state64_t PlatformThreadRegisters; | |
289 | #else | |
290 | #error Unknown Architecture | |
291 | #endif | |
292 | ||
293 | #elif OS(WINDOWS) | |
294 | typedef CONTEXT PlatformThreadRegisters; | |
295 | #elif USE(PTHREADS) | |
296 | typedef pthread_attr_t PlatformThreadRegisters; | |
297 | #else | |
298 | #error Need a thread register struct for this platform | |
299 | #endif | |
300 | ||
301 | static size_t getPlatformThreadRegisters(const PlatformThread& platformThread, PlatformThreadRegisters& regs) | |
302 | { | |
303 | #if OS(DARWIN) | |
304 | ||
305 | #if CPU(X86) | |
306 | unsigned user_count = sizeof(regs)/sizeof(int); | |
307 | thread_state_flavor_t flavor = i386_THREAD_STATE; | |
308 | #elif CPU(X86_64) | |
309 | unsigned user_count = x86_THREAD_STATE64_COUNT; | |
310 | thread_state_flavor_t flavor = x86_THREAD_STATE64; | |
311 | #elif CPU(PPC) | |
312 | unsigned user_count = PPC_THREAD_STATE_COUNT; | |
313 | thread_state_flavor_t flavor = PPC_THREAD_STATE; | |
314 | #elif CPU(PPC64) | |
315 | unsigned user_count = PPC_THREAD_STATE64_COUNT; | |
316 | thread_state_flavor_t flavor = PPC_THREAD_STATE64; | |
317 | #elif CPU(ARM) | |
318 | unsigned user_count = ARM_THREAD_STATE_COUNT; | |
319 | thread_state_flavor_t flavor = ARM_THREAD_STATE; | |
320 | #elif CPU(ARM64) | |
321 | unsigned user_count = ARM_THREAD_STATE64_COUNT; | |
322 | thread_state_flavor_t flavor = ARM_THREAD_STATE64; | |
323 | #else | |
324 | #error Unknown Architecture | |
325 | #endif | |
326 | ||
327 | kern_return_t result = thread_get_state(platformThread, flavor, (thread_state_t)®s, &user_count); | |
328 | if (result != KERN_SUCCESS) { | |
329 | WTFReportFatalError(__FILE__, __LINE__, WTF_PRETTY_FUNCTION, | |
330 | "JavaScript garbage collection failed because thread_get_state returned an error (%d). This is probably the result of running inside Rosetta, which is not supported.", result); | |
331 | CRASH(); | |
332 | } | |
333 | return user_count * sizeof(usword_t); | |
334 | // end OS(DARWIN) | |
335 | ||
336 | #elif OS(WINDOWS) | |
337 | regs.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL; | |
338 | GetThreadContext(platformThread, ®s); | |
339 | return sizeof(CONTEXT); | |
340 | #elif USE(PTHREADS) | |
341 | pthread_attr_init(®s); | |
342 | #if HAVE(PTHREAD_NP_H) || OS(NETBSD) | |
343 | #if !OS(OPENBSD) | |
344 | // e.g. on FreeBSD 5.4, neundorf@kde.org | |
345 | pthread_attr_get_np(platformThread, ®s); | |
346 | #endif | |
347 | #else | |
348 | // FIXME: this function is non-portable; other POSIX systems may have different np alternatives | |
349 | pthread_getattr_np(platformThread, ®s); | |
350 | #endif | |
351 | return 0; | |
352 | #else | |
353 | #error Need a way to get thread registers on this platform | |
354 | #endif | |
355 | } | |
356 | ||
357 | static inline void* otherThreadStackPointer(const PlatformThreadRegisters& regs) | |
358 | { | |
359 | #if OS(DARWIN) | |
360 | ||
361 | #if __DARWIN_UNIX03 | |
362 | ||
363 | #if CPU(X86) | |
364 | return reinterpret_cast<void*>(regs.__esp); | |
365 | #elif CPU(X86_64) | |
366 | return reinterpret_cast<void*>(regs.__rsp); | |
367 | #elif CPU(PPC) || CPU(PPC64) | |
368 | return reinterpret_cast<void*>(regs.__r1); | |
369 | #elif CPU(ARM) | |
370 | return reinterpret_cast<void*>(regs.__sp); | |
371 | #elif CPU(ARM64) | |
372 | return reinterpret_cast<void*>(regs.__sp); | |
373 | #else | |
374 | #error Unknown Architecture | |
375 | #endif | |
376 | ||
377 | #else // !__DARWIN_UNIX03 | |
378 | ||
379 | #if CPU(X86) | |
380 | return reinterpret_cast<void*>(regs.esp); | |
381 | #elif CPU(X86_64) | |
382 | return reinterpret_cast<void*>(regs.rsp); | |
383 | #elif CPU(PPC) || CPU(PPC64) | |
384 | return reinterpret_cast<void*>(regs.r1); | |
385 | #else | |
386 | #error Unknown Architecture | |
387 | #endif | |
388 | ||
389 | #endif // __DARWIN_UNIX03 | |
390 | ||
391 | // end OS(DARWIN) | |
392 | #elif OS(WINDOWS) | |
393 | ||
394 | #if CPU(ARM) | |
395 | return reinterpret_cast<void*>((uintptr_t) regs.Sp); | |
396 | #elif CPU(MIPS) | |
397 | return reinterpret_cast<void*>((uintptr_t) regs.IntSp); | |
398 | #elif CPU(X86) | |
399 | return reinterpret_cast<void*>((uintptr_t) regs.Esp); | |
400 | #elif CPU(X86_64) | |
401 | return reinterpret_cast<void*>((uintptr_t) regs.Rsp); | |
402 | #else | |
403 | #error Unknown Architecture | |
404 | #endif | |
405 | ||
406 | #elif USE(PTHREADS) | |
407 | void* stackBase = 0; | |
408 | size_t stackSize = 0; | |
409 | #if OS(OPENBSD) | |
410 | stack_t ss; | |
411 | int rc = pthread_stackseg_np(pthread_self(), &ss); | |
412 | stackBase = (void*)((size_t) ss.ss_sp - ss.ss_size); | |
413 | stackSize = ss.ss_size; | |
414 | #else | |
415 | int rc = pthread_attr_getstack(®s, &stackBase, &stackSize); | |
416 | #endif | |
417 | (void)rc; // FIXME: Deal with error code somehow? Seems fatal. | |
418 | ASSERT(stackBase); | |
419 | return static_cast<char*>(stackBase) + stackSize; | |
420 | #else | |
421 | #error Need a way to get the stack pointer for another thread on this platform | |
422 | #endif | |
423 | } | |
424 | ||
425 | static void freePlatformThreadRegisters(PlatformThreadRegisters& regs) | |
426 | { | |
427 | #if USE(PTHREADS) && !OS(WINDOWS) && !OS(DARWIN) | |
428 | pthread_attr_destroy(®s); | |
429 | #else | |
430 | UNUSED_PARAM(regs); | |
431 | #endif | |
432 | } | |
433 | ||
434 | void MachineThreads::gatherFromOtherThread(ConservativeRoots& conservativeRoots, Thread* thread, JITStubRoutineSet& jitStubRoutines, CodeBlockSet& codeBlocks) | |
435 | { | |
436 | PlatformThreadRegisters regs; | |
437 | size_t regSize = getPlatformThreadRegisters(thread->platformThread, regs); | |
438 | ||
439 | conservativeRoots.add(static_cast<void*>(®s), static_cast<void*>(reinterpret_cast<char*>(®s) + regSize), jitStubRoutines, codeBlocks); | |
440 | ||
441 | void* stackPointer = otherThreadStackPointer(regs); | |
442 | void* stackBase = thread->stackBase; | |
443 | swapIfBackwards(stackPointer, stackBase); | |
444 | stackPointer = reinterpret_cast<void*>(WTF::roundUpToMultipleOf<sizeof(void*)>(reinterpret_cast<uintptr_t>(stackPointer))); | |
445 | conservativeRoots.add(stackPointer, stackBase, jitStubRoutines, codeBlocks); | |
446 | ||
447 | freePlatformThreadRegisters(regs); | |
448 | } | |
449 | ||
450 | void MachineThreads::gatherConservativeRoots(ConservativeRoots& conservativeRoots, JITStubRoutineSet& jitStubRoutines, CodeBlockSet& codeBlocks, void* stackCurrent, RegisterState& registers) | |
451 | { | |
452 | gatherFromCurrentThread(conservativeRoots, jitStubRoutines, codeBlocks, stackCurrent, registers); | |
453 | ||
454 | if (m_threadSpecific) { | |
455 | PlatformThread currentPlatformThread = getCurrentPlatformThread(); | |
456 | ||
457 | MutexLocker lock(m_registeredThreadsMutex); | |
458 | ||
459 | Thread* threadsToBeDeleted = nullptr; | |
460 | ||
461 | #ifndef NDEBUG | |
462 | // Forbid malloc during the gather phase. The gather phase suspends | |
463 | // threads, so a malloc during gather would risk a deadlock with a | |
464 | // thread that had been suspended while holding the malloc lock. | |
465 | fastMallocForbid(); | |
466 | #endif | |
467 | int numberOfThreads = 0; // Using 0 to denote that we haven't counted the number of threads yet. | |
468 | int index = 1; | |
469 | Thread* previousThread = nullptr; | |
470 | for (Thread* thread = m_registeredThreads; thread; index++) { | |
471 | if (!equalThread(thread->platformThread, currentPlatformThread)) { | |
472 | bool success = suspendThread(thread->platformThread); | |
473 | #if OS(DARWIN) | |
474 | if (!success) { | |
475 | if (!numberOfThreads) { | |
476 | for (Thread* countedThread = m_registeredThreads; countedThread; countedThread = countedThread->next) | |
477 | numberOfThreads++; | |
478 | } | |
479 | ||
480 | // Re-do the suspension to get the actual failure result for logging. | |
481 | kern_return_t error = thread_suspend(thread->platformThread); | |
482 | ASSERT(error != KERN_SUCCESS); | |
483 | ||
484 | WTFReportError(__FILE__, __LINE__, WTF_PRETTY_FUNCTION, | |
485 | "JavaScript garbage collection encountered an invalid thread (err 0x%x): Thread [%d/%d: %p] platformThread %p.", | |
486 | error, index, numberOfThreads, thread, reinterpret_cast<void*>(thread->platformThread)); | |
487 | ||
488 | // Put the invalid thread on the threadsToBeDeleted list. | |
489 | // We can't just delete it here because we have suspended other | |
490 | // threads, and they may still be holding the C heap lock which | |
491 | // we need for deleting the invalid thread. Hence, we need to | |
492 | // defer the deletion till after we have resumed all threads. | |
493 | Thread* nextThread = thread->next; | |
494 | thread->next = threadsToBeDeleted; | |
495 | threadsToBeDeleted = thread; | |
496 | ||
497 | if (previousThread) | |
498 | previousThread->next = nextThread; | |
499 | else | |
500 | m_registeredThreads = nextThread; | |
501 | thread = nextThread; | |
502 | continue; | |
503 | } | |
504 | #else | |
505 | UNUSED_PARAM(numberOfThreads); | |
506 | ASSERT_UNUSED(success, success); | |
507 | #endif | |
508 | } | |
509 | previousThread = thread; | |
510 | thread = thread->next; | |
511 | } | |
512 | ||
513 | // It is safe to access the registeredThreads list, because we earlier asserted that locks are being held, | |
514 | // and since this is a shared heap, they are real locks. | |
515 | for (Thread* thread = m_registeredThreads; thread; thread = thread->next) { | |
516 | if (!equalThread(thread->platformThread, currentPlatformThread)) | |
517 | gatherFromOtherThread(conservativeRoots, thread, jitStubRoutines, codeBlocks); | |
518 | } | |
519 | ||
520 | for (Thread* thread = m_registeredThreads; thread; thread = thread->next) { | |
521 | if (!equalThread(thread->platformThread, currentPlatformThread)) | |
522 | resumeThread(thread->platformThread); | |
523 | } | |
524 | ||
525 | #ifndef NDEBUG | |
526 | fastMallocAllow(); | |
527 | #endif | |
528 | for (Thread* thread = threadsToBeDeleted; thread; ) { | |
529 | Thread* nextThread = thread->next; | |
530 | delete thread; | |
531 | thread = nextThread; | |
532 | } | |
533 | } | |
534 | } | |
535 | ||
536 | } // namespace JSC |