diff options
author | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
---|---|---|
committer | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
commit | 554fd8c5195424bdbcabf5de30fdc183aba391bd (patch) | |
tree | 976dc5ab7fddf506dadce60ae936f43f58787092 /libgo/runtime/go-go.c | |
download | cbb-gcc-4.6.4-upstream.tar.bz2 cbb-gcc-4.6.4-upstream.tar.xz |
obtained gcc-4.6.4.tar.bz2 from upstream website;upstream
verified gcc-4.6.4.tar.bz2.sig;
imported gcc-4.6.4 source tree from verified upstream tarball.
downloading a git-generated archive based on the 'upstream' tag
should provide you with a source tree that is binary identical
to the one extracted from the above tarball.
if you have obtained the source via the command 'git clone',
however, do note that line-endings of files in your working
directory might differ from line-endings of the respective
files in the upstream repository.
Diffstat (limited to 'libgo/runtime/go-go.c')
-rw-r--r-- | libgo/runtime/go-go.c | 656 |
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) ⊤ + 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) ⊤ + 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 +} |