1// Copyright 2023 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 bisect can be used by compilers and other programs
6// to serve as a target for the bisect debugging tool.
7// See [golang.org/x/tools/cmd/bisect] for details about using the tool.
8//
9// To be a bisect target, allowing bisect to help determine which of a set of independent
10// changes provokes a failure, a program needs to:
11//
12//  1. Define a way to accept a change pattern on its command line or in its environment.
13//     The most common mechanism is a command-line flag.
14//     The pattern can be passed to [New] to create a [Matcher], the compiled form of a pattern.
15//
16//  2. Assign each change a unique ID. One possibility is to use a sequence number,
17//     but the most common mechanism is to hash some kind of identifying information
18//     like the file and line number where the change might be applied.
19//     [Hash] hashes its arguments to compute an ID.
20//
21//  3. Enable each change that the pattern says should be enabled.
22//     The [Matcher.ShouldEnable] method answers this question for a given change ID.
23//
24//  4. Print a report identifying each change that the pattern says should be printed.
25//     The [Matcher.ShouldPrint] method answers this question for a given change ID.
26//     The report consists of one more lines on standard error or standard output
27//     that contain a “match marker”. [Marker] returns the match marker for a given ID.
28//     When bisect reports a change as causing the failure, it identifies the change
29//     by printing the report lines with the match marker removed.
30//
31// # Example Usage
32//
33// A program starts by defining how it receives the pattern. In this example, we will assume a flag.
34// The next step is to compile the pattern:
35//
36//	m, err := bisect.New(patternFlag)
37//	if err != nil {
38//		log.Fatal(err)
39//	}
40//
41// Then, each time a potential change is considered, the program computes
42// a change ID by hashing identifying information (source file and line, in this case)
43// and then calls m.ShouldPrint and m.ShouldEnable to decide whether to
44// print and enable the change, respectively. The two can return different values
45// depending on whether bisect is trying to find a minimal set of changes to
46// disable or to enable to provoke the failure.
47//
48// It is usually helpful to write a helper function that accepts the identifying information
49// and then takes care of hashing, printing, and reporting whether the identified change
50// should be enabled. For example, a helper for changes identified by a file and line number
51// would be:
52//
53//	func ShouldEnable(file string, line int) {
54//		h := bisect.Hash(file, line)
55//		if m.ShouldPrint(h) {
56//			fmt.Fprintf(os.Stderr, "%v %s:%d\n", bisect.Marker(h), file, line)
57//		}
58//		return m.ShouldEnable(h)
59//	}
60//
61// Finally, note that New returns a nil Matcher when there is no pattern,
62// meaning that the target is not running under bisect at all,
63// so all changes should be enabled and none should be printed.
64// In that common case, the computation of the hash can be avoided entirely
65// by checking for m == nil first:
66//
67//	func ShouldEnable(file string, line int) bool {
68//		if m == nil {
69//			return true
70//		}
71//		h := bisect.Hash(file, line)
72//		if m.ShouldPrint(h) {
73//			fmt.Fprintf(os.Stderr, "%v %s:%d\n", bisect.Marker(h), file, line)
74//		}
75//		return m.ShouldEnable(h)
76//	}
77//
78// When the identifying information is expensive to format, this code can call
79// [Matcher.MarkerOnly] to find out whether short report lines containing only the
80// marker are permitted for a given run. (Bisect permits such lines when it is
81// still exploring the space of possible changes and will not be showing the
82// output to the user.) If so, the client can choose to print only the marker:
83//
84//	func ShouldEnable(file string, line int) bool {
85//		if m == nil {
86//			return true
87//		}
88//		h := bisect.Hash(file, line)
89//		if m.ShouldPrint(h) {
90//			if m.MarkerOnly() {
91//				bisect.PrintMarker(os.Stderr, h)
92//			} else {
93//				fmt.Fprintf(os.Stderr, "%v %s:%d\n", bisect.Marker(h), file, line)
94//			}
95//		}
96//		return m.ShouldEnable(h)
97//	}
98//
99// This specific helper – deciding whether to enable a change identified by
100// file and line number and printing about the change when necessary – is
101// provided by the [Matcher.FileLine] method.
102//
103// Another common usage is deciding whether to make a change in a function
104// based on the caller's stack, to identify the specific calling contexts that the
105// change breaks. The [Matcher.Stack] method takes care of obtaining the stack,
106// printing it when necessary, and reporting whether to enable the change
107// based on that stack.
108//
109// # Pattern Syntax
110//
111// Patterns are generated by the bisect tool and interpreted by [New].
112// Users should not have to understand the patterns except when
113// debugging a target's bisect support or debugging the bisect tool itself.
114//
115// The pattern syntax selecting a change is a sequence of bit strings
116// separated by + and - operators. Each bit string denotes the set of
117// changes with IDs ending in those bits, + is set addition, - is set subtraction,
118// and the expression is evaluated in the usual left-to-right order.
119// The special binary number “y” denotes the set of all changes,
120// standing in for the empty bit string.
121// In the expression, all the + operators must appear before all the - operators.
122// A leading + adds to an empty set. A leading - subtracts from the set of all
123// possible suffixes.
124//
125// For example:
126//
127//   - “01+10” and “+01+10” both denote the set of changes
128//     with IDs ending with the bits 01 or 10.
129//
130//   - “01+10-1001” denotes the set of changes with IDs
131//     ending with the bits 01 or 10, but excluding those ending in 1001.
132//
133//   - “-01-1000” and “y-01-1000 both denote the set of all changes
134//     with IDs not ending in 01 nor 1000.
135//
136//   - “0+1-01+001” is not a valid pattern, because all the + operators do not
137//     appear before all the - operators.
138//
139// In the syntaxes described so far, the pattern specifies the changes to
140// enable and report. If a pattern is prefixed by a “!”, the meaning
141// changes: the pattern specifies the changes to DISABLE and report. This
142// mode of operation is needed when a program passes with all changes
143// enabled but fails with no changes enabled. In this case, bisect
144// searches for minimal sets of changes to disable.
145// Put another way, the leading “!” inverts the result from [Matcher.ShouldEnable]
146// but does not invert the result from [Matcher.ShouldPrint].
147//
148// As a convenience for manual debugging, “n” is an alias for “!y”,
149// meaning to disable and report all changes.
150//
151// Finally, a leading “v” in the pattern indicates that the reports will be shown
152// to the user of bisect to describe the changes involved in a failure.
153// At the API level, the leading “v” causes [Matcher.Visible] to return true.
154// See the next section for details.
155//
156// # Match Reports
157//
158// The target program must enable only those changed matched
159// by the pattern, and it must print a match report for each such change.
160// A match report consists of one or more lines of text that will be
161// printed by the bisect tool to describe a change implicated in causing
162// a failure. Each line in the report for a given change must contain a
163// match marker with that change ID, as returned by [Marker].
164// The markers are elided when displaying the lines to the user.
165//
166// A match marker has the form “[bisect-match 0x1234]” where
167// 0x1234 is the change ID in hexadecimal.
168// An alternate form is “[bisect-match 010101]”, giving the change ID in binary.
169//
170// When [Matcher.Visible] returns false, the match reports are only
171// being processed by bisect to learn the set of enabled changes,
172// not shown to the user, meaning that each report can be a match
173// marker on a line by itself, eliding the usual textual description.
174// When the textual description is expensive to compute,
175// checking [Matcher.Visible] can help the avoid that expense
176// in most runs.
177package bisect
178
179import (
180	"runtime"
181	"sync"
182	"sync/atomic"
183)
184
185// New creates and returns a new Matcher implementing the given pattern.
186// The pattern syntax is defined in the package doc comment.
187//
188// In addition to the pattern syntax syntax, New("") returns nil, nil.
189// The nil *Matcher is valid for use: it returns true from ShouldEnable
190// and false from ShouldPrint for all changes. Callers can avoid calling
191// [Hash], [Matcher.ShouldEnable], and [Matcher.ShouldPrint] entirely
192// when they recognize the nil Matcher.
193func New(pattern string) (*Matcher, error) {
194	if pattern == "" {
195		return nil, nil
196	}
197
198	m := new(Matcher)
199
200	p := pattern
201	// Special case for leading 'q' so that 'qn' quietly disables, e.g. fmahash=qn to disable fma
202	// Any instance of 'v' disables 'q'.
203	if len(p) > 0 && p[0] == 'q' {
204		m.quiet = true
205		p = p[1:]
206		if p == "" {
207			return nil, &parseError{"invalid pattern syntax: " + pattern}
208		}
209	}
210	// Allow multiple v, so that “bisect cmd vPATTERN” can force verbose all the time.
211	for len(p) > 0 && p[0] == 'v' {
212		m.verbose = true
213		m.quiet = false
214		p = p[1:]
215		if p == "" {
216			return nil, &parseError{"invalid pattern syntax: " + pattern}
217		}
218	}
219
220	// Allow multiple !, each negating the last, so that “bisect cmd !PATTERN” works
221	// even when bisect chooses to add its own !.
222	m.enable = true
223	for len(p) > 0 && p[0] == '!' {
224		m.enable = !m.enable
225		p = p[1:]
226		if p == "" {
227			return nil, &parseError{"invalid pattern syntax: " + pattern}
228		}
229	}
230
231	if p == "n" {
232		// n is an alias for !y.
233		m.enable = !m.enable
234		p = "y"
235	}
236
237	// Parse actual pattern syntax.
238	result := true
239	bits := uint64(0)
240	start := 0
241	wid := 1 // 1-bit (binary); sometimes 4-bit (hex)
242	for i := 0; i <= len(p); i++ {
243		// Imagine a trailing - at the end of the pattern to flush final suffix
244		c := byte('-')
245		if i < len(p) {
246			c = p[i]
247		}
248		if i == start && wid == 1 && c == 'x' { // leading x for hex
249			start = i + 1
250			wid = 4
251			continue
252		}
253		switch c {
254		default:
255			return nil, &parseError{"invalid pattern syntax: " + pattern}
256		case '2', '3', '4', '5', '6', '7', '8', '9':
257			if wid != 4 {
258				return nil, &parseError{"invalid pattern syntax: " + pattern}
259			}
260			fallthrough
261		case '0', '1':
262			bits <<= wid
263			bits |= uint64(c - '0')
264		case 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F':
265			if wid != 4 {
266				return nil, &parseError{"invalid pattern syntax: " + pattern}
267			}
268			bits <<= 4
269			bits |= uint64(c&^0x20 - 'A' + 10)
270		case 'y':
271			if i+1 < len(p) && (p[i+1] == '0' || p[i+1] == '1') {
272				return nil, &parseError{"invalid pattern syntax: " + pattern}
273			}
274			bits = 0
275		case '+', '-':
276			if c == '+' && result == false {
277				// Have already seen a -. Should be - from here on.
278				return nil, &parseError{"invalid pattern syntax (+ after -): " + pattern}
279			}
280			if i > 0 {
281				n := (i - start) * wid
282				if n > 64 {
283					return nil, &parseError{"pattern bits too long: " + pattern}
284				}
285				if n <= 0 {
286					return nil, &parseError{"invalid pattern syntax: " + pattern}
287				}
288				if p[start] == 'y' {
289					n = 0
290				}
291				mask := uint64(1)<<n - 1
292				m.list = append(m.list, cond{mask, bits, result})
293			} else if c == '-' {
294				// leading - subtracts from complete set
295				m.list = append(m.list, cond{0, 0, true})
296			}
297			bits = 0
298			result = c == '+'
299			start = i + 1
300			wid = 1
301		}
302	}
303	return m, nil
304}
305
306// A Matcher is the parsed, compiled form of a PATTERN string.
307// The nil *Matcher is valid: it has all changes enabled but none reported.
308type Matcher struct {
309	verbose bool   // annotate reporting with human-helpful information
310	quiet   bool   // disables all reporting.  reset if verbose is true. use case is -d=fmahash=qn
311	enable  bool   // when true, list is for “enable and report” (when false, “disable and report”)
312	list    []cond // conditions; later ones win over earlier ones
313	dedup   atomic.Pointer[dedup]
314}
315
316// A cond is a single condition in the matcher.
317// Given an input id, if id&mask == bits, return the result.
318type cond struct {
319	mask   uint64
320	bits   uint64
321	result bool
322}
323
324// MarkerOnly reports whether it is okay to print only the marker for
325// a given change, omitting the identifying information.
326// MarkerOnly returns true when bisect is using the printed reports
327// only for an intermediate search step, not for showing to users.
328func (m *Matcher) MarkerOnly() bool {
329	return !m.verbose
330}
331
332// ShouldEnable reports whether the change with the given id should be enabled.
333func (m *Matcher) ShouldEnable(id uint64) bool {
334	if m == nil {
335		return true
336	}
337	return m.matchResult(id) == m.enable
338}
339
340// ShouldPrint reports whether to print identifying information about the change with the given id.
341func (m *Matcher) ShouldPrint(id uint64) bool {
342	if m == nil || m.quiet {
343		return false
344	}
345	return m.matchResult(id)
346}
347
348// matchResult returns the result from the first condition that matches id.
349func (m *Matcher) matchResult(id uint64) bool {
350	for i := len(m.list) - 1; i >= 0; i-- {
351		c := &m.list[i]
352		if id&c.mask == c.bits {
353			return c.result
354		}
355	}
356	return false
357}
358
359// FileLine reports whether the change identified by file and line should be enabled.
360// If the change should be printed, FileLine prints a one-line report to w.
361func (m *Matcher) FileLine(w Writer, file string, line int) bool {
362	if m == nil {
363		return true
364	}
365	return m.fileLine(w, file, line)
366}
367
368// fileLine does the real work for FileLine.
369// This lets FileLine's body handle m == nil and potentially be inlined.
370func (m *Matcher) fileLine(w Writer, file string, line int) bool {
371	h := Hash(file, line)
372	if m.ShouldPrint(h) {
373		if m.MarkerOnly() {
374			PrintMarker(w, h)
375		} else {
376			printFileLine(w, h, file, line)
377		}
378	}
379	return m.ShouldEnable(h)
380}
381
382// printFileLine prints a non-marker-only report for file:line to w.
383func printFileLine(w Writer, h uint64, file string, line int) error {
384	const markerLen = 40 // overestimate
385	b := make([]byte, 0, markerLen+len(file)+24)
386	b = AppendMarker(b, h)
387	b = appendFileLine(b, file, line)
388	b = append(b, '\n')
389	_, err := w.Write(b)
390	return err
391}
392
393// appendFileLine appends file:line to dst, returning the extended slice.
394func appendFileLine(dst []byte, file string, line int) []byte {
395	dst = append(dst, file...)
396	dst = append(dst, ':')
397	u := uint(line)
398	if line < 0 {
399		dst = append(dst, '-')
400		u = -u
401	}
402	var buf [24]byte
403	i := len(buf)
404	for i == len(buf) || u > 0 {
405		i--
406		buf[i] = '0' + byte(u%10)
407		u /= 10
408	}
409	dst = append(dst, buf[i:]...)
410	return dst
411}
412
413// MatchStack assigns the current call stack a change ID.
414// If the stack should be printed, MatchStack prints it.
415// Then MatchStack reports whether a change at the current call stack should be enabled.
416func (m *Matcher) Stack(w Writer) bool {
417	if m == nil {
418		return true
419	}
420	return m.stack(w)
421}
422
423// stack does the real work for Stack.
424// This lets stack's body handle m == nil and potentially be inlined.
425func (m *Matcher) stack(w Writer) bool {
426	const maxStack = 16
427	var stk [maxStack]uintptr
428	n := runtime.Callers(2, stk[:])
429	// caller #2 is not for printing; need it to normalize PCs if ASLR.
430	if n <= 1 {
431		return false
432	}
433
434	base := stk[0]
435	// normalize PCs
436	for i := range stk[:n] {
437		stk[i] -= base
438	}
439
440	h := Hash(stk[:n])
441	if m.ShouldPrint(h) {
442		var d *dedup
443		for {
444			d = m.dedup.Load()
445			if d != nil {
446				break
447			}
448			d = new(dedup)
449			if m.dedup.CompareAndSwap(nil, d) {
450				break
451			}
452		}
453
454		if m.MarkerOnly() {
455			if !d.seenLossy(h) {
456				PrintMarker(w, h)
457			}
458		} else {
459			if !d.seen(h) {
460				// Restore PCs in stack for printing
461				for i := range stk[:n] {
462					stk[i] += base
463				}
464				printStack(w, h, stk[1:n])
465			}
466		}
467	}
468	return m.ShouldEnable(h)
469}
470
471// Writer is the same interface as io.Writer.
472// It is duplicated here to avoid importing io.
473type Writer interface {
474	Write([]byte) (int, error)
475}
476
477// PrintMarker prints to w a one-line report containing only the marker for h.
478// It is appropriate to use when [Matcher.ShouldPrint] and [Matcher.MarkerOnly] both return true.
479func PrintMarker(w Writer, h uint64) error {
480	var buf [50]byte
481	b := AppendMarker(buf[:0], h)
482	b = append(b, '\n')
483	_, err := w.Write(b)
484	return err
485}
486
487// printStack prints to w a multi-line report containing a formatting of the call stack stk,
488// with each line preceded by the marker for h.
489func printStack(w Writer, h uint64, stk []uintptr) error {
490	buf := make([]byte, 0, 2048)
491
492	var prefixBuf [100]byte
493	prefix := AppendMarker(prefixBuf[:0], h)
494
495	frames := runtime.CallersFrames(stk)
496	for {
497		f, more := frames.Next()
498		buf = append(buf, prefix...)
499		buf = append(buf, f.Function...)
500		buf = append(buf, "()\n"...)
501		buf = append(buf, prefix...)
502		buf = append(buf, '\t')
503		buf = appendFileLine(buf, f.File, f.Line)
504		buf = append(buf, '\n')
505		if !more {
506			break
507		}
508	}
509	buf = append(buf, prefix...)
510	buf = append(buf, '\n')
511	_, err := w.Write(buf)
512	return err
513}
514
515// Marker returns the match marker text to use on any line reporting details
516// about a match of the given ID.
517// It always returns the hexadecimal format.
518func Marker(id uint64) string {
519	return string(AppendMarker(nil, id))
520}
521
522// AppendMarker is like [Marker] but appends the marker to dst.
523func AppendMarker(dst []byte, id uint64) []byte {
524	const prefix = "[bisect-match 0x"
525	var buf [len(prefix) + 16 + 1]byte
526	copy(buf[:], prefix)
527	for i := 0; i < 16; i++ {
528		buf[len(prefix)+i] = "0123456789abcdef"[id>>60]
529		id <<= 4
530	}
531	buf[len(prefix)+16] = ']'
532	return append(dst, buf[:]...)
533}
534
535// CutMarker finds the first match marker in line and removes it,
536// returning the shortened line (with the marker removed),
537// the ID from the match marker,
538// and whether a marker was found at all.
539// If there is no marker, CutMarker returns line, 0, false.
540func CutMarker(line string) (short string, id uint64, ok bool) {
541	// Find first instance of prefix.
542	prefix := "[bisect-match "
543	i := 0
544	for ; ; i++ {
545		if i >= len(line)-len(prefix) {
546			return line, 0, false
547		}
548		if line[i] == '[' && line[i:i+len(prefix)] == prefix {
549			break
550		}
551	}
552
553	// Scan to ].
554	j := i + len(prefix)
555	for j < len(line) && line[j] != ']' {
556		j++
557	}
558	if j >= len(line) {
559		return line, 0, false
560	}
561
562	// Parse id.
563	idstr := line[i+len(prefix) : j]
564	if len(idstr) >= 3 && idstr[:2] == "0x" {
565		// parse hex
566		if len(idstr) > 2+16 { // max 0x + 16 digits
567			return line, 0, false
568		}
569		for i := 2; i < len(idstr); i++ {
570			id <<= 4
571			switch c := idstr[i]; {
572			case '0' <= c && c <= '9':
573				id |= uint64(c - '0')
574			case 'a' <= c && c <= 'f':
575				id |= uint64(c - 'a' + 10)
576			case 'A' <= c && c <= 'F':
577				id |= uint64(c - 'A' + 10)
578			}
579		}
580	} else {
581		if idstr == "" || len(idstr) > 64 { // min 1 digit, max 64 digits
582			return line, 0, false
583		}
584		// parse binary
585		for i := 0; i < len(idstr); i++ {
586			id <<= 1
587			switch c := idstr[i]; c {
588			default:
589				return line, 0, false
590			case '0', '1':
591				id |= uint64(c - '0')
592			}
593		}
594	}
595
596	// Construct shortened line.
597	// Remove at most one space from around the marker,
598	// so that "foo [marker] bar" shortens to "foo bar".
599	j++ // skip ]
600	if i > 0 && line[i-1] == ' ' {
601		i--
602	} else if j < len(line) && line[j] == ' ' {
603		j++
604	}
605	short = line[:i] + line[j:]
606	return short, id, true
607}
608
609// Hash computes a hash of the data arguments,
610// each of which must be of type string, byte, int, uint, int32, uint32, int64, uint64, uintptr, or a slice of one of those types.
611func Hash(data ...any) uint64 {
612	h := offset64
613	for _, v := range data {
614		switch v := v.(type) {
615		default:
616			// Note: Not printing the type, because reflect.ValueOf(v)
617			// would make the interfaces prepared by the caller escape
618			// and therefore allocate. This way, Hash(file, line) runs
619			// without any allocation. It should be clear from the
620			// source code calling Hash what the bad argument was.
621			panic("bisect.Hash: unexpected argument type")
622		case string:
623			h = fnvString(h, v)
624		case byte:
625			h = fnv(h, v)
626		case int:
627			h = fnvUint64(h, uint64(v))
628		case uint:
629			h = fnvUint64(h, uint64(v))
630		case int32:
631			h = fnvUint32(h, uint32(v))
632		case uint32:
633			h = fnvUint32(h, v)
634		case int64:
635			h = fnvUint64(h, uint64(v))
636		case uint64:
637			h = fnvUint64(h, v)
638		case uintptr:
639			h = fnvUint64(h, uint64(v))
640		case []string:
641			for _, x := range v {
642				h = fnvString(h, x)
643			}
644		case []byte:
645			for _, x := range v {
646				h = fnv(h, x)
647			}
648		case []int:
649			for _, x := range v {
650				h = fnvUint64(h, uint64(x))
651			}
652		case []uint:
653			for _, x := range v {
654				h = fnvUint64(h, uint64(x))
655			}
656		case []int32:
657			for _, x := range v {
658				h = fnvUint32(h, uint32(x))
659			}
660		case []uint32:
661			for _, x := range v {
662				h = fnvUint32(h, x)
663			}
664		case []int64:
665			for _, x := range v {
666				h = fnvUint64(h, uint64(x))
667			}
668		case []uint64:
669			for _, x := range v {
670				h = fnvUint64(h, x)
671			}
672		case []uintptr:
673			for _, x := range v {
674				h = fnvUint64(h, uint64(x))
675			}
676		}
677	}
678	return h
679}
680
681// Trivial error implementation, here to avoid importing errors.
682
683// parseError is a trivial error implementation,
684// defined here to avoid importing errors.
685type parseError struct{ text string }
686
687func (e *parseError) Error() string { return e.text }
688
689// FNV-1a implementation. See Go's hash/fnv/fnv.go.
690// Copied here for simplicity (can handle integers more directly)
691// and to avoid importing hash/fnv.
692
693const (
694	offset64 uint64 = 14695981039346656037
695	prime64  uint64 = 1099511628211
696)
697
698func fnv(h uint64, x byte) uint64 {
699	h ^= uint64(x)
700	h *= prime64
701	return h
702}
703
704func fnvString(h uint64, x string) uint64 {
705	for i := 0; i < len(x); i++ {
706		h ^= uint64(x[i])
707		h *= prime64
708	}
709	return h
710}
711
712func fnvUint64(h uint64, x uint64) uint64 {
713	for i := 0; i < 8; i++ {
714		h ^= x & 0xFF
715		x >>= 8
716		h *= prime64
717	}
718	return h
719}
720
721func fnvUint32(h uint64, x uint32) uint64 {
722	for i := 0; i < 4; i++ {
723		h ^= uint64(x & 0xFF)
724		x >>= 8
725		h *= prime64
726	}
727	return h
728}
729
730// A dedup is a deduplicator for call stacks, so that we only print
731// a report for new call stacks, not for call stacks we've already
732// reported.
733//
734// It has two modes: an approximate but lock-free mode that
735// may still emit some duplicates, and a precise mode that uses
736// a lock and never emits duplicates.
737type dedup struct {
738	// 128-entry 4-way, lossy cache for seenLossy
739	recent [128][4]uint64
740
741	// complete history for seen
742	mu sync.Mutex
743	m  map[uint64]bool
744}
745
746// seen records that h has now been seen and reports whether it was seen before.
747// When seen returns false, the caller is expected to print a report for h.
748func (d *dedup) seen(h uint64) bool {
749	d.mu.Lock()
750	if d.m == nil {
751		d.m = make(map[uint64]bool)
752	}
753	seen := d.m[h]
754	d.m[h] = true
755	d.mu.Unlock()
756	return seen
757}
758
759// seenLossy is a variant of seen that avoids a lock by using a cache of recently seen hashes.
760// Each cache entry is N-way set-associative: h can appear in any of the slots.
761// If h does not appear in any of them, then it is inserted into a random slot,
762// overwriting whatever was there before.
763func (d *dedup) seenLossy(h uint64) bool {
764	cache := &d.recent[uint(h)%uint(len(d.recent))]
765	for i := 0; i < len(cache); i++ {
766		if atomic.LoadUint64(&cache[i]) == h {
767			return true
768		}
769	}
770
771	// Compute index in set to evict as hash of current set.
772	ch := offset64
773	for _, x := range cache {
774		ch = fnvUint64(ch, x)
775	}
776	atomic.StoreUint64(&cache[uint(ch)%uint(len(cache))], h)
777	return false
778}
779