github.com/bir3/gocompiler@v0.9.2202/src/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/bir3/gocompiler/src/cmd/internal/src" 10 "fmt" 11 "html" 12 "io" 13 "os" 14 "github.com/bir3/gocompiler/exec" 15 "path/filepath" 16 "strconv" 17 "strings" 18 ) 19 20 type 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 30 func 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. 55 func (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. 61 func (w *HTMLWriter) Logf(msg string, args ...interface{}) { 62 w.Func.Logf(msg, args...) 63 } 64 65 func (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 74 body { 75 font-size: 14px; 76 font-family: Arial, sans-serif; 77 } 78 79 h1 { 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 97 table { 98 border: 1px solid black; 99 table-layout: fixed; 100 width: 300px; 101 } 102 103 th, td { 104 border: 1px solid black; 105 overflow: hidden; 106 width: 400px; 107 vertical-align: top; 108 padding: 5px; 109 } 110 111 td > h2 { 112 cursor: pointer; 113 font-size: 120%; 114 margin: 5px 0px 5px 0px; 115 } 116 117 td.collapsed { 118 font-size: 12px; 119 width: 12px; 120 border: 1px solid white; 121 padding: 2px; 122 cursor: pointer; 123 background: #fafafa; 124 } 125 126 td.collapsed div { 127 text-align: right; 128 transform: rotate(180deg); 129 writing-mode: vertical-lr; 130 white-space: pre; 131 } 132 133 code, pre, .lines, .ast { 134 font-family: Menlo, monospace; 135 font-size: 12px; 136 } 137 138 pre { 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 160 div.line-number { 161 font-size: 12px; 162 } 163 164 .ast { 165 white-space: nowrap; 166 } 167 168 td.ssa-prog { 169 width: 600px; 170 word-wrap: break-word; 171 } 172 173 li { 174 list-style-type: none; 175 } 176 177 li.ssa-long-value { 178 text-indent: -2em; /* indent wrapped lines */ 179 } 180 181 li.ssa-value-list { 182 display: inline; 183 } 184 185 li.ssa-start-block { 186 padding: 0; 187 margin: 0; 188 } 189 190 li.ssa-end-block { 191 padding: 0; 192 margin: 0; 193 } 194 195 ul.ssa-print-func { 196 padding-left: 0; 197 } 198 199 li.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 208 button:hover { 209 background-color: #eee; 210 cursor: pointer; 211 } 212 213 dl.ssa-gen { 214 padding-left: 0; 215 } 216 217 dt.ssa-prog-src { 218 padding: 0; 219 margin: 0; 220 float: left; 221 width: 4em; 222 } 223 224 dd.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 265 svg { 266 cursor: default; 267 outline: 1px solid #eee; 268 width: 100%; 269 } 270 271 body.darkmode { 272 background-color: rgb(21, 21, 21); 273 color: rgb(230, 255, 255); 274 opacity: 100%; 275 } 276 277 td.darkmode { 278 background-color: rgb(21, 21, 21); 279 border: 1px solid gray; 280 } 281 282 body.darkmode table, th { 283 border: 1px solid gray; 284 } 285 286 body.darkmode text { 287 fill: white; 288 } 289 290 body.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 347 ellipse.outline-blue { stroke-width: 2px; stroke: #2893ff; } 348 ellipse.outline-red { stroke-width: 2px; stroke: red; } 349 ellipse.outline-blueviolet { stroke-width: 2px; stroke: blueviolet; } 350 ellipse.outline-darkolivegreen { stroke-width: 2px; stroke: darkolivegreen; } 351 ellipse.outline-fuchsia { stroke-width: 2px; stroke: fuchsia; } 352 ellipse.outline-sienna { stroke-width: 2px; stroke: sienna; } 353 ellipse.outline-gold { stroke-width: 2px; stroke: gold; } 354 ellipse.outline-orangered { stroke-width: 2px; stroke: orangered; } 355 ellipse.outline-teal { stroke-width: 2px; stroke: teal; } 356 ellipse.outline-maroon { stroke-width: 2px; stroke: maroon; } 357 ellipse.outline-black { stroke-width: 2px; stroke: black; } 358 359 /* Capture alternative for outline-black and ellipse.outline-black when in dark mode */ 360 body.darkmode .outline-black { outline: gray solid 2px; } 361 body.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. 368 let expandedDefault = [ 369 "start", 370 "deadcode", 371 "opt", 372 "lower", 373 "late-deadcode", 374 "regalloc", 375 "genssa", 376 ]; 377 if (history.state === null) { 378 history.pushState({expandedDefault}, "", location.href); 379 } 380 381 // ordered list of all available highlight colors 382 var 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? 398 var highlighted = {}; 399 for (var i = 0; i < highlights.length; i++) { 400 highlighted[highlights[i]] = ""; 401 } 402 403 // ordered list of all available outline colors 404 var 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? 419 var outlined = {}; 420 for (var i = 0; i < outlines.length; i++) { 421 outlined[outlines[i]] = ""; 422 } 423 424 window.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 614 function 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 623 function 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. 638 function 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 647 function 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 656 function 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 705 function 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> 751 Click on a value or block to toggle highlighting of that value/block 752 and its uses. (Values and blocks are highlighted by ID, and IDs of 753 dead items may be reused, so not all highlights necessarily correspond 754 to the clicked item.) 755 </p> 756 757 <p> 758 Faded out values and blocks are dead code that has not been eliminated. 759 </p> 760 761 <p> 762 Values 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. 767 Edge 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 778 func (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. 792 func (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. 806 func (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. 824 type 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. 832 type ByTopo []*FuncLines 833 834 func (x ByTopo) Len() int { return len(x) } 835 func (x ByTopo) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 836 func (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. 847 func (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 888 func (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. 929 func (w *HTMLWriter) WriteColumn(phase, title, class, html string) { 930 w.WriteMultiTitleColumn(phase, []string{title}, class, html) 931 } 932 933 func (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 953 func (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 959 func (w *HTMLWriter) WriteString(s string) { 960 if _, err := io.WriteString(w.w, s); err != nil { 961 w.Fatalf("%v", err) 962 } 963 } 964 965 func (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 973 func (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 1017 func (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 1025 func (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 1058 func (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 1072 func (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 1175 func (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 1185 func (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 1194 type htmlFuncPrinter struct { 1195 w io.Writer 1196 } 1197 1198 func (p htmlFuncPrinter) header(f *Func) {} 1199 1200 func (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 1224 func (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 1235 func (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 1245 func (p htmlFuncPrinter) startDepCycle() { 1246 fmt.Fprintln(p.w, "<span class=\"depcycle\">") 1247 } 1248 1249 func (p htmlFuncPrinter) endDepCycle() { 1250 fmt.Fprintln(p.w, "</span>") 1251 } 1252 1253 func (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 1261 type 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. 1273 func 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 1312 func passIdxByName(name string) int { 1313 for i, p := range passes { 1314 if p.name == name { 1315 return i 1316 } 1317 } 1318 return -1 1319 }