runtime: refactor fpunwindExpand to use provided buffer

fpunwindExpand currently allocates a new slice to hold the expanded call
stack. In each place it's used, the resulting slice won't be needed
immediately afterward, so the allocation is wasteful. Refactor
fpunwindExpand to instead expand the call stack into a provided buffer.

Change-Id: I05b26c191a8f76404c21ccbe3bd422325540425b
Reviewed-on: https://go-review.googlesource.com/c/go/+/543715
Reviewed-by: Cherry Mui <cherryyz@google.com>
Auto-Submit: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
This commit is contained in:
Nick Ripley 2023-11-19 20:01:57 -05:00 committed by Gopher Robot
parent f43d9c40f3
commit b5bfb5a3ce

View file

@ -147,20 +147,22 @@ func (t *traceStackTable) put(pcs []uintptr) uint64 {
// releases all memory and resets state. It must only be called once the caller
// can guarantee that there are no more writers to the table.
func (t *traceStackTable) dump(gen uintptr) {
stackBuf := make([]uintptr, traceStackSize)
w := unsafeTraceWriter(gen, nil)
if root := (*traceMapNode)(t.tab.root.Load()); root != nil {
w = dumpStacksRec(root, w)
w = dumpStacksRec(root, w, stackBuf)
}
w.flush().end()
t.tab.reset()
}
func dumpStacksRec(node *traceMapNode, w traceWriter) traceWriter {
func dumpStacksRec(node *traceMapNode, w traceWriter, stackBuf []uintptr) traceWriter {
stack := unsafe.Slice((*uintptr)(unsafe.Pointer(&node.data[0])), uintptr(len(node.data))/unsafe.Sizeof(uintptr(0)))
// N.B. This might allocate, but that's OK because we're not writing to the M's buffer,
// but one we're about to create (with ensure).
frames := makeTraceFrames(w.gen, fpunwindExpand(stack))
n := fpunwindExpand(stackBuf, stack)
frames := makeTraceFrames(w.gen, stackBuf[:n])
// The maximum number of bytes required to hold the encoded stack, given that
// it contains N frames.
@ -194,7 +196,7 @@ func dumpStacksRec(node *traceMapNode, w traceWriter) traceWriter {
if child == nil {
continue
}
w = dumpStacksRec((*traceMapNode)(child), w)
w = dumpStacksRec((*traceMapNode)(child), w, stackBuf)
}
return w
}
@ -260,31 +262,36 @@ func fpTracebackPCs(fp unsafe.Pointer, pcBuf []uintptr) (i int) {
return i
}
// fpunwindExpand expands a call stack from pcBuf into dst,
// returning the number of PCs written to dst.
// pcBuf and dst should not overlap.
//
// fpunwindExpand checks if pcBuf contains logical frames (which include inlined
// frames) or physical frames (produced by frame pointer unwinding) using a
// sentinel value in pcBuf[0]. Logical frames are simply returned without the
// sentinel. Physical frames are turned into logical frames via inline unwinding
// and by applying the skip value that's stored in pcBuf[0].
func fpunwindExpand(pcBuf []uintptr) []uintptr {
func fpunwindExpand(dst, pcBuf []uintptr) int {
if len(pcBuf) > 0 && pcBuf[0] == logicalStackSentinel {
// pcBuf contains logical rather than inlined frames, skip has already been
// applied, just return it without the sentinel value in pcBuf[0].
return pcBuf[1:]
return copy(dst, pcBuf[1:])
}
var (
n int
lastFuncID = abi.FuncIDNormal
newPCBuf = make([]uintptr, 0, traceStackSize)
skip = pcBuf[0]
// skipOrAdd skips or appends retPC to newPCBuf and returns true if more
// pcs can be added.
skipOrAdd = func(retPC uintptr) bool {
if skip > 0 {
skip--
} else {
newPCBuf = append(newPCBuf, retPC)
} else if n < len(dst) {
dst[n] = retPC
n++
}
return len(newPCBuf) < cap(newPCBuf)
return n < len(dst)
}
)
@ -312,7 +319,7 @@ outer:
lastFuncID = sf.funcID
}
}
return newPCBuf
return n
}
// startPCForTrace returns the start PC of a goroutine for tracing purposes.