summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2022-06-14 22:49:11 -0700
committerBrad Fitzpatrick <brad@danga.com>2022-07-19 11:33:13 -0700
commit2a22ea3e831a699e89fe9aa379ac0f3fcb177be9 (patch)
tree91d1d037fb307015ad3f27238259e91c1dfa454d
parent4d0461f7215244d807a749e7cdd1bb81e607948c (diff)
downloadtailscale-2a22ea3e831a699e89fe9aa379ac0f3fcb177be9.tar.xz
tailscale-2a22ea3e831a699e89fe9aa379ac0f3fcb177be9.zip
util/deephash: generate type-specific hasher funcs
name old time/op new time/op delta Hash-8 71.1µs ± 2% 71.5µs ± 1% ~ (p=0.114 n=9+8) HashPacketFilter-8 8.39µs ± 1% 4.83µs ± 2% -42.38% (p=0.000 n=8+9) HashMapAcyclic-8 56.2µs ± 1% 56.9µs ± 2% +1.17% (p=0.035 n=10+9) TailcfgNode-8 6.49µs ± 2% 3.54µs ± 1% -45.37% (p=0.000 n=9+9) HashArray-8 729ns ± 2% 566ns ± 3% -22.30% (p=0.000 n=10+10) name old alloc/op new alloc/op delta Hash-8 24.0B ± 0% 24.0B ± 0% ~ (all equal) HashPacketFilter-8 24.0B ± 0% 24.0B ± 0% ~ (all equal) HashMapAcyclic-8 0.00B 0.00B ~ (all equal) TailcfgNode-8 0.00B 0.00B ~ (all equal) HashArray-8 0.00B 0.00B ~ (all equal) name old allocs/op new allocs/op delta Hash-8 1.00 ± 0% 1.00 ± 0% ~ (all equal) HashPacketFilter-8 1.00 ± 0% 1.00 ± 0% ~ (all equal) HashMapAcyclic-8 0.00 0.00 ~ (all equal) TailcfgNode-8 0.00 0.00 ~ (all equal) HashArray-8 0.00 0.00 ~ (all equal) Change-Id: I34c4e786e748fe60280646d40cc63a2adb2ea6fe Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--util/deephash/deephash.go434
-rw-r--r--util/deephash/deephash_test.go235
2 files changed, 667 insertions, 2 deletions
diff --git a/util/deephash/deephash.go b/util/deephash/deephash.go
index 4defffca1..82bb1a05e 100644
--- a/util/deephash/deephash.go
+++ b/util/deephash/deephash.go
@@ -26,6 +26,7 @@ import (
"encoding/hex"
"fmt"
"hash"
+ "log"
"math"
"reflect"
"sync"
@@ -142,6 +143,35 @@ func Hash(v any) (s Sum) {
return h.sum()
}
+// HasherForType is like Hash, but it returns a Hash func that's specialized for
+// the provided reflect type, avoiding a map lookup per value.
+func HasherForType[T any]() func(T) Sum {
+ var zeroT T
+ ti := getTypeInfo(reflect.TypeOf(zeroT))
+ seedOnce.Do(initSeed)
+
+ return func(v T) Sum {
+ h := hasherPool.Get().(*hasher)
+ defer hasherPool.Put(h)
+ h.reset()
+ h.hashUint64(seed)
+
+ rv := reflect.ValueOf(v)
+
+ if rv.IsValid() {
+ // Always treat the Hash input as an interface (it is), including hashing
+ // its type, otherwise two Hash calls of different types could hash to the
+ // same bytes off the different types and get equivalent Sum values. This is
+ // the same thing that we do for reflect.Kind Interface in hashValue, but
+ // the initial reflect.ValueOf from an interface value effectively strips
+ // the interface box off so we have to do it at the top level by hand.
+ h.hashType(rv.Type())
+ h.hashValueWithType(rv, ti, false)
+ }
+ return h.sum()
+ }
+}
+
// Update sets last to the hash of v and reports whether its value changed.
func Update(last *Sum, v ...any) (changed bool) {
sum := Hash(v)
@@ -170,14 +200,26 @@ func (h *hasher) hashUint32(i uint32) {
binary.LittleEndian.PutUint32(h.scratch[:4], i)
h.bw.Write(h.scratch[:4])
}
+func (h *hasher) hashLen(n int) {
+ binary.LittleEndian.PutUint64(h.scratch[:8], uint64(n))
+ h.bw.Write(h.scratch[:8])
+}
func (h *hasher) hashUint64(i uint64) {
binary.LittleEndian.PutUint64(h.scratch[:8], i)
h.bw.Write(h.scratch[:8])
}
-var uint8Type = reflect.TypeOf(byte(0))
+var (
+ uint8Type = reflect.TypeOf(byte(0))
+ timeTimeType = reflect.TypeOf(time.Time{})
+)
// typeInfo describes properties of a type.
+//
+// A non-nil typeInfo is populated into the typeHasher map
+// when its type is first requested, before its func is created.
+// Its func field fn is only populated once the type has been created.
+// This is used for recursive types.
type typeInfo struct {
rtype reflect.Type
canMemHash bool
@@ -190,11 +232,394 @@ type typeInfo struct {
// keyTypeInfo is the map key type's typeInfo.
// It's set when rtype is of Kind Map.
keyTypeInfo *typeInfo
+
+ hashFuncOnce sync.Once
+ hashFuncLazy typeHasherFunc // nil until created
}
+// returns ok if it was handled; else slow path runs
+type typeHasherFunc func(h *hasher, v reflect.Value) (ok bool)
+
var typeInfoMap sync.Map // map[reflect.Type]*typeInfo
var typeInfoMapPopulate sync.Mutex // just for adding to typeInfoMap
+func (ti *typeInfo) hasher() typeHasherFunc {
+ ti.hashFuncOnce.Do(ti.buildHashFuncOnce)
+ return ti.hashFuncLazy
+}
+
+func (ti *typeInfo) buildHashFuncOnce() {
+ ti.hashFuncLazy = genTypeHasher(ti.rtype)
+}
+
+func (h *hasher) hashBoolv(v reflect.Value) bool {
+ var b byte
+ if v.Bool() {
+ b = 1
+ }
+ h.hashUint8(b)
+ return true
+}
+
+func (h *hasher) hashUint8v(v reflect.Value) bool {
+ h.hashUint8(uint8(v.Uint()))
+ return true
+}
+
+func (h *hasher) hashInt8v(v reflect.Value) bool {
+ h.hashUint8(uint8(v.Int()))
+ return true
+}
+
+func (h *hasher) hashUint16v(v reflect.Value) bool {
+ h.hashUint16(uint16(v.Uint()))
+ return true
+}
+
+func (h *hasher) hashInt16v(v reflect.Value) bool {
+ h.hashUint16(uint16(v.Int()))
+ return true
+}
+
+func (h *hasher) hashUint32v(v reflect.Value) bool {
+ h.hashUint32(uint32(v.Uint()))
+ return true
+}
+
+func (h *hasher) hashInt32v(v reflect.Value) bool {
+ h.hashUint32(uint32(v.Int()))
+ return true
+}
+
+func (h *hasher) hashUint64v(v reflect.Value) bool {
+ h.hashUint64(v.Uint())
+ return true
+}
+
+func (h *hasher) hashInt64v(v reflect.Value) bool {
+ h.hashUint64(uint64(v.Int()))
+ return true
+}
+
+func hashStructAppenderTo(h *hasher, v reflect.Value) bool {
+ if !v.CanInterface() {
+ return false // slow path
+ }
+ var a appenderTo
+ if v.CanAddr() {
+ a = v.Addr().Interface().(appenderTo)
+ } else {
+ a = v.Interface().(appenderTo)
+ }
+ size := h.scratch[:8]
+ record := a.AppendTo(size)
+ binary.LittleEndian.PutUint64(record, uint64(len(record)-len(size)))
+ h.bw.Write(record)
+ return true
+}
+
+// hashPointerAppenderTo hashes v, a reflect.Ptr, that implements appenderTo.
+func hashPointerAppenderTo(h *hasher, v reflect.Value) bool {
+ if !v.CanInterface() {
+ return false // slow path
+ }
+ if v.IsNil() {
+ h.hashUint8(0) // indicates nil
+ return true
+ }
+ h.hashUint8(1) // indicates visiting a pointer
+ a := v.Interface().(appenderTo)
+ size := h.scratch[:8]
+ record := a.AppendTo(size)
+ binary.LittleEndian.PutUint64(record, uint64(len(record)-len(size)))
+ h.bw.Write(record)
+ return true
+}
+
+// fieldInfo describes a struct field.
+type fieldInfo struct {
+ index int // index of field for reflect.Value.Field(n)
+ typeInfo *typeInfo
+ canMemHash bool
+ offset uintptr // when we can memhash the field
+ size uintptr // when we can memhash the field
+}
+
+// mergeContiguousFieldsCopy returns a copy of f with contiguous memhashable fields
+// merged together. Such fields get a bogus index and fu value.
+func mergeContiguousFieldsCopy(in []fieldInfo) []fieldInfo {
+ ret := make([]fieldInfo, 0, len(in))
+ var last *fieldInfo
+ for _, f := range in {
+ // Combine two fields if they're both contiguous & memhash-able.
+ if f.canMemHash && last != nil && last.canMemHash && last.offset+last.size == f.offset {
+ last.size += f.size
+ last.index = -1
+ last.typeInfo = nil
+ } else {
+ ret = append(ret, f)
+ last = &ret[len(ret)-1]
+ }
+ }
+ return ret
+}
+
+// genHashStructFields generates a typeHasherFunc for t, which must be of kind Struct.
+func genHashStructFields(t reflect.Type) typeHasherFunc {
+ fields := make([]fieldInfo, 0, t.NumField())
+ for i, n := 0, t.NumField(); i < n; i++ {
+ sf := t.Field(i)
+ if sf.Type.Size() == 0 {
+ continue
+ }
+ fields = append(fields, fieldInfo{
+ index: i,
+ typeInfo: getTypeInfo(sf.Type),
+ canMemHash: canMemHash(sf.Type),
+ offset: sf.Offset,
+ size: sf.Type.Size(),
+ })
+ }
+ fieldsIfCanAddr := mergeContiguousFieldsCopy(fields)
+ return structHasher{fields, fieldsIfCanAddr}.hash
+}
+
+type structHasher struct {
+ fields, fieldsIfCanAddr []fieldInfo
+}
+
+func (sh structHasher) hash(h *hasher, v reflect.Value) bool {
+ var base unsafe.Pointer
+ if v.CanAddr() {
+ base = v.Addr().UnsafePointer()
+ for _, f := range sh.fieldsIfCanAddr {
+ if f.canMemHash {
+ h.bw.Write(unsafe.Slice((*byte)(unsafe.Pointer(uintptr(base)+f.offset)), f.size))
+ } else if !f.typeInfo.hasher()(h, v.Field(f.index)) {
+ return false
+ }
+ }
+ } else {
+ for _, f := range sh.fields {
+ if !f.typeInfo.hasher()(h, v.Field(f.index)) {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+// genHashPtrToMemoryRange returns a hasher where the reflect.Value is a Ptr to
+// the provided eleType.
+func genHashPtrToMemoryRange(eleType reflect.Type) typeHasherFunc {
+ size := eleType.Size()
+ return func(h *hasher, v reflect.Value) bool {
+ if v.IsNil() {
+ h.hashUint8(0) // indicates nil
+ } else {
+ h.hashUint8(1) // indicates visiting a pointer
+ h.bw.Write(unsafe.Slice((*byte)(v.UnsafePointer()), size))
+ }
+ return true
+ }
+}
+
+const debug = false
+
+func genTypeHasher(t reflect.Type) typeHasherFunc {
+ if debug {
+ log.Printf("generating func for %v", t)
+ }
+
+ switch t.Kind() {
+ case reflect.Bool:
+ return (*hasher).hashBoolv
+ case reflect.Int8:
+ return (*hasher).hashInt8v
+ case reflect.Int16:
+ return (*hasher).hashInt16v
+ case reflect.Int32:
+ return (*hasher).hashInt32v
+ case reflect.Int, reflect.Int64:
+ return (*hasher).hashInt64v
+ case reflect.Uint8:
+ return (*hasher).hashUint8v
+ case reflect.Uint16:
+ return (*hasher).hashUint16v
+ case reflect.Uint32:
+ return (*hasher).hashUint32v
+ case reflect.Uint, reflect.Uintptr, reflect.Uint64:
+ return (*hasher).hashUint64v
+ case reflect.Float32:
+ return (*hasher).hashFloat32v
+ case reflect.Float64:
+ return (*hasher).hashFloat64v
+ case reflect.Complex64:
+ return (*hasher).hashComplex64v
+ case reflect.Complex128:
+ return (*hasher).hashComplex128v
+ case reflect.String:
+ return (*hasher).hashString
+ case reflect.Slice:
+ et := t.Elem()
+ if canMemHash(et) {
+ return (*hasher).hashSliceMem
+ }
+ eti := getTypeInfo(et)
+ return genHashSliceElements(eti)
+ case reflect.Array:
+ et := t.Elem()
+ eti := getTypeInfo(et)
+ return genHashArray(t, eti)
+ case reflect.Struct:
+ if t == timeTimeType {
+ return (*hasher).hashTimev
+ }
+ if t.Implements(appenderToType) {
+ return hashStructAppenderTo
+ }
+ return genHashStructFields(t)
+ case reflect.Pointer:
+ et := t.Elem()
+ if canMemHash(et) {
+ return genHashPtrToMemoryRange(et)
+ }
+ if t.Implements(appenderToType) {
+ return hashPointerAppenderTo
+ }
+ if !typeIsRecursive(t) {
+ eti := getTypeInfo(et)
+ return func(h *hasher, v reflect.Value) bool {
+ if v.IsNil() {
+ h.hashUint8(0) // indicates nil
+ return true
+ }
+ h.hashUint8(1) // indicates visiting a pointer
+ return eti.hasher()(h, v.Elem())
+ }
+ }
+ }
+
+ return func(h *hasher, v reflect.Value) bool {
+ if debug {
+ log.Printf("unhandled type %v", v.Type())
+ }
+ return false
+ }
+}
+
+// hashString hashes v, of kind String.
+func (h *hasher) hashString(v reflect.Value) bool {
+ s := v.String()
+ h.hashLen(len(s))
+ h.bw.WriteString(s)
+ return true
+}
+
+func (h *hasher) hashFloat32v(v reflect.Value) bool {
+ h.hashUint32(math.Float32bits(float32(v.Float())))
+ return true
+}
+
+func (h *hasher) hashFloat64v(v reflect.Value) bool {
+ h.hashUint64(math.Float64bits(v.Float()))
+ return true
+}
+
+func (h *hasher) hashComplex64v(v reflect.Value) bool {
+ c := complex64(v.Complex())
+ h.hashUint32(math.Float32bits(real(c)))
+ h.hashUint32(math.Float32bits(imag(c)))
+ return true
+}
+
+func (h *hasher) hashComplex128v(v reflect.Value) bool {
+ c := v.Complex()
+ h.hashUint64(math.Float64bits(real(c)))
+ h.hashUint64(math.Float64bits(imag(c)))
+ return true
+}
+
+// hashString hashes v, of kind time.Time.
+func (h *hasher) hashTimev(v reflect.Value) bool {
+ var t time.Time
+ if v.CanAddr() {
+ t = *(v.Addr().Interface().(*time.Time))
+ } else {
+ t = v.Interface().(time.Time)
+ }
+ b := t.AppendFormat(h.scratch[:1], time.RFC3339Nano)
+ b[0] = byte(len(b) - 1) // more than sufficient width; if not, good enough.
+ h.bw.Write(b)
+ return true
+}
+
+// hashSliceMem hashes v, of kind Slice, with a memhash-able element type.
+func (h *hasher) hashSliceMem(v reflect.Value) bool {
+ vLen := v.Len()
+ h.hashUint64(uint64(vLen))
+ if vLen == 0 {
+ return true
+ }
+ h.bw.Write(unsafe.Slice((*byte)(v.UnsafePointer()), v.Type().Elem().Size()*uintptr(vLen)))
+ return true
+}
+
+func genHashArrayMem(n int, arraySize uintptr, efu *typeInfo) typeHasherFunc {
+ byElement := genHashArrayElements(n, efu)
+ return func(h *hasher, v reflect.Value) bool {
+ if v.CanAddr() {
+ h.bw.Write(unsafe.Slice((*byte)(v.Addr().UnsafePointer()), arraySize))
+ return true
+ }
+ return byElement(h, v)
+ }
+}
+
+func genHashArrayElements(n int, eti *typeInfo) typeHasherFunc {
+ return func(h *hasher, v reflect.Value) bool {
+ for i := 0; i < n; i++ {
+ if !eti.hasher()(h, v.Index(i)) {
+ return false
+ }
+ }
+ return true
+ }
+}
+
+func noopHasherFunc(h *hasher, v reflect.Value) bool { return true }
+
+func genHashArray(t reflect.Type, eti *typeInfo) typeHasherFunc {
+ if t.Size() == 0 {
+ return noopHasherFunc
+ }
+ et := t.Elem()
+ if canMemHash(et) {
+ return genHashArrayMem(t.Len(), t.Size(), eti)
+ }
+ n := t.Len()
+ return genHashArrayElements(n, eti)
+}
+
+func genHashSliceElements(eti *typeInfo) typeHasherFunc {
+ return sliceElementHasher{eti}.hash
+}
+
+type sliceElementHasher struct {
+ eti *typeInfo
+}
+
+func (seh sliceElementHasher) hash(h *hasher, v reflect.Value) bool {
+ vLen := v.Len()
+ h.hashUint64(uint64(vLen))
+ for i := 0; i < vLen; i++ {
+ if !seh.eti.hasher()(h, v.Index(i)) {
+ return false
+ }
+ }
+ return true
+}
+
func getTypeInfo(t reflect.Type) *typeInfo {
if f, ok := typeInfoMap.Load(t); ok {
return f.(*typeInfo)
@@ -353,6 +778,13 @@ func (h *hasher) hashValueWithType(v reflect.Value, ti *typeInfo, forceCycleChec
w := h.bw
doCheckCycles := forceCycleChecking || ti.isRecursive
+ if !doCheckCycles {
+ hf := ti.hasher()
+ if hf(h, v) {
+ return
+ }
+ }
+
// Generic handling.
switch v.Kind() {
default:
diff --git a/util/deephash/deephash_test.go b/util/deephash/deephash_test.go
index 643c42806..b30d69584 100644
--- a/util/deephash/deephash_test.go
+++ b/util/deephash/deephash_test.go
@@ -392,6 +392,238 @@ func TestCanMemHash(t *testing.T) {
}
}
+func TestGetTypeHasher(t *testing.T) {
+ switch runtime.GOARCH {
+ case "amd64", "arm64", "arm", "386", "riscv64":
+ default:
+ // Test outputs below are specifically for little-endian machines.
+ // Just skip everything else for now. Feel free to add more above if
+ // you have the hardware to test and it's little-endian.
+ t.Skipf("skipping on %v", runtime.GOARCH)
+ }
+ type typedString string
+ var (
+ someInt = int('A')
+ someComplex128 = complex128(1 + 2i)
+ someIP = netaddr.MustParseIP("1.2.3.4")
+ )
+ tests := []struct {
+ name string
+ val any
+ want bool // set true automatically if out != ""
+ out string
+ out32 string // overwrites out if 32-bit
+ }{
+ {
+ name: "int",
+ val: int(1),
+ out: "\x01\x00\x00\x00\x00\x00\x00\x00",
+ },
+ {
+ name: "int_negative",
+ val: int(-1),
+ out: "\xff\xff\xff\xff\xff\xff\xff\xff",
+ },
+ {
+ name: "int8",
+ val: int8(1),
+ out: "\x01",
+ },
+ {
+ name: "float64",
+ val: float64(1.0),
+ out: "\x00\x00\x00\x00\x00\x00\xf0?",
+ },
+ {
+ name: "float32",
+ val: float32(1.0),
+ out: "\x00\x00\x80?",
+ },
+ {
+ name: "string",
+ val: "foo",
+ out: "\x03\x00\x00\x00\x00\x00\x00\x00foo",
+ },
+ {
+ name: "typedString",
+ val: typedString("foo"),
+ out: "\x03\x00\x00\x00\x00\x00\x00\x00foo",
+ },
+ {
+ name: "string_slice",
+ val: []string{"foo", "bar"},
+ out: "\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00foo\x03\x00\x00\x00\x00\x00\x00\x00bar",
+ },
+ {
+ name: "int_slice",
+ val: []int{1, 0, -1},
+ out: "\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff",
+ out32: "\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff",
+ },
+ {
+ name: "struct",
+ val: struct {
+ a, b int
+ c uint16
+ }{1, -1, 2},
+ out: "\x01\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x02\x00",
+ },
+ {
+ name: "nil_int_ptr",
+ val: (*int)(nil),
+ out: "\x00",
+ },
+ {
+ name: "int_ptr",
+ val: &someInt,
+ out: "\x01A\x00\x00\x00\x00\x00\x00\x00",
+ out32: "\x01A\x00\x00\x00",
+ },
+ {
+ name: "nil_uint32_ptr",
+ val: (*uint32)(nil),
+ out: "\x00",
+ },
+ {
+ name: "complex128_ptr",
+ val: &someComplex128,
+ out: "\x01\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@",
+ },
+ {
+ name: "packet_filter",
+ val: filterRules,
+ out: "\x04\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00*\v\x00\x00\x00\x00\x00\x00\x0010.1.3.4/32\v\x00\x00\x00\x00\x00\x00\x0010.0.0.0/24\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x001.2.3.4/32\x01 \x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x001.2.3.4/32\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00foo\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ out32: "\x04\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00*\v\x00\x00\x00\x00\x00\x00\x0010.1.3.4/32\v\x00\x00\x00\x00\x00\x00\x0010.0.0.0/24\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x001.2.3.4/32\x01 \x00\x00\x00\x01\x00\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x001.2.3.4/32\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00foo\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ },
+ {
+ name: "netaddr.IP",
+ val: netaddr.MustParseIP("fe80::123%foo"),
+ out: "\r\x00\x00\x00\x00\x00\x00\x00fe80::123%foo",
+ },
+ {
+ name: "ptr-netaddr.IP",
+ val: &someIP,
+ out: "\x01\a\x00\x00\x00\x00\x00\x00\x001.2.3.4",
+ },
+ {
+ name: "ptr-nil-netaddr.IP",
+ val: (*netaddr.IP)(nil),
+ out: "\x00",
+ },
+ {
+ name: "time",
+ val: time.Unix(0, 0).In(time.UTC),
+ out: "\x141970-01-01T00:00:00Z",
+ },
+ {
+ name: "time_custom_zone",
+ val: time.Unix(1655311822, 0).In(time.FixedZone("FOO", -60*60)),
+ out: "\x192022-06-15T15:50:22-01:00",
+ },
+ {
+ name: "time_nil",
+ val: (*time.Time)(nil),
+ out: "\x00",
+ },
+ {
+ name: "array_memhash",
+ val: [4]byte{1, 2, 3, 4},
+ out: "\x01\x02\x03\x04",
+ },
+ {
+ name: "array_ptr_memhash",
+ val: ptrTo([4]byte{1, 2, 3, 4}),
+ out: "\x01\x01\x02\x03\x04",
+ },
+ {
+ name: "ptr_to_struct_partially_memhashable",
+ val: &struct {
+ A int16
+ B int16
+ C *int
+ }{5, 6, nil},
+ out: "\x01\x05\x00\x06\x00\x00",
+ },
+ {
+ name: "struct_partially_memhashable_but_cant_addr",
+ val: struct {
+ A int16
+ B int16
+ C *int
+ }{5, 6, nil},
+ out: "\x05\x00\x06\x00\x00",
+ },
+ {
+ name: "array_elements",
+ val: [4]byte{1, 2, 3, 4},
+ out: "\x01\x02\x03\x04",
+ },
+ {
+ name: "bool",
+ val: true,
+ out: "\x01",
+ },
+ {
+ name: "IntIntByteInt",
+ val: IntIntByteInt{1, 2, 3, 4},
+ out: "\x01\x00\x00\x00\x02\x00\x00\x00\x03\x04\x00\x00\x00",
+ },
+ {
+ name: "IntIntByteInt-canddr",
+ val: &IntIntByteInt{1, 2, 3, 4},
+ out: "\x01\x01\x00\x00\x00\x02\x00\x00\x00\x03\x04\x00\x00\x00",
+ },
+ {
+ name: "array-IntIntByteInt",
+ val: [2]IntIntByteInt{
+ {1, 2, 3, 4},
+ {5, 6, 7, 8},
+ },
+ out: "\x01\x00\x00\x00\x02\x00\x00\x00\x03\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00\a\b\x00\x00\x00",
+ },
+ {
+ name: "array-IntIntByteInt-canaddr",
+ val: &[2]IntIntByteInt{
+ {1, 2, 3, 4},
+ {5, 6, 7, 8},
+ },
+ out: "\x01\x01\x00\x00\x00\x02\x00\x00\x00\x03\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00\a\b\x00\x00\x00",
+ },
+ {
+ name: "tailcfg.Node",
+ val: &tailcfg.Node{},
+ out: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x140001-01-01T00:00:00Z\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x140001-01-01T00:00:00Z\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ rv := reflect.ValueOf(tt.val)
+ fn := getTypeInfo(rv.Type()).hasher()
+ var buf bytes.Buffer
+ h := &hasher{
+ bw: bufio.NewWriter(&buf),
+ }
+ got := fn(h, rv)
+ const ptrSize = 32 << uintptr(^uintptr(0)>>63)
+ if tt.out32 != "" && ptrSize == 32 {
+ tt.out = tt.out32
+ }
+ if tt.out != "" {
+ tt.want = true
+ }
+ if got != tt.want {
+ t.Fatalf("func returned %v; want %v", got, tt.want)
+ }
+ if err := h.bw.Flush(); err != nil {
+ t.Fatal(err)
+ }
+ if got := buf.String(); got != tt.out {
+ t.Fatalf("got %q; want %q", got, tt.out)
+ }
+ })
+ }
+}
+
var sink = Hash("foo")
func BenchmarkHash(b *testing.B) {
@@ -448,8 +680,9 @@ var filterRules = []tailcfg.FilterRule{
func BenchmarkHashPacketFilter(b *testing.B) {
b.ReportAllocs()
+ hash := HasherForType[[]tailcfg.FilterRule]()
for i := 0; i < b.N; i++ {
- sink = Hash(filterRules)
+ sink = hash(filterRules)
}
}