github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/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 "cmd/internal/src" 10 "fmt" 11 "html" 12 "io" 13 "os" 14 "path/filepath" 15 "strconv" 16 "strings" 17 ) 18 19 type HTMLWriter struct { 20 Logger 21 w io.WriteCloser 22 path string 23 } 24 25 func NewHTMLWriter(path string, logger Logger, funcname string) *HTMLWriter { 26 out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 27 if err != nil { 28 logger.Fatalf(src.NoXPos, "%v", err) 29 } 30 pwd, err := os.Getwd() 31 if err != nil { 32 logger.Fatalf(src.NoXPos, "%v", err) 33 } 34 html := HTMLWriter{w: out, Logger: logger, path: filepath.Join(pwd, path)} 35 html.start(funcname) 36 return &html 37 } 38 39 func (w *HTMLWriter) start(name string) { 40 if w == nil { 41 return 42 } 43 w.WriteString("<html>") 44 w.WriteString(`<head> 45 <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> 46 <style> 47 48 body { 49 font-size: 14px; 50 font-family: Arial, sans-serif; 51 } 52 53 h1 { 54 font-size: 18px; 55 display: inline-block; 56 margin: 0 1em .5em 0; 57 } 58 59 #helplink { 60 display: inline-block; 61 } 62 63 #help { 64 display: none; 65 } 66 67 .stats { 68 font-size: 60%; 69 } 70 71 table { 72 border: 1px solid black; 73 table-layout: fixed; 74 width: 300px; 75 } 76 77 th, td { 78 border: 1px solid black; 79 overflow: hidden; 80 width: 400px; 81 vertical-align: top; 82 padding: 5px; 83 } 84 85 td > h2 { 86 cursor: pointer; 87 font-size: 120%; 88 } 89 90 td.collapsed { 91 font-size: 12px; 92 width: 12px; 93 border: 0px; 94 padding: 0; 95 cursor: pointer; 96 background: #fafafa; 97 } 98 99 td.collapsed div { 100 -moz-transform: rotate(-90.0deg); /* FF3.5+ */ 101 -o-transform: rotate(-90.0deg); /* Opera 10.5 */ 102 -webkit-transform: rotate(-90.0deg); /* Saf3.1+, Chrome */ 103 filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0.083); /* IE6,IE7 */ 104 -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0.083)"; /* IE8 */ 105 margin-top: 10.3em; 106 margin-left: -10em; 107 margin-right: -10em; 108 text-align: right; 109 } 110 111 code, pre, .lines, .ast { 112 font-family: Menlo, monospace; 113 font-size: 12px; 114 } 115 116 pre { 117 -moz-tab-size: 4; 118 -o-tab-size: 4; 119 tab-size: 4; 120 } 121 122 .allow-x-scroll { 123 overflow-x: scroll; 124 } 125 126 .lines { 127 float: left; 128 overflow: hidden; 129 text-align: right; 130 } 131 132 .lines div { 133 padding-right: 10px; 134 color: gray; 135 } 136 137 div.line-number { 138 font-size: 12px; 139 } 140 141 .ast { 142 white-space: nowrap; 143 } 144 145 td.ssa-prog { 146 width: 600px; 147 word-wrap: break-word; 148 } 149 150 li { 151 list-style-type: none; 152 } 153 154 li.ssa-long-value { 155 text-indent: -2em; /* indent wrapped lines */ 156 } 157 158 li.ssa-value-list { 159 display: inline; 160 } 161 162 li.ssa-start-block { 163 padding: 0; 164 margin: 0; 165 } 166 167 li.ssa-end-block { 168 padding: 0; 169 margin: 0; 170 } 171 172 ul.ssa-print-func { 173 padding-left: 0; 174 } 175 176 dl.ssa-gen { 177 padding-left: 0; 178 } 179 180 dt.ssa-prog-src { 181 padding: 0; 182 margin: 0; 183 float: left; 184 width: 4em; 185 } 186 187 dd.ssa-prog { 188 padding: 0; 189 margin-right: 0; 190 margin-left: 4em; 191 } 192 193 .dead-value { 194 color: gray; 195 } 196 197 .dead-block { 198 opacity: 0.5; 199 } 200 201 .depcycle { 202 font-style: italic; 203 } 204 205 .line-number { 206 font-size: 11px; 207 } 208 209 .no-line-number { 210 font-size: 11px; 211 color: gray; 212 } 213 214 .highlight-aquamarine { background-color: aquamarine; } 215 .highlight-coral { background-color: coral; } 216 .highlight-lightpink { background-color: lightpink; } 217 .highlight-lightsteelblue { background-color: lightsteelblue; } 218 .highlight-palegreen { background-color: palegreen; } 219 .highlight-skyblue { background-color: skyblue; } 220 .highlight-lightgray { background-color: lightgray; } 221 .highlight-yellow { background-color: yellow; } 222 .highlight-lime { background-color: lime; } 223 .highlight-khaki { background-color: khaki; } 224 .highlight-aqua { background-color: aqua; } 225 .highlight-salmon { background-color: salmon; } 226 227 .outline-blue { outline: blue solid 2px; } 228 .outline-red { outline: red solid 2px; } 229 .outline-blueviolet { outline: blueviolet solid 2px; } 230 .outline-darkolivegreen { outline: darkolivegreen solid 2px; } 231 .outline-fuchsia { outline: fuchsia solid 2px; } 232 .outline-sienna { outline: sienna solid 2px; } 233 .outline-gold { outline: gold solid 2px; } 234 .outline-orangered { outline: orangered solid 2px; } 235 .outline-teal { outline: teal solid 2px; } 236 .outline-maroon { outline: maroon solid 2px; } 237 .outline-black { outline: black solid 2px; } 238 239 </style> 240 241 <script type="text/javascript"> 242 // ordered list of all available highlight colors 243 var highlights = [ 244 "highlight-aquamarine", 245 "highlight-coral", 246 "highlight-lightpink", 247 "highlight-lightsteelblue", 248 "highlight-palegreen", 249 "highlight-skyblue", 250 "highlight-lightgray", 251 "highlight-yellow", 252 "highlight-lime", 253 "highlight-khaki", 254 "highlight-aqua", 255 "highlight-salmon" 256 ]; 257 258 // state: which value is highlighted this color? 259 var highlighted = {}; 260 for (var i = 0; i < highlights.length; i++) { 261 highlighted[highlights[i]] = ""; 262 } 263 264 // ordered list of all available outline colors 265 var outlines = [ 266 "outline-blue", 267 "outline-red", 268 "outline-blueviolet", 269 "outline-darkolivegreen", 270 "outline-fuchsia", 271 "outline-sienna", 272 "outline-gold", 273 "outline-orangered", 274 "outline-teal", 275 "outline-maroon", 276 "outline-black" 277 ]; 278 279 // state: which value is outlined this color? 280 var outlined = {}; 281 for (var i = 0; i < outlines.length; i++) { 282 outlined[outlines[i]] = ""; 283 } 284 285 window.onload = function() { 286 var ssaElemClicked = function(elem, event, selections, selected) { 287 event.stopPropagation(); 288 289 // TODO: pushState with updated state and read it on page load, 290 // so that state can survive across reloads 291 292 // find all values with the same name 293 var c = elem.classList.item(0); 294 var x = document.getElementsByClassName(c); 295 296 // if selected, remove selections from all of them 297 // otherwise, attempt to add 298 299 var remove = ""; 300 for (var i = 0; i < selections.length; i++) { 301 var color = selections[i]; 302 if (selected[color] == c) { 303 remove = color; 304 break; 305 } 306 } 307 308 if (remove != "") { 309 for (var i = 0; i < x.length; i++) { 310 x[i].classList.remove(remove); 311 } 312 selected[remove] = ""; 313 return; 314 } 315 316 // we're adding a selection 317 // find first available color 318 var avail = ""; 319 for (var i = 0; i < selections.length; i++) { 320 var color = selections[i]; 321 if (selected[color] == "") { 322 avail = color; 323 break; 324 } 325 } 326 if (avail == "") { 327 alert("out of selection colors; go add more"); 328 return; 329 } 330 331 // set that as the selection 332 for (var i = 0; i < x.length; i++) { 333 x[i].classList.add(avail); 334 } 335 selected[avail] = c; 336 }; 337 338 var ssaValueClicked = function(event) { 339 ssaElemClicked(this, event, highlights, highlighted); 340 }; 341 342 var ssaBlockClicked = function(event) { 343 ssaElemClicked(this, event, outlines, outlined); 344 }; 345 346 var ssavalues = document.getElementsByClassName("ssa-value"); 347 for (var i = 0; i < ssavalues.length; i++) { 348 ssavalues[i].addEventListener('click', ssaValueClicked); 349 } 350 351 var ssalongvalues = document.getElementsByClassName("ssa-long-value"); 352 for (var i = 0; i < ssalongvalues.length; i++) { 353 // don't attach listeners to li nodes, just the spans they contain 354 if (ssalongvalues[i].nodeName == "SPAN") { 355 ssalongvalues[i].addEventListener('click', ssaValueClicked); 356 } 357 } 358 359 var ssablocks = document.getElementsByClassName("ssa-block"); 360 for (var i = 0; i < ssablocks.length; i++) { 361 ssablocks[i].addEventListener('click', ssaBlockClicked); 362 } 363 364 var lines = document.getElementsByClassName("line-number"); 365 for (var i = 0; i < lines.length; i++) { 366 lines[i].addEventListener('click', ssaValueClicked); 367 } 368 369 // Contains phase names which are expanded by default. Other columns are collapsed. 370 var expandedDefault = [ 371 "start", 372 "deadcode", 373 "opt", 374 "lower", 375 "late deadcode", 376 "regalloc", 377 "genssa", 378 ]; 379 380 function toggler(phase) { 381 return function() { 382 toggle_cell(phase+'-col'); 383 toggle_cell(phase+'-exp'); 384 }; 385 } 386 387 function toggle_cell(id) { 388 var e = document.getElementById(id); 389 if (e.style.display == 'table-cell') { 390 e.style.display = 'none'; 391 } else { 392 e.style.display = 'table-cell'; 393 } 394 } 395 396 // Go through all columns and collapse needed phases. 397 var td = document.getElementsByTagName("td"); 398 for (var i = 0; i < td.length; i++) { 399 var id = td[i].id; 400 var phase = id.substr(0, id.length-4); 401 var show = expandedDefault.indexOf(phase) !== -1 402 if (id.endsWith("-exp")) { 403 var h2 = td[i].getElementsByTagName("h2"); 404 if (h2 && h2[0]) { 405 h2[0].addEventListener('click', toggler(phase)); 406 } 407 } else { 408 td[i].addEventListener('click', toggler(phase)); 409 } 410 if (id.endsWith("-col") && show || id.endsWith("-exp") && !show) { 411 td[i].style.display = 'none'; 412 continue; 413 } 414 td[i].style.display = 'table-cell'; 415 } 416 }; 417 418 function toggle_visibility(id) { 419 var e = document.getElementById(id); 420 if (e.style.display == 'block') { 421 e.style.display = 'none'; 422 } else { 423 e.style.display = 'block'; 424 } 425 } 426 </script> 427 428 </head>`) 429 w.WriteString("<body>") 430 w.WriteString("<h1>") 431 w.WriteString(html.EscapeString(name)) 432 w.WriteString("</h1>") 433 w.WriteString(` 434 <a href="#" onclick="toggle_visibility('help');" id="helplink">help</a> 435 <div id="help"> 436 437 <p> 438 Click on a value or block to toggle highlighting of that value/block 439 and its uses. (Values and blocks are highlighted by ID, and IDs of 440 dead items may be reused, so not all highlights necessarily correspond 441 to the clicked item.) 442 </p> 443 444 <p> 445 Faded out values and blocks are dead code that has not been eliminated. 446 </p> 447 448 <p> 449 Values printed in italics have a dependency cycle. 450 </p> 451 452 </div> 453 `) 454 w.WriteString("<table>") 455 w.WriteString("<tr>") 456 } 457 458 func (w *HTMLWriter) Close() { 459 if w == nil { 460 return 461 } 462 io.WriteString(w.w, "</tr>") 463 io.WriteString(w.w, "</table>") 464 io.WriteString(w.w, "</body>") 465 io.WriteString(w.w, "</html>") 466 w.w.Close() 467 fmt.Printf("dumped SSA to %v\n", w.path) 468 } 469 470 // WriteFunc writes f in a column headed by title. 471 // phase is used for collapsing columns and should be unique across the table. 472 func (w *HTMLWriter) WriteFunc(phase, title string, f *Func) { 473 if w == nil { 474 return // avoid generating HTML just to discard it 475 } 476 w.WriteColumn(phase, title, "", f.HTML()) 477 // TODO: Add visual representation of f's CFG. 478 } 479 480 // FuncLines contains source code for a function to be displayed 481 // in sources column. 482 type FuncLines struct { 483 Filename string 484 StartLineno uint 485 Lines []string 486 } 487 488 // ByTopo sorts topologically: target function is on top, 489 // followed by inlined functions sorted by filename and line numbers. 490 type ByTopo []*FuncLines 491 492 func (x ByTopo) Len() int { return len(x) } 493 func (x ByTopo) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 494 func (x ByTopo) Less(i, j int) bool { 495 a := x[i] 496 b := x[j] 497 if a.Filename == b.Filename { 498 return a.StartLineno < b.StartLineno 499 } 500 return a.Filename < b.Filename 501 } 502 503 // WriteSources writes lines as source code in a column headed by title. 504 // phase is used for collapsing columns and should be unique across the table. 505 func (w *HTMLWriter) WriteSources(phase string, all []*FuncLines) { 506 if w == nil { 507 return // avoid generating HTML just to discard it 508 } 509 var buf bytes.Buffer 510 fmt.Fprint(&buf, "<div class=\"lines\" style=\"width: 8%\">") 511 filename := "" 512 for _, fl := range all { 513 fmt.Fprint(&buf, "<div> </div>") 514 if filename != fl.Filename { 515 fmt.Fprint(&buf, "<div> </div>") 516 filename = fl.Filename 517 } 518 for i := range fl.Lines { 519 ln := int(fl.StartLineno) + i 520 fmt.Fprintf(&buf, "<div class=\"l%v line-number\">%v</div>", ln, ln) 521 } 522 } 523 fmt.Fprint(&buf, "</div><div style=\"width: 92%\"><pre>") 524 filename = "" 525 for _, fl := range all { 526 fmt.Fprint(&buf, "<div> </div>") 527 if filename != fl.Filename { 528 fmt.Fprintf(&buf, "<div><strong>%v</strong></div>", fl.Filename) 529 filename = fl.Filename 530 } 531 for i, line := range fl.Lines { 532 ln := int(fl.StartLineno) + i 533 var escaped string 534 if strings.TrimSpace(line) == "" { 535 escaped = " " 536 } else { 537 escaped = html.EscapeString(line) 538 } 539 fmt.Fprintf(&buf, "<div class=\"l%v line-number\">%v</div>", ln, escaped) 540 } 541 } 542 fmt.Fprint(&buf, "</pre></div>") 543 w.WriteColumn(phase, phase, "allow-x-scroll", buf.String()) 544 } 545 546 func (w *HTMLWriter) WriteAST(phase string, buf *bytes.Buffer) { 547 if w == nil { 548 return // avoid generating HTML just to discard it 549 } 550 lines := strings.Split(buf.String(), "\n") 551 var out bytes.Buffer 552 553 fmt.Fprint(&out, "<div>") 554 for _, l := range lines { 555 l = strings.TrimSpace(l) 556 var escaped string 557 var lineNo string 558 if l == "" { 559 escaped = " " 560 } else { 561 if strings.HasPrefix(l, "buildssa") { 562 escaped = fmt.Sprintf("<b>%v</b>", l) 563 } else { 564 // Parse the line number from the format l(123). 565 idx := strings.Index(l, " l(") 566 if idx != -1 { 567 subl := l[idx+3:] 568 idxEnd := strings.Index(subl, ")") 569 if idxEnd != -1 { 570 if _, err := strconv.Atoi(subl[:idxEnd]); err == nil { 571 lineNo = subl[:idxEnd] 572 } 573 } 574 } 575 escaped = html.EscapeString(l) 576 } 577 } 578 if lineNo != "" { 579 fmt.Fprintf(&out, "<div class=\"l%v line-number ast\">%v</div>", lineNo, escaped) 580 } else { 581 fmt.Fprintf(&out, "<div class=\"ast\">%v</div>", escaped) 582 } 583 } 584 fmt.Fprint(&out, "</div>") 585 w.WriteColumn(phase, phase, "allow-x-scroll", out.String()) 586 } 587 588 // WriteColumn writes raw HTML in a column headed by title. 589 // It is intended for pre- and post-compilation log output. 590 func (w *HTMLWriter) WriteColumn(phase, title, class, html string) { 591 if w == nil { 592 return 593 } 594 id := strings.Replace(phase, " ", "-", -1) 595 // collapsed column 596 w.Printf("<td id=\"%v-col\" class=\"collapsed\"><div>%v</div></td>", id, phase) 597 598 if class == "" { 599 w.Printf("<td id=\"%v-exp\">", id) 600 } else { 601 w.Printf("<td id=\"%v-exp\" class=\"%v\">", id, class) 602 } 603 w.WriteString("<h2>" + title + "</h2>") 604 w.WriteString(html) 605 w.WriteString("</td>") 606 } 607 608 func (w *HTMLWriter) Printf(msg string, v ...interface{}) { 609 if _, err := fmt.Fprintf(w.w, msg, v...); err != nil { 610 w.Fatalf(src.NoXPos, "%v", err) 611 } 612 } 613 614 func (w *HTMLWriter) WriteString(s string) { 615 if _, err := io.WriteString(w.w, s); err != nil { 616 w.Fatalf(src.NoXPos, "%v", err) 617 } 618 } 619 620 func (v *Value) HTML() string { 621 // TODO: Using the value ID as the class ignores the fact 622 // that value IDs get recycled and that some values 623 // are transmuted into other values. 624 s := v.String() 625 return fmt.Sprintf("<span class=\"%s ssa-value\">%s</span>", s, s) 626 } 627 628 func (v *Value) LongHTML() string { 629 // TODO: Any intra-value formatting? 630 // I'm wary of adding too much visual noise, 631 // but a little bit might be valuable. 632 // We already have visual noise in the form of punctuation 633 // maybe we could replace some of that with formatting. 634 s := fmt.Sprintf("<span class=\"%s ssa-long-value\">", v.String()) 635 636 linenumber := "<span class=\"no-line-number\">(?)</span>" 637 if v.Pos.IsKnown() { 638 linenumber = fmt.Sprintf("<span class=\"l%v line-number\">(%s)</span>", v.Pos.LineNumber(), v.Pos.LineNumberHTML()) 639 } 640 641 s += fmt.Sprintf("%s %s = %s", v.HTML(), linenumber, v.Op.String()) 642 643 s += " <" + html.EscapeString(v.Type.String()) + ">" 644 s += html.EscapeString(v.auxString()) 645 for _, a := range v.Args { 646 s += fmt.Sprintf(" %s", a.HTML()) 647 } 648 r := v.Block.Func.RegAlloc 649 if int(v.ID) < len(r) && r[v.ID] != nil { 650 s += " : " + html.EscapeString(r[v.ID].String()) 651 } 652 var names []string 653 for name, values := range v.Block.Func.NamedValues { 654 for _, value := range values { 655 if value == v { 656 names = append(names, name.String()) 657 break // drop duplicates. 658 } 659 } 660 } 661 if len(names) != 0 { 662 s += " (" + strings.Join(names, ", ") + ")" 663 } 664 665 s += "</span>" 666 return s 667 } 668 669 func (b *Block) HTML() string { 670 // TODO: Using the value ID as the class ignores the fact 671 // that value IDs get recycled and that some values 672 // are transmuted into other values. 673 s := html.EscapeString(b.String()) 674 return fmt.Sprintf("<span class=\"%s ssa-block\">%s</span>", s, s) 675 } 676 677 func (b *Block) LongHTML() string { 678 // TODO: improve this for HTML? 679 s := fmt.Sprintf("<span class=\"%s ssa-block\">%s</span>", html.EscapeString(b.String()), html.EscapeString(b.Kind.String())) 680 if b.Aux != nil { 681 s += html.EscapeString(fmt.Sprintf(" {%v}", b.Aux)) 682 } 683 if b.Control != nil { 684 s += fmt.Sprintf(" %s", b.Control.HTML()) 685 } 686 if len(b.Succs) > 0 { 687 s += " →" // right arrow 688 for _, e := range b.Succs { 689 c := e.b 690 s += " " + c.HTML() 691 } 692 } 693 switch b.Likely { 694 case BranchUnlikely: 695 s += " (unlikely)" 696 case BranchLikely: 697 s += " (likely)" 698 } 699 if b.Pos.IsKnown() { 700 // TODO does not begin to deal with the full complexity of line numbers. 701 // Maybe we want a string/slice instead, of outer-inner when inlining. 702 s += fmt.Sprintf(" <span class=\"l%v line-number\">(%s)</span>", b.Pos.LineNumber(), b.Pos.LineNumberHTML()) 703 } 704 return s 705 } 706 707 func (f *Func) HTML() string { 708 var buf bytes.Buffer 709 fmt.Fprint(&buf, "<code>") 710 p := htmlFuncPrinter{w: &buf} 711 fprintFunc(p, f) 712 713 // fprintFunc(&buf, f) // TODO: HTML, not text, <br /> for line breaks, etc. 714 fmt.Fprint(&buf, "</code>") 715 return buf.String() 716 } 717 718 type htmlFuncPrinter struct { 719 w io.Writer 720 } 721 722 func (p htmlFuncPrinter) header(f *Func) {} 723 724 func (p htmlFuncPrinter) startBlock(b *Block, reachable bool) { 725 // TODO: Make blocks collapsable? 726 var dead string 727 if !reachable { 728 dead = "dead-block" 729 } 730 fmt.Fprintf(p.w, "<ul class=\"%s ssa-print-func %s\">", b, dead) 731 fmt.Fprintf(p.w, "<li class=\"ssa-start-block\">%s:", b.HTML()) 732 if len(b.Preds) > 0 { 733 io.WriteString(p.w, " ←") // left arrow 734 for _, e := range b.Preds { 735 pred := e.b 736 fmt.Fprintf(p.w, " %s", pred.HTML()) 737 } 738 } 739 io.WriteString(p.w, "</li>") 740 if len(b.Values) > 0 { // start list of values 741 io.WriteString(p.w, "<li class=\"ssa-value-list\">") 742 io.WriteString(p.w, "<ul>") 743 } 744 } 745 746 func (p htmlFuncPrinter) endBlock(b *Block) { 747 if len(b.Values) > 0 { // end list of values 748 io.WriteString(p.w, "</ul>") 749 io.WriteString(p.w, "</li>") 750 } 751 io.WriteString(p.w, "<li class=\"ssa-end-block\">") 752 fmt.Fprint(p.w, b.LongHTML()) 753 io.WriteString(p.w, "</li>") 754 io.WriteString(p.w, "</ul>") 755 // io.WriteString(p.w, "</span>") 756 } 757 758 func (p htmlFuncPrinter) value(v *Value, live bool) { 759 var dead string 760 if !live { 761 dead = "dead-value" 762 } 763 fmt.Fprintf(p.w, "<li class=\"ssa-long-value %s\">", dead) 764 fmt.Fprint(p.w, v.LongHTML()) 765 io.WriteString(p.w, "</li>") 766 } 767 768 func (p htmlFuncPrinter) startDepCycle() { 769 fmt.Fprintln(p.w, "<span class=\"depcycle\">") 770 } 771 772 func (p htmlFuncPrinter) endDepCycle() { 773 fmt.Fprintln(p.w, "</span>") 774 } 775 776 func (p htmlFuncPrinter) named(n LocalSlot, vals []*Value) { 777 fmt.Fprintf(p.w, "<li>name %s: ", n) 778 for _, val := range vals { 779 fmt.Fprintf(p.w, "%s ", val.HTML()) 780 } 781 fmt.Fprintf(p.w, "</li>") 782 }