// Copyright 2015 The Go Authors. All rights reserved. // Copyright 2019 Dominik Honnef. All rights reserved. package ir import ( "bytes" "fmt" "go/types" "html" "io" "log" "os" "os/exec" "path/filepath" "reflect" "sort" "strings" ) func live(f *Function) []bool { max := 0 var ops []*Value for _, b := range f.Blocks { for _, instr := range b.Instrs { if int(instr.ID()) > max { max = int(instr.ID()) } } } out := make([]bool, max+1) var q []Node for _, b := range f.Blocks { for _, instr := range b.Instrs { switch instr.(type) { case *BlankStore, *Call, *ConstantSwitch, *Defer, *Go, *If, *Jump, *MapUpdate, *Next, *Panic, *Recv, *Return, *RunDefers, *Send, *Store, *Unreachable: out[instr.ID()] = true q = append(q, instr) } } } for len(q) > 0 { v := q[len(q)-1] q = q[:len(q)-1] for _, op := range v.Operands(ops) { if *op == nil { continue } if !out[(*op).ID()] { out[(*op).ID()] = true q = append(q, *op) } } } return out } type funcPrinter interface { startBlock(b *BasicBlock, reachable bool) endBlock(b *BasicBlock) value(v Node, live bool) startDepCycle() endDepCycle() named(n string, vals []Value) } func namedValues(f *Function) map[types.Object][]Value { names := map[types.Object][]Value{} for _, b := range f.Blocks { for _, instr := range b.Instrs { if instr, ok := instr.(*DebugRef); ok { if obj := instr.object; obj != nil { names[obj] = append(names[obj], instr.X) } } } } // XXX deduplicate values return names } func fprintFunc(p funcPrinter, f *Function) { // XXX does our IR form preserve unreachable blocks? // reachable, live := findlive(f) l := live(f) for _, b := range f.Blocks { // XXX // p.startBlock(b, reachable[b.Index]) p.startBlock(b, true) end := len(b.Instrs) - 1 if end < 0 { end = 0 } for _, v := range b.Instrs[:end] { if _, ok := v.(*DebugRef); !ok { p.value(v, l[v.ID()]) } } p.endBlock(b) } names := namedValues(f) keys := make([]types.Object, 0, len(names)) for key := range names { keys = append(keys, key) } sort.Slice(keys, func(i, j int) bool { return keys[i].Pos() < keys[j].Pos() }) for _, key := range keys { p.named(key.Name(), names[key]) } } func opName(v Node) string { switch v := v.(type) { case *Call: if v.Common().IsInvoke() { return "Invoke" } return "Call" case *Alloc: if v.Heap { return "HeapAlloc" } return "StackAlloc" case *Select: if v.Blocking { return "SelectBlocking" } return "SelectNonBlocking" default: return reflect.ValueOf(v).Type().Elem().Name() } } type HTMLWriter struct { w io.WriteCloser path string dot *dotWriter } func NewHTMLWriter(path string, funcname, cfgMask string) *HTMLWriter { out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { log.Fatalf("%v", err) } pwd, err := os.Getwd() if err != nil { log.Fatalf("%v", err) } html := HTMLWriter{w: out, path: filepath.Join(pwd, path)} html.dot = newDotWriter() html.start(funcname) return &html } func (w *HTMLWriter) start(name string) { if w == nil { return } w.WriteString("") w.WriteString(` `) w.WriteString("") w.WriteString("

") w.WriteString(html.EscapeString(name)) w.WriteString("

") w.WriteString(` help

Click on a value or block to toggle highlighting of that value/block and its uses. (Values and blocks are highlighted by ID, and IDs of dead items may be reused, so not all highlights necessarily correspond to the clicked item.)

Faded out values and blocks are dead code that has not been eliminated.

Values printed in italics have a dependency cycle.

CFG: Dashed edge is for unlikely branches. Blue color is for backward edges. Edge with a dot means that this edge follows the order in which blocks were laidout.

