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