summaryrefslogtreecommitdiff
path: root/src/pkg/runtime/cgocall.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/runtime/cgocall.c')
-rw-r--r--src/pkg/runtime/cgocall.c219
1 files changed, 174 insertions, 45 deletions
diff --git a/src/pkg/runtime/cgocall.c b/src/pkg/runtime/cgocall.c
index 741e8f0b8..58f287e90 100644
--- a/src/pkg/runtime/cgocall.c
+++ b/src/pkg/runtime/cgocall.c
@@ -3,18 +3,97 @@
// license that can be found in the LICENSE file.
#include "runtime.h"
+#include "arch.h"
#include "stack.h"
#include "cgocall.h"
+// Cgo call and callback support.
+//
+// To call into the C function f from Go, the cgo-generated code calls
+// runtime.cgocall(_cgo_Cfunc_f, frame), where _cgo_Cfunc_f is a
+// gcc-compiled function written by cgo.
+//
+// runtime.cgocall (below) locks g to m, calls entersyscall
+// so as not to block other goroutines or the garbage collector,
+// and then calls runtime.asmcgocall(_cgo_Cfunc_f, frame).
+//
+// runtime.asmcgocall (in $GOARCH/asm.s) switches to the m->g0 stack
+// (assumed to be an operating system-allocated stack, so safe to run
+// gcc-compiled code on) and calls _cgo_Cfunc_f(frame).
+//
+// _cgo_Cfunc_f invokes the actual C function f with arguments
+// taken from the frame structure, records the results in the frame,
+// and returns to runtime.asmcgocall.
+//
+// After it regains control, runtime.asmcgocall switches back to the
+// original g (m->curg)'s stack and returns to runtime.cgocall.
+//
+// After it regains control, runtime.cgocall calls exitsyscall, which blocks
+// until this m can run Go code without violating the $GOMAXPROCS limit,
+// and then unlocks g from m.
+//
+// The above description skipped over the possibility of the gcc-compiled
+// function f calling back into Go. If that happens, we continue down
+// the rabbit hole during the execution of f.
+//
+// To make it possible for gcc-compiled C code to call a Go function p.GoF,
+// cgo writes a gcc-compiled function named GoF (not p.GoF, since gcc doesn't
+// know about packages). The gcc-compiled C function f calls GoF.
+//
+// GoF calls crosscall2(_cgoexp_GoF, frame, framesize). Crosscall2
+// (in cgo/$GOOS.S, a gcc-compiled assembly file) is a two-argument
+// adapter from the gcc function call ABI to the 6c function call ABI.
+// It is called from gcc to call 6c functions. In this case it calls
+// _cgoexp_GoF(frame, framesize), still running on m->g0's stack
+// and outside the $GOMAXPROCS limit. Thus, this code cannot yet
+// call arbitrary Go code directly and must be careful not to allocate
+// memory or use up m->g0's stack.
+//
+// _cgoexp_GoF calls runtime.cgocallback(p.GoF, frame, framesize).
+// (The reason for having _cgoexp_GoF instead of writing a crosscall3
+// to make this call directly is that _cgoexp_GoF, because it is compiled
+// with 6c instead of gcc, can refer to dotted names like
+// runtime.cgocallback and p.GoF.)
+//
+// runtime.cgocallback (in $GOOS/asm.s) switches from m->g0's
+// stack to the original g (m->curg)'s stack, on which it calls
+// runtime.cgocallbackg(p.GoF, frame, framesize).
+// As part of the stack switch, runtime.cgocallback saves the current
+// SP as m->g0->sched.sp, so that any use of m->g0's stack during the
+// execution of the callback will be done below the existing stack frames.
+// Before overwriting m->g0->sched.sp, it pushes the old value on the
+// m->g0 stack, so that it can be restored later.
+//
+// runtime.cgocallbackg (below) is now running on a real goroutine
+// stack (not an m->g0 stack). First it calls runtime.exitsyscall, which will
+// block until the $GOMAXPROCS limit allows running this goroutine.
+// Once exitsyscall has returned, it is safe to do things like call the memory
+// allocator or invoke the Go callback function p.GoF. runtime.cgocallback
+// first defers a function to unwind m->g0.sched.sp, so that if p.GoF
+// panics, m->g0.sched.sp will be restored to its old value: the m->g0 stack
+// and the m->curg stack will be unwound in lock step.
+// Then it calls p.GoF. Finally it pops but does not execute the deferred
+// function, calls runtime.entersyscall, and returns to runtime.cgocallback.
+//
+// After it regains control, runtime.cgocallback switches back to
+// m->g0's stack (the pointer is still in m->g0.sched.sp), restores the old
+// m->g0.sched.sp value from the stack, and returns to _cgoexp_GoF.
+//
+// _cgoexp_GoF immediately returns to crosscall2, which restores the
+// callee-save registers for gcc and returns to GoF, which returns to f.
+
void *initcgo; /* filled in by dynamic linker when Cgo is available */
int64 ncgocall;
-void runtime·entersyscall(void);
-void runtime·exitsyscall(void);
+
+static void unlockm(void);
+static void unwindm(void);
+
+// Call from Go to C.
void
runtime·cgocall(void (*fn)(void*), void *arg)
{
- G *oldlock;
+ Defer *d;
if(!runtime·iscgo)
runtime·throw("cgocall unavailable");
@@ -28,61 +107,49 @@ runtime·cgocall(void (*fn)(void*), void *arg)
* Lock g to m to ensure we stay on the same stack if we do a
* cgo callback.
*/
- oldlock = m->lockedg;
- m->lockedg = g;
- g->lockedm = m;
+ d = nil;
+ if(m->lockedg == nil) {
+ m->lockedg = g;
+ g->lockedm = m;
+
+ // Add entry to defer stack in case of panic.
+ d = runtime·malloc(sizeof(*d));
+ d->fn = (byte*)unlockm;
+ d->siz = 0;
+ d->link = g->defer;
+ d->argp = (void*)-1; // unused because unwindm never recovers
+ g->defer = d;
+ }
/*
* Announce we are entering a system call
* so that the scheduler knows to create another
* M to run goroutines while we are in the
* foreign code.
+ *
+ * The call to asmcgocall is guaranteed not to
+ * split the stack and does not allocate memory,
+ * so it is safe to call while "in a system call", outside
+ * the $GOMAXPROCS accounting.
*/
runtime·entersyscall();
- runtime·runcgo(fn, arg);
+ runtime·asmcgocall(fn, arg);
runtime·exitsyscall();
- m->lockedg = oldlock;
- if(oldlock == nil)
- g->lockedm = nil;
-
- return;
+ if(d != nil) {
+ if(g->defer != d || d->fn != (byte*)unlockm)
+ runtime·throw("runtime: bad defer entry in cgocallback");
+ g->defer = d->link;
+ runtime·free(d);
+ unlockm();
+ }
}
-// When a C function calls back into Go, the wrapper function will
-// call this. This switches to a Go stack, copies the arguments
-// (arg/argsize) on to the stack, calls the function, copies the
-// arguments back where they came from, and finally returns to the old
-// stack.
-void
-runtime·cgocallback(void (*fn)(void), void *arg, int32 argsize)
+static void
+unlockm(void)
{
- Gobuf oldsched, oldg1sched;
- G *g1;
- void *sp;
-
- if(g != m->g0)
- runtime·throw("bad g in cgocallback");
-
- g1 = m->curg;
- oldsched = m->sched;
- oldg1sched = g1->sched;
-
- runtime·startcgocallback(g1);
-
- sp = g1->sched.sp - argsize;
- if(sp < g1->stackguard - StackGuard - StackSystem + 8) // +8 for return address
- runtime·throw("g stack overflow in cgocallback");
- runtime·mcpy(sp, arg, argsize);
-
- runtime·runcgocallback(g1, sp, fn);
-
- runtime·mcpy(arg, sp, argsize);
-
- runtime·endcgocallback(g1);
-
- m->sched = oldsched;
- g1->sched = oldg1sched;
+ m->lockedg = nil;
+ g->lockedm = nil;
}
void
@@ -92,6 +159,8 @@ runtime·Cgocalls(int64 ret)
FLUSH(&ret);
}
+// Helper functions for cgo code.
+
void (*_cgo_malloc)(void*);
void (*_cgo_free)(void*);
@@ -115,3 +184,63 @@ runtime·cfree(void *p)
runtime·cgocall(_cgo_free, p);
}
+// Call from C back to Go.
+
+void
+runtime·cgocallbackg(void (*fn)(void), void *arg, uintptr argsize)
+{
+ Defer *d;
+
+ if(g != m->curg)
+ runtime·throw("runtime: bad g in cgocallback");
+
+ runtime·exitsyscall(); // coming out of cgo call
+
+ // Add entry to defer stack in case of panic.
+ d = runtime·malloc(sizeof(*d));
+ d->fn = (byte*)unwindm;
+ d->siz = 0;
+ d->link = g->defer;
+ d->argp = (void*)-1; // unused because unwindm never recovers
+ g->defer = d;
+
+ // Invoke callback.
+ reflect·call((byte*)fn, arg, argsize);
+
+ // Pop defer.
+ // Do not unwind m->g0->sched.sp.
+ // Our caller, cgocallback, will do that.
+ if(g->defer != d || d->fn != (byte*)unwindm)
+ runtime·throw("runtime: bad defer entry in cgocallback");
+ g->defer = d->link;
+ runtime·free(d);
+
+ runtime·entersyscall(); // going back to cgo call
+}
+
+static void
+unwindm(void)
+{
+ // Restore sp saved by cgocallback during
+ // unwind of g's stack (see comment at top of file).
+ switch(thechar){
+ default:
+ runtime·throw("runtime: unwindm not implemented");
+ case '8':
+ case '6':
+ m->g0->sched.sp = *(void**)m->g0->sched.sp;
+ break;
+ }
+}
+
+void
+runtime·badcgocallback(void) // called from assembly
+{
+ runtime·throw("runtime: misaligned stack in cgocallback");
+}
+
+void
+runtime·cgounimpl(void) // called from (incomplete) assembly
+{
+ runtime·throw("runtime: cgo not implemented");
+}