// Copyright 2013 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. package runtime import "unsafe" const ( // stackDebug == 0: no logging // == 1: logging of per-stack operations // == 2: logging of per-frame operations // == 3: logging of per-word updates // == 4: logging of per-word reads stackDebug = 0 stackFromSystem = 0 // allocate stacks from system memory instead of the heap stackFaultOnFree = 0 // old stacks are mapped noaccess to detect use after free stackPoisonCopy = 0 // fill stack that should not be accessed with garbage, to detect bad dereferences during copy stackCache = 1 ) const ( uintptrMask = 1<<(8*ptrSize) - 1 poisonStack = uintptrMask & 0x6868686868686868 // Goroutine preemption request. // Stored into g->stackguard0 to cause split stack check failure. // Must be greater than any real sp. // 0xfffffade in hex. stackPreempt = uintptrMask & -1314 // Thread is forking. // Stored into g->stackguard0 to cause split stack check failure. // Must be greater than any real sp. stackFork = uintptrMask & -1234 ) // Global pool of spans that have free stacks. // Stacks are assigned an order according to size. // order = log_2(size/FixedStack) // There is a free list for each order. // TODO: one lock per order? var stackpool [_NumStackOrders]mspan var stackpoolmu mutex // List of stack spans to be freed at the end of GC. Protected by // stackpoolmu. var stackFreeQueue mspan // Cached value of haveexperiment("framepointer") var framepointer_enabled bool func stackinit() { if _StackCacheSize&_PageMask != 0 { throw("cache size must be a multiple of page size") } for i := range stackpool { mSpanList_Init(&stackpool[i]) } mSpanList_Init(&stackFreeQueue) } // Allocates a stack from the free pool. Must be called with // stackpoolmu held. func stackpoolalloc(order uint8) gclinkptr { list := &stackpool[order] s := list.next if s == list { // no free stacks. Allocate another span worth. s = mHeap_AllocStack(&mheap_, _StackCacheSize>>_PageShift) if s == nil { throw("out of memory") } if s.ref != 0 { throw("bad ref") } if s.freelist.ptr() != nil { throw("bad freelist") } for i := uintptr(0); i < _StackCacheSize; i += _FixedStack << order { x := gclinkptr(uintptr(s.start)<<_PageShift + i) x.ptr().next = s.freelist s.freelist = x } mSpanList_Insert(list, s) } x := s.freelist if x.ptr() == nil { throw("span has no free stacks") } s.freelist = x.ptr().next s.ref++ if s.freelist.ptr() == nil { // all stacks in s are allocated. mSpanList_Remove(s) } return x } // Adds stack x to the free pool. Must be called with stackpoolmu held. func stackpoolfree(x gclinkptr, order uint8) { s := mHeap_Lookup(&mheap_, (unsafe.Pointer)(x)) if s.state != _MSpanStack { throw("freeing stack not in a stack span") } if s.freelist.ptr() == nil { // s will now have a free stack mSpanList_Insert(&stackpool[order], s) } x.ptr().next = s.freelist s.freelist = x s.ref-- if gcphase == _GCoff && s.ref == 0 { // Span is completely free. Return it to the heap // immediately if we're sweeping. // // If GC is active, we delay the free until the end of // GC to avoid the following type of situation: // // 1) GC starts, scans a SudoG but does not yet mark the SudoG.elem pointer // 2) The stack that pointer points to is copied // 3) The old stack is freed // 4) The containing span is marked free // 5) GC attempts to mark the SudoG.elem pointer. The // marking fails because the pointer looks like a // pointer into a free span. // // By not freeing, we prevent step #4 until GC is done. mSpanList_Remove(s) s.freelist = 0 mHeap_FreeStack(&mheap_, s) } } // stackcacherefill/stackcacherelease implement a global pool of stack segments. // The pool is required to prevent unlimited growth of per-thread caches. func stackcacherefill(c *mcache, order uint8) { if stackDebug >= 1 { print("stackcacherefill order=", order, "\n") } // Grab some stacks from the global cache. // Grab half of the allowed capacity (to prevent thrashing). var list gclinkptr var size uintptr lock(&stackpoolmu) for size < _StackCacheSize/2 { x := stackpoolalloc(order) x.ptr().next = list list = x size += _FixedStack << order } unlock(&stackpoolmu) c.stackcache[order].list = list c.stackcache[order].size = size } func stackcacherelease(c *mcache, order uint8) { if stackDebug >= 1 { print("stackcacherelease order=", order, "\n") } x := c.stackcache[order].list size := c.stackcache[order].size lock(&stackpoolmu) for size > _StackCacheSize/2 { y := x.ptr().next stackpoolfree(x, order) x = y size -= _FixedStack << order } unlock(&stackpoolmu) c.stackcache[order].list = x c.stackcache[order].size = size } func stackcache_clear(c *mcache) { if stackDebug >= 1 { print("stackcache clear\n") } lock(&stackpoolmu) for order := uint8(0); order < _NumStackOrders; order++ { x := c.stackcache[order].list for x.ptr() != nil { y := x.ptr().next stackpoolfree(x, order) x = y } c.stackcache[order].list = 0 c.stackcache[order].size = 0 } unlock(&stackpoolmu) } func stackalloc(n uint32) (stack, []stkbar) { // Stackalloc must be called on scheduler stack, so that we // never try to grow the stack during the code that stackalloc runs. // Doing so would cause a deadlock (issue 1547). thisg := getg() if thisg != thisg.m.g0 { throw("stackalloc not on scheduler stack") } if n&(n-1) != 0 { throw("stack size not a power of 2") } if stackDebug >= 1 { print("stackalloc ", n, "\n") } // Compute the size of stack barrier array. maxstkbar := gcMaxStackBarriers(int(n)) nstkbar := unsafe.Sizeof(stkbar{}) * uintptr(maxstkbar) if debug.efence != 0 || stackFromSystem != 0 { v := sysAlloc(round(uintptr(n), _PageSize), &memstats.stacks_sys) if v == nil { throw("out of memory (stackalloc)") } top := uintptr(n) - nstkbar stkbarSlice := slice{add(v, top), 0, maxstkbar} return stack{uintptr(v), uintptr(v) + top}, *(*[]stkbar)(unsafe.Pointer(&stkbarSlice)) } // Small stacks are allocated with a fixed-size free-list allocator. // If we need a stack of a bigger size, we fall back on allocating // a dedicated span. var v unsafe.Pointer if stackCache != 0 && n < _FixedStack<<_NumStackOrders && n < _StackCacheSize { order := uint8(0) n2 := n for n2 > _FixedStack { order++ n2 >>= 1 } var x gclinkptr c := thisg.m.mcache if c == nil || thisg.m.preemptoff != "" || thisg.m.helpgc != 0 { // c == nil can happen in the guts of exitsyscall or // procresize. Just get a stack from the global pool. // Also don't touch stackcache during gc // as it's flushed concurrently. lock(&stackpoolmu) x = stackpoolalloc(order) unlock(&stackpoolmu) } else { x = c.stackcache[order].list if x.ptr() == nil { stackcacherefill(c, order) x = c.stackcache[order].list } c.stackcache[order].list = x.ptr().next c.stackcache[order].size -= uintptr(n) } v = (unsafe.Pointer)(x) } else { s := mHeap_AllocStack(&mheap_, round(uintptr(n), _PageSize)>>_PageShift) if s == nil { throw("out of memory") } v = (unsafe.Pointer)(s.start << _PageShift) } if raceenabled { racemalloc(v, uintptr(n)) } if stackDebug >= 1 { print(" allocated ", v, "\n") } top := uintptr(n) - nstkbar stkbarSlice := slice{add(v, top), 0, maxstkbar} return stack{uintptr(v), uintptr(v) + top}, *(*[]stkbar)(unsafe.Pointer(&stkbarSlice)) } func stackfree(stk stack, n uintptr) { gp := getg() v := (unsafe.Pointer)(stk.lo) if n&(n-1) != 0 { throw("stack not a power of 2") } if stk.lo+n < stk.hi { throw("bad stack size") } if stackDebug >= 1 { println("stackfree", v, n) memclr(v, n) // for testing, clobber stack data } if debug.efence != 0 || stackFromSystem != 0 { if debug.efence != 0 || stackFaultOnFree != 0 { sysFault(v, n) } else { sysFree(v, n, &memstats.stacks_sys) } return } if stackCache != 0 && n < _FixedStack<<_NumStackOrders && n < _StackCacheSize { order := uint8(0) n2 := n for n2 > _FixedStack { order++ n2 >>= 1 } x := gclinkptr(v) c := gp.m.mcache if c == nil || gp.m.preemptoff != "" || gp.m.helpgc != 0 { lock(&stackpoolmu) stackpoolfree(x, order) unlock(&stackpoolmu) } else { if c.stackcache[order].size >= _StackCacheSize { stackcacherelease(c, order) } x.ptr().next = c.stackcache[order].list c.stackcache[order].list = x c.stackcache[order].size += n } } else { s := mHeap_Lookup(&mheap_, v) if s.state != _MSpanStack { println(hex(s.start<<_PageShift), v) throw("bad span state") } if gcphase == _GCoff { // Free the stack immediately if we're // sweeping. mHeap_FreeStack(&mheap_, s) } else { // Otherwise, add it to a list of stack spans // to be freed at the end of GC. // // TODO(austin): Make it possible to re-use // these spans as stacks, like we do for small // stack spans. (See issue #11466.) lock(&stackpoolmu) mSpanList_Insert(&stackFreeQueue, s) unlock(&stackpoolmu) } } } var maxstacksize uintptr = 1 << 20 // enough until runtime.main sets it for real var ptrnames = []string{ 0: "scalar", 1: "ptr", } // Stack frame layout // // (x86) // +------------------+ // | args from caller | // +------------------+ <- frame->argp // | return address | // +------------------+ // | caller's BP (*) | (*) if framepointer_enabled && varp < sp // +------------------+ <- frame->varp // | locals | // +------------------+ // | args to callee | // +------------------+ <- frame->sp // // (arm) // +------------------+ // | args from caller | // +------------------+ <- frame->argp // | caller's retaddr | // +------------------+ <- frame->varp // | locals | // +------------------+ // | args to callee | // +------------------+ // | return address | // +------------------+ <- frame->sp type adjustinfo struct { old stack delta uintptr // ptr distance from old to new stack (newbase - oldbase) } // Adjustpointer checks whether *vpp is in the old stack described by adjinfo. // If so, it rewrites *vpp to point into the new stack. func adjustpointer(adjinfo *adjustinfo, vpp unsafe.Pointer) { pp := (*unsafe.Pointer)(vpp) p := *pp if stackDebug >= 4 { print(" ", pp, ":", p, "\n") } if adjinfo.old.lo <= uintptr(p) && uintptr(p) < adjinfo.old.hi { *pp = add(p, adjinfo.delta) if stackDebug >= 3 { print(" adjust ptr ", pp, ":", p, " -> ", *pp, "\n") } } } // Information from the compiler about the layout of stack frames. type bitvector struct { n int32 // # of bits bytedata *uint8 } type gobitvector struct { n uintptr bytedata []uint8 } func gobv(bv bitvector) gobitvector { return gobitvector{ uintptr(bv.n), (*[1 << 30]byte)(unsafe.Pointer(bv.bytedata))[:(bv.n+7)/8], } } func ptrbit(bv *gobitvector, i uintptr) uint8 { return (bv.bytedata[i/8] >> (i % 8)) & 1 } // bv describes the memory starting at address scanp. // Adjust any pointers contained therein. func adjustpointers(scanp unsafe.Pointer, cbv *bitvector, adjinfo *adjustinfo, f *_func) { bv := gobv(*cbv) minp := adjinfo.old.lo maxp := adjinfo.old.hi delta := adjinfo.delta num := uintptr(bv.n) for i := uintptr(0); i < num; i++ { if stackDebug >= 4 { print(" ", add(scanp, i*ptrSize), ":", ptrnames[ptrbit(&bv, i)], ":", hex(*(*uintptr)(add(scanp, i*ptrSize))), " # ", i, " ", bv.bytedata[i/8], "\n") } if ptrbit(&bv, i) == 1 { pp := (*uintptr)(add(scanp, i*ptrSize)) p := *pp if f != nil && 0 < p && p < _PageSize && debug.invalidptr != 0 || p == poisonStack { // Looks like a junk value in a pointer slot. // Live analysis wrong? getg().m.traceback = 2 print("runtime: bad pointer in frame ", funcname(f), " at ", pp, ": ", hex(p), "\n") throw("invalid stack pointer") } if minp <= p && p < maxp { if stackDebug >= 3 { print("adjust ptr ", p, " ", funcname(f), "\n") } *pp = p + delta } } } } // Note: the argument/return area is adjusted by the callee. func adjustframe(frame *stkframe, arg unsafe.Pointer) bool { adjinfo := (*adjustinfo)(arg) targetpc := frame.continpc if targetpc == 0 { // Frame is dead. return true } f := frame.fn if stackDebug >= 2 { print(" adjusting ", funcname(f), " frame=[", hex(frame.sp), ",", hex(frame.fp), "] pc=", hex(frame.pc), " continpc=", hex(frame.continpc), "\n") } if f.entry == systemstack_switchPC { // A special routine at the bottom of stack of a goroutine that does an systemstack call. // We will allow it to be copied even though we don't // have full GC info for it (because it is written in asm). return true } if targetpc != f.entry { targetpc-- } pcdata := pcdatavalue(f, _PCDATA_StackMapIndex, targetpc) if pcdata == -1 { pcdata = 0 // in prologue } // Adjust local variables if stack frame has been allocated. size := frame.varp - frame.sp var minsize uintptr switch thechar { case '6', '8': minsize = 0 case '7': minsize = spAlign default: minsize = ptrSize } if size > minsize { var bv bitvector stackmap := (*stackmap)(funcdata(f, _FUNCDATA_LocalsPointerMaps)) if stackmap == nil || stackmap.n <= 0 { print("runtime: frame ", funcname(f), " untyped locals ", hex(frame.varp-size), "+", hex(size), "\n") throw("missing stackmap") } // Locals bitmap information, scan just the pointers in locals. if pcdata < 0 || pcdata >= stackmap.n { // don't know where we are print("runtime: pcdata is ", pcdata, " and ", stackmap.n, " locals stack map entries for ", funcname(f), " (targetpc=", targetpc, ")\n") throw("bad symbol table") } bv = stackmapdata(stackmap, pcdata) size = uintptr(bv.n) * ptrSize if stackDebug >= 3 { print(" locals ", pcdata, "/", stackmap.n, " ", size/ptrSize, " words ", bv.bytedata, "\n") } adjustpointers(unsafe.Pointer(frame.varp-size), &bv, adjinfo, f) } // Adjust saved base pointer if there is one. if thechar == '6' && frame.argp-frame.varp == 2*regSize { if !framepointer_enabled { print("runtime: found space for saved base pointer, but no framepointer experiment\n") print("argp=", hex(frame.argp), " varp=", hex(frame.varp), "\n") throw("bad frame layout") } if stackDebug >= 3 { print(" saved bp\n") } adjustpointer(adjinfo, unsafe.Pointer(frame.varp)) } // Adjust arguments. if frame.arglen > 0 { var bv bitvector if frame.argmap != nil { bv = *frame.argmap } else { stackmap := (*stackmap)(funcdata(f, _FUNCDATA_ArgsPointerMaps)) if stackmap == nil || stackmap.n <= 0 { print("runtime: frame ", funcname(f), " untyped args ", frame.argp, "+", uintptr(frame.arglen), "\n") throw("missing stackmap") } if pcdata < 0 || pcdata >= stackmap.n { // don't know where we are print("runtime: pcdata is ", pcdata, " and ", stackmap.n, " args stack map entries for ", funcname(f), " (targetpc=", targetpc, ")\n") throw("bad symbol table") } bv = stackmapdata(stackmap, pcdata) } if stackDebug >= 3 { print(" args\n") } adjustpointers(unsafe.Pointer(frame.argp), &bv, adjinfo, nil) } return true } func adjustctxt(gp *g, adjinfo *adjustinfo) { adjustpointer(adjinfo, (unsafe.Pointer)(&gp.sched.ctxt)) } func adjustdefers(gp *g, adjinfo *adjustinfo) { // Adjust defer argument blocks the same way we adjust active stack frames. tracebackdefers(gp, adjustframe, noescape(unsafe.Pointer(adjinfo))) // Adjust pointers in the Defer structs. // Defer structs themselves are never on the stack. for d := gp._defer; d != nil; d = d.link { adjustpointer(adjinfo, (unsafe.Pointer)(&d.fn)) adjustpointer(adjinfo, (unsafe.Pointer)(&d.sp)) adjustpointer(adjinfo, (unsafe.Pointer)(&d._panic)) } } func adjustpanics(gp *g, adjinfo *adjustinfo) { // Panics are on stack and already adjusted. // Update pointer to head of list in G. adjustpointer(adjinfo, (unsafe.Pointer)(&gp._panic)) } func adjustsudogs(gp *g, adjinfo *adjustinfo) { // the data elements pointed to by a SudoG structure // might be in the stack. for s := gp.waiting; s != nil; s = s.waitlink { adjustpointer(adjinfo, (unsafe.Pointer)(&s.elem)) adjustpointer(adjinfo, (unsafe.Pointer)(&s.selectdone)) } } func adjuststkbar(gp *g, adjinfo *adjustinfo) { for i := int(gp.stkbarPos); i < len(gp.stkbar); i++ { adjustpointer(adjinfo, (unsafe.Pointer)(&gp.stkbar[i].savedLRPtr)) } } func fillstack(stk stack, b byte) { for p := stk.lo; p < stk.hi; p++ { *(*byte)(unsafe.Pointer(p)) = b } } // Copies gp's stack to a new stack of a different size. // Caller must have changed gp status to Gcopystack. func copystack(gp *g, newsize uintptr) { if gp.syscallsp != 0 { throw("stack growth not allowed in system call") } old := gp.stack if old.lo == 0 { throw("nil stackbase") } used := old.hi - gp.sched.sp // allocate new stack new, newstkbar := stackalloc(uint32(newsize)) if stackPoisonCopy != 0 { fillstack(new, 0xfd) } if stackDebug >= 1 { print("copystack gp=", gp, " [", hex(old.lo), " ", hex(old.hi-used), " ", hex(old.hi), "]/", gp.stackAlloc, " -> [", hex(new.lo), " ", hex(new.hi-used), " ", hex(new.hi), "]/", newsize, "\n") } // adjust pointers in the to-be-copied frames var adjinfo adjustinfo adjinfo.old = old adjinfo.delta = new.hi - old.hi gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, adjustframe, noescape(unsafe.Pointer(&adjinfo)), 0) // adjust other miscellaneous things that have pointers into stacks. adjustctxt(gp, &adjinfo) adjustdefers(gp, &adjinfo) adjustpanics(gp, &adjinfo) adjustsudogs(gp, &adjinfo) adjuststkbar(gp, &adjinfo) // copy the stack to the new location if stackPoisonCopy != 0 { fillstack(new, 0xfb) } memmove(unsafe.Pointer(new.hi-used), unsafe.Pointer(old.hi-used), used) // copy old stack barriers to new stack barrier array newstkbar = newstkbar[:len(gp.stkbar)] copy(newstkbar, gp.stkbar) // Swap out old stack for new one gp.stack = new gp.stackguard0 = new.lo + _StackGuard // NOTE: might clobber a preempt request gp.sched.sp = new.hi - used oldsize := gp.stackAlloc gp.stackAlloc = newsize gp.stkbar = newstkbar // free old stack if stackPoisonCopy != 0 { fillstack(old, 0xfc) } stackfree(old, oldsize) } // round x up to a power of 2. func round2(x int32) int32 { s := uint(0) for 1<<s < x { s++ } return 1 << s } // Called from runtime·morestack when more stack is needed. // Allocate larger stack and relocate to new stack. // Stack growth is multiplicative, for constant amortized cost. // // g->atomicstatus will be Grunning or Gscanrunning upon entry. // If the GC is trying to stop this g then it will set preemptscan to true. func newstack() { thisg := getg() // TODO: double check all gp. shouldn't be getg(). if thisg.m.morebuf.g.ptr().stackguard0 == stackFork { throw("stack growth after fork") } if thisg.m.morebuf.g.ptr() != thisg.m.curg { print("runtime: newstack called from g=", thisg.m.morebuf.g, "\n"+"\tm=", thisg.m, " m->curg=", thisg.m.curg, " m->g0=", thisg.m.g0, " m->gsignal=", thisg.m.gsignal, "\n") morebuf := thisg.m.morebuf traceback(morebuf.pc, morebuf.sp, morebuf.lr, morebuf.g.ptr()) throw("runtime: wrong goroutine in newstack") } if thisg.m.curg.throwsplit { gp := thisg.m.curg // Update syscallsp, syscallpc in case traceback uses them. morebuf := thisg.m.morebuf gp.syscallsp = morebuf.sp gp.syscallpc = morebuf.pc print("runtime: newstack sp=", hex(gp.sched.sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n", "\tmorebuf={pc:", hex(morebuf.pc), " sp:", hex(morebuf.sp), " lr:", hex(morebuf.lr), "}\n", "\tsched={pc:", hex(gp.sched.pc), " sp:", hex(gp.sched.sp), " lr:", hex(gp.sched.lr), " ctxt:", gp.sched.ctxt, "}\n") traceback(morebuf.pc, morebuf.sp, morebuf.lr, gp) throw("runtime: stack split at bad time") } gp := thisg.m.curg morebuf := thisg.m.morebuf thisg.m.morebuf.pc = 0 thisg.m.morebuf.lr = 0 thisg.m.morebuf.sp = 0 thisg.m.morebuf.g = 0 rewindmorestack(&gp.sched) // NOTE: stackguard0 may change underfoot, if another thread // is about to try to preempt gp. Read it just once and use that same // value now and below. preempt := atomicloaduintptr(&gp.stackguard0) == stackPreempt // Be conservative about where we preempt. // We are interested in preempting user Go code, not runtime code. // If we're holding locks, mallocing, or preemption is disabled, don't // preempt. // This check is very early in newstack so that even the status change // from Grunning to Gwaiting and back doesn't happen in this case. // That status change by itself can be viewed as a small preemption, // because the GC might change Gwaiting to Gscanwaiting, and then // this goroutine has to wait for the GC to finish before continuing. // If the GC is in some way dependent on this goroutine (for example, // it needs a lock held by the goroutine), that small preemption turns // into a real deadlock. if preempt { if thisg.m.locks != 0 || thisg.m.mallocing != 0 || thisg.m.preemptoff != "" || thisg.m.p.ptr().status != _Prunning { // Let the goroutine keep running for now. // gp->preempt is set, so it will be preempted next time. gp.stackguard0 = gp.stack.lo + _StackGuard gogo(&gp.sched) // never return } } // The goroutine must be executing in order to call newstack, // so it must be Grunning (or Gscanrunning). casgstatus(gp, _Grunning, _Gwaiting) gp.waitreason = "stack growth" if gp.stack.lo == 0 { throw("missing stack in newstack") } sp := gp.sched.sp if thechar == '6' || thechar == '8' { // The call to morestack cost a word. sp -= ptrSize } if stackDebug >= 1 || sp < gp.stack.lo { print("runtime: newstack sp=", hex(sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n", "\tmorebuf={pc:", hex(morebuf.pc), " sp:", hex(morebuf.sp), " lr:", hex(morebuf.lr), "}\n", "\tsched={pc:", hex(gp.sched.pc), " sp:", hex(gp.sched.sp), " lr:", hex(gp.sched.lr), " ctxt:", gp.sched.ctxt, "}\n") } if sp < gp.stack.lo { print("runtime: gp=", gp, ", gp->status=", hex(readgstatus(gp)), "\n ") print("runtime: split stack overflow: ", hex(sp), " < ", hex(gp.stack.lo), "\n") throw("runtime: split stack overflow") } if gp.sched.ctxt != nil { // morestack wrote sched.ctxt on its way in here, // without a write barrier. Run the write barrier now. // It is not possible to be preempted between then // and now, so it's okay. writebarrierptr_nostore((*uintptr)(unsafe.Pointer(&gp.sched.ctxt)), uintptr(gp.sched.ctxt)) } if preempt { if gp == thisg.m.g0 { throw("runtime: preempt g0") } if thisg.m.p == 0 && thisg.m.locks == 0 { throw("runtime: g is running but p is not") } if gp.preemptscan { for !castogscanstatus(gp, _Gwaiting, _Gscanwaiting) { // Likely to be racing with the GC as // it sees a _Gwaiting and does the // stack scan. If so, gcworkdone will // be set and gcphasework will simply // return. } if !gp.gcscandone { scanstack(gp) gp.gcscandone = true } gp.preemptscan = false gp.preempt = false casfrom_Gscanstatus(gp, _Gscanwaiting, _Gwaiting) casgstatus(gp, _Gwaiting, _Grunning) gp.stackguard0 = gp.stack.lo + _StackGuard gogo(&gp.sched) // never return } // Act like goroutine called runtime.Gosched. casgstatus(gp, _Gwaiting, _Grunning) gopreempt_m(gp) // never return } // Allocate a bigger segment and move the stack. oldsize := int(gp.stackAlloc) newsize := oldsize * 2 if uintptr(newsize) > maxstacksize { print("runtime: goroutine stack exceeds ", maxstacksize, "-byte limit\n") throw("stack overflow") } casgstatus(gp, _Gwaiting, _Gcopystack) // The concurrent GC will not scan the stack while we are doing the copy since // the gp is in a Gcopystack status. copystack(gp, uintptr(newsize)) if stackDebug >= 1 { print("stack grow done\n") } casgstatus(gp, _Gcopystack, _Grunning) gogo(&gp.sched) } //go:nosplit func nilfunc() { *(*uint8)(nil) = 0 } // adjust Gobuf as if it executed a call to fn // and then did an immediate gosave. func gostartcallfn(gobuf *gobuf, fv *funcval) { var fn unsafe.Pointer if fv != nil { fn = (unsafe.Pointer)(fv.fn) } else { fn = unsafe.Pointer(funcPC(nilfunc)) } gostartcall(gobuf, fn, (unsafe.Pointer)(fv)) } // Maybe shrink the stack being used by gp. // Called at garbage collection time. func shrinkstack(gp *g) { if readgstatus(gp) == _Gdead { if gp.stack.lo != 0 { // Free whole stack - it will get reallocated // if G is used again. stackfree(gp.stack, gp.stackAlloc) gp.stack.lo = 0 gp.stack.hi = 0 gp.stkbar = nil gp.stkbarPos = 0 } return } if gp.stack.lo == 0 { throw("missing stack in shrinkstack") } if debug.gcshrinkstackoff > 0 { return } oldsize := gp.stackAlloc newsize := oldsize / 2 // Don't shrink the allocation below the minimum-sized stack // allocation. if newsize < _FixedStack { return } // Compute how much of the stack is currently in use and only // shrink the stack if gp is using less than a quarter of its // current stack. The currently used stack includes everything // down to the SP plus the stack guard space that ensures // there's room for nosplit functions. avail := gp.stack.hi - gp.stack.lo if used := gp.stack.hi - gp.sched.sp + _StackLimit; used >= avail/4 { return } // We can't copy the stack if we're in a syscall. // The syscall might have pointers into the stack. if gp.syscallsp != 0 { return } if goos_windows != 0 && gp.m != nil && gp.m.libcallsp != 0 { return } if stackDebug > 0 { print("shrinking stack ", oldsize, "->", newsize, "\n") } oldstatus := casgcopystack(gp) copystack(gp, newsize) casgstatus(gp, _Gcopystack, oldstatus) } // freeStackSpans frees unused stack spans at the end of GC. func freeStackSpans() { lock(&stackpoolmu) // Scan stack pools for empty stack spans. for order := range stackpool { list := &stackpool[order] for s := list.next; s != list; { next := s.next if s.ref == 0 { mSpanList_Remove(s) s.freelist = 0 mHeap_FreeStack(&mheap_, s) } s = next } } // Free queued stack spans. for stackFreeQueue.next != &stackFreeQueue { s := stackFreeQueue.next mSpanList_Remove(s) mHeap_FreeStack(&mheap_, s) } unlock(&stackpoolmu) } //go:nosplit func morestackc() { systemstack(func() { throw("attempt to execute C code on Go stack") }) }