This patch adds some support for context switching to the -fsplit-stack support routines in libgcc. I intend to use this in the Go library to support multiplexing multiple goroutines onto a single pthread. They work similarly to getcontext/setcontext/makecontext.
This patch also adds control over whether the -fsplit-stack support routines block signals or not. This is intended for use by programs in which most threads block signals. If the thread already blocks signals, there is no need for the -fsplit-stack routines to also block signals. This is important because it is one of the slowest parts of splitting the stack at present. I believe that the only part of this patch which requires approval is the small new function in target-supports.exp. I would of course appreciate comments on the API and ABI of the other functions. I have not yet completed, or even really started, the work in the Go library, but I want to get these functions in before the 4.7 cutoff. I will continue working on the Go library after the cutoff, as changes there will only affect Go. My goal is to have gc 4.7 have full support for the Go 1 release, which will be a stable version of Go to be supported for at least a year. Ian libgcc/ChangeLog: 2011-11-07 Ian Lance Taylor <i...@google.com> * generic-morestack.c: Include <string.h>. (uintptr_type): Define. (struct initial_sp): Add dont_block_signals field. Reduce size of extra array by 1. (allocate_segment): Set prev field to NULL. Don't set __morestack_current_segment or __morestack_segments. (__generic_morestack): Update current->prev and *pp after calling allocate_segment. (__morestack_block_signals): Don't do anything if dont_block_signals is set. (__morestack_unblock_signals): Likewise. (__generic_findstack): Check for initial_sp == NULL. Add casts to uintptr_type. (__splitstack_block_signals): New function. (enum __splitstack_content_offsets): Define. (__splitstack_getcontext, __splitstack_setcontext): New functions. (__splitstack_makecontext): New function. (__splitstack_block_signals_context): New function. (__splitstack_find_context): New function. * config/i386/morestack.S (__morestack_get_guard): New function. (__morestack_set_guard, __morestack_make_guard): New functions. * libgcc-std.ver.in: Add new functions to GCC_4.7.0. gcc/testsuite/ChangeLog: 2011-11-07 Ian Lance Taylor <i...@google.com> * lib/target-supports.exp (check_effective_target_ucontext_h): New procedure. * gcc.dg/split-5.c: New test.
Index: libgcc/generic-morestack.c =================================================================== --- libgcc/generic-morestack.c (revision 181110) +++ libgcc/generic-morestack.c (working copy) @@ -41,12 +41,15 @@ see the files COPYING3 and COPYING.RUNTI #include <errno.h> #include <signal.h> #include <stdlib.h> +#include <string.h> #include <unistd.h> #include <sys/mman.h> #include <sys/uio.h> #include "generic-morestack.h" +typedef unsigned uintptr_type __attribute__ ((mode (pointer))); + /* This file contains subroutines that are used by code compiled with -fsplit-stack. */ @@ -88,14 +91,50 @@ extern void * __morestack_allocate_stack_space (size_t size) __attribute__ ((visibility ("hidden"))); -/* This is a function which -fsplit-stack code can call to get a list - of the stacks. Since it is not called only by the compiler, it is - not hidden. */ +/* These are functions which -fsplit-stack code can call. These are + not called by the compiler, and are not hidden. FIXME: These + should be in some header file somewhere, somehow. */ extern void * __splitstack_find (void *, void *, size_t *, void **, void **, void **) __attribute__ ((visibility ("default"))); +extern void +__splitstack_block_signals (int *, int *) + __attribute__ ((visibility ("default"))); + +extern void +__splitstack_getcontext (void *context[10]) + __attribute__ ((no_split_stack, visibility ("default"))); + +extern void +__splitstack_setcontext (void *context[10]) + __attribute__ ((no_split_stack, visibility ("default"))); + +extern void * +__splitstack_makecontext (size_t, void *context[10], size_t *) + __attribute__ ((visibility ("default"))); + +extern void +__splitstack_block_signals_context (void *context[10], int *, int *) + __attribute__ ((visibility ("default"))); + +extern void * +__splitstack_find_context (void *context[10], size_t *, void **, void **, + void **) + __attribute__ ((visibility ("default"))); + +/* These functions must be defined by the processor specific code. */ + +extern void *__morestack_get_guard (void) + __attribute__ ((no_split_stack, visibility ("hidden"))); + +extern void __morestack_set_guard (void *) + __attribute__ ((no_split_stack, visibility ("hidden"))); + +extern void *__morestack_make_guard (void *, size_t) + __attribute__ ((no_split_stack, visibility ("hidden"))); + /* When we allocate a stack segment we put this header at the start. */ @@ -138,8 +177,13 @@ struct initial_sp /* A signal mask, put here so that the thread can use it without needing stack space. */ sigset_t mask; + /* Non-zero if we should not block signals. This is a reversed flag + so that the default zero value is the safe value. The type is + uintptr_type because it replaced one of the void * pointers in + extra. */ + uintptr_type dont_block_signals; /* Some extra space for later extensibility. */ - void *extra[5]; + void *extra[4]; }; /* A list of memory blocks allocated by dynamic stack allocation. @@ -339,18 +383,13 @@ allocate_segment (size_t frame_size) pss = (struct stack_segment *) space; - pss->prev = __morestack_current_segment; + pss->prev = NULL; pss->next = NULL; pss->size = allocate - overhead; pss->dynamic_allocation = NULL; pss->free_dynamic_allocation = NULL; pss->extra = NULL; - if (__morestack_current_segment != NULL) - __morestack_current_segment->next = pss; - else - __morestack_segments = pss; - return pss; } @@ -513,7 +552,11 @@ __generic_morestack (size_t *pframe_size current = *pp; if (current == NULL) - current = allocate_segment (frame_size + param_size); + { + current = allocate_segment (frame_size + param_size); + current->prev = __morestack_current_segment; + *pp = current; + } current->old_stack = old_stack; @@ -614,7 +657,9 @@ extern int pthread_sigmask (int, const s void __morestack_block_signals (void) { - if (pthread_sigmask) + if (__morestack_initial_sp.dont_block_signals) + ; + else if (pthread_sigmask) pthread_sigmask (SIG_BLOCK, &__morestack_fullmask, &__morestack_initial_sp.mask); else @@ -627,7 +672,9 @@ __morestack_block_signals (void) void __morestack_unblock_signals (void) { - if (pthread_sigmask) + if (__morestack_initial_sp.dont_block_signals) + ; + else if (pthread_sigmask) pthread_sigmask (SIG_SETMASK, &__morestack_initial_sp.mask, NULL); else sigprocmask (SIG_SETMASK, &__morestack_initial_sp.mask, NULL); @@ -727,6 +774,10 @@ __generic_findstack (void *stack) } /* We have popped back to the original stack. */ + + if (__morestack_initial_sp.sp == NULL) + return 0; + #ifdef STACK_GROWS_DOWNWARD if ((char *) stack >= (char *) __morestack_initial_sp.sp) used = 0; @@ -796,11 +847,14 @@ __splitstack_find (void *segment_arg, vo void *ret; char *nsp; - if (segment_arg == (void *) 1) + if (segment_arg == (void *) (uintptr_type) 1) { char *isp = (char *) *initial_sp; - *next_segment = (void *) 2; + if (isp == NULL) + return NULL; + + *next_segment = (void *) (uintptr_type) 2; *next_sp = NULL; #ifdef STACK_GROWS_DOWNWARD if ((char *) sp >= isp) @@ -814,7 +868,7 @@ __splitstack_find (void *segment_arg, vo return (void *) isp; #endif } - else if (segment_arg == (void *) 2) + else if (segment_arg == (void *) (uintptr_type) 2) return NULL; else if (segment_arg != NULL) segment = (struct stack_segment *) segment_arg; @@ -826,8 +880,8 @@ __splitstack_find (void *segment_arg, vo while (1) { if (segment == NULL) - return __splitstack_find ((void *) 1, sp, len, next_segment, - next_sp, initial_sp); + return __splitstack_find ((void *) (uintptr_type) 1, sp, len, + next_segment, next_sp, initial_sp); if ((char *) sp >= (char *) (segment + 1) && (char *) sp <= (char *) (segment + 1) + segment->size) break; @@ -836,7 +890,7 @@ __splitstack_find (void *segment_arg, vo } if (segment->prev == NULL) - *next_segment = (void *) 1; + *next_segment = (void *) (uintptr_type) 1; else *next_segment = segment->prev; @@ -878,4 +932,164 @@ __splitstack_find (void *segment_arg, vo return ret; } +/* Tell the split stack code whether it has to block signals while + manipulating the stack. This is for programs in which some threads + block all signals. If a thread already blocks signals, there is no + need for the split stack code to block them as well. If NEW is not + NULL, then if *NEW is non-zero signals will be blocked while + splitting the stack, otherwise they will not. If OLD is not NULL, + *OLD will be set to the old value. */ + +void +__splitstack_block_signals (int *new, int *old) +{ + if (old != NULL) + *old = __morestack_initial_sp.dont_block_signals ? 0 : 1; + if (new != NULL) + __morestack_initial_sp.dont_block_signals = *new ? 0 : 1; +} + +/* The offsets into the arrays used by __splitstack_getcontext and + __splitstack_setcontext. */ + +enum __splitstack_context_offsets +{ + MORESTACK_SEGMENTS = 0, + CURRENT_SEGMENT = 1, + CURRENT_STACK = 2, + STACK_GUARD = 3, + INITIAL_SP = 4, + INITIAL_SP_LEN = 5, + BLOCK_SIGNALS = 6, + + NUMBER_OFFSETS = 10 +}; + +/* Get the current split stack context. This may be used for + coroutine switching, similar to getcontext. The argument should + have at least 10 void *pointers for extensibility, although we + don't currently use all of them. This would normally be called + immediately before a call to getcontext or swapcontext or + setjmp. */ + +void +__splitstack_getcontext (void *context[NUMBER_OFFSETS]) +{ + memset (context, 0, NUMBER_OFFSETS * sizeof (void *)); + context[MORESTACK_SEGMENTS] = (void *) __morestack_segments; + context[CURRENT_SEGMENT] = (void *) __morestack_current_segment; + context[CURRENT_STACK] = (void *) &context; + context[STACK_GUARD] = __morestack_get_guard (); + context[INITIAL_SP] = (void *) __morestack_initial_sp.sp; + context[INITIAL_SP_LEN] = (void *) (uintptr_type) __morestack_initial_sp.len; + context[BLOCK_SIGNALS] = (void *) __morestack_initial_sp.dont_block_signals; +} + +/* Set the current split stack context. The argument should be a + context previously passed to __splitstack_getcontext. This would + normally be called immediately after a call to getcontext or + swapcontext or setjmp if something jumped to it. */ + +void +__splitstack_setcontext (void *context[NUMBER_OFFSETS]) +{ + __morestack_segments = (struct stack_segment *) context[MORESTACK_SEGMENTS]; + __morestack_current_segment = + (struct stack_segment *) context[CURRENT_SEGMENT]; + __morestack_set_guard (context[STACK_GUARD]); + __morestack_initial_sp.sp = context[INITIAL_SP]; + __morestack_initial_sp.len = (size_t) context[INITIAL_SP_LEN]; + __morestack_initial_sp.dont_block_signals = + (uintptr_type) context[BLOCK_SIGNALS]; +} + +/* Create a new split stack context. This will allocate a new stack + segment which may be used by a coroutine. STACK_SIZE is the + minimum size of the new stack. The caller is responsible for + actually setting the stack pointer. This would normally be called + before a call to makecontext, and the returned stack pointer and + size would be used to set the uc_stack field. A function called + via makecontext on a stack created by __splitstack_makecontext may + not return. Note that the returned pointer points to the lowest + address in the stack space, and thus may not be the value to which + to set the stack pointer. */ + +void * +__splitstack_makecontext (size_t stack_size, void *context[NUMBER_OFFSETS], + size_t *size) +{ + struct stack_segment *segment; + void *initial_sp; + + memset (context, 0, NUMBER_OFFSETS * sizeof (void *)); + segment = allocate_segment (stack_size); + context[MORESTACK_SEGMENTS] = segment; + context[CURRENT_SEGMENT] = segment; +#ifdef STACK_GROWS_DOWNWARD + initial_sp = (void *) ((char *) (segment + 1) + segment->size); +#else + initial_sp = (void *) (segment + 1); +#endif + context[STACK_GUARD] = __morestack_make_guard (initial_sp, segment->size); + context[INITIAL_SP] = NULL; + context[INITIAL_SP_LEN] = 0; + *size = segment->size; + return (void *) (segment + 1); +} + +/* Like __splitstack_block_signals, but operating on CONTEXT, rather + than on the current state. */ + +void +__splitstack_block_signals_context (void *context[NUMBER_OFFSETS], int *new, + int *old) +{ + if (old != NULL) + *old = ((uintptr_type) context[BLOCK_SIGNALS]) != 0 ? 0 : 1; + if (new != NULL) + context[BLOCK_SIGNALS] = (void *) (uintptr_type) (*new ? 0 : 1); +} + +/* Find the stack segments associated with a split stack context. + This will return the address of the first stack segment and set + *STACK_SIZE to its size. It will set next_segment, next_sp, and + initial_sp which may be passed to __splitstack_find to find the + remaining segments. */ + +void * +__splitstack_find_context (void *context[NUMBER_OFFSETS], size_t *stack_size, + void **next_segment, void **next_sp, + void **initial_sp) +{ + void *sp; + struct stack_segment *segment; + + *initial_sp = context[INITIAL_SP]; + + sp = context[CURRENT_STACK]; + if (sp == NULL) + { + /* Most likely this context was created but was never used. The + value 2 is a code used by __splitstack_find to mean that we + have reached the end of the list of stacks. */ + *next_segment = (void *) (uintptr_type) 2; + *next_sp = NULL; + *initial_sp = NULL; + return NULL; + } + + segment = context[CURRENT_SEGMENT]; + if (segment == NULL) + { + /* Most likely this context was saved by a thread which was not + created using __splistack_makecontext and which has never + split the stack. The value 1 is a code used by + __splitstack_find to look at the initial stack. */ + segment = (struct stack_segment *) (uintptr_type) 1; + } + + return __splitstack_find (segment, sp, stack_size, next_segment, next_sp, + initial_sp); +} + #endif /* !defined (inhibit_libc) */ Index: libgcc/config/i386/morestack.S =================================================================== --- libgcc/config/i386/morestack.S (revision 181110) +++ libgcc/config/i386/morestack.S (working copy) @@ -1,5 +1,5 @@ # x86/x86_64 support for -fsplit-stack. -# Copyright (C) 2009, 2010 Free Software Foundation, Inc. +# Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc. # Contributed by Ian Lance Taylor <i...@google.com>. # This file is part of GCC. @@ -620,6 +620,82 @@ __stack_split_initialize: .size __stack_split_initialize, . - __stack_split_initialize #endif +# Routines to get and set the guard, for __splitstack_getcontext, +# __splitstack_setcontext, and __splitstack_makecontext. + +# void *__morestack_get_guard (void) returns the current stack guard. + .text + .global __morestack_get_guard + .hidden __morestack_get_guard + +#ifdef __ELF__ + .type __morestack_get_guard,@function +#endif + +__morestack_get_guard: + +#ifndef __x86_64__ + movl %gs:0x30,%eax +#else +#ifdef __LP64__ + movq %fs:0x70,%rax +#else + movl %fs:0x40,%eax +#endif +#endif + ret + +#ifdef __ELF__ + .size __morestack_get_guard, . - __morestack_get_guard +#endif + +# void __morestack_set_guard (void *) sets the stack guard. + .global __morestack_set_guard + .hidden __morestack_set_guard + +#ifdef __ELF__ + .type __morestack_set_guard,@function +#endif + +__morestack_set_guard: + +#ifndef __x86_64__ + movl 4(%esp),%eax + movl %eax,%gs:0x30 +#else + X86_64_SAVE_NEW_STACK_BOUNDARY (di) +#endif + ret + +#ifdef __ELF__ + .size __morestack_set_guard, . - __morestack_set_guard +#endif + +# void *__morestack_make_guard (void *, size_t) returns the stack +# guard value for a stack. + .global __morestack_make_guard + .hidden __morestack_make_guard + +#ifdef __ELF__ + .type __morestack_make_guard,@function +#endif + +__morestack_make_guard: + +#ifndef __x86_64__ + movl 4(%esp),%eax + subl 8(%esp),%eax + addl $BACKOFF,%eax +#else + subq %rsi,%rdi + addq $BACKOFF,%rdi + movq %rdi,%rax +#endif + ret + +#ifdef __ELF__ + .size __morestack_make_guard, . - __morestack_make_guard +#endif # Make __stack_split_initialize a high priority constructor. FIXME: # This is ELF specific. Index: libgcc/libgcc-std.ver.in =================================================================== --- libgcc/libgcc-std.ver.in (revision 181110) +++ libgcc/libgcc-std.ver.in (working copy) @@ -1,5 +1,5 @@ # Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, -# 2008, 2010 Free Software Foundation, Inc. +# 2008, 2010, 2011 Free Software Foundation, Inc. # # This file is part of GCC. # @@ -1926,4 +1926,10 @@ GCC_4.7.0 { __PFX__clrsbsi2 __PFX__clrsbdi2 __PFX__clrsbti2 + __splitstack_block_signals + __splitstack_getcontext + __splitstack_setcontext + __splitstack_makecontext + __splitstack_block_signals_context + __splitstack_find_context } Index: gcc/testsuite/lib/target-supports.exp =================================================================== --- gcc/testsuite/lib/target-supports.exp (revision 181110) +++ gcc/testsuite/lib/target-supports.exp (working copy) @@ -4313,3 +4313,11 @@ proc check_effective_target_non_strict_a void foo(void) { z = (c *) y; } } "-Wcast-align"] } + +# Return 1 if the target has <ucontext.h>. + +proc check_effective_target_ucontext_h { } { + return [check_no_compiler_messages ucontext_h assembly { + #include <ucontext.h> + }] +} Index: gcc/testsuite/gcc.dg/split-5.c =================================================================== --- gcc/testsuite/gcc.dg/split-5.c (revision 0) +++ gcc/testsuite/gcc.dg/split-5.c (revision 0) @@ -0,0 +1,171 @@ +/* { dg-do run } */ +/* { dg-require-effective-target split_stack } */ +/* { dg-require-effective-target pthread_h } */ +/* { dg-require-effective-target ucontext_h } */ +/* { dg-options "-pthread -fsplit-stack" } */ + +#include <stdlib.h> +#include <pthread.h> +#include <ucontext.h> + +extern void __splitstack_getcontext (void *context[10]); + +extern void __splitstack_setcontext (void *context[10]); + +extern void *__splitstack_makecontext (size_t, void *context[10], size_t *); + +extern void __splitstack_block_signals (int *, int *); + +extern void __splitstack_block_signals_context (void *context[10], int *, + int *); + +extern void *__splitstack_find (void *, void *, size_t *, void **, void **, + void **); + +extern void *__splitstack_find_context (void *context[10], size_t *, void **, + void **, void **); + +static ucontext_t c1; +static void *s1[10]; + +static ucontext_t c2; +static void *s2[10]; + +static void swap (ucontext_t *, void *fs[10], ucontext_t *, void *ts[10]) + __attribute__ ((no_split_stack)); + +static void +swap (ucontext_t *fu, void *fs[10], ucontext_t *tu, void *ts[10]) +{ + __splitstack_getcontext (fs); + __splitstack_setcontext (ts); + swapcontext (fu, tu); + __splitstack_setcontext (fs); +} + +/* Use a noinline function to ensure that the buffer is not removed + from the stack. */ +static void use_buffer (char *buf) __attribute__ ((noinline)); +static void +use_buffer (char *buf) +{ + buf[0] = '\0'; +} + +static void +down (int i, const char *msg, ucontext_t *me, void *mes[10], + ucontext_t *other, void *others[10]) +{ + char buf[10000]; + + if (i > 0) + { + use_buffer (buf); + swap (me, mes, other, others); + down (i - 1, msg, me, mes, other, others); + } + else + { + int c = 0; + void *stack; + size_t stack_size; + void *next_segment = NULL; + void *next_sp = NULL; + void *initial_sp = NULL; + + stack = __splitstack_find_context (mes, &stack_size, &next_segment, + &next_sp, &initial_sp); + if (stack != NULL) + { + ++c; + while (__splitstack_find (next_segment, next_sp, &stack_size, + &next_segment, &next_sp, &initial_sp) + != NULL) + ++c; + } + } +} + +static void +go1 (void) +{ + down (1000, "go1", &c1, s1, &c2, s2); + pthread_exit (NULL); +} + +static void +go2 (void) +{ + down (1000, "go2", &c2, s2, &c1, s1); + pthread_exit (NULL); +} + +struct thread_context +{ + ucontext_t *u; + void **s; +}; + +static void *start_thread (void *) __attribute__ ((no_split_stack)); + +static void * +start_thread (void *context) +{ + struct thread_context *tc = (struct thread_context *) context; + int block; + + block = 0; + __splitstack_block_signals (&block, NULL); + __splitstack_setcontext (tc->s); + setcontext (tc->u); + abort (); +} + +int +main (int argc __attribute__ ((unused)), char **argv __attribute__ ((unused))) +{ + pthread_t tid; + int err; + size_t size; + struct thread_context tc; + int block; + + if (getcontext (&c1) < 0) + abort (); + + c2 = c1; + + c1.uc_stack.ss_sp = __splitstack_makecontext (8192, &s1[0], &size); + if (c1.uc_stack.ss_sp == NULL) + abort (); + c1.uc_stack.ss_flags = 0; + c1.uc_stack.ss_size = size; + c1.uc_link = NULL; + block = 0; + __splitstack_block_signals_context (&s1[0], &block, NULL); + makecontext (&c1, go1, 0); + + c2.uc_stack.ss_sp = __splitstack_makecontext (8192, &s2[0], &size); + if (c2.uc_stack.ss_sp == NULL) + abort (); + c2.uc_stack.ss_flags = 0; + c2.uc_stack.ss_size = size; + c2.uc_link = NULL; + __splitstack_block_signals_context (&s2[0], &block, NULL); + makecontext (&c2, go2, 0); + + block = 0; + __splitstack_block_signals (&block, NULL); + + tc.u = &c1; + tc.s = &s1[0]; + err = pthread_create (&tid, NULL, start_thread, &tc); + if (err != 0) + abort (); + + err = pthread_join (tid, NULL); + if (err != 0) + abort (); + + return 0; +}