Commit 5d444e36 authored by Robert Griesemer's avatar Robert Griesemer

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/136397Reviewed-by: 's avatarAlan Donovan <adonovan@google.com>
parent f99fc3a1
// 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
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment