1
0
mirror of https://github.com/golang/go synced 2024-11-23 05:10:09 -07:00

cmd/compile: modify debug-hash to support match exclusion

The goal here is to enable a search that will locate all the instances
of a failure, not just the first one.  This helps with searches for
loopvar-change breakage, FP differences from fused-multiply-add, and
allows certain semantics queries that can be implemented as compiler
changes (for example, where does integer overflow routinely occur?)

Change-Id: Ic28f1695d47e421c2089d1f3f7c4b40c56db970f
Reviewed-on: https://go-review.googlesource.com/c/go/+/481195
Run-TryBot: David Chase <drchase@google.com>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
David Chase 2023-03-31 17:26:04 -04:00
parent f9cf2c4d04
commit ef9c211de8

View File

@ -39,6 +39,7 @@ type HashDebug struct {
posTmp []src.Pos
bytesTmp bytes.Buffer
matches []hashAndMask // A hash matches if one of these matches.
excludes []hashAndMask // explicitly excluded hash suffixes
yes, no bool
fileSuffixOnly bool // for Pos hashes, remove the directory prefix.
inlineSuffixOnly bool // for Pos hashes, remove all but the most inline position.
@ -76,15 +77,23 @@ var LoopVarHash *HashDebug // for debugging shared/private loop variable changes
//
// 3. is "n" or "N" (returns false)
//
// 4. is a suffix of the sha1 hash of pkgAndName (returns true)
// 4. does not explicitly exclude the sha1 hash of pkgAndName (see step 6)
//
// 5. OR
// if the value is in the regular language "[01]+(/[01]+)+"
// test the [01]+ substrings after in order returning true
// for the first one that suffix-matches. The substrings AFTER
// the first slash are numbered 0,1, etc and are named
// fmt.Sprintf("%s%d", varname, number)
// Clause 5 is not really intended for human use and only
// 5. is a suffix of the sha1 hash of pkgAndName (returns true)
//
// 6. OR
// if the (non-empty) value is in the regular language
// "(-[01]+/)+?([01]+(/[01]+)+?"
// (exclude..)(....include...)
// test the [01]+ exclude substrings, if any suffix-match, return false (4 above)
// test the [01]+ include substrings, if any suffix-match, return true
// The include substrings AFTER the first slash are numbered 0,1, etc and
// are named fmt.Sprintf("%s%d", varname, number)
// As an extra-special case for multiple failure search,
// an excludes-only string ending in a slash (terminated, not separated)
// implicitly specifies the include string "0/1", that is, match everything.
// (Exclude strings are used for automated search for multiple failures.)
// Clause 6 is not really intended for human use and only
// matters for failures that require multiple triggers.
//
// Otherwise it returns false.
@ -169,11 +178,35 @@ func NewHashDebug(ev, s string, file writeSyncer) *HashDebug {
return hd
}
ss := strings.Split(s, "/")
hd.matches = append(hd.matches, toHashAndMask(ss[0], ev))
// first remove any leading exclusions; these are preceded with "-"
i := 0
for len(ss) > 0 {
s := ss[0]
if len(s) == 0 || len(s) > 0 && s[0] != '-' {
break
}
ss = ss[1:]
hd.excludes = append(hd.excludes, toHashAndMask(s[1:], fmt.Sprintf("%s%d", "HASH_EXCLUDE", i)))
i++
}
// hash searches may use additional EVs with 0, 1, 2, ... suffixes.
for i := 1; i < len(ss); i++ {
evi := fmt.Sprintf("%s%d", ev, i-1) // convention is extras begin indexing at zero
hd.matches = append(hd.matches, toHashAndMask(ss[i], evi))
i = 0
for _, s := range ss {
if s == "" {
if i != 0 || len(ss) > 1 && ss[1] != "" || len(ss) > 2 {
Fatalf("Empty hash match string for %s should be first (and only) one", ev)
}
// Special case of should match everything.
hd.matches = append(hd.matches, toHashAndMask("0", fmt.Sprintf("%s0", ev)))
hd.matches = append(hd.matches, toHashAndMask("1", fmt.Sprintf("%s1", ev)))
break
}
if i == 0 {
hd.matches = append(hd.matches, toHashAndMask(s, fmt.Sprintf("%s", ev)))
} else {
hd.matches = append(hd.matches, toHashAndMask(s, fmt.Sprintf("%s%d", ev, i-1)))
}
i++
}
return hd
@ -216,6 +249,36 @@ func (d *HashDebug) DebugHashMatch(pkgAndName string) bool {
return d.DebugHashMatchParam(pkgAndName, 0)
}
func (d *HashDebug) excluded(hash uint64) bool {
for _, m := range d.excludes {
if (m.hash^hash)&m.mask == 0 {
return true
}
}
return false
}
func hashString(hash uint64) string {
hstr := ""
if hash == 0 {
hstr = "0"
} else {
for ; hash != 0; hash = hash >> 1 {
hstr = string('0'+byte(hash&1)) + hstr
}
}
return hstr
}
func (d *HashDebug) match(hash uint64) *hashAndMask {
for i, m := range d.matches {
if (m.hash^hash)&m.mask == 0 {
return &d.matches[i]
}
}
return nil
}
// DebugHashMatchParam returns true if either the variable used to create d is
// unset, or if its value is y, or if it is a suffix of the base-two
// representation of the hash of pkgAndName and param. If the variable is not
@ -236,20 +299,14 @@ func (d *HashDebug) DebugHashMatchParam(pkgAndName string, param uint64) bool {
hash := hashOf(pkgAndName, param)
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
// Return false for explicitly excluded hashes
if d.excluded(hash) {
return false
}
}
d.logDebugHashMatch(m.name, pkgAndName, hstr, param)
if m := d.match(hash); m != nil {
d.logDebugHashMatch(m.name, pkgAndName, hashString(hash), param)
return true
}
}
return false
}
@ -284,20 +341,14 @@ func (d *HashDebug) debugHashMatchPos(ctxt *obj.Link, pos src.XPos) bool {
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
// Return false for explicitly excluded hashes
if d.excluded(hash) {
return false
}
}
d.logDebugHashMatchLocked(m.name, "POS="+string(b), hstr, 0)
if m := d.match(hash); m != nil {
d.logDebugHashMatchLocked(m.name, "POS="+string(b), hashString(hash), 0)
return true
}
}
return false
}