From 4090af83c57c857de600ada68e7a27dffd37d8b1 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 6 Dec 2020 15:17:05 -0500 Subject: [PATCH] [dev.regabi] cmd/compile: use reflection in ir.Dump ir.Dump is the final (I think!) piece of the compiler that was walking nodes using Left, Right etc without knowing what they meant. This CL uses reflection to walk nodes without knowing what they mean instead. One benefit is that we can print actual meanings (field names). While we are here, I could not resist fixing a long-standing mental TODO: make the line number more clearly a line number. I've forgotten where the line number is in the dumps far too many times in the last decade. As a small example, here is a fragment of go tool compile -W test/235.go: . FOR l(28) tc(1) . . LT-init . . . AS l(28) tc(1) . . . . NAME-main..autotmp_4 l(28) x(0) class(PAUTO) esc(N) tc(1) assigned used int . . . . LEN l(28) tc(1) int . . . . . NAME-main.xs g(2) l(26) x(0) class(PPARAM) esc(no) tc(1) used SLICE-[]uint64 . . LT l(28) tc(1) hascall bool . . . NAME-main.i g(4) l(28) x(0) class(PAUTO) esc(no) tc(1) assigned used int . . . NAME-main..autotmp_4 l(28) x(0) class(PAUTO) esc(N) tc(1) assigned used int . . BLOCK l(28) . . BLOCK-list . . . ASOP-ADD l(28) tc(1) implicit(true) int . . . . NAME-main.i g(4) l(28) x(0) class(PAUTO) esc(no) tc(1) assigned used int . . . . LITERAL-1 l(28) tc(1) int . FOR-body . . VARKILL l(28) tc(1) . . . NAME-main..autotmp_4 l(28) x(0) class(PAUTO) esc(N) tc(1) assigned used int . . IF l(29) tc(1) . . . LT l(29) tc(1) bool . . . . INDEX l(29) tc(1) uint64 . . . . . NAME-main.xs g(2) l(26) x(0) class(PPARAM) esc(no) tc(1) used SLICE-[]uint64 . . . . . NAME-main.i g(4) l(28) x(0) class(PAUTO) esc(no) tc(1) assigned used int . . . . NAME-main.m g(3) l(27) x(0) class(PAUTO) esc(no) tc(1) assigned used uint64 . . IF-body . . . AS l(30) tc(1) . . . . NAME-main.m g(3) l(27) x(0) class(PAUTO) esc(no) tc(1) assigned used uint64 . . . . INDEX l(30) tc(1) uint64 . . . . . NAME-main.xs g(2) l(26) x(0) class(PPARAM) esc(no) tc(1) used SLICE-[]uint64 . . . . . NAME-main.i g(4) l(28) x(0) class(PAUTO) esc(no) tc(1) assigned used int and here it is after this CL: . FOR tc(1) # 235.go:28 . FOR-Cond . . LT-init . . . AS tc(1) # 235.go:28 . . . . NAME-main..autotmp_4 x(0) class(PAUTO) esc(N) tc(1) assigned used int # 235.go:28 . . . . LEN tc(1) int # 235.go:28 int . . . . . NAME-main.xs g(2) x(0) class(PPARAM) esc(no) tc(1) used SLICE-[]uint64 # 235.go:26 . . LT tc(1) hascall bool # 235.go:28 bool . . . NAME-main.i g(4) x(0) class(PAUTO) esc(no) tc(1) assigned used int # 235.go:28 . . . NAME-main..autotmp_4 x(0) class(PAUTO) esc(N) tc(1) assigned used int # 235.go:28 . FOR-Post . . BLOCK # 235.go:28 . . BLOCK-List . . . ASOP-ADD tc(1) implicit(true) int # 235.go:28 int . . . . NAME-main.i g(4) x(0) class(PAUTO) esc(no) tc(1) assigned used int # 235.go:28 . . . . LITERAL-1 tc(1) int # 235.go:28 . FOR-Body . . VARKILL tc(1) # 235.go:28 . . . NAME-main..autotmp_4 x(0) class(PAUTO) esc(N) tc(1) assigned used int # 235.go:28 . . IF tc(1) # 235.go:29 . . IF-Cond . . . LT tc(1) bool # 235.go:29 bool . . . . INDEX tc(1) uint64 # 235.go:29 uint64 . . . . . NAME-main.xs g(2) x(0) class(PPARAM) esc(no) tc(1) used SLICE-[]uint64 # 235.go:26 . . . . . NAME-main.i g(4) x(0) class(PAUTO) esc(no) tc(1) assigned used int # 235.go:28 . . . . NAME-main.m g(3) x(0) class(PAUTO) esc(no) tc(1) assigned used uint64 # 235.go:27 . . IF-Body . . . AS tc(1) # 235.go:30 . . . . NAME-main.m g(3) x(0) class(PAUTO) esc(no) tc(1) assigned used uint64 # 235.go:27 . . . . INDEX tc(1) uint64 # 235.go:30 uint64 . . . . . NAME-main.xs g(2) x(0) class(PPARAM) esc(no) tc(1) used SLICE-[]uint64 # 235.go:26 . . . . . NAME-main.i g(4) x(0) class(PAUTO) esc(no) tc(1) assigned used int # 235.go:28 Note in particular the clear marking of FOR-Cond, FOR-Post, FOR-Body compared to the original. The only changes to a few test files are the improved field name lines, and of course the line numbers. Passes buildall w/ toolstash -cmp. Change-Id: I5b654d9d8ee898976d4c387742ea688a082bac78 Reviewed-on: https://go-review.googlesource.com/c/go/+/275785 Trust: Russ Cox Run-TryBot: Russ Cox TryBot-Result: Go Bot Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/ir/fmt.go | 149 +++++++++++++++++++---------- 1 file changed, 97 insertions(+), 52 deletions(-) diff --git a/src/cmd/compile/internal/ir/fmt.go b/src/cmd/compile/internal/ir/fmt.go index 68e425bdaa..4bea6e2ae0 100644 --- a/src/cmd/compile/internal/ir/fmt.go +++ b/src/cmd/compile/internal/ir/fmt.go @@ -10,6 +10,9 @@ import ( "go/constant" "io" "os" + "path/filepath" + "reflect" + "strings" "unicode/utf8" @@ -957,17 +960,6 @@ func dumpNodeHeader(w io.Writer, n Node) { fmt.Fprintf(w, " defn(%p)", n.Name().Defn) } - if n.Pos().IsKnown() { - pfx := "" - switch n.Pos().IsStmt() { - case src.PosNotStmt: - pfx = "_" // "-" would be confusing - case src.PosIsStmt: - pfx = "+" - } - fmt.Fprintf(w, " l(%s%d)", pfx, n.Pos().Line()) - } - if n.Offset() != types.BADWIDTH { fmt.Fprintf(w, " x(%d)", n.Offset()) } @@ -1029,6 +1021,32 @@ func dumpNodeHeader(w io.Writer, n Node) { if n.Name() != nil && n.Name().Used() { fmt.Fprint(w, " used") } + + if n.Op() == OCLOSURE { + if fn := n.Func(); fn != nil && fn.Nname.Sym() != nil { + fmt.Fprintf(w, " fnName(%+v)", fn.Nname.Sym()) + } + } + + if n.Type() != nil { + if n.Op() == OTYPE { + fmt.Fprintf(w, " type") + } + fmt.Fprintf(w, " %+v", n.Type()) + } + + if n.Pos().IsKnown() { + pfx := "" + switch n.Pos().IsStmt() { + case src.PosNotStmt: + pfx = "_" // "-" would be confusing + case src.PosIsStmt: + pfx = "+" + } + pos := base.Ctxt.PosTable.Pos(n.Pos()) + file := filepath.Base(pos.Filename()) + fmt.Fprintf(w, " # %s%s:%d", pfx, file, pos.Line()) + } } func dumpNode(w io.Writer, n Node, depth int) { @@ -1052,6 +1070,7 @@ func dumpNode(w io.Writer, n Node, depth int) { case OLITERAL: fmt.Fprintf(w, "%+v-%v", n.Op(), n.Val()) dumpNodeHeader(w, n) + return case ONAME, ONONAME, OMETHEXPR: if n.Sym() != nil { @@ -1065,6 +1084,7 @@ func dumpNode(w io.Writer, n Node, depth int) { fmt.Fprintf(w, "%+v-ntype", n.Op()) dumpNode(w, n.Name().Ntype, depth+1) } + return case OASOP: fmt.Fprintf(w, "%+v-%+v", n.Op(), n.SubOp()) @@ -1073,61 +1093,86 @@ func dumpNode(w io.Writer, n Node, depth int) { case OTYPE: fmt.Fprintf(w, "%+v %+v", n.Op(), n.Sym()) dumpNodeHeader(w, n) - fmt.Fprintf(w, " type=%+v", n.Type()) if n.Type() == nil && n.Name() != nil && n.Name().Ntype != nil { indent(w, depth) fmt.Fprintf(w, "%+v-ntype", n.Op()) dumpNode(w, n.Name().Ntype, depth+1) } - } + return - if n.Op() == OCLOSURE && n.Func() != nil && n.Func().Nname.Sym() != nil { - fmt.Fprintf(w, " fnName %+v", n.Func().Nname.Sym()) + case OCLOSURE: + fmt.Fprintf(w, "%+v", n.Op()) + dumpNodeHeader(w, n) + + case ODCLFUNC: + // Func has many fields we don't want to print. + // Bypass reflection and just print what we want. + fmt.Fprintf(w, "%+v", n.Op()) + dumpNodeHeader(w, n) + fn := n.Func() + if len(fn.Dcl) > 0 { + indent(w, depth) + fmt.Fprintf(w, "%+v-Dcl", n.Op()) + for _, dcl := range n.Func().Dcl { + dumpNode(w, dcl, depth+1) + } + } + if fn.Body().Len() > 0 { + indent(w, depth) + fmt.Fprintf(w, "%+v-body", n.Op()) + dumpNodes(w, n.Body(), depth+1) + } + return } - if n.Sym() != nil && n.Op() != ONAME { + + if n.Sym() != nil { fmt.Fprintf(w, " %+v", n.Sym()) } - if n.Type() != nil { fmt.Fprintf(w, " %+v", n.Type()) } - if n.Left() != nil { - dumpNode(w, n.Left(), depth+1) - } - if n.Right() != nil { - dumpNode(w, n.Right(), depth+1) - } - if n.Op() == OCLOSURE && n.Func() != nil && n.Func().Body().Len() != 0 { - indent(w, depth) - // The function associated with a closure - fmt.Fprintf(w, "%+v-clofunc", n.Op()) - dumpNode(w, n.Func(), depth+1) - } - if n.Op() == ODCLFUNC && n.Func() != nil && n.Func().Dcl != nil && len(n.Func().Dcl) != 0 { - indent(w, depth) - // The dcls for a func or closure - fmt.Fprintf(w, "%+v-dcl", n.Op()) - for _, dcl := range n.Func().Dcl { - dumpNode(w, dcl, depth+1) + v := reflect.ValueOf(n).Elem() + t := reflect.TypeOf(n).Elem() + nf := t.NumField() + for i := 0; i < nf; i++ { + tf := t.Field(i) + vf := v.Field(i) + if tf.PkgPath != "" { + // skip unexported field - Interface will fail + continue + } + switch tf.Type.Kind() { + case reflect.Interface, reflect.Ptr, reflect.Slice: + if vf.IsNil() { + continue + } + } + name := strings.TrimSuffix(tf.Name, "_") + // Do not bother with field name header lines for the + // most common positional arguments: unary, binary expr, + // index expr, send stmt, go and defer call expression. + switch name { + case "X", "Y", "Index", "Chan", "Value", "Call": + name = "" + } + switch val := vf.Interface().(type) { + case Node: + if name != "" { + indent(w, depth) + fmt.Fprintf(w, "%+v-%s", n.Op(), name) + } + dumpNode(w, val, depth+1) + case Nodes: + if val.Len() == 0 { + continue + } + if name != "" { + indent(w, depth) + fmt.Fprintf(w, "%+v-%s", n.Op(), name) + } + dumpNodes(w, val, depth+1) } - } - if n.List().Len() != 0 { - indent(w, depth) - fmt.Fprintf(w, "%+v-list", n.Op()) - dumpNodes(w, n.List(), depth+1) - } - - if n.Rlist().Len() != 0 { - indent(w, depth) - fmt.Fprintf(w, "%+v-rlist", n.Op()) - dumpNodes(w, n.Rlist(), depth+1) - } - - if n.Body().Len() != 0 { - indent(w, depth) - fmt.Fprintf(w, "%+v-body", n.Op()) - dumpNodes(w, n.Body(), depth+1) } } -- 2.50.0