From: Austin Clements Date: Tue, 14 Feb 2023 18:54:29 +0000 (-0500) Subject: runtime: delete gentraceback X-Git-Tag: go1.21rc1~1314 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=3790ceca9735432486ba34da28f214349e4c1e7e;p=gostls13.git runtime: delete gentraceback Printing is the only remaining functionality of gentraceback. Move this into the traceback printing code and eliminate gentraceback. This lets us simplify the logic, which fixes at least one minor bug: previously, if inline unwinding pushed the total printed count over _TracebackMaxFrames, we would print extra frames and then fail to print "additional frames elided". The cumulative performance effect of the series of changes starting with "add a benchmark of Callers" (CL 472956) is: goos: linux goarch: amd64 pkg: runtime cpu: Intel(R) Xeon(R) CPU E5-2690 v3 @ 2.60GHz │ baseline │ unwinder │ │ sec/op │ sec/op vs base │ Callers/cached-48 1.464µ ± 1% 1.684µ ± 1% +15.03% (p=0.000 n=20) Callers/inlined-48 1.391µ ± 1% 1.536µ ± 1% +10.42% (p=0.000 n=20) Callers/no-cache-48 10.50µ ± 1% 11.11µ ± 0% +5.82% (p=0.000 n=20) StackCopyPtr-48 88.74m ± 1% 81.22m ± 2% -8.48% (p=0.000 n=20) StackCopy-48 80.90m ± 1% 70.56m ± 1% -12.78% (p=0.000 n=20) StackCopyNoCache-48 2.458m ± 1% 2.209m ± 1% -10.15% (p=0.000 n=20) StackCopyWithStkobj-48 26.81m ± 1% 25.66m ± 1% -4.28% (p=0.000 n=20) geomean 518.8µ 512.9µ -1.14% The performance impact of intermediate CLs in this sequence varies a lot as we went through many refactorings. The slowdown in Callers comes primarily from the introduction of unwinder because that doesn't get inlined and results in somewhat worse code generation in code that's extremely hot in those microbenchmarks. The performance gains on stack copying come mostly from replacing callbacks with direct use of the unwinder. Updates #54466. Fixes #32383. Change-Id: I4970603b2861633eecec30545e852688bc7cc9a4 Reviewed-on: https://go-review.googlesource.com/c/go/+/468301 Reviewed-by: Michael Pratt Run-TryBot: Austin Clements TryBot-Result: Gopher Robot --- diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index 93f6ee831e..2e98f895a3 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -1049,12 +1049,6 @@ type ancestorInfo struct { gopc uintptr // pc of go statement that created this goroutine } -const ( - _TraceRuntimeFrames = 1 << iota // include frames for internal runtime functions. - _TraceTrap // the initial PC, SP are from a trap, not a return PC from a call - _TraceJumpStack // if traceback is on a systemstack, resume trace at g that called into it -) - // The maximum number of frames we print for a traceback const _TracebackMaxFrames = 100 diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index b100a3c3b2..f0d61cd994 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -607,7 +607,7 @@ func tracebackPCs(u *unwinder, skip int, pcBuf []uintptr) int { f := u.frame.fn cgoN := u.cgoCallers(cgoBuf[:]) - // TODO: Why does &u.cache cause u to escape? + // TODO: Why does &u.cache cause u to escape? (Same in traceback2) for iu, uf := newInlineUnwinder(f, u.symPC(), noEscapePtr(&u.cache)); n < len(pcBuf) && uf.valid(); uf = iu.next(uf) { sf := iu.srcFunc(uf) if sf.funcID == funcID_wrapper && elideWrapperCalling(u.calleeFuncID) { @@ -632,119 +632,6 @@ func tracebackPCs(u *unwinder, skip int, pcBuf []uintptr) int { return n } -// Generic traceback. Handles runtime stack prints (pcbuf == nil). -// -// The skip argument is only valid with pcbuf != nil and counts the number -// of logical frames to skip rather than physical frames (with inlining, a -// PC in pcbuf can represent multiple calls). -func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max int, callback func(*stkframe, unsafe.Pointer) bool, v unsafe.Pointer, flags uint) int { - if pcbuf != nil { - throw("pcbuf argument no longer supported") - } - if callback != nil { - throw("callback argument no longer supported") - } - - // Translate flags - var uflags unwindFlags - printing := true - uflags |= unwindPrintErrors - if flags&_TraceTrap != 0 { - uflags |= unwindTrap - } - if flags&_TraceJumpStack != 0 { - uflags |= unwindJumpStack - } - - // Initialize stack unwinder - var u unwinder - u.initAt(pc0, sp0, lr0, gp, uflags) - - level, _, _ := gotraceback() - - nprint := 0 - n := 0 - var cgoBuf [32]uintptr - for ; n < max && u.valid(); u.next() { - frame := &u.frame - f := frame.fn - - cgoN := u.cgoCallers(cgoBuf[:]) - - if printing { - // assume skip=0 for printing. - // - // Never elide wrappers if we haven't printed - // any frames. And don't elide wrappers that - // called panic rather than the wrapped - // function. Otherwise, leave them out. - for iu, uf := newInlineUnwinder(f, u.symPC(), noEscapePtr(&u.cache)); uf.valid(); uf = iu.next(uf) { - sf := iu.srcFunc(uf) - if (flags&_TraceRuntimeFrames) != 0 || showframe(sf, gp, nprint == 0, u.calleeFuncID) { - name := sf.name() - file, line := iu.fileLine(uf) - if name == "runtime.gopanic" { - name = "panic" - } - // Print during crash. - // main(0x1, 0x2, 0x3) - // /home/rsc/go/src/runtime/x.go:23 +0xf - // - print(name, "(") - if iu.isInlined(uf) { - print("...") - } else { - argp := unsafe.Pointer(frame.argp) - printArgs(f, argp, u.symPC()) - } - print(")\n") - print("\t", file, ":", line) - if !iu.isInlined(uf) { - if frame.pc > f.entry() { - print(" +", hex(frame.pc-f.entry())) - } - if gp.m != nil && gp.m.throwing >= throwTypeRuntime && gp == gp.m.curg || level >= 2 { - print(" fp=", hex(frame.fp), " sp=", hex(frame.sp), " pc=", hex(frame.pc)) - } - } - print("\n") - nprint++ - } - } - // Print cgo frames. - if cgoN > 0 { - var arg cgoSymbolizerArg - anySymbolized := false - for _, pc := range cgoBuf[:cgoN] { - if n >= max { - break - } - if cgoSymbolizer == nil { - print("non-Go function at pc=", hex(pc), "\n") - } else { - c := printOneCgoTraceback(pc, max-n, &arg) - n += c - 1 // +1 a few lines down - anySymbolized = true - } - nprint++ - } - if anySymbolized { - // Free symbolization state. - arg.pc = 0 - callCgoSymbolizer(&arg) - } - } - } - n++ - } - - if printing { - n = nprint - } - - return n -} - // printArgs prints function arguments in traceback. func printArgs(f funcInfo, argp unsafe.Pointer, pc uintptr) { // The "instruction" of argument printing is encoded in _FUNCDATA_ArgInfo. @@ -891,10 +778,10 @@ func tracebacktrap(pc, sp, lr uintptr, gp *g) { traceback1(gp.m.libcallpc, gp.m.libcallsp, 0, gp.m.libcallg.ptr(), 0) return } - traceback1(pc, sp, lr, gp, _TraceTrap) + traceback1(pc, sp, lr, gp, unwindTrap) } -func traceback1(pc, sp, lr uintptr, gp *g, flags uint) { +func traceback1(pc, sp, lr uintptr, gp *g, flags unwindFlags) { // If the goroutine is in cgo, and we have a cgo traceback, print that. if iscgo && gp.m != nil && gp.m.ncgo > 0 && gp.syscallsp != 0 && gp.m.cgoCallers != nil && gp.m.cgoCallers[0] != 0 { // Lock cgoCallers so that a signal handler won't @@ -915,21 +802,25 @@ func traceback1(pc, sp, lr uintptr, gp *g, flags uint) { // Override registers if blocked in system call. pc = gp.syscallpc sp = gp.syscallsp - flags &^= _TraceTrap + flags &^= unwindTrap } if gp.m != nil && gp.m.vdsoSP != 0 { // Override registers if running in VDSO. This comes after the // _Gsyscall check to cover VDSO calls after entersyscall. pc = gp.m.vdsoPC sp = gp.m.vdsoSP - flags &^= _TraceTrap + flags &^= unwindTrap } // Print traceback. By default, omits runtime frames. // If that means we print nothing at all, repeat forcing all frames printed. - n := gentraceback(pc, sp, lr, gp, 0, nil, _TracebackMaxFrames, nil, nil, flags) - if n == 0 && (flags&_TraceRuntimeFrames) == 0 { - n = gentraceback(pc, sp, lr, gp, 0, nil, _TracebackMaxFrames, nil, nil, flags|_TraceRuntimeFrames) + flags |= unwindPrintErrors + var u unwinder + u.initAt(pc, sp, lr, gp, flags) + n := traceback2(&u, false) + if n == 0 { + u.initAt(pc, sp, lr, gp, flags) + n = traceback2(&u, true) } if n == _TracebackMaxFrames { print("...additional frames elided...\n") @@ -944,6 +835,77 @@ func traceback1(pc, sp, lr uintptr, gp *g, flags uint) { } } +func traceback2(u *unwinder, showRuntime bool) int { + gp := u.g.ptr() + level, _, _ := gotraceback() + n := 0 + const max = _TracebackMaxFrames + var cgoBuf [32]uintptr + for ; n < max && u.valid(); u.next() { + f := u.frame.fn + for iu, uf := newInlineUnwinder(f, u.symPC(), noEscapePtr(&u.cache)); n < max && uf.valid(); uf = iu.next(uf) { + sf := iu.srcFunc(uf) + if !(showRuntime || showframe(sf, gp, n == 0, u.calleeFuncID)) { + continue + } + + name := sf.name() + file, line := iu.fileLine(uf) + if name == "runtime.gopanic" { + name = "panic" + } + // Print during crash. + // main(0x1, 0x2, 0x3) + // /home/rsc/go/src/runtime/x.go:23 +0xf + // + print(name, "(") + if iu.isInlined(uf) { + print("...") + } else { + argp := unsafe.Pointer(u.frame.argp) + printArgs(f, argp, u.symPC()) + } + print(")\n") + print("\t", file, ":", line) + if !iu.isInlined(uf) { + if u.frame.pc > f.entry() { + print(" +", hex(u.frame.pc-f.entry())) + } + if gp.m != nil && gp.m.throwing >= throwTypeRuntime && gp == gp.m.curg || level >= 2 { + print(" fp=", hex(u.frame.fp), " sp=", hex(u.frame.sp), " pc=", hex(u.frame.pc)) + } + } + print("\n") + n++ + } + + // Print cgo frames. + if cgoN := u.cgoCallers(cgoBuf[:]); cgoN > 0 { + var arg cgoSymbolizerArg + anySymbolized := false + for _, pc := range cgoBuf[:cgoN] { + if n >= max { + break + } + if cgoSymbolizer == nil { + print("non-Go function at pc=", hex(pc), "\n") + } else { + c := printOneCgoTraceback(pc, max-n, &arg) + n += c - 1 // +1 a few lines down + anySymbolized = true + } + n++ + } + if anySymbolized { + // Free symbolization state. + arg.pc = 0 + callCgoSymbolizer(&arg) + } + } + } + return n +} + // printAncestorTraceback prints the traceback of the given ancestor. // TODO: Unify this with gentraceback and CallersFrames. func printAncestorTraceback(ancestor ancestorInfo) {