The upcoming Go 1.5 release of the gc compiler adds support for "go build -buildmode=c-archive". This can be used to build Go code into an archive (a .a file) that can be linked with a non-Go program, such that the non-Go program can call into Go code. This patch adds support for that to gccgo.
I'm going to commit this patch to the GCC 5 branch as well. This will permit the Go 1.5 go tool to be used with the GCC 5.2 and later. That way people won't have to wait for GCC 6. This patch seems safe to me and passes all my testing. Bootstrapped and ran Go tests and gc cgo tests on x86_64-unknown-linux-gnu. Committed to mainline and GCC 5 branch. Ian
diff -r ae8a4c892d42 libgo/Makefile.am --- a/libgo/Makefile.am Fri Apr 24 15:07:55 2015 -0700 +++ b/libgo/Makefile.am Wed Apr 29 13:41:41 2015 -0700 @@ -105,7 +105,7 @@ toolexeclib_LIBRARIES = libgobegin-llgo.a else toolexeclib_LTLIBRARIES = libgo.la -toolexeclib_LIBRARIES = libgobegin.a libnetgo.a +toolexeclib_LIBRARIES = libgobegin.a libgolibbegin.a libnetgo.a endif toolexeclibgo_DATA = \ @@ -2036,6 +2036,11 @@ libgobegin_a_CFLAGS = $(AM_CFLAGS) -fPIC libgobegin_llgo_a_CFLAGS = $(AM_CFLAGS) -fPIC +libgolibbegin_a_SOURCES = \ + runtime/go-libmain.c + +libgolibbegin_a_CFLAGS = $(AM_CFLAGS) -fPIC + libnetgo_a_SOURCES = $(go_netgo_files) libnetgo_a_LIBADD = netgo.o @@ -2067,7 +2072,7 @@ BUILDNETGO = \ $(MKDIR_P) $(@D); \ files=`echo $^ | sed -e 's/[^ ]*\.gox//g'`; \ - $(GOCOMPILE) -I . -c -fgo-pkgpath=net -o $@ $$files + $(GOCOMPILE) -I . -c -fPIC -fgo-pkgpath=net -o $@ $$files GOTESTFLAGS = GOBENCH = diff -r ae8a4c892d42 libgo/runtime/go-cgo.c --- a/libgo/runtime/go-cgo.c Fri Apr 24 15:07:55 2015 -0700 +++ b/libgo/runtime/go-cgo.c Wed Apr 29 13:41:41 2015 -0700 @@ -8,6 +8,9 @@ #include "go-alloc.h" #include "interface.h" #include "go-panic.h" +#include "go-type.h" + +extern void __go_receive (ChanType *, Hchan *, byte *); /* Prepare to call from code written in Go to code written in C or C++. This takes the current goroutine out of the Go scheduler, as @@ -86,6 +89,15 @@ runtime_exitsyscall (); + if (runtime_g ()->ncgo == 0) + { + /* The C call to Go came from a thread not currently running any + Go. In the case of -buildmode=c-archive or c-shared, this + call may be coming in before package initialization is + complete. Wait until it is. */ + __go_receive (NULL, runtime_main_init_done, NULL); + } + mp = runtime_m (); if (mp->needextram) { @@ -177,3 +189,65 @@ __go_panic (e); } + +/* Used for _cgo_wait_runtime_init_done. This is based on code in + runtime/cgo/gcc_libinit.c in the master library. */ + +static pthread_cond_t runtime_init_cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t runtime_init_mu = PTHREAD_MUTEX_INITIALIZER; +static _Bool runtime_init_done; + +/* This is called by exported cgo functions to ensure that the runtime + has been initialized before we enter the function. This is needed + when building with -buildmode=c-archive or similar. */ + +void +_cgo_wait_runtime_init_done (void) +{ + int err; + + if (__atomic_load_n (&runtime_init_done, __ATOMIC_ACQUIRE)) + return; + + err = pthread_mutex_lock (&runtime_init_mu); + if (err != 0) + abort (); + while (!__atomic_load_n (&runtime_init_done, __ATOMIC_ACQUIRE)) + { + err = pthread_cond_wait (&runtime_init_cond, &runtime_init_mu); + if (err != 0) + abort (); + } + err = pthread_mutex_unlock (&runtime_init_mu); + if (err != 0) + abort (); +} + +/* This is called by runtime_main after the Go runtime is + initialized. */ + +void +_cgo_notify_runtime_init_done (void) +{ + int err; + + err = pthread_mutex_lock (&runtime_init_mu); + if (err != 0) + abort (); + __atomic_store_n (&runtime_init_done, 1, __ATOMIC_RELEASE); + err = pthread_cond_broadcast (&runtime_init_cond); + if (err != 0) + abort (); + err = pthread_mutex_unlock (&runtime_init_mu); + if (err != 0) + abort (); +} + +// runtime_iscgo is set to true if some cgo code is linked in. +// This is done by a constructor in the cgo generated code. +_Bool runtime_iscgo; + +// runtime_cgoHasExtraM is set on startup when an extra M is created +// for cgo. The extra M must be created before any C/C++ code calls +// cgocallback. +_Bool runtime_cgoHasExtraM; diff -r ae8a4c892d42 libgo/runtime/go-libmain.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libgo/runtime/go-libmain.c Wed Apr 29 13:41:41 2015 -0700 @@ -0,0 +1,114 @@ +/* go-libmain.c -- the startup function for a Go library. + + Copyright 2015 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 "config.h" + +#include <errno.h> +#include <pthread.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#include "runtime.h" +#include "go-alloc.h" +#include "array.h" +#include "arch.h" +#include "malloc.h" + +/* This is used when building a standalone Go library using the Go + command's -buildmode=c-archive or -buildmode=c-shared option. It + starts up the Go code as a global constructor but does not take any + other action. The main program is written in some other language + and calls exported Go functions as needed. */ + +static void die (const char *, int); +static void initfn (int, char **, char **); +static void *gostart (void *); + +/* Used to pass arguments to the thread that runs the Go startup. */ + +struct args { + int argc; + char **argv; +}; + +/* We use .init_array so that we can get the command line arguments. + This obviously assumes .init_array support; different systems may + require other approaches. */ + +typedef void (*initarrayfn) (int, char **, char **); + +static initarrayfn initarray[1] +__attribute__ ((section (".init_array"), used)) = + { initfn }; + +/* This function is called at program startup time. It starts a new + thread to do the actual Go startup, so that program startup is not + paused waiting for the Go initialization functions. Exported cgo + functions will wait for initialization to complete if + necessary. */ + +static void +initfn (int argc, char **argv, char** env __attribute__ ((unused))) +{ + int err; + pthread_attr_t attr; + struct args *a; + pthread_t tid; + + a = (struct args *) malloc (sizeof *a); + if (a == NULL) + die ("malloc", errno); + a->argc = argc; + a->argv = argv; + + err = pthread_attr_init (&attr); + if (err != 0) + die ("pthread_attr_init", err); + err = pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); + if (err != 0) + die ("pthread_attr_setdetachstate", err); + + err = pthread_create (&tid, &attr, gostart, (void *) a); + if (err != 0) + die ("pthread_create", err); + + err = pthread_attr_destroy (&attr); + if (err != 0) + die ("pthread_attr_destroy", err); +} + +/* Start up the Go runtime. */ + +static void * +gostart (void *arg) +{ + struct args *a = (struct args *) arg; + + runtime_isarchive = true; + + if (runtime_isstarted) + return NULL; + runtime_isstarted = true; + + runtime_check (); + runtime_args (a->argc, (byte **) a->argv); + runtime_osinit (); + runtime_schedinit (); + __go_go (runtime_main, NULL); + runtime_mstart (runtime_m ()); + abort (); +} + +/* If something goes wrong during program startup, crash. There is no + way to report failure and nobody to whom to report it. */ + +static void +die (const char *fn, int err) +{ + fprintf (stderr, "%s: %d\n", fn, err); + exit (EXIT_FAILURE); +} diff -r ae8a4c892d42 libgo/runtime/go-main.c --- a/libgo/runtime/go-main.c Fri Apr 24 15:07:55 2015 -0700 +++ b/libgo/runtime/go-main.c Wed Apr 29 13:41:41 2015 -0700 @@ -35,6 +35,12 @@ int main (int argc, char **argv) { + runtime_isarchive = false; + + if (runtime_isstarted) + return NULL; + runtime_isstarted = true; + runtime_check (); runtime_args (argc, (byte **) argv); runtime_osinit (); diff -r ae8a4c892d42 libgo/runtime/proc.c --- a/libgo/runtime/proc.c Fri Apr 24 15:07:55 2015 -0700 +++ b/libgo/runtime/proc.c Wed Apr 29 13:41:41 2015 -0700 @@ -372,7 +372,6 @@ Sched runtime_sched; int32 runtime_gomaxprocs; uint32 runtime_needextram = 1; -bool runtime_iscgo = true; M runtime_m0; G runtime_g0; // idle goroutine for m0 G* runtime_lastg; @@ -389,6 +388,8 @@ uintptr runtime_allglen; static uintptr allgcap; +bool runtime_isarchive; + void* runtime_mstart(void*); static void runqput(P*, G*); static G* runqget(P*); @@ -428,6 +429,8 @@ static bool exitsyscallfast(void); static void allgadd(G*); +bool runtime_isstarted; + // The bootstrap sequence is: // // call osinit @@ -490,6 +493,64 @@ extern void main_init(void) __asm__ (GOSYM_PREFIX "__go_init_main"); extern void main_main(void) __asm__ (GOSYM_PREFIX "main.main"); +// Used to determine the field alignment. + +struct field_align +{ + char c; + Hchan *p; +}; + +// main_init_done is a signal used by cgocallbackg that initialization +// has been completed. It is made before _cgo_notify_runtime_init_done, +// so all cgo calls can rely on it existing. When main_init is +// complete, it is closed, meaning cgocallbackg can reliably receive +// from it. +Hchan *runtime_main_init_done; + +// The chan bool type, for runtime_main_init_done. + +extern const struct __go_type_descriptor bool_type_descriptor + __asm__ (GOSYM_PREFIX "__go_tdn_bool"); + +static struct __go_channel_type chan_bool_type_descriptor = + { + /* __common */ + { + /* __code */ + GO_CHAN, + /* __align */ + __alignof (Hchan *), + /* __field_align */ + offsetof (struct field_align, p) - 1, + /* __size */ + sizeof (Hchan *), + /* __hash */ + 0, /* This value doesn't matter. */ + /* __hashfn */ + __go_type_hash_error, + /* __equalfn */ + __go_type_equal_error, + /* __gc */ + NULL, /* This value doesn't matter */ + /* __reflection */ + NULL, /* This value doesn't matter */ + /* __uncommon */ + NULL, + /* __pointer_to_this */ + NULL, + /* __zero */ + NULL /* This value doesn't matter */ + }, + /* __element_type */ + &bool_type_descriptor, + /* __dir */ + CHANNEL_BOTH_DIR + }; + +extern Hchan *__go_new_channel (ChanType *, uintptr); +extern void closechan(Hchan *) __asm__ (GOSYM_PREFIX "runtime.closechan"); + static void initDone(void *arg __attribute__ ((unused))) { runtime_unlockOSThread(); @@ -535,8 +596,15 @@ if(m != &runtime_m0) runtime_throw("runtime_main not on m0"); __go_go(runtime_MHeap_Scavenger, nil); + + runtime_main_init_done = __go_new_channel(&chan_bool_type_descriptor, 0); + + _cgo_notify_runtime_init_done(); + main_init(); + closechan(runtime_main_init_done); + if(g->defer != &d || d.__pfn != initDone) runtime_throw("runtime: bad defer entry after init"); g->defer = d.__next; @@ -547,6 +615,14 @@ // roots. mstats.enablegc = 1; + if(runtime_isarchive) { + // This is not a complete program, but is instead a + // library built using -buildmode=c-archive or + // c-shared. Now that we are initialized, there is + // nothing further to do. + return; + } + main_main(); // Make racy client program work: if panicking on @@ -1011,8 +1087,14 @@ // Install signal handlers; after minit so that minit can // prepare the thread to be able to handle the signals. - if(m == &runtime_m0) + if(m == &runtime_m0) { + if(runtime_iscgo && !runtime_cgoHasExtraM) { + runtime_cgoHasExtraM = true; + runtime_newextram(); + runtime_needextram = 0; + } runtime_initsig(); + } if(m->mstartfn) m->mstartfn(); @@ -2747,6 +2829,13 @@ int32 run, grunning, s; uintptr i; + // For -buildmode=c-shared or -buildmode=c-archive it's OK if + // there are no running goroutines. The calling program is + // assumed to be running. + if(runtime_isarchive) { + return; + } + // -1 for sysmon run = runtime_sched.mcount - runtime_sched.nmidle - runtime_sched.nmidlelocked - 1 - countextra(); if(run > 0) @@ -3332,6 +3421,7 @@ runtime_proc_scan(struct Workbuf** wbufp, void (*enqueue1)(struct Workbuf**, Obj)) { enqueue1(wbufp, (Obj){(byte*)&runtime_sched, sizeof runtime_sched, 0}); + enqueue1(wbufp, (Obj){(byte*)&runtime_main_init_done, sizeof runtime_main_init_done, 0}); } // Return whether we are waiting for a GC. This gc toolchain uses diff -r ae8a4c892d42 libgo/runtime/runtime.h --- a/libgo/runtime/runtime.h Fri Apr 24 15:07:55 2015 -0700 +++ b/libgo/runtime/runtime.h Wed Apr 29 13:41:41 2015 -0700 @@ -509,6 +509,9 @@ extern DebugVars runtime_debug; extern uintptr runtime_maxstacksize; +extern bool runtime_isstarted; +extern bool runtime_isarchive; + /* * common functions and data */ @@ -845,3 +848,9 @@ struct time_now_ret now() __asm__ (GOSYM_PREFIX "time.now") __attribute__ ((no_split_stack)); + +extern void _cgo_wait_runtime_init_done (void); +extern void _cgo_notify_runtime_init_done (void); +extern _Bool runtime_iscgo; +extern _Bool runtime_cgoHasExtraM; +extern Hchan *runtime_main_init_done;