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