Source file src/cmd/compile/internal/ssa/html.go

Documentation: cmd/compile/internal/ssa

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

View as plain text