github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/ssa/html.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ssa
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"html"
    11  	"io"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"github.com/go-asm/go/cmd/src"
    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  // ByTopo sorts topologically: target function is on top,
   832  // followed by inlined functions sorted by filename and line numbers.
   833  type ByTopo []*FuncLines
   834  
   835  func (x ByTopo) Len() int      { return len(x) }
   836  func (x ByTopo) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
   837  func (x ByTopo) Less(i, j int) bool {
   838  	a := x[i]
   839  	b := x[j]
   840  	if a.Filename == b.Filename {
   841  		return a.StartLineno < b.StartLineno
   842  	}
   843  	return a.Filename < b.Filename
   844  }
   845  
   846  // WriteSources writes lines as source code in a column headed by title.
   847  // phase is used for collapsing columns and should be unique across the table.
   848  func (w *HTMLWriter) WriteSources(phase string, all []*FuncLines) {
   849  	if w == nil {
   850  		return // avoid generating HTML just to discard it
   851  	}
   852  	var buf strings.Builder
   853  	fmt.Fprint(&buf, "<div class=\"lines\" style=\"width: 8%\">")
   854  	filename := ""
   855  	for _, fl := range all {
   856  		fmt.Fprint(&buf, "<div>&nbsp;</div>")
   857  		if filename != fl.Filename {
   858  			fmt.Fprint(&buf, "<div>&nbsp;</div>")
   859  			filename = fl.Filename
   860  		}
   861  		for i := range fl.Lines {
   862  			ln := int(fl.StartLineno) + i
   863  			fmt.Fprintf(&buf, "<div class=\"l%v line-number\">%v</div>", ln, ln)
   864  		}
   865  	}
   866  	fmt.Fprint(&buf, "</div><div style=\"width: 92%\"><pre>")
   867  	filename = ""
   868  	for _, fl := range all {
   869  		fmt.Fprint(&buf, "<div>&nbsp;</div>")
   870  		if filename != fl.Filename {
   871  			fmt.Fprintf(&buf, "<div><strong>%v</strong></div>", fl.Filename)
   872  			filename = fl.Filename
   873  		}
   874  		for i, line := range fl.Lines {
   875  			ln := int(fl.StartLineno) + i
   876  			var escaped string
   877  			if strings.TrimSpace(line) == "" {
   878  				escaped = "&nbsp;"
   879  			} else {
   880  				escaped = html.EscapeString(line)
   881  			}
   882  			fmt.Fprintf(&buf, "<div class=\"l%v line-number\">%v</div>", ln, escaped)
   883  		}
   884  	}
   885  	fmt.Fprint(&buf, "</pre></div>")
   886  	w.WriteColumn(phase, phase, "allow-x-scroll", buf.String())
   887  }
   888  
   889  func (w *HTMLWriter) WriteAST(phase string, buf *bytes.Buffer) {
   890  	if w == nil {
   891  		return // avoid generating HTML just to discard it
   892  	}
   893  	lines := strings.Split(buf.String(), "\n")
   894  	var out strings.Builder
   895  
   896  	fmt.Fprint(&out, "<div>")
   897  	for _, l := range lines {
   898  		l = strings.TrimSpace(l)
   899  		var escaped string
   900  		var lineNo string
   901  		if l == "" {
   902  			escaped = "&nbsp;"
   903  		} else {
   904  			if strings.HasPrefix(l, "buildssa") {
   905  				escaped = fmt.Sprintf("<b>%v</b>", l)
   906  			} else {
   907  				// Parse the line number from the format file:line:col.
   908  				// See the implementation in ir/fmt.go:dumpNodeHeader.
   909  				sl := strings.Split(l, ":")
   910  				if len(sl) >= 3 {
   911  					if _, err := strconv.Atoi(sl[len(sl)-2]); err == nil {
   912  						lineNo = sl[len(sl)-2]
   913  					}
   914  				}
   915  				escaped = html.EscapeString(l)
   916  			}
   917  		}
   918  		if lineNo != "" {
   919  			fmt.Fprintf(&out, "<div class=\"l%v line-number ast\">%v</div>", lineNo, escaped)
   920  		} else {
   921  			fmt.Fprintf(&out, "<div class=\"ast\">%v</div>", escaped)
   922  		}
   923  	}
   924  	fmt.Fprint(&out, "</div>")
   925  	w.WriteColumn(phase, phase, "allow-x-scroll", out.String())
   926  }
   927  
   928  // WriteColumn writes raw HTML in a column headed by title.
   929  // It is intended for pre- and post-compilation log output.
   930  func (w *HTMLWriter) WriteColumn(phase, title, class, html string) {
   931  	w.WriteMultiTitleColumn(phase, []string{title}, class, html)
   932  }
   933  
   934  func (w *HTMLWriter) WriteMultiTitleColumn(phase string, titles []string, class, html string) {
   935  	if w == nil {
   936  		return
   937  	}
   938  	id := strings.Replace(phase, " ", "-", -1)
   939  	// collapsed column
   940  	w.Printf("<td id=\"%v-col\" class=\"collapsed\"><div>%v</div></td>", id, phase)
   941  
   942  	if class == "" {
   943  		w.Printf("<td id=\"%v-exp\">", id)
   944  	} else {
   945  		w.Printf("<td id=\"%v-exp\" class=\"%v\">", id, class)
   946  	}
   947  	for _, title := range titles {
   948  		w.WriteString("<h2>" + title + "</h2>")
   949  	}
   950  	w.WriteString(html)
   951  	w.WriteString("</td>\n")
   952  }
   953  
   954  func (w *HTMLWriter) Printf(msg string, v ...interface{}) {
   955  	if _, err := fmt.Fprintf(w.w, msg, v...); err != nil {
   956  		w.Fatalf("%v", err)
   957  	}
   958  }
   959  
   960  func (w *HTMLWriter) WriteString(s string) {
   961  	if _, err := io.WriteString(w.w, s); err != nil {
   962  		w.Fatalf("%v", err)
   963  	}
   964  }
   965  
   966  func (v *Value) HTML() string {
   967  	// TODO: Using the value ID as the class ignores the fact
   968  	// that value IDs get recycled and that some values
   969  	// are transmuted into other values.
   970  	s := v.String()
   971  	return fmt.Sprintf("<span class=\"%s ssa-value\">%s</span>", s, s)
   972  }
   973  
   974  func (v *Value) LongHTML() string {
   975  	// TODO: Any intra-value formatting?
   976  	// I'm wary of adding too much visual noise,
   977  	// but a little bit might be valuable.
   978  	// We already have visual noise in the form of punctuation
   979  	// maybe we could replace some of that with formatting.
   980  	s := fmt.Sprintf("<span class=\"%s ssa-long-value\">", v.String())
   981  
   982  	linenumber := "<span class=\"no-line-number\">(?)</span>"
   983  	if v.Pos.IsKnown() {
   984  		linenumber = fmt.Sprintf("<span class=\"l%v line-number\">(%s)</span>", v.Pos.LineNumber(), v.Pos.LineNumberHTML())
   985  	}
   986  
   987  	s += fmt.Sprintf("%s %s = %s", v.HTML(), linenumber, v.Op.String())
   988  
   989  	s += " &lt;" + html.EscapeString(v.Type.String()) + "&gt;"
   990  	s += html.EscapeString(v.auxString())
   991  	for _, a := range v.Args {
   992  		s += fmt.Sprintf(" %s", a.HTML())
   993  	}
   994  	r := v.Block.Func.RegAlloc
   995  	if int(v.ID) < len(r) && r[v.ID] != nil {
   996  		s += " : " + html.EscapeString(r[v.ID].String())
   997  	}
   998  	if reg := v.Block.Func.tempRegs[v.ID]; reg != nil {
   999  		s += " tmp=" + reg.String()
  1000  	}
  1001  	var names []string
  1002  	for name, values := range v.Block.Func.NamedValues {
  1003  		for _, value := range values {
  1004  			if value == v {
  1005  				names = append(names, name.String())
  1006  				break // drop duplicates.
  1007  			}
  1008  		}
  1009  	}
  1010  	if len(names) != 0 {
  1011  		s += " (" + strings.Join(names, ", ") + ")"
  1012  	}
  1013  
  1014  	s += "</span>"
  1015  	return s
  1016  }
  1017  
  1018  func (b *Block) HTML() string {
  1019  	// TODO: Using the value ID as the class ignores the fact
  1020  	// that value IDs get recycled and that some values
  1021  	// are transmuted into other values.
  1022  	s := html.EscapeString(b.String())
  1023  	return fmt.Sprintf("<span class=\"%s ssa-block\">%s</span>", s, s)
  1024  }
  1025  
  1026  func (b *Block) LongHTML() string {
  1027  	// TODO: improve this for HTML?
  1028  	s := fmt.Sprintf("<span class=\"%s ssa-block\">%s</span>", html.EscapeString(b.String()), html.EscapeString(b.Kind.String()))
  1029  	if b.Aux != nil {
  1030  		s += html.EscapeString(fmt.Sprintf(" {%v}", b.Aux))
  1031  	}
  1032  	if t := b.AuxIntString(); t != "" {
  1033  		s += html.EscapeString(fmt.Sprintf(" [%v]", t))
  1034  	}
  1035  	for _, c := range b.ControlValues() {
  1036  		s += fmt.Sprintf(" %s", c.HTML())
  1037  	}
  1038  	if len(b.Succs) > 0 {
  1039  		s += " &#8594;" // right arrow
  1040  		for _, e := range b.Succs {
  1041  			c := e.b
  1042  			s += " " + c.HTML()
  1043  		}
  1044  	}
  1045  	switch b.Likely {
  1046  	case BranchUnlikely:
  1047  		s += " (unlikely)"
  1048  	case BranchLikely:
  1049  		s += " (likely)"
  1050  	}
  1051  	if b.Pos.IsKnown() {
  1052  		// TODO does not begin to deal with the full complexity of line numbers.
  1053  		// Maybe we want a string/slice instead, of outer-inner when inlining.
  1054  		s += fmt.Sprintf(" <span class=\"l%v line-number\">(%s)</span>", b.Pos.LineNumber(), b.Pos.LineNumberHTML())
  1055  	}
  1056  	return s
  1057  }
  1058  
  1059  func (f *Func) HTML(phase string, dot *dotWriter) string {
  1060  	buf := new(strings.Builder)
  1061  	if dot != nil {
  1062  		dot.writeFuncSVG(buf, phase, f)
  1063  	}
  1064  	fmt.Fprint(buf, "<code>")
  1065  	p := htmlFuncPrinter{w: buf}
  1066  	fprintFunc(p, f)
  1067  
  1068  	// fprintFunc(&buf, f) // TODO: HTML, not text, <br> for line breaks, etc.
  1069  	fmt.Fprint(buf, "</code>")
  1070  	return buf.String()
  1071  }
  1072  
  1073  func (d *dotWriter) writeFuncSVG(w io.Writer, phase string, f *Func) {
  1074  	if d.broken {
  1075  		return
  1076  	}
  1077  	if _, ok := d.phases[phase]; !ok {
  1078  		return
  1079  	}
  1080  	cmd := exec.Command(d.path, "-Tsvg")
  1081  	pipe, err := cmd.StdinPipe()
  1082  	if err != nil {
  1083  		d.broken = true
  1084  		fmt.Println(err)
  1085  		return
  1086  	}
  1087  	buf := new(bytes.Buffer)
  1088  	cmd.Stdout = buf
  1089  	bufErr := new(strings.Builder)
  1090  	cmd.Stderr = bufErr
  1091  	err = cmd.Start()
  1092  	if err != nil {
  1093  		d.broken = true
  1094  		fmt.Println(err)
  1095  		return
  1096  	}
  1097  	fmt.Fprint(pipe, `digraph "" { margin=0; ranksep=.2; `)
  1098  	id := strings.Replace(phase, " ", "-", -1)
  1099  	fmt.Fprintf(pipe, `id="g_graph_%s";`, id)
  1100  	fmt.Fprintf(pipe, `node [style=filled,fillcolor=white,fontsize=16,fontname="Menlo,Times,serif",margin="0.01,0.03"];`)
  1101  	fmt.Fprintf(pipe, `edge [fontsize=16,fontname="Menlo,Times,serif"];`)
  1102  	for i, b := range f.Blocks {
  1103  		if b.Kind == BlockInvalid {
  1104  			continue
  1105  		}
  1106  		layout := ""
  1107  		if f.laidout {
  1108  			layout = fmt.Sprintf(" #%d", i)
  1109  		}
  1110  		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())
  1111  	}
  1112  	indexOf := make([]int, f.NumBlocks())
  1113  	for i, b := range f.Blocks {
  1114  		indexOf[b.ID] = i
  1115  	}
  1116  	layoutDrawn := make([]bool, f.NumBlocks())
  1117  
  1118  	ponums := make([]int32, f.NumBlocks())
  1119  	_ = postorderWithNumbering(f, ponums)
  1120  	isBackEdge := func(from, to ID) bool {
  1121  		return ponums[from] <= ponums[to]
  1122  	}
  1123  
  1124  	for _, b := range f.Blocks {
  1125  		for i, s := range b.Succs {
  1126  			style := "solid"
  1127  			color := "black"
  1128  			arrow := "vee"
  1129  			if b.unlikelyIndex() == i {
  1130  				style = "dashed"
  1131  			}
  1132  			if f.laidout && indexOf[s.b.ID] == indexOf[b.ID]+1 {
  1133  				// Red color means ordered edge. It overrides other colors.
  1134  				arrow = "dotvee"
  1135  				layoutDrawn[s.b.ID] = true
  1136  			} else if isBackEdge(b.ID, s.b.ID) {
  1137  				color = "#2893ff"
  1138  			}
  1139  			fmt.Fprintf(pipe, `%v -> %v [label=" %d ",style="%s",color="%s",arrowhead="%s"];`, b, s.b, i, style, color, arrow)
  1140  		}
  1141  	}
  1142  	if f.laidout {
  1143  		fmt.Fprintln(pipe, `edge[constraint=false,color=gray,style=solid,arrowhead=dot];`)
  1144  		colors := [...]string{"#eea24f", "#f38385", "#f4d164", "#ca89fc", "gray"}
  1145  		ci := 0
  1146  		for i := 1; i < len(f.Blocks); i++ {
  1147  			if layoutDrawn[f.Blocks[i].ID] {
  1148  				continue
  1149  			}
  1150  			fmt.Fprintf(pipe, `%s -> %s [color="%s"];`, f.Blocks[i-1], f.Blocks[i], colors[ci])
  1151  			ci = (ci + 1) % len(colors)
  1152  		}
  1153  	}
  1154  	fmt.Fprint(pipe, "}")
  1155  	pipe.Close()
  1156  	err = cmd.Wait()
  1157  	if err != nil {
  1158  		d.broken = true
  1159  		fmt.Printf("dot: %v\n%v\n", err, bufErr.String())
  1160  		return
  1161  	}
  1162  
  1163  	svgID := "svg_graph_" + id
  1164  	fmt.Fprintf(w, `<div class="zoom"><button onclick="return graphReduce('%s');">-</button> <button onclick="return graphEnlarge('%s');">+</button></div>`, svgID, svgID)
  1165  	// For now, an awful hack: edit the html as it passes through
  1166  	// our fingers, finding '<svg ' and injecting needed attributes after it.
  1167  	err = d.copyUntil(w, buf, `<svg `)
  1168  	if err != nil {
  1169  		fmt.Printf("injecting attributes: %v\n", err)
  1170  		return
  1171  	}
  1172  	fmt.Fprintf(w, ` id="%s" onload="makeDraggable(evt)" `, svgID)
  1173  	io.Copy(w, buf)
  1174  }
  1175  
  1176  func (b *Block) unlikelyIndex() int {
  1177  	switch b.Likely {
  1178  	case BranchLikely:
  1179  		return 1
  1180  	case BranchUnlikely:
  1181  		return 0
  1182  	}
  1183  	return -1
  1184  }
  1185  
  1186  func (d *dotWriter) copyUntil(w io.Writer, buf *bytes.Buffer, sep string) error {
  1187  	i := bytes.Index(buf.Bytes(), []byte(sep))
  1188  	if i == -1 {
  1189  		return fmt.Errorf("couldn't find dot sep %q", sep)
  1190  	}
  1191  	_, err := io.CopyN(w, buf, int64(i+len(sep)))
  1192  	return err
  1193  }
  1194  
  1195  type htmlFuncPrinter struct {
  1196  	w io.Writer
  1197  }
  1198  
  1199  func (p htmlFuncPrinter) header(f *Func) {}
  1200  
  1201  func (p htmlFuncPrinter) startBlock(b *Block, reachable bool) {
  1202  	var dead string
  1203  	if !reachable {
  1204  		dead = "dead-block"
  1205  	}
  1206  	fmt.Fprintf(p.w, "<ul class=\"%s ssa-print-func %s\">", b, dead)
  1207  	fmt.Fprintf(p.w, "<li class=\"ssa-start-block\">%s:", b.HTML())
  1208  	if len(b.Preds) > 0 {
  1209  		io.WriteString(p.w, " &#8592;") // left arrow
  1210  		for _, e := range b.Preds {
  1211  			pred := e.b
  1212  			fmt.Fprintf(p.w, " %s", pred.HTML())
  1213  		}
  1214  	}
  1215  	if len(b.Values) > 0 {
  1216  		io.WriteString(p.w, `<button onclick="hideBlock(this)">-</button>`)
  1217  	}
  1218  	io.WriteString(p.w, "</li>")
  1219  	if len(b.Values) > 0 { // start list of values
  1220  		io.WriteString(p.w, "<li class=\"ssa-value-list\">")
  1221  		io.WriteString(p.w, "<ul>")
  1222  	}
  1223  }
  1224  
  1225  func (p htmlFuncPrinter) endBlock(b *Block, reachable bool) {
  1226  	if len(b.Values) > 0 { // end list of values
  1227  		io.WriteString(p.w, "</ul>")
  1228  		io.WriteString(p.w, "</li>")
  1229  	}
  1230  	io.WriteString(p.w, "<li class=\"ssa-end-block\">")
  1231  	fmt.Fprint(p.w, b.LongHTML())
  1232  	io.WriteString(p.w, "</li>")
  1233  	io.WriteString(p.w, "</ul>")
  1234  }
  1235  
  1236  func (p htmlFuncPrinter) value(v *Value, live bool) {
  1237  	var dead string
  1238  	if !live {
  1239  		dead = "dead-value"
  1240  	}
  1241  	fmt.Fprintf(p.w, "<li class=\"ssa-long-value %s\">", dead)
  1242  	fmt.Fprint(p.w, v.LongHTML())
  1243  	io.WriteString(p.w, "</li>")
  1244  }
  1245  
  1246  func (p htmlFuncPrinter) startDepCycle() {
  1247  	fmt.Fprintln(p.w, "<span class=\"depcycle\">")
  1248  }
  1249  
  1250  func (p htmlFuncPrinter) endDepCycle() {
  1251  	fmt.Fprintln(p.w, "</span>")
  1252  }
  1253  
  1254  func (p htmlFuncPrinter) named(n LocalSlot, vals []*Value) {
  1255  	fmt.Fprintf(p.w, "<li>name %s: ", n)
  1256  	for _, val := range vals {
  1257  		fmt.Fprintf(p.w, "%s ", val.HTML())
  1258  	}
  1259  	fmt.Fprintf(p.w, "</li>")
  1260  }
  1261  
  1262  type dotWriter struct {
  1263  	path   string
  1264  	broken bool
  1265  	phases map[string]bool // keys specify phases with CFGs
  1266  }
  1267  
  1268  // newDotWriter returns non-nil value when mask is valid.
  1269  // dotWriter will generate SVGs only for the phases specified in the mask.
  1270  // mask can contain following patterns and combinations of them:
  1271  // *   - all of them;
  1272  // x-y - x through y, inclusive;
  1273  // x,y - x and y, but not the passes between.
  1274  func newDotWriter(mask string) *dotWriter {
  1275  	if mask == "" {
  1276  		return nil
  1277  	}
  1278  	// User can specify phase name with _ instead of spaces.
  1279  	mask = strings.Replace(mask, "_", " ", -1)
  1280  	ph := make(map[string]bool)
  1281  	ranges := strings.Split(mask, ",")
  1282  	for _, r := range ranges {
  1283  		spl := strings.Split(r, "-")
  1284  		if len(spl) > 2 {
  1285  			fmt.Printf("range is not valid: %v\n", mask)
  1286  			return nil
  1287  		}
  1288  		var first, last int
  1289  		if mask == "*" {
  1290  			first = 0
  1291  			last = len(passes) - 1
  1292  		} else {
  1293  			first = passIdxByName(spl[0])
  1294  			last = passIdxByName(spl[len(spl)-1])
  1295  		}
  1296  		if first < 0 || last < 0 || first > last {
  1297  			fmt.Printf("range is not valid: %v\n", r)
  1298  			return nil
  1299  		}
  1300  		for p := first; p <= last; p++ {
  1301  			ph[passes[p].name] = true
  1302  		}
  1303  	}
  1304  
  1305  	path, err := exec.LookPath("dot")
  1306  	if err != nil {
  1307  		fmt.Println(err)
  1308  		return nil
  1309  	}
  1310  	return &dotWriter{path: path, phases: ph}
  1311  }
  1312  
  1313  func passIdxByName(name string) int {
  1314  	for i, p := range passes {
  1315  		if p.name == name {
  1316  			return i
  1317  		}
  1318  	}
  1319  	return -1
  1320  }