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.
CFG: Dashed edge is for unlikely branches. Blue color is for backward edges.
Edge with a dot means that this edge follows the order in which blocks were laidout.
`)
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 for %s to %v\n", w.Func.NameABI(), w.path)
}
// WritePhase writes f in a column headed by title.
// phase is used for collapsing columns and should be unique across the table.
func (w *HTMLWriter) WritePhase(phase, title string) {
if w == nil {
return // avoid generating HTML just to discard it
}
hash := ssa.HashFunc(w.Func)
w.pendingPhases = append(w.pendingPhases, phase)
w.pendingTitles = append(w.pendingTitles, title)
if !bytes.Equal(hash, w.prevHash) {
w.FlushPhases()
}
w.prevHash = hash
}
// FatalCleanup should be called to do cleanup if the compilation is exiting early due to
// a fatal error.
func (w *HTMLWriter) FatalCleanup() {
const stats = "crashed"
w.WritePhase(w.Func.Pass.Name, fmt.Sprintf("%s %s", w.Func.Pass.Name, stats))
w.FlushPhases()
}
// FlushPhases collects any pending phases and titles, writes them to the html, and resets the pending slices.
func (w *HTMLWriter) FlushPhases() {
if w == nil {
return
}
phaseLen := len(w.pendingPhases)
if phaseLen == 0 {
return
}
phases := strings.Join(w.pendingPhases, " + ")
w.WriteMultiTitleColumn(
phases,
w.pendingTitles,
fmt.Sprintf("hash-%x", w.prevHash),
HTML(w.Func, w.pendingPhases[phaseLen-1], w.dot),
)
w.pendingPhases = w.pendingPhases[:0]
w.pendingTitles = w.pendingTitles[:0]
}
// FuncLines contains source code for a function to be displayed
// in sources column.
type FuncLines struct {
Filename string
StartLineno uint
Lines []string
}
// ByTopoCmp sorts topologically: target function is on top,
// followed by inlined functions sorted by filename and line numbers.
func ByTopoCmp(a, b *FuncLines) int {
if r := strings.Compare(a.Filename, b.Filename); r != 0 {
return r
}
return cmp.Compare(a.StartLineno, b.StartLineno)
}
// 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 strings.Builder
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 strings.Builder
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 file:line:col.
// See the implementation in ir/fmt.go:dumpNodeHeader.
sl := strings.Split(l, ":")
if len(sl) >= 3 {
if _, err := strconv.Atoi(sl[len(sl)-2]); err == nil {
lineNo = sl[len(sl)-2]
}
}
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) {
w.WriteMultiTitleColumn(phase, []string{title}, class, html)
}
func (w *HTMLWriter) WriteMultiTitleColumn(phase string, titles []string, class, html string) {
if w == nil {
return
}
id := strings.ReplaceAll(phase, " ", "-")
// collapsed column
w.Printf("
%v
", id, phase)
if class == "" {
w.Printf("
", id)
} else {
w.Printf("
", id, class)
}
for _, title := range titles {
w.WriteString("
" + title + "
")
}
w.WriteString(html)
w.WriteString("
\n")
}
func (w *HTMLWriter) Printf(msg string, v ...any) {
if _, err := fmt.Fprintf(w.w, msg, v...); err != nil {
w.Fatalf("%v", err)
}
}
func (w *HTMLWriter) WriteString(s string) {
if _, err := io.WriteString(w.w, s); err != nil {
w.Fatalf("%v", err)
}
}
func HTML(f *ssa.Func, phase string, dot *dotWriter) string {
buf := new(strings.Builder)
if dot != nil {
dot.writeFuncSVG(buf, phase, f)
}
fmt.Fprint(buf, "")
p := htmlFuncPrinter{w: buf}
ssa.FprintFunc(p, f)
// fprintFunc(&buf, f) // TODO: HTML, not text, for line breaks, etc.
fmt.Fprint(buf, "")
return buf.String()
}
func (d *dotWriter) writeFuncSVG(w io.Writer, phase string, f *ssa.Func) {
if d.broken {
return
}
if _, ok := d.phases[phase]; !ok {
return
}
cmd := exec.Command(d.path, "-Tsvg")
pipe, err := cmd.StdinPipe()
if err != nil {
d.broken = true
fmt.Println(err)
return
}
buf := new(bytes.Buffer)
cmd.Stdout = buf
bufErr := new(strings.Builder)
cmd.Stderr = bufErr
err = cmd.Start()
if err != nil {
d.broken = true
fmt.Println(err)
return
}
fmt.Fprint(pipe, `digraph "" { margin=0; ranksep=.2; `)
id := strings.ReplaceAll(phase, " ", "-")
fmt.Fprintf(pipe, `id="g_graph_%s";`, id)
fmt.Fprintf(pipe, `node [style=filled,fillcolor=white,fontsize=16,fontname="Menlo,Times,serif",margin="0.01,0.03"];`)
fmt.Fprintf(pipe, `edge [fontsize=16,fontname="Menlo,Times,serif"];`)
for i, b := range f.Blocks {
if b.Kind == block.BlockInvalid {
continue
}
layout := ""
if f.Laidout {
layout = fmt.Sprintf(" #%d", i)
}
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())
}
indexOf := make([]int, f.NumBlocks())
for i, b := range f.Blocks {
indexOf[b.ID] = i
}
layoutDrawn := make([]bool, f.NumBlocks())
ponums := make([]int32, f.NumBlocks())
_ = ssa.PostorderWithNumbering(f, ponums)
isBackEdge := func(from, to ssa.ID) bool {
return ponums[from] <= ponums[to]
}
for _, b := range f.Blocks {
for i, s := range b.Succs {
style := "solid"
color := "black"
arrow := "vee"
if b.UnlikelyIndex() == i {
style = "dashed"
}
if f.Laidout && indexOf[s.B.ID] == indexOf[b.ID]+1 {
// Red color means ordered edge. It overrides other colors.
arrow = "dotvee"
layoutDrawn[s.B.ID] = true
} else if isBackEdge(b.ID, s.B.ID) {
color = "#2893ff"
}
fmt.Fprintf(pipe, `%v -> %v [label=" %d ",style="%s",color="%s",arrowhead="%s"];`, b, s.B, i, style, color, arrow)
}
}
if f.Laidout {
fmt.Fprintln(pipe, `edge[constraint=false,color=gray,style=solid,arrowhead=dot];`)
colors := [...]string{"#eea24f", "#f38385", "#f4d164", "#ca89fc", "gray"}
ci := 0
for i := 1; i < len(f.Blocks); i++ {
if layoutDrawn[f.Blocks[i].ID] {
continue
}
fmt.Fprintf(pipe, `%s -> %s [color="%s"];`, f.Blocks[i-1], f.Blocks[i], colors[ci])
ci = (ci + 1) % len(colors)
}
}
fmt.Fprint(pipe, "}")
pipe.Close()
err = cmd.Wait()
if err != nil {
d.broken = true
fmt.Printf("dot: %v\n%v\n", err, bufErr.String())
return
}
svgID := "svg_graph_" + id
fmt.Fprintf(w, `
`, svgID, svgID)
// For now, an awful hack: edit the html as it passes through
// our fingers, finding '