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

View as plain text