Skip to content

Commit

Permalink
cmd/compile: add CFG graphs to ssa.html
Browse files Browse the repository at this point in the history
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 golang#20355 and golang#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 golang#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
  • Loading branch information
josharian committed May 16, 2017
1 parent 4b0d74f commit 76db49d
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 17 deletions.
1 change: 1 addition & 0 deletions src/cmd/compile/fmt_test.go
Expand Up @@ -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": "",
Expand Down
1 change: 1 addition & 0 deletions src/cmd/compile/internal/ssa/func.go
Expand Up @@ -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
Expand Down
221 changes: 204 additions & 17 deletions src/cmd/compile/internal/ssa/html.go
Expand Up @@ -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 {
Expand All @@ -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
}
Expand All @@ -33,6 +37,23 @@ func (w *HTMLWriter) start(name string) {
return
}
w.WriteString("<html>")
// 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(`<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<style>
Expand All @@ -54,13 +75,13 @@ func (w *HTMLWriter) start(name string) {
table {
border: 1px solid black;
table-layout: fixed;
width: 300px;
width: ` + tableWidth + `px;
}
th, td {
border: 1px solid black;
overflow: hidden;
width: 400px;
width: ` + elemWidth + `px;
vertical-align: top;
padding: 5px;
}
Expand Down Expand Up @@ -129,13 +150,21 @@ dd.ssa-prog {
.highlight-powderblue { background-color: powderblue; }
.highlight-lightgray { background-color: lightgray; }
.outline-blue { outline: blue solid 2px; }
.outline-red { outline: red solid 2px; }
.outline-blueviolet { outline: blueviolet solid 2px; }
.outline-darkolivegreen { outline: darkolivegreen solid 2px; }
.outline-fuchsia { outline: fuchsia solid 2px; }
.outline-sienna { outline: sienna solid 2px; }
.outline-gold { outline: gold solid 2px; }
span.outline-blue { outline: blue solid 2px; }
span.outline-red { outline: red solid 2px; }
span.outline-blueviolet { outline: blueviolet solid 2px; }
span.outline-darkolivegreen { outline: darkolivegreen solid 2px; }
span.outline-fuchsia { outline: fuchsia solid 2px; }
span.outline-sienna { outline: sienna solid 2px; }
span.outline-gold { outline: gold solid 2px; }
ellipse.outline-blue { stroke: blue; stroke-width: 3; }
ellipse.outline-red { stroke: red; stroke-width: 3; }
ellipse.outline-blueviolet { stroke: blueviolet; stroke-width: 3; }
ellipse.outline-darkolivegreen { stroke: darkolivegreen; stroke-width: 3; }
ellipse.outline-fuchsia { stroke: fuchsia; stroke-width: 3; }
ellipse.outline-sienna { stroke: sienna; stroke-width: 3; }
ellipse.outline-gold { stroke: gold; stroke-width: 3; }
</style>
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
</p>
<p>
Press 'w' to make the columns wider, 's' to make them skinnier.
</pr>
</div>
`)
w.WriteString("<table>")
Expand All @@ -301,6 +367,10 @@ func (w *HTMLWriter) Close() {
io.WriteString(w.w, "</table>")
io.WriteString(w.w, "</body>")
io.WriteString(w.w, "</html>")
// if w.dot.err != nil {
// // TODO: Put this somewhere visible in the HTML instead of panicking
// panic(w.dot.err)
// }
w.w.Close()
}

Expand All @@ -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.
Expand Down Expand Up @@ -399,17 +468,112 @@ func (b *Block) LongHTML() string {
return s
}

func (f *Func) HTML() string {
var buf bytes.Buffer
fmt.Fprint(&buf, "<code>")
p := htmlFuncPrinter{w: &buf}
func (f *Func) HTML(dot *dotWriter) string {
buf := new(bytes.Buffer)
dot.writeFuncSVG(buf, f)
fmt.Fprint(buf, "<code>")
p := htmlFuncPrinter{w: buf}
fprintFunc(p, f)

// fprintFunc(&buf, f) // TODO: HTML, not text, <br /> for line breaks, etc.
fmt.Fprint(&buf, "</code>")
fmt.Fprint(buf, "</code>")
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 '<svg width="..." height="..." [everything else]'
// and replacing it with '<svg width="100%" [everything else]'.
d.copyAfter(w, buf, `<svg `)
d.copyAfter(w, buf, `width="`)
io.WriteString(w, `100%"`)
d.copyAfter(ioutil.Discard, buf, `"`)
d.copyAfter(ioutil.Discard, buf, `height="`)
d.copyAfter(ioutil.Discard, buf, `"`)
if d.err != nil {
return
}
io.Copy(w, buf)
d.ngraphs++ // used to give each node a unique id
}

func (b *Block) unlikelyIndex() int {
switch b.Likely {
case BranchLikely:
return 1
case BranchUnlikely:
return 0
}
return -1
}

func (d *dotWriter) copyAfter(w io.Writer, buf *bytes.Buffer, sep string) {
if d.err != nil {
return
}
i := bytes.Index(buf.Bytes(), []byte(sep))
if i == -1 {
d.setErr(fmt.Errorf("couldn't find dot sep %q", sep))
return
}
io.CopyN(w, buf, int64(i+len(sep)))
}

type htmlFuncPrinter struct {
w io.Writer
}
Expand Down Expand Up @@ -475,3 +639,26 @@ func (p htmlFuncPrinter) named(n LocalSlot, vals []*Value) {
}
fmt.Fprintf(p.w, "</li>")
}

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
}
}
1 change: 1 addition & 0 deletions src/cmd/compile/internal/ssa/layout.go
Expand Up @@ -117,4 +117,5 @@ blockloop:
}
}
f.Blocks = order
f.laidout = true
}

0 comments on commit 76db49d

Please sign in to comment.