github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/cmd/compile/internal/ssa/html.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ssa
     6  
     7  import (
     8  	"bytes"
     9  	"cmd/internal/src"
    10  	"fmt"
    11  	"html"
    12  	"io"
    13  	"os"
    14  	"path/filepath"
    15  	"strconv"
    16  	"strings"
    17  )
    18  
    19  type HTMLWriter struct {
    20  	Logger
    21  	w    io.WriteCloser
    22  	path string
    23  }
    24  
    25  func NewHTMLWriter(path string, logger Logger, funcname string) *HTMLWriter {
    26  	out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
    27  	if err != nil {
    28  		logger.Fatalf(src.NoXPos, "%v", err)
    29  	}
    30  	pwd, err := os.Getwd()
    31  	if err != nil {
    32  		logger.Fatalf(src.NoXPos, "%v", err)
    33  	}
    34  	html := HTMLWriter{w: out, Logger: logger, path: filepath.Join(pwd, path)}
    35  	html.start(funcname)
    36  	return &html
    37  }
    38  
    39  func (w *HTMLWriter) start(name string) {
    40  	if w == nil {
    41  		return
    42  	}
    43  	w.WriteString("<html>")
    44  	w.WriteString(`<head>
    45  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    46  <style>
    47  
    48  body {
    49      font-size: 14px;
    50      font-family: Arial, sans-serif;
    51  }
    52  
    53  h1 {
    54      font-size: 18px;
    55      display: inline-block;
    56      margin: 0 1em .5em 0;
    57  }
    58  
    59  #helplink {
    60      display: inline-block;
    61  }
    62  
    63  #help {
    64      display: none;
    65  }
    66  
    67  .stats {
    68      font-size: 60%;
    69  }
    70  
    71  table {
    72      border: 1px solid black;
    73      table-layout: fixed;
    74      width: 300px;
    75  }
    76  
    77  th, td {
    78      border: 1px solid black;
    79      overflow: hidden;
    80      width: 400px;
    81      vertical-align: top;
    82      padding: 5px;
    83  }
    84  
    85  td > h2 {
    86      cursor: pointer;
    87      font-size: 120%;
    88  }
    89  
    90  td.collapsed {
    91      font-size: 12px;
    92      width: 12px;
    93      border: 0px;
    94      padding: 0;
    95      cursor: pointer;
    96      background: #fafafa;
    97  }
    98  
    99  td.collapsed  div {
   100       -moz-transform: rotate(-90.0deg);  /* FF3.5+ */
   101         -o-transform: rotate(-90.0deg);  /* Opera 10.5 */
   102    -webkit-transform: rotate(-90.0deg);  /* Saf3.1+, Chrome */
   103               filter:  progid:DXImageTransform.Microsoft.BasicImage(rotation=0.083);  /* IE6,IE7 */
   104           -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0.083)"; /* IE8 */
   105           margin-top: 10.3em;
   106           margin-left: -10em;
   107           margin-right: -10em;
   108           text-align: right;
   109  }
   110  
   111  code, pre, .lines, .ast {
   112      font-family: Menlo, monospace;
   113      font-size: 12px;
   114  }
   115  
   116  pre {
   117      -moz-tab-size: 4;
   118      -o-tab-size:   4;
   119      tab-size:      4;
   120  }
   121  
   122  .allow-x-scroll {
   123      overflow-x: scroll;
   124  }
   125  
   126  .lines {
   127      float: left;
   128      overflow: hidden;
   129      text-align: right;
   130  }
   131  
   132  .lines div {
   133      padding-right: 10px;
   134      color: gray;
   135  }
   136  
   137  div.line-number {
   138      font-size: 12px;
   139  }
   140  
   141  .ast {
   142      white-space: nowrap;
   143  }
   144  
   145  td.ssa-prog {
   146      width: 600px;
   147      word-wrap: break-word;
   148  }
   149  
   150  li {
   151      list-style-type: none;
   152  }
   153  
   154  li.ssa-long-value {
   155      text-indent: -2em;  /* indent wrapped lines */
   156  }
   157  
   158  li.ssa-value-list {
   159      display: inline;
   160  }
   161  
   162  li.ssa-start-block {
   163      padding: 0;
   164      margin: 0;
   165  }
   166  
   167  li.ssa-end-block {
   168      padding: 0;
   169      margin: 0;
   170  }
   171  
   172  ul.ssa-print-func {
   173      padding-left: 0;
   174  }
   175  
   176  dl.ssa-gen {
   177      padding-left: 0;
   178  }
   179  
   180  dt.ssa-prog-src {
   181      padding: 0;
   182      margin: 0;
   183      float: left;
   184      width: 4em;
   185  }
   186  
   187  dd.ssa-prog {
   188      padding: 0;
   189      margin-right: 0;
   190      margin-left: 4em;
   191  }
   192  
   193  .dead-value {
   194      color: gray;
   195  }
   196  
   197  .dead-block {
   198      opacity: 0.5;
   199  }
   200  
   201  .depcycle {
   202      font-style: italic;
   203  }
   204  
   205  .line-number {
   206      font-size: 11px;
   207  }
   208  
   209  .no-line-number {
   210      font-size: 11px;
   211      color: gray;
   212  }
   213  
   214  .highlight-aquamarine     { background-color: aquamarine; }
   215  .highlight-coral          { background-color: coral; }
   216  .highlight-lightpink      { background-color: lightpink; }
   217  .highlight-lightsteelblue { background-color: lightsteelblue; }
   218  .highlight-palegreen      { background-color: palegreen; }
   219  .highlight-skyblue        { background-color: skyblue; }
   220  .highlight-lightgray      { background-color: lightgray; }
   221  .highlight-yellow         { background-color: yellow; }
   222  .highlight-lime           { background-color: lime; }
   223  .highlight-khaki          { background-color: khaki; }
   224  .highlight-aqua           { background-color: aqua; }
   225  .highlight-salmon         { background-color: salmon; }
   226  
   227  .outline-blue           { outline: blue solid 2px; }
   228  .outline-red            { outline: red solid 2px; }
   229  .outline-blueviolet     { outline: blueviolet solid 2px; }
   230  .outline-darkolivegreen { outline: darkolivegreen solid 2px; }
   231  .outline-fuchsia        { outline: fuchsia solid 2px; }
   232  .outline-sienna         { outline: sienna solid 2px; }
   233  .outline-gold           { outline: gold solid 2px; }
   234  .outline-orangered      { outline: orangered solid 2px; }
   235  .outline-teal           { outline: teal solid 2px; }
   236  .outline-maroon         { outline: maroon solid 2px; }
   237  .outline-black          { outline: black solid 2px; }
   238  
   239  </style>
   240  
   241  <script type="text/javascript">
   242  // ordered list of all available highlight colors
   243  var highlights = [
   244      "highlight-aquamarine",
   245      "highlight-coral",
   246      "highlight-lightpink",
   247      "highlight-lightsteelblue",
   248      "highlight-palegreen",
   249      "highlight-skyblue",
   250      "highlight-lightgray",
   251      "highlight-yellow",
   252      "highlight-lime",
   253      "highlight-khaki",
   254      "highlight-aqua",
   255      "highlight-salmon"
   256  ];
   257  
   258  // state: which value is highlighted this color?
   259  var highlighted = {};
   260  for (var i = 0; i < highlights.length; i++) {
   261      highlighted[highlights[i]] = "";
   262  }
   263  
   264  // ordered list of all available outline colors
   265  var outlines = [
   266      "outline-blue",
   267      "outline-red",
   268      "outline-blueviolet",
   269      "outline-darkolivegreen",
   270      "outline-fuchsia",
   271      "outline-sienna",
   272      "outline-gold",
   273      "outline-orangered",
   274      "outline-teal",
   275      "outline-maroon",
   276      "outline-black"
   277  ];
   278  
   279  // state: which value is outlined this color?
   280  var outlined = {};
   281  for (var i = 0; i < outlines.length; i++) {
   282      outlined[outlines[i]] = "";
   283  }
   284  
   285  window.onload = function() {
   286      var ssaElemClicked = function(elem, event, selections, selected) {
   287          event.stopPropagation();
   288  
   289          // TODO: pushState with updated state and read it on page load,
   290          // so that state can survive across reloads
   291  
   292          // find all values with the same name
   293          var c = elem.classList.item(0);
   294          var x = document.getElementsByClassName(c);
   295  
   296          // if selected, remove selections from all of them
   297          // otherwise, attempt to add
   298  
   299          var remove = "";
   300          for (var i = 0; i < selections.length; i++) {
   301              var color = selections[i];
   302              if (selected[color] == c) {
   303                  remove = color;
   304                  break;
   305              }
   306          }
   307  
   308          if (remove != "") {
   309              for (var i = 0; i < x.length; i++) {
   310                  x[i].classList.remove(remove);
   311              }
   312              selected[remove] = "";
   313              return;
   314          }
   315  
   316          // we're adding a selection
   317          // find first available color
   318          var avail = "";
   319          for (var i = 0; i < selections.length; i++) {
   320              var color = selections[i];
   321              if (selected[color] == "") {
   322                  avail = color;
   323                  break;
   324              }
   325          }
   326          if (avail == "") {
   327              alert("out of selection colors; go add more");
   328              return;
   329          }
   330  
   331          // set that as the selection
   332          for (var i = 0; i < x.length; i++) {
   333              x[i].classList.add(avail);
   334          }
   335          selected[avail] = c;
   336      };
   337  
   338      var ssaValueClicked = function(event) {
   339          ssaElemClicked(this, event, highlights, highlighted);
   340      };
   341  
   342      var ssaBlockClicked = function(event) {
   343          ssaElemClicked(this, event, outlines, outlined);
   344      };
   345  
   346      var ssavalues = document.getElementsByClassName("ssa-value");
   347      for (var i = 0; i < ssavalues.length; i++) {
   348          ssavalues[i].addEventListener('click', ssaValueClicked);
   349      }
   350  
   351      var ssalongvalues = document.getElementsByClassName("ssa-long-value");
   352      for (var i = 0; i < ssalongvalues.length; i++) {
   353          // don't attach listeners to li nodes, just the spans they contain
   354          if (ssalongvalues[i].nodeName == "SPAN") {
   355              ssalongvalues[i].addEventListener('click', ssaValueClicked);
   356          }
   357      }
   358  
   359      var ssablocks = document.getElementsByClassName("ssa-block");
   360      for (var i = 0; i < ssablocks.length; i++) {
   361          ssablocks[i].addEventListener('click', ssaBlockClicked);
   362      }
   363  
   364      var lines = document.getElementsByClassName("line-number");
   365      for (var i = 0; i < lines.length; i++) {
   366          lines[i].addEventListener('click', ssaValueClicked);
   367      }
   368  
   369      // Contains phase names which are expanded by default. Other columns are collapsed.
   370      var expandedDefault = [
   371          "start",
   372          "deadcode",
   373          "opt",
   374          "lower",
   375          "late deadcode",
   376          "regalloc",
   377          "genssa",
   378      ];
   379  
   380      function toggler(phase) {
   381          return function() {
   382              toggle_cell(phase+'-col');
   383              toggle_cell(phase+'-exp');
   384          };
   385      }
   386  
   387      function toggle_cell(id) {
   388          var e = document.getElementById(id);
   389          if (e.style.display == 'table-cell') {
   390              e.style.display = 'none';
   391          } else {
   392              e.style.display = 'table-cell';
   393          }
   394      }
   395  
   396      // Go through all columns and collapse needed phases.
   397      var td = document.getElementsByTagName("td");
   398      for (var i = 0; i < td.length; i++) {
   399          var id = td[i].id;
   400          var phase = id.substr(0, id.length-4);
   401          var show = expandedDefault.indexOf(phase) !== -1
   402          if (id.endsWith("-exp")) {
   403              var h2 = td[i].getElementsByTagName("h2");
   404              if (h2 && h2[0]) {
   405                  h2[0].addEventListener('click', toggler(phase));
   406              }
   407          } else {
   408              td[i].addEventListener('click', toggler(phase));
   409          }
   410          if (id.endsWith("-col") && show || id.endsWith("-exp") && !show) {
   411              td[i].style.display = 'none';
   412              continue;
   413          }
   414          td[i].style.display = 'table-cell';
   415      }
   416  };
   417  
   418  function toggle_visibility(id) {
   419      var e = document.getElementById(id);
   420      if (e.style.display == 'block') {
   421          e.style.display = 'none';
   422      } else {
   423          e.style.display = 'block';
   424      }
   425  }
   426  </script>
   427  
   428  </head>`)
   429  	w.WriteString("<body>")
   430  	w.WriteString("<h1>")
   431  	w.WriteString(html.EscapeString(name))
   432  	w.WriteString("</h1>")
   433  	w.WriteString(`
   434  <a href="#" onclick="toggle_visibility('help');" id="helplink">help</a>
   435  <div id="help">
   436  
   437  <p>
   438  Click on a value or block to toggle highlighting of that value/block
   439  and its uses.  (Values and blocks are highlighted by ID, and IDs of
   440  dead items may be reused, so not all highlights necessarily correspond
   441  to the clicked item.)
   442  </p>
   443  
   444  <p>
   445  Faded out values and blocks are dead code that has not been eliminated.
   446  </p>
   447  
   448  <p>
   449  Values printed in italics have a dependency cycle.
   450  </p>
   451  
   452  </div>
   453  `)
   454  	w.WriteString("<table>")
   455  	w.WriteString("<tr>")
   456  }
   457  
   458  func (w *HTMLWriter) Close() {
   459  	if w == nil {
   460  		return
   461  	}
   462  	io.WriteString(w.w, "</tr>")
   463  	io.WriteString(w.w, "</table>")
   464  	io.WriteString(w.w, "</body>")
   465  	io.WriteString(w.w, "</html>")
   466  	w.w.Close()
   467  	fmt.Printf("dumped SSA to %v\n", w.path)
   468  }
   469  
   470  // WriteFunc writes f in a column headed by title.
   471  // phase is used for collapsing columns and should be unique across the table.
   472  func (w *HTMLWriter) WriteFunc(phase, title string, f *Func) {
   473  	if w == nil {
   474  		return // avoid generating HTML just to discard it
   475  	}
   476  	w.WriteColumn(phase, title, "", f.HTML())
   477  	// TODO: Add visual representation of f's CFG.
   478  }
   479  
   480  // FuncLines contains source code for a function to be displayed
   481  // in sources column.
   482  type FuncLines struct {
   483  	Filename    string
   484  	StartLineno uint
   485  	Lines       []string
   486  }
   487  
   488  // ByTopo sorts topologically: target function is on top,
   489  // followed by inlined functions sorted by filename and line numbers.
   490  type ByTopo []*FuncLines
   491  
   492  func (x ByTopo) Len() int      { return len(x) }
   493  func (x ByTopo) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
   494  func (x ByTopo) Less(i, j int) bool {
   495  	a := x[i]
   496  	b := x[j]
   497  	if a.Filename == b.Filename {
   498  		return a.StartLineno < b.StartLineno
   499  	}
   500  	return a.Filename < b.Filename
   501  }
   502  
   503  // WriteSources writes lines as source code in a column headed by title.
   504  // phase is used for collapsing columns and should be unique across the table.
   505  func (w *HTMLWriter) WriteSources(phase string, all []*FuncLines) {
   506  	if w == nil {
   507  		return // avoid generating HTML just to discard it
   508  	}
   509  	var buf bytes.Buffer
   510  	fmt.Fprint(&buf, "<div class=\"lines\" style=\"width: 8%\">")
   511  	filename := ""
   512  	for _, fl := range all {
   513  		fmt.Fprint(&buf, "<div>&nbsp;</div>")
   514  		if filename != fl.Filename {
   515  			fmt.Fprint(&buf, "<div>&nbsp;</div>")
   516  			filename = fl.Filename
   517  		}
   518  		for i := range fl.Lines {
   519  			ln := int(fl.StartLineno) + i
   520  			fmt.Fprintf(&buf, "<div class=\"l%v line-number\">%v</div>", ln, ln)
   521  		}
   522  	}
   523  	fmt.Fprint(&buf, "</div><div style=\"width: 92%\"><pre>")
   524  	filename = ""
   525  	for _, fl := range all {
   526  		fmt.Fprint(&buf, "<div>&nbsp;</div>")
   527  		if filename != fl.Filename {
   528  			fmt.Fprintf(&buf, "<div><strong>%v</strong></div>", fl.Filename)
   529  			filename = fl.Filename
   530  		}
   531  		for i, line := range fl.Lines {
   532  			ln := int(fl.StartLineno) + i
   533  			var escaped string
   534  			if strings.TrimSpace(line) == "" {
   535  				escaped = "&nbsp;"
   536  			} else {
   537  				escaped = html.EscapeString(line)
   538  			}
   539  			fmt.Fprintf(&buf, "<div class=\"l%v line-number\">%v</div>", ln, escaped)
   540  		}
   541  	}
   542  	fmt.Fprint(&buf, "</pre></div>")
   543  	w.WriteColumn(phase, phase, "allow-x-scroll", buf.String())
   544  }
   545  
   546  func (w *HTMLWriter) WriteAST(phase string, buf *bytes.Buffer) {
   547  	if w == nil {
   548  		return // avoid generating HTML just to discard it
   549  	}
   550  	lines := strings.Split(buf.String(), "\n")
   551  	var out bytes.Buffer
   552  
   553  	fmt.Fprint(&out, "<div>")
   554  	for _, l := range lines {
   555  		l = strings.TrimSpace(l)
   556  		var escaped string
   557  		var lineNo string
   558  		if l == "" {
   559  			escaped = "&nbsp;"
   560  		} else {
   561  			if strings.HasPrefix(l, "buildssa") {
   562  				escaped = fmt.Sprintf("<b>%v</b>", l)
   563  			} else {
   564  				// Parse the line number from the format l(123).
   565  				idx := strings.Index(l, " l(")
   566  				if idx != -1 {
   567  					subl := l[idx+3:]
   568  					idxEnd := strings.Index(subl, ")")
   569  					if idxEnd != -1 {
   570  						if _, err := strconv.Atoi(subl[:idxEnd]); err == nil {
   571  							lineNo = subl[:idxEnd]
   572  						}
   573  					}
   574  				}
   575  				escaped = html.EscapeString(l)
   576  			}
   577  		}
   578  		if lineNo != "" {
   579  			fmt.Fprintf(&out, "<div class=\"l%v line-number ast\">%v</div>", lineNo, escaped)
   580  		} else {
   581  			fmt.Fprintf(&out, "<div class=\"ast\">%v</div>", escaped)
   582  		}
   583  	}
   584  	fmt.Fprint(&out, "</div>")
   585  	w.WriteColumn(phase, phase, "allow-x-scroll", out.String())
   586  }
   587  
   588  // WriteColumn writes raw HTML in a column headed by title.
   589  // It is intended for pre- and post-compilation log output.
   590  func (w *HTMLWriter) WriteColumn(phase, title, class, html string) {
   591  	if w == nil {
   592  		return
   593  	}
   594  	id := strings.Replace(phase, " ", "-", -1)
   595  	// collapsed column
   596  	w.Printf("<td id=\"%v-col\" class=\"collapsed\"><div>%v</div></td>", id, phase)
   597  
   598  	if class == "" {
   599  		w.Printf("<td id=\"%v-exp\">", id)
   600  	} else {
   601  		w.Printf("<td id=\"%v-exp\" class=\"%v\">", id, class)
   602  	}
   603  	w.WriteString("<h2>" + title + "</h2>")
   604  	w.WriteString(html)
   605  	w.WriteString("</td>")
   606  }
   607  
   608  func (w *HTMLWriter) Printf(msg string, v ...interface{}) {
   609  	if _, err := fmt.Fprintf(w.w, msg, v...); err != nil {
   610  		w.Fatalf(src.NoXPos, "%v", err)
   611  	}
   612  }
   613  
   614  func (w *HTMLWriter) WriteString(s string) {
   615  	if _, err := io.WriteString(w.w, s); err != nil {
   616  		w.Fatalf(src.NoXPos, "%v", err)
   617  	}
   618  }
   619  
   620  func (v *Value) HTML() string {
   621  	// TODO: Using the value ID as the class ignores the fact
   622  	// that value IDs get recycled and that some values
   623  	// are transmuted into other values.
   624  	s := v.String()
   625  	return fmt.Sprintf("<span class=\"%s ssa-value\">%s</span>", s, s)
   626  }
   627  
   628  func (v *Value) LongHTML() string {
   629  	// TODO: Any intra-value formatting?
   630  	// I'm wary of adding too much visual noise,
   631  	// but a little bit might be valuable.
   632  	// We already have visual noise in the form of punctuation
   633  	// maybe we could replace some of that with formatting.
   634  	s := fmt.Sprintf("<span class=\"%s ssa-long-value\">", v.String())
   635  
   636  	linenumber := "<span class=\"no-line-number\">(?)</span>"
   637  	if v.Pos.IsKnown() {
   638  		linenumber = fmt.Sprintf("<span class=\"l%v line-number\">(%s)</span>", v.Pos.LineNumber(), v.Pos.LineNumberHTML())
   639  	}
   640  
   641  	s += fmt.Sprintf("%s %s = %s", v.HTML(), linenumber, v.Op.String())
   642  
   643  	s += " &lt;" + html.EscapeString(v.Type.String()) + "&gt;"
   644  	s += html.EscapeString(v.auxString())
   645  	for _, a := range v.Args {
   646  		s += fmt.Sprintf(" %s", a.HTML())
   647  	}
   648  	r := v.Block.Func.RegAlloc
   649  	if int(v.ID) < len(r) && r[v.ID] != nil {
   650  		s += " : " + html.EscapeString(r[v.ID].String())
   651  	}
   652  	var names []string
   653  	for name, values := range v.Block.Func.NamedValues {
   654  		for _, value := range values {
   655  			if value == v {
   656  				names = append(names, name.String())
   657  				break // drop duplicates.
   658  			}
   659  		}
   660  	}
   661  	if len(names) != 0 {
   662  		s += " (" + strings.Join(names, ", ") + ")"
   663  	}
   664  
   665  	s += "</span>"
   666  	return s
   667  }
   668  
   669  func (b *Block) HTML() string {
   670  	// TODO: Using the value ID as the class ignores the fact
   671  	// that value IDs get recycled and that some values
   672  	// are transmuted into other values.
   673  	s := html.EscapeString(b.String())
   674  	return fmt.Sprintf("<span class=\"%s ssa-block\">%s</span>", s, s)
   675  }
   676  
   677  func (b *Block) LongHTML() string {
   678  	// TODO: improve this for HTML?
   679  	s := fmt.Sprintf("<span class=\"%s ssa-block\">%s</span>", html.EscapeString(b.String()), html.EscapeString(b.Kind.String()))
   680  	if b.Aux != nil {
   681  		s += html.EscapeString(fmt.Sprintf(" {%v}", b.Aux))
   682  	}
   683  	if b.Control != nil {
   684  		s += fmt.Sprintf(" %s", b.Control.HTML())
   685  	}
   686  	if len(b.Succs) > 0 {
   687  		s += " &#8594;" // right arrow
   688  		for _, e := range b.Succs {
   689  			c := e.b
   690  			s += " " + c.HTML()
   691  		}
   692  	}
   693  	switch b.Likely {
   694  	case BranchUnlikely:
   695  		s += " (unlikely)"
   696  	case BranchLikely:
   697  		s += " (likely)"
   698  	}
   699  	if b.Pos.IsKnown() {
   700  		// TODO does not begin to deal with the full complexity of line numbers.
   701  		// Maybe we want a string/slice instead, of outer-inner when inlining.
   702  		s += fmt.Sprintf(" <span class=\"l%v line-number\">(%s)</span>", b.Pos.LineNumber(), b.Pos.LineNumberHTML())
   703  	}
   704  	return s
   705  }
   706  
   707  func (f *Func) HTML() string {
   708  	var buf bytes.Buffer
   709  	fmt.Fprint(&buf, "<code>")
   710  	p := htmlFuncPrinter{w: &buf}
   711  	fprintFunc(p, f)
   712  
   713  	// fprintFunc(&buf, f) // TODO: HTML, not text, <br /> for line breaks, etc.
   714  	fmt.Fprint(&buf, "</code>")
   715  	return buf.String()
   716  }
   717  
   718  type htmlFuncPrinter struct {
   719  	w io.Writer
   720  }
   721  
   722  func (p htmlFuncPrinter) header(f *Func) {}
   723  
   724  func (p htmlFuncPrinter) startBlock(b *Block, reachable bool) {
   725  	// TODO: Make blocks collapsable?
   726  	var dead string
   727  	if !reachable {
   728  		dead = "dead-block"
   729  	}
   730  	fmt.Fprintf(p.w, "<ul class=\"%s ssa-print-func %s\">", b, dead)
   731  	fmt.Fprintf(p.w, "<li class=\"ssa-start-block\">%s:", b.HTML())
   732  	if len(b.Preds) > 0 {
   733  		io.WriteString(p.w, " &#8592;") // left arrow
   734  		for _, e := range b.Preds {
   735  			pred := e.b
   736  			fmt.Fprintf(p.w, " %s", pred.HTML())
   737  		}
   738  	}
   739  	io.WriteString(p.w, "</li>")
   740  	if len(b.Values) > 0 { // start list of values
   741  		io.WriteString(p.w, "<li class=\"ssa-value-list\">")
   742  		io.WriteString(p.w, "<ul>")
   743  	}
   744  }
   745  
   746  func (p htmlFuncPrinter) endBlock(b *Block) {
   747  	if len(b.Values) > 0 { // end list of values
   748  		io.WriteString(p.w, "</ul>")
   749  		io.WriteString(p.w, "</li>")
   750  	}
   751  	io.WriteString(p.w, "<li class=\"ssa-end-block\">")
   752  	fmt.Fprint(p.w, b.LongHTML())
   753  	io.WriteString(p.w, "</li>")
   754  	io.WriteString(p.w, "</ul>")
   755  	// io.WriteString(p.w, "</span>")
   756  }
   757  
   758  func (p htmlFuncPrinter) value(v *Value, live bool) {
   759  	var dead string
   760  	if !live {
   761  		dead = "dead-value"
   762  	}
   763  	fmt.Fprintf(p.w, "<li class=\"ssa-long-value %s\">", dead)
   764  	fmt.Fprint(p.w, v.LongHTML())
   765  	io.WriteString(p.w, "</li>")
   766  }
   767  
   768  func (p htmlFuncPrinter) startDepCycle() {
   769  	fmt.Fprintln(p.w, "<span class=\"depcycle\">")
   770  }
   771  
   772  func (p htmlFuncPrinter) endDepCycle() {
   773  	fmt.Fprintln(p.w, "</span>")
   774  }
   775  
   776  func (p htmlFuncPrinter) named(n LocalSlot, vals []*Value) {
   777  	fmt.Fprintf(p.w, "<li>name %s: ", n)
   778  	for _, val := range vals {
   779  		fmt.Fprintf(p.w, "%s ", val.HTML())
   780  	}
   781  	fmt.Fprintf(p.w, "</li>")
   782  }