// 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"
"cmd/internal/src"
"fmt"
"html"
"io"
"os"
"path/filepath"
"strconv"
"strings"
)
type HTMLWriter struct {
Logger
w io.WriteCloser
path string
}
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(src.NoXPos, "%v", err)
}
pwd, err := os.Getwd()
if err != nil {
logger.Fatalf(src.NoXPos, "%v", err)
}
html := HTMLWriter{w: out, Logger: logger, path: filepath.Join(pwd, path)}
html.start(funcname)
return &html
}
func (w *HTMLWriter) start(name string) {
if w == nil {
return
}
w.WriteString("")
w.WriteString(`
`)
w.WriteString("")
w.WriteString("")
w.WriteString(html.EscapeString(name))
w.WriteString("
")
w.WriteString(`
help
Click on a value or block to toggle highlighting of that value/block
and its uses. (Values and blocks are highlighted by ID, and IDs of
dead items may be reused, so not all highlights necessarily correspond
to the clicked item.)
Faded out values and blocks are dead code that has not been eliminated.
Values printed in italics have a dependency cycle.
`)
w.WriteString("")
w.WriteString("")
}
func (w *HTMLWriter) Close() {
if w == nil {
return
}
io.WriteString(w.w, "
")
io.WriteString(w.w, "
")
io.WriteString(w.w, "")
io.WriteString(w.w, "")
w.w.Close()
fmt.Printf("dumped SSA to %v\n", w.path)
}
// WriteFunc writes f in a column headed by title.
// phase is used for collapsing columns and should be unique across the table.
func (w *HTMLWriter) WriteFunc(phase, title string, f *Func) {
if w == nil {
return // avoid generating HTML just to discard it
}
w.WriteColumn(phase, title, "", f.HTML())
// TODO: Add visual representation of f's CFG.
}
// FuncLines contains source code for a function to be displayed
// in sources column.
type FuncLines struct {
Filename string
StartLineno uint
Lines []string
}
// ByTopo sorts topologically: target function is on top,
// followed by inlined functions sorted by filename and line numbers.
type ByTopo []*FuncLines
func (x ByTopo) Len() int { return len(x) }
func (x ByTopo) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByTopo) Less(i, j int) bool {
a := x[i]
b := x[j]
if a.Filename == b.Filename {
return a.StartLineno < b.StartLineno
}
return a.Filename < b.Filename
}
// WriteSources writes lines as source code in a column headed by title.
// phase is used for collapsing columns and should be unique across the table.
func (w *HTMLWriter) WriteSources(phase string, all []*FuncLines) {
if w == nil {
return // avoid generating HTML just to discard it
}
var buf bytes.Buffer
fmt.Fprint(&buf, "")
filename := ""
for _, fl := range all {
fmt.Fprint(&buf, "
")
if filename != fl.Filename {
fmt.Fprint(&buf, "
")
filename = fl.Filename
}
for i := range fl.Lines {
ln := int(fl.StartLineno) + i
fmt.Fprintf(&buf, "
%v
", ln, ln)
}
}
fmt.Fprint(&buf, "
")
filename = ""
for _, fl := range all {
fmt.Fprint(&buf, "
")
if filename != fl.Filename {
fmt.Fprintf(&buf, "%v
", fl.Filename)
filename = fl.Filename
}
for i, line := range fl.Lines {
ln := int(fl.StartLineno) + i
var escaped string
if strings.TrimSpace(line) == "" {
escaped = " "
} else {
escaped = html.EscapeString(line)
}
fmt.Fprintf(&buf, "%v
", ln, escaped)
}
}
fmt.Fprint(&buf, " ")
w.WriteColumn(phase, phase, "allow-x-scroll", buf.String())
}
func (w *HTMLWriter) WriteAST(phase string, buf *bytes.Buffer) {
if w == nil {
return // avoid generating HTML just to discard it
}
lines := strings.Split(buf.String(), "\n")
var out bytes.Buffer
fmt.Fprint(&out, "")
for _, l := range lines {
l = strings.TrimSpace(l)
var escaped string
var lineNo string
if l == "" {
escaped = " "
} else {
if strings.HasPrefix(l, "buildssa") {
escaped = fmt.Sprintf("
%v", l)
} else {
// Parse the line number from the format l(123).
idx := strings.Index(l, " l(")
if idx != -1 {
subl := l[idx+3:]
idxEnd := strings.Index(subl, ")")
if idxEnd != -1 {
if _, err := strconv.Atoi(subl[:idxEnd]); err == nil {
lineNo = subl[:idxEnd]
}
}
}
escaped = html.EscapeString(l)
}
}
if lineNo != "" {
fmt.Fprintf(&out, "
%v
", lineNo, escaped)
} else {
fmt.Fprintf(&out, "
%v
", escaped)
}
}
fmt.Fprint(&out, "
")
w.WriteColumn(phase, phase, "allow-x-scroll", out.String())
}
// WriteColumn writes raw HTML in a column headed by title.
// It is intended for pre- and post-compilation log output.
func (w *HTMLWriter) WriteColumn(phase, title, class, html string) {
if w == nil {
return
}
id := strings.Replace(phase, " ", "-", -1)
// collapsed column
w.Printf("%v | ", id, phase)
if class == "" {
w.Printf("", id)
} else {
w.Printf(" | ", id, class)
}
w.WriteString("" + title + "")
w.WriteString(html)
w.WriteString(" | ")
}
func (w *HTMLWriter) Printf(msg string, v ...interface{}) {
if _, err := fmt.Fprintf(w.w, msg, v...); err != nil {
w.Fatalf(src.NoXPos, "%v", err)
}
}
func (w *HTMLWriter) WriteString(s string) {
if _, err := io.WriteString(w.w, s); err != nil {
w.Fatalf(src.NoXPos, "%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.
s := v.String()
return fmt.Sprintf("%s", s, s)
}
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("", v.String())
linenumber := "(?)"
if v.Pos.IsKnown() {
linenumber = fmt.Sprintf("(%s)", v.Pos.LineNumber(), v.Pos.LineNumberHTML())
}
s += fmt.Sprintf("%s %s = %s", v.HTML(), linenumber, v.Op.String())
s += " <" + html.EscapeString(v.Type.String()) + ">"
s += html.EscapeString(v.auxString())
for _, a := range v.Args {
s += fmt.Sprintf(" %s", a.HTML())
}
r := v.Block.Func.RegAlloc
if int(v.ID) < len(r) && r[v.ID] != nil {
s += " : " + html.EscapeString(r[v.ID].String())
}
var names []string
for name, values := range v.Block.Func.NamedValues {
for _, value := range values {
if value == v {
names = append(names, name.String())
break // drop duplicates.
}
}
}
if len(names) != 0 {
s += " (" + strings.Join(names, ", ") + ")"
}
s += ""
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.
s := html.EscapeString(b.String())
return fmt.Sprintf("%s", s, s)
}
func (b *Block) LongHTML() string {
// TODO: improve this for HTML?
s := fmt.Sprintf("%s", html.EscapeString(b.String()), html.EscapeString(b.Kind.String()))
if b.Aux != nil {
s += html.EscapeString(fmt.Sprintf(" {%v}", b.Aux))
}
if b.Control != nil {
s += fmt.Sprintf(" %s", b.Control.HTML())
}
if len(b.Succs) > 0 {
s += " →" // right arrow
for _, e := range b.Succs {
c := e.b
s += " " + c.HTML()
}
}
switch b.Likely {
case BranchUnlikely:
s += " (unlikely)"
case BranchLikely:
s += " (likely)"
}
if b.Pos.IsKnown() {
// TODO does not begin to deal with the full complexity of line numbers.
// Maybe we want a string/slice instead, of outer-inner when inlining.
s += fmt.Sprintf(" (%s)", b.Pos.LineNumber(), b.Pos.LineNumberHTML())
}
return s
}
func (f *Func) HTML() string {
var buf bytes.Buffer
fmt.Fprint(&buf, "")
p := htmlFuncPrinter{w: &buf}
fprintFunc(p, f)
// fprintFunc(&buf, f) // TODO: HTML, not text,
for line breaks, etc.
fmt.Fprint(&buf, "")
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, "", b, dead)
fmt.Fprintf(p.w, "- %s:", b.HTML())
if len(b.Preds) > 0 {
io.WriteString(p.w, " ←") // left arrow
for _, e := range b.Preds {
pred := e.b
fmt.Fprintf(p.w, " %s", pred.HTML())
}
}
io.WriteString(p.w, "
")
if len(b.Values) > 0 { // start list of values
io.WriteString(p.w, "- ")
io.WriteString(p.w, "
")
}
}
func (p htmlFuncPrinter) endBlock(b *Block) {
if len(b.Values) > 0 { // end list of values
io.WriteString(p.w, "
")
io.WriteString(p.w, " ")
}
io.WriteString(p.w, "- ")
fmt.Fprint(p.w, b.LongHTML())
io.WriteString(p.w, "
")
io.WriteString(p.w, "
")
// io.WriteString(p.w, "")
}
func (p htmlFuncPrinter) value(v *Value, live bool) {
var dead string
if !live {
dead = "dead-value"
}
fmt.Fprintf(p.w, "", dead)
fmt.Fprint(p.w, v.LongHTML())
io.WriteString(p.w, "")
}
func (p htmlFuncPrinter) startDepCycle() {
fmt.Fprintln(p.w, "")
}
func (p htmlFuncPrinter) endDepCycle() {
fmt.Fprintln(p.w, "")
}
func (p htmlFuncPrinter) named(n LocalSlot, vals []*Value) {
fmt.Fprintf(p.w, "name %s: ", n)
for _, val := range vals {
fmt.Fprintf(p.w, "%s ", val.HTML())
}
fmt.Fprintf(p.w, "")
}