1// Copyright 2015 The Go Authors. All rights reserved. 2// Copyright 2019 Dominik Honnef. All rights reserved. 3 4package ir 5 6import ( 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 21func 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 62type 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 71func 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 86func 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 121func 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 143type HTMLWriter struct { 144 w io.WriteCloser 145 path string 146 dot *dotWriter 147} 148 149func 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 164func (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 173body { 174 font-size: 14px; 175 font-family: Arial, sans-serif; 176} 177 178h1 { 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 196table { 197 border: 1px solid black; 198 table-layout: fixed; 199 width: 300px; 200} 201 202th, td { 203 border: 1px solid black; 204 overflow: hidden; 205 width: 400px; 206 vertical-align: top; 207 padding: 5px; 208} 209 210td > h2 { 211 cursor: pointer; 212 font-size: 120%; 213} 214 215td.collapsed { 216 font-size: 12px; 217 width: 12px; 218 border: 0px; 219 padding: 0; 220 cursor: pointer; 221 background: #fafafa; 222} 223 224td.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 236code, pre, .lines, .ast { 237 font-family: Menlo, monospace; 238 font-size: 12px; 239} 240 241pre { 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 262div.line-number { 263 font-size: 12px; 264} 265 266.ast { 267 white-space: nowrap; 268} 269 270td.ssa-prog { 271 width: 600px; 272 word-wrap: break-word; 273} 274 275li { 276 list-style-type: none; 277} 278 279li.ssa-long-value { 280 text-indent: -2em; /* indent wrapped lines */ 281} 282 283li.ssa-value-list { 284 display: inline; 285} 286 287li.ssa-start-block { 288 padding: 0; 289 margin: 0; 290} 291 292li.ssa-end-block { 293 padding: 0; 294 margin: 0; 295} 296 297ul.ssa-print-func { 298 padding-left: 0; 299} 300 301li.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 310button:hover { 311 background-color: #eee; 312 cursor: pointer; 313} 314 315dl.ssa-gen { 316 padding-left: 0; 317} 318 319dt.ssa-prog-src { 320 padding: 0; 321 margin: 0; 322 float: left; 323 width: 4em; 324} 325 326dd.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 367svg { 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 397ellipse.outline-blue { stroke-width: 2px; stroke: blue; } 398ellipse.outline-red { stroke-width: 2px; stroke: red; } 399ellipse.outline-blueviolet { stroke-width: 2px; stroke: blueviolet; } 400ellipse.outline-darkolivegreen { stroke-width: 2px; stroke: darkolivegreen; } 401ellipse.outline-fuchsia { stroke-width: 2px; stroke: fuchsia; } 402ellipse.outline-sienna { stroke-width: 2px; stroke: sienna; } 403ellipse.outline-gold { stroke-width: 2px; stroke: gold; } 404ellipse.outline-orangered { stroke-width: 2px; stroke: orangered; } 405ellipse.outline-teal { stroke-width: 2px; stroke: teal; } 406ellipse.outline-maroon { stroke-width: 2px; stroke: maroon; } 407ellipse.outline-black { stroke-width: 2px; stroke: black; } 408 409</style> 410 411<script type="text/javascript"> 412// ordered list of all available highlight colors 413var 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? 429var highlighted = {}; 430for (var i = 0; i < highlights.length; i++) { 431 highlighted[highlights[i]] = ""; 432} 433 434// ordered list of all available outline colors 435var 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? 450var outlined = {}; 451for (var i = 0; i < outlines.length; i++) { 452 outlined[outlines[i]] = ""; 453} 454 455window.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 620function 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 629function 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. 644function 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 653function 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 662function 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> 721Click on a value or block to toggle highlighting of that value/block 722and its uses. (Values and blocks are highlighted by ID, and IDs of 723dead items may be reused, so not all highlights necessarily correspond 724to the clicked item.) 725</p> 726 727<p> 728Faded out values and blocks are dead code that has not been eliminated. 729</p> 730 731<p> 732Values 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. 737Edge 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 746func (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. 760func (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. 769func (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 787func (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 793func (w *HTMLWriter) WriteString(s string) { 794 if _, err := io.WriteString(w.w, s); err != nil { 795 log.Fatalf("%v", err) 796 } 797} 798 799func 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 819func 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 904func 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 912func 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 941func 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 955type htmlFuncPrinter struct { 956 w io.Writer 957} 958 959func (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 982func (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 993func (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 1003func (p htmlFuncPrinter) startDepCycle() { 1004 fmt.Fprintln(p.w, "<span class=\"depcycle\">") 1005} 1006 1007func (p htmlFuncPrinter) endDepCycle() { 1008 fmt.Fprintln(p.w, "</span>") 1009} 1010 1011func (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 1019type 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. 1030func 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 1039func (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 1117func (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} 1125