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