mirror of
https://github.com/burrowers/garble.git
synced 2026-04-22 15:47:04 +08:00
re-bundle the latest version of typeutil.Hasher
And document how we do this and why.
This commit is contained in:
@@ -105,6 +105,32 @@ the hash of the operation's inputs, and the last component is the *content ID*,
|
|||||||
the hash of the operation's output. For more, read
|
the hash of the operation's output. For more, read
|
||||||
[the docs in buildid.go](https://github.com/golang/go/blob/master/src/cmd/go/internal/work/buildid.go)
|
[the docs in buildid.go](https://github.com/golang/go/blob/master/src/cmd/go/internal/work/buildid.go)
|
||||||
|
|
||||||
|
### Bundled x/tools packages
|
||||||
|
|
||||||
|
`bundled_typeutil.go` and `bundled_typeparams.go` are modified copies of x/tools packages.
|
||||||
|
We need to modify [typeutil.Hasher](https://pkg.go.dev/golang.org/x/tools/go/types/typeutil#Hasher)
|
||||||
|
so that it doesn't include struct field tags in the hash algorithm,
|
||||||
|
given that struct field tags do not affect type identity.
|
||||||
|
|
||||||
|
The typeutil package imports the internal `typeparams` package, so we must bundle that too,
|
||||||
|
and replace the import with references to the bundled typeparams file.
|
||||||
|
|
||||||
|
Our patches are marked via `// NOTE(garble)` and are done by hand.
|
||||||
|
We also remove the `//go:generate` lines added by `bundle`
|
||||||
|
because re-running the tool will undo the manual edits.
|
||||||
|
|
||||||
|
Finally, we also remove any bundled API we don't need, that is,
|
||||||
|
anything which is not reachable from the bundled `typeutil_Hasher` type.
|
||||||
|
We also ensure that `staticcheck` does not report any unused code.
|
||||||
|
|
||||||
|
To update these files to a newer x/tools version to gain upstream fixes:
|
||||||
|
|
||||||
|
* `go get golang.org/x/tools@latest`
|
||||||
|
* `go tool bundle -o bundled_typeutil.go golang.org/x/tools/go/types/typeutil`
|
||||||
|
* `go tool bundle -o bundled_typeparams.go golang.org/x/tools/internal/typeparams`
|
||||||
|
* re-apply the changes mentioned above
|
||||||
|
* inspect the diff to ensure the changes look reasonable and we haven't lost any of our edits
|
||||||
|
|
||||||
### Benchmarking
|
### Benchmarking
|
||||||
|
|
||||||
A build benchmark is available, to be able to measure the cost of builing a
|
A build benchmark is available, to be able to measure the cost of builing a
|
||||||
|
|||||||
+34
-11
@@ -1,16 +1,20 @@
|
|||||||
// Originally bundled from golang.org/x/tools/internal/typeparams@v0.29.0,
|
// NOTE(garble): bundled as of golang.org/x/tools v0.42.0; see CONTRIBUTING.md.
|
||||||
// as it is used by x/tools/go/types/typeutil and is an internal package.
|
|
||||||
|
//lint:file-ignore ST1012 NOTE(garble): err var names get a new prefix
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errEmptyTypeSet = errors.New("empty type set")
|
const typeparams_debug = false
|
||||||
|
|
||||||
|
var typeparams_ErrEmptyTypeSet = errors.New("empty type set")
|
||||||
|
|
||||||
// InterfaceTermSet computes the normalized terms for a constraint interface,
|
// InterfaceTermSet computes the normalized terms for a constraint interface,
|
||||||
// returning an error if the term set cannot be computed or is empty. In the
|
// returning an error if the term set cannot be computed or is empty. In the
|
||||||
@@ -38,7 +42,7 @@ func typeparams_computeTermSet(typ types.Type) ([]*types.Term, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if tset.terms.isEmpty() {
|
if tset.terms.isEmpty() {
|
||||||
return nil, errEmptyTypeSet
|
return nil, typeparams_ErrEmptyTypeSet
|
||||||
}
|
}
|
||||||
if tset.terms.isAll() {
|
if tset.terms.isAll() {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -60,11 +64,26 @@ type typeparams_termSet struct {
|
|||||||
terms typeparams_termlist
|
terms typeparams_termlist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func typeparams_indentf(depth int, format string, args ...any) {
|
||||||
|
fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...)
|
||||||
|
}
|
||||||
|
|
||||||
func typeparams_computeTermSetInternal(t types.Type, seen map[types.Type]*typeparams_termSet, depth int) (res *typeparams_termSet, err error) {
|
func typeparams_computeTermSetInternal(t types.Type, seen map[types.Type]*typeparams_termSet, depth int) (res *typeparams_termSet, err error) {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
panic("nil type")
|
panic("nil type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if typeparams_debug {
|
||||||
|
typeparams_indentf(depth, "%s", t.String())
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
typeparams_indentf(depth, "=> %s", err)
|
||||||
|
} else {
|
||||||
|
typeparams_indentf(depth, "=> %s", res.terms.String())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
const maxTermCount = 100
|
const maxTermCount = 100
|
||||||
if tset, ok := seen[t]; ok {
|
if tset, ok := seen[t]; ok {
|
||||||
if !tset.complete {
|
if !tset.complete {
|
||||||
@@ -85,8 +104,7 @@ func typeparams_computeTermSetInternal(t types.Type, seen map[types.Type]*typepa
|
|||||||
// The term set of an interface is the intersection of the term sets of its
|
// The term set of an interface is the intersection of the term sets of its
|
||||||
// embedded types.
|
// embedded types.
|
||||||
tset.terms = typeparams_allTermlist
|
tset.terms = typeparams_allTermlist
|
||||||
for i := range u.NumEmbeddeds() {
|
for embedded := range u.EmbeddedTypes() {
|
||||||
embedded := u.EmbeddedType(i)
|
|
||||||
if _, ok := embedded.Underlying().(*types.TypeParam); ok {
|
if _, ok := embedded.Underlying().(*types.TypeParam); ok {
|
||||||
return nil, fmt.Errorf("invalid embedded type %T", embedded)
|
return nil, fmt.Errorf("invalid embedded type %T", embedded)
|
||||||
}
|
}
|
||||||
@@ -99,8 +117,7 @@ func typeparams_computeTermSetInternal(t types.Type, seen map[types.Type]*typepa
|
|||||||
case *types.Union:
|
case *types.Union:
|
||||||
// The term set of a union is the union of term sets of its terms.
|
// The term set of a union is the union of term sets of its terms.
|
||||||
tset.terms = nil
|
tset.terms = nil
|
||||||
for i := range u.Len() {
|
for t := range u.Terms() {
|
||||||
t := u.Term(i)
|
|
||||||
var terms typeparams_termlist
|
var terms typeparams_termlist
|
||||||
switch t.Type().Underlying().(type) {
|
switch t.Type().Underlying().(type) {
|
||||||
case *types.Interface:
|
case *types.Interface:
|
||||||
@@ -156,15 +173,18 @@ type typeparams_termlist []*typeparams_term
|
|||||||
// It is in normal form.
|
// It is in normal form.
|
||||||
var typeparams_allTermlist = typeparams_termlist{new(typeparams_term)}
|
var typeparams_allTermlist = typeparams_termlist{new(typeparams_term)}
|
||||||
|
|
||||||
|
// termSep is the separator used between individual terms.
|
||||||
|
const typeparams_termSep = " | "
|
||||||
|
|
||||||
// String prints the termlist exactly (without normalization).
|
// String prints the termlist exactly (without normalization).
|
||||||
func (xl typeparams_termlist) String() string {
|
func (xl typeparams_termlist) String() string {
|
||||||
if len(xl) == 0 {
|
if len(xl) == 0 {
|
||||||
return "∅"
|
return "∅"
|
||||||
}
|
}
|
||||||
var buf bytes.Buffer
|
var buf strings.Builder
|
||||||
for i, x := range xl {
|
for i, x := range xl {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
buf.WriteString(" | ")
|
buf.WriteString(typeparams_termSep)
|
||||||
}
|
}
|
||||||
buf.WriteString(x.String())
|
buf.WriteString(x.String())
|
||||||
}
|
}
|
||||||
@@ -342,6 +362,9 @@ func (x *typeparams_term) intersect(y *typeparams_term) *typeparams_term {
|
|||||||
// disjoint reports whether x ∩ y == ∅.
|
// disjoint reports whether x ∩ y == ∅.
|
||||||
// x.typ and y.typ must not be nil.
|
// x.typ and y.typ must not be nil.
|
||||||
func (x *typeparams_term) disjoint(y *typeparams_term) bool {
|
func (x *typeparams_term) disjoint(y *typeparams_term) bool {
|
||||||
|
if typeparams_debug && (x.typ == nil || y.typ == nil) {
|
||||||
|
panic("invalid argument(s)")
|
||||||
|
}
|
||||||
ux := x.typ
|
ux := x.typ
|
||||||
if y.tilde {
|
if y.tilde {
|
||||||
ux = typeparams_under(ux)
|
ux = typeparams_under(ux)
|
||||||
|
|||||||
+15
-23
@@ -1,12 +1,11 @@
|
|||||||
// Originally bundled from golang.org/x/tools/go/types/typeutil@v0.29.0.
|
// NOTE(garble): bundled as of golang.org/x/tools v0.42.0; see CONTRIBUTING.md.
|
||||||
// Edited to just keep the hasher API in place, removing the use of internal/typeparams,
|
|
||||||
// and removed the inclusion of struct field tags in the hasher.
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// -- Hasher --
|
// -- Hasher --
|
||||||
@@ -37,7 +36,7 @@ type typeutil_hasher struct{ inGenericSig bool }
|
|||||||
// hashString computes the Fowler–Noll–Vo hash of s.
|
// hashString computes the Fowler–Noll–Vo hash of s.
|
||||||
func typeutil_hashString(s string) uint32 {
|
func typeutil_hashString(s string) uint32 {
|
||||||
var h uint32
|
var h uint32
|
||||||
for i := range len(s) {
|
for i := 0; i < len(s); i++ {
|
||||||
h ^= uint32(s[i])
|
h ^= uint32(s[i])
|
||||||
h *= 16777619
|
h *= 16777619
|
||||||
}
|
}
|
||||||
@@ -67,7 +66,7 @@ func (h typeutil_hasher) hash(t types.Type) uint32 {
|
|||||||
if f.Anonymous() {
|
if f.Anonymous() {
|
||||||
hash += 8861
|
hash += 8861
|
||||||
}
|
}
|
||||||
// NOTE: we must not hash struct field tags, as they do not affect type identity.
|
// NOTE(garble): we must not hash struct field tags, as they do not affect type identity.
|
||||||
// hash += typeutil_hashString(t.Tag(i))
|
// hash += typeutil_hashString(t.Tag(i))
|
||||||
hash += typeutil_hashString(f.Name()) // (ignore f.Pkg)
|
hash += typeutil_hashString(f.Name()) // (ignore f.Pkg)
|
||||||
hash += h.hash(f.Type())
|
hash += h.hash(f.Type())
|
||||||
@@ -84,10 +83,13 @@ func (h typeutil_hasher) hash(t types.Type) uint32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tparams := t.TypeParams()
|
tparams := t.TypeParams()
|
||||||
for i := range tparams.Len() {
|
if n := tparams.Len(); n > 0 {
|
||||||
h.inGenericSig = true
|
h.inGenericSig = true // affects constraints, params, and results
|
||||||
tparam := tparams.At(i)
|
|
||||||
hash += 7 * h.hash(tparam.Constraint())
|
for i := range n {
|
||||||
|
tparam := tparams.At(i)
|
||||||
|
hash += 7 * h.hash(tparam.Constraint())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results())
|
return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results())
|
||||||
@@ -129,8 +131,7 @@ func (h typeutil_hasher) hash(t types.Type) uint32 {
|
|||||||
case *types.Named:
|
case *types.Named:
|
||||||
hash := h.hashTypeName(t.Obj())
|
hash := h.hashTypeName(t.Obj())
|
||||||
targs := t.TypeArgs()
|
targs := t.TypeArgs()
|
||||||
for i := range targs.Len() {
|
for targ := range targs.Types() {
|
||||||
targ := targs.At(i)
|
|
||||||
hash += 2 * h.hash(targ)
|
hash += 2 * h.hash(targ)
|
||||||
}
|
}
|
||||||
return hash
|
return hash
|
||||||
@@ -200,21 +201,12 @@ func (h typeutil_hasher) hashTypeParam(t *types.TypeParam) uint32 {
|
|||||||
|
|
||||||
// hashTypeName hashes the pointer of tname.
|
// hashTypeName hashes the pointer of tname.
|
||||||
func (typeutil_hasher) hashTypeName(tname *types.TypeName) uint32 {
|
func (typeutil_hasher) hashTypeName(tname *types.TypeName) uint32 {
|
||||||
// NOTE: we must not hash any pointers, as garble is a toolexec tool
|
// NOTE(garble): we must not hash any pointers,
|
||||||
// so by nature it uses multiple processes.
|
// as garble is a toolexec tool so by nature it uses multiple processes.
|
||||||
return typeutil_hashString(tname.Name())
|
return typeutil_hashString(tname.Name())
|
||||||
// Since types.Identical uses == to compare TypeNames,
|
// Since types.Identical uses == to compare TypeNames,
|
||||||
// the Hash function uses maphash.Comparable.
|
// the Hash function uses maphash.Comparable.
|
||||||
// TODO(adonovan): or will, when it becomes available in go1.24.
|
// hash := maphash.Comparable(typeutil_theSeed, tname)
|
||||||
// In the meantime we use the pointer's numeric value.
|
|
||||||
//
|
|
||||||
// hash := maphash.Comparable(theSeed, tname)
|
|
||||||
//
|
|
||||||
// (Another approach would be to hash the name and package
|
|
||||||
// path, and whether or not it is a package-level typename. It
|
|
||||||
// is rare for a package to define multiple local types with
|
|
||||||
// the same name.)
|
|
||||||
// hash := uintptr(unsafe.Pointer(tname))
|
|
||||||
// return uint32(hash ^ (hash >> 32))
|
// return uint32(hash ^ (hash >> 32))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user