diff options
Diffstat (limited to 'src/libcgo/cgocall.c')
-rw-r--r-- | src/libcgo/cgocall.c | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/src/libcgo/cgocall.c b/src/libcgo/cgocall.c new file mode 100644 index 000000000..c089f1d5d --- /dev/null +++ b/src/libcgo/cgocall.c @@ -0,0 +1,278 @@ +// 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. + +#define _GNU_SOURCE +#include <stdio.h> +#include <errno.h> +#include <linux/futex.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <pthread.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +#define nil ((void*)0) + +/* + * gcc implementation of src/pkg/runtime/linux/thread.c + */ +typedef struct Lock Lock; +typedef struct Note Note; +typedef uint32_t uint32; + +struct Lock +{ + uint32 key; + uint32 sema; // ignored +}; + +struct Note +{ + Lock lock; + uint32 pad; +}; + +static struct timespec longtime = +{ + 1<<30, // 34 years + 0 +}; + +static int +cas(uint32 *val, uint32 old, uint32 new) +{ + int ret; + + __asm__ __volatile__( + "lock; cmpxchgl %2, 0(%3)\n" + "setz %%al\n" + : "=a" (ret) + : "a" (old), + "r" (new), + "r" (val) + : "memory", "cc" + ); + + return ret & 1; +} + +static void +futexsleep(uint32 *addr, uint32 val) +{ + int ret; + + ret = syscall(SYS_futex, (int*)addr, FUTEX_WAIT, val, &longtime, nil, 0); + if(ret >= 0 || errno == EAGAIN || errno == EINTR) + return; + fprintf(stderr, "futexsleep: %s\n", strerror(errno)); + *(int*)0 = 0; +} + +static void +futexwakeup(uint32 *addr) +{ + int ret; + + ret = syscall(SYS_futex, (int*)addr, FUTEX_WAKE, 1, nil, nil, 0); + if(ret >= 0) + return; + fprintf(stderr, "futexwakeup: %s\n", strerror(errno)); + *(int*)0 = 0; +} + +static void +futexlock(Lock *l) +{ + uint32 v; + +again: + v = l->key; + if((v&1) == 0){ + if(cas(&l->key, v, v|1)){ + // Lock wasn't held; we grabbed it. + return; + } + goto again; + } + + if(!cas(&l->key, v, v+2)) + goto again; + + futexsleep(&l->key, v+2); + for(;;){ + v = l->key; + if((int)v < 2) { + fprintf(stderr, "futexsleep: invalid key %d\n", (int)v); + *(int*)0 = 0; + } + if(cas(&l->key, v, v-2)) + break; + } + goto again; +} + +static void +futexunlock(Lock *l) +{ + uint32 v; + +again: + v = l->key; + if((v&1) == 0) + *(int*)0 = 0; + if(!cas(&l->key, v, v&~1)) + goto again; + + // If there were waiters, wake one. + if(v & ~1) + futexwakeup(&l->key); +} + +static void +lock(Lock *l) +{ + futexlock(l); +} + +static void +unlock(Lock *l) +{ + futexunlock(l); +} + +void +noteclear(Note *n) +{ + n->lock.key = 0; + futexlock(&n->lock); +} + +static void +notewakeup(Note *n) +{ + futexunlock(&n->lock); +} + +static void +notesleep(Note *n) +{ + futexlock(&n->lock); + futexunlock(&n->lock); +} + +/* + * runtime Cgo server. + * gcc half of src/pkg/runtime/cgocall.c + */ + +typedef struct CgoWork CgoWork; +typedef struct CgoServer CgoServer; +typedef struct Cgo Cgo; + +struct Cgo +{ + Lock lock; + CgoServer *idle; + CgoWork *whead; + CgoWork *wtail; +}; + +struct CgoServer +{ + CgoServer *next; + Note note; + CgoWork *work; +}; + +struct CgoWork +{ + CgoWork *next; + Note note; + void (*fn)(void*); + void *arg; +}; + +Cgo cgo; + +static void newserver(void); + +void +initcgo(void) +{ + newserver(); +} + +static void* go_pthread(void*); + +/* + * allocate servers to handle any work that has piled up + * and one more server to sit idle and wait for new work. + */ +static void +newserver(void) +{ + CgoServer *f; + CgoWork *w, *next; + pthread_t p; + + lock(&cgo.lock); + if(cgo.idle == nil) { + // kick off new servers with work to do + for(w=cgo.whead; w; w=next) { + next = w; + w->next = nil; + f = malloc(sizeof *f); + memset(f, 0, sizeof *f); + f->work = w; + noteclear(&f->note); + notewakeup(&f->note); + if(pthread_create(&p, nil, go_pthread, f) < 0) { + fprintf(stderr, "pthread_create: %s\n", strerror(errno)); + *(int*)0 = 0; + } + } + cgo.whead = nil; + cgo.wtail = nil; + + // kick off one more server to sit idle + f = malloc(sizeof *f); + memset(f, 0, sizeof *f); + f->next = cgo.idle; + noteclear(&f->note); + cgo.idle = f; + if(pthread_create(&p, nil, go_pthread, f) < 0) { + fprintf(stderr, "pthread_create: %s\n", strerror(errno)); + *(int*)0 = 0; + } + } + unlock(&cgo.lock); +} + +static void* +go_pthread(void *v) +{ + CgoServer *f; + CgoWork *w; + + f = v; + for(;;) { + // wait for work + notesleep(&f->note); + + // do work + w = f->work; + w->fn(w->arg); + notewakeup(&w->note); + + // queue f on idle list + f->work = nil; + noteclear(&f->note); + lock(&cgo.lock); + f->next = cgo.idle; + cgo.idle = f; + unlock(&cgo.lock); + } +} + |