Change-Id: Ibc7de639b31ed85c1270f5f9a889b4e6b4228be7
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2022-07-27 21:06:00 -07:00
parent ab60f28227
commit 4acfd07815
2 changed files with 97 additions and 2 deletions

View File

@ -424,11 +424,56 @@ func genHashPtrToMemoryRange(eleType reflect.Type) typeHasherFunc {
}
}
const debug = false
const debug = true
// canMaybeFastPath reports whether t is a type that we might be able to
// hash quickly at runtime with a typeHasherFunc.
//
// If it returns false, the slow path should be used directly.
func canMaybeFastPath(t reflect.Type) (ret bool) {
defer func() {
if !ret {
log.Printf("Can't on %v", t)
}
}()
ti := getTypeInfo(t)
if ti.isRecursive {
return false
}
switch t.Kind() {
case reflect.Map, reflect.Interface, reflect.Func, reflect.UnsafePointer, reflect.Chan:
return false
case reflect.Array:
return t.Size() == 0 || canMaybeFastPath(t.Elem())
case reflect.Slice, reflect.Pointer:
return canMaybeFastPath(t.Elem())
case reflect.Struct:
if t == timeTimeType || t.Implements(appenderToType) {
return true
}
for i, n := 0, t.NumField(); i < n; i++ {
sf := t.Field(i)
if sf.Type.Size() == 0 {
continue
}
if !canMaybeFastPath(sf.Type) {
return false
}
}
return true
}
return true
}
func genTypeHasher(t reflect.Type) typeHasherFunc {
fastable := canMaybeFastPath(t)
if debug {
log.Printf("generating func for %v", t)
log.Printf("generating func for %v; fastable=%v", t, fastable)
}
if !fastable {
return func(h *hasher, v reflect.Value) bool {
return false
}
}
switch t.Kind() {

View File

@ -317,6 +317,45 @@ type RecursiveStruct struct {
}
}
func TestCanMaybeFastPath(t *testing.T) {
type RecursiveStruct struct {
v *RecursiveStruct
}
type RecursiveChan chan *RecursiveChan
type FastStruct struct{ _, _ string }
tests := []struct {
val any
want bool
}{
{val: 42, want: true},
{val: "string", want: true},
{val: 1 + 2i, want: true},
{val: struct{}{}, want: true},
{val: (*RecursiveStruct)(nil), want: false},
{val: RecursiveStruct{}, want: false},
{val: FastStruct{}, want: true},
{val: time.Unix(0, 0), want: true},
{val: structs.Incomparable{}, want: true}, // ignore its [0]func()
{val: tailcfg.NetPortRange{}, want: true}, // uses structs.Incomparable
{val: (*tailcfg.Node)(nil), want: true},
{val: map[string]bool{}, want: false},
{val: func() {}, want: false},
{val: make(chan int), want: false},
{val: unsafe.Pointer(nil), want: false},
{val: make(RecursiveChan), want: false},
{val: make(chan int), want: false},
{val: tailcfg.SSHRule{}, want: false}, // contains a map
{val: (*tailcfg.SSHRule)(nil), want: false},
}
for _, tt := range tests {
got := canMaybeFastPath(reflect.TypeOf(tt.val))
if got != tt.want {
t.Errorf("for type %T: got %v, want %v", tt.val, got, tt.want)
}
}
}
type IntThenByte struct {
i int
b byte
@ -611,6 +650,17 @@ func TestGetTypeHasher(t *testing.T) {
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",
},
{
name: "no_hashing_for_mixed_fast_slow_type",
val: struct {
A, B string
M map[string]string
}{
"foo", "bar", map[string]string{"alice": "bob"},
},
want: false,
out: "", // no "foo" or "bar" included
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {