diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index b843ebf4370476..9dd28e38c3bf18 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -438,9 +438,16 @@ func Main(archInit func(*Arch)) { } ssaDump = os.Getenv("GOSSAFUNC") - if strings.HasSuffix(ssaDump, "+") { - ssaDump = ssaDump[:len(ssaDump)-1] - ssaDumpStdout = true + if ssaDump != "" { + if strings.HasSuffix(ssaDump, "+") { + ssaDump = ssaDump[:len(ssaDump)-1] + ssaDumpStdout = true + } + spl := strings.Split(ssaDump, ":") + if len(spl) > 1 { + ssaDump = spl[0] + ssaDumpCFG = spl[1] + } } trackScopes = flagDWARF diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 7a4152a9e6d489..27af607d6f0e50 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -26,6 +26,7 @@ var ssaCaches []ssa.Cache var ssaDump string // early copy of $GOSSAFUNC; the func name to dump output for var ssaDumpStdout bool // whether to dump to stdout +var ssaDumpCFG string // generate CFGs for these phases const ssaDumpFile = "ssa.html" // ssaDumpInlined holds all inlined functions when ssaDump contains a function name. @@ -155,7 +156,7 @@ func buildssa(fn *Node, worker int) *ssa.Func { s.softFloat = s.config.SoftFloat if printssa { - s.f.HTMLWriter = ssa.NewHTMLWriter(ssaDumpFile, s.f.Frontend(), name) + s.f.HTMLWriter = ssa.NewHTMLWriter(ssaDumpFile, s.f.Frontend(), name, ssaDumpCFG) // TODO: generate and print a mapping from nodes to values and blocks dumpSourcesColumn(s.f.HTMLWriter, fn) s.f.HTMLWriter.WriteAST("AST", astBuf) diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go index d73d39ce288f51..7e7e2042d9da54 100644 --- a/src/cmd/compile/internal/ssa/func.go +++ b/src/cmd/compile/internal/ssa/func.go @@ -43,6 +43,7 @@ type Func struct { PrintOrHtmlSSA bool // true if GOSSAFUNC matches, true even if fe.Log() (spew phase results to stdout) is false. scheduled bool // Values in Blocks are in final order + laidout bool // Blocks are ordered NoSplit bool // true if function is marked as nosplit. Used by schedule check pass. // when register allocation is done, maps value ids to locations diff --git a/src/cmd/compile/internal/ssa/html.go b/src/cmd/compile/internal/ssa/html.go index d76d7c7b334650..3ea83f90a271cb 100644 --- a/src/cmd/compile/internal/ssa/html.go +++ b/src/cmd/compile/internal/ssa/html.go @@ -11,6 +11,7 @@ import ( "html" "io" "os" + "os/exec" "path/filepath" "strconv" "strings" @@ -20,9 +21,10 @@ type HTMLWriter struct { Logger w io.WriteCloser path string + dot *dotWriter } -func NewHTMLWriter(path string, logger Logger, funcname string) *HTMLWriter { +func NewHTMLWriter(path string, logger Logger, funcname, cfgMask 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) @@ -32,6 +34,7 @@ func NewHTMLWriter(path string, logger Logger, funcname string) *HTMLWriter { logger.Fatalf(src.NoXPos, "%v", err) } html := HTMLWriter{w: out, Logger: logger, path: filepath.Join(pwd, path)} + html.dot = newDotWriter(cfgMask) html.start(funcname) return &html } @@ -211,6 +214,25 @@ dd.ssa-prog { color: gray; } +.zoom { + position: absolute; + float: left; + white-space: nowrap; + background-color: #eee; +} + +.zoom a:link, .zoom a:visited { + text-decoration: none; + color: blue; + font-size: 16px; + padding: 4px 2px; +} + +svg { + cursor: default; + outline: 1px solid #eee; +} + .highlight-aquamarine { background-color: aquamarine; } .highlight-coral { background-color: coral; } .highlight-lightpink { background-color: lightpink; } @@ -236,6 +258,18 @@ dd.ssa-prog { .outline-maroon { outline: maroon solid 2px; } .outline-black { outline: black solid 2px; } +ellipse.outline-blue { stroke-width: 2px; stroke: blue; } +ellipse.outline-red { stroke-width: 2px; stroke: red; } +ellipse.outline-blueviolet { stroke-width: 2px; stroke: blueviolet; } +ellipse.outline-darkolivegreen { stroke-width: 2px; stroke: darkolivegreen; } +ellipse.outline-fuchsia { stroke-width: 2px; stroke: fuchsia; } +ellipse.outline-sienna { stroke-width: 2px; stroke: sienna; } +ellipse.outline-gold { stroke-width: 2px; stroke: gold; } +ellipse.outline-orangered { stroke-width: 2px; stroke: orangered; } +ellipse.outline-teal { stroke-width: 2px; stroke: teal; } +ellipse.outline-maroon { stroke-width: 2px; stroke: maroon; } +ellipse.outline-black { stroke-width: 2px; stroke: black; } + + +// TODO: scale the graph with the viewBox attribute. +function graphReduce(id) { + var node = document.getElementById(id); + if (node) { + node.width.baseVal.value *= 0.9; + node.height.baseVal.value *= 0.9; + } + return false; +} + +function graphEnlarge(id) { + var node = document.getElementById(id); + if (node) { + node.width.baseVal.value *= 1.1; + node.height.baseVal.value *= 1.1; + } + return false; +} + +function makeDraggable(event) { + var svg = event.target; + if (window.PointerEvent) { + svg.addEventListener('pointerdown', startDrag); + svg.addEventListener('pointermove', drag); + svg.addEventListener('pointerup', endDrag); + svg.addEventListener('pointerleave', endDrag); + } else { + svg.addEventListener('mousedown', startDrag); + svg.addEventListener('mousemove', drag); + svg.addEventListener('mouseup', endDrag); + svg.addEventListener('mouseleave', endDrag); + } + + var point = svg.createSVGPoint(); + var isPointerDown = false; + var pointerOrigin; + var viewBox = svg.viewBox.baseVal; + + function getPointFromEvent (event) { + point.x = event.clientX; + point.y = event.clientY; + + // We get the current transformation matrix of the SVG and we inverse it + var invertedSVGMatrix = svg.getScreenCTM().inverse(); + return point.matrixTransform(invertedSVGMatrix); + } + + function startDrag(event) { + isPointerDown = true; + pointerOrigin = getPointFromEvent(event); + } + + function drag(event) { + if (!isPointerDown) { + return; + } + event.preventDefault(); + + var pointerPosition = getPointFromEvent(event); + viewBox.x -= (pointerPosition.x - pointerOrigin.x); + viewBox.y -= (pointerPosition.y - pointerOrigin.y); + } + + function endDrag(event) { + isPointerDown = false; + } +} `) w.WriteString("
") @@ -431,7 +564,7 @@ function toggle_visibility(id) { w.WriteString(html.EscapeString(name)) w.WriteString("") w.WriteString(` -help +help@@ -449,6 +582,11 @@ 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. +
+