// Copyright 2015 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. // Garbage collector: stack barriers // // Stack barriers enable the garbage collector to determine how much // of a gorountine stack has changed between when a stack is scanned // during the concurrent scan phase and when it is re-scanned during // the stop-the-world mark termination phase. Mark termination only // needs to re-scan the changed part, so for deep stacks this can // significantly reduce GC pause time compared to the alternative of // re-scanning whole stacks. The deeper the stacks, the more stack // barriers help. // // When stacks are scanned during the concurrent scan phase, the stack // scan installs stack barriers by selecting stack frames and // overwriting the saved return PCs (or link registers) of these // frames with the PC of a "stack barrier trampoline". Later, when a // selected frame returns, it "returns" to this trampoline instead of // returning to its actual caller. The trampoline records that the // stack has unwound past this frame and jumps to the original return // PC recorded when the stack barrier was installed. Mark termination // re-scans only as far as the first frame that hasn't hit a stack // barrier and then removes and un-hit stack barriers. // // This scheme is very lightweight. No special code is required in the // mutator to record stack unwinding and the trampoline is only a few // assembly instructions. // // Book-keeping // ------------ // // The primary cost of stack barriers is book-keeping: the runtime has // to record the locations of all stack barriers and the original // return PCs in order to return to the correct caller when a stack // barrier is hit and so it can remove un-hit stack barriers. In order // to minimize this cost, the Go runtime places stack barriers in // exponentially-spaced frames, starting 1K past the current frame. // The book-keeping structure hence grows logarithmically with the // size of the stack and mark termination re-scans at most twice as // much stack as necessary. // // The runtime reserves space for this book-keeping structure at the // top of the stack allocation itself (just above the outermost // frame). This is necessary because the regular memory allocator can // itself grow the stack, and hence can't be used when allocating // stack-related structures. // // For debugging, the runtime also supports installing stack barriers // at every frame. However, this requires significantly more // book-keeping space. // // Correctness // ----------- // // The runtime and the compiler cooperate to ensure that all objects // reachable from the stack as of mark termination are marked. // Anything unchanged since the concurrent scan phase will be marked // because it is marked by the concurrent scan. After the concurrent // scan, there are three possible classes of stack modifications that // must be tracked: // // 1) Mutator writes below the lowest un-hit stack barrier. This // includes all writes performed by an executing function to its own // stack frame. This part of the stack will be re-scanned by mark // termination, which will mark any objects made reachable from // modifications to this part of the stack. // // 2) Mutator writes above the lowest un-hit stack barrier. It's // possible for a mutator to modify the stack above the lowest un-hit // stack barrier if a higher frame has passed down a pointer to a // stack variable in its frame. This is called an "up-pointer". The // compiler ensures that writes through up-pointers have an // accompanying write barrier (it simply doesn't distinguish between // writes through up-pointers and writes through heap pointers). This // write barrier marks any object made reachable from modifications to // this part of the stack. // // 3) Runtime writes to the stack. Various runtime operations such as // sends to unbuffered channels can write to arbitrary parts of the // stack, including above the lowest un-hit stack barrier. We solve // this in two ways. In many cases, the runtime can perform an // explicit write barrier operation like in case 2. However, in the // case of bulk memory move (typedmemmove), the runtime doesn't // necessary have ready access to a pointer bitmap for the memory // being copied, so it simply unwinds any stack barriers below the // destination. // // Gotchas // ------- // // Anything that inspects or manipulates the stack potentially needs // to understand stack barriers. The most obvious case is that // gentraceback needs to use the original return PC when it encounters // the stack barrier trampoline. Anything that unwinds the stack such // as panic/recover must unwind stack barriers in tandem with // unwinding the stack. // // Stack barriers require that any goroutine whose stack has been // scanned must execute write barriers. Go solves this by simply // enabling write barriers globally during the concurrent scan phase. // However, traditionally, write barriers are not enabled during this // phase. // // Synchronization // --------------- // // For the most part, accessing and modifying stack barriers is // synchronized around GC safe points. Installing stack barriers // forces the G to a safe point, while all other operations that // modify stack barriers run on the G and prevent it from reaching a // safe point. // // Subtlety arises when a G may be tracebacked when *not* at a safe // point. This happens during sigprof. For this, each G has a "stack // barrier lock" (see gcLockStackBarriers, gcUnlockStackBarriers). // Operations that manipulate stack barriers acquire this lock, while // sigprof tries to acquire it and simply skips the traceback if it // can't acquire it. There is one exception for performance and // complexity reasons: hitting a stack barrier manipulates the stack // barrier list without acquiring the stack barrier lock. For this, // gentraceback performs a special fix up if the traceback starts in // the stack barrier function. package runtime import ( "runtime/internal/atomic" "runtime/internal/sys" "unsafe" ) const debugStackBarrier = false // firstStackBarrierOffset is the approximate byte offset at // which to place the first stack barrier from the current SP. // This is a lower bound on how much stack will have to be // re-scanned during mark termination. Subsequent barriers are // placed at firstStackBarrierOffset * 2^n offsets. // // For debugging, this can be set to 0, which will install a // stack barrier at every frame. If you do this, you may also // have to raise _StackMin, since the stack barrier // bookkeeping will use a large amount of each stack. var firstStackBarrierOffset = 1024 // gcMaxStackBarriers returns the maximum number of stack barriers // that can be installed in a stack of stackSize bytes. func gcMaxStackBarriers(stackSize int) (n int) { if firstStackBarrierOffset == 0 { // Special debugging case for inserting stack barriers // at every frame. Steal half of the stack for the // []stkbar. Technically, if the stack were to consist // solely of return PCs we would need two thirds of // the stack, but stealing that much breaks things and // this doesn't happen in practice. return stackSize / 2 / int(unsafe.Sizeof(stkbar{})) } offset := firstStackBarrierOffset for offset < stackSize { n++ offset *= 2 } return n + 1 } // gcInstallStackBarrier installs a stack barrier over the return PC of frame. //go:nowritebarrier func gcInstallStackBarrier(gp *g, frame *stkframe) bool { if frame.lr == 0 { if debugStackBarrier { print("not installing stack barrier with no LR, goid=", gp.goid, "\n") } return false } if frame.fn.entry == cgocallback_gofuncPC { // cgocallback_gofunc doesn't return to its LR; // instead, its return path puts LR in g.sched.pc and // switches back to the system stack on which // cgocallback_gofunc was originally called. We can't // have a stack barrier in g.sched.pc, so don't // install one in this frame. if debugStackBarrier { print("not installing stack barrier over LR of cgocallback_gofunc, goid=", gp.goid, "\n") } return false } // Save the return PC and overwrite it with stackBarrier. var lrUintptr uintptr if usesLR { lrUintptr = frame.sp } else { lrUintptr = frame.fp - sys.RegSize } lrPtr := (*sys.Uintreg)(unsafe.Pointer(lrUintptr)) if debugStackBarrier { print("install stack barrier at ", hex(lrUintptr), " over ", hex(*lrPtr), ", goid=", gp.goid, "\n") if uintptr(*lrPtr) != frame.lr { print("frame.lr=", hex(frame.lr)) throw("frame.lr differs from stack LR") } } gp.stkbar = gp.stkbar[:len(gp.stkbar)+1] stkbar := &gp.stkbar[len(gp.stkbar)-1] stkbar.savedLRPtr = lrUintptr stkbar.savedLRVal = uintptr(*lrPtr) *lrPtr = sys.Uintreg(stackBarrierPC) return true } // gcRemoveStackBarriers removes all stack barriers installed in gp's stack. //go:nowritebarrier func gcRemoveStackBarriers(gp *g) { if debugStackBarrier && gp.stkbarPos != 0 { print("hit ", gp.stkbarPos, " stack barriers, goid=", gp.goid, "\n") } gcLockStackBarriers(gp) // Remove stack barriers that we didn't hit. for _, stkbar := range gp.stkbar[gp.stkbarPos:] { gcRemoveStackBarrier(gp, stkbar) } // Clear recorded stack barriers so copystack doesn't try to // adjust them. gp.stkbarPos = 0 gp.stkbar = gp.stkbar[:0] gcUnlockStackBarriers(gp) } // gcRemoveStackBarrier removes a single stack barrier. It is the // inverse operation of gcInstallStackBarrier. // // This is nosplit to ensure gp's stack does not move. // //go:nowritebarrier //go:nosplit func gcRemoveStackBarrier(gp *g, stkbar stkbar) { if debugStackBarrier { print("remove stack barrier at ", hex(stkbar.savedLRPtr), " with ", hex(stkbar.savedLRVal), ", goid=", gp.goid, "\n") } lrPtr := (*sys.Uintreg)(unsafe.Pointer(stkbar.savedLRPtr)) if val := *lrPtr; val != sys.Uintreg(stackBarrierPC) { printlock() print("at *", hex(stkbar.savedLRPtr), " expected stack barrier PC ", hex(stackBarrierPC), ", found ", hex(val), ", goid=", gp.goid, "\n") print("gp.stkbar=") gcPrintStkbars(gp, -1) print(", gp.stack=[", hex(gp.stack.lo), ",", hex(gp.stack.hi), ")\n") throw("stack barrier lost") } *lrPtr = sys.Uintreg(stkbar.savedLRVal) } // gcPrintStkbars prints the stack barriers of gp for debugging. It // places a "@@@" marker at gp.stkbarPos. If marker >= 0, it will also // place a "==>" marker before the marker'th entry. func gcPrintStkbars(gp *g, marker int) { print("[") for i, s := range gp.stkbar { if i > 0 { print(" ") } if i == int(gp.stkbarPos) { print("@@@ ") } if i == marker { print("==> ") } print("*", hex(s.savedLRPtr), "=", hex(s.savedLRVal)) } if int(gp.stkbarPos) == len(gp.stkbar) { print(" @@@") } if marker == len(gp.stkbar) { print(" ==>") } print("]") } // gcUnwindBarriers marks all stack barriers up the frame containing // sp as hit and removes them. This is used during stack unwinding for // panic/recover and by heapBitsBulkBarrier to force stack re-scanning // when its destination is on the stack. // // This is nosplit to ensure gp's stack does not move. // //go:nosplit func gcUnwindBarriers(gp *g, sp uintptr) { gcLockStackBarriers(gp) // On LR machines, if there is a stack barrier on the return // from the frame containing sp, this will mark it as hit even // though it isn't, but it's okay to be conservative. before := gp.stkbarPos for int(gp.stkbarPos) < len(gp.stkbar) && gp.stkbar[gp.stkbarPos].savedLRPtr < sp { gcRemoveStackBarrier(gp, gp.stkbar[gp.stkbarPos]) gp.stkbarPos++ } gcUnlockStackBarriers(gp) if debugStackBarrier && gp.stkbarPos != before { print("skip barriers below ", hex(sp), " in goid=", gp.goid, ": ") // We skipped barriers between the "==>" marker // (before) and the "@@@" marker (gp.stkbarPos). gcPrintStkbars(gp, int(before)) print("\n") } } // nextBarrierPC returns the original return PC of the next stack barrier. // Used by getcallerpc, so it must be nosplit. //go:nosplit func nextBarrierPC() uintptr { gp := getg() return gp.stkbar[gp.stkbarPos].savedLRVal } // setNextBarrierPC sets the return PC of the next stack barrier. // Used by setcallerpc, so it must be nosplit. //go:nosplit func setNextBarrierPC(pc uintptr) { gp := getg() gcLockStackBarriers(gp) gp.stkbar[gp.stkbarPos].savedLRVal = pc gcUnlockStackBarriers(gp) } // gcLockStackBarriers synchronizes with tracebacks of gp's stack // during sigprof for installation or removal of stack barriers. It // blocks until any current sigprof is done tracebacking gp's stack // and then disallows profiling tracebacks of gp's stack. // // This is necessary because a sigprof during barrier installation or // removal could observe inconsistencies between the stkbar array and // the stack itself and crash. // //go:nosplit func gcLockStackBarriers(gp *g) { // Disable preemption so scanstack cannot run while the caller // is manipulating the stack barriers. acquirem() for !atomic.Cas(&gp.stackLock, 0, 1) { osyield() } } //go:nosplit func gcTryLockStackBarriers(gp *g) bool { mp := acquirem() result := atomic.Cas(&gp.stackLock, 0, 1) if !result { releasem(mp) } return result } func gcUnlockStackBarriers(gp *g) { atomic.Store(&gp.stackLock, 0) releasem(getg().m) }