summaryrefslogtreecommitdiff
path: root/libgo/runtime/go-go.c
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/runtime/go-go.c')
-rw-r--r--libgo/runtime/go-go.c656
1 files changed, 656 insertions, 0 deletions
diff --git a/libgo/runtime/go-go.c b/libgo/runtime/go-go.c
new file mode 100644
index 000000000..3d8e9e629
--- /dev/null
+++ b/libgo/runtime/go-go.c
@@ -0,0 +1,656 @@
+/* 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 <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <semaphore.h>
+
+#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 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;
+
+ /* This will look runtime_mheap as needed. */
+ runtime_MCache_ReleaseAll (mcache);
+
+ /* This should never deadlock--there shouldn't be any code that
+ holds the runtime_mheap lock when locking __go_thread_ids_lock.
+ We don't want to do this after releasing __go_thread_ids_lock
+ because it will mean that the garbage collector might run, and
+ the garbage collector does not try to lock runtime_mheap in all
+ cases since it knows it is running single-threaded. */
+ 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);
+
+ /* As soon as we release this look, a GC could run. Since this
+ thread is no longer on the list, the GC will not find our M
+ structure, so it could get freed at any time. That means that
+ any code from here to thread exit must not assume that m is
+ valid. */
+ m = NULL;
+
+ i = pthread_mutex_unlock (&__go_thread_ids_lock);
+ __go_assert (i == 0);
+
+ 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) &top;
+ 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->holds_finlock, 1, 1))
+ {
+ /* We can't interrupt the thread while it holds the finalizer
+ lock. Otherwise we can get into a deadlock when mark calls
+ runtime_walkfintab. */
+ __sync_bool_compare_and_swap (&pm->gcing_for_finlock, 0, 1);
+ return;
+ }
+
+ 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. */
+ __builtin_unwind_init ();
+
+ 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. */
+ __builtin_unwind_init ();
+
+ 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) &top;
+ 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
+}