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

View as plain text