...

Source file src/pkg/cmd/compile/internal/ssa/html.go

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

View as plain text