github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/ir/dump.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // This file implements textual dumping of arbitrary data structures
     6  // for debugging purposes. The code is customized for Node graphs
     7  // and may be used for an alternative view of the node structure.
     8  
     9  package ir
    10  
    11  import (
    12  	"fmt"
    13  	"io"
    14  	"os"
    15  	"reflect"
    16  	"regexp"
    17  
    18  	"github.com/go-asm/go/cmd/compile/base"
    19  	"github.com/go-asm/go/cmd/compile/types"
    20  	"github.com/go-asm/go/cmd/src"
    21  )
    22  
    23  // DumpAny is like FDumpAny but prints to stderr.
    24  func DumpAny(root interface{}, filter string, depth int) {
    25  	FDumpAny(os.Stderr, root, filter, depth)
    26  }
    27  
    28  // FDumpAny prints the structure of a rooted data structure
    29  // to w by depth-first traversal of the data structure.
    30  //
    31  // The filter parameter is a regular expression. If it is
    32  // non-empty, only struct fields whose names match filter
    33  // are printed.
    34  //
    35  // The depth parameter controls how deep traversal recurses
    36  // before it returns (higher value means greater depth).
    37  // If an empty field filter is given, a good depth default value
    38  // is 4. A negative depth means no depth limit, which may be fine
    39  // for small data structures or if there is a non-empty filter.
    40  //
    41  // In the output, Node structs are identified by their Op name
    42  // rather than their type; struct fields with zero values or
    43  // non-matching field names are omitted, and "…" means recursion
    44  // depth has been reached or struct fields have been omitted.
    45  func FDumpAny(w io.Writer, root interface{}, filter string, depth int) {
    46  	if root == nil {
    47  		fmt.Fprintln(w, "nil")
    48  		return
    49  	}
    50  
    51  	if filter == "" {
    52  		filter = ".*" // default
    53  	}
    54  
    55  	p := dumper{
    56  		output:  w,
    57  		fieldrx: regexp.MustCompile(filter),
    58  		ptrmap:  make(map[uintptr]int),
    59  		last:    '\n', // force printing of line number on first line
    60  	}
    61  
    62  	p.dump(reflect.ValueOf(root), depth)
    63  	p.printf("\n")
    64  }
    65  
    66  type dumper struct {
    67  	output  io.Writer
    68  	fieldrx *regexp.Regexp  // field name filter
    69  	ptrmap  map[uintptr]int // ptr -> dump line number
    70  	lastadr string          // last address string printed (for shortening)
    71  
    72  	// output
    73  	indent int  // current indentation level
    74  	last   byte // last byte processed by Write
    75  	line   int  // current line number
    76  }
    77  
    78  var indentBytes = []byte(".  ")
    79  
    80  func (p *dumper) Write(data []byte) (n int, err error) {
    81  	var m int
    82  	for i, b := range data {
    83  		// invariant: data[0:n] has been written
    84  		if b == '\n' {
    85  			m, err = p.output.Write(data[n : i+1])
    86  			n += m
    87  			if err != nil {
    88  				return
    89  			}
    90  		} else if p.last == '\n' {
    91  			p.line++
    92  			_, err = fmt.Fprintf(p.output, "%6d  ", p.line)
    93  			if err != nil {
    94  				return
    95  			}
    96  			for j := p.indent; j > 0; j-- {
    97  				_, err = p.output.Write(indentBytes)
    98  				if err != nil {
    99  					return
   100  				}
   101  			}
   102  		}
   103  		p.last = b
   104  	}
   105  	if len(data) > n {
   106  		m, err = p.output.Write(data[n:])
   107  		n += m
   108  	}
   109  	return
   110  }
   111  
   112  // printf is a convenience wrapper.
   113  func (p *dumper) printf(format string, args ...interface{}) {
   114  	if _, err := fmt.Fprintf(p, format, args...); err != nil {
   115  		panic(err)
   116  	}
   117  }
   118  
   119  // addr returns the (hexadecimal) address string of the object
   120  // represented by x (or "?" if x is not addressable), with the
   121  // common prefix between this and the prior address replaced by
   122  // "0x…" to make it easier to visually match addresses.
   123  func (p *dumper) addr(x reflect.Value) string {
   124  	if !x.CanAddr() {
   125  		return "?"
   126  	}
   127  	adr := fmt.Sprintf("%p", x.Addr().Interface())
   128  	s := adr
   129  	if i := commonPrefixLen(p.lastadr, adr); i > 0 {
   130  		s = "0x…" + adr[i:]
   131  	}
   132  	p.lastadr = adr
   133  	return s
   134  }
   135  
   136  // dump prints the contents of x.
   137  func (p *dumper) dump(x reflect.Value, depth int) {
   138  	if depth == 0 {
   139  		p.printf("…")
   140  		return
   141  	}
   142  
   143  	if pos, ok := x.Interface().(src.XPos); ok {
   144  		p.printf("%s", base.FmtPos(pos))
   145  		return
   146  	}
   147  
   148  	switch x.Kind() {
   149  	case reflect.String:
   150  		p.printf("%q", x.Interface()) // print strings in quotes
   151  
   152  	case reflect.Interface:
   153  		if x.IsNil() {
   154  			p.printf("nil")
   155  			return
   156  		}
   157  		p.dump(x.Elem(), depth-1)
   158  
   159  	case reflect.Ptr:
   160  		if x.IsNil() {
   161  			p.printf("nil")
   162  			return
   163  		}
   164  
   165  		p.printf("*")
   166  		ptr := x.Pointer()
   167  		if line, exists := p.ptrmap[ptr]; exists {
   168  			p.printf("(@%d)", line)
   169  			return
   170  		}
   171  		p.ptrmap[ptr] = p.line
   172  		p.dump(x.Elem(), depth) // don't count pointer indirection towards depth
   173  
   174  	case reflect.Slice:
   175  		if x.IsNil() {
   176  			p.printf("nil")
   177  			return
   178  		}
   179  		p.printf("%s (%d entries) {", x.Type(), x.Len())
   180  		if x.Len() > 0 {
   181  			p.indent++
   182  			p.printf("\n")
   183  			for i, n := 0, x.Len(); i < n; i++ {
   184  				p.printf("%d: ", i)
   185  				p.dump(x.Index(i), depth-1)
   186  				p.printf("\n")
   187  			}
   188  			p.indent--
   189  		}
   190  		p.printf("}")
   191  
   192  	case reflect.Struct:
   193  		typ := x.Type()
   194  
   195  		isNode := false
   196  		if n, ok := x.Interface().(Node); ok {
   197  			isNode = true
   198  			p.printf("%s %s {", n.Op().String(), p.addr(x))
   199  		} else {
   200  			p.printf("%s {", typ)
   201  		}
   202  		p.indent++
   203  
   204  		first := true
   205  		omitted := false
   206  		for i, n := 0, typ.NumField(); i < n; i++ {
   207  			// Exclude non-exported fields because their
   208  			// values cannot be accessed via reflection.
   209  			if name := typ.Field(i).Name; types.IsExported(name) {
   210  				if !p.fieldrx.MatchString(name) {
   211  					omitted = true
   212  					continue // field name not selected by filter
   213  				}
   214  
   215  				// special cases
   216  				if isNode && name == "Op" {
   217  					omitted = true
   218  					continue // Op field already printed for Nodes
   219  				}
   220  				x := x.Field(i)
   221  				if x.IsZero() {
   222  					omitted = true
   223  					continue // exclude zero-valued fields
   224  				}
   225  				if n, ok := x.Interface().(Nodes); ok && len(n) == 0 {
   226  					omitted = true
   227  					continue // exclude empty Nodes slices
   228  				}
   229  
   230  				if first {
   231  					p.printf("\n")
   232  					first = false
   233  				}
   234  				p.printf("%s: ", name)
   235  				p.dump(x, depth-1)
   236  				p.printf("\n")
   237  			}
   238  		}
   239  		if omitted {
   240  			p.printf("…\n")
   241  		}
   242  
   243  		p.indent--
   244  		p.printf("}")
   245  
   246  	default:
   247  		p.printf("%v", x.Interface())
   248  	}
   249  }
   250  
   251  func commonPrefixLen(a, b string) (i int) {
   252  	for i < len(a) && i < len(b) && a[i] == b[i] {
   253  		i++
   254  	}
   255  	return
   256  }