]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: delete gentraceback
authorAustin Clements <austin@google.com>
Tue, 14 Feb 2023 18:54:29 +0000 (13:54 -0500)
committerAustin Clements <austin@google.com>
Fri, 10 Mar 2023 17:59:39 +0000 (17:59 +0000)
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 <mpratt@google.com>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/runtime/runtime2.go
src/runtime/traceback.go

index 93f6ee831e1cb2b7e64f1d452c52016fa4e9ba98..2e98f895a37b40e353f4ef3a18a0f617818dafa2 100644 (file)
@@ -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
 
index b100a3c3b210fabd9be39886541320c94641c074..f0d61cd994d7e3a51e8ba1f4a8abc27ac74bbe51 100644 (file)
@@ -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) {