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

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

View as plain text