Source file src/cmd/compile/internal/ir/dump.go

     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  	"crypto/sha256"
    13  	"encoding/hex"
    14  	"fmt"
    15  	"io"
    16  	"net/url"
    17  	"os"
    18  	"reflect"
    19  	"regexp"
    20  	"strings"
    21  	"sync"
    22  
    23  	"cmd/compile/internal/base"
    24  	"cmd/compile/internal/types"
    25  	"cmd/internal/src"
    26  )
    27  
    28  // DumpAny is like FDumpAny but prints to stderr.
    29  func DumpAny(root any, filter string, depth int) {
    30  	FDumpAny(os.Stderr, root, filter, depth)
    31  }
    32  
    33  // FDumpAny prints the structure of a rooted data structure
    34  // to w by depth-first traversal of the data structure.
    35  //
    36  // The filter parameter is a regular expression. If it is
    37  // non-empty, only struct fields whose names match filter
    38  // are printed.
    39  //
    40  // The depth parameter controls how deep traversal recurses
    41  // before it returns (higher value means greater depth).
    42  // If an empty field filter is given, a good depth default value
    43  // is 4. A negative depth means no depth limit, which may be fine
    44  // for small data structures or if there is a non-empty filter.
    45  //
    46  // In the output, Node structs are identified by their Op name
    47  // rather than their type; struct fields with zero values or
    48  // non-matching field names are omitted, and "…" means recursion
    49  // depth has been reached or struct fields have been omitted.
    50  func FDumpAny(w io.Writer, root any, filter string, depth int) {
    51  	if root == nil {
    52  		fmt.Fprintln(w, "nil")
    53  		return
    54  	}
    55  
    56  	if filter == "" {
    57  		filter = ".*" // default
    58  	}
    59  
    60  	p := dumper{
    61  		output:  w,
    62  		fieldrx: regexp.MustCompile(filter),
    63  		ptrmap:  make(map[uintptr]int),
    64  		last:    '\n', // force printing of line number on first line
    65  	}
    66  
    67  	p.dump(reflect.ValueOf(root), depth)
    68  	p.printf("\n")
    69  }
    70  
    71  // MatchAstDump returns true if the fn matches the value
    72  // of the astdump debug flag.  Fn matches in the following
    73  // cases:
    74  //
    75  //   - astdump == name(fn)
    76  //   - astdump == pkgname(fn).name(fn)
    77  //   - astdump == afterslash(pkgname(fn)).name(fn)
    78  //   - astdump begins with a "~" and what follows "~" is a
    79  //     regular expression matching pkgname(fn).name(fn)
    80  //
    81  // If MatchAstDump returns true, it also prints to os.Stderr
    82  //
    83  //	\nir.Match(<fn>, <astdump>) for <where>\n
    84  func MatchAstDump(fn *Func, where string) bool {
    85  	if len(base.Debug.AstDump) == 0 {
    86  		return false
    87  	}
    88  	return matchForDump(fn, base.Ctxt.Pkgpath, where)
    89  }
    90  
    91  var dbgRE *regexp.Regexp
    92  var onceDbgRE sync.Once
    93  
    94  func matchForDump(fn *Func, pkgPath, where string) bool {
    95  	dbg := false
    96  	flag := base.Debug.AstDump
    97  	if flag[0] == '~' {
    98  		onceDbgRE.Do(func() { dbgRE = regexp.MustCompile(flag[1:]) })
    99  		dbg = dbgRE.MatchString(pkgPath + "." + FuncName(fn))
   100  	} else {
   101  		dbg = matchPkgFn(pkgPath, FuncName(fn), flag)
   102  	}
   103  	return dbg
   104  }
   105  
   106  // matchPkgFn returns true if pkg and fnName "match" toMatch.
   107  // "aFunc" matches "aFunc" (in any package)
   108  // "aPkg.aFunc" matches "aPkg.aFunc"
   109  // "aPkg/subPkg.aFunc" matches "subPkg.aFunc"
   110  func matchPkgFn(pkgName, fnName, toMatch string) bool {
   111  	if fnName == toMatch {
   112  		return true
   113  	}
   114  	matchPkgDotName := func(pkg string) bool {
   115  		// Allocation-free equality check for toMatch == base.Ctxt.Pkgpath + "." + fnName
   116  		return len(toMatch) == len(pkg)+1+len(fnName) &&
   117  			strings.HasPrefix(toMatch, pkg) && toMatch[len(pkg)] == '.' && strings.HasSuffix(toMatch, fnName)
   118  	}
   119  	if matchPkgDotName(pkgName) {
   120  		return true
   121  	}
   122  	if l := strings.LastIndexByte(pkgName, '/'); l > 0 && matchPkgDotName(pkgName[l+1:]) {
   123  		return true
   124  	}
   125  
   126  	return false
   127  }
   128  
   129  // AstDump appends the ast dump for fn to the ast dump file for fn.
   130  // The generated file name is
   131  //
   132  //	url.PathEscape(PkgFuncName(fn)) + ".ast"
   133  //
   134  // It also prints
   135  //
   136  //	Writing ast output to <astfilename>\n
   137  //
   138  // to os.Stderr.
   139  func AstDump(fn *Func, why string) {
   140  	err := withLockAndFile(
   141  		fn,
   142  		func(w io.Writer) {
   143  			FDump(w, why, fn)
   144  		},
   145  	)
   146  	// strip text following comma, for phase names.
   147  	comma := strings.Index(why, ",")
   148  	if comma > 0 {
   149  		why = why[:comma]
   150  	}
   151  	DumpNodeHTML(fn, why, fn)
   152  	if err != nil {
   153  		fmt.Fprintf(os.Stderr, "Dump returned error %v\n", err)
   154  	}
   155  }
   156  
   157  var mu sync.Mutex
   158  var astDumpFiles = make(map[string]bool)
   159  
   160  // escapedFileName constructs a file name from fn and suffix,
   161  // url-path-escaping the function part of the name and replacing it
   162  // with a hash if it is too long.  The suffix is neither escaped
   163  // nor including in the length calculation, so an excessively
   164  // creative suffix will result in problems.
   165  func escapedFileName(fn *Func, suffix string) string {
   166  	name := url.PathEscape(PkgFuncName(fn))
   167  	if len(name) > 125 { // arbitrary limit on file names, as if anyone types these in by hand
   168  		hash := sha256.Sum256([]byte(name))
   169  		name = hex.EncodeToString(hash[:8])
   170  	}
   171  	return name + suffix
   172  }
   173  
   174  // withLockAndFile manages ast dump files for various function names
   175  // and invokes a dumping function to write output, under a lock.
   176  func withLockAndFile(fn *Func, dump func(io.Writer)) (err error) {
   177  	name := escapedFileName(fn, ".ast")
   178  
   179  	// Ensure that debugging output is not scrambled and is written promptly
   180  	mu.Lock()
   181  	defer mu.Unlock()
   182  	mode := os.O_APPEND | os.O_RDWR
   183  	if !astDumpFiles[name] {
   184  		astDumpFiles[name] = true
   185  		mode = os.O_CREATE | os.O_TRUNC | os.O_RDWR
   186  		fmt.Fprintf(os.Stderr, "Writing text ast output for %s to %s\n", PkgFuncName(fn), name)
   187  	}
   188  
   189  	fi, err := os.OpenFile(name, mode, 0777)
   190  	if err != nil {
   191  		return err
   192  	}
   193  	defer func() { err = fi.Close() }()
   194  	dump(fi)
   195  	return
   196  }
   197  
   198  var htmlWriters = make(map[*Func]*HTMLWriter)
   199  var orderedFuncs = []*Func{}
   200  
   201  // DumpNodeHTML dumps the node n to the HTML writer for fn.
   202  // It uses the same phase name as the text dump.
   203  func DumpNodeHTML(fn *Func, why string, n Node) {
   204  	mu.Lock()
   205  	defer mu.Unlock()
   206  	w, ok := htmlWriters[fn]
   207  	if !ok {
   208  		name := escapedFileName(fn, ".html")
   209  		w = NewHTMLWriter(name, fn, "")
   210  		htmlWriters[fn] = w
   211  		orderedFuncs = append(orderedFuncs, fn)
   212  	}
   213  	w.WritePhase(why, why)
   214  }
   215  
   216  // CloseHTMLWriters closes the HTML writer for fn, if one exists.
   217  func CloseHTMLWriters() {
   218  	mu.Lock()
   219  	defer mu.Unlock()
   220  	for _, fn := range orderedFuncs {
   221  		if w, ok := htmlWriters[fn]; ok {
   222  			w.Close()
   223  			delete(htmlWriters, fn)
   224  		}
   225  	}
   226  	orderedFuncs = nil
   227  }
   228  
   229  type dumper struct {
   230  	output  io.Writer
   231  	fieldrx *regexp.Regexp  // field name filter
   232  	ptrmap  map[uintptr]int // ptr -> dump line number
   233  	lastadr string          // last address string printed (for shortening)
   234  
   235  	// output
   236  	indent int  // current indentation level
   237  	last   byte // last byte processed by Write
   238  	line   int  // current line number
   239  }
   240  
   241  var indentBytes = []byte(".  ")
   242  
   243  func (p *dumper) Write(data []byte) (n int, err error) {
   244  	var m int
   245  	for i, b := range data {
   246  		// invariant: data[0:n] has been written
   247  		if b == '\n' {
   248  			m, err = p.output.Write(data[n : i+1])
   249  			n += m
   250  			if err != nil {
   251  				return
   252  			}
   253  		} else if p.last == '\n' {
   254  			p.line++
   255  			_, err = fmt.Fprintf(p.output, "%6d  ", p.line)
   256  			if err != nil {
   257  				return
   258  			}
   259  			for j := p.indent; j > 0; j-- {
   260  				_, err = p.output.Write(indentBytes)
   261  				if err != nil {
   262  					return
   263  				}
   264  			}
   265  		}
   266  		p.last = b
   267  	}
   268  	if len(data) > n {
   269  		m, err = p.output.Write(data[n:])
   270  		n += m
   271  	}
   272  	return
   273  }
   274  
   275  // printf is a convenience wrapper.
   276  func (p *dumper) printf(format string, args ...any) {
   277  	if _, err := fmt.Fprintf(p, format, args...); err != nil {
   278  		panic(err)
   279  	}
   280  }
   281  
   282  // addr returns the (hexadecimal) address string of the object
   283  // represented by x (or "?" if x is not addressable), with the
   284  // common prefix between this and the prior address replaced by
   285  // "0x…" to make it easier to visually match addresses.
   286  func (p *dumper) addr(x reflect.Value) string {
   287  	if !x.CanAddr() {
   288  		return "?"
   289  	}
   290  	adr := fmt.Sprintf("%p", x.Addr().Interface())
   291  	s := adr
   292  	if i := commonPrefixLen(p.lastadr, adr); i > 0 {
   293  		s = "0x…" + adr[i:]
   294  	}
   295  	p.lastadr = adr
   296  	return s
   297  }
   298  
   299  // dump prints the contents of x.
   300  func (p *dumper) dump(x reflect.Value, depth int) {
   301  	if depth == 0 {
   302  		p.printf("…")
   303  		return
   304  	}
   305  
   306  	if pos, ok := x.Interface().(src.XPos); ok {
   307  		p.printf("%s", base.FmtPos(pos))
   308  		return
   309  	}
   310  
   311  	switch x.Kind() {
   312  	case reflect.String:
   313  		p.printf("%q", x.Interface()) // print strings in quotes
   314  
   315  	case reflect.Interface:
   316  		if x.IsNil() {
   317  			p.printf("nil")
   318  			return
   319  		}
   320  		p.dump(x.Elem(), depth-1)
   321  
   322  	case reflect.Ptr:
   323  		if x.IsNil() {
   324  			p.printf("nil")
   325  			return
   326  		}
   327  
   328  		p.printf("*")
   329  		ptr := x.Pointer()
   330  		if line, exists := p.ptrmap[ptr]; exists {
   331  			p.printf("(@%d)", line)
   332  			return
   333  		}
   334  		p.ptrmap[ptr] = p.line
   335  		p.dump(x.Elem(), depth) // don't count pointer indirection towards depth
   336  
   337  	case reflect.Slice:
   338  		if x.IsNil() {
   339  			p.printf("nil")
   340  			return
   341  		}
   342  		p.printf("%s (%d entries) {", x.Type(), x.Len())
   343  		if x.Len() > 0 {
   344  			p.indent++
   345  			p.printf("\n")
   346  			for i, n := 0, x.Len(); i < n; i++ {
   347  				p.printf("%d: ", i)
   348  				p.dump(x.Index(i), depth-1)
   349  				p.printf("\n")
   350  			}
   351  			p.indent--
   352  		}
   353  		p.printf("}")
   354  
   355  	case reflect.Struct:
   356  		typ := x.Type()
   357  
   358  		isNode := false
   359  		if n, ok := x.Interface().(Node); ok {
   360  			isNode = true
   361  			p.printf("%s %s {", n.Op().String(), p.addr(x))
   362  		} else {
   363  			p.printf("%s {", typ)
   364  		}
   365  		p.indent++
   366  
   367  		first := true
   368  		omitted := false
   369  		for i, n := 0, typ.NumField(); i < n; i++ {
   370  			// Exclude non-exported fields because their
   371  			// values cannot be accessed via reflection.
   372  			if name := typ.Field(i).Name; types.IsExported(name) {
   373  				if !p.fieldrx.MatchString(name) {
   374  					omitted = true
   375  					continue // field name not selected by filter
   376  				}
   377  
   378  				// special cases
   379  				if isNode && name == "Op" {
   380  					omitted = true
   381  					continue // Op field already printed for Nodes
   382  				}
   383  				x := x.Field(i)
   384  				if x.IsZero() {
   385  					omitted = true
   386  					continue // exclude zero-valued fields
   387  				}
   388  				if n, ok := x.Interface().(Nodes); ok && len(n) == 0 {
   389  					omitted = true
   390  					continue // exclude empty Nodes slices
   391  				}
   392  
   393  				if first {
   394  					p.printf("\n")
   395  					first = false
   396  				}
   397  				p.printf("%s: ", name)
   398  				p.dump(x, depth-1)
   399  				p.printf("\n")
   400  			}
   401  		}
   402  		if omitted {
   403  			p.printf("…\n")
   404  		}
   405  
   406  		p.indent--
   407  		p.printf("}")
   408  
   409  	default:
   410  		p.printf("%v", x.Interface())
   411  	}
   412  }
   413  
   414  func commonPrefixLen(a, b string) (i int) {
   415  	for i < len(a) && i < len(b) && a[i] == b[i] {
   416  		i++
   417  	}
   418  	return
   419  }
   420  

View as plain text