github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/go/ir/html.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Copyright 2019 Dominik Honnef. All rights reserved.
     3  
     4  package ir
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"go/types"
    10  	"html"
    11  	"io"
    12  	"log"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"reflect"
    17  	"sort"
    18  	"strings"
    19  )
    20  
    21  func live(f *Function) []bool {
    22  	max := 0
    23  	var ops []*Value
    24  
    25  	for _, b := range f.Blocks {
    26  		for _, instr := range b.Instrs {
    27  			if int(instr.ID()) > max {
    28  				max = int(instr.ID())
    29  			}
    30  		}
    31  	}
    32  
    33  	out := make([]bool, max+1)
    34  	var q []Node
    35  	for _, b := range f.Blocks {
    36  		for _, instr := range b.Instrs {
    37  			switch instr.(type) {
    38  			case *BlankStore, *Call, *ConstantSwitch, *Defer, *Go, *If, *Jump, *MapUpdate, *Next, *Panic, *Recv, *Return, *RunDefers, *Send, *Store, *Unreachable:
    39  				out[instr.ID()] = true
    40  				q = append(q, instr)
    41  			}
    42  		}
    43  	}
    44  
    45  	for len(q) > 0 {
    46  		v := q[len(q)-1]
    47  		q = q[:len(q)-1]
    48  		for _, op := range v.Operands(ops) {
    49  			if *op == nil {
    50  				continue
    51  			}
    52  			if !out[(*op).ID()] {
    53  				out[(*op).ID()] = true
    54  				q = append(q, *op)
    55  			}
    56  		}
    57  	}
    58  
    59  	return out
    60  }
    61  
    62  type funcPrinter interface {
    63  	startBlock(b *BasicBlock, reachable bool)
    64  	endBlock(b *BasicBlock)
    65  	value(v Node, live bool)
    66  	startDepCycle()
    67  	endDepCycle()
    68  	named(n string, vals []Value)
    69  }
    70  
    71  func namedValues(f *Function) map[types.Object][]Value {
    72  	names := map[types.Object][]Value{}
    73  	for _, b := range f.Blocks {
    74  		for _, instr := range b.Instrs {
    75  			if instr, ok := instr.(*DebugRef); ok {
    76  				if obj := instr.object; obj != nil {
    77  					names[obj] = append(names[obj], instr.X)
    78  				}
    79  			}
    80  		}
    81  	}
    82  	// XXX deduplicate values
    83  	return names
    84  }
    85  
    86  func fprintFunc(p funcPrinter, f *Function) {
    87  	// XXX does our IR form preserve unreachable blocks?
    88  	// reachable, live := findlive(f)
    89  
    90  	l := live(f)
    91  	for _, b := range f.Blocks {
    92  		// XXX
    93  		// p.startBlock(b, reachable[b.Index])
    94  		p.startBlock(b, true)
    95  
    96  		end := len(b.Instrs) - 1
    97  		if end < 0 {
    98  			end = 0
    99  		}
   100  		for _, v := range b.Instrs[:end] {
   101  			if _, ok := v.(*DebugRef); !ok {
   102  				p.value(v, l[v.ID()])
   103  			}
   104  		}
   105  		p.endBlock(b)
   106  	}
   107  
   108  	names := namedValues(f)
   109  	keys := make([]types.Object, 0, len(names))
   110  	for key := range names {
   111  		keys = append(keys, key)
   112  	}
   113  	sort.Slice(keys, func(i, j int) bool {
   114  		return keys[i].Pos() < keys[j].Pos()
   115  	})
   116  	for _, key := range keys {
   117  		p.named(key.Name(), names[key])
   118  	}
   119  }
   120  
   121  func opName(v Node) string {
   122  	switch v := v.(type) {
   123  	case *Call:
   124  		if v.Common().IsInvoke() {
   125  			return "Invoke"
   126  		}
   127  		return "Call"
   128  	case *Alloc:
   129  		if v.Heap {
   130  			return "HeapAlloc"
   131  		}
   132  		return "StackAlloc"
   133  	case *Select:
   134  		if v.Blocking {
   135  			return "SelectBlocking"
   136  		}
   137  		return "SelectNonBlocking"
   138  	default:
   139  		return reflect.ValueOf(v).Type().Elem().Name()
   140  	}
   141  }
   142  
   143  type HTMLWriter struct {
   144  	w    io.WriteCloser
   145  	path string
   146  	dot  *dotWriter
   147  }
   148  
   149  func NewHTMLWriter(path string, funcname, cfgMask string) *HTMLWriter {
   150  	out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
   151  	if err != nil {
   152  		log.Fatalf("%v", err)
   153  	}
   154  	pwd, err := os.Getwd()
   155  	if err != nil {
   156  		log.Fatalf("%v", err)
   157  	}
   158  	html := HTMLWriter{w: out, path: filepath.Join(pwd, path)}
   159  	html.dot = newDotWriter()
   160  	html.start(funcname)
   161  	return &html
   162  }
   163  
   164  func (w *HTMLWriter) start(name string) {
   165  	if w == nil {
   166  		return
   167  	}
   168  	w.WriteString("<html>")
   169  	w.WriteString(`<head>
   170  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
   171  <style>
   172  
   173  body {
   174      font-size: 14px;
   175      font-family: Arial, sans-serif;
   176  }
   177  
   178  h1 {
   179      font-size: 18px;
   180      display: inline-block;
   181      margin: 0 1em .5em 0;
   182  }
   183  
   184  #helplink {
   185      display: inline-block;
   186  }
   187  
   188  #help {
   189      display: none;
   190  }
   191  
   192  .stats {
   193      font-size: 60%;
   194  }
   195  
   196  table {
   197      border: 1px solid black;
   198      table-layout: fixed;
   199      width: 300px;
   200  }
   201  
   202  th, td {
   203      border: 1px solid black;
   204      overflow: hidden;
   205      width: 400px;
   206      vertical-align: top;
   207      padding: 5px;
   208  }
   209  
   210  td > h2 {
   211      cursor: pointer;
   212      font-size: 120%;
   213  }
   214  
   215  td.collapsed {
   216      font-size: 12px;
   217      width: 12px;
   218      border: 0px;
   219      padding: 0;
   220      cursor: pointer;
   221      background: #fafafa;
   222  }
   223  
   224  td.collapsed  div {
   225       -moz-transform: rotate(-90.0deg);  /* FF3.5+ */
   226         -o-transform: rotate(-90.0deg);  /* Opera 10.5 */
   227    -webkit-transform: rotate(-90.0deg);  /* Saf3.1+, Chrome */
   228               filter:  progid:DXImageTransform.Microsoft.BasicImage(rotation=0.083);  /* IE6,IE7 */
   229           -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0.083)"; /* IE8 */
   230           margin-top: 10.3em;
   231           margin-left: -10em;
   232           margin-right: -10em;
   233           text-align: right;
   234  }
   235  
   236  code, pre, .lines, .ast {
   237      font-family: Menlo, monospace;
   238      font-size: 12px;
   239  }
   240  
   241  pre {
   242      -moz-tab-size: 4;
   243      -o-tab-size:   4;
   244      tab-size:      4;
   245  }
   246  
   247  .allow-x-scroll {
   248      overflow-x: scroll;
   249  }
   250  
   251  .lines {
   252      float: left;
   253      overflow: hidden;
   254      text-align: right;
   255  }
   256  
   257  .lines div {
   258      padding-right: 10px;
   259      color: gray;
   260  }
   261  
   262  div.line-number {
   263      font-size: 12px;
   264  }
   265  
   266  .ast {
   267      white-space: nowrap;
   268  }
   269  
   270  td.ssa-prog {
   271      width: 600px;
   272      word-wrap: break-word;
   273  }
   274  
   275  li {
   276      list-style-type: none;
   277  }
   278  
   279  li.ssa-long-value {
   280      text-indent: -2em;  /* indent wrapped lines */
   281  }
   282  
   283  li.ssa-value-list {
   284      display: inline;
   285  }
   286  
   287  li.ssa-start-block {
   288      padding: 0;
   289      margin: 0;
   290  }
   291  
   292  li.ssa-end-block {
   293      padding: 0;
   294      margin: 0;
   295  }
   296  
   297  ul.ssa-print-func {
   298      padding-left: 0;
   299  }
   300  
   301  li.ssa-start-block button {
   302      padding: 0 1em;
   303      margin: 0;
   304      border: none;
   305      display: inline;
   306      font-size: 14px;
   307      float: right;
   308  }
   309  
   310  button:hover {
   311      background-color: #eee;
   312      cursor: pointer;
   313  }
   314  
   315  dl.ssa-gen {
   316      padding-left: 0;
   317  }
   318  
   319  dt.ssa-prog-src {
   320      padding: 0;
   321      margin: 0;
   322      float: left;
   323      width: 4em;
   324  }
   325  
   326  dd.ssa-prog {
   327      padding: 0;
   328      margin-right: 0;
   329      margin-left: 4em;
   330  }
   331  
   332  .dead-value {
   333      color: gray;
   334  }
   335  
   336  .dead-block {
   337      opacity: 0.5;
   338  }
   339  
   340  .depcycle {
   341      font-style: italic;
   342  }
   343  
   344  .line-number {
   345      font-size: 11px;
   346  }
   347  
   348  .no-line-number {
   349      font-size: 11px;
   350      color: gray;
   351  }
   352  
   353  .zoom {
   354  	position: absolute;
   355  	float: left;
   356  	white-space: nowrap;
   357  	background-color: #eee;
   358  }
   359  
   360  .zoom a:link, .zoom a:visited  {
   361      text-decoration: none;
   362      color: blue;
   363      font-size: 16px;
   364      padding: 4px 2px;
   365  }
   366  
   367  svg {
   368      cursor: default;
   369      outline: 1px solid #eee;
   370  }
   371  
   372  .highlight-aquamarine     { background-color: aquamarine; }
   373  .highlight-coral          { background-color: coral; }
   374  .highlight-lightpink      { background-color: lightpink; }
   375  .highlight-lightsteelblue { background-color: lightsteelblue; }
   376  .highlight-palegreen      { background-color: palegreen; }
   377  .highlight-skyblue        { background-color: skyblue; }
   378  .highlight-lightgray      { background-color: lightgray; }
   379  .highlight-yellow         { background-color: yellow; }
   380  .highlight-lime           { background-color: lime; }
   381  .highlight-khaki          { background-color: khaki; }
   382  .highlight-aqua           { background-color: aqua; }
   383  .highlight-salmon         { background-color: salmon; }
   384  
   385  .outline-blue           { outline: blue solid 2px; }
   386  .outline-red            { outline: red solid 2px; }
   387  .outline-blueviolet     { outline: blueviolet solid 2px; }
   388  .outline-darkolivegreen { outline: darkolivegreen solid 2px; }
   389  .outline-fuchsia        { outline: fuchsia solid 2px; }
   390  .outline-sienna         { outline: sienna solid 2px; }
   391  .outline-gold           { outline: gold solid 2px; }
   392  .outline-orangered      { outline: orangered solid 2px; }
   393  .outline-teal           { outline: teal solid 2px; }
   394  .outline-maroon         { outline: maroon solid 2px; }
   395  .outline-black          { outline: black solid 2px; }
   396  
   397  ellipse.outline-blue           { stroke-width: 2px; stroke: blue; }
   398  ellipse.outline-red            { stroke-width: 2px; stroke: red; }
   399  ellipse.outline-blueviolet     { stroke-width: 2px; stroke: blueviolet; }
   400  ellipse.outline-darkolivegreen { stroke-width: 2px; stroke: darkolivegreen; }
   401  ellipse.outline-fuchsia        { stroke-width: 2px; stroke: fuchsia; }
   402  ellipse.outline-sienna         { stroke-width: 2px; stroke: sienna; }
   403  ellipse.outline-gold           { stroke-width: 2px; stroke: gold; }
   404  ellipse.outline-orangered      { stroke-width: 2px; stroke: orangered; }
   405  ellipse.outline-teal           { stroke-width: 2px; stroke: teal; }
   406  ellipse.outline-maroon         { stroke-width: 2px; stroke: maroon; }
   407  ellipse.outline-black          { stroke-width: 2px; stroke: black; }
   408  
   409  </style>
   410  
   411  <script type="text/javascript">
   412  // ordered list of all available highlight colors
   413  var highlights = [
   414      "highlight-aquamarine",
   415      "highlight-coral",
   416      "highlight-lightpink",
   417      "highlight-lightsteelblue",
   418      "highlight-palegreen",
   419      "highlight-skyblue",
   420      "highlight-lightgray",
   421      "highlight-yellow",
   422      "highlight-lime",
   423      "highlight-khaki",
   424      "highlight-aqua",
   425      "highlight-salmon"
   426  ];
   427  
   428  // state: which value is highlighted this color?
   429  var highlighted = {};
   430  for (var i = 0; i < highlights.length; i++) {
   431      highlighted[highlights[i]] = "";
   432  }
   433  
   434  // ordered list of all available outline colors
   435  var outlines = [
   436      "outline-blue",
   437      "outline-red",
   438      "outline-blueviolet",
   439      "outline-darkolivegreen",
   440      "outline-fuchsia",
   441      "outline-sienna",
   442      "outline-gold",
   443      "outline-orangered",
   444      "outline-teal",
   445      "outline-maroon",
   446      "outline-black"
   447  ];
   448  
   449  // state: which value is outlined this color?
   450  var outlined = {};
   451  for (var i = 0; i < outlines.length; i++) {
   452      outlined[outlines[i]] = "";
   453  }
   454  
   455  window.onload = function() {
   456      var ssaElemClicked = function(elem, event, selections, selected) {
   457          event.stopPropagation();
   458  
   459          // TODO: pushState with updated state and read it on page load,
   460          // so that state can survive across reloads
   461  
   462          // find all values with the same name
   463          var c = elem.classList.item(0);
   464          var x = document.getElementsByClassName(c);
   465  
   466          // if selected, remove selections from all of them
   467          // otherwise, attempt to add
   468  
   469          var remove = "";
   470          for (var i = 0; i < selections.length; i++) {
   471              var color = selections[i];
   472              if (selected[color] == c) {
   473                  remove = color;
   474                  break;
   475              }
   476          }
   477  
   478          if (remove != "") {
   479              for (var i = 0; i < x.length; i++) {
   480                  x[i].classList.remove(remove);
   481              }
   482              selected[remove] = "";
   483              return;
   484          }
   485  
   486          // we're adding a selection
   487          // find first available color
   488          var avail = "";
   489          for (var i = 0; i < selections.length; i++) {
   490              var color = selections[i];
   491              if (selected[color] == "") {
   492                  avail = color;
   493                  break;
   494              }
   495          }
   496          if (avail == "") {
   497              alert("out of selection colors; go add more");
   498              return;
   499          }
   500  
   501          // set that as the selection
   502          for (var i = 0; i < x.length; i++) {
   503              x[i].classList.add(avail);
   504          }
   505          selected[avail] = c;
   506      };
   507  
   508      var ssaValueClicked = function(event) {
   509          ssaElemClicked(this, event, highlights, highlighted);
   510      };
   511  
   512      var ssaBlockClicked = function(event) {
   513          ssaElemClicked(this, event, outlines, outlined);
   514      };
   515  
   516      var ssavalues = document.getElementsByClassName("ssa-value");
   517      for (var i = 0; i < ssavalues.length; i++) {
   518          ssavalues[i].addEventListener('click', ssaValueClicked);
   519      }
   520  
   521      var ssalongvalues = document.getElementsByClassName("ssa-long-value");
   522      for (var i = 0; i < ssalongvalues.length; i++) {
   523          // don't attach listeners to li nodes, just the spans they contain
   524          if (ssalongvalues[i].nodeName == "SPAN") {
   525              ssalongvalues[i].addEventListener('click', ssaValueClicked);
   526          }
   527      }
   528  
   529      var ssablocks = document.getElementsByClassName("ssa-block");
   530      for (var i = 0; i < ssablocks.length; i++) {
   531          ssablocks[i].addEventListener('click', ssaBlockClicked);
   532      }
   533  
   534      var lines = document.getElementsByClassName("line-number");
   535      for (var i = 0; i < lines.length; i++) {
   536          lines[i].addEventListener('click', ssaValueClicked);
   537      }
   538  
   539      // Contains phase names which are expanded by default. Other columns are collapsed.
   540      var expandedDefault = [
   541          "start",
   542          "deadcode",
   543          "opt",
   544          "lower",
   545          "late deadcode",
   546          "regalloc",
   547          "genssa",
   548      ];
   549  
   550      function toggler(phase) {
   551          return function() {
   552              toggle_cell(phase+'-col');
   553              toggle_cell(phase+'-exp');
   554          };
   555      }
   556  
   557      function toggle_cell(id) {
   558          var e = document.getElementById(id);
   559          if (e.style.display == 'table-cell') {
   560              e.style.display = 'none';
   561          } else {
   562              e.style.display = 'table-cell';
   563          }
   564      }
   565  
   566      // Go through all columns and collapse needed phases.
   567      var td = document.getElementsByTagName("td");
   568      for (var i = 0; i < td.length; i++) {
   569          var id = td[i].id;
   570          var phase = id.substr(0, id.length-4);
   571          var show = expandedDefault.indexOf(phase) !== -1
   572          if (id.endsWith("-exp")) {
   573              var h2 = td[i].getElementsByTagName("h2");
   574              if (h2 && h2[0]) {
   575                  h2[0].addEventListener('click', toggler(phase));
   576              }
   577          } else {
   578              td[i].addEventListener('click', toggler(phase));
   579          }
   580          if (id.endsWith("-col") && show || id.endsWith("-exp") && !show) {
   581              td[i].style.display = 'none';
   582              continue;
   583          }
   584          td[i].style.display = 'table-cell';
   585      }
   586  
   587      // find all svg block nodes, add their block classes
   588      var nodes = document.querySelectorAll('*[id^="graph_node_"]');
   589      for (var i = 0; i < nodes.length; i++) {
   590      	var node = nodes[i];
   591      	var name = node.id.toString();
   592      	var block = name.substring(name.lastIndexOf("_")+1);
   593      	node.classList.remove("node");
   594      	node.classList.add(block);
   595          node.addEventListener('click', ssaBlockClicked);
   596          var ellipse = node.getElementsByTagName('ellipse')[0];
   597          ellipse.classList.add(block);
   598          ellipse.addEventListener('click', ssaBlockClicked);
   599      }
   600  
   601      // make big graphs smaller
   602      var targetScale = 0.5;
   603      var nodes = document.querySelectorAll('*[id^="svg_graph_"]');
   604      // TODO: Implement smarter auto-zoom using the viewBox attribute
   605      // and in case of big graphs set the width and height of the svg graph to
   606      // maximum allowed.
   607      for (var i = 0; i < nodes.length; i++) {
   608      	var node = nodes[i];
   609      	var name = node.id.toString();
   610      	var phase = name.substring(name.lastIndexOf("_")+1);
   611      	var gNode = document.getElementById("g_graph_"+phase);
   612      	var scale = gNode.transform.baseVal.getItem(0).matrix.a;
   613      	if (scale > targetScale) {
   614      		node.width.baseVal.value *= targetScale / scale;
   615      		node.height.baseVal.value *= targetScale / scale;
   616      	}
   617      }
   618  };
   619  
   620  function toggle_visibility(id) {
   621      var e = document.getElementById(id);
   622      if (e.style.display == 'block') {
   623          e.style.display = 'none';
   624      } else {
   625          e.style.display = 'block';
   626      }
   627  }
   628  
   629  function hideBlock(el) {
   630      var es = el.parentNode.parentNode.getElementsByClassName("ssa-value-list");
   631      if (es.length===0)
   632          return;
   633      var e = es[0];
   634      if (e.style.display === 'block' || e.style.display === '') {
   635          e.style.display = 'none';
   636          el.innerHTML = '+';
   637      } else {
   638          e.style.display = 'block';
   639          el.innerHTML = '-';
   640      }
   641  }
   642  
   643  // TODO: scale the graph with the viewBox attribute.
   644  function graphReduce(id) {
   645      var node = document.getElementById(id);
   646      if (node) {
   647      		node.width.baseVal.value *= 0.9;
   648      		node.height.baseVal.value *= 0.9;
   649      }
   650      return false;
   651  }
   652  
   653  function graphEnlarge(id) {
   654      var node = document.getElementById(id);
   655      if (node) {
   656      		node.width.baseVal.value *= 1.1;
   657      		node.height.baseVal.value *= 1.1;
   658      }
   659      return false;
   660  }
   661  
   662  function makeDraggable(event) {
   663      var svg = event.target;
   664      if (window.PointerEvent) {
   665          svg.addEventListener('pointerdown', startDrag);
   666          svg.addEventListener('pointermove', drag);
   667          svg.addEventListener('pointerup', endDrag);
   668          svg.addEventListener('pointerleave', endDrag);
   669      } else {
   670          svg.addEventListener('mousedown', startDrag);
   671          svg.addEventListener('mousemove', drag);
   672          svg.addEventListener('mouseup', endDrag);
   673          svg.addEventListener('mouseleave', endDrag);
   674      }
   675  
   676      var point = svg.createSVGPoint();
   677      var isPointerDown = false;
   678      var pointerOrigin;
   679      var viewBox = svg.viewBox.baseVal;
   680  
   681      function getPointFromEvent (event) {
   682          point.x = event.clientX;
   683          point.y = event.clientY;
   684  
   685          // We get the current transformation matrix of the SVG and we inverse it
   686          var invertedSVGMatrix = svg.getScreenCTM().inverse();
   687          return point.matrixTransform(invertedSVGMatrix);
   688      }
   689  
   690      function startDrag(event) {
   691          isPointerDown = true;
   692          pointerOrigin = getPointFromEvent(event);
   693      }
   694  
   695      function drag(event) {
   696          if (!isPointerDown) {
   697              return;
   698          }
   699          event.preventDefault();
   700  
   701          var pointerPosition = getPointFromEvent(event);
   702          viewBox.x -= (pointerPosition.x - pointerOrigin.x);
   703          viewBox.y -= (pointerPosition.y - pointerOrigin.y);
   704      }
   705  
   706      function endDrag(event) {
   707          isPointerDown = false;
   708      }
   709  }</script>
   710  
   711  </head>`)
   712  	w.WriteString("<body>")
   713  	w.WriteString("<h1>")
   714  	w.WriteString(html.EscapeString(name))
   715  	w.WriteString("</h1>")
   716  	w.WriteString(`
   717  <a href="#" onclick="toggle_visibility('help');return false;" id="helplink">help</a>
   718  <div id="help">
   719  
   720  <p>
   721  Click on a value or block to toggle highlighting of that value/block
   722  and its uses.  (Values and blocks are highlighted by ID, and IDs of
   723  dead items may be reused, so not all highlights necessarily correspond
   724  to the clicked item.)
   725  </p>
   726  
   727  <p>
   728  Faded out values and blocks are dead code that has not been eliminated.
   729  </p>
   730  
   731  <p>
   732  Values printed in italics have a dependency cycle.
   733  </p>
   734  
   735  <p>
   736  <b>CFG</b>: Dashed edge is for unlikely branches. Blue color is for backward edges.
   737  Edge with a dot means that this edge follows the order in which blocks were laid out.
   738  </p>
   739  
   740  </div>
   741  `)
   742  	w.WriteString("<table>")
   743  	w.WriteString("<tr>")
   744  }
   745  
   746  func (w *HTMLWriter) Close() {
   747  	if w == nil {
   748  		return
   749  	}
   750  	io.WriteString(w.w, "</tr>")
   751  	io.WriteString(w.w, "</table>")
   752  	io.WriteString(w.w, "</body>")
   753  	io.WriteString(w.w, "</html>")
   754  	w.w.Close()
   755  	fmt.Printf("dumped IR to %v\n", w.path)
   756  }
   757  
   758  // WriteFunc writes f in a column headed by title.
   759  // phase is used for collapsing columns and should be unique across the table.
   760  func (w *HTMLWriter) WriteFunc(phase, title string, f *Function) {
   761  	if w == nil {
   762  		return
   763  	}
   764  	w.WriteColumn(phase, title, "", funcHTML(f, phase, w.dot))
   765  }
   766  
   767  // WriteColumn writes raw HTML in a column headed by title.
   768  // It is intended for pre- and post-compilation log output.
   769  func (w *HTMLWriter) WriteColumn(phase, title, class, html string) {
   770  	if w == nil {
   771  		return
   772  	}
   773  	id := strings.Replace(phase, " ", "-", -1)
   774  	// collapsed column
   775  	w.Printf("<td id=\"%v-col\" class=\"collapsed\"><div>%v</div></td>", id, phase)
   776  
   777  	if class == "" {
   778  		w.Printf("<td id=\"%v-exp\">", id)
   779  	} else {
   780  		w.Printf("<td id=\"%v-exp\" class=\"%v\">", id, class)
   781  	}
   782  	w.WriteString("<h2>" + title + "</h2>")
   783  	w.WriteString(html)
   784  	w.WriteString("</td>")
   785  }
   786  
   787  func (w *HTMLWriter) Printf(msg string, v ...interface{}) {
   788  	if _, err := fmt.Fprintf(w.w, msg, v...); err != nil {
   789  		log.Fatalf("%v", err)
   790  	}
   791  }
   792  
   793  func (w *HTMLWriter) WriteString(s string) {
   794  	if _, err := io.WriteString(w.w, s); err != nil {
   795  		log.Fatalf("%v", err)
   796  	}
   797  }
   798  
   799  func valueHTML(v Node) string {
   800  	if v == nil {
   801  		return "&lt;nil&gt;"
   802  	}
   803  	// TODO: Using the value ID as the class ignores the fact
   804  	// that value IDs get recycled and that some values
   805  	// are transmuted into other values.
   806  	class := fmt.Sprintf("t%d", v.ID())
   807  	var label string
   808  	switch v := v.(type) {
   809  	case *Function:
   810  		label = v.RelString(nil)
   811  	case *Builtin:
   812  		label = v.Name()
   813  	default:
   814  		label = class
   815  	}
   816  	return fmt.Sprintf("<span class=\"%s ssa-value\">%s</span>", class, label)
   817  }
   818  
   819  func valueLongHTML(v Node) string {
   820  	// TODO: Any intra-value formatting?
   821  	// I'm wary of adding too much visual noise,
   822  	// but a little bit might be valuable.
   823  	// We already have visual noise in the form of punctuation
   824  	// maybe we could replace some of that with formatting.
   825  	s := fmt.Sprintf("<span class=\"t%d ssa-long-value\">", v.ID())
   826  
   827  	linenumber := "<span class=\"no-line-number\">(?)</span>"
   828  	if v.Pos().IsValid() {
   829  		line := v.Parent().Prog.Fset.Position(v.Pos()).Line
   830  		linenumber = fmt.Sprintf("<span class=\"l%v line-number\">(%d)</span>", line, line)
   831  	}
   832  
   833  	s += fmt.Sprintf("%s %s = %s", valueHTML(v), linenumber, opName(v))
   834  
   835  	if v, ok := v.(Value); ok {
   836  		s += " &lt;" + html.EscapeString(v.Type().String()) + "&gt;"
   837  	}
   838  
   839  	switch v := v.(type) {
   840  	case *Parameter:
   841  		s += fmt.Sprintf(" {%s}", html.EscapeString(v.name))
   842  	case *BinOp:
   843  		s += fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String()))
   844  	case *UnOp:
   845  		s += fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String()))
   846  	case *Extract:
   847  		name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name()
   848  		s += fmt.Sprintf(" [%d] (%s)", v.Index, name)
   849  	case *Field:
   850  		st := v.X.Type().Underlying().(*types.Struct)
   851  		// Be robust against a bad index.
   852  		name := "?"
   853  		if 0 <= v.Field && v.Field < st.NumFields() {
   854  			name = st.Field(v.Field).Name()
   855  		}
   856  		s += fmt.Sprintf(" [%d] (%s)", v.Field, name)
   857  	case *FieldAddr:
   858  		st := deref(v.X.Type()).Underlying().(*types.Struct)
   859  		// Be robust against a bad index.
   860  		name := "?"
   861  		if 0 <= v.Field && v.Field < st.NumFields() {
   862  			name = st.Field(v.Field).Name()
   863  		}
   864  
   865  		s += fmt.Sprintf(" [%d] (%s)", v.Field, name)
   866  	case *Recv:
   867  		s += fmt.Sprintf(" {%t}", v.CommaOk)
   868  	case *Call:
   869  		if v.Common().IsInvoke() {
   870  			s += fmt.Sprintf(" {%s}", html.EscapeString(v.Common().Method.FullName()))
   871  		}
   872  	case *Const:
   873  		if v.Value == nil {
   874  			s += " {&lt;nil&gt;}"
   875  		} else {
   876  			s += fmt.Sprintf(" {%s}", html.EscapeString(v.Value.String()))
   877  		}
   878  	case *Sigma:
   879  		s += fmt.Sprintf(" [#%s]", v.From)
   880  	}
   881  	for _, a := range v.Operands(nil) {
   882  		s += fmt.Sprintf(" %s", valueHTML(*a))
   883  	}
   884  
   885  	// OPT(dh): we're calling namedValues many times on the same function.
   886  	allNames := namedValues(v.Parent())
   887  	var names []string
   888  	for name, values := range allNames {
   889  		for _, value := range values {
   890  			if v == value {
   891  				names = append(names, name.Name())
   892  				break
   893  			}
   894  		}
   895  	}
   896  	if len(names) != 0 {
   897  		s += " (" + strings.Join(names, ", ") + ")"
   898  	}
   899  
   900  	s += "</span>"
   901  	return s
   902  }
   903  
   904  func blockHTML(b *BasicBlock) string {
   905  	// TODO: Using the value ID as the class ignores the fact
   906  	// that value IDs get recycled and that some values
   907  	// are transmuted into other values.
   908  	s := html.EscapeString(b.String())
   909  	return fmt.Sprintf("<span class=\"%s ssa-block\">%s</span>", s, s)
   910  }
   911  
   912  func blockLongHTML(b *BasicBlock) string {
   913  	var kind string
   914  	var term Instruction
   915  	if len(b.Instrs) > 0 {
   916  		term = b.Control()
   917  		kind = opName(term)
   918  	}
   919  	// TODO: improve this for HTML?
   920  	s := fmt.Sprintf("<span class=\"b%d ssa-block\">%s</span>", b.Index, kind)
   921  
   922  	if term != nil {
   923  		ops := term.Operands(nil)
   924  		if len(ops) > 0 {
   925  			var ss []string
   926  			for _, op := range ops {
   927  				ss = append(ss, valueHTML(*op))
   928  			}
   929  			s += " " + strings.Join(ss, ", ")
   930  		}
   931  	}
   932  	if len(b.Succs) > 0 {
   933  		s += " &#8594;" // right arrow
   934  		for _, c := range b.Succs {
   935  			s += " " + blockHTML(c)
   936  		}
   937  	}
   938  	return s
   939  }
   940  
   941  func funcHTML(f *Function, phase string, dot *dotWriter) string {
   942  	buf := new(bytes.Buffer)
   943  	if dot != nil {
   944  		dot.writeFuncSVG(buf, phase, f)
   945  	}
   946  	fmt.Fprint(buf, "<code>")
   947  	p := htmlFuncPrinter{w: buf}
   948  	fprintFunc(p, f)
   949  
   950  	// fprintFunc(&buf, f) // TODO: HTML, not text, <br /> for line breaks, etc.
   951  	fmt.Fprint(buf, "</code>")
   952  	return buf.String()
   953  }
   954  
   955  type htmlFuncPrinter struct {
   956  	w io.Writer
   957  }
   958  
   959  func (p htmlFuncPrinter) startBlock(b *BasicBlock, reachable bool) {
   960  	var dead string
   961  	if !reachable {
   962  		dead = "dead-block"
   963  	}
   964  	fmt.Fprintf(p.w, "<ul class=\"%s ssa-print-func %s\">", b, dead)
   965  	fmt.Fprintf(p.w, "<li class=\"ssa-start-block\">%s:", blockHTML(b))
   966  	if len(b.Preds) > 0 {
   967  		io.WriteString(p.w, " &#8592;") // left arrow
   968  		for _, pred := range b.Preds {
   969  			fmt.Fprintf(p.w, " %s", blockHTML(pred))
   970  		}
   971  	}
   972  	if len(b.Instrs) > 0 {
   973  		io.WriteString(p.w, `<button onclick="hideBlock(this)">-</button>`)
   974  	}
   975  	io.WriteString(p.w, "</li>")
   976  	if len(b.Instrs) > 0 { // start list of values
   977  		io.WriteString(p.w, "<li class=\"ssa-value-list\">")
   978  		io.WriteString(p.w, "<ul>")
   979  	}
   980  }
   981  
   982  func (p htmlFuncPrinter) endBlock(b *BasicBlock) {
   983  	if len(b.Instrs) > 0 { // end list of values
   984  		io.WriteString(p.w, "</ul>")
   985  		io.WriteString(p.w, "</li>")
   986  	}
   987  	io.WriteString(p.w, "<li class=\"ssa-end-block\">")
   988  	fmt.Fprint(p.w, blockLongHTML(b))
   989  	io.WriteString(p.w, "</li>")
   990  	io.WriteString(p.w, "</ul>")
   991  }
   992  
   993  func (p htmlFuncPrinter) value(v Node, live bool) {
   994  	var dead string
   995  	if !live {
   996  		dead = "dead-value"
   997  	}
   998  	fmt.Fprintf(p.w, "<li class=\"ssa-long-value %s\">", dead)
   999  	fmt.Fprint(p.w, valueLongHTML(v))
  1000  	io.WriteString(p.w, "</li>")
  1001  }
  1002  
  1003  func (p htmlFuncPrinter) startDepCycle() {
  1004  	fmt.Fprintln(p.w, "<span class=\"depcycle\">")
  1005  }
  1006  
  1007  func (p htmlFuncPrinter) endDepCycle() {
  1008  	fmt.Fprintln(p.w, "</span>")
  1009  }
  1010  
  1011  func (p htmlFuncPrinter) named(n string, vals []Value) {
  1012  	fmt.Fprintf(p.w, "<li>name %s: ", n)
  1013  	for _, val := range vals {
  1014  		fmt.Fprintf(p.w, "%s ", valueHTML(val))
  1015  	}
  1016  	fmt.Fprintf(p.w, "</li>")
  1017  }
  1018  
  1019  type dotWriter struct {
  1020  	path   string
  1021  	broken bool
  1022  }
  1023  
  1024  // newDotWriter returns non-nil value when mask is valid.
  1025  // dotWriter will generate SVGs only for the phases specified in the mask.
  1026  // mask can contain following patterns and combinations of them:
  1027  // *   - all of them;
  1028  // x-y - x through y, inclusive;
  1029  // x,y - x and y, but not the passes between.
  1030  func newDotWriter() *dotWriter {
  1031  	path, err := exec.LookPath("dot")
  1032  	if err != nil {
  1033  		fmt.Println(err)
  1034  		return nil
  1035  	}
  1036  	return &dotWriter{path: path}
  1037  }
  1038  
  1039  func (d *dotWriter) writeFuncSVG(w io.Writer, phase string, f *Function) {
  1040  	if d.broken {
  1041  		return
  1042  	}
  1043  	cmd := exec.Command(d.path, "-Tsvg")
  1044  	pipe, err := cmd.StdinPipe()
  1045  	if err != nil {
  1046  		d.broken = true
  1047  		fmt.Println(err)
  1048  		return
  1049  	}
  1050  	buf := new(bytes.Buffer)
  1051  	cmd.Stdout = buf
  1052  	bufErr := new(bytes.Buffer)
  1053  	cmd.Stderr = bufErr
  1054  	err = cmd.Start()
  1055  	if err != nil {
  1056  		d.broken = true
  1057  		fmt.Println(err)
  1058  		return
  1059  	}
  1060  	fmt.Fprint(pipe, `digraph "" { margin=0; size="4,40"; ranksep=.2; `)
  1061  	id := strings.Replace(phase, " ", "-", -1)
  1062  	fmt.Fprintf(pipe, `id="g_graph_%s";`, id)
  1063  	fmt.Fprintf(pipe, `node [style=filled,fillcolor=white,fontsize=16,fontname="Menlo,Times,serif",margin="0.01,0.03"];`)
  1064  	fmt.Fprintf(pipe, `edge [fontsize=16,fontname="Menlo,Times,serif"];`)
  1065  	for _, b := range f.Blocks {
  1066  		layout := ""
  1067  		fmt.Fprintf(pipe, `%v [label="%v%s\n%v",id="graph_node_%v_%v"];`, b, b, layout, b.Control().String(), id, b)
  1068  	}
  1069  	indexOf := make([]int, len(f.Blocks))
  1070  	for i, b := range f.Blocks {
  1071  		indexOf[b.Index] = i
  1072  	}
  1073  
  1074  	// XXX
  1075  	/*
  1076  		ponums := make([]int32, len(f.Blocks))
  1077  		_ = postorderWithNumbering(f, ponums)
  1078  		isBackEdge := func(from, to int) bool {
  1079  			return ponums[from] <= ponums[to]
  1080  		}
  1081  	*/
  1082  	isBackEdge := func(from, to int) bool { return false }
  1083  
  1084  	for _, b := range f.Blocks {
  1085  		for i, s := range b.Succs {
  1086  			style := "solid"
  1087  			color := "black"
  1088  			arrow := "vee"
  1089  			if isBackEdge(b.Index, s.Index) {
  1090  				color = "blue"
  1091  			}
  1092  			fmt.Fprintf(pipe, `%v -> %v [label=" %d ",style="%s",color="%s",arrowhead="%s"];`, b, s, i, style, color, arrow)
  1093  		}
  1094  	}
  1095  	fmt.Fprint(pipe, "}")
  1096  	pipe.Close()
  1097  	err = cmd.Wait()
  1098  	if err != nil {
  1099  		d.broken = true
  1100  		fmt.Printf("dot: %v\n%v\n", err, bufErr.String())
  1101  		return
  1102  	}
  1103  
  1104  	svgID := "svg_graph_" + id
  1105  	fmt.Fprintf(w, `<div class="zoom"><button onclick="return graphReduce('%s');">-</button> <button onclick="return graphEnlarge('%s');">+</button></div>`, svgID, svgID)
  1106  	// For now, an awful hack: edit the html as it passes through
  1107  	// our fingers, finding '<svg ' and injecting needed attributes after it.
  1108  	err = d.copyUntil(w, buf, `<svg `)
  1109  	if err != nil {
  1110  		fmt.Printf("injecting attributes: %v\n", err)
  1111  		return
  1112  	}
  1113  	fmt.Fprintf(w, ` id="%s" onload="makeDraggable(evt)" width="100%%" `, svgID)
  1114  	io.Copy(w, buf)
  1115  }
  1116  
  1117  func (d *dotWriter) copyUntil(w io.Writer, buf *bytes.Buffer, sep string) error {
  1118  	i := bytes.Index(buf.Bytes(), []byte(sep))
  1119  	if i == -1 {
  1120  		return fmt.Errorf("couldn't find dot sep %q", sep)
  1121  	}
  1122  	_, err := io.CopyN(w, buf, int64(i+len(sep)))
  1123  	return err
  1124  }