Commit 6184f46e authored by Rick Hudson's avatar Rick Hudson

[dev.garbage] runtime: Concurrent scan code

Routines and logic to preform a concurrent stack scan of go-routines.
This CL excersizes most of the functionality needed. The
major exception being that it does not scan running goroutines.
After doing the scans it relies on a STW to finish the GC, including
rescanning the stacks. It is intended to achieve correctness,
performance will follow.

LGTM=rsc
R=golang-codereviews, rsc
CC=dvyukov, golang-codereviews
https://golang.org/cl/156580043
parent 62a4359e
...@@ -438,7 +438,15 @@ func gogc(force int32) { ...@@ -438,7 +438,15 @@ func gogc(force int32) {
mp = acquirem() mp = acquirem()
mp.gcing = 1 mp.gcing = 1
releasem(mp) releasem(mp)
onM(stoptheworld) onM(stoptheworld)
onM(finishsweep_m) // finish sweep before we start concurrent scan.
onM(starttheworld)
// Do a concurrent heap scan before we stop the world.
onM(gcscan_m)
onM(stoptheworld)
if mp != acquirem() { if mp != acquirem() {
gothrow("gogc: rescheduled") gothrow("gogc: rescheduled")
} }
......
...@@ -343,11 +343,6 @@ struct MCache ...@@ -343,11 +343,6 @@ struct MCache
StackFreeList stackcache[NumStackOrders]; StackFreeList stackcache[NumStackOrders];
SudoG* sudogcache; SudoG* sudogcache;
// Cached P local buffer holding grey objects (marked by not yet scanned)
// Used by mutator for write barrier work.
// GC uses the mcache of the P it is running on for stack and global scanning
// work as well marking.
Workbuf* gcworkbuf;
// Local allocator stats, flushed during GC. // Local allocator stats, flushed during GC.
uintptr local_nlookup; // number of pointer lookups uintptr local_nlookup; // number of pointer lookups
......
...@@ -39,12 +39,12 @@ runtime·allocmcache(void) ...@@ -39,12 +39,12 @@ runtime·allocmcache(void)
return c; return c;
} }
// mheap.lock needs to be held to release the gcworkbuf.
static void static void
freemcache(MCache *c) freemcache(MCache *c)
{ {
runtime·MCache_ReleaseAll(c); runtime·MCache_ReleaseAll(c);
runtime·stackcache_clear(c); runtime·stackcache_clear(c);
runtime·gcworkbuffree(c->gcworkbuf);
runtime·lock(&runtime·mheap.lock); runtime·lock(&runtime·mheap.lock);
runtime·purgecachedstats(c); runtime·purgecachedstats(c);
runtime·FixAlloc_Free(&runtime·mheap.cachealloc, c); runtime·FixAlloc_Free(&runtime·mheap.cachealloc, c);
......
This diff is collapsed.
...@@ -423,13 +423,7 @@ runtime·casgstatus(G *gp, uint32 oldval, uint32 newval) ...@@ -423,13 +423,7 @@ runtime·casgstatus(G *gp, uint32 oldval, uint32 newval)
// loop if gp->atomicstatus is in a scan state giving // loop if gp->atomicstatus is in a scan state giving
// GC time to finish and change the state to oldval. // GC time to finish and change the state to oldval.
while(!runtime·cas(&gp->atomicstatus, oldval, newval)) { while(!runtime·cas(&gp->atomicstatus, oldval, newval)) {
// Help GC if needed.
if(gp->preemptscan && !gp->gcworkdone && (oldval == Grunning || oldval == Gsyscall)) {
gp->preemptscan = false;
g->m->ptrarg[0] = gp;
fn = helpcasgstatus;
runtime·onM(&fn);
}
} }
} }
...@@ -504,6 +498,13 @@ runtime·stopg(G *gp) ...@@ -504,6 +498,13 @@ runtime·stopg(G *gp)
return false; return false;
case Grunning: case Grunning:
if(runtime·gcphase == GCscan) {
gp->gcworkdone = true;
return false;
// Running routines not scanned during
// GCscan phase, we only scan non-running routines.
}
// Claim goroutine, so we aren't racing with a status // Claim goroutine, so we aren't racing with a status
// transition away from Grunning. // transition away from Grunning.
if(!runtime·castogscanstatus(gp, Grunning, Gscanrunning)) if(!runtime·castogscanstatus(gp, Grunning, Gscanrunning))
...@@ -1918,6 +1919,7 @@ exitsyscallfast(void) ...@@ -1918,6 +1919,7 @@ exitsyscallfast(void)
// Freezetheworld sets stopwait but does not retake P's. // Freezetheworld sets stopwait but does not retake P's.
if(runtime·sched.stopwait) { if(runtime·sched.stopwait) {
g->m->mcache = nil;
g->m->p = nil; g->m->p = nil;
return false; return false;
} }
...@@ -1930,6 +1932,7 @@ exitsyscallfast(void) ...@@ -1930,6 +1932,7 @@ exitsyscallfast(void)
return true; return true;
} }
// Try to get any other idle P. // Try to get any other idle P.
g->m->mcache = nil;
g->m->p = nil; g->m->p = nil;
if(runtime·sched.pidle) { if(runtime·sched.pidle) {
fn = exitsyscallfast_pidle; fn = exitsyscallfast_pidle;
...@@ -2617,6 +2620,8 @@ runtime·setcpuprofilerate_m(void) ...@@ -2617,6 +2620,8 @@ runtime·setcpuprofilerate_m(void)
P *runtime·newP(void); P *runtime·newP(void);
// Change number of processors. The world is stopped, sched is locked. // Change number of processors. The world is stopped, sched is locked.
// gcworkbufs are not being modified by either the GC or
// the write barrier code.
static void static void
procresize(int32 new) procresize(int32 new)
{ {
......
...@@ -649,6 +649,7 @@ struct ForceGCState ...@@ -649,6 +649,7 @@ struct ForceGCState
}; };
extern uint32 runtime·gcphase; extern uint32 runtime·gcphase;
extern Mutex runtime·allglock;
/* /*
* defined macros * defined macros
...@@ -677,6 +678,7 @@ enum { ...@@ -677,6 +678,7 @@ enum {
uint32 runtime·readgstatus(G*); uint32 runtime·readgstatus(G*);
void runtime·casgstatus(G*, uint32, uint32); void runtime·casgstatus(G*, uint32, uint32);
bool runtime·castogscanstatus(G*, uint32, uint32);
void runtime·quiesce(G*); void runtime·quiesce(G*);
bool runtime·stopg(G*); bool runtime·stopg(G*);
void runtime·restartg(G*); void runtime·restartg(G*);
......
...@@ -587,13 +587,13 @@ adjustsudogs(G *gp, AdjustInfo *adjinfo) ...@@ -587,13 +587,13 @@ adjustsudogs(G *gp, AdjustInfo *adjinfo)
} }
// Copies gp's stack to a new stack of a different size. // Copies gp's stack to a new stack of a different size.
// Caller must have changed gp status to Gcopystack.
static void static void
copystack(G *gp, uintptr newsize) copystack(G *gp, uintptr newsize)
{ {
Stack old, new; Stack old, new;
uintptr used; uintptr used;
AdjustInfo adjinfo; AdjustInfo adjinfo;
uint32 oldstatus;
bool (*cb)(Stkframe*, void*); bool (*cb)(Stkframe*, void*);
byte *p, *ep; byte *p, *ep;
...@@ -637,20 +637,11 @@ copystack(G *gp, uintptr newsize) ...@@ -637,20 +637,11 @@ copystack(G *gp, uintptr newsize)
} }
runtime·memmove((byte*)new.hi - used, (byte*)old.hi - used, used); runtime·memmove((byte*)new.hi - used, (byte*)old.hi - used, used);
oldstatus = runtime·readgstatus(gp);
oldstatus &= ~Gscan;
if(oldstatus == Gwaiting || oldstatus == Grunnable)
runtime·casgstatus(gp, oldstatus, Gcopystack); // oldstatus is Gwaiting or Grunnable
else
runtime·throw("copystack: bad status, not Gwaiting or Grunnable");
// Swap out old stack for new one // Swap out old stack for new one
gp->stack = new; gp->stack = new;
gp->stackguard0 = new.lo + StackGuard; // NOTE: might clobber a preempt request gp->stackguard0 = new.lo + StackGuard; // NOTE: might clobber a preempt request
gp->sched.sp = new.hi - used; gp->sched.sp = new.hi - used;
runtime·casgstatus(gp, Gcopystack, oldstatus); // oldstatus is Gwaiting or Grunnable
// free old stack // free old stack
if(StackPoisonCopy) { if(StackPoisonCopy) {
p = (byte*)old.lo; p = (byte*)old.lo;
...@@ -700,6 +691,7 @@ void ...@@ -700,6 +691,7 @@ void
runtime·newstack(void) runtime·newstack(void)
{ {
int32 oldsize, newsize; int32 oldsize, newsize;
uint32 oldstatus;
uintptr sp; uintptr sp;
G *gp; G *gp;
Gobuf morebuf; Gobuf morebuf;
...@@ -789,12 +781,15 @@ runtime·newstack(void) ...@@ -789,12 +781,15 @@ runtime·newstack(void)
runtime·throw("stack overflow"); runtime·throw("stack overflow");
} }
// Note that the concurrent GC might be scanning the stack as we try to replace it. oldstatus = runtime·readgstatus(gp);
// copystack takes care of the appropriate coordination with the stack scanner. oldstatus &= ~Gscan;
runtime·casgstatus(gp, oldstatus, Gcopystack); // oldstatus is Gwaiting or Grunnable
// The concurrent GC will not scan the stack while we are doing the copy since
// the gp is in a Gcopystack status.
copystack(gp, newsize); copystack(gp, newsize);
if(StackDebug >= 1) if(StackDebug >= 1)
runtime·printf("stack grow done\n"); runtime·printf("stack grow done\n");
runtime·casgstatus(gp, Gwaiting, Grunning); runtime·casgstatus(gp, Gcopystack, Grunning);
runtime·gogo(&gp->sched); runtime·gogo(&gp->sched);
} }
...@@ -825,6 +820,7 @@ void ...@@ -825,6 +820,7 @@ void
runtime·shrinkstack(G *gp) runtime·shrinkstack(G *gp)
{ {
uintptr used, oldsize, newsize; uintptr used, oldsize, newsize;
uint32 oldstatus;
if(runtime·readgstatus(gp) == Gdead) { if(runtime·readgstatus(gp) == Gdead) {
if(gp->stack.lo != 0) { if(gp->stack.lo != 0) {
...@@ -858,8 +854,19 @@ runtime·shrinkstack(G *gp) ...@@ -858,8 +854,19 @@ runtime·shrinkstack(G *gp)
#endif #endif
if(StackDebug > 0) if(StackDebug > 0)
runtime·printf("shrinking stack %D->%D\n", (uint64)oldsize, (uint64)newsize); runtime·printf("shrinking stack %D->%D\n", (uint64)oldsize, (uint64)newsize);
// This is being done in a Gscan state and was initiated by the GC so no need to move to
// the Gcopystate.
// The world is stopped, so the goroutine must be Gwaiting or Grunnable,
// and what it is is not changing underfoot.
oldstatus = runtime·readgstatus(gp);
oldstatus &= ~Gscan;
if(oldstatus != Gwaiting && oldstatus != Grunnable)
runtime·throw("status is not Gwaiting or Grunnable");
runtime·casgstatus(gp, oldstatus, Gcopystack);
copystack(gp, newsize); copystack(gp, newsize);
} runtime·casgstatus(gp, Gcopystack, oldstatus);
}
// Do any delayed stack freeing that was queued up during GC. // Do any delayed stack freeing that was queued up during GC.
void void
......
...@@ -106,6 +106,8 @@ func recovery_m(*g) ...@@ -106,6 +106,8 @@ func recovery_m(*g)
func mcacheRefill_m() func mcacheRefill_m()
func largeAlloc_m() func largeAlloc_m()
func gc_m() func gc_m()
func gcscan_m()
func finishsweep_m()
func scavenge_m() func scavenge_m()
func setFinalizer_m() func setFinalizer_m()
func removeFinalizer_m() func removeFinalizer_m()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment