From d33043d37dc8cc5d691ff590ebcd2fd42e356a66 Mon Sep 17 00:00:00 2001 From: David Chase Date: Mon, 7 Nov 2022 11:02:08 -0500 Subject: [PATCH] cmd/compile: add ability to hash-debug on file:line, including inlining Modified the fmahash gc debug flag to use this, and modified the test to check for a hash match that includes inlining. Also made the test non-short to ensure portability. Note fma.go has been enhanced into an FMA test that requires two separate FMAs in order to "fail"; if either one is 2-rounding, then it "passes". (It neither passes nor fails here; its role is to demonstrate that the FMAs are correctly reported; the enhanced failure mode was discovered while testing the search tool.) Change-Id: I4e328e3654f442d498eac982135420abb59c5434 Reviewed-on: https://go-review.googlesource.com/c/go/+/448358 TryBot-Result: Gopher Robot Reviewed-by: Keith Randall Reviewed-by: Than McIntosh Run-TryBot: David Chase --- src/cmd/compile/internal/base/hashdebug.go | 84 ++++++++++++++++++-- src/cmd/compile/internal/ssa/fmahash_test.go | 15 ++-- src/cmd/compile/internal/ssa/func.go | 5 +- src/cmd/compile/internal/ssa/testdata/fma.go | 8 +- 4 files changed, 95 insertions(+), 17 deletions(-) diff --git a/src/cmd/compile/internal/base/hashdebug.go b/src/cmd/compile/internal/base/hashdebug.go index 609f80393e..6c4821bbf6 100644 --- a/src/cmd/compile/internal/base/hashdebug.go +++ b/src/cmd/compile/internal/base/hashdebug.go @@ -5,7 +5,10 @@ package base import ( + "bytes" "cmd/internal/notsha256" + "cmd/internal/obj" + "cmd/internal/src" "fmt" "io" "os" @@ -27,13 +30,15 @@ type hashAndMask struct { } type HashDebug struct { - mu sync.Mutex - name string // base name of the flag/variable. + mu sync.Mutex // for logfile, posTmp, bytesTmp + name string // base name of the flag/variable. // what file (if any) receives the yes/no logging? // default is os.Stdout - logfile writeSyncer - matches []hashAndMask // A hash matches if one of these matches. - yes, no bool + logfile writeSyncer + posTmp []src.Pos + bytesTmp bytes.Buffer + matches []hashAndMask // A hash matches if one of these matches. + yes, no bool } // The default compiler-debugging HashDebug, for "-d=gossahash=..." @@ -152,7 +157,11 @@ func NewHashDebug(ev, s string, file writeSyncer) *HashDebug { } func hashOf(pkgAndName string, param uint64) uint64 { - hbytes := notsha256.Sum256([]byte(pkgAndName)) + return hashOfBytes([]byte(pkgAndName), param) +} + +func hashOfBytes(sbytes []byte, param uint64) uint64 { + hbytes := notsha256.Sum256(sbytes) hash := uint64(hbytes[7])<<56 + uint64(hbytes[6])<<48 + uint64(hbytes[5])<<40 + uint64(hbytes[4])<<32 + uint64(hbytes[3])<<24 + uint64(hbytes[2])<<16 + @@ -196,6 +205,7 @@ func (d *HashDebug) DebugHashMatchParam(pkgAndName string, param uint64) bool { if d.no { return false } + if d.yes { d.logDebugHashMatch(d.name, pkgAndName, "y", param) return true @@ -220,9 +230,71 @@ func (d *HashDebug) DebugHashMatchParam(pkgAndName string, param uint64) bool { return false } +// DebugHashMatchPos is similar to DebugHashMatchParam, but for hash computation +// it uses the source position including all inlining information instead of +// package name and path. The output trigger string is prefixed with "POS=" so +// that tools processing the output can reliably tell the difference. The mutex +// locking is also more frequent and more granular. +func (d *HashDebug) DebugHashMatchPos(ctxt *obj.Link, pos src.XPos) bool { + if d == nil { + return true + } + if d.no { + return false + } + d.mu.Lock() + defer d.mu.Unlock() + + b := d.bytesForPos(ctxt, pos) + + if d.yes { + d.logDebugHashMatchLocked(d.name, string(b), "y", 0) + return true + } + + hash := hashOfBytes(b, 0) + + for _, m := range d.matches { + if (m.hash^hash)&m.mask == 0 { + hstr := "" + if hash == 0 { + hstr = "0" + } else { + for ; hash != 0; hash = hash >> 1 { + hstr = string('0'+byte(hash&1)) + hstr + } + } + d.logDebugHashMatchLocked(m.name, "POS="+string(b), hstr, 0) + return true + } + } + return false +} + +// bytesForPos renders a position, including inlining, into d.bytesTmp +// and returns the byte array. d.mu must be locked. +func (d *HashDebug) bytesForPos(ctxt *obj.Link, pos src.XPos) []byte { + d.posTmp = ctxt.AllPos(pos, d.posTmp) + // Reverse posTmp to put outermost first. + b := &d.bytesTmp + b.Reset() + for i := len(d.posTmp) - 1; i >= 0; i-- { + p := &d.posTmp[i] + fmt.Fprintf(b, "%s:%d:%d", p.Filename(), p.Line(), p.Col()) + if i != 0 { + b.WriteByte(';') + } + } + return b.Bytes() +} + func (d *HashDebug) logDebugHashMatch(varname, name, hstr string, param uint64) { d.mu.Lock() defer d.mu.Unlock() + d.logDebugHashMatchLocked(varname, name, hstr, param) +} + +func (d *HashDebug) logDebugHashMatchLocked(varname, name, hstr string, param uint64) { file := d.logfile if file == nil { if tmpfile := os.Getenv("GSHS_LOGFILE"); tmpfile != "" { diff --git a/src/cmd/compile/internal/ssa/fmahash_test.go b/src/cmd/compile/internal/ssa/fmahash_test.go index 78dd0baea2..1df6a63c25 100644 --- a/src/cmd/compile/internal/ssa/fmahash_test.go +++ b/src/cmd/compile/internal/ssa/fmahash_test.go @@ -9,8 +9,8 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "runtime" - "strings" "testing" ) @@ -18,9 +18,6 @@ import ( // It does not check or run the generated code. // The test file is however a useful example of fused-vs-cascaded multiply-add. func TestFmaHash(t *testing.T) { - if testing.Short() { - t.Skip("Slow test, usually avoid it, testing.Short") - } switch runtime.GOOS { case "linux", "darwin": default: @@ -42,7 +39,9 @@ func TestFmaHash(t *testing.T) { source := filepath.Join("testdata", "fma.go") output := filepath.Join(tmpdir, "fma.exe") cmd := exec.Command(gocmd, "build", "-o", output, source) - cmd.Env = append(cmd.Env, "GOCOMPILEDEBUG=fmahash=101111101101111001110110", "GOOS=linux", "GOARCH=arm64", "HOME="+tmpdir) + // The hash-dependence on file path name is dodged by specifying "all hashes ending in 1" plus "all hashes ending in 0" + // i.e., all hashes. This will print all the FMAs; this test is only interested in one of them (that should appear near the end). + cmd.Env = append(cmd.Env, "GOCOMPILEDEBUG=fmahash=1/0", "GOOS=linux", "GOARCH=arm64", "HOME="+tmpdir) t.Logf("%v", cmd) t.Logf("%v", cmd.Env) b, e := cmd.CombinedOutput() @@ -50,7 +49,9 @@ func TestFmaHash(t *testing.T) { t.Error(e) } s := string(b) // Looking for "GOFMAHASH triggered main.main:24" - if !strings.Contains(s, "fmahash triggered main.main:24") { - t.Errorf("Expected to see 'fmahash triggered main.main:24' in \n-----\n%s-----", s) + re := "fmahash(0?) triggered POS=.*fma.go:29:..;.*fma.go:18:.." + match := regexp.MustCompile(re) + if !match.MatchString(s) { + t.Errorf("Expected to match '%s' with \n-----\n%s-----", re, s) } } diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go index 18226c42b9..c988461a40 100644 --- a/src/cmd/compile/internal/ssa/func.go +++ b/src/cmd/compile/internal/ssa/func.go @@ -811,7 +811,6 @@ func (f *Func) useFMA(v *Value) bool { if base.FmaHash == nil { return true } - - name := f.fe.MyImportPath() + "." + f.Name - return base.FmaHash.DebugHashMatchParam(name, uint64(v.Pos.Line())) + ctxt := v.Block.Func.Config.Ctxt() + return base.FmaHash.DebugHashMatchPos(ctxt, v.Pos) } diff --git a/src/cmd/compile/internal/ssa/testdata/fma.go b/src/cmd/compile/internal/ssa/testdata/fma.go index 468448b9e6..13a7ff1e1c 100644 --- a/src/cmd/compile/internal/ssa/testdata/fma.go +++ b/src/cmd/compile/internal/ssa/testdata/fma.go @@ -14,6 +14,10 @@ func f(x float64) float64 { return x } +func inlineFma(x, y, z float64) float64 { + return x + y*z +} + func main() { w, x, y := 1.0, 1.0, 1.0 x = f(x + x/(1<<52)) @@ -21,7 +25,9 @@ func main() { y = f(y + y/(1<<52)) w0 := f(2 * w * (1 - w)) w1 := f(w * (1 + w)) - x = x + w0*w1 // GOFMAHASH=101111101101111001110110 + x = x + w0*w1 + x = inlineFma(x, w0, w1) + y = y + f(w0*w1) y = y + f(w0*w1) fmt.Println(x, y, x-y) -- 2.50.0