/* go-go.c -- the go function. Copyright 2009 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ #include #include #include #include #include #include #include #include "config.h" #include "go-assert.h" #include "go-panic.h" #include "go-alloc.h" #include "runtime.h" #include "malloc.h" #ifdef USING_SPLIT_STACK /* FIXME: This is not declared anywhere. */ extern void *__splitstack_find (void *, void *, size_t *, void **, void **, void **); #endif /* We need to ensure that all callee-saved registers are stored on the stack, in case they hold pointers. */ #if defined(__i386__) #ifndef __PIC__ #define SAVE_REGS asm ("" : : : "esi", "edi", "ebx") #else #define SAVE_REGS asm ("" : : : "esi", "edi") #endif #elif defined(__x86_64__) #define SAVE_REGS asm ("" : : : "r12", "r13", "r14", "r15", "rbp", "rbx") #else #error must define SAVE_REGS #endif /* We stop the threads by sending them the signal GO_SIG_STOP and we start them by sending them the signal GO_SIG_START. */ #define GO_SIG_START (SIGRTMIN + 1) #define GO_SIG_STOP (SIGRTMIN + 2) #ifndef SA_RESTART #define SA_RESTART 0 #endif /* A doubly linked list of the threads we have started. */ struct __go_thread_id { /* Links. */ struct __go_thread_id *prev; struct __go_thread_id *next; /* True if the thread ID has not yet been filled in. */ _Bool tentative; /* Thread ID. */ pthread_t id; /* Thread's M structure. */ struct M *m; /* If the thread ID has not been filled in, the function we are running. */ void (*pfn) (void *); /* If the thread ID has not been filled in, the argument to the function. */ void *arg; }; static struct __go_thread_id *__go_all_thread_ids; /* A lock to control access to ALL_THREAD_IDS. */ static pthread_mutex_t __go_thread_ids_lock = PTHREAD_MUTEX_INITIALIZER; /* A semaphore used to wait until all the threads have stopped. */ static sem_t __go_thread_ready_sem; /* A signal set used to wait until garbage collection is complete. */ static sigset_t __go_thread_wait_sigset; /* Remove the current thread from the list of threads. */ static void remove_current_thread (void) { struct __go_thread_id *list_entry; MCache *mcache; int i; list_entry = m->list_entry; mcache = m->mcache; i = pthread_mutex_lock (&__go_thread_ids_lock); __go_assert (i == 0); if (list_entry->prev != NULL) list_entry->prev->next = list_entry->next; else __go_all_thread_ids = list_entry->next; if (list_entry->next != NULL) list_entry->next->prev = list_entry->prev; runtime_MCache_ReleaseAll (mcache); i = pthread_mutex_unlock (&__go_thread_ids_lock); __go_assert (i == 0); runtime_lock (&runtime_mheap); mstats.heap_alloc += mcache->local_alloc; mstats.heap_objects += mcache->local_objects; __builtin_memset (mcache, 0, sizeof (struct MCache)); runtime_FixAlloc_Free (&runtime_mheap.cachealloc, mcache); runtime_unlock (&runtime_mheap); free (list_entry); } /* Start the thread. */ static void * start_go_thread (void *thread_arg) { struct M *newm = (struct M *) thread_arg; void (*pfn) (void *); void *arg; struct __go_thread_id *list_entry; int i; #ifdef __rtems__ __wrap_rtems_task_variable_add ((void **) &m); __wrap_rtems_task_variable_add ((void **) &__go_panic_defer); #endif m = newm; list_entry = newm->list_entry; pfn = list_entry->pfn; arg = list_entry->arg; #ifndef USING_SPLIT_STACK /* If we don't support split stack, record the current stack as the top of the stack. There shouldn't be anything relevant to the garbage collector above this point. */ m->gc_sp = (void *) &arg; #endif /* Finish up the entry on the thread list. */ i = pthread_mutex_lock (&__go_thread_ids_lock); __go_assert (i == 0); list_entry->id = pthread_self (); list_entry->pfn = NULL; list_entry->arg = NULL; list_entry->tentative = 0; i = pthread_mutex_unlock (&__go_thread_ids_lock); __go_assert (i == 0); (*pfn) (arg); remove_current_thread (); return NULL; } /* The runtime.Goexit function. */ void Goexit (void) asm ("libgo_runtime.runtime.Goexit"); void Goexit (void) { remove_current_thread (); pthread_exit (NULL); abort (); } /* Implement the go statement. */ void __go_go (void (*pfn) (void*), void *arg) { int i; pthread_attr_t attr; struct M *newm; struct __go_thread_id *list_entry; pthread_t tid; i = pthread_attr_init (&attr); __go_assert (i == 0); i = pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); __go_assert (i == 0); #ifdef LINKER_SUPPORTS_SPLIT_STACK /* The linker knows how to handle calls between code which uses -fsplit-stack and code which does not. That means that we can run with a smaller stack and rely on the -fsplit-stack support to save us. The GNU/Linux glibc library won't let us have a very small stack, but we make it as small as we can. */ #ifndef PTHREAD_STACK_MIN #define PTHREAD_STACK_MIN 8192 #endif i = pthread_attr_setstacksize (&attr, PTHREAD_STACK_MIN); __go_assert (i == 0); #endif newm = __go_alloc (sizeof (M)); list_entry = malloc (sizeof (struct __go_thread_id)); list_entry->prev = NULL; list_entry->next = NULL; list_entry->tentative = 1; list_entry->m = newm; list_entry->pfn = pfn; list_entry->arg = arg; newm->list_entry = list_entry; newm->mcache = runtime_allocmcache (); /* Add the thread to the list of all threads, marked as tentative since it is not yet ready to go. */ i = pthread_mutex_lock (&__go_thread_ids_lock); __go_assert (i == 0); if (__go_all_thread_ids != NULL) __go_all_thread_ids->prev = list_entry; list_entry->next = __go_all_thread_ids; __go_all_thread_ids = list_entry; i = pthread_mutex_unlock (&__go_thread_ids_lock); __go_assert (i == 0); /* Start the thread. */ i = pthread_create (&tid, &attr, start_go_thread, newm); __go_assert (i == 0); i = pthread_attr_destroy (&attr); __go_assert (i == 0); } /* This is the signal handler for GO_SIG_START. The garbage collector will send this signal to a thread when it wants the thread to start. We don't have to actually do anything here, but we need a signal handler since ignoring the signal will mean that the sigsuspend will never see it. */ static void gc_start_handler (int sig __attribute__ ((unused))) { } /* Tell the garbage collector that we are ready, and wait for the garbage collector to tell us that it is done. This may be called by a signal handler, so it is restricted to using functions which are async cancel safe. */ static void stop_for_gc (void) { int i; /* Tell the garbage collector about our stack. */ #ifdef USING_SPLIT_STACK m->gc_sp = __splitstack_find (NULL, NULL, &m->gc_len, &m->gc_next_segment, &m->gc_next_sp, &m->gc_initial_sp); #else { uintptr_t top = (uintptr_t) m->gc_sp; uintptr_t bottom = (uintptr_t) ⊤ if (top < bottom) { m->gc_next_sp = m->gc_sp; m->gc_len = bottom - top; } else { m->gc_next_sp = (void *) bottom; m->gc_len = top - bottom; } } #endif /* FIXME: Perhaps we should just move __go_panic_defer into M. */ m->gc_panic_defer = __go_panic_defer; /* Tell the garbage collector that we are ready by posting to the semaphore. */ i = sem_post (&__go_thread_ready_sem); __go_assert (i == 0); /* Wait for the garbage collector to tell us to continue. */ sigsuspend (&__go_thread_wait_sigset); } /* This is the signal handler for GO_SIG_STOP. The garbage collector will send this signal to a thread when it wants the thread to stop. */ static void gc_stop_handler (int sig __attribute__ ((unused))) { struct M *pm = m; if (__sync_bool_compare_and_swap (&pm->mallocing, 1, 1)) { /* m->mallocing was already non-zero. We can't interrupt the thread while it is running an malloc. Instead, tell it to call back to us when done. */ __sync_bool_compare_and_swap (&pm->gcing, 0, 1); return; } if (__sync_bool_compare_and_swap (&pm->nomemprof, 1, 1)) { /* Similarly, we can't interrupt the thread while it is building profiling information. Otherwise we can get into a deadlock when sweepspan calls MProf_Free. */ __sync_bool_compare_and_swap (&pm->gcing_for_prof, 0, 1); return; } stop_for_gc (); } /* This is called by malloc when it gets a signal during the malloc call itself. */ int __go_run_goroutine_gc (int r) { /* Force callee-saved registers to be saved on the stack. This is not needed if we are invoked from the signal handler, but it is needed if we are called directly, since otherwise we might miss something that a function somewhere up the call stack is holding in a register. */ SAVE_REGS; stop_for_gc (); /* This avoids tail recursion, to make sure that the saved registers are on the stack. */ return r; } /* Stop all the other threads for garbage collection. */ void runtime_stoptheworld (void) { int i; pthread_t me; int c; struct __go_thread_id *p; i = pthread_mutex_lock (&__go_thread_ids_lock); __go_assert (i == 0); me = pthread_self (); c = 0; p = __go_all_thread_ids; while (p != NULL) { if (p->tentative || pthread_equal (me, p->id)) p = p->next; else { i = pthread_kill (p->id, GO_SIG_STOP); if (i == 0) { ++c; p = p->next; } else if (i == ESRCH) { struct __go_thread_id *next; /* This thread died somehow. Remove it from the list. */ next = p->next; if (p->prev != NULL) p->prev->next = next; else __go_all_thread_ids = next; if (next != NULL) next->prev = p->prev; free (p); p = next; } else abort (); } } /* Wait for each thread to receive the signal and post to the semaphore. If a thread receives the signal but contrives to die before it posts to the semaphore, then we will hang forever here. */ while (c > 0) { i = sem_wait (&__go_thread_ready_sem); if (i < 0 && errno == EINTR) continue; __go_assert (i == 0); --c; } /* The gc_panic_defer field should now be set for all M's except the one in this thread. Set this one now. */ m->gc_panic_defer = __go_panic_defer; /* Leave with __go_thread_ids_lock held. */ } /* Scan all the stacks for garbage collection. This should be called with __go_thread_ids_lock held. */ void __go_scanstacks (void (*scan) (byte *, int64)) { pthread_t me; struct __go_thread_id *p; /* Make sure all the registers for this thread are on the stack. */ SAVE_REGS; me = pthread_self (); for (p = __go_all_thread_ids; p != NULL; p = p->next) { if (p->tentative) { /* The goroutine function and argument can be allocated on the heap, so we have to scan them for a thread that has not yet started. */ scan ((void *) &p->pfn, sizeof (void *)); scan ((void *) &p->arg, sizeof (void *)); scan ((void *) &p->m, sizeof (void *)); continue; } #ifdef USING_SPLIT_STACK void *sp; size_t len; void *next_segment; void *next_sp; void *initial_sp; if (pthread_equal (me, p->id)) { next_segment = NULL; next_sp = NULL; initial_sp = NULL; sp = __splitstack_find (NULL, NULL, &len, &next_segment, &next_sp, &initial_sp); } else { sp = p->m->gc_sp; len = p->m->gc_len; next_segment = p->m->gc_next_segment; next_sp = p->m->gc_next_sp; initial_sp = p->m->gc_initial_sp; } while (sp != NULL) { scan (sp, len); sp = __splitstack_find (next_segment, next_sp, &len, &next_segment, &next_sp, &initial_sp); } #else /* !defined(USING_SPLIT_STACK) */ if (pthread_equal (me, p->id)) { uintptr_t top = (uintptr_t) m->gc_sp; uintptr_t bottom = (uintptr_t) ⊤ if (top < bottom) scan (m->gc_sp, bottom - top); else scan ((void *) bottom, top - bottom); } else { scan (p->m->gc_next_sp, p->m->gc_len); } #endif /* !defined(USING_SPLIT_STACK) */ /* Also scan the M structure while we're at it. */ scan ((void *) &p->m, sizeof (void *)); } } /* Release all the memory caches. This is called with __go_thread_ids_lock held. */ void __go_stealcache (void) { struct __go_thread_id *p; for (p = __go_all_thread_ids; p != NULL; p = p->next) runtime_MCache_ReleaseAll (p->m->mcache); } /* Gather memory cache statistics. This is called with __go_thread_ids_lock held. */ void __go_cachestats (void) { struct __go_thread_id *p; for (p = __go_all_thread_ids; p != NULL; p = p->next) { MCache *c; c = p->m->mcache; mstats.heap_alloc += c->local_alloc; c->local_alloc = 0; mstats.heap_objects += c->local_objects; c->local_objects = 0; } } /* Start the other threads after garbage collection. */ void runtime_starttheworld (void) { int i; pthread_t me; struct __go_thread_id *p; /* Here __go_thread_ids_lock should be held. */ me = pthread_self (); p = __go_all_thread_ids; while (p != NULL) { if (p->tentative || pthread_equal (me, p->id)) p = p->next; else { i = pthread_kill (p->id, GO_SIG_START); if (i == 0) p = p->next; else abort (); } } i = pthread_mutex_unlock (&__go_thread_ids_lock); __go_assert (i == 0); } /* Initialize the interaction between goroutines and the garbage collector. */ void __go_gc_goroutine_init (void *sp __attribute__ ((unused))) { struct __go_thread_id *list_entry; int i; sigset_t sset; struct sigaction act; /* Add the initial thread to the list of all threads. */ list_entry = malloc (sizeof (struct __go_thread_id)); list_entry->prev = NULL; list_entry->next = NULL; list_entry->tentative = 0; list_entry->id = pthread_self (); list_entry->m = m; list_entry->pfn = NULL; list_entry->arg = NULL; __go_all_thread_ids = list_entry; /* Initialize the semaphore which signals when threads are ready for GC. */ i = sem_init (&__go_thread_ready_sem, 0, 0); __go_assert (i == 0); /* Fetch the current signal mask. */ i = sigemptyset (&sset); __go_assert (i == 0); i = sigprocmask (SIG_BLOCK, NULL, &sset); __go_assert (i == 0); /* Make sure that GO_SIG_START is not blocked and GO_SIG_STOP is blocked, and save that set for use with later calls to sigsuspend while waiting for GC to complete. */ i = sigdelset (&sset, GO_SIG_START); __go_assert (i == 0); i = sigaddset (&sset, GO_SIG_STOP); __go_assert (i == 0); __go_thread_wait_sigset = sset; /* Block SIG_SET_START and unblock SIG_SET_STOP, and use that for the process signal mask. */ i = sigaddset (&sset, GO_SIG_START); __go_assert (i == 0); i = sigdelset (&sset, GO_SIG_STOP); __go_assert (i == 0); i = sigprocmask (SIG_SETMASK, &sset, NULL); __go_assert (i == 0); /* Install the signal handlers. */ memset (&act, 0, sizeof act); i = sigemptyset (&act.sa_mask); __go_assert (i == 0); act.sa_handler = gc_start_handler; act.sa_flags = SA_RESTART; i = sigaction (GO_SIG_START, &act, NULL); __go_assert (i == 0); /* We could consider using an alternate signal stack for this. The function does not use much stack space, so it may be OK. */ act.sa_handler = gc_stop_handler; i = sigaction (GO_SIG_STOP, &act, NULL); __go_assert (i == 0); #ifndef USING_SPLIT_STACK /* If we don't support split stack, record the current stack as the top of the stack. */ m->gc_sp = sp; #endif }