From 76db49db5f934c7bc7b322743acbe57ab3a9c253 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Sat, 13 May 2017 16:40:52 -0700 Subject: [PATCH] cmd/compile: add CFG graphs to ssa.html DO NOT SUBMIT This CL adds CFG graphs to ssa.html. It execs dot to generate SVG, which then gets inlined into the html. Some standard naming and javascript hacks enable integration with the rest of ssa.html: Clicking on blocks highlights the relevant part of the CFG graph, and vice versa. Sample output and screenshots can be seen in issues #20355 and #20356. There are serious problems with this CL, though. Performance: * Calling dot after every pass is noticeably slow. * The generated output is giant. * The browser is very slow to render the resulting page. * Clicking on blocks is even slower than before. * Some things I want to do, like allow the user to change the table column widths, lock up the browser. Appearance: * The CFGs can easily be large and overwhelming. Toggling them on/off might be workable, if the performance concerns above were addressed. * I can't figure out the right size to render the CFGs; simple ones are currently oversized and cartoonish, while larger ones are unreadable. * They requires an unsatisfying amount of explanation (see #20356). Block layout information is particularly inferior/confusing. * Dead blocks float awkwardly in the sky, with no indication that they are dead. * It'd be nice to somehow add visual information about loops, which we can calculate, and which is non-obvious in large graphs, but I don't know how. * It'd be nice to add more information per block, like the number of values it contains, or even the values themselves, but adding info to a node makes the graph even less readable. Just adding the f.Blocks index in parens was not good. Bugs, incompleteness: * I seem to have broken highlighting around the entire block in the text. * Need to hook up some way to communicate dot-related errors without bringing down the process. * Might need some way to enable/disable dot entirely. Change-Id: I19abc3007f396bdb710ba7563668d343c0924feb --- src/cmd/compile/fmt_test.go | 1 + src/cmd/compile/internal/ssa/func.go | 1 + src/cmd/compile/internal/ssa/html.go | 221 +++++++++++++++++++++++-- src/cmd/compile/internal/ssa/layout.go | 1 + 4 files changed, 207 insertions(+), 17 deletions(-) diff --git a/src/cmd/compile/fmt_test.go b/src/cmd/compile/fmt_test.go index 59de326a91947..2a989261225d0 100644 --- a/src/cmd/compile/fmt_test.go +++ b/src/cmd/compile/fmt_test.go @@ -626,6 +626,7 @@ var knownFormats = map[string]string{ "cmd/compile/internal/gc.Val %v": "", "cmd/compile/internal/gc.fmtMode %d": "", "cmd/compile/internal/gc.initKind %d": "", + "cmd/compile/internal/ssa.BlockKind %v": "", "cmd/compile/internal/ssa.BranchPrediction %d": "", "cmd/compile/internal/ssa.Edge %v": "", "cmd/compile/internal/ssa.GCNode %v": "", diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go index 7ec596372adeb..64b7c8c53a19f 100644 --- a/src/cmd/compile/internal/ssa/func.go +++ b/src/cmd/compile/internal/ssa/func.go @@ -42,6 +42,7 @@ type Func struct { DebugTest bool // default true unless $GOSSAHASH != ""; as a debugging aid, make new code conditional on this and use GOSSAHASH to binary search for failing cases scheduled bool // Values in Blocks are in final order + laidout bool // Blocks are ordered NoSplit bool // true if function is marked as nosplit. Used by schedule check pass. WBPos src.XPos // line number of first write barrier diff --git a/src/cmd/compile/internal/ssa/html.go b/src/cmd/compile/internal/ssa/html.go index d554907bebe54..54965d33466f9 100644 --- a/src/cmd/compile/internal/ssa/html.go +++ b/src/cmd/compile/internal/ssa/html.go @@ -10,12 +10,15 @@ import ( "fmt" "html" "io" + "io/ioutil" "os" + "os/exec" ) type HTMLWriter struct { Logger - w io.WriteCloser + w io.WriteCloser + dot *dotWriter } func NewHTMLWriter(path string, logger Logger, funcname string) *HTMLWriter { @@ -24,6 +27,7 @@ func NewHTMLWriter(path string, logger Logger, funcname string) *HTMLWriter { logger.Fatalf(src.NoXPos, "%v", err) } html := HTMLWriter{w: out, Logger: logger} + html.dot = newDotWriter() html.start(funcname) return &html } @@ -33,6 +37,23 @@ func (w *HTMLWriter) start(name string) { return } w.WriteString("") + // TODO: These numbers work well for fannkuch. + // The columns are too big for simpler CFGs. + // How do I pick a good size? + // And it will need to be applied post-facto; + // should we buffer the entire HTML so that + // we can fix it up in html head, + // or should we fix it with javascript? + // If we fix it with javascript, + // we can just let the user pick the size. + // This seems better but the resulting reflow + // seems to make Chrome lock up. + tableWidth := "400" + elemWidth := "300" + if w.dot.err == nil { + tableWidth = "800" + elemWidth = "700" + } w.WriteString(` @@ -252,6 +281,39 @@ window.onload = function() { for (var i = 0; i < ssablocks.length; i++) { ssablocks[i].addEventListener('click', ssaBlockClicked); } + + // find all svg block nodes, add their block classes + var nodes = document.querySelectorAll('*[id^="graph_node_"]'); + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + var name = node.id.toString(); + var block = name.substring(name.lastIndexOf("_")+1); + node.classList.remove("node"); + node.classList.add(block); + node.addEventListener('click', ssaBlockClicked); + var ellipse = node.getElementsByTagName('ellipse')[0]; + ellipse.classList.add(block); + } + + document.onkeypress = function(e) { + console.log(e.keyCode); + return; // TODO: decide what to do here...see comments about table width above + switch (e.keyCode) { + case 'w'.charCodeAt(): + // Make columns wider by applying a new "wide columns" class. + var tagnames = ["table", "th", "td"]; + for (var j = 0; j < tagnames.length; i++) { + console.log("tag", tagnames[j]) + var x = document.getElementsByTagName(tagnames[j]); + for (var i = 0; i < x.length; i++) { + console.log("add width3 to", x[i]) + x[i].classList.add("width3"); + } + } + case 's'.charCodeAt(): + // TODO: make skinnier + } + }; }; function toggle_visibility(id) { @@ -287,6 +349,10 @@ Faded out values and blocks are dead code that has not been eliminated. Values printed in italics have a dependency cycle.

