github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/go/ir/html.go (about) 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 laid out. 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 "<nil>" 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 += " <" + html.EscapeString(v.Type().String()) + ">" 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 += " {<nil>}" 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 += " →" // 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, " ←") // 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 }