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