diff options
| author | David Chase <drchase@google.com> | 2022-06-13 17:53:32 -0400 |
|---|---|---|
| committer | David Chase <drchase@google.com> | 2022-08-10 17:44:57 +0000 |
| commit | 522f0fc4251b4df23661f588eb9b0b40c4423bef (patch) | |
| tree | 784331bff9489ea5071685feb1636a49e26e892a /src/cmd/compile/internal/base/hashdebug.go | |
| parent | f19f6c79e48b0c7aeeeb26e07b626af11653dc99 (diff) | |
| download | go-522f0fc4251b4df23661f588eb9b0b40c4423bef.tar.xz | |
cmd/compile: refactor GOSSAHASH debugging to make it usable outside ssa package.
I've needed this more than once in the past, I hack it in,
then throw it away, seems sensible to make the change and
save it.
Fixes #53937.
Change-Id: I7fe886b1c93d73cbf553bed587f2c30f0f5d5a0b
Reviewed-on: https://go-review.googlesource.com/c/go/+/418015
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
Diffstat (limited to 'src/cmd/compile/internal/base/hashdebug.go')
| -rw-r--r-- | src/cmd/compile/internal/base/hashdebug.go | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/base/hashdebug.go b/src/cmd/compile/internal/base/hashdebug.go new file mode 100644 index 0000000000..ca2e8a2420 --- /dev/null +++ b/src/cmd/compile/internal/base/hashdebug.go @@ -0,0 +1,147 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package base + +import ( + "cmd/internal/notsha256" + "fmt" + "io" + "os" + "strings" + "sync" +) + +const GOSSAHASH = "GOSSAHASH" + +type writeSyncer interface { + io.Writer + Sync() error +} + +type HashDebug struct { + mu sync.Mutex + // what file (if any) receives the yes/no logging? + // default is os.Stdout + logfile writeSyncer +} + +var hd HashDebug + +// DebugHashMatch reports whether environment variable GOSSAHASH +// +// 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. OR +// if evname(i) is a suffix of the sha1 hash of name +// where evname(i)=fmt.Sprintf("GOSSAHASH%d", i), +// for 0<=i<n such that for all i evname(i) != "" and evname(n) == "" +// +// That is, as long as they're not empty, try GOSSAHASH, GOSSAHASH0, GOSSAHASH1, etc, +// but quit trying at the first empty environment variable substitution. +// +// Otherwise it returns false. +// Clause 4 is not really intended for human use. +// +// Unless GOSSAHASH is empty, when DebugHashMatch returns true 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. +// +// Typical use: +// +// 1. you make a change to the compiler, say, adding a new phase +// 2. it is broken in some mystifying way, for example, make.bash builds a broken +// compiler that almost works, but crashes compiling a test in run.bash. +// 3. add this guard to the code, which by default leaves it broken, but +// does not run the broken new code if GOSSAHASH is non-empty and non-matching: +// +// if !base.DebugHashMatch(ir.PkgFuncName(fn)) { +// return nil // early exit, do nothing +// } +// +// 4. rebuild w/o the bad code, GOSSAHASH=n ./all.bash to verify that you +// put theguard in the right place with the right sense of the test. +// 5. use github.com/dr2chase/gossahash to search for the error: +// +// go install github.com/dr2chase/gossahash@latest +// +// gossahash -- <the thing that fails> +// +// for example: GOMAXPROCS=1 gossahash -- ./all.bash +// 6. gossahash should return a single function whose miscompilation +// causes the problem, and you can focus on that. +// +func DebugHashMatch(pkgAndName string) bool { + return hd.DebugHashMatch(pkgAndName) +} + +func (d *HashDebug) DebugHashMatch(pkgAndName string) bool { + evname := GOSSAHASH + evhash := os.Getenv(evname) + hstr := "" + + switch evhash { + case "": + return true // default behavior with no EV is "on" + case "n", "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. + for _, b := range notsha256.Sum256([]byte(pkgAndName)) { + hstr += fmt.Sprintf("%08b", b) + } + + if evhash == "y" || evhash == "Y" || strings.HasSuffix(hstr, evhash) { + d.logDebugHashMatch(evname, pkgAndName, hstr) + 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) { + d.logDebugHashMatch(ev, pkgAndName, hstr) + return true + } + } + return false +} + +func (d *HashDebug) logDebugHashMatch(evname, name, hstr string) { + d.mu.Lock() + defer d.mu.Unlock() + file := d.logfile + if file == nil { + if tmpfile := os.Getenv("GSHS_LOGFILE"); tmpfile != "" { + var err error + file, err = os.OpenFile(tmpfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + Fatalf("could not open hash-testing logfile %s", tmpfile) + return + } + } + if file == nil { + file = os.Stdout + } + d.logfile = file + } + if len(hstr) > 24 { + hstr = hstr[len(hstr)-24:] + } + // External tools depend on this string + fmt.Fprintf(file, "%s triggered %s %s\n", evname, name, hstr) + file.Sync() +} |
