internal/godebug: add bisect support

CL 491875 introduces a new bisect command, which we plan to
document for use by end users to debug semantic changes in
the compiler and in GODEBUGs.

This CL adds bisect support to GODEBUGs, at least the ones
used via internal/godebug. Support for runtime-internal
GODEBUGs like panicnil will take a bit more work in followup CLs.

The new API in internal/bisect to support stack-based bisecting
should be easily reusable in non-GODEBUG settings as well,
once we finalize and export the API.

Change-Id: I6cf779c775329aceb3f3b2b2b2f221ce8a67deee
Reviewed-on: https://go-review.googlesource.com/c/go/+/491975
Reviewed-by: David Chase <drchase@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
This commit is contained in:
Russ Cox 2023-05-03 09:54:46 -04:00
parent 93897960cb
commit 81a31f5dd5
6 changed files with 399 additions and 80 deletions

View file

@ -29,10 +29,16 @@
// documentation for details.
package godebug
// Note: Be careful about new imports here. Any package
// that internal/godebug imports cannot itself import internal/godebug,
// meaning it cannot introduce a GODEBUG setting of its own.
// We keep imports to the absolute bare minimum.
import (
"internal/bisect"
"internal/godebugs"
"sync"
"sync/atomic"
"unsafe"
_ "unsafe" // go:linkname
)
@ -44,12 +50,17 @@ type Setting struct {
}
type setting struct {
value atomic.Pointer[string]
value atomic.Pointer[value]
nonDefaultOnce sync.Once
nonDefault atomic.Uint64
info *godebugs.Info
}
type value struct {
text string
bisect *bisect.Matcher
}
// New returns a new Setting for the $GODEBUG setting with the given name.
//
// GODEBUGs meant for use by end users must be listed in ../godebugs/table.go,
@ -114,7 +125,7 @@ func (s *Setting) register() {
// Once entered into the map, the name is never removed.
var cache sync.Map // name string -> value *atomic.Pointer[string]
var empty string
var empty value
// Value returns the current value for the GODEBUG setting s.
//
@ -130,7 +141,11 @@ func (s *Setting) Value() string {
panic("godebug: Value of name not listed in godebugs.All: " + s.name)
}
})
return *s.value.Load()
v := *s.value.Load()
if v.bisect != nil && !v.bisect.Stack(&stderr) {
return ""
}
return v.text
}
// lookup returns the unique *setting value for the given name.
@ -221,6 +236,9 @@ func update(def, env string) {
// Later settings override earlier ones.
// Parse only updates settings k=v for which did[k] = false.
// It also sets did[k] = true for settings that it updates.
// Each value v can also have the form v#pattern,
// in which case the GODEBUG is only enabled for call stacks
// matching pattern, for use with golang.org/x/tools/cmd/bisect.
func parse(did map[string]bool, s string) {
// Scan the string backward so that later settings are used
// and earlier settings are ignored.
@ -232,10 +250,18 @@ func parse(did map[string]bool, s string) {
for i := end - 1; i >= -1; i-- {
if i == -1 || s[i] == ',' {
if eq >= 0 {
name, value := s[i+1:eq], s[eq+1:end]
name, arg := s[i+1:eq], s[eq+1:end]
if !did[name] {
did[name] = true
lookup(name).value.Store(&value)
v := &value{text: arg}
for j := 0; j < len(arg); j++ {
if arg[j] == '#' {
v.text = arg[:j]
v.bisect, _ = bisect.New(arg[j+1:])
break
}
}
lookup(name).value.Store(v)
}
}
eq = -1
@ -245,3 +271,19 @@ func parse(did map[string]bool, s string) {
}
}
}
type runtimeStderr struct{}
var stderr runtimeStderr
func (*runtimeStderr) Write(b []byte) (int, error) {
if len(b) > 0 {
write(2, unsafe.Pointer(&b[0]), int32(len(b)))
}
return len(b), nil
}
// Since we cannot import os or syscall, use the runtime's write function
// to print to standard error.
//go:linkname write runtime.write
func write(fd uintptr, p unsafe.Pointer, n int32) int32