]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile/internal/gc: add alternative node dumper for debugging
authorRobert Griesemer <gri@golang.org>
Wed, 19 Sep 2018 23:20:51 +0000 (16:20 -0700)
committerRobert Griesemer <gri@golang.org>
Mon, 1 Oct 2018 21:53:00 +0000 (21:53 +0000)
dump/fdump is a reflection-based data structure dumper slightly
customized for the compiler's Node data structure. It dumps the
transitivle closure of Node (and other) data structures using a
recursive descent depth first traversal and permits filtering
options (recursion depth limitation, filtering of struct fields).

I have been using it to diagnose compiler bugs and found it more
useful than the existing node printing code in some cases because
field filtering reduces the output to the interesting parts.

No impact on rest of compiler if functions are not called (which
they only should during a debugging session).

Change-Id: I79d7227f10dd78dbd4bbcdf204db236102fc97a7
Reviewed-on: https://go-review.googlesource.com/136397
Reviewed-by: Alan Donovan <adonovan@google.com>
src/cmd/compile/internal/gc/dump.go [new file with mode: 0644]

diff --git a/src/cmd/compile/internal/gc/dump.go b/src/cmd/compile/internal/gc/dump.go
new file mode 100644 (file)
index 0000000..8de90ad
--- /dev/null
@@ -0,0 +1,287 @@
+// Copyright 2018 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.
+
+// This file implements textual dumping of arbitrary data structures
+// for debugging purposes. The code is customized for Node graphs
+// and may be used for an alternative view of the node structure.
+
+package gc
+
+import (
+       "cmd/compile/internal/types"
+       "cmd/internal/src"
+       "fmt"
+       "io"
+       "os"
+       "reflect"
+       "regexp"
+       "unicode"
+       "unicode/utf8"
+)
+
+// dump is like fdump but prints to stderr.
+func dump(root interface{}, filter string, depth int) {
+       fdump(os.Stderr, root, filter, depth)
+}
+
+// fdump prints the structure of a rooted data structure
+// to w by depth-first traversal of the data structure.
+//
+// The filter parameter is a regular expression. If it is
+// non-empty, only struct fields whose names match filter
+// are printed.
+//
+// The depth parameter controls how deep traversal recurses
+// before it returns (higher value means greater depth).
+// If an empty field filter is given, a good depth default value
+// is 4. A negative depth means no depth limit, which may be fine
+// for small data structures or if there is a non-empty filter.
+//
+// In the output, Node structs are identified by their Op name
+// rather than their type; struct fields with zero values or
+// non-matching field names are omitted, and "…" means recursion
+// depth has been reached or struct fields have been omitted.
+func fdump(w io.Writer, root interface{}, filter string, depth int) {
+       if root == nil {
+               fmt.Fprintln(w, "nil")
+               return
+       }
+
+       if filter == "" {
+               filter = ".*" // default
+       }
+
+       p := dumper{
+               output:  w,
+               fieldrx: regexp.MustCompile(filter),
+               ptrmap:  make(map[uintptr]int),
+               last:    '\n', // force printing of line number on first line
+       }
+
+       p.dump(reflect.ValueOf(root), depth)
+       p.printf("\n")
+}
+
+type dumper struct {
+       output  io.Writer
+       fieldrx *regexp.Regexp  // field name filter
+       ptrmap  map[uintptr]int // ptr -> dump line number
+       lastadr string          // last address string printed (for shortening)
+
+       // output
+       indent int  // current indentation level
+       last   byte // last byte processed by Write
+       line   int  // current line number
+}
+
+var indentBytes = []byte(".  ")
+
+func (p *dumper) Write(data []byte) (n int, err error) {
+       var m int
+       for i, b := range data {
+               // invariant: data[0:n] has been written
+               if b == '\n' {
+                       m, err = p.output.Write(data[n : i+1])
+                       n += m
+                       if err != nil {
+                               return
+                       }
+               } else if p.last == '\n' {
+                       p.line++
+                       _, err = fmt.Fprintf(p.output, "%6d  ", p.line)
+                       if err != nil {
+                               return
+                       }
+                       for j := p.indent; j > 0; j-- {
+                               _, err = p.output.Write(indentBytes)
+                               if err != nil {
+                                       return
+                               }
+                       }
+               }
+               p.last = b
+       }
+       if len(data) > n {
+               m, err = p.output.Write(data[n:])
+               n += m
+       }
+       return
+}
+
+// printf is a convenience wrapper.
+func (p *dumper) printf(format string, args ...interface{}) {
+       if _, err := fmt.Fprintf(p, format, args...); err != nil {
+               panic(err)
+       }
+}
+
+// addr returns the (hexadecimal) address string of the object
+// represented by x (or "?" if x is not addressable), with the
+// common prefix between this and the prior address replaced by
+// "0x…" to make it easier to visually match addresses.
+func (p *dumper) addr(x reflect.Value) string {
+       if !x.CanAddr() {
+               return "?"
+       }
+       adr := fmt.Sprintf("%p", x.Addr().Interface())
+       s := adr
+       if i := commonPrefixLen(p.lastadr, adr); i > 0 {
+               s = "0x…" + adr[i:]
+       }
+       p.lastadr = adr
+       return s
+}
+
+// dump prints the contents of x.
+func (p *dumper) dump(x reflect.Value, depth int) {
+       if depth == 0 {
+               p.printf("…")
+               return
+       }
+
+       // special cases
+       switch v := x.Interface().(type) {
+       case Nodes:
+               // unpack Nodes since reflect cannot look inside
+               // due to the unexported field in its struct
+               x = reflect.ValueOf(v.Slice())
+
+       case src.XPos:
+               p.printf("%s", linestr(v))
+               return
+
+       case *types.Node:
+               x = reflect.ValueOf(asNode(v))
+       }
+
+       switch x.Kind() {
+       case reflect.String:
+               p.printf("%q", x.Interface()) // print strings in quotes
+
+       case reflect.Interface:
+               if x.IsNil() {
+                       p.printf("nil")
+                       return
+               }
+               p.dump(x.Elem(), depth-1)
+
+       case reflect.Ptr:
+               if x.IsNil() {
+                       p.printf("nil")
+                       return
+               }
+
+               p.printf("*")
+               ptr := x.Pointer()
+               if line, exists := p.ptrmap[ptr]; exists {
+                       p.printf("(@%d)", line)
+                       return
+               }
+               p.ptrmap[ptr] = p.line
+               p.dump(x.Elem(), depth) // don't count pointer indirection towards depth
+
+       case reflect.Slice:
+               if x.IsNil() {
+                       p.printf("nil")
+                       return
+               }
+               p.printf("%s (%d entries) {", x.Type(), x.Len())
+               if x.Len() > 0 {
+                       p.indent++
+                       p.printf("\n")
+                       for i, n := 0, x.Len(); i < n; i++ {
+                               p.printf("%d: ", i)
+                               p.dump(x.Index(i), depth-1)
+                               p.printf("\n")
+                       }
+                       p.indent--
+               }
+               p.printf("}")
+
+       case reflect.Struct:
+               typ := x.Type()
+
+               isNode := false
+               if n, ok := x.Interface().(Node); ok {
+                       isNode = true
+                       p.printf("%s %s {", n.Op.String(), p.addr(x))
+               } else {
+                       p.printf("%s {", typ)
+               }
+               p.indent++
+
+               first := true
+               omitted := false
+               for i, n := 0, typ.NumField(); i < n; i++ {
+                       // Exclude non-exported fields because their
+                       // values cannot be accessed via reflection.
+                       if name := typ.Field(i).Name; isExported(name) {
+                               if !p.fieldrx.MatchString(name) {
+                                       omitted = true
+                                       continue // field name not selected by filter
+                               }
+
+                               // special cases
+                               if isNode && name == "Op" {
+                                       omitted = true
+                                       continue // Op field already printed for Nodes
+                               }
+                               x := x.Field(i)
+                               if isZeroVal(x) {
+                                       omitted = true
+                                       continue // exclude zero-valued fields
+                               }
+                               if n, ok := x.Interface().(Nodes); ok && n.Len() == 0 {
+                                       omitted = true
+                                       continue // exclude empty Nodes slices
+                               }
+
+                               if first {
+                                       p.printf("\n")
+                                       first = false
+                               }
+                               p.printf("%s: ", name)
+                               p.dump(x, depth-1)
+                               p.printf("\n")
+                       }
+               }
+               if omitted {
+                       p.printf("…\n")
+               }
+
+               p.indent--
+               p.printf("}")
+
+       default:
+               p.printf("%v", x.Interface())
+       }
+}
+
+func isZeroVal(x reflect.Value) bool {
+       switch x.Kind() {
+       case reflect.Bool:
+               return !x.Bool()
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               return x.Int() == 0
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+               return x.Uint() == 0
+       case reflect.String:
+               return x.String() == ""
+       case reflect.Interface, reflect.Ptr, reflect.Slice:
+               return x.IsNil()
+       }
+       return false
+}
+
+func isExported(name string) bool {
+       ch, _ := utf8.DecodeRuneInString(name)
+       return unicode.IsUpper(ch)
+}
+
+func commonPrefixLen(a, b string) (i int) {
+       for i < len(a) && i < len(b) && a[i] == b[i] {
+               i++
+       }
+       return
+}