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