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