import (
"bytes"
- "crypto/sha1"
"fmt"
"html"
"math"
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
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.
// 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
"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
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 {
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)
}
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.
package ssa
-import "cmd/internal/obj"
+import (
+ "cmd/internal/obj"
+ "crypto/sha1"
+ "fmt"
+ "os"
+ "strings"
+)
type Config struct {
arch string // "amd64", etc.
// 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
c.blocks[i].ID = ID(i)
}
+ c.logfiles = make(map[string]*os.File)
+
return c
}
}
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
+}