From: Robert Griesemer Date: Wed, 19 Sep 2018 23:20:51 +0000 (-0700) Subject: cmd/compile/internal/gc: add alternative node dumper for debugging X-Git-Tag: go1.12beta1~961 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=5d444e36a664e7f0c4b6e4e53bb76f964028bd22;p=gostls13.git cmd/compile/internal/gc: add alternative node dumper for debugging 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 --- diff --git a/src/cmd/compile/internal/gc/dump.go b/src/cmd/compile/internal/gc/dump.go new file mode 100644 index 0000000000..8de90adf05 --- /dev/null +++ b/src/cmd/compile/internal/gc/dump.go @@ -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 +}