aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/base
diff options
context:
space:
mode:
authorDavid Chase <drchase@google.com>2022-06-13 17:53:32 -0400
committerDavid Chase <drchase@google.com>2022-08-10 17:44:57 +0000
commit522f0fc4251b4df23661f588eb9b0b40c4423bef (patch)
tree784331bff9489ea5071685feb1636a49e26e892a /src/cmd/compile/internal/base
parentf19f6c79e48b0c7aeeeb26e07b626af11653dc99 (diff)
downloadgo-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')
-rw-r--r--src/cmd/compile/internal/base/hashdebug.go147
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()
+}