diff --git a/src/cmd/compile/internal/gc/lex.go b/src/cmd/compile/internal/gc/lex.go index 51ad6162bf..46122d264d 100644 --- a/src/cmd/compile/internal/gc/lex.go +++ b/src/cmd/compile/internal/gc/lex.go @@ -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) } } diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 4d381e5070..a463f9dfc5 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -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 diff --git a/src/cmd/compile/internal/ssa/compile.go b/src/cmd/compile/internal/ssa/compile.go index dfead98c65..23dab9e273 100644 --- a/src/cmd/compile/internal/ssa/compile.go +++ b/src/cmd/compile/internal/ssa/compile.go @@ -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 %s", 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. diff --git a/src/cmd/compile/internal/ssa/config.go b/src/cmd/compile/internal/ssa/config.go index 81061a7219..8657509c5c 100644 --- a/src/cmd/compile/internal/ssa/config.go +++ b/src/cmd/compile/internal/ssa/config.go @@ -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 +} diff --git a/src/cmd/compile/internal/ssa/cse.go b/src/cmd/compile/internal/ssa/cse.go index f7958542aa..c44748535b 100644 --- a/src/cmd/compile/internal/ssa/cse.go +++ b/src/cmd/compile/internal/ssa/cse.go @@ -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) } } diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go index 6e101ec1cb..9441110769 100644 --- a/src/cmd/compile/internal/ssa/func.go +++ b/src/cmd/compile/internal/ssa/func.go @@ -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 { diff --git a/src/cmd/compile/internal/ssa/func_test.go b/src/cmd/compile/internal/ssa/func_test.go index 53213d2c11..fa6a1a8751 100644 --- a/src/cmd/compile/internal/ssa/func_test.go +++ b/src/cmd/compile/internal/ssa/func_test.go @@ -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.