`) w.WriteString("") w.WriteString("") } func (w *HTMLWriter) Close() { if w == nil { return } io.WriteString(w.w, "") io.WriteString(w.w, "
") io.WriteString(w.w, "") io.WriteString(w.w, "") w.w.Close() fmt.Printf("dumped IR to %v\n", w.path) } // WriteFunc writes f in a column headed by title. // phase is used for collapsing columns and should be unique across the table. func (w *HTMLWriter) WriteFunc(phase, title string, f *Function) { if w == nil { return } w.WriteColumn(phase, title, "", funcHTML(f, phase, w.dot)) } // WriteColumn writes raw HTML in a column headed by title. // It is intended for pre- and post-compilation log output. func (w *HTMLWriter) WriteColumn(phase, title, class, html string) { if w == nil { return } id := strings.Replace(phase, " ", "-", -1) // collapsed column w.Printf("
%v
", id, phase) if class == "" { w.Printf("", id) } else { w.Printf("", id, class) } w.WriteString("

" + title + "

") w.WriteString(html) w.WriteString("") } func (w *HTMLWriter) Printf(msg string, v ...interface{}) { if _, err := fmt.Fprintf(w.w, msg, v...); err != nil { log.Fatalf("%v", err) } } func (w *HTMLWriter) WriteString(s string) { if _, err := io.WriteString(w.w, s); err != nil { log.Fatalf("%v", err) } } func valueHTML(v Node) string { if v == nil { return "<nil>" } // TODO: Using the value ID as the class ignores the fact // that value IDs get recycled and that some values // are transmuted into other values. class := fmt.Sprintf("t%d", v.ID()) var label string switch v := v.(type) { case *Function: label = v.RelString(nil) case *Builtin: label = v.Name() default: label = class } return fmt.Sprintf("%s", class, label) } func valueLongHTML(v Node) string { // TODO: Any intra-value formatting? // I'm wary of adding too much visual noise, // but a little bit might be valuable. // We already have visual noise in the form of punctuation // maybe we could replace some of that with formatting. s := fmt.Sprintf("", v.ID()) linenumber := "(?)" if v.Pos().IsValid() { line := v.Parent().Prog.Fset.Position(v.Pos()).Line linenumber = fmt.Sprintf("(%d)", line, line) } s += fmt.Sprintf("%s %s = %s", valueHTML(v), linenumber, opName(v)) if v, ok := v.(Value); ok { s += " <" + html.EscapeString(v.Type().String()) + ">" } switch v := v.(type) { case *Parameter: s += fmt.Sprintf(" {%s}", html.EscapeString(v.name)) case *BinOp: s += fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String())) case *UnOp: s += fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String())) case *Extract: name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name() s += fmt.Sprintf(" [%d] (%s)", v.Index, name) case *Field: st := v.X.Type().Underlying().(*types.Struct) // Be robust against a bad index. name := "?" if 0 <= v.Field && v.Field < st.NumFields() { name = st.Field(v.Field).Name() } s += fmt.Sprintf(" [%d] (%s)", v.Field, name) case *FieldAddr: st := deref(v.X.Type()).Underlying().(*types.Struct) // Be robust against a bad index. name := "?" if 0 <= v.Field && v.Field < st.NumFields() { name = st.Field(v.Field).Name() } s += fmt.Sprintf(" [%d] (%s)", v.Field, name) case *Recv: s += fmt.Sprintf(" {%t}", v.CommaOk) case *Call: if v.Common().IsInvoke() { s += fmt.Sprintf(" {%s}", html.EscapeString(v.Common().Method.FullName())) } case *Const: if v.Value == nil { s += " {<nil>}" } else { s += fmt.Sprintf(" {%s}", html.EscapeString(v.Value.String())) } case *Sigma: s += fmt.Sprintf(" [#%s]", v.From) } for _, a := range v.Operands(nil) { s += fmt.Sprintf(" %s", valueHTML(*a)) } // OPT(dh): we're calling namedValues many times on the same function. allNames := namedValues(v.Parent()) var names []string for name, values := range allNames { for _, value := range values { if v == value { names = append(names, name.Name()) break } } } if len(names) != 0 { s += " (" + strings.Join(names, ", ") + ")" } s += "" return s } func blockHTML(b *BasicBlock) string { // TODO: Using the value ID as the class ignores the fact // that value IDs get recycled and that some values // are transmuted into other values. s := html.EscapeString(b.String()) return fmt.Sprintf("%s", s, s) } func blockLongHTML(b *BasicBlock) string { var kind string var term Instruction if len(b.Instrs) > 0 { term = b.Control() kind = opName(term) } // TODO: improve this for HTML? s := fmt.Sprintf("%s", b.Index, kind) if term != nil { ops := term.Operands(nil) if len(ops) > 0 { var ss []string for _, op := range ops { ss = append(ss, valueHTML(*op)) } s += " " + strings.Join(ss, ", ") } } if len(b.Succs) > 0 { s += " →" // right arrow for _, c := range b.Succs { s += " " + blockHTML(c) } } return s } func funcHTML(f *Function, phase string, dot *dotWriter) string { buf := new(bytes.Buffer) if dot != nil { dot.writeFuncSVG(buf, phase, f) } fmt.Fprint(buf, "") p := htmlFuncPrinter{w: buf} fprintFunc(p, f) // fprintFunc(&buf, f) // TODO: HTML, not text,
for line breaks, etc. fmt.Fprint(buf, "
") return buf.String() } type htmlFuncPrinter struct { w io.Writer } func (p htmlFuncPrinter) startBlock(b *BasicBlock, reachable bool) { var dead string if !reachable { dead = "dead-block" } fmt.Fprintf(p.w, "") } func (p htmlFuncPrinter) value(v Node, live bool) { var dead string if !live { dead = "dead-value" } fmt.Fprintf(p.w, "
  • ", dead) fmt.Fprint(p.w, valueLongHTML(v)) io.WriteString(p.w, "
  • ") } func (p htmlFuncPrinter) startDepCycle() { fmt.Fprintln(p.w, "") } func (p htmlFuncPrinter) endDepCycle() { fmt.Fprintln(p.w, "") } func (p htmlFuncPrinter) named(n string, vals []Value) { fmt.Fprintf(p.w, "
  • name %s: ", n) for _, val := range vals { fmt.Fprintf(p.w, "%s ", valueHTML(val)) } fmt.Fprintf(p.w, "
  • ") } type dotWriter struct { path string broken bool } // newDotWriter returns non-nil value when mask is valid. // dotWriter will generate SVGs only for the phases specified in the mask. // mask can contain following patterns and combinations of them: // * - all of them; // x-y - x through y, inclusive; // x,y - x and y, but not the passes between. func newDotWriter() *dotWriter { path, err := exec.LookPath("dot") if err != nil { fmt.Println(err) return nil } return &dotWriter{path: path} } func (d *dotWriter) writeFuncSVG(w io.Writer, phase string, f *Function) { if d.broken { return } cmd := exec.Command(d.path, "-Tsvg") pipe, err := cmd.StdinPipe() if err != nil { d.broken = true fmt.Println(err) return } buf := new(bytes.Buffer) cmd.Stdout = buf bufErr := new(bytes.Buffer) cmd.Stderr = bufErr err = cmd.Start() if err != nil { d.broken = true fmt.Println(err) return } fmt.Fprint(pipe, `digraph "" { margin=0; size="4,40"; ranksep=.2; `) id := strings.Replace(phase, " ", "-", -1) fmt.Fprintf(pipe, `id="g_graph_%s";`, id) fmt.Fprintf(pipe, `node [style=filled,fillcolor=white,fontsize=16,fontname="Menlo,Times,serif",margin="0.01,0.03"];`) fmt.Fprintf(pipe, `edge [fontsize=16,fontname="Menlo,Times,serif"];`) for _, b := range f.Blocks { layout := "" fmt.Fprintf(pipe, `%v [label="%v%s\n%v",id="graph_node_%v_%v"];`, b, b, layout, b.Control().String(), id, b) } indexOf := make([]int, len(f.Blocks)) for i, b := range f.Blocks { indexOf[b.Index] = i } // XXX /* ponums := make([]int32, len(f.Blocks)) _ = postorderWithNumbering(f, ponums) isBackEdge := func(from, to int) bool { return ponums[from] <= ponums[to] } */ isBackEdge := func(from, to int) bool { return false } for _, b := range f.Blocks { for i, s := range b.Succs { style := "solid" color := "black" arrow := "vee" if isBackEdge(b.Index, s.Index) { color = "blue" } fmt.Fprintf(pipe, `%v -> %v [label=" %d ",style="%s",color="%s",arrowhead="%s"];`, b, s, i, style, color, arrow) } } fmt.Fprint(pipe, "}") pipe.Close() err = cmd.Wait() if err != nil { d.broken = true fmt.Printf("dot: %v\n%v\n", err, bufErr.String()) return } svgID := "svg_graph_" + id fmt.Fprintf(w, `
    `, svgID, svgID) // For now, an awful hack: edit the html as it passes through // our fingers, finding '