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

View as plain text