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