diff mbox

libgo patch committed: Support -buildmode=c-archive

Message ID CAOyqgcWmsi3GnjBA6hQ7NfPAQuxA-_iZqJkGekeiYxfdm7kWRA@mail.gmail.com
State New
Headers show

Commit Message

Ian Lance Taylor April 29, 2015, 9:39 p.m. UTC
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 mbox

Patch

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;