2015-08-10 12:15:52 -07:00
|
|
|
// Copyright 2015 The Go Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
package ssa
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"fmt"
|
|
|
|
|
"html"
|
|
|
|
|
"io"
|
|
|
|
|
"os"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type HTMLWriter struct {
|
|
|
|
|
Logger
|
|
|
|
|
*os.File
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewHTMLWriter(path string, logger Logger, funcname string) *HTMLWriter {
|
|
|
|
|
out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Fatalf("%v", err)
|
|
|
|
|
}
|
|
|
|
|
html := HTMLWriter{File: out, Logger: logger}
|
|
|
|
|
html.start(funcname)
|
|
|
|
|
return &html
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *HTMLWriter) start(name string) {
|
|
|
|
|
if w == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.WriteString("<html>")
|
|
|
|
|
w.WriteString(`<head>
|
|
|
|
|
<style>
|
|
|
|
|
|
|
|
|
|
#helplink {
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
display: block;
|
|
|
|
|
margin-top: -15px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#help {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-04 17:33:56 -07:00
|
|
|
.stats {
|
|
|
|
|
font-size: 60%;
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-10 12:15:52 -07:00
|
|
|
table {
|
|
|
|
|
border: 1px solid black;
|
|
|
|
|
table-layout: fixed;
|
|
|
|
|
width: 300px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
th, td {
|
|
|
|
|
border: 1px solid black;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
width: 400px;
|
|
|
|
|
vertical-align: top;
|
|
|
|
|
padding: 5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
li {
|
|
|
|
|
list-style-type: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
li.ssa-long-value {
|
|
|
|
|
text-indent: -2em; /* indent wrapped lines */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
li.ssa-value-list {
|
|
|
|
|
display: inline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
li.ssa-start-block {
|
|
|
|
|
padding: 0;
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
li.ssa-end-block {
|
|
|
|
|
padding: 0;
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ul.ssa-print-func {
|
|
|
|
|
padding-left: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dl.ssa-gen {
|
|
|
|
|
padding-left: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dt.ssa-prog-src {
|
|
|
|
|
padding: 0;
|
|
|
|
|
margin: 0;
|
|
|
|
|
float: left;
|
|
|
|
|
width: 4em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dd.ssa-prog {
|
|
|
|
|
padding: 0;
|
|
|
|
|
margin-right: 0;
|
|
|
|
|
margin-left: 4em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dead-value {
|
|
|
|
|
color: gray;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dead-block {
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.depcycle {
|
|
|
|
|
font-style: italic;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.highlight-yellow { background-color: yellow; }
|
|
|
|
|
.highlight-aquamarine { background-color: aquamarine; }
|
|
|
|
|
.highlight-coral { background-color: coral; }
|
|
|
|
|
.highlight-lightpink { background-color: lightpink; }
|
|
|
|
|
.highlight-lightsteelblue { background-color: lightsteelblue; }
|
|
|
|
|
.highlight-palegreen { background-color: palegreen; }
|
|
|
|
|
.highlight-powderblue { background-color: powderblue; }
|
|
|
|
|
.highlight-lightgray { background-color: lightgray; }
|
|
|
|
|
|
|
|
|
|
.outline-blue { outline: blue solid 2px; }
|
|
|
|
|
.outline-red { outline: red solid 2px; }
|
|
|
|
|
.outline-blueviolet { outline: blueviolet solid 2px; }
|
|
|
|
|
.outline-darkolivegreen { outline: darkolivegreen solid 2px; }
|
|
|
|
|
.outline-fuchsia { outline: fuchsia solid 2px; }
|
|
|
|
|
.outline-sienna { outline: sienna solid 2px; }
|
|
|
|
|
.outline-gold { outline: gold solid 2px; }
|
|
|
|
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
<script type="text/javascript">
|
|
|
|
|
// ordered list of all available highlight colors
|
|
|
|
|
var highlights = [
|
|
|
|
|
"highlight-yellow",
|
|
|
|
|
"highlight-aquamarine",
|
|
|
|
|
"highlight-coral",
|
|
|
|
|
"highlight-lightpink",
|
|
|
|
|
"highlight-lightsteelblue",
|
|
|
|
|
"highlight-palegreen",
|
|
|
|
|
"highlight-lightgray"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// state: which value is highlighted this color?
|
|
|
|
|
var highlighted = {};
|
|
|
|
|
for (var i = 0; i < highlights.length; i++) {
|
|
|
|
|
highlighted[highlights[i]] = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ordered list of all available outline colors
|
|
|
|
|
var outlines = [
|
|
|
|
|
"outline-blue",
|
|
|
|
|
"outline-red",
|
|
|
|
|
"outline-blueviolet",
|
|
|
|
|
"outline-darkolivegreen",
|
|
|
|
|
"outline-fuchsia",
|
|
|
|
|
"outline-sienna",
|
|
|
|
|
"outline-gold"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// state: which value is outlined this color?
|
|
|
|
|
var outlined = {};
|
|
|
|
|
for (var i = 0; i < outlines.length; i++) {
|
|
|
|
|
outlined[outlines[i]] = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.onload = function() {
|
|
|
|
|
var ssaElemClicked = function(elem, event, selections, selected) {
|
|
|
|
|
event.stopPropagation()
|
|
|
|
|
|
|
|
|
|
// TODO: pushState with updated state and read it on page load,
|
|
|
|
|
// so that state can survive across reloads
|
|
|
|
|
|
|
|
|
|
// find all values with the same name
|
|
|
|
|
var c = elem.classList.item(0);
|
|
|
|
|
var x = document.getElementsByClassName(c);
|
|
|
|
|
|
|
|
|
|
// if selected, remove selections from all of them
|
|
|
|
|
// otherwise, attempt to add
|
|
|
|
|
|
|
|
|
|
var remove = "";
|
|
|
|
|
for (var i = 0; i < selections.length; i++) {
|
|
|
|
|
var color = selections[i];
|
|
|
|
|
if (selected[color] == c) {
|
|
|
|
|
remove = color;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (remove != "") {
|
|
|
|
|
for (var i = 0; i < x.length; i++) {
|
|
|
|
|
x[i].classList.remove(remove);
|
|
|
|
|
}
|
|
|
|
|
selected[remove] = "";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we're adding a selection
|
|
|
|
|
// find first available color
|
|
|
|
|
var avail = "";
|
|
|
|
|
for (var i = 0; i < selections.length; i++) {
|
|
|
|
|
var color = selections[i];
|
|
|
|
|
if (selected[color] == "") {
|
|
|
|
|
avail = color;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (avail == "") {
|
|
|
|
|
alert("out of selection colors; go add more");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// set that as the selection
|
|
|
|
|
for (var i = 0; i < x.length; i++) {
|
|
|
|
|
x[i].classList.add(avail);
|
|
|
|
|
}
|
|
|
|
|
selected[avail] = c;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var ssaValueClicked = function(event) {
|
|
|
|
|
ssaElemClicked(this, event, highlights, highlighted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ssaBlockClicked = function(event) {
|
|
|
|
|
ssaElemClicked(this, event, outlines, outlined);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ssavalues = document.getElementsByClassName("ssa-value");
|
|
|
|
|
for (var i = 0; i < ssavalues.length; i++) {
|
|
|
|
|
ssavalues[i].addEventListener('click', ssaValueClicked);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ssalongvalues = document.getElementsByClassName("ssa-long-value");
|
|
|
|
|
for (var i = 0; i < ssalongvalues.length; i++) {
|
|
|
|
|
// don't attach listeners to li nodes, just the spans they contain
|
|
|
|
|
if (ssalongvalues[i].nodeName == "SPAN") {
|
|
|
|
|
ssalongvalues[i].addEventListener('click', ssaValueClicked);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ssablocks = document.getElementsByClassName("ssa-block");
|
|
|
|
|
for (var i = 0; i < ssablocks.length; i++) {
|
|
|
|
|
ssablocks[i].addEventListener('click', ssaBlockClicked);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function toggle_visibility(id) {
|
|
|
|
|
var e = document.getElementById(id);
|
|
|
|
|
if(e.style.display == 'block')
|
|
|
|
|
e.style.display = 'none';
|
|
|
|
|
else
|
|
|
|
|
e.style.display = 'block';
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
</head>`)
|
|
|
|
|
// TODO: Add javascript click handlers for blocks
|
|
|
|
|
// to outline that block across all phases
|
|
|
|
|
w.WriteString("<body>")
|
|
|
|
|
w.WriteString("<h1>")
|
|
|
|
|
w.WriteString(html.EscapeString(name))
|
|
|
|
|
w.WriteString("</h1>")
|
|
|
|
|
w.WriteString(`
|
|
|
|
|
<a href="#" onclick="toggle_visibility('help');" id="helplink">help</a>
|
|
|
|
|
<div id="help">
|
|
|
|
|
|
|
|
|
|
<p>
|
|
|
|
|
Click on a value or block to toggle highlighting of that value/block and its uses.
|
|
|
|
|
Values and blocks are highlighted by ID, which may vary across passes.
|
|
|
|
|
(TODO: Fix this.)
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<p>
|
|
|
|
|
Faded out values and blocks are dead code that has not been eliminated.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<p>
|
|
|
|
|
Values printed in italics have a dependency cycle.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
`)
|
|
|
|
|
w.WriteString("<table>")
|
|
|
|
|
w.WriteString("<tr>")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *HTMLWriter) Close() {
|
|
|
|
|
if w == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.WriteString("</tr>")
|
|
|
|
|
w.WriteString("</table>")
|
|
|
|
|
w.WriteString("</body>")
|
|
|
|
|
w.WriteString("</html>")
|
|
|
|
|
w.File.Close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WriteFunc writes f in a column headed by title.
|
|
|
|
|
func (w *HTMLWriter) WriteFunc(title string, f *Func) {
|
|
|
|
|
if w == nil {
|
|
|
|
|
return // avoid generating HTML just to discard it
|
|
|
|
|
}
|
|
|
|
|
w.WriteColumn(title, f.HTML())
|
|
|
|
|
// TODO: Add visual representation of f's CFG.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WriteColumn writes raw HTML in a column headed by title.
|
|
|
|
|
// It is intended for pre- and post-compilation log output.
|
|
|
|
|
func (w *HTMLWriter) WriteColumn(title string, html string) {
|
|
|
|
|
if w == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.WriteString("<td>")
|
|
|
|
|
w.WriteString("<h2>" + title + "</h2>")
|
|
|
|
|
w.WriteString(html)
|
|
|
|
|
w.WriteString("</td>")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *HTMLWriter) Printf(msg string, v ...interface{}) {
|
|
|
|
|
if _, err := fmt.Fprintf(w.File, msg, v...); err != nil {
|
|
|
|
|
w.Fatalf("%v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *HTMLWriter) WriteString(s string) {
|
|
|
|
|
if _, err := w.File.WriteString(s); err != nil {
|
|
|
|
|
w.Fatalf("%v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *Value) HTML() string {
|
|
|
|
|
// TODO: Using the value ID as the class ignores the fact
|
|
|
|
|
// that value IDs get recycled and that some values
|
|
|
|
|
// are transmuted into other values.
|
|
|
|
|
return fmt.Sprintf("<span class=\"%[1]s ssa-value\">%[1]s</span>", v.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (v *Value) LongHTML() string {
|
|
|
|
|
// TODO: Any intra-value formatting?
|
|
|
|
|
// I'm wary of adding too much visual noise,
|
|
|
|
|
// but a little bit might be valuable.
|
|
|
|
|
// We already have visual noise in the form of punctuation
|
|
|
|
|
// maybe we could replace some of that with formatting.
|
|
|
|
|
s := fmt.Sprintf("<span class=\"%s ssa-long-value\">", v.String())
|
|
|
|
|
s += fmt.Sprintf("%s = %s", v.HTML(), v.Op.String())
|
|
|
|
|
s += " <" + html.EscapeString(v.Type.String()) + ">"
|
|
|
|
|
if v.AuxInt != 0 {
|
|
|
|
|
s += fmt.Sprintf(" [%d]", v.AuxInt)
|
|
|
|
|
}
|
|
|
|
|
if v.Aux != nil {
|
|
|
|
|
if _, ok := v.Aux.(string); ok {
|
|
|
|
|
s += html.EscapeString(fmt.Sprintf(" {%q}", v.Aux))
|
|
|
|
|
} else {
|
|
|
|
|
s += html.EscapeString(fmt.Sprintf(" {%v}", v.Aux))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, a := range v.Args {
|
|
|
|
|
s += fmt.Sprintf(" %s", a.HTML())
|
|
|
|
|
}
|
|
|
|
|
r := v.Block.Func.RegAlloc
|
2015-08-11 12:51:33 -07:00
|
|
|
if int(v.ID) < len(r) && r[v.ID] != nil {
|
2015-08-10 12:15:52 -07:00
|
|
|
s += " : " + r[v.ID].Name()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s += "</span>"
|
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Block) HTML() string {
|
|
|
|
|
// TODO: Using the value ID as the class ignores the fact
|
|
|
|
|
// that value IDs get recycled and that some values
|
|
|
|
|
// are transmuted into other values.
|
|
|
|
|
return fmt.Sprintf("<span class=\"%[1]s ssa-block\">%[1]s</span>", html.EscapeString(b.String()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *Block) LongHTML() string {
|
|
|
|
|
// TODO: improve this for HTML?
|
2015-08-27 10:29:01 -07:00
|
|
|
s := fmt.Sprintf("<span class=\"%s ssa-block\">%s</span>", html.EscapeString(b.String()), html.EscapeString(b.Kind.String()))
|
2015-08-10 12:15:52 -07:00
|
|
|
if b.Control != nil {
|
|
|
|
|
s += fmt.Sprintf(" %s", b.Control.HTML())
|
|
|
|
|
}
|
|
|
|
|
if len(b.Succs) > 0 {
|
|
|
|
|
s += " →" // right arrow
|
|
|
|
|
for _, c := range b.Succs {
|
|
|
|
|
s += " " + c.HTML()
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-08-17 00:29:56 -07:00
|
|
|
switch b.Likely {
|
|
|
|
|
case BranchUnlikely:
|
|
|
|
|
s += " (unlikely)"
|
|
|
|
|
case BranchLikely:
|
|
|
|
|
s += " (likely)"
|
|
|
|
|
}
|
2015-08-10 12:15:52 -07:00
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *Func) HTML() string {
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
fmt.Fprint(&buf, "<code>")
|
|
|
|
|
p := htmlFuncPrinter{w: &buf}
|
|
|
|
|
fprintFunc(p, f)
|
|
|
|
|
|
|
|
|
|
// fprintFunc(&buf, f) // TODO: HTML, not text, <br /> for line breaks, etc.
|
|
|
|
|
fmt.Fprint(&buf, "</code>")
|
|
|
|
|
return buf.String()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type htmlFuncPrinter struct {
|
|
|
|
|
w io.Writer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p htmlFuncPrinter) header(f *Func) {}
|
|
|
|
|
|
|
|
|
|
func (p htmlFuncPrinter) startBlock(b *Block, reachable bool) {
|
|
|
|
|
// TODO: Make blocks collapsable?
|
|
|
|
|
var dead string
|
|
|
|
|
if !reachable {
|
|
|
|
|
dead = "dead-block"
|
|
|
|
|
}
|
|
|
|
|
fmt.Fprintf(p.w, "<ul class=\"%s ssa-print-func %s\">", b, dead)
|
|
|
|
|
fmt.Fprintf(p.w, "<li class=\"ssa-start-block\">%s:", b.HTML())
|
|
|
|
|
if len(b.Preds) > 0 {
|
|
|
|
|
io.WriteString(p.w, " ←") // left arrow
|
|
|
|
|
for _, pred := range b.Preds {
|
|
|
|
|
fmt.Fprintf(p.w, " %s", pred.HTML())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
io.WriteString(p.w, "</li>")
|
|
|
|
|
if len(b.Values) > 0 { // start list of values
|
|
|
|
|
io.WriteString(p.w, "<li class=\"ssa-value-list\">")
|
|
|
|
|
io.WriteString(p.w, "<ul>")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p htmlFuncPrinter) endBlock(b *Block) {
|
|
|
|
|
if len(b.Values) > 0 { // end list of values
|
|
|
|
|
io.WriteString(p.w, "</ul>")
|
|
|
|
|
io.WriteString(p.w, "</li>")
|
|
|
|
|
}
|
|
|
|
|
io.WriteString(p.w, "<li class=\"ssa-end-block\">")
|
|
|
|
|
fmt.Fprint(p.w, b.LongHTML())
|
|
|
|
|
io.WriteString(p.w, "</li>")
|
|
|
|
|
io.WriteString(p.w, "</ul>")
|
|
|
|
|
// io.WriteString(p.w, "</span>")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p htmlFuncPrinter) value(v *Value, live bool) {
|
|
|
|
|
var dead string
|
|
|
|
|
if !live {
|
|
|
|
|
dead = "dead-value"
|
|
|
|
|
}
|
|
|
|
|
fmt.Fprintf(p.w, "<li class=\"ssa-long-value %s\">", dead)
|
|
|
|
|
fmt.Fprint(p.w, v.LongHTML())
|
|
|
|
|
io.WriteString(p.w, "</li>")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p htmlFuncPrinter) startDepCycle() {
|
|
|
|
|
fmt.Fprintln(p.w, "<span class=\"depcycle\">")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p htmlFuncPrinter) endDepCycle() {
|
|
|
|
|
fmt.Fprintln(p.w, "</span>")
|
|
|
|
|
}
|