]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.ssa] cmd/compile: enhance command line option processing for SSA
authorDavid Chase <drchase@google.com>
Thu, 25 Feb 2016 18:10:51 +0000 (13:10 -0500)
committerDavid Chase <drchase@google.com>
Thu, 25 Feb 2016 20:32:15 +0000 (20:32 +0000)
The -d compiler flag can also specify ssa phase and flag,
for example -d=ssa/generic_cse/time,ssa/generic_cse/stats

Spaces in the phase names can be specified with an
underscore.  Flags currently parsed (not necessarily
recognized by the phases yet) are:

   on, off, mem, time, debug, stats, and test

On, off and time are handled in the harness,
debug, stats, and test are interpreted by the phase itself.

The pass is now attached to the Func being compiled, and a
new method logStats(key, ...value) on *Func to encourage a
semi-standardized format for that output.  Output fields
are separated by tabs to ease digestion by awk and
spreadsheets.  For example,
if f.pass.stats > 0 {
f.logStat("CSE REWRITES", rewrites)
}

Change-Id: I16db2b5af64c50ca9a47efeb51d961147a903abc
Reviewed-on: https://go-review.googlesource.com/19885
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Todd Neal <todd@tneal.org>
src/cmd/compile/internal/gc/lex.go
src/cmd/compile/internal/gc/ssa.go
src/cmd/compile/internal/ssa/compile.go
src/cmd/compile/internal/ssa/config.go
src/cmd/compile/internal/ssa/cse.go
src/cmd/compile/internal/ssa/func.go
src/cmd/compile/internal/ssa/func_test.go

index 51ad6162bf25aadc4efd2792225df981acb3972c..46122d264d2a4c352ad677174c8200fb089c5ed5 100644 (file)
@@ -55,7 +55,6 @@ var debugtab = []struct {
        {"typeassert", &Debug_typeassert}, // print information about type assertion inlining
        {"wb", &Debug_wb},                 // print information about write barriers
        {"export", &Debug_export},         // print export data
-       {"ssa", &ssa.Debug},               // ssa debugging flag
 }
 
 const (
@@ -286,6 +285,23 @@ func Main() {
                                        }
                                }
                        }
+                       // special case for ssa for now
+                       if strings.HasPrefix(name, "ssa/") {
+                               // expect form ssa/phase/flag
+                               // e.g. -d=ssa/generic_cse/time
+                               // _ in phase name also matches space
+                               phase := name[4:]
+                               flag := "debug" // default flag is debug
+                               if i := strings.Index(phase, "/"); i >= 0 {
+                                       flag = phase[i+1:]
+                                       phase = phase[:i]
+                               }
+                               err := ssa.PhaseOption(phase, flag, val)
+                               if err != "" {
+                                       log.Fatalf(err)
+                               }
+                               continue Split
+                       }
                        log.Fatalf("unknown debug key -d %s\n", name)
                }
        }
index 4d381e507060f809707958283928e04f1dbbd3a0..a463f9dfc50404ce7071d7fb48f0575578f6b0f8 100644 (file)
@@ -6,7 +6,6 @@ package gc
 
 import (
        "bytes"
-       "crypto/sha1"
        "fmt"
        "html"
        "math"
@@ -24,6 +23,15 @@ const minZeroPage = 4096
 var ssaConfig *ssa.Config
 var ssaExp ssaExport
 
+func initssa() *ssa.Config {
+       ssaExp.unimplemented = false
+       ssaExp.mustImplement = true
+       if ssaConfig == nil {
+               ssaConfig = ssa.NewConfig(Thearch.Thestring, &ssaExp, Ctxt, Debug['N'] == 0)
+       }
+       return ssaConfig
+}
+
 func shouldssa(fn *Node) bool {
        if Thearch.Thestring != "amd64" {
                return false
@@ -67,42 +75,7 @@ func shouldssa(fn *Node) bool {
                return localpkg.Name == pkg
        }
 
-       gossahash := os.Getenv("GOSSAHASH")
-       if gossahash == "" || gossahash == "y" || gossahash == "Y" {
-               return true
-       }
-       if gossahash == "n" || gossahash == "N" {
-               return false
-       }
-
-       // Check the hash of the name against a partial input hash.
-       // We use this feature to do a binary search within a package to
-       // find a function that is incorrectly compiled.
-       hstr := ""
-       for _, b := range sha1.Sum([]byte(name)) {
-               hstr += fmt.Sprintf("%08b", b)
-       }
-
-       if strings.HasSuffix(hstr, gossahash) {
-               fmt.Printf("GOSSAHASH triggered %s\n", name)
-               return true
-       }
-
-       // Iteratively try additional hashes to allow tests for multi-point
-       // failure.
-       for i := 0; true; i++ {
-               ev := fmt.Sprintf("GOSSAHASH%d", i)
-               evv := os.Getenv(ev)
-               if evv == "" {
-                       break
-               }
-               if strings.HasSuffix(hstr, evv) {
-                       fmt.Printf("%s triggered %s\n", ev, name)
-                       return true
-               }
-       }
-
-       return false
+       return initssa().DebugHashMatch("GOSSAHASH", name)
 }
 
 // buildssa builds an SSA function.
@@ -123,12 +96,8 @@ func buildssa(fn *Node) *ssa.Func {
        // TODO(khr): build config just once at the start of the compiler binary
 
        ssaExp.log = printssa
-       ssaExp.unimplemented = false
-       ssaExp.mustImplement = true
-       if ssaConfig == nil {
-               ssaConfig = ssa.NewConfig(Thearch.Thestring, &ssaExp, Ctxt, Debug['N'] == 0)
-       }
-       s.config = ssaConfig
+
+       s.config = initssa()
        s.f = s.config.NewFunc()
        s.f.Name = name
        s.exitCode = fn.Func.Exit
index dfead98c65825aa91d28a281ff21296a8e8635d6..23dab9e2735589bb96021d594b4819de604617fb 100644 (file)
@@ -8,11 +8,10 @@ import (
        "fmt"
        "log"
        "runtime"
+       "strings"
        "time"
 )
 
-var Debug int
-
 // Compile is the main entry point for this package.
 // Compile modifies f so that on return:
 //   · all Values in f map to 0 or 1 assembly instructions of the target architecture
@@ -47,22 +46,23 @@ func Compile(f *Func) {
                if !f.Config.optimize && !p.required {
                        continue
                }
+               f.pass = &p
                phaseName = p.name
                if f.Log() {
                        f.Logf("  pass %s begin\n", p.name)
                }
                // TODO: capture logging during this pass, add it to the HTML
                var mStart runtime.MemStats
-               if logMemStats {
+               if logMemStats || p.mem {
                        runtime.ReadMemStats(&mStart)
                }
 
                tStart := time.Now()
                p.fn(f)
+               tEnd := time.Now()
 
+               // Need something less crude than "Log the whole intermediate result".
                if f.Log() || f.Config.HTML != nil {
-                       tEnd := time.Now()
-
                        time := tEnd.Sub(tStart).Nanoseconds()
                        var stats string
                        if logMemStats {
@@ -79,6 +79,20 @@ func Compile(f *Func) {
                        printFunc(f)
                        f.Config.HTML.WriteFunc(fmt.Sprintf("after %s <span class=\"stats\">%s</span>", phaseName, stats), f)
                }
+               if p.time || p.mem {
+                       // Surround timing information w/ enough context to allow comparisons.
+                       time := tEnd.Sub(tStart).Nanoseconds()
+                       if p.time {
+                               f.logStat("TIME(ns)", time)
+                       }
+                       if p.mem {
+                               var mEnd runtime.MemStats
+                               runtime.ReadMemStats(&mEnd)
+                               nBytes := mEnd.TotalAlloc - mStart.TotalAlloc
+                               nAllocs := mEnd.Mallocs - mStart.Mallocs
+                               f.logStat("TIME(ns):BYTES:ALLOCS", time, nBytes, nAllocs)
+                       }
+               }
                checkFunc(f)
        }
 
@@ -90,39 +104,84 @@ type pass struct {
        name     string
        fn       func(*Func)
        required bool
+       disabled bool
+       time     bool // report time to run pass
+       mem      bool // report mem stats to run pass
+       stats    int  // pass reports own "stats" (e.g., branches removed)
+       debug    int  // pass performs some debugging. =1 should be in error-testing-friendly Warnl format.
+       test     int  // pass-specific ad-hoc option, perhaps useful in development
+}
+
+// PhaseOption sets the specified flag in the specified ssa phase,
+// returning empty string if this was successful or a string explaining
+// the error if it was not.  A version of the phase name with "_"
+// replaced by " " is also checked for a match.
+// See gc/lex.go for dissection of the option string.  Example use:
+// GO_GCFLAGS=-d=ssa/generic_cse/time,ssa/generic_cse/stats,ssa/generic_cse/debug=3 ./make.bash ...
+//
+func PhaseOption(phase, flag string, val int) string {
+       underphase := strings.Replace(phase, "_", " ", -1)
+       for i, p := range passes {
+               if p.name == phase || p.name == underphase {
+                       switch flag {
+                       case "on":
+                               p.disabled = val == 0
+                       case "off":
+                               p.disabled = val != 0
+                       case "time":
+                               p.time = val != 0
+                       case "mem":
+                               p.mem = val != 0
+                       case "debug":
+                               p.debug = val
+                       case "stats":
+                               p.stats = val
+                       case "test":
+                               p.test = val
+                       default:
+                               return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option", flag, phase)
+                       }
+                       if p.disabled && p.required {
+                               return fmt.Sprintf("Cannot disable required SSA phase %s using -d=ssa/%s debug option", phase, phase)
+                       }
+                       passes[i] = p
+                       return ""
+               }
+       }
+       return fmt.Sprintf("Did not find a phase matching %s in -d=ssa/... debug option", phase)
 }
 
 // list of passes for the compiler
 var passes = [...]pass{
        // TODO: combine phielim and copyelim into a single pass?
-       {"early phielim", phielim, false},
-       {"early copyelim", copyelim, false},
-       {"early deadcode", deadcode, false}, // remove generated dead code to avoid doing pointless work during opt
-       {"short circuit", shortcircuit, false},
-       {"decompose user", decomposeUser, true},
-       {"decompose builtin", decomposeBuiltIn, true},
-       {"opt", opt, true},                // TODO: split required rules and optimizing rules
-       {"zero arg cse", zcse, true},      // required to merge OpSB values
-       {"opt deadcode", deadcode, false}, // remove any blocks orphaned during opt
-       {"generic cse", cse, false},
-       {"nilcheckelim", nilcheckelim, false},
-       {"generic deadcode", deadcode, false},
-       {"fuse", fuse, false},
-       {"dse", dse, false},
-       {"tighten", tighten, false}, // move values closer to their uses
-       {"lower", lower, true},
-       {"lowered cse", cse, false},
-       {"lowered deadcode", deadcode, true},
-       {"checkLower", checkLower, true},
-       {"late phielim", phielim, false},
-       {"late copyelim", copyelim, false},
-       {"late deadcode", deadcode, false},
-       {"critical", critical, true},   // remove critical edges
-       {"layout", layout, true},       // schedule blocks
-       {"schedule", schedule, true},   // schedule values
-       {"flagalloc", flagalloc, true}, // allocate flags register
-       {"regalloc", regalloc, true},   // allocate int & float registers + stack slots
-       {"trim", trim, false},          // remove empty blocks
+       {name: "early phielim", fn: phielim},
+       {name: "early copyelim", fn: copyelim},
+       {name: "early deadcode", fn: deadcode}, // remove generated dead code to avoid doing pointless work during opt
+       {name: "short circuit", fn: shortcircuit},
+       {name: "decompose user", fn: decomposeUser, required: true},
+       {name: "decompose builtin", fn: decomposeBuiltIn, required: true},
+       {name: "opt", fn: opt, required: true},           // TODO: split required rules and optimizing rules
+       {name: "zero arg cse", fn: zcse, required: true}, // required to merge OpSB values
+       {name: "opt deadcode", fn: deadcode},             // remove any blocks orphaned during opt
+       {name: "generic cse", fn: cse},
+       {name: "nilcheckelim", fn: nilcheckelim},
+       {name: "generic deadcode", fn: deadcode},
+       {name: "fuse", fn: fuse},
+       {name: "dse", fn: dse},
+       {name: "tighten", fn: tighten}, // move values closer to their uses
+       {name: "lower", fn: lower, required: true},
+       {name: "lowered cse", fn: cse},
+       {name: "lowered deadcode", fn: deadcode, required: true},
+       {name: "checkLower", fn: checkLower, required: true},
+       {name: "late phielim", fn: phielim},
+       {name: "late copyelim", fn: copyelim},
+       {name: "late deadcode", fn: deadcode},
+       {name: "critical", fn: critical, required: true},   // remove critical edges
+       {name: "layout", fn: layout, required: true},       // schedule blocks
+       {name: "schedule", fn: schedule, required: true},   // schedule values
+       {name: "flagalloc", fn: flagalloc, required: true}, // allocate flags register
+       {name: "regalloc", fn: regalloc, required: true},   // allocate int & float registers + stack slots
+       {name: "trim", fn: trim},                           // remove empty blocks
 }
 
 // Double-check phase ordering constraints.
index 81061a7219bfa25d0f22ca2aca3a36be47c2d7e3..8657509c5ccc9c4e821481655a0617a482fb0143 100644 (file)
@@ -4,7 +4,13 @@
 
 package ssa
 
-import "cmd/internal/obj"
+import (
+       "cmd/internal/obj"
+       "crypto/sha1"
+       "fmt"
+       "os"
+       "strings"
+)
 
 type Config struct {
        arch       string                     // "amd64", etc.
@@ -20,6 +26,10 @@ type Config struct {
 
        // TODO: more stuff.  Compiler flags of interest, ...
 
+       // Given an environment variable used for debug hash match,
+       // what file (if any) receives the yes/no logging?
+       logfiles map[string]*os.File
+
        // Storage for low-numbered values and blocks.
        values [2000]Value
        blocks [200]Block
@@ -120,6 +130,8 @@ func NewConfig(arch string, fe Frontend, ctxt *obj.Link, optimize bool) *Config
                c.blocks[i].ID = ID(i)
        }
 
+       c.logfiles = make(map[string]*os.File)
+
        return c
 }
 
@@ -145,3 +157,79 @@ func (c *Config) Unimplementedf(line int32, msg string, args ...interface{}) {
 }
 func (c *Config) Warnl(line int, msg string, args ...interface{}) { c.fe.Warnl(line, msg, args...) }
 func (c *Config) Debug_checknil() bool                            { return c.fe.Debug_checknil() }
+
+func (c *Config) logDebugHashMatch(evname, name string) {
+       var file *os.File
+       file = c.logfiles[evname]
+       if file == nil {
+               file = os.Stdout
+               tmpfile := os.Getenv("GSHS_LOGFILE")
+               if tmpfile != "" {
+                       var ok error
+                       file, ok = os.Create(tmpfile)
+                       if ok != nil {
+                               c.Fatalf(0, "Could not open hash-testing logfile %s", tmpfile)
+                       }
+               }
+               c.logfiles[evname] = file
+       }
+       s := fmt.Sprintf("%s triggered %s\n", evname, name)
+       file.WriteString(s)
+       file.Sync()
+}
+
+// DebugHashMatch returns true if environment variable evname
+// 1) is empty (this is a special more-quickly implemented case of 3)
+// 2) is "y" or "Y"
+// 3) is a suffix of the sha1 hash of name
+// 4) is a suffix of the environment variable
+//    fmt.Sprintf("%s%d", evname, n)
+//    provided that all such variables are nonempty for 0 <= i <= n
+// Otherwise it returns false.
+// When true is returned the message
+//  "%s triggered %s\n", evname, name
+// is printed on the file named in environment variable
+//  GSHS_LOGFILE
+// or standard out if that is empty or there is an error
+// opening the file.
+
+func (c *Config) DebugHashMatch(evname, name string) bool {
+       evhash := os.Getenv(evname)
+       if evhash == "" {
+               return true // default behavior with no EV is "on"
+       }
+       if evhash == "y" || evhash == "Y" {
+               c.logDebugHashMatch(evname, name)
+               return true
+       }
+       if evhash == "n" || evhash == "N" {
+               return false
+       }
+       // Check the hash of the name against a partial input hash.
+       // We use this feature to do a binary search to
+       // find a function that is incorrectly compiled.
+       hstr := ""
+       for _, b := range sha1.Sum([]byte(name)) {
+               hstr += fmt.Sprintf("%08b", b)
+       }
+
+       if strings.HasSuffix(hstr, evhash) {
+               c.logDebugHashMatch(evname, name)
+               return true
+       }
+
+       // Iteratively try additional hashes to allow tests for multi-point
+       // failure.
+       for i := 0; true; i++ {
+               ev := fmt.Sprintf("%s%d", evname, i)
+               evv := os.Getenv(ev)
+               if evv == "" {
+                       break
+               }
+               if strings.HasSuffix(hstr, evv) {
+                       c.logDebugHashMatch(ev, name)
+                       return true
+               }
+       }
+       return false
+}
index f7958542aa3f75fbd35d5102a3cb6051a17aa232..c44748535b24c62748c7f8bb8631fbffb43a39fc 100644 (file)
@@ -61,7 +61,7 @@ func cse(f *Func) {
                }
        }
        for i, e := range partition {
-               if Debug > 1 && len(e) > 500 {
+               if f.pass.debug > 1 && len(e) > 500 {
                        fmt.Printf("CSE.large partition (%d): ", len(e))
                        for j := 0; j < 3; j++ {
                                fmt.Printf("%s ", e[j].LongString())
@@ -72,7 +72,7 @@ func cse(f *Func) {
                for _, v := range e {
                        valueEqClass[v.ID] = ID(i)
                }
-               if Debug > 2 && len(e) > 1 {
+               if f.pass.debug > 2 && len(e) > 1 {
                        fmt.Printf("CSE.partition #%d:", i)
                        for _, v := range e {
                                fmt.Printf(" %s", v.String())
@@ -163,7 +163,7 @@ func cse(f *Func) {
                }
        }
 
-       rewrites := 0
+       rewrites := int64(0)
 
        // Apply substitutions
        for _, b := range f.Blocks {
@@ -186,8 +186,8 @@ func cse(f *Func) {
                        }
                }
        }
-       if Debug > 0 && rewrites > 0 {
-               fmt.Printf("CSE: %d rewrites\n", rewrites)
+       if f.pass.stats > 0 {
+               f.logStat("CSE REWRITES", rewrites)
        }
 }
 
index 6e101ec1cbb3841d7957f360f875c4d187f8ce7d..9441110769f0c86b918f2e2c542c5ca69fa24df3 100644 (file)
@@ -4,12 +4,16 @@
 
 package ssa
 
-import "math"
+import (
+       "fmt"
+       "math"
+)
 
 // A Func represents a Go func declaration (or function literal) and
 // its body.  This package compiles each Func independently.
 type Func struct {
        Config     *Config     // architecture information
+       pass       *pass       // current pass information (name, options, etc.)
        Name       string      // e.g. bytes·Compare
        Type       Type        // type signature of the function.
        StaticData interface{} // associated static data, untouched by the ssa package
@@ -89,6 +93,20 @@ func (f *Func) newValue(op Op, t Type, b *Block, line int32) *Value {
        return v
 }
 
+// logPassStat writes a string key and int value as a warning in a
+// tab-separated format easily handled by spreadsheets or awk.
+// file names, lines, and function names are included to provide enough (?)
+// context to allow item-by-item comparisons across runs.
+// For example:
+// awk 'BEGIN {FS="\t"} $3~/TIME/{sum+=$4} END{print "t(ns)=",sum}' t.log
+func (f *Func) logStat(key string, args ...interface{}) {
+       value := ""
+       for _, a := range args {
+               value += fmt.Sprintf("\t%v", a)
+       }
+       f.Config.Warnl(int(f.Entry.Line), "\t%s\t%s%s\t%s", f.pass.name, key, value, f.Name)
+}
+
 // freeValue frees a value.  It must no longer be referenced.
 func (f *Func) freeValue(v *Value) {
        if v.Block == nil {
index 53213d2c11ecfe67a51773555e6f40e0d6112f56..fa6a1a8751b07e7e18819a6f974811e36cc74e03 100644 (file)
@@ -134,12 +134,18 @@ type fun struct {
        values map[string]*Value
 }
 
+var emptyPass pass = pass{
+       name: "empty pass",
+}
+
 // Fun takes the name of an entry bloc and a series of Bloc calls, and
 // returns a fun containing the composed Func. entry must be a name
 // supplied to one of the Bloc functions. Each of the bloc names and
 // valu names should be unique across the Fun.
 func Fun(c *Config, entry string, blocs ...bloc) fun {
        f := c.NewFunc()
+       f.pass = &emptyPass
+
        blocks := make(map[string]*Block)
        values := make(map[string]*Value)
        // Create all the blocks and values.