1
2
3
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 w io.WriteCloser
22 Func *Func
23 path string
24 dot *dotWriter
25 prevHash []byte
26 pendingPhases []string
27 pendingTitles []string
28 }
29
30 func 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
55 func (w *HTMLWriter) Fatalf(msg string, args ...interface{}) {
56 fe := w.Func.Frontend()
57 fe.Fatalf(src.NoXPos, msg, args...)
58 }
59
60
61 func (w *HTMLWriter) Logf(msg string, args ...interface{}) {
62 w.Func.Logf(msg, args...)
63 }
64
65 func (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
74 body {
75 font-size: 14px;
76 font-family: Arial, sans-serif;
77 }
78
79 h1 {
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
97 table {
98 border: 1px solid black;
99 table-layout: fixed;
100 width: 300px;
101 }
102
103 th, td {
104 border: 1px solid black;
105 overflow: hidden;
106 width: 400px;
107 vertical-align: top;
108 padding: 5px;
109 }
110
111 td > h2 {
112 cursor: pointer;
113 font-size: 120%;
114 margin: 5px 0px 5px 0px;
115 }
116
117 td.collapsed {
118 font-size: 12px;
119 width: 12px;
120 border: 1px solid white;
121 padding: 2px;
122 cursor: pointer;
123 background: #fafafa;
124 }
125
126 td.collapsed div {
127 text-align: right;
128 transform: rotate(180deg);
129 writing-mode: vertical-lr;
130 white-space: pre;
131 }
132
133 code, pre, .lines, .ast {
134 font-family: Menlo, monospace;
135 font-size: 12px;
136 }
137
138 pre {
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
160 div.line-number {
161 font-size: 12px;
162 }
163
164 .ast {
165 white-space: nowrap;
166 }
167
168 td.ssa-prog {
169 width: 600px;
170 word-wrap: break-word;
171 }
172
173 li {
174 list-style-type: none;
175 }
176
177 li.ssa-long-value {
178 text-indent: -2em; /* indent wrapped lines */
179 }
180
181 li.ssa-value-list {
182 display: inline;
183 }
184
185 li.ssa-start-block {
186 padding: 0;
187 margin: 0;
188 }
189
190 li.ssa-end-block {
191 padding: 0;
192 margin: 0;
193 }
194
195 ul.ssa-print-func {
196 padding-left: 0;
197 }
198
199 li.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
208 button:hover {
209 background-color: #eee;
210 cursor: pointer;
211 }
212
213 dl.ssa-gen {
214 padding-left: 0;
215 }
216
217 dt.ssa-prog-src {
218 padding: 0;
219 margin: 0;
220 float: left;
221 width: 4em;
222 }
223
224 dd.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
265 svg {
266 cursor: default;
267 outline: 1px solid #eee;
268 width: 100%;
269 }
270
271 body.darkmode {
272 background-color: rgb(21, 21, 21);
273 color: rgb(230, 255, 255);
274 opacity: 100%;
275 }
276
277 td.darkmode {
278 background-color: rgb(21, 21, 21);
279 border: 1px solid gray;
280 }
281
282 body.darkmode table, th {
283 border: 1px solid gray;
284 }
285
286 body.darkmode text {
287 fill: white;
288 }
289
290 body.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
347 ellipse.outline-blue { stroke-width: 2px; stroke: #2893ff; }
348 ellipse.outline-red { stroke-width: 2px; stroke: red; }
349 ellipse.outline-blueviolet { stroke-width: 2px; stroke: blueviolet; }
350 ellipse.outline-darkolivegreen { stroke-width: 2px; stroke: darkolivegreen; }
351 ellipse.outline-fuchsia { stroke-width: 2px; stroke: fuchsia; }
352 ellipse.outline-sienna { stroke-width: 2px; stroke: sienna; }
353 ellipse.outline-gold { stroke-width: 2px; stroke: gold; }
354 ellipse.outline-orangered { stroke-width: 2px; stroke: orangered; }
355 ellipse.outline-teal { stroke-width: 2px; stroke: teal; }
356 ellipse.outline-maroon { stroke-width: 2px; stroke: maroon; }
357 ellipse.outline-black { stroke-width: 2px; stroke: black; }
358
359 /* Capture alternative for outline-black and ellipse.outline-black when in dark mode */
360 body.darkmode .outline-black { outline: gray solid 2px; }
361 body.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.
368 let expandedDefault = [
369 "start",
370 "deadcode",
371 "opt",
372 "lower",
373 "late-deadcode",
374 "regalloc",
375 "genssa",
376 ];
377 if (history.state === null) {
378 history.pushState({expandedDefault}, "", location.href);
379 }
380
381 // ordered list of all available highlight colors
382 var 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?
398 var highlighted = {};
399 for (var i = 0; i < highlights.length; i++) {
400 highlighted[highlights[i]] = "";
401 }
402
403 // ordered list of all available outline colors
404 var 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?
419 var outlined = {};
420 for (var i = 0; i < outlines.length; i++) {
421 outlined[outlines[i]] = "";
422 }
423
424 window.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
614 function 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
623 function 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.
638 function 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
647 function 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
656 function 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
705 function 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>
751 Click on a value or block to toggle highlighting of that value/block
752 and its uses. (Values and blocks are highlighted by ID, and IDs of
753 dead items may be reused, so not all highlights necessarily correspond
754 to the clicked item.)
755 </p>
756
757 <p>
758 Faded out values and blocks are dead code that has not been eliminated.
759 </p>
760
761 <p>
762 Values 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.
767 Edge 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
778 func (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
791
792 func (w *HTMLWriter) WritePhase(phase, title string) {
793 if w == nil {
794 return
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
806 func (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
823
824 type FuncLines struct {
825 Filename string
826 StartLineno uint
827 Lines []string
828 }
829
830
831
832 type ByTopo []*FuncLines
833
834 func (x ByTopo) Len() int { return len(x) }
835 func (x ByTopo) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
836 func (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
846
847 func (w *HTMLWriter) WriteSources(phase string, all []*FuncLines) {
848 if w == nil {
849 return
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> </div>")
856 if filename != fl.Filename {
857 fmt.Fprint(&buf, "<div> </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> </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 = " "
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
888 func (w *HTMLWriter) WriteAST(phase string, buf *bytes.Buffer) {
889 if w == nil {
890 return
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 = " "
902 } else {
903 if strings.HasPrefix(l, "buildssa") {
904 escaped = fmt.Sprintf("<b>%v</b>", l)
905 } else {
906
907
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
928
929 func (w *HTMLWriter) WriteColumn(phase, title, class, html string) {
930 w.WriteMultiTitleColumn(phase, []string{title}, class, html)
931 }
932
933 func (w *HTMLWriter) WriteMultiTitleColumn(phase string, titles []string, class, html string) {
934 if w == nil {
935 return
936 }
937 id := strings.Replace(phase, " ", "-", -1)
938
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
953 func (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
959 func (w *HTMLWriter) WriteString(s string) {
960 if _, err := io.WriteString(w.w, s); err != nil {
961 w.Fatalf("%v", err)
962 }
963 }
964
965 func (v *Value) HTML() string {
966
967
968
969 s := v.String()
970 return fmt.Sprintf("<span class=\"%s ssa-value\">%s</span>", s, s)
971 }
972
973 func (v *Value) LongHTML() string {
974
975
976
977
978
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 += " <" + html.EscapeString(v.Type.String()) + ">"
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
1006 }
1007 }
1008 }
1009 if len(names) != 0 {
1010 s += " (" + strings.Join(names, ", ") + ")"
1011 }
1012
1013 s += "</span>"
1014 return s
1015 }
1016
1017 func (b *Block) HTML() string {
1018
1019
1020
1021 s := html.EscapeString(b.String())
1022 return fmt.Sprintf("<span class=\"%s ssa-block\">%s</span>", s, s)
1023 }
1024
1025 func (b *Block) LongHTML() string {
1026
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 += " →"
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
1052
1053 s += fmt.Sprintf(" <span class=\"l%v line-number\">(%s)</span>", b.Pos.LineNumber(), b.Pos.LineNumberHTML())
1054 }
1055 return s
1056 }
1057
1058 func (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
1068 fmt.Fprint(buf, "</code>")
1069 return buf.String()
1070 }
1071
1072 func (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
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
1165
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
1175 func (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
1185 func (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
1194 type htmlFuncPrinter struct {
1195 w io.Writer
1196 }
1197
1198 func (p htmlFuncPrinter) header(f *Func) {}
1199
1200 func (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, " ←")
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 {
1219 io.WriteString(p.w, "<li class=\"ssa-value-list\">")
1220 io.WriteString(p.w, "<ul>")
1221 }
1222 }
1223
1224 func (p htmlFuncPrinter) endBlock(b *Block, reachable bool) {
1225 if len(b.Values) > 0 {
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
1235 func (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
1245 func (p htmlFuncPrinter) startDepCycle() {
1246 fmt.Fprintln(p.w, "<span class=\"depcycle\">")
1247 }
1248
1249 func (p htmlFuncPrinter) endDepCycle() {
1250 fmt.Fprintln(p.w, "</span>")
1251 }
1252
1253 func (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
1261 type dotWriter struct {
1262 path string
1263 broken bool
1264 phases map[string]bool
1265 }
1266
1267
1268
1269
1270
1271
1272
1273 func newDotWriter(mask string) *dotWriter {
1274 if mask == "" {
1275 return nil
1276 }
1277
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
1312 func 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
View as plain text