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

     1  // Copyright 2026 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 ir
     6  
     7  import (
     8  	"bufio"
     9  	"cmd/compile/internal/base"
    10  	"cmd/compile/internal/types"
    11  	"cmd/internal/src"
    12  	"crypto/sha256"
    13  	"encoding/hex"
    14  	"fmt"
    15  	"html"
    16  	"io"
    17  	"os"
    18  	"path/filepath"
    19  	"reflect"
    20  	"strings"
    21  )
    22  
    23  // An HTMLWriter dumps IR to multicolumn HTML, similar to what the
    24  // ssa backend does for GOSSAFUNC.  This is not the format used for
    25  // the ast column in GOSSAFUNC output.
    26  type HTMLWriter struct {
    27  	w             *BufferedWriterCloser
    28  	Func          *Func
    29  	canonIdMap    map[Node]int
    30  	prevCanonId   int
    31  	path          string
    32  	prevHash      []byte
    33  	pendingPhases []string
    34  	pendingTitles []string
    35  }
    36  
    37  // BufferedWriterCloser is here to help avoid pre-buffering the whole
    38  // rendered HTML in memory, which can cause problems for large inputs.
    39  type BufferedWriterCloser struct {
    40  	file io.Closer
    41  	w    *bufio.Writer
    42  }
    43  
    44  func (b *BufferedWriterCloser) Write(p []byte) (n int, err error) {
    45  	return b.w.Write(p)
    46  }
    47  
    48  func (b *BufferedWriterCloser) Close() error {
    49  	b.w.Flush()
    50  	b.w = nil
    51  	return b.file.Close()
    52  }
    53  
    54  func NewBufferedWriterCloser(f io.WriteCloser) *BufferedWriterCloser {
    55  	return &BufferedWriterCloser{file: f, w: bufio.NewWriter(f)}
    56  }
    57  
    58  func NewHTMLWriter(path string, f *Func, cfgMask string) *HTMLWriter {
    59  	path = strings.ReplaceAll(path, "/", string(filepath.Separator))
    60  	out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
    61  	if err != nil {
    62  		base.Fatalf("%v", err)
    63  	}
    64  	reportPath := path
    65  	if !filepath.IsAbs(reportPath) {
    66  		pwd, err := os.Getwd()
    67  		if err != nil {
    68  			base.Fatalf("%v", err)
    69  		}
    70  		reportPath = filepath.Join(pwd, path)
    71  	}
    72  	h := HTMLWriter{
    73  		w:          NewBufferedWriterCloser(out),
    74  		Func:       f,
    75  		path:       reportPath,
    76  		canonIdMap: make(map[Node]int),
    77  	}
    78  	h.start()
    79  	return &h
    80  }
    81  
    82  // canonId assigns indices to nodes based on pointer identity.
    83  // this helps ensure that output html files don't gratuitously
    84  // differ from run to run.
    85  func (h *HTMLWriter) canonId(n Node) int {
    86  	if id := h.canonIdMap[n]; id > 0 {
    87  		return id
    88  	}
    89  	h.prevCanonId++
    90  	h.canonIdMap[n] = h.prevCanonId
    91  	return h.prevCanonId
    92  }
    93  
    94  // Fatalf reports an error and exits.
    95  func (w *HTMLWriter) Fatalf(msg string, args ...any) {
    96  	base.FatalfAt(src.NoXPos, msg, args...)
    97  }
    98  
    99  const (
   100  	RIGHT_ARROW = "\u25BA" // click-to-open (is closed)
   101  	DOWN_ARROW  = "\u25BC" // click-to-close (is open)
   102  )
   103  
   104  func (w *HTMLWriter) start() {
   105  	if w == nil {
   106  		return
   107  	}
   108  	escName := html.EscapeString(PkgFuncName(w.Func))
   109  	w.Print("<!DOCTYPE html>")
   110  	w.Print("<html>")
   111  	w.Printf(`<head>
   112  <meta name="generator" content="AST display for %s">
   113  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
   114  %s
   115  %s
   116  <title>AST display for %s</title>
   117  </head>`, escName, CSS, JS, escName)
   118  	w.Print("<body>")
   119  	w.Print("<h1>")
   120  	w.Print(html.EscapeString(w.Func.Sym().Name))
   121  	w.Print("</h1>")
   122  	w.Print(`
   123  <a href="#" onclick="toggle_visibility('help');return false;" id="helplink">help</a>
   124  <div id="help">
   125  
   126  <p>
   127  Click anywhere on a node (with "cell" cursor) to outline a node and all of its subtrees.
   128  </p>
   129  <p>
   130  Click on a name (with "crosshair" cursor) to highlight every occurrence of a name.
   131  (Note that all the name nodes are the same node, so those also all outline together).
   132  </p>
   133  <p>
   134  Click on a file, line, or column (with "crosshair" cursor) to highlight positions
   135  in that file, at that file:line, or at that file:line:column, respectively.<br>Inlined
   136  locations are not treated as a single location, but as a sequence of locations that
   137  can be independently highlighted.
   138  </p>
   139  <p>
   140  Click on a ` + DOWN_ARROW + ` to collapse a subtree, or on a ` + RIGHT_ARROW + ` to expand a subtree.
   141  </p>
   142  
   143  
   144  </div>
   145  <label for="dark-mode-button" style="margin-left: 15px; cursor: pointer;">darkmode</label>
   146  <input type="checkbox" onclick="toggleDarkMode();" id="dark-mode-button" style="cursor: pointer" />
   147  `)
   148  	w.Print("<table>")
   149  	w.Print("<tr>")
   150  }
   151  
   152  func (w *HTMLWriter) Close() {
   153  	if w == nil {
   154  		return
   155  	}
   156  	w.Print("</tr>")
   157  	w.Print("</table>")
   158  	w.Print("</body>")
   159  	w.Print("</html>\n")
   160  	w.w.Close()
   161  	fmt.Fprintf(os.Stderr, "Writing html ast output for %s to %s\n", PkgFuncName(w.Func), w.path)
   162  }
   163  
   164  // WritePhase writes f in a column headed by title.
   165  // phase is used for collapsing columns and should be unique across the table.
   166  func (w *HTMLWriter) WritePhase(phase, title string) {
   167  	if w == nil {
   168  		return // avoid generating HTML just to discard it
   169  	}
   170  	w.pendingPhases = append(w.pendingPhases, phase)
   171  	w.pendingTitles = append(w.pendingTitles, title)
   172  	w.flushPhases()
   173  }
   174  
   175  // flushPhases collects any pending phases and titles, writes them to the html, and resets the pending slices.
   176  func (w *HTMLWriter) flushPhases() {
   177  	phaseLen := len(w.pendingPhases)
   178  	if phaseLen == 0 {
   179  		return
   180  	}
   181  	phases := strings.Join(w.pendingPhases, "  +  ")
   182  	w.WriteMultiTitleColumn(
   183  		phases,
   184  		w.pendingTitles,
   185  		"allow-x-scroll",
   186  		w.FuncHTML(w.pendingPhases[phaseLen-1]),
   187  	)
   188  	w.pendingPhases = w.pendingPhases[:0]
   189  	w.pendingTitles = w.pendingTitles[:0]
   190  }
   191  
   192  func (w *HTMLWriter) WriteMultiTitleColumn(phase string, titles []string, class string, writeContent func()) {
   193  	if w == nil {
   194  		return
   195  	}
   196  	id := strings.ReplaceAll(phase, " ", "-")
   197  	// collapsed column
   198  	w.Printf("<td id=\"%v-col\" class=\"collapsed\"><div>%v</div></td>", id, phase)
   199  
   200  	if class == "" {
   201  		w.Printf("<td id=\"%v-exp\">", id)
   202  	} else {
   203  		w.Printf("<td id=\"%v-exp\" class=\"%v\">", id, class)
   204  	}
   205  	for _, title := range titles {
   206  		w.Print("<h2>" + title + "</h2>")
   207  	}
   208  	writeContent()
   209  	w.Print("<div class=\"resizer\"></div>")
   210  	w.Print("</td>\n")
   211  }
   212  
   213  func (w *HTMLWriter) Printf(msg string, v ...any) {
   214  	if _, err := fmt.Fprintf(w.w, msg, v...); err != nil {
   215  		w.Fatalf("%v", err)
   216  	}
   217  }
   218  
   219  func (w *HTMLWriter) Print(s string) {
   220  	if _, err := fmt.Fprint(w.w, s); err != nil {
   221  		w.Fatalf("%v", err)
   222  	}
   223  }
   224  
   225  func (w *HTMLWriter) indent(n int) {
   226  	indent(w.w, n)
   227  }
   228  
   229  func (w *HTMLWriter) FuncHTML(phase string) func() {
   230  	return func() {
   231  		w.Print("<pre>") // use pre for formatting to preserve indentation
   232  		w.dumpNodesHTML(w.Func.Body, 1)
   233  		w.Print("</pre>")
   234  	}
   235  }
   236  
   237  func (h *HTMLWriter) dumpNodesHTML(list Nodes, depth int) {
   238  	if len(list) == 0 {
   239  		h.Print(" <nil>")
   240  		return
   241  	}
   242  
   243  	for _, n := range list {
   244  		h.dumpNodeHTML(n, depth)
   245  	}
   246  }
   247  
   248  // indent prints indentation to w.
   249  func (h *HTMLWriter) indentForToggle(depth int, hasChildren bool) {
   250  	h.Print("\n")
   251  	if depth == 0 {
   252  		return
   253  	}
   254  	for i := 0; i < depth-1; i++ {
   255  		h.Print(".   ")
   256  	}
   257  	if hasChildren {
   258  		h.Print(". ")
   259  	} else {
   260  		h.Print(".   ")
   261  	}
   262  }
   263  
   264  func (h *HTMLWriter) dumpNodeHTML(n Node, depth int) {
   265  	hasChildren := nodeHasChildren(n)
   266  	h.indentForToggle(depth, hasChildren)
   267  
   268  	if depth > 40 {
   269  		h.Print("...")
   270  		return
   271  	}
   272  
   273  	if n == nil {
   274  		h.Print("NilIrNode")
   275  		return
   276  	}
   277  
   278  	// For HTML, we want to wrap the node and its details in a span that can be highlighted
   279  	// across all occurrences of the span in all columns, so it has to be linked to the node ID,
   280  	// which is its address. Canonicalize the address to a counter so that repeated compiler
   281  	// runs yield the same html.
   282  	//
   283  	// JS Equivalence logic:
   284  	//   var c = elem.classList.item(0);
   285  	//   var x = document.getElementsByClassName(c);
   286  	//
   287  	// Tag each class with its canonicalized index.
   288  
   289  	h.Printf("<span class=\"n%d ir-node\">", h.canonId(n))
   290  	defer h.Printf("</span>")
   291  
   292  	if hasChildren {
   293  		h.Print(`<span class="toggle" onclick="toggle_node(this)">` + DOWN_ARROW + `</span> `) // NOTE TRAILING SPACE after </span>!
   294  	}
   295  
   296  	if len(n.Init()) != 0 {
   297  		h.Print(`<span class="node-body">`)
   298  		h.Printf("%+v-init", n.Op())
   299  		h.dumpNodesHTML(n.Init(), depth+1)
   300  		h.indent(depth)
   301  		h.Print(`</span>`)
   302  	}
   303  
   304  	switch n.Op() {
   305  	default:
   306  		h.Printf("%+v", n.Op())
   307  		h.dumpNodeHeaderHTML(n)
   308  
   309  	case OLITERAL:
   310  		h.Printf("%+v-%v", n.Op(), html.EscapeString(fmt.Sprintf("%v", n.Val())))
   311  		h.dumpNodeHeaderHTML(n)
   312  		return
   313  
   314  	case ONAME, ONONAME:
   315  		if n.Sym() != nil {
   316  			// Name highlighting:
   317  			// Create a hash for the symbol name to use as a class
   318  			// We use the same irValueClicked logic which uses the first class as the identifier
   319  			name := fmt.Sprintf("%v", n.Sym())
   320  			hash := sha256.Sum256([]byte(name))
   321  			symID := "sym-" + hex.EncodeToString(hash[:6])
   322  			h.Printf("%+v-<span class=\"%s variable-name\">%+v</span>", n.Op(), symID, html.EscapeString(name))
   323  		} else {
   324  			h.Printf("%+v", n.Op())
   325  		}
   326  		h.dumpNodeHeaderHTML(n)
   327  		return
   328  
   329  	case OLINKSYMOFFSET:
   330  		n := n.(*LinksymOffsetExpr)
   331  		h.Printf("%+v-%v", n.Op(), html.EscapeString(fmt.Sprintf("%v", n.Linksym)))
   332  		if n.Offset_ != 0 {
   333  			h.Printf("%+v", n.Offset_)
   334  		}
   335  		h.dumpNodeHeaderHTML(n)
   336  
   337  	case OASOP:
   338  		n := n.(*AssignOpStmt)
   339  		h.Printf("%+v-%+v", n.Op(), n.AsOp)
   340  		h.dumpNodeHeaderHTML(n)
   341  
   342  	case OTYPE:
   343  		h.Printf("%+v %+v", n.Op(), html.EscapeString(fmt.Sprintf("%v", n.Sym())))
   344  		h.dumpNodeHeaderHTML(n)
   345  		return
   346  
   347  	case OCLOSURE:
   348  		h.Printf("%+v", n.Op())
   349  		h.dumpNodeHeaderHTML(n)
   350  
   351  	case ODCLFUNC:
   352  		n := n.(*Func)
   353  		h.Printf("%+v", n.Op())
   354  		h.dumpNodeHeaderHTML(n)
   355  		if hasChildren {
   356  			h.Print(`<span class="node-body">`)
   357  			defer h.Print(`</span>`)
   358  		}
   359  		fn := n
   360  		if len(fn.Dcl) > 0 {
   361  			h.indent(depth)
   362  			h.Printf("%+v-Dcl", n.Op())
   363  			for _, dcl := range n.Dcl {
   364  				h.dumpNodeHTML(dcl, depth+1)
   365  			}
   366  		}
   367  		if len(fn.ClosureVars) > 0 {
   368  			h.indent(depth)
   369  			h.Printf("%+v-ClosureVars", n.Op())
   370  			for _, cv := range fn.ClosureVars {
   371  				h.dumpNodeHTML(cv, depth+1)
   372  			}
   373  		}
   374  		if len(fn.Body) > 0 {
   375  			h.indent(depth)
   376  			h.Printf("%+v-body", n.Op())
   377  			h.dumpNodesHTML(fn.Body, depth+1)
   378  		}
   379  		return
   380  	}
   381  	if hasChildren {
   382  		h.Print(`<span class="node-body">`)
   383  		defer h.Print(`</span>`)
   384  	}
   385  
   386  	v := reflect.ValueOf(n).Elem()
   387  	t := reflect.TypeOf(n).Elem()
   388  	nf := t.NumField()
   389  	for i := 0; i < nf; i++ {
   390  		tf := t.Field(i)
   391  		vf := v.Field(i)
   392  		if tf.PkgPath != "" {
   393  			continue
   394  		}
   395  		switch tf.Type.Kind() {
   396  		case reflect.Interface, reflect.Ptr, reflect.Slice:
   397  			if vf.IsNil() {
   398  				continue
   399  			}
   400  		}
   401  		name := strings.TrimSuffix(tf.Name, "_")
   402  		switch name {
   403  		case "X", "Y", "Index", "Chan", "Value", "Call":
   404  			name = ""
   405  		}
   406  		switch val := vf.Interface().(type) {
   407  		case Node:
   408  			if name != "" {
   409  				h.indent(depth)
   410  				h.Printf("%+v-%s", n.Op(), name)
   411  			}
   412  			h.dumpNodeHTML(val, depth+1)
   413  		case Nodes:
   414  			if len(val) == 0 {
   415  				continue
   416  			}
   417  			if name != "" {
   418  				h.indent(depth)
   419  				h.Printf("%+v-%s", n.Op(), name)
   420  			}
   421  			h.dumpNodesHTML(val, depth+1)
   422  		default:
   423  			if vf.Kind() == reflect.Slice && vf.Type().Elem().Implements(nodeType) {
   424  				if vf.Len() == 0 {
   425  					continue
   426  				}
   427  				if name != "" {
   428  					h.indent(depth)
   429  					h.Printf("%+v-%s", n.Op(), name)
   430  				}
   431  				for i, n := 0, vf.Len(); i < n; i++ {
   432  					h.dumpNodeHTML(vf.Index(i).Interface().(Node), depth+1)
   433  				}
   434  			}
   435  		}
   436  	}
   437  }
   438  
   439  func nodeHasChildren(n Node) bool {
   440  	if n == nil {
   441  		return false
   442  	}
   443  	if len(n.Init()) != 0 {
   444  		return true
   445  	}
   446  	switch n.Op() {
   447  	case OLITERAL, ONAME, ONONAME, OTYPE:
   448  		return false
   449  	case ODCLFUNC:
   450  		n := n.(*Func)
   451  		return len(n.Dcl) > 0 || len(n.ClosureVars) > 0 || len(n.Body) > 0
   452  	}
   453  
   454  	v := reflect.ValueOf(n).Elem()
   455  	t := reflect.TypeOf(n).Elem()
   456  	nf := t.NumField()
   457  	for i := 0; i < nf; i++ {
   458  		tf := t.Field(i)
   459  		vf := v.Field(i)
   460  		if tf.PkgPath != "" {
   461  			continue
   462  		}
   463  		switch tf.Type.Kind() {
   464  		case reflect.Interface, reflect.Ptr, reflect.Slice:
   465  			if vf.IsNil() {
   466  				continue
   467  			}
   468  		}
   469  		switch val := vf.Interface().(type) {
   470  		case Node:
   471  			return true
   472  		case Nodes:
   473  			if len(val) > 0 {
   474  				return true
   475  			}
   476  		default:
   477  			if vf.Kind() == reflect.Slice && vf.Type().Elem().Implements(nodeType) {
   478  				if vf.Len() > 0 {
   479  					return true
   480  				}
   481  			}
   482  		}
   483  	}
   484  	return false
   485  }
   486  
   487  func (h *HTMLWriter) dumpNodeHeaderHTML(n Node) {
   488  	// print pointer to be able to see identical nodes
   489  	if base.Debug.DumpPtrs != 0 {
   490  		h.Printf(" p(%p)", n)
   491  	}
   492  
   493  	if base.Debug.DumpPtrs != 0 && n.Name() != nil && n.Name().Defn != nil {
   494  		h.Printf(" defn(%p)", n.Name().Defn)
   495  	}
   496  
   497  	if base.Debug.DumpPtrs != 0 && n.Name() != nil && n.Name().Curfn != nil {
   498  		h.Printf(" curfn(%p)", n.Name().Curfn)
   499  	}
   500  	if base.Debug.DumpPtrs != 0 && n.Name() != nil && n.Name().Outer != nil {
   501  		h.Printf(" outer(%p)", n.Name().Outer)
   502  	}
   503  
   504  	if EscFmt != nil {
   505  		if esc := EscFmt(n); esc != "" {
   506  			h.Printf(" %s", html.EscapeString(esc))
   507  		}
   508  	}
   509  
   510  	if n.Sym() != nil && n.Op() != ONAME && n.Op() != ONONAME && n.Op() != OTYPE {
   511  		h.Printf(" %+v", html.EscapeString(fmt.Sprintf("%v", n.Sym())))
   512  	}
   513  
   514  	v := reflect.ValueOf(n).Elem()
   515  	t := v.Type()
   516  	nf := t.NumField()
   517  	for i := 0; i < nf; i++ {
   518  		tf := t.Field(i)
   519  		if tf.PkgPath != "" {
   520  			continue
   521  		}
   522  		k := tf.Type.Kind()
   523  		if reflect.Bool <= k && k <= reflect.Complex128 {
   524  			name := strings.TrimSuffix(tf.Name, "_")
   525  			vf := v.Field(i)
   526  			vfi := vf.Interface()
   527  			if name == "Offset" && vfi == types.BADWIDTH || name != "Offset" && vf.IsZero() {
   528  				continue
   529  			}
   530  			if vfi == true {
   531  				h.Printf(" %s", name)
   532  			} else {
   533  				h.Printf(" %s:%+v", name, html.EscapeString(fmt.Sprintf("%v", vf.Interface())))
   534  			}
   535  		}
   536  	}
   537  
   538  	v = reflect.ValueOf(n)
   539  	t = v.Type()
   540  	nm := t.NumMethod()
   541  	for i := 0; i < nm; i++ {
   542  		tm := t.Method(i)
   543  		if tm.PkgPath != "" {
   544  			continue
   545  		}
   546  		m := v.Method(i)
   547  		mt := m.Type()
   548  		if mt.NumIn() == 0 && mt.NumOut() == 1 && mt.Out(0).Kind() == reflect.Bool {
   549  			func() {
   550  				defer func() { recover() }()
   551  				if m.Call(nil)[0].Bool() {
   552  					name := strings.TrimSuffix(tm.Name, "_")
   553  					h.Printf(" %s", name)
   554  				}
   555  			}()
   556  		}
   557  	}
   558  
   559  	if n.Op() == OCLOSURE {
   560  		n := n.(*ClosureExpr)
   561  		if fn := n.Func; fn != nil && fn.Nname.Sym() != nil {
   562  			h.Printf(" fnName(%+v)", html.EscapeString(fmt.Sprintf("%v", fn.Nname.Sym())))
   563  		}
   564  	}
   565  
   566  	if n.Type() != nil {
   567  		if n.Op() == OTYPE {
   568  			h.Printf(" type")
   569  		}
   570  		h.Printf(" %+v", html.EscapeString(fmt.Sprintf("%v", n.Type())))
   571  	}
   572  	if n.Typecheck() != 0 {
   573  		h.Printf(" tc(%d)", n.Typecheck())
   574  	}
   575  
   576  	if n.Pos().IsKnown() {
   577  		h.Print(" <span class=\"line-number\">")
   578  		switch n.Pos().IsStmt() {
   579  		case src.PosNotStmt:
   580  			h.Print("_")
   581  		case src.PosIsStmt:
   582  			h.Print("+")
   583  		}
   584  		sep := ""
   585  		base.Ctxt.AllPos(n.Pos(), func(pos src.Pos) {
   586  			h.Print(sep)
   587  			sep = " "
   588  			// Hierarchical highlighting:
   589  			// Click file -> highlight all ranges in this file
   590  			// Click line -> highlight all ranges at this line (in this file)
   591  			// Click col  -> highlight this specific range
   592  
   593  			file := pos.Filename()
   594  			// Create a hash for the filename to use as a class
   595  			hash := sha256.Sum256([]byte(file))
   596  			fileID := "loc-" + hex.EncodeToString(hash[:6])
   597  			lineID := fmt.Sprintf("%s-L%d", fileID, pos.Line())
   598  			colID := fmt.Sprintf("%s-C%d", lineID, pos.Col())
   599  
   600  			// File part: triggers fileID
   601  			h.Printf("<span class=\"%s line-number\">%s</span>:", fileID, html.EscapeString(filepath.Base(file)))
   602  			// Line part: triggers lineID (and fileID via class list)
   603  			h.Printf("<span class=\"%s %s line-number\">%d</span>:", lineID, fileID, pos.Line())
   604  			// Col part: triggers colID (and lineID, fileID)
   605  			h.Printf("<span class=\"%s %s %s line-number\">%d</span>", colID, lineID, fileID, pos.Col())
   606  		})
   607  		h.Print("</span>")
   608  	}
   609  }
   610  
   611  const (
   612  	CSS = `<style>
   613  
   614  body {
   615      font-size: 14px;
   616      font-family: Arial, sans-serif;
   617  }
   618  
   619  h1 {
   620      font-size: 18px;
   621      display: inline-block;
   622      margin: 0 1em .5em 0;
   623  }
   624  
   625  #helplink {
   626      display: inline-block;
   627  }
   628  
   629  #help {
   630      display: none;
   631  }
   632  
   633  table {
   634      border: 1px solid black;
   635      table-layout: fixed;
   636      width: 300px;
   637  }
   638  
   639  th, td {
   640      border: 1px solid black;
   641      overflow: hidden;
   642      width: 400px;
   643      vertical-align: top;
   644      padding: 5px;
   645      position: relative;
   646  }
   647  
   648  .resizer {
   649      display: inline-block;
   650      background: transparent;
   651      width: 10px;
   652      height: 100%;
   653      position: absolute;
   654      right: 0;
   655      top: 0;
   656      cursor: col-resize;
   657      z-index: 100;
   658  }
   659  
   660  td > h2 {
   661      cursor: pointer;
   662      font-size: 120%;
   663      margin: 5px 0px 5px 0px;
   664  }
   665  
   666  td.collapsed {
   667      font-size: 12px;
   668      width: 12px;
   669      border: 1px solid white;
   670      padding: 2px;
   671      cursor: pointer;
   672      background: #fafafa;
   673  }
   674  
   675  td.collapsed div {
   676      text-align: right;
   677      transform: rotate(180deg);
   678      writing-mode: vertical-lr;
   679      white-space: pre;
   680  }
   681  
   682  pre {
   683      font-family: Menlo, monospace;
   684      font-size: 12px;
   685  }
   686  
   687  pre {
   688      -moz-tab-size: 4;
   689      -o-tab-size:   4;
   690      tab-size:      4;
   691  }
   692  
   693  .allow-x-scroll {
   694      overflow-x: scroll;
   695  }
   696  
   697  .ir-node {
   698      cursor: cell;
   699  }
   700  
   701  .variable-name {
   702      cursor: crosshair;
   703  }
   704  
   705  .line-number {
   706      font-size: 11px;
   707      cursor: crosshair;
   708  }
   709  
   710  body.darkmode {
   711      background-color: rgb(21, 21, 21);
   712      color: rgb(230, 255, 255);
   713      opacity: 100%;
   714  }
   715  
   716  td.darkmode {
   717      background-color: rgb(21, 21, 21);
   718      border: 1px solid gray;
   719  }
   720  
   721  body.darkmode table, th {
   722      border: 1px solid gray;
   723  }
   724  
   725  body.darkmode text {
   726      fill: white;
   727  }
   728  
   729  .highlight-aquamarine     { background-color: aquamarine; color: black; }
   730  .highlight-coral          { background-color: coral; color: black; }
   731  .highlight-lightpink      { background-color: lightpink; color: black; }
   732  .highlight-lightsteelblue { background-color: lightsteelblue; color: black; }
   733  .highlight-palegreen      { background-color: palegreen; color: black; }
   734  .highlight-skyblue        { background-color: skyblue; color: black; }
   735  .highlight-lightgray      { background-color: lightgray; color: black; }
   736  .highlight-yellow         { background-color: yellow; color: black; }
   737  .highlight-lime           { background-color: lime; color: black; }
   738  .highlight-khaki          { background-color: khaki; color: black; }
   739  .highlight-aqua           { background-color: aqua; color: black; }
   740  .highlight-salmon         { background-color: salmon; color: black; }
   741  
   742  
   743  .outline-blue           { outline: #2893ff solid 2px; }
   744  .outline-red            { outline: red solid 2px; }
   745  .outline-blueviolet     { outline: blueviolet solid 2px; }
   746  .outline-darkolivegreen { outline: darkolivegreen solid 2px; }
   747  .outline-fuchsia        { outline: fuchsia solid 2px; }
   748  .outline-sienna         { outline: sienna solid 2px; }
   749  .outline-gold           { outline: gold solid 2px; }
   750  .outline-orangered      { outline: orangered solid 2px; }
   751  .outline-teal           { outline: teal solid 2px; }
   752  .outline-maroon         { outline: maroon solid 2px; }
   753  .outline-black          { outline: black solid 2px; }
   754  
   755  /* Capture alternative for outline-black and ellipse.outline-black when in dark mode */
   756  body.darkmode .outline-black        { outline: gray solid 2px; }
   757  
   758  .toggle {
   759      cursor: pointer;
   760      display: inline-block;
   761      text-align: center;
   762      user-select: none;
   763      font-size: 12px; // hand-tweaked
   764  }
   765  
   766  </style>
   767  `
   768  
   769  	JS = `<script type="text/javascript">
   770  
   771  // Contains phase names which are expanded by default. Other columns are collapsed.
   772  let expandedDefault = [
   773      "bloop",
   774      "loopvar",
   775      "escape",
   776      "slice",
   777      "walk",
   778  ];
   779  if (history.state === null) {
   780      history.pushState({expandedDefault}, "", location.href);
   781  }
   782  
   783  // ordered list of all available highlight colors
   784  var highlights = [
   785      "highlight-aquamarine",
   786      "highlight-coral",
   787      "highlight-lightpink",
   788      "highlight-lightsteelblue",
   789      "highlight-palegreen",
   790      "highlight-skyblue",
   791      "highlight-lightgray",
   792      "highlight-yellow",
   793      "highlight-lime",
   794      "highlight-khaki",
   795      "highlight-aqua",
   796      "highlight-salmon"
   797  ];
   798  
   799  // state: which value is highlighted this color?
   800  var highlighted = {};
   801  for (var i = 0; i < highlights.length; i++) {
   802      highlighted[highlights[i]] = "";
   803  }
   804  
   805  // ordered list of all available outline colors
   806  var outlines = [
   807      "outline-blue",
   808      "outline-red",
   809      "outline-blueviolet",
   810      "outline-darkolivegreen",
   811      "outline-fuchsia",
   812      "outline-sienna",
   813      "outline-gold",
   814      "outline-orangered",
   815      "outline-teal",
   816      "outline-maroon",
   817      "outline-black"
   818  ];
   819  
   820  // state: which value is outlined this color?
   821  var outlined = {};
   822  for (var i = 0; i < outlines.length; i++) {
   823      outlined[outlines[i]] = "";
   824  }
   825  
   826  window.onload = function() {
   827      if (history.state !== null) {
   828          expandedDefault = history.state.expandedDefault;
   829      }
   830      if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
   831          toggleDarkMode();
   832          document.getElementById("dark-mode-button").checked = true;
   833      }
   834  
   835      var irElemClicked = function(elem, event, selections, selected) {
   836          event.stopPropagation();
   837  
   838          // find all values with the same name
   839          var c = elem.classList.item(0);
   840          var x = document.getElementsByClassName(c);
   841  
   842          // if selected, remove selections from all of them
   843          // otherwise, attempt to add
   844  
   845          var remove = "";
   846          for (var i = 0; i < selections.length; i++) {
   847              var color = selections[i];
   848              if (selected[color] == c) {
   849                  remove = color;
   850                  break;
   851              }
   852          }
   853  
   854          if (remove != "") {
   855              for (var i = 0; i < x.length; i++) {
   856                  x[i].classList.remove(remove);
   857              }
   858              selected[remove] = "";
   859              return;
   860          }
   861  
   862          // we're adding a selection
   863          // find first available color
   864          var avail = "";
   865          for (var i = 0; i < selections.length; i++) {
   866              var color = selections[i];
   867              if (selected[color] == "") {
   868                  avail = color;
   869                  break;
   870              }
   871          }
   872          if (avail == "") {
   873              alert("out of selection colors; go add more");
   874              return;
   875          }
   876  
   877          // set that as the selection
   878          for (var i = 0; i < x.length; i++) {
   879              x[i].classList.add(avail);
   880          }
   881          selected[avail] = c;
   882      };
   883  
   884      var irValueClicked = function(event) {
   885          irElemClicked(this, event, highlights, highlighted);
   886      };
   887  
   888      var irTreeClicked = function(event) {
   889          irElemClicked(this, event, outlines, outlined);
   890      };
   891  
   892      var irValues = document.getElementsByClassName("ir-node");
   893      for (var i = 0; i < irValues.length; i++) {
   894          irValues[i].addEventListener('click', irTreeClicked);
   895      }
   896  
   897      var lines = document.getElementsByClassName("line-number");
   898      for (var i = 0; i < lines.length; i++) {
   899          lines[i].addEventListener('click', irValueClicked);
   900      }
   901  
   902      var variableNames = document.getElementsByClassName("variable-name");
   903      for (var i = 0; i < variableNames.length; i++) {
   904          variableNames[i].addEventListener('click', irValueClicked);
   905      }
   906  
   907      function toggler(phase) {
   908          return function() {
   909              toggle_cell(phase+'-col');
   910              toggle_cell(phase+'-exp');
   911              const i = expandedDefault.indexOf(phase);
   912              if (i !== -1) {
   913                  expandedDefault.splice(i, 1);
   914              } else {
   915                  expandedDefault.push(phase);
   916              }
   917              history.pushState({expandedDefault}, "", location.href);
   918          };
   919      }
   920  
   921      function toggle_cell(id) {
   922          var e = document.getElementById(id);
   923          if (e.style.display == 'table-cell') {
   924              e.style.display = 'none';
   925          } else {
   926              e.style.display = 'table-cell';
   927          }
   928      }
   929  
   930      // Go through all columns and collapse needed phases.
   931      const td = document.getElementsByTagName("td");
   932      for (let i = 0; i < td.length; i++) {
   933          const id = td[i].id;
   934          const phase = id.substr(0, id.length-4);
   935          let show = expandedDefault.indexOf(phase) !== -1
   936  
   937          // If show == false, check to see if this is a combined column (multiple phases).
   938          // If combined, check each of the phases to see if they are in our expandedDefaults.
   939          // If any are found, that entire combined column gets shown.
   940          if (!show) {
   941              const combined = phase.split('--+--');
   942              const len = combined.length;
   943              if (len > 1) {
   944                  for (let i = 0; i < len; i++) {
   945                      const num = expandedDefault.indexOf(combined[i]);
   946                      if (num !== -1) {
   947                          expandedDefault.splice(num, 1);
   948                          if (expandedDefault.indexOf(phase) === -1) {
   949                              expandedDefault.push(phase);
   950                              show = true;
   951                          }
   952                      }
   953                  }
   954              }
   955          }
   956          if (id.endsWith("-exp")) {
   957              const h2Els = td[i].getElementsByTagName("h2");
   958              const len = h2Els.length;
   959              if (len > 0) {
   960                  for (let i = 0; i < len; i++) {
   961                      h2Els[i].addEventListener('click', toggler(phase));
   962                  }
   963              }
   964          } else {
   965              td[i].addEventListener('click', toggler(phase));
   966          }
   967          if (id.endsWith("-col") && show || id.endsWith("-exp") && !show) {
   968              td[i].style.display = 'none';
   969              continue;
   970          }
   971          td[i].style.display = 'table-cell';
   972      }
   973  
   974      var resizers = document.getElementsByClassName("resizer");
   975      for (var i = 0; i < resizers.length; i++) {
   976          var resizer = resizers[i];
   977          resizer.addEventListener('mousedown', initDrag, false);
   978      }
   979  };
   980  
   981  var startX, startWidth, resizableCol;
   982  
   983  function initDrag(e) {
   984      resizableCol = this.parentElement;
   985      startX = e.clientX;
   986      startWidth = parseInt(document.defaultView.getComputedStyle(resizableCol).width, 10);
   987      document.documentElement.addEventListener('mousemove', doDrag, false);
   988      document.documentElement.addEventListener('mouseup', stopDrag, false);
   989  }
   990  
   991  function doDrag(e) {
   992      resizableCol.style.width = (startWidth + e.clientX - startX) + 'px';
   993  }
   994  
   995  function stopDrag(e) {
   996      document.documentElement.removeEventListener('mousemove', doDrag, false);
   997      document.documentElement.removeEventListener('mouseup', stopDrag, false);
   998  }
   999  
  1000  function toggle_visibility(id) {
  1001      var e = document.getElementById(id);
  1002      if (e.style.display == 'block') {
  1003          e.style.display = 'none';
  1004      } else {
  1005          e.style.display = 'block';
  1006      }
  1007  }
  1008  
  1009  function toggleDarkMode() {
  1010      document.body.classList.toggle('darkmode');
  1011  
  1012      // Collect all of the "collapsed" elements and apply dark mode on each collapsed column
  1013      const collapsedEls = document.getElementsByClassName('collapsed');
  1014      const len = collapsedEls.length;
  1015  
  1016      for (let i = 0; i < len; i++) {
  1017          collapsedEls[i].classList.toggle('darkmode');
  1018      }
  1019  }
  1020  
  1021  function toggle_node(e) {
  1022      event.stopPropagation();
  1023      var parent = e.parentNode;
  1024      var children = parent.children;
  1025      for (var i = 0; i < children.length; i++) {
  1026          if (children[i].classList.contains("node-body")) {
  1027              if (children[i].style.display == "none") {
  1028                  children[i].style.display = "";
  1029              } else {
  1030                  children[i].style.display = "none";
  1031              }
  1032          }
  1033      }
  1034      if (e.innerText == "` + RIGHT_ARROW + `") {
  1035          e.innerText = "` + DOWN_ARROW + `";
  1036      } else {
  1037          e.innerText = "` + RIGHT_ARROW + `";
  1038      }
  1039  }
  1040  
  1041  </script>
  1042  `
  1043  )
  1044  

View as plain text