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 }