github.com/notti/nocgo@v0.0.0-20190619201224-fc443047424c/internal/fakecgo/cgo.go (about)

     1  /*
     2  Package fakecgo fakes necessary functionality to support calling C-code (TLS initialization for main thread and subsequent threads).
     3  
     4  Usage
     5  
     6  Just import this library with
     7  	import _ "github.com/notti/nocgo/fakecgo"
     8  and enjoy the side effects (e.g., you can't use cgo any longer) :)
     9  */
    10  package fakecgo
    11  
    12  import (
    13  	"syscall"
    14  	"unsafe"
    15  )
    16  
    17  // WARNING: please read this before changing/improving anything
    18  // This here might look like (ugly) go - but it is actually somehow C-code written in go (basically stuff in runtime/cgo/)
    19  // Yeah this somehow works, but needs the trampolines from trampoline_*.s to fix calling conventions cdecl <-> go
    20  //
    21  // Beware that strings must end with a 0 byte to not confuse C-code we call
    22  //
    23  // Write barriers (we will be called while go is in a state where this is not possible) and stack split (we will be on systemstack anyway) are NOT allowed in here
    24  // -> e.g. use memmove for copying to pointers
    25  // go:nowritebarrierrec is only allowed inside runtime - so this has to be checked manually :(
    26  
    27  // pthread_create will call this with ts as argument in a new thread -> this fixes up arguments to go (in assembly) and calls threadentry
    28  func threadentry_trampoline()
    29  
    30  // here we will store a pointer to the provided setg func
    31  var setg_func uintptr
    32  
    33  // x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c)
    34  // This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us
    35  // Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup
    36  // This function can't be go:systemstack since go is not in a state where the systemcheck would work.
    37  //
    38  //go:nosplit
    39  func x_cgo_init(g *g, setg uintptr) {
    40  	var size size_t
    41  	var attr pthread_attr
    42  
    43  	// we need an extra variable here - otherwise go generates "safe" code, which is not allowed here
    44  	stackSp := uintptr(unsafe.Pointer(&size))
    45  
    46  	setg_func = setg
    47  
    48  	pthread_attr_init(&attr)
    49  	pthread_attr_getstacksize(&attr, &size)
    50  	g.stack.lo = stackSp - uintptr(size) + 4096
    51  	pthread_attr_destroy(&attr)
    52  }
    53  
    54  // _cgo_thread_start is split into three parts in cgo since only one part is system dependent (keep it here for easier handling)
    55  
    56  // _cgo_thread_start(ThreadStart *arg) (runtime/cgo/gcc_util.c)
    57  // This get's called instead of the go code for creating new threads
    58  // -> pthread_* stuff is used, so threads are setup correctly for C
    59  // If this is missing, TLS is only setup correctly on thread 1!
    60  // This function should be go:systemstack instead of go:nosplit (but that requires runtime)
    61  //
    62  //go:nosplit
    63  func x_cgo_thread_start(arg *threadstart) {
    64  	ts := malloc(unsafe.Sizeof(threadstart{}))
    65  
    66  	if ts == 0 {
    67  		dprintf(2, "couldn't allocate memory for threadstart\n\000")
    68  		abort()
    69  	}
    70  
    71  	// *ts = *arg would cause a write barrier, which is not allowed
    72  	memmove(unsafe.Pointer(ts), unsafe.Pointer(arg), unsafe.Sizeof(threadstart{}))
    73  
    74  	var attr pthread_attr
    75  	var ign, oset sigset_t
    76  	var p pthread_t
    77  	var size size_t
    78  
    79  	sigfillset(&ign)
    80  	pthread_sigmask(SIG_SETMASK, &ign, &oset)
    81  
    82  	pthread_attr_init(&attr)
    83  	pthread_attr_getstacksize(&attr, &size)
    84  	(*g)((*threadstart)(unsafe.Pointer(ts)).g).stack.hi = uintptr(size)
    85  
    86  	var err int32
    87  
    88  	for tries := 0; tries < 20; tries++ {
    89  		err = pthread_create(&p, &attr, unsafe.Pointer(funcPC(threadentry_trampoline)), unsafe.Pointer(ts))
    90  		if err == 0 {
    91  			pthread_detach(p)
    92  			break
    93  		}
    94  		if err != int32(syscall.EAGAIN) {
    95  			break
    96  		}
    97  		ts := timespec{tv_sec: 0, tv_nsec: (tries + 1) * 1000 * 1000}
    98  		nanosleep(&ts, nil)
    99  	}
   100  
   101  	pthread_sigmask(SIG_SETMASK, &oset, nil)
   102  
   103  	if err != 0 {
   104  		dprintf(2, "pthread_create failed: %s\n\000", strerror(err))
   105  		abort()
   106  	}
   107  }
   108  
   109  func setg_trampoline(uintptr, unsafe.Pointer)
   110  
   111  //go:nosplit
   112  func threadentry(v unsafe.Pointer) uintptr {
   113  	ts := *(*threadstart)(v)
   114  
   115  	free(v)
   116  
   117  	setg_trampoline(setg_func, ts.g)
   118  
   119  	// faking funcs in go is a bit a... involved - but the following works :)
   120  	fn := uintptr(unsafe.Pointer(&ts.fn))
   121  	(*(*func())(unsafe.Pointer(&fn)))()
   122  
   123  	return 0
   124  }
   125  
   126  // The following functions are required by the runtime - otherwise it complains via panic that they are missing
   127  
   128  // do nothing - we don't support being a library for now
   129  // _cgo_notify_runtime_init_done (runtime/cgo/gcc_libinit.c)
   130  //go:nosplit
   131  func x_cgo_notify_runtime_init_done() {}
   132  
   133  // _cgo_setenv(char **arg) (runtime/cgo/gcc_setenv.c)
   134  //go:nosplit
   135  func x_cgo_setenv(arg [2]*byte) {
   136  	setenv(arg[0], arg[1], 1)
   137  }
   138  
   139  // _cgo_unsetenv(char *arg) (runtime/cgo/gcc_setenv.c)
   140  //go:nosplit
   141  func x_cgo_unsetenv(arg *byte) {
   142  	unsetenv(arg)
   143  }