This patch to libgo fixes cgo callbacks from non-Go threads. Those threads will not have an M or G structure. This basically adjusts the support in the master Go library to work with gccgo.
There are also some cgo patches required. They are in https://codereview.appspot.com/11406047/ and will be applied to the master repository when approved. Bootstrapped and ran Go testsuite on x86_64-unknown-linux-gnu. Committed to mainline and 4.8 branch. Ian
diff -r b916a03755cf libgo/runtime/go-cgo.c --- a/libgo/runtime/go-cgo.c Mon Jul 22 21:40:43 2013 -0700 +++ b/libgo/runtime/go-cgo.c Tue Jul 23 13:19:03 2013 -0700 @@ -35,6 +35,9 @@ M* m; G* g; + if (runtime_needextram && runtime_cas (&runtime_needextram, 1, 0)) + runtime_newextram (); + m = runtime_m (); ++m->ncgocall; g = runtime_g (); @@ -71,7 +74,24 @@ void syscall_cgocallback () { + M *mp; + + mp = runtime_m (); + if (mp == NULL) + { + runtime_needm (); + mp = runtime_m (); + mp->dropextram = true; + } + runtime_exitsyscall (); + + mp = runtime_m (); + if (mp->needextram) + { + mp->needextram = 0; + runtime_newextram (); + } } /* Prepare to return to C/C++ code from a callback to Go code. */ @@ -79,7 +99,15 @@ void syscall_cgocallbackdone () { + M *mp; + runtime_entersyscall (); + mp = runtime_m (); + if (mp->dropextram && runtime_g ()->ncgo == 0) + { + mp->dropextram = false; + runtime_dropm (); + } } /* Allocate memory and save it in a list visible to the Go garbage diff -r b916a03755cf libgo/runtime/go-defer.c --- a/libgo/runtime/go-defer.c Mon Jul 22 21:40:43 2013 -0700 +++ b/libgo/runtime/go-defer.c Tue Jul 23 13:19:03 2013 -0700 @@ -42,6 +42,7 @@ { struct __go_defer_stack *d; void (*pfn) (void *); + M *m; d = g->defer; pfn = d->__pfn; @@ -51,7 +52,14 @@ (*pfn) (d->__arg); g->defer = d->__next; - __go_free (d); + + /* This may be called by a cgo callback routine to defer the + call to syscall.CgocallBackDone, in which case we will not + have a memory context. Don't try to free anything in that + case--the GC will release it later. */ + m = runtime_m (); + if (m != NULL && m->mcache != NULL) + __go_free (d); /* Since we are executing a defer function here, we know we are returning from the calling function. If the calling diff -r b916a03755cf libgo/runtime/go-panic.c --- a/libgo/runtime/go-panic.c Mon Jul 22 21:40:43 2013 -0700 +++ b/libgo/runtime/go-panic.c Tue Jul 23 13:19:03 2013 -0700 @@ -54,6 +54,7 @@ { struct __go_defer_stack *d; void (*pfn) (void *); + M *m; d = g->defer; if (d == NULL) @@ -95,7 +96,14 @@ } g->defer = d->__next; - __go_free (d); + + /* This may be called by a cgo callback routine to defer the + call to syscall.CgocallBackDone, in which case we will not + have a memory context. Don't try to free anything in that + case--the GC will release it later. */ + m = runtime_m (); + if (m != NULL && m->mcache != NULL) + __go_free (d); } /* The panic was not recovered. */ diff -r b916a03755cf libgo/runtime/proc.c --- a/libgo/runtime/proc.c Mon Jul 22 21:40:43 2013 -0700 +++ b/libgo/runtime/proc.c Tue Jul 23 13:19:03 2013 -0700 @@ -397,7 +397,8 @@ Sched runtime_sched; int32 runtime_gomaxprocs; bool runtime_singleproc; -bool runtime_iscgo; +bool runtime_iscgo = true; +uint32 runtime_needextram = 1; uint32 runtime_gcwaiting; M runtime_m0; G runtime_g0; // idle goroutine for m0 @@ -901,8 +902,8 @@ #ifdef USING_SPLIT_STACK { - int dont_block_signals = 0; - __splitstack_block_signals(&dont_block_signals, nil); + int dont_block_signals = 0; + __splitstack_block_signals(&dont_block_signals, nil); } #endif @@ -944,7 +945,7 @@ // Allocate a new m unassociated with any thread. // Can use p for allocation context if needed. M* -runtime_allocm(P *p) +runtime_allocm(P *p, int32 stacksize, byte** ret_g0_stack, size_t* ret_g0_stacksize) { M *mp; @@ -961,7 +962,7 @@ mp = runtime_mal(sizeof *mp); mcommoninit(mp); - mp->g0 = runtime_malg(-1, nil, nil); + mp->g0 = runtime_malg(stacksize, ret_g0_stack, ret_g0_stacksize); if(p == m->p) releasep(); @@ -1006,6 +1007,9 @@ // // When the callback is done with the m, it calls dropm to // put the m back on the list. +// +// Unlike the gc toolchain, we start running on curg, since we are +// just going to return and let the caller continue. void runtime_needm(void) { @@ -1027,18 +1031,40 @@ mp->needextram = mp->schedlink == nil; unlockextra(mp->schedlink); - // Install m and g (= m->g0) and set the stack bounds - // to match the current stack. We don't actually know - // how big the stack is, like we don't know how big any - // scheduling stack is, but we assume there's at least 32 kB, - // which is more than enough for us. - runtime_setmg(mp, mp->g0); + // Install m and g (= m->curg). + runtime_setmg(mp, mp->curg); - // We assume that the split stack support has been initialized - // for this new thread. + // Initialize g's context as in mstart. + initcontext(); + g->status = Gsyscall; + g->entry = nil; + g->param = nil; +#ifdef USING_SPLIT_STACK + __splitstack_getcontext(&g->stack_context[0]); +#else + g->gcinitial_sp = ∓ + g->gcstack_size = 0; + g->gcnext_sp = ∓ +#endif + getcontext(&g->context); + + if(g->entry != nil) { + // Got here from mcall. + void (*pfn)(G*) = (void (*)(G*))g->entry; + G* gp = (G*)g->param; + pfn(gp); + *(int*)0x22 = 0x22; + } // Initialize this thread to use the m. runtime_minit(); + +#ifdef USING_SPLIT_STACK + { + int dont_block_signals = 0; + __splitstack_block_signals(&dont_block_signals, nil); + } +#endif } // newextram allocates an m and puts it on the extra list. @@ -1049,15 +1075,17 @@ { M *mp, *mnext; G *gp; + byte *g0_sp, *sp; + size_t g0_spsize, spsize; // Create extra goroutine locked to extra m. // The goroutine is the context in which the cgo callback will run. // The sched.pc will never be returned to, but setting it to // runtime.goexit makes clear to the traceback routines where // the goroutine stack ends. - mp = runtime_allocm(nil); - gp = runtime_malg(StackMin, nil, nil); - gp->status = Gsyscall; + mp = runtime_allocm(nil, StackMin, &g0_sp, &g0_spsize); + gp = runtime_malg(StackMin, &sp, &spsize); + gp->status = Gdead; mp->curg = gp; mp->locked = LockInternal; mp->lockedg = gp; @@ -1072,6 +1100,16 @@ runtime_unlock(&runtime_sched); gp->goid = runtime_xadd64(&runtime_sched.goidgen, 1); + // The context for gp will be set up in runtime_needm. But + // here we need to set up the context for g0. + getcontext(&mp->g0->context); + mp->g0->context.uc_stack.ss_sp = g0_sp; +#ifdef MAKECONTEXT_STACK_TOP + mp->g0->context.uc_stack.ss_sp += g0_spsize; +#endif + mp->g0->context.uc_stack.ss_size = g0_spsize; + makecontext(&mp->g0->context, kickoff, 0); + // Add m to the extra list. mnext = lockextra(true); mp->schedlink = mnext; @@ -1114,6 +1152,8 @@ mp = m; runtime_setmg(nil, nil); + mp->curg->status = Gdead; + mnext = lockextra(true); mp->schedlink = mnext; unlockextra(mp); @@ -1159,6 +1199,29 @@ runtime_atomicstorep(&runtime_extram, mp); } +static int32 +countextra() +{ + M *mp, *mc; + int32 c; + + for(;;) { + mp = runtime_atomicloadp(&runtime_extram); + if(mp == MLOCKED) { + runtime_osyield(); + continue; + } + if(!runtime_casp(&runtime_extram, mp, MLOCKED)) { + runtime_osyield(); + continue; + } + c = 0; + for(mc = mp; mc != nil; mc = mc->schedlink) + c++; + runtime_atomicstorep(&runtime_extram, mp); + return c; + } +} // Create a new m. It will start off with a call to fn, or else the scheduler. static void @@ -1166,7 +1229,7 @@ { M *mp; - mp = runtime_allocm(p); + mp = runtime_allocm(p, -1, nil, nil); mp->nextp = p; mp->mstartfn = fn; @@ -2348,7 +2411,7 @@ int32 run, grunning, s; // -1 for sysmon - run = runtime_sched.mcount - runtime_sched.nmidle - runtime_sched.mlocked - 1; + run = runtime_sched.mcount - runtime_sched.nmidle - runtime_sched.mlocked - 1 - countextra(); if(run > 0) return; if(run < 0) { diff -r b916a03755cf libgo/runtime/runtime.h --- a/libgo/runtime/runtime.h Mon Jul 22 21:40:43 2013 -0700 +++ b/libgo/runtime/runtime.h Tue Jul 23 13:19:03 2013 -0700 @@ -273,6 +273,7 @@ GCStats gcstats; bool racecall; bool needextram; + bool dropextram; // for gccgo: drop after call is done. void* racepc; void (*waitunlockf)(Lock*); void* waitlock; @@ -450,6 +451,7 @@ extern M* runtime_allm; extern P** runtime_allp; extern int32 runtime_gomaxprocs; +extern uint32 runtime_needextram; extern bool runtime_singleproc; extern uint32 runtime_panicking; extern uint32 runtime_gcwaiting; // gc is waiting to run @@ -518,6 +520,8 @@ void runtime_mpreinit(M*); void runtime_minit(void); void runtime_unminit(void); +void runtime_needm(void); +void runtime_dropm(void); void runtime_signalstack(byte*, int32); MCache* runtime_allocmcache(void); void runtime_freemcache(MCache*);