+

+Press 'w' to make the columns wider, 's' to make them skinnier. + + `) w.WriteString("") @@ -301,6 +367,10 @@ func (w *HTMLWriter) Close() { io.WriteString(w.w, "
") io.WriteString(w.w, "") io.WriteString(w.w, "") + // if w.dot.err != nil { + // // TODO: Put this somewhere visible in the HTML instead of panicking + // panic(w.dot.err) + // } w.w.Close() } @@ -309,8 +379,7 @@ func (w *HTMLWriter) WriteFunc(title string, f *Func) { if w == nil { return // avoid generating HTML just to discard it } - w.WriteColumn(title, f.HTML()) - // TODO: Add visual representation of f's CFG. + w.WriteColumn(title, f.HTML(w.dot)) } // WriteColumn writes raw HTML in a column headed by title. @@ -399,17 +468,112 @@ func (b *Block) LongHTML() string { return s } -func (f *Func) HTML() string { - var buf bytes.Buffer - fmt.Fprint(&buf, "") - p := htmlFuncPrinter{w: &buf} +func (f *Func) HTML(dot *dotWriter) string { + buf := new(bytes.Buffer) + dot.writeFuncSVG(buf, 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, "
") + fmt.Fprint(buf, "
") return buf.String() } +func (d *dotWriter) writeFuncSVG(w io.Writer, f *Func) { + if d.err != nil { + return + } + buf := new(bytes.Buffer) + cmd := exec.Command(d.path, "-Tsvg") + pipe, err := cmd.StdinPipe() + d.setErr(err) + cmd.Stdout = buf + d.setErr(cmd.Start()) + fmt.Fprintln(pipe, "digraph {") + //fmt.Fprintln(pipe, "splines=ortho;") + for i, b := range f.Blocks { + layout := "" + if f.laidout { + layout = fmt.Sprintf(" (%d)", i) + } + fmt.Fprintf(pipe, `%v [label="%v%s\n%v",id="graph_node_%d_%v"];`, b, b, layout, b.Kind, d.ngraphs, b) + fmt.Fprintln(pipe) + } + indexOf := make([]int, f.NumBlocks()) + for i, b := range f.Blocks { + indexOf[b.ID] = i + } + layoutDrawn := make([]bool, f.NumBlocks()) + for _, b := range f.Blocks { + for i, s := range b.Succs { + style := "solid" + if b.unlikelyIndex() == i { + style = "dashed" + } + color := "black" + if f.laidout && indexOf[s.b.ID] == indexOf[b.ID]+1 { + color = "green" + layoutDrawn[s.b.ID] = true + } + fmt.Fprintf(pipe, `%v -> %v [label=" %d ",style="%s",color="%s"];`, b, s.b, i, style, color) + fmt.Fprintln(pipe) + } + } + if f.laidout { + fmt.Fprintln(pipe, "edge[constraint=false];") + for i := 1; i < len(f.Blocks); i++ { + if layoutDrawn[f.Blocks[i].ID] { + continue + } + fmt.Fprintf(pipe, `%s -> %s [color="green",style="dotted"];`, f.Blocks[i-1], f.Blocks[i]) + fmt.Fprintln(pipe) + } + } + fmt.Fprintln(pipe, "}") + pipe.Close() + d.setErr(cmd.Wait()) + + // Apparently there's no way to give a reliable target width to dot? + // And no way to supply an HTML class for the svg element either? + // For now, use an awful hack--edit the html as it passes through + // our fingers, finding '") } + +type dotWriter struct { + path string + err error + ngraphs int +} + +func newDotWriter() *dotWriter { + // if os.Getenv("GOSSACFG") == "" { + // return &dotWriter{err: errors.New("enable visual CFG by setting GOSSACFG=1")} + // } + path, err := exec.LookPath("dot") + return &dotWriter{path: path, err: err} +} + +func (d *dotWriter) setErr(err error) { + if err == nil { + return + } + if d.err == nil { + d.err = err + } +} diff --git a/src/cmd/compile/internal/ssa/layout.go b/src/cmd/compile/internal/ssa/layout.go index a2d4785e52bd9..eddcb4a51e6e2 100644 --- a/src/cmd/compile/internal/ssa/layout.go +++ b/src/cmd/compile/internal/ssa/layout.go @@ -117,4 +117,5 @@ blockloop: } } f.Blocks = order + f.laidout = true }