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