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