github.com/bir3/gocompiler@v0.3.205/src/cmd/compile/internal/base/hashdebug.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package base 6 7 import ( 8 "bytes" 9 "github.com/bir3/gocompiler/src/cmd/internal/notsha256" 10 "github.com/bir3/gocompiler/src/cmd/internal/obj" 11 "github.com/bir3/gocompiler/src/cmd/internal/src" 12 "fmt" 13 "io" 14 "os" 15 "strconv" 16 "strings" 17 "sync" 18 ) 19 20 type writeSyncer interface { 21 io.Writer 22 Sync() error 23 } 24 25 type hashAndMask struct { 26 // a hash h matches if (h^hash)&mask == 0 27 hash uint64 28 mask uint64 29 name string // base name, or base name + "0", "1", etc. 30 } 31 32 type HashDebug struct { 33 mu sync.Mutex // for logfile, posTmp, bytesTmp 34 name string // base name of the flag/variable. 35 // what file (if any) receives the yes/no logging? 36 // default is os.Stdout 37 logfile writeSyncer 38 posTmp []src.Pos 39 bytesTmp bytes.Buffer 40 matches []hashAndMask // A hash matches if one of these matches. 41 yes, no bool 42 } 43 44 // The default compiler-debugging HashDebug, for "-d=gossahash=..." 45 var hashDebug *HashDebug 46 var FmaHash *HashDebug 47 48 // DebugHashMatch reports whether debug variable Gossahash 49 // 50 // 1. is empty (returns true; this is a special more-quickly implemented case of 4 below) 51 // 52 // 2. is "y" or "Y" (returns true) 53 // 54 // 3. is "n" or "N" (returns false) 55 // 56 // 4. is a suffix of the sha1 hash of pkgAndName (returns true) 57 // 58 // 5. OR 59 // if the value is in the regular language "[01]+(;[01]+)+" 60 // test the [01]+ substrings after in order returning true 61 // for the first one that suffix-matches. The substrings AFTER 62 // the first semicolon are numbered 0,1, etc and are named 63 // fmt.Sprintf("%s%d", varname, number) 64 // Clause 5 is not really intended for human use and only 65 // matters for failures that require multiple triggers. 66 // 67 // Otherwise it returns false. 68 // 69 // Unless Flags.Gossahash is empty, when DebugHashMatch returns true the message 70 // 71 // "%s triggered %s\n", varname, pkgAndName 72 // 73 // is printed on the file named in environment variable GSHS_LOGFILE, 74 // or standard out if that is empty. "Varname" is either the name of 75 // the variable or the name of the substring, depending on which matched. 76 // 77 // Typical use: 78 // 79 // 1. you make a change to the compiler, say, adding a new phase 80 // 81 // 2. it is broken in some mystifying way, for example, make.bash builds a broken 82 // compiler that almost works, but crashes compiling a test in run.bash. 83 // 84 // 3. add this guard to the code, which by default leaves it broken, but does not 85 // run the broken new code if Flags.Gossahash is non-empty and non-matching: 86 // 87 // if !base.DebugHashMatch(ir.PkgFuncName(fn)) { 88 // return nil // early exit, do nothing 89 // } 90 // 91 // 4. rebuild w/o the bad code, 92 // GOCOMPILEDEBUG=gossahash=n ./all.bash 93 // to verify that you put the guard in the right place with the right sense of the test. 94 // 95 // 5. use github.com/dr2chase/gossahash to search for the error: 96 // 97 // go install github.com/dr2chase/gossahash@latest 98 // 99 // gossahash -- <the thing that fails> 100 // 101 // for example: GOMAXPROCS=1 gossahash -- ./all.bash 102 // 103 // 6. gossahash should return a single function whose miscompilation 104 // causes the problem, and you can focus on that. 105 func DebugHashMatch(pkgAndName string) bool { 106 return hashDebug.DebugHashMatch(pkgAndName) 107 } 108 109 // HasDebugHash returns true if Flags.Gossahash is non-empty, which 110 // results in hashDebug being not-nil. I.e., if !HasDebugHash(), 111 // there is no need to create the string for hashing and testing. 112 func HasDebugHash() bool { 113 return hashDebug != nil 114 } 115 116 func toHashAndMask(s, varname string) hashAndMask { 117 l := len(s) 118 if l > 64 { 119 s = s[l-64:] 120 l = 64 121 } 122 m := ^(^uint64(0) << l) 123 h, err := strconv.ParseUint(s, 2, 64) 124 if err != nil { 125 Fatalf("Could not parse %s (=%s) as a binary number", varname, s) 126 } 127 128 return hashAndMask{name: varname, hash: h, mask: m} 129 } 130 131 // NewHashDebug returns a new hash-debug tester for the 132 // environment variable ev. If ev is not set, it returns 133 // nil, allowing a lightweight check for normal-case behavior. 134 func NewHashDebug(ev, s string, file writeSyncer) *HashDebug { 135 if s == "" { 136 return nil 137 } 138 139 hd := &HashDebug{name: ev, logfile: file} 140 switch s[0] { 141 case 'y', 'Y': 142 hd.yes = true 143 return hd 144 case 'n', 'N': 145 hd.no = true 146 return hd 147 } 148 ss := strings.Split(s, "/") 149 hd.matches = append(hd.matches, toHashAndMask(ss[0], ev)) 150 // hash searches may use additional EVs with 0, 1, 2, ... suffixes. 151 for i := 1; i < len(ss); i++ { 152 evi := fmt.Sprintf("%s%d", ev, i-1) // convention is extras begin indexing at zero 153 hd.matches = append(hd.matches, toHashAndMask(ss[i], evi)) 154 } 155 return hd 156 157 } 158 159 func hashOf(pkgAndName string, param uint64) uint64 { 160 return hashOfBytes([]byte(pkgAndName), param) 161 } 162 163 func hashOfBytes(sbytes []byte, param uint64) uint64 { 164 hbytes := notsha256.Sum256(sbytes) 165 hash := uint64(hbytes[7])<<56 + uint64(hbytes[6])<<48 + 166 uint64(hbytes[5])<<40 + uint64(hbytes[4])<<32 + 167 uint64(hbytes[3])<<24 + uint64(hbytes[2])<<16 + 168 uint64(hbytes[1])<<8 + uint64(hbytes[0]) 169 170 if param != 0 { 171 // Because param is probably a line number, probably near zero, 172 // hash it up a little bit, but even so only the lower-order bits 173 // likely matter because search focuses on those. 174 p0 := param + uint64(hbytes[9]) + uint64(hbytes[10])<<8 + 175 uint64(hbytes[11])<<16 + uint64(hbytes[12])<<24 176 177 p1 := param + uint64(hbytes[13]) + uint64(hbytes[14])<<8 + 178 uint64(hbytes[15])<<16 + uint64(hbytes[16])<<24 179 180 param += p0 * p1 181 param ^= param>>17 ^ param<<47 182 } 183 184 return hash ^ param 185 } 186 187 // DebugHashMatch returns true if either the variable used to create d is 188 // unset, or if its value is y, or if it is a suffix of the base-two 189 // representation of the hash of pkgAndName. If the variable is not nil, 190 // then a true result is accompanied by stylized output to d.logfile, which 191 // is used for automated bug search. 192 func (d *HashDebug) DebugHashMatch(pkgAndName string) bool { 193 return d.DebugHashMatchParam(pkgAndName, 0) 194 } 195 196 // DebugHashMatchParam returns true if either the variable used to create d is 197 // unset, or if its value is y, or if it is a suffix of the base-two 198 // representation of the hash of pkgAndName and param. If the variable is not 199 // nil, then a true result is accompanied by stylized output to d.logfile, 200 // which is used for automated bug search. 201 func (d *HashDebug) DebugHashMatchParam(pkgAndName string, param uint64) bool { 202 if d == nil { 203 return true 204 } 205 if d.no { 206 return false 207 } 208 209 if d.yes { 210 d.logDebugHashMatch(d.name, pkgAndName, "y", param) 211 return true 212 } 213 214 hash := hashOf(pkgAndName, param) 215 216 for _, m := range d.matches { 217 if (m.hash^hash)&m.mask == 0 { 218 hstr := "" 219 if hash == 0 { 220 hstr = "0" 221 } else { 222 for ; hash != 0; hash = hash >> 1 { 223 hstr = string('0'+byte(hash&1)) + hstr 224 } 225 } 226 d.logDebugHashMatch(m.name, pkgAndName, hstr, param) 227 return true 228 } 229 } 230 return false 231 } 232 233 // DebugHashMatchPos is similar to DebugHashMatchParam, but for hash computation 234 // it uses the source position including all inlining information instead of 235 // package name and path. The output trigger string is prefixed with "POS=" so 236 // that tools processing the output can reliably tell the difference. The mutex 237 // locking is also more frequent and more granular. 238 func (d *HashDebug) DebugHashMatchPos(ctxt *obj.Link, pos src.XPos) bool { 239 if d == nil { 240 return true 241 } 242 if d.no { 243 return false 244 } 245 d.mu.Lock() 246 defer d.mu.Unlock() 247 248 b := d.bytesForPos(ctxt, pos) 249 250 if d.yes { 251 d.logDebugHashMatchLocked(d.name, string(b), "y", 0) 252 return true 253 } 254 255 hash := hashOfBytes(b, 0) 256 257 for _, m := range d.matches { 258 if (m.hash^hash)&m.mask == 0 { 259 hstr := "" 260 if hash == 0 { 261 hstr = "0" 262 } else { 263 for ; hash != 0; hash = hash >> 1 { 264 hstr = string('0'+byte(hash&1)) + hstr 265 } 266 } 267 d.logDebugHashMatchLocked(m.name, "POS="+string(b), hstr, 0) 268 return true 269 } 270 } 271 return false 272 } 273 274 // bytesForPos renders a position, including inlining, into d.bytesTmp 275 // and returns the byte array. d.mu must be locked. 276 func (d *HashDebug) bytesForPos(ctxt *obj.Link, pos src.XPos) []byte { 277 d.posTmp = ctxt.AllPos(pos, d.posTmp) 278 // Reverse posTmp to put outermost first. 279 b := &d.bytesTmp 280 b.Reset() 281 for i := len(d.posTmp) - 1; i >= 0; i-- { 282 p := &d.posTmp[i] 283 fmt.Fprintf(b, "%s:%d:%d", p.Filename(), p.Line(), p.Col()) 284 if i != 0 { 285 b.WriteByte(';') 286 } 287 } 288 return b.Bytes() 289 } 290 291 func (d *HashDebug) logDebugHashMatch(varname, name, hstr string, param uint64) { 292 d.mu.Lock() 293 defer d.mu.Unlock() 294 d.logDebugHashMatchLocked(varname, name, hstr, param) 295 } 296 297 func (d *HashDebug) logDebugHashMatchLocked(varname, name, hstr string, param uint64) { 298 file := d.logfile 299 if file == nil { 300 if tmpfile := os.Getenv("GSHS_LOGFILE"); tmpfile != "" { 301 var err error 302 file, err = os.OpenFile(tmpfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 303 if err != nil { 304 Fatalf("could not open hash-testing logfile %s", tmpfile) 305 return 306 } 307 } 308 if file == nil { 309 file = os.Stdout 310 } 311 d.logfile = file 312 } 313 if len(hstr) > 24 { 314 hstr = hstr[len(hstr)-24:] 315 } 316 // External tools depend on this string 317 if param == 0 { 318 fmt.Fprintf(file, "%s triggered %s %s\n", varname, name, hstr) 319 } else { 320 fmt.Fprintf(file, "%s triggered %s:%d %s\n", varname, name, param, hstr) 321 } 322 file.Sync() 323